From 8999666f07311ba025ee91e801c04dd2fd5adb96 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 24 Nov 2024 20:47:03 -0800 Subject: [PATCH 1/5] Fixed ninja binary location logic to use ninja.BIN_DIR. Previous logic no longer works starting with python ninja package version 1.11.1.2 --- CHANGES.txt | 6 + RELEASE.txt | 7 + SCons/Tool/ninja/NinjaState.py | 19 ++- test/ninja/build_libraries.py | 9 +- test/ninja/command_line_targets.py | 7 +- test/ninja/copy_function_command.py | 7 +- test/ninja/default_targets.py | 4 +- test/ninja/force_scons_callback.py | 5 +- test/ninja/generate_and_build.py | 7 +- test/ninja/generate_and_build_cxx.py | 7 +- test/ninja/generate_source.py | 7 +- test/ninja/generated_sources_alias.py | 5 +- test/ninja/iterative_speedup.py | 7 +- test/ninja/mingw_command_generator_action.py | 4 +- test/ninja/mingw_depfile_format.py | 4 +- test/ninja/mkdir_function_command.py | 7 +- test/ninja/multi_env.py | 7 +- test/ninja/ninja_command_line.py | 7 +- test/ninja/ninja_conftest.py | 7 +- test/ninja/ninja_file_deterministic.py | 4 +- test/ninja/ninja_handle_control_c_rebuild.py | 6 +- test/ninja/no_for_sig_subst.py | 7 +- test/ninja/response_file.py | 7 +- test/ninja/shell_command.py | 7 +- test/ninja/shutdown_scons_daemon.py | 4 +- testing/framework/TestSCons.py | 146 +++++++++++-------- 26 files changed, 134 insertions(+), 180 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e449bbab56..964296614d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -85,6 +85,12 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER via top-level `from __future__ import annotations`. - Implemented type hints for Nodes. + From William Deegan: + - Update ninja tool to use ninja.BIN_DIR to find pypi packaged ninja binary. + python ninja package version 1.11.1.2 changed the location and previous + logic no longer worked. + - Added ninja_binary() method to TestSCons to centralize logic to find ninja binary + From Alex James: - On Darwin, PermissionErrors are now handled while trying to access /etc/paths.d. This may occur if SCons is invoked in a sandboxed diff --git a/RELEASE.txt b/RELEASE.txt index 8e88a0bbde..4aa9697d39 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -140,6 +140,11 @@ FIXES - Make sure unknown variables from a Variables file are recognized as such (issue #4645) +- Update ninja tool to use ninja.BIN_DIR to find pypi packaged ninja binary. + python ninja package version 1.11.1.2 changed the location and previous + logic no longer worked. + + IMPROVEMENTS ------------ @@ -196,6 +201,8 @@ DEVELOPMENT - Implemented type hints for Nodes. +- Added ninja_binary() method to TestSCons to centralize logic to find ninja binary + Thanks to the following contributors listed below for their contributions to this release. ========================================================================================== .. code-block:: text diff --git a/SCons/Tool/ninja/NinjaState.py b/SCons/Tool/ninja/NinjaState.py index 549af7855e..da7aa28f12 100644 --- a/SCons/Tool/ninja/NinjaState.py +++ b/SCons/Tool/ninja/NinjaState.py @@ -60,12 +60,11 @@ def __init__(self, env, ninja_file, ninja_syntax) -> None: if not self.ninja_bin_path: # default to using ninja installed with python module ninja_bin = 'ninja.exe' if env["PLATFORM"] == "win32" else 'ninja' + self.ninja_bin_path = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - ninja_bin)) + ninja.BIN_DIR, ninja_bin + )) + if not os.path.exists(self.ninja_bin_path): # couldn't find it, just give the bin name and hope # its in the path later @@ -398,7 +397,7 @@ def generate(self): self.rules.update({key: non_rsp_rule}) else: self.rules.update({key: rule}) - + self.pools.update(self.env.get(NINJA_POOLS, {})) content = io.StringIO() @@ -435,7 +434,7 @@ def generate(self): generated_source_files = sorted( [] if not generated_sources_build else generated_sources_build['implicit'] ) - + def check_generated_source_deps(build): return ( build != generated_sources_build @@ -464,7 +463,7 @@ def check_generated_source_deps(build): rule="phony", implicit=generated_source_files ) - + def check_generated_source_deps(build): return ( not build["rule"] == "INSTALL" @@ -661,7 +660,7 @@ def check_generated_source_deps(build): all_targets = [str(node) for node in NINJA_DEFAULT_TARGETS] else: all_targets = list(all_targets) - + if len(all_targets) == 0: all_targets = ["phony_default"] ninja_sorted_build( @@ -669,7 +668,7 @@ def check_generated_source_deps(build): outputs=all_targets, rule="phony", ) - + ninja.default([self.ninja_syntax.escape_path(path) for path in sorted(all_targets)]) with NamedTemporaryFile(delete=False, mode='w') as temp_ninja_file: diff --git a/test/ninja/build_libraries.py b/test/ninja/build_libraries.py index 0a1941ab5a..0561f6f544 100644 --- a/test/ninja/build_libraries.py +++ b/test/ninja/build_libraries.py @@ -23,8 +23,6 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -import os - import TestSCons from TestCmd import IS_WINDOWS, IS_MACOS from TestSCons import _exe, _lib, lib_, _dll, dll_ @@ -36,12 +34,7 @@ except ImportError: test.skip_test("Could not find ninja module. Skipping test.\n") -ninja_bin = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/command_line_targets.py b/test/ninja/command_line_targets.py index 9fa77eae47..b043f62bca 100644 --- a/test/ninja/command_line_targets.py +++ b/test/ninja/command_line_targets.py @@ -36,12 +36,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/copy_function_command.py b/test/ninja/copy_function_command.py index 7e999b33e5..13036ee3a0 100644 --- a/test/ninja/copy_function_command.py +++ b/test/ninja/copy_function_command.py @@ -37,12 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/default_targets.py b/test/ninja/default_targets.py index 1b2f2b9f4b..7752d5ab7e 100644 --- a/test/ninja/default_targets.py +++ b/test/ninja/default_targets.py @@ -36,9 +36,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath( - os.path.join(ninja.__file__, os.pardir, 'data', 'bin', 'ninja' + _exe) -) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/force_scons_callback.py b/test/ninja/force_scons_callback.py index e0da0ddd31..b668f92c98 100644 --- a/test/ninja/force_scons_callback.py +++ b/test/ninja/force_scons_callback.py @@ -37,9 +37,8 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath( - os.path.join(ninja.__file__, os.pardir, "data", "bin", "ninja" + _exe) -) +ninja_bin = test.ninja_binary() + test.dir_fixture("ninja-fixture") diff --git a/test/ninja/generate_and_build.py b/test/ninja/generate_and_build.py index e1c26d4bb0..c14af70452 100644 --- a/test/ninja/generate_and_build.py +++ b/test/ninja/generate_and_build.py @@ -37,12 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/generate_and_build_cxx.py b/test/ninja/generate_and_build_cxx.py index 074a5cb9af..51f68dca00 100644 --- a/test/ninja/generate_and_build_cxx.py +++ b/test/ninja/generate_and_build_cxx.py @@ -37,12 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/generate_source.py b/test/ninja/generate_source.py index 8300176c2e..f4bd0c0aef 100644 --- a/test/ninja/generate_source.py +++ b/test/ninja/generate_source.py @@ -37,12 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/generated_sources_alias.py b/test/ninja/generated_sources_alias.py index 2c4ed36ae0..3e7f9d16b1 100644 --- a/test/ninja/generated_sources_alias.py +++ b/test/ninja/generated_sources_alias.py @@ -37,9 +37,8 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.BIN_DIR, - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() + test.dir_fixture('ninja-fixture') diff --git a/test/ninja/iterative_speedup.py b/test/ninja/iterative_speedup.py index e5673b0b42..8190175326 100644 --- a/test/ninja/iterative_speedup.py +++ b/test/ninja/iterative_speedup.py @@ -39,12 +39,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/mingw_command_generator_action.py b/test/ninja/mingw_command_generator_action.py index 58c5106427..8fc08a816a 100644 --- a/test/ninja/mingw_command_generator_action.py +++ b/test/ninja/mingw_command_generator_action.py @@ -52,9 +52,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.BIN_DIR, - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/mingw_depfile_format.py b/test/ninja/mingw_depfile_format.py index 5de7b5fbca..e9c89a0f63 100644 --- a/test/ninja/mingw_depfile_format.py +++ b/test/ninja/mingw_depfile_format.py @@ -36,9 +36,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.BIN_DIR, - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/mkdir_function_command.py b/test/ninja/mkdir_function_command.py index 8a17623e7f..c01cb982f7 100644 --- a/test/ninja/mkdir_function_command.py +++ b/test/ninja/mkdir_function_command.py @@ -37,12 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.write('SConstruct', """ SetOption('experimental','ninja') diff --git a/test/ninja/multi_env.py b/test/ninja/multi_env.py index e5da6cf885..6aaeccd584 100644 --- a/test/ninja/multi_env.py +++ b/test/ninja/multi_env.py @@ -37,12 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test = TestSCons.TestSCons() diff --git a/test/ninja/ninja_command_line.py b/test/ninja/ninja_command_line.py index d8e3c08107..d6744aa713 100644 --- a/test/ninja/ninja_command_line.py +++ b/test/ninja/ninja_command_line.py @@ -37,12 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture', 'src') diff --git a/test/ninja/ninja_conftest.py b/test/ninja/ninja_conftest.py index 91d2e03b9b..a92ecd9935 100644 --- a/test/ninja/ninja_conftest.py +++ b/test/ninja/ninja_conftest.py @@ -37,12 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/ninja_file_deterministic.py b/test/ninja/ninja_file_deterministic.py index 9832f226ac..232e7abba5 100644 --- a/test/ninja/ninja_file_deterministic.py +++ b/test/ninja/ninja_file_deterministic.py @@ -39,9 +39,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.BIN_DIR, - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/ninja_handle_control_c_rebuild.py b/test/ninja/ninja_handle_control_c_rebuild.py index 9f6b41366c..5635367395 100644 --- a/test/ninja/ninja_handle_control_c_rebuild.py +++ b/test/ninja/ninja_handle_control_c_rebuild.py @@ -23,7 +23,7 @@ # """ This test ensures if ninja gets a control-c (or other interrupting signal) while -regenerating the build.ninja, it doesn't remove the build.ninja leaving it +regenerating the build.ninja, it doesn't remove the build.ninja leaving it in an unworkable state. """ import os @@ -41,9 +41,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath( - os.path.join(ninja.__file__, os.pardir, "data", "bin", "ninja" + _exe) -) +ninja_bin = test.ninja_binary() test.dir_fixture("ninja-fixture") diff --git a/test/ninja/no_for_sig_subst.py b/test/ninja/no_for_sig_subst.py index a0292ae029..da33f8d1c0 100644 --- a/test/ninja/no_for_sig_subst.py +++ b/test/ninja/no_for_sig_subst.py @@ -37,12 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/response_file.py b/test/ninja/response_file.py index 3d23c2b2ae..e9f778ae8c 100644 --- a/test/ninja/response_file.py +++ b/test/ninja/response_file.py @@ -42,12 +42,7 @@ _exe = TestSCons._exe _obj = TestSCons._obj -ninja_bin = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/shell_command.py b/test/ninja/shell_command.py index a6c48c4288..0ed8f2a0d0 100644 --- a/test/ninja/shell_command.py +++ b/test/ninja/shell_command.py @@ -37,12 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath(os.path.join( - ninja.__file__, - os.pardir, - 'data', - 'bin', - 'ninja' + _exe)) +ninja_bin = test.ninja_binary() test.dir_fixture('ninja-fixture') diff --git a/test/ninja/shutdown_scons_daemon.py b/test/ninja/shutdown_scons_daemon.py index 64ec2c717a..25823d742e 100644 --- a/test/ninja/shutdown_scons_daemon.py +++ b/test/ninja/shutdown_scons_daemon.py @@ -44,9 +44,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = os.path.abspath( - os.path.join(ninja.__file__, os.pardir, "data", "bin", "ninja" + _exe) -) +ninja_bin = test.ninja_binary() test.dir_fixture("ninja-fixture") diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py index 51aa7cf38a..d6c51251e8 100644 --- a/testing/framework/TestSCons.py +++ b/testing/framework/TestSCons.py @@ -40,17 +40,14 @@ import re import shutil import sys -import time import subprocess as sp +import time import zipfile from collections import namedtuple +from SCons.Util import get_hash_format, get_current_hash_algorithm_used from TestCommon import * from TestCommon import __all__, _python_ -from SCons.Util import get_hash_format, get_current_hash_algorithm_used - -from TestCmd import Popen -from TestCmd import PIPE # Some tests which verify that SCons has been packaged properly need to # look for specific version file names. Replicating the version number @@ -224,6 +221,8 @@ def restore_sconsflags(sconsflags) -> None: # Helpers for Configure()'s config.log processing ConfigCheckInfo = namedtuple('ConfigCheckInfo', ['check_string', 'result', 'cached', 'temp_filename']) + + # check_string: the string output to for this checker # results : The expected results for each check # cached : If the corresponding check is expected to be cached @@ -234,6 +233,7 @@ class NoMatch(Exception): """ Exception for matchPart to indicate there was no match found in the passed logfile """ + def __init__(self, p) -> None: self.pos = p @@ -418,7 +418,8 @@ def where_is(self, prog, path=None, pathext=None): return None - def wrap_stdout(self, build_str: str="", read_str: str="", error: int=0, cleaning: int=0) -> str: + def wrap_stdout(self, build_str: str = "", read_str: str = "", error: int = 0, + cleaning: int = 0) -> str: """Wraps "expect" strings in SCons boilerplate. Given strings of expected output specific to a test, @@ -444,11 +445,11 @@ def wrap_stdout(self, build_str: str="", read_str: str="", error: int=0, cleanin term = f"scons: done {lc}ing targets.\n" return "scons: Reading SConscript files ...\n" + \ - read_str + \ - "scons: done reading SConscript files.\n" + \ - f"scons: {cap}ing targets ...\n" + \ - build_str + \ - term + read_str + \ + "scons: done reading SConscript files.\n" + \ + f"scons: {cap}ing targets ...\n" + \ + build_str + \ + term def run(self, *args, **kw) -> None: """ @@ -484,7 +485,7 @@ def run(self, *args, **kw) -> None: # kw['options'] = ' '.join(options) # TestCommon.run(self, *args, **kw) - def up_to_date(self, arguments: str='.', read_str: str="", **kw) -> None: + def up_to_date(self, arguments: str = '.', read_str: str = "", **kw) -> None: """Asserts that all of the targets listed in arguments is up to date, but does not make any assumptions on other targets. This function is most useful in conjunction with the -n option. @@ -500,7 +501,7 @@ def up_to_date(self, arguments: str='.', read_str: str="", **kw) -> None: kw['match'] = self.match_re_dotall self.run(**kw) - def not_up_to_date(self, arguments: str='.', read_str: str="", **kw) -> None: + def not_up_to_date(self, arguments: str = '.', read_str: str = "", **kw) -> None: """Asserts that none of the targets listed in arguments is up to date, but does not make any assumptions on other targets. This function is most useful in conjunction with the -n option. @@ -510,7 +511,8 @@ def not_up_to_date(self, arguments: str='.', read_str: str="", **kw) -> None: s = f"{s}(?!scons: `{re.escape(arg)}' is up to date.)" s = f"({s}[^\n]*\n)*" kw['arguments'] = arguments - stdout = re.escape(self.wrap_stdout(read_str=read_str, build_str='ARGUMENTSGOHERE')) + stdout = re.escape( + self.wrap_stdout(read_str=read_str, build_str='ARGUMENTSGOHERE')) kw['stdout'] = stdout.replace('ARGUMENTSGOHERE', s) kw['match'] = self.match_re_dotall self.run(**kw) @@ -619,15 +621,15 @@ def RunPair(option, expected) -> None: return warning - def diff_substr(self, expect, actual, prelen: int=20, postlen: int=40) -> str: + def diff_substr(self, expect, actual, prelen: int = 20, postlen: int = 40) -> str: i = 0 for x, y in zip(expect, actual): if x != y: return "Actual did not match expect at char %d:\n" \ " Expect: %s\n" \ " Actual: %s\n" \ - % (i, repr(expect[i - prelen:i + postlen]), - repr(actual[i - prelen:i + postlen])) + % (i, repr(expect[i - prelen:i + postlen]), + repr(actual[i - prelen:i + postlen])) i = i + 1 return "Actual matched the expected output???" @@ -671,7 +673,7 @@ def normalize_ps(self, s): return s @staticmethod - def to_bytes_re_sub(pattern, repl, string, count: int=0, flags: int=0): + def to_bytes_re_sub(pattern, repl, string, count: int = 0, flags: int = 0): """ Wrapper around re.sub to change pattern and repl to bytes to work with both python 2 & 3 @@ -713,8 +715,9 @@ def normalize_pdf(self, s): d = zlib.decompress(s[b:e]) d = self.to_bytes_re_sub(r'%%CreationDate: [^\n]*\n', r'%%CreationDate: 1970 Jan 01 00:00:00\n', d) - d = self.to_bytes_re_sub(r'%DVIPSSource: TeX output \d\d\d\d\.\d\d\.\d\d:\d\d\d\d', - r'%DVIPSSource: TeX output 1970.01.01:0000', d) + d = self.to_bytes_re_sub( + r'%DVIPSSource: TeX output \d\d\d\d\.\d\d\.\d\d:\d\d\d\d', + r'%DVIPSSource: TeX output 1970.01.01:0000', d) d = self.to_bytes_re_sub(r'/(BaseFont|FontName) /[A-Z]{6}', r'/\1 /XXXXXX', d) r.append(d) @@ -754,10 +757,9 @@ def get_sconsignname(self) -> str: if hash_format is None and current_hash_algorithm == 'md5': return ".sconsign" else: - database_prefix=f".sconsign_{current_hash_algorithm}" + database_prefix = f".sconsign_{current_hash_algorithm}" return database_prefix - def unlink_sconsignfile(self, name: str = '.sconsign.dblite') -> None: """Delete the sconsign file. @@ -850,7 +852,8 @@ def java_where_includes(self, version=None): '/usr/lib/jvm/default-java/include/jni.h', '/usr/lib/jvm/java-*-oracle/include/jni.h'] else: - jni_dirs = [f'/System/Library/Frameworks/JavaVM.framework/Versions/{version}*/Headers/jni.h'] + jni_dirs = [ + f'/System/Library/Frameworks/JavaVM.framework/Versions/{version}*/Headers/jni.h'] jni_dirs.extend([f'/usr/lib/jvm/java-*-sun-{version}*/include/jni.h', f'/usr/lib/jvm/java-{version}*-openjdk*/include/jni.h', f'/usr/java/jdk{version}*/include/jni.h']) @@ -975,7 +978,8 @@ def java_where_java(self, version=None) -> str: where_java = self.where_is('java', ENV['PATH']) if not where_java: - self.skip_test("Could not find Java java, skipping test(s).\n", from_fw=True) + self.skip_test("Could not find Java java, skipping test(s).\n", + from_fw=True) elif sys.platform == "darwin": self.java_mac_check(where_java, 'java') @@ -996,7 +1000,8 @@ def java_where_javac(self, version=None) -> tuple[str, str]: else: where_javac = self.where_is('javac', ENV['PATH']) if not where_javac: - self.skip_test("Could not find Java javac, skipping test(s).\n", from_fw=True) + self.skip_test("Could not find Java javac, skipping test(s).\n", + from_fw=True) elif sys.platform == "darwin": self.java_mac_check(where_javac, 'javac') @@ -1050,7 +1055,8 @@ def java_where_javah(self, version=None) -> str: else: where_javah = self.where_is('javah', ENV['PATH']) if not where_javah: - self.skip_test("Could not find Java javah, skipping test(s).\n", from_fw=True) + self.skip_test("Could not find Java javah, skipping test(s).\n", + from_fw=True) return where_javah def java_where_rmic(self, version=None) -> str: @@ -1068,7 +1074,9 @@ def java_where_rmic(self, version=None) -> str: else: where_rmic = self.where_is('rmic', ENV['PATH']) if not where_rmic: - self.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n", from_fw=True) + self.skip_test( + "Could not find Java rmic, skipping non-simulated test(s).\n", + from_fw=True) return where_rmic def java_get_class_files(self, dir): @@ -1079,7 +1087,16 @@ def java_get_class_files(self, dir): result.append(os.path.join(dirpath, fname)) return sorted(result) - def Qt_dummy_installation(self, dir: str='qt') -> None: + def ninja_binary(self): + try: + import ninja + except ImportError: + return False + + return os.path.abspath(os.path.join(ninja.BIN_DIR, 'ninja' + _exe)) + + + def Qt_dummy_installation(self, dir: str = 'qt') -> None: # create a dummy qt installation self.subdir(dir, [dir, 'bin'], [dir, 'include'], [dir, 'lib']) @@ -1185,11 +1202,11 @@ def Qt_dummy_installation(self, dir: str='qt') -> None: self.QT_UIC = f"{_python_} {self.workpath(dir, 'bin', 'myuic.py')}" self.QT_LIB_DIR = self.workpath(dir, 'lib') - def Qt_create_SConstruct(self, place, qt_tool: str='qt3') -> None: + def Qt_create_SConstruct(self, place, qt_tool: str = 'qt3') -> None: if isinstance(place, list): place = self.workpath(*place) - var_prefix=qt_tool.upper() + var_prefix = qt_tool.upper() self.write(place, f"""\ if ARGUMENTS.get('noqtdir', 0): {var_prefix}DIR = None @@ -1239,7 +1256,7 @@ def coverage_run(self) -> bool: """ return 'COVERAGE_PROCESS_START' in os.environ or 'COVERAGE_FILE' in os.environ - def skip_if_not_msvc(self, check_platform: bool=True) -> None: + def skip_if_not_msvc(self, check_platform: bool = True) -> None: """ Skip test if MSVC is not available. Check whether we are on a Windows platform and skip the test if @@ -1264,10 +1281,10 @@ def skip_if_not_msvc(self, check_platform: bool=True) -> None: pass def checkConfigureLogAndStdout(self, checks, - logfile: str='config.log', - sconf_dir: str='.sconf_temp', - sconstruct: str="SConstruct", - doCheckLog: bool=True, doCheckStdout: bool=True): + logfile: str = 'config.log', + sconf_dir: str = '.sconf_temp', + sconstruct: str = "SConstruct", + doCheckLog: bool = True, doCheckStdout: bool = True): """ Verify expected output from Configure. Used to verify the expected output from using Configure() @@ -1298,7 +1315,8 @@ def checkConfigureLogAndStdout(self, checks, # sys.stderr.write("LOGFILE[%s]:%s"%(type(logfile),logfile)) if (doCheckLog and - logfile.find("scons: warning: The stored build information has an unexpected class.") >= 0): + logfile.find( + "scons: warning: The stored build information has an unexpected class.") >= 0): self.fail_test() log = r'file \S*%s\,line \d+:' % re.escape(sconstruct) + ls @@ -1321,7 +1339,7 @@ def checkConfigureLogAndStdout(self, checks, result_cached = 1 for bld_desc in check_info.cached: # each TryXXX for ext, flag in bld_desc: # each file in TryBuild - conf_filename = re.escape(check_info.temp_filename%ext) + conf_filename = re.escape(check_info.temp_filename % ext) if flag == self.NCR: # NCR = Non Cached Rebuild @@ -1339,8 +1357,9 @@ def checkConfigureLogAndStdout(self, checks, re.escape("scons: Configure: \"") + \ conf_filename + \ re.escape("\" is up to date.") + ls - log = log + re.escape("scons: Configure: The original builder " - "output was:") + ls + log = log + re.escape( + "scons: Configure: The original builder " + "output was:") + ls log = f"{log}( \\|.*{ls})+" if flag == self.NCF: # non-cached rebuild failure @@ -1351,8 +1370,10 @@ def checkConfigureLogAndStdout(self, checks, log = log + \ re.escape("scons: Configure: Building \"") + \ conf_filename + \ - re.escape("\" failed in a previous run and all its sources are up to date.") + ls - log = log + re.escape("scons: Configure: The original builder output was:") + ls + re.escape( + "\" failed in a previous run and all its sources are up to date.") + ls + log = log + re.escape( + "scons: Configure: The original builder output was:") + ls log = f"{log}( \\|.*{ls})+" if result_cached: result = f"(cached) {check_info.result}" @@ -1396,11 +1417,9 @@ def checkConfigureLogAndStdout(self, checks, print("-----------------------------------------------------") self.fail_test() - - def checkLogAndStdout(self, checks, results, cached, logfile, sconf_dir, sconstruct, - doCheckLog: bool=True, doCheckStdout: bool=True): + doCheckLog: bool = True, doCheckStdout: bool = True): """ Verify expected output from Configure. Used to verify the expected output from using Configure() @@ -1434,7 +1453,8 @@ def checkLogAndStdout(self, checks, results, cached, # sys.stderr.write("LOGFILE[%s]:%s"%(type(logfile),logfile)) if (doCheckLog and - logfile.find("scons: warning: The stored build information has an unexpected class.") >= 0): + logfile.find( + "scons: warning: The stored build information has an unexpected class.") >= 0): self.fail_test() sconf_dir = sconf_dir @@ -1462,10 +1482,12 @@ def checkLogAndStdout(self, checks, results, cached, for bld_desc in cache_desc: # each TryXXX for ext, flag in bld_desc: # each file in TryBuild if ext in ['.c', '.cpp']: - conf_filename = re.escape(os.path.join(sconf_dir, "conftest")) +\ + conf_filename = re.escape( + os.path.join(sconf_dir, "conftest")) + \ r'_[a-z0-9]{32,64}_\d+%s' % re.escape(ext) elif ext == '': - conf_filename = re.escape(os.path.join(sconf_dir, "conftest")) +\ + conf_filename = re.escape( + os.path.join(sconf_dir, "conftest")) + \ r'_[a-z0-9]{32,64}(_\d+_[a-z0-9]{32,64})?' else: @@ -1477,8 +1499,10 @@ def checkLogAndStdout(self, checks, results, cached, # this shortcut should be sufficient. # TODO: perhaps revisit and/or fix file naming for intermediate files in # Configure context logic - conf_filename = re.escape(os.path.join(sconf_dir, "conftest")) +\ - r'_[a-z0-9]{32,64}_\d+(_[a-z0-9]{32,64})?%s' % re.escape(ext) + conf_filename = re.escape( + os.path.join(sconf_dir, "conftest")) + \ + r'_[a-z0-9]{32,64}_\d+(_[a-z0-9]{32,64})?%s' % re.escape( + ext) if flag == self.NCR: # NCR = Non Cached Rebuild @@ -1496,8 +1520,9 @@ def checkLogAndStdout(self, checks, results, cached, re.escape("scons: Configure: \"") + \ conf_filename + \ re.escape("\" is up to date.") + ls - log = log + re.escape("scons: Configure: The original builder " - "output was:") + ls + log = log + re.escape( + "scons: Configure: The original builder " + "output was:") + ls log = f"{log}( \\|.*{ls})+" if flag == self.NCF: # non-cached rebuild failure @@ -1508,8 +1533,10 @@ def checkLogAndStdout(self, checks, results, cached, log = log + \ re.escape("scons: Configure: Building \"") + \ conf_filename + \ - re.escape("\" failed in a previous run and all its sources are up to date.") + ls - log = log + re.escape("scons: Configure: The original builder output was:") + ls + re.escape( + "\" failed in a previous run and all its sources are up to date.") + ls + log = log + re.escape( + "scons: Configure: The original builder output was:") + ls log = f"{log}( \\|.*{ls})+" # cnt = cnt + 1 if result_cached: @@ -1562,7 +1589,7 @@ def get_python_version(self) -> str: # see also sys.prefix documentation return python_minor_version_string() - def get_platform_python_info(self, python_h_required: bool=False): + def get_platform_python_info(self, python_h_required: bool = False): """Return information about Python. Returns a path to a Python executable suitable for testing on @@ -1577,7 +1604,8 @@ def get_platform_python_info(self, python_h_required: bool=False): """ python = os.environ.get('python_executable', self.where_is('python')) if not python: - self.skip_test('Can not find installed "python", skipping test.\n', from_fw=True) + self.skip_test('Can not find installed "python", skipping test.\n', + from_fw=True) # construct a program to run in the intended environment # in order to fetch the characteristics of that Python. @@ -1635,7 +1663,8 @@ def venv_path(): stdout = self.stdout() or "" incpath, libpath, libname, python_h = stdout.strip().split('\n') if python_h == "False" and python_h_required: - self.skip_test('Can not find required "Python.h", skipping test.\n', from_fw=True) + self.skip_test('Can not find required "Python.h", skipping test.\n', + from_fw=True) return (python, incpath, libpath, libname + _lib) @@ -1656,7 +1685,7 @@ def start(self, *args, **kw): restore_sconsflags(sconsflags) return p - def wait_for(self, fname, timeout: float=20.0, popen=None) -> None: + def wait_for(self, fname, timeout: float = 20.0, popen=None) -> None: """ Waits for the specified file name to exist. """ @@ -2004,7 +2033,7 @@ def copy_timing_configuration(self, source_dir, dest_dir) -> None: destination = source.replace(source_dir, dest_dir) shutil.copy2(source, destination) - def up_to_date(self, arguments: str='.', read_str: str="", **kw) -> None: + def up_to_date(self, arguments: str = '.', read_str: str = "", **kw) -> None: """Asserts that all of the targets listed in arguments is up to date, but does not make any assumptions on other targets. This function is most useful in conjunction with the -n option. @@ -2025,7 +2054,6 @@ def up_to_date(self, arguments: str='.', read_str: str="", **kw) -> None: self.run(**kw) - # In some environments, $AR will generate a warning message to stderr # if the library doesn't previously exist and is being created. One # way to fix this is to tell AR to be quiet (sometimes the 'c' flag), From 0b5ab8b6e4ab4fab4206220c593d9e7d88b0ebf9 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Mon, 25 Nov 2024 12:43:54 -0800 Subject: [PATCH 2/5] updated so TestSCons.NINJA_BINARY is set and used by all such tests --- test/ninja/build_libraries.py | 2 +- test/ninja/command_line_targets.py | 2 +- test/ninja/copy_function_command.py | 2 +- test/ninja/default_targets.py | 2 +- test/ninja/force_scons_callback.py | 3 +-- test/ninja/generate_and_build.py | 2 +- test/ninja/generate_and_build_cxx.py | 2 +- test/ninja/generate_source.py | 2 +- test/ninja/generated_sources_alias.py | 3 +-- test/ninja/iterative_speedup.py | 2 +- test/ninja/mingw_command_generator_action.py | 2 +- test/ninja/mingw_depfile_format.py | 2 +- test/ninja/mkdir_function_command.py | 2 +- test/ninja/multi_env.py | 2 +- test/ninja/ninja_command_line.py | 2 +- test/ninja/ninja_conftest.py | 2 +- test/ninja/ninja_file_deterministic.py | 2 +- test/ninja/ninja_handle_control_c_rebuild.py | 3 +-- test/ninja/no_for_sig_subst.py | 2 +- test/ninja/response_file.py | 2 +- test/ninja/shell_command.py | 2 +- test/ninja/shutdown_scons_daemon.py | 2 +- testing/framework/TestCmd.py | 3 +++ testing/framework/TestSCons.py | 18 ++++++++---------- 24 files changed, 33 insertions(+), 35 deletions(-) diff --git a/test/ninja/build_libraries.py b/test/ninja/build_libraries.py index 0561f6f544..9436053c7b 100644 --- a/test/ninja/build_libraries.py +++ b/test/ninja/build_libraries.py @@ -34,7 +34,7 @@ except ImportError: test.skip_test("Could not find ninja module. Skipping test.\n") -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/command_line_targets.py b/test/ninja/command_line_targets.py index b043f62bca..1a6033b9e0 100644 --- a/test/ninja/command_line_targets.py +++ b/test/ninja/command_line_targets.py @@ -36,7 +36,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/copy_function_command.py b/test/ninja/copy_function_command.py index 13036ee3a0..e8721a19d9 100644 --- a/test/ninja/copy_function_command.py +++ b/test/ninja/copy_function_command.py @@ -37,7 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/default_targets.py b/test/ninja/default_targets.py index 7752d5ab7e..e809d75379 100644 --- a/test/ninja/default_targets.py +++ b/test/ninja/default_targets.py @@ -36,7 +36,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/force_scons_callback.py b/test/ninja/force_scons_callback.py index b668f92c98..3ae5a5a616 100644 --- a/test/ninja/force_scons_callback.py +++ b/test/ninja/force_scons_callback.py @@ -37,8 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() - +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture("ninja-fixture") diff --git a/test/ninja/generate_and_build.py b/test/ninja/generate_and_build.py index c14af70452..9d056836fa 100644 --- a/test/ninja/generate_and_build.py +++ b/test/ninja/generate_and_build.py @@ -37,7 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/generate_and_build_cxx.py b/test/ninja/generate_and_build_cxx.py index 51f68dca00..80faa4bbe1 100644 --- a/test/ninja/generate_and_build_cxx.py +++ b/test/ninja/generate_and_build_cxx.py @@ -37,7 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/generate_source.py b/test/ninja/generate_source.py index f4bd0c0aef..b848b17c14 100644 --- a/test/ninja/generate_source.py +++ b/test/ninja/generate_source.py @@ -37,7 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/generated_sources_alias.py b/test/ninja/generated_sources_alias.py index 3e7f9d16b1..22ca734b33 100644 --- a/test/ninja/generated_sources_alias.py +++ b/test/ninja/generated_sources_alias.py @@ -37,8 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() - +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/iterative_speedup.py b/test/ninja/iterative_speedup.py index 8190175326..cbd4b6ac99 100644 --- a/test/ninja/iterative_speedup.py +++ b/test/ninja/iterative_speedup.py @@ -39,7 +39,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/mingw_command_generator_action.py b/test/ninja/mingw_command_generator_action.py index 8fc08a816a..ad7878469a 100644 --- a/test/ninja/mingw_command_generator_action.py +++ b/test/ninja/mingw_command_generator_action.py @@ -52,7 +52,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/mingw_depfile_format.py b/test/ninja/mingw_depfile_format.py index e9c89a0f63..7792440d0e 100644 --- a/test/ninja/mingw_depfile_format.py +++ b/test/ninja/mingw_depfile_format.py @@ -36,7 +36,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/mkdir_function_command.py b/test/ninja/mkdir_function_command.py index c01cb982f7..3fd678a3f8 100644 --- a/test/ninja/mkdir_function_command.py +++ b/test/ninja/mkdir_function_command.py @@ -37,7 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.write('SConstruct', """ SetOption('experimental','ninja') diff --git a/test/ninja/multi_env.py b/test/ninja/multi_env.py index 6aaeccd584..7b113ed22b 100644 --- a/test/ninja/multi_env.py +++ b/test/ninja/multi_env.py @@ -37,7 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test = TestSCons.TestSCons() diff --git a/test/ninja/ninja_command_line.py b/test/ninja/ninja_command_line.py index d6744aa713..8fc232e428 100644 --- a/test/ninja/ninja_command_line.py +++ b/test/ninja/ninja_command_line.py @@ -37,7 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture', 'src') diff --git a/test/ninja/ninja_conftest.py b/test/ninja/ninja_conftest.py index a92ecd9935..60c4b033ee 100644 --- a/test/ninja/ninja_conftest.py +++ b/test/ninja/ninja_conftest.py @@ -37,7 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/ninja_file_deterministic.py b/test/ninja/ninja_file_deterministic.py index 232e7abba5..3d226ca4e3 100644 --- a/test/ninja/ninja_file_deterministic.py +++ b/test/ninja/ninja_file_deterministic.py @@ -39,7 +39,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/ninja_handle_control_c_rebuild.py b/test/ninja/ninja_handle_control_c_rebuild.py index 5635367395..d187c5f437 100644 --- a/test/ninja/ninja_handle_control_c_rebuild.py +++ b/test/ninja/ninja_handle_control_c_rebuild.py @@ -40,8 +40,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe - -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture("ninja-fixture") diff --git a/test/ninja/no_for_sig_subst.py b/test/ninja/no_for_sig_subst.py index da33f8d1c0..ef2c795991 100644 --- a/test/ninja/no_for_sig_subst.py +++ b/test/ninja/no_for_sig_subst.py @@ -37,7 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/response_file.py b/test/ninja/response_file.py index e9f778ae8c..f2561bbfa9 100644 --- a/test/ninja/response_file.py +++ b/test/ninja/response_file.py @@ -42,7 +42,7 @@ _exe = TestSCons._exe _obj = TestSCons._obj -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/shell_command.py b/test/ninja/shell_command.py index 0ed8f2a0d0..8b3d134298 100644 --- a/test/ninja/shell_command.py +++ b/test/ninja/shell_command.py @@ -37,7 +37,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture('ninja-fixture') diff --git a/test/ninja/shutdown_scons_daemon.py b/test/ninja/shutdown_scons_daemon.py index 25823d742e..c646f970f8 100644 --- a/test/ninja/shutdown_scons_daemon.py +++ b/test/ninja/shutdown_scons_daemon.py @@ -44,7 +44,7 @@ _python_ = TestSCons._python_ _exe = TestSCons._exe -ninja_bin = test.ninja_binary() +ninja_bin = TestSCons.NINJA_BINARY test.dir_fixture("ninja-fixture") diff --git a/testing/framework/TestCmd.py b/testing/framework/TestCmd.py index 7307078c5e..56c282c990 100644 --- a/testing/framework/TestCmd.py +++ b/testing/framework/TestCmd.py @@ -337,6 +337,9 @@ IS_ROOT = False NEED_HELPER = os.environ.get('SCONS_NO_DIRECT_SCRIPT') + + + # sentinel for cases where None won't do _Null = object() diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py index d6c51251e8..5c95c24236 100644 --- a/testing/framework/TestSCons.py +++ b/testing/framework/TestSCons.py @@ -74,7 +74,8 @@ 'lib_', '_lib', 'dll_', - '_dll' + '_dll', + 'NINJA_BINARY' ]) machine_map = { @@ -103,6 +104,12 @@ _dll = dll_suffix dll_ = dll_prefix +try: + import ninja + NINJA_BINARY = os.path.abspath(os.path.join(ninja.BIN_DIR, 'ninja' + _exe)) +except ImportError: + NINJA_BINARY = None + if sys.platform == 'cygwin': # On Cygwin, os.path.normcase() lies, so just report back the # fact that the underlying Win32 OS is case-insensitive. @@ -1087,15 +1094,6 @@ def java_get_class_files(self, dir): result.append(os.path.join(dirpath, fname)) return sorted(result) - def ninja_binary(self): - try: - import ninja - except ImportError: - return False - - return os.path.abspath(os.path.join(ninja.BIN_DIR, 'ninja' + _exe)) - - def Qt_dummy_installation(self, dir: str = 'qt') -> None: # create a dummy qt installation From 6d70e82c31788fdbc95cc9dff0b116f632be5d62 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Mon, 25 Nov 2024 13:43:46 -0800 Subject: [PATCH 3/5] renamed SCons.Tool.ninja -> SCons.Tool.ninja_tool and added alias in tool loading logic. This fixes changes in this PR breaking JavaCommonTests because pypi's ninja module and SCons.Tool.ninja had the same name which python couldn't differentiate --- SCons/Tool/__init__.py | 1 + SCons/Tool/{ninja => ninja_tool}/Globals.py | 0 SCons/Tool/{ninja => ninja_tool}/Methods.py | 8 ++++---- SCons/Tool/{ninja => ninja_tool}/NinjaState.py | 2 +- SCons/Tool/{ninja => ninja_tool}/Overrides.py | 0 SCons/Tool/{ninja => ninja_tool}/Rules.py | 0 SCons/Tool/{ninja => ninja_tool}/Utils.py | 8 ++++---- SCons/Tool/{ninja => ninja_tool}/__init__.py | 10 +++++----- SCons/Tool/{ninja => ninja_tool}/ninja.xml | 0 SCons/Tool/{ninja => ninja_tool}/ninja_daemon_build.py | 0 SCons/Tool/{ninja => ninja_tool}/ninja_run_daemon.py | 0 SCons/Tool/{ninja => ninja_tool}/ninja_scons_daemon.py | 0 12 files changed, 15 insertions(+), 14 deletions(-) rename SCons/Tool/{ninja => ninja_tool}/Globals.py (100%) rename SCons/Tool/{ninja => ninja_tool}/Methods.py (97%) rename SCons/Tool/{ninja => ninja_tool}/NinjaState.py (99%) rename SCons/Tool/{ninja => ninja_tool}/Overrides.py (100%) rename SCons/Tool/{ninja => ninja_tool}/Rules.py (100%) rename SCons/Tool/{ninja => ninja_tool}/Utils.py (98%) rename SCons/Tool/{ninja => ninja_tool}/__init__.py (99%) rename SCons/Tool/{ninja => ninja_tool}/ninja.xml (100%) rename SCons/Tool/{ninja => ninja_tool}/ninja_daemon_build.py (100%) rename SCons/Tool/{ninja => ninja_tool}/ninja_run_daemon.py (100%) rename SCons/Tool/{ninja => ninja_tool}/ninja_scons_daemon.py (100%) diff --git a/SCons/Tool/__init__.py b/SCons/Tool/__init__.py index a7bc927ebc..23b7eeba2c 100644 --- a/SCons/Tool/__init__.py +++ b/SCons/Tool/__init__.py @@ -102,6 +102,7 @@ 'gettext': 'gettext_tool', 'clang++': 'clangxx', 'as': 'asm', + 'ninja' : 'ninja_tool' } diff --git a/SCons/Tool/ninja/Globals.py b/SCons/Tool/ninja_tool/Globals.py similarity index 100% rename from SCons/Tool/ninja/Globals.py rename to SCons/Tool/ninja_tool/Globals.py diff --git a/SCons/Tool/ninja/Methods.py b/SCons/Tool/ninja_tool/Methods.py similarity index 97% rename from SCons/Tool/ninja/Methods.py rename to SCons/Tool/ninja_tool/Methods.py index ff006c072e..01866e5f3f 100644 --- a/SCons/Tool/ninja/Methods.py +++ b/SCons/Tool/ninja_tool/Methods.py @@ -30,9 +30,9 @@ import SCons from SCons.Subst import SUBST_CMD -from SCons.Tool.ninja import NINJA_CUSTOM_HANDLERS, NINJA_RULES, NINJA_POOLS -from SCons.Tool.ninja.Globals import __NINJA_RULE_MAPPING -from SCons.Tool.ninja.Utils import get_targets_sources, get_dependencies, get_order_only, get_outputs, get_inputs, \ +from SCons.Tool.ninja_tool import NINJA_CUSTOM_HANDLERS, NINJA_RULES, NINJA_POOLS +from SCons.Tool.ninja_tool.Globals import __NINJA_RULE_MAPPING +from SCons.Tool.ninja_tool.Utils import get_targets_sources, get_dependencies, get_order_only, get_outputs, get_inputs, \ get_rule, get_path, generate_command, get_command_env, get_comstr if TYPE_CHECKING: @@ -46,7 +46,7 @@ def register_custom_handler(env, name, handler) -> None: def register_custom_rule_mapping(env, pre_subst_string, rule) -> None: """Register a function to call for a given rule.""" - SCons.Tool.ninja.Globals.__NINJA_RULE_MAPPING[pre_subst_string] = rule + __NINJA_RULE_MAPPING[pre_subst_string] = rule def register_custom_rule(env, rule, command, description: str="", deps=None, pool=None, use_depfile: bool=False, use_response_file: bool=False, response_file_content: str="$rspc") -> None: diff --git a/SCons/Tool/ninja/NinjaState.py b/SCons/Tool/ninja_tool/NinjaState.py similarity index 99% rename from SCons/Tool/ninja/NinjaState.py rename to SCons/Tool/ninja_tool/NinjaState.py index da7aa28f12..274331e0de 100644 --- a/SCons/Tool/ninja/NinjaState.py +++ b/SCons/Tool/ninja_tool/NinjaState.py @@ -753,7 +753,7 @@ def action_to_ninja_build(self, node, action=None): # Ninja builders out of being sources of ninja builders but I # can't fix every DAG problem so we just skip ninja_builders # if we find one - if SCons.Tool.ninja.NINJA_STATE.ninja_file == str(node): + if SCons.Tool.ninja_tool.NINJA_STATE.ninja_file == str(node): build = None elif isinstance(action, SCons.Action.FunctionAction): build = self.handle_func_action(node, action) diff --git a/SCons/Tool/ninja/Overrides.py b/SCons/Tool/ninja_tool/Overrides.py similarity index 100% rename from SCons/Tool/ninja/Overrides.py rename to SCons/Tool/ninja_tool/Overrides.py diff --git a/SCons/Tool/ninja/Rules.py b/SCons/Tool/ninja_tool/Rules.py similarity index 100% rename from SCons/Tool/ninja/Rules.py rename to SCons/Tool/ninja_tool/Rules.py diff --git a/SCons/Tool/ninja/Utils.py b/SCons/Tool/ninja_tool/Utils.py similarity index 98% rename from SCons/Tool/ninja/Utils.py rename to SCons/Tool/ninja_tool/Utils.py index 24d439ef5e..7782687791 100644 --- a/SCons/Tool/ninja/Utils.py +++ b/SCons/Tool/ninja_tool/Utils.py @@ -413,14 +413,14 @@ def ninja_stat(_self, path): """ try: - return SCons.Tool.ninja.Globals.NINJA_STAT_MEMO[path] + return SCons.Tool.ninja_tool.Globals.NINJA_STAT_MEMO[path] except KeyError: try: result = os.stat(path) except os.error: result = None - SCons.Tool.ninja.Globals.NINJA_STAT_MEMO[path] = result + SCons.Tool.ninja_tool.Globals.NINJA_STAT_MEMO[path] = result return result @@ -430,7 +430,7 @@ def ninja_whereis(thing, *_args, **_kwargs): # Optimize for success, this gets called significantly more often # when the value is already memoized than when it's not. try: - return SCons.Tool.ninja.Globals.NINJA_WHEREIS_MEMO[thing] + return SCons.Tool.ninja_tool.Globals.NINJA_WHEREIS_MEMO[thing] except KeyError: # TODO: Fix this to respect env['ENV']['PATH']... WPD # We do not honor any env['ENV'] or env[*] variables in the @@ -443,7 +443,7 @@ def ninja_whereis(thing, *_args, **_kwargs): # with shell quoting is nigh impossible. So I've decided to # cross that bridge when it's absolutely required. path = shutil.which(thing) - SCons.Tool.ninja.Globals.NINJA_WHEREIS_MEMO[thing] = path + SCons.Tool.ninja_tool.Globals.NINJA_WHEREIS_MEMO[thing] = path return path diff --git a/SCons/Tool/ninja/__init__.py b/SCons/Tool/ninja_tool/__init__.py similarity index 99% rename from SCons/Tool/ninja/__init__.py rename to SCons/Tool/ninja_tool/__init__.py index 7320d03852..d86c2c9d4d 100644 --- a/SCons/Tool/ninja/__init__.py +++ b/SCons/Tool/ninja_tool/__init__.py @@ -32,7 +32,7 @@ import SCons import SCons.Script -import SCons.Tool.ninja.Globals +from SCons.Tool.ninja_tool.Globals import ninja_builder_initialized from SCons.Script import GetOption from SCons.Util import sanitize_shell_env @@ -187,13 +187,13 @@ def ninja_emitter(target, source, env): def generate(env): """Generate the NINJA builders.""" - global NINJA_STATE, NINJA_CMDLINE_TARGETS + global NINJA_STATE, NINJA_CMDLINE_TARGETS, ninja_builder_initialized if 'ninja' not in GetOption('experimental'): return - if not SCons.Tool.ninja.Globals.ninja_builder_initialized: - SCons.Tool.ninja.Globals.ninja_builder_initialized = True + if not ninja_builder_initialized: + ninja_builder_initialized = True ninja_add_command_line_options() @@ -255,7 +255,7 @@ def ninja_generate_deps(env): pass else: env.Append(CCFLAGS='$CCDEPFLAGS') - + env.AddMethod(CheckNinjaCompdbExpand, "CheckNinjaCompdbExpand") # Provide a way for custom rule authors to easily access command diff --git a/SCons/Tool/ninja/ninja.xml b/SCons/Tool/ninja_tool/ninja.xml similarity index 100% rename from SCons/Tool/ninja/ninja.xml rename to SCons/Tool/ninja_tool/ninja.xml diff --git a/SCons/Tool/ninja/ninja_daemon_build.py b/SCons/Tool/ninja_tool/ninja_daemon_build.py similarity index 100% rename from SCons/Tool/ninja/ninja_daemon_build.py rename to SCons/Tool/ninja_tool/ninja_daemon_build.py diff --git a/SCons/Tool/ninja/ninja_run_daemon.py b/SCons/Tool/ninja_tool/ninja_run_daemon.py similarity index 100% rename from SCons/Tool/ninja/ninja_run_daemon.py rename to SCons/Tool/ninja_tool/ninja_run_daemon.py diff --git a/SCons/Tool/ninja/ninja_scons_daemon.py b/SCons/Tool/ninja_tool/ninja_scons_daemon.py similarity index 100% rename from SCons/Tool/ninja/ninja_scons_daemon.py rename to SCons/Tool/ninja_tool/ninja_scons_daemon.py From eeb5dfd941c580ac329ab2d8c97f3dfae4e5f0a9 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Mon, 25 Nov 2024 14:08:52 -0800 Subject: [PATCH 4/5] [ci skip] Update CHANGES/RELEASE with newer changes --- CHANGES.txt | 4 +++- RELEASE.txt | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 964296614d..54def0ad48 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -89,7 +89,9 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - Update ninja tool to use ninja.BIN_DIR to find pypi packaged ninja binary. python ninja package version 1.11.1.2 changed the location and previous logic no longer worked. - - Added ninja_binary() method to TestSCons to centralize logic to find ninja binary + - Added TestSCons.NINJA_BINARY to TestSCons to centralize logic to find ninja binary + - Refactored SCons.Tool.ninja -> SCons.Tool.ninja_tool, and added alias so + env.Tool('ninja') will still work. This avoids conflicting with the pypi module ninja. From Alex James: - On Darwin, PermissionErrors are now handled while trying to access diff --git a/RELEASE.txt b/RELEASE.txt index 4aa9697d39..af5fb2fd2f 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -201,7 +201,10 @@ DEVELOPMENT - Implemented type hints for Nodes. -- Added ninja_binary() method to TestSCons to centralize logic to find ninja binary +- Added TestSCons.NINJA_BINARY to TestSCons to centralize logic to find ninja binary + +- Refactored SCons.Tool.ninja -> SCons.Tool.ninja_tool, and added alias so env.Tool('ninja') + will still work. This avoids conflicting with the pypi module ninja. Thanks to the following contributors listed below for their contributions to this release. ========================================================================================== From eeb025f1591b0838f549b310883ab6a28eb42381 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 25 Nov 2024 09:42:40 -0700 Subject: [PATCH 5/5] Add Variables.defualted attribute Also document the Variables.unknown attribute as being the same thing as the ruturn from the UnknownVariables method. Signed-off-by: Mats Wichmann --- CHANGES.txt | 8 +- RELEASE.txt | 6 ++ SCons/Variables/VariablesTests.py | 9 +- SCons/Variables/__init__.py | 67 ++++++++++----- doc/man/scons.xml | 131 ++++++++++++++++++------------ 5 files changed, 146 insertions(+), 75 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 54def0ad48..aa29180f5a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -160,7 +160,13 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER variable names are given. - Update Clean and NoClean documentation. - Make sure unknown variables from a Variables file are recognized - as such (issue #4645) + as such. Previously only unknowns from the command line were + recognized (issue #4645). + - A Variables object now makes available a "defaulted" attribute, + a list of variable names that were set in the environment with + their values taken from the default in the variable description + (if a variable was set to the same value as the default in one + of the input sources, it is not included in this list). RELEASE 4.8.1 - Tue, 03 Sep 2024 17:22:20 -0700 diff --git a/RELEASE.txt b/RELEASE.txt index af5fb2fd2f..aa3e3c15db 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -76,6 +76,12 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY always returns a dict. The default remains to return different types depending on whether zero, one, or multiple construction +- A Variables object now makes available a "defaulted" attribute, + a list of variable names that were set in the environment with + their values taken from the default in the variable description + (if a variable was set to the same value as the default in one + of the input sources, it is not included in this list). + FIXES ----- diff --git a/SCons/Variables/VariablesTests.py b/SCons/Variables/VariablesTests.py index 80c4d11397..bc981e06fe 100644 --- a/SCons/Variables/VariablesTests.py +++ b/SCons/Variables/VariablesTests.py @@ -659,24 +659,28 @@ def test_AddOptionUpdatesUnknown(self) -> None: Get one unknown from args and one from a variables file. Add these later, making sure they no longer appear in unknowns after the subsequent Update(). + + While we're here, test the *defaulted* attribute. """ test = TestSCons.TestSCons() var_file = test.workpath('vars.py') test.write('vars.py', 'FROMFILE="added"') opts = SCons.Variables.Variables(files=var_file) - opts.Add('A', 'A test variable', "1") + opts.Add('A', 'A test variable', default="1") + opts.Add('B', 'Test variable B', default="1") args = { 'A' : 'a', 'ADDEDLATER' : 'notaddedyet', } env = Environment() - opts.Update(env,args) + opts.Update(env, args) r = opts.UnknownVariables() with self.subTest(): self.assertEqual('notaddedyet', r['ADDEDLATER']) self.assertEqual('added', r['FROMFILE']) self.assertEqual('a', env['A']) + self.assertEqual(['B'], opts.defaulted) opts.Add('ADDEDLATER', 'An option not present initially', "1") opts.Add('FROMFILE', 'An option from a file also absent', "1") @@ -693,6 +697,7 @@ def test_AddOptionUpdatesUnknown(self) -> None: self.assertEqual('added', env['ADDEDLATER']) self.assertNotIn('FROMFILE', r) self.assertEqual('added', env['FROMFILE']) + self.assertEqual(['B'], opts.defaulted) def test_AddOptionWithAliasUpdatesUnknown(self) -> None: """Test updating of the 'unknown' dict (with aliases)""" diff --git a/SCons/Variables/__init__.py b/SCons/Variables/__init__.py index 1826c64445..de26e7b65f 100644 --- a/SCons/Variables/__init__.py +++ b/SCons/Variables/__init__.py @@ -27,6 +27,7 @@ import os.path import sys +from contextlib import suppress from functools import cmp_to_key from typing import Callable, Sequence @@ -58,22 +59,30 @@ class Variable: __slots__ = ('key', 'aliases', 'help', 'default', 'validator', 'converter', 'do_subst') def __lt__(self, other): - """Comparison fuction so Variable instances sort.""" + """Comparison fuction so :class:`Variable` instances sort.""" return self.key < other.key def __str__(self) -> str: - """Provide a way to "print" a Variable object.""" + """Provide a way to "print" a :class:`Variable` object.""" return ( - f"({self.key!r}, {self.aliases}, {self.help!r}, {self.default!r}, " + f"({self.key!r}, {self.aliases}, " + f"help={self.help!r}, default={self.default!r}, " f"validator={self.validator}, converter={self.converter})" ) class Variables: - """A container for multiple Build Variables. + """A container for Build Variables. - Includes methods to updates the environment with the variables, - and to render the help text. + Includes a method to populate the variables with values into a + construction envirionment, and methods to render the help text. + + Note that the pubic API for creating a ``Variables`` object is + :func:`SCons.Script.Variables`, a kind of factory function, which + defaults to supplying the contents of :attr:`~SCons.Script.ARGUMENTS` + as the *args* parameter if it was not otherwise given. That is the + behavior documented in the manpage for ``Variables`` - and different + from the default if you instantiate this directly. Arguments: files: string or list of strings naming variable config scripts @@ -83,11 +92,15 @@ class Variables: instead of a fresh instance. Currently inoperable (default ``False``) .. versionchanged:: 4.8.0 - The default for *is_global* changed to ``False`` (previously - ``True`` but it had no effect due to an implementation error). + The default for *is_global* changed to ``False`` (the previous + default ``True`` had no effect due to an implementation error). .. deprecated:: 4.8.0 *is_global* is deprecated. + + .. versionadded:: NEXT_RELEASE + The :attr:`defaulted` attribute now lists those variables which + were filled in from default values. """ def __init__( @@ -102,15 +115,18 @@ def __init__( files = [files] if files else [] self.files: Sequence[str] = files self.unknown: dict[str, str] = {} + self.defaulted: list[str] = [] def __str__(self) -> str: - """Provide a way to "print" a Variables object.""" - s = "Variables(\n options=[\n" - for option in self.options: - s += f" {str(option)},\n" - s += " ],\n" - s += f" args={self.args},\n files={self.files},\n unknown={self.unknown},\n)" - return s + """Provide a way to "print" a :class:`Variables` object.""" + opts = ',\n'.join((f" {option!s}" for option in self.options)) + return ( + f"Variables(\n options=[\n{opts}\n ],\n" + f" args={self.args},\n" + f" files={self.files},\n" + f" unknown={self.unknown},\n" + f" defaulted={self.defaulted},\n)" + ) # lint: W0622: Redefining built-in 'help' def _do_add( @@ -122,7 +138,7 @@ def _do_add( converter: Callable | None = None, **kwargs, ) -> None: - """Create a Variable and add it to the list. + """Create a :class:`Variable` and add it to the list. This is the internal implementation for :meth:`Add` and :meth:`AddVariables`. Not part of the public API. @@ -203,9 +219,9 @@ def Add( return self._do_add(key, *args, **kwargs) def AddVariables(self, *optlist) -> None: - """Add a list of Build Variables. + """Add Build Variables. - Each list element is a tuple/list of arguments to be passed on + Each *optlist* element is a sequence of arguments to be passed on to the underlying method for adding variables. Example:: @@ -223,13 +239,22 @@ def AddVariables(self, *optlist) -> None: def Update(self, env, args: dict | None = None) -> None: """Update an environment with the Build Variables. + Collects variables from the input sources which do not match + a variable description in this object. These are ignored for + purposes of adding to *env*, but can be retrieved using the + :meth:`UnknownVariables` method. Also collects variables which + are set in *env* from the default in a variable description and + not from the input sources. These are available in the + :attr:`defaulted` attribute. + Args: env: the environment to update. args: a dictionary of keys and values to update in *env*. If omitted, uses the saved :attr:`args` """ - # first pull in the defaults + # first pull in the defaults, except any which are None. values = {opt.key: opt.default for opt in self.options if opt.default is not None} + self.defaulted = list(values) # next set the values specified in any options script(s) for filename in self.files: @@ -256,6 +281,8 @@ def Update(self, env, args: dict | None = None) -> None: for option in self.options: if arg in option.aliases + [option.key,]: values[option.key] = value + with suppress(ValueError): + self.defaulted.remove(option.key) added = True if not added: self.unknown[arg] = value @@ -269,6 +296,8 @@ def Update(self, env, args: dict | None = None) -> None: for option in self.options: if arg in option.aliases + [option.key,]: values[option.key] = value + with suppress(ValueError): + self.defaulted.remove(option.key) added = True if not added: self.unknown[arg] = value diff --git a/doc/man/scons.xml b/doc/man/scons.xml index 8366bbd92e..1b7e886c69 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -4704,47 +4704,52 @@ env = conf.Finish() Command-Line Construction Variables -Often when building software, -specialized information needs to be conveyed at build time -to override the defaults in the build scripts. -Command-line arguments (like --implcit-cache) -and giving names of build targets are two ways to do that. -Another is to provide variable-assignment arguments -on the command line. -For the particular case where you want to specify new + +&SCons; depends on information stored in &consvars; to +control how targets are built. +It is often necessary to pass +specialized information at build time +to override the variables in the build scripts. +This can be done through variable-assignment arguments +on the command line and/or in stored variable files. + + + +For the case where you want to specify new values for &consvars;, &SCons; provides a &Variables; object to simplify collecting those and updating a &consenv; with the values. -The typical calling style looks like: +This helps processing commands lines like this: -scons VARIABLE=foo +scons VARIABLE=foo OTHERVAR=bar -Variables specified in the above way -can be manually processed by accessing the +Variables supplied on the command line +can always be manually processed by iterating the &ARGUMENTS; dictionary -(or &ARGLIST; list), -but using a &Variables; object allows you to describe +or the &ARGLIST; list, +However, using a &Variables; object allows you to describe anticipated variables, -convert them to a suitable type if necessary, -validate the values are within defined constraints, -and define defaults, help messages and aliases. -This is conceptually similar to the structure of options +perform necessary type conversion, +validate that values meet defined constraints, +and specify default values, help messages and aliases. +This provides a somewhat similar interface to option handling (see &f-link-AddOption;). -It also allows obtaining values from a saved variables file, +A &Variables; object also allows +obtaining values from a saved variables file, or from a custom dictionary in an &SConscript; file. The processed variables can then be applied to the desired &consenv;. -Roughly speaking, arguments are used to convey information to the -&SCons; program about how it should behave; -variables are used to convey information to the build -(although &SCons; does not enforce any such constraint). +Conceptually, command-line targets control what to build, +command-line variables (and variable files) control how to build, +and command-line options control how &SCons; operates +(although &SCons; does not enforce that separation). To obtain an object for manipulating variables, @@ -4755,23 +4760,23 @@ call the &Variables; factory function: Variables([files, [args]]) If files is a filename or list of filenames, -they are considered to be &Python; scripts which will -be executed to set variables when the +they are executed as &Python; scripts +to set saved variables when the Update -method is called - -this allows the use of &Python; syntax in the assignments. -A file can be the result of an earlier call to the +method is called. +This allows the use of &Python; syntax in the assignments. +A variables file can be the result of an previous call to the &Save; method. If files is not specified, or the files argument is None, -then no files will be read. +then no files will be processed. Supplying None is required if there are no files but you want to specify args as a positional argument; -this can be omitted if using the keyword argument style. +or you can use keyword arguments to avoid that. If any of files is missing, it is silently skipped. @@ -4822,25 +4827,42 @@ vars = Variables(files=None, args=ARGUMENTS) -A &Variables; object serves as a container for -descriptions of variables, -which are added by calling methods of the object. -Each variable consists of a name (which will -become a &consvar;), aliases for the name, +A &Variables; object is a container for variable descriptions, +added by calling the +Add or +AddVariables +methods. +Each variable description consists of a name (which will +be used as the &consvar; name), aliases for the name, a help message, a default value, and functions to validate and convert values. -Once the object is asked to process variables, -it matches up data from the input -sources it was given with the definitions, -and generates key-value pairs which are added -to the specified &consenv;, -except that if any variable was described -to have a default of None, -it is not added to -the construction environment unless it -appears in the input sources. -Otherwise, a variable not in the -input sources is added using its default value. +Processing of input sources +is deferred until the +Update +method is called, +at which time the variables are added to the +specified &consenv;. +Variables from the input sources which do not match any +names or aliases from the variable descriptions in this object are skipped, +except that a dictionary of their names and values are made available +in the .unknown attribute of the &Variables; object. +This list can also be obtained via the +UnknownVariables +method. +If a variable description has a default value +other than None and does not +appear in the input sources, +it is added to the &consenv; with its default value. +A list of variables set from their defaults and +not supplied a value in the input sources is +available as the .defaulted attribute +of the &Variables; object. +The unknown variables and defaulted information is +not available until the &Update; method has run. + + +New in NEXT_RELEASE: +the defaulted attribute. @@ -4848,7 +4870,8 @@ Note that since the variables are eventually added as &consvars;, you should choose variable names which do not unintentionally change pre-defined &consvars; that your project will make use of (see for a reference), -since variables obtained have values overridden, not merged. +since the specified values are assigned, not merged, +to the respective &consvars;. @@ -5037,7 +5060,9 @@ variables that were specified in the files and/or args parameters when &Variables; -was called, but the object was not actually configured for. +was called, but which were not configured in the object. +The same dictionary is also available as the +unknown attribute of the object. This information is not available until the Update method has run. @@ -5148,7 +5173,7 @@ vars.FormatVariableHelpText = my_format &SCons; provides five pre-defined variable types, accessible through factory functions that generate a tuple appropriate for directly passing to the -Add +Add or AddVariables methods. @@ -5191,7 +5216,7 @@ as false. Set up a variable named key -whose value may only be from +whose value will be a choice from a specified list ("enumeration") of values. The variable will have a default value of default @@ -5234,8 +5259,8 @@ converted to lower case. Set up a variable named key -whose value may be one or more -from a specified list of values. +whose value will be one or more +choices from a specified list of values. The variable will have a default value of default, and help