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
9 changes: 6 additions & 3 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,12 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
- Added user configurable setting of ninja depfile format via NINJA_DEPFILE_PARSE_FORMAT.
Now setting NINJA_DEPFILE_PARSE_FORMAT to [msvc,gcc,clang] can force the ninja expected
format. Compiler tools will also configure the variable automatically.
- Added SHELL_ENV_GENERATOR construction variables. This variable allows the user to Define
a function which will be called to generate or alter the execution environment which will
be used in the shell command of some Action.
- Added SHELL_ENV_GENERATORS construction variable. This variable should be set to a list
(or an iterable) which contains functions to be called in order
when constructing the execution environment (Generally this is the shell environment
variables). This allows the user to customize how (for example) PATH is constructed.
Note that these are called for every build command run by SCons. It could have considerable
performance impact if not used carefully.
- Updated ninja scons daemon scripts to output errors to stderr as well as the daemon log.
- Fix typo in ninja scons daemon startup which causes ConnectionRefusedError to not retry
to connect to the server during start up.
Expand Down
9 changes: 6 additions & 3 deletions RELEASE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ NEW FUNCTIONALITY

- Added MSVC_USE_SCRIPT_ARGS variable to pass arguments to MSVC_USE_SCRIPT.
- Added Configure.CheckMember() checker to check if struct/class has the specified member.
- Added SHELL_ENV_GENERATOR construction variables. This variable allows the user to Define
a function which will be called to generate or alter the execution environment which will
be used in the shell command of some Action.
- Added SHELL_ENV_GENERATORS construction variable. This variable should be set to a list
(or an iterable) which contains functions to be called in order
when constructing the execution environment (Generally this is the shell environment
variables). This allows the user to customize how (for example) PATH is constructed.
Note that these are called for every build command run by SCons. It could have considerable
performance impact if not used carefully.
- Added MSVC_USE_SETTINGS variable to pass a dictionary to configure the msvc compiler
system environment as an alternative to bypassing Visual Studio autodetection entirely.

Expand Down
28 changes: 25 additions & 3 deletions SCons/Action.py
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ def _string_from_cmd_list(cmd_list):
default_ENV = None


def get_default_ENV(env, target=None, source=None):
def get_default_ENV(env):
"""
A fiddlin' little function that has an 'import SCons.Environment' which
can't be moved to the top level without creating an import loop. Since
Expand All @@ -755,6 +755,29 @@ def get_default_ENV(env, target=None, source=None):
return default_ENV


def _resolve_shell_env(env, target, source):
"""
First get default environment.
Then if SHELL_ENV_GENERATORS is set and is iterable,
call each callable in that list to allow it to alter
the created execution environment.
"""
ENV = get_default_ENV(env)
shell_gen = env.get('SHELL_ENV_GENERATORS')
if shell_gen:
try:
shell_gens = iter(shell_gen)
except TypeError:
raise SCons.Errors.UserError("SHELL_ENV_GENERATORS must be iteratable.")
else:
ENV = ENV.copy()
for generator in shell_gens:
ENV = generator(env, target, source, ENV)
if not isinstance(ENV, dict):
raise SCons.Errors.UserError(f"SHELL_ENV_GENERATORS function: {generator} must return a dict.")
return ENV


def _subproc(scons_env, cmd, error='ignore', **kw):
"""Wrapper for subprocess which pulls from construction env.

Expand Down Expand Up @@ -924,10 +947,9 @@ def execute(self, target, source, env, executor=None):

escape = env.get('ESCAPE', lambda x: x)

ENV = env.get('SHELL_ENV_GENERATOR', get_default_ENV)(env, target, source)
ENV = _resolve_shell_env(env, target, source)

# Ensure that the ENV values are all strings:

for key, value in ENV.items():
if not is_String(value):
if is_List(value):
Expand Down
35 changes: 27 additions & 8 deletions SCons/Action.xml
Original file line number Diff line number Diff line change
Expand Up @@ -200,18 +200,32 @@ in which the command should be executed.
</summary>
</cvar>

<cvar name="SHELL_ENV_GENERATOR">
<cvar name="SHELL_ENV_GENERATORS">
<summary>
<para>
A function to generate or alter the environment dictionary which will be used
when executing the &cv-link-SPAWN; function. This primarily give the
user a chance to customize the execution environment for particular Actions.
It must return a dictionary containing the environment variables as
keys and the values as values.
Must be a list (or an iterable) containing functions where each function generates or
alters the environment dictionary which will be used
when executing the &cv-link-SPAWN; function. The functions will initially
be passed a reference of the current execution environment (e.g. env['ENV']),
and each called while iterating the list. Each function must return a dictionary
which will then be passed to the next function iterated. The return dictionary
should contain keys which represent the environment variables and their respective
values.

This primary purpose of this construction variable is to give the user the ability
to substitute execution environment variables based on env, targets, and sources.
If desired, the user can completely customize the execution environment for particular
targets.
</para>

<example_commands>
def custom_shell_env(env, target, source):
def custom_shell_env(env, target, source, shell_env):
"""customize shell_env if desired"""
if str(target[0]) == 'special_target':
shell_env['SPECIAL_VAR'] = env.subst('SOME_VAR', target=target, source=source)
return shell_env

env["SHELL_ENV_GENERATORS"] = [custom_shell_env]
</example_commands>

<para>
Expand All @@ -223,10 +237,15 @@ execution environment can be derived from.
<varname>target</varname>
The list of targets associated with this action.
</para>
<para>
<para>
<varname>source</varname>
The list of sources associated with this action.
</para>
<para>
<varname>shell_env</varname>
The current shell_env after iterating other SHELL_ENV_GENERATORS functions. This can be compared
to the passed env['ENV'] to detect any changes.
</para>
</summary>
</cvar>

