Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Switch to using a fully vendored distlib inside of pip.vendor

* Includes helper scripts to maintaing pip.vendor
  • Loading branch information...
commit 9936d6b42483a17abfcbb9f1fbbf349b35244b83 1 parent e18314e
@dstufft dstufft authored
Showing with 24,246 additions and 230 deletions.
  1. +0 −1  .travis.yml
  2. +0 −1  pip/distlib/__init__.py
  3. +0 −222 pip/distlib/version.py
  4. +4 −4 pip/util.py
  5. +12 −0 pip/vendor/Makefile
  6. +7 −0 pip/vendor/__init__.py
  7. +22 −0 pip/vendor/distlib/__init__.py
  8. +6 −0 pip/vendor/distlib/_backport/__init__.py
  9. +41 −0 pip/vendor/distlib/_backport/misc.py
  10. +761 −0 pip/vendor/distlib/_backport/shutil.py
  11. +787 −0 pip/vendor/distlib/_backport/sysconfig.py
  12. +2,607 −0 pip/vendor/distlib/_backport/tarfile.py
  13. +754 −0 pip/vendor/distlib/compat.py
  14. +1,261 −0 pip/vendor/distlib/database.py
  15. +22 −0 pip/vendor/distlib/distlib/__init__.py
  16. +6 −0 pip/vendor/distlib/distlib/_backport/__init__.py
  17. +41 −0 pip/vendor/distlib/distlib/_backport/misc.py
  18. +761 −0 pip/vendor/distlib/distlib/_backport/shutil.py
  19. +787 −0 pip/vendor/distlib/distlib/_backport/sysconfig.py
  20. +2,607 −0 pip/vendor/distlib/distlib/_backport/tarfile.py
  21. +754 −0 pip/vendor/distlib/distlib/compat.py
  22. +1,261 −0 pip/vendor/distlib/distlib/database.py
  23. +515 −0 pip/vendor/distlib/distlib/index.py
  24. +1,128 −0 pip/vendor/distlib/distlib/locators.py
  25. +361 −0 pip/vendor/distlib/distlib/manifest.py
  26. +189 −0 pip/vendor/distlib/distlib/markers.py
  27. +631 −0 pip/vendor/distlib/distlib/metadata.py
  28. +312 −0 pip/vendor/distlib/distlib/resources.py
  29. +253 −0 pip/vendor/distlib/distlib/scripts.py
  30. +1,351 −0 pip/vendor/distlib/distlib/util.py
  31. +635 −0 pip/vendor/distlib/distlib/version.py
  32. +496 −0 pip/vendor/distlib/distlib/wheel.py
  33. +515 −0 pip/vendor/distlib/index.py
  34. +1,128 −0 pip/vendor/distlib/locators.py
  35. +361 −0 pip/vendor/distlib/manifest.py
  36. +189 −0 pip/vendor/distlib/markers.py
  37. +631 −0 pip/vendor/distlib/metadata.py
  38. +312 −0 pip/vendor/distlib/resources.py
  39. +253 −0 pip/vendor/distlib/scripts.py
  40. +1,351 −0 pip/vendor/distlib/util.py
  41. +635 −0 pip/vendor/distlib/version.py
  42. +496 −0 pip/vendor/distlib/wheel.py
  43. +1 −0  pip/vendor/vendor.txt
  44. +2 −2 setup.py
