From f1a4abc20898b570cd84d6b64f731b3a3d771d49 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Sat, 7 Dec 2024 07:17:16 -0700 Subject: [PATCH 1/3] Tweak Variables docs Adjusts some doctrings and comments, and one error in typing. Manpage has the introdctory Variables material updated a bit, and the methods sorted, to match everywhere else in the manpage. Signed-off-by: Mats Wichmann --- CHANGES.txt | 1 + RELEASE.txt | 2 + SCons/Tool/install.xml | 2 +- SCons/Variables/EnumVariable.py | 11 +- SCons/Variables/ListVariable.py | 23 +-- SCons/Variables/__init__.py | 37 ++-- doc/man/scons.xml | 333 +++++++++++++++++++------------- 7 files changed, 244 insertions(+), 165 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f074da86e..01aac59a9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -174,6 +174,7 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - A build Variable is now a dataclass, with initialization moving to the automatically provided method; the Variables class no longer writes directly to a Variable (makes static checkers happier). + - Improved Variables documentation. - The (optional) C Conditional Scanner now does limited macro replacement on the contents of CPPDEFINES, to improve finding deps that are conditionally included. Previously replacement was only diff --git a/RELEASE.txt b/RELEASE.txt index b35ece265..b98c5e652 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -205,6 +205,8 @@ DOCUMENTATION - Update Clean and NoClean documentation. +- Improved Variables documentation. + DEVELOPMENT ----------- diff --git a/SCons/Tool/install.xml b/SCons/Tool/install.xml index cdb044b69..3cbb0f4f6 100644 --- a/SCons/Tool/install.xml +++ b/SCons/Tool/install.xml @@ -77,7 +77,7 @@ a "live" location in the system. -See also &FindInstalledFiles;. +See also &f-link-FindInstalledFiles;. For more thoughts on installation, see the User Guide (particularly the section on Command-Line Targets and the chapters on Installing Files and on Alias Targets). diff --git a/SCons/Variables/EnumVariable.py b/SCons/Variables/EnumVariable.py index f154a133b..a576af513 100644 --- a/SCons/Variables/EnumVariable.py +++ b/SCons/Variables/EnumVariable.py @@ -77,19 +77,20 @@ def EnumVariable( ) -> tuple[str, str, str, Callable, Callable]: """Return a tuple describing an enumaration SCons Variable. - The input parameters describe a variable with only predefined values - allowed. The value of *ignorecase* defines the behavior of the + An Enum Variable is an abstraction that allows choosing one + value from a provided list of possibilities (*allowed_values*). + The value of *ignorecase* defines the behavior of the validator and converter: if ``0``, the validator/converter are case-sensitive; if ``1``, the validator/converter are case-insensitive; if ``2``, the validator/converter are case-insensitive and the converted value will always be lower-case. Arguments: - key: variable name, passed directly through to the return tuple. - default: default values, passed directly through to the return tuple. + key: the name of the variable. + default: default value, passed directly through to the return tuple. help: descriptive part of the help text, will have the allowed values automatically appended. - allowed_values: list of the allowed values for this variable. + allowed_values: the values for the choice. map: optional dictionary which may be used for converting the input value into canonical values (e.g. for aliases). ignorecase: defines the behavior of the validator and converter. diff --git a/SCons/Variables/ListVariable.py b/SCons/Variables/ListVariable.py index 4ea7dc304..880496f70 100644 --- a/SCons/Variables/ListVariable.py +++ b/SCons/Variables/ListVariable.py @@ -185,23 +185,24 @@ def ListVariable( names: list[str], map: dict | None = None, validator: Callable | None = None, -) -> tuple[str, str, str, None, Callable]: +) -> tuple[str, str, str, Callable, Callable]: """Return a tuple describing a list variable. - The input parameters describe a list variable, where the values - can be one or more from *names* plus the special values ``all`` - and ``none``. + A List Variable is an abstraction that allows choosing one or more + values from a provided list of possibilities (*names). The special terms + ``all`` and ``none`` are also provided to help make the selection. Arguments: key: the name of the list variable. help: the basic help message. Will have text appended indicating - 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*. 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. + the allowed values (not including any extra names from *map*). + default: the default value(s) for the list variable. Can be given + as string (use commas to -separated multiple values), or as a list + of strings. ``all`` or ``none`` are allowed as *default*. + A must-specify ListVariable can be simulated by giving a value + that is not part of *names*, which will cause validation to fail + if the variable is not given in the input sources. + names: the values to choose from. 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 diff --git a/SCons/Variables/__init__.py b/SCons/Variables/__init__.py index 521380468..2d151e0c9 100644 --- a/SCons/Variables/__init__.py +++ b/SCons/Variables/__init__.py @@ -225,13 +225,17 @@ def AddVariables(self, *optlist) -> None: def Update(self, env, args: dict | None = None) -> None: """Update an environment with the Build Variables. - Collects variables from the input sources which do not match - a variable description in this object. These are ignored for - purposes of adding to *env*, but can be retrieved using the - :meth:`UnknownVariables` method. Also collects variables which - are set in *env* from the default in a variable description and - not from the input sources. These are available in the - :attr:`defaulted` attribute. + This is where the work of adding variables to the environment + happens, The input sources saved at init time are scanned for + variables to add, though if *args* is passed, then it is used + instead of the saved one. If any variable description set up + a callback for a validator and/or converter, those are called. + Variables from the input sources which do not match a variable + description in this object are ignored for purposes of adding + to *env*, but are saved in the :attr:`unknown` dict attribute. + Variables which are set in *env* from the default in a variable + description and not from the input sources are saved in the + :attr:`defaulted` list attribute. Args: env: the environment to update. @@ -242,7 +246,7 @@ def Update(self, env, args: dict | None = None) -> None: values = {opt.key: opt.default for opt in self.options if opt.default is not None} self.defaulted = list(values) - # next set the values specified in any options script(s) + # next set the values specified in any saved-variables script(s) for filename in self.files: # TODO: issue #816 use Node to access saved-variables file? if os.path.exists(filename): @@ -288,8 +292,16 @@ def Update(self, env, args: dict | None = None) -> None: if not added: self.unknown[arg] = value - # put the variables in the environment: + # put the variables in the environment # (don't copy over variables that are not declared as options) + # + # Nitpicking: in OO terms, this method increases coupling as its + # main work is to update a different object (env), rather than + # the object it's bound to (although it does update self, too). + # It's tricky to decouple because the algorithm counts on directly + # setting a var in *env* first so it can call env.subst() on it + # to transform it. + for option in self.options: try: env[option.key] = values[option.key] @@ -400,9 +412,10 @@ def GenerateHelpText(self, env, sort: bool | Callable = False) -> str: (must take two arguments and return ``-1``, ``0`` or ``1``) or a boolean to indicate if it should be sorted. """ - # TODO the 'sort' argument matched the old way Python's sorted() - # worked, taking a comparison function argument. That has been - # removed so now we have to convert to a key. + # TODO this interface was designed when Pythin sorted() took an + # optional comparison function (pre-3.0). Since it no longer does, + # we use functools.cmp_to_key() since can't really change the + # documented meaning of the "sort" argument. Maybe someday? if callable(sort): options = sorted(self.options, key=cmp_to_key(lambda x, y: sort(x.key, y.key))) elif sort is True: diff --git a/doc/man/scons.xml b/doc/man/scons.xml index 1b7e886c6..764134140 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -4832,20 +4832,22 @@ added by calling the Add or AddVariables methods. -Each variable description consists of a name (which will -be used as the &consvar; name), aliases for the name, +A variable description consists of a name, +a list of aliases for the name, a help message, a default value, and functions to validate and convert values. -Processing of input sources -is deferred until the +Processing of input sources is deferred until the Update method is called, at which time the variables are added to the -specified &consenv;. +specified &consenv;, +using the name as the &consvar; name; +any aliases are not added. Variables from the input sources which do not match any names or aliases from the variable descriptions in this object are skipped, -except that a dictionary of their names and values are made available -in the .unknown attribute of the &Variables; object. +except that a dictionary of their names and values are made available in the +unknown +attribute of the &Variables; object. This list can also be obtained via the UnknownVariables method. @@ -4854,19 +4856,15 @@ other than None and does not appear in the input sources, it is added to the &consenv; with its default value. A list of variables set from their defaults and -not supplied a value in the input sources is -available as the .defaulted attribute -of the &Variables; object. +not from the input sources is available as the +defaulted +attribute of the &Variables; object. The unknown variables and defaulted information is not available until the &Update; method has run. -New in NEXT_RELEASE: -the defaulted attribute. - - -Note that since the variables are eventually added as &consvars;, +Since the variables are eventually added as &consvars;, you should choose variable names which do not unintentionally change pre-defined &consvars; that your project will make use of (see for a reference), @@ -4875,16 +4873,16 @@ to the respective &consvars;. -Also note there is currently no way to use the &Variables; -mechanism to define a variable which the user is -required to supply; -if necessary this can be implemented by accessing -&ARGUMENTS; directly, -although that only applies to the command line, -not to any stored-values files. +The &Variables; subsystem does not directly support a way +to define a variable the user must supply, +but this can be simulated by using a validator function, +and specifying a default value which the validator will reject, +resulting in an invalid value error message +(the convenience methods &EnumVariable; and +&ListVariable; make this relatively straightforward). -A Variables object has the following methods: +A &Variables; object has the following methods: @@ -4897,17 +4895,24 @@ or a sequence of strings, in which case the first item in the sequence is taken as the variable name, and any remaining values are considered aliases for the variable. key is mandatory, -there is no default. +the other fields are optional. help is the help text for the variable (defaults to an empty string). default is the default value of the variable (defaults to None). +The variable will be set to the value of +default if it does +not appear in the input sources, +except if default +is None, +in which case it is not added to the &consenv; +unless it has been set in the input sources. -If the optional validator argument is supplied, +If the validator argument is supplied, it is a callback function to validate the value of the variable when the variables are processed (that is, when the &Update; @@ -4922,7 +4927,7 @@ No return value is expected from the validator. -If the optional converter argument is supplied, +If the converter argument is supplied, it is a callback function to convert the value into one suitable for adding to the &consenv;. A converter function must accept the @@ -4944,7 +4949,7 @@ it can raise a ValueError. Substitution will be performed on the variable value before the converter and validator are called, unless the optional subst parameter -is false (default True). +is false (the default is True). Suppressing substitution may be useful if the variable value looks like a &consvar; reference (e.g. $VAR) and the validator and/or converter should see it unexpanded. @@ -5018,60 +5023,97 @@ opt.AddVariables( - - vars.Update(env, [args]) + + vars.FormatVariableHelpText(env, opt, help, default, actual, aliases) -Process the input sources recorded -when the &Variables; object was initialized -and update +Returns a formatted string +containing the printable help text +for the single variable opt. +All of the arguments must be supplied +except aliases, which is optional. env -with the customized &consvars;. -The names of any variables in the input sources that are not -configured in the &Variables; object -are recorded and may be retrieved using the -&UnknownVariables; -method. +is the &consenv; containing the variable values, +(env is not used by the standard +implementation of FormatVariableHelpText); +var +is the name of the variable; +help +is the text of the initial help message when the variable was +added to the &Variables; object; +default +is the default value assigned when the variable was added +to the &Variables; object; +actual +is the value as assigned in env +(which may be the same as default, +if none of the input sources assign to the variable); +and aliases +are any alias names for the variable, +if omitted defaults to an empty list. + -If the optional -args -argument is provided, it is a dictionary of variables -to use in place of the one saved when -&Variables; -was called. +FormatVariableHelpText +is normally not called directy, but by +&GenerateHelpText;, which does the work of +obtaining the necessary values. +You can patch in your own +function that takes the same function signature +in order to customize the appearance of variable help messages. +Example: -Normally, &Update; is not called directly, -but rather invoked indirectly by passing the &Variables; object to -the &f-link-Environment; function: - -env = Environment(..., variables=vars) +def my_format(env, var, help, default, actual): + fmt = "\n%s: default=%s actual=%s (%s)\n" + return fmt % (var, default, actual, help) + +vars.FormatVariableHelpText = my_format + +Note that &GenerateHelpText; +will not put any blank lines or extra +characters between the entries, +so you must add those characters to the returned +string if you want the entries separated. + - - vars.UnknownVariables() + + vars.GenerateHelpText(env, [sort]) -Returns a dictionary containing any -variables that were specified in the -files and/or -args parameters -when &Variables; -was called, but which were not configured in the object. -The same dictionary is also available as the -unknown attribute of the object. -This information is not available until the -Update -method has run. + +Return a formatted string with the help text collected +from all the variables configured in this &Variables; object. +This string is suitable for passing in to the &f-link-Help; function. +The generated string include an indication of the +actual value in the environment given by env. + + + +If the optional +sort parameter is set to +a callable value, it is used as a comparison function to +determine how to sort the added variables. +This function must accept two arguments, compare them, +and return a negative integer if the first is +less-than the second, zero if equal, or a positive integer +if greater-than. +If sort is not callable, +but evaluates true, +an alphabetical sort is performed. +The default is False (unsorted). -env = Environment(variables=vars) -for key, value in vars.UnknownVariables(): - print("unknown variable: %s=%s" % (key, value)) +Help(vars.GenerateHelpText(env)) + +def cmp(a, b): + return (a > b) - (a < b) + +Help(vars.GenerateHelpText(env, sort=cmp)) @@ -5101,73 +5143,99 @@ vars.Save('variables.cache', env) - - vars.GenerateHelpText(env, [sort]) + + vars.UnknownVariables() - -Return a formatted string with the help text collected -from all the variables configured in this &Variables; object. -This string is suitable for passing in to the &f-link-Help; function. -The generated string include an indication of the -actual value in the environment given by env. +Returns a dictionary containing any +variables that were specified in the +files and/or +args parameters +when &Variables; +was called, but the object was not actually configured for. +This information is not available until the +Update +method has run. + +env = Environment(variables=vars) +for key, value in vars.UnknownVariables(): + print("unknown variable: %s=%s" % (key, value)) + + + + + + + vars.Update(env, [args]) + +Process the input sources recorded +when the &Variables; object was initialized +and update +env +with the customized &consvars;. +The names of any variables in the input sources that are not +configured in the &Variables; object +are recorded and may be retrieved using the +&UnknownVariables; +method. + If the optional -sort parameter is set to -a callable value, it is used as a comparison function to -determine how to sort the added variables. -This function must accept two arguments, compare them, -and return a negative integer if the first is -less-than the second, zero if equal, or a positive integer -if greater-than. -If sort is not callable, -but is set to True, -an alphabetical sort is performed. -The default is False (unsorted). +args +argument is provided, it must be a dictionary of variables, +which will be used in place of the one saved when the +&Variables; object +was created. - -Help(vars.GenerateHelpText(env)) - -def cmp(a, b): - return (a > b) - (a < b) +Normally, &Update; is not called directly, +but rather invoked indirectly by passing the &Variables; object to +the &f-link-Environment; function: -Help(vars.GenerateHelpText(env, sort=cmp)) + +env = Environment(..., variables=vars) + - - vars.FormatVariableHelpText(env, opt, help, default, actual) - -Returns a formatted string -containing the printable help text -for the single option opt. -It is normally not called directly, -but is called by the &GenerateHelpText; -method to create the returned help text. -It may be overridden with your own -function that takes the arguments specified above -and returns a string of help text formatted to your liking. -Note that &GenerateHelpText; -will not put any blank lines or extra -characters in between the entries, -so you must add those characters to the returned -string if you want the entries separated. + +A &Variables; object also makes available two data attributes +that can be read for further information. These only have +values if Update +has previously run. + - -def my_format(env, opt, help, default, actual): - fmt = "\n%s: default=%s actual=%s (%s)\n" - return fmt % (opt, default, actual, help) + + + vars.defaulted + + +A list of variable names that were set in the &consenv; +from the default values in the variable descriptions - +that is, variables that have a default value and were +not defined in the input sources. + + + -vars.FormatVariableHelpText = my_format - + + vars.unknown + + +A dictionary of variables that were specified in the input sources, +but do not have matching variable definitions. +This is the same information that is returned by the +&UnknownVariables; method. + - +Added in NEXT_RELEASE: +the defaulted attribute. + &SCons; provides five pre-defined variable types, @@ -5216,7 +5284,7 @@ as false. Set up a variable named key -whose value will be a choice from +whose value may only be chosen from a specified list ("enumeration") of values. The variable will have a default value of default @@ -5231,23 +5299,14 @@ argument is a dictionary that can be used to map additional names into a particular name in the allowed_values list. -If the value of optional -ignore_case -is -0 -(the default), -then the values are case-sensitive. -If the value of -ignore_case -is -1, -then values will be matched +If the optional +ignorecase is 0 (the default), +the values are considered case-sensitive. +If ignorecase is 1, +values will be matched case-insensitively. -If the value of -ignore_case -is -2, -then values will be matched +If ignorecase is 2, +values will be matched case-insensitively, and all input values will be converted to lower case. @@ -5259,8 +5318,8 @@ converted to lower case. Set up a variable named key -whose value will be one or more -choices from a specified list of values. +whose value may be one or more choices +from a specified list of values. The variable will have a default value of default, and help @@ -5293,8 +5352,9 @@ 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. - +Added in 4.8.0: +the validator parameter. + @@ -5466,7 +5526,8 @@ vars.AddVariables( PathVariable( "qtdir", help="where the root of Qt is installed", - default=qtdir), + default=qtdir + ), PathVariable( "foopath", help="where the foo library is installed", From 406fb6e1cfb5e5684fe3311c7f5393e4e8531bf4 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Sun, 15 Dec 2024 09:54:23 -0700 Subject: [PATCH 2/3] Change update-release-info test for Python changes Signed-off-by: Mats Wichmann --- CHANGES.txt | 2 ++ test/update-release-info/update-release-info.py | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f074da86e..f7f20896c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -181,6 +181,8 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER Only object-like macros are replaced (not function-like), and only on a whole-word basis; recursion is limited to five levels and does not error out if that limit is reached (issue #4523). + - The update-release-info test is adapted to accept changed help output + introduced in Python 3.12.8/3.13.1. RELEASE 4.8.1 - Tue, 03 Sep 2024 17:22:20 -0700 diff --git a/test/update-release-info/update-release-info.py b/test/update-release-info/update-release-info.py index 2de4713b0..bebd8a987 100644 --- a/test/update-release-info/update-release-info.py +++ b/test/update-release-info/update-release-info.py @@ -53,11 +53,24 @@ if not os.path.exists(test.program): test.skip_test("update-release-info.py is not distributed in this package\n") -expected_stderr = """usage: update-release-info.py [-h] [--verbose] [--timestamp TIMESTAMP] +expected_stderr = """\ +usage: update-release-info.py [-h] [--verbose] [--timestamp TIMESTAMP] [{develop,release,post}] update-release-info.py: error: argument mode: invalid choice: 'bad' (choose from 'develop', 'release', 'post') """ -test.run(arguments='bad', stderr=expected_stderr, status=2) +# The way the choices are rendered in help by argparse changed with +# Python 3.12.8, # 3.13.1, 3.14.0a2. Change the test to accept either. +expected_stderr_new = """\ +usage: update-release-info.py [-h] [--verbose] [--timestamp TIMESTAMP] + [{develop,release,post}] +update-release-info.py: error: argument mode: invalid choice: 'bad' (choose from develop, release, post) +""" +test.run(arguments='bad', stderr=None, status=2) +fail_strings = [ + expected_stderr, + expected_stderr_new, +] +test.must_contain_any_line(test.stderr(), fail_strings) # Strings to go in ReleaseConfig combo_strings = [ From fe00f8bf24b0c5b879eab5c9c4e29e7f7cb8440d Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 15 Dec 2024 15:41:51 -0800 Subject: [PATCH 3/3] [ci skip] Fix typos --- SCons/Variables/__init__.py | 2 +- doc/man/scons.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SCons/Variables/__init__.py b/SCons/Variables/__init__.py index 2d151e0c9..2ac95d067 100644 --- a/SCons/Variables/__init__.py +++ b/SCons/Variables/__init__.py @@ -412,7 +412,7 @@ def GenerateHelpText(self, env, sort: bool | Callable = False) -> str: (must take two arguments and return ``-1``, ``0`` or ``1``) or a boolean to indicate if it should be sorted. """ - # TODO this interface was designed when Pythin sorted() took an + # TODO this interface was designed when Python's sorted() took an # optional comparison function (pre-3.0). Since it no longer does, # we use functools.cmp_to_key() since can't really change the # documented meaning of the "sort" argument. Maybe someday? diff --git a/doc/man/scons.xml b/doc/man/scons.xml index 764134140..2d5241498 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -5054,7 +5054,7 @@ if omitted defaults to an empty list. FormatVariableHelpText -is normally not called directy, but by +is normally not called directly, but by &GenerateHelpText;, which does the work of obtaining the necessary values. You can patch in your own