Expand Down
4 changes: 2 additions & 2 deletions SCons/Tool/ninja/Methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def get_generic_shell_command(env, node, action, targets, sources, executor=None
"GENERATED_CMD",
{
"cmd": generate_command(env, node, action, targets, sources, executor=executor),
"env": get_command_env(env),
"env": get_command_env(env, targets, sources),
},
# Since this function is a rule mapping provider, it must return a list of dependencies,
# and usually this would be the path to a tool, such as a compiler, used for this rule.
Expand Down Expand Up @@ -266,7 +266,7 @@ def get_response_file_command(env, node, action, targets, sources, executor=None

variables = {"rspc": rsp_content, rule: cmd}
if use_command_env:
variables["env"] = get_command_env(env)
variables["env"] = get_command_env(env, targets, sources)

for key, value in custom_env.items():
variables["env"] += env.subst(
Expand Down
11 changes: 10 additions & 1 deletion SCons/Tool/ninja/NinjaState.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,15 @@ def handle_list_action(self, node, action):

# Remove all preceding and proceeding whitespace
cmdline = cmdline.strip()
env = node.env if node.env else self.env
executor = node.get_executor()
if executor is not None:
targets = executor.get_all_targets()
else:
if hasattr(node, "target_peers"):
targets = node.target_peers
else:
targets = [node]

# Make sure we didn't generate an empty cmdline
if cmdline:
Expand All @@ -817,7 +826,7 @@ def handle_list_action(self, node, action):
"rule": get_rule(node, "GENERATED_CMD"),
"variables": {
"cmd": cmdline,
"env": get_command_env(node.env if node.env else self.env),
"env": get_command_env(env, targets, node.sources),
},
"implicit": dependencies,
}
Expand Down
4 changes: 2 additions & 2 deletions SCons/Tool/ninja/Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def ninja_noop(*_args, **_kwargs):
return None


def get_command_env(env):
def get_command_env(env, target, source):
"""
Return a string that sets the environment for any environment variables that
differ between the OS environment and the SCons command ENV.
Expand All @@ -275,7 +275,7 @@ def get_command_env(env):
# os.environ or differ from it. We assume if it's a new or
# differing key from the process environment then it's
# important to pass down to commands in the Ninja file.
ENV = get_default_ENV(env)
ENV = SCons.Action._resolve_shell_env(env, target, source)
scons_specified_env = {
key: value
for key, value in ENV.items()
Expand Down
24 changes: 18 additions & 6 deletions test/Actions/subst_shell_env-fixture/SConstruct
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
import sys

def custom_environment_expansion(env, target, source):
ENV = env['ENV'].copy()
ENV['EXPANDED_SHELL_VAR'] = env.subst(env['ENV']['EXPANDED_SHELL_VAR'], target=target, source=source)
def custom_environment_expansion1(env, target, source, shell_env):
ENV = shell_env.copy()
ENV['EXPANDED_SHELL_VAR1'] = env.subst(env['ENV']['EXPANDED_SHELL_VAR1'], target=target, source=source)
return ENV

def custom_environment_expansion2(env, target, source, shell_env):
ENV = shell_env.copy()
ENV['EXPANDED_SHELL_VAR2'] = env.subst(env['ENV']['EXPANDED_SHELL_VAR2'], target=target, source=source)
return ENV

def expand_this_generator(env, target, source, for_signature):
return "I_got_expanded_to_" + str(target[0])

def expand_that_generator(env, target, source, for_signature):
return str(target[0]) + "_is_from_expansion"

env = Environment(tools=['textfile'])

env['SHELL_ENV_GENERATOR'] = custom_environment_expansion
env['SHELL_ENV_GENERATORS'] = [custom_environment_expansion1, custom_environment_expansion2]

env['EXPAND_THIS'] = expand_this_generator
env['ENV']['EXPANDED_SHELL_VAR'] = "$EXPAND_THIS"
env['EXPAND_THAT'] = expand_that_generator

env['ENV']['EXPANDED_SHELL_VAR1'] = "$EXPAND_THIS"
env['ENV']['EXPANDED_SHELL_VAR2'] = "$EXPAND_THAT"
env['ENV']['NON_EXPANDED_SHELL_VAR'] = "$EXPAND_THIS"

env.Textfile('expand_script.py', [
'import os',
'print(os.environ["EXPANDED_SHELL_VAR"])',
'print(os.environ["EXPANDED_SHELL_VAR1"])',
'print(os.environ["EXPANDED_SHELL_VAR2"])',
'print(os.environ["NON_EXPANDED_SHELL_VAR"])',
])
env.Command('out.txt', 'expand_script.py', fr'{sys.executable} $SOURCE > $TARGET')
Expand Down
6 changes: 3 additions & 3 deletions test/Actions/subst_shell_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

"""
Verify that shell environment variables can be expanded per target/source
when exectuting actions on the command line.
when executing actions on the command line.
"""
import os

Expand All @@ -36,8 +36,8 @@
test.dir_fixture('subst_shell_env-fixture')

test.run(arguments = ['-Q'])
test.must_match('out.txt', f"I_got_expanded_to_out.txt{os.linesep}$EXPAND_THIS{os.linesep}")
test.must_match('out2.txt', f"I_got_expanded_to_out2.txt{os.linesep}$EXPAND_THIS{os.linesep}")
test.must_match('out.txt', f"I_got_expanded_to_out.txt{os.linesep}out.txt_is_from_expansion{os.linesep}$EXPAND_THIS{os.linesep}")
test.must_match('out2.txt', f"I_got_expanded_to_out2.txt{os.linesep}out2.txt_is_from_expansion{os.linesep}$EXPAND_THIS{os.linesep}")

test.pass_test()

Expand Down