python项目使用uv+meson管理#
这段时间忙着在做一些python项目的框架和后端构建管理,简单说下背景。
有大量的python项目需要继承到一个大的workspace里面,其中的一部分项目需要先使用cython生成对应的.c文件再执行编译操作,默认使用setuptools构建,但是构建速度很慢,而且没有高效的缓存管理。让我一点一点来说说,遇到了什么问题,怎么解决的
UV管理代码层面#
uv管理代码很简单,只需要安装uv,然后创建对应的目录,并且执行指令即可。
具体到根目录,多个工程的根目录是一个大的pyproject.toml,里面写上具体的members是啥,包括待安装的依赖的地址。dependency-groups是uv本身的dev需要使用的依赖,这些依赖之所以写在dependency-groups的目的以后再说。
# Empty workspace only
[tool.uv.workspace]
members = [
"xxx",
"yyy",
]
[tool.uv]
index-url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"
[tool.uv.sources]
local_dependency = { workspace = true }
remote_dependency = { url = "http:abc.def.whl" }
[dependency-groups]
dev = [
"cython>=3.1.5",
"meson>=1.9.1",
"ninja>=1.13.0",
"numpy>=1.26.0",
"ruff>=0.12.11",
"setuptools>=80.9.0",
"wheel>=0.45.1",
]
之后就可以添加具体的项目到根目录的pyproject.toml了
# For app
mkdir -p /path/to/subproject && cd /path/to/subproject && uv init --name subproject
# For lib
mkdir -p /path/to/sublibproject && cd /path/to/sublibproject && uv init --name sublibproject --lib
Meson构建层面#
一般的python项目,构建后端是不需要meson的,这里是因为有个项目需要先将代码转换为c,再进行构建操作。这里遇到的问题比较多。
我尝试了两种方法
- 方法1 meson-python+meson:其中meson-python作为构建的backend
- 方法2 setuptools + meson:其中setuptools负责调用meson setup并且调用meson build指令。
先说方法2,我个人在执行构建的时候遇到了这么几个问题
我期望uv sync在我更改了目录之后,会重新执行构建,但是uv sync默认是editable的安装方式,也就是假设默认是直接调用对应的代码,因此它检测代码是否需要重新构建的依据默认是检查代码的pyproject.toml,setup.py( For directories, uv caches based on the last-modified time of the
pyproject.toml,setup.py, orsetup.cfgfile.请参考https://docs.astral.sh/uv/concepts/cache/#dependency-caching)。所以,如果py文件做了修改,需要重新构建,再次执行uv sync,并不会重新生成.c乃至对应的.o,.so文件。具体的解法有两种:一种是我们直接放弃让uv管理具体的cache,直接让meson管理后端cache,那么需要在根目录下面的pyproject.toml里面写上如下内容,不让uv管理
[tool.uv] reinstall-package = ["your-package-name"]另一种是修改uv判断是否需要重新编译的逻辑,在项目的的pyproject.toml里面写上根据所有py文件和mesonbuild文件判断是否需要重新执行构建。这种方法的问题是,用户需要知道具体的构建依赖的文件有哪些
[tool.uv] cache-keys = [{ file = "**/*.py" }, { file = "meson.build" }]
上一个问题解决了,新的问题出现了,默认情况下uv 构建的环境在构建结束就删除了,meson的cache就会报错诸如cython文件不见了。这个是因为uv的venv(也就是uv sync安装的目录)和构建的目录是隔离的,因此解决办法很简单了,让项目构建的目录不和venv隔离,直接使用venv的依赖。因此修改为如下的内容(no-build-isolation-package的含义可以自行查找)。需要加上dev的依赖,让uv 管理
[tool.uv] reinstall-package = ["your-package-name"] no-build-isolation-package = ["your-package-name"] dev = [ "cython>=3.1.5", "meson>=1.9.1", "ninja>=1.13.0", "numpy>=1.26.0", "ruff>=0.12.11", "setuptools>=80.9.0", "wheel>=0.45.1", ]
我个人更偏向meson-python的方法,使用起来很简单。需要做如下修改,在项目里面的pyproject.toml写上
requires = [
"meson-python>=0.15.0",
"meson>=1.2.0",
"ninja>=1.10.0",
"Cython>=3.0.0",
"numpy>=1.20.0",
]
build-backend = "mesonpy"
对应的meson.build写上之后,维持根目录的no-build-isolation-package,另外去掉setuptools和wheel,添加上meson-python的支持即可。
[tool.uv]
reinstall-package = ["your-package-name"]
no-build-isolation-package = ["your-package-name"]
[dependency-groups]
dev = [
"cython>=3.1.5",
"meson>=1.9.1",
"meson-python>=0.15.0",
"ninja>=1.13.0",
"numpy>=1.26.0",
"ruff>=0.12.11",
]
这里具体的meson.build就不多赘述了,将原本的setup.py的逻辑复刻即可。我这里简单来说就是执行cython,然后执行安装/
构建时候的奇葩问题#
meson无法重命名生成的so文件,很多文件本身basename又重合,怎么解决?Meson 层面还遇到的一个问题是怎么正确地执行安装和生成操作,代码当中有很多文件名字相同,所以只能把名字拼上路径,而py.extesion_module又不能自定义生成的so文件的安装名称,所以我只能最后使用custom_target在build目录里面生成自定义的路径,最终使用install_subdir执行安装操作。但是注意!
这种方式只能使用于非editable模式安装的代码,因为install_subdir导致meson-python并不知道如何找到构建的so文件。但是uv sync默认是使用editable模式执行安装的
为了能够解决editable模式安装的代码,最后我只能采用了一种很hack的方法,全局的meson.build处理每个文件,如果遇到重名的文件就跳过,然后使用重名文件所在目录的meson.build来处理这些重名文件,如果重名文件的目录没有meson.build,就直接出错停止。这种方式配合上设置py.extension_module的install为true,最终可以正常的生成要安装的so文件。python的import也没有问题了
sccache权限问题:这个问题是真的折腾了我很久,我先来描述背景。因为项目需要,对于meson构建的库需要两种安装方式:
- 一种调用uv sync,触发权限为普通用户eai
- 另一种方式需要使用uv pip install –system -e,需要root(sudo)权限。
问题出在如果先执行sudo uv pip install –system -e,sccache如果以root权限执行,那么它会把下载生成的cache文件保存为只有root才能读取。然后使用uv sync就会触发普通用户尝试写文件的权限错误。如果先以普通用户执行uv sync,就会出现meson工具链是root身份,但是sccache生成文件无法写入root相关权限的问题。因此最后我写了一个脚本,判断sccache是不是当前用户启动的,如果不是就需要重新启动,不过目前只支持root和另外一个用户(最多俩人)
结尾#
唉,尴尬
