Repository: mesonbuild/meson

Issue:
LLVM dependency
LLVM has it's own special tool for configuration, `llvm-config`. It would be nice for `dependency` to wrap llvm config.

I'm planning to write such a class (tomorrow at this point), but I wanted to make sure that there was interest and no one had already done so and just never created a PR.


Discussion:
I can say I have a use for it: https://github.com/GNOME/gnome-builder/blob/master/plugins/clang/meson.build#L29-L53

Hopefully you can handle everything that does.
llvm-config returns different values for --cflags and --cxxflags, including the std. Does meson have a way to represent this?
Not at the moment. But I looked at what the commands output and the results are just plain terrible. As an example for C++ it returns `-std=c++0x` and then a few arguments later `-std=c++11`. It also has arguments such as `-Wall` `-O2` and `-g` and `-DNDEBUG`. A dependency provider should never, ever produce any of these (or some of the other flags there), it will screw up everything else. Behaving in this way is understandable if you are just using it for quick Makefile invocations but not as a part of a modern build system.

As an example GNUStep does the same thing and we filter out all of the troublesome flags. I guess we could do the same here but I'd prefer first filing a bug against LLVM to:

 - ship standard pkg-config files
 - if that is not possible, fix the output of `llvm-config` to get rid of those garbage arguments (because then what probably remains is the same for C and C++)

there is a bug for package config, it's been opened and ignored since 2011 https://bugs.llvm.org//show_bug.cgi?id=9405 (their ssl certificate is broken)

Wow, different levels of broken on different systems, I get `-O2` and `-O3`, but only `-std=c++11`, plus a bunch of other `-W` and `-D` flags we probably don't want.

I don't disagree that it would be great if LLVM would ship a package-config file, or if they would fix llvm-config to not return garabage, but we're still going to need to support old versions of llvm that are still broken for some time.

I think that we should pobably just filter out everything except the -f arguments from both the c and c++ flags and return those combined?
Okay, combining C and C++ flags gives warnings (at least with GCC and clang), but I do have a patch that mostly works.
File a MR and we'll go from there.

Resolving patch:
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -909,7 +909,14 @@ def get_clike_dynamic_linker(self):
         # Pick a compiler based on the language priority-order
         for l in clike_langs:
             if l in self.compilers or l in dep_langs:
-                return all_compilers[l]
+                try:
+                    return all_compilers[l]
+                except KeyError:
+                    raise MesonException(
+                        'Could not get a dynamic linker for build target {!r}. '
+                        'Requires a linker for language "{}", but that is not '
+                        'a project language.'.format(self.name, l))
+
         m = 'Could not get a dynamic linker for build target {!r}'
         raise AssertionError(m.format(self.name))
 
diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py
--- a/mesonbuild/dependencies.py
+++ b/mesonbuild/dependencies.py
@@ -22,6 +22,7 @@
 import re
 import sys
 import os, stat, glob, shutil
+import shlex
 import subprocess
 import sysconfig
 from enum import Enum
@@ -356,13 +357,16 @@ class WxDependency(Dependency):
     def __init__(self, environment, kwargs):
         Dependency.__init__(self, 'wx', kwargs)
         self.is_found = False
+        # FIXME: use version instead of modversion
         self.modversion = 'none'
         if WxDependency.wx_found is None:
             self.check_wxconfig()
         if not WxDependency.wx_found:
+            # FIXME: this message could be printed after Dependncy found
             mlog.log("Neither wx-config-3.0 nor wx-config found; can't detect dependency")
             return
 
+        # FIXME: This should print stdout and stderr using mlog.debug
         p, out = Popen_safe([self.wxc, '--version'])[0:2]
         if p.returncode != 0:
             mlog.log('Dependency wxwidgets found:', mlog.red('NO'))
@@ -382,10 +386,12 @@ def __init__(self, environment, kwargs):
             # wx-config seems to have a cflags as well but since it requires C++,
             # this should be good, at least for now.
             p, out = Popen_safe([self.wxc, '--cxxflags'])[0:2]
+            # FIXME: this error should only be raised if required is true
             if p.returncode != 0:
                 raise DependencyException('Could not generate cargs for wxwidgets.')
             self.cargs = out.split()
 
+            # FIXME: this error should only be raised if required is true
             p, out = Popen_safe([self.wxc, '--libs'] + self.requested_modules)[0:2]
             if p.returncode != 0:
                 raise DependencyException('Could not generate libs for wxwidgets.')
@@ -1607,6 +1613,131 @@ def __init__(self, environment, kwargs):
     def get_link_args(self):
         return []
 
