nix下安装python库的问题

最近偿试在nix-darwin下安装一些python库,主要是为了配合使用一些emacs插件。然后遇到这个问题:

我要安装pyqt6-webengine和pyqt6-webengine-qt6,看文档,是前者依赖后者,前者是python绑定,后者是实际对应的qt库。

由于nix没有官方包,我直接写了个nix脚本来打包:

let
  newPython = pkgs.python312.override
    {
      self = newPython;
      packageOverrides = pself: psuper: {

        pyqt6-webengine-qt6 = pself.buildPythonPackage {
          pname = "pyqt6-webengine-qt6";
          version = "6.7.0";
          format = "wheel";
          src = fetchurl {
            url = "https://files.pythonhosted.org/packages/28/28/585923fa248c424b26aa2ffa29d76680169fd2563f6f06aa2615cde673e0/PyQt6_WebEngine_Qt6-6.7.0-py3-none-macosx_11_0_arm64.whl";
            hash = "sha256-rsXYnM8pU1G3u2EUp2evirHgD6wgX6yz/d7VbOee2T0=";
          };
          doCheck = false;
        };

        pyqt6-webengine = pself.buildPythonPackage {
          pname = "pyqt6-webengine";
          version = "6.7.0";
          format = "wheel";
          nativeBuildInputs = [ pyqt6-webengine-qt6 ];
          buildInputs = [ pyqt6-webengine-qt6 ];
          propagatedBuildInputs = [ pyqt6-webengine-qt6 ];
          dependencies = [ pyqt6-webengine-qt6 ];
          src = fetchurl {
            url = "https://files.pythonhosted.org/packages/f9/74/10eb5db10e50584096c136f2ed15cee782da19344822dafff02a70de60ad/PyQt6_WebEngine-6.7.0-cp38-abi3-macosx_10_14_universal2.whl";
            hash = "sha256-Gj351qwt+huzuCbzkmwT21ttQn6W6NV0xapURaixPbg=";
          };
          doCheck = false;
        };
        
      };
    };

in
{
  home.packages = with pkgs;
    [
      (newPython.withPackages
        (pypkg: with pypkg; [
          pyqt6-webengine-qt6
          pyqt6-webengine
        ]))
    ];
}

因为不太会直接从源码安装,所以就直接安装wheel文件了。 然后使用时出现问题:

In [1]: from PyQt6.QtWebEngineWidgets import QWebEngineView
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
Cell In[1], line 1
----> 1 from PyQt6.QtWebEngineWidgets import QWebEngineView 

ImportError: dlopen(/nix/store/w24q90w781jxvff39jjmz5dikdki111q-python3-3.12.5-env/lib/python3.12/site-packages/PyQt6/QtWebEngineWidgets.abi3.so, 0x0002): Library not loaded: @rpath/QtWebEngineWidgets.framework/Versions/A/QtWebEngineWidgets
  Referenced from: <F9BA94A8-7757-3733-870C-E667C5A1EAC6> /nix/store/hg5374m8dyl0a90l274z2fdijarq62w5-python3.12-pyqt6-webengine-6.7.0/lib/python3.12/site-packages/PyQt6/QtWebEngineWidgets.abi3.so
  Reason: tried: '/nix/store/hg5374m8dyl0a90l274z2fdijarq62w5-python3.12-pyqt6-webengine-6.7.0/lib/python3.12/site-packages/PyQt6/Qt6/lib/QtWebEngineWidgets.framework/Versions/A/QtWebEngineWidgets' (no such file), '/nix/store/hg5374m8dyl0a90l274z2fdijarq62w5-python3.12-pyqt6-webengine-6.7.0/lib/python3.12/site-packages/PyQt6/Qt6/lib/QtWebEngineWidgets.framework/Versions/A/QtWebEngineWidgets' (no such file)

看说明,是dlopen在打开 QtWebEngineWidgets.abi3.so时,偿试加载其依赖的so @rpath/QtWebEngineWidgets.framework/Versions/A/QtWebEngineWidgets 找不到文件。

在替换 @rpath时,用的是QtWebEngineWidgets.abi3.so的实际安装路径(/nix/store/hg5374m8dyl0a90l274z2fdijarq62w5-python3.12-pyqt6-webengine-6.7.0/lib/python3.12/site-packages/PyQt6/QtWebEngineWidgets.abi3.so),而不是在python环境中的符号连接路径(/nix/store/w24q90w781jxvff39jjmz5dikdki111q-python3-3.12.5-env/lib/python3.12/site-packages/PyQt6)。如果用符号连接路径作为前缀,替换@rpath是能找到的。

