1/* This function provides a generic Python package builder. It is 2 intended to work with packages that use `distutils/setuptools' 3 (http://pypi.python.org/pypi/setuptools/), which represents a large 4 number of Python packages nowadays. */ 5 6{ python, setuptools, unzip, wrapPython, lib, bootstrapped-pip 7, ensureNewerSourcesHook }: 8 9{ name 10 11# by default prefix `name` e.g. "python3.3-${name}" 12, namePrefix ? python.libPrefix + "-" 13 14, buildInputs ? [] 15 16# propagate build dependencies so in case we have A -> B -> C, 17# C can import package A propagated by B 18, propagatedBuildInputs ? [] 19 20# passed to "python setup.py build_ext" 21# https://github.com/pypa/pip/issues/881 22, setupPyBuildFlags ? [] 23 24# enable tests by default 25, doCheck ? true 26 27# DEPRECATED: use propagatedBuildInputs 28, pythonPath ? [] 29 30# used to disable derivation, useful for specific python versions 31, disabled ? false 32 33, meta ? {} 34 35# Execute before shell hook 36, preShellHook ? "" 37 38# Execute after shell hook 39, postShellHook ? "" 40 41# Additional arguments to pass to the makeWrapper function, which wraps 42# generated binaries. 43, makeWrapperArgs ? [] 44 45# Additional flags to pass to "pip install". 46, installFlags ? [] 47 48, ... } @ attrs: 49 50 51# Keep extra attributes from `attrs`, e.g., `patchPhase', etc. 52if disabled 53then throw "${name} not supported for interpreter ${python.executable}" 54else 55 56let 57 # use setuptools shim (so that setuptools is imported before distutils) 58 # pip does the same thing: https://github.com/pypa/pip/pull/3265 59 setuppy = ./run_setup.py; 60 # For backwards compatibility, let's use an alias 61 doInstallCheck = doCheck; 62in 63python.stdenv.mkDerivation (builtins.removeAttrs attrs ["disabled" "doCheck"] // { 64 name = namePrefix + name; 65 66 buildInputs = [ wrapPython bootstrapped-pip ] ++ buildInputs ++ pythonPath 67 ++ [ (ensureNewerSourcesHook { year = "1980"; }) ] 68 ++ (lib.optional (lib.hasSuffix "zip" attrs.src.name or "") unzip); 69 70 # propagate python/setuptools to active setup-hook in nix-shell 71 propagatedBuildInputs = propagatedBuildInputs ++ [ python setuptools ]; 72 73 pythonPath = pythonPath; 74 75 configurePhase = attrs.configurePhase or '' 76 runHook preConfigure 77 78 # patch python interpreter to write null timestamps when compiling python files 79 # this way python doesn't try to update them when we freeze timestamps in nix store 80 export DETERMINISTIC_BUILD=1 81 82 runHook postConfigure 83 ''; 84 85 # we copy nix_run_setup.py over so it's executed relative to the root of the source 86 # many project make that assumption 87 buildPhase = attrs.buildPhase or '' 88 runHook preBuild 89 cp ${setuppy} nix_run_setup.py 90 ${python.interpreter} nix_run_setup.py ${lib.optionalString (setupPyBuildFlags != []) ("build_ext " + (lib.concatStringsSep " " setupPyBuildFlags))} bdist_wheel 91 runHook postBuild 92 ''; 93 94 installPhase = attrs.installPhase or '' 95 runHook preInstall 96 97 mkdir -p "$out/${python.sitePackages}" 98 export PYTHONPATH="$out/${python.sitePackages}:$PYTHONPATH" 99 100 pushd dist 101 ${bootstrapped-pip}/bin/pip install *.whl --no-index --prefix=$out --no-cache ${toString installFlags} 102 popd 103 104 runHook postInstall 105 ''; 106 107 # We run all tests after software has been installed since that is 108 # a common idiom in Python 109 doInstallCheck = doInstallCheck; 110 111 installCheckPhase = attrs.checkPhase or '' 112 runHook preCheck 113 ${python.interpreter} nix_run_setup.py test 114 runHook postCheck 115 ''; 116 117 postFixup = attrs.postFixup or '' 118 wrapPythonPrograms 119 120 # check if we have two packages with the same name in closure and fail 121 # this shouldn't happen, something went wrong with dependencies specs 122 ${python.interpreter} ${./catch_conflicts.py} 123 ''; 124 125 shellHook = attrs.shellHook or '' 126 ${preShellHook} 127 if test -e setup.py; then 128 tmp_path=$(mktemp -d) 129 export PATH="$tmp_path/bin:$PATH" 130 export PYTHONPATH="$tmp_path/${python.sitePackages}:$PYTHONPATH" 131 mkdir -p $tmp_path/${python.sitePackages} 132 ${bootstrapped-pip}/bin/pip install -e . --prefix $tmp_path 133 fi 134 ${postShellHook} 135 ''; 136 137 meta = with lib.maintainers; { 138 # default to python's platforms 139 platforms = python.meta.platforms; 140 } // meta // { 141 # add extra maintainer(s) to every package 142 maintainers = (meta.maintainers or []) ++ [ chaoflow iElectric ]; 143 # a marker for release utilities to discover python packages 144 isBuildPythonPackage = python.meta.platforms; 145 }; 146})