##// END OF EJS Templates
mercurial: support loading modules from zipimporter...
Gregory Szorc -
r27225:30a20167 default
parent child Browse files
Show More
@@ -1,113 +1,144 b''
1 # __init__.py - Startup and module loading logic for Mercurial.
1 # __init__.py - Startup and module loading logic for Mercurial.
2 #
2 #
3 # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import imp
10 import imp
11 import os
11 import os
12 import sys
12 import sys
13 import zipimport
13
14
14 __all__ = []
15 __all__ = []
15
16
16 # Rules for how modules can be loaded. Values are:
17 # Rules for how modules can be loaded. Values are:
17 #
18 #
18 # c - require C extensions
19 # c - require C extensions
19 # allow - allow pure Python implementation when C loading fails
20 # allow - allow pure Python implementation when C loading fails
20 # py - only load pure Python modules
21 # py - only load pure Python modules
21 modulepolicy = '@MODULELOADPOLICY@'
22 modulepolicy = '@MODULELOADPOLICY@'
22
23
23 # By default, require the C extensions for performance reasons.
24 # By default, require the C extensions for performance reasons.
24 if modulepolicy == '@' 'MODULELOADPOLICY' '@':
25 if modulepolicy == '@' 'MODULELOADPOLICY' '@':
25 modulepolicy = 'c'
26 modulepolicy = 'c'
26
27
27 # PyPy doesn't load C extensions.
28 # PyPy doesn't load C extensions.
28 #
29 #
29 # The canonical way to do this is to test platform.python_implementation().
30 # The canonical way to do this is to test platform.python_implementation().
30 # But we don't import platform and don't bloat for it here.
31 # But we don't import platform and don't bloat for it here.
31 if '__pypy__' in sys.builtin_module_names:
32 if '__pypy__' in sys.builtin_module_names:
32 modulepolicy = 'py'
33 modulepolicy = 'py'
33
34
34 # Environment variable can always force settings.
35 # Environment variable can always force settings.
35 modulepolicy = os.environ.get('HGMODULEPOLICY', modulepolicy)
36 modulepolicy = os.environ.get('HGMODULEPOLICY', modulepolicy)
36
37
37 # Modules that have both Python and C implementations. See also the
38 # Modules that have both Python and C implementations. See also the
38 # set of .py files under mercurial/pure/.
39 # set of .py files under mercurial/pure/.
39 _dualmodules = set([
40 _dualmodules = set([
40 'mercurial.base85',
41 'mercurial.base85',
41 'mercurial.bdiff',
42 'mercurial.bdiff',
42 'mercurial.diffhelpers',
43 'mercurial.diffhelpers',
43 'mercurial.mpatch',
44 'mercurial.mpatch',
44 'mercurial.osutil',
45 'mercurial.osutil',
45 'mercurial.parsers',
46 'mercurial.parsers',
46 ])
47 ])
47
48
48 class hgimporter(object):
49 class hgimporter(object):
49 """Object that conforms to import hook interface defined in PEP-302."""
50 """Object that conforms to import hook interface defined in PEP-302."""
50 def find_module(self, name, path=None):
51 def find_module(self, name, path=None):
51 # We only care about modules that have both C and pure implementations.
52 # We only care about modules that have both C and pure implementations.
52 if name in _dualmodules:
53 if name in _dualmodules:
53 return self
54 return self
54 return None
55 return None
55
56
56 def load_module(self, name):
57 def load_module(self, name):
57 mod = sys.modules.get(name, None)
58 mod = sys.modules.get(name, None)
58 if mod:
59 if mod:
59 return mod
60 return mod
60
61
61 mercurial = sys.modules['mercurial']
62 mercurial = sys.modules['mercurial']
62
63
64 # The zip importer behaves sufficiently differently from the default
65 # importer to warrant its own code path.
66 loader = getattr(mercurial, '__loader__', None)
67 if isinstance(loader, zipimport.zipimporter):
68 def ziploader(*paths):
69 """Obtain a zipimporter for a directory under the main zip."""
70 path = os.path.join(loader.archive, *paths)
71 zl = sys.path_importer_cache.get(path)
72 if not zl:
73 zl = zipimport.zipimporter(path)
74 return zl
75
76 try:
77 if modulepolicy == 'py':
78 raise ImportError()
79
80 zl = ziploader('mercurial')
81 mod = zl.load_module(name)
82 # Unlike imp, ziploader doesn't expose module metadata that
83 # indicates the type of module. So just assume what we found
84 # is OK (even though it could be a pure Python module).
85 except ImportError:
86 if modulepolicy == 'c':
87 raise
88 zl = ziploader('mercurial', 'pure')
89 mod = zl.load_module(name)
90
91 sys.modules[name] = mod
92 return mod
93
63 # Unlike the default importer which searches special locations and
94 # Unlike the default importer which searches special locations and
64 # sys.path, we only look in the directory where "mercurial" was
95 # sys.path, we only look in the directory where "mercurial" was
65 # imported from.
96 # imported from.
66
97
67 # imp.find_module doesn't support submodules (modules with ".").
98 # imp.find_module doesn't support submodules (modules with ".").
68 # Instead you have to pass the parent package's __path__ attribute
99 # Instead you have to pass the parent package's __path__ attribute
69 # as the path argument.
100 # as the path argument.
70 stem = name.split('.')[-1]
101 stem = name.split('.')[-1]
71
102
72 try:
103 try:
73 if modulepolicy == 'py':
104 if modulepolicy == 'py':
74 raise ImportError()
105 raise ImportError()
75
106
76 modinfo = imp.find_module(stem, mercurial.__path__)
107 modinfo = imp.find_module(stem, mercurial.__path__)
77
108
78 # The Mercurial installer used to copy files from
109 # The Mercurial installer used to copy files from
79 # mercurial/pure/*.py to mercurial/*.py. Therefore, it's possible
110 # mercurial/pure/*.py to mercurial/*.py. Therefore, it's possible
80 # for some installations to have .py files under mercurial/*.
111 # for some installations to have .py files under mercurial/*.
81 # Loading Python modules when we expected C versions could result
112 # Loading Python modules when we expected C versions could result
82 # in a) poor performance b) loading a version from a previous
113 # in a) poor performance b) loading a version from a previous
83 # Mercurial version, potentially leading to incompatibility. Either
114 # Mercurial version, potentially leading to incompatibility. Either
84 # scenario is bad. So we verify that modules loaded from
115 # scenario is bad. So we verify that modules loaded from
85 # mercurial/* are C extensions. If the current policy allows the
116 # mercurial/* are C extensions. If the current policy allows the
86 # loading of .py modules, the module will be re-imported from
117 # loading of .py modules, the module will be re-imported from
87 # mercurial/pure/* below.
118 # mercurial/pure/* below.
88 if modinfo[2][2] != imp.C_EXTENSION:
119 if modinfo[2][2] != imp.C_EXTENSION:
89 raise ImportError('.py version of %s found where C '
120 raise ImportError('.py version of %s found where C '
90 'version should exist' % name)
121 'version should exist' % name)
91
122
92 except ImportError:
123 except ImportError:
93 if modulepolicy == 'c':
124 if modulepolicy == 'c':
94 raise
125 raise
95
126
96 # Could not load the C extension and pure Python is allowed. So
127 # Could not load the C extension and pure Python is allowed. So
97 # try to load them.
128 # try to load them.
98 from . import pure
129 from . import pure
99 modinfo = imp.find_module(stem, pure.__path__)
130 modinfo = imp.find_module(stem, pure.__path__)
100 if not modinfo:
131 if not modinfo:
101 raise ImportError('could not find mercurial module %s' %
132 raise ImportError('could not find mercurial module %s' %
102 name)
133 name)
103
134
104 mod = imp.load_module(name, *modinfo)
135 mod = imp.load_module(name, *modinfo)
105 sys.modules[name] = mod
136 sys.modules[name] = mod
106 return mod
137 return mod
107
138
108 # We automagically register our custom importer as a side-effect of loading.
139 # We automagically register our custom importer as a side-effect of loading.
109 # This is necessary to ensure that any entry points are able to import
140 # This is necessary to ensure that any entry points are able to import
110 # mercurial.* modules without having to perform this registration themselves.
141 # mercurial.* modules without having to perform this registration themselves.
111 if not any(isinstance(x, hgimporter) for x in sys.meta_path):
142 if not any(isinstance(x, hgimporter) for x in sys.meta_path):
112 # meta_path is used before any implicit finders and before sys.path.
143 # meta_path is used before any implicit finders and before sys.path.
113 sys.meta_path.insert(0, hgimporter())
144 sys.meta_path.insert(0, hgimporter())
General Comments 0
You need to be logged in to leave comments. Login now