Skip to content

Instantly share code, notes, and snippets.

@balsoft
Last active August 24, 2021 21:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save balsoft/83ce6fdd8679471f591a03f7da359a37 to your computer and use it in GitHub Desktop.
Save balsoft/83ce6fdd8679471f591a03f7da359a37 to your computer and use it in GitHub Desktop.
Build crates with nix
{ 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;
}
{ 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