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})