diff --git a/doc/build-helpers/special.md b/doc/build-helpers/special.md index 9da278f094dd8..58ff18054c6e5 100644 --- a/doc/build-helpers/special.md +++ b/doc/build-helpers/special.md @@ -3,6 +3,7 @@ This chapter describes several special build helpers. ```{=include=} sections +special/buildenv.section.md special/fakenss.section.md special/fhs-environments.section.md special/makesetuphook.section.md diff --git a/doc/build-helpers/special/buildenv.section.md b/doc/build-helpers/special/buildenv.section.md new file mode 100644 index 0000000000000..7ae1dbb94e368 --- /dev/null +++ b/doc/build-helpers/special/buildenv.section.md @@ -0,0 +1,99 @@ +# buildEnv {#sec-buildEnv} + +`buildEnv` constructs a derivation containing directories and symbolic links, which resembles the profile layout where a list of derivations or store paths are installed. + +Unlike [`symlinkJoin`](#trivial-builder-symlinkJoin), `buildEnv` takes special care of the outputs to link and checks for content collisions across the paths by default. +A common use case for `buildEnv` is constructing environment wrappers, such as an interpreter with modules or a program with extensions. +For example, [`python.withPackage`](#attributes-on-interpreters-packages) is based on `buildEnv`. + +## Arguments {#sec-buildEnv-arguments} + +`buildEnv` takes [fixed-point arguments (`buildEnv (finalAttrs: { })`)](#chap-build-helpers-finalAttrs) as well as a plain attribute set. + +Unless otherwise noted, arguments can be overridden directly using [`.overrideAttrs`](#sec-pkg-overrideAttrs). + +- `name` or `pname` and `version` (required): + The name of the environment. + +- `paths` (required): + The derivations or store paths to symlink ("install"). + + The elements can be any path-like object that string-interpolates to a store path. + The priority of each path is taken from `.meta.priority` and falls back to `lib.meta.defaultPriority` if not set. + + The argument `paths` is passed as attribute `passthru.paths` to prevent unexpected context pollution. + `passthru.paths` can be overridden with `.overrideAttrs`. + +- `extraOutputsToInstall` (default to `[ ]`): + Package outputs to include in addition to what `meta.outputsToInstall` specifies. + +- `includeClosures` (default to `false`): + Whether to include closures of all input paths. + The list of the closure paths are constructed with `writeClosure`. + They are installed with lower priority and with build-time exceptions silenced. + +- `extraPrefix` (default to `""`): + Root the result in directory `"$out${extraPrefix}"`, e.g. `"/share"`. + +- `ignoreCollisions` (default: `false`): + Don't fail the build upon content collisions. + +- `checkCollisionContents` (default: `true`): + If there is a collision, check whether the contents and permissions match; and only if not, throw a collision error. + +- `ignoreSingleFileOutputs` (default: `false`): + Don't fail the build upon single-file outputs. + +- `manifest` (default: `""`): + The manifest file (if any). A symlink `$out/manifest` will be created to it. + +- `pathsToLink` (default: `[ "/" ]`): + The paths (relative to each element of `paths`) that we want to symlink (e.g., `["/bin"]`). + Any file outside the directories in this list won't be symlinked into the produced environment. + +- `postBuild` (default: `""`): + Shell commands to run after building the symlink tree. + +- `passthru` and `meta` (default: `{ }`): + `stdenv.mkDerivation`-supported attributes not passing down to `builtins.derivation`. + +- `derivationArgs` (default: `{ }`): + Additional `stdenv.mkDerivation` arguments, such as `nativeBuildInputs`/`buildInputs` for `postBuild` dependencies and setup hooks. + + `derivationArgs` is not passed down to `stdenv.mkDerivation`. + Override its attributes directly via `.overrideAttrs` and reference directly via `finalAttrs`. + +## Build-time exceptions {#sec-buildEnv-exceptions} + +There are situations where the specified `paths` might not produce sensible profile layout. +By default, the builder fails early upon detecting these exceptions. +`buildEnv` provides arguments to fine-tune or ignore certain exceptions. + +### Path collisions {#ssec-buildEnv-collisions} + +Path collisions occur when files provided by two more output paths with the same priority overlap with each other, making the result profile layout potentially affected by the order of elements of `paths`. +This is undesirable in several use cases, such as when `paths` are determined by merging Nix modules. + +If the argument `checkCollisionContents` is `true`, the builder checks whether the overlapping paths share the same content and mode, and fails only if not. + +The argument `ignoreCollisions` silence the collision checks and allow the files to be overwritten based on the order of chosen output paths. + +In addition to silencing this exception with `ignoreCollisions`, one can also adjust the priority of colliding packages and store paths. +Store paths can specify priority in the form + +```nix +{ + outPath = ; + meta.priority = ; +} +``` + +And [`lib.meta.setPrio`](#function-library-lib.meta.setPrio)-related Nixpkgs Library functions also apply to a string-like attribute set (`{ outPath = ; }`). + +### Single-file outputs {#ssec-buildEnv-singleFileOutputs} + +When an output path provides a single file instead of a directory, it inherently cannot merge into the result layout. +All discoverable packages should configure their `meta.outputsToInstall` correctly, so that single-file outputs won't be installed into a profile. + +Set `ignoreSingleFileOutputs` to `true` to drop all single-file output paths silently. +This option is useful when the specified paths contain the output paths of package tests. diff --git a/doc/redirects.json b/doc/redirects.json index 752f14e694f79..9d581d45150c5 100644 --- a/doc/redirects.json +++ b/doc/redirects.json @@ -259,6 +259,9 @@ "sec-build-helper-extendMkDerivation": [ "index.html#sec-build-helper-extendMkDerivation" ], + "sec-buildEnv-exceptions": [ + "index.html#sec-buildEnv-exceptions" + ], "sec-building-packages-with-llvm": [ "index.html#sec-building-packages-with-llvm" ], @@ -647,6 +650,12 @@ "sec-treefmt-options-reference": [ "index.html#sec-treefmt-options-reference" ], + "ssec-buildEnv-collisions": [ + "index.html#ssec-buildEnv-collisions" + ], + "ssec-buildEnv-singleFileOutputs": [ + "index.html#ssec-buildEnv-singleFileOutputs" + ], "ssec-cosmic-common-issues": [ "index.html#ssec-cosmic-common-issues" ], @@ -2056,6 +2065,12 @@ "chap-special": [ "index.html#chap-special" ], + "sec-buildEnv": [ + "index.html#sec-buildEnv" + ], + "sec-buildEnv-arguments": [ + "index.html#sec-buildEnv-arguments" + ], "sec-fakeNss": [ "index.html#sec-fakeNss" ], diff --git a/doc/release-notes/rl-2511.section.md b/doc/release-notes/rl-2511.section.md index baa6daea1584d..fb4d34dd8ce64 100644 --- a/doc/release-notes/rl-2511.section.md +++ b/doc/release-notes/rl-2511.section.md @@ -324,6 +324,13 @@ and [release notes for v18](https://goteleport.com/docs/changelog/#1800-070325). +- `buildEnv` now takes fixed-point arguments (`finalAttrs: { }`). + The custom overrider `.override` is deprecated but kept in this release. It will be removed in future releases after tree-wide transition. + The argument `paths` is passed as `passthru.paths` to avoid bringing in unexpected context. + +- `buildEnv` now takes `derivationArgs` for additional arguments to pass to `stdenv.mkDerivation`. + A compatibility layer is added for directly-specified arguments `nativeBuildInputs` and `buildInputs`. + - Added `rewriteURL` attribute to the nixpkgs `config`, to allow for rewriting the URLs downloaded by `fetchurl`. - Added `hashedMirrors` attribute to the nixpkgs `config`, to allow for customization of the hashed mirrors used by `fetchurl`. diff --git a/pkgs/build-support/buildenv/default.nix b/pkgs/build-support/buildenv/default.nix index e694a9b838e17..08a2710806556 100644 --- a/pkgs/build-support/buildenv/default.nix +++ b/pkgs/build-support/buildenv/default.nix @@ -1,9 +1,9 @@ -# buildEnv creates a tree of symlinks to the specified paths. This is -# a fork of the hardcoded buildEnv in the Nix distribution. +# buildEnv creates a tree of symlinks to the specified paths. +# This is a fork of the hardcoded buildEnv in the Nix distribution. { buildPackages, - runCommand, + stdenvNoCC, lib, replaceVars, writeClosure, @@ -15,115 +15,162 @@ let }; in +# Backward compatibility for deprecated custom overrider .override +# TODO(@ShamrockLee): Warn, throw and remove after tree-wide transition. lib.makeOverridable ( - { - name ? lib.throwIf ( - pname == null || version == null - ) "buildEnv: expect arguments 'pname' and 'version' or 'name'" "${pname}-${version}", - pname ? null, - version ? null, - - # The manifest file (if any). A symlink $out/manifest will be - # created to it. - manifest ? "", - - # The paths to symlink. - paths, - - # Whether to ignore collisions or abort. - ignoreCollisions ? false, - - # Whether to ignore outputs that are a single file instead of a directory. - ignoreSingleFileOutputs ? false, - - # Whether to include closures of all input paths. - includeClosures ? false, - - # If there is a collision, check whether the contents and permissions match - # and only if not, throw a collision error. - checkCollisionContents ? true, - - # The paths (relative to each element of `paths') that we want to - # symlink (e.g., ["/bin"]). Any file not inside any of the - # directories in the list is not symlinked. - pathsToLink ? [ "/" ], - - # The package outputs to include. By default, only the default - # output is included. - extraOutputsToInstall ? [ ], - - # Root the result in directory "$out${extraPrefix}", e.g. "/share". - extraPrefix ? "", - - # Shell commands to run after building the symlink tree. - postBuild ? "", - - # Additional inputs - nativeBuildInputs ? [ ], # Handy e.g. if using makeWrapper in `postBuild`. - buildInputs ? [ ], - - passthru ? { }, - meta ? { }, - }: - let - chosenOutputs = map (drv: { - paths = - # First add the usual output(s): respect if user has chosen explicitly, - # and otherwise use `meta.outputsToInstall`. The attribute is guaranteed - # to exist in mkDerivation-created cases. The other cases (e.g. runCommand) - # aren't expected to have multiple outputs. - ( - if - (!drv ? outputSpecified || !drv.outputSpecified) && drv.meta.outputsToInstall or null != null - then - map (outName: drv.${outName}) drv.meta.outputsToInstall - else - [ drv ] - ) - # Add any extra outputs specified by the caller of `buildEnv`. - ++ lib.filter (p: p != null) (map (outName: drv.${outName} or null) extraOutputsToInstall); - priority = drv.meta.priority or lib.meta.defaultPriority; - }) paths; - - pathsForClosure = lib.pipe chosenOutputs [ - (map (p: p.paths)) - lib.flatten - (lib.remove null) + lib.extendMkDerivation { + constructDrv = stdenvNoCC.mkDerivation; + excludeDrvArgNames = [ + # Override these arguments directly + "derivationArgs" + + # `meta.outputsToInstall` and `extraOutputsToInstall` does not necessarily include the first + # element of outputs, while the outPath of the latter will be the string-interpolated result. + # Exclude to prevent unexpected context. + "paths" ]; - in - runCommand name - ( - rec { + + extendDrvArgs = + finalAttrs: + { + # The manifest file (if any). A symlink $out/manifest will be + # created to it. + manifest ? "", + + # The paths to symlink. + paths, + + # Whether to ignore collisions or abort. + ignoreCollisions ? false, + + # Whether to ignore outputs that are a single file instead of a directory. + ignoreSingleFileOutputs ? false, + + # Whether to include closures of all input paths. + includeClosures ? false, + + # If there is a collision, check whether the contents and permissions match + # and only if not, throw a collision error. + checkCollisionContents ? true, + + # The paths (relative to each element of `paths') that we want to + # symlink (e.g., ["/bin"]). Any file not inside any of the + # directories in the list is not symlinked. + pathsToLink ? [ "/" ], + + # The package outputs to include. By default, only the default + # output is included. + extraOutputsToInstall ? [ ], + + # Root the result in directory "$out${extraPrefix}", e.g. "/share". + extraPrefix ? "", + + # Shell commands to run after building the symlink tree. + postBuild ? "", + + passthru ? { }, + meta ? { }, + + # Additional stdenv.mkDerivation arguments + # such as nativeBuildInputs/buildInputs for postBuild dependencies. + derivationArgs ? { }, + + # Placeholder name arguments. + name ? null, + pname ? null, + version ? null, + + # `stdenv.mkDerivation` args before introducing derivationArgs. + nativeBuildInputs ? null, + buildInputs ? null, + }@args: + let + compatArgs = + lib.optionalAttrs (args ? nativeBuildInputs) { + inherit nativeBuildInputs; + } + // lib.optionalAttrs (args ? buildInputs) { + inherit buildInputs; + }; + + chosenOutputs = map (drv: { + paths = + # First add the usual output(s): respect if user has chosen explicitly, + # and otherwise use `meta.outputsToInstall`. The attribute is guaranteed + # to exist in mkDerivation-created cases. The other cases (e.g. runCommand) + # aren't expected to have multiple outputs. + ( + if + (!drv ? outputSpecified || !drv.outputSpecified) && drv.meta.outputsToInstall or null != null + then + map (outName: drv.${outName}) drv.meta.outputsToInstall + else + [ drv ] + ) + # Add any extra outputs specified by the caller of `buildEnv`. + ++ lib.filter (p: p != null) ( + map (outName: drv.${outName} or null) finalAttrs.extraOutputsToInstall + ); + priority = drv.meta.priority or lib.meta.defaultPriority; + # Silently use the original `paths` if `passthru.paths` is missing. + }) finalAttrs.passthru.paths or paths; + + pathsForClosure = lib.pipe chosenOutputs [ + (map (p: p.paths)) + lib.flatten + (lib.remove null) + ]; + in + assert + args ? name + || (args ? pname && args ? version) + || throw "buildEnv: expects arguments 'pname' and 'version', or 'name'"; + compatArgs + // derivationArgs + // { inherit + extraOutputsToInstall manifest ignoreCollisions checkCollisionContents ignoreSingleFileOutputs - passthru + includeClosures meta pathsToLink extraPrefix postBuild - nativeBuildInputs - buildInputs ; pathsToLinkJSON = builtins.toJSON pathsToLink; pkgs = builtins.toJSON chosenOutputs; - extraPathsFrom = lib.optional includeClosures (writeClosure pathsForClosure); - preferLocalBuild = true; - allowSubstitutes = false; + extraPathsFrom = lib.optionalString finalAttrs.includeClosures (writeClosure pathsForClosure); + + # Explicitly opt out: builder.pl reads all configuration from %ENV, + # which is fundamentally incompatible with __structuredAttrs = true. + __structuredAttrs = false; + + preferLocalBuild = derivationArgs.preferLocalBuild or true; + allowSubstitutes = derivationArgs.allowSubstitutes or false; + passAsFile = [ + "buildCommand" + ] # XXX: The size is somewhat arbitrary - passAsFile = if builtins.stringLength pkgs >= 128 * 1024 then [ "pkgs" ] else [ ]; - } - // lib.optionalAttrs (pname != null) { - inherit pname; - } - // lib.optionalAttrs (version != null) { - inherit version; - } - ) - '' - ${buildPackages.perl}/bin/perl -w ${builder} - eval "$postBuild" - '' + ++ lib.optional (lib.stringLength finalAttrs.pkgs >= 128 * 1024) "pkgs" + ++ derivationArgs.passAsFile or [ ]; + + buildCommand = '' + ${buildPackages.perl}/bin/perl -w ${builder} + eval "$postBuild" + ''; + + passthru = { + # The `paths` attribute is referenced and overridden from passthru + inherit paths; + } + // derivationArgs.passthru or { } + // passthru; + }; + + # Function argument set pattern doesn't have an ellipsis + inheritFunctionArgs = false; + } ) diff --git a/pkgs/test/buildenv.nix b/pkgs/test/buildenv.nix new file mode 100644 index 0000000000000..3d4f1477c49a8 --- /dev/null +++ b/pkgs/test/buildenv.nix @@ -0,0 +1,383 @@ +{ + lib, + pkgs, + stdenvNoCC, +}: + +let + inherit (pkgs) buildEnv; + + testingThrow = expr: { + expr = (builtins.tryEval (builtins.seq expr "didn't throw")); + expected = { + success = false; + value = false; + }; + }; + + tests-name = { + testNameFromNameArg = { + expr = + (buildEnv { + name = "test-env"; + paths = [ ]; + }).name; + expected = "test-env"; + }; + + testNameFromPnameVersion = { + expr = + (buildEnv { + pname = "test-env"; + version = "1.0"; + paths = [ ]; + }).name; + expected = "test-env-1.0"; + }; + + testMissingNameThrows = testingThrow (buildEnv { paths = [ ]; }).drvPath; + }; + + tests-passthru-paths = { + testPathsInPassthru = { + expr = + let + env = buildEnv { + name = "test-env"; + paths = [ pkgs.hello ]; + }; + in + builtins.length env.paths > 0; + expected = true; + }; + + testPassthruPathsOverridable = { + expr = + let + env = buildEnv { + name = "test-env"; + paths = [ pkgs.hello ]; + }; + overridden = env.overrideAttrs { + passthru.paths = [ pkgs.figlet ]; + }; + in + builtins.length overridden.paths == 1; + expected = true; + }; + }; + + tests-finalAttrs = { + testFinalAttrsSelfReference = { + expr = + let + env = buildEnv (finalAttrs: { + name = "test-env"; + paths = [ ]; + passthru.description = "An env named ${finalAttrs.name}"; + }); + in + env.description; + expected = "An env named test-env"; + }; + }; + + tests-overrideAttrs = + let + base = buildEnv { + name = "test-env"; + paths = [ pkgs.hello ]; + passthru.custom = "original"; + }; + overridden = base.overrideAttrs ( + finalAttrs: prev: { + passthru = prev.passthru // { + custom = "modified"; + }; + } + ); + in + { + testOverrideAttrsChangesPassthru = { + expr = overridden.custom; + expected = "modified"; + }; + + testOverrideAttrsPreservesName = { + expr = overridden.name; + expected = "test-env"; + }; + + testOverrideAttrsAffectsDrv = { + expr = + let + withPostBuild = base.overrideAttrs { postBuild = "echo overridden"; }; + in + base.drvPath != withPostBuild.drvPath; + expected = true; + }; + }; + + tests-passthru-merging = + let + env = buildEnv { + name = "test-env"; + paths = [ pkgs.hello ]; + derivationArgs.passthru.fromDerivationArgs = "a"; + passthru.fromPassthru = "b"; + }; + in + { + testPassthruMergingAutoPathsPresent = { + expr = env ? paths; + expected = true; + }; + + testPassthruMergingDerivationArgs = { + expr = env.fromDerivationArgs; + expected = "a"; + }; + + testPassthruMergingDirectPassthru = { + expr = env.fromPassthru; + expected = "b"; + }; + + # Direct passthru takes precedence over derivationArgs.passthru + testPassthruMergingPrecedence = { + expr = + let + env' = buildEnv { + name = "test-env"; + paths = [ ]; + derivationArgs.passthru.key = "from-derivationArgs"; + passthru.key = "from-passthru"; + }; + in + env'.key; + expected = "from-passthru"; + }; + }; + + tests-derivationArgs = + let + env = buildEnv { + name = "test-env"; + paths = [ ]; + derivationArgs.allowSubstitutes = true; + }; + in + { + # derivationArgs.allowSubstitutes overrides the default (false) + testDerivationArgsForwarded = { + expr = env.allowSubstitutes; + expected = true; + }; + + # Backward compat: top-level nativeBuildInputs still works + testCompatNativeBuildInputs = { + expr = + let + env' = buildEnv { + name = "test-env"; + paths = [ ]; + nativeBuildInputs = [ pkgs.hello ]; + }; + in + builtins.length env'.nativeBuildInputs > 0; + expected = true; + }; + }; + + # Build tests: derivations that build a buildEnv and verify its output. + # These are exposed via passthru.buildTests and checked in buildCommand. + buildTests = { + basic-symlinking = + pkgs.runCommand "test-buildenv-basic-symlinking" + { + testEnv = buildEnv { + name = "test-env"; + paths = [ pkgs.hello ]; + }; + } + '' + # With a single package, buildEnv symlinks the directory itself + test -L "$testEnv/bin" || { echo "FAIL: $testEnv/bin is not a symlink"; exit 1; } + + # The symlink should point into the store + target=$(readlink "$testEnv/bin") + case "$target" in + /nix/store/*) ;; + *) echo "FAIL: symlink target '$target' is not a store path"; exit 1 ;; + esac + + # The binary should be accessible and executable through the symlink + test -x "$testEnv/bin/hello" || { echo "FAIL: hello binary not executable"; exit 1; } + "$testEnv/bin/hello" > /dev/null || { echo "FAIL: hello binary did not run"; exit 1; } + + touch $out + ''; + + pathsToLink = + pkgs.runCommand "test-buildenv-pathsToLink" + { + testEnv = buildEnv { + name = "test-env"; + paths = [ pkgs.hello ]; + pathsToLink = [ "/bin" ]; + }; + } + '' + # /bin should exist + test -d "$testEnv/bin" || { echo "FAIL: $testEnv/bin missing"; exit 1; } + + # Other directories from hello (like /share) should NOT exist + test ! -e "$testEnv/share" || { echo "FAIL: $testEnv/share should not exist with pathsToLink = [\"/bin\"]"; exit 1; } + + touch $out + ''; + + extraPrefix = + pkgs.runCommand "test-buildenv-extraPrefix" + { + testEnv = buildEnv { + name = "test-env"; + paths = [ pkgs.hello ]; + extraPrefix = "/myprefix"; + }; + } + '' + # Content should be under the extra prefix + test -e "$testEnv/myprefix/bin/hello" || { echo "FAIL: $testEnv/myprefix/bin/hello missing"; exit 1; } + test -x "$testEnv/myprefix/bin/hello" || { echo "FAIL: $testEnv/myprefix/bin/hello not executable"; exit 1; } + + # Content should NOT be at the top level + test ! -e "$testEnv/bin" || { echo "FAIL: $testEnv/bin should not exist at top level with extraPrefix"; exit 1; } + + touch $out + ''; + + postBuild = + pkgs.runCommand "test-buildenv-postBuild" + { + testEnv = buildEnv { + name = "test-env"; + paths = [ ]; + postBuild = '' + echo "postBuild was here" > $out/marker + ''; + }; + } + '' + # postBuild should have created the marker file + test -f "$testEnv/marker" || { echo "FAIL: $testEnv/marker missing; postBuild did not run"; exit 1; } + content=$(cat "$testEnv/marker") + test "$content" = "postBuild was here" || { echo "FAIL: marker content wrong: $content"; exit 1; } + + touch $out + ''; + + # buildEnv explicitly sets __structuredAttrs = false because builder.pl + # reads all inputs from environment variables. Verify the build succeeds + # even when derivationArgs tries to enable structuredAttrs. + structuredAttrs-overridden = + pkgs.runCommand "test-buildenv-structuredAttrs-overridden" + { + testEnv = buildEnv { + name = "test-env-structuredAttrs"; + paths = [ pkgs.hello ]; + derivationArgs.__structuredAttrs = true; + }; + } + '' + test -x "$testEnv/bin/hello" || { echo "FAIL: hello not present after structuredAttrs override"; exit 1; } + touch $out + ''; + + ignoreCollisions = + pkgs.runCommand "test-buildenv-ignoreCollisions" + { + # Two copies of hello with different priorities that collide + testEnv = buildEnv { + name = "test-env-ignore"; + paths = [ + pkgs.hello + (lib.meta.setPrio 1 pkgs.hello) + ]; + ignoreCollisions = true; + }; + } + '' + # Should succeed because ignoreCollisions = true + test -x "$testEnv/bin/hello" || { echo "FAIL: hello not present with ignoreCollisions"; exit 1; } + + touch $out + ''; + }; + + # buildEnv's builder.pl reads all inputs from %ENV, which is + # fundamentally incompatible with __structuredAttrs = true. + # buildEnv explicitly forces __structuredAttrs = false. + tests-structuredAttrs = { + testStructuredAttrsExplicitlyFalse = { + expr = + (buildEnv { + name = "test-env"; + paths = [ ]; + }).__structuredAttrs; + expected = false; + }; + + testStructuredAttrsCantBeOverriddenViaDerivationArgs = { + expr = + (buildEnv { + name = "test-env"; + paths = [ ]; + derivationArgs.__structuredAttrs = true; + }).__structuredAttrs; + expected = false; + }; + }; + + tests = + tests-name + // tests-passthru-paths + // tests-finalAttrs + // tests-overrideAttrs + // tests-passthru-merging + // tests-derivationArgs + // tests-structuredAttrs; +in + +stdenvNoCC.mkDerivation (finalAttrs: { + __structuredAttrs = true; + name = "test-buildenv"; + passthru = { + inherit tests buildTests; + failures = lib.runTests finalAttrs.passthru.tests; + }; + testResults = lib.mapAttrs (_: test: test.expr == test.expected) finalAttrs.passthru.tests; + buildCommand = '' + touch $out + for testName in "''${!testResults[@]}"; do + if [[ -n "''${testResults[$testName]}" ]]; then + echo "$testName success" + else + echo "$testName fail" + fi + done + '' + + lib.optionalString (lib.any (v: !v) (lib.attrValues finalAttrs.testResults)) '' + { + echo "ERROR: tests.buildenv: Encountering failed tests." + for testName in "''${!testResults[@]}"; do + if [[ -z "''${testResults[$testName]}" ]]; then + echo "- $testName" + fi + done + echo "To inspect the expected and actual result, " + echo ' evaluate `tests.buildenv.tests.''${testName}`.' + } >&2 + exit 1 + ''; +}) diff --git a/pkgs/test/default.nix b/pkgs/test/default.nix index acd0982ed5f89..64f38efb5c2b0 100644 --- a/pkgs/test/default.nix +++ b/pkgs/test/default.nix @@ -183,6 +183,8 @@ in nixosOptionsDoc = recurseIntoAttrs (callPackage ../../nixos/lib/make-options-doc/tests.nix { }); + buildenv = callPackage ./buildenv.nix { }; + overriding = callPackage ./overriding.nix { }; texlive = recurseIntoAttrs (callPackage ./texlive { }); diff --git a/pkgs/tools/typesetting/tex/texlive/build-tex-env.nix b/pkgs/tools/typesetting/tex/texlive/build-tex-env.nix index 021bcd119e91d..7d00099ece85d 100644 --- a/pkgs/tools/typesetting/tex/texlive/build-tex-env.nix +++ b/pkgs/tools/typesetting/tex/texlive/build-tex-env.nix @@ -44,28 +44,6 @@ lib.fix ( }@args: let - ### buildEnv with custom attributes - buildEnv' = - args: - (buildEnv ( - { - inherit pname version; - - inherit (args) paths; - } - // lib.optionalAttrs (args ? extraOutputsToInstall) { inherit (args) extraOutputsToInstall; } - // lib.optionalAttrs (args ? pathsToLink) { inherit (args) pathsToLink; } - )).overrideAttrs - ( - removeAttrs args [ - "extraOutputsToInstall" - "pathsToLink" - "name" - "paths" - "pkgs" - ] - ); - ### texlive.combine backward compatibility # if necessary, convert old style { pkgs = [ ... ]; } packages to attribute sets isOldPkgList = p: !p.outputSpecified or false && p ? pkgs && builtins.all (p: p ? tlType) p.pkgs; @@ -230,7 +208,7 @@ lib.fix ( name = "${pname}-${version}"; - texmfdist = buildEnv' { + texmfdist = buildEnv { name = "${name}-texmfdist"; # remove fake derivations (without 'outPath') to avoid undesired build dependencies @@ -299,19 +277,21 @@ lib.fix ( # other outputs nonEnvOutputs = lib.genAttrs pkgList.nonEnvOutputs ( outName: - buildEnv' { + buildEnv { inherit name; - outputs = [ outName ]; paths = builtins.catAttrs "outPath" ( pkgList.otherOutputs.${outName} or [ ] ++ pkgList.specifiedOutputs.${outName} or [ ] ); - # force the output to be ${outName} or nix-env will not work - nativeBuildInputs = [ - (writeShellScript "force-output.sh" '' - export out="''${${outName}-}" - '') - ]; - inherit meta passthru; + derivationArgs = { + outputs = [ outName ]; + nativeBuildInputs = [ + # force the output to be ${outName} or nix-env will not work + (writeShellScript "force-output.sh" '' + export out="''${${outName}-}" + '') + ]; + inherit meta passthru; + }; } ); @@ -429,79 +409,80 @@ lib.fix ( updmapLines = { pname, fontMaps, ... }: [ "# from ${pname}:" ] ++ fontMaps; - out = - # no indent for git diff purposes - buildEnv' { + in + buildEnv { + + inherit name; + + # remove fake derivations (without 'outPath') to avoid undesired build dependencies + paths = + builtins.catAttrs "outPath" pkgList.bin + ++ lib.optionals (!__combine && __formatsOf == null) pkgList.formats + ++ lib.optional __combine doc; + pathsToLink = [ + "/" + "/share/texmf-var/scripts" + "/share/texmf-var/tex/generic/config" + "/share/texmf-var/web2c" + "/share/texmf-config" + "/bin" # ensure these are writeable directories + ]; + + postBuild = '' + . "${./build-tex-env.sh}" + ''; + + derivationArgs = { + # use attrNames, attrValues to ensure the two lists are sorted in the same way + outputs = [ + "out" + ] + ++ lib.optionals (!__combine && __formatsOf == null) (builtins.attrNames nonEnvOutputs); + otherOutputs = lib.optionals (!__combine && __formatsOf == null) ( + builtins.attrValues nonEnvOutputs + ); - inherit name; + nativeBuildInputs = [ + makeWrapper + libfaketime + tl."texlive.infra" # mktexlsr + tl.texlive-scripts # fmtutil, updmap + tl.texlive-scripts-extra # texlinks + perl + ]; - # use attrNames, attrValues to ensure the two lists are sorted in the same way - outputs = [ - "out" - ] - ++ lib.optionals (!__combine && __formatsOf == null) (builtins.attrNames nonEnvOutputs); - otherOutputs = lib.optionals (!__combine && __formatsOf == null) ( - builtins.attrValues nonEnvOutputs - ); + buildInputs = [ + coreutils + gawk + gnugrep + gnused + ] + ++ lib.optional needsGhostscript ghostscript; - # remove fake derivations (without 'outPath') to avoid undesired build dependencies - paths = - builtins.catAttrs "outPath" pkgList.bin - ++ lib.optionals (!__combine && __formatsOf == null) pkgList.formats - ++ lib.optional __combine doc; - pathsToLink = [ - "/" - "/share/texmf-var/scripts" - "/share/texmf-var/tex/generic/config" - "/share/texmf-var/web2c" - "/share/texmf-config" - "/bin" # ensure these are writeable directories - ]; - - nativeBuildInputs = [ - makeWrapper - libfaketime - tl."texlive.infra" # mktexlsr - tl.texlive-scripts # fmtutil, updmap - tl.texlive-scripts-extra # texlinks - perl - ]; - - buildInputs = [ - coreutils - gawk - gnugrep - gnused - ] - ++ lib.optional needsGhostscript ghostscript; - - inherit meta passthru __combine; - __formatsOf = __formatsOf.pname or null; - - inherit texmfdist texmfroot; - - fontconfigFile = makeFontsConf { fontDirectories = [ "${texmfroot}/texmf-dist/fonts" ]; }; - - fmtutilCnf = assembleConfigLines fmtutilLines pkgList.sortedFormatPkgs; - updmapCfg = assembleConfigLines updmapLines pkgList.sortedFontMaps; - - languageDat = assembleConfigLines langDatLines pkgList.sortedHyphenPatterns; - languageDef = assembleConfigLines langDefLines pkgList.sortedHyphenPatterns; - languageLua = assembleConfigLines langLuaLines pkgList.sortedHyphenPatterns; - - postactionScripts = builtins.catAttrs "postactionScript" pkgList.tlpkg; - - postBuild = '' - . "${./build-tex-env.sh}" - ''; + inherit passthru __combine; + __formatsOf = __formatsOf.pname or null; - allowSubstitutes = true; - preferLocalBuild = false; - }; - in - # outputsToInstall must be set *after* overrideAttrs (used in buildEnv') or it fails the checkMeta tests - if __combine || __formatsOf != null then - out - else - lib.addMetaAttrs { inherit (pkgList) outputsToInstall; } out + inherit texmfdist texmfroot; + + fontconfigFile = makeFontsConf { fontDirectories = [ "${texmfroot}/texmf-dist/fonts" ]; }; + + fmtutilCnf = assembleConfigLines fmtutilLines pkgList.sortedFormatPkgs; + updmapCfg = assembleConfigLines updmapLines pkgList.sortedFontMaps; + + languageDat = assembleConfigLines langDatLines pkgList.sortedHyphenPatterns; + languageDef = assembleConfigLines langDefLines pkgList.sortedHyphenPatterns; + languageLua = assembleConfigLines langLuaLines pkgList.sortedHyphenPatterns; + + postactionScripts = builtins.catAttrs "postactionScript" pkgList.tlpkg; + + allowSubstitutes = true; + preferLocalBuild = false; + + meta = + meta + // lib.optionalAttrs (!__combine && __formatsOf == null) { + inherit (pkgList) outputsToInstall; + }; + }; + } )