1# shellcheck shell=bash
2
3# Setup hook that modifies Python dependencies versions.
4#
5# Example usage in a derivation:
6#
7# { …, python3Packages, … }:
8#
9# python3Packages.buildPythonPackage {
10# …
11# # This will relax the dependency restrictions
12# # e.g.: abc>1,<=2 -> abc
13# pythonRelaxDeps = [ "abc" ];
14# # This will relax all dependencies restrictions instead
15# # pythonRelaxDeps = true;
16# # This will remove the dependency
17# # e.g.: cde>1,<=2 -> <nothing>
18# pythonRemoveDeps = [ "cde" ];
19# # This will remove all dependencies from the project
20# # pythonRemoveDeps = true;
21# …
22# }
23#
24# IMPLEMENTATION NOTES:
25#
26# The "Requires-Dist" dependency specification format is described in PEP 508.
27# Examples that the regular expressions in this hook needs to support:
28#
29# Requires-Dist: foo
30# -> foo
31# Requires-Dist: foo[optional]
32# -> foo[optional]
33# Requires-Dist: foo[optional]~=1.2.3
34# -> foo[optional]
35# Requires-Dist: foo[optional, xyz] (~=1.2.3)
36# -> foo[optional, xyz]
37# Requires-Dist: foo[optional]~=1.2.3 ; os_name = "posix"
38# -> foo[optional] ; os_name = "posix"
39#
40# Currently unsupported: URL specs (foo @ https://example.com/a.zip).
41
42_pythonRelaxDeps() {
43 local -r metadata_file="$1"
44
45 if [[ -z "${pythonRelaxDeps[*]-}" ]] || [[ "$pythonRelaxDeps" == 0 ]]; then
46 return
47 elif [[ "$pythonRelaxDeps" == 1 ]]; then
48 sed -i "$metadata_file" -r \
49 -e 's/(Requires-Dist: [a-zA-Z0-9_.-]+\s*(\[[^]]+\])?)[^;]*(;.*)?/\1\3/'
50 else
51 # shellcheck disable=SC2048
52 for dep in ${pythonRelaxDeps[*]}; do
53 sed -i "$metadata_file" -r \
54 -e "s/(Requires-Dist: $dep\s*(\[[^]]+\])?)[^;]*(;.*)?/\1\3/i"
55 done
56 fi
57}
58
59_pythonRemoveDeps() {
60 local -r metadata_file="$1"
61
62 if [[ -z "${pythonRemoveDeps[*]-}" ]] || [[ "$pythonRemoveDeps" == 0 ]]; then
63 return
64 elif [[ "$pythonRemoveDeps" == 1 ]]; then
65 sed -i "$metadata_file" \
66 -e '/Requires-Dist:.*/d'
67 else
68 # shellcheck disable=SC2048
69 for dep in ${pythonRemoveDeps[*]-}; do
70 sed -i "$metadata_file" \
71 -e "/Requires-Dist: $dep/d"
72 done
73 fi
74
75}
76
77pythonRelaxDepsHook() {
78 pushd dist
79
80 local -r unpack_dir="unpacked"
81 local -r metadata_file="$unpack_dir/*/*.dist-info/METADATA"
82
83 # We generally shouldn't have multiple wheel files, but let's be safer here
84 for wheel in *".whl"; do
85
86 PYTHONPATH="@wheel@/@pythonSitePackages@:$PYTHONPATH" \
87 @pythonInterpreter@ -m wheel unpack --dest "$unpack_dir" "$wheel"
88 rm -rf "$wheel"
89
90 # Using no quotes on purpose since we need to expand the glob from `$metadata_file`
91 # shellcheck disable=SC2086
92 _pythonRelaxDeps $metadata_file
93 # shellcheck disable=SC2086
94 _pythonRemoveDeps $metadata_file
95
96 if (("${NIX_DEBUG:-0}" >= 1)); then
97 echo "pythonRelaxDepsHook: resulting METADATA for '$wheel':"
98 # shellcheck disable=SC2086
99 cat $metadata_file
100 fi
101
102 PYTHONPATH="@wheel@/@pythonSitePackages@:$PYTHONPATH" \
103 @pythonInterpreter@ -m wheel pack "$unpack_dir/"*
104 done
105
106 # Remove the folder since it will otherwise be in the dist output.
107 rm -rf "$unpack_dir"
108
109 popd
110}
111
112postBuild+=" pythonRelaxDepsHook"