Skip to content

Commit

Permalink
Add Variables.defualted attribute
Browse files Browse the repository at this point in the history
Also document the Variables.unknown attribute as being the
same thing as the ruturn from the UnknownVariables method.

Signed-off-by: Mats Wichmann <[email protected]>
  • Loading branch information
mwichmann committed Nov 27, 2024
1 parent f1723d8 commit eeb025f
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 75 deletions.
8 changes: 7 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,13 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
variable names are given.
- Update Clean and NoClean documentation.
- Make sure unknown variables from a Variables file are recognized
as such (issue #4645)
as such. Previously only unknowns from the command line were
recognized (issue #4645).
- A Variables object now makes available a "defaulted" attribute,
a list of variable names that were set in the environment with
their values taken from the default in the variable description
(if a variable was set to the same value as the default in one
of the input sources, it is not included in this list).


RELEASE 4.8.1 - Tue, 03 Sep 2024 17:22:20 -0700
Expand Down
6 changes: 6 additions & 0 deletions RELEASE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY
always returns a dict. The default remains to return different
types depending on whether zero, one, or multiple construction

- A Variables object now makes available a "defaulted" attribute,
a list of variable names that were set in the environment with
their values taken from the default in the variable description
(if a variable was set to the same value as the default in one
of the input sources, it is not included in this list).

FIXES
-----

Expand Down
9 changes: 7 additions & 2 deletions SCons/Variables/VariablesTests.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,24 +659,28 @@ def test_AddOptionUpdatesUnknown(self) -> None:
Get one unknown from args and one from a variables file.
Add these later, making sure they no longer appear in unknowns
after the subsequent Update().
While we're here, test the *defaulted* attribute.
"""
test = TestSCons.TestSCons()
var_file = test.workpath('vars.py')
test.write('vars.py', 'FROMFILE="added"')
opts = SCons.Variables.Variables(files=var_file)
opts.Add('A', 'A test variable', "1")
opts.Add('A', 'A test variable', default="1")
opts.Add('B', 'Test variable B', default="1")
args = {
'A' : 'a',
'ADDEDLATER' : 'notaddedyet',
}
env = Environment()
opts.Update(env,args)
opts.Update(env, args)

r = opts.UnknownVariables()
with self.subTest():
self.assertEqual('notaddedyet', r['ADDEDLATER'])
self.assertEqual('added', r['FROMFILE'])
self.assertEqual('a', env['A'])
self.assertEqual(['B'], opts.defaulted)

opts.Add('ADDEDLATER', 'An option not present initially', "1")
opts.Add('FROMFILE', 'An option from a file also absent', "1")
Expand All @@ -693,6 +697,7 @@ def test_AddOptionUpdatesUnknown(self) -> None:
self.assertEqual('added', env['ADDEDLATER'])
self.assertNotIn('FROMFILE', r)
self.assertEqual('added', env['FROMFILE'])
self.assertEqual(['B'], opts.defaulted)

def test_AddOptionWithAliasUpdatesUnknown(self) -> None:
"""Test updating of the 'unknown' dict (with aliases)"""
Expand Down
67 changes: 48 additions & 19 deletions SCons/Variables/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import os.path
import sys
from contextlib import suppress
from functools import cmp_to_key
from typing import Callable, Sequence

Expand Down Expand Up @@ -58,22 +59,30 @@ class Variable:
__slots__ = ('key', 'aliases', 'help', 'default', 'validator', 'converter', 'do_subst')

def __lt__(self, other):
"""Comparison fuction so Variable instances sort."""
"""Comparison fuction so :class:`Variable` instances sort."""
return self.key < other.key

def __str__(self) -> str:
"""Provide a way to "print" a Variable object."""
"""Provide a way to "print" a :class:`Variable` object."""
return (
f"({self.key!r}, {self.aliases}, {self.help!r}, {self.default!r}, "
f"({self.key!r}, {self.aliases}, "
f"help={self.help!r}, default={self.default!r}, "
f"validator={self.validator}, converter={self.converter})"
)


class Variables:
"""A container for multiple Build Variables.
"""A container for Build Variables.
Includes methods to updates the environment with the variables,
and to render the help text.
Includes a method to populate the variables with values into a
construction envirionment, and methods to render the help text.
Note that the pubic API for creating a ``Variables`` object is
:func:`SCons.Script.Variables`, a kind of factory function, which
defaults to supplying the contents of :attr:`~SCons.Script.ARGUMENTS`
as the *args* parameter if it was not otherwise given. That is the
behavior documented in the manpage for ``Variables`` - and different
from the default if you instantiate this directly.
Arguments:
files: string or list of strings naming variable config scripts
Expand All @@ -83,11 +92,15 @@ class Variables:
instead of a fresh instance. Currently inoperable (default ``False``)
.. versionchanged:: 4.8.0
The default for *is_global* changed to ``False`` (previously
``True`` but it had no effect due to an implementation error).
The default for *is_global* changed to ``False`` (the previous
default ``True`` had no effect due to an implementation error).
.. deprecated:: 4.8.0
*is_global* is deprecated.
.. versionadded:: NEXT_RELEASE
The :attr:`defaulted` attribute now lists those variables which
were filled in from default values.
"""

def __init__(
Expand All @@ -102,15 +115,18 @@ def __init__(
files = [files] if files else []
self.files: Sequence[str] = files
self.unknown: dict[str, str] = {}
self.defaulted: list[str] = []

def __str__(self) -> str:
"""Provide a way to "print" a Variables object."""
s = "Variables(\n options=[\n"
for option in self.options:
s += f" {str(option)},\n"
s += " ],\n"
s += f" args={self.args},\n files={self.files},\n unknown={self.unknown},\n)"
return s
"""Provide a way to "print" a :class:`Variables` object."""
opts = ',\n'.join((f" {option!s}" for option in self.options))
return (
f"Variables(\n options=[\n{opts}\n ],\n"
f" args={self.args},\n"
f" files={self.files},\n"
f" unknown={self.unknown},\n"
f" defaulted={self.defaulted},\n)"
)

# lint: W0622: Redefining built-in 'help'
def _do_add(
Expand All @@ -122,7 +138,7 @@ def _do_add(
converter: Callable | None = None,
**kwargs,
) -> None:
"""Create a Variable and add it to the list.
"""Create a :class:`Variable` and add it to the list.
This is the internal implementation for :meth:`Add` and
:meth:`AddVariables`. Not part of the public API.
Expand Down Expand Up @@ -203,9 +219,9 @@ def Add(
return self._do_add(key, *args, **kwargs)

def AddVariables(self, *optlist) -> None:
"""Add a list of Build Variables.
"""Add Build Variables.
Each list element is a tuple/list of arguments to be passed on
Each *optlist* element is a sequence of arguments to be passed on
to the underlying method for adding variables.
Example::
Expand All @@ -223,13 +239,22 @@ def AddVariables(self, *optlist) -> None:
def Update(self, env, args: dict | None = None) -> None:
"""Update an environment with the Build Variables.
Collects variables from the input sources which do not match
a variable description in this object. These are ignored for
purposes of adding to *env*, but can be retrieved using the
:meth:`UnknownVariables` method. Also collects variables which
are set in *env* from the default in a variable description and
not from the input sources. These are available in the
:attr:`defaulted` attribute.
Args:
env: the environment to update.
args: a dictionary of keys and values to update in *env*.
If omitted, uses the saved :attr:`args`
"""
# first pull in the defaults
# first pull in the defaults, except any which are None.
values = {opt.key: opt.default for opt in self.options if opt.default is not None}
self.defaulted = list(values)

# next set the values specified in any options script(s)
for filename in self.files:
Expand All @@ -256,6 +281,8 @@ def Update(self, env, args: dict | None = None) -> None:
for option in self.options:
if arg in option.aliases + [option.key,]:
values[option.key] = value
with suppress(ValueError):
self.defaulted.remove(option.key)
added = True
if not added:
self.unknown[arg] = value
Expand All @@ -269,6 +296,8 @@ def Update(self, env, args: dict | None = None) -> None:
for option in self.options:
if arg in option.aliases + [option.key,]:
values[option.key] = value
with suppress(ValueError):
self.defaulted.remove(option.key)
added = True
if not added:
self.unknown[arg] = value
Expand Down
Loading

0 comments on commit eeb025f

Please sign in to comment.