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