Skip to content

Commit 01f77b1

Browse files
authored
Merge pull request #4657 from mwichmann/feature/vars-defaulted
Add `Variables.defaulted` attribute
2 parents f1723d8 + eeb025f commit 01f77b1

File tree

5 files changed

+146
-75
lines changed

5 files changed

+146
-75
lines changed

CHANGES.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,13 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
160160
variable names are given.
161161
- Update Clean and NoClean documentation.
162162
- Make sure unknown variables from a Variables file are recognized
163-
as such (issue #4645)
163+
as such. Previously only unknowns from the command line were
164+
recognized (issue #4645).
165+
- A Variables object now makes available a "defaulted" attribute,
166+
a list of variable names that were set in the environment with
167+
their values taken from the default in the variable description
168+
(if a variable was set to the same value as the default in one
169+
of the input sources, it is not included in this list).
164170

165171

166172
RELEASE 4.8.1 - Tue, 03 Sep 2024 17:22:20 -0700

RELEASE.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY
7676
always returns a dict. The default remains to return different
7777
types depending on whether zero, one, or multiple construction
7878

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

SCons/Variables/VariablesTests.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -659,24 +659,28 @@ def test_AddOptionUpdatesUnknown(self) -> None:
659659
Get one unknown from args and one from a variables file.
660660
Add these later, making sure they no longer appear in unknowns
661661
after the subsequent Update().
662+
663+
While we're here, test the *defaulted* attribute.
662664
"""
663665
test = TestSCons.TestSCons()
664666
var_file = test.workpath('vars.py')
665667
test.write('vars.py', 'FROMFILE="added"')
666668
opts = SCons.Variables.Variables(files=var_file)
667-
opts.Add('A', 'A test variable', "1")
669+
opts.Add('A', 'A test variable', default="1")
670+
opts.Add('B', 'Test variable B', default="1")
668671
args = {
669672
'A' : 'a',
670673
'ADDEDLATER' : 'notaddedyet',
671674
}
672675
env = Environment()
673-
opts.Update(env,args)
676+
opts.Update(env, args)
674677

675678
r = opts.UnknownVariables()
676679
with self.subTest():
677680
self.assertEqual('notaddedyet', r['ADDEDLATER'])
678681
self.assertEqual('added', r['FROMFILE'])
679682
self.assertEqual('a', env['A'])
683+
self.assertEqual(['B'], opts.defaulted)
680684

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

697702
def test_AddOptionWithAliasUpdatesUnknown(self) -> None:
698703
"""Test updating of the 'unknown' dict (with aliases)"""

SCons/Variables/__init__.py

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import os.path
2929
import sys
30+
from contextlib import suppress
3031
from functools import cmp_to_key
3132
from typing import Callable, Sequence
3233

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

6061
def __lt__(self, other):
61-
"""Comparison fuction so Variable instances sort."""
62+
"""Comparison fuction so :class:`Variable` instances sort."""
6263
return self.key < other.key
6364

6465
def __str__(self) -> str:
65-
"""Provide a way to "print" a Variable object."""
66+
"""Provide a way to "print" a :class:`Variable` object."""
6667
return (
67-
f"({self.key!r}, {self.aliases}, {self.help!r}, {self.default!r}, "
68+
f"({self.key!r}, {self.aliases}, "
69+
f"help={self.help!r}, default={self.default!r}, "
6870
f"validator={self.validator}, converter={self.converter})"
6971
)
7072

7173

7274
class Variables:
73-
"""A container for multiple Build Variables.
75+
"""A container for Build Variables.
7476
75-
Includes methods to updates the environment with the variables,
76-
and to render the help text.
77+
Includes a method to populate the variables with values into a
78+
construction envirionment, and methods to render the help text.
79+
80+
Note that the pubic API for creating a ``Variables`` object is
81+
:func:`SCons.Script.Variables`, a kind of factory function, which
82+
defaults to supplying the contents of :attr:`~SCons.Script.ARGUMENTS`
83+
as the *args* parameter if it was not otherwise given. That is the
84+
behavior documented in the manpage for ``Variables`` - and different
85+
from the default if you instantiate this directly.
7786
7887
Arguments:
7988
files: string or list of strings naming variable config scripts
@@ -83,11 +92,15 @@ class Variables:
8392
instead of a fresh instance. Currently inoperable (default ``False``)
8493
8594
.. versionchanged:: 4.8.0
86-
The default for *is_global* changed to ``False`` (previously
87-
``True`` but it had no effect due to an implementation error).
95+
The default for *is_global* changed to ``False`` (the previous
96+
default ``True`` had no effect due to an implementation error).
8897
8998
.. deprecated:: 4.8.0
9099
*is_global* is deprecated.
100+
101+
.. versionadded:: NEXT_RELEASE
102+
The :attr:`defaulted` attribute now lists those variables which
103+
were filled in from default values.
91104
"""
92105

93106
def __init__(
@@ -102,15 +115,18 @@ def __init__(
102115
files = [files] if files else []
103116
self.files: Sequence[str] = files
104117
self.unknown: dict[str, str] = {}
118+
self.defaulted: list[str] = []
105119

106120
def __str__(self) -> str:
107-
"""Provide a way to "print" a Variables object."""
108-
s = "Variables(\n options=[\n"
109-
for option in self.options:
110-
s += f" {str(option)},\n"
111-
s += " ],\n"
112-
s += f" args={self.args},\n files={self.files},\n unknown={self.unknown},\n)"
113-
return s
121+
"""Provide a way to "print" a :class:`Variables` object."""
122+
opts = ',\n'.join((f" {option!s}" for option in self.options))
123+
return (
124+
f"Variables(\n options=[\n{opts}\n ],\n"
125+
f" args={self.args},\n"
126+
f" files={self.files},\n"
127+
f" unknown={self.unknown},\n"
128+
f" defaulted={self.defaulted},\n)"
129+
)
114130

115131
# lint: W0622: Redefining built-in 'help'
116132
def _do_add(
@@ -122,7 +138,7 @@ def _do_add(
122138
converter: Callable | None = None,
123139
**kwargs,
124140
) -> None:
125-
"""Create a Variable and add it to the list.
141+
"""Create a :class:`Variable` and add it to the list.
126142
127143
This is the internal implementation for :meth:`Add` and
128144
:meth:`AddVariables`. Not part of the public API.
@@ -203,9 +219,9 @@ def Add(
203219
return self._do_add(key, *args, **kwargs)
204220

205221
def AddVariables(self, *optlist) -> None:
206-
"""Add a list of Build Variables.
222+
"""Add Build Variables.
207223
208-
Each list element is a tuple/list of arguments to be passed on
224+
Each *optlist* element is a sequence of arguments to be passed on
209225
to the underlying method for adding variables.
210226
211227
Example::
@@ -223,13 +239,22 @@ def AddVariables(self, *optlist) -> None:
223239
def Update(self, env, args: dict | None = None) -> None:
224240
"""Update an environment with the Build Variables.
225241
242+
Collects variables from the input sources which do not match
243+
a variable description in this object. These are ignored for
244+
purposes of adding to *env*, but can be retrieved using the
245+
:meth:`UnknownVariables` method. Also collects variables which
246+
are set in *env* from the default in a variable description and
247+
not from the input sources. These are available in the
248+
:attr:`defaulted` attribute.
249+
226250
Args:
227251
env: the environment to update.
228252
args: a dictionary of keys and values to update in *env*.
229253
If omitted, uses the saved :attr:`args`
230254
"""
231-
# first pull in the defaults
255+
# first pull in the defaults, except any which are None.
232256
values = {opt.key: opt.default for opt in self.options if opt.default is not None}
257+
self.defaulted = list(values)
233258

234259
# next set the values specified in any options script(s)
235260
for filename in self.files:
@@ -256,6 +281,8 @@ def Update(self, env, args: dict | None = None) -> None:
256281
for option in self.options:
257282
if arg in option.aliases + [option.key,]:
258283
values[option.key] = value
284+
with suppress(ValueError):
285+
self.defaulted.remove(option.key)
259286
added = True
260287
if not added:
261288
self.unknown[arg] = value
@@ -269,6 +296,8 @@ def Update(self, env, args: dict | None = None) -> None:
269296
for option in self.options:
270297
if arg in option.aliases + [option.key,]:
271298
values[option.key] = value
299+
with suppress(ValueError):
300+
self.defaulted.remove(option.key)
272301
added = True
273302
if not added:
274303
self.unknown[arg] = value

0 commit comments

Comments
 (0)