Skip to content

Commit

Permalink
cmake: allow dynamic linking with LLVM
Browse files Browse the repository at this point in the history
llvm-config is unsuitable for standard cross-compile,
because we need to build llvm especially for it, which
is not done is almost any distros, so, for example,
standard bootstrap chroot will be unsuitable.

This patch is trying to acheive feature parity between
config-tool searching of LLVM and CMake-based one,
which is arch-agnostic.

Signed-off-by: Konstantin <ria.freelander@gmail.com>
  • Loading branch information
rilian-la-te authored and mensinda committed Jan 28, 2023
1 parent 01275fb commit 89146e8
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 73 deletions.
150 changes: 130 additions & 20 deletions mesonbuild/dependencies/data/CMakeListsLLVM.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
# fail noisily if attempt to use this file without setting:
# cmake_minimum_required(VERSION ${CMAKE_VERSION})
# project(... LANGUAGES ...)

cmake_policy(SET CMP0000 NEW)

set(PACKAGE_FOUND FALSE)

list(REMOVE_DUPLICATES LLVM_MESON_VERSIONS)

while(TRUE)
find_package(LLVM REQUIRED CONFIG QUIET)
#Activate CMake version selection
foreach(i IN LISTS LLVM_MESON_VERSIONS)
find_package(LLVM ${i}
CONFIG
NAMES ${LLVM_MESON_PACKAGE_NAMES}
QUIET)
if(LLVM_FOUND)
break()
endif()
endforeach()

# ARCHS has to be set via the CMD interface
if(LLVM_FOUND OR "${ARCHS}" STREQUAL "")
Expand All @@ -13,9 +29,70 @@ while(TRUE)
list(REMOVE_AT ARCHS 0)
endwhile()

function(meson_llvm_cmake_dynamic_available mod out)
# Check if we can only compare LLVM_DYLIB_COMPONENTS, because
# we do not need complex component translation logic, if all
# is covered by one variable
if(mod IN_LIST LLVM_DYLIB_COMPONENTS)
set(${out} TRUE PARENT_SCOPE)
return()
elseif((NOT (mod IN_LIST LLVM_DYLIB_COMPONENTS))
AND (NOT("${LLVM_DYLIB_COMPONENTS}" STREQUAL "all")))
set(${out} FALSE PARENT_SCOPE)
return()
endif()

# Complex heurisic to filter all pseudo-components and skip invalid names
# LLVM_DYLIB_COMPONENTS will be 'all', because in other case we returned
# in previous check. 'all' is also handled there.
set(llvm_pseudo_components "native" "backend" "engine" "all-targets")
is_llvm_target_specifier(${mod} mod_spec INCLUDED_TARGETS)
string(TOUPPER "${LLVM_AVAILABLE_LIBS}" capitalized_libs)
string(TOUPPER "${LLVM_TARGETS_TO_BUILD}" capitalized_tgts)
if(mod_spec)
set(${out} TRUE PARENT_SCOPE)
elseif(mod IN_LIST capitalized_tgts)
set(${out} TRUE PARENT_SCOPE)
elseif(mod IN_LIST llvm_pseudo_components)
set(${out} TRUE PARENT_SCOPE)
elseif(LLVM${mod} IN_LIST capitalized_libs)
set(${out} TRUE PARENT_SCOPE)
else()
set(${out} FALSE PARENT_SCOPE)
endif()
endfunction()

function(is_static target ret)
if(TARGET ${target})
get_target_property(target_type ${target} TYPE)
if(target_type STREQUAL "STATIC_LIBRARY")
set(${ret} TRUE PARENT_SCOPE)
return()
endif()
endif()
set(${ret} FALSE PARENT_SCOPE)
endfunction()

# Concatenate LLVM_MESON_REQUIRED_MODULES and LLVM_MESON_OPTIONAL_MODULES
set(LLVM_MESON_MODULES ${LLVM_MESON_REQUIRED_MODULES} ${LLVM_MESON_OPTIONAL_MODULES})


# Check if LLVM exists in dynamic world
# Initialization before modules checking
if(LLVM_FOUND)
set(PACKAGE_FOUND TRUE)
if(LLVM_MESON_DYLIB AND TARGET LLVM)
set(PACKAGE_FOUND TRUE)
elseif(NOT LLVM_MESON_DYLIB)
# Use LLVMSupport to check if static targets exist
set(static_tg FALSE)
is_static(LLVMSupport static_tg)
if(static_tg)
set(PACKAGE_FOUND TRUE)
endif(static_tg)
endif()
endif()

