Repository: SCons/scons

Issue:
Inconsistent treatment of strings as CXXFLAGS
This issue was originally created at: 2009-04-10 23:48:43.
This issue was reported by: `russel`.
russel said at 2009-04-10 23:48:43
>The following raised on the email lists was responded to with a number of replies saying "its a bug", hence this issue.
> 
>I am seeing a difference in behaviour of SCons (Subversion Trunk) on Ubuntu 8.10 and Mac OS X 10.5.  In the SConscript (well actually a subsidiary SConstruct but I don't think that matters) I have:
```python
environment['CXXFLAGS'] += '-O3 -W -Wall -Wundef  -Wcast-align -Wno-unused-parameter -Wshadow -Wredundant-decls'
```
> 
>This works fine on Ubuntu but fails on Mac OS X.  On Mac OS X I have to have:
```python
environment['CXXFLAGS'] += ['-O3' , '-W' , '-Wall' , '-Wundef' , '-Wcast-align' , '-Wno-unused-parameter' , '-Wshadow' , '-Wredundant-decls']
```
> 
>which also works fine on Ubuntu.  Now in some sense the latter is clearly more correct, so the question is whether it is a bug that the former works at all on Ubuntu.
> 
>Thanks.

gregnoel said at 2009-04-15 20:31:11
>Bug party triage.

dirkbaechle said at 2014-07-13 09:08:32
>It would be cool if someone with access to a Mac could confirm whether this is still an issue in SCons >= 2.3.2, or not.
> 
>My suspicion is, that this got fixed in the meantime by initializing CXXFLAGS to a CLVar(''). Otherwise this could probably get marked as duplicate of issue #2962, and resolved that way.

bdbaddog said at 2014-07-20 18:33:10
>Still an issue on mac.

bdbaddog said at 2014-07-20 19:54:58
>Here's a dummy SConstruct to try this out.
> 
>Fails on both mac and ubuntu for me:

```python
import os.path
 
if not os.path.exists('test2399.cxx'):
    open('test2399.cxx','w').write("""
 
#include <stdio.h>
 
main()
{
    printf("Hello World");
}
""")
 
env=Environment()
 
if True:
    env['CXXFLAGS'] += '-O3 -W -Wall -Wundef  -Wcast-align -Wno-unused-parameter -Wshadow -Wredundant-decls'
else:
    env['CXXFLAGS'] += ['-O3' , '-W' , '-Wall' , '-Wundef' ,  '-Wcast-align', '-Wno-unused-parameter' , '-Wshadow' , '-Wredundant-decls']
 
env.Program('blah','test2399.cxx')
```




Discussion:
This is because `CLVar` has no `__iadd__` dunder method, which what's called for `+=`.  Thus it falls back to `UserList.__iadd__`, which has the irritating characteristic of if the RHS doesn't match the LHS type it converts to list - and that's what we're getting - a list made from a string, thus one-letter-per-element - before adding.

Resolving patch:
diff --git a/SCons/Util.py b/SCons/Util.py
--- a/SCons/Util.py
+++ b/SCons/Util.py
@@ -1048,22 +1048,38 @@ def Split(arg):
     else:
         return [arg]
 
+
 class CLVar(UserList):
     """A class for command-line construction variables.
 
-    This is a list that uses Split() to split an initial string along
-    white-space arguments, and similarly to split any strings that get
-    added.  This allows us to Do the Right Thing with Append() and
+    Forces the use of a list of strings, matching individual arguments
+    that will be issued on the command line.  Like UserList,
+    but the argument passed to __init__ will be processed by the
+    Split function, which includes special handling for string types -
+    they will be split into a list of words, not coereced directly
+    to a list.  The same happens if adding a string,
+    which allows us to Do the Right Thing with Append() and
     Prepend() (as well as straight Python foo = env['VAR'] + 'arg1
     arg2') regardless of whether a user adds a list or a string to a
     command-line construction variable.
+
+    Side effect: spaces will be stripped from individual string
+    arguments. If you need spaces preserved, pass strings containing
+    spaces inside a list argument.
     """
-    def __init__(self, seq = []):
-        UserList.__init__(self, Split(seq))
+
+    def __init__(self, seq=[]):
+        super().__init__(Split(seq))
+
     def __add__(self, other):
-        return UserList.__add__(self, CLVar(other))
+        return super().__add__(CLVar(other))
+
     def __radd__(self, other):
-        return UserList.__radd__(self, CLVar(other))
+        return super().__radd__(CLVar(other))
+
+    def __iadd__(self, other):
+        return super().__iadd__(CLVar(other))
+
     def __str__(self):
         return ' '.join(self.data)
 
diff --git a/SCons/UtilTests.py b/SCons/UtilTests.py
--- a/SCons/UtilTests.py
+++ b/SCons/UtilTests.py
@@ -550,101 +550,120 @@ def test_addPathIfNotExists(self):
 
     def test_CLVar(self):
         """Test the command-line construction variable class"""
-        f = SCons.Util.CLVar('a b')
 
-        r = f + 'c d'
+        # input to CLVar is a string - should be split
+        f = SCons.Util.CLVar('aa bb')
+
+        r = f + 'cc dd'
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a', 'b', 'c', 'd'], r.data
-        assert str(r) == 'a b c d', str(r)
+        assert r.data == ['aa', 'bb', 'cc', 'dd'], r.data
+        assert str(r) == 'aa bb cc dd', str(r)
 
-        r = f + ' c d'
+        r = f + ' cc dd'
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a', 'b', 'c', 'd'], r.data
-        assert str(r) == 'a b c d', str(r)
+        assert r.data == ['aa', 'bb', 'cc', 'dd'], r.data
+        assert str(r) == 'aa bb cc dd', str(r)
 
-        r = f + ['c d']
+        r = f + ['cc dd']
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a', 'b', 'c d'], r.data
-        assert str(r) == 'a b c d', str(r)
+        assert r.data == ['aa', 'bb', 'cc dd'], r.data
+        assert str(r) == 'aa bb cc dd', str(r)
 
-        r = f + [' c d']
+        r = f + [' cc dd']
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a', 'b', ' c d'], r.data
-        assert str(r) == 'a b  c d', str(r)
+        assert r.data == ['aa', 'bb', ' cc dd'], r.data
+        assert str(r) == 'aa bb  cc dd', str(r)
 
-        r = f + ['c', 'd']
+        r = f + ['cc', 'dd']
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a', 'b', 'c', 'd'], r.data
-        assert str(r) == 'a b c d', str(r)
+        assert r.data == ['aa', 'bb', 'cc', 'dd'], r.data
+        assert str(r) == 'aa bb cc dd', str(r)
 
-        r = f + [' c', 'd']
+        r = f + [' cc', 'dd']
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a', 'b', ' c', 'd'], r.data
-        assert str(r) == 'a b  c d', str(r)
+        assert r.data == ['aa', 'bb', ' cc', 'dd'], r.data
+        assert str(r) == 'aa bb  cc dd', str(r)
 
-        f = SCons.Util.CLVar(['a b'])
+        # input to CLVar is a list of one string, should not be split
+        f = SCons.Util.CLVar(['aa bb'])
 
-        r = f + 'c d'
+        r = f + 'cc dd'
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a b', 'c', 'd'], r.data
-        assert str(r) == 'a b c d', str(r)
+        assert r.data == ['aa bb', 'cc', 'dd'], r.data
+        assert str(r) == 'aa bb cc dd', str(r)
 
-        r = f + ' c d'
+        r = f + ' cc dd'
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a b', 'c', 'd'], r.data
-        assert str(r) == 'a b c d', str(r)
+        assert r.data == ['aa bb', 'cc', 'dd'], r.data
+        assert str(r) == 'aa bb cc dd', str(r)
 
-        r = f + ['c d']
+        r = f + ['cc dd']
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a b', 'c d'], r.data
-        assert str(r) == 'a b c d', str(r)
+        assert r.data == ['aa bb', 'cc dd'], r.data
+        assert str(r) == 'aa bb cc dd', str(r)
 
-        r = f + [' c d']
+        r = f + [' cc dd']
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a b', ' c d'], r.data
-        assert str(r) == 'a b  c d', str(r)
+        assert r.data == ['aa bb', ' cc dd'], r.data
+        assert str(r) == 'aa bb  cc dd', str(r)
 
-        r = f + ['c', 'd']
+        r = f + ['cc', 'dd']
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a b', 'c', 'd'], r.data
-        assert str(r) == 'a b c d', str(r)
+        assert r.data == ['aa bb', 'cc', 'dd'], r.data
+        assert str(r) == 'aa bb cc dd', str(r)
 
-        r = f + [' c', 'd']
+        r = f + [' cc', 'dd']
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a b', ' c', 'd'], r.data
-        assert str(r) == 'a b  c d', str(r)
+        assert r.data == ['aa bb', ' cc', 'dd'], r.data
+        assert str(r) == 'aa bb  cc dd', str(r)
 
-        f = SCons.Util.CLVar(['a', 'b'])
+        # input to CLVar is a list of strings
+        f = SCons.Util.CLVar(['aa', 'bb'])
 
-        r = f + 'c d'
+        r = f + 'cc dd'
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a', 'b', 'c', 'd'], r.data
-        assert str(r) == 'a b c d', str(r)
+        assert r.data == ['aa', 'bb', 'cc', 'dd'], r.data
+        assert str(r) == 'aa bb cc dd', str(r)
 
-        r = f + ' c d'
+        r = f + ' cc dd'
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a', 'b', 'c', 'd'], r.data
-        assert str(r) == 'a b c d', str(r)
+        assert r.data == ['aa', 'bb', 'cc', 'dd'], r.data
+        assert str(r) == 'aa bb cc dd', str(r)
 
-        r = f + ['c d']
+        r = f + ['cc dd']
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a', 'b', 'c d'], r.data
-        assert str(r) == 'a b c d', str(r)
+        assert r.data == ['aa', 'bb', 'cc dd'], r.data
+        assert str(r) == 'aa bb cc dd', str(r)
 
-        r = f + [' c d']
+        r = f + [' cc dd']
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a', 'b', ' c d'], r.data
-        assert str(r) == 'a b  c d', str(r)
+        assert r.data == ['aa', 'bb', ' cc dd'], r.data
+        assert str(r) == 'aa bb  cc dd', str(r)
 
-        r = f + ['c', 'd']
+        r = f + ['cc', 'dd']
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a', 'b', 'c', 'd'], r.data
-        assert str(r) == 'a b c d', str(r)
+        assert r.data == ['aa', 'bb', 'cc', 'dd'], r.data
+        assert str(r) == 'aa bb cc dd', str(r)
 
-        r = f + [' c', 'd']
+        r = f + [' cc', 'dd']
         assert isinstance(r, SCons.Util.CLVar), type(r)
-        assert r.data == ['a', 'b', ' c', 'd'], r.data
-        assert str(r) == 'a b  c d', str(r)
+        assert r.data == ['aa', 'bb', ' cc', 'dd'], r.data
+        assert str(r) == 'aa bb  cc dd', str(r)
+
+        # make sure inplace adding a string works as well (issue 2399)
+        # UserList would convert the string to a list of chars
+        f = SCons.Util.CLVar(['aa', 'bb'])
+        f += 'cc dd'
+        assert isinstance(f, SCons.Util.CLVar), type(f)
+        assert f.data == ['aa', 'bb', 'cc', 'dd'], f.data
+        assert str(f) == 'aa bb cc dd', str(f)
+
+        f = SCons.Util.CLVar(['aa', 'bb'])
+        f += ' cc dd'
+        assert isinstance(f, SCons.Util.CLVar), type(f)
+        assert f.data == ['aa', 'bb', 'cc', 'dd'], f.data
+        assert str(f) == 'aa bb cc dd', str(f)
+
 
     def test_Selector(self):
         """Test the Selector class"""


Test patch: