Skip to content

Commit

Permalink
Merge pull request #4657 from mwichmann/feature/vars-defaulted
Browse files Browse the repository at this point in the history
Add `Variables.defaulted` attribute
  • Loading branch information
bdbaddog authored Dec 6, 2024
2 parents f1723d8 + eeb025f commit 01f77b1
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 01f77b1

Please sign in to comment.