at 24.11-pre 5.7 kB view raw
1{ pythonOnBuildForHost, runCommand, writeShellScript, coreutils, gnugrep }: let 2 3 pythonPkgs = pythonOnBuildForHost.pkgs; 4 5 ### UTILITIES 6 7 # customize a package so that its store paths differs 8 customize = pkg: pkg.overrideAttrs { some_modification = true; }; 9 10 # generates minimal pyproject.toml 11 pyprojectToml = pname: builtins.toFile "pyproject.toml" '' 12 [project] 13 name = "${pname}" 14 version = "1.0.0" 15 ''; 16 17 # generates source for a python project 18 projectSource = pname: runCommand "my-project-source" {} '' 19 mkdir -p $out/src 20 cp ${pyprojectToml pname} $out/pyproject.toml 21 touch $out/src/__init__.py 22 ''; 23 24 # helper to reduce boilerplate 25 generatePythonPackage = args: pythonPkgs.buildPythonPackage ( 26 { 27 version = "1.0.0"; 28 src = runCommand "my-project-source" {} '' 29 mkdir -p $out/src 30 cp ${pyprojectToml args.pname} $out/pyproject.toml 31 touch $out/src/__init__.py 32 ''; 33 pyproject = true; 34 catchConflicts = true; 35 buildInputs = [ pythonPkgs.setuptools ]; 36 } 37 // args 38 ); 39 40 # in order to test for a failing build, wrap it in a shell script 41 expectFailure = build: errorMsg: build.overrideDerivation (old: { 42 builder = writeShellScript "test-for-failure" '' 43 export PATH=${coreutils}/bin:${gnugrep}/bin:$PATH 44 ${old.builder} "$@" > ./log 2>&1 45 status=$? 46 cat ./log 47 if [ $status -eq 0 ] || ! grep -q "${errorMsg}" ./log; then 48 echo "The build should have failed with '${errorMsg}', but it didn't" 49 exit 1 50 else 51 echo "The build failed as expected with: ${errorMsg}" 52 mkdir -p $out 53 fi 54 ''; 55 }); 56in { 57 58 ### TEST CASES 59 60 # Test case which must not trigger any conflicts. 61 # This derivation has runtime dependencies on custom versions of multiple build tools. 62 # This scenario is relevant for lang2nix tools which do not override the nixpkgs fix-point. 63 # see https://github.com/NixOS/nixpkgs/issues/283695 64 ignores-build-time-deps = 65 generatePythonPackage { 66 pname = "ignores-build-time-deps"; 67 buildInputs = [ 68 pythonPkgs.build 69 pythonPkgs.packaging 70 pythonPkgs.setuptools 71 pythonPkgs.wheel 72 ]; 73 propagatedBuildInputs = [ 74 # Add customized versions of build tools as runtime deps 75 (customize pythonPkgs.packaging) 76 (customize pythonPkgs.setuptools) 77 (customize pythonPkgs.wheel) 78 ]; 79 }; 80 81 # multi-output derivation with dependency on itself must not crash 82 cyclic-dependencies = 83 generatePythonPackage { 84 pname = "cyclic-dependencies"; 85 preFixup = '' 86 propagatedBuildInputs+=("$out") 87 ''; 88 }; 89 90 # Simplest test case that should trigger a conflict 91 catches-simple-conflict = let 92 # this build must fail due to conflicts 93 package = pythonPkgs.buildPythonPackage rec { 94 pname = "catches-simple-conflict"; 95 version = "0.0.0"; 96 src = projectSource pname; 97 pyproject = true; 98 catchConflicts = true; 99 buildInputs = [ 100 pythonPkgs.setuptools 101 ]; 102 # depend on two different versions of packaging 103 # (an actual runtime dependency conflict) 104 propagatedBuildInputs = [ 105 pythonPkgs.packaging 106 (customize pythonPkgs.packaging) 107 ]; 108 }; 109 in 110 expectFailure package "Found duplicated packages in closure for dependency 'packaging'"; 111 112 113 /* 114 More complex test case with a transitive conflict 115 116 Test sets up this dependency tree: 117 118 toplevel 119 dep1 120 leaf 121 dep2 122 leaf (customized version -> conflicting) 123 */ 124 catches-transitive-conflict = let 125 # package depending on both dependency1 and dependency2 126 toplevel = generatePythonPackage { 127 pname = "catches-transitive-conflict"; 128 propagatedBuildInputs = [ dep1 dep2 ]; 129 }; 130 # dep1 package depending on leaf 131 dep1 = generatePythonPackage { 132 pname = "dependency1"; 133 propagatedBuildInputs = [ leaf ]; 134 }; 135 # dep2 package depending on conflicting version of leaf 136 dep2 = generatePythonPackage { 137 pname = "dependency2"; 138 propagatedBuildInputs = [ (customize leaf) ]; 139 }; 140 # some leaf package 141 leaf = generatePythonPackage { 142 pname = "leaf"; 143 }; 144 in 145 expectFailure toplevel "Found duplicated packages in closure for dependency 'leaf'"; 146 147 /* 148 Transitive conflict with multiple dependency chains leading to the 149 conflicting package. 150 151 Test sets up this dependency tree: 152 153 toplevel 154 dep1 155 leaf 156 dep2 157 leaf 158 dep3 159 leaf (customized version -> conflicting) 160 */ 161 catches-conflict-multiple-chains = let 162 # package depending on dependency1, dependency2 and dependency3 163 toplevel = generatePythonPackage { 164 pname = "catches-conflict-multiple-chains"; 165 propagatedBuildInputs = [ dep1 dep2 dep3 ]; 166 }; 167 # dep1 package depending on leaf 168 dep1 = generatePythonPackage { 169 pname = "dependency1"; 170 propagatedBuildInputs = [ leaf ]; 171 }; 172 # dep2 package depending on leaf 173 dep2 = generatePythonPackage { 174 pname = "dependency2"; 175 propagatedBuildInputs = [ leaf ]; 176 }; 177 # dep3 package depending on conflicting version of leaf 178 dep3 = generatePythonPackage { 179 pname = "dependency3"; 180 propagatedBuildInputs = [ (customize leaf) ]; 181 }; 182 # some leaf package 183 leaf = generatePythonPackage { 184 pname = "leaf"; 185 }; 186 in 187 expectFailure toplevel "Found duplicated packages in closure for dependency 'leaf'"; 188}