Add buildPackage for building and installing cargo packages

This commit is contained in:
Ivan Petkov 2022-01-03 14:22:08 -08:00
parent 47316bfe5b
commit f69c8a4c44
No known key found for this signature in database
GPG Key ID: BB6F9EFC065832B6
17 changed files with 239 additions and 8 deletions

53
lib/buildPackage.nix Normal file
View File

@ -0,0 +1,53 @@
{ buildWithCargo
, installFromCargoBuildLogHook
}:
{ cargoBuildCommand ? "cargo build --workspace --release"
, cargoExtraArgs ? ""
, ...
}@args:
# NB: we use overrideAttrs here so that our extra additions here do not end up
# invalidating any deps builds by being inherited. For example, we probably don't
# care about installing bins/libs from the deps only build, so there's no point to
# trying to build it with the install scripts in its build environment.
(buildWithCargo args).overrideAttrs (old: {
# Don't copy target dir by default since we are going to be installing bins/libs
doCopyTargetToOutput = args.doCopyTargetToOutput or false;
buildPhase = args.buildPhase or ''
runHook preBuild
${args.buildPhaseCargoCommand or ''
cargoBuildLog=$(mktemp cargoBuildLogXXXX.json)
${cargoBuildCommand} --message-format json-render-diagnostics ${cargoExtraArgs} >"$cargoBuildLog"
''}
runHook postBuild
'';
installPhase = args.installPhase or ''
runHook preInstall
${args.installPhaseCargoCommand or ''
if [ -n "$cargoBuildLog" -a -f "$cargoBuildLog" ]; then
installFromCargoBuildLog "$out" "$cargoBuildLog"
else
echo '
$cargoBuildLog is either undefined or does not point to a valid file location!
By default `buildPackage` will capture cargo'"'"'s output and use it to determine which binaries
should be installed (instead of just guessing based on what is present in cargo'"'"'s target directory).
If you are overriding the derivation with a custom build step, you have two options:
1. override `installPhaseCargoCommand` with the appropriate installation steps
2. ensure that cargo'"'"'s build log is captured in a file and point $cargoBuildLog at it
At a minimum, the latter option can be achieved with running:
cargoBuildLog=$(mktemp cargoBuildLogXXXX.json)
cargo build --message-format json-render-diagnostics >"$cargoBuildLog"
'
false
fi
''}
runHook postInstall
'';
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [
installFromCargoBuildLogHook
];
})

View File

@ -48,9 +48,6 @@ mkCargoDerivation (args // {
# This can be inferred automatically if the `src` root has a Cargo.lock file.
cargoVendorDir = args.cargoVendorDir or (vendorCargoDepsFromArgs args);
# Don't copy target dir by default since we are going to be installing bins/libs
doCopyTargetToOutput = args.doCopyTargetToOutput or false;
nativeBuildInputs = (args.nativeBuildInputs or [ ]) ++ [
installFromCargoArtifactsHook
];

View File

@ -13,6 +13,7 @@ lib.makeScope newScope (self:
inherit fromTOML toTOML;
buildDepsOnly = callPackage ./buildDepsOnly.nix { };
buildPackage = callPackage ./buildPackage.nix { };
buildWithCargo = callPackage ./buildWithCargo.nix { };
cleanCargoToml = callPackage ./cleanCargoToml.nix { };
crateNameFromCargoToml = callPackage ./crateNameFromCargoToml.nix { };

View File

@ -1,4 +1,6 @@
{ makeSetupHook
{ cargo
, makeSetupHook
, jq
, rsync
, zstd
}:
@ -35,6 +37,15 @@
name = "installFromCargoArtifactsHook";
} ./installFromCargoArtifactsHook.sh;
installFromCargoBuildLogHook = makeSetupHook
{
name = "installFromCargoBuildLogHook";
substitutions = {
cargo = "${cargo}/bin/cargo";
jq = "${jq}/bin/jq";
};
} ./installFromCargoBuildLogHook.sh;
remapSourcePathPrefixHook = makeSetupHook
{
name = "remapSourcePathPrefixHook";

View File

@ -0,0 +1,42 @@
function installFromCargoBuildLog() (
local dest=${1:-${out}}
local log=${2:-${cargoBuildLog:?not defined}}
if ! [ -f "${log}" ]; then
echo unable to install, cargo build log does not exist at: ${log}
false
fi
echo searching for bins/libs to install from cargo build log at ${log}
local select_non_test='select(.reason == "compiler-artifact" and .profile.test == false)'
local select_bins="${select_non_test} | .executable | select(.!= null)"
local select_lib_files="${select_non_test}"'
| select(.target.kind | contains(["staticlib"]) or contains(["cdylib"]))
| .filenames[]
| select(endswith(".rlib") | not)
'
function installArtifacts() {
local loc=${1?:missing}
mkdir -p "${loc}"
while IFS= read -r to_install; do
echo installing ${to_install}
cp "${to_install}" "${loc}"
done
rmdir --ignore-fail-on-non-empty "${loc}"
}
@jq@ -r <"${log}" "${select_bins}" | installArtifacts "${dest}/bin"
@cargo@ metadata --format-version 1 | @jq@ '.workspace_members[]' | (
while IFS= read -r ws_member; do
local select_member_libs="select(.package_id == ${ws_member}) | ${select_lib_files}"
@jq@ -r <"${log}" "${select_member_libs}" | installArtifacts "${dest}/lib"
done
)
echo searching for bins/libs complete!
)

View File

@ -45,8 +45,26 @@ pkgs.lib.makeScope myLib.newScope (self:
src = ./various-targets;
};
simple = myLib.buildWithCargo {
doCopyTargetToOutput = false;
manyLibs = myLib.buildPackage {
src = ./with-libs;
pname = "my-libs";
version = "0.0.1";
cargoArtifacts = null;
};
manyLibsInstalledAsExpected = pkgs.runCommand "manyLibsInstalledAsExpected" { } ''
cat >expected <<EOF
liball_types.a
liball_types.so
libonly_cdylib.so
libonly_staticlib.a
EOF
diff ./expected <(ls -1 ${self.manyLibs}/lib)
touch $out
'';
simple = myLib.buildPackage {
src = ./simple;
};
@ -58,8 +76,7 @@ pkgs.lib.makeScope myLib.newScope (self:
smokeOverlappingTargets =
let
overlappingTargets = myLib.buildWithCargo {
doCopyTargetToOutput = false;
overlappingTargets = myLib.buildPackage {
src = ./overlapping-targets;
};
in

28
tests/with-libs/Cargo.lock generated Normal file
View File

@ -0,0 +1,28 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "all-types"
version = "0.1.0"
dependencies = [
"some-dep",
]
[[package]]
name = "only-cdylib"
version = "0.1.0"
dependencies = [
"some-dep",
]
[[package]]
name = "only-staticlib"
version = "0.1.0"
dependencies = [
"some-dep",
]
[[package]]
name = "some-dep"
version = "0.1.0"

View File

@ -0,0 +1,7 @@
[workspace]
members = [
"only-cdylib",
"only-staticlib",
"all-types",
]
exclude = ["some-dep"]

View File

@ -0,0 +1,15 @@
[package]
name = "all-types"
version = "0.1.0"
edition = "2021"
[dependencies]
some-dep = { path = "../some-dep" }
[lib]
crate-type = [
"cdylib",
"rlib",
"staticlib",
]

View File

@ -0,0 +1,4 @@
#[no_mangle]
pub fn bar(a: i32) -> i32 {
some_dep::foo(a) * a
}

View File

@ -0,0 +1,13 @@
[package]
name = "only-cdylib"
version = "0.1.0"
edition = "2021"
[dependencies]
some-dep = { path = "../some-dep" }
[lib]
crate-type = [
"cdylib",
]

View File

@ -0,0 +1,4 @@
#[no_mangle]
pub fn bar(a: i32) -> i32 {
some_dep::foo(a) * a
}

View File

@ -0,0 +1,13 @@
[package]
name = "only-staticlib"
version = "0.1.0"
edition = "2021"
[dependencies]
some-dep = { path = "../some-dep" }
[lib]
crate-type = [
"staticlib",
]

View File

@ -0,0 +1,4 @@
#[no_mangle]
pub fn bar(a: i32) -> i32 {
some_dep::foo(a) * a
}

7
tests/with-libs/some-dep/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "some-dep"
version = "0.1.0"

View File

@ -0,0 +1,11 @@
[package]
name = "some-dep"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = [
"cdylib",
"rlib",
"staticlib",
]

View File

@ -0,0 +1,4 @@
#[no_mangle]
pub fn foo(a: i32) -> i32 {
a + 42
}