diff options
Diffstat (limited to 'nixpkgs/pkgs/test')
322 files changed, 8821 insertions, 0 deletions
diff --git a/nixpkgs/pkgs/test/auto-patchelf-hook/default.nix b/nixpkgs/pkgs/test/auto-patchelf-hook/default.nix new file mode 100644 index 000000000000..6e05e729fba8 --- /dev/null +++ b/nixpkgs/pkgs/test/auto-patchelf-hook/default.nix @@ -0,0 +1,6 @@ +{ lib, callPackage }: + +lib.recurseIntoAttrs { + withStructuredAttrs = callPackage ./package.nix { __structuredAttrs = true; }; + withoutStructuredAttrs = callPackage ./package.nix { __structuredAttrs = false; }; +} diff --git a/nixpkgs/pkgs/test/auto-patchelf-hook/package.nix b/nixpkgs/pkgs/test/auto-patchelf-hook/package.nix new file mode 100644 index 000000000000..be03ee68c039 --- /dev/null +++ b/nixpkgs/pkgs/test/auto-patchelf-hook/package.nix @@ -0,0 +1,96 @@ +# This is a test for autoPatchelfHook. To test it, we just need a simple binary +# which uses the hook. We took the derivation from tonelib-jam, which sounds +# like a good candidate with a small closure, and trimmed it down. + +{ stdenv +, lib +, fetchurl +, autoPatchelfHook +, dpkg +, freetype +, curl +# This test checks that the behavior of autoPatchelfHook is correct whether +# __structuredAttrs +# (https://nixos.org/manual/nix/stable/language/advanced-attributes#adv-attr-structuredAttrs) +# is set or not. Hence __structuredAttrs is provided as a parameter. +, __structuredAttrs +}: + +let runtimeDependencies = [ + (lib.getLib curl) + "/some/dep" + "/some/other/dep" +] +# A dependency with space only works with __structuredAttrs set to true. +++ lib.lists.optional __structuredAttrs "/some/dep with space"; +in + +stdenv.mkDerivation { + name = "auto-patchelf-test"; + + src = fetchurl { + url = "https://tonelib.net/download/221222/ToneLib-Jam-amd64.deb"; + sha256 = "sha256-c6At2lRPngQPpE7O+VY/Hsfw+QfIb3COIuHfbqqIEuM="; + }; + + unpackCmd = '' + dpkg -x $curSrc source + ''; + + nativeBuildInputs = [ + dpkg + autoPatchelfHook + ]; + + installPhase = '' + mv usr $out + ''; + + buildInputs = [ + freetype + ]; + + autoPatchelfIgnoreMissingDeps = [ + "libGL.so.1" + "libasound.so.2" + ]; + + inherit runtimeDependencies; + + # Additional phase performing the actual test. + installCheckPhase = + let allDeps = runtimeDependencies ++ [ (lib.getLib freetype) ]; + in + '' + local binary="$out/bin/ToneLib-Jam" + local interpreter=$(patchelf --print-interpreter $binary) + local runpath=$(patchelf --print-rpath $binary) + local glibcStorePath="${stdenv.cc.libc}" + + # Check that the glibc path is a prefix of the interpreter. If + # autoPatchelfHook ran correctly, the binary should have set the interpreter + # to point to the store. + echo "[auto-patchelf-hook-test]: Check that the interpreter is in the store" + test "''${interpreter#$glibcStorePath}" != "$interpreter" + + readarray -td':' runpathArray < <(echo -n "$runpath") + + echo "[auto-patchelf-hook-test]: Check that the runpath has the right number of entries" + test "''${#runpathArray[@]}" -eq ${builtins.toString (builtins.length allDeps)} + + echo "[auto-patchelf-hook-test]: Check that the runpath contains the expected runtime deps" + '' + + lib.strings.concatStringsSep "\n" + (lib.lists.imap0 + (i: path: + let iAsStr = builtins.toString i; in + '' + echo "[auto-patchelf-hook-test]: Check that entry ${iAsStr} is ${path}" + test "''${paths[${iAsStr}]}" = "$path" + '') + allDeps + ); + + doInstallCheck = true; + inherit __structuredAttrs; +} diff --git a/nixpkgs/pkgs/test/buildFHSEnv/default.nix b/nixpkgs/pkgs/test/buildFHSEnv/default.nix new file mode 100644 index 000000000000..0a355e1aeeac --- /dev/null +++ b/nixpkgs/pkgs/test/buildFHSEnv/default.nix @@ -0,0 +1,84 @@ +{ lib +, buildFHSEnv +, runCommand +, stdenv +, fetchurl +, dpkg +, glibc +, callPackage +}: + +let + getSharedObjectFromDebian = sharedObjectName: src: stdenv.mkDerivation { + name = "${sharedObjectName}-fetcher"; + inherit src; + nativeBuildInputs = [ + dpkg + ]; + dontBuild = true; + dontConfigure = true; + dontFixup = true; + installPhase = '' + echo shared objects found are: + ls -l usr/lib/*/ + cp usr/lib/*/${sharedObjectName} $out + ''; + }; + + makeSharedObjectTest = sharedObject: targetPkgs: let + lddFHSEnv = buildFHSEnv { + name = "ldd-with-ncurses-FHS-env"; + inherit targetPkgs; + runScript = "ldd"; + }; + ldd-in-FHS = "${lddFHSEnv}/bin/${lddFHSEnv.name}"; + ldd = "${lib.getBin glibc}/bin/ldd"; + find_libFHSEnv = buildFHSEnv { + name = "ls-with-ncurses-FHS-env"; + targetPkgs = p: [ + p.ncurses5 + ]; + runScript = "find /lib/ -executable"; + }; + find_lib-in-FHS = "${find_libFHSEnv}/bin/${find_libFHSEnv.name}"; + in runCommand "FHS-lib-test" {} '' + echo original ldd output is: + ${ldd} ${sharedObject} + lddOutput="$(${ldd-in-FHS} ${sharedObject})" + echo ldd output inside FHS is: + echo "$lddOutput" + if echo $lddOutput | grep -q "not found"; then + echo "shared object could not find all dependencies in the FHS!" + echo The libraries below where found in the FHS: + ${find_lib-in-FHS} + exit 1 + else + echo $lddOutput > $out + fi + ''; + +in { + # This test proves an issue with buildFHSEnv - don't expect it to succeed, + # this is discussed in https://github.com/NixOS/nixpkgs/pull/279844 . + libtinfo = makeSharedObjectTest (getSharedObjectFromDebian "libedit.so.2.0.70" (fetchurl { + url = "mirror://debian/pool/main/libe/libedit/libedit2_3.1-20221030-2_amd64.deb"; + hash = "sha256-HPFKvycW0yedsS0GV6VzfPcAdKHnHTvfcyBmJePInOY="; + })) (p: let + ncurses' = p.ncurses.overrideAttrs (old: { + configureFlags = old.configureFlags ++ [ "--with-termlib" ]; + postFixup = ""; + }); + in [ + (ncurses'.override { unicodeSupport = false; }) + p.libbsd + ]); + + liblzma = makeSharedObjectTest (getSharedObjectFromDebian "libxml2.so.2.9.14" (fetchurl { + url = "mirror://debian/pool/main/libx/libxml2/libxml2_2.9.14+dfsg-1.3~deb12u1_amd64.deb"; + hash = "sha256-NbdstwOPwclAIEpPBfM/+3nQJzU85Gk5fZrc+Pmz4ac="; + })) (p: [ + p.xz + p.zlib + p.icu72 + ]); +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/atomics.cc b/nixpkgs/pkgs/test/cc-wrapper/atomics.cc new file mode 100644 index 000000000000..23601ae92f0b --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/atomics.cc @@ -0,0 +1,8 @@ +#include <atomic> +#include <cstdint> + +int main() +{ + std::atomic_int x = {0}; + return !std::atomic_is_lock_free(&x); +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/cc-main.c b/nixpkgs/pkgs/test/cc-wrapper/cc-main.c new file mode 100644 index 000000000000..06f28bc33c69 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/cc-main.c @@ -0,0 +1,7 @@ +#include <stdio.h> + +int main(int argc, char **argv) +{ + fprintf(stderr, "ok\n"); + return 0; +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/cflags-main.c b/nixpkgs/pkgs/test/cc-wrapper/cflags-main.c new file mode 100644 index 000000000000..9491232b5387 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/cflags-main.c @@ -0,0 +1,10 @@ +#include <stdio.h> +#include <foo.h> + +int main(int argc, char **argv) +{ + if (foo() != 42) + return 1; + fprintf(stderr, "ok\n"); + return 0; +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/core-foundation-main.c b/nixpkgs/pkgs/test/cc-wrapper/core-foundation-main.c new file mode 100644 index 000000000000..fb3bd3126191 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/core-foundation-main.c @@ -0,0 +1,7 @@ +#include <CoreFoundation/CoreFoundation.h> + +int main(int argc, char** argv) +{ + CFShow(CFSTR("ok")); + return 0; +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/cxx-main.cc b/nixpkgs/pkgs/test/cc-wrapper/cxx-main.cc new file mode 100644 index 000000000000..83f704617a46 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/cxx-main.cc @@ -0,0 +1,7 @@ +#include <iostream> + +int main(int argc, char **argv) +{ + std::cerr << "ok" << std::endl; + return 0; +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/default.nix b/nixpkgs/pkgs/test/cc-wrapper/default.nix new file mode 100644 index 000000000000..a0088751d4a2 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/default.nix @@ -0,0 +1,124 @@ +{ lib, stdenv, glibc, buildPackages }: + +let + # Sanitizers are not supported on Darwin. + # Sanitizer headers aren't available in older libc++ stdenvs due to a bug + sanitizersWorking = (stdenv.buildPlatform == stdenv.hostPlatform) && !stdenv.isDarwin && !stdenv.hostPlatform.isMusl && ( + (stdenv.cc.isClang && lib.versionAtLeast (lib.getVersion stdenv.cc.name) "5.0.0") + || (stdenv.cc.isGNU && stdenv.isLinux) + ); + staticLibc = lib.optionalString (stdenv.hostPlatform.libc == "glibc") "-L ${glibc.static}/lib"; + emulator = stdenv.hostPlatform.emulator buildPackages; + isCxx = stdenv.cc.libcxx != null; + libcxxStdenvSuffix = lib.optionalString isCxx "-libcxx"; +in stdenv.mkDerivation { + pname = "cc-wrapper-test-${stdenv.cc.cc.pname}${libcxxStdenvSuffix}"; + version = stdenv.cc.version; + + buildCommand = '' + echo "Testing: ${stdenv.cc.name}" >&2 + echo "With libc: ${stdenv.cc.libc.name}" >&2 + set -o pipefail + + NIX_DEBUG=1 $CC -v + NIX_DEBUG=1 $CXX -v + + echo "checking whether compiler builds valid C binaries... " >&2 + $CC -o cc-check ${./cc-main.c} + ${emulator} ./cc-check + + echo "checking whether compiler builds valid C++ binaries... " >&2 + $CXX -o cxx-check ${./cxx-main.cc} + ${emulator} ./cxx-check + + # test for https://github.com/NixOS/nixpkgs/issues/214524#issuecomment-1431745905 + # .../include/cxxabi.h:20:10: fatal error: '__cxxabi_config.h' file not found + # in libcxxStdenv + echo "checking whether cxxabi.h can be included... " >&2 + $CXX -o include-cxxabi ${./include-cxxabi.cc} + ${emulator} ./include-cxxabi + + # cxx doesn't have libatomic.so + ${lib.optionalString (!isCxx) '' + # https://github.com/NixOS/nixpkgs/issues/91285 + echo "checking whether libatomic.so can be linked... " >&2 + $CXX -shared -o atomics.so ${./atomics.cc} -latomic ${lib.optionalString (stdenv.cc.isClang && lib.versionOlder stdenv.cc.version "6.0.0" ) "-std=c++17"} + $READELF -d ./atomics.so | grep libatomic.so && echo "ok" >&2 || echo "failed" >&2 + ''} + + ${lib.optionalString (stdenv.isDarwin && stdenv.cc.isClang) '' + echo "checking whether compiler can build with CoreFoundation.framework... " >&2 + mkdir -p foo/lib + $CC -framework CoreFoundation -o core-foundation-check ${./core-foundation-main.c} + ${emulator} ./core-foundation-check + ''} + + + ${lib.optionalString (!stdenv.isDarwin) '' + echo "checking whether compiler builds valid static C binaries... " >&2 + $CC ${staticLibc} -static -o cc-static ${./cc-main.c} + ${emulator} ./cc-static + ${lib.optionalString (stdenv.cc.isGNU && lib.versionAtLeast (lib.getVersion stdenv.cc.name) "8.0.0") '' + echo "checking whether compiler builds valid static pie C binaries... " >&2 + $CC ${staticLibc} -static-pie -o cc-static-pie ${./cc-main.c} + ${emulator} ./cc-static-pie + ''} + ''} + + ${# See: https://github.com/llvm/llvm-project/commit/ed1d07282cc9d8e4c25d585e03e5c8a1b6f63a74 + # `gcc` does not support this so we gate the test on `clang` + lib.optionalString stdenv.cc.isClang '' + echo "checking whether cc-wrapper accepts -- followed by positional (file) args..." >&2 + mkdir -p positional + + # Make sure `--` is not parsed as a "non flag arg"; we should get an + # input file error here and *not* a linker error. + { ! $CC --; } |& grep -q "no input files" + + # And that positional file args _must_ be files (this is just testing + # that we remembered to put the `--` back in the args to the compiler): + { ! $CC -c -- -o foo ${./foo.c}; } \ + |& grep -q "no such file or directory: '-o'" + + # Now check that we accept single and multiple positional file args: + $CC -c -DVALUE=42 -o positional/foo.o -- ${./foo.c} + $CC -o positional/main -- positional/foo.o ${./ldflags-main.c} + ${emulator} ./positional/main + ''} + + echo "checking whether compiler uses NIX_CFLAGS_COMPILE... " >&2 + mkdir -p foo/include + cp ${./foo.c} foo/include/foo.h + NIX_CFLAGS_COMPILE="-Ifoo/include -DVALUE=42" $CC -o cflags-check ${./cflags-main.c} + ${emulator} ./cflags-check + + echo "checking whether compiler uses NIX_LDFLAGS... " >&2 + mkdir -p foo/lib + $CC -shared \ + ${lib.optionalString stdenv.isDarwin "-Wl,-install_name,@rpath/libfoo.dylib"} \ + -DVALUE=42 \ + -o foo/lib/libfoo${stdenv.hostPlatform.extensions.sharedLibrary} \ + ${./foo.c} + + NIX_LDFLAGS="-L$NIX_BUILD_TOP/foo/lib -rpath $NIX_BUILD_TOP/foo/lib" $CC -lfoo -o ldflags-check ${./ldflags-main.c} + ${emulator} ./ldflags-check + + echo "Check whether -nostdinc and -nostdinc++ is handled correctly" >&2 + mkdir -p std-include + cp ${./stdio.h} std-include/stdio.h + NIX_DEBUG=1 $CC -I std-include -nostdinc -o nostdinc-main ${./nostdinc-main.c} + ${emulator} ./nostdinc-main + $CXX -I std-include -nostdinc++ -o nostdinc-main++ ${./nostdinc-main.c} + ${emulator} ./nostdinc-main++ + + ${lib.optionalString sanitizersWorking '' + echo "checking whether sanitizers are fully functional... ">&2 + $CC -o sanitizers -fsanitize=address,undefined ${./sanitizers.c} + ASAN_OPTIONS=use_sigaltstack=0 ${emulator} ./sanitizers + ''} + + touch $out + ''; + + meta.platforms = lib.platforms.all; +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/foo.c b/nixpkgs/pkgs/test/cc-wrapper/foo.c new file mode 100644 index 000000000000..8be674be3103 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/foo.c @@ -0,0 +1,4 @@ +unsigned int foo(void) +{ + return VALUE; +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/fortify1-example.c b/nixpkgs/pkgs/test/cc-wrapper/fortify1-example.c new file mode 100644 index 000000000000..48b9c268e728 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/fortify1-example.c @@ -0,0 +1,16 @@ +/* an example that should be protected by FORTIFY_SOURCE=1 */ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + + +int main(int argc, char *argv[]) { + /* allocate on the heap so we're likely to get an + * over-allocation and can be more sure that a + * failure is because of fortify protection rather + * than a genuine segfault */ + char* buffer = malloc(sizeof(char) * 7); + strcpy(buffer, argv[1]); + puts(buffer); + return 0; +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/fortify2-example.c b/nixpkgs/pkgs/test/cc-wrapper/fortify2-example.c new file mode 100644 index 000000000000..dfb5a8e87294 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/fortify2-example.c @@ -0,0 +1,16 @@ +/* an example that should be protected by FORTIFY_SOURCE=2 but + * not FORTIFY_SOURCE=1 */ +#include <stdio.h> +#include <string.h> + +struct buffer_with_pad { + char buffer[7]; + char pad[25]; +}; + +int main(int argc, char *argv[]) { + struct buffer_with_pad b; + strcpy(b.buffer, argv[1]); + puts(b.buffer); + return 0; +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/fortify3-example.c b/nixpkgs/pkgs/test/cc-wrapper/fortify3-example.c new file mode 100644 index 000000000000..9a0a5f4792c3 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/fortify3-example.c @@ -0,0 +1,13 @@ +/* an example that should be protected by FORTIFY_SOURCE=3 but + * not FORTIFY_SOURCE=2 */ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + + +int main(int argc, char *argv[]) { + char* buffer = malloc(atoi(argv[2])); + strcpy(buffer, argv[1]); + puts(buffer); + return 0; +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/hardening.nix b/nixpkgs/pkgs/test/cc-wrapper/hardening.nix new file mode 100644 index 000000000000..41ddaefdfea8 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/hardening.nix @@ -0,0 +1,396 @@ +{ lib +, stdenv +, runCommand +, runCommandWith +, runCommandCC +, debian-devscripts +}: + +let + # writeCBin from trivial-builders won't let us choose + # our own stdenv + writeCBinWithStdenv = codePath: stdenv': env: runCommandWith { + name = "test-bin"; + stdenv = stdenv'; + derivationArgs = { + inherit codePath; + preferLocalBuild = true; + allowSubstitutes = false; + } // env; + } '' + [ -n "$preBuild" ] && eval "$preBuild" + n=$out/bin/test-bin + mkdir -p "$(dirname "$n")" + cp "$codePath" code.c + NIX_DEBUG=1 $CC -x c code.c -O1 $TEST_EXTRA_FLAGS -o "$n" + ''; + + f1exampleWithStdEnv = writeCBinWithStdenv ./fortify1-example.c; + f2exampleWithStdEnv = writeCBinWithStdenv ./fortify2-example.c; + f3exampleWithStdEnv = writeCBinWithStdenv ./fortify3-example.c; + + stdenvUnsupport = additionalUnsupported: stdenv.override { + cc = stdenv.cc.override { + cc = (lib.extendDerivation true { + hardeningUnsupportedFlags = (stdenv.cc.cc.hardeningUnsupportedFlags or []) ++ additionalUnsupported; + } stdenv.cc.cc); + }; + allowedRequisites = null; + }; + + checkTestBin = testBin: { + # can only test flags that are detectable by hardening-check + ignoreBindNow ? true, + ignoreFortify ? true, + ignorePie ? true, + ignoreRelRO ? true, + ignoreStackProtector ? true, + expectFailure ? false, + }: let + expectFailureClause = lib.optionalString expectFailure + " && echo 'ERROR: Expected hardening-check to fail, but it passed!' >&2 && exit 1"; + in runCommandCC "check-test-bin" { + nativeBuildInputs = [ debian-devscripts ]; + buildInputs = [ testBin ]; + meta.platforms = lib.platforms.linux; # ELF-reliant + } '' + hardening-check --nocfprotection \ + ${lib.optionalString ignoreBindNow "--nobindnow"} \ + ${lib.optionalString ignoreFortify "--nofortify"} \ + ${lib.optionalString ignorePie "--nopie"} \ + ${lib.optionalString ignoreRelRO "--norelro"} \ + ${lib.optionalString ignoreStackProtector "--nostackprotector"} \ + $(PATH=$HOST_PATH type -P test-bin) ${expectFailureClause} + touch $out + ''; + + nameDrvAfterAttrName = builtins.mapAttrs (name: drv: + drv.overrideAttrs (_: { name = "test-${name}"; }) + ); + + # returning a specific exit code when aborting due to a fortify + # check isn't mandated. so it's better to just ensure that a + # nonzero exit code is returned when we go a single byte beyond + # the buffer, with the example programs being designed to be + # unlikely to genuinely segfault for such a small overflow. + fortifyExecTest = testBin: runCommand "exec-test" { + buildInputs = [ + testBin + ]; + meta.broken = !(stdenv.buildPlatform.canExecute stdenv.hostPlatform); + } '' + ( + export PATH=$HOST_PATH + echo "Saturated buffer:" # check program isn't completly broken + test-bin 012345 7 + echo "One byte too far:" # eighth byte being the null terminator + (! test-bin 0123456 7) || (echo 'Expected failure, but succeeded!' && exit 1) + ) + echo "Expected behaviour observed" + touch $out + ''; + + brokenIf = cond: drv: if cond then drv.overrideAttrs (old: { meta = old.meta or {} // { broken = true; }; }) else drv; + +in nameDrvAfterAttrName ({ + bindNowExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "bindnow" ]; + }) { + ignoreBindNow = false; + }); + + # musl implementation undetectable by this means even if present + fortifyExplicitEnabled = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify" ]; + }) { + ignoreFortify = false; + }); + + fortify1ExplicitEnabledExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify" ]; + }); + + # musl implementation is effectively FORTIFY_SOURCE=1-only, + # clang-on-glibc also only appears to support FORTIFY_SOURCE=1 (!) + fortifyExplicitEnabledExecTest = brokenIf ( + stdenv.hostPlatform.isMusl || (stdenv.cc.isClang && stdenv.hostPlatform.libc == "glibc") + ) (fortifyExecTest (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify" ]; + })); + + fortify3ExplicitEnabled = brokenIf ( + stdenv.hostPlatform.isMusl || !stdenv.cc.isGNU || lib.versionOlder stdenv.cc.version "12" + ) (checkTestBin (f3exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify3" ]; + }) { + ignoreFortify = false; + }); + + # musl implementation is effectively FORTIFY_SOURCE=1-only + fortify3ExplicitEnabledExecTest = brokenIf ( + stdenv.hostPlatform.isMusl || !stdenv.cc.isGNU || lib.versionOlder stdenv.cc.version "12" + ) (fortifyExecTest (f3exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify3" ]; + })); + + pieExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "pie" ]; + }) { + ignorePie = false; + }); + + relROExplicitEnabled = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "relro" ]; + }) { + ignoreRelRO = false; + }; + + stackProtectorExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "stackprotector" ]; + }) { + ignoreStackProtector = false; + }); + + bindNowExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "bindnow" ]; + }) { + ignoreBindNow = false; + expectFailure = true; + }; + + fortifyExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "fortify" ]; + }) { + ignoreFortify = false; + expectFailure = true; + }; + + fortify3ExplicitDisabled = checkTestBin (f3exampleWithStdEnv stdenv { + hardeningDisable = [ "fortify3" ]; + }) { + ignoreFortify = false; + expectFailure = true; + }; + + fortifyExplicitDisabledDisablesFortify3 = checkTestBin (f3exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify3" ]; + hardeningDisable = [ "fortify" ]; + }) { + ignoreFortify = false; + expectFailure = true; + }; + + fortify3ExplicitDisabledDoesntDisableFortify = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify" ]; + hardeningDisable = [ "fortify3" ]; + }) { + ignoreFortify = false; + }; + + pieExplicitDisabled = brokenIf ( + stdenv.hostPlatform.isMusl && stdenv.cc.isClang + ) (checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "pie" ]; + }) { + ignorePie = false; + expectFailure = true; + }); + + # can't force-disable ("partial"?) relro + relROExplicitDisabled = brokenIf true (checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "pie" ]; + }) { + ignoreRelRO = false; + expectFailure = true; + }); + + stackProtectorExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "stackprotector" ]; + }) { + ignoreStackProtector = false; + expectFailure = true; + }; + + # most flags can't be "unsupported" by compiler alone and + # binutils doesn't have an accessible hardeningUnsupportedFlags + # mechanism, so can only test a couple of flags through altered + # stdenv trickery + + fortifyStdenvUnsupp = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["fortify"]) { + hardeningEnable = [ "fortify" ]; + }) { + ignoreFortify = false; + expectFailure = true; + }; + + fortify3StdenvUnsupp = checkTestBin (f3exampleWithStdEnv (stdenvUnsupport ["fortify3"]) { + hardeningEnable = [ "fortify3" ]; + }) { + ignoreFortify = false; + expectFailure = true; + }; + + fortifyStdenvUnsuppUnsupportsFortify3 = checkTestBin (f3exampleWithStdEnv (stdenvUnsupport ["fortify"]) { + hardeningEnable = [ "fortify3" ]; + }) { + ignoreFortify = false; + expectFailure = true; + }; + + fortify3StdenvUnsuppDoesntUnsuppFortify = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["fortify3"]) { + hardeningEnable = [ "fortify" ]; + }) { + ignoreFortify = false; + }); + + fortify3StdenvUnsuppDoesntUnsuppFortifyExecTest = fortifyExecTest (f2exampleWithStdEnv (stdenvUnsupport ["fortify3"]) { + hardeningEnable = [ "fortify" ]; + }); + + stackProtectorStdenvUnsupp = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) { + hardeningEnable = [ "stackprotector" ]; + }) { + ignoreStackProtector = false; + expectFailure = true; + }; + + # NIX_HARDENING_ENABLE set in the shell overrides hardeningDisable + # and hardeningEnable + + stackProtectorReenabledEnv = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "stackprotector" ]; + preBuild = '' + export NIX_HARDENING_ENABLE="stackprotector" + ''; + }) { + ignoreStackProtector = false; + }; + + stackProtectorReenabledFromAllEnv = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "all" ]; + preBuild = '' + export NIX_HARDENING_ENABLE="stackprotector" + ''; + }) { + ignoreStackProtector = false; + }; + + stackProtectorRedisabledEnv = checkTestBin (f2exampleWithStdEnv stdenv { + hardeningEnable = [ "stackprotector" ]; + preBuild = '' + export NIX_HARDENING_ENABLE="" + ''; + }) { + ignoreStackProtector = false; + expectFailure = true; + }; + + fortify3EnabledEnvEnablesFortify = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "fortify" "fortify3" ]; + preBuild = '' + export NIX_HARDENING_ENABLE="fortify3" + ''; + }) { + ignoreFortify = false; + }); + + fortify3EnabledEnvEnablesFortifyExecTest = fortifyExecTest (f2exampleWithStdEnv stdenv { + hardeningDisable = [ "fortify" "fortify3" ]; + preBuild = '' + export NIX_HARDENING_ENABLE="fortify3" + ''; + }); + + fortifyEnabledEnvDoesntEnableFortify3 = checkTestBin (f3exampleWithStdEnv stdenv { + hardeningDisable = [ "fortify" "fortify3" ]; + preBuild = '' + export NIX_HARDENING_ENABLE="fortify" + ''; + }) { + ignoreFortify = false; + expectFailure = true; + }; + + # NIX_HARDENING_ENABLE can't enable an unsupported feature + + stackProtectorUnsupportedEnabledEnv = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) { + preBuild = '' + export NIX_HARDENING_ENABLE="stackprotector" + ''; + }) { + ignoreStackProtector = false; + expectFailure = true; + }; + + # undetectable by this means on static even if present + fortify1ExplicitEnabledCmdlineDisabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f1exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify" ]; + preBuild = '' + export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0' + ''; + }) { + ignoreFortify = false; + expectFailure = true; + }); + + # musl implementation undetectable by this means even if present + fortify1ExplicitDisabledCmdlineEnabled = brokenIf ( + stdenv.hostPlatform.isMusl || stdenv.hostPlatform.isStatic + ) (checkTestBin (f1exampleWithStdEnv stdenv { + hardeningDisable = [ "fortify" ]; + preBuild = '' + export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1' + ''; + }) { + ignoreFortify = false; + }); + + fortify1ExplicitDisabledCmdlineEnabledExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv { + hardeningDisable = [ "fortify" ]; + preBuild = '' + export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1' + ''; + }); + + fortify1ExplicitEnabledCmdlineDisabledNoWarn = f1exampleWithStdEnv stdenv { + hardeningEnable = [ "fortify" ]; + preBuild = '' + export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0 -Werror' + ''; + }; + +} // (let + tb = f2exampleWithStdEnv stdenv { + hardeningDisable = [ "all" ]; + hardeningEnable = [ "fortify" "pie" ]; + }; +in { + + allExplicitDisabledBindNow = checkTestBin tb { + ignoreBindNow = false; + expectFailure = true; + }; + + allExplicitDisabledFortify = checkTestBin tb { + ignoreFortify = false; + expectFailure = true; + }; + + allExplicitDisabledPie = brokenIf ( + stdenv.hostPlatform.isMusl && stdenv.cc.isClang + ) (checkTestBin tb { + ignorePie = false; + expectFailure = true; + }); + + # can't force-disable ("partial"?) relro + allExplicitDisabledRelRO = brokenIf true (checkTestBin tb { + ignoreRelRO = false; + expectFailure = true; + }); + + allExplicitDisabledStackProtector = checkTestBin tb { + ignoreStackProtector = false; + expectFailure = true; + }; +})) diff --git a/nixpkgs/pkgs/test/cc-wrapper/include-cxxabi.cc b/nixpkgs/pkgs/test/cc-wrapper/include-cxxabi.cc new file mode 100644 index 000000000000..6ffc97e414a5 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/include-cxxabi.cc @@ -0,0 +1,8 @@ +#include <cxxabi.h> +#include <iostream> + +int main(int argc, char **argv) +{ + std::cerr << "ok" << std::endl; + return 0; +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/ldflags-main.c b/nixpkgs/pkgs/test/cc-wrapper/ldflags-main.c new file mode 100644 index 000000000000..89832b3bbad2 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/ldflags-main.c @@ -0,0 +1,12 @@ +#include <stdio.h> + +extern unsigned int foo(void); + +int main(int argc, char **argv) +{ + if (foo() != 42) { + return 1; + } + fprintf(stderr, "ok\n"); + return 0; +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/multilib.nix b/nixpkgs/pkgs/test/cc-wrapper/multilib.nix new file mode 100644 index 000000000000..a26880681f22 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/multilib.nix @@ -0,0 +1,37 @@ +{ lib, stdenv }: + +stdenv.mkDerivation { + name = "cc-multilib-test"; + + # XXX: "depend" on cc-wrapper test? + + # TODO: Have tests report pointer size or something; ensure they are what we asked for + buildCommand = '' + NIX_DEBUG=1 $CC -v + NIX_DEBUG=1 $CXX -v + + printf "checking whether compiler builds valid C binaries...\n " >&2 + $CC -o cc-check ${./cc-main.c} + ./cc-check + + printf "checking whether compiler builds valid 32bit C binaries...\n " >&2 + $CC -m32 -o c32-check ${./cc-main.c} + ./c32-check + + printf "checking whether compiler builds valid 64bit C binaries...\n " >&2 + $CC -m64 -o c64-check ${./cc-main.c} + ./c64-check + + printf "checking whether compiler builds valid 32bit C++ binaries...\n " >&2 + $CXX -m32 -o cxx32-check ${./cxx-main.cc} + ./cxx32-check + + printf "checking whether compiler builds valid 64bit C++ binaries...\n " >&2 + $CXX -m64 -o cxx64-check ${./cxx-main.cc} + ./cxx64-check + + touch $out + ''; + + meta.platforms = lib.platforms.x86_64; +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/nostdinc-main.c b/nixpkgs/pkgs/test/cc-wrapper/nostdinc-main.c new file mode 100644 index 000000000000..f71d155b1b27 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/nostdinc-main.c @@ -0,0 +1,8 @@ +// This one should not come from libc because of -nostdinc +#include <stdio.h> + +int main(int argc, char *argv[]) { + // provided by our own stdio.h + foo(); + return 0; +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/sanitizers.c b/nixpkgs/pkgs/test/cc-wrapper/sanitizers.c new file mode 100644 index 000000000000..93dd78a903ce --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/sanitizers.c @@ -0,0 +1,8 @@ +#include <sanitizer/asan_interface.h> +#include <stdio.h> + +int main(int argc, char **argv) +{ + fprintf(stderr, "ok\n"); + return 0; +} diff --git a/nixpkgs/pkgs/test/cc-wrapper/stdio.h b/nixpkgs/pkgs/test/cc-wrapper/stdio.h new file mode 100644 index 000000000000..4bddf1d9d486 --- /dev/null +++ b/nixpkgs/pkgs/test/cc-wrapper/stdio.h @@ -0,0 +1 @@ +static void foo(void) {} diff --git a/nixpkgs/pkgs/test/checkpointBuild/default.nix b/nixpkgs/pkgs/test/checkpointBuild/default.nix new file mode 100644 index 000000000000..0843dcd3d6eb --- /dev/null +++ b/nixpkgs/pkgs/test/checkpointBuild/default.nix @@ -0,0 +1,57 @@ +{ hello, checkpointBuildTools, runCommand, texinfo, stdenv, rsync }: +let + baseHelloArtifacts = checkpointBuildTools.prepareCheckpointBuild hello; + patchedHello = hello.overrideAttrs (old: { + buildInputs = [ texinfo ]; + src = runCommand "patch-hello-src" { } '' + mkdir -p $out + cd $out + tar xf ${hello.src} --strip-components=1 + patch -p1 < ${./hello.patch} + ''; + }); + checkpointBuiltHello = checkpointBuildTools.mkCheckpointBuild patchedHello baseHelloArtifacts; + + checkpointBuiltHelloWithCheck = checkpointBuiltHello.overrideAttrs (old: { + doCheck = true; + checkPhase = '' + echo "checking if unchanged source file is not recompiled" + [ "$(stat --format="%Y" lib/exitfail.o)" = "$(stat --format="%Y" ${baseHelloArtifacts}/outputs/lib/exitfail.o)" ] + ''; + }); + + baseHelloRemoveFileArtifacts = checkpointBuildTools.prepareCheckpointBuild (hello.overrideAttrs (old: { + patches = [ ./hello-additionalFile.patch ]; + })); + + preparedHelloRemoveFileSrc = runCommand "patch-hello-src" { } '' + mkdir -p $out + cd $out + tar xf ${hello.src} --strip-components=1 + patch -p1 < ${./hello-additionalFile.patch} + ''; + + patchedHelloRemoveFile = hello.overrideAttrs (old: { + buildInputs = [ texinfo ]; + src = runCommand "patch-hello-src" { } '' + mkdir -p $out + cd $out + ${rsync}/bin/rsync -cutU --chown=$USER:$USER --chmod=+w -r ${preparedHelloRemoveFileSrc}/* . + patch -p1 < ${./hello-removeFile.patch} + ''; + }); + + checkpointBuiltHelloWithRemovedFile = checkpointBuildTools.mkCheckpointBuild patchedHelloRemoveFile baseHelloRemoveFileArtifacts; +in +stdenv.mkDerivation { + name = "patched-hello-returns-correct-output"; + buildCommand = '' + touch $out + + echo "testing output of hello binary" + [ "$(${checkpointBuiltHelloWithCheck}/bin/hello)" = "Hello, incremental world!" ] + echo "testing output of hello with removed file" + [ "$(${checkpointBuiltHelloWithRemovedFile}/bin/hello)" = "Hello, incremental world!" ] + ''; +} + diff --git a/nixpkgs/pkgs/test/checkpointBuild/hello-additionalFile.patch b/nixpkgs/pkgs/test/checkpointBuild/hello-additionalFile.patch new file mode 100644 index 000000000000..345bc10ee49e --- /dev/null +++ b/nixpkgs/pkgs/test/checkpointBuild/hello-additionalFile.patch @@ -0,0 +1,67 @@ +:100644 100644 0000000 0000000 M Makefile.in +:000000 100644 0000000 0000000 A src/additionalFile.c +:100644 100644 0000000 0000000 M src/hello.c +:100644 100644 0000000 0000000 M src/system.h + +diff --git a/Makefile.in b/Makefile.in +index 1597d39..f63f830 100644 +--- a/Makefile.in ++++ b/Makefile.in +@@ -312,7 +312,7 @@ am_lib_libhello_a_OBJECTS = lib/basename-lgpl.$(OBJEXT) \ + lib/version-etc.$(OBJEXT) lib/version-etc-fsf.$(OBJEXT) \ + lib/wctype-h.$(OBJEXT) lib/xmalloc.$(OBJEXT) \ + lib/xalloc-die.$(OBJEXT) lib/xstriconv.$(OBJEXT) \ +- lib/xstrndup.$(OBJEXT) ++ lib/xstrndup.$(OBJEXT) src/additionalFile.$(OBJEXT) + lib_libhello_a_OBJECTS = $(am_lib_libhello_a_OBJECTS) + am_hello_OBJECTS = src/hello.$(OBJEXT) + hello_OBJECTS = $(am_hello_OBJECTS) +@@ -1842,7 +1842,7 @@ lib_libhello_a_SOURCES = lib/basename-lgpl.c lib/c-ctype.h \ + $(am__append_4) $(am__append_5) lib/version-etc.h \ + lib/version-etc.c lib/version-etc-fsf.c lib/wctype-h.c \ + lib/xmalloc.c lib/xalloc-die.c lib/xstriconv.h lib/xstriconv.c \ +- lib/xstrndup.h lib/xstrndup.c ++ lib/xstrndup.h lib/xstrndup.c src/additionalFile.c + lib_libhello_a_LIBADD = $(gl_LIBOBJS) + lib_libhello_a_DEPENDENCIES = $(gl_LIBOBJS) + EXTRA_lib_libhello_a_SOURCES = lib/close.c lib/stripslash.c lib/dup2.c \ +diff --git a/src/additionalFile.c b/src/additionalFile.c +new file mode 100644 +index 0000000..34d683d +--- /dev/null ++++ b/src/additionalFile.c +@@ -0,0 +1,6 @@ ++#include "config.h" ++#include "system.h" ++ ++int somefunc() { ++ return 0; ++} +diff --git a/src/hello.c b/src/hello.c +index 2e7d38e..a8e36dc 100644 +--- a/src/hello.c ++++ b/src/hello.c +@@ -146,7 +146,11 @@ main (int argc, char *argv[]) + #endif + + /* Having initialized gettext, get the default message. */ +- greeting_msg = _("Hello, world!"); ++ if (somefunc() == 0) { ++ greeting_msg = _("Hello, world!"); ++ } else { ++ greeting_msg = _("Hello, incremental world!"); ++ } + + /* Even exiting has subtleties. On exit, if any writes failed, change + the exit status. The /dev/full device on GNU/Linux can be used for +diff --git a/src/system.h b/src/system.h +index d39cdb9..dc425d2 100644 +--- a/src/system.h ++++ b/src/system.h +@@ -59,4 +59,6 @@ + } \ + while (0) + ++int somefunc(); ++ + #endif /* HELLO_SYSTEM_H */ diff --git a/nixpkgs/pkgs/test/checkpointBuild/hello-removeFile.patch b/nixpkgs/pkgs/test/checkpointBuild/hello-removeFile.patch new file mode 100644 index 000000000000..2939790dabce --- /dev/null +++ b/nixpkgs/pkgs/test/checkpointBuild/hello-removeFile.patch @@ -0,0 +1,67 @@ +:100644 100644 0000000 0000000 M Makefile.in +:100644 000000 0000000 0000000 D src/additionalFile.c +:100644 100644 0000000 0000000 M src/hello.c +:100755 100755 0000000 0000000 M tests/hello-1 + +diff --git a/Makefile.in b/Makefile.in +index f63f830..1597d39 100644 +--- a/Makefile.in ++++ b/Makefile.in +@@ -312,7 +312,7 @@ am_lib_libhello_a_OBJECTS = lib/basename-lgpl.$(OBJEXT) \ + lib/version-etc.$(OBJEXT) lib/version-etc-fsf.$(OBJEXT) \ + lib/wctype-h.$(OBJEXT) lib/xmalloc.$(OBJEXT) \ + lib/xalloc-die.$(OBJEXT) lib/xstriconv.$(OBJEXT) \ +- lib/xstrndup.$(OBJEXT) src/additionalFile.$(OBJEXT) ++ lib/xstrndup.$(OBJEXT) + lib_libhello_a_OBJECTS = $(am_lib_libhello_a_OBJECTS) + am_hello_OBJECTS = src/hello.$(OBJEXT) + hello_OBJECTS = $(am_hello_OBJECTS) +@@ -1842,7 +1842,7 @@ lib_libhello_a_SOURCES = lib/basename-lgpl.c lib/c-ctype.h \ + $(am__append_4) $(am__append_5) lib/version-etc.h \ + lib/version-etc.c lib/version-etc-fsf.c lib/wctype-h.c \ + lib/xmalloc.c lib/xalloc-die.c lib/xstriconv.h lib/xstriconv.c \ +- lib/xstrndup.h lib/xstrndup.c src/additionalFile.c ++ lib/xstrndup.h lib/xstrndup.c + lib_libhello_a_LIBADD = $(gl_LIBOBJS) + lib_libhello_a_DEPENDENCIES = $(gl_LIBOBJS) + EXTRA_lib_libhello_a_SOURCES = lib/close.c lib/stripslash.c lib/dup2.c \ +diff --git a/src/additionalFile.c b/src/additionalFile.c +deleted file mode 100644 +index 34d683d..0000000 +--- a/src/additionalFile.c ++++ /dev/null +@@ -1,6 +0,0 @@ +-#include "config.h" +-#include "system.h" +- +-int somefunc() { +- return 0; +-} +diff --git a/src/hello.c b/src/hello.c +index a8e36dc..53722d9 100644 +--- a/src/hello.c ++++ b/src/hello.c +@@ -126,6 +126,10 @@ parse_options (int argc, char *argv[], const char **greeting_msg) + } + } + ++int somefunc() { ++ return 1; ++} ++ + int + main (int argc, char *argv[]) + { +diff --git a/tests/hello-1 b/tests/hello-1 +index 96ffef8..f0b9f8d 100755 +--- a/tests/hello-1 ++++ b/tests/hello-1 +@@ -21,7 +21,7 @@ export LANGUAGE LC_ALL LC_MESSAGES LANG + + tmpfiles="hello-test1.ok" + cat <<EOF > hello-test1.ok +-Hello, world! ++Hello, incremental world! + EOF + + tmpfiles="$tmpfiles hello-test1.out" diff --git a/nixpkgs/pkgs/test/checkpointBuild/hello.patch b/nixpkgs/pkgs/test/checkpointBuild/hello.patch new file mode 100644 index 000000000000..3d0d50c2f20e --- /dev/null +++ b/nixpkgs/pkgs/test/checkpointBuild/hello.patch @@ -0,0 +1,26 @@ +diff --git a/src/hello.c b/src/hello.c +index 182303c..453962f 100644 +--- a/src/hello.c ++++ b/src/hello.c +@@ -57,7 +57,7 @@ main (int argc, char *argv[]) + #endif + + /* Having initialized gettext, get the default message. */ +- greeting_msg = _("Hello, world!"); ++ greeting_msg = _("Hello, incremental world!"); + + /* Even exiting has subtleties. On exit, if any writes failed, change + the exit status. The /dev/full device on GNU/Linux can be used for +diff --git a/tests/hello-1 b/tests/hello-1 +index 3b7a815..e15fa95 100755 +--- a/tests/hello-1 ++++ b/tests/hello-1 +@@ -21,7 +21,7 @@ export LANGUAGE LC_ALL LC_MESSAGES LANG + + tmpfiles="hello-test1.ok" + cat <<EOF > hello-test1.ok +-Hello, world! ++Hello, incremental world! + EOF + + tmpfiles="$tmpfiles hello-test1.out" diff --git a/nixpkgs/pkgs/test/config.nix b/nixpkgs/pkgs/test/config.nix new file mode 100644 index 000000000000..734e1aace148 --- /dev/null +++ b/nixpkgs/pkgs/test/config.nix @@ -0,0 +1,24 @@ +{ lib, ... }: +lib.recurseIntoAttrs { + + # https://github.com/NixOS/nixpkgs/issues/175196 + allowPkgsInPermittedInsecurePackages = + let pkgs = import ../.. { + config = { + permittedInsecurePackages = + tempAllow pkgs.authy "2.1.0" [ "electron-9.4.4" ]; + }; + }; + # A simplification of `tempAllow` that doesn't check the version, but + # has the same strictness characteristics. Actually checking a version + # here would add undue maintenance. + # + # Original: + # tempAllow = p: v: pa: + # lib.optionals (lib.assertMsg (p.version == v) "${p.name} is no longer at version ${v}, consider removing the tempAllow") pa; + # + tempAllow = p: v: pa: builtins.seq v builtins.seq p.version pa; + + in pkgs.hello; + +} diff --git a/nixpkgs/pkgs/test/coq/default.nix b/nixpkgs/pkgs/test/coq/default.nix new file mode 100644 index 000000000000..cf59dd473b4c --- /dev/null +++ b/nixpkgs/pkgs/test/coq/default.nix @@ -0,0 +1,6 @@ +{ lib, callPackage }: + +lib.recurseIntoAttrs { + overrideCoqDerivation = callPackage ./overrideCoqDerivation { }; +} + diff --git a/nixpkgs/pkgs/test/coq/overrideCoqDerivation/default.nix b/nixpkgs/pkgs/test/coq/overrideCoqDerivation/default.nix new file mode 100644 index 000000000000..c251c498e609 --- /dev/null +++ b/nixpkgs/pkgs/test/coq/overrideCoqDerivation/default.nix @@ -0,0 +1,40 @@ +{ lib, coq, mkCoqPackages, runCommand }: + +let + + # This is just coq, but with dontFilter set to true. We need to set + # dontFilter to true here so that _all_ packages are visibile in coqPackages. + # There may be some versions of the top-level coq and coqPackages that don't + # build QuickChick, which is what we are using for this test below. + coqWithAllPackages = coq // { dontFilter = true; }; + + coqPackages = mkCoqPackages coqWithAllPackages; + + # This is the main test. This uses overrideCoqDerivation to + # override arguments to mkCoqDerivation. + # + # Here, we override the defaultVersion and release arguments to + # mkCoqDerivation. + overriddenQuickChick = + coqPackages.lib.overrideCoqDerivation + { + defaultVersion = "9999"; + release."9999".sha256 = lib.fakeSha256; + } + coqPackages.QuickChick; +in + +runCommand + "coq-overrideCoqDerivation-test-0.1" + { meta.maintainers = with lib.maintainers; [cdepillabout]; } + '' + # Confirm that the computed version number for the overridden QuickChick does + # actually become 9999, as set above. + if [ "${overriddenQuickChick.version}" -eq "9999" ]; then + echo "overriddenQuickChick version was successfully set to 9999" + touch $out + else + echo "ERROR: overriddenQuickChick version was supposed to be 9999, but was actually: ${overriddenQuickChick.version}" + exit 1 + fi + '' diff --git a/nixpkgs/pkgs/test/cross/default.nix b/nixpkgs/pkgs/test/cross/default.nix new file mode 100644 index 000000000000..bd233db4cd50 --- /dev/null +++ b/nixpkgs/pkgs/test/cross/default.nix @@ -0,0 +1,169 @@ +{ pkgs, lib }: + +let + + testedSystems = lib.filterAttrs (name: value: let + platform = lib.systems.elaborate value; + in platform.isLinux || platform.isWindows + ) lib.systems.examples; + + getExecutable = pkgs: pkgFun: exec: + "${pkgFun pkgs}${exec}${pkgs.stdenv.hostPlatform.extensions.executable}"; + + compareTest = { emulator, pkgFun, hostPkgs, crossPkgs, exec, args ? [] }: let + pkgName = (pkgFun hostPkgs).name; + args' = lib.concatStringsSep " " args; + in crossPkgs.runCommand "test-${pkgName}-${crossPkgs.stdenv.hostPlatform.config}" { + nativeBuildInputs = [ pkgs.dos2unix ]; + } '' + # Just in case we are using wine, get rid of that annoying extra + # stuff. + export WINEDEBUG=-all + + HOME=$(pwd) + mkdir -p $out + + # We need to remove whitespace, unfortunately + # Windows programs use \r but Unix programs use \n + + echo Running native-built program natively + + # find expected value natively + ${getExecutable hostPkgs pkgFun exec} ${args'} \ + | dos2unix > $out/expected + + echo Running cross-built program in emulator + + # run emulator to get actual value + ${emulator} ${getExecutable crossPkgs pkgFun exec} ${args'} \ + | dos2unix > $out/actual + + echo Comparing results... + + if [ "$(cat $out/actual)" != "$(cat $out/expected)" ]; then + echo "${pkgName} did not output expected value:" + cat $out/expected + echo "instead it output:" + cat $out/actual + exit 1 + else + echo "${pkgName} test passed" + echo "both produced output:" + cat $out/actual + fi + ''; + + mapMultiPlatformTest = crossSystemFun: test: lib.mapAttrs (name: system: test rec { + crossPkgs = import pkgs.path { + localSystem = { inherit (pkgs.stdenv.hostPlatform) config; }; + crossSystem = crossSystemFun system; + }; + + emulator = crossPkgs.stdenv.hostPlatform.emulator pkgs; + + # Apply some transformation on windows to get dlls in the right + # place. Unfortunately mingw doesn’t seem to be able to do linking + # properly. + platformFun = pkg: if crossPkgs.stdenv.hostPlatform.isWindows then + pkgs.buildEnv { + name = "${pkg.name}-winlinks"; + paths = [pkg] ++ pkg.buildInputs; + } else pkg; + }) testedSystems; + + tests = { + + file = {platformFun, crossPkgs, emulator}: compareTest { + inherit emulator crossPkgs; + hostPkgs = pkgs; + exec = "/bin/file"; + args = [ + "${pkgs.file}/share/man/man1/file.1.gz" + "${pkgs.dejavu_fonts}/share/fonts/truetype/DejaVuMathTeXGyre.ttf" + ]; + pkgFun = pkgs: platformFun pkgs.file; + }; + + hello = {platformFun, crossPkgs, emulator}: compareTest { + inherit emulator crossPkgs; + hostPkgs = pkgs; + exec = "/bin/hello"; + pkgFun = pkgs: pkgs.hello; + }; + + pkg-config = {platformFun, crossPkgs, emulator}: crossPkgs.runCommand + "test-pkg-config-${crossPkgs.stdenv.hostPlatform.config}" + { + depsBuildBuild = [ crossPkgs.pkgsBuildBuild.pkg-config ]; + nativeBuildInputs = [ crossPkgs.pkgsBuildHost.pkg-config crossPkgs.buildPackages.zlib ]; + depsBuildTarget = [ crossPkgs.pkgsBuildTarget.pkg-config ]; + buildInputs = [ crossPkgs.zlib ]; + NIX_DEBUG = 7; + } '' + mkdir $out + ${crossPkgs.pkgsBuildBuild.pkg-config.targetPrefix}pkg-config --cflags zlib > "$out/for-build" + ${crossPkgs.pkgsBuildHost.pkg-config.targetPrefix}pkg-config --cflags zlib > "$out/for-host" + ! diff "$out/for-build" "$out/for-host" + ''; + }; + + # see https://github.com/NixOS/nixpkgs/issues/213453 + # this is a good test of a lot of tricky glibc/libgcc corner cases + mbuffer = let + mbuffer = pkgs.pkgsCross.aarch64-multiplatform.mbuffer; + emulator = with lib.systems; (elaborate examples.aarch64-multiplatform).emulator pkgs; + in + pkgs.runCommand "test-mbuffer" {} '' + echo hello | ${emulator} ${mbuffer}/bin/mbuffer + touch $out + ''; + + # This is meant to be a carefully curated list of builds/packages + # that tend to break when refactoring our cross-compilation + # infrastructure. + # + # It should strike a balance between being small enough to fit in + # a single eval (i.e. not so large that hydra-eval-jobs is needed) + # so we can ask @ofborg to check it, yet should have good examples + # of things that often break. So, no buckshot `mapTestOnCross` + # calls here. + sanity = [ + mbuffer + #pkgs.pkgsCross.gnu64.bash # https://github.com/NixOS/nixpkgs/issues/243164 + pkgs.gcc_multi.cc + pkgs.pkgsMusl.stdenv + pkgs.pkgsLLVM.stdenv + pkgs.pkgsStatic.bash + #pkgs.pkgsCross.gnu64_simplekernel.bash # https://github.com/NixOS/nixpkgs/issues/264989 + pkgs.pkgsCross.arm-embedded.stdenv + pkgs.pkgsCross.sheevaplug.stdenv # for armv5tel + pkgs.pkgsCross.raspberryPi.stdenv # for armv6l + pkgs.pkgsCross.armv7l-hf-multiplatform.stdenv + pkgs.pkgsCross.m68k.stdenv + pkgs.pkgsCross.aarch64-multiplatform.pkgsBuildTarget.gcc + pkgs.pkgsCross.powernv.pkgsBuildTarget.gcc + pkgs.pkgsCross.s390.stdenv + pkgs.pkgsCross.mips64el-linux-gnuabi64.stdenv + pkgs.pkgsCross.mips64el-linux-gnuabin32.stdenv + pkgs.pkgsCross.mingwW64.stdenv + + ] ++ lib.optionals (with pkgs.stdenv.buildPlatform; isx86_64 && isLinux) [ + # Musl-to-glibc cross on the same architecture tends to turn up + # lots of interesting corner cases. Only expected to work for + # x86_64-linux buildPlatform. + pkgs.pkgsMusl.pkgsCross.gnu64.hello + + # Two web browsers -- exercises almost the entire packageset + pkgs.pkgsCross.aarch64-multiplatform.qutebrowser-qt5 + pkgs.pkgsCross.aarch64-multiplatform.firefox + + # Uses pkgsCross.riscv64-embedded; see https://github.com/NixOS/nixpkgs/issues/267859 + pkgs.spike + ]; + +in { + gcc = (lib.mapAttrs (_: mapMultiPlatformTest (system: system // {useLLVM = false;})) tests); + llvm = (lib.mapAttrs (_: mapMultiPlatformTest (system: system // {useLLVM = true;})) tests); + + inherit mbuffer sanity; +} diff --git a/nixpkgs/pkgs/test/cuda/default.nix b/nixpkgs/pkgs/test/cuda/default.nix new file mode 100644 index 000000000000..dd9ad8b814dc --- /dev/null +++ b/nixpkgs/pkgs/test/cuda/default.nix @@ -0,0 +1,51 @@ +{ + lib, + recurseIntoAttrs, + + cudaPackages, + cudaPackagesGoogle, + + cudaPackages_10_0, + cudaPackages_10_1, + cudaPackages_10_2, + cudaPackages_10, + + cudaPackages_11_0, + cudaPackages_11_1, + cudaPackages_11_2, + cudaPackages_11_3, + cudaPackages_11_4, + cudaPackages_11_5, + cudaPackages_11_6, + cudaPackages_11_7, + cudaPackages_11_8, + cudaPackages_11, + + cudaPackages_12_0, + cudaPackages_12_1, + cudaPackages_12_2, + cudaPackages_12_3, + cudaPackages_12, +}@args: + +let + isTest = + name: package: + builtins.elem (package.pname or null) [ + "cuda-samples" + "cuda-library-samples" + "saxpy" + ]; +in +(lib.trivial.pipe args [ + (lib.filterAttrs (name: _: lib.hasPrefix "cudaPackages" name)) + (lib.mapAttrs ( + _: ps: + lib.pipe ps [ + (lib.filterAttrs isTest) + (as: as // { __attrsFailEvaluation = true; }) + recurseIntoAttrs + ] + )) + recurseIntoAttrs +]) diff --git a/nixpkgs/pkgs/test/cue/default.nix b/nixpkgs/pkgs/test/cue/default.nix new file mode 100644 index 000000000000..2cc8bf34bb04 --- /dev/null +++ b/nixpkgs/pkgs/test/cue/default.nix @@ -0,0 +1,24 @@ +{ writeCueValidator, runCommand, writeText, ... }: + +let + validator = writeCueValidator + (writeText "schema.cue" '' + #Def1: { + field1: string + } + '') + { document = "#Def1"; }; +in runCommand "cue-validation" {} '' + cat > valid.json <<EOF + { "field1": "abc" } + EOF + cat > invalid.json <<EOF + { "field2": "abc" } + EOF + ${validator} valid.json + if ${validator} invalid.json; then + echo "this example should fail" + exit 1 + fi + touch $out +'' diff --git a/nixpkgs/pkgs/test/default.nix b/nixpkgs/pkgs/test/default.nix new file mode 100644 index 000000000000..5382cae6159f --- /dev/null +++ b/nixpkgs/pkgs/test/default.nix @@ -0,0 +1,178 @@ +{ pkgs, callPackage }: + +with pkgs; + +{ + cc-wrapper = with builtins; let + pkgNames = (attrNames pkgs); + llvmTests = let + pkgSets = lib.pipe pkgNames [ + (filter (lib.hasPrefix "llvmPackages")) + (filter (n: n != "rocmPackages.llvm")) + # Are throw aliases. + (filter (n: n != "llvmPackages_rocm")) + (filter (n: n != "llvmPackages_latest")) + (filter (n: n != "llvmPackages_git")) + (filter (n: n != "llvmPackages_6")) + (filter (n: n != "llvmPackages_7")) + (filter (n: n != "llvmPackages_8")) + (filter (n: n != "llvmPackages_10")) + ]; + tests = lib.genAttrs pkgSets (name: recurseIntoAttrs { + clang = callPackage ./cc-wrapper { stdenv = pkgs.${name}.stdenv; }; + libcxx = callPackage ./cc-wrapper { stdenv = pkgs.${name}.libcxxStdenv; }; + }); + in + tests; + gccTests = let + pkgSets = lib.pipe (attrNames pkgs) ([ + (filter (lib.hasPrefix "gcc")) + (filter (lib.hasSuffix "Stdenv")) + (filter (n: n != "gccCrossLibcStdenv")) + ] ++ lib.optionals (!( + (stdenv.buildPlatform.isLinux && stdenv.buildPlatform.isx86_64) && + (stdenv.hostPlatform.isLinux && stdenv.hostPlatform.isx86_64) + )) [ + (filter (n: !lib.hasSuffix "MultiStdenv" n)) + ]); + in lib.genAttrs pkgSets (name: callPackage ./cc-wrapper { stdenv = pkgs.${name}; }); + in recurseIntoAttrs { + default = callPackage ./cc-wrapper { }; + + supported = stdenv.mkDerivation { + name = "cc-wrapper-supported"; + builtGCC = + let + inherit (lib) filterAttrs; + sets = lib.pipe gccTests ([ + (filterAttrs (_: v: lib.meta.availableOn stdenv.hostPlatform v.stdenv.cc)) + # Broken + (filterAttrs (n: _: n != "gcc49Stdenv")) + (filterAttrs (n: _: n != "gccMultiStdenv")) + ] ++ lib.optionals (stdenv.hostPlatform.isDarwin && stdenv.hostPlatform.isAarch64) [ + # fails with things like + # ld: warning: ld: warning: object file (trunctfsf2_s.o) was built for newer macOS version (11.0) than being linked (10.5) + # ld: warning: ld: warning: could not create compact unwind for ___fixunstfdi: register 20 saved somewhere other than in frame + (filterAttrs (n: _: n != "gcc11Stdenv")) + ]); + in + toJSON sets; + + builtLLVM = + let + inherit (lib) filterAttrs; + sets = lib.pipe llvmTests ([ + (filterAttrs (_: v: lib.meta.availableOn stdenv.hostPlatform v.clang.stdenv.cc)) + (filterAttrs (_: v: lib.meta.availableOn stdenv.hostPlatform v.libcxx.stdenv.cc)) + + # libcxxStdenv broken + # fix in https://github.com/NixOS/nixpkgs/pull/216273 + ] ++ lib.optionals (stdenv.hostPlatform.isDarwin && stdenv.hostPlatform.isAarch64) [ + (filterAttrs (n: _: n != "llvmPackages_9")) + ]); + in + toJSON sets; + buildCommand = '' + touch $out + ''; + }; + + llvmTests = recurseIntoAttrs llvmTests; + inherit gccTests; + }; + + stdenv-inputs = callPackage ./stdenv-inputs { }; + stdenv = callPackage ./stdenv { }; + + hardeningFlags = recurseIntoAttrs (callPackage ./cc-wrapper/hardening.nix {}); + hardeningFlags-gcc = recurseIntoAttrs (callPackage ./cc-wrapper/hardening.nix { + stdenv = gccStdenv; + }); + hardeningFlags-clang = recurseIntoAttrs (callPackage ./cc-wrapper/hardening.nix { + stdenv = llvmPackages.stdenv; + }); + + config = callPackage ./config.nix { }; + + top-level = callPackage ./top-level { }; + + haskell = callPackage ./haskell { }; + + hooks = callPackage ./hooks { }; + + cc-multilib-gcc = callPackage ./cc-wrapper/multilib.nix { stdenv = gccMultiStdenv; }; + cc-multilib-clang = callPackage ./cc-wrapper/multilib.nix { stdenv = clangMultiStdenv; }; + + fetchurl = callPackages ../build-support/fetchurl/tests.nix { }; + fetchtorrent = callPackages ../build-support/fetchtorrent/tests.nix { }; + fetchpatch = callPackages ../build-support/fetchpatch/tests.nix { }; + fetchpatch2 = callPackages ../build-support/fetchpatch/tests.nix { fetchpatch = fetchpatch2; }; + fetchDebianPatch = callPackages ../build-support/fetchdebianpatch/tests.nix { }; + fetchzip = callPackages ../build-support/fetchzip/tests.nix { }; + fetchgit = callPackages ../build-support/fetchgit/tests.nix { }; + fetchFirefoxAddon = callPackages ../build-support/fetchfirefoxaddon/tests.nix { }; + fetchPypiLegacy = callPackages ../build-support/fetchpypilegacy/tests.nix { }; + + install-shell-files = callPackage ./install-shell-files {}; + + checkpointBuildTools = callPackage ./checkpointBuild {}; + + kernel-config = callPackage ./kernel.nix {}; + + ld-library-path = callPackage ./ld-library-path {}; + + macOSSierraShared = callPackage ./macos-sierra-shared {}; + + cross = callPackage ./cross {} // { __attrsFailEvaluation = true; }; + + php = recurseIntoAttrs (callPackages ./php {}); + + pkg-config = recurseIntoAttrs (callPackage ../top-level/pkg-config/tests.nix { }) // { __recurseIntoDerivationForReleaseJobs = true; }; + + buildRustCrate = callPackage ../build-support/rust/build-rust-crate/test { }; + importCargoLock = callPackage ../build-support/rust/test/import-cargo-lock { }; + + vim = callPackage ./vim {}; + + nixos-functions = callPackage ./nixos-functions {}; + + overriding = callPackage ./overriding.nix { }; + + texlive = callPackage ./texlive {}; + + cuda = callPackage ./cuda { }; + + trivial-builders = callPackage ../build-support/trivial-builders/test/default.nix {}; + + writers = callPackage ../build-support/writers/test.nix {}; + + testers = callPackage ../build-support/testers/test/default.nix {}; + + dhall = callPackage ./dhall { }; + + cue-validation = callPackage ./cue {}; + + coq = callPackage ./coq {}; + + dotnet = recurseIntoAttrs (callPackages ./dotnet { }); + + makeHardcodeGsettingsPatch = callPackage ./make-hardcode-gsettings-patch { }; + + makeWrapper = callPackage ./make-wrapper { }; + makeBinaryWrapper = callPackage ./make-binary-wrapper { + makeBinaryWrapper = pkgs.makeBinaryWrapper.override { + # Enable sanitizers in the tests only, to avoid the performance cost in regular usage. + # The sanitizers cause errors on aarch64-darwin, see https://github.com/NixOS/nixpkgs/pull/150079#issuecomment-994132734 + sanitizers = pkgs.lib.optionals (! (pkgs.stdenv.isDarwin && pkgs.stdenv.isAarch64)) + [ "undefined" "address" ]; + }; + }; + + pkgs-lib = recurseIntoAttrs (import ../pkgs-lib/tests { inherit pkgs; }); + + buildFHSEnv = recurseIntoAttrs (callPackages ./buildFHSEnv { }); + + nixpkgs-check-by-name = callPackage ./nixpkgs-check-by-name { }; + + auto-patchelf-hook = callPackage ./auto-patchelf-hook { }; +} diff --git a/nixpkgs/pkgs/test/dhall/buildDhallUrl/default.nix b/nixpkgs/pkgs/test/dhall/buildDhallUrl/default.nix new file mode 100644 index 000000000000..a75101a303d6 --- /dev/null +++ b/nixpkgs/pkgs/test/dhall/buildDhallUrl/default.nix @@ -0,0 +1,14 @@ +{ dhallPackages, lib }: + +# This file tests that dhallPackages.buildDhallUrl is able to successfully +# build a Nix Dhall package for a given remote Dhall import. +# +# TODO: It would be nice to extend this test to make sure that the resulting +# Nix Dhall package is has the expected contents. + +dhallPackages.buildDhallUrl { + url = "https://raw.githubusercontent.com/cdepillabout/example-dhall-nix/e6a675c72ecd4dd23d254a02aea8181fe875747f/mydhallfile.dhall"; + hash = "sha256-434x+QjHRzuprBdw0h6wmwB1Zj6yZqQb533me8XdO4c="; + dhallHash = "sha256-434x+QjHRzuprBdw0h6wmwB1Zj6yZqQb533me8XdO4c="; + source = true; +} diff --git a/nixpkgs/pkgs/test/dhall/default.nix b/nixpkgs/pkgs/test/dhall/default.nix new file mode 100644 index 000000000000..4c7eba6c9579 --- /dev/null +++ b/nixpkgs/pkgs/test/dhall/default.nix @@ -0,0 +1,6 @@ +{ lib, callPackage }: + +lib.recurseIntoAttrs { + buildDhallUrl = callPackage ./buildDhallUrl { }; + generateDhallDirectoryPackage = callPackage ./generateDhallDirectoryPackage { }; +} diff --git a/nixpkgs/pkgs/test/dhall/generateDhallDirectoryPackage/default.nix b/nixpkgs/pkgs/test/dhall/generateDhallDirectoryPackage/default.nix new file mode 100644 index 000000000000..4771e1629806 --- /dev/null +++ b/nixpkgs/pkgs/test/dhall/generateDhallDirectoryPackage/default.nix @@ -0,0 +1,17 @@ +{ dhallPackages, fetchFromGitHub }: + +# This file tests that dhallPackages.generateDhallDirectoryPackage works. +# +# TODO: It would be nice to extend this test to make sure that the resulting +# Nix file has the expected contents, but it might be tough to do that easily +# without IFD. + +dhallPackages.generateDhallDirectoryPackage { + src = fetchFromGitHub { + owner = "cdepillabout"; + repo = "example-dhall-nix"; + rev = "e6a675c72ecd4dd23d254a02aea8181fe875747f"; + sha256 = "sha256-c/EZq76s/+hmLkaeJWKqgh2KrHuJRYI6kWry0E0YQ6s="; + }; + file = "mydhallfile.dhall"; +} diff --git a/nixpkgs/pkgs/test/dotnet/default.nix b/nixpkgs/pkgs/test/dotnet/default.nix new file mode 100644 index 000000000000..7592b09d76e3 --- /dev/null +++ b/nixpkgs/pkgs/test/dotnet/default.nix @@ -0,0 +1,5 @@ +{ callPackage }: + +{ + project-references = callPackage ./project-references { }; +} diff --git a/nixpkgs/pkgs/test/dotnet/project-references/application/Application.cs b/nixpkgs/pkgs/test/dotnet/project-references/application/Application.cs new file mode 100644 index 000000000000..ae2c956a4df7 --- /dev/null +++ b/nixpkgs/pkgs/test/dotnet/project-references/application/Application.cs @@ -0,0 +1 @@ +ProjectReferencesTest.Library.Hello(); diff --git a/nixpkgs/pkgs/test/dotnet/project-references/application/Application.csproj b/nixpkgs/pkgs/test/dotnet/project-references/application/Application.csproj new file mode 100644 index 000000000000..6507637a5535 --- /dev/null +++ b/nixpkgs/pkgs/test/dotnet/project-references/application/Application.csproj @@ -0,0 +1,10 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <OutputType>exe</OutputType> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="../library/Library.csproj" /> + <PackageReference Include="ProjectReferencesTest.Library" Version="*" Condition=" '$(ContinuousIntegrationBuild)'=='true' " /> + </ItemGroup> +</Project> diff --git a/nixpkgs/pkgs/test/dotnet/project-references/default.nix b/nixpkgs/pkgs/test/dotnet/project-references/default.nix new file mode 100644 index 000000000000..f40b9196c209 --- /dev/null +++ b/nixpkgs/pkgs/test/dotnet/project-references/default.nix @@ -0,0 +1,38 @@ +# Tests the `projectReferences = [ ... ];` feature of buildDotnetModule. +# The `library` derivation exposes a .nupkg, which is then consumed by the `application` derivation. +# https://nixos.org/manual/nixpkgs/unstable/index.html#packaging-a-dotnet-application + +{ lib +, dotnet-sdk +, buildDotnetModule +, runCommand +}: + +let + nugetDeps = ./nuget-deps.nix; + + # Specify the TargetFramework via an environment variable so that we don't + # have to update the .csproj files when updating dotnet-sdk + TargetFramework = "net${lib.versions.majorMinor (lib.getVersion dotnet-sdk)}"; + + library = buildDotnetModule { + name = "project-references-test-library"; + src = ./library; + inherit nugetDeps TargetFramework; + + packNupkg = true; + }; + + application = buildDotnetModule { + name = "project-references-test-application"; + src = ./application; + inherit nugetDeps TargetFramework; + + projectReferences = [ library ]; + }; +in + +runCommand "project-references-test" { } '' + ${application}/bin/Application + touch $out +'' diff --git a/nixpkgs/pkgs/test/dotnet/project-references/library/Library.cs b/nixpkgs/pkgs/test/dotnet/project-references/library/Library.cs new file mode 100644 index 000000000000..a4af4a0fea2d --- /dev/null +++ b/nixpkgs/pkgs/test/dotnet/project-references/library/Library.cs @@ -0,0 +1,8 @@ +namespace ProjectReferencesTest; +public static class Library +{ + public static void Hello() + { + System.Console.WriteLine("Hello, World!"); + } +} diff --git a/nixpkgs/pkgs/test/dotnet/project-references/library/Library.csproj b/nixpkgs/pkgs/test/dotnet/project-references/library/Library.csproj new file mode 100644 index 000000000000..b9a71276d24a --- /dev/null +++ b/nixpkgs/pkgs/test/dotnet/project-references/library/Library.csproj @@ -0,0 +1,5 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <PackageId>ProjectReferencesTest.Library</PackageId> + </PropertyGroup> +</Project> diff --git a/nixpkgs/pkgs/test/dotnet/project-references/nuget-deps.nix b/nixpkgs/pkgs/test/dotnet/project-references/nuget-deps.nix new file mode 100644 index 000000000000..f3a17967e25c --- /dev/null +++ b/nixpkgs/pkgs/test/dotnet/project-references/nuget-deps.nix @@ -0,0 +1,5 @@ +# This file was automatically generated by passthru.fetch-deps. +# Please dont edit it manually, your changes might get overwritten! + +{ fetchNuGet }: [ +] diff --git a/nixpkgs/pkgs/test/haskell/cabalSdist/default.nix b/nixpkgs/pkgs/test/haskell/cabalSdist/default.nix new file mode 100644 index 000000000000..1031e51e4f14 --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/cabalSdist/default.nix @@ -0,0 +1,28 @@ +{ lib, haskellPackages, runCommand }: + +let + localRaw = haskellPackages.callPackage ./local/generated.nix {}; +in +lib.recurseIntoAttrs rec { + + helloFromCabalSdist = haskellPackages.buildFromCabalSdist haskellPackages.hello; + + # A more complicated example with a cabal hook. + hercules-ci-cnix-store = haskellPackages.buildFromCabalSdist haskellPackages.hercules-ci-cnix-store; + + localFromCabalSdist = haskellPackages.buildFromCabalSdist localRaw; + + assumptionLocalHasDirectReference = runCommand "localHasDirectReference" { + drvPath = builtins.unsafeDiscardOutputDependency localRaw.drvPath; + } '' + grep ${./local} $drvPath >/dev/null + touch $out + ''; + + localHasNoDirectReference = runCommand "localHasNoDirectReference" { + drvPath = builtins.unsafeDiscardOutputDependency localFromCabalSdist.drvPath; + } '' + grep -v ${./local} $drvPath >/dev/null + touch $out + ''; +} diff --git a/nixpkgs/pkgs/test/haskell/cabalSdist/local/CHANGELOG.md b/nixpkgs/pkgs/test/haskell/cabalSdist/local/CHANGELOG.md new file mode 100644 index 000000000000..53cc3ae43d8a --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/cabalSdist/local/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for local + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/nixpkgs/pkgs/test/haskell/cabalSdist/local/app/Main.hs b/nixpkgs/pkgs/test/haskell/cabalSdist/local/app/Main.hs new file mode 100644 index 000000000000..65ae4a05d5db --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/cabalSdist/local/app/Main.hs @@ -0,0 +1,4 @@ +module Main where + +main :: IO () +main = putStrLn "Hello, Haskell!" diff --git a/nixpkgs/pkgs/test/haskell/cabalSdist/local/generated.nix b/nixpkgs/pkgs/test/haskell/cabalSdist/local/generated.nix new file mode 100644 index 000000000000..bfa299962bcb --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/cabalSdist/local/generated.nix @@ -0,0 +1,12 @@ +# nix run ../../../../..#cabal2nix -- ./. +{ mkDerivation, base, lib }: +mkDerivation { + pname = "local"; + version = "0.1.0.0"; + src = ./.; + isLibrary = false; + isExecutable = true; + executableHaskellDepends = [ base ]; + description = "Nixpkgs test case"; + license = lib.licenses.mit; +} diff --git a/nixpkgs/pkgs/test/haskell/cabalSdist/local/local.cabal b/nixpkgs/pkgs/test/haskell/cabalSdist/local/local.cabal new file mode 100644 index 000000000000..1670aa3af631 --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/cabalSdist/local/local.cabal @@ -0,0 +1,13 @@ +cabal-version: 2.4 +name: local +version: 0.1.0.0 + +synopsis: Nixpkgs test case +license: MIT +extra-source-files: CHANGELOG.md + +executable local + main-is: Main.hs + build-depends: base + hs-source-dirs: app + default-language: Haskell2010 diff --git a/nixpkgs/pkgs/test/haskell/default.nix b/nixpkgs/pkgs/test/haskell/default.nix new file mode 100644 index 000000000000..8f1f21d65b51 --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/default.nix @@ -0,0 +1,10 @@ +{ lib, callPackage }: + +lib.recurseIntoAttrs { + shellFor = callPackage ./shellFor { }; + cabalSdist = callPackage ./cabalSdist { }; + documentationTarball = callPackage ./documentationTarball { }; + setBuildTarget = callPackage ./setBuildTarget { }; + incremental = callPackage ./incremental { }; + upstreamStackHpackVersion = callPackage ./upstreamStackHpackVersion { }; +} diff --git a/nixpkgs/pkgs/test/haskell/documentationTarball/default.nix b/nixpkgs/pkgs/test/haskell/documentationTarball/default.nix new file mode 100644 index 000000000000..337510281012 --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/documentationTarball/default.nix @@ -0,0 +1,21 @@ +{ pkgs, haskellPackages }: + +let + drv = haskellPackages.vector; + docs = pkgs.haskell.lib.compose.documentationTarball drv; + +in pkgs.runCommand "test haskell.lib.compose.documentationTarball" { + meta = { + inherit (docs.meta) platforms; + }; +} '' + tar xvzf "${docs}/${drv.name}-docs.tar.gz" + + # Check for Haddock html + find "${drv.name}-docs" | grep -q "Data-Vector.html" + + # Check for source html + find "${drv.name}-docs" | grep -q "src/Data.Vector.html" + + touch "$out" +'' diff --git a/nixpkgs/pkgs/test/haskell/incremental/default.nix b/nixpkgs/pkgs/test/haskell/incremental/default.nix new file mode 100644 index 000000000000..c7bd43b11af6 --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/incremental/default.nix @@ -0,0 +1,35 @@ +# Demonstration of incremental builds for Haskell. Useful for speeding up CI. +# +# See: https://www.haskellforall.com/2022/12/nixpkgs-support-for-incremental-haskell.html +# See: https://felixspringer.xyz/homepage/blog/incrementalHaskellBuildsWithNix + +{ haskell, haskellPackages, lib }: + +let + inherit (haskell.lib.compose) overrideCabal; + + # Incremental builds work with GHC >=9.4. + temporary = haskellPackages.temporary; + + # This will do a full build of `temporary`, while writing the intermediate build products + # (compiled modules, etc.) to the `intermediates` output. + temporary-full-build-with-incremental-output = overrideCabal (drv: { + doInstallIntermediates = true; + enableSeparateIntermediatesOutput = true; + }) temporary; + + # This will do an incremental build of `temporary` by copying the previously + # compiled modules and intermediate build products into the source tree + # before running the build. + # + # GHC will then naturally pick up and reuse these products, making this build + # complete much more quickly than the previous one. + temporary-incremental-build = overrideCabal (drv: { + previousIntermediates = temporary-full-build-with-incremental-output.intermediates; + }) temporary; +in + temporary-incremental-build.overrideAttrs (old: { + meta = { + maintainers = lib.teams.mercury.members; + }; + }) diff --git a/nixpkgs/pkgs/test/haskell/setBuildTarget/Bar.hs b/nixpkgs/pkgs/test/haskell/setBuildTarget/Bar.hs new file mode 100644 index 000000000000..010014082c7d --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/setBuildTarget/Bar.hs @@ -0,0 +1,4 @@ +module Main where + +main :: IO () +main = putStrLn "Hello, Bar!" diff --git a/nixpkgs/pkgs/test/haskell/setBuildTarget/Foo.hs b/nixpkgs/pkgs/test/haskell/setBuildTarget/Foo.hs new file mode 100644 index 000000000000..fec7bb11fe6c --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/setBuildTarget/Foo.hs @@ -0,0 +1,4 @@ +module Main where + +main :: IO () +main = putStrLn "Hello, Foo!" diff --git a/nixpkgs/pkgs/test/haskell/setBuildTarget/Setup.hs b/nixpkgs/pkgs/test/haskell/setBuildTarget/Setup.hs new file mode 100644 index 000000000000..9a994af677b0 --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/setBuildTarget/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/nixpkgs/pkgs/test/haskell/setBuildTarget/default.nix b/nixpkgs/pkgs/test/haskell/setBuildTarget/default.nix new file mode 100644 index 000000000000..f0c78c510449 --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/setBuildTarget/default.nix @@ -0,0 +1,43 @@ +{ pkgs, haskellPackages }: + +let + # This can be regenerated by running `cabal2nix .` in the current directory. + pkgDef = + { mkDerivation, base, lib }: + mkDerivation { + pname = "haskell-setBuildTarget"; + version = "0.1.0.0"; + src = ./.; + isLibrary = false; + isExecutable = true; + executableHaskellDepends = [ base ]; + license = lib.licenses.bsd3; + }; + + drv = haskellPackages.callPackage pkgDef {}; + + test = target: excluded: + let only = pkgs.haskell.lib.compose.setBuildTarget target drv; + in '' + if [[ ! -f "${only}/bin/${target}" ]]; then + echo "${target} was not built" + exit 1 + fi + + if [[ -f "${only}/bin/${excluded}" ]]; then + echo "${excluded} was built, when it should not have been" + exit 1 + fi + ''; + +in +pkgs.runCommand "test haskell.lib.compose.setBuildTarget" { + meta = { + inherit (drv.meta) platforms; + }; +} '' + ${test "foo" "bar"} + ${test "bar" "foo"} + touch "$out" +'' + diff --git a/nixpkgs/pkgs/test/haskell/setBuildTarget/haskell-setBuildTarget.cabal b/nixpkgs/pkgs/test/haskell/setBuildTarget/haskell-setBuildTarget.cabal new file mode 100644 index 000000000000..7395e139451c --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/setBuildTarget/haskell-setBuildTarget.cabal @@ -0,0 +1,16 @@ +cabal-version: >=1.10 +name: haskell-setBuildTarget +version: 0.1.0.0 +author: Isaac Shapira +maintainer: fresheyeball@protonmail.com +build-type: Simple + +executable foo + main-is: Foo.hs + build-depends: base + default-language: Haskell2010 + +executable bar + main-is: Bar.hs + build-depends: base + default-language: Haskell2010 diff --git a/nixpkgs/pkgs/test/haskell/shellFor/default.nix b/nixpkgs/pkgs/test/haskell/shellFor/default.nix new file mode 100644 index 000000000000..83daf079cc0f --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/shellFor/default.nix @@ -0,0 +1,57 @@ +{ lib, writeText, haskellPackages, cabal-install }: + +(haskellPackages.shellFor { + packages = p: [ p.constraints p.linear ]; + # WARNING: When updating this, make sure that the libraries passed to + # `extraDependencies` are not actually transitive dependencies of libraries in + # `packages` above. We explicitly want to test that it is possible to specify + # `extraDependencies` that are not in the closure of `packages`. + extraDependencies = p: { libraryHaskellDepends = [ p.conduit ]; }; + nativeBuildInputs = [ cabal-install ]; + phases = [ "unpackPhase" "buildPhase" "installPhase" ]; + unpackPhase = '' + sourceRoot=$(pwd)/scratch + mkdir -p "$sourceRoot" + cd "$sourceRoot" + tar -xf ${haskellPackages.constraints.src} + tar -xf ${haskellPackages.linear.src} + cp ${writeText "cabal.project" "packages: constraints* linear*"} cabal.project + ''; + buildPhase = '' + export HOME=$(mktemp -d) + mkdir -p $HOME/.cabal + touch $HOME/.cabal/config + + # Check that the extraDependencies.libraryHaskellDepends arg is correctly + # picked up. This uses ghci to interpret a small Haskell program that uses + # a package from extraDependencies. + ghci <<EOF + :set -XOverloadedStrings + :m + Conduit + runResourceT $ connect (yield "done") (sinkFile "outfile") + EOF + + if [[ "done" != "$(cat outfile)" ]]; then + echo "ERROR: extraDependencies appear not to be available in the environment" + exit 1 + fi + + # Check packages arg + cabal v2-build --offline --verbose constraints linear --ghc-options="-O0 -j$NIX_BUILD_CORES" + ''; + installPhase = '' + touch $out + ''; +}).overrideAttrs (oldAttrs: { + meta = + let + oldMeta = oldAttrs.meta or {}; + oldMaintainers = oldMeta.maintainers or []; + additionalMaintainers = with lib.maintainers; [ cdepillabout ]; + allMaintainers = oldMaintainers ++ additionalMaintainers; + in + oldMeta // { + maintainers = allMaintainers; + inherit (cabal-install.meta) platforms; + }; +}) diff --git a/nixpkgs/pkgs/test/haskell/upstreamStackHpackVersion/default.nix b/nixpkgs/pkgs/test/haskell/upstreamStackHpackVersion/default.nix new file mode 100644 index 000000000000..f3ddbcd3e016 --- /dev/null +++ b/nixpkgs/pkgs/test/haskell/upstreamStackHpackVersion/default.nix @@ -0,0 +1,152 @@ + +# This derivation confirms that the version of hpack used by stack in Nixpkgs +# is the exact same version as the upstream stack release. +# +# It is important to make sure the version of hpack used by stack in Nixpkgs +# matches with the version of hpack used by the upstream stack release. This +# is because hpack works slightly differently based on the version, and it can +# be frustrating to use hpack in a team setting when members are using different +# versions. See for more info: https://github.com/NixOS/nixpkgs/issues/223390 +# +# This test is written as a fixed-output derivation, because we need to access +# accesses the internet to download the upstream stack release. + +{ cacert, curl, lib, stack, stdenv }: + +let + # Find the hpack derivation that is a dependency of stack. Throw exception + # if hpack cannot be found. + hpack = + lib.findFirst + (v: v.pname or "" == "hpack") + (throw "could not find stack's hpack dependency") + stack.passthru.getCabalDeps.executableHaskellDepends; + + # This is a statically linked version of stack, so it should be usable within + # the Nixpkgs builder (at least on x86_64-linux). + stackDownloadUrl = + "https://github.com/commercialhaskell/stack/releases/download/v${stack.version}/stack-${stack.version}-linux-x86_64.tar.gz"; + + # This test code has been explicitly pulled out of the derivation below so + # that it can be hashed and added to the `name` of the derivation. This is + # so that this test derivation won't be cached if the body of the test is + # modified. + # + # WARNING: When modifying this script, make sure you don't introduce any + # paths to the Nix store within it. We only want this derivation to be re-run + # when the stack version (or the version of its hpack dependency) changes in + # Nixpkgs. + testScript = '' + curl=( + curl + --location + --max-redirs 20 + --retry 3 + --disable-epsv + --cookie-jar cookies + --user-agent "curl " + --insecure + ) + + # Fetch the statically-linked upstream Stack binary. + echo "Trying to download a statically linked stack binary from ${stackDownloadUrl} to ./stack.tar.gz ..." + "''${curl[@]}" "${stackDownloadUrl}" > ./stack.tar.gz + tar xf ./stack.tar.gz + + upstream_stack_version_output="$(./stack-${stack.version}-linux-x86_64/stack --version)" + echo "upstream \`stack --version\` output: $upstream_stack_version_output" + + nixpkgs_stack_version_output="$(stack --version)" + echo "nixpkgs \`stack --version\` output: $nixpkgs_stack_version_output" + + # Confirm that the upstream stack version is the same as the stack version + # in Nixpkgs. This check isn't strictly necessary, but it is a good sanity + # check. + + if [[ "$upstream_stack_version_output" =~ "Version "([0-9]+((\.[0-9]+)+)) ]]; then + upstream_stack_version="''${BASH_REMATCH[1]}" + + echo "parsed upstream stack version: $upstream_stack_version" + echo "stack version from nixpkgs: ${stack.version}" + + if [[ "${stack.version}" != "$upstream_stack_version" ]]; then + echo "ERROR: stack version in Nixpkgs (${stack.version}) does not match the upstream version for some reason: $upstream_stack_version" + exit 1 + fi + else + echo "ERROR: Upstream stack version cannot be found in --version output: $upstream_stack_version" + exit 1 + fi + + # Confirm that the hpack version used in the upstream stack release is the + # same as the hpack version used by the Nixpkgs stack binary. + + if [[ "$upstream_stack_version_output" =~ hpack-([0-9]+((\.[0-9]+)+)) ]]; then + upstream_hpack_version="''${BASH_REMATCH[1]}" + + echo "parsed upstream stack's hpack version: $upstream_hpack_version" + echo "Nixpkgs stack's hpack version: ${hpack.version}" + + if [[ "${hpack.version}" != "$upstream_hpack_version" ]]; then + echo "ERROR: stack's hpack version in Nixpkgs (${hpack.version}) does not match the upstream stack's hpack version: $upstream_hpack_version" + echo "The stack derivation in Nixpkgs needs to be fixed up so that it depends on hpack-$upstream_hpack_version, instead of ${hpack.name}" + exit 1 + fi + else + echo "ERROR: Upstream stack's hpack version cannot be found in --version output: $upstream_hpack_version" + exit 1 + fi + + # Output a string with a known hash. + echo "success" > $out + ''; + + testScriptHash = builtins.hashString "sha256" testScript; +in + +stdenv.mkDerivation { + + # This name is very important. + # + # The idea here is that want this derivation to be re-run everytime the + # version of stack (or the version of its hpack dependency) changes in + # Nixpkgs. We also want to re-run this derivation whenever the test script + # is changed. + # + # Nix/Hydra will re-run derivations if their name changes (even if they are a + # FOD and they have the same hash). + # + # The name of this derivation contains the stack version string, the hpack + # version string, and a hash of the test script. So Nix will know to + # re-run this version when (and only when) one of those values change. + name = "upstream-stack-hpack-version-test-${stack.name}-${hpack.name}-${testScriptHash}"; + + # This is the sha256 hash for the string "success", which is output upon this + # test succeeding. + outputHash = "sha256-gbK9TqmMjbZlVPvI12N6GmmhMPMx/rcyt1yqtMSGj9U="; + outputHashMode = "flat"; + outputHashAlgo = "sha256"; + + nativeBuildInputs = [ curl stack ]; + + impureEnvVars = lib.fetchers.proxyImpureEnvVars; + + buildCommand = '' + # Make sure curl can access HTTPS sites, like GitHub. + # + # Note that we absolutely don't want the Nix store path of the cacert + # derivation in the testScript, because we don't want to rebuild this + # derivation when only the cacert derivation changes. + export SSL_CERT_FILE="${cacert}/etc/ssl/certs/ca-bundle.crt" + '' + testScript; + + meta = with lib; { + description = "Test that the stack in Nixpkgs uses the same version of Hpack as the upstream stack release"; + maintainers = with maintainers; [ cdepillabout ]; + + # This derivation internally runs a statically-linked version of stack from + # upstream. This statically-linked version of stack is only available for + # x86_64-linux, so this test can only be run on x86_64-linux. + platforms = [ "x86_64-linux" ]; + }; +} diff --git a/nixpkgs/pkgs/test/hooks/default.nix b/nixpkgs/pkgs/test/hooks/default.nix new file mode 100644 index 000000000000..aabf939b6865 --- /dev/null +++ b/nixpkgs/pkgs/test/hooks/default.nix @@ -0,0 +1,8 @@ +# To run these tests: +# nix-build -A tests.hooks + +{ stdenv, tests, lib }: + +{ + default-stdenv-hooks = lib.recurseIntoAttrs tests.stdenv.hooks; +} diff --git a/nixpkgs/pkgs/test/install-shell-files/default.nix b/nixpkgs/pkgs/test/install-shell-files/default.nix new file mode 100644 index 000000000000..aef5acc1de6b --- /dev/null +++ b/nixpkgs/pkgs/test/install-shell-files/default.nix @@ -0,0 +1,125 @@ +{ lib, runCommandLocal, recurseIntoAttrs, installShellFiles }: + +let + runTest = name: env: buildCommand: + runCommandLocal "install-shell-files--${name}" ({ + nativeBuildInputs = [ installShellFiles ]; + meta.platforms = lib.platforms.all; + } // env) buildCommand; +in + +recurseIntoAttrs { + # installManPage + + install-manpage = runTest "install-manpage" {} '' + mkdir -p doc + echo foo > doc/foo.1 + echo bar > doc/bar.2.gz + echo baz > doc/baz.3 + + installManPage doc/* + + cmp doc/foo.1 $out/share/man/man1/foo.1 + cmp doc/bar.2.gz $out/share/man/man2/bar.2.gz + cmp doc/baz.3 $out/share/man/man3/baz.3 + ''; + install-manpage-outputs = runTest "install-manpage-outputs" { + outputs = [ "out" "man" "devman" ]; + } '' + mkdir -p doc + echo foo > doc/foo.1 + echo bar > doc/bar.3 + + installManPage doc/* + + # assert they didn't go into $out + [[ ! -f $out/share/man/man1/foo.1 && ! -f $out/share/man/man3/bar.3 ]] + + # foo.1 alone went into man + cmp doc/foo.1 ''${!outputMan:?}/share/man/man1/foo.1 + [[ ! -f ''${!outputMan:?}/share/man/man3/bar.3 ]] + + # bar.3 alone went into devman + cmp doc/bar.3 ''${!outputDevman:?}/share/man/man3/bar.3 + [[ ! -f ''${!outputDevman:?}/share/man/man1/foo.1 ]] + + touch $out + ''; + + # installShellCompletion + + install-completion = runTest "install-completion" {} '' + echo foo > foo + echo bar > bar + echo baz > baz + echo qux > qux.zsh + echo quux > quux + + installShellCompletion --bash foo bar --zsh baz qux.zsh --fish quux + + cmp foo $out/share/bash-completion/completions/foo + cmp bar $out/share/bash-completion/completions/bar + cmp baz $out/share/zsh/site-functions/_baz + cmp qux.zsh $out/share/zsh/site-functions/_qux + cmp quux $out/share/fish/vendor_completions.d/quux + ''; + install-completion-output = runTest "install-completion-output" { + outputs = [ "out" "bin" ]; + } '' + echo foo > foo + + installShellCompletion --bash foo + + # assert it didn't go into $out + [[ ! -f $out/share/bash-completion/completions/foo ]] + + cmp foo ''${!outputBin:?}/share/bash-completion/completions/foo + + touch $out + ''; + install-completion-name = runTest "install-completion-name" {} '' + echo foo > foo + echo bar > bar + echo baz > baz + + installShellCompletion --bash --name foobar.bash foo --zsh --name _foobar bar --fish baz + + cmp foo $out/share/bash-completion/completions/foobar.bash + cmp bar $out/share/zsh/site-functions/_foobar + cmp baz $out/share/fish/vendor_completions.d/baz + ''; + install-completion-inference = runTest "install-completion-inference" {} '' + echo foo > foo.bash + echo bar > bar.zsh + echo baz > baz.fish + + installShellCompletion foo.bash bar.zsh baz.fish + + cmp foo.bash $out/share/bash-completion/completions/foo.bash + cmp bar.zsh $out/share/zsh/site-functions/_bar + cmp baz.fish $out/share/fish/vendor_completions.d/baz.fish + ''; + install-completion-cmd = runTest "install-completion-cmd" {} '' + echo foo > foo.bash + echo bar > bar.zsh + echo baz > baz.fish + echo qux > qux.fish + + installShellCompletion --cmd foobar --bash foo.bash --zsh bar.zsh --fish baz.fish --name qux qux.fish + + cmp foo.bash $out/share/bash-completion/completions/foobar.bash + cmp bar.zsh $out/share/zsh/site-functions/_foobar + cmp baz.fish $out/share/fish/vendor_completions.d/foobar.fish + cmp qux.fish $out/share/fish/vendor_completions.d/qux + ''; + install-completion-fifo = runTest "install-completion-fifo" {} '' + installShellCompletion \ + --bash --name foo.bash <(echo foo) \ + --zsh --name _foo <(echo bar) \ + --fish --name foo.fish <(echo baz) + + [[ $(<$out/share/bash-completion/completions/foo.bash) == foo ]] || { echo "foo.bash comparison failed"; exit 1; } + [[ $(<$out/share/zsh/site-functions/_foo) == bar ]] || { echo "_foo comparison failed"; exit 1; } + [[ $(<$out/share/fish/vendor_completions.d/foo.fish) == baz ]] || { echo "foo.fish comparison failed"; exit 1; } + ''; +} diff --git a/nixpkgs/pkgs/test/kernel.nix b/nixpkgs/pkgs/test/kernel.nix new file mode 100644 index 000000000000..2ccd188b1edb --- /dev/null +++ b/nixpkgs/pkgs/test/kernel.nix @@ -0,0 +1,73 @@ +# to run these tests: +# nix-instantiate --eval --strict . -A tests.kernel-config +# +# make sure to use NON EXISTING kernel settings else they may conflict with +# common-config.nix +{ lib, pkgs }: + +with lib; +with kernel; + +let + lts_kernel = pkgs.linuxPackages.kernel; + + # to see the result once the module transformed the lose structured config + getConfig = structuredConfig: + (lts_kernel.override { + structuredExtraConfig = structuredConfig; + }).configfile.structuredConfig; + + mandatoryVsOptionalConfig = mkMerge [ + { NIXOS_FAKE_USB_DEBUG = yes;} + { NIXOS_FAKE_USB_DEBUG = option yes; } + ]; + + freeformConfig = mkMerge [ + { NIXOS_FAKE_MMC_BLOCK_MINORS = freeform "32"; } # same as default, won't trigger any error + { NIXOS_FAKE_MMC_BLOCK_MINORS = freeform "64"; } # will trigger an error but the message is not great: + ]; + + mkDefaultWorksConfig = mkMerge [ + { "NIXOS_TEST_BOOLEAN" = yes; } + { "NIXOS_TEST_BOOLEAN" = lib.mkDefault no; } + ]; + + allOptionalRemainOptional = mkMerge [ + { NIXOS_FAKE_USB_DEBUG = option yes;} + { NIXOS_FAKE_USB_DEBUG = option yes;} + ]; + +in +runTests { + testEasy = { + expr = (getConfig { NIXOS_FAKE_USB_DEBUG = yes;}).NIXOS_FAKE_USB_DEBUG; + expected = { tristate = "y"; optional = false; freeform = null; }; + }; + + # mandatory flag should win over optional + testMandatoryCheck = { + expr = (getConfig mandatoryVsOptionalConfig).NIXOS_FAKE_USB_DEBUG.optional; + expected = false; + }; + + testYesWinsOverNo = { + expr = (getConfig mkDefaultWorksConfig)."NIXOS_TEST_BOOLEAN".tristate; + expected = "y"; + }; + + testAllOptionalRemainOptional = { + expr = (getConfig allOptionalRemainOptional)."NIXOS_FAKE_USB_DEBUG".optional; + expected = true; + }; + + # check that freeform options are unique + # Should trigger + # > The option `settings.NIXOS_FAKE_MMC_BLOCK_MINORS.freeform' has conflicting definitions, in `<unknown-file>' and `<unknown-file>' + testTreeform = let + res = builtins.tryEval ( (getConfig freeformConfig).NIXOS_FAKE_MMC_BLOCK_MINORS.freeform); + in { + expr = res.success; + expected = false; + }; + +} diff --git a/nixpkgs/pkgs/test/ld-library-path/default.nix b/nixpkgs/pkgs/test/ld-library-path/default.nix new file mode 100644 index 000000000000..74c52cef2532 --- /dev/null +++ b/nixpkgs/pkgs/test/ld-library-path/default.nix @@ -0,0 +1,88 @@ +{ lib, stdenv }: + +# This tests that libraries listed in LD_LIBRARY_PATH take precedence over those listed in RPATH. + +let + # A simple test library: libgreeting.so which exports a single function getGreeting() returning the good old hello greeting. + libgreeting = stdenv.mkDerivation { + name = "libgreeting"; + + code = '' + const char* getGreeting() { return "Hello, world!"; } + ''; + + unpackPhase = '' + echo "$code" > libgreeting.c + ''; + + installPhase = '' + mkdir -p $out/lib + $CC -c -fpic libgreeting.c + $CC -shared libgreeting.o -o $out/lib/libgreeting.so + ''; + }; + + # A variant of libgreeting.so that returns a different message. + libgoodbye = libgreeting.overrideAttrs (_: { + name = "libgoodbye"; + code = '' + const char* getGreeting() { return "Goodbye, world!"; } + ''; + }); + + # A simple consumer of libgreeting.so that just prints the greeting to stdout. + testProgram = stdenv.mkDerivation { + name = "greeting-test"; + + buildInputs = [ libgreeting ]; + + code = '' + #include <stdio.h> + + extern const char* getGreeting(void); + + int main() { + puts(getGreeting()); + } + ''; + + unpackPhase = '' + echo "$code" > greeting-test.c + ''; + + installPhase = '' + mkdir -p $out/bin + $CC -c greeting-test.c + $CC greeting-test.o -lgreeting -o $out/bin/greeting-test + + # Now test the installed binaries right after compiling them. In particular, + # don't do this in installCheckPhase because fixupPhase has been run by then! + ( + export PATH=$out/bin + set -x + + # Verify that our unmodified binary works as expected. + [ "$(greeting-test)" = "Hello, world!" ] + + # And finally, test that a library in LD_LIBRARY_PATH takes precedence over the linked-in library. + [ "$(LD_LIBRARY_PATH=${libgoodbye}/lib greeting-test)" = "Goodbye, world!" ] + ) + ''; + + }; +in stdenv.mkDerivation { + name = "test-LD_LIBRARY_PATH"; + nativeBuildInputs = [ testProgram ]; + + buildCommand = '' + # And for good measure, repeat the tests again from a separate derivation, + # as fixupPhase done by the stdenv can (and has!) affect the result. + + [ "$(greeting-test)" = "Hello, world!" ] + [ "$(LD_LIBRARY_PATH=${libgoodbye}/lib greeting-test)" = "Goodbye, world!" ] + + touch $out + ''; + + meta.platforms = lib.platforms.linux; +} diff --git a/nixpkgs/pkgs/test/macos-sierra-shared/default.nix b/nixpkgs/pkgs/test/macos-sierra-shared/default.nix new file mode 100644 index 000000000000..810d5d97829b --- /dev/null +++ b/nixpkgs/pkgs/test/macos-sierra-shared/default.nix @@ -0,0 +1,90 @@ +{ lib, clangStdenv, clang-sierraHack-stdenv, stdenvNoCC }: + +let + makeBigExe = stdenv: prefix: rec { + + count = 320; + + sillyLibs = lib.genList (i: stdenv.mkDerivation rec { + name = "${prefix}-fluff-${toString i}"; + unpackPhase = '' + src=$PWD + cat << 'EOF' > ${name}.c + unsigned int asdf_${toString i}(void) { + return ${toString i}; + } + EOF + ''; + buildPhase = '' + $CC -std=c99 -shared ${name}.c -o lib${name}.dylib -Wl,-install_name,$out/lib/lib${name}.dylib + ''; + installPhase = '' + mkdir -p "$out/lib" + mv lib${name}.dylib "$out/lib" + ''; + meta.platforms = lib.platforms.darwin; + }) count; + + finalExe = stdenv.mkDerivation { + name = "${prefix}-final-asdf"; + unpackPhase = '' + src=$PWD + cat << 'EOF' > main.cxx + + #include <cstdlib> + #include <iostream> + + ${toString (lib.genList (i: "extern \"C\" unsigned int asdf_${toString i}(void); ") count)} + + unsigned int (*funs[])(void) = { + ${toString (lib.genList (i: "asdf_${toString i},") count)} + }; + + int main(int argc, char **argv) { + bool ret; + unsigned int i = 0; + for (auto f : funs) { + if (f() != i++) { + std::cerr << "Failed to get expected response from function #" << i << std::endl; + return EXIT_FAILURE; + } + } + return EXIT_SUCCESS; + } + EOF + ''; + buildPhase = '' + $CXX -std=c++11 main.cxx ${toString (map (x: "-l${x.name}") sillyLibs)} -o ${prefix}-asdf + ''; + buildInputs = sillyLibs; + installPhase = '' + mkdir -p "$out/bin" + mv ${prefix}-asdf "$out/bin" + ''; + meta.platforms = lib.platforms.darwin; + }; + + }; + + good = makeBigExe clang-sierraHack-stdenv "good"; + + bad = makeBigExe clangStdenv "bad"; + +in stdenvNoCC.mkDerivation { + name = "macos-sierra-shared-test"; + buildInputs = [ good.finalExe bad.finalExe ]; + # TODO(@Ericson2314): Be impure or require exact MacOS version of builder? + buildCommand = '' + if bad-asdf &> /dev/null + then echo "WARNING: bad-asdf did not fail, not running on sierra?" >&2 + else echo "bad-asdf should fail on sierra, OK" >&2 + fi + + # Must succeed on all supported MacOS versions + good-asdf + echo "good-asdf should succeed on sierra, OK" + + touch $out + ''; + meta.platforms = lib.platforms.darwin; +} diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/add-flags.c b/nixpkgs/pkgs/test/make-binary-wrapper/add-flags.c new file mode 100644 index 000000000000..d998a5f6f983 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/add-flags.c @@ -0,0 +1,25 @@ +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> + +int main(int argc, char **argv) { + char **argv_tmp = calloc(6 + argc + 2 + 1, sizeof(*argv_tmp)); + assert(argv_tmp != NULL); + argv_tmp[0] = argv[0]; + argv_tmp[1] = "-x"; + argv_tmp[2] = "-y"; + argv_tmp[3] = "-z"; + argv_tmp[4] = "-abc"; + argv_tmp[5] = "-g"; + argv_tmp[6] = "*.txt"; + for (int i = 1; i < argc; ++i) { + argv_tmp[6 + i] = argv[i]; + } + argv_tmp[6 + argc + 0] = "-foo"; + argv_tmp[6 + argc + 1] = "-bar"; + argv_tmp[6 + argc + 2] = NULL; + argv = argv_tmp; + + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/add-flags.cmdline b/nixpkgs/pkgs/test/make-binary-wrapper/add-flags.cmdline new file mode 100644 index 000000000000..1ca964ab4e7a --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/add-flags.cmdline @@ -0,0 +1,4 @@ + --append-flags "-foo -bar" \ + --add-flags "-x -y -z" \ + --add-flags -abc \ + --add-flags "-g *.txt" diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/add-flags.env b/nixpkgs/pkgs/test/make-binary-wrapper/add-flags.env new file mode 100644 index 000000000000..f0641ef36f7c --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/add-flags.env @@ -0,0 +1,10 @@ +CWD=SUBST_CWD +SUBST_ARGV0 +-x +-y +-z +-abc +-g +*.txt +-foo +-bar diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/argv0.c b/nixpkgs/pkgs/test/make-binary-wrapper/argv0.c new file mode 100644 index 000000000000..70c36889dc89 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/argv0.c @@ -0,0 +1,7 @@ +#include <unistd.h> +#include <stdlib.h> + +int main(int argc, char **argv) { + argv[0] = "alternative-name"; + return execv("/send/me/flags", argv); +} diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/argv0.cmdline b/nixpkgs/pkgs/test/make-binary-wrapper/argv0.cmdline new file mode 100644 index 000000000000..1cadce8312a4 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/argv0.cmdline @@ -0,0 +1 @@ + --argv0 alternative-name diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/argv0.env b/nixpkgs/pkgs/test/make-binary-wrapper/argv0.env new file mode 100644 index 000000000000..04c13d32ee6d --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/argv0.env @@ -0,0 +1,2 @@ +CWD=SUBST_CWD +alternative-name diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/basic.c b/nixpkgs/pkgs/test/make-binary-wrapper/basic.c new file mode 100644 index 000000000000..1c1266181377 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/basic.c @@ -0,0 +1,7 @@ +#include <unistd.h> +#include <stdlib.h> + +int main(int argc, char **argv) { + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/basic.cmdline b/nixpkgs/pkgs/test/make-binary-wrapper/basic.cmdline new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/basic.cmdline diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/basic.env b/nixpkgs/pkgs/test/make-binary-wrapper/basic.env new file mode 100644 index 000000000000..b0da31959447 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/basic.env @@ -0,0 +1,2 @@ +CWD=SUBST_CWD +SUBST_ARGV0 diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/chdir.c b/nixpkgs/pkgs/test/make-binary-wrapper/chdir.c new file mode 100644 index 000000000000..9e0b7e2c7f52 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/chdir.c @@ -0,0 +1,11 @@ +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + +#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0) + +int main(int argc, char **argv) { + assert_success(chdir("./tmp/foo")); + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/chdir.cmdline b/nixpkgs/pkgs/test/make-binary-wrapper/chdir.cmdline new file mode 100644 index 000000000000..d6ab081e8d35 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/chdir.cmdline @@ -0,0 +1 @@ + --chdir ./tmp/foo diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/chdir.env b/nixpkgs/pkgs/test/make-binary-wrapper/chdir.env new file mode 100644 index 000000000000..ea1c61054e50 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/chdir.env @@ -0,0 +1,2 @@ +CWD=SUBST_CWD/tmp/foo +SUBST_ARGV0 diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/combination.c b/nixpkgs/pkgs/test/make-binary-wrapper/combination.c new file mode 100644 index 000000000000..8ce8a4722a0b --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/combination.c @@ -0,0 +1,53 @@ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> + +#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0) + +void set_env_prefix(char *env, char *sep, char *prefix) { + char *existing = getenv(env); + if (existing) { + char *val; + assert_success(asprintf(&val, "%s%s%s", prefix, sep, existing)); + assert_success(setenv(env, val, 1)); + free(val); + } else { + assert_success(setenv(env, prefix, 1)); + } +} + +void set_env_suffix(char *env, char *sep, char *suffix) { + char *existing = getenv(env); + if (existing) { + char *val; + assert_success(asprintf(&val, "%s%s%s", existing, sep, suffix)); + assert_success(setenv(env, val, 1)); + free(val); + } else { + assert_success(setenv(env, suffix, 1)); + } +} + +int main(int argc, char **argv) { + assert_success(setenv("MESSAGE", "HELLO", 0)); + set_env_prefix("PATH", ":", "/usr/bin/"); + set_env_suffix("PATH", ":", "/usr/local/bin/"); + putenv("MESSAGE2=WORLD"); + + char **argv_tmp = calloc(3 + argc + 0 + 1, sizeof(*argv_tmp)); + assert(argv_tmp != NULL); + argv_tmp[0] = argv[0]; + argv_tmp[1] = "-x"; + argv_tmp[2] = "-y"; + argv_tmp[3] = "-z"; + for (int i = 1; i < argc; ++i) { + argv_tmp[3 + i] = argv[i]; + } + argv_tmp[3 + argc + 0] = NULL; + argv = argv_tmp; + + argv[0] = "my-wrapper"; + return execv("/send/me/flags", argv); +} diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/combination.cmdline b/nixpkgs/pkgs/test/make-binary-wrapper/combination.cmdline new file mode 100644 index 000000000000..fb3861235c8b --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/combination.cmdline @@ -0,0 +1,6 @@ + --argv0 my-wrapper \ + --set-default MESSAGE HELLO \ + --prefix PATH : /usr/bin/ \ + --suffix PATH : /usr/local/bin/ \ + --add-flags "-x -y -z" \ + --set MESSAGE2 WORLD diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/combination.env b/nixpkgs/pkgs/test/make-binary-wrapper/combination.env new file mode 100644 index 000000000000..886420c01d1e --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/combination.env @@ -0,0 +1,8 @@ +MESSAGE=HELLO +PATH=/usr/bin/:/usr/local/bin/ +MESSAGE2=WORLD +CWD=SUBST_CWD +my-wrapper +-x +-y +-z diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/cross.nix b/nixpkgs/pkgs/test/make-binary-wrapper/cross.nix new file mode 100644 index 000000000000..64912364b863 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/cross.nix @@ -0,0 +1,28 @@ +{ stdenv +, runCommand +, makeBinaryWrapper +, binutils +, lib +, expectedArch ? stdenv.hostPlatform.parsed.cpu.name +}: + + +runCommand "make-binary-wrapper-test-cross" { + nativeBuildInputs = [ + makeBinaryWrapper + binutils + ]; + # For x86_64-linux the machine field is + # Advanced Micro Devices X86-64 + # and uses a dash instead of a underscore unlike x86_64-linux in hostPlatform.parsed.cpu.name + expectedArch = lib.replaceStrings ["_"] ["-"] expectedArch; +} '' + touch prog + chmod +x prog + makeWrapper prog $out + read -r _ arch < <($READELF --file-header $out | grep Machine:) + if [[ ''${arch,,} != *"''${expectedArch,,}"* ]]; then + echo "expected $expectedArch, got $arch" + exit 1 + fi +'' diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/default.nix b/nixpkgs/pkgs/test/make-binary-wrapper/default.nix new file mode 100644 index 000000000000..4c6fffd100a7 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/default.nix @@ -0,0 +1,61 @@ +{ lib +, stdenv +, pkgsCross +, makeBinaryWrapper +, writeText +, runCommand +, runCommandCC +}: + +let + env = { nativeBuildInputs = [ makeBinaryWrapper ]; }; + envCheck = runCommandCC "envcheck" env '' + cc -Wall -Werror -Wpedantic -o $out ${./envcheck.c} + ''; + makeGoldenTest = testname: runCommand "make-binary-wrapper-test-${testname}" env '' + mkdir -p tmp/foo # for the chdir test + + params=$(<"${./.}/${testname}.cmdline") + eval "makeCWrapper /send/me/flags $params" > wrapper.c + + diff wrapper.c "${./.}/${testname}.c" + + if [ -f "${./.}/${testname}.env" ]; then + eval "makeWrapper ${envCheck} wrapped $params" + env -i ./wrapped > env.txt + sed "s#SUBST_ARGV0#${envCheck}#;s#SUBST_CWD#$PWD#" \ + "${./.}/${testname}.env" > golden-env.txt + if ! diff env.txt golden-env.txt; then + echo "env/argv should be:" + cat golden-env.txt + echo "env/argv output is:" + cat env.txt + exit 1 + fi + else + # without a golden env, we expect the wrapper compilation to fail + ! eval "makeWrapper ${envCheck} wrapped $params" &> error.txt + fi + + cp wrapper.c $out + ''; + tests = lib.genAttrs [ + "add-flags" + "argv0" + "basic" + "chdir" + "combination" + "env" + "inherit-argv0" + "invalid-env" + "overlength-strings" + "prefix" + "suffix" + ] makeGoldenTest // lib.optionalAttrs (! stdenv.isDarwin) { + cross = pkgsCross.${if stdenv.buildPlatform.isAarch64 then "gnu64" else "aarch64-multiplatform"}.callPackage ./cross.nix { }; + }; +in + +writeText "make-binary-wrapper-tests" '' + ${lib.concatStringsSep "\n" (builtins.attrValues tests)} +'' // tests diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/env.c b/nixpkgs/pkgs/test/make-binary-wrapper/env.c new file mode 100644 index 000000000000..7e0422dee3bd --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/env.c @@ -0,0 +1,14 @@ +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + +#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0) + +int main(int argc, char **argv) { + putenv("PART1=HELLO"); + assert_success(setenv("PART2", "WORLD", 0)); + assert_success(unsetenv("SOME_OTHER_VARIABLE")); + putenv("PART3=\"!!\n\""); + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/env.cmdline b/nixpkgs/pkgs/test/make-binary-wrapper/env.cmdline new file mode 100644 index 000000000000..3c89f33e2dce --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/env.cmdline @@ -0,0 +1,4 @@ + --set PART1 HELLO \ + --set-default PART2 WORLD \ + --unset SOME_OTHER_VARIABLE \ + --set PART3 $'"!!\n"' diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/env.env b/nixpkgs/pkgs/test/make-binary-wrapper/env.env new file mode 100644 index 000000000000..c7661e165e09 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/env.env @@ -0,0 +1,6 @@ +PART1=HELLO +PART2=WORLD +PART3="!! +" +CWD=SUBST_CWD +SUBST_ARGV0 diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/envcheck.c b/nixpkgs/pkgs/test/make-binary-wrapper/envcheck.c new file mode 100644 index 000000000000..848fbdaa80f2 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/envcheck.c @@ -0,0 +1,22 @@ +#include <limits.h> +#include <stdio.h> +#include <unistd.h> + +int main(int argc, char **argv, char **envp) { + for (char **env = envp; *env != 0; ++env) { + puts(*env); + } + + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd))) { + printf("CWD=%s\n", cwd); + } else { + perror("getcwd() error"); + return 1; + } + + for (int i=0; i < argc; ++i) { + puts(argv[i]); + } + return 0; +} diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/inherit-argv0.c b/nixpkgs/pkgs/test/make-binary-wrapper/inherit-argv0.c new file mode 100644 index 000000000000..e1c2bc926aa7 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/inherit-argv0.c @@ -0,0 +1,6 @@ +#include <unistd.h> +#include <stdlib.h> + +int main(int argc, char **argv) { + return execv("/send/me/flags", argv); +} diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/inherit-argv0.cmdline b/nixpkgs/pkgs/test/make-binary-wrapper/inherit-argv0.cmdline new file mode 100644 index 000000000000..088076799835 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/inherit-argv0.cmdline @@ -0,0 +1 @@ + --inherit-argv0 diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/inherit-argv0.env b/nixpkgs/pkgs/test/make-binary-wrapper/inherit-argv0.env new file mode 100644 index 000000000000..c46ca95eefbc --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/inherit-argv0.env @@ -0,0 +1,2 @@ +CWD=SUBST_CWD +./wrapped diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/invalid-env.c b/nixpkgs/pkgs/test/make-binary-wrapper/invalid-env.c new file mode 100644 index 000000000000..4dfd36fb68a0 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/invalid-env.c @@ -0,0 +1,14 @@ +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + +#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0) + +int main(int argc, char **argv) { + putenv("==TEST1"); + #error Illegal environment variable name `=` (cannot contain `=`) + assert_success(setenv("", "TEST2", 0)); + #error Environment variable name can't be empty. + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/invalid-env.cmdline b/nixpkgs/pkgs/test/make-binary-wrapper/invalid-env.cmdline new file mode 100644 index 000000000000..a03b001e754e --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/invalid-env.cmdline @@ -0,0 +1,2 @@ + --set "=" "TEST1" \ + --set-default "" "TEST2" diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/overlength-strings.c b/nixpkgs/pkgs/test/make-binary-wrapper/overlength-strings.c new file mode 100644 index 000000000000..579705d33e94 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/overlength-strings.c @@ -0,0 +1,25 @@ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> + +#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0) + +void set_env_prefix(char *env, char *sep, char *prefix) { + char *existing = getenv(env); + if (existing) { + char *val; + assert_success(asprintf(&val, "%s%s%s", prefix, sep, existing)); + assert_success(setenv(env, val, 1)); + free(val); + } else { + assert_success(setenv(env, prefix, 1)); + } +} + +int main(int argc, char **argv) { + set_env_prefix("PATH", ":", "/nix/store/00000000000000000000000000000000-loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong"); + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/overlength-strings.cmdline b/nixpkgs/pkgs/test/make-binary-wrapper/overlength-strings.cmdline new file mode 100644 index 000000000000..686abbb1cdb9 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/overlength-strings.cmdline @@ -0,0 +1 @@ + --prefix PATH : /nix/store/00000000000000000000000000000000-loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/overlength-strings.env b/nixpkgs/pkgs/test/make-binary-wrapper/overlength-strings.env new file mode 100644 index 000000000000..83a02f5f8343 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/overlength-strings.env @@ -0,0 +1,3 @@ +PATH=/nix/store/00000000000000000000000000000000-loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong +CWD=SUBST_CWD +SUBST_ARGV0 diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/prefix.c b/nixpkgs/pkgs/test/make-binary-wrapper/prefix.c new file mode 100644 index 000000000000..ea8fbdc64a84 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/prefix.c @@ -0,0 +1,26 @@ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> + +#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0) + +void set_env_prefix(char *env, char *sep, char *prefix) { + char *existing = getenv(env); + if (existing) { + char *val; + assert_success(asprintf(&val, "%s%s%s", prefix, sep, existing)); + assert_success(setenv(env, val, 1)); + free(val); + } else { + assert_success(setenv(env, prefix, 1)); + } +} + +int main(int argc, char **argv) { + set_env_prefix("PATH", ":", "/usr/bin/"); + set_env_prefix("PATH", ":", "/usr/local/bin/"); + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/prefix.cmdline b/nixpkgs/pkgs/test/make-binary-wrapper/prefix.cmdline new file mode 100644 index 000000000000..99cebf9503f4 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/prefix.cmdline @@ -0,0 +1,2 @@ + --prefix PATH : /usr/bin/ \ + --prefix PATH : /usr/local/bin/ diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/prefix.env b/nixpkgs/pkgs/test/make-binary-wrapper/prefix.env new file mode 100644 index 000000000000..033676457c57 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/prefix.env @@ -0,0 +1,3 @@ +PATH=/usr/local/bin/:/usr/bin/ +CWD=SUBST_CWD +SUBST_ARGV0 diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/suffix.c b/nixpkgs/pkgs/test/make-binary-wrapper/suffix.c new file mode 100644 index 000000000000..d33f86c070ca --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/suffix.c @@ -0,0 +1,26 @@ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> + +#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0) + +void set_env_suffix(char *env, char *sep, char *suffix) { + char *existing = getenv(env); + if (existing) { + char *val; + assert_success(asprintf(&val, "%s%s%s", existing, sep, suffix)); + assert_success(setenv(env, val, 1)); + free(val); + } else { + assert_success(setenv(env, suffix, 1)); + } +} + +int main(int argc, char **argv) { + set_env_suffix("PATH", ":", "/usr/bin/"); + set_env_suffix("PATH", ":", "/usr/local/bin/"); + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/suffix.cmdline b/nixpkgs/pkgs/test/make-binary-wrapper/suffix.cmdline new file mode 100644 index 000000000000..95d291f3c169 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/suffix.cmdline @@ -0,0 +1,2 @@ + --suffix PATH : /usr/bin/ \ + --suffix PATH : /usr/local/bin/ diff --git a/nixpkgs/pkgs/test/make-binary-wrapper/suffix.env b/nixpkgs/pkgs/test/make-binary-wrapper/suffix.env new file mode 100644 index 000000000000..3ce4cc54de41 --- /dev/null +++ b/nixpkgs/pkgs/test/make-binary-wrapper/suffix.env @@ -0,0 +1,3 @@ +PATH=/usr/bin/:/usr/local/bin/ +CWD=SUBST_CWD +SUBST_ARGV0 diff --git a/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/default.nix b/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/default.nix new file mode 100644 index 000000000000..e27a6f179208 --- /dev/null +++ b/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/default.nix @@ -0,0 +1,77 @@ +{ runCommandLocal +, lib +, git +, clang-tools +, makeHardcodeGsettingsPatch +}: + +let + mkTest = + { + name, + expected, + src, + patches ? [ ], + schemaIdToVariableMapping, + }: + + let + patch = makeHardcodeGsettingsPatch ({ + inherit src schemaIdToVariableMapping; + inherit patches; + }); + in + runCommandLocal + "makeHardcodeGsettingsPatch-tests-${name}" + + { + nativeBuildInputs = [ + git + clang-tools + ]; + } + + '' + cp -r --no-preserve=all "${src}" src + cp -r --no-preserve=all "${expected}" src-expected + + pushd src + for patch in ${lib.escapeShellArgs (builtins.map (p: "${p}") patches)}; do + patch < "$patch" + done + patch < "${patch}" + popd + + find src -name '*.c' -print0 | while read -d $'\0' sourceFile; do + sourceFile=''${sourceFile#src/} + clang-format -style='{BasedOnStyle: InheritParentConfig, ColumnLimit: 240}' -i "src/$sourceFile" "src-expected/$sourceFile" + git diff --no-index "src/$sourceFile" "src-expected/$sourceFile" | cat + done + touch "$out" + ''; +in +{ + basic = mkTest { + name = "basic"; + src = ./fixtures/example-project; + schemaIdToVariableMapping = { + "org.gnome.evolution-data-server.addressbook" = "EDS"; + "org.gnome.evolution.calendar" = "EVO"; + "org.gnome.seahorse.nautilus.window" = "SEANAUT"; + }; + expected = ./fixtures/example-project-patched; + }; + + patches = mkTest { + name = "patches"; + src = ./fixtures/example-project-wrapped-settings-constructor; + patches = [ + # Avoid using wrapper function, which the generator cannot handle. + ./fixtures/example-project-wrapped-settings-constructor-resolve.patch + ]; + schemaIdToVariableMapping = { + "org.gnome.evolution-data-server.addressbook" = "EDS"; + }; + expected = ./fixtures/example-project-wrapped-settings-constructor-patched; + }; +} diff --git a/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project-patched/main.c b/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project-patched/main.c new file mode 100644 index 000000000000..7822a42b840a --- /dev/null +++ b/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project-patched/main.c @@ -0,0 +1,85 @@ +#include <gio/gio.h> +#include <glib-object.h> + +void schema_id_literal() { + GSettings *settings; + { + g_autoptr(GSettingsSchemaSource) schema_source; + g_autoptr(GSettingsSchema) schema; + schema_source = g_settings_schema_source_new_from_directory("@EDS@", g_settings_schema_source_get_default(), TRUE, NULL); + schema = g_settings_schema_source_lookup(schema_source, "org.gnome.evolution-data-server.addressbook", FALSE); + settings = g_settings_new_full(schema, NULL, NULL); + } + g_object_unref(settings); +} + +#define SELF_UID_PATH_ID "org.gnome.evolution-data-server.addressbook" +int schema_id_from_constant() { + GSettings *settings; + { + g_autoptr(GSettingsSchemaSource) schema_source; + g_autoptr(GSettingsSchema) schema; + schema_source = g_settings_schema_source_new_from_directory("@EDS@", g_settings_schema_source_get_default(), TRUE, NULL); + schema = g_settings_schema_source_lookup(schema_source, SELF_UID_PATH_ID, FALSE); + settings = g_settings_new_full(schema, NULL, NULL); + } + g_object_unref(settings); +} + +void schema_id_autoptr() { + g_autoptr(GSettings) settings = NULL; + { + g_autoptr(GSettingsSchemaSource) schema_source; + g_autoptr(GSettingsSchema) schema; + schema_source = g_settings_schema_source_new_from_directory("@EVO@", g_settings_schema_source_get_default(), TRUE, NULL); + schema = g_settings_schema_source_lookup(schema_source, "org.gnome.evolution.calendar", FALSE); + settings = g_settings_new_full(schema, NULL, NULL); + } +} + +void schema_id_with_backend() { + GSettings *settings; + { + g_autoptr(GSettingsSchemaSource) schema_source; + g_autoptr(GSettingsSchema) schema; + schema_source = g_settings_schema_source_new_from_directory("@EDS@", g_settings_schema_source_get_default(), TRUE, NULL); + schema = g_settings_schema_source_lookup(schema_source, "org.gnome.evolution-data-server.addressbook", FALSE); + settings = g_settings_new_full(schema, g_settings_backend_get_default(), NULL); + } + g_object_unref(settings); +} + +void schema_id_with_backend_and_path() { + GSettings *settings; + { + g_autoptr(GSettingsSchemaSource) schema_source; + g_autoptr(GSettingsSchema) schema; + schema_source = g_settings_schema_source_new_from_directory("@SEANAUT@", g_settings_schema_source_get_default(), TRUE, NULL); + schema = g_settings_schema_source_lookup(schema_source, "org.gnome.seahorse.nautilus.window", FALSE); + settings = g_settings_new_full(schema, g_settings_backend_get_default(), "/org/gnome/seahorse/nautilus/windows/123/"); + } + g_object_unref(settings); +} + +void schema_id_with_path() { + GSettings *settings; + { + g_autoptr(GSettingsSchemaSource) schema_source; + g_autoptr(GSettingsSchema) schema; + schema_source = g_settings_schema_source_new_from_directory("@SEANAUT@", g_settings_schema_source_get_default(), TRUE, NULL); + schema = g_settings_schema_source_lookup(schema_source, "org.gnome.seahorse.nautilus.window", FALSE); + settings = g_settings_new_full(schema, NULL, "/org/gnome/seahorse/nautilus/windows/123/"); + } + g_object_unref(settings); +} + +int main() { + schema_id_literal(); + schema_id_from_constant(); + schema_id_autoptr(); + schema_id_with_backend(); + schema_id_with_backend_and_path(); + schema_id_with_path(); + + return 0; +} diff --git a/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project-wrapped-settings-constructor-patched/main.c b/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project-wrapped-settings-constructor-patched/main.c new file mode 100644 index 000000000000..b0894614de7c --- /dev/null +++ b/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project-wrapped-settings-constructor-patched/main.c @@ -0,0 +1,15 @@ +#include <gio/gio.h> +#include <glib-object.h> + +int main() { + g_autoptr(GSettings) settings; + { + g_autoptr(GSettingsSchemaSource) schema_source; + g_autoptr(GSettingsSchema) schema; + schema_source = g_settings_schema_source_new_from_directory("@EDS@", g_settings_schema_source_get_default(), TRUE, NULL); + schema = g_settings_schema_source_lookup(schema_source, "org.gnome.evolution-data-server.addressbook", FALSE); + settings = g_settings_new_full(schema, NULL, NULL); + } + + return 0; +} diff --git a/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project-wrapped-settings-constructor-resolve.patch b/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project-wrapped-settings-constructor-resolve.patch new file mode 100644 index 000000000000..70b80a9e8627 --- /dev/null +++ b/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project-wrapped-settings-constructor-resolve.patch @@ -0,0 +1,17 @@ +--- a/main.c ++++ b/main.c +@@ -1,13 +1,9 @@ + #include <gio/gio.h> + #include <glib-object.h> + +-void my_settings_new(const char *schema_id) { +- return g_settings_new(schema_id); +-} +- + int main() { + g_autoptr (GSettings) settings; +- settings = my_settings_new("org.gnome.evolution-data-server.addressbook"); ++ settings = g_settings_new("org.gnome.evolution-data-server.addressbook"); + + return 0; + } diff --git a/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project-wrapped-settings-constructor/main.c b/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project-wrapped-settings-constructor/main.c new file mode 100644 index 000000000000..0821097f350b --- /dev/null +++ b/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project-wrapped-settings-constructor/main.c @@ -0,0 +1,13 @@ +#include <gio/gio.h> +#include <glib-object.h> + +void my_settings_new(const char *schema_id) { + return g_settings_new(schema_id); +} + +int main() { + g_autoptr (GSettings) settings; + settings = my_settings_new("org.gnome.evolution-data-server.addressbook"); + + return 0; +} diff --git a/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project/main.c b/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project/main.c new file mode 100644 index 000000000000..afcb3686ec84 --- /dev/null +++ b/nixpkgs/pkgs/test/make-hardcode-gsettings-patch/fixtures/example-project/main.c @@ -0,0 +1,49 @@ +#include <gio/gio.h> +#include <glib-object.h> + +void schema_id_literal() { + GSettings *settings; + settings = g_settings_new("org.gnome.evolution-data-server.addressbook"); + g_object_unref(settings); +} + +#define SELF_UID_PATH_ID "org.gnome.evolution-data-server.addressbook" +int schema_id_from_constant() { + GSettings *settings; + settings = g_settings_new(SELF_UID_PATH_ID); + g_object_unref(settings); +} + +void schema_id_autoptr() { + g_autoptr(GSettings) settings = NULL; + settings = g_settings_new("org.gnome.evolution.calendar"); +} + +void schema_id_with_backend() { + GSettings *settings; + settings = g_settings_new_with_backend("org.gnome.evolution-data-server.addressbook", g_settings_backend_get_default()); + g_object_unref(settings); +} + +void schema_id_with_backend_and_path() { + GSettings *settings; + settings = g_settings_new_with_backend_and_path("org.gnome.seahorse.nautilus.window", g_settings_backend_get_default(), "/org/gnome/seahorse/nautilus/windows/123/"); + g_object_unref(settings); +} + +void schema_id_with_path() { + GSettings *settings; + settings = g_settings_new_with_path("org.gnome.seahorse.nautilus.window", "/org/gnome/seahorse/nautilus/windows/123/"); + g_object_unref(settings); +} + +int main() { + schema_id_literal(); + schema_id_from_constant(); + schema_id_autoptr(); + schema_id_with_backend(); + schema_id_with_backend_and_path(); + schema_id_with_path(); + + return 0; +} diff --git a/nixpkgs/pkgs/test/make-wrapper/default.nix b/nixpkgs/pkgs/test/make-wrapper/default.nix new file mode 100644 index 000000000000..5cc7cee5a864 --- /dev/null +++ b/nixpkgs/pkgs/test/make-wrapper/default.nix @@ -0,0 +1,139 @@ +{ lib +, writeText +, writeCBin +, writeShellScript +, makeWrapper +, runCommand +, which +, ... +}: + +let + # Testfiles + foofile = writeText "foofile" "foo"; + barfile = writeText "barfile" "bar"; + + # Wrapped binaries + wrappedArgv0 = writeCBin "wrapped-argv0" '' + #include <stdio.h> + #include <stdlib.h> + + void main(int argc, char** argv) { + printf("argv0=%s", argv[0]); + exit(0); + } + ''; + wrappedBinaryVar = writeShellScript "wrapped-var" '' + echo "VAR=$VAR" + ''; + wrappedBinaryArgs = writeShellScript "wrapped-args" '' + echo "$@" + ''; + + mkWrapperBinary = { name, args, wrapped ? wrappedBinaryVar }: runCommand name + { + nativeBuildInputs = [ makeWrapper ]; + } '' + mkdir -p $out/bin + makeWrapper "${wrapped}" "$out/bin/${name}" ${lib.escapeShellArgs args} + ''; + + mkTest = cmd: toExpect: '' + output="$(${cmd})" + if [[ "$output" != '${toExpect}' ]]; then + echo "test failed: the output of ${cmd} was '$output', expected '${toExpect}'" + echo "the wrapper contents:" + for i in ${cmd}; do + if [[ $i =~ ^test- ]]; then + cat $(which $i) + fi + done + exit 1 + fi + ''; +in +runCommand "make-wrapper-test" +{ + nativeBuildInputs = [ + which + (mkWrapperBinary { name = "test-argv0"; args = [ "--argv0" "foo" ]; wrapped = "${wrappedArgv0}/bin/wrapped-argv0"; }) + (mkWrapperBinary { name = "test-set"; args = [ "--set" "VAR" "abc" ]; }) + (mkWrapperBinary { name = "test-set-default"; args = [ "--set-default" "VAR" "abc" ]; }) + (mkWrapperBinary { name = "test-unset"; args = [ "--unset" "VAR" ]; }) + (mkWrapperBinary { name = "test-run"; args = [ "--run" "echo bar" ]; }) + (mkWrapperBinary { name = "test-run-and-set"; args = [ "--run" "export VAR=foo" "--set" "VAR" "bar" ]; }) + (mkWrapperBinary { name = "test-args"; args = [ "--add-flags" "abc" "--append-flags" "xyz" ]; wrapped = wrappedBinaryArgs; }) + (mkWrapperBinary { name = "test-prefix"; args = [ "--prefix" "VAR" ":" "abc" ]; }) + (mkWrapperBinary { name = "test-prefix-noglob"; args = [ "--prefix" "VAR" ":" "./*" ]; }) + (mkWrapperBinary { name = "test-suffix"; args = [ "--suffix" "VAR" ":" "abc" ]; }) + (mkWrapperBinary { name = "test-prefix-and-suffix"; args = [ "--prefix" "VAR" ":" "foo" "--suffix" "VAR" ":" "bar" ]; }) + (mkWrapperBinary { name = "test-prefix-multi"; args = [ "--prefix" "VAR" ":" "abc:foo:foo" ]; }) + (mkWrapperBinary { name = "test-suffix-each"; args = [ "--suffix-each" "VAR" ":" "foo bar:def" ]; }) + (mkWrapperBinary { name = "test-prefix-each"; args = [ "--prefix-each" "VAR" ":" "foo bar:def" ]; }) + (mkWrapperBinary { name = "test-suffix-contents"; args = [ "--suffix-contents" "VAR" ":" "${foofile} ${barfile}" ]; }) + (mkWrapperBinary { name = "test-prefix-contents"; args = [ "--prefix-contents" "VAR" ":" "${foofile} ${barfile}" ]; }) + ]; +} + ( + # --argv0 works + mkTest "test-argv0" "argv0=foo" + + # --set works + + mkTest "test-set" "VAR=abc" + # --set overwrites the variable + + mkTest "VAR=foo test-set" "VAR=abc" + # --set-default works + + mkTest "test-set-default" "VAR=abc" + # --set-default doesn"t overwrite the variable + + mkTest "VAR=foo test-set-default" "VAR=foo" + # --unset works + + mkTest "VAR=foo test-unset" "VAR=" + + # --add-flags and --append-flags work + + mkTest "test-args" "abc xyz" + # given flags are kept + + mkTest "test-args foo" "abc foo xyz" + + # --run works + + mkTest "test-run" "bar\nVAR=" + # --run & --set works + + mkTest "test-run-and-set" "VAR=bar" + + # --prefix works + + mkTest "VAR=foo test-prefix" "VAR=abc:foo" + # sets variable if not set yet + + mkTest "test-prefix" "VAR=abc" + # prepends value only once + + mkTest "VAR=abc test-prefix" "VAR=abc" + # Moves value to the front if it already existed + + mkTest "VAR=foo:abc test-prefix" "VAR=abc:foo" + + mkTest "VAR=abc:foo:bar test-prefix-multi" "VAR=abc:foo:bar" + # Doesn't overwrite parts of the string + + mkTest "VAR=test:abcde:test test-prefix" "VAR=abc:test:abcde:test" + # Only append the value once when given multiple times in a parameter + # to makeWrapper + + mkTest "test-prefix" "VAR=abc" + # --prefix doesn't expand globs + + mkTest "VAR=f?oo test-prefix-noglob" "VAR=./*:f?oo" + + + # --suffix works + + mkTest "VAR=foo test-suffix" "VAR=foo:abc" + # sets variable if not set yet + + mkTest "test-suffix" "VAR=abc" + # adds the same value only once + + mkTest "VAR=abc test-suffix" "VAR=abc" + + mkTest "VAR=abc:foo test-suffix" "VAR=abc:foo" + # --prefix in combination with --suffix + + mkTest "VAR=abc test-prefix-and-suffix" "VAR=foo:abc:bar" + + # --suffix-each works + + mkTest "VAR=abc test-suffix-each" "VAR=abc:foo:bar:def" + # --prefix-each works + + mkTest "VAR=abc test-prefix-each" "VAR=bar:def:foo:abc" + # --suffix-contents works + + mkTest "VAR=abc test-suffix-contents" "VAR=abc:foo:bar" + # --prefix-contents works + + mkTest "VAR=abc test-prefix-contents" "VAR=bar:foo:abc" + + "touch $out" + ) diff --git a/nixpkgs/pkgs/test/nixos-functions/default.nix b/nixpkgs/pkgs/test/nixos-functions/default.nix new file mode 100644 index 000000000000..bdd5e3c6d8b4 --- /dev/null +++ b/nixpkgs/pkgs/test/nixos-functions/default.nix @@ -0,0 +1,31 @@ +/* + +This file is a test that makes sure that the `pkgs.nixos` and +`pkgs.testers.nixosTest` functions work. It's far from a perfect test suite, +but better than not checking them at all on hydra. + +To run this test: + + nixpkgs$ nix-build -A tests.nixos-functions + + */ +{ pkgs, lib, stdenv, ... }: + +let + dummyVersioning = { + revision = "test"; + versionSuffix = "test"; + label = "test"; + }; +in lib.optionalAttrs stdenv.hostPlatform.isLinux ( + pkgs.recurseIntoAttrs { + + nixos-test = (pkgs.nixos { + system.nixos = dummyVersioning; + boot.loader.grub.enable = false; + fileSystems."/".device = "/dev/null"; + system.stateVersion = lib.trivial.release; + }).toplevel; + + } +) diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/.envrc b/nixpkgs/pkgs/test/nixpkgs-check-by-name/.envrc new file mode 100644 index 000000000000..4555ccf0921f --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/.envrc @@ -0,0 +1,7 @@ +if has nix_direnv_watch_file; then + nix_direnv_watch_file default.nix shell.nix +else + watch_file default.nix shell.nix +fi + +use nix diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/.gitignore b/nixpkgs/pkgs/test/nixpkgs-check-by-name/.gitignore new file mode 100644 index 000000000000..75e92a908987 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/.gitignore @@ -0,0 +1,2 @@ +target +.direnv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/Cargo.lock b/nixpkgs/pkgs/test/nixpkgs-check-by-name/Cargo.lock new file mode 100644 index 000000000000..904a9cff0e78 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/Cargo.lock @@ -0,0 +1,606 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d5f1946157a96594eb2d2c10eb7ad9a2b27518cb3000209dec700c35df9197d" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78116e32a042dd73c2901f0dc30790d20ff3447f3e3472fad359e8c3d282bcd6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "colored" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +dependencies = [ + "is-terminal", + "lazy_static", + "windows-sys", +] + +[[package]] +name = "countme" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "indoc" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nixpkgs-check-by-name" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "colored", + "indoc", + "itertools", + "lazy_static", + "regex", + "rnix", + "rowan", + "serde", + "serde_json", + "temp-env", + "tempfile", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "rnix" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb35cedbeb70e0ccabef2a31bcff0aebd114f19566086300b8f42c725fc2cb5f" +dependencies = [ + "rowan", +] + +[[package]] +name = "rowan" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64449cfef9483a475ed56ae30e2da5ee96448789fb2aa240a04beb6a055078bf" +dependencies = [ + "countme", + "hashbrown", + "memoffset", + "rustc-hash", + "text-size", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "temp-env" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e010429b1f3ea1311190c658c7570100f03c1dab05c16cfab774181c648d656a" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + +[[package]] +name = "text-size" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/Cargo.toml b/nixpkgs/pkgs/test/nixpkgs-check-by-name/Cargo.toml new file mode 100644 index 000000000000..5240cd69f996 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "nixpkgs-check-by-name" +version = "0.1.0" +edition = "2021" + +[dependencies] +rnix = "0.11.0" +regex = "1.9.3" +clap = { version = "4.3.23", features = ["derive"] } +serde_json = "1.0.105" +tempfile = "3.8.0" +serde = { version = "1.0.185", features = ["derive"] } +anyhow = "1.0" +lazy_static = "1.4.0" +colored = "2.0.4" +itertools = "0.11.0" +rowan = "0.15.11" + +[dev-dependencies] +temp-env = "0.3.5" +indoc = "2.0.4" diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/README.md b/nixpkgs/pkgs/test/nixpkgs-check-by-name/README.md new file mode 100644 index 000000000000..1aa256978416 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/README.md @@ -0,0 +1,102 @@ +# Nixpkgs pkgs/by-name checker + +This directory implements a program to check the [validity](#validity-checks) of the `pkgs/by-name` Nixpkgs directory. +This is part of the implementation of [RFC 140](https://github.com/NixOS/rfcs/pull/140). + +A [pinned version](./scripts/pinned-tool.json) of this tool is used by [this GitHub Actions workflow](../../../.github/workflows/check-by-name.yml). +See [./scripts](./scripts/README.md#update-pinned-toolsh) for how to update the pinned version. + +The source of the tool being right inside Nixpkgs allows any Nixpkgs committer to make updates to it. + +## Interface + +The interface of the tool is shown with `--help`: +``` +cargo run -- --help +``` + +The interface may be changed over time only if the CI workflow making use of it is adjusted to deal with the change appropriately. + +## Validity checks + +These checks are performed by this tool: + +### File structure checks +- `pkgs/by-name` must only contain subdirectories of the form `${shard}/${name}`, called _package directories_. +- The `name`'s of package directories must be unique when lowercased. +- `name` is a string only consisting of the ASCII characters `a-z`, `A-Z`, `0-9`, `-` or `_`. +- `shard` is the lowercased first two letters of `name`, expressed in Nix: `shard = toLower (substring 0 2 name)`. +- Each package directory must contain a `package.nix` file and may contain arbitrary other files. + +### Nix parser checks +- Each package directory must not refer to files outside itself using symlinks or Nix path expressions. + +### Nix evaluation checks + +Evaluate Nixpkgs with `system` set to `x86_64-linux` and check that: +- For each package directory, the `pkgs.${name}` attribute must be defined as `callPackage pkgs/by-name/${shard}/${name}/package.nix args` for some `args`. +- For each package directory, `pkgs.lib.isDerivation pkgs.${name}` must be `true`. + +### Ratchet checks + +Furthermore, this tool implements certain [ratchet](https://qntm.org/ratchet) checks. +This allows gradually phasing out deprecated patterns without breaking the base branch or having to migrate it all at once. +It works by not allowing new instances of the pattern to be introduced, but allowing already existing instances. +The existing instances are coming from `<BASE_NIXPKGS>`, which is then checked against `<NIXPKGS>` for new instances. +Ratchets should be removed eventually once the pattern is not used anymore. + +The current ratchets are: + +- New manual definitions of `pkgs.${name}` (e.g. in `pkgs/top-level/all-packages.nix`) with `args = { }` + (see [nix evaluation checks](#nix-evaluation-checks)) must not be introduced. +- New top-level packages defined using `pkgs.callPackage` must be defined with a package directory. + - Once a top-level package uses `pkgs/by-name`, it also can't be moved back out of it. + +## Development + +Enter the development environment in this directory either automatically with `direnv` or with +``` +nix-shell +``` + +Then use `cargo`: +``` +cargo build +cargo test +cargo fmt +cargo clippy +``` + +## Tests + +Tests are declared in [`./tests`](./tests) as subdirectories imitating Nixpkgs with these files: +- `default.nix`: + Always contains + ```nix + import <test-nixpkgs> { root = ./.; } + ``` + which makes + ``` + nix-instantiate <subdir> --eval -A <attr> --arg overlays <overlays> + ``` + work very similarly to the real Nixpkgs, just enough for the program to be able to test it. +- `pkgs/by-name`: + The `pkgs/by-name` directory to check. + +- `all-packages.nix` (optional): + Contains an overlay of the form + ```nix + self: super: { + # ... + } + ``` + allowing the simulation of package overrides to the real [`pkgs/top-level/all-packages.nix`](../../top-level/all-packages.nix`). + The default is an empty overlay. + +- `base` (optional): + Contains another subdirectory imitating Nixpkgs with potentially any of the above structures. + This is used for [ratchet checks](#ratchet-checks). + +- `expected` (optional): + A file containing the expected standard output. + The default is expecting an empty standard output. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/default.nix new file mode 100644 index 000000000000..8836da1f403f --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/default.nix @@ -0,0 +1,82 @@ +{ + lib, + rustPlatform, + nix, + rustfmt, + clippy, + mkShell, + makeWrapper, + runCommand, +}: +let + runtimeExprPath = ./src/eval.nix; + nixpkgsLibPath = ../../../lib; + testNixpkgsPath = ./tests/mock-nixpkgs.nix; + + # Needed to make Nix evaluation work inside nix builds + initNix = '' + export TEST_ROOT=$(pwd)/test-tmp + export NIX_CONF_DIR=$TEST_ROOT/etc + export NIX_LOCALSTATE_DIR=$TEST_ROOT/var + export NIX_LOG_DIR=$TEST_ROOT/var/log/nix + export NIX_STATE_DIR=$TEST_ROOT/var/nix + export NIX_STORE_DIR=$TEST_ROOT/store + + # Ensure that even if tests run in parallel, we don't get an error + # We'd run into https://github.com/NixOS/nix/issues/2706 unless the store is initialised first + nix-store --init + ''; + + fs = lib.fileset; + + package = + rustPlatform.buildRustPackage { + name = "nixpkgs-check-by-name"; + src = fs.toSource { + root = ./.; + fileset = fs.unions [ + ./Cargo.lock + ./Cargo.toml + ./src + ./tests + ]; + }; + cargoLock.lockFile = ./Cargo.lock; + nativeBuildInputs = [ + nix + rustfmt + clippy + makeWrapper + ]; + env.NIX_CHECK_BY_NAME_EXPR_PATH = "${runtimeExprPath}"; + env.NIX_PATH = "test-nixpkgs=${testNixpkgsPath}:test-nixpkgs/lib=${nixpkgsLibPath}"; + preCheck = initNix; + postCheck = '' + cargo fmt --check + cargo clippy -- -D warnings + ''; + postInstall = '' + wrapProgram $out/bin/nixpkgs-check-by-name \ + --set NIX_CHECK_BY_NAME_EXPR_PATH "$NIX_CHECK_BY_NAME_EXPR_PATH" + ''; + passthru.shell = mkShell { + env.NIX_CHECK_BY_NAME_EXPR_PATH = toString runtimeExprPath; + env.NIX_PATH = "test-nixpkgs=${toString testNixpkgsPath}:test-nixpkgs/lib=${toString nixpkgsLibPath}"; + inputsFrom = [ package ]; + }; + + # Tests the tool on the current Nixpkgs tree, this is a good sanity check + passthru.tests.nixpkgs = runCommand "test-nixpkgs-check-by-name" { + nativeBuildInputs = [ + package + nix + ]; + nixpkgsPath = lib.cleanSource ../../..; + } '' + ${initNix} + nixpkgs-check-by-name --base "$nixpkgsPath" "$nixpkgsPath" + touch $out + ''; + }; +in +package diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/scripts/README.md b/nixpkgs/pkgs/test/nixpkgs-check-by-name/scripts/README.md new file mode 100644 index 000000000000..ccd4108ea288 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/scripts/README.md @@ -0,0 +1,38 @@ +# CI-related Scripts + +This directory contains scripts and files used and related to the CI running the `pkgs/by-name` checks in Nixpkgs. +See also the [CI GitHub Action](../../../../.github/workflows/check-by-name.yml). + +## `./run-local.sh BASE_BRANCH [REPOSITORY]` + +Runs the `pkgs/by-name` check on the HEAD commit, closely matching what CI does. + +Note that this can't do exactly the same as CI, +because CI needs to rely on GitHub's server-side Git history to compute the mergeability of PRs before the check can be started. +In turn when running locally, we don't want to have to push commits to test them, +and we can also rely on the local Git history to do the mergeability check. + +Arguments: +- `BASE_BRANCH`: The base branch to use, e.g. master or release-23.11 +- `REPOSITORY`: The repository to fetch the base branch from, defaults to https://github.com/NixOS/nixpkgs.git + +## `./update-pinned-tool.sh` + +Updates the pinned CI tool in [`./pinned-tool.json`](./pinned-tool.json) to the +[latest version from the `nixos-unstable` channel](https://hydra.nixos.org/job/nixos/trunk-combined/nixpkgs.tests.nixpkgs-check-by-name.x86_64-linux). + +This script needs to be called manually when the CI tooling needs to be updated. + +The `pinned-tool.json` file gets populated with both: +- The `/nix/store` path for `x86_64-linux`, such that CI doesn't have to evaluate Nixpkgs and can directly fetch it from the cache instead. +- The Nixpkgs revision, such that the `./run-local.sh` script can be used to run the checks locally on any system. + +To ensure that the tool is always pre-built for `x86_64-linux` in the `nixos-unstable` channel, +it's included in the `tested` jobset description in [`nixos/release-combined.nix`](../../../nixos/release-combined.nix). + +Why not just build the tooling right from the PRs Nixpkgs version? +- Because it allows CI to check all PRs, even if they would break the CI tooling. +- Because it makes the CI check very fast, since no Nix builds need to be done, even for mass rebuilds. +- Because it improves security, since we don't have to build potentially untrusted code from PRs. + The tool only needs a very minimal Nix evaluation at runtime, which can work with [readonly-mode](https://nixos.org/manual/nix/stable/command-ref/opt-common.html#opt-readonly-mode) and [restrict-eval](https://nixos.org/manual/nix/stable/command-ref/conf-file.html#conf-restrict-eval). + diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/scripts/pinned-tool.json b/nixpkgs/pkgs/test/nixpkgs-check-by-name/scripts/pinned-tool.json new file mode 100644 index 000000000000..e20a11baadaf --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/scripts/pinned-tool.json @@ -0,0 +1,4 @@ +{ + "rev": "d934204a0f8d9198e1e4515dd6fec76a139c87f0", + "ci-path": "/nix/store/5fjdmbiziyp47gfc9kmfgvxdlzd6bba1-nixpkgs-check-by-name" +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/scripts/run-local.sh b/nixpkgs/pkgs/test/nixpkgs-check-by-name/scripts/run-local.sh new file mode 100755 index 000000000000..46cbd5e858e2 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/scripts/run-local.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env nix-shell +#!nix-shell -i bash -p jq + +set -o pipefail -o errexit -o nounset + +trace() { echo >&2 "$@"; } + +tmp=$(mktemp -d) +cleanup() { + # Don't exit early if anything fails to cleanup + set +o errexit + + trace -n "Cleaning up.. " + + [[ -e "$tmp/base" ]] && git worktree remove --force "$tmp/base" + [[ -e "$tmp/merged" ]] && git worktree remove --force "$tmp/merged" + [[ -e "$tmp/tool-nixpkgs" ]] && git worktree remove --force "$tmp/tool-nixpkgs" + + rm -rf "$tmp" + + trace "Done" +} +trap cleanup exit + + +repo=https://github.com/NixOS/nixpkgs.git + +if (( $# != 0 )); then + baseBranch=$1 + shift +else + trace "Usage: $0 BASE_BRANCH [REPOSITORY]" + trace "BASE_BRANCH: The base branch to use, e.g. master or release-23.11" + trace "REPOSITORY: The repository to fetch the base branch from, defaults to $repo" + exit 1 +fi + +if (( $# != 0 )); then + repo=$1 + shift +fi + +if [[ -n "$(git status --porcelain)" ]]; then + trace -e "\e[33mWarning: Dirty tree, uncommitted changes won't be taken into account\e[0m" +fi +headSha=$(git rev-parse HEAD) +trace -e "Using HEAD commit \e[34m$headSha\e[0m" + +trace -n "Creating Git worktree for the HEAD commit in $tmp/merged.. " +git worktree add --detach -q "$tmp/merged" HEAD +trace "Done" + +trace -n "Fetching base branch $baseBranch to compare against.. " +git fetch -q "$repo" refs/heads/"$baseBranch" +baseSha=$(git rev-parse FETCH_HEAD) +trace -e "\e[34m$baseSha\e[0m" + +trace -n "Creating Git worktree for the base branch in $tmp/base.. " +git worktree add -q "$tmp/base" "$baseSha" +trace "Done" + +trace -n "Merging base branch into the HEAD commit in $tmp/merged.. " +git -C "$tmp/merged" merge -q --no-edit "$baseSha" +trace -e "\e[34m$(git -C "$tmp/merged" rev-parse HEAD)\e[0m" + +trace -n "Reading pinned nixpkgs-check-by-name revision from pinned-tool.json.. " +toolSha=$(jq -r .rev "$tmp/merged/pkgs/test/nixpkgs-check-by-name/scripts/pinned-tool.json") +trace -e "\e[34m$toolSha\e[0m" + +trace -n "Creating Git worktree for the nixpkgs-check-by-name revision in $tmp/tool-nixpkgs.. " +git worktree add -q "$tmp/tool-nixpkgs" "$toolSha" +trace "Done" + +trace "Building/fetching nixpkgs-check-by-name.." +nix-build -o "$tmp/tool" "$tmp/tool-nixpkgs" \ + -A tests.nixpkgs-check-by-name \ + --arg config '{}' \ + --arg overlays '[]' \ + -j 0 + +trace "Running nixpkgs-check-by-name.." +"$tmp/tool/bin/nixpkgs-check-by-name" --base "$tmp/base" "$tmp/merged" diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/scripts/update-pinned-tool.sh b/nixpkgs/pkgs/test/nixpkgs-check-by-name/scripts/update-pinned-tool.sh new file mode 100755 index 000000000000..dbc6e91df08a --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/scripts/update-pinned-tool.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env nix-shell +#!nix-shell -i bash -p jq + +set -o pipefail -o errexit -o nounset + +trace() { echo >&2 "$@"; } + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +# Determined by `runs-on: ubuntu-latest` in .github/workflows/check-by-name.yml +CI_SYSTEM=x86_64-linux + +channel=nixos-unstable +pin_file=$SCRIPT_DIR/pinned-tool.json + +trace -n "Fetching latest version of channel $channel.. " +# This is probably the easiest way to get Nix to output the path to a downloaded channel! +nixpkgs=$(nix-instantiate --find-file nixpkgs -I nixpkgs=channel:"$channel") +trace "$nixpkgs" + +# This file only exists in channels +rev=$(<"$nixpkgs/.git-revision") +trace -e "Git revision of channel $channel is \e[34m$rev\e[0m" + +trace -n "Fetching the prebuilt version of nixpkgs-check-by-name for $CI_SYSTEM.. " +# This is the architecture used by CI, we want to prefetch the exact path to avoid having to evaluate Nixpkgs +ci_path=$(nix-build --no-out-link "$nixpkgs" \ + -A tests.nixpkgs-check-by-name \ + --arg config '{}' \ + --argstr system "$CI_SYSTEM" \ + --arg overlays '[]' \ + -j 0 \ + | tee /dev/stderr) + +trace "Updating $pin_file" +jq -n \ + --arg rev "$rev" \ + --arg ci-path "$ci_path" \ + '$ARGS.named' \ + > "$pin_file" diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/shell.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/shell.nix new file mode 100644 index 000000000000..33bcf45b8d05 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/shell.nix @@ -0,0 +1,6 @@ +let + pkgs = import ../../.. { + config = {}; + overlays = []; + }; +in pkgs.tests.nixpkgs-check-by-name.shell diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/eval.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/eval.nix new file mode 100644 index 000000000000..ab1c41e0b145 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/eval.nix @@ -0,0 +1,116 @@ +# Takes a path to nixpkgs and a path to the json-encoded list of `pkgs/by-name` attributes. +# Returns a value containing information on all Nixpkgs attributes +# which is decoded on the Rust side. +# See ./eval.rs for the meaning of the returned values +{ + attrsPath, + nixpkgsPath, +}: +let + attrs = builtins.fromJSON (builtins.readFile attrsPath); + + # We need to check whether attributes are defined manually e.g. in + # `all-packages.nix`, automatically by the `pkgs/by-name` overlay, or + # neither. The only way to do so is to override `callPackage` and + # `_internalCallByNamePackageFile` with our own version that adds this + # information to the result, and then try to access it. + overlay = final: prev: { + + # Adds information to each attribute about whether it's manually defined using `callPackage` + callPackage = fn: args: + addVariantInfo (prev.callPackage fn args) { + # This is a manual definition of the attribute, and it's a callPackage, specifically a semantic callPackage + ManualDefinition.is_semantic_call_package = true; + }; + + # Adds information to each attribute about whether it's automatically + # defined by the `pkgs/by-name` overlay. This internal attribute is only + # used by that overlay. + # This overrides the above `callPackage` information (we don't need that + # one, since `pkgs/by-name` always uses `callPackage` underneath. + _internalCallByNamePackageFile = file: + addVariantInfo (prev._internalCallByNamePackageFile file) { + AutoDefinition = null; + }; + + }; + + # We can't just replace attribute values with their info in the overlay, + # because attributes can depend on other attributes, so this would break evaluation. + addVariantInfo = value: variant: + if builtins.isAttrs value then + value // { + _callPackageVariant = variant; + } + else + # It's very rare that callPackage doesn't return an attribute set, but it can occur. + # In such a case we can't really return anything sensible that would include the info, + # so just don't return the value directly and treat it as if it wasn't a callPackage. + value; + + pkgs = import nixpkgsPath { + # Don't let the users home directory influence this result + config = { }; + overlays = [ overlay ]; + # We check evaluation and callPackage only for x86_64-linux. + # Not ideal, but hard to fix + system = "x86_64-linux"; + }; + + # See AttributeInfo in ./eval.rs for the meaning of this + attrInfo = name: value: { + location = builtins.unsafeGetAttrPos name pkgs; + attribute_variant = + if ! builtins.isAttrs value then + { NonAttributeSet = null; } + else + { + AttributeSet = { + is_derivation = pkgs.lib.isDerivation value; + definition_variant = + if ! value ? _callPackageVariant then + { ManualDefinition.is_semantic_call_package = false; } + else + value._callPackageVariant; + }; + }; + }; + + # Information on all attributes that are in pkgs/by-name. + byNameAttrs = builtins.listToAttrs (map (name: { + inherit name; + value.ByName = + if ! pkgs ? ${name} then + { Missing = null; } + else + # Evaluation failures are not allowed, so don't try to catch them + { Existing = attrInfo name pkgs.${name}; }; + }) attrs); + + # Information on all attributes that exist but are not in pkgs/by-name. + # We need this to enforce pkgs/by-name for new packages + nonByNameAttrs = builtins.mapAttrs (name: value: + let + # Packages outside `pkgs/by-name` often fail evaluation, + # so we need to handle that + output = attrInfo name value; + result = builtins.tryEval (builtins.deepSeq output null); + in + { + NonByName = + if result.success then + { EvalSuccess = output; } + else + { EvalFailure = null; }; + } + ) (builtins.removeAttrs pkgs attrs); + + # All attributes + attributes = byNameAttrs // nonByNameAttrs; +in +# We output them in the form [ [ <name> <value> ] ]` such that the Rust side +# doesn't need to sort them again to get deterministic behavior (good for testing) +map (name: [ + name + attributes.${name} +]) (builtins.attrNames attributes) diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/eval.rs b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/eval.rs new file mode 100644 index 000000000000..e90a95533144 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/eval.rs @@ -0,0 +1,474 @@ +use crate::nixpkgs_problem::NixpkgsProblem; +use crate::ratchet; +use crate::structure; +use crate::utils; +use crate::validation::ResultIteratorExt as _; +use crate::validation::{self, Validation::Success}; +use crate::NixFileStore; +use std::path::Path; + +use anyhow::Context; +use serde::Deserialize; +use std::path::PathBuf; +use std::process; +use tempfile::NamedTempFile; + +/// Attribute set of this structure is returned by eval.nix +#[derive(Deserialize)] +enum Attribute { + /// An attribute that should be defined via pkgs/by-name + ByName(ByNameAttribute), + /// An attribute not defined via pkgs/by-name + NonByName(NonByNameAttribute), +} + +#[derive(Deserialize)] +enum NonByNameAttribute { + /// The attribute doesn't evaluate + EvalFailure, + EvalSuccess(AttributeInfo), +} + +#[derive(Deserialize)] +enum ByNameAttribute { + /// The attribute doesn't exist at all + Missing, + Existing(AttributeInfo), +} + +#[derive(Deserialize)] +struct AttributeInfo { + /// The location of the attribute as returned by `builtins.unsafeGetAttrPos` + location: Option<Location>, + attribute_variant: AttributeVariant, +} + +/// The structure returned by a successful `builtins.unsafeGetAttrPos` +#[derive(Deserialize, Clone, Debug)] +struct Location { + pub file: PathBuf, + pub line: usize, + pub column: usize, +} + +#[derive(Deserialize)] +pub enum AttributeVariant { + /// The attribute is not an attribute set, we're limited in the amount of information we can get + /// from it (though it's obviously not a derivation) + NonAttributeSet, + AttributeSet { + /// Whether the attribute is a derivation (`lib.isDerivation`) + is_derivation: bool, + /// The type of callPackage + definition_variant: DefinitionVariant, + }, +} + +#[derive(Deserialize)] +pub enum DefinitionVariant { + /// An automatic definition by the `pkgs/by-name` overlay + /// Though it's detected using the internal _internalCallByNamePackageFile attribute, + /// which can in theory also be used by other code + AutoDefinition, + /// A manual definition of the attribute, typically in `all-packages.nix` + ManualDefinition { + /// Whether the attribute is defined as `pkgs.callPackage ...` or something else. + is_semantic_call_package: bool, + }, +} + +/// Check that the Nixpkgs attribute values corresponding to the packages in pkgs/by-name are +/// of the form `callPackage <package_file> { ... }`. +/// See the `eval.nix` file for how this is achieved on the Nix side +pub fn check_values( + nixpkgs_path: &Path, + nix_file_store: &mut NixFileStore, + package_names: Vec<String>, + keep_nix_path: bool, +) -> validation::Result<ratchet::Nixpkgs> { + // Write the list of packages we need to check into a temporary JSON file. + // This can then get read by the Nix evaluation. + let attrs_file = NamedTempFile::new().with_context(|| "Failed to create a temporary file")?; + // We need to canonicalise this path because if it's a symlink (which can be the case on + // Darwin), Nix would need to read both the symlink and the target path, therefore need 2 + // NIX_PATH entries for restrict-eval. But if we resolve the symlinks then only one predictable + // entry is needed. + let attrs_file_path = attrs_file.path().canonicalize()?; + + serde_json::to_writer(&attrs_file, &package_names).with_context(|| { + format!( + "Failed to serialise the package names to the temporary path {}", + attrs_file_path.display() + ) + })?; + + let expr_path = std::env::var("NIX_CHECK_BY_NAME_EXPR_PATH") + .with_context(|| "Could not get environment variable NIX_CHECK_BY_NAME_EXPR_PATH")?; + // With restrict-eval, only paths in NIX_PATH can be accessed, so we explicitly specify the + // ones needed needed + let mut command = process::Command::new("nix-instantiate"); + command + // Inherit stderr so that error messages always get shown + .stderr(process::Stdio::inherit()) + .args([ + "--eval", + "--json", + "--strict", + "--readonly-mode", + "--restrict-eval", + "--show-trace", + ]) + // Pass the path to the attrs_file as an argument and add it to the NIX_PATH so it can be + // accessed in restrict-eval mode + .args(["--arg", "attrsPath"]) + .arg(&attrs_file_path) + .arg("-I") + .arg(&attrs_file_path) + // Same for the nixpkgs to test + .args(["--arg", "nixpkgsPath"]) + .arg(nixpkgs_path) + .arg("-I") + .arg(nixpkgs_path); + + // Clear NIX_PATH to be sure it doesn't influence the result + // But not when requested to keep it, used so that the tests can pass extra Nix files + if !keep_nix_path { + command.env_remove("NIX_PATH"); + } + + command.args(["-I", &expr_path]); + command.arg(expr_path); + + let result = command + .output() + .with_context(|| format!("Failed to run command {command:?}"))?; + + if !result.status.success() { + anyhow::bail!("Failed to run command {command:?}"); + } + // Parse the resulting JSON value + let attributes: Vec<(String, Attribute)> = serde_json::from_slice(&result.stdout) + .with_context(|| { + format!( + "Failed to deserialise {}", + String::from_utf8_lossy(&result.stdout) + ) + })?; + + let check_result = validation::sequence( + attributes + .into_iter() + .map(|(attribute_name, attribute_value)| { + let check_result = match attribute_value { + Attribute::NonByName(non_by_name_attribute) => handle_non_by_name_attribute( + nixpkgs_path, + nix_file_store, + non_by_name_attribute, + )?, + Attribute::ByName(by_name_attribute) => by_name( + nix_file_store, + nixpkgs_path, + &attribute_name, + by_name_attribute, + )?, + }; + Ok::<_, anyhow::Error>(check_result.map(|value| (attribute_name.clone(), value))) + }) + .collect_vec()?, + ); + + Ok(check_result.map(|elems| ratchet::Nixpkgs { + package_names: elems.iter().map(|(name, _)| name.to_owned()).collect(), + package_map: elems.into_iter().collect(), + })) +} + +/// Handles the evaluation result for an attribute in `pkgs/by-name`, +/// turning it into a validation result. +fn by_name( + nix_file_store: &mut NixFileStore, + nixpkgs_path: &Path, + attribute_name: &str, + by_name_attribute: ByNameAttribute, +) -> validation::Result<ratchet::Package> { + use ratchet::RatchetState::*; + use ByNameAttribute::*; + + let relative_package_file = structure::relative_file_for_package(attribute_name); + let absolute_package_file = nixpkgs_path.join(&relative_package_file); + + // At this point we know that `pkgs/by-name/fo/foo/package.nix` has to exists. + // This match decides whether the attribute `foo` is defined accordingly + // and whether a legacy manual definition could be removed + let manual_definition_result = match by_name_attribute { + // The attribute is missing + Missing => { + // This indicates a bug in the `pkgs/by-name` overlay, because it's supposed to + // automatically defined attributes in `pkgs/by-name` + NixpkgsProblem::UndefinedAttr { + relative_package_file: relative_package_file.to_owned(), + package_name: attribute_name.to_owned(), + } + .into() + } + // The attribute exists + Existing(AttributeInfo { + // But it's not an attribute set, which limits the amount of information we can get + // about this attribute (see ./eval.nix) + attribute_variant: AttributeVariant::NonAttributeSet, + location: _location, + }) => { + // The only thing we know is that it's definitely not a derivation, since those are + // always attribute sets. + // + // We can't know whether the attribute is automatically or manually defined for sure, + // and while we could check the location, the error seems clear enough as is. + NixpkgsProblem::NonDerivation { + relative_package_file: relative_package_file.to_owned(), + package_name: attribute_name.to_owned(), + } + .into() + } + // The attribute exists + Existing(AttributeInfo { + // And it's an attribute set, which allows us to get more information about it + attribute_variant: + AttributeVariant::AttributeSet { + is_derivation, + definition_variant, + }, + location, + }) => { + // Only derivations are allowed in `pkgs/by-name` + let is_derivation_result = if is_derivation { + Success(()) + } else { + NixpkgsProblem::NonDerivation { + relative_package_file: relative_package_file.to_owned(), + package_name: attribute_name.to_owned(), + } + .into() + }; + + // If the definition looks correct + let variant_result = match definition_variant { + // An automatic `callPackage` by the `pkgs/by-name` overlay. + // Though this gets detected by checking whether the internal + // `_internalCallByNamePackageFile` was used + DefinitionVariant::AutoDefinition => { + if let Some(_location) = location { + // Such an automatic definition should definitely not have a location + // Having one indicates that somebody is using `_internalCallByNamePackageFile`, + NixpkgsProblem::InternalCallPackageUsed { + attr_name: attribute_name.to_owned(), + } + .into() + } else { + Success(Tight) + } + } + // The attribute is manually defined, e.g. in `all-packages.nix`. + // This means we need to enforce it to look like this: + // callPackage ../pkgs/by-name/fo/foo/package.nix { ... } + DefinitionVariant::ManualDefinition { + is_semantic_call_package, + } => { + // We should expect manual definitions to have a location, otherwise we can't + // enforce the expected format + if let Some(location) = location { + // Parse the Nix file in the location and figure out whether it's an + // attribute definition of the form `= callPackage <arg1> <arg2>`, + // returning the arguments if so. + let optional_syntactic_call_package = nix_file_store + .get(&location.file)? + .call_package_argument_info_at( + location.line, + location.column, + // We're passing `pkgs/by-name/fo/foo/package.nix` here, which causes + // the function to verify that `<arg1>` is the same path, + // making `syntactic_call_package.relative_path` end up as `""` + // TODO: This is confusing and should be improved + &absolute_package_file, + )?; + + // At this point, we completed two different checks for whether it's a + // `callPackage` + match (is_semantic_call_package, optional_syntactic_call_package) { + // Something like `<attr> = { ... }` + // or a `pkgs.callPackage` but with the wrong file + (false, None) + // Something like `<attr> = pythonPackages.callPackage ./pkgs/by-name/...` + | (false, Some(_)) + // Something like `<attr> = bar` where `bar = pkgs.callPackage ...` + // or a `callPackage` but with the wrong file + | (true, None) => { + // All of these are not of the expected form, so error out + // TODO: Make error more specific, don't lump everything together + NixpkgsProblem::WrongCallPackage { + relative_package_file: relative_package_file.to_owned(), + package_name: attribute_name.to_owned(), + }.into() + } + // Something like `<attr> = pkgs.callPackage ./pkgs/by-name/...`, + // with the correct file + (true, Some(syntactic_call_package)) => { + Success( + // Manual definitions with empty arguments are not allowed + // anymore + if syntactic_call_package.empty_arg { + Loose(()) + } else { + Tight + } + ) + } + } + } else { + // If manual definitions don't have a location, it's likely `mapAttrs`'d + // over, e.g. if it's defined in aliases.nix. + // We can't verify whether its of the expected `callPackage`, so error out + NixpkgsProblem::CannotDetermineAttributeLocation { + attr_name: attribute_name.to_owned(), + } + .into() + } + } + }; + + // Independently report problems about whether it's a derivation and the callPackage variant + is_derivation_result.and(variant_result) + } + }; + Ok( + // Packages being checked in this function are _always_ already defined in `pkgs/by-name`, + // so instead of repeating ourselves all the time to define `uses_by_name`, just set it + // once at the end with a map + manual_definition_result.map(|manual_definition| ratchet::Package { + manual_definition, + uses_by_name: Tight, + }), + ) +} + +/// Handles the evaluation result for an attribute _not_ in `pkgs/by-name`, +/// turning it into a validation result. +fn handle_non_by_name_attribute( + nixpkgs_path: &Path, + nix_file_store: &mut NixFileStore, + non_by_name_attribute: NonByNameAttribute, +) -> validation::Result<ratchet::Package> { + use ratchet::RatchetState::*; + use NonByNameAttribute::*; + + // The ratchet state whether this attribute uses `pkgs/by-name`. + // This is never `Tight`, because we only either: + // - Know that the attribute _could_ be migrated to `pkgs/by-name`, which is `Loose` + // - Or we're unsure, in which case we use NonApplicable + let uses_by_name = + // This is a big ol' match on various properties of the attribute + + // First, it needs to succeed evaluation. We can't know whether an attribute could be + // migrated to `pkgs/by-name` if it doesn't evaluate, since we need to check that it's a + // derivation. + // + // This only has the minor negative effect that if a PR that breaks evaluation + // gets merged, fixing those failures won't force anything into `pkgs/by-name`. + // + // For now this isn't our problem, but in the future we + // might have another check to enforce that evaluation must not be broken. + // + // The alternative of assuming that failing attributes would have been fit for `pkgs/by-name` + // has the problem that if a package evaluation gets broken temporarily, + // fixing it requires a move to pkgs/by-name, which could happen more + // often and isn't really justified. + if let EvalSuccess(AttributeInfo { + // We're only interested in attributes that are attribute sets (which includes + // derivations). Anything else can't be in `pkgs/by-name`. + attribute_variant: AttributeVariant::AttributeSet { + // Indeed, we only care about derivations, non-derivation attribute sets can't be + // in `pkgs/by-name` + is_derivation: true, + // Of the two definition variants, really only the manual one makes sense here. + // Special cases are: + // - Manual aliases to auto-called packages are not treated as manual definitions, + // due to limitations in the semantic callPackage detection. So those should be + // ignored. + // - Manual definitions using the internal _internalCallByNamePackageFile are + // not treated as manual definitions, since _internalCallByNamePackageFile is + // used to detect automatic ones. We can't distinguish from the above case, so we + // just need to ignore this one too, even if that internal attribute should never + // be called manually. + definition_variant: DefinitionVariant::ManualDefinition { is_semantic_call_package } + }, + // We need the location of the manual definition, because otherwise + // we can't figure out whether it's a syntactic callPackage + location: Some(location), + }) = non_by_name_attribute { + + // Parse the Nix file in the location and figure out whether it's an + // attribute definition of the form `= callPackage <arg1> <arg2>`, + // returning the arguments if so. + let optional_syntactic_call_package = nix_file_store + .get(&location.file)? + .call_package_argument_info_at( + location.line, + location.column, + // Passing the Nixpkgs path here both checks that the <arg1> is within Nixpkgs, and + // strips the absolute Nixpkgs path from it, such that + // syntactic_call_package.relative_path is relative to Nixpkgs + nixpkgs_path + )?; + + // At this point, we completed two different checks for whether it's a + // `callPackage` + match (is_semantic_call_package, optional_syntactic_call_package) { + // Something like `<attr> = { }` + (false, None) + // Something like `<attr> = pythonPackages.callPackage ...` + | (false, Some(_)) + // Something like `<attr> = bar` where `bar = pkgs.callPackage ...` + | (true, None) => { + // In all of these cases, it's not possible to migrate the package to `pkgs/by-name` + NonApplicable + } + // Something like `<attr> = pkgs.callPackage ...` + (true, Some(syntactic_call_package)) => { + // It's only possible to migrate such a definitions if.. + match syntactic_call_package.relative_path { + Some(ref rel_path) if rel_path.starts_with(utils::BASE_SUBPATH) => { + // ..the path is not already within `pkgs/by-name` like + // + // foo-variant = callPackage ../by-name/fo/foo/package.nix { + // someFlag = true; + // } + // + // While such definitions could be moved to `pkgs/by-name` by using + // `.override { someFlag = true; }` instead, this changes the semantics in + // relation with overlays, so migration is generally not possible. + // + // See also "package variants" in RFC 140: + // https://github.com/NixOS/rfcs/blob/master/rfcs/0140-simple-package-paths.md#package-variants + NonApplicable + } + _ => { + // Otherwise, the path is outside `pkgs/by-name`, which means it can be + // migrated + Loose(syntactic_call_package) + } + } + } + } + } else { + // This catches all the cases not matched by the above `if let`, falling back to not being + // able to migrate such attributes + NonApplicable + }; + Ok(Success(ratchet::Package { + // Packages being checked in this function _always_ need a manual definition, because + // they're not using `pkgs/by-name` which would allow avoiding it. + // so instead of repeating ourselves all the time to define `manual_definition`, + // just set it once at the end here + manual_definition: Tight, + uses_by_name, + })) +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/main.rs b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/main.rs new file mode 100644 index 000000000000..0d0ddcd7e632 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/main.rs @@ -0,0 +1,266 @@ +use crate::nix_file::NixFileStore; +mod eval; +mod nix_file; +mod nixpkgs_problem; +mod ratchet; +mod references; +mod structure; +mod utils; +mod validation; + +use crate::structure::check_structure; +use crate::validation::Validation::Failure; +use crate::validation::Validation::Success; +use anyhow::Context; +use clap::Parser; +use colored::Colorize; +use std::io; +use std::path::{Path, PathBuf}; +use std::process::ExitCode; + +/// Program to check the validity of pkgs/by-name +/// +/// This CLI interface may be changed over time if the CI workflow making use of +/// it is adjusted to deal with the change appropriately. +/// +/// Exit code: +/// - `0`: If the validation is successful +/// - `1`: If the validation is not successful +/// - `2`: If an unexpected I/O error occurs +/// +/// Standard error: +/// - Informative messages +/// - Detected problems if validation is not successful +#[derive(Parser, Debug)] +#[command(about, verbatim_doc_comment)] +pub struct Args { + /// Path to the main Nixpkgs to check. + /// For PRs, this should be set to a checkout of the PR branch. + nixpkgs: PathBuf, + + /// Path to the base Nixpkgs to run ratchet checks against. + /// For PRs, this should be set to a checkout of the PRs base branch. + #[arg(long)] + base: PathBuf, +} + +fn main() -> ExitCode { + let args = Args::parse(); + match process(&args.base, &args.nixpkgs, false, &mut io::stderr()) { + Ok(true) => { + eprintln!("{}", "Validated successfully".green()); + ExitCode::SUCCESS + } + Ok(false) => { + eprintln!("{}", "Validation failed, see above errors".yellow()); + ExitCode::from(1) + } + Err(e) => { + eprintln!("{} {:#}", "I/O error: ".yellow(), e); + ExitCode::from(2) + } + } +} + +/// Does the actual work. This is the abstraction used both by `main` and the tests. +/// +/// # Arguments +/// - `base_nixpkgs`: Path to the base Nixpkgs to run ratchet checks against. +/// - `main_nixpkgs`: Path to the main Nixpkgs to check. +/// - `keep_nix_path`: Whether the value of the NIX_PATH environment variable should be kept for +/// the evaluation stage, allowing its contents to be accessed. +/// This is used to allow the tests to access e.g. the mock-nixpkgs.nix file +/// - `error_writer`: An `io::Write` value to write validation errors to, if any. +/// +/// # Return value +/// - `Err(e)` if an I/O-related error `e` occurred. +/// - `Ok(false)` if there are problems, all of which will be written to `error_writer`. +/// - `Ok(true)` if there are no problems +pub fn process<W: io::Write>( + base_nixpkgs: &Path, + main_nixpkgs: &Path, + keep_nix_path: bool, + error_writer: &mut W, +) -> anyhow::Result<bool> { + // Check the main Nixpkgs first + let main_result = check_nixpkgs(main_nixpkgs, keep_nix_path, error_writer)?; + let check_result = main_result.result_map(|nixpkgs_version| { + // If the main Nixpkgs doesn't have any problems, run the ratchet checks against the base + // Nixpkgs + check_nixpkgs(base_nixpkgs, keep_nix_path, error_writer)?.result_map( + |base_nixpkgs_version| { + Ok(ratchet::Nixpkgs::compare( + base_nixpkgs_version, + nixpkgs_version, + )) + }, + ) + })?; + + match check_result { + Failure(errors) => { + for error in errors { + writeln!(error_writer, "{}", error.to_string().red())? + } + Ok(false) + } + Success(()) => Ok(true), + } +} + +/// Checks whether the pkgs/by-name structure in Nixpkgs is valid. +/// +/// This does not include ratchet checks, see ../README.md#ratchet-checks +/// Instead a `ratchet::Nixpkgs` value is returned, whose `compare` method allows performing the +/// ratchet check against another result. +pub fn check_nixpkgs<W: io::Write>( + nixpkgs_path: &Path, + keep_nix_path: bool, + error_writer: &mut W, +) -> validation::Result<ratchet::Nixpkgs> { + let mut nix_file_store = NixFileStore::default(); + + Ok({ + let nixpkgs_path = nixpkgs_path.canonicalize().with_context(|| { + format!( + "Nixpkgs path {} could not be resolved", + nixpkgs_path.display() + ) + })?; + + if !nixpkgs_path.join(utils::BASE_SUBPATH).exists() { + writeln!( + error_writer, + "Given Nixpkgs path does not contain a {} subdirectory, no check necessary.", + utils::BASE_SUBPATH + )?; + Success(ratchet::Nixpkgs::default()) + } else { + check_structure(&nixpkgs_path, &mut nix_file_store)?.result_map(|package_names| + // Only if we could successfully parse the structure, we do the evaluation checks + eval::check_values(&nixpkgs_path, &mut nix_file_store, package_names, keep_nix_path))? + } + }) +} + +#[cfg(test)] +mod tests { + use crate::process; + use crate::utils; + use anyhow::Context; + use std::fs; + use std::path::Path; + use tempfile::{tempdir_in, TempDir}; + + #[test] + fn tests_dir() -> anyhow::Result<()> { + for entry in Path::new("tests").read_dir()? { + let entry = entry?; + let path = entry.path(); + let name = entry.file_name().to_string_lossy().into_owned(); + + if !path.is_dir() { + continue; + } + + let expected_errors = + fs::read_to_string(path.join("expected")).unwrap_or(String::new()); + + test_nixpkgs(&name, &path, &expected_errors)?; + } + Ok(()) + } + + // tempfile::tempdir needs to be wrapped in temp_env lock + // because it accesses TMPDIR environment variable. + pub fn tempdir() -> anyhow::Result<TempDir> { + let empty_list: [(&str, Option<&str>); 0] = []; + Ok(temp_env::with_vars(empty_list, tempfile::tempdir)?) + } + + // We cannot check case-conflicting files into Nixpkgs (the channel would fail to + // build), so we generate the case-conflicting file instead. + #[test] + fn test_case_sensitive() -> anyhow::Result<()> { + let temp_nixpkgs = tempdir()?; + let path = temp_nixpkgs.path(); + + if is_case_insensitive_fs(&path)? { + eprintln!("We're on a case-insensitive filesystem, skipping case-sensitivity test"); + return Ok(()); + } + + let base = path.join(utils::BASE_SUBPATH); + + fs::create_dir_all(base.join("fo/foo"))?; + fs::write(base.join("fo/foo/package.nix"), "{ someDrv }: someDrv")?; + + fs::create_dir_all(base.join("fo/foO"))?; + fs::write(base.join("fo/foO/package.nix"), "{ someDrv }: someDrv")?; + + test_nixpkgs( + "case_sensitive", + &path, + "pkgs/by-name/fo: Duplicate case-sensitive package directories \"foO\" and \"foo\".\n", + )?; + + Ok(()) + } + + /// Tests symlinked temporary directories. + /// This is needed because on darwin, `/tmp` is a symlink to `/private/tmp`, and Nix's + /// restrict-eval doesn't also allow access to the canonical path when you allow the + /// non-canonical one. + /// + /// The error if we didn't do this would look like this: + /// error: access to canonical path '/private/var/folders/[...]/.tmpFbcNO0' is forbidden in restricted mode + #[test] + fn test_symlinked_tmpdir() -> anyhow::Result<()> { + // Create a directory with two entries: + // - actual (dir) + // - symlinked -> actual (symlink) + let temp_root = tempdir()?; + fs::create_dir(temp_root.path().join("actual"))?; + std::os::unix::fs::symlink("actual", temp_root.path().join("symlinked"))?; + let tmpdir = temp_root.path().join("symlinked"); + + temp_env::with_var("TMPDIR", Some(&tmpdir), || { + test_nixpkgs("symlinked_tmpdir", Path::new("tests/success"), "") + }) + } + + fn test_nixpkgs(name: &str, path: &Path, expected_errors: &str) -> anyhow::Result<()> { + let base_path = path.join("base"); + let base_nixpkgs = if base_path.exists() { + base_path.as_path() + } else { + Path::new("tests/empty-base") + }; + + // We don't want coloring to mess up the tests + let writer = temp_env::with_var("NO_COLOR", Some("1"), || -> anyhow::Result<_> { + let mut writer = vec![]; + process(base_nixpkgs, &path, true, &mut writer) + .with_context(|| format!("Failed test case {name}"))?; + Ok(writer) + })?; + + let actual_errors = String::from_utf8_lossy(&writer); + + if actual_errors != expected_errors { + panic!( + "Failed test case {name}, expected these errors:\n\n{}\n\nbut got these:\n\n{}", + expected_errors, actual_errors + ); + } + Ok(()) + } + + /// Check whether a path is in a case-insensitive filesystem + fn is_case_insensitive_fs(path: &Path) -> anyhow::Result<bool> { + let dir = tempdir_in(path)?; + let base = dir.path(); + fs::write(base.join("aaa"), "")?; + Ok(base.join("AAA").exists()) + } +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/nix_file.rs b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/nix_file.rs new file mode 100644 index 000000000000..836c5e2dcdda --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/nix_file.rs @@ -0,0 +1,510 @@ +//! This is a utility module for interacting with the syntax of Nix files + +use crate::utils::LineIndex; +use anyhow::Context; +use rnix::ast; +use rnix::ast::Expr; +use rnix::ast::HasEntry; +use rnix::SyntaxKind; +use rowan::ast::AstNode; +use rowan::TextSize; +use rowan::TokenAtOffset; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::fs::read_to_string; +use std::path::Path; +use std::path::PathBuf; + +/// A structure to store parse results of Nix files in memory, +/// making sure that the same file never has to be parsed twice +#[derive(Default)] +pub struct NixFileStore { + entries: HashMap<PathBuf, NixFile>, +} + +impl NixFileStore { + /// Get the store entry for a Nix file if it exists, otherwise parse the file, insert it into + /// the store, and return the value + /// + /// Note that this function only gives an anyhow::Result::Err for I/O errors. + /// A parse error is anyhow::Result::Ok(Result::Err(error)) + pub fn get(&mut self, path: &Path) -> anyhow::Result<&NixFile> { + match self.entries.entry(path.to_owned()) { + Entry::Occupied(entry) => Ok(entry.into_mut()), + Entry::Vacant(entry) => Ok(entry.insert(NixFile::new(path)?)), + } + } +} + +/// A structure for storing a successfully parsed Nix file +pub struct NixFile { + /// The parent directory of the Nix file, for more convenient error handling + pub parent_dir: PathBuf, + /// The path to the file itself, for errors + pub path: PathBuf, + pub syntax_root: rnix::Root, + pub line_index: LineIndex, +} + +impl NixFile { + /// Creates a new NixFile, failing for I/O or parse errors + fn new(path: impl AsRef<Path>) -> anyhow::Result<NixFile> { + let Some(parent_dir) = path.as_ref().parent() else { + anyhow::bail!("Could not get parent of path {}", path.as_ref().display()) + }; + + let contents = read_to_string(&path) + .with_context(|| format!("Could not read file {}", path.as_ref().display()))?; + let line_index = LineIndex::new(&contents); + + // NOTE: There's now another Nixpkgs CI check to make sure all changed Nix files parse + // correctly, though that uses mainline Nix instead of rnix, so it doesn't give the same + // errors. In the future we should unify these two checks, ideally moving the other CI + // check into this tool as well and checking for both mainline Nix and rnix. + rnix::Root::parse(&contents) + // rnix's ::ok returns Result<_, _> , so no error is thrown away like it would be with + // std::result's ::ok + .ok() + .map(|syntax_root| NixFile { + parent_dir: parent_dir.to_path_buf(), + path: path.as_ref().to_owned(), + syntax_root, + line_index, + }) + .with_context(|| format!("Could not parse file {} with rnix", path.as_ref().display())) + } +} + +/// Information about callPackage arguments +#[derive(Debug, PartialEq)] +pub struct CallPackageArgumentInfo { + /// The relative path of the first argument, or `None` if it's not a path. + pub relative_path: Option<PathBuf>, + /// Whether the second argument is an empty attribute set + pub empty_arg: bool, +} + +impl NixFile { + /// Returns information about callPackage arguments for an attribute at a specific line/column + /// index. + /// If the location is not of the form `<attr> = callPackage <arg1> <arg2>;`, `None` is + /// returned. + /// This function only returns `Err` for problems that can't be caused by the Nix contents, + /// but rather problems in this programs code itself. + /// + /// This is meant to be used with the location returned from `builtins.unsafeGetAttrPos`, e.g.: + /// - Create file `default.nix` with contents + /// ```nix + /// self: { + /// foo = self.callPackage ./default.nix { }; + /// } + /// ``` + /// - Evaluate + /// ```nix + /// builtins.unsafeGetAttrPos "foo" (import ./default.nix { }) + /// ``` + /// results in `{ file = ./default.nix; line = 2; column = 3; }` + /// - Get the NixFile for `.file` from a `NixFileStore` + /// - Call this function with `.line`, `.column` and `relative_to` as the (absolute) current directory + /// + /// You'll get back + /// ```rust + /// Some(CallPackageArgumentInfo { path = Some("default.nix"), empty_arg: true }) + /// ``` + /// + /// Note that this also returns the same for `pythonPackages.callPackage`. It doesn't make an + /// attempt at distinguishing this. + pub fn call_package_argument_info_at( + &self, + line: usize, + column: usize, + relative_to: &Path, + ) -> anyhow::Result<Option<CallPackageArgumentInfo>> { + let Some(attrpath_value) = self.attrpath_value_at(line, column)? else { + return Ok(None); + }; + self.attrpath_value_call_package_argument_info(attrpath_value, relative_to) + } + + // Internal function mainly to make it independently testable + fn attrpath_value_at( + &self, + line: usize, + column: usize, + ) -> anyhow::Result<Option<ast::AttrpathValue>> { + let index = self.line_index.fromlinecolumn(line, column); + + let token_at_offset = self + .syntax_root + .syntax() + .token_at_offset(TextSize::from(index as u32)); + + // The token_at_offset function takes indices to mean a location _between_ characters, + // which in this case is some spacing followed by the attribute name: + // + // foo = 10; + // /\ + // This is the token offset, we get both the (newline + indentation) on the left side, + // and the attribute name on the right side. + let TokenAtOffset::Between(_space, token) = token_at_offset else { + anyhow::bail!("Line {line} column {column} in {} is not the start of a token, but rather {token_at_offset:?}", self.path.display()) + }; + + // token looks like "foo" + let Some(node) = token.parent() else { + anyhow::bail!( + "Token on line {line} column {column} in {} does not have a parent node: {token:?}", + self.path.display() + ) + }; + + // node looks like "foo" + let Some(attrpath_node) = node.parent() else { + anyhow::bail!( + "Node in {} does not have a parent node: {node:?}", + self.path.display() + ) + }; + + if attrpath_node.kind() != SyntaxKind::NODE_ATTRPATH { + // This can happen for e.g. `inherit foo`, so definitely not a syntactic `callPackage` + return Ok(None); + } + // attrpath_node looks like "foo.bar" + let Some(attrpath_value_node) = attrpath_node.parent() else { + anyhow::bail!( + "Attribute path node in {} does not have a parent node: {attrpath_node:?}", + self.path.display() + ) + }; + + if !ast::AttrpathValue::can_cast(attrpath_value_node.kind()) { + anyhow::bail!( + "Node in {} is not an attribute path value node: {attrpath_value_node:?}", + self.path.display() + ) + } + // attrpath_value_node looks like "foo.bar = 10;" + + // unwrap is fine because we confirmed that we can cast with the above check. + // We could avoid this `unwrap` for a `clone`, since `cast` consumes the argument, + // but we still need it for the error message when the cast fails. + Ok(Some(ast::AttrpathValue::cast(attrpath_value_node).unwrap())) + } + + // Internal function mainly to make attrpath_value_at independently testable + fn attrpath_value_call_package_argument_info( + &self, + attrpath_value: ast::AttrpathValue, + relative_to: &Path, + ) -> anyhow::Result<Option<CallPackageArgumentInfo>> { + let Some(attrpath) = attrpath_value.attrpath() else { + anyhow::bail!("attrpath value node doesn't have an attrpath: {attrpath_value:?}") + }; + + // At this point we know it's something like `foo...bar = ...` + + if attrpath.attrs().count() > 1 { + // If the attribute path has multiple entries, the left-most entry is an attribute and + // can't be a `callPackage`. + // + // FIXME: `builtins.unsafeGetAttrPos` will return the same position for all attribute + // paths and we can't really know which one it is. We could have a case like + // `foo.bar = callPackage ... { }` and trying to determine if `bar` is a `callPackage`, + // where this is not correct. + // However, this case typically doesn't occur anyways, + // because top-level packages wouldn't be nested under an attribute set. + return Ok(None); + } + let Some(value) = attrpath_value.value() else { + anyhow::bail!("attrpath value node doesn't have a value: {attrpath_value:?}") + }; + + // At this point we know it's something like `foo = ...` + + let Expr::Apply(apply1) = value else { + // Not even a function call, instead something like `foo = null` + return Ok(None); + }; + let Some(function1) = apply1.lambda() else { + anyhow::bail!("apply node doesn't have a lambda: {apply1:?}") + }; + let Some(arg1) = apply1.argument() else { + anyhow::bail!("apply node doesn't have an argument: {apply1:?}") + }; + + // At this point we know it's something like `foo = <fun> <arg>`. + // For a callPackage, `<fun>` would be `callPackage ./file` and `<arg>` would be `{ }` + + let empty_arg = if let Expr::AttrSet(attrset) = arg1 { + // We can only statically determine whether the argument is empty if it's an attribute + // set _expression_, even though other kind of expressions could evaluate to an attribute + // set _value_. But this is what we want anyways + attrset.entries().next().is_none() + } else { + false + }; + + // Because callPackage takes two curried arguments, the first function needs to be a + // function call itself + let Expr::Apply(apply2) = function1 else { + // Not a callPackage, instead something like `foo = import ./foo` + return Ok(None); + }; + let Some(function2) = apply2.lambda() else { + anyhow::bail!("apply node doesn't have a lambda: {apply2:?}") + }; + let Some(arg2) = apply2.argument() else { + anyhow::bail!("apply node doesn't have an argument: {apply2:?}") + }; + + // At this point we know it's something like `foo = <fun2> <arg2> <arg1>`. + // For a callPackage, `<fun2>` would be `callPackage`, `<arg2>` would be `./file` + + // Check that <arg2> is a path expression + let path = if let Expr::Path(actual_path) = arg2 { + // Try to statically resolve the path and turn it into a nixpkgs-relative path + if let ResolvedPath::Within(p) = self.static_resolve_path(actual_path, relative_to) { + Some(p) + } else { + // We can't statically know an existing path inside Nixpkgs used as <arg2> + None + } + } else { + // <arg2> is not a path, but rather e.g. an inline expression + None + }; + + // Check that <fun2> is an identifier, or an attribute path with an identifier at the end + let ident = match function2 { + Expr::Ident(ident) => { + // This means it's something like `foo = callPackage <arg2> <arg1>` + ident + } + Expr::Select(select) => { + // This means it's something like `foo = self.callPackage <arg2> <arg1>`. + // We also end up here for e.g. `pythonPackages.callPackage`, but the + // callPackage-mocking method will take care of not triggering for this case. + + if select.default_expr().is_some() { + // Very odd case, but this would be `foo = self.callPackage or true ./test.nix {} + // (yes this is valid Nix code) + return Ok(None); + } + let Some(attrpath) = select.attrpath() else { + anyhow::bail!("select node doesn't have an attrpath: {select:?}") + }; + let Some(last) = attrpath.attrs().last() else { + // This case shouldn't be possible, it would be `foo = self. ./test.nix {}`, + // which shouldn't parse + anyhow::bail!("select node has an empty attrpath: {select:?}") + }; + if let ast::Attr::Ident(ident) = last { + ident + } else { + // Here it's something like `foo = self."callPackage" /test.nix {}` + // which we're not gonna bother with + return Ok(None); + } + } + // Any other expression we're not gonna treat as callPackage + _ => return Ok(None), + }; + + let Some(token) = ident.ident_token() else { + anyhow::bail!("ident node doesn't have a token: {ident:?}") + }; + + if token.text() == "callPackage" { + Ok(Some(CallPackageArgumentInfo { + relative_path: path, + empty_arg, + })) + } else { + Ok(None) + } + } +} + +/// The result of trying to statically resolve a Nix path expression +pub enum ResolvedPath { + /// Something like `./foo/${bar}/baz`, can't be known statically + Interpolated, + /// Something like `<nixpkgs>`, can't be known statically + SearchPath, + /// Path couldn't be resolved due to an IO error, + /// e.g. if the path doesn't exist or you don't have the right permissions + Unresolvable(std::io::Error), + /// The path is outside the given absolute path + Outside, + /// The path is within the given absolute path. + /// The `PathBuf` is the relative path under the given absolute path. + Within(PathBuf), +} + +impl NixFile { + /// Statically resolves a Nix path expression and checks that it's within an absolute path + /// + /// E.g. for the path expression `./bar.nix` in `./foo.nix` and an absolute path of the + /// current directory, the function returns `ResolvedPath::Within(./bar.nix)` + pub fn static_resolve_path(&self, node: ast::Path, relative_to: &Path) -> ResolvedPath { + if node.parts().count() != 1 { + // If there's more than 1 interpolated part, it's of the form `./foo/${bar}/baz`. + return ResolvedPath::Interpolated; + } + + let text = node.to_string(); + + if text.starts_with('<') { + // A search path like `<nixpkgs>`. There doesn't appear to be better way to detect + // these in rnix + return ResolvedPath::SearchPath; + } + + // Join the file's parent directory and the path expression, then resolve it + // FIXME: Expressions like `../../../../foo/bar/baz/qux` or absolute paths + // may resolve close to the original file, but may have left the relative_to. + // That should be checked more strictly + match self.parent_dir.join(Path::new(&text)).canonicalize() { + Err(resolution_error) => ResolvedPath::Unresolvable(resolution_error), + Ok(resolved) => { + // Check if it's within relative_to + match resolved.strip_prefix(relative_to) { + Err(_prefix_error) => ResolvedPath::Outside, + Ok(suffix) => ResolvedPath::Within(suffix.to_path_buf()), + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests; + use indoc::indoc; + + #[test] + fn detects_attributes() -> anyhow::Result<()> { + let temp_dir = tests::tempdir()?; + let file = temp_dir.path().join("file.nix"); + let contents = indoc! {r#" + toInherit: { + foo = 1; + "bar" = 2; + ${"baz"} = 3; + "${"qux"}" = 4; + + # A + quux + # B + = + # C + 5 + # D + ; + # E + + /**/quuux/**/=/**/5/**/;/*E*/ + + inherit toInherit; + } + "#}; + + std::fs::write(&file, contents)?; + + let nix_file = NixFile::new(&file)?; + + // These are builtins.unsafeGetAttrPos locations for the attributes + let cases = [ + (2, 3, Some("foo = 1;")), + (3, 3, Some(r#""bar" = 2;"#)), + (4, 3, Some(r#"${"baz"} = 3;"#)), + (5, 3, Some(r#""${"qux"}" = 4;"#)), + (8, 3, Some("quux\n # B\n =\n # C\n 5\n # D\n ;")), + (17, 7, Some("quuux/**/=/**/5/**/;")), + (19, 10, None), + ]; + + for (line, column, expected_result) in cases { + let actual_result = nix_file + .attrpath_value_at(line, column)? + .map(|node| node.to_string()); + assert_eq!(actual_result.as_deref(), expected_result); + } + + Ok(()) + } + + #[test] + fn detects_call_package() -> anyhow::Result<()> { + let temp_dir = tests::tempdir()?; + let file = temp_dir.path().join("file.nix"); + let contents = indoc! {r#" + self: with self; { + a.sub = null; + b = null; + c = import ./file.nix; + d = import ./file.nix { }; + e = pythonPackages.callPackage ./file.nix { }; + f = callPackage ./file.nix { }; + g = callPackage ({ }: { }) { }; + h = callPackage ./file.nix { x = 0; }; + i = callPackage ({ }: { }) (let in { }); + } + "#}; + + std::fs::write(&file, contents)?; + + let nix_file = NixFile::new(&file)?; + + let cases = [ + (2, None), + (3, None), + (4, None), + (5, None), + ( + 6, + Some(CallPackageArgumentInfo { + relative_path: Some(PathBuf::from("file.nix")), + empty_arg: true, + }), + ), + ( + 7, + Some(CallPackageArgumentInfo { + relative_path: Some(PathBuf::from("file.nix")), + empty_arg: true, + }), + ), + ( + 8, + Some(CallPackageArgumentInfo { + relative_path: None, + empty_arg: true, + }), + ), + ( + 9, + Some(CallPackageArgumentInfo { + relative_path: Some(PathBuf::from("file.nix")), + empty_arg: false, + }), + ), + ( + 10, + Some(CallPackageArgumentInfo { + relative_path: None, + empty_arg: false, + }), + ), + ]; + + for (line, expected_result) in cases { + let actual_result = nix_file.call_package_argument_info_at(line, 3, temp_dir.path())?; + assert_eq!(actual_result, expected_result); + } + + Ok(()) + } +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/nixpkgs_problem.rs b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/nixpkgs_problem.rs new file mode 100644 index 000000000000..e13869adaa41 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/nixpkgs_problem.rs @@ -0,0 +1,273 @@ +use crate::structure; +use crate::utils::PACKAGE_NIX_FILENAME; +use std::ffi::OsString; +use std::fmt; +use std::io; +use std::path::PathBuf; + +/// Any problem that can occur when checking Nixpkgs +pub enum NixpkgsProblem { + ShardNonDir { + relative_shard_path: PathBuf, + }, + InvalidShardName { + relative_shard_path: PathBuf, + shard_name: String, + }, + PackageNonDir { + relative_package_dir: PathBuf, + }, + CaseSensitiveDuplicate { + relative_shard_path: PathBuf, + first: OsString, + second: OsString, + }, + InvalidPackageName { + relative_package_dir: PathBuf, + package_name: String, + }, + IncorrectShard { + relative_package_dir: PathBuf, + correct_relative_package_dir: PathBuf, + }, + PackageNixNonExistent { + relative_package_dir: PathBuf, + }, + PackageNixDir { + relative_package_dir: PathBuf, + }, + UndefinedAttr { + relative_package_file: PathBuf, + package_name: String, + }, + WrongCallPackage { + relative_package_file: PathBuf, + package_name: String, + }, + NonDerivation { + relative_package_file: PathBuf, + package_name: String, + }, + OutsideSymlink { + relative_package_dir: PathBuf, + subpath: PathBuf, + }, + UnresolvableSymlink { + relative_package_dir: PathBuf, + subpath: PathBuf, + io_error: io::Error, + }, + PathInterpolation { + relative_package_dir: PathBuf, + subpath: PathBuf, + line: usize, + text: String, + }, + SearchPath { + relative_package_dir: PathBuf, + subpath: PathBuf, + line: usize, + text: String, + }, + OutsidePathReference { + relative_package_dir: PathBuf, + subpath: PathBuf, + line: usize, + text: String, + }, + UnresolvablePathReference { + relative_package_dir: PathBuf, + subpath: PathBuf, + line: usize, + text: String, + io_error: io::Error, + }, + MovedOutOfByName { + package_name: String, + call_package_path: Option<PathBuf>, + empty_arg: bool, + }, + NewPackageNotUsingByName { + package_name: String, + call_package_path: Option<PathBuf>, + empty_arg: bool, + }, + InternalCallPackageUsed { + attr_name: String, + }, + CannotDetermineAttributeLocation { + attr_name: String, + }, +} + +impl fmt::Display for NixpkgsProblem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + NixpkgsProblem::ShardNonDir { relative_shard_path } => + write!( + f, + "{}: This is a file, but it should be a directory.", + relative_shard_path.display(), + ), + NixpkgsProblem::InvalidShardName { relative_shard_path, shard_name } => + write!( + f, + "{}: Invalid directory name \"{shard_name}\", must be at most 2 ASCII characters consisting of a-z, 0-9, \"-\" or \"_\".", + relative_shard_path.display() + ), + NixpkgsProblem::PackageNonDir { relative_package_dir } => + write!( + f, + "{}: This path is a file, but it should be a directory.", + relative_package_dir.display(), + ), + NixpkgsProblem::CaseSensitiveDuplicate { relative_shard_path, first, second } => + write!( + f, + "{}: Duplicate case-sensitive package directories {first:?} and {second:?}.", + relative_shard_path.display(), + ), + NixpkgsProblem::InvalidPackageName { relative_package_dir, package_name } => + write!( + f, + "{}: Invalid package directory name \"{package_name}\", must be ASCII characters consisting of a-z, A-Z, 0-9, \"-\" or \"_\".", + relative_package_dir.display(), + ), + NixpkgsProblem::IncorrectShard { relative_package_dir, correct_relative_package_dir } => + write!( + f, + "{}: Incorrect directory location, should be {} instead.", + relative_package_dir.display(), + correct_relative_package_dir.display(), + ), + NixpkgsProblem::PackageNixNonExistent { relative_package_dir } => + write!( + f, + "{}: Missing required \"{PACKAGE_NIX_FILENAME}\" file.", + relative_package_dir.display(), + ), + NixpkgsProblem::PackageNixDir { relative_package_dir } => + write!( + f, + "{}: \"{PACKAGE_NIX_FILENAME}\" must be a file.", + relative_package_dir.display(), + ), + NixpkgsProblem::UndefinedAttr { relative_package_file, package_name } => + write!( + f, + "pkgs.{package_name}: This attribute is not defined but it should be defined automatically as {}", + relative_package_file.display() + ), + NixpkgsProblem::WrongCallPackage { relative_package_file, package_name } => + write!( + f, + "pkgs.{package_name}: This attribute is manually defined (most likely in pkgs/top-level/all-packages.nix), which is only allowed if the definition is of the form `pkgs.callPackage {} {{ ... }}` with a non-empty second argument.", + relative_package_file.display() + ), + NixpkgsProblem::NonDerivation { relative_package_file, package_name } => + write!( + f, + "pkgs.{package_name}: This attribute defined by {} is not a derivation", + relative_package_file.display() + ), + NixpkgsProblem::OutsideSymlink { relative_package_dir, subpath } => + write!( + f, + "{}: Path {} is a symlink pointing to a path outside the directory of that package.", + relative_package_dir.display(), + subpath.display(), + ), + NixpkgsProblem::UnresolvableSymlink { relative_package_dir, subpath, io_error } => + write!( + f, + "{}: Path {} is a symlink which cannot be resolved: {io_error}.", + relative_package_dir.display(), + subpath.display(), + ), + NixpkgsProblem::PathInterpolation { relative_package_dir, subpath, line, text } => + write!( + f, + "{}: File {} at line {line} contains the path expression \"{}\", which is not yet supported and may point outside the directory of that package.", + relative_package_dir.display(), + subpath.display(), + text + ), + NixpkgsProblem::SearchPath { relative_package_dir, subpath, line, text } => + write!( + f, + "{}: File {} at line {line} contains the nix search path expression \"{}\" which may point outside the directory of that package.", + relative_package_dir.display(), + subpath.display(), + text + ), + NixpkgsProblem::OutsidePathReference { relative_package_dir, subpath, line, text } => + write!( + f, + "{}: File {} at line {line} contains the path expression \"{}\" which may point outside the directory of that package.", + relative_package_dir.display(), + subpath.display(), + text, + ), + NixpkgsProblem::UnresolvablePathReference { relative_package_dir, subpath, line, text, io_error } => + write!( + f, + "{}: File {} at line {line} contains the path expression \"{}\" which cannot be resolved: {io_error}.", + relative_package_dir.display(), + subpath.display(), + text, + ), + NixpkgsProblem::MovedOutOfByName { package_name, call_package_path, empty_arg } => { + let call_package_arg = + if let Some(path) = &call_package_path { + format!("./{}", path.display()) + } else { + "...".into() + }; + if *empty_arg { + write!( + f, + "pkgs.{package_name}: This top-level package was previously defined in {}, but is now manually defined as `callPackage {call_package_arg} {{ }}` (e.g. in `pkgs/top-level/all-packages.nix`). Please move the package back and remove the manual `callPackage`.", + structure::relative_file_for_package(package_name).display(), + ) + } else { + // This can happen if users mistakenly assume that for custom arguments, + // pkgs/by-name can't be used. + write!( + f, + "pkgs.{package_name}: This top-level package was previously defined in {}, but is now manually defined as `callPackage {call_package_arg} {{ ... }}` (e.g. in `pkgs/top-level/all-packages.nix`). While the manual `callPackage` is still needed, it's not necessary to move the package files.", + structure::relative_file_for_package(package_name).display(), + ) + } + }, + NixpkgsProblem::NewPackageNotUsingByName { package_name, call_package_path, empty_arg } => { + let call_package_arg = + if let Some(path) = &call_package_path { + format!("./{}", path.display()) + } else { + "...".into() + }; + let extra = + if *empty_arg { + "Since the second `callPackage` argument is `{ }`, no manual `callPackage` (e.g. in `pkgs/top-level/all-packages.nix`) is needed anymore." + } else { + "Since the second `callPackage` argument is not `{ }`, the manual `callPackage` (e.g. in `pkgs/top-level/all-packages.nix`) is still needed." + }; + write!( + f, + "pkgs.{package_name}: This is a new top-level package of the form `callPackage {call_package_arg} {{ }}`. Please define it in {} instead. See `pkgs/by-name/README.md` for more details. {extra}", + structure::relative_file_for_package(package_name).display(), + ) + }, + NixpkgsProblem::InternalCallPackageUsed { attr_name } => + write!( + f, + "pkgs.{attr_name}: This attribute is defined using `_internalCallByNamePackageFile`, which is an internal function not intended for manual use.", + ), + NixpkgsProblem::CannotDetermineAttributeLocation { attr_name } => + write!( + f, + "pkgs.{attr_name}: Cannot determine the location of this attribute using `builtins.unsafeGetAttrPos`.", + ), + } + } +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/ratchet.rs b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/ratchet.rs new file mode 100644 index 000000000000..200bf92c516a --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/ratchet.rs @@ -0,0 +1,173 @@ +//! This module implements the ratchet checks, see ../README.md#ratchet-checks +//! +//! Each type has a `compare` method that validates the ratchet checks for that item. + +use crate::nix_file::CallPackageArgumentInfo; +use crate::nixpkgs_problem::NixpkgsProblem; +use crate::structure; +use crate::validation::{self, Validation, Validation::Success}; +use std::collections::HashMap; + +/// The ratchet value for the entirety of Nixpkgs. +#[derive(Default)] +pub struct Nixpkgs { + /// Sorted list of packages in package_map + pub package_names: Vec<String>, + /// The ratchet values for all packages + pub package_map: HashMap<String, Package>, +} + +impl Nixpkgs { + /// Validates the ratchet checks for Nixpkgs + pub fn compare(from: Self, to: Self) -> Validation<()> { + validation::sequence_( + // We only loop over the current attributes, + // we don't need to check ones that were removed + to.package_names.into_iter().map(|name| { + Package::compare(&name, from.package_map.get(&name), &to.package_map[&name]) + }), + ) + } +} + +/// The ratchet value for a top-level package +pub struct Package { + /// The ratchet value for the check for non-auto-called empty arguments + pub manual_definition: RatchetState<ManualDefinition>, + + /// The ratchet value for the check for new packages using pkgs/by-name + pub uses_by_name: RatchetState<UsesByName>, +} + +impl Package { + /// Validates the ratchet checks for a top-level package + pub fn compare(name: &str, optional_from: Option<&Self>, to: &Self) -> Validation<()> { + validation::sequence_([ + RatchetState::<ManualDefinition>::compare( + name, + optional_from.map(|x| &x.manual_definition), + &to.manual_definition, + ), + RatchetState::<UsesByName>::compare( + name, + optional_from.map(|x| &x.uses_by_name), + &to.uses_by_name, + ), + ]) + } +} + +/// The ratchet state of a generic ratchet check. +pub enum RatchetState<Ratchet: ToNixpkgsProblem> { + /// The ratchet is loose, it can be tightened more. + /// In other words, this is the legacy state we're trying to move away from. + /// Introducing new instances is not allowed but previous instances will continue to be allowed. + /// The `Context` is context for error messages in case a new instance of this state is + /// introduced + Loose(Ratchet::ToContext), + /// The ratchet is tight, it can't be tightened any further. + /// This is either because we already use the latest state, or because the ratchet isn't + /// relevant. + Tight, + /// This ratchet can't be applied. + /// State transitions from/to NonApplicable are always allowed + NonApplicable, +} + +/// A trait that can convert an attribute-specific error context into a NixpkgsProblem +pub trait ToNixpkgsProblem { + /// Context relating to the Nixpkgs that is being transitioned _to_ + type ToContext; + + /// How to convert an attribute-specific error context into a NixpkgsProblem + fn to_nixpkgs_problem( + name: &str, + optional_from: Option<()>, + to: &Self::ToContext, + ) -> NixpkgsProblem; +} + +impl<Context: ToNixpkgsProblem> RatchetState<Context> { + /// Compare the previous ratchet state of an attribute to the new state. + /// The previous state may be `None` in case the attribute is new. + fn compare(name: &str, optional_from: Option<&Self>, to: &Self) -> Validation<()> { + match (optional_from, to) { + // Loosening a ratchet is now allowed + (Some(RatchetState::Tight), RatchetState::Loose(loose_context)) => { + Context::to_nixpkgs_problem(name, Some(()), loose_context).into() + } + + // Introducing a loose ratchet is also not allowed + (None, RatchetState::Loose(loose_context)) => { + Context::to_nixpkgs_problem(name, None, loose_context).into() + } + + // Everything else is allowed, including: + // - Loose -> Loose (grandfathering policy for a loose ratchet) + // - -> Tight (always okay to keep or make the ratchet tight) + // - Anything involving NotApplicable, where we can't really make any good calls + _ => Success(()), + } + } +} + +/// The ratchet to check whether a top-level attribute has/needs +/// a manual definition, e.g. in all-packages.nix. +/// +/// This ratchet is only tight for attributes that: +/// - Are not defined in `pkgs/by-name`, and rely on a manual definition +/// - Are defined in `pkgs/by-name` without any manual definition, +/// (no custom argument overrides) +/// - Are defined with `pkgs/by-name` with a manual definition that can't be removed +/// because it provides custom argument overrides +/// +/// In comparison, this ratchet is loose for attributes that: +/// - Are defined in `pkgs/by-name` with a manual definition +/// that doesn't have any custom argument overrides +pub enum ManualDefinition {} + +impl ToNixpkgsProblem for ManualDefinition { + type ToContext = (); + + fn to_nixpkgs_problem( + name: &str, + _optional_from: Option<()>, + _to: &Self::ToContext, + ) -> NixpkgsProblem { + NixpkgsProblem::WrongCallPackage { + relative_package_file: structure::relative_file_for_package(name), + package_name: name.to_owned(), + } + } +} + +/// The ratchet value of an attribute +/// for the check that new packages use pkgs/by-name +/// +/// This checks that all new package defined using callPackage must be defined via pkgs/by-name +/// It also checks that once a package uses pkgs/by-name, it can't switch back to all-packages.nix +pub enum UsesByName {} + +impl ToNixpkgsProblem for UsesByName { + type ToContext = CallPackageArgumentInfo; + + fn to_nixpkgs_problem( + name: &str, + optional_from: Option<()>, + to: &Self::ToContext, + ) -> NixpkgsProblem { + if let Some(()) = optional_from { + NixpkgsProblem::MovedOutOfByName { + package_name: name.to_owned(), + call_package_path: to.relative_path.clone(), + empty_arg: to.empty_arg, + } + } else { + NixpkgsProblem::NewPackageNotUsingByName { + package_name: name.to_owned(), + call_package_path: to.relative_path.clone(), + empty_arg: to.empty_arg, + } + } + } +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/references.rs b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/references.rs new file mode 100644 index 000000000000..169e996300ba --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/references.rs @@ -0,0 +1,174 @@ +use crate::nixpkgs_problem::NixpkgsProblem; +use crate::utils; +use crate::validation::{self, ResultIteratorExt, Validation::Success}; +use crate::NixFileStore; + +use anyhow::Context; +use rowan::ast::AstNode; +use std::ffi::OsStr; +use std::path::Path; + +/// Check that every package directory in pkgs/by-name doesn't link to outside that directory. +/// Both symlinks and Nix path expressions are checked. +pub fn check_references( + nix_file_store: &mut NixFileStore, + relative_package_dir: &Path, + absolute_package_dir: &Path, +) -> validation::Result<()> { + // The first subpath to check is the package directory itself, which we can represent as an + // empty path, since the absolute package directory gets prepended to this. + // We don't use `./.` to keep the error messages cleaner + // (there's no canonicalisation going on underneath) + let subpath = Path::new(""); + check_path( + nix_file_store, + relative_package_dir, + absolute_package_dir, + subpath, + ) + .with_context(|| { + format!( + "While checking the references in package directory {}", + relative_package_dir.display() + ) + }) +} + +/// Checks for a specific path to not have references outside +/// +/// The subpath is the relative path within the package directory we're currently checking. +/// A relative path so that the error messages don't get absolute paths (which are messy in CI). +/// The absolute package directory gets prepended before doing anything with it though. +fn check_path( + nix_file_store: &mut NixFileStore, + relative_package_dir: &Path, + absolute_package_dir: &Path, + subpath: &Path, +) -> validation::Result<()> { + let path = absolute_package_dir.join(subpath); + + Ok(if path.is_symlink() { + // Check whether the symlink resolves to outside the package directory + match path.canonicalize() { + Ok(target) => { + // No need to handle the case of it being inside the directory, since we scan through the + // entire directory recursively anyways + if let Err(_prefix_error) = target.strip_prefix(absolute_package_dir) { + NixpkgsProblem::OutsideSymlink { + relative_package_dir: relative_package_dir.to_path_buf(), + subpath: subpath.to_path_buf(), + } + .into() + } else { + Success(()) + } + } + Err(io_error) => NixpkgsProblem::UnresolvableSymlink { + relative_package_dir: relative_package_dir.to_path_buf(), + subpath: subpath.to_path_buf(), + io_error, + } + .into(), + } + } else if path.is_dir() { + // Recursively check each entry + validation::sequence_( + utils::read_dir_sorted(&path)? + .into_iter() + .map(|entry| { + check_path( + nix_file_store, + relative_package_dir, + absolute_package_dir, + &subpath.join(entry.file_name()), + ) + }) + .collect_vec() + .with_context(|| format!("Error while recursing into {}", subpath.display()))?, + ) + } else if path.is_file() { + // Only check Nix files + if let Some(ext) = path.extension() { + if ext == OsStr::new("nix") { + check_nix_file( + nix_file_store, + relative_package_dir, + absolute_package_dir, + subpath, + ) + .with_context(|| format!("Error while checking Nix file {}", subpath.display()))? + } else { + Success(()) + } + } else { + Success(()) + } + } else { + // This should never happen, git doesn't support other file types + anyhow::bail!("Unsupported file type for path {}", subpath.display()); + }) +} + +/// Check whether a nix file contains path expression references pointing outside the package +/// directory +fn check_nix_file( + nix_file_store: &mut NixFileStore, + relative_package_dir: &Path, + absolute_package_dir: &Path, + subpath: &Path, +) -> validation::Result<()> { + let path = absolute_package_dir.join(subpath); + + let nix_file = nix_file_store.get(&path)?; + + Ok(validation::sequence_( + nix_file.syntax_root.syntax().descendants().map(|node| { + let text = node.text().to_string(); + let line = nix_file.line_index.line(node.text_range().start().into()); + + // We're only interested in Path expressions + let Some(path) = rnix::ast::Path::cast(node) else { + return Success(()); + }; + + use crate::nix_file::ResolvedPath; + + match nix_file.static_resolve_path(path, absolute_package_dir) { + ResolvedPath::Interpolated => NixpkgsProblem::PathInterpolation { + relative_package_dir: relative_package_dir.to_path_buf(), + subpath: subpath.to_path_buf(), + line, + text, + } + .into(), + ResolvedPath::SearchPath => NixpkgsProblem::SearchPath { + relative_package_dir: relative_package_dir.to_path_buf(), + subpath: subpath.to_path_buf(), + line, + text, + } + .into(), + ResolvedPath::Outside => NixpkgsProblem::OutsidePathReference { + relative_package_dir: relative_package_dir.to_path_buf(), + subpath: subpath.to_path_buf(), + line, + text, + } + .into(), + ResolvedPath::Unresolvable(e) => NixpkgsProblem::UnresolvablePathReference { + relative_package_dir: relative_package_dir.to_path_buf(), + subpath: subpath.to_path_buf(), + line, + text, + io_error: e, + } + .into(), + ResolvedPath::Within(..) => { + // No need to handle the case of it being inside the directory, since we scan through the + // entire directory recursively anyways + Success(()) + } + } + }), + )) +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/structure.rs b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/structure.rs new file mode 100644 index 000000000000..9b615dd9969a --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/structure.rs @@ -0,0 +1,182 @@ +use crate::nixpkgs_problem::NixpkgsProblem; +use crate::references; +use crate::utils; +use crate::utils::{BASE_SUBPATH, PACKAGE_NIX_FILENAME}; +use crate::validation::{self, ResultIteratorExt, Validation::Success}; +use crate::NixFileStore; +use itertools::concat; +use lazy_static::lazy_static; +use regex::Regex; +use std::fs::DirEntry; +use std::path::{Path, PathBuf}; + +lazy_static! { + static ref SHARD_NAME_REGEX: Regex = Regex::new(r"^[a-z0-9_-]{1,2}$").unwrap(); + static ref PACKAGE_NAME_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9_-]+$").unwrap(); +} + +// Some utility functions for the basic structure + +pub fn shard_for_package(package_name: &str) -> String { + package_name.to_lowercase().chars().take(2).collect() +} + +pub fn relative_dir_for_shard(shard_name: &str) -> PathBuf { + PathBuf::from(format!("{BASE_SUBPATH}/{shard_name}")) +} + +pub fn relative_dir_for_package(package_name: &str) -> PathBuf { + relative_dir_for_shard(&shard_for_package(package_name)).join(package_name) +} + +pub fn relative_file_for_package(package_name: &str) -> PathBuf { + relative_dir_for_package(package_name).join(PACKAGE_NIX_FILENAME) +} + +/// Check the structure of Nixpkgs, returning the attribute names that are defined in +/// `pkgs/by-name` +pub fn check_structure( + path: &Path, + nix_file_store: &mut NixFileStore, +) -> validation::Result<Vec<String>> { + let base_dir = path.join(BASE_SUBPATH); + + let shard_results = utils::read_dir_sorted(&base_dir)? + .into_iter() + .map(|shard_entry| -> validation::Result<_> { + let shard_path = shard_entry.path(); + let shard_name = shard_entry.file_name().to_string_lossy().into_owned(); + let relative_shard_path = relative_dir_for_shard(&shard_name); + + Ok(if shard_name == "README.md" { + // README.md is allowed to be a file and not checked + + Success(vec![]) + } else if !shard_path.is_dir() { + NixpkgsProblem::ShardNonDir { + relative_shard_path: relative_shard_path.clone(), + } + .into() + // we can't check for any other errors if it's a file, since there's no subdirectories to check + } else { + let shard_name_valid = SHARD_NAME_REGEX.is_match(&shard_name); + let result = if !shard_name_valid { + NixpkgsProblem::InvalidShardName { + relative_shard_path: relative_shard_path.clone(), + shard_name: shard_name.clone(), + } + .into() + } else { + Success(()) + }; + + let entries = utils::read_dir_sorted(&shard_path)?; + + let duplicate_results = entries + .iter() + .zip(entries.iter().skip(1)) + .filter(|(l, r)| { + l.file_name().to_ascii_lowercase() == r.file_name().to_ascii_lowercase() + }) + .map(|(l, r)| { + NixpkgsProblem::CaseSensitiveDuplicate { + relative_shard_path: relative_shard_path.clone(), + first: l.file_name(), + second: r.file_name(), + } + .into() + }); + + let result = result.and(validation::sequence_(duplicate_results)); + + let package_results = entries + .into_iter() + .map(|package_entry| { + check_package( + nix_file_store, + path, + &shard_name, + shard_name_valid, + package_entry, + ) + }) + .collect_vec()?; + + result.and(validation::sequence(package_results)) + }) + }) + .collect_vec()?; + + // Combine the package names conatained within each shard into a longer list + Ok(validation::sequence(shard_results).map(concat)) +} + +fn check_package( + nix_file_store: &mut NixFileStore, + path: &Path, + shard_name: &str, + shard_name_valid: bool, + package_entry: DirEntry, +) -> validation::Result<String> { + let package_path = package_entry.path(); + let package_name = package_entry.file_name().to_string_lossy().into_owned(); + let relative_package_dir = PathBuf::from(format!("{BASE_SUBPATH}/{shard_name}/{package_name}")); + + Ok(if !package_path.is_dir() { + NixpkgsProblem::PackageNonDir { + relative_package_dir: relative_package_dir.clone(), + } + .into() + } else { + let package_name_valid = PACKAGE_NAME_REGEX.is_match(&package_name); + let result = if !package_name_valid { + NixpkgsProblem::InvalidPackageName { + relative_package_dir: relative_package_dir.clone(), + package_name: package_name.clone(), + } + .into() + } else { + Success(()) + }; + + let correct_relative_package_dir = relative_dir_for_package(&package_name); + let result = result.and(if relative_package_dir != correct_relative_package_dir { + // Only show this error if we have a valid shard and package name + // Because if one of those is wrong, you should fix that first + if shard_name_valid && package_name_valid { + NixpkgsProblem::IncorrectShard { + relative_package_dir: relative_package_dir.clone(), + correct_relative_package_dir: correct_relative_package_dir.clone(), + } + .into() + } else { + Success(()) + } + } else { + Success(()) + }); + + let package_nix_path = package_path.join(PACKAGE_NIX_FILENAME); + let result = result.and(if !package_nix_path.exists() { + NixpkgsProblem::PackageNixNonExistent { + relative_package_dir: relative_package_dir.clone(), + } + .into() + } else if package_nix_path.is_dir() { + NixpkgsProblem::PackageNixDir { + relative_package_dir: relative_package_dir.clone(), + } + .into() + } else { + Success(()) + }); + + let result = result.and(references::check_references( + nix_file_store, + &relative_package_dir, + &path.join(&relative_package_dir), + )?); + + result.map(|_| package_name.clone()) + }) +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/utils.rs b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/utils.rs new file mode 100644 index 000000000000..9a5d12748918 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/utils.rs @@ -0,0 +1,95 @@ +use anyhow::Context; +use std::fs; +use std::io; +use std::path::Path; + +pub const BASE_SUBPATH: &str = "pkgs/by-name"; +pub const PACKAGE_NIX_FILENAME: &str = "package.nix"; + +/// Deterministic file listing so that tests are reproducible +pub fn read_dir_sorted(base_dir: &Path) -> anyhow::Result<Vec<fs::DirEntry>> { + let listing = base_dir + .read_dir() + .with_context(|| format!("Could not list directory {}", base_dir.display()))?; + let mut shard_entries = listing + .collect::<io::Result<Vec<_>>>() + .with_context(|| format!("Could not list directory {}", base_dir.display()))?; + shard_entries.sort_by_key(|entry| entry.file_name()); + Ok(shard_entries) +} + +/// A simple utility for calculating the line for a string offset. +/// This doesn't do any Unicode handling, though that probably doesn't matter +/// because newlines can't split up Unicode characters. Also this is only used +/// for error reporting +pub struct LineIndex { + /// Stores the indices of newlines + newlines: Vec<usize>, +} + +impl LineIndex { + pub fn new(s: &str) -> LineIndex { + let mut newlines = vec![]; + let mut index = 0; + // Iterates over all newline-split parts of the string, adding the index of the newline to + // the vec + for split in s.split_inclusive('\n') { + index += split.len(); + newlines.push(index - 1); + } + LineIndex { newlines } + } + + /// Returns the line number for a string index. + /// If the index points to a newline, returns the line number before the newline + pub fn line(&self, index: usize) -> usize { + match self.newlines.binary_search(&index) { + // +1 because lines are 1-indexed + Ok(x) => x + 1, + Err(x) => x + 1, + } + } + + /// Returns the string index for a line and column. + pub fn fromlinecolumn(&self, line: usize, column: usize) -> usize { + // If it's the 1th line, the column is the index + if line == 1 { + // But columns are 1-indexed + column - 1 + } else { + // For the nth line, we add the index of the (n-1)st newline to the column, + // and remove one more from the index since arrays are 0-indexed. + // Then add the 1-indexed column to get not the newline index itself, + // but rather the index of the position on the next line + self.newlines[line - 2] + column + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn line_index() { + let line_index = LineIndex::new("a\nbc\n\ndef\n"); + + let pairs = [ + (0, 1, 1), + (1, 1, 2), + (2, 2, 1), + (3, 2, 2), + (4, 2, 3), + (5, 3, 1), + (6, 4, 1), + (7, 4, 2), + (8, 4, 3), + (9, 4, 4), + ]; + + for (index, line, column) in pairs { + assert_eq!(line_index.line(index), line); + assert_eq!(line_index.fromlinecolumn(line, column), index); + } + } +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/validation.rs b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/validation.rs new file mode 100644 index 000000000000..b14bfb92eb2e --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/validation.rs @@ -0,0 +1,111 @@ +use crate::nixpkgs_problem::NixpkgsProblem; +use itertools::concat; +use itertools::{ + Either::{Left, Right}, + Itertools, +}; +use Validation::*; + +/// The validation result of a check. +/// Instead of exiting at the first failure, +/// this type can accumulate multiple failures. +/// This can be achieved using the functions `and`, `sequence` and `sequence_` +/// +/// This leans on https://hackage.haskell.org/package/validation +pub enum Validation<A> { + Failure(Vec<NixpkgsProblem>), + Success(A), +} + +impl<A> From<NixpkgsProblem> for Validation<A> { + /// Create a `Validation<A>` from a single check problem + fn from(value: NixpkgsProblem) -> Self { + Failure(vec![value]) + } +} + +/// A type alias representing the result of a check, either: +/// - Err(anyhow::Error): A fatal failure, typically I/O errors. +/// Such failures are not caused by the files in Nixpkgs. +/// This hints at a bug in the code or a problem with the deployment. +/// - Ok(Failure(Vec<NixpkgsProblem>)): A non-fatal validation problem with the Nixpkgs files. +/// Further checks can be run even with this result type. +/// Such problems can be fixed by changing the Nixpkgs files. +/// - Ok(Success(A)): A successful (potentially intermediate) result with an arbitrary value. +/// No fatal errors have occurred and no validation problems have been found with Nixpkgs. +pub type Result<A> = anyhow::Result<Validation<A>>; + +pub trait ResultIteratorExt<A, E>: Sized + Iterator<Item = std::result::Result<A, E>> { + fn collect_vec(self) -> std::result::Result<Vec<A>, E>; +} + +impl<I, A, E> ResultIteratorExt<A, E> for I +where + I: Sized + Iterator<Item = std::result::Result<A, E>>, +{ + /// A convenience version of `collect` specialised to a vector + fn collect_vec(self) -> std::result::Result<Vec<A>, E> { + self.collect() + } +} + +impl<A> Validation<A> { + /// Map a `Validation<A>` to a `Validation<B>` by applying a function to the + /// potentially contained value in case of success. + pub fn map<B>(self, f: impl FnOnce(A) -> B) -> Validation<B> { + match self { + Failure(err) => Failure(err), + Success(value) => Success(f(value)), + } + } + + /// Map a `Validation<A>` to a `Result<B>` by applying a function `A -> Result<B>` + /// only if there is a `Success` value + pub fn result_map<B>(self, f: impl FnOnce(A) -> Result<B>) -> Result<B> { + match self { + Failure(err) => Ok(Failure(err)), + Success(value) => f(value), + } + } +} + +impl Validation<()> { + /// Combine two validations, both of which need to be successful for the return value to be successful. + /// The `NixpkgsProblem`s of both sides are returned concatenated. + pub fn and<A>(self, other: Validation<A>) -> Validation<A> { + match (self, other) { + (Success(_), Success(right_value)) => Success(right_value), + (Failure(errors), Success(_)) => Failure(errors), + (Success(_), Failure(errors)) => Failure(errors), + (Failure(errors_l), Failure(errors_r)) => Failure(concat([errors_l, errors_r])), + } + } +} + +/// Combine many validations into a single one. +/// All given validations need to be successful in order for the returned validation to be successful, +/// in which case the returned validation value contains a `Vec` of each individual value. +/// Otherwise the `NixpkgsProblem`s of all validations are returned concatenated. +pub fn sequence<A>(check_results: impl IntoIterator<Item = Validation<A>>) -> Validation<Vec<A>> { + let (errors, values): (Vec<Vec<NixpkgsProblem>>, Vec<A>) = check_results + .into_iter() + .partition_map(|validation| match validation { + Failure(err) => Left(err), + Success(value) => Right(value), + }); + + // To combine the errors from the results we flatten all the error Vec's into a new Vec + // This is not very efficient, but doesn't matter because generally we should have no errors + let flattened_errors = errors.into_iter().concat(); + + if flattened_errors.is_empty() { + Success(values) + } else { + Failure(flattened_errors) + } +} + +/// Like `sequence`, but without any containing value, for convenience +pub fn sequence_(validations: impl IntoIterator<Item = Validation<()>>) -> Validation<()> { + sequence(validations).map(|_| ()) +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/aliases/aliases.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/aliases/aliases.nix new file mode 100644 index 000000000000..c70ec23478ea --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/aliases/aliases.nix @@ -0,0 +1,3 @@ +self: super: { + baz = self.foo; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/aliases/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/aliases/all-packages.nix new file mode 100644 index 000000000000..2cdfcef08fa6 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/aliases/all-packages.nix @@ -0,0 +1,3 @@ +self: super: { + bar = self.foo; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/aliases/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/aliases/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/aliases/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/aliases/pkgs/by-name/fo/foo/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/aliases/pkgs/by-name/fo/foo/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/aliases/pkgs/by-name/fo/foo/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/broken-autocall/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/broken-autocall/default.nix new file mode 100644 index 000000000000..bd4825f8bad8 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/broken-autocall/default.nix @@ -0,0 +1,4 @@ +args: +builtins.removeAttrs + (import <test-nixpkgs> { root = ./.; } args) + [ "foo" ] diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/broken-autocall/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/broken-autocall/expected new file mode 100644 index 000000000000..fff17c6c7cd5 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/broken-autocall/expected @@ -0,0 +1 @@ +pkgs.foo: This attribute is not defined but it should be defined automatically as pkgs/by-name/fo/foo/package.nix diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/broken-autocall/pkgs/by-name/fo/foo/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/broken-autocall/pkgs/by-name/fo/foo/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/broken-autocall/pkgs/by-name/fo/foo/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/callPackage-syntax/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/callPackage-syntax/all-packages.nix new file mode 100644 index 000000000000..306d719c9e9d --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/callPackage-syntax/all-packages.nix @@ -0,0 +1,7 @@ +self: super: { + set = self.callPackages ({ callPackage }: { + foo = callPackage ({ someDrv }: someDrv) { }; + }) { }; + + inherit (self.set) foo; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/callPackage-syntax/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/callPackage-syntax/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/callPackage-syntax/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/callPackage-syntax/pkgs/by-name/README.md b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/callPackage-syntax/pkgs/by-name/README.md new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/callPackage-syntax/pkgs/by-name/README.md diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/empty-base/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/empty-base/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/empty-base/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/empty-base/pkgs/by-name/README.md b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/empty-base/pkgs/by-name/README.md new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/empty-base/pkgs/by-name/README.md diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/incorrect-shard/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/incorrect-shard/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/incorrect-shard/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/incorrect-shard/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/incorrect-shard/expected new file mode 100644 index 000000000000..3627368c0ef0 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/incorrect-shard/expected @@ -0,0 +1 @@ +pkgs/by-name/aa/FOO: Incorrect directory location, should be pkgs/by-name/fo/FOO instead. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/incorrect-shard/pkgs/by-name/aa/FOO/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/incorrect-shard/pkgs/by-name/aa/FOO/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/incorrect-shard/pkgs/by-name/aa/FOO/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/all-packages.nix new file mode 100644 index 000000000000..3fbe2d5e51dc --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/all-packages.nix @@ -0,0 +1,4 @@ +self: super: { + foo = self._internalCallByNamePackageFile ./foo.nix; + bar = self._internalCallByNamePackageFile ./foo.nix; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/expected new file mode 100644 index 000000000000..404795ee5c79 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/expected @@ -0,0 +1 @@ +pkgs.foo: This attribute is defined using `_internalCallByNamePackageFile`, which is an internal function not intended for manual use. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/foo.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/foo.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/foo.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/pkgs/by-name/README.md b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/pkgs/by-name/README.md new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/pkgs/by-name/README.md diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/pkgs/by-name/fo/foo/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/pkgs/by-name/fo/foo/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/internalCallPackage/pkgs/by-name/fo/foo/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-package-name/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-package-name/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-package-name/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-package-name/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-package-name/expected new file mode 100644 index 000000000000..8c8eafdcb3d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-package-name/expected @@ -0,0 +1 @@ +pkgs/by-name/fo/fo@: Invalid package directory name "fo@", must be ASCII characters consisting of a-z, A-Z, 0-9, "-" or "_". diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-package-name/pkgs/by-name/fo/fo@/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-package-name/pkgs/by-name/fo/fo@/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-package-name/pkgs/by-name/fo/fo@/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-shard-name/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-shard-name/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-shard-name/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-shard-name/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-shard-name/expected new file mode 100644 index 000000000000..248aa8877966 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-shard-name/expected @@ -0,0 +1 @@ +pkgs/by-name/A: Invalid directory name "A", must be at most 2 ASCII characters consisting of a-z, 0-9, "-" or "_". diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-shard-name/pkgs/by-name/A/A/.git-keep b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-shard-name/pkgs/by-name/A/A/.git-keep new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-shard-name/pkgs/by-name/A/A/.git-keep diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-shard-name/pkgs/by-name/A/A/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-shard-name/pkgs/by-name/A/A/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/invalid-shard-name/pkgs/by-name/A/A/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/all-packages.nix new file mode 100644 index 000000000000..07b2caaab4e7 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/all-packages.nix @@ -0,0 +1,10 @@ +self: super: { + nonAttributeSet = self.callPackage ({ someDrv }: someDrv) { }; + nonCallPackage = self.callPackage ({ someDrv }: someDrv) { }; + internalCallByName = self.callPackage ({ someDrv }: someDrv) { }; + nonDerivation = self.callPackage ({ someDrv }: someDrv) { }; + + onlyMove = self.callPackage ./pkgs/by-name/on/onlyMove/package.nix { }; + + noEval = self.callPackage ./pkgs/by-name/no/noEval/package.nix { }; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/base/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/base/all-packages.nix new file mode 100644 index 000000000000..75efb5952e7a --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/base/all-packages.nix @@ -0,0 +1,9 @@ +self: super: { + nonAttributeSet = null; + nonCallPackage = self.someDrv; + internalCallByName = self._internalCallByNamePackageFile ./some-pkg.nix; + nonDerivation = self.callPackage ({ }: { }) { }; + + onlyMove = self.callPackage ({ someDrv }: someDrv) { }; + noEval = throw "foo"; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/base/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/base/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/base/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/base/pkgs/by-name/README.md b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/base/pkgs/by-name/README.md new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/base/pkgs/by-name/README.md diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/base/some-pkg.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/base/some-pkg.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/base/some-pkg.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/expected new file mode 100644 index 000000000000..29d33f7dbdc0 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/expected @@ -0,0 +1,2 @@ +pkgs.noEval: This attribute is manually defined (most likely in pkgs/top-level/all-packages.nix), which is only allowed if the definition is of the form `pkgs.callPackage pkgs/by-name/no/noEval/package.nix { ... }` with a non-empty second argument. +pkgs.onlyMove: This attribute is manually defined (most likely in pkgs/top-level/all-packages.nix), which is only allowed if the definition is of the form `pkgs.callPackage pkgs/by-name/on/onlyMove/package.nix { ... }` with a non-empty second argument. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/pkgs/by-name/no/noEval/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/pkgs/by-name/no/noEval/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/pkgs/by-name/no/noEval/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/pkgs/by-name/on/onlyMove/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/pkgs/by-name/on/onlyMove/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/manual-definition/pkgs/by-name/on/onlyMove/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/missing-package-nix/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/missing-package-nix/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/missing-package-nix/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/missing-package-nix/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/missing-package-nix/expected new file mode 100644 index 000000000000..ce1afcbf2d34 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/missing-package-nix/expected @@ -0,0 +1 @@ +pkgs/by-name/fo/foo: Missing required "package.nix" file. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/missing-package-nix/pkgs/by-name/fo/foo/.git-keep b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/missing-package-nix/pkgs/by-name/fo/foo/.git-keep new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/missing-package-nix/pkgs/by-name/fo/foo/.git-keep diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/mock-nixpkgs.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/mock-nixpkgs.nix new file mode 100644 index 000000000000..81a9c916ac2d --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/mock-nixpkgs.nix @@ -0,0 +1,101 @@ +/* +This file returns a mocked version of Nixpkgs' default.nix for testing purposes. +It does not depend on Nixpkgs itself for the sake of simplicity. + +It takes one attribute as an argument: +- `root`: The root of Nixpkgs to read other files from, including: + - `./pkgs/by-name`: The `pkgs/by-name` directory to test + - `./all-packages.nix`: A file containing an overlay to mirror the real `pkgs/top-level/all-packages.nix`. + This allows adding overrides on top of the auto-called packages in `pkgs/by-name`. + +It returns a Nixpkgs-like function that can be auto-called and evaluates to an attribute set. +*/ +{ + root, +}: +# The arguments for the Nixpkgs function +{ + # Passed by the checker to modify `callPackage` + overlays ? [], + # Passed by the checker to make sure a real Nixpkgs isn't influenced by impurities + config ? {}, + # Passed by the checker to make sure a real Nixpkgs isn't influenced by impurities + system ? null, +}: +let + + # Simplified versions of lib functions + lib = import <test-nixpkgs/lib>; + + # The base fixed-point function to populate the resulting attribute set + pkgsFun = self: { + inherit lib; + newScope = extra: lib.callPackageWith (self // extra); + callPackage = self.newScope { }; + callPackages = lib.callPackagesWith self; + someDrv = { type = "derivation"; }; + }; + + baseDirectory = root + "/pkgs/by-name"; + + # Generates { <name> = <file>; } entries mapping package names to their `package.nix` files in `pkgs/by-name`. + # Could be more efficient, but this is only for testing. + autoCalledPackageFiles = + let + entries = builtins.readDir baseDirectory; + + namesForShard = shard: + if entries.${shard} != "directory" then + # Only README.md is allowed to be a file, but it's not this code's job to check for that + { } + else + builtins.mapAttrs + (name: _: baseDirectory + "/${shard}/${name}/package.nix") + (builtins.readDir (baseDirectory + "/${shard}")); + + in + builtins.foldl' + (acc: el: acc // el) + { } + (map namesForShard (builtins.attrNames entries)); + + # Turns autoCalledPackageFiles into an overlay that `callPackage`'s all of them + autoCalledPackages = self: super: + { + # Needed to be able to detect empty arguments in all-packages.nix + # See a more detailed description in pkgs/top-level/by-name-overlay.nix + _internalCallByNamePackageFile = file: self.callPackage file { }; + } + // builtins.mapAttrs + (name: self._internalCallByNamePackageFile) + autoCalledPackageFiles; + + # A list optionally containing the `all-packages.nix` file from the test case as an overlay + optionalAllPackagesOverlay = + if builtins.pathExists (root + "/all-packages.nix") then + [ (import (root + "/all-packages.nix")) ] + else + [ ]; + + # A list optionally containing the `aliases.nix` file from the test case as an overlay + # But only if config.allowAliases is not false + optionalAliasesOverlay = + if (config.allowAliases or true) && builtins.pathExists (root + "/aliases.nix") then + [ (import (root + "/aliases.nix")) ] + else + [ ]; + + # All the overlays in the right order, including the user-supplied ones + allOverlays = + [ + autoCalledPackages + ] + ++ optionalAllPackagesOverlay + ++ optionalAliasesOverlay + ++ overlays; + + # Apply all the overlays in order to the base fixed-point function pkgsFun + f = builtins.foldl' (f: overlay: lib.extends overlay f) pkgsFun allOverlays; +in +# Evaluate the fixed-point +lib.fix f diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/all-packages.nix new file mode 100644 index 000000000000..16834c4f7856 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/all-packages.nix @@ -0,0 +1,10 @@ +self: super: { + foo1 = self.callPackage ({ someDrv }: someDrv) { }; + foo2 = self.callPackage ./without-config.nix { }; + foo3 = self.callPackage ({ someDrv, enableFoo }: someDrv) { + enableFoo = null; + }; + foo4 = self.callPackage ./with-config.nix { + enableFoo = null; + }; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/pkgs/by-name/fo/foo1/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/pkgs/by-name/fo/foo1/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/pkgs/by-name/fo/foo1/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/pkgs/by-name/fo/foo2/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/pkgs/by-name/fo/foo2/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/pkgs/by-name/fo/foo2/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/pkgs/by-name/fo/foo3/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/pkgs/by-name/fo/foo3/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/pkgs/by-name/fo/foo3/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/pkgs/by-name/fo/foo4/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/pkgs/by-name/fo/foo4/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/base/pkgs/by-name/fo/foo4/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/expected new file mode 100644 index 000000000000..96da50b52491 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/expected @@ -0,0 +1,4 @@ +pkgs.foo1: This top-level package was previously defined in pkgs/by-name/fo/foo1/package.nix, but is now manually defined as `callPackage ... { }` (e.g. in `pkgs/top-level/all-packages.nix`). Please move the package back and remove the manual `callPackage`. +pkgs.foo2: This top-level package was previously defined in pkgs/by-name/fo/foo2/package.nix, but is now manually defined as `callPackage ./without-config.nix { }` (e.g. in `pkgs/top-level/all-packages.nix`). Please move the package back and remove the manual `callPackage`. +pkgs.foo3: This top-level package was previously defined in pkgs/by-name/fo/foo3/package.nix, but is now manually defined as `callPackage ... { ... }` (e.g. in `pkgs/top-level/all-packages.nix`). While the manual `callPackage` is still needed, it's not necessary to move the package files. +pkgs.foo4: This top-level package was previously defined in pkgs/by-name/fo/foo4/package.nix, but is now manually defined as `callPackage ./with-config.nix { ... }` (e.g. in `pkgs/top-level/all-packages.nix`). While the manual `callPackage` is still needed, it's not necessary to move the package files. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/pkgs/by-name/README.md b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/pkgs/by-name/README.md new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/pkgs/by-name/README.md diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/with-config.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/with-config.nix new file mode 100644 index 000000000000..7cca53882ea5 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/with-config.nix @@ -0,0 +1 @@ +{ someDrv, enableFoo }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/without-config.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/without-config.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/move-to-non-by-name/without-config.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/expected new file mode 100644 index 000000000000..ff5d18556ef0 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/expected @@ -0,0 +1,13 @@ +pkgs/by-name/A: Invalid directory name "A", must be at most 2 ASCII characters consisting of a-z, 0-9, "-" or "_". +pkgs/by-name/A/fo@: Invalid package directory name "fo@", must be ASCII characters consisting of a-z, A-Z, 0-9, "-" or "_". +pkgs/by-name/A/fo@: Path foo is a symlink which cannot be resolved: No such file or directory (os error 2). +pkgs/by-name/A/fo@: Path package.nix is a symlink pointing to a path outside the directory of that package. +pkgs/by-name/aa: This is a file, but it should be a directory. +pkgs/by-name/ba/bar: This path is a file, but it should be a directory. +pkgs/by-name/ba/baz: "package.nix" must be a file. +pkgs/by-name/ba/foo: Incorrect directory location, should be pkgs/by-name/fo/foo instead. +pkgs/by-name/ba/foo: File package.nix at line 2 contains the path expression "/bar" which cannot be resolved: No such file or directory (os error 2). +pkgs/by-name/ba/foo: File package.nix at line 3 contains the path expression "../." which may point outside the directory of that package. +pkgs/by-name/ba/foo: File package.nix at line 4 contains the nix search path expression "<nixpkgs>" which may point outside the directory of that package. +pkgs/by-name/ba/foo: File package.nix at line 5 contains the path expression "./${"test"}", which is not yet supported and may point outside the directory of that package. +pkgs/by-name/fo/foo: Missing required "package.nix" file. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/A/fo@/foo b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/A/fo@/foo new file mode 120000 index 000000000000..c86c3f3551ec --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/A/fo@/foo @@ -0,0 +1 @@ +none \ No newline at end of file diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/A/fo@/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/A/fo@/package.nix new file mode 120000 index 000000000000..f079163d158a --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/A/fo@/package.nix @@ -0,0 +1 @@ +../../../../someDrv.nix \ No newline at end of file diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/aa b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/aa new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/aa diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/ba/bar b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/ba/bar new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/ba/bar diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/ba/baz/package.nix/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/ba/baz/package.nix/default.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/ba/baz/package.nix/default.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/ba/foo/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/ba/foo/package.nix new file mode 100644 index 000000000000..31b4742f5914 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/ba/foo/package.nix @@ -0,0 +1,6 @@ +{ someDrv }: someDrv // { + escapeAbsolute = /bar; + escapeRelative = ../.; + nixPath = <nixpkgs>; + pathWithSubexpr = ./${"test"}; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/fo/foo/.git-keep b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/fo/foo/.git-keep new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/pkgs/by-name/fo/foo/.git-keep diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/someDrv.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/someDrv.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/multiple-failures/someDrv.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/all-packages.nix new file mode 100644 index 000000000000..069119ad4c7b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/all-packages.nix @@ -0,0 +1,11 @@ +self: super: { + before = self.callPackage ({ someDrv }: someDrv) { }; + new1 = self.callPackage ({ someDrv }: someDrv) { }; + new2 = self.callPackage ./without-config.nix { }; + new3 = self.callPackage ({ someDrv, enableNew }: someDrv) { + enableNew = null; + }; + new4 = self.callPackage ./with-config.nix { + enableNew = null; + }; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/base/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/base/all-packages.nix new file mode 100644 index 000000000000..c2665d04d5f2 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/base/all-packages.nix @@ -0,0 +1,5 @@ +self: super: { + + before = self.callPackage ({ someDrv }: someDrv) { }; + +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/base/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/base/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/base/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/base/pkgs/by-name/README.md b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/base/pkgs/by-name/README.md new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/base/pkgs/by-name/README.md diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/expected new file mode 100644 index 000000000000..3f294f26dfd8 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/expected @@ -0,0 +1,4 @@ +pkgs.new1: This is a new top-level package of the form `callPackage ... { }`. Please define it in pkgs/by-name/ne/new1/package.nix instead. See `pkgs/by-name/README.md` for more details. Since the second `callPackage` argument is `{ }`, no manual `callPackage` (e.g. in `pkgs/top-level/all-packages.nix`) is needed anymore. +pkgs.new2: This is a new top-level package of the form `callPackage ./without-config.nix { }`. Please define it in pkgs/by-name/ne/new2/package.nix instead. See `pkgs/by-name/README.md` for more details. Since the second `callPackage` argument is `{ }`, no manual `callPackage` (e.g. in `pkgs/top-level/all-packages.nix`) is needed anymore. +pkgs.new3: This is a new top-level package of the form `callPackage ... { }`. Please define it in pkgs/by-name/ne/new3/package.nix instead. See `pkgs/by-name/README.md` for more details. Since the second `callPackage` argument is not `{ }`, the manual `callPackage` (e.g. in `pkgs/top-level/all-packages.nix`) is still needed. +pkgs.new4: This is a new top-level package of the form `callPackage ./with-config.nix { }`. Please define it in pkgs/by-name/ne/new4/package.nix instead. See `pkgs/by-name/README.md` for more details. Since the second `callPackage` argument is not `{ }`, the manual `callPackage` (e.g. in `pkgs/top-level/all-packages.nix`) is still needed. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/pkgs/by-name/README.md b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/pkgs/by-name/README.md new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/pkgs/by-name/README.md diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/with-config.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/with-config.nix new file mode 100644 index 000000000000..65bcbf9928a2 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/with-config.nix @@ -0,0 +1 @@ +{ someDrv, enableNew }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/without-config.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/without-config.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/new-package-non-by-name/without-config.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-by-name/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-by-name/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-by-name/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-by-name/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-by-name/expected new file mode 100644 index 000000000000..ddcb2df46e5f --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-by-name/expected @@ -0,0 +1 @@ +Given Nixpkgs path does not contain a pkgs/by-name subdirectory, no check necessary. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/all-packages.nix new file mode 100644 index 000000000000..38762c6de1cc --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/all-packages.nix @@ -0,0 +1,5 @@ +self: super: { + iDontEval = throw "I don't eval"; + + futureEval = self.callPackage ({ someDrv }: someDrv) { }; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/base/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/base/all-packages.nix new file mode 100644 index 000000000000..ac763b454eb0 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/base/all-packages.nix @@ -0,0 +1,3 @@ +self: super: { + futureEval = throw "foo"; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/base/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/base/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/base/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/base/pkgs/by-name/README.md b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/base/pkgs/by-name/README.md new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/base/pkgs/by-name/README.md diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/pkgs/by-name/README.md b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/pkgs/by-name/README.md new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/no-eval/pkgs/by-name/README.md diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-attrs/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-attrs/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-attrs/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-attrs/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-attrs/expected new file mode 100644 index 000000000000..e6c923790102 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-attrs/expected @@ -0,0 +1 @@ +pkgs.nonDerivation: This attribute defined by pkgs/by-name/no/nonDerivation/package.nix is not a derivation diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-attrs/pkgs/by-name/no/nonDerivation/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-attrs/pkgs/by-name/no/nonDerivation/package.nix new file mode 100644 index 000000000000..bd68dba1ded5 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-attrs/pkgs/by-name/no/nonDerivation/package.nix @@ -0,0 +1 @@ +{ }: null diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-derivation/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-derivation/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-derivation/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-derivation/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-derivation/expected new file mode 100644 index 000000000000..e6c923790102 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-derivation/expected @@ -0,0 +1 @@ +pkgs.nonDerivation: This attribute defined by pkgs/by-name/no/nonDerivation/package.nix is not a derivation diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-derivation/pkgs/by-name/no/nonDerivation/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-derivation/pkgs/by-name/no/nonDerivation/package.nix new file mode 100644 index 000000000000..b021e28c2145 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-derivation/pkgs/by-name/no/nonDerivation/package.nix @@ -0,0 +1 @@ +{ }: { } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-syntactical-callPackage-by-name/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-syntactical-callPackage-by-name/all-packages.nix new file mode 100644 index 000000000000..3e0ea20c2281 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-syntactical-callPackage-by-name/all-packages.nix @@ -0,0 +1,6 @@ +self: super: { + + bar = (x: x) self.callPackage ./pkgs/by-name/fo/foo/package.nix { someFlag = true; }; + foo = self.bar; + +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-syntactical-callPackage-by-name/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-syntactical-callPackage-by-name/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-syntactical-callPackage-by-name/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-syntactical-callPackage-by-name/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-syntactical-callPackage-by-name/expected new file mode 100644 index 000000000000..9df788191ece --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-syntactical-callPackage-by-name/expected @@ -0,0 +1 @@ +pkgs.foo: This attribute is manually defined (most likely in pkgs/top-level/all-packages.nix), which is only allowed if the definition is of the form `pkgs.callPackage pkgs/by-name/fo/foo/package.nix { ... }` with a non-empty second argument. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-syntactical-callPackage-by-name/pkgs/by-name/fo/foo/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-syntactical-callPackage-by-name/pkgs/by-name/fo/foo/package.nix new file mode 100644 index 000000000000..5ad6ea5e24d6 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/non-syntactical-callPackage-by-name/pkgs/by-name/fo/foo/package.nix @@ -0,0 +1 @@ +{ someDrv, someFlag }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/one-letter/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/one-letter/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/one-letter/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/one-letter/pkgs/by-name/a/a/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/one-letter/pkgs/by-name/a/a/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/one-letter/pkgs/by-name/a/a/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/only-callPackage-derivations/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/only-callPackage-derivations/all-packages.nix new file mode 100644 index 000000000000..5b1ed9d2ccda --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/only-callPackage-derivations/all-packages.nix @@ -0,0 +1,16 @@ +self: super: { + alternateCallPackage = self.myScope.callPackage ({ myScopeValue, someDrv }: + assert myScopeValue; + someDrv + ) { }; + + myScope = self.lib.makeScope self.newScope (self: { + myScopeValue = true; + }); + + myPackages = self.callPackages ({ someDrv }: { + a = someDrv; + b = someDrv; + }) { }; + inherit (self.myPackages) a b; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/only-callPackage-derivations/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/only-callPackage-derivations/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/only-callPackage-derivations/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/only-callPackage-derivations/pkgs/by-name/README.md b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/only-callPackage-derivations/pkgs/by-name/README.md new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/only-callPackage-derivations/pkgs/by-name/README.md diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/all-packages.nix new file mode 100644 index 000000000000..8bedb90d89a7 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/all-packages.nix @@ -0,0 +1,3 @@ +self: super: { + nonDerivation = self.callPackage ./someDrv.nix { }; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/expected new file mode 100644 index 000000000000..51479e22d26f --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/expected @@ -0,0 +1 @@ +pkgs.nonDerivation: This attribute is manually defined (most likely in pkgs/top-level/all-packages.nix), which is only allowed if the definition is of the form `pkgs.callPackage pkgs/by-name/no/nonDerivation/package.nix { ... }` with a non-empty second argument. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/pkgs/by-name/no/nonDerivation/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/pkgs/by-name/no/nonDerivation/package.nix new file mode 100644 index 000000000000..bd68dba1ded5 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/pkgs/by-name/no/nonDerivation/package.nix @@ -0,0 +1 @@ +{ }: null diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/someDrv.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/someDrv.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-different-file/someDrv.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/all-packages.nix new file mode 100644 index 000000000000..d369dd7228dc --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/all-packages.nix @@ -0,0 +1,3 @@ +self: super: { + nonDerivation = self.callPackage ./pkgs/by-name/no/nonDerivation/package.nix { }; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/base/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/base/all-packages.nix new file mode 100644 index 000000000000..d369dd7228dc --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/base/all-packages.nix @@ -0,0 +1,3 @@ +self: super: { + nonDerivation = self.callPackage ./pkgs/by-name/no/nonDerivation/package.nix { }; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/base/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/base/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/base/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/base/pkgs/by-name/no/nonDerivation/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/base/pkgs/by-name/no/nonDerivation/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/base/pkgs/by-name/no/nonDerivation/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/expected new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/expected diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/pkgs/by-name/no/nonDerivation/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/pkgs/by-name/no/nonDerivation/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg-gradual/pkgs/by-name/no/nonDerivation/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/all-packages.nix new file mode 100644 index 000000000000..d369dd7228dc --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/all-packages.nix @@ -0,0 +1,3 @@ +self: super: { + nonDerivation = self.callPackage ./pkgs/by-name/no/nonDerivation/package.nix { }; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/base/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/base/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/base/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/base/pkgs/by-name/README.md b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/base/pkgs/by-name/README.md new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/base/pkgs/by-name/README.md diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/expected new file mode 100644 index 000000000000..51479e22d26f --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/expected @@ -0,0 +1 @@ +pkgs.nonDerivation: This attribute is manually defined (most likely in pkgs/top-level/all-packages.nix), which is only allowed if the definition is of the form `pkgs.callPackage pkgs/by-name/no/nonDerivation/package.nix { ... }` with a non-empty second argument. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/pkgs/by-name/no/nonDerivation/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/pkgs/by-name/no/nonDerivation/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-empty-arg/pkgs/by-name/no/nonDerivation/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-call-package/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-call-package/all-packages.nix new file mode 100644 index 000000000000..853c3a87db56 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-call-package/all-packages.nix @@ -0,0 +1,3 @@ +self: super: { + nonDerivation = self.someDrv; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-call-package/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-call-package/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-call-package/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-call-package/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-call-package/expected new file mode 100644 index 000000000000..51479e22d26f --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-call-package/expected @@ -0,0 +1 @@ +pkgs.nonDerivation: This attribute is manually defined (most likely in pkgs/top-level/all-packages.nix), which is only allowed if the definition is of the form `pkgs.callPackage pkgs/by-name/no/nonDerivation/package.nix { ... }` with a non-empty second argument. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-call-package/pkgs/by-name/no/nonDerivation/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-call-package/pkgs/by-name/no/nonDerivation/package.nix new file mode 100644 index 000000000000..bd68dba1ded5 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-call-package/pkgs/by-name/no/nonDerivation/package.nix @@ -0,0 +1 @@ +{ }: null diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-file/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-file/all-packages.nix new file mode 100644 index 000000000000..dc07f69b40ee --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-file/all-packages.nix @@ -0,0 +1,3 @@ +self: super: { + nonDerivation = self.callPackage ({ someDrv }: someDrv) { }; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-file/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-file/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-file/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-file/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-file/expected new file mode 100644 index 000000000000..51479e22d26f --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-file/expected @@ -0,0 +1 @@ +pkgs.nonDerivation: This attribute is manually defined (most likely in pkgs/top-level/all-packages.nix), which is only allowed if the definition is of the form `pkgs.callPackage pkgs/by-name/no/nonDerivation/package.nix { ... }` with a non-empty second argument. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-file/pkgs/by-name/no/nonDerivation/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-file/pkgs/by-name/no/nonDerivation/package.nix new file mode 100644 index 000000000000..bd68dba1ded5 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-no-file/pkgs/by-name/no/nonDerivation/package.nix @@ -0,0 +1 @@ +{ }: null diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-success/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-success/all-packages.nix new file mode 100644 index 000000000000..6b323756ae41 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-success/all-packages.nix @@ -0,0 +1,5 @@ +self: super: { + foo = self.callPackage ./pkgs/by-name/fo/foo/package.nix { + enableBar = true; + }; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-success/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-success/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-success/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-success/pkgs/by-name/fo/foo/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-success/pkgs/by-name/fo/foo/package.nix new file mode 100644 index 000000000000..c811a7215a28 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/override-success/pkgs/by-name/fo/foo/package.nix @@ -0,0 +1,8 @@ +{ + someDrv, + enableBar ? false, +}: +if enableBar then + someDrv +else + {} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-dir-is-file/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-dir-is-file/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-dir-is-file/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-dir-is-file/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-dir-is-file/expected new file mode 100644 index 000000000000..3ad4b8f820f5 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-dir-is-file/expected @@ -0,0 +1 @@ +pkgs/by-name/fo/foo: This path is a file, but it should be a directory. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-dir-is-file/pkgs/by-name/fo/foo b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-dir-is-file/pkgs/by-name/fo/foo new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-dir-is-file/pkgs/by-name/fo/foo diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-nix-dir/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-nix-dir/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-nix-dir/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-nix-dir/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-nix-dir/expected new file mode 100644 index 000000000000..67a0c69fe29e --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-nix-dir/expected @@ -0,0 +1 @@ +pkgs/by-name/fo/foo: "package.nix" must be a file. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-nix-dir/pkgs/by-name/fo/foo/package.nix/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-nix-dir/pkgs/by-name/fo/foo/package.nix/default.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-nix-dir/pkgs/by-name/fo/foo/package.nix/default.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/all-packages.nix new file mode 100644 index 000000000000..85f8c6138c5c --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/all-packages.nix @@ -0,0 +1,5 @@ +self: super: { + foo-variant-unvarianted = self.callPackage ./package.nix { }; + + foo-variant-new = self.callPackage ./pkgs/by-name/fo/foo/package.nix { }; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/base/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/base/all-packages.nix new file mode 100644 index 000000000000..734604360073 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/base/all-packages.nix @@ -0,0 +1,3 @@ +self: super: { + foo-variant-unvarianted = self.callPackage ./pkgs/by-name/fo/foo/package.nix { }; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/base/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/base/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/base/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/base/pkgs/by-name/fo/foo/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/base/pkgs/by-name/fo/foo/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/base/pkgs/by-name/fo/foo/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/pkgs/by-name/fo/foo/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/pkgs/by-name/fo/foo/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/package-variants/pkgs/by-name/fo/foo/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-absolute/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-absolute/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-absolute/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-absolute/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-absolute/expected new file mode 100644 index 000000000000..7d20c32aad68 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-absolute/expected @@ -0,0 +1 @@ +pkgs/by-name/aa/aa: File package.nix at line 2 contains the path expression "/foo" which cannot be resolved: No such file or directory (os error 2). diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-absolute/pkgs/by-name/aa/aa/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-absolute/pkgs/by-name/aa/aa/package.nix new file mode 100644 index 000000000000..7a51ba1ec719 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-absolute/pkgs/by-name/aa/aa/package.nix @@ -0,0 +1,3 @@ +{ someDrv }: someDrv // { + escape = /foo; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-escape/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-escape/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-escape/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-escape/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-escape/expected new file mode 100644 index 000000000000..3d7fb64e80a3 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-escape/expected @@ -0,0 +1 @@ +pkgs/by-name/aa/aa: File package.nix at line 2 contains the path expression "../." which may point outside the directory of that package. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-escape/pkgs/by-name/aa/aa/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-escape/pkgs/by-name/aa/aa/package.nix new file mode 100644 index 000000000000..5989f52eb899 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-escape/pkgs/by-name/aa/aa/package.nix @@ -0,0 +1,3 @@ +{ someDrv }: someDrv // { + escape = ../.; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-nix-path/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-nix-path/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-nix-path/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-nix-path/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-nix-path/expected new file mode 100644 index 000000000000..b0cdff4a4778 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-nix-path/expected @@ -0,0 +1 @@ +pkgs/by-name/aa/aa: File package.nix at line 2 contains the nix search path expression "<nixpkgs>" which may point outside the directory of that package. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-nix-path/pkgs/by-name/aa/aa/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-nix-path/pkgs/by-name/aa/aa/package.nix new file mode 100644 index 000000000000..864fdce13319 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-nix-path/pkgs/by-name/aa/aa/package.nix @@ -0,0 +1,3 @@ +{ someDrv }: someDrv // { + nixPath = <nixpkgs>; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-path-subexpr/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-path-subexpr/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-path-subexpr/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-path-subexpr/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-path-subexpr/expected new file mode 100644 index 000000000000..ad662af27a86 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-path-subexpr/expected @@ -0,0 +1 @@ +pkgs/by-name/aa/aa: File package.nix at line 2 contains the path expression "./${"test"}", which is not yet supported and may point outside the directory of that package. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-path-subexpr/pkgs/by-name/aa/aa/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-path-subexpr/pkgs/by-name/aa/aa/package.nix new file mode 100644 index 000000000000..a94ba7541263 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-path-subexpr/pkgs/by-name/aa/aa/package.nix @@ -0,0 +1,3 @@ +{ someDrv }: someDrv // { + pathWithSubexpr = ./${"test"}; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/pkgs/by-name/aa/aa/dir/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/pkgs/by-name/aa/aa/dir/default.nix new file mode 100644 index 000000000000..7e4a7548fec7 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/pkgs/by-name/aa/aa/dir/default.nix @@ -0,0 +1,2 @@ +# Recursive +../package.nix diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/pkgs/by-name/aa/aa/file b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/pkgs/by-name/aa/aa/file new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/pkgs/by-name/aa/aa/file diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/pkgs/by-name/aa/aa/file.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/pkgs/by-name/aa/aa/file.nix new file mode 100644 index 000000000000..bd55e601bf64 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/pkgs/by-name/aa/aa/file.nix @@ -0,0 +1,2 @@ +# Recursive test +import ./file.nix diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/pkgs/by-name/aa/aa/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/pkgs/by-name/aa/aa/package.nix new file mode 100644 index 000000000000..474db5b0ebfc --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/ref-success/pkgs/by-name/aa/aa/package.nix @@ -0,0 +1,5 @@ +{ someDrv }: someDrv // { + nixFile = ./file.nix; + nonNixFile = ./file; + directory = ./dir; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/shard-file/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/shard-file/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/shard-file/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/shard-file/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/shard-file/expected new file mode 100644 index 000000000000..447b38e6b6c1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/shard-file/expected @@ -0,0 +1 @@ +pkgs/by-name/fo: This is a file, but it should be a directory. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/shard-file/pkgs/by-name/fo b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/shard-file/pkgs/by-name/fo new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/shard-file/pkgs/by-name/fo diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/all-packages.nix new file mode 100644 index 000000000000..688f52b9358f --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/all-packages.nix @@ -0,0 +1,6 @@ +self: super: { + a = self.callPackage ./pkgs/by-name/a/a/package.nix { }; + b = self.callPackage ({ someDrv }: someDrv) { }; + c = self.callPackage ./pkgs/by-name/c/c/package.nix { }; + d = self.callPackage ({ someDrv }: someDrv) { }; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/expected new file mode 100644 index 000000000000..349e9ad47c41 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/expected @@ -0,0 +1,4 @@ +pkgs.a: This attribute is manually defined (most likely in pkgs/top-level/all-packages.nix), which is only allowed if the definition is of the form `pkgs.callPackage pkgs/by-name/a/a/package.nix { ... }` with a non-empty second argument. +pkgs.b: This is a new top-level package of the form `callPackage ... { }`. Please define it in pkgs/by-name/b/b/package.nix instead. See `pkgs/by-name/README.md` for more details. Since the second `callPackage` argument is `{ }`, no manual `callPackage` (e.g. in `pkgs/top-level/all-packages.nix`) is needed anymore. +pkgs.c: This attribute is manually defined (most likely in pkgs/top-level/all-packages.nix), which is only allowed if the definition is of the form `pkgs.callPackage pkgs/by-name/c/c/package.nix { ... }` with a non-empty second argument. +pkgs.d: This is a new top-level package of the form `callPackage ... { }`. Please define it in pkgs/by-name/d/d/package.nix instead. See `pkgs/by-name/README.md` for more details. Since the second `callPackage` argument is `{ }`, no manual `callPackage` (e.g. in `pkgs/top-level/all-packages.nix`) is needed anymore. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/pkgs/by-name/a/a/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/pkgs/by-name/a/a/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/pkgs/by-name/a/a/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/pkgs/by-name/c/c/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/pkgs/by-name/c/c/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/sorted-order/pkgs/by-name/c/c/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/success/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/success/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/success/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/success/pkgs/by-name/fo/foo/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/success/pkgs/by-name/fo/foo/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/success/pkgs/by-name/fo/foo/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-escape/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-escape/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-escape/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-escape/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-escape/expected new file mode 100644 index 000000000000..335c5d6b6e5d --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-escape/expected @@ -0,0 +1 @@ +pkgs/by-name/fo/foo: Path package.nix is a symlink pointing to a path outside the directory of that package. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-escape/pkgs/by-name/fo/foo/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-escape/pkgs/by-name/fo/foo/package.nix new file mode 120000 index 000000000000..f079163d158a --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-escape/pkgs/by-name/fo/foo/package.nix @@ -0,0 +1 @@ +../../../../someDrv.nix \ No newline at end of file diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-escape/someDrv.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-escape/someDrv.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-escape/someDrv.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/expected new file mode 100644 index 000000000000..c1e7a28205a7 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/expected @@ -0,0 +1 @@ +pkgs/by-name/fo/foo: Path foo is a symlink which cannot be resolved: No such file or directory (os error 2). diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/pkgs/by-name/fo/foo/foo b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/pkgs/by-name/fo/foo/foo new file mode 120000 index 000000000000..c86c3f3551ec --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/pkgs/by-name/fo/foo/foo @@ -0,0 +1 @@ +none \ No newline at end of file diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/pkgs/by-name/fo/foo/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/pkgs/by-name/fo/foo/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/pkgs/by-name/fo/foo/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/someDrv.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/someDrv.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/symlink-invalid/someDrv.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/unknown-location/all-packages.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/unknown-location/all-packages.nix new file mode 100644 index 000000000000..3398e974cb6b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/unknown-location/all-packages.nix @@ -0,0 +1,3 @@ +self: super: builtins.mapAttrs (name: value: value) { + foo = self.someDrv; +} diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/unknown-location/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/unknown-location/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/unknown-location/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/unknown-location/expected b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/unknown-location/expected new file mode 100644 index 000000000000..2a248c23ab50 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/unknown-location/expected @@ -0,0 +1 @@ +pkgs.foo: Cannot determine the location of this attribute using `builtins.unsafeGetAttrPos`. diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/unknown-location/pkgs/by-name/fo/foo/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/unknown-location/pkgs/by-name/fo/foo/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/unknown-location/pkgs/by-name/fo/foo/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/uppercase/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/uppercase/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/uppercase/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/uppercase/pkgs/by-name/fo/FOO/package.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/uppercase/pkgs/by-name/fo/FOO/package.nix new file mode 100644 index 000000000000..a1b92efbbadb --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/uppercase/pkgs/by-name/fo/FOO/package.nix @@ -0,0 +1 @@ +{ someDrv }: someDrv diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/with-readme/default.nix b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/with-readme/default.nix new file mode 100644 index 000000000000..861260cdca4b --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/with-readme/default.nix @@ -0,0 +1 @@ +import <test-nixpkgs> { root = ./.; } diff --git a/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/with-readme/pkgs/by-name/README.md b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/with-readme/pkgs/by-name/README.md new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/nixpkgs/pkgs/test/nixpkgs-check-by-name/tests/with-readme/pkgs/by-name/README.md diff --git a/nixpkgs/pkgs/test/overriding.nix b/nixpkgs/pkgs/test/overriding.nix new file mode 100644 index 000000000000..f2519088f138 --- /dev/null +++ b/nixpkgs/pkgs/test/overriding.nix @@ -0,0 +1,66 @@ +{ lib, pkgs, stdenvNoCC }: + +let + tests = + let + p = pkgs.python3Packages.xpybutil.overridePythonAttrs (_: { dontWrapPythonPrograms = true; }); + in + [ + ({ + name = "overridePythonAttrs"; + expr = !lib.hasInfix "wrapPythonPrograms" p.postFixup; + expected = true; + }) + ({ + name = "repeatedOverrides-pname"; + expr = repeatedOverrides.pname == "a-better-hello-with-blackjack"; + expected = true; + }) + ({ + name = "repeatedOverrides-entangled-pname"; + expr = repeatedOverrides.entangled.pname == "a-better-figlet-with-blackjack"; + expected = true; + }) + ({ + name = "overriding-using-only-attrset"; + expr = (pkgs.hello.overrideAttrs { pname = "hello-overriden"; }).pname == "hello-overriden"; + expected = true; + }) + ({ + name = "overriding-using-only-attrset-no-final-attrs"; + expr = ((stdenvNoCC.mkDerivation { pname = "hello-no-final-attrs"; }).overrideAttrs { pname = "hello-no-final-attrs-overridden"; }).pname == "hello-no-final-attrs-overridden"; + expected = true; + }) + ]; + + addEntangled = origOverrideAttrs: f: + origOverrideAttrs ( + lib.composeExtensions f (self: super: { + passthru = super.passthru // { + entangled = super.passthru.entangled.overrideAttrs f; + overrideAttrs = addEntangled self.overrideAttrs; + }; + }) + ); + + entangle = pkg1: pkg2: pkg1.overrideAttrs (self: super: { + passthru = super.passthru // { + entangled = pkg2; + overrideAttrs = addEntangled self.overrideAttrs; + }; + }); + + example = entangle pkgs.hello pkgs.figlet; + + overrides1 = example.overrideAttrs (_: super: { pname = "a-better-${super.pname}"; }); + + repeatedOverrides = overrides1.overrideAttrs (_: super: { pname = "${super.pname}-with-blackjack"; }); +in + +stdenvNoCC.mkDerivation { + name = "test-overriding"; + passthru = { inherit tests; }; + buildCommand = '' + touch $out + '' + lib.concatMapStringsSep "\n" (t: "([[ ${lib.boolToString t.expr} == ${lib.boolToString t.expected} ]] && echo '${t.name} success') || (echo '${t.name} fail' && exit 1)") tests; +} diff --git a/nixpkgs/pkgs/test/php/default.nix b/nixpkgs/pkgs/test/php/default.nix new file mode 100644 index 000000000000..3c6c8f61b6db --- /dev/null +++ b/nixpkgs/pkgs/test/php/default.nix @@ -0,0 +1,116 @@ +{ lib +, php +, runCommand +}: + +let + runTest = name: body: runCommand name { } '' + testFailed= + checking() { + echo -n "Checking $1... " > /dev/stderr + } + ok() { + echo ok > /dev/stderr + } + nok() { + echo fail > /dev/stderr + testFailed=1 + } + + ${body} + + if test -n "$testFailed"; then + exit 1 + fi + + touch $out + ''; + + check = cond: if cond then "ok" else "nok"; +in +{ + withExtensions-enables-previously-disabled-extensions = runTest "php-test-withExtensions-enables-previously-disabled-extensions" '' + php="${php}" + + checking "that imagick is not present by default" + $php/bin/php -r 'exit(extension_loaded("imagick") ? 1 : 0);' && ok || nok + + phpWithImagick="${php.withExtensions ({ all, ... }: [ all.imagick ])}" + checking "that imagick extension is present when enabled" + $phpWithImagick/bin/php -r 'exit(extension_loaded("imagick") ? 0 : 1);' && ok || nok + ''; + + overrideAttrs-preserves-enabled-extensions = + let + customPhp = + (php.withExtensions ({ all, ... }: [ all.imagick ])).overrideAttrs (attrs: { + postInstall = attrs.postInstall or "" + '' + touch "$out/oApee-was-here" + ''; + }); + in + runTest "php-test-overrideAttrs-preserves-enabled-extensions" '' + php="${customPhp}" + phpUnwrapped="${customPhp.unwrapped}" + + checking "if overrides took hold" + test -f "$phpUnwrapped/oApee-was-here" && ok || nok + + checking "if imagick extension is still present" + $php/bin/php -r 'exit(extension_loaded("imagick") ? 0 : 1);' && ok || nok + + checking "if imagick extension is linked against the overridden PHP" + echo $php + $php/bin/php -r 'exit(extension_loaded("imagick") ? 0 : 1);' && ok || nok + ''; + + unwrapped-overrideAttrs-stacks = + let + customPhp = + lib.pipe php.unwrapped [ + (pkg: pkg.overrideAttrs (attrs: { + postInstall = attrs.postInstall or "" + '' + touch "$out/oAs-first" + ''; + })) + + (pkg: pkg.overrideAttrs (attrs: { + postInstall = attrs.postInstall or "" + '' + touch "$out/oAs-second" + ''; + })) + ]; + in + runTest "php-test-unwrapped-overrideAttrs-stacks" '' + checking "if first override remained" + ${check (builtins.match ".*oAs-first.*" customPhp.postInstall != null)} + + checking "if second override is there" + ${check (builtins.match ".*oAs-second.*" customPhp.postInstall != null)} + ''; + + wrapped-overrideAttrs-stacks = + let + customPhp = + lib.pipe php [ + (pkg: pkg.overrideAttrs (attrs: { + postInstall = attrs.postInstall or "" + '' + touch "$out/oAs-first" + ''; + })) + + (pkg: pkg.overrideAttrs (attrs: { + postInstall = attrs.postInstall or "" + '' + touch "$out/oAs-second" + ''; + })) + ]; + in + runTest "php-test-wrapped-overrideAttrs-stacks" '' + checking "if first override remained" + ${check (builtins.match ".*oAs-first.*" customPhp.unwrapped.postInstall != null)} + + checking "if second override is there" + ${check (builtins.match ".*oAs-second.*" customPhp.unwrapped.postInstall != null)} + ''; +} diff --git a/nixpkgs/pkgs/test/release/default.nix b/nixpkgs/pkgs/test/release/default.nix new file mode 100644 index 000000000000..2ab730b5c482 --- /dev/null +++ b/nixpkgs/pkgs/test/release/default.nix @@ -0,0 +1,46 @@ +# Adapted from lib/tests/release.nix +{ pkgs-path ? ../../.. +, pkgs ? import pkgs-path {} +, lib ? pkgs.lib +, nix ? pkgs.nix +}: + +# +# This verifies that release-attrpaths-superset.nix does not encounter +# infinite recursion or non-tryEval-able failures. +# +pkgs.runCommand "all-attrs-eval-under-tryEval" { + nativeBuildInputs = [ + nix + pkgs.gitMinimal + ] ++ lib.optional pkgs.stdenv.isLinux pkgs.inotify-tools; + strictDeps = true; +} +'' + datadir="${nix}/share" + export TEST_ROOT=$(pwd)/test-tmp + export HOME=$(mktemp -d) + export NIX_BUILD_HOOK= + export NIX_CONF_DIR=$TEST_ROOT/etc + export NIX_LOCALSTATE_DIR=$TEST_ROOT/var + export NIX_LOG_DIR=$TEST_ROOT/var/log/nix + export NIX_STATE_DIR=$TEST_ROOT/var/nix + export NIX_STORE_DIR=$TEST_ROOT/store + export PAGER=cat + cacheDir=$TEST_ROOT/binary-cache + + nix-store --init + + cp -r ${pkgs-path}/lib lib + cp -r ${pkgs-path}/pkgs pkgs + cp -r ${pkgs-path}/default.nix default.nix + cp -r ${pkgs-path}/nixos nixos + cp -r ${pkgs-path}/maintainers maintainers + cp -r ${pkgs-path}/.version .version + cp -r ${pkgs-path}/doc doc + echo "Running pkgs/top-level/release-attrpaths-superset.nix" + nix-instantiate --eval --strict --json pkgs/top-level/release-attrpaths-superset.nix -A names > /dev/null + + mkdir $out + echo success > $out/${nix.version} +'' diff --git a/nixpkgs/pkgs/test/simple/builder.sh b/nixpkgs/pkgs/test/simple/builder.sh new file mode 100644 index 000000000000..0b09a109bead --- /dev/null +++ b/nixpkgs/pkgs/test/simple/builder.sh @@ -0,0 +1,43 @@ +if [ -e "$NIX_ATTRS_SH_FILE" ]; then . "$NIX_ATTRS_SH_FILE"; elif [ -f .attrs.sh ]; then . .attrs.sh; fi +set -x + +export NIX_DEBUG=1 + +source $stdenv/setup + +export NIX_ENFORCE_PURITY=1 + +mkdir $out +mkdir $out/bin + +cat > hello.c <<EOF +#include <stdio.h> + +int main(int argc, char * * argv) +{ + printf("Hello World!\n"); + return 0; +} +EOF + +#gcc -I/nix/store/foo -I /nix/store/foo -I/usr/lib -I /usr/lib hello.c -o $out/bin/hello +gcc -I`pwd` -L /nix/store/abcd/lib -isystem /usr/lib hello.c -o $out/bin/hello + +$out/bin/hello + +cat > hello2.cc <<EOF +#include <iostream> + +int main(int argc, char * * argv) +{ + std::cout << "Hello World!\n"; + std::cout << VALUE << std::endl; + return 0; +} +EOF + +g++ hello2.cc -o $out/bin/hello2 -DVALUE="1 + 2 * 3" + +$out/bin/hello2 + +ld -v diff --git a/nixpkgs/pkgs/test/stdenv-inputs/bar.c b/nixpkgs/pkgs/test/stdenv-inputs/bar.c new file mode 100644 index 000000000000..2d7299c2d462 --- /dev/null +++ b/nixpkgs/pkgs/test/stdenv-inputs/bar.c @@ -0,0 +1,3 @@ +unsigned int bar(void) { + return 42; +} diff --git a/nixpkgs/pkgs/test/stdenv-inputs/cc-main.c b/nixpkgs/pkgs/test/stdenv-inputs/cc-main.c new file mode 100644 index 000000000000..06f28bc33c69 --- /dev/null +++ b/nixpkgs/pkgs/test/stdenv-inputs/cc-main.c @@ -0,0 +1,7 @@ +#include <stdio.h> + +int main(int argc, char **argv) +{ + fprintf(stderr, "ok\n"); + return 0; +} diff --git a/nixpkgs/pkgs/test/stdenv-inputs/default.nix b/nixpkgs/pkgs/test/stdenv-inputs/default.nix new file mode 100644 index 000000000000..6a2e441d0191 --- /dev/null +++ b/nixpkgs/pkgs/test/stdenv-inputs/default.nix @@ -0,0 +1,68 @@ +{ lib, stdenv }: + +let + foo = stdenv.mkDerivation { + name = "foo-test"; + + dontUnpack = true; + + installPhase = '' + mkdir -p $out/bin $out/include $out/lib + $CC -o $out/bin/foo ${./cc-main.c} + chmod +x $out/bin/foo + cp ${./foo.c} $out/include/foo.h + $CC -shared \ + ${lib.optionalString stdenv.isDarwin "-Wl,-install_name,$out/lib/libfoo.dylib"} \ + -o $out/lib/libfoo${stdenv.hostPlatform.extensions.sharedLibrary} \ + ${./foo.c} + ''; + }; + + bar = stdenv.mkDerivation { + name = "bar-test"; + outputs = [ "out" "dev" ]; + + dontUnpack = true; + + installPhase = '' + mkdir -p $out/bin $dev/include $dev/lib + $CC -o $out/bin/bar ${./cc-main.c} + chmod +x $out/bin/bar + cp ${./bar.c} $dev/include/bar.h + $CC -shared \ + ${lib.optionalString stdenv.isDarwin "-Wl,-install_name,$dev/lib/libbar.dylib"} \ + -o $dev/lib/libbar${stdenv.hostPlatform.extensions.sharedLibrary} \ + ${./bar.c} + ''; + }; +in + +stdenv.mkDerivation { + name = "stdenv-inputs-test"; + phases = [ "buildPhase" ]; + + buildInputs = [ foo bar ]; + + buildPhase = '' + env + + printf "checking whether binaries are available... " >&2 + foo && bar + + printf "checking whether compiler can find headers... " >&2 + $CC -o include-check ${./include-main.c} + ./include-check + + printf "checking whether compiler can find headers... " >&2 + $CC -o include-check ${./include-main.c} + ./include-check + + printf "checking whether compiler can find libraries... " >&2 + $CC -lfoo -lbar -o lib-check ${./lib-main.c} + ./lib-check + + touch $out + ''; + + meta.platforms = lib.platforms.all; +} diff --git a/nixpkgs/pkgs/test/stdenv-inputs/foo.c b/nixpkgs/pkgs/test/stdenv-inputs/foo.c new file mode 100644 index 000000000000..0253a26d5d2c --- /dev/null +++ b/nixpkgs/pkgs/test/stdenv-inputs/foo.c @@ -0,0 +1,3 @@ +unsigned int foo(void) { + return 42; +} diff --git a/nixpkgs/pkgs/test/stdenv-inputs/include-main.c b/nixpkgs/pkgs/test/stdenv-inputs/include-main.c new file mode 100644 index 000000000000..35e5ee0d90f7 --- /dev/null +++ b/nixpkgs/pkgs/test/stdenv-inputs/include-main.c @@ -0,0 +1,13 @@ +#include <stdio.h> +#include <foo.h> +#include <bar.h> + +int main(int argc, char **argv) +{ + if (foo() != 42) + return 1; + if (bar() != 42) + return 1; + fprintf(stderr, "ok\n"); + return 0; +} diff --git a/nixpkgs/pkgs/test/stdenv-inputs/lib-main.c b/nixpkgs/pkgs/test/stdenv-inputs/lib-main.c new file mode 100644 index 000000000000..c9488fe43e55 --- /dev/null +++ b/nixpkgs/pkgs/test/stdenv-inputs/lib-main.c @@ -0,0 +1,14 @@ +#include <stdio.h> + +extern unsigned int foo(void); +extern unsigned int bar(void); + +int main(int argc, char **argv) +{ + if (foo() != 42) + return 1; + if (bar() != 42) + return 1; + fprintf(stderr, "ok\n"); + return 0; +} diff --git a/nixpkgs/pkgs/test/stdenv/default.nix b/nixpkgs/pkgs/test/stdenv/default.nix new file mode 100644 index 000000000000..3882eb2b625c --- /dev/null +++ b/nixpkgs/pkgs/test/stdenv/default.nix @@ -0,0 +1,302 @@ +# To run these tests: +# nix-build -A tests.stdenv + +{ stdenv +, pkgs +, lib +, testers +}: + +let + # early enough not to rebuild gcc but late enough to have patchelf + earlyPkgs = stdenv.__bootPackages.stdenv.__bootPackages; + earlierPkgs = stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages; + # use a early stdenv so when hacking on stdenv this test can be run quickly + bootStdenv = stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv; + pkgsStructured = import pkgs.path { config = { structuredAttrsByDefault = true; }; inherit (stdenv.hostPlatform) system; }; + bootStdenvStructuredAttrsByDefault = pkgsStructured.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv; + + runCommand = earlierPkgs.runCommand; + + + ccWrapperSubstitutionsTest = { name, stdenv', extraAttrs ? { } }: + + stdenv'.cc.overrideAttrs (previousAttrs: ({ + inherit name; + + postFixup = previousAttrs.postFixup + '' + declare -p wrapperName + echo "env.wrapperName = $wrapperName" + [[ $wrapperName == "CC_WRAPPER" ]] || (echo "'\$wrapperName' was not 'CC_WRAPPER'" && false) + declare -p suffixSalt + echo "env.suffixSalt = $suffixSalt" + [[ $suffixSalt == "${stdenv'.cc.suffixSalt}" ]] || (echo "'\$suffxSalt' was not '${stdenv'.cc.suffixSalt}'" && false) + + grep -q "@out@" $out/bin/cc || echo "@out@ in $out/bin/cc was substituted" + grep -q "@suffixSalt@" $out/bin/cc && (echo "$out/bin/cc contains unsubstituted variables" && false) + + touch $out + ''; + } // extraAttrs)); + + testEnvAttrset = { name, stdenv', extraAttrs ? { } }: + stdenv'.mkDerivation + ({ + inherit name; + env = { + string = "testing-string"; + }; + + passAsFile = [ "buildCommand" ]; + buildCommand = '' + declare -p string + echo "env.string = $string" + [[ $string == "testing-string" ]] || (echo "'\$string' was not 'testing-string'" && false) + [[ "$(declare -p string)" == 'declare -x string="testing-string"' ]] || (echo "'\$string' was not exported" && false) + touch $out + ''; + } // extraAttrs); + + testPrependAndAppendToVar = { name, stdenv', extraAttrs ? { } }: + stdenv'.mkDerivation + ({ + inherit name; + env = { + string = "testing-string"; + }; + + passAsFile = [ "buildCommand" ] ++ lib.optionals (extraAttrs ? extraTest) [ "extraTest" ]; + buildCommand = '' + declare -p string + appendToVar string hello + # test that quoted strings work + prependToVar string "world" + declare -p string + + declare -A associativeArray=(["X"]="Y") + [[ $(appendToVar associativeArray "fail" 2>&1) =~ "trying to use" ]] || (echo "prependToVar did not catch prepending associativeArray" && false) + [[ $(prependToVar associativeArray "fail" 2>&1) =~ "trying to use" ]] || (echo "prependToVar did not catch prepending associativeArray" && false) + + [[ $string == "world testing-string hello" ]] || (echo "'\$string' was not 'world testing-string hello'" && false) + + # test appending to a unset variable + appendToVar nonExistant created hello + typeset -p nonExistant + if [[ -n $__structuredAttrs ]]; then + [[ "''${nonExistant[@]}" == "created hello" ]] + else + # there's a extra " " in front here and a extra " " in the end of prependToVar + # shouldn't matter because these functions will mostly be used for $*Flags and the Flag variable will in most cases already exit + [[ "$nonExistant" == " created hello" ]] + fi + + eval "$extraTest" + + touch $out + ''; + } // extraAttrs); + +in + +{ + # tests for hooks in `stdenv.defaultNativeBuildInputs` + hooks = lib.recurseIntoAttrs (import ./hooks.nix { stdenv = bootStdenv; pkgs = earlyPkgs; inherit lib; }); + + outputs-no-out = runCommand "outputs-no-out-assert" { + result = earlierPkgs.testers.testBuildFailure (bootStdenv.mkDerivation { + NIX_DEBUG = 1; + name = "outputs-no-out"; + outputs = ["foo"]; + buildPhase = ":"; + installPhase = '' + touch $foo + ''; + }); + + # Assumption: the first output* variable to be configured is + # _overrideFirst outputDev "dev" "out" + expectedMsg = "error: _assignFirst: could not find a non-empty variable whose name to assign to outputDev.\n The following variables were all unset or empty:\n dev out"; + } '' + grep -F "$expectedMsg" $result/testBuildFailure.log >/dev/null + touch $out + ''; + + test-env-attrset = testEnvAttrset { name = "test-env-attrset"; stdenv' = bootStdenv; }; + + # Test compatibility with derivations using `env` as a regular variable. + test-env-derivation = bootStdenv.mkDerivation rec { + name = "test-env-derivation"; + env = bootStdenv.mkDerivation { + name = "foo"; + buildCommand = '' + mkdir "$out" + touch "$out/bar" + ''; + }; + + passAsFile = [ "buildCommand" ]; + buildCommand = '' + declare -p env + [[ $env == "${env}" ]] + touch "$out" + ''; + }; + + # Check that mkDerivation rejects MD5 hashes + rejectedHashes = lib.recurseIntoAttrs { + md5 = + let drv = runCommand "md5 outputHash rejected" { + outputHash = "md5-fPt7dxVVP7ffY3MxkQdwVw=="; + } "true"; + in assert !(builtins.tryEval drv).success; {}; + }; + + test-inputDerivation = let + inherit (stdenv.mkDerivation { + dep1 = derivation { name = "dep1"; builder = "/bin/sh"; args = [ "-c" ": > $out" ]; system = builtins.currentSystem; }; + dep2 = derivation { name = "dep2"; builder = "/bin/sh"; args = [ "-c" ": > $out" ]; system = builtins.currentSystem; }; + passAsFile = [ "dep2" ]; + }) inputDerivation; + in + runCommand "test-inputDerivation" { + exportReferencesGraph = [ "graph" inputDerivation ]; + } '' + grep ${inputDerivation.dep1} graph + grep ${inputDerivation.dep2} graph + touch $out + ''; + + test-prepend-append-to-var = testPrependAndAppendToVar { + name = "test-prepend-append-to-var"; + stdenv' = bootStdenv; + }; + + test-structured-env-attrset = testEnvAttrset { + name = "test-structured-env-attrset"; + stdenv' = bootStdenv; + extraAttrs = { __structuredAttrs = true; }; + }; + + test-cc-wrapper-substitutions = ccWrapperSubstitutionsTest { + name = "test-cc-wrapper-substitutions"; + stdenv' = bootStdenv; + }; + + structuredAttrsByDefault = lib.recurseIntoAttrs { + + hooks = lib.recurseIntoAttrs (import ./hooks.nix { stdenv = bootStdenvStructuredAttrsByDefault; pkgs = earlyPkgs; inherit lib; }); + + test-cc-wrapper-substitutions = ccWrapperSubstitutionsTest { + name = "test-cc-wrapper-substitutions-structuredAttrsByDefault"; + stdenv' = bootStdenvStructuredAttrsByDefault; + }; + + test-structured-env-attrset = testEnvAttrset { + name = "test-structured-env-attrset-structuredAttrsByDefault"; + stdenv' = bootStdenvStructuredAttrsByDefault; + }; + + test-prepend-append-to-var = testPrependAndAppendToVar { + name = "test-prepend-append-to-var-structuredAttrsByDefault"; + stdenv' = bootStdenvStructuredAttrsByDefault; + extraAttrs = { + # will be a bash indexed array in attrs.sh + # declare -a list=('a' 'b' ) + # and a json array in attrs.json + # "list":["a","b"] + list = [ "a" "b" ]; + # will be a bash associative array(dictionary) in attrs.sh + # declare -A array=(['a']='1' ['b']='2' ) + # and a json object in attrs.json + # {"array":{"a":"1","b":"2"} + array = { a = "1"; b = "2"; }; + extraTest = '' + declare -p array + array+=(["c"]="3") + declare -p array + + [[ "''${array[c]}" == "3" ]] || (echo "c element of '\$array' was not '3'" && false) + + declare -p list + prependToVar list hello + # test that quoted strings work + appendToVar list "world" + declare -p list + + [[ "''${list[0]}" == "hello" ]] || (echo "first element of '\$list' was not 'hello'" && false) + [[ "''${list[1]}" == "a" ]] || (echo "first element of '\$list' was not 'a'" && false) + [[ "''${list[-1]}" == "world" ]] || (echo "last element of '\$list' was not 'world'" && false) + ''; + }; + }; + + test-golden-example-structuredAttrs = + let + goldenSh = earlyPkgs.writeText "goldenSh" '' + declare -A EXAMPLE_ATTRS=(['foo']='bar' ) + declare EXAMPLE_BOOL_FALSE= + declare EXAMPLE_BOOL_TRUE=1 + declare EXAMPLE_INT=123 + declare EXAMPLE_INT_NEG=-123 + declare -a EXAMPLE_LIST=('foo' 'bar' ) + declare EXAMPLE_STR='foo bar' + ''; + goldenJson = earlyPkgs.writeText "goldenSh" '' + { + "EXAMPLE_ATTRS": { + "foo": "bar" + }, + "EXAMPLE_BOOL_FALSE": false, + "EXAMPLE_BOOL_TRUE": true, + "EXAMPLE_INT": 123, + "EXAMPLE_INT_NEG": -123, + "EXAMPLE_LIST": [ + "foo", + "bar" + ], + "EXAMPLE_NESTED_ATTRS": { + "foo": { + "bar": "baz" + } + }, + "EXAMPLE_NESTED_LIST": [ + [ + "foo", + "bar" + ], + [ + "baz" + ] + ], + "EXAMPLE_STR": "foo bar" + } + ''; + in + bootStdenvStructuredAttrsByDefault.mkDerivation { + name = "test-golden-example-structuredAttrsByDefault"; + nativeBuildInputs = [ earlyPkgs.jq ]; + + EXAMPLE_BOOL_TRUE = true; + EXAMPLE_BOOL_FALSE = false; + EXAMPLE_INT = 123; + EXAMPLE_INT_NEG = -123; + EXAMPLE_STR = "foo bar"; + EXAMPLE_LIST = [ "foo" "bar" ]; + EXAMPLE_NESTED_LIST = [ [ "foo" "bar" ] [ "baz" ] ]; + EXAMPLE_ATTRS = { foo = "bar"; }; + EXAMPLE_NESTED_ATTRS = { foo.bar = "baz"; }; + + inherit goldenSh; + inherit goldenJson; + + buildCommand = '' + mkdir -p $out + cat $NIX_ATTRS_SH_FILE | grep "EXAMPLE" | grep -v -E 'installPhase|jq' > $out/sh + jq 'with_entries(select(.key|match("EXAMPLE")))' $NIX_ATTRS_JSON_FILE > $out/json + diff $out/sh $goldenSh + diff $out/json $goldenJson + ''; + }; + + }; +} diff --git a/nixpkgs/pkgs/test/stdenv/gcc-stageCompare.nix b/nixpkgs/pkgs/test/stdenv/gcc-stageCompare.nix new file mode 100644 index 000000000000..e5c2ed5921b3 --- /dev/null +++ b/nixpkgs/pkgs/test/stdenv/gcc-stageCompare.nix @@ -0,0 +1,32 @@ +# This test *must* be run prior to releasing any build of either stdenv or the +# gcc that it exports! This check should also be part of CI for any PR that +# causes a rebuild of `stdenv.cc`. +# +# When we used gcc's internal bootstrap it did this check as part of (and +# serially with) the gcc derivation. Now that we bootstrap externally this +# check can be done in parallel with any/all of stdenv's referrers. But we +# must remember to do the check. +# + +{ stdenv +, pkgs +, lib +}: + +assert stdenv.cc.isGNU; +with pkgs; +# rebuild gcc using the "final" stdenv +let gcc-stageCompare = (gcc-unwrapped.override { + reproducibleBuild = true; + profiledCompiler = false; + stdenv = overrideCC stdenv (wrapCCWith { + cc = stdenv.cc; + }); + }).overrideAttrs(_: { + NIX_OUTPATH_USED_AS_RANDOM_SEED = stdenv.cc.cc.out; + }); +in (runCommand "gcc-stageCompare" {} '' + diff -sr ${pkgs.gcc-unwrapped.checksum}/checksums ${gcc-stageCompare.checksum}/checksums && touch $out +'').overrideAttrs (a: { + meta = (a.meta or { }) // { platforms = lib.platforms.linux; }; +}) diff --git a/nixpkgs/pkgs/test/stdenv/hooks.nix b/nixpkgs/pkgs/test/stdenv/hooks.nix new file mode 100644 index 000000000000..eb1b3f61bda6 --- /dev/null +++ b/nixpkgs/pkgs/test/stdenv/hooks.nix @@ -0,0 +1,136 @@ +{ stdenv, pkgs, lib }: + +# ordering should match defaultNativeBuildInputs + +{ + # TODO: add audit-tmpdir + compress-man-pages = + let + manFile = pkgs.writeText "small-man" '' + .TH HELLO "1" "May 2022" "hello 2.12.1" "User Commands" + .SH NAME + hello - friendly greeting program + ''; + in + stdenv.mkDerivation { + name = "test-compress-man-pages"; + buildCommand = '' + mkdir -p $out/share/man + cp ${manFile} $out/share/man/small-man.1 + compressManPages $out + [[ -e $out/share/man/small-man.1.gz ]] + ''; + }; + make-symlinks-relative = stdenv.mkDerivation { + name = "test-make-symlinks-relative"; + outputs = [ "out" "man" ]; + buildCommand = '' + mkdir -p $out/{bar,baz} + mkdir -p $man/share/{x,y} + source1="$out/bar/foo" + destination1="$out/baz/foo" + source2="$man/share/x/file1" + destination2="$man/share/y/file2" + echo foo > $source1 + echo foo > $source2 + ln -s $source1 $destination1 + ln -s $source2 $destination2 + echo "symlink before patching: $(readlink $destination1)" + echo "symlink before patching: $(readlink $destination2)" + + _makeSymlinksRelativeInAllOutputs + + echo "symlink after patching: $(readlink $destination1)" + ([[ -e $destination1 ]] && echo "symlink isn't broken") || (echo "symlink is broken" && exit 1) + ([[ $(readlink $destination1) == "../bar/foo" ]] && echo "absolute symlink was made relative") || (echo "symlink was not made relative" && exit 1) + echo "symlink after patching: $(readlink $destination2)" + ([[ -e $destination2 ]] && echo "symlink isn't broken") || (echo "symlink is broken" && exit 1) + ([[ $(readlink $destination2) == "../x/file1" ]] && echo "absolute symlink was made relative") || (echo "symlink was not made relative" && exit 1) + ''; + }; + move-docs = stdenv.mkDerivation { + name = "test-move-docs"; + buildCommand = '' + mkdir -p $out/{man,doc,info} + touch $out/{man,doc,info}/foo + cat $out/{man,doc,info}/foo + + _moveToShare + + (cat $out/share/{man,doc,info}/foo 2>/dev/null && echo "man,doc,info were moved") || (echo "man,doc,info were not moved" && exit 1) + ''; + }; + move-lib64 = stdenv.mkDerivation { + name = "test-move-lib64"; + buildCommand = '' + mkdir -p $out/lib64 + touch $out/lib64/foo + cat $out/lib64/foo + + _moveLib64 + + # check symlink + [[ -h $out/lib64 ]] + ([[ -e $out/lib64 ]] && echo "symlink isn't broken") || (echo "symlink is broken" && exit 1) + [[ -e $out/lib/foo ]] + ''; + }; + move-sbin = stdenv.mkDerivation { + name = "test-move-sbin"; + buildCommand = '' + mkdir -p $out/sbin + touch $out/sbin/foo + cat $out/sbin/foo + + _moveSbin + + # check symlink + [[ -h $out/sbin ]] + ([[ -e $out/sbin ]] && echo "symlink isn't broken") || (echo "symlink is broken" && exit 1) + [[ -e $out/bin/foo ]] + ''; + }; + # TODO: add multiple-outputs + patch-shebangs = import ./patch-shebangs.nix { inherit stdenv lib pkgs; }; + prune-libtool-files = + let + libFoo = pkgs.writeText "libFoo" '' + # Generated by libtool (GNU libtool) 2.4.6 + old_library=''' + dependency_libs=' -Lbar.la -Lbaz.la' + ''; + in + stdenv.mkDerivation { + name = "test-prune-libtool-files"; + buildCommand = '' + mkdir -p $out/lib + cp ${libFoo} $out/lib/libFoo.la + _pruneLibtoolFiles + grep "^dependency_libs=''' #pruned" $out/lib/libFoo.la + # confirm file doesn't only contain the above + grep "^old_library='''" $out/lib/libFoo.la + ''; + }; + reproducible-builds = stdenv.mkDerivation { + name = "test-reproducible-builds"; + buildCommand = '' + # can't be tested more precisely because the value of random-seed changes depending on the output + [[ $NIX_CFLAGS_COMPILE =~ "-frandom-seed=" ]] + touch $out + ''; + }; + set-source-date-epoch-to-latest = stdenv.mkDerivation { + name = "test-set-source-date-epoch-to-latest"; + buildCommand = '' + sourceRoot=$NIX_BUILD_TOP/source + mkdir -p $sourceRoot + touch --date=1/1/2015 $sourceRoot/foo + + _updateSourceDateEpochFromSourceRoot + + [[ $SOURCE_DATE_EPOCH == "1420070400" ]] + touch $out + ''; + }; + # TODO: add strip +} diff --git a/nixpkgs/pkgs/test/stdenv/patch-shebangs.nix b/nixpkgs/pkgs/test/stdenv/patch-shebangs.nix new file mode 100644 index 000000000000..db9ca2fcaafe --- /dev/null +++ b/nixpkgs/pkgs/test/stdenv/patch-shebangs.nix @@ -0,0 +1,130 @@ +{ lib, stdenv, pkgs }: + +# since the tests are using a early stdenv, the stdenv will have dontPatchShebangs=1, so it has to be unset +# https://github.com/NixOS/nixpkgs/blob/768a982bfc9d29a6bd3beb963ed4b054451ce3d0/pkgs/stdenv/linux/default.nix#L148-L153 + +# strictDeps has to be disabled because the shell isn't in buildInputs + +let + tests = { + bad-shebang = stdenv.mkDerivation { + name = "bad-shebang"; + strictDeps = false; + dontUnpack = true; + installPhase = '' + mkdir -p $out/bin + echo "#!/bin/bash" > $out/bin/test + echo "echo -n hello" >> $out/bin/test + chmod +x $out/bin/test + dontPatchShebangs= + ''; + passthru = { + assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null"; + }; + }; + + ignores-nix-store = stdenv.mkDerivation { + name = "ignores-nix-store"; + strictDeps = false; + dontUnpack = true; + installPhase = '' + mkdir -p $out/bin + echo "#!$NIX_STORE/path/to/bash" > $out/bin/test + echo "echo -n hello" >> $out/bin/test + chmod +x $out/bin/test + dontPatchShebangs= + ''; + passthru = { + assertion = "grep \"^#!$NIX_STORE/path/to/bash\" $out/bin/test > /dev/null"; + }; + }; + + updates-nix-store = stdenv.mkDerivation { + name = "updates-nix-store"; + strictDeps = false; + dontUnpack = true; + installPhase = '' + mkdir -p $out/bin + echo "#!$NIX_STORE/path/to/bash" > $out/bin/test + echo "echo -n hello" >> $out/bin/test + chmod +x $out/bin/test + patchShebangs --update $out/bin/test + dontPatchShebangs=1 + ''; + passthru = { + assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null"; + }; + }; + + split-string = stdenv.mkDerivation { + name = "split-string"; + strictDeps = false; + dontUnpack = true; + installPhase = '' + mkdir -p $out/bin + echo "#!/usr/bin/env -S bash --posix" > $out/bin/test + echo "echo -n hello" >> $out/bin/test + chmod +x $out/bin/test + dontPatchShebangs= + ''; + passthru = { + assertion = "grep -v '^#!${pkgs.coreutils}/bin/env -S ${stdenv.shell} --posix' $out/bin/test > /dev/null"; + }; + }; + + without-trailing-newline = stdenv.mkDerivation { + name = "without-trailing-newline"; + strictDeps = false; + dontUnpack = true; + installPhase = '' + mkdir -p $out/bin + printf "#!/bin/bash" > $out/bin/test + chmod +x $out/bin/test + dontPatchShebangs= + ''; + passthru = { + assertion = "grep '^#!${stdenv.shell}' $out/bin/test > /dev/null"; + }; + }; + + }; +in +stdenv.mkDerivation { + name = "test-patch-shebangs"; + passthru = { inherit (tests) bad-shebang ignores-nix-store updates-nix-store split-string without-trailing-newline; }; + buildCommand = '' + validate() { + local name=$1 + local testout=$2 + local assertion=$3 + + echo -n "... $name: " >&2 + + local rc=0 + (out=$testout eval "$assertion") || rc=1 + + if [ "$rc" -eq 0 ]; then + echo "yes" >&2 + else + echo "no" >&2 + fi + + return "$rc" + } + + echo "checking whether patchShebangs works properly... ">&2 + + fail= + ${lib.concatStringsSep "\n" (lib.mapAttrsToList (_: test: '' + validate "${test.name}" "${test}" ${lib.escapeShellArg test.assertion} || fail=1 + '') tests)} + + if [ "$fail" ]; then + echo "failed" + exit 1 + else + echo "succeeded" + touch $out + fi + ''; +} diff --git a/nixpkgs/pkgs/test/texlive/default.nix b/nixpkgs/pkgs/test/texlive/default.nix new file mode 100644 index 000000000000..5f7067543932 --- /dev/null +++ b/nixpkgs/pkgs/test/texlive/default.nix @@ -0,0 +1,695 @@ +{ lib, stdenv, buildEnv, runCommand, fetchurl, file, texlive, writeShellScript, writeText, texliveInfraOnly, texliveConTeXt, texliveSmall, texliveMedium, texliveFull }: + +rec { + + mkTeXTest = lib.makeOverridable ( + { name + , format + , text + , texLive ? texliveSmall + , options ? "-interaction=errorstopmode" + , preTest ? "" + , postTest ? "" + , ... + }@attrs: runCommand "texlive-test-tex-${name}" + ({ + nativeBuildInputs = [ texLive ] ++ attrs.nativeBuildInputs or [ ]; + text = builtins.toFile "${name}.tex" text; + } // builtins.removeAttrs attrs [ "nativeBuildInputs" "text" "texLive" ]) + '' + export HOME="$(mktemp -d)" + mkdir "$out" + cd "$out" + cp "$text" "$name.tex" + ${preTest} + $format $options "$name.tex" + ${postTest} + '' + ); + + tlpdbNix = runCommand "texlive-test-tlpdb-nix" { + nixpkgsTlpdbNix = ../../tools/typesetting/tex/texlive/tlpdb.nix; + tlpdbNix = texlive.tlpdb.nix; + } + '' + mkdir -p "$out" + diff -u "''${nixpkgsTlpdbNix}" "''${tlpdbNix}" | tee "$out/tlpdb.nix.patch" + ''; + + # test two completely different font discovery mechanisms, both of which were once broken: + # - lualatex uses its own luaotfload script (#220228) + # - xelatex uses fontconfig (#228196) + opentype-fonts = lib.recurseIntoAttrs rec { + lualatex = mkTeXTest { + name = "opentype-fonts-lualatex"; + format = "lualatex"; + texLive = texliveSmall.withPackages (ps: [ ps.libertinus-fonts ]); + text = '' + \documentclass{article} + \usepackage{fontspec} + \setmainfont{Libertinus Serif} + \begin{document} + \LaTeX{} is great + \end{document} + ''; + }; + xelatex = lualatex.override { + name = "opentype-fonts-xelatex"; + format = "xelatex"; + }; + }; + + chktex = runCommand "texlive-test-chktex" { + nativeBuildInputs = [ + (texlive.withPackages (ps: [ ps.chktex ])) + ]; + input = builtins.toFile "chktex-sample.tex" '' + \documentclass{article} + \begin{document} + \LaTeX is great + \end{document} + ''; + } '' + # chktex is supposed to return 2 when it (successfully) finds warnings, but no errors, + # see http://git.savannah.nongnu.org/cgit/chktex.git/commit/?id=ec0fb9b58f02a62ff0bfec98b997208e9d7a5998 + (set +e; chktex -v -nall -w1 "$input" 2>&1; [ $? = 2 ] || exit 1; set -e) | tee "$out" + # also check that the output does indeed contain "One warning printed" + grep "One warning printed" "$out" + ''; + + context = mkTeXTest { + name = "texlive-test-context"; + format = "context"; + texLive = texliveConTeXt; + text = '' + \starttext + \startsection[title={ConTeXt test document}] + This is an {\em incredibly} simple ConTeXt document. + \stopsection + \stoptext + ''; + }; + + dvipng = lib.recurseIntoAttrs { + # https://github.com/NixOS/nixpkgs/issues/75605 + basic = runCommand "texlive-test-dvipng-basic" { + nativeBuildInputs = [ file texliveMedium ]; + input = fetchurl { + name = "test_dvipng.tex"; + url = "http://git.savannah.nongnu.org/cgit/dvipng.git/plain/test_dvipng.tex?id=b872753590a18605260078f56cbd6f28d39dc035"; + sha256 = "1pjpf1jvwj2pv5crzdgcrzvbmn7kfmgxa39pcvskl4pa0c9hl88n"; + }; + } '' + cp "$input" ./document.tex + + latex document.tex + dvipng -T tight -strict -picky document.dvi + for f in document*.png; do + file "$f" | tee output + grep PNG output + done + + mkdir "$out" + mv document*.png "$out"/ + ''; + + # test dvipng's limited capability to render postscript specials via GS + ghostscript = runCommand "texlive-test-ghostscript" { + nativeBuildInputs = [ file (texliveSmall.withPackages (ps: [ ps.dvipng ])) ]; + input = builtins.toFile "postscript-sample.tex" '' + \documentclass{minimal} + \begin{document} + Ni + \special{ps: + newpath + 0 0 moveto + 7 7 rlineto + 0 7 moveto + 7 -7 rlineto + stroke + showpage + } + \end{document} + ''; + gs_trap = writeShellScript "gs_trap.sh" '' + exit 1 + ''; + } '' + cp "$gs_trap" ./gs + export PATH=$PWD:$PATH + # check that the trap works + gs && exit 1 + + cp "$input" ./document.tex + + latex document.tex + dvipng -T 1in,1in -strict -picky document.dvi + for f in document*.png; do + file "$f" | tee output + grep PNG output + done + + mkdir "$out" + mv document*.png "$out"/ + ''; + }; + + # https://github.com/NixOS/nixpkgs/issues/75070 + dvisvgm = runCommand "texlive-test-dvisvgm" { + nativeBuildInputs = [ file texliveMedium ]; + input = builtins.toFile "dvisvgm-sample.tex" '' + \documentclass{article} + \begin{document} + mwe + \end{document} + ''; + } '' + cp "$input" ./document.tex + + latex document.tex + dvisvgm document.dvi -n -o document_dvi.svg + cat document_dvi.svg + file document_dvi.svg | grep SVG + + pdflatex document.tex + dvisvgm -P document.pdf -n -o document_pdf.svg + cat document_pdf.svg + file document_pdf.svg | grep SVG + + mkdir "$out" + mv document*.svg "$out"/ + ''; + + texdoc = runCommand "texlive-test-texdoc" { + nativeBuildInputs = [ + (texlive.withPackages (ps: with ps; [ luatex ps.texdoc ps.texdoc.texdoc ])) + ]; + } '' + texdoc --version + + texdoc --debug --list texdoc | tee "$out" + grep texdoc.pdf "$out" + ''; + + # check that the default language is US English + defaultLanguage = lib.recurseIntoAttrs rec { + # language.def + etex = mkTeXTest { + name = "default-language-etex"; + format = "etex"; + text = '' + \catcode`\@=11 + \ifnum\language=\lang@USenglish \message{[tests.texlive] Default language is US English.} + \else\errmessage{[tests.texlive] Error: default language is NOT US English.}\fi + \ifnum\language=0\message{[tests.texlive] Default language has id 0.} + \else\errmessage{[tests.texlive] Error: default language does NOT have id 0.}\fi + \bye + ''; + }; + # language.dat + latex = mkTeXTest { + name = "default-language-latex"; + format = "latex"; + text = '' + \makeatletter + \ifnum\language=\l@USenglish \GenericWarning{}{[tests.texlive] Default language is US English} + \else\GenericError{}{[tests.texlive] Error: default language is NOT US English}{}{}\fi + \ifnum\language=0\GenericWarning{}{[tests.texlive] Default language has id 0} + \else\GenericError{}{[tests.texlive] Error: default language does NOT have id 0}{}{}\fi + \stop + ''; + }; + # language.dat.lua + luatex = etex.override { + name = "default-language-luatex"; + format = "luatex"; + }; + }; + + # check that all languages are available, including synonyms + allLanguages = let hyphenBase = texlive.pkgs.hyphen-base; texLive = texliveFull; in + lib.recurseIntoAttrs { + # language.def + etex = mkTeXTest { + name = "all-languages-etex"; + format = "etex"; + inherit hyphenBase texLive; + text = '' + \catcode`\@=11 + \input kvsetkeys.sty + \def\CheckLang#1{ + \ifcsname lang@#1\endcsname\message{[tests.texlive] Found language #1} + \else\errmessage{[tests.texlive] Error: missing language #1}\fi + } + \comma@parse{@texLanguages@}\CheckLang + \bye + ''; + preTest = '' + texLanguages="$(sed -n -E 's/^\\addlanguage\s*\{([^}]+)\}.*$/\1/p' < "$hyphenBase"/tex/generic/config/language.def)" + texLanguages="''${texLanguages//$'\n'/,}" + substituteInPlace "$name.tex" --subst-var texLanguages + ''; + }; + # language.dat + latex = mkTeXTest { + name = "all-languages-latex"; + format = "latex"; + inherit hyphenBase texLive; + text = '' + \makeatletter + \@for\Lang:=italian,@texLanguages@\do{ + \ifcsname l@\Lang\endcsname + \GenericWarning{}{[tests.texlive] Found language \Lang} + \else + \GenericError{}{[tests.texlive] Error: missing language \Lang}{}{} + \fi + } + \stop + ''; + preTest = '' + texLanguages="$(sed -n -E 's/^([^%= \t]+).*$/\1/p' < "$hyphenBase"/tex/generic/config/language.dat)" + texLanguages="''${texLanguages//$'\n'/,}" + substituteInPlace "$name.tex" --subst-var texLanguages + ''; + }; + # language.dat.lua + luatex = mkTeXTest { + name = "all-languages-luatex"; + format = "luatex"; + inherit hyphenBase texLive; + text = '' + \directlua{ + require('luatex-hyphen.lua') + langs = '@texLanguages@,' + texio.write('\string\n') + for l in langs:gmatch('([^,]+),') do + if luatexhyphen.lookupname(l) \string~= nil then + texio.write('[tests.texlive] Found language '..l..'.\string\n') + else + error('[tests.texlive] Error: missing language '..l..'.', 2) + end + end + } + \bye + ''; + preTest = '' + texLanguages="$(sed -n -E 's/^.*\[("|'\''')(.*)("|'\''')].*$/\2/p' < "$hyphenBase"/tex/generic/config/language.dat.lua)" + texLanguages="''${texLanguages//$'\n'/,}" + substituteInPlace "$name.tex" --subst-var texLanguages + ''; + }; + }; + + # test that language files are generated as expected + hyphen-base = runCommand "texlive-test-hyphen-base" { + hyphenBase = texlive.pkgs.hyphen-base; + schemeFull = texliveFull; + schemeInfraOnly = texliveInfraOnly; + } '' + mkdir -p "$out"/{scheme-infraonly,scheme-full} + + # create language files with no hyphenation patterns + cat "$hyphenBase"/tex/generic/config/language.us >language.dat + cat "$hyphenBase"/tex/generic/config/language.us.def >language.def + cat "$hyphenBase"/tex/generic/config/language.us.lua >language.dat.lua + + cat >>language.dat.lua <<EOF + } + EOF + + cat >>language.def <<EOF + %%% No changes may be made beyond this point. + + \uselanguage {USenglish} %%% This MUST be the last line of the file. + EOF + + for fname in language.{dat,def,dat.lua} ; do + diff --ignore-matching-lines='^\(%\|--\) Generated by ' -u \ + {"$hyphenBase","$schemeFull"/share/texmf-var}/tex/generic/config/"$fname" \ + | tee "$out/scheme-full/$fname.patch" + diff --ignore-matching-lines='^\(%\|--\) Generated by ' -u \ + {,"$schemeInfraOnly"/share/texmf-var/tex/generic/config/}"$fname" \ + | tee "$out/scheme-infraonly/$fname.patch" + done + ''; + + # test that fmtutil.cnf is fully regenerated on scheme-full + fmtutilCnf = runCommand "texlive-test-fmtutil.cnf" { + kpathsea = texlive.pkgs.kpathsea.tex; + schemeFull = texliveFull; + } '' + mkdir -p "$out" + + diff --ignore-matching-lines='^# Generated by ' -u \ + {"$kpathsea","$schemeFull"/share/texmf-var}/web2c/fmtutil.cnf \ + | tee "$out/fmtutil.cnf.patch" + ''; + + # verify that the restricted mode gets enabled when + # needed (detected by checking if it disallows --gscmd) + repstopdf = runCommand "texlive-test-repstopdf" { + nativeBuildInputs = [ (texlive.withPackages (ps: [ ps.epstopdf ])) ]; + } '' + ! (epstopdf --gscmd echo /dev/null 2>&1 || true) | grep forbidden >/dev/null + (repstopdf --gscmd echo /dev/null 2>&1 || true) | grep forbidden >/dev/null + mkdir "$out" + ''; + + # verify that the restricted mode gets enabled when + # needed (detected by checking if it disallows --gscmd) + rpdfcrop = runCommand "texlive-test-rpdfcrop" { + nativeBuildInputs = [ (texlive.withPackages (ps: [ ps.pdfcrop ])) ]; + } '' + ! (pdfcrop --gscmd echo $(command -v pdfcrop) 2>&1 || true) | grep 'restricted mode' >/dev/null + (rpdfcrop --gscmd echo $(command -v pdfcrop) 2>&1 || true) | grep 'restricted mode' >/dev/null + mkdir "$out" + ''; + + # check that all binaries run successfully, in the following sense: + # (1) run --version, -v, --help, -h successfully; or + # (2) run --help, -h, or no argument with error code but show help text; or + # (3) run successfully on a test.tex or similar file + # we ignore the binaries that cannot be tested as above, and are either + # compiled binaries or trivial shell wrappers + binaries = let + # TODO known broken binaries + broken = [ + # *.inc files in source container rather than run + "texaccents" + + # 'Error initialising QuantumRenderer: no suitable pipeline found' + "tlcockpit" + ] ++ lib.optional stdenv.isDarwin "epspdftk"; # wish shebang is a script, not a binary! + + # (1) binaries requiring -v + shortVersion = [ "devnag" "diadia" "pmxchords" "ptex2pdf" "simpdftex" "ttf2afm" ]; + # (1) binaries requiring --help or -h + help = [ "arlatex" "bundledoc" "cachepic" "checklistings" "dvipos" "extractres" "fig4latex" "fragmaster" + "kpsewhere" "latex-git-log" "ltxfileinfo" "mendex" "perltex" "pn2pdf" "psbook" "psnup" "psresize" "purifyeps" + "simpdftex" "tex2xindy" "texluac" "texluajitc" "upmendex" "urlbst" "yplan" ]; + shortHelp = [ "adhocfilelist" "authorindex" "bbl2bib" "bibdoiadd" "bibmradd" "biburl2doi" "bibzbladd" "ctanupload" + "disdvi" "dvibook" "dviconcat" "getmapdl" "latex2man" "listings-ext.sh" "pygmentex" ]; + # (2) binaries that return non-zero exit code even if correctly asked for help + ignoreExitCode = [ "authorindex" "dvibook" "dviconcat" "dvipos" "extractres" "fig4latex" "fragmaster" "latex2man" + "latex-git-log" "listings-ext.sh" "psbook" "psnup" "psresize" "purifyeps" "tex2xindy" "texluac" + "texluajitc" ]; + # (2) binaries that print help on no argument, returning non-zero exit code + noArg = [ "a2ping" "bg5+latex" "bg5+pdflatex" "bg5latex" "bg5pdflatex" "cef5latex" "cef5pdflatex" "ceflatex" + "cefpdflatex" "cefslatex" "cefspdflatex" "chkdvifont" "dvi2fax" "dvired" "dviselect" "dvitodvi" "epsffit" + "findhyph" "gbklatex" "gbkpdflatex" "komkindex" "kpsepath" "listbib" "listings-ext" "mag" "mathspic" "mf2pt1" + "mk4ht" "mkt1font" "mkgrkindex" "musixflx" "pdf2ps" "pdfclose" "pdftosrc" "pdfxup" "pedigree" "pfb2pfa" "pk2bm" + "prepmx" "ps2pk" "psselect" "pstops" "rubibtex" "rubikrotation" "sjislatex" "sjispdflatex" "srcredact" "t4ht" + "teckit_compile" "tex4ht" "texdiff" "texdirflatten" "texplate" "tie" "ttf2kotexfont" "ttfdump" "vlna" "vpl2ovp" + "vpl2vpl" "yplan" ]; + # (3) binaries requiring a .tex file + contextTest = [ "htcontext" ]; + latexTest = [ "de-macro" "e2pall" "htlatex" "htxelatex" "makeindex" "pslatex" "rumakeindex" "tpic2pdftex" + "wordcount" "xhlatex" ]; + texTest = [ "fontinst" "htmex" "httex" "httexi" "htxetex" ]; + # tricky binaries or scripts that are obviously working but are hard to test + # (e.g. because they expect user input no matter the arguments) + # (printafm comes from ghostscript, not texlive) + ignored = [ + # compiled binaries + "dt2dv" "dv2dt" "dvi2tty" "dvidvi" "dvispc" "otp2ocp" "outocp" "pmxab" + + # GUI scripts that accept no argument or crash without a graphics server; please test manualy + "epspdftk" "texdoctk" "tlshell" "xasy" + + # requires Cinderella, not open source and not distributed via Nixpkgs + "ketcindy" + ]; + # binaries that need a combined scheme and cannot work standalone + needScheme = [ + # pfarrei: require working kpse to find lua module + "a5toa4" + + # bibexport: requires kpsewhich + "bibexport" + + # crossrefware: require bibtexperllibs under TEXMFROOT + "bbl2bib" "bibdoiadd" "bibmradd" "biburl2doi" "bibzbladd" "checkcites" "ltx2crossrefxml" + + # epstopdf: requires kpsewhich + "epstopdf" "repstopdf" + + # requires kpsewhich + "memoize-extract.pl" "memoize-extract.py" + + # require other texlive binaries in PATH + "allcm" "allec" "chkweb" "fontinst" "ht*" "installfont-tl" "kanji-config-updmap-sys" "kanji-config-updmap-user" + "kpse*" "latexfileversion" "mkocp" "mkofm" "mtxrunjit" "pdftex-quiet" "pslatex" "rumakeindex" "texconfig" + "texconfig-sys" "texexec" "texlinks" "texmfstart" "typeoutfileinfo" "wordcount" "xdvi" "xhlatex" + + # misc luatex binaries searching for luatex in PATH + "citeproc-lua" "context" "contextjit" "ctanbib" "digestif" "epspdf" "l3build" "luafindfont" "luaotfload-tool" + "luatools" "make4ht" "pmxchords" "tex4ebook" "texblend" "texdoc" "texfindpkg" "texlogsieve" "xindex" + + # requires full TEXMFROOT (e.g. for config) + "mktexfmt" "mktexmf" "mktexpk" "mktextfm" "psnup" "psresize" "pstops" "tlmgr" "updmap" "webquiz" + + # texlive-scripts: requires texlive.infra's TeXLive::TLUtils under TEXMFROOT + "fmtutil" "fmtutil-sys" "fmtutil-user" + + # texlive-scripts: not used in nixpkgs, need updmap in PATH + "updmap-sys" "updmap-user" + ]; + + # simple test files + contextTestTex = writeText "context-test.tex" '' + \starttext + A simple test file. + \stoptext + ''; + latexTestTex = writeText "latex-test.tex" '' + \documentclass{article} + \begin{document} + A simple test file. + \end{document} + ''; + texTestTex = writeText "tex-test.tex" '' + Hello. + \bye + ''; + + # link all binaries in single derivation + binPackages = lib.catAttrs "out" (lib.attrValues texlive.pkgs); + binaries = buildEnv { name = "texlive-binaries"; paths = binPackages; }; + in + runCommand "texlive-test-binaries" + { + inherit binaries contextTestTex latexTestTex texTestTex; + texliveScheme = texliveFull; + } + '' + loadables="$(command -v bash)" + loadables="''${loadables%/bin/bash}/lib/bash" + enable -f "$loadables/realpath" realpath + mkdir -p "$out" + export HOME="$(mktemp -d)" + declare -i binCount=0 ignoredCount=0 brokenCount=0 failedCount=0 + cp "$contextTestTex" context-test.tex + cp "$latexTestTex" latex-test.tex + cp "$texTestTex" tex-test.tex + + testBin () { + path="$(realpath "$bin")" + path="''${path##*/}" + if [[ -z "$ignoreExitCode" ]] ; then + PATH="$path" "$bin" $args >"$out/$base.log" 2>&1 + ret=$? + if [[ $ret == 0 ]] && grep -i 'command not found' "$out/$base.log" >/dev/null ; then + echo "command not found when running '$base''${args:+ $args}'" + return 1 + fi + return $ret + else + PATH="$path" "$bin" $args >"$out/$base.log" 2>&1 + ret=$? + if [[ $ret == 0 ]] && grep -i 'command not found' "$out/$base.log" >/dev/null ; then + echo "command not found when running '$base''${args:+ $args}'" + return 1 + fi + if ! grep -Ei '(Example:|Options:|Syntax:|Usage:|improper command|SYNOPSIS)' "$out/$base.log" >/dev/null ; then + echo "did not find usage info when running '$base''${args:+ $args}'" + return $ret + fi + fi + } + + for bin in "$binaries"/bin/* ; do + base="''${bin##*/}" + args= + ignoreExitCode= + binCount=$((binCount + 1)) + + # ignore non-executable files (such as context.lua) + if [[ ! -x "$bin" ]] ; then + ignoredCount=$((ignoredCount + 1)) + continue + fi + + case "$base" in + ${lib.concatStringsSep "|" ignored}) + ignoredCount=$((ignoredCount + 1)) + continue ;; + ${lib.concatStringsSep "|" broken}) + brokenCount=$((brokenCount + 1)) + continue ;; + ${lib.concatStringsSep "|" help}) + args=--help ;; + ${lib.concatStringsSep "|" shortHelp}) + args=-h ;; + ${lib.concatStringsSep "|" noArg}) + ;; + ${lib.concatStringsSep "|" contextTest}) + args=context-test.tex ;; + ${lib.concatStringsSep "|" latexTest}) + args=latex-test.tex ;; + ${lib.concatStringsSep "|" texTest}) + args=tex-test.tex ;; + ${lib.concatStringsSep "|" shortVersion}) + args=-v ;; + ebong) + touch empty + args=empty ;; + ht) + args='latex latex-test.tex' ;; + pdf2dsc) + args='--help --help --help' ;; + typeoutfileinfo) + args=/dev/null ;; + *) + args=--version ;; + esac + + case "$base" in + ${lib.concatStringsSep "|" (ignoreExitCode ++ noArg)}) + ignoreExitCode=1 ;; + esac + + case "$base" in + ${lib.concatStringsSep "|" needScheme}) + bin="$texliveScheme/bin/$base" + if [[ ! -f "$bin" ]] ; then + ignoredCount=$((ignoredCount + 1)) + continue + fi ;; + esac + + if testBin ; then : ; else # preserve exit code + echo "failed '$base''${args:+ $args}' (exit code: $?)" + sed 's/^/ > /' < "$out/$base.log" + failedCount=$((failedCount + 1)) + fi + done + + echo "tested $binCount binaries: $ignoredCount ignored, $brokenCount broken, $failedCount failed" + [[ $failedCount = 0 ]] + ''; + + # check that all scripts have a Nix shebang + shebangs = let + binPackages = lib.catAttrs "out" (lib.attrValues texlive.pkgs); + in + runCommand "texlive-test-shebangs" { } + ('' + echo "checking that all texlive scripts shebangs are in '$NIX_STORE'" + declare -i scriptCount=0 invalidCount=0 + '' + + (lib.concatMapStrings + (pkg: '' + for bin in '${pkg.outPath}'/bin/* ; do + grep -I -q . "$bin" || continue # ignore binary files + [[ -x "$bin" ]] || continue # ignore non-executable files (such as context.lua) + scriptCount=$((scriptCount + 1)) + read -r cmdline < "$bin" + read -r interp <<< "$cmdline" + if [[ "$interp" != "#!$NIX_STORE"/* && "$interp" != "#! $NIX_STORE"/* ]] ; then + echo "error: non-nix shebang '$interp' in script '$bin'" + invalidCount=$((invalidCount + 1)) + fi + done + '') + binPackages) + + '' + echo "checked $scriptCount scripts, found $invalidCount non-nix shebangs" + [[ $invalidCount -gt 0 ]] && exit 1 + mkdir -p "$out" + '' + ); + + # verify that the precomputed licensing information in default.nix + # does indeed match the metadata of the individual packages. + # + # This is part of the test suite (and not the normal evaluation) to save + # time for "normal" evaluations. To be more in line with the other tests, this + # also builds a derivation, even though it is essentially an eval-time assertion. + licenses = + let + concatLicenses = builtins.foldl' (acc: el: if builtins.elem el acc then acc else acc ++ [ el ]); + # converts a license to its attribute name in lib.licenses + licenseToAttrName = license: + builtins.head (builtins.attrNames + (lib.filterAttrs (n: v: license == v) lib.licenses)); + lt = (a: b: a < b); + + savedLicenses = scheme: scheme.meta.license; + savedLicensesAttrNames = scheme: map licenseToAttrName (savedLicenses scheme); + + correctLicenses = scheme: builtins.foldl' + (acc: pkg: concatLicenses acc (lib.toList (pkg.meta.license or []))) + [] + scheme.passthru.requiredTeXPackages; + correctLicensesAttrNames = scheme: + lib.sort lt + (map licenseToAttrName (correctLicenses scheme)); + + hasLicenseMismatch = scheme: + (lib.isDerivation scheme) && + (savedLicensesAttrNames scheme) != (correctLicensesAttrNames scheme); + incorrectSchemes = lib.filterAttrs + (n: hasLicenseMismatch) + (texlive.combined // texlive.schemes); + prettyPrint = name: scheme: + '' + license info for ${name} is incorrect! Note that order is enforced. + saved: [ ${lib.concatStringsSep " " (savedLicensesAttrNames scheme)} ] + correct: [ ${lib.concatStringsSep " " (correctLicensesAttrNames scheme)} ] + ''; + errorText = lib.concatStringsSep "\n\n" (lib.mapAttrsToList prettyPrint incorrectSchemes); + in + runCommand "texlive-test-license" { + inherit errorText; + } + (if (incorrectSchemes == {}) + then "echo everything is fine! > $out" + else '' + echo "$errorText" + false + ''); + + # verify that all fixed hashes are present + # this is effectively an eval-time assertion, converted into a derivation for + # ease of testing + fixedHashes = with lib; let + fods = lib.concatMap + (p: lib.optional (p ? tex && isDerivation p.tex) p.tex + ++ lib.optional (p ? texdoc) p.texdoc + ++ lib.optional (p ? texsource) p.texsource + ++ lib.optional (p ? tlpkg) p.tlpkg) + (attrValues texlive.pkgs); + errorText = concatMapStrings (p: optionalString (! p ? outputHash) "${p.pname}-${p.tlOutputName} does not have a fixed output hash\n") fods; + in runCommand "texlive-test-fixed-hashes" { + inherit errorText; + passAsFile = [ "errorText" ]; + } '' + if [[ -s "$errorTextPath" ]] ; then + cat "$errorTextPath" + echo Failed: some TeX Live packages do not have fixed output hashes. Please read UPGRADING.md for how to generate a new fixed-hashes.nix. + exit 1 + else + touch "$out" + fi + ''; +} diff --git a/nixpkgs/pkgs/test/top-level/default.nix b/nixpkgs/pkgs/test/top-level/default.nix new file mode 100644 index 000000000000..fdb9fe09a88b --- /dev/null +++ b/nixpkgs/pkgs/test/top-level/default.nix @@ -0,0 +1,47 @@ +{ lib, pkgs, ... }: +let + nixpkgsFun = import ../../top-level; +in +lib.recurseIntoAttrs { + platformEquality = + let + configsLocal = [ + # crossSystem is implicitly set to localSystem. + { + localSystem = { system = "x86_64-linux"; }; + } + { + localSystem = { system = "aarch64-linux"; }; + crossSystem = null; + } + # Both systems explicitly set to the same string. + { + localSystem = { system = "x86_64-linux"; }; + crossSystem = { system = "x86_64-linux"; }; + } + # Vendor and ABI inferred from system double. + { + localSystem = { system = "aarch64-linux"; }; + crossSystem = { config = "aarch64-unknown-linux-gnu"; }; + } + ]; + configsCross = [ + # GNU is inferred from double, but config explicitly requests musl. + { + localSystem = { system = "aarch64-linux"; }; + crossSystem = { config = "aarch64-unknown-linux-musl"; }; + } + # Cross-compile from AArch64 to x86-64. + { + localSystem = { system = "aarch64-linux"; }; + crossSystem = { system = "x86_64-unknown-linux-gnu"; }; + } + ]; + + pkgsLocal = map nixpkgsFun configsLocal; + pkgsCross = map nixpkgsFun configsCross; + in + assert lib.all (p: p.buildPlatform == p.hostPlatform) pkgsLocal; + assert lib.all (p: p.buildPlatform != p.hostPlatform) pkgsCross; + pkgs.emptyFile; +} diff --git a/nixpkgs/pkgs/test/vim/default.nix b/nixpkgs/pkgs/test/vim/default.nix new file mode 100644 index 000000000000..33e1e551d4f9 --- /dev/null +++ b/nixpkgs/pkgs/test/vim/default.nix @@ -0,0 +1,26 @@ +{ vimUtils, vim-full, writeText, vimPlugins +, lib, fetchFromGitHub +, pkgs +}: +let + inherit (vimUtils) buildVimPlugin; + + packages.myVimPackage.start = with vimPlugins; [ vim-nix ]; + +in + pkgs.recurseIntoAttrs (rec { + vim_empty_config = vimUtils.vimrcFile { beforePlugins = ""; customRC = ""; }; + + ### vim tests + ################## + + test_vim_with_vim_nix_using_plug = vim-full.customize { + name = "vim-with-vim-addon-nix-using-plug"; + vimrcConfig.plug.plugins = with vimPlugins; [ vim-nix ]; + }; + + test_vim_with_vim_nix = vim-full.customize { + name = "vim-with-vim-addon-nix"; + vimrcConfig.packages.myVimPackage.start = with vimPlugins; [ vim-nix ]; + }; +}) |