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.cfg
file is redundant to the equivalent information expressed in thedistutils
/setuptools
driver file,setup.py
.The bolerplate bootstrap and the (largely) boilerplate
buildout.cfg
are 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.buildout
may 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
setuptools
world 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.py
script.Resist the temptation to exploit the additional expressiveness of the configuration language provided by
zc.buildout
when packaging reusable librarires. -
Keep
setup.py
as 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.py
introspectable.In particular, move the various kinds dependency information out into globals, if the
zc.buildout
framework cannot introspect it directly.For instance, because
zc.buildout
does not support thetests_require
keyword directly, we should perhaps adopt a conventional name (e.g.,TESTS_REQUIRE
) at "module scope" in thesetup.py
script, which contains the testing dependencies to be passed tosetup()
.zc.buildout
could 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
/setuptools
commands (build
,develop
,install
,test
, see note below) work with the package in an environment which does not havezc.buildout
installed.The
virtualenv
package provides an easy means of creating such environments. -
Distribute the
sdist
tarball.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
sdist
distributions, 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_win
package, for the benefit of Windows users who can't compile the extension. You might also distribute abdist_egg
built 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 thesdist
version.