summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Deegan <bill@baddogconsulting.com>2023-02-18 12:51:29 -0800
committerGitHub <noreply@github.com>2023-02-18 12:51:29 -0800
commit4b97682fb76be5d42efc850564571a4c2c596d2a (patch)
tree6849c78355858003426df6f7381751a875e008d9
parent38088491a865286797956c3b918f4f1be2d89800 (diff)
parent747e7e01486733887fc020499e79ebd25b1ca854 (diff)
downloadscons-git-4b97682fb76be5d42efc850564571a4c2c596d2a.tar.gz
Merge pull request #4263 from mwichmann/maint/CPPDEFINES-Append
Split out CPPDEFINES handling in Append methods
-rw-r--r--CHANGES.txt16
-rw-r--r--RELEASE.txt25
-rw-r--r--SCons/Defaults.py128
-rw-r--r--SCons/Defaults.xml95
-rw-r--r--SCons/DefaultsTests.py69
-rw-r--r--SCons/Environment.py351
-rw-r--r--SCons/Environment.xml263
-rw-r--r--SCons/Util/types.py35
-rw-r--r--test/CPPDEFINES/append.py391
-rwxr-xr-xtest/CPPDEFINES/basic.py55
-rw-r--r--test/CPPDEFINES/fixture/SConstruct-Append133
-rw-r--r--test/CPPDEFINES/fixture/SConstruct-Prepend134
-rw-r--r--test/CPPDEFINES/live.py33
-rw-r--r--test/CPPDEFINES/pkg-config.py50
-rw-r--r--test/CPPDEFINES/prepend.py349
-rw-r--r--test/CPPDEFINES/scan.py32
-rw-r--r--test/CPPDEFINES/undefined.py13
17 files changed, 1627 insertions, 545 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 5a3651ad8..4a2cb0d8e 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -133,6 +133,14 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
Also added "unique" keyword, to control whether a library is added
or not if it is already in the $LIBS construction var in the
configure context. (issue #2768).
+ - Completely refactored the CPPDEFINES logic in Append/AppendUnique/Prepend/PrependUnique
+ This change fixes the following GH Issues:
+ - GH Issue #3876 - Append() and AppendUnique() will handle CPPDEFINES the same
+ - GH Issue #4254 - Make handling tuples in CPPDEFINES consistent.
+ - We no longer sort the keys added to CPPDEFINES by their dictionary keys.
+ We take advantage that their order is now stable based on insertion order
+ in Python 3.5+
+ - Added/modifed unit and system tests to verify these changes.
RELEASE 4.4.0 - Sat, 30 Jul 2022 14:08:29 -0700
@@ -295,16 +303,16 @@ RELEASE 4.4.0 - Sat, 30 Jul 2022 14:08:29 -0700
- Ninja:Added user configurable setting of ninja depfile format via NINJA_DEPFILE_PARSE_FORMAT.
Now setting NINJA_DEPFILE_PARSE_FORMAT to [msvc,gcc,clang] can force the ninja expected
format. Compiler tools will also configure the variable automatically.
- - Ninja: Made ninja tool force the ninja file as the only target.
+ - Ninja: Made ninja tool force the ninja file as the only target.
- Ninja: Improved the default targets setup and made sure there is always a default target for
the ninja file, which excludes targets that start and stop the daemon.
- Ninja: Update ninja tool so targets passed to SCons are propagated to ninja when scons
automatically executes ninja.
- - Small refactor of scons daemons using a shared StateInfo class for communication
+ - Small refactor of scons daemons using a shared StateInfo class for communication
between the scons interactive thread and the http server thread. Added error handling
for scons interactive failing to startup.
- Ninja: Updated ninja scons daemon scripts to output errors to stderr as well as the daemon log.
- - Ninja: Fix typo in ninja scons daemon startup which causes ConnectionRefusedError to not retry
+ - Ninja: Fix typo in ninja scons daemon startup which causes ConnectionRefusedError to not retry
- Added SHELL_ENV_GENERATORS construction variable. This variable should be set to a list
(or an iterable) which contains functions to be called in order
when constructing the execution environment (Generally this is the shell environment
@@ -546,7 +554,7 @@ RELEASE 4.2.0 - Sat, 31 Jul 2021 18:12:46 -0700
- Fix Issue #3906 - `IMPLICIT_COMMAND_DEPENDENCIES` was not properly disabled when
set to any string value (For example ['none','false','no','off'])
Also previously 'All' wouldn't have the desired affect.
-
+
From Ivan Kravets:
- Provide a custom argument escape function for `TempFileMunge` using a new
`TEMPFILEARGESCFUNC` variable. Useful if you need to apply extra operations on
diff --git a/RELEASE.txt b/RELEASE.txt
index 6c1c5f281..7a96cec03 100644
--- a/RELEASE.txt
+++ b/RELEASE.txt
@@ -17,6 +17,11 @@ NOTE: If you build with Python 3.10.0 and then rebuild with 3.10.1 (or higher),
see unexpected rebuilds. This is due to Python internals changing which changed
the signature of a Python Action Function.
+NOTE: If you use a dictionary to specify your CPPDEFINES, you may see an unexpected rebuild.
+ The insertion order of dictionary keys is now preserved when generating the command line.
+ Previously these were sorted alphabecially. This change to the command line,
+ while generating identical set of CPPDEFINES can change order and cause a rebuild.
+
NEW FUNCTIONALITY
-----------------
@@ -51,7 +56,12 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY
which controls whether to add the library if it is already in the $LIBS
list. This brings the library-adding functionality in Configure in line
with the regular Append, AppendUnique, Prepend and PrependUnique methods.
-
+- CPPDEFINES values added via a dictionary type are longer sorted by
+ key. This used to be required to maintain a consistent order of
+ commandline arguments between SCons runs, but meant macros were not
+ always emitted in the order entered. Sorting is no longer required
+ after Python interpreter improvements. There might be a one-time
+ rebuild of targets that involved such sorted keys in their actions.
FIXES
-----
@@ -90,7 +100,16 @@ FIXES
actual build would not see the file as modified. Leading to incorrect incremental builds.
Now configure checks now clear node info for non conftest nodes, so they will be re-evaluated for
the real taskmaster run when the build commences.
-
+- Inconsistent behavior of adding values to the CPPDEFINES construction
+ variable has been cleaned up (described in issue #4254). Now a tuple,
+ whether provided by itself or as a member of a list, is consistently
+ interpreted as a macro with replacement value. When adding a list,
+ individual members are added in sequence without interpretation.
+ A string value containing spaces is split if it is the initial value or
+ added, but not if it is given as a member of an added list. Detection
+ of duplicate macros now works for both valued and unvalued forms.
+- Handling of CPPDEFINES macros via Prepend and PrependUnique now works
+ (previously this was special-cased only for Append and AppendUnique).
IMPROVEMENTS
------------
@@ -139,6 +158,8 @@ DOCUMENTATION
- Additional explanations for MSVSProject and MSVSSolution builders.
- Updated MSVC documentation - adds "version added" annotations on recently
added construction variables and provides a version-mapping table.
+- Added further details in the documentation of Append and related methods
+ on the special handling of CPPDEFINES.
DEVELOPMENT
-----------
diff --git a/SCons/Defaults.py b/SCons/Defaults.py
index 40c3e4a52..b21ce4c4a 100644
--- a/SCons/Defaults.py
+++ b/SCons/Defaults.py
@@ -36,6 +36,7 @@ import shutil
import stat
import sys
import time
+from typing import List
import SCons.Action
import SCons.Builder
@@ -46,7 +47,7 @@ import SCons.PathList
import SCons.Scanner.Dir
import SCons.Subst
import SCons.Tool
-import SCons.Util
+from SCons.Util import is_List, is_String, is_Sequence, is_Tuple, is_Dict, flatten
# A placeholder for a default Environment (for fetching source files
# from source code management systems and the like). This must be
@@ -166,7 +167,7 @@ def get_paths_str(dest) -> str:
def quote(arg):
return f'"{arg}"'
- if SCons.Util.is_List(dest):
+ if is_List(dest):
elem_strs = [quote(d) for d in dest]
return f'[{", ".join(elem_strs)}]'
else:
@@ -202,11 +203,11 @@ def chmod_func(dest, mode) -> None:
"""
from string import digits
SCons.Node.FS.invalidate_node_memos(dest)
- if not SCons.Util.is_List(dest):
+ if not is_List(dest):
dest = [dest]
- if SCons.Util.is_String(mode) and 0 not in [i in digits for i in mode]:
+ if is_String(mode) and 0 not in [i in digits for i in mode]:
mode = int(mode, 8)
- if not SCons.Util.is_String(mode):
+ if not is_String(mode):
for element in dest:
os.chmod(str(element), mode)
else:
@@ -244,7 +245,7 @@ def chmod_func(dest, mode) -> None:
def chmod_strfunc(dest, mode) -> str:
"""strfunction for the Chmod action function."""
- if not SCons.Util.is_String(mode):
+ if not is_String(mode):
return f'Chmod({get_paths_str(dest)}, {mode:#o})'
else:
return f'Chmod({get_paths_str(dest)}, "{mode}")'
@@ -272,10 +273,10 @@ def copy_func(dest, src, symlinks=True) -> int:
"""
dest = str(dest)
- src = [str(n) for n in src] if SCons.Util.is_List(src) else str(src)
+ src = [str(n) for n in src] if is_List(src) else str(src)
SCons.Node.FS.invalidate_node_memos(dest)
- if SCons.Util.is_List(src):
+ if is_List(src):
# this fails only if dest exists and is not a dir
try:
os.makedirs(dest, exist_ok=True)
@@ -322,7 +323,7 @@ def delete_func(dest, must_exist=False) -> None:
unless *must_exist* evaluates false (the default).
"""
SCons.Node.FS.invalidate_node_memos(dest)
- if not SCons.Util.is_List(dest):
+ if not is_List(dest):
dest = [dest]
for entry in dest:
entry = str(entry)
@@ -348,7 +349,7 @@ Delete = ActionFactory(delete_func, delete_strfunc)
def mkdir_func(dest) -> None:
"""Implementation of the Mkdir action function."""
SCons.Node.FS.invalidate_node_memos(dest)
- if not SCons.Util.is_List(dest):
+ if not is_List(dest):
dest = [dest]
for entry in dest:
os.makedirs(str(entry), exist_ok=True)
@@ -372,7 +373,7 @@ Move = ActionFactory(
def touch_func(dest) -> None:
"""Implementation of the Touch action function."""
SCons.Node.FS.invalidate_node_memos(dest)
- if not SCons.Util.is_List(dest):
+ if not is_List(dest):
dest = [dest]
for file in dest:
file = str(file)
@@ -433,7 +434,7 @@ def _concat_ixes(prefix, items_iter, suffix, env):
prefix = str(env.subst(prefix, SCons.Subst.SUBST_RAW))
suffix = str(env.subst(suffix, SCons.Subst.SUBST_RAW))
- for x in SCons.Util.flatten(items_iter):
+ for x in flatten(items_iter):
if isinstance(x, SCons.Node.FS.File):
result.append(x)
continue
@@ -479,8 +480,8 @@ def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None):
else:
c = _concat_ixes
- stripprefixes = list(map(env.subst, SCons.Util.flatten(stripprefixes)))
- stripsuffixes = list(map(env.subst, SCons.Util.flatten(stripsuffixes)))
+ stripprefixes = list(map(env.subst, flatten(stripprefixes)))
+ stripsuffixes = list(map(env.subst, flatten(stripsuffixes)))
stripped = []
for l in SCons.PathList.PathList(itms).subst_path(env, None, None):
@@ -488,7 +489,7 @@ def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None):
stripped.append(l)
continue
- if not SCons.Util.is_String(l):
+ if not is_String(l):
l = str(l)
for stripprefix in stripprefixes:
@@ -510,53 +511,82 @@ def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None):
return c(prefix, stripped, suffix, env)
-def processDefines(defs):
- """process defines, resolving strings, lists, dictionaries, into a list of
- strings
+def processDefines(defs) -> List[str]:
+ """Return list of strings for preprocessor defines from *defs*.
+
+ Resolves the different forms ``CPPDEFINES`` can be assembled in:
+ if the Append/Prepend routines are used beyond a initial setting it
+ will be a deque, but if written to only once (Environment initializer,
+ or direct write) it can be a multitude of types.
+
+ Any prefix/suffix is handled elsewhere (usually :func:`_concat_ixes`).
+
+ .. versionchanged:: 4.5.0
+ Bare tuples are now treated the same as tuple-in-sequence, assumed
+ to describe a valued macro. Bare strings are now split on space.
+ A dictionary is no longer sorted before handling.
"""
- if SCons.Util.is_List(defs):
- l = []
- for d in defs:
- if d is None:
+ dlist = []
+ if is_List(defs):
+ for define in defs:
+ if define is None:
continue
- elif SCons.Util.is_List(d) or isinstance(d, tuple):
- if len(d) >= 2:
- l.append(str(d[0]) + '=' + str(d[1]))
+ elif is_Sequence(define):
+ if len(define) > 2:
+ raise SCons.Errors.UserError(
+ f"Invalid tuple in CPPDEFINES: {define!r}, "
+ "must be a tuple with only two elements"
+ )
+ name, *value = define
+ if value and value[0] is not None:
+ # TODO: do we need to quote value if it contains space?
+ dlist.append(f"{name}={value[0]}")
else:
- l.append(str(d[0]))
- elif SCons.Util.is_Dict(d):
- for macro, value in d.items():
+ dlist.append(str(define[0]))
+ elif is_Dict(define):
+ for macro, value in define.items():
if value is not None:
- l.append(str(macro) + '=' + str(value))
+ # TODO: do we need to quote value if it contains space?
+ dlist.append(f"{macro}={value}")
else:
- l.append(str(macro))
- elif SCons.Util.is_String(d):
- l.append(str(d))
+ dlist.append(str(macro))
+ elif is_String(define):
+ dlist.append(str(define))
else:
- raise SCons.Errors.UserError("DEFINE %s is not a list, dict, string or None." % repr(d))
- elif SCons.Util.is_Dict(defs):
- # The items in a dictionary are stored in random order, but
- # if the order of the command-line options changes from
- # invocation to invocation, then the signature of the command
- # line will change and we'll get random unnecessary rebuilds.
- # Consequently, we have to sort the keys to ensure a
- # consistent order...
- l = []
- for k, v in sorted(defs.items()):
- if v is None:
- l.append(str(k))
+ raise SCons.Errors.UserError(
+ f"CPPDEFINES entry {define!r} is not a tuple, list, "
+ "dict, string or None."
+ )
+ elif is_Tuple(defs):
+ if len(defs) > 2:
+ raise SCons.Errors.UserError(
+ f"Invalid tuple in CPPDEFINES: {defs!r}, "
+ "must be a tuple with only two elements"
+ )
+ name, *value = defs
+ if value and value[0] is not None:
+ # TODO: do we need to quote value if it contains space?
+ dlist.append(f"{name}={value[0]}")
+ else:
+ dlist.append(str(define[0]))
+ elif is_Dict(defs):
+ for macro, value in defs.items():
+ if value is None:
+ dlist.append(str(macro))
else:
- l.append(str(k) + '=' + str(v))
+ dlist.append(f"{macro}={value}")
+ elif is_String(defs):
+ return defs.split()
else:
- l = [str(defs)]
- return l
+ dlist.append(str(defs))
+
+ return dlist
def _defines(prefix, defs, suffix, env, target=None, source=None, c=_concat_ixes):
- """A wrapper around _concat_ixes that turns a list or string
+ """A wrapper around :func:`_concat_ixes` that turns a list or string
into a list of C preprocessor command-line definitions.
"""
-
return c(prefix, env.subst_list(processDefines(defs), target=target, source=source), suffix, env)
diff --git a/SCons/Defaults.xml b/SCons/Defaults.xml
index 27b088294..7b37475a2 100644
--- a/SCons/Defaults.xml
+++ b/SCons/Defaults.xml
@@ -92,68 +92,111 @@ to each definition in &cv-link-CPPDEFINES;.
<summary>
<para>
A platform independent specification of C preprocessor macro definitions.
-The definitions will be added to command lines
+The definitions are added to command lines
through the automatically-generated
-&cv-link-_CPPDEFFLAGS; &consvar; (see above),
+&cv-link-_CPPDEFFLAGS; &consvar;,
which is constructed according to
-the type of value of &cv-CPPDEFINES;:
+the contents of &cv-CPPDEFINES;:
</para>
+<itemizedlist>
+<listitem>
<para>
If &cv-CPPDEFINES; is a string,
the values of the
&cv-link-CPPDEFPREFIX; and &cv-link-CPPDEFSUFFIX; &consvars;
-will be respectively prepended and appended to
-each definition in &cv-link-CPPDEFINES;.
+are respectively prepended and appended to
+each definition in &cv-CPPDEFINES;,
+split on whitespace.
</para>
<example_commands>
-# Will add -Dxyz to POSIX compiler command lines,
+# Adds -Dxyz to POSIX compiler command lines,
# and /Dxyz to Microsoft Visual C++ command lines.
env = Environment(CPPDEFINES='xyz')
</example_commands>
+</listitem>
+<listitem>
<para>
If &cv-CPPDEFINES; is a list,
the values of the
&cv-CPPDEFPREFIX; and &cv-CPPDEFSUFFIX; &consvars;
-will be respectively prepended and appended to
+are respectively prepended and appended to
each element in the list.
-If any element is a list or tuple,
-then the first item is the name being
-defined and the second item is its value:
+If any element is a tuple (or list)
+then the first item of the tuple is the macro name
+and the second is the macro definition.
+If the definition is not omitted or <literal>None</literal>,
+the name and definition are combined into a single
+<literal>name=definition</literal> item
+before the preending/appending.
</para>
<example_commands>
-# Will add -DB=2 -DA to POSIX compiler command lines,
+# Adds -DB=2 -DA to POSIX compiler command lines,
# and /DB=2 /DA to Microsoft Visual C++ command lines.
env = Environment(CPPDEFINES=[('B', 2), 'A'])
</example_commands>
+</listitem>
+<listitem>
<para>
If &cv-CPPDEFINES; is a dictionary,
the values of the
&cv-CPPDEFPREFIX; and &cv-CPPDEFSUFFIX; &consvars;
-will be respectively prepended and appended to
-each item from the dictionary.
-The key of each dictionary item
-is a name being defined
-to the dictionary item's corresponding value;
-if the value is
-<literal>None</literal>,
-then the name is defined without an explicit value.
-Note that the resulting flags are sorted by keyword
-to ensure that the order of the options on the
-command line is consistent each time
-&scons;
-is run.
+are respectively prepended and appended to
+each key from the dictionary.
+If the value for a key is not <literal>None</literal>,
+then the key (macro name) and the value
+(macros definition) are combined into a single
+<literal>name=definition</literal> item
+before the prepending/appending.
</para>
<example_commands>
-# Will add -DA -DB=2 to POSIX compiler command lines,
-# and /DA /DB=2 to Microsoft Visual C++ command lines.
+# Adds -DA -DB=2 to POSIX compiler command lines,
+# or /DA /DB=2 to Microsoft Visual C++ command lines.
env = Environment(CPPDEFINES={'B':2, 'A':None})
</example_commands>
+</listitem>
+</itemizedlist>
+
+<para>
+Depending on how contents are added to &cv-CPPDEFINES;,
+it may be transformed into a compound type,
+for example a list containing strings, tuples and/or dictionaries.
+&SCons; can correctly expand such a compound type.
+</para>
+
+<para>
+Note that &SCons; may call the compiler via a shell.
+If a macro definition contains characters such as spaces that
+have meaning to the shell, or is intended to be a string value,
+you may need to use the shell's quoting syntax to avoid
+interpretation by the shell before the preprocessor sees it.
+Function-like macros are not supported via this mechanism
+(and some compilers do not even implement that functionality
+via the command lines).
+When quoting, note that
+one set of quote characters are used to define a &Python; string,
+then quotes embedded inside that would be consumed by the shell
+unless escaped. These examples may help illustrate:
+</para>
+
+<example_commands>
+env = Environment(CPPDEFINES=['USE_ALT_HEADER=\\"foo_alt.h\\"'])
+env = Environment(CPPDEFINES=[('USE_ALT_HEADER', '\\"foo_alt.h\\"')])
+</example_commands>
+
+<para>
+:<emphasis>Changed in version 4.5</emphasis>:
+&SCons; no longer sorts &cv-CPPDEFINES; values entered
+in dictionary form. &Python; now preserves dictionary
+keys in the order they are entered, so it is no longer
+necessary to sort them to ensure a stable command line.
+</para>
+
</summary>
</cvar>
diff --git a/SCons/DefaultsTests.py b/SCons/DefaultsTests.py
index 7fd62ae8b..b23f3285e 100644
--- a/SCons/DefaultsTests.py
+++ b/SCons/DefaultsTests.py
@@ -27,7 +27,8 @@ import collections
import TestCmd
-from SCons.Defaults import mkdir_func, _defines
+from SCons.Defaults import mkdir_func, _defines, processDefines
+from SCons.Errors import UserError
class DummyEnvironment(collections.UserDict):
@@ -90,18 +91,66 @@ class DefaultsTestCase(unittest.TestCase):
"""
env = DummyEnvironment()
- # Neither source or target specified
- x = _defines('-D', ['A', 'B', 'C'], 'XYZ', env)
- self.assertEqual(x, ['-DAXYZ', '-DBXYZ', '-DCXYZ'])
+ with self.subTest():
+ # Neither source or target specified
+ x = _defines('-D', ['A', 'B', 'C'], 'XYZ', env)
+ self.assertEqual(x, ['-DAXYZ', '-DBXYZ', '-DCXYZ'])
- # only source specified
- y = _defines('-D', ['AA', 'BA', 'CA'], 'XYZA', env, 'XYZ')
- self.assertEqual(y, ['-DAAXYZA', '-DBAXYZA', '-DCAXYZA'])
+ with self.subTest():
+ # only source specified
+ y = _defines('-D', ['AA', 'BA', 'CA'], 'XYZA', env, 'XYZ')
+ self.assertEqual(y, ['-DAAXYZA', '-DBAXYZA', '-DCAXYZA'])
- # source and target specified
- z = _defines('-D', ['AAB', 'BAB', 'CAB'], 'XYZAB', env, 'XYZ', 'abc')
- self.assertEqual(z,['-DAABXYZAB', '-DBABXYZAB', '-DCABXYZAB'])
+ with self.subTest():
+ # source and target specified
+ z = _defines('-D', ['AAB', 'BAB', 'CAB'], 'XYZAB', env, 'XYZ', 'abc')
+ self.assertEqual(z, ['-DAABXYZAB', '-DBABXYZAB', '-DCABXYZAB'])
+ def test_processDefines(self):
+ """Verify correct handling in processDefines."""
+ env = DummyEnvironment()
+
+ with self.subTest():
+ # macro name only
+ rv = processDefines('name')
+ self.assertEqual(rv, ['name'])
+
+ with self.subTest():
+ # macro with value
+ rv = processDefines('name=val')
+ self.assertEqual(rv, ['name=val'])
+
+ with self.subTest():
+ # space-separated macros
+ rv = processDefines('name1 name2=val2')
+ self.assertEqual(rv, ['name1', 'name2=val2'])
+
+ with self.subTest():
+ # single list
+ rv = processDefines(['name', 'val'])
+ self.assertEqual(rv, ['name', 'val'])
+
+ with self.subTest():
+ # single tuple
+ rv = processDefines(('name', 'val'))
+ self.assertEqual(rv, ['name=val'])
+
+ with self.subTest():
+ # single dict
+ rv = processDefines({'foo': None, 'name': 'val'})
+ self.assertEqual(rv, ['foo', 'name=val'])
+
+ with self.subTest():
+ # compound list
+ rv = processDefines(['foo', ('name', 'val'), ['name2', 'val2']])
+ self.assertEqual(rv, ['foo', 'name=val', 'name2=val2'])
+
+ with self.subTest():
+ # invalid tuple
+ with self.assertRaises(
+ UserError, msg="Invalid tuple should throw SCons.Errors.UserError"
+ ):
+ rv = processDefines([('name', 'val', 'bad')])
if __name__ == "__main__":
diff --git a/SCons/Environment.py b/SCons/Environment.py
index 7212c89ea..9140d2726 100644
--- a/SCons/Environment.py
+++ b/SCons/Environment.py
@@ -35,7 +35,7 @@ import os
import sys
import re
import shlex
-from collections import UserDict
+from collections import UserDict, deque
import SCons.Action
import SCons.Builder
@@ -65,6 +65,7 @@ from SCons.Util import (
flatten,
is_Dict,
is_List,
+ is_Scalar,
is_Sequence,
is_String,
is_Tuple,
@@ -193,6 +194,206 @@ def _delete_duplicates(l, keep_last):
return result
+def _add_cppdefines(
+ env_dict: dict,
+ val, # add annotation?
+ prepend: bool = False,
+ unique: bool = False,
+ delete_existing: bool = False,
+) -> None:
+ """Adds to ``CPPDEFINES``, using the rules for C preprocessor macros.
+
+ This is split out from regular construction variable addition because
+ these entries can express either a macro with a replacement value or
+ one without. A macro with replacement value can be supplied as *val*
+ in three ways: as a combined string ``"name=value"``; as a tuple
+ ``(name, value)``, or as an entry in a dictionary ``{"name": value}``.
+ A list argument with multiple macros can also be given.
+
+ Additions can be unconditional (duplicates allowed) or uniquing (no dupes).
+
+ Note if a replacement value is supplied, *unique* requires a full
+ match to decide uniqueness - both the macro name and the replacement.
+ The inner :func:`_is_in` is used to figure that out.
+
+ Args:
+ env_dict: the dictionary containing the ``CPPDEFINES`` to be modified.
+ val: the value to add, can be string, sequence or dict
+ prepend: whether to put *val* in front or back.
+ unique: whether to add *val* if it already exists.
+ delete_existing: if *unique* is true, add *val* after removing previous.
+
+ .. versionadded:: 4.5.0
+ """
+
+ def _add_define(item, defines: deque, prepend: bool = False) -> None:
+ """Convenience function to prepend/append a single value.
+
+ Sole purpose is to shorten code in the outer function.
+ """
+ if prepend:
+ defines.appendleft(item)
+ else:
+ defines.append(item)
+
+
+ def _is_in(item, defines: deque):
+ """Returns match for *item* if found in *defines*.
+
+ Accounts for type differences: tuple ("FOO", "BAR"), list
+ ["FOO", "BAR"], string "FOO=BAR" and dict {"FOO": "BAR"} all
+ differ as far as Python equality comparison is concerned, but
+ are the same for purposes of creating the preprocessor macro.
+ Also an unvalued string should match something like ``("FOO", None)``.
+ Since the caller may wish to remove a matched entry, we need to
+ return it - cannot remove *item* itself unless it happened to
+ be an exact (type) match.
+
+ Called from a place we know *defines* is always a deque, and
+ *item* will not be a dict, so don't need to do much type checking.
+ If this ends up used more generally, would need to adjust that.
+
+ Note implied assumption that members of a list-valued define
+ will not be dicts - we cannot actually guarantee this, since
+ if the initial add is a list its contents are not converted.
+ """
+ def _macro_conv(v) -> list:
+ """Normalizes a macro to a list for comparisons."""
+ if is_Tuple(v):
+ return list(v)
+ elif is_String(v):
+ rv = v.split("=")
+ if len(rv) == 1:
+ return [v, None]
+ return rv
+ return v
+
+ if item in defines: # cheap check first
+ return item
+
+ item = _macro_conv(item)
+ for define in defines:
+ if item == _macro_conv(define):
+ return define
+
+ return False
+
+ key = 'CPPDEFINES'
+ try:
+ defines = env_dict[key]
+ except KeyError:
+ # This is a new entry, just save it as is. Defer conversion to
+ # preferred type until someone tries to amend the value.
+ # processDefines has no problem with unconverted values if it
+ # gets called without any later additions.
+ if is_String(val):
+ env_dict[key] = val.split()
+ else:
+ env_dict[key] = val
+ return
+
+ # Convert type of existing to deque (if necessary) to simplify processing
+ # of additions - inserting at either end is cheap. Deferred conversion
+ # is also useful in case CPPDEFINES was set initially without calling
+ # through here (e.g. Environment kwarg, or direct assignment).
+ if isinstance(defines, deque):
+ # Already a deque? do nothing. Explicit check is so we don't get
+ # picked up by the is_list case below.
+ pass
+ elif is_String(defines):
+ env_dict[key] = deque(defines.split())
+ elif is_Tuple(defines):
+ if len(defines) > 2:
+ raise SCons.Errors.UserError(
+ f"Invalid tuple in CPPDEFINES: {define!r}, must be a two-tuple"
+ )
+ env_dict[key] = deque([defines])
+ elif is_List(defines):
+ # a little extra work in case the initial container has dict
+ # item(s) inside it, so those can be matched by _is_in().
+ result = deque()
+ for define in defines:
+ if is_Dict(define):
+ result.extend(define.items())
+ else:
+ result.append(define)
+ env_dict[key] = result
+ elif is_Dict(defines):
+ env_dict[key] = deque(defines.items())
+ else:
+ env_dict[key] = deque(defines)
+ defines = env_dict[key] # in case we reassigned due to conversion
+
+ # now actually do the addition.
+ if is_Dict(val):
+ # Unpack the dict while applying to existing
+ for item in val.items():
+ if unique:
+ match = _is_in(item, defines)
+ if match and delete_existing:
+ defines.remove(match)
+ _add_define(item, defines, prepend)
+ elif not match:
+ _add_define(item, defines, prepend)
+ else:
+ _add_define(item, defines, prepend)
+
+ elif is_String(val):
+ for v in val.split():
+ if unique:
+ match = _is_in(v, defines)
+ if match and delete_existing:
+ defines.remove(match)
+ _add_define(v, defines, prepend)
+ elif not match:
+ _add_define(v, defines, prepend)
+ else:
+ _add_define(v, defines, prepend)
+
+ # A tuple appended to anything should yield -Dkey=value
+ elif is_Tuple(val):
+ if len(val) > 2:
+ raise SCons.Errors.UserError(
+ f"Invalid tuple added to CPPDEFINES: {val!r}, "
+ "must be a two-tuple"
+ )
+ if len(val) == 1:
+ val = (val[0], None) # normalize
+ if not is_Scalar(val[0]) or not is_Scalar(val[1]):
+ raise SCons.Errors.UserError(
+ f"Invalid tuple added to CPPDEFINES: {val!r}, "
+ "values must be scalar"
+ )
+ if unique:
+ match = _is_in(val, defines)
+ if match and delete_existing:
+ defines.remove(match)
+ _add_define(val, defines, prepend)
+ elif not match:
+ _add_define(val, defines, prepend)
+ else:
+ _add_define(val, defines, prepend)
+
+ elif is_List(val):
+ tmp = []
+ for item in val:
+ if unique:
+ match = _is_in(item, defines)
+ if match and delete_existing:
+ defines.remove(match)
+ tmp.append(item)
+ elif not match:
+ tmp.append(item)
+ else:
+ tmp.append(item)
+
+ if prepend:
+ defines.extendleft(tmp)
+ else:
+ defines.extend(tmp)
+
+ # else: # are there any other cases? processDefines doesn't think so.
+
# The following is partly based on code in a comment added by Peter
# Shannon at the following page (there called the "transplant" class):
@@ -837,8 +1038,8 @@ class SubstitutionEnvironment:
def MergeFlags(self, args, unique=True) -> None:
"""Merge flags into construction variables.
- Merges the flags from ``args`` into this construction environent.
- If ``args`` is not a dict, it is first converted to one with
+ Merges the flags from *args* into this construction environent.
+ If *args* is not a dict, it is first converted to one with
flags distributed into appropriate construction variables.
See :meth:`ParseFlags`.
@@ -1215,16 +1416,15 @@ class Base(SubstitutionEnvironment):
kw = copy_non_reserved_keywords(kw)
for key, val in kw.items():
+ if key == 'CPPDEFINES':
+ _add_cppdefines(self._dict, val)
+ continue
+
try:
- if key == 'CPPDEFINES' and is_String(self._dict[key]):
- self._dict[key] = [self._dict[key]]
orig = self._dict[key]
except KeyError:
# No existing var in the environment, so set to the new value.
- if key == 'CPPDEFINES' and is_String(val):
- self._dict[key] = [val]
- else:
- self._dict[key] = val
+ self._dict[key] = val
continue
try:
@@ -1263,19 +1463,8 @@ class Base(SubstitutionEnvironment):
# things like UserList will incorrectly coerce the
# original dict to a list (which we don't want).
if is_List(val):
- if key == 'CPPDEFINES':
- tmp = []
- for (k, v) in orig.items():
- if v is not None:
- tmp.append((k, v))
- else:
- tmp.append((k,))
- orig = tmp
- orig += val
- self._dict[key] = orig
- else:
- for v in val:
- orig[v] = None
+ for v in val:
+ orig[v] = None
else:
try:
update_dict(val)
@@ -1330,6 +1519,9 @@ class Base(SubstitutionEnvironment):
"""
kw = copy_non_reserved_keywords(kw)
for key, val in kw.items():
+ if key == 'CPPDEFINES':
+ _add_cppdefines(self._dict, val, unique=True, delete_existing=delete_existing)
+ continue
if is_List(val):
val = _delete_duplicates(val, delete_existing)
if key not in self._dict or self._dict[key] in ('', None):
@@ -1338,46 +1530,8 @@ class Base(SubstitutionEnvironment):
self._dict[key].update(val)
elif is_List(val):
dk = self._dict[key]
- if key == 'CPPDEFINES':
- tmp = []
- for i in val:
- if is_List(i):
- if len(i) >= 2:
- tmp.append((i[0], i[1]))
- else:
- tmp.append((i[0],))
- elif is_Tuple(i):
- tmp.append(i)
- else:
- tmp.append((i,))
- val = tmp
- # Construct a list of (key, value) tuples.
- if is_Dict(dk):
- tmp = []
- for (k, v) in dk.items():
- if v is not None:
- tmp.append((k, v))
- else:
- tmp.append((k,))
- dk = tmp
- elif is_String(dk):
- dk = [(dk,)]
- else:
- tmp = []
- for i in dk:
- if is_List(i):
- if len(i) >= 2:
- tmp.append((i[0], i[1]))
- else:
- tmp.append((i[0],))
- elif is_Tuple(i):
- tmp.append(i)
- else:
- tmp.append((i,))
- dk = tmp
- else:
- if not is_List(dk):
- dk = [dk]
+ if not is_List(dk):
+ dk = [dk]
if delete_existing:
dk = [x for x in dk if x not in val]
else:
@@ -1386,70 +1540,15 @@ class Base(SubstitutionEnvironment):
else:
dk = self._dict[key]
if is_List(dk):
- if key == 'CPPDEFINES':
- tmp = []
- for i in dk:
- if is_List(i):
- if len(i) >= 2:
- tmp.append((i[0], i[1]))
- else:
- tmp.append((i[0],))
- elif is_Tuple(i):
- tmp.append(i)
- else:
- tmp.append((i,))
- dk = tmp
- # Construct a list of (key, value) tuples.
- if is_Dict(val):
- tmp = []
- for (k, v) in val.items():
- if v is not None:
- tmp.append((k, v))
- else:
- tmp.append((k,))
- val = tmp
- elif is_String(val):
- val = [(val,)]
- if delete_existing:
- dk = list(filter(lambda x, val=val: x not in val, dk))
- self._dict[key] = dk + val
- else:
- dk = [x for x in dk if x not in val]
- self._dict[key] = dk + val
+ # By elimination, val is not a list. Since dk is a
+ # list, wrap val in a list first.
+ if delete_existing:
+ dk = list(filter(lambda x, val=val: x not in val, dk))
+ self._dict[key] = dk + [val]
else:
- # By elimination, val is not a list. Since dk is a
- # list, wrap val in a list first.
- if delete_existing:
- dk = list(filter(lambda x, val=val: x not in val, dk))
+ if val not in dk:
self._dict[key] = dk + [val]
- else:
- if val not in dk:
- self._dict[key] = dk + [val]
else:
- if key == 'CPPDEFINES':
- if is_String(dk):
- dk = [dk]
- elif is_Dict(dk):
- tmp = []
- for (k, v) in dk.items():
- if v is not None:
- tmp.append((k, v))
- else:
- tmp.append((k,))
- dk = tmp
- if is_String(val):
- if val in dk:
- val = []
- else:
- val = [val]
- elif is_Dict(val):
- tmp = []
- for i,j in val.items():
- if j is not None:
- tmp.append((i,j))
- else:
- tmp.append(i)
- val = tmp
if delete_existing:
dk = [x for x in dk if x not in val]
self._dict[key] = dk + val
@@ -1726,6 +1825,9 @@ class Base(SubstitutionEnvironment):
kw = copy_non_reserved_keywords(kw)
for key, val in kw.items():
+ if key == 'CPPDEFINES':
+ _add_cppdefines(self._dict, val, prepend=True)
+ continue
try:
orig = self._dict[key]
except KeyError:
@@ -1815,6 +1917,9 @@ class Base(SubstitutionEnvironment):
"""
kw = copy_non_reserved_keywords(kw)
for key, val in kw.items():
+ if key == 'CPPDEFINES':
+ _add_cppdefines(self._dict, val, unique=True, prepend=True, delete_existing=delete_existing)
+ continue
if is_List(val):
val = _delete_duplicates(val, not delete_existing)
if key not in self._dict or self._dict[key] in ('', None):
diff --git a/SCons/Environment.xml b/SCons/Environment.xml
index 2e06b1e65..f87e88393 100644
--- a/SCons/Environment.xml
+++ b/SCons/Environment.xml
@@ -499,126 +499,205 @@ Multiple targets can be passed in to a single call to
</arguments>
<summary>
<para>
-Intelligently append values to &consvars; in the &consenv;
-named by <varname>env</varname>.
+Appends value(s) intelligently to &consvars; in
+<varname>env</varname>.
The &consvars; and values to add to them are passed as
<parameter>key=val</parameter> pairs (&Python; keyword arguments).
&f-env-Append; is designed to allow adding values
-without normally having to know the data type of an existing &consvar;.
+without having to think about the data type of an existing &consvar;.
Regular &Python; syntax can also be used to manipulate the &consvar;,
-but for that you must know the type of the &consvar;:
-for example, different &Python; syntax is needed to combine
-a list of values with a single string value, or vice versa.
+but for that you may need to know the types involved,
+for example pure &Python; lets you directly "add" two lists of strings,
+but adding a string to a list or a list to a string requires
+different syntax - things &f-Append; takes care of.
Some pre-defined &consvars; do have type expectations
-based on how &SCons; will use them,
-for example &cv-link-CPPDEFINES; is normally a string or a list of strings,
-but can be a string,
-a list of strings,
-a list of tuples,
-or a dictionary, while &cv-link-LIBEMITTER;
-would expect a callable or list of callables,
-and &cv-link-BUILDERS; would expect a mapping type.
+based on how &SCons; will use them:
+for example &cv-link-CPPDEFINES; is often a string or a list of strings,
+but can also be a list of tuples or a dictionary;
+while &cv-link-LIBEMITTER;
+is expected to be a callable or list of callables,
+and &cv-link-BUILDERS; is expected to be a dictionary.
Consult the documentation for the various &consvars; for more details.
</para>
<para>
-The following descriptions apply to both the append
-and prepend functions, the only difference being
-the insertion point of the added values.
-</para>
-<para>
-If <varname>env</varname>. does not have a &consvar;
-indicated by <parameter>key</parameter>,
-<parameter>val</parameter>
-is added to the environment under that key as-is.
-</para>
-
-<para>
-<parameter>val</parameter> can be almost any type,
-and &SCons; will combine it with an existing value into an appropriate type,
-but there are a few special cases to be aware of.
-When two strings are combined,
-the result is normally a new string,
-with the caller responsible for supplying any needed separation.
-The exception to this is the &consvar; &cv-link-CPPDEFINES;,
-in which each item will be postprocessed by adding a prefix
-and/or suffix,
-so the contents are treated as a list of strings, that is,
-adding a string will result in a separate string entry,
-not a combined string. For &cv-CPPDEFINES; as well as
-for &cv-link-LIBS;, and the various <literal>*PATH</literal>;
-variables, &SCons; will supply the compiler-specific
-syntax (e.g. adding a <literal>-D</literal> or <literal>/D</literal>
-prefix for &cv-CPPDEFINES;), so this syntax should be omitted when
+The following descriptions apply to both the &f-Append;
+and &f-Prepend; methods, as well as their
+<emphasis role="bold">Unique</emphasis> variants,
+with the differences being the insertion point of the added values
+and whether duplication is allowed.
+</para>
+
+<para>
+<parameter>val</parameter> can be almost any type.
+If <varname>env</varname> does not have a &consvar;
+named <parameter>key</parameter>,
+then <parameter>key</parameter> is simply
+stored with a value of <parameter>val</parameter>.
+Otherwise, <parameter>val</parameter> is
+combinined with the existing value,
+possibly converting into an appropriate type
+which can hold the expanded contents.
+There are a few special cases to be aware of.
+Normally, when two strings are combined,
+the result is a new string containing their concatenation
+(and you are responsible for supplying any needed separation);
+however, the contents of &cv-link-CPPDEFINES; will
+will be postprocessed by adding a prefix and/or suffix
+to each entry when the command line is produced,
+so &SCons; keeps them separate -
+appending a string will result in a separate string entry,
+not a combined string.
+For &cv-CPPDEFINES;. as well as
+&cv-link-LIBS;, and the various <literal>*PATH</literal> variables,
+&SCons; will amend the variable by supplying the compiler-specific
+syntax (e.g. prepending a <literal>-D</literal> or <literal>/D</literal>
+prefix for &cv-CPPDEFINES;), so you should omit this syntax when
adding values to these variables.
-Example (gcc syntax shown in the expansion of &CPPDEFINES;):
+Examples (gcc syntax shown in the expansion of &CPPDEFINES;):
</para>
<example_commands>
env = Environment(CXXFLAGS="-std=c11", CPPDEFINES="RELEASE")
-print("CXXFLAGS={}, CPPDEFINES={}".format(env['CXXFLAGS'], env['CPPDEFINES']))
-# notice including a leading space in CXXFLAGS value
+print(f"CXXFLAGS = {env['CXXFLAGS']}, CPPDEFINES = {env['CPPDEFINES']}")
+# notice including a leading space in CXXFLAGS addition
env.Append(CXXFLAGS=" -O", CPPDEFINES="EXTRA")
-print("CXXFLAGS={}, CPPDEFINES={}".format(env['CXXFLAGS'], env['CPPDEFINES']))
-print("CPPDEFINES will expand to {}".format(env.subst("$_CPPDEFFLAGS")))
+print(f"CXXFLAGS = {env['CXXFLAGS']}, CPPDEFINES = {env['CPPDEFINES']}")
+print("CPPDEFINES will expand to", env.subst('$_CPPDEFFLAGS'))
</example_commands>
<screen>
$ scons -Q
-CXXFLAGS=-std=c11, CPPDEFINES=RELEASE
-CXXFLAGS=-std=c11 -O, CPPDEFINES=['RELEASE', 'EXTRA']
+CXXFLAGS = -std=c11, CPPDEFINES = RELEASE
+CXXFLAGS = -std=c11 -O, CPPDEFINES = deque(['RELEASE', 'EXTRA'])
CPPDEFINES will expand to -DRELEASE -DEXTRA
scons: `.' is up to date.
</screen>
<para>
-Because &cv-link-CPPDEFINES; is intended to
-describe C/C++ pre-processor macro definitions,
-it accepts additional syntax.
-Preprocessor macros can be valued, or un-valued, as in
-<computeroutput>-DBAR=1</computeroutput> or
-<computeroutput>-DFOO</computeroutput>.
-The macro can be be supplied as a complete string including the value,
-or as a tuple (or list) of macro, value, or as a dictionary.
-Example (again gcc syntax in the expanded defines):
+Because &cv-link-CPPDEFINES; is intended for command-line
+specification of C/C++ preprocessor macros,
+additional syntax is accepted when adding to it.
+The preprocessor accepts arguments to predefine a macro name by itself
+(<computeroutput>-DFOO</computeroutput> for most compilers,
+<computeroutput>/DFOO</computeroutput> for Microsoft C++),
+which gives it an implicit value of <constant>1</constant>,
+or can be given with a replacement value
+(<computeroutput>-DBAR=TEXT</computeroutput>).
+&SCons; follows these rules when adding to &cv-CPPDEFINES;:
+</para>
+<itemizedlist>
+<listitem>
+<para>A string is split on spaces,
+giving an easy way to enter multiple macros in one addition.
+Use an <literal>=</literal> to specify a valued macro.</para>
+</listitem>
+<listitem>
+<para>A tuple is treated as a valued macro.
+Use the value <constant>None</constant> if the macro should not have a value.
+It is an error to supply more than two elements in such a tuple.</para>
+</listitem>
+<listitem>
+<para>A list is processed in order,
+adding each item without further interpretation.
+In this case, space-separated strings are not split.</para>
+</listitem>
+<listitem>
+<para>A dictionary is processed in order,
+adding each key:value pair as a valued macro.
+Use the value <constant>None</constant> if the macro should not have a value.
+</para>
+</listitem>
+</itemizedlist>
+
+<para>
+Examples:
</para>
<example_commands>
env = Environment(CPPDEFINES="FOO")
-print("CPPDEFINES={}".format(env['CPPDEFINES']))
+print("CPPDEFINES =", env['CPPDEFINES'])
env.Append(CPPDEFINES="BAR=1")
-print("CPPDEFINES={}".format(env['CPPDEFINES']))
-env.Append(CPPDEFINES=("OTHER", 2))
-print("CPPDEFINES={}".format(env['CPPDEFINES']))
+print("CPPDEFINES =", env['CPPDEFINES'])
+env.Append(CPPDEFINES=[("OTHER", 2)])
+print("CPPDEFINES =", env['CPPDEFINES'])
env.Append(CPPDEFINES={"EXTRA": "arg"})
-print("CPPDEFINES={}".format(env['CPPDEFINES']))
-print("CPPDEFINES will expand to {}".format(env.subst("$_CPPDEFFLAGS")))
+print("CPPDEFINES =", env['CPPDEFINES'])
+print("CPPDEFINES will expand to", env.subst('$_CPPDEFFLAGS'))
</example_commands>
<screen>
$ scons -Q
-CPPDEFINES=FOO
-CPPDEFINES=['FOO', 'BAR=1']
-CPPDEFINES=['FOO', 'BAR=1', ('OTHER', 2)]
-CPPDEFINES=['FOO', 'BAR=1', ('OTHER', 2), {'EXTRA': 'arg'}]
+CPPDEFINES = FOO
+CPPDEFINES = deque(['FOO', 'BAR=1'])
+CPPDEFINES = deque(['FOO', 'BAR=1', ('OTHER', 2)])
+CPPDEFINES = deque(['FOO', 'BAR=1', ('OTHER', 2), ('EXTRA', 'arg')])
CPPDEFINES will expand to -DFOO -DBAR=1 -DOTHER=2 -DEXTRA=arg
scons: `.' is up to date.
</screen>
<para>
-Adding a string <parameter>val</parameter>
-to a dictonary &consvar; will enter
-<parameter>val</parameter> as the key in the dict,
+Examples of adding multiple macros:
+</para>
+
+<example_commands>
+env = Environment()
+env.Append(CPPDEFINES=[("ONE", 1), "TWO", ("THREE", )])
+print("CPPDEFINES =", env['CPPDEFINES'])
+env.Append(CPPDEFINES={"FOUR": 4, "FIVE": None})
+print("CPPDEFINES =", env['CPPDEFINES'])
+print("CPPDEFINES will expand to", env.subst('$_CPPDEFFLAGS'))
+</example_commands>
+
+<screen>
+$ scons -Q
+CPPDEFINES = [('ONE', 1), 'TWO', ('THREE',)]
+CPPDEFINES = deque([('ONE', 1), 'TWO', ('THREE',), ('FOUR', 4), ('FIVE', None)])
+CPPDEFINES will expand to -DONE=1 -DTWO -DTHREE -DFOUR=4 -DFIVE
+scons: `.' is up to date.
+</screen>
+
+<para>
+<emphasis>Changed in version 4.5</emphasis>:
+clarifined the use of tuples vs. other types,
+handling is now consistent across the four functions.
+</para>
+
+<example_commands>
+env = Environment()
+env.Append(CPPDEFINES=("MACRO1", "MACRO2"))
+print("CPPDEFINES =", env['CPPDEFINES'])
+env.Append(CPPDEFINES=[("MACRO3", "MACRO4")])
+print("CPPDEFINES =", env['CPPDEFINES'])
+print("CPPDEFINES will expand to", env.subst('$_CPPDEFFLAGS'))
+</example_commands>
+
+<screen>
+$ scons -Q
+CPPDEFINES = ('MACRO1', 'MACRO2')
+CPPDEFINES = deque(['MACRO1', 'MACRO2', ('MACRO3', 'MACRO4')])
+CPPDEFINES will expand to -DMACRO1 -DMACRO2 -DMACRO3=MACRO4
+scons: `.' is up to date.
+</screen>
+
+<para>
+See &cv-link-CPPDEFINES; for more details.
+</para>
+
+<para>
+Appending a string <parameter>val</parameter>
+to a dictonary-typed &consvar; enters
+<parameter>val</parameter> as the key in the dictionary,
and <literal>None</literal> as its value.
-Using a tuple type to supply a key + value only works
-for the special case of &cv-link-CPPDEFINES;
+Using a tuple type to supply a <literal>key, value</literal>
+only works for the special case of &cv-CPPDEFINES;
described above.
</para>
<para>
Although most combinations of types work without
needing to know the details, some combinations
-do not make sense and a &Python; exception will be raised.
+do not make sense and &Python; raises an exception.
</para>
<para>
@@ -626,7 +705,7 @@ When using &f-env-Append; to modify &consvars;
which are path specifications (conventionally,
the names of such end in <literal>PATH</literal>),
it is recommended to add the values as a list of strings,
-even if there is only a single string to add.
+even if you are only adding a single string.
The same goes for adding library names to &cv-LIBS;.
</para>
@@ -696,20 +775,20 @@ See also &f-link-env-PrependENVPath;.
<scons_function name="AppendUnique">
<arguments signature="env">
-(key=val, [...], delete_existing=False)
+(key=val, [...], [delete_existing=False])
</arguments>
<summary>
<para>
Append values to &consvars; in the current &consenv;,
maintaining uniqueness.
-Works like &f-link-env-Append; (see for details),
-except that values already present in the &consvar;
-will not be added again.
+Works like &f-link-env-Append;,
+except that values that would become duplicates
+are not added.
If <parameter>delete_existing</parameter>
-is <constant>True</constant>,
-the existing matching value is first removed,
-and the requested value is added,
-having the effect of moving such values to the end.
+is set to a true value, then for any duplicate,
+the existing instance of <parameter>val</parameter> is first removed,
+then <parameter>val</parameter> is appended,
+having the effect of moving it to the end.
</para>
<para>
@@ -2716,22 +2795,22 @@ See also &f-link-env-AppendENVPath;.
<scons_function name="PrependUnique">
<arguments signature="env">
-(key=val, delete_existing=False, [...])
+(key=val, [...], [delete_existing=False])
</arguments>
<summary>
<para>
Prepend values to &consvars; in the current &consenv;,
maintaining uniqueness.
-Works like &f-link-env-Append; (see for details),
+Works like &f-link-env-Append;,
except that values are added to the front,
-rather than the end, of any existing value of the &consvar;,
-and values already present in the &consvar;
-will not be added again.
+rather than the end, of the &consvar;,
+and values that would become duplicates
+are not added.
If <parameter>delete_existing</parameter>
-is <constant>True</constant>,
-the existing matching value is first removed,
-and the requested value is inserted,
-having the effect of moving such values to the front.
+is set to a true value, then for any duplicate,
+the existing instance of <parameter>val</parameter> is first removed,
+then <parameter>val</parameter> is inserted,
+having the effect of moving it to the front.
</para>
<para>
diff --git a/SCons/Util/types.py b/SCons/Util/types.py
index 9aef13ef9..2071217e9 100644
--- a/SCons/Util/types.py
+++ b/SCons/Util/types.py
@@ -12,8 +12,8 @@ import pprint
import re
from typing import Optional
-from collections import UserDict, UserList, UserString
-from collections.abc import MappingView
+from collections import UserDict, UserList, UserString, deque
+from collections.abc import MappingView, Iterable
# Functions for deciding if things are like various types, mainly to
# handle UserDict, UserList and UserString like their underlying types.
@@ -23,20 +23,22 @@ from collections.abc import MappingView
# exception, but handling the exception when it's not the right type is
# often too slow.
-# We are using the following trick to speed up these
-# functions. Default arguments are used to take a snapshot of
-# the global functions and constants used by these functions. This
-# transforms accesses to global variable into local variables
-# accesses (i.e. LOAD_FAST instead of LOAD_GLOBAL).
-# Since checkers dislike this, it's now annotated for pylint to flag
+# A trick is used to speed up these functions. Default arguments are
+# used to take a snapshot of the global functions and constants used
+# by these functions. This transforms accesses to global variables into
+# local variable accesses (i.e. LOAD_FAST instead of LOAD_GLOBAL).
+# Since checkers dislike this, it's now annotated for pylint, to flag
# (mostly for other readers of this code) we're doing this intentionally.
-# TODO: PY3 check these are still valid choices for all of these funcs.
+# TODO: experts affirm this is still faster, but maybe check if worth it?
DictTypes = (dict, UserDict)
-ListTypes = (list, UserList)
+ListTypes = (list, UserList, deque)
-# Handle getting dictionary views.
-SequenceTypes = (list, tuple, UserList, MappingView)
+# With Python 3, there are view types that are sequences. Other interesting
+# sequences are range and bytearray. What we don't want is strings: while
+# they are iterable sequences, in SCons usage iterating over a string is
+# almost never what we want. So basically iterable-but-not-string:
+SequenceTypes = (list, tuple, deque, UserList, MappingView)
# Note that profiling data shows a speed-up when comparing
# explicitly with str instead of simply comparing
@@ -84,16 +86,19 @@ def is_String( # pylint: disable=redefined-outer-name,redefined-builtin
def is_Scalar( # pylint: disable=redefined-outer-name,redefined-builtin
- obj, isinstance=isinstance, StringTypes=StringTypes, SequenceTypes=SequenceTypes
+ obj, isinstance=isinstance, StringTypes=StringTypes, Iterable=Iterable,
) -> bool:
- """Check if object is a scalar."""
+ """Check if object is a scalar: not a container or iterable."""
# Profiling shows that there is an impressive speed-up of 2x
# when explicitly checking for strings instead of just not
# sequence when the argument (i.e. obj) is already a string.
# But, if obj is a not string then it is twice as fast to
# check only for 'not sequence'. The following code therefore
# assumes that the obj argument is a string most of the time.
- return isinstance(obj, StringTypes) or not isinstance(obj, SequenceTypes)
+ # Update: now using collections.abc.Iterable for the 2nd check.
+ # Note: None is considered a "scalar" for this check, which is correct
+ # for the usage in SCons.Environment._add_cppdefines.
+ return isinstance(obj, StringTypes) or not isinstance(obj, Iterable)
# From Dinu C. Gherman,
diff --git a/test/CPPDEFINES/append.py b/test/CPPDEFINES/append.py
index 874fceb7d..32917abcb 100644
--- a/test/CPPDEFINES/append.py
+++ b/test/CPPDEFINES/append.py
@@ -1,6 +1,8 @@
#!/usr/bin/env python
#
-# __COPYRIGHT__
+# 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
@@ -20,9 +22,6 @@
# 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.
-#
-
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
"""
Verify appending to CPPPDEFINES with various data types.
@@ -33,203 +32,315 @@ import TestSCons
test = TestSCons.TestSCons()
-# Note: we explicitly set CPPDEFPREFIX here to simplify testing on
-# Windows.
-
-test.write('SConstruct', """\
-env_1738_2 = Environment(CPPDEFPREFIX='-D')
-env_1738_2['CPPDEFINES'] = ['FOO']
-env_1738_2.Append(CPPDEFINES={'value' : '1'})
-print(env_1738_2.subst('$_CPPDEFFLAGS'))
-#env_1738_2.Object('test_1738_2', 'main.c')
-
-# https://github.com/SCons/scons/issues/2300
-env_2300_1 = Environment(CPPDEFINES = 'foo', CPPDEFPREFIX='-D')
-env_2300_1.Append(CPPDEFINES='bar')
-print(env_2300_1.subst('$_CPPDEFFLAGS'))
-
-env_2300_2 = Environment(CPPDEFINES = ['foo'], CPPDEFPREFIX='-D') # note the list
-env_2300_2.Append(CPPDEFINES='bar')
-print(env_2300_2.subst('$_CPPDEFFLAGS'))
-
-# https://github.com/SCons/scons/issues/1152
-# https://github.com/SCons/scons/issues/2900
-# Python3 dicts dont preserve order. Hence we supply subclass of OrderedDict
-# whose __str__ and __repr__ act like a normal dict.
-from collections import OrderedDict
-class OrderedPrintingDict(OrderedDict):
- def __repr__(self):
- return '{' + ', '.join(['%r: %r'%(k, v) for (k, v) in self.items()]) + '}'
-
- __str__ = __repr__
-
- # Because dict-like objects (except dict and UserDict) are not deep copied
- # directly when constructing Environment(CPPDEFINES = OrderedPrintingDict(...))
- def __semi_deepcopy__(self):
- return self.copy()
-
-cases=[('string', 'FOO'),
- ('list', ['NAME1', 'NAME2']),
- ('list-of-2lists', [('NAME1','VAL1'), ['NAME2','VAL2']]),
- ('dict', OrderedPrintingDict([('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]))
- ]
-
-for (t1, c1) in cases:
- for (t2, c2) in cases:
- print("==== Testing CPPDEFINES, appending a %s to a %s"%(t2, t1))
- print(" orig = %s, append = %s"%(c1, c2))
- env=Environment(CPPDEFINES = c1, CPPDEFPREFIX='-D')
- env.Append(CPPDEFINES = c2)
- final=env.subst('$_CPPDEFFLAGS',source="src", target="tgt")
- print('Append:\\n\\tresult=%s\\n\\tfinal=%s'%\\
- (env['CPPDEFINES'], final))
- env=Environment(CPPDEFINES = c1, CPPDEFPREFIX='-D')
- env.AppendUnique(CPPDEFINES = c2)
- final=env.subst('$_CPPDEFFLAGS',source="src", target="tgt")
- print('AppendUnique:\\n\\tresult=%s\\n\\tfinal=%s'%\\
- (env['CPPDEFINES'], final))
-""")
+# Note: explicitly set CPPDEFPREFIX here to simplify testing on Windows.
+# Link: fixture/SConstruct-Append
+test.file_fixture(["fixture", "SConstruct-Append"], "SConstruct")
expect_print_output="""\
-DFOO -Dvalue=1
-Dfoo -Dbar
-Dfoo -Dbar
+-Dfoo -Dbar -Dbaz
+-Dfoo bar -Dbaz
+-Dfoo -Dbar baz
+-Dfoo -Dbar -Dbaz
+-DMacro2=Value2 -DMacro4 -DMacro3=Value3 -DMacro1=Value1
+-DMacro1=Value1
+-DMacro1 -DValue1
==== Testing CPPDEFINES, appending a string to a string
- orig = FOO, append = FOO
+ orig = 'FOO', append = 'FOO'
+Append:
+ result=['FOO', 'FOO']
+ final=-DFOO -DFOO
+AppendUnique:
+ result=['FOO']
+ final=-DFOO
+==== Testing CPPDEFINES, appending a valuestring to a string
+ orig = 'FOO', append = 'NAME1=VAL1'
Append:
- result=['FOO', 'FOO']
- final=-DFOO -DFOO
+ result=['FOO', 'NAME1=VAL1']
+ final=-DFOO -DNAME1=VAL1
AppendUnique:
- result=['FOO']
- final=-DFOO
+ result=['FOO', 'NAME1=VAL1']
+ final=-DFOO -DNAME1=VAL1
==== Testing CPPDEFINES, appending a list to a string
- orig = FOO, append = ['NAME1', 'NAME2']
+ orig = 'FOO', append = ['NAME1', 'NAME2', 'NAME3']
Append:
- result=['FOO', 'NAME1', 'NAME2']
- final=-DFOO -DNAME1 -DNAME2
+ result=['FOO', 'NAME1', 'NAME2', 'NAME3']
+ final=-DFOO -DNAME1 -DNAME2 -DNAME3
AppendUnique:
- result=[('FOO',), ('NAME1',), ('NAME2',)]
- final=-DFOO -DNAME1 -DNAME2
+ result=['FOO', 'NAME1', 'NAME2', 'NAME3']
+ final=-DFOO -DNAME1 -DNAME2 -DNAME3
+==== Testing CPPDEFINES, appending a tuple to a string
+ orig = 'FOO', append = ('NAME1', 'VAL1')
+Append:
+ result=['FOO', ('NAME1', 'VAL1')]
+ final=-DFOO -DNAME1=VAL1
+AppendUnique:
+ result=['FOO', ('NAME1', 'VAL1')]
+ final=-DFOO -DNAME1=VAL1
==== Testing CPPDEFINES, appending a list-of-2lists to a string
- orig = FOO, append = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ orig = 'FOO', append = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
Append:
- result=['FOO', ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
- final=-DFOO -DNAME1=VAL1 -DNAME2=VAL2
+ result=['FOO', ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DFOO -DNAME1=VAL1 -DNAME2=VAL2
AppendUnique:
- result=[('FOO',), ('NAME1', 'VAL1'), ('NAME2', 'VAL2')]
- final=-DFOO -DNAME1=VAL1 -DNAME2=VAL2
+ result=['FOO', ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DFOO -DNAME1=VAL1 -DNAME2=VAL2
==== Testing CPPDEFINES, appending a dict to a string
- orig = FOO, append = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
+ orig = 'FOO', append = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
+Append:
+ result=['FOO', ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DFOO -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+AppendUnique:
+ result=['FOO', ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DFOO -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+==== Testing CPPDEFINES, appending a string to a valuestring
+ orig = 'NAME1=VAL1', append = 'FOO'
+Append:
+ result=['NAME1=VAL1', 'FOO']
+ final=-DNAME1=VAL1 -DFOO
+AppendUnique:
+ result=['NAME1=VAL1', 'FOO']
+ final=-DNAME1=VAL1 -DFOO
+==== Testing CPPDEFINES, appending a valuestring to a valuestring
+ orig = 'NAME1=VAL1', append = 'NAME1=VAL1'
+Append:
+ result=['NAME1=VAL1', 'NAME1=VAL1']
+ final=-DNAME1=VAL1 -DNAME1=VAL1
+AppendUnique:
+ result=['NAME1=VAL1']
+ final=-DNAME1=VAL1
+==== Testing CPPDEFINES, appending a list to a valuestring
+ orig = 'NAME1=VAL1', append = ['NAME1', 'NAME2', 'NAME3']
+Append:
+ result=['NAME1=VAL1', 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1=VAL1 -DNAME1 -DNAME2 -DNAME3
+AppendUnique:
+ result=['NAME1=VAL1', 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1=VAL1 -DNAME1 -DNAME2 -DNAME3
+==== Testing CPPDEFINES, appending a tuple to a valuestring
+ orig = 'NAME1=VAL1', append = ('NAME1', 'VAL1')
+Append:
+ result=['NAME1=VAL1', ('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1 -DNAME1=VAL1
+AppendUnique:
+ result=['NAME1=VAL1']
+ final=-DNAME1=VAL1
+==== Testing CPPDEFINES, appending a list-of-2lists to a valuestring
+ orig = 'NAME1=VAL1', append = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+Append:
+ result=['NAME1=VAL1', ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1=VAL1 -DNAME1=VAL1 -DNAME2=VAL2
+AppendUnique:
+ result=['NAME1=VAL1', ['NAME2', 'VAL2']]
+ final=-DNAME1=VAL1 -DNAME2=VAL2
+==== Testing CPPDEFINES, appending a dict to a valuestring
+ orig = 'NAME1=VAL1', append = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
Append:
- result=['FOO', {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}]
- final=-DFOO -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+ result=['NAME1=VAL1', ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
AppendUnique:
- result=['FOO', ('NAME2', 'VAL2'), 'NAME3', ('NAME1', 'VAL1')]
- final=-DFOO -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+ result=['NAME1=VAL1', ('NAME2', 'VAL2'), ('NAME3', None)]
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME3
==== Testing CPPDEFINES, appending a string to a list
- orig = ['NAME1', 'NAME2'], append = FOO
+ orig = ['NAME1', 'NAME2', 'NAME3'], append = 'FOO'
+Append:
+ result=['NAME1', 'NAME2', 'NAME3', 'FOO']
+ final=-DNAME1 -DNAME2 -DNAME3 -DFOO
+AppendUnique:
+ result=['NAME1', 'NAME2', 'NAME3', 'FOO']
+ final=-DNAME1 -DNAME2 -DNAME3 -DFOO
+==== Testing CPPDEFINES, appending a valuestring to a list
+ orig = ['NAME1', 'NAME2', 'NAME3'], append = 'NAME1=VAL1'
Append:
- result=['NAME1', 'NAME2', 'FOO']
- final=-DNAME1 -DNAME2 -DFOO
+ result=['NAME1', 'NAME2', 'NAME3', 'NAME1=VAL1']
+ final=-DNAME1 -DNAME2 -DNAME3 -DNAME1=VAL1
AppendUnique:
- result=[('NAME1',), ('NAME2',), ('FOO',)]
- final=-DNAME1 -DNAME2 -DFOO
+ result=['NAME1', 'NAME2', 'NAME3', 'NAME1=VAL1']
+ final=-DNAME1 -DNAME2 -DNAME3 -DNAME1=VAL1
==== Testing CPPDEFINES, appending a list to a list
- orig = ['NAME1', 'NAME2'], append = ['NAME1', 'NAME2']
+ orig = ['NAME1', 'NAME2', 'NAME3'], append = ['NAME1', 'NAME2', 'NAME3']
Append:
- result=['NAME1', 'NAME2', 'NAME1', 'NAME2']
- final=-DNAME1 -DNAME2 -DNAME1 -DNAME2
+ result=['NAME1', 'NAME2', 'NAME3', 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1 -DNAME2 -DNAME3 -DNAME1 -DNAME2 -DNAME3
AppendUnique:
- result=[('NAME1',), ('NAME2',)]
- final=-DNAME1 -DNAME2
+ result=['NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1 -DNAME2 -DNAME3
+==== Testing CPPDEFINES, appending a tuple to a list
+ orig = ['NAME1', 'NAME2', 'NAME3'], append = ('NAME1', 'VAL1')
+Append:
+ result=['NAME1', 'NAME2', 'NAME3', ('NAME1', 'VAL1')]
+ final=-DNAME1 -DNAME2 -DNAME3 -DNAME1=VAL1
+AppendUnique:
+ result=['NAME1', 'NAME2', 'NAME3', ('NAME1', 'VAL1')]
+ final=-DNAME1 -DNAME2 -DNAME3 -DNAME1=VAL1
==== Testing CPPDEFINES, appending a list-of-2lists to a list
- orig = ['NAME1', 'NAME2'], append = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ orig = ['NAME1', 'NAME2', 'NAME3'], append = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
Append:
- result=['NAME1', 'NAME2', ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
- final=-DNAME1 -DNAME2 -DNAME1=VAL1 -DNAME2=VAL2
+ result=['NAME1', 'NAME2', 'NAME3', ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1 -DNAME2 -DNAME3 -DNAME1=VAL1 -DNAME2=VAL2
AppendUnique:
- result=[('NAME1',), ('NAME2',), ('NAME1', 'VAL1'), ('NAME2', 'VAL2')]
- final=-DNAME1 -DNAME2 -DNAME1=VAL1 -DNAME2=VAL2
+ result=['NAME1', 'NAME2', 'NAME3', ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1 -DNAME2 -DNAME3 -DNAME1=VAL1 -DNAME2=VAL2
==== Testing CPPDEFINES, appending a dict to a list
- orig = ['NAME1', 'NAME2'], append = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
+ orig = ['NAME1', 'NAME2', 'NAME3'], append = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
+Append:
+ result=['NAME1', 'NAME2', 'NAME3', ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME1 -DNAME2 -DNAME3 -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+AppendUnique:
+ result=['NAME1', 'NAME2', 'NAME3', ('NAME2', 'VAL2'), ('NAME1', 'VAL1')]
+ final=-DNAME1 -DNAME2 -DNAME3 -DNAME2=VAL2 -DNAME1=VAL1
+==== Testing CPPDEFINES, appending a string to a tuple
+ orig = ('NAME1', 'VAL1'), append = 'FOO'
+Append:
+ result=[('NAME1', 'VAL1'), 'FOO']
+ final=-DNAME1=VAL1 -DFOO
+AppendUnique:
+ result=[('NAME1', 'VAL1'), 'FOO']
+ final=-DNAME1=VAL1 -DFOO
+==== Testing CPPDEFINES, appending a valuestring to a tuple
+ orig = ('NAME1', 'VAL1'), append = 'NAME1=VAL1'
+Append:
+ result=[('NAME1', 'VAL1'), 'NAME1=VAL1']
+ final=-DNAME1=VAL1 -DNAME1=VAL1
+AppendUnique:
+ result=[('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1
+==== Testing CPPDEFINES, appending a list to a tuple
+ orig = ('NAME1', 'VAL1'), append = ['NAME1', 'NAME2', 'NAME3']
+Append:
+ result=[('NAME1', 'VAL1'), 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1=VAL1 -DNAME1 -DNAME2 -DNAME3
+AppendUnique:
+ result=[('NAME1', 'VAL1'), 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1=VAL1 -DNAME1 -DNAME2 -DNAME3
+==== Testing CPPDEFINES, appending a tuple to a tuple
+ orig = ('NAME1', 'VAL1'), append = ('NAME1', 'VAL1')
Append:
- result=['NAME1', 'NAME2', {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}]
- final=-DNAME1 -DNAME2 -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+ result=[('NAME1', 'VAL1'), ('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1 -DNAME1=VAL1
AppendUnique:
- result=[('NAME1',), ('NAME2',), ('NAME2', 'VAL2'), ('NAME3',), ('NAME1', 'VAL1')]
- final=-DNAME1 -DNAME2 -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+ result=[('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1
+==== Testing CPPDEFINES, appending a list-of-2lists to a tuple
+ orig = ('NAME1', 'VAL1'), append = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+Append:
+ result=[('NAME1', 'VAL1'), ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1=VAL1 -DNAME1=VAL1 -DNAME2=VAL2
+AppendUnique:
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1=VAL1 -DNAME2=VAL2
+==== Testing CPPDEFINES, appending a dict to a tuple
+ orig = ('NAME1', 'VAL1'), append = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
+Append:
+ result=[('NAME1', 'VAL1'), ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+AppendUnique:
+ result=[('NAME1', 'VAL1'), ('NAME2', 'VAL2'), ('NAME3', None)]
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME3
==== Testing CPPDEFINES, appending a string to a list-of-2lists
- orig = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']], append = FOO
+ orig = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']], append = 'FOO'
Append:
- result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2'], 'FOO']
- final=-DNAME1=VAL1 -DNAME2=VAL2 -DFOO
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2'], 'FOO']
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DFOO
AppendUnique:
- result=[('NAME1', 'VAL1'), ('NAME2', 'VAL2'), ('FOO',)]
- final=-DNAME1=VAL1 -DNAME2=VAL2 -DFOO
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2'], 'FOO']
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DFOO
+==== Testing CPPDEFINES, appending a valuestring to a list-of-2lists
+ orig = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']], append = 'NAME1=VAL1'
+Append:
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2'], 'NAME1=VAL1']
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME1=VAL1
+AppendUnique:
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1=VAL1 -DNAME2=VAL2
==== Testing CPPDEFINES, appending a list to a list-of-2lists
- orig = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']], append = ['NAME1', 'NAME2']
+ orig = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']], append = ['NAME1', 'NAME2', 'NAME3']
Append:
- result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2'], 'NAME1', 'NAME2']
- final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME1 -DNAME2
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2'], 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME1 -DNAME2 -DNAME3
AppendUnique:
- result=[('NAME1', 'VAL1'), ('NAME2', 'VAL2'), ('NAME1',), ('NAME2',)]
- final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME1 -DNAME2
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2'], 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME1 -DNAME2 -DNAME3
+==== Testing CPPDEFINES, appending a tuple to a list-of-2lists
+ orig = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']], append = ('NAME1', 'VAL1')
+Append:
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2'], ('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME1=VAL1
+AppendUnique:
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1=VAL1 -DNAME2=VAL2
==== Testing CPPDEFINES, appending a list-of-2lists to a list-of-2lists
orig = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']], append = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
Append:
- result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2'], ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
- final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME1=VAL1 -DNAME2=VAL2
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2'], ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME1=VAL1 -DNAME2=VAL2
AppendUnique:
- result=[('NAME1', 'VAL1'), ('NAME2', 'VAL2')]
- final=-DNAME1=VAL1 -DNAME2=VAL2
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1=VAL1 -DNAME2=VAL2
==== Testing CPPDEFINES, appending a dict to a list-of-2lists
orig = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']], append = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
Append:
- result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2'], {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}]
- final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2'], ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
AppendUnique:
- result=[('NAME2', 'VAL2'), ('NAME3',), ('NAME1', 'VAL1')]
- final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2'], ('NAME3', None)]
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME3
==== Testing CPPDEFINES, appending a string to a dict
- orig = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}, append = FOO
+ orig = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}, append = 'FOO'
+Append:
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1'), 'FOO']
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1 -DFOO
+AppendUnique:
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1'), 'FOO']
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1 -DFOO
+==== Testing CPPDEFINES, appending a valuestring to a dict
+ orig = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}, append = 'NAME1=VAL1'
Append:
- result={'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1', 'FOO': None}
- final=-DFOO -DNAME1=VAL1 -DNAME2=VAL2 -DNAME3
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1'), 'NAME1=VAL1']
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1 -DNAME1=VAL1
AppendUnique:
- result=[('NAME2', 'VAL2'), ('NAME3',), ('NAME1', 'VAL1'), 'FOO']
- final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1 -DFOO
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
==== Testing CPPDEFINES, appending a list to a dict
- orig = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}, append = ['NAME1', 'NAME2']
+ orig = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}, append = ['NAME1', 'NAME2', 'NAME3']
Append:
- result=[('NAME2', 'VAL2'), ('NAME3',), ('NAME1', 'VAL1'), 'NAME1', 'NAME2']
- final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1 -DNAME1 -DNAME2
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1'), 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1 -DNAME1 -DNAME2 -DNAME3
AppendUnique:
- result=[('NAME2', 'VAL2'), ('NAME3',), ('NAME1', 'VAL1'), ('NAME1',), ('NAME2',)]
- final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1 -DNAME1 -DNAME2
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1'), 'NAME1', 'NAME2']
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1 -DNAME1 -DNAME2
+==== Testing CPPDEFINES, appending a tuple to a dict
+ orig = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}, append = ('NAME1', 'VAL1')
+Append:
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1'), ('NAME1', 'VAL1')]
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1 -DNAME1=VAL1
+AppendUnique:
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
==== Testing CPPDEFINES, appending a list-of-2lists to a dict
orig = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}, append = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
Append:
- result=[('NAME2', 'VAL2'), ('NAME3',), ('NAME1', 'VAL1'), ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
- final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1 -DNAME1=VAL1 -DNAME2=VAL2
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1'), ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1 -DNAME1=VAL1 -DNAME2=VAL2
AppendUnique:
- result=[('NAME2', 'VAL2'), ('NAME3',), ('NAME1', 'VAL1')]
- final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
==== Testing CPPDEFINES, appending a dict to a dict
orig = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}, append = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
Append:
- result={'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
- final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME3
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1'), ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1 -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
AppendUnique:
- result={'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
- final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME3
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
"""
build_output="scons: `.' is up to date.\n"
-
-expect = test.wrap_stdout(build_str=build_output,
- read_str = expect_print_output)
-test.run(arguments = '.', stdout=expect)
+expect = test.wrap_stdout(build_str=build_output, read_str=expect_print_output)
+test.run(arguments='.', stdout=expect)
test.pass_test()
# Local Variables:
diff --git a/test/CPPDEFINES/basic.py b/test/CPPDEFINES/basic.py
index 176f543de..b66ea8a87 100755
--- a/test/CPPDEFINES/basic.py
+++ b/test/CPPDEFINES/basic.py
@@ -1,6 +1,8 @@
#!/usr/bin/env python
#
-# __COPYRIGHT__
+# 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
@@ -20,9 +22,6 @@
# 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.
-#
-
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
"""
Verify basic use of CPPPDEFINES with various data types.
@@ -37,7 +36,7 @@ test_list = [
'xyz',
['x', 'y', 'z'],
['x', ['y', 123], 'z', ('int', '$INTEGER')],
- { 'c' : 3, 'b': None, 'a' : 1 },
+ {'c': 3, 'b': None, 'a': 1},
"${TESTDEFS}",
"${GEN}",
]
@@ -48,33 +47,59 @@ def generator(target, source, env, for_signature):
return 'TARGET_AND_SOURCE_ARE_MISSING'
for i in test_list:
- env = Environment(CPPDEFPREFIX='-D', CPPDEFSUFFIX='', INTEGER=0, TESTDEFS=["FOO", "BAR=1"], GEN=generator)
+ env = Environment(
+ CPPDEFPREFIX='-D',
+ CPPDEFSUFFIX='',
+ INTEGER=0,
+ TESTDEFS=["FOO", "BAR=1"],
+ GEN=generator,
+ )
ttt = env.Entry('#ttt')
sss = env.Entry('#sss')
- print(env.Clone(CPPDEFINES=i).subst('$_CPPDEFFLAGS', target=[ttt], source=[sss]))
+ print(
+ env.Clone(CPPDEFINES=i).subst(
+ '$_CPPDEFFLAGS',
+ target=[ttt],
+ source=[sss],
+ )
+ )
+
for i in test_list:
- env = Environment(CPPDEFPREFIX='|', CPPDEFSUFFIX='|', INTEGER=1, TESTDEFS=["FOO", "BAR=1"], GEN=generator)
+ env = Environment(
+ CPPDEFPREFIX='|',
+ CPPDEFSUFFIX='|',
+ INTEGER=1,
+ TESTDEFS=["FOO", "BAR=1"],
+ GEN=generator,
+ )
ttt = env.Entry('#ttt')
sss = env.Entry('#sss')
- print(env.Clone(CPPDEFINES=i).subst('$_CPPDEFFLAGS', target=[ttt], source=[sss]))
+ print(
+ env.Clone(CPPDEFINES=i).subst(
+ '$_CPPDEFFLAGS',
+ target=[ttt],
+ source=[sss],
+ )
+ )
""")
-expect = test.wrap_stdout(build_str="scons: `.' is up to date.\n",
- read_str = """\
+expect = test.wrap_stdout(
+ build_str="scons: `.' is up to date.\n",
+ read_str="""\
-Dxyz
-Dx -Dy -Dz
-Dx -Dy=123 -Dz -Dint=0
--Da=1 -Db -Dc=3
+-Dc=3 -Db -Da=1
-DFOO -DBAR=1
-Dttt_GENERATED_sss
|xyz|
|x| |y| |z|
|x| |y=123| |z| |int=1|
-|a=1| |b| |c=3|
+|c=3| |b| |a=1|
|FOO| |BAR=1|
|ttt_GENERATED_sss|
-""")
-
+""",
+)
test.run(arguments = '.', stdout=expect)
test.pass_test()
diff --git a/test/CPPDEFINES/fixture/SConstruct-Append b/test/CPPDEFINES/fixture/SConstruct-Append
new file mode 100644
index 000000000..8c26270f3
--- /dev/null
+++ b/test/CPPDEFINES/fixture/SConstruct-Append
@@ -0,0 +1,133 @@
+# SPDX-License-Identifier: MIT
+#
+# Copyright The SCons Foundation
+
+"""Append/AppendUnique tests"""
+
+DefaultEnvironment(tools=[])
+
+# Special cases:
+# https://github.com/SCons/scons/issues/1738
+env_1738_2 = Environment(CPPDEFPREFIX='-D')
+env_1738_2['CPPDEFINES'] = ['FOO']
+env_1738_2.Append(CPPDEFINES={'value': '1'})
+print(env_1738_2.subst('$_CPPDEFFLAGS'))
+# env_1738_2.Object('test_1738_2', 'main.c')
+
+# https://github.com/SCons/scons/issues/2300
+env_2300_1 = Environment(CPPDEFINES='foo', CPPDEFPREFIX='-D')
+env_2300_1.Append(CPPDEFINES='bar')
+print(env_2300_1.subst('$_CPPDEFFLAGS'))
+
+env_2300_2 = Environment(CPPDEFINES=['foo'], CPPDEFPREFIX='-D') # note the list
+env_2300_2.Append(CPPDEFINES='bar')
+print(env_2300_2.subst('$_CPPDEFFLAGS'))
+
+# An initial space-separated string will be split, but not a string in a list.
+env_multi = Environment(CPPDEFPREFIX='-D')
+env_multi['CPPDEFINES'] = "foo bar"
+env_multi.Append(CPPDEFINES="baz")
+print(env_multi.subst('$_CPPDEFFLAGS'))
+
+env_multi = Environment(CPPDEFPREFIX='-D')
+env_multi['CPPDEFINES'] = ["foo bar"]
+env_multi.Append(CPPDEFINES="baz")
+print(env_multi.subst('$_CPPDEFFLAGS'))
+
+env_multi = Environment(CPPDEFPREFIX='-D')
+env_multi['CPPDEFINES'] = "foo"
+env_multi.Append(CPPDEFINES=["bar baz"])
+print(env_multi.subst('$_CPPDEFFLAGS'))
+
+env_multi = Environment(CPPDEFPREFIX='-D')
+env_multi['CPPDEFINES'] = "foo"
+env_multi.Append(CPPDEFINES="bar baz")
+print(env_multi.subst('$_CPPDEFFLAGS'))
+
+# Check that AppendUnique(..., delete_existing=True) works as expected.
+# Each addition is in different but matching form, and different order
+# so we expect a reordered list, but with the same macro defines.
+env_multi = Environment(CPPDEFPREFIX='-D')
+env_multi.Append(CPPDEFINES=["Macro1=Value1", ("Macro2", "Value2"), {"Macro3": "Value3"}, "Macro4"])
+try:
+ env_multi.AppendUnique(CPPDEFINES="Macro2=Value2", delete_existing=True)
+ env_multi.AppendUnique(CPPDEFINES=[("Macro4", None)], delete_existing=True)
+ env_multi.AppendUnique(CPPDEFINES=[("Macro3", "Value3")], delete_existing=True)
+ env_multi.AppendUnique(CPPDEFINES={"Macro1": "Value1"}, delete_existing=True)
+except Exception as t:
+ print(f"Prepend FAILED: {t}")
+else:
+ print(env_multi.subst('$_CPPDEFFLAGS'))
+
+# A lone tuple handled differently than a lone list.
+env_multi = Environment(CPPDEFPREFIX='-D', CPPDEFINES=("Macro1", "Value1"))
+print(env_multi.subst('$_CPPDEFFLAGS'))
+env_multi = Environment(CPPDEFPREFIX='-D', CPPDEFINES=["Macro1", "Value1"])
+print(env_multi.subst('$_CPPDEFFLAGS'))
+
+# https://github.com/SCons/scons/issues/1152
+# https://github.com/SCons/scons/issues/2900
+# Python3 dicts dont preserve order. Hence we supply subclass of OrderedDict
+# whose __str__ and __repr__ act like a normal dict.
+from collections import OrderedDict
+
+class OrderedPrintingDict(OrderedDict):
+ def __repr__(self):
+ return '{' + ', '.join([f'{k!r}: {v!r}' for (k, v) in self.items()]) + '}'
+
+ __str__ = __repr__
+
+ # Because dict-like objects (except dict and UserDict) are not deep copied
+ # directly when constructing Environment(CPPDEFINES=OrderedPrintingDict(...))
+ def __semi_deepcopy__(self):
+ return self.copy()
+
+
+# each of these types will be appended to each of the others
+# the first item in each tuple is a label for the output
+cases = [
+ ('string', 'FOO'),
+ ('valuestring', 'NAME1=VAL1'),
+ ('list', ['NAME1', 'NAME2', 'NAME3']),
+ ('tuple', ('NAME1', 'VAL1')),
+ ('list-of-2lists', [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]),
+ (
+ 'dict', # intentionally not sorted by key
+ OrderedPrintingDict([('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]),
+ ),
+]
+
+
+def dlist(coll):
+ # if it's a deque, turn it into a list for display purposes
+ from collections import deque
+
+ if isinstance(coll, deque):
+ return list(coll)
+ return coll
+
+
+for (t1, c1) in cases:
+ for (t2, c2) in cases:
+ print(f"==== Testing CPPDEFINES, appending a {t2} to a {t1}")
+ # string-like appearance if the value is a string
+ orig = f"{c1!r}" if isinstance(c1, str) else c1
+ app = f"{c2!r}" if isinstance(c2, str) else c2
+ print(f" orig = {orig}, append = {app}")
+ env = Environment(CPPDEFINES=c1, CPPDEFPREFIX='-D')
+ try:
+ env.Append(CPPDEFINES=c2)
+ final = env.subst('$_CPPDEFFLAGS', source="src", target="tgt")
+ print(f"Append:\n result={dlist(env['CPPDEFINES'])}\n final={final}")
+ except Exception as t:
+ print(f"Append:\n FAILED: {t}")
+
+ env = Environment(CPPDEFINES=c1, CPPDEFPREFIX='-D')
+ try:
+ env.AppendUnique(CPPDEFINES=c2)
+ final = env.subst('$_CPPDEFFLAGS', source="src", target="tgt")
+ print(
+ f"AppendUnique:\n result={dlist(env['CPPDEFINES'])}\n final={final}"
+ )
+ except Exception as t:
+ print(f"AppendUnique:\n FAILED: {t}")
diff --git a/test/CPPDEFINES/fixture/SConstruct-Prepend b/test/CPPDEFINES/fixture/SConstruct-Prepend
new file mode 100644
index 000000000..26546f113
--- /dev/null
+++ b/test/CPPDEFINES/fixture/SConstruct-Prepend
@@ -0,0 +1,134 @@
+# SPDX-License-Identifier: MIT
+#
+# Copyright The SCons Foundation
+
+"""Prepend/PrependUnique tests"""
+
+DefaultEnvironment(tools=[])
+
+# Special cases:
+# https://github.com/SCons/scons/issues/1738
+env_1738_2 = Environment(CPPDEFPREFIX='-D')
+env_1738_2['CPPDEFINES'] = ['FOO']
+env_1738_2.Prepend(CPPDEFINES={'value': '1'})
+print(env_1738_2.subst('$_CPPDEFFLAGS'))
+# env_1738_2.Object('test_1738_2', 'main.c')
+
+# https://github.com/SCons/scons/issues/2300
+env_2300_1 = Environment(CPPDEFINES='foo', CPPDEFPREFIX='-D')
+env_2300_1.Prepend(CPPDEFINES='bar')
+print(env_2300_1.subst('$_CPPDEFFLAGS'))
+
+env_2300_2 = Environment(CPPDEFINES=['foo'], CPPDEFPREFIX='-D') # note the list
+env_2300_2.Prepend(CPPDEFINES='bar')
+print(env_2300_2.subst('$_CPPDEFFLAGS'))
+
+# An initial space-separated string will be split, but not a string in a list.
+env_multi = Environment(CPPDEFPREFIX='-D')
+env_multi['CPPDEFINES'] = "foo bar"
+env_multi.Prepend(CPPDEFINES="baz")
+print(env_multi.subst('$_CPPDEFFLAGS'))
+
+env_multi = Environment(CPPDEFPREFIX='-D')
+env_multi['CPPDEFINES'] = ["foo bar"]
+env_multi.Prepend(CPPDEFINES="baz")
+print(env_multi.subst('$_CPPDEFFLAGS'))
+
+env_multi = Environment(CPPDEFPREFIX='-D')
+env_multi['CPPDEFINES'] = "foo"
+env_multi.Prepend(CPPDEFINES=["bar baz"])
+print(env_multi.subst('$_CPPDEFFLAGS'))
+
+env_multi = Environment(CPPDEFPREFIX='-D')
+env_multi['CPPDEFINES'] = "foo"
+env_multi.Prepend(CPPDEFINES="bar baz")
+print(env_multi.subst('$_CPPDEFFLAGS'))
+
+# Check that PrependUnique(..., delete_existing=True) works as expected.
+# Each addition is in different but matching form, and different order
+# so we expect a reordered list, but with the same macro defines.
+env_multi = Environment(CPPDEFPREFIX='-D')
+env_multi.Prepend(CPPDEFINES=["Macro1=Value1", ("Macro2", "Value2"), {"Macro3": "Value3"}])
+try:
+ env_multi.PrependUnique(CPPDEFINES="Macro2=Value2", delete_existing=True)
+ env_multi.PrependUnique(CPPDEFINES=[("Macro4", None)], delete_existing=True)
+ env_multi.PrependUnique(CPPDEFINES=[("Macro3", "Value3")], delete_existing=True)
+ env_multi.PrependUnique(CPPDEFINES={"Macro1": "Value1"}, delete_existing=True)
+except Exception as t:
+ print(f"Prepend FAILED: {t}")
+else:
+ print(env_multi.subst('$_CPPDEFFLAGS'))
+
+# A lone tuple handled differently than a lone list.
+env_tuple = Environment(CPPDEFPREFIX='-D', CPPDEFINES=("Macro1", "Value1"))
+print(env_tuple.subst('$_CPPDEFFLAGS'))
+env_multi = Environment(CPPDEFPREFIX='-D', CPPDEFINES=["Macro1", "Value1"])
+print(env_multi.subst('$_CPPDEFFLAGS'))
+
+# https://github.com/SCons/scons/issues/1152
+# https://github.com/SCons/scons/issues/2900
+# Python3 dicts dont preserve order. Hence we supply subclass of OrderedDict
+# whose __str__ and __repr__ act like a normal dict.
+from collections import OrderedDict
+
+
+class OrderedPrintingDict(OrderedDict):
+ def __repr__(self):
+ return '{' + ', '.join([f'{k!r}: {v!r}' for (k, v) in self.items()]) + '}'
+
+ __str__ = __repr__
+
+ # Because dict-like objects (except dict and UserDict) are not deep copied
+ # directly when constructing Environment(CPPDEFINES=OrderedPrintingDict(...))
+ def __semi_deepcopy__(self):
+ return self.copy()
+
+
+# each of these types will be prepended to each of the others
+# the first item in each tuple is a label for the output
+cases = [
+ ('string', 'FOO'),
+ ('valuestring', 'NAME1=VAL1'),
+ ('list', ['NAME1', 'NAME2', 'NAME3']),
+ ('tuple', ('NAME1', 'VAL1')),
+ ('list-of-2lists', [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]),
+ (
+ 'dict', # intentionally not sorted by key
+ OrderedPrintingDict([('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]),
+ ),
+]
+
+
+def dlist(coll):
+ # if it's a deque, turn it into a list for display purposes
+ from collections import deque
+
+ if isinstance(coll, deque):
+ return list(coll)
+ return coll
+
+
+for (t1, c1) in cases:
+ for (t2, c2) in cases:
+ print(f"==== Testing CPPDEFINES, prepending a {t2} to a {t1}")
+ # string-like appearance if the value is a string
+ orig = f"{c1!r}" if isinstance(c1, str) else c1
+ pre = f"{c2!r}" if isinstance(c2, str) else c2
+ print(f" orig = {orig}, prepend = {pre}")
+ env = Environment(CPPDEFINES=c1, CPPDEFPREFIX='-D')
+ try:
+ env.Prepend(CPPDEFINES=c2)
+ final = env.subst('$_CPPDEFFLAGS', source="src", target="tgt")
+ print(f"Prepend:\n result={dlist(env['CPPDEFINES'])}\n final={final}")
+ except Exception as t:
+ print(f"Prepend:\n FAILED: {t}")
+
+ env = Environment(CPPDEFINES=c1, CPPDEFPREFIX='-D')
+ try:
+ env.PrependUnique(CPPDEFINES=c2)
+ final = env.subst('$_CPPDEFFLAGS', source="src", target="tgt")
+ print(
+ f"PrependUnique:\n result={dlist(env['CPPDEFINES'])}\n final={final}"
+ )
+ except Exception as t:
+ print(f"PrependUnique:\n FAILED: {t}")
diff --git a/test/CPPDEFINES/live.py b/test/CPPDEFINES/live.py
index 0c7ad7874..97e0e1323 100644
--- a/test/CPPDEFINES/live.py
+++ b/test/CPPDEFINES/live.py
@@ -1,6 +1,8 @@
#!/usr/bin/env python
#
-# __COPYRIGHT__
+# 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
@@ -20,9 +22,6 @@
# 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.
-#
-
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
"""
Verify basic use of CPPDEFINES with live compilation.
@@ -33,14 +32,14 @@ import TestSCons
test = TestSCons.TestSCons()
test.write('SConstruct', """\
-foo = Environment(CPPDEFINES = ['FOO', ('VAL', '$VALUE')], VALUE=7)
-bar = Environment(CPPDEFINES = {'BAR':None, 'VAL':8})
-baz = Environment(CPPDEFINES = ['BAZ', ('VAL', 9)])
-f = foo.Object(target = 'foo', source = 'prog.c')
-b = bar.Object(target = 'bar', source = 'prog.c')
-foo.Program(target = 'foo', source = f)
-bar.Program(target = 'bar', source = b)
-baz.Program(target = 'baz', source = 'baz.cpp')
+foo = Environment(CPPDEFINES=['FOO', ('VAL', '$VALUE')], VALUE=7)
+bar = Environment(CPPDEFINES={'BAR': None, 'VAL': 8})
+baz = Environment(CPPDEFINES=['BAZ', ('VAL', 9)])
+f = foo.Object(target='foo', source='prog.c')
+b = bar.Object(target='bar', source='prog.c')
+foo.Program(target='foo', source=f)
+bar.Program(target='bar', source=b)
+baz.Program(target='baz', source='baz.cpp')
""")
test.write('prog.c', r"""
@@ -74,12 +73,10 @@ main(int argc, char *argv[])
}
""")
-
-test.run(arguments = '.')
-
-test.run(program = test.workpath('foo'), stdout = "prog.c: FOO 7\n")
-test.run(program = test.workpath('bar'), stdout = "prog.c: BAR 8\n")
-test.run(program = test.workpath('baz'), stdout = "baz.cpp: BAZ 9\n")
+test.run(arguments='.')
+test.run(program=test.workpath('foo'), stdout="prog.c: FOO 7\n")
+test.run(program=test.workpath('bar'), stdout="prog.c: BAR 8\n")
+test.run(program=test.workpath('baz'), stdout="baz.cpp: BAZ 9\n")
test.pass_test()
diff --git a/test/CPPDEFINES/pkg-config.py b/test/CPPDEFINES/pkg-config.py
index 65210cec7..cd8c9dc7b 100644
--- a/test/CPPDEFINES/pkg-config.py
+++ b/test/CPPDEFINES/pkg-config.py
@@ -1,6 +1,8 @@
#!/usr/bin/env python
#
-# __COPYRIGHT__
+# 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
@@ -20,9 +22,6 @@
# 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.
-#
-
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
"""
Verify merging with MergeFlags to CPPPDEFINES with various data types.
@@ -69,12 +68,14 @@ else:
test.write('SConstruct', """\
import os
import sys
+
# Python3 dicts dont preserve order. Hence we supply subclass of OrderedDict
# whose __str__ and __repr__ act like a normal dict.
from collections import OrderedDict
+
class OrderedPrintingDict(OrderedDict):
def __repr__(self):
- return '{' + ', '.join(['%r: %r'%(k, v) for (k, v) in self.items()]) + '}'
+ return '{' + ', '.join(['%r: %r' % (k, v) for (k, v) in self.items()]) + '}'
__str__ = __repr__
@@ -85,29 +86,41 @@ class OrderedPrintingDict(OrderedDict):
""" + """
# https://github.com/SCons/scons/issues/2671
# Passing test cases
-env_1 = Environment(CPPDEFINES=[('DEBUG','1'), 'TEST'], tools = ['%(pkg_config_tools)s'])
+env_1 = Environment(CPPDEFINES=[('DEBUG', '1'), 'TEST'], tools=['%(pkg_config_tools)s'])
if sys.platform == 'win32':
- os.environ['PKG_CONFIG_PATH'] = env_1.Dir('.').abspath.replace("\\\\" , "/")
-env_1.ParseConfig('%(pkg_config_cl_path)s "%(pkg_config_path)s" --cflags %(pkg_config_file)s')
+ os.environ['PKG_CONFIG_PATH'] = env_1.Dir('.').abspath.replace("\\\\", "/")
+env_1.ParseConfig(
+ '%(pkg_config_cl_path)s "%(pkg_config_path)s" --cflags %(pkg_config_file)s'
+)
print(env_1.subst('$_CPPDEFFLAGS'))
-env_2 = Environment(CPPDEFINES=[('DEBUG','1'), 'TEST'], tools = ['%(pkg_config_tools)s'])
+env_2 = Environment(CPPDEFINES=[('DEBUG', '1'), 'TEST'], tools=['%(pkg_config_tools)s'])
env_2.MergeFlags('-DSOMETHING -DVARIABLE=2')
print(env_2.subst('$_CPPDEFFLAGS'))
# Failing test cases
-env_3 = Environment(CPPDEFINES=OrderedPrintingDict([('DEBUG', 1), ('TEST', None)]), tools = ['%(pkg_config_tools)s'])
-env_3.ParseConfig('%(pkg_config_cl_path)s "%(pkg_config_path)s" --cflags %(pkg_config_file)s')
+env_3 = Environment(
+ CPPDEFINES=OrderedPrintingDict([('DEBUG', 1), ('TEST', None)]),
+ tools=['%(pkg_config_tools)s'],
+)
+env_3.ParseConfig(
+ '%(pkg_config_cl_path)s "%(pkg_config_path)s" --cflags %(pkg_config_file)s'
+)
print(env_3.subst('$_CPPDEFFLAGS'))
-env_4 = Environment(CPPDEFINES=OrderedPrintingDict([('DEBUG', 1), ('TEST', None)]), tools = ['%(pkg_config_tools)s'])
+env_4 = Environment(
+ CPPDEFINES=OrderedPrintingDict([('DEBUG', 1), ('TEST', None)]),
+ tools=['%(pkg_config_tools)s'],
+)
env_4.MergeFlags('-DSOMETHING -DVARIABLE=2')
print(env_4.subst('$_CPPDEFFLAGS'))
# https://github.com/SCons/scons/issues/1738
-env_1738_1 = Environment(tools = ['%(pkg_config_tools)s'])
-env_1738_1.ParseConfig('%(pkg_config_cl_path)s "%(pkg_config_path)s" --cflags --libs %(pkg_config_file)s')
-env_1738_1.Append(CPPDEFINES={'value' : '1'})
+env_1738_1 = Environment(tools=['%(pkg_config_tools)s'])
+env_1738_1.ParseConfig(
+ '%(pkg_config_cl_path)s "%(pkg_config_path)s" --cflags --libs %(pkg_config_file)s'
+)
+env_1738_1.Append(CPPDEFINES={'value': '1'})
print(env_1738_1.subst('$_CPPDEFFLAGS'))
"""%locals() )
@@ -119,11 +132,10 @@ expect_print_output="""\
-DSOMETHING -DVARIABLE=2 -Dvalue=1
"""
-build_output="scons: `.' is up to date.\n"
+build_output = "scons: `.' is up to date.\n"
+expect = test.wrap_stdout(build_str=build_output, read_str=expect_print_output)
+test.run(arguments='.', stdout=expect)
-expect = test.wrap_stdout(build_str=build_output,
- read_str = expect_print_output)
-test.run(arguments = '.', stdout=expect)
test.pass_test()
# Local Variables:
diff --git a/test/CPPDEFINES/prepend.py b/test/CPPDEFINES/prepend.py
new file mode 100644
index 000000000..fb61678a8
--- /dev/null
+++ b/test/CPPDEFINES/prepend.py
@@ -0,0 +1,349 @@
+#!/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 prepending to CPPPDEFINES with various data types.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+# Note: explicitly set CPPDEFPREFIX here to simplify testing on Windows.
+
+# Link: fixture/SConstruct-Prepend
+test.file_fixture(["fixture", "SConstruct-Prepend"], "SConstruct")
+
+expect_print_output="""\
+-Dvalue=1 -DFOO
+-Dbar -Dfoo
+-Dbar -Dfoo
+-Dbaz -Dfoo -Dbar
+-Dbaz -Dfoo bar
+-Dbar baz -Dfoo
+-Dbaz -Dbar -Dfoo
+-DMacro1=Value1 -DMacro3=Value3 -DMacro4 -DMacro2=Value2
+-DMacro1=Value1
+-DMacro1 -DValue1
+==== Testing CPPDEFINES, prepending a string to a string
+ orig = 'FOO', prepend = 'FOO'
+Prepend:
+ result=['FOO', 'FOO']
+ final=-DFOO -DFOO
+PrependUnique:
+ result=['FOO']
+ final=-DFOO
+==== Testing CPPDEFINES, prepending a valuestring to a string
+ orig = 'FOO', prepend = 'NAME1=VAL1'
+Prepend:
+ result=['NAME1=VAL1', 'FOO']
+ final=-DNAME1=VAL1 -DFOO
+PrependUnique:
+ result=['NAME1=VAL1', 'FOO']
+ final=-DNAME1=VAL1 -DFOO
+==== Testing CPPDEFINES, prepending a list to a string
+ orig = 'FOO', prepend = ['NAME1', 'NAME2', 'NAME3']
+Prepend:
+ result=['NAME3', 'NAME2', 'NAME1', 'FOO']
+ final=-DNAME3 -DNAME2 -DNAME1 -DFOO
+PrependUnique:
+ result=['NAME3', 'NAME2', 'NAME1', 'FOO']
+ final=-DNAME3 -DNAME2 -DNAME1 -DFOO
+==== Testing CPPDEFINES, prepending a tuple to a string
+ orig = 'FOO', prepend = ('NAME1', 'VAL1')
+Prepend:
+ result=[('NAME1', 'VAL1'), 'FOO']
+ final=-DNAME1=VAL1 -DFOO
+PrependUnique:
+ result=[('NAME1', 'VAL1'), 'FOO']
+ final=-DNAME1=VAL1 -DFOO
+==== Testing CPPDEFINES, prepending a list-of-2lists to a string
+ orig = 'FOO', prepend = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+Prepend:
+ result=[['NAME2', 'VAL2'], ('NAME1', 'VAL1'), 'FOO']
+ final=-DNAME2=VAL2 -DNAME1=VAL1 -DFOO
+PrependUnique:
+ result=[['NAME2', 'VAL2'], ('NAME1', 'VAL1'), 'FOO']
+ final=-DNAME2=VAL2 -DNAME1=VAL1 -DFOO
+==== Testing CPPDEFINES, prepending a dict to a string
+ orig = 'FOO', prepend = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
+Prepend:
+ result=[('NAME1', 'VAL1'), ('NAME3', None), ('NAME2', 'VAL2'), 'FOO']
+ final=-DNAME1=VAL1 -DNAME3 -DNAME2=VAL2 -DFOO
+PrependUnique:
+ result=[('NAME1', 'VAL1'), ('NAME3', None), ('NAME2', 'VAL2'), 'FOO']
+ final=-DNAME1=VAL1 -DNAME3 -DNAME2=VAL2 -DFOO
+==== Testing CPPDEFINES, prepending a string to a valuestring
+ orig = 'NAME1=VAL1', prepend = 'FOO'
+Prepend:
+ result=['FOO', 'NAME1=VAL1']
+ final=-DFOO -DNAME1=VAL1
+PrependUnique:
+ result=['FOO', 'NAME1=VAL1']
+ final=-DFOO -DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a valuestring to a valuestring
+ orig = 'NAME1=VAL1', prepend = 'NAME1=VAL1'
+Prepend:
+ result=['NAME1=VAL1', 'NAME1=VAL1']
+ final=-DNAME1=VAL1 -DNAME1=VAL1
+PrependUnique:
+ result=['NAME1=VAL1']
+ final=-DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a list to a valuestring
+ orig = 'NAME1=VAL1', prepend = ['NAME1', 'NAME2', 'NAME3']
+Prepend:
+ result=['NAME3', 'NAME2', 'NAME1', 'NAME1=VAL1']
+ final=-DNAME3 -DNAME2 -DNAME1 -DNAME1=VAL1
+PrependUnique:
+ result=['NAME3', 'NAME2', 'NAME1', 'NAME1=VAL1']
+ final=-DNAME3 -DNAME2 -DNAME1 -DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a tuple to a valuestring
+ orig = 'NAME1=VAL1', prepend = ('NAME1', 'VAL1')
+Prepend:
+ result=[('NAME1', 'VAL1'), 'NAME1=VAL1']
+ final=-DNAME1=VAL1 -DNAME1=VAL1
+PrependUnique:
+ result=['NAME1=VAL1']
+ final=-DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a list-of-2lists to a valuestring
+ orig = 'NAME1=VAL1', prepend = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+Prepend:
+ result=[['NAME2', 'VAL2'], ('NAME1', 'VAL1'), 'NAME1=VAL1']
+ final=-DNAME2=VAL2 -DNAME1=VAL1 -DNAME1=VAL1
+PrependUnique:
+ result=[['NAME2', 'VAL2'], 'NAME1=VAL1']
+ final=-DNAME2=VAL2 -DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a dict to a valuestring
+ orig = 'NAME1=VAL1', prepend = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
+Prepend:
+ result=[('NAME1', 'VAL1'), ('NAME3', None), ('NAME2', 'VAL2'), 'NAME1=VAL1']
+ final=-DNAME1=VAL1 -DNAME3 -DNAME2=VAL2 -DNAME1=VAL1
+PrependUnique:
+ result=[('NAME3', None), ('NAME2', 'VAL2'), 'NAME1=VAL1']
+ final=-DNAME3 -DNAME2=VAL2 -DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a string to a list
+ orig = ['NAME1', 'NAME2', 'NAME3'], prepend = 'FOO'
+Prepend:
+ result=['FOO', 'NAME1', 'NAME2', 'NAME3']
+ final=-DFOO -DNAME1 -DNAME2 -DNAME3
+PrependUnique:
+ result=['FOO', 'NAME1', 'NAME2', 'NAME3']
+ final=-DFOO -DNAME1 -DNAME2 -DNAME3
+==== Testing CPPDEFINES, prepending a valuestring to a list
+ orig = ['NAME1', 'NAME2', 'NAME3'], prepend = 'NAME1=VAL1'
+Prepend:
+ result=['NAME1=VAL1', 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1=VAL1 -DNAME1 -DNAME2 -DNAME3
+PrependUnique:
+ result=['NAME1=VAL1', 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1=VAL1 -DNAME1 -DNAME2 -DNAME3
+==== Testing CPPDEFINES, prepending a list to a list
+ orig = ['NAME1', 'NAME2', 'NAME3'], prepend = ['NAME1', 'NAME2', 'NAME3']
+Prepend:
+ result=['NAME3', 'NAME2', 'NAME1', 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME3 -DNAME2 -DNAME1 -DNAME1 -DNAME2 -DNAME3
+PrependUnique:
+ result=['NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1 -DNAME2 -DNAME3
+==== Testing CPPDEFINES, prepending a tuple to a list
+ orig = ['NAME1', 'NAME2', 'NAME3'], prepend = ('NAME1', 'VAL1')
+Prepend:
+ result=[('NAME1', 'VAL1'), 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1=VAL1 -DNAME1 -DNAME2 -DNAME3
+PrependUnique:
+ result=[('NAME1', 'VAL1'), 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1=VAL1 -DNAME1 -DNAME2 -DNAME3
+==== Testing CPPDEFINES, prepending a list-of-2lists to a list
+ orig = ['NAME1', 'NAME2', 'NAME3'], prepend = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+Prepend:
+ result=[['NAME2', 'VAL2'], ('NAME1', 'VAL1'), 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME2=VAL2 -DNAME1=VAL1 -DNAME1 -DNAME2 -DNAME3
+PrependUnique:
+ result=[['NAME2', 'VAL2'], ('NAME1', 'VAL1'), 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME2=VAL2 -DNAME1=VAL1 -DNAME1 -DNAME2 -DNAME3
+==== Testing CPPDEFINES, prepending a dict to a list
+ orig = ['NAME1', 'NAME2', 'NAME3'], prepend = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
+Prepend:
+ result=[('NAME1', 'VAL1'), ('NAME3', None), ('NAME2', 'VAL2'), 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1=VAL1 -DNAME3 -DNAME2=VAL2 -DNAME1 -DNAME2 -DNAME3
+PrependUnique:
+ result=[('NAME1', 'VAL1'), ('NAME2', 'VAL2'), 'NAME1', 'NAME2', 'NAME3']
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME1 -DNAME2 -DNAME3
+==== Testing CPPDEFINES, prepending a string to a tuple
+ orig = ('NAME1', 'VAL1'), prepend = 'FOO'
+Prepend:
+ result=['FOO', ('NAME1', 'VAL1')]
+ final=-DFOO -DNAME1=VAL1
+PrependUnique:
+ result=['FOO', ('NAME1', 'VAL1')]
+ final=-DFOO -DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a valuestring to a tuple
+ orig = ('NAME1', 'VAL1'), prepend = 'NAME1=VAL1'
+Prepend:
+ result=['NAME1=VAL1', ('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1 -DNAME1=VAL1
+PrependUnique:
+ result=[('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a list to a tuple
+ orig = ('NAME1', 'VAL1'), prepend = ['NAME1', 'NAME2', 'NAME3']
+Prepend:
+ result=['NAME3', 'NAME2', 'NAME1', ('NAME1', 'VAL1')]
+ final=-DNAME3 -DNAME2 -DNAME1 -DNAME1=VAL1
+PrependUnique:
+ result=['NAME3', 'NAME2', 'NAME1', ('NAME1', 'VAL1')]
+ final=-DNAME3 -DNAME2 -DNAME1 -DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a tuple to a tuple
+ orig = ('NAME1', 'VAL1'), prepend = ('NAME1', 'VAL1')
+Prepend:
+ result=[('NAME1', 'VAL1'), ('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1 -DNAME1=VAL1
+PrependUnique:
+ result=[('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a list-of-2lists to a tuple
+ orig = ('NAME1', 'VAL1'), prepend = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+Prepend:
+ result=[['NAME2', 'VAL2'], ('NAME1', 'VAL1'), ('NAME1', 'VAL1')]
+ final=-DNAME2=VAL2 -DNAME1=VAL1 -DNAME1=VAL1
+PrependUnique:
+ result=[['NAME2', 'VAL2'], ('NAME1', 'VAL1')]
+ final=-DNAME2=VAL2 -DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a dict to a tuple
+ orig = ('NAME1', 'VAL1'), prepend = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
+Prepend:
+ result=[('NAME1', 'VAL1'), ('NAME3', None), ('NAME2', 'VAL2'), ('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1 -DNAME3 -DNAME2=VAL2 -DNAME1=VAL1
+PrependUnique:
+ result=[('NAME3', None), ('NAME2', 'VAL2'), ('NAME1', 'VAL1')]
+ final=-DNAME3 -DNAME2=VAL2 -DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a string to a list-of-2lists
+ orig = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']], prepend = 'FOO'
+Prepend:
+ result=['FOO', ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DFOO -DNAME1=VAL1 -DNAME2=VAL2
+PrependUnique:
+ result=['FOO', ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DFOO -DNAME1=VAL1 -DNAME2=VAL2
+==== Testing CPPDEFINES, prepending a valuestring to a list-of-2lists
+ orig = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']], prepend = 'NAME1=VAL1'
+Prepend:
+ result=['NAME1=VAL1', ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1=VAL1 -DNAME1=VAL1 -DNAME2=VAL2
+PrependUnique:
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1=VAL1 -DNAME2=VAL2
+==== Testing CPPDEFINES, prepending a list to a list-of-2lists
+ orig = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']], prepend = ['NAME1', 'NAME2', 'NAME3']
+Prepend:
+ result=['NAME3', 'NAME2', 'NAME1', ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME3 -DNAME2 -DNAME1 -DNAME1=VAL1 -DNAME2=VAL2
+PrependUnique:
+ result=['NAME3', 'NAME2', 'NAME1', ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME3 -DNAME2 -DNAME1 -DNAME1=VAL1 -DNAME2=VAL2
+==== Testing CPPDEFINES, prepending a tuple to a list-of-2lists
+ orig = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']], prepend = ('NAME1', 'VAL1')
+Prepend:
+ result=[('NAME1', 'VAL1'), ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1=VAL1 -DNAME1=VAL1 -DNAME2=VAL2
+PrependUnique:
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1=VAL1 -DNAME2=VAL2
+==== Testing CPPDEFINES, prepending a list-of-2lists to a list-of-2lists
+ orig = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']], prepend = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+Prepend:
+ result=[['NAME2', 'VAL2'], ('NAME1', 'VAL1'), ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME2=VAL2 -DNAME1=VAL1 -DNAME1=VAL1 -DNAME2=VAL2
+PrependUnique:
+ result=[('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1=VAL1 -DNAME2=VAL2
+==== Testing CPPDEFINES, prepending a dict to a list-of-2lists
+ orig = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']], prepend = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
+Prepend:
+ result=[('NAME1', 'VAL1'), ('NAME3', None), ('NAME2', 'VAL2'), ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME1=VAL1 -DNAME3 -DNAME2=VAL2 -DNAME1=VAL1 -DNAME2=VAL2
+PrependUnique:
+ result=[('NAME3', None), ('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+ final=-DNAME3 -DNAME1=VAL1 -DNAME2=VAL2
+==== Testing CPPDEFINES, prepending a string to a dict
+ orig = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}, prepend = 'FOO'
+Prepend:
+ result=['FOO', ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DFOO -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+PrependUnique:
+ result=['FOO', ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DFOO -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a valuestring to a dict
+ orig = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}, prepend = 'NAME1=VAL1'
+Prepend:
+ result=['NAME1=VAL1', ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+PrependUnique:
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a list to a dict
+ orig = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}, prepend = ['NAME1', 'NAME2', 'NAME3']
+Prepend:
+ result=['NAME3', 'NAME2', 'NAME1', ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME3 -DNAME2 -DNAME1 -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+PrependUnique:
+ result=['NAME2', 'NAME1', ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME2 -DNAME1 -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a tuple to a dict
+ orig = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}, prepend = ('NAME1', 'VAL1')
+Prepend:
+ result=[('NAME1', 'VAL1'), ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1 -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+PrependUnique:
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a list-of-2lists to a dict
+ orig = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}, prepend = [('NAME1', 'VAL1'), ['NAME2', 'VAL2']]
+Prepend:
+ result=[['NAME2', 'VAL2'], ('NAME1', 'VAL1'), ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME2=VAL2 -DNAME1=VAL1 -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+PrependUnique:
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+==== Testing CPPDEFINES, prepending a dict to a dict
+ orig = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}, prepend = {'NAME2': 'VAL2', 'NAME3': None, 'NAME1': 'VAL1'}
+Prepend:
+ result=[('NAME1', 'VAL1'), ('NAME3', None), ('NAME2', 'VAL2'), ('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME1=VAL1 -DNAME3 -DNAME2=VAL2 -DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+PrependUnique:
+ result=[('NAME2', 'VAL2'), ('NAME3', None), ('NAME1', 'VAL1')]
+ final=-DNAME2=VAL2 -DNAME3 -DNAME1=VAL1
+"""
+
+build_output="scons: `.' is up to date.\n"
+expect = test.wrap_stdout(build_str=build_output, read_str=expect_print_output)
+test.run(arguments='.', stdout=expect)
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/CPPDEFINES/scan.py b/test/CPPDEFINES/scan.py
index 53186425a..49cbec240 100644
--- a/test/CPPDEFINES/scan.py
+++ b/test/CPPDEFINES/scan.py
@@ -1,6 +1,8 @@
#!/usr/bin/env python
#
-# __COPYRIGHT__
+# 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
@@ -20,9 +22,6 @@
# 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.
-#
-
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
"""
Verify that use of the Scanner that evaluates CPP lines works as expected.
@@ -41,12 +40,12 @@ f3_exe = 'f3' + TestSCons._exe
f4_exe = 'f4' + TestSCons._exe
test.write('SConstruct', """\
-env = Environment(CPPPATH = ['.'])
+env = Environment(CPPPATH=['.'])
-f1 = env.Object('f1', 'fff.c', CPPDEFINES = ['F1'])
-f2 = env.Object('f2', 'fff.c', CPPDEFINES = [('F2', 1)])
-f3 = env.Object('f3', 'fff.c', CPPDEFINES = {'F3':None})
-f4 = env.Object('f4', 'fff.c', CPPDEFINES = {'F4':1})
+f1 = env.Object('f1', 'fff.c', CPPDEFINES=['F1'])
+f2 = env.Object('f2', 'fff.c', CPPDEFINES=[('F2', 1)])
+f3 = env.Object('f3', 'fff.c', CPPDEFINES={'F3': None})
+f4 = env.Object('f4', 'fff.c', CPPDEFINES={'F4': 1})
env.Program('f1', ['prog.c', f1])
env.Program('f2', ['prog.c', f2])
@@ -110,20 +109,16 @@ main(int argc, char *argv[])
test.run(arguments = '.')
-
test.run(program = test.workpath('f1'), stdout = "prog.c: F1\n")
test.run(program = test.workpath('f2'), stdout = "prog.c: F2\n")
test.run(program = test.workpath('f3'), stdout = "prog.c: F3\n")
test.run(program = test.workpath('f4'), stdout = "prog.c: F4\n")
-
-
test.write('f1.h', """
#define STRING "F1 again"
""")
test.up_to_date(arguments = '%(f2_exe)s %(f3_exe)s %(f4_exe)s' % locals())
-
test.not_up_to_date(arguments = '.')
test.run(program = test.workpath('f1'), stdout = "prog.c: F1 again\n")
@@ -131,14 +126,11 @@ test.run(program = test.workpath('f2'), stdout = "prog.c: F2\n")
test.run(program = test.workpath('f3'), stdout = "prog.c: F3\n")
test.run(program = test.workpath('f4'), stdout = "prog.c: F4\n")
-
-
test.write('f2.h', """
#define STRING "F2 again"
""")
test.up_to_date(arguments = '%(f1_exe)s %(f3_exe)s %(f4_exe)s' % locals())
-
test.not_up_to_date(arguments = '.')
test.run(program = test.workpath('f1'), stdout = "prog.c: F1 again\n")
@@ -146,14 +138,11 @@ test.run(program = test.workpath('f2'), stdout = "prog.c: F2 again\n")
test.run(program = test.workpath('f3'), stdout = "prog.c: F3\n")
test.run(program = test.workpath('f4'), stdout = "prog.c: F4\n")
-
-
test.write('f3.h', """
#define STRING "F3 again"
""")
test.up_to_date(arguments = '%(f1_exe)s %(f2_exe)s %(f4_exe)s' % locals())
-
test.not_up_to_date(arguments = '.')
test.run(program = test.workpath('f1'), stdout = "prog.c: F1 again\n")
@@ -161,14 +150,11 @@ test.run(program = test.workpath('f2'), stdout = "prog.c: F2 again\n")
test.run(program = test.workpath('f3'), stdout = "prog.c: F3 again\n")
test.run(program = test.workpath('f4'), stdout = "prog.c: F4\n")
-
-
test.write('f4.h', """
#define STRING "F4 again"
""")
test.up_to_date(arguments = '%(f1_exe)s %(f2_exe)s %(f3_exe)s' % locals())
-
test.not_up_to_date(arguments = '.')
test.run(program = test.workpath('f1'), stdout = "prog.c: F1 again\n")
@@ -176,8 +162,6 @@ test.run(program = test.workpath('f2'), stdout = "prog.c: F2 again\n")
test.run(program = test.workpath('f3'), stdout = "prog.c: F3 again\n")
test.run(program = test.workpath('f4'), stdout = "prog.c: F4 again\n")
-
-
test.pass_test()
# Local Variables:
diff --git a/test/CPPDEFINES/undefined.py b/test/CPPDEFINES/undefined.py
index b26b05a32..31568ea4b 100644
--- a/test/CPPDEFINES/undefined.py
+++ b/test/CPPDEFINES/undefined.py
@@ -1,6 +1,8 @@
#!/usr/bin/env python
#
-# __COPYRIGHT__
+# 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
@@ -20,9 +22,6 @@
# 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.
-#
-
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
"""
Verify that $_CPPDEFFLAGS doesn't barf when CPPDEFINES isn't defined.
@@ -37,10 +36,8 @@ env = Environment()
print(env.subst('$_CPPDEFFLAGS'))
""")
-expect = test.wrap_stdout(build_str="scons: `.' is up to date.\n",
- read_str = "\n")
-
-test.run(arguments = '.', stdout=expect)
+expect = test.wrap_stdout(build_str="scons: `.' is up to date.\n", read_str="\n")
+test.run(arguments='.', stdout=expect)
test.pass_test()