Introducing Dovetail
====================
Dovetail is a light-weight, multi-platform build tool for Python with
Continuous Integration servers like Jenkins in mind.
-----
**TL;DR**
Builds are complex, integrate many tools and sometimes must run on
many platforms. Writing good build scripts is hard.
Dovetail helps in all these areas, and is not a rip'n'replace for your
existing tools. You can readily automate a build using Dovetail.
-----
Building an application is not just running::
> python setup.py sdist
What about:
* Building binary distributions *for several target platforms*
* Building the user documentation *and* the API docs?
* Running your unit tests, sometimes using several test frameworks?
* Installing your application in a clean virtual environment and running
user tests?
* Running code quality tools like
`Coverage <http://http://nedbatchelder.com/code/coverage/>`_ and
`Pylint <http://pypi.python.org/pypi/pylint/>`_?
* Tagging your code in your DVCS?
* Uploading the artifacts to a repository? That's probably at least an Egg,
a source distribution, documentation and your web site
**How can you guarantee everyone, especially the new team members, are
building in the same way?**
Many teams solve this by writing scripts, but that raises more questions:
* Do you have a *lot of scripts* lying around, each doing their
own thing, and little shared code?
* Do you have *operating system specific scripts* that do the same
thing, but on different operating systems?
* Are your scripts *reliable* and *maintainable*?
If you need to improve in these areas, **Dovetail can help**. Dovetail:
* Is **pure Python**, so the build runs everywhere and is maintainable
* Provides a **simple API** to externalize many common build
requirements
* There are **no new configuration file formats** or 4GLs of abstruse XML
or other syntaxes
* Makes it simple to **query the build environment** and adjust
the build appropriately
* **Audits all the build steps and decisions**
* Properly **catches build errors** and displays the details of what went wrong
* Makes it terribly easy to **automate the build** in a tool like
`Jenkins <http://jenkins-ci.org/>`_.
A nice unexpected benefit for the maintainer was that it has become easier
to build in my IDE; I also get precisely the same build from the command line.
Dovetail does not replace `Setuptools <http://pypi.python.org/pypi/setuptools/>`_
or `distutils <http://docs.python.org/distutils/introduction.html>`_ -
these are the perfect tools for the specific build step of creating a distributable
package.
Functionality
-------------
A Dovetail build script is *a standard Python script*. Functions are declared to
be *tasks* in the build by decorating them. Further decorators declare:
* *Task dependencies*, with the same build script or across related files
* *Required packages*, which are downloaded and installed if not present
* *Conditions*, such as tests on environment variables or the file system.
* Build *directory structure*
* *Error conditions*, such as a non-zero return or output in stderr.
Dovetail works with numerous other tools to automate build steps, and has built-in
integration with `Virtualenv <http://http://www.virtualenv.org/>`_. Any build can
be run in either the Python version on the path, or any nominated virtual environment.
Dovetail installs packages as required, even in the middle of a build. This means that
you run a simple task in a complex build without installing all the documentation
and test packages.
Example
-------
A trivial example of a Dovetail build script is given below. It uses
`Sphinx <http://sphinx.pocoo.org/>`_ to build the project documentation::
from dovetail import task, requires, check_result, call, mkdirs, do_if, IsDir
from os import path
from shutil import rmtree
DOCSOURCE = path.abspath(path.join(path.dirname(__file__), "source"))
BUILD = path.abspath(path.join(path.dirname(__file__), "..", "build"))
@task # Declares the function clean() is a build task
@do_if(IsDir(BUILD)) # Only run if the build directory exists
def clean():
"""Clean the project of all build artifacts"""
rmtree(BUILD)
@task # Declares the function clean() is a build task
@requires('sphinx') # Ensures the sphinx package is installed
@mkdirs(BUILD) # Make the build directory if it doesn't exist
@check_result # Fails the build if sphinx fails
def doc():
"""Builds the Sphinx User documentation"""
return call("sphinx-build {0} {1}".format(DOCSOURCE, BUILD).split(' '))
Builds are run simply from the OS command line::
$ dovetail clean doc