Skip to content

Commit

Permalink
replaceVarsWith: init
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
wolfgangwalther committed Dec 14, 2024
1 parent 942be98 commit 4ce241c
Show file tree
Hide file tree
Showing 15 changed files with 281 additions and 182 deletions.
2 changes: 1 addition & 1 deletion nixos/modules/installer/tools/tools.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ''
Expand Down
86 changes: 0 additions & 86 deletions pkgs/build-support/replace-vars/default.nix

This file was deleted.

131 changes: 131 additions & 0 deletions pkgs/build-support/replace-vars/replace-vars-with.nix
Original file line number Diff line number Diff line change
@@ -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
)
36 changes: 36 additions & 0 deletions pkgs/build-support/replace-vars/replace-vars.nix
Original file line number Diff line number Diff line change
@@ -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; }
2 changes: 1 addition & 1 deletion pkgs/by-name/de/deterministic-uname/deterministic-uname.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#! @shell@
#! @runtimeShell@

set -o errexit
set -o nounset
Expand Down
54 changes: 28 additions & 26 deletions pkgs/by-name/de/deterministic-uname/package.nix
Original file line number Diff line number Diff line change
@@ -1,44 +1,46 @@
# 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;

dir = "bin";
isExecutable = true;

inherit coreutils getopt;

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";

# 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";
replacements = {
inherit coreutils getopt runtimeShell;

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";

# 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)";
Expand Down
40 changes: 21 additions & 19 deletions pkgs/by-name/ls/lsb-release/package.nix
Original file line number Diff line number Diff line change
@@ -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;
};
}
Loading

0 comments on commit 4ce241c

Please sign in to comment.