aboutsummaryrefslogtreecommitdiffstats
path: root/src/3rdparty/virtualenv/relocate_virtualenv.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/3rdparty/virtualenv/relocate_virtualenv.py')
-rw-r--r--src/3rdparty/virtualenv/relocate_virtualenv.py301
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()