summaryrefslogtreecommitdiffstats
path: root/Lib/_bootsubprocess.py
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2020-03-09 22:45:59 (GMT)
committerGitHub <noreply@github.com>2020-03-09 22:45:59 (GMT)
commitaddaaaa946855ad59c8f5c698aa0891d7e44f018 (patch)
tree0de7ede6cf1117055d6a45120a73d3c866e3f0bb /Lib/_bootsubprocess.py
parent9ad58acbe8b90b4d0f2d2e139e38bb5aa32b7fb6 (diff)
downloadcpython-addaaaa946855ad59c8f5c698aa0891d7e44f018.zip
cpython-addaaaa946855ad59c8f5c698aa0891d7e44f018.tar.gz
cpython-addaaaa946855ad59c8f5c698aa0891d7e44f018.tar.bz2
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.
Diffstat (limited to 'Lib/_bootsubprocess.py')
-rw-r--r--Lib/_bootsubprocess.py102
1 files changed, 102 insertions, 0 deletions
diff --git a/Lib/_bootsubprocess.py b/Lib/_bootsubprocess.py
new file mode 100644
index 0000000..962301a
--- /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