Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/major-differences-nixpkgs.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,19 @@ changes differ significantly from whath one would expct with Nixpkgs.

- setupHooks for build managers are now explicit and opt-in.
- E.g. `meson.configurePhaseHook` now needs to be specified.
- `doCheck` now defaults to `false` across the package set.
- Test suites are not executed as part of the main build by default.
- This keeps the critical build path lean and avoids rebuilding the world
when only a test-related input changes.
- To run a package's tests, override with `doCheck = true` or evaluate the
dedicated test derivation (e.g. `pkg.passthru.tests.*`).
- `buildPythonPackage` exposes a `testDir` attribute for deferring test
execution into a separate derivation.
- When set, `testDir` should point to the directory (relative to the source
root) containing the package's test suite.
- An additional `test-src` output is produced containing just the test
sources, and a `passthru.tests.python` derivation is auto-generated that
runs the test suite against the installed package using the configured
`nativeCheckInputs` / `checkInputs`.
- This decouples test execution from the main build, allowing test failures
or test-only dependency churn to avoid invalidating downstream consumers.
7 changes: 7 additions & 0 deletions python/hooks/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,13 @@ in
} ./python-output-dist-hook.sh
) { };

pythonOutputTestSrcHook = callPackage (
{ makePythonHook }:
makePythonHook {
name = "python-output-test-src-hook";
} ./python-output-test-src-hook.sh
) { };

pythonRecompileBytecodeHook = callPackage (
{ makePythonHook }:
makePythonHook {
Expand Down
22 changes: 22 additions & 0 deletions python/hooks/python-output-test-src-hook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Setup hook for storing test sources in a separate output
# shellcheck shell=bash

echo "Sourcing python-output-test-src-hook.sh"

pythonOutputTestSrcPhase() {
echo "Executing pythonOutputTestSrcPhase"
if [[ -n "${testDir:-}" && -d "$testDir" ]]; then
# shellcheck disable=SC2154
mkdir -p "$test_src"
cp -R "$testDir" "$test_src/"
else
cat >&2 <<EOF
testDir='${testDir:-}' does not exist in the source tree.
Ensure the testDir attribute points to a valid directory relative to the source root.
EOF
return 1
fi
echo "Finished executing pythonOutputTestSrcPhase"
}

appendToVar preFixupPhases pythonOutputTestSrcPhase
155 changes: 138 additions & 17 deletions python/mk-python-derivation.nix
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
pythonImportsCheckHook,
pythonNamespacesHook,
pythonOutputDistHook,
pythonOutputTestSrcHook,
pythonRelaxDepsHook,
pythonRemoveBinBytecodeHook,
pythonRemoveTestsDirHook,
Expand Down Expand Up @@ -113,10 +114,42 @@ let
"build-system"
];

# Python-specific attrs that should be visible to the user's `finalAttrs`
# fixed-point view but stripped from the underlying mkDerivation call so they
# are not forwarded to the `derivation` builtin (which cannot coerce attrsets
# like `optional-dependencies` to strings).
pythonOnlyAttrNames = [
"dependencies"
"optional-dependencies"
"build-system"
];

# Wrap `stdenv.mkDerivation` so that the user's function sees a `finalAttrs`
# value containing Python-specific attributes (e.g. `optional-dependencies`),
# while the underlying `mkDerivation` receives a sanitised attrset.
pythonMkDerivation =
fn:
stdenv.mkDerivation (
drvFinalAttrs:
let
# Re-expose the Python-specific attrs that the inner `mkDerivation`
# doesn't track. These reference the user's own returned attrs, so the
# fixed-point still terminates as long as the user doesn't define them
# in terms of `finalAttrs.<pythonOnlyAttr>` itself.
userFinalAttrs = drvFinalAttrs // {
dependencies = userAttrs.dependencies or [ ];
optional-dependencies = userAttrs.optional-dependencies or { };
build-system = userAttrs.build-system or [ ];
};
userAttrs = fn userFinalAttrs;
in
removeAttrs userAttrs pythonOnlyAttrNames
);

in

