2024-05-13 21:24:10 +00:00
|
|
|
{ pythonOnBuildForHost, runCommand, writeShellScript, coreutils, gnugrep }:
|
|
|
|
let
|
2024-05-02 00:46:19 +00:00
|
|
|
|
|
|
|
pythonPkgs = pythonOnBuildForHost.pkgs;
|
|
|
|
|
|
|
|
### UTILITIES
|
|
|
|
|
|
|
|
# customize a package so that its store paths differs
|
|
|
|
customize = pkg: pkg.overrideAttrs { some_modification = true; };
|
|
|
|
|
|
|
|
# generates minimal pyproject.toml
|
2024-05-13 21:24:10 +00:00
|
|
|
pyprojectToml = pname:
|
|
|
|
builtins.toFile "pyproject.toml" ''
|
|
|
|
[project]
|
|
|
|
name = "${pname}"
|
|
|
|
version = "1.0.0"
|
|
|
|
'';
|
2024-05-02 00:46:19 +00:00
|
|
|
|
|
|
|
# generates source for a python project
|
2024-05-13 21:24:10 +00:00
|
|
|
projectSource = pname:
|
|
|
|
runCommand "my-project-source" { } ''
|
|
|
|
mkdir -p $out/src
|
|
|
|
cp ${pyprojectToml pname} $out/pyproject.toml
|
|
|
|
touch $out/src/__init__.py
|
|
|
|
'';
|
2024-05-02 00:46:19 +00:00
|
|
|
|
|
|
|
# helper to reduce boilerplate
|
2024-05-13 21:24:10 +00:00
|
|
|
generatePythonPackage = args:
|
|
|
|
pythonPkgs.buildPythonPackage ({
|
2024-05-02 00:46:19 +00:00
|
|
|
version = "1.0.0";
|
2024-05-13 21:24:10 +00:00
|
|
|
src = runCommand "my-project-source" { } ''
|
2024-05-02 00:46:19 +00:00
|
|
|
mkdir -p $out/src
|
|
|
|
cp ${pyprojectToml args.pname} $out/pyproject.toml
|
|
|
|
touch $out/src/__init__.py
|
|
|
|
'';
|
|
|
|
pyproject = true;
|
|
|
|
catchConflicts = true;
|
|
|
|
buildInputs = [ pythonPkgs.setuptools ];
|
2024-05-13 21:24:10 +00:00
|
|
|
} // args);
|
2024-05-02 00:46:19 +00:00
|
|
|
|
|
|
|
# in order to test for a failing build, wrap it in a shell script
|
2024-05-13 21:24:10 +00:00
|
|
|
expectFailure = build: errorMsg:
|
|
|
|
build.overrideDerivation (old: {
|
|
|
|
builder = writeShellScript "test-for-failure" ''
|
|
|
|
export PATH=${coreutils}/bin:${gnugrep}/bin:$PATH
|
|
|
|
${old.builder} "$@" > ./log 2>&1
|
|
|
|
status=$?
|
|
|
|
cat ./log
|
|
|
|
if [ $status -eq 0 ] || ! grep -q "${errorMsg}" ./log; then
|
|
|
|
echo "The build should have failed with '${errorMsg}', but it didn't"
|
|
|
|
exit 1
|
|
|
|
else
|
|
|
|
echo "The build failed as expected with: ${errorMsg}"
|
|
|
|
mkdir -p $out
|
|
|
|
fi
|
|
|
|
'';
|
|
|
|
});
|
2024-05-02 00:46:19 +00:00
|
|
|
in {
|
|
|
|
|
|
|
|
### TEST CASES
|
|
|
|
|
|
|
|
# Test case which must not trigger any conflicts.
|
|
|
|
# This derivation has runtime dependencies on custom versions of multiple build tools.
|
|
|
|
# This scenario is relevant for lang2nix tools which do not override the nixpkgs fix-point.
|
|
|
|
# see https://github.com/NixOS/nixpkgs/issues/283695
|
2024-05-13 21:24:10 +00:00
|
|
|
ignores-build-time-deps = generatePythonPackage {
|
|
|
|
pname = "ignores-build-time-deps";
|
|
|
|
buildInputs = [
|
|
|
|
pythonPkgs.build
|
|
|
|
pythonPkgs.packaging
|
|
|
|
pythonPkgs.setuptools
|
|
|
|
pythonPkgs.wheel
|
|
|
|
];
|
|
|
|
propagatedBuildInputs = [
|
|
|
|
# Add customized versions of build tools as runtime deps
|
|
|
|
(customize pythonPkgs.packaging)
|
|
|
|
(customize pythonPkgs.setuptools)
|
|
|
|
(customize pythonPkgs.wheel)
|
|
|
|
];
|
|
|
|
};
|
2024-05-02 00:46:19 +00:00
|
|
|
|
|
|
|
# multi-output derivation with dependency on itself must not crash
|
2024-05-13 21:24:10 +00:00
|
|
|
cyclic-dependencies = generatePythonPackage {
|
|
|
|
pname = "cyclic-dependencies";
|
|
|
|
preFixup = ''
|
|
|
|
propagatedBuildInputs+=("$out")
|
|
|
|
'';
|
|
|
|
};
|
2024-05-02 00:46:19 +00:00
|
|
|
|
|
|
|
# Simplest test case that should trigger a conflict
|
|
|
|
catches-simple-conflict = let
|
|
|
|
# this build must fail due to conflicts
|
|
|
|
package = pythonPkgs.buildPythonPackage rec {
|
|
|
|
pname = "catches-simple-conflict";
|
|
|
|
version = "0.0.0";
|
|
|
|
src = projectSource pname;
|
|
|
|
pyproject = true;
|
|
|
|
catchConflicts = true;
|
2024-05-13 21:24:10 +00:00
|
|
|
buildInputs = [ pythonPkgs.setuptools ];
|
2024-05-02 00:46:19 +00:00
|
|
|
# depend on two different versions of packaging
|
|
|
|
# (an actual runtime dependency conflict)
|
2024-05-13 21:24:10 +00:00
|
|
|
propagatedBuildInputs =
|
|
|
|
[ pythonPkgs.packaging (customize pythonPkgs.packaging) ];
|
2024-05-02 00:46:19 +00:00
|
|
|
};
|
2024-05-13 21:24:10 +00:00
|
|
|
in expectFailure package
|
|
|
|
"Found duplicated packages in closure for dependency 'packaging'";
|
2024-05-02 00:46:19 +00:00
|
|
|
|
2024-05-13 21:24:10 +00:00
|
|
|
/* More complex test case with a transitive conflict
|
2024-05-02 00:46:19 +00:00
|
|
|
|
2024-05-13 21:24:10 +00:00
|
|
|
Test sets up this dependency tree:
|
2024-05-02 00:46:19 +00:00
|
|
|
|
2024-05-13 21:24:10 +00:00
|
|
|
toplevel
|
|
|
|
├── dep1
|
|
|
|
│ └── leaf
|
|
|
|
└── dep2
|
|
|
|
└── leaf (customized version -> conflicting)
|
2024-05-02 00:46:19 +00:00
|
|
|
*/
|
|
|
|
catches-transitive-conflict = let
|
|
|
|
# package depending on both dependency1 and dependency2
|
|
|
|
toplevel = generatePythonPackage {
|
|
|
|
pname = "catches-transitive-conflict";
|
|
|
|
propagatedBuildInputs = [ dep1 dep2 ];
|
|
|
|
};
|
|
|
|
# dep1 package depending on leaf
|
|
|
|
dep1 = generatePythonPackage {
|
|
|
|
pname = "dependency1";
|
|
|
|
propagatedBuildInputs = [ leaf ];
|
|
|
|
};
|
|
|
|
# dep2 package depending on conflicting version of leaf
|
|
|
|
dep2 = generatePythonPackage {
|
|
|
|
pname = "dependency2";
|
|
|
|
propagatedBuildInputs = [ (customize leaf) ];
|
|
|
|
};
|
|
|
|
# some leaf package
|
2024-05-13 21:24:10 +00:00
|
|
|
leaf = generatePythonPackage { pname = "leaf"; };
|
|
|
|
in expectFailure toplevel
|
|
|
|
"Found duplicated packages in closure for dependency 'leaf'";
|
2024-05-02 00:46:19 +00:00
|
|
|
}
|