有没有nix大佬帮忙看下应该怎样处理?

我已经放弃用nix玩python了,老实按个pdm,然后用pdm+venv吧

先说最简单的:在 24.05 的 channel 下已经有了 pyqt6-webengine 了,只需要

{ python312 }:

python312.withPackages (p: with p; [ pyqt6-webengine ])

使用 nix-channel --list 确认 channel 版本

如果实在需要打包 pyqt6-webengine 这里给一个 autoPatchelfHook 的使用示例:

{ autoPatchelfHook, python312, qt6, stdenv }:

(python312.override {
  packageOverrides = final: prev: {
    pyqt6-webengine = final.buildPythonPackage rec {
      pname = "PyQt6_WebEngine";
      version = "6.7.0";
      format = "wheel";
      src = final.fetchPypi {
        inherit pname version format;
        dist = "cp38";
        python = "cp38";
        abi = "abi3";

        inherit (if stdenv.isLinux && stdenv.isx86_64 then {
          platform = "manylinux_2_28_x86_64";
          hash = "sha256-CYtfjVkDowkQEAmGlPhN14bMqHjuKKWjzmUVB9XWVGg=";
        } else if stdenv.isDarwin then {
          platform = "macosx_10_14_universal2";
          hash = "sha256-Gj351qwt+huzuCbzkmwT21ttQn6W6NV0xapURaixPbg=";
        } else {
          platform = null;
          hash = null;
        })
          platform hash;
      };
      buildInputs = [ qt6.full ];
      nativeBuildInputs = [ autoPatchelfHook ];
      propagatedBuildInputs = [ final.pyqt6 ];
    };
  };
}).withPackages (p: with p; [ pyqt6-webengine ])

保存到 package.nix 文件中

$ nix-build -E "with import <nixpkgs> {}; callPackage ./package.nix {}"
/nix/store/ld6yg1pz6xjfy6lzi4ijq12gf6nya98q-python3-3.12.5-env
$ ./result/bin/python
Python 3.12.5 (main, Aug  6 2024, 19:08:49) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from PyQt6.QtWebEngineWidgets import QWebEngineView
>>>

我这里还有很多类似的包要处理,单独一个pyqt6-webengine 解决了不能完全起作用。还是得探索一程直接从wheel文件安装python库并集成到系统python的方法。

我测试了一下,发现原来是dlopen在查找第一个.so的依赖时,没有用调用dlopen时传入的目录(符号连接 /nix/store/<sha256>-python3-3.12.5-env/lib/python3.12/site-packages/PyQt6/....),而是转成了.so所在的实际位置(/nix/store/<sha256>-python3.12-pyqt6-webengine-6.7.0/lib/python3.12/site-packages/PyQt6/...),然后dlopen在这个目录下去搜索依赖so,结果搜不到。

如果我现在强制手动给最开始的so增加 @rpath 的配置:

install_name_tool -add_rpath /nix/store/<sha256>-python3-3.12.5-env/lib/python3.12/site-packages/PyQt6/Qt6/lib /nix/store/<sha256>-python3-3.12.5-env/lib/python3.12/site-packages/PyQt6/QtWebEngineWidgets.abi3.so

问题就解决了。


所以这里应该是可以通过在 postInstall 中增加 patch 命令,给对应 so 增加rpath。

我试了下,nix中,调用 python.withPackages (…) 后,会返回一个新的包,这个包的路径,就是python集成环境的路径,所有安装的包都会链接到这个路径下。dlopen最开始打开so时,也是传入的这个路径。但是在调用buildPythonPackage时,不知有没有办法获取这个路径?如果可以的话,只需要增加一个@rpath就可以解决所有问题了。

