From 94ec4684be18aeb6fa8fd9c74d7bfa1baf83f926 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Sun, 2 Jun 2024 13:42:08 -0600 Subject: [PATCH 01/23] Make is_valid_construction_var really be a bool function Signed-off-by: Mats Wichmann --- CHANGES.txt | 13 +++++++++++-- SCons/Environment.py | 4 ++-- SCons/EnvironmentTests.py | 34 +++++++++++++++++----------------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4dff9dad4b..5be451f1dd 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,8 +4,11 @@ Change Log -NOTE: The 4.0.0 Release of SCons dropped Python 2.7 Support -NOTE: 4.3.0 now requires Python 3.6.0 and above. Python 3.5.x is no longer supported +NOTE: The 4.0.0 release of SCons dropped Python 2.7 support. Use 3.1.2 if + Python 2.7 support is required (but note old SCons releases are unsupported). +NOTE: Since SCons 4.3.0, Python 3.6.0 or above is required. +NOTE: Python 3.6 support is deprecated and will be dropped in a future reease. + python.org no longer supports 3.6 or 3.7, and will drop 3.8 in Oct. 2024. RELEASE VERSION/DATE TO BE FILLED IN LATER @@ -76,6 +79,12 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - Framework for scons-time tests adjusted so a path with a long username Windows has squashed doesn't get re-expanded. Fixes a problem seen on GitHub Windows runner which uses a name "runneradmin". + - SCons.Environment.is_valid_construction_var now returns a boolean to + match the convention that functions beginnig with "is" have yes/no + answers (previously returned either None or an re.match object). + Now matches the annotation and docstring (which were prematurely + updated in 4.6). All SCons usage except unit test was already fully + consistent with a bool. RELEASE 4.7.0 - Sun, 17 Mar 2024 17:22:20 -0700 diff --git a/SCons/Environment.py b/SCons/Environment.py index 5bf763d91a..ae44414154 100644 --- a/SCons/Environment.py +++ b/SCons/Environment.py @@ -512,9 +512,9 @@ def update(self, mapping) -> None: _is_valid_var = re.compile(r'[_a-zA-Z]\w*$') -def is_valid_construction_var(varstr) -> bool: +def is_valid_construction_var(varstr: str) -> bool: """Return True if *varstr* is a legitimate construction variable.""" - return _is_valid_var.match(varstr) + return bool(_is_valid_var.match(varstr)) class SubstitutionEnvironment: diff --git a/SCons/EnvironmentTests.py b/SCons/EnvironmentTests.py index 9d1229c44d..d213099d1a 100644 --- a/SCons/EnvironmentTests.py +++ b/SCons/EnvironmentTests.py @@ -2306,7 +2306,7 @@ def my_depends(target, dependency, tlist=tlist, dlist=dlist) -> None: exc_caught = None try: - env.ParseDepends(test.workpath('does_not_exist'), must_exist=1) + env.ParseDepends(test.workpath('does_not_exist'), must_exist=True) except IOError: exc_caught = 1 assert exc_caught, "did not catch expected IOError" @@ -2314,7 +2314,7 @@ def my_depends(target, dependency, tlist=tlist, dlist=dlist) -> None: del tlist[:] del dlist[:] - env.ParseDepends('$SINGLE', only_one=1) + env.ParseDepends('$SINGLE', only_one=True) t = list(map(str, tlist)) d = list(map(str, dlist)) assert t == ['f0'], t @@ -2331,7 +2331,7 @@ def my_depends(target, dependency, tlist=tlist, dlist=dlist) -> None: exc_caught = None try: - env.ParseDepends(test.workpath('multiple'), only_one=1) + env.ParseDepends(test.workpath('multiple'), only_one=True) except SCons.Errors.UserError: exc_caught = 1 assert exc_caught, "did not catch expected UserError" @@ -4147,33 +4147,33 @@ class EnvironmentVariableTestCase(unittest.TestCase): def test_is_valid_construction_var(self) -> None: """Testing is_valid_construction_var()""" r = is_valid_construction_var("_a") - assert r is not None, r + assert r, r r = is_valid_construction_var("z_") - assert r is not None, r + assert r, r r = is_valid_construction_var("X_") - assert r is not None, r + assert r, r r = is_valid_construction_var("2a") - assert r is None, r + assert not r, r r = is_valid_construction_var("a2_") - assert r is not None, r + assert r, r r = is_valid_construction_var("/") - assert r is None, r + assert not r, r r = is_valid_construction_var("_/") - assert r is None, r + assert not r, r r = is_valid_construction_var("a/") - assert r is None, r + assert not r, r r = is_valid_construction_var(".b") - assert r is None, r + assert not r, r r = is_valid_construction_var("_.b") - assert r is None, r + assert not r, r r = is_valid_construction_var("b1._") - assert r is None, r + assert not r, r r = is_valid_construction_var("-b") - assert r is None, r + assert not r, r r = is_valid_construction_var("_-b") - assert r is None, r + assert not r, r r = is_valid_construction_var("b1-_") - assert r is None, r + assert not r, r From a217d6ad0a166053d01b3b1f823e5989a89c7c17 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Mon, 3 Jun 2024 09:40:01 +0200 Subject: [PATCH 02/23] Fixed typo, added RELEASE.txt --- CHANGES.txt | 4 ++-- RELEASE.txt | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 5be451f1dd..ab13ef074b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -79,8 +79,8 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - Framework for scons-time tests adjusted so a path with a long username Windows has squashed doesn't get re-expanded. Fixes a problem seen on GitHub Windows runner which uses a name "runneradmin". - - SCons.Environment.is_valid_construction_var now returns a boolean to - match the convention that functions beginnig with "is" have yes/no + - SCons.Environment.is_valid_construction_var() now returns a boolean to + match the convention that functions beginning with "is" have yes/no answers (previously returned either None or an re.match object). Now matches the annotation and docstring (which were prematurely updated in 4.6). All SCons usage except unit test was already fully diff --git a/RELEASE.txt b/RELEASE.txt index fd269d65b1..b9c253f4d4 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -44,6 +44,12 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY of CCFLAGS; the latter variable could cause a compiler warning. - The implementation of Variables was slightly refactored, there should not be user-visible changes. +- SCons.Environment.is_valid_construction_var() now returns a boolean to + match the convention that functions beginning with "is" have yes/no + answers (previously returned either None or an re.match object). + Now matches the annotation and docstring (which were prematurely + updated in 4.6). All SCons usage except unit test was already fully + consistent with a bool. FIXES ----- From 18b45e456412379c9182dd9cb4c8b30ca1e841b8 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 3 Jun 2024 07:18:54 -0600 Subject: [PATCH 03/23] Allow a Variable to not be substituted New parameter do_subst added to the variables Add method, if false indicates the variable value should not be substituted by the Variables logic. The default is True. Fixes #4241. Signed-off-by: Mats Wichmann --- CHANGES.txt | 3 +++ RELEASE.txt | 3 +++ SCons/Tool/yacc.xml | 4 ++-- SCons/Variables/PathVariable.py | 2 +- SCons/Variables/__init__.py | 26 ++++++++++++++++++-------- doc/generated/variables.gen | 4 ++-- doc/man/scons.xml | 17 ++++++++++++++++- 7 files changed, 45 insertions(+), 14 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ab13ef074b..8850bfe9d2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -85,6 +85,9 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER Now matches the annotation and docstring (which were prematurely updated in 4.6). All SCons usage except unit test was already fully consistent with a bool. + - When a variable is added to a Variables object, it can now be flagged + as "don't perform substitution". This allows variables to contain + characters which would otherwise cause expansion. Fixes #4241. RELEASE 4.7.0 - Sun, 17 Mar 2024 17:22:20 -0700 diff --git a/RELEASE.txt b/RELEASE.txt index b9c253f4d4..ec22f1057a 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -50,6 +50,9 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY Now matches the annotation and docstring (which were prematurely updated in 4.6). All SCons usage except unit test was already fully consistent with a bool. +- The Variables object Add method now accepts a do_subst keyword argument + (defaults to True) which can be set to inhibit substitution prior to + calling the variable's converter and validator. FIXES ----- diff --git a/SCons/Tool/yacc.xml b/SCons/Tool/yacc.xml index 82725dbade..729c408286 100644 --- a/SCons/Tool/yacc.xml +++ b/SCons/Tool/yacc.xml @@ -236,7 +236,7 @@ The value is used only if &cv-YACC_GRAPH_FILE_SUFFIX; is not set. The default value is .gv. -Changed in version 4.X.Y: deprecated. The default value +Changed in version 4.6.0: deprecated. The default value changed from .vcg (&bison; stopped generating .vcg output with version 2.4, in 2006). @@ -261,7 +261,7 @@ Various yacc tools have emitted various formats at different times. Set this to match what your parser generator produces. -New in version 4.X.Y. +New in version 4.6.0. diff --git a/SCons/Variables/PathVariable.py b/SCons/Variables/PathVariable.py index 6ea4e6bc93..4a827c5e12 100644 --- a/SCons/Variables/PathVariable.py +++ b/SCons/Variables/PathVariable.py @@ -141,7 +141,7 @@ def PathExists(key, val, env) -> None: # lint: W0622: Redefining built-in 'help' (redefined-builtin) def __call__( - self, key, help: str, default, validator: Optional[Callable] = None + self, key: str, help: str, default, validator: Optional[Callable] = None ) -> Tuple[str, str, str, Callable, None]: """Return a tuple describing a path list SCons Variable. diff --git a/SCons/Variables/__init__.py b/SCons/Variables/__init__.py index 867493d7c8..03f7ef3b50 100644 --- a/SCons/Variables/__init__.py +++ b/SCons/Variables/__init__.py @@ -49,7 +49,7 @@ class Variable: """A Build Variable.""" - __slots__ = ('key', 'aliases', 'help', 'default', 'validator', 'converter') + __slots__ = ('key', 'aliases', 'help', 'default', 'validator', 'converter', 'do_subst') def __lt__(self, other): """Comparison fuction so Variable instances sort.""" @@ -87,9 +87,9 @@ def __init__( ) -> None: self.options: List[Variable] = [] self.args = args if args is not None else {} - if not SCons.Util.is_List(files): + if not SCons.Util.is_Sequence(files): files = [files] if files else [] - self.files = files + self.files: Sequence[str] = files self.unknown: Dict[str, str] = {} def __str__(self) -> str: @@ -132,6 +132,7 @@ def _do_add( option.default = default option.validator = validator option.converter = converter + option.do_subst = kwargs.get("subst", True) self.options.append(option) @@ -171,8 +172,11 @@ def Add( value before putting it in the environment. (default: ``None``) """ if SCons.Util.is_Sequence(key): - if not (len(args) or len(kwargs)): - return self._do_add(*key) + # If no other positional args (and no fundamental kwargs), + # unpack key, and pass the kwargs on: + known_kw = {'help', 'default', 'validator', 'converter'} + if not args and not known_kw.intersection(kwargs.keys()): + return self._do_add(*key, **kwargs) return self._do_add(key, *args, **kwargs) @@ -247,7 +251,10 @@ def Update(self, env, args: Optional[dict] = None) -> None: # apply converters for option in self.options: if option.converter and option.key in values: - value = env.subst(f'${option.key}') + if option.do_subst: + value = env.subst(f'${option.key}') + else: + value = env[option.key] try: try: env[option.key] = option.converter(value) @@ -262,7 +269,11 @@ def Update(self, env, args: Optional[dict] = None) -> None: # apply validators for option in self.options: if option.validator and option.key in values: - option.validator(option.key, env.subst(f'${option.key}'), env) + if option.do_subst: + value = env.subst('${%s}'%option.key) + else: + value = env[option.key] + option.validator(option.key, value, env) def UnknownVariables(self) -> dict: """Return dict of unknown variables. @@ -340,7 +351,6 @@ def GenerateHelpText(self, env, sort: Union[bool, Callable] = False) -> str: # removed so now we have to convert to a key. if callable(sort): options = sorted(self.options, key=cmp_to_key(lambda x, y: sort(x.key, y.key))) - elif sort is True: options = sorted(self.options) else: diff --git a/doc/generated/variables.gen b/doc/generated/variables.gen index 8c89616d35..fad7d5d4ae 100644 --- a/doc/generated/variables.gen +++ b/doc/generated/variables.gen @@ -10668,7 +10668,7 @@ Various yacc tools have emitted various formats at different times. Set this to match what your parser generator produces. -New in version 4.X.Y. +New in version 4.6.0. @@ -10826,7 +10826,7 @@ The value is used only if &cv-YACC_GRAPH_FILE_SUFFIX; is not set. The default value is .gv. -Changed in version 4.X.Y: deprecated. The default value +Changed in version 4.6.0: deprecated. The default value changed from .vcg (&bison; stopped generating .vcg output with version 2.4, in 2006). diff --git a/doc/man/scons.xml b/doc/man/scons.xml index cdaaa44ac9..eb02a23248 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -4835,7 +4835,7 @@ not to any stored-values files. - vars.Add(key, [help, default, validator, converter]) + vars.Add(key, [help, default, validator, converter, do_subst]) Add a customizable &consvar; to the &Variables; object. key @@ -4887,6 +4887,16 @@ or there is no separate validator it can raise a ValueError. + +Substitution will be performed on the variable value +as it is added, before the converter and validator are called, +unless the optional do_subst parameter +is false (default True). +Suppressing substitution may be useful if the variable value +looks like a &consvar; reference ($VAR) +to be expanded later. + + As a special case, if key is a sequence and is the only @@ -4919,6 +4929,11 @@ def valid_color(key, val, env): vars.Add('COLOR', validator=valid_color) + + +Changed in version 4.8.0: +added the do_subst parameter. + From 9033b66adb098852c1f89b8d008297cb279debe9 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 3 Jun 2024 08:15:43 -0600 Subject: [PATCH 04/23] runtest.py now recognizes exit code 5 The unittest module's main() can exit with a code of 5, which means no tests were run. SCons/Script/MainTests.py currently has no tests, so this particular error code is expected - should not cause runtest to give up with an "unknown error code". Signed-off-by: Mats Wichmann --- CHANGES.txt | 4 ++++ runtest.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index ab13ef074b..aaa689d7e3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -85,6 +85,10 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER Now matches the annotation and docstring (which were prematurely updated in 4.6). All SCons usage except unit test was already fully consistent with a bool. + - The test runner now recognizes the unittest module's return code of 5, + which means no tests were run. SCons/Script/MainTests.py currently + has no tests, so this particular error code is expected - should not + cause runtest to give up with an "unknown error code". RELEASE 4.7.0 - Sun, 17 Mar 2024 17:22:20 -0700 diff --git a/runtest.py b/runtest.py index 1922ccff79..220b490333 100755 --- a/runtest.py +++ b/runtest.py @@ -417,7 +417,7 @@ class SystemExecutor(RuntestBase): def execute(self, env): self.stderr, self.stdout, s = spawn_it(self.command_args, env) self.status = s - if s < 0 or s > 2: + if s < 0 or s > 2 and s != 5: sys.stdout.write("Unexpected exit status %d\n" % s) From 272d72beefb6b1d4efce68ec4c0ea2404f6fcb31 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 3 Jun 2024 10:38:21 -0600 Subject: [PATCH 05/23] Adding testcase for Add(..., subst) Signed-off-by: Mats Wichmann --- RELEASE.txt | 2 +- SCons/Variables/VariablesTests.py | 22 ++++++++++++++++++++++ SCons/Variables/__init__.py | 7 +++++-- doc/man/scons.xml | 12 ++++++------ 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/RELEASE.txt b/RELEASE.txt index ec22f1057a..063c116290 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -50,7 +50,7 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY Now matches the annotation and docstring (which were prematurely updated in 4.6). All SCons usage except unit test was already fully consistent with a bool. -- The Variables object Add method now accepts a do_subst keyword argument +- The Variables object Add method now accepts a subst keyword argument (defaults to True) which can be set to inhibit substitution prior to calling the variable's converter and validator. diff --git a/SCons/Variables/VariablesTests.py b/SCons/Variables/VariablesTests.py index 145bee31f3..866b2ff427 100644 --- a/SCons/Variables/VariablesTests.py +++ b/SCons/Variables/VariablesTests.py @@ -150,6 +150,28 @@ def test_Update(self) -> None: opts.Update(env, {}) assert env['ANSWER'] == 54 + # Test that the value is not substituted if 'subst' is False + def check_subst(key, value, env) -> None: + """Check that variable was not substituted before we get called.""" + assert value == "$ORIGIN", \ + f"Validator: '$ORIGIN' was substituted to {value!r}" + + def conv_subst(value) -> None: + """Check that variable was not substituted before we get called.""" + assert value == "$ORIGIN", \ + f"Converter: '$ORIGIN' was substituted to {value!r}" + return value + + opts.Add('NOSUB', + help='Variable whose value will not be substituted', + default='$ORIGIN', + validator=check_subst, + converter=conv_subst, + subst=False) + env = Environment() + opts.Update(env) + assert env['NOSUB'] == "$ORIGIN" + # Test that a bad value from the file is used and # validation fails correctly. test = TestSCons.TestSCons() diff --git a/SCons/Variables/__init__.py b/SCons/Variables/__init__.py index 03f7ef3b50..80cda2b95b 100644 --- a/SCons/Variables/__init__.py +++ b/SCons/Variables/__init__.py @@ -114,6 +114,9 @@ def _do_add( """Create a Variable and add it to the list. Internal routine, not public API. + + .. versionadded:: 4.8.0 + *subst* keyword argument is now recognized. """ option = Variable() @@ -252,7 +255,7 @@ def Update(self, env, args: Optional[dict] = None) -> None: for option in self.options: if option.converter and option.key in values: if option.do_subst: - value = env.subst(f'${option.key}') + value = env.subst('${%s}' % option.key) else: value = env[option.key] try: @@ -270,7 +273,7 @@ def Update(self, env, args: Optional[dict] = None) -> None: for option in self.options: if option.validator and option.key in values: if option.do_subst: - value = env.subst('${%s}'%option.key) + value = env.subst('${%s}' % option.key) else: value = env[option.key] option.validator(option.key, value, env) diff --git a/doc/man/scons.xml b/doc/man/scons.xml index eb02a23248..57d38e8ee0 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -4835,7 +4835,7 @@ not to any stored-values files. - vars.Add(key, [help, default, validator, converter, do_subst]) + vars.Add(key, [help, default, validator, converter, subst]) Add a customizable &consvar; to the &Variables; object. key @@ -4889,12 +4889,12 @@ it can raise a ValueError. Substitution will be performed on the variable value -as it is added, before the converter and validator are called, -unless the optional do_subst parameter +before the converter and validator are called, +unless the optional subst parameter is false (default True). Suppressing substitution may be useful if the variable value -looks like a &consvar; reference ($VAR) -to be expanded later. +looks like a &consvar; reference (e.g. $VAR) +and the validator and/or converter should see it unexpanded. @@ -4932,7 +4932,7 @@ vars.Add('COLOR', validator=valid_color) Changed in version 4.8.0: -added the do_subst parameter. +added the subst parameter. From 91b6bcc272292756e47cee9548158cccfaff1755 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 3 Jun 2024 11:38:12 -0600 Subject: [PATCH 06/23] Additional testcase for subst on Variables This tests the other side of the coin: when vars.Add(..., subst=True) is used, substitution *is* performaed. Signed-off-by: Mats Wichmann --- SCons/Variables/VariablesTests.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/SCons/Variables/VariablesTests.py b/SCons/Variables/VariablesTests.py index 866b2ff427..7c6eaab171 100644 --- a/SCons/Variables/VariablesTests.py +++ b/SCons/Variables/VariablesTests.py @@ -151,26 +151,46 @@ def test_Update(self) -> None: assert env['ANSWER'] == 54 # Test that the value is not substituted if 'subst' is False - def check_subst(key, value, env) -> None: + # and that it is if 'subst' is True. + def check_no_subst(key, value, env) -> None: """Check that variable was not substituted before we get called.""" assert value == "$ORIGIN", \ f"Validator: '$ORIGIN' was substituted to {value!r}" - def conv_subst(value) -> None: + def conv_no_subst(value) -> None: """Check that variable was not substituted before we get called.""" assert value == "$ORIGIN", \ f"Converter: '$ORIGIN' was substituted to {value!r}" return value + def check_subst(key, value, env) -> None: + """Check that variable was substituted before we get called.""" + assert value == "Value", \ + f"Validator: '$SUB' was not substituted {value!r} instead of 'Value'" + + def conv_subst(value) -> None: + """Check that variable was not substituted before we get called.""" + assert value == "Value", \ + f"Converter: '$SUB' was substituted to {value!r} instead of 'Value'" + return value + opts.Add('NOSUB', help='Variable whose value will not be substituted', default='$ORIGIN', + validator=check_no_subst, + converter=conv_no_subst, + subst=False) + opts.Add('SUB', + help='Variable whose value will be substituted', + default='$VAR', validator=check_subst, converter=conv_subst, - subst=False) + subst=True) env = Environment() + env['VAR'] = "Value" opts.Update(env) - assert env['NOSUB'] == "$ORIGIN" + assert env['NOSUB'] == "$ORIGIN", env['NOSUB'] + assert env['SUB'] == env['VAR'], env['SUB'] # Test that a bad value from the file is used and # validation fails correctly. From a7f4ab10e2a20f15ef18db6bef9ed6b682468c55 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 3 Jun 2024 12:13:34 -0600 Subject: [PATCH 07/23] Small docstring update for vars.Add Signed-off-by: Mats Wichmann --- SCons/Variables/__init__.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/SCons/Variables/__init__.py b/SCons/Variables/__init__.py index 80cda2b95b..9c9c3f4961 100644 --- a/SCons/Variables/__init__.py +++ b/SCons/Variables/__init__.py @@ -135,7 +135,8 @@ def _do_add( option.default = default option.validator = validator option.converter = converter - option.do_subst = kwargs.get("subst", True) + option.do_subst = kwargs.pop("subst", True) + # TODO should any remaining kwargs be saved in the Variable? self.options.append(option) @@ -158,21 +159,26 @@ def Add( Arguments: key: the name of the variable, or a 5-tuple (or list). If *key* is a tuple, and there are no additional positional - arguments, it is unpacked into the variable name plus the four - listed keyword arguments from below. + arguments, it is unpacked into the variable name plus the + *help*, *default*, *validator* and *converter keyword args. If *key* is a tuple and there are additional positional arguments, the first word of the tuple is taken as the variable name, and the remainder as aliases. - args: optional positional arguments, corresponding to the four - listed keyword arguments. + args: optional positional arguments, corresponding to the + *help*, *default*, *validator* and *converter keyword args. kwargs: arbitrary keyword arguments used by the variable itself. Keyword Args: - help: help text for the variable (default: ``""``) + help: help text for the variable (default: empty string) default: default value for variable (default: ``None``) validator: function called to validate the value (default: ``None``) converter: function to be called to convert the variable's value before putting it in the environment. (default: ``None``) + subst: if true perform substitution on the value before the converter + and validator functions (if any) are called (default: ``True``) + + .. versionadded:: 4.8.0 + The *subst* keyword argument is now specially recognized. """ if SCons.Util.is_Sequence(key): # If no other positional args (and no fundamental kwargs), From 265046d40a5b4433bffd3d8da932ea5c7a4fb874 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 3 Jun 2024 12:30:40 -0600 Subject: [PATCH 08/23] Even more vars.Add() docstring tweaking. Signed-off-by: Mats Wichmann --- SCons/Variables/__init__.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/SCons/Variables/__init__.py b/SCons/Variables/__init__.py index 9c9c3f4961..34fb68b08d 100644 --- a/SCons/Variables/__init__.py +++ b/SCons/Variables/__init__.py @@ -113,7 +113,8 @@ def _do_add( ) -> None: """Create a Variable and add it to the list. - Internal routine, not public API. + This is the internal implementation for :meth:`Add` and + :meth:`AddVariables`. Not part of the public API. .. versionadded:: 4.8.0 *subst* keyword argument is now recognized. @@ -157,15 +158,16 @@ def Add( """Add a Build Variable. Arguments: - key: the name of the variable, or a 5-tuple (or list). - If *key* is a tuple, and there are no additional positional - arguments, it is unpacked into the variable name plus the - *help*, *default*, *validator* and *converter keyword args. - If *key* is a tuple and there are additional positional arguments, - the first word of the tuple is taken as the variable name, - and the remainder as aliases. + key: the name of the variable, or a 5-tuple (or other sequence). + If *key* is a tuple, and there are no additional arguments + except the *help*, *default*, *validator* and *converter* + keyword arguments, *key* is unpacked into the variable name + plus the *help*, *default*, *validator* and *converter* + arguments; if there are additional arguments, the first + elements of *key* is taken as the variable name, and the + remainder as aliases. args: optional positional arguments, corresponding to the - *help*, *default*, *validator* and *converter keyword args. + *help*, *default*, *validator* and *converter* keyword args. kwargs: arbitrary keyword arguments used by the variable itself. Keyword Args: @@ -174,7 +176,7 @@ def Add( validator: function called to validate the value (default: ``None``) converter: function to be called to convert the variable's value before putting it in the environment. (default: ``None``) - subst: if true perform substitution on the value before the converter + subst: perform substitution on the value before the converter and validator functions (if any) are called (default: ``True``) .. versionadded:: 4.8.0 From 4c668368a211527e2f2c4ad4e462eeb3bb2a3a35 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Tue, 4 Jun 2024 07:47:29 -0600 Subject: [PATCH 09/23] Reproducible builds info updated Just tweaked the readmes (top-level and in the packaging/etc dir) and the site_init script sample itself. Signed-off-by: Mats Wichmann --- CHANGES.txt | 1 + README.rst | 22 +++++++++++++--------- packaging/etc/README.txt | 24 ++++++++++++++++-------- packaging/etc/reproducible_site_init.py | 20 +++++++++++++------- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ab13ef074b..df2425eab7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -85,6 +85,7 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER Now matches the annotation and docstring (which were prematurely updated in 4.6). All SCons usage except unit test was already fully consistent with a bool. + - Updated the notes about reproducible builds with SCons and the example. RELEASE 4.7.0 - Sun, 17 Mar 2024 17:22:20 -0700 diff --git a/README.rst b/README.rst index ae3c0f4a28..3da4fc0734 100755 --- a/README.rst +++ b/README.rst @@ -249,6 +249,19 @@ notifications and other GitHub events (``#github-update``), if those are of interest. See the website for more contact information: https://scons.org/contact.html. +Reproducible Builds +=================== +SCons itself is set up to do "reproducible builds" +(see (https://reproducible-builds.org/specs/source-date-epoch/) +if environment variables ``SOURCE_DATE_EPOCH`` is set - that is, +fields in the package which could change each time the package is +constructed are forced to constant values. + +To support other projects which wish to do the same, a sample script +is provided which can be placed in a site directory, which imports +``SOURCE_DATE_EPOCH`` and sets it in the execution environment of +every created construction envirionment. There's also an installer +script (POSIX shell only). See packaging/etc/README.txt for more details. Donations ========= @@ -258,15 +271,6 @@ software, or hardware) to support continued work on the project. Information is available at https://www.scons.org/donate.html or the GitHub Sponsors button on https://github.com/scons/scons. -Reproducible Builds -=================== -In order to suppor those users who which to produce reproducible builds -(https://reproducible-builds.org/specs/source-date-epoch/) we're now including -logic to force SCons to propagate SOURCE_DATE_EPOCH from your shell environment for -all SCons builds to support reproducible builds we're now providing an example -site_init.py and a script to install it in your ~/.scons. See packaging/etc/README.txt -for more info - For More Information ==================== diff --git a/packaging/etc/README.txt b/packaging/etc/README.txt index ffb9cc10b2..11f1eab309 100644 --- a/packaging/etc/README.txt +++ b/packaging/etc/README.txt @@ -1,10 +1,18 @@ -This directory contains a number of scripts/files useful when building/packageing SCons +This directory contains helpers for doing reproducible builds with SCons. + +To force SCons to propagate SOURCE_DATE_EPOCH from the shell running SCons, +the reproducible_site_init.py file can be installed (as site_init.py) +in any site directory - either in the project itself, or more globally. +See the manpage for default site directories or how to set your own path: +https://scons.org/doc/production/HTML/scons-man.html#opt-site-dir. +This code will make sure SOURCE_DATE_EPOCH is set in the execution +environment, meaning any external commands run by SCons will have it +in their environment. Any logic in your build system itself will still +need to examine this variable. + +The shell script reproducible_install.sh can be used to install the +Python site file in your user site directory ($HOME/.scons/site_scons). +It is careful to not overwrite any existing site_init.py there. This +only works for a POSIX shell. -To force SCons to propagate SOURCE_DATE_EPOCH from the shell running SCons we're providing -a script to create a ~/.scons/site_scons/site_init.py. -Note that reproducible_install.sh will NOT overwite an existing ~/.scons/site_scons/site_init.py This supports https://reproducible-builds.org/specs/source-date-epoch/ -If you wanted to include this in your build tree you would place in site_scons/site_init.py relative -to your SConstruct. -* reproducible_install.sh -* reproducible_site_init.py \ No newline at end of file diff --git a/packaging/etc/reproducible_site_init.py b/packaging/etc/reproducible_site_init.py index 2b6b42a91f..5f7513ba19 100644 --- a/packaging/etc/reproducible_site_init.py +++ b/packaging/etc/reproducible_site_init.py @@ -1,5 +1,6 @@ """ -Use this file as your ~/.site_scons/scons_init.py to enable reprodicble builds as described at +Use this file as your site_init.py in a site directory, +to enable reprodicble builds as described at https://reproducible-builds.org/specs/source-date-epoch/ """ @@ -8,17 +9,22 @@ old_init = SCons.Environment.Base.__init__ -print("Adding logic to propagate SOURCE_DATE_EPOCH from the shell environment when building with SCons") +print( + "Adding logic to propagate SOURCE_DATE_EPOCH from the shell environment when building with SCons" +) def new_init(self, **kw): - """ - This logic will add SOURCE_DATE_EPOCH to the execution environment used to run - all the build commands. + """Replacement Environment initializer. + + When this is monkey-patched into :class:`SCons.Environment.Base` it adds + ``SOURCE_DATE_EPOCH`` to the execution environment used to run + all external build commands; the original iinitializer is called first. """ old_init(self, **kw) - if 'SOURCE_DATE_EPOCH' in os.environ: - self._dict['ENV']['SOURCE_DATE_EPOCH'] = os.environ['SOURCE_DATE_EPOCH'] + epoch = os.environ.get("SOURCE_DATE_EPOCH") + if epoch is not None: + self._dict["ENV"]["SOURCE_DATE_EPOCH"] = epoch SCons.Environment.Base.__init__ = new_init From 68eb8859776439714c6e38a6e2ef53031199afbc Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Tue, 4 Jun 2024 09:44:18 -0600 Subject: [PATCH 10/23] Teach Clone() to respect the variables= kwarg. Previously, "variables" would just be set as a construction var, now it has the same meaning as an Environment() call. Docs updated and test added. Fixes #3590 Signed-off-by: Mats Wichmann --- CHANGES.txt | 1 + RELEASE.txt | 1 + SCons/Environment.py | 28 +++++++++++++----- SCons/Environment.xml | 32 +++++++++++++++------ test/Clone-Variables.py | 63 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 test/Clone-Variables.py diff --git a/CHANGES.txt b/CHANGES.txt index ab13ef074b..0f827a7943 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -85,6 +85,7 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER Now matches the annotation and docstring (which were prematurely updated in 4.6). All SCons usage except unit test was already fully consistent with a bool. + - The Clone() method now respects the variables argument (fixes #3590) RELEASE 4.7.0 - Sun, 17 Mar 2024 17:22:20 -0700 diff --git a/RELEASE.txt b/RELEASE.txt index b9c253f4d4..c994041623 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -58,6 +58,7 @@ FIXES - Improved the conversion of a "foreign" exception from an action into BuildError by making sure our defaults get applied even in corner cases. Fixes Issue #4530 +- The Clone() method now respects the variables argument (fixes #3590) IMPROVEMENTS ------------ diff --git a/SCons/Environment.py b/SCons/Environment.py index ae44414154..952ffc17c8 100644 --- a/SCons/Environment.py +++ b/SCons/Environment.py @@ -1568,16 +1568,28 @@ def AppendUnique(self, delete_existing: bool=False, **kw) -> None: self._dict[key] = dk + val self.scanner_map_delete(kw) - def Clone(self, tools=[], toolpath=None, parse_flags = None, **kw): + def Clone(self, tools=[], toolpath=None, variables=None, parse_flags=None, **kw): """Return a copy of a construction Environment. - The copy is like a Python "deep copy"--that is, independent - copies are made recursively of each objects--except that - a reference is copied when an object is not deep-copyable - (like a function). There are no references to any mutable - objects in the original Environment. - """ + The copy is like a Python "deep copy": independent copies are made + recursively of each object, except that a reference is copied when + an object is not deep-copyable (like a function). There are no + references to any mutable objects in the original environment. + + Unrecognized keyword arguments are taken as construction variable + assignments. + Arguments: + tools: list of tools to initialize. + toolpath: list of paths to search for tools. + variables: a :class:`~SCons.Variables.Variables` object to + use to populate construction variables from command-line + variables. + parse_flags: option strings to parse into construction variables. + + .. versionadded:: 4.8.0 + The optional *variables* parameter was added. + """ builders = self._dict.get('BUILDERS', {}) clone = copy.copy(self) @@ -1603,6 +1615,8 @@ def Clone(self, tools=[], toolpath=None, parse_flags = None, **kw): for key, value in kw.items(): new[key] = SCons.Subst.scons_subst_once(value, self, key) clone.Replace(**new) + if variables: + variables.Update(clone) apply_tools(clone, tools, toolpath) diff --git a/SCons/Environment.xml b/SCons/Environment.xml index 5f152f22ee..c09d3848d6 100644 --- a/SCons/Environment.xml +++ b/SCons/Environment.xml @@ -1079,11 +1079,12 @@ Clean(docdir, os.path.join(docdir, projectname)) -Returns a separate copy of a construction environment. -If there are any keyword arguments specified, -they are added to the returned copy, +Returns an independent copy of a &consenv;. +If there are any unrecognized keyword arguments specified, +they are added as &consvars; in the copy, overwriting any existing values -for the keywords. +for those keywords. +See the manpage section "Construction Environments" for more details. @@ -1096,8 +1097,9 @@ env3 = env.Clone(CCFLAGS='-g') -Additionally, a list of tools and a toolpath may be specified, as in -the &f-link-Environment; constructor: +A list of tools +and a toolpath may be specified, +as in the &f-link-Environment; constructor: @@ -1110,7 +1112,7 @@ env4 = env.Clone(tools=['msvc', MyTool]) The parse_flags -keyword argument is also recognized to allow merging command-line +keyword argument is also recognized, to allow merging command-line style arguments into the appropriate construction variables (see &f-link-env-MergeFlags;). @@ -1119,6 +1121,17 @@ variables (see &f-link-env-MergeFlags;). # create an environment for compiling programs that use wxWidgets wx_env = env.Clone(parse_flags='!wx-config --cflags --cxxflags') + + +The variables +keyword argument is also recognized, to allow (re)initializing +&consvars; from a Variables object. + + + +Changed in version 4.8.0: +the variables parameter was added. + @@ -1760,7 +1773,7 @@ will print: -Return a new construction environment +Return a new &consenv; initialized with the specified key=value pairs. @@ -1770,7 +1783,8 @@ The keyword arguments toolpath, tools and variables -are also specially recognized. +are specially recognized and do not lead to +&consvar; creation. See the manpage section "Construction Environments" for more details. diff --git a/test/Clone-Variables.py b/test/Clone-Variables.py new file mode 100644 index 0000000000..383caeafbb --- /dev/null +++ b/test/Clone-Variables.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# +# MIT License +# +# Copyright The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +""" +Verify that Clone() respects the variables kwarg. + +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.write('SConstruct', """\ +vars = Variables() +vars.Add(BoolVariable('MYTEST', 'help', default=False)) + +_ = DefaultEnvironment(tools=[]) +env = Environment(variables=vars, tools=[]) +print(f"MYTEST={env.Dictionary('MYTEST')}") +env.Replace(MYTEST=True) +print(f"MYTEST={env.Dictionary('MYTEST')}") +env1 = env.Clone(variables=vars) +print(f"MYTEST={env1.Dictionary('MYTEST')}") +""") + +expect = """\ +MYTEST=False +MYTEST=True +MYTEST=False +""" + +test.run(arguments = '-q -Q', stdout=expect) + +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: From 3f2df31eca2b962c1fa03a40351c9103d9edbc78 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Thu, 6 Jun 2024 18:27:58 +0200 Subject: [PATCH 11/23] Added blurb to RELEASE.txt --- RELEASE.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE.txt b/RELEASE.txt index b9c253f4d4..e7e9ff409b 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -82,6 +82,7 @@ DOCUMENTATION - Restructured API Docs build so main package contents are listed before contents of package submodules. - Updated manpage description of Command "builder" and function. +- Updated the notes about reproducible builds with SCons and the example. From 7852c2691e4e30464bd951e44367d15bb1b475a1 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Thu, 6 Jun 2024 18:30:01 +0200 Subject: [PATCH 12/23] Added blurb to RELEASE.txt --- RELEASE.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE.txt b/RELEASE.txt index b9c253f4d4..6d49a48748 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -98,6 +98,10 @@ DEVELOPMENT The repo-wide line-ending is now `lf`, with the exception of a few Windows-only files using `crlf` instead. Any files not already fitting this format have been explicitly converted. +- The test runner now recognizes the unittest module's return code of 5, + which means no tests were run. SCons/Script/MainTests.py currently + has no tests, so this particular error code is expected - should not + cause runtest to give up with an "unknown error code". Thanks to the following contributors listed below for their contributions to this release. From b78cfe95487433df7c9f3305571acab755fcdc1b Mon Sep 17 00:00:00 2001 From: William Deegan Date: Thu, 6 Jun 2024 18:37:12 +0200 Subject: [PATCH 13/23] added reference to new arg 'subst' in the blurb in CHANGES.txt --- CHANGES.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 7fe6f8a517..66b149e203 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -86,8 +86,9 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER updated in 4.6). All SCons usage except unit test was already fully consistent with a bool. - When a variable is added to a Variables object, it can now be flagged - as "don't perform substitution". This allows variables to contain - characters which would otherwise cause expansion. Fixes #4241. + as "don't perform substitution" by setting the argument subst. + This allows variables to contain characters which would otherwise + cause expansion. Fixes #4241. - The test runner now recognizes the unittest module's return code of 5, which means no tests were run. SCons/Script/MainTests.py currently has no tests, so this particular error code is expected - should not From d01c21a666090a93875b5398fc7243cc9315e71a Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Thu, 6 Jun 2024 10:57:50 -0600 Subject: [PATCH 14/23] Move environment var checker to SCons.Util is_valid_construction_var was defined in Environment, but also used in Variables. This meant a possibility of import loops, since Variables used Environment for this function, and Environment uses Variables to update from an object in Environment() and Clone(). According to breadcumbs in a benchmark script it used to be in Util at some point in the past. Signed-off-by: Mats Wichmann --- CHANGES.txt | 8 ++++++-- SCons/Environment.py | 10 ++-------- SCons/EnvironmentTests.py | 35 ----------------------------------- SCons/Util/UtilTests.py | 36 ++++++++++++++++++++++++++++++++++++ SCons/Util/__init__.py | 1 + SCons/Util/envs.py | 7 +++++++ SCons/Variables/__init__.py | 15 ++++++++------- bench/env.__setitem__.py | 26 +++++++++++++------------- 8 files changed, 73 insertions(+), 65 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 66b149e203..07dcaa48e3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -86,8 +86,8 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER updated in 4.6). All SCons usage except unit test was already fully consistent with a bool. - When a variable is added to a Variables object, it can now be flagged - as "don't perform substitution" by setting the argument subst. - This allows variables to contain characters which would otherwise + as "don't perform substitution" by setting the argument subst. + This allows variables to contain characters which would otherwise cause expansion. Fixes #4241. - The test runner now recognizes the unittest module's return code of 5, which means no tests were run. SCons/Script/MainTests.py currently @@ -95,6 +95,10 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER cause runtest to give up with an "unknown error code". - Updated the notes about reproducible builds with SCons and the example. - The Clone() method now respects the variables argument (fixes #3590) + - is_valid_construction_var (not past of the public API) moved from + Environment.py to Util to avoid the chance of import loops. Variables + and Environment both use the routine and Environment() uses a Variables() + object so better to move to a safter location. RELEASE 4.7.0 - Sun, 17 Mar 2024 17:22:20 -0700 diff --git a/SCons/Environment.py b/SCons/Environment.py index 952ffc17c8..6669bf8ada 100644 --- a/SCons/Environment.py +++ b/SCons/Environment.py @@ -76,6 +76,7 @@ to_String_for_subst, uniquer_hashables, ) +from SCons.Util.envs import is_valid_construction_var from SCons.Util.sctyping import ExecutorType class _Null: @@ -510,13 +511,6 @@ def update(self, mapping) -> None: self.__setitem__(i, v) -_is_valid_var = re.compile(r'[_a-zA-Z]\w*$') - -def is_valid_construction_var(varstr: str) -> bool: - """Return True if *varstr* is a legitimate construction variable.""" - return bool(_is_valid_var.match(varstr)) - - class SubstitutionEnvironment: """Base class for different flavors of construction environments. @@ -605,7 +599,7 @@ def __setitem__(self, key, value): # key and we don't need to check. If we do check, using a # global, pre-compiled regular expression directly is more # efficient than calling another function or a method. - if key not in self._dict and not _is_valid_var.match(key): + if key not in self._dict and not is_valid_construction_var(key): raise UserError("Illegal construction variable `%s'" % key) self._dict[key] = value diff --git a/SCons/EnvironmentTests.py b/SCons/EnvironmentTests.py index d213099d1a..8d90d4d171 100644 --- a/SCons/EnvironmentTests.py +++ b/SCons/EnvironmentTests.py @@ -38,7 +38,6 @@ NoSubstitutionProxy, OverrideEnvironment, SubstitutionEnvironment, - is_valid_construction_var, ) from SCons.Util import CLVar from SCons.SConsign import current_sconsign_filename @@ -4142,40 +4141,6 @@ def test_subst_target_source(self) -> None: x = proxy.subst_target_source(*args, **kw) assert x == ' ttt sss ', x -class EnvironmentVariableTestCase(unittest.TestCase): - - def test_is_valid_construction_var(self) -> None: - """Testing is_valid_construction_var()""" - r = is_valid_construction_var("_a") - assert r, r - r = is_valid_construction_var("z_") - assert r, r - r = is_valid_construction_var("X_") - assert r, r - r = is_valid_construction_var("2a") - assert not r, r - r = is_valid_construction_var("a2_") - assert r, r - r = is_valid_construction_var("/") - assert not r, r - r = is_valid_construction_var("_/") - assert not r, r - r = is_valid_construction_var("a/") - assert not r, r - r = is_valid_construction_var(".b") - assert not r, r - r = is_valid_construction_var("_.b") - assert not r, r - r = is_valid_construction_var("b1._") - assert not r, r - r = is_valid_construction_var("-b") - assert not r, r - r = is_valid_construction_var("_-b") - assert not r, r - r = is_valid_construction_var("b1-_") - assert not r, r - - if __name__ == "__main__": unittest.main() diff --git a/SCons/Util/UtilTests.py b/SCons/Util/UtilTests.py index ff32bab8d2..b1c01086e4 100644 --- a/SCons/Util/UtilTests.py +++ b/SCons/Util/UtilTests.py @@ -73,6 +73,7 @@ to_bytes, to_str, ) +from SCons.Util.envs import is_valid_construction_var from SCons.Util.hashes import ( _attempt_init_of_python_3_9_hash_object, _attempt_get_hash_function, @@ -1206,6 +1207,41 @@ def test_default(self) -> None: assert var is False, 'var should be False, not %s' % repr(var) +class EnvironmentVariableTestCase(unittest.TestCase): + + def test_is_valid_construction_var(self) -> None: + """Testing is_valid_construction_var()""" + r = is_valid_construction_var("_a") + assert r, r + r = is_valid_construction_var("z_") + assert r, r + r = is_valid_construction_var("X_") + assert r, r + r = is_valid_construction_var("2a") + assert not r, r + r = is_valid_construction_var("a2_") + assert r, r + r = is_valid_construction_var("/") + assert not r, r + r = is_valid_construction_var("_/") + assert not r, r + r = is_valid_construction_var("a/") + assert not r, r + r = is_valid_construction_var(".b") + assert not r, r + r = is_valid_construction_var("_.b") + assert not r, r + r = is_valid_construction_var("b1._") + assert not r, r + r = is_valid_construction_var("-b") + assert not r, r + r = is_valid_construction_var("_-b") + assert not r, r + r = is_valid_construction_var("b1-_") + assert not r, r + + + if __name__ == "__main__": unittest.main() diff --git a/SCons/Util/__init__.py b/SCons/Util/__init__.py index 95c1b9978d..28565da797 100644 --- a/SCons/Util/__init__.py +++ b/SCons/Util/__init__.py @@ -107,6 +107,7 @@ AppendPath, AddPathIfNotExists, AddMethod, + is_valid_construction_var, ) from .filelock import FileLock, SConsLockFailure diff --git a/SCons/Util/envs.py b/SCons/Util/envs.py index db4d65ab94..68a40488c6 100644 --- a/SCons/Util/envs.py +++ b/SCons/Util/envs.py @@ -9,6 +9,7 @@ that don't need the specifics of the Environment class. """ +import re import os from types import MethodType, FunctionType from typing import Union, Callable, Optional, Any @@ -329,6 +330,12 @@ def AddMethod(obj, function: Callable, name: Optional[str] = None) -> None: setattr(obj, name, method) +_is_valid_var_re = re.compile(r'[_a-zA-Z]\w*$') + +def is_valid_construction_var(varstr: str) -> bool: + """Return True if *varstr* is a legitimate construction variable.""" + return bool(_is_valid_var_re.match(varstr)) + # Local Variables: # tab-width:4 # indent-tabs-mode:nil diff --git a/SCons/Variables/__init__.py b/SCons/Variables/__init__.py index 34fb68b08d..1c41130025 100644 --- a/SCons/Variables/__init__.py +++ b/SCons/Variables/__init__.py @@ -28,7 +28,6 @@ from functools import cmp_to_key from typing import Callable, Dict, List, Optional, Sequence, Union -import SCons.Environment import SCons.Errors import SCons.Util import SCons.Warnings @@ -70,13 +69,15 @@ class Variables: files: string or list of strings naming variable config scripts (default ``None``) args: dictionary to override values set from *files*. (default ``None``) - is_global: if true, return a global singleton Variables object instead - of a fresh instance. Currently inoperable (default ``False``) + is_global: if true, return a global singleton :class:`Variables` object + 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). - *is_global* is deprecated. + The default for *is_global* changed to ``False`` (previously + ``True`` but it had no effect due to an implementation error). + + .. deprecated:: 4.8.0 + *is_global* is deprecated. """ def __init__( @@ -130,7 +131,7 @@ def _do_add( option.key = key # TODO: normalize to not include key in aliases. Currently breaks tests. option.aliases = [key,] - if not SCons.Environment.is_valid_construction_var(option.key): + if not SCons.Util.is_valid_construction_var(option.key): raise SCons.Errors.UserError(f"Illegal Variables key {option.key!r}") option.help = help option.default = default diff --git a/bench/env.__setitem__.py b/bench/env.__setitem__.py index f9fe0c0cd1..2cd0da826f 100644 --- a/bench/env.__setitem__.py +++ b/bench/env.__setitem__.py @@ -1,8 +1,7 @@ # __COPYRIGHT__ # # Benchmarks for testing various possible implementations of the -# env.__setitem__() method(s) in the src/engine/SCons/Environment.py -# module. +# env.__setitem__() method(s) in the SCons/Environment.py module. from __future__ import print_function @@ -25,10 +24,10 @@ def __init__(self, name, num, init, statement): self.name = name self.statement = statement self.__result = None - + def timeit(self): self.__result = self.__timer.timeit(self.__num) - + def getResult(self): return self.__result @@ -62,8 +61,9 @@ def times(num=1000000, init='', title='Results:', **statements): import SCons.Errors import SCons.Environment +import SCons.Util -is_valid_construction_var = SCons.Environment.is_valid_construction_var +is_valid_construction_var = SCons.Util.is_valid_construction_var global_valid_var = re.compile(r'[_a-zA-Z]\w*$') # The classes with different __setitem__() implementations that we're @@ -80,7 +80,7 @@ def times(num=1000000, init='', title='Results:', **statements): # # The env_Original subclass contains the original implementation (which # actually had the is_valid_construction_var() function in SCons.Util -# originally). +# originally). Update: it has moved back, to avoid import loop with Variables. # # The other subclasses (except for env_Best) each contain *one* # significant change from the env_Original implementation. The doc string @@ -116,7 +116,7 @@ def __setitem__(self, key, value): if special: special(self, key, value) else: - if not SCons.Environment.is_valid_construction_var(key): + if not SCons.Util.is_valid_construction_var(key): raise SCons.Errors.UserError("Illegal construction variable `%s'" % key) self._dict[key] = value @@ -176,7 +176,7 @@ def __setitem__(self, key, value): if key in self._special_set: self._special_set[key](self, key, value) else: - if not SCons.Environment.is_valid_construction_var(key): + if not SCons.Util.is_valid_construction_var(key): raise SCons.Errors.UserError("Illegal construction variable `%s'" % key) self._dict[key] = value @@ -186,7 +186,7 @@ def __setitem__(self, key, value): if key in ('BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES'): self._special_set[key](self, key, value) else: - if not SCons.Environment.is_valid_construction_var(key): + if not SCons.Util.is_valid_construction_var(key): raise SCons.Errors.UserError("Illegal construction variable `%s'" % key) self._dict[key] = value @@ -196,7 +196,7 @@ def __setitem__(self, key, value): if key in ['BUILDERS', 'SCANNERS', 'TARGET', 'TARGETS', 'SOURCE', 'SOURCES']: self._special_set[key](self, key, value) else: - if not SCons.Environment.is_valid_construction_var(key): + if not SCons.Util.is_valid_construction_var(key): raise SCons.Errors.UserError("Illegal construction variable `%s'" % key) self._dict[key] = value @@ -206,7 +206,7 @@ def __setitem__(self, key, value): if key in self._special_set_keys: self._special_set[key](self, key, value) else: - if not SCons.Environment.is_valid_construction_var(key): + if not SCons.Util.is_valid_construction_var(key): raise SCons.Errors.UserError("Illegal construction variable `%s'" % key) self._dict[key] = value @@ -220,7 +220,7 @@ def __setitem__(self, key, value): try: self._dict[key] except KeyError: - if not SCons.Environment.is_valid_construction_var(key): + if not SCons.Util.is_valid_construction_var(key): raise SCons.Errors.UserError("Illegal construction variable `%s'" % key) self._dict[key] = value @@ -232,7 +232,7 @@ def __setitem__(self, key, value): special(self, key, value) else: if key not in self._dict \ - and not SCons.Environment.is_valid_construction_var(key): + and not SCons.Util.is_valid_construction_var(key): raise SCons.Errors.UserError("Illegal construction variable `%s'" % key) self._dict[key] = value From 0fe8822467af809932c0e6101fc59744a9d5b3a5 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Fri, 7 Jun 2024 10:08:10 +0200 Subject: [PATCH 15/23] fix some spelling issues and add blurb to RELEASE --- CHANGES.txt | 6 +++--- RELEASE.txt | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 07dcaa48e3..d0eb245f54 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -95,10 +95,10 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER cause runtest to give up with an "unknown error code". - Updated the notes about reproducible builds with SCons and the example. - The Clone() method now respects the variables argument (fixes #3590) - - is_valid_construction_var (not past of the public API) moved from - Environment.py to Util to avoid the chance of import loops. Variables + - is_valid_construction_var() (not part of the public API) moved from + SCons.Environment to SCons.Util to avoid the chance of import loops. Variables and Environment both use the routine and Environment() uses a Variables() - object so better to move to a safter location. + object so better to move to a safer location. RELEASE 4.7.0 - Sun, 17 Mar 2024 17:22:20 -0700 diff --git a/RELEASE.txt b/RELEASE.txt index 02ea1b1c6c..8955e465d0 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -107,7 +107,10 @@ DEVELOPMENT which means no tests were run. SCons/Script/MainTests.py currently has no tests, so this particular error code is expected - should not cause runtest to give up with an "unknown error code". - +- is_valid_construction_var() (not part of the public API) moved from + SCons.Environment to SCons.Util to avoid the chance of import loops. Variables + and Environment both use the routine and Environment() uses a Variables() + object so better to move to a safer location. Thanks to the following contributors listed below for their contributions to this release. ========================================================================================== From 1ad6d783c455ee26558497b942305099cfd9fdf9 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 10 Jun 2024 11:01:30 -0600 Subject: [PATCH 16/23] ListVariable now has a separate validator Previously the converter did both conversion and validation, making it difficult to write a custom validator. Signed-off-by: Mats Wichmann --- CHANGES.txt | 11 +++- RELEASE.txt | 5 ++ SCons/Variables/ListVariable.py | 92 ++++++++++++++++++++++------ SCons/Variables/ListVariableTests.py | 22 +++++-- doc/man/scons.xml | 12 +++- test/Variables/BoolVariable.py | 10 ++- test/Variables/EnumVariable.py | 13 ++-- test/Variables/ListVariable.py | 35 +++++------ test/Variables/PackageVariable.py | 9 ++- test/Variables/PathVariable.py | 10 ++- test/Variables/Variables.py | 13 +++- test/Variables/chdir.py | 3 +- test/Variables/help.py | 3 +- test/Variables/import.py | 3 +- 14 files changed, 161 insertions(+), 80 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d0eb245f54..e8004c75f7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -96,9 +96,14 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - Updated the notes about reproducible builds with SCons and the example. - The Clone() method now respects the variables argument (fixes #3590) - is_valid_construction_var() (not part of the public API) moved from - SCons.Environment to SCons.Util to avoid the chance of import loops. Variables - and Environment both use the routine and Environment() uses a Variables() - object so better to move to a safer location. + SCons.Environment to SCons.Util to avoid the chance of import loops. + Variables and Environment both use the routine and Environment() uses + a Variables() object so better to move to a safer location. + - ListVariable now has a separate validator, with the functionality + that was previously part of the converter. The main effect is to + allow a developer to supply a custom validator, which previously + could be inhibited by the converter failing before the validator + is reached. RELEASE 4.7.0 - Sun, 17 Mar 2024 17:22:20 -0700 diff --git a/RELEASE.txt b/RELEASE.txt index 8955e465d0..fc066809af 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -53,6 +53,11 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY - The Variables object Add method now accepts a subst keyword argument (defaults to True) which can be set to inhibit substitution prior to calling the variable's converter and validator. +- ListVariable now has a separate validator, with the functionality + that was previously part of the converter. The main effect is to + allow a developer to supply a custom validator, which previously + could be inhibited by the converter failing before the validator + is reached. FIXES ----- diff --git a/SCons/Variables/ListVariable.py b/SCons/Variables/ListVariable.py index 0c9fa13289..bbecbaf948 100644 --- a/SCons/Variables/ListVariable.py +++ b/SCons/Variables/ListVariable.py @@ -23,10 +23,11 @@ """Variable type for List Variables. -A list variable may given as 'all', 'none' or a list of names -separated by comma. After the variable has been processed, the variable -value holds either the named list elements, all list elements or no -list elements at all. +A list variable allows selecting one or more from a supplied set of +allowable values, as well as from an optional mapping of alternate names +(such as aliases and abbreviations) and the special names ``'all'`` and +``'none'``. Specified values are converted during processing into values +only from the allowable values set. Usage example:: @@ -63,12 +64,18 @@ class _ListVariable(collections.UserList): """Internal class holding the data for a List Variable. - The initializer accepts two arguments, the list of actual values - given, and the list of allowable values. Not normally instantiated - by hand, but rather by the ListVariable converter function. + This is normally not directly instantiated, rather the ListVariable + converter callback "converts" string input (or the default value + if none) into an instance and stores it. + + Args: + initlist: the list of actual values given. + allowedElems: the list of allowable values. """ - def __init__(self, initlist=None, allowedElems=None) -> None: + def __init__( + self, initlist: Optional[list] = None, allowedElems: Optional[list] = None + ) -> None: if initlist is None: initlist = [] if allowedElems is None: @@ -106,7 +113,12 @@ def prepare_to_store(self): return str(self) def _converter(val, allowedElems, mapdict) -> _ListVariable: - """Convert list variables.""" + """Callback to convert list variables into a suitable form. + + The arguments *allowedElems* and *mapdict* are non-standard + for a :class:`Variables` converter: the lambda in the + :func:`ListVariable` function arranges for us to be called correctly. + """ if val == 'none': val = [] elif val == 'all': @@ -114,18 +126,47 @@ def _converter(val, allowedElems, mapdict) -> _ListVariable: else: val = [_f for _f in val.split(',') if _f] val = [mapdict.get(v, v) for v in val] - notAllowed = [v for v in val if v not in allowedElems] - if notAllowed: - raise ValueError( - f"Invalid value(s) for option: {','.join(notAllowed)}" - ) return _ListVariable(val, allowedElems) -# def _validator(key, val, env) -> None: -# """ """ -# # TODO: write validator for list variable -# pass +def _validator(key, val, env) -> None: + """Callback to validate supplied value(s) for a ListVariable. + + Validation means "is *val* in the allowed list"? *val* has + been subject to substitution before the validator is called. The + converter created a :class:`_ListVariable` container which is stored + in *env* after it runs; this includes the allowable elements list. + Substitution makes a string made out of the values (only), + so we need to fish the allowed elements list out of the environment + to complete the validation. + + Note that since 18b45e456, whether or not ``subst`` has been + called is conditional on the value of the *subst* argument to + :meth:`~SCons.Variables.Variables.Add`, so we have to account for + possible different types of *val*. + + Raises: + UserError: if validation failed. + + .. versionadded:: 4.8.0 + ``_validator`` split off from :func:`_converter` with an additional + check for whether *val* has been substituted before the call. + """ + allowedElems = env[key].allowedElems + if isinstance(val, _ListVariable): # not substituted, use .data + notAllowed = [v for v in val.data if v not in allowedElems] + else: # val will be a string + notAllowed = [v for v in val.split() if v not in allowedElems] + if notAllowed: + # Converter only synthesized 'all' and 'none', they are never + # in the allowed list, so we need to add those to the error message + # (as is done for the help msg). + valid = ','.join(allowedElems + ['all', 'none']) + msg = ( + f"Invalid value(s) for variable {key!r}: {','.join(notAllowed)!r}. " + f"Valid values are: {valid}" + ) + raise SCons.Errors.UserError(msg) from None # lint: W0622: Redefining built-in 'help' (redefined-builtin) @@ -136,6 +177,7 @@ def ListVariable( default: Union[str, List[str]], names: List[str], map: Optional[dict] = None, + validator: Optional[Callable] = None, ) -> Tuple[str, str, str, None, Callable]: """Return a tuple describing a list variable. @@ -149,25 +191,35 @@ def ListVariable( the allowable values (not including any extra names from *map*). default: the default value(s) for the list variable. Can be given as string (possibly comma-separated), or as a list of strings. - ``all`` or ``none`` are allowed as *default*. + ``all`` or ``none`` are allowed as *default*. You can also simulate + a must-specify ListVariable by giving a *default* that is not part + of *names*, it will fail validation if not supplied. names: the allowable values. Must be a list of strings. map: optional dictionary to map alternative names to the ones in *names*, providing a form of alias. The converter will make the replacement, names from *map* are not stored and will not appear in the help message. + validator: optional callback to validate supplied values. + The default validator is used if not specified. Returns: A tuple including the correct converter and validator. The result is usable as input to :meth:`~SCons.Variables.Variables.Add`. + + .. versionchanged:: 4.8.0 + The validation step was split from the converter to allow for + custom validators. The *validator* keyword argument was added. """ if map is None: map = {} + if validator is None: + validator = _validator names_str = f"allowed names: {' '.join(names)}" if SCons.Util.is_List(default): default = ','.join(default) help = '\n '.join( (help, '(all|none|comma-separated list of names)', names_str)) - return key, help, default, None, lambda val: _converter(val, names, map) + return key, help, default, validator, lambda val: _converter(val, names, map) # Local Variables: # tab-width:4 diff --git a/SCons/Variables/ListVariableTests.py b/SCons/Variables/ListVariableTests.py index 54aad051d1..62ce879b75 100644 --- a/SCons/Variables/ListVariableTests.py +++ b/SCons/Variables/ListVariableTests.py @@ -38,7 +38,7 @@ def test_ListVariable(self) -> None: assert o.key == 'test', o.key assert o.help == 'test option help\n (all|none|comma-separated list of names)\n allowed names: one two three', repr(o.help) assert o.default == 'all', o.default - assert o.validator is None, o.validator + assert o.validator is not None, o.validator assert o.converter is not None, o.converter opts = SCons.Variables.Variables() @@ -52,9 +52,15 @@ def test_ListVariable(self) -> None: def test_converter(self) -> None: """Test the ListVariable converter""" opts = SCons.Variables.Variables() - opts.Add(SCons.Variables.ListVariable('test', 'test option help', 'all', - ['one', 'two', 'three'], - {'ONE':'one', 'TWO':'two'})) + opts.Add( + SCons.Variables.ListVariable( + 'test', + 'test option help', + 'all', + ['one', 'two', 'three'], + {'ONE': 'one', 'TWO': 'two'}, + ) + ) o = opts.options[0] @@ -101,8 +107,12 @@ def test_converter(self) -> None: x = o.converter('three,ONE,TWO') assert str(x) == 'all', x - with self.assertRaises(ValueError): - x = o.converter('no_match') + # invalid value should convert (no change) without error + x = o.converter('no_match') + assert str(x) == 'no_match', x + # ... and fail to validate + with self.assertRaises(SCons.Errors.UserError): + z = o.validator('test', 'no_match', {"test": x}) def test_copy(self) -> None: """Test copying a ListVariable like an Environment would""" diff --git a/doc/man/scons.xml b/doc/man/scons.xml index 57d38e8ee0..c51936832f 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -5203,7 +5203,7 @@ converted to lower case. - ListVariable(key, help, default, names, [map]) + ListVariable(key, help, default, names, [map, validator]) Set up a variable @@ -5225,6 +5225,8 @@ separated by commas. default may be specified either as a string of comma-separated value, or as a list of values. + + The optional map argument is a dictionary @@ -5236,6 +5238,14 @@ list. (Note that the additional values accepted through the use of a map are not reflected in the generated help message). + +The optional validator argument +can be used to specify a custom validator callback function, +as described for Add. +The default is to use an internal validator routine. + +New in 4.8.0: validator. + diff --git a/test/Variables/BoolVariable.py b/test/Variables/BoolVariable.py index c63322b29a..aa01a30a14 100644 --- a/test/Variables/BoolVariable.py +++ b/test/Variables/BoolVariable.py @@ -39,10 +39,7 @@ def check(expect): test.write(SConstruct_path, """\ -from SCons.Variables.BoolVariable import BoolVariable - -BV = BoolVariable - +from SCons.Variables.BoolVariable import BoolVariable as BV from SCons.Variables import BoolVariable opts = Variables(args=ARGUMENTS) @@ -51,7 +48,8 @@ def check(expect): BV('profile', 'create profiling informations', False), ) -env = Environment(variables=opts) +_ = DefaultEnvironment(tools=[]) +env = Environment(variables=opts, tools=[]) Help(opts.GenerateHelpText(env)) print(env['warnings']) @@ -69,7 +67,7 @@ def check(expect): expect_stderr = """ scons: *** Error converting option: 'warnings' Invalid value for boolean variable: 'irgendwas' -""" + test.python_file_line(SConstruct_path, 13) +""" + test.python_file_line(SConstruct_path, 11) test.run(arguments='warnings=irgendwas', stderr=expect_stderr, status=2) diff --git a/test/Variables/EnumVariable.py b/test/Variables/EnumVariable.py index 111fff3504..066df741be 100644 --- a/test/Variables/EnumVariable.py +++ b/test/Variables/EnumVariable.py @@ -38,9 +38,7 @@ def check(expect): assert result[1:len(expect)+1] == expect, (result[1:len(expect)+1], expect) test.write(SConstruct_path, """\ -from SCons.Variables.EnumVariable import EnumVariable -EV = EnumVariable - +from SCons.Variables.EnumVariable import EnumVariable as EV from SCons.Variables import EnumVariable list_of_libs = Split('x11 gl qt ical') @@ -58,7 +56,8 @@ def check(expect): map={}, ignorecase=2), # make lowercase ) -env = Environment(variables=opts) +_ = DefaultEnvironment(tools=[]) +env = Environment(variables=opts, tools=[]) Help(opts.GenerateHelpText(env)) print(env['debug']) @@ -78,19 +77,19 @@ def check(expect): expect_stderr = """ scons: *** Invalid value for enum variable 'debug': 'FULL'. Valid values are: ('yes', 'no', 'full') -""" + test.python_file_line(SConstruct_path, 21) +""" + test.python_file_line(SConstruct_path, 20) test.run(arguments='debug=FULL', stderr=expect_stderr, status=2) expect_stderr = """ scons: *** Invalid value for enum variable 'guilib': 'irgendwas'. Valid values are: ('motif', 'gtk', 'kde') -""" + test.python_file_line(SConstruct_path, 21) +""" + test.python_file_line(SConstruct_path, 20) test.run(arguments='guilib=IrGeNdwas', stderr=expect_stderr, status=2) expect_stderr = """ scons: *** Invalid value for enum variable 'some': 'irgendwas'. Valid values are: ('xaver', 'eins') -""" + test.python_file_line(SConstruct_path, 21) +""" + test.python_file_line(SConstruct_path, 20) test.run(arguments='some=IrGeNdwas', stderr=expect_stderr, status=2) diff --git a/test/Variables/ListVariable.py b/test/Variables/ListVariable.py index 2bd032733f..52f1bc56ef 100644 --- a/test/Variables/ListVariable.py +++ b/test/Variables/ListVariable.py @@ -43,9 +43,7 @@ def check(expect): test.write(SConstruct_path, """\ -from SCons.Variables.ListVariable import ListVariable -LV = ListVariable - +from SCons.Variables.ListVariable import ListVariable as LV from SCons.Variables import ListVariable list_of_libs = Split('x11 gl qt ical') @@ -59,10 +57,10 @@ def check(expect): names = list_of_libs, map = {'GL':'gl', 'QT':'qt'}), LV('listvariable', 'listvariable help', 'all', names=['l1', 'l2', 'l3']) - ) +) -DefaultEnvironment(tools=[]) # test speedup -env = Environment(variables=opts) +_ = DefaultEnvironment(tools=[]) # test speedup +env = Environment(variables=opts, tools=[]) opts.Save(optsfile, env) Help(opts.GenerateHelpText(env)) @@ -113,39 +111,34 @@ def check(expect): expect_stderr = """ -scons: *** Error converting option: 'shared' -Invalid value(s) for option: foo -""" + test.python_file_line(SConstruct_path, 20) +scons: *** Invalid value(s) for variable 'shared': 'foo'. Valid values are: gl,ical,qt,x11,all,none +""" + test.python_file_line(SConstruct_path, 18) test.run(arguments='shared=foo', stderr=expect_stderr, status=2) # be paranoid in testing some more combinations expect_stderr = """ -scons: *** Error converting option: 'shared' -Invalid value(s) for option: foo -""" + test.python_file_line(SConstruct_path, 20) +scons: *** Invalid value(s) for variable 'shared': 'foo'. Valid values are: gl,ical,qt,x11,all,none +""" + test.python_file_line(SConstruct_path, 18) test.run(arguments='shared=foo,ical', stderr=expect_stderr, status=2) expect_stderr = """ -scons: *** Error converting option: 'shared' -Invalid value(s) for option: foo -""" + test.python_file_line(SConstruct_path, 20) +scons: *** Invalid value(s) for variable 'shared': 'foo'. Valid values are: gl,ical,qt,x11,all,none +""" + test.python_file_line(SConstruct_path, 18) test.run(arguments='shared=ical,foo', stderr=expect_stderr, status=2) expect_stderr = """ -scons: *** Error converting option: 'shared' -Invalid value(s) for option: foo -""" + test.python_file_line(SConstruct_path, 20) +scons: *** Invalid value(s) for variable 'shared': 'foo'. Valid values are: gl,ical,qt,x11,all,none +""" + test.python_file_line(SConstruct_path, 18) test.run(arguments='shared=ical,foo,x11', stderr=expect_stderr, status=2) expect_stderr = """ -scons: *** Error converting option: 'shared' -Invalid value(s) for option: foo,bar -""" + test.python_file_line(SConstruct_path, 20) +scons: *** Invalid value(s) for variable 'shared': 'foo,bar'. Valid values are: gl,ical,qt,x11,all,none +""" + test.python_file_line(SConstruct_path, 18) test.run(arguments='shared=foo,x11,,,bar', stderr=expect_stderr, status=2) diff --git a/test/Variables/PackageVariable.py b/test/Variables/PackageVariable.py index 47ee01bcc9..64e0fa878c 100644 --- a/test/Variables/PackageVariable.py +++ b/test/Variables/PackageVariable.py @@ -39,9 +39,7 @@ def check(expect): assert result[1:len(expect)+1] == expect, (result[1:len(expect)+1], expect) test.write(SConstruct_path, """\ -from SCons.Variables.PackageVariable import PackageVariable -PV = PackageVariable - +from SCons.Variables.PackageVariable import PackageVariable as PV from SCons.Variables import PackageVariable opts = Variables(args=ARGUMENTS) @@ -52,7 +50,8 @@ def check(expect): PV('package', 'help for package', 'yes'), ) -env = Environment(variables=opts) +_ = DefaultEnvironment(tools=[]) +env = Environment(variables=opts, tools=[]) Help(opts.GenerateHelpText(env)) print(env['x11']) @@ -73,7 +72,7 @@ def check(expect): expect_stderr = """ scons: *** Path does not exist for variable 'x11': '/non/existing/path/' -""" + test.python_file_line(SConstruct_path, 14) +""" + test.python_file_line(SConstruct_path, 13) test.run(arguments='x11=/non/existing/path/', stderr=expect_stderr, status=2) diff --git a/test/Variables/PathVariable.py b/test/Variables/PathVariable.py index 265f48f52f..61b21b3573 100644 --- a/test/Variables/PathVariable.py +++ b/test/Variables/PathVariable.py @@ -47,9 +47,7 @@ def check(expect): libpath = os.path.join(workpath, 'lib') test.write(SConstruct_path, """\ -from SCons.Variables.PathVariable import PathVariable -PV = PathVariable - +from SCons.Variables.PathVariable import PathVariable as PV from SCons.Variables import PathVariable qtdir = r'%s' @@ -60,8 +58,8 @@ def check(expect): PV('qt_libraries', 'where the Qt library is installed', r'%s'), ) -DefaultEnvironment(tools=[]) # test speedup -env = Environment(variables=opts) +_ = DefaultEnvironment(tools=[]) # test speedup +env = Environment(variables=opts, tools=[]) Help(opts.GenerateHelpText(env)) print(env['qtdir']) @@ -92,7 +90,7 @@ def check(expect): check([qtpath, libpath, libpath]) qtpath = os.path.join(workpath, 'non', 'existing', 'path') -SConstruct_file_line = test.python_file_line(test.workpath('SConstruct'), 15)[:-1] +SConstruct_file_line = test.python_file_line(test.workpath('SConstruct'), 13)[:-1] expect_stderr = """ scons: *** Path for variable 'qtdir' does not exist: %(qtpath)s diff --git a/test/Variables/Variables.py b/test/Variables/Variables.py index 77426c5913..d585b57a42 100644 --- a/test/Variables/Variables.py +++ b/test/Variables/Variables.py @@ -23,12 +23,21 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" +Test general behavior of Variables including save files. + +Note this test is coded to expect a compiler tool to have run +so that CC and CCFLAGS are set. The first test "run" collects +those values and uses them as a baseline for the actual tests. +We should be able to mock that in some way. +""" + import TestSCons test = TestSCons.TestSCons() test.write('SConstruct', """\ -DefaultEnvironment(tools=[]) # test speedup +_ = DefaultEnvironment(tools=[]) # test speedup env = Environment() print(env['CC']) print(" ".join(env['CCFLAGS'])) @@ -277,7 +286,7 @@ def checkSave(file, expected): names = ['a','b','c',]) DefaultEnvironment(tools=[]) # test speedup -env = Environment(variables=opts) +env = Environment(variables=opts, tools=[]) print(env['RELEASE_BUILD']) print(env['DEBUG_BUILD']) diff --git a/test/Variables/chdir.py b/test/Variables/chdir.py index 2c553e2374..ed7cf2be3e 100644 --- a/test/Variables/chdir.py +++ b/test/Variables/chdir.py @@ -43,7 +43,8 @@ SConscript_contents = """\ Import("opts") -env = Environment() +_ = DefaultEnvironment(tools=[]) +env = Environment(tools=[]) opts.Update(env) print("VARIABLE = "+repr(env['VARIABLE'])) """ diff --git a/test/Variables/help.py b/test/Variables/help.py index 186a173d7b..84a405a9d3 100644 --- a/test/Variables/help.py +++ b/test/Variables/help.py @@ -92,7 +92,8 @@ PathVariable('qt_libraries', 'where the Qt library is installed', r'%(libdirvar)s'), ) -env = Environment(variables=opts) +_ = DefaultEnvironment(tools=[]) +env = Environment(variables=opts, tools=[]) Help(opts.GenerateHelpText(env)) print(env['warnings']) diff --git a/test/Variables/import.py b/test/Variables/import.py index 8db7624476..833b299c9f 100644 --- a/test/Variables/import.py +++ b/test/Variables/import.py @@ -45,7 +45,8 @@ SConscript_contents = """\ Import("opts") -env = Environment() +_ = DefaultEnvironment(tools=[]) +env = Environment(tools=[]) opts.Update(env) print("VARIABLE = %s"%env.get('VARIABLE')) """ From 2093f7a1bd5d2cd5a0e603b02ad672fb0fb3fafe Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 17 Jun 2024 14:57:09 -0600 Subject: [PATCH 17/23] AddOption now recognizes "settable" option AddOption and the internal add_local_option which AddOption calls now recognize a "settable" keyword argument to indicate a project-added option can also be modified using SetOption. There was a TODO in SCons/Script/SConsOptions.py about removing three hardcoded ninja options in the "settable" list once this change was made. When that was done it turned out one test failed, because it called the SetOption before the ninja tool was initialized. Logic reordered in the test, but this is a thing to watch out for. Closes #3983. Signed-off-by: Mats Wichmann --- CHANGES.txt | 3 + RELEASE.txt | 3 + SCons/Script/Main.py | 43 ++++-- SCons/Script/Main.xml | 40 ++++-- SCons/Script/SConsOptions.py | 136 ++++++++++-------- SCons/Tool/ninja/Utils.py | 3 + doc/user/command-line.xml | 20 +-- test/AddOption/basic.py | 57 ++++---- test/AddOption/help.py | 29 ++-- test/GetOption/BadSetOption.py | 9 +- .../sconstruct_ninja_determinism | 4 +- 11 files changed, 212 insertions(+), 135 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d0eb245f54..8005b68d92 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -99,6 +99,9 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER SCons.Environment to SCons.Util to avoid the chance of import loops. Variables and Environment both use the routine and Environment() uses a Variables() object so better to move to a safer location. + - AddOption and the internal add_local_option which AddOption calls now + recognize a "settable" keyword argument to indicate a project-added + option can also be modified using SetOption. Fixes #3983. RELEASE 4.7.0 - Sun, 17 Mar 2024 17:22:20 -0700 diff --git a/RELEASE.txt b/RELEASE.txt index 8955e465d0..4d61af3674 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -53,6 +53,9 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY - The Variables object Add method now accepts a subst keyword argument (defaults to True) which can be set to inhibit substitution prior to calling the variable's converter and validator. +- AddOption and the internal add_local_option which AddOption calls now + recognize a "settable" keyword argument to indicate a project-added + option can also be modified using SetOption. FIXES ----- diff --git a/SCons/Script/Main.py b/SCons/Script/Main.py index d00da42cc8..a5f8f42c2d 100644 --- a/SCons/Script/Main.py +++ b/SCons/Script/Main.py @@ -41,7 +41,7 @@ import traceback import platform import threading -from typing import Optional, List +from typing import Optional, List, TYPE_CHECKING import SCons.CacheDir import SCons.Debug @@ -59,6 +59,8 @@ import SCons.Util import SCons.Warnings import SCons.Script.Interactive +if TYPE_CHECKING: + from SCons.Script import SConsOption from SCons.Util.stats import count_stats, memory_stats, time_stats, ENABLE_JSON, write_scons_stats_file, JSON_OUTPUT_FILE from SCons import __version__ as SConsVersion @@ -174,6 +176,7 @@ def __call__(self, node) -> None: ProgressObject = SCons.Util.Null() def Progress(*args, **kw) -> None: + """Show progress during building - Public API.""" global ProgressObject ProgressObject = Progressor(*args, **kw) @@ -501,29 +504,47 @@ def __getattr__(self, attr): # TODO: to quiet checkers, FakeOptionParser should also define # raise_exception_on_error, preserve_unknown_options, largs and parse_args - def add_local_option(self, *args, **kw) -> None: + def add_local_option(self, *args, **kw) -> "SConsOption": pass OptionsParser = FakeOptionParser() -def AddOption(*args, **kw): +def AddOption(*args, settable: bool = False, **kw) -> "SConsOption": + """Add a local option to the option parser - Public API. + + If the *settable* parameter is true, the option will be included in the + list of settable options; all other keyword arguments are passed on to + :meth:`~SCons.Script.SConsOptions.SConsOptionParser.add_local_option`. + + .. versionchanged:: 4.8.0 + The *settable* parameter added to allow including the new option + to the table of options eligible to use :func:`SetOption`. + + """ if 'default' not in kw: kw['default'] = None + kw['settable'] = settable result = OptionsParser.add_local_option(*args, **kw) return result -def GetOption(name): +def GetOption(name: str): + """Get the value from an option - Public API.""" return getattr(OptionsParser.values, name) -def SetOption(name, value): +def SetOption(name: str, value): + """Set the value of an option - Public API.""" return OptionsParser.values.set_option(name, value) -def DebugOptions(json=None): - """ - API to allow specifying options to SCons debug logic - Currently only json is supported which changes the - json file written by --debug=json from the default +def DebugOptions(json: Optional[str] = None) -> None: + """Specify options to SCons debug logic - Public API. + + Currently only *json* is supported, which changes the JSON file + written to if the ``--debug=json`` command-line option is specified + to the value supplied. + + .. versionadded:: 4.6.0 + """ if json is not None: json_node = SCons.Defaults.DefaultEnvironment().arg2nodes(json) @@ -540,7 +561,7 @@ def DebugOptions(json=None): raise SCons.Errors.UserError(f"Unable to create directory for JSON debug output file: {SCons.Util.stats.JSON_OUTPUT_FILE}") -def ValidateOptions(throw_exception: bool=False) -> None: +def ValidateOptions(throw_exception: bool = False) -> None: """Validate options passed to SCons on the command line. Checks that all options given on the command line are known to this diff --git a/SCons/Script/Main.xml b/SCons/Script/Main.xml index 36e7d305c5..6b63e91bfc 100644 --- a/SCons/Script/Main.xml +++ b/SCons/Script/Main.xml @@ -93,21 +93,25 @@ the option value may be accessed using &f-link-GetOption; or &f-link-env-GetOption;. -&f-link-SetOption; is not currently supported for -options added with &f-AddOption;. - +override a value set in an SConscript file. + + + +Changed in 4.8.0: added the +settable keyword argument +to enable an added option to be settable via &SetOption;. @@ -196,6 +200,7 @@ Allows setting options for SCons debug options. Currently the only supported val DebugOptions(json='#/build/output/scons_stats.json') +New in version 4.6.0. @@ -334,9 +339,10 @@ atexit.register(print_build_failures) Query the value of settable options which may have been set -on the command line, or by using the &f-link-SetOption; function. +on the command line, via option defaults, +or by using the &f-link-SetOption; function. The value of the option is returned in a type matching how the -option was declared - see the documentation for the +option was declared - see the documentation of the corresponding command line option for information about each specific option. @@ -801,6 +807,16 @@ are not settable using &f-SetOption; since those files must be read in order to find the &f-SetOption; call in the first place. + +For project-specific options (sometimes called +local options) +added via an &f-link-AddOption; call, +&f-SetOption; is available only after the +&f-AddOption; call has completed successfully, +and only if that call included the +settable=True argument. + + The settable variables with their associated command-line options are: diff --git a/SCons/Script/SConsOptions.py b/SCons/Script/SConsOptions.py index e8e5cbf4e9..780c83ba25 100644 --- a/SCons/Script/SConsOptions.py +++ b/SCons/Script/SConsOptions.py @@ -66,17 +66,18 @@ def diskcheck_convert(value): class SConsValues(optparse.Values): - """ - Holder class for uniform access to SCons options, regardless - of whether they can be set on the command line or in the - SConscript files (using the SetOption() function). + """Holder class for uniform access to SCons options. + + Usable whether or not of whether they can be set on the command line + or in the SConscript files (using the :func:`~SComs.Main.SetOption` + function). A SCons option value can originate three different ways: - 1) set on the command line; - 2) set in an SConscript file; - 3) the default setting (from the the op.add_option() - calls in the Parser() function, below). + 1) set on the command line; + 2) set in an SConscript file; + 3) the default setting (from the the ``op.add_option`` + calls in the :func:`Parser` function, below). The command line always overrides a value set in a SConscript file, which in turn always overrides default settings. Because we want @@ -87,15 +88,16 @@ class SConsValues(optparse.Values): The solution implemented in this class is to keep these different sets of settings separate (command line, SConscript file, and default) - and to override the __getattr__() method to check them in turn. + and to override the :meth:`__getattr__` method to check them in turn + (a little similar in concept to a ChainMap). This should allow the rest of the code to just fetch values as attributes of an instance of this class, without having to worry about where they came from. Note that not all command line options are settable from SConscript files, and the ones that are must be explicitly added to the - "settable" list in this class, and optionally validated and coerced - in the set_option() method. + :attr:`settable` list in this class, and optionally validated and coerced + in the :meth:`set_option` method. """ def __init__(self, defaults) -> None: @@ -103,10 +105,11 @@ def __init__(self, defaults) -> None: self.__SConscript_settings__ = {} def __getattr__(self, attr): - """ - Fetches an options value, checking first for explicit settings - from the command line (which are direct attributes), then the - SConscript file settings, then the default values. + """Fetch an options value, respecting priority rules. + + Check first for explicit settings from the command line + (which are direct attributes), then the SConscript file settings, + then the default values. """ try: return self.__dict__[attr] @@ -148,26 +151,24 @@ def __getattr__(self, attr): 'silent', 'stack_size', 'warn', - - # TODO: Remove these once we update the AddOption() API to allow setting - # added flag as settable. - # Requested settable flag in : https://github.com/SCons/scons/issues/3983 - # From experimental ninja - 'disable_execute_ninja', - 'disable_ninja', - 'skip_ninja_regen' ] def set_option(self, name, value): - """Sets an option from an SConscript file. + """Sets an option *name* from an SConscript file. + + Any necessary validation steps are in-line here. Validation should + be along the same lines as for options processed from the command + line - it's kind of a pain to have to duplicate. On the other + hand, we can hope that the build maintainer will be more careful + about correct SetOption values and it's not as big a deal to + have validation for everything. Raises: - UserError: invalid or malformed option ("error in your script") + UserError: the option is not settable. """ - if name not in self.settable: raise SCons.Errors.UserError( - "This option is not settable from a SConscript file: %s" % name + f"This option is not settable from an SConscript file: {name!r}" ) # the following are for options that need some extra processing @@ -247,7 +248,6 @@ def convert_value(self, opt, value): return tuple([self.check_value(opt, v) for v in value]) def process(self, opt, value, values, parser): - # First, convert the value(s) to the right type. Howl if any # value(s) are bogus. value = self.convert_value(opt, value) @@ -397,46 +397,46 @@ def _process_long_opt(self, rargs, values): option.process(opt, value, values, self) def reparse_local_options(self) -> None: - """ Re-parse the leftover command-line options. + """Re-parse the leftover command-line options. - Parse options stored in `self.largs`, so that any value + Parse options are stored in ``self.largs``, so that any value overridden on the command line is immediately available - if the user turns around and does a :func:`GetOption` right away. + if the user turns around and does a :func:`~SCons.Script.Main.GetOption` + right away. We mimic the processing of the single args in the original OptionParser :func:`_process_args`, but here we allow exact matches for long-opts only (no partial argument names!). - Otherwise there could be problems in :func:`add_local_option` + Otherwise there could be problems in :meth:`add_local_option` below. When called from there, we try to reparse the command-line arguments that 1. haven't been processed so far (`self.largs`), but 2. are possibly not added to the list of options yet. - So, when we only have a value for "--myargument" so far, - a command-line argument of "--myarg=test" would set it, + So, when we only have a value for ``--myargument`` so far, + a command-line argument of ``--myarg=test`` would set it, per the behaviour of :func:`_match_long_opt`, which allows for partial matches of the option name, as long as the common prefix appears to be unique. This would lead to further confusion, because we might want - to add another option "--myarg" later on (see issue #2929). - + to add another option ``--myarg`` later on (see issue #2929). """ rargs = [] largs_restore = [] # Loop over all remaining arguments skip = False - for l in self.largs: + for larg in self.largs: if skip: # Accept all remaining arguments as they are - largs_restore.append(l) + largs_restore.append(larg) else: - if len(l) > 2 and l[0:2] == "--": + if len(larg) > 2 and larg[0:2] == "--": # Check long option - lopt = (l,) - if "=" in l: + lopt = [larg] + if "=" in larg: # Split into option and value - lopt = l.split("=", 1) + lopt = larg.split("=", 1) if lopt[0] in self._long_opt: # Argument is already known @@ -445,26 +445,35 @@ def reparse_local_options(self) -> None: # Not known yet, so reject for now largs_restore.append('='.join(lopt)) else: - if l == "--" or l == "-": + if larg in("--", "-"): # Stop normal processing and don't # process the rest of the command-line opts - largs_restore.append(l) + largs_restore.append(larg) skip = True else: - rargs.append(l) + rargs.append(larg) # Parse the filtered list self.parse_args(rargs, self.values) - # Restore the list of remaining arguments for the + # Restore the list of leftover arguments for the # next call of AddOption/add_local_option... self.largs = self.largs + largs_restore - def add_local_option(self, *args, **kw): + def add_local_option(self, *args, **kw) -> SConsOption: """ Adds a local option to the parser. - This is initiated by an :func:`AddOption` call to add a user-defined - command-line option. We add the option to a separate option - group for the local options, creating the group if necessary. + This is initiated by an :func:`~SCons.Script.Main.AddOption` call to + add a user-defined command-line option. Add the option to a separate + option group for the local options, creating the group if necessary. + + The keyword argument *settable* is recognized specially (and + removed from *kw*). If true, the option is marked as modifiable; + by default "local" (project-added) options are not eligible for + for :func:`~SCons.Script.Main.SetOption` calls. + + .. versionchanged:: 4.8.0 + Added special handling of *settable*. + """ try: group = self.local_option_group @@ -473,6 +482,7 @@ def add_local_option(self, *args, **kw): group = self.add_option_group(group) self.local_option_group = group + settable = kw.pop('settable') result = group.add_option(*args, **kw) if result: # The option was added successfully. We now have to add the @@ -485,6 +495,8 @@ def add_local_option(self, *args, **kw): # right away. setattr(self.values.__defaults__, result.dest, result.default) self.reparse_local_options() + if settable: + SConsValues.settable.append(result.dest) return result @@ -614,7 +626,8 @@ def store_local_option_strings(self, parser, group): """Local-only version of store_option_strings. We need to replicate this so the formatter will be set up - properly if we didn't go through the "normal" store_option_strings + properly if we didn't go through the "normal" + :math:`~optparse.HelpFormatter.store_option_strings`. .. versionadded:: 4.6.0 """ @@ -633,15 +646,18 @@ def Parser(version): """Returns a parser object initialized with the standard SCons options. Add options in the order we want them to show up in the ``-H`` help - text, basically alphabetical. Each ``op.add_option()`` call - should have a consistent format:: - - op.add_option("-L", "--long-option-name", - nargs=1, type="string", - dest="long_option_name", default='foo', - action="callback", callback=opt_long_option, - help="help text goes here", - metavar="VAR") + text, basically alphabetical. For readability, Each + :meth:`~optparse.OptionContainer.add_option` call should have a + consistent format:: + + op.add_option( + "-L", "--long-option-name", + nargs=1, type="string", + dest="long_option_name", default='foo', + action="callback", callback=opt_long_option, + help="help text goes here", + metavar="VAR" + ) Even though the :mod:`optparse` module constructs reasonable default destination names from the long option names, we're going to be diff --git a/SCons/Tool/ninja/Utils.py b/SCons/Tool/ninja/Utils.py index 126d5de0a9..cdce92895c 100644 --- a/SCons/Tool/ninja/Utils.py +++ b/SCons/Tool/ninja/Utils.py @@ -45,6 +45,7 @@ def ninja_add_command_line_options() -> None: metavar='BOOL', action="store_true", default=False, + settable=True, help='Disable automatically running ninja after scons') AddOption('--disable-ninja', @@ -52,6 +53,7 @@ def ninja_add_command_line_options() -> None: metavar='BOOL', action="store_true", default=False, + settable=True, help='Disable ninja generation and build with scons even if tool is loaded. '+ 'Also used by ninja to build targets which only scons can build.') @@ -60,6 +62,7 @@ def ninja_add_command_line_options() -> None: metavar='BOOL', action="store_true", default=False, + settable=True, help='Allow scons to skip regeneration of the ninja file and restarting of the daemon. ' + 'Care should be taken in cases where Glob is in use or SCons generated files are used in ' + 'command lines.') diff --git a/doc/user/command-line.xml b/doc/user/command-line.xml index fea1af1336..d4bb83d00d 100644 --- a/doc/user/command-line.xml +++ b/doc/user/command-line.xml @@ -417,8 +417,10 @@ foo.in - &SetOption; is not currently supported for - options added with &AddOption;. + &SetOption; works for options added with &AddOption;, + but only if they were created with + settable=True in the call to &AddOption; + (only available in SCons 4.8.0 and later). @@ -660,14 +662,12 @@ foo.in - &SetOption; is not currently supported for - options added with &AddOption;. - + + &f-link-SetOption; works for options added with &AddOption;, + but only if they were created with + settable=True in the call to &AddOption; + (only available in SCons 4.8.0 and later). + diff --git a/test/AddOption/basic.py b/test/AddOption/basic.py index 64fb7ba1c1..9fc3c4d9f5 100644 --- a/test/AddOption/basic.py +++ b/test/AddOption/basic.py @@ -24,8 +24,8 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -Verify the help text when the AddOption() function is used (and when -it's not). +Verify added options give the expected default/command line values +when fetched with GetOption. """ import TestSCons @@ -35,35 +35,44 @@ test.write('SConstruct', """\ DefaultEnvironment(tools=[]) env = Environment(tools=[]) -AddOption('--force', - action="store_true", - help='force installation (overwrite any existing files)') -AddOption('--prefix', - nargs=1, - dest='prefix', - action='store', - type='string', - metavar='DIR', - help='installation prefix') +AddOption( + '--force', + action="store_true", + help='force installation (overwrite any existing files)', +) +AddOption( + '--prefix', + nargs=1, + dest='prefix', + action='store', + type='string', + metavar='DIR', + settable=True, + help='installation prefix', +) +AddOption( + '--set', + action="store_true", + help="try SetOption of 'prefix' to '/opt/share'" +) f = GetOption('force') if f: f = "True" print(f) print(GetOption('prefix')) +if GetOption('set'): + SetOption('prefix', '/opt/share') + print(GetOption('prefix')) """) -test.run('-Q -q .', - stdout="None\nNone\n") - -test.run('-Q -q . --force', - stdout="True\nNone\n") - -test.run('-Q -q . --prefix=/home/foo', - stdout="None\n/home/foo\n") - -test.run('-Q -q . -- --prefix=/home/foo --force', - status=1, - stdout="None\nNone\n") +test.run('-Q -q .', stdout="None\nNone\n") +test.run('-Q -q . --force', stdout="True\nNone\n") +test.run('-Q -q . --prefix=/home/foo', stdout="None\n/home/foo\n") +test.run('-Q -q . -- --prefix=/home/foo --force', status=1, stdout="None\nNone\n") +# check that SetOption works on prefix... +test.run('-Q -q . --set', stdout="None\nNone\n/opt/share\n") +# but the "command line wins" rule is not violated +test.run('-Q -q . --set --prefix=/home/foo', stdout="None\n/home/foo\n/home/foo\n") test.pass_test() diff --git a/test/AddOption/help.py b/test/AddOption/help.py index 6d855bd2d2..772a381d63 100644 --- a/test/AddOption/help.py +++ b/test/AddOption/help.py @@ -23,6 +23,11 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" +Verify the help text when the AddOption() function is used (and when +it's not). +""" + import TestSCons test = TestSCons.TestSCons() @@ -30,16 +35,20 @@ test.write('SConstruct', """\ DefaultEnvironment(tools=[]) env = Environment(tools=[]) -AddOption('--force', - action="store_true", - help='force installation (overwrite existing files)') -AddOption('--prefix', - nargs=1, - dest='prefix', - action='store', - type='string', - metavar='DIR', - help='installation prefix') +AddOption( + '--force', + action="store_true", + help='force installation (overwrite existing files)', +) +AddOption( + '--prefix', + nargs=1, + dest='prefix', + action='store', + type='string', + metavar='DIR', + help='installation prefix', +) """) expected_lines = [ diff --git a/test/GetOption/BadSetOption.py b/test/GetOption/BadSetOption.py index 7b0a33dcaf..f408e39022 100644 --- a/test/GetOption/BadSetOption.py +++ b/test/GetOption/BadSetOption.py @@ -32,7 +32,7 @@ test = TestSCons.TestSCons() badopts = ( - ("no_such_var", True, "This option is not settable from a SConscript file: no_such_var"), + ("no_such_var", True, "This option is not settable from an SConscript file: 'no_such_var'"), ("num_jobs", -22, "A positive integer is required: -22"), ("max_drift", "'Foo'", "An integer is required: 'Foo'"), ("duplicate", "'cookie'", "Not a valid duplication style: cookie"), @@ -45,11 +45,10 @@ SConstruct = "SC-" + opt test.write( SConstruct, - """\ + f"""\ DefaultEnvironment(tools=[]) -SetOption("%(opt)s", %(value)s) -""" - % locals(), +SetOption("{opt}", {value}) +""", ) expect = r"scons: *** %s" % expect test.run(arguments='-Q -f %s .' % SConstruct, stderr=None, status=2) diff --git a/test/ninja/ninja_test_sconscripts/sconstruct_ninja_determinism b/test/ninja/ninja_test_sconscripts/sconstruct_ninja_determinism index a7982d4067..6d206364ca 100644 --- a/test/ninja/ninja_test_sconscripts/sconstruct_ninja_determinism +++ b/test/ninja/ninja_test_sconscripts/sconstruct_ninja_determinism @@ -5,12 +5,10 @@ import random SetOption('experimental', 'ninja') -SetOption('skip_ninja_regen', True) DefaultEnvironment(tools=[]) - env = Environment(tools=[]) - env.Tool('ninja') +SetOption('skip_ninja_regen', True) # must wait until tool creates the option # make the dependency list vary in order. Ninja tool should sort them to be deterministic. for i in range(1, 10): From 5ca36e89419a5635a54ce66e241a48b335ba6bcf Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Wed, 19 Jun 2024 12:55:45 -0600 Subject: [PATCH 18/23] Semi-phony commit to see if AppVeyor triggers Signed-off-by: Mats Wichmann --- .appveyor.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 07b6fe1826..30783a0b55 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,7 +6,7 @@ image: # linux builds done in Travis CI for now - - Visual Studio 2017 + # - Visual Studio 2017 - Visual Studio 2019 - Visual Studio 2022 diff --git a/pyproject.toml b/pyproject.toml index 8d755dcb2b..be4988bf16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,4 +73,4 @@ target-version = ['py36'] skip-string-normalization = true [tool.mypy] -python_version = "3.6" +python_version = "3.8" From 66d0f8f2c05c4f43c0e1ec3d471b7004501d3219 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Thu, 20 Jun 2024 08:57:56 -0600 Subject: [PATCH 19/23] Tweak some docstrings for Options Also change the build matrix on AppVeyor so we can hopefully get to a pass. Signed-off-by: Mats Wichmann --- .appveyor.yml | 4 +-- SCons/Script/SConsOptions.py | 62 +++++++++++++++--------------------- 2 files changed, 28 insertions(+), 38 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 30783a0b55..e7596da4ae 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,9 +6,9 @@ image: # linux builds done in Travis CI for now - # - Visual Studio 2017 + - Visual Studio 2017 - Visual Studio 2019 - - Visual Studio 2022 + #- Visual Studio 2022 XXX Temporary disable while failing on .10 versions cache: - downloads -> appveyor.yml diff --git a/SCons/Script/SConsOptions.py b/SCons/Script/SConsOptions.py index 780c83ba25..d4cc992682 100644 --- a/SCons/Script/SConsOptions.py +++ b/SCons/Script/SConsOptions.py @@ -68,15 +68,11 @@ def diskcheck_convert(value): class SConsValues(optparse.Values): """Holder class for uniform access to SCons options. - Usable whether or not of whether they can be set on the command line - or in the SConscript files (using the :func:`~SComs.Main.SetOption` - function). - A SCons option value can originate three different ways: - 1) set on the command line; - 2) set in an SConscript file; - 3) the default setting (from the the ``op.add_option`` + 1. set on the command line. + 2. set in an SConscript file via :func:`~SCons.Script.Main.SetOption`. + 3. the default setting (from the the ``op.add_option()`` calls in the :func:`Parser` function, below). The command line always overrides a value set in a SConscript file, @@ -88,11 +84,10 @@ class SConsValues(optparse.Values): The solution implemented in this class is to keep these different sets of settings separate (command line, SConscript file, and default) - and to override the :meth:`__getattr__` method to check them in turn - (a little similar in concept to a ChainMap). - This should allow the rest of the code to just fetch values as - attributes of an instance of this class, without having to worry - about where they came from. + and to override the :meth:`__getattr__` method to check them in turn. + This allows the rest of the code to just fetch values as attributes of + an instance of this class, without having to worry about where they + came from (the scheme is similar to a ``ChainMap``). Note that not all command line options are settable from SConscript files, and the ones that are must be explicitly added to the @@ -107,27 +102,22 @@ def __init__(self, defaults) -> None: def __getattr__(self, attr): """Fetch an options value, respecting priority rules. - Check first for explicit settings from the command line - (which are direct attributes), then the SConscript file settings, - then the default values. + This is a little tricky: since we're answering questions + about outselves, we have avoid lookups that would send us into + into infinite recursion, thus the ``__dict__`` stuff. """ try: - return self.__dict__[attr] + return self.__dict__[attr] except KeyError: try: return self.__dict__['__SConscript_settings__'][attr] except KeyError: try: return getattr(self.__dict__['__defaults__'], attr) - except KeyError: - # Added because with py3 this is a new class, - # not a classic class, and due to the way - # In that case it will create an object without - # __defaults__, and then query for __setstate__ - # which will throw an exception of KeyError - # deepcopy() is expecting AttributeError if __setstate__ - # is not available. - raise AttributeError(attr) + except KeyError as exc: + # Need to respond with AttributeError because + # deepcopy expects that if __setstate__ is not available. + raise AttributeError(attr) from exc # keep this list in sync with the SetOption doc in SCons/Script/Main.xml # search for UPDATE_SETOPTION_DOCS there. @@ -153,15 +143,17 @@ def __getattr__(self, attr): 'warn', ] - def set_option(self, name, value): + def set_option(self, name: str, value) -> None: """Sets an option *name* from an SConscript file. - Any necessary validation steps are in-line here. Validation should - be along the same lines as for options processed from the command - line - it's kind of a pain to have to duplicate. On the other - hand, we can hope that the build maintainer will be more careful - about correct SetOption values and it's not as big a deal to - have validation for everything. + Vvalidation steps for known (that is, defined in SCons itself) + options are in-line here. Validation should be along the same + lines as for options processed from the command line - + it's kind of a pain to have to duplicate. Project-defined options + can specify callbacks for the command-line version, but will have + no inbuilt validation here. It's up to the build system maintainer + to make sure :func:`~SCons.Script.Main.SetOption` is being used + correctly, we can't really do any better here. Raises: UserError: the option is not settable. @@ -313,9 +305,7 @@ class SConsOptionParser(optparse.OptionParser): raise_exception_on_error = False def error(self, msg): - """ - overridden OptionValueError exception handler - """ + """Overridden OptionValueError exception handler.""" if self.raise_exception_on_error: raise SConsBadOptionError(msg, self) else: @@ -399,7 +389,7 @@ def _process_long_opt(self, rargs, values): def reparse_local_options(self) -> None: """Re-parse the leftover command-line options. - Parse options are stored in ``self.largs``, so that any value + Leftover options are stored in ``self.largs``, so that any value overridden on the command line is immediately available if the user turns around and does a :func:`~SCons.Script.Main.GetOption` right away. From 8609d78eb8681778bafd4fba9383cce322ea6c1c Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 23 Jun 2024 15:31:35 -0700 Subject: [PATCH 20/23] [ci skip] fix bad merge of CHANGES blurb --- CHANGES.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index aa306222f5..d5f6f65d64 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -102,9 +102,6 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - AddOption and the internal add_local_option which AddOption calls now recognize a "settable" keyword argument to indicate a project-added option can also be modified using SetOption. Fixes #3983. - SCons.Environment to SCons.Util to avoid the chance of import loops. - Variables and Environment both use the routine and Environment() uses - a Variables() object so better to move to a safer location. - ListVariable now has a separate validator, with the functionality that was previously part of the converter. The main effect is to allow a developer to supply a custom validator, which previously From 25c015ce10b68e40720af616237411c0b5e26953 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 23 Jun 2024 15:34:24 -0700 Subject: [PATCH 21/23] [ci skip] Add note that this change may break SetOption() + ninja usage with fix --- CHANGES.txt | 4 ++++ RELEASE.txt | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index d5f6f65d64..e2c11c5f62 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -102,6 +102,10 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - AddOption and the internal add_local_option which AddOption calls now recognize a "settable" keyword argument to indicate a project-added option can also be modified using SetOption. Fixes #3983. + NOTE: If you were using ninja and using SetOption() for ninja options + in your SConscripts prior to loading the ninja tool, you will now + see an error. The fix is to move the SetOption() to after you've loaded + the ninja tool. - ListVariable now has a separate validator, with the functionality that was previously part of the converter. The main effect is to allow a developer to supply a custom validator, which previously diff --git a/RELEASE.txt b/RELEASE.txt index 5dac018289..d6fa72926a 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -56,6 +56,10 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY - AddOption and the internal add_local_option which AddOption calls now recognize a "settable" keyword argument to indicate a project-added option can also be modified using SetOption. + NOTE: If you were using ninja and using SetOption() for ninja options + in your SConscripts prior to loading the ninja tool, you will now + see an error. The fix is to move the SetOption() to after you've loaded + the ninja tool. - ListVariable now has a separate validator, with the functionality that was previously part of the converter. The main effect is to allow a developer to supply a custom validator, which previously From 6253094147a4044b0686c595eed465c3e7e9c5dd Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 24 Jun 2024 09:36:28 -0600 Subject: [PATCH 22/23] Adjust AppVeyor build The VS2017 x Python 3.6 combo is failing one test using xsltproc. We don't know why, but try moving the Python vesion in case it's involved (perhaps something in urllib and/or the certs/ssl that go with 3.6, since it seems to be failing to fetch a file). Signed-off-by: Mats Wichmann --- .appveyor.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index e7596da4ae..1674bade2a 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -8,7 +8,7 @@ image: # linux builds done in Travis CI for now - Visual Studio 2017 - Visual Studio 2019 - #- Visual Studio 2022 XXX Temporary disable while failing on .10 versions + #- Visual Studio 2022 # Temporary disable while failing on 14.40 build tools cache: - downloads -> appveyor.yml @@ -37,38 +37,38 @@ environment: # Test oldest and newest supported Pythons, and a subset in between. # Skipping 3.7 and 3.9 at this time - WINPYTHON: "Python312" - - WINPYTHON: "Python310" - - WINPYTHON: "Python38" - - WINPYTHON: "Python36" # remove sets of build jobs based on criteria below # to fine tune the number and platforms tested matrix: exclude: - # test python 3.6 on Visual Studio 2017 image + # XXX test python 3.6 on Visual Studio 2017 image + # test python 3.8 on Visual Studio 2017 image - image: Visual Studio 2017 WINPYTHON: "Python312" - image: Visual Studio 2017 WINPYTHON: "Python310" - image: Visual Studio 2017 - WINPYTHON: "Python38" + WINPYTHON: "Python36" - # test python 3.8 on Visual Studio 2019 image + # test python 3.10 on Visual Studio 2019 image - image: Visual Studio 2019 WINPYTHON: "Python312" - image: Visual Studio 2019 - WINPYTHON: "Python310" + WINPYTHON: "Python38" - image: Visual Studio 2019 WINPYTHON: "Python36" - # test python 3.10 and 3.11 on Visual Studio 2022 image + # test python 3.12 on Visual Studio 2022 image - image: Visual Studio 2022 - WINPYTHON: "Python36" + WINPYTHON: "Python310" - image: Visual Studio 2022 WINPYTHON: "Python38" + - image: Visual Studio 2022 + WINPYTHON: "Python36" # Remove some binaries we don't want to be found # Note this is no longer needed, git-windows bin/ is quite minimal now. From d3419391452be551dc87fc0b29218b05bc529a9e Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Fri, 10 May 2024 11:55:48 -0600 Subject: [PATCH 23/23] Standardize license header on in-use doc files [skip appveyor] There are no code changes. Signed-off-by: Mats Wichmann --- CHANGES.txt | 2 ++ RELEASE.txt | 1 + SCons/Action.xml | 5 +-- SCons/Defaults.xml | 5 +-- SCons/Environment.xml | 5 +-- SCons/Platform/Platform.xml | 5 +-- SCons/Platform/posix.xml | 5 +-- SCons/Platform/sunos.xml | 5 +-- SCons/Platform/win32.xml | 5 +-- SCons/Scanner/Scanner.xml | 5 +-- SCons/Script/Main.xml | 5 +-- SCons/Script/SConscript.xml | 5 +-- SCons/Subst.xml | 5 +-- SCons/Tool/386asm.xml | 5 +-- SCons/Tool/DCommon.xml | 4 ++- SCons/Tool/Tool.xml | 5 +-- SCons/Tool/aixc++.xml | 5 +-- SCons/Tool/aixcc.xml | 5 +-- SCons/Tool/aixf77.xml | 5 +-- SCons/Tool/aixlink.xml | 5 +-- SCons/Tool/applelink.xml | 5 +-- SCons/Tool/ar.xml | 5 +-- SCons/Tool/as.xml | 5 +-- SCons/Tool/bcc32.xml | 5 +-- SCons/Tool/c++.xml | 5 +-- SCons/Tool/cc.xml | 5 +-- SCons/Tool/clang.xml | 27 ++-------------- SCons/Tool/clangxx.xml | 5 +-- SCons/Tool/compilation_db.xml | 6 ++-- SCons/Tool/cvf.xml | 5 +-- SCons/Tool/cyglink.xml | 5 +-- SCons/Tool/default.xml | 4 ++- SCons/Tool/dmd.xml | 5 +-- SCons/Tool/docbook/docbook.xml | 9 +++--- SCons/Tool/dvi.xml | 5 +-- SCons/Tool/dvipdf.xml | 5 +-- SCons/Tool/dvips.xml | 5 +-- SCons/Tool/f03.xml | 5 +-- SCons/Tool/f08.xml | 5 +-- SCons/Tool/f77.xml | 5 +-- SCons/Tool/f90.xml | 5 +-- SCons/Tool/f95.xml | 5 +-- SCons/Tool/fortran.xml | 5 +-- SCons/Tool/g++.xml | 5 +-- SCons/Tool/g77.xml | 5 +-- SCons/Tool/gas.xml | 5 +-- SCons/Tool/gdc.xml | 5 +-- SCons/Tool/gettext.xml | 17 +++++----- SCons/Tool/gfortran.xml | 5 +-- SCons/Tool/gnulink.xml | 5 +-- SCons/Tool/gs.xml | 5 +-- SCons/Tool/hpc++.xml | 5 +-- SCons/Tool/hpcc.xml | 5 +-- SCons/Tool/hplink.xml | 5 +-- SCons/Tool/icc.xml | 5 +-- SCons/Tool/icl.xml | 5 +-- SCons/Tool/ifl.xml | 5 +-- SCons/Tool/ifort.xml | 5 +-- SCons/Tool/ilink.xml | 5 +-- SCons/Tool/ilink32.xml | 5 +-- SCons/Tool/install.xml | 5 +-- SCons/Tool/intelc.xml | 5 +-- SCons/Tool/jar.xml | 5 ++- SCons/Tool/javac.xml | 9 +++--- SCons/Tool/javah.xml | 5 ++- SCons/Tool/latex.xml | 5 +-- SCons/Tool/ldc.xml | 5 +-- SCons/Tool/lex.xml | 27 ++-------------- SCons/Tool/link.xml | 11 ++++--- SCons/Tool/linkloc.xml | 5 +-- SCons/Tool/m4.xml | 5 +-- SCons/Tool/masm.xml | 5 +-- SCons/Tool/midl.xml | 5 +-- SCons/Tool/mingw.xml | 5 +-- SCons/Tool/msgfmt.xml | 9 +++--- SCons/Tool/msginit.xml | 13 ++++---- SCons/Tool/msgmerge.xml | 13 ++++---- SCons/Tool/mslib.xml | 5 +-- SCons/Tool/mslink.xml | 5 +-- SCons/Tool/mssdk.xml | 7 ++-- SCons/Tool/msvc.xml | 2 +- SCons/Tool/msvs.xml | 4 +-- SCons/Tool/mwcc.xml | 5 +-- SCons/Tool/mwld.xml | 5 +-- SCons/Tool/nasm.xml | 5 +-- SCons/Tool/ninja/ninja.xml | 31 +++--------------- SCons/Tool/packaging/packaging.xml | 7 ++-- SCons/Tool/pdf.xml | 5 +-- SCons/Tool/pdflatex.xml | 5 +-- SCons/Tool/pdftex.xml | 5 +-- SCons/Tool/python.xml | 5 +-- SCons/Tool/qt.xml | 5 +-- SCons/Tool/qt3.xml | 5 +-- SCons/Tool/rmic.xml | 5 ++- SCons/Tool/rpcgen.xml | 5 +-- SCons/Tool/sgiar.xml | 5 +-- SCons/Tool/sgic++.xml | 5 +-- SCons/Tool/sgicc.xml | 5 +-- SCons/Tool/sgilink.xml | 5 +-- SCons/Tool/sunar.xml | 5 +-- SCons/Tool/sunc++.xml | 5 +-- SCons/Tool/suncc.xml | 5 +-- SCons/Tool/sunf77.xml | 5 +-- SCons/Tool/sunf90.xml | 5 +-- SCons/Tool/sunf95.xml | 5 +-- SCons/Tool/sunlink.xml | 5 +-- SCons/Tool/swig.xml | 5 +-- SCons/Tool/tar.xml | 5 +-- SCons/Tool/tex.xml | 5 +-- SCons/Tool/textfile.xml | 5 +-- SCons/Tool/tlib.xml | 5 +-- SCons/Tool/xgettext.xml | 25 ++++++++------- SCons/Tool/yacc.xml | 27 ++-------------- SCons/Tool/zip.xml | 5 +-- bin/SConsDoc.py | 51 ++++++++++-------------------- doc/man/scons.xml | 27 +++------------- doc/scons.mod | 41 ++++++++---------------- doc/user/actions.xml | 3 ++ doc/user/add-method.xml | 3 ++ doc/user/alias.xml | 3 ++ doc/user/ant.xml | 3 ++ doc/user/build-install.xml | 3 ++ doc/user/builders-built-in.xml | 3 ++ doc/user/builders-commands.xml | 3 ++ doc/user/builders-writing.xml | 3 ++ doc/user/builders.xml | 3 ++ doc/user/caching.xml | 3 ++ doc/user/command-line.xml | 3 ++ doc/user/depends.xml | 3 ++ doc/user/environments.xml | 3 ++ doc/user/errors.xml | 3 ++ doc/user/example.xml | 3 ++ doc/user/external.xml | 3 ++ doc/user/factories.xml | 3 ++ doc/user/file-removal.xml | 3 ++ doc/user/functions.xml | 3 ++ doc/user/gettext.xml | 3 ++ doc/user/hierarchy.xml | 3 ++ doc/user/install.xml | 3 ++ doc/user/java.xml | 3 ++ doc/user/less-simple.xml | 3 ++ doc/user/libraries.xml | 3 ++ doc/user/main.xml | 3 ++ doc/user/make.xml | 3 ++ doc/user/mergeflags.xml | 3 ++ doc/user/misc.xml | 3 ++ doc/user/nodes.xml | 3 ++ doc/user/output.xml | 3 ++ doc/user/parse_flags_arg.xml | 3 ++ doc/user/parseconfig.xml | 3 ++ doc/user/parseflags.xml | 3 ++ doc/user/preface.xml | 3 ++ doc/user/python.xml | 3 ++ doc/user/repositories.xml | 3 ++ doc/user/run.xml | 3 ++ doc/user/scanners.xml | 3 ++ doc/user/sconf.xml | 3 ++ doc/user/separate.xml | 3 ++ doc/user/sideeffect.xml | 3 ++ doc/user/simple.xml | 3 ++ doc/user/tasks.xml | 3 ++ doc/user/tools.xml | 3 ++ doc/user/troubleshoot.xml | 3 ++ doc/user/variables.xml | 3 ++ doc/user/variants.xml | 4 ++- 165 files changed, 547 insertions(+), 437 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e2c11c5f62..012a5969f7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -111,6 +111,8 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER allow a developer to supply a custom validator, which previously could be inhibited by the converter failing before the validator is reached. + - Regularized header (copyright, licens) at top of documentation files + using SPDX. RELEASE 4.7.0 - Sun, 17 Mar 2024 17:22:20 -0700 diff --git a/RELEASE.txt b/RELEASE.txt index d6fa72926a..389a1f6f6d 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -99,6 +99,7 @@ DOCUMENTATION before contents of package submodules. - Updated manpage description of Command "builder" and function. - Updated the notes about reproducible builds with SCons and the example. +- Regularized header (copyright, licens) at top of documentation files using SPDX. diff --git a/SCons/Action.xml b/SCons/Action.xml index becd30f792..c71c305eab 100644 --- a/SCons/Action.xml +++ b/SCons/Action.xml @@ -1,9 +1,10 @@ -This tool tries to make working with Docbook in SCons a little easier. +This tool tries to make working with Docbook in &SCons; a little easier. It provides several toolchains for creating different output formats, like HTML or PDF. Contained in the package is a distribution of the Docbook XSL stylesheets as of version 1.76.1. diff --git a/SCons/Tool/dvi.xml b/SCons/Tool/dvi.xml index ee67e1474e..a631c2abad 100644 --- a/SCons/Tool/dvi.xml +++ b/SCons/Tool/dvi.xml @@ -1,9 +1,10 @@ - &t-link-xgettext; - to extract internationalized messages from source code to + &t-link-xgettext; - to extract internationalized messages from source code to POT file(s), @@ -59,7 +60,7 @@ so you're encouraged to see their individual documentation. Each of the above tools provides its own builder(s) which may be used to perform particular activities related to software internationalization. You -may be however interested in top-level +may be however interested in top-level &b-link-Translate; builder. @@ -100,7 +101,7 @@ a SCons script when invoking &b-Translate; # SConscript in 'po/' directory env = Environment( tools = ["default", "gettext"] ) env['POAUTOINIT'] = 1 -env.Translate(['en','pl'], ['../a.cpp','../b.cpp']) +env.Translate(['en','pl'], ['../a.cpp','../b.cpp']) @@ -111,7 +112,7 @@ If you wish, you may also stick to conventional style known from # LINGUAS -en pl +en pl #end @@ -127,7 +128,7 @@ b.cpp env = Environment( tools = ["default", "gettext"] ) env['POAUTOINIT'] = 1 env['XGETTEXTPATH'] = ['../'] -env.Translate(LINGUAS_FILE = 1, XGETTEXTFROM = 'POTFILES.in') +env.Translate(LINGUAS_FILE = 1, XGETTEXTFROM = 'POTFILES.in') @@ -154,7 +155,7 @@ Let's prepare a development tree as below project/ + SConstruct - + build/ + + build/ + src/ + po/ + SConscript diff --git a/SCons/Tool/gfortran.xml b/SCons/Tool/gfortran.xml index 95458fc46a..fc18327820 100644 --- a/SCons/Tool/gfortran.xml +++ b/SCons/Tool/gfortran.xml @@ -1,9 +1,10 @@ : for POSIX systems or - ; for Windows), + ; for Windows), it will be split on the separator into a list of individual paths for dependency scanning purposes. It will not be modified for JDK command-line usage, @@ -278,7 +277,7 @@ env = Environment(JAVACCOMSTR="Compiling class files $TARGETS from $SOURCES") &SCons; always - supplies a + supplies a when invoking the Java compiler &javac;, regardless of the setting of &cv-link-JAVASOURCEPATH;, as it passes the path(s) to the source(s) supplied diff --git a/SCons/Tool/javah.xml b/SCons/Tool/javah.xml index 16e969a5f6..7021753c71 100644 --- a/SCons/Tool/javah.xml +++ b/SCons/Tool/javah.xml @@ -1,11 +1,10 @@ '.dll'. -Used to override &cv-link-SHLIBNOVERSIONSYMLINKS;/&cv-link-LDMODULENOVERSIONSYMLINKS; when +Used to override &cv-link-SHLIBNOVERSIONSYMLINKS;/&cv-link-LDMODULENOVERSIONSYMLINKS; when creating versioned import library for a shared library/loadable module. If not defined, then &cv-link-SHLIBNOVERSIONSYMLINKS;/&cv-link-LDMODULENOVERSIONSYMLINKS; is used to determine whether to disable symlink generation or not. @@ -410,7 +411,7 @@ The variable is used, for example, by &t-link-gnulink; linker tool. -This will construct the SONAME using on the base library name +This will construct the SONAME using on the base library name (test in the example below) and use specified SOVERSION to create SONAME. @@ -419,7 +420,7 @@ env.SharedLibrary('test', 'test.c', SHLIBVERSION='0.1.2', SOVERSION='2') The variable is used, for example, by &t-link-gnulink; linker tool. -In the example above SONAME would be libtest.so.2 +In the example above SONAME would be libtest.so.2 which would be a symlink and point to libtest.so.0.1.2 diff --git a/SCons/Tool/linkloc.xml b/SCons/Tool/linkloc.xml index 2b5ba6a5d1..86d80d73cd 100644 --- a/SCons/Tool/linkloc.xml +++ b/SCons/Tool/linkloc.xml @@ -1,9 +1,10 @@ LINGUAS file: Example 4. -Compile files for languages defined in LINGUAS file +Compile files for languages defined in LINGUAS file (another version): @@ -126,7 +127,7 @@ See &t-link-msgfmt; tool and &b-link-MOFiles; builder. -String to display when msgfmt(1) is invoked +String to display when msgfmt(1) is invoked (default: '', which means ``print &cv-link-MSGFMTCOM;''). See &t-link-msgfmt; tool and &b-link-MOFiles; builder. diff --git a/SCons/Tool/msginit.xml b/SCons/Tool/msginit.xml index 70178518b6..667ed54b61 100644 --- a/SCons/Tool/msginit.xml +++ b/SCons/Tool/msginit.xml @@ -1,9 +1,10 @@ en.po and pl.po from # ... - env.POInit(['en', 'pl']) # messages.pot --> [en.po, pl.po] + env.POInit(['en', 'pl']) # messages.pot --> [en.po, pl.po] @@ -92,7 +93,7 @@ Initialize en.po and pl.po from # ... - env.POInit(['en', 'pl'], ['foo']) # foo.pot --> [en.po, pl.po] + env.POInit(['en', 'pl'], ['foo']) # foo.pot --> [en.po, pl.po] @@ -103,7 +104,7 @@ variable: # ... - env.POInit(['en', 'pl'], POTDOMAIN='foo') # foo.pot --> [en.po, pl.po] + env.POInit(['en', 'pl'], POTDOMAIN='foo') # foo.pot --> [en.po, pl.po] @@ -193,7 +194,7 @@ See &t-link-msginit; tool and &b-link-POInit; builder. -String to display when msginit(1) is invoked +String to display when msginit(1) is invoked (default: '', which means ``print &cv-link-MSGINITCOM;''). See &t-link-msginit; tool and &b-link-POInit; builder. diff --git a/SCons/Tool/msgmerge.xml b/SCons/Tool/msgmerge.xml index 266ccc7049..1f0437cf0f 100644 --- a/SCons/Tool/msgmerge.xml +++ b/SCons/Tool/msgmerge.xml @@ -1,9 +1,10 @@ This scons tool is a part of scons &t-link-gettext; toolset. It provides scons interface to msgmerge(1) command, which merges two -Uniform style .po files together. +Uniform style .po files together. @@ -62,9 +63,9 @@ does. Target nodes defined through &b-POUpdate; are not built by default (they're Ignored from '.' node). Instead, -they are added automatically to special Alias +they are added automatically to special Alias ('po-update' by default). The alias name may be changed -through the &cv-link-POUPDATE_ALIAS; construction variable. You can easily +through the &cv-link-POUPDATE_ALIAS; construction variable. You can easily update PO files in your project by scons po-update. @@ -128,7 +129,7 @@ from messages.pot template: # produce 'en.po', 'pl.po' + files defined in 'LINGUAS': - env.POUpdate(['en', 'pl' ], LINGUAS_FILE = 1) + env.POUpdate(['en', 'pl' ], LINGUAS_FILE = 1) diff --git a/SCons/Tool/mslib.xml b/SCons/Tool/mslib.xml index a79ec4869e..fdd9ea77c9 100644 --- a/SCons/Tool/mslib.xml +++ b/SCons/Tool/mslib.xml @@ -1,9 +1,10 @@ 6.0A, 6.0, 2003R2 -and +and 2003R1. diff --git a/SCons/Tool/msvc.xml b/SCons/Tool/msvc.xml index a0f657368c..9f433a2453 100644 --- a/SCons/Tool/msvc.xml +++ b/SCons/Tool/msvc.xml @@ -1,7 +1,7 @@ Determines the type of format ninja should expect when parsing header include depfiles. Can be , , or . - The option corresponds to format, and + The option corresponds to format, and or correspond to . diff --git a/SCons/Tool/packaging/packaging.xml b/SCons/Tool/packaging/packaging.xml index b248880e33..62ba558a5d 100644 --- a/SCons/Tool/packaging/packaging.xml +++ b/SCons/Tool/packaging/packaging.xml @@ -1,9 +1,10 @@ xgettext(1) program, which extracts internationalized messages from source code. The tool provides &b-POTUpdate; builder to make PO -Template files. +Template files. @@ -67,7 +68,7 @@ special alias (pot-update by default, see &cv-link-POTUPDATE_ALIAS;) so you can update/create them easily with scons pot-update. The file is not written until there is no real change in internationalized messages (or in comments that enter -POT file). +POT file). @@ -90,7 +91,7 @@ Let's create po/ directory and place following env = Environment( tools = ['default', 'xgettext'] ) env.POTUpdate(['foo'], ['../a.cpp', '../b.cpp']) env.POTUpdate(['bar'], ['../c.cpp', '../d.cpp']) - + Then invoke scons few times: @@ -111,7 +112,7 @@ case default target messages.pot will be used. The default target may also be overridden by setting &cv-link-POTDOMAIN; construction variable or providing it as an override to &b-POTUpdate; builder: - + # SConstruct script env = Environment( tools = ['default', 'xgettext'] ) env['POTDOMAIN'] = "foo" @@ -124,21 +125,21 @@ variable or providing it as an override to &b-POTUpdate; builder: The sources may be specified within separate file, for example POTFILES.in: - + # POTFILES.in in 'po/' subdirectory ../a.cpp ../b.cpp # end of file - + The name of the file (POTFILES.in) containing the list of sources is provided via &cv-link-XGETTEXTFROM;: - + # SConstruct file in 'po/' subdirectory env = Environment( tools = ['default', 'xgettext'] ) env.POTUpdate(XGETTEXTFROM = 'POTFILES.in') - + Example 4. @@ -209,10 +210,10 @@ message "Hello from ../a.cpp". When you reverse order in # SConstruct file in '0/1/po/' subdirectory env = Environment( tools = ['default', 'xgettext'] ) env.POTUpdate(XGETTEXTFROM = 'POTFILES.in', XGETTEXTPATH=['../../', '../']) - + then the messages.pot will contain -msgid "Hello from ../../a.cpp" line and not +msgid "Hello from ../../a.cpp" line and not msgid "Hello from ../a.cpp". diff --git a/SCons/Tool/yacc.xml b/SCons/Tool/yacc.xml index 729c408286..89f45ba4e4 100644 --- a/SCons/Tool/yacc.xml +++ b/SCons/Tool/yacc.xml @@ -1,31 +1,10 @@ + - + Action"> Builder"> Builders"> @@ -171,9 +154,11 @@ Add"> diff --git a/doc/user/actions.xml b/doc/user/actions.xml index d357eb6190..ed2eeb7df6 100644 --- a/doc/user/actions.xml +++ b/doc/user/actions.xml @@ -3,6 +3,9 @@