diff options
Diffstat (limited to 'SCons/Environment.py')
-rw-r--r-- | SCons/Environment.py | 306 |
1 files changed, 183 insertions, 123 deletions
diff --git a/SCons/Environment.py b/SCons/Environment.py index 7212c89ea..bc69f05cd 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 @@ -193,6 +193,162 @@ 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. + + Split out from regular construction variable handling because these + entries can express either a macro with a replacement list or one + without. A macro with replacement list can be supplied three ways: + as a combined string ``name=value``; as a tuple contained in + a sequence type ``[("name", value)]``; or as a dictionary entry + ``{"name": value}``. Appending/prepending can be unconditional + (duplicates allowed) or uniquing (no dupes). + + Note if a replacement list is supplied, "unique" requires a full + match - both the name and the replacement must be equal. + + 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. + """ + + 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: ("FOO", "BAR"), ["FOO", "BAR"] + "FOO=BAR" and {"FOO": "BAR"} all iffer as far as Python equality + comparison is concerned, but are the same for purposes of creating + the preprocessor macro. 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 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): + return v.split("=") + 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 + # deque until someone tries to amend the value, processDefines + # can handle all of these fine. + if is_String(val): + env_dict[key] = val.split() + else: + env_dict[key] = val + return + + # Convert type of existing to deque to simplify processing of addition - + # inserting at either end is cheap. + if isinstance(defines, deque): + # filter deques out to avoid catching in is_List check below + pass + elif is_String(defines): + env_dict[key] = deque(defines.split()) + elif is_Tuple(defines) or 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 it after the try block. + + 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): + 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 +993,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 +1371,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 +1418,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 +1474,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 +1485,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 +1495,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 +1780,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 +1872,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): |