diff options
Diffstat (limited to 'distutils2/util.py')
| -rw-r--r-- | distutils2/util.py | 403 |
1 files changed, 193 insertions, 210 deletions
diff --git a/distutils2/util.py b/distutils2/util.py index 473d6b6..fbc8b45 100644 --- a/distutils2/util.py +++ b/distutils2/util.py @@ -9,12 +9,15 @@ import posixpath import re import string import sys -import shutil -import tarfile -import zipfile +from subprocess import call as sub_call from copy import copy from fnmatch import fnmatchcase +try: + from glob import iglob as std_iglob +except ImportError: + from glob import glob as std_iglob # for python < 2.5 from ConfigParser import RawConfigParser +from inspect import getsource from distutils2.errors import (DistutilsPlatformError, DistutilsFileError, DistutilsByteCompileError, DistutilsExecError) @@ -175,29 +178,6 @@ def subst_vars(s, local_vars): raise ValueError("invalid variable '$%s'" % var) -def grok_environment_error(exc, prefix="error: "): - """Generate a useful error message from an EnvironmentError. - - This will generate an IOError or an OSError exception object. - Handles Python 1.5.1 and 1.5.2 styles, and - does what it can to deal with exception objects that don't have a - filename (which happens when the error is due to a two-file operation, - such as 'rename()' or 'link()'. Returns the error message as a string - prefixed with 'prefix'. - """ - # check for Python 1.5.2-style {IO,OS}Error exception objects - if hasattr(exc, 'filename') and hasattr(exc, 'strerror'): - if exc.filename: - error = prefix + "%s: %s" % (exc.filename, exc.strerror) - else: - # two-argument functions in posix module don't - # include the filename in the exception object! - error = prefix + "%s" % exc.strerror - else: - error = prefix + str(exc[-1]) - - return error - # Needed by 'split_quoted()' _wordchars_re = _squote_re = _dquote_re = None @@ -238,20 +218,20 @@ def split_quoted(s): words.append(s[:end]) break - if s[end] in string.whitespace: # unescaped, unquoted whitespace: now - words.append(s[:end]) # we definitely have a word delimiter + if s[end] in string.whitespace: # unescaped, unquoted whitespace: now + words.append(s[:end]) # we definitely have a word delimiter s = s[end:].lstrip() pos = 0 - elif s[end] == '\\': # preserve whatever is being escaped; - # will become part of the current word + elif s[end] == '\\': # preserve whatever is being escaped; + # will become part of the current word s = s[:end] + s[end + 1:] pos = end + 1 else: - if s[end] == "'": # slurp singly-quoted string + if s[end] == "'": # slurp singly-quoted string m = _squote_re.match(s, end) - elif s[end] == '"': # slurp doubly-quoted string + elif s[end] == '"': # slurp doubly-quoted string m = _dquote_re.match(s, end) else: raise RuntimeError("this can't happen " @@ -542,9 +522,9 @@ def newer_group(sources, target, missing='error'): if missing == 'error': # blow up when we stat() the file pass elif missing == 'ignore': # missing source dropped from - continue # target's dependency list + continue # target's dependency list elif missing == 'newer': # missing source means target is - return True # out-of-date + return True # out-of-date if os.stat(source).st_mtime > target_mtime: return True @@ -664,6 +644,7 @@ def resolve_name(name): return ret + def splitext(path): """Like os.path.splitext, but take off .tar too""" base, ext = posixpath.splitext(path) @@ -673,83 +654,6 @@ def splitext(path): return base, ext -def unzip_file(filename, location, flatten=True): - """Unzip the file (zip file located at filename) to the destination - location""" - if not os.path.exists(location): - os.makedirs(location) - zipfp = open(filename, 'rb') - try: - zip = zipfile.ZipFile(zipfp) - leading = has_leading_dir(zip.namelist()) and flatten - for name in zip.namelist(): - data = zip.read(name) - fn = name - if leading: - fn = split_leading_dir(name)[1] - fn = os.path.join(location, fn) - dir = os.path.dirname(fn) - if not os.path.exists(dir): - os.makedirs(dir) - if fn.endswith('/') or fn.endswith('\\'): - # A directory - if not os.path.exists(fn): - os.makedirs(fn) - else: - fp = open(fn, 'wb') - try: - fp.write(data) - finally: - fp.close() - finally: - zipfp.close() - - -def untar_file(filename, location): - """Untar the file (tar file located at filename) to the destination - location - """ - if not os.path.exists(location): - os.makedirs(location) - if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'): - mode = 'r:gz' - elif (filename.lower().endswith('.bz2') - or filename.lower().endswith('.tbz')): - mode = 'r:bz2' - elif filename.lower().endswith('.tar'): - mode = 'r' - else: - mode = 'r:*' - tar = tarfile.open(filename, mode) - try: - leading = has_leading_dir([member.name for member in tar.getmembers()]) - for member in tar.getmembers(): - fn = member.name - if leading: - fn = split_leading_dir(fn)[1] - path = os.path.join(location, fn) - if member.isdir(): - if not os.path.exists(path): - os.makedirs(path) - else: - try: - fp = tar.extractfile(member) - except (KeyError, AttributeError): - # Some corrupt tar files seem to produce this - # (specifically bad symlinks) - continue - if not os.path.exists(os.path.dirname(path)): - os.makedirs(os.path.dirname(path)) - destfp = open(path, 'wb') - try: - shutil.copyfileobj(fp, destfp) - finally: - destfp.close() - fp.close() - finally: - tar.close() - - def has_leading_dir(paths): """Returns true if all the paths have the same leading path name (i.e., everything is in one subdirectory in an archive)""" @@ -869,69 +773,22 @@ def _spawn_os2(cmd, search_path=1, verbose=0, dry_run=0, env=None): "command '%s' failed: %s" % (cmd[0], exc[-1])) if rc != 0: # and this reflects the command running but failing - logger.debug("command '%s' failed with exit status %d" % (cmd[0], rc)) + logger.debug("command '%s' failed with exit status %d", + (cmd[0], rc)) raise DistutilsExecError( "command '%s' failed with exit status %d" % (cmd[0], rc)) -def _spawn_posix(cmd, search_path=1, verbose=0, dry_run=0, env=None): - logger.info(' '.join(cmd)) +def _spawn_posix(cmd, search_path=1, verbose=1, dry_run=0, env=None): + cmd = ' '.join(cmd) + if verbose: + logger.info(cmd) if dry_run: return - - if env is None: - exec_fn = search_path and os.execvp or os.execv - else: - exec_fn = search_path and os.execvpe or os.execve - - pid = os.fork() - - if pid == 0: # in the child - try: - if env is None: - exec_fn(cmd[0], cmd) - else: - exec_fn(cmd[0], cmd, env) - except OSError, e: - sys.stderr.write("unable to execute %s: %s\n" % - (cmd[0], e.strerror)) - os._exit(1) - - sys.stderr.write("unable to execute %s for unknown reasons" % cmd[0]) - os._exit(1) - else: # in the parent - # Loop until the child either exits or is terminated by a signal - # (ie. keep waiting if it's merely stopped) - while 1: - try: - pid, status = os.waitpid(pid, 0) - except OSError, exc: - import errno - if exc.errno == errno.EINTR: - continue - raise DistutilsExecError( - "command '%s' failed: %s" % (cmd[0], exc[-1])) - if os.WIFSIGNALED(status): - raise DistutilsExecError( - "command '%s' terminated by signal %d" % \ - (cmd[0], os.WTERMSIG(status))) - - elif os.WIFEXITED(status): - exit_status = os.WEXITSTATUS(status) - if exit_status == 0: - return # hey, it succeeded! - else: - raise DistutilsExecError( - "command '%s' failed with exit status %d" % \ - (cmd[0], exit_status)) - - elif os.WIFSTOPPED(status): - continue - - else: - raise DistutilsExecError( - "unknown error executing '%s': termination status %d" % \ - (cmd[0], status)) + exit_status = sub_call(cmd, shell=True, env=env) + if exit_status != 0: + msg = "command '%s' failed with exit status %d" + raise DistutilsExecError(msg % (cmd, exit_status)) def find_executable(executable, path=None): @@ -971,6 +828,7 @@ username:%s password:%s """ + def get_pypirc_path(): """Returns rc file path.""" return os.path.join(os.path.expanduser('~'), '.pypirc') @@ -1045,44 +903,10 @@ def read_pypirc(repository=DEFAULT_REPOSITORY, realm=DEFAULT_REALM): return {} -def metadata_to_dict(meta): - """XXX might want to move it to the Metadata class.""" - data = { - 'metadata_version' : meta.version, - 'name': meta['Name'], - 'version': meta['Version'], - 'summary': meta['Summary'], - 'home_page': meta['Home-page'], - 'author': meta['Author'], - 'author_email': meta['Author-email'], - 'license': meta['License'], - 'description': meta['Description'], - 'keywords': meta['Keywords'], - 'platform': meta['Platform'], - 'classifier': meta['Classifier'], - 'download_url': meta['Download-URL'], - } - - if meta.version == '1.2': - data['requires_dist'] = meta['Requires-Dist'] - data['requires_python'] = meta['Requires-Python'] - data['requires_external'] = meta['Requires-External'] - data['provides_dist'] = meta['Provides-Dist'] - data['obsoletes_dist'] = meta['Obsoletes-Dist'] - data['project_url'] = [','.join(url) for url in - meta['Project-URL']] - - elif meta.version == '1.1': - data['provides'] = meta['Provides'] - data['requires'] = meta['Requires'] - data['obsoletes'] = meta['Obsoletes'] - - return data - # utility functions for 2to3 support -def run_2to3(files, doctests_only=False, fixer_names=None, options=None, - explicit=None): +def run_2to3(files, doctests_only=False, fixer_names=None, + options=None, explicit=None): """ Wrapper function around the refactor() class which performs the conversions on a list of python files. Invoke 2to3 on a list of Python files. The files should all come @@ -1096,15 +920,13 @@ def run_2to3(files, doctests_only=False, fixer_names=None, options=None, fixers = [] fixers = get_fixers_from_package('lib2to3.fixes') - if fixer_names: for fixername in fixer_names: - fixers.extend([fixer for fixer in get_fixers_from_package(fixername)]) + fixers.extend([fixer for fixer in + get_fixers_from_package(fixername)]) r = RefactoringTool(fixers, options=options) - if doctests_only: - r.refactor(files, doctests_only=True, write=True) - else: - r.refactor(files, write=True) + r.refactor(files, write=True, doctests_only=doctests_only) + class Mixin2to3: """ Wrapper class for commands that run 2to3. @@ -1126,3 +948,164 @@ class Mixin2to3: """ Issues a call to util.run_2to3. """ return run_2to3(files, doctests_only, self.fixer_names, self.options, self.explicit) + +RICH_GLOB = re.compile(r'\{([^}]*)\}') +_CHECK_RECURSIVE_GLOB = re.compile(r'[^/,{]\*\*|\*\*[^/,}]') +_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$') + + +def iglob(path_glob): + """Richer glob than the std glob module support ** and {opt1,opt2,opt3}""" + if _CHECK_RECURSIVE_GLOB.search(path_glob): + msg = """Invalid glob %r: Recursive glob "**" must be used alone""" + raise ValueError(msg % path_glob) + if _CHECK_MISMATCH_SET.search(path_glob): + msg = """Invalid glob %r: Mismatching set marker '{' or '}'""" + raise ValueError(msg % path_glob) + return _iglob(path_glob) + + +def _iglob(path_glob): + """Actual logic of the iglob function""" + rich_path_glob = RICH_GLOB.split(path_glob, 1) + if len(rich_path_glob) > 1: + assert len(rich_path_glob) == 3, rich_path_glob + prefix, set, suffix = rich_path_glob + for item in set.split(','): + for path in _iglob(''.join((prefix, item, suffix))): + yield path + else: + if '**' not in path_glob: + for item in std_iglob(path_glob): + yield item + else: + prefix, radical = path_glob.split('**', 1) + if prefix == '': + prefix = '.' + if radical == '': + radical = '*' + else: + radical = radical.lstrip('/') + for (path, dir, files) in os.walk(prefix): + path = os.path.normpath(path) + for file in _iglob(os.path.join(path, radical)): + yield file + + +def cfg_to_args(path='setup.cfg'): + """ Distutils2 to distutils1 compatibility util. + + This method uses an existing setup.cfg to generate a dictionnary of + keywords that can be used by distutils.core.setup(kwargs**). + + :param file: + The setup.cfg path. + :raises DistutilsFileError: + When the setup.cfg file is not found. + + """ + # We need to declare the following constants here so that it's easier to + # generate the setup.py afterwards, using inspect.getsource. + + # XXX ** == needs testing + D1_D2_SETUP_ARGS = {"name": ("metadata",), + "version": ("metadata",), + "author": ("metadata",), + "author_email": ("metadata",), + "maintainer": ("metadata",), + "maintainer_email": ("metadata",), + "url": ("metadata", "home_page"), + "description": ("metadata", "summary"), + "long_description": ("metadata", "description"), + "download-url": ("metadata",), + "classifiers": ("metadata", "classifier"), + "platforms": ("metadata", "platform"), # ** + "license": ("metadata",), + "requires": ("metadata", "requires_dist"), + "provides": ("metadata", "provides_dist"), # ** + "obsoletes": ("metadata", "obsoletes_dist"), # ** + "packages": ("files",), + "scripts": ("files",), + "py_modules": ("files", "modules"), # ** + } + + MULTI_FIELDS = ("classifiers", + "requires", + "platforms", + "packages", + "scripts") + + def has_get_option(config, section, option): + if config.has_option(section, option): + return config.get(section, option) + elif config.has_option(section, option.replace('_', '-')): + return config.get(section, option.replace('_', '-')) + else: + return False + + # The method source code really starts here. + config = RawConfigParser() + if not os.path.exists(file): + raise DistutilsFileError("file '%s' does not exist" % + os.path.abspath(file)) + config.read(path) + + kwargs = {} + for arg in D1_D2_SETUP_ARGS: + if len(D1_D2_SETUP_ARGS[arg]) == 2: + # The distutils field name is different than distutils2's. + section, option = D1_D2_SETUP_ARGS[arg] + + elif len(D1_D2_SETUP_ARGS[arg]) == 1: + # The distutils field name is the same thant distutils2's. + section = D1_D2_SETUP_ARGS[arg][0] + option = arg + + in_cfg_value = has_get_option(config, section, option) + if not in_cfg_value: + # There is no such option in the setup.cfg + if arg == "long_description": + filename = has_get_option(config, section, "description_file") + if filename: + in_cfg_value = open(filename).read() + else: + continue + + if arg in MULTI_FIELDS: + # Special behaviour when we have a multi line option + if "\n" in in_cfg_value: + in_cfg_value = in_cfg_value.strip().split('\n') + else: + in_cfg_value = list((in_cfg_value,)) + + kwargs[arg] = in_cfg_value + + return kwargs + + +_SETUP_TMPL = """\ +# This script was automatically generated by Distutils2 +import os +from distutils.core import setup +from ConfigParser import RawConfigParser + +%(func)s + +setup(**cfg_to_args()) +""" + + +def generate_setup_py(): + """Generates a distutils compatible setup.py using an existing setup.cfg. + + :raises DistutilsFileError: + When a setup.py already exists. + """ + if os.path.exists("setup.py"): + raise DistutilsFileError("A pre existing setup.py file exists") + + handle = open("setup.py", "w") + try: + handle.write(_SETUP_TMPL % {'func': getsource(cfg_to_args)}) + finally: + handle.close() |
