at 23.11-beta 10 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, setuptoolsBuildHook 23, setuptoolsCheckHook 24, wheelUnpackHook 25, eggUnpackHook 26, eggBuildHook 27, eggInstallHook 28}: 29 30{ name ? "${attrs.pname}-${attrs.version}" 31 32# Build-time dependencies for the package 33, nativeBuildInputs ? [] 34 35# Run-time dependencies for the package 36, buildInputs ? [] 37 38# Dependencies needed for running the checkPhase. 39# These are added to buildInputs when doCheck = true. 40, checkInputs ? [] 41, nativeCheckInputs ? [] 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# pyproject = true <-> format = "pyproject" 85# pyproject = false <-> format = "other" 86# https://github.com/NixOS/nixpkgs/issues/253154 87, pyproject ? null 88 89# Several package formats are supported. 90# "setuptools" : Install a common setuptools/distutils based package. This builds a wheel. 91# "wheel" : Install from a pre-compiled wheel. 92# "pyproject": Install a package using a ``pyproject.toml`` file (PEP517). This builds a wheel. 93# "egg": Install a package from an egg. 94# "other" : Provide your own buildPhase and installPhase. 95, format ? null 96 97, meta ? {} 98 99, passthru ? {} 100 101, doCheck ? config.doCheckByDefault or false 102 103, disabledTestPaths ? [] 104 105# Allow passing in a custom stdenv to buildPython* 106, stdenv ? python.stdenv 107 108, ... } @ attrs: 109 110assert (pyproject != null) -> (format == null); 111 112let 113 format' = 114 if pyproject != null then 115 if pyproject then 116 "pyproject" 117 else 118 "other" 119 else if format != null then 120 format 121 else 122 "setuptools"; 123 124 withDistOutput = lib.elem format' ["pyproject" "setuptools" "wheel"]; 125 126 name_ = name; 127 128 validatePythonMatches = attrName: let 129 isPythonModule = drv: 130 # all pythonModules have the pythonModule attribute 131 (drv ? "pythonModule") 132 # Some pythonModules are turned in to a pythonApplication by setting the field to false 133 && (!builtins.isBool drv.pythonModule); 134 isMismatchedPython = drv: drv.pythonModule != python; 135 136 optionalLocation = let 137 pos = builtins.unsafeGetAttrPos (if attrs ? "pname" then "pname" else "name") attrs; 138 in lib.optionalString (pos != null) " at ${pos.file}:${toString pos.line}:${toString pos.column}"; 139 140 leftPadName = name: against: let 141 len = lib.max (lib.stringLength name) (lib.stringLength against); 142 in lib.strings.fixedWidthString len " " name; 143 144 throwMismatch = drv: let 145 myName = "'${namePrefix}${name}'"; 146 theirName = "'${drv.name}'"; 147 in throw '' 148 Python version mismatch in ${myName}: 149 150 The Python derivation ${myName} depends on a Python derivation 151 named ${theirName}, but the two derivations use different versions 152 of Python: 153 154 ${leftPadName myName theirName} uses ${python} 155 ${leftPadName theirName myName} uses ${toString drv.pythonModule} 156 157 Possible solutions: 158 159 * If ${theirName} is a Python library, change the reference to ${theirName} 160 in the ${attrName} of ${myName} to use a ${theirName} built from the same 161 version of Python 162 163 * If ${theirName} is used as a tool during the build, move the reference to 164 ${theirName} in ${myName} from ${attrName} to nativeBuildInputs 165 166 * If ${theirName} provides executables that are called at run time, pass its 167 bin path to makeWrapperArgs: 168 169 makeWrapperArgs = [ "--prefix PATH : ''${lib.makeBinPath [ ${lib.getName drv } ] }" ]; 170 171 ${optionalLocation} 172 ''; 173 174 checkDrv = drv: 175 if (isPythonModule drv) && (isMismatchedPython drv) 176 then throwMismatch drv 177 else drv; 178 179 in inputs: builtins.map (checkDrv) inputs; 180 181 isBootstrapInstallPackage = builtins.elem (attrs.pname or null) [ 182 "flit-core" "installer" 183 ]; 184 185 isBootstrapPackage = isBootstrapInstallPackage || builtins.elem (attrs.pname or null) ([ 186 "build" "packaging" "pyproject-hooks" "wheel" 187 ] ++ lib.optionals (python.pythonOlder "3.11") [ 188 "tomli" 189 ]); 190 191 isSetuptoolsDependency = builtins.elem (attrs.pname or null) [ 192 "setuptools" "wheel" 193 ]; 194 195 # Keep extra attributes from `attrs`, e.g., `patchPhase', etc. 196 self = toPythonModule (stdenv.mkDerivation ((builtins.removeAttrs attrs [ 197 "disabled" "checkPhase" "checkInputs" "nativeCheckInputs" "doCheck" "doInstallCheck" "dontWrapPythonPrograms" "catchConflicts" "pyproject" "format" 198 "disabledTestPaths" "outputs" "stdenv" 199 ]) // { 200 201 name = namePrefix + name_; 202 203 nativeBuildInputs = [ 204 python 205 wrapPython 206 ensureNewerSourcesForZipFilesHook # move to wheel installer (pip) or builder (setuptools, flit, ...)? 207 pythonRemoveTestsDirHook 208 ] ++ lib.optionals (catchConflicts && !isBootstrapPackage && !isSetuptoolsDependency) [ 209 # 210 # 1. When building a package that is also part of the bootstrap chain, we 211 # must ignore conflicts after installation, because there will be one with 212 # the package in the bootstrap. 213 # 214 # 2. When a package is a dependency of setuptools, we must ignore conflicts 215 # because the hook that checks for conflicts uses setuptools. 216 # 217 pythonCatchConflictsHook 218 ] ++ lib.optionals removeBinBytecode [ 219 pythonRemoveBinBytecodeHook 220 ] ++ lib.optionals (lib.hasSuffix "zip" (attrs.src.name or "")) [ 221 unzip 222 ] ++ lib.optionals (format' == "setuptools") [ 223 setuptoolsBuildHook 224 ] ++ lib.optionals (format' == "pyproject") [( 225 if isBootstrapPackage then 226 pypaBuildHook.override { 227 inherit (python.pythonOnBuildForHost.pkgs.bootstrap) build; 228 wheel = null; 229 } 230 else 231 pypaBuildHook 232 )] ++ lib.optionals (format' == "wheel") [ 233 wheelUnpackHook 234 ] ++ lib.optionals (format' == "egg") [ 235 eggUnpackHook eggBuildHook eggInstallHook 236 ] ++ lib.optionals (format' != "other") [( 237 if isBootstrapInstallPackage then 238 pypaInstallHook.override { 239 inherit (python.pythonOnBuildForHost.pkgs.bootstrap) installer; 240 } 241 else 242 pypaInstallHook 243 )] ++ lib.optionals (stdenv.buildPlatform == stdenv.hostPlatform) [ 244 # This is a test, however, it should be ran independent of the checkPhase and checkInputs 245 pythonImportsCheckHook 246 ] ++ lib.optionals (python.pythonAtLeast "3.3") [ 247 # Optionally enforce PEP420 for python3 248 pythonNamespacesHook 249 ] ++ lib.optionals withDistOutput [ 250 pythonOutputDistHook 251 ] ++ nativeBuildInputs; 252 253 buildInputs = validatePythonMatches "buildInputs" (buildInputs ++ pythonPath); 254 255 propagatedBuildInputs = validatePythonMatches "propagatedBuildInputs" (propagatedBuildInputs ++ [ 256 # we propagate python even for packages transformed with 'toPythonApplication' 257 # this pollutes the PATH but avoids rebuilds 258 # see https://github.com/NixOS/nixpkgs/issues/170887 for more context 259 python 260 ]); 261 262 inherit strictDeps; 263 264 LANG = "${if python.stdenv.isDarwin then "en_US" else "C"}.UTF-8"; 265 266 # Python packages don't have a checkPhase, only an installCheckPhase 267 doCheck = false; 268 doInstallCheck = attrs.doCheck or true; 269 nativeInstallCheckInputs = [ 270 ] ++ lib.optionals (format' == "setuptools") [ 271 # Longer-term we should get rid of this and require 272 # users of this function to set the `installCheckPhase` or 273 # pass in a hook that sets it. 274 setuptoolsCheckHook 275 ] ++ nativeCheckInputs; 276 installCheckInputs = checkInputs; 277 278 postFixup = lib.optionalString (!dontWrapPythonPrograms) '' 279 wrapPythonPrograms 280 '' + attrs.postFixup or ""; 281 282 # Python packages built through cross-compilation are always for the host platform. 283 disallowedReferences = lib.optionals (python.stdenv.hostPlatform != python.stdenv.buildPlatform) [ python.pythonOnBuildForHost ]; 284 285 outputs = outputs ++ lib.optional withDistOutput "dist"; 286 287 meta = { 288 # default to python's platforms 289 platforms = python.meta.platforms; 290 isBuildPythonPackage = python.meta.platforms; 291 } // meta; 292 } // lib.optionalAttrs (attrs?checkPhase) { 293 # If given use the specified checkPhase, otherwise use the setup hook. 294 # Longer-term we should get rid of `checkPhase` and use `installCheckPhase`. 295 installCheckPhase = attrs.checkPhase; 296 } // lib.optionalAttrs (disabledTestPaths != []) { 297 disabledTestPaths = lib.escapeShellArgs disabledTestPaths; 298 })); 299 300 passthru.updateScript = let 301 filename = builtins.head (lib.splitString ":" self.meta.position); 302 in attrs.passthru.updateScript or [ update-python-libraries filename ]; 303in lib.extendDerivation 304 (disabled -> throw "${name} not supported for interpreter ${python.executable}") 305 passthru 306 self