Repository: mesonbuild/meson

Issue:
Meson doesn't handle 'link_args' correctly for non-gnu D compilers
Sample config
```
project('test-project', 'd')

libabc_dep = dependency('libabc')

test_exe = executable('test', 'app.d',
                        #d_args: ['-I/usr/include', '-L/usr/lib'],
                        dependencies: libabc_dep
)
```
When compiling the `test_exe` target, the compiler fails to add the arguments specified in the pkgconfig (usually, `-I/usr/include` and `-L/usr/lib`). If I add the right arguments to `d_args`, as shown in the commented code, it works.

I have only tried to link dependencies provided by pkgconfig. I also tried compiling with both LDC and DMD, same result.


Discussion:
The issue is probably that pkg-config drops the system prefixes when outputting compile args and link args because most compilers search there by default.

This breaks for LCD and DMD because they don't search there, but I suspect that GDC will work fine.
I verified this. If you create a D target with a dependency on `glib-2.0` because it has both compiler and linker flags that are non-standard.

The flags get properly passed to the command line. Here is GDC:

    gdc -Idsimpleapp@exe -I. -I../abc -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -fdiagnostics-color=always -Wall -Wdeprecated -g -O0  -MD -MQ 'dsimpleapp@exe/app.d.o' -MF 'dsimpleapp@exe/app.d.o.deps' -o 'dsimpleapp@exe/app.d.o' -c ../abc/app.d

And ldc:

    ldc2 -Idsimpleapp@exe -I. -I../abc -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -enable-color -wi -g -O0   -of 'dsimpleapp@exe/app.d.o' -c ../abc/app.d

So this seems to be a case of LDC and DMD missing internal search directories?
This should've been fixed by:

```
commit 9da031c04c9905949c896a8fdc5b608acd6dde47
Author: Nirbheek Chauhan <nirbheek@centricular.com>
Date:   Mon Nov 6 15:42:09 2017 +0530

    dependencies: Force pkg-config to output -L args

    Sometimes pkg-config can decide that the libdir is a system library dir
    and must not be included in the output because that would mess up the
    library search order for pkg-config libraries that must be sourced from
    a non-system prefix.

    However, when we're doing manual searching, we always want to see the
    library directory even if it's the system path, otherwise we can't do
    manual searching at all.
```

Please test and reopen if the bug still exists.
The issue was that meson would add `-L-Lpath` instead of `-Lpath` resulting in the lib not being detected by the compiler.
@GoaLitiuM what should we do here? I remember that in the past `-L-L` was required for some D compilers, has that changed?
I'm not sure, I don't know enough pkg-config or D outside Windows, but I did notice the `translate_args_to_nongnu` function is called for both compile and linking arguments, so at least for the user added linker arguments, the extra `-L` should not be needed here. The translation of `-L` arguments should only be applied for linker arguments because in D compilers the `-L` argument is always the "pass to linker" argument.

In this case, `d_args: '-L/usr/lib'` should remain untouched. The correct place for the arguments should be in `link_args` instead, but the code does not make the distinction between those so the arguments are always translated.
@ljmf00 is experiencing the same issue.

Resolving patch:
diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py
--- a/mesonbuild/compilers/d.py
+++ b/mesonbuild/compilers/d.py
@@ -95,6 +95,9 @@ def needs_static_linker(self):
     def name_string(self):
         return ' '.join(self.exelist)
 
+    def get_exelist(self):
+        return self.exelist
+
     def get_linker_exelist(self):
         return self.exelist[:]
 
@@ -294,6 +297,11 @@ def translate_args_to_nongnu(cls, args):
                 # a linker search path.
                 dcargs.append('-L' + arg)
                 continue
+            elif arg.startswith('/') or arg.startswith('./'):
+                # absolute (or relative) paths passed to the linker may be static libraries
+                # or other objects that we need to link.
+                dcargs.append('-L' + arg)
+                continue
             elif arg.startswith('-mscrtlib='):
                 mscrtlib = arg[10:].lower()
 
