importer.pxi 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. import copy
  2. import imp
  3. import importlib
  4. import marshal
  5. import os
  6. import re
  7. import sys
  8. import traceback
  9. from os.path import sep as path_sep
  10. import __res as __resource
  11. env_entry_point = 'Y_PYTHON_ENTRY_POINT'
  12. env_source_root = 'Y_PYTHON_SOURCE_ROOT'
  13. executable = sys.executable or 'Y_PYTHON'
  14. sys.modules['run_import_hook'] = __resource
  15. find_py_module = lambda mod: __resource.find('/py_modules/' + mod)
  16. find_py_code = lambda mod: __resource.find('/py_code/' + mod)
  17. Y_PYTHON_SOURCE_ROOT = os.environ.get(env_source_root)
  18. if Y_PYTHON_SOURCE_ROOT is not None:
  19. Y_PYTHON_SOURCE_ROOT = os.path.abspath(os.path.expanduser(Y_PYTHON_SOURCE_ROOT))
  20. os.environ[env_source_root] = Y_PYTHON_SOURCE_ROOT
  21. def file_bytes(path):
  22. with open(path, 'rb') as f:
  23. return f.read()
  24. def iter_keys(prefix):
  25. l = len(prefix)
  26. for idx in xrange(__resource.count()):
  27. key = __resource.key_by_index(idx)
  28. if key.startswith(prefix):
  29. yield key, key[l:]
  30. def iter_py_modules(with_keys=False):
  31. for key, mod in iter_keys('/py_modules/'):
  32. if '/' in mod:
  33. raise Exception('Incorrect py_modules resource: ' + repr(key))
  34. if with_keys:
  35. yield key, mod
  36. else:
  37. yield mod
  38. def iter_prefixes(s):
  39. i = s.find('.')
  40. while i >= 0:
  41. yield s[:i]
  42. i = s.find('.', i + 1)
  43. def resfs_resolve(path):
  44. """
  45. Return the absolute path of a root-relative path if it exists.
  46. """
  47. if Y_PYTHON_SOURCE_ROOT:
  48. abspath = os.path.join(Y_PYTHON_SOURCE_ROOT, path)
  49. if os.path.exists(abspath):
  50. return abspath
  51. def resfs_src(key, resfs_file=False):
  52. """
  53. Return the root-relative file path of a resource key.
  54. """
  55. if resfs_file:
  56. key = 'resfs/file/' + key
  57. return __resource.find('resfs/src/' + key)
  58. def resfs_read(path, builtin=None):
  59. """
  60. Return the bytes of the resource file at path, or None.
  61. If builtin is True, do not look for it on the filesystem.
  62. If builtin is False, do not look in the builtin resources.
  63. """
  64. if builtin is not True:
  65. arcpath = resfs_src(path, resfs_file=True)
  66. if arcpath:
  67. fspath = resfs_resolve(arcpath)
  68. if fspath:
  69. return file_bytes(fspath)
  70. if builtin is not False:
  71. return __resource.find('resfs/file/' + path)
  72. def resfs_files(prefix=''):
  73. """
  74. List builtin resource file paths.
  75. """
  76. return [key[11:] for key, _ in iter_keys('resfs/file/' + prefix)]
  77. class ResourceImporter(object):
  78. """ A meta_path importer that loads code from built-in resources.
  79. """
  80. def __init__(self):
  81. self.memory = set(iter_py_modules()) # Set of importable module names.
  82. self.source_map = {} # Map from file names to module names.
  83. self._source_name = {} # Map from original to altered module names.
  84. self._package_prefix = ''
  85. self._before_import_callback = None
  86. self._after_import_callback = None
  87. for p in list(self.memory) + list(sys.builtin_module_names):
  88. for pp in iter_prefixes(p):
  89. k = pp + '.__init__'
  90. if k not in self.memory:
  91. self.memory.add(k)
  92. def for_package(self, name):
  93. importer = copy.copy(self)
  94. importer._package_prefix = name + '.'
  95. return importer
  96. def set_callbacks(self, before_import=None, after_import=None):
  97. """Callable[[module], None]"""
  98. self._before_import_callback = before_import
  99. self._after_import_callback = after_import
  100. # PEP-302 finder.
  101. def find_module(self, fullname, path=None):
  102. try:
  103. self.is_package(fullname)
  104. except ImportError:
  105. return None
  106. return self
  107. # PEP-302 extension 1 of 3: data loader.
  108. def get_data(self, path):
  109. abspath = resfs_resolve(path)
  110. if abspath:
  111. return file_bytes(abspath)
  112. path = path.replace('\\', '/')
  113. data = resfs_read(path, builtin=True)
  114. if data is None:
  115. raise IOError(path) # Y_PYTHON_ENTRY_POINT=:resource_files
  116. return data
  117. # PEP-302 extension 2 of 3: get __file__ without importing.
  118. def get_filename(self, fullname):
  119. modname = fullname
  120. if self.is_package(fullname):
  121. fullname += '.__init__'
  122. return resfs_src('/py_modules/' + fullname) or modname
  123. # PEP-302 extension 3 of 3: packaging introspection.
  124. # Used by `linecache` (while printing tracebacks) unless module filename
  125. # exists on the filesystem.
  126. def get_source(self, fullname):
  127. fullname = self._source_name.get(fullname, fullname)
  128. if self.is_package(fullname):
  129. fullname += '.__init__'
  130. abspath = resfs_resolve(self.get_filename(fullname))
  131. if abspath:
  132. return file_bytes(abspath)
  133. return find_py_module(fullname)
  134. def get_code(self, fullname):
  135. modname = fullname
  136. if self.is_package(fullname):
  137. fullname += '.__init__'
  138. abspath = resfs_resolve(self.get_filename(fullname))
  139. if abspath:
  140. data = file_bytes(abspath)
  141. return compile(data, abspath, 'exec', dont_inherit=True)
  142. pyc = find_py_code(fullname)
  143. if pyc:
  144. return marshal.loads(pyc)
  145. else:
  146. # This covers packages with no __init__.py in resources.
  147. return compile('', modname, 'exec', dont_inherit=True)
  148. def is_package(self, fullname):
  149. if fullname in self.memory:
  150. return False
  151. if fullname + '.__init__' in self.memory:
  152. return True
  153. raise ImportError(fullname)
  154. # Extension for contrib/python/coverage.
  155. def file_source(self, filename):
  156. """
  157. Return the key of the module source by its resource path.
  158. """
  159. if not self.source_map:
  160. for key, mod in iter_py_modules(with_keys=True):
  161. path = self.get_filename(mod)
  162. self.source_map[path] = key
  163. if filename in self.source_map:
  164. return self.source_map[filename]
  165. if resfs_read(filename, builtin=True) is not None:
  166. return 'resfs/file/' + filename
  167. return ''
  168. # Extension for pkgutil.iter_modules.
  169. def iter_modules(self, prefix=''):
  170. rx = re.compile(re.escape(self._package_prefix) + r'([^.]+)(\.__init__)?$')
  171. for p in self.memory:
  172. m = rx.match(p)
  173. if m:
  174. yield prefix + m.group(1), m.group(2) is not None
  175. # PEP-302 loader.
  176. def load_module(self, mod_name, fix_name=None):
  177. code = self.get_code(mod_name)
  178. is_package = self.is_package(mod_name)
  179. source_name = self._source_name
  180. mod = imp.new_module(mod_name)
  181. mod.__loader__ = self
  182. mod.__file__ = code.co_filename
  183. if is_package:
  184. mod.__path__ = [executable + path_sep + mod_name.replace('.', path_sep)]
  185. mod.__package__ = mod_name
  186. else:
  187. mod.__package__ = mod_name.rpartition('.')[0]
  188. if fix_name:
  189. mod.__name__ = fix_name
  190. self._source_name = dict(source_name, **{fix_name: mod_name})
  191. old_mod = sys.modules.get(mod_name, None)
  192. sys.modules[mod_name] = mod
  193. # __name__ and __file__ could be overwritten after execution
  194. # So these two things are needed if wee want to be consistent at some point
  195. initial_modname = mod.__name__
  196. initial_filename = mod.__file__
  197. if self._before_import_callback:
  198. self._before_import_callback(initial_modname, initial_filename)
  199. try:
  200. exec code in mod.__dict__
  201. old_mod = sys.modules[mod_name]
  202. finally:
  203. sys.modules[mod_name] = old_mod
  204. # "Zero-cost". Just in case import error occurs
  205. if self._after_import_callback:
  206. self._after_import_callback(initial_modname, initial_filename)
  207. # Some hacky modules (e.g. pygments.lexers) replace themselves in
  208. # `sys.modules` with proxies.
  209. return sys.modules[mod_name]
  210. def run_main(self):
  211. entry_point = os.environ.pop(env_entry_point, None)
  212. if entry_point is None:
  213. entry_point = __resource.find('PY_MAIN')
  214. if entry_point is None:
  215. entry_point = '__main__'
  216. if ':' in entry_point:
  217. mod_name, func_name = entry_point.split(':', 1)
  218. if mod_name == '':
  219. mod_name = 'library.python.runtime.entry_points'
  220. mod = importlib.import_module(mod_name)
  221. func = getattr(mod, func_name)
  222. return func()
  223. if entry_point not in self.memory:
  224. raise Exception(entry_point + ' not found')
  225. self.load_module(entry_point, fix_name='__main__')
  226. importer = ResourceImporter()
  227. sys.meta_path.insert(0, importer)
  228. def executable_path_hook(path):
  229. if path == executable:
  230. return importer
  231. if path.startswith(executable + path_sep):
  232. return importer.for_package(path[len(executable + path_sep):].replace(path_sep, '.'))
  233. raise ImportError(path)
  234. if executable not in sys.path:
  235. sys.path.insert(0, executable)
  236. sys.path_hooks.insert(0, executable_path_hook)
  237. sys.path_importer_cache[executable] = importer
  238. # Indicator that modules and resources are built-in rather than on the file system.
  239. sys.is_standalone_binary = True
  240. sys.frozen = True
  241. # Set of names of importable modules.
  242. sys.extra_modules = importer.memory
  243. # Use pure-python implementation of traceback printer.
  244. # Built-in printer (PyTraceBack_Print) does not support custom module loaders
  245. sys.excepthook = traceback.print_exception