replaceVarsWith: init

Takes the extended features of nix substituteAll to a replaceVars
variant to get rid of those cases that use substituteAll to build a full
package with meta information etc.
This commit is contained in:
Wolfgang Walther
2024-11-30 18:16:56 +01:00
parent 942be98f98
commit 4ce241c882
15 changed files with 278 additions and 179 deletions

View File

@@ -43,7 +43,7 @@ let
manPage = ./manpages/nixos-version.8;
};
nixos-install = pkgs.nixos-install.override { nix = config.nix.package; };
nixos-install = pkgs.nixos-install.override { };
nixos-rebuild = pkgs.nixos-rebuild.override { nix = config.nix.package; };
defaultConfigTemplate = ''

View File

@@ -1,86 +0,0 @@
{ lib, stdenvNoCC }:
/**
`replaceVars` is a wrapper around the [bash function `substitute`](https://nixos.org/manual/nixpkgs/stable/#fun-substitute)
in the stdenv. It allows for terse replacement of names in the specified path, while checking
for common mistakes such as naming a replacement that does nothing or forgetting a variable which
needs to be replaced.
As with the [`--subst-var-by`](https://nixos.org/manual/nixpkgs/stable/#fun-substitute-subst-var-by)
flag, names are encoded as `@name@` in the provided file at the provided path.
Any unmatched variable names in the file at the provided path will cause a build failure.
By default, any remaining text that matches `@[A-Za-z_][0-9A-Za-z_'-]@` in the output after replacement
has occurred will cause a build failure. Variables can be excluded from this check by passing "null" for them.
# Inputs
`path` ([Store Path](https://nixos.org/manual/nix/latest/store/store-path.html#store-path) String)
: The file in which to replace variables.
`attrs` (AttrsOf String)
: Each entry in this set corresponds to a `--subst-var-by` entry in [`substitute`](https://nixos.org/manual/nixpkgs/stable/#fun-substitute) or
null to keep it unchanged.
# Example
```nix
{ replaceVars }:
replaceVars ./greeting.txt { world = "hello"; }
```
See `../../test/replace-vars/default.nix` for tests of this function.
*/
path: attrs:
let
# We use `--replace-fail` instead of `--subst-var-by` so that if the thing isn't there, we fail.
subst-var-by =
name: value:
lib.optionals (value != null) [
"--replace-fail"
(lib.escapeShellArg "@${name}@")
(lib.escapeShellArg value)
];
replacements = lib.concatLists (lib.mapAttrsToList subst-var-by attrs);
left-overs = map ({ name, ... }: name) (
builtins.filter ({ value, ... }: value == null) (lib.attrsToList attrs)
);
in
stdenvNoCC.mkDerivation {
name = baseNameOf (toString path);
src = path;
doCheck = true;
dontUnpack = true;
preferLocalBuild = true;
allowSubstitutes = false;
buildPhase = ''
runHook preBuild
substitute "$src" "$out" ${lib.concatStringsSep " " replacements}
runHook postBuild
'';
# Look for Nix identifiers surrounded by `@` that aren't substituted.
checkPhase =
let
lookahead =
if builtins.length left-overs == 0 then "" else "(?!${builtins.concatStringsSep "|" left-overs}@)";
regex = lib.escapeShellArg "@${lookahead}[a-zA-Z_][0-9A-Za-z_'-]*@";
in
''
runHook preCheck
if grep -Pqe ${regex} "$out"; then
echo The following look like unsubstituted Nix identifiers that remain in "$out":
grep -Poe ${regex} "$out"
echo Use the more precise '`substitute`' function if this check is in error.
exit 1
fi
runHook postCheck
'';
}

View File