+class LLVMDependency(Dependency):
+    """LLVM dependency.
+
+    LLVM uses a special tool, llvm-config, which has arguments for getting
+    c args, cxx args, and ldargs as well as version.
+    """
+
+    # Ordered list of llvm-config binaries to try. Start with base, then try
+    # newest back to oldest (3.5 is abitrary), and finally the devel version.
+    llvm_config_bins = [
+        'llvm-config', 'llvm-config-4.0', 'llvm-config-3.9', 'llvm-config39',
+        'llvm-config-3.8', 'llvm-config38', 'llvm-config-3.7', 'llvm-config37',
+        'llvm-config-3.6', 'llvm-config36', 'llvm-config-3.5', 'llvm-config35',
+        'llvm-config-devel',
+    ]
+    llvmconfig = None
+    _llvmconfig_found = False
+    __best_found = None
+
+    def __init__(self, environment, kwargs):
+        super().__init__('llvm-config', 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.
+        self.language = 'cpp'
+        self.cargs = []
+        self.libs = []
+        self.modules = []
+
+        required = kwargs.get('required', True)
+        req_version = kwargs.get('version', None)
+        if self.llvmconfig is None:
+            self.check_llvmconfig(req_version)
+        if not self._llvmconfig_found:
+            if self.__best_found is not None:
+                mlog.log('found {!r} but need:'.format(self.version),
+                         req_version)
+            else:
+                mlog.log("No llvm-config found; can't detect dependency")
+            mlog.log('Dependency LLVM found:', mlog.red('NO'))
+            if required:
+                raise DependencyException('Dependency LLVM not found')
+            return
+
+        p, out, err = Popen_safe([self.llvmconfig, '--version'])
+        if p.returncode != 0:
+            mlog.debug('stdout: {}\nstderr: {}'.format(out, err))
+            if required:
+                raise DependencyException('Dependency LLVM not found')
+            return
+        else:
+            self.version = out.strip()
+            mlog.log('Dependency LLVM found:', mlog.green('YES'))
+            self.is_found = True
+
+            p, out = Popen_safe(
+                [self.llvmconfig, '--libs', '--ldflags', '--system-libs'])[:2]
+            if p.returncode != 0:
+                raise DependencyException('Could not generate libs for LLVM.')
+            self.libs = shlex.split(out)
+
+            p, out = Popen_safe([self.llvmconfig, '--cppflags'])[:2]
+            if p.returncode != 0:
+                raise DependencyException('Could not generate includedir for LLVM.')
+            self.cargs = shlex.split(out)
+
+            p, out = Popen_safe([self.llvmconfig, '--components'])[:2]
+            if p.returncode != 0:
+                raise DependencyException('Could not generate modules for LLVM.')
+            self.modules = shlex.split(out)
+
+        modules = mesonlib.stringlistify(kwargs.get('modules', []))
+        for mod in modules:
+            if mod not in self.modules:
+                mlog.log('LLVM module', mod, 'found:', mlog.red('NO'))
+                self.is_found = False
+                if required:
+                    raise DependencyException(
+                        'Could not find required LLVM Component: {}'.format(mod))
+            else:
+                mlog.log('LLVM module', mod, 'found:', mlog.green('YES'))
+
+    def get_version(self):
+        return self.version
+
+    def get_compile_args(self):
+        return self.cargs
+
+    def get_link_args(self):
+        return self.libs
+
+    @classmethod
+    def check_llvmconfig(cls, version_req):
+        """Try to find the highest version of llvm-config."""
+        for llvmconfig in cls.llvm_config_bins:
+            try:
+                p, out = Popen_safe([llvmconfig, '--version'])[0:2]
+                out = out.strip()
+                if p.returncode != 0:
+                    continue
+                if version_req:
+                    if version_compare(out, version_req, strict=True):
+                        if cls.__best_found and version_compare(out, '<={}'.format(cls.__best_found), strict=True):
+                            continue
+                        cls.__best_found = out
+                        cls.llvmconfig = llvmconfig
+                else:
+                    # If no specific version is requested use the first version
+                    # found, since that should be the best.
+                    cls.__best_found = out
+                    cls.llvmconfig = llvmconfig
+                    break
+            except (FileNotFoundError, PermissionError):
+                pass
+        if cls.__best_found:
+            mlog.log('Found llvm-config:',
+                     mlog.bold(shutil.which(cls.llvmconfig)),
+                     '({})'.format(out.strip()))
+            cls._llvmconfig_found = True
+        else:
+            cls.llvmconfig = False
+
+    def need_threads(self):
+        return True
+
+
 def get_dep_identifier(name, kwargs):
     elements = [name]
     modlist = kwargs.get('modules', [])
@@ -1671,4 +1802,5 @@ def find_external_dependency(name, environment, kwargs):
             'threads': ThreadDependency,
             'python3': Python3Dependency,
             'valgrind': ValgrindDependency,
+            'llvm': LLVMDependency,
             }


