Repository: SCons/scons

Issue:
AppendUnique  breaks CPPScanner when using defs without value
`AppendUnique` turns string values into tuples with single values, breaking `dictify_CPPDEFINES`, and thus CPPScanner.

Details of my investigation here: https://community.platformio.org/t/libraries-included-inside-conditional-guard-are-still-added-to-the-dep-graph-by-ldf/26326/4

1) Platformio inside its ProcessFlags parses defs like `-DA -DB=C into ('A', ('B', 'C'))` for `env[“CPPDEFINES”]`
2) `libopencm3.py` script tries to add `-DSTM32F4` def via `env.ApppendUnique(CPPDEFINES=["STM32F4"])` (which is not really necessary, but that’s irrelevant)
3) **`env.AppendUnique` turns all previous defs without value (that were strings) into tuples with single value, and adds new values also as tuples**: `(('A', ), ('B', 'C'), ('STM32F4',))`
4) **CPPScanner expects defs without value to be strings, but any invocation of `env.AppendUnique` breaks that expectation**
5) Scons crashes:
```python
def dictify_CPPDEFINES(env):
    cppdefines = env.get('CPPDEFINES', {})
    if cppdefines is None:
        return {}
    if SCons.Util.is_Sequence(cppdefines):
        result = {}
        for c in cppdefines:
            if SCons.Util.is_Sequence(c):
                result[c[0]] = c[1] # <-- [1] tuple index out of range
            else:
                result[c] = None
        return result
```

**Required information**
* Version of SCons 4.3
* Version of Python 3.9
* Which python distribution if applicable (python.org, cygwin, anaconda, macports, brew,etc) brew
* How you installed SCons viaPIO
* What Platform are you on? (Linux/Windows and which version) mac



Discussion:
Looks like it's easy enough to patch the hole here, without necessarily tackling the deeper question of why we have keep converting the type of CPPDEFINES. 

Do you have a simple example that actually tickles the `IndexError`

Meanwhile, /me scratches head over:
```python
env = Environment()
env.Append(CPPDEFINES="A")
env.Append(CPPDEFINES="B=C")
env.Append(CPPDEFINES=["STM32F4"])
env.AppendUnique(CPPDEFINES=["STM32F4"])
```
Showing
```
env['CPPDEFINES']=['A']
env['CPPDEFINES']=['A', 'B=C']
env['CPPDEFINES']=['A', 'B=C', 'STM32F4']
env['CPPDEFINES']=[('A',), ('B=C',), ('STM32F4',)]
```
as in - why does Append's speciai-case handling for CPPDEFINES not tuple-ify and AppendUnique's does?
I think `dictify_CPPDEFINES(env)` is enough to invoke the issue:
```
env = Environment()
env.AppendUnique(CPPDEFINES="A")
dictify_CPPDEFINES(env)
```
 

I think more proper example  of difference would  be to `env.Append(CPPDEFINES=("B", "C"))` (or whatever syntax is to append the tuple, I'm not fluent in python). You'll see that it'll add tuble for `("B", "C")`. But for case with no value (e.g. `"B"`) it will keep the string in it. So that means `AppendUnique` doesn't simplify the values, while `Append` does (and then other things rely on it) 
Indeed, it is... 
```python
from SCons.Scanner.C import dictify_CPPDEFINES
d = dictify_CPPDEFINES(env)
```


Resolving patch:
diff --git a/SCons/Scanner/C.py b/SCons/Scanner/C.py
--- a/SCons/Scanner/C.py
+++ b/SCons/Scanner/C.py
@@ -61,7 +61,8 @@ def read_file(self, file):
             self.missing.append((file, self.current_file))
             return ''
 
-def dictify_CPPDEFINES(env):
+def dictify_CPPDEFINES(env) -> dict:
+    """Returns CPPDEFINES converted to a dict."""
     cppdefines = env.get('CPPDEFINES', {})
     if cppdefines is None:
         return {}
@@ -69,7 +70,11 @@ def dictify_CPPDEFINES(env):
         result = {}
         for c in cppdefines:
             if SCons.Util.is_Sequence(c):
-                result[c[0]] = c[1]
+                try:
+                    result[c[0]] = c[1]
+                except IndexError:
+                    # it could be a one-item sequence
+                    result[c[0]] = None
             else:
                 result[c] = None
         return result
diff --git a/SCons/Scanner/CTests.py b/SCons/Scanner/CTests.py
--- a/SCons/Scanner/CTests.py
+++ b/SCons/Scanner/CTests.py
@@ -490,6 +490,17 @@ def runTest(self):
         headers = ['d1/f1.h']
         deps_match(self, deps, headers)
 
+class dictify_CPPDEFINESTestCase(unittest.TestCase):
+    def runTest(self):
+        """Make sure single-item tuples convert correctly.
+
+        This is a regression test: AppendUnique turns sequences into
+        lists of tuples, and dictify could gack on these.
+        """
+        env = DummyEnvironment(CPPDEFINES=(("VALUED_DEFINE", 1), ("UNVALUED_DEFINE", )))
+        d = SCons.Scanner.C.dictify_CPPDEFINES(env)
+        expect = {'VALUED_DEFINE': 1, 'UNVALUED_DEFINE': None}
+        assert d == expect
 
 def suite():
     suite = unittest.TestSuite()
@@ -510,6 +521,7 @@ def suite():
     suite.addTest(CConditionalScannerTestCase1())
     suite.addTest(CConditionalScannerTestCase2())
     suite.addTest(CConditionalScannerTestCase3())
+    suite.addTest(dictify_CPPDEFINESTestCase())
     return suite
 
 if __name__ == "__main__":


Test patch: