如何从 setup_require 依赖项覆盖 setuptools 命令?

How can I override a setuptools command from a setup_require dependancy?

上下文:

为了能够在构建时在 setuptools 中将 GNU gettext po 文件转换为 mo 文件,我创建了 setuptools.command.build_py 的子 class 来编译它们(通过 pymsgfmt 的副本) 在调用其基数 class:

之前
from setuptools.command.build_py import build_py as _build_py

class build_py(_build_py):
    parent = _build_py
    def run(self):
        self.compile_po_files()  # internal implementation
        self.parent.run(self)

    def get_outputs(self):  # overriden to produce a correct list of installed files
        build_mo = self.get_finalized_command("build_mo")
        return _build_py.get_outputs(self) + self.outputs

那我只需要在setupcmdclass参数中声明即可:

setup(
    ...
    cmdclass = {"build_py", mypgk.build_py},
)

到目前为止一切顺利,当我的模块在 setup.py 脚本中安装和导入时,setuptools 的构建阶段正确处理了我的 po 文件。

问题:

目标是允许使用 pip 简单安装源分发。事情看起来不错,因为 pip 会处理任何依赖项,前提是它们在 install_requiressetup_requires 参数中声明。这就是先有鸡还是先有蛋的问题:当 setup.py 被 运行 时依赖项被安装,但是如果没有 mypkg 就不能 运行 。

当前研究:

我已经尝试使用魔术 entry_pointsmypkg setup.py 脚本中声明 build_py 覆盖:

...
entry_points = {
    "distutils.commands": [
        "build_py = mypkg:build_py",
        ],
}

但它没有任何效果,而我可以这样声明一个有效的新 build_mo 命令:

entry_points = {
    "distutils.commands": [
        "build_mo = mypkg:build_py",
        ],
}

长话短说,python setup.py build_mo 调用我的覆盖,而 python setup.py build_py 调用 setuptools 版本。

问题:

为什么我用 entry_points 声明覆盖 build_py 命令的尝试不起作用,我该怎么做?

解释问题

我已经接近解决方案了。在对 setuptools 文档和来源进行更多研究后,我终于意识到它已经使用 entry_points 机制来用它自己的命令覆盖 distutils 命令。

这意味着当您尝试覆盖 setuptools 命令时,实际上您建议对同一命令进行第二次覆盖。由于 setuptools 处理它的方式,只使用找到的第一个覆盖,并且根据我的测试,setuptools 一个是第一个。

我现在可以说,正因如此,只有来自 distutils 且未在 setuptools 中被覆盖的命令才能以这种方式处理。好消息是 build 没有被覆盖,在正常使用中,build_py 总是从 build.

调用

可能的解决方案:

由于 build 命令未被 setuptools 覆盖,因此很容易将其替换为 entry_point。然后自定义 build 命令 class 可以更新 cmdclass 目录以声明自定义 build_py class 因为基础 build 加载它。代码可以是:

from distutils.command.build import build as _build

class build(_build):
    parent = _build
    def run(self):
        self.distribution.cmdclass["build_py"] = build_py
        self.parent.run(self)

在我的测试中,setuptools 使用自定义 build_py class 和一个简单的

就足够了
setup(
    ...
    setup_requires = ["mypkg"],
)