Test patch:
diff --git a/test cases/frameworks/15 llvm/meson.build b/test cases/frameworks/15 llvm/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/frameworks/15 llvm/meson.build	
@@ -0,0 +1,10 @@
+project('llvmtest', ['c', 'cpp'], default_options : ['c_std=c99'])
+
+llvm_dep = dependency(
+  'llvm',
+  modules : ['bitwriter', 'asmprinter', 'executionengine', 'target',
+             'mcjit', 'nativecodegen'],
+  required : true,
+)
+
+executable('sum', 'sum.c',  dependencies : llvm_dep)
diff --git a/test cases/frameworks/15 llvm/sum.c b/test cases/frameworks/15 llvm/sum.c
new file mode 100644
--- /dev/null
+++ b/test cases/frameworks/15 llvm/sum.c	
@@ -0,0 +1,76 @@
+/** This code is public domain, and taken from 
+ * https://github.com/paulsmith/getting-started-llvm-c-api/blob/master/sum.c
+ */
+/**
+ * LLVM equivalent of:
+ *
+ * int sum(int a, int b) {
+ *     return a + b;
+ * }
+ */
+
+#include <llvm-c/Core.h>
+#include <llvm-c/ExecutionEngine.h>
+#include <llvm-c/Target.h>
+#include <llvm-c/Analysis.h>
+#include <llvm-c/BitWriter.h>
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int argc, char const *argv[]) {
+    LLVMModuleRef mod = LLVMModuleCreateWithName("my_module");
+
+    LLVMTypeRef param_types[] = { LLVMInt32Type(), LLVMInt32Type() };
+    LLVMTypeRef ret_type = LLVMFunctionType(LLVMInt32Type(), param_types, 2, 0);
+    LLVMValueRef sum = LLVMAddFunction(mod, "sum", ret_type);
+
+    LLVMBasicBlockRef entry = LLVMAppendBasicBlock(sum, "entry");
+
+    LLVMBuilderRef builder = LLVMCreateBuilder();
+    LLVMPositionBuilderAtEnd(builder, entry);
+    LLVMValueRef tmp = LLVMBuildAdd(builder, LLVMGetParam(sum, 0), LLVMGetParam(sum, 1), "tmp");
+    LLVMBuildRet(builder, tmp);
+
+    char *error = NULL;
+    LLVMVerifyModule(mod, LLVMAbortProcessAction, &error);
+    LLVMDisposeMessage(error);
+
+    LLVMExecutionEngineRef engine;
+    error = NULL;
+    LLVMLinkInMCJIT();
+    LLVMInitializeNativeAsmPrinter();
+    LLVMInitializeNativeTarget();
+    if (LLVMCreateExecutionEngineForModule(&engine, mod, &error) != 0) {
+        fprintf(stderr, "failed to create execution engine\n");
+        abort();
+    }
+    if (error) {
+        fprintf(stderr, "error: %s\n", error);
+        LLVMDisposeMessage(error);
+        exit(EXIT_FAILURE);
+    }
+
+    if (argc < 3) {
+        fprintf(stderr, "usage: %s x y\n", argv[0]);
+        exit(EXIT_FAILURE);
+    }
+    long long x = strtoll(argv[1], NULL, 10);
+    long long y = strtoll(argv[2], NULL, 10);
+
+    LLVMGenericValueRef args[] = {
+        LLVMCreateGenericValueOfInt(LLVMInt32Type(), x, 0),
+        LLVMCreateGenericValueOfInt(LLVMInt32Type(), y, 0)
+    };
+    LLVMGenericValueRef res = LLVMRunFunction(engine, sum, 2, args);
+    printf("%d\n", (int)LLVMGenericValueToInt(res, 0));
+
+    // Write out bitcode to file
+    if (LLVMWriteBitcodeToFile(mod, "sum.bc") != 0) {
+        fprintf(stderr, "error writing bitcode to file, skipping\n");
+    }
+
+    LLVMDisposeBuilder(builder);
+    LLVMDisposeExecutionEngine(engine);
+}