Repository: mesonbuild/meson

Issue:
empty -D flag passed to g++ causes build to fail
meson 0.51.0 generates this compile command and the resulting error:

```
c++ -Imeson-out/25a6634@@alphabot@exe -Isrc -I../src -I/usr/include/libdrm -I/usr/include -I/usr/include/SDL2 -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/harfbuzz -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -Wnon-virtual-dtor -std=c++17 -g -D_REENTRANT -D -MD -MQ 'meson-out/25a6634@@alphabot@exe/main.cpp.o' -MF 'meson-out/25a6634@@alphabot@exe/main.cpp.o.d' -o 'meson-out/25a6634@@alphabot@exe/main.cpp.o' -c ../src/main.cpp
cc1plus: error: to generate dependencies you must specify either ‘-M’ or ‘-MM’
```

The error is caused by the empty -D after -D_REENTRANT which confuses g++ 9.1.0.

the meson.build file looks like this:
```
libgl         = dependency('GL')
libpng        = dependency('PNG')
libsdl2       = dependency('SDL2')
libsdl2_mixer = dependency('SDL2_mixer')
libfreetype2  = dependency('freetype2')

executable('alphabot',
           'main.cpp',
           implicit_include_directories: true,
           dependencies: [libgl, libpng, libsdl2, libsdl2_mixer, libfreetype2],
           #cpp_args : '-DTEST',
           install: true)
```

Uncommenting #cpp_args : '-DTEST' will result in "-D_REENTRANT -D -DTEST" and allow the compilation to succeed.


