mill-scale/mill-scale.nix
Robin Appelman 306a9f8d74
All checks were successful
CI / checks (push) Successful in 22s
update toolchain for semver-checks
2026-04-03 19:39:19 +02:00

487 lines
16 KiB
Nix

# mill-scale -- Another rust module for flakelight
# Copyright (C) 2024 Robin Appelman <robin@icewind.nl>
# SPDX-License-Identifier: MIT
{
lib,
src,
config,
flakelight,
inputs,
...
}: let
inherit (builtins) elem readFile pathExists match any concatLists;
inherit (lib) getExe map mkDefault mkIf mkMerge mkOption warnIf optionalAttrs types optionalString genAttrs hasInfix makeSearchPathOutput;
inherit (lib.fileset) fileFilter toSource unions;
inherit (flakelight.types) fileset function optFunctionTo;
makePkgConfigPath = makeSearchPathOutput "dev" "lib/pkgconfig";
filteredSrc = toSource {
root = src;
fileset = unions (config.extraPaths ++ [config.fileset]);
};
cargoToml = fromTOML (readFile config.cargoToml);
cargoMeta =
(import ./cargo-meta.nix {
inherit lib;
})
cargoToml;
inherit (cargoMeta) tomlPackage hasMsrv hasWorkspace hasNonDefaultFeatures hasDefaultFeatures msrv;
maybeWorkspace = optionalString hasWorkspace "--workspace";
hasExamples = pathExists (src + /examples);
hasDefaultPackage = pathExists (src + /nix/package.nix);
autoDeps = import ./autodeps {
inherit (config) cargoLock;
inherit lib src config;
};
buildDeps = pkgs: rec {
buildInputs = (autoDeps pkgs).buildInputs ++ (config.buildInputs pkgs);
nativeBuildInputs = (autoDeps pkgs).nativeBuildInputs ++ (config.nativeBuildInputs pkgs);
runtimeInputs = (autoDeps pkgs).runtimeInputs ++ (config.runtimeInputs pkgs);
env =
(autoDeps pkgs).env
// (config.buildEnv pkgs)
// {
LD_LIBRARY_PATH = "/run/opengl-driver/lib/:${lib.makeLibraryPath runtimeInputs}";
};
};
defaultToolchain = pkgs:
if pathExists (src + "/rust-toolchain.toml")
then pkgs.rust-bin.fromRustupToolchainFile (src + "/rust-toolchain.toml")
else pkgs.rust-bin.stable.latest.default;
autoTools = let
definitions = import ./autotools.nix;
perDependency = map (dep: definitions.${dep} or []) (cargoMeta.dependencies ++ cargoMeta.dev-dependencies);
all = concatLists perDependency;
in
pkgs: map (pkgName: pkgs.${pkgName}) all;
in
warnIf (! builtins ? readFileType) "Unsupported Nix version in use."
{
options = {
cargoToml = mkOption {
type = with types; path;
default = src + /Cargo.toml;
};
cargoLock = mkOption {
type = with types; path;
default = src + /Cargo.lock;
};
extraFiles = mkOption {
type = with types; listOf str;
default = [];
};
extraFilesRegex = mkOption {
type = with types; listOf str;
default = [];
};
extraPaths = mkOption {
type = with types; listOf path;
default = [];
};
fileset = mkOption {
type = fileset;
default =
fileFilter
(file:
file.hasExt "rs"
|| match "snapshot__.*\.snap" file.name != null
|| elem file.name (["Cargo.toml" "Cargo.lock" "config.toml"] ++ config.extraFiles)
|| any (re: match re file.name != null) config.extraFilesRegex)
src;
};
crossTargets = mkOption {
type = with types; listOf str;
default = [];
};
buildInputs = mkOption {
type = function;
default = pkgs: [];
description = "build inputs for the package";
};
nativeBuildInputs = mkOption {
type = function;
default = pkgs: [];
description = "native build inputs for the package";
};
buildEnv = mkOption {
type = function;
default = pkgs: {};
description = "build environent variables for the package";
};
runtimeInputs = mkOption {
type = function;
default = pkgs: [];
description = "runtime inputs for the package";
};
tools = mkOption {
type = function;
default = pkgs: with pkgs; [cargo-edit bacon];
description = "extra packages to make available in the dev shells";
};
autodeps = mkOption {
type = types.bool;
default = true;
description = "Automatically detect (some) of the build dependencies";
};
packageOpts = mkOption {
type = optFunctionTo types.attrs;
default = {};
};
toolchain = mkOption {
type = function;
default = defaultToolchain;
description = "rust toolchain to use";
};
msrvToolchain = mkOption {
type = function;
default = pkgs: pkgs.rust-bin.stable.${msrv}.default;
description = "rust toolchain to use for msrv check";
};
semverToolchain = mkOption {
type = function;
default = pkgs: pkgs.rust-bin.stable."1.89.0".default;
description = "rust toolchain to use for cargo-semver-checks";
};
miriToolchain = mkOption {
type = function;
default = pkgs:
pkgs.rust-bin.selectLatestNightlyWith (toolchain:
toolchain.default.override {
extensions = ["miri" "rust-src"];
});
description = "rust toolchain to use for miri";
};
cargoTest = mkOption {
type = types.bool;
default = true;
description = "create a check for `cargo test`";
};
};
config = mkMerge [
(mkIf (pathExists config.cargoToml) {
withOverlays = [
(import inputs.rust-overlay)
(import ./overlay.nix)
(final: {
inputs,
rust-bin,
writeShellApplication,
stdenvNoCC,
...
} @ prev: rec {
commonCraneArgs =
{
inherit (config) cargoToml cargoLock;
src = filteredSrc;
strictDeps = true;
doCheck = false;
inherit ((buildDeps final)) buildInputs nativeBuildInputs;
}
// (buildDeps final).env;
allFeaturesCraneArgs =
commonCraneArgs
// {
cargoExtraArgs = "--locked --all-features ${maybeWorkspace}";
pname = "${crateName}-all-features";
};
noDefaultFeaturesCraneArgs =
commonCraneArgs
// {
cargoExtraArgs = "--locked --no-default-features ${maybeWorkspace}";
pname = "${crateName}-no-default-features";
};
msrvCraneArgs =
commonCraneArgs
// {
cargoExtraArgs = "--locked --all-features ${maybeWorkspace}";
pname = "${crateName}-msrv";
nativeBuildInputs = commonCraneArgs.nativeBuildInputs ++ [final.cargo-hack];
};
crateName =
(craneLib.crateNameFromCargoToml {
inherit (config) cargoToml;
inherit src;
}).pname;
craneLib = (inputs.crane.mkLib final).overrideToolchain (p: p.rustToolchain);
craneLibForTargets = targets: (inputs.crane.mkLib final).overrideToolchain (p: p.rustToolchain.override {inherit targets;});
craneLibMsrv = (inputs.crane.mkLib final).overrideToolchain (p: p.msrvRustToolchain);
cargoArtifacts = craneLib.buildDepsOnly commonCraneArgs;
cargoArtifactsAllFeatures = craneLib.buildDepsOnly allFeaturesCraneArgs;
cargoArtifactsNoDefault = craneLib.buildDepsOnly noDefaultFeaturesCraneArgs;
cargoArtifactsMsrv = craneLibMsrv.buildDepsOnly msrvCraneArgs;
rustToolchain = config.toolchain prev;
msrvRustToolchain = config.msrvToolchain prev;
miriRustToolchain = config.miriToolchain prev;
msrvToolchain = config.msrvToolchain prev;
semverToolchain = config.semverToolchain prev;
cargo-expand = writeShellApplication {
name = "cargo-expand";
runtimeInputs = [prev.cargo-expand];
text = ''
# shellcheck disable=SC2068
RUSTC_BOOTSTRAP=1 cargo-expand $@
'';
};
cargo-miri = writeShellApplication {
name = "cargo-miri";
runtimeInputs = [miriRustToolchain];
text = ''
# shellcheck disable=SC2068
cargo miri $@
'';
};
})
];
description = mkIf (tomlPackage ? description) tomlPackage.description;
# license will need to be set if Cargo license is a complex expression
license = mkIf (tomlPackage ? license) (mkDefault tomlPackage.license);
pname = tomlPackage.name;
packages =
(optionalAttrs (!hasDefaultPackage) {
default = {
craneLib,
cargoArtifacts,
defaultMeta,
commonCraneArgs,
pkgs,
}:
craneLib.buildPackage (commonCraneArgs
// {
inherit cargoArtifacts;
meta = defaultMeta;
}
// (config.packageOpts pkgs)
// (buildDeps pkgs).env);
})
// (genAttrs config.crossTargets (
target: {
craneLibForTargets,
cargoArtifacts,
defaultMeta,
callPackage,
crateName,
commonCraneArgs,
pkgs,
}: let
targetCraneLib = craneLibForTargets [target];
crossArgs = callPackage ./crossArgs.nix {} target;
in
targetCraneLib.buildPackage
(commonCraneArgs
// {
meta =
defaultMeta
// {
targetPlatform = target;
binarySuffix = crossArgs.BINARY_SUFFIX or "";
};
pname = "${crateName}-${target}";
cargoExtraArgs = "--target ${target}";
}
// crossArgs
// (config.packageOpts pkgs))
// (buildDeps pkgs).env
));
outputs = {
lib.crossMatrix =
map
(target: {
inherit target;
binary-suffix = optionalString (hasInfix "windows" target) ".exe";
})
config.crossTargets;
};
checks = {
craneLib,
craneLibMsrv,
cargoArtifacts,
cargoArtifactsMsrv,
cargoArtifactsAllFeatures,
cargoArtifactsNoDefault,
crateName,
commonCraneArgs,
allFeaturesCraneArgs,
noDefaultFeaturesCraneArgs,
msrvCraneArgs,
pkgs,
...
}: let
packageOpts = config.packageOpts pkgs // (buildDeps pkgs).env;
in
{
clippy = craneLib.cargoClippy (commonCraneArgs
// {
inherit cargoArtifacts;
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
}
// packageOpts);
}
// (optionalAttrs config.cargoTest {
test = craneLib.cargoTest (commonCraneArgs
// {
inherit cargoArtifacts;
doCheck = true;
cargoExtraArgs = "--locked --all-targets ${maybeWorkspace}";
}
// packageOpts);
})
// (optionalAttrs hasMsrv
{
msrv =
craneLibMsrv.buildPackage
(msrvCraneArgs
// {
pname = "${crateName}-msrv";
cargoArtifacts = cargoArtifactsMsrv;
cargoBuildCommand = "cargo hack --no-dev-deps check";
cargoExtraArgs = "--release --all-features ${maybeWorkspace}";
installPhaseCommand = "mkdir $out";
}
// packageOpts);
})
// (optionalAttrs (hasNonDefaultFeatures && config.cargoTest) {
test-all-features = craneLib.cargoTest (allFeaturesCraneArgs
// {
cargoArtifacts = cargoArtifactsAllFeatures;
doCheck = true;
}
// packageOpts);
})
// (optionalAttrs hasNonDefaultFeatures {
clippy-all-features = craneLib.cargoClippy (allFeaturesCraneArgs
// {
cargoArtifacts = cargoArtifactsAllFeatures;
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
}
// packageOpts);
})
// (optionalAttrs (hasDefaultFeatures && config.cargoTest) {
test-no-default-features = craneLib.cargoTest (noDefaultFeaturesCraneArgs
// {
cargoArtifacts = cargoArtifactsNoDefault;
doCheck = true;
}
// packageOpts);
})
// (optionalAttrs hasDefaultFeatures {
clippy-no-default-features = craneLib.cargoClippy (noDefaultFeaturesCraneArgs
// {
cargoArtifacts = cargoArtifactsNoDefault;
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
}
// packageOpts);
})
// (optionalAttrs hasExamples {
examples = craneLibMsrv.buildPackage (commonCraneArgs
// {
pname = "${crateName}-examples";
cargoExtraArgs = "--examples ${optionalString hasNonDefaultFeatures "--all-features"} ${maybeWorkspace}";
}
// packageOpts);
});
apps = {
cargo-miri,
cargo-semver-checks,
rustToolchain,
semverToolchain,
miriRustToolchain,
writeShellApplication,
...
} @ pkgs: let
deps =
(buildDeps pkgs).buildInputs
++ (buildDeps pkgs).nativeBuildInputs
++ (with pkgs; [stdenv.cc gnumake gnused gawk gnugrep bash coreutils]);
miri-wrapped = writeShellApplication {
name = "cargo-miri";
runtimeInputs = [miriRustToolchain] ++ deps;
text = ''
PKG_CONFIG_PATH=${makePkgConfigPath deps} cargo miri "$@"
'';
};
semver-checks-wrapped = writeShellApplication {
name = "cargo-semver-checks";
runtimeInputs = [cargo-semver-checks semverToolchain] ++ deps;
text = ''
PKG_CONFIG_PATH=${makePkgConfigPath deps} cargo-semver-checks "$@"
'';
};
in {
miri = "${miri-wrapped}/bin/cargo-miri $@";
semver-checks = "${semver-checks-wrapped}/bin/cargo-semver-checks semver-checks $@";
bench = "${rustToolchain}/bin/cargo bench $@";
};
})
rec {
devShells =
rec {
default = {
packages = pkgs:
with pkgs;
[rustToolchain]
++ (config.tools pkgs)
++ (autoTools pkgs)
++ (buildDeps pkgs).buildInputs
++ (buildDeps pkgs).nativeBuildInputs;
env = {
rustPlatform,
pkgs,
...
}:
{
RUST_SRC_PATH = toString rustPlatform.rustLibSrc;
}
// (buildDeps pkgs).env;
};
miri = {
packages = pkgs:
with pkgs;
[miriRustToolchain]
++ (config.tools pkgs)
++ (autoTools pkgs)
++ (buildDeps pkgs).buildInputs
++ (buildDeps pkgs).nativeBuildInputs;
inherit (default) env;
};
}
// (optionalAttrs hasMsrv {
msrv = {
packages = pkgs:
with pkgs;
[msrvRustToolchain]
++ (config.tools pkgs)
++ (autoTools pkgs)
++ (buildDeps pkgs).buildInputs
++ (buildDeps pkgs).nativeBuildInputs;
inherit (devShells.default) env;
};
});
formatters = pkgs:
with pkgs; {
"*.nix" = getExe alejandra;
"*.rs" = getExe rustfmt;
};
}
];
}