Ian Bicking: the old part of his blog

Python's Makefile

Sometimes people decry the lack of a good make tool in Python. There are some make tools (e.g., SCons or pymake), but that's not actually what people are usually thinking about. It's not about building code based on a system of dependencies. We do pretty well without that anyway.

Because really people are talking about something more like rake -- something where you can put together a bunch of code management tools. These aren't commands provided by the code, these are commands used on the code. You can do this in a Makefile too, but none of it really has any relation to what make does -- it's just a convention for where to put tools.

We do have the infrastructure for this in Python, but no one is really using it. So I'm writing this to suggest people use it more: the setup.py file. So where in another environment someone does rake COMMAND, we can do python setup.py COMMAND.

Setuptools in particular provides an extension mechanism for this. With it you can add new globally-available commands. An example of this is buildutils. This package is, sadly, unmaintained; in part I'm writing this blog post to encourage people to steal/remake/repair the kind of functionality buildutils provides, and provide entirely new functionality.

The basic pattern is that you create a distribution that provides the [distutils.commands] entry point. This points to a subclass of distutils.cmd.Command; the class is a little arcane, but not really too hard to handle. It looks like this:

from distutils.cmd import Command
from distutils.errors import *
class my_command(Command):
    description = "<help description>"
    user_options = [('long-option=', 'l', '<help>'),
                    ('other-long-option', 'o', '<help>')]
    boolean_options = 'other-long-option'
    def initialize_options(self):
        self.long_option = <default>
        self.other_long_option = False
    def finalize_options(self):
        if self.long_option is not valid:
            raise DistutilsOptionError(...)
    def run(self):
        do stuff...

There's other details as well. It's not a great framework, but it's a passable one.

All the options you provide can be set in setup.cfg (in this case under [my_command]). This lets the package configure itself for the command. You can also add new keyword arguments to setup(), but I'd avoid that technique (distutils/setuptools will whine but not fail if it gets unexpected keyword arguments -- and they'll only be "expected" if you have all the right packages installed).

When you add a command this way it becomes globally available. Sometimes you don't want that -- a command might be applicable only to code using a particular framework, for instance -- so there is another option that comes from distutils (and is inherited by setuptools): --command-packages. This points to a package where each module is a separate command. As a result is good practice to put all your commands in a subpackage dedicated to distutils commands. In a setup.cfg file this then looks like:

[global]
command_packages = buildutils.command, other.module, ...

The commands provided don't show up in --help-commands (unfortunately), but they do work.

Ideally I'd like to see all the commands people write around maintenance tasks use this convention -- document extraction tools, file publishing tools, tools to create boilerplate source files and templates, tools to test and analyze code, etc. It will make distributing and using these tools easy, and hopefully get everyone on the Good Packaging Bandwagon.

Update: Trying to put my money where my mouth is, I wrote up a little recipe for combining CSS and Javascript files in a project (with accompanying setup.cfg).

Created 15 Apr
Modified 25 Apr

Comments:

FTR, pymake is dead, and has been replaced functionally by buildit (http://www.agendaless.com/Members/chrism/software/buildit/).

# Chris McDonough

Oh. And Ian is right inasmuch buildit (and to some degree ZC's buildout framework) are not really tools for packaging and deploying Python modules and packages (although they can be used for this). Instead, they fall into the same space that make does as a general purpose "put files on the filesystem using these shell commands" tools, and they happen to be written in Python and exensible in Python.

# Chris McDonough

I'm not sure I understand how zc.buildout is not really a tool for packaging and deploying Python modules and packages. I thought it was. That's not to flame, I just have difficulty seeing the difference.

# Jan-Wijbrand Kolman

They are tools for working with the deployed modules. So for instance, when I have a library and I want to release it, I have to create a tag in the version control. I have to update the news file, run some tests, change some metadata in setup.py, etc. Maybe I have to update some gettext catalogs, etc. These are the kinds of tools I am talking about.

What zc.buildout and buildit do comes after that. After you have does all these code maintenance tasks, they help you use the code in a real deployment.

# Ian Bicking

Hrm, I think around is more how zc.buildout fits in the picture. In combination with find-links it can be a great way to maintain a consistent set of the kinds of tools you could run from make; but all packaged up for python. I think this use overlaps with workingenv. One of my pet gripes with setup.py has nothing to do with its utility. I like my build configuration to be declaritive. placing entry_points, version, and all sorts of other build facts in setup.py makes it a pain to consume that data else where. .egg-info is only available after the build has run. There does not seem to be a 'best practice' for sourcing entry-points etc from a seperate, declaritive, file. setup.cfg maybe but its not what I use.

A couple of buildout recipes I've been fooling around with: http://svn.wiretooth.com/svn/open/rsb_sourcesvn/trunk/ checkout sources from svn into a build out http://svn.wiretooth.com/svn/open/rsb_setupdevelop/trunk/ wraps zc.recipe.egg:scripts to do the equivelent of 'python setup.py develop' Illustrate the way my setup.py usage is going these days.

And then I tend to have a 'toolbox' buildout that pulls in this config http://svn.wiretooth.com/svn/open/share/zc.buildout.cfgs/python-development-tools.cfg

Using buildouts 'config from url' trick.

All that said, your OP is spot on, its not make we are all missing.

# robinbryce

By the way, setuptools also lets you define additional setup() arguments (that can then be used by your add-on commands), and you can use the setup_requires argument to make your setup script depend on eggs containing the commands and/or arguments your setup script may need.

# Phillip J. Eby

Doesn't this conflicts with eggs? When you have only an egg and easy_install, you'll not be able to do this...

# Lior Gradstein

If you just have an egg, you aren't developing the package, so you don't need access to the development tools.

# Ian Bicking

I love you 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 博客群发软件 博客群发软件 博客群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件 群发软件
# 群发软件

The thing I love most about Python is that many of its APIs are intuitive and obvious. Compare, e.g. reading a line from a text file in Python and in Java. Sometimes when I use a new standard library module I don't even have to go read its documentation: a brief glance with pydoc suffices to show the method or class names, and the rest is obvious.

Distutils, sadly, is an exception. Despite reading the documentation and creating three setup.py files, I do not feel comfortable with it. If I were stranded on a desert island with no Internet access and no documentation, I could not write a setup.py file.

# Marius Gedminas

I started going in this direction a long time ago, with my Using distutils post. There is a section entitled "Adding a new command: automated tests", wherein I showed how to create test and clean targets (or whatever distutils calls them, I forget now).

# Darren Chamberlain

How come no one talks about aap http://www.a-a-p.org/ ?

# Kent