@@ -0,0 +1,131 @@
{ lib, stdenvNoCC }:
/**
`replaceVarsWith` is a wrapper around the [bash function `substitute`](https://nixos.org/manual/nixpkgs/stable/#fun-substitute)
in the stdenv. It allows for terse replacement of names in the specified path, while checking
for common mistakes such as naming a replacement that does nothing or forgetting a variable which
needs to be replaced.
As with the [`--subst-var-by`](https://nixos.org/manual/nixpkgs/stable/#fun-substitute-subst-var-by)
flag, names are encoded as `@name@` in the provided file at the provided path.
Any unmatched variable names in the file at the provided path will cause a build failure.
By default, any remaining text that matches `@[A-Za-z_][0-9A-Za-z_'-]@` in the output after replacement
has occurred will cause a build failure. Variables can be excluded from this check by passing "null" for them.
# Inputs
`src` ([Store Path](https://nixos.org/manual/nix/latest/store/store-path.html#store-path) String)
: The file in which to replace variables.
`replacements` (AttrsOf String)
: Each entry in this set corresponds to a `--subst-var-by` entry in [`substitute`](https://nixos.org/manual/nixpkgs/stable/#fun-substitute) or
null to keep it unchanged.
`dir` (String)
: Sub directory in $out to store the result in. Commonly set to "bin".
`isExecutable` (Boolean)
: Whether to mark the output file as executable.
Most arguments supported by mkDerivation are also supported, with some exceptions for which
an error will be thrown.
# Example
```nix
{ replaceVarsWith }:
replaceVarsWith {
src = ./my-setup-hook.sh;
replacements = { world = "hello"; };
dir = "bin";
isExecutable = true;
}
```
See `../../test/replace-vars/default.nix` for tests of this function. Also see `replaceVars` for a short
version with src and replacements only.
*/
{
src,
replacements,
dir ? null,
isExecutable ? false,
...
}@attrs:
let
# We use `--replace-fail` instead of `--subst-var-by` so that if the thing isn't there, we fail.
subst-var-by =
name: value:
lib.optionals (value != null) [
"--replace-fail"
(lib.escapeShellArg "@${name}@")
(lib.escapeShellArg value)
];
substitutions = lib.concatLists (lib.mapAttrsToList subst-var-by replacements);
left-overs = map ({ name, ... }: name) (
builtins.filter ({ value, ... }: value == null) (lib.attrsToList replacements)
);
optionalAttrs =
if (builtins.intersectAttrs attrs forcedAttrs == { }) then
builtins.removeAttrs attrs [ "replacements" ]
else
throw "Passing any of ${builtins.concatStringsSep ", " (builtins.attrNames forcedAttrs)} to replaceVarsWith is not supported.";
forcedAttrs = {
doCheck = true;
dontUnpack = true;
preferLocalBuild = true;
allowSubstitutes = false;
buildPhase = ''
runHook preBuild
target=$out
if test -n "$dir"; then
target=$out/$dir/$name
mkdir -p $out/$dir
fi
substitute "$src" "$target" ${lib.concatStringsSep " " substitutions}
if test -n "$isExecutable"; then
chmod +x $target
fi
runHook postBuild
'';
# Look for Nix identifiers surrounded by `@` that aren't substituted.
checkPhase =
let
lookahead =
if builtins.length left-overs == 0 then "" else "(?!${builtins.concatStringsSep "|" left-overs}@)";
regex = lib.escapeShellArg "@${lookahead}[a-zA-Z_][0-9A-Za-z_'-]*@";
in
''
runHook preCheck
if grep -Pqe ${regex} "$out"; then
echo The following look like unsubstituted Nix identifiers that remain in "$out":
grep -Poe ${regex} "$out"
echo Use the more precise '`substitute`' function if this check is in error.
exit 1
fi
runHook postCheck
'';
};
in
stdenvNoCC.mkDerivation (
{
name = baseNameOf (toString src);
}
// optionalAttrs
// forcedAttrs
)

View File

@@ -0,0 +1,36 @@
{ replaceVarsWith }:
/**
`replaceVars` is a wrapper around the [bash function `substitute`](https://nixos.org/manual/nixpkgs/stable/#fun-substitute)
in the stdenv. It allows for terse replacement of names in the specified path, while checking
for common mistakes such as naming a replacement that does nothing or forgetting a variable which
needs to be replaced.
As with the [`--subst-var-by`](https://nixos.org/manual/nixpkgs/stable/#fun-substitute-subst-var-by)
flag, names are encoded as `@name@` in the provided file at the provided path.
Any unmatched variable names in the file at the provided path will cause a build failure.
By default, any remaining text that matches `@[A-Za-z_][0-9A-Za-z_'-]@` in the output after replacement
has occurred will cause a build failure. Variables can be excluded from this check by passing "null" for them.
# Inputs
`src` ([Store Path](https://nixos.org/manual/nix/latest/store/store-path.html#store-path) String)
: The file in which to replace variables.
`replacements` (AttrsOf String)
: Each entry in this set corresponds to a `--subst-var-by` entry in [`substitute`](https://nixos.org/manual/nixpkgs/stable/#fun-substitute) or
null to keep it unchanged.
# Example
```nix
{ replaceVars }:
replaceVars ./greeting.txt { world = "hello"; }
```
See `../../test/replace-vars/default.nix` for tests of this function.
*/
src: replacements: replaceVarsWith { inherit src replacements; }

