# 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 += f' "{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')