diff --git a/pkgs/development/interpreters/python/hooks/default.nix b/pkgs/development/interpreters/python/hooks/default.nix index 306c33a91fdd0..5deb3cf97b8ed 100644 --- a/pkgs/development/interpreters/python/hooks/default.nix +++ b/pkgs/development/interpreters/python/hooks/default.nix @@ -66,7 +66,19 @@ in { pypaBuildHook = callPackage ({ makePythonHook, build, wheel }: makePythonHook { name = "pypa-build-hook.sh"; - propagatedBuildInputs = [ build wheel ]; + propagatedBuildInputs = [ wheel ]; + substitutions = { + inherit build; + }; + # A test to ensure that this hook never propagates any of its dependencies + # into the build environment. + # This prevents false positive alerts raised by catchConflictsHook. + # Such conflicts don't happen within the standard nixpkgs python package + # set, but in downstream projects that build packages depending on other + # versions of this hook's dependencies. + passthru.tests = import ./pypa-build-hook-tests.nix { + inherit pythonForBuild runCommand; + }; } ./pypa-build-hook.sh) { inherit (pythonForBuild.pkgs) build; }; diff --git a/pkgs/development/interpreters/python/hooks/pypa-build-hook-test.nix b/pkgs/development/interpreters/python/hooks/pypa-build-hook-test.nix new file mode 100644 index 0000000000000..d909e34241f16 --- /dev/null +++ b/pkgs/development/interpreters/python/hooks/pypa-build-hook-test.nix @@ -0,0 +1,32 @@ +{ pythonForBuild, runCommand }: { + dont-propagate-conflicting-deps = let + # customize a package so that its store paths differs + mkConflict = pkg: pkg.overrideAttrs { some_modification = true; }; + # minimal pyproject.toml for the example project + pyprojectToml = builtins.toFile "pyproject.toml" '' + [project] + name = "my-project" + version = "1.0.0" + ''; + # the source of the example project + projectSource = runCommand "my-project-source" {} '' + mkdir -p $out/src + cp ${pyprojectToml} $out/pyproject.toml + touch $out/src/__init__.py + ''; + in + # this build must never triger conflicts + pythonForBuild.pkgs.buildPythonPackage { + pname = "dont-propagate-conflicting-deps"; + version = "0.0.0"; + src = projectSource; + format = "pyproject"; + propagatedBuildInputs = [ + # At least one dependency of `build` should be included here to + # keep the test meaningful + (mkConflict pythonForBuild.pkgs.tomli) + # setuptools is also needed to build the example project + pythonForBuild.pkgs.setuptools + ]; + }; +} diff --git a/pkgs/development/interpreters/python/hooks/pypa-build-hook.sh b/pkgs/development/interpreters/python/hooks/pypa-build-hook.sh index 5d77613bf565f..dd49d935bcee7 100644 --- a/pkgs/development/interpreters/python/hooks/pypa-build-hook.sh +++ b/pkgs/development/interpreters/python/hooks/pypa-build-hook.sh @@ -6,7 +6,7 @@ pypaBuildPhase() { runHook preBuild echo "Creating a wheel..." - pyproject-build --no-isolation --outdir dist/ --wheel $pypaBuildFlags + @build@/bin/pyproject-build --no-isolation --outdir dist/ --wheel $pypaBuildFlags echo "Finished creating a wheel..." runHook postBuild diff --git a/pkgs/development/python-modules/bootstrap/build/default.nix b/pkgs/development/python-modules/bootstrap/build/default.nix index 639d2e3292cb0..f4e49bd65605c 100644 --- a/pkgs/development/python-modules/bootstrap/build/default.nix +++ b/pkgs/development/python-modules/bootstrap/build/default.nix @@ -7,12 +7,15 @@ , packaging , pyproject-hooks , tomli +, makeWrapper }: let buildBootstrapPythonModule = basePackage: attrs: stdenv.mkDerivation ({ pname = "${python.libPrefix}-bootstrap-${basePackage.pname}"; inherit (basePackage) version src meta; + nativeBuildInputs = [ makeWrapper ]; + buildPhase = '' runHook preBuild @@ -38,12 +41,30 @@ let bootstrap-pyproject-hooks = buildBootstrapPythonModule pyproject-hooks {}; bootstrap-tomli = buildBootstrapPythonModule tomli {}; + + sitePkgs = python.sitePackages; in buildBootstrapPythonModule build { - propagatedBuildInputs = [ - bootstrap-packaging - bootstrap-pyproject-hooks - ] ++ lib.optionals (python.pythonOlder "3.11") [ - bootstrap-tomli - ]; + # like the installPhase above, but wrapping the pyproject-build command + # to set up PYTHONPATH with the correct dependencies. + # This allows using `pyproject-build` without propagating its dependencies + # into the build environment, which is necessary to prevent + # pythonCatchConflicts from raising false positive alerts. + # This would happen whenever the package to build has a dependency on + # another version of a package that is also a dependency of pyproject-build. + installPhase = '' + runHook preInstall + + PYTHONPATH="${installer}/${python.sitePackages}" \ + ${python.interpreter} -m installer \ + --destdir "$out" --prefix "" dist/*.whl + + wrapProgram $out/bin/pyproject-build \ + --prefix PYTHONPATH : "$out/${sitePkgs}" \ + --prefix PYTHONPATH : "${bootstrap-pyproject-hooks}/${sitePkgs}" \ + --prefix PYTHONPATH : "${bootstrap-packaging}/${sitePkgs}" \ + --prefix PYTHONPATH : "${bootstrap-tomli}/${sitePkgs}" + + runHook postInstall + ''; }