Merge pull request #5185 from mensinda/cmakeLLVM

CMake llvm dependency backend
This commit is contained in:
Dylan Baker 2019-05-03 10:17:20 -07:00 committed by GitHub
commit 6c61edeb30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 220 additions and 26 deletions

View File

@ -35,7 +35,7 @@ from .. import mesonlib
from ..compilers import clib_langs from ..compilers import clib_langs
from ..environment import BinaryTable, Environment, MachineInfo from ..environment import BinaryTable, Environment, MachineInfo
from ..mesonlib import MachineChoice, MesonException, OrderedSet, PerMachine from ..mesonlib import MachineChoice, MesonException, OrderedSet, PerMachine
from ..mesonlib import Popen_safe, version_compare_many, version_compare, listify from ..mesonlib import Popen_safe, version_compare_many, version_compare, listify, stringlistify, extract_as_list
from ..mesonlib import Version, LibType from ..mesonlib import Version, LibType
# These must be defined in this file to avoid cyclical references. # These must be defined in this file to avoid cyclical references.
@ -971,6 +971,24 @@ class CMakeDependency(ExternalDependency):
def _gen_exception(self, msg): def _gen_exception(self, msg):
return DependencyException('Dependency {} not found: {}'.format(self.name, msg)) return DependencyException('Dependency {} not found: {}'.format(self.name, msg))
def _main_cmake_file(self) -> str:
return 'CMakeLists.txt'
def _extra_cmake_opts(self) -> List[str]:
return []
def _map_module_list(self, modules: List[Tuple[str, bool]]) -> List[Tuple[str, bool]]:
# Map the input module list to something else
# This function will only be executed AFTER the initial CMake
# interpreter pass has completed. Thus variables defined in the
# CMakeLists.txt can be accessed here.
return modules
def _original_module_name(self, module: str) -> str:
# Reverse the module mapping done by _map_module_list for
# one module
return module
def __init__(self, name: str, environment: Environment, kwargs, language=None): def __init__(self, name: str, environment: Environment, kwargs, language=None):
super().__init__('cmake', environment, language, kwargs) super().__init__('cmake', environment, language, kwargs)
self.name = name self.name = name
@ -990,6 +1008,9 @@ class CMakeDependency(ExternalDependency):
# Where all CMake "build dirs" are located # Where all CMake "build dirs" are located
self.cmake_root_dir = environment.scratch_dir self.cmake_root_dir = environment.scratch_dir
# List of successfully found modules
self.found_modules = []
# When finding dependencies for cross-compiling, we don't care about # When finding dependencies for cross-compiling, we don't care about
# the 'native' CMake binary # the 'native' CMake binary
# TODO: Test if this works as expected # TODO: Test if this works as expected
@ -1061,15 +1082,10 @@ class CMakeDependency(ExternalDependency):
if self.cmakeinfo is None: if self.cmakeinfo is None:
raise self._gen_exception('Unable to obtain CMake system information') raise self._gen_exception('Unable to obtain CMake system information')
modules = kwargs.get('modules', []) modules = [(x, True) for x in stringlistify(extract_as_list(kwargs, 'modules'))]
cm_path = kwargs.get('cmake_module_path', []) modules += [(x, False) for x in stringlistify(extract_as_list(kwargs, 'optional_modules'))]
cm_args = kwargs.get('cmake_args', []) cm_path = stringlistify(extract_as_list(kwargs, 'cmake_module_path'))
if not isinstance(modules, list): cm_args = stringlistify(extract_as_list(kwargs, 'cmake_args'))
modules = [modules]
if not isinstance(cm_path, list):
cm_path = [cm_path]
if not isinstance(cm_args, list):
cm_args = [cm_args]
cm_path = [x if os.path.isabs(x) else os.path.join(environment.get_source_dir(), x) for x in cm_path] cm_path = [x if os.path.isabs(x) else os.path.join(environment.get_source_dir(), x) for x in cm_path]
if cm_path: if cm_path:
cm_args += ['-DCMAKE_MODULE_PATH={}'.format(';'.join(cm_path))] cm_args += ['-DCMAKE_MODULE_PATH={}'.format(';'.join(cm_path))]
@ -1242,7 +1258,7 @@ class CMakeDependency(ExternalDependency):
return False return False
def _detect_dep(self, name: str, modules: List[str], args: List[str]): def _detect_dep(self, name: str, modules: List[Tuple[str, bool]], args: List[str]):
# Detect a dependency with CMake using the '--find-package' mode # Detect a dependency with CMake using the '--find-package' mode
# and the trace output (stderr) # and the trace output (stderr)
# #
@ -1265,11 +1281,12 @@ class CMakeDependency(ExternalDependency):
# Prepare options # Prepare options
cmake_opts = ['--trace-expand', '-DNAME={}'.format(name), '-DARCHS={}'.format(';'.join(self.cmakeinfo['archs']))] + args + ['.'] cmake_opts = ['--trace-expand', '-DNAME={}'.format(name), '-DARCHS={}'.format(';'.join(self.cmakeinfo['archs']))] + args + ['.']
cmake_opts += self._extra_cmake_opts()
if len(i) > 0: if len(i) > 0:
cmake_opts = ['-G', i] + cmake_opts cmake_opts = ['-G', i] + cmake_opts
# Run CMake # Run CMake
ret1, out1, err1 = self._call_cmake(cmake_opts, 'CMakeLists.txt') ret1, out1, err1 = self._call_cmake(cmake_opts, self._main_cmake_file())
# Current generator was successful # Current generator was successful
if ret1 == 0: if ret1 == 0:
@ -1327,6 +1344,11 @@ class CMakeDependency(ExternalDependency):
self.version = vers_raw[0] self.version = vers_raw[0]
self.version.strip('"\' ') self.version.strip('"\' ')
# Post-process module list. Used in derived classes to modify the
# module list (append prepend a string, etc.).
modules = self._map_module_list(modules)
autodetected_module_list = False
# Try guessing a CMake target if none is provided # Try guessing a CMake target if none is provided
if len(modules) == 0: if len(modules) == 0:
for i in self.targets: for i in self.targets:
@ -1334,17 +1356,19 @@ class CMakeDependency(ExternalDependency):
lname = name.lower() lname = name.lower()
if '{}::{}'.format(lname, lname) == tg or lname == tg.replace('::', ''): if '{}::{}'.format(lname, lname) == tg or lname == tg.replace('::', ''):
mlog.debug('Guessed CMake target \'{}\''.format(i)) mlog.debug('Guessed CMake target \'{}\''.format(i))
modules = [i] modules = [(i, True)]
autodetected_module_list = True
break break
# Failed to guess a target --> try the old-style method # Failed to guess a target --> try the old-style method
if len(modules) == 0: if len(modules) == 0:
incDirs = self.get_first_cmake_var_of(['PACKAGE_INCLUDE_DIRS']) incDirs = self.get_first_cmake_var_of(['PACKAGE_INCLUDE_DIRS'])
defs = self.get_first_cmake_var_of(['PACKAGE_DEFINITIONS'])
libs = self.get_first_cmake_var_of(['PACKAGE_LIBRARIES']) libs = self.get_first_cmake_var_of(['PACKAGE_LIBRARIES'])
# Try to use old style variables if no module is specified # Try to use old style variables if no module is specified
if len(libs) > 0: if len(libs) > 0:
self.compile_args = list(map(lambda x: '-I{}'.format(x), incDirs)) self.compile_args = list(map(lambda x: '-I{}'.format(x), incDirs)) + defs
self.link_args = libs self.link_args = libs
mlog.debug('using old-style CMake variables for dependency {}'.format(name)) mlog.debug('using old-style CMake variables for dependency {}'.format(name))
return return
@ -1361,13 +1385,19 @@ class CMakeDependency(ExternalDependency):
compileDefinitions = [] compileDefinitions = []
compileOptions = [] compileOptions = []
libraries = [] libraries = []
for i in modules: for i, required in modules:
if i not in self.targets: if i not in self.targets:
raise self._gen_exception('CMake: invalid CMake target {} for {}.\n' if not required:
mlog.warning('CMake: Optional module', mlog.bold(self._original_module_name(i)), 'for', mlog.bold(name), 'was not found')
continue
raise self._gen_exception('CMake: invalid module {} for {}.\n'
'Try to explicitly specify one or more targets with the "modules" property.\n' 'Try to explicitly specify one or more targets with the "modules" property.\n'
'Valid targets are:\n{}'.format(i, name, list(self.targets.keys()))) 'Valid targets are:\n{}'.format(self._original_module_name(i), name, list(self.targets.keys())))
targets = [i] targets = [i]
if not autodetected_module_list:
self.found_modules += [i]
while len(targets) > 0: while len(targets) > 0:
curr = targets.pop(0) curr = targets.pop(0)
@ -1432,7 +1462,7 @@ class CMakeDependency(ExternalDependency):
self.compile_args = compileOptions + compileDefinitions + list(map(lambda x: '-I{}'.format(x), incDirs)) self.compile_args = compileOptions + compileDefinitions + list(map(lambda x: '-I{}'.format(x), incDirs))
self.link_args = libraries self.link_args = libraries
def get_first_cmake_var_of(self, var_list): def get_first_cmake_var_of(self, var_list: List[str]) -> List[str]:
# Return the first found CMake variable in list var_list # Return the first found CMake variable in list var_list
for i in var_list: for i in var_list:
if i in self.vars: if i in self.vars:
@ -1440,7 +1470,7 @@ class CMakeDependency(ExternalDependency):
return [] return []
def get_cmake_var(self, var): def get_cmake_var(self, var: str) -> List[str]:
# Return the value of the CMake variable var or an empty list if var does not exist # Return the value of the CMake variable var or an empty list if var does not exist
if var in self.vars: if var in self.vars:
return self.vars[var] return self.vars[var]
@ -1747,6 +1777,13 @@ set(CMAKE_SIZEOF_VOID_P "{}")
def log_tried(self): def log_tried(self):
return self.type_name return self.type_name
def log_details(self) -> str:
modules = [self._original_module_name(x) for x in self.found_modules]
modules = sorted(set(modules))
if modules:
return 'modules: ' + ', '.join(modules)
return ''
class DubDependency(ExternalDependency): class DubDependency(ExternalDependency):
class_dubbin = None class_dubbin = None

