Repository: spack/spack

Issue:
spack reorders cflags specified in compilers.yaml making them invalid
```
bash-4.2$ grep cflags ~/.spack/linux/compilers.yaml
      cflags: -O3 -fp-model fast -xMIC-AVX512
bash-4.2$ spack env numactl |grep CFLAGS
CFLAGS=-O3 fast -xMIC-AVX512 -fp-model
bash-4.2$ icc -O3 -fp-model fast -xMIC-AVX512 ~/petsc/src/benchmarks/sizeof.c  && echo sucess!
sucess!
bash-4.2$ icc -O3 fast -xMIC-AVX512 -fp-model ~/petsc/src/benchmarks/sizeof.c && echo sucess!
icc: error #10236: File not found:  'fast'
icc: command line warning #10159: invalid argument for option '-fp-model'
icc: command line error: no files specified; for help type "icc -help"
bash-4.2$ 

```


Discussion:
From what I can tell (e.g. looking at https://software.intel.com/en-us/node/522979) you can't set options like

`-fp-model=fast`

and must set `-fp-model fast`.

@balay I'm assuming that reordering the flags is acceptable provided that `-fp-model fast` is "kept together" and in that order. So the following should work:

`-fp-model fast -O3 -xMIC-AVX512`
`-xMIC-AVX512 -O3 -fp-model fast`

Resolving patch:
diff --git a/lib/spack/spack/compiler.py b/lib/spack/spack/compiler.py
--- a/lib/spack/spack/compiler.py
+++ b/lib/spack/spack/compiler.py
@@ -65,6 +65,27 @@ def dumpversion(compiler_path):
     return get_compiler_version(compiler_path, '-dumpversion')
 
 
+def tokenize_flags(flags_str):
+    """Given a compiler flag specification as a string, this returns a list
+       where the entries are the flags. For compiler options which set values
+       using the syntax "-flag value", this function groups flags and their
+       values together. Any token not preceded by a "-" is considered the
+       value of a prior flag."""
+    tokens = flags_str.split()
+    if not tokens:
+        return []
+    flag = tokens[0]
+    flags = []
+    for token in tokens[1:]:
+        if not token.startswith('-'):
+            flag += ' ' + token
+        else:
+            flags.append(flag)
+            flag = token
+    flags.append(flag)
+    return flags
+
+
 class Compiler(object):
     """This class encapsulates a Spack "compiler", which includes C,
        C++, and Fortran compilers.  Subclasses should implement
@@ -147,7 +168,7 @@ def check(exe):
         for flag in spack.spec.FlagMap.valid_compiler_flags():
             value = kwargs.get(flag, None)
             if value is not None:
-                self.flags[flag] = value.split()
+                self.flags[flag] = tokenize_flags(value)
 
     @property
     def version(self):
diff --git a/lib/spack/spack/concretize.py b/lib/spack/spack/concretize.py
--- a/lib/spack/spack/concretize.py
+++ b/lib/spack/spack/concretize.py
@@ -366,6 +366,9 @@ def concretize_compiler_flags(self, spec):
                 nearest_flags = set(nearest.compiler_flags.get(flag, []))
                 flags = set(spec.compiler_flags.get(flag, []))
                 if (nearest_flags - flags):
+                    # TODO: these set operations may reorder the flags, which
+                    # for some orders of flags can be invalid. See:
+                    # https://github.com/spack/spack/issues/6154#issuecomment-342365573
                     spec.compiler_flags[flag] = list(nearest_flags | flags)
                     ret = True
             except StopIteration:
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -1122,7 +1122,7 @@ def _add_flag(self, name, value):
             self._set_architecture(target=value)
         elif name in valid_flags:
             assert(self.compiler_flags is not None)
-            self.compiler_flags[name] = value.split()
+            self.compiler_flags[name] = spack.compiler.tokenize_flags(value)
         else:
             # FIXME:
             # All other flags represent variants. 'foo=true' and 'foo=false'


Test patch:
diff --git a/lib/spack/spack/test/compilers.py b/lib/spack/spack/test/compilers.py
--- a/lib/spack/spack/test/compilers.py
+++ b/lib/spack/spack/test/compilers.py
@@ -47,3 +47,23 @@ def test_all_compilers(self):
         filtered = [x for x in all_compilers if str(x.spec) == 'clang@3.3']
         filtered = [x for x in filtered if x.operating_system == 'SuSE11']
         assert len(filtered) == 1
+
+
+def test_compiler_flags_from_config_are_grouped():
+    compiler_entry = {
+        'spec': 'intel@17.0.2',
+        'operating_system': 'foo-os',
+        'paths': {
+            'cc': 'cc-path',
+            'cxx': 'cxx-path',
+            'fc': None,
+            'f77': None
+        },
+        'flags': {
+            'cflags': '-O0 -foo-flag foo-val'
+        },
+        'modules': None
+    }
+
+    compiler = compilers.compiler_from_config_entry(compiler_entry)
+    assert any(x == '-foo-flag foo-val' for x in compiler.flags['cflags'])
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -203,6 +203,12 @@ def test_different_compilers_get_different_flags(self):
         assert set(client.compiler_flags['fflags']) == set(['-O0'])
         assert not set(cmake.compiler_flags['fflags'])
 
+    def test_compiler_flags_from_user_are_grouped(self):
+        spec = Spec('a%gcc cflags="-O -foo-flag foo-val" platform=test')
+        spec.concretize()
+        cflags = spec.compiler_flags['cflags']
+        assert any(x == '-foo-flag foo-val' for x in cflags)
+
     def concretize_multi_provider(self):
         s = Spec('mpileaks ^multi-provider-mpi@3.0')
         s.concretize()