[Python-checkins] bpo-39763: Add _bootsubprocess to build Python on AIX (GH-18872)

Victor Stinner webhook-mailer at python.org
Mon Mar 9 18:46:07 EDT 2020


https://github.com/python/cpython/commit/addaaaa946855ad59c8f5c698aa0891d7e44f018
commit: addaaaa946855ad59c8f5c698aa0891d7e44f018
branch: master
author: Victor Stinner <vstinner at python.org>
committer: GitHub <noreply at github.com>
date: 2020-03-09T23:45:59+01:00
summary:

bpo-39763: Add _bootsubprocess to build Python on AIX (GH-18872)

Add _bootsubprocess module to bootstrap Python: subprocess
implementation which only uses the os module.

On AIX, distutils.util uses _aix_support which calls
subprocess.check_output(), before the _posixsubprocess module is
built. Implement check_output() with os.system() in _bootsubprocess.

files:
A Lib/_bootsubprocess.py
M setup.py

diff --git a/Lib/_bootsubprocess.py b/Lib/_bootsubprocess.py
new file mode 100644
index 0000000000000..962301ae1499e
--- /dev/null
+++ b/Lib/_bootsubprocess.py
@@ -0,0 +1,102 @@
+"""
+Basic subprocess implementation for POSIX which only uses os functions. Only
+implement features required by setup.py to build C extension modules when
+subprocess is unavailable. setup.py is not used on Windows.
+"""
+import os
+
+
+# distutils.spawn used by distutils.command.build_ext
+# calls subprocess.Popen().wait()
+class Popen:
+    def __init__(self, cmd, env=None):
+        self._cmd = cmd
+        self._env = env
+        self.returncode = None
+
+    def wait(self):
+        pid = os.fork()
+        if pid == 0:
+            # Child process
+            try:
+                if self._env is not None:
+                    os.execve(self._cmd[0], self._cmd, self._env)
+                else:
+                    os.execv(self._cmd[0], self._cmd)
+            finally:
+                os._exit(1)
+        else:
+            # Parent process
+            pid, status = os.waitpid(pid, 0)
+            if os.WIFSIGNALED(status):
+                self.returncode = -os.WTERMSIG(status)
+            elif os.WIFEXITED(status):
+                self.returncode = os.WEXITSTATUS(status)
+            elif os.WIFSTOPPED(status):
+                self.returncode = -os.WSTOPSIG(status)
+            else:
+                raise Exception(f"unknown child process exit status: {status!r}")
+
+        return self.returncode
+
+
+def _check_cmd(cmd):
+    # Use regex [a-zA-Z0-9./-]+: reject empty string, space, etc.
+    safe_chars = []
+    for first, last in (("a", "z"), ("A", "Z"), ("0", "9")):
+        for ch in range(ord(first), ord(last) + 1):
+            safe_chars.append(chr(ch))
+    safe_chars.append("./-")
+    safe_chars = ''.join(safe_chars)
+
+    if isinstance(cmd, (tuple, list)):
+        check_strs = cmd
+    elif isinstance(cmd, str):
+        check_strs = [cmd]
+    else:
+        return False
+
+    for arg in check_strs:
+        if not isinstance(arg, str):
+            return False
+        if not arg:
+            # reject empty string
+            return False
+        for ch in arg:
+            if ch not in safe_chars:
+                return False
+
+    return True
+
+
+# _aix_support used by distutil.util calls subprocess.check_output()
+def check_output(cmd, **kwargs):
+    if kwargs:
+        raise NotImplementedError(repr(kwargs))
+
+    if not _check_cmd(cmd):
+        raise ValueError(f"unsupported command: {cmd!r}")
+
+    tmp_filename = "check_output.tmp"
+    if not isinstance(cmd, str):
+        cmd = " ".join(cmd)
+    cmd = f"{cmd} >{tmp_filename}"
+
+    try:
+        # system() spawns a shell
+        status = os.system(cmd)
+        if status:
+            raise ValueError(f"Command {cmd!r} failed with status {status!r}")
+
+        try:
+            with open(tmp_filename, "rb") as fp:
+                stdout = fp.read()
+        except FileNotFoundError:
+            stdout = b''
+    finally:
+        try:
+            os.unlink(tmp_filename)
+        except OSError:
+            pass
+
+    return stdout
diff --git a/setup.py b/setup.py
index a331315817950..24ce9a632dd90 100644
--- a/setup.py
+++ b/setup.py
@@ -16,53 +16,17 @@
     del subprocess
     SUBPROCESS_BOOTSTRAP = False
 except ImportError:
-    SUBPROCESS_BOOTSTRAP = True
-
     # Bootstrap Python: distutils.spawn uses subprocess to build C extensions,
     # subprocess requires C extensions built by setup.py like _posixsubprocess.
     #
-    # Basic subprocess implementation for POSIX (setup.py is not used on
-    # Windows) which only uses os functions. Only implement features required
-    # by distutils.spawn.
+    # Use _bootsubprocess which only uses the os module.
     #
     # It is dropped from sys.modules as soon as all C extension modules
     # are built.
-    class Popen:
-        def __init__(self, cmd, env=None):
-            self._cmd = cmd
-            self._env = env
-            self.returncode = None
-
-        def wait(self):
-            pid = os.fork()
-            if pid == 0:
-                # Child process
-                try:
-                    if self._env is not None:
-                        os.execve(self._cmd[0], self._cmd, self._env)
-                    else:
-                        os.execv(self._cmd[0], self._cmd)
-                finally:
-                    os._exit(1)
-            else:
-                # Parent process
-                pid, status = os.waitpid(pid, 0)
-                if os.WIFSIGNALED(status):
-                    self.returncode = -os.WTERMSIG(status)
-                elif os.WIFEXITED(status):
-                    self.returncode = os.WEXITSTATUS(status)
-                elif os.WIFSTOPPED(status):
-                    self.returncode = -os.WSTOPSIG(status)
-                else:
-                    # Should never happen
-                    raise Exception("Unknown child exit status!")
-
-            return self.returncode
-
-    mod = type(sys)('subprocess')
-    mod.Popen = Popen
-    sys.modules['subprocess'] = mod
-    del mod
+    import _bootsubprocess
+    sys.modules['subprocess'] = _bootsubprocess
+    del _bootsubprocess
+    SUBPROCESS_BOOTSTRAP = True
 
 
 from distutils import log



More information about the Python-checkins mailing list