LWN:让Python的各种软件安装方式不再冲突!
关注了就能看到更多这么棒的文章哦~
Cooperative package management for Python
By Jake Edge
August 31, 2021
DeepL assisted translation
https://lwn.net/Articles/867657/
系统中使用的软件包管理器同 Python 自己的安装机制 (主要是 pip,但也有其他一些方案) 之间一直都有一些冲突,现在看来终于要被解决了,或者至少可以规范化下来。PEP 668 ("外部软件包管理器和 Python 软件包管理器之间的优雅合作方案") 提案的目的就是要提出能让两种类型的软件包安装方式能配合工作,而不是时不时地相互破坏。由于许多操作系统都依赖 Python 工具,其中使用的软件包版本可能与用户的 Python 应用程序的版本可能是不同的。因此,人们非常希望能让它们可以很好地配合工作,从而使系统更加稳定。
这个问题的根本原因在于,发行版中的软件包管理器和 Python 软件包管理器 (本文后续部分用 "pip" 来代称) 往往公用同一个 "site-packages" 目录,来存储已安装的软件包。如果更新了某一个软件包,或者更糟糕的情况是删除了某个软件包,那么在其中一个包管理器中可能是可以正常执行的,但在另一个包管理器中却导致了破坏。正如 PEP 中所指出的,这一切都可能会造成真实的麻烦:
这可能会给发行版的完整性(integrity)带来严重问题,因为发行版的软件包管理工具本身就是用 Python 编写的。例如可能无意中用 pip install 命令破坏了 Fedora 的 dnf 命令,难以恢复回去。
sys.path 这个系统参数用来管理 Python 在遇到 import 语句时在哪里寻找 module。它会根据 PYTHONPATH 环境变量来初始化,然后添加了一些安装时和调用时相关的目录。sys.path 是一系列的目录,会按顺序查找,行为上很像它所模仿的 shell 中的 PATH 环境变量。Python 程序可以修改 sys.path 来指定要搜索的位置,这个功能被 virtual environment 利用来实现其功能。
挺长时间以来,为了避免与操作系统安装的软件包冲突,更推荐的做法是在 virtual environment 中使用 pip,而不是直接安装到当前系统中去。但这一般来说不是强制性的要求,所以用户有时候还是会遇到问题。PEP 668 的一个目标就是允许发行版来声明他们提供了另一种管理 Python 软件包的机制,这样一来 pip 的默认行为就会相应地改变。用户仍然可以覆盖修改该默认行为,但希望这已经足够能提醒他们要注意到可能出现的问题。
如果发行版希望选择这个新的行为,那么就会通过在 Python 标准库所在的目录中放置一个名为 EXTERNALLY-MANAGED 的配置文件来告知 pip 发行版在用自己的工具管理 Python 包。如果 pip 在那里发现了 EXTERNALLY-MANAGED 文件,并且当前 pip 没有在 virtual environment 中运行,那么它就应该报错退出,除非用户明确用命令行参数来修改默认行为。PEP 中推荐用 –break-system-packages 这个参数名称。EXTERNALLY-MANAGED 文件可以包含错误信息,当 pip 因为此原因而退出时,就会返回这些错误信息。这些错误信息也可以在文件中根据本地化信息进行翻译。其目的都是让这个错误信息能够提供出这个发行版特有的一些指导信息,来指导用户以正确的方式创建一个 virtual environment 虚拟环境。
另一个可能出现问题是在软件包被 pip 从系统范围内安装的版本中移除时。例如,如果用户在全系统范围内安装了一个软件包,并遇到了问题,那么此时最 "明显的" 解决方案可能会导致更大的问题:
在系统位置进行安装还有一个更糟糕的问题:如果你试图用 sudo pip uninstall 来解决这种问题,那你可能最终会删除掉系统包管理器所提供的软件包。事实上,哪怕你只是简单地进行一个软件包的升级操作,这种情况也会发生,因为 pip 会试图删除操作系统所提供的旧版本软件包。此时仅仅使用系统上剩余的软件都可能无法将系统恢复到之前的正常状态了。
PEP 中提出的第二个改动就是限制 pip 只在指定的目录中运行。这里的设计思路是让发行版将这两种软件包分别放置到它们自己的目录中来管理,这其实是一些 Linux 发行版已经在做的事情了:
例如,Fedora 和 Debian(以及它们的衍生版本)都使用了隔离方式,也就是将 /usr/local 路径用来在 pip 等方式安装的软件包,而 /usr 用在发行版所安装的软件包。Fedora 的相应目录就是/usr/local/lib/python3.x/site-packages 与/usr/lib/python3.x/site-packages。(Debian 使用了 /usr/local/lib/python3/dist-packages 和 /usr/lib/python3/dist-packages,前者是作为与本地编译生成的 Python 解释器的额外隔离用途的,也就是说如果在/usr/local/bin 中编译和安装了 upstream 的 CPython,那么它就会查找 /usr/local/lib/python3/site-packages 目录,而 Debian 希望确保那些本地构建出来的 python 解释器所安装的软件包不会出现在发行版提供的 python 解释器的 sys.path 中)。
所以这个提议中就要求 pip 需要查询它将会放置软件包的位置,并且只修改该目录下的文件。由于本地安装的软件包通常在 sys.path 中会位于系统级别软件包目录之前,因此这可能会导致 pip 安装的版本好像覆盖了(shadow)发行版所提供的相应软件包。当然,这种 "shadow" 方式安装的软件包也会导致之前提到的一些问题,所以建议 pip 在碰到这种情况的时候也要发出 warning。
PEP 中对使用场景以及这些改动将产生的影响进行了深入分析。"本 PEP 中改变的行为旨在让尽可能多的使用场景能 '做正确的事'"。尤其是允许发行版可以针对系统软件包以及 pip 安装的软件包指定两组不同位置互不干扰,这种做法基本上是对当前一些发行版的现行做法进行了标准化。PEP 的 "对发行版的建议" 这一小节专门指出这种隔离方式应该是未来的最应该使用的做法。
然而,在某些情况下,发行版并不希望默认采用这种新行为。那些用来运行单个应用程序的 container 容器就可能无法从这个规定中得到任何好处,所以 PEP 建议发行版针对这些 container image 可以改变默认行为:
为这些单一应用的 container 制作官方镜像的发行版(例如,Docker container images)就应该删除 EXTERNALLY-MANAGED 文件,最好确保该镜像内用户在安装软件包更新时也不会导致这个文件再次出现(比如 RUN apt-get dist-upgrade 这种情况)。在基于 dpkg 的系统上,只要使用 dpkg-divert –local 来永久性地改变这个文件的名字就可以了。在其他系统上可能需要有一些配置 flag 供 post-install 脚本调用来重新删除 EXTERNALLY-MANAGED 这个文件。
总的来说,这个 PEP 似乎没有很大争议。PEP 的相关讨论中大部分都是积极的回应,尽管 Paul Moore(他可能是最终会批准该提案的 PEP-Delegate)有些担心那些被影响的人可能完全不知道这个信息:
我还是希望能有更多的讨论。人们提到的 linux-sig 讨论自从 5 月份以来,总共只有 6 条信息,而这里也只有几条 message。我不认为这里我们可以有充足理由判定 "沉默意味着赞同",目前很难确定可能会感兴趣的那些人员主要活跃在什么地方,所以在这种情况下,沉默似乎更可能意味着 "他们完全不知道这个提议"。具体来说,我建议在 PEP 中加入一小节来列出已经确认支持该提案的发行版,列明发行版的名称,并提供一个链接来指向他们当时做出承诺的那个信息。
假如上述这个确认信息会很快搜集完整,或者人们采纳并解决了那些反对意见和建议,那么 PEP 668 应该会成为 Python 一个很重要的进步。像 DNF 和 apt 这样的工具同 pip 之间的冲突,显然在过去确实造成了一些问题,而且今后还会再次造成问题。因此要是能找到一种既能合作又不会造成重大向后兼容问题的方法就非常重要了。我们要确保其他发行版加入这个改动,并且所有这些改动最终都是可选的,那么应该会给我们带来更稳定的发行版,最终会让用户对 Python 和发行版的满意度都会更高。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~