On Sep 27, 2007, at 10:51 AM, Phillip J. Eby wrote:
It won't work correctly. First, pkg_resources is part of setuptools, so if you install a new version of setuptools, you have to reload() it too.
Second, it's not *safe* to reload it, if it or setuptools were already imported at the time the function is called. That's because easy_install runs the setup.py of a package it's building from source. So if you use easy_install to install a package that needs a newer version, reloading pkg_resources or setuptools (and note that setuptools is a package with lots of submodules!) will break the host easy_install process.
Okay. Attached is a patch that doesn't import pkg_resources and doesn't reload anything. I haven't yet tested it -- I am submitting it only to inform the discussion and get any early feedback from you on whether the very idea is sound. Regards, Zooko diff -rN -u old-up/setuptools-0.6c7/ez_setup.py new-up/ setuptools-0.6c7/ez_setup.py --- old-up/setuptools-0.6c7/ez_setup.py 2007-09-28 16:41:24.000000000 -0600 +++ new-up/setuptools-0.6c7/ez_setup.py 2007-09-28 16:41:25.000000000 -0600 @@ -1,4 +1,4 @@ -#!python +#!/usr/bin/env python """Bootstrap setuptools installation If you want to use setuptools in your package's setup.py, just include this @@ -13,7 +13,7 @@ This file can also be run as a script to install or upgrade setuptools. """ -import sys +import os, re, subprocess, sys DEFAULT_VERSION = "0.6c7" DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] @@ -44,8 +44,6 @@ 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', } -import sys, os - def _validate_md5(egg_name, data): if egg_name in md5_data: from md5 import md5 @@ -58,6 +56,42 @@ sys.exit(2) return data +# The following code to parse versions is copied from pkg_resources.py so that +# we can parse versions without importing that module. +component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE) +replace = {'pre':'c', 'preview':'c','-':'final-','rc':'c','dev':'@'}.get + +def _parse_version_parts(s): + for part in component_re.split(s): + part = replace(part,part) + if not part or part=='.': + continue + if part[:1] in '0123456789': + yield part.zfill(8) # pad for numeric comparison + else: + yield '*'+part + + yield '*final' # ensure that alpha/beta/candidate are before final + +def parse_version(s): + parts = [] + for part in _parse_version_parts(s.lower()): + if part.startswith('*'): + if part<'*final': # remove '-' before a prerelease tag + while parts and parts[-1]=='*final-': parts.pop() + # remove trailing zeros from each series of numeric parts + while parts and parts[-1]=='00000000': + parts.pop() + parts.append(part) + return tuple(parts) + +def setuptools_is_new_enough(required_version): + """Return True if setuptools is already installed and has a version + number >= required_version.""" + sub = subprocess.Popen([sys.executable, "-c", "import setuptools;print setuptools.__version__"], stdout=subprocess.PIPE) + verstr = sub.stdout.read().strip() + ver = parse_version(verstr) + return ver and ver >= parse_version(required_version) def use_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, @@ -74,32 +108,11 @@ this routine will print a message to ``sys.stderr`` and raise SystemExit in an attempt to abort the calling script. """ - try: - import setuptools - if setuptools.__version__ == '0.0.1': - print >>sys.stderr, ( - "You have an obsolete version of setuptools installed. Please\n" - "remove it from your system entirely before rerunning this script." - ) - sys.exit(2) - except ImportError: + if not setuptools_is_new_enough(version): egg = download_setuptools(version, download_base, to_dir, download_delay) sys.path.insert(0, egg) import setuptools; setuptools.bootstrap_install_from = egg - import pkg_resources - try: - pkg_resources.require("setuptools>="+version) - - except pkg_resources.VersionConflict, e: - # XXX could we install in a subprocess here? - print >>sys.stderr, ( - "The required version of setuptools (>=%s) is not available, and\n" - "can't be installed while this script is running. Please install\n" - " a more recent version first.\n\n(Currently using %r)" - ) % (version, e.args[0]) - sys.exit(2) - def download_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay = 15 @@ -150,9 +163,14 @@ def main(argv, version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" - try: - import setuptools - except ImportError: + if setuptools_is_new_enough(version): + if argv: + from setuptools.command.easy_install import main + main(argv) + else: + print "Setuptools version",version,"or greater has been installed." + print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' + else: egg = None try: egg = download_setuptools(version, delay=0) @@ -162,31 +180,6 @@ finally: if egg and os.path.exists(egg): os.unlink(egg) - else: - if setuptools.__version__ == '0.0.1': - # tell the user to uninstall obsolete version - use_setuptools(version) - - req = "setuptools>="+version - import pkg_resources - try: - pkg_resources.require(req) - except pkg_resources.VersionConflict: - try: - from setuptools.command.easy_install import main - except ImportError: - from easy_install import main - main(list(argv)+[download_setuptools(delay=0)]) - sys.exit(0) # try to force an exit - else: - if argv: - from setuptools.command.easy_install import main - main(argv) - else: - print "Setuptools version",version,"or greater has been installed." - print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' - - def update_md5(filenames): """Update our built-in md5 registry"""