mirror of
https://github.com/facebook/sapling.git
synced 2024-10-17 04:08:01 +03:00
50ddbd96dd
Summary: This is needed for the `py2exe`-like loading of the the python stdlib and mercurial files. The `zipimport` per se works out of the box in Python. The problem is loading native extensions. Since some of them are parts of packages, such as `mercurial.cext.osutil`, and `mercurial/cext/` folder structure does not exist in the embedded case, we're just doing what `py2exe` does: renaming every native extension file to include the full package path, e.g. `mercurial.cext.osutil.pyd` and tweaking the loader to know to try such files. NB: I want to rename `hgdemandimport` to `hgimport` later, since now there are more responsibilities for this package. We can probably also just merge it with `mercurial`, since we don't care about `python3` Reviewed By: quark-zju Differential Revision: D9919806 fbshipit-source-id: 216ae904760311003e7171cb6fbe0bbc4b1abe2b
97 lines
3.8 KiB
Python
97 lines
3.8 KiB
Python
# embeddedimport - an ability to load python modules from a zip file
|
|
#
|
|
# Copyright 2018 Facebook Inc.
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2 or any later version.
|
|
#
|
|
# This module, together some other logic in Mercurial, mimics the approach
|
|
# of `py2exe`, albeit does it simpler. Basically, standard `zipimport` module
|
|
# already knows how to load Python modules from a `.zip` file, but it cannot
|
|
# load the dynamic library extensions from a zip file. This may be a problem
|
|
# when those extensions are a part of a package together with some Python
|
|
# modules. Here we teach Python to load such extensions from files
|
|
# named `path.to.packaged.module[pyd|so]` when the code contains
|
|
# `import path.to.packaged.module`.
|
|
|
|
import imp
|
|
import os
|
|
import sys
|
|
import zipimport
|
|
|
|
|
|
class EmbeddedImporter(zipimport.zipimporter):
|
|
"""ZipImporter with the ability to load .pyd/.so extensions
|
|
|
|
We cannot use separate importers to load from .zip file and to
|
|
load .pyd/.so extensions, because Python caches importers and
|
|
once a package is being imported by some importer, all of the
|
|
modules form this package will be imported by the same one.
|
|
Because `mercurial` is a package, which contains both Python
|
|
and native modules, we need to make sure that the same importer
|
|
can deal with both types.
|
|
Note also that this class is intantiated by the runtime,
|
|
not by us and we don't control what's passed to __init__,
|
|
so we can't make instance-level parametrization."""
|
|
|
|
# directories where native modules should be sought
|
|
nativeextdirs = []
|
|
# File extensions that indicate shared-lib-based Python extension
|
|
suffixes = [s[0] for s in imp.get_suffixes() if s[2] == imp.C_EXTENSION]
|
|
|
|
def find_module(self, fullname, path=None):
|
|
result = zipimport.zipimporter.find_module(self, fullname, path)
|
|
if result:
|
|
return result
|
|
for nativextdir in self.nativeextdirs:
|
|
fullname = os.path.join(nativextdir, fullname)
|
|
for suff in self.suffixes:
|
|
if os.path.exists(fullname + suff):
|
|
return self
|
|
return None
|
|
|
|
def load_module(self, fullname):
|
|
if fullname in sys.modules:
|
|
return sys.modules[fullname]
|
|
try:
|
|
result = zipimport.zipimporter.load_module(self, fullname)
|
|
except zipimport.ZipImportError:
|
|
result = None
|
|
if result:
|
|
return result
|
|
modname = fullname
|
|
for nativextdir in self.nativeextdirs:
|
|
fullname = os.path.join(nativextdir, fullname)
|
|
for suff in self.suffixes:
|
|
if os.path.exists(fullname + suff):
|
|
return imp.load_dynamic(modname, fullname + suff)
|
|
return None
|
|
|
|
|
|
def tryenableembedded():
|
|
"""Enable the embedded-extension-loading hook if we are in the zipfile"""
|
|
dirname = os.path.dirname
|
|
nativeextdirs = []
|
|
for item in sys.path:
|
|
item = os.path.realpath(item)
|
|
while not (item.endswith(".zip") and os.path.isfile(item)):
|
|
dn = dirname(item)
|
|
if dn == item:
|
|
break
|
|
item = dn
|
|
# if dirname(item) == item, it is guranteed to be a drive/fs root
|
|
# so it's enough to just check that item.endswith(".zip")
|
|
if item.endswith(".zip"):
|
|
nativeextdirs.append(dirname(item))
|
|
|
|
if not nativeextdirs:
|
|
return
|
|
|
|
EmbeddedImporter.nativeextdirs = list(set(nativeextdirs))
|
|
new_hooks = [EmbeddedImporter]
|
|
new_hooks.extend([ph for ph in sys.path_hooks if ph is not zipimport.zipimporter])
|
|
sys.path_hooks = new_hooks
|
|
# at this time the default (not modified) zipimporter is already
|
|
# cached, so let's invalidate the caches
|
|
sys.path_importer_cache.clear()
|