diff --git a/pkgs/build-support/trivial-builders/default.nix b/pkgs/build-support/trivial-builders/default.nix index be67b7b177aca..34b9e5a452a22 100644 --- a/pkgs/build-support/trivial-builders/default.nix +++ b/pkgs/build-support/trivial-builders/default.nix @@ -3,7 +3,11 @@ let inherit (lib) optionalAttrs + optionalString + hasPrefix warn + map + isList ; in @@ -462,6 +466,29 @@ rec { ... + To create a directory structure from a specific subdirectory of input `paths` instead of their full trees, + you can either append the subdirectory path to each input path, or use the `stripPrefix` argument to + remove the common prefix during linking. + + Example: + + + # create symlinks of tmpfiles.d rules from multiple packages + symlinkJoin { name = "tmpfiles.d"; paths = [ pkgs.lvm2 pkgs.nix ]; stripPrefix = "/lib/tmpfiles.d"; } + + + This creates a derivation with a directory structure like the following: + + + /nix/store/m5s775yicb763hfa133jwml5hwmwzv14-tmpfiles.d + |-- lvm2.conf -> /nix/store/k6js0l5f0zpvrhay49579fj939j77p2w-lvm2-2.03.29/lib/tmpfiles.d/lvm2.conf + `-- nix-daemon.conf -> /nix/store/z4v2s3s3y79fmabhps5hakb3c5dwaj5a-nix-1.33.7/lib/tmpfiles.d/nix-daemon.conf + + + By default, packages that don't contain the specified subdirectory are silently skipped. + Set `failOnMissing = true` to make the build fail if any input package is missing the subdirectory + (this is the default behavior when not using stripPrefix). + symlinkJoin and linkFarm are similar functions, but they output derivations with different structure. @@ -484,15 +511,29 @@ rec { "symlinkJoin requires either a `name` OR `pname` and `version`"; "${args_.pname}-${args_.version}" , paths + , stripPrefix ? "" , preferLocalBuild ? true , allowSubstitutes ? false , postBuild ? "" + , failOnMissing ? stripPrefix == "" , ... }: + assert lib.assertMsg (stripPrefix != "" -> (hasPrefix "/" stripPrefix && stripPrefix != "/")) '' + stripPrefix must be either an empty string (disable stripping behavior), or relative path prefixed with /. + + Ensure that the path starts with / and specifies path to the subdirectory. + ''; + let - args = removeAttrs args_ [ "name" "postBuild" ] + mapPaths = f: paths: map (path: + if path == null then null + else if isList path then mapPaths f path + else f path + ) paths; + args = removeAttrs args_ [ "name" "postBuild" "stripPrefix" "paths" "failOnMissing" ] // { inherit preferLocalBuild allowSubstitutes; + paths = mapPaths (path: "${path}${stripPrefix}") paths; passAsFile = [ "paths" ]; }; # pass the defaults in @@ -500,7 +541,7 @@ rec { '' mkdir -p $out for i in $(cat $pathsPath); do - ${lndir}/bin/lndir -silent $i $out + ${optionalString (!failOnMissing) "if test -d $i; then "}${lndir}/bin/lndir -silent $i $out${optionalString (!failOnMissing) "; fi"} done ${postBuild} ''; diff --git a/pkgs/build-support/trivial-builders/test/default.nix b/pkgs/build-support/trivial-builders/test/default.nix index e1ed0be72bf35..be8c8d320a039 100644 --- a/pkgs/build-support/trivial-builders/test/default.nix +++ b/pkgs/build-support/trivial-builders/test/default.nix @@ -19,6 +19,7 @@ in recurseIntoAttrs { concat = callPackage ./concat-test.nix {}; linkFarm = callPackage ./link-farm.nix {}; + symlinkJoin = recurseIntoAttrs (callPackage ./symlink-join.nix {}); overriding = callPackage ../test-overriding.nix {}; inherit references; writeCBin = callPackage ./writeCBin.nix {}; diff --git a/pkgs/build-support/trivial-builders/test/symlink-join.nix b/pkgs/build-support/trivial-builders/test/symlink-join.nix new file mode 100644 index 0000000000000..d50581d3f38af --- /dev/null +++ b/pkgs/build-support/trivial-builders/test/symlink-join.nix @@ -0,0 +1,136 @@ +{ + symlinkJoin, + writeTextFile, + runCommand, + testers, +}: + +let + inherit (testers) testEqualContents testBuildFailure; + + foo = writeTextFile { + name = "foo"; + text = "foo"; + destination = "/etc/test.d/foo"; + }; + + bar = writeTextFile { + name = "bar"; + text = "bar"; + destination = "/etc/test.d/bar"; + }; + + baz = writeTextFile { + name = "baz"; + text = "baz"; + destination = "/var/lib/arbitrary/baz"; + }; + + qux = writeTextFile { + name = "qux"; + text = "qux"; + }; + + emulatedSymlinkJoinFooBarStrip = runCommand "symlinkJoin-strip-foo-bar" { } '' + mkdir $out + ln -s ${foo}/etc/test.d/foo $out/ + ln -s ${bar}/etc/test.d/bar $out/ + ''; +in +{ + symlinkJoin = testEqualContents { + assertion = "symlinkJoin"; + actual = symlinkJoin { + name = "symlinkJoin"; + paths = [ + foo + bar + baz + ]; + }; + expected = runCommand "symlinkJoin-foo-bar-baz" { } '' + mkdir -p $out/{var/lib/arbitrary,etc/test.d} + ln -s {${foo},${bar}}/etc/test.d/* $out/etc/test.d + ln -s ${baz}/var/lib/arbitrary/baz $out/var/lib/arbitrary/ + ''; + }; + + symlinkJoin-strip-paths = testEqualContents { + assertion = "symlinkJoin-strip-paths"; + actual = symlinkJoin { + name = "symlinkJoinPrefix"; + paths = [ + foo + bar + ]; + stripPrefix = "/etc/test.d"; + }; + expected = emulatedSymlinkJoinFooBarStrip; + }; + + symlinkJoin-strip-paths-skip-missing = testEqualContents { + assertion = "symlinkJoin-strip-paths-skip-missing"; + actual = symlinkJoin { + name = "symlinkJoinPrefix"; + paths = [ + foo + bar + baz + ]; + stripPrefix = "/etc/test.d"; + }; + expected = emulatedSymlinkJoinFooBarStrip; + }; + + symlinkJoin-strip-paths-skip-not-directories = testEqualContents { + assertion = "symlinkJoin-strip-paths-skip-not-directories"; + actual = symlinkJoin { + name = "symlinkJoinPrefix"; + paths = [ + foo + bar + qux + ]; + stripPrefix = "/etc/test.d"; + }; + expected = emulatedSymlinkJoinFooBarStrip; + }; + + symlinkJoin-fails-on-missing = + runCommand "symlinkJoin-fails-on-missing" + { + failed = testBuildFailure (symlinkJoin { + name = "symlinkJoin-fail"; + paths = [ + foo + bar + baz + ]; + stripPrefix = "/etc/test.d"; + failOnMissing = true; + }); + } + '' + grep -e "-baz/etc/test.d: No such file or directory" $failed/testBuildFailure.log + touch $out + ''; + + symlinkJoin-fails-on-file = + runCommand "symlinkJoin-fails-on-file" + { + failed = testBuildFailure (symlinkJoin { + name = "symlinkJoin-fail"; + paths = [ + foo + bar + qux + ]; + stripPrefix = "/etc/test.d"; + failOnMissing = true; + }); + } + '' + grep -e "-qux/etc/test.d: Not a directory" $failed/testBuildFailure.log + touch $out + ''; +}