lib.extendMkDerivation {
constructDrv = stdenv.mkDerivation;
constructDrv = pythonMkDerivation;

excludeDrvArgNames = [
"disabled"
Expand Down Expand Up @@ -167,6 +200,21 @@ lib.extendMkDerivation {

outputs ? [ "out" ],

# Files and/or directories (relative to the source root) to bundle into
# the `test_src` output. When non-empty, a "test_src" output is created
# and `passthru.tests.python` is auto-generated to run the package's
# test suite against the installed package without rebuilding.
#
# Typical usage is `testPaths = [ "tests" ];`. Packages whose suites
# reference sibling files (helper modules, fixture data, README files
# used by doctests, etc.) should list those alongside the test
# directory, e.g. `testPaths = [ "tests" "smartypants" "README.rst" ];`.
#
# The list is forwarded to the `pythonOutputTestSrcHook` as a
# whitespace-separated environment variable; entries must therefore
# not contain whitespace.
testPaths ? [ ],

# used to disable derivation, useful for specific python versions
disabled ? false,

Expand Down Expand Up @@ -243,6 +291,8 @@ lib.extendMkDerivation {

withDistOutput = withDistOutput' format';

withTestSrcOutput = testPaths != [ ];

validatePythonMatches =
let
throwMismatch =
Expand Down Expand Up @@ -384,6 +434,9 @@ lib.extendMkDerivation {
++ optionals withDistOutput [
pythonOutputDistHook
]
++ optionals withTestSrcOutput [
pythonOutputTestSrcHook
]
++ nativeBuildInputs
++ getFinalPassthru "build-system";

Expand All @@ -406,7 +459,7 @@ lib.extendMkDerivation {

# Python packages don't have a checkPhase, only an installCheckPhase
doCheck = false;
doInstallCheck = attrs.doCheck or true;
doInstallCheck = doCheck;
nativeInstallCheckInputs = nativeCheckInputs ++ attrs.nativeInstallCheckInputs or [ ];
installCheckInputs = checkInputs ++ attrs.installCheckInputs or [ ];

Expand All @@ -423,21 +476,89 @@ lib.extendMkDerivation {
python.pythonOnBuildForHost
];

outputs = outputs ++ optional withDistOutput "dist";

passthru = {
inherit
disabled
pyproject
build-system
dependencies
optional-dependencies
;
}
// {
updateScript = nix-update-script { };
}
// attrs.passthru or { };
outputs = outputs ++ optional withDistOutput "dist" ++ optional withTestSrcOutput "test_src";
}
// {
# Re-expose Python-specific attrs at the top-level of the returned
# attrset so that they're visible in the user's `finalAttrs` fixed-point
# view (see `pythonMkDerivation`). These are removed before being passed
# to `stdenv.mkDerivation`, so they never reach the `derivation` builtin.
inherit dependencies optional-dependencies build-system;
}
// optionalAttrs withTestSrcOutput {
inherit testPaths;
}
// {
passthru =
let
userPassthru = attrs.passthru or { };
# Forward test-related hooks from the parent derivation so that
# patchShebangs, environment setup, fixture preparation, etc. all
# apply in the auto-generated test derivation as well.
forwardedTestHookNames = [
"preCheck"
"postCheck"
"preInstallCheck"
"postInstallCheck"
"disabledTests"
"disabledTestPaths"
"enabledTestPaths"
"pytestFlags"
"pytestFlagsArray"
"unittestFlagsArray"
"PYTEST_DISABLE_PLUGIN_AUTOLOAD"
];
forwardedTestHooks = lib.filterAttrs (n: _: attrs ? ${n}) (
lib.genAttrs forwardedTestHookNames (n: attrs.${n} or null)
);
autoTests = optionalAttrs withTestSrcOutput {
python = stdenv.mkDerivation (
{
name = "${name}-tests";
src = finalAttrs.finalPackage.test_src;
dontConfigure = true;
dontBuild = true;
# We still produce an empty `$out` because some preDist hooks
# (e.g. pytest's `pytestcachePhase`) expect it to exist.
installPhase = "mkdir -p $out";
# Python check hooks (pytestCheckHook, unittestCheckHook, ...)
# append themselves to preDistPhases, which runs after the
# installCheckPhase. We enable both doCheck and doInstallCheck
# so that any user-provided checkPhase / installCheckPhase
# forwarded below also executes.
doCheck = true;
doInstallCheck = true;
nativeBuildInputs = [
python
finalAttrs.finalPackage
]
++ nativeCheckInputs;
buildInputs = checkInputs;
}
// forwardedTestHooks
// optionalAttrs (attrs ? checkPhase) {
# Mirror the parent's handling: a user-supplied checkPhase is
# really an installCheckPhase in Python land.
installCheckPhase = attrs.checkPhase;
}
// optionalAttrs (attrs ? installCheckPhase) {
inherit (attrs) installCheckPhase;
}
);
};
in
{
inherit
disabled
pyproject
build-system
dependencies
optional-dependencies
;
updateScript = nix-update-script { };
tests = autoTests // (userPassthru.tests or { });
}
// removeAttrs userPassthru [ "tests" ];

