1# policy.py - module policy logic for Mercurial.
2#
3# Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
4#
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.
7
8from __future__ import absolute_import
9
10import os
11import sys
12
13from .pycompat import getattr
14
15# Rules for how modules can be loaded. Values are:
16#
17#    c - require C extensions
18#    rust+c - require Rust and C extensions
19#    rust+c-allow - allow Rust and C extensions with fallback to pure Python
20#                   for each
21#    allow - allow pure Python implementation when C loading fails
22#    cffi - required cffi versions (implemented within pure module)
23#    cffi-allow - allow pure Python implementation if cffi version is missing
24#    py - only load pure Python modules
25#
26# By default, fall back to the pure modules so the in-place build can
27# run without recompiling the C extensions. This will be overridden by
28# __modulepolicy__ generated by setup.py.
29policy = b'allow'
30_packageprefs = {
31    # policy: (versioned package, pure package)
32    b'c': ('cext', None),
33    b'allow': ('cext', 'pure'),
34    b'cffi': ('cffi', None),
35    b'cffi-allow': ('cffi', 'pure'),
36    b'py': (None, 'pure'),
37    # For now, rust policies impact importrust only
38    b'rust+c': ('cext', None),
39    b'rust+c-allow': ('cext', 'pure'),
40}
41
42try:
43    from . import __modulepolicy__
44
45    policy = __modulepolicy__.modulepolicy
46except ImportError:
47    pass
48
49# PyPy doesn't load C extensions.
50#
51# The canonical way to do this is to test platform.python_implementation().
52# But we don't import platform and don't bloat for it here.
53if '__pypy__' in sys.builtin_module_names:
54    policy = b'cffi'
55
56# Environment variable can always force settings.
57if sys.version_info[0] >= 3:
58    if 'HGMODULEPOLICY' in os.environ:
59        policy = os.environ['HGMODULEPOLICY'].encode('utf-8')
60else:
61    policy = os.environ.get('HGMODULEPOLICY', policy)
62
63
64def _importfrom(pkgname, modname):
65    # from .<pkgname> import <modname> (where . is looked through this module)
66    fakelocals = {}
67    pkg = __import__(pkgname, globals(), fakelocals, [modname], level=1)
68    try:
69        fakelocals[modname] = mod = getattr(pkg, modname)
70    except AttributeError:
71        raise ImportError('cannot import name %s' % modname)
72    # force import; fakelocals[modname] may be replaced with the real module
73    getattr(mod, '__doc__', None)
74    return fakelocals[modname]
75
76
77# keep in sync with "version" in C modules
78_cextversions = {
79    ('cext', 'base85'): 1,
80    ('cext', 'bdiff'): 3,
81    ('cext', 'mpatch'): 1,
82    ('cext', 'osutil'): 4,
83    ('cext', 'parsers'): 20,
84}
85
86# map import request to other package or module
87_modredirects = {
88    ('cext', 'charencode'): ('cext', 'parsers'),
89    ('cffi', 'base85'): ('pure', 'base85'),
90    ('cffi', 'charencode'): ('pure', 'charencode'),
91    ('cffi', 'parsers'): ('pure', 'parsers'),
92}
93
94
95def _checkmod(pkgname, modname, mod):
96    expected = _cextversions.get((pkgname, modname))
97    actual = getattr(mod, 'version', None)
98    if actual != expected:
99        raise ImportError(
100            'cannot import module %s.%s '
101            '(expected version: %d, actual: %r)'
102            % (pkgname, modname, expected, actual)
103        )
104
105
106def importmod(modname):
107    """Import module according to policy and check API version"""
108    try:
109        verpkg, purepkg = _packageprefs[policy]
110    except KeyError:
111        raise ImportError('invalid HGMODULEPOLICY %r' % policy)
112    assert verpkg or purepkg
113    if verpkg:
114        pn, mn = _modredirects.get((verpkg, modname), (verpkg, modname))
115        try:
116            mod = _importfrom(pn, mn)
117            if pn == verpkg:
118                _checkmod(pn, mn, mod)
119            return mod
120        except ImportError:
121            if not purepkg:
122                raise
123    pn, mn = _modredirects.get((purepkg, modname), (purepkg, modname))
124    return _importfrom(pn, mn)
125
126
127def _isrustpermissive():
128    """Assuming the policy is a Rust one, tell if it's permissive."""
129    return policy.endswith(b'-allow')
130
131
132def importrust(modname, member=None, default=None):
133    """Import Rust module according to policy and availability.
134
135    If policy isn't a Rust one, this returns `default`.
136
137    If either the module or its member is not available, this returns `default`
138    if policy is permissive and raises `ImportError` if not.
139    """
140    if not policy.startswith(b'rust'):
141        return default
142
143    try:
144        mod = _importfrom('rustext', modname)
145    except ImportError:
146        if _isrustpermissive():
147            return default
148        raise
149    if member is None:
150        return mod
151
152    try:
153        return getattr(mod, member)
154    except AttributeError:
155        if _isrustpermissive():
156            return default
157        raise ImportError("Cannot import name %s" % member)
158