Last active
August 24, 2021 21:40
-
-
Save balsoft/83ce6fdd8679471f591a03f7da359a37 to your computer and use it in GitHub Desktop.
Build crates with nix
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ stdenv, buildRustCrate, fetchurl, lib, defaultCrateOverrides }: | |
{ src, overrides ? { }, features ? [ "default" ] | |
, builtin ? [ "core" "compiler_builtins" ], cargoToml ? src + "/Cargo.toml" | |
, cargoLock ? src + "/Cargo.lock", local ? true }: | |
let | |
project = builtins.fromTOML (builtins.readFile cargoToml); | |
projectLock = builtins.fromTOML (builtins.readFile cargoLock); | |
packages = builtins.foldl' (a: n: | |
a // { | |
${n.name} = (a.${n.name} or { }) // { | |
any = if a ? ${n.name} then | |
throw | |
"Cannot choose 'any' version of package ${n.name} when there are multiple" | |
else | |
n; | |
${n.version} = n; | |
}; | |
}) { } projectLock.package; | |
parsePackageId = pid: | |
let m = builtins.match "([A-Za-z0-9_-]*) ([A-Za-z0-9_.-]*)(.*)?" pid; | |
in if isNull m then { | |
name = pid; | |
value = "any"; | |
} else { | |
name = builtins.elemAt m 0; | |
value = builtins.elemAt m 1; | |
}; | |
buildDep = { name, version ? "any", lock ? packages.${name}.${version}, features ? [ ], local ? false | |
, src ? fetchurl { | |
name = "${lock.name}-${lock.version}.tar.gz"; | |
url = | |
"https://static.crates.io/crates/${lock.name}/${lock.name}-${lock.version}.crate"; | |
sha256 = lock.checksum; | |
} }: | |
let | |
toml = stdenv.mkDerivation { | |
name = "Cargo.toml"; | |
inherit src; | |
phases = [ "unpackPhase" "buildPhase" ]; | |
buildPhase = "cp Cargo.toml $out"; | |
preferLocalBuild = true; | |
}; | |
desc = builtins.fromTOML (builtins.readFile toml); | |
isIn = name: d: | |
builtins.any (x: x) (builtins.attrValues | |
(builtins.mapAttrs (dep: value: name == value.package or dep) d)); | |
isDep = name: | |
builtins.any (isIn name) [ | |
desc.dependencies or { } | |
desc.dev-dependencies or { } | |
desc.build-dependencies or { } | |
]; | |
recurseIntoFeatures = builtins.concatMap (feat: | |
let m = builtins.match "([a-zA-Z0-9_-]*)/([a-zA-Z0-9_-]*)" feat; | |
in if isNull m then | |
if isDep feat then [{ # Dependency | |
dependency = feat; | |
}] else if (desc.features or { }) ? ${feat} then | |
([{ # Feature of this package defined in Cargo.toml | |
package = lock.name; | |
feature = feat; | |
}] ++ recurseIntoFeatures desc.features.${feat}) | |
else if feat == "default" then [{ | |
package = lock.name; | |
feature = feat; | |
}] else | |
throw | |
"${feat} is not a dependency, feature or feature of a dependency of ${name} (${ | |
builtins.toJSON desc | |
})" | |
else [ | |
{ # Feature of a dependency | |
package = builtins.elemAt m 0; | |
feature = builtins.elemAt m 1; | |
} | |
{ dependency = builtins.elemAt m 0; } | |
]); | |
enabledFeatures = recurseIntoFeatures features; | |
packageFeats = pack: | |
map ({ package, feature }: feature) | |
(builtins.filter (item: item ? package && item.package == pack) | |
enabledFeatures); | |
depsRequiredByFeatures = map ({ dependency }: dependency) | |
(builtins.filter (item: item ? dependency) enabledFeatures); | |
lockedDeps = builtins.listToAttrs (map parsePackageId lock.dependencies); | |
deps = d: | |
let | |
isActualDep = item: | |
((!(d.${item} ? optional && d.${item}.optional) | |
|| builtins.elem item | |
depsRequiredByFeatures) # If dep is optional, it must be required by a feature | |
&& !builtins.elem item builtin); # If dep is builtin, ignore it | |
actualDeps = builtins.filter isActualDep (builtins.attrNames d); | |
feats = name: | |
(d.${name}.features or [ ]) ++ packageFeats name | |
++ lib.optional (d.${name}.default-features or true) "default"; | |
in map (name: | |
(buildDep ( | |
# (builtins.trace "${pid}" lib.traceValSeq) | |
{ | |
inherit name; | |
version = lockedDeps.${name}; | |
features = feats name; | |
} // lib.optionalAttrs (local && d.${name} ? path) { | |
src = src + "/${d.${name}.path}"; | |
}))) actualDeps; | |
buildRustCrate' = buildRustCrate.override { | |
defaultCrateOverrides = defaultCrateOverrides // overrides; | |
}; | |
in buildRustCrate' rec { | |
crateName = desc.package.name; | |
inherit (desc.package) version; | |
inherit src; | |
procMacro = desc.lib.proc_macro or false || desc.lib.proc-macro or false; | |
features = packageFeats desc.package.name; | |
dependencies = deps desc.dependencies or { }; | |
buildDependencies = deps desc.build-dependencies or { }; | |
}; | |
in buildDep { | |
inherit (project.package) name version; | |
inherit src features local; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ callCrate }: | |
{ src, cargoToml ? src + "/Cargo.toml", cargoLock ? src + "/Cargo.lock" | |
, overrides ? { } }: | |
let | |
workspace = builtins.fromTOML (builtins.readFile cargoToml); | |
memberPackage = path: | |
callCrate { | |
src = src + "/${path}"; # We use this src because packages in a workspace can depend on one another | |
inherit cargoLock; # Workspace members don't have lockfiles | |
inherit overrides; | |
}; | |
memberPackages = builtins.listToAttrs (map (pkg: { | |
name = pkg.crateName; | |
value = pkg; | |
}) (map memberPackage workspace.workspace.members)); | |
in memberPackages |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment