commit b4506dc6e677378e0f3b04509e574d9f2c7856d2
parent bd83c4d0d87e16df949ae929d5ed68b13a9f6b07
Author: triesap <tyson@radroots.org>
Date: Thu, 20 Aug 2026 18:53:30 +0000
build: add crane service package
- add a locked parameterized Crane release-package builder
- compile the shared service fixture with the pinned Rust toolchain
- reject unsafe package, binary, profile, and environment inputs
- prove the binary output excludes source and toolchain closure drift
Diffstat:
7 files changed, 161 insertions(+), 16 deletions(-)
diff --git a/build/nix/service/default.nix b/build/nix/service/default.nix
@@ -1,7 +1,12 @@
-{ lib, pkgs }:
+{
+ crane,
+ lib,
+ pkgs,
+}:
{
supportedSystems = import ./systems.nix;
mkToolchain = import ./toolchain.nix { inherit pkgs; };
mkNativeInputs = import ./native-inputs.nix { inherit lib; };
+ mkServicePackage = import ./package.nix { inherit crane lib pkgs; };
mkServiceOutputs = import ./compose.nix { inherit lib; };
}
diff --git a/build/nix/service/fixture-service/Cargo.lock b/build/nix/service/fixture-service/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "fixture-service"
+version = "0.1.0"
diff --git a/build/nix/service/fixture-service/Cargo.toml b/build/nix/service/fixture-service/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "fixture-service"
+version = "0.1.0"
+edition = "2024"
+publish = false
+
+[[bin]]
+name = "fixture-service"
+path = "src/main.rs"
+
+[workspace]
diff --git a/build/nix/service/fixture-service/src/main.rs b/build/nix/service/fixture-service/src/main.rs
@@ -0,0 +1,14 @@
+use std::process::ExitCode;
+
+fn main() -> ExitCode {
+ match std::env::args().nth(1).as_deref() {
+ Some("--help") => {
+ println!("fixture-service");
+ ExitCode::SUCCESS
+ }
+ _ => {
+ eprintln!("usage: fixture-service --help");
+ ExitCode::from(2)
+ }
+ }
+}
diff --git a/build/nix/service/fixture.nix b/build/nix/service/fixture.nix
@@ -2,6 +2,7 @@
lib,
pkgs,
service,
+ toolchain,
}:
let
nativeInputs = service.mkNativeInputs {
@@ -10,32 +11,39 @@ let
RADROOTS_SERVICE_FIXTURE = "1";
};
};
- package = pkgs.writeShellApplication {
- name = "fixture-service";
- runtimeInputs = nativeInputs.nativeBuildInputs;
- text = ''
- case "''${1:-}" in
- --help)
- echo "fixture-service"
- ;;
- *)
- echo "usage: fixture-service --help" >&2
- exit 2
- ;;
- esac
- '';
+ fixtureSource = ./fixture-service;
+ package = service.mkServicePackage {
+ inherit nativeInputs toolchain;
+ source = fixtureSource;
+ cargoLock = fixtureSource + "/Cargo.lock";
+ servicePackage = "fixture-service";
+ binaryName = "fixture-service";
+ releaseProfile = "release";
};
smoke =
pkgs.runCommand "radroots-service-helper-fixture-smoke"
{
nativeBuildInputs = [
package
+ pkgs.file
pkgs.gnugrep
+ pkgs.nix
];
}
''
fixture-service --help > output
grep -Fx "fixture-service" output
+ file ${package}/bin/fixture-service > file-type
+ if grep -Fi "script" file-type; then
+ echo "fixture package installed a source wrapper" >&2
+ exit 1
+ fi
+ test ! -e ${package}/Cargo.toml
+ test ! -e ${package}/src
+ if nix-store --query --requisites ${package} | grep -Fx ${toolchain}; then
+ echo "fixture runtime closure retains the Rust toolchain" >&2
+ exit 1
+ fi
touch "$out"
'';
outputs = service.mkServiceOutputs {
@@ -82,6 +90,43 @@ let
nativeInputs = { };
}).nativeInputs
);
+ invalidServicePackage = builtins.tryEval (
+ (service.mkServicePackage {
+ inherit nativeInputs toolchain;
+ source = fixtureSource;
+ cargoLock = fixtureSource + "/Cargo.lock";
+ servicePackage = "../fixture";
+ }).outPath
+ );
+ invalidBinaryName = builtins.tryEval (
+ (service.mkServicePackage {
+ inherit nativeInputs toolchain;
+ source = fixtureSource;
+ cargoLock = fixtureSource + "/Cargo.lock";
+ servicePackage = "fixture-service";
+ binaryName = "fixture service";
+ }).outPath
+ );
+ invalidReleaseProfile = builtins.tryEval (
+ (service.mkServicePackage {
+ inherit nativeInputs toolchain;
+ source = fixtureSource;
+ cargoLock = fixtureSource + "/Cargo.lock";
+ servicePackage = "fixture-service";
+ releaseProfile = "dev";
+ }).outPath
+ );
+ profileOverride = builtins.tryEval (
+ (service.mkServicePackage {
+ inherit toolchain;
+ source = fixtureSource;
+ cargoLock = fixtureSource + "/Cargo.lock";
+ servicePackage = "fixture-service";
+ nativeInputs = service.mkNativeInputs {
+ environment.CARGO_PROFILE = "dev";
+ };
+ }).outPath
+ );
in
assert
service.supportedSystems == [
@@ -102,6 +147,10 @@ assert invalidName.success == false;
assert defaultOverride.success == false;
assert invalidPackage.success == false;
assert invalidNativeInputs.success == false;
+assert invalidServicePackage.success == false;
+assert invalidBinaryName.success == false;
+assert invalidReleaseProfile.success == false;
+assert profileOverride.success == false;
{
inherit outputs;
check = smoke;
diff --git a/build/nix/service/package.nix b/build/nix/service/package.nix
@@ -0,0 +1,57 @@
+{
+ crane,
+ lib,
+ pkgs,
+}:
+{
+ toolchain,
+ source,
+ cargoLock,
+ servicePackage,
+ binaryName ? servicePackage,
+ nativeInputs,
+ releaseProfile ? "release",
+}:
+assert lib.assertMsg (lib.isDerivation toolchain) "toolchain must be a derivation";
+assert lib.assertMsg (builtins.pathExists source) "source must exist";
+assert lib.assertMsg (builtins.pathExists cargoLock) "cargoLock must exist";
+assert lib.assertMsg (
+ builtins.isString servicePackage && builtins.match "^[a-z][a-z0-9_-]*$" servicePackage != null
+) "servicePackage must be a lowercase Cargo package identifier";
+assert lib.assertMsg (
+ builtins.isString binaryName && builtins.match "^[a-z][a-z0-9_-]*$" binaryName != null
+) "binaryName must be a lowercase Cargo binary identifier";
+assert lib.assertMsg (
+ builtins.isString releaseProfile
+ && builtins.match "^release(-[a-z0-9][a-z0-9_-]*)?$" releaseProfile != null
+) "releaseProfile must be release or a release-prefixed Cargo profile";
+assert lib.assertMsg (
+ builtins.isAttrs nativeInputs
+ && builtins.isList (nativeInputs.nativeBuildInputs or null)
+ && builtins.isList (nativeInputs.buildInputs or null)
+ && builtins.isAttrs (nativeInputs.environment or null)
+) "nativeInputs must come from mkNativeInputs";
+assert lib.assertMsg (
+ !(builtins.hasAttr "CARGO_PROFILE" nativeInputs.environment)
+) "nativeInputs.environment must not replace CARGO_PROFILE";
+let
+ craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
+ cargoExtraArgs = "--locked --package ${servicePackage} --bin ${binaryName}";
+ commonArgs = {
+ src = craneLib.cleanCargoSource source;
+ inherit cargoLock cargoExtraArgs;
+ CARGO_PROFILE = releaseProfile;
+ strictDeps = true;
+ nativeBuildInputs = nativeInputs.nativeBuildInputs;
+ buildInputs = nativeInputs.buildInputs;
+ env = nativeInputs.environment;
+ doCheck = false;
+ };
+ cargoArtifacts = craneLib.buildDepsOnly commonArgs;
+in
+craneLib.buildPackage (
+ commonArgs
+ // {
+ inherit cargoArtifacts;
+ }
+)
diff --git a/flake.nix b/flake.nix
@@ -34,6 +34,7 @@
overlays = [ inputs.rust-overlay.overlays.default ];
};
service = import ./build/nix/service {
+ crane = inputs.crane;
inherit lib pkgs;
};
toolchains = {
@@ -50,6 +51,7 @@
};
serviceFixture = import ./build/nix/service/fixture.nix {
inherit lib pkgs service;
+ toolchain = toolchains.stable;
};
in
{
@@ -78,7 +80,7 @@
inherit common pkgs toolchains;
};
- packages = {
+ packages = serviceFixture.outputs.packages // {
xtask = common.xtaskPackage;
};
};