nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1# Generic builder only used for EOL and deprecated Python 2.
2
3{
4 lib,
5 config,
6 python,
7 # Allow passing in a custom stdenv to buildPython*.override
8 stdenv,
9 wrapPython,
10 unzip,
11 ensureNewerSourcesForZipFilesHook,
12 # Whether the derivation provides a Python module or not.
13 toPythonModule,
14 namePrefix,
15 update-python-libraries,
16 setuptools,
17 pipBuildHook,
18 pipInstallHook,
19 pythonCatchConflictsHook,
20 pythonImportsCheckHook,
21 pythonOutputDistHook,
22 pythonRemoveBinBytecodeHook,
23 pythonRemoveTestsDirHook,
24 setuptoolsBuildHook,
25 wheelUnpackHook,
26 eggUnpackHook,
27 eggBuildHook,
28 eggInstallHook,
29}:
30
31{
32 name ? "${attrs.pname}-${attrs.version}",
33
34 # Build-time dependencies for the package
35 nativeBuildInputs ? [ ],
36
37 # Run-time dependencies for the package
38 buildInputs ? [ ],
39
40 # Dependencies needed for running the checkPhase.
41 # These are added to buildInputs when doCheck = true.
42 checkInputs ? [ ],
43 nativeCheckInputs ? [ ],
44
45 # propagate build dependencies so in case we have A -> B -> C,
46 # C can import package A propagated by B
47 propagatedBuildInputs ? [ ],
48
49 # DEPRECATED: use propagatedBuildInputs
50 pythonPath ? [ ],
51
52 # Enabled to detect some (native)BuildInputs mistakes
53 strictDeps ? true,
54
55 outputs ? [ "out" ],
56
57 # used to disable derivation, useful for specific python versions
58 disabled ? false,
59
60 # Raise an error if two packages are installed with the same name
61 # TODO: For cross we probably need a different PYTHONPATH, or not
62 # add the runtime deps until after buildPhase.
63 catchConflicts ? (python.stdenv.hostPlatform == python.stdenv.buildPlatform),
64
65 # Additional arguments to pass to the makeWrapper function, which wraps
66 # generated binaries.
67 makeWrapperArgs ? [ ],
68
69 # Skip wrapping of python programs altogether
70 dontWrapPythonPrograms ? false,
71
72 # Don't use Pip to install a wheel
73 # Note this is actually a variable for the pipInstallPhase in pip's setupHook.
74 # It's included here to prevent an infinite recursion.
75 dontUsePipInstall ? false,
76
77 # Skip setting the PYTHONNOUSERSITE environment variable in wrapped programs
78 permitUserSite ? false,
79
80 # Remove bytecode from bin folder.
81 # When a Python script has the extension `.py`, bytecode is generated
82 # Typically, executables in bin have no extension, so no bytecode is generated.
83 # However, some packages do provide executables with extensions, and thus bytecode is generated.
84 removeBinBytecode ? true,
85
86 # Several package formats are supported.
87 # "setuptools" : Install a common setuptools/distutils based package. This builds a wheel.
88 # "wheel" : Install from a pre-compiled wheel.
89 # "pyproject": Install a package using a ``pyproject.toml`` file (PEP517). This builds a wheel.
90 # "egg": Install a package from an egg.
91 # "other" : Provide your own buildPhase and installPhase.
92 format ? "setuptools",
93
94 meta ? { },
95
96 passthru ? { },
97
98 doCheck ? true,
99
100 disabledTestPaths ? [ ],
101
102 ...
103}@attrs:
104
105let
106 withDistOutput = lib.elem format [
107 "pyproject"
108 "setuptools"
109 "wheel"
110 ];
111
112 name_ = name;
113
114 validatePythonMatches =
115 attrName:
116 let
117 isPythonModule =
118 drv:
119 # all pythonModules have the pythonModule attribute
120 (drv ? "pythonModule")
121 # Some pythonModules are turned in to a pythonApplication by setting the field to false
122 && (!builtins.isBool drv.pythonModule);
123 isMismatchedPython = drv: drv.pythonModule != python;
124
125 optionalLocation =
126 let
127 pos = builtins.unsafeGetAttrPos (if attrs ? "pname" then "pname" else "name") attrs;
128 in
129 lib.optionalString (pos != null) " at ${pos.file}:${toString pos.line}:${toString pos.column}";
130
131 leftPadName =
132 name: against:
133 let
134 len = lib.max (lib.stringLength name) (lib.stringLength against);
135 in
136 lib.strings.fixedWidthString len " " name;
137
138 throwMismatch =
139 drv:
140 let
141 myName = "'${namePrefix}${name}'";
142 theirName = "'${drv.name}'";
143 in
144 throw ''
145 Python version mismatch in ${myName}:
146
147 The Python derivation ${myName} depends on a Python derivation
148 named ${theirName}, but the two derivations use different versions
149 of Python:
150
151 ${leftPadName myName theirName} uses ${python}
152 ${leftPadName theirName myName} uses ${toString drv.pythonModule}
153
154 Possible solutions:
155
156 * If ${theirName} is a Python library, change the reference to ${theirName}
157 in the ${attrName} of ${myName} to use a ${theirName} built from the same
158 version of Python
159
160 * If ${theirName} is used as a tool during the build, move the reference to
161 ${theirName} in ${myName} from ${attrName} to nativeBuildInputs
162
163 * If ${theirName} provides executables that are called at run time, pass its
164 bin path to makeWrapperArgs:
165
166 makeWrapperArgs = [ "--prefix PATH : ''${lib.makeBinPath [ ${lib.getName drv} ] }" ];
167
168 ${optionalLocation}
169 '';
170
171 checkDrv = drv: if (isPythonModule drv) && (isMismatchedPython drv) then throwMismatch drv else drv;
172
173 in
174 inputs: map checkDrv inputs;
175
176 # Keep extra attributes from `attrs`, e.g., `patchPhase', etc.
177 self = toPythonModule (
178 stdenv.mkDerivation (
179 (removeAttrs attrs [
180 "disabled"
181 "checkPhase"
182 "checkInputs"
183 "nativeCheckInputs"
184 "doCheck"
185 "doInstallCheck"
186 "dontWrapPythonPrograms"
187 "catchConflicts"
188 "format"
189 "disabledTestPaths"
190 "outputs"
191 ])
192 // {
193
194 name = namePrefix + name_;
195
196 nativeBuildInputs = [
197 python
198 wrapPython
199 ensureNewerSourcesForZipFilesHook # move to wheel installer (pip) or builder (setuptools, ...)?
200 pythonRemoveTestsDirHook
201 ]
202 ++ lib.optionals catchConflicts [
203 pythonCatchConflictsHook
204 ]
205 ++ lib.optionals removeBinBytecode [
206 pythonRemoveBinBytecodeHook
207 ]
208 ++ lib.optionals (lib.hasSuffix "zip" (attrs.src.name or "")) [
209 unzip
210 ]
211 ++ lib.optionals (format == "setuptools") [
212 setuptoolsBuildHook
213 ]
214 ++ lib.optionals (format == "pyproject") [
215 pipBuildHook
216 ]
217 ++ lib.optionals (format == "wheel") [
218 wheelUnpackHook
219 ]
220 ++ lib.optionals (format == "egg") [
221 eggUnpackHook
222 eggBuildHook
223 eggInstallHook
224 ]
225 ++ lib.optionals (format != "other") [
226 pipInstallHook
227 ]
228 ++ lib.optionals (stdenv.buildPlatform == stdenv.hostPlatform) [
229 # This is a test, however, it should be ran independent of the checkPhase and checkInputs
230 pythonImportsCheckHook
231 ]
232 ++ lib.optionals withDistOutput [
233 pythonOutputDistHook
234 ]
235 ++ nativeBuildInputs;
236
237 buildInputs = validatePythonMatches "buildInputs" (buildInputs ++ pythonPath);
238
239 propagatedBuildInputs = validatePythonMatches "propagatedBuildInputs" (
240 propagatedBuildInputs
241 ++ [
242 # we propagate python even for packages transformed with 'toPythonApplication'
243 # this pollutes the PATH but avoids rebuilds
244 # see https://github.com/NixOS/nixpkgs/issues/170887 for more context
245 python
246 ]
247 );
248
249 inherit strictDeps;
250
251 LANG = "${if python.stdenv.hostPlatform.isDarwin then "en_US" else "C"}.UTF-8";
252
253 # Python packages don't have a checkPhase, only an installCheckPhase
254 doCheck = false;
255 doInstallCheck = attrs.doCheck or true;
256 nativeInstallCheckInputs = nativeCheckInputs;
257 installCheckInputs = checkInputs;
258
259 postFixup =
260 lib.optionalString (!dontWrapPythonPrograms) ''
261 wrapPythonPrograms
262 ''
263 + attrs.postFixup or "";
264
265 # Python packages built through cross-compilation are always for the host platform.
266 disallowedReferences = lib.optionals (python.stdenv.hostPlatform != python.stdenv.buildPlatform) [
267 python.pythonOnBuildForHost
268 ];
269
270 outputs = outputs ++ lib.optional withDistOutput "dist";
271
272 meta = {
273 # default to python's platforms
274 platforms = python.meta.platforms;
275 isBuildPythonPackage = python.meta.platforms;
276 }
277 // meta;
278 }
279 // lib.optionalAttrs (attrs ? checkPhase) {
280 # If given use the specified checkPhase, otherwise use the setup hook.
281 # Longer-term we should get rid of `checkPhase` and use `installCheckPhase`.
282 installCheckPhase = attrs.checkPhase;
283 }
284 // lib.optionalAttrs (disabledTestPaths != [ ]) {
285 disabledTestPaths = lib.escapeShellArgs disabledTestPaths;
286 }
287 )
288 );
289
290 passthru.updateScript =
291 let
292 filename = builtins.head (lib.splitString ":" self.meta.position);
293 in
294 attrs.passthru.updateScript or [
295 update-python-libraries
296 filename
297 ];
298in
299lib.extendDerivation (
300 disabled -> throw "${name} not supported for interpreter ${python.executable}"
301) passthru self