about summary refs log tree commit diff
path: root/pkgs
diff options
context:
space:
mode:
authorThiago Kenji Okada <thiagokokada@gmail.com>2022-04-28 20:58:59 +0100
committerThiago Kenji Okada <thiagokokada@gmail.com>2022-04-30 13:19:30 +0100
commite19019fe327a7a9cd8539cfda2381bc2cc59ff75 (patch)
tree5dc64d3804db2f8c73dcafd56b0cba943226bdf3 /pkgs
parentf08d8f2263359c8a680f806136a697dde8a587f3 (diff)
downloadnixlib-e19019fe327a7a9cd8539cfda2381bc2cc59ff75.tar
nixlib-e19019fe327a7a9cd8539cfda2381bc2cc59ff75.tar.gz
nixlib-e19019fe327a7a9cd8539cfda2381bc2cc59ff75.tar.bz2
nixlib-e19019fe327a7a9cd8539cfda2381bc2cc59ff75.tar.lz
nixlib-e19019fe327a7a9cd8539cfda2381bc2cc59ff75.tar.xz
nixlib-e19019fe327a7a9cd8539cfda2381bc2cc59ff75.tar.zst
nixlib-e19019fe327a7a9cd8539cfda2381bc2cc59ff75.zip
pythonRelaxDepsHook: init
We have a common pattern here in nixpkgs for Python applications: when a
Python package ships with either a requirements.txt or setup.py file, we
generally end up having to modify its version restriction, otherwise we have
build failures since we package only one specific version of each package
normally.

However, this end up being done in a completely ad-hoc way: some people
use substituteInPlace, some others use sed, others uses patches, etc.
In many cases, the code ends up being buggy, so it may work in one
version and breaks on the next one. We can instead implement one
standard way of doing this, and trying to be a correct as possible.

So this is what this commit does: it implements a new build hook, that
when called will automatically patch the wheel file. This is one of the
most generic ways to patch Python dependencies, and should work in
multiple cases.
Diffstat (limited to 'pkgs')
-rw-r--r--pkgs/development/interpreters/python/hooks/default.nix13
-rw-r--r--pkgs/development/interpreters/python/hooks/python-relax-deps-hook.sh83
-rw-r--r--pkgs/top-level/python-packages.nix1
3 files changed, 95 insertions, 2 deletions
diff --git a/pkgs/development/interpreters/python/hooks/default.nix b/pkgs/development/interpreters/python/hooks/default.nix
index 1a0618225a37..63a2d5fbdd40 100644
--- a/pkgs/development/interpreters/python/hooks/default.nix
+++ b/pkgs/development/interpreters/python/hooks/default.nix
@@ -124,6 +124,15 @@ in rec {
       };
     } ./python-recompile-bytecode-hook.sh ) {};
 
+  pythonRelaxDepsHook = callPackage ({ wheel }:
+    makeSetupHook {
+      name = "python-relax-deps-hook";
+      deps = [ wheel ];
+      substitutions = {
+        inherit pythonInterpreter;
+      };
+    } ./python-relax-deps-hook.sh) {};
+
   pythonRemoveBinBytecodeHook = callPackage ({ }:
     makeSetupHook {
       name = "python-remove-bin-bytecode-hook";
@@ -161,8 +170,8 @@ in rec {
       deps = [ ensureNewerSourcesForZipFilesHook ];
       substitutions = {
         inherit pythonInterpreter;
-    };
-  } ./venv-shell-hook.sh) {});
+      };
+    } ./venv-shell-hook.sh) {});
 
   wheelUnpackHook = callPackage ({ wheel }:
     makeSetupHook {
diff --git a/pkgs/development/interpreters/python/hooks/python-relax-deps-hook.sh b/pkgs/development/interpreters/python/hooks/python-relax-deps-hook.sh
new file mode 100644
index 000000000000..7e1cfe51724b
--- /dev/null
+++ b/pkgs/development/interpreters/python/hooks/python-relax-deps-hook.sh
@@ -0,0 +1,83 @@
+# shellcheck shell=bash
+
+# Setup hook that modifies Python dependencies versions.
+#
+# Example usage in a derivation:
+#
+#   { …, pythonPackages, … }:
+#
+#   pythonPackages.buildPythonPackage {
+#     …
+#     nativeBuildInputs = [ pythonPackages.pythonRelaxDepsHook ];
+#
+#     # This will relax the dependency restrictions
+#     # e.g.: abc>1,<=2 -> abc
+#     pythonRelaxDeps = [ "abc" ];
+#     # This will relax all dependencies restrictions instead
+#     # pythonRelaxDeps = true;
+#     # This will remove the dependency
+#     # e.g.: cde>1,<=2 -> <nothing>
+#     pythonRemoveDeps = [ "cde" ];
+#     # This will remove all dependencies from the project
+#     # pythonRemoveDeps = true;
+#     …
+#   }
+
+_pythonRelaxDeps() {
+    local -r metadata_file="$1"
+
+    if [[ -z "${pythonRelaxDeps:-}" ]] || [[ "$pythonRelaxDeps" == 0 ]]; then
+        return
+    elif [[ "$pythonRelaxDeps" == 1 ]]; then
+        sed -i "$metadata_file" -r \
+            -e 's/(Requires-Dist: \S*) \(.*\)/\1/'
+    else
+        for dep in $pythonRelaxDeps; do
+            sed -i "$metadata_file" -r \
+                -e "s/(Requires-Dist: $dep) \(.*\)/\1/"
+        done
+    fi
+}
+
+_pythonRemoveDeps() {
+    local -r metadata_file="$1"
+
+    if [[ -z "${pythonRemoveDeps:-}" ]] || [[ "$pythonRemoveDeps" == 0 ]]; then
+        return
+    elif [[ "$pythonRemoveDeps" == 1 ]]; then
+        sed -i "$metadata_file" \
+            -e '/Requires-Dist:.*/d'
+    else
+        for dep in $pythonRemoveDeps; do
+            sed -i "$metadata_file" \
+                -e "/Requires-Dist: $dep/d"
+        done
+    fi
+
+}
+
+pythonRelaxDepsHook() {
+    pushd dist
+
+    local -r package="$pname-$version"
+    local -r unpack_dir="unpacked"
+    local -r metadata_file="$unpack_dir/$package/$package.dist-info/METADATA"
+    local -r wheel=$(echo "$package"*".whl")
+
+    @pythonInterpreter@ -m wheel unpack --dest "$unpack_dir" "$wheel"
+    rm -rf "$wheel"
+
+    _pythonRelaxDeps "$metadata_file"
+    _pythonRemoveDeps "$metadata_file"
+
+    if (( "${NIX_DEBUG:-0}" >= 1 )); then
+        echo "pythonRelaxDepsHook: resulting METADATA:"
+        cat "$unpack_dir/$package/$package.dist-info/METADATA"
+    fi
+
+    @pythonInterpreter@ -m wheel pack "$unpack_dir/$package"
+
+    popd
+}
+
+postBuild+=" pythonRelaxDepsHook"
diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix
index 2d9631e4ac3b..39f0901a020b 100644
--- a/pkgs/top-level/python-packages.nix
+++ b/pkgs/top-level/python-packages.nix
@@ -119,6 +119,7 @@ in {
     pythonImportsCheckHook
     pythonNamespacesHook
     pythonRecompileBytecodeHook
+    pythonRelaxDepsHook
     pythonRemoveBinBytecodeHook
     pythonRemoveTestsDirHook
     setuptoolsBuildHook