Discussion:
That `-D` argument is most likely coming from some dependency. We should not add those ourselves ('-D_REENTRANT` definitely comes from a dependency, probably SDL). Can you check what arguments your deps provide (by invoking pkg-config manually)?
pkg-config doesn't show anything suspicious:
```
pkg-config --cflags {gl,libpng,sdl2,SDL2_mixer,freetype2}
-I/usr/include/libdrm -I/usr/include/libpng16 -I/usr/include/SDL2 -D_REENTRANT -I/usr/include/freetype2 -I/usr/include/harfbuzz -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include
```

By removing the dependencies one by one I could pin down the error to being caused by the PNG library.
```
pkg-config --libs --cflags libpng
-I/usr/include/libpng16 -lpng16 -lz
```
But I don't see where the empty define comes from.
I discovered that using dependency('libpng') instead of dependency('PNG') works.

With PNG meson informed me:
Run-time dependency png found: YES 1.6.37
With libpng it says:
Run-time dependency libpng found: YES 1.6.37

but at some point it goes wrong.
I'm currently running into a similar problem with an erroneous empty `-D` flag on 0.51.0 (and master) caused by [this commit](https://github.com/mesonbuild/meson/commit/eaa232987e3410198fa3a1eb422ca5b8a3baba8c) from #5436 (via git bisect), and it comes from the CMake dependency lookup.

@baraclese Can you check the `<builddir>/meson-logs/meson-log.txt` when you use `dependency('PNG')` and see if it uses CMake in that case? If so, we likely have the same problem.
In fact, I realized I could test it easily myself. Here's the log for `dependency('PNG')` on Meson master:

```
Guessed CMake target 'PNG::PNG'
CMake TARGET:
  -- name:      PNG::PNG
  -- type:      UNKNOWN
  -- properies: {
      'INTERFACE_COMPILE_DEFINITIONS': ['']
      'INTERFACE_INCLUDE_DIRECTORIES': ['/nix/store/jwhcj2djpsr3hppjd5wbc427mjckyrp0-libpng-apng-1.6.37-dev/include', '/nix/store/khw9zwxs9p2p53wp6a7njyia08dkzdxf-zlib-1.2.11-dev/include']
      'IMPORTED_LOCATION': ['/nix/store/scx7grksizdy2kxaxmhwf1wmdwp6m545-libpng-apng-1.6.37/lib/libpng.so']
      'IMPORTED_CONFIGURATIONS': ['RELEASE']
      'IMPORTED_LOCATION_RELEASE': ['/nix/store/scx7grksizdy2kxaxmhwf1wmdwp6m545-libpng-apng-1.6.37/lib/libpng.so']
     }
Include Dirs:         ['/nix/store/jwhcj2djpsr3hppjd5wbc427mjckyrp0-libpng-apng-1.6.37-dev/include', '/nix/store/khw9zwxs9p2p53wp6a7njyia08dkzdxf-zlib-1.2.11-dev/include']
Compiler Definitions: ['-D']
Compiler Options:     []
Libraries:            ['/nix/store/scx7grksizdy2kxaxmhwf1wmdwp6m545-libpng-apng-1.6.37/lib/libpng.so']
Run-time dependency png found: YES 1.6.37
```

Notice how it parsed out `'INTERFACE_COMPILE_DEFINITIONS': ['']` which becomes `-D` followed by that empty string. According to bisect for my case, this was introduced by the commit I linked before. In this PNG case, it looks like it was introduced by [another commit](https://github.com/mesonbuild/meson/commit/31ab9627533e1ae0a024c36226f703ac9ceab0a2) in the same PR.
Notice that the first commit removed [this line](https://github.com/mesonbuild/meson/commit/eaa232987e3410198fa3a1eb422ca5b8a3baba8c#diff-bc427959078b2a6a3516350d36b823abL1672) and the second commit removed [this line](https://github.com/mesonbuild/meson/commit/31ab9627533e1ae0a024c36226f703ac9ceab0a2#diff-bc427959078b2a6a3516350d36b823abL1707) both of which explicitly filtered out empty strings:

```python
propVal = list(filter(lambda x: len(x) > 0, propVal))
```

I don't know if simply re-adding this filter would be correct in all cases, so I'd like to know @dcbaker's thoughts on this.
I did some debug printing on the PNG case and found this line in the CMake trace that Meson parses:

```
/nix/store/kfkca61vsa4ij4y4mgxba79z1sy6kxkr-cmake-3.14.5/share/cmake-3.14/Modules/FindPNG.cmake(108):  set_target_properties(PNG::PNG PROPERTIES INTERFACE_COMPILE_DEFINITIONS  INTERFACE_INCLUDE_DIRECTORIES /nix/store/jwhcj2djpsr3hppjd5wbc427mjckyrp0-libpng-apng-1.6.37-dev/include;/nix/store/khw9zwxs9p2p53wp6a7njyia08dkzdxf-zlib-1.2.11-dev/include INTERFACE_LINK_LIBRARIES ZLIB::ZLIB )
```

The relevant part:

```
set_target_properties(PNG::PNG PROPERTIES INTERFACE_COMPILE_DEFINITIONS  INTERFACE_INCLUDE_DIRECTORIES
```

Note carefully the two spaces after `INTERFACE_COMPILE_DEFINITIONS`. As far as I can tell, this part of the line gets passed to a `.split(' ')` which does this in Python:

```python
>>> 'PNG::PNG PROPERTIES INTERFACE_COMPILE_DEFINITIONS  INTERFACE_INCLUDE_DIRECTORIES'.split(' ')
['PNG::PNG', 'PROPERTIES', 'INTERFACE_COMPILE_DEFINITIONS', '', 'INTERFACE_INCLUDE_DIRECTORIES']
```

So if we don't either split differently or filter these out, we'll have empty strings in the lists. I don't know CMake so I don't know the semantically correct interpretation.
In my personal case, I was using `dependency('KF5WidgetsAddons')` which depends on `Qt5::Core` which ended up with three different empty string problems:

```
      'INTERFACE_COMPILE_DEFINITIONS': ['QT_CORE_LIB', '']
      'INTERFACE_LINK_LIBRARIES': ['']
      'IMPORTED_LINK_INTERFACE_LIBRARIES_RELEASE': ['']
```

### INTERFACE_COMPILE_DEFINITIONS

The first one happens a bit differently from the PNG case. We see this line:

```
set_property(TARGET Qt5::Core APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS $<$<NOT:$<CONFIG:Debug>>:QT_NO_DEBUG> )
```

Which Meson lexes to:

```
set_property(['TARGET', 'Qt5::Core', 'APPEND', 'PROPERTY', 'INTERFACE_COMPILE_DEFINITIONS', ''])
```

I don't know about the `$<>` syntax, but I can see that they are explicitly removed by the CMake trace lexer [here](https://github.com/mesonbuild/meson/blob/4751f6d854a948c49dec3157d9e38b198f0e3ee8/mesonbuild/cmake/traceparser.py#L399). However, in this case that would clearly leave behind another empty string in the `args` list.

### INTERFACE_LINK_LIBRARIES & IMPORTED_LINK_INTERFACE_LIBRARIES_RELEASE

The second and third ones happen like the PNG case, with double spaces (both occur in this one trace line):

```
set_target_properties(Qt5::Core PROPERTIES INTERFACE_LINK_LIBRARIES  IMPORTED_LOCATION_RELEASE /nix/store/inhpfzny3lgm94ghddbd1vj1qz9s8z29-qtbase-5.12.0/lib/libQt5Core.so.5.12.0 IMPORTED_SONAME_RELEASE libQt5Core.so.5 IMPORTED_LINK_INTERFACE_LIBRARIES_RELEASE  )
```

Resolving patch:
diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py
--- a/mesonbuild/dependencies/base.py
+++ b/mesonbuild/dependencies/base.py
@@ -1290,15 +1290,18 @@ def _detect_dep(self, name: str, modules: List[Tuple[str, bool]], args: List[str
 
         # Failed to guess a target --> try the old-style method
         if len(modules) == 0:
-            incDirs = self.traceparser.get_cmake_var('PACKAGE_INCLUDE_DIRS')
-            defs = self.traceparser.get_cmake_var('PACKAGE_DEFINITIONS')
-            libs = self.traceparser.get_cmake_var('PACKAGE_LIBRARIES')
+            incDirs = [x for x in self.traceparser.get_cmake_var('PACKAGE_INCLUDE_DIRS') if x]
+            defs = [x for x in self.traceparser.get_cmake_var('PACKAGE_DEFINITIONS') if x]
+            libs = [x for x in self.traceparser.get_cmake_var('PACKAGE_LIBRARIES') if x]
 
             # Try to use old style variables if no module is specified
             if len(libs) > 0:
                 self.compile_args = list(map(lambda x: '-I{}'.format(x), incDirs)) + defs
                 self.link_args = libs
                 mlog.debug('using old-style CMake variables for dependency {}'.format(name))
+                mlog.debug('Include Dirs:         {}'.format(incDirs))
+                mlog.debug('Compiler Definitions: {}'.format(defs))
+                mlog.debug('Libraries:            {}'.format(libs))
                 return
 
             # Even the old-style approach failed. Nothing else we can do here
@@ -1340,39 +1343,37 @@ def _detect_dep(self, name: str, modules: List[Tuple[str, bool]], args: List[str
                 mlog.debug(tgt)
 
                 if 'INTERFACE_INCLUDE_DIRECTORIES' in tgt.properies:
-                    incDirs += tgt.properies['INTERFACE_INCLUDE_DIRECTORIES']
+                    incDirs += [x for x in tgt.properies['INTERFACE_INCLUDE_DIRECTORIES'] if x]
 
                 if 'INTERFACE_COMPILE_DEFINITIONS' in tgt.properies:
-                    tempDefs = list(tgt.properies['INTERFACE_COMPILE_DEFINITIONS'])
-                    tempDefs = list(map(lambda x: '-D{}'.format(re.sub('^-D', '', x)), tempDefs))
-                    compileDefinitions += tempDefs
+                    compileDefinitions += ['-D' + re.sub('^-D', '', x) for x in tgt.properies['INTERFACE_COMPILE_DEFINITIONS'] if x]
 
                 if 'INTERFACE_COMPILE_OPTIONS' in tgt.properies:
-                    compileOptions += tgt.properies['INTERFACE_COMPILE_OPTIONS']
+                    compileOptions += [x for x in tgt.properies['INTERFACE_COMPILE_OPTIONS'] if x]
 
                 if 'IMPORTED_CONFIGURATIONS' in tgt.properies:
-                    cfgs = tgt.properies['IMPORTED_CONFIGURATIONS']
+                    cfgs = [x for x in tgt.properies['IMPORTED_CONFIGURATIONS'] if x]
                     cfg = cfgs[0]
 
                 if 'RELEASE' in cfgs:
                     cfg = 'RELEASE'
 
                 if 'IMPORTED_IMPLIB_{}'.format(cfg) in tgt.properies:
-                    libraries += tgt.properies['IMPORTED_IMPLIB_{}'.format(cfg)]
+                    libraries += [x for x in tgt.properies['IMPORTED_IMPLIB_{}'.format(cfg)] if x]
                 elif 'IMPORTED_IMPLIB' in tgt.properies:
-                    libraries += tgt.properies['IMPORTED_IMPLIB']
+                    libraries += [x for x in tgt.properies['IMPORTED_IMPLIB'] if x]
                 elif 'IMPORTED_LOCATION_{}'.format(cfg) in tgt.properies:
-                    libraries += tgt.properies['IMPORTED_LOCATION_{}'.format(cfg)]
+                    libraries += [x for x in tgt.properies['IMPORTED_LOCATION_{}'.format(cfg)] if x]
                 elif 'IMPORTED_LOCATION' in tgt.properies:
-                    libraries += tgt.properies['IMPORTED_LOCATION']
+                    libraries += [x for x in tgt.properies['IMPORTED_LOCATION'] if x]
 
                 if 'INTERFACE_LINK_LIBRARIES' in tgt.properies:
-                    otherDeps += tgt.properies['INTERFACE_LINK_LIBRARIES']
+                    otherDeps += [x for x in tgt.properies['INTERFACE_LINK_LIBRARIES'] if x]
 
                 if 'IMPORTED_LINK_DEPENDENT_LIBRARIES_{}'.format(cfg) in tgt.properies:
-                    otherDeps += tgt.properies['IMPORTED_LINK_DEPENDENT_LIBRARIES_{}'.format(cfg)]
+                    otherDeps += [x for x in tgt.properies['IMPORTED_LINK_DEPENDENT_LIBRARIES_{}'.format(cfg)] if x]
                 elif 'IMPORTED_LINK_DEPENDENT_LIBRARIES' in tgt.properies:
-                    otherDeps += tgt.properies['IMPORTED_LINK_DEPENDENT_LIBRARIES']
+                    otherDeps += [x for x in tgt.properies['IMPORTED_LINK_DEPENDENT_LIBRARIES'] if x]
 
                 for j in otherDeps:
                     if j in self.traceparser.targets:
@@ -1391,7 +1392,7 @@ def _detect_dep(self, name: str, modules: List[Tuple[str, bool]], args: List[str
         mlog.debug('Compiler Options:     {}'.format(compileOptions))
         mlog.debug('Libraries:            {}'.format(libraries))
 
-        self.compile_args = compileOptions + compileDefinitions + list(map(lambda x: '-I{}'.format(x), incDirs))
+        self.compile_args = compileOptions + compileDefinitions + ['-I{}'.format(x) for x in incDirs]
         self.link_args = libraries
 
     def _setup_cmake_dir(self, cmake_file: str) -> str:


Test patch:
diff --git a/test cases/linuxlike/13 cmake dependency/cmake/FindImportedTarget.cmake b/test cases/linuxlike/13 cmake dependency/cmake/FindImportedTarget.cmake
new file mode 100644
--- /dev/null
+++ b/test cases/linuxlike/13 cmake dependency/cmake/FindImportedTarget.cmake	
@@ -0,0 +1,13 @@
+find_package(ZLIB)
+
+if(ZLIB_FOUND OR ZLIB_Found)
+  set(ImportedTarget_FOUND ON)
+  add_library(mesonTestLibDefs UNKNOWN IMPORTED)
+  set_property(TARGET mesonTestLibDefs PROPERTY IMPORTED_LOCATION ${ZLIB_LIBRARY})
+  set_property(TARGET mesonTestLibDefs PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${ZLIB_INCLUDE_DIR})
+  set_property(TARGET mesonTestLibDefs APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS REQUIRED_MESON_FLAG1)
+  set_property(TARGET mesonTestLibDefs APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS $<$<NOT:$<CONFIG:Debug>>:QT_NO_DEBUG>) # Error empty string
+  set_property(TARGET mesonTestLibDefs APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS REQUIRED_MESON_FLAG2)
+else()
+  set(ImportedTarget_FOUND OFF)
+endif()
diff --git a/test cases/linuxlike/13 cmake dependency/meson.build b/test cases/linuxlike/13 cmake dependency/meson.build
--- a/test cases/linuxlike/13 cmake dependency/meson.build	
+++ b/test cases/linuxlike/13 cmake dependency/meson.build	
@@ -42,6 +42,12 @@ depm1 = dependency('SomethingLikeZLIB', required : true, method : 'cmake', cmake
 depm2 = dependency('SomethingLikeZLIB', required : true, method : 'cmake', cmake_module_path : ['cmake'])
 depm3 = dependency('SomethingLikeZLIB', required : true, cmake_module_path : 'cmake')
 
+# Test some edge cases with spaces, etc.
+
+testDep = dependency('ImportedTarget', required : true, method : 'cmake', cmake_module_path : 'cmake', modules: 'mesonTestLibDefs')
+testFlagSet = executable('testFlagSet', ['testFlagSet.c'], dependencies: [testDep])
+test('testFlagSetTest', testFlagSet)
+
 # Try to compile a test that takes a dep and an include_directories
 
 cc = meson.get_compiler('c')
diff --git a/test cases/linuxlike/13 cmake dependency/testFlagSet.c b/test cases/linuxlike/13 cmake dependency/testFlagSet.c
new file mode 100644
--- /dev/null
+++ b/test cases/linuxlike/13 cmake dependency/testFlagSet.c	
@@ -0,0 +1,18 @@
+#include<stdio.h>
+#include<zlib.h>
+
+#ifndef REQUIRED_MESON_FLAG1
+#error "REQUIRED_MESON_FLAG1 not set"
+#endif
+
+#ifndef REQUIRED_MESON_FLAG2
+#error "REQUIRED_MESON_FLAG2 not set"
+#endif
+
+int main(int argc, char *argv[]) {
+  printf("Hello World\n");
+  void * something = deflate;
+  if(something != 0)
+    return 0;
+  return 1;
+}