meta = {
# default to python's platforms
Expand Down
12 changes: 7 additions & 5 deletions python/pkgs/aiohttp/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@
zlib-ng,
}:

buildPythonPackage rec {
buildPythonPackage (finalAttrs: {
pname = "aiohttp";
version = "3.13.2";
pyproject = true;

src = fetchFromGitHub {
owner = "aio-libs";
repo = "aiohttp";
tag = "v${version}";
tag = "v${finalAttrs.version}";
hash = "sha256-LqYGrrWgSZazk0hjQvTFwqtU/PtMEaPi+m1Ya8Ds+pU=";
};

Expand Down Expand Up @@ -99,7 +99,7 @@ buildPythonPackage rec {
++ lib.optionals (pythonOlder "3.11") [
async-timeout
]
++ optional-dependencies.speedups;
++ finalAttrs.optional-dependencies.speedups;

optional-dependencies.speedups = [
aiodns
Expand Down Expand Up @@ -150,6 +150,8 @@ buildPythonPackage rec {
"test_close"
];

testPaths = [ "tests" ];

__darwinAllowLocalNetworking = true;

preCheck = ''
Expand All @@ -165,10 +167,10 @@ buildPythonPackage rec {
'';

meta = {
changelog = "https://docs.aiohttp.org/en/${src.tag}/changes.html";
changelog = "https://docs.aiohttp.org/en/${finalAttrs.src.tag}/changes.html";
description = "Asynchronous HTTP Client/Server for Python and asyncio";
license = lib.licenses.asl20;
homepage = "https://github.com/aio-libs/aiohttp";

};
}
})
15 changes: 11 additions & 4 deletions python/pkgs/babel/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@
tzdata,
}:

buildPythonPackage rec {
buildPythonPackage (finalAttrs: {
pname = "babel";
version = "2.17.0";
pyproject = true;

disabled = pythonOlder "3.7";

src = fetchPypi {
inherit pname version;
inherit (finalAttrs) pname version;
hash = "sha256-DFTP+xn2kM3MUqO1C8v3HgeoCNHIDVSfJFm50s8K+50=";
};

Expand All @@ -39,6 +39,13 @@ buildPythonPackage rec {
]
++ lib.optionals isPyPy [ tzdata ];

# Some tests read `babel/locale-data/*.dat` and `babel/global.dat` as
# plain files relative to the source root.
testPaths = [
"tests"
"babel"
];

disabledTests = [
# fails on days switching from and to daylight saving time in EST
# https://github.com/python-babel/babel/issues/988
Expand All @@ -50,9 +57,9 @@ buildPythonPackage rec {
meta = {
description = "Collection of internationalizing tools";
homepage = "https://babel.pocoo.org/";
changelog = "https://github.com/python-babel/babel/releases/tag/v${version}";
changelog = "https://github.com/python-babel/babel/releases/tag/v${finalAttrs.version}";
license = lib.licenses.bsd3;

mainProgram = "pybabel";
};
}
})
8 changes: 5 additions & 3 deletions python/pkgs/bcrypt/3.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
six,
}:

buildPythonPackage rec {
buildPythonPackage (finalAttrs: {
pname = "bcrypt";
version = "3.2.2";
pyproject = true;

src = fetchPypi {
inherit pname version;
inherit (finalAttrs) pname version;
hash = "sha256-QzxBDCF3BXcF2iqfLNAd0VdJOyp6wUyFk6FrPatra/s=";
};

Expand All @@ -29,6 +29,8 @@ buildPythonPackage rec {

nativeCheckInputs = [ pytestCheckHook ];

testPaths = [ "tests" ];

pythonImportsCheck = [ "bcrypt" ];

meta = {
Expand All @@ -37,4 +39,4 @@ buildPythonPackage rec {
license = lib.licenses.asl20;
maintainers = [ ];
};
}
})
Loading
Loading