View File

@ -0,0 +1,95 @@
cmake_minimum_required(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION} )
set(PACKAGE_FOUND FALSE)
while(TRUE)
find_package(LLVM REQUIRED CONFIG QUIET)
# ARCHS has to be set via the CMD interface
if(LLVM_FOUND OR "${ARCHS}" STREQUAL "")
break()
endif()
list(GET ARCHS 0 CMAKE_LIBRARY_ARCHITECTURE)
list(REMOVE_AT ARCHS 0)
endwhile()
if(LLVM_FOUND)
set(PACKAGE_FOUND TRUE)
foreach(mod IN LISTS LLVM_MESON_MODULES)
# Reset variables
set(out_mods)
set(real_mods)
# Generate a lower and upper case version
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()
endforeach()
# Check the following variables:
# LLVM_PACKAGE_VERSION
# LLVM_VERSION
# LLVM_VERSION_STRING
if(NOT DEFINED PACKAGE_VERSION)
if(DEFINED LLVM_PACKAGE_VERSION)
set(PACKAGE_VERSION "${LLVM_PACKAGE_VERSION}")
elseif(DEFINED LLVM_VERSION)
set(PACKAGE_VERSION "${LLVM_VERSION}")
elseif(DEFINED LLVM_VERSION_STRING)
set(PACKAGE_VERSION "${LLVM_VERSION_STRING}")
endif()
endif()
# Check the following variables:
# LLVM_LIBRARIES
# LLVM_LIBS
set(libs)
if(DEFINED LLVM_LIBRARIES)
set(libs LLVM_LIBRARIES)
elseif(DEFINED LLVM_LIBS)
set(libs LLVM_LIBS)
endif()
# Check the following variables:
# LLVM_INCLUDE_DIRS
# LLVM_INCLUDES
# LLVM_INCLUDE_DIR
set(includes)
if(DEFINED LLVM_INCLUDE_DIRS)
set(includes LLVM_INCLUDE_DIRS)
elseif(DEFINED LLVM_INCLUDES)
set(includes LLVM_INCLUDES)
elseif(DEFINED LLVM_INCLUDE_DIR)
set(includes LLVM_INCLUDE_DIR)
endif()
# Check the following variables:
# LLVM_DEFINITIONS
set(definitions)
if(DEFINED LLVM_DEFINITIONS)
set(definitions LLVM_DEFINITIONS)
endif()
set(PACKAGE_INCLUDE_DIRS "${${includes}}")
set(PACKAGE_DEFINITIONS "${${definitions}}")
set(PACKAGE_LIBRARIES "${${libs}}")
endif()

