diff --git a/pkgs/build-support/replace-vars/default.nix b/pkgs/build-support/replace-vars/default.nix new file mode 100644 index 000000000000..a6fc94f4f376 --- /dev/null +++ b/pkgs/build-support/replace-vars/default.nix @@ -0,0 +1,77 @@ +{ 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. + + Any remaining text that matches `@[A-Za-z_][0-9A-Za-z_'-]@` in the output after replacement + has occurred will cause a build failure. + + # 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). + + # 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: [ + "--replace-fail" + (lib.escapeShellArg "@${name}@") + (lib.escapeShellArg value) + ]; + + replacements = lib.concatLists (lib.mapAttrsToList subst-var-by 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 + regex = lib.escapeShellArg "@[a-zA-Z_][0-9A-Za-z_'-]*@"; + in + '' + runHook preCheck + if grep -qe ${regex} "$out"; then + echo The following look like unsubstituted Nix identifiers that remain in "$out": + grep -oe ${regex} "$out" + echo Use the more precise '`substitute`' function if this check is in error. + exit 1 + fi + runHook postCheck + ''; +} diff --git a/pkgs/test/default.nix b/pkgs/test/default.nix index 2d8d1c1c6842..29125f1d2979 100644 --- a/pkgs/test/default.nix +++ b/pkgs/test/default.nix @@ -183,5 +183,7 @@ with pkgs; systemd = callPackage ./systemd { }; + replaceVars = recurseIntoAttrs (callPackage ./replace-vars { }); + substitute = recurseIntoAttrs (callPackage ./substitute { }); } diff --git a/pkgs/test/replace-vars/default.nix b/pkgs/test/replace-vars/default.nix new file mode 100644 index 000000000000..76dc81de49c8 --- /dev/null +++ b/pkgs/test/replace-vars/default.nix @@ -0,0 +1,74 @@ +{ + replaceVars, + emptyDirectory, + emptyFile, + runCommand, + testers, +}: +let + inherit (testers) testEqualContents testBuildFailure; +in +{ + # Success case for `replaceVars`. + replaceVars = testEqualContents { + assertion = "replaceVars"; + actual = replaceVars ./source.txt { + free = "free"; + "equal in" = "are the same in"; + brotherhood = "shared humanity"; + }; + + expected = builtins.toFile "expected" '' + All human beings are born free and are the same in dignity and rights. + They are endowed with reason and conscience and should act towards + one another in a spirit of shared humanity. + + -- eroosevelt@humanrights.un.org + ''; + }; + + # There might eventually be a usecase for this, but it's not supported at the moment. + replaceVars-fails-on-directory = + runCommand "replaceVars-fails" { failed = testBuildFailure (replaceVars emptyDirectory { }); } + '' + grep -e "ERROR: file.*empty-directory.*does not exist" $failed/testBuildFailure.log + touch $out + ''; + + replaceVars-fails-in-build-phase = + runCommand "replaceVars-fails" + { failed = testBuildFailure (replaceVars emptyFile { not-found = "boo~"; }); } + '' + grep -e "ERROR: pattern @not-found@ doesn't match anything in file.*empty-file" $failed/testBuildFailure.log + touch $out + ''; + + replaceVars-fails-in-check-phase = + runCommand "replaceVars-fails" + { + failed = + let + src = builtins.toFile "source.txt" '' + Header. + before @whatIsThis@ middle @It'sOdd2Me@ after. + @cannot detect due to space@ + Trailer. + ''; + in + testBuildFailure (replaceVars src { }); + } + '' + grep -e "unsubstituted Nix identifiers.*source.txt" $failed/testBuildFailure.log + grep -F "@whatIsThis@" $failed/testBuildFailure.log + grep -F "@It'sOdd2Me@" $failed/testBuildFailure.log + grep -F 'more precise `substitute` function' $failed/testBuildFailure.log + + # Shouldn't see irrelevant details. + ! grep -q -E -e "Header|before|middle|after|Trailer" $failed/testBuildFailure.log + + # Shouldn't see the "cannot detect" version. + ! grep -q -F "cannot detect due to space" $failed/testBuildFailure.log + + touch $out + ''; +} diff --git a/pkgs/test/replace-vars/source.txt b/pkgs/test/replace-vars/source.txt new file mode 100644 index 000000000000..b3d293859e8b --- /dev/null +++ b/pkgs/test/replace-vars/source.txt @@ -0,0 +1,5 @@ +All human beings are born @free@ and @equal in@ dignity and rights. +They are endowed with reason and conscience and should act towards +one another in a spirit of @brotherhood@. + + -- eroosevelt@humanrights.un.org diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 6782cf4d67c4..7da267e935d4 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -1335,6 +1335,8 @@ with pkgs; replaceDependency = callPackage ../build-support/replace-dependency.nix { }; + replaceVars = callPackage ../build-support/replace-vars { }; + nukeReferences = callPackage ../build-support/nuke-references { inherit (darwin) signingUtils; };