[Distutils] A plan for scripts (in EasyInstall)
Phillip J. Eby
pje at telecommunity.com
Mon Jun 6 16:44:19 CEST 2005
Here's my semi-final plan for doing scripts with EasyInstall; please let me
know if you see any issues with it:
* Make bdist_egg stick the scripts under an EGG-INFO/scripts/ subdirectory
of the egg, by abusing the install_scripts command. (Note: this does *not*
mean they will be copied to 'PackageName.egg-info', only that they will be
included in the built egg file. Currently, subdirectories of .egg-info are
not included in the built egg, so this is completely separate.)
* Add 'metadata_isdir' and 'metadata_listdir' APIs to pkg_resources, to
allow inspecting the contents of EGG-INFO/scripts
* Add these options to the easy_install command:
--exclude-scripts, -x Don't install scripts
--scripts-to=DIR, -t DIR Install scripts to DIR
The --scripts-to option would default to being the *same as the
installation directory*, unless you're installing to site-packages, in
which case they would go to the Python default location for installing scripts.
Why the installation directory? Because if you run the scripts from there,
the egg(s) will be in a directory on sys.path, meaning that 'require()'
will then work. In essence, I'm assuming that the normal use case for
specifying an install-dir is to create an "application directory" filled
with the eggs needed to run a specific application. For example, on a
Unix-like system you might be installing to your ~/bin.
The downside to this assumption is that since the scripts are in the same
place, they might become importable when that's not intended. So if you're
installing to a personal ~/lib/python, you will probably want to use -x or
-t to override.
Anyway, if you are installing scripts, easy_install will just do what the
distutils does now to install them in their specified locations. This
basically means giving them executable permissions and munging the #! line
if applicable. On a multi-version install (-m or -i), it seems like we
should also add a line like:
from pkg_resources import require; require("thispackage==version"); del
require
But, as I've pointed out before, it's a tricky modification, as it would
need to be inserted just before the first *executable* line of code in the
script, which is often not the first line. Docstrings and __future__
statements both have to be skipped, or else the script could be broken by
the modification. Further, even a successful modification is going to
change the script's line numbering, which could have tech support
implications. So, I'm somewhat reluctant to do this without a way to turn
it off (other than by skipping scripts). There also needs to be a way to
verify that the script is in fact a Python script! (Presumably by checking
for a .py/.pyw extension or a #! line containing "python".)
Also, adding such a 'require()' line might be more restrictive than
necessary; the script might include its own require() already!
So, here's an alternative possibility. Let's suppose that the script *is*
a Python script. What if we ran it from inside the egg? We could write
out the script as a stub loader, looking something like this:
#!python <-- copied from original script, if present
import pkg_resources
pkg_resources.run_main("scriptname", "EggName")
The 'run_main' function would do several things:
* require() the appropriate package(s)
* Clear everything but __name__ from the __main__ namespace
* Load the script file into memory, and "poke" it into linecache.cache,
so that tracebacks from the script execution will still show its source
code, with correct line numbers
* exec the script in __main__, using something like:
maindict['__file__'] = pseudo_filename
code = compile(script_source, pseudo_filename, "exec")
exec code in maindict, maindict
Hm. execfile() could also be used if the script file actually exists, in
which case we could also skip the seeding of linecache. Probably we can
add a run_script() method to the IMetadataProvider interface, so that
different egg formats can handle this appropriately.
Now that we've come this far, it becomes clear that these "scripts" are
nothing more than bootstraps -- which means that in a future version I can
imagine allowing more user-friendly installation options, like .exe files
on Windows, "applications" on the Mac, and extension-stripping everywhere
else. However, it may be that the choice of script installation policy is
largely a matter of vehement personal preference, so there should probably
be a way to configure that. It could also include a way to define custom
installation policies in Python modules, and a way to select a particular
policy at runtime, e.g.:
easy_install --script-policy=mymodule.foo_policy ...
At this point, however, easy_install options will have gotten complex
enough to warrant configuration files for standard settings. We could
probably hijack the existing distutils configuration scheme for that,
though, treating 'easy_install' as if it were a distutils command.
Whew. I think that about covers it. Thoughts, anyone?
More information about the Distutils-SIG
mailing list