diff --git a/ChangeLog b/ChangeLog index 8786266..14fdfb1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,13 @@ Changelog ========= +0.1.6 (2015-03-26) +------------------ + +- Update setuptools bootstrap script. [Leo Singer] + 0.1.5 (2015-03-26) +------------------ - Add LIGO/Virgo notice types. [Leo Singer] diff --git a/ez_setup.py b/ez_setup.py index 23ea9a2..9dc2c87 100644 --- a/ez_setup.py +++ b/ez_setup.py @@ -1,14 +1,14 @@ -#!/usr/bin/env python +#!python """Bootstrap setuptools installation -To use setuptools in your package's setup.py, include this -file in the same directory and add this to the top of your setup.py:: +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: from ez_setup import use_setuptools use_setuptools() -To require a specific version of setuptools, set a download -mirror, or use an alternate download directory, simply supply +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. @@ -17,12 +17,10 @@ import shutil import sys import tempfile -import zipfile +import tarfile import optparse import subprocess import platform -import textwrap -import contextlib from distutils import log @@ -31,19 +29,38 @@ except ImportError: USER_SITE = None -DEFAULT_VERSION = "3.5.1" +DEFAULT_VERSION = "1.4.2" DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" def _python_cmd(*args): - """ - Return True if the command succeeded. - """ args = (sys.executable,) + args return subprocess.call(args) == 0 +def _check_call_py24(cmd, *args, **kwargs): + res = subprocess.call(cmd, *args, **kwargs) + class CalledProcessError(Exception): + pass + if not res == 0: + msg = "Command '%s' return non-zero exit status %d" % (cmd, res) + raise CalledProcessError(msg) +vars(subprocess).setdefault('check_call', _check_call_py24) + +def _install(tarball, install_args=()): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) -def _install(archive_filename, install_args=()): - with archive_context(archive_filename): # installing log.warn('Installing Setuptools') if not _python_cmd('setup.py', 'install', *install_args): @@ -51,61 +68,47 @@ def _install(archive_filename, install_args=()): log.warn('See the error message above.') # exitcode will be 2 return 2 + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) -def _build_egg(egg, archive_filename, to_dir): - with archive_context(archive_filename): - # building an egg - log.warn('Building a Setuptools egg in %s', to_dir) - _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) - # returning the result - log.warn(egg) - if not os.path.exists(egg): - raise IOError('Could not build the egg.') - - -def get_zip_class(): - """ - Supplement ZipFile class to support context manager for Python 2.6 - """ - class ContextualZipFile(zipfile.ZipFile): - def __enter__(self): - return self - def __exit__(self, type, value, traceback): - self.close - return zipfile.ZipFile if hasattr(zipfile.ZipFile, '__exit__') else \ - ContextualZipFile - - -@contextlib.contextmanager -def archive_context(filename): - # extracting the archive +def _build_egg(egg, tarball, to_dir): + # extracting the tarball tmpdir = tempfile.mkdtemp() log.warn('Extracting in %s', tmpdir) old_wd = os.getcwd() try: os.chdir(tmpdir) - with get_zip_class()(filename) as archive: - archive.extractall() + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() # going in the directory subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) os.chdir(subdir) log.warn('Now working in %s', subdir) - yield + + # building an egg + log.warn('Building a Setuptools egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) finally: os.chdir(old_wd) shutil.rmtree(tmpdir) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') def _do_download(version, download_base, to_dir, download_delay): egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' % (version, sys.version_info[0], sys.version_info[1])) if not os.path.exists(egg): - archive = download_setuptools(version, download_base, + tarball = download_setuptools(version, download_base, to_dir, download_delay) - _build_egg(egg, archive, to_dir) + _build_egg(egg, tarball, to_dir) sys.path.insert(0, egg) # Remove previously-imported pkg_resources if present (see @@ -118,10 +121,11 @@ def _do_download(version, download_base, to_dir, download_delay): def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, download_delay=15): + to_dir=os.curdir, download_delay=15): + # making sure we use the absolute path to_dir = os.path.abspath(to_dir) - rep_modules = 'pkg_resources', 'setuptools' - imported = set(sys.modules).intersection(rep_modules) + was_imported = 'pkg_resources' in sys.modules or \ + 'setuptools' in sys.modules try: import pkg_resources except ImportError: @@ -129,24 +133,23 @@ def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, try: pkg_resources.require("setuptools>=" + version) return - except pkg_resources.DistributionNotFound: - return _do_download(version, download_base, to_dir, download_delay) - except pkg_resources.VersionConflict as VC_err: - if imported: - msg = textwrap.dedent(""" - The required version of setuptools (>={version}) is not available, - and can't be installed while this script is running. Please - install a more recent version first, using - 'easy_install -U setuptools'. - - (Currently using {VC_err.args[0]!r}) - """).format(VC_err=VC_err, version=version) - sys.stderr.write(msg) + except pkg_resources.VersionConflict: + e = sys.exc_info()[1] + if was_imported: + sys.stderr.write( + "The required version of setuptools (>=%s) is not available,\n" + "and can't be installed while this script is running. Please\n" + "install a more recent version first, using\n" + "'easy_install -U setuptools'." + "\n\n(Currently using %r)\n" % (version, e.args[0])) sys.exit(2) - - # otherwise, reload ok - del pkg_resources, sys.modules['pkg_resources'] - return _do_download(version, download_base, to_dir, download_delay) + else: + del pkg_resources, sys.modules['pkg_resources'] # reload ok + return _do_download(version, download_base, to_dir, + download_delay) + except pkg_resources.DistributionNotFound: + return _do_download(version, download_base, to_dir, + download_delay) def _clean_check(cmd, target): """ @@ -181,7 +184,7 @@ def has_powershell(): try: try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) - except Exception: + except: return False finally: devnull.close() @@ -199,7 +202,7 @@ def has_curl(): try: try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) - except Exception: + except: return False finally: devnull.close() @@ -217,7 +220,7 @@ def has_wget(): try: try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) - except Exception: + except: return False finally: devnull.close() @@ -263,9 +266,9 @@ def get_best_downloader(): return dl def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, delay=15, downloader_factory=get_best_downloader): - """ - Download setuptools from a specified location and return its filename + to_dir=os.curdir, delay=15, + downloader_factory=get_best_downloader): + """Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end @@ -278,20 +281,74 @@ def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, """ # making sure we use the absolute path to_dir = os.path.abspath(to_dir) - zip_name = "setuptools-%s.zip" % version - url = download_base + zip_name - saveto = os.path.join(to_dir, zip_name) + tgz_name = "setuptools-%s.tar.gz" % version + url = download_base + tgz_name + saveto = os.path.join(to_dir, tgz_name) if not os.path.exists(saveto): # Avoid repeated downloads log.warn("Downloading %s", url) downloader = downloader_factory() downloader(url, saveto) return os.path.realpath(saveto) + +def _extractall(self, path=".", members=None): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). + """ + import copy + import operator + from tarfile import ExtractError + directories = [] + + if members is None: + members = self + + for tarinfo in members: + if tarinfo.isdir(): + # Extract directories with a safe mode. + directories.append(tarinfo) + tarinfo = copy.copy(tarinfo) + tarinfo.mode = 448 # decimal for oct 0700 + self.extract(tarinfo, path) + + # Reverse sort directories. + if sys.version_info < (2, 4): + def sorter(dir1, dir2): + return cmp(dir1.name, dir2.name) + directories.sort(sorter) + directories.reverse() + else: + directories.sort(key=operator.attrgetter('name'), reverse=True) + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) + try: + self.chown(tarinfo, dirpath) + self.utime(tarinfo, dirpath) + self.chmod(tarinfo, dirpath) + except ExtractError: + e = sys.exc_info()[1] + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + def _build_install_args(options): """ Build the arguments to 'python setup.py install' on the setuptools package """ - return ['--user'] if options.user_install else [] + install_args = [] + if options.user_install: + if sys.version_info < (2, 6): + log.warn("--user requires Python 2.6 or later") + raise SystemExit(1) + install_args.append('--user') + return install_args def _parse_args(): """ @@ -310,23 +367,16 @@ def _parse_args(): const=lambda: download_file_insecure, default=get_best_downloader, help='Use internal, non-validating downloader' ) - parser.add_option( - '--version', help="Specify which version to download", - default=DEFAULT_VERSION, - ) options, args = parser.parse_args() # positional arguments are ignored return options -def main(): +def main(version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" options = _parse_args() - archive = download_setuptools( - version=options.version, - download_base=options.download_base, - downloader_factory=options.downloader_factory, - ) - return _install(archive, _build_install_args(options)) + tarball = download_setuptools(download_base=options.download_base, + downloader_factory=options.downloader_factory) + return _install(tarball, _build_install_args(options)) if __name__ == '__main__': sys.exit(main()) diff --git a/setup.py b/setup.py index b6a3237..acb2dae 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ setup( name='pygcn', - version='0.1.5', + version='0.1.6', author='Leo Singer', author_email='leo.singer@ligo.org', description=gcn.__doc__.splitlines()[1],