Bug 951736 - Adjust sys.path while native commands are executed; r=ted
authorGregory Szorc <gps@mozilla.com>
Wed, 18 Dec 2013 09:45:56 -0800
changeset 361 8fa921cbf2ff
parent 360 42627616700c
child 362 d564760b353e
push id229
push usergszorc@mozilla.com
push dateThu, 19 Dec 2013 18:38:39 +0000
reviewersted
bugs951736
Bug 951736 - Adjust sys.path while native commands are executed; r=ted The previous behavior only adjusted sys.path during module import. This caused problems when there were delayed imports.
pymake/process.py
tests/native-command-delay-load.mk
tests/pycmd.py
tests/subdir/delayload.py
--- a/pymake/process.py
+++ b/pymake/process.py
@@ -341,31 +341,16 @@ class PythonException(Exception):
     def __init__(self, message, exitcode):
         Exception.__init__(self)
         self.message = message
         self.exitcode = exitcode
 
     def __str__(self):
         return self.message
 
-def load_module_recursive(module, path):
-    """
-    Like __import__, but allow passing a custom path to search for modules.
-    """
-    oldsyspath = sys.path
-    sys.path = []
-    for p in path:
-        site.addsitedir(p)
-    sys.path.extend(oldsyspath)
-    try:
-        __import__(module)
-    except ImportError:
-        return
-    finally:
-        sys.path = oldsyspath
 
 class PythonJob(Job):
     """
     A job that calls a Python method.
     """
     def __init__(self, module, method, argv, env, cwd, pycommandpath=None):
         self.module = module
         self.method = method
@@ -375,26 +360,38 @@ class PythonJob(Job):
         self.pycommandpath = pycommandpath or []
         self.parentpid = os.getpid()
 
     def run(self):
         assert os.getpid() != self.parentpid
         # os.environ is a magic dictionary. Setting it to something else
         # doesn't affect the environment of subprocesses, so use clear/update
         oldenv = dict(os.environ)
+
+        # sys.path is adjusted for the entire lifetime of the command
+        # execution. This ensures any delayed imports will still work.
+        oldsyspath = list(sys.path)
         try:
             os.chdir(self.cwd)
             os.environ.clear()
             os.environ.update(self.env)
+
+            sys.path = []
+            for p in sys.path + self.pycommandpath:
+                site.addsitedir(p)
+            sys.path.extend(oldsyspath)
+
             if self.module not in sys.modules:
-                load_module_recursive(self.module,
-                                      sys.path + self.pycommandpath)
-            if self.module not in sys.modules:
-                print >>sys.stderr, "No module named '%s'" % self.module
-                return -127                
+                try:
+                    __import__(self.module)
+                except Exception as e:
+                    print >>sys.stderr, 'Error importing %s: %s' % (
+                        self.module, e)
+                    return -127
+
             m = sys.modules[self.module]
             if self.method not in m.__dict__:
                 print >>sys.stderr, "No method named '%s' in module %s" % (self.method, self.module)
                 return -127
             rv = m.__dict__[self.method](self.argv)
             if rv != 0 and rv is not None:
                 print >>sys.stderr, (
                     "Native command '%s %s' returned value '%s'" %
@@ -410,20 +407,22 @@ class PythonJob(Job):
                 pass # sys.exit(0) is not a failure
             else:
                 print >>sys.stderr, e
                 traceback.print_exc()
                 return -127
         finally:
             os.environ.clear()
             os.environ.update(oldenv)
+            sys.path = oldsyspath
             # multiprocessing exits via os._exit, make sure that all output
             # from command gets written out before that happens.
             sys.stdout.flush()
             sys.stderr.flush()
+
         return 0
 
 def job_runner(job):
     """
     Run a job. Called in a Process pool.
     """
     return job.run()
 
new file mode 100644
--- /dev/null
+++ b/tests/native-command-delay-load.mk
@@ -0,0 +1,12 @@
+#T gmake skip
+
+# This test exists to verify that sys.path is adjusted during command
+# execution and that delay importing a module will work.
+
+CMD = %pycmd delayloadfn
+PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
+
+all:
+	$(CMD)
+	@echo TEST-PASS
+
--- a/tests/pycmd.py
+++ b/tests/pycmd.py
@@ -28,8 +28,11 @@ def asplode(args):
   sys.exit(arg0)
 
 def asplode_return(args):
   arg0 = convertasplode(args[0])
   return arg0
 
 def asplode_raise(args):
   raise Exception(args[0])
+
+def delayloadfn(args):
+    import delayload
new file mode 100644
--- /dev/null
+++ b/tests/subdir/delayload.py
@@ -0,0 +1,1 @@
+# This module exists to test delay importing of modules at run-time.