Repository: mesonbuild/meson

Issue:
Is llvm with CMake is always static and nothing changed from 0.51x?
**Describe the bug**
I want to cross-compile with CMake-based LLVM detection (without LLVM-config). LLVM should be dynamic lib (at best, it should find libLLVM.so) with CMake. We cannot use llvm-config from cross-file due to qemu requirement (to run mipsel binaries on amd64), so, I do not provide it in cross file.

**To Reproduce**
Make native mipsel debian chroot via debootstrap
Try to compile some llvm-dependent package with sysroot of this chroot. Non-llvm dependent library works fine.

**Expected behavior**
LLVM should be found without using qemy bu CMake usage.

**system parameters**
* Cross build, Ubuntu 20.04 -> debian bullseye mipsel
* 3.8.10
* 0.63.0
* 1.10.0
Cross-file:

```
[constants]
fs = '/mnt/chroot/debian'
common_flags = ['--sysroot=' + fs ]

[host_machine]
system = 'linux'
cpu_family = 'mips'
cpu = 'mips32'
endian = 'little'

[properties]
sizeof_int = 4
sizeof_wchar_t = 4
sizeof_void* = 4

alignment_char = 1
alignment_void* = 4
alignment_double = 4

has_function_printf = true

sys_root = fs
pkg_config_libdir = [fs + '/usr/lib/mipsel-linux-gnu/pkgconfig', fs + '/usr/share/pkgconfig']

c_args = common_flags + ['-I' + fs + '/usr/include', '-L' + fs + '/usr/lib/mipsel-linux-gnu']

[binaries]
c = 'mipsel-linux-gnu-gcc'
cpp = 'mipsel-linux-gnu-g++'
strip = 'mipsel-linux-gnu-strip'
pkgconfig = 'pkg-config'
```



Discussion:
My understanding is that this is a flaw in llvm's own cmake files.

At any rate, this has not changed in meson git master.
@eli-schwartz I tried to fix it in my PR, but it certainly has bug. How I can unittest it? How LLVM dependency handling is unit tested at all?
In the `test cases/` directory there should be some cmake tests. Trying to run a project test with `method: 'cmake'` should get you more or less what you want.

But I'll leave meaningful responses to the person that actually understands our cmake support (I've assigned the PR already).

Resolving patch:
diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py
--- a/mesonbuild/dependencies/dev.py
+++ b/mesonbuild/dependencies/dev.py
@@ -24,12 +24,13 @@
 import shutil
 import subprocess
 import typing as T
+import functools
 
 from mesonbuild.interpreterbase.decorators import FeatureDeprecated
 
 from .. import mesonlib, mlog
 from ..environment import get_llvm_tool_names
-from ..mesonlib import version_compare, stringlistify, extract_as_list
+from ..mesonlib import version_compare, version_compare_many, search_version, stringlistify, extract_as_list
 from .base import DependencyException, DependencyMethods, detect_compiler, strip_system_libdirs, SystemDependency, ExternalDependency, DependencyTypeName
 from .cmake import CMakeDependency
 from .configtool import ConfigToolDependency
@@ -418,14 +419,15 @@ def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]) ->
 
         super().__init__(name, env, kwargs, language='cpp', force_use_global_compilers=True)
 
-        # Cmake will always create a statically linked binary, so don't use
-        # cmake if dynamic is required
-        if not self.static:
-            self.is_found = False
-            mlog.warning('Ignoring LLVM CMake dependency because dynamic was requested', fatal=False)
+        if self.traceparser is None:
             return
 
-        if self.traceparser is None:
+        if not self.is_found:
+            return
+
+        #CMake will return not found due to not defined LLVM_DYLIB_COMPONENTS
+        if not self.static and version_compare(self.version, '< 7.0') and self.llvm_modules:
+            mlog.warning('Before version 7.0 cmake does not export modules for dynamic linking, cannot check required modules')
             return
 
         # Extract extra include directories and definitions
@@ -444,8 +446,33 @@ def _main_cmake_file(self) -> str:
         # Use a custom CMakeLists.txt for LLVM
         return 'CMakeListsLLVM.txt'
 
