diff options
Diffstat (limited to 'src/3rdparty/virtualenv/relocate_virtualenv.py')
-rw-r--r-- | src/3rdparty/virtualenv/relocate_virtualenv.py | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/src/3rdparty/virtualenv/relocate_virtualenv.py b/src/3rdparty/virtualenv/relocate_virtualenv.py new file mode 100644 index 0000000..3cfedeb --- /dev/null +++ b/src/3rdparty/virtualenv/relocate_virtualenv.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python +"""Makes a python installation relocatable""" + +# Copied from https://github.com/pypa/virtualenv/blob/legacy/virtualenv.py +# Newer virtualenv versions don't provide this functionality anymore + +import os +import sys +import logging +from os.path import join + +logging.basicConfig(stream=sys.stdout, level=logging.INFO) +logger = logging.getLogger() + +VERSION = "{}.{}".format(*sys.version_info) +PY_VERSION = "python{}.{}".format(*sys.version_info) + +IS_PYPY = hasattr(sys, "pypy_version_info") +IS_WIN = sys.platform == "win32" +IS_CYGWIN = sys.platform == "cygwin" +IS_DARWIN = sys.platform == "darwin" +ABI_FLAGS = getattr(sys, "abiflags", "") + +# Relocating the environment: +def make_environment_relocatable(home_dir): + """ + Makes the already-existing environment use relative paths, and takes out + the #!-based environment selection in scripts. + """ + home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) + activate_this = os.path.join(bin_dir, "activate_this.py") + if not os.path.exists(activate_this): + logger.fatal( + "The environment doesn't have a file %s -- please re-run virtualenv " "on this environment to update it", + activate_this, + ) + fixup_scripts(home_dir, bin_dir) + fixup_pth_and_egg_link(home_dir) + # FIXME: need to fix up distutils.cfg + + +OK_ABS_SCRIPTS = [ + "python", + PY_VERSION, + "activate", + "activate.bat", + "activate_this.py", + "activate.fish", + "activate.csh", + "activate.xsh", +] + + +def mkdir(at_path): + if not os.path.exists(at_path): + logger.info("Creating %s", at_path) + os.makedirs(at_path) + else: + logger.info("Directory %s already exists", at_path) + +def fixup_scripts(_, bin_dir): + if IS_WIN: + new_shebang_args = ("{} /c".format(os.path.normcase(os.environ.get("COMSPEC", "cmd.exe"))), "", ".exe") + else: + new_shebang_args = ("/usr/bin/env", VERSION, "") + + # This is what we expect at the top of scripts: + shebang = "#!{}".format( + os.path.normcase(os.path.join(os.path.abspath(bin_dir), "python{}".format(new_shebang_args[2]))) + ) + # This is what we'll put: + new_shebang = "#!{} python{}{}".format(*new_shebang_args) + + for filename in os.listdir(bin_dir): + filename = os.path.join(bin_dir, filename) + if not os.path.isfile(filename): + # ignore child directories, e.g. .svn ones. + continue + with open(filename, "rb") as f: + try: + lines = f.read().decode("utf-8").splitlines() + except UnicodeDecodeError: + # This is probably a binary program instead + # of a script, so just ignore it. + continue + if not lines: + logger.warning("Script %s is an empty file", filename) + continue + + old_shebang = lines[0].strip() + old_shebang = old_shebang[0:2] + os.path.normcase(old_shebang[2:]) + + if not old_shebang.startswith(shebang): + if os.path.basename(filename) in OK_ABS_SCRIPTS: + logger.debug("Cannot make script %s relative", filename) + elif lines[0].strip() == new_shebang: + logger.info("Script %s has already been made relative", filename) + else: + logger.warning( + "Script %s cannot be made relative (it's not a normal script that starts with %s)", + filename, + shebang, + ) + continue + logger.info("Making script %s relative", filename) + #logger.notify("Making script %s relative", filename) + script = relative_script([new_shebang] + lines[1:]) + with open(filename, "wb") as f: + f.write("\n".join(script).encode("utf-8")) + + +def relative_script(lines): + """Return a script that'll work in a relocatable environment.""" + activate = ( + "import os; " + "activate_this=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'activate_this.py'); " + "exec(compile(open(activate_this).read(), activate_this, 'exec'), { '__file__': activate_this}); " + "del os, activate_this" + ) + # Find the last future statement in the script. If we insert the activation + # line before a future statement, Python will raise a SyntaxError. + activate_at = None + for idx, line in reversed(list(enumerate(lines))): + if line.split()[:3] == ["from", "__future__", "import"]: + activate_at = idx + 1 + break + if activate_at is None: + # Activate after the shebang. + activate_at = 1 + return lines[:activate_at] + ["", activate, ""] + lines[activate_at:] + + +def fixup_pth_and_egg_link(home_dir, sys_path=None): + """Makes .pth and .egg-link files use relative paths""" + home_dir = os.path.normcase(os.path.abspath(home_dir)) + if sys_path is None: + sys_path = sys.path + for a_path in sys_path: + if not a_path: + a_path = "." + if not os.path.isdir(a_path): + continue + a_path = os.path.normcase(os.path.abspath(a_path)) + if not a_path.startswith(home_dir): + logger.debug("Skipping system (non-environment) directory %s", a_path) + continue + for filename in os.listdir(a_path): + filename = os.path.join(a_path, filename) + if filename.endswith(".pth"): + if not os.access(filename, os.W_OK): + logger.warning("Cannot write .pth file %s, skipping", filename) + else: + fixup_pth_file(filename) + if filename.endswith(".egg-link"): + if not os.access(filename, os.W_OK): + logger.warning("Cannot write .egg-link file %s, skipping", filename) + else: + fixup_egg_link(filename) + + +def fixup_pth_file(filename): + lines = [] + with open(filename) as f: + prev_lines = f.readlines() + for line in prev_lines: + line = line.strip() + if not line or line.startswith("#") or line.startswith("import ") or os.path.abspath(line) != line: + lines.append(line) + else: + new_value = make_relative_path(filename, line) + if line != new_value: + logger.debug("Rewriting path {} as {} (in {})".format(line, new_value, filename)) + lines.append(new_value) + if lines == prev_lines: + logger.info("No changes to .pth file %s", filename) + return + logger.notify("Making paths in .pth file %s relative", filename) + with open(filename, "w") as f: + f.write("\n".join(lines) + "\n") + + +def fixup_egg_link(filename): + with open(filename) as f: + link = f.readline().strip() + if os.path.abspath(link) != link: + logger.debug("Link in %s already relative", filename) + return + new_link = make_relative_path(filename, link) + logger.notify("Rewriting link {} in {} as {}".format(link, filename, new_link)) + with open(filename, "w") as f: + f.write(new_link) + + +def make_relative_path(source, dest, dest_is_directory=True): + """ + Make a filename relative, where the filename is dest, and it is + being referred to from the filename source. + >>> make_relative_path('/usr/share/something/a-file.pth', + ... '/usr/share/another-place/src/Directory') + '../another-place/src/Directory' + >>> make_relative_path('/usr/share/something/a-file.pth', + ... '/home/user/src/Directory') + '../../../home/user/src/Directory' + >>> make_relative_path('/usr/share/a-file.pth', '/usr/share/') + './' + """ + source = os.path.dirname(source) + if not dest_is_directory: + dest_filename = os.path.basename(dest) + dest = os.path.dirname(dest) + else: + dest_filename = None + dest = os.path.normpath(os.path.abspath(dest)) + source = os.path.normpath(os.path.abspath(source)) + dest_parts = dest.strip(os.path.sep).split(os.path.sep) + source_parts = source.strip(os.path.sep).split(os.path.sep) + while dest_parts and source_parts and dest_parts[0] == source_parts[0]: + dest_parts.pop(0) + source_parts.pop(0) + full_parts = [".."] * len(source_parts) + dest_parts + if not dest_is_directory and dest_filename is not None: + full_parts.append(dest_filename) + if not full_parts: + # Special case for the current directory (otherwise it'd be '') + return "./" + return os.path.sep.join(full_parts) + +def path_locations(home_dir, dry_run=False): + """Return the path locations for the environment (where libraries are, + where scripts go, etc)""" + home_dir = os.path.abspath(home_dir) + lib_dir, inc_dir, bin_dir = None, None, None + # XXX: We'd use distutils.sysconfig.get_python_inc/lib but its + # prefix arg is broken: http://bugs.python.org/issue3386 + if IS_WIN: + # Windows has lots of problems with executables with spaces in + # the name; this function will remove them (using the ~1 + # format): + if not dry_run: + mkdir(home_dir) + if " " in home_dir: + import ctypes + + get_short_path_name = ctypes.windll.kernel32.GetShortPathNameW + size = max(len(home_dir) + 1, 256) + buf = ctypes.create_unicode_buffer(size) + try: + # noinspection PyUnresolvedReferences + u = unicode + except NameError: + u = str + ret = get_short_path_name(u(home_dir), buf, size) + if not ret: + print('Error: the path "{}" has a space in it'.format(home_dir)) + print("We could not determine the short pathname for it.") + print("Exiting.") + sys.exit(3) + home_dir = str(buf.value) + lib_dir = join(home_dir, "Lib") + inc_dir = join(home_dir, "Include") + bin_dir = join(home_dir, "Scripts") + if IS_PYPY: + lib_dir = home_dir + inc_dir = join(home_dir, "include") + bin_dir = join(home_dir, "bin") + elif not IS_WIN: + lib_dir = join(home_dir, "lib", PY_VERSION) + inc_dir = join(home_dir, "include", PY_VERSION + ABI_FLAGS) + bin_dir = join(home_dir, "bin") + return home_dir, lib_dir, inc_dir, bin_dir + +def main(): + args = sys.argv[1:] + if not args: + print("You must provide a DEST_DIR") + sys.exit(2) + if len(args) > 1: + print("There must be only one argument: DEST_DIR (you gave {})".format(" ".join(args))) + sys.exit(2) + + home_dir = args[0] + + if os.path.exists(home_dir) and os.path.isfile(home_dir): + logger.fatal("ERROR: File already exists and is not a directory.") + logger.fatal("Please provide a different path or delete the file.") + sys.exit(3) + + if os.pathsep in home_dir: + logger.fatal("ERROR: target path contains the operating system path separator '{}'".format(os.pathsep)) + logger.fatal("This is not allowed as would make the activation scripts unusable.".format(os.pathsep)) + sys.exit(3) + + if os.environ.get("WORKING_ENV"): + logger.fatal("ERROR: you cannot run virtualenv while in a working env") + logger.fatal("Please deactivate your working env, then re-run this script") + sys.exit(3) + + make_environment_relocatable(home_dir) + +if __name__ == "__main__": + main() |