1
0
Fork 0
mirror of https://github.com/hercules-ci/flake-parts.git synced 2025-04-09 10:34:00 +00:00
This commit is contained in:
bb010g 2023-10-03 17:38:36 +02:00 committed by GitHub
commit 1b9caca30e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 566 additions and 133 deletions

View file

@ -1,6 +1,10 @@
# Separate `dev` flake
Wouldn't recommend this pattern normally, but I'm trying to keep
deps low for `flake-parts` until we have split dev inputs
that don't carry over to dependent lock files.
```sh
nix develop --impure -f './dev' 'mySystem.devShells.default'
nix repl -f './dev'
```

View file

@ -1,16 +1,36 @@
let
flake-parts = builtins.getFlake (toString ../.);
lib = flake-parts.inputs.nixpkgs-lib.lib;
sourceInfo = inputs.flake-parts.sourceInfo; # used by pre-commit module, etc
flake = builtins.getFlake (toString ./.);
fmc-lib = (builtins.getFlake (toString ../.)).lib;
args = {
inherit self;
} // flake.inputs;
self = {
inherit (flake) inputs;
outPath = ../.; # used by pre-commit module, etc
outputs = self.config.flake;
} //
fmc-lib.mkFlake
{ inputs = args; }
./flake-module.nix;
inputs = flake.inputs // { inherit flake-parts; };
makeResult = specialArgs: flakeModule: result:
let
outputs = flake.outputs // flake-parts.lib.mkFlake
{
inputs = inputs // { self = result; };
# debugging tool
specialArgs = {
replaceSpecialArgs = newSpecialArgs:
let
newSpecialArgs' =
if lib.isFunction newSpecialArgs
then newSpecialArgs specialArgs
else newSpecialArgs;
newResult = makeResult newSpecialArgs' flakeModule newResult;
in
newResult;
} // specialArgs;
}
flakeModule;
in
outputs // sourceInfo // {
inherit inputs outputs sourceInfo;
_type = "flake";
};
in
self.config.flake // { inherit (flake) inputs; }
let
# eagerly import to reproduce inline evaluation
result = makeResult { } (import ./flake-module.nix) result;
in
result

View file

