diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py --- a/mercurial/debugcommands.py +++ b/mercurial/debugcommands.py @@ -1278,16 +1278,28 @@ def debuginstall(ui, **opts): fm.write('hgmodules', _("checking installed modules (%s)...\n"), os.path.dirname(pycompat.fsencode(__file__))) - if policy.policy in ('c', 'allow'): + rustandc = policy.policy in ('rust+c', 'rust+c-allow') + rustext = rustandc # for now, that's the only case + cext = policy.policy in ('c', 'allow') or rustandc + nopure = cext or rustext + if nopure: err = None try: - from .cext import ( - base85, - bdiff, - mpatch, - osutil, - ) - dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes + if cext: + from .cext import ( + base85, + bdiff, + mpatch, + osutil, + ) + # quiet pyflakes + dir(bdiff), dir(mpatch), dir(base85), dir(osutil) + if rustext: + from .rustext import ( + ancestor, + dirstate, + ) + dir(ancestor), dir(dirstate) # quiet pyflakes except Exception as inst: err = stringutil.forcebytestr(inst) problems += 1 diff --git a/mercurial/policy.py b/mercurial/policy.py --- a/mercurial/policy.py +++ b/mercurial/policy.py @@ -13,6 +13,9 @@ import sys # Rules for how modules can be loaded. Values are: # # c - require C extensions +# rust+c - require Rust and C extensions +# rust+c-allow - allow Rust and C extensions with fallback to pure Python +# for each # allow - allow pure Python implementation when C loading fails # cffi - required cffi versions (implemented within pure module) # cffi-allow - allow pure Python implementation if cffi version is missing @@ -29,6 +32,9 @@ policy = b'allow' b'cffi': (r'cffi', None), b'cffi-allow': (r'cffi', r'pure'), b'py': (None, r'pure'), + # For now, rust policies impact importrust only + b'rust+c': (r'cext', None), + b'rust+c-allow': (r'cext', r'pure'), } try: @@ -107,3 +113,34 @@ def importmod(modname): raise pn, mn = _modredirects.get((purepkg, modname), (purepkg, modname)) return _importfrom(pn, mn) + +def _isrustpermissive(): + """Assuming the policy is a Rust one, tell if it's permissive.""" + return policy.endswith(b'-allow') + +def importrust(modname, member=None, default=None): + """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 + + try: + mod = _importfrom(r'rustext', modname) + except ImportError: + if _isrustpermissive(): + return default + raise + if member is None: + return mod + + try: + return getattr(mod, member) + except AttributeError: + if _isrustpermissive(): + return default + raise ImportError(r"Cannot import name %s" % member) diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -556,10 +556,17 @@ class hgbuildpy(build_py): if self.distribution.pure: modulepolicy = 'py' elif self.build_lib == '.': - # in-place build should run without rebuilding C extensions - modulepolicy = 'allow' + # in-place build should run without rebuilding C + # and Rust extensions + if hgrustext == 'cpython': + modulepolicy = 'rust+c-allow' + else: + modulepolicy = 'allow' else: - modulepolicy = 'c' + if hgrustext == 'cpython': + modulepolicy = 'rust+c' + else: + modulepolicy = 'c' content = b''.join([ b'# this file is autogenerated by setup.py\n',