跳过正文

2025-10-21-python项目使用uv+meson管理

·2527 字·6 分钟
作者
菜狗
Focus
目录

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,我个人在执行构建的时候遇到了这么几个问题

  1. 我期望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, or setup.cfg file.请参考https://docs.astral.sh/uv/concepts/cache/#dependency-caching)。所以,如果py文件做了修改,需要重新构建,再次执行uv sync,并不会重新生成.c乃至对应的.o,.so文件。具体的解法有两种:

    1. 一种是我们直接放弃让uv管理具体的cache,直接让meson管理后端cache,那么需要在根目录下面的pyproject.toml里面写上如下内容,不让uv管理

      
      [tool.uv]
      reinstall-package = ["your-package-name"]
      
    2. 另一种是修改uv判断是否需要重新编译的逻辑,在项目的的pyproject.toml里面写上根据所有py文件和mesonbuild文件判断是否需要重新执行构建。这种方法的问题是,用户需要知道具体的构建依赖的文件有哪些

      [tool.uv]
      cache-keys = [{ file = "**/*.py" }, { file = "meson.build" }]
      
  2. 上一个问题解决了,新的问题出现了,默认情况下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,然后执行安装/

构建时候的奇葩问题
#

  1. meson无法重命名生成的so文件,很多文件本身basename又重合,怎么解决?Meson 层面还遇到的一个问题是怎么正确地执行安装和生成操作,代码当中有很多文件名字相同,所以只能把名字拼上路径,而py.extesion_module又不能自定义生成的so文件的安装名称,所以我只能最后使用custom_target在build目录里面生成自定义的路径,最终使用install_subdir执行安装操作。但是注意!

    1. 这种方式只能使用于非editable模式安装的代码,因为install_subdir导致meson-python并不知道如何找到构建的so文件。但是uv sync默认是使用editable模式执行安装的

    2. 为了能够解决editable模式安装的代码,最后我只能采用了一种很hack的方法,全局的meson.build处理每个文件,如果遇到重名的文件就跳过,然后使用重名文件所在目录的meson.build来处理这些重名文件,如果重名文件的目录没有meson.build,就直接出错停止。这种方式配合上设置py.extension_module的install为true,最终可以正常的生成要安装的so文件。python的import也没有问题了

  2. 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和另外一个用户(最多俩人)

结尾
#

唉,尴尬

狗头的赞赏码.jpg