zc.buildout vs. "plain" setuptools
tl;dr
A brief proposal on how and when one might prefer to use "standard"
setuptools features, rather than relying on zc.buildout.
Overview
The Zope development community has largely adopted zc.buildout as a
de facto standard for how it builds and maintains Zope-related
software. This post attempts to outline some reasons why zc.buildout
may not be a "sufficient" tool for all cases, and proposes some additional
practices which may help alleviate the issues described.
Strengths of zc.buildout
The zc.buildout framework
allows for creating a repeatable environment using one or more
"recipes" (which serve as plug-ins for the fraemwork) and a
configuration file, which defines policies for a given environment.
There are recipes available for a wide variety of tasks, ranging from
building and testing a single Python package, to building a Python
application using any number of such packages, to building arbitrary
applications from source (e.g., using "configure && make && make install").
After bootstrapping, a developer can ensure that her environment is
in a known state by running (or re-running) the bin/buildout command
created by the bootstrap script. This command parses the buildout.cfg
file, and uses it to build a number of "parts", each configured via
a section in the configuration file, and driven by a recipe.
As the environment grows more complex, the find-grained control
provided by zc.buildout becomes much more helpful.
Weaknesses of zc.buildout
At the simpler end of the spectrum, however (building and testing a single, pure-Python package), the advantages are not so clear. In particular:
At the level of a single package, the policy maintained in the
buildout.cfgfile is redundant to the equivalent information expressed in thedistutils/setuptoolsdriver file,setup.py.The bolerplate bootstrap and the (largely) boilerplate
buildout.cfgare noticeable as "clutter" in the checkout / tarball, which provide no interesting benefit to developers who mean only to "consume" the package.-
Python developers unfamiliar with
zc.buildoutmay be put off by its "foreign" paradigm, which violates their expectation that a package should be manageable using the standard commands ofdistutils(a de jure standard) orsetuptools(a de facto standard).For instance, runing the tests for a package in the
setuptoolsworld involves merely unpacking the tarball and runningsetup.py test; forzc.buildout, the equivalent steps are unpacking, running the bootstrap script, then runnning an arbitrarily-named test runner script. Documenting this paradigm in each package is extra effort for the maintainer, as well.
Recommended Practices
Rather than advocating that the Zope community abandon zc.buildout,
for the "simple package" case, I would like to propose some practices
which mitigate some of the downsides.
-
Keep metadata inside the
setup.pyscript.Resist the temptation to exploit the additional expressiveness of the configuration language provided by
zc.buildoutwhen packaging reusable librarires. -
Keep
setup.pyas declarative as possible.Avoid "tricky" things inside the script: use the "standard" mechanisms as much as possible. For instance, use
pkg_resources.find_packages, rather than roll your own file-finding logic. -
Make metadata in
setup.pyintrospectable.In particular, move the various kinds dependency information out into globals, if the
zc.buildoutframework cannot introspect it directly.For instance, because
zc.buildoutdoes not support thetests_requirekeyword directly, we should perhaps adopt a conventional name (e.g.,TESTS_REQUIRE) at "module scope" in thesetup.pyscript, which contains the testing dependencies to be passed tosetup().zc.buildoutcould then use such a name to configure its test runner scripts. -
Ensure that the package works in the abasence of
zc.buildout.Before releasing the package to the wider Python world, ensure that the "standard"
distutils/setuptoolscommands (build,develop,install,test, see note below) work with the package in an environment which does not havezc.buildoutinstalled.The
virtualenvpackage provides an easy means of creating such environments. -
Distribute the
sdisttarball.Unless your package has C extensions, you should plan to distribute only the source distribution (the one created by
setup.py sdist). This tarball will contain all the files in your package, including tests and documentation, as opposed to the various binary formats. The binary formats often have subtle portability issues, as well some of which can't be resolved (e.g., 32-bit vs. 64-bit, Python version, UCS2 vs. UCS4, etc.).Windows developers should be able to install pure Python
sdistdistributions, as well, even without the compiler required to build C extensions.If your package does include C extensions, then you should plan, if possible, to build and distribute the
bdist_winpackage, for the benefit of Windows users who can't compile the extension. You might also distribute abdist_eggbuilt for Windows (some users prefer the installer version, and others the egg). Generally, it would be best to avoid building other binary distributions at all: on any platform where Python or Zope is likely to run, the extension should be trivial to build from thesdistversion.