不然的话,就必须给每个依赖的so,都增加一个@rpath。比如这里如果依赖 pyqt6-webengine-qt6,就得这样写:

      pyqt6-webengine = pself.buildPythonPackage {
        pname = "pyqt6-webengine";
        version = "6.7.0";
        format = "wheel";
        nativeBuildInputs = [ pyqt6-webengine-qt6 ];
        buildInputs = [ pyqt6-webengine-qt6 ];
        propagatedBuildInputs = [ pyqt6-webengine-qt6 ];
        dependencies = [ pyqt6-webengine-qt6 ];
        src = fetchurl {
          url = "https://files.pythonhosted.org/packages/f9/74/10eb5db10e50584096c136f2ed15cee782da19344822dafff02a70de60ad/PyQt6_WebEngine-6.7.0-cp38-abi3-macosx_10_14_universal2.whl";
          hash = "sha256-Gj351qwt+huzuCbzkmwT21ttQn6W6NV0xapURaixPbg=";
        };
        doCheck = false;
        postInstall = ''
          install_name_tool -add_rpath ${pyqt6-webengine-qt6}/lib/python3.12/site-packages/PyQt6/Qt6/lib $out/lib/python3.12/site-packages/PyQt6/QtWebEngineWidgets.abi3.so
        '';
      };

postInstall里,每多一个依赖,就得多写一行,有点痛苦。

有没有办法在 buildPythonPackage 中获取到调用 python.withPackages之后的路径呀?

nativeBuildInputs = [ autoPatchelfHook ];

配合 buildInputs 里,根据提示缺失的 lib,添加 pkgs 里相应的包,可以解决大部份链接路径问题,也就是你现在遇到的 .so 找不到的问题。

你再次检查 有没有使用autoPatchelfHook

不需要手动添加 add_rpath

感谢,第一次听说pdm,孤陋寡闻了。

请教下,pdm有能做为全局的python解释器使用吗?此外能否做到像nix那样,清空所有包,然后执行一条命令就复现整个环境?

      pyqt6-webengine = pself.buildPythonPackage {
        pname = "pyqt6-webengine";
        version = "6.7.0";
        format = "wheel";
        nativeBuildInputs = [ pyqt6-webengine-qt6 pkgs.autoPatchelfHook ];
        buildInputs = [ pyqt6-webengine-qt6 ];
        src = fetchurl {
          url = "https://files.pythonhosted.org/packages/f9/74/10eb5db10e50584096c136f2ed15cee782da19344822dafff02a70de60ad/PyQt6_WebEngine-6.7.0-cp38-abi3-macosx_10_14_universal2.whl";
          hash = "sha256-Gj351qwt+huzuCbzkmwT21ttQn6W6NV0xapURaixPbg=";
        };
        doCheck = false;
        postInstall = ''
          install_name_tool -add_rpath ${pyqt6-webengine-qt6}/lib/python3.12/site-packages/PyQt6/Qt6/lib $out/lib/python3.12/site-packages/PyQt6/QtWebEngineWidgets.abi3.so
        '';
      };

感谢回复,我按你说的,添加了 autoPatchelfHook,报错:

error: builder for '/nix/store/l16iy5jv0cs7x9qk6203fq3i6y3fpy34-python3.12-pyqt6-webengine-6.7.0.drv' failed with exit code 1;
       last 25 log lines:
       > checking for references to /private/tmp/nix-build-python3.12-pyqt6-webengine-6.7.0.drv-0/ in /nix/store/5xjwq66jklvh1xm5f80vkm0wh00sv65k-python3.12-pyqt6-webengine-6.7.0...
       > patching script interpreter paths in /nix/store/5xjwq66jklvh1xm5f80vkm0wh00sv65k-python3.12-pyqt6-webengine-6.7.0
       > stripping (with command strip and flags -S) in  /nix/store/5xjwq66jklvh1xm5f80vkm0wh00sv65k-python3.12-pyqt6-webengine-6.7.0/lib
       > checking for references to /private/tmp/nix-build-python3.12-pyqt6-webengine-6.7.0.drv-0/ in /nix/store/mydm84hna59y2i0m5hg4kjq4447cnv5x-python3.12-pyqt6-webengine-6.7.0-dist...
       > patching script interpreter paths in /nix/store/mydm84hna59y2i0m5hg4kjq4447cnv5x-python3.12-pyqt6-webengine-6.7.0-dist
       > Executing pythonRemoveTestsDir
       > Finished executing pythonRemoveTestsDir
       > Traceback (most recent call last):
       >   File "/nix/store/5jah78jkphn4phk6msqf50kan5w0myn0-auto-patchelf.py", line 432, in <module>
       >     with open_elf(interpreter_path) as interpreter:
       >   File "/nix/store/9pj4rzx5pbynkkxq1srzwjhywmcfxws3-python3-3.12.5/lib/python3.12/contextlib.py", line 137, in __enter__
       >     return next(self.gen)
       >            ^^^^^^^^^^^^^^
       >   File "/nix/store/5jah78jkphn4phk6msqf50kan5w0myn0-auto-patchelf.py", line 27, in open_elf
       >     yield ELFFile(stream)
       >           ^^^^^^^^^^^^^^^
       >   File "/nix/store/38qv2rwsh1wqka1p0jqzb016z37ykvxa-python3-3.12.5-env/lib/python3.12/site-packages/elftools/elf/elffile.py", line 84, in __init__
       >     self._identify_file()
       >   File "/nix/store/38qv2rwsh1wqka1p0jqzb016z37ykvxa-python3-3.12.5-env/lib/python3.12/site-packages/elftools/elf/elffile.py", line 570, in _identify_file
       >     elf_assert(magic == b'\x7fELF', 'Magic number does not match')
       >   File "/nix/store/38qv2rwsh1wqka1p0jqzb016z37ykvxa-python3-3.12.5-env/lib/python3.12/site-packages/elftools/common/utils.py", line 80, in elf_assert
       >     _assert_with_exception(cond, msg, ELFError)
       >   File "/nix/store/38qv2rwsh1wqka1p0jqzb016z37ykvxa-python3-3.12.5-env/lib/python3.12/site-packages/elftools/common/utils.py", line 143, in _assert_with_exception
       >     raise exception_type(msg)
       > elftools.common.exceptions.ELFError: Magic number does not match
       For full logs, run 'nix-store -l /nix/store/l16iy5jv0cs7x9qk6203fq3i6y3fpy34-python3.12-pyqt6-webengine-6.7.0.drv'.

