Repository: mesonbuild/meson

Issue:
get_compiler_dirs() brokenness with gcc/clang on Windows
relevant code: https://github.com/mesonbuild/meson/blob/23f831a67497135091bcb44675ff5a7fb9ce8425/mesonbuild/compilers/clike.py#L174

get_compiler_dirs() currently assumes that the paths returned by the compiler are separated by `:`, but gcc gives:

```
$ gcc --print-search-dirs
install: C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.3.0/
programs: =C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.3.0/;C:/msys64/mingw64/bin/../lib/gcc/;C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.3.0/../../../../x86_64-w64-mingw32/bin/x86_64-w64-mingw32/8.3.0/;C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.3.0/../../../../x86_64-w64-mingw32/bin/
libraries: =C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.3.0/;C:/msys64/mingw64/bin/../lib/gcc/;C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.3.0/../../../../x86_64-w64-mingw32/lib/x86_64-w64-mingw32/8.3.0/;C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.3.0/../../../../x86_64-w64-mingw32/lib/../lib/;C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.3.0/../../../x86_64-w64-mingw32/8.3.0/;C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.3.0/../../../../lib/;C:/building/msys64/mingw64/x86_64-w64-mingw32/lib/x86_64-w64-mingw32/8.3.0/;C:/building/msys64/mingw64/x86_64-w64-mingw32/lib/../lib/;C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.3.0/../../../../x86_64-w64-mingw32/lib/;C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.3.0/../../../;C:/building/msys64/mingw64/x86_64-w64-mingw32/lib/
```

clang on the other hand gives:
```
$ clang --print-search-dirs
programs: =C:\msys64\mingw64\bin
libraries: =C:\msys64\mingw64\lib\clang\8.0.0:C:\msys64\mingw64\lib\gcc\x86_64-w64-mingw32\8.3.0:C:\msys64\mingw64\x86_64-w64-mingw32\lib:C:\msys64\mingw64\lib:C:\msys64\mingw64\x86_64-w64-mingw32/sys-root/mingw/lib
```

which seems broken, and I quickly found a matching upstream bug: https://reviews.llvm.org/D61121

The case triggering errors for me here was a meson project statically linking against a subproject which links against system libraries, and the paths for the system libs were bogus because of this.

edit: building pixman with mingw also failed because of this.


Discussion:
What would be the best way to select a path separator? check is_windows_compiler minus cygwin?

edit: mingw on Linux uses :, so maybe just os.pathsep except cygwin.

Resolving patch:
diff --git a/mesonbuild/compilers/clike.py b/mesonbuild/compilers/clike.py
--- a/mesonbuild/compilers/clike.py
+++ b/mesonbuild/compilers/clike.py
@@ -170,10 +170,22 @@ def _get_search_dirs(self, env):
             stdo = p.stdo
         return stdo
 
-    @staticmethod
-    def _split_fetch_real_dirs(pathstr, sep=':'):
-        paths = []
-        for p in pathstr.split(sep):
+    def _split_fetch_real_dirs(self, pathstr):
+        # We need to use the path separator used by the compiler for printing
+        # lists of paths ("gcc --print-search-dirs"). By default
+        # we assume it uses the platform native separator.
+        pathsep = os.pathsep
+
+        # clang uses ':' instead of ';' on Windows https://reviews.llvm.org/D61121
+        # so we need to repair things like 'C:\foo:C:\bar'
+        if pathsep == ';':
+            pathstr = re.sub(r':([^/\\])', r';\1', pathstr)
+
+        # pathlib treats empty paths as '.', so filter those out
+        paths = [p for p in pathstr.split(pathsep) if p]
+
+        result = []
+        for p in paths:
             # GCC returns paths like this:
             # /usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/lib
             # It would make sense to normalize them to get rid of the .. parts
@@ -185,15 +197,15 @@ def _split_fetch_real_dirs(pathstr, sep=':'):
             pobj = Path(p)
             unresolved = pobj.as_posix()
             if pobj.exists():
-                if unresolved not in paths:
-                    paths.append(unresolved)
+                if unresolved not in result:
+                    result.append(unresolved)
                 try:
                     resolved = Path(p).resolve().as_posix()
-                    if resolved not in paths:
-                        paths.append(resolved)
+                    if resolved not in result:
+                        result.append(resolved)
                 except FileNotFoundError:
                     pass
-        return tuple(paths)
+        return tuple(result)
 
     def get_compiler_dirs(self, env, name):
         '''
diff --git a/run_unittests.py b/run_unittests.py
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -1587,6 +1587,14 @@ def test_default_options_prefix_dependent_defaults(self):
                     self.assertEqual(value, expected[args][name])
             self.wipe()
 
+    def test_clike_get_library_dirs(self):
+        env = get_fake_env()
+        cc = env.detect_c_compiler(False)
+        for d in cc.get_library_dirs(env):
+            self.assertTrue(os.path.exists(d))
+            self.assertTrue(os.path.isdir(d))
+            self.assertTrue(os.path.isabs(d))
+
     def test_static_library_overwrite(self):
         '''
         Tests that static libraries are never appended to, always overwritten.


Test patch: