Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
1# Generic builder. 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 nix-update-script, 14 setuptools, 15 pypaBuildHook, 16 pypaInstallHook, 17 pythonCatchConflictsHook, 18 pythonImportsCheckHook, 19 pythonNamespacesHook, 20 pythonOutputDistHook, 21 pythonRelaxDepsHook, 22 pythonRemoveBinBytecodeHook, 23 pythonRemoveTestsDirHook, 24 pythonRuntimeDepsCheckHook, 25 setuptoolsBuildHook, 26 wheelUnpackHook, 27 eggUnpackHook, 28 eggBuildHook, 29 eggInstallHook, 30}: 31 32let 33 inherit (builtins) unsafeGetAttrPos; 34 inherit (lib) 35 elem 36 extendDerivation 37 fixedWidthString 38 flip 39 getName 40 hasSuffix 41 head 42 isBool 43 max 44 optional 45 optionalAttrs 46 optionals 47 optionalString 48 removePrefix 49 splitString 50 stringLength 51 ; 52 53 getOptionalAttrs = 54 names: attrs: lib.getAttrs (lib.intersectLists names (lib.attrNames attrs)) attrs; 55 56 leftPadName = 57 name: against: 58 let 59 len = max (stringLength name) (stringLength against); 60 in 61 fixedWidthString len " " name; 62 63 isPythonModule = 64 drv: 65 # all pythonModules have the pythonModule attribute 66 (drv ? "pythonModule") 67 # Some pythonModules are turned in to a pythonApplication by setting the field to false 68 && (!isBool drv.pythonModule); 69 70 isMismatchedPython = drv: drv.pythonModule != python; 71 72 withDistOutput' = flip elem [ 73 "pyproject" 74 "setuptools" 75 "wheel" 76 ]; 77 78 isBootstrapInstallPackage' = flip elem [ 79 "flit-core" 80 "installer" 81 ]; 82 83 isBootstrapPackage' = flip elem ( 84 [ 85 "build" 86 "packaging" 87 "pyproject-hooks" 88 "wheel" 89 ] 90 ++ optionals (python.pythonOlder "3.11") [ 91 "tomli" 92 ] 93 ); 94 95 isSetuptoolsDependency' = flip elem [ 96 "setuptools" 97 "wheel" 98 ]; 99 100 cleanAttrs = flip removeAttrs [ 101 "disabled" 102 "checkPhase" 103 "checkInputs" 104 "nativeCheckInputs" 105 "doCheck" 106 "doInstallCheck" 107 "pyproject" 108 "format" 109 "stdenv" 110 "dependencies" 111 "optional-dependencies" 112 "build-system" 113 ]; 114 115in 116 117{ 118 # Build-time dependencies for the package 119 nativeBuildInputs ? [ ], 120 121 # Run-time dependencies for the package 122 buildInputs ? [ ], 123 124 # Dependencies needed for running the checkPhase. 125 # These are added to buildInputs when doCheck = true. 126 checkInputs ? [ ], 127 nativeCheckInputs ? [ ], 128 129 # propagate build dependencies so in case we have A -> B -> C, 130 # C can import package A propagated by B 131 propagatedBuildInputs ? [ ], 132 133 # Python module dependencies. 134 # These are named after PEP-621. 135 dependencies ? [ ], 136 optional-dependencies ? { }, 137 138 # Python PEP-517 build systems. 139 build-system ? [ ], 140 141 # DEPRECATED: use propagatedBuildInputs 142 pythonPath ? [ ], 143 144 # Enabled to detect some (native)BuildInputs mistakes 145 strictDeps ? true, 146 147 outputs ? [ "out" ], 148 149 # used to disable derivation, useful for specific python versions 150 disabled ? false, 151 152 # Raise an error if two packages are installed with the same name 153 # TODO: For cross we probably need a different PYTHONPATH, or not 154 # add the runtime deps until after buildPhase. 155 catchConflicts ? (python.stdenv.hostPlatform == python.stdenv.buildPlatform), 156 157 # Additional arguments to pass to the makeWrapper function, which wraps 158 # generated binaries. 159 makeWrapperArgs ? [ ], 160 161 # Skip wrapping of python programs altogether 162 dontWrapPythonPrograms ? false, 163 164 # Don't use Pip to install a wheel 165 # Note this is actually a variable for the pipInstallPhase in pip's setupHook. 166 # It's included here to prevent an infinite recursion. 167 dontUsePipInstall ? false, 168 169 # Skip setting the PYTHONNOUSERSITE environment variable in wrapped programs 170 permitUserSite ? false, 171 172 # Remove bytecode from bin folder. 173 # When a Python script has the extension `.py`, bytecode is generated 174 # Typically, executables in bin have no extension, so no bytecode is generated. 175 # However, some packages do provide executables with extensions, and thus bytecode is generated. 176 removeBinBytecode ? true, 177 178 # pyproject = true <-> format = "pyproject" 179 # pyproject = false <-> format = "other" 180 # https://github.com/NixOS/nixpkgs/issues/253154 181 pyproject ? null, 182 183 # Several package formats are supported. 184 # "setuptools" : Install a common setuptools/distutils based package. This builds a wheel. 185 # "wheel" : Install from a pre-compiled wheel. 186 # "pyproject": Install a package using a ``pyproject.toml`` file (PEP517). This builds a wheel. 187 # "egg": Install a package from an egg. 188 # "other" : Provide your own buildPhase and installPhase. 189 format ? null, 190 191 meta ? { }, 192 193 doCheck ? true, 194 195 # Allow passing in a custom stdenv to buildPython* 196 stdenv ? python.stdenv, 197 198 ... 199}@attrs: 200 201let 202 # Keep extra attributes from `attrs`, e.g., `patchPhase', etc. 203 self = stdenv.mkDerivation ( 204 finalAttrs: 205 let 206 getFinalPassthru = 207 let 208 pos = unsafeGetAttrPos "passthru" finalAttrs; 209 in 210 attrName: 211 finalAttrs.passthru.${attrName} or (throw ( 212 '' 213 ${finalAttrs.name}: passthru.${attrName} missing after overrideAttrs overriding. 214 '' 215 + optionalString (pos != null) '' 216 Last overridden at ${pos.file}:${toString pos.line} 217 '' 218 )); 219 220 format' = 221 assert (getFinalPassthru "pyproject" != null) -> (format == null); 222 if getFinalPassthru "pyproject" != null then 223 if getFinalPassthru "pyproject" then "pyproject" else "other" 224 else if format != null then 225 format 226 else 227 throw "${name} does not configure a `format`. To build with setuptools as before, set `pyproject = true` and `build-system = [ setuptools ]`.`"; 228 229 withDistOutput = withDistOutput' format'; 230 231 validatePythonMatches = 232 let 233 throwMismatch = 234 attrName: drv: 235 let 236 myName = "'${finalAttrs.name}'"; 237 theirName = "'${drv.name}'"; 238 optionalLocation = 239 let 240 pos = unsafeGetAttrPos (if attrs ? "pname" then "pname" else "name") attrs; 241 in 242 optionalString (pos != null) " at ${pos.file}:${toString pos.line}:${toString pos.column}"; 243 in 244 throw '' 245 Python version mismatch in ${myName}: 246 247 The Python derivation ${myName} depends on a Python derivation 248 named ${theirName}, but the two derivations use different versions 249 of Python: 250 251 ${leftPadName myName theirName} uses ${python} 252 ${leftPadName theirName myName} uses ${toString drv.pythonModule} 253 254 Possible solutions: 255 256 * If ${theirName} is a Python library, change the reference to ${theirName} 257 in the ${attrName} of ${myName} to use a ${theirName} built from the same 258 version of Python 259 260 * If ${theirName} is used as a tool during the build, move the reference to 261 ${theirName} in ${myName} from ${attrName} to nativeBuildInputs 262 263 * If ${theirName} provides executables that are called at run time, pass its 264 bin path to makeWrapperArgs: 265 266 makeWrapperArgs = [ "--prefix PATH : ''${lib.makeBinPath [ ${getName drv} ] }" ]; 267 268 ${optionalLocation} 269 ''; 270 271 checkDrv = 272 attrName: drv: 273 if (isPythonModule drv) && (isMismatchedPython drv) then throwMismatch attrName drv else drv; 274 275 in 276 attrName: inputs: map (checkDrv attrName) inputs; 277 278 isBootstrapInstallPackage = isBootstrapInstallPackage' (finalAttrs.pname or null); 279 280 isBootstrapPackage = isBootstrapInstallPackage || isBootstrapPackage' (finalAttrs.pname or null); 281 282 isSetuptoolsDependency = isSetuptoolsDependency' (finalAttrs.pname or null); 283 284 name = namePrefix + attrs.name or "${finalAttrs.pname}-${finalAttrs.version}"; 285 286 in 287 (cleanAttrs attrs) 288 // { 289 inherit name; 290 291 inherit catchConflicts; 292 293 nativeBuildInputs = [ 294 python 295 wrapPython 296 ensureNewerSourcesForZipFilesHook # move to wheel installer (pip) or builder (setuptools, flit, ...)? 297 pythonRemoveTestsDirHook 298 ] 299 ++ optionals (finalAttrs.catchConflicts && !isBootstrapPackage && !isSetuptoolsDependency) [ 300 # 301 # 1. When building a package that is also part of the bootstrap chain, we 302 # must ignore conflicts after installation, because there will be one with 303 # the package in the bootstrap. 304 # 305 # 2. When a package is a dependency of setuptools, we must ignore conflicts 306 # because the hook that checks for conflicts uses setuptools. 307 # 308 pythonCatchConflictsHook 309 ] 310 ++ 311 optionals (finalAttrs.pythonRelaxDeps or [ ] != [ ] || finalAttrs.pythonRemoveDeps or [ ] != [ ]) 312 [ 313 pythonRelaxDepsHook 314 ] 315 ++ optionals removeBinBytecode [ 316 pythonRemoveBinBytecodeHook 317 ] 318 ++ optionals (hasSuffix "zip" (finalAttrs.src.name or "")) [ 319 unzip 320 ] 321 ++ optionals (format' == "setuptools") [ 322 setuptoolsBuildHook 323 ] 324 ++ optionals (format' == "pyproject") [ 325 ( 326 if isBootstrapPackage then 327 pypaBuildHook.override { 328 inherit (python.pythonOnBuildForHost.pkgs.bootstrap) build; 329 wheel = null; 330 } 331 else 332 pypaBuildHook 333 ) 334 ( 335 if isBootstrapPackage then 336 pythonRuntimeDepsCheckHook.override { 337 inherit (python.pythonOnBuildForHost.pkgs.bootstrap) packaging; 338 } 339 else 340 pythonRuntimeDepsCheckHook 341 ) 342 ] 343 ++ optionals (format' == "wheel") [ 344 wheelUnpackHook 345 ] 346 ++ optionals (format' == "egg") [ 347 eggUnpackHook 348 eggBuildHook 349 eggInstallHook 350 ] 351 ++ optionals (format' != "other") [ 352 ( 353 if isBootstrapInstallPackage then 354 pypaInstallHook.override { 355 inherit (python.pythonOnBuildForHost.pkgs.bootstrap) installer; 356 } 357 else 358 pypaInstallHook 359 ) 360 ] 361 ++ optionals (stdenv.buildPlatform == stdenv.hostPlatform) [ 362 # This is a test, however, it should be ran independent of the checkPhase and checkInputs 363 pythonImportsCheckHook 364 ] 365 ++ optionals (python.pythonAtLeast "3.3") [ 366 # Optionally enforce PEP420 for python3 367 pythonNamespacesHook 368 ] 369 ++ optionals withDistOutput [ 370 pythonOutputDistHook 371 ] 372 ++ nativeBuildInputs 373 ++ getFinalPassthru "build-system"; 374 375 buildInputs = validatePythonMatches "buildInputs" (buildInputs ++ pythonPath); 376 377 propagatedBuildInputs = validatePythonMatches "propagatedBuildInputs" ( 378 propagatedBuildInputs 379 ++ getFinalPassthru "dependencies" 380 ++ [ 381 # we propagate python even for packages transformed with 'toPythonApplication' 382 # this pollutes the PATH but avoids rebuilds 383 # see https://github.com/NixOS/nixpkgs/issues/170887 for more context 384 python 385 ] 386 ); 387 388 inherit strictDeps; 389 390 LANG = "${if python.stdenv.hostPlatform.isDarwin then "en_US" else "C"}.UTF-8"; 391 392 # Python packages don't have a checkPhase, only an installCheckPhase 393 doCheck = false; 394 doInstallCheck = attrs.doCheck or true; 395 nativeInstallCheckInputs = nativeCheckInputs ++ attrs.nativeInstallCheckInputs or [ ]; 396 installCheckInputs = checkInputs ++ attrs.installCheckInputs or [ ]; 397 398 inherit dontWrapPythonPrograms; 399 400 postFixup = 401 optionalString (!finalAttrs.dontWrapPythonPrograms) '' 402 wrapPythonPrograms 403 '' 404 + attrs.postFixup or ""; 405 406 # Python packages built through cross-compilation are always for the host platform. 407 disallowedReferences = optionals (python.stdenv.hostPlatform != python.stdenv.buildPlatform) [ 408 python.pythonOnBuildForHost 409 ]; 410 411 outputs = outputs ++ optional withDistOutput "dist"; 412 413 passthru = { 414 inherit 415 disabled 416 pyproject 417 build-system 418 dependencies 419 optional-dependencies 420 ; 421 } 422 // { 423 updateScript = nix-update-script { }; 424 } 425 // attrs.passthru or { }; 426 427 meta = { 428 # default to python's platforms 429 platforms = python.meta.platforms; 430 isBuildPythonPackage = python.meta.platforms; 431 } 432 // meta; 433 } 434 // optionalAttrs (attrs ? checkPhase) { 435 # If given use the specified checkPhase, otherwise use the setup hook. 436 # Longer-term we should get rid of `checkPhase` and use `installCheckPhase`. 437 installCheckPhase = attrs.checkPhase; 438 } 439 // 440 lib.mapAttrs 441 ( 442 name: value: 443 lib.throwIf ( 444 attrs.${name} == [ ] 445 ) "${lib.getName finalAttrs}: ${name} must be unspecified, null or a non-empty list." attrs.${name} 446 ) 447 ( 448 getOptionalAttrs [ 449 "enabledTestMarks" 450 "enabledTestPaths" 451 "enabledTests" 452 ] attrs 453 ) 454 ); 455 456 # This derivation transformation function must be independent to `attrs` 457 # for fixed-point arguments support in the future. 458 transformDrv = 459 let 460 # Workaround to make the `lib.extendDerivation`-based disabled functionality 461 # respect `<pkg>.overrideAttrs` 462 # It doesn't cover `<pkg>.<output>.overrideAttrs`. 463 disablePythonPackage = 464 drv: 465 extendDerivation ( 466 drv.disabled 467 -> throw "${removePrefix namePrefix drv.name} not supported for interpreter ${python.executable}" 468 ) { } drv 469 // { 470 overrideAttrs = fdrv: disablePythonPackage (drv.overrideAttrs fdrv); 471 }; 472 in 473 drv: disablePythonPackage (toPythonModule drv); 474 475in 476transformDrv self