我的pyqt6-webengine是直接通过wheel安装的,依赖的qt库也是通过pyqt6-webengine-qt6安装的。不知道是不是因为我这里是mac的原因?

好像autoPatchelfHook只能用于linux,mac下不行

确实 autoPatchelfHook只能用于linux ,你先看看这个,我也试试。

https://wiki.nixos.org/wiki/Python#Prefix_library_paths_using_wrapProgram

试下这个

保存到 package.nix

{ lib, makeWrapper, python312, qt6, stdenv, symlinkJoin }:

let
  pythonldlibpath = lib.makeLibraryPath [ qt6.full stdenv.cc.cc.lib ];
  wrapPrefix =
    if (!stdenv.isDarwin) then "LD_LIBRARY_PATH" else "DYLD_LIBRARY_PATH";

  python = (python312.override {
    packageOverrides = final: prev: {
      pyqt6-webengine = final.buildPythonPackage rec {
        pname = "PyQt6_WebEngine";
        version = "6.7.0";
        format = "wheel";
        src = final.fetchPypi {
          inherit pname version format;
          dist = "cp38";
          python = "cp38";
          abi = "abi3";

          inherit (if stdenv.isLinux && stdenv.isx86_64 then {
            platform = "manylinux_2_28_x86_64";
            hash = "sha256-CYtfjVkDowkQEAmGlPhN14bMqHjuKKWjzmUVB9XWVGg=";
          } else if stdenv.isDarwin then {
            platform = "macosx_10_14_universal2";
            hash = "sha256-Gj351qwt+huzuCbzkmwT21ttQn6W6NV0xapURaixPbg=";
          } else {
            platform = null;
            hash = null;
          })
            platform hash;
        };

        propagatedBuildInputs = [ final.pyqt6 ];
        dependencies = [ final.pyqt6-webengine-qt6 ];
      };
      pyqt6-webengine-qt6 = final.buildPythonPackage rec {
        pname = "PyQt6_WebEngine_Qt6";
        version = "6.7.0";
        format = "wheel";

        src = final.fetchPypi {
          inherit pname version format;
          dist = "py3";
          python = "py3";

          inherit (if stdenv.isLinux && stdenv.isx86_64 then {
            platform = "manylinux_2_28_x86_64";
            hash = "sha256-jxX612XWovuswBuyMzC6HqNi/SifzbUugnCnmROHRsU=";
          } else if stdenv.isDarwin && stdenv.isx86_64 then {
            platform = "macosx_10_14_x86_64";
            hash = "sha256-qEq/8EqGAO8fZrdCLpaSrLzs0ai3FqqjhGmFlaZvrxw=";
          } else {
            platform = null;
            hash = null;
          })
            platform hash;
        };
      };
    };
  }).withPackages (p: with p; [ pyqt6-webengine ]);
