michael@0: #!/usr/bin/env python michael@0: """Create a "virtual" Python installation michael@0: """ michael@0: michael@0: __version__ = "1.11.4" michael@0: virtualenv_version = __version__ # legacy michael@0: michael@0: import base64 michael@0: import sys michael@0: import os michael@0: import codecs michael@0: import optparse michael@0: import re michael@0: import shutil michael@0: import logging michael@0: import tempfile michael@0: import zlib michael@0: import errno michael@0: import glob michael@0: import distutils.sysconfig michael@0: from distutils.util import strtobool michael@0: import struct michael@0: import subprocess michael@0: import tarfile michael@0: michael@0: if sys.version_info < (2, 6): michael@0: print('ERROR: %s' % sys.exc_info()[1]) michael@0: print('ERROR: this script requires Python 2.6 or greater.') michael@0: sys.exit(101) michael@0: michael@0: try: michael@0: set michael@0: except NameError: michael@0: from sets import Set as set michael@0: try: michael@0: basestring michael@0: except NameError: michael@0: basestring = str michael@0: michael@0: try: michael@0: import ConfigParser michael@0: except ImportError: michael@0: import configparser as ConfigParser michael@0: michael@0: join = os.path.join michael@0: py_version = 'python%s.%s' % (sys.version_info[0], sys.version_info[1]) michael@0: michael@0: is_jython = sys.platform.startswith('java') michael@0: is_pypy = hasattr(sys, 'pypy_version_info') michael@0: is_win = (sys.platform == 'win32') michael@0: is_cygwin = (sys.platform == 'cygwin') michael@0: is_darwin = (sys.platform == 'darwin') michael@0: abiflags = getattr(sys, 'abiflags', '') michael@0: michael@0: user_dir = os.path.expanduser('~') michael@0: if is_win: michael@0: default_storage_dir = os.path.join(user_dir, 'virtualenv') michael@0: else: michael@0: default_storage_dir = os.path.join(user_dir, '.virtualenv') michael@0: default_config_file = os.path.join(default_storage_dir, 'virtualenv.ini') michael@0: michael@0: if is_pypy: michael@0: expected_exe = 'pypy' michael@0: elif is_jython: michael@0: expected_exe = 'jython' michael@0: else: michael@0: expected_exe = 'python' michael@0: michael@0: # Return a mapping of version -> Python executable michael@0: # Only provided for Windows, where the information in the registry is used michael@0: if not is_win: michael@0: def get_installed_pythons(): michael@0: return {} michael@0: else: michael@0: try: michael@0: import winreg michael@0: except ImportError: michael@0: import _winreg as winreg michael@0: michael@0: def get_installed_pythons(): michael@0: python_core = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, michael@0: "Software\\Python\\PythonCore") michael@0: i = 0 michael@0: versions = [] michael@0: while True: michael@0: try: michael@0: versions.append(winreg.EnumKey(python_core, i)) michael@0: i = i + 1 michael@0: except WindowsError: michael@0: break michael@0: exes = dict() michael@0: for ver in versions: michael@0: path = winreg.QueryValue(python_core, "%s\\InstallPath" % ver) michael@0: exes[ver] = join(path, "python.exe") michael@0: michael@0: winreg.CloseKey(python_core) michael@0: michael@0: # Add the major versions michael@0: # Sort the keys, then repeatedly update the major version entry michael@0: # Last executable (i.e., highest version) wins with this approach michael@0: for ver in sorted(exes): michael@0: exes[ver[0]] = exes[ver] michael@0: michael@0: return exes michael@0: michael@0: REQUIRED_MODULES = ['os', 'posix', 'posixpath', 'nt', 'ntpath', 'genericpath', michael@0: 'fnmatch', 'locale', 'encodings', 'codecs', michael@0: 'stat', 'UserDict', 'readline', 'copy_reg', 'types', michael@0: 're', 'sre', 'sre_parse', 'sre_constants', 'sre_compile', michael@0: 'zlib'] michael@0: michael@0: REQUIRED_FILES = ['lib-dynload', 'config'] michael@0: michael@0: majver, minver = sys.version_info[:2] michael@0: if majver == 2: michael@0: if minver >= 6: michael@0: REQUIRED_MODULES.extend(['warnings', 'linecache', '_abcoll', 'abc']) michael@0: if minver >= 7: michael@0: REQUIRED_MODULES.extend(['_weakrefset']) michael@0: if minver <= 3: michael@0: REQUIRED_MODULES.extend(['sets', '__future__']) michael@0: elif majver == 3: michael@0: # Some extra modules are needed for Python 3, but different ones michael@0: # for different versions. michael@0: REQUIRED_MODULES.extend(['_abcoll', 'warnings', 'linecache', 'abc', 'io', michael@0: '_weakrefset', 'copyreg', 'tempfile', 'random', michael@0: '__future__', 'collections', 'keyword', 'tarfile', michael@0: 'shutil', 'struct', 'copy', 'tokenize', 'token', michael@0: 'functools', 'heapq', 'bisect', 'weakref', michael@0: 'reprlib']) michael@0: if minver >= 2: michael@0: REQUIRED_FILES[-1] = 'config-%s' % majver michael@0: if minver >= 3: michael@0: import sysconfig michael@0: platdir = sysconfig.get_config_var('PLATDIR') michael@0: REQUIRED_FILES.append(platdir) michael@0: # The whole list of 3.3 modules is reproduced below - the current michael@0: # uncommented ones are required for 3.3 as of now, but more may be michael@0: # added as 3.3 development continues. michael@0: REQUIRED_MODULES.extend([ michael@0: #"aifc", michael@0: #"antigravity", michael@0: #"argparse", michael@0: #"ast", michael@0: #"asynchat", michael@0: #"asyncore", michael@0: "base64", michael@0: #"bdb", michael@0: #"binhex", michael@0: #"bisect", michael@0: #"calendar", michael@0: #"cgi", michael@0: #"cgitb", michael@0: #"chunk", michael@0: #"cmd", michael@0: #"codeop", michael@0: #"code", michael@0: #"colorsys", michael@0: #"_compat_pickle", michael@0: #"compileall", michael@0: #"concurrent", michael@0: #"configparser", michael@0: #"contextlib", michael@0: #"cProfile", michael@0: #"crypt", michael@0: #"csv", michael@0: #"ctypes", michael@0: #"curses", michael@0: #"datetime", michael@0: #"dbm", michael@0: #"decimal", michael@0: #"difflib", michael@0: #"dis", michael@0: #"doctest", michael@0: #"dummy_threading", michael@0: "_dummy_thread", michael@0: #"email", michael@0: #"filecmp", michael@0: #"fileinput", michael@0: #"formatter", michael@0: #"fractions", michael@0: #"ftplib", michael@0: #"functools", michael@0: #"getopt", michael@0: #"getpass", michael@0: #"gettext", michael@0: #"glob", michael@0: #"gzip", michael@0: "hashlib", michael@0: #"heapq", michael@0: "hmac", michael@0: #"html", michael@0: #"http", michael@0: #"idlelib", michael@0: #"imaplib", michael@0: #"imghdr", michael@0: "imp", michael@0: "importlib", michael@0: #"inspect", michael@0: #"json", michael@0: #"lib2to3", michael@0: #"logging", michael@0: #"macpath", michael@0: #"macurl2path", michael@0: #"mailbox", michael@0: #"mailcap", michael@0: #"_markupbase", michael@0: #"mimetypes", michael@0: #"modulefinder", michael@0: #"multiprocessing", michael@0: #"netrc", michael@0: #"nntplib", michael@0: #"nturl2path", michael@0: #"numbers", michael@0: #"opcode", michael@0: #"optparse", michael@0: #"os2emxpath", michael@0: #"pdb", michael@0: #"pickle", michael@0: #"pickletools", michael@0: #"pipes", michael@0: #"pkgutil", michael@0: #"platform", michael@0: #"plat-linux2", michael@0: #"plistlib", michael@0: #"poplib", michael@0: #"pprint", michael@0: #"profile", michael@0: #"pstats", michael@0: #"pty", michael@0: #"pyclbr", michael@0: #"py_compile", michael@0: #"pydoc_data", michael@0: #"pydoc", michael@0: #"_pyio", michael@0: #"queue", michael@0: #"quopri", michael@0: #"reprlib", michael@0: "rlcompleter", michael@0: #"runpy", michael@0: #"sched", michael@0: #"shelve", michael@0: #"shlex", michael@0: #"smtpd", michael@0: #"smtplib", michael@0: #"sndhdr", michael@0: #"socket", michael@0: #"socketserver", michael@0: #"sqlite3", michael@0: #"ssl", michael@0: #"stringprep", michael@0: #"string", michael@0: #"_strptime", michael@0: #"subprocess", michael@0: #"sunau", michael@0: #"symbol", michael@0: #"symtable", michael@0: #"sysconfig", michael@0: #"tabnanny", michael@0: #"telnetlib", michael@0: #"test", michael@0: #"textwrap", michael@0: #"this", michael@0: #"_threading_local", michael@0: #"threading", michael@0: #"timeit", michael@0: #"tkinter", michael@0: #"tokenize", michael@0: #"token", michael@0: #"traceback", michael@0: #"trace", michael@0: #"tty", michael@0: #"turtledemo", michael@0: #"turtle", michael@0: #"unittest", michael@0: #"urllib", michael@0: #"uuid", michael@0: #"uu", michael@0: #"wave", michael@0: #"weakref", michael@0: #"webbrowser", michael@0: #"wsgiref", michael@0: #"xdrlib", michael@0: #"xml", michael@0: #"xmlrpc", michael@0: #"zipfile", michael@0: ]) michael@0: if minver >= 4: michael@0: REQUIRED_MODULES.extend([ michael@0: 'operator', michael@0: '_collections_abc', michael@0: '_bootlocale', michael@0: ]) michael@0: michael@0: if is_pypy: michael@0: # these are needed to correctly display the exceptions that may happen michael@0: # during the bootstrap michael@0: REQUIRED_MODULES.extend(['traceback', 'linecache']) michael@0: michael@0: class Logger(object): michael@0: michael@0: """ michael@0: Logging object for use in command-line script. Allows ranges of michael@0: levels, to avoid some redundancy of displayed information. michael@0: """ michael@0: michael@0: DEBUG = logging.DEBUG michael@0: INFO = logging.INFO michael@0: NOTIFY = (logging.INFO+logging.WARN)/2 michael@0: WARN = WARNING = logging.WARN michael@0: ERROR = logging.ERROR michael@0: FATAL = logging.FATAL michael@0: michael@0: LEVELS = [DEBUG, INFO, NOTIFY, WARN, ERROR, FATAL] michael@0: michael@0: def __init__(self, consumers): michael@0: self.consumers = consumers michael@0: self.indent = 0 michael@0: self.in_progress = None michael@0: self.in_progress_hanging = False michael@0: michael@0: def debug(self, msg, *args, **kw): michael@0: self.log(self.DEBUG, msg, *args, **kw) michael@0: def info(self, msg, *args, **kw): michael@0: self.log(self.INFO, msg, *args, **kw) michael@0: def notify(self, msg, *args, **kw): michael@0: self.log(self.NOTIFY, msg, *args, **kw) michael@0: def warn(self, msg, *args, **kw): michael@0: self.log(self.WARN, msg, *args, **kw) michael@0: def error(self, msg, *args, **kw): michael@0: self.log(self.ERROR, msg, *args, **kw) michael@0: def fatal(self, msg, *args, **kw): michael@0: self.log(self.FATAL, msg, *args, **kw) michael@0: def log(self, level, msg, *args, **kw): michael@0: if args: michael@0: if kw: michael@0: raise TypeError( michael@0: "You may give positional or keyword arguments, not both") michael@0: args = args or kw michael@0: rendered = None michael@0: for consumer_level, consumer in self.consumers: michael@0: if self.level_matches(level, consumer_level): michael@0: if (self.in_progress_hanging michael@0: and consumer in (sys.stdout, sys.stderr)): michael@0: self.in_progress_hanging = False michael@0: sys.stdout.write('\n') michael@0: sys.stdout.flush() michael@0: if rendered is None: michael@0: if args: michael@0: rendered = msg % args michael@0: else: michael@0: rendered = msg michael@0: rendered = ' '*self.indent + rendered michael@0: if hasattr(consumer, 'write'): michael@0: consumer.write(rendered+'\n') michael@0: else: michael@0: consumer(rendered) michael@0: michael@0: def start_progress(self, msg): michael@0: assert not self.in_progress, ( michael@0: "Tried to start_progress(%r) while in_progress %r" michael@0: % (msg, self.in_progress)) michael@0: if self.level_matches(self.NOTIFY, self._stdout_level()): michael@0: sys.stdout.write(msg) michael@0: sys.stdout.flush() michael@0: self.in_progress_hanging = True michael@0: else: michael@0: self.in_progress_hanging = False michael@0: self.in_progress = msg michael@0: michael@0: def end_progress(self, msg='done.'): michael@0: assert self.in_progress, ( michael@0: "Tried to end_progress without start_progress") michael@0: if self.stdout_level_matches(self.NOTIFY): michael@0: if not self.in_progress_hanging: michael@0: # Some message has been printed out since start_progress michael@0: sys.stdout.write('...' + self.in_progress + msg + '\n') michael@0: sys.stdout.flush() michael@0: else: michael@0: sys.stdout.write(msg + '\n') michael@0: sys.stdout.flush() michael@0: self.in_progress = None michael@0: self.in_progress_hanging = False michael@0: michael@0: def show_progress(self): michael@0: """If we are in a progress scope, and no log messages have been michael@0: shown, write out another '.'""" michael@0: if self.in_progress_hanging: michael@0: sys.stdout.write('.') michael@0: sys.stdout.flush() michael@0: michael@0: def stdout_level_matches(self, level): michael@0: """Returns true if a message at this level will go to stdout""" michael@0: return self.level_matches(level, self._stdout_level()) michael@0: michael@0: def _stdout_level(self): michael@0: """Returns the level that stdout runs at""" michael@0: for level, consumer in self.consumers: michael@0: if consumer is sys.stdout: michael@0: return level michael@0: return self.FATAL michael@0: michael@0: def level_matches(self, level, consumer_level): michael@0: """ michael@0: >>> l = Logger([]) michael@0: >>> l.level_matches(3, 4) michael@0: False michael@0: >>> l.level_matches(3, 2) michael@0: True michael@0: >>> l.level_matches(slice(None, 3), 3) michael@0: False michael@0: >>> l.level_matches(slice(None, 3), 2) michael@0: True michael@0: >>> l.level_matches(slice(1, 3), 1) michael@0: True michael@0: >>> l.level_matches(slice(2, 3), 1) michael@0: False michael@0: """ michael@0: if isinstance(level, slice): michael@0: start, stop = level.start, level.stop michael@0: if start is not None and start > consumer_level: michael@0: return False michael@0: if stop is not None and stop <= consumer_level: michael@0: return False michael@0: return True michael@0: else: michael@0: return level >= consumer_level michael@0: michael@0: #@classmethod michael@0: def level_for_integer(cls, level): michael@0: levels = cls.LEVELS michael@0: if level < 0: michael@0: return levels[0] michael@0: if level >= len(levels): michael@0: return levels[-1] michael@0: return levels[level] michael@0: michael@0: level_for_integer = classmethod(level_for_integer) michael@0: michael@0: # create a silent logger just to prevent this from being undefined michael@0: # will be overridden with requested verbosity main() is called. michael@0: logger = Logger([(Logger.LEVELS[-1], sys.stdout)]) michael@0: michael@0: def mkdir(path): michael@0: if not os.path.exists(path): michael@0: logger.info('Creating %s', path) michael@0: os.makedirs(path) michael@0: else: michael@0: logger.info('Directory %s already exists', path) michael@0: michael@0: def copyfileordir(src, dest, symlink=True): michael@0: if os.path.isdir(src): michael@0: shutil.copytree(src, dest, symlink) michael@0: else: michael@0: shutil.copy2(src, dest) michael@0: michael@0: def copyfile(src, dest, symlink=True): michael@0: if not os.path.exists(src): michael@0: # Some bad symlink in the src michael@0: logger.warn('Cannot find file %s (bad symlink)', src) michael@0: return michael@0: if os.path.exists(dest): michael@0: logger.debug('File %s already exists', dest) michael@0: return michael@0: if not os.path.exists(os.path.dirname(dest)): michael@0: logger.info('Creating parent directories for %s', os.path.dirname(dest)) michael@0: os.makedirs(os.path.dirname(dest)) michael@0: if not os.path.islink(src): michael@0: srcpath = os.path.abspath(src) michael@0: else: michael@0: srcpath = os.readlink(src) michael@0: if symlink and hasattr(os, 'symlink') and not is_win: michael@0: logger.info('Symlinking %s', dest) michael@0: try: michael@0: os.symlink(srcpath, dest) michael@0: except (OSError, NotImplementedError): michael@0: logger.info('Symlinking failed, copying to %s', dest) michael@0: copyfileordir(src, dest, symlink) michael@0: else: michael@0: logger.info('Copying to %s', dest) michael@0: copyfileordir(src, dest, symlink) michael@0: michael@0: def writefile(dest, content, overwrite=True): michael@0: if not os.path.exists(dest): michael@0: logger.info('Writing %s', dest) michael@0: f = open(dest, 'wb') michael@0: f.write(content.encode('utf-8')) michael@0: f.close() michael@0: return michael@0: else: michael@0: f = open(dest, 'rb') michael@0: c = f.read() michael@0: f.close() michael@0: if c != content.encode("utf-8"): michael@0: if not overwrite: michael@0: logger.notify('File %s exists with different content; not overwriting', dest) michael@0: return michael@0: logger.notify('Overwriting %s with new content', dest) michael@0: f = open(dest, 'wb') michael@0: f.write(content.encode('utf-8')) michael@0: f.close() michael@0: else: michael@0: logger.info('Content %s already in place', dest) michael@0: michael@0: def rmtree(dir): michael@0: if os.path.exists(dir): michael@0: logger.notify('Deleting tree %s', dir) michael@0: shutil.rmtree(dir) michael@0: else: michael@0: logger.info('Do not need to delete %s; already gone', dir) michael@0: michael@0: def make_exe(fn): michael@0: if hasattr(os, 'chmod'): michael@0: oldmode = os.stat(fn).st_mode & 0xFFF # 0o7777 michael@0: newmode = (oldmode | 0x16D) & 0xFFF # 0o555, 0o7777 michael@0: os.chmod(fn, newmode) michael@0: logger.info('Changed mode of %s to %s', fn, oct(newmode)) michael@0: michael@0: def _find_file(filename, dirs): michael@0: for dir in reversed(dirs): michael@0: files = glob.glob(os.path.join(dir, filename)) michael@0: if files and os.path.isfile(files[0]): michael@0: return True, files[0] michael@0: return False, filename michael@0: michael@0: def file_search_dirs(): michael@0: here = os.path.dirname(os.path.abspath(__file__)) michael@0: dirs = ['.', here, michael@0: join(here, 'virtualenv_support')] michael@0: if os.path.splitext(os.path.dirname(__file__))[0] != 'virtualenv': michael@0: # Probably some boot script; just in case virtualenv is installed... michael@0: try: michael@0: import virtualenv michael@0: except ImportError: michael@0: pass michael@0: else: michael@0: dirs.append(os.path.join(os.path.dirname(virtualenv.__file__), 'virtualenv_support')) michael@0: return [d for d in dirs if os.path.isdir(d)] michael@0: michael@0: michael@0: class UpdatingDefaultsHelpFormatter(optparse.IndentedHelpFormatter): michael@0: """ michael@0: Custom help formatter for use in ConfigOptionParser that updates michael@0: the defaults before expanding them, allowing them to show up correctly michael@0: in the help listing michael@0: """ michael@0: def expand_default(self, option): michael@0: if self.parser is not None: michael@0: self.parser.update_defaults(self.parser.defaults) michael@0: return optparse.IndentedHelpFormatter.expand_default(self, option) michael@0: michael@0: michael@0: class ConfigOptionParser(optparse.OptionParser): michael@0: """ michael@0: Custom option parser which updates its defaults by checking the michael@0: configuration files and environmental variables michael@0: """ michael@0: def __init__(self, *args, **kwargs): michael@0: self.config = ConfigParser.RawConfigParser() michael@0: self.files = self.get_config_files() michael@0: self.config.read(self.files) michael@0: optparse.OptionParser.__init__(self, *args, **kwargs) michael@0: michael@0: def get_config_files(self): michael@0: config_file = os.environ.get('VIRTUALENV_CONFIG_FILE', False) michael@0: if config_file and os.path.exists(config_file): michael@0: return [config_file] michael@0: return [default_config_file] michael@0: michael@0: def update_defaults(self, defaults): michael@0: """ michael@0: Updates the given defaults with values from the config files and michael@0: the environ. Does a little special handling for certain types of michael@0: options (lists). michael@0: """ michael@0: # Then go and look for the other sources of configuration: michael@0: config = {} michael@0: # 1. config files michael@0: config.update(dict(self.get_config_section('virtualenv'))) michael@0: # 2. environmental variables michael@0: config.update(dict(self.get_environ_vars())) michael@0: # Then set the options with those values michael@0: for key, val in config.items(): michael@0: key = key.replace('_', '-') michael@0: if not key.startswith('--'): michael@0: key = '--%s' % key # only prefer long opts michael@0: option = self.get_option(key) michael@0: if option is not None: michael@0: # ignore empty values michael@0: if not val: michael@0: continue michael@0: # handle multiline configs michael@0: if option.action == 'append': michael@0: val = val.split() michael@0: else: michael@0: option.nargs = 1 michael@0: if option.action == 'store_false': michael@0: val = not strtobool(val) michael@0: elif option.action in ('store_true', 'count'): michael@0: val = strtobool(val) michael@0: try: michael@0: val = option.convert_value(key, val) michael@0: except optparse.OptionValueError: michael@0: e = sys.exc_info()[1] michael@0: print("An error occured during configuration: %s" % e) michael@0: sys.exit(3) michael@0: defaults[option.dest] = val michael@0: return defaults michael@0: michael@0: def get_config_section(self, name): michael@0: """ michael@0: Get a section of a configuration michael@0: """ michael@0: if self.config.has_section(name): michael@0: return self.config.items(name) michael@0: return [] michael@0: michael@0: def get_environ_vars(self, prefix='VIRTUALENV_'): michael@0: """ michael@0: Returns a generator with all environmental vars with prefix VIRTUALENV michael@0: """ michael@0: for key, val in os.environ.items(): michael@0: if key.startswith(prefix): michael@0: yield (key.replace(prefix, '').lower(), val) michael@0: michael@0: def get_default_values(self): michael@0: """ michael@0: Overridding to make updating the defaults after instantiation of michael@0: the option parser possible, update_defaults() does the dirty work. michael@0: """ michael@0: if not self.process_default_values: michael@0: # Old, pre-Optik 1.5 behaviour. michael@0: return optparse.Values(self.defaults) michael@0: michael@0: defaults = self.update_defaults(self.defaults.copy()) # ours michael@0: for option in self._get_all_options(): michael@0: default = defaults.get(option.dest) michael@0: if isinstance(default, basestring): michael@0: opt_str = option.get_opt_string() michael@0: defaults[option.dest] = option.check_value(opt_str, default) michael@0: return optparse.Values(defaults) michael@0: michael@0: michael@0: def main(): michael@0: parser = ConfigOptionParser( michael@0: version=virtualenv_version, michael@0: usage="%prog [OPTIONS] DEST_DIR", michael@0: formatter=UpdatingDefaultsHelpFormatter()) michael@0: michael@0: parser.add_option( michael@0: '-v', '--verbose', michael@0: action='count', michael@0: dest='verbose', michael@0: default=0, michael@0: help="Increase verbosity.") michael@0: michael@0: parser.add_option( michael@0: '-q', '--quiet', michael@0: action='count', michael@0: dest='quiet', michael@0: default=0, michael@0: help='Decrease verbosity.') michael@0: michael@0: parser.add_option( michael@0: '-p', '--python', michael@0: dest='python', michael@0: metavar='PYTHON_EXE', michael@0: help='The Python interpreter to use, e.g., --python=python2.5 will use the python2.5 ' michael@0: 'interpreter to create the new environment. The default is the interpreter that ' michael@0: 'virtualenv was installed with (%s)' % sys.executable) michael@0: michael@0: parser.add_option( michael@0: '--clear', michael@0: dest='clear', michael@0: action='store_true', michael@0: help="Clear out the non-root install and start from scratch.") michael@0: michael@0: parser.set_defaults(system_site_packages=False) michael@0: parser.add_option( michael@0: '--no-site-packages', michael@0: dest='system_site_packages', michael@0: action='store_false', michael@0: help="DEPRECATED. Retained only for backward compatibility. " michael@0: "Not having access to global site-packages is now the default behavior.") michael@0: michael@0: parser.add_option( michael@0: '--system-site-packages', michael@0: dest='system_site_packages', michael@0: action='store_true', michael@0: help="Give the virtual environment access to the global site-packages.") michael@0: michael@0: parser.add_option( michael@0: '--always-copy', michael@0: dest='symlink', michael@0: action='store_false', michael@0: default=True, michael@0: help="Always copy files rather than symlinking.") michael@0: michael@0: parser.add_option( michael@0: '--unzip-setuptools', michael@0: dest='unzip_setuptools', michael@0: action='store_true', michael@0: help="Unzip Setuptools when installing it.") michael@0: michael@0: parser.add_option( michael@0: '--relocatable', michael@0: dest='relocatable', michael@0: action='store_true', michael@0: help='Make an EXISTING virtualenv environment relocatable. ' michael@0: 'This fixes up scripts and makes all .pth files relative.') michael@0: michael@0: parser.add_option( michael@0: '--no-setuptools', michael@0: dest='no_setuptools', michael@0: action='store_true', michael@0: help='Do not install setuptools (or pip) in the new virtualenv.') michael@0: michael@0: parser.add_option( michael@0: '--no-pip', michael@0: dest='no_pip', michael@0: action='store_true', michael@0: help='Do not install pip in the new virtualenv.') michael@0: michael@0: default_search_dirs = file_search_dirs() michael@0: parser.add_option( michael@0: '--extra-search-dir', michael@0: dest="search_dirs", michael@0: action="append", michael@0: metavar='DIR', michael@0: default=default_search_dirs, michael@0: help="Directory to look for setuptools/pip distributions in. " michael@0: "This option can be used multiple times.") michael@0: michael@0: parser.add_option( michael@0: '--never-download', michael@0: dest="never_download", michael@0: action="store_true", michael@0: default=True, michael@0: help="DEPRECATED. Retained only for backward compatibility. This option has no effect. " michael@0: "Virtualenv never downloads pip or setuptools.") michael@0: michael@0: parser.add_option( michael@0: '--prompt', michael@0: dest='prompt', michael@0: help='Provides an alternative prompt prefix for this environment.') michael@0: michael@0: parser.add_option( michael@0: '--setuptools', michael@0: dest='setuptools', michael@0: action='store_true', michael@0: help="DEPRECATED. Retained only for backward compatibility. This option has no effect.") michael@0: michael@0: parser.add_option( michael@0: '--distribute', michael@0: dest='distribute', michael@0: action='store_true', michael@0: help="DEPRECATED. Retained only for backward compatibility. This option has no effect.") michael@0: michael@0: if 'extend_parser' in globals(): michael@0: extend_parser(parser) michael@0: michael@0: options, args = parser.parse_args() michael@0: michael@0: global logger michael@0: michael@0: if 'adjust_options' in globals(): michael@0: adjust_options(options, args) michael@0: michael@0: verbosity = options.verbose - options.quiet michael@0: logger = Logger([(Logger.level_for_integer(2 - verbosity), sys.stdout)]) michael@0: michael@0: if options.python and not os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'): michael@0: env = os.environ.copy() michael@0: interpreter = resolve_interpreter(options.python) michael@0: if interpreter == sys.executable: michael@0: logger.warn('Already using interpreter %s' % interpreter) michael@0: else: michael@0: logger.notify('Running virtualenv with interpreter %s' % interpreter) michael@0: env['VIRTUALENV_INTERPRETER_RUNNING'] = 'true' michael@0: file = __file__ michael@0: if file.endswith('.pyc'): michael@0: file = file[:-1] michael@0: popen = subprocess.Popen([interpreter, file] + sys.argv[1:], env=env) michael@0: raise SystemExit(popen.wait()) michael@0: michael@0: if not args: michael@0: print('You must provide a DEST_DIR') michael@0: parser.print_help() michael@0: sys.exit(2) michael@0: if len(args) > 1: michael@0: print('There must be only one argument: DEST_DIR (you gave %s)' % ( michael@0: ' '.join(args))) michael@0: parser.print_help() michael@0: sys.exit(2) michael@0: michael@0: home_dir = args[0] michael@0: michael@0: if os.environ.get('WORKING_ENV'): michael@0: logger.fatal('ERROR: you cannot run virtualenv while in a workingenv') michael@0: logger.fatal('Please deactivate your workingenv, then re-run this script') michael@0: sys.exit(3) michael@0: michael@0: if 'PYTHONHOME' in os.environ: michael@0: logger.warn('PYTHONHOME is set. You *must* activate the virtualenv before using it') michael@0: del os.environ['PYTHONHOME'] michael@0: michael@0: if options.relocatable: michael@0: make_environment_relocatable(home_dir) michael@0: return michael@0: michael@0: if not options.never_download: michael@0: logger.warn('The --never-download option is for backward compatibility only.') michael@0: logger.warn('Setting it to false is no longer supported, and will be ignored.') michael@0: michael@0: create_environment(home_dir, michael@0: site_packages=options.system_site_packages, michael@0: clear=options.clear, michael@0: unzip_setuptools=options.unzip_setuptools, michael@0: prompt=options.prompt, michael@0: search_dirs=options.search_dirs, michael@0: never_download=True, michael@0: no_setuptools=options.no_setuptools, michael@0: no_pip=options.no_pip, michael@0: symlink=options.symlink) michael@0: if 'after_install' in globals(): michael@0: after_install(options, home_dir) michael@0: michael@0: def call_subprocess(cmd, show_stdout=True, michael@0: filter_stdout=None, cwd=None, michael@0: raise_on_returncode=True, extra_env=None, michael@0: remove_from_env=None): michael@0: cmd_parts = [] michael@0: for part in cmd: michael@0: if len(part) > 45: michael@0: part = part[:20]+"..."+part[-20:] michael@0: if ' ' in part or '\n' in part or '"' in part or "'" in part: michael@0: part = '"%s"' % part.replace('"', '\\"') michael@0: if hasattr(part, 'decode'): michael@0: try: michael@0: part = part.decode(sys.getdefaultencoding()) michael@0: except UnicodeDecodeError: michael@0: part = part.decode(sys.getfilesystemencoding()) michael@0: cmd_parts.append(part) michael@0: cmd_desc = ' '.join(cmd_parts) michael@0: if show_stdout: michael@0: stdout = None michael@0: else: michael@0: stdout = subprocess.PIPE michael@0: logger.debug("Running command %s" % cmd_desc) michael@0: if extra_env or remove_from_env: michael@0: env = os.environ.copy() michael@0: if extra_env: michael@0: env.update(extra_env) michael@0: if remove_from_env: michael@0: for varname in remove_from_env: michael@0: env.pop(varname, None) michael@0: else: michael@0: env = None michael@0: try: michael@0: proc = subprocess.Popen( michael@0: cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout, michael@0: cwd=cwd, env=env) michael@0: except Exception: michael@0: e = sys.exc_info()[1] michael@0: logger.fatal( michael@0: "Error %s while executing command %s" % (e, cmd_desc)) michael@0: raise michael@0: all_output = [] michael@0: if stdout is not None: michael@0: stdout = proc.stdout michael@0: encoding = sys.getdefaultencoding() michael@0: fs_encoding = sys.getfilesystemencoding() michael@0: while 1: michael@0: line = stdout.readline() michael@0: try: michael@0: line = line.decode(encoding) michael@0: except UnicodeDecodeError: michael@0: line = line.decode(fs_encoding) michael@0: if not line: michael@0: break michael@0: line = line.rstrip() michael@0: all_output.append(line) michael@0: if filter_stdout: michael@0: level = filter_stdout(line) michael@0: if isinstance(level, tuple): michael@0: level, line = level michael@0: logger.log(level, line) michael@0: if not logger.stdout_level_matches(level): michael@0: logger.show_progress() michael@0: else: michael@0: logger.info(line) michael@0: else: michael@0: proc.communicate() michael@0: proc.wait() michael@0: if proc.returncode: michael@0: if raise_on_returncode: michael@0: if all_output: michael@0: logger.notify('Complete output from command %s:' % cmd_desc) michael@0: logger.notify('\n'.join(all_output) + '\n----------------------------------------') michael@0: raise OSError( michael@0: "Command %s failed with error code %s" michael@0: % (cmd_desc, proc.returncode)) michael@0: else: michael@0: logger.warn( michael@0: "Command %s had error code %s" michael@0: % (cmd_desc, proc.returncode)) michael@0: michael@0: def filter_install_output(line): michael@0: if line.strip().startswith('running'): michael@0: return Logger.INFO michael@0: return Logger.DEBUG michael@0: michael@0: def find_wheels(projects, search_dirs): michael@0: """Find wheels from which we can import PROJECTS. michael@0: michael@0: Scan through SEARCH_DIRS for a wheel for each PROJECT in turn. Return michael@0: a list of the first wheel found for each PROJECT michael@0: """ michael@0: michael@0: wheels = [] michael@0: michael@0: # Look through SEARCH_DIRS for the first suitable wheel. Don't bother michael@0: # about version checking here, as this is simply to get something we can michael@0: # then use to install the correct version. michael@0: for project in projects: michael@0: for dirname in search_dirs: michael@0: # This relies on only having "universal" wheels available. michael@0: # The pattern could be tightened to require -py2.py3-none-any.whl. michael@0: files = glob.glob(os.path.join(dirname, project + '-*.whl')) michael@0: if files: michael@0: wheels.append(os.path.abspath(files[0])) michael@0: break michael@0: else: michael@0: # We're out of luck, so quit with a suitable error michael@0: logger.fatal('Cannot find a wheel for %s' % (project,)) michael@0: michael@0: return wheels michael@0: michael@0: def install_wheel(project_names, py_executable, search_dirs=None): michael@0: if search_dirs is None: michael@0: search_dirs = file_search_dirs() michael@0: michael@0: wheels = find_wheels(['setuptools', 'pip'], search_dirs) michael@0: pythonpath = os.pathsep.join(wheels) michael@0: findlinks = ' '.join(search_dirs) michael@0: michael@0: cmd = [ michael@0: py_executable, '-c', michael@0: 'import sys, pip; sys.exit(pip.main(["install", "--ignore-installed"] + sys.argv[1:]))', michael@0: ] + project_names michael@0: logger.start_progress('Installing %s...' % (', '.join(project_names))) michael@0: logger.indent += 2 michael@0: try: michael@0: call_subprocess(cmd, show_stdout=False, michael@0: extra_env = { michael@0: 'PYTHONPATH': pythonpath, michael@0: 'PIP_FIND_LINKS': findlinks, michael@0: 'PIP_USE_WHEEL': '1', michael@0: 'PIP_PRE': '1', michael@0: 'PIP_NO_INDEX': '1' michael@0: } michael@0: ) michael@0: finally: michael@0: logger.indent -= 2 michael@0: logger.end_progress() michael@0: michael@0: def create_environment(home_dir, site_packages=False, clear=False, michael@0: unzip_setuptools=False, michael@0: prompt=None, search_dirs=None, never_download=False, michael@0: no_setuptools=False, no_pip=False, symlink=True): michael@0: """ michael@0: Creates a new environment in ``home_dir``. michael@0: michael@0: If ``site_packages`` is true, then the global ``site-packages/`` michael@0: directory will be on the path. michael@0: michael@0: If ``clear`` is true (default False) then the environment will michael@0: first be cleared. michael@0: """ michael@0: home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) michael@0: michael@0: py_executable = os.path.abspath(install_python( michael@0: home_dir, lib_dir, inc_dir, bin_dir, michael@0: site_packages=site_packages, clear=clear, symlink=symlink)) michael@0: michael@0: install_distutils(home_dir) michael@0: michael@0: if not no_setuptools: michael@0: to_install = ['setuptools'] michael@0: if not no_pip: michael@0: to_install.append('pip') michael@0: install_wheel(to_install, py_executable, search_dirs) michael@0: michael@0: install_activate(home_dir, bin_dir, prompt) michael@0: michael@0: def is_executable_file(fpath): michael@0: return os.path.isfile(fpath) and os.access(fpath, os.X_OK) michael@0: michael@0: def path_locations(home_dir): michael@0: """Return the path locations for the environment (where libraries are, michael@0: where scripts go, etc)""" michael@0: # XXX: We'd use distutils.sysconfig.get_python_inc/lib but its michael@0: # prefix arg is broken: http://bugs.python.org/issue3386 michael@0: if is_win: michael@0: # Windows has lots of problems with executables with spaces in michael@0: # the name; this function will remove them (using the ~1 michael@0: # format): michael@0: mkdir(home_dir) michael@0: if ' ' in home_dir: michael@0: import ctypes michael@0: GetShortPathName = ctypes.windll.kernel32.GetShortPathNameW michael@0: size = max(len(home_dir)+1, 256) michael@0: buf = ctypes.create_unicode_buffer(size) michael@0: try: michael@0: u = unicode michael@0: except NameError: michael@0: u = str michael@0: ret = GetShortPathName(u(home_dir), buf, size) michael@0: if not ret: michael@0: print('Error: the path "%s" has a space in it' % home_dir) michael@0: print('We could not determine the short pathname for it.') michael@0: print('Exiting.') michael@0: sys.exit(3) michael@0: home_dir = str(buf.value) michael@0: lib_dir = join(home_dir, 'Lib') michael@0: inc_dir = join(home_dir, 'Include') michael@0: bin_dir = join(home_dir, 'Scripts') michael@0: if is_jython: michael@0: lib_dir = join(home_dir, 'Lib') michael@0: inc_dir = join(home_dir, 'Include') michael@0: bin_dir = join(home_dir, 'bin') michael@0: elif is_pypy: michael@0: lib_dir = home_dir michael@0: inc_dir = join(home_dir, 'include') michael@0: bin_dir = join(home_dir, 'bin') michael@0: elif not is_win: michael@0: lib_dir = join(home_dir, 'lib', py_version) michael@0: multiarch_exec = '/usr/bin/multiarch-platform' michael@0: if is_executable_file(multiarch_exec): michael@0: # In Mageia (2) and Mandriva distros the include dir must be like: michael@0: # virtualenv/include/multiarch-x86_64-linux/python2.7 michael@0: # instead of being virtualenv/include/python2.7 michael@0: p = subprocess.Popen(multiarch_exec, stdout=subprocess.PIPE, stderr=subprocess.PIPE) michael@0: stdout, stderr = p.communicate() michael@0: # stdout.strip is needed to remove newline character michael@0: inc_dir = join(home_dir, 'include', stdout.strip(), py_version + abiflags) michael@0: else: michael@0: inc_dir = join(home_dir, 'include', py_version + abiflags) michael@0: bin_dir = join(home_dir, 'bin') michael@0: return home_dir, lib_dir, inc_dir, bin_dir michael@0: michael@0: michael@0: def change_prefix(filename, dst_prefix): michael@0: prefixes = [sys.prefix] michael@0: michael@0: if is_darwin: michael@0: prefixes.extend(( michael@0: os.path.join("/Library/Python", sys.version[:3], "site-packages"), michael@0: os.path.join(sys.prefix, "Extras", "lib", "python"), michael@0: os.path.join("~", "Library", "Python", sys.version[:3], "site-packages"), michael@0: # Python 2.6 no-frameworks michael@0: os.path.join("~", ".local", "lib","python", sys.version[:3], "site-packages"), michael@0: # System Python 2.7 on OSX Mountain Lion michael@0: os.path.join("~", "Library", "Python", sys.version[:3], "lib", "python", "site-packages"))) michael@0: michael@0: if hasattr(sys, 'real_prefix'): michael@0: prefixes.append(sys.real_prefix) michael@0: if hasattr(sys, 'base_prefix'): michael@0: prefixes.append(sys.base_prefix) michael@0: prefixes = list(map(os.path.expanduser, prefixes)) michael@0: prefixes = list(map(os.path.abspath, prefixes)) michael@0: # Check longer prefixes first so we don't split in the middle of a filename michael@0: prefixes = sorted(prefixes, key=len, reverse=True) michael@0: filename = os.path.abspath(filename) michael@0: for src_prefix in prefixes: michael@0: if filename.startswith(src_prefix): michael@0: _, relpath = filename.split(src_prefix, 1) michael@0: if src_prefix != os.sep: # sys.prefix == "/" michael@0: assert relpath[0] == os.sep michael@0: relpath = relpath[1:] michael@0: return join(dst_prefix, relpath) michael@0: assert False, "Filename %s does not start with any of these prefixes: %s" % \ michael@0: (filename, prefixes) michael@0: michael@0: def copy_required_modules(dst_prefix, symlink): michael@0: import imp michael@0: # If we are running under -p, we need to remove the current michael@0: # directory from sys.path temporarily here, so that we michael@0: # definitely get the modules from the site directory of michael@0: # the interpreter we are running under, not the one michael@0: # virtualenv.py is installed under (which might lead to py2/py3 michael@0: # incompatibility issues) michael@0: _prev_sys_path = sys.path michael@0: if os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'): michael@0: sys.path = sys.path[1:] michael@0: try: michael@0: for modname in REQUIRED_MODULES: michael@0: if modname in sys.builtin_module_names: michael@0: logger.info("Ignoring built-in bootstrap module: %s" % modname) michael@0: continue michael@0: try: michael@0: f, filename, _ = imp.find_module(modname) michael@0: except ImportError: michael@0: logger.info("Cannot import bootstrap module: %s" % modname) michael@0: else: michael@0: if f is not None: michael@0: f.close() michael@0: # special-case custom readline.so on OS X, but not for pypy: michael@0: if modname == 'readline' and sys.platform == 'darwin' and not ( michael@0: is_pypy or filename.endswith(join('lib-dynload', 'readline.so'))): michael@0: dst_filename = join(dst_prefix, 'lib', 'python%s' % sys.version[:3], 'readline.so') michael@0: elif modname == 'readline' and sys.platform == 'win32': michael@0: # special-case for Windows, where readline is not a michael@0: # standard module, though it may have been installed in michael@0: # site-packages by a third-party package michael@0: pass michael@0: else: michael@0: dst_filename = change_prefix(filename, dst_prefix) michael@0: copyfile(filename, dst_filename, symlink) michael@0: if filename.endswith('.pyc'): michael@0: pyfile = filename[:-1] michael@0: if os.path.exists(pyfile): michael@0: copyfile(pyfile, dst_filename[:-1], symlink) michael@0: finally: michael@0: sys.path = _prev_sys_path michael@0: michael@0: michael@0: def subst_path(prefix_path, prefix, home_dir): michael@0: prefix_path = os.path.normpath(prefix_path) michael@0: prefix = os.path.normpath(prefix) michael@0: home_dir = os.path.normpath(home_dir) michael@0: if not prefix_path.startswith(prefix): michael@0: logger.warn('Path not in prefix %r %r', prefix_path, prefix) michael@0: return michael@0: return prefix_path.replace(prefix, home_dir, 1) michael@0: michael@0: michael@0: def install_python(home_dir, lib_dir, inc_dir, bin_dir, site_packages, clear, symlink=True): michael@0: """Install just the base environment, no distutils patches etc""" michael@0: if sys.executable.startswith(bin_dir): michael@0: print('Please use the *system* python to run this script') michael@0: return michael@0: michael@0: if clear: michael@0: rmtree(lib_dir) michael@0: ## FIXME: why not delete it? michael@0: ## Maybe it should delete everything with #!/path/to/venv/python in it michael@0: logger.notify('Not deleting %s', bin_dir) michael@0: michael@0: if hasattr(sys, 'real_prefix'): michael@0: logger.notify('Using real prefix %r' % sys.real_prefix) michael@0: prefix = sys.real_prefix michael@0: elif hasattr(sys, 'base_prefix'): michael@0: logger.notify('Using base prefix %r' % sys.base_prefix) michael@0: prefix = sys.base_prefix michael@0: else: michael@0: prefix = sys.prefix michael@0: mkdir(lib_dir) michael@0: fix_lib64(lib_dir, symlink) michael@0: stdlib_dirs = [os.path.dirname(os.__file__)] michael@0: if is_win: michael@0: stdlib_dirs.append(join(os.path.dirname(stdlib_dirs[0]), 'DLLs')) michael@0: elif is_darwin: michael@0: stdlib_dirs.append(join(stdlib_dirs[0], 'site-packages')) michael@0: if hasattr(os, 'symlink'): michael@0: logger.info('Symlinking Python bootstrap modules') michael@0: else: michael@0: logger.info('Copying Python bootstrap modules') michael@0: logger.indent += 2 michael@0: try: michael@0: # copy required files... michael@0: for stdlib_dir in stdlib_dirs: michael@0: if not os.path.isdir(stdlib_dir): michael@0: continue michael@0: for fn in os.listdir(stdlib_dir): michael@0: bn = os.path.splitext(fn)[0] michael@0: if fn != 'site-packages' and bn in REQUIRED_FILES: michael@0: copyfile(join(stdlib_dir, fn), join(lib_dir, fn), symlink) michael@0: # ...and modules michael@0: copy_required_modules(home_dir, symlink) michael@0: finally: michael@0: logger.indent -= 2 michael@0: mkdir(join(lib_dir, 'site-packages')) michael@0: import site michael@0: site_filename = site.__file__ michael@0: if site_filename.endswith('.pyc'): michael@0: site_filename = site_filename[:-1] michael@0: elif site_filename.endswith('$py.class'): michael@0: site_filename = site_filename.replace('$py.class', '.py') michael@0: site_filename_dst = change_prefix(site_filename, home_dir) michael@0: site_dir = os.path.dirname(site_filename_dst) michael@0: writefile(site_filename_dst, SITE_PY) michael@0: writefile(join(site_dir, 'orig-prefix.txt'), prefix) michael@0: site_packages_filename = join(site_dir, 'no-global-site-packages.txt') michael@0: if not site_packages: michael@0: writefile(site_packages_filename, '') michael@0: michael@0: if is_pypy or is_win: michael@0: stdinc_dir = join(prefix, 'include') michael@0: else: michael@0: stdinc_dir = join(prefix, 'include', py_version + abiflags) michael@0: if os.path.exists(stdinc_dir): michael@0: copyfile(stdinc_dir, inc_dir, symlink) michael@0: else: michael@0: logger.debug('No include dir %s' % stdinc_dir) michael@0: michael@0: platinc_dir = distutils.sysconfig.get_python_inc(plat_specific=1) michael@0: if platinc_dir != stdinc_dir: michael@0: platinc_dest = distutils.sysconfig.get_python_inc( michael@0: plat_specific=1, prefix=home_dir) michael@0: if platinc_dir == platinc_dest: michael@0: # Do platinc_dest manually due to a CPython bug; michael@0: # not http://bugs.python.org/issue3386 but a close cousin michael@0: platinc_dest = subst_path(platinc_dir, prefix, home_dir) michael@0: if platinc_dest: michael@0: # PyPy's stdinc_dir and prefix are relative to the original binary michael@0: # (traversing virtualenvs), whereas the platinc_dir is relative to michael@0: # the inner virtualenv and ignores the prefix argument. michael@0: # This seems more evolved than designed. michael@0: copyfile(platinc_dir, platinc_dest, symlink) michael@0: michael@0: # pypy never uses exec_prefix, just ignore it michael@0: if sys.exec_prefix != prefix and not is_pypy: michael@0: if is_win: michael@0: exec_dir = join(sys.exec_prefix, 'lib') michael@0: elif is_jython: michael@0: exec_dir = join(sys.exec_prefix, 'Lib') michael@0: else: michael@0: exec_dir = join(sys.exec_prefix, 'lib', py_version) michael@0: for fn in os.listdir(exec_dir): michael@0: copyfile(join(exec_dir, fn), join(lib_dir, fn), symlink) michael@0: michael@0: if is_jython: michael@0: # Jython has either jython-dev.jar and javalib/ dir, or just michael@0: # jython.jar michael@0: for name in 'jython-dev.jar', 'javalib', 'jython.jar': michael@0: src = join(prefix, name) michael@0: if os.path.exists(src): michael@0: copyfile(src, join(home_dir, name), symlink) michael@0: # XXX: registry should always exist after Jython 2.5rc1 michael@0: src = join(prefix, 'registry') michael@0: if os.path.exists(src): michael@0: copyfile(src, join(home_dir, 'registry'), symlink=False) michael@0: copyfile(join(prefix, 'cachedir'), join(home_dir, 'cachedir'), michael@0: symlink=False) michael@0: michael@0: mkdir(bin_dir) michael@0: py_executable = join(bin_dir, os.path.basename(sys.executable)) michael@0: if 'Python.framework' in prefix: michael@0: # OS X framework builds cause validation to break michael@0: # https://github.com/pypa/virtualenv/issues/322 michael@0: if os.environ.get('__PYVENV_LAUNCHER__'): michael@0: del os.environ["__PYVENV_LAUNCHER__"] michael@0: if re.search(r'/Python(?:-32|-64)*$', py_executable): michael@0: # The name of the python executable is not quite what michael@0: # we want, rename it. michael@0: py_executable = os.path.join( michael@0: os.path.dirname(py_executable), 'python') michael@0: michael@0: logger.notify('New %s executable in %s', expected_exe, py_executable) michael@0: pcbuild_dir = os.path.dirname(sys.executable) michael@0: pyd_pth = os.path.join(lib_dir, 'site-packages', 'virtualenv_builddir_pyd.pth') michael@0: if is_win and os.path.exists(os.path.join(pcbuild_dir, 'build.bat')): michael@0: logger.notify('Detected python running from build directory %s', pcbuild_dir) michael@0: logger.notify('Writing .pth file linking to build directory for *.pyd files') michael@0: writefile(pyd_pth, pcbuild_dir) michael@0: else: michael@0: pcbuild_dir = None michael@0: if os.path.exists(pyd_pth): michael@0: logger.info('Deleting %s (not Windows env or not build directory python)' % pyd_pth) michael@0: os.unlink(pyd_pth) michael@0: michael@0: if sys.executable != py_executable: michael@0: ## FIXME: could I just hard link? michael@0: executable = sys.executable michael@0: shutil.copyfile(executable, py_executable) michael@0: make_exe(py_executable) michael@0: if is_win or is_cygwin: michael@0: pythonw = os.path.join(os.path.dirname(sys.executable), 'pythonw.exe') michael@0: if os.path.exists(pythonw): michael@0: logger.info('Also created pythonw.exe') michael@0: shutil.copyfile(pythonw, os.path.join(os.path.dirname(py_executable), 'pythonw.exe')) michael@0: python_d = os.path.join(os.path.dirname(sys.executable), 'python_d.exe') michael@0: python_d_dest = os.path.join(os.path.dirname(py_executable), 'python_d.exe') michael@0: if os.path.exists(python_d): michael@0: logger.info('Also created python_d.exe') michael@0: shutil.copyfile(python_d, python_d_dest) michael@0: elif os.path.exists(python_d_dest): michael@0: logger.info('Removed python_d.exe as it is no longer at the source') michael@0: os.unlink(python_d_dest) michael@0: # we need to copy the DLL to enforce that windows will load the correct one. michael@0: # may not exist if we are cygwin. michael@0: py_executable_dll = 'python%s%s.dll' % ( michael@0: sys.version_info[0], sys.version_info[1]) michael@0: py_executable_dll_d = 'python%s%s_d.dll' % ( michael@0: sys.version_info[0], sys.version_info[1]) michael@0: pythondll = os.path.join(os.path.dirname(sys.executable), py_executable_dll) michael@0: pythondll_d = os.path.join(os.path.dirname(sys.executable), py_executable_dll_d) michael@0: pythondll_d_dest = os.path.join(os.path.dirname(py_executable), py_executable_dll_d) michael@0: if os.path.exists(pythondll): michael@0: logger.info('Also created %s' % py_executable_dll) michael@0: shutil.copyfile(pythondll, os.path.join(os.path.dirname(py_executable), py_executable_dll)) michael@0: if os.path.exists(pythondll_d): michael@0: logger.info('Also created %s' % py_executable_dll_d) michael@0: shutil.copyfile(pythondll_d, pythondll_d_dest) michael@0: elif os.path.exists(pythondll_d_dest): michael@0: logger.info('Removed %s as the source does not exist' % pythondll_d_dest) michael@0: os.unlink(pythondll_d_dest) michael@0: if is_pypy: michael@0: # make a symlink python --> pypy-c michael@0: python_executable = os.path.join(os.path.dirname(py_executable), 'python') michael@0: if sys.platform in ('win32', 'cygwin'): michael@0: python_executable += '.exe' michael@0: logger.info('Also created executable %s' % python_executable) michael@0: copyfile(py_executable, python_executable, symlink) michael@0: michael@0: if is_win: michael@0: for name in 'libexpat.dll', 'libpypy.dll', 'libpypy-c.dll', 'libeay32.dll', 'ssleay32.dll', 'sqlite.dll': michael@0: src = join(prefix, name) michael@0: if os.path.exists(src): michael@0: copyfile(src, join(bin_dir, name), symlink) michael@0: michael@0: if os.path.splitext(os.path.basename(py_executable))[0] != expected_exe: michael@0: secondary_exe = os.path.join(os.path.dirname(py_executable), michael@0: expected_exe) michael@0: py_executable_ext = os.path.splitext(py_executable)[1] michael@0: if py_executable_ext.lower() == '.exe': michael@0: # python2.4 gives an extension of '.4' :P michael@0: secondary_exe += py_executable_ext michael@0: if os.path.exists(secondary_exe): michael@0: logger.warn('Not overwriting existing %s script %s (you must use %s)' michael@0: % (expected_exe, secondary_exe, py_executable)) michael@0: else: michael@0: logger.notify('Also creating executable in %s' % secondary_exe) michael@0: shutil.copyfile(sys.executable, secondary_exe) michael@0: make_exe(secondary_exe) michael@0: michael@0: if '.framework' in prefix: michael@0: if 'Python.framework' in prefix: michael@0: logger.debug('MacOSX Python framework detected') michael@0: # Make sure we use the the embedded interpreter inside michael@0: # the framework, even if sys.executable points to michael@0: # the stub executable in ${sys.prefix}/bin michael@0: # See http://groups.google.com/group/python-virtualenv/ michael@0: # browse_thread/thread/17cab2f85da75951 michael@0: original_python = os.path.join( michael@0: prefix, 'Resources/Python.app/Contents/MacOS/Python') michael@0: if 'EPD' in prefix: michael@0: logger.debug('EPD framework detected') michael@0: original_python = os.path.join(prefix, 'bin/python') michael@0: shutil.copy(original_python, py_executable) michael@0: michael@0: # Copy the framework's dylib into the virtual michael@0: # environment michael@0: virtual_lib = os.path.join(home_dir, '.Python') michael@0: michael@0: if os.path.exists(virtual_lib): michael@0: os.unlink(virtual_lib) michael@0: copyfile( michael@0: os.path.join(prefix, 'Python'), michael@0: virtual_lib, michael@0: symlink) michael@0: michael@0: # And then change the install_name of the copied python executable michael@0: try: michael@0: mach_o_change(py_executable, michael@0: os.path.join(prefix, 'Python'), michael@0: '@executable_path/../.Python') michael@0: except: michael@0: e = sys.exc_info()[1] michael@0: logger.warn("Could not call mach_o_change: %s. " michael@0: "Trying to call install_name_tool instead." % e) michael@0: try: michael@0: call_subprocess( michael@0: ["install_name_tool", "-change", michael@0: os.path.join(prefix, 'Python'), michael@0: '@executable_path/../.Python', michael@0: py_executable]) michael@0: except: michael@0: logger.fatal("Could not call install_name_tool -- you must " michael@0: "have Apple's development tools installed") michael@0: raise michael@0: michael@0: if not is_win: michael@0: # Ensure that 'python', 'pythonX' and 'pythonX.Y' all exist michael@0: py_exe_version_major = 'python%s' % sys.version_info[0] michael@0: py_exe_version_major_minor = 'python%s.%s' % ( michael@0: sys.version_info[0], sys.version_info[1]) michael@0: py_exe_no_version = 'python' michael@0: required_symlinks = [ py_exe_no_version, py_exe_version_major, michael@0: py_exe_version_major_minor ] michael@0: michael@0: py_executable_base = os.path.basename(py_executable) michael@0: michael@0: if py_executable_base in required_symlinks: michael@0: # Don't try to symlink to yourself. michael@0: required_symlinks.remove(py_executable_base) michael@0: michael@0: for pth in required_symlinks: michael@0: full_pth = join(bin_dir, pth) michael@0: if os.path.exists(full_pth): michael@0: os.unlink(full_pth) michael@0: if symlink: michael@0: os.symlink(py_executable_base, full_pth) michael@0: else: michael@0: copyfile(py_executable, full_pth, symlink) michael@0: michael@0: if is_win and ' ' in py_executable: michael@0: # There's a bug with subprocess on Windows when using a first michael@0: # argument that has a space in it. Instead we have to quote michael@0: # the value: michael@0: py_executable = '"%s"' % py_executable michael@0: # NOTE: keep this check as one line, cmd.exe doesn't cope with line breaks michael@0: cmd = [py_executable, '-c', 'import sys;out=sys.stdout;' michael@0: 'getattr(out, "buffer", out).write(sys.prefix.encode("utf-8"))'] michael@0: logger.info('Testing executable with %s %s "%s"' % tuple(cmd)) michael@0: try: michael@0: proc = subprocess.Popen(cmd, michael@0: stdout=subprocess.PIPE) michael@0: proc_stdout, proc_stderr = proc.communicate() michael@0: except OSError: michael@0: e = sys.exc_info()[1] michael@0: if e.errno == errno.EACCES: michael@0: logger.fatal('ERROR: The executable %s could not be run: %s' % (py_executable, e)) michael@0: sys.exit(100) michael@0: else: michael@0: raise e michael@0: michael@0: proc_stdout = proc_stdout.strip().decode("utf-8") michael@0: proc_stdout = os.path.normcase(os.path.abspath(proc_stdout)) michael@0: norm_home_dir = os.path.normcase(os.path.abspath(home_dir)) michael@0: if hasattr(norm_home_dir, 'decode'): michael@0: norm_home_dir = norm_home_dir.decode(sys.getfilesystemencoding()) michael@0: if proc_stdout != norm_home_dir: michael@0: logger.fatal( michael@0: 'ERROR: The executable %s is not functioning' % py_executable) michael@0: logger.fatal( michael@0: 'ERROR: It thinks sys.prefix is %r (should be %r)' michael@0: % (proc_stdout, norm_home_dir)) michael@0: logger.fatal( michael@0: 'ERROR: virtualenv is not compatible with this system or executable') michael@0: if is_win: michael@0: logger.fatal( michael@0: 'Note: some Windows users have reported this error when they ' michael@0: 'installed Python for "Only this user" or have multiple ' michael@0: 'versions of Python installed. Copying the appropriate ' michael@0: 'PythonXX.dll to the virtualenv Scripts/ directory may fix ' michael@0: 'this problem.') michael@0: sys.exit(100) michael@0: else: michael@0: logger.info('Got sys.prefix result: %r' % proc_stdout) michael@0: michael@0: pydistutils = os.path.expanduser('~/.pydistutils.cfg') michael@0: if os.path.exists(pydistutils): michael@0: logger.notify('Please make sure you remove any previous custom paths from ' michael@0: 'your %s file.' % pydistutils) michael@0: ## FIXME: really this should be calculated earlier michael@0: michael@0: fix_local_scheme(home_dir, symlink) michael@0: michael@0: if site_packages: michael@0: if os.path.exists(site_packages_filename): michael@0: logger.info('Deleting %s' % site_packages_filename) michael@0: os.unlink(site_packages_filename) michael@0: michael@0: return py_executable michael@0: michael@0: michael@0: def install_activate(home_dir, bin_dir, prompt=None): michael@0: home_dir = os.path.abspath(home_dir) michael@0: if is_win or is_jython and os._name == 'nt': michael@0: files = { michael@0: 'activate.bat': ACTIVATE_BAT, michael@0: 'deactivate.bat': DEACTIVATE_BAT, michael@0: 'activate.ps1': ACTIVATE_PS, michael@0: } michael@0: michael@0: # MSYS needs paths of the form /c/path/to/file michael@0: drive, tail = os.path.splitdrive(home_dir.replace(os.sep, '/')) michael@0: home_dir_msys = (drive and "/%s%s" or "%s%s") % (drive[:1], tail) michael@0: michael@0: # Run-time conditional enables (basic) Cygwin compatibility michael@0: home_dir_sh = ("""$(if [ "$OSTYPE" "==" "cygwin" ]; then cygpath -u '%s'; else echo '%s'; fi;)""" % michael@0: (home_dir, home_dir_msys)) michael@0: files['activate'] = ACTIVATE_SH.replace('__VIRTUAL_ENV__', home_dir_sh) michael@0: michael@0: else: michael@0: files = {'activate': ACTIVATE_SH} michael@0: michael@0: # suppling activate.fish in addition to, not instead of, the michael@0: # bash script support. michael@0: files['activate.fish'] = ACTIVATE_FISH michael@0: michael@0: # same for csh/tcsh support... michael@0: files['activate.csh'] = ACTIVATE_CSH michael@0: michael@0: files['activate_this.py'] = ACTIVATE_THIS michael@0: if hasattr(home_dir, 'decode'): michael@0: home_dir = home_dir.decode(sys.getfilesystemencoding()) michael@0: vname = os.path.basename(home_dir) michael@0: for name, content in files.items(): michael@0: content = content.replace('__VIRTUAL_PROMPT__', prompt or '') michael@0: content = content.replace('__VIRTUAL_WINPROMPT__', prompt or '(%s)' % vname) michael@0: content = content.replace('__VIRTUAL_ENV__', home_dir) michael@0: content = content.replace('__VIRTUAL_NAME__', vname) michael@0: content = content.replace('__BIN_NAME__', os.path.basename(bin_dir)) michael@0: writefile(os.path.join(bin_dir, name), content) michael@0: michael@0: def install_distutils(home_dir): michael@0: distutils_path = change_prefix(distutils.__path__[0], home_dir) michael@0: mkdir(distutils_path) michael@0: ## FIXME: maybe this prefix setting should only be put in place if michael@0: ## there's a local distutils.cfg with a prefix setting? michael@0: home_dir = os.path.abspath(home_dir) michael@0: ## FIXME: this is breaking things, removing for now: michael@0: #distutils_cfg = DISTUTILS_CFG + "\n[install]\nprefix=%s\n" % home_dir michael@0: writefile(os.path.join(distutils_path, '__init__.py'), DISTUTILS_INIT) michael@0: writefile(os.path.join(distutils_path, 'distutils.cfg'), DISTUTILS_CFG, overwrite=False) michael@0: michael@0: def fix_local_scheme(home_dir, symlink=True): michael@0: """ michael@0: Platforms that use the "posix_local" install scheme (like Ubuntu with michael@0: Python 2.7) need to be given an additional "local" location, sigh. michael@0: """ michael@0: try: michael@0: import sysconfig michael@0: except ImportError: michael@0: pass michael@0: else: michael@0: if sysconfig._get_default_scheme() == 'posix_local': michael@0: local_path = os.path.join(home_dir, 'local') michael@0: if not os.path.exists(local_path): michael@0: os.mkdir(local_path) michael@0: for subdir_name in os.listdir(home_dir): michael@0: if subdir_name == 'local': michael@0: continue michael@0: copyfile(os.path.abspath(os.path.join(home_dir, subdir_name)), \ michael@0: os.path.join(local_path, subdir_name), symlink) michael@0: michael@0: def fix_lib64(lib_dir, symlink=True): michael@0: """ michael@0: Some platforms (particularly Gentoo on x64) put things in lib64/pythonX.Y michael@0: instead of lib/pythonX.Y. If this is such a platform we'll just create a michael@0: symlink so lib64 points to lib michael@0: """ michael@0: if [p for p in distutils.sysconfig.get_config_vars().values() michael@0: if isinstance(p, basestring) and 'lib64' in p]: michael@0: # PyPy's library path scheme is not affected by this. michael@0: # Return early or we will die on the following assert. michael@0: if is_pypy: michael@0: logger.debug('PyPy detected, skipping lib64 symlinking') michael@0: return michael@0: michael@0: logger.debug('This system uses lib64; symlinking lib64 to lib') michael@0: michael@0: assert os.path.basename(lib_dir) == 'python%s' % sys.version[:3], ( michael@0: "Unexpected python lib dir: %r" % lib_dir) michael@0: lib_parent = os.path.dirname(lib_dir) michael@0: top_level = os.path.dirname(lib_parent) michael@0: lib_dir = os.path.join(top_level, 'lib') michael@0: lib64_link = os.path.join(top_level, 'lib64') michael@0: assert os.path.basename(lib_parent) == 'lib', ( michael@0: "Unexpected parent dir: %r" % lib_parent) michael@0: if os.path.lexists(lib64_link): michael@0: return michael@0: cp_or_ln = (os.symlink if symlink else copyfile) michael@0: cp_or_ln('lib', lib64_link) michael@0: michael@0: def resolve_interpreter(exe): michael@0: """ michael@0: If the executable given isn't an absolute path, search $PATH for the interpreter michael@0: """ michael@0: # If the "executable" is a version number, get the installed executable for michael@0: # that version michael@0: python_versions = get_installed_pythons() michael@0: if exe in python_versions: michael@0: exe = python_versions[exe] michael@0: michael@0: if os.path.abspath(exe) != exe: michael@0: paths = os.environ.get('PATH', '').split(os.pathsep) michael@0: for path in paths: michael@0: if os.path.exists(os.path.join(path, exe)): michael@0: exe = os.path.join(path, exe) michael@0: break michael@0: if not os.path.exists(exe): michael@0: logger.fatal('The executable %s (from --python=%s) does not exist' % (exe, exe)) michael@0: raise SystemExit(3) michael@0: if not is_executable(exe): michael@0: logger.fatal('The executable %s (from --python=%s) is not executable' % (exe, exe)) michael@0: raise SystemExit(3) michael@0: return exe michael@0: michael@0: def is_executable(exe): michael@0: """Checks a file is executable""" michael@0: return os.access(exe, os.X_OK) michael@0: michael@0: ############################################################ michael@0: ## Relocating the environment: michael@0: michael@0: def make_environment_relocatable(home_dir): michael@0: """ michael@0: Makes the already-existing environment use relative paths, and takes out michael@0: the #!-based environment selection in scripts. michael@0: """ michael@0: home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) michael@0: activate_this = os.path.join(bin_dir, 'activate_this.py') michael@0: if not os.path.exists(activate_this): michael@0: logger.fatal( michael@0: 'The environment doesn\'t have a file %s -- please re-run virtualenv ' michael@0: 'on this environment to update it' % activate_this) michael@0: fixup_scripts(home_dir, bin_dir) michael@0: fixup_pth_and_egg_link(home_dir) michael@0: ## FIXME: need to fix up distutils.cfg michael@0: michael@0: OK_ABS_SCRIPTS = ['python', 'python%s' % sys.version[:3], michael@0: 'activate', 'activate.bat', 'activate_this.py', michael@0: 'activate.fish', 'activate.csh'] michael@0: michael@0: def fixup_scripts(home_dir, bin_dir): michael@0: if is_win: michael@0: new_shebang_args = ( michael@0: '%s /c' % os.path.normcase(os.environ.get('COMSPEC', 'cmd.exe')), michael@0: '', '.exe') michael@0: else: michael@0: new_shebang_args = ('/usr/bin/env', sys.version[:3], '') michael@0: michael@0: # This is what we expect at the top of scripts: michael@0: shebang = '#!%s' % os.path.normcase(os.path.join( michael@0: os.path.abspath(bin_dir), 'python%s' % new_shebang_args[2])) michael@0: # This is what we'll put: michael@0: new_shebang = '#!%s python%s%s' % new_shebang_args michael@0: michael@0: for filename in os.listdir(bin_dir): michael@0: filename = os.path.join(bin_dir, filename) michael@0: if not os.path.isfile(filename): michael@0: # ignore subdirs, e.g. .svn ones. michael@0: continue michael@0: f = open(filename, 'rb') michael@0: try: michael@0: try: michael@0: lines = f.read().decode('utf-8').splitlines() michael@0: except UnicodeDecodeError: michael@0: # This is probably a binary program instead michael@0: # of a script, so just ignore it. michael@0: continue michael@0: finally: michael@0: f.close() michael@0: if not lines: michael@0: logger.warn('Script %s is an empty file' % filename) michael@0: continue michael@0: michael@0: old_shebang = lines[0].strip() michael@0: old_shebang = old_shebang[0:2] + os.path.normcase(old_shebang[2:]) michael@0: michael@0: if not old_shebang.startswith(shebang): michael@0: if os.path.basename(filename) in OK_ABS_SCRIPTS: michael@0: logger.debug('Cannot make script %s relative' % filename) michael@0: elif lines[0].strip() == new_shebang: michael@0: logger.info('Script %s has already been made relative' % filename) michael@0: else: michael@0: logger.warn('Script %s cannot be made relative (it\'s not a normal script that starts with %s)' michael@0: % (filename, shebang)) michael@0: continue michael@0: logger.notify('Making script %s relative' % filename) michael@0: script = relative_script([new_shebang] + lines[1:]) michael@0: f = open(filename, 'wb') michael@0: f.write('\n'.join(script).encode('utf-8')) michael@0: f.close() michael@0: michael@0: def relative_script(lines): michael@0: "Return a script that'll work in a relocatable environment." michael@0: 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" michael@0: # Find the last future statement in the script. If we insert the activation michael@0: # line before a future statement, Python will raise a SyntaxError. michael@0: activate_at = None michael@0: for idx, line in reversed(list(enumerate(lines))): michael@0: if line.split()[:3] == ['from', '__future__', 'import']: michael@0: activate_at = idx + 1 michael@0: break michael@0: if activate_at is None: michael@0: # Activate after the shebang. michael@0: activate_at = 1 michael@0: return lines[:activate_at] + ['', activate, ''] + lines[activate_at:] michael@0: michael@0: def fixup_pth_and_egg_link(home_dir, sys_path=None): michael@0: """Makes .pth and .egg-link files use relative paths""" michael@0: home_dir = os.path.normcase(os.path.abspath(home_dir)) michael@0: if sys_path is None: michael@0: sys_path = sys.path michael@0: for path in sys_path: michael@0: if not path: michael@0: path = '.' michael@0: if not os.path.isdir(path): michael@0: continue michael@0: path = os.path.normcase(os.path.abspath(path)) michael@0: if not path.startswith(home_dir): michael@0: logger.debug('Skipping system (non-environment) directory %s' % path) michael@0: continue michael@0: for filename in os.listdir(path): michael@0: filename = os.path.join(path, filename) michael@0: if filename.endswith('.pth'): michael@0: if not os.access(filename, os.W_OK): michael@0: logger.warn('Cannot write .pth file %s, skipping' % filename) michael@0: else: michael@0: fixup_pth_file(filename) michael@0: if filename.endswith('.egg-link'): michael@0: if not os.access(filename, os.W_OK): michael@0: logger.warn('Cannot write .egg-link file %s, skipping' % filename) michael@0: else: michael@0: fixup_egg_link(filename) michael@0: michael@0: def fixup_pth_file(filename): michael@0: lines = [] michael@0: prev_lines = [] michael@0: f = open(filename) michael@0: prev_lines = f.readlines() michael@0: f.close() michael@0: for line in prev_lines: michael@0: line = line.strip() michael@0: if (not line or line.startswith('#') or line.startswith('import ') michael@0: or os.path.abspath(line) != line): michael@0: lines.append(line) michael@0: else: michael@0: new_value = make_relative_path(filename, line) michael@0: if line != new_value: michael@0: logger.debug('Rewriting path %s as %s (in %s)' % (line, new_value, filename)) michael@0: lines.append(new_value) michael@0: if lines == prev_lines: michael@0: logger.info('No changes to .pth file %s' % filename) michael@0: return michael@0: logger.notify('Making paths in .pth file %s relative' % filename) michael@0: f = open(filename, 'w') michael@0: f.write('\n'.join(lines) + '\n') michael@0: f.close() michael@0: michael@0: def fixup_egg_link(filename): michael@0: f = open(filename) michael@0: link = f.readline().strip() michael@0: f.close() michael@0: if os.path.abspath(link) != link: michael@0: logger.debug('Link in %s already relative' % filename) michael@0: return michael@0: new_link = make_relative_path(filename, link) michael@0: logger.notify('Rewriting link %s in %s as %s' % (link, filename, new_link)) michael@0: f = open(filename, 'w') michael@0: f.write(new_link) michael@0: f.close() michael@0: michael@0: def make_relative_path(source, dest, dest_is_directory=True): michael@0: """ michael@0: Make a filename relative, where the filename is dest, and it is michael@0: being referred to from the filename source. michael@0: michael@0: >>> make_relative_path('/usr/share/something/a-file.pth', michael@0: ... '/usr/share/another-place/src/Directory') michael@0: '../another-place/src/Directory' michael@0: >>> make_relative_path('/usr/share/something/a-file.pth', michael@0: ... '/home/user/src/Directory') michael@0: '../../../home/user/src/Directory' michael@0: >>> make_relative_path('/usr/share/a-file.pth', '/usr/share/') michael@0: './' michael@0: """ michael@0: source = os.path.dirname(source) michael@0: if not dest_is_directory: michael@0: dest_filename = os.path.basename(dest) michael@0: dest = os.path.dirname(dest) michael@0: dest = os.path.normpath(os.path.abspath(dest)) michael@0: source = os.path.normpath(os.path.abspath(source)) michael@0: dest_parts = dest.strip(os.path.sep).split(os.path.sep) michael@0: source_parts = source.strip(os.path.sep).split(os.path.sep) michael@0: while dest_parts and source_parts and dest_parts[0] == source_parts[0]: michael@0: dest_parts.pop(0) michael@0: source_parts.pop(0) michael@0: full_parts = ['..']*len(source_parts) + dest_parts michael@0: if not dest_is_directory: michael@0: full_parts.append(dest_filename) michael@0: if not full_parts: michael@0: # Special case for the current directory (otherwise it'd be '') michael@0: return './' michael@0: return os.path.sep.join(full_parts) michael@0: michael@0: michael@0: michael@0: ############################################################ michael@0: ## Bootstrap script creation: michael@0: michael@0: def create_bootstrap_script(extra_text, python_version=''): michael@0: """ michael@0: Creates a bootstrap script, which is like this script but with michael@0: extend_parser, adjust_options, and after_install hooks. michael@0: michael@0: This returns a string that (written to disk of course) can be used michael@0: as a bootstrap script with your own customizations. The script michael@0: will be the standard virtualenv.py script, with your extra text michael@0: added (your extra text should be Python code). michael@0: michael@0: If you include these functions, they will be called: michael@0: michael@0: ``extend_parser(optparse_parser)``: michael@0: You can add or remove options from the parser here. michael@0: michael@0: ``adjust_options(options, args)``: michael@0: You can change options here, or change the args (if you accept michael@0: different kinds of arguments, be sure you modify ``args`` so it is michael@0: only ``[DEST_DIR]``). michael@0: michael@0: ``after_install(options, home_dir)``: michael@0: michael@0: After everything is installed, this function is called. This michael@0: is probably the function you are most likely to use. An michael@0: example would be:: michael@0: michael@0: def after_install(options, home_dir): michael@0: subprocess.call([join(home_dir, 'bin', 'easy_install'), michael@0: 'MyPackage']) michael@0: subprocess.call([join(home_dir, 'bin', 'my-package-script'), michael@0: 'setup', home_dir]) michael@0: michael@0: This example immediately installs a package, and runs a setup michael@0: script from that package. michael@0: michael@0: If you provide something like ``python_version='2.5'`` then the michael@0: script will start with ``#!/usr/bin/env python2.5`` instead of michael@0: ``#!/usr/bin/env python``. You can use this when the script must michael@0: be run with a particular Python version. michael@0: """ michael@0: filename = __file__ michael@0: if filename.endswith('.pyc'): michael@0: filename = filename[:-1] michael@0: f = codecs.open(filename, 'r', encoding='utf-8') michael@0: content = f.read() michael@0: f.close() michael@0: py_exe = 'python%s' % python_version michael@0: content = (('#!/usr/bin/env %s\n' % py_exe) michael@0: + '## WARNING: This file is generated\n' michael@0: + content) michael@0: return content.replace('##EXT' 'END##', extra_text) michael@0: michael@0: ##EXTEND## michael@0: michael@0: def convert(s): michael@0: b = base64.b64decode(s.encode('ascii')) michael@0: return zlib.decompress(b).decode('utf-8') michael@0: michael@0: ##file site.py michael@0: SITE_PY = convert(""" michael@0: eJzFPf1z2zaWv/OvwMqToZTIdOJ0e3tOnRsncVrvuYm3SWdz63q0lARZrCmSJUjL2pu7v/3eBwAC michael@0: JCXbm+6cphNLJPDw8PC+8PAeOhgMTopCZnOxyud1KoWScTlbiiKulkos8lJUy6Sc7xdxWW3g6ewm michael@0: vpZKVLlQGxVhqygInn7lJ3gqPi8TZVCAb3Fd5au4SmZxmm5EsiryspJzMa/LJLsWSZZUSZwm/4AW michael@0: eRaJp1+PQXCWCZh5mshS3MpSAVwl8oW42FTLPBPDusA5v4j+GL8cjYWalUlRQYNS4wwUWcZVkEk5 michael@0: BzShZa2AlEkl91UhZ8kimdmG67xO56JI45kUf/87T42ahmGg8pVcL2UpRQbIAEwJsArEA74mpZjl michael@0: cxkJ8UbOYhyAnzfEChjaGNdMIRmzXKR5dg1zyuRMKhWXGzGc1hUBIpTFPAecEsCgStI0WOfljRrB michael@0: ktJ6rOGRiJk9/Mkwe8A8cfwu5wCOH7Pg5yy5GzNs4B4EVy2ZbUq5SO5EjGDhp7yTs4l+NkwWYp4s michael@0: FkCDrBphk4ARUCJNpgcFLcd3eoVeHxBWlitjGEMiytyYX1KPKDirRJwqYNu6QBopwvydnCZxBtTI michael@0: bmE4gAgkDfrGmSeqsuPQ7EQOAEpcxwqkZKXEcBUnGTDrj/GM0P5rks3ztRoRBWC1lPi1VpU7/2EP michael@0: AaC1Q4BxgItlVrPO0uRGppsRIPAZsC+lqtMKBWKelHJW5WUiFQEA1DZC3gHSYxGXUpOQOdPI7Zjo michael@0: TzRJMlxYFDAUeHyJJFkk13VJEiYWCXAucMX7jz+Jd6dvzk4+aB4zwFhmr1eAM0ChhXZwggHEQa3K michael@0: gzQHgY6Cc/wj4vkchewaxwe8mgYH9650MIS5F1G7j7PgQHa9uHoYmGMFyoTGCqjff0OXsVoCff7n michael@0: nvUOgpNtVKGJ87f1MgeZzOKVFMuY+Qs5I/hOw3kdFdXyFXCDQjgVkErh4iCCCcIDkrg0G+aZFAWw michael@0: WJpkchQAhabU1l9FYIUPebZPa93iBIBQBhm8dJ6NaMRMwkS7sF6hvjCNNzQz3SSw67zKS1IcwP/Z michael@0: jHRRGmc3hKMihuJvU3mdZBkihLwQhHshDaxuEuDEeSTOqRXpBdNIhKy9uCWKRA28hEwHPCnv4lWR michael@0: yjGLL+rW3WqEBpOVMGudMsdBy4rUK61aM9Ve3juMvrS4jtCslqUE4PXUE7pFno/FFHQ2YVPEKxav michael@0: ap0T5wQ98kSdkCeoJfTF70DRE6XqlbQvkVdAsxBDBYs8TfM1kOwoCITYw0bGKPvMCW/hHfwLcPHf michael@0: VFazZRA4I1nAGhQivw0UAgGTIDPN1RoJj9s0K7eVTJKxpsjLuSxpqIcR+4ARf2BjnGvwIa+0UePp michael@0: 4irnq6RClTTVJjNhi5eFFevHVzxvmAZYbkU0M00bOq1wemmxjKfSuCRTuUBJ0Iv0yi47jBn0jEm2 michael@0: uBIrtjLwDsgiE7Yg/YoFlc6ikuQEAAwWvjhLijqlRgoZTMQw0Kog+KsYTXqunSVgbzbLASokNt8z michael@0: sD+A2z9AjNbLBOgzAwigYVBLwfJNk6pEB6HRR4Fv9E1/Hh849WyhbRMPuYiTVFv5OAvO6OFpWZL4 michael@0: zmSBvcaaGApmmFXo2l1nQEcU88FgEATGHdoo8zVXQVVujoAVhBlnMpnWCRq+yQRNvf6hAh5FOAN7 michael@0: 3Ww7Cw80hOn0AajkdFmU+Qpf27l9AmUCY2GPYE9ckJaR7CB7nPgKyeeq9MI0RdvtsLNAPRRc/HT6 michael@0: /uzL6SdxLC4blTZu67MrGPM0i4GtySIAU7WGbXQZtETFl6DuE+/BvBNTgD2j3iS+Mq5q4F1A/XNZ michael@0: 02uYxsx7GZx+OHlzfjr5+dPpT5NPZ59PAUGwMzLYoymjeazBYVQRCAdw5VxF2r4GnR704M3JJ/sg michael@0: mCRq8u03wG7wZHgtK2DicggzHotwFd8pYNBwTE1HiGOnAVjwcDQSr8Xh06cvDwlasSk2AAzMrtMU michael@0: H060RZ8k2SIPR9T4V3bpj1lJaf/t8uibK3F8LMJf49s4DMCHapoyS/xI4vR5U0joWsGfYa5GQTCX michael@0: CxC9G4kCOnxKfvGIO8CSQMtc2+lf8yQz75kr3SFIfwypB+AwmczSWClsPJmEQATq0POBDhE71yh1 michael@0: Q+hYbNyuI40KfkoJC5thlzH+04NiPKV+iAaj6HYxjUBcV7NYSW5F04d+kwnqrMlkqAcEYSaJAYeL michael@0: 1VAoTBPUWWUCfi1xHuqwqcpT/InwUQuQAOLWCrUkLpLeOkW3cVpLNXQmBUQcDltkREWbKOJHcFGG michael@0: YImbpRuN2tQ0PAPNgHxpDlq0bFEOP3vg74C6Mps43Ojx3otphpj+mXcahAO4nCGqe6VaUFg7iovT michael@0: C/Hy+eE+ujOw55xb6njN0UInWS3twwWslpEHRph7GXlx6bJAPYtPj3bDXEV2ZbqssNBLXMpVfivn michael@0: gC0ysLPK4id6AztzmMcshlUEvU7+AKtQ4zfGuA/l2YO0oO8A1FsRFLP+Zun3OBggMwWKiDfWRGq9 michael@0: 62dTWJT5bYLOxnSjX4KtBGWJFtM4NoGzcB6ToUkEDQFecIaUWssQ1GFZs8NKeCNItBfzRrFGBO4c michael@0: NfUVfb3J8nU24Z3wMSrd4ciyLgqWZl5s0CzBnngPVgiQzGFj1xCNoYDLL1C29gF5mD5MFyhLewsA michael@0: BIZe0XbNgWW2ejRF3jXisAhj9EqQ8JYS/YVbMwRttQwxHEj0NrIPjJZASDA5q+CsatBMhrJmmsHA michael@0: Dkl8rjuPeAvqA2hRMQKzOdTQuJGh3+URKGdx7iolpx9a5C9fvjDbqCXFVxCxKU4aXYgFGcuo2IBh michael@0: TUAnGI+MozXEBmtwbgFMrTRriv1PIi/YG4P1vNCyDX4A7O6qqjg6OFiv15GOLuTl9YFaHPzxT99+ michael@0: +6fnrBPnc+IfmI4jLTrUFh3QO/Roo++MBXptVq7Fj0nmcyPBGkryysgVRfy+r5N5Lo72R1Z/Ihc3 michael@0: Zhr/Na4MKJCJGZSpDLQdNBg9UftPopdqIJ6QdbZthyP2S7RJtVbMt7rQo8rBEwC/ZZbXaKobTlDi michael@0: GVg32KHP5bS+Du3gno00P2CqKKdDywP7L64QA58zDF8ZUzxBLUFsgRbfIf1PzDYxeUdaQyB50UR1 michael@0: ds+bfi1miDt/uLxbX9MRGjPDRCF3oET4TR4sgLZxV3Lwo11btHuOa2s+niEwlj4wzKsdyyEKDuGC michael@0: azF2pc7havR4QZrWrJpBwbiqERQ0OIlTprYGRzYyRJDo3ZjNPi+sbgF0akUOTXzArAK0cMfpWLs2 michael@0: KzieEPLAsXhBTyS4yEedd895aes0pYBOi0c9qjBgb6HRTufAl0MDYCwG5c8Dbmm2KR9bi8Jr0AMs michael@0: 5xgQMtiiw0z4xvUBB3uDHnbqWP1tvZnGfSBwkYYci3oQdEL5mEcoFUhTMfR7bmNxS9zuYDstDjGV michael@0: WSYSabVFuNrKo1eodhqmRZKh7nUWKZqlOXjFVisSIzXvfWeB9kH4uM+YaQnUZGjI4TQ6Jm/PE8BQ michael@0: t8Pw2XWNgQY3DoMYrRJF1g3JtIR/wK2g+AYFo4CWBM2CeaiU+RP7HWTOzld/2cIeltDIEG7TbW5I michael@0: x2JoOOb9nkAy6mgMSEEGJOwKI7mOrA5S4DBngTzhhtdyq3QTjEiBnDkWhNQM4E4vvQ0OPonwBIQk michael@0: FCHfVUoW4pkYwPK1RfVhuvt35VIThBg6DchV0NGLYzey4UQ1jltRDp+h/fgGnZUUOXDwFFweN9Dv michael@0: srlhWht0AWfdV9wWKdDIFIcZjFxUrwxh3GDyH46dFg2xzCCGobyBvCMdM9IosMutQcOCGzDemrfH michael@0: 0o/diAX2HYa5OpSrO9j/hWWiZrkKKWbSjl24H80VXdpYbM+T6QD+eAswGF15kGSq4xcYZfknBgk9 michael@0: 6GEfdG+yGBaZx+U6yUJSYJp+x/7SdPCwpPSM3MEn2k4dwEQx4nnwvgQBoaPPAxAn1ASwK5eh0m5/ michael@0: F+zOKQ4sXO4+8Nzmy6OXV13ijrdFeOynf6lO76oyVrhaKS8aCwWuVteAo9KFycXZRh9e6sNt3CaU michael@0: uYJdpPj46YtAQnBcdx1vHjf1huERm3vn5H0M6qDX7iVXa3bELoAIakVklIPw8Rz5cGQfO7kdE3sE michael@0: kEcxzI5FMZA0n/wzcHYtFIyxP99kGEdrqwz8wOtvv5n0REZdJL/9ZnDPKC1i9In9sOUJ2pE5qWDX michael@0: bEsZp+RqOH0oqJg1rGPbFCPW57T90zx21eNzarRs7Lu/BX4MFAypS/ARno8bsnWnih/fndoKT9up michael@0: HcA6u1Xz2aNFgL19Pv0VdshKB9Vu4ySlcwWY/P4+Klezued4Rb/28CDtVDAOCfr2X+ryOXBDyNGE michael@0: UXc62hk7MQHnnl2w+RSx6qKyp3MImiMwLy/APf7sQtUWzDDucz5eOOxRTd6M+5yJr1Gr+PldNJAF michael@0: 5tFg0Ef2rez4/zHL5/+aST5wKubk+ne0ho8E9HvNhI0HQ9PGw4fVv+yu3TXAHmCetridO9zC7tB8 michael@0: Vrkwzh2rJCWeou56KtaUrkCxVTwpAihz9vt64OAy6kPvt3VZ8tE1qcBClvt4HDsWmKllPL9eE7Mn michael@0: Dj7ICjGxzWYUq3byevI+NRLq6LOdSdjsG/rlbJmbmJXMbpMS+oLCHYY/fPzxNOw3IRjHhU4PtyIP michael@0: 9xsQ7iOYNtTECR/Thyn0mC7/vFS1ty4+QU1GgIkIa7L12gc/EGziCP1rcE9EyDuw5WN23KHPlnJ2 michael@0: M5GUOoBsil2doPhbfI2Y2IwCP/9LxQtKYoOZzNIaacWON2YfLupsRucjlQT/SqcKY+oQJQRw+G+R michael@0: xtdiSJ3nGHrS3EjRqdu41N5nUeaYnCrqZH5wncyF/K2OU9zWy8UCcMHDK/0q4uEpAiXecU4DJy0q michael@0: OavLpNoACWKV67M/Sn9wGk43PNGhhyQf8zABMSHiSHzCaeN7JtzckMsEB/wTD5wk7ruxg5OsENFz michael@0: eJ/lExx1Qjm+Y0aqey5Pj4P2CDkAGABQmP9gpCN3/htJr9wDRlpzl6ioJT1SupGGnJwxhDIcYaSD michael@0: f9NPnxFd3tqC5fV2LK93Y3ndxvK6F8trH8vr3Vi6IoELa4NWRhL6AlftY43efBs35sTDnMazJbfD michael@0: 3E/M8QSIojAbbCNTnALtRbb4fI+AkNp2DpzpYZM/k3BSaZlzCFyDRO7HQyy9mTfJ605nysbRnXkq michael@0: xp3dlkPk9z2IIkoVm1J3lrd5XMWRJxfXaT4FsbXojhsAY9FOJ+JYaXY7mXJ0t2WpBhf/9fmHjx+w michael@0: OYIamPQG6oaLiIYFpzJ8GpfXqitNzeavAHakln4iDnXTAPceGFnjUfb4n3eU4YGMI9aUoZCLAjwA michael@0: yuqyzdzcpzBsPddJUvo5MzkfNh2LQVYNmkltIdLJxcW7k88nAwr5Df534AqMoa0vHS4+poVt0PXf michael@0: 3OaW4tgHhFrHthrj587Jo3XDEffbWAO248O3Hhw+xGD3hgn8Wf5LKQVLAoSKdPD3MYR68B7oq7YJ michael@0: HfoYRuwk/7kna+ys2HeO7DkuiiP6fccO7QH8w07cY0yAANqFGpqdQbOZail9a153UNQB+kBf76u3 michael@0: YO2tV3sn41PUTqLHAXQoa5ttd/+8cxo2ekpWb06/P/twfvbm4uTzD44LiK7cx08Hh+L0xy+C8kPQ michael@0: gLFPFGNqRIWZSGBY3EInMc/hvxojP/O64iAx9Hp3fq5PalZY6oK5z2hzInjOaUwWGgfNOAptH+r8 michael@0: I8Qo1Rskp6aI0nWo5gj3SyuuZ1G5zo+mUqUpOqu13nrpWjFTU0bn2hFIHzR2ScEgOMUMXlEWe2V2 michael@0: hSWfAOo6qx6ktI22iSEpBQU76QLO+Zc5XfECpdQZnjSdtaK/DF1cw6tIFWkCO7lXoZUl3Q3TYxrG michael@0: 0Q/tATfj1acBne4wsm7Is96KBVqtVyHPTfcfNYz2Ww0YNgz2DuadSUoPoQxsTG4TITbik5xQ3sFX michael@0: u/R6DRQsGB70VbiIhukSmH0Mm2uxTGADATy5BOuL+wSA0FoJ/0DgyIkOyByzM8K3q/n+X0JNEL/1 michael@0: L7/0NK/KdP9vooBdkOBUorCHmG7jd7DxiWQkTj++H4WMHKXmir/UWB4ADgkFQB1pp/wlPkGfDJVM michael@0: Fzq/xNcH+EL7CfS61b2URam797vGIUrAEzUkr+GJMvQLMd3Lwh7jVEYt0Fj5YDHDCkI3DcF89sSn michael@0: pUxTne9+9u78FHxHLMZACeJzt1MYjuMleISuk++4wrEFCg/Y4XWJbFyiC0tJFvPIa9YbtEaRo95e michael@0: XoZdJwoMd3t1osBlnCgX7SFOm2GZcoIIWRnWwiwrs3arDVLYbUMUR5lhlphclJTA6vME8DI9jXlL michael@0: BHslLPUwEXg+RU6yymQspskM9CioXFCoYxASJC7WMxLn5RnHwPNSmTIoeFhsyuR6WeHpBnSOqAQD michael@0: m/948uX87AOVJRy+bLzuHuYc005gzEkkx5giiNEO+OKm/SFXTSZ9PKtfIQzUPvCn/YqzU455gE4/ michael@0: Dizin/YrrkM7dnaCPANQUHXRFg/cADjd+uSmkQXG1e6D8eOmADaY+WAoFollLzrRw51flxNty5Yp michael@0: obiPefmIA5xFYVPSdGc3Ja390XNcFHjONR/2N4K3fbJlPlPoetN5sy35zf10pBBLYgGjbmt/DJMd michael@0: 1mmqp+Mw2zZuoW2ttrG/ZE6s1Gk3y1CUgYhDt/PIZbJ+JaybMwd6adQdYOI7ja6RxF5VPvglG2gP michael@0: w8PEEruzTzEdqYyFjABGMqSu/anBh0KLAAqEsn+HjuSOR08PvTk61uD+OWrdBbbxB1CEOheXajzy michael@0: EjgRvvzGjiO/IrRQjx6J0PFUMpnlNk8MP+slepUv/Dn2ygAFMVHsyji7lkOGNTYwn/nE3hKCJW3r michael@0: kfoyueozLOIMnNO7LRzelYv+gxODWosROu1u5KatjnzyYIPeUpCdBPPBl/EadH9RV0NeyS3n0L21 michael@0: dNuh3g8Rsw+hqT59H4YYjvkt3LI+DeBeamhY6OH9tuUUltfGOLLWPraqmkL7QnuwsxK2ZpWiYxmn michael@0: ONH4otYLaAzucWPyB/apThSyv3vqxJyYkAXKg7sgvbkNdINWOGHA5UpcOZpQOnxTTaPfzeWtTMFo michael@0: gJEdYrXDr7baYRTZcEpvHthXY3exudj040ZvGsyOTDkGIkCFGL2Bnl0INTjgCv+idyJxdkPO8du/ michael@0: no3F2w8/wb9v5EewoFjzOBZ/g9HF27yEbSUX7dJtCljAUfF+Ma8VFkYSNDqh4Isn0Fu78MiLpyG6 michael@0: ssQvKbEKUmAybbni204ARZ4gFbI37oGpl4DfpqCr5YQaB7FvLQb6JdJge40L1oUc6JbRslqlaCac michael@0: 4EiziJeD87O3px8+nUbVHTK2+Tlwgid+HhZORx8Nl3gMNhb2yazGJ1eOv/yDTIsed1nvNU29DO41 michael@0: RQjbkcLuL/kmjdjuKeISAwai2MzzWYQtgdO5RK9ag/88craV99p3z7girOFIH541Tjw+BmqIX9r6 michael@0: ZwANqY+eE/UkhOIp1orx42jQb4HHgiLa8OfpzXruBsR10Q9NsI1pM+uh392qwCXTWcOznER4Hdtl michael@0: MHWgaRKr1XTm1gd+zIS+CAWUGx1vyEVcp5WQGWylaG9PN1KAgndL+lhCmFXYilGdG0Vn0nW8UU7u michael@0: UazEAEcdUFE9nsNQoBC23j/GN2wGsNZQ1FwCDdAJUdo25U5XVc+WLMG8EyLq9eQbrJPspZvGoynM michael@0: g/LGeNb4rzBP9BYZo2tZ6fnzg+Ho8kWT4EDB6JlX0DsrwNi5bLIHGrN4+vTpQPzH/U4PoxKleX4D michael@0: 3hjA7nVWzun1FoOtJ2dXq+vQmzcR8ONsKS/hwRUFze3zOqOI5I6utCDS/jUwQlyb0DKjad8yxxyr michael@0: K/l8mVvwOZU2GD9nCV13hBElicpW3xqF0SYjTcSSoBjCWM2SJOToBKzHJq+xFg+ji5pf5B1wfIJg michael@0: xvgWD8Z4h71Ex5LyZi33WHSOxYAADyiljEejYmaqRgM8JxcbjebkLEuqpozkuXtmqq8AqOwtRpqv michael@0: RLxGyTDzaBHDKev0WLVxrPOdLOptVPLZpRtnbM2SX9+HO7A2SFq+WBhM4aFZpFkuy5kxp7hiySyp michael@0: HDCmHcLhznR5E1mfKOhBaQDqnazC3Eq0ffsHuy4uph/p+HjfjKSzhip7IRbHhOKslVcYRc34FH2y michael@0: hLR8a76MYJQPFM3WnoA3lviDjqViDYF3b4dbzlhn+j4OTttoLukAOHQHlFWQlh09HeFcPGbhM9Nu michael@0: uUUDP7QzJ9xuk7Kq43Sir32YoJ82sefpGk9bBrezwNN6K+Db5+D47uuMfXAcTHIN0hMzbk1FxrFY michael@0: 6MhE5FaW+UVYRY5e3iH7SuBTIGXmE1MPbWJHl5ZdbaGpTtV0VDyCemaKl7Y45KZqplNw4mI+pvQm michael@0: U+6wxXn2M0fp6grxWgxfjsVha+czKzZ4kxMg+2Qe+q4YdYOpOMEAM8f2vRji9bEYvhiLP+6AHm0Z michael@0: 4OjQHaG9j21B2Ark5dWjyZgmUyJb2JfCfn9fncMImp5xHF21yd8l03dEpX9vUYkrBHWi8ot2onJr michael@0: 7K371s7HRzJcgeJYJHK+/0QhCTXSjW7ezuCEHxbQ79kcLV073lTUUOHcFDYj60YPOhrRuM12EFOU michael@0: rtUX1++irmHDae8cMGkyrVRFe8scpjFq9FpEBQCTvqM0/IZ3u8B7TQrXP9t6xKqLACzYngiCrvTk michael@0: A7OmYSOo9zqCj9IA9zCKCPEwtVEUrmQ9QkRCugeHmOhZ6xDb4fjfnXm4xGDbUWgHy2+/2YWnK5i9 michael@0: RR09C7q70sITWVte0Sy3+fQH5jxG6ev6VQLjQGlEB5xVc1UluZlHmL3Md9DkNot5hZdB0sk0msRU michael@0: um4Tb6X51i/0Yyh2QMlksBbgSdULPEi+pbstTxQlveEVNd8cvhibymAGpCfwMnr5TF8BSd3M5Qe+ michael@0: jz3Wezd4qfsdRv/mAEsqv7d91dnN0LSOW3dB+YOFFD0bRRNLh8Yw3V8H0qxZLPDOxIaY7FvbC0De michael@0: g7czBT/HXH6ag8MGG9KoD11XYzTSu021bRHg+03GNsl5UNdGkSLSu4Rtm/LcpTgfLQq6V78FwRAC michael@0: cv4y5jfoCtbFkQ2xGZuCJ59DN5sTP9VNb90Z2xM0ttVNuGv63H/X3HWLwM7cJDN05u7Xl7o00H23 michael@0: W9E+GnB4QxPiQSUSjcbvNyauHRjrHJr+CL3+IPndTjjTLWblPjAmYwfj/cSeGntj9lfxzP2OCWH7 michael@0: fCGzW07c62y0pt2xGW2Of4inwMkv+NzeMEAZTXPNgbxfohv2JpwjO5HX12oS4+2OE9pkUz5XZ/dk michael@0: tm3v6XI+GauN2W3hpUUAwnCTzrx1k+uBMUBX8i3TnA7l3E4jaGhKGnaykFUyZ5Ogt3YALuKIKfU3 michael@0: gXhOIx6kEgPdqi6LEnbDA30XMefp9KU2N0BNAG8VqxuDuukx1lfTkmKl5DBTgsxx2laSDxCBjXjH michael@0: NEwm9h3wyvPmmoVkbJlBZvVKlnHVXDHkZwQksOlqRqCic1xcJzzXSGWLS1zEEssbDlIYILPfn8HG michael@0: 0ttU77hXYWS13cPZiXrokO9jrmxwjJHh4uTOXi/oXms1p6utXe/QNmu4zl6pBMtg7sojHaljZfxW michael@0: 39/Fd8xyJB/9S4d/QN7dyks/C92qM/ZuLRrOM1chdC9swhsDyDj33cPY4YDujYutDbAd39cXllE6 michael@0: HuaWxpaK2ifvVTjNaKMmgoQJo/dEkPyigEdGkDz4D4wg6VszwdBofLQe6C0TuCfUxOrBvYKyYQTo michael@0: MwEi4QF26wJDYyqHbtJ9kavkbmAvlGZd6VTyGfOAHNm9m4xA8FWTys1Q9q6C2xVB8qWLHn9//vHN michael@0: yTnRYnJx8vY/T76npCw8LmnZqgeH2LJ8n6m976V/u+E2nUjTN3iDbc8NsVzDpCF03ndyEHog9Ner michael@0: 9S1oW5G5r7d16NT9dDsB4run3YK6TWX3Qu74ZbrGxE2faeVpB/opJ9WaX05mgnlkTupYHJqTOPO+ michael@0: OTzRMtqJLW9bOCe9tatOtL+qbwHdEvce2SRrWgE8M0H+skcmpmLGBubZQWn/bz4oMxyrDc0NOiCF michael@0: M+nc5EiXODKoyv//iZSg7GLc27GjOLZ3c1M7Ph5S9tJ5PPudycgQxCv3G3Tn5wr7XKZbqBAErPD0 michael@0: PYWMiNF/+kDVph88UeJynwqL91HZXNlfuGbauf1rgkkGlb3vS3GCEh+zQuNFnbqJA7ZPpwM5fXQa michael@0: lS+cShbQfAdA50Y8FbA3+kusEKcbEcLGUbtkmBxLdNSX9TnIo910sDe0ei72t5WdumWXQrzY3nDe michael@0: quzUPQ65h7qnh6pNcZ9jgTFLc1s9qXhNkPk4U9AFX57zgWfoetsPX28vXxzZwwXkd3ztKBLKJhs4 michael@0: hv3Sycbceamk052YpRxTuh7u1ZyQsG5x5UBln2Db3qZTkrJl/2PyHBjSwHvfHzIzPbyr9wdtTC3r michael@0: HcGUxPCJGtG0nCIejbt9MupOt1FbXSBckPQAIB0VCLAQTEc3OgmiG87yHj7Xu8FpTdfxuidMoSMV michael@0: lCzmcwT3ML5fg1+7OxUSP6g7o2j6c4M2B+olB+Fm34FbjbxQyHaT0J56wwdbXACuye7v/+IB/btp michael@0: jLb74S6/2rZ62VsHyL4sZr5iZlCLROZxBEYG9OaQtDWWSxhBx2toGjq6DNXMDfkCHT/KpsXLtmmD michael@0: Qc7sRHsA1igE/wfVIOdx michael@0: """) michael@0: michael@0: ##file activate.sh michael@0: ACTIVATE_SH = convert(""" michael@0: eJytVVFvokAQfudXTLEPtTlLeo9tvMSmJpq02hSvl7u2wRUG2QR2DSxSe7n/frOACEVNLlceRHa+ michael@0: nfl25pvZDswCnoDPQ4QoTRQsENIEPci4CsBMZBq7CAsuLOYqvmYKTTj3YxnBgiXBudGBjUzBZUJI michael@0: BXEqgCvweIyuCjeG4eF2F5x14bcB9KQiQQWrjSddI1/oQIx6SYYeoFjzWIoIhYI1izlbhJjkKO7D michael@0: M/QEmKfO9O7WeRo/zr4P7pyHwWxkwitcgwpQ5Ej96OX+PmiFwLeVjFUOrNYKaq1Nud3nR2n8nI2m michael@0: k9H0friPTGVsUdptaxGrTEfpNVFEskxpXtUkkCkl1UNF9cgLBkx48J4EXyALuBtAwNYIjF5kcmUU michael@0: abMKmMq1ULoiRbgsDEkTSsKSGFCJ6Z8vY/2xYiSacmtyAfCDdCNTVZoVF8vSTQOoEwSnOrngBkws michael@0: MYGMBMg8/bMBLSYKS7pYEXP0PqT+ZmBT0Xuy+Pplj5yn4aM9nk72JD8/Wi+Gr98sD9eWSMOwkapD michael@0: BbUv91XSvmyVkICt2tmXR4tWmrcUCsjWOpw87YidEC8i0gdTSOFhouJUNxR+4NYBG0MftoCTD9F7 michael@0: 2rTtxG3oPwY1b2HncYwhrlmj6Wq924xtGDWqfdNxap+OYxplEurnMVo9RWks+rH8qKEtx7kZT5zJ michael@0: 4H7oOFclrN6uFe+d+nW2aIUsSgs/42EIPuOhXq+jEo3S6tX6w2ilNkDnIpHCWdEQhFgwj9pkk7FN michael@0: l/y5eQvRSIQ5+TrL05lewxWpt/Lbhes5cJF3mLET1MGhcKCF+40tNWnUulxrpojwDo2sObdje3Bz michael@0: N3QeHqf3D7OjEXMVV8LN3ZlvuzoWHqiUcNKHtwNd0IbvPGKYYM31nPKCgkUILw3KL+Y8l7aO1ArS michael@0: Ad37nIU0fCj5NE5gQCuC5sOSu+UdI2NeXg/lFkQIlFpdWVaWZRfvqGiirC9o6liJ9FXGYrSY9mI1 michael@0: D/Ncozgn13vJvsznr7DnkJWXsyMH7e42ljdJ+aqNDF1bFnKWFLdj31xtaJYK6EXFgqmV/ymD/ROG michael@0: +n8O9H8f5vsGOWXsL1+1k3g= michael@0: """) michael@0: michael@0: ##file activate.fish michael@0: ACTIVATE_FISH = convert(""" michael@0: eJydVW2P2jgQ/s6vmAZQoVpA9/WkqqJaTou0u6x2uZVOVWWZZEKsS+yc7UDpr+84bziQbauLxEvs michael@0: eXnsZ56ZIWwTYSAWKUJWGAs7hMJgBEdhEwiMKnSIsBNywUMrDtziPBYmCeBDrFUG7v8HmCTW5n8u michael@0: Fu7NJJim81Bl08EQTqqAkEupLOhCgrAQCY2hTU+DQVxIiqgkRNiEBphFEKy+kd1BaFvwFOUBuIxA michael@0: oy20BKtAKp3xFMo0QNtCK5mhtMEA6BmSpUELKo38TThwLfguRVNaiRgs0llnEoIR29zfstf18/bv michael@0: 5T17Wm7vAiiN3ONCzfbfwC3DtWXXDqHfAGX0q6z/bO82j3ebh1VwnbrduwTQbvwcRtesAfMGor/W michael@0: L3fs6Xnz8LRlm9fV8/P61sM0LDNwCZjl9gSpCokJRzpryGQ5t8kNGFUt51QjOZGu0Mj35FlYlXEr michael@0: yC09EVOp4lEXfF84Lz1qbhBsgl59vDedXI3rTV03xipduSgt9kLytI3XmBp3aV6MPoMQGNUU62T6 michael@0: uQdeefTy1Hfj10zVHg2pq8fXDoHBiOv94csfXwN49xECqWREy7pwukKfvxdMY2j23vXDPuuxxeE+ michael@0: JOdCOhxCE3N44B1ZeSLuZh8Mmkr2wEPAmPfKWHA2uxIRjEopdbQYjDz3BWOf14/scfmwoki1eQvX michael@0: ExBdF60Mqh+Y/QcX4uiH4Amwzx79KOVFtbL63sXJbtcvy8/3q5rupmO5CnE91wBviQAhjUUegYpL michael@0: vVEbpLt2/W+PklRgq5Ku6mp+rpMhhCo/lXthQTxJ2ysO4Ka0ad97S7VT/n6YXus6fzk3fLnBZW5C michael@0: KDC6gSO62QDqgFqLCCtPmjegjnLeAdArtSE8VYGbAJ/aLb+vnQutFhk768E9uRbSxhCMzdgEveYw michael@0: IZ5ZqFKl6+kz7UR4U+buqQZXu9SIujrAfD7f0FXpozB4Q0gwp31H9mVTZGGC4b871/wm7lvyDLu1 michael@0: FUyvTj/yvD66k3UPTs08x1AQQaGziOl0S1qRkPG9COtBTSTWM9NzQ4R64B+Px/l3tDzCgxv5C6Ni michael@0: e+QaF9xFWrxx0V/G5uvYQOdiZzvYpQUVQSIsTr1TTghI33GnPbTA7/GCqcE3oE3GZurq4HeQXQD6 michael@0: 32XS1ITj/qLjN72ob0hc5C9bzw8MhfmL michael@0: """) michael@0: michael@0: ##file activate.csh michael@0: ACTIVATE_CSH = convert(""" michael@0: eJx9VG1P2zAQ/u5fcYQKNgTNPtN1WxlIQ4KCUEGaxuQ6yYVYSuzKdhqVX7+zk3bpy5YPUXL3PPfc michael@0: ne98DLNCWshliVDV1kGCUFvMoJGugMjq2qQIiVSxSJ1cCofD1BYRnOVGV0CfZ0N2DD91DalQSjsw michael@0: tQLpIJMGU1euvPe7QeJlkKzgWixlhnAt4aoUVsLnLBiy5NtbJWQ5THX1ZciYKKWwkOFaE04dUm6D michael@0: r/zh7pq/3D7Nnid3/HEy+wFHY/gEJydg0aFaQrBFgz1c5DG1IhTs+UZgsBC2GMFBlaeH+8dZXwcW michael@0: VPvCjXdlAvCfQsE7al0+07XjZvrSCUevR5dnkVeKlFYZmUztG4BdzL2u9KyLVabTU0bdfg7a0hgs michael@0: cSmUg6UwUiQl2iHrcbcVGNvPCiLOe7+cRwG13z9qRGgx2z6DHjfm/Op2yqeT+xvOLzs0PTKHDz2V michael@0: tkckFHoQfQRXoGJAj9el0FyJCmEMhzgMS4sB7KPOE2ExoLcSieYwDvR+cP8cg11gKkVJc2wRcm1g michael@0: QhYFlXiTaTfO2ki0fQoiFM4tLuO4aZrhOzqR4dIPcWx17hphMBY+Srwh7RTyN83XOWkcSPh1Pg/k michael@0: TXX/jbJTbMtUmcxZ+/bbqOsy82suFQg/BhdSOTRhMNBHlUarCpU7JzBhmkKmRejKOQzayQe6MWoa michael@0: n1wqWmuh6LZAaHxcdeqIlVLhIBJdO9/kbl0It2oEXQj+eGjJOuvOIR/YGRqvFhttUB2XTvLXYN2H michael@0: 37CBdbW2W7j2r2+VsCn0doVWcFG1/4y1VwBjfwAyoZhD michael@0: """) michael@0: michael@0: ##file activate.bat michael@0: ACTIVATE_BAT = convert(""" michael@0: eJx9UdEKgjAUfW6wfxjiIH+hEDKUFHSKLCMI7kNOEkIf9P9pTJ3OLJ/03HPPPed4Es9XS9qqwqgT michael@0: PbGKKOdXL4aAFS7A4gvAwgijuiKlqOpGlATS2NeMLE+TjJM9RkQ+SmqAXLrBo1LLIeLdiWlD6jZt michael@0: r7VNubWkndkXaxg5GO3UaOOKS6drO3luDDiO5my3iA0YAKGzPRV1ack8cOdhysI0CYzIPzjSiH5X michael@0: 0QcvC8Lfaj0emsVKYF2rhL5L3fCkVjV76kShi59NHwDniAHzkgDgqBcwOgTMx+gDQQqXCw== michael@0: """) michael@0: michael@0: ##file deactivate.bat michael@0: DEACTIVATE_BAT = convert(""" michael@0: eJxzSE3OyFfIT0vj4ipOLVEI8wwKCXX0iXf1C7Pl4spMU0hJTcvMS01RiPf3cYmHyQYE+fsGhCho michael@0: cCkAAUibEkTEVhWLMlUlLk6QGixStlyaeCyJDPHw9/Pw93VFsQguim4ZXAJoIUw5DhX47XUM8UCx michael@0: EchHtwsohN1bILUgw61c/Vy4AJYPYm4= michael@0: """) michael@0: michael@0: ##file activate.ps1 michael@0: ACTIVATE_PS = convert(""" michael@0: eJylWdmS40Z2fVeE/oHT6rCloNUEAXDThB6wAyQAEjsB29GBjdgXYiWgmC/zgz/Jv+AEWNVd3S2N michael@0: xuOKYEUxM+/Jmzfvcm7W//zXf/+wUMOoXtyi1F9kbd0sHH/hFc2iLtrK9b3FrSqyxaVQwr8uhqJd michael@0: uHaeg9mqzRdR8/13Pyy8qPLdJh0+LMhi0QCoXxYfFh9WtttEnd34H8p6/f1300KauwrULws39e18 michael@0: 0ZaLNm9rgN/ZVf3h++/e124Vlc0vKsspHy+Yyi5+XbzPhijvCtduoiL/kA1ukWV27n0o7Sb8LIFj michael@0: CvWR5GQgUJdp1Pw8TS9+rPy6SDv/+e3d+0+4qw8f3v20+PliV37efEYBAB9FTKC+RHn/Cfxn3rdv michael@0: 00Fube5O+iyCtHDs9BfPfz3q4sfFv9d91Ljhfy7ei0VO+nVTtdOkv/jpt0l2AX6iG1jXgKnnDuD4 michael@0: ke2k/i8fzzz5UedkVcP4pwF+Wvz2FJl+3vt598urXf5Y6LNA5WcFOP7r0sW7b9a+W/xcu0Xpv5zk michael@0: Kfq3P9Dz9di/fCxS72MXVU1rpx9L4Bxl85Wmn5a+zP76Zuh3pL9ROWr87PN+//GHIl+oOtvn9XSU michael@0: qH+p0gQBFnx1uV+JLH5O5zv+PXW+WepXVVHZT0+oQezkIATcIm+ivPV/z5J/+cYj3ir4w0Lx09vC michael@0: e5n/y5/Y5LPPfdrqb88ga/PabxZRVfmp39l588m/6u+/e+OpP+dF7n1WZpJ9//Z4v372fDDz9eHB michael@0: 7Juvs/BLMHzrxL9+9twXpJfhd1/DrpQ5Euu/vlss3wp9HXC/54C/Ld69m6zwdx3tC0d8daSv0V8B michael@0: n4b9YYF53sJelJV/ix6LZspw/sJtqyl5LJ5r/23htA1Imfm/gt9R7dqVB1LjhydAX4Gb+zksQF59 michael@0: 9+P7H//U+376afFuvh2/T6P85Xr/5c8C6OXyFY4BGuN+EE0+GeR201b+wkkLN5mmBY5TfMw8ngqL michael@0: CztXxCSXKMCYrRIElWkEJlEPYsSOeKBVZCAQTKBhApMwRFQzmCThE0YQu2CdEhgjbgmk9GluHpfR michael@0: /hhwJCZhGI5jt5FsAkOrObVyE6g2y1snyhMGFlDY1x+BoHpCMulTj5JYWNAYJmnKpvLxXgmQ8az1 michael@0: 4fUGxxcitMbbhDFcsiAItg04E+OSBIHTUYD1HI4FHH4kMREPknuYRMyhh3AARWMkfhCketqD1CWJ michael@0: mTCo/nhUScoQcInB1hpFhIKoIXLo5jLpwFCgsnLCx1QlEMlz/iFEGqzH3vWYcpRcThgWnEKm0QcS michael@0: rA8ek2a2IYYeowUanOZOlrbWSJUC4c7y2EMI3uJPMnMF/SSXdk6E495VLhzkWHps0rOhKwqk+xBI michael@0: DhJirhdUCTamMfXz2Hy303hM4DFJ8QL21BcPBULR+gcdYxoeiDqOFSqpi5B5PUISfGg46gFZBPo4 michael@0: jdh8lueaWuVSMTURfbAUnLINr/QYuuYoMQV6l1aWxuZVTjlaLC14UzqZ+ziTGDzJzhiYoPLrt3uI michael@0: tXkVR47kAo09lo5BD76CH51cTt1snVpMOttLhY93yxChCQPI4OBecS7++h4p4Bdn4H97bJongtPk michael@0: s9gQnXku1vzsjjmX4/o4YUDkXkjHwDg5FXozU0fW4y5kyeYW0uJWlh536BKr0kMGjtzTkng6Ep62 michael@0: uTWnQtiIqKnEsx7e1hLtzlXs7Upw9TwEnp0t9yzCGgUJIZConx9OHJArLkRYW0dW42G9OeR5Nzwk michael@0: yk1mX7du5RGHT7dka7N3AznmSif7y6tuKe2N1Al/1TUPRqH6E2GLVc27h9IptMLkCKQYRqPQJgzV michael@0: 2m6WLsSipS3v3b1/WmXEYY1meLEVIU/arOGVkyie7ZsH05ZKpjFW4cpY0YkjySpSExNG2TS8nnJx michael@0: nrQmWh2WY3cP1eISP9wbaVK35ZXc60yC3VN/j9n7UFoK6zvjSTE2+Pvz6Mx322rnftfP8Y0XKIdv michael@0: Qd7AfK0nexBTMqRiErvCMa3Hegpfjdh58glW2oNMsKeAX8x6YJLZs9K8/ozjJkWL+JmECMvhQ54x michael@0: 9rsTHwcoGrDi6Y4I+H7yY4/rJVPAbYymUH7C2D3uiUS3KQ1nrCAUkE1dJMneDQIJMQQx5SONxoEO michael@0: OEn1/Ig1eBBUeEDRuOT2WGGGE4bNypBLFh2PeIg3bEbg44PHiqNDbGIQm50LW6MJU62JHCGBrmc9 michael@0: 2F7WBJrrj1ssnTAK4sxwRgh5LLblhwNAclv3Gd+jC/etCfyfR8TMhcWQz8TBIbG8IIyAQ81w2n/C michael@0: mHWAwRzxd3WoBY7BZnsqGOWrOCKwGkMMNfO0Kci/joZgEocLjNnzgcmdehPHJY0FudXgsr+v44TB michael@0: I3jnMGnsK5veAhgi9iXGifkHMOC09Rh9cAw9sQ0asl6wKMk8mpzFYaaDSgG4F0wisQDDBRpjCINg michael@0: FIxhlhQ31xdSkkk6odXZFpTYOQpOOgw9ugM2cDQ+2MYa7JsEirGBrOuxsQy5nPMRdYjsTJ/j1iNw michael@0: FeSt1jY2+dd5yx1/pzZMOQXUIDcXeAzR7QlDRM8AMkUldXOmGmvYXPABjxqkYKO7VAY6JRU7kpXr michael@0: +Epu2BU3qFFXClFi27784LrDZsJwbNlDw0JzhZ6M0SMXE4iBHehCpHVkrQhpTFn2dsvsZYkiPEEB michael@0: GSEAwdiur9LS1U6P2U9JhGp4hnFpJo4FfkdJHcwV6Q5dV1Q9uNeeu7rV8PAjwdFg9RLtroifOr0k michael@0: uOiRTo/obNPhQIf42Fr4mtThWoSjitEdAmFW66UCe8WFjPk1YVNpL9srFbond7jrLg8tqAasIMpy michael@0: zkH0SY/6zVAwJrEc14zt14YRXdY+fcJ4qOd2XKB0/Kghw1ovd11t2o+zjt+txndo1ZDZ2T+uMVHT michael@0: VSXhedBAHoJIID9xm6wPQI3cXY+HR7vxtrJuCKh6kbXaW5KkVeJsdsjqsYsOwYSh0w5sMbu7LF8J michael@0: 5T7U6LJdiTx+ca7RKlulGgS5Z1JSU2Llt32cHFipkaurtBrvNX5UtvNZjkufZ/r1/XyLl6yOpytL michael@0: Km8Fn+y4wkhlqZP5db0rooqy7xdL4wxzFVTX+6HaxuQJK5E5B1neSSovZ9ALB8091dDbbjVxhWNY michael@0: Ve5hn1VnI9OF0wpvaRm7SZuC1IRczwC7GnkhPt3muHV1YxUJfo+uh1sYnJy+vI0ZwuPV2uqWJYUH michael@0: bmBsi1zmFSxHrqwA+WIzLrHkwW4r+bad7xbOzJCnKIa3S3YvrzEBK1Dc0emzJW+SqysQfdEDorQG michael@0: 9ZJlbQzEHQV8naPaF440YXzJk/7vHGK2xwuP+Gc5xITxyiP+WQ4x18oXHjFzCBy9kir1EFTAm0Zq michael@0: LYwS8MpiGhtfxiBRDXpxDWxk9g9Q2fzPPAhS6VFDAc/aiNGatUkPtZIStZFQ1qD0IlJa/5ZPAi5J michael@0: ySp1ETDomZMnvgiysZSBfMikrSDte/K5lqV6iwC5q7YN9I1dBZXUytDJNqU74MJsUyNNLAPopWK3 michael@0: tzmLkCiDyl7WQnj9sm7Kd5kzgpoccdNeMw/6zPVB3pUwMgi4C7hj4AMFAf4G27oXH8NNT9zll/sK michael@0: S6wVlQwazjxWKWy20ZzXb9ne8ngGalPBWSUSj9xkc1drsXkZ8oOyvYT3e0rnYsGwx85xZB9wKeKg michael@0: cJKZnamYwiaMymZvzk6wtDUkxmdUg0mPad0YHtvzpjEfp2iMxvORhnx0kCVLf5Qa43WJsVoyfEyI michael@0: pzmf8ruM6xBr7dnBgzyxpqXuUPYaKahOaz1LrxNkS/Q3Ae5AC+xl6NbxAqXXlzghZBZHmOrM6Y6Y michael@0: ctAkltwlF7SKEsShjVh7QHuxMU0a08/eiu3x3M+07OijMcKFFltByXrpk8w+JNnZpnp3CfgjV1Ax michael@0: gUYCnWwYow42I5wHCcTzLXK0hMZN2DrPM/zCSqe9jRSlJnr70BPE4+zrwbk/xVIDHy2FAQyHoomT michael@0: Tt5jiM68nBQut35Y0qLclLiQrutxt/c0OlSqXAC8VrxW97lGoRWzhOnifE2zbF05W4xuyhg7JTUL michael@0: aqJ7SWDywhjlal0b+NLTpERBgnPW0+Nw99X2Ws72gOL27iER9jgzj7Uu09JaZ3n+hmCjjvZpjNst michael@0: vOWWTbuLrg+/1ltX8WpPauEDEvcunIgTxuMEHweWKCx2KQ9DU/UKdO/3za4Szm2iHYL+ss9AAttm michael@0: gZHq2pkUXFbV+FiJCKrpBms18zH75vax5jSo7FNunrVWY3Chvd8KKnHdaTt/6ealwaA1x17yTlft michael@0: 8VBle3nAE+7R0MScC3MJofNCCkA9PGKBgGMYEwfB2QO5j8zUqa8F/EkWKCzGQJ5EZ05HTly1B01E michael@0: z813G5BY++RZ2sxbQS8ZveGPJNabp5kXAeoign6Tlt5+L8i5ZquY9+S+KEUHkmYMRFBxRrHnbl2X michael@0: rVemKnG+oB1yd9+zT+4c43jQ0wWmQRR6mTCkY1q3VG05Y120ZzKOMBe6Vy7I5Vz4ygPB3yY4G0FP michael@0: 8RxiMx985YJPXsgRU58EuHj75gygTzejP+W/zKGe78UQN3yOJ1aMQV9hFH+GAfLRsza84WlPLAI/ michael@0: 9G/5JdcHftEfH+Y3/fHUG7/o8bv98dzzy3e8S+XCvgqB+VUf7sH0yDHpONdbRE8tAg9NWOzcTJ7q michael@0: TuAxe/AJ07c1Rs9okJvl1/0G60qvbdDzz5zO0FuPFQIHNp9y9Bd1CufYVx7dB26mAxwa8GMNrN/U michael@0: oGbNZ3EQ7inLzHy5tRg9AXJrN8cB59cCUBeCiVO7zKM0jU0MamhnRThkg/NMmBOGb6StNeD9tDfA michael@0: 7czsAWopDdnGoXUHtA+s/k0vNPkBcxEI13jVd/axp85va3LpwGggXXWw12Gwr/JGAH0b8CPboiZd michael@0: QO1l0mk/UHukud4C+w5uRoNzpCmoW6GbgbMyaQNkga2pQINB18lOXOCJzSWPFOhZcwzdgrsQnne7 michael@0: nvjBi+7cP2BbtBeDOW5uOLGf3z94FasKIguOqJl+8ss/6Kumns4cuWbqq5592TN/RNIbn5Qo6qbi michael@0: O4F0P9txxPAwagqPlftztO8cWBzdN/jz3b7GD6JHYP/Zp4ToAMaA74M+EGSft3hEGMuf8EwjnTk/ michael@0: nz/P7SLipB/ogQ6xNX0fDqNncMCfHqGLCMM0ZzFa+6lPJYQ5p81vW4HkCvidYf6kb+P/oB965g8K michael@0: C6uR0rdjX1DNKc5pOSTquI8uQ6KXxYaKBn+30/09tK4kMpJPgUIQkbENEPbuezNPPje2Um83SgyX michael@0: GTCJb6MnGVIpgncdQg1qz2bvPfxYD9fewCXDomx9S+HQJuX6W3VAL+v5WZMudRQZk9ZdOk6GIUtC michael@0: PqEb/uwSIrtR7/edzqgEdtpEwq7p2J5OQV+RLrmtTvFwFpf03M/VrRyTZ73qVod7v7Jh2Dwe5J25 michael@0: JqFOU2qEu1sP+CRotklediycKfLjeIZzjJQsvKmiGSNQhxuJpKa+hoWUizaE1PuIRGzJqropwgVB michael@0: oo1hr870MZLgnXF5ZIpr6mF0L8aSy2gVnTAuoB4WEd4d5NPVC9TMotYXERKlTcwQ2KiB/C48AEfH michael@0: Qbyq4CN8xTFnTvf/ebOc3isnjD95s0QF0nx9s+y+zMmz782xL0SgEmRpA3x1w1Ff9/74xcxKEPdS michael@0: IEFTz6GgU0+BK/UZ5Gwbl4gZwycxEw+Kqa5QmMkh4OzgzEVPnDAiAOGBFaBW4wkDmj1G4RyElKgj michael@0: NlLCq8zsp085MNh/+R4t1Q8yxoSv8PUpTt7izZwf2BTHZZ3pIZpUIpuLkL1nNL6sYcHqcKm237wp michael@0: T2+RCjgXweXd2Zp7ZM8W6dG5bZsqo0nrJBTx8EC0+CQQdzEGnabTnkzofu1pYkWl4E7XSniECdxy michael@0: vLYavPMcL9LW5SToJFNnos+uqweOHriUZ1ntIYZUonc7ltEQ6oTRtwOHNwez2sVREskHN+bqG3ua michael@0: eaEbJ8XpyO8CeD9QJc8nbLP2C2R3A437ISUNyt5Yd0TbDNcl11/DSsOzdbi/VhCC0KE6v1vqVNkq michael@0: 45ZnG6fiV2NwzInxCNth3BwL0+8814jE6+1W1EeWtpWbSZJOJNYXmWRXa7vLnAljE692eHjZ4y5u michael@0: y1u63De0IzKca7As48Z3XshVF+3XiLNz0JIMh/JOpbiNLlMi672uO0wYzOCZjRxcxj3D+gVenGIE michael@0: MvFUGGXuRps2RzMcgWIRolHXpGUP6sMsQt1hspUBnVKUn/WQj2u6j3SXd9Xz0QtEzoM7qTu5y7gR michael@0: q9gNNsrlEMLdikBt9bFvBnfbUIh6voTw7eDsyTmPKUvF0bHqWLbHe3VRHyRZnNeSGKsB73q66Vsk michael@0: taxWYmwz1tYVFG/vOQhlM0gUkyvIab3nv2caJ1udU1F3pDMty7stubTE4OJqm0i0ECfrJIkLtraC michael@0: HwRWKzlqpfhEIqYH09eT9WrOhQyt8YEoyBlnXtAT37WHIQ03TIuEHbnRxZDdLun0iok9PUC79prU michael@0: m5beZzfQUelEXnhzb/pIROKx3F7qCttYIFGh5dXNzFzID7u8vKykA8Uejf7XXz//S4nKvW//ofS/ michael@0: QastYw== michael@0: """) michael@0: michael@0: ##file distutils-init.py michael@0: DISTUTILS_INIT = convert(""" michael@0: eJytV1uL4zYUfvevOE0ottuMW9q3gVDa3aUMXXbLMlDKMBiNrSTqOJKRlMxkf33PkXyRbGe7Dw2E michael@0: UXTu37lpxLFV2oIyifAncxmOL0xLIfcG+gv80x9VW6maw7o/CANSWWBwFtqeWMPlGY6qPjV8A0bB michael@0: C4eKSTgZ5LRgFeyErMEeOBhbN+Ipgeizhjtnhkn7DdyjuNLPoCS0l/ayQTG0djwZC08cLXozeMss michael@0: aG5EzQ0IScpnWtHSTXuxByV/QCmxE7y+eS0uxWeoheaVVfqSJHiU7Mhhi6gULbOHorshkrEnKxpT michael@0: 0n3A8Y8SMpuwZx6aoix3ouFlmW8gHRSkeSJ2g7hU+kiHLDaQw3bmRDaTGfTnty7gPm0FHbIBg9U9 michael@0: oh1kZzAFLaue2R6htPCtAda2nGlDSUJ4PZBgCJBGVcwKTAMz/vJiLD+Oin5Z5QlvDPdulC6EsiyE michael@0: NFzb7McNTKJzbJqzphx92VKRFY1idenzmq3K0emRcbWBD0ryqc4NZGmKOOOX9Pz5x+/l27tP797c michael@0: f/z0d+4NruGNai8uAM0bfsYaw8itFk8ny41jsfpyO+BWlpqfhcG4yxLdi/0tQqoT4a8Vby382mt8 michael@0: p7XSo7aWGdPBc+b6utaBmCQ7rQKQoWtAuthQCiold2KfJIPTT8xwg9blPumc+YDZC/wYGdAyHpJk michael@0: vUbHbHWAp5No6pK/WhhLEWrFjUwtPEv1Agf8YmnsuXUQYkeZoHm8ogP16gt2uHoxcEMdf2C6pmbw michael@0: hUMsWGhanboh4IzzmsIpWs134jVPqD/c74bZHdY69UKKSn/+KfVhxLgUlToemayLMYQOqfEC61bh michael@0: cbhwaqoGUzIyZRFHPmau5juaWqwRn3mpWmoEA5nhzS5gog/5jbcFQqOZvmBasZtwYlG93k5GEiyw michael@0: buHhMWLjDarEGpMGB2LFs5nIJkhp/nUmZneFaRth++lieJtHepIvKgx6PJqIlD9X2j6pG1i9x3pZ michael@0: 5bHuCPFiirGHeO7McvoXkz786GaKVzC9DSpnOxJdc4xm6NSVq7lNEnKdVlnpu9BNYoKX2Iq3wvgh michael@0: gGEUM66kK6j4NiyoneuPLSwaCWDxczgaolEWpiMyDVDb7dNuLAbriL8ig8mmeju31oNvQdpnvEPC michael@0: 1vAXbWacGRVrGt/uXN/gU0CDDwgooKRrHfTBb1/s9lYZ8ZqOBU0yLvpuP6+K9hLFsvIjeNhBi0KL michael@0: MlOuWRn3FRwx5oHXjl0YImUx0+gLzjGchrgzca026ETmYJzPD+IpuKzNi8AFn048Thd63OdD86M6 michael@0: 84zE8yQm0VqXdbbgvub2pKVnS76icBGdeTHHXTKspUmr4NYo/furFLKiMdQzFjHJNcdAnMhltBJK michael@0: 0/IKX3DVFqvPJ2dLE7bDBkH0l/PJ29074+F0CsGYOxsb7U3myTUncYfXqnLLfa6sJybX4g+hmcjO michael@0: kMRBfA1JellfRRKJcyRpxdS4rIl6FdmQCWjo/o9Qz7yKffoP4JHjOvABcRn4CZIT2RH4jnxmfpVG michael@0: qgLaAvQBNfuO6X0/Ux02nb4FKx3vgP+XnkX0QW9pLy/NsXgdN24dD3LxO2Nwil7Zlc1dqtP3d7/h michael@0: kzp1/+7hGBuY4pk0XD/0Ao/oTe/XGrfyM773aB7iUhgkpy+dwAMalxMP0DrBcsVw/6p25+/hobP9 michael@0: GBknrWExDhLJ1bwt1NcCNblaFbMKCyvmX0PeRaQ= michael@0: """) michael@0: michael@0: ##file distutils.cfg michael@0: DISTUTILS_CFG = convert(""" michael@0: eJxNj00KwkAMhfc9xYNuxe4Ft57AjYiUtDO1wXSmNJnK3N5pdSEEAu8nH6lxHVlRhtDHMPATA4uH michael@0: xJ4EFmGbvfJiicSHFRzUSISMY6hq3GLCRLnIvSTnEefN0FIjw5tF0Hkk9Q5dRunBsVoyFi24aaLg michael@0: 9FDOlL0FPGluf4QjcInLlxd6f6rqkgPu/5nHLg0cXCscXoozRrP51DRT3j9QNl99AP53T2Q= michael@0: """) michael@0: michael@0: ##file activate_this.py michael@0: ACTIVATE_THIS = convert(""" michael@0: eJyNU01v2zAMvetXEB4K21jmDOstQA4dMGCHbeihlyEIDMWmG62yJEiKE//7kXKdpN2KzYBt8euR michael@0: fKSyLPs8wiEo8wh4wqZTGou4V6Hm0wJa1cSiTkJdr8+GsoTRHuCotBayiWqQEYGtMCgfD1KjGYBe michael@0: 5a3p0cRKiAe2NtLADikftnDco0ko/SFEVgEZ8aRC5GLux7i3BpSJ6J1H+i7A2CjiHq9z7JRZuuQq michael@0: siwTIvpxJYCeuWaBpwZdhB+yxy/eWz+ZvVSU8C4E9FFZkyxFsvCT/ZzL8gcz9aXVE14Yyp2M+2W0 michael@0: y7n5mp0qN+avKXvbsyyzUqjeWR8hjGE+2iCE1W1tQ82hsCZN9UzlJr+/e/iab8WfqsmPI6pWeUPd michael@0: FrMsd4H/55poeO9n54COhUs+sZNEzNtg/wanpjpuqHJaxs76HtZryI/K3H7KJ/KDIhqcbJ7kI4ar michael@0: XL+sMgXnX0D+Te2Iy5xdP8yueSlQB/x/ED2BTAtyE3K4SYUN6AMNfbO63f4lBW3bUJPbTL+mjSxS michael@0: PyRfJkZRgj+VbFv+EzHFi5pKwUEepa4JslMnwkowSRCXI+m5XvEOvtuBrxHdhLalG0JofYBok6qj michael@0: YdN2dEngUlbC4PG60M1WEN0piu7Nq7on0mgyyUw3iV1etLo6r/81biWdQ9MWHFaePWZYaq+nmp+t michael@0: s3az+sj7eA0jfgPfeoN1 michael@0: """) michael@0: michael@0: MH_MAGIC = 0xfeedface michael@0: MH_CIGAM = 0xcefaedfe michael@0: MH_MAGIC_64 = 0xfeedfacf michael@0: MH_CIGAM_64 = 0xcffaedfe michael@0: FAT_MAGIC = 0xcafebabe michael@0: BIG_ENDIAN = '>' michael@0: LITTLE_ENDIAN = '<' michael@0: LC_LOAD_DYLIB = 0xc michael@0: maxint = majver == 3 and getattr(sys, 'maxsize') or getattr(sys, 'maxint') michael@0: michael@0: michael@0: class fileview(object): michael@0: """ michael@0: A proxy for file-like objects that exposes a given view of a file. michael@0: Modified from macholib. michael@0: """ michael@0: michael@0: def __init__(self, fileobj, start=0, size=maxint): michael@0: if isinstance(fileobj, fileview): michael@0: self._fileobj = fileobj._fileobj michael@0: else: michael@0: self._fileobj = fileobj michael@0: self._start = start michael@0: self._end = start + size michael@0: self._pos = 0 michael@0: michael@0: def __repr__(self): michael@0: return '' % ( michael@0: self._start, self._end, self._fileobj) michael@0: michael@0: def tell(self): michael@0: return self._pos michael@0: michael@0: def _checkwindow(self, seekto, op): michael@0: if not (self._start <= seekto <= self._end): michael@0: raise IOError("%s to offset %d is outside window [%d, %d]" % ( michael@0: op, seekto, self._start, self._end)) michael@0: michael@0: def seek(self, offset, whence=0): michael@0: seekto = offset michael@0: if whence == os.SEEK_SET: michael@0: seekto += self._start michael@0: elif whence == os.SEEK_CUR: michael@0: seekto += self._start + self._pos michael@0: elif whence == os.SEEK_END: michael@0: seekto += self._end michael@0: else: michael@0: raise IOError("Invalid whence argument to seek: %r" % (whence,)) michael@0: self._checkwindow(seekto, 'seek') michael@0: self._fileobj.seek(seekto) michael@0: self._pos = seekto - self._start michael@0: michael@0: def write(self, bytes): michael@0: here = self._start + self._pos michael@0: self._checkwindow(here, 'write') michael@0: self._checkwindow(here + len(bytes), 'write') michael@0: self._fileobj.seek(here, os.SEEK_SET) michael@0: self._fileobj.write(bytes) michael@0: self._pos += len(bytes) michael@0: michael@0: def read(self, size=maxint): michael@0: assert size >= 0 michael@0: here = self._start + self._pos michael@0: self._checkwindow(here, 'read') michael@0: size = min(size, self._end - here) michael@0: self._fileobj.seek(here, os.SEEK_SET) michael@0: bytes = self._fileobj.read(size) michael@0: self._pos += len(bytes) michael@0: return bytes michael@0: michael@0: michael@0: def read_data(file, endian, num=1): michael@0: """ michael@0: Read a given number of 32-bits unsigned integers from the given file michael@0: with the given endianness. michael@0: """ michael@0: res = struct.unpack(endian + 'L' * num, file.read(num * 4)) michael@0: if len(res) == 1: michael@0: return res[0] michael@0: return res michael@0: michael@0: michael@0: def mach_o_change(path, what, value): michael@0: """ michael@0: Replace a given name (what) in any LC_LOAD_DYLIB command found in michael@0: the given binary with a new name (value), provided it's shorter. michael@0: """ michael@0: michael@0: def do_macho(file, bits, endian): michael@0: # Read Mach-O header (the magic number is assumed read by the caller) michael@0: cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = read_data(file, endian, 6) michael@0: # 64-bits header has one more field. michael@0: if bits == 64: michael@0: read_data(file, endian) michael@0: # The header is followed by ncmds commands michael@0: for n in range(ncmds): michael@0: where = file.tell() michael@0: # Read command header michael@0: cmd, cmdsize = read_data(file, endian, 2) michael@0: if cmd == LC_LOAD_DYLIB: michael@0: # The first data field in LC_LOAD_DYLIB commands is the michael@0: # offset of the name, starting from the beginning of the michael@0: # command. michael@0: name_offset = read_data(file, endian) michael@0: file.seek(where + name_offset, os.SEEK_SET) michael@0: # Read the NUL terminated string michael@0: load = file.read(cmdsize - name_offset).decode() michael@0: load = load[:load.index('\0')] michael@0: # If the string is what is being replaced, overwrite it. michael@0: if load == what: michael@0: file.seek(where + name_offset, os.SEEK_SET) michael@0: file.write(value.encode() + '\0'.encode()) michael@0: # Seek to the next command michael@0: file.seek(where + cmdsize, os.SEEK_SET) michael@0: michael@0: def do_file(file, offset=0, size=maxint): michael@0: file = fileview(file, offset, size) michael@0: # Read magic number michael@0: magic = read_data(file, BIG_ENDIAN) michael@0: if magic == FAT_MAGIC: michael@0: # Fat binaries contain nfat_arch Mach-O binaries michael@0: nfat_arch = read_data(file, BIG_ENDIAN) michael@0: for n in range(nfat_arch): michael@0: # Read arch header michael@0: cputype, cpusubtype, offset, size, align = read_data(file, BIG_ENDIAN, 5) michael@0: do_file(file, offset, size) michael@0: elif magic == MH_MAGIC: michael@0: do_macho(file, 32, BIG_ENDIAN) michael@0: elif magic == MH_CIGAM: michael@0: do_macho(file, 32, LITTLE_ENDIAN) michael@0: elif magic == MH_MAGIC_64: michael@0: do_macho(file, 64, BIG_ENDIAN) michael@0: elif magic == MH_CIGAM_64: michael@0: do_macho(file, 64, LITTLE_ENDIAN) michael@0: michael@0: assert(len(what) >= len(value)) michael@0: do_file(open(path, 'r+b')) michael@0: michael@0: michael@0: if __name__ == '__main__': michael@0: main() michael@0: michael@0: ## TODO: michael@0: ## Copy python.exe.manifest michael@0: ## Monkeypatch distutils.sysconfig