mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-08-27 02:34:53 +00:00
`attrListWith` re-emits every flag as an `lib.mkOrder` definition, so `argv` and `flags` share one ordering space. Unadorned flags arrived at `lib.modules.defaultOrderPriority`, the same priority as unadorned `argv`, and only landed after `argv` because the declaring module's `config` happened to be collected last. Give flags that carry no ordering property of their own a priority of 1250, between `lib.modules.defaultOrderPriority` and `lib.mkAfter`. Plain flags now provably follow plain `argv`, `lib.mkAfter` on `argv` still places trailing positional arguments after the flags, and an explicit `lib.mkOrder` on a flag is honoured verbatim, which is what interleaving a sub-command among flags needs. Also render path flag values through `pathOrStr`. `lib.cli.toCommandLine` formats values with `lib.generators.mkValueStringDefault`, which has no case for paths and aborts; coercing first yields the store path, matching `argv`. Assisted-by: Claude:claude-opus-5
310 lines
7.8 KiB
Nix
310 lines
7.8 KiB
Nix
# Run:
|
|
# nix-instantiate --eval lib/services/test.nix
|
|
let
|
|
lib = import ../.;
|
|
|
|
inherit (lib) mkOption types;
|
|
|
|
portable-lib = import ./lib.nix { inherit lib; };
|
|
|
|
configured = portable-lib.configure {
|
|
serviceManagerPkgs = throw "do not use pkgs in this test";
|
|
extraRootModules = [ ];
|
|
extraRootSpecialArgs = { };
|
|
};
|
|
|
|
dummyPkg =
|
|
name:
|
|
derivation {
|
|
system = "dummy";
|
|
name = name;
|
|
builder = "/bin/false";
|
|
};
|
|
|
|
exampleConfig = {
|
|
services = {
|
|
service1 = {
|
|
process = {
|
|
argv = [
|
|
"/usr/bin/echo" # *giggles*
|
|
"hello"
|
|
];
|
|
};
|
|
assertions = [
|
|
{
|
|
assertion = false;
|
|
message = "you can't enable this for that reason";
|
|
}
|
|
];
|
|
warnings = [
|
|
"The `foo' service is deprecated and will go away soon!"
|
|
];
|
|
};
|
|
service2 = {
|
|
process = {
|
|
# No meta.mainProgram, because it's supposedly an executable script _file_,
|
|
# not a directory with a bin directory containing the main program.
|
|
argv = [
|
|
(dummyPkg "cowsay.sh")
|
|
"world"
|
|
];
|
|
};
|
|
};
|
|
service3 = {
|
|
process = {
|
|
argv = [ "/bin/false" ];
|
|
};
|
|
services.exclacow = {
|
|
process = {
|
|
argv = [
|
|
(lib.getExe (
|
|
dummyPkg "cowsay-ng"
|
|
// {
|
|
meta.mainProgram = "cowsay";
|
|
}
|
|
))
|
|
"!"
|
|
];
|
|
};
|
|
assertions = [
|
|
{
|
|
assertion = false;
|
|
message = "you can't enable this for such reason";
|
|
}
|
|
];
|
|
warnings = [
|
|
"The `bar' service is deprecated and will go away soon!"
|
|
];
|
|
};
|
|
};
|
|
# The default `flagFormat`, and one flag of every supported value kind.
|
|
flagsDefault = {
|
|
process = {
|
|
argv = [ "/bin/flagged" ];
|
|
flags = {
|
|
"--bool-off" = false;
|
|
"--bool-on" = true;
|
|
"--config" = ./test.nix;
|
|
"--count" = 3;
|
|
"--name" = "example";
|
|
"--unset" = null;
|
|
};
|
|
};
|
|
};
|
|
# A `flagFormat` that joins with `=` and spells out booleans.
|
|
flagsCustomFormat = {
|
|
process = {
|
|
argv = [ "/bin/flagged" ];
|
|
flagFormat = name: {
|
|
option = "--${name}";
|
|
sep = "=";
|
|
explicitBool = true;
|
|
};
|
|
flags = {
|
|
port = 8080;
|
|
quiet = false;
|
|
verbose = true;
|
|
};
|
|
};
|
|
};
|
|
# The list form, which allows a flag to be repeated.
|
|
flagsRepeated = {
|
|
process = {
|
|
argv = [ "/bin/flagged" ];
|
|
flags = [
|
|
{ "--host" = "a"; }
|
|
{ "--host" = "b"; }
|
|
];
|
|
};
|
|
};
|
|
# `argv` and `flags` share one `lib.mkOrder` space.
|
|
flagsOrdering = {
|
|
process = {
|
|
argv = lib.mkMerge [
|
|
(lib.mkBefore [ "/bin/gt" ])
|
|
(lib.mkOrder 800 [ "server" ])
|
|
(lib.mkAfter [ "TRAILING" ])
|
|
];
|
|
flags = lib.mkMerge [
|
|
{ "--listen" = "a"; }
|
|
{ "--disable-landlock" = lib.mkOrder 600 true; }
|
|
];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
exampleEval = lib.evalModules {
|
|
modules = [
|
|
{
|
|
options.services = mkOption {
|
|
type = types.attrsOf configured.serviceSubmodule;
|
|
};
|
|
}
|
|
exampleConfig
|
|
];
|
|
};
|
|
|
|
# Every service carries some assertions that hold; only the violated ones are of interest here.
|
|
failures = lib.filter (a: !a.assertion);
|
|
|
|
filterEval =
|
|
config:
|
|
lib.optionalAttrs (config ? process) {
|
|
inherit (config) warnings;
|
|
assertions = failures config.assertions;
|
|
# Only `argv` is relevant here; `process` also carries the reload options.
|
|
process = { inherit (config.process) argv; };
|
|
}
|
|
// {
|
|
services = lib.mapAttrs (k: filterEval) config.services;
|
|
};
|
|
|
|
test =
|
|
assert
|
|
filterEval exampleEval.config == {
|
|
services = {
|
|
service1 = {
|
|
process = {
|
|
argv = [
|
|
"/usr/bin/echo"
|
|
"hello"
|
|
];
|
|
};
|
|
services = { };
|
|
assertions = [
|
|
{
|
|
assertion = false;
|
|
message = "you can't enable this for that reason";
|
|
}
|
|
];
|
|
warnings = [
|
|
"The `foo' service is deprecated and will go away soon!"
|
|
];
|
|
};
|
|
service2 = {
|
|
process = {
|
|
argv = [
|
|
"${dummyPkg "cowsay.sh"}"
|
|
"world"
|
|
];
|
|
};
|
|
services = { };
|
|
assertions = [ ];
|
|
warnings = [ ];
|
|
};
|
|
service3 = {
|
|
process = {
|
|
argv = [ "/bin/false" ];
|
|
};
|
|
services.exclacow = {
|
|
process = {
|
|
argv = [
|
|
"${dummyPkg "cowsay-ng"}/bin/cowsay"
|
|
"!"
|
|
];
|
|
};
|
|
services = { };
|
|
assertions = [
|
|
{
|
|
assertion = false;
|
|
message = "you can't enable this for such reason";
|
|
}
|
|
];
|
|
warnings = [ "The `bar' service is deprecated and will go away soon!" ];
|
|
};
|
|
assertions = [ ];
|
|
warnings = [ ];
|
|
};
|
|
flagsDefault = {
|
|
process = {
|
|
argv = [
|
|
"/bin/flagged"
|
|
"--bool-on"
|
|
"--config"
|
|
"${./test.nix}"
|
|
"--count"
|
|
"3"
|
|
"--name"
|
|
"example"
|
|
];
|
|
};
|
|
services = { };
|
|
assertions = [ ];
|
|
warnings = [ ];
|
|
};
|
|
flagsCustomFormat = {
|
|
process = {
|
|
argv = [
|
|
"/bin/flagged"
|
|
"--port=8080"
|
|
"--quiet=false"
|
|
"--verbose=true"
|
|
];
|
|
};
|
|
services = { };
|
|
assertions = [ ];
|
|
warnings = [ ];
|
|
};
|
|
flagsRepeated = {
|
|
process = {
|
|
argv = [
|
|
"/bin/flagged"
|
|
"--host"
|
|
"a"
|
|
"--host"
|
|
"b"
|
|
];
|
|
};
|
|
services = { };
|
|
assertions = [ ];
|
|
warnings = [ ];
|
|
};
|
|
flagsOrdering = {
|
|
process = {
|
|
argv = [
|
|
"/bin/gt"
|
|
"--disable-landlock"
|
|
"server"
|
|
"--listen"
|
|
"a"
|
|
"TRAILING"
|
|
];
|
|
};
|
|
services = { };
|
|
assertions = [ ];
|
|
warnings = [ ];
|
|
};
|
|
};
|
|
};
|
|
|
|
assert
|
|
portable-lib.getWarnings [ "service1" ] exampleEval.config.services.service1 == [
|
|
"in service1: The `foo' service is deprecated and will go away soon!"
|
|
];
|
|
|
|
assert
|
|
failures (portable-lib.getAssertions [ "service1" ] exampleEval.config.services.service1) == [
|
|
{
|
|
message = "in service1: you can't enable this for that reason";
|
|
assertion = false;
|
|
}
|
|
];
|
|
|
|
assert
|
|
portable-lib.getWarnings [ "service3" ] exampleEval.config.services.service3 == [
|
|
"in service3.services.exclacow: The `bar' service is deprecated and will go away soon!"
|
|
];
|
|
assert
|
|
failures (portable-lib.getAssertions [ "service3" ] exampleEval.config.services.service3) == [
|
|
{
|
|
message = "in service3.services.exclacow: you can't enable this for such reason";
|
|
assertion = false;
|
|
}
|
|
];
|
|
|
|
"ok";
|
|
|
|
in
|
|
test
|