Skip to content
Open
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
10 changes: 8 additions & 2 deletions SCons/ActionTests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1165,11 +1165,17 @@ def test_execute(self) -> None:
env5['ENV']['XYZZY'] = 'xyzzy'
r = act(target=DummyNode('out5'), source=[], env=env5)

ENV = {'XYZZY': 'xyzzy5', 'PATH': PATH}
if sys.platform == 'win32':
# Obscure: on Windows, we fail to initialize Python if SystemRoot
# missing (from current support list: versions 3.7-3.10).
# The direct assignment to ENV in the Clone call means we don't
# retain any of ENV from the cloned environment.
ENV['SystemRoot'] = os.environ.get('SystemRoot', "C:\\WINDOWS")
act = SCons.Action.CommandAction(cmd5)
r = act(target=DummyNode('out5'),
source=[],
env=env.Clone(ENV={'XYZZY': 'xyzzy5',
'PATH': PATH}))
env=env.Clone(ENV=ENV))
assert r == 0
c = test.read(outfile, 'r')
assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy5'\n", c
Expand Down
7 changes: 2 additions & 5 deletions SCons/Platform/Platform.xml
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,8 @@ The suffix used for executable file names.
<summary>
<para>
A string naming the shell program that will be passed to the
&cv-SPAWN;
function.
See the
&cv-SPAWN;
construction variable for more information.
command spawner function.
See the &cv-link-SPAWN; &consvar; for more information.
</para>
</summary>
</cvar>
Expand Down
106 changes: 79 additions & 27 deletions SCons/Platform/posix.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@
selection method.
"""

from __future__ import annotations

import platform
import subprocess
from collections.abc import Callable
from shlex import quote as escape

from SCons.Platform import TempFileMunge
from SCons.Platform.virtualenv import ImportVirtualenv
Expand All @@ -40,8 +44,17 @@
13 : 126,
}

def escape(arg):
"""escape shell special characters"""
def old_escape(arg: str) -> str:
"""Escapes shell special characters.

This is the default escape function stored in ``env["ESCAPE"]`` for
the posix platform, which, if not overridden, is passed to
:func:`~SCons.Subst.escape_list` just before a command is spawned,
as well to the actual spawner function (as defined by ``env["SPAWN"]``).

TODO: we're trying to use shlex.quote as the escape function instead.
Leave this function around (renamed) until we prove the replacement is valid.
"""
slash = '\\'
special = '"$'

Expand All @@ -53,33 +66,72 @@ def escape(arg):
return '"' + arg + '"'


def exec_subprocess(l, env):
proc = subprocess.Popen(l, env = env, close_fds = True)
return proc.wait()

def subprocess_spawn(sh, escape, cmd, args, env):
return exec_subprocess([sh, '-c', ' '.join(args)], env)

def exec_popen3(l, env, stdout, stderr):
proc = subprocess.Popen(l, env = env, close_fds = True,
stdout = stdout,
stderr = stderr)
return proc.wait()

def piped_env_spawn(sh, escape, cmd, args, env, stdout, stderr):
# spawn using Popen3 combined with the env command
# the command name and the command's stdout is written to stdout
# the command's stderr is written to stderr
return exec_popen3([sh, '-c', ' '.join(args)],
env, stdout, stderr)
def spawn(
sh: str,
escape: Callable[[str], str],
cmd: str,
args: list[str],
env: dict,
) -> int:
"""Run command line *args* using shell *sh*.

Arguments:
sh: the name of the command to use as the shell
escape: a function to quote the produced command line. Ignored.
cmd: conventionally, the name of the command, usually taken from
the first item of *args*, but since the command is actually a
shell, is ignored.
args: the argument list representing the command to execute
env: the execution environment for the command.

Returns:
the exit code of the command. :py:mod:`subprocess` is explicitly
instructed not to raise an exception if the command fails.
"""
cmdargs = [sh, '-c', ' '.join(args)]
proc = subprocess.run(cmdargs, env=env, close_fds=True, check=False)
return proc.returncode


def piped_spawn(
sh: str,
escape: Callable[[str], str],
cmd: str,
args: list[str],
env: dict,
stdout, # : Scons.Util.Unbuffered
stderr, # : Scons.Util.Unbuffered
) -> int:
"""Run command line *args* using shell *sh*, capturing output.

Similar to :func:`spawn`, but captures output - this is used by
the SConf subsystem when running compile/configure checks, where
we specifically need the result data. This ends up handled by
a wrapper method :meth:`~SCons.SConf.SConfBase.pspawn_wrapper`.

Arguments:
sh: the name of the command to use as the shell
escape: a function to quote the produced command line. Ignored.
cmd: conventionally, the name of the command, usually taken from
the first item of *args*, but since the command is actually a
shell, is ignored.
args: the argument list representing the command to execute
env: the execution environment for the command.
stdout: the place to send the output
stderr: the place to send the error output

Returns:
the exit code of the command. :py:mod:`subprocess` is explicitly
instructed not to raise an exception if the command fails.
"""
cmdargs = [sh, '-c', ' '.join(args)]
proc = subprocess.run(
cmdargs, env=env, close_fds=True, stdout=stdout, stderr=stderr, check=False
)
return proc.returncode


def generate(env) -> None:
# Bearing in mind we have python 2.4 as a baseline, we can just do this:
spawn = subprocess_spawn
pspawn = piped_env_spawn
# Note that this means that 'escape' is no longer used

if 'ENV' not in env:
env['ENV'] = {}
env['ENV']['PATH'] = '/usr/local/bin:/opt/bin:/bin:/usr/bin:/snap/bin'
Expand All @@ -98,7 +150,7 @@ def generate(env) -> None:
env['LIBLITERALPREFIX'] = ''
env['HOST_OS'] = 'posix'
env['HOST_ARCH'] = platform.machine()
env['PSPAWN'] = pspawn
env['PSPAWN'] = piped_spawn
env['SPAWN'] = spawn
env['SHELL'] = 'sh'
env['ESCAPE'] = escape
Expand Down
Loading
Loading