#!/usr/bin/env python """Create a "virtual" Python installation""" # fmt: off import os # isort:skip import sys # isort:skip # If we are running in a new interpreter to create a virtualenv, # we do NOT want paths from our existing location interfering with anything, # So we remove this file's directory from sys.path - most likely to be # the previous interpreter's site-packages. Solves #705, #763, #779 if os.environ.get("VIRTUALENV_INTERPRETER_RUNNING"): for path in sys.path[:]: if os.path.realpath(os.path.dirname(__file__)) == os.path.realpath(path): sys.path.remove(path) # fmt: on import base64 import codecs import distutils.spawn import distutils.sysconfig import errno import glob import logging import optparse import os import re import shutil import struct import subprocess import sys import textwrap import zlib from distutils.util import strtobool from os.path import join try: import ConfigParser except ImportError: import configparser as ConfigParser __version__ = "16.1.0" virtualenv_version = __version__ # legacy if sys.version_info < (2, 7): print("ERROR: %s" % sys.exc_info()[1]) print("ERROR: this script requires Python 2.7 or greater.") sys.exit(101) try: basestring except NameError: basestring = str py_version = "python{}.{}".format(sys.version_info[0], sys.version_info[1]) is_jython = sys.platform.startswith("java") is_pypy = hasattr(sys, "pypy_version_info") is_win = sys.platform == "win32" is_cygwin = sys.platform == "cygwin" is_darwin = sys.platform == "darwin" abiflags = getattr(sys, "abiflags", "") user_dir = os.path.expanduser("~") if is_win: default_storage_dir = os.path.join(user_dir, "virtualenv") else: default_storage_dir = os.path.join(user_dir, ".virtualenv") default_config_file = os.path.join(default_storage_dir, "virtualenv.ini") if is_pypy: expected_exe = "pypy" elif is_jython: expected_exe = "jython" else: expected_exe = "python" # Return a mapping of version -> Python executable # Only provided for Windows, where the information in the registry is used if not is_win: def get_installed_pythons(): return {} else: try: import winreg except ImportError: import _winreg as winreg def get_installed_pythons(): exes = dict() # If both system and current user installations are found for a # particular Python version, the current user one is used for key in (winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER): try: python_core = winreg.CreateKey(key, "Software\\Python\\PythonCore") except WindowsError: # No registered Python installations continue i = 0 while True: try: version = winreg.EnumKey(python_core, i) i += 1 try: path = winreg.QueryValue(python_core, "%s\\InstallPath" % version) except WindowsError: continue exes[version] = join(path, "python.exe") except WindowsError: break winreg.CloseKey(python_core) # For versions that track separate 32-bit (`X.Y-32`) & 64-bit (`X-Y`) # installation registrations, add a `X.Y-64` version tag and make the # extensionless `X.Y` version tag represent the 64-bit installation if # available or 32-bit if it is not updated = {} for ver in exes: if ver < "3.5": continue if ver.endswith("-32"): base_ver = ver[:-3] if base_ver not in exes: updated[base_ver] = exes[ver] else: updated[ver + "-64"] = exes[ver] exes.update(updated) # Add the major versions # Sort the keys, then repeatedly update the major version entry # Last executable (i.e., highest version) wins with this approach, # 64-bit over 32-bit if both are found for ver in sorted(exes): exes[ver[0]] = exes[ver] return exes REQUIRED_MODULES = [ "os", "posix", "posixpath", "nt", "ntpath", "genericpath", "fnmatch", "locale", "encodings", "codecs", "stat", "UserDict", "readline", "copy_reg", "types", "re", "sre", "sre_parse", "sre_constants", "sre_compile", "zlib", ] REQUIRED_FILES = ["lib-dynload", "config"] majver, minver = sys.version_info[:2] if majver == 2: if minver >= 6: REQUIRED_MODULES.extend(["warnings", "linecache", "_abcoll", "abc"]) if minver >= 7: REQUIRED_MODULES.extend(["_weakrefset"]) elif majver == 3: # Some extra modules are needed for Python 3, but different ones # for different versions. REQUIRED_MODULES.extend( [ "_abcoll", "warnings", "linecache", "abc", "io", "_weakrefset", "copyreg", "tempfile", "random", "__future__", "collections", "keyword", "tarfile", "shutil", "struct", "copy", "tokenize", "token", "functools", "heapq", "bisect", "weakref", "reprlib", ] ) if minver >= 2: REQUIRED_FILES[-1] = "config-%s" % majver if minver >= 3: import sysconfig platdir = sysconfig.get_config_var("PLATDIR") REQUIRED_FILES.append(platdir) REQUIRED_MODULES.extend(["base64", "_dummy_thread", "hashlib", "hmac", "imp", "importlib", "rlcompleter"]) if minver >= 4: REQUIRED_MODULES.extend(["operator", "_collections_abc", "_bootlocale"]) if minver >= 6: REQUIRED_MODULES.extend(["enum"]) if is_pypy: # these are needed to correctly display the exceptions that may happen # during the bootstrap REQUIRED_MODULES.extend(["traceback", "linecache"]) if majver == 3: # _functools is needed to import locale during stdio initialization and # needs to be copied on PyPy because it's not built in REQUIRED_MODULES.append("_functools") class Logger(object): """ Logging object for use in command-line script. Allows ranges of levels, to avoid some redundancy of displayed information. """ DEBUG = logging.DEBUG INFO = logging.INFO NOTIFY = (logging.INFO + logging.WARN) / 2 WARN = WARNING = logging.WARN ERROR = logging.ERROR FATAL = logging.FATAL LEVELS = [DEBUG, INFO, NOTIFY, WARN, ERROR, FATAL] def __init__(self, consumers): self.consumers = consumers self.indent = 0 self.in_progress = None self.in_progress_hanging = False def debug(self, msg, *args, **kw): self.log(self.DEBUG, msg, *args, **kw) def info(self, msg, *args, **kw): self.log(self.INFO, msg, *args, **kw) def notify(self, msg, *args, **kw): self.log(self.NOTIFY, msg, *args, **kw) def warn(self, msg, *args, **kw): self.log(self.WARN, msg, *args, **kw) def error(self, msg, *args, **kw): self.log(self.ERROR, msg, *args, **kw) def fatal(self, msg, *args, **kw): self.log(self.FATAL, msg, *args, **kw) def log(self, level, msg, *args, **kw): if args: if kw: raise TypeError("You may give positional or keyword arguments, not both") args = args or kw rendered = None for consumer_level, consumer in self.consumers: if self.level_matches(level, consumer_level): if self.in_progress_hanging and consumer in (sys.stdout, sys.stderr): self.in_progress_hanging = False print("") sys.stdout.flush() if rendered is None: if args: rendered = msg % args else: rendered = msg rendered = " " * self.indent + rendered if hasattr(consumer, "write"): consumer.write(rendered + "\n") else: consumer(rendered) def start_progress(self, msg): assert not self.in_progress, "Tried to start_progress({!r}) while in_progress {!r}".format( msg, self.in_progress ) if self.level_matches(self.NOTIFY, self._stdout_level()): print(msg) sys.stdout.flush() self.in_progress_hanging = True else: self.in_progress_hanging = False self.in_progress = msg def end_progress(self, msg="done."): assert self.in_progress, "Tried to end_progress without start_progress" if self.stdout_level_matches(self.NOTIFY): if not self.in_progress_hanging: # Some message has been printed out since start_progress print("...{}{}".format(self.in_progress, msg)) sys.stdout.flush() else: print(msg) sys.stdout.flush() self.in_progress = None self.in_progress_hanging = False def show_progress(self): """If we are in a progress scope, and no log messages have been shown, write out another '.'""" if self.in_progress_hanging: print(".") sys.stdout.flush() def stdout_level_matches(self, level): """Returns true if a message at this level will go to stdout""" return self.level_matches(level, self._stdout_level()) def _stdout_level(self): """Returns the level that stdout runs at""" for level, consumer in self.consumers: if consumer is sys.stdout: return level return self.FATAL def level_matches(self, level, consumer_level): """ >>> l = Logger([]) >>> l.level_matches(3, 4) False >>> l.level_matches(3, 2) True >>> l.level_matches(slice(None, 3), 3) False >>> l.level_matches(slice(None, 3), 2) True >>> l.level_matches(slice(1, 3), 1) True >>> l.level_matches(slice(2, 3), 1) False """ if isinstance(level, slice): start, stop = level.start, level.stop if start is not None and start > consumer_level: return False if stop is not None and stop <= consumer_level: return False return True else: return level >= consumer_level @classmethod def level_for_integer(cls, level): levels = cls.LEVELS if level < 0: return levels[0] if level >= len(levels): return levels[-1] return levels[level] # create a silent logger just to prevent this from being undefined # will be overridden with requested verbosity main() is called. logger = Logger([(Logger.LEVELS[-1], sys.stdout)]) def mkdir(path): if not os.path.exists(path): logger.info("Creating %s", path) os.makedirs(path) else: logger.info("Directory %s already exists", path) def copyfileordir(src, dest, symlink=True): if os.path.isdir(src): shutil.copytree(src, dest, symlink) else: shutil.copy2(src, dest) def copyfile(src, dest, symlink=True): if not os.path.exists(src): # Some bad symlink in the src logger.warn("Cannot find file %s (bad symlink)", src) return if os.path.exists(dest): logger.debug("File %s already exists", dest) return if not os.path.exists(os.path.dirname(dest)): logger.info("Creating parent directories for %s", os.path.dirname(dest)) os.makedirs(os.path.dirname(dest)) if not os.path.islink(src): srcpath = os.path.abspath(src) else: srcpath = os.readlink(src) if symlink and hasattr(os, "symlink") and not is_win: logger.info("Symlinking %s", dest) try: os.symlink(srcpath, dest) except (OSError, NotImplementedError): logger.info("Symlinking failed, copying to %s", dest) copyfileordir(src, dest, symlink) else: logger.info("Copying to %s", dest) copyfileordir(src, dest, symlink) def writefile(dest, content, overwrite=True): if not os.path.exists(dest): logger.info("Writing %s", dest) with open(dest, "wb") as f: f.write(content.encode("utf-8")) return else: with open(dest, "rb") as f: c = f.read() if c != content.encode("utf-8"): if not overwrite: logger.notify("File %s exists with different content; not overwriting", dest) return logger.notify("Overwriting %s with new content", dest) with open(dest, "wb") as f: f.write(content.encode("utf-8")) else: logger.info("Content %s already in place", dest) def rmtree(dir): if os.path.exists(dir): logger.notify("Deleting tree %s", dir) shutil.rmtree(dir) else: logger.info("Do not need to delete %s; already gone", dir) def make_exe(fn): if hasattr(os, "chmod"): oldmode = os.stat(fn).st_mode & 0xFFF # 0o7777 newmode = (oldmode | 0x16D) & 0xFFF # 0o555, 0o7777 os.chmod(fn, newmode) logger.info("Changed mode of %s to %s", fn, oct(newmode)) def _find_file(filename, dirs): for dir in reversed(dirs): files = glob.glob(os.path.join(dir, filename)) if files and os.path.isfile(files[0]): return True, files[0] return False, filename def file_search_dirs(): here = os.path.dirname(os.path.abspath(__file__)) dirs = [here, join(here, "virtualenv_support")] if os.path.splitext(os.path.dirname(__file__))[0] != "virtualenv": # Probably some boot script; just in case virtualenv is installed... try: import virtualenv except ImportError: pass else: dirs.append(os.path.join(os.path.dirname(virtualenv.__file__), "virtualenv_support")) return [d for d in dirs if os.path.isdir(d)] class UpdatingDefaultsHelpFormatter(optparse.IndentedHelpFormatter): """ Custom help formatter for use in ConfigOptionParser that updates the defaults before expanding them, allowing them to show up correctly in the help listing """ def expand_default(self, option): if self.parser is not None: self.parser.update_defaults(self.parser.defaults) return optparse.IndentedHelpFormatter.expand_default(self, option) class ConfigOptionParser(optparse.OptionParser): """ Custom option parser which updates its defaults by checking the configuration files and environmental variables """ def __init__(self, *args, **kwargs): self.config = ConfigParser.RawConfigParser() self.files = self.get_config_files() self.config.read(self.files) optparse.OptionParser.__init__(self, *args, **kwargs) def get_config_files(self): config_file = os.environ.get("VIRTUALENV_CONFIG_FILE", False) if config_file and os.path.exists(config_file): return [config_file] return [default_config_file] def update_defaults(self, defaults): """ Updates the given defaults with values from the config files and the environ. Does a little special handling for certain types of options (lists). """ # Then go and look for the other sources of configuration: config = {} # 1. config files config.update(dict(self.get_config_section("virtualenv"))) # 2. environmental variables config.update(dict(self.get_environ_vars())) # Then set the options with those values for key, val in config.items(): key = key.replace("_", "-") if not key.startswith("--"): key = "--%s" % key # only prefer long opts option = self.get_option(key) if option is not None: # ignore empty values if not val: continue # handle multiline configs if option.action == "append": val = val.split() else: option.nargs = 1 if option.action == "store_false": val = not strtobool(val) elif option.action in ("store_true", "count"): val = strtobool(val) try: val = option.convert_value(key, val) except optparse.OptionValueError: e = sys.exc_info()[1] print("An error occurred during configuration: %s" % e) sys.exit(3) defaults[option.dest] = val return defaults def get_config_section(self, name): """ Get a section of a configuration """ if self.config.has_section(name): return self.config.items(name) return [] def get_environ_vars(self, prefix="VIRTUALENV_"): """ Returns a generator with all environmental vars with prefix VIRTUALENV """ for key, val in os.environ.items(): if key.startswith(prefix): yield (key.replace(prefix, "").lower(), val) def get_default_values(self): """ Overridding to make updating the defaults after instantiation of the option parser possible, update_defaults() does the dirty work. """ if not self.process_default_values: # Old, pre-Optik 1.5 behaviour. return optparse.Values(self.defaults) defaults = self.update_defaults(self.defaults.copy()) # ours for option in self._get_all_options(): default = defaults.get(option.dest) if isinstance(default, basestring): opt_str = option.get_opt_string() defaults[option.dest] = option.check_value(opt_str, default) return optparse.Values(defaults) def main(): parser = ConfigOptionParser( version=virtualenv_version, usage="%prog [OPTIONS] DEST_DIR", formatter=UpdatingDefaultsHelpFormatter() ) parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0, help="Increase verbosity.") parser.add_option("-q", "--quiet", action="count", dest="quiet", default=0, help="Decrease verbosity.") parser.add_option( "-p", "--python", dest="python", metavar="PYTHON_EXE", help="The Python interpreter to use, e.g., --python=python3.5 will use the python3.5 " "interpreter to create the new environment. The default is the interpreter that " "virtualenv was installed with (%s)" % sys.executable, ) parser.add_option( "--clear", dest="clear", action="store_true", help="Clear out the non-root install and start from scratch." ) parser.set_defaults(system_site_packages=False) parser.add_option( "--no-site-packages", dest="system_site_packages", action="store_false", help="DEPRECATED. Retained only for backward compatibility. " "Not having access to global site-packages is now the default behavior.", ) parser.add_option( "--system-site-packages", dest="system_site_packages", action="store_true", help="Give the virtual environment access to the global site-packages.", ) parser.add_option( "--always-copy", dest="symlink", action="store_false", default=True, help="Always copy files rather than symlinking.", ) parser.add_option( "--relocatable", dest="relocatable", action="store_true", help="Make an EXISTING virtualenv environment relocatable. " "This fixes up scripts and makes all .pth files relative.", ) parser.add_option( "--no-setuptools", dest="no_setuptools", action="store_true", help="Do not install setuptools in the new virtualenv.", ) parser.add_option("--no-pip", dest="no_pip", action="store_true", help="Do not install pip in the new virtualenv.") parser.add_option( "--no-wheel", dest="no_wheel", action="store_true", help="Do not install wheel in the new virtualenv." ) default_search_dirs = file_search_dirs() parser.add_option( "--extra-search-dir", dest="search_dirs", action="append", metavar="DIR", default=default_search_dirs, help="Directory to look for setuptools/pip distributions in. " "This option can be used multiple times.", ) parser.add_option( "--download", dest="download", default=True, action="store_true", help="Download preinstalled packages from PyPI.", ) parser.add_option( "--no-download", "--never-download", dest="download", action="store_false", help="Do not download preinstalled packages from PyPI.", ) parser.add_option("--prompt", dest="prompt", help="Provides an alternative prompt prefix for this environment.") parser.add_option( "--setuptools", dest="setuptools", action="store_true", help="DEPRECATED. Retained only for backward compatibility. This option has no effect.", ) parser.add_option( "--distribute", dest="distribute", action="store_true", help="DEPRECATED. Retained only for backward compatibility. This option has no effect.", ) parser.add_option( "--unzip-setuptools", action="store_true", help="DEPRECATED. Retained only for backward compatibility. This option has no effect.", ) if "extend_parser" in globals(): extend_parser(parser) # noqa: F821 options, args = parser.parse_args() global logger if "adjust_options" in globals(): adjust_options(options, args) # noqa: F821 verbosity = options.verbose - options.quiet logger = Logger([(Logger.level_for_integer(2 - verbosity), sys.stdout)]) if options.python and not os.environ.get("VIRTUALENV_INTERPRETER_RUNNING"): env = os.environ.copy() interpreter = resolve_interpreter(options.python) if interpreter == sys.executable: logger.warn("Already using interpreter %s" % interpreter) else: logger.notify("Running virtualenv with interpreter %s" % interpreter) env["VIRTUALENV_INTERPRETER_RUNNING"] = "true" file = __file__ if file.endswith(".pyc"): file = file[:-1] popen = subprocess.Popen([interpreter, file] + sys.argv[1:], env=env) raise SystemExit(popen.wait()) if not args: print("You must provide a DEST_DIR") parser.print_help() sys.exit(2) if len(args) > 1: print("There must be only one argument: DEST_DIR (you gave %s)" % (" ".join(args))) parser.print_help() sys.exit(2) home_dir = args[0] if os.path.exists(home_dir) and os.path.isfile(home_dir): logger.fatal("ERROR: File already exists and is not a directory.") logger.fatal("Please provide a different path or delete the file.") sys.exit(3) if os.environ.get("WORKING_ENV"): logger.fatal("ERROR: you cannot run virtualenv while in a workingenv") logger.fatal("Please deactivate your workingenv, then re-run this script") sys.exit(3) if "PYTHONHOME" in os.environ: logger.warn("PYTHONHOME is set. You *must* activate the virtualenv before using it") del os.environ["PYTHONHOME"] if options.relocatable: make_environment_relocatable(home_dir) return create_environment( home_dir, site_packages=options.system_site_packages, clear=options.clear, prompt=options.prompt, search_dirs=options.search_dirs, download=options.download, no_setuptools=options.no_setuptools, no_pip=options.no_pip, no_wheel=options.no_wheel, symlink=options.symlink, ) if "after_install" in globals(): after_install(options, home_dir) # noqa: F821 def call_subprocess( cmd, show_stdout=True, filter_stdout=None, cwd=None, raise_on_returncode=True, extra_env=None, remove_from_env=None, stdin=None, ): cmd_parts = [] for part in cmd: if len(part) > 45: part = part[:20] + "..." + part[-20:] if " " in part or "\n" in part or '"' in part or "'" in part: part = '"%s"' % part.replace('"', '\\"') if hasattr(part, "decode"): try: part = part.decode(sys.getdefaultencoding()) except UnicodeDecodeError: part = part.decode(sys.getfilesystemencoding()) cmd_parts.append(part) cmd_desc = " ".join(cmd_parts) if show_stdout: stdout = None else: stdout = subprocess.PIPE logger.debug("Running command %s" % cmd_desc) if extra_env or remove_from_env: env = os.environ.copy() if extra_env: env.update(extra_env) if remove_from_env: for varname in remove_from_env: env.pop(varname, None) else: env = None try: proc = subprocess.Popen( cmd, stderr=subprocess.STDOUT, stdin=None if stdin is None else subprocess.PIPE, stdout=stdout, cwd=cwd, env=env, ) except Exception: e = sys.exc_info()[1] logger.fatal("Error {} while executing command {}".format(e, cmd_desc)) raise all_output = [] if stdout is not None: if stdin is not None: proc.stdin.write(stdin) proc.stdin.close() stdout = proc.stdout encoding = sys.getdefaultencoding() fs_encoding = sys.getfilesystemencoding() while 1: line = stdout.readline() try: line = line.decode(encoding) except UnicodeDecodeError: line = line.decode(fs_encoding) if not line: break line = line.rstrip() all_output.append(line) if filter_stdout: level = filter_stdout(line) if isinstance(level, tuple): level, line = level logger.log(level, line) if not logger.stdout_level_matches(level): logger.show_progress() else: logger.info(line) else: proc.communicate(stdin) proc.wait() if proc.returncode: if raise_on_returncode: if all_output: logger.notify("Complete output from command %s:" % cmd_desc) logger.notify("\n".join(all_output) + "\n----------------------------------------") raise OSError("Command {} failed with error code {}".format(cmd_desc, proc.returncode)) else: logger.warn("Command {} had error code {}".format(cmd_desc, proc.returncode)) def filter_install_output(line): if line.strip().startswith("running"): return Logger.INFO return Logger.DEBUG def find_wheels(projects, search_dirs): """Find wheels from which we can import PROJECTS. Scan through SEARCH_DIRS for a wheel for each PROJECT in turn. Return a list of the first wheel found for each PROJECT """ wheels = [] # Look through SEARCH_DIRS for the first suitable wheel. Don't bother # about version checking here, as this is simply to get something we can # then use to install the correct version. for project in projects: for dirname in search_dirs: # This relies on only having "universal" wheels available. # The pattern could be tightened to require -py2.py3-none-any.whl. files = glob.glob(os.path.join(dirname, project + "-*.whl")) if files: wheels.append(os.path.abspath(files[0])) break else: # We're out of luck, so quit with a suitable error logger.fatal("Cannot find a wheel for {}".format(project)) return wheels def install_wheel(project_names, py_executable, search_dirs=None, download=False): if search_dirs is None: search_dirs = file_search_dirs() wheels = find_wheels(["setuptools", "pip"], search_dirs) python_path = os.pathsep.join(wheels) # PIP_FIND_LINKS uses space as the path separator and thus cannot have paths # with spaces in them. Convert any of those to local file:// URL form. try: from urlparse import urljoin from urllib import pathname2url except ImportError: from urllib.parse import urljoin from urllib.request import pathname2url def space_path2url(p): if " " not in p: return p return urljoin("file:", pathname2url(os.path.abspath(p))) findlinks = " ".join(space_path2url(d) for d in search_dirs) SCRIPT = textwrap.dedent( """ import sys import pkgutil import tempfile import os try: from pip._internal import main as _main cert_data = pkgutil.get_data("pip._vendor.certifi", "cacert.pem") except ImportError: from pip import main as _main cert_data = pkgutil.get_data("pip._vendor.requests", "cacert.pem") if cert_data is not None: cert_file = tempfile.NamedTemporaryFile(delete=False) cert_file.write(cert_data) cert_file.close() else: cert_file = None try: args = ["install", "--ignore-installed"] if cert_file is not None: args += ["--cert", cert_file.name{}] args += sys.argv[1:] sys.exit(_main(args)) finally: if cert_file is not None: os.remove(cert_file.name) """.format( ", '--no-cache'" if is_jython else "" ) ).encode("utf8") cmd = [py_executable, "-"] + project_names logger.start_progress("Installing %s..." % (", ".join(project_names))) logger.indent += 2 env = { "PYTHONPATH": python_path, "JYTHONPATH": python_path, # for Jython < 3.x "PIP_FIND_LINKS": findlinks, "PIP_USE_WHEEL": "1", "PIP_ONLY_BINARY": ":all:", "PIP_USER": "0", "PIP_NO_INPUT": "1", } if not download: env["PIP_NO_INDEX"] = "1" try: call_subprocess(cmd, show_stdout=False, extra_env=env, stdin=SCRIPT) finally: logger.indent -= 2 logger.end_progress() def create_environment( home_dir, site_packages=False, clear=False, prompt=None, search_dirs=None, download=False, no_setuptools=False, no_pip=False, no_wheel=False, symlink=True, ): """ Creates a new environment in ``home_dir``. If ``site_packages`` is true, then the global ``site-packages/`` directory will be on the path. If ``clear`` is true (default False) then the environment will first be cleared. """ home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) py_executable = os.path.abspath( install_python(home_dir, lib_dir, inc_dir, bin_dir, site_packages=site_packages, clear=clear, symlink=symlink) ) install_distutils(home_dir) to_install = [] if not no_setuptools: to_install.append("setuptools") if not no_pip: to_install.append("pip") if not no_wheel: to_install.append("wheel") if to_install: install_wheel(to_install, py_executable, search_dirs, download=download) install_activate(home_dir, bin_dir, prompt) install_python_config(home_dir, bin_dir, prompt) def is_executable_file(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) def path_locations(home_dir, dry_run=False): """Return the path locations for the environment (where libraries are, where scripts go, etc)""" home_dir = os.path.abspath(home_dir) # XXX: We'd use distutils.sysconfig.get_python_inc/lib but its # prefix arg is broken: http://bugs.python.org/issue3386 if is_win: # Windows has lots of problems with executables with spaces in # the name; this function will remove them (using the ~1 # format): if not dry_run: mkdir(home_dir) if " " in home_dir: import ctypes GetShortPathName = ctypes.windll.kernel32.GetShortPathNameW size = max(len(home_dir) + 1, 256) buf = ctypes.create_unicode_buffer(size) try: u = unicode except NameError: u = str ret = GetShortPathName(u(home_dir), buf, size) if not ret: print('Error: the path "%s" has a space in it' % home_dir) print("We could not determine the short pathname for it.") print("Exiting.") sys.exit(3) home_dir = str(buf.value) lib_dir = join(home_dir, "Lib") inc_dir = join(home_dir, "Include") bin_dir = join(home_dir, "Scripts") if is_jython: lib_dir = join(home_dir, "Lib") inc_dir = join(home_dir, "Include") bin_dir = join(home_dir, "bin") elif is_pypy: lib_dir = home_dir inc_dir = join(home_dir, "include") bin_dir = join(home_dir, "bin") elif not is_win: lib_dir = join(home_dir, "lib", py_version) inc_dir = join(home_dir, "include", py_version + abiflags) bin_dir = join(home_dir, "bin") return home_dir, lib_dir, inc_dir, bin_dir def change_prefix(filename, dst_prefix): prefixes = [sys.prefix] if is_darwin: prefixes.extend( ( os.path.join("/Library/Python", sys.version[:3], "site-packages"), os.path.join(sys.prefix, "Extras", "lib", "python"), os.path.join("~", "Library", "Python", sys.version[:3], "site-packages"), # Python 2.6 no-frameworks os.path.join("~", ".local", "lib", "python", sys.version[:3], "site-packages"), # System Python 2.7 on OSX Mountain Lion os.path.join("~", "Library", "Python", sys.version[:3], "lib", "python", "site-packages"), ) ) if hasattr(sys, "real_prefix"): prefixes.append(sys.real_prefix) if hasattr(sys, "base_prefix"): prefixes.append(sys.base_prefix) prefixes = list(map(os.path.expanduser, prefixes)) prefixes = list(map(os.path.abspath, prefixes)) # Check longer prefixes first so we don't split in the middle of a filename prefixes = sorted(prefixes, key=len, reverse=True) filename = os.path.abspath(filename) # On Windows, make sure drive letter is uppercase if is_win and filename[0] in "abcdefghijklmnopqrstuvwxyz": filename = filename[0].upper() + filename[1:] for i, prefix in enumerate(prefixes): if is_win and prefix[0] in "abcdefghijklmnopqrstuvwxyz": prefixes[i] = prefix[0].upper() + prefix[1:] for src_prefix in prefixes: if filename.startswith(src_prefix): _, relpath = filename.split(src_prefix, 1) if src_prefix != os.sep: # sys.prefix == "/" assert relpath[0] == os.sep relpath = relpath[1:] return join(dst_prefix, relpath) assert False, "Filename {} does not start with any of these prefixes: {}".format(filename, prefixes) def copy_required_modules(dst_prefix, symlink): import warnings with warnings.catch_warnings(): # Ignore deprecation of the imp module # TODO: do not use deprecated imp module warnings.simplefilter("ignore") import imp for modname in REQUIRED_MODULES: if modname in sys.builtin_module_names: logger.info("Ignoring built-in bootstrap module: %s" % modname) continue try: f, filename, _ = imp.find_module(modname) except ImportError: logger.info("Cannot import bootstrap module: %s" % modname) else: if f is not None: f.close() # special-case custom readline.so on OS X, but not for pypy: if ( modname == "readline" and sys.platform == "darwin" and not (is_pypy or filename.endswith(join("lib-dynload", "readline.so"))) ): dst_filename = join(dst_prefix, "lib", "python%s" % sys.version[:3], "readline.so") elif modname == "readline" and sys.platform == "win32": # special-case for Windows, where readline is not a # standard module, though it may have been installed in # site-packages by a third-party package pass else: dst_filename = change_prefix(filename, dst_prefix) copyfile(filename, dst_filename, symlink) if filename.endswith(".pyc"): pyfile = filename[:-1] if os.path.exists(pyfile): copyfile(pyfile, dst_filename[:-1], symlink) def copy_tcltk(src, dest, symlink): """ copy tcl/tk libraries on Windows (issue #93) """ for libversion in "8.5", "8.6": for libname in "tcl", "tk": srcdir = join(src, "tcl", libname + libversion) destdir = join(dest, "tcl", libname + libversion) # Only copy the dirs from the above combinations that exist if os.path.exists(srcdir) and not os.path.exists(destdir): copyfileordir(srcdir, destdir, symlink) def subst_path(prefix_path, prefix, home_dir): prefix_path = os.path.normpath(prefix_path) prefix = os.path.normpath(prefix) home_dir = os.path.normpath(home_dir) if not prefix_path.startswith(prefix): logger.warn("Path not in prefix %r %r", prefix_path, prefix) return return prefix_path.replace(prefix, home_dir, 1) def install_python(home_dir, lib_dir, inc_dir, bin_dir, site_packages, clear, symlink=True): """Install just the base environment, no distutils patches etc""" if sys.executable.startswith(bin_dir): print("Please use the *system* python to run this script") return if clear: rmtree(lib_dir) # FIXME: why not delete it? # Maybe it should delete everything with #!/path/to/venv/python in it logger.notify("Not deleting %s", bin_dir) if hasattr(sys, "real_prefix"): logger.notify("Using real prefix %r" % sys.real_prefix) prefix = sys.real_prefix elif hasattr(sys, "base_prefix"): logger.notify("Using base prefix %r" % sys.base_prefix) prefix = sys.base_prefix else: prefix = sys.prefix prefix = os.path.abspath(prefix) mkdir(lib_dir) fix_lib64(lib_dir, symlink) stdlib_dirs = [os.path.dirname(os.__file__)] if is_win: stdlib_dirs.append(join(os.path.dirname(stdlib_dirs[0]), "DLLs")) elif is_darwin: stdlib_dirs.append(join(stdlib_dirs[0], "site-packages")) if hasattr(os, "symlink"): logger.info("Symlinking Python bootstrap modules") else: logger.info("Copying Python bootstrap modules") logger.indent += 2 try: # copy required files... for stdlib_dir in stdlib_dirs: if not os.path.isdir(stdlib_dir): continue for fn in os.listdir(stdlib_dir): bn = os.path.splitext(fn)[0] if fn != "site-packages" and bn in REQUIRED_FILES: copyfile(join(stdlib_dir, fn), join(lib_dir, fn), symlink) # ...and modules copy_required_modules(home_dir, symlink) finally: logger.indent -= 2 # ...copy tcl/tk if is_win: copy_tcltk(prefix, home_dir, symlink) mkdir(join(lib_dir, "site-packages")) import site site_filename = site.__file__ if site_filename.endswith(".pyc") or site_filename.endswith(".pyo"): site_filename = site_filename[:-1] elif site_filename.endswith("$py.class"): site_filename = site_filename.replace("$py.class", ".py") site_filename_dst = change_prefix(site_filename, home_dir) site_dir = os.path.dirname(site_filename_dst) writefile(site_filename_dst, SITE_PY) writefile(join(site_dir, "orig-prefix.txt"), prefix) site_packages_filename = join(site_dir, "no-global-site-packages.txt") if not site_packages: writefile(site_packages_filename, "") if is_pypy or is_win: stdinc_dir = join(prefix, "include") else: stdinc_dir = join(prefix, "include", py_version + abiflags) if os.path.exists(stdinc_dir): copyfile(stdinc_dir, inc_dir, symlink) else: logger.debug("No include dir %s" % stdinc_dir) platinc_dir = distutils.sysconfig.get_python_inc(plat_specific=1) if platinc_dir != stdinc_dir: platinc_dest = distutils.sysconfig.get_python_inc(plat_specific=1, prefix=home_dir) if platinc_dir == platinc_dest: # Do platinc_dest manually due to a CPython bug; # not http://bugs.python.org/issue3386 but a close cousin platinc_dest = subst_path(platinc_dir, prefix, home_dir) if platinc_dest: # PyPy's stdinc_dir and prefix are relative to the original binary # (traversing virtualenvs), whereas the platinc_dir is relative to # the inner virtualenv and ignores the prefix argument. # This seems more evolved than designed. copyfile(platinc_dir, platinc_dest, symlink) # pypy never uses exec_prefix, just ignore it if sys.exec_prefix != prefix and not is_pypy: if is_win: exec_dir = join(sys.exec_prefix, "lib") elif is_jython: exec_dir = join(sys.exec_prefix, "Lib") else: exec_dir = join(sys.exec_prefix, "lib", py_version) if os.path.isdir(exec_dir): for fn in os.listdir(exec_dir): copyfile(join(exec_dir, fn), join(lib_dir, fn), symlink) if is_jython: # Jython has either jython-dev.jar and javalib/ dir, or just # jython.jar for name in "jython-dev.jar", "javalib", "jython.jar": src = join(prefix, name) if os.path.exists(src): copyfile(src, join(home_dir, name), symlink) # XXX: registry should always exist after Jython 2.5rc1 src = join(prefix, "registry") if os.path.exists(src): copyfile(src, join(home_dir, "registry"), symlink=False) copyfile(join(prefix, "cachedir"), join(home_dir, "cachedir"), symlink=False) mkdir(bin_dir) py_executable = join(bin_dir, os.path.basename(sys.executable)) if "Python.framework" in prefix: # OS X framework builds cause validation to break # https://github.com/pypa/virtualenv/issues/322 if os.environ.get("__PYVENV_LAUNCHER__"): del os.environ["__PYVENV_LAUNCHER__"] if re.search(r"/Python(?:-32|-64)*$", py_executable): # The name of the python executable is not quite what # we want, rename it. py_executable = os.path.join(os.path.dirname(py_executable), "python") logger.notify("New %s executable in %s", expected_exe, py_executable) pcbuild_dir = os.path.dirname(sys.executable) pyd_pth = os.path.join(lib_dir, "site-packages", "virtualenv_builddir_pyd.pth") if is_win and os.path.exists(os.path.join(pcbuild_dir, "build.bat")): logger.notify("Detected python running from build directory %s", pcbuild_dir) logger.notify("Writing .pth file linking to build directory for *.pyd files") writefile(pyd_pth, pcbuild_dir) else: pcbuild_dir = None if os.path.exists(pyd_pth): logger.info("Deleting %s (not Windows env or not build directory python)" % pyd_pth) os.unlink(pyd_pth) if sys.executable != py_executable: # FIXME: could I just hard link? executable = sys.executable shutil.copyfile(executable, py_executable) make_exe(py_executable) if is_win or is_cygwin: pythonw = os.path.join(os.path.dirname(sys.executable), "pythonw.exe") if os.path.exists(pythonw): logger.info("Also created pythonw.exe") shutil.copyfile(pythonw, os.path.join(os.path.dirname(py_executable), "pythonw.exe")) python_d = os.path.join(os.path.dirname(sys.executable), "python_d.exe") python_d_dest = os.path.join(os.path.dirname(py_executable), "python_d.exe") if os.path.exists(python_d): logger.info("Also created python_d.exe") shutil.copyfile(python_d, python_d_dest) elif os.path.exists(python_d_dest): logger.info("Removed python_d.exe as it is no longer at the source") os.unlink(python_d_dest) # we need to copy the DLL to enforce that windows will load the correct one. # may not exist if we are cygwin. if is_pypy: py_executable_dlls = [("libpypy-c.dll", "libpypy_d-c.dll")] else: py_executable_dlls = [ ("python%s.dll" % (sys.version_info[0]), "python%s_d.dll" % (sys.version_info[0])), ( "python{}{}.dll".format(sys.version_info[0], sys.version_info[1]), "python{}{}_d.dll".format(sys.version_info[0], sys.version_info[1]), ), ] for py_executable_dll, py_executable_dll_d in py_executable_dlls: pythondll = os.path.join(os.path.dirname(sys.executable), py_executable_dll) pythondll_d = os.path.join(os.path.dirname(sys.executable), py_executable_dll_d) pythondll_d_dest = os.path.join(os.path.dirname(py_executable), py_executable_dll_d) if os.path.exists(pythondll): logger.info("Also created %s" % py_executable_dll) shutil.copyfile(pythondll, os.path.join(os.path.dirname(py_executable), py_executable_dll)) if os.path.exists(pythondll_d): logger.info("Also created %s" % py_executable_dll_d) shutil.copyfile(pythondll_d, pythondll_d_dest) elif os.path.exists(pythondll_d_dest): logger.info("Removed %s as the source does not exist" % pythondll_d_dest) os.unlink(pythondll_d_dest) if is_pypy: # make a symlink python --> pypy-c python_executable = os.path.join(os.path.dirname(py_executable), "python") if sys.platform in ("win32", "cygwin"): python_executable += ".exe" logger.info("Also created executable %s" % python_executable) copyfile(py_executable, python_executable, symlink) if is_win: for name in ["libexpat.dll", "libeay32.dll", "ssleay32.dll", "sqlite3.dll", "tcl85.dll", "tk85.dll"]: src = join(prefix, name) if os.path.exists(src): copyfile(src, join(bin_dir, name), symlink) for d in sys.path: if d.endswith("lib_pypy"): break else: logger.fatal("Could not find lib_pypy in sys.path") raise SystemExit(3) logger.info("Copying lib_pypy") copyfile(d, os.path.join(home_dir, "lib_pypy"), symlink) if os.path.splitext(os.path.basename(py_executable))[0] != expected_exe: secondary_exe = os.path.join(os.path.dirname(py_executable), expected_exe) py_executable_ext = os.path.splitext(py_executable)[1] if py_executable_ext.lower() == ".exe": # python2.4 gives an extension of '.4' :P secondary_exe += py_executable_ext if os.path.exists(secondary_exe): logger.warn( "Not overwriting existing {} script {} (you must use {})".format( expected_exe, secondary_exe, py_executable ) ) else: logger.notify("Also creating executable in %s" % secondary_exe) shutil.copyfile(sys.executable, secondary_exe) make_exe(secondary_exe) if ".framework" in prefix: if "Python.framework" in prefix: logger.debug("MacOSX Python framework detected") # Make sure we use the embedded interpreter inside # the framework, even if sys.executable points to # the stub executable in ${sys.prefix}/bin # See http://groups.google.com/group/python-virtualenv/ # browse_thread/thread/17cab2f85da75951 original_python = os.path.join(prefix, "Resources/Python.app/Contents/MacOS/Python") if "EPD" in prefix: logger.debug("EPD framework detected") original_python = os.path.join(prefix, "bin/python") shutil.copy(original_python, py_executable) # Copy the framework's dylib into the virtual # environment virtual_lib = os.path.join(home_dir, ".Python") if os.path.exists(virtual_lib): os.unlink(virtual_lib) copyfile(os.path.join(prefix, "Python"), virtual_lib, symlink) # And then change the install_name of the copied python executable try: mach_o_change(py_executable, os.path.join(prefix, "Python"), "@executable_path/../.Python") except Exception: e = sys.exc_info()[1] logger.warn("Could not call mach_o_change: %s. " "Trying to call install_name_tool instead." % e) try: call_subprocess( [ "install_name_tool", "-change", os.path.join(prefix, "Python"), "@executable_path/../.Python", py_executable, ] ) except Exception: logger.fatal("Could not call install_name_tool -- you must " "have Apple's development tools installed") raise if not is_win: # Ensure that 'python', 'pythonX' and 'pythonX.Y' all exist py_exe_version_major = "python%s" % sys.version_info[0] py_exe_version_major_minor = "python{}.{}".format(sys.version_info[0], sys.version_info[1]) py_exe_no_version = "python" required_symlinks = [py_exe_no_version, py_exe_version_major, py_exe_version_major_minor] py_executable_base = os.path.basename(py_executable) if py_executable_base in required_symlinks: # Don't try to symlink to yourself. required_symlinks.remove(py_executable_base) for pth in required_symlinks: full_pth = join(bin_dir, pth) if os.path.exists(full_pth): os.unlink(full_pth) if symlink: os.symlink(py_executable_base, full_pth) else: copyfile(py_executable, full_pth, symlink) cmd = [ py_executable, "-c", "import sys;out=sys.stdout;" 'getattr(out, "buffer", out).write(sys.prefix.encode("utf-8"))', ] logger.info('Testing executable with %s %s "%s"' % tuple(cmd)) try: proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) proc_stdout, proc_stderr = proc.communicate() except OSError: e = sys.exc_info()[1] if e.errno == errno.EACCES: logger.fatal("ERROR: The executable {} could not be run: {}".format(py_executable, e)) sys.exit(100) else: raise e proc_stdout = proc_stdout.strip().decode("utf-8") proc_stdout = os.path.normcase(os.path.abspath(proc_stdout)) norm_home_dir = os.path.normcase(os.path.abspath(home_dir)) if hasattr(norm_home_dir, "decode"): norm_home_dir = norm_home_dir.decode(sys.getfilesystemencoding()) if proc_stdout != norm_home_dir: logger.fatal("ERROR: The executable %s is not functioning" % py_executable) logger.fatal("ERROR: It thinks sys.prefix is {!r} (should be {!r})".format(proc_stdout, norm_home_dir)) logger.fatal("ERROR: virtualenv is not compatible with this system or executable") if is_win: logger.fatal( "Note: some Windows users have reported this error when they " 'installed Python for "Only this user" or have multiple ' "versions of Python installed. Copying the appropriate " "PythonXX.dll to the virtualenv Scripts/ directory may fix " "this problem." ) sys.exit(100) else: logger.info("Got sys.prefix result: %r" % proc_stdout) pydistutils = os.path.expanduser("~/.pydistutils.cfg") if os.path.exists(pydistutils): logger.notify("Please make sure you remove any previous custom paths from " "your %s file." % pydistutils) # FIXME: really this should be calculated earlier fix_local_scheme(home_dir, symlink) if site_packages: if os.path.exists(site_packages_filename): logger.info("Deleting %s" % site_packages_filename) os.unlink(site_packages_filename) return py_executable def install_activate(home_dir, bin_dir, prompt=None): if is_win or is_jython and os._name == "nt": files = {"activate.bat": ACTIVATE_BAT, "deactivate.bat": DEACTIVATE_BAT, "activate.ps1": ACTIVATE_PS} # MSYS needs paths of the form /c/path/to/file drive, tail = os.path.splitdrive(home_dir.replace(os.sep, "/")) home_dir_msys = (drive and "/%s%s" or "%s%s") % (drive[:1], tail) # Run-time conditional enables (basic) Cygwin compatibility home_dir_sh = """$(if [ "$OSTYPE" "==" "cygwin" ]; then cygpath -u '{}'; else echo '{}'; fi;)""".format( home_dir, home_dir_msys ) files["activate"] = ACTIVATE_SH.replace("__VIRTUAL_ENV__", home_dir_sh) else: files = {"activate": ACTIVATE_SH} # suppling activate.fish in addition to, not instead of, the # bash script support. files["activate.fish"] = ACTIVATE_FISH # same for csh/tcsh support... files["activate.csh"] = ACTIVATE_CSH # same for powershell files["activate.ps1"] = ACTIVATE_PS files["activate_this.py"] = ACTIVATE_THIS install_files(home_dir, bin_dir, prompt, files) def install_files(home_dir, bin_dir, prompt, files): if hasattr(home_dir, "decode"): home_dir = home_dir.decode(sys.getfilesystemencoding()) vname = os.path.basename(home_dir) for name, content in files.items(): content = content.replace("__VIRTUAL_PROMPT__", prompt or "") content = content.replace("__VIRTUAL_WINPROMPT__", prompt or "(%s)" % vname) content = content.replace("__VIRTUAL_ENV__", home_dir) content = content.replace("__VIRTUAL_NAME__", vname) content = content.replace("__BIN_NAME__", os.path.basename(bin_dir)) content = content.replace("__PATH_SEP__", os.pathsep) writefile(os.path.join(bin_dir, name), content) def install_python_config(home_dir, bin_dir, prompt=None): if sys.platform == "win32" or is_jython and os._name == "nt": files = {} else: files = {"python-config": PYTHON_CONFIG} install_files(home_dir, bin_dir, prompt, files) for name, _ in files.items(): make_exe(os.path.join(bin_dir, name)) def install_distutils(home_dir): distutils_path = change_prefix(distutils.__path__[0], home_dir) mkdir(distutils_path) # FIXME: maybe this prefix setting should only be put in place if # there's a local distutils.cfg with a prefix setting? home_dir = os.path.abspath(home_dir) # FIXME: this is breaking things, removing for now: # distutils_cfg = DISTUTILS_CFG + "\n[install]\nprefix=%s\n" % home_dir writefile(os.path.join(distutils_path, "__init__.py"), DISTUTILS_INIT) writefile(os.path.join(distutils_path, "distutils.cfg"), DISTUTILS_CFG, overwrite=False) def fix_local_scheme(home_dir, symlink=True): """ Platforms that use the "posix_local" install scheme (like Ubuntu with Python 2.7) need to be given an additional "local" location, sigh. """ try: import sysconfig except ImportError: pass else: if sysconfig._get_default_scheme() == "posix_local": local_path = os.path.join(home_dir, "local") if not os.path.exists(local_path): os.mkdir(local_path) for subdir_name in os.listdir(home_dir): if subdir_name == "local": continue copyfile( os.path.abspath(os.path.join(home_dir, subdir_name)), os.path.join(local_path, subdir_name), symlink, ) def fix_lib64(lib_dir, symlink=True): """ Some platforms (particularly Gentoo on x64) put things in lib64/pythonX.Y instead of lib/pythonX.Y. If this is such a platform we'll just create a symlink so lib64 points to lib """ # PyPy's library path scheme is not affected by this. # Return early or we will die on the following assert. if is_pypy: logger.debug("PyPy detected, skipping lib64 symlinking") return # Check we have a lib64 library path if not [p for p in distutils.sysconfig.get_config_vars().values() if isinstance(p, basestring) and "lib64" in p]: return logger.debug("This system uses lib64; symlinking lib64 to lib") assert os.path.basename(lib_dir) == "python%s" % sys.version[:3], "Unexpected python lib dir: %r" % lib_dir lib_parent = os.path.dirname(lib_dir) top_level = os.path.dirname(lib_parent) lib_dir = os.path.join(top_level, "lib") lib64_link = os.path.join(top_level, "lib64") assert os.path.basename(lib_parent) == "lib", "Unexpected parent dir: %r" % lib_parent if os.path.lexists(lib64_link): return if symlink: os.symlink(lib_dir, lib64_link) else: copyfile(lib_dir, lib64_link) def resolve_interpreter(exe): """ If the executable given isn't an absolute path, search $PATH for the interpreter """ # If the "executable" is a version number, get the installed executable for # that version orig_exe = exe python_versions = get_installed_pythons() if exe in python_versions: exe = python_versions[exe] if os.path.abspath(exe) != exe: exe = distutils.spawn.find_executable(exe) or exe if not os.path.exists(exe): logger.fatal("The path {} (from --python={}) does not exist".format(exe, orig_exe)) raise SystemExit(3) if not is_executable(exe): logger.fatal("The path {} (from --python={}) is not an executable file".format(exe, orig_exe)) raise SystemExit(3) return exe def is_executable(exe): """Checks a file is executable""" return os.path.isfile(exe) and os.access(exe, os.X_OK) # Relocating the environment: def make_environment_relocatable(home_dir): """ Makes the already-existing environment use relative paths, and takes out the #!-based environment selection in scripts. """ home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) activate_this = os.path.join(bin_dir, "activate_this.py") if not os.path.exists(activate_this): logger.fatal( "The environment doesn't have a file %s -- please re-run virtualenv " "on this environment to update it" % activate_this ) fixup_scripts(home_dir, bin_dir) fixup_pth_and_egg_link(home_dir) # FIXME: need to fix up distutils.cfg OK_ABS_SCRIPTS = [ "python", "python%s" % sys.version[:3], "activate", "activate.bat", "activate_this.py", "activate.fish", "activate.csh", ] def fixup_scripts(home_dir, bin_dir): if is_win: new_shebang_args = ("%s /c" % os.path.normcase(os.environ.get("COMSPEC", "cmd.exe")), "", ".exe") else: new_shebang_args = ("/usr/bin/env", sys.version[:3], "") # This is what we expect at the top of scripts: shebang = "#!%s" % os.path.normcase(os.path.join(os.path.abspath(bin_dir), "python%s" % new_shebang_args[2])) # This is what we'll put: new_shebang = "#!%s python%s%s" % new_shebang_args for filename in os.listdir(bin_dir): filename = os.path.join(bin_dir, filename) if not os.path.isfile(filename): # ignore subdirs, e.g. .svn ones. continue lines = None with open(filename, "rb") as f: try: lines = f.read().decode("utf-8").splitlines() except UnicodeDecodeError: # This is probably a binary program instead # of a script, so just ignore it. continue if not lines: logger.warn("Script %s is an empty file" % filename) continue old_shebang = lines[0].strip() old_shebang = old_shebang[0:2] + os.path.normcase(old_shebang[2:]) if not old_shebang.startswith(shebang): if os.path.basename(filename) in OK_ABS_SCRIPTS: logger.debug("Cannot make script %s relative" % filename) elif lines[0].strip() == new_shebang: logger.info("Script %s has already been made relative" % filename) else: logger.warn( "Script %s cannot be made relative (it's not a normal script that starts with %s)" % (filename, shebang) ) continue logger.notify("Making script %s relative" % filename) script = relative_script([new_shebang] + lines[1:]) with open(filename, "wb") as f: f.write("\n".join(script).encode("utf-8")) def relative_script(lines): "Return a script that'll work in a relocatable environment." activate = ( "import os; " "activate_this=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'activate_this.py'); " "exec(compile(open(activate_this).read(), activate_this, 'exec'), dict(__file__=activate_this)); " "del os, activate_this" ) # Find the last future statement in the script. If we insert the activation # line before a future statement, Python will raise a SyntaxError. activate_at = None for idx, line in reversed(list(enumerate(lines))): if line.split()[:3] == ["from", "__future__", "import"]: activate_at = idx + 1 break if activate_at is None: # Activate after the shebang. activate_at = 1 return lines[:activate_at] + ["", activate, ""] + lines[activate_at:] def fixup_pth_and_egg_link(home_dir, sys_path=None): """Makes .pth and .egg-link files use relative paths""" home_dir = os.path.normcase(os.path.abspath(home_dir)) if sys_path is None: sys_path = sys.path for path in sys_path: if not path: path = "." if not os.path.isdir(path): continue path = os.path.normcase(os.path.abspath(path)) if not path.startswith(home_dir): logger.debug("Skipping system (non-environment) directory %s" % path) continue for filename in os.listdir(path): filename = os.path.join(path, filename) if filename.endswith(".pth"): if not os.access(filename, os.W_OK): logger.warn("Cannot write .pth file %s, skipping" % filename) else: fixup_pth_file(filename) if filename.endswith(".egg-link"): if not os.access(filename, os.W_OK): logger.warn("Cannot write .egg-link file %s, skipping" % filename) else: fixup_egg_link(filename) def fixup_pth_file(filename): lines = [] prev_lines = [] with open(filename) as f: prev_lines = f.readlines() for line in prev_lines: line = line.strip() if not line or line.startswith("#") or line.startswith("import ") or os.path.abspath(line) != line: lines.append(line) else: new_value = make_relative_path(filename, line) if line != new_value: logger.debug("Rewriting path {} as {} (in {})".format(line, new_value, filename)) lines.append(new_value) if lines == prev_lines: logger.info("No changes to .pth file %s" % filename) return logger.notify("Making paths in .pth file %s relative" % filename) with open(filename, "w") as f: f.write("\n".join(lines) + "\n") def fixup_egg_link(filename): with open(filename) as f: link = f.readline().strip() if os.path.abspath(link) != link: logger.debug("Link in %s already relative" % filename) return new_link = make_relative_path(filename, link) logger.notify("Rewriting link {} in {} as {}".format(link, filename, new_link)) with open(filename, "w") as f: f.write(new_link) def make_relative_path(source, dest, dest_is_directory=True): """ Make a filename relative, where the filename is dest, and it is being referred to from the filename source. >>> make_relative_path('/usr/share/something/a-file.pth', ... '/usr/share/another-place/src/Directory') '../another-place/src/Directory' >>> make_relative_path('/usr/share/something/a-file.pth', ... '/home/user/src/Directory') '../../../home/user/src/Directory' >>> make_relative_path('/usr/share/a-file.pth', '/usr/share/') './' """ source = os.path.dirname(source) if not dest_is_directory: dest_filename = os.path.basename(dest) dest = os.path.dirname(dest) dest = os.path.normpath(os.path.abspath(dest)) source = os.path.normpath(os.path.abspath(source)) dest_parts = dest.strip(os.path.sep).split(os.path.sep) source_parts = source.strip(os.path.sep).split(os.path.sep) while dest_parts and source_parts and dest_parts[0] == source_parts[0]: dest_parts.pop(0) source_parts.pop(0) full_parts = [".."] * len(source_parts) + dest_parts if not dest_is_directory: full_parts.append(dest_filename) if not full_parts: # Special case for the current directory (otherwise it'd be '') return "./" return os.path.sep.join(full_parts) # Bootstrap script creation: def create_bootstrap_script(extra_text, python_version=""): """ Creates a bootstrap script, which is like this script but with extend_parser, adjust_options, and after_install hooks. This returns a string that (written to disk of course) can be used as a bootstrap script with your own customizations. The script will be the standard virtualenv.py script, with your extra text added (your extra text should be Python code). If you include these functions, they will be called: ``extend_parser(optparse_parser)``: You can add or remove options from the parser here. ``adjust_options(options, args)``: You can change options here, or change the args (if you accept different kinds of arguments, be sure you modify ``args`` so it is only ``[DEST_DIR]``). ``after_install(options, home_dir)``: After everything is installed, this function is called. This is probably the function you are most likely to use. An example would be:: def after_install(options, home_dir): subprocess.call([join(home_dir, 'bin', 'easy_install'), 'MyPackage']) subprocess.call([join(home_dir, 'bin', 'my-package-script'), 'setup', home_dir]) This example immediately installs a package, and runs a setup script from that package. If you provide something like ``python_version='2.5'`` then the script will start with ``#!/usr/bin/env python2.5`` instead of ``#!/usr/bin/env python``. You can use this when the script must be run with a particular Python version. """ filename = __file__ if filename.endswith(".pyc"): filename = filename[:-1] with codecs.open(filename, "r", encoding="utf-8") as f: content = f.read() py_exe = "python%s" % python_version content = ("#!/usr/bin/env %s\n" % py_exe) + "# WARNING: This file is generated\n" + content return content.replace("##EXT" "END##", extra_text) # EXTEND def convert(s): b = base64.b64decode(s.encode("ascii")) return zlib.decompress(b).decode("utf-8") # file site.py SITE_PY = convert( """ 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 AfPVUFM= """ ) # file activate.sh ACTIVATE_SH = convert( """ eJytVd9v2kAMfs9fYQLq2m4MscdNVKMqEkgtVIQxbeuUHolpTgsXdHehpT/+9/mSEBJS2MOaB0ji z77P9menDpOAK5jzEGERKw0zhFihD/dcB2CrKJYewoyLFvM0XzGNNpzOZbSAGVPBqVWHdRSDx4SI NMhYANfgc4meDteW5ePGC45P4MkCumKhUENzDsu1H3lw1vJx1RJxGMKns6O2lWDqINGgotAHFCsu I7FAoWHFJGezEFWGqsEvaD5C42naHb93X+A3+elYCgVaxgh8DmQAys9HL2SS0mIaWBgm7mTN/O3G kzu6vHCng/HkW/fSve5O+hTOpnhfQAcoEry5jKVjNypoO0fgwzKSOgHm79KUK06Jfc7/RebHpD8a 9kdXvT2UcnuFWG6p0stNB0mWUUQ1q3uiGRVEMfXHR03dTuQATPjwqIIPcB9wL4CArRAY/ZHJixYL Y9YBtcAoLQtFevOoI9QaHcEdMSAB0d08kuZhyUiSmav6CPCdVBnFOjNrLu6yMCWgKRA0TInBC5i4 QwX3JG/mm581GKnSsSSxJTFHf9MAKr8w5T/vOv1mUurn5/zlT6fvTntjZzAaNl9rQ5JkU5KIc0GX inagwU57T2eddqWlTrvaS6d9sImZeUMkhWysveF0m37NcGub9Dpgi0j4qGiOzATjDr06OBjOYQOo 7RBoGtNm9Denv1i0LVI7lxJDXLHSSBeWRflsyyqw7diuW3h0XdvK6lBMyaoMG1UyHdTsoYBuue75 YOgOu1c91/2cwYpznPPeDoQpGL2xSm09NKp7BsvQ2hnT3aMs07lUnskpxewvBk73/LLnXo9HV9eT ijB3hWBO2ygoiWg/bKuZxqCCQq0DD3vkWIVvI2KosIw+vqW1gIItEG5KJb+xb09g65ktwYKgTc51 uGJ/EFQs0ayEWLCQM5V9N4g+1+8UbXOJzF8bqhKtIqIwicWvzNFROZJlpfD8A7Vc044R0FxkcezG VzsV75usvTdYef+57v5n1b225qhXfwEmxHEs """ ) # file activate.fish ACTIVATE_FISH = convert( """ eJyFVlFv2zYQftevuMoOnBS1g70WGIZ08RADSRw4boBhGGhGOsUcKFIjKbUu9uN7lCyJsrVWDxZE fnf38e6+oyew3QsLmZAIeWkdvCKUFlP6EeoNdlaXJkF4FeqaJ05U3OEiE3a/g/dfhNsLBRxMqZQH +3W4hL1zxcfra/9l9yjlItE5XIFFa4VW7xfRBG41KO28IQgHqTCYOHlYRFFWKoqiFaTYhoN5CrPl V8JVwriSS1QV5DpF4CoFg640CpwGt0dyanIugRDCaJWjcotZBPRMCGjRgZZpuAsVN4K/SrQ1SmTg kHIwVxBP2fr+lr2sNtvPN/fs6WZ7F9cY/3hP87ev4FfhHDjEIYwDUKXRz6L+ub1bP96tH5Yjsbu9 Uwbdxo95DGE/YPPH6vmOPW3WD09btn5Zbjar24DPBJ7JO1eAeeEOIHVCBdhNffVZW01WcEcdQ0Xi UuovdakM5roiqM5gV4MLo8nDrm281tYS891ieBQJ5+6jgNHScItBu8zsSYymc6zTBmsy2og3objs 44ThIbAdAyTAqK9YgPqZBZ5ZjNZqLPPDah3RbVGXjy/DKsZTbt6qv375O4Z3v8JMaZXSsim9tnA2 KKLM5u3eu3HOxSHVyfhWL9eOX81xAp+V5yiMQYkVDyW3iAKRD5lFUdvmwckgZiz4ZCzuYWcSg2kt tIFS42lgfs3Yp9Uje7x5WJKnI7zju5v2+tj5bNLiImreMP8XTtQzTiNQ6BgeQy91trrp12e6OLg9 LczzZg3qejboTqnfhidjt6vnm0/3y2PnxMcB+LsuDnWzJyUVgwoxlEQXm5NYTrvzKMBBz4ftftbN A/ioGqjleIUDQlPruCvtB8i0CW0sobi/Jmwh+YFujLNx4MM3xq2TcBw8NSR1hcYIujfIa0Xv9LcA s8r8jfQB/vF3YGGwoBTX5MLbQvEc+9jhpOw78yhX1k/IuoxGKJfB7MJe2NkoHC7pLCzRks7eXGNX nQeUFv/H3eWFvYLZiDFcvtIQ9IyH3REHbtsp0qRgMzIQu3R2NsleQ4z+Op72WY/hP2j+KXTA0QE3 nFutYMbG3AnpuuO/AygvqEs= """ ) # file activate.csh ACTIVATE_CSH = convert( """ eJx9k9tq20AQhu/3Kf7IJm5N4vRarts6caCBxAnBCZSmLCtpXC1IK2e1svFNn72zklzkA9WFkOb0 z34708Mi1SWWOiPkVekQEaqSEmy0SxGURWVjQqTNlYqdXitHo7hMAwyXtsjBn8OR6OFHUSFWxhQO tjLQDom2FLts6703ljgvQbTFTK11QphpXGeq1Pic1IYk+vY7VzobxUX+ZSRESQ6GNpk2NBm8iYEQ KtOqREK7LjBwxN32v8rH+5l8vXtevEzv5dN08R1nE3zC+Tm4CJk1alvQP4oL3wMfVRkvduQdw1Kq ynSMkzrPjw9Pi64SVsxj5SaHQnXgf6Rq/7hx+W53jtv5aysdvJ2Fw8BrBaYwCZts5SFQW/OITMe6 2iZFzPR6eKm1tbWU0VoZh7WyWkUZlSPRyd1XqC/ioCsEUnZ+pQya6zoiyChazGL/JjrZ4fuVlNd3 czmfPtxKGf7L4Ecv8aGj1ZBiuZpE8BEuJSPAj1fn8tKonDDBqRxBWUkng/e6cV6aTKKXHtlNUWWJ 3wdtoDyZS20c2ZoV+SLaFiYn4y44mGM2qY5TXoOSLtBvxgG8WhUTXfIgJ1CG14qw8XXNwHFWrCxB RUXl/HHaGeK47Ubx5ngCPHmt9eDEJ8aIiTex/hh1cseAyR8Mg367VWwYdiuG+4RaSebzs7+jFb7/ Qqd+g6mF1Uz2LnK3rfX08dulhcFl3vwL0SyW+At+C2qe """ ) # file activate.bat ACTIVATE_BAT = convert( """ eJx9Ul9LhEAQfxf8DoOclI/dYyFkaCmcq4gZQTBUrincuZFbff12T133TM+nnd35/Zvxlr7XDFhV mUZHOVhFlOWP3g4DUriIWoVomYZpNBWUtGpaWgImO191pFkSpzlcmgaI70jVX7n2Qp8tuByg+46O CMHbMq64T+nmlJt082D1T44muCDk2prgEHF4mdI9RaS/QwSt3zSyIAaftRccvqVTBziD1x/WlPD5 xd729NDBb8Nr4DU9QNMKsJeH9pkhPedhQsIkDuCDCa6A+NF9IevVFAohkqizdHetg/tkWvPoftWJ MCqnOxv7/x7Np6yv9P2Ker5dmX8yNyCkkWnbZy3N5LarczlqL8htx2EM9rQ/2H5BvIsIEi8OEG8U +g8CsNTr """ ) # file deactivate.bat DEACTIVATE_BAT = convert( """ eJyFkN0KgkAUhO8X9h0GQapXCIQEDQX/EBO6kso1F9KN3Or1201Si6JzN+fMGT5mxQ61gKgqSijp mETup9nGDgo3yi29S90QjmhnEteOYb6AFNjdBC9xvoj9iTUd7lzWkDVrwFuYiZ15JiW8QiskSlbx lpUo4sApXtlJGodJhqNQWW7k+Ou831ACNZrC6BeW+eXPNEbfl7OiXr6H/oHZZl4ceXHoToG0nuIM pk+k4fAba/wd0Pr4P2CqyLeOlJ4iKfkJo6v/iaH9YzfPMEoeMG2RUA== """ ) # file activate.ps1 ACTIVATE_PS = convert( """ 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 """ ) # file distutils-init.py DISTUTILS_INIT = convert( """ eJytV21v5DQQ/u5fMaRCJLANcHxBlVYI7g504nSgU7+gqorcxNk1zdrB9m679+uZsfPivGyPD0Sq 5PWMZ8bPPDPjykOrjQN5aJkMS237lT0PyydulFQ7y9gV6LbUlQBpQWkHHE7SuCNvhDrBQVfHRmzA angSUHIFR4uaDpyGWqoK3F6AdVUjH9DQO2+bK/cF3OIBbR5BK2jP7XmDimj/cLQOHgT6CIZxlzsw wspKWJCKzKEdtHbdnt1eq29RT9ZSVNfP+Tn/BJU0onTanIfL+dgZQ4HiBwFbvG7ecrfPux0SWXd0 srEF7Ucaf2up0pl6GgzmRVHLRhRFtoFkMJBkTNbDcaXNgRbp1EEG20UQ6eLMYD+7YYBfn4+cFmly i7BGaRg8QMvLR75DBB18aYG3reDGUjYQ1YAfWMKh0SV3EtHnNmyerROH0dBPeBfRWBG8Fz7yosil ssK49LsNzC8FV8iOf/gN/Prjq+/9ISN4U4yRbYlzeaN5VYTkpkkxXmFUTDbwQSsx97CBNEER/ZGd P3//rXjz7uPb17d/fPwry7zDK3it27O/jhGNOCHREAdn5MPRCetVnDmHG4VbGXGSFlEoCgxvGm8e S/0R8VyK1sHPvcW3xmgzWmu5tR1YJ2EuWx2EjNVGR5BDR1na2FBCSq1quaN7SYuCG/soW8aGKzxw KyzGonasC+0DZjaKalTAOHBBtYxQlnt4OMqmKsSzg5GcUGlh1VcOHpV+gj3+IWt2wnk8seJsVFze zp6K9wmLXD9ZuKai33NTUXUESpEKUtDoY9cHvG9R0dXy1ohaPmeMCsb/brirkfxUHAka/eFVEi4x xSgv9eHAVZWPN+hQGzeQ0RqXwwbzdsoG8zNqpROVbExjJWrqXLyRn0ShW6oRm1rR1JEOfRQ37uaI jOHmjCnGOsMWRtydatK3VN3C3f1ETTRoEvmmLHbIUqSLI5soodl/c7HYy23bSNe3GyvajLEXjUQV P2mLlDNP7ZBILMz3SJGkq8T+m4Ccr8PKXkjzanKHH10fCQbmuxFDthBx4SryQquOlfaGMYqWhlYR Cs93YEKR1PI30oa6x8jzhRbDMRKIM92PmVP7QtjCqpsOi45ZCHWYVlgMrbbyORnzjQPW+DPdPEvy 9hwBV++S0K2G5r16aPXMXGuR8T7ZE8U4aq8uLYnSqdIYC5Y5NgscNjiPGgwi9cAsy4t0cqEq+yRx IC4iXikBbwhpedAnkdLxjE1FNA9Vla6Eb4Q7GhXUWHgTfCbliM8KDWJ6jS18yjFsqkV4vhRSlVSm vWI+FXWsGsSzkyk1zcK2osQnULnFEg352VIP6uBBHMPmsjd1+959XMsxHstwp057l1jF7FKYOPMq XfphuDTXC9klDGJ4ijk8M3vYuC6hSQ/QF9BE8RJNasQVjjSSXkQ3VgJqWf8j3IuopjF9FnzUuQx+ JFwHf4ZmMUezt9eJTzyMnImpSLYKfyRPv+ZmZztgPT6dxRU/ne6Qg5ceEPRhvDTN1lradIg1fogN 56YTeQiK3qaly3y6k/fvfsGHaOL/N8KONihN29OwfdcfuMdo+rjwicftIz6NqDfyphmOzh8FUQjU OmchoHvC5YLn/jHq19/AXef8fuqdzMaUHI4sSBblY4VlK1J2kRsLnsW8+Rc/zwIT """ ) # file distutils.cfg DISTUTILS_CFG = convert( """ eJxNj00KwkAMhfc9xYNuxe4Ft57AjYiUtDO1wXSmNJnK3N5pdSEEAu8nH6lxHVlRhtDHMPATA4uH xJ4EFmGbvfJiicSHFRzUSISMY6hq3GLCRLnIvSTnEefN0FIjw5tF0Hkk9Q5dRunBsVoyFi24aaLg 9FDOlL0FPGluf4QjcInLlxd6f6rqkgPu/5nHLg0cXCscXoozRrP51DRT3j9QNl99AP53T2Q= """ ) # file activate_this.py ACTIVATE_THIS = convert( """ eJyNU8Fu2zAMvesrCA1FbSxzhvUWIIcOGLDDNvTQyxAEhmLTiVZZMiTFif9+pFwnbtFiM2BbFJ8e +ShSSvl1gGPQdg94xqrRBrN40KHk1QJqXcWsTEZZri+OPIfBHeGkjRGqirpXEYG90Gsfj8qg7YFe 7Z1t0cZCiEf2VsrCDike1nA6oE0s7TFE3gJy4lmHyMk8DPHgLGgb0Xce6bsA66KIB5zH2Gm77BJU SCmFiH5YCaBnylngucIuwi/V4jfvnR/dXmkKeB8C+qidTZ4sefiRv6e0/NGOuox+wmuFbjsVD8vo lpP4kkFFN9y+Ltn7yDyXKWAudNs5H8GFaRV0xMt6CEI4U5culMwFawIWz7Ut9hgz+XD/+F0uQMpc XF2bcXs74vlkUWtvqQzZZKtd4P8lWbrVjxM4YMfGNa7YKarY+2T/JiehDcspOqNi43wL6zXIk7Z3 X+R4K6ybglVPao9hFuuP0zbj+CTyh96xVoZ+mqAkHE3A/ycxI8nYOTdBwk1KrEcfqBs2q7vtGyGo DfuSYNM1GGrVLOkhOxeC8YWqa/5TNbIXieSCkR6VKYmn0WciSGeTIa5L2uckxQf46XoeKpqLuqZ5 IbY2QHRpq6Ebpo5pksHxV8LiaZ7dZiuoxukUTdGrZMdK0XUkN80VQ17oW12GYc5bqK5DW2d8LL8g JlqS11LOz95pd7P6zE04pxF/AX70hVA= """ ) # file python-config PYTHON_CONFIG = convert( """ eJyNVV1P2zAUfc+v8ODBiSABxlulTipbO6p1LWqBgVhlhcZpPYUkctzSivHfd6+dpGloGH2Ja/ue e+65Hz78xNhtf3x90xmw7vCWsRPGLvpDNuz87MKfdKMWSWxZ4ilNpCLZJiuWc66SVFUOZkkcirll rfxIBAzOMtImDzSVPBRrekwoX/OZu/0r4lm0DHiG60g86u8sjPw5rCyy86NRkB8QuuBRSqfAKESn 3orLTCQxE3GYkC9tYp8fk89OSwNsmXgizrhUtnumeSgeo5GbLUMk49Rv+2nK48Cm/qMwfp333J2/ dVcAGE0CIQHBsgIeEr4Wij0LtWDLzJ9ze5YEvH2WI6CHTAVcSu9ZCsXtgxu81CIvp6/k4eXsdfo7 PvDCRD75yi41QitfzlcPp1OI7i/1/iQitqnr0iMgQ+A6wa+IKwwdxyk9IiXNAzgquTFU8NIxAVjM osm1Zz526e+shQ4hKRVci69nPC3Kw4NQEmkQ65E7OodxorSvxjvpBjQHDmWFIQ1mlmzlS5vedseT /mgIEsMJ7Lxz2bLAF9M5xeLEhdbHxpWOw0GdkJApMVBRF1y+a0z3c9WZPAXGFcFrJgCIB+024uad 0CrzmEoRa3Ub4swNIHPGf7QDV+2uj2OiFWsChgCwjKqN6rp5izpbH6Wc1O1TclQTP/XVwi6anTr1 1sbubjZLI1+VptPSdCfwnFBrB1jvebrTA9uUhU2/9gad7xPqeFkaQcnnLbCViZK8d7R1kxzFrIJV 8EaLYmKYpvGVkig+3C5HCXbM1jGCGekiM2pRCVPyRyXYdPf6kcbWEQ36F5V4Gq9N7icNNw+JHwRE LTgxRXACpvnQv/PuT0xCCAywY/K4hE6Now2qDwaSE5FB+1agsoUveYDepS83qFcF1NufvULD3fTl g6Hgf7WBt6lzMeiyyWVn3P1WVbwaczHmTzE9A5SyItTVgFYyvs/L/fXlaNgbw8v3azT+0eikVlWD /vBHbzQumP23uBCjsYdrL9OWARwxs/nuLOzeXbPJTa/Xv6sUmQir5pC1YRLz3eA+CD8Z0XpcW8v9 MZWF36ryyXXf3yBIz6nzqz8Muyz0m5Qj7OexfYo/Ph3LqvkHUg7AuA== """ ) MH_MAGIC = 0xFEEDFACE MH_CIGAM = 0xCEFAEDFE MH_MAGIC_64 = 0xFEEDFACF MH_CIGAM_64 = 0xCFFAEDFE FAT_MAGIC = 0xCAFEBABE BIG_ENDIAN = ">" LITTLE_ENDIAN = "<" LC_LOAD_DYLIB = 0xC maxint = majver == 3 and getattr(sys, "maxsize") or getattr(sys, "maxint") class fileview(object): """ A proxy for file-like objects that exposes a given view of a file. Modified from macholib. """ def __init__(self, fileobj, start=0, size=maxint): if isinstance(fileobj, fileview): self._fileobj = fileobj._fileobj else: self._fileobj = fileobj self._start = start self._end = start + size self._pos = 0 def __repr__(self): return "" % (self._start, self._end, self._fileobj) def tell(self): return self._pos def _checkwindow(self, seekto, op): if not (self._start <= seekto <= self._end): raise IOError("%s to offset %d is outside window [%d, %d]" % (op, seekto, self._start, self._end)) def seek(self, offset, whence=0): seekto = offset if whence == os.SEEK_SET: seekto += self._start elif whence == os.SEEK_CUR: seekto += self._start + self._pos elif whence == os.SEEK_END: seekto += self._end else: raise IOError("Invalid whence argument to seek: {!r}".format(whence)) self._checkwindow(seekto, "seek") self._fileobj.seek(seekto) self._pos = seekto - self._start def write(self, bytes): here = self._start + self._pos self._checkwindow(here, "write") self._checkwindow(here + len(bytes), "write") self._fileobj.seek(here, os.SEEK_SET) self._fileobj.write(bytes) self._pos += len(bytes) def read(self, size=maxint): assert size >= 0 here = self._start + self._pos self._checkwindow(here, "read") size = min(size, self._end - here) self._fileobj.seek(here, os.SEEK_SET) bytes = self._fileobj.read(size) self._pos += len(bytes) return bytes def read_data(file, endian, num=1): """ Read a given number of 32-bits unsigned integers from the given file with the given endianness. """ res = struct.unpack(endian + "L" * num, file.read(num * 4)) if len(res) == 1: return res[0] return res def mach_o_change(path, what, value): """ Replace a given name (what) in any LC_LOAD_DYLIB command found in the given binary with a new name (value), provided it's shorter. """ def do_macho(file, bits, endian): # Read Mach-O header (the magic number is assumed read by the caller) cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = read_data(file, endian, 6) # 64-bits header has one more field. if bits == 64: read_data(file, endian) # The header is followed by ncmds commands for _ in range(ncmds): where = file.tell() # Read command header cmd, cmdsize = read_data(file, endian, 2) if cmd == LC_LOAD_DYLIB: # The first data field in LC_LOAD_DYLIB commands is the # offset of the name, starting from the beginning of the # command. name_offset = read_data(file, endian) file.seek(where + name_offset, os.SEEK_SET) # Read the NUL terminated string load = file.read(cmdsize - name_offset).decode() load = load[: load.index("\0")] # If the string is what is being replaced, overwrite it. if load == what: file.seek(where + name_offset, os.SEEK_SET) file.write(value.encode() + "\0".encode()) # Seek to the next command file.seek(where + cmdsize, os.SEEK_SET) def do_file(file, offset=0, size=maxint): file = fileview(file, offset, size) # Read magic number magic = read_data(file, BIG_ENDIAN) if magic == FAT_MAGIC: # Fat binaries contain nfat_arch Mach-O binaries nfat_arch = read_data(file, BIG_ENDIAN) for _ in range(nfat_arch): # Read arch header cputype, cpusubtype, offset, size, align = read_data(file, BIG_ENDIAN, 5) do_file(file, offset, size) elif magic == MH_MAGIC: do_macho(file, 32, BIG_ENDIAN) elif magic == MH_CIGAM: do_macho(file, 32, LITTLE_ENDIAN) elif magic == MH_MAGIC_64: do_macho(file, 64, BIG_ENDIAN) elif magic == MH_CIGAM_64: do_macho(file, 64, LITTLE_ENDIAN) assert len(what) >= len(value) with open(path, "r+b") as f: do_file(f) if __name__ == "__main__": main() # TODO: # Copy python.exe.manifest # Monkeypatch distutils.sysconfig