in symlinkJoin {
  name = "python-with-pyqt6-webengine";
  paths = [ python ];
  buildInputs = [ makeWrapper ];
  postBuild = ''
    wrapProgram "$out/bin/python" --prefix ${wrapPrefix} : "${pythonldlibpath}"
  '';
}
$ nix-build -E 'with import <nixpkgs> {}; callPackage ./package.nix {}'
/nix/store/8xpagafr7lq1rnni3dv1byiajx1jb882-python-with-pyqt6-webengine
$ ./result/bin/python
Python 3.12.5 (main, Aug  6 2024, 19:08:49) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from PyQt6.QtWebEngineWidgets import QWebEngineView
>>>
$ ls /nix/store/479bw3w6bniibh382azgmzkwvhvb8wqw-python3-3.12.5-env/lib/python3.12/site-packages/PyQt6/Qt6/lib
libQt6WebEngineCore.so.6  libQt6WebEngineQuickDelegatesQml.so.6  libQt6WebEngineQuick.so.6  libQt6WebEngineWidgets.so.6

我要确认一件事情, PyPi 上的 PyQt6_WebEngine_Qt6 只有 20M , lib 也只有上面列出的这些,你使用 pyqt6-webengine 的时候会依赖一个完整的 Qt 的吗?如果依赖,那你的 Qt 是要从 nix 的 pkgs 安装吗?像我给的示例这样。

pyqt6-webengine只依赖了pyqt6-webengine-qt6,后者就是专门把qt相关库抽出来打包的。我这里没有安装完整的qt

那理论上只需要从示例中删掉 qt6.full 和 final.pyqt6 就好了,但是我这里会报:

./result/bin/python
Python 3.12.5 (main, Aug  6 2024, 19:08:49) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from PyQt6.QtWebEngineWidgets import QWebEngineView
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: libQt6PrintSupport.so.6: cannot open shared object file: No such file or directory
>>>

提示依赖 libQt6PrintSupport.so.6

还是报一样的错,我感觉这里跟用 symlinkJoin 无关,根本原因还是在,dlopen打开第一个so时,是通过符号连接的路径打开的,但是当搜寻依赖的so时,用的路径却是第一个so的实际路径。如果继续使用符号连接的路径去搜索,是可以搜到的。


我用的package.nix

{ lib, makeWrapper, python312, stdenv, symlinkJoin }:

let
  pythonldlibpath = lib.makeLibraryPath [ stdenv.cc.cc.lib ];
  wrapPrefix =
    if (!stdenv.isDarwin) then "LD_LIBRARY_PATH" else "DYLD_LIBRARY_PATH";

  python = builtins.trace stdenv.isDarwin (python312.override {
    packageOverrides = final: prev: {
      pyqt6-webengine = final.buildPythonPackage rec {
        pname = "PyQt6_WebEngine";
        version = "6.7.0";
        format = "wheel";
        src = final.fetchPypi {
          inherit pname version format;
          dist = "cp38";
          python = "cp38";
          abi = "abi3";

          inherit (if stdenv.isLinux && stdenv.isx86_64 then {
            platform = "manylinux_2_28_x86_64";
            hash = "sha256-CYtfjVkDowkQEAmGlPhN14bMqHjuKKWjzmUVB9XWVGg=";
          } else if stdenv.isDarwin then {
            platform = "macosx_10_14_universal2";
            hash = "sha256-Gj351qwt+huzuCbzkmwT21ttQn6W6NV0xapURaixPbg=";
          } else {
            platform = null;
            hash = null;
          })
            platform hash;
        };

        propagatedBuildInputs = [ ];
        dependencies = [ final.pyqt6-webengine-qt6 ];
      };
      pyqt6-webengine-qt6 = final.buildPythonPackage rec {
        pname = "PyQt6_WebEngine_Qt6";
        version = "6.7.0";
        format = "wheel";

        src = final.fetchPypi {
          inherit pname version format;
          dist = "py3";
          python = "py3";

          inherit (if stdenv.isLinux && stdenv.isx86_64 then {
            platform = "manylinux_2_28_x86_64";
            hash = "sha256-jxX612XWovuswBuyMzC6HqNi/SifzbUugnCnmROHRsU=";
          } else if stdenv.isDarwin then {
            platform = "macosx_11_0_arm64";
            hash = "sha256-rsXYnM8pU1G3u2EUp2evirHgD6wgX6yz/d7VbOee2T0=";
          } else {
            platform = null;
            hash = null;
          })
            platform hash;
        };
      };
    };
  }).withPackages (p: with p; [ pyqt6-webengine ]);
