从编程语言特定的 pip, conda,到操作系统级的 apt, pacman,我们在构建工程时离不开包管理工具为我们处理依赖。本文旨在总结这些工具的使用技巧。
如果你也经常用包管理工具,可能能从本文中获得启发。然而,本文不保证全面性(不保证这是认识它们的最佳视角/使用它们的最佳实践)和准确性(以工具的文档为准)
本系列的更新策略:标注 X月X日更新
系统级包管理工具的使用技巧
一些小知识:
pacman -S package_name1 package_name2
安装的是包和包的依赖项- pacman 数据库中依据安装理由将所有包分为两类:显式命令安装(explicit) 和 作为依赖安装(deps)。
- 可以通过 pacman -Q 选项来查询数据库,如 -Qe 列出所有显式命令安装的包;-Qi <package-name> 列出所选的性质。
用同时升级解决版本死锁
考虑下面的例子:
$ sudo pacman -S pipewire
error: failed to prepare transaction (could not satisfy dependencies)
installing libcamera (0.3.0-1) breaks dependency 'libcamera.so=0.2-64' required by libcamera-ipa
$ sudo pacman -S libcamera libcamera-ipa
installing libcamera (0.3.0-1) breaks dependency 'libcamera.so=0.2-64' required by pipewire
说明当下版本的(很可能是作为依赖安装的) libcamera 同时被 pipewire 和 libcamera-ipa 所依赖。新版本的 pipewire 依赖新版本的 libcamera,升级 pipewire 必须自动升级 libcamera,而这会让旧版本的 libcamera-ipa 损坏(而这个旧版本可能是显式安装的,包管理器不敢自作主张升级)。反之同理。这形成了一个版本死锁。
libcamera <- libcamera-ipa
\_ pipewire
-- means version tied
如此场景下,只能同时升级 libcamera-ipa 和 pipewire,这种情况下包管理器知晓两个包都要升级,从而可以给出一个同时升级三个包的计划:
sudo pacman -S libcamera-ipa pipewire
【2024年12月30日更新】
对于 Arch Linux 系统中出现的版本不一致的问题,可能是先前 partial upgrade 的恶果(Arch Linux 是不支持 partial upgrade 的)。推荐的解决方案是用 pacman -S 的 -u ( 即 –sysupgrade ) 选项来更新整个系统:
sudo pacman -Syu pipewire libcamera libcamera-ipa
先更新包索引,再下载包或更新系统
Ubuntu 安装包的时候,经常遇到 ”Error 404… E: Unable to fetch some archives, maybe run apt-get update or try with –fix-missing?” 的报错,而在 apt-get update
之后重新安装,就能够成功。为什么?
apt-get update 的说明是 “Used to re-synchronize the package index files from their sources. The indexes of available packages are fetched from the location(s) specified in /etc/apt/sources.list” 简单来说,报错的原因很可能是本地的数据库存储的链接过期失效了或者没有所需最新版本的地址,apt-get update 通过从远端拉去最新的包索引解决了这一问题。
这对应 pacman 的 -y 选项,它 “updates the local package database that pacman maintains and prevents pacman from trying to install & requesting an outdated version from mirror sites”,是解决 “Error retrieving file from… the requested URL returned error 404” 的办法。
【2024年12月30日更新】
对于 Arch Linux 上的 pacman 而言,当更新本地包数据库时,必须更新整个系统。(When refreshing the package database, always do a full upgrade with pacman -Syu
.)
换言之,不要使用形如 pacman -Sy package
的命令。
杂项
一些报错的原因可能是包管理器自身依赖的包版本过低没有更新。这种情况下,先设法升级这个依赖。例如,pacman 报错 ”corrupted package – PGP signature“,则可能是 archlinux-keyring 版本过低的问题,解决方案是 pacman -Sy --needed archlinux-keyring
。(2024年12月30日更新:这个命令之所以没有加 -u,是因为更新整个系统的所有包,会导致有些包安装过程中报错 “corrupted package”,因此必须先进行 partial upgrade 更新 archlinux-keyring,随后再更新整个系统)
小实验:用 pacman -Qi 查询数据库
以 pacman -Qi dbus 为例,其输出是:
Name : dbus
Version : 1.14.10-1
Description : Freedesktop.org message bus system
Architecture : x86_64
URL : https://wiki.freedesktop.org/www/Software/dbus/
Licenses : GPL custom
Groups : None
Provides : libdbus libdbus-1.so=3-64
Depends On : audit expat systemd-libs libaudit.so=1-64 libsystemd.so=0-64
Optional Deps : None
Required By : avahi bluez jack2 libdecor libnvme libpcap libpulse libteam pipewire pipewire-audio pipewire-pulse rtkit systemd wpa_supplicant xorg-server
Optional For : None
Conflicts With : libdbus
Replaces : libdbus
Installed Size : 911.37 KiB
Packager : Jan Alexander Steffens (heftig) <heftig@archlinux.org>
...
Install Reason : Installed as a dependency for another package
dbus 是进程间通信系统,通常作为 systemd (系统和服务管理器) 的依赖安装。可以看到,包依赖的可能是包,也可能还额外包括包对应的 .so 文件 (shared object, 共享库,动态链接库)。同时,包提供的也可以是包和 .so 文件。你能看到那些包依赖这个包,以及这个包是通过命令行显式安装的还是作为依赖安装的。
编程语言的包管理工具:以 Python pip 为例
pip 的一些常用指令:
查询包信息 python -m pip show package-name
用同时升级解决版本死锁
考虑下面的例子:
>>> import openai
...
File "C:\miniconda3\envs\genir\lib\site-packages\pydantic_core\__init__.py", line 6, in <module>
from ._pydantic_core import (
ModuleNotFoundError: No module named 'pydantic_core._pydantic_core
这个环境下,pydantic 和 pydantic core 都是作为依赖安装的。它们出现了问题,需要重新安装或者升级,记得带上依赖它的、显式安装的包:
pip install pydantic pydantic_core openai
pip install 从哪里安装包?
”包“的指代
Python 的一个分发包(distribution package)是一个可供下载的软件。例如,pip install pkg
就是下载名为 pkg 的分发包。
Python 的一个导入包(import package)是一个 Python 编程语言模块(module)。Python Modules 文档 将模块解释为”为了长程序的可维护性,将定义放到一个文件中,以便脚本或者交互式解释器中使用“、Python Packaging Glossary 将其定义为”代码复用的最基本单元 (basic unit of code reusability)“、Javascript 文档将模块定义为”复杂工程中将 JS 程序拆分为独立的文件,在使用时导入“。程序中的 import pkg
就是导入一个模块。
附注:Python中的扩展模块(extension module)定义为用 C, C++ 等低层语言写成的模块。
下载分发包就可以使用对应的导入包,但二者未必重名,例如图像处理的 Pillow 分发包提供 PIL 模块。看到 import foo
就 pip install foo
是易错和危险的。不同的分发包可能提供重名的导入包(比如源于代码仓库的fork),罕见地,一个分发包可能提供多个导入包。
Python 分发包的格式
Pypi(Python 包索引)上的包以两种格式存在:源分发包(Source Distribution, sdist)和二进制分发包(Binary Distribution,又称 Wheel)。前者形如 pip-23.3.1.tar.gz
,后者形如 pip-23.3.1-py3-none-any.whl
。
源分发包的 .tar.gz 压缩包中含有源代码和存储元信息的 PKG-INFO 文件。作为小实验,可以从 Pypi 中下载 pip-24.3.1.tar.gz,然后解压之 python -m tarfile -e pip-24.3.1.tar.gz
,查验其中的文件。
二进制分发包是安装时需要拷贝的文件。典型地,对含有扩展模块的包,Wheel 含有平台相关的二进制,即 DLL 或 .so。对于纯 Python 语言编写的模块,Wheel 剥离了测试、文档等 sdist 中常有的文件。Wheel 是一个 zip 文件,作为小实验,可以从 Pypi 中下载 pip-24.3.1-py3-none-any.whl,然后解压之 python -m zipfile -e pip-24.3.1-py3-none-any.whl pip-wheel
。Wheel 名中,py3 代表 Python 实现,none 代表不依赖 Python 版本,any 代表不依赖平台。
egg 是一个在 2023 年 8 月被弃用(用 wheel 取代的)格式。
pip 可以直接安装 sdist 和 wheel 格式的分发包:python -m pip install {sampleproject-1.0.tar.gz, sampleproject-1.0-py3-none-any.whl}
查看安装了哪些包
python -m pip list
查看环境中安装了哪些分发包
python -m pip show package-name
查看分发包的信息
动手创建一个 Python 分发包
创建一个纯 Python 语言的包
构建前端(build frontend),如 pip 和 build,会把构建分发包的任务交给构建后端(build backend),如 setuptools。
创建如下文件树,在 example.py 和 pyproject.toml 中填入适合的内容。下面假设 __init__.py 留空。
packaging_tutorial/
├── LICENSE
├── pyproject.toml
├── README.md
├── src/
│ └── mypkg/
│ ├── __init__.py
│ └── example.py
└── tests/
在该文件夹下运行 python -m build
,会得到 dist 文件夹,生成了分发包。
安装该分发包:python -m pip install ./dist/mypkg-0.0.1-py3-none-any.whl
,验证之:from mypkg import example; example.add_one(2);
创建一个含有扩展模块的包
把含有扩展模块的包加入 Python 中,可以参考这篇教程(它对 python setup.py build 等命令的使用可能是过时的 as of 2024)。
Leave a Reply