policy.py
164 lines
| 5.1 KiB
| text/x-python
|
PythonLexer
/ mercurial / policy.py
timeless
|
r29266 | # policy.py - module policy logic for Mercurial. | ||
# | ||||
# Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com> | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
Matt Harbison
|
r52756 | from __future__ import annotations | ||
timeless
|
r29266 | |||
import os | ||||
import sys | ||||
Matt Harbison
|
r52617 | import typing | ||
if typing.TYPE_CHECKING: | ||||
from typing import ( | ||||
Dict, | ||||
Optional, | ||||
Tuple, | ||||
) | ||||
timeless
|
r29266 | |||
# Rules for how modules can be loaded. Values are: | ||||
# | ||||
# c - require C extensions | ||||
Georges Racinet
|
r42651 | # rust+c - require Rust and C extensions | ||
# rust+c-allow - allow Rust and C extensions with fallback to pure Python | ||||
# for each | ||||
timeless
|
r29266 | # allow - allow pure Python implementation when C loading fails | ||
Maciej Fijalkowski
|
r29490 | # cffi - required cffi versions (implemented within pure module) | ||
# cffi-allow - allow pure Python implementation if cffi version is missing | ||||
timeless
|
r29266 | # py - only load pure Python modules | ||
# | ||||
Yuya Nishihara
|
r32251 | # By default, fall back to the pure modules so the in-place build can | ||
# run without recompiling the C extensions. This will be overridden by | ||||
# __modulepolicy__ generated by setup.py. | ||||
Matt Harbison
|
r52617 | policy: bytes = b'allow' | ||
_packageprefs: "Dict[bytes, Tuple[Optional[str], Optional[str]]]" = { | ||||
Yuya Nishihara
|
r32366 | # policy: (versioned package, pure package) | ||
Augie Fackler
|
r43906 | b'c': ('cext', None), | ||
b'allow': ('cext', 'pure'), | ||||
b'cffi': ('cffi', None), | ||||
b'cffi-allow': ('cffi', 'pure'), | ||||
b'py': (None, 'pure'), | ||||
Georges Racinet
|
r42651 | # For now, rust policies impact importrust only | ||
Augie Fackler
|
r43906 | b'rust+c': ('cext', None), | ||
b'rust+c-allow': ('cext', 'pure'), | ||||
Yuya Nishihara
|
r32366 | } | ||
Maciej Fijalkowski
|
r29490 | |||
timeless
|
r29266 | try: | ||
Matt Harbison
|
r52624 | from . import __modulepolicy__ # pytype: disable=import-error | ||
Augie Fackler
|
r43345 | |||
Matt Harbison
|
r52617 | policy: bytes = __modulepolicy__.modulepolicy | ||
timeless
|
r29266 | except ImportError: | ||
pass | ||||
# PyPy doesn't load C extensions. | ||||
# | ||||
# The canonical way to do this is to test platform.python_implementation(). | ||||
# But we don't import platform and don't bloat for it here. | ||||
Augie Fackler
|
r43906 | if '__pypy__' in sys.builtin_module_names: | ||
Matt Harbison
|
r52617 | policy: bytes = b'cffi' | ||
timeless
|
r29266 | |||
# Environment variable can always force settings. | ||||
r52850 | if os.environ.get('HGMODULEPOLICY'): # ignore None and Empty | |||
Matt Harbison
|
r52617 | policy: bytes = os.environ['HGMODULEPOLICY'].encode('utf-8') | ||
Yuya Nishihara
|
r32366 | |||
Augie Fackler
|
r43345 | |||
Matt Harbison
|
r52617 | def _importfrom(pkgname: str, modname: str): | ||
Yuya Nishihara
|
r32366 | # from .<pkgname> import <modname> (where . is looked through this module) | ||
fakelocals = {} | ||||
pkg = __import__(pkgname, globals(), fakelocals, [modname], level=1) | ||||
try: | ||||
fakelocals[modname] = mod = getattr(pkg, modname) | ||||
except AttributeError: | ||||
Augie Fackler
|
r43906 | raise ImportError('cannot import name %s' % modname) | ||
Yuya Nishihara
|
r32366 | # force import; fakelocals[modname] may be replaced with the real module | ||
Gregory Szorc
|
r43373 | getattr(mod, '__doc__', None) | ||
Yuya Nishihara
|
r32366 | return fakelocals[modname] | ||
Augie Fackler
|
r43345 | |||
Jun Wu
|
r32428 | # keep in sync with "version" in C modules | ||
Matt Harbison
|
r52617 | _cextversions: "Dict[Tuple[str, str], int]" = { | ||
Augie Fackler
|
r43906 | ('cext', 'base85'): 1, | ||
('cext', 'bdiff'): 3, | ||||
('cext', 'mpatch'): 1, | ||||
('cext', 'osutil'): 4, | ||||
Raphaël Gomès
|
r50715 | ('cext', 'parsers'): 21, | ||
Jun Wu
|
r32428 | } | ||
Yuya Nishihara
|
r33755 | # map import request to other package or module | ||
Matt Harbison
|
r52617 | _modredirects: "Dict[Tuple[str, str], Tuple[str, str]]" = { | ||
Augie Fackler
|
r43906 | ('cext', 'charencode'): ('cext', 'parsers'), | ||
('cffi', 'base85'): ('pure', 'base85'), | ||||
('cffi', 'charencode'): ('pure', 'charencode'), | ||||
('cffi', 'parsers'): ('pure', 'parsers'), | ||||
Yuya Nishihara
|
r33755 | } | ||
Augie Fackler
|
r43345 | |||
Matt Harbison
|
r52617 | def _checkmod(pkgname: str, modname: str, mod) -> None: | ||
Yuya Nishihara
|
r32511 | expected = _cextversions.get((pkgname, modname)) | ||
Gregory Szorc
|
r43373 | actual = getattr(mod, 'version', None) | ||
Yuya Nishihara
|
r32366 | if actual != expected: | ||
Augie Fackler
|
r43345 | raise ImportError( | ||
Augie Fackler
|
r43906 | 'cannot import module %s.%s ' | ||
'(expected version: %d, actual: %r)' | ||||
Augie Fackler
|
r43345 | % (pkgname, modname, expected, actual) | ||
) | ||||
Yuya Nishihara
|
r32366 | |||
Matt Harbison
|
r52617 | def importmod(modname: str): | ||
Yuya Nishihara
|
r32366 | """Import module according to policy and check API version""" | ||
try: | ||||
verpkg, purepkg = _packageprefs[policy] | ||||
except KeyError: | ||||
Augie Fackler
|
r43906 | raise ImportError('invalid HGMODULEPOLICY %r' % policy) | ||
Yuya Nishihara
|
r32366 | assert verpkg or purepkg | ||
if verpkg: | ||||
Yuya Nishihara
|
r33755 | pn, mn = _modredirects.get((verpkg, modname), (verpkg, modname)) | ||
Yuya Nishihara
|
r32366 | try: | ||
Yuya Nishihara
|
r33755 | mod = _importfrom(pn, mn) | ||
if pn == verpkg: | ||||
_checkmod(pn, mn, mod) | ||||
Yuya Nishihara
|
r32366 | return mod | ||
except ImportError: | ||||
if not purepkg: | ||||
raise | ||||
Yuya Nishihara
|
r33755 | pn, mn = _modredirects.get((purepkg, modname), (purepkg, modname)) | ||
return _importfrom(pn, mn) | ||||
Georges Racinet
|
r42651 | |||
Augie Fackler
|
r43345 | |||
Matt Harbison
|
r52617 | def _isrustpermissive() -> bool: | ||
Georges Racinet
|
r42651 | """Assuming the policy is a Rust one, tell if it's permissive.""" | ||
return policy.endswith(b'-allow') | ||||
Augie Fackler
|
r43345 | |||
Raphaël Gomès
|
r53299 | def importrust( | ||
modname: str, member: "Optional[str]" = None, default=None, pyo3=False | ||||
): | ||||
Georges Racinet
|
r42651 | """Import Rust module according to policy and availability. | ||
If policy isn't a Rust one, this returns `default`. | ||||
If either the module or its member is not available, this returns `default` | ||||
if policy is permissive and raises `ImportError` if not. | ||||
""" | ||||
if not policy.startswith(b'rust'): | ||||
return default | ||||
Georges Racinet
|
r53303 | dlib_name = "pyo3_rustext" if pyo3 else "rustext" | ||
Raphaël Gomès
|
r53299 | |||
Georges Racinet
|
r42651 | try: | ||
Raphaël Gomès
|
r53299 | mod = _importfrom(dlib_name, modname) | ||
Georges Racinet
|
r42651 | except ImportError: | ||
if _isrustpermissive(): | ||||
return default | ||||
raise | ||||
if member is None: | ||||
return mod | ||||
try: | ||||
return getattr(mod, member) | ||||
except AttributeError: | ||||
if _isrustpermissive(): | ||||
return default | ||||
Augie Fackler
|
r43809 | raise ImportError("Cannot import name %s" % member) | ||