b4379cf745
Package files are parsed to determine the dependency package dependencies, and these are used to find the packages to download which might be required. This doesn't implement every detail of the opkg dependency resolution, but instead tries to find all packages which might be required. In particular, no attempt is made to resolve dependencies on virtual packages (which may be provided by multiple packages) to a single package (all providing packages are downloaded).
205 lines
5.8 KiB
Nix
205 lines
5.8 KiB
Nix
{ pkgs ? import <nixpkgs> {}
|
|
# OpenWRT release
|
|
, release ? "21.02.3"
|
|
# OpenWRT target
|
|
, target
|
|
, variant ? "generic"
|
|
# Checksum of the `sha256sums` file
|
|
, sha256
|
|
# Checksum of a feed's `Packages` file
|
|
, feedsSha256
|
|
# Manually specify packages' arch for OpenWRT<19 releases without profiles.json
|
|
, packagesArch ? null
|
|
}:
|
|
let
|
|
inherit (pkgs) lib fetchurl;
|
|
|
|
sanitizeFilename = fileName:
|
|
builtins.replaceStrings [ "~" ] [ "-" ] (
|
|
builtins.baseNameOf fileName
|
|
);
|
|
|
|
fetchSums = url: sha256:
|
|
let
|
|
sumsFile = fetchurl {
|
|
url = "${url}/sha256sums";
|
|
inherit sha256;
|
|
};
|
|
|
|
filesSha256 =
|
|
builtins.foldl' (filesSha256: line:
|
|
let
|
|
m = builtins.match "([0-9a-f]+) \\*(.+)" line;
|
|
in
|
|
if builtins.isList m && builtins.length m == 2
|
|
then filesSha256 // {
|
|
${builtins.elemAt m 1} = builtins.elemAt m 0;
|
|
} else filesSha256
|
|
) {} (lib.splitString "\n" (builtins.readFile sumsFile));
|
|
|
|
in
|
|
builtins.mapAttrs (file: sha256:
|
|
fetchurl {
|
|
url = "${url}/${file}";
|
|
name = sanitizeFilename file;
|
|
inherit sha256;
|
|
}
|
|
) filesSha256;
|
|
|
|
parsePackages = url: packagesContent:
|
|
let
|
|
parsedRaw = map (section:
|
|
builtins.foldl' (data: line:
|
|
let
|
|
m = builtins.match "(.+): (.+)" line;
|
|
in
|
|
if builtins.isList m && builtins.length m == 2
|
|
then data // {
|
|
${builtins.elemAt m 0} = builtins.elemAt m 1;
|
|
} else data
|
|
) {} (lib.splitString "\n" section)
|
|
) (lib.splitString "\n\n" packagesContent);
|
|
|
|
parseDepends = depStr:
|
|
map (dep: builtins.elemAt (lib.splitString " " dep) 0)
|
|
(lib.splitString ", " depStr);
|
|
in
|
|
builtins.foldl'
|
|
(variantFiles: parsed:
|
|
if parsed ? Filename && parsed ? SHA256sum
|
|
then
|
|
variantFiles // {
|
|
${parsed.Package} = {
|
|
filename = parsed.Filename;
|
|
file = fetchurl {
|
|
url = "${url}/${parsed.Filename}";
|
|
sha256 = parsed.SHA256sum;
|
|
name = sanitizeFilename parsed.Filename;
|
|
};
|
|
depends = if parsed ? Depends then parseDepends parsed.Depends else [];
|
|
provides = if parsed ? Provides then parsed.Provides else null;
|
|
type = "real";
|
|
};
|
|
}
|
|
else
|
|
variantFiles
|
|
) {} parsedRaw;
|
|
|
|
baseUrl = "https://downloads.openwrt.org/releases/${release}";
|
|
|
|
variantFiles = fetchSums "${baseUrl}/targets/${target}/${variant}" sha256;
|
|
|
|
feedsPackagesFile = builtins.mapAttrs (feed: sha256:
|
|
fetchurl {
|
|
url = "${baseUrl}/packages/${arch}/${feed}/Packages";
|
|
inherit sha256;
|
|
}
|
|
) feedsSha256;
|
|
|
|
packagesByFeed = builtins.mapAttrs (feed: packagesFile:
|
|
parsePackages "${baseUrl}/packages/${arch}/${feed}" (builtins.readFile packagesFile)
|
|
) feedsPackagesFile;
|
|
|
|
corePackages =
|
|
parsePackages
|
|
"${baseUrl}/targets/${target}/${variant}/packages"
|
|
(builtins.readFile variantFiles."packages/Packages");
|
|
|
|
realPackages =
|
|
(builtins.foldl' (a: b: a // b) { } (builtins.attrValues packagesByFeed))
|
|
// corePackages;
|
|
|
|
# for each package that 'provides' something, register that package as a
|
|
# dependency of the provided package. if there is no real package with that
|
|
# name, then a 'virtual' one is created. Using this, when a real package
|
|
# depends on something provided by several real packages, all possible
|
|
# providers will be downloaded
|
|
addVirtual = packages:
|
|
builtins.foldl'
|
|
(packages: pn:
|
|
let
|
|
p = packages.${pn};
|
|
in
|
|
if p.provides != null
|
|
then
|
|
let
|
|
vp = if packages ? ${p.provides} then packages.${p.provides} else {
|
|
type = "virtual";
|
|
depends = [ ];
|
|
};
|
|
in
|
|
packages // {
|
|
${p.provides} = vp // { depends = vp.depends ++ [ pn ]; };
|
|
}
|
|
else
|
|
packages
|
|
)
|
|
packages
|
|
(builtins.attrNames packages);
|
|
|
|
# packages which aren't available in feeds but are provided by imagebuilders
|
|
dummyPackages =
|
|
let
|
|
dummyPackage = { depends = [ ]; provides = null; type = "dummy"; };
|
|
in
|
|
{
|
|
libc = dummyPackage;
|
|
kernel = dummyPackage;
|
|
};
|
|
|
|
# all packages, including dummy and virtual
|
|
allPackages = addVirtual (realPackages // dummyPackages);
|
|
|
|
# remove package names starting with '-' from deps
|
|
#
|
|
# this should be used on the dependencies before expanding their
|
|
# requirements, and assumes that '-' deps come after the non-'-' version in
|
|
# the list
|
|
applyMinusDeps = deps:
|
|
builtins.foldl'
|
|
(deps: dep:
|
|
if lib.hasPrefix "-" dep
|
|
then
|
|
lib.remove (lib.removePrefix "-" dep) deps
|
|
else
|
|
deps ++ [ dep ]
|
|
) [ ]
|
|
deps;
|
|
|
|
# given a package set and a list of package names to install (including names
|
|
# starting with - to be removed), find all possible required package names
|
|
expandDeps = packages:
|
|
let
|
|
addDep = current_deps: new_dep:
|
|
if builtins.hasAttr new_dep current_deps
|
|
then
|
|
current_deps
|
|
else
|
|
let
|
|
with_new_dep = current_deps // { ${new_dep} = true; };
|
|
deps = packages.${new_dep}.depends;
|
|
in
|
|
builtins.foldl' addDep with_new_dep deps;
|
|
in
|
|
deps: builtins.attrNames (builtins.foldl' addDep { } (applyMinusDeps deps));
|
|
|
|
profiles =
|
|
if variantFiles ? "profiles.json"
|
|
then builtins.fromJSON (
|
|
builtins.readFile (
|
|
variantFiles."profiles.json"
|
|
)
|
|
)
|
|
else null;
|
|
|
|
arch = if packagesArch == null
|
|
then profiles.arch_packages
|
|
else packagesArch;
|
|
|
|
in {
|
|
inherit allPackages;
|
|
inherit expandDeps;
|
|
inherit variantFiles;
|
|
inherit profiles arch;
|
|
}
|