@ -1,19 +1,25 @@
{ config, lib, inputs, withSystem, ... }:
{
{ config, inputs, lib, options, specialArgs, withSystem, ... } @ args:
let
rootArgs = args;
rootConfig = config;
rootOptions = options;
rootSpecialArgs = specialArgs;
in
# debugging tool
specialArgs.flakeModuleTransformer or (args: flakeModule: flakeModule) args {
imports = [
inputs.pre-commit-hooks-nix.flakeModule
inputs.hercules-ci-effects.flakeModule # herculesCI attr
];
systems = [ "x86_64-linux" "aarch64-darwin" ];
config.systems = [ "x86_64-linux" "aarch64-darwin" ];
hercules-ci.flake-update = {
config.hercules-ci.flake-update = {
enable = true;
autoMergeMethod = "merge";
when.dayOfMonth = 1;
};
perSystem = { config, pkgs, ... }: {
config.perSystem = { config, pkgs, ... }: {
devShells.default = pkgs.mkShell {
nativeBuildInputs = [
@ -38,11 +44,18 @@
in tests.runTests pkgs.emptyFile // { internals = tests; };
};
flake = {
# for repl exploration / debug
config.config = config;
options.mySystem = lib.mkOption { default = config.allSystems.${builtins.currentSystem}; };
config.effects = withSystem "x86_64-linux" ({ pkgs, hci-effects, ... }: {
config.flake = { config, options, specialArgs, ... } @ args: {
# for REPL exploration / debugging
config.allFlakeModuleArgs = args // config._module.args // specialArgs;
config.allRootModuleArgs = rootArgs // rootConfig._module.args // rootSpecialArgs;
config.transformFlakeModule = flakeModuleTransformer:
rootSpecialArgs.replaceSpecialArgs (prevSpecialArgs: prevSpecialArgs // {
inherit flakeModuleTransformer;
});
options.mySystem = lib.mkOption {
default = rootConfig.allSystems.${builtins.currentSystem};
};
config.effects = withSystem "x86_64-linux" ({ hci-effects, pkgs, ... }: {
tests = {
template = pkgs.callPackage ./tests/template.nix { inherit hci-effects; };
};

View file

@ -2,17 +2,54 @@
#
# nix build -f dev checks.x86_64-linux.eval-tests
rec {
f-p = builtins.getFlake (toString ../..);
flake-parts = f-p;
let
flake-parts = builtins.getFlake (toString ../..);
lib = flake-parts.inputs.nixpkgs-lib.lib;
in
(lib.makeExtensibleWithCustomName "extendEvalTests" (evalTests: {
inherit evalTests;
inherit flake-parts;
flake-parts-lib = evalTests.flake-parts.lib;
inherit lib;
devFlake = builtins.getFlake (toString ../.);
nixpkgs = devFlake.inputs.nixpkgs;
nixpkgs = evalTests.devFlake.inputs.nixpkgs;
f-p-lib = f-p.lib;
inherit (evalTests.flake-parts-lib) mkFlake;
weakEvalTests.callFlake = { ... } @ flake:
let
sourceInfo = flake.sourceInfo or { };
inputs = flake.inputs or { };
outputs = flake.outputs inputs;
result = outputs;
in
result;
strongEvalTests.callFlake = { ... } @ flake:
let
sourceInfo = { outPath = "/unknown_eval-tests_flake"; } //
flake.sourceInfo or { };
inputs = flake.inputs or { };
outputs = flake.outputs (inputs // { self = result; });
result = outputs // sourceInfo // {
inherit inputs outputs sourceInfo;
_type = "flake";
};
in
assert builtins.isFunction flake.outputs;
result;
inherit (f-p-lib) mkFlake;
inherit (f-p.inputs.nixpkgs-lib) lib;
withWeakEvalTests = evalTests.extendEvalTests (finalEvalTests: prevEvalTests:
builtins.mapAttrs (name: value: finalEvalTests.weakEvalTests.${name})
prevEvalTests.weakEvalTests
);
withStrongEvalTests = evalTests.extendEvalTests (finalEvalTests: prevEvalTests:
builtins.mapAttrs (name: value: finalEvalTests.strongEvalTests.${name})
prevEvalTests.strongEvalTests
);
exhibitingInfiniteRecursion = false;
exhibitInfiniteRecursion = evalTests.extendEvalTests
(finalEvalTest: prevEvalTests: { exhibitingInfiniteRecursion = true; });
pkg = system: name: derivation {
name = name;
@ -20,41 +57,202 @@ rec {
system = system;
};
empty = mkFlake
{ inputs.self = { }; }
{
empty = evalTests.callFlake {
inputs.flake-parts = evalTests.flake-parts;
inputs.self = { };
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ ];
};
};
weakEvalTests.emptyResult = {
apps = { };
checks = { };
devShells = { };
formatter = { };
legacyPackages = { };
nixosConfigurations = { };
nixosModules = { };
overlays = { };
packages = { };
};
strongEvalTests.emptyResult = let
_type = "flake";
inputs.flake-parts = evalTests.flake-parts;
inputs.self = { };
outputs = evalTests.weakEvalTests.emptyResult;
sourceInfo.outPath = "/unknown_eval-tests_flake";
result = outputs // sourceInfo // { inherit _type inputs outputs sourceInfo; };
in result;
runEmptyTests = ok:
assert evalTests.empty == evalTests.emptyResult;
ok;
emptyTestsResult = evalTests.runEmptyTests "ok";
example1 = mkFlake
{ inputs.self = { }; }
{
tooEmpty = evalTests.callFlake {
inputs.flake-parts = evalTests.flake-parts;
inputs.self = { };
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } {
};
};
# Shallow evaluation is successful…
weakEvalTests.tooEmptyResultTried0.success = true;
weakEvalTests.tooEmptyResultTried0.value = { };
weakEvalTests.tooEmptyResultTried0TestTried.success = true;
weakEvalTests.tooEmptyResultTried0TestTried.value = false;
# …including for flake outputs…
strongEvalTests.tooEmptyResultTried0 = evalTests.weakEvalTests.tooEmptyResultTried0;
strongEvalTests.tooEmptyResultTried0TestTried = evalTests.weakEvalTests.tooEmptyResultTried0TestTried;
# …but any evaluations of attribute values (flake output values) are not.
weakEvalTests.tooEmptyResultTried1.success = true;
weakEvalTests.tooEmptyResultTried1.value = {
apps = { };
checks = { };
devShells = { };
formatter = { };
legacyPackages = { };
nixosConfigurations = { };
nixosModules = { };
overlays = { };
packages = { };
};
weakEvalTests.tooEmptyResultTried1TestTried.success = false;
weakEvalTests.tooEmptyResultTried1TestTried.value = false;
strongEvalTests.tooEmptyResultTried1.success = true;
strongEvalTests.tooEmptyResultTried1.value = let
_type = "flake";
inputs.flake-parts = evalTests.flake-parts;
inputs.self = { };
outputs = evalTests.weakEvalTests.tooEmptyResultTried1.value;
sourceInfo.outPath = "/unknown_eval-tests_flake";
result = outputs // sourceInfo // { inherit _type inputs outputs sourceInfo; };
in result;
strongEvalTests.tooEmptyResultTried1TestTried.success = false;
strongEvalTests.tooEmptyResultTried1TestTried.value = false;
runTooEmptyTests = ok:
let
tooEmptyResultTried = builtins.tryEval evalTests.tooEmpty;
tooEmptyResultTried0TestTried = builtins.tryEval (tooEmptyResultTried == evalTests.tooEmptyResultTried0);
tooEmptyResultTried1TestTried = builtins.tryEval (tooEmptyResultTried == evalTests.tooEmptyResultTried1);
in
assert tooEmptyResultTried0TestTried == evalTests.tooEmptyResultTried0TestTried;
assert tooEmptyResultTried1TestTried == evalTests.tooEmptyResultTried1TestTried;
ok;
tooEmptyTestsResult = evalTests.runTooEmptyTests "ok";
example1 = evalTests.callFlake {
inputs.flake-parts = evalTests.flake-parts;
inputs.self = { };
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ "a" "b" ];
perSystem = { system, ... }: {
packages.hello = pkg system "hello";
packages.hello = evalTests.pkg system "hello";
};
};
};
weakEvalTests.example1Result = {
apps = { a = { }; b = { }; };
checks = { a = { }; b = { }; };
devShells = { a = { }; b = { }; };
formatter = { };
legacyPackages = { a = { }; b = { }; };
nixosConfigurations = { };
nixosModules = { };
overlays = { };
packages = {
a = { hello = evalTests.pkg "a" "hello"; };
b = { hello = evalTests.pkg "b" "hello"; };
};
};
strongEvalTests.example1Result = let
_type = "flake";
inputs.flake-parts = evalTests.flake-parts;
inputs.self = { };
outputs = evalTests.weakEvalTests.example1Result;
sourceInfo.outPath = "/unknown_eval-tests_flake";
result = outputs // sourceInfo // { inherit _type inputs outputs sourceInfo; };
in result;
runExample1Tests = ok:
assert evalTests.example1 == evalTests.example1Result;
ok;
example1TestsResult = evalTests.runExample1Tests "ok";
packagesNonStrictInDevShells = mkFlake
{ inputs.self = packagesNonStrictInDevShells; /* approximation */ }
{
# This test case is a fun one. In the REPL, try `exhibitInfiniteRecursion.*`.
# In the case that `mkFlake` *isn't* called from a flake, `inputs.self` is
# unlikely to refer to the result of the `mkFlake` evaluation. If
# `inputs.self` isn't actually self-referential, evaluating attribute values
# of `self` is not divergent. Evaluation of `self.outPath` is useful for
# paths in documentation & error messages. However, if that evaluation occurs
# in a `builtins.addErrorContext` message forced by an erroring `self`, both
# `self` will never evaluate *and* `builtins.toString self.outPath` must
# evaluate, causing Nix to instead throw an infinite recursion error. Even
# just `inputs.self ? outPath` throws an infinite recursion error.
# (`builtins.tryEval` can only catch errors created by `builtins.throw` or
# `builtins.assert`, so evaluation is guarded with
# `exhibitingInfiniteRecursion` here to keep `runTests` from diverging.)
# In this particular case, `mkFlake` evaluates `self ? outPath` to know if the
# default module location it provides should be generic or specific. As
# explained, this evaluation is unsafe under an uncatchably divergent `self`.
# Thus, `outPath` cannot be safely sourced from `self` at the top-level.
#
# When tests are exhibititing infinite recursion, the abnormally correct
# `self` is provided.
weakEvalTests.nonexistentOption = let result = evalTests.callFlake {
inputs.flake-parts = evalTests.flake-parts;
inputs.self = if !evalTests.exhibitingInfiniteRecursion then { } else result;
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } {
config.systems = [ ];
config.nonexistentOption = null;
};
}; in result;
# When using actual flakes, this test always diverges. Unless tests are
# exhibiting infinite recursion, the flake is made equivalent to `empty`.
strongEvalTests.nonexistentOption = evalTests.callFlake {
inputs.flake-parts = evalTests.flake-parts;
inputs.self = { };
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } ({
config.systems = [ ];
} // (if !evalTests.exhibitingInfiniteRecursion then { } else {
config.nonexistentOption = null;
}));
};
weakEvalTests.nonexistentOptionResultTried0.success = true;
weakEvalTests.nonexistentOptionResultTried0.value = { };
weakEvalTests.nonexistentOptionResultTried0TestTried.success = true;
weakEvalTests.nonexistentOptionResultTried0TestTried.value = false;
strongEvalTests.nonexistentOptionResultTried0 = evalTests.weakEvalTests.nonexistentOptionResultTried0;
strongEvalTests.nonexistentOptionResultTried0TestTried = evalTests.weakEvalTests.nonexistentOptionResultTried0TestTried;
runNonexistentOptionTests = ok:
let
nonexistentOptionResultTried = builtins.tryEval evalTests.nonexistentOption;
nonexistentOptionResultTried0TestTried = builtins.tryEval (nonexistentOptionResultTried == evalTests.nonexistentOptionResultTried0);
in
assert nonexistentOptionResultTried0TestTried == evalTests.nonexistentOptionResultTried0TestTried;
ok;
nonexistentOptionTestsResult = evalTests.runNonexistentOptionTests "ok";
packagesNonStrictInDevShells = evalTests.callFlake {
inputs.flake-parts = evalTests.flake-parts;
# approximation
inputs.self = evalTests.packagesNonStrictInDevShells;
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ "a" "b" ];
perSystem = { system, self', ... }: {
packages.hello = pkg system "hello";
perSystem = { self', system, ... }: {
packages.hello = evalTests.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 ];
easyOverlay = evalTests.callFlake {
inputs.flake-parts = evalTests.flake-parts;
inputs.self = { };
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ inputs.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 = evalTests.pkg system "hello";
packages.hello_new = final.hello;
overlayAttrs = {
hello = config.packages.hello;
@ -63,11 +261,13 @@ rec {
};
};
};
};
flakeModulesDeclare = mkFlake
{ inputs.self = { outPath = ./.; }; }
({ config, ... }: {
imports = [ flake-parts.flakeModules.flakeModules ];
flakeModulesDeclare = evalTests.callFlake {
inputs.flake-parts = evalTests.flake-parts;
inputs.self = { outPath = ./.; };
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } ({ config, inputs, lib, ... }: {
imports = [ inputs.flake-parts.flakeModules.flakeModules ];
systems = [ ];
flake.flakeModules.default = { lib, ... }: {
options.flake.test123 = lib.mkOption { default = "option123"; };
@ -77,92 +277,90 @@ rec {
flake.test123 = "123test";
};
});
};
flakeModulesImport = mkFlake
{ inputs.self = { }; }
{
imports = [ flakeModulesDeclare.flakeModules.default ];
flakeModulesImport = evalTests.callFlake {
inputs.flake-parts = evalTests.flake-parts;
inputs.flakeModulesDeclare = evalTests.flakeModulesDeclare;
inputs.self = { };
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ inputs.flakeModulesDeclare.flakeModules.default ];
};
};
runFlakeModulesImportTests = ok:
assert evalTests.flakeModulesImport.test123 == "123test";
ok;
flakeModulesImportTestsResult = evalTests.runFlakeModulesImportTests "ok";
flakeModulesDisable = mkFlake
{ inputs.self = { }; }
{
imports = [ flakeModulesDeclare.flakeModules.default ];
disabledModules = [ flakeModulesDeclare.flakeModules.extra ];
flakeModulesDisable = evalTests.callFlake {
inputs.flake-parts = evalTests.flake-parts;
inputs.flakeModulesDeclare = evalTests.flakeModulesDeclare;
inputs.self = { };
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ inputs.flakeModulesDeclare.flakeModules.default ];
disabledModules = [ inputs.flakeModulesDeclare.flakeModules.extra ];
};
};
runFlakeModulesDisableTests = ok:
assert evalTests.flakeModulesDisable.test123 == "option123";
ok;
flakeModulesDisableTestsResult = evalTests.runFlakeModulesDisableTests "ok";
nixpkgsWithoutEasyOverlay = import nixpkgs {
nixpkgsWithoutEasyOverlay = import evalTests.nixpkgs {
system = "x86_64-linux";
overlays = [ ];
config = { };
};
nixpkgsWithEasyOverlay = import nixpkgs {
nixpkgsWithEasyOverlay = import evalTests.nixpkgs {
# non-memoized
system = "x86_64-linux";
overlays = [ easyOverlay.overlays.default ];
overlays = [ evalTests.easyOverlay.overlays.default ];
config = { };
};
nixpkgsWithEasyOverlayMemoized = import nixpkgs {
nixpkgsWithEasyOverlayMemoized = import evalTests.nixpkgs {
# memoized
system = "aarch64-linux";
overlays = [ easyOverlay.overlays.default ];
overlays = [ evalTests.easyOverlay.overlays.default ];
config = { };
};
tryEvalOutputs = outputs: builtins.seq (builtins.attrNames outputs) outputs;
runTests = ok:
assert empty == {
apps = { };
checks = { };
devShells = { };
formatter = { };
legacyPackages = { };
nixosConfigurations = { };
nixosModules = { };
overlays = { };
packages = { };
};
assert evalTests.runEmptyTests true;
assert example1 == {
apps = { a = { }; b = { }; };
checks = { a = { }; b = { }; };
devShells = { a = { }; b = { }; };
formatter = { };
legacyPackages = { a = { }; b = { }; };
nixosConfigurations = { };
nixosModules = { };
overlays = { };
packages = {
a = { hello = pkg "a" "hello"; };
b = { hello = pkg "b" "hello"; };
};
};
assert evalTests.runTooEmptyTests true;
assert evalTests.runExample1Tests true;
assert evalTests.runNonexistentOptionTests true;
# - 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";
assert evalTests.nixpkgsWithEasyOverlay.hello == evalTests.pkg "x86_64-linux" "hello";
# - perSystem is invoked for the right system, when system is memoized
assert nixpkgsWithEasyOverlayMemoized.hello == pkg "aarch64-linux" "hello";
assert evalTests.nixpkgsWithEasyOverlayMemoized.hello == evalTests.pkg "aarch64-linux" "hello";
# - Non-exported package does not become part of overlay.
assert nixpkgsWithEasyOverlay.default or null != pkg "x86_64-linux" "hello";
assert evalTests.nixpkgsWithEasyOverlay.default or null != evalTests.pkg "x86_64-linux" "hello";
# - hello_old comes from super
assert nixpkgsWithEasyOverlay.hello_old == nixpkgsWithoutEasyOverlay.hello;
assert evalTests.nixpkgsWithEasyOverlay.hello_old == evalTests.nixpkgsWithoutEasyOverlay.hello;
# - `hello_new` shows that the `final` wiring works
assert nixpkgsWithEasyOverlay.hello_new == nixpkgsWithEasyOverlay.hello;
assert evalTests.nixpkgsWithEasyOverlay.hello_new == evalTests.nixpkgsWithEasyOverlay.hello;
assert flakeModulesImport.test123 == "123test";
assert evalTests.runFlakeModulesImportTests true;
assert flakeModulesDisable.test123 == "option123";
assert evalTests.runFlakeModulesDisableTests true;
assert packagesNonStrictInDevShells.packages.a.default == pkg "a" "hello";
assert evalTests.packagesNonStrictInDevShells.packages.a.default == evalTests.pkg "a" "hello";
ok;
result = runTests "ok";
}
result = evalTests.runTests "ok";
})).withWeakEvalTests

View file

@ -37,8 +37,11 @@ let
attrs@{ staticModules ? [ ] }: mkOptionType {
name = "deferredModule";
description = "module";
descriptionClass = "noun";
check = x: isAttrs x || isFunction x || path.check x;
merge = loc: defs: staticModules ++ map (def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value) defs;
merge = loc: defs: {
imports = staticModules ++ map (def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value) defs;
};
inherit (submoduleWith { modules = staticModules; })
getSubOptions
getSubModules;

View file

@ -15,7 +15,9 @@ let
};
getExe = x:
"${lib.getBin x}/bin/${x.meta.mainProgram or (throw ''Package ${x.name or ""} does not have meta.mainProgram set, so I don't know how to find the main executable. You can set meta.mainProgram, or pass the full path to executable, e.g. program = "''${pkg}/bin/foo"'')}";
"${lib.getBin x}/bin/${x.meta.mainProgram or (throw
''Package ${x.name or ""} does not have meta.mainProgram set, so I don't know how to find the main executable. You can set `meta.mainProgram`, or pass the full path to executable, e.g. program = "''${pkg}/bin/foo"''
)}";
appType = lib.types.submodule {
options = {

View file

@ -9,22 +9,23 @@
let
system =
config._module.args.system or
config._module.args.pkgs.stdenv.hostPlatform.system or
(throw "moduleWithSystem: Could not determine the configuration's system parameter for this module system application.");
config._module.args.pkgs.stdenv.hostPlatform.system or (throw
"moduleWithSystem: Could not determine the `system` parameter for this module set evaluation."
);
allArgs = withSystem system (args: args);
allPerSystemArgs = withSystem system (args: args);
lazyArgsPerParameter = f: builtins.mapAttrs
(k: v: allArgs.${k} or (throw "moduleWithSystem: module argument `${k}` does not exist."))
lazyPerSystemArgsPerParameter = f: builtins.mapAttrs
(k: v: allPerSystemArgs.${k} or (throw "moduleWithSystem: per-system argument `${k}` does not exist."))
(builtins.functionArgs f);
# Use reflection to make the call lazy in the argument.
# Restricts args to the ones declared.
callLazily = f: a: f (lazyArgsPerParameter f);
callLazily = f: a: f (lazyPerSystemArgsPerParameter f);
in
{
imports = [
(callLazily module allArgs)
(callLazily module allPerSystemArgs)
];
};
};

View file

@ -1,7 +1,7 @@
#
# Nixpkgs module. The only exception to the rule.
#
# Provides a `pkgs` argument in `perSystem`.
# Provides customizable `nixpkgs` and `pkgs` arguments in `perSystem`.
#
# Arguably, this shouldn't be in flake-parts, but in nixpkgs.
# Nixpkgs could define its own module that does this, which would be
@ -11,14 +11,210 @@
# will be accepted into flake-parts, because it's against the
# spirit of Flakes.
#
{ config, flake-parts-lib, inputs, lib, options, ... }:
let
inherit (lib)
last
literalExpression
mapAttrs
mdDoc
mkDefault
mkOption
mkOptionDefault
mkOverride
toList
types
;
inherit (flake-parts-lib)
mkPerSystemOption
mkSubmoduleOptions
;
extendSubModules = type: modules:
type.substSubModules (type.getSubModules ++ modules);
getOptionSubOptions = locSuffix: opt:
let
loc = opt.loc ++ locSuffix;
type = extendSubModules opt.type [{ _module.args.name = last loc; }];
in
type.getSubOptions loc;
mkSubmoduleOptionsWithShorthand =
options: mkOption {
type = types.submoduleWith {
modules = [{ inherit options; }];
shorthandOnlyDefinesConfig = true;
};
};
# Shorthand for `types.submoduleWith`.
submoduleWithModules =
{ ... }@attrs:
modules:
types.submoduleWith (attrs // { modules = toList modules; });
rootConfig = config;
rootOptions = options;
in
{
config = {
perSystem = { inputs', lib, ... }: {
options = {
nixpkgs = {
evals = mkOption {
default = { default = { }; };
description = ''
Configuration for Nixpkgs evaluations of {option}`perSystem.nixpkgs.evals`.
'';
type = types.lazyAttrsOf (submoduleWithModules { } ({ config, name, options, ... }: {
_file = ./nixpkgs.nix;
options = {
input = mkOption {
description = mdDoc ''
Nixpkgs function for evaluation.
'';
default = inputs.nixpkgs or (throw
"flake-parts: The flake does not have a `nixpkgs` input. Please add it, or set `${options.input}` yourself."
);
defaultText = literalExpression ''inputs.nixpkgs'';
type = types.coercedTo types.path import (types.functionTo types.unspecified);
};
settings = mkOption {
default = { };
description = mdDoc ''
Settings argument for the Nixpkgs evaluations of {option}`perSystem.nixpkgs.evals.<name>`.
'';
type = rootOptions.nixpkgs.settings.type;
};
};
config = {
settings = mkDefault rootConfig.nixpkgs.settings;
};
}));
};
settings = mkOption {
default = { };
description = mdDoc ''
Default settings argument for each Nixpkgs evaluations of {option}`nixpkgs.evals`.
'';
# This submodule uses `shorthandOnlyDefinesConfig` because of the top-level `config`
# attribute and to make future upstreaming of this module to Nixpkgs easier.
type = submoduleWithModules { shorthandOnlyDefinesConfig = true; } ({ config, name, options, ... }: {
_file = ./nixpkgs.nix;
freeformType = types.lazyAttrsOf types.raw;
options = {
config = mkOption {
default = { };
description = mdDoc ''
Config for this Nixpkgs evaluation.
'';
type = submoduleWithModules { } {
_file = ./nixpkgs.nix;
freeformType = types.lazyAttrsOf types.raw;
};
};
crossOverlays = mkOption {
default = [ ];
description = mdDoc ''
List of Nixpkgs overlays to apply to target packages only for this Nixpkgs evaluation.
'';
type = types.listOf (types.uniq (types.functionTo (types.functionTo (types.lazyAttrsOf types.unspecified))));
};
overlays = mkOption {
default = [ ];
description = mdDoc ''
List of Nixpkgs overlays for this Nixpkgs evaluation.
'';
type = types.listOf (types.uniq (types.functionTo (types.functionTo (types.lazyAttrsOf types.unspecified))));
};
};
});
};
};
perSystem = mkPerSystemOption ({ config, system, ... }: {
_file = ./nixpkgs.nix;
options = {
nixpkgs = {
evals = mkOption {
default = { };
description = ''
Configuration for Nixpkgs evaluations.
'';
type = types.lazyAttrsOf (submoduleWithModules { } [
({ config, name, options, ... }: {
_file = ./nixpkgs.nix;
options = {
output = mkOption {
default = rootConfig.nixpkgs.evals.${name}.input config.settings;
defaultText = literalExpression
''config.nixpkgs.evals.''${name}.input config.perSystem.nixpkgs.''${name}.settings'';
description = mdDoc ''
Evaluated Nixpkgs.
'';
type = types.raw;
};
settings = mkOption {
default = { };
description = mdDoc ''
Settings argument for the Nixpkgs evaluations of {option}`perSystem.nixpkgs.evals.<name>`.
'';
type = (getOptionSubOptions [ name ] rootOptions.nixpkgs.evals).settings.type;
};
};
config = {
settings = mkDefault rootConfig.nixpkgs.evals.${name}.settings;
};
})
# Separate module, for type merging
({ config, name, options, ... }: {
_file = ./nixpkgs.nix;
options = {
# `mkSubmoduleOptions` can't be used here due to `shorthandOnlyDefinesConfig`.
settings = mkSubmoduleOptionsWithShorthand {
localSystem = mkOption {
apply = lib.systems.elaborate;
default = config.settings.crossSystem;
defaultText = literalExpression ''config.perSystem.nixpkgs.evals.''${name}.crossSystem'';
description = mdDoc ''
Specifies the platform on which Nixpkgs packages should be built.
Also known as `buildPlatform`.
By default, Nixpkgs packages are built on the system where they run, but
you can change where it's built. Setting this option will cause NixOS to
be cross-compiled.
For instance, if you're doing distributed multi-platform deployment,
or if you're building for machines, you can set this to match your
development system and/or build farm.
'';
type = types.either types.str types.attrs;
};
crossSystem = mkOption {
apply = lib.systems.elaborate;
default = system;
defaultText = literalExpression ''system'';
description = mdDoc ''
Specifies the system where packages from this Nixpkgs evaluation will run.
Also known as `hostPlatform`.
To cross-compile, see also {option}`config.perSystem.nixpkgs.evals.<name>.localSystem`.
'';
type = types.either types.str types.attrs;
};
};
};
})
]);
};
};
};
config = {
_module.args.pkgs = lib.mkOptionDefault (
builtins.seq
(inputs'.nixpkgs or (throw "flake-parts: The flake does not have a `nixpkgs` input. Please add it, or set `perSystem._module.args.pkgs` yourself."))
inputs'.nixpkgs.legacyPackages
nixpkgs = {
evals = mapAttrs (name: genericConfig: { }) rootConfig.nixpkgs.evals;
};
};
});
};
config = {
perSystem = { config, nixpkgs, options, ... }: {
_file = ./nixpkgs.nix;
config = {
_module.args.nixpkgs = mkOptionDefault (mapAttrs (name: nixpkgs: nixpkgs.output) config.nixpkgs.evals);
_module.args.pkgs = mkOptionDefault (
nixpkgs.default or (throw "flake-parts: The `perSystem` argument `nixpkgs` does not have a default attribute. Please configure `${options._module.args}.nixpkgs.default`, or set `${options._module.args}.nixpkgs` or `${options._module.args}.pkgs` yourself.")
);
};
};

View file

@ -1,4 +1,4 @@
{ config, lib, flake-parts-lib, self, ... }:
{ config, lib, flake-parts-lib, inputs, self, ... }:
let
inherit (lib)
genAttrs
@ -92,7 +92,7 @@ in
type = mkPerSystemType ({ config, system, ... }: {
_file = ./perSystem.nix;
config = {
_module.args.inputs' = mapAttrs (k: rootConfig.perInput system) self.inputs;
_module.args.inputs' = mapAttrs (k: rootConfig.perInput system) inputs;
_module.args.self' = rootConfig.perInput system self;
# Custom error messages
@ -103,9 +103,9 @@ in
_module.args.moduleWithSystem = throwAliasError "moduleWithSystem";
};
});
apply = modules: system:
apply = module: system:
(lib.evalModules {
inherit modules;
modules = [ module ];
prefix = [ "perSystem" system ];
specialArgs = {
inherit system;

View file

@ -1,7 +1,7 @@
# Definitions can be imported from a separate file like this one
{ self, lib, ... }: {
perSystem = { config, self', inputs', pkgs, ... }: {
{ config, lib, inputs, ... }: {
perSystem = { config, inputs', pkgs, ... }: {
# Definitions like this are entirely equivalent to the ones
# you may have directly in flake.nix.
packages.hello = pkgs.hello;
@ -9,8 +9,8 @@
flake = {
nixosModules.hello = { pkgs, ... }: {
environment.systemPackages = [
# or self.inputs.nixpkgs.legacyPackages.${pkgs.stdenv.hostPlatform.system}.hello
self.packages.${pkgs.stdenv.hostPlatform.system}.hello
# or inputs.nixpkgs.legacyPackages.${pkgs.stdenv.hostPlatform.system}.hello
config.flake.packages.${pkgs.stdenv.hostPlatform.system}.hello
];
};
};

View file

@ -3,16 +3,12 @@
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = inputs@{ flake-parts, nixpkgs, ... }:
outputs = inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ "x86_64-linux" "aarch64-darwin" ];
# This sets `pkgs` to a Nixpkgs with the `allowUnfree` option set.
nixpkgs.settings.config.allowUnfree = true;
perSystem = { pkgs, system, ... }: {
# This sets `pkgs` to a nixpkgs with allowUnfree option set.
_module.args.pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
};
packages.default = pkgs.hello-unfree;
};
};