diff options
Diffstat (limited to 'mesonbuild/cmake')
-rw-r--r-- | mesonbuild/cmake/__init__.py | 7 | ||||
-rw-r--r-- | mesonbuild/cmake/client.py | 14 | ||||
-rw-r--r-- | mesonbuild/cmake/common.py | 12 | ||||
-rw-r--r-- | mesonbuild/cmake/executor.py | 185 | ||||
-rw-r--r-- | mesonbuild/cmake/interpreter.py | 100 | ||||
-rw-r--r-- | mesonbuild/cmake/toolchain.py | 217 |
6 files changed, 292 insertions, 243 deletions
diff --git a/mesonbuild/cmake/__init__.py b/mesonbuild/cmake/__init__.py index db7aefd..9840a5f 100644 --- a/mesonbuild/cmake/__init__.py +++ b/mesonbuild/cmake/__init__.py @@ -18,10 +18,12 @@ __all__ = [ 'CMakeClient', 'CMakeExecutor', + 'CMakeExecScope', 'CMakeException', 'CMakeFileAPI', 'CMakeInterpreter', 'CMakeTarget', + 'CMakeToolchain', 'CMakeTraceLine', 'CMakeTraceParser', 'SingleTargetOptions', @@ -31,10 +33,11 @@ __all__ = [ 'cmake_defines_to_args', ] -from .common import CMakeException, SingleTargetOptions, TargetOptions, cmake_defines_to_args +from .common import CMakeException, SingleTargetOptions, TargetOptions, cmake_defines_to_args, language_map from .client import CMakeClient from .executor import CMakeExecutor from .fileapi import CMakeFileAPI from .generator import parse_generator_expressions -from .interpreter import CMakeInterpreter, language_map +from .interpreter import CMakeInterpreter +from .toolchain import CMakeToolchain, CMakeExecScope from .traceparser import CMakeTarget, CMakeTraceLine, CMakeTraceParser diff --git a/mesonbuild/cmake/client.py b/mesonbuild/cmake/client.py index ce79e8e..8f9456b 100644 --- a/mesonbuild/cmake/client.py +++ b/mesonbuild/cmake/client.py @@ -16,8 +16,6 @@ # or an interpreter-based tool. from .common import CMakeException, CMakeConfiguration, CMakeBuildFile -from .executor import CMakeExecutor -from ..mesonlib import MachineChoice from .. import mlog from contextlib import contextmanager from subprocess import Popen, PIPE, TimeoutExpired @@ -27,6 +25,7 @@ import json if T.TYPE_CHECKING: from ..environment import Environment + from .executor import CMakeExecutor CMAKE_SERVER_BEGIN_STR = '[== "CMake Server" ==[' CMAKE_SERVER_END_STR = ']== "CMake Server" ==]' @@ -331,20 +330,17 @@ class CMakeClient: return ReplyCMakeInputs(data['cookie'], Path(data['cmakeRootDirectory']), Path(data['sourceDirectory']), files) @contextmanager - def connect(self) -> T.Generator[None, None, None]: - self.startup() + def connect(self, cmake_exe: 'CMakeExecutor') -> T.Generator[None, None, None]: + self.startup(cmake_exe) try: yield finally: self.shutdown() - def startup(self) -> None: + def startup(self, cmake_exe: 'CMakeExecutor') -> None: if self.proc is not None: raise CMakeException('The CMake server was already started') - for_machine = MachineChoice.HOST # TODO make parameter - cmake_exe = CMakeExecutor(self.env, '>=3.7', for_machine) - if not cmake_exe.found(): - raise CMakeException('Unable to find CMake') + assert cmake_exe.found() mlog.debug('Starting CMake server with CMake', mlog.bold(' '.join(cmake_exe.get_command())), 'version', mlog.cyan(cmake_exe.version())) self.proc = Popen(cmake_exe.get_command() + ['-E', 'server', '--experimental', '--debug'], stdin=PIPE, stdout=PIPE) diff --git a/mesonbuild/cmake/common.py b/mesonbuild/cmake/common.py index 21460ca..3534ec0 100644 --- a/mesonbuild/cmake/common.py +++ b/mesonbuild/cmake/common.py @@ -20,6 +20,18 @@ from .. import mlog from .._pathlib import Path import typing as T +language_map = { + 'c': 'C', + 'cpp': 'CXX', + 'cuda': 'CUDA', + 'objc': 'OBJC', + 'objcpp': 'OBJCXX', + 'cs': 'CSharp', + 'java': 'Java', + 'fortran': 'Fortran', + 'swift': 'Swift', +} + class CMakeException(MesonException): pass diff --git a/mesonbuild/cmake/executor.py b/mesonbuild/cmake/executor.py index 0413b56..e11dbe9 100644 --- a/mesonbuild/cmake/executor.py +++ b/mesonbuild/cmake/executor.py @@ -21,59 +21,19 @@ from threading import Thread import typing as T import re import os -import shutil -import ctypes -import textwrap -from .. import mlog, mesonlib -from ..mesonlib import PerMachine, Popen_safe, version_compare, MachineChoice -from ..environment import Environment +from .. import mlog +from ..mesonlib import PerMachine, Popen_safe, version_compare, MachineChoice, is_windows from ..envconfig import get_env_var -from ..compilers import ( - AppleClangCCompiler, AppleClangCPPCompiler, AppleClangObjCCompiler, - AppleClangObjCPPCompiler -) if T.TYPE_CHECKING: + from ..environment import Environment from ..dependencies.base import ExternalProgram from ..compilers import Compiler TYPE_result = T.Tuple[int, T.Optional[str], T.Optional[str]] TYPE_cache_key = T.Tuple[str, T.Tuple[str, ...], str, T.FrozenSet[T.Tuple[str, str]]] -_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', -} - -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. - """ - 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') - - class CMakeExecutor: # The class's copy of the CMake path. Avoids having to search for it # multiple times in the same Meson invocation. @@ -81,7 +41,7 @@ class CMakeExecutor: class_cmakevers = PerMachine(None, None) # type: PerMachine[T.Optional[str]] class_cmake_cache = {} # type: T.Dict[T.Any, TYPE_result] - def __init__(self, environment: Environment, version: str, for_machine: MachineChoice, silent: bool = False): + def __init__(self, environment: 'Environment', version: str, for_machine: MachineChoice, silent: bool = False): self.min_version = version self.environment = environment self.for_machine = for_machine @@ -109,7 +69,7 @@ class CMakeExecutor: 'CMAKE_PREFIX_PATH') if env_pref_path_raw is not None: env_pref_path = [] # type: T.List[str] - if mesonlib.is_windows(): + if is_windows(): # Cannot split on ':' on Windows because its in the drive letter env_pref_path = env_pref_path_raw.split(os.pathsep) else: @@ -123,7 +83,7 @@ class CMakeExecutor: if self.prefix_paths: self.extra_cmake_args += ['-DCMAKE_PREFIX_PATH={}'.format(';'.join(self.prefix_paths))] - def find_cmake_binary(self, environment: Environment, silent: bool = False) -> T.Tuple[T.Optional['ExternalProgram'], T.Optional[str]]: + def find_cmake_binary(self, environment: 'Environment', silent: bool = False) -> T.Tuple[T.Optional['ExternalProgram'], T.Optional[str]]: from ..dependencies.base import find_external_program, NonExistingExternalProgram # Only search for CMake the first time and store the result in the class @@ -176,7 +136,7 @@ class CMakeExecutor: return None except PermissionError: msg = 'Found CMake {!r} but didn\'t have permissions to run it.'.format(' '.join(cmakebin.get_command())) - if not mesonlib.is_windows(): + if not is_windows(): msg += '\n\nOn Unix-like systems this is often caused by scripts that are not executable.' mlog.warning(msg) return None @@ -257,11 +217,12 @@ class CMakeExecutor: rc = ret.returncode out = ret.stdout.decode(errors='ignore') err = ret.stderr.decode(errors='ignore') - call = ' '.join(cmd) - mlog.debug("Called `{}` in {} -> {}".format(call, build_dir, rc)) return rc, out, err def _call_impl(self, args: T.List[str], build_dir: Path, env: T.Optional[T.Dict[str, str]]) -> TYPE_result: + mlog.debug('Calling CMake ({}) in {} with:'.format(self.cmakebin.get_command(), build_dir)) + for i in args: + mlog.debug(' - "{}"'.format(i)) if not self.print_cmout: return self._call_quiet(args, build_dir, env) else: @@ -285,132 +246,6 @@ class CMakeExecutor: cache[key] = self._call_impl(args, build_dir, env) return cache[key] - def call_with_fake_build(self, args: T.List[str], build_dir: Path, env: T.Optional[T.Dict[str, str]] = None) -> TYPE_result: - # First check the cache - cache = CMakeExecutor.class_cmake_cache - key = self._cache_key(args, build_dir, env) - if key in cache: - return cache[key] - - build_dir.mkdir(exist_ok=True, parents=True) - - # Try to set the correct compiler for C and C++ - # This step is required to make try_compile work inside CMake - fallback = Path(__file__).resolve() # A file used as a fallback wehen everything else fails - compilers = self.environment.coredata.compilers[MachineChoice.BUILD] - - def make_abs(exe: str, lang: str) -> str: - if Path(exe).is_absolute(): - return exe - - p = shutil.which(exe) - if p is None: - mlog.debug('Failed to find a {} compiler for CMake. This might cause CMake to fail.'.format(lang)) - return str(fallback) - return p - - def choose_compiler(lang: str) -> T.Tuple[str, str, str, str]: - comp_obj = None - exe_list = [] - if lang in compilers: - comp_obj = compilers[lang] - else: - try: - comp_obj = self.environment.compiler_from_language(lang, MachineChoice.BUILD) - except Exception: - pass - - if comp_obj is not None: - exe_list = comp_obj.get_exelist() - comp_id = meson_compiler_to_cmake_id(comp_obj) - comp_version = comp_obj.version.upper() - - if len(exe_list) == 1: - return make_abs(exe_list[0], lang), '', comp_id, comp_version - elif len(exe_list) == 2: - return make_abs(exe_list[1], lang), make_abs(exe_list[0], lang), comp_id, comp_version - else: - mlog.debug('Failed to find a {} compiler for CMake. This might cause CMake to fail.'.format(lang)) - return str(fallback), '', 'GNU', '' - - c_comp, c_launcher, c_id, c_version = choose_compiler('c') - cxx_comp, cxx_launcher, cxx_id, cxx_version = choose_compiler('cpp') - fortran_comp, fortran_launcher, _, _ = choose_compiler('fortran') - - # on Windows, choose_compiler returns path with \ as separator - replace by / before writing to CMAKE file - c_comp = c_comp.replace('\\', '/') - c_launcher = c_launcher.replace('\\', '/') - cxx_comp = cxx_comp.replace('\\', '/') - cxx_launcher = cxx_launcher.replace('\\', '/') - fortran_comp = fortran_comp.replace('\\', '/') - fortran_launcher = fortran_launcher.replace('\\', '/') - - # Reset the CMake cache - (build_dir / 'CMakeCache.txt').write_text('CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1\n') - - # Fake the compiler files - comp_dir = build_dir / 'CMakeFiles' / self.cmakevers - comp_dir.mkdir(parents=True, exist_ok=True) - - c_comp_file = comp_dir / 'CMakeCCompiler.cmake' - cxx_comp_file = comp_dir / 'CMakeCXXCompiler.cmake' - fortran_comp_file = comp_dir / 'CMakeFortranCompiler.cmake' - - if c_comp and not c_comp_file.is_file(): - is_gnu = '1' if c_id == 'GNU' else '' - c_comp_file.write_text(textwrap.dedent('''\ - # Fake CMake file to skip the boring and slow stuff - set(CMAKE_C_COMPILER "{}") # Should be a valid compiler for try_compile, etc. - set(CMAKE_C_COMPILER_LAUNCHER "{}") # The compiler launcher (if presentt) - set(CMAKE_COMPILER_IS_GNUCC {}) - set(CMAKE_C_COMPILER_ID "{}") - set(CMAKE_C_COMPILER_VERSION "{}") - set(CMAKE_C_COMPILER_LOADED 1) - set(CMAKE_C_COMPILER_FORCED 1) - set(CMAKE_C_COMPILER_WORKS TRUE) - set(CMAKE_C_ABI_COMPILED TRUE) - set(CMAKE_C_SOURCE_FILE_EXTENSIONS c;m) - set(CMAKE_C_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC) - set(CMAKE_SIZEOF_VOID_P "{}") - '''.format(c_comp, c_launcher, is_gnu, c_id, c_version, - ctypes.sizeof(ctypes.c_void_p)))) - - if cxx_comp and not cxx_comp_file.is_file(): - is_gnu = '1' if cxx_id == 'GNU' else '' - cxx_comp_file.write_text(textwrap.dedent('''\ - # Fake CMake file to skip the boring and slow stuff - set(CMAKE_CXX_COMPILER "{}") # Should be a valid compiler for try_compile, etc. - set(CMAKE_CXX_COMPILER_LAUNCHER "{}") # The compiler launcher (if presentt) - set(CMAKE_COMPILER_IS_GNUCXX {}) - set(CMAKE_CXX_COMPILER_ID "{}") - set(CMAKE_CXX_COMPILER_VERSION "{}") - set(CMAKE_CXX_COMPILER_LOADED 1) - set(CMAKE_CXX_COMPILER_FORCED 1) - set(CMAKE_CXX_COMPILER_WORKS TRUE) - set(CMAKE_CXX_ABI_COMPILED TRUE) - set(CMAKE_CXX_IGNORE_EXTENSIONS inl;h;hpp;HPP;H;o;O;obj;OBJ;def;DEF;rc;RC) - set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;mm;CPP) - set(CMAKE_SIZEOF_VOID_P "{}") - '''.format(cxx_comp, cxx_launcher, is_gnu, cxx_id, cxx_version, - ctypes.sizeof(ctypes.c_void_p)))) - - if fortran_comp and not fortran_comp_file.is_file(): - fortran_comp_file.write_text(textwrap.dedent('''\ - # Fake CMake file to skip the boring and slow stuff - set(CMAKE_Fortran_COMPILER "{}") # Should be a valid compiler for try_compile, etc. - set(CMAKE_Fortran_COMPILER_LAUNCHER "{}") # The compiler launcher (if presentt) - set(CMAKE_Fortran_COMPILER_ID "GNU") # Pretend we have found GCC - set(CMAKE_COMPILER_IS_GNUG77 1) - set(CMAKE_Fortran_COMPILER_LOADED 1) - set(CMAKE_Fortran_COMPILER_WORKS TRUE) - set(CMAKE_Fortran_ABI_COMPILED TRUE) - set(CMAKE_Fortran_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC) - set(CMAKE_Fortran_SOURCE_FILE_EXTENSIONS f;F;fpp;FPP;f77;F77;f90;F90;for;For;FOR;f95;F95) - set(CMAKE_SIZEOF_VOID_P "{}") - '''.format(fortran_comp, fortran_launcher, ctypes.sizeof(ctypes.c_void_p)))) - - return self.call(args, build_dir, env) - def found(self) -> bool: return self.cmakebin is not None diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index cad4509..03ed90d 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -15,10 +15,11 @@ # This class contains the basic functionality needed to run any interpreter # or an interpreter-based tool. -from .common import CMakeException, CMakeTarget, TargetOptions, CMakeConfiguration +from .common import CMakeException, CMakeTarget, TargetOptions, CMakeConfiguration, language_map from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel, ReplyCMakeInputs, ReplyCodeModel from .fileapi import CMakeFileAPI from .executor import CMakeExecutor +from .toolchain import CMakeToolchain, CMakeExecScope from .traceparser import CMakeTraceParser, CMakeGeneratorTarget from .. import mlog, mesonlib from ..mesonlib import MachineChoice, OrderedSet, version_compare, path_is_in_root, relative_to_if_possible @@ -69,6 +70,7 @@ disable_policy_warnings = [ 'CMP0067', 'CMP0082', 'CMP0089', + 'CMP0102', ] backend_generator_map = { @@ -80,18 +82,6 @@ backend_generator_map = { 'vs2019': 'Visual Studio 16 2019', } -language_map = { - 'c': 'C', - 'cpp': 'CXX', - 'cuda': 'CUDA', - 'objc': 'OBJC', - 'objcpp': 'OBJCXX', - 'cs': 'CSharp', - 'java': 'Java', - 'fortran': 'Fortran', - 'swift': 'Swift', -} - target_type_map = { 'STATIC_LIBRARY': 'static_library', 'MODULE_LIBRARY': 'shared_module', @@ -221,8 +211,9 @@ class OutputTargetMap: return '__art_{}__'.format(fname.name) class ConverterTarget: - def __init__(self, target: CMakeTarget, env: 'Environment') -> None: + def __init__(self, target: CMakeTarget, env: 'Environment', for_machine: MachineChoice) -> None: self.env = env + self.for_machine = for_machine self.artifacts = target.artifacts self.src_dir = target.src_dir self.build_dir = target.build_dir @@ -653,7 +644,7 @@ class ConverterCustomTarget: tgt_counter = 0 # type: int out_counter = 0 # type: int - def __init__(self, target: CMakeGeneratorTarget) -> None: + def __init__(self, target: CMakeGeneratorTarget, env: 'Environment', for_machine: MachineChoice) -> None: assert target.current_bin_dir is not None assert target.current_src_dir is not None self.name = target.name @@ -671,6 +662,8 @@ class ConverterCustomTarget: self.depends = [] # type: T.List[T.Union[ConverterTarget, ConverterCustomTarget]] self.current_bin_dir = target.current_bin_dir # type: Path self.current_src_dir = target.current_src_dir # type: Path + self.env = env + self.for_machine = for_machine self._raw_target = target # Convert the target name to a valid meson target name @@ -723,6 +716,11 @@ class ConverterCustomTarget: continue target = output_target_map.executable(j) if target: + # When cross compiling, binaries have to be executed with an exe_wrapper (for instance wine for mingw-w64) + if self.env.exe_wrapper is not None and self.env.properties[self.for_machine].get_cmake_use_exe_wrapper(): + from ..dependencies import ExternalProgram + assert isinstance(self.env.exe_wrapper, ExternalProgram) + cmd += self.env.exe_wrapper.get_command() cmd += [target] continue elif j in trace.targets: @@ -821,6 +819,7 @@ class CMakeInterpreter: self.build_dir = Path(env.get_build_dir()) / self.build_dir_rel self.install_prefix = install_prefix self.env = env + self.for_machine = MachineChoice.HOST # TODO make parameter self.backend_name = backend.name self.linkers = set() # type: T.Set[str] self.cmake_api = CMakeAPI.SERVER @@ -844,65 +843,53 @@ class CMakeInterpreter: self.generated_targets = {} # type: T.Dict[str, T.Dict[str, T.Optional[str]]] self.internal_name_map = {} # type: T.Dict[str, str] - def configure(self, extra_cmake_options: T.List[str]) -> None: - for_machine = MachineChoice.HOST # TODO make parameter + # Do some special handling for object libraries for certain configurations + self._object_lib_workaround = False + if self.backend_name.startswith('vs'): + for comp in self.env.coredata.compilers[self.for_machine].values(): + if comp.get_linker_id() == 'link': + self._object_lib_workaround = True + break + + def configure(self, extra_cmake_options: T.List[str]) -> CMakeExecutor: # Find CMake - cmake_exe = CMakeExecutor(self.env, '>=3.7', for_machine) + cmake_exe = CMakeExecutor(self.env, '>=3.7', MachineChoice.BUILD) if not cmake_exe.found(): raise CMakeException('Unable to find CMake') self.trace = CMakeTraceParser(cmake_exe.version(), self.build_dir, permissive=True) preload_file = mesondata['cmake/data/preload.cmake'].write_to_private(self.env) - - # Prefere CMAKE_PROJECT_INCLUDE over CMAKE_TOOLCHAIN_FILE if possible, - # since CMAKE_PROJECT_INCLUDE was actually designed for code injection. - preload_var = 'CMAKE_PROJECT_INCLUDE' - if version_compare(cmake_exe.version(), '<3.15'): - preload_var = 'CMAKE_TOOLCHAIN_FILE' + toolchain = CMakeToolchain(self.env, self.for_machine, CMakeExecScope.SUBPROJECT, self.build_dir.parent, preload_file) + toolchain_file = toolchain.write() generator = backend_generator_map[self.backend_name] cmake_args = [] + cmake_args += ['-G', generator] + cmake_args += ['-DCMAKE_INSTALL_PREFIX={}'.format(self.install_prefix)] + cmake_args += extra_cmake_options trace_args = self.trace.trace_args() cmcmp_args = ['-DCMAKE_POLICY_WARNING_{}=OFF'.format(x) for x in disable_policy_warnings] - pload_args = ['-D{}={}'.format(preload_var, str(preload_file))] if version_compare(cmake_exe.version(), '>=3.14'): self.cmake_api = CMakeAPI.FILE self.fileapi.setup_request() - # Map meson compiler to CMake variables - for lang, comp in self.env.coredata.compilers[for_machine].items(): - if lang not in language_map: - continue - self.linkers.add(comp.get_linker_id()) - cmake_lang = language_map[lang] - exelist = comp.get_exelist() - if len(exelist) == 1: - cmake_args += ['-DCMAKE_{}_COMPILER={}'.format(cmake_lang, exelist[0])] - elif len(exelist) == 2: - cmake_args += ['-DCMAKE_{}_COMPILER_LAUNCHER={}'.format(cmake_lang, exelist[0]), - '-DCMAKE_{}_COMPILER={}'.format(cmake_lang, exelist[1])] - if hasattr(comp, 'get_linker_exelist') and comp.get_id() == 'clang-cl': - cmake_args += ['-DCMAKE_LINKER={}'.format(comp.get_linker_exelist()[0])] - cmake_args += ['-G', generator] - cmake_args += ['-DCMAKE_INSTALL_PREFIX={}'.format(self.install_prefix)] - cmake_args += extra_cmake_options - # Run CMake mlog.log() with mlog.nested(): mlog.log('Configuring the build directory with', mlog.bold('CMake'), 'version', mlog.cyan(cmake_exe.version())) - mlog.log(mlog.bold('Running:'), ' '.join(cmake_args)) + mlog.log(mlog.bold('Running CMake with:'), ' '.join(cmake_args)) mlog.log(mlog.bold(' - build directory: '), self.build_dir.as_posix()) mlog.log(mlog.bold(' - source directory: '), self.src_dir.as_posix()) - mlog.log(mlog.bold(' - trace args: '), ' '.join(trace_args)) + mlog.log(mlog.bold(' - toolchain file: '), toolchain_file.as_posix()) mlog.log(mlog.bold(' - preload file: '), preload_file.as_posix()) + mlog.log(mlog.bold(' - trace args: '), ' '.join(trace_args)) mlog.log(mlog.bold(' - disabled policy warnings:'), '[{}]'.format(', '.join(disable_policy_warnings))) mlog.log() self.build_dir.mkdir(parents=True, exist_ok=True) os_env = environ.copy() os_env['LC_ALL'] = 'C' - final_args = cmake_args + trace_args + cmcmp_args + pload_args + [self.src_dir.as_posix()] + final_args = cmake_args + trace_args + cmcmp_args + toolchain.get_cmake_args() + [self.src_dir.as_posix()] cmake_exe.set_exec_mode(print_cmout=True, always_capture_stderr=self.trace.requires_stderr()) rc, _, self.raw_trace = cmake_exe.call(final_args, self.build_dir, env=os_env, disable_cache=True) @@ -913,11 +900,13 @@ class CMakeInterpreter: if rc != 0: raise CMakeException('Failed to configure the CMake subproject') + return cmake_exe + def initialise(self, extra_cmake_options: T.List[str]) -> None: # Run configure the old way because doing it # with the server doesn't work for some reason # Additionally, the File API requires a configure anyway - self.configure(extra_cmake_options) + cmake_exe = self.configure(extra_cmake_options) # Continue with the file API If supported if self.cmake_api is CMakeAPI.FILE: @@ -934,7 +923,7 @@ class CMakeInterpreter: self.codemodel_configs = self.fileapi.get_cmake_configurations() return - with self.client.connect(): + with self.client.connect(cmake_exe): generator = backend_generator_map[self.backend_name] self.client.do_handshake(self.src_dir, self.build_dir, generator, 1) @@ -982,7 +971,7 @@ class CMakeInterpreter: # dummy CMake internal target types if k_0.type not in skip_targets and k_0.name not in added_target_names: added_target_names += [k_0.name] - self.targets += [ConverterTarget(k_0, self.env)] + self.targets += [ConverterTarget(k_0, self.env, self.for_machine)] # Add interface targets from trace, if not already present. # This step is required because interface targets were removed from @@ -997,10 +986,10 @@ class CMakeInterpreter: 'sourceDirectory': self.src_dir, 'buildDirectory': self.build_dir, }) - self.targets += [ConverterTarget(dummy, self.env)] + self.targets += [ConverterTarget(dummy, self.env, self.for_machine)] for i_2 in self.trace.custom_targets: - self.custom_targets += [ConverterCustomTarget(i_2)] + self.custom_targets += [ConverterCustomTarget(i_2, self.env, self.for_machine)] # generate the output_target_map for i_3 in [*self.targets, *self.custom_targets]: @@ -1020,7 +1009,7 @@ class CMakeInterpreter: # Second pass: Detect object library dependencies for tgt in self.targets: - tgt.process_object_libs(object_libs, self._object_lib_workaround()) + tgt.process_object_libs(object_libs, self._object_lib_workaround) # Third pass: Reassign dependencies to avoid some loops for tgt in self.targets: @@ -1279,7 +1268,7 @@ class CMakeInterpreter: detect_cycle(tgt) tgt_var = tgt.name # type: str - def resolve_source(x: T.Any) -> T.Any: + def resolve_source(x: T.Union[str, ConverterTarget, ConverterCustomTarget, CustomTargetReference]) -> T.Union[str, IdNode, IndexNode]: if isinstance(x, ConverterTarget): if x.name not in processed: process_target(x) @@ -1296,7 +1285,7 @@ class CMakeInterpreter: return x # Generate the command list - command = [] + command = [] # type: T.List[T.Union[str, IdNode, IndexNode]] command += mesonlib.meson_command command += ['--internal', 'cmake_run_ctgt'] command += ['-o', '@OUTPUT@'] @@ -1346,6 +1335,3 @@ class CMakeInterpreter: def target_list(self) -> T.List[str]: return list(self.internal_name_map.keys()) - - def _object_lib_workaround(self) -> bool: - return 'link' in self.linkers and self.backend_name.startswith('vs') diff --git a/mesonbuild/cmake/toolchain.py b/mesonbuild/cmake/toolchain.py new file mode 100644 index 0000000..52d1f10 --- /dev/null +++ b/mesonbuild/cmake/toolchain.py @@ -0,0 +1,217 @@ +# Copyright 2020 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._pathlib import Path +from ..envconfig import CMakeSkipCompilerTest +from ..mesonlib import MachineChoice +from .common import language_map +from .. import mlog + +import shutil +import typing as T +from enum import Enum +from textwrap import dedent + +if T.TYPE_CHECKING: + 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: + self.env = env + 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.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.variables = self.get_defaults() + self.variables.update(self.cmakevars.get_variables()) + + 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()) + mlog.cmd_ci_include(self.toolchain_file.as_posix()) + return self.toolchain_file + + def get_cmake_args(self) -> T.List[str]: + args = ['-DCMAKE_TOOLCHAIN_FILE=' + self.toolchain_file.as_posix()] + if self.preload_file is not None: + args += ['-DMESON_PRELOAD_FILE=' + self.preload_file.as_posix()] + return args + + def generate(self) -> str: + res = dedent('''\ + ###################################### + ### AUTOMATICALLY GENERATED FILE ### + ###################################### + + # This file was generated from the configuration in the + # relevant meson machine file. See the meson documentation + # https://mesonbuild.com/Machine-files.html for more information + + if(DEFINED MESON_PRELOAD_FILE) + include("${MESON_PRELOAD_FILE}") + endif() + + ''') + + # Escape all \ in the values + for key, value in self.variables.items(): + self.variables[key] = [x.replace('\\', '/') for x in value] + + # 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 += ' "{}"'.format(i) + + res += ')\n' + res += '\n' + + # Add the user provided toolchain file + user_file = self.properties.get_cmake_toolchain_file() + if user_file is not None: + res += dedent(''' + # Load the CMake toolchain file specified by the user + include("{}") + + '''.format(user_file.as_posix())) + + return res + + def get_defaults(self) -> T.Dict[str, T.List[str]]: + defaults = {} # type: T.Dict[str, T.List[str]] + + # Do nothing if the user does not want automatic defaults + if not self.properties.get_cmake_defaults(): + return defaults + + # Best effort to map the meson system name to CMAKE_SYSTEM_NAME, which + # is not trivial since CMake lacks a list of all supported + # CMAKE_SYSTEM_NAME values. + SYSTEM_MAP = { + 'android': 'Android', + 'linux': 'Linux', + 'windows': 'Windows', + 'freebsd': 'FreeBSD', + 'darwin': 'Darwin', + } # type: T.Dict[str, str] + + # Only set these in a cross build. Otherwise CMake will trip up in native + # builds and thing they are cross (which causes TRY_RUN() to break) + if self.env.is_cross_build(when_building_for=self.for_machine): + defaults['CMAKE_SYSTEM_NAME'] = [SYSTEM_MAP.get(self.minfo.system, self.minfo.system)] + defaults['CMAKE_SYSTEM_PROCESSOR'] = [self.minfo.cpu_family] + + defaults['CMAKE_SIZEOF_VOID_P'] = ['8' if self.minfo.is_64_bit else '4'] + + sys_root = self.properties.get_sys_root() + 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 + + p = shutil.which(exe) + if p is None: + return exe + return p + + # 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: + continue + elif len(exe_list) == 2: + defaults[prefix + 'COMPILER'] = [exe_list[1]] + defaults[prefix + 'COMPILER_LAUNCHER'] = [exe_list[0]] + else: + defaults[prefix + 'COMPILER'] = exe_list + 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') |