1# Generic builder.
2
3{ lib
4, config
5, python
6, wrapPython
7, unzip
8, ensureNewerSourcesForZipFilesHook
9# Whether the derivation provides a Python module or not.
10, toPythonModule
11, namePrefix
12, update-python-libraries
13, setuptools
14, flitBuildHook
15, pipBuildHook
16, pipInstallHook
17, pythonCatchConflictsHook
18, pythonImportsCheckHook
19, pythonNamespacesHook
20, pythonOutputDistHook
21, pythonRemoveBinBytecodeHook
22, pythonRemoveTestsDirHook
23, setuptoolsBuildHook
24, setuptoolsCheckHook
25, wheelUnpackHook
26, eggUnpackHook
27, eggBuildHook
28, eggInstallHook
29}:
30
31{ name ? "${attrs.pname}-${attrs.version}"
32
33# Build-time dependencies for the package
34, nativeBuildInputs ? []
35
36# Run-time dependencies for the package
37, buildInputs ? []
38
39# Dependencies needed for running the checkPhase.
40# These are added to buildInputs when doCheck = true.
41, checkInputs ? []
42
43# propagate build dependencies so in case we have A -> B -> C,
44# C can import package A propagated by B
45, propagatedBuildInputs ? []
46
47# DEPRECATED: use propagatedBuildInputs
48, pythonPath ? []
49
50# Enabled to detect some (native)BuildInputs mistakes
51, strictDeps ? true
52
53, outputs ? [ "out" ]
54
55# used to disable derivation, useful for specific python versions
56, disabled ? false
57
58# Raise an error if two packages are installed with the same name
59# TODO: For cross we probably need a different PYTHONPATH, or not
60# add the runtime deps until after buildPhase.
61, catchConflicts ? (python.stdenv.hostPlatform == python.stdenv.buildPlatform)
62
63# Additional arguments to pass to the makeWrapper function, which wraps
64# generated binaries.
65, makeWrapperArgs ? []
66
67# Skip wrapping of python programs altogether
68, dontWrapPythonPrograms ? false
69
70# Don't use Pip to install a wheel
71# Note this is actually a variable for the pipInstallPhase in pip's setupHook.
72# It's included here to prevent an infinite recursion.
73, dontUsePipInstall ? false
74
75# Skip setting the PYTHONNOUSERSITE environment variable in wrapped programs
76, permitUserSite ? false
77
78# Remove bytecode from bin folder.
79# When a Python script has the extension `.py`, bytecode is generated
80# Typically, executables in bin have no extension, so no bytecode is generated.
81# However, some packages do provide executables with extensions, and thus bytecode is generated.
82, removeBinBytecode ? true
83
84# Several package formats are supported.
85# "setuptools" : Install a common setuptools/distutils based package. This builds a wheel.
86# "wheel" : Install from a pre-compiled wheel.
87# "flit" : Install a flit package. This builds a wheel.
88# "pyproject": Install a package using a ``pyproject.toml`` file (PEP517). This builds a wheel.
89# "egg": Install a package from an egg.
90# "other" : Provide your own buildPhase and installPhase.
91, format ? "setuptools"
92
93, meta ? {}
94
95, passthru ? {}
96
97, doCheck ? config.doCheckByDefault or false
98
99, disabledTestPaths ? []
100
101, ... } @ attrs:
102
103
104# Keep extra attributes from `attrs`, e.g., `patchPhase', etc.
105if disabled
106then throw "${name} not supported for interpreter ${python.executable}"
107else
108
109let
110 inherit (python) stdenv;
111
112 withDistOutput = lib.elem format ["pyproject" "setuptools" "flit" "wheel"];
113
114 name_ = name;
115
116 self = toPythonModule (stdenv.mkDerivation ((builtins.removeAttrs attrs [
117 "disabled" "checkPhase" "checkInputs" "doCheck" "doInstallCheck" "dontWrapPythonPrograms" "catchConflicts" "format"
118 "disabledTestPaths" "outputs"
119 ]) // {
120
121 name = namePrefix + name_;
122
123 nativeBuildInputs = [
124 python
125 wrapPython
126 ensureNewerSourcesForZipFilesHook # move to wheel installer (pip) or builder (setuptools, flit, ...)?
127 pythonRemoveTestsDirHook
128 ] ++ lib.optionals catchConflicts [
129 pythonCatchConflictsHook
130 ] ++ lib.optionals removeBinBytecode [
131 pythonRemoveBinBytecodeHook
132 ] ++ lib.optionals (lib.hasSuffix "zip" (attrs.src.name or "")) [
133 unzip
134 ] ++ lib.optionals (format == "setuptools") [
135 setuptoolsBuildHook
136 ] ++ lib.optionals (format == "flit") [
137 flitBuildHook
138 ] ++ lib.optionals (format == "pyproject") [
139 pipBuildHook
140 ] ++ lib.optionals (format == "wheel") [
141 wheelUnpackHook
142 ] ++ lib.optionals (format == "egg") [
143 eggUnpackHook eggBuildHook eggInstallHook
144 ] ++ lib.optionals (!(format == "other") || dontUsePipInstall) [
145 pipInstallHook
146 ] ++ lib.optionals (stdenv.buildPlatform == stdenv.hostPlatform) [
147 # This is a test, however, it should be ran independent of the checkPhase and checkInputs
148 pythonImportsCheckHook
149 ] ++ lib.optionals (python.pythonAtLeast "3.3") [
150 # Optionally enforce PEP420 for python3
151 pythonNamespacesHook
152 ] ++ lib.optionals withDistOutput [
153 pythonOutputDistHook
154 ] ++ nativeBuildInputs;
155
156 buildInputs = buildInputs ++ pythonPath;
157
158 propagatedBuildInputs = propagatedBuildInputs ++ [
159 # we propagate python even for packages transformed with 'toPythonApplication'
160 # this pollutes the PATH but avoids rebuilds
161 # see https://github.com/NixOS/nixpkgs/issues/170887 for more context
162 python
163 ];
164
165 inherit strictDeps;
166
167 LANG = "${if python.stdenv.isDarwin then "en_US" else "C"}.UTF-8";
168
169 # Python packages don't have a checkPhase, only an installCheckPhase
170 doCheck = false;
171 doInstallCheck = attrs.doCheck or true;
172 installCheckInputs = [
173 ] ++ lib.optionals (format == "setuptools") [
174 # Longer-term we should get rid of this and require
175 # users of this function to set the `installCheckPhase` or
176 # pass in a hook that sets it.
177 setuptoolsCheckHook
178 ] ++ checkInputs;
179
180 postFixup = lib.optionalString (!dontWrapPythonPrograms) ''
181 wrapPythonPrograms
182 '' + attrs.postFixup or "";
183
184 # Python packages built through cross-compilation are always for the host platform.
185 disallowedReferences = lib.optionals (python.stdenv.hostPlatform != python.stdenv.buildPlatform) [ python.pythonForBuild ];
186
187 outputs = outputs ++ lib.optional withDistOutput "dist";
188
189 meta = {
190 # default to python's platforms
191 platforms = python.meta.platforms;
192 isBuildPythonPackage = python.meta.platforms;
193 } // meta;
194 } // lib.optionalAttrs (attrs?checkPhase) {
195 # If given use the specified checkPhase, otherwise use the setup hook.
196 # Longer-term we should get rid of `checkPhase` and use `installCheckPhase`.
197 installCheckPhase = attrs.checkPhase;
198 } // lib.optionalAttrs (disabledTestPaths != []) {
199 disabledTestPaths = lib.escapeShellArgs disabledTestPaths;
200 }));
201
202 passthru.updateScript = let
203 filename = builtins.head (lib.splitString ":" self.meta.position);
204 in attrs.passthru.updateScript or [ update-python-libraries filename ];
205in lib.extendDerivation true passthru self