View File

@ -20,14 +20,16 @@ import glob
import os import os
import re import re
from .. import mesonlib from .. import mesonlib, mlog
from ..mesonlib import version_compare, stringlistify, extract_as_list, MachineChoice from ..mesonlib import version_compare, stringlistify, extract_as_list, MachineChoice
from .base import ( from .base import (
DependencyException, DependencyMethods, ExternalDependency, PkgConfigDependency, DependencyException, DependencyMethods, ExternalDependency, PkgConfigDependency,
strip_system_libdirs, ConfigToolDependency, strip_system_libdirs, ConfigToolDependency, CMakeDependency
) )
from .misc import ThreadDependency from .misc import ThreadDependency
from typing import List, Tuple
def get_shared_library_suffix(environment, native): def get_shared_library_suffix(environment, native):
"""This is only gauranteed to work for languages that compile to machine """This is only gauranteed to work for languages that compile to machine
@ -192,7 +194,7 @@ class GMockDependency(ExternalDependency):
return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM] return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM]
class LLVMDependency(ConfigToolDependency): class LLVMDependencyConfigTool(ConfigToolDependency):
""" """
LLVM uses a special tool, llvm-config, which has arguments for getting LLVM uses a special tool, llvm-config, which has arguments for getting
c args, cxx args, and ldargs as well as version. c args, cxx args, and ldargs as well as version.
@ -399,6 +401,66 @@ class LLVMDependency(ConfigToolDependency):
return 'modules: ' + ', '.join(self.module_details) return 'modules: ' + ', '.join(self.module_details)
return '' return ''
class LLVMDependencyCMake(CMakeDependency):
def __init__(self, env, kwargs):
self.llvm_modules = stringlistify(extract_as_list(kwargs, 'modules'))
self.llvm_opt_modules = stringlistify(extract_as_list(kwargs, 'optional_modules'))
super().__init__(name='LLVM', environment=env, language='cpp', kwargs=kwargs)
# Extract extra include directories and definitions
inc_dirs = self.get_cmake_var('PACKAGE_INCLUDE_DIRS')
defs = self.get_cmake_var('PACKAGE_DEFINITIONS')
temp = ['-I' + x for x in inc_dirs] + defs
self.compile_args += [x for x in temp if x not in self.compile_args]
self._add_sub_dependency(ThreadDependency, env, kwargs)
def _main_cmake_file(self) -> str:
# Use a custom CMakeLists.txt for LLVM
return 'CMakeListsLLVM.txt'
def _extra_cmake_opts(self) -> List[str]:
return ['-DLLVM_MESON_MODULES={}'.format(';'.join(self.llvm_modules + self.llvm_opt_modules))]
def _map_module_list(self, modules: List[Tuple[str, bool]]) -> List[Tuple[str, bool]]:
res = []
for mod, required in modules:
cm_targets = self.get_cmake_var('MESON_LLVM_TARGETS_{}'.format(mod))
if not cm_targets:
if required:
raise self._gen_exception('LLVM module {} was not found'.format(mod))
else:
mlog.warning('Optional LLVM module', mlog.bold(mod), 'was not found')
continue
for i in cm_targets:
res += [(i, required)]
return res
def _original_module_name(self, module: str) -> str:
orig_name = self.get_cmake_var('MESON_TARGET_TO_LLVM_{}'.format(module))
if orig_name:
return orig_name[0]
return module
class LLVMDependency(ExternalDependency):
def __init__(self, env, kwargs):
super().__init__('LLVM', env, 'cpp', kwargs)
@classmethod
def _factory(cls, env, kwargs):
methods = cls._process_method_kw(kwargs)
candidates = []
if DependencyMethods.CMAKE in methods:
candidates.append(functools.partial(LLVMDependencyCMake, env, kwargs))
if DependencyMethods.CONFIG_TOOL in methods:
candidates.append(functools.partial(LLVMDependencyConfigTool, env, kwargs))
return candidates
@staticmethod
def get_methods():
return [DependencyMethods.CMAKE, DependencyMethods.CONFIG_TOOL]
class ValgrindDependency(PkgConfigDependency): class ValgrindDependency(PkgConfigDependency):
''' '''