View
1  .travis.yml
@@ -1,6 +1,5 @@
language: python
python:
- - 2.5
- 2.6
- 2.7
- 3.2
View
1  pip/distlib/__init__.py
@@ -1 +0,0 @@
-from . import version
View
222 pip/distlib/version.py
@@ -1,222 +0,0 @@
-# Taken from distlib so we can easily determine what a pre-release is.
-# It has been stripped down to only the parts needed for that purpose.
-import re
-
-
-class UnsupportedVersionError(Exception):
- """This is an unsupported version."""
- pass
-
-
-class HugeMajorVersionError(UnsupportedVersionError):
- """An irrational version because the major version number is huge
- (often because a year or date was used).
-
- See `error_on_huge_major_num` option in `NormalizedVersion` for details.
- This guard can be disabled by setting that option False.
- """
- pass
-
-
-# A marker used in the second and third parts of the `parts` tuple, for
-# versions that don't have those segments, to sort properly. An example
-# of versions in sort order ('highest' last):
-# 1.0b1 ((1,0), ('b',1), ('z',))
-# 1.0.dev345 ((1,0), ('z',), ('dev', 345))
-# 1.0 ((1,0), ('z',), ('z',))
-# 1.0.post256.dev345 ((1,0), ('z',), ('z', 'post', 256, 'dev', 345))
-# 1.0.post345 ((1,0), ('z',), ('z', 'post', 345, 'z'))
-# ^ ^ ^
-# 'b' < 'z' ---------------------/ | |
-# | |
-# 'dev' < 'z' ----------------------------/ |
-# |
-# 'dev' < 'z' ----------------------------------------------/
-# 'f' for 'final' would be kind of nice, but due to bugs in the support of
-# 'rc' we must use 'z'
-_FINAL_MARKER = ('z',)
-
-_VERSION_RE = re.compile(r'''
- ^
- (?P<version>\d+\.\d+(\.\d+)*) # minimum 'N.N'
- (?:
- (?P<prerel>[abc]|rc) # 'a'=alpha, 'b'=beta, 'c'=release candidate
- # 'rc'= alias for release candidate
- (?P<prerelversion>\d+(?:\.\d+)*)
- )?
- (?P<postdev>(\.post(?P<post>\d+))?(\.dev(?P<dev>\d+))?)?
- $''', re.VERBOSE)
-
-
-def _parse_numdots(s, full_ver, drop_zeroes=False, min_length=0):
- """Parse 'N.N.N' sequences, return a list of ints.
-
- @param s {str} 'N.N.N...' sequence to be parsed
- @param full_ver_str {str} The full version string from which this
- comes. Used for error strings.
- @param min_length {int} The length to which to pad the
- returned list with zeros, if necessary. Default 0.
- """
- result = []
- for n in s.split("."):
- #if len(n) > 1 and n[0] == '0':
- # raise UnsupportedVersionError("cannot have leading zero in "
- # "version number segment: '%s' in %r" % (n, full_ver))
- result.append(int(n))
- if drop_zeroes:
- while (result and result[-1] == 0 and
- (1 + len(result)) > min_length):
- result.pop()
- return result
-
-
-def normalized_key(s, fail_on_huge_major_ver=True):
- match = _VERSION_RE.search(s)
- if not match:
- raise UnsupportedVersionError(s)
-
- groups = match.groupdict()
- parts = []
-
- # main version
- block = _parse_numdots(groups['version'], s, min_length=2)
- parts.append(tuple(block))
-
- # prerelease
- prerel = groups.get('prerel')
- if prerel is not None:
- block = [prerel]
- block += _parse_numdots(groups.get('prerelversion'), s, min_length=1)
- parts.append(tuple(block))
- else:
- parts.append(_FINAL_MARKER)
-
- # postdev
- if groups.get('postdev'):
- post = groups.get('post')
- dev = groups.get('dev')
- postdev = []
- if post is not None:
- postdev.extend((_FINAL_MARKER[0], 'post', int(post)))
- if dev is None:
- postdev.append(_FINAL_MARKER[0])
- if dev is not None:
- postdev.extend(('dev', int(dev)))
- parts.append(tuple(postdev))
- else:
- parts.append(_FINAL_MARKER)
- if fail_on_huge_major_ver and parts[0][0] > 1980:
- raise HugeMajorVersionError("huge major version number, %r, "
- "which might cause future problems: %r" % (parts[0][0], s))
- return tuple(parts)
-
-
-def suggest_normalized_version(s):
- """Suggest a normalized version close to the given version string.
-
- If you have a version string that isn't rational (i.e. NormalizedVersion
- doesn't like it) then you might be able to get an equivalent (or close)
- rational version from this function.
-
- This does a number of simple normalizations to the given string, based
- on observation of versions currently in use on PyPI. Given a dump of
- those version during PyCon 2009, 4287 of them:
- - 2312 (53.93%) match NormalizedVersion without change
- with the automatic suggestion
- - 3474 (81.04%) match when using this suggestion method
-
- @param s {str} An irrational version string.
- @returns A rational version string, or None, if couldn't determine one.
- """
- try:
- normalized_key(s)
- return s # already rational
- except UnsupportedVersionError:
- pass
-
- rs = s.lower()
-
- # part of this could use maketrans
- for orig, repl in (('-alpha', 'a'), ('-beta', 'b'), ('alpha', 'a'),
- ('beta', 'b'), ('rc', 'c'), ('-final', ''),
- ('-pre', 'c'),
- ('-release', ''), ('.release', ''), ('-stable', ''),
- ('+', '.'), ('_', '.'), (' ', ''), ('.final', ''),
- ('final', '')):
- rs = rs.replace(orig, repl)
-
- # if something ends with dev or pre, we add a 0
- rs = re.sub(r"pre$", r"pre0", rs)
- rs = re.sub(r"dev$", r"dev0", rs)
-
- # if we have something like "b-2" or "a.2" at the end of the
- # version, that is pobably beta, alpha, etc
- # let's remove the dash or dot
- rs = re.sub(r"([abc]|rc)[\-\.](\d+)$", r"\1\2", rs)
-
- # 1.0-dev-r371 -> 1.0.dev371
- # 0.1-dev-r79 -> 0.1.dev79
- rs = re.sub(r"[\-\.](dev)[\-\.]?r?(\d+)$", r".\1\2", rs)
-
- # Clean: 2.0.a.3, 2.0.b1, 0.9.0~c1
- rs = re.sub(r"[.~]?([abc])\.?", r"\1", rs)
-
- # Clean: v0.3, v1.0
- if rs.startswith('v'):
- rs = rs[1:]
-
- # Clean leading '0's on numbers.
- #TODO: unintended side-effect on, e.g., "2003.05.09"
- # PyPI stats: 77 (~2%) better
- rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs)
-
- # Clean a/b/c with no version. E.g. "1.0a" -> "1.0a0". Setuptools infers
- # zero.
- # PyPI stats: 245 (7.56%) better
- rs = re.sub(r"(\d+[abc])$", r"\g<1>0", rs)
-
- # the 'dev-rNNN' tag is a dev tag
- rs = re.sub(r"\.?(dev-r|dev\.r)\.?(\d+)$", r".dev\2", rs)
-
- # clean the - when used as a pre delimiter
- rs = re.sub(r"-(a|b|c)(\d+)$", r"\1\2", rs)
-
- # a terminal "dev" or "devel" can be changed into ".dev0"
- rs = re.sub(r"[\.\-](dev|devel)$", r".dev0", rs)
-
- # a terminal "dev" can be changed into ".dev0"
- rs = re.sub(r"(?![\.\-])dev$", r".dev0", rs)
-
- # a terminal "final" or "stable" can be removed
- rs = re.sub(r"(final|stable)$", "", rs)
-
- # The 'r' and the '-' tags are post release tags
- # 0.4a1.r10 -> 0.4a1.post10
- # 0.9.33-17222 -> 0.9.33.post17222
- # 0.9.33-r17222 -> 0.9.33.post17222
- rs = re.sub(r"\.?(r|-|-r)\.?(\d+)$", r".post\2", rs)
-
- # Clean 'r' instead of 'dev' usage:
- # 0.9.33+r17222 -> 0.9.33.dev17222
- # 1.0dev123 -> 1.0.dev123
- # 1.0.git123 -> 1.0.dev123
- # 1.0.bzr123 -> 1.0.dev123
- # 0.1a0dev.123 -> 0.1a0.dev123
- # PyPI stats: ~150 (~4%) better
- rs = re.sub(r"\.?(dev|git|bzr)\.?(\d+)$", r".dev\2", rs)
-
- # Clean '.pre' (normalized from '-pre' above) instead of 'c' usage:
- # 0.2.pre1 -> 0.2c1
- # 0.2-c1 -> 0.2c1
- # 1.0preview123 -> 1.0c123
- # PyPI stats: ~21 (0.62%) better
- rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs)
-
- # Tcl/Tk uses "px" for their post release markers
- rs = re.sub(r"p(\d+)$", r".post\1", rs)
-
- try:
- normalized_key(rs)
- except UnsupportedVersionError:
- rs = None
- return rs
View
8 pip/util.py
@@ -14,7 +14,7 @@
console_to_str, user_site, ssl)
from pip.locations import site_packages, running_under_virtualenv, virtualenv_no_global
from pip.log import logger
-from pip import distlib
+from pip.vendor.distlib import version
__all__ = ['rmtree', 'display_path', 'backup_dir',
'find_command', 'ask', 'Inf',
@@ -669,18 +669,18 @@ def call_subprocess(cmd, show_stdout=True,
return ''.join(all_output)
-def is_prerelease(version):
+def is_prerelease(vers):
"""
Attempt to determine if this is a pre-release using PEP386/PEP426 rules.
Will return True if it is a pre-release, False is not, and None if we cannot
determine.
"""
- normalized = distlib.version.suggest_normalized_version(version)
+ normalized = version.suggest_normalized_version(vers)
if normalized is None:
# Cannot normalize
return
- parsed = distlib.version.normalized_key(normalized)
+ parsed = version.normalized_key(normalized)
return any([any([y in set(["a", "b", "c", "rc", "dev"]) for y in x]) for x in parsed])
View
12 pip/vendor/Makefile
@@ -0,0 +1,12 @@
+all: clean vendor
+
+clean:
+ @# Delete vendored items
+ for i in `find . -maxdepth 1 -type d -print`; do rm -rf $i; done
+
+vendor:
+ @# Install vendored libraries
+ pip install -t . -r vendor.txt
+
+ @# Cleanup .egg-info directories
+ rm -rf *.egg-info
View
7 pip/vendor/__init__.py
@@ -0,0 +1,7 @@
+"""
+pip.vendor is for vendoring dependencies of pip to prevent needing pip to
+depend on something external.
+
+Files inside of pip.vendor should be considered immutable and should only be
+updated to versions from upstream.
+"""
View
22 pip/vendor/distlib/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012-2013 Vinay Sajip.
+# Licensed to the Python Software Foundation under a contributor agreement.
+# See LICENSE.txt and CONTRIBUTORS.txt.
+#
+import logging
+
+__version__ = '0.1.0'
+
+class DistlibException(Exception):
+ pass
+
+try:
+ from logging import NullHandler
+except ImportError: # pragma: no cover
+ class NullHandler(logging.Handler):
+ def handle(self, record): pass
+ def emit(self, record): pass
+
+logger = logging.getLogger(__name__)
+logger.addHandler(NullHandler())
View
6 pip/vendor/distlib/_backport/__init__.py
@@ -0,0 +1,6 @@
+"""Modules copied from Python 3 standard libraries, for internal use only.
+
+Individual classes and functions are found in d2._backport.misc. Intended
+usage is to always import things missing from 3.1 from that module: the
+built-in/stdlib objects will be used if found.
+"""
View
41 pip/vendor/distlib/_backport/misc.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012 The Python Software Foundation.
+# See LICENSE.txt and CONTRIBUTORS.txt.
+#
+"""Backports for individual classes and functions."""
+
+import os
+import sys
+
+__all__ = ['cache_from_source', 'callable', 'fsencode']
+
+
+try:
+ from imp import cache_from_source
+except ImportError:
+ def cache_from_source(py_file, debug=__debug__):
+ ext = debug and 'c' or 'o'
+ return py_file + ext
+
+
+try:
+ callable = callable
+except NameError:
+ from collections import Callable
+
+ def callable(obj):
+ return isinstance(obj, Callable)
+
+
+try:
+ fsencode = os.fsencode
+except AttributeError:
+ def fsencode(filename):
+ if isinstance(filename, bytes):
+ return filename
+ elif isinstance(filename, str):
+ return filename.encode(sys.getfilesystemencoding())
+ else:
+ raise TypeError("expect bytes or str, not %s" %
+ type(filename).__name__)
View
761 pip/vendor/distlib/_backport/shutil.py
@@ -0,0 +1,761 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012 The Python Software Foundation.
+# See LICENSE.txt and CONTRIBUTORS.txt.
+#
+"""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.
+
+"""
+
+import os
+import sys
+import stat
+from os.path import abspath
+import fnmatch
+import collections
+import errno
+from . import tarfile
+
+try:
+ import bz2
+ _BZ2_SUPPORTED = True
+except ImportError:
+ _BZ2_SUPPORTED = False
+
+try:
+ from pwd import getpwnam
+except ImportError:
+ getpwnam = None
+
+try:
+ from grp import getgrnam
+except ImportError:
+ getgrnam = None
+
+__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", "ignore_patterns"]
+
+class Error(EnvironmentError):
+ pass
+
+class SpecialFileError(EnvironmentError):
+ """Raised when trying to do a kind of operation (e.g. copying) which is
+ not supported on a special file (e.g. a named pipe)"""
+
+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:
+ WindowsError = None
+
+def copyfileobj(fsrc, fdst, length=16*1024):
+ """copy data from file-like object fsrc to file-like object fdst"""
+ while 1:
+ buf = fsrc.read(length)
+ if not buf:
+ break
+ fdst.write(buf)
+
+def _samefile(src, dst):
+ # Macintosh, Unix.
+ if hasattr(os.path, 'samefile'):
+ try:
+ return os.path.samefile(src, dst)
+ except OSError:
+ return False
+
+ # All other platforms: check for same pathname.
+ return (os.path.normcase(os.path.abspath(src)) ==
+ os.path.normcase(os.path.abspath(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))
+
+ for fn in [src, dst]:
+ try:
+ st = os.stat(fn)
+ except OSError:
+ # File most likely does not exist
+ pass
+ else:
+ # XXX What about other special files? (sockets, devices...)
+ if stat.S_ISFIFO(st.st_mode):
+ raise SpecialFileError("`%s` is a named pipe" % fn)
+
+ with open(src, 'rb') as fsrc:
+ with open(dst, 'wb') as fdst:
+ copyfileobj(fsrc, fdst)
+
+def copymode(src, dst):
+ """Copy mode bits from src to dst"""
+ if hasattr(os, 'chmod'):
+ st = os.stat(src)
+ mode = stat.S_IMODE(st.st_mode)
+ os.chmod(dst, mode)
+
+def copystat(src, dst):
+ """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
+ st = os.stat(src)
+ mode = stat.S_IMODE(st.st_mode)
+ if hasattr(os, 'utime'):
+ os.utime(dst, (st.st_atime, st.st_mtime))
+ if hasattr(os, 'chmod'):
+ os.chmod(dst, mode)
+ if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
+ try:
+ os.chflags(dst, st.st_flags)
+ except OSError as why:
+ if (not hasattr(errno, 'EOPNOTSUPP') or
+ why.errno != errno.EOPNOTSUPP):
+ raise
+
+def copy(src, dst):
+ """Copy data and mode bits ("cp src dst").
+
+ The destination may be a directory.
+
+ """
+ if os.path.isdir(dst):
+ dst = os.path.join(dst, os.path.basename(src))
+ copyfile(src, dst)
+ copymode(src, dst)
+
+def copy2(src, dst):
+ """Copy data and all stat info ("cp -p src dst").
+
+ The destination may be a directory.
+
+ """
+ if os.path.isdir(dst):
+ dst = os.path.join(dst, os.path.basename(src))
+ copyfile(src, dst)
+ copystat(src, dst)
+
+def ignore_patterns(*patterns):
+ """Function that can be used as copytree() ignore parameter.
+
+ Patterns is a sequence of glob-style patterns
+ that are used to exclude files"""
+ def _ignore_patterns(path, names):
+ ignored_names = []
+ for pattern in patterns:
+ ignored_names.extend(fnmatch.filter(names, pattern))
+ return set(ignored_names)
+ return _ignore_patterns
+
+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.
+
+ 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. 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
+ being visited by copytree(), and `names` which is the list of
+ `src` contents, as returned by os.listdir():
+
+ callable(src, names) -> ignored_names
+
+ Since copytree() is called recursively, the callable will be
+ called once for each directory that is copied. It returns a
+ list of names relative to the `src` directory that should
+ not be copied.
+
+ 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)
+ if ignore is not None:
+ ignored_names = ignore(src, names)
+ else:
+ ignored_names = set()
+
+ os.makedirs(dst)
+ errors = []
+ for name in names:
+ if name in ignored_names:
+ continue
+ srcname = os.path.join(src, name)
+ dstname = os.path.join(dst, name)
+ try:
+ if os.path.islink(srcname):
+ linkto = os.readlink(srcname)
+ 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, copy_function)
+ else:
+ # Will raise a SpecialFileError for unsupported file types
+ copy_function(srcname, dstname)
+ # catch the Error from the recursive copytree so that we can
+ # continue with other files
+ except Error as err:
+ errors.extend(err.args[0])
+ except EnvironmentError as why:
+ errors.append((srcname, dstname, str(why)))
+ try:
+ copystat(src, dst)
+ except OSError as why:
+ if WindowsError is not None and isinstance(why, WindowsError):
+ # Copying file access times may fail on Windows
+ pass
+ else:
+ errors.extend((src, dst, str(why)))
+ if errors:
+ raise Error(errors)
+
+def rmtree(path, ignore_errors=False, onerror=None):
+ """Recursively delete a directory tree.
+
+ If ignore_errors is set, errors are ignored; otherwise, if onerror
+ is set, it is called to handle the error with arguments (func,
+ path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
+ path is the argument to that function that caused it to fail; and
+ exc_info is a tuple returned by sys.exc_info(). If ignore_errors
+ is false and onerror is None, an exception is raised.
+
+ """
+ if ignore_errors:
+ def onerror(*args):
+ pass
+ elif onerror is None:
+ def onerror(*args):
+ raise
+ try:
+ if os.path.islink(path):
+ # symlinks to directories are forbidden, see bug #1669
+ raise OSError("Cannot call rmtree on a symbolic link")
+ except OSError:
+ onerror(os.path.islink, path, sys.exc_info())
+ # can't continue even if onerror hook returns
+ return
+ names = []
+ try:
+ names = os.listdir(path)
+ except os.error:
+ onerror(os.listdir, path, sys.exc_info())
+ for name in names:
+ fullname = os.path.join(path, name)
+ try:
+ mode = os.lstat(fullname).st_mode
+ except os.error:
+ mode = 0
+ if stat.S_ISDIR(mode):
+ rmtree(fullname, ignore_errors, onerror)
+ else:
+ try:
+ os.remove(fullname)
+ except os.error:
+ onerror(os.remove, fullname, sys.exc_info())
+ try:
+ os.rmdir(path)
+ except os.error:
+ onerror(os.rmdir, path, sys.exc_info())
+
+
+def _basename(path):
+ # A basename() variant which first strips the trailing slash, if present.
+ # Thus we always get the last component of the path, even for directories.
+ return os.path.basename(path.rstrip(os.path.sep))
+
+def move(src, dst):
+ """Recursively move a file or directory to another location. This is
+ similar to the Unix "mv" command.
+
+ If the destination is a directory or a symlink to a directory, the source
+ is moved inside the directory. The destination path must not already
+ exist.
+
+ If the destination already exists but is not a directory, it may be
+ overwritten depending on os.rename() semantics.
+
+ If the destination is on our current filesystem, then rename() is used.
+ Otherwise, src is copied to the destination and then removed.
+ A lot more could be done here... A look at a mv.c shows a lot of
+ the issues this implementation glosses over.
+
+ """
+ real_dst = dst
+ if os.path.isdir(dst):
+ if _samefile(src, dst):
+ # We might be on a case insensitive filesystem,
+ # perform the rename anyway.
+ os.rename(src, dst)
+ return
+
+ real_dst = os.path.join(dst, _basename(src))
+ if os.path.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))
+ copytree(src, real_dst, symlinks=True)
+ rmtree(src)
+ else:
+ copy2(src, real_dst)
+ os.unlink(src)
+
+def _destinsrc(src, dst):
+ src = abspath(src)
+ dst = abspath(dst)
+ if not src.endswith(os.path.sep):
+ src += os.path.sep
+ if not dst.endswith(os.path.sep):
+ dst += os.path.sep
+ return dst.startswith(src)
+
+def _get_gid(name):
+ """Returns a gid, given a group name."""
+ if getgrnam is None or name is None:
+ return None
+ try:
+ result = getgrnam(name)
+ except KeyError:
+ result = None
+ if result is not None:
+ return result[2]
+ return None
+
+def _get_uid(name):
+ """Returns an uid, given a user name."""
+ if getpwnam is None or name is None:
+ return None
+ try:
+ result = getpwnam(name)
+ except KeyError:
+ result = None
+ if result is not None:
+ return result[2]
+ return None
+
+def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
+ owner=None, group=None, logger=None):
+ """Create a (possibly compressed) tar file from all the files under
+ 'base_dir'.
+
+ '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_name' + ".tar", possibly plus
+ the appropriate compression extension (".gz", or ".bz2").
+
+ Returns the output filename.
+ """
+ tar_compression = {'gzip': 'gz', None: ''}
+ compress_ext = {'gzip': '.gz'}
+
+ if _BZ2_SUPPORTED:
+ tar_compression['bzip2'] = 'bz2'
+ compress_ext['bzip2'] = '.bz2'
+
+ # 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 : {0}".format(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)
+ if not dry_run:
+ os.makedirs(archive_dir)
+
+ # creating the tarball
+ if logger is not None:
+ logger.info('Creating tar archive')
+
+ uid = _get_uid(owner)
+ gid = _get_gid(group)
+
+ def _set_uid_gid(tarinfo):
+ if gid is not None:
+ tarinfo.gid = gid
+ tarinfo.gname = group
+ if uid is not None:
+ tarinfo.uid = uid
+ tarinfo.uname = owner
+ return tarinfo
+
+ if not dry_run:
+ tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
+ try:
+ tar.add(base_dir, filter=_set_uid_gid)
+ finally:
+ tar.close()
+
+ return archive_name
+
+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"
+ else:
+ zipoptions = "-rq"
+ from distutils.errors import DistutilsExecError
+ from distutils.spawn import spawn
+ try:
+ spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
+ except DistutilsExecError:
+ # XXX really should distinguish between "couldn't find
+ # external 'zip' command" and "zip failed".
+ 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_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
+ file.
+ """
+ zip_filename = base_name + ".zip"
+ archive_dir = os.path.dirname(base_name)
+
+ if not os.path.exists(archive_dir):
+ if logger is not None:
+ logger.info("creating %s", archive_dir)
+ if not dry_run:
+ os.makedirs(archive_dir)
+
+ # If zipfile module is not available, try spawning an external 'zip'
+ # command.
+ try:
+ import zipfile
+ except ImportError:
+ zipfile = None
+
+ if zipfile is None:
+ _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",
+ zip_filename, base_dir)
+
+ if not dry_run:
+ zip = zipfile.ZipFile(zip_filename, "w",
+ compression=zipfile.ZIP_DEFLATED)
+
+ for dirpath, dirnames, filenames in os.walk(base_dir):
+ for name in filenames:
+ path = os.path.normpath(os.path.join(dirpath, name))
+ if os.path.isfile(path):
+ zip.write(path, path)
+ if logger is not None:
+ logger.info("adding '%s'", path)
+ zip.close()
+
+ return zip_filename
+
+_ARCHIVE_FORMATS = {
+ 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
+ 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
+ 'tar': (_make_tarball, [('compress', None)], "uncompressed tar 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()]
+ formats.sort()
+ return formats
+
+def register_archive_format(name, function, extra_args=None, description=''):
+ """Registers an archive format.
+
+ name is the name of the format. function is the callable that will be
+ used to create archives. 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_archive_formats() function.
+ """
+ if extra_args is None:
+ extra_args = []
+ if not isinstance(function, collections.Callable):
+ raise TypeError('The %s object is not callable' % function)
+ 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:
+ raise TypeError('extra_args elements are : (arg_name, value)')
+
+ _ARCHIVE_FORMATS[name] = (function, extra_args, description)
+
+def unregister_archive_format(name):
+ del _ARCHIVE_FORMATS[name]
+
+def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
+ dry_run=0, owner=None, group=None, logger=None):
+ """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", "bztar"
+ or "gztar".
+
+ 'root_dir' is a directory that will be the root directory of the
+ archive; ie. we typically chdir into 'root_dir' before creating the
+ archive. 'base_dir' is the directory where we start archiving from;
+ ie. 'base_dir' will be the common prefix of all files and
+ directories in the archive. 'root_dir' and 'base_dir' both default
+ to the current directory. Returns the name of the archive file.
+
+ 'owner' and 'group' are used when creating a tar archive. By default,
+ uses the current owner and group.
+ """
+ save_cwd = os.getcwd()
+ if root_dir is not None:
+ if logger is not None:
+ logger.debug("changing into '%s'", root_dir)
+ base_name = os.path.abspath(base_name)
+ if not dry_run:
+ os.chdir(root_dir)
+
+ if base_dir is None:
+ base_dir = os.curdir
+
+ kwargs = {'dry_run': dry_run, 'logger': logger}
+
+ try:
+ format_info = _ARCHIVE_FORMATS[format]
+ except KeyError:
+ raise ValueError("unknown archive format '%s'" % format)
+
+ func = format_info[0]
+ for arg, val in format_info[1]:
+ kwargs[arg] = val
+
+ if format != 'zip':
+ kwargs['owner'] = owner
+ kwargs['group'] = group
+
+ try:
+ filename = func(base_name, base_dir, **kwargs)
+ finally:
+ if root_dir is not None:
+ if logger is not None:
+ logger.debug("changing back to '%s'", save_cwd)
+ 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.items()]
+ 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.items():
+ 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 isinstance(function, collections.Callable):
+ 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`
+ """
+ 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.items():
+ 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()
+
+ if format is not None:
+ try:
+ format_info = _UNPACK_FORMATS[format]
+ except KeyError:
+ raise ValueError("Unknown unpack format '{0}'".format(format))
+
+ func = format_info[1]
+ func(filename, extract_dir, **dict(format_info[2]))
+ 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)
View
787 pip/vendor/distlib/_backport/sysconfig.py
@@ -0,0 +1,787 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012 The Python Software Foundation.
+# See LICENSE.txt and CONTRIBUTORS.txt.
+#
+"""Access to Python's configuration information."""
+
+import codecs
+import os
+import re
+import sys
+from os.path import pardir, realpath
+try:
+ import configparser
+except ImportError:
+ import ConfigParser as configparser
+
+
+__all__ = [
+ 'get_config_h_filename',
+ 'get_config_var',
+ 'get_config_vars',
+ 'get_makefile_filename',
+ 'get_path',
+ 'get_path_names',
+ 'get_paths',
+ 'get_platform',
+ 'get_python_version',
+ 'get_scheme_names',
+ 'parse_config_h',
+]
+
+
+def _safe_realpath(path):
+ try:
+ return realpath(path)
+ except OSError:
+ return path
+
+
+if sys.executable:
+ _PROJECT_BASE = os.path.dirname(_safe_realpath(sys.executable))
+else:
+ # sys.executable can be empty if argv[0] has been changed and Python is
+ # unable to retrieve the real program name
+ _PROJECT_BASE = _safe_realpath(os.getcwd())
+
+if os.name == "nt" and "pcbuild" in _PROJECT_BASE[-8:].lower():
+ _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir))
+# PC/VS7.1
+if os.name == "nt" and "\\pc\\v" in _PROJECT_BASE[-10:].lower():
+ _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
+# PC/AMD64
+if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower():
+ _PROJECT_BASE = _safe_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)):
+ return True
+ return False
+
+_PYTHON_BUILD = is_python_build()
+
+_cfg_read = False
+
+def _ensure_cfg_read():
+ global _cfg_read
+ if not _cfg_read:
+ from distlib.resources import finder
+ _finder = finder('distlib._backport')
+ _cfgfile = _finder.find('sysconfig.cfg')
+ assert _cfgfile, 'sysconfig.cfg exists'
+ with _cfgfile.as_stream() as s:
+ _SCHEMES.readfp(s)
+ if _PYTHON_BUILD:
+ for scheme in ('posix_prefix', 'posix_home'):
+ _SCHEMES.set(scheme, 'include', '{srcdir}/Include')
+ _SCHEMES.set(scheme, 'platinclude', '{projectbase}/.')
+
+ _cfg_read = True
+
+
+_SCHEMES = configparser.RawConfigParser()
+_VAR_REPL = re.compile(r'\{([^{]*?)\}')
+
+def _expand_globals(config):
+ _ensure_cfg_read()
+ if config.has_section('globals'):
+ globals = config.items('globals')
+ else:
+ globals = tuple()
+
+ sections = config.sections()
+ for section in sections:
+ if section == 'globals':
+ continue
+ for option, value in globals:
+ if config.has_option(section, option):
+ continue
+ config.set(section, option, value)
+ config.remove_section('globals')
+
+ # now expanding local variables defined in the cfg file
+ #
+ 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))
+
+#_expand_globals(_SCHEMES)
+
+ # FIXME don't rely on sys.version here, its format is an implementation detail
+ # of CPython, use sys.version_info or sys.hexversion
+_PY_VERSION = sys.version.split()[0]
+_PY_VERSION_SHORT = sys.version[:3]
+_PY_VERSION_SHORT_NO_DOT = _PY_VERSION[0] + _PY_VERSION[2]
+_PREFIX = os.path.normpath(sys.prefix)
+_EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
+_CONFIG_VARS = None
+_USER_BASE = None
+
+
+def _subst_vars(path, 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)
+ if name in local_vars:
+ return local_vars[name]
+ elif name in os.environ:
+ return os.environ[name]
+ 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:
+ continue
+ target_dict[key] = value
+
+
+def _expand_vars(scheme, vars):
+ res = {}
+ if vars is None:
+ vars = {}
+ _extend_dict(vars, get_config_vars())
+
+ for key, value in _SCHEMES.items(scheme):
+ if os.name in ('posix', 'nt'):
+ value = os.path.expanduser(value)
+ 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))
+
+ # what about 'os2emx', 'riscos' ?
+ if os.name == "nt":
+ base = os.environ.get("APPDATA") or "~"
+ if env_base:
+ return env_base
+ else:
+ return joinuser(base, "Python")
+
+ if sys.platform == "darwin":
+ framework = get_config_var("PYTHONFRAMEWORK")
+ if framework:
+ if env_base:
+ return env_base
+ else:
+ return joinuser("~", "Library", framework, "%d.%d" %
+ sys.version_info[:2])
+
+ if env_base:
+ return env_base
+ else:
+ return joinuser("~", ".local")
+
+
+def _parse_makefile(filename, vars=None):
+ """Parse a Makefile-style file.
+
+ A dictionary containing name/value pairs is returned. If an
+ optional dictionary is passed in as the second argument, it is
+ used instead of a new dictionary.
+ """
+ # 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*(.*)")
+ _findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
+ _findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
+
+ if vars is None:
+ vars = {}
+ done = {}
+ notdone = {}
+
+ with codecs.open(filename, encoding='utf-8', errors="surrogateescape") as f:
+ lines = f.readlines()
+
+ for line in lines:
+ if line.startswith('#') or line.strip() == '':
+ continue
+ m = _variable_rx.match(line)
+ if m:
+ n, v = m.group(1, 2)
+ v = v.strip()
+ # `$$' is a literal `$' in make
+ tmpv = v.replace('$$', '')
+
+ if "$" in tmpv:
+ notdone[n] = v
+ else:
+ try:
+ v = int(v)
+ except ValueError:
+ # insert literal `$'
+ done[n] = v.replace('$$', '$')
+ else:
+ done[n] = v
+
+ # do variable interpolation here
+ variables = list(notdone.keys())
+
+ # Variables with a 'PY_' prefix in the makefile. These need to
+ # be made available without that prefix through sysconfig.
+ # Special care is needed to ensure that variable expansion works, even
+ # if the expansion uses the name without a prefix.
+ renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS')
+
+ while len(variables) > 0:
+ for name in tuple(variables):
+ value = notdone[name]
+ m = _findvar1_rx.search(value) or _findvar2_rx.search(value)
+ if m is not None:
+ n = m.group(1)
+ found = True
+ if n in done:
+ item = str(done[n])
+ elif n in notdone:
+ # get it on a subsequent round
+ found = False
+ elif n in os.environ:
+ # do it like make: fall back to environment
+ item = os.environ[n]
+
+ elif n in renamed_variables:
+ if (name.startswith('PY_') and
+ name[3:] in renamed_variables):
+ item = ""
+
+ elif 'PY_' + n in notdone:
+ found = False
+
+ else:
+ item = str(done['PY_' + n])
+
+ else:
+ done[n] = item = ""
+
+ if found:
+ after = value[m.end():]
+ value = value[:m.start()] + item + after
+ if "$" in after:
+ notdone[name] = value
+ else:
+ try:
+ value = int(value)
+ except ValueError:
+ done[name] = value.strip()
+ else:
+ done[name] = value
+ variables.remove(name)
+
+ if (name.startswith('PY_') and
+ name[3:] in renamed_variables):
+
+ name = name[3:]
+ if name not in done:
+ done[name] = value
+
+ else:
+ # bogus variable reference (e.g. "prefix=$/opt/python");
+ # just drop it since we can't deal
+ done[name] = value
+ variables.remove(name)
+
+ # strip spurious spaces
+ for k, v in done.items():
+ if isinstance(v, str):
+ done[k] = v.strip()
+
+ # save the results in the global dictionary
+ vars.update(done)
+ return vars
+
+
+def get_makefile_filename():
+ """Return the path of the Makefile."""
+ if _PYTHON_BUILD:
+ return os.path.join(_PROJECT_BASE, "Makefile")
+ if hasattr(sys, 'abiflags'):
+ config_dir_name = 'config-%s%s' % (_PY_VERSION_SHORT, sys.abiflags)
+ else:
+ config_dir_name = 'config'
+ return os.path.join(get_path('stdlib'), config_dir_name, 'Makefile')
+
+
+def _init_posix(vars):
+ """Initialize the module as appropriate for POSIX systems."""
+ # load the installed Makefile:
+ makefile = get_makefile_filename()
+ try:
+ _parse_makefile(makefile, vars)
+ except IOError as e:
+ msg = "invalid Python installation: unable to open %s" % makefile
+ if hasattr(e, "strerror"):
+ msg = msg + " (%s)" % e.strerror
+ raise IOError(msg)
+ # load the installed pyconfig.h:
+ config_h = get_config_h_filename()
+ try:
+ with open(config_h) as f:
+ parse_config_h(f, vars)
+ except IOError as e:
+ msg = "invalid Python installation: unable to open %s" % config_h
+ if hasattr(e, "strerror"):
+ msg = msg + " (%s)" % e.strerror
+ raise IOError(msg)
+ # On AIX, there are wrong paths to the linker scripts in the Makefile
+ # -- these paths are relative to the Python source, but when installed
+ # the scripts are in another directory.
+ if _PYTHON_BUILD:
+ vars['LDSHARED'] = vars['BLDSHARED']
+
+
+def _init_non_posix(vars):
+ """Initialize the module as appropriate for NT"""
+ # set basic install directories
+ vars['LIBDEST'] = get_path('stdlib')
+ vars['BINLIBDEST'] = get_path('platstdlib')
+ vars['INCLUDEPY'] = get_path('include')
+ vars['SO'] = '.pyd'
+ vars['EXE'] = '.exe'
+ vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT
+ vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable))
+
+#
+# public APIs
+#
+
+
+def parse_config_h(fp, vars=None):
+ """Parse a config.h-style file.
+
+ A dictionary containing name/value pairs is returned. If an
+ optional dictionary is passed in as the second argument, it is
+ used instead of a new dictionary.
+ """
+ if vars is None:
+ vars = {}
+ define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
+ undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n")
+
+ while True:
+ line = fp.readline()
+ if not line:
+ break
+ m = define_rx.match(line)
+ if m:
+ n, v = m.group(1, 2)
+ try:
+ v = int(v)
+ except ValueError:
+ pass
+ vars[n] = v
+ else:
+ m = undef_rx.match(line)
+ if m:
+ vars[m.group(1)] = 0
+ return vars
+
+
+def get_config_h_filename():
+ """Return the path of pyconfig.h."""
+ if _PYTHON_BUILD:
+ if os.name == "nt":
+ inc_dir = os.path.join(_PROJECT_BASE, "PC")
+ else:
+ inc_dir = _PROJECT_BASE
+ else:
+ inc_dir = get_path('platinclude')
+ return os.path.join(inc_dir, 'pyconfig.h')
+
+
+def get_scheme_names():
+ """Return a tuple containing the schemes names."""
+ return tuple(sorted(_SCHEMES.sections()))
+
+
+def get_path_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):
+ """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.
+ """
+ _ensure_cfg_read()
+ if expand:
+ return _expand_vars(scheme, vars)
+ else:
+ return dict(_SCHEMES.items(scheme))
+
+
+def get_path(name, scheme=_get_default_scheme(), vars=None, expand=True):
+ """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.
+
+ On Unix, this means every variable defined in Python's installed Makefile;
+ On Windows and Mac OS it's a much smaller set.
+
+ With arguments, return a list of values that result from looking up
+ each argument in the configuration variable dictionary.
+ """
+ global _CONFIG_VARS
+ if _CONFIG_VARS is None:
+ _CONFIG_VARS = {}
+ # Normalized versions of prefix and exec_prefix are handy to have;
+ # in fact, these are the standard versions used most places in the
+ # distutils2 module.
+ _CONFIG_VARS['prefix'] = _PREFIX
+ _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX
+ _CONFIG_VARS['py_version'] = _PY_VERSION
+ _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
+ _CONFIG_VARS['py_version_nodot'] = _PY_VERSION[0] + _PY_VERSION[2]
+ _CONFIG_VARS['base'] = _PREFIX
+ _CONFIG_VARS['platbase'] = _EXEC_PREFIX
+ _CONFIG_VARS['projectbase'] = _PROJECT_BASE
+ try:
+ _CONFIG_VARS['abiflags'] = sys.abiflags
+ except AttributeError:
+ # sys.abiflags may not be defined on all platforms.
+ _CONFIG_VARS['abiflags'] = ''
+
+ if os.name in ('nt', 'os2'):
+ _init_non_posix(_CONFIG_VARS)
+ if os.name == 'posix':
+ _init_posix(_CONFIG_VARS)
+ # Setting 'userbase' is done below the call to the
+ # init function to enable using 'get_config_var' in
+ # the init-function.
+ if sys.version >= '2.6':
+ _CONFIG_VARS['userbase'] = _getuserbase()
+
+ if 'srcdir' not in _CONFIG_VARS:
+ _CONFIG_VARS['srcdir'] = _PROJECT_BASE
+ else:
+ _CONFIG_VARS['srcdir'] = _safe_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
+ # from a different directory.
+ if _PYTHON_BUILD and os.name == "posix":
+ base = _PROJECT_BASE
+ try:
+ cwd = os.getcwd()
+ except OSError:
+ cwd = None
+ if (not os.path.isabs(_CONFIG_VARS['srcdir']) and
+ base != cwd):
+ # srcdir is relative and we are not in the same directory
+ # as the executable. Assume executable is in the build
+ # directory and make srcdir absolute.
+ srcdir = os.path.join(base, _CONFIG_VARS['srcdir'])
+ _CONFIG_VARS['srcdir'] = os.path.normpath(srcdir)
+
+ if sys.platform == 'darwin':
+ kernel_version = os.uname()[2] # Kernel version (8.4.3)
+ major_version = int(kernel_version.split('.')[0])
+
+ if major_version < 8:
+ # On Mac OS X before 10.4, check if -arch and -isysroot
+ # are in CFLAGS or LDFLAGS and remove them if they are.
+ # This is needed when building extensions on a 10.3 system
+ # using a universal build of python.
+ for key in ('LDFLAGS', 'BASECFLAGS',
+ # a number of derived variables. These need to be
+ # patched up as well.
+ 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
+ flags = _CONFIG_VARS[key]
+ flags = re.sub('-arch\s+\w+\s', ' ', flags)
+ flags = re.sub('-isysroot [^ \t]*', ' ', flags)
+ _CONFIG_VARS[key] = flags
+ else:
+ # Allow the user to override the architecture flags using
+ # an environment variable.
+ # NOTE: This name was introduced by Apple in OSX 10.5 and
+ # is used by several scripting languages distributed with
+ # that OS release.
+ if 'ARCHFLAGS' in os.environ:
+ arch = os.environ['ARCHFLAGS']
+ for key in ('LDFLAGS', 'BASECFLAGS',
+ # a number of derived variables. These need to be
+ # patched up as well.
+ 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
+
+ flags = _CONFIG_VARS[key]
+ flags = re.sub('-arch\s+\w+\s', ' ', flags)
+ flags = flags + ' ' + arch
+ _CONFIG_VARS[key] = flags
+
+ # If we're on OSX 10.5 or later and the user tries to
+ # compiles an extension using an SDK that is not present
+ # on the current machine it is better to not use an SDK
+ # than to fail.
+ #
+ # The major usecase for this is users using a Python.org
+ # binary installer on OSX 10.6: that installer uses
+ # the 10.4u SDK, but that SDK is not installed by default
+ # when you install Xcode.
+ #
+ CFLAGS = _CONFIG_VARS.get('CFLAGS', '')
+ m = re.search('-isysroot\s+(\S+)', CFLAGS)
+ if m is not None:
+ sdk = m.group(1)
+ if not os.path.exists(sdk):
+ for key in ('LDFLAGS', 'BASECFLAGS',
+ # a number of derived variables. These need to be
+ # patched up as well.
+ 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
+
+ flags = _CONFIG_VARS[key]
+ flags = re.sub('-isysroot\s+\S+(\s|$)', ' ', flags)
+ _CONFIG_VARS[key] = flags
+
+ if args:
+ vals = []
+ for name in args:
+ vals.append(_CONFIG_VARS.get(name))
+ return vals
+ else:
+ return _CONFIG_VARS
+
+
+def get_config_var(name):
+ """Return the value of a single variable using the dictionary returned by
+ 'get_config_vars()'.
+
+ Equivalent to get_config_vars().get(name)
+ """
+ return get_config_vars().get(name)
+
+
+def get_platform():
+ """Return a string that identifies the current platform.
+
+ This is used mainly to distinguish platform-specific build directories and
+ platform-specific built distributions. Typically includes the OS name
+ and version and the architecture (as supplied by 'os.uname()'),
+ although the exact information included depends on the OS; eg. for IRIX
+ the architecture isn't particularly important (IRIX only runs on SGI
+ hardware), but for Linux the kernel version isn't particularly
+ important.
+
+ Examples of returned values:
+ linux-i586
+ linux-alpha (?)
+ solaris-2.6-sun4u
+ irix-5.3
+ irix64-6.2
+
+ Windows will return one of:
+ win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
+ win-ia64 (64bit Windows on Itanium)
+ win32 (all others - specifically, sys.platform is returned)
+
+ For other non-POSIX platforms, currently just returns 'sys.platform'.
+ """
+ if os.name == 'nt':
+ # sniff sys.version for architecture.
+ prefix = " bit ("
+ i = sys.version.find(prefix)
+ if i == -1:
+ return sys.platform
+ j = sys.version.find(")", i)
+ look = sys.version[i+len(prefix):j].lower()
+ if look == 'amd64':
+ return 'win-amd64'
+ if look == 'itanium':
+ return 'win-ia64'
+ return sys.platform
+
+ if os.name != "posix" or not hasattr(os, 'uname'):
+ # XXX what about the architecture? NT is Intel or Alpha,
+ # Mac OS is M68k or PPC, etc.
+ return sys.platform
+
+ # Try to distinguish various flavours of Unix
+ osname, host, release, version, machine = os.uname()
+
+ # Convert the OS name to lowercase, remove '/' characters
+ # (to accommodate BSD/OS), and translate spaces (for "Power Macintosh")
+ osname = osname.lower().replace('/', '')
+ machine = machine.replace(' ', '_')
+ machine = machine.replace('/', '-')
+
+ if osname[:5] == "linux":
+ # At least on Linux/Intel, 'machine' is the processor --
+ # i386, etc.
+ # XXX what about Alpha, SPARC, etc?
+ return "%s-%s" % (osname, machine)
+ elif osname[:5] == "sunos":
+ if release[0] >= "5": # SunOS 5 == Solaris 2
+ osname = "solaris"
+ release = "%d.%s" % (int(release[0]) - 3, release[2:])
+ # fall through to standard osname-release-machine representation
+ elif osname[:4] == "irix": # could be "irix64"!
+ return "%s-%s" % (osname, release)
+ elif osname[:3] == "aix":
+ return "%s-%s.%s" % (osname, version, release)
+ elif osname[:6] == "cygwin":
+ osname = "cygwin"
+ rel_re = re.compile(r'[\d.]+')
+ m = rel_re.match(release)
+ if m:
+ release = m.group()
+ elif osname[:6] == "darwin":
+ #
+ # For our purposes, we'll assume that the system version from
+ # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set
+ # to. This makes the compatibility story a bit more sane because the
+ # machine is going to compile and link as if it were
+ # MACOSX_DEPLOYMENT_TARGET.
+ cfgvars = get_config_vars()
+ macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET')
+
+ if True:
+ # Always calculate the release of the running machine,
+ # needed to determine if we can build fat binaries or not.
+
+ macrelease = macver
+ # Get the system version. Reading this plist is a documented
+ # way to get the system version (see the documentation for
+ # the Gestalt Manager)
+ try:
+ f = open('/System/Library/CoreServices/SystemVersion.plist')
+ except IOError:
+ # We're on a plain darwin box, fall back to the default
+ # behaviour.
+ pass
+ else:
+ try:
+ m = re.search(r'<key>ProductUserVisibleVersion</key>\s*'
+ r'<string>(.*?)</string>', f.read())
+ finally:
+ f.close()
+ if m is not None:
+ macrelease = '.'.join(m.group(1).split('.')[:2])
+ # else: fall back to the default behaviour
+
+ if not macver:
+ macver = macrelease
+
+ if macver:
+ release = macver
+ osname = "macosx"
+
+ if ((macrelease + '.') >= '10.4.' and
+ '-arch' in get_config_vars().get('CFLAGS', '').strip()):
+ # The universal build will build fat binaries, but not on
+ # systems before 10.4
+ #
+ # Try to detect 4-way universal builds, those have machine-type
+ # 'universal' instead of 'fat'.
+
+ machine = 'fat'
+ cflags = get_config_vars().get('CFLAGS')
+
+ archs = re.findall('-arch\s+(\S+)', cflags)
+ archs = tuple(sorted(set(archs)))
+
+ if len(archs) == 1:
+ machine = archs[0]
+ elif archs == ('i386', 'ppc'):
+ machine = 'fat'
+ elif archs == ('i386', 'x86_64'):
+ machine = 'intel'
+ elif archs == ('i386', 'ppc', 'x86_64'):
+ machine = 'fat3'
+ elif archs == ('ppc64', 'x86_64'):
+ machine = 'fat64'
+ elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
+ machine = 'universal'
+ else:
+ raise ValueError(
+ "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.maxsize >= 2**32:
+ machine = 'x86_64'
+
+ elif machine in ('PowerPC', 'Power_Macintosh'):
+ # Pick a sane name for the PPC architecture.
+ # See 'i386' case
+ if sys.maxsize >= 2**32:
+ machine = 'ppc64'
+ else:
+ machine = 'ppc'
+
+ return "%s-%s-%s" % (osname, release, machine)
+
+
+def get_python_version():
+ return _PY_VERSION_SHORT
+
+
+def _print_dict(title, data):
+ for index, (key, value) in enumerate(sorted(data.items())):
+ if index == 0:
+ print('%s: ' % (title))
+ print('\t%s = "%s"' % (key, value))
+
+
+def _main():
+ """Display all information sysconfig detains."""
+ print('Platform: "%s"' % get_platform())
+ print('Python version: "%s"' % get_python_version())
+ print('Current installation scheme: "%s"' % _get_default_scheme())
+ print()
+ _print_dict('Paths', get_paths())
+ print()
+ _print_dict('Variables', get_config_vars())
+
+
+if __name__ == '__main__':
+ _main()
View
2,607 pip/vendor/distlib/_backport/tarfile.py
@@ -0,0 +1,2607 @@
+#-------------------------------------------------------------------
+# tarfile.py
+#-------------------------------------------------------------------
+# Copyright (C) 2002 Lars Gustaebel <lars@gustaebel.de>
+# All rights reserved.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+from __future__ import print_function
+
+"""Read from and write to tar format archives.
+"""
+
+__version__ = "$Revision$"
+
+version = "0.9.0"
+__author__ = "Lars Gust\u00e4bel (lars@gustaebel.de)"
+__date__ = "$Date: 2011-02-25 17:42:01 +0200 (Fri, 25 Feb 2011) $"
+__cvsid__ = "$Id: tarfile.py 88586 2011-02-25 15:42:01Z marc-andre.lemburg $"
+__credits__ = "Gustavo Niemeyer, Niels Gust\u00e4bel, Richard Townsend."
+
+#---------
+# Imports
+#---------
+import sys
+import os
+import stat
+import errno
+import time
+import struct
+import copy
+import re
+
+try:
+ import grp, pwd
+except ImportError:
+ grp = pwd = None
+
+# os.symlink on Windows prior to 6.0 raises NotImplementedError
+symlink_exception = (AttributeError, NotImplementedError)
+try:
+ # WindowsError (1314) will be raised if the caller does not hold the
+ # SeCreateSymbolicLinkPrivilege privilege
+ symlink_exception += (WindowsError,)
+except NameError:
+ pass
+
+# from tarfile import *
+__all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError"]
+
+if sys.version_info[0] < 3:
+ import __builtin__ as builtins
+else:
+ import builtins
+
+_open = builtins.open # Since 'open' is TarFile.open
+
+#---------------------------------------------------------
+# tar constants
+#---------------------------------------------------------
+NUL = b"\0" # the null character
+BLOCKSIZE = 512 # length of processing blocks
+RECORDSIZE = BLOCKSIZE * 20 # length of records
+GNU_MAGIC = b"ustar \0" # magic gnu tar string
+POSIX_MAGIC = b"ustar\x0000" # magic posix tar string
+
+LENGTH_NAME = 100 # maximum length of a filename
+LENGTH_LINK = 100 # maximum length of a linkname
+LENGTH_PREFIX = 155 # maximum length of the prefix field
+
+REGTYPE = b"0" # regular file
+AREGTYPE = b"\0" # regular file
+LNKTYPE = b"1" # link (inside tarfile)
+SYMTYPE = b"2" # symbolic link
+CHRTYPE = b"3" # character special device
+BLKTYPE = b"4" # block special device
+DIRTYPE = b"5" # directory
+FIFOTYPE = b"6" # fifo special device
+CONTTYPE = b"7" # contiguous file
+
+GNUTYPE_LONGNAME = b"L" # GNU tar longname
+GNUTYPE_LONGLINK = b"K" # GNU tar longlink
+GNUTYPE_SPARSE = b"S" # GNU tar sparse file
+
+XHDTYPE = b"x" # POSIX.1-2001 extended header
+XGLTYPE = b"g" # POSIX.1-2001 global header
+SOLARIS_XHDTYPE = b"X" # Solaris extended header
+
+USTAR_FORMAT = 0 # POSIX.1-1988 (ustar) format
+GNU_FORMAT = 1 # GNU tar format
+PAX_FORMAT = 2 # POSIX.1-2001 (pax) format
+DEFAULT_FORMAT = GNU_FORMAT
+
+#---------------------------------------------------------
+# tarfile constants
+#---------------------------------------------------------
+# File types that tarfile supports:
+SUPPORTED_TYPES = (REGTYPE, AREGTYPE, LNKTYPE,
+ SYMTYPE, DIRTYPE, FIFOTYPE,
+ CONTTYPE, CHRTYPE, BLKTYPE,
+ GNUTYPE_LONGNAME, GNUTYPE_LONGLINK,
+ GNUTYPE_SPARSE)
+
+# File types that will be treated as a regular file.
+REGULAR_TYPES = (REGTYPE, AREGTYPE,
+ CONTTYPE, GNUTYPE_SPARSE)
+
+# File types that are part of the GNU tar format.
+GNU_TYPES = (GNUTYPE_LONGNAME, GNUTYPE_LONGLINK,
+ GNUTYPE_SPARSE)
+
+# Fields from a pax header that override a TarInfo attribute.
+PAX_FIELDS = ("path", "linkpath", "size", "mtime",
+ "uid", "gid", "uname", "gname")
+
+# Fields from a pax header that are affected by hdrcharset.
+PAX_NAME_FIELDS = set(("path", "linkpath", "uname", "gname"))
+
+# Fields in a pax header that are numbers, all other fields
+# are treated as strings.
+PAX_NUMBER_FIELDS = {
+ "atime": float,
+ "ctime": float,
+ "mtime": float,
+ "uid": int,
+ "gid": int,
+ "size": int
+}
+
+#---------------------------------------------------------
+# Bits used in the mode field, values in octal.
+#---------------------------------------------------------
+S_IFLNK = 0o120000 # symbolic link
+S_IFREG = 0o100000 # regular file
+S_IFBLK = 0o060000 # block device
+S_IFDIR = 0o040000 # directory
+S_IFCHR = 0o020000 # character device
+S_IFIFO = 0o010000 # fifo
+
+TSUID = 0o4000 # set UID on execution
+TSGID = 0o2000 # set GID on execution
+TSVTX = 0o1000 # reserved
+
+TUREAD = 0o400 # read by owner
+TUWRITE = 0o200 # write by owner
+TUEXEC = 0o100 # execute/search by owner
+TGREAD = 0o040 # read by group
+TGWRITE = 0o020 # write by group
+TGEXEC = 0o010 # execute/search by group
+TOREAD = 0o004 # read by other
+TOWRITE = 0o002 # write by other
+TOEXEC = 0o001 # execute/search by other
+
+#---------------------------------------------------------
+# initialization
+#---------------------------------------------------------
+if os.name in ("nt", "ce"):
+ ENCODING = "utf-8"
+else:
+ ENCODING = sys.getfilesystemencoding()
+
+#---------------------------------------------------------
+# Some useful functions
+#---------------------------------------------------------
+
+def stn(s, length, encoding, errors):
+ """Convert a string to a null-terminated bytes object.
+ """
+ s = s.encode(encoding, errors)
+ return s[:length] + (length - len(s)) * NUL
+
+def nts(s, encoding, errors):
+ """Convert a null-terminated bytes object to a string.
+ """
+ p = s.find(b"\0")
+ if p != -1:
+ s = s[:p]
+ return s.decode(encoding, errors)
+
+def nti(s):
+ """Convert a number field to a python number.
+ """
+ # There are two possible encodings for a number field, see
+ # itn() below.
+ if s[0] != chr(0o200):
+ try:
+ n = int(nts(s, "ascii", "strict") or "0", 8)
+ except ValueError:
+ raise InvalidHeaderError("invalid header")
+ else:
+ n = 0
+ for i in range(len(s) - 1):
+ n <<= 8
+ n += ord(s[i + 1])
+ return n
+
+def itn(n, digits=8, format=DEFAULT_FORMAT):
+ """Convert a python number to a number field.
+ """
+ # POSIX 1003.1-1988 requires numbers to be encoded as a string of
+ # octal digits followed by a null-byte, this allows values up to
+ # (8**(digits-1))-1. GNU tar allows storing numbers greater than
+ # that if necessary. A leading 0o200 byte indicates this particular
+ # encoding, the following digits-1 bytes are a big-endian
+ # representation. This allows values up to (256**(digits-1))-1.
+ if 0 <= n < 8 ** (digits - 1):
+ s = ("%0*o" % (digits - 1, n)).encode("ascii") + NUL
+ else:
+ if format != GNU_FORMAT or n >= 256 ** (digits - 1):
+ raise ValueError("overflow in number field")
+
+ if n < 0:
+ # XXX We mimic GNU tar's behaviour with negative numbers,
+ # this could raise OverflowError.
+ n = struct.unpack("L", struct.pack("l", n))[0]
+
+ s = bytearray()
+ for i in range(digits - 1):
+ s.insert(0, n & 0o377)
+ n >>= 8
+ s.insert(0, 0o200)
+ return s
+
+def calc_chksums(buf):
+ """Calculate the checksum for a member's header by summing up all
+ characters except for the chksum field which is treated as if
+ it was filled with spaces. According to the GNU tar sources,
+ some tars (Sun and NeXT) calculate chksum with signed char,
+ which will be different if there are chars in the buffer with
+ the high bit set. So we calculate two checksums, unsigned and
+ signed.
+ """
+ unsigned_chksum = 256 + sum(struct.unpack("148B", buf[:148]) + struct.unpack("356B", buf[156:512]))
+ signed_chksum = 256 + sum(struct.unpack("148b", buf[:148]) + struct.unpack("356b", buf[156:512]))
+ return unsigned_chksum, signed_chksum
+
+def copyfileobj(src, dst, length=None):
+ """Copy length bytes from fileobj src to fileobj dst.
+ If length is None, copy the entire content.
+ """
+ if length == 0: