2

I have a distutils-style Python package which requires a specific, and quite large, dependency for its build step. Currently, this dependency is specified under the setup_requires argument to distutils.setup. Unfortunately, this means the dependency will be built for any execution of setup.py, including when running setup.py clean. This creates the rather ironic situation of the clean step sometimes causing large amount of code to be compiled.

As I said, this setup dependency is only required for the build step. Is there a way to encode this logic in setup.py so that all commands that do not invoke the build command are run without it?

hoefling
  • 59,418
  • 12
  • 147
  • 194
skoy
  • 266
  • 2
  • 10

2 Answers2

2

You can always order the Distribution to fetch some packages explicitly, same way as they will be if you define them in setup_requires. Example with numpy dependency required for build command only:

from distutils.command.build import build as build_orig
from setuptools import setup, find_packages,  Command, dist


class build(build_orig):

    def run(self):
        self.distribution.fetch_build_eggs(['numpy'])
        # numpy becomes available after this line. Test it:
        import numpy
        print(numpy.__version__)
        super().run()

setup(
    name='spam',
    packages=find_packages(),
    cmdclass={'build': build,}
    ...
)

The dependencies are passed the same as they would be defined in setup_requires arg, so version specs are also ok:

self.distribution.fetch_build_eggs(['numpy>=1.13'])

Although I must note that fetching dependencies via setup_requires is usually much slower than installing them via pip (especially when you have some heavy dependencies that must be built from source first), so if you can be sure you will have pip available (or use python3.4 and newer), the approach suggested by phd in his answer will save you time. Fetching eggs via distribution may, however, come handy when building for old python versions or obscure python installations like the system python on MacOS.

hoefling
  • 59,418
  • 12
  • 147
  • 194
1
if sys.argv[0] == 'build':
    kw = {'setup_requires': [req1, req2, …]}
else:
    kw = {}

setup(
    …,
    **kw
)

Another approach to try is override build command with a custom cmdclass:

from setuptools.command.build import build as _build

class build(_build):
    def run(self):
        subprocess.call(["pip", "install", req1, req2…])
        _build.run(self)

setup(
    …,
    cmdclass={'build': build},
)

and avoid setup_requires at all.

David Brabant
  • 41,623
  • 16
  • 83
  • 111
phd
  • 82,685
  • 13
  • 120
  • 165
  • Unfortunately that fails when the build command is invoked as a sub-command of something else. (e.g.: When running `python setup.py install`.) – skoy Jan 17 '18 at 06:47
  • I added another approach to the answer. – phd Jan 17 '18 at 10:04