View File

@ -35,7 +35,7 @@ packages = ['mesonbuild',
'mesonbuild.modules', 'mesonbuild.modules',
'mesonbuild.scripts', 'mesonbuild.scripts',
'mesonbuild.wrap'] 'mesonbuild.wrap']
package_data = {'mesonbuild.dependencies': ['data/CMakeLists.txt', 'data/CMakePathInfo.txt']} package_data = {'mesonbuild.dependencies': ['data/CMakeLists.txt', 'data/CMakeListsLLVM.txt', 'data/CMakePathInfo.txt']}
data_files = [] data_files = []
if sys.platform != 'win32': if sys.platform != 'win32':
# Only useful on UNIX-like systems # Only useful on UNIX-like systems

View File

@ -15,7 +15,7 @@
project('config tool variable', 'cpp') project('config tool variable', 'cpp')
dep_llvm = dependency('llvm', required : false) dep_llvm = dependency('llvm', method : 'config-tool', required : false)
if not dep_llvm.found() if not dep_llvm.found()
error('MESON_SKIP_TEST LLVM not installed.') error('MESON_SKIP_TEST LLVM not installed.')
endif endif

View File

@ -35,7 +35,7 @@ foreach static : [true, false]
llvm_dep = dependency( llvm_dep = dependency(
'llvm', 'llvm',
modules : ['bitwriter', 'asmprinter', 'executionengine', 'target', modules : ['bitwriter', 'asmprinter', 'executionengine', 'target',
'mcjit', 'nativecodegen'], 'mcjit', 'nativecodegen', 'amdgpu'],
required : false, required : false,
static : static, static : static,
) )

View File

@ -8,7 +8,7 @@ if case == 'find_program'
assert(result.stdout().strip().endswith('12345'), 'Didn\'t load bash from config file') assert(result.stdout().strip().endswith('12345'), 'Didn\'t load bash from config file')
elif case == 'config_dep' elif case == 'config_dep'
add_languages('cpp') add_languages('cpp')
dep = dependency('llvm') dep = dependency('llvm', method : 'config-tool')
assert(dep.get_configtool_variable('version').endswith('12345'), 'Didn\'t load llvm from config file') assert(dep.get_configtool_variable('version').endswith('12345'), 'Didn\'t load llvm from config file')
elif case == 'python3' elif case == 'python3'
prog = import('python3').find_python() prog = import('python3').find_python()