Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions Lib/_bootsubprocess.py
Original file line number Diff line number Diff line change
@@ -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
46 changes: 5 additions & 41 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down