From fd1485d077c06414752a90d2aef84b09d8a4cca2 Mon Sep 17 00:00:00 2001 From: tv Date: Thu, 12 Nov 2026 02:33:44 +0000 Subject: populate: name inputs of generated flakes validly Generated flakes used source names as input names, but Nix accepts fewer names there than populate does, e.g. not alt-1.0 or self. Following such a source failed late, at locking, with an error mentioning neither krops nor the source. Names Nix doesn't accept get replaced by ones derived from them. An edge case follows a source named alt-1.0. --- pkgs/populate/default.nix | 21 ++++++++++++++------- tests/scenarios.nix | 11 +++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/pkgs/populate/default.nix b/pkgs/populate/default.nix index 5939742..3e5f47b 100644 --- a/pkgs/populate/default.nix +++ b/pkgs/populate/default.nix @@ -57,6 +57,13 @@ let flakesDir = "${target.path}/.populate/flakes"; generated = filterAttrs (_: source: flakeFollows source != []) sources; names = attrNames generated; + # Source names become input names in generated flakes, where Nix accepts + # fewer names than populate. Others get replaced by one derived from + # them. + inputName = name: + if builtins.match "[a-zA-Z][a-zA-Z0-9_-]*" name != null && name != "self" + then name + else "source-${substring 0 16 (builtins.hashString "sha256" name)}"; drawnFrom = name: map (x: x.source) (flakeFollows generated.${name}); sorted = toposort (a: b: elem a (drawnFrom b)) names; # Generated flakes refer to each other's generated flakes, so they don't @@ -88,12 +95,12 @@ let else (findFirst (y: y.path == parent) null follows).source; rest = if parent == null then x.path else drop (length parent) x.path; in - "inputs.${toJSON base}" + + "inputs.${toJSON (inputName base)}" + # A source with a generated flake has itself as input therein. optionalString (parent != null && generated ? ${base}) - ".inputs.${toJSON base}" + + ".inputs.${toJSON (inputName base)}" + concatMapStrings (name: ".inputs.${toJSON name}") rest + - ".follows = ${toJSON x.source};"; + ".follows = ${toJSON (inputName x.source)};"; in /* sh */ '' ( url=$(flake_url ${quote name}) @@ -105,19 +112,19 @@ let cd ${quote "${flakesDir}/${name}"} { echo '{' - printf ' inputs.%s.url = "%s";\n' ${quote (toJSON name)} "$url" + printf ' inputs.%s.url = "%s";\n' ${quote (toJSON (inputName name))} "$url" ${concatImapStrings (i: source: /* sh */ '' - printf ' inputs.%s.url = "%s";\n' ${quote (toJSON source)} "$url${toString i}" + printf ' inputs.%s.url = "%s";\n' ${quote (toJSON (inputName source))} "$url${toString i}" ${optionalString (!generated ? ${source}) /* sh */ '' if ! test -e "$(readlink -f ${quote "${target.path}/${source}"})"/flake.nix; then - printf ' inputs.%s.flake = false;\n' ${quote (toJSON source)} + printf ' inputs.%s.flake = false;\n' ${quote (toJSON (inputName source))} fi ''} '') drawn} ${concatMapStrings (x: /* sh */ '' printf ' %s\n' ${quote (override x)} '') follows} - printf ' outputs = inputs: inputs.%s;\n' ${quote (toJSON name)} + printf ' outputs = inputs: inputs.%s;\n' ${quote (toJSON (inputName name))} echo '}' } > flake.nix # Not just ., which Nix takes for the enclosing Git work tree, if any. diff --git a/tests/scenarios.nix b/tests/scenarios.nix index 0e54054..6dbb84d 100644 --- a/tests/scenarios.nix +++ b/tests/scenarios.nix @@ -945,6 +945,17 @@ assert !(krops.isLocalTarget (targets.remote "/")); } ]; }) + (s "source-name" { + phases = twice { + sources = { + "alt-1.0".file = "/fixtures/alt"; + x.file = { path = "/fixtures/filesrc"; inputs.dep.follows = "alt-1.0"; }; + }; + check = '' + expect_eq msg "$(nix_eval_online "$T"/.populate/flakes/x#msg)" top:alt,sub + ''; + }; + }) ] ++ lib.optional (targetKind == "local") # The target has the path the input is locked to. (s "uselocal" { -- cgit v1.3.1