diff options
| author | Arc Riley <arcriley@gmail.com> | 2011-03-13 00:12:23 -0500 |
|---|---|---|
| committer | Arc Riley <arcriley@gmail.com> | 2011-03-13 00:12:23 -0500 |
| commit | efa667603bc41168dc547563fffef464b728adfa (patch) | |
| tree | 6b3f76ba3d107c984335253b3d42d9ab65cb7748 /distutils2/_backport | |
| parent | f3f78c2aca495aed87bc416e5f20134b9d0fc351 (diff) | |
| parent | e2d2d3d18380338920819d2bd0524f6629b1874b (diff) | |
| download | disutils2-efa667603bc41168dc547563fffef464b728adfa.tar.gz | |
Branch merge to trunk
Diffstat (limited to 'distutils2/_backport')
15 files changed, 1503 insertions, 267 deletions
diff --git a/distutils2/_backport/__init__.py b/distutils2/_backport/__init__.py index bfbf6ec..834bfa9 100644 --- a/distutils2/_backport/__init__.py +++ b/distutils2/_backport/__init__.py @@ -1,5 +1,5 @@ """Things that will land in the Python 3.3 std lib but which we must drag along -with us for now to support 2.x.""" + us for now to support 2.x.""" def any(seq): for elem in seq: diff --git a/distutils2/_backport/pkgutil.py b/distutils2/_backport/pkgutil.py index c53bd22..441ad6f 100644 --- a/distutils2/_backport/pkgutil.py +++ b/distutils2/_backport/pkgutil.py @@ -1,24 +1,27 @@ """Utilities to support packages.""" -# NOTE: This module must remain compatible with Python 2.3, as it is shared -# by setuptools for distribution with Python 2.3 and up. - -import os -import sys import imp -import os.path +import sys + from csv import reader as csv_reader +import os +import re +from stat import ST_SIZE from types import ModuleType +import warnings + +try: + from hashlib import md5 +except ImportError: + from md5 import md5 + from distutils2.errors import DistutilsError -from distutils2.metadata import DistributionMetadata +from distutils2.metadata import Metadata from distutils2.version import suggest_normalized_version, VersionPredicate -import zipimport try: import cStringIO as StringIO except ImportError: import StringIO -import re -import warnings __all__ = [ @@ -28,10 +31,14 @@ __all__ = [ 'Distribution', 'EggInfoDistribution', 'distinfo_dirname', 'get_distributions', 'get_distribution', 'get_file_users', 'provides_distribution', 'obsoletes_distribution', - 'enable_cache', 'disable_cache', 'clear_cache' + 'enable_cache', 'disable_cache', 'clear_cache', ] +########################## +# PEP 302 Implementation # +########################## + def read_code(stream): # This helper is needed in order for the :pep:`302` emulation to # correctly handle compiled files @@ -41,7 +48,7 @@ def read_code(stream): if magic != imp.get_magic(): return None - stream.read(4) # Skip timestamp + stream.read(4) # Skip timestamp return marshal.load(stream) @@ -49,7 +56,7 @@ def simplegeneric(func): """Make a trivial single-dispatch generic function""" registry = {} - def wrapper(*args, **kw): + def wrapper(*args, ** kw): ob = args[0] try: cls = ob.__class__ @@ -64,12 +71,12 @@ def simplegeneric(func): pass mro = cls.__mro__[1:] except TypeError: - mro = object, # must be an ExtensionClass or some such :( + mro = object, # must be an ExtensionClass or some such :( for t in mro: if t in registry: - return registry[t](*args, **kw) + return registry[t](*args, ** kw) else: - return func(*args, **kw) + return func(*args, ** kw) try: wrapper.__name__ = func.__name__ except (TypeError, AttributeError): @@ -173,7 +180,6 @@ def iter_modules(path=None, prefix=''): #@simplegeneric def iter_importer_modules(importer, prefix=''): - "" if not hasattr(importer, 'iter_modules'): return [] return importer.iter_modules(prefix) @@ -331,9 +337,9 @@ class ImpLoader(object): def get_filename(self, fullname=None): fullname = self._fix_name(fullname) mod_type = self.etc[2] - if self.etc[2] == imp.PKG_DIRECTORY: + if mod_type == imp.PKG_DIRECTORY: return self._get_delegate().get_filename() - elif self.etc[2] in (imp.PY_SOURCE, imp.PY_COMPILED, imp.C_EXTENSION): + elif mod_type in (imp.PY_SOURCE, imp.PY_COMPILED, imp.C_EXTENSION): return self.filename return None @@ -343,8 +349,7 @@ try: from zipimport import zipimporter def iter_zipimport_modules(importer, prefix=''): - dirlist = zipimport._zip_directory_cache[importer.archive].keys() - dirlist.sort() + dirlist = sorted(zipimport._zip_directory_cache[importer.archive]) _prefix = importer.prefix plen = len(_prefix) yielded = {} @@ -433,7 +438,8 @@ def iter_importers(fullname=""): import mechanism will find the latter. Items of the following types can be affected by this discrepancy: - ``imp.C_EXTENSION, imp.PY_SOURCE, imp.PY_COMPILED, imp.PKG_DIRECTORY`` + :data:`imp.C_EXTENSION`, :data:`imp.PY_SOURCE`, :data:`imp.PY_COMPILED`, + :data:`imp.PKG_DIRECTORY` """ if fullname.startswith('.'): raise ImportError("Relative module names not supported") @@ -535,13 +541,13 @@ def extend_path(path, name): # frozen package. Return the path unchanged in that case. return path - pname = os.path.join(*name.split('.')) # Reconstitute as relative path + pname = os.path.join(*name.split('.')) # Reconstitute as relative path # Just in case os.extsep != '.' sname = os.extsep.join(name.split('.')) sname_pkg = sname + os.extsep + "pkg" init_py = "__init__" + os.extsep + "py" - path = path[:] # Start with a copy of the existing path + path = path[:] # Start with a copy of the existing path for dir in sys.path: if not isinstance(dir, basestring) or not os.path.isdir(dir): @@ -566,7 +572,7 @@ def extend_path(path, name): line = line.rstrip('\n') if not line or line.startswith('#'): continue - path.append(line) # Don't check for existence! + path.append(line) # Don't check for existence! f.close() return path @@ -610,19 +616,20 @@ def get_data(package, resource): resource_name = os.path.join(*parts) return loader.get_data(resource_name) + ########################## # PEP 376 Implementation # ########################## -DIST_FILES = ('INSTALLER', 'METADATA', 'RECORD', 'REQUESTED',) +DIST_FILES = ('INSTALLER', 'METADATA', 'RECORD', 'REQUESTED', 'RESOURCES') # Cache -_cache_name = {} # maps names to Distribution instances -_cache_name_egg = {} # maps names to EggInfoDistribution instances -_cache_path = {} # maps paths to Distribution instances -_cache_path_egg = {} # maps paths to EggInfoDistribution instances -_cache_generated = False # indicates if .dist-info distributions are cached -_cache_generated_egg = False # indicates if .dist-info and .egg are cached +_cache_name = {} # maps names to Distribution instances +_cache_name_egg = {} # maps names to EggInfoDistribution instances +_cache_path = {} # maps paths to Distribution instances +_cache_path_egg = {} # maps paths to EggInfoDistribution instances +_cache_generated = False # indicates if .dist-info distributions are cached +_cache_generated_egg = False # indicates if .dist-info and .egg are cached _cache_enabled = True @@ -637,6 +644,7 @@ def enable_cache(): _cache_enabled = True + def disable_cache(): """ Disables the internal cache. @@ -648,10 +656,11 @@ def disable_cache(): _cache_enabled = False + def clear_cache(): """ Clears the internal cache. """ - global _cache_name, _cache_name_egg, cache_path, _cache_path_egg, \ - _cache_generated, _cache_generated_egg + global _cache_name, _cache_name_egg, _cache_path, _cache_path_egg, \ + _cache_generated, _cache_generated_egg _cache_name = {} _cache_name_egg = {} @@ -661,14 +670,14 @@ def clear_cache(): _cache_generated_egg = False -def _yield_distributions(include_dist, include_egg): +def _yield_distributions(include_dist, include_egg, paths=sys.path): """ Yield .dist-info and .egg(-info) distributions, based on the arguments :parameter include_dist: yield .dist-info distributions :parameter include_egg: yield .egg(-info) distributions """ - for path in sys.path: + for path in paths: realpath = os.path.realpath(path) if not os.path.isdir(realpath): continue @@ -680,8 +689,7 @@ def _yield_distributions(include_dist, include_egg): dir.endswith('.egg')): yield EggInfoDistribution(dist_path) - -def _generate_cache(use_egg_info=False): +def _generate_cache(use_egg_info=False, paths=sys.path): global _cache_generated, _cache_generated_egg if _cache_generated_egg or (_cache_generated and not use_egg_info): @@ -690,7 +698,7 @@ def _generate_cache(use_egg_info=False): gen_dist = not _cache_generated gen_egg = use_egg_info - for dist in _yield_distributions(gen_dist, gen_egg): + for dist in _yield_distributions(gen_dist, gen_egg, paths): if isinstance(dist, Distribution): _cache_path[dist.path] = dist if not dist.name in _cache_name: @@ -718,7 +726,7 @@ class Distribution(object): name = '' """The name of the distribution.""" metadata = None - """A :class:`distutils2.metadata.DistributionMetadata` instance loaded with + """A :class:`distutils2.metadata.Metadata` instance loaded with the distribution's ``METADATA`` file.""" requested = False """A boolean that indicates whether the ``REQUESTED`` metadata file is @@ -730,7 +738,7 @@ class Distribution(object): self.metadata = _cache_path[path].metadata else: metadata_path = os.path.join(path, 'METADATA') - self.metadata = DistributionMetadata(path=metadata_path) + self.metadata = Metadata(path=metadata_path) self.path = path self.name = self.metadata['name'] @@ -738,9 +746,12 @@ class Distribution(object): if _cache_enabled and not path in _cache_path: _cache_path[path] = self + def __repr__(self): + return '%s-%s at %s' % (self.name, self.metadata.version, self.path) + def _get_records(self, local=False): - RECORD = os.path.join(self.path, 'RECORD') - record_reader = csv_reader(open(RECORD, 'rb'), delimiter=',') + RECORD = self.get_distinfo_file('RECORD') + record_reader = csv_reader(RECORD, delimiter=',') for row in record_reader: path, md5, size = row[:] + [None for i in xrange(len(row), 3)] if local: @@ -748,6 +759,15 @@ class Distribution(object): path = os.path.join(sys.prefix, path) yield path, md5, size + def get_resource_path(self, relative_path): + resources_file = self.get_distinfo_file('RESOURCES') + resources_reader = csv_reader(resources_file, delimiter=',') + for relative, destination in resources_reader: + if relative == relative_path: + return destination + raise KeyError('No resource file with relative path %s were installed' % + relative_path) + def get_installed_files(self, local=False): """ Iterates over the ``RECORD`` entries and returns a tuple @@ -805,13 +825,13 @@ class Distribution(object): distinfo_dirname, path = path.split(os.sep)[-2:] if distinfo_dirname != self.path.split(os.sep)[-1]: raise DistutilsError("Requested dist-info file does not " - "belong to the %s distribution. '%s' was requested." \ - % (self.name, os.sep.join([distinfo_dirname, path]))) + "belong to the %s distribution. '%s' was requested." \ + % (self.name, os.sep.join([distinfo_dirname, path]))) # The file must be relative if path not in DIST_FILES: raise DistutilsError("Requested an invalid dist-info file: " - "%s" % path) + "%s" % path) # Convert the relative path back to absolute path = os.path.join(self.path, path) @@ -848,17 +868,17 @@ class EggInfoDistribution(object): name = '' """The name of the distribution.""" metadata = None - """A :class:`distutils2.metadata.DistributionMetadata` instance loaded with + """A :class:`distutils2.metadata.Metadata` instance loaded with the distribution's ``METADATA`` file.""" - _REQUIREMENT = re.compile( \ - r'(?P<name>[-A-Za-z0-9_.]+)\s*' \ - r'(?P<first>(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)?\s*' \ - r'(?P<rest>(?:\s*,\s*(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)*)\s*' \ - r'(?P<extras>\[.*\])?') + _REQUIREMENT = re.compile(\ + r'(?P<name>[-A-Za-z0-9_.]+)\s*' \ + r'(?P<first>(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)?\s*' \ + r'(?P<rest>(?:\s*,\s*(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)*)\s*' \ + r'(?P<extras>\[.*\])?') - def __init__(self, path): + def __init__(self, path, display_warnings=False): self.path = path - + self.display_warnings = display_warnings if _cache_enabled and path in _cache_path_egg: self.metadata = _cache_path_egg[path].metadata self.name = self.metadata['Name'] @@ -871,7 +891,8 @@ class EggInfoDistribution(object): if isinstance(strs, basestring): for s in strs.splitlines(): s = s.strip() - if s and not s.startswith('#'): # skip blank lines/comments + # skip blank lines/comments + if s and not s.startswith('#'): yield s else: for ss in strs: @@ -882,16 +903,17 @@ class EggInfoDistribution(object): if path.endswith('.egg'): if os.path.isdir(path): meta_path = os.path.join(path, 'EGG-INFO', 'PKG-INFO') - self.metadata = DistributionMetadata(path=meta_path) + self.metadata = Metadata(path=meta_path) try: req_path = os.path.join(path, 'EGG-INFO', 'requires.txt') requires = open(req_path, 'r').read() except IOError: requires = None else: + # FIXME handle the case where zipfile is not available zipf = zipimport.zipimporter(path) fileobj = StringIO.StringIO(zipf.get_data('EGG-INFO/PKG-INFO')) - self.metadata = DistributionMetadata(fileobj=fileobj) + self.metadata = Metadata(fileobj=fileobj) try: requires = zipf.get_data('EGG-INFO/requires.txt') except IOError: @@ -905,35 +927,41 @@ class EggInfoDistribution(object): requires = req_f.read() except IOError: requires = None - self.metadata = DistributionMetadata(path=path) + self.metadata = Metadata(path=path) self.name = self.metadata['name'] else: raise ValueError('The path must end with .egg-info or .egg') - provides = "%s (%s)" % (self.metadata['name'], - self.metadata['version']) - if self.metadata['Metadata-Version'] == '1.2': - self.metadata['Provides-Dist'] += (provides,) - else: - self.metadata['Provides'] += (provides,) + + if requires is not None: + if self.metadata['Metadata-Version'] == '1.1': + # we can't have 1.1 metadata *and* Setuptools requires + for field in ('Obsoletes', 'Requires', 'Provides'): + del self.metadata[field] + reqs = [] + if requires is not None: for line in yield_lines(requires): - if line[0] == '[': + if line[0] == '[' and self.display_warnings: warnings.warn('distutils2 does not support extensions ' 'in requires.txt') break else: match = self._REQUIREMENT.match(line.strip()) if not match: - raise ValueError('Distribution %s has ill formed ' - 'requires.txt file (%s)' % - (self.name, line)) + # this happens when we encounter extras + # since they are written at the end of the file + # we just exit + break + #raise ValueError('Distribution %s has ill formed ' + # 'requires.txt file (%s)' % + # (self.name, line)) else: if match.group('extras'): s = (('Distribution %s uses extra requirements ' - 'which are not supported in distutils') \ - % (self.name)) + 'which are not supported in distutils') \ + % (self.name)) warnings.warn(s) name = match.group('name') version = None @@ -941,20 +969,50 @@ class EggInfoDistribution(object): version = match.group('first') if match.group('rest'): version += match.group('rest') - version = version.replace(' ', '') # trim spaces + version = version.replace(' ', '') # trim spaces if version is None: reqs.append(name) else: reqs.append('%s (%s)' % (name, version)) - if self.metadata['Metadata-Version'] == '1.2': + + if len(reqs) > 0: self.metadata['Requires-Dist'] += reqs - else: - self.metadata['Requires'] += reqs + if _cache_enabled: _cache_path_egg[self.path] = self + def __repr__(self): + return '%s-%s at %s' % (self.name, self.metadata.version, self.path) + def get_installed_files(self, local=False): + + def _md5(path): + f = open(path) + try: + content = f.read() + finally: + f.close() + return md5(content).hexdigest() + + def _size(path): + return os.stat(path)[ST_SIZE] + + path = self.path + if local: + path = path.replace('/', os.sep) + + # XXX What about scripts and data files ? + if os.path.isfile(path): + return [(path, _md5(path), _size(path))] + else: + files = [] + for root, dir, files_ in os.walk(path): + for item in files_: + item = os.path.join(root, item) + files.append((item, _md5(item), _size(item))) + return files + return [] def uses(self, path): @@ -962,18 +1020,12 @@ class EggInfoDistribution(object): def __eq__(self, other): return isinstance(other, EggInfoDistribution) and \ - self.path == other.path + self.path == other.path # See http://docs.python.org/reference/datamodel#object.__hash__ __hash__ = object.__hash__ -def _normalize_dist_name(name): - """Returns a normalized name from the given *name*. - :rtype: string""" - return name.replace('-', '_') - - def distinfo_dirname(name, version): """ The *name* and *version* parameters are converted into their @@ -993,7 +1045,7 @@ def distinfo_dirname(name, version): :returns: directory name :rtype: string""" file_extension = '.dist-info' - name = _normalize_dist_name(name) + name = name.replace('-', '_') normalized_version = suggest_normalized_version(version) # Because this is a lookup procedure, something will be returned even if # it is a version that cannot be normalized @@ -1003,7 +1055,7 @@ def distinfo_dirname(name, version): return '-'.join([name, normalized_version]) + file_extension -def get_distributions(use_egg_info=False): +def get_distributions(use_egg_info=False, paths=sys.path): """ Provides an iterator that looks for ``.dist-info`` directories in ``sys.path`` and returns :class:`Distribution` instances for each one of @@ -1014,10 +1066,10 @@ def get_distributions(use_egg_info=False): instances """ if not _cache_enabled: - for dist in _yield_distributions(True, use_egg_info): + for dist in _yield_distributions(True, use_egg_info, paths): yield dist else: - _generate_cache(use_egg_info) + _generate_cache(use_egg_info, paths) for dist in _cache_path.itervalues(): yield dist @@ -1027,7 +1079,7 @@ def get_distributions(use_egg_info=False): yield dist -def get_distribution(name, use_egg_info=False): +def get_distribution(name, use_egg_info=False, paths=None): """ Scans all elements in ``sys.path`` and looks for all directories ending with ``.dist-info``. Returns a :class:`Distribution` @@ -1044,12 +1096,15 @@ def get_distribution(name, use_egg_info=False): :rtype: :class:`Distribution` or :class:`EggInfoDistribution` or None """ + if paths == None: + paths = sys.path + if not _cache_enabled: - for dist in _yield_distributions(True, use_egg_info): + for dist in _yield_distributions(True, use_egg_info, paths): if dist.name == name: return dist else: - _generate_cache(use_egg_info) + _generate_cache(use_egg_info, paths) if name in _cache_name: return _cache_name[name][0] @@ -1086,7 +1141,7 @@ def obsoletes_distribution(name, version=None, use_egg_info=False): predicate = VersionPredicate(obs) except ValueError: raise DistutilsError(('Distribution %s has ill formed' + - ' obsoletes field') % (dist.name,)) + ' obsoletes field') % (dist.name,)) if name == o_components[0] and predicate.match(version): yield dist break @@ -1132,9 +1187,9 @@ def provides_distribution(name, version=None, use_egg_info=False): p_name, p_ver = p_components if len(p_ver) < 2 or p_ver[0] != '(' or p_ver[-1] != ')': raise DistutilsError(('Distribution %s has invalid ' + - 'provides field: %s') \ - % (dist.name, p)) - p_ver = p_ver[1:-1] # trim off the parenthesis + 'provides field: %s') \ + % (dist.name, p)) + p_ver = p_ver[1:-1] # trim off the parenthesis if p_name == name and predicate.match(p_ver): yield dist break @@ -1153,3 +1208,15 @@ def get_file_users(path): for dist in get_distributions(): if dist.uses(path): yield dist + +def resource_path(distribution_name, relative_path): + dist = get_distribution(distribution_name) + if dist != None: + return dist.get_resource_path(relative_path) + raise LookupError('No distribution named %s is installed.' % + distribution_name) + +def resource_open(distribution_name, relative_path, * args, ** kwargs): + file = open(resource_path(distribution_name, relative_path), * args, + ** kwargs) + return file
\ No newline at end of file diff --git a/distutils2/_backport/shutil.py b/distutils2/_backport/shutil.py index b9e6a77..ef34e43 100644 --- a/distutils2/_backport/shutil.py +++ b/distutils2/_backport/shutil.py @@ -1,4 +1,4 @@ -"""Utility functions for copying files and directory trees. +"""Utility functions for copying and archiving files and directory trees. XXX The functions here don't copy the resource fork or other metadata on Mac. @@ -9,7 +9,13 @@ import sys import stat from os.path import abspath import fnmatch -from warnings import warn +import errno + +try: + import bz2 + _BZ2_SUPPORTED = True +except ImportError: + _BZ2_SUPPORTED = False try: from pwd import getpwnam @@ -21,9 +27,12 @@ try: except ImportError: getgrnam = None -__all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2", - "copytree","move","rmtree","Error", "SpecialFileError", - "ExecError","make_archive"] +__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", + "copytree", "move", "rmtree", "Error", "SpecialFileError", + "ExecError", "make_archive", "get_archive_formats", + "register_archive_format", "unregister_archive_format", + "get_unpack_formats", "register_unpack_format", + "unregister_unpack_format", "unpack_archive"] class Error(EnvironmentError): pass @@ -35,6 +44,14 @@ class SpecialFileError(EnvironmentError): class ExecError(EnvironmentError): """Raised when a command could not be executed""" +class ReadError(EnvironmentError): + """Raised when an archive cannot be read""" + +class RegistryError(Exception): + """Raised when a registery operation with the archiving + and unpacking registeries fails""" + + try: WindowsError except NameError: @@ -50,7 +67,7 @@ def copyfileobj(fsrc, fdst, length=16*1024): def _samefile(src, dst): # Macintosh, Unix. - if hasattr(os.path,'samefile'): + if hasattr(os.path, 'samefile'): try: return os.path.samefile(src, dst) except OSError: @@ -63,10 +80,8 @@ def _samefile(src, dst): def copyfile(src, dst): """Copy data from src to dst""" if _samefile(src, dst): - raise Error, "`%s` and `%s` are the same file" % (src, dst) + raise Error("`%s` and `%s` are the same file" % (src, dst)) - fsrc = None - fdst = None for fn in [src, dst]: try: st = os.stat(fn) @@ -77,15 +92,16 @@ def copyfile(src, dst): # XXX What about other special files? (sockets, devices...) if stat.S_ISFIFO(st.st_mode): raise SpecialFileError("`%s` is a named pipe" % fn) + + fsrc = open(src, 'rb') try: - fsrc = open(src, 'rb') fdst = open(dst, 'wb') - copyfileobj(fsrc, fdst) - finally: - if fdst: + try: + copyfileobj(fsrc, fdst) + finally: fdst.close() - if fsrc: - fsrc.close() + finally: + fsrc.close() def copymode(src, dst): """Copy mode bits from src to dst""" @@ -103,8 +119,12 @@ def copystat(src, dst): if hasattr(os, 'chmod'): os.chmod(dst, mode) if hasattr(os, 'chflags') and hasattr(st, 'st_flags'): - os.chflags(dst, st.st_flags) - + try: + os.chflags(dst, st.st_flags) + except OSError, why: + if (not hasattr(errno, 'EOPNOTSUPP') or + why.errno != errno.EOPNOTSUPP): + raise def copy(src, dst): """Copy data and mode bits ("cp src dst"). @@ -140,8 +160,9 @@ def ignore_patterns(*patterns): return set(ignored_names) return _ignore_patterns -def copytree(src, dst, symlinks=False, ignore=None): - """Recursively copy a directory tree using copy2(). +def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, + ignore_dangling_symlinks=False): + """Recursively copy a directory tree. The destination directory must not already exist. If exception(s) occur, an Error is raised with a list of reasons. @@ -149,7 +170,13 @@ def copytree(src, dst, symlinks=False, ignore=None): If the optional symlinks flag is true, symbolic links in the source tree result in symbolic links in the destination tree; if it is false, the contents of the files pointed to by symbolic - links are copied. + links are copied. If the file pointed by the symlink doesn't + exist, an exception will be added in the list of errors raised in + an Error exception at the end of the copy process. + + You can set the optional ignore_dangling_symlinks flag to true if you + want to silence this exception. Notice that this has no effect on + platforms that don't support os.symlink. The optional ignore argument is a callable. If given, it is called with the `src` parameter, which is the directory @@ -163,7 +190,10 @@ def copytree(src, dst, symlinks=False, ignore=None): list of names relative to the `src` directory that should not be copied. - XXX Consider this example code rather than the ultimate tool. + The optional copy_function argument is a callable that will be used + to copy each file. It will be called with the source path and the + destination path as arguments. By default, copy2() is used, but any + function that supports the same signature (like copy()) can be used. """ names = os.listdir(src) @@ -182,14 +212,21 @@ def copytree(src, dst, symlinks=False, ignore=None): srcname = os.path.join(src, name) dstname = os.path.join(dst, name) try: - if symlinks and os.path.islink(srcname): + if os.path.islink(srcname): linkto = os.readlink(srcname) - os.symlink(linkto, dstname) + if symlinks: + os.symlink(linkto, dstname) + else: + # ignore dangling symlink if the flag is on + if not os.path.exists(linkto) and ignore_dangling_symlinks: + continue + # otherwise let the copy occurs. copy2 will raise an error + copy_function(srcname, dstname) elif os.path.isdir(srcname): - copytree(srcname, dstname, symlinks, ignore) + copytree(srcname, dstname, symlinks, ignore, copy_function) else: # Will raise a SpecialFileError for unsupported file types - copy2(srcname, dstname) + copy_function(srcname, dstname) # catch the Error from the recursive copytree so that we can # continue with other files except Error, err: @@ -205,7 +242,7 @@ def copytree(src, dst, symlinks=False, ignore=None): else: errors.extend((src, dst, str(why))) if errors: - raise Error, errors + raise Error(errors) def rmtree(path, ignore_errors=False, onerror=None): """Recursively delete a directory tree. @@ -235,7 +272,7 @@ def rmtree(path, ignore_errors=False, onerror=None): names = [] try: names = os.listdir(path) - except os.error, err: + except os.error: onerror(os.listdir, path, sys.exc_info()) for name in names: fullname = os.path.join(path, name) @@ -248,7 +285,7 @@ def rmtree(path, ignore_errors=False, onerror=None): else: try: os.remove(fullname) - except os.error, err: + except os.error: onerror(os.remove, fullname, sys.exc_info()) try: os.rmdir(path) @@ -282,13 +319,13 @@ def move(src, dst): if os.path.isdir(dst): real_dst = os.path.join(dst, _basename(src)) if os.path.exists(real_dst): - raise Error, "Destination path '%s' already exists" % real_dst + raise Error("Destination path '%s' already exists" % real_dst) try: os.rename(src, real_dst) except OSError: if os.path.isdir(src): if _destinsrc(src, dst): - raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst) + raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst)) copytree(src, real_dst, symlinks=True) rmtree(src) else: @@ -333,44 +370,45 @@ def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0, """Create a (possibly compressed) tar file from all the files under 'base_dir'. - 'compress' must be "gzip" (the default), "compress", "bzip2", or None. - (compress will be deprecated in Python 3.2) + 'compress' must be "gzip" (the default), "bzip2", or None. 'owner' and 'group' can be used to define an owner and a group for the archive that is being built. If not provided, the current owner and group will be used. - The output tar file will be named 'base_dir' + ".tar", possibly plus - the appropriate compression extension (".gz", ".bz2" or ".Z"). + The output tar file will be named 'base_name' + ".tar", possibly plus + the appropriate compression extension (".gz", or ".bz2"). Returns the output filename. """ - tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''} - compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'compress': '.Z'} + tar_compression = {'gzip': 'gz', None: ''} + compress_ext = {'gzip': '.gz'} - # flags for compression program, each element of list will be an argument - if compress is not None and compress not in compress_ext.keys(): - raise ValueError, \ - ("bad value for 'compress': must be None, 'gzip', 'bzip2' " - "or 'compress'") + if _BZ2_SUPPORTED: + tar_compression['bzip2'] = 'bz2' + compress_ext['bzip2'] = '.bz2' - archive_name = base_name + '.tar' - if compress != 'compress': - archive_name += compress_ext.get(compress, '') + # flags for compression program, each element of list will be an argument + if compress is not None and compress not in compress_ext: + raise ValueError("bad value for 'compress', or compression format not " + "supported: %s" % compress) + archive_name = base_name + '.tar' + compress_ext.get(compress, '') archive_dir = os.path.dirname(archive_name) + if not os.path.exists(archive_dir): if logger is not None: - logger.info("creating %s" % archive_dir) + logger.info("creating %s", archive_dir) if not dry_run: os.makedirs(archive_dir) - # creating the tarball + # XXX late import because of circular dependency between shutil and + # tarfile :( from distutils2._backport import tarfile if logger is not None: - logger.info('Creating tar archive') + logger.info('creating tar archive') uid = _get_uid(owner) gid = _get_gid(group) @@ -391,23 +429,9 @@ def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0, finally: tar.close() - # compression using `compress` - # XXX this block will be removed in Python 3.2 - if compress == 'compress': - warn("'compress' will be deprecated.", PendingDeprecationWarning) - # the option varies depending on the platform - compressed_name = archive_name + compress_ext[compress] - if sys.platform == 'win32': - cmd = [compress, archive_name, compressed_name] - else: - cmd = [compress, '-f', archive_name] - from distutils2.spawn import spawn - spawn(cmd, dry_run=dry_run) - return compressed_name - return archive_name -def _call_external_zip(directory, verbose=False): +def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False): # XXX see if we want to keep an external call here if verbose: zipoptions = "-r" @@ -420,15 +444,14 @@ def _call_external_zip(directory, verbose=False): except DistutilsExecError: # XXX really should distinguish between "couldn't find # external 'zip' command" and "zip failed". - raise ExecError, \ - ("unable to create zip file '%s': " + raise ExecError("unable to create zip file '%s': " "could neither import the 'zipfile' module nor " "find a standalone zip utility") % zip_filename def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None): """Create a zip file from all the files under 'base_dir'. - The output zip file will be named 'base_dir' + ".zip". Uses either the + The output zip file will be named 'base_name' + ".zip". Uses either the "zipfile" Python module (if available) or the InfoZIP "zip" utility (if installed and found on the default search path). If neither tool is available, raises ExecError. Returns the name of the output zip @@ -451,7 +474,7 @@ def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None): zipfile = None if zipfile is None: - _call_external_zip(base_dir, verbose) + _call_external_zip(base_dir, zip_filename, verbose, dry_run) else: if logger is not None: logger.info("creating '%s' and adding '%s' to it", @@ -475,19 +498,21 @@ def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None): _ARCHIVE_FORMATS = { 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"), 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"), - 'ztar': (_make_tarball, [('compress', 'compress')], - "compressed tar file"), 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"), - 'zip': (_make_zipfile, [],"ZIP file") + 'zip': (_make_zipfile, [], "ZIP file"), } +if _BZ2_SUPPORTED: + _ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')], + "bzip2'ed tar-file") + def get_archive_formats(): """Returns a list of supported formats for archiving and unarchiving. Each element of the returned sequence is a tuple (name, description) """ formats = [(name, registry[2]) for name, registry in - _ARCHIVE_FORMATS.items()] + _ARCHIVE_FORMATS.iteritems()] formats.sort() return formats @@ -507,7 +532,7 @@ def register_archive_format(name, function, extra_args=None, description=''): if not isinstance(extra_args, (tuple, list)): raise TypeError('extra_args needs to be a sequence') for element in extra_args: - if not isinstance(element, (tuple, list)) or len(element) !=2 : + if not isinstance(element, (tuple, list)) or len(element) !=2: raise TypeError('extra_args elements are : (arg_name, value)') _ARCHIVE_FORMATS[name] = (function, extra_args, description) @@ -520,7 +545,7 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, """Create an archive file (eg. zip or tar). 'base_name' is the name of the file to create, minus any format-specific - extension; 'format' is the archive format: one of "zip", "tar", "ztar", + extension; 'format' is the archive format: one of "zip", "tar", "bztar" or "gztar". 'root_dir' is a directory that will be the root directory of the @@ -549,7 +574,7 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, try: format_info = _ARCHIVE_FORMATS[format] except KeyError: - raise ValueError, "unknown archive format '%s'" % format + raise ValueError("unknown archive format '%s'" % format) func = format_info[0] for arg, val in format_info[1]: @@ -568,3 +593,176 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, os.chdir(save_cwd) return filename + + +def get_unpack_formats(): + """Returns a list of supported formats for unpacking. + + Each element of the returned sequence is a tuple + (name, extensions, description) + """ + formats = [(name, info[0], info[3]) for name, info in + _UNPACK_FORMATS.iteritems()] + formats.sort() + return formats + +def _check_unpack_options(extensions, function, extra_args): + """Checks what gets registered as an unpacker.""" + # first make sure no other unpacker is registered for this extension + existing_extensions = {} + for name, info in _UNPACK_FORMATS.iteritems(): + for ext in info[0]: + existing_extensions[ext] = name + + for extension in extensions: + if extension in existing_extensions: + msg = '%s is already registered for "%s"' + raise RegistryError(msg % (extension, + existing_extensions[extension])) + + if not callable(function): + raise TypeError('The registered function must be a callable') + + +def register_unpack_format(name, extensions, function, extra_args=None, + description=''): + """Registers an unpack format. + + `name` is the name of the format. `extensions` is a list of extensions + corresponding to the format. + + `function` is the callable that will be + used to unpack archives. The callable will receive archives to unpack. + If it's unable to handle an archive, it needs to raise a ReadError + exception. + + If provided, `extra_args` is a sequence of + (name, value) tuples that will be passed as arguments to the callable. + description can be provided to describe the format, and will be returned + by the get_unpack_formats() function. + """ + if extra_args is None: + extra_args = [] + _check_unpack_options(extensions, function, extra_args) + _UNPACK_FORMATS[name] = extensions, function, extra_args, description + +def unregister_unpack_format(name): + """Removes the pack format from the registery.""" + del _UNPACK_FORMATS[name] + +def _ensure_directory(path): + """Ensure that the parent directory of `path` exists""" + dirname = os.path.dirname(path) + if not os.path.isdir(dirname): + os.makedirs(dirname) + +def _unpack_zipfile(filename, extract_dir): + """Unpack zip `filename` to `extract_dir` + """ + try: + import zipfile + except ImportError: + raise ReadError('zlib not supported, cannot unpack this archive.') + + if not zipfile.is_zipfile(filename): + raise ReadError("%s is not a zip file" % filename) + + zip = zipfile.ZipFile(filename) + try: + for info in zip.infolist(): + name = info.filename + + # don't extract absolute paths or ones with .. in them + if name.startswith('/') or '..' in name: + continue + + target = os.path.join(extract_dir, *name.split('/')) + if not target: + continue + + _ensure_directory(target) + if not name.endswith('/'): + # file + data = zip.read(info.filename) + f = open(target, 'wb') + try: + f.write(data) + finally: + f.close() + del data + finally: + zip.close() + +def _unpack_tarfile(filename, extract_dir): + """Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir` + """ + from distutils2._backport import tarfile + try: + tarobj = tarfile.open(filename) + except tarfile.TarError: + raise ReadError( + "%s is not a compressed or uncompressed tar file" % filename) + try: + tarobj.extractall(extract_dir) + finally: + tarobj.close() + +_UNPACK_FORMATS = { + 'gztar': (['.tar.gz', '.tgz'], _unpack_tarfile, [], "gzip'ed tar-file"), + 'tar': (['.tar'], _unpack_tarfile, [], "uncompressed tar file"), + 'zip': (['.zip'], _unpack_zipfile, [], "ZIP file") + } + +if _BZ2_SUPPORTED: + _UNPACK_FORMATS['bztar'] = (['.bz2'], _unpack_tarfile, [], + "bzip2'ed tar-file") + +def _find_unpack_format(filename): + for name, info in _UNPACK_FORMATS.iteritems(): + for extension in info[0]: + if filename.endswith(extension): + return name + return None + +def unpack_archive(filename, extract_dir=None, format=None): + """Unpack an archive. + + `filename` is the name of the archive. + + `extract_dir` is the name of the target directory, where the archive + is unpacked. If not provided, the current working directory is used. + + `format` is the archive format: one of "zip", "tar", or "gztar". Or any + other registered format. If not provided, unpack_archive will use the + filename extension and see if an unpacker was registered for that + extension. + + In case none is found, a ValueError is raised. + """ + if extract_dir is None: + extract_dir = os.getcwd() + + func = None + + if format is not None: + try: + format_info = _UNPACK_FORMATS[format] + except KeyError: + raise ValueError("Unknown unpack format '{0}'".format(format)) + + func = format_info[0] + func(filename, extract_dir, **dict(format_info[1])) + else: + # we need to look at the registered unpackers supported extensions + format = _find_unpack_format(filename) + if format is None: + raise ReadError("Unknown archive format '{0}'".format(filename)) + + func = _UNPACK_FORMATS[format][1] + kwargs = dict(_UNPACK_FORMATS[format][2]) + func(filename, extract_dir, **kwargs) + + if func is None: + raise ValueError('Unknown archive format: %s' % filename) + + return extract_dir diff --git a/distutils2/_backport/sysconfig.py b/distutils2/_backport/sysconfig.py index 7024d04..7760e89 100644 --- a/distutils2/_backport/sysconfig.py +++ b/distutils2/_backport/sysconfig.py @@ -1,8 +1,6 @@ -"""Provide access to Python's configuration information. - -""" -import sys +"""Provide access to Python's configuration information.""" import os +import sys import re from os.path import pardir, realpath from ConfigParser import RawConfigParser @@ -18,6 +16,7 @@ _SCHEMES = RawConfigParser() _SCHEMES.read(_CONFIG_FILE) _VAR_REPL = re.compile(r'\{([^{]*?)\}') + def _expand_globals(config): if config.has_section('globals'): globals = config.items('globals') @@ -38,11 +37,13 @@ def _expand_globals(config): # for section in config.sections(): variables = dict(config.items(section)) + def _replacer(matchobj): name = matchobj.group(1) if name in variables: return variables[name] return matchobj.group(0) + for option, value in config.items(section): config.set(section, option, _VAR_REPL.sub(_replacer, value)) @@ -69,6 +70,7 @@ if os.name == "nt" and "\\pc\\v" in _PROJECT_BASE[-10:].lower(): if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower(): _PROJECT_BASE = realpath(os.path.join(_PROJECT_BASE, pardir, pardir)) + def is_python_build(): for fn in ("Setup.dist", "Setup.local"): if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)): @@ -84,10 +86,10 @@ if _PYTHON_BUILD: def _subst_vars(path, local_vars): - """In the string `path`, replace tokens like {some.thing} with the corresponding value from the map `local_vars`. + """In the string `path`, replace tokens like {some.thing} with the + corresponding value from the map `local_vars`. If there is no corresponding value, leave the token unchanged. - """ def _replacer(matchobj): name = matchobj.group(1) @@ -98,13 +100,14 @@ def _subst_vars(path, local_vars): return matchobj.group(0) return _VAR_REPL.sub(_replacer, path) + def _extend_dict(target_dict, other_dict): - target_keys = target_dict.keys() - for key, value in other_dict.items(): - if key in target_keys: + for key, value in other_dict.iteritems(): + if key in target_dict: continue target_dict[key] = value + def _expand_vars(scheme, vars): res = {} if vars is None: @@ -117,14 +120,25 @@ def _expand_vars(scheme, vars): res[key] = os.path.normpath(_subst_vars(value, vars)) return res +def format_value(value, vars): + def _replacer(matchobj): + name = matchobj.group(1) + if name in vars: + return vars[name] + return matchobj.group(0) + return _VAR_REPL.sub(_replacer, value) + + def _get_default_scheme(): if os.name == 'posix': # the default scheme for posix is posix_prefix return 'posix_prefix' return os.name + def _getuserbase(): env_base = os.environ.get("PYTHONUSERBASE", None) + def joinuser(*args): return os.path.expanduser(os.path.join(*args)) @@ -158,7 +172,6 @@ def _parse_makefile(filename, vars=None): optional dictionary is passed in as the second argument, it is used instead of a new dictionary. """ - import re # Regexes needed for parsing Makefile (and similar syntaxes, # like old-style Setup files). _variable_rx = re.compile("([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)") @@ -256,7 +269,6 @@ def _parse_makefile(filename, vars=None): if name not in done: done[name] = value - else: # bogus variable reference; just drop it since we can't deal variables.remove(name) @@ -267,6 +279,7 @@ def _parse_makefile(filename, vars=None): def get_makefile_filename(): + """Return the path of the Makefile.""" if _PYTHON_BUILD: return os.path.join(_PROJECT_BASE, "Makefile") return os.path.join(get_path('stdlib'), "config", "Makefile") @@ -315,6 +328,7 @@ def _init_posix(vars): if _PYTHON_BUILD: vars['LDSHARED'] = vars['BLDSHARED'] + def _init_non_posix(vars): """Initialize the module as appropriate for NT""" # set basic install directories @@ -338,7 +352,6 @@ def parse_config_h(fp, vars=None): optional dictionary is passed in as the second argument, it is used instead of a new dictionary. """ - import re if vars is None: vars = {} define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n") @@ -351,8 +364,10 @@ def parse_config_h(fp, vars=None): m = define_rx.match(line) if m: n, v = m.group(1, 2) - try: v = int(v) - except ValueError: pass + try: + v = int(v) + except ValueError: + pass vars[n] = v else: m = undef_rx.match(line) @@ -360,8 +375,9 @@ def parse_config_h(fp, vars=None): vars[m.group(1)] = 0 return vars + def get_config_h_filename(): - """Returns the path of pyconfig.h.""" + """Return the path of pyconfig.h.""" if _PYTHON_BUILD: if os.name == "nt": inc_dir = os.path.join(_PROJECT_BASE, "PC") @@ -371,17 +387,20 @@ def get_config_h_filename(): inc_dir = get_path('platinclude') return os.path.join(inc_dir, 'pyconfig.h') + def get_scheme_names(): - """Returns a tuple containing the schemes names.""" + """Return a tuple containing the schemes names.""" return tuple(sorted(_SCHEMES.sections())) + def get_path_names(): - """Returns a tuple containing the paths names.""" + """Return a tuple containing the paths names.""" # xxx see if we want a static list return _SCHEMES.options('posix_prefix') + def get_paths(scheme=_get_default_scheme(), vars=None, expand=True): - """Returns a mapping containing an install scheme. + """Return a mapping containing an install scheme. ``scheme`` is the install scheme name. If not provided, it will return the default scheme for the current platform. @@ -391,13 +410,15 @@ def get_paths(scheme=_get_default_scheme(), vars=None, expand=True): else: return dict(_SCHEMES.items(scheme)) + def get_path(name, scheme=_get_default_scheme(), vars=None, expand=True): - """Returns a path corresponding to the scheme. + """Return a path corresponding to the scheme. ``scheme`` is the install scheme name. """ return get_paths(scheme, vars, expand)[name] + def get_config_vars(*args): """With no arguments, return a dictionary of all configuration variables relevant for the current platform. @@ -408,7 +429,6 @@ def get_config_vars(*args): With arguments, return a list of values that result from looking up each argument in the configuration variable dictionary. """ - import re global _CONFIG_VARS if _CONFIG_VARS is None: _CONFIG_VARS = {} @@ -440,7 +460,6 @@ def get_config_vars(*args): else: _CONFIG_VARS['srcdir'] = realpath(_CONFIG_VARS['srcdir']) - # Convert srcdir into an absolute path if it appears necessary. # Normally it is relative to the build directory. However, during # testing, for example, we might be running a non-installed python @@ -456,7 +475,7 @@ def get_config_vars(*args): _CONFIG_VARS['srcdir'] = os.path.normpath(srcdir) if sys.platform == 'darwin': - kernel_version = os.uname()[2] # Kernel version (8.4.3) + kernel_version = os.uname()[2] # Kernel version (8.4.3) major_version = int(kernel_version.split('.')[0]) if major_version < 8: @@ -522,6 +541,7 @@ def get_config_vars(*args): else: return _CONFIG_VARS + def get_config_var(name): """Return the value of a single variable using the dictionary returned by 'get_config_vars()'. @@ -530,6 +550,7 @@ def get_config_var(name): """ return get_config_vars().get(name) + def get_platform(): """Return a string that identifies the current platform. @@ -555,7 +576,6 @@ def get_platform(): For other non-POSIX platforms, currently just returns 'sys.platform'. """ - import re if os.name == 'nt': # sniff sys.version for architecture. prefix = " bit (" @@ -563,7 +583,7 @@ def get_platform(): if i == -1: return sys.platform j = sys.version.find(")", i) - look = sys.version[i+len(prefix):j].lower() + look = sys.version[i + len(prefix):j].lower() if look == 'amd64': return 'win-amd64' if look == 'itanium': @@ -600,7 +620,7 @@ def get_platform(): return "%s-%s.%s" % (osname, version, release) elif osname[:6] == "cygwin": osname = "cygwin" - rel_re = re.compile (r'[\d.]+') + rel_re = re.compile(r'[\d.]+') m = rel_re.match(release) if m: release = m.group() @@ -675,19 +695,19 @@ def get_platform(): machine = 'universal' else: raise ValueError( - "Don't know machine value for archs=%r"%(archs,)) + "Don't know machine value for archs=%r" % (archs,)) elif machine == 'i386': # On OSX the machine type returned by uname is always the # 32-bit variant, even if the executable architecture is # the 64-bit variant - if sys.maxint >= 2**32: + if sys.maxint >= (2 ** 32): machine = 'x86_64' elif machine in ('PowerPC', 'Power_Macintosh'): # Pick a sane name for the PPC architecture. # See 'i386' case - if sys.maxint >= 2**32: + if sys.maxint >= (2 ** 32): machine = 'ppc64' else: machine = 'ppc' @@ -698,12 +718,14 @@ def get_platform(): def get_python_version(): return _PY_VERSION_SHORT + def _print_dict(title, data): - for index, (key, value) in enumerate(sorted(data.items())): + for index, (key, value) in enumerate(sorted(data.iteritems())): if index == 0: print '%s: ' % (title) print '\t%s = "%s"' % (key, value) + def _main(): """Display all information sysconfig detains.""" print 'Platform: "%s"' % get_platform() @@ -714,5 +736,6 @@ def _main(): print _print_dict('Variables', get_config_vars()) + if __name__ == '__main__': _main() diff --git a/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/INSTALLER b/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/INSTALLER new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/INSTALLER diff --git a/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/METADATA b/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/METADATA new file mode 100644 index 0000000..65e839a --- /dev/null +++ b/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/METADATA @@ -0,0 +1,4 @@ +Metadata-version: 1.2 +Name: babar +Version: 0.1 +Author: FELD Boris
\ No newline at end of file diff --git a/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/RECORD b/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/RECORD new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/RECORD diff --git a/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/REQUESTED b/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/REQUESTED diff --git a/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/RESOURCES b/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/RESOURCES new file mode 100644 index 0000000..5d0da49 --- /dev/null +++ b/distutils2/_backport/tests/fake_dists/babar-0.1.dist-info/RESOURCES @@ -0,0 +1,2 @@ +babar.png,babar.png +babar.cfg,babar.cfg
\ No newline at end of file diff --git a/distutils2/_backport/tests/fake_dists/babar.cfg b/distutils2/_backport/tests/fake_dists/babar.cfg new file mode 100644 index 0000000..ecd6efe --- /dev/null +++ b/distutils2/_backport/tests/fake_dists/babar.cfg @@ -0,0 +1 @@ +Config
\ No newline at end of file diff --git a/distutils2/_backport/tests/fake_dists/babar.png b/distutils2/_backport/tests/fake_dists/babar.png new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/distutils2/_backport/tests/fake_dists/babar.png diff --git a/distutils2/_backport/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO b/distutils2/_backport/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO new file mode 100644 index 0000000..499a083 --- /dev/null +++ b/distutils2/_backport/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO @@ -0,0 +1,5 @@ +Metadata-Version: 1.2 +Name: coconuts-aster +Version: 10.3 +Provides-Dist: strawberry (0.6) +Provides-Dist: banana (0.4) diff --git a/distutils2/_backport/tests/test_pkgutil.py b/distutils2/_backport/tests/test_pkgutil.py index d938f89..d37e0b4 100644 --- a/distutils2/_backport/tests/test_pkgutil.py +++ b/distutils2/_backport/tests/test_pkgutil.py @@ -1,19 +1,27 @@ # -*- coding: utf-8 -*- """Tests for PEP 376 pkgutil functionality""" +import imp import sys -import os + import csv -import imp -import tempfile +import os import shutil +import tempfile import zipfile try: from hashlib import md5 except ImportError: from distutils2._backport.hashlib import md5 +from distutils2.errors import DistutilsError +from distutils2.metadata import Metadata from distutils2.tests import unittest, run_unittest, support, TESTFN + from distutils2._backport import pkgutil +from distutils2._backport.pkgutil import ( + Distribution, EggInfoDistribution, get_distribution, get_distributions, + provides_distribution, obsoletes_distribution, get_file_users, + distinfo_dirname, _yield_distributions) try: from os.path import relpath @@ -106,11 +114,16 @@ class TestPkgUtilData(unittest.TestCase): self.assertEqual(res1, RESOURCE_DATA) res2 = pkgutil.get_data(pkg, 'sub/res.txt') self.assertEqual(res2, RESOURCE_DATA) + + names = [] + for loader, name, ispkg in pkgutil.iter_modules([zip_file]): + names.append(name) + self.assertEqual(names, ['test_getdata_zipfile']) + del sys.path[0] del sys.modules[pkg] - # Adapted from Python 2.7's trunk @@ -169,7 +182,7 @@ class TestPkgUtilDistribution(unittest.TestCase): def setUp(self): super(TestPkgUtilDistribution, self).setUp() self.fake_dists_path = os.path.abspath( - os.path.join(os.path.dirname(__file__), 'fake_dists')) + os.path.join(os.path.dirname(__file__), 'fake_dists')) pkgutil.disable_cache() self.distinfo_dirs = [os.path.join(self.fake_dists_path, dir) @@ -192,7 +205,7 @@ class TestPkgUtilDistribution(unittest.TestCase): # Setup the RECORD file for this dist record_file = os.path.join(distinfo_dir, 'RECORD') record_writer = csv.writer(open(record_file, 'w'), delimiter=',', - quoting=csv.QUOTE_NONE) + quoting=csv.QUOTE_NONE) dist_location = distinfo_dir.replace('.dist-info', '') for path, dirs, files in os.walk(dist_location): @@ -201,15 +214,15 @@ class TestPkgUtilDistribution(unittest.TestCase): os.path.join(path, f))) for file in ['INSTALLER', 'METADATA', 'REQUESTED']: record_writer.writerow(record_pieces( - os.path.join(distinfo_dir, file))) + os.path.join(distinfo_dir, file))) record_writer.writerow([relpath(record_file, sys.prefix)]) - del record_writer # causes the RECORD file to close + del record_writer # causes the RECORD file to close record_reader = csv.reader(open(record_file, 'rb')) record_data = [] for row in record_reader: path, md5_, size = row[:] + \ - [None for i in xrange(len(row), 3)] - record_data.append([path, (md5_, size,)]) + [None for i in xrange(len(row), 3)] + record_data.append([path, (md5_, size, )]) self.records[distinfo_dir] = dict(record_data) def tearDown(self): @@ -223,31 +236,26 @@ class TestPkgUtilDistribution(unittest.TestCase): def test_instantiation(self): # Test the Distribution class's instantiation provides us with usable # attributes. - # Import the Distribution class - from distutils2._backport.pkgutil import distinfo_dirname, Distribution - here = os.path.abspath(os.path.dirname(__file__)) name = 'choxie' version = '2.0.0.9' dist_path = os.path.join(here, 'fake_dists', - distinfo_dirname(name, version)) + distinfo_dirname(name, version)) dist = Distribution(dist_path) self.assertEqual(dist.name, name) - from distutils2.metadata import DistributionMetadata - self.assertTrue(isinstance(dist.metadata, DistributionMetadata)) + self.assertTrue(isinstance(dist.metadata, Metadata)) self.assertEqual(dist.metadata['version'], version) self.assertTrue(isinstance(dist.requested, type(bool()))) def test_installed_files(self): # Test the iteration of installed files. # Test the distribution's installed files - from distutils2._backport.pkgutil import Distribution for distinfo_dir in self.distinfo_dirs: dist = Distribution(distinfo_dir) for path, md5_, size in dist.get_installed_files(): record_data = self.records[dist.path] - self.assertTrue(path in record_data.keys()) + self.assertIn(path, record_data) self.assertEqual(md5_, record_data[path][0]) self.assertEqual(size, record_data[path][1]) @@ -256,27 +264,25 @@ class TestPkgUtilDistribution(unittest.TestCase): # Criteria to test against distinfo_name = 'grammar-1.0a4' distinfo_dir = os.path.join(self.fake_dists_path, - distinfo_name + '.dist-info') + distinfo_name + '.dist-info') true_path = [self.fake_dists_path, distinfo_name, \ - 'grammar', 'utils.py'] + 'grammar', 'utils.py'] true_path = relpath(os.path.join(*true_path), sys.prefix) false_path = [self.fake_dists_path, 'towel_stuff-0.1', 'towel_stuff', '__init__.py'] false_path = relpath(os.path.join(*false_path), sys.prefix) # Test if the distribution uses the file in question - from distutils2._backport.pkgutil import Distribution dist = Distribution(distinfo_dir) self.assertTrue(dist.uses(true_path)) self.assertFalse(dist.uses(false_path)) def test_get_distinfo_file(self): # Test the retrieval of dist-info file objects. - from distutils2._backport.pkgutil import Distribution distinfo_name = 'choxie-2.0.0.9' other_distinfo_name = 'grammar-1.0a4' distinfo_dir = os.path.join(self.fake_dists_path, - distinfo_name + '.dist-info') + distinfo_name + '.dist-info') dist = Distribution(distinfo_dir) # Test for known good file matches distinfo_files = [ @@ -293,22 +299,20 @@ class TestPkgUtilDistribution(unittest.TestCase): # Is it the correct file? self.assertEqual(value.name, os.path.join(distinfo_dir, distfile)) - from distutils2.errors import DistutilsError # Test an absolute path that is part of another distributions dist-info other_distinfo_file = os.path.join(self.fake_dists_path, - other_distinfo_name + '.dist-info', 'REQUESTED') + other_distinfo_name + '.dist-info', 'REQUESTED') self.assertRaises(DistutilsError, dist.get_distinfo_file, - other_distinfo_file) + other_distinfo_file) # Test for a file that does not exist and should not exist self.assertRaises(DistutilsError, dist.get_distinfo_file, \ 'ENTRYPOINTS') def test_get_distinfo_files(self): # Test for the iteration of RECORD path entries. - from distutils2._backport.pkgutil import Distribution distinfo_name = 'towel_stuff-0.1' distinfo_dir = os.path.join(self.fake_dists_path, - distinfo_name + '.dist-info') + distinfo_name + '.dist-info') dist = Distribution(distinfo_dir) # Test for the iteration of the raw path distinfo_record_paths = self.records[distinfo_dir].keys() @@ -316,10 +320,20 @@ class TestPkgUtilDistribution(unittest.TestCase): self.assertEqual(sorted(found), sorted(distinfo_record_paths)) # Test for the iteration of local absolute paths distinfo_record_paths = [os.path.join(sys.prefix, path) - for path in self.records[distinfo_dir].keys()] + for path in self.records[distinfo_dir]] found = [path for path in dist.get_distinfo_files(local=True)] self.assertEqual(sorted(found), sorted(distinfo_record_paths)) + def test_get_resources_path(self): + distinfo_name = 'babar-0.1' + distinfo_dir = os.path.join(self.fake_dists_path, + distinfo_name + '.dist-info') + dist = Distribution(distinfo_dir) + resource_path = dist.get_resource_path('babar.png') + self.assertEqual(resource_path, 'babar.png') + self.assertRaises(KeyError, dist.get_resource_path, 'notexist') + + class TestPkgUtilPEP376(support.LoggingCatcher, support.WarningsCatcher, unittest.TestCase): @@ -354,9 +368,6 @@ class TestPkgUtilPEP376(support.LoggingCatcher, support.WarningsCatcher, ('python-ldap', '2.5 a---5', 'python_ldap-2.5 a---5.dist-info'), ] - # Import the function in question - from distutils2._backport.pkgutil import distinfo_dirname - # Loop through the items to validate the results for name, version, standard_dirname in items: dirname = distinfo_dirname(name, version) @@ -366,23 +377,18 @@ class TestPkgUtilPEP376(support.LoggingCatcher, support.WarningsCatcher, # Lookup all distributions found in the ``sys.path``. # This test could potentially pick up other installed distributions fake_dists = [('grammar', '1.0a4'), ('choxie', '2.0.0.9'), - ('towel-stuff', '0.1')] + ('towel-stuff', '0.1'), ('babar', '0.1')] found_dists = [] - # Import the function in question - from distutils2._backport.pkgutil import get_distributions, \ - Distribution, \ - EggInfoDistribution - # Verify the fake dists have been found. dists = [dist for dist in get_distributions()] for dist in dists: if not isinstance(dist, Distribution): self.fail("item received was not a Distribution instance: " - "%s" % type(dist)) - if dist.name in dict(fake_dists).keys() and \ - dist.path.startswith(self.fake_dists_path): - found_dists.append((dist.name, dist.metadata['version'],)) + "%s" % type(dist)) + if dist.name in dict(fake_dists) and \ + dist.path.startswith(self.fake_dists_path): + found_dists.append((dist.name, dist.metadata['version'], )) else: # check that it doesn't find anything more than this self.assertFalse(dist.path.startswith(self.fake_dists_path)) @@ -393,6 +399,7 @@ class TestPkgUtilPEP376(support.LoggingCatcher, support.WarningsCatcher, # Now, test if the egg-info distributions are found correctly as well fake_dists += [('bacon', '0.1'), ('cheese', '2.0.2'), + ('coconuts-aster', '10.3'), ('banana', '0.4'), ('strawberry', '0.6'), ('truffles', '5.0'), ('nut', 'funkyversion')] found_dists = [] @@ -403,9 +410,9 @@ class TestPkgUtilPEP376(support.LoggingCatcher, support.WarningsCatcher, isinstance(dist, EggInfoDistribution)): self.fail("item received was not a Distribution or " "EggInfoDistribution instance: %s" % type(dist)) - if dist.name in dict(fake_dists).keys() and \ - dist.path.startswith(self.fake_dists_path): - found_dists.append((dist.name, dist.metadata['version'])) + if dist.name in dict(fake_dists) and \ + dist.path.startswith(self.fake_dists_path): + found_dists.append((dist.name, dist.metadata['version'])) else: self.assertFalse(dist.path.startswith(self.fake_dists_path)) @@ -414,12 +421,7 @@ class TestPkgUtilPEP376(support.LoggingCatcher, support.WarningsCatcher, def test_get_distribution(self): # Test for looking up a distribution by name. # Test the lookup of the towel-stuff distribution - name = 'towel-stuff' # Note: This is different from the directory name - - # Import the function in question - from distutils2._backport.pkgutil import get_distribution, \ - Distribution, \ - EggInfoDistribution + name = 'towel-stuff' # Note: This is different from the directory name # Lookup the distribution dist = get_distribution(name) @@ -459,19 +461,15 @@ class TestPkgUtilPEP376(support.LoggingCatcher, support.WarningsCatcher, def test_get_file_users(self): # Test the iteration of distributions that use a file. - from distutils2._backport.pkgutil import get_file_users, Distribution name = 'towel_stuff-0.1' path = os.path.join(self.fake_dists_path, name, - 'towel_stuff', '__init__.py') + 'towel_stuff', '__init__.py') for dist in get_file_users(path): self.assertTrue(isinstance(dist, Distribution)) self.assertEqual(dist.name, name) def test_provides(self): # Test for looking up distributions by what they provide - from distutils2._backport.pkgutil import provides_distribution - from distutils2.errors import DistutilsError - checkLists = lambda x, y: self.assertListEqual(sorted(x), sorted(y)) l = [dist.name for dist in provides_distribution('truffles')] @@ -507,33 +505,30 @@ class TestPkgUtilPEP376(support.LoggingCatcher, support.WarningsCatcher, l = [dist.name for dist in provides_distribution('truffles', '>1.5', use_egg_info=True)] - checkLists(l, ['bacon', 'truffles']) + checkLists(l, ['bacon']) l = [dist.name for dist in provides_distribution('truffles', '>=1.0')] checkLists(l, ['choxie', 'towel-stuff']) l = [dist.name for dist in provides_distribution('strawberry', '0.6', use_egg_info=True)] - checkLists(l, ['strawberry']) + checkLists(l, ['coconuts-aster']) l = [dist.name for dist in provides_distribution('strawberry', '>=0.5', use_egg_info=True)] - checkLists(l, ['strawberry']) - + checkLists(l, ['coconuts-aster']) l = [dist.name for dist in provides_distribution('strawberry', '>0.6', use_egg_info=True)] checkLists(l, []) - l = [dist.name for dist in provides_distribution('banana', '0.4', use_egg_info=True)] - checkLists(l, ['banana']) + checkLists(l, ['coconuts-aster']) l = [dist.name for dist in provides_distribution('banana', '>=0.3', use_egg_info=True)] - checkLists(l, ['banana']) - + checkLists(l, ['coconuts-aster']) l = [dist.name for dist in provides_distribution('banana', '!=0.4', use_egg_info=True)] @@ -541,9 +536,6 @@ class TestPkgUtilPEP376(support.LoggingCatcher, support.WarningsCatcher, def test_obsoletes(self): # Test looking for distributions based on what they obsolete - from distutils2._backport.pkgutil import obsoletes_distribution - from distutils2.errors import DistutilsError - checkLists = lambda x, y: self.assertListEqual(sorted(x), sorted(y)) l = [dist.name for dist in obsoletes_distribution('truffles', '1.0')] @@ -553,7 +545,6 @@ class TestPkgUtilPEP376(support.LoggingCatcher, support.WarningsCatcher, use_egg_info=True)] checkLists(l, ['cheese', 'bacon']) - l = [dist.name for dist in obsoletes_distribution('truffles', '0.8')] checkLists(l, ['choxie']) @@ -573,14 +564,13 @@ class TestPkgUtilPEP376(support.LoggingCatcher, support.WarningsCatcher, def test_yield_distribution(self): # tests the internal function _yield_distributions - from distutils2._backport.pkgutil import _yield_distributions checkLists = lambda x, y: self.assertListEqual(sorted(x), sorted(y)) eggs = [('bacon', '0.1'), ('banana', '0.4'), ('strawberry', '0.6'), ('truffles', '5.0'), ('cheese', '2.0.2'), - ('nut', 'funkyversion')] + ('coconuts-aster', '10.3'), ('nut', 'funkyversion')] dists = [('choxie', '2.0.0.9'), ('grammar', '1.0a4'), - ('towel-stuff', '0.1')] + ('towel-stuff', '0.1'), ('babar', '0.1')] checkLists([], _yield_distributions(False, False)) diff --git a/distutils2/_backport/tests/test_shutil.py b/distutils2/_backport/tests/test_shutil.py new file mode 100644 index 0000000..55e7a0e --- /dev/null +++ b/distutils2/_backport/tests/test_shutil.py @@ -0,0 +1,945 @@ +import os +import sys +import tempfile +import stat +import tarfile +from os.path import splitdrive +from StringIO import StringIO + +from distutils.spawn import find_executable, spawn +from distutils2._backport import shutil +from distutils2._backport.shutil import ( + _make_tarball, _make_zipfile, make_archive, unpack_archive, + register_archive_format, unregister_archive_format, get_archive_formats, + register_unpack_format, unregister_unpack_format, get_unpack_formats, + Error, RegistryError) + +from distutils2.tests import unittest, support, TESTFN + +try: + import bz2 + BZ2_SUPPORTED = True +except ImportError: + BZ2_SUPPORTED = False + +TESTFN2 = TESTFN + "2" + +try: + import grp + import pwd + UID_GID_SUPPORT = True +except ImportError: + UID_GID_SUPPORT = False + +try: + import zlib +except ImportError: + zlib = None + +try: + import zipfile + ZIP_SUPPORT = True +except ImportError: + ZIP_SUPPORT = find_executable('zip') + +class TestShutil(unittest.TestCase): + + def setUp(self): + super(TestShutil, self).setUp() + self.tempdirs = [] + + def tearDown(self): + super(TestShutil, self).tearDown() + while self.tempdirs: + d = self.tempdirs.pop() + shutil.rmtree(d, os.name in ('nt', 'cygwin')) + + def write_file(self, path, content='xxx'): + """Writes a file in the given path. + + + path can be a string or a sequence. + """ + if isinstance(path, (list, tuple)): + path = os.path.join(*path) + f = open(path, 'w') + try: + f.write(content) + finally: + f.close() + + def mkdtemp(self): + """Create a temporary directory that will be cleaned up. + + Returns the path of the directory. + """ + d = tempfile.mkdtemp() + self.tempdirs.append(d) + return d + + def test_rmtree_errors(self): + # filename is guaranteed not to exist + filename = tempfile.mktemp() + self.assertRaises(OSError, shutil.rmtree, filename) + + # See bug #1071513 for why we don't run this on cygwin + # and bug #1076467 for why we don't run this as root. + if (hasattr(os, 'chmod') and sys.platform[:6] != 'cygwin' + and not (hasattr(os, 'geteuid') and os.geteuid() == 0)): + def test_on_error(self): + self.errorState = 0 + os.mkdir(TESTFN) + self.childpath = os.path.join(TESTFN, 'a') + f = open(self.childpath, 'w') + f.close() + old_dir_mode = os.stat(TESTFN).st_mode + old_child_mode = os.stat(self.childpath).st_mode + # Make unwritable. + os.chmod(self.childpath, stat.S_IREAD) + os.chmod(TESTFN, stat.S_IREAD) + + shutil.rmtree(TESTFN, onerror=self.check_args_to_onerror) + # Test whether onerror has actually been called. + self.assertEqual(self.errorState, 2, + "Expected call to onerror function did not happen.") + + # Make writable again. + os.chmod(TESTFN, old_dir_mode) + os.chmod(self.childpath, old_child_mode) + + # Clean up. + shutil.rmtree(TESTFN) + + def check_args_to_onerror(self, func, arg, exc): + # test_rmtree_errors deliberately runs rmtree + # on a directory that is chmod 400, which will fail. + # This function is run when shutil.rmtree fails. + # 99.9% of the time it initially fails to remove + # a file in the directory, so the first time through + # func is os.remove. + # However, some Linux machines running ZFS on + # FUSE experienced a failure earlier in the process + # at os.listdir. The first failure may legally + # be either. + if self.errorState == 0: + if func is os.remove: + self.assertEqual(arg, self.childpath) + else: + self.assertIs(func, os.listdir, + "func must be either os.remove or os.listdir") + self.assertEqual(arg, TESTFN) + self.assertTrue(issubclass(exc[0], OSError)) + self.errorState = 1 + else: + self.assertEqual(func, os.rmdir) + self.assertEqual(arg, TESTFN) + self.assertTrue(issubclass(exc[0], OSError)) + self.errorState = 2 + + def test_rmtree_dont_delete_file(self): + # When called on a file instead of a directory, don't delete it. + handle, path = tempfile.mkstemp() + os.fdopen(handle).close() + self.assertRaises(OSError, shutil.rmtree, path) + os.remove(path) + + def _write_data(self, path, data): + f = open(path, "w") + f.write(data) + f.close() + + def test_copytree_simple(self): + + def read_data(path): + f = open(path) + data = f.read() + f.close() + return data + + src_dir = tempfile.mkdtemp() + dst_dir = os.path.join(tempfile.mkdtemp(), 'destination') + self._write_data(os.path.join(src_dir, 'test.txt'), '123') + os.mkdir(os.path.join(src_dir, 'test_dir')) + self._write_data(os.path.join(src_dir, 'test_dir', 'test.txt'), '456') + + try: + shutil.copytree(src_dir, dst_dir) + self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'test.txt'))) + self.assertTrue(os.path.isdir(os.path.join(dst_dir, 'test_dir'))) + self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'test_dir', + 'test.txt'))) + actual = read_data(os.path.join(dst_dir, 'test.txt')) + self.assertEqual(actual, '123') + actual = read_data(os.path.join(dst_dir, 'test_dir', 'test.txt')) + self.assertEqual(actual, '456') + finally: + for path in ( + os.path.join(src_dir, 'test.txt'), + os.path.join(dst_dir, 'test.txt'), + os.path.join(src_dir, 'test_dir', 'test.txt'), + os.path.join(dst_dir, 'test_dir', 'test.txt'), + ): + if os.path.exists(path): + os.remove(path) + for path in (src_dir, + os.path.dirname(dst_dir) + ): + if os.path.exists(path): + shutil.rmtree(path) + + def test_copytree_with_exclude(self): + + def read_data(path): + f = open(path) + data = f.read() + f.close() + return data + + # creating data + join = os.path.join + exists = os.path.exists + src_dir = tempfile.mkdtemp() + try: + dst_dir = join(tempfile.mkdtemp(), 'destination') + self._write_data(join(src_dir, 'test.txt'), '123') + self._write_data(join(src_dir, 'test.tmp'), '123') + os.mkdir(join(src_dir, 'test_dir')) + self._write_data(join(src_dir, 'test_dir', 'test.txt'), '456') + os.mkdir(join(src_dir, 'test_dir2')) + self._write_data(join(src_dir, 'test_dir2', 'test.txt'), '456') + os.mkdir(join(src_dir, 'test_dir2', 'subdir')) + os.mkdir(join(src_dir, 'test_dir2', 'subdir2')) + self._write_data(join(src_dir, 'test_dir2', 'subdir', 'test.txt'), + '456') + self._write_data(join(src_dir, 'test_dir2', 'subdir2', 'test.py'), + '456') + + + # testing glob-like patterns + try: + patterns = shutil.ignore_patterns('*.tmp', 'test_dir2') + shutil.copytree(src_dir, dst_dir, ignore=patterns) + # checking the result: some elements should not be copied + self.assertTrue(exists(join(dst_dir, 'test.txt'))) + self.assertTrue(not exists(join(dst_dir, 'test.tmp'))) + self.assertTrue(not exists(join(dst_dir, 'test_dir2'))) + finally: + if os.path.exists(dst_dir): + shutil.rmtree(dst_dir) + try: + patterns = shutil.ignore_patterns('*.tmp', 'subdir*') + shutil.copytree(src_dir, dst_dir, ignore=patterns) + # checking the result: some elements should not be copied + self.assertTrue(not exists(join(dst_dir, 'test.tmp'))) + self.assertTrue(not exists(join(dst_dir, 'test_dir2', 'subdir2'))) + self.assertTrue(not exists(join(dst_dir, 'test_dir2', 'subdir'))) + finally: + if os.path.exists(dst_dir): + shutil.rmtree(dst_dir) + + # testing callable-style + try: + def _filter(src, names): + res = [] + for name in names: + path = os.path.join(src, name) + + if (os.path.isdir(path) and + path.split()[-1] == 'subdir'): + res.append(name) + elif os.path.splitext(path)[-1] in ('.py'): + res.append(name) + return res + + shutil.copytree(src_dir, dst_dir, ignore=_filter) + + # checking the result: some elements should not be copied + self.assertTrue(not exists(join(dst_dir, 'test_dir2', 'subdir2', + 'test.py'))) + self.assertTrue(not exists(join(dst_dir, 'test_dir2', 'subdir'))) + + finally: + if os.path.exists(dst_dir): + shutil.rmtree(dst_dir) + finally: + shutil.rmtree(src_dir) + shutil.rmtree(os.path.dirname(dst_dir)) + + @support.skip_unless_symlink + def test_dont_copy_file_onto_link_to_itself(self): + # bug 851123. + os.mkdir(TESTFN) + src = os.path.join(TESTFN, 'cheese') + dst = os.path.join(TESTFN, 'shop') + try: + f = open(src, 'w') + f.write('cheddar') + f.close() + + if hasattr(os, "link"): + os.link(src, dst) + self.assertRaises(shutil.Error, shutil.copyfile, src, dst) + f = open(src, 'r') + try: + self.assertEqual(f.read(), 'cheddar') + finally: + f.close() + os.remove(dst) + + # Using `src` here would mean we end up with a symlink pointing + # to TESTFN/TESTFN/cheese, while it should point at + # TESTFN/cheese. + os.symlink('cheese', dst) + self.assertRaises(shutil.Error, shutil.copyfile, src, dst) + f = open(src, 'r') + try: + self.assertEqual(f.read(), 'cheddar') + finally: + f.close() + os.remove(dst) + finally: + try: + shutil.rmtree(TESTFN) + except OSError: + pass + + @support.skip_unless_symlink + def test_rmtree_on_symlink(self): + # bug 1669. + os.mkdir(TESTFN) + try: + src = os.path.join(TESTFN, 'cheese') + dst = os.path.join(TESTFN, 'shop') + os.mkdir(src) + os.symlink(src, dst) + self.assertRaises(OSError, shutil.rmtree, dst) + finally: + shutil.rmtree(TESTFN, ignore_errors=True) + + if hasattr(os, "mkfifo"): + # Issue #3002: copyfile and copytree block indefinitely on named pipes + def test_copyfile_named_pipe(self): + os.mkfifo(TESTFN) + try: + self.assertRaises(shutil.SpecialFileError, + shutil.copyfile, TESTFN, TESTFN2) + self.assertRaises(shutil.SpecialFileError, + shutil.copyfile, __file__, TESTFN) + finally: + os.remove(TESTFN) + + @unittest.skipUnless(hasattr(os, 'mkfifo'), 'requires os.mkfifo') + def test_copytree_named_pipe(self): + os.mkdir(TESTFN) + try: + subdir = os.path.join(TESTFN, "subdir") + os.mkdir(subdir) + pipe = os.path.join(subdir, "mypipe") + os.mkfifo(pipe) + try: + shutil.copytree(TESTFN, TESTFN2) + except shutil.Error, e: + errors = e.args[0] + self.assertEqual(len(errors), 1) + src, dst, error_msg = errors[0] + self.assertEqual("`%s` is a named pipe" % pipe, error_msg) + else: + self.fail("shutil.Error should have been raised") + finally: + shutil.rmtree(TESTFN, ignore_errors=True) + shutil.rmtree(TESTFN2, ignore_errors=True) + + def test_copytree_special_func(self): + + src_dir = self.mkdtemp() + dst_dir = os.path.join(self.mkdtemp(), 'destination') + self._write_data(os.path.join(src_dir, 'test.txt'), '123') + os.mkdir(os.path.join(src_dir, 'test_dir')) + self._write_data(os.path.join(src_dir, 'test_dir', 'test.txt'), '456') + + copied = [] + def _copy(src, dst): + copied.append((src, dst)) + + shutil.copytree(src_dir, dst_dir, copy_function=_copy) + self.assertEquals(len(copied), 2) + + @support.skip_unless_symlink + def test_copytree_dangling_symlinks(self): + + # a dangling symlink raises an error at the end + src_dir = self.mkdtemp() + dst_dir = os.path.join(self.mkdtemp(), 'destination') + os.symlink('IDONTEXIST', os.path.join(src_dir, 'test.txt')) + os.mkdir(os.path.join(src_dir, 'test_dir')) + self._write_data(os.path.join(src_dir, 'test_dir', 'test.txt'), '456') + self.assertRaises(Error, shutil.copytree, src_dir, dst_dir) + + # a dangling symlink is ignored with the proper flag + dst_dir = os.path.join(self.mkdtemp(), 'destination2') + shutil.copytree(src_dir, dst_dir, ignore_dangling_symlinks=True) + self.assertNotIn('test.txt', os.listdir(dst_dir)) + + # a dangling symlink is copied if symlinks=True + dst_dir = os.path.join(self.mkdtemp(), 'destination3') + shutil.copytree(src_dir, dst_dir, symlinks=True) + self.assertIn('test.txt', os.listdir(dst_dir)) + + @unittest.skipUnless(zlib, "requires zlib") + def test_make_tarball(self): + # creating something to tar + tmpdir = self.mkdtemp() + self.write_file([tmpdir, 'file1'], 'xxx') + self.write_file([tmpdir, 'file2'], 'xxx') + os.mkdir(os.path.join(tmpdir, 'sub')) + self.write_file([tmpdir, 'sub', 'file3'], 'xxx') + + tmpdir2 = self.mkdtemp() + unittest.skipUnless(splitdrive(tmpdir)[0] == splitdrive(tmpdir2)[0], + "source and target should be on same drive") + + base_name = os.path.join(tmpdir2, 'archive') + + # working with relative paths to avoid tar warnings + old_dir = os.getcwd() + os.chdir(tmpdir) + try: + _make_tarball(splitdrive(base_name)[1], '.') + finally: + os.chdir(old_dir) + + # check if the compressed tarball was created + tarball = base_name + '.tar.gz' + self.assertTrue(os.path.exists(tarball)) + + # trying an uncompressed one + base_name = os.path.join(tmpdir2, 'archive') + old_dir = os.getcwd() + os.chdir(tmpdir) + try: + _make_tarball(splitdrive(base_name)[1], '.', compress=None) + finally: + os.chdir(old_dir) + tarball = base_name + '.tar' + self.assertTrue(os.path.exists(tarball)) + + def _tarinfo(self, path): + tar = tarfile.open(path) + try: + names = tar.getnames() + names.sort() + return tuple(names) + finally: + tar.close() + + def _create_files(self): + # creating something to tar + tmpdir = self.mkdtemp() + dist = os.path.join(tmpdir, 'dist') + os.mkdir(dist) + self.write_file([dist, 'file1'], 'xxx') + self.write_file([dist, 'file2'], 'xxx') + os.mkdir(os.path.join(dist, 'sub')) + self.write_file([dist, 'sub', 'file3'], 'xxx') + os.mkdir(os.path.join(dist, 'sub2')) + tmpdir2 = self.mkdtemp() + base_name = os.path.join(tmpdir2, 'archive') + return tmpdir, tmpdir2, base_name + + @unittest.skipUnless(zlib, "Requires zlib") + @unittest.skipUnless(find_executable('tar') and find_executable('gzip'), + 'Need the tar command to run') + def test_tarfile_vs_tar(self): + tmpdir, tmpdir2, base_name = self._create_files() + old_dir = os.getcwd() + os.chdir(tmpdir) + try: + _make_tarball(base_name, 'dist') + finally: + os.chdir(old_dir) + + # check if the compressed tarball was created + tarball = base_name + '.tar.gz' + self.assertTrue(os.path.exists(tarball)) + + # now create another tarball using `tar` + tarball2 = os.path.join(tmpdir, 'archive2.tar.gz') + tar_cmd = ['tar', '-cf', 'archive2.tar', 'dist'] + gzip_cmd = ['gzip', '-f9', 'archive2.tar'] + old_dir = os.getcwd() + old_stdout = sys.stdout + os.chdir(tmpdir) + sys.stdout = StringIO() + + try: + spawn(tar_cmd) + spawn(gzip_cmd) + finally: + os.chdir(old_dir) + sys.stdout = old_stdout + + self.assertTrue(os.path.exists(tarball2)) + # let's compare both tarballs + self.assertEquals(self._tarinfo(tarball), self._tarinfo(tarball2)) + + # trying an uncompressed one + base_name = os.path.join(tmpdir2, 'archive') + old_dir = os.getcwd() + os.chdir(tmpdir) + try: + _make_tarball(base_name, 'dist', compress=None) + finally: + os.chdir(old_dir) + tarball = base_name + '.tar' + self.assertTrue(os.path.exists(tarball)) + + # now for a dry_run + base_name = os.path.join(tmpdir2, 'archive') + old_dir = os.getcwd() + os.chdir(tmpdir) + try: + _make_tarball(base_name, 'dist', compress=None, dry_run=True) + finally: + os.chdir(old_dir) + tarball = base_name + '.tar' + self.assertTrue(os.path.exists(tarball)) + + @unittest.skipUnless(zlib, "Requires zlib") + @unittest.skipUnless(ZIP_SUPPORT, 'Need zip support to run') + def test_make_zipfile(self): + # creating something to tar + tmpdir = self.mkdtemp() + self.write_file([tmpdir, 'file1'], 'xxx') + self.write_file([tmpdir, 'file2'], 'xxx') + + tmpdir2 = self.mkdtemp() + base_name = os.path.join(tmpdir2, 'archive') + _make_zipfile(base_name, tmpdir) + + # check if the compressed tarball was created + tarball = base_name + '.zip' + self.assertTrue(os.path.exists(tarball)) + + + def test_make_archive(self): + tmpdir = self.mkdtemp() + base_name = os.path.join(tmpdir, 'archive') + self.assertRaises(ValueError, make_archive, base_name, 'xxx') + + @unittest.skipUnless(zlib, "Requires zlib") + def test_make_archive_owner_group(self): + # testing make_archive with owner and group, with various combinations + # this works even if there's not gid/uid support + if UID_GID_SUPPORT: + group = grp.getgrgid(0)[0] + owner = pwd.getpwuid(0)[0] + else: + group = owner = 'root' + + base_dir, root_dir, base_name = self._create_files() + base_name = os.path.join(self.mkdtemp() , 'archive') + res = make_archive(base_name, 'zip', root_dir, base_dir, owner=owner, + group=group) + self.assertTrue(os.path.exists(res)) + + res = make_archive(base_name, 'zip', root_dir, base_dir) + self.assertTrue(os.path.exists(res)) + + res = make_archive(base_name, 'tar', root_dir, base_dir, + owner=owner, group=group) + self.assertTrue(os.path.exists(res)) + + res = make_archive(base_name, 'tar', root_dir, base_dir, + owner='kjhkjhkjg', group='oihohoh') + self.assertTrue(os.path.exists(res)) + + + @unittest.skipUnless(zlib, "Requires zlib") + @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support") + def test_tarfile_root_owner(self): + tmpdir, tmpdir2, base_name = self._create_files() + old_dir = os.getcwd() + os.chdir(tmpdir) + group = grp.getgrgid(0)[0] + owner = pwd.getpwuid(0)[0] + try: + archive_name = _make_tarball(base_name, 'dist', compress=None, + owner=owner, group=group) + finally: + os.chdir(old_dir) + + # check if the compressed tarball was created + self.assertTrue(os.path.exists(archive_name)) + + # now checks the rights + archive = tarfile.open(archive_name) + try: + for member in archive.getmembers(): + self.assertEquals(member.uid, 0) + self.assertEquals(member.gid, 0) + finally: + archive.close() + + def test_make_archive_cwd(self): + current_dir = os.getcwd() + def _breaks(*args, **kw): + raise RuntimeError() + + register_archive_format('xxx', _breaks, [], 'xxx file') + try: + try: + make_archive('xxx', 'xxx', root_dir=self.mkdtemp()) + except Exception: + pass + self.assertEquals(os.getcwd(), current_dir) + finally: + unregister_archive_format('xxx') + + def test_register_archive_format(self): + + self.assertRaises(TypeError, register_archive_format, 'xxx', 1) + self.assertRaises(TypeError, register_archive_format, 'xxx', lambda: x, + 1) + self.assertRaises(TypeError, register_archive_format, 'xxx', lambda: x, + [(1, 2), (1, 2, 3)]) + + register_archive_format('xxx', lambda: x, [(1, 2)], 'xxx file') + formats = [name for name, params in get_archive_formats()] + self.assertIn('xxx', formats) + + unregister_archive_format('xxx') + formats = [name for name, params in get_archive_formats()] + self.assertNotIn('xxx', formats) + + def _compare_dirs(self, dir1, dir2): + # check that dir1 and dir2 are equivalent, + # return the diff + diff = [] + for root, dirs, files in os.walk(dir1): + for file_ in files: + path = os.path.join(root, file_) + target_path = os.path.join(dir2, os.path.split(path)[-1]) + if not os.path.exists(target_path): + diff.append(file_) + return diff + + @unittest.skipUnless(zlib, "Requires zlib") + def test_unpack_archive(self): + formats = ['tar', 'gztar', 'zip'] + if BZ2_SUPPORTED: + formats.append('bztar') + + for format in formats: + tmpdir = self.mkdtemp() + base_dir, root_dir, base_name = self._create_files() + tmpdir2 = self.mkdtemp() + filename = make_archive(base_name, format, root_dir, base_dir) + + # let's try to unpack it now + unpack_archive(filename, tmpdir2) + diff = self._compare_dirs(tmpdir, tmpdir2) + self.assertEquals(diff, []) + + def test_unpack_registery(self): + + formats = get_unpack_formats() + + def _boo(filename, extract_dir, extra): + self.assertEquals(extra, 1) + self.assertEquals(filename, 'stuff.boo') + self.assertEquals(extract_dir, 'xx') + + register_unpack_format('Boo', ['.boo', '.b2'], _boo, [('extra', 1)]) + unpack_archive('stuff.boo', 'xx') + + # trying to register a .boo unpacker again + self.assertRaises(RegistryError, register_unpack_format, 'Boo2', + ['.boo'], _boo) + + # should work now + unregister_unpack_format('Boo') + register_unpack_format('Boo2', ['.boo'], _boo) + self.assertIn(('Boo2', ['.boo'], ''), get_unpack_formats()) + self.assertNotIn(('Boo', ['.boo'], ''), get_unpack_formats()) + + # let's leave a clean state + unregister_unpack_format('Boo2') + self.assertEquals(get_unpack_formats(), formats) + + +class TestMove(unittest.TestCase): + + def setUp(self): + filename = "foo" + self.src_dir = tempfile.mkdtemp() + self.dst_dir = tempfile.mkdtemp() + self.src_file = os.path.join(self.src_dir, filename) + self.dst_file = os.path.join(self.dst_dir, filename) + # Try to create a dir in the current directory, hoping that it is + # not located on the same filesystem as the system tmp dir. + try: + self.dir_other_fs = tempfile.mkdtemp( + dir=os.path.dirname(__file__)) + self.file_other_fs = os.path.join(self.dir_other_fs, + filename) + except OSError: + self.dir_other_fs = None + f = open(self.src_file, "wb") + try: + f.write("spam") + finally: + f.close() + + def tearDown(self): + for d in (self.src_dir, self.dst_dir, self.dir_other_fs): + try: + if d: + shutil.rmtree(d) + except: + pass + + def _check_move_file(self, src, dst, real_dst): + f = open(src, "rb") + try: + contents = f.read() + finally: + f.close() + + shutil.move(src, dst) + f = open(real_dst, "rb") + try: + self.assertEqual(contents, f.read()) + finally: + f.close() + + self.assertFalse(os.path.exists(src)) + + def _check_move_dir(self, src, dst, real_dst): + contents = sorted(os.listdir(src)) + shutil.move(src, dst) + self.assertEqual(contents, sorted(os.listdir(real_dst))) + self.assertFalse(os.path.exists(src)) + + def test_move_file(self): + # Move a file to another location on the same filesystem. + self._check_move_file(self.src_file, self.dst_file, self.dst_file) + + def test_move_file_to_dir(self): + # Move a file inside an existing dir on the same filesystem. + self._check_move_file(self.src_file, self.dst_dir, self.dst_file) + + def test_move_file_other_fs(self): + # Move a file to an existing dir on another filesystem. + if not self.dir_other_fs: + # skip + return + self._check_move_file(self.src_file, self.file_other_fs, + self.file_other_fs) + + def test_move_file_to_dir_other_fs(self): + # Move a file to another location on another filesystem. + if not self.dir_other_fs: + # skip + return + self._check_move_file(self.src_file, self.dir_other_fs, + self.file_other_fs) + + def test_move_dir(self): + # Move a dir to another location on the same filesystem. + dst_dir = tempfile.mktemp() + try: + self._check_move_dir(self.src_dir, dst_dir, dst_dir) + finally: + try: + shutil.rmtree(dst_dir) + except: + pass + + def test_move_dir_other_fs(self): + # Move a dir to another location on another filesystem. + if not self.dir_other_fs: + # skip + return + dst_dir = tempfile.mktemp(dir=self.dir_other_fs) + try: + self._check_move_dir(self.src_dir, dst_dir, dst_dir) + finally: + try: + shutil.rmtree(dst_dir) + except: + pass + + def test_move_dir_to_dir(self): + # Move a dir inside an existing dir on the same filesystem. + self._check_move_dir(self.src_dir, self.dst_dir, + os.path.join(self.dst_dir, os.path.basename(self.src_dir))) + + def test_move_dir_to_dir_other_fs(self): + # Move a dir inside an existing dir on another filesystem. + if not self.dir_other_fs: + # skip + return + self._check_move_dir(self.src_dir, self.dir_other_fs, + os.path.join(self.dir_other_fs, os.path.basename(self.src_dir))) + + def test_existing_file_inside_dest_dir(self): + # A file with the same name inside the destination dir already exists. + f = open(self.dst_file, "wb") + try: + pass + finally: + f.close() + self.assertRaises(shutil.Error, shutil.move, self.src_file, self.dst_dir) + + def test_dont_move_dir_in_itself(self): + # Moving a dir inside itself raises an Error. + dst = os.path.join(self.src_dir, "bar") + self.assertRaises(shutil.Error, shutil.move, self.src_dir, dst) + + def test_destinsrc_false_negative(self): + os.mkdir(TESTFN) + try: + for src, dst in [('srcdir', 'srcdir/dest')]: + src = os.path.join(TESTFN, src) + dst = os.path.join(TESTFN, dst) + self.assertTrue(shutil._destinsrc(src, dst), + msg='_destinsrc() wrongly concluded that ' + 'dst (%s) is not in src (%s)' % (dst, src)) + finally: + shutil.rmtree(TESTFN, ignore_errors=True) + + def test_destinsrc_false_positive(self): + os.mkdir(TESTFN) + try: + for src, dst in [('srcdir', 'src/dest'), ('srcdir', 'srcdir.new')]: + src = os.path.join(TESTFN, src) + dst = os.path.join(TESTFN, dst) + self.assertFalse(shutil._destinsrc(src, dst), + msg='_destinsrc() wrongly concluded that ' + 'dst (%s) is in src (%s)' % (dst, src)) + finally: + shutil.rmtree(TESTFN, ignore_errors=True) + + +class TestCopyFile(unittest.TestCase): + + _delete = False + + class Faux(object): + _entered = False + _exited_with = None + _raised = False + + def __init__(self, raise_in_exit=False, suppress_at_exit=True): + self._raise_in_exit = raise_in_exit + self._suppress_at_exit = suppress_at_exit + + def read(self, *args): + return '' + + def __enter__(self): + self._entered = True + + def __exit__(self, exc_type, exc_val, exc_tb): + self._exited_with = exc_type, exc_val, exc_tb + if self._raise_in_exit: + self._raised = True + raise IOError("Cannot close") + return self._suppress_at_exit + + def tearDown(self): + if self._delete: + del shutil.open + + def _set_shutil_open(self, func): + shutil.open = func + self._delete = True + + def test_w_source_open_fails(self): + def _open(filename, mode='r'): + if filename == 'srcfile': + raise IOError('Cannot open "srcfile"') + assert 0 # shouldn't reach here. + + self._set_shutil_open(_open) + + self.assertRaises(IOError, shutil.copyfile, 'srcfile', 'destfile') + + @unittest.skip("can't use the with statement and support 2.4") + def test_w_dest_open_fails(self): + + srcfile = self.Faux() + + def _open(filename, mode='r'): + if filename == 'srcfile': + return srcfile + if filename == 'destfile': + raise IOError('Cannot open "destfile"') + assert 0 # shouldn't reach here. + + self._set_shutil_open(_open) + + shutil.copyfile('srcfile', 'destfile') + self.assertTrue(srcfile._entered) + self.assertTrue(srcfile._exited_with[0] is IOError) + self.assertEqual(srcfile._exited_with[1].args, + ('Cannot open "destfile"',)) + + @unittest.skip("can't use the with statement and support 2.4") + def test_w_dest_close_fails(self): + + srcfile = self.Faux() + destfile = self.Faux(True) + + def _open(filename, mode='r'): + if filename == 'srcfile': + return srcfile + if filename == 'destfile': + return destfile + assert 0 # shouldn't reach here. + + self._set_shutil_open(_open) + + shutil.copyfile('srcfile', 'destfile') + self.assertTrue(srcfile._entered) + self.assertTrue(destfile._entered) + self.assertTrue(destfile._raised) + self.assertTrue(srcfile._exited_with[0] is IOError) + self.assertEqual(srcfile._exited_with[1].args, + ('Cannot close',)) + + @unittest.skip("can't use the with statement and support 2.4") + def test_w_source_close_fails(self): + + srcfile = self.Faux(True) + destfile = self.Faux() + + def _open(filename, mode='r'): + if filename == 'srcfile': + return srcfile + if filename == 'destfile': + return destfile + assert 0 # shouldn't reach here. + + self._set_shutil_open(_open) + + self.assertRaises(IOError, + shutil.copyfile, 'srcfile', 'destfile') + self.assertTrue(srcfile._entered) + self.assertTrue(destfile._entered) + self.assertFalse(destfile._raised) + self.assertTrue(srcfile._exited_with[0] is None) + self.assertTrue(srcfile._raised) + + +def test_suite(): + suite = unittest.TestSuite() + load = unittest.defaultTestLoader.loadTestsFromTestCase + suite.addTest(load(TestCopyFile)) + suite.addTest(load(TestMove)) + suite.addTest(load(TestShutil)) + return suite + + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/distutils2/_backport/tests/test_sysconfig.py b/distutils2/_backport/tests/test_sysconfig.py index 9e844e1..702281f 100644 --- a/distutils2/_backport/tests/test_sysconfig.py +++ b/distutils2/_backport/tests/test_sysconfig.py @@ -4,7 +4,7 @@ import os import sys import subprocess import shutil -from copy import copy, deepcopy +from copy import copy from ConfigParser import RawConfigParser from StringIO import StringIO @@ -16,6 +16,7 @@ from distutils2._backport.sysconfig import ( from distutils2.tests import unittest, TESTFN, unlink from distutils2.tests.support import EnvironGuard +from test.test_support import TESTFN, unlink try: from test.test_support import skip_unless_symlink |
