Serge Ballesta
Serge Ballesta

Reputation: 148965

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

Context:

In order to be able to translate GNU gettext po files into mo ones at build time in setuptools, I have created a subclass of setuptools.command.build_py that compiles them (through a copy of pymsgfmt) before calling its base 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

Then I have only to declare it in the cmdclass parameter of setup:

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

So far, so good, when my module is installed and imported in the setup.py script, the build phase of setuptools correctly process my po files.

Problem:

The goal would be to allow a simple installation of a source distribution with pip. Things look nice, because pip takes care of any dependency provided they are declared in a install_requires, or setup_requires parameter. And this is where the chicken and egg problem lies: the dependancy are installed when setup.py is runned, but it cannot run without mypkg being first installed.

Current research:

I have tried to use the magic entry_points to declare the build_py override in mypkg setup.py script:

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

but it has no effects, while I can declare a working new build_mo command that way:

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

Long story short, python setup.py build_mo calls my override, while python setup.py build_py calls setuptools version.

Question:

Why does my attempt of overriding the build_py command with an entry_points declaration not work, and how could I do it?

Upvotes: 5

Views: 2234

Answers (1)

Serge Ballesta
Serge Ballesta

Reputation: 148965

Explaining the problem

I was close to the solution. After some more research in setuptools docs and sources, I finally realized that it already used the entry_points machinery to override distutils commands by its own ones.

That means that when you try to override a setuptools command, in fact you propose a second override for the same command. Because of the way setuptools processes it, only the first override found is used, and from my tests, setuptools one is that first.

I can now say that because of that, only commands from distutils that are not overriden in setuptools can be processed that way. The good news is that build is not overriden and that in normal use, build_py is always called from build.

Possible solution:

As the build command is not overriden by setuptools, it is easy to replace it with an entry_point. Then the custom build command class can update the cmdclass directory to declare the custom build_py class because the base build loads it. The code can be:

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)

In my test, it is enough to make setuptools use the custom build_py class with a simple

setup(
    ...
    setup_requires = ["mypkg"],
)

Upvotes: 4

Related Questions