+    # Check version in CMake to return exact version as config tool (latest allowed)
+    # It is safe to add .0 to latest argument, it will discarded if we use search_version
+    def llvm_cmake_versions(self) -> T.List[str]:
+
+        def ver_from_suf(req: str) -> str:
+            return search_version(req.strip('-')+'.0')
+
+        def version_sorter(a: str, b: str) -> int:
+            if version_compare(a, "="+b):
+                return 0
+            if version_compare(a, "<"+b):
+                return 1
+            return -1
+
+        llvm_requested_versions = [ver_from_suf(x) for x in get_llvm_tool_names('') if version_compare(ver_from_suf(x), '>=0')]
+        if self.version_reqs:
+            llvm_requested_versions = [ver_from_suf(x) for x in get_llvm_tool_names('') if version_compare_many(ver_from_suf(x), self.version_reqs)]
+        # CMake sorting before 3.18 is incorrect, sort it here instead
+        return sorted(llvm_requested_versions, key=functools.cmp_to_key(version_sorter))
+
+    # Split required and optional modules to distinguish it in CMake
     def _extra_cmake_opts(self) -> T.List[str]:
-        return ['-DLLVM_MESON_MODULES={}'.format(';'.join(self.llvm_modules + self.llvm_opt_modules))]
+        return ['-DLLVM_MESON_REQUIRED_MODULES={}'.format(';'.join(self.llvm_modules)),
+                '-DLLVM_MESON_OPTIONAL_MODULES={}'.format(';'.join(self.llvm_opt_modules)),
+                '-DLLVM_MESON_PACKAGE_NAMES={}'.format(';'.join(get_llvm_tool_names(self.name))),
+                '-DLLVM_MESON_VERSIONS={}'.format(';'.join(self.llvm_cmake_versions())),
+                '-DLLVM_MESON_DYLIB={}'.format('OFF' if self.static else 'ON')]
 
     def _map_module_list(self, modules: T.List[T.Tuple[str, bool]], components: T.List[T.Tuple[str, bool]]) -> T.List[T.Tuple[str, bool]]:
         res = []


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	
@@ -2,50 +2,92 @@ project('llvmtest', ['c', 'cpp'], default_options : ['c_std=c99'])
 
 method = get_option('method')
 static = get_option('link-static')
-d = dependency('llvm', required : false, method : method, static : static)
-if not d.found()
-  error('MESON_SKIP_TEST llvm not found.')
-endif
 
-d = dependency('llvm', modules : 'not-found', required : false, static : static, method : method)
-assert(d.found() == false, 'not-found llvm module found')
+if(method == 'combination')
+  d = dependency('llvm', version : static ? '>0.1' : '>=7.0', required : false, static : static)
+  if not d.found()
+    error('MESON_SKIP_TEST llvm not found or llvm version is too low')
+  endif
+  llvm_ct_dep = dependency(
+    'llvm',
+    modules : ['bitwriter', 'asmprinter', 'executionengine', 'target',
+              'mcjit', 'nativecodegen', 'amdgpu', 'engine'],
+    required : false,
+    static : static,
+    method : 'config-tool',
+  )
+  llvm_cm_dep = dependency(
+    'llvm',
+    modules : ['bitwriter', 'asmprinter', 'executionengine', 'target',
+              'mcjit', 'nativecodegen', 'amdgpu', 'engine'],
+    required : false,
+    static : static,
+    method : 'cmake',
+  )
+  assert(llvm_ct_dep.found() == llvm_cm_dep.found(), 'config-tool and cmake results differ')
+  cm_version_major = llvm_cm_dep.version().split('.')[0].to_int()
+  cm_version_minor = llvm_cm_dep.version().split('.')[1].to_int()
+  ct_version_major = llvm_ct_dep.version().split('.')[0].to_int()
+  ct_version_minor = llvm_ct_dep.version().split('.')[1].to_int()
+  assert(cm_version_major == ct_version_major, 'config-tool and cmake returns different major versions')
+  assert(cm_version_minor == ct_version_minor, 'config-tool and cmake returns different minor versions')
+else
+  d = dependency('llvm', required : false, method : method, static : static)
+  if not d.found()
+    error('MESON_SKIP_TEST llvm not found.')
+  endif
 
-d = dependency('llvm', version : '<0.1', required : false, static : static, method : method)
-assert(d.found() == false, 'ancient llvm module found')
+  if(not static and method == 'cmake')
+    d = dependency('llvm', version : '>=7.0', required : false, static : static)
+    if not d.found()
+      error('MESON_SKIP_TEST llvm version is too low for cmake dynamic link.')
+    endif
+  endif
 
-d = dependency('llvm', optional_modules : 'not-found', required : false, static : static, method : method)
-assert(d.found() == true, 'optional module stopped llvm from being found.')
+  d = dependency('llvm', modules : 'not-found', required : false, static : static, method : method)
+  assert(d.found() == false, 'not-found llvm module found')
 
