Repository: SCons/scons

Issue:
bug in _defines() method in Defaults.py
This issue was originally created at: 2009-03-03 17:44:03.
This issue was reported by: `drowley`.
drowley said at 2009-03-03 17:44:03

>The following SConstruct executes the wrong compile command:
```python
env = Environment()
env.Replace(CPPDEFINES="${DEFS}", DEFS=["foo1", "foo2"])
objects = env.Program("test", env.Glob("main.cpp"))
``` 
> 
>Expected command:  `g++ -o main.o -c -Dfoo1 -Dfoo2 main.cpp`
> 
>Actual command:    `g++ -o main.o -c "-Dfoo1 foo2" main.cpp`
> 
> 
>I think the bug is in the following line in Defaults.py:
```python
    return c(prefix, env.subst_path(l), suffix, env)
```
> 
>should probably be:
```python
    return c(prefix, env.subst_list(l), suffix, env)
``` 
>However, `subst_list()` returns `[["foo1", "foo2"]]` and the `_concat_ixes()` method doesn't handle nested lists. So the following line should probably also be added at the beginning of `_concat_ixes()`:
```python
    list = SCons.Util.flatten(list)
```
gregnoel said at 2009-04-10 08:02:31
>Bug party triage.  Gary to apply patch and see if breaks anything.




Discussion:
I wonder if this is still a problem?  If it's the same line (not a lot of reference in the above) it now looks like:
```
    return c(prefix, env.subst_path(processDefines(defs)), suffix, env)
```
Where processDefines does what looks like the required cleanup. Perhaps fixed?
Looks like not fixed.

```
% python ../../as_scons/scripts/scons.py
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o main.o -c "-Dfoo1 foo2" main.cpp
g++ -o test main.o
scons: done building targets.
```


and if making the proposed change (`subst_paths` to `subst_list`) we get a new result:
```
g++ -o main.o -c "-D['foo1', 'foo2']" main.cpp
```

that's not right either. But adding the additional `Flatten` does make it turn out as expected:
```
g++ -o main.o -c -Dfoo1 -Dfoo2 main.cpp
```
So this patch, then, would yield the desired result. But is it correct? (I renamed the parameter named _list_ since it's usually a poor name as it overwrites the builtin). SCons testsuite still passes...

[Defaults.txt](https://github.com/SCons/scons/files/5793271/Defaults.txt)

Hmm.. I can see expecting the results the original reporter expects..
I'd guess you could get away with a unit test to test it rather than a system test if you wanted to PR it.. ;)
I can't seem to make one that works. I thought something like this:
```python
env.Replace(CPPDEFINES="${DEFS}", DEFS=["foo", "bar"])
macros = env.subst('$_CPPDEFFLAGS', raw=1)
print(f"\nDEBUG: {env['CPPDEFINES']=}, {env['DEFS']=}, {macros=}\n")
expect = ["-Dfoo", "-Dbar"]
assert macros == expect, macros
```
But that appears to expand to... nothing (thus the debug print)... I'm doing something wrong, obviously. Similar sort of thing externally - just an SConstruct - does expand.

Resolving patch:
diff --git a/SCons/Defaults.py b/SCons/Defaults.py
--- a/SCons/Defaults.py
+++ b/SCons/Defaults.py
@@ -358,26 +358,26 @@ def touch_func(dest):
 # Internal utility functions
 
 
-def _concat(prefix, list, suffix, env, f=lambda x: x, target=None, source=None):
+def _concat(prefix, iter, suffix, env, f=lambda x: x, target=None, source=None):
     """
-    Creates a new list from 'list' by first interpolating each element
+    Creates a new list from 'iter' by first interpolating each element
     in the list using the 'env' dictionary and then calling f on the
     list, and finally calling _concat_ixes to concatenate 'prefix' and
     'suffix' onto each element of the list.
     """
-    if not list:
-        return list
+    if not iter:
+        return iter
 
-    l = f(SCons.PathList.PathList(list).subst_path(env, target, source))
+    l = f(SCons.PathList.PathList(iter).subst_path(env, target, source))
     if l is not None:
-        list = l
+        iter = l
 
-    return _concat_ixes(prefix, list, suffix, env)
+    return _concat_ixes(prefix, iter, suffix, env)
 
 
-def _concat_ixes(prefix, list, suffix, env):
+def _concat_ixes(prefix, iter, suffix, env):
     """
-    Creates a new list from 'list' by concatenating the 'prefix' and
+    Creates a new list from 'iter' by concatenating the 'prefix' and
     'suffix' arguments onto each element of the list.  A trailing space
     on 'prefix' or leading space on 'suffix' will cause them to be put
     into separate list elements rather than being concatenated.
@@ -389,7 +389,7 @@ def _concat_ixes(prefix, list, suffix, env):
     prefix = str(env.subst(prefix, SCons.Subst.SUBST_RAW))
     suffix = str(env.subst(suffix, SCons.Subst.SUBST_RAW))
 
-    for x in list:
+    for x in SCons.Util.flatten(iter):
         if isinstance(x, SCons.Node.FS.File):
             result.append(x)
             continue
@@ -513,7 +513,7 @@ def _defines(prefix, defs, suffix, env, c=_concat_ixes):
     into a list of C preprocessor command-line definitions.
     """
 
-    return c(prefix, env.subst_path(processDefines(defs)), suffix, env)
+    return c(prefix, env.subst_list(processDefines(defs)), suffix, env)
 
 
 class NullCmdGenerator:


Test patch:
diff --git a/test/CPPDEFINES/basic.py b/test/CPPDEFINES/basic.py
--- a/test/CPPDEFINES/basic.py
+++ b/test/CPPDEFINES/basic.py
@@ -38,12 +38,13 @@
     ['x', 'y', 'z'],
     ['x', ['y', 123], 'z', ('int', '$INTEGER')],
     { 'c' : 3, 'b': None, 'a' : 1 },
+    "${TESTDEFS}",
 ]
 for i in test_list:
-    env = Environment(CPPDEFPREFIX='-D', CPPDEFSUFFIX='', INTEGER=0)
+    env = Environment(CPPDEFPREFIX='-D', CPPDEFSUFFIX='', INTEGER=0, TESTDEFS=["FOO", "BAR=1"])
     print(env.Clone(CPPDEFINES=i).subst('$_CPPDEFFLAGS'))
 for i in test_list:
-    env = Environment(CPPDEFPREFIX='|', CPPDEFSUFFIX='|', INTEGER=1)
+    env = Environment(CPPDEFPREFIX='|', CPPDEFSUFFIX='|', INTEGER=1, TESTDEFS=["FOO", "BAR=1"])
     print(env.Clone(CPPDEFINES=i).subst('$_CPPDEFFLAGS'))
 """)
 
@@ -53,10 +54,12 @@
 -Dx -Dy -Dz
 -Dx -Dy=123 -Dz -Dint=0
 -Da=1 -Db -Dc=3
+-DFOO -DBAR=1
 |xyz|
 |x| |y| |z|
 |x| |y=123| |z| |int=1|
 |a=1| |b| |c=3|
+|FOO| |BAR=1|
 """)
 
 test.run(arguments = '.', stdout=expect)