[Distutils] Re: Opening up backdoors to 'setup()'

Greg Ward gward@python.net
Mon, 28 Aug 2000 22:00:37 -0400

On 28 August 2000, Amos Latteier said:
> I've thought about this a little. It seems to me that the basic desire
> is to be able to run setup from python, not the command line. I think we
> could accommodate this with something like this
>   distutils.core.run_setup(file, commands)

Hmmm, that does look cleaner for your needs than setting a global mode
variable.  (Of course, it might end up setting a global mode variable
behind the scenes, but never mind that.  If I break 'setup()' up into
its constituent parts, or farm more of it off to the Distribution class,
that shouldn't be necessary.)

However, since I just went ahead and implemented 'script_name' and
'script_args' arguments to 'setup()', this is not needed for Paul's

There's still a fundamental problem: the interesting info is encoded
into keyword arguments, and the only (clean) way to get ahold of keyword 
arguments is to be the function that is called.  That means that
somewhere along the lines, the function 'distutils.core.setup()' is
called with the keyword args in the setup script.  To get our hands on
what's passed to 'setup()', we either have to:

  * write a custom 'setup()' and replace the "real"
    'distutils.core.setup()' with it (yuck)

  * modify the existing 'distutils.core.setup()' so that some external
    stimulus -- ie. something not passed in as keyword args from
    the setup script -- can influence its behaviour, eg. tell it to
    stop processing once it has "parsed" the keyword args and created
    a Distribution object.  When I see "external stimulus" I think
    either "global variable" or "instance attribute"; since 'setup()'
    is a function, it has to be the former.  Merde!

> This would return the results of running the named setup file with the
> given commands. I don't really care about how you spell the file and the
> commands. For example, perhaps file should be an open file object rather
> than a path, and maybe commands is a list of strings instead of a
> string. Whatever ;-)

What it returns depends on how far you want it to run.  For you, it's
enough to construct the Distribution object and return it (along with
its slave DistributionMetadata object, of course).  In some cases, you
might want to do everything up to running the commands; other times, you
actually want to run the commands.  Again, this can't be specified in
the keyword args in the setup script, which implies some external
stimulus is needed.

> The remaining problem is that most commands don't return anything, they
> just print information. This means that when you run a command on a
> setup file from python, it won't be easy to figure out whether it worked
> or not.

Well, the Distutils commands are all about side-effect; what could be
more side-effect-ful than dumping a bunch of stuff in /usr/lib?  ;-)

However, they're not completely unresponsive: several commands (notably
the build_* and install_* families) have 'get_inputs()' and/or
'get_outputs()' methods that return lists of filenames.  This is a very
informal, not-widely-implemented interface -- ie. it gets implemented
when it's needed.  They're essential to support the 'bdist_*' commands.
Generally, you need to actually run a command to ask it what its outputs
were, so these methods aren't quite as useful as they could be.

> In my case I'll probably write a new command called something like
> 'metadata' that doesn't do anything but return meta-data about the
> distribution.

We've been down this road before!  The outcome was the
DistributionMetadata class, defined in dist.py.  Once you have a
Distribution object, you can query its metadata using
'get_description()', 'get_fullname()', etc.

> This would allow me to do something like this:
>   meta_data=distutils.core.run_setup(open('foo-1.0.tgz'), 'metadata')
> which is what I want.

I think I would spell this:

   dist = distutils.core.run_setup('foo-1.0/setup.py', stop_after="init")

and then you can do this:

   print "%s (version %s), provided by %s" %
         (dist.get_name(), dist.get_version(), dist.get_contact())

Seem reasonable?

Greg Ward - just another Python hacker                  gward@python.net
Sure, I'm paranoid... but am I paranoid ENOUGH?