You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

pip安装PyPI包时执行shell脚本失效及requirements.txt找不到问题

问题

我发布了一个依赖额外Apt包的PyPI包,于是在GitHub仓库根目录放了setup.sh脚本,想让用户执行pip install mypackage时自动运行这个脚本。我在setup.py里通过cmdclass自定义了CustomInstall类,代码如下:

from setuptools import setup, find_packages
from setuptools.command.install import install
import subprocess
import codecs 
import os

class CustomInstall(install):
    def run(self):
        subprocess.check_call("bash setup.sh", shell=True)
        install.run(self)

with open('requirements.txt') as f:
    requirements = f.read().splitlines()


setup(
    ...
    cmdclass={'install': CustomInstall},
    install_requires=requirements,
    ...
)

本地执行python setup.py sdist bdist_wheel时,CustomInstall类能正常运行,但用pip安装时这个类被忽略。后来我只执行python setup.py sdist生成tar.gz包,用pip安装时又报错:

with open('requirements.txt') as f:

           ^^^^^^^^^^^^^^^^^^^^^^^^

  FileNotFoundError: [Errno 2] No such file or directory: 'requirements.txt'

requirements.txt在仓库根目录,想知道哪里错了?是路径问题吗?

解决方案

1. 为什么pip安装时CustomInstall被忽略?

当你用bdist_wheel生成wheel包时,pip安装wheel包不会执行setup.py里的自定义install命令——wheel包是预构建的,安装过程跳过了setup.py的执行阶段,直接解压文件到目标路径。这就是本地运行setup.py有效,但pip装wheel包时自定义逻辑失效的原因。

2. requirements.txt找不到的原因

生成sdist源码包时,默认只会把setup.py、setup.cfg、pyproject.toml和包的源码文件打包进去,requirements.txt和setup.sh不会被自动包含到tar.gz包里。所以pip安装源码包时,运行setup.py找不到这两个文件,就会报错。

3. 修复步骤

(1)确保必要文件被打包进源码包

在仓库根目录创建MANIFEST.in文件,添加以下内容,把requirements.txt和setup.sh包含到源码包中:

include requirements.txt
include setup.sh

这样生成sdist时,这两个文件会被加入tar.gz包。

(2)调整setup.py中的路径处理

因为安装时当前工作目录可能不是包的解压目录,所以要通过__file__获取setup.py所在的绝对路径,再拼接其他文件的路径,避免相对路径错误:

from setuptools import setup, find_packages
from setuptools.command.install import install
import subprocess
import os

# 获取setup.py所在目录的绝对路径
BASE_DIR = os.path.abspath(os.path.dirname(__file__))

class CustomInstall(install):
    def run(self):
        # 拼接setup.sh的绝对路径
        setup_sh_path = os.path.join(BASE_DIR, 'setup.sh')
        subprocess.check_call(["bash", setup_sh_path], shell=False)
        install.run(self)

# 读取requirements.txt的绝对路径
with open(os.path.join(BASE_DIR, 'requirements.txt')) as f:
    requirements = f.read().splitlines()

setup(
    ...
    cmdclass={'install': CustomInstall},
    install_requires=requirements,
    ...
)

这里把shell=True改成shell=False并传入列表形式的命令,更安全,也避免路径解析问题。

(3)关于wheel包的替代方案

如果想支持wheel包的自动脚本执行,wheel本身不允许安装时运行自定义脚本(这是设计上的安全限制)。你可以:

  • 只发布源码包(sdist),这样用户安装时会执行setup.py的自定义逻辑;
  • 把Apt依赖的检查和提示放到包的初始化代码里(比如__init__.py),当用户第一次导入包时检测依赖是否存在,引导用户手动安装;
  • 使用post-install钩子,但这需要依赖setuptools的特定机制,且同样不适用于wheel包。

内容的提问来源于stack exchange,提问作者Ayus Chatterjee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 09:25:14