Repository: mesonbuild/meson

Issue:
LLVM statically links on < 4.0, dynamically links on >= 4.0
*grumble*

We need a fairly significant workaround to fix this (I'll write it). I just wanted to document this here so that if others ran into it they'd know.

Basically in 4.0 LLVM added a --link-static and --link-shared option to llvm-config, and a default mode (on linux distros this seems to be shared on the distros tested (arch, fedora, debian)). Before that it was static linking from llvm-config, and you had to figure out where LLVM-x.y.so was located yourself if you wanted shared-linking.

LLVM 5.0 also was the first version with symbol mangling, so static linking might be smarter for < 5.0 anyway.

*grumble* 


Discussion:

Resolving patch:
diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py
--- a/mesonbuild/dependencies/dev.py
+++ b/mesonbuild/dependencies/dev.py
@@ -16,6 +16,7 @@
 # development purposes, such as testing, debugging, etc..
 
 import os
+import re
 import shlex
 import shutil
 
@@ -136,8 +137,10 @@ def __init__(self, environment, kwargs):
         # It's necessary for LLVM <= 3.8 to use the C++ linker. For 3.9 and 4.0
         # the C linker works fine if only using the C API.
         super().__init__('llvm-config', environment, 'cpp', kwargs)
-        self.modules = []
+        self.provided_modules = []
+        self.required_modules = set()
         self.llvmconfig = None
+        self.static = kwargs.get('static', False)
         self.__best_found = None
         # FIXME: Support multiple version requirements ala PkgConfigDependency
         req_version = kwargs.get('version', None)
@@ -169,31 +172,90 @@ def __init__(self, environment, kwargs):
         # for users who want the patch version.
         self.version = out.strip().rstrip('svn')
 
-        p, out = Popen_safe(
-            [self.llvmconfig, '--libs', '--ldflags'])[:2]
+        p, out = Popen_safe([self.llvmconfig, '--components'])[:2]
         if p.returncode != 0:
-            raise DependencyException('Could not generate libs for LLVM.')
-        self.link_args = strip_system_libdirs(environment, shlex.split(out))
+            raise DependencyException('Could not generate modules for LLVM.')
+        self.provided_modules = shlex.split(out)
+
+        modules = stringlistify(extract_as_list(kwargs, 'modules'))
+        self.check_components(modules)
+        opt_modules = stringlistify(extract_as_list(kwargs, 'optional_modules'))
+        self.check_components(opt_modules, required=False)
+
         p, out = Popen_safe([self.llvmconfig, '--cppflags'])[:2]
         if p.returncode != 0:
             raise DependencyException('Could not generate includedir for LLVM.')
         cargs = mesonlib.OrderedSet(shlex.split(out))
         self.compile_args = list(cargs.difference(self.__cpp_blacklist))
 
-        p, out = Popen_safe([self.llvmconfig, '--components'])[:2]
+        if version_compare(self.version, '>= 3.9'):
+            self._set_new_link_args()
+        else:
+            self._set_old_link_args()
+        self.link_args = strip_system_libdirs(environment, self.link_args)
+
+    def _set_new_link_args(self):
+        """How to set linker args for LLVM versions >= 3.9"""
+        link_args = ['--link-static', '--system-libs'] if self.static else ['--link-shared']
+        p, out = Popen_safe(
+            [self.llvmconfig, '--libs', '--ldflags'] + link_args + list(self.required_modules))[:2]
         if p.returncode != 0:
-            raise DependencyException('Could not generate modules for LLVM.')
-        self.modules = shlex.split(out)
+            raise DependencyException('Could not generate libs for LLVM.')
+        self.link_args = shlex.split(out)
 
-        modules = stringlistify(extract_as_list(kwargs, 'modules'))
+    def _set_old_link_args(self):
+        """Setting linker args for older versions of llvm.
+
+        Old versions of LLVM bring an extra level of insanity with them.
+        llvm-config will provide the correct arguments for static linking, but
+        not for shared-linnking, we have to figure those out ourselves, because
+        of course we do.
+        """
+        if self.static:
+            p, out = Popen_safe(
+                [self.llvmconfig, '--libs', '--ldflags', '--system-libs'] + list(self.required_modules))[:2]
+            if p.returncode != 0:
+                raise DependencyException('Could not generate libs for LLVM.')
+            self.link_args = shlex.split(out)
+        else:
+            # llvm-config will provide arguments for static linking, so we get
+            # to figure out for ourselves what to link with. We'll do that by
+            # checking in the directory provided by --libdir for a library
+            # called libLLVM-<ver>.(so|dylib|dll)
+            p, out = Popen_safe([self.llvmconfig, '--libdir'])[:2]
+            if p.returncode != 0:
+                raise DependencyException('Could not generate libs for LLVM.')
+            libdir = out.strip()
+
+            expected_name = 'libLLVM-{}'.format(self.version)
+            re_name = re.compile(r'{}.(so|dll|dylib)'.format(expected_name))
+
+            for file_ in os.listdir(libdir):
+                if re_name.match(file_):
+                    self.link_args = ['-L{}'.format(libdir),
+                                      '-l{}'.format(os.path.splitext(file_.lstrip('lib'))[0])]
+                    break
+            else:
+                raise DependencyException(
+                    'Could not find a dynamically linkable library for LLVM.')
+
+    def check_components(self, modules, required=True):
+        """Check for llvm components (modules in meson terms).
+
+        The required option is whether the module is required, not whether LLVM
+        is required.
+        """
         for mod in sorted(set(modules)):
-            if mod not in self.modules:
-                mlog.log('LLVM module', mod, 'found:', mlog.red('NO'))
-                self.is_found = False
-                if self.required:
-                    raise DependencyException(
-                        'Could not find required LLVM Component: {}'.format(mod))
+            if mod not in self.provided_modules:
+                mlog.log('LLVM module', mod, 'found:', mlog.red('NO'),
+                         '(optional)' if not required else '')
+                if required:
+                    self.is_found = False
+                    if self.required:
+                        raise DependencyException(
+                            'Could not find required LLVM Component: {}'.format(mod))
             else:
+                self.required_modules.add(mod)
                 mlog.log('LLVM module', mod, 'found:', mlog.green('YES'))
 
     def check_llvmconfig(self, version_req):


Test patch:
diff --git a/test cases/frameworks/15 llvm/meson.build b/test cases/frameworks/15 llvm/meson.build
--- a/test cases/frameworks/15 llvm/meson.build	
+++ b/test cases/frameworks/15 llvm/meson.build	
@@ -1,21 +1,31 @@
 project('llvmtest', ['c', 'cpp'], default_options : ['c_std=c99'])
 
-llvm_dep = dependency(
-  'llvm',
-  modules : ['bitwriter', 'asmprinter', 'executionengine', 'target',
-             'mcjit', 'nativecodegen'],
-  required : true,
-)
-
 d = dependency('llvm', modules : 'not-found', required : false)
 assert(d.found() == false, 'not-found llvm module found')
 
 d = dependency('llvm', version : '<0.1', required : false)
 assert(d.found() == false, 'ancient llvm module found')
 
-executable('sum', 'sum.c',  dependencies : [
-  llvm_dep,
-  dependency('zlib'),
-  meson.get_compiler('c').find_library('dl', required : false),
-  dependency('tinfo'),
-  ])
+d = dependency('llvm', optional_modules : 'not-found', required : false)
+assert(d.found() == true, 'optional module stopped llvm from being found.')
+
+foreach static : [true, false]
+  llvm_dep = dependency(
+    'llvm',
+    modules : ['bitwriter', 'asmprinter', 'executionengine', 'target',
+               'mcjit', 'nativecodegen'],
+    required : true,
+    static : static,
+  )
+  name = static ? 'static' : 'dynamic'
+  executable(
+    'sum-@0@'.format(name),
+    'sum.c',
+    dependencies : [
+      llvm_dep,
+      dependency('zlib'),
+      dependency('glib-2.0'),
+      meson.get_compiler('c').find_library('dl', required : false),
+    ]
+  )
+endforeach