@@ -309,6 +317,7 @@ def translate_args_to_nongnu(cls, args):
                         dcargs.append('-L/DEFAULTLIB:legacy_stdio_definitions.lib')
 
                 dcargs.append(arg)
+
                 continue
             dcargs.append(arg)
 
diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py
--- a/mesonbuild/dependencies/base.py
+++ b/mesonbuild/dependencies/base.py
@@ -24,6 +24,7 @@
 import shlex
 import shutil
 import textwrap
+import platform
 from enum import Enum
 from pathlib import PurePath
 
@@ -849,12 +850,13 @@ def __init__(self, name, environment, kwargs):
         super().__init__('dub', environment, 'd', kwargs)
         self.name = name
         self.compiler = super().get_compiler()
+        self.module_path = None
 
         if 'required' in kwargs:
             self.required = kwargs.get('required')
 
         if DubDependency.class_dubbin is None:
-            self.dubbin = self.check_dub()
+            self.dubbin = self._check_dub()
             DubDependency.class_dubbin = self.dubbin
         else:
             self.dubbin = DubDependency.class_dubbin
@@ -875,20 +877,33 @@ def __init__(self, name, environment, kwargs):
             self.is_found = False
             return
 
-        j = json.loads(res)
         comp = self.compiler.get_id().replace('llvm', 'ldc').replace('gcc', 'gdc')
+        packages = []
+        j = json.loads(res)
         for package in j['packages']:
+            packages.append(package['name'])
             if package['name'] == name:
-                if j['compiler'] != comp:
-                    msg = ['Dependency', mlog.bold(name), 'found but it was compiled with']
-                    msg += [mlog.bold(j['compiler']), 'and we are using', mlog.bold(comp)]
-                    mlog.error(*msg)
+                self.is_found = True
+
+                not_lib = True
+                if 'targetType' in package:
+                    if package['targetType'] == 'library':
+                        not_lib = False
+
+                if not_lib:
+                    mlog.error(mlog.bold(name), 'found but it isn\'t a library')
+                    self.is_found = False
+                    return
+
+                self.module_path = self._find_right_lib_path(package['path'], comp, j, True, package['targetFileName'])
+
+                if not os.path.exists(self.module_path):
+                    mlog.error(mlog.bold(name), 'found but it wasn\'t compiled with', mlog.bold(comp))
                     self.is_found = False
                     return
 
                 self.version = package['version']
                 self.pkg = package
-                break
 
         if self.pkg['targetFileName'].endswith('.a'):
             self.static = True
@@ -899,39 +914,93 @@ def __init__(self, name, environment, kwargs):
         for path in self.pkg['importPaths']:
             self.compile_args.append('-I' + os.path.join(self.pkg['path'], path))
 
-        self.link_args = []
+        self.link_args = self.raw_link_args = []
         for flag in self.pkg['lflags']:
             self.link_args.append(flag)
 
-        search_paths = []
-        search_paths.append(os.path.join(self.pkg['path'], self.pkg['targetPath']))
-        found, res = self.__search_paths(search_paths, self.pkg['targetFileName'])
-        for file in res:
-            self.link_args.append(file)
-
-        self.is_found = found
+        self.link_args.append(os.path.join(self.module_path, self.pkg['targetFileName']))
+
+        # Handle dependencies
+        libs = []
+
+        def add_lib_args(field_name, target):
+            if field_name in target['buildSettings']:
+                if os.name is 'nt':
+                    for lib in target['buildSettings'][field_name]:
+                        if lib not in target['buildSettings'][field_name]:
+                            libs.append(lib)
+                            # TODO: Use CCompiler.find_library to find the path of the .lib files
+                else: # posix and everything else
+                    for lib in target['buildSettings'][field_name]:
+                        if lib not in libs:
+                            libs.append(lib)
+                            pkgdep = PkgConfigDependency(lib, environment, {'required': 'true', 'silent': 'true'})
+                            for arg in pkgdep.get_compile_args():
+                                self.compile_args.append(arg)
+                            for arg in pkgdep.get_link_args():
+                                self.link_args.append(arg)
+                            for arg in pkgdep.get_link_args(raw=True):
+                                self.raw_link_args.append(arg)
+
+        for target in j['targets']:
+            if target['rootPackage'] in packages:
+                add_lib_args('libs', target)
+                add_lib_args('libs-{}'.format(platform.machine()), target)
+                for file in target['buildSettings']['linkerFiles']:
+                    self.link_args.append(self._find_right_lib_path(file, comp, j))
 
     def get_compiler(self):
         return self.compiler
 