View File

@@ -1,4 +1,4 @@
#! @shell@
#! @runtimeShell@
set -o errexit
set -o nounset

View File

@@ -1,14 +1,14 @@
# expr and script based on our lsb_release
{ stdenv
, lib
, substituteAll
, replaceVarsWith
, coreutils
, getopt
, runtimeShell
, modDirVersion ? ""
, forPlatform ? stdenv.buildPlatform
}:
substituteAll {
replaceVarsWith {
name = "uname";
src = ./deterministic-uname.sh;
@@ -16,29 +16,31 @@ substituteAll {
dir = "bin";
isExecutable = true;
inherit coreutils getopt;
replacements = {
inherit coreutils getopt runtimeShell;
uSystem = if forPlatform.uname.system != null then forPlatform.uname.system else "unknown";
inherit (forPlatform.uname) processor;
uSystem = if forPlatform.uname.system != null then forPlatform.uname.system else "unknown";
inherit (forPlatform.uname) processor;
# uname -o
# maybe add to lib/systems/default.nix uname attrset
# https://github.com/coreutils/coreutils/blob/7fc84d1c0f6b35231b0b4577b70aaa26bf548a7c/src/uname.c#L373-L374
# https://stackoverflow.com/questions/61711186/where-does-host-operating-system-in-uname-c-comes-from
# https://github.com/coreutils/gnulib/blob/master/m4/host-os.m4
operatingSystem =
if forPlatform.isLinux
then "GNU/Linux"
else if forPlatform.isDarwin
then "Darwin" # darwin isn't in host-os.m4 so where does this come from?
else if forPlatform.isFreeBSD
then "FreeBSD"
else "unknown";
# uname -o
# maybe add to lib/systems/default.nix uname attrset
# https://github.com/coreutils/coreutils/blob/7fc84d1c0f6b35231b0b4577b70aaa26bf548a7c/src/uname.c#L373-L374
# https://stackoverflow.com/questions/61711186/where-does-host-operating-system-in-uname-c-comes-from
# https://github.com/coreutils/gnulib/blob/master/m4/host-os.m4
operatingSystem =
if forPlatform.isLinux
then "GNU/Linux"
else if forPlatform.isDarwin
then "Darwin" # darwin isn't in host-os.m4 so where does this come from?
else if forPlatform.isFreeBSD
then "FreeBSD"
else "unknown";
# in os-specific/linux module packages
# --replace '$(shell uname -r)' "${kernel.modDirVersion}" \
# is a common thing to do.
modDirVersion = if modDirVersion != "" then modDirVersion else "unknown";
# in os-specific/linux module packages
# --replace '$(shell uname -r)' "${kernel.modDirVersion}" \
# is a common thing to do.
modDirVersion = if modDirVersion != "" then modDirVersion else "unknown";
};
meta = with lib; {
description = "Print certain system information (hardcoded with lib/system values)";

View File

@@ -1,26 +1,28 @@
{
replaceVars,
runCommand,
replaceVarsWith,
lib,
runtimeShell,
coreutils,
getopt,
}:
runCommand "lsb_release"
{
meta = with lib; {
description = "Prints certain LSB (Linux Standard Base) and Distribution information";
mainProgram = "lsb_release";
license = [ licenses.mit ];
maintainers = with maintainers; [ primeos ];
platforms = platforms.linux;
};
}
''
install -Dm 555 ${
replaceVars ./lsb_release.sh {
inherit runtimeShell coreutils getopt;
}
} $out/bin/lsb_release
''
replaceVarsWith {
name = "lsb_release";
src = ./lsb_release.sh;
dir = "bin";
isExecutable = true;
replacements = {
inherit coreutils getopt runtimeShell;
};
meta = with lib; {
description = "Prints certain LSB (Linux Standard Base) and Distribution information";
mainProgram = "lsb_release";
license = [ licenses.mit ];
maintainers = with maintainers; [ primeos ];
platforms = platforms.linux;
};
}

View File

@@ -1,13 +1,17 @@
{
substituteAll,
replaceVarsWith,
runtimeShell,
installShellFiles,
}:
substituteAll {
replaceVarsWith {
name = "nixos-build-vms";
src = ./nixos-build-vms.sh;
inherit runtimeShell;
buildVms = ./build-vms.nix;
replacements = {
inherit runtimeShell;
buildVms = ./build-vms.nix;
};
dir = "bin";
isExecutable = true;

View File

@@ -9,7 +9,7 @@ use Getopt::Long qw(:config gnu_getopt no_bundling);
use Cwd 'abs_path';
use Time::HiRes;
my $nsenter = "@utillinux@/bin/nsenter";
my $nsenter = "@util-linux@/bin/nsenter";
my $su = "@su@";
my $configurationDirectory = "@configurationDirectory@";

View File

@@ -1,4 +1,4 @@
{ substituteAll
{ replaceVarsWith
, perl
, shadow
, util-linux
@@ -7,33 +7,35 @@
, nixosTests
}:
substituteAll {
name = "nixos-container";
dir = "bin";
isExecutable = true;
src = ./nixos-container.pl;
replaceVarsWith {
name = "nixos-container";
dir = "bin";
isExecutable = true;
src = ./nixos-container.pl;
replacements = {
perl = perl.withPackages (p: [ p.FileSlurp ]);
su = "${shadow.su}/bin/su";
utillinux = util-linux;
inherit configurationDirectory stateDirectory;
inherit configurationDirectory stateDirectory util-linux;
};
passthru = {
tests = {
inherit (nixosTests)
containers-imperative
containers-ip
containers-tmpfs
containers-ephemeral
containers-unified-hierarchy
;
};
passthru = {
tests = {
inherit (nixosTests)
containers-imperative
containers-ip
containers-tmpfs
containers-ephemeral
containers-unified-hierarchy
;
};
};
postInstall = ''
t=$out/share/bash-completion/completions
mkdir -p $t
cp ${./nixos-container-completion.sh} $t/nixos-container
'';
meta.mainProgram = "nixos-container";
postInstall = ''
t=$out/share/bash-completion/completions
mkdir -p $t
cp ${./nixos-container-completion.sh} $t/nixos-container
'';
meta.mainProgram = "nixos-container";
}

View File

@@ -1,19 +1,21 @@
{
lib,
substituteAll,
replaceVarsWith,
runtimeShell,
installShellFiles,
util-linuxMinimal,
}:
substituteAll {
replaceVarsWith {
name = "nixos-enter";
src = ./nixos-enter.sh;
inherit runtimeShell;
replacements = {
inherit runtimeShell;
path = lib.makeBinPath [
util-linuxMinimal
];
path = lib.makeBinPath [
util-linuxMinimal
];
};
dir = "bin";
isExecutable = true;

View File

@@ -1,24 +1,25 @@
{
lib,
substituteAll,
replaceVarsWith,
runtimeShell,
installShellFiles,
nix,
jq,
nixos-enter,
util-linuxMinimal,
}:
substituteAll {
replaceVarsWith {
name = "nixos-install";
src = ./nixos-install.sh;
inherit runtimeShell nix;
replacements = {
inherit runtimeShell;
path = lib.makeBinPath [
jq
nixos-enter
util-linuxMinimal
];
path = lib.makeBinPath [
jq
nixos-enter
util-linuxMinimal
];
};
dir = "bin";
isExecutable = true;

View File

@@ -1,9 +1,12 @@
{ substituteAll, runtimeShell }:
{ replaceVarsWith, runtimeShell }:
substituteAll {
replaceVarsWith {
name = "xargs-j";
shell = runtimeShell;
src = ./xargs-j.sh;
dir = "bin";
isExecutable = true;
replacements = {
inherit runtimeShell;
};
}

View File

@@ -1,4 +1,4 @@
#! @shell@
#! @runtimeShell@
declare -a args=()

View File

@@ -770,7 +770,9 @@ with pkgs;
cutoffPackages = [ newDependency ];
};
replaceVars = callPackage ../build-support/replace-vars { };
replaceVarsWith = callPackage ../build-support/replace-vars/replace-vars-with.nix { };
replaceVars = callPackage ../build-support/replace-vars/replace-vars.nix { };
replaceDirectDependencies = callPackage ../build-support/replace-direct-dependencies.nix { };