From 0e777e7c90d5ac89b457621aa96a513dce0d3e2f Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 28 May 2021 23:35:19 +0200 Subject: [PATCH 1/5] cmake: CMakeTraceParser improvements - handle cached CMake variables differently - associate variables with source files - better performance (str to Path and generator expressions) --- mesonbuild/cmake/generator.py | 4 +++ mesonbuild/cmake/traceparser.py | 54 +++++++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/mesonbuild/cmake/generator.py b/mesonbuild/cmake/generator.py index 02d56cc5b..848fdf944 100644 --- a/mesonbuild/cmake/generator.py +++ b/mesonbuild/cmake/generator.py @@ -23,6 +23,10 @@ def parse_generator_expressions(raw: str) -> str: use cases. ''' + # Early abort if no generator expression present + if '$<' not in raw: + return raw + out = '' # type: str i = 0 # type: int diff --git a/mesonbuild/cmake/traceparser.py b/mesonbuild/cmake/traceparser.py index 6294fa161..23a185289 100644 --- a/mesonbuild/cmake/traceparser.py +++ b/mesonbuild/cmake/traceparser.py @@ -22,21 +22,31 @@ from ..mesonlib import version_compare import typing as T from pathlib import Path +from functools import lru_cache import re import json import textwrap class CMakeTraceLine: - def __init__(self, file: Path, line: int, func: str, args: T.List[str]) -> None: - self.file = file + def __init__(self, file_str: str, line: int, func: str, args: T.List[str]) -> None: + self.file = CMakeTraceLine._to_path(file_str) self.line = line self.func = func.lower() self.args = args + @staticmethod + @lru_cache(maxsize=None) + def _to_path(file_str: str) -> Path: + return Path(file_str) + def __repr__(self) -> str: s = 'CMake TRACE: {0}:{1} {2}({3})' return s.format(self.file, self.line, self.func, self.args) +class CMakeCacheEntry(T.NamedTuple): + value: T.List[str] + type: str + class CMakeTarget: def __init__( self, @@ -81,8 +91,10 @@ class CMakeGeneratorTarget(CMakeTarget): class CMakeTraceParser: def __init__(self, cmake_version: str, build_dir: Path, permissive: bool = True) -> None: - self.vars = {} # type: T.Dict[str, T.List[str]] - self.targets = {} # type: T.Dict[str, CMakeTarget] + self.vars: T.Dict[str, T.List[str]] = {} + self.vars_by_file: T.Dict[Path, T.Dict[str, T.List[str]]] = {} + self.targets: T.Dict[str, CMakeTarget] = {} + self.cache: T.Dict[str, CMakeCacheEntry] = {} self.explicit_headers = set() # type: T.Set[Path] @@ -234,6 +246,14 @@ class CMakeTraceParser: """ # DOC: https://cmake.org/cmake/help/latest/command/set.html + cache_type = None + cache_force = 'FORCE' in tline.args + try: + cache_idx = tline.args.index('CACHE') + cache_type = tline.args[cache_idx + 1] + except (ValueError, IndexError): + pass + # 1st remove PARENT_SCOPE and CACHE from args args = [] for i in tline.args: @@ -256,12 +276,19 @@ class CMakeTraceParser: identifier = args.pop(0) value = ' '.join(args) + # Write to the CMake cache instead + if cache_type: + # Honor how the CMake FORCE parameter works + if identifier not in self.cache or cache_force: + self.cache[identifier] = CMakeCacheEntry(value.split(';'), cache_type) + if not value: # Same as unset if identifier in self.vars: del self.vars[identifier] else: self.vars[identifier] = value.split(';') + self.vars_by_file.setdefault(tline.file, {})[identifier] = value.split(';') def _cmake_unset(self, tline: CMakeTraceLine) -> None: # DOC: https://cmake.org/cmake/help/latest/command/unset.html @@ -437,17 +464,18 @@ class CMakeTraceParser: if not value: return - def do_target(tgt: str) -> None: - if i not in self.targets: - return self._gen_exception('set_property', f'TARGET {i} not found', tline) + def do_target(t: str) -> None: + if t not in self.targets: + return self._gen_exception('set_property', f'TARGET {t} not found', tline) - if identifier not in self.targets[i].properties: - self.targets[i].properties[identifier] = [] + tgt = self.targets[t] + if identifier not in tgt.properties: + tgt.properties[identifier] = [] if append: - self.targets[i].properties[identifier] += value + tgt.properties[identifier] += value else: - self.targets[i].properties[identifier] = value + tgt.properties[identifier] = value def do_source(src: str) -> None: if identifier != 'HEADER_FILE_ONLY' or not self._str_to_bool(value): @@ -652,7 +680,7 @@ class CMakeTraceParser: argl = args.split(' ') argl = list(map(lambda x: x.strip(), argl)) - yield CMakeTraceLine(Path(file), int(line), func, argl) + yield CMakeTraceLine(file, int(line), func, argl) def _lex_trace_json(self, trace: str) -> T.Generator[CMakeTraceLine, None, None]: lines = trace.splitlines(keepends=False) @@ -667,7 +695,7 @@ class CMakeTraceParser: for j in args: assert isinstance(j, str) args = [parse_generator_expressions(x) for x in args] - yield CMakeTraceLine(Path(data['file']), data['line'], data['cmd'], args) + yield CMakeTraceLine(data['file'], data['line'], data['cmd'], args) def _flatten_args(self, args: T.List[str]) -> T.List[str]: # Split lists in arguments From 49c730ef05573410eda88256678ed6d1379cf5ea Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 28 May 2021 23:39:24 +0200 Subject: [PATCH 2/5] cmake: Fix CMakeToolchain (fixes #8293) Instead of guessing the internal compiler variables, Meson now runns CMake once to determine what they actually are. --- mesonbuild/cmake/interpreter.py | 2 +- mesonbuild/cmake/toolchain.py | 158 +++++++++++++++++++------------- mesonbuild/coredata.py | 32 +++++++ mesonbuild/dependencies/base.py | 4 +- 4 files changed, 128 insertions(+), 68 deletions(-) diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 130723f9d..513df60b7 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -892,7 +892,7 @@ class CMakeInterpreter: self.trace = CMakeTraceParser(cmake_exe.version(), self.build_dir, permissive=True) preload_file = mesondata['cmake/data/preload.cmake'].write_to_private(self.env) - toolchain = CMakeToolchain(self.env, self.for_machine, CMakeExecScope.SUBPROJECT, self.build_dir.parent, preload_file) + toolchain = CMakeToolchain(cmake_exe, self.env, self.for_machine, CMakeExecScope.SUBPROJECT, self.build_dir, preload_file) toolchain_file = toolchain.write() # TODO: drop this check once the deprecated `cmake_args` kwarg is removed diff --git a/mesonbuild/cmake/toolchain.py b/mesonbuild/cmake/toolchain.py index c9f821adc..c5056d7e8 100644 --- a/mesonbuild/cmake/toolchain.py +++ b/mesonbuild/cmake/toolchain.py @@ -13,6 +13,7 @@ # limitations under the License. from pathlib import Path +from .traceparser import CMakeTraceParser from ..envconfig import CMakeSkipCompilerTest from ..mesonlib import MachineChoice from .common import language_map @@ -24,53 +25,49 @@ from enum import Enum from textwrap import dedent if T.TYPE_CHECKING: + from .executor import CMakeExecutor from ..envconfig import MachineInfo, Properties, CMakeVariables from ..environment import Environment - from ..compilers import Compiler - - -_MESON_TO_CMAKE_MAPPING = { - 'arm': 'ARMCC', - 'armclang': 'ARMClang', - 'clang': 'Clang', - 'clang-cl': 'MSVC', - 'flang': 'Flang', - 'g95': 'G95', - 'gcc': 'GNU', - 'intel': 'Intel', - 'intel-cl': 'MSVC', - 'msvc': 'MSVC', - 'pathscale': 'PathScale', - 'pgi': 'PGI', - 'sun': 'SunPro', -} class CMakeExecScope(Enum): SUBPROJECT = 'subproject' DEPENDENCY = 'dependency' class CMakeToolchain: - def __init__(self, env: 'Environment', for_machine: MachineChoice, exec_scope: CMakeExecScope, out_dir: Path, preload_file: T.Optional[Path] = None) -> None: + def __init__(self, cmakebin: 'CMakeExecutor', env: 'Environment', for_machine: MachineChoice, exec_scope: CMakeExecScope, build_dir: Path, preload_file: T.Optional[Path] = None) -> None: self.env = env + self.cmakebin = cmakebin self.for_machine = for_machine self.exec_scope = exec_scope self.preload_file = preload_file - self.toolchain_file = out_dir / 'CMakeMesonToolchainFile.cmake' - self.toolchain_file = self.toolchain_file.resolve() + self.build_dir = build_dir + self.build_dir = self.build_dir.resolve() + self.toolchain_file = build_dir / 'CMakeMesonToolchainFile.cmake' + self.cmcache_file = build_dir / 'CMakeCache.txt' self.minfo = self.env.machines[self.for_machine] self.properties = self.env.properties[self.for_machine] self.compilers = self.env.coredata.compilers[self.for_machine] self.cmakevars = self.env.cmakevars[self.for_machine] + self.cmakestate = self.env.coredata.cmake_cache[self.for_machine] self.variables = self.get_defaults() self.variables.update(self.cmakevars.get_variables()) + # Determine whether CMake the compiler test should be skipped + skip_status = self.properties.get_cmake_skip_compiler_test() + self.skip_check = skip_status == CMakeSkipCompilerTest.ALWAYS + if skip_status == CMakeSkipCompilerTest.DEP_ONLY and self.exec_scope == CMakeExecScope.DEPENDENCY: + self.skip_check = True + if not self.properties.get_cmake_defaults(): + self.skip_check = False + assert self.toolchain_file.is_absolute() def write(self) -> Path: if not self.toolchain_file.parent.exists(): self.toolchain_file.parent.mkdir(parents=True) self.toolchain_file.write_text(self.generate()) + self.cmcache_file.write_text(self.generate_cache()) mlog.cmd_ci_include(self.toolchain_file.as_posix()) return self.toolchain_file @@ -80,6 +77,16 @@ class CMakeToolchain: args += ['-DMESON_PRELOAD_FILE=' + self.preload_file.as_posix()] return args + @staticmethod + def _print_vars(vars: T.Dict[str, T.List[str]]) -> str: + res = '' + for key, value in vars.items(): + res += 'set(' + key + for i in value: + res += f' "{i}"' + res += ')\n' + return res + def generate(self) -> str: res = dedent('''\ ###################################### @@ -100,14 +107,19 @@ class CMakeToolchain: for key, value in self.variables.items(): self.variables[key] = [x.replace('\\', '/') for x in value] + # Set compiler + if self.skip_check: + self.update_cmake_compiler_state() + res += '# CMake compiler state variables\n' + for lang, vars in self.cmakestate: + res += f'# -- Variables for language {lang}\n' + res += self._print_vars(vars) + res += '\n' + res += '\n' + # Set variables from the current machine config res += '# Variables from meson\n' - for key, value in self.variables.items(): - res += 'set(' + key - for i in value: - res += f' "{i}"' - - res += ')\n' + res += self._print_vars(self.variables) res += '\n' # Add the user provided toolchain file @@ -121,6 +133,15 @@ class CMakeToolchain: return res + def generate_cache(self) -> str: + if not self.skip_check: + return '' + + res = '' + for name, v in self.cmakestate.cmake_cache.items(): + res += f'{name}:{v.type}={";".join(v.value)}\n' + return res + def get_defaults(self) -> T.Dict[str, T.List[str]]: defaults = {} # type: T.Dict[str, T.List[str]] @@ -151,11 +172,6 @@ class CMakeToolchain: if sys_root: defaults['CMAKE_SYSROOT'] = [sys_root] - # Determine whether CMake the compiler test should be skipped - skip_check = self.properties.get_cmake_skip_compiler_test() == CMakeSkipCompilerTest.ALWAYS - if self.properties.get_cmake_skip_compiler_test() == CMakeSkipCompilerTest.DEP_ONLY and self.exec_scope == CMakeExecScope.DEPENDENCY: - skip_check = True - def make_abs(exe: str) -> str: if Path(exe).is_absolute(): return exe @@ -168,9 +184,6 @@ class CMakeToolchain: # Set the compiler variables for lang, comp_obj in self.compilers.items(): exe_list = [make_abs(x) for x in comp_obj.get_exelist()] - comp_id = CMakeToolchain.meson_compiler_to_cmake_id(comp_obj) - comp_version = comp_obj.version.upper() - prefix = 'CMAKE_{}_'.format(language_map.get(lang, lang.upper())) if not exe_list: @@ -183,35 +196,50 @@ class CMakeToolchain: if comp_obj.get_id() == 'clang-cl': defaults['CMAKE_LINKER'] = comp_obj.get_linker_exelist() - # Setting the variables after this check cause CMake to skip - # validating the compiler - if not skip_check: - continue - - defaults[prefix + 'COMPILER_ID'] = [comp_id] - defaults[prefix + 'COMPILER_VERSION'] = [comp_version] - #defaults[prefix + 'COMPILER_LOADED'] = ['1'] - defaults[prefix + 'COMPILER_FORCED'] = ['1'] - defaults[prefix + 'COMPILER_WORKS'] = ['TRUE'] - #defaults[prefix + 'ABI_COMPILED'] = ['TRUE'] - return defaults - @staticmethod - def meson_compiler_to_cmake_id(cobj: 'Compiler') -> str: - """Translate meson compiler's into CMAKE compiler ID's. - - Most of these can be handled by a simple table lookup, with a few - exceptions. - - Clang and Apple's Clang are both identified as "clang" by meson. To make - things more complicated gcc and vanilla clang both use Apple's ld64 on - macOS. The only way to know for sure is to do an isinstance() check. - """ - from ..compilers import (AppleClangCCompiler, AppleClangCPPCompiler, - AppleClangObjCCompiler, AppleClangObjCPPCompiler) - if isinstance(cobj, (AppleClangCCompiler, AppleClangCPPCompiler, - AppleClangObjCCompiler, AppleClangObjCPPCompiler)): - return 'AppleClang' - # If no mapping, try GNU and hope that the build files don't care - return _MESON_TO_CMAKE_MAPPING.get(cobj.get_id(), 'GNU') + def update_cmake_compiler_state(self) -> None: + # Check if all variables are already cached + if self.cmakestate.languages.issuperset(self.compilers.keys()): + return + + # Generate the CMakeLists.txt + mlog.debug('CMake Toolchain: Calling CMake once to generate the compiler state') + languages = list(self.compilers.keys()) + lang_ids = [language_map.get(x, x.upper()) for x in languages] + cmake_content = dedent(f''' + cmake_minimum_required(VERSION 3.7) + project(CompInfo {' '.join(lang_ids)}) + ''') + + build_dir = Path(self.env.scratch_dir) / '__CMake_compiler_info__' + build_dir.mkdir(parents=True, exist_ok=True) + cmake_file = build_dir / 'CMakeLists.txt' + cmake_file.write_text(cmake_content) + + # Generate the temporary toolchain file + temp_toolchain_file = build_dir / 'CMakeMesonTempToolchainFile.cmake' + temp_toolchain_file.write_text(CMakeToolchain._print_vars(self.variables)) + + # Configure + trace = CMakeTraceParser(self.cmakebin.version(), build_dir) + self.cmakebin.set_exec_mode(print_cmout=False, always_capture_stderr=trace.requires_stderr()) + cmake_args = [*trace.trace_args(), '-DCMAKE_TOOLCHAIN_FILE=' + temp_toolchain_file.as_posix(), '.'] + rc, _, raw_trace = self.cmakebin.call(cmake_args, build_dir=build_dir, disable_cache=True) + + if rc != 0: + mlog.warning('CMake Toolchain: Failed to determine CMake compilers state') + return + + # Parse output + trace.parse(raw_trace) + self.cmakestate.cmake_cache = {**trace.cache} + + vars_by_file = {k.name: v for (k, v) in trace.vars_by_file.items()} + + for lang in languages: + lang_cmake = language_map.get(lang, lang.upper()) + file_name = f'CMake{lang_cmake}Compiler.cmake' + vars = vars_by_file.setdefault(file_name, {}) + vars[f'CMAKE_{lang_cmake}_COMPILER_FORCED'] = ['1'] + self.cmakestate.update(lang, vars) diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 5e726a462..2fbab52c5 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -36,6 +36,7 @@ if T.TYPE_CHECKING: from .compilers.compilers import Compiler, CompileResult # noqa: F401 from .environment import Environment from .mesonlib import OptionOverrideProxy + from .cmake.traceparser import CMakeCacheEntry OptionDictType = T.Union[T.Dict[str, 'UserOption[T.Any]'], OptionOverrideProxy] KeyedOptionDictType = T.Union[T.Dict['OptionKey', 'UserOption[T.Any]'], OptionOverrideProxy] @@ -374,6 +375,34 @@ class DependencyCache: def clear(self) -> None: self.__cache.clear() + +class CMakeStateCache: + """Class that stores internal CMake compiler states. + + This cache is used to reduce the startup overhead of CMake by caching + all internal CMake compiler variables. + """ + + def __init__(self) -> None: + self.__cache: T.Dict[str, T.Dict[str, T.List[str]]] = {} + self.cmake_cache: T.Dict[str, 'CMakeCacheEntry'] = {} + + def __iter__(self) -> T.Iterator[T.Tuple[str, T.Dict[str, T.List[str]]]]: + return iter(self.__cache.items()) + + def items(self) -> T.Iterator[T.Tuple[str, T.Dict[str, T.List[str]]]]: + return iter(self.__cache.items()) + + def update(self, language: str, variables: T.Dict[str, T.List[str]]): + if language not in self.__cache: + self.__cache[language] = {} + self.__cache[language].update(variables) + + @property + def languages(self) -> T.Set[str]: + return set(self.__cache.keys()) + + # Can't bind this near the class method it seems, sadly. _V = T.TypeVar('_V') @@ -414,6 +443,9 @@ class CoreData: self.compiler_check_cache = OrderedDict() # type: T.Dict[CompilerCheckCacheKey, compiler.CompileResult] + # CMake cache + self.cmake_cache: PerMachine[CMakeStateCache] = PerMachine(CMakeStateCache(), CMakeStateCache()) + # Only to print a warning if it changes between Meson invocations. self.config_files = self.__load_config_files(options, scratch_dir, 'native') self.builtin_options_libdir_cross_fixup() diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 225280093..d4b45fa6d 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -1146,7 +1146,7 @@ class CMakeDependency(ExternalDependency): gen_list += CMakeDependency.class_cmake_generators temp_parser = CMakeTraceParser(self.cmakebin.version(), self._get_build_dir()) - toolchain = CMakeToolchain(self.env, self.for_machine, CMakeExecScope.DEPENDENCY, self._get_build_dir()) + toolchain = CMakeToolchain(self.cmakebin, self.env, self.for_machine, CMakeExecScope.DEPENDENCY, self._get_build_dir()) toolchain.write() for i in gen_list: @@ -1346,7 +1346,7 @@ class CMakeDependency(ExternalDependency): # Map the components comp_mapped = self._map_component_list(modules, components) - toolchain = CMakeToolchain(self.env, self.for_machine, CMakeExecScope.DEPENDENCY, self._get_build_dir()) + toolchain = CMakeToolchain(self.cmakebin, self.env, self.for_machine, CMakeExecScope.DEPENDENCY, self._get_build_dir()) toolchain.write() for i in gen_list: From 06f528a5b7a42eadf84961414087117deb2365aa Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 28 May 2021 23:44:58 +0200 Subject: [PATCH 3/5] cmake: Update test case --- .../subprojects/cmMod/CMakeLists.txt | 2 +- .../cmake/23 cmake toolchain/meson.build | 6 +++++- .../23 cmake toolchain/nativefile.ini.in | 1 + .../subprojects/cmMod/CMakeLists.txt | 6 +++++- .../subprojects/cmModFortran/CMakeLists.txt | 19 +++++++++++++++++++ 5 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 test cases/cmake/23 cmake toolchain/subprojects/cmModFortran/CMakeLists.txt diff --git a/test cases/cmake/16 threads/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/16 threads/subprojects/cmMod/CMakeLists.txt index 442a60eec..4d61b0c37 100644 --- a/test cases/cmake/16 threads/subprojects/cmMod/CMakeLists.txt +++ b/test cases/cmake/16 threads/subprojects/cmMod/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) -project(cmMod CXX) +project(cmMod C CXX) set (CMAKE_CXX_STANDARD 14) if(NOT USE_PTHREAD STREQUAL NOT_SET) diff --git a/test cases/cmake/23 cmake toolchain/meson.build b/test cases/cmake/23 cmake toolchain/meson.build index 98f8d21df..8399597a5 100644 --- a/test cases/cmake/23 cmake toolchain/meson.build +++ b/test cases/cmake/23 cmake toolchain/meson.build @@ -1,4 +1,4 @@ -project('cmake toolchain test', ['c', 'cpp']) +project('cmake toolchain test', ['c']) if meson.is_cross_build() error('MESON_SKIP_TEST: skip this on cross builds') @@ -7,3 +7,7 @@ endif cm = import('cmake') sub_pro = cm.subproject('cmMod') + +add_languages('cpp') + +sub_pro = cm.subproject('cmModFortran') diff --git a/test cases/cmake/23 cmake toolchain/nativefile.ini.in b/test cases/cmake/23 cmake toolchain/nativefile.ini.in index 2cd6e947a..1f4037de3 100644 --- a/test cases/cmake/23 cmake toolchain/nativefile.ini.in +++ b/test cases/cmake/23 cmake toolchain/nativefile.ini.in @@ -1,6 +1,7 @@ [properties] cmake_toolchain_file = '@MESON_TEST_ROOT@/CMakeToolchain.cmake' +cmake_skip_compiler_test = 'always' [cmake] diff --git a/test cases/cmake/23 cmake toolchain/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/23 cmake toolchain/subprojects/cmMod/CMakeLists.txt index 8aeabc2a2..a00affaeb 100644 --- a/test cases/cmake/23 cmake toolchain/subprojects/cmMod/CMakeLists.txt +++ b/test cases/cmake/23 cmake toolchain/subprojects/cmMod/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) -project(cmMod) +project(cmMod NONE) if(NOT "${MESON_TEST_VAR1}" STREQUAL "VAR1 space") message(FATAL_ERROR "MESON_TEST_VAR1 -- '${MESON_TEST_VAR1}' != 'VAR1 space'") @@ -9,3 +9,7 @@ endif() if(NOT "${MESON_TEST_VAR2}" STREQUAL "VAR2") message(FATAL_ERROR "MESON_TEST_VAR2 -- '${MESON_TEST_VAR2}' != 'VAR2'") endif() + +if(NOT DEFINED CMAKE_C_COMPILER_VERSION) + message(FATAL_ERROR "CMAKE_C_COMPILER_VERSION was not defined") +endif() diff --git a/test cases/cmake/23 cmake toolchain/subprojects/cmModFortran/CMakeLists.txt b/test cases/cmake/23 cmake toolchain/subprojects/cmModFortran/CMakeLists.txt new file mode 100644 index 000000000..ecf1737fc --- /dev/null +++ b/test cases/cmake/23 cmake toolchain/subprojects/cmModFortran/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.5) + +project(cmMod NONE) + +if(NOT "${MESON_TEST_VAR1}" STREQUAL "VAR1 space") + message(FATAL_ERROR "MESON_TEST_VAR1 -- '${MESON_TEST_VAR1}' != 'VAR1 space'") +endif() + +if(NOT "${MESON_TEST_VAR2}" STREQUAL "VAR2") + message(FATAL_ERROR "MESON_TEST_VAR2 -- '${MESON_TEST_VAR2}' != 'VAR2'") +endif() + +if(NOT DEFINED CMAKE_C_COMPILER_VERSION) + message(FATAL_ERROR "CMAKE_C_COMPILER_VERSION was not defined") +endif() + +if(NOT DEFINED CMAKE_CXX_COMPILER_VERSION) + message(FATAL_ERROR "CMAKE_CXX_COMPILER_VERSION was not defined") +endif() From f0812baf8d1c746a4b5b7d8661a4e3d13af577b7 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 29 May 2021 16:57:25 +0200 Subject: [PATCH 4/5] cmake: exclude generated files from the buildsystem files list --- mesonbuild/cmake/interpreter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 513df60b7..26fc63532 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -953,6 +953,7 @@ class CMakeInterpreter: cmake_files = self.fileapi.get_cmake_sources() self.bs_files = [x.file for x in cmake_files if not x.is_cmake and not x.is_temp] self.bs_files = [relative_to_if_possible(x, Path(self.env.get_source_dir())) for x in self.bs_files] + self.bs_files = [x for x in self.bs_files if not path_is_in_root(x, Path(self.env.get_build_dir()), resolve=True)] self.bs_files = list(OrderedSet(self.bs_files)) # Load the codemodel configurations @@ -980,6 +981,7 @@ class CMakeInterpreter: src_dir = bs_reply.src_dir self.bs_files = [x.file for x in bs_reply.build_files if not x.is_cmake and not x.is_temp] self.bs_files = [relative_to_if_possible(src_dir / x, Path(self.env.get_source_dir()), resolve=True) for x in self.bs_files] + self.bs_files = [x for x in self.bs_files if not path_is_in_root(x, Path(self.env.get_build_dir()), resolve=True)] self.bs_files = list(OrderedSet(self.bs_files)) self.codemodel_configs = cm_reply.configs From 9b5463681e8f5df8dbe628be0a95982584b2343b Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sun, 30 May 2021 16:33:09 +0200 Subject: [PATCH 5/5] cmake: select correct generator in toolchain.py --- mesonbuild/cmake/__init__.py | 4 +++- mesonbuild/cmake/common.py | 20 +++++++++++++++++++- mesonbuild/cmake/interpreter.py | 14 ++------------ mesonbuild/cmake/toolchain.py | 7 +++++-- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/mesonbuild/cmake/__init__.py b/mesonbuild/cmake/__init__.py index b13b5a895..d39bf2424 100644 --- a/mesonbuild/cmake/__init__.py +++ b/mesonbuild/cmake/__init__.py @@ -30,11 +30,13 @@ __all__ = [ 'TargetOptions', 'parse_generator_expressions', 'language_map', + 'backend_generator_map', + 'cmake_get_generator_args', 'cmake_defines_to_args', 'check_cmake_args', ] -from .common import CMakeException, SingleTargetOptions, TargetOptions, cmake_defines_to_args, language_map, check_cmake_args +from .common import CMakeException, SingleTargetOptions, TargetOptions, cmake_defines_to_args, language_map, backend_generator_map, cmake_get_generator_args, check_cmake_args from .client import CMakeClient from .executor import CMakeExecutor from .fileapi import CMakeFileAPI diff --git a/mesonbuild/cmake/common.py b/mesonbuild/cmake/common.py index f0a54b58c..d1f86f083 100644 --- a/mesonbuild/cmake/common.py +++ b/mesonbuild/cmake/common.py @@ -15,11 +15,14 @@ # This class contains the basic functionality needed to run any interpreter # or an interpreter-based tool. -from ..mesonlib import MesonException +from ..mesonlib import MesonException, OptionKey from .. import mlog from pathlib import Path import typing as T +if T.TYPE_CHECKING: + from ..environment import Environment + language_map = { 'c': 'C', 'cpp': 'CXX', @@ -32,6 +35,15 @@ language_map = { 'swift': 'Swift', } +backend_generator_map = { + 'ninja': 'Ninja', + 'xcode': 'Xcode', + 'vs2010': 'Visual Studio 10 2010', + 'vs2015': 'Visual Studio 15 2017', + 'vs2017': 'Visual Studio 15 2017', + 'vs2019': 'Visual Studio 16 2019', +} + blacklist_cmake_defs = [ 'CMAKE_TOOLCHAIN_FILE', 'CMAKE_PROJECT_INCLUDE', @@ -87,6 +99,12 @@ def _flags_to_list(raw: str) -> T.List[str]: res = list(filter(lambda x: len(x) > 0, res)) return res +def cmake_get_generator_args(env: 'Environment') -> T.List[str]: + backend_name = env.coredata.get_option(OptionKey('backend')) + assert isinstance(backend_name, str) + assert backend_name in backend_generator_map + return ['-G', backend_generator_map[backend_name]] + def cmake_defines_to_args(raw: T.Any, permissive: bool = False) -> T.List[str]: res = [] # type: T.List[str] if not isinstance(raw, list): diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 26fc63532..cc6adf18c 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -15,7 +15,7 @@ # This class contains the basic functionality needed to run any interpreter # or an interpreter-based tool. -from .common import CMakeException, CMakeTarget, TargetOptions, CMakeConfiguration, language_map, check_cmake_args +from .common import CMakeException, CMakeTarget, TargetOptions, CMakeConfiguration, language_map, backend_generator_map, cmake_get_generator_args, check_cmake_args from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel, ReplyCMakeInputs, ReplyCodeModel from .fileapi import CMakeFileAPI from .executor import CMakeExecutor @@ -74,15 +74,6 @@ disable_policy_warnings = [ 'CMP0102', ] -backend_generator_map = { - 'ninja': 'Ninja', - 'xcode': 'Xcode', - 'vs2010': 'Visual Studio 10 2010', - 'vs2015': 'Visual Studio 15 2017', - 'vs2017': 'Visual Studio 15 2017', - 'vs2019': 'Visual Studio 16 2019', -} - target_type_map = { 'STATIC_LIBRARY': 'static_library', 'MODULE_LIBRARY': 'shared_module', @@ -898,9 +889,8 @@ class CMakeInterpreter: # TODO: drop this check once the deprecated `cmake_args` kwarg is removed extra_cmake_options = check_cmake_args(extra_cmake_options) - generator = backend_generator_map[self.backend_name] cmake_args = [] - cmake_args += ['-G', generator] + cmake_args += cmake_get_generator_args(self.env) cmake_args += [f'-DCMAKE_INSTALL_PREFIX={self.install_prefix}'] cmake_args += extra_cmake_options trace_args = self.trace.trace_args() diff --git a/mesonbuild/cmake/toolchain.py b/mesonbuild/cmake/toolchain.py index c5056d7e8..f3e487d15 100644 --- a/mesonbuild/cmake/toolchain.py +++ b/mesonbuild/cmake/toolchain.py @@ -16,7 +16,7 @@ from pathlib import Path from .traceparser import CMakeTraceParser from ..envconfig import CMakeSkipCompilerTest from ..mesonlib import MachineChoice -from .common import language_map +from .common import language_map, cmake_get_generator_args from .. import mlog import shutil @@ -224,7 +224,10 @@ class CMakeToolchain: # Configure trace = CMakeTraceParser(self.cmakebin.version(), build_dir) self.cmakebin.set_exec_mode(print_cmout=False, always_capture_stderr=trace.requires_stderr()) - cmake_args = [*trace.trace_args(), '-DCMAKE_TOOLCHAIN_FILE=' + temp_toolchain_file.as_posix(), '.'] + cmake_args = [] + cmake_args += trace.trace_args() + cmake_args += cmake_get_generator_args(self.env) + cmake_args += [f'-DCMAKE_TOOLCHAIN_FILE={temp_toolchain_file.as_posix()}', '.'] rc, _, raw_trace = self.cmakebin.call(cmake_args, build_dir=build_dir, disable_cache=True) if rc != 0: