diff options
Diffstat (limited to 'nixpkgs/pkgs/build-support/trivial-builders')
16 files changed, 1722 insertions, 0 deletions
diff --git a/nixpkgs/pkgs/build-support/trivial-builders/default.nix b/nixpkgs/pkgs/build-support/trivial-builders/default.nix new file mode 100644 index 000000000000..1625b0c96719 --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/default.nix @@ -0,0 +1,899 @@ +{ lib, config, stdenv, stdenvNoCC, jq, lndir, runtimeShell, shellcheck-minimal }: + +let + inherit (lib) + optionalAttrs + warn + ; +in + +rec { + + # Docs in doc/build-helpers/trivial-build-helpers.chapter.md + # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-runCommand + runCommand = name: env: runCommandWith { + stdenv = stdenvNoCC; + runLocal = false; + inherit name; + derivationArgs = env; + }; + # Docs in doc/build-helpers/trivial-build-helpers.chapter.md + # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-runCommandLocal + runCommandLocal = name: env: runCommandWith { + stdenv = stdenvNoCC; + runLocal = true; + inherit name; + derivationArgs = env; + }; + # Docs in doc/build-helpers/trivial-build-helpers.chapter.md + # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-runCommandCC + runCommandCC = name: env: runCommandWith { + stdenv = stdenv; + runLocal = false; + inherit name; + derivationArgs = env; + }; + # `runCommandCCLocal` left out on purpose. + # We shouldn’t force the user to have a cc in scope. + + # TODO: Move documentation for runCommandWith to the Nixpkgs manual + /* + Generalized version of the `runCommand`-variants + which does customized behavior via a single + attribute set passed as the first argument + instead of having a lot of variants like + `runCommand*`. Additionally it allows changing + the used `stdenv` freely and has a more explicit + approach to changing the arguments passed to + `stdenv.mkDerivation`. + */ + runCommandWith = + let + # prevent infinite recursion for the default stdenv value + defaultStdenv = stdenv; + in + { + # which stdenv to use, defaults to a stdenv with a C compiler, pkgs.stdenv + stdenv ? defaultStdenv + # whether to build this derivation locally instead of substituting + , runLocal ? false + # extra arguments to pass to stdenv.mkDerivation + , derivationArgs ? { } + # name of the resulting derivation + , name + # TODO(@Artturin): enable strictDeps always + }: buildCommand: + stdenv.mkDerivation ({ + enableParallelBuilding = true; + inherit buildCommand name; + passAsFile = [ "buildCommand" ] + ++ (derivationArgs.passAsFile or [ ]); + } + // lib.optionalAttrs (! derivationArgs?meta) { + pos = let args = builtins.attrNames derivationArgs; in + if builtins.length args > 0 + then builtins.unsafeGetAttrPos (builtins.head args) derivationArgs + else null; + } + // (lib.optionalAttrs runLocal { + preferLocalBuild = true; + allowSubstitutes = false; + }) + // builtins.removeAttrs derivationArgs [ "passAsFile" ]); + + + # Docs in doc/build-helpers/trivial-build-helpers.chapter.md + # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-writeTextFile + writeTextFile = + { name + , text + , executable ? false + , destination ? "" + , checkPhase ? "" + , meta ? { } + , allowSubstitutes ? false + , preferLocalBuild ? true + , derivationArgs ? { } + }: + let + matches = builtins.match "/bin/([^/]+)" destination; + in + runCommand name + ({ + inherit text executable checkPhase allowSubstitutes preferLocalBuild; + passAsFile = [ "text" ] + ++ derivationArgs.passAsFile or [ ]; + meta = lib.optionalAttrs (executable && matches != null) + { + mainProgram = lib.head matches; + } // meta // derivationArgs.meta or {}; + } // removeAttrs derivationArgs [ "passAsFile" "meta" ]) + '' + target=$out${lib.escapeShellArg destination} + mkdir -p "$(dirname "$target")" + + if [ -e "$textPath" ]; then + mv "$textPath" "$target" + else + echo -n "$text" > "$target" + fi + + if [ -n "$executable" ]; then + chmod +x "$target" + fi + + eval "$checkPhase" + ''; + + # See doc/build-helpers/trivial-build-helpers.chapter.md + # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-text-writing + writeText = name: text: writeTextFile { inherit name text; }; + + # See doc/build-helpers/trivial-build-helpers.chapter.md + # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-text-writing + writeTextDir = path: text: writeTextFile { + inherit text; + name = builtins.baseNameOf path; + destination = "/${path}"; + }; + + # See doc/build-helpers/trivial-build-helpers.chapter.md + # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-text-writing + writeScript = name: text: writeTextFile { inherit name text; executable = true; }; + + # See doc/build-helpers/trivial-build-helpers.chapter.md + # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-text-writing + writeScriptBin = name: text: writeTextFile { + inherit name text; + executable = true; + destination = "/bin/${name}"; + }; + + # See doc/build-helpers/trivial-build-helpers.chapter.md + # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-text-writing + writeShellScript = name: text: + writeTextFile { + inherit name; + executable = true; + text = '' + #!${runtimeShell} + ${text} + ''; + checkPhase = '' + ${stdenv.shellDryRun} "$target" + ''; + }; + + # See doc/build-helpers/trivial-build-helpers.chapter.md + # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-text-writing + writeShellScriptBin = name: text: + writeTextFile { + inherit name; + executable = true; + destination = "/bin/${name}"; + text = '' + #!${runtimeShell} + ${text} + ''; + checkPhase = '' + ${stdenv.shellDryRun} "$target" + ''; + meta.mainProgram = name; + }; + + # TODO: move parameter documentation to the Nixpkgs manual + # See doc/build-helpers/trivial-build-helpers.chapter.md + # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-writeShellApplication + writeShellApplication = + { + /* + The name of the script to write. + + Type: String + */ + name, + /* + The shell script's text, not including a shebang. + + Type: String + */ + text, + /* + Inputs to add to the shell script's `$PATH` at runtime. + + Type: [String|Derivation] + */ + runtimeInputs ? [ ], + /* + Extra environment variables to set at runtime. + + Type: AttrSet + */ + runtimeEnv ? null, + /* + `stdenv.mkDerivation`'s `meta` argument. + + Type: AttrSet + */ + meta ? { }, + /* + The `checkPhase` to run. Defaults to `shellcheck` on supported + platforms and `bash -n`. + + The script path will be given as `$target` in the `checkPhase`. + + Type: String + */ + checkPhase ? null, + /* + Checks to exclude when running `shellcheck`, e.g. `[ "SC2016" ]`. + + See <https://www.shellcheck.net/wiki/> for a list of checks. + + Type: [String] + */ + excludeShellChecks ? [ ], + /* + Extra command-line flags to pass to ShellCheck. + + Type: [String] + */ + extraShellCheckFlags ? [ ], + /* + Bash options to activate with `set -o` at the start of the script. + + Defaults to `[ "errexit" "nounset" "pipefail" ]`. + + Type: [String] + */ + bashOptions ? [ "errexit" "nounset" "pipefail" ], + /* Extra arguments to pass to `stdenv.mkDerivation`. + + :::{.caution} + Certain derivation attributes are used internally, + overriding those could cause problems. + ::: + + Type: AttrSet + */ + derivationArgs ? { }, + }: + writeTextFile { + inherit name meta derivationArgs; + executable = true; + destination = "/bin/${name}"; + allowSubstitutes = true; + preferLocalBuild = false; + text = '' + #!${runtimeShell} + ${lib.concatMapStringsSep "\n" (option: "set -o ${option}") bashOptions} + '' + lib.optionalString (runtimeEnv != null) + (lib.concatStrings + (lib.mapAttrsToList + (name: value: '' + ${lib.toShellVar name value} + export ${name} + '') + runtimeEnv)) + + lib.optionalString (runtimeInputs != [ ]) '' + + export PATH="${lib.makeBinPath runtimeInputs}:$PATH" + '' + '' + + ${text} + ''; + + checkPhase = + # GHC (=> shellcheck) isn't supported on some platforms (such as risc-v) + # but we still want to use writeShellApplication on those platforms + let + shellcheckSupported = lib.meta.availableOn stdenv.buildPlatform shellcheck-minimal.compiler; + excludeFlags = lib.optionals (excludeShellChecks != [ ]) [ "--exclude" (lib.concatStringsSep "," excludeShellChecks) ]; + shellcheckCommand = lib.optionalString shellcheckSupported '' + # use shellcheck which does not include docs + # pandoc takes long to build and documentation isn't needed for just running the cli + ${lib.getExe shellcheck-minimal} ${lib.escapeShellArgs (excludeFlags ++ extraShellCheckFlags)} "$target" + ''; + in + if checkPhase == null then '' + runHook preCheck + ${stdenv.shellDryRun} "$target" + ${shellcheckCommand} + runHook postCheck + '' + else checkPhase; + }; + + # Create a C binary + # TODO: add to writers? pkgs/build-support/writers + writeCBin = pname: code: + runCommandCC pname + { + inherit pname code; + executable = true; + passAsFile = [ "code" ]; + # Pointless to do this on a remote machine. + preferLocalBuild = true; + allowSubstitutes = false; + meta = { + mainProgram = pname; + }; + } + '' + n=$out/bin/${pname} + mkdir -p "$(dirname "$n")" + mv "$codePath" code.c + $CC -x c code.c -o "$n" + ''; + + # TODO: deduplicate with documentation in doc/build-helpers/trivial-build-helpers.chapter.md + # see also https://github.com/NixOS/nixpkgs/pull/249721 + # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-concatText + /* concat a list of files to the nix store. + The contents of files are added to the file in the store. + + Example: + + + # Writes my-file to /nix/store/<store path> + concatTextFile { + name = "my-file"; + files = [ drv1 "${drv2}/path/to/file" ]; + } + + + See also the `concatText` helper function below. + + + # Writes executable my-file to /nix/store/<store path>/bin/my-file + concatTextFile { + name = "my-file"; + files = [ drv1 "${drv2}/path/to/file" ]; + executable = true; + destination = "/bin/my-file"; + } + + + */ + concatTextFile = + { name # the name of the derivation + , files + , executable ? false # run chmod +x ? + , destination ? "" # relative path appended to $out eg "/bin/foo" + , checkPhase ? "" # syntax checks, e.g. for scripts + , meta ? { } + }: + runCommandLocal name + { inherit files executable checkPhase meta destination; } + '' + file=$out$destination + mkdir -p "$(dirname "$file")" + cat $files > "$file" + + if [ -n "$executable" ]; then + chmod +x "$file" + fi + + eval "$checkPhase" + ''; + + # TODO: deduplicate with documentation in doc/build-helpers/trivial-build-helpers.chapter.md + # see also https://github.com/NixOS/nixpkgs/pull/249721 + # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-concatText + /* + Writes a text file to nix store with no optional parameters available. + + Example: + + + # Writes contents of files to /nix/store/<store path> + concatText "my-file" [ file1 file2 ] + + + */ + concatText = name: files: concatTextFile { inherit name files; }; + + # TODO: deduplicate with documentation in doc/build-helpers/trivial-build-helpers.chapter.md + # see also https://github.com/NixOS/nixpkgs/pull/249721 + # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-concatText + /* + Writes a text file to nix store with and mark it as executable. + + Example: + # Writes contents of files to /nix/store/<store path> + concatScript "my-file" [ file1 file2 ] + + */ + concatScript = name: files: concatTextFile { inherit name files; executable = true; }; + + + /* + TODO: Deduplicate this documentation. + More docs in doc/build-helpers/trivial-build-helpers.chapter.md + See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-symlinkJoin + + Create a forest of symlinks to the files in `paths`. + + This creates a single derivation that replicates the directory structure + of all the input paths. + + BEWARE: it may not "work right" when the passed paths contain symlinks to directories. + + Example: + + + # adds symlinks of hello to current build. + symlinkJoin { name = "myhello"; paths = [ pkgs.hello ]; } + + + + + # adds symlinks of hello and stack to current build and prints "links added" + symlinkJoin { name = "myexample"; paths = [ pkgs.hello pkgs.stack ]; postBuild = "echo links added"; } + + + This creates a derivation with a directory structure like the following: + + + /nix/store/sglsr5g079a5235hy29da3mq3hv8sjmm-myexample + |-- bin + | |-- hello -> /nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10/bin/hello + | `-- stack -> /nix/store/6lzdpxshx78281vy056lbk553ijsdr44-stack-2.1.3.1/bin/stack + `-- share + |-- bash-completion + | `-- completions + | `-- stack -> /nix/store/6lzdpxshx78281vy056lbk553ijsdr44-stack-2.1.3.1/share/bash-completion/completions/stack + |-- fish + | `-- vendor_completions.d + | `-- stack.fish -> /nix/store/6lzdpxshx78281vy056lbk553ijsdr44-stack-2.1.3.1/share/fish/vendor_completions.d/stack.fish + ... + + + symlinkJoin and linkFarm are similar functions, but they output + derivations with different structure. + + symlinkJoin is used to create a derivation with a familiar directory + structure (top-level bin/, share/, etc), but with all actual files being symlinks to + the files in the input derivations. + + symlinkJoin is used many places in nixpkgs to create a single derivation + that appears to contain binaries, libraries, documentation, etc from + multiple input derivations. + + linkFarm is instead used to create a simple derivation with symlinks to + other derivations. A derivation created with linkFarm is often used in CI + as a easy way to build multiple derivations at once. + */ + symlinkJoin = + args_@{ name + , paths + , preferLocalBuild ? true + , allowSubstitutes ? false + , postBuild ? "" + , ... + }: + let + args = removeAttrs args_ [ "name" "postBuild" ] + // { + inherit preferLocalBuild allowSubstitutes; + passAsFile = [ "paths" ]; + }; # pass the defaults + in + runCommand name args + '' + mkdir -p $out + for i in $(cat $pathsPath); do + ${lndir}/bin/lndir -silent $i $out + done + ${postBuild} + ''; + + # TODO: move linkFarm docs to the Nixpkgs manual + /* + Quickly create a set of symlinks to derivations. + + This creates a simple derivation with symlinks to all inputs. + + entries can be a list of attribute sets like + + [ { name = "name" ; path = "/nix/store/..."; } ] + + + or an attribute set name -> path like: + + { name = "/nix/store/..."; other = "/nix/store/..."; } + + + Example: + + # Symlinks hello and stack paths in store to current $out/hello-test and + # $out/foobar. + linkFarm "myexample" [ { name = "hello-test"; path = pkgs.hello; } { name = "foobar"; path = pkgs.stack; } ] + + This creates a derivation with a directory structure like the following: + + /nix/store/qc5728m4sa344mbks99r3q05mymwm4rw-myexample + |-- foobar -> /nix/store/6lzdpxshx78281vy056lbk553ijsdr44-stack-2.1.3.1 + `-- hello-test -> /nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 + + + See the note on symlinkJoin for the difference between linkFarm and symlinkJoin. + */ + linkFarm = name: entries: + let + entries' = + if (lib.isAttrs entries) then entries + # We do this foldl to have last-wins semantics in case of repeated entries + else if (lib.isList entries) then lib.foldl (a: b: a // { "${b.name}" = b.path; }) { } entries + else throw "linkFarm entries must be either attrs or a list!"; + + linkCommands = lib.mapAttrsToList + (name: path: '' + mkdir -p "$(dirname ${lib.escapeShellArg "${name}"})" + ln -s ${lib.escapeShellArg "${path}"} ${lib.escapeShellArg "${name}"} + '') + entries'; + in + runCommand name + { + preferLocalBuild = true; + allowSubstitutes = false; + passthru.entries = entries'; + } '' + mkdir -p $out + cd $out + ${lib.concatStrings linkCommands} + ''; + + # TODO: move linkFarmFromDrvs docs to the Nixpkgs manual + /* + Easily create a linkFarm from a set of derivations. + + This calls linkFarm with a list of entries created from the list of input + derivations. It turns each input derivation into an attribute set + like { name = drv.name ; path = drv }, and passes this to linkFarm. + + Example: + + # Symlinks the hello, gcc, and ghc derivations in $out + linkFarmFromDrvs "myexample" [ pkgs.hello pkgs.gcc pkgs.ghc ] + + This creates a derivation with a directory structure like the following: + + + /nix/store/m3s6wkjy9c3wy830201bqsb91nk2yj8c-myexample + |-- gcc-wrapper-9.2.0 -> /nix/store/fqhjxf9ii4w4gqcsx59fyw2vvj91486a-gcc-wrapper-9.2.0 + |-- ghc-8.6.5 -> /nix/store/gnf3s07bglhbbk4y6m76sbh42siym0s6-ghc-8.6.5 + `-- hello-2.10 -> /nix/store/k0ll91c4npk4lg8lqhx00glg2m735g74-hello-2.10 + + */ + linkFarmFromDrvs = name: drvs: + let mkEntryFromDrv = drv: { name = drv.name; path = drv; }; + in linkFarm name (map mkEntryFromDrv drvs); + + # TODO: move onlyBin docs to the Nixpkgs manual + /* + Produce a derivation that links to the target derivation's `/bin`, + and *only* `/bin`. + + This is useful when your favourite package doesn't have a separate + bin output and other contents of the package's output (e.g. setup + hooks) cause trouble when used in your environment. + */ + onlyBin = drv: runCommand "${drv.name}-only-bin" { } '' + mkdir -p $out + ln -s ${lib.getBin drv}/bin $out/bin + ''; + + + # Docs in doc/builders/special/makesetuphook.section.md + # See https://nixos.org/manual/nixpkgs/unstable/#sec-pkgs.makeSetupHook + makeSetupHook = + { name ? lib.warn "calling makeSetupHook without passing a name is deprecated." "hook" + , deps ? [ ] + # hooks go in nativeBuildInput so these will be nativeBuildInput + , propagatedBuildInputs ? [ ] + # these will be buildInputs + , depsTargetTargetPropagated ? [ ] + , meta ? { } + , passthru ? { } + , substitutions ? { } + }: + script: + runCommand name + (substitutions // { + # TODO(@Artturin:) substitutions should be inside the env attrset + # but users are likely passing non-substitution arguments through substitutions + # turn off __structuredAttrs to unbreak substituteAll + __structuredAttrs = false; + inherit meta; + inherit depsTargetTargetPropagated; + propagatedBuildInputs = + # remove list conditionals before 23.11 + lib.warnIf (!lib.isList deps) "'deps' argument to makeSetupHook must be a list. content of deps: ${toString deps}" + (lib.warnIf (deps != [ ]) "'deps' argument to makeSetupHook is deprecated and will be removed in release 23.11., Please use propagatedBuildInputs instead. content of deps: ${toString deps}" + propagatedBuildInputs ++ (if lib.isList deps then deps else [ deps ])); + strictDeps = true; + # TODO 2023-01, no backport: simplify to inherit passthru; + passthru = passthru + // optionalAttrs (substitutions?passthru) + (warn "makeSetupHook (name = ${lib.strings.escapeNixString name}): `substitutions.passthru` is deprecated. Please set `passthru` directly." + substitutions.passthru); + }) + ('' + mkdir -p $out/nix-support + cp ${script} $out/nix-support/setup-hook + recordPropagatedDependencies + '' + lib.optionalString (substitutions != { }) '' + substituteAll ${script} $out/nix-support/setup-hook + ''); + + + # Docs in doc/build-helpers/trivial-build-helpers.chapter.md + # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-writeReferencesToFile + # TODO: Convert to throw after Nixpkgs 24.05 branch-off. + writeReferencesToFile = (if config.allowAliases then lib.warn else throw) + "writeReferencesToFile is deprecated in favour of writeClosure" + (path: writeClosure [ path ]); + + # Docs in doc/build-helpers/trivial-build-helpers.chapter.md + # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-writeClosure + writeClosure = paths: runCommand "runtime-deps" + { + # Get the cleaner exportReferencesGraph interface + __structuredAttrs = true; + exportReferencesGraph.graph = paths; + nativeBuildInputs = [ jq ]; + } + '' + jq -r ".graph | map(.path) | sort | .[]" "$NIX_ATTRS_JSON_FILE" > "$out" + ''; + + # Docs in doc/build-helpers/trivial-build-helpers.chapter.md + # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-writeDirectReferencesToFile + writeDirectReferencesToFile = path: runCommand "runtime-references" + { + exportReferencesGraph = [ "graph" path ]; + inherit path; + } + '' + touch ./references + while read p; do + read dummy + read nrRefs + if [[ $p == $path ]]; then + for ((i = 0; i < nrRefs; i++)); do + read ref; + echo $ref >>./references + done + else + for ((i = 0; i < nrRefs; i++)); do + read ref; + done + fi + done < graph + sort ./references >$out + ''; + + # TODO: move writeStringReferencesToFile docs to the Nixpkgs manual + /* + Extract a string's references to derivations and paths (its + context) and write them to a text file, removing the input string + itself from the dependency graph. This is useful when you want to + make a derivation depend on the string's references, but not its + contents (to avoid unnecessary rebuilds, for example). + + Note that this only works as intended on Nix >= 2.3. + */ + writeStringReferencesToFile = string: + /* + The basic operation this performs is to copy the string context + from `string` to a second string and wrap that string in a + derivation. However, that alone is not enough, since nothing in the + string refers to the output paths of the derivations/paths in its + context, meaning they'll be considered build-time dependencies and + removed from the wrapper derivation's closure. Putting the + necessary output paths in the new string is however not very + straightforward - the attrset returned by `getContext` contains + only references to derivations' .drv-paths, not their output + paths. In order to "convert" them, we try to extract the + corresponding paths from the original string using regex. + */ + let + # Taken from https://github.com/NixOS/nix/blob/130284b8508dad3c70e8160b15f3d62042fc730a/src/libutil/hash.cc#L84 + nixHashChars = "0123456789abcdfghijklmnpqrsvwxyz"; + context = builtins.getContext string; + derivations = lib.filterAttrs (n: v: v ? outputs) context; + # Objects copied from outside of the store, such as paths and + # `builtins.fetch*`ed ones + sources = lib.attrNames (lib.filterAttrs (n: v: v ? path) context); + packages = + lib.mapAttrs' + (name: value: + { + inherit value; + name = lib.head (builtins.match "${builtins.storeDir}/[${nixHashChars}]+-(.*)\.drv" name); + }) + derivations; + # The syntax of output paths differs between outputs named `out` + # and other, explicitly named ones. For explicitly named ones, + # the output name is suffixed as `-name`, but `out` outputs + # aren't suffixed at all, and thus aren't easily distinguished + # from named output paths. Therefore, we find all the named ones + # first so we can use them to remove false matches when looking + # for `out` outputs (see the definition of `outputPaths`). + namedOutputPaths = + lib.flatten + (lib.mapAttrsToList + (name: value: + (map + (output: + lib.filter + lib.isList + (builtins.split "(${builtins.storeDir}/[${nixHashChars}]+-${name}-${output})" string)) + (lib.remove "out" value.outputs))) + packages); + # Only `out` outputs + outputPaths = + lib.flatten + (lib.mapAttrsToList + (name: value: + if lib.elem "out" value.outputs then + lib.filter + (x: lib.isList x && + # If the matched path is in `namedOutputPaths`, + # it's a partial match of an output path where + # the output name isn't `out` + lib.all (o: !lib.hasPrefix (lib.head x) o) namedOutputPaths) + (builtins.split "(${builtins.storeDir}/[${nixHashChars}]+-${name})" string) + else + [ ]) + packages); + allPaths = lib.concatStringsSep "\n" (lib.unique (sources ++ namedOutputPaths ++ outputPaths)); + allPathsWithContext = builtins.appendContext allPaths context; + in + if builtins ? getContext then + writeText "string-references" allPathsWithContext + else + writeDirectReferencesToFile (writeText "string-file" string); + + + # Docs in doc/build-helpers/fetchers.chapter.md + # See https://nixos.org/manual/nixpkgs/unstable/#requirefile + requireFile = + { name ? null + , sha256 ? null + , sha1 ? null + , hash ? null + , url ? null + , message ? null + , hashMode ? "flat" + }: + assert (message != null) || (url != null); + assert (sha256 != null) || (sha1 != null) || (hash != null); + assert (name != null) || (url != null); + let + msg = + if message != null then message + else '' + Unfortunately, we cannot download file ${name_} automatically. + Please go to ${url} to download it yourself, and add it to the Nix store + using either + nix-store --add-fixed ${hashAlgo} ${name_} + or + nix-prefetch-url --type ${hashAlgo} file:///path/to/${name_} + ''; + hashAlgo = + if hash != null then (builtins.head (lib.strings.splitString "-" hash)) + else if sha256 != null then "sha256" + else "sha1"; + hashAlgo_ = if hash != null then "" else hashAlgo; + hash_ = + if hash != null then hash + else if sha256 != null then sha256 + else sha1; + name_ = if name == null then baseNameOf (toString url) else name; + in + stdenvNoCC.mkDerivation { + name = name_; + outputHashMode = hashMode; + outputHashAlgo = hashAlgo_; + outputHash = hash_; + preferLocalBuild = true; + allowSubstitutes = false; + builder = writeScript "restrict-message" '' + source ${stdenvNoCC}/setup + cat <<_EOF_ + + *** + ${msg} + *** + + _EOF_ + exit 1 + ''; + }; + + + # TODO: move copyPathToStore docs to the Nixpkgs manual + /* + Copy a path to the Nix store. + Nix automatically copies files to the store before stringifying paths. + If you need the store path of a file, ${copyPathToStore <path>} can be + shortened to ${<path>}. + */ + copyPathToStore = builtins.filterSource (p: t: true); + + + # TODO: move copyPathsToStore docs to the Nixpkgs manual + /* + Copy a list of paths to the Nix store. + */ + copyPathsToStore = builtins.map copyPathToStore; + + # TODO: move applyPatches docs to the Nixpkgs manual + /* Applies a list of patches to a source directory. + + Example: + + # Patching nixpkgs: + + applyPatches { + src = pkgs.path; + patches = [ + (pkgs.fetchpatch { + url = "https://github.com/NixOS/nixpkgs/commit/1f770d20550a413e508e081ddc08464e9d08ba3d.patch"; + sha256 = "1nlzx171y3r3jbk0qhvnl711kmdk57jlq4na8f8bs8wz2pbffymr"; + }) + ]; + } + + */ + applyPatches = + { src + , name ? (if builtins.typeOf src == "path" + then builtins.baseNameOf src + else + if builtins.isAttrs src && builtins.hasAttr "name" src + then src.name + else throw "applyPatches: please supply a `name` argument because a default name can only be computed when the `src` is a path or is an attribute set with a `name` attribute." + ) + "-patched" + , patches ? [ ] + , prePatch ? "" + , postPatch ? "" + , ... + }@args: + if patches == [ ] && prePatch == "" && postPatch == "" + then src # nothing to do, so use original src to avoid additional drv + else stdenvNoCC.mkDerivation + ({ + inherit name src patches prePatch postPatch; + preferLocalBuild = true; + allowSubstitutes = false; + phases = "unpackPhase patchPhase installPhase"; + installPhase = "cp -R ./ $out"; + } + # Carry `meta` information from the underlying `src` if present. + // (optionalAttrs (src?meta) { inherit (src) meta; }) + // (removeAttrs args [ "src" "name" "patches" "prePatch" "postPatch" ])); + + # TODO: move docs to Nixpkgs manual + /* An immutable file in the store with a length of 0 bytes. */ + emptyFile = runCommand "empty-file" + { + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + outputHash = "0ip26j2h11n1kgkz36rl4akv694yz65hr72q4kv4b3lxcbi65b3p"; + preferLocalBuild = true; + } "touch $out"; + + # TODO: move docs to Nixpkgs manual + /* An immutable empty directory in the store. */ + emptyDirectory = runCommand "empty-directory" + { + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + outputHash = "0sjjj9z1dhilhpc8pq4154czrb79z9cm044jvn75kxcjv6v5l2m5"; + preferLocalBuild = true; + } "mkdir $out"; +} diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test-overriding.nix b/nixpkgs/pkgs/build-support/trivial-builders/test-overriding.nix new file mode 100644 index 000000000000..a16bbbee1b1b --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test-overriding.nix @@ -0,0 +1,110 @@ +# Check that overriding works for trivial-builders like +# `writeShellScript` via `overrideAttrs`. This is useful +# to override the `checkPhase`, e. g. if you want +# to disable extglob in `writeShellScript`. +# +# Run using `nix-build -A tests.trivial-builders.overriding`. +{ lib +, stdenv +, runtimeShell +, runCommand +, callPackage +, writeShellScript +, writeTextFile +, writeShellScriptBin +}: + +let + extglobScript = '' + shopt -s extglob + touch success + echo @(success|failure) + rm success + ''; + + simpleCase = case: + writeShellScript "test-trivial-overriding-${case}" extglobScript; + + callPackageCase = case: callPackage ( + { writeShellScript }: + writeShellScript "test-trivial-callpackage-overriding-${case}" extglobScript + ) { }; + + binCase = case: + writeShellScriptBin "test-trivial-overriding-bin-${case}" extglobScript; + + # building this derivation would fail without overriding + textFileCase = writeTextFile { + name = "test-trivial-overriding-text-file"; + checkPhase = "false"; + text = '' + #!${runtimeShell} + echo success + ''; + executable = true; + }; + + disallowExtglob = x: x.overrideAttrs (_: { + checkPhase = '' + ${stdenv.shell} -n "$target" + ''; + }); + + # Run old checkPhase, but only succeed if it fails. + # This HACK is required because we can't introspect build failures + # in nix: With `assertFail` we want to make sure that the default + # `checkPhase` would fail if extglob was used in the script. + assertFail = x: x.overrideAttrs (old: { + checkPhase = '' + if + ${old.checkPhase} + then exit 1; fi + ''; + }); + + mkCase = case: outcome: isBin: + let + drv = lib.pipe outcome ([ case ] ++ lib.optionals (outcome == "fail") [ disallowExtglob assertFail ]); + in if isBin then "${drv}/bin/${drv.name}" else drv; + + writeTextOverrides = { + # Make sure extglob works by default + simpleSucc = mkCase simpleCase "succ" false; + # Ensure it's possible to fail; in this case extglob is not enabled + simpleFail = mkCase simpleCase "fail" false; + # Do the same checks after wrapping with callPackage + # to make sure callPackage doesn't mess with the override + callpSucc = mkCase callPackageCase "succ" false; + callpFail = mkCase callPackageCase "fail" false; + # Do the same check using `writeShellScriptBin` + binSucc = mkCase binCase "succ" true; + binFail = mkCase binCase "fail" true; + # Check that we can also override plain writeTextFile + textFileSuccess = textFileCase.overrideAttrs (_: { + checkPhase = "true"; + }); + }; + + # `runTest` forces nix to build the script of our test case and + # run its `checkPhase` which is our main interest. Additionally + # it executes the script and thus makes sure that extglob also + # works at run time. + runTest = script: + let + name = script.name or (builtins.baseNameOf script); + in writeShellScript "run-${name}" '' + if [ "$(${script})" != "success" ]; then + echo "Failed in ${name}" + exit 1 + fi + ''; +in + +runCommand "test-writeShellScript-overriding" { + passthru = { inherit writeTextOverrides; }; +} '' + ${lib.concatMapStrings (test: '' + ${runTest test} + '') (lib.attrValues writeTextOverrides)} + touch "$out" +'' diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test/concat-test.nix b/nixpkgs/pkgs/build-support/trivial-builders/test/concat-test.nix new file mode 100644 index 000000000000..5ce435619069 --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test/concat-test.nix @@ -0,0 +1,12 @@ +{ callPackage, lib, pkgs, runCommand, concatText, writeText, hello, emptyFile }: +let + stri = writeText "pathToTest"; + txt1 = stri "abc"; + txt2 = stri hello; + res = concatText "textToTest" [ txt1 txt2 ]; +in +runCommand "test-concatPaths" { } '' + diff -U3 <(cat ${txt1} ${txt2}) ${res} + diff -U3 ${concatText "void" []} ${emptyFile} + touch $out +'' diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test/default.nix b/nixpkgs/pkgs/build-support/trivial-builders/test/default.nix new file mode 100644 index 000000000000..e1ed0be72bf3 --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test/default.nix @@ -0,0 +1,36 @@ +/* + Run all tests with: + + cd nixpkgs + nix-build -A tests.trivial-builders + + or run a specific test with: + + cd nixpkgs + nix-build -A tests.trivial-builders.foo + +*/ + +{ callPackage, lib, stdenv }: +let + inherit (lib) recurseIntoAttrs; + references = callPackage ./references {}; +in +recurseIntoAttrs { + concat = callPackage ./concat-test.nix {}; + linkFarm = callPackage ./link-farm.nix {}; + overriding = callPackage ../test-overriding.nix {}; + inherit references; + writeCBin = callPackage ./writeCBin.nix {}; + writeClosure-union = callPackage ./writeClosure-union.nix { + inherit (references) samples; + }; + writeShellApplication = callPackage ./writeShellApplication.nix {}; + writeScriptBin = callPackage ./writeScriptBin.nix {}; + writeShellScript = callPackage ./write-shell-script.nix {}; + writeShellScriptBin = callPackage ./writeShellScriptBin.nix {}; + writeStringReferencesToFile = callPackage ./writeStringReferencesToFile.nix { + inherit (references) samples; + }; + writeTextFile = callPackage ./write-text-file.nix {}; +} diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test/link-farm.nix b/nixpkgs/pkgs/build-support/trivial-builders/test/link-farm.nix new file mode 100644 index 000000000000..1ebfc707632f --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test/link-farm.nix @@ -0,0 +1,45 @@ +{ linkFarm, hello, writeTextFile, runCommand }: +let + foo = writeTextFile { + name = "foo"; + text = "foo"; + }; + + linkFarmFromList = linkFarm "linkFarmFromList" [ + { name = "foo"; path = foo; } + { name = "hello"; path = hello; } + ]; + + linkFarmWithRepeats = linkFarm "linkFarmWithRepeats" [ + { name = "foo"; path = foo; } + { name = "hello"; path = hello; } + { name = "foo"; path = hello; } + ]; + + linkFarmFromAttrs = linkFarm "linkFarmFromAttrs" { + inherit foo hello; + }; +in +runCommand "test-linkFarm" { } '' + function assertPathEquals() { + local a b; + a="$(realpath "$1")" + b="$(realpath "$2")" + if [ "$a" != "$b" ]; then + echo "path mismatch!" + echo "a: $1 -> $a" + echo "b: $2 -> $b" + exit 1 + fi + } + + assertPathEquals "${linkFarmFromList}/foo" "${foo}" + assertPathEquals "${linkFarmFromList}/hello" "${hello}" + + assertPathEquals "${linkFarmWithRepeats}/foo" "${hello}" + assertPathEquals "${linkFarmWithRepeats}/hello" "${hello}" + + assertPathEquals "${linkFarmFromAttrs}/foo" "${foo}" + assertPathEquals "${linkFarmFromAttrs}/hello" "${hello}" + touch $out +'' diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test/references/default.nix b/nixpkgs/pkgs/build-support/trivial-builders/test/references/default.nix new file mode 100644 index 000000000000..85df57ea210c --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test/references/default.nix @@ -0,0 +1,135 @@ +{ lib +, stdenvNoCC +, testers +, callPackage +, writeText + # nativeBuildInputs +, shellcheck-minimal + # Samples +, samples ? cleanSamples (callPackage ./samples.nix { }) + # Filter out the non-string-like attributes such as <pkg>.override added by + # callPackage. +, cleanSamples ? lib.filterAttrs (n: lib.isStringLike) + # Test targets +, writeDirectReferencesToFile +, writeClosure +}: + +# -------------------------------------------------------------------------- # +# +# trivial-builders test +# +# -------------------------------------------------------------------------- # +# +# Execute this build script directly (quick): +# +# * Classic +# $ NIX_PATH="nixpkgs=$PWD" nix-shell -p tests.trivial-builders.references.testScriptBin --run references-test +# +# * Flake-based +# $ nix run .#tests.trivial-builders.references.testScriptBin +# +# or in the build sandbox with a ~20s VM overhead: +# +# * Classic +# $ nix-build --no-out-link -A tests.trivial-builders.references +# +# * Flake-based +# $ nix build -L --no-link .#tests.trivial-builders.references +# +# -------------------------------------------------------------------------- # + +let + # Map each attribute to an element specification of Bash associative arrary + # and concatenate them with white spaces, to be used to define a + # one-line Bash associative array. + samplesToString = attrs: + lib.concatMapStringsSep " " (name: "[${name}]=${lib.escapeShellArg "${attrs.${name}}"}") (builtins.attrNames attrs); + + closures = lib.mapAttrs (n: v: writeClosure [ v ]) samples; + directReferences = lib.mapAttrs (n: v: writeDirectReferencesToFile v) samples; + collectiveClosure = writeClosure (lib.attrValues samples); + + testScriptBin = stdenvNoCC.mkDerivation (finalAttrs: { + name = "references-test"; + + src = ./references-test.sh; + dontUnpack = true; + dontBuild = true; + + installPhase = '' + runHook preInstall + mkdir -p "$out/bin" + substitute "$src" "$out/bin/${finalAttrs.meta.mainProgram}" \ + --replace "@SAMPLES@" ${lib.escapeShellArg (samplesToString samples)} \ + --replace "@CLOSURES@" ${lib.escapeShellArg (samplesToString closures)} \ + --replace "@DIRECT_REFS@" ${lib.escapeShellArg (samplesToString directReferences)} \ + --replace "@COLLECTIVE_CLOSURE@" ${lib.escapeShellArg collectiveClosure} + runHook postInstall + chmod +x "$out/bin/${finalAttrs.meta.mainProgram}" + ''; + + doInstallCheck = true; + nativeInstallCheckInputs = [ + shellcheck-minimal + ]; + installCheckPhase = '' + runHook preInstallCheck + shellcheck "$out/bin/${finalAttrs.meta.mainProgram}" + runHook postInstallCheck + ''; + + passthru = { + inherit + collectiveClosure + directReferences + closures + samples + ; + }; + + meta = with lib; { + mainProgram = "references-test"; + }; + }); +in +testers.runNixOSTest ({ config, lib, ... }: +let + # Use the testScriptBin from guest pkgs. + # The attribute path to access the guest version of testScriptBin is + # tests.trivial-builders.references.config.node.pkgs.tests.trivial-builders.references.testScriptBin + # which is why passthru.guestTestScriptBin is provided. + guestTestScriptBin = config.node.pkgs.tests.trivial-builders.references.testScriptBin; +in +{ + name = "nixpkgs-trivial-builders-references"; + nodes.machine = { config, lib, pkgs, ... }: { + virtualisation.writableStore = true; + + # Test runs without network, so we don't substitute and prepare our deps + nix.settings.substituters = lib.mkForce [ ]; + system.extraDependencies = [ guestTestScriptBin ]; + }; + testScript = + '' + machine.succeed(""" + ${lib.getExe guestTestScriptBin} 2>/dev/console + """) + ''; + passthru = { + inherit + collectiveClosure + directReferences + closures + samples + testScriptBin + ; + inherit guestTestScriptBin; + }; + meta = { + maintainers = with lib.maintainers; [ + roberth + ShamrockLee + ]; + }; +}) diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test/references/references-test.sh b/nixpkgs/pkgs/build-support/trivial-builders/test/references/references-test.sh new file mode 100755 index 000000000000..92e4467287ed --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test/references/references-test.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +# -------------------------------------------------------------------------- # +# +# trivial-builders test +# +# -------------------------------------------------------------------------- # +# +# Execute this build script directly (quick): +# +# * Classic +# $ NIX_PATH="nixpkgs=$PWD" nix-shell -p tests.trivial-builders.references.testScriptBin --run references-test +# +# * Flake-based +# $ nix run .#tests.trivial-builders.references.testScriptBin +# +# or in the build sandbox with a ~20s VM overhead: +# +# * Classic +# $ nix-build --no-out-link -A tests.trivial-builders.references +# +# * Flake-based +# $ nix build -L --no-link .#tests.trivial-builders.references +# +# -------------------------------------------------------------------------- # + +# strict bash +set -euo pipefail + +# debug +# set -x +# PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' + +cd "$(dirname "${BASH_SOURCE[0]}")" # nixpkgs root + + # Inject the path to compare from the Nix expression + + # Associative Arrays + declare -A samples=( @SAMPLES@ ) + declare -A directRefs=( @DIRECT_REFS@ ) + declare -A closures=( @CLOSURES@ ) + + # Path string + collectiveClosure=@COLLECTIVE_CLOSURE@ + +echo >&2 Testing direct closures... +for i in "${!samples[@]}"; do + echo >&2 Checking "$i" "${samples[$i]}" "${directRefs[$i]}" + diff -U3 \ + <(sort <"${directRefs[$i]}") \ + <(nix-store -q --references "${samples[$i]}" | sort) +done + +echo >&2 Testing closure... +for i in "${!samples[@]}"; do + echo >&2 Checking "$i" "${samples[$i]}" "${closures[$i]}" + diff -U3 \ + <(sort <"${closures[$i]}") \ + <(nix-store -q --requisites "${samples[$i]}" | sort) +done + +echo >&2 Testing mixed closures... +echo >&2 Checking all samples "(${samples[*]})" "$collectiveClosure" +diff -U3 \ + <(sort <"$collectiveClosure") \ + <(nix-store -q --requisites "${samples[@]}" | sort) + +echo 'OK!' diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test/references/samples.nix b/nixpkgs/pkgs/build-support/trivial-builders/test/references/samples.nix new file mode 100644 index 000000000000..3afb970c08ae --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test/references/samples.nix @@ -0,0 +1,30 @@ +{ lib +, runCommand +, writeText +, emptyFile +, emptyDirectory +, figlet +, hello +, zlib +}: +{ + inherit + figlet + hello + zlib + ; + zlib-dev = zlib.dev; + norefs = writeText "hi" "hello"; + norefsDup = writeText "hi" "hello"; + helloRef = writeText "hi" "hello ${hello}"; + helloRefDup = writeText "hi" "hello ${hello}"; + path = ./samples.nix; + pathLike.outPath = ./samples.nix; + helloFigletRef = writeText "hi" "hello ${hello} ${figlet}"; + selfRef = runCommand "self-ref-1" { } "echo $out >$out"; + selfRef2 = runCommand "self-ref-2" { } ''echo "${figlet}, $out" >$out''; + inherit + emptyFile + emptyDirectory + ; +} diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test/write-shell-script.nix b/nixpkgs/pkgs/build-support/trivial-builders/test/write-shell-script.nix new file mode 100644 index 000000000000..a5c9f1fae42f --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test/write-shell-script.nix @@ -0,0 +1,14 @@ +{ lib, writeShellScript }: let + output = "hello"; +in (writeShellScript "test-script" '' + echo ${lib.escapeShellArg output} +'').overrideAttrs (old: { + checkPhase = old.checkPhase or "" + '' + expected=${lib.escapeShellArg output} + got=$("$target") + if [[ "$got" != "$expected" ]]; then + echo "wrong output: expected $expected, got $got" + exit 1 + fi + ''; +}) diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test/write-text-file.nix b/nixpkgs/pkgs/build-support/trivial-builders/test/write-text-file.nix new file mode 100644 index 000000000000..2e6685c1980b --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test/write-text-file.nix @@ -0,0 +1,71 @@ +/* + To run: + + cd nixpkgs + nix-build -A tests.trivial-builders.writeTextFile + + or to run an individual test case + + cd nixpkgs + nix-build -A tests.trivial-builders.writeTextFile.foo +*/ +{ lib, runCommand, runtimeShell, writeTextFile }: +let + veryWeirdName = ''here's a name with some "bad" characters, like spaces and quotes''; +in +lib.recurseIntoAttrs { + + different-exe-name = + let + pkg = writeTextFile { + name = "bar"; + destination = "/bin/foo"; + executable = true; + text = '' + #!${runtimeShell} + echo hi + ''; + }; + in + assert pkg.meta.mainProgram == "foo"; + assert baseNameOf (lib.getExe pkg) == "foo"; + assert pkg.name == "bar"; + runCommand "test-writeTextFile-different-exe-name" {} '' + PATH="${lib.makeBinPath [ pkg ]}:$PATH" + x=$(foo) + [[ "$x" == hi ]] + touch $out + ''; + + weird-name = writeTextFile { + name = "weird-names"; + destination = "/etc/${veryWeirdName}"; + text = ''passed!''; + checkPhase = '' + # intentionally hardcode everything here, to make sure + # Nix does not mess with file paths + + name="here's a name with some \"bad\" characters, like spaces and quotes" + fullPath="$out/etc/$name" + + if [ -f "$fullPath" ]; then + echo "[PASS] File exists!" + else + echo "[FAIL] File was not created at expected path!" + exit 1 + fi + + content=$(<"$fullPath") + expected="passed!" + + if [ "$content" = "$expected" ]; then + echo "[PASS] Contents match!" + else + echo "[FAIL] File contents don't match!" + echo " Expected: $expected" + echo " Got: $content" + exit 2 + fi + ''; + }; +} diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test/writeCBin.nix b/nixpkgs/pkgs/build-support/trivial-builders/test/writeCBin.nix new file mode 100644 index 000000000000..56cab45b3801 --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test/writeCBin.nix @@ -0,0 +1,43 @@ +/* + Run with: + + cd nixpkgs + nix-build -A tests.trivial-builders.writeCBin +*/ + +{ lib, writeCBin, runCommand }: +let + output = "hello"; + pkg = writeCBin "test-script" '' + #include <stdio.h> + int main () { + printf("hello\n"); + return 0; + } + ''; +in + assert pkg.meta.mainProgram == "test-script"; + runCommand "test-writeCBin" { } '' + + echo Testing with getExe... + + target=${lib.getExe pkg} + expected=${lib.escapeShellArg output} + got=$("$target") + if [[ "$got" != "$expected" ]]; then + echo "wrong output: expected $expected, got $got" + exit 1 + fi + + echo Testing with makeBinPath... + + PATH="${lib.makeBinPath [ pkg ]}:$PATH" + got=$(test-script) + if [[ "$got" != "$expected" ]]; then + echo "wrong output: expected $expected, got $got" + exit 1 + fi + + touch $out + '' + diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test/writeClosure-union.nix b/nixpkgs/pkgs/build-support/trivial-builders/test/writeClosure-union.nix new file mode 100644 index 000000000000..92a2bf9f0988 --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test/writeClosure-union.nix @@ -0,0 +1,23 @@ +{ lib +, runCommandLocal + # Test targets +, writeClosure +, samples +}: +runCommandLocal "test-trivial-builders-writeClosure-union" { + __structuredAttrs = true; + closures = lib.mapAttrs (n: v: writeClosure [ v ]) samples; + collectiveClosure = writeClosure (lib.attrValues samples); + inherit samples; + meta.maintainers = with lib.maintainers; [ + ShamrockLee + ]; +} '' + set -eu -o pipefail + echo >&2 Testing mixed closures... + echo >&2 Checking all samples "(''${samples[*]})" "$collectiveClosure" + diff -U3 \ + <(sort <"$collectiveClosure") \ + <(cat "''${closures[@]}" | sort | uniq) + touch "$out" +'' diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test/writeScriptBin.nix b/nixpkgs/pkgs/build-support/trivial-builders/test/writeScriptBin.nix new file mode 100644 index 000000000000..1487443130da --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test/writeScriptBin.nix @@ -0,0 +1,39 @@ +/* + Run with: + + cd nixpkgs + nix-build -A tests.trivial-builders.writeShellScriptBin +*/ + +{ lib, writeScriptBin, runCommand }: +let + output = "hello"; + pkg = writeScriptBin "test-script" '' + echo ${lib.escapeShellArg output} + ''; +in + assert pkg.meta.mainProgram == "test-script"; + runCommand "test-writeScriptBin" { } '' + + echo Testing with getExe... + + target=${lib.getExe pkg} + expected=${lib.escapeShellArg output} + got=$("$target") + if [[ "$got" != "$expected" ]]; then + echo "wrong output: expected $expected, got $got" + exit 1 + fi + + echo Testing with makeBinPath... + + PATH="${lib.makeBinPath [ pkg ]}:$PATH" + got=$(test-script) + if [[ "$got" != "$expected" ]]; then + echo "wrong output: expected $expected, got $got" + exit 1 + fi + + touch $out + '' + diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test/writeShellApplication.nix b/nixpkgs/pkgs/build-support/trivial-builders/test/writeShellApplication.nix new file mode 100644 index 000000000000..c50f5a4d283f --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test/writeShellApplication.nix @@ -0,0 +1,141 @@ +# Run with: +# nix-build -A tests.trivial-builders.writeShellApplication +{ writeShellApplication +, writeTextFile +, runCommand +, lib +, linkFarm +, diffutils +, hello +}: +let + checkShellApplication = args@{name, expected, ...}: + let + writeShellApplicationArgs = builtins.removeAttrs args ["expected"]; + script = writeShellApplication writeShellApplicationArgs; + executable = lib.getExe script; + expected' = writeTextFile { + name = "${name}-expected"; + text = expected; + }; + actual = "${name}-actual"; + in + runCommand name { } '' + echo "Running test executable ${name}" + ${executable} > ${actual} + echo "Got output from test executable:" + cat ${actual} + echo "Checking test output against expected output:" + ${diffutils}/bin/diff --color --unified ${expected'} ${actual} + touch $out + ''; +in +linkFarm "writeShellApplication-tests" { + test-meta = + let + script = writeShellApplication { + name = "test-meta"; + text = ""; + meta.description = "A test for the `writeShellApplication` `meta` argument."; + }; + in + assert script.meta.mainProgram == "test-meta"; + assert script.meta.description == "A test for the `writeShellApplication` `meta` argument."; + script; + + test-runtime-inputs = + checkShellApplication { + name = "test-runtime-inputs"; + text = '' + hello + ''; + runtimeInputs = [ hello ]; + expected = "Hello, world!\n"; + }; + + test-runtime-env = + checkShellApplication { + name = "test-runtime-env"; + runtimeEnv = { + MY_COOL_ENV_VAR = "my-cool-env-value"; + MY_OTHER_COOL_ENV_VAR = "my-other-cool-env-value"; + # Check that we can serialize a bunch of different types: + BOOL = true; + INT = 1; + LIST = [1 2 3]; + MAP = { + a = "a"; + b = "b"; + }; + }; + text = '' + echo "$MY_COOL_ENV_VAR" + echo "$MY_OTHER_COOL_ENV_VAR" + ''; + expected = '' + my-cool-env-value + my-other-cool-env-value + ''; + }; + + test-check-phase = + checkShellApplication { + name = "test-check-phase"; + text = ""; + checkPhase = '' + echo "echo -n hello" > $target + ''; + expected = "hello"; + }; + + test-argument-forwarding = + checkShellApplication { + name = "test-argument-forwarding"; + text = ""; + derivationArgs.MY_BUILD_TIME_VARIABLE = "puppy"; + derivationArgs.postCheck = '' + if [[ "$MY_BUILD_TIME_VARIABLE" != puppy ]]; then + echo "\$MY_BUILD_TIME_VARIABLE is not set to 'puppy'!" + exit 1 + fi + ''; + meta.description = "A test checking that `writeShellApplication` forwards extra arguments to `stdenv.mkDerivation`."; + expected = ""; + }; + + test-exclude-shell-checks = writeShellApplication { + name = "test-exclude-shell-checks"; + excludeShellChecks = [ "SC2016" ]; + text = '' + # Triggers SC2016: Expressions don't expand in single quotes, use double + # quotes for that. + echo '$SHELL' + ''; + }; + + test-bash-options-pipefail = checkShellApplication { + name = "test-bash-options-pipefail"; + text = '' + touch my-test-file + echo puppy | grep doggy | sed 's/doggy/puppy/g' + # ^^^^^^^^^^ This will fail. + true + ''; + # Don't use `pipefail`: + bashOptions = ["errexit" "nounset"]; + expected = ""; + }; + + test-bash-options-nounset = checkShellApplication { + name = "test-bash-options-nounset"; + text = '' + echo -n "$someUndefinedVariable" + ''; + # Don't use `nounset`: + bashOptions = []; + # Don't warn about the undefined variable at build time: + excludeShellChecks = [ "SC2154" ]; + expected = ""; + }; + +} diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test/writeShellScriptBin.nix b/nixpkgs/pkgs/build-support/trivial-builders/test/writeShellScriptBin.nix new file mode 100644 index 000000000000..e93410e25bcb --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test/writeShellScriptBin.nix @@ -0,0 +1,39 @@ +/* + Run with: + + cd nixpkgs + nix-build -A tests.trivial-builders.writeShellScriptBin +*/ + +{ lib, writeShellScriptBin, runCommand }: +let + output = "hello"; + pkg = writeShellScriptBin "test-script" '' + echo ${lib.escapeShellArg output} + ''; +in + assert pkg.meta.mainProgram == "test-script"; + runCommand "test-writeShellScriptBin" { } '' + + echo Testing with getExe... + + target=${lib.getExe pkg} + expected=${lib.escapeShellArg output} + got=$("$target") + if [[ "$got" != "$expected" ]]; then + echo "wrong output: expected $expected, got $got" + exit 1 + fi + + echo Testing with makeBinPath... + + PATH="${lib.makeBinPath [ pkg ]}:$PATH" + got=$(test-script) + if [[ "$got" != "$expected" ]]; then + echo "wrong output: expected $expected, got $got" + exit 1 + fi + + touch $out + '' + diff --git a/nixpkgs/pkgs/build-support/trivial-builders/test/writeStringReferencesToFile.nix b/nixpkgs/pkgs/build-support/trivial-builders/test/writeStringReferencesToFile.nix new file mode 100644 index 000000000000..dedd7e183a17 --- /dev/null +++ b/nixpkgs/pkgs/build-support/trivial-builders/test/writeStringReferencesToFile.nix @@ -0,0 +1,17 @@ +{ callPackage, lib, pkgs, runCommand, samples, writeText, writeStringReferencesToFile }: +let + samplePaths = lib.unique (lib.attrValues samples); + stri = x: "${x}"; + sampleText = writeText "sample-text" (lib.concatStringsSep "\n" (lib.unique (map stri samplePaths))); + stringReferencesText = + writeStringReferencesToFile + ((lib.concatMapStringsSep "fillertext" + stri + (lib.attrValues samples)) + '' + STORE=${builtins.storeDir};\nsystemctl start bar-foo.service + ''); +in +runCommand "test-writeStringReferencesToFile" { } '' + diff -U3 <(sort ${stringReferencesText}) <(sort ${sampleText}) + touch $out +'' |