if(PACKAGE_FOUND)
foreach(mod IN LISTS LLVM_MESON_MODULES)
# Reset variables
set(out_mods)
Expand All @@ -25,23 +102,53 @@ if(LLVM_FOUND)
string(TOLOWER "${mod}" mod_L)
string(TOUPPER "${mod}" mod_U)

# Get the mapped components
llvm_map_components_to_libnames(out_mods ${mod} ${mod_L} ${mod_U})
list(SORT out_mods)
list(REMOVE_DUPLICATES out_mods)

# Make sure that the modules exist
foreach(i IN LISTS out_mods)
if(TARGET ${i})
list(APPEND real_mods ${i})
endif()
endforeach()

# Set the output variables
set(MESON_LLVM_TARGETS_${mod} ${real_mods})
foreach(i IN LISTS real_mods)
set(MESON_TARGET_TO_LLVM_${i} ${mod})
endforeach()
# Special case - "all-targets" pseudo target
# Just append all targets, if pseudo-target exists
if("${mod}" STREQUAL "all-targets")
set(mod_L ${LLVM_TARGETS_TO_BUILD})
string(TOUPPER "${LLVM_TARGETS_TO_BUILD}" mod_U)
endif()

# Check if required module is linked is inside libLLVM.so.
# If not, skip this module
if(LLVM_MESON_DYLIB
AND DEFINED LLVM_DYLIB_COMPONENTS)
meson_llvm_cmake_dynamic_available(${mod} MOD_F)
meson_llvm_cmake_dynamic_available(${mod_L} MOD_L_F)
meson_llvm_cmake_dynamic_available(${mod_U} MOD_U_F)
if(MOD_F OR MOD_L_F OR MOD_U_F)
set(MESON_LLVM_TARGETS_${mod} LLVM)
endif()
elseif(LLVM_MESON_DYLIB AND (mod IN_LIST LLVM_MESON_REQUIRED_MODULES))
# Dynamic was requested, but no required variables set, we cannot continue
set(PACKAGE_FOUND FALSE)
break()
elseif(LLVM_MESON_DYLIB)
# Dynamic was requested, and we request optional modules only. Continue
continue()
else()
# CMake only do this for static components, and we
# replicate its behaviour
# Get the mapped components
llvm_map_components_to_libnames(out_mods ${mod} ${mod_L} ${mod_U})
list(SORT out_mods)
list(REMOVE_DUPLICATES out_mods)

# Make sure that the modules exist
foreach(i IN LISTS out_mods)
set(static_tg FALSE)
is_static(${i} static_tg)
if(static_tg)
list(APPEND real_mods ${i})
endif()
endforeach()

# Set the output variables
set(MESON_LLVM_TARGETS_${mod} ${real_mods})
foreach(i IN LISTS real_mods)
set(MESON_TARGET_TO_LLVM_${i} ${mod})
endforeach()
endif()
endforeach()

# Check the following variables:
Expand All @@ -62,7 +169,10 @@ if(LLVM_FOUND)
# LLVM_LIBRARIES
# LLVM_LIBS
set(libs)
if(DEFINED LLVM_LIBRARIES)
#Hardcode LLVM, because we links with libLLVM.so when dynamic
if(LLVM_MESON_DYLIB)
get_target_property(libs LLVM IMPORTED_LOCATION)
elseif(DEFINED LLVM_LIBRARIES)
set(libs LLVM_LIBRARIES)
elseif(DEFINED LLVM_LIBS)
set(libs LLVM_LIBS)
Expand Down
43 changes: 35 additions & 8 deletions mesonbuild/dependencies/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 = []
Expand Down
120 changes: 81 additions & 39 deletions test cases/frameworks/15 llvm/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion test cases/frameworks/15 llvm/meson_options.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
option(
'method',
type : 'combo',
choices : ['config-tool', 'cmake']
choices : ['config-tool', 'cmake', 'combination']
)
option(
'link-static',
Expand Down

0 comments on commit 89146e8

Please sign in to comment.