Skip to content

Commit addaaaa

Browse files
authored
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.
1 parent 9ad58ac commit addaaaa

File tree

2 files changed

+107
-41
lines changed

2 files changed

+107
-41
lines changed

Lib/_bootsubprocess.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""
2+
Basic subprocess implementation for POSIX which only uses os functions. Only
3+
implement features required by setup.py to build C extension modules when
4+
subprocess is unavailable. setup.py is not used on Windows.
5+
"""
6+
import os
7+
8+
9+
# distutils.spawn used by distutils.command.build_ext
10+
# calls subprocess.Popen().wait()
11+
class Popen:
12+
def __init__(self, cmd, env=None):
13+
self._cmd = cmd
14+
self._env = env
15+
self.returncode = None
16+
17+
def wait(self):
18+
pid = os.fork()
19+
if pid == 0:
20+
# Child process
21+
try:
22+
if self._env is not None:
23+
os.execve(self._cmd[0], self._cmd, self._env)
24+
else:
25+
os.execv(self._cmd[0], self._cmd)
26+
finally:
27+
os._exit(1)
28+
else:
29+
# Parent process
30+
pid, status = os.waitpid(pid, 0)
31+
if os.WIFSIGNALED(status):
32+
self.returncode = -os.WTERMSIG(status)
33+
elif os.WIFEXITED(status):
34+
self.returncode = os.WEXITSTATUS(status)
35+
elif os.WIFSTOPPED(status):
36+
self.returncode = -os.WSTOPSIG(status)
37+
else:
38+
raise Exception(f"unknown child process exit status: {status!r}")
39+
40+
return self.returncode
41+
42+
43+
def _check_cmd(cmd):
44+
# Use regex [a-zA-Z0-9./-]+: reject empty string, space, etc.
45+
safe_chars = []
46+
for first, last in (("a", "z"), ("A", "Z"), ("0", "9")):
47+
for ch in range(ord(first), ord(last) + 1):
48+
safe_chars.append(chr(ch))
49+
safe_chars.append("./-")
50+
safe_chars = ''.join(safe_chars)
51+
52+
if isinstance(cmd, (tuple, list)):
53+
check_strs = cmd
54+
elif isinstance(cmd, str):
55+
check_strs = [cmd]
56+
else:
57+
return False
58+
59+
for arg in check_strs:
60+
if not isinstance(arg, str):
61+
return False
62+
if not arg:
63+
# reject empty string
64+
return False
65+
for ch in arg:
66+
if ch not in safe_chars:
67+
return False
68+
69+
return True
70+
71+
72+
# _aix_support used by distutil.util calls subprocess.check_output()
73+
def check_output(cmd, **kwargs):
74+
if kwargs:
75+
raise NotImplementedError(repr(kwargs))
76+
77+
if not _check_cmd(cmd):
78+
raise ValueError(f"unsupported command: {cmd!r}")
79+
80+
tmp_filename = "check_output.tmp"
81+
if not isinstance(cmd, str):
82+
cmd = " ".join(cmd)
83+
cmd = f"{cmd} >{tmp_filename}"
84+
85+
try:
86+
# system() spawns a shell
87+
status = os.system(cmd)
88+
if status:
89+
raise ValueError(f"Command {cmd!r} failed with status {status!r}")
90+
91+
try:
92+
with open(tmp_filename, "rb") as fp:
93+
stdout = fp.read()
94+
except FileNotFoundError:
95+
stdout = b''
96+
finally:
97+
try:
98+
os.unlink(tmp_filename)
99+
except OSError:
100+
pass
101+
102+
return stdout

setup.py

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,53 +16,17 @@
1616
del subprocess
1717
SUBPROCESS_BOOTSTRAP = False
1818
except ImportError:
19-
SUBPROCESS_BOOTSTRAP = True
20-
2119
# Bootstrap Python: distutils.spawn uses subprocess to build C extensions,
2220
# subprocess requires C extensions built by setup.py like _posixsubprocess.
2321
#
24-
# Basic subprocess implementation for POSIX (setup.py is not used on
25-
# Windows) which only uses os functions. Only implement features required
26-
# by distutils.spawn.
22+
# Use _bootsubprocess which only uses the os module.
2723
#
2824
# It is dropped from sys.modules as soon as all C extension modules
2925
# are built.
30-
class Popen:
31-
def __init__(self, cmd, env=None):
32-
self._cmd = cmd
33-
self._env = env
34-
self.returncode = None
35-
36-
def wait(self):
37-
pid = os.fork()
38-
if pid == 0:
39-
# Child process
40-
try:
41-
if self._env is not None:
42-
os.execve(self._cmd[0], self._cmd, self._env)
43-
else:
44-
os.execv(self._cmd[0], self._cmd)
45-
finally:
46-
os._exit(1)
47-
else:
48-
# Parent process
49-
pid, status = os.waitpid(pid, 0)
50-
if os.WIFSIGNALED(status):
51-
self.returncode = -os.WTERMSIG(status)
52-
elif os.WIFEXITED(status):
53-
self.returncode = os.WEXITSTATUS(status)
54-
elif os.WIFSTOPPED(status):
55-
self.returncode = -os.WSTOPSIG(status)
56-
else:
57-
# Should never happen
58-
raise Exception("Unknown child exit status!")
59-
60-
return self.returncode
61-
62-
mod = type(sys)('subprocess')
63-
mod.Popen = Popen
64-
sys.modules['subprocess'] = mod
65-
del mod
26+
import _bootsubprocess
27+
sys.modules['subprocess'] = _bootsubprocess
28+
del _bootsubprocess
29+
SUBPROCESS_BOOTSTRAP = True
6630

6731

6832
from distutils import log

0 commit comments

Comments
 (0)