4.flake-parts/dev/tests/eval-tests.nix
Robert Hensing 577772566b Memoize undeclared systems
The memoize function is not a pretty thing in terms of implementation,
but it's exactly what we need to solve this UX problem and performance
problem.

Without this, every distinct `withSystem` call will cause a re-evaluation
of the `perSystem` module, which is inefficient. Now, it's a one-time
13KB and length(system) attribute lookups: negligible compared to any
instantiations and such.

Nix doesn't offer memoization for functions yet, so this is the best we
can do.
2025-07-21 20:07:18 +02:00

302 lines
7.9 KiB
Nix

# Run with
#
# nix build .#checks.x86_64-linux.eval-tests
{ flake-parts }:
rec {
nixpkgs = flake-parts.inputs.nixpkgs;
f-p-lib = flake-parts.lib;
inherit (f-p-lib) mkFlake;
inherit (flake-parts.inputs.nixpkgs-lib) lib;
pkg = system: name:
derivation
{
name = name;
builder = "no-builder";
system = system;
}
// {
meta = {
mainProgram = name;
};
};
empty = mkFlake
{ inputs.self = { }; }
{
systems = [ ];
};
emptyExposeArgs = mkFlake
{ inputs.self = { outPath = "the self outpath"; }; }
({ config, moduleLocation, ... }: {
flake = {
inherit moduleLocation;
};
});
emptyExposeArgsNoSelf = mkFlake
{ inputs.self = throw "self won't be available in case of some errors"; }
({ config, moduleLocation, ... }: {
flake = {
inherit moduleLocation;
};
});
example1 = mkFlake
{ inputs.self = { }; }
{
systems = [ "a" "b" ];
perSystem = { config, system, ... }: {
packages.hello = pkg system "hello";
apps.hello.program = config.packages.hello;
};
};
packagesNonStrictInDevShells = mkFlake
{ inputs.self = packagesNonStrictInDevShells; /* approximation */ }
{
systems = [ "a" "b" ];
perSystem = { system, self', ... }: {
packages.hello = pkg system "hello";
packages.default = self'.packages.hello;
devShells = throw "can't be strict in perSystem.devShells!";
};
flake.devShells = throw "can't be strict in devShells!";
};
easyOverlay = mkFlake
{ inputs.self = { }; }
{
imports = [ flake-parts.flakeModules.easyOverlay ];
systems = [ "a" "aarch64-linux" ];
perSystem = { system, config, final, pkgs, ... }: {
packages.default = config.packages.hello;
packages.hello = pkg system "hello";
packages.hello_new = final.hello;
overlayAttrs = {
hello = config.packages.hello;
hello_old = pkgs.hello;
hello_new = config.packages.hello_new;
};
};
};
modulesFlake = mkFlake
{
inputs.self = { };
moduleLocation = "modulesFlake";
}
{
imports = [ flake-parts.flakeModules.modules ];
systems = [ ];
flake = {
modules.generic.example = { lib, ... }: {
options.generic.example = lib.mkOption { default = "works in any module system application"; };
};
modules.foo.example = { lib, ... }: {
options.foo.example = lib.mkOption { default = "works in foo application"; };
};
};
};
flakeModulesDeclare = mkFlake
{ inputs.self = { outPath = ./.; }; }
({ config, ... }: {
imports = [ flake-parts.flakeModules.flakeModules ];
systems = [ ];
flake.flakeModules.default = { lib, ... }: {
options.flake.test123 = lib.mkOption { default = "option123"; };
imports = [ config.flake.flakeModules.extra ];
};
flake.flakeModules.extra = {
flake.test123 = "123test";
};
});
flakeModulesImport = mkFlake
{ inputs.self = { }; }
{
imports = [ flakeModulesDeclare.flakeModules.default ];
};
flakeModulesDisable = mkFlake
{ inputs.self = { }; }
{
imports = [ flakeModulesDeclare.flakeModules.default ];
disabledModules = [ flakeModulesDeclare.flakeModules.extra ];
};
nixpkgsWithoutEasyOverlay = import nixpkgs {
system = "x86_64-linux";
overlays = [ ];
config = { };
};
nixpkgsWithEasyOverlay = import nixpkgs {
# non-memoized
system = "x86_64-linux";
overlays = [ easyOverlay.overlays.default ];
config = { };
};
nixpkgsWithEasyOverlayMemoized = import nixpkgs {
# memoized
system = "aarch64-linux";
overlays = [ easyOverlay.overlays.default ];
config = { };
};
specialArgFlake = mkFlake
{
inputs.self = { };
specialArgs.soSpecial = true;
}
({ soSpecial, ... }: {
imports = assert soSpecial; [ ];
flake.foo = true;
});
partitionWithoutExtraInputsFlake = mkFlake
{
inputs.self = { };
}
({ config, ... }: {
imports = [ flake-parts.flakeModules.partitions ];
systems = [ "x86_64-linux" ];
partitions.dev.module = { inputs, ... }: builtins.seq inputs { };
partitionedAttrs.devShells = "dev";
});
/**
This one is for manual testing. Should look like:
```
nix-repl> checks.x86_64-linux.eval-tests.internals.printSystem.withSystem "foo" ({ config, ... }: null)
trace: Evaluating perSystem for foo
null
nix-repl> checks.x86_64-linux.eval-tests.internals.printSystem.withSystem "foo" ({ config, ... }: null)
null
```
*/
printSystem = mkFlake
{ inputs.self = { }; }
({ withSystem, ... }: {
systems = [ ];
perSystem = { config, system, ... }:
builtins.trace "Evaluating perSystem for ${system}" { };
flake.withSystem = withSystem;
});
dogfoodProvider = mkFlake
{ inputs.self = { }; }
({ flake-parts-lib, ... }: {
imports = [
(flake-parts-lib.importAndPublish "dogfood" { flake.marker = "dogfood"; })
];
});
dogfoodConsumer = mkFlake
{ inputs.self = { }; }
({ flake-parts-lib, ... }: {
imports = [
dogfoodProvider.modules.flake.dogfood
];
});
runTests = ok:
assert empty == {
apps = { };
checks = { };
devShells = { };
formatter = { };
legacyPackages = { };
nixosConfigurations = { };
nixosModules = { };
overlays = { };
packages = { };
};
assert example1 == {
apps = {
a = {
hello = {
program = "${pkg "a" "hello"}/bin/hello";
type = "app";
meta = { };
};
};
b = {
hello = {
program = "${pkg "b" "hello"}/bin/hello";
type = "app";
meta = { };
};
};
};
checks = { a = { }; b = { }; };
devShells = { a = { }; b = { }; };
formatter = { };
legacyPackages = { a = { }; b = { }; };
nixosConfigurations = { };
nixosModules = { };
overlays = { };
packages = {
a = { hello = pkg "a" "hello"; };
b = { hello = pkg "b" "hello"; };
};
};
# - exported package becomes part of overlay.
# - perSystem is invoked for the right system, when system is non-memoized
assert nixpkgsWithEasyOverlay.hello == pkg "x86_64-linux" "hello";
# - perSystem is invoked for the right system, when system is memoized
assert nixpkgsWithEasyOverlayMemoized.hello == pkg "aarch64-linux" "hello";
# - Non-exported package does not become part of overlay.
assert nixpkgsWithEasyOverlay.default or null != pkg "x86_64-linux" "hello";
# - hello_old comes from super
assert nixpkgsWithEasyOverlay.hello_old == nixpkgsWithoutEasyOverlay.hello;
# - `hello_new` shows that the `final` wiring works
assert nixpkgsWithEasyOverlay.hello_new == nixpkgsWithEasyOverlay.hello;
assert flakeModulesImport.test123 == "123test";
assert flakeModulesDisable.test123 == "option123";
assert packagesNonStrictInDevShells.packages.a.default == pkg "a" "hello";
assert emptyExposeArgs.moduleLocation == "the self outpath/flake.nix";
assert (lib.evalModules {
class = "barrr";
modules = [
modulesFlake.modules.generic.example
];
}).config.generic.example == "works in any module system application";
assert (lib.evalModules {
class = "foo";
modules = [
modulesFlake.modules.foo.example
];
}).config.foo.example == "works in foo application";
assert specialArgFlake.foo;
assert builtins.isAttrs partitionWithoutExtraInputsFlake.devShells.x86_64-linux;
assert dogfoodProvider.marker == "dogfood";
assert dogfoodConsumer.marker == "dogfood";
ok;
result = runTests "ok";
}