Merge pull request #67 from yusdacra/fix/build-rust-package

fix(rust): handle git source vendoring properly
This commit is contained in:
DavHau 2022-01-16 16:26:48 +01:00 committed by GitHub
commit dcdf2f27df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 139 additions and 44 deletions

View File

@ -12,6 +12,7 @@
getCyclicDependencies,
getDependencies,
getSource,
getSourceSpec,
produceDerivation,
...
@ -30,40 +31,99 @@ let
direct ++ (l.map (dep: getAllTransitiveDependencies dep.name dep.version) direct)
));
# TODO: implement a user option that will make the vendoring
# copy sources instead of symlinking them. This can be useful
# for some Rust packages that modify their own dependencies
# via their build hooks.
vendorPackageDependencies = pname: version:
let
deps = getAllTransitiveDependencies pname version;
makeSource = dep: {
name = "${dep.name}-${dep.version}";
makeSource = dep:
let
path = getSource dep.name dep.version;
isGit = (getSourceSpec dep.name dep.version).type == "git";
in {
inherit path isGit dep;
name = "${dep.name}-${dep.version}";
};
sources = l.map makeSource deps;
findCrateSource = source:
let
inherit (pkgs) cargo jq;
pkg = source.dep;
in ''
# If the target package is in a workspace, or if it's the top-level
# crate, we should find the crate path using `cargo metadata`.
crateCargoTOML=$(${cargo}/bin/cargo metadata --format-version 1 --no-deps --manifest-path $tree/Cargo.toml | \
${jq}/bin/jq -r '.packages[] | select(.name == "${pkg.name}") | .manifest_path')
# If the repository is not a workspace the package might be in a subdirectory.
if [[ -z $crateCargoTOML ]]; then
for manifest in $(find $tree -name "Cargo.toml"); do
echo Looking at $manifest
crateCargoTOML=$(${cargo}/bin/cargo metadata --format-version 1 --no-deps --manifest-path "$manifest" | ${jq}/bin/jq -r '.packages[] | select(.name == "${pkg.name}") | .manifest_path' || :)
if [[ ! -z $crateCargoTOML ]]; then
break
fi
done
if [[ -z $crateCargoTOML ]]; then
>&2 echo "Cannot find path for crate '${pkg.name}-${pkg.version}' in the tree in: $tree"
exit 1
fi
fi
echo Found crate ${pkg.name} at $crateCargoTOML
tree="$(dirname $crateCargoTOML)"
'';
makeScript = source:
''
tree="${source.path}"
${l.optionalString source.isGit (findCrateSource source)}
cp -prvd "$tree" $out/${source.name}
chmod u+w $out/${source.name}
${l.optionalString source.isGit "printf '{\"files\":{},\"package\":null}' > \"$out/${source.name}/.cargo-checksum.json\""}
'';
in
pkgs.runCommand "vendor-${pname}-${version}" {} ''
mkdir -p $out
${
l.concatMapStringsSep "\n"
(source: "ln -s ${source.path} $out/${source.name}")
makeScript
sources
}
'';
# Generates a shell script that writes git vendor entries to .cargo/config.
writeGitVendorEntries =
let
makeEntry = source:
''
[source."${source.url}"]
replace-with = "vendored-sources"
git = "${source.url}"
${l.optionalString (source ? type) "${source.type} = \"${source.value}\""}
'';
entries = l.map makeEntry subsystemAttrs.gitSources;
in ''
cat >> ../.cargo/config <<EOF
${l.concatStringsSep "\n" entries}
EOF
'';
buildPackage = pname: version:
let src = getSource pname version; in
let
src = getSource pname version;
vendorDir = vendorPackageDependencies pname version;
in
produceDerivation pname (pkgs.rustPlatform.buildRustPackage {
inherit pname version src;
postUnpack = ''
ln -s ${vendorPackageDependencies pname version} ./${src.name}/nix-vendor
ln -s ${vendorDir} ./nix-vendor
'';
cargoVendorDir = "nix-vendor";
cargoVendorDir = "../nix-vendor";
preBuild = ''
${writeGitVendorEntries}
'';
});
in
rec {

View File

@ -302,6 +302,7 @@ let
inherit (dreamLockInterface)
subsystemAttrs
getSourceSpec
getDependencies
getCyclicDependencies
mainPackageName

View File

@ -2,6 +2,20 @@
"_subsystem": {
"packages": [
{ "name": "ripgrep", "version": "13.0.0" }
],
"gitSources": [
{
"url": "https://github.com/rust-random/rand.git",
"sha": "19404d68764ed08513131f82157e2ccad69dcf83",
"type": "branch",
"value": "master"
},
{
"url": "https://github.com/rust-random/rand.git",
"sha": "074cb6a997f91db653f8a53c755ddc07495ab429",
"type": "tag",
"value": "0.7.3"
}
]
}
}

View File

@ -24,17 +24,19 @@
(n: v: if v == "directory" then recurseFiles "${path}/${n}" else "${path}/${n}")
(l.readDir path)
);
getAllFiles = dirs: l.flatten (l.map recurseFiles dirs);
getCargoTomlPaths = l.filter (path: l.baseNameOf path == "Cargo.toml");
getCargoTomls = l.map (path: { inherit path; value = l.fromTOML (l.readFile path); });
getCargoPackages = l.filter (toml: l.hasAttrByPath [ "package" "name" ] toml.value);
# Find all Cargo.toml files and parse them
allFiles = l.flatten (l.map recurseFiles inputDirectories);
cargoTomlPaths = l.filter (path: l.baseNameOf path == "Cargo.toml") allFiles;
cargoTomls = l.map (path: { inherit path; value = l.fromTOML (l.readFile path); }) cargoTomlPaths;
allFiles = getAllFiles inputDirectories;
cargoTomlPaths = getCargoTomlPaths allFiles;
cargoTomls = getCargoTomls cargoTomlPaths;
# Filter cargo-tomls to for files that actually contain packages
cargoPackages =
l.filter
(toml: l.hasAttrByPath [ "package" "name" ] toml.value)
cargoTomls;
cargoPackages = getCargoPackages cargoTomls;
packageName =
if args.packageName == "{automatic}"
@ -101,6 +103,33 @@
name = toml.package.name;
version = toml.package.version or (l.warn "no version found in Cargo.toml for ${name}, defaulting to unknown" "unknown");
};
# Parses a git source, taken straight from nixpkgs.
parseGitSource = src:
let
parts = builtins.match ''git\+([^?]+)(\?(rev|tag|branch)=(.*))?#(.*)'' src;
type = builtins.elemAt parts 2; # rev, tag or branch
value = builtins.elemAt parts 3;
in
if parts == null then null
else {
url = builtins.elemAt parts 0;
sha = builtins.elemAt parts 4;
} // lib.optionalAttrs (type != null) { inherit type value; };
# Extracts a source type from a dependency.
getSourceTypeFrom = dependencyObject:
let checkType = type: l.hasPrefix "${type}+" dependencyObject.source; in
if !(l.hasAttr "source" dependencyObject)
then "path"
else if checkType "git" then
"git"
else if checkType "registry" then
if dependencyObject.source == "registry+https://github.com/rust-lang/crates.io-index"
then "crates-io"
else throw "registries other than crates.io are not supported yet"
else
throw "unknown or unsupported source type: ${dependencyObject.source}";
in
utils.simpleTranslate
@ -140,8 +169,11 @@
# Extract subsystem specific attributes.
# The structure of this should be defined in:
# ./src/specifications/{subsystem}
subsystemAttrs = {
subsystemAttrs = rec {
packages = l.map (toml: { inherit (toml.value.package) name version; }) cargoPackages;
gitSources = let
gitDeps = l.filter (dep: (getSourceTypeFrom dep) == "git") parsedDeps;
in l.unique (l.map (dep: parseGitSource dep.source) gitDeps);
};
# FUNCTIONS
@ -160,18 +192,7 @@
l.map makeDepNameVersion (dependencyObject.dependencies or [ ]);
# return the source type of a package object
getSourceType = dependencyObject:
let checkType = type: l.hasPrefix "${type}+" dependencyObject.source; in
if !(l.hasAttr "source" dependencyObject)
then "path"
else if checkType "git" then
"git"
else if checkType "registry" then
if dependencyObject.source == "registry+https://github.com/rust-lang/crates.io-index"
then "crates-io"
else throw "registries other than crates.io are not supported yet"
else
throw "unknown or unsupported source type: ${dependencyObject.source}";
getSourceType = getSourceTypeFrom;
# An attrset of constructor functions.
# Given a dependency object and a source type, construct the
@ -180,12 +201,12 @@
path = dependencyObject:
let
findCratePath = name:
l.dirOf (
l.baseNameOf (l.dirOf (
l.findFirst
(toml: toml.value.package.name == name)
(throw "could not find crate ${name}")
cargoPackages
).path;
).path);
in
{
path = findCratePath dependencyObject.name;
@ -193,18 +214,11 @@
git = dependencyObject:
let
source = dependencyObject.source;
extractRevision = source: l.last (l.splitString "#" source);
extractRepoUrl = source:
let
splitted = l.head (l.splitString "?" source);
split = l.substring 4 (l.stringLength splitted) splitted;
in l.head (l.splitString "#" split);
parsed = parseGitSource dependencyObject.source;
in
{
url = extractRepoUrl source;
rev = extractRevision source;
url = parsed.url;
rev = parsed.sha;
};
crates-io = dependencyObject:

View File

@ -77,6 +77,11 @@ let
cyclicDependencies = lock.cyclicDependencies;
getSourceSpec = pname: version:
sources."${pname}"."${version}" or (
throw "The source spec for ${pname}#${version} is not defined in lockfile."
);
getDependencies = pname: version:
b.filter
(dep: ! b.elem dep cyclicDependencies."${pname}"."${version}" or [])
@ -96,6 +101,7 @@ let
subsystemAttrs
getCyclicDependencies
getDependencies
getSourceSpec
packageVersions
subDreamLocks
;