[Distutils] How to customize egg_info behavior

P.J. Eby pje at telecommunity.com
Sun Aug 1 21:09:28 CEST 2010

At 10:36 PM 7/31/2010 -0700, Jason R. Coombs wrote:
>Thanks for the tip, pje. Your suggestion was very helpful. Perhaps I can ask
>a follow-up.
>In addition to calculating the tag at package-time (when setup.py is run for
>sdist/bdist*), I am calculating the version at package-time.
>The problem I have with this approach is with source distributions (sdists).
>Since the sdists get the same setup.py file, the calculations for version
>and tag are done both at packaging time and at install time. I would like to
>configure my setup.py such that when sdist is run, the version and tag are
>calculated and added to the egg_info, but when a consumer of that sdist goes
>to install it, the version and tag are not calculated and just consumed from
>the egg_info (similar to what happens when installing a bdist_egg).

When setuptools builds an sdist, it includes an updated setup.cfg 
containing all the original egg_info settings, so that part is done for you.

What this means is that you can programmatically tell if you're 
running from an sdist or a checkout by looking for the tag_build 
setting in setup.cfg.  That means that you could potentially just 
specify version='' in your setup(), and put the *entire* version 
number in the tag_build option to egg_info.

In order for that to work correctly, what you'd need to do is:

1. Try to read setup.cfg using ConfigParser, and read out the 
'tag_build' from the 'egg_info' section.

2. If it's found, set the version string to that, and force tag_build 
to an *empty string* in the options['egg_info'] passed to setup(), so 
that tag_build doesn't get stuck back in again (doubling-up the version).

3. If it's not found, do a straight-up calculation.

The main downside to this approach, however, is that you have to put 
all this code in setup.py, and you can't import it from anywhere, 
unless you bundle the code with every project you're using it on.

So here's what you do instead.  Implement this as a setup() argument, e.g.:

     def hg_version_calc(dist, attr, value):
         if value:
             from ConfigParser import ConfigParser
             parser = ConfigParser()
             if parser.has_section('egg_info') and 'tag_build' in 
                 version = parser.options('egg_info')['tag_build']
                 version = calculate_version()
             dist.metadata.version = version

And register this as a 'distutils.setup_keywords' entry point in your 
plugin project.  You then need only include a 'use_hg_version = True' 
in your setup() call, along with a setup_requires to ensure the 
plugin is available.

Now, there is one minor hitch with this; as shown above, this code 
will double-up version strings in certain circumstances; 
specifically, if an 'egg_info' is done from an sdist -- which is 
going to happen if you build pretty much anything from an sdist, be 
it an sdist, egg, RPM, or anything else.

There isn't any easy way to work around this at the moment (although 
I will implement a formal fix  in later releases of setuptools).  In 
the meantime, you can monkeypatch thus, in the same plugin where you 
implement the above function:

     from setuptools.command.egg_info import egg_info
     old_tagged_version = egg_info.tagged_version

     def tagged_version(self):
         if self.distribution.use_hg_version:
             return safe_version(self.distribution.get_version())
             return old_tagged_version(self)

     egg_info.tagged_version = tagged_version

Whew.  This was a tough one to figure out, and it's a little 
hacky.  Probably in 0.7 I'll just factor this into a separate entry 
point group for version calculations, so that it's not as much 
trouble to do something like this.

More information about the Distutils-SIG mailing list