site.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. # -*- coding: utf-8 -*-
  2. """
  3. A simple shim module to fix up things on Python 2 only.
  4. Note: until we setup correctly the paths we can only import built-ins.
  5. """
  6. import sys
  7. def main():
  8. """Patch what needed, and invoke the original site.py"""
  9. here = __file__ # the distutils.install patterns will be injected relative to this site.py, save it here
  10. config = read_pyvenv()
  11. sys.real_prefix = sys.base_prefix = config["base-prefix"]
  12. sys.base_exec_prefix = config["base-exec-prefix"]
  13. sys.base_executable = config["base-executable"]
  14. global_site_package_enabled = config.get("include-system-site-packages", False) == "true"
  15. rewrite_standard_library_sys_path()
  16. disable_user_site_package()
  17. load_host_site(here)
  18. if global_site_package_enabled:
  19. add_global_site_package()
  20. rewrite_getsitepackages(here)
  21. def load_host_site(here):
  22. """trigger reload of site.py - now it will use the standard library instance that will take care of init"""
  23. # we have a duality here, we generate the platform and pure library path based on what distutils.install specifies
  24. # because this is what pip will be using; the host site.py though may contain it's own pattern for where the
  25. # platform and pure library paths should exist
  26. # notably on Ubuntu there's a patch for getsitepackages to point to
  27. # - prefix + local/lib/pythonx.y/dist-packages
  28. # - prefix + lib/pythonx.y/dist-packages
  29. # while distutils.install.cmd still points both of these to
  30. # - prefix + lib/python2.7/site-packages
  31. # to facilitate when the two match, or not we first reload the site.py, now triggering the import of host site.py,
  32. # as this will ensure that initialization code within host site.py runs
  33. reload(sys.modules["site"]) # noqa # call system site.py to setup import libraries
  34. # and then if the distutils site packages are not on the sys.path we add them via add_site_dir; note we must add
  35. # them by invoking add_site_dir to trigger the processing of pth files
  36. add_site_dir = sys.modules["site"].addsitedir
  37. for path in get_site_packages_dirs(here):
  38. add_site_dir(path)
  39. def get_site_packages_dirs(here):
  40. import json
  41. import os
  42. site_packages = r"""
  43. ["..\\site-packages"]
  44. """
  45. for path in json.loads(site_packages):
  46. yield os.path.abspath(os.path.join(here, path.encode("utf-8")))
  47. sep = "\\" if sys.platform == "win32" else "/" # no os module here yet - poor mans version
  48. def read_pyvenv():
  49. """read pyvenv.cfg"""
  50. config_file = "{}{}pyvenv.cfg".format(sys.prefix, sep)
  51. with open(config_file) as file_handler:
  52. lines = file_handler.readlines()
  53. config = {}
  54. for line in lines:
  55. try:
  56. split_at = line.index("=")
  57. except ValueError:
  58. continue # ignore bad/empty lines
  59. else:
  60. config[line[:split_at].strip()] = line[split_at + 1 :].strip()
  61. return config
  62. def rewrite_standard_library_sys_path():
  63. """Once this site file is loaded the standard library paths have already been set, fix them up"""
  64. exe, prefix, exec_prefix = get_exe_prefixes(base=False)
  65. base_exe, base_prefix, base_exec = get_exe_prefixes(base=True)
  66. exe_dir = exe[: exe.rfind(sep)]
  67. for at, path in enumerate(sys.path):
  68. path = abs_path(path) # replace old sys prefix path starts with new
  69. skip_rewrite = path == exe_dir # don't fix the current executable location, notably on Windows this gets added
  70. skip_rewrite = skip_rewrite
  71. if not skip_rewrite:
  72. sys.path[at] = map_path(path, base_exe, exe_dir, exec_prefix, base_prefix, prefix, base_exec)
  73. # the rewrite above may have changed elements from PYTHONPATH, revert these if on
  74. if sys.flags.ignore_environment:
  75. return
  76. import os
  77. python_paths = []
  78. if "PYTHONPATH" in os.environ and os.environ["PYTHONPATH"]:
  79. for path in os.environ["PYTHONPATH"].split(os.pathsep):
  80. if path not in python_paths:
  81. python_paths.append(path)
  82. sys.path[: len(python_paths)] = python_paths
  83. def get_exe_prefixes(base=False):
  84. return tuple(abs_path(getattr(sys, ("base_" if base else "") + i)) for i in ("executable", "prefix", "exec_prefix"))
  85. def abs_path(value):
  86. values, keep = value.split(sep), []
  87. at = len(values) - 1
  88. while at >= 0:
  89. if values[at] == "..":
  90. at -= 1
  91. else:
  92. keep.append(values[at])
  93. at -= 1
  94. return sep.join(keep[::-1])
  95. def map_path(path, base_executable, exe_dir, exec_prefix, base_prefix, prefix, base_exec_prefix):
  96. if path_starts_with(path, exe_dir):
  97. # content inside the exe folder needs to remap to original executables folder
  98. orig_exe_folder = base_executable[: base_executable.rfind(sep)]
  99. return "{}{}".format(orig_exe_folder, path[len(exe_dir) :])
  100. elif path_starts_with(path, prefix):
  101. return "{}{}".format(base_prefix, path[len(prefix) :])
  102. elif path_starts_with(path, exec_prefix):
  103. return "{}{}".format(base_exec_prefix, path[len(exec_prefix) :])
  104. return path
  105. def path_starts_with(directory, value):
  106. return directory.startswith(value if value[-1] == sep else value + sep)
  107. def disable_user_site_package():
  108. """Flip the switch on enable user site package"""
  109. # sys.flags is a c-extension type, so we cannot monkeypatch it, replace it with a python class to flip it
  110. sys.original_flags = sys.flags
  111. class Flags(object):
  112. def __init__(self):
  113. self.__dict__ = {key: getattr(sys.flags, key) for key in dir(sys.flags) if not key.startswith("_")}
  114. sys.flags = Flags()
  115. sys.flags.no_user_site = 1
  116. def add_global_site_package():
  117. """add the global site package"""
  118. import site
  119. # add user site package
  120. sys.flags = sys.original_flags # restore original
  121. site.ENABLE_USER_SITE = None # reset user site check
  122. # add the global site package to the path - use new prefix and delegate to site.py
  123. orig_prefixes = None
  124. try:
  125. orig_prefixes = site.PREFIXES
  126. site.PREFIXES = [sys.base_prefix, sys.base_exec_prefix]
  127. site.main()
  128. finally:
  129. site.PREFIXES = orig_prefixes + site.PREFIXES
  130. # Debian and it's derivatives patch this function. We undo the damage
  131. def rewrite_getsitepackages(here):
  132. site = sys.modules["site"]
  133. site_package_dirs = get_site_packages_dirs(here)
  134. orig_getsitepackages = site.getsitepackages
  135. def getsitepackages():
  136. sitepackages = orig_getsitepackages()
  137. if sys.prefix not in site.PREFIXES or sys.exec_prefix not in site.PREFIXES:
  138. # Someone messed with the prefixes, so we stop patching
  139. return sitepackages
  140. for path in site_package_dirs:
  141. if path not in sitepackages:
  142. sitepackages.insert(0, path)
  143. return sitepackages
  144. site.getsitepackages = getsitepackages
  145. main()