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).