at master 9.2 kB view raw
1# Generic builder only used for EOL and deprecated Python 2. 2 3{ 4 lib, 5 config, 6 python, 7 wrapPython, 8 unzip, 9 ensureNewerSourcesForZipFilesHook, 10 # Whether the derivation provides a Python module or not. 11 toPythonModule, 12 namePrefix, 13 update-python-libraries, 14 setuptools, 15 pipBuildHook, 16 pipInstallHook, 17 pythonCatchConflictsHook, 18 pythonImportsCheckHook, 19 pythonOutputDistHook, 20 pythonRemoveBinBytecodeHook, 21 pythonRemoveTestsDirHook, 22 setuptoolsBuildHook, 23 wheelUnpackHook, 24 eggUnpackHook, 25 eggBuildHook, 26 eggInstallHook, 27}: 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 # 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 # "pyproject": Install a package using a ``pyproject.toml`` file (PEP517). This builds a wheel. 88 # "egg": Install a package from an egg. 89 # "other" : Provide your own buildPhase and installPhase. 90 format ? "setuptools", 91 92 meta ? { }, 93 94 passthru ? { }, 95 96 doCheck ? true, 97 98 disabledTestPaths ? [ ], 99 100 ... 101}@attrs: 102 103let 104 inherit (python) stdenv; 105 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: builtins.map (checkDrv) inputs; 175 176 # Keep extra attributes from `attrs`, e.g., `patchPhase', etc. 177 self = toPythonModule ( 178 stdenv.mkDerivation ( 179 (builtins.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