Index: recipe_engine/third_party/setuptools/command/easy_install.py |
diff --git a/recipe_engine/third_party/setuptools/command/easy_install.py b/recipe_engine/third_party/setuptools/command/easy_install.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a71a7f363a132b34c295b2e06aa62ed8b698e1c3 |
--- /dev/null |
+++ b/recipe_engine/third_party/setuptools/command/easy_install.py |
@@ -0,0 +1,2157 @@ |
+#!/usr/bin/env python |
+ |
+""" |
+Easy Install |
+------------ |
+ |
+A tool for doing automatic download/extract/build of distutils-based Python |
+packages. For detailed documentation, see the accompanying EasyInstall.txt |
+file, or visit the `EasyInstall home page`__. |
+ |
+__ https://pythonhosted.org/setuptools/easy_install.html |
+ |
+""" |
+ |
+from glob import glob |
+from distutils.util import get_platform |
+from distutils.util import convert_path, subst_vars |
+from distutils.errors import DistutilsArgError, DistutilsOptionError, \ |
+ DistutilsError, DistutilsPlatformError |
+from distutils.command.install import INSTALL_SCHEMES, SCHEME_KEYS |
+from distutils import log, dir_util |
+from distutils.command.build_scripts import first_line_re |
+import sys |
+import os |
+import zipimport |
+import shutil |
+import tempfile |
+import zipfile |
+import re |
+import stat |
+import random |
+import platform |
+import textwrap |
+import warnings |
+import site |
+import struct |
+ |
+from setuptools import Command |
+from setuptools.sandbox import run_setup |
+from setuptools.py31compat import get_path, get_config_vars |
+from setuptools.command import setopt |
+from setuptools.archive_util import unpack_archive |
+from setuptools.package_index import PackageIndex |
+from setuptools.package_index import URL_SCHEME |
+from setuptools.command import bdist_egg, egg_info |
+from setuptools.compat import (iteritems, maxsize, basestring, unicode, |
+ reraise, PY2, PY3) |
+from pkg_resources import ( |
+ yield_lines, normalize_path, resource_string, ensure_directory, |
+ get_distribution, find_distributions, Environment, Requirement, |
+ Distribution, PathMetadata, EggMetadata, WorkingSet, DistributionNotFound, |
+ VersionConflict, DEVELOP_DIST, |
+) |
+import pkg_resources |
+ |
+ |
+# Turn on PEP440Warnings |
+warnings.filterwarnings("default", category=pkg_resources.PEP440Warning) |
+ |
+ |
+sys_executable = os.environ.get('__PYVENV_LAUNCHER__', |
+ os.path.normpath(sys.executable)) |
+ |
+ |
+__all__ = [ |
+ 'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg', |
+ 'main', 'get_exe_prefixes', |
+] |
+ |
+ |
+def is_64bit(): |
+ return struct.calcsize("P") == 8 |
+ |
+ |
+def samefile(p1, p2): |
+ both_exist = os.path.exists(p1) and os.path.exists(p2) |
+ use_samefile = hasattr(os.path, 'samefile') and both_exist |
+ if use_samefile: |
+ return os.path.samefile(p1, p2) |
+ norm_p1 = os.path.normpath(os.path.normcase(p1)) |
+ norm_p2 = os.path.normpath(os.path.normcase(p2)) |
+ return norm_p1 == norm_p2 |
+ |
+ |
+if PY2: |
+ def _to_ascii(s): |
+ return s |
+ |
+ def isascii(s): |
+ try: |
+ unicode(s, 'ascii') |
+ return True |
+ except UnicodeError: |
+ return False |
+else: |
+ def _to_ascii(s): |
+ return s.encode('ascii') |
+ |
+ def isascii(s): |
+ try: |
+ s.encode('ascii') |
+ return True |
+ except UnicodeError: |
+ return False |
+ |
+ |
+class easy_install(Command): |
+ """Manage a download/build/install process""" |
+ description = "Find/get/install Python packages" |
+ command_consumes_arguments = True |
+ |
+ user_options = [ |
+ ('prefix=', None, "installation prefix"), |
+ ("zip-ok", "z", "install package as a zipfile"), |
+ ("multi-version", "m", "make apps have to require() a version"), |
+ ("upgrade", "U", "force upgrade (searches PyPI for latest versions)"), |
+ ("install-dir=", "d", "install package to DIR"), |
+ ("script-dir=", "s", "install scripts to DIR"), |
+ ("exclude-scripts", "x", "Don't install scripts"), |
+ ("always-copy", "a", "Copy all needed packages to install dir"), |
+ ("index-url=", "i", "base URL of Python Package Index"), |
+ ("find-links=", "f", "additional URL(s) to search for packages"), |
+ ("build-directory=", "b", |
+ "download/extract/build in DIR; keep the results"), |
+ ('optimize=', 'O', |
+ "also compile with optimization: -O1 for \"python -O\", " |
+ "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), |
+ ('record=', None, |
+ "filename in which to record list of installed files"), |
+ ('always-unzip', 'Z', "don't install as a zipfile, no matter what"), |
+ ('site-dirs=', 'S', "list of directories where .pth files work"), |
+ ('editable', 'e', "Install specified packages in editable form"), |
+ ('no-deps', 'N', "don't install dependencies"), |
+ ('allow-hosts=', 'H', "pattern(s) that hostnames must match"), |
+ ('local-snapshots-ok', 'l', |
+ "allow building eggs from local checkouts"), |
+ ('version', None, "print version information and exit"), |
+ ('no-find-links', None, |
+ "Don't load find-links defined in packages being installed") |
+ ] |
+ boolean_options = [ |
+ 'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy', |
+ 'editable', |
+ 'no-deps', 'local-snapshots-ok', 'version' |
+ ] |
+ |
+ if site.ENABLE_USER_SITE: |
+ help_msg = "install in user site-package '%s'" % site.USER_SITE |
+ user_options.append(('user', None, help_msg)) |
+ boolean_options.append('user') |
+ |
+ negative_opt = {'always-unzip': 'zip-ok'} |
+ create_index = PackageIndex |
+ |
+ def initialize_options(self): |
+ if site.ENABLE_USER_SITE: |
+ whereami = os.path.abspath(__file__) |
+ self.user = whereami.startswith(site.USER_SITE) |
+ else: |
+ self.user = 0 |
+ |
+ self.zip_ok = self.local_snapshots_ok = None |
+ self.install_dir = self.script_dir = self.exclude_scripts = None |
+ self.index_url = None |
+ self.find_links = None |
+ self.build_directory = None |
+ self.args = None |
+ self.optimize = self.record = None |
+ self.upgrade = self.always_copy = self.multi_version = None |
+ self.editable = self.no_deps = self.allow_hosts = None |
+ self.root = self.prefix = self.no_report = None |
+ self.version = None |
+ self.install_purelib = None # for pure module distributions |
+ self.install_platlib = None # non-pure (dists w/ extensions) |
+ self.install_headers = None # for C/C++ headers |
+ self.install_lib = None # set to either purelib or platlib |
+ self.install_scripts = None |
+ self.install_data = None |
+ self.install_base = None |
+ self.install_platbase = None |
+ if site.ENABLE_USER_SITE: |
+ self.install_userbase = site.USER_BASE |
+ self.install_usersite = site.USER_SITE |
+ else: |
+ self.install_userbase = None |
+ self.install_usersite = None |
+ self.no_find_links = None |
+ |
+ # Options not specifiable via command line |
+ self.package_index = None |
+ self.pth_file = self.always_copy_from = None |
+ self.site_dirs = None |
+ self.installed_projects = {} |
+ self.sitepy_installed = False |
+ # Always read easy_install options, even if we are subclassed, or have |
+ # an independent instance created. This ensures that defaults will |
+ # always come from the standard configuration file(s)' "easy_install" |
+ # section, even if this is a "develop" or "install" command, or some |
+ # other embedding. |
+ self._dry_run = None |
+ self.verbose = self.distribution.verbose |
+ self.distribution._set_command_options( |
+ self, self.distribution.get_option_dict('easy_install') |
+ ) |
+ |
+ def delete_blockers(self, blockers): |
+ for filename in blockers: |
+ if os.path.exists(filename) or os.path.islink(filename): |
+ log.info("Deleting %s", filename) |
+ if not self.dry_run: |
+ if (os.path.isdir(filename) and |
+ not os.path.islink(filename)): |
+ rmtree(filename) |
+ else: |
+ os.unlink(filename) |
+ |
+ def finalize_options(self): |
+ if self.version: |
+ print('setuptools %s' % get_distribution('setuptools').version) |
+ sys.exit() |
+ |
+ py_version = sys.version.split()[0] |
+ prefix, exec_prefix = get_config_vars('prefix', 'exec_prefix') |
+ |
+ self.config_vars = { |
+ 'dist_name': self.distribution.get_name(), |
+ 'dist_version': self.distribution.get_version(), |
+ 'dist_fullname': self.distribution.get_fullname(), |
+ 'py_version': py_version, |
+ 'py_version_short': py_version[0:3], |
+ 'py_version_nodot': py_version[0] + py_version[2], |
+ 'sys_prefix': prefix, |
+ 'prefix': prefix, |
+ 'sys_exec_prefix': exec_prefix, |
+ 'exec_prefix': exec_prefix, |
+ # Only python 3.2+ has abiflags |
+ 'abiflags': getattr(sys, 'abiflags', ''), |
+ } |
+ |
+ if site.ENABLE_USER_SITE: |
+ self.config_vars['userbase'] = self.install_userbase |
+ self.config_vars['usersite'] = self.install_usersite |
+ |
+ # fix the install_dir if "--user" was used |
+ # XXX: duplicate of the code in the setup command |
+ if self.user and site.ENABLE_USER_SITE: |
+ self.create_home_path() |
+ if self.install_userbase is None: |
+ raise DistutilsPlatformError( |
+ "User base directory is not specified") |
+ self.install_base = self.install_platbase = self.install_userbase |
+ if os.name == 'posix': |
+ self.select_scheme("unix_user") |
+ else: |
+ self.select_scheme(os.name + "_user") |
+ |
+ self.expand_basedirs() |
+ self.expand_dirs() |
+ |
+ self._expand('install_dir', 'script_dir', 'build_directory', |
+ 'site_dirs') |
+ # If a non-default installation directory was specified, default the |
+ # script directory to match it. |
+ if self.script_dir is None: |
+ self.script_dir = self.install_dir |
+ |
+ if self.no_find_links is None: |
+ self.no_find_links = False |
+ |
+ # Let install_dir get set by install_lib command, which in turn |
+ # gets its info from the install command, and takes into account |
+ # --prefix and --home and all that other crud. |
+ self.set_undefined_options( |
+ 'install_lib', ('install_dir', 'install_dir') |
+ ) |
+ # Likewise, set default script_dir from 'install_scripts.install_dir' |
+ self.set_undefined_options( |
+ 'install_scripts', ('install_dir', 'script_dir') |
+ ) |
+ |
+ if self.user and self.install_purelib: |
+ self.install_dir = self.install_purelib |
+ self.script_dir = self.install_scripts |
+ # default --record from the install command |
+ self.set_undefined_options('install', ('record', 'record')) |
+ # Should this be moved to the if statement below? It's not used |
+ # elsewhere |
+ normpath = map(normalize_path, sys.path) |
+ self.all_site_dirs = get_site_dirs() |
+ if self.site_dirs is not None: |
+ site_dirs = [ |
+ os.path.expanduser(s.strip()) for s in |
+ self.site_dirs.split(',') |
+ ] |
+ for d in site_dirs: |
+ if not os.path.isdir(d): |
+ log.warn("%s (in --site-dirs) does not exist", d) |
+ elif normalize_path(d) not in normpath: |
+ raise DistutilsOptionError( |
+ d + " (in --site-dirs) is not on sys.path" |
+ ) |
+ else: |
+ self.all_site_dirs.append(normalize_path(d)) |
+ if not self.editable: |
+ self.check_site_dir() |
+ self.index_url = self.index_url or "https://pypi.python.org/simple" |
+ self.shadow_path = self.all_site_dirs[:] |
+ for path_item in self.install_dir, normalize_path(self.script_dir): |
+ if path_item not in self.shadow_path: |
+ self.shadow_path.insert(0, path_item) |
+ |
+ if self.allow_hosts is not None: |
+ hosts = [s.strip() for s in self.allow_hosts.split(',')] |
+ else: |
+ hosts = ['*'] |
+ if self.package_index is None: |
+ self.package_index = self.create_index( |
+ self.index_url, search_path=self.shadow_path, hosts=hosts, |
+ ) |
+ self.local_index = Environment(self.shadow_path + sys.path) |
+ |
+ if self.find_links is not None: |
+ if isinstance(self.find_links, basestring): |
+ self.find_links = self.find_links.split() |
+ else: |
+ self.find_links = [] |
+ if self.local_snapshots_ok: |
+ self.package_index.scan_egg_links(self.shadow_path + sys.path) |
+ if not self.no_find_links: |
+ self.package_index.add_find_links(self.find_links) |
+ self.set_undefined_options('install_lib', ('optimize', 'optimize')) |
+ if not isinstance(self.optimize, int): |
+ try: |
+ self.optimize = int(self.optimize) |
+ if not (0 <= self.optimize <= 2): |
+ raise ValueError |
+ except ValueError: |
+ raise DistutilsOptionError("--optimize must be 0, 1, or 2") |
+ |
+ if self.editable and not self.build_directory: |
+ raise DistutilsArgError( |
+ "Must specify a build directory (-b) when using --editable" |
+ ) |
+ if not self.args: |
+ raise DistutilsArgError( |
+ "No urls, filenames, or requirements specified (see --help)") |
+ |
+ self.outputs = [] |
+ |
+ def _expand_attrs(self, attrs): |
+ for attr in attrs: |
+ val = getattr(self, attr) |
+ if val is not None: |
+ if os.name == 'posix' or os.name == 'nt': |
+ val = os.path.expanduser(val) |
+ val = subst_vars(val, self.config_vars) |
+ setattr(self, attr, val) |
+ |
+ def expand_basedirs(self): |
+ """Calls `os.path.expanduser` on install_base, install_platbase and |
+ root.""" |
+ self._expand_attrs(['install_base', 'install_platbase', 'root']) |
+ |
+ def expand_dirs(self): |
+ """Calls `os.path.expanduser` on install dirs.""" |
+ self._expand_attrs(['install_purelib', 'install_platlib', |
+ 'install_lib', 'install_headers', |
+ 'install_scripts', 'install_data', ]) |
+ |
+ def run(self): |
+ if self.verbose != self.distribution.verbose: |
+ log.set_verbosity(self.verbose) |
+ try: |
+ for spec in self.args: |
+ self.easy_install(spec, not self.no_deps) |
+ if self.record: |
+ outputs = self.outputs |
+ if self.root: # strip any package prefix |
+ root_len = len(self.root) |
+ for counter in range(len(outputs)): |
+ outputs[counter] = outputs[counter][root_len:] |
+ from distutils import file_util |
+ |
+ self.execute( |
+ file_util.write_file, (self.record, outputs), |
+ "writing list of installed files to '%s'" % |
+ self.record |
+ ) |
+ self.warn_deprecated_options() |
+ finally: |
+ log.set_verbosity(self.distribution.verbose) |
+ |
+ def pseudo_tempname(self): |
+ """Return a pseudo-tempname base in the install directory. |
+ This code is intentionally naive; if a malicious party can write to |
+ the target directory you're already in deep doodoo. |
+ """ |
+ try: |
+ pid = os.getpid() |
+ except: |
+ pid = random.randint(0, maxsize) |
+ return os.path.join(self.install_dir, "test-easy-install-%s" % pid) |
+ |
+ def warn_deprecated_options(self): |
+ pass |
+ |
+ def check_site_dir(self): |
+ """Verify that self.install_dir is .pth-capable dir, if needed""" |
+ |
+ instdir = normalize_path(self.install_dir) |
+ pth_file = os.path.join(instdir, 'easy-install.pth') |
+ |
+ # Is it a configured, PYTHONPATH, implicit, or explicit site dir? |
+ is_site_dir = instdir in self.all_site_dirs |
+ |
+ if not is_site_dir and not self.multi_version: |
+ # No? Then directly test whether it does .pth file processing |
+ is_site_dir = self.check_pth_processing() |
+ else: |
+ # make sure we can write to target dir |
+ testfile = self.pseudo_tempname() + '.write-test' |
+ test_exists = os.path.exists(testfile) |
+ try: |
+ if test_exists: |
+ os.unlink(testfile) |
+ open(testfile, 'w').close() |
+ os.unlink(testfile) |
+ except (OSError, IOError): |
+ self.cant_write_to_target() |
+ |
+ if not is_site_dir and not self.multi_version: |
+ # Can't install non-multi to non-site dir |
+ raise DistutilsError(self.no_default_version_msg()) |
+ |
+ if is_site_dir: |
+ if self.pth_file is None: |
+ self.pth_file = PthDistributions(pth_file, self.all_site_dirs) |
+ else: |
+ self.pth_file = None |
+ |
+ PYTHONPATH = os.environ.get('PYTHONPATH', '').split(os.pathsep) |
+ if instdir not in map(normalize_path, [_f for _f in PYTHONPATH if _f]): |
+ # only PYTHONPATH dirs need a site.py, so pretend it's there |
+ self.sitepy_installed = True |
+ elif self.multi_version and not os.path.exists(pth_file): |
+ self.sitepy_installed = True # don't need site.py in this case |
+ self.pth_file = None # and don't create a .pth file |
+ self.install_dir = instdir |
+ |
+ def cant_write_to_target(self): |
+ template = """can't create or remove files in install directory |
+ |
+The following error occurred while trying to add or remove files in the |
+installation directory: |
+ |
+ %s |
+ |
+The installation directory you specified (via --install-dir, --prefix, or |
+the distutils default setting) was: |
+ |
+ %s |
+""" |
+ msg = template % (sys.exc_info()[1], self.install_dir,) |
+ |
+ if not os.path.exists(self.install_dir): |
+ msg += """ |
+This directory does not currently exist. Please create it and try again, or |
+choose a different installation directory (using the -d or --install-dir |
+option). |
+""" |
+ else: |
+ msg += """ |
+Perhaps your account does not have write access to this directory? If the |
+installation directory is a system-owned directory, you may need to sign in |
+as the administrator or "root" account. If you do not have administrative |
+access to this machine, you may wish to choose a different installation |
+directory, preferably one that is listed in your PYTHONPATH environment |
+variable. |
+ |
+For information on other options, you may wish to consult the |
+documentation at: |
+ |
+ https://pythonhosted.org/setuptools/easy_install.html |
+ |
+Please make the appropriate changes for your system and try again. |
+""" |
+ raise DistutilsError(msg) |
+ |
+ def check_pth_processing(self): |
+ """Empirically verify whether .pth files are supported in inst. dir""" |
+ instdir = self.install_dir |
+ log.info("Checking .pth file support in %s", instdir) |
+ pth_file = self.pseudo_tempname() + ".pth" |
+ ok_file = pth_file + '.ok' |
+ ok_exists = os.path.exists(ok_file) |
+ try: |
+ if ok_exists: |
+ os.unlink(ok_file) |
+ dirname = os.path.dirname(ok_file) |
+ if not os.path.exists(dirname): |
+ os.makedirs(dirname) |
+ f = open(pth_file, 'w') |
+ except (OSError, IOError): |
+ self.cant_write_to_target() |
+ else: |
+ try: |
+ f.write("import os; f = open(%r, 'w'); f.write('OK'); " |
+ "f.close()\n" % (ok_file,)) |
+ f.close() |
+ f = None |
+ executable = sys.executable |
+ if os.name == 'nt': |
+ dirname, basename = os.path.split(executable) |
+ alt = os.path.join(dirname, 'pythonw.exe') |
+ if (basename.lower() == 'python.exe' and |
+ os.path.exists(alt)): |
+ # use pythonw.exe to avoid opening a console window |
+ executable = alt |
+ |
+ from distutils.spawn import spawn |
+ |
+ spawn([executable, '-E', '-c', 'pass'], 0) |
+ |
+ if os.path.exists(ok_file): |
+ log.info( |
+ "TEST PASSED: %s appears to support .pth files", |
+ instdir |
+ ) |
+ return True |
+ finally: |
+ if f: |
+ f.close() |
+ if os.path.exists(ok_file): |
+ os.unlink(ok_file) |
+ if os.path.exists(pth_file): |
+ os.unlink(pth_file) |
+ if not self.multi_version: |
+ log.warn("TEST FAILED: %s does NOT support .pth files", instdir) |
+ return False |
+ |
+ def install_egg_scripts(self, dist): |
+ """Write all the scripts for `dist`, unless scripts are excluded""" |
+ if not self.exclude_scripts and dist.metadata_isdir('scripts'): |
+ for script_name in dist.metadata_listdir('scripts'): |
+ if dist.metadata_isdir('scripts/' + script_name): |
+ # The "script" is a directory, likely a Python 3 |
+ # __pycache__ directory, so skip it. |
+ continue |
+ self.install_script( |
+ dist, script_name, |
+ dist.get_metadata('scripts/' + script_name) |
+ ) |
+ self.install_wrapper_scripts(dist) |
+ |
+ def add_output(self, path): |
+ if os.path.isdir(path): |
+ for base, dirs, files in os.walk(path): |
+ for filename in files: |
+ self.outputs.append(os.path.join(base, filename)) |
+ else: |
+ self.outputs.append(path) |
+ |
+ def not_editable(self, spec): |
+ if self.editable: |
+ raise DistutilsArgError( |
+ "Invalid argument %r: you can't use filenames or URLs " |
+ "with --editable (except via the --find-links option)." |
+ % (spec,) |
+ ) |
+ |
+ def check_editable(self, spec): |
+ if not self.editable: |
+ return |
+ |
+ if os.path.exists(os.path.join(self.build_directory, spec.key)): |
+ raise DistutilsArgError( |
+ "%r already exists in %s; can't do a checkout there" % |
+ (spec.key, self.build_directory) |
+ ) |
+ |
+ def easy_install(self, spec, deps=False): |
+ tmpdir = tempfile.mkdtemp(prefix="easy_install-") |
+ download = None |
+ if not self.editable: |
+ self.install_site_py() |
+ |
+ try: |
+ if not isinstance(spec, Requirement): |
+ if URL_SCHEME(spec): |
+ # It's a url, download it to tmpdir and process |
+ self.not_editable(spec) |
+ download = self.package_index.download(spec, tmpdir) |
+ return self.install_item(None, download, tmpdir, deps, |
+ True) |
+ |
+ elif os.path.exists(spec): |
+ # Existing file or directory, just process it directly |
+ self.not_editable(spec) |
+ return self.install_item(None, spec, tmpdir, deps, True) |
+ else: |
+ spec = parse_requirement_arg(spec) |
+ |
+ self.check_editable(spec) |
+ dist = self.package_index.fetch_distribution( |
+ spec, tmpdir, self.upgrade, self.editable, |
+ not self.always_copy, self.local_index |
+ ) |
+ if dist is None: |
+ msg = "Could not find suitable distribution for %r" % spec |
+ if self.always_copy: |
+ msg += " (--always-copy skips system and development eggs)" |
+ raise DistutilsError(msg) |
+ elif dist.precedence == DEVELOP_DIST: |
+ # .egg-info dists don't need installing, just process deps |
+ self.process_distribution(spec, dist, deps, "Using") |
+ return dist |
+ else: |
+ return self.install_item(spec, dist.location, tmpdir, deps) |
+ |
+ finally: |
+ if os.path.exists(tmpdir): |
+ rmtree(tmpdir) |
+ |
+ def install_item(self, spec, download, tmpdir, deps, install_needed=False): |
+ |
+ # Installation is also needed if file in tmpdir or is not an egg |
+ install_needed = install_needed or self.always_copy |
+ install_needed = install_needed or os.path.dirname(download) == tmpdir |
+ install_needed = install_needed or not download.endswith('.egg') |
+ install_needed = install_needed or ( |
+ self.always_copy_from is not None and |
+ os.path.dirname(normalize_path(download)) == |
+ normalize_path(self.always_copy_from) |
+ ) |
+ |
+ if spec and not install_needed: |
+ # at this point, we know it's a local .egg, we just don't know if |
+ # it's already installed. |
+ for dist in self.local_index[spec.project_name]: |
+ if dist.location == download: |
+ break |
+ else: |
+ install_needed = True # it's not in the local index |
+ |
+ log.info("Processing %s", os.path.basename(download)) |
+ |
+ if install_needed: |
+ dists = self.install_eggs(spec, download, tmpdir) |
+ for dist in dists: |
+ self.process_distribution(spec, dist, deps) |
+ else: |
+ dists = [self.egg_distribution(download)] |
+ self.process_distribution(spec, dists[0], deps, "Using") |
+ |
+ if spec is not None: |
+ for dist in dists: |
+ if dist in spec: |
+ return dist |
+ |
+ def select_scheme(self, name): |
+ """Sets the install directories by applying the install schemes.""" |
+ # it's the caller's problem if they supply a bad name! |
+ scheme = INSTALL_SCHEMES[name] |
+ for key in SCHEME_KEYS: |
+ attrname = 'install_' + key |
+ if getattr(self, attrname) is None: |
+ setattr(self, attrname, scheme[key]) |
+ |
+ def process_distribution(self, requirement, dist, deps=True, *info): |
+ self.update_pth(dist) |
+ self.package_index.add(dist) |
+ if dist in self.local_index[dist.key]: |
+ self.local_index.remove(dist) |
+ self.local_index.add(dist) |
+ self.install_egg_scripts(dist) |
+ self.installed_projects[dist.key] = dist |
+ log.info(self.installation_report(requirement, dist, *info)) |
+ if (dist.has_metadata('dependency_links.txt') and |
+ not self.no_find_links): |
+ self.package_index.add_find_links( |
+ dist.get_metadata_lines('dependency_links.txt') |
+ ) |
+ if not deps and not self.always_copy: |
+ return |
+ elif requirement is not None and dist.key != requirement.key: |
+ log.warn("Skipping dependencies for %s", dist) |
+ return # XXX this is not the distribution we were looking for |
+ elif requirement is None or dist not in requirement: |
+ # if we wound up with a different version, resolve what we've got |
+ distreq = dist.as_requirement() |
+ requirement = requirement or distreq |
+ requirement = Requirement( |
+ distreq.project_name, distreq.specs, requirement.extras |
+ ) |
+ log.info("Processing dependencies for %s", requirement) |
+ try: |
+ distros = WorkingSet([]).resolve( |
+ [requirement], self.local_index, self.easy_install |
+ ) |
+ except DistributionNotFound: |
+ e = sys.exc_info()[1] |
+ raise DistutilsError( |
+ "Could not find required distribution %s" % e.args |
+ ) |
+ except VersionConflict: |
+ e = sys.exc_info()[1] |
+ raise DistutilsError( |
+ "Installed distribution %s conflicts with requirement %s" |
+ % e.args |
+ ) |
+ if self.always_copy or self.always_copy_from: |
+ # Force all the relevant distros to be copied or activated |
+ for dist in distros: |
+ if dist.key not in self.installed_projects: |
+ self.easy_install(dist.as_requirement()) |
+ log.info("Finished processing dependencies for %s", requirement) |
+ |
+ def should_unzip(self, dist): |
+ if self.zip_ok is not None: |
+ return not self.zip_ok |
+ if dist.has_metadata('not-zip-safe'): |
+ return True |
+ if not dist.has_metadata('zip-safe'): |
+ return True |
+ return False |
+ |
+ def maybe_move(self, spec, dist_filename, setup_base): |
+ dst = os.path.join(self.build_directory, spec.key) |
+ if os.path.exists(dst): |
+ msg = ("%r already exists in %s; build directory %s will not be " |
+ "kept") |
+ log.warn(msg, spec.key, self.build_directory, setup_base) |
+ return setup_base |
+ if os.path.isdir(dist_filename): |
+ setup_base = dist_filename |
+ else: |
+ if os.path.dirname(dist_filename) == setup_base: |
+ os.unlink(dist_filename) # get it out of the tmp dir |
+ contents = os.listdir(setup_base) |
+ if len(contents) == 1: |
+ dist_filename = os.path.join(setup_base, contents[0]) |
+ if os.path.isdir(dist_filename): |
+ # if the only thing there is a directory, move it instead |
+ setup_base = dist_filename |
+ ensure_directory(dst) |
+ shutil.move(setup_base, dst) |
+ return dst |
+ |
+ def install_wrapper_scripts(self, dist): |
+ if not self.exclude_scripts: |
+ for args in get_script_args(dist): |
+ self.write_script(*args) |
+ |
+ def install_script(self, dist, script_name, script_text, dev_path=None): |
+ """Generate a legacy script wrapper and install it""" |
+ spec = str(dist.as_requirement()) |
+ is_script = is_python_script(script_text, script_name) |
+ |
+ if is_script: |
+ script_text = (get_script_header(script_text) + |
+ self._load_template(dev_path) % locals()) |
+ self.write_script(script_name, _to_ascii(script_text), 'b') |
+ |
+ @staticmethod |
+ def _load_template(dev_path): |
+ """ |
+ There are a couple of template scripts in the package. This |
+ function loads one of them and prepares it for use. |
+ """ |
+ # See https://bitbucket.org/pypa/setuptools/issue/134 for info |
+ # on script file naming and downstream issues with SVR4 |
+ name = 'script.tmpl' |
+ if dev_path: |
+ name = name.replace('.tmpl', ' (dev).tmpl') |
+ |
+ raw_bytes = resource_string('setuptools', name) |
+ return raw_bytes.decode('utf-8') |
+ |
+ def write_script(self, script_name, contents, mode="t", blockers=()): |
+ """Write an executable file to the scripts directory""" |
+ self.delete_blockers( # clean up old .py/.pyw w/o a script |
+ [os.path.join(self.script_dir, x) for x in blockers] |
+ ) |
+ log.info("Installing %s script to %s", script_name, self.script_dir) |
+ target = os.path.join(self.script_dir, script_name) |
+ self.add_output(target) |
+ |
+ mask = current_umask() |
+ if not self.dry_run: |
+ ensure_directory(target) |
+ if os.path.exists(target): |
+ os.unlink(target) |
+ f = open(target, "w" + mode) |
+ f.write(contents) |
+ f.close() |
+ chmod(target, 0o777 - mask) |
+ |
+ def install_eggs(self, spec, dist_filename, tmpdir): |
+ # .egg dirs or files are already built, so just return them |
+ if dist_filename.lower().endswith('.egg'): |
+ return [self.install_egg(dist_filename, tmpdir)] |
+ elif dist_filename.lower().endswith('.exe'): |
+ return [self.install_exe(dist_filename, tmpdir)] |
+ |
+ # Anything else, try to extract and build |
+ setup_base = tmpdir |
+ if os.path.isfile(dist_filename) and not dist_filename.endswith('.py'): |
+ unpack_archive(dist_filename, tmpdir, self.unpack_progress) |
+ elif os.path.isdir(dist_filename): |
+ setup_base = os.path.abspath(dist_filename) |
+ |
+ if (setup_base.startswith(tmpdir) # something we downloaded |
+ and self.build_directory and spec is not None): |
+ setup_base = self.maybe_move(spec, dist_filename, setup_base) |
+ |
+ # Find the setup.py file |
+ setup_script = os.path.join(setup_base, 'setup.py') |
+ |
+ if not os.path.exists(setup_script): |
+ setups = glob(os.path.join(setup_base, '*', 'setup.py')) |
+ if not setups: |
+ raise DistutilsError( |
+ "Couldn't find a setup script in %s" % |
+ os.path.abspath(dist_filename) |
+ ) |
+ if len(setups) > 1: |
+ raise DistutilsError( |
+ "Multiple setup scripts in %s" % |
+ os.path.abspath(dist_filename) |
+ ) |
+ setup_script = setups[0] |
+ |
+ # Now run it, and return the result |
+ if self.editable: |
+ log.info(self.report_editable(spec, setup_script)) |
+ return [] |
+ else: |
+ return self.build_and_install(setup_script, setup_base) |
+ |
+ def egg_distribution(self, egg_path): |
+ if os.path.isdir(egg_path): |
+ metadata = PathMetadata(egg_path, os.path.join(egg_path, |
+ 'EGG-INFO')) |
+ else: |
+ metadata = EggMetadata(zipimport.zipimporter(egg_path)) |
+ return Distribution.from_filename(egg_path, metadata=metadata) |
+ |
+ def install_egg(self, egg_path, tmpdir): |
+ destination = os.path.join(self.install_dir, |
+ os.path.basename(egg_path)) |
+ destination = os.path.abspath(destination) |
+ if not self.dry_run: |
+ ensure_directory(destination) |
+ |
+ dist = self.egg_distribution(egg_path) |
+ if not samefile(egg_path, destination): |
+ if os.path.isdir(destination) and not os.path.islink(destination): |
+ dir_util.remove_tree(destination, dry_run=self.dry_run) |
+ elif os.path.exists(destination): |
+ self.execute(os.unlink, (destination,), "Removing " + |
+ destination) |
+ try: |
+ new_dist_is_zipped = False |
+ if os.path.isdir(egg_path): |
+ if egg_path.startswith(tmpdir): |
+ f, m = shutil.move, "Moving" |
+ else: |
+ f, m = shutil.copytree, "Copying" |
+ elif self.should_unzip(dist): |
+ self.mkpath(destination) |
+ f, m = self.unpack_and_compile, "Extracting" |
+ else: |
+ new_dist_is_zipped = True |
+ if egg_path.startswith(tmpdir): |
+ f, m = shutil.move, "Moving" |
+ else: |
+ f, m = shutil.copy2, "Copying" |
+ self.execute(f, (egg_path, destination), |
+ (m + " %s to %s") % |
+ (os.path.basename(egg_path), |
+ os.path.dirname(destination))) |
+ update_dist_caches(destination, |
+ fix_zipimporter_caches=new_dist_is_zipped) |
+ except: |
+ update_dist_caches(destination, fix_zipimporter_caches=False) |
+ raise |
+ |
+ self.add_output(destination) |
+ return self.egg_distribution(destination) |
+ |
+ def install_exe(self, dist_filename, tmpdir): |
+ # See if it's valid, get data |
+ cfg = extract_wininst_cfg(dist_filename) |
+ if cfg is None: |
+ raise DistutilsError( |
+ "%s is not a valid distutils Windows .exe" % dist_filename |
+ ) |
+ # Create a dummy distribution object until we build the real distro |
+ dist = Distribution( |
+ None, |
+ project_name=cfg.get('metadata', 'name'), |
+ version=cfg.get('metadata', 'version'), platform=get_platform(), |
+ ) |
+ |
+ # Convert the .exe to an unpacked egg |
+ egg_path = dist.location = os.path.join(tmpdir, dist.egg_name() + |
+ '.egg') |
+ egg_tmp = egg_path + '.tmp' |
+ _egg_info = os.path.join(egg_tmp, 'EGG-INFO') |
+ pkg_inf = os.path.join(_egg_info, 'PKG-INFO') |
+ ensure_directory(pkg_inf) # make sure EGG-INFO dir exists |
+ dist._provider = PathMetadata(egg_tmp, _egg_info) # XXX |
+ self.exe_to_egg(dist_filename, egg_tmp) |
+ |
+ # Write EGG-INFO/PKG-INFO |
+ if not os.path.exists(pkg_inf): |
+ f = open(pkg_inf, 'w') |
+ f.write('Metadata-Version: 1.0\n') |
+ for k, v in cfg.items('metadata'): |
+ if k != 'target_version': |
+ f.write('%s: %s\n' % (k.replace('_', '-').title(), v)) |
+ f.close() |
+ script_dir = os.path.join(_egg_info, 'scripts') |
+ self.delete_blockers( # delete entry-point scripts to avoid duping |
+ [os.path.join(script_dir, args[0]) for args in |
+ get_script_args(dist)] |
+ ) |
+ # Build .egg file from tmpdir |
+ bdist_egg.make_zipfile( |
+ egg_path, egg_tmp, verbose=self.verbose, dry_run=self.dry_run |
+ ) |
+ # install the .egg |
+ return self.install_egg(egg_path, tmpdir) |
+ |
+ def exe_to_egg(self, dist_filename, egg_tmp): |
+ """Extract a bdist_wininst to the directories an egg would use""" |
+ # Check for .pth file and set up prefix translations |
+ prefixes = get_exe_prefixes(dist_filename) |
+ to_compile = [] |
+ native_libs = [] |
+ top_level = {} |
+ |
+ def process(src, dst): |
+ s = src.lower() |
+ for old, new in prefixes: |
+ if s.startswith(old): |
+ src = new + src[len(old):] |
+ parts = src.split('/') |
+ dst = os.path.join(egg_tmp, *parts) |
+ dl = dst.lower() |
+ if dl.endswith('.pyd') or dl.endswith('.dll'): |
+ parts[-1] = bdist_egg.strip_module(parts[-1]) |
+ top_level[os.path.splitext(parts[0])[0]] = 1 |
+ native_libs.append(src) |
+ elif dl.endswith('.py') and old != 'SCRIPTS/': |
+ top_level[os.path.splitext(parts[0])[0]] = 1 |
+ to_compile.append(dst) |
+ return dst |
+ if not src.endswith('.pth'): |
+ log.warn("WARNING: can't process %s", src) |
+ return None |
+ |
+ # extract, tracking .pyd/.dll->native_libs and .py -> to_compile |
+ unpack_archive(dist_filename, egg_tmp, process) |
+ stubs = [] |
+ for res in native_libs: |
+ if res.lower().endswith('.pyd'): # create stubs for .pyd's |
+ parts = res.split('/') |
+ resource = parts[-1] |
+ parts[-1] = bdist_egg.strip_module(parts[-1]) + '.py' |
+ pyfile = os.path.join(egg_tmp, *parts) |
+ to_compile.append(pyfile) |
+ stubs.append(pyfile) |
+ bdist_egg.write_stub(resource, pyfile) |
+ self.byte_compile(to_compile) # compile .py's |
+ bdist_egg.write_safety_flag( |
+ os.path.join(egg_tmp, 'EGG-INFO'), |
+ bdist_egg.analyze_egg(egg_tmp, stubs)) # write zip-safety flag |
+ |
+ for name in 'top_level', 'native_libs': |
+ if locals()[name]: |
+ txt = os.path.join(egg_tmp, 'EGG-INFO', name + '.txt') |
+ if not os.path.exists(txt): |
+ f = open(txt, 'w') |
+ f.write('\n'.join(locals()[name]) + '\n') |
+ f.close() |
+ |
+ def installation_report(self, req, dist, what="Installed"): |
+ """Helpful installation message for display to package users""" |
+ msg = "\n%(what)s %(eggloc)s%(extras)s" |
+ if self.multi_version and not self.no_report: |
+ msg += """ |
+ |
+Because this distribution was installed --multi-version, before you can |
+import modules from this package in an application, you will need to |
+'import pkg_resources' and then use a 'require()' call similar to one of |
+these examples, in order to select the desired version: |
+ |
+ pkg_resources.require("%(name)s") # latest installed version |
+ pkg_resources.require("%(name)s==%(version)s") # this exact version |
+ pkg_resources.require("%(name)s>=%(version)s") # this version or higher |
+""" |
+ if self.install_dir not in map(normalize_path, sys.path): |
+ msg += """ |
+ |
+Note also that the installation directory must be on sys.path at runtime for |
+this to work. (e.g. by being the application's script directory, by being on |
+PYTHONPATH, or by being added to sys.path by your code.) |
+""" |
+ eggloc = dist.location |
+ name = dist.project_name |
+ version = dist.version |
+ extras = '' # TODO: self.report_extras(req, dist) |
+ return msg % locals() |
+ |
+ def report_editable(self, spec, setup_script): |
+ dirname = os.path.dirname(setup_script) |
+ python = sys.executable |
+ return """\nExtracted editable version of %(spec)s to %(dirname)s |
+ |
+If it uses setuptools in its setup script, you can activate it in |
+"development" mode by going to that directory and running:: |
+ |
+ %(python)s setup.py develop |
+ |
+See the setuptools documentation for the "develop" command for more info. |
+""" % locals() |
+ |
+ def run_setup(self, setup_script, setup_base, args): |
+ sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg) |
+ sys.modules.setdefault('distutils.command.egg_info', egg_info) |
+ |
+ args = list(args) |
+ if self.verbose > 2: |
+ v = 'v' * (self.verbose - 1) |
+ args.insert(0, '-' + v) |
+ elif self.verbose < 2: |
+ args.insert(0, '-q') |
+ if self.dry_run: |
+ args.insert(0, '-n') |
+ log.info( |
+ "Running %s %s", setup_script[len(setup_base) + 1:], ' '.join(args) |
+ ) |
+ try: |
+ run_setup(setup_script, args) |
+ except SystemExit: |
+ v = sys.exc_info()[1] |
+ raise DistutilsError("Setup script exited with %s" % (v.args[0],)) |
+ |
+ def build_and_install(self, setup_script, setup_base): |
+ args = ['bdist_egg', '--dist-dir'] |
+ |
+ dist_dir = tempfile.mkdtemp( |
+ prefix='egg-dist-tmp-', dir=os.path.dirname(setup_script) |
+ ) |
+ try: |
+ self._set_fetcher_options(os.path.dirname(setup_script)) |
+ args.append(dist_dir) |
+ |
+ self.run_setup(setup_script, setup_base, args) |
+ all_eggs = Environment([dist_dir]) |
+ eggs = [] |
+ for key in all_eggs: |
+ for dist in all_eggs[key]: |
+ eggs.append(self.install_egg(dist.location, setup_base)) |
+ if not eggs and not self.dry_run: |
+ log.warn("No eggs found in %s (setup script problem?)", |
+ dist_dir) |
+ return eggs |
+ finally: |
+ rmtree(dist_dir) |
+ log.set_verbosity(self.verbose) # restore our log verbosity |
+ |
+ def _set_fetcher_options(self, base): |
+ """ |
+ When easy_install is about to run bdist_egg on a source dist, that |
+ source dist might have 'setup_requires' directives, requiring |
+ additional fetching. Ensure the fetcher options given to easy_install |
+ are available to that command as well. |
+ """ |
+ # find the fetch options from easy_install and write them out |
+ # to the setup.cfg file. |
+ ei_opts = self.distribution.get_option_dict('easy_install').copy() |
+ fetch_directives = ( |
+ 'find_links', 'site_dirs', 'index_url', 'optimize', |
+ 'site_dirs', 'allow_hosts', |
+ ) |
+ fetch_options = {} |
+ for key, val in ei_opts.items(): |
+ if key not in fetch_directives: |
+ continue |
+ fetch_options[key.replace('_', '-')] = val[1] |
+ # create a settings dictionary suitable for `edit_config` |
+ settings = dict(easy_install=fetch_options) |
+ cfg_filename = os.path.join(base, 'setup.cfg') |
+ setopt.edit_config(cfg_filename, settings) |
+ |
+ def update_pth(self, dist): |
+ if self.pth_file is None: |
+ return |
+ |
+ for d in self.pth_file[dist.key]: # drop old entries |
+ if self.multi_version or d.location != dist.location: |
+ log.info("Removing %s from easy-install.pth file", d) |
+ self.pth_file.remove(d) |
+ if d.location in self.shadow_path: |
+ self.shadow_path.remove(d.location) |
+ |
+ if not self.multi_version: |
+ if dist.location in self.pth_file.paths: |
+ log.info( |
+ "%s is already the active version in easy-install.pth", |
+ dist |
+ ) |
+ else: |
+ log.info("Adding %s to easy-install.pth file", dist) |
+ self.pth_file.add(dist) # add new entry |
+ if dist.location not in self.shadow_path: |
+ self.shadow_path.append(dist.location) |
+ |
+ if not self.dry_run: |
+ |
+ self.pth_file.save() |
+ |
+ if dist.key == 'setuptools': |
+ # Ensure that setuptools itself never becomes unavailable! |
+ # XXX should this check for latest version? |
+ filename = os.path.join(self.install_dir, 'setuptools.pth') |
+ if os.path.islink(filename): |
+ os.unlink(filename) |
+ f = open(filename, 'wt') |
+ f.write(self.pth_file.make_relative(dist.location) + '\n') |
+ f.close() |
+ |
+ def unpack_progress(self, src, dst): |
+ # Progress filter for unpacking |
+ log.debug("Unpacking %s to %s", src, dst) |
+ return dst # only unpack-and-compile skips files for dry run |
+ |
+ def unpack_and_compile(self, egg_path, destination): |
+ to_compile = [] |
+ to_chmod = [] |
+ |
+ def pf(src, dst): |
+ if dst.endswith('.py') and not src.startswith('EGG-INFO/'): |
+ to_compile.append(dst) |
+ elif dst.endswith('.dll') or dst.endswith('.so'): |
+ to_chmod.append(dst) |
+ self.unpack_progress(src, dst) |
+ return not self.dry_run and dst or None |
+ |
+ unpack_archive(egg_path, destination, pf) |
+ self.byte_compile(to_compile) |
+ if not self.dry_run: |
+ for f in to_chmod: |
+ mode = ((os.stat(f)[stat.ST_MODE]) | 0o555) & 0o7755 |
+ chmod(f, mode) |
+ |
+ def byte_compile(self, to_compile): |
+ if sys.dont_write_bytecode: |
+ self.warn('byte-compiling is disabled, skipping.') |
+ return |
+ |
+ from distutils.util import byte_compile |
+ |
+ try: |
+ # try to make the byte compile messages quieter |
+ log.set_verbosity(self.verbose - 1) |
+ |
+ byte_compile(to_compile, optimize=0, force=1, dry_run=self.dry_run) |
+ if self.optimize: |
+ byte_compile( |
+ to_compile, optimize=self.optimize, force=1, |
+ dry_run=self.dry_run |
+ ) |
+ finally: |
+ log.set_verbosity(self.verbose) # restore original verbosity |
+ |
+ def no_default_version_msg(self): |
+ template = """bad install directory or PYTHONPATH |
+ |
+You are attempting to install a package to a directory that is not |
+on PYTHONPATH and which Python does not read ".pth" files from. The |
+installation directory you specified (via --install-dir, --prefix, or |
+the distutils default setting) was: |
+ |
+ %s |
+ |
+and your PYTHONPATH environment variable currently contains: |
+ |
+ %r |
+ |
+Here are some of your options for correcting the problem: |
+ |
+* You can choose a different installation directory, i.e., one that is |
+ on PYTHONPATH or supports .pth files |
+ |
+* You can add the installation directory to the PYTHONPATH environment |
+ variable. (It must then also be on PYTHONPATH whenever you run |
+ Python and want to use the package(s) you are installing.) |
+ |
+* You can set up the installation directory to support ".pth" files by |
+ using one of the approaches described here: |
+ |
+ https://pythonhosted.org/setuptools/easy_install.html#custom-installation-locations |
+ |
+Please make the appropriate changes for your system and try again.""" |
+ return template % (self.install_dir, os.environ.get('PYTHONPATH', '')) |
+ |
+ def install_site_py(self): |
+ """Make sure there's a site.py in the target dir, if needed""" |
+ |
+ if self.sitepy_installed: |
+ return # already did it, or don't need to |
+ |
+ sitepy = os.path.join(self.install_dir, "site.py") |
+ source = resource_string("setuptools", "site-patch.py") |
+ current = "" |
+ |
+ if os.path.exists(sitepy): |
+ log.debug("Checking existing site.py in %s", self.install_dir) |
+ f = open(sitepy, 'rb') |
+ current = f.read() |
+ # we want str, not bytes |
+ if PY3: |
+ current = current.decode() |
+ |
+ f.close() |
+ if not current.startswith('def __boot():'): |
+ raise DistutilsError( |
+ "%s is not a setuptools-generated site.py; please" |
+ " remove it." % sitepy |
+ ) |
+ |
+ if current != source: |
+ log.info("Creating %s", sitepy) |
+ if not self.dry_run: |
+ ensure_directory(sitepy) |
+ f = open(sitepy, 'wb') |
+ f.write(source) |
+ f.close() |
+ self.byte_compile([sitepy]) |
+ |
+ self.sitepy_installed = True |
+ |
+ def create_home_path(self): |
+ """Create directories under ~.""" |
+ if not self.user: |
+ return |
+ home = convert_path(os.path.expanduser("~")) |
+ for name, path in iteritems(self.config_vars): |
+ if path.startswith(home) and not os.path.isdir(path): |
+ self.debug_print("os.makedirs('%s', 0o700)" % path) |
+ os.makedirs(path, 0o700) |
+ |
+ INSTALL_SCHEMES = dict( |
+ posix=dict( |
+ install_dir='$base/lib/python$py_version_short/site-packages', |
+ script_dir='$base/bin', |
+ ), |
+ ) |
+ |
+ DEFAULT_SCHEME = dict( |
+ install_dir='$base/Lib/site-packages', |
+ script_dir='$base/Scripts', |
+ ) |
+ |
+ def _expand(self, *attrs): |
+ config_vars = self.get_finalized_command('install').config_vars |
+ |
+ if self.prefix: |
+ # Set default install_dir/scripts from --prefix |
+ config_vars = config_vars.copy() |
+ config_vars['base'] = self.prefix |
+ scheme = self.INSTALL_SCHEMES.get(os.name, self.DEFAULT_SCHEME) |
+ for attr, val in scheme.items(): |
+ if getattr(self, attr, None) is None: |
+ setattr(self, attr, val) |
+ |
+ from distutils.util import subst_vars |
+ |
+ for attr in attrs: |
+ val = getattr(self, attr) |
+ if val is not None: |
+ val = subst_vars(val, config_vars) |
+ if os.name == 'posix': |
+ val = os.path.expanduser(val) |
+ setattr(self, attr, val) |
+ |
+ |
+def get_site_dirs(): |
+ # return a list of 'site' dirs |
+ sitedirs = [_f for _f in os.environ.get('PYTHONPATH', |
+ '').split(os.pathsep) if _f] |
+ prefixes = [sys.prefix] |
+ if sys.exec_prefix != sys.prefix: |
+ prefixes.append(sys.exec_prefix) |
+ for prefix in prefixes: |
+ if prefix: |
+ if sys.platform in ('os2emx', 'riscos'): |
+ sitedirs.append(os.path.join(prefix, "Lib", "site-packages")) |
+ elif os.sep == '/': |
+ sitedirs.extend([os.path.join(prefix, |
+ "lib", |
+ "python" + sys.version[:3], |
+ "site-packages"), |
+ os.path.join(prefix, "lib", "site-python")]) |
+ else: |
+ sitedirs.extend( |
+ [prefix, os.path.join(prefix, "lib", "site-packages")] |
+ ) |
+ if sys.platform == 'darwin': |
+ # for framework builds *only* we add the standard Apple |
+ # locations. Currently only per-user, but /Library and |
+ # /Network/Library could be added too |
+ if 'Python.framework' in prefix: |
+ home = os.environ.get('HOME') |
+ if home: |
+ sitedirs.append( |
+ os.path.join(home, |
+ 'Library', |
+ 'Python', |
+ sys.version[:3], |
+ 'site-packages')) |
+ lib_paths = get_path('purelib'), get_path('platlib') |
+ for site_lib in lib_paths: |
+ if site_lib not in sitedirs: |
+ sitedirs.append(site_lib) |
+ |
+ if site.ENABLE_USER_SITE: |
+ sitedirs.append(site.USER_SITE) |
+ |
+ sitedirs = list(map(normalize_path, sitedirs)) |
+ |
+ return sitedirs |
+ |
+ |
+def expand_paths(inputs): |
+ """Yield sys.path directories that might contain "old-style" packages""" |
+ |
+ seen = {} |
+ |
+ for dirname in inputs: |
+ dirname = normalize_path(dirname) |
+ if dirname in seen: |
+ continue |
+ |
+ seen[dirname] = 1 |
+ if not os.path.isdir(dirname): |
+ continue |
+ |
+ files = os.listdir(dirname) |
+ yield dirname, files |
+ |
+ for name in files: |
+ if not name.endswith('.pth'): |
+ # We only care about the .pth files |
+ continue |
+ if name in ('easy-install.pth', 'setuptools.pth'): |
+ # Ignore .pth files that we control |
+ continue |
+ |
+ # Read the .pth file |
+ f = open(os.path.join(dirname, name)) |
+ lines = list(yield_lines(f)) |
+ f.close() |
+ |
+ # Yield existing non-dupe, non-import directory lines from it |
+ for line in lines: |
+ if not line.startswith("import"): |
+ line = normalize_path(line.rstrip()) |
+ if line not in seen: |
+ seen[line] = 1 |
+ if not os.path.isdir(line): |
+ continue |
+ yield line, os.listdir(line) |
+ |
+ |
+def extract_wininst_cfg(dist_filename): |
+ """Extract configuration data from a bdist_wininst .exe |
+ |
+ Returns a ConfigParser.RawConfigParser, or None |
+ """ |
+ f = open(dist_filename, 'rb') |
+ try: |
+ endrec = zipfile._EndRecData(f) |
+ if endrec is None: |
+ return None |
+ |
+ prepended = (endrec[9] - endrec[5]) - endrec[6] |
+ if prepended < 12: # no wininst data here |
+ return None |
+ f.seek(prepended - 12) |
+ |
+ from setuptools.compat import StringIO, ConfigParser |
+ import struct |
+ |
+ tag, cfglen, bmlen = struct.unpack("<iii", f.read(12)) |
+ if tag not in (0x1234567A, 0x1234567B): |
+ return None # not a valid tag |
+ |
+ f.seek(prepended - (12 + cfglen)) |
+ cfg = ConfigParser.RawConfigParser( |
+ {'version': '', 'target_version': ''}) |
+ try: |
+ part = f.read(cfglen) |
+ # part is in bytes, but we need to read up to the first null |
+ # byte. |
+ if sys.version_info >= (2, 6): |
+ null_byte = bytes([0]) |
+ else: |
+ null_byte = chr(0) |
+ config = part.split(null_byte, 1)[0] |
+ # Now the config is in bytes, but for RawConfigParser, it should |
+ # be text, so decode it. |
+ config = config.decode(sys.getfilesystemencoding()) |
+ cfg.readfp(StringIO(config)) |
+ except ConfigParser.Error: |
+ return None |
+ if not cfg.has_section('metadata') or not cfg.has_section('Setup'): |
+ return None |
+ return cfg |
+ |
+ finally: |
+ f.close() |
+ |
+ |
+def get_exe_prefixes(exe_filename): |
+ """Get exe->egg path translations for a given .exe file""" |
+ |
+ prefixes = [ |
+ ('PURELIB/', ''), ('PLATLIB/pywin32_system32', ''), |
+ ('PLATLIB/', ''), |
+ ('SCRIPTS/', 'EGG-INFO/scripts/'), |
+ ('DATA/lib/site-packages', ''), |
+ ] |
+ z = zipfile.ZipFile(exe_filename) |
+ try: |
+ for info in z.infolist(): |
+ name = info.filename |
+ parts = name.split('/') |
+ if len(parts) == 3 and parts[2] == 'PKG-INFO': |
+ if parts[1].endswith('.egg-info'): |
+ prefixes.insert(0, ('/'.join(parts[:2]), 'EGG-INFO/')) |
+ break |
+ if len(parts) != 2 or not name.endswith('.pth'): |
+ continue |
+ if name.endswith('-nspkg.pth'): |
+ continue |
+ if parts[0].upper() in ('PURELIB', 'PLATLIB'): |
+ contents = z.read(name) |
+ if PY3: |
+ contents = contents.decode() |
+ for pth in yield_lines(contents): |
+ pth = pth.strip().replace('\\', '/') |
+ if not pth.startswith('import'): |
+ prefixes.append((('%s/%s/' % (parts[0], pth)), '')) |
+ finally: |
+ z.close() |
+ prefixes = [(x.lower(), y) for x, y in prefixes] |
+ prefixes.sort() |
+ prefixes.reverse() |
+ return prefixes |
+ |
+ |
+def parse_requirement_arg(spec): |
+ try: |
+ return Requirement.parse(spec) |
+ except ValueError: |
+ raise DistutilsError( |
+ "Not a URL, existing file, or requirement spec: %r" % (spec,) |
+ ) |
+ |
+ |
+class PthDistributions(Environment): |
+ """A .pth file with Distribution paths in it""" |
+ |
+ dirty = False |
+ |
+ def __init__(self, filename, sitedirs=()): |
+ self.filename = filename |
+ self.sitedirs = list(map(normalize_path, sitedirs)) |
+ self.basedir = normalize_path(os.path.dirname(self.filename)) |
+ self._load() |
+ Environment.__init__(self, [], None, None) |
+ for path in yield_lines(self.paths): |
+ list(map(self.add, find_distributions(path, True))) |
+ |
+ def _load(self): |
+ self.paths = [] |
+ saw_import = False |
+ seen = dict.fromkeys(self.sitedirs) |
+ if os.path.isfile(self.filename): |
+ f = open(self.filename, 'rt') |
+ for line in f: |
+ if line.startswith('import'): |
+ saw_import = True |
+ continue |
+ path = line.rstrip() |
+ self.paths.append(path) |
+ if not path.strip() or path.strip().startswith('#'): |
+ continue |
+ # skip non-existent paths, in case somebody deleted a package |
+ # manually, and duplicate paths as well |
+ path = self.paths[-1] = normalize_path( |
+ os.path.join(self.basedir, path) |
+ ) |
+ if not os.path.exists(path) or path in seen: |
+ self.paths.pop() # skip it |
+ self.dirty = True # we cleaned up, so we're dirty now :) |
+ continue |
+ seen[path] = 1 |
+ f.close() |
+ |
+ if self.paths and not saw_import: |
+ self.dirty = True # ensure anything we touch has import wrappers |
+ while self.paths and not self.paths[-1].strip(): |
+ self.paths.pop() |
+ |
+ def save(self): |
+ """Write changed .pth file back to disk""" |
+ if not self.dirty: |
+ return |
+ |
+ data = '\n'.join(map(self.make_relative, self.paths)) |
+ if data: |
+ log.debug("Saving %s", self.filename) |
+ data = ( |
+ "import sys; sys.__plen = len(sys.path)\n" |
+ "%s\n" |
+ "import sys; new=sys.path[sys.__plen:];" |
+ " del sys.path[sys.__plen:];" |
+ " p=getattr(sys,'__egginsert',0); sys.path[p:p]=new;" |
+ " sys.__egginsert = p+len(new)\n" |
+ ) % data |
+ |
+ if os.path.islink(self.filename): |
+ os.unlink(self.filename) |
+ f = open(self.filename, 'wt') |
+ f.write(data) |
+ f.close() |
+ |
+ elif os.path.exists(self.filename): |
+ log.debug("Deleting empty %s", self.filename) |
+ os.unlink(self.filename) |
+ |
+ self.dirty = False |
+ |
+ def add(self, dist): |
+ """Add `dist` to the distribution map""" |
+ if (dist.location not in self.paths and ( |
+ dist.location not in self.sitedirs or |
+ dist.location == os.getcwd() # account for '.' being in PYTHONPATH |
+ )): |
+ self.paths.append(dist.location) |
+ self.dirty = True |
+ Environment.add(self, dist) |
+ |
+ def remove(self, dist): |
+ """Remove `dist` from the distribution map""" |
+ while dist.location in self.paths: |
+ self.paths.remove(dist.location) |
+ self.dirty = True |
+ Environment.remove(self, dist) |
+ |
+ def make_relative(self, path): |
+ npath, last = os.path.split(normalize_path(path)) |
+ baselen = len(self.basedir) |
+ parts = [last] |
+ sep = os.altsep == '/' and '/' or os.sep |
+ while len(npath) >= baselen: |
+ if npath == self.basedir: |
+ parts.append(os.curdir) |
+ parts.reverse() |
+ return sep.join(parts) |
+ npath, last = os.path.split(npath) |
+ parts.append(last) |
+ else: |
+ return path |
+ |
+ |
+def _first_line_re(): |
+ """ |
+ Return a regular expression based on first_line_re suitable for matching |
+ strings. |
+ """ |
+ if isinstance(first_line_re.pattern, str): |
+ return first_line_re |
+ |
+ # first_line_re in Python >=3.1.4 and >=3.2.1 is a bytes pattern. |
+ return re.compile(first_line_re.pattern.decode()) |
+ |
+ |
+def get_script_header(script_text, executable=sys_executable, wininst=False): |
+ """Create a #! line, getting options (if any) from script_text""" |
+ first = (script_text + '\n').splitlines()[0] |
+ match = _first_line_re().match(first) |
+ options = '' |
+ if match: |
+ options = match.group(1) or '' |
+ if options: |
+ options = ' ' + options |
+ if wininst: |
+ executable = "python.exe" |
+ else: |
+ executable = nt_quote_arg(executable) |
+ hdr = "#!%(executable)s%(options)s\n" % locals() |
+ if not isascii(hdr): |
+ # Non-ascii path to sys.executable, use -x to prevent warnings |
+ if options: |
+ if options.strip().startswith('-'): |
+ options = ' -x' + options.strip()[1:] |
+ # else: punt, we can't do it, let the warning happen anyway |
+ else: |
+ options = ' -x' |
+ executable = fix_jython_executable(executable, options) |
+ hdr = "#!%(executable)s%(options)s\n" % locals() |
+ return hdr |
+ |
+ |
+def auto_chmod(func, arg, exc): |
+ if func is os.remove and os.name == 'nt': |
+ chmod(arg, stat.S_IWRITE) |
+ return func(arg) |
+ et, ev, _ = sys.exc_info() |
+ reraise(et, (ev[0], ev[1] + (" %s %s" % (func, arg)))) |
+ |
+ |
+def update_dist_caches(dist_path, fix_zipimporter_caches): |
+ """ |
+ Fix any globally cached `dist_path` related data |
+ |
+ `dist_path` should be a path of a newly installed egg distribution (zipped |
+ or unzipped). |
+ |
+ sys.path_importer_cache contains finder objects that have been cached when |
+ importing data from the original distribution. Any such finders need to be |
+ cleared since the replacement distribution might be packaged differently, |
+ e.g. a zipped egg distribution might get replaced with an unzipped egg |
+ folder or vice versa. Having the old finders cached may then cause Python |
+ to attempt loading modules from the replacement distribution using an |
+ incorrect loader. |
+ |
+ zipimport.zipimporter objects are Python loaders charged with importing |
+ data packaged inside zip archives. If stale loaders referencing the |
+ original distribution, are left behind, they can fail to load modules from |
+ the replacement distribution. E.g. if an old zipimport.zipimporter instance |
+ is used to load data from a new zipped egg archive, it may cause the |
+ operation to attempt to locate the requested data in the wrong location - |
+ one indicated by the original distribution's zip archive directory |
+ information. Such an operation may then fail outright, e.g. report having |
+ read a 'bad local file header', or even worse, it may fail silently & |
+ return invalid data. |
+ |
+ zipimport._zip_directory_cache contains cached zip archive directory |
+ information for all existing zipimport.zipimporter instances and all such |
+ instances connected to the same archive share the same cached directory |
+ information. |
+ |
+ If asked, and the underlying Python implementation allows it, we can fix |
+ all existing zipimport.zipimporter instances instead of having to track |
+ them down and remove them one by one, by updating their shared cached zip |
+ archive directory information. This, of course, assumes that the |
+ replacement distribution is packaged as a zipped egg. |
+ |
+ If not asked to fix existing zipimport.zipimporter instances, we still do |
+ our best to clear any remaining zipimport.zipimporter related cached data |
+ that might somehow later get used when attempting to load data from the new |
+ distribution and thus cause such load operations to fail. Note that when |
+ tracking down such remaining stale data, we can not catch every conceivable |
+ usage from here, and we clear only those that we know of and have found to |
+ cause problems if left alive. Any remaining caches should be updated by |
+ whomever is in charge of maintaining them, i.e. they should be ready to |
+ handle us replacing their zip archives with new distributions at runtime. |
+ |
+ """ |
+ # There are several other known sources of stale zipimport.zipimporter |
+ # instances that we do not clear here, but might if ever given a reason to |
+ # do so: |
+ # * Global setuptools pkg_resources.working_set (a.k.a. 'master working |
+ # set') may contain distributions which may in turn contain their |
+ # zipimport.zipimporter loaders. |
+ # * Several zipimport.zipimporter loaders held by local variables further |
+ # up the function call stack when running the setuptools installation. |
+ # * Already loaded modules may have their __loader__ attribute set to the |
+ # exact loader instance used when importing them. Python 3.4 docs state |
+ # that this information is intended mostly for introspection and so is |
+ # not expected to cause us problems. |
+ normalized_path = normalize_path(dist_path) |
+ _uncache(normalized_path, sys.path_importer_cache) |
+ if fix_zipimporter_caches: |
+ _replace_zip_directory_cache_data(normalized_path) |
+ else: |
+ # Here, even though we do not want to fix existing and now stale |
+ # zipimporter cache information, we still want to remove it. Related to |
+ # Python's zip archive directory information cache, we clear each of |
+ # its stale entries in two phases: |
+ # 1. Clear the entry so attempting to access zip archive information |
+ # via any existing stale zipimport.zipimporter instances fails. |
+ # 2. Remove the entry from the cache so any newly constructed |
+ # zipimport.zipimporter instances do not end up using old stale |
+ # zip archive directory information. |
+ # This whole stale data removal step does not seem strictly necessary, |
+ # but has been left in because it was done before we started replacing |
+ # the zip archive directory information cache content if possible, and |
+ # there are no relevant unit tests that we can depend on to tell us if |
+ # this is really needed. |
+ _remove_and_clear_zip_directory_cache_data(normalized_path) |
+ |
+ |
+def _collect_zipimporter_cache_entries(normalized_path, cache): |
+ """ |
+ Return zipimporter cache entry keys related to a given normalized path. |
+ |
+ Alternative path spellings (e.g. those using different character case or |
+ those using alternative path separators) related to the same path are |
+ included. Any sub-path entries are included as well, i.e. those |
+ corresponding to zip archives embedded in other zip archives. |
+ |
+ """ |
+ result = [] |
+ prefix_len = len(normalized_path) |
+ for p in cache: |
+ np = normalize_path(p) |
+ if (np.startswith(normalized_path) and |
+ np[prefix_len:prefix_len + 1] in (os.sep, '')): |
+ result.append(p) |
+ return result |
+ |
+ |
+def _update_zipimporter_cache(normalized_path, cache, updater=None): |
+ """ |
+ Update zipimporter cache data for a given normalized path. |
+ |
+ Any sub-path entries are processed as well, i.e. those corresponding to zip |
+ archives embedded in other zip archives. |
+ |
+ Given updater is a callable taking a cache entry key and the original entry |
+ (after already removing the entry from the cache), and expected to update |
+ the entry and possibly return a new one to be inserted in its place. |
+ Returning None indicates that the entry should not be replaced with a new |
+ one. If no updater is given, the cache entries are simply removed without |
+ any additional processing, the same as if the updater simply returned None. |
+ |
+ """ |
+ for p in _collect_zipimporter_cache_entries(normalized_path, cache): |
+ # N.B. pypy's custom zipimport._zip_directory_cache implementation does |
+ # not support the complete dict interface: |
+ # * Does not support item assignment, thus not allowing this function |
+ # to be used only for removing existing cache entries. |
+ # * Does not support the dict.pop() method, forcing us to use the |
+ # get/del patterns instead. For more detailed information see the |
+ # following links: |
+ # https://bitbucket.org/pypa/setuptools/issue/202/more-robust-zipimporter-cache-invalidation#comment-10495960 |
+ # https://bitbucket.org/pypy/pypy/src/dd07756a34a41f674c0cacfbc8ae1d4cc9ea2ae4/pypy/module/zipimport/interp_zipimport.py#cl-99 |
+ old_entry = cache[p] |
+ del cache[p] |
+ new_entry = updater and updater(p, old_entry) |
+ if new_entry is not None: |
+ cache[p] = new_entry |
+ |
+ |
+def _uncache(normalized_path, cache): |
+ _update_zipimporter_cache(normalized_path, cache) |
+ |
+ |
+def _remove_and_clear_zip_directory_cache_data(normalized_path): |
+ def clear_and_remove_cached_zip_archive_directory_data(path, old_entry): |
+ old_entry.clear() |
+ |
+ _update_zipimporter_cache( |
+ normalized_path, zipimport._zip_directory_cache, |
+ updater=clear_and_remove_cached_zip_archive_directory_data) |
+ |
+# PyPy Python implementation does not allow directly writing to the |
+# zipimport._zip_directory_cache and so prevents us from attempting to correct |
+# its content. The best we can do there is clear the problematic cache content |
+# and have PyPy repopulate it as needed. The downside is that if there are any |
+# stale zipimport.zipimporter instances laying around, attempting to use them |
+# will fail due to not having its zip archive directory information available |
+# instead of being automatically corrected to use the new correct zip archive |
+# directory information. |
+if '__pypy__' in sys.builtin_module_names: |
+ _replace_zip_directory_cache_data = \ |
+ _remove_and_clear_zip_directory_cache_data |
+else: |
+ def _replace_zip_directory_cache_data(normalized_path): |
+ def replace_cached_zip_archive_directory_data(path, old_entry): |
+ # N.B. In theory, we could load the zip directory information just |
+ # once for all updated path spellings, and then copy it locally and |
+ # update its contained path strings to contain the correct |
+ # spelling, but that seems like a way too invasive move (this cache |
+ # structure is not officially documented anywhere and could in |
+ # theory change with new Python releases) for no significant |
+ # benefit. |
+ old_entry.clear() |
+ zipimport.zipimporter(path) |
+ old_entry.update(zipimport._zip_directory_cache[path]) |
+ return old_entry |
+ |
+ _update_zipimporter_cache( |
+ normalized_path, zipimport._zip_directory_cache, |
+ updater=replace_cached_zip_archive_directory_data) |
+ |
+ |
+def is_python(text, filename='<string>'): |
+ "Is this string a valid Python script?" |
+ try: |
+ compile(text, filename, 'exec') |
+ except (SyntaxError, TypeError): |
+ return False |
+ else: |
+ return True |
+ |
+ |
+def is_sh(executable): |
+ """Determine if the specified executable is a .sh (contains a #! line)""" |
+ try: |
+ fp = open(executable) |
+ magic = fp.read(2) |
+ fp.close() |
+ except (OSError, IOError): |
+ return executable |
+ return magic == '#!' |
+ |
+ |
+def nt_quote_arg(arg): |
+ """Quote a command line argument according to Windows parsing rules""" |
+ |
+ result = [] |
+ needquote = False |
+ nb = 0 |
+ |
+ needquote = (" " in arg) or ("\t" in arg) |
+ if needquote: |
+ result.append('"') |
+ |
+ for c in arg: |
+ if c == '\\': |
+ nb += 1 |
+ elif c == '"': |
+ # double preceding backslashes, then add a \" |
+ result.append('\\' * (nb * 2) + '\\"') |
+ nb = 0 |
+ else: |
+ if nb: |
+ result.append('\\' * nb) |
+ nb = 0 |
+ result.append(c) |
+ |
+ if nb: |
+ result.append('\\' * nb) |
+ |
+ if needquote: |
+ result.append('\\' * nb) # double the trailing backslashes |
+ result.append('"') |
+ |
+ return ''.join(result) |
+ |
+ |
+def is_python_script(script_text, filename): |
+ """Is this text, as a whole, a Python script? (as opposed to shell/bat/etc. |
+ """ |
+ if filename.endswith('.py') or filename.endswith('.pyw'): |
+ return True # extension says it's Python |
+ if is_python(script_text, filename): |
+ return True # it's syntactically valid Python |
+ if script_text.startswith('#!'): |
+ # It begins with a '#!' line, so check if 'python' is in it somewhere |
+ return 'python' in script_text.splitlines()[0].lower() |
+ |
+ return False # Not any Python I can recognize |
+ |
+ |
+try: |
+ from os import chmod as _chmod |
+except ImportError: |
+ # Jython compatibility |
+ def _chmod(*args): |
+ pass |
+ |
+ |
+def chmod(path, mode): |
+ log.debug("changing mode of %s to %o", path, mode) |
+ try: |
+ _chmod(path, mode) |
+ except os.error: |
+ e = sys.exc_info()[1] |
+ log.debug("chmod failed: %s", e) |
+ |
+ |
+def fix_jython_executable(executable, options): |
+ if sys.platform.startswith('java') and is_sh(executable): |
+ # Workaround for Jython is not needed on Linux systems. |
+ import java |
+ |
+ if java.lang.System.getProperty("os.name") == "Linux": |
+ return executable |
+ |
+ # Workaround Jython's sys.executable being a .sh (an invalid |
+ # shebang line interpreter) |
+ if options: |
+ # Can't apply the workaround, leave it broken |
+ log.warn( |
+ "WARNING: Unable to adapt shebang line for Jython," |
+ " the following script is NOT executable\n" |
+ " see http://bugs.jython.org/issue1112 for" |
+ " more information.") |
+ else: |
+ return '/usr/bin/env %s' % executable |
+ return executable |
+ |
+ |
+class ScriptWriter(object): |
+ """ |
+ Encapsulates behavior around writing entry point scripts for console and |
+ gui apps. |
+ """ |
+ |
+ template = textwrap.dedent(""" |
+ # EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r |
+ __requires__ = %(spec)r |
+ import sys |
+ from pkg_resources import load_entry_point |
+ |
+ if __name__ == '__main__': |
+ sys.exit( |
+ load_entry_point(%(spec)r, %(group)r, %(name)r)() |
+ ) |
+ """).lstrip() |
+ |
+ @classmethod |
+ def get_script_args(cls, dist, executable=sys_executable, wininst=False): |
+ """ |
+ Yield write_script() argument tuples for a distribution's entrypoints |
+ """ |
+ gen_class = cls.get_writer(wininst) |
+ spec = str(dist.as_requirement()) |
+ header = get_script_header("", executable, wininst) |
+ for type_ in 'console', 'gui': |
+ group = type_ + '_scripts' |
+ for name, ep in dist.get_entry_map(group).items(): |
+ script_text = gen_class.template % locals() |
+ for res in gen_class._get_script_args(type_, name, header, |
+ script_text): |
+ yield res |
+ |
+ @classmethod |
+ def get_writer(cls, force_windows): |
+ if force_windows or sys.platform == 'win32': |
+ return WindowsScriptWriter.get_writer() |
+ return cls |
+ |
+ @classmethod |
+ def _get_script_args(cls, type_, name, header, script_text): |
+ # Simply write the stub with no extension. |
+ yield (name, header + script_text) |
+ |
+ |
+class WindowsScriptWriter(ScriptWriter): |
+ @classmethod |
+ def get_writer(cls): |
+ """ |
+ Get a script writer suitable for Windows |
+ """ |
+ writer_lookup = dict( |
+ executable=WindowsExecutableLauncherWriter, |
+ natural=cls, |
+ ) |
+ # for compatibility, use the executable launcher by default |
+ launcher = os.environ.get('SETUPTOOLS_LAUNCHER', 'executable') |
+ return writer_lookup[launcher] |
+ |
+ @classmethod |
+ def _get_script_args(cls, type_, name, header, script_text): |
+ "For Windows, add a .py extension" |
+ ext = dict(console='.pya', gui='.pyw')[type_] |
+ if ext not in os.environ['PATHEXT'].lower().split(';'): |
+ warnings.warn("%s not listed in PATHEXT; scripts will not be " |
+ "recognized as executables." % ext, UserWarning) |
+ old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe'] |
+ old.remove(ext) |
+ header = cls._adjust_header(type_, header) |
+ blockers = [name + x for x in old] |
+ yield name + ext, header + script_text, 't', blockers |
+ |
+ @staticmethod |
+ def _adjust_header(type_, orig_header): |
+ """ |
+ Make sure 'pythonw' is used for gui and and 'python' is used for |
+ console (regardless of what sys.executable is). |
+ """ |
+ pattern = 'pythonw.exe' |
+ repl = 'python.exe' |
+ if type_ == 'gui': |
+ pattern, repl = repl, pattern |
+ pattern_ob = re.compile(re.escape(pattern), re.IGNORECASE) |
+ new_header = pattern_ob.sub(string=orig_header, repl=repl) |
+ clean_header = new_header[2:-1].strip('"') |
+ if sys.platform == 'win32' and not os.path.exists(clean_header): |
+ # the adjusted version doesn't exist, so return the original |
+ return orig_header |
+ return new_header |
+ |
+ |
+class WindowsExecutableLauncherWriter(WindowsScriptWriter): |
+ @classmethod |
+ def _get_script_args(cls, type_, name, header, script_text): |
+ """ |
+ For Windows, add a .py extension and an .exe launcher |
+ """ |
+ if type_ == 'gui': |
+ launcher_type = 'gui' |
+ ext = '-script.pyw' |
+ old = ['.pyw'] |
+ else: |
+ launcher_type = 'cli' |
+ ext = '-script.py' |
+ old = ['.py', '.pyc', '.pyo'] |
+ hdr = cls._adjust_header(type_, header) |
+ blockers = [name + x for x in old] |
+ yield (name + ext, hdr + script_text, 't', blockers) |
+ yield ( |
+ name + '.exe', get_win_launcher(launcher_type), |
+ 'b' # write in binary mode |
+ ) |
+ if not is_64bit(): |
+ # install a manifest for the launcher to prevent Windows |
+ # from detecting it as an installer (which it will for |
+ # launchers like easy_install.exe). Consider only |
+ # adding a manifest for launchers detected as installers. |
+ # See Distribute #143 for details. |
+ m_name = name + '.exe.manifest' |
+ yield (m_name, load_launcher_manifest(name), 't') |
+ |
+ |
+# for backward-compatibility |
+get_script_args = ScriptWriter.get_script_args |
+ |
+ |
+def get_win_launcher(type): |
+ """ |
+ Load the Windows launcher (executable) suitable for launching a script. |
+ |
+ `type` should be either 'cli' or 'gui' |
+ |
+ Returns the executable as a byte string. |
+ """ |
+ launcher_fn = '%s.exe' % type |
+ if platform.machine().lower() == 'arm': |
+ launcher_fn = launcher_fn.replace(".", "-arm.") |
+ if is_64bit(): |
+ launcher_fn = launcher_fn.replace(".", "-64.") |
+ else: |
+ launcher_fn = launcher_fn.replace(".", "-32.") |
+ return resource_string('setuptools', launcher_fn) |
+ |
+ |
+def load_launcher_manifest(name): |
+ manifest = pkg_resources.resource_string(__name__, 'launcher manifest.xml') |
+ if PY2: |
+ return manifest % vars() |
+ else: |
+ return manifest.decode('utf-8') % vars() |
+ |
+ |
+def rmtree(path, ignore_errors=False, onerror=auto_chmod): |
+ """Recursively delete a directory tree. |
+ |
+ This code is taken from the Python 2.4 version of 'shutil', because |
+ the 2.3 version doesn't really work right. |
+ """ |
+ if ignore_errors: |
+ def onerror(*args): |
+ pass |
+ elif onerror is None: |
+ def onerror(*args): |
+ raise |
+ 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 current_umask(): |
+ tmp = os.umask(0o022) |
+ os.umask(tmp) |
+ return tmp |
+ |
+ |
+def bootstrap(): |
+ # This function is called when setuptools*.egg is run using /bin/sh |
+ import setuptools |
+ |
+ argv0 = os.path.dirname(setuptools.__path__[0]) |
+ sys.argv[0] = argv0 |
+ sys.argv.append(argv0) |
+ main() |
+ |
+ |
+def main(argv=None, **kw): |
+ from setuptools import setup |
+ from setuptools.dist import Distribution |
+ import distutils.core |
+ |
+ USAGE = """\ |
+usage: %(script)s [options] requirement_or_url ... |
+ or: %(script)s --help |
+""" |
+ |
+ def gen_usage(script_name): |
+ return USAGE % dict( |
+ script=os.path.basename(script_name), |
+ ) |
+ |
+ def with_ei_usage(f): |
+ old_gen_usage = distutils.core.gen_usage |
+ try: |
+ distutils.core.gen_usage = gen_usage |
+ return f() |
+ finally: |
+ distutils.core.gen_usage = old_gen_usage |
+ |
+ class DistributionWithoutHelpCommands(Distribution): |
+ common_usage = "" |
+ |
+ def _show_help(self, *args, **kw): |
+ with_ei_usage(lambda: Distribution._show_help(self, *args, **kw)) |
+ |
+ if argv is None: |
+ argv = sys.argv[1:] |
+ |
+ with_ei_usage( |
+ lambda: setup( |
+ script_args=['-q', 'easy_install', '-v'] + argv, |
+ script_name=sys.argv[0] or 'easy_install', |
+ distclass=DistributionWithoutHelpCommands, **kw |
+ ) |
+ ) |