Repository: mesonbuild/meson

Issue:
Compiler get_define returns unexpected string value
In specific circumstances, `get_define()` returns weird results, for example:

```
    mingw_version = cc.get_define('__MINGW64_VERSION_STR',
        prefix : '#include <_mingw.h>')

    message('Using MinGW-w64 ' + mingw_version)
```

This is defines as:

```c
#define __STRINGIFY(x) #x
#define __MINGW64_STRINGIFY(x) \
  __STRINGIFY(x)

#define __MINGW64_VERSION_MAJOR 6
#define __MINGW64_VERSION_MINOR 0
#define __MINGW64_VERSION_BUGFIX 0

// […]

#define __MINGW64_VERSION_STR	\
  __MINGW64_STRINGIFY(__MINGW64_VERSION_MAJOR) \
  "." \
  __MINGW64_STRINGIFY(__MINGW64_VERSION_MINOR) \
  "." \
  __MINGW64_STRINGIFY(__MINGW64_VERSION_BUGFIX)
```

So what I expected was `6.0.0` or `"6.0.0"` but instead it is `"6" "." "0" "." "0"`, which I guess is technically correct but at least for my usecase relatively useless/unreadable and I can't find an easy way to get the "normalized" string for this.


Discussion:
This should not be hard to fix, luckily. The `get_define()` implementation needs to be made aware of string concatenation rules. We could use `ast.literal_eval()` since Python has the same rules, or we could do it manually.

Resolving patch:
diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py
--- a/mesonbuild/compilers/c.py
+++ b/mesonbuild/compilers/c.py
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import subprocess, os.path
+import subprocess, os.path, re
 
 from .. import mlog
 from .. import coredata
@@ -567,8 +567,9 @@ def get_define(self, dname, prefix, env, extra_args, dependencies):
             if p.returncode != 0:
                 raise EnvironmentException('Could not get define {!r}'.format(dname))
         # Get the preprocessed value after the delimiter,
-        # minus the extra newline at the end
-        return p.stdo.split(delim + '\n')[-1][:-1]
+        # minus the extra newline at the end and
+        # merge string literals.
+        return CCompiler.concatenate_string_literals(p.stdo.split(delim + '\n')[-1][:-1])
 
     def get_return_value(self, fname, rtype, prefix, env, extra_args, dependencies):
         if rtype == 'string':
@@ -933,6 +934,16 @@ def has_multi_link_arguments(self, args, env):
         code = 'int main(int argc, char **argv) { return 0; }'
         return self.has_arguments(args, env, code, mode='link')
 
+    @staticmethod
+    def concatenate_string_literals(s):
+        pattern = re.compile(r'(?P<pre>.*([^\\]")|^")(?P<str1>([^\\"]|\\.)*)"\s+"(?P<str2>([^\\"]|\\.)*)(?P<post>".*)')
+        ret = s
+        m = pattern.match(ret)
+        while m:
+            ret = ''.join(m.group('pre', 'str1', 'str2', 'post'))
+            m = pattern.match(ret)
+        return ret
+
 class ClangCCompiler(ClangCompiler, CCompiler):
     def __init__(self, exelist, version, clang_type, is_cross, exe_wrapper=None, **kwargs):
         CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs)


Test patch:
diff --git a/test cases/common/140 get define/concat.h b/test cases/common/140 get define/concat.h
new file mode 100644
--- /dev/null
+++ b/test cases/common/140 get define/concat.h	
@@ -0,0 +1,24 @@
+#define __STRINGIFY(x) #x
+#define TEST_STRINGIFY(x) __STRINGIFY(x)
+
+#define TEST_VERSION_MAJOR 6
+#define TEST_VERSION_MINOR 0
+#define TEST_VERSION_BUGFIX 0
+
+#define TEST_VERSION_STR                                       \
+    TEST_STRINGIFY(TEST_VERSION_MAJOR)                         \
+    "." TEST_STRINGIFY(TEST_VERSION_MINOR) "." TEST_STRINGIFY( \
+        TEST_VERSION_BUGFIX)
+
+#define TEST_CONCAT_1 \
+    "ab"              \
+    "cd"              \
+    "ef"              \
+    ""
+#define TEST_CONCAT_2 1
+#define TEST_CONCAT_3 1 2 3
+#define TEST_CONCAT_4 "ab" 1 "cd"
+#define TEST_CONCAT_5 \
+    "ab\""            \
+    "cd"
+#define TEST_CONCAT_6 "ab\" \"cd"
diff --git a/test cases/common/140 get define/meson.build b/test cases/common/140 get define/meson.build
--- a/test cases/common/140 get define/meson.build	
+++ b/test cases/common/140 get define/meson.build	
@@ -80,4 +80,22 @@ foreach lang : ['c', 'cpp']
     have = cc.get_define('MESON_TEST_ISSUE_1665')
     assert(have == '1', 'MESON_TEST_ISSUE_1665 value is "@0@" instead of "1"'.format(have))
   endif
+
+  have = cc.get_define('TEST_VERSION_STR',
+        prefix : '#include <concat.h>', include_directories: include_directories('.'))
+  assert(have == '"6.0.0"', 'TEST_VERSION_STR value is "@0@" instead of ""6.0.0""'.format(have))
+
+  concat_examples = {
+  'TEST_CONCAT_1': '"abcdef"',
+  'TEST_CONCAT_2': '1',
+  'TEST_CONCAT_3': '1 2 3',
+  'TEST_CONCAT_4': '"ab" 1 "cd"',
+  'TEST_CONCAT_5': '"ab\"cd"',
+  'TEST_CONCAT_6': '"ab\" \"cd"',
+  }
+  foreach def,expected : concat_examples
+    have = cc.get_define(def,
+          prefix : '#include <concat.h>', include_directories: include_directories('.'))
+    assert(have == expected, '@0@ value is "@1@" instead of "@2@"'.format(def, have, expected))
+  endforeach
 endforeach