1# shellcheck shell=bash
2
3# Setup hook that modifies Python dependencies versions.
4#
5# Example usage in a derivation:
6#
7# { …, pythonPackages, … }:
8#
9# pythonPackages.buildPythonPackage {
10# …
11# nativeBuildInputs = [ pythonPackages.pythonRelaxDepsHook ];
12#
13# # This will relax the dependency restrictions
14# # e.g.: abc>1,<=2 -> abc
15# pythonRelaxDeps = [ "abc" ];
16# # This will relax all dependencies restrictions instead
17# # pythonRelaxDeps = true;
18# # This will remove the dependency
19# # e.g.: cde>1,<=2 -> <nothing>
20# pythonRemoveDeps = [ "cde" ];
21# # This will remove all dependencies from the project
22# # pythonRemoveDeps = true;
23# …
24# }
25#
26# IMPLEMENTATION NOTES:
27#
28# The "Requires-Dist" dependency specification format is described in PEP 508.
29# Examples that the regular expressions in this hook needs to support:
30#
31# Requires-Dist: foo
32# -> foo
33# Requires-Dist: foo[optional]
34# -> foo[optional]
35# Requires-Dist: foo[optional]~=1.2.3
36# -> foo[optional]
37# Requires-Dist: foo[optional, xyz] (~=1.2.3)
38# -> foo[optional, xyz]
39# Requires-Dist: foo[optional]~=1.2.3 ; os_name = "posix"
40# -> foo[optional] ; os_name = "posix"
41#
42# Currently unsupported: URL specs (foo @ https://example.com/a.zip).
43
44_pythonRelaxDeps() {
45 local -r metadata_file="$1"
46
47 if [[ -z "${pythonRelaxDeps:-}" ]] || [[ "$pythonRelaxDeps" == 0 ]]; then
48 return
49 elif [[ "$pythonRelaxDeps" == 1 ]]; then
50 sed -i "$metadata_file" -r \
51 -e 's/(Requires-Dist: [a-zA-Z0-9_.-]+\s*(\[[^]]+\])?)[^;]*(;.*)?/\1\3/'
52 else
53 for dep in $pythonRelaxDeps; do
54 sed -i "$metadata_file" -r \
55 -e "s/(Requires-Dist: $dep\s*(\[[^]]+\])?)[^;]*(;.*)?/\1\3/i"
56 done
57 fi
58}
59
60_pythonRemoveDeps() {
61 local -r metadata_file="$1"
62
63 if [[ -z "${pythonRemoveDeps:-}" ]] || [[ "$pythonRemoveDeps" == 0 ]]; then
64 return
65 elif [[ "$pythonRemoveDeps" == 1 ]]; then
66 sed -i "$metadata_file" \
67 -e '/Requires-Dist:.*/d'
68 else
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 _pythonRelaxDeps $metadata_file
92 _pythonRemoveDeps $metadata_file
93
94 if (( "${NIX_DEBUG:-0}" >= 1 )); then
95 echo "pythonRelaxDepsHook: resulting METADATA for '$wheel':"
96 cat $metadata_file
97 fi
98
99 PYTHONPATH="@wheel@/@pythonSitePackages@:$PYTHONPATH" \
100 @pythonInterpreter@ -m wheel pack "$unpack_dir/"*
101 done
102
103 # Remove the folder since it will otherwise be in the dist output.
104 rm -rf "$unpack_dir"
105
106 popd
107}
108
109postBuild+=" pythonRelaxDepsHook"