-# Check we can apply a version constraint
-d = dependency('llvm', version : ['< 500', '>=@0@'.format(d.version())], required: false, static : static, method : method)
-assert(d.found() == true, 'Cannot set version constraints')
+  d = dependency('llvm', version : '<0.1', required : false, static : static, method : method)
+  assert(d.found() == false, 'ancient llvm module found')
 
-dep_tinfo = dependency('tinfo', required : false)
-if not dep_tinfo.found()
-  cpp = meson.get_compiler('cpp')
-  dep_tinfo = cpp.find_library('tinfo', required: false)
-endif
+  d = dependency('llvm', optional_modules : 'not-found', required : false, static : static, method : method)
+  assert(d.found() == true, 'optional module stopped llvm from being found.')
 
-llvm_dep = dependency(
-  'llvm',
-  modules : ['bitwriter', 'asmprinter', 'executionengine', 'target',
-             'mcjit', 'nativecodegen', 'amdgpu'],
-  required : false,
-  static : static,
-  method : method,
-)
-
-if not llvm_dep.found()
-  error('MESON_SKIP_TEST required llvm modules not found.')
-endif
+  # Check we can apply a version constraint
+  d = dependency('llvm', version : ['< 500', '>=@0@'.format(d.version())], required: false, static : static, method : method)
+  assert(d.found() == true, 'Cannot set version constraints')
 
-executable(
-    'sum',
-    'sum.c',
-    dependencies : [
-      llvm_dep, dep_tinfo,
-      # zlib will be statically linked on windows
-      dependency('zlib', required : host_machine.system() != 'windows'),
-      meson.get_compiler('c').find_library('dl', required : false),
-    ]
+  # Check if we have to get pseudo components
+  d = dependency('llvm', modules: ['all-targets','native','engine'], required: false, static : static, method : method)
+  assert(d.found() == true, 'Cannot find pseudo components')
+
+  dep_tinfo = dependency('tinfo', required : false)
+  if not dep_tinfo.found()
+    cpp = meson.get_compiler('cpp')
+    dep_tinfo = cpp.find_library('tinfo', required: false)
+  endif
+
+  llvm_dep = dependency(
+    'llvm',
+    modules : ['bitwriter', 'asmprinter', 'executionengine', 'target',
+              'mcjit', 'nativecodegen', 'amdgpu'],
+    required : false,
+    static : static,
+    method : method,
   )
+
+  if not llvm_dep.found()
+    error('MESON_SKIP_TEST required llvm modules not found.')
+  endif
+
+  executable(
+      'sum',
+      'sum.c',
+      dependencies : [
+        llvm_dep, dep_tinfo,
+        # zlib will be statically linked on windows
+        dependency('zlib', required : host_machine.system() != 'windows'),
+        meson.get_compiler('c').find_library('dl', required : false),
+      ]
+    )
+endif
diff --git a/test cases/frameworks/15 llvm/meson_options.txt b/test cases/frameworks/15 llvm/meson_options.txt
--- a/test cases/frameworks/15 llvm/meson_options.txt	
+++ b/test cases/frameworks/15 llvm/meson_options.txt	
@@ -1,7 +1,7 @@
 option(
   'method',
   type : 'combo',
-  choices : ['config-tool', 'cmake']
+  choices : ['config-tool', 'cmake', 'combination']
 )
 option(
   'link-static',
diff --git a/test cases/frameworks/15 llvm/test.json b/test cases/frameworks/15 llvm/test.json
--- a/test cases/frameworks/15 llvm/test.json	
+++ b/test cases/frameworks/15 llvm/test.json	
@@ -3,16 +3,17 @@
     "options": {
       "method": [
         { "val": "config-tool", "skip_on_jobname": ["msys2-gcc"]},
-        { "val": "cmake", "skip_on_jobname": ["msys2-gcc"] }
+        { "val": "cmake", "skip_on_jobname": ["msys2-gcc"] },
+        { "val": "combination", "skip_on_jobname": ["msys2-gcc"]}
       ],
       "link-static": [
         { "val": true, "skip_on_jobname": ["opensuse"] },
         { "val": false }
       ]
-    },
-    "exclude": [
-      { "method": "cmake", "link-static": false }
-    ]
+    }
   },
-  "skip_on_jobname": ["azure", "cygwin"]
+  "skip_on_jobname": ["azure", "cygwin"],
+  "tools": {
+    "cmake": ">=3.11"
+  }
 }