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.
This commit is contained in:
Victor Stinner 2020-03-09 23:45:59 +01:00 committed by GitHub
parent 9ad58acbe8
commit addaaaa946
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 107 additions and 41 deletions

102
Lib/_bootsubprocess.py Normal file
View File

@ -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

View File

@ -16,53 +16,17 @@ try:
del subprocess del subprocess
SUBPROCESS_BOOTSTRAP = False SUBPROCESS_BOOTSTRAP = False
except ImportError: except ImportError:
SUBPROCESS_BOOTSTRAP = True
# Bootstrap Python: distutils.spawn uses subprocess to build C extensions, # Bootstrap Python: distutils.spawn uses subprocess to build C extensions,
# subprocess requires C extensions built by setup.py like _posixsubprocess. # subprocess requires C extensions built by setup.py like _posixsubprocess.
# #
# Basic subprocess implementation for POSIX (setup.py is not used on # Use _bootsubprocess which only uses the os module.
# Windows) which only uses os functions. Only implement features required
# by distutils.spawn.
# #
# It is dropped from sys.modules as soon as all C extension modules # It is dropped from sys.modules as soon as all C extension modules
# are built. # are built.
class Popen: import _bootsubprocess
def __init__(self, cmd, env=None): sys.modules['subprocess'] = _bootsubprocess
self._cmd = cmd del _bootsubprocess
self._env = env SUBPROCESS_BOOTSTRAP = True
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
from distutils import log from distutils import log