-    def __search_paths(self, search_paths, target_file):
-        found = False
-        res = []
-        if target_file == '':
-            return True, res
-        for path in search_paths:
-                if os.path.isdir(path):
-                    for file in os.listdir(path):
-                        if file == target_file:
-                            res.append(os.path.join(path, file))
-                            found = True
-        return found, res
+    def _find_right_lib_path(self, default_path, comp, j, folder_only=False, file_name=''):
+        path = ''
+
+        module_build_path = lib_file_name = ''
+        if folder_only:
+            module_build_path = default_path
+            lib_file_name = file_name
+        else:
+            module_build_path = os.path.dirname(default_path)
+            lib_file_name = os.path.basename(default_path)
+
+        # Get D version implemented in the compiler
+        # gdc doesn't support this
+        ret, res = self._call_dubbin(['--version'])
+
+        if ret != 0:
+            mlog.error('Failed to run {!r}', mlog.bold(comp))
+            self.is_found = False
+            return
+
+        d_ver = re.search('v[0-9].[0-9][0-9][0-9].[0-9]', res) # Ex.: v2.081.2
+        if d_ver is not None:
+            d_ver = d_ver.group().rsplit('.', 1)[0].replace('v', '').replace('.', '') # Fix structure. Ex.: 2081
+        else:
+            d_ver = '' # gdc
+
+        # Ex.: library-debug-linux.posix-x86_64-ldc_2081-EF934983A3319F8F8FF2F0E107A363BA
+        build_name = 'library-{}-{}.{}-{}-{}_{}'.format(j['buildType'], j['platform'][0], j['platform'][1], platform.machine(), comp, d_ver)
+        for entry in os.listdir(os.path.join(module_build_path, '.dub', 'build')):
+            if entry.startswith(build_name):
+                for file in os.listdir(os.path.join(module_build_path, '.dub', 'build', entry)):
+                    if file == lib_file_name:
+                        if folder_only:
+                            path = os.path.join(module_build_path, '.dub', 'build', entry)
+                        else:
+                            path = os.path.join(module_build_path, '.dub', 'build', entry, lib_file_name)
+                        break
+
+        return path
 
     def _call_dubbin(self, args, env=None):
         p, out = Popen_safe(self.dubbin.get_command() + args, env=env)[0:2]
         return p.returncode, out.strip()
 
-    def check_dub(self):
+    def _call_copmbin(self, args, env=None):
+        p, out = Popen_safe(self.compiler.get_exelist() + args, env=env)[0:2]
+        return p.returncode, out.strip()
+
+    def _check_dub(self):
         dubbin = ExternalProgram('dub', silent=True)
         if dubbin.found():
             try:


Test patch:
diff --git a/test cases/failing/85 dub libray/meson.build b/test cases/failing/85 dub libray/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/failing/85 dub libray/meson.build	
@@ -0,0 +1,3 @@
+project('dub', 'd', meson_version: '0.48.0')
+
+dependency('dubtestproject', method: 'dub') # Not library (none)
diff --git a/test cases/failing/86 dub executable/meson.build b/test cases/failing/86 dub executable/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/failing/86 dub executable/meson.build	
@@ -0,0 +1,3 @@
+project('dub', 'd', meson_version: '0.48.0')
+
+dependency('dubtestproject:test1', method: 'dub') # Not library (executable)
diff --git a/test cases/failing/87 dub compiler/meson.build b/test cases/failing/87 dub compiler/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/failing/87 dub compiler/meson.build	
@@ -0,0 +1,3 @@
+project('dub', 'd', meson_version: '0.48.0')
+
+dependency('dubtestproject:test2', method: 'dub') # Compiler mismatch