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