Gabe Black has uploaded this change for review. ( https://gem5-review.googlesource.com/c/public/gem5/+/48363 )

Change subject: scons: Update the special module importer API.
......................................................................

scons: Update the special module importer API.

In the SConscript, there is a special importer which enables importing
embedded code using various m5.* paths. This was implemented using an
API which has been deprecated and replaced in more recent versions of
python.

Change-Id: I5900f269af48befbcedcb9d25353f04f6297ce9d
---
M src/SConscript
M src/python/importer.py
M src/python/m5/objects/__init__.py
3 files changed, 79 insertions(+), 75 deletions(-)



diff --git a/src/SConscript b/src/SConscript
index dbd28ea..34ba841 100644
--- a/src/SConscript
+++ b/src/SConscript
@@ -42,6 +42,9 @@
 import distutils.spawn
 import functools
 import imp
+import importlib
+import importlib.machinery
+import importlib.util
 import os
 import os.path
 import re
@@ -767,10 +770,20 @@
 #
 SimObject.fixed = True

-class DictImporter(object):
-    '''This importer takes a dictionary of arbitrary module names that
-    map to arbitrary filenames.'''
+class SimpleModuleLoader(importlib.abc.Loader):
+ '''A simple wrapper which delegates setting up a module to a function.'''
+    def __init__(self, executor):
+        super(SimpleModuleLoader, self).__init__()
+        self.executor = executor
+    def create_module(self, spec):
+        return None
+
+    def exec_module(self, module):
+        self.executor(module)
+
+class M5MetaPathFinder(importlib.abc.MetaPathFinder):
     def __init__(self, modules):
+        super(M5MetaPathFinder, self).__init__()
         self.modules = modules
         self.installed = set()

@@ -780,42 +793,46 @@
             del sys.modules[module]
         self.installed = set()

-    def find_module(self, fullname, path):
-        if fullname == 'm5.defines':
-            return self
+    def find_spec(self, fullname, path, target=None):
+        spec = None

-        if fullname == 'm5.objects':
-            return self
+        # If this isn't even in the m5 package, ignore it.
+        if fullname.startswith('m5.'):
+            if fullname.startswith('m5.objects'):
+                # When imported in this context, return a spec for a dummy
+                # package which just serves to house the modules within it.
+                # This is subtley different from "import * from m5.objects"
+ # which relies on the __init__.py in m5.objects. That in turn + # indirectly relies on the c++ based _m5 package which doesn't
+                # exist yet.
+                if fullname == 'm5.objects':
+                    dummy_loader = SimpleModuleLoader(lambda x: None)
+                    spec = importlib.machinery.ModuleSpec(
+                            name=fullname, loader=dummy_loader,
+                            is_package=True)
+                    spec.loader_state = self.modules.keys()

-        source = self.modules.get(fullname, None)
-        if source is not None and fullname.startswith('m5.objects'):
-            return self
+ # If this is a module within the m5.objects package, return a
+                # spec that maps to its source file.
+                elif fullname in self.modules:
+                    source = self.modules[fullname]
+                    spec = importlib.util.spec_from_file_location(
+                            name=fullname, location=source.abspath)

-        return None
+            # The artificial m5.defines subpackage.
+            elif fullname == 'm5.defines':
+                def build_m5_defines(module):
+                    module.__dict__['buildEnv'] = dict(build_env)

-    def load_module(self, fullname):
-        mod = imp.new_module(fullname)
-        sys.modules[fullname] = mod
-        self.installed.add(fullname)
+                spec = importlib.util.spec_from_loader(name=fullname,
+                        loader=SimpleModuleLoader(build_m5_defines))

-        mod.__loader__ = self
-        if fullname == 'm5.objects':
-            mod.__path__ = fullname.split('.')
-            return mod
+        # If we're handling this module, write it down so we can unload it
+        # later.
+        if spec is not None:
+            self.installed.add(fullname)

-        if fullname == 'm5.defines':
-            mod.__dict__['buildEnv'] = dict(build_env)
-            return mod
-
-        source = self.modules[fullname]
-        if source.modname == '__init__':
-            mod.__path__ = source.modpath
-        mod.__file__ = source.abspath
-
- compiled = compile(open(source.abspath).read(), source.abspath, 'exec')
-        exec(compiled, mod.__dict__)
-
-        return mod
+        return spec

 import m5.SimObject
 import m5.params
@@ -826,7 +843,7 @@
 # install the python importer so we can grab stuff from the source
 # tree itself.  We can't have SimObjects added after this point or
 # else we won't know about them for the rest of the stuff.
-importer = DictImporter(PySource.modules)
+importer = M5MetaPathFinder(PySource.modules)
 sys.meta_path[0:0] = [ importer ]

 # import all sim objects so we can populate the all_objects list
diff --git a/src/python/importer.py b/src/python/importer.py
index b89b4a8..4e5e907 100644
--- a/src/python/importer.py
+++ b/src/python/importer.py
@@ -24,12 +24,25 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+import importlib
+import os
+
+class ByteCodeLoader(importlib.abc.Loader):
+    def __init__(self, code):
+        super(ByteCodeLoader, self).__init__()
+        self.code = code
+
+    def exec_module(self, module):
+        exec(self.code, module.__dict__)
+
 # Simple importer that allows python to import data from a dict of
 # code objects.  The keys are the module path, and the items are the
 # filename and bytecode of the file.
 class CodeImporter(object):
     def __init__(self):
         self.modules = {}
+        override_var = os.environ.get('M5_OVERRIDE_PY_SOURCE', 'false')
+        self.override = (override_var.lower() in ('true', 'yes'))

     def add_module(self, filename, abspath, modpath, code):
         if modpath in self.modules:
@@ -37,50 +50,24 @@

         self.modules[modpath] = (filename, abspath, code)

-    def find_module(self, fullname, path):
-        if fullname in self.modules:
-            return self
+    def find_spec(self, fullname, path, target=None):
+        if fullname not in self.modules:
+            return None

-        return None
+        srcfile, abspath, code = self.modules[fullname]

-    def load_module(self, fullname):
-        # Because the importer is created and initialized in its own
-        # little sandbox (in init.cc), the globals that were available
-        # when the importer module was loaded and CodeImporter was
-        # defined are not available when load_module is actually
-        # called. Soooo, the imports must live here.
-        import imp
-        import os
-        import sys
+        if self.override and os.path.exists(abspath):
+            src = open(abspath, 'r').read()
+            code = compile(src, abspath, 'exec')

-        try:
-            mod = sys.modules[fullname]
-        except KeyError:
-            mod = imp.new_module(fullname)
-            sys.modules[fullname] = mod
+        is_package = (os.path.basename(srcfile) == '__init__.py')
+        spec = importlib.util.spec_from_loader(
+                name=fullname, loader=ByteCodeLoader(code),
+                is_package=is_package)

-        try:
-            mod.__loader__ = self
-            srcfile,abspath,code = self.modules[fullname]
+        spec.loader_state = self.modules.keys()

- override = os.environ.get('M5_OVERRIDE_PY_SOURCE', 'false').lower()
-            if override in ('true', 'yes') and  os.path.exists(abspath):
-                src = open(abspath, 'r').read()
-                code = compile(src, abspath, 'exec')
-
-            if os.path.basename(srcfile) == '__init__.py':
-                mod.__path__ = fullname.split('.')
-                mod.__package__ = fullname
-            else:
-                mod.__package__ = fullname.rpartition('.')[0]
-            mod.__file__ = srcfile
-
-            exec(code, mod.__dict__)
-        except Exception:
-            del sys.modules[fullname]
-            raise
-
-        return mod
+        return spec

 # Create an importer and add it to the meta_path so future imports can
 # use it.  There's currently nothing in the importer, but calls to
diff --git a/src/python/m5/objects/__init__.py b/src/python/m5/objects/__init__.py
index 3ec3b8c..e59f9a8 100644
--- a/src/python/m5/objects/__init__.py
+++ b/src/python/m5/objects/__init__.py
@@ -28,10 +28,10 @@
 from m5.SimObject import *

 try:
-    modules = __loader__.modules
+    modules = __spec__.loader_state
 except NameError:
     modules = { }

-for module in modules.keys():
+for module in modules:
     if module.startswith('m5.objects.'):
         exec("from %s import *" % module)

--
To view, visit https://gem5-review.googlesource.com/c/public/gem5/+/48363
To unsubscribe, or for help writing mail filters, visit https://gem5-review.googlesource.com/settings

Gerrit-Project: public/gem5
Gerrit-Branch: develop
Gerrit-Change-Id: I5900f269af48befbcedcb9d25353f04f6297ce9d
Gerrit-Change-Number: 48363
Gerrit-PatchSet: 1
Gerrit-Owner: Gabe Black <[email protected]>
Gerrit-MessageType: newchange
_______________________________________________
gem5-dev mailing list -- [email protected]
To unsubscribe send an email to [email protected]
%(web_page_url)slistinfo%(cgiext)s/%(_internal_name)s

Reply via email to