in symlinkJoin {
  name = "python-with-pyqt6-webengine";
  paths = [ python ];
  buildInputs = [ makeWrapper ];
  postBuild = ''
    wrapProgram "$out/bin/python" --prefix ${wrapPrefix} : "${pythonldlibpath}"
  '';
}

以下是报错:

Python 3.12.5 (main, Aug  6 2024, 19:08:49) [Clang 16.0.6 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from PyQt6.QtWebEngineWidgets import QWebEngineView 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: dlopen(/nix/store/hn8ziqgpjfkbblq6ns66hl61kcn5nz23-python3-3.12.5-env/lib/python3.12/site-packages/PyQt6/QtWebEngineWidgets.abi3.so, 0x0002): Library not loaded: @rpath/QtWebEngineWidgets.framework/Versions/A/QtWebEngineWidgets
  Referenced from: <F9BA94A8-7757-3733-870C-E667C5A1EAC6> /nix/store/7li4kdm4925wvamdm0gmmj567g0g9c3m-python3.12-PyQt6_WebEngine-6.7.0/lib/python3.12/site-packages/PyQt6/QtWebEngineWidgets.abi3.so
  Reason: tried: '/nix/store/7li4kdm4925wvamdm0gmmj567g0g9c3m-python3.12-PyQt6_WebEngine-6.7.0/lib/python3.12/site-packages/PyQt6/Qt6/lib/QtWebEngineWidgets.framework/Versions/A/QtWebEngineWidgets' (no such file), '/nix/store/7li4kdm4925wvamdm0gmmj567g0g9c3m-python3.12-PyQt6_WebEngine-6.7.0/lib/python3.12/site-packages/PyQt6/Qt6/lib/QtWebEngineWidgets.framework/Versions/A/QtWebEngineWidgets' (no such file)
>>>

/nix/store/hn8ziqgpjfkbblq6ns66hl61kcn5nz23-python3-3.12.5-env/lib/python3.12/site-packages/PyQt6/QtWebEngineWidgets.abi3.so是一个符号连接,真实路径是/nix/store/7li4kdm4925wvamdm0gmmj567g0g9c3m-python3.12-PyQt6_WebEngine-6.7.0/lib/python3.12/site-packages/PyQt6/QtWebEngineWidgets.abi3.so,而后续dlopen查找依赖时,就用 /nix/store/7li4kdm4925wvamdm0gmmj567g0g9c3m-python3.12-PyQt6_WebEngine-6.7.0/lib/python3.12/site-packages/PyQt6开始查找了,所以找不到so

如果真的只是符号连接的问题,你仿照这个 postBuild 用复制文件替换掉链接试试,你要改成对应的,是符号链接的目录或文件(我这里符号链接是最里面的 lib)

我试了下在 aws 上开不了 macOS 的机器,没有环境帮你试

或者要不你还是按照你之前的思路 add_rpath 吧

{ python312, python312Packages, stdenv }:

python312.buildEnv.override {
  extraLibs = [
    (let
      pyqt6-webengine-qt6 = python312Packages.buildPythonPackage rec {
        pname = "PyQt6_WebEngine_Qt6";
        version = "6.7.0";
        format = "wheel";
        src = python312Packages.fetchPypi {
          inherit pname version format;
          dist = "py3";
          python = "py3";

          inherit (if stdenv.isLinux && stdenv.isx86_64 then {
            platform = "manylinux_2_28_x86_64";
            hash = "sha256-jxX612XWovuswBuyMzC6HqNi/SifzbUugnCnmROHRsU=";
          } else if stdenv.isDarwin then {
            platform = "macosx_11_0_arm64";
            hash = "sha256-rsXYnM8pU1G3u2EUp2evirHgD6wgX6yz/d7VbOee2T0=";
          } else {
            platform = null;
            hash = null;
          })
            platform hash;
        };
      };
    in python312Packages.buildPythonPackage rec {
      pname = "PyQt6_WebEngine";
      version = "6.7.0";
      format = "wheel";
      src = python312Packages.fetchPypi {
        inherit pname version format;
        dist = "cp38";
        python = "cp38";
        abi = "abi3";

        inherit (if stdenv.isLinux && stdenv.isx86_64 then {
          platform = "manylinux_2_28_x86_64";
          hash = "sha256-CYtfjVkDowkQEAmGlPhN14bMqHjuKKWjzmUVB9XWVGg=";
        } else if stdenv.isDarwin then {
          platform = "macosx_10_14_universal2";
          hash = "sha256-Gj351qwt+huzuCbzkmwT21ttQn6W6NV0xapURaixPbg=";
        } else {
          platform = null;
          hash = null;
        })
          platform hash;
      };
      dependencies = [ pyqt6-webengine-qt6 ];
    })
  ];
  postBuild = ''
    cp -rL $out/lib/python3.12/site-packages/PyQt6/Qt6/{lib,actual}
    rm $out/lib/python3.12/site-packages/PyQt6/Qt6/lib
    mv $out/lib/python3.12/site-packages/PyQt6/Qt6/{actual,lib}
  '';
}

add_rpath方法我这边已经弄成了,倒是不多,需要操作4个so,确实也是无奈的办法。整个过程倒是挺让人头疼的,感觉就像Nasy说的,用nix搞python包不是一个好主意。感觉nix在这块还是有点欠缺。

你最后这段我没时间测试了,不过整体上看,应该不会成功。因为这里安装python及其包的逻辑我大致理解是:

  1. 安装python,python有一个自己的安装目录
  2. 安装各个包,每个包也有自己的安装目录(通常是/nix/store/-package-name/lib/python3.12/site-packages/… 之类
  3. 将python本身和各个包相关目录或文件组成一个新包,在这个包的安装目录下,有许多链接,分别链接到1和2中的对应目录。

目前我还没完全弄清楚步骤3是什么时候做的,但我感觉在 buildPythonPackage中增加的postBuild/postInstall都是在步骤2中进行的,里面的 $out 都是这个包自己的安装目录,没有实际操作步骤3中的软链接。

最后这个例子的 postBuild 是 python312.buildEnv 的,也就是你这里的第 3 阶段,这个阶段不能 add_rpath 之前已经 buildPythonPackage 的包,但是可以修改替换最终环境的目录。

最终环境目录会有软链接指向 2 阶段的各个包,软链接里面的内容不能改,但是软链接本身可以从最终环境目录删除和替换。

哦哦,昨晚太晚看晕头了,我试试看下

可以的,这个方法我这边测试是通过了。

如此说来,关于python安装包就有两种形式的,第一种是按官网文档中的:

python312.withPackages (pypkgs: with pypkgs; [
    pyqt6  pyqt6-qt6 ...
])

这个方法至少在mac下,无法解决上面出现的dlopen通过符号链接加载so时,又通过符号链接的真实路径作为 @rpath 去搜索依赖so的问题。如果强行在这上面作文章,应该就只有我上面的用 install_name_tool / elfpatch 等工具给so二进制文件增加so搜索路径。

第二种是你上面给的方法:

{ python312, python312Packages, stdenv, lib, fetchurl, callPackage }:
let
  newPython = python312.buildEnv.override {
    postBuild = ''
      echo ".........................."
      echo $out
      if [ -d "$out/lib/python3.12/site-packages/PyQt6" ]; then
          echo "merge all python stuff..."
          cp -rL $out/lib/python3.12/site-packages/{PyQt6,actual}
          rm -rf $out/lib/python3.12/site-packages/PyQt6
          chmod u+w $out/lib/python3.12/site-packages/actual
          mv     $out/lib/python3.12/site-packages/{actual,PyQt6}
      fi
      echo "........................."
    '';

    # 填写所需python包
    extraLibs = (with python312.pkgs; [
      pyqt6 pyqt6-qt6 pyqt6-webengine pyqt6-webengine-qt6 ...
    ])
    ;
  };
in
newPython

重点就是上面的postBuild中,该过程发生在我上面说的步骤3之后,此时的 $out 就是最终python集成环境的路径,通过 cp -rL 将符号链接全部转成真实路径。

这样做好处是适应性应该更好一些,其他python包遇到类似问题也可以在postBuild中处理。缺点是相当于所有python包会安装至少两份,占用一定磁盘空间。 还有就是如果 cp -rL中如果出现循环链接复制时可能会有问题。

1 个赞