diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-29 04:41:38 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-29 04:41:38 +0000 |
commit | 7b6e527f440cd7e6f8be2b07cee320ee6ca18786 (patch) | |
tree | 4a2738d69fa2814659fdadddf5826282e73d81f4 /mesonbuild/compilers | |
parent | Initial commit. (diff) | |
download | meson-7b6e527f440cd7e6f8be2b07cee320ee6ca18786.tar.xz meson-7b6e527f440cd7e6f8be2b07cee320ee6ca18786.zip |
Adding upstream version 1.0.1.upstream/1.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mesonbuild/compilers')
33 files changed, 12260 insertions, 0 deletions
diff --git a/mesonbuild/compilers/__init__.py b/mesonbuild/compilers/__init__.py new file mode 100644 index 0000000..c516aab --- /dev/null +++ b/mesonbuild/compilers/__init__.py @@ -0,0 +1,97 @@ +# Copyright 2017 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. + +# Public symbols for compilers sub-package when using 'from . import compilers' +__all__ = [ + 'Compiler', + 'RunResult', + + 'all_languages', + 'base_options', + 'clib_langs', + 'clink_langs', + 'c_suffixes', + 'cpp_suffixes', + 'get_base_compile_args', + 'get_base_link_args', + 'is_assembly', + 'is_header', + 'is_library', + 'is_llvm_ir', + 'is_object', + 'is_source', + 'is_known_suffix', + 'lang_suffixes', + 'LANGUAGES_USING_LDFLAGS', + 'sort_clink', + 'SUFFIX_TO_LANG', + + 'compiler_from_language', + 'detect_compiler_for', + 'detect_static_linker', + 'detect_c_compiler', + 'detect_cpp_compiler', + 'detect_cuda_compiler', + 'detect_fortran_compiler', + 'detect_objc_compiler', + 'detect_objcpp_compiler', + 'detect_java_compiler', + 'detect_cs_compiler', + 'detect_vala_compiler', + 'detect_rust_compiler', + 'detect_d_compiler', + 'detect_swift_compiler', +] + +# Bring symbols from each module into compilers sub-package namespace +from .compilers import ( + Compiler, + RunResult, + all_languages, + base_options, + clib_langs, + clink_langs, + c_suffixes, + cpp_suffixes, + get_base_compile_args, + get_base_link_args, + is_header, + is_source, + is_assembly, + is_llvm_ir, + is_object, + is_library, + is_known_suffix, + lang_suffixes, + LANGUAGES_USING_LDFLAGS, + sort_clink, + SUFFIX_TO_LANG, +) +from .detect import ( + compiler_from_language, + detect_compiler_for, + detect_static_linker, + detect_c_compiler, + detect_cpp_compiler, + detect_cuda_compiler, + detect_objc_compiler, + detect_objcpp_compiler, + detect_fortran_compiler, + detect_java_compiler, + detect_cs_compiler, + detect_vala_compiler, + detect_rust_compiler, + detect_d_compiler, + detect_swift_compiler, +) diff --git a/mesonbuild/compilers/asm.py b/mesonbuild/compilers/asm.py new file mode 100644 index 0000000..9a95599 --- /dev/null +++ b/mesonbuild/compilers/asm.py @@ -0,0 +1,231 @@ +import os +import typing as T + +from ..mesonlib import EnvironmentException, get_meson_command +from .compilers import Compiler + +if T.TYPE_CHECKING: + from ..environment import Environment + +nasm_optimization_args = { + 'plain': [], + '0': ['-O0'], + 'g': ['-O0'], + '1': ['-O1'], + '2': ['-Ox'], + '3': ['-Ox'], + 's': ['-Ox'], +} # type: T.Dict[str, T.List[str]] + + +class NasmCompiler(Compiler): + language = 'nasm' + id = 'nasm' + + def needs_static_linker(self) -> bool: + return True + + def get_always_args(self) -> T.List[str]: + cpu = '64' if self.info.is_64_bit else '32' + if self.info.is_windows() or self.info.is_cygwin(): + plat = 'win' + define = f'WIN{cpu}' + elif self.info.is_darwin(): + plat = 'macho' + define = 'MACHO' + else: + plat = 'elf' + define = 'ELF' + args = ['-f', f'{plat}{cpu}', f'-D{define}'] + if self.info.is_64_bit: + args.append('-D__x86_64__') + return args + + def get_werror_args(self) -> T.List[str]: + return ['-Werror'] + + def get_output_args(self, outputname: str) -> T.List[str]: + return ['-o', outputname] + + def unix_args_to_native(self, args: T.List[str]) -> T.List[str]: + outargs = [] + for arg in args: + if arg == '-pthread': + continue + outargs.append(arg) + return outargs + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return nasm_optimization_args[optimization_level] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + if is_debug: + if self.info.is_windows(): + return [] + return ['-g', '-F', 'dwarf'] + return [] + + def get_depfile_suffix(self) -> str: + return 'd' + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + return ['-MD', outfile, '-MQ', outtarget] + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + if self.info.cpu_family not in {'x86', 'x86_64'}: + raise EnvironmentException(f'ASM compiler {self.id!r} does not support {self.info.cpu_family} CPU family') + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + # FIXME: Not implemented + return [] + + def get_pic_args(self) -> T.List[str]: + return [] + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + if not path: + path = '.' + return ['-I' + path] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:2] == '-I': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + return parameter_list + + def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: + return [] + +class YasmCompiler(NasmCompiler): + id = 'yasm' + + def get_exelist(self, ccache: bool = True) -> T.List[str]: + # Wrap yasm executable with an internal script that will write depfile. + exelist = super().get_exelist(ccache) + return get_meson_command() + ['--internal', 'yasm'] + exelist + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + if is_debug: + if self.info.is_windows(): + return ['-g', 'null'] + return ['-g', 'dwarf2'] + return [] + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + return ['--depfile', outfile] + +# https://learn.microsoft.com/en-us/cpp/assembler/masm/ml-and-ml64-command-line-reference +class MasmCompiler(Compiler): + language = 'masm' + id = 'ml' + + def get_compile_only_args(self) -> T.List[str]: + return ['/c'] + + def get_argument_syntax(self) -> str: + return 'msvc' + + def needs_static_linker(self) -> bool: + return True + + def get_always_args(self) -> T.List[str]: + return ['/nologo'] + + def get_werror_args(self) -> T.List[str]: + return ['/WX'] + + def get_output_args(self, outputname: str) -> T.List[str]: + return ['/Fo', outputname] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return [] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + if is_debug: + return ['/Zi'] + return [] + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + if self.info.cpu_family not in {'x86', 'x86_64'}: + raise EnvironmentException(f'ASM compiler {self.id!r} does not support {self.info.cpu_family} CPU family') + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + # FIXME: Not implemented + return [] + + def get_pic_args(self) -> T.List[str]: + return [] + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + if not path: + path = '.' + return ['-I' + path] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:2] == '-I' or i[:2] == '/I': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + return parameter_list + + def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: + return [] + + def depfile_for_object(self, objfile: str) -> T.Optional[str]: + return None + + +# https://learn.microsoft.com/en-us/cpp/assembler/arm/arm-assembler-command-line-reference +class MasmARMCompiler(Compiler): + language = 'masm' + id = 'armasm' + + def needs_static_linker(self) -> bool: + return True + + def get_always_args(self) -> T.List[str]: + return ['-nologo'] + + def get_werror_args(self) -> T.List[str]: + return [] + + def get_output_args(self, outputname: str) -> T.List[str]: + return ['-o', outputname] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return [] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + if is_debug: + return ['-g'] + return [] + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + if self.info.cpu_family not in {'arm', 'aarch64'}: + raise EnvironmentException(f'ASM compiler {self.id!r} does not support {self.info.cpu_family} CPU family') + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + # FIXME: Not implemented + return [] + + def get_pic_args(self) -> T.List[str]: + return [] + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + if not path: + path = '.' + return ['-i' + path] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:2] == '-I': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + return parameter_list + + def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: + return [] + + def depfile_for_object(self, objfile: str) -> T.Optional[str]: + return None diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py new file mode 100644 index 0000000..4d9283a --- /dev/null +++ b/mesonbuild/compilers/c.py @@ -0,0 +1,740 @@ +# Copyright 2012-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 __future__ import annotations + +import os.path +import typing as T + +from .. import coredata +from .. import mlog +from ..mesonlib import MesonException, version_compare, OptionKey +from .c_function_attributes import C_FUNC_ATTRIBUTES +from .mixins.clike import CLikeCompiler +from .mixins.ccrx import CcrxCompiler +from .mixins.xc16 import Xc16Compiler +from .mixins.compcert import CompCertCompiler +from .mixins.ti import TICompiler +from .mixins.arm import ArmCompiler, ArmclangCompiler +from .mixins.visualstudio import MSVCCompiler, ClangClCompiler +from .mixins.gnu import GnuCompiler +from .mixins.gnu import gnu_common_warning_args, gnu_c_warning_args +from .mixins.intel import IntelGnuLikeCompiler, IntelVisualStudioLikeCompiler +from .mixins.clang import ClangCompiler +from .mixins.elbrus import ElbrusCompiler +from .mixins.pgi import PGICompiler +from .mixins.emscripten import EmscriptenMixin +from .compilers import ( + gnu_winlibs, + msvc_winlibs, + Compiler, +) + +if T.TYPE_CHECKING: + from ..coredata import MutableKeyedOptionDictType, KeyedOptionDictType + from ..dependencies import Dependency + from ..envconfig import MachineInfo + from ..environment import Environment + from ..linkers import DynamicLinker + from ..mesonlib import MachineChoice + from ..programs import ExternalProgram + from .compilers import CompileCheckMode + + CompilerMixinBase = Compiler +else: + CompilerMixinBase = object + + +class CCompiler(CLikeCompiler, Compiler): + def attribute_check_func(self, name: str) -> str: + try: + return C_FUNC_ATTRIBUTES[name] + except KeyError: + raise MesonException(f'Unknown function attribute "{name}"') + + language = 'c' + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + # If a child ObjC or CPP class has already set it, don't set it ourselves + Compiler.__init__(self, ccache, exelist, version, for_machine, info, + is_cross=is_cross, full_version=full_version, linker=linker) + CLikeCompiler.__init__(self, exe_wrapper) + + def get_no_stdinc_args(self) -> T.List[str]: + return ['-nostdinc'] + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + code = 'int main(void) { int class=0; return class; }\n' + return self._sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) + + def has_header_symbol(self, hname: str, symbol: str, prefix: str, + env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol} + t = '''{prefix} + #include <{header}> + int main(void) {{ + /* If it's not defined as a macro, try to use as a symbol */ + #ifndef {symbol} + {symbol}; + #endif + return 0; + }}''' + return self.compiles(t.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies) + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = super().get_options() + opts.update({ + OptionKey('std', machine=self.for_machine, lang=self.language): coredata.UserComboOption( + 'C language standard to use', + ['none'], + 'none', + ) + }) + return opts + + +class _ClangCStds(CompilerMixinBase): + + """Mixin class for clang based compilers for setting C standards. + + This is used by both ClangCCompiler and ClangClCompiler, as they share + the same versions + """ + + _C17_VERSION = '>=6.0.0' + _C18_VERSION = '>=8.0.0' + _C2X_VERSION = '>=9.0.0' + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = super().get_options() + c_stds = ['c89', 'c99', 'c11'] + g_stds = ['gnu89', 'gnu99', 'gnu11'] + # https://releases.llvm.org/6.0.0/tools/clang/docs/ReleaseNotes.html + # https://en.wikipedia.org/wiki/Xcode#Latest_versions + if version_compare(self.version, self._C17_VERSION): + c_stds += ['c17'] + g_stds += ['gnu17'] + if version_compare(self.version, self._C18_VERSION): + c_stds += ['c18'] + g_stds += ['gnu18'] + if version_compare(self.version, self._C2X_VERSION): + c_stds += ['c2x'] + g_stds += ['gnu2x'] + opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + c_stds + g_stds + return opts + + +class ClangCCompiler(_ClangCStds, ClangCompiler, CCompiler): + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, info, exe_wrapper, linker=linker, full_version=full_version) + ClangCompiler.__init__(self, defines) + default_warn_args = ['-Wall', '-Winvalid-pch'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-Wpedantic'], + 'everything': ['-Weverything']} + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = super().get_options() + if self.info.is_windows() or self.info.is_cygwin(): + opts.update({ + OptionKey('winlibs', machine=self.for_machine, lang=self.language): coredata.UserArrayOption( + 'Standard Win libraries to link against', + gnu_winlibs, + ), + }) + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] + if std.value != 'none': + args.append('-std=' + std.value) + return args + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + if self.info.is_windows() or self.info.is_cygwin(): + # without a typedict mypy can't understand this. + libs = options[OptionKey('winlibs', machine=self.for_machine, lang=self.language)].value.copy() + assert isinstance(libs, list) + for l in libs: + assert isinstance(l, str) + return libs + return [] + + +class ArmLtdClangCCompiler(ClangCCompiler): + + id = 'armltdclang' + + +class AppleClangCCompiler(ClangCCompiler): + + """Handle the differences between Apple Clang and Vanilla Clang. + + Right now this just handles the differences between the versions that new + C standards were added. + """ + + _C17_VERSION = '>=10.0.0' + _C18_VERSION = '>=11.0.0' + _C2X_VERSION = '>=11.0.0' + + +class EmscriptenCCompiler(EmscriptenMixin, ClangCCompiler): + + id = 'emscripten' + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + full_version: T.Optional[str] = None): + if not is_cross: + raise MesonException('Emscripten compiler can only be used for cross compilation.') + if not version_compare(version, '>=1.39.19'): + raise MesonException('Meson requires Emscripten >= 1.39.19') + ClangCCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper=exe_wrapper, linker=linker, + defines=defines, full_version=full_version) + + +class ArmclangCCompiler(ArmclangCompiler, CCompiler): + ''' + Keil armclang + ''' + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + ArmclangCompiler.__init__(self) + default_warn_args = ['-Wall', '-Winvalid-pch'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-Wpedantic'], + 'everything': ['-Weverything']} + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CCompiler.get_options(self) + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c90', 'c99', 'c11', 'gnu90', 'gnu99', 'gnu11'] + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] + if std.value != 'none': + args.append('-std=' + std.value) + return args + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + return [] + + +class GnuCCompiler(GnuCompiler, CCompiler): + + _C18_VERSION = '>=8.0.0' + _C2X_VERSION = '>=9.0.0' + _INVALID_PCH_VERSION = ">=3.4.0" + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, info, exe_wrapper, linker=linker, full_version=full_version) + GnuCompiler.__init__(self, defines) + default_warn_args = ['-Wall'] + if version_compare(self.version, self._INVALID_PCH_VERSION): + default_warn_args += ['-Winvalid-pch'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-Wpedantic'], + 'everything': (default_warn_args + ['-Wextra', '-Wpedantic'] + + self.supported_warn_args(gnu_common_warning_args) + + self.supported_warn_args(gnu_c_warning_args))} + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CCompiler.get_options(self) + c_stds = ['c89', 'c99', 'c11'] + g_stds = ['gnu89', 'gnu99', 'gnu11'] + if version_compare(self.version, self._C18_VERSION): + c_stds += ['c17', 'c18'] + g_stds += ['gnu17', 'gnu18'] + if version_compare(self.version, self._C2X_VERSION): + c_stds += ['c2x'] + g_stds += ['gnu2x'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none'] + c_stds + g_stds + if self.info.is_windows() or self.info.is_cygwin(): + opts.update({ + key.evolve('winlibs'): coredata.UserArrayOption( + 'Standard Win libraries to link against', + gnu_winlibs, + ), + }) + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + std = options[OptionKey('std', lang=self.language, machine=self.for_machine)] + if std.value != 'none': + args.append('-std=' + std.value) + return args + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + if self.info.is_windows() or self.info.is_cygwin(): + # without a typeddict mypy can't figure this out + libs: T.List[str] = options[OptionKey('winlibs', lang=self.language, machine=self.for_machine)].value.copy() + assert isinstance(libs, list) + for l in libs: + assert isinstance(l, str) + return libs + return [] + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + return ['-fpch-preprocess', '-include', os.path.basename(header)] + + +class PGICCompiler(PGICompiler, CCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + PGICompiler.__init__(self) + + +class NvidiaHPC_CCompiler(PGICompiler, CCompiler): + + id = 'nvidia_hpc' + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + PGICompiler.__init__(self) + + +class ElbrusCCompiler(ElbrusCompiler, CCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + ElbrusCompiler.__init__(self) + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CCompiler.get_options(self) + stds = ['c89', 'c9x', 'c99', 'gnu89', 'gnu9x', 'gnu99'] + stds += ['iso9899:1990', 'iso9899:199409', 'iso9899:1999'] + if version_compare(self.version, '>=1.20.00'): + stds += ['c11', 'gnu11'] + if version_compare(self.version, '>=1.21.00') and version_compare(self.version, '<1.22.00'): + stds += ['c90', 'c1x', 'gnu90', 'gnu1x', 'iso9899:2011'] + if version_compare(self.version, '>=1.23.00'): + stds += ['c90', 'c1x', 'gnu90', 'gnu1x', 'iso9899:2011'] + if version_compare(self.version, '>=1.26.00'): + stds += ['c17', 'c18', 'iso9899:2017', 'iso9899:2018', 'gnu17', 'gnu18'] + opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + stds + return opts + + # Elbrus C compiler does not have lchmod, but there is only linker warning, not compiler error. + # So we should explicitly fail at this case. + def has_function(self, funcname: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + if funcname == 'lchmod': + return False, False + else: + return super().has_function(funcname, prefix, env, + extra_args=extra_args, + dependencies=dependencies) + + +class IntelCCompiler(IntelGnuLikeCompiler, CCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + IntelGnuLikeCompiler.__init__(self) + self.lang_header = 'c-header' + default_warn_args = ['-Wall', '-w3'] + self.warn_args = {'0': [], + '1': default_warn_args + ['-diag-disable:remark'], + '2': default_warn_args + ['-Wextra', '-diag-disable:remark'], + '3': default_warn_args + ['-Wextra', '-diag-disable:remark'], + 'everything': default_warn_args + ['-Wextra']} + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CCompiler.get_options(self) + c_stds = ['c89', 'c99'] + g_stds = ['gnu89', 'gnu99'] + if version_compare(self.version, '>=16.0.0'): + c_stds += ['c11'] + opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + c_stds + g_stds + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] + if std.value != 'none': + args.append('-std=' + std.value) + return args + + +class IntelLLVMCCompiler(ClangCCompiler): + + id = 'intel-llvm' + + +class VisualStudioLikeCCompilerMixin(CompilerMixinBase): + + """Shared methods that apply to MSVC-like C compilers.""" + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = super().get_options() + opts.update({ + OptionKey('winlibs', machine=self.for_machine, lang=self.language): coredata.UserArrayOption( + 'Windows libs to link against.', + msvc_winlibs, + ), + }) + return opts + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + # need a TypeDict to make this work + key = OptionKey('winlibs', machine=self.for_machine, lang=self.language) + libs = options[key].value.copy() + assert isinstance(libs, list) + for l in libs: + assert isinstance(l, str) + return libs + + +class VisualStudioCCompiler(MSVCCompiler, VisualStudioLikeCCompilerMixin, CCompiler): + + _C11_VERSION = '>=19.28' + _C17_VERSION = '>=19.28' + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', target: str, + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, + full_version=full_version) + MSVCCompiler.__init__(self, target) + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = super().get_options() + c_stds = ['c89', 'c99'] + # Need to have these to be compatible with projects + # that set c_std to e.g. gnu99. + # https://github.com/mesonbuild/meson/issues/7611 + g_stds = ['gnu89', 'gnu90', 'gnu9x', 'gnu99'] + if version_compare(self.version, self._C11_VERSION): + c_stds += ['c11'] + g_stds += ['gnu1x', 'gnu11'] + if version_compare(self.version, self._C17_VERSION): + c_stds += ['c17', 'c18'] + g_stds += ['gnu17', 'gnu18'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none'] + c_stds + g_stds + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] + if std.value.startswith('gnu'): + mlog.log_once( + 'cl.exe does not actually support gnu standards, and meson ' + 'will instead demote to the nearest ISO C standard. This ' + 'may cause compilation to fail.') + # As of MVSC 16.8, /std:c11 and /std:c17 are the only valid C standard options. + if std.value in {'c11', 'gnu1x', 'gnu11'}: + args.append('/std:c11') + elif std.value in {'c17', 'c18', 'gnu17', 'gnu18'}: + args.append('/std:c17') + return args + + +class ClangClCCompiler(_ClangCStds, ClangClCompiler, VisualStudioLikeCCompilerMixin, CCompiler): + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', target: str, + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, [], exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, + full_version=full_version) + ClangClCompiler.__init__(self, target) + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key].value + if std != "none": + return [f'/clang:-std={std}'] + return [] + + +class IntelClCCompiler(IntelVisualStudioLikeCompiler, VisualStudioLikeCCompilerMixin, CCompiler): + + """Intel "ICL" compiler abstraction.""" + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', target: str, + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, [], exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, + full_version=full_version) + IntelVisualStudioLikeCompiler.__init__(self, target) + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = super().get_options() + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c89', 'c99', 'c11'] + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value == 'c89': + mlog.log_once("ICL doesn't explicitly implement c89, setting the standard to 'none', which is close.") + elif std.value != 'none': + args.append('/Qstd:' + std.value) + return args + + +class IntelLLVMClCCompiler(IntelClCCompiler): + + id = 'intel-llvm-cl' + + +class ArmCCompiler(ArmCompiler, CCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, + full_version=full_version) + ArmCompiler.__init__(self) + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CCompiler.get_options(self) + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c89', 'c99', 'c11'] + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value != 'none': + args.append('--' + std.value) + return args + + +class CcrxCCompiler(CcrxCompiler, CCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + CcrxCompiler.__init__(self) + + # Override CCompiler.get_always_args + def get_always_args(self) -> T.List[str]: + return ['-nologo'] + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CCompiler.get_options(self) + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c89', 'c99'] + return opts + + def get_no_stdinc_args(self) -> T.List[str]: + return [] + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value == 'c89': + args.append('-lang=c') + elif std.value == 'c99': + args.append('-lang=c99') + return args + + def get_compile_only_args(self) -> T.List[str]: + return [] + + def get_no_optimization_args(self) -> T.List[str]: + return ['-optimize=0'] + + def get_output_args(self, target: str) -> T.List[str]: + return [f'-output=obj={target}'] + + def get_werror_args(self) -> T.List[str]: + return ['-change_message=error'] + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + if path == '': + path = '.' + return ['-include=' + path] + + +class Xc16CCompiler(Xc16Compiler, CCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + Xc16Compiler.__init__(self) + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CCompiler.get_options(self) + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c89', 'c99', 'gnu89', 'gnu99'] + return opts + + def get_no_stdinc_args(self) -> T.List[str]: + return [] + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value != 'none': + args.append('-ansi') + args.append('-std=' + std.value) + return args + + def get_compile_only_args(self) -> T.List[str]: + return [] + + def get_no_optimization_args(self) -> T.List[str]: + return ['-O0'] + + def get_output_args(self, target: str) -> T.List[str]: + return [f'-o{target}'] + + def get_werror_args(self) -> T.List[str]: + return ['-change_message=error'] + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + if path == '': + path = '.' + return ['-I' + path] + +class CompCertCCompiler(CompCertCompiler, CCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + CompCertCompiler.__init__(self) + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CCompiler.get_options(self) + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c89', 'c99'] + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + return [] + + def get_no_optimization_args(self) -> T.List[str]: + return ['-O0'] + + def get_output_args(self, target: str) -> T.List[str]: + return [f'-o{target}'] + + def get_werror_args(self) -> T.List[str]: + return ['-Werror'] + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + if path == '': + path = '.' + return ['-I' + path] + +class TICCompiler(TICompiler, CCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + TICompiler.__init__(self) + + # Override CCompiler.get_always_args + def get_always_args(self) -> T.List[str]: + return [] + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CCompiler.get_options(self) + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c89', 'c99', 'c11'] + return opts + + def get_no_stdinc_args(self) -> T.List[str]: + return [] + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value != 'none': + args.append('--' + std.value) + return args + +class C2000CCompiler(TICCompiler): + # Required for backwards compat with projects created before ti-cgt support existed + id = 'c2000' diff --git a/mesonbuild/compilers/c_function_attributes.py b/mesonbuild/compilers/c_function_attributes.py new file mode 100644 index 0000000..f663bfc --- /dev/null +++ b/mesonbuild/compilers/c_function_attributes.py @@ -0,0 +1,141 @@ +# These functions are based on the following code: +# https://git.savannah.gnu.org/gitweb/?p=autoconf-archive.git;a=blob_plain;f=m4/ax_gcc_func_attribute.m4, +# which is licensed under the following terms: +# +# Copyright (c) 2013 Gabriele Svelto <gabriele.svelto@gmail.com> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. +# + +C_FUNC_ATTRIBUTES = { + 'alias': ''' + int foo(void) { return 0; } + int bar(void) __attribute__((alias("foo")));''', + 'aligned': + 'int foo(void) __attribute__((aligned(32)));', + 'alloc_size': + 'void *foo(int a) __attribute__((alloc_size(1)));', + 'always_inline': + 'inline __attribute__((always_inline)) int foo(void) { return 0; }', + 'artificial': + 'inline __attribute__((artificial)) int foo(void) { return 0; }', + 'cold': + 'int foo(void) __attribute__((cold));', + 'const': + 'int foo(void) __attribute__((const));', + 'constructor': + 'int foo(void) __attribute__((constructor));', + 'constructor_priority': + 'int foo( void ) __attribute__((__constructor__(65535/2)));', + 'deprecated': + 'int foo(void) __attribute__((deprecated("")));', + 'destructor': + 'int foo(void) __attribute__((destructor));', + 'dllexport': + '__declspec(dllexport) int foo(void) { return 0; }', + 'dllimport': + '__declspec(dllimport) int foo(void);', + 'error': + 'int foo(void) __attribute__((error("")));', + 'externally_visible': + 'int foo(void) __attribute__((externally_visible));', + 'fallthrough': ''' + int foo( void ) { + switch (0) { + case 1: __attribute__((fallthrough)); + case 2: break; + } + return 0; + };''', + 'flatten': + 'int foo(void) __attribute__((flatten));', + 'format': + 'int foo(const char * p, ...) __attribute__((format(printf, 1, 2)));', + 'format_arg': + 'char * foo(const char * p) __attribute__((format_arg(1)));', + 'force_align_arg_pointer': + '__attribute__((force_align_arg_pointer)) int foo(void) { return 0; }', + 'gnu_inline': + 'inline __attribute__((gnu_inline)) int foo(void) { return 0; }', + 'hot': + 'int foo(void) __attribute__((hot));', + 'ifunc': + ('int my_foo(void) { return 0; }' + 'static int (*resolve_foo(void))(void) { return my_foo; }' + 'int foo(void) __attribute__((ifunc("resolve_foo")));'), + 'leaf': + '__attribute__((leaf)) int foo(void) { return 0; }', + 'malloc': + 'int *foo(void) __attribute__((malloc));', + 'noclone': + 'int foo(void) __attribute__((noclone));', + 'noinline': + '__attribute__((noinline)) int foo(void) { return 0; }', + 'nonnull': + 'int foo(char * p) __attribute__((nonnull(1)));', + 'noreturn': + 'int foo(void) __attribute__((noreturn));', + 'nothrow': + 'int foo(void) __attribute__((nothrow));', + 'optimize': + '__attribute__((optimize(3))) int foo(void) { return 0; }', + 'packed': + 'struct __attribute__((packed)) foo { int bar; };', + 'pure': + 'int foo(void) __attribute__((pure));', + 'returns_nonnull': + 'int *foo(void) __attribute__((returns_nonnull));', + 'section': ''' + #if defined(__APPLE__) && defined(__MACH__) + extern int foo __attribute__((section("__BAR,__bar"))); + #else + extern int foo __attribute__((section(".bar"))); + #endif''', + 'sentinel': + 'int foo(const char *bar, ...) __attribute__((sentinel));', + 'unused': + 'int foo(void) __attribute__((unused));', + 'used': + 'int foo(void) __attribute__((used));', + 'visibility': ''' + int foo_def(void) __attribute__((visibility("default"))); + int foo_hid(void) __attribute__((visibility("hidden"))); + int foo_int(void) __attribute__((visibility("internal")));''', + 'visibility:default': + 'int foo(void) __attribute__((visibility("default")));', + 'visibility:hidden': + 'int foo(void) __attribute__((visibility("hidden")));', + 'visibility:internal': + 'int foo(void) __attribute__((visibility("internal")));', + 'visibility:protected': + 'int foo(void) __attribute__((visibility("protected")));', + 'warning': + 'int foo(void) __attribute__((warning("")));', + 'warn_unused_result': + 'int foo(void) __attribute__((warn_unused_result));', + 'weak': + 'int foo(void) __attribute__((weak));', + 'weakref': ''' + static int foo(void) { return 0; } + static int var(void) __attribute__((weakref("foo")));''', + 'retain': '__attribute__((retain)) int x;', +} + +CXX_FUNC_ATTRIBUTES = { + # Alias must be applied to the mangled name in C++ + 'alias': + ('extern "C" {' + 'int foo(void) { return 0; }' + '}' + 'int bar(void) __attribute__((alias("foo")));' + ), + 'ifunc': + ('extern "C" {' + 'int my_foo(void) { return 0; }' + 'static int (*resolve_foo(void))(void) { return my_foo; }' + '}' + 'int foo(void) __attribute__((ifunc("resolve_foo")));'), +} diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py new file mode 100644 index 0000000..c5a51cb --- /dev/null +++ b/mesonbuild/compilers/compilers.py @@ -0,0 +1,1342 @@ +# Copyright 2012-2022 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 __future__ import annotations + +import abc +import contextlib, os.path, re +import enum +import itertools +import typing as T +from functools import lru_cache + +from .. import coredata +from .. import mlog +from .. import mesonlib +from ..mesonlib import ( + HoldableObject, + EnvironmentException, MesonException, + Popen_safe, LibType, TemporaryDirectoryWinProof, OptionKey, +) + +from ..arglist import CompilerArgs + +if T.TYPE_CHECKING: + from ..build import BuildTarget + from ..coredata import MutableKeyedOptionDictType, KeyedOptionDictType + from ..envconfig import MachineInfo + from ..environment import Environment + from ..linkers import DynamicLinker, RSPFileSyntax + from ..mesonlib import MachineChoice + from ..dependencies import Dependency + + CompilerType = T.TypeVar('CompilerType', bound='Compiler') + _T = T.TypeVar('_T') + +"""This file contains the data files of all compilers Meson knows +about. To support a new compiler, add its information below. +Also add corresponding autodetection code in environment.py.""" + +header_suffixes = {'h', 'hh', 'hpp', 'hxx', 'H', 'ipp', 'moc', 'vapi', 'di'} +obj_suffixes = {'o', 'obj', 'res'} +# To the emscripten compiler, .js files are libraries +lib_suffixes = {'a', 'lib', 'dll', 'dll.a', 'dylib', 'so', 'js'} +# Mapping of language to suffixes of files that should always be in that language +# This means we can't include .h headers here since they could be C, C++, ObjC, etc. +# First suffix is the language's default. +lang_suffixes = { + 'c': ('c',), + 'cpp': ('cpp', 'cc', 'cxx', 'c++', 'hh', 'hpp', 'ipp', 'hxx', 'ino', 'ixx', 'C'), + 'cuda': ('cu',), + # f90, f95, f03, f08 are for free-form fortran ('f90' recommended) + # f, for, ftn, fpp are for fixed-form fortran ('f' or 'for' recommended) + 'fortran': ('f90', 'f95', 'f03', 'f08', 'f', 'for', 'ftn', 'fpp'), + 'd': ('d', 'di'), + 'objc': ('m',), + 'objcpp': ('mm',), + 'rust': ('rs',), + 'vala': ('vala', 'vapi', 'gs'), + 'cs': ('cs',), + 'swift': ('swift',), + 'java': ('java',), + 'cython': ('pyx', ), + 'nasm': ('asm',), + 'masm': ('masm',), +} +all_languages = lang_suffixes.keys() +c_cpp_suffixes = {'h'} +cpp_suffixes = set(lang_suffixes['cpp']) | c_cpp_suffixes +c_suffixes = set(lang_suffixes['c']) | c_cpp_suffixes +assembler_suffixes = {'s', 'S', 'asm', 'masm'} +llvm_ir_suffixes = {'ll'} +all_suffixes = set(itertools.chain(*lang_suffixes.values(), assembler_suffixes, llvm_ir_suffixes, c_cpp_suffixes)) +source_suffixes = all_suffixes - header_suffixes +# List of languages that by default consume and output libraries following the +# C ABI; these can generally be used interchangeably +# This must be sorted, see sort_clink(). +clib_langs = ('objcpp', 'cpp', 'objc', 'c', 'fortran') +# List of languages that can be linked with C code directly by the linker +# used in build.py:process_compilers() and build.py:get_dynamic_linker() +# This must be sorted, see sort_clink(). +clink_langs = ('d', 'cuda') + clib_langs + +SUFFIX_TO_LANG = dict(itertools.chain(*( + [(suffix, lang) for suffix in v] for lang, v in lang_suffixes.items()))) # type: T.Dict[str, str] + +# Languages that should use LDFLAGS arguments when linking. +LANGUAGES_USING_LDFLAGS = {'objcpp', 'cpp', 'objc', 'c', 'fortran', 'd', 'cuda'} # type: T.Set[str] +# Languages that should use CPPFLAGS arguments when linking. +LANGUAGES_USING_CPPFLAGS = {'c', 'cpp', 'objc', 'objcpp'} # type: T.Set[str] +soregex = re.compile(r'.*\.so(\.[0-9]+)?(\.[0-9]+)?(\.[0-9]+)?$') + +# Environment variables that each lang uses. +CFLAGS_MAPPING: T.Mapping[str, str] = { + 'c': 'CFLAGS', + 'cpp': 'CXXFLAGS', + 'cuda': 'CUFLAGS', + 'objc': 'OBJCFLAGS', + 'objcpp': 'OBJCXXFLAGS', + 'fortran': 'FFLAGS', + 'd': 'DFLAGS', + 'vala': 'VALAFLAGS', + 'rust': 'RUSTFLAGS', + 'cython': 'CYTHONFLAGS', + 'cs': 'CSFLAGS', # This one might not be standard. +} + +# All these are only for C-linkable languages; see `clink_langs` above. + +def sort_clink(lang: str) -> int: + ''' + Sorting function to sort the list of languages according to + reversed(compilers.clink_langs) and append the unknown langs in the end. + The purpose is to prefer C over C++ for files that can be compiled by + both such as assembly, C, etc. Also applies to ObjC, ObjC++, etc. + ''' + if lang not in clink_langs: + return 1 + return -clink_langs.index(lang) + +def is_header(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): + fname = fname.fname + suffix = fname.split('.')[-1] + return suffix in header_suffixes + +def is_source(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): + fname = fname.fname + suffix = fname.split('.')[-1].lower() + return suffix in source_suffixes + +def is_assembly(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): + fname = fname.fname + suffix = fname.split('.')[-1] + return suffix in assembler_suffixes + +def is_llvm_ir(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): + fname = fname.fname + suffix = fname.split('.')[-1] + return suffix in llvm_ir_suffixes + +@lru_cache(maxsize=None) +def cached_by_name(fname: 'mesonlib.FileOrString') -> bool: + suffix = fname.split('.')[-1] + return suffix in obj_suffixes + +def is_object(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): + fname = fname.fname + return cached_by_name(fname) + +def is_library(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): + fname = fname.fname + + if soregex.match(fname): + return True + + suffix = fname.split('.')[-1] + return suffix in lib_suffixes + +def is_known_suffix(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): + fname = fname.fname + suffix = fname.split('.')[-1] + + return suffix in all_suffixes + + +class CompileCheckMode(enum.Enum): + + PREPROCESS = 'preprocess' + COMPILE = 'compile' + LINK = 'link' + + +cuda_buildtype_args = {'plain': [], + 'debug': ['-g', '-G'], + 'debugoptimized': ['-g', '-lineinfo'], + 'release': [], + 'minsize': [], + 'custom': [], + } # type: T.Dict[str, T.List[str]] +java_buildtype_args = {'plain': [], + 'debug': ['-g'], + 'debugoptimized': ['-g'], + 'release': [], + 'minsize': [], + 'custom': [], + } # type: T.Dict[str, T.List[str]] + +rust_buildtype_args = {'plain': [], + 'debug': [], + 'debugoptimized': [], + 'release': [], + 'minsize': [], + 'custom': [], + } # type: T.Dict[str, T.List[str]] + +d_gdc_buildtype_args = {'plain': [], + 'debug': [], + 'debugoptimized': ['-finline-functions'], + 'release': ['-finline-functions'], + 'minsize': [], + 'custom': [], + } # type: T.Dict[str, T.List[str]] + +d_ldc_buildtype_args = {'plain': [], + 'debug': [], + 'debugoptimized': ['-enable-inlining', '-Hkeep-all-bodies'], + 'release': ['-enable-inlining', '-Hkeep-all-bodies'], + 'minsize': [], + 'custom': [], + } # type: T.Dict[str, T.List[str]] + +d_dmd_buildtype_args = {'plain': [], + 'debug': [], + 'debugoptimized': ['-inline'], + 'release': ['-inline'], + 'minsize': [], + 'custom': [], + } # type: T.Dict[str, T.List[str]] + +mono_buildtype_args = {'plain': [], + 'debug': [], + 'debugoptimized': ['-optimize+'], + 'release': ['-optimize+'], + 'minsize': [], + 'custom': [], + } # type: T.Dict[str, T.List[str]] + +swift_buildtype_args = {'plain': [], + 'debug': [], + 'debugoptimized': [], + 'release': [], + 'minsize': [], + 'custom': [], + } # type: T.Dict[str, T.List[str]] + +gnu_winlibs = ['-lkernel32', '-luser32', '-lgdi32', '-lwinspool', '-lshell32', + '-lole32', '-loleaut32', '-luuid', '-lcomdlg32', '-ladvapi32'] # type: T.List[str] + +msvc_winlibs = ['kernel32.lib', 'user32.lib', 'gdi32.lib', + 'winspool.lib', 'shell32.lib', 'ole32.lib', 'oleaut32.lib', + 'uuid.lib', 'comdlg32.lib', 'advapi32.lib'] # type: T.List[str] + +clike_optimization_args = {'plain': [], + '0': [], + 'g': [], + '1': ['-O1'], + '2': ['-O2'], + '3': ['-O3'], + 's': ['-Os'], + } # type: T.Dict[str, T.List[str]] + +cuda_optimization_args = {'plain': [], + '0': [], + 'g': ['-O0'], + '1': ['-O1'], + '2': ['-O2'], + '3': ['-O3'], + 's': ['-O3'] + } # type: T.Dict[str, T.List[str]] + +cuda_debug_args = {False: [], + True: ['-g']} # type: T.Dict[bool, T.List[str]] + +clike_debug_args = {False: [], + True: ['-g']} # type: T.Dict[bool, T.List[str]] + +base_options: 'KeyedOptionDictType' = { + OptionKey('b_pch'): coredata.UserBooleanOption('Use precompiled headers', True), + OptionKey('b_lto'): coredata.UserBooleanOption('Use link time optimization', False), + OptionKey('b_lto_threads'): coredata.UserIntegerOption('Use multiple threads for Link Time Optimization', (None, None, 0)), + OptionKey('b_lto_mode'): coredata.UserComboOption('Select between different LTO modes.', + ['default', 'thin'], + 'default'), + OptionKey('b_thinlto_cache'): coredata.UserBooleanOption('Use LLVM ThinLTO caching for faster incremental builds', False), + OptionKey('b_thinlto_cache_dir'): coredata.UserStringOption('Directory to store ThinLTO cache objects', ''), + OptionKey('b_sanitize'): coredata.UserComboOption('Code sanitizer to use', + ['none', 'address', 'thread', 'undefined', 'memory', 'leak', 'address,undefined'], + 'none'), + OptionKey('b_lundef'): coredata.UserBooleanOption('Use -Wl,--no-undefined when linking', True), + OptionKey('b_asneeded'): coredata.UserBooleanOption('Use -Wl,--as-needed when linking', True), + OptionKey('b_pgo'): coredata.UserComboOption('Use profile guided optimization', + ['off', 'generate', 'use'], + 'off'), + OptionKey('b_coverage'): coredata.UserBooleanOption('Enable coverage tracking.', False), + OptionKey('b_colorout'): coredata.UserComboOption('Use colored output', + ['auto', 'always', 'never'], + 'always'), + OptionKey('b_ndebug'): coredata.UserComboOption('Disable asserts', ['true', 'false', 'if-release'], 'false'), + OptionKey('b_staticpic'): coredata.UserBooleanOption('Build static libraries as position independent', True), + OptionKey('b_pie'): coredata.UserBooleanOption('Build executables as position independent', False), + OptionKey('b_bitcode'): coredata.UserBooleanOption('Generate and embed bitcode (only macOS/iOS/tvOS)', False), + OptionKey('b_vscrt'): coredata.UserComboOption('VS run-time library type to use.', + ['none', 'md', 'mdd', 'mt', 'mtd', 'from_buildtype', 'static_from_buildtype'], + 'from_buildtype'), +} + +def option_enabled(boptions: T.Set[OptionKey], options: 'KeyedOptionDictType', + option: OptionKey) -> bool: + try: + if option not in boptions: + return False + ret = options[option].value + assert isinstance(ret, bool), 'must return bool' # could also be str + return ret + except KeyError: + return False + + +def get_option_value(options: 'KeyedOptionDictType', opt: OptionKey, fallback: '_T') -> '_T': + """Get the value of an option, or the fallback value.""" + try: + v: '_T' = options[opt].value + except KeyError: + return fallback + + assert isinstance(v, type(fallback)), f'Should have {type(fallback)!r} but was {type(v)!r}' + # Mypy doesn't understand that the above assert ensures that v is type _T + return v + + +def get_base_compile_args(options: 'KeyedOptionDictType', compiler: 'Compiler') -> T.List[str]: + args = [] # type T.List[str] + try: + if options[OptionKey('b_lto')].value: + args.extend(compiler.get_lto_compile_args( + threads=get_option_value(options, OptionKey('b_lto_threads'), 0), + mode=get_option_value(options, OptionKey('b_lto_mode'), 'default'))) + except KeyError: + pass + try: + args += compiler.get_colorout_args(options[OptionKey('b_colorout')].value) + except KeyError: + pass + try: + args += compiler.sanitizer_compile_args(options[OptionKey('b_sanitize')].value) + except KeyError: + pass + try: + pgo_val = options[OptionKey('b_pgo')].value + if pgo_val == 'generate': + args.extend(compiler.get_profile_generate_args()) + elif pgo_val == 'use': + args.extend(compiler.get_profile_use_args()) + except KeyError: + pass + try: + if options[OptionKey('b_coverage')].value: + args += compiler.get_coverage_args() + except KeyError: + pass + try: + if (options[OptionKey('b_ndebug')].value == 'true' or + (options[OptionKey('b_ndebug')].value == 'if-release' and + options[OptionKey('buildtype')].value in {'release', 'plain'})): + args += compiler.get_disable_assert_args() + except KeyError: + pass + # This does not need a try...except + if option_enabled(compiler.base_options, options, OptionKey('b_bitcode')): + args.append('-fembed-bitcode') + try: + crt_val = options[OptionKey('b_vscrt')].value + buildtype = options[OptionKey('buildtype')].value + try: + args += compiler.get_crt_compile_args(crt_val, buildtype) + except AttributeError: + pass + except KeyError: + pass + return args + +def get_base_link_args(options: 'KeyedOptionDictType', linker: 'Compiler', + is_shared_module: bool, build_dir: str) -> T.List[str]: + args = [] # type: T.List[str] + try: + if options[OptionKey('b_lto')].value: + thinlto_cache_dir = None + if get_option_value(options, OptionKey('b_thinlto_cache'), False): + thinlto_cache_dir = get_option_value(options, OptionKey('b_thinlto_cache_dir'), '') + if thinlto_cache_dir == '': + thinlto_cache_dir = os.path.join(build_dir, 'meson-private', 'thinlto-cache') + args.extend(linker.get_lto_link_args( + threads=get_option_value(options, OptionKey('b_lto_threads'), 0), + mode=get_option_value(options, OptionKey('b_lto_mode'), 'default'), + thinlto_cache_dir=thinlto_cache_dir)) + except KeyError: + pass + try: + args += linker.sanitizer_link_args(options[OptionKey('b_sanitize')].value) + except KeyError: + pass + try: + pgo_val = options[OptionKey('b_pgo')].value + if pgo_val == 'generate': + args.extend(linker.get_profile_generate_args()) + elif pgo_val == 'use': + args.extend(linker.get_profile_use_args()) + except KeyError: + pass + try: + if options[OptionKey('b_coverage')].value: + args += linker.get_coverage_link_args() + except KeyError: + pass + + as_needed = option_enabled(linker.base_options, options, OptionKey('b_asneeded')) + bitcode = option_enabled(linker.base_options, options, OptionKey('b_bitcode')) + # Shared modules cannot be built with bitcode_bundle because + # -bitcode_bundle is incompatible with -undefined and -bundle + if bitcode and not is_shared_module: + args.extend(linker.bitcode_args()) + elif as_needed: + # -Wl,-dead_strip_dylibs is incompatible with bitcode + args.extend(linker.get_asneeded_args()) + + # Apple's ld (the only one that supports bitcode) does not like -undefined + # arguments or -headerpad_max_install_names when bitcode is enabled + if not bitcode: + args.extend(linker.headerpad_args()) + if (not is_shared_module and + option_enabled(linker.base_options, options, OptionKey('b_lundef'))): + args.extend(linker.no_undefined_link_args()) + else: + args.extend(linker.get_allow_undefined_link_args()) + + try: + crt_val = options[OptionKey('b_vscrt')].value + buildtype = options[OptionKey('buildtype')].value + try: + args += linker.get_crt_link_args(crt_val, buildtype) + except AttributeError: + pass + except KeyError: + pass + return args + + +class CrossNoRunException(MesonException): + pass + +class RunResult(HoldableObject): + def __init__(self, compiled: bool, returncode: int = 999, + stdout: str = 'UNDEFINED', stderr: str = 'UNDEFINED'): + self.compiled = compiled + self.returncode = returncode + self.stdout = stdout + self.stderr = stderr + + +class CompileResult(HoldableObject): + + """The result of Compiler.compiles (and friends).""" + + def __init__(self, stdo: T.Optional[str] = None, stde: T.Optional[str] = None, + command: T.Optional[T.List[str]] = None, + returncode: int = 999, + input_name: T.Optional[str] = None, + output_name: T.Optional[str] = None, + cached: bool = False): + self.stdout = stdo + self.stderr = stde + self.input_name = input_name + self.output_name = output_name + self.command = command or [] + self.cached = cached + self.returncode = returncode + + +class Compiler(HoldableObject, metaclass=abc.ABCMeta): + # Libraries to ignore in find_library() since they are provided by the + # compiler or the C library. Currently only used for MSVC. + ignore_libs = [] # type: T.List[str] + # Libraries that are internal compiler implementations, and must not be + # manually searched. + internal_libs = [] # type: T.List[str] + + LINKER_PREFIX = None # type: T.Union[None, str, T.List[str]] + INVOKES_LINKER = True + + language: str + id: str + warn_args: T.Dict[str, T.List[str]] + mode: str = 'COMPILER' + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, + for_machine: MachineChoice, info: 'MachineInfo', + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None, is_cross: bool = False): + self.exelist = ccache + exelist + self.exelist_no_ccache = exelist + # In case it's been overridden by a child class already + if not hasattr(self, 'file_suffixes'): + self.file_suffixes = lang_suffixes[self.language] + if not hasattr(self, 'can_compile_suffixes'): + self.can_compile_suffixes = set(self.file_suffixes) + self.default_suffix = self.file_suffixes[0] + self.version = version + self.full_version = full_version + self.for_machine = for_machine + self.base_options: T.Set[OptionKey] = set() + self.linker = linker + self.info = info + self.is_cross = is_cross + self.modes: T.List[Compiler] = [] + + def __repr__(self) -> str: + repr_str = "<{0}: v{1} `{2}`>" + return repr_str.format(self.__class__.__name__, self.version, + ' '.join(self.exelist)) + + @lru_cache(maxsize=None) + def can_compile(self, src: 'mesonlib.FileOrString') -> bool: + if isinstance(src, mesonlib.File): + src = src.fname + suffix = os.path.splitext(src)[1] + if suffix != '.C': + suffix = suffix.lower() + return bool(suffix) and suffix[1:] in self.can_compile_suffixes + + def get_id(self) -> str: + return self.id + + def get_modes(self) -> T.List[Compiler]: + return self.modes + + def get_linker_id(self) -> str: + # There is not guarantee that we have a dynamic linker instance, as + # some languages don't have separate linkers and compilers. In those + # cases return the compiler id + try: + return self.linker.id + except AttributeError: + return self.id + + def get_version_string(self) -> str: + details = [self.id, self.version] + if self.full_version: + details += ['"%s"' % (self.full_version)] + return '(%s)' % (' '.join(details)) + + def get_language(self) -> str: + return self.language + + @classmethod + def get_display_language(cls) -> str: + return cls.language.capitalize() + + def get_default_suffix(self) -> str: + return self.default_suffix + + def get_define(self, dname: str, prefix: str, env: 'Environment', + extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], + dependencies: T.List['Dependency'], + disable_cache: bool = False) -> T.Tuple[str, bool]: + raise EnvironmentException('%s does not support get_define ' % self.get_id()) + + def compute_int(self, expression: str, low: T.Optional[int], high: T.Optional[int], + guess: T.Optional[int], prefix: str, env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], + dependencies: T.Optional[T.List['Dependency']]) -> int: + raise EnvironmentException('%s does not support compute_int ' % self.get_id()) + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + raise EnvironmentException('%s does not support compute_parameters_with_absolute_paths ' % self.get_id()) + + def has_members(self, typename: str, membernames: T.List[str], + prefix: str, env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + raise EnvironmentException('%s does not support has_member(s) ' % self.get_id()) + + def has_type(self, typename: str, prefix: str, env: 'Environment', + extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], *, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + raise EnvironmentException('%s does not support has_type ' % self.get_id()) + + def symbols_have_underscore_prefix(self, env: 'Environment') -> bool: + raise EnvironmentException('%s does not support symbols_have_underscore_prefix ' % self.get_id()) + + def get_exelist(self, ccache: bool = True) -> T.List[str]: + return self.exelist.copy() if ccache else self.exelist_no_ccache.copy() + + def get_linker_exelist(self) -> T.List[str]: + return self.linker.get_exelist() + + @abc.abstractmethod + def get_output_args(self, outputname: str) -> T.List[str]: + pass + + def get_linker_output_args(self, outputname: str) -> T.List[str]: + return self.linker.get_output_args(outputname) + + def get_linker_search_args(self, dirname: str) -> T.List[str]: + return self.linker.get_search_args(dirname) + + def get_builtin_define(self, define: str) -> T.Optional[str]: + raise EnvironmentException('%s does not support get_builtin_define.' % self.id) + + def has_builtin_define(self, define: str) -> bool: + raise EnvironmentException('%s does not support has_builtin_define.' % self.id) + + def get_always_args(self) -> T.List[str]: + return [] + + def can_linker_accept_rsp(self) -> bool: + """ + Determines whether the linker can accept arguments using the @rsp syntax. + """ + return self.linker.get_accepts_rsp() + + def get_linker_always_args(self) -> T.List[str]: + return self.linker.get_always_args() + + def get_linker_lib_prefix(self) -> str: + return self.linker.get_lib_prefix() + + def gen_import_library_args(self, implibname: str) -> T.List[str]: + """ + Used only on Windows for libraries that need an import library. + This currently means C, C++, Fortran. + """ + return [] + + def get_options(self) -> 'MutableKeyedOptionDictType': + return {} + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + return [] + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + return self.linker.get_option_args(options) + + def check_header(self, hname: str, prefix: str, env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + """Check that header is usable. + + Returns a two item tuple of bools. The first bool is whether the + check succeeded, the second is whether the result was cached (True) + or run fresh (False). + """ + raise EnvironmentException('Language %s does not support header checks.' % self.get_display_language()) + + def has_header(self, hname: str, prefix: str, env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None, + disable_cache: bool = False) -> T.Tuple[bool, bool]: + """Check that header is exists. + + This check will return true if the file exists, even if it contains: + + ```c + # error "You thought you could use this, LOLZ!" + ``` + + Use check_header if your header only works in some cases. + + Returns a two item tuple of bools. The first bool is whether the + check succeeded, the second is whether the result was cached (True) + or run fresh (False). + """ + raise EnvironmentException('Language %s does not support header checks.' % self.get_display_language()) + + def has_header_symbol(self, hname: str, symbol: str, prefix: str, + env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + raise EnvironmentException('Language %s does not support header symbol checks.' % self.get_display_language()) + + def run(self, code: 'mesonlib.FileOrString', env: 'Environment', *, + extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]], None] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> RunResult: + raise EnvironmentException('Language %s does not support run checks.' % self.get_display_language()) + + def sizeof(self, typename: str, prefix: str, env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: + raise EnvironmentException('Language %s does not support sizeof checks.' % self.get_display_language()) + + def alignment(self, typename: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: + raise EnvironmentException('Language %s does not support alignment checks.' % self.get_display_language()) + + def has_function(self, funcname: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + """See if a function exists. + + Returns a two item tuple of bools. The first bool is whether the + check succeeded, the second is whether the result was cached (True) + or run fresh (False). + """ + raise EnvironmentException('Language %s does not support function checks.' % self.get_display_language()) + + @classmethod + def _unix_args_to_native(cls, args: T.List[str], info: MachineInfo) -> T.List[str]: + "Always returns a copy that can be independently mutated" + return args.copy() + + def unix_args_to_native(self, args: T.List[str]) -> T.List[str]: + return self._unix_args_to_native(args, self.info) + + @classmethod + def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]: + "Always returns a copy that can be independently mutated" + return args.copy() + + def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], + libtype: LibType = LibType.PREFER_SHARED) -> T.Optional[T.List[str]]: + raise EnvironmentException(f'Language {self.get_display_language()} does not support library finding.') + + def get_library_naming(self, env: 'Environment', libtype: LibType, + strict: bool = False) -> T.Optional[T.Tuple[str, ...]]: + raise EnvironmentException( + 'Language {} does not support get_library_naming.'.format( + self.get_display_language())) + + def get_program_dirs(self, env: 'Environment') -> T.List[str]: + return [] + + def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: + raise EnvironmentException( + 'Language {} does not support has_multi_arguments.'.format( + self.get_display_language())) + + def has_multi_link_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: + return self.linker.has_multi_arguments(args, env) + + def _get_compile_output(self, dirname: str, mode: str) -> str: + # TODO: mode should really be an enum + # In pre-processor mode, the output is sent to stdout and discarded + if mode == 'preprocess': + return None + # Extension only matters if running results; '.exe' is + # guaranteed to be executable on every platform. + if mode == 'link': + suffix = 'exe' + else: + suffix = 'obj' + return os.path.join(dirname, 'output.' + suffix) + + def get_compiler_args_for_mode(self, mode: CompileCheckMode) -> T.List[str]: + # TODO: mode should really be an enum + args = [] # type: T.List[str] + args += self.get_always_args() + if mode is CompileCheckMode.COMPILE: + args += self.get_compile_only_args() + elif mode is CompileCheckMode.PREPROCESS: + args += self.get_preprocess_only_args() + else: + assert mode is CompileCheckMode.LINK + return args + + def compiler_args(self, args: T.Optional[T.Iterable[str]] = None) -> CompilerArgs: + """Return an appropriate CompilerArgs instance for this class.""" + return CompilerArgs(self, args) + + @contextlib.contextmanager + def compile(self, code: 'mesonlib.FileOrString', + extra_args: T.Union[None, CompilerArgs, T.List[str]] = None, + *, mode: str = 'link', want_output: bool = False, + temp_dir: T.Optional[str] = None) -> T.Iterator[T.Optional[CompileResult]]: + # TODO: there isn't really any reason for this to be a contextmanager + if extra_args is None: + extra_args = [] + + with TemporaryDirectoryWinProof(dir=temp_dir) as tmpdirname: + no_ccache = False + if isinstance(code, str): + srcname = os.path.join(tmpdirname, + 'testfile.' + self.default_suffix) + with open(srcname, 'w', encoding='utf-8') as ofile: + ofile.write(code) + # ccache would result in a cache miss + no_ccache = True + contents = code + else: + srcname = code.fname + if not is_object(code.fname): + with open(code.fname, encoding='utf-8') as f: + contents = f.read() + else: + contents = '<binary>' + + # Construct the compiler command-line + commands = self.compiler_args() + commands.append(srcname) + + # Preprocess mode outputs to stdout, so no output args + output = self._get_compile_output(tmpdirname, mode) + if mode != 'preprocess': + commands += self.get_output_args(output) + commands.extend(self.get_compiler_args_for_mode(CompileCheckMode(mode))) + + # extra_args must be last because it could contain '/link' to + # pass args to VisualStudio's linker. In that case everything + # in the command line after '/link' is given to the linker. + if extra_args: + commands += extra_args + # Generate full command-line with the exelist + command_list = self.get_exelist(ccache=not no_ccache) + commands.to_native() + mlog.debug('Running compile:') + mlog.debug('Working directory: ', tmpdirname) + mlog.debug('Command line: ', ' '.join(command_list), '\n') + mlog.debug('Code:\n', contents) + os_env = os.environ.copy() + os_env['LC_ALL'] = 'C' + if no_ccache: + os_env['CCACHE_DISABLE'] = '1' + p, stdo, stde = Popen_safe(command_list, cwd=tmpdirname, env=os_env) + mlog.debug('Compiler stdout:\n', stdo) + mlog.debug('Compiler stderr:\n', stde) + + result = CompileResult(stdo, stde, command_list, p.returncode, input_name=srcname) + if want_output: + result.output_name = output + yield result + + @contextlib.contextmanager + def cached_compile(self, code: 'mesonlib.FileOrString', cdata: coredata.CoreData, *, + extra_args: T.Union[None, T.List[str], CompilerArgs] = None, + mode: str = 'link', + temp_dir: T.Optional[str] = None) -> T.Iterator[T.Optional[CompileResult]]: + # TODO: There's isn't really any reason for this to be a context manager + + # Calculate the key + textra_args = tuple(extra_args) if extra_args is not None else tuple() # type: T.Tuple[str, ...] + key = (tuple(self.exelist), self.version, code, textra_args, mode) # type: coredata.CompilerCheckCacheKey + + # Check if not cached, and generate, otherwise get from the cache + if key in cdata.compiler_check_cache: + p = cdata.compiler_check_cache[key] + p.cached = True + mlog.debug('Using cached compile:') + mlog.debug('Cached command line: ', ' '.join(p.command), '\n') + mlog.debug('Code:\n', code) + mlog.debug('Cached compiler stdout:\n', p.stdout) + mlog.debug('Cached compiler stderr:\n', p.stderr) + yield p + else: + with self.compile(code, extra_args=extra_args, mode=mode, want_output=False, temp_dir=temp_dir) as p: + cdata.compiler_check_cache[key] = p + yield p + + def get_colorout_args(self, colortype: str) -> T.List[str]: + # TODO: colortype can probably be an emum + return [] + + # Some compilers (msvc) write debug info to a separate file. + # These args specify where it should be written. + def get_compile_debugfile_args(self, rel_obj: str, pch: bool = False) -> T.List[str]: + return [] + + def get_link_debugfile_name(self, targetfile: str) -> str: + return self.linker.get_debugfile_name(targetfile) + + def get_link_debugfile_args(self, targetfile: str) -> T.List[str]: + return self.linker.get_debugfile_args(targetfile) + + def get_std_shared_lib_link_args(self) -> T.List[str]: + return self.linker.get_std_shared_lib_args() + + def get_std_shared_module_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + return self.linker.get_std_shared_module_args(options) + + def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: + return self.linker.get_link_whole_for(args) + + def get_allow_undefined_link_args(self) -> T.List[str]: + return self.linker.get_allow_undefined_args() + + def no_undefined_link_args(self) -> T.List[str]: + return self.linker.no_undefined_args() + + def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: + """Compiler arguments needed to enable the given instruction set. + + Return type ay be an empty list meaning nothing needed or None + meaning the given set is not supported. + """ + return None + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str, + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + return self.linker.build_rpath_args( + env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + + def thread_flags(self, env: 'Environment') -> T.List[str]: + return [] + + def thread_link_flags(self, env: 'Environment') -> T.List[str]: + return self.linker.thread_flags(env) + + def openmp_flags(self) -> T.List[str]: + raise EnvironmentException('Language %s does not support OpenMP flags.' % self.get_display_language()) + + def openmp_link_flags(self) -> T.List[str]: + return self.openmp_flags() + + def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]: + return [] + + def gnu_symbol_visibility_args(self, vistype: str) -> T.List[str]: + return [] + + def get_gui_app_args(self, value: bool) -> T.List[str]: + # Only used on Windows + return self.linker.get_gui_app_args(value) + + def get_win_subsystem_args(self, value: str) -> T.List[str]: + # By default the dynamic linker is going to return an empty + # array in case it either doesn't support Windows subsystems + # or does not target Windows + return self.linker.get_win_subsystem_args(value) + + def has_func_attribute(self, name: str, env: 'Environment') -> T.Tuple[bool, bool]: + raise EnvironmentException( + f'Language {self.get_display_language()} does not support function attributes.') + + def get_pic_args(self) -> T.List[str]: + m = 'Language {} does not support position-independent code' + raise EnvironmentException(m.format(self.get_display_language())) + + def get_pie_args(self) -> T.List[str]: + m = 'Language {} does not support position-independent executable' + raise EnvironmentException(m.format(self.get_display_language())) + + def get_pie_link_args(self) -> T.List[str]: + return self.linker.get_pie_args() + + def get_argument_syntax(self) -> str: + """Returns the argument family type. + + Compilers fall into families if they try to emulate the command line + interface of another compiler. For example, clang is in the GCC family + since it accepts most of the same arguments as GCC. ICL (ICC on + windows) is in the MSVC family since it accepts most of the same + arguments as MSVC. + """ + return 'other' + + def get_profile_generate_args(self) -> T.List[str]: + raise EnvironmentException( + '%s does not support get_profile_generate_args ' % self.get_id()) + + def get_profile_use_args(self) -> T.List[str]: + raise EnvironmentException( + '%s does not support get_profile_use_args ' % self.get_id()) + + def remove_linkerlike_args(self, args: T.List[str]) -> T.List[str]: + rm_exact = ('-headerpad_max_install_names',) + rm_prefixes = ('-Wl,', '-L',) + rm_next = ('-L', '-framework',) + ret = [] # T.List[str] + iargs = iter(args) + for arg in iargs: + # Remove this argument + if arg in rm_exact: + continue + # If the argument starts with this, but is not *exactly* this + # f.ex., '-L' should match ['-Lfoo'] but not ['-L', 'foo'] + if arg.startswith(rm_prefixes) and arg not in rm_prefixes: + continue + # Ignore this argument and the one after it + if arg in rm_next: + next(iargs) + continue + ret.append(arg) + return ret + + def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]: + return [] + + def get_lto_link_args(self, *, threads: int = 0, mode: str = 'default', + thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: + return self.linker.get_lto_args() + + def sanitizer_compile_args(self, value: str) -> T.List[str]: + return [] + + def sanitizer_link_args(self, value: str) -> T.List[str]: + return self.linker.sanitizer_args(value) + + def get_asneeded_args(self) -> T.List[str]: + return self.linker.get_asneeded_args() + + def headerpad_args(self) -> T.List[str]: + return self.linker.headerpad_args() + + def bitcode_args(self) -> T.List[str]: + return self.linker.bitcode_args() + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + raise EnvironmentException(f'{self.id} does not implement get_buildtype_args') + + def get_buildtype_linker_args(self, buildtype: str) -> T.List[str]: + return self.linker.get_buildtype_args(buildtype) + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, + darwin_versions: T.Tuple[str, str]) -> T.List[str]: + return self.linker.get_soname_args( + env, prefix, shlib_name, suffix, soversion, + darwin_versions) + + def get_target_link_args(self, target: 'BuildTarget') -> T.List[str]: + return target.link_args + + def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]: + return dep.get_compile_args() + + def get_dependency_link_args(self, dep: 'Dependency') -> T.List[str]: + return dep.get_link_args() + + @classmethod + def use_linker_args(cls, linker: str, version: str) -> T.List[str]: + """Get a list of arguments to pass to the compiler to set the linker. + """ + return [] + + def get_coverage_args(self) -> T.List[str]: + return [] + + def get_coverage_link_args(self) -> T.List[str]: + return self.linker.get_coverage_args() + + def get_disable_assert_args(self) -> T.List[str]: + return [] + + def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: + raise EnvironmentException('This compiler does not support Windows CRT selection') + + def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]: + raise EnvironmentException('This compiler does not support Windows CRT selection') + + def get_compile_only_args(self) -> T.List[str]: + return [] + + def get_preprocess_only_args(self) -> T.List[str]: + raise EnvironmentException('This compiler does not have a preprocessor') + + def get_preprocess_to_file_args(self) -> T.List[str]: + return self.get_preprocess_only_args() + + def get_default_include_dirs(self) -> T.List[str]: + # TODO: This is a candidate for returning an immutable list + return [] + + def get_largefile_args(self) -> T.List[str]: + '''Enable transparent large-file-support for 32-bit UNIX systems''' + if not (self.get_argument_syntax() == 'msvc' or self.info.is_darwin()): + # Enable large-file support unconditionally on all platforms other + # than macOS and MSVC. macOS is now 64-bit-only so it doesn't + # need anything special, and MSVC doesn't have automatic LFS. + # You must use the 64-bit counterparts explicitly. + # glibc, musl, and uclibc, and all BSD libcs support this. On Android, + # support for transparent LFS is available depending on the version of + # Bionic: https://github.com/android/platform_bionic#32-bit-abi-bugs + # https://code.google.com/p/android/issues/detail?id=64613 + # + # If this breaks your code, fix it! It's been 20+ years! + return ['-D_FILE_OFFSET_BITS=64'] + # We don't enable -D_LARGEFILE64_SOURCE since that enables + # transitionary features and must be enabled by programs that use + # those features explicitly. + return [] + + def get_library_dirs(self, env: 'Environment', + elf_class: T.Optional[int] = None) -> T.List[str]: + return [] + + def get_return_value(self, + fname: str, + rtype: str, + prefix: str, + env: 'Environment', + extra_args: T.Optional[T.List[str]], + dependencies: T.Optional[T.List['Dependency']]) -> T.Union[str, int]: + raise EnvironmentException(f'{self.id} does not support get_return_value') + + def find_framework(self, + name: str, + env: 'Environment', + extra_dirs: T.List[str], + allow_system: bool = True) -> T.Optional[T.List[str]]: + raise EnvironmentException(f'{self.id} does not support find_framework') + + def find_framework_paths(self, env: 'Environment') -> T.List[str]: + raise EnvironmentException(f'{self.id} does not support find_framework_paths') + + def attribute_check_func(self, name: str) -> str: + raise EnvironmentException(f'{self.id} does not support attribute checks') + + def get_pch_suffix(self) -> str: + raise EnvironmentException(f'{self.id} does not support pre compiled headers') + + def get_pch_name(self, name: str) -> str: + raise EnvironmentException(f'{self.id} does not support pre compiled headers') + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + raise EnvironmentException(f'{self.id} does not support pre compiled headers') + + def get_has_func_attribute_extra_args(self, name: str) -> T.List[str]: + raise EnvironmentException(f'{self.id} does not support function attributes') + + def name_string(self) -> str: + return ' '.join(self.exelist) + + @abc.abstractmethod + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + """Check that this compiler actually works. + + This should provide a simple compile/link test. Something as simple as: + ```python + main(): return 0 + ``` + is good enough here. + """ + + def split_shlib_to_parts(self, fname: str) -> T.Tuple[T.Optional[str], str]: + return None, fname + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + return [] + + def get_std_exe_link_args(self) -> T.List[str]: + # TODO: is this a linker property? + return [] + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + return [] + + def depfile_for_object(self, objfile: str) -> T.Optional[str]: + return objfile + '.' + self.get_depfile_suffix() + + def get_depfile_suffix(self) -> str: + raise EnvironmentException(f'{self.id} does not implement get_depfile_suffix') + + def get_no_stdinc_args(self) -> T.List[str]: + """Arguments to turn off default inclusion of standard libraries.""" + return [] + + def get_warn_args(self, level: str) -> T.List[str]: + return [] + + def get_werror_args(self) -> T.List[str]: + return [] + + @abc.abstractmethod + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + pass + + def get_module_incdir_args(self) -> T.Tuple[str, ...]: + raise EnvironmentException(f'{self.id} does not implement get_module_incdir_args') + + def get_module_outdir_args(self, path: str) -> T.List[str]: + raise EnvironmentException(f'{self.id} does not implement get_module_outdir_args') + + def module_name_to_filename(self, module_name: str) -> str: + raise EnvironmentException(f'{self.id} does not implement module_name_to_filename') + + def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: + """Arguments to pass the compiler and/or linker for checks. + + The default implementation turns off optimizations. + + Examples of things that go here: + - extra arguments for error checking + - Arguments required to make the compiler exit with a non-zero status + when something is wrong. + """ + return self.get_no_optimization_args() + + def get_no_optimization_args(self) -> T.List[str]: + """Arguments to the compiler to turn off all optimizations.""" + return [] + + def build_wrapper_args(self, env: 'Environment', + extra_args: T.Union[None, CompilerArgs, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], + dependencies: T.Optional[T.List['Dependency']], + mode: CompileCheckMode = CompileCheckMode.COMPILE) -> CompilerArgs: + """Arguments to pass the build_wrapper helper. + + This generally needs to be set on a per-language baises. It provides + a hook for languages to handle dependencies and extra args. The base + implementation handles the most common cases, namely adding the + check_arguments, unwrapping dependencies, and appending extra args. + """ + if callable(extra_args): + extra_args = extra_args(mode) + if extra_args is None: + extra_args = [] + if dependencies is None: + dependencies = [] + + # Collect compiler arguments + args = self.compiler_args(self.get_compiler_check_args(mode)) + for d in dependencies: + # Add compile flags needed by dependencies + args += d.get_compile_args() + if mode is CompileCheckMode.LINK: + # Add link flags needed to find dependencies + args += d.get_link_args() + + if mode is CompileCheckMode.COMPILE: + # Add DFLAGS from the env + args += env.coredata.get_external_args(self.for_machine, self.language) + elif mode is CompileCheckMode.LINK: + # Add LDFLAGS from the env + args += env.coredata.get_external_link_args(self.for_machine, self.language) + # extra_args must override all other arguments, so we add them last + args += extra_args + return args + + @contextlib.contextmanager + def _build_wrapper(self, code: 'mesonlib.FileOrString', env: 'Environment', + extra_args: T.Union[None, CompilerArgs, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None, + mode: str = 'compile', want_output: bool = False, + disable_cache: bool = False, + temp_dir: str = None) -> T.Iterator[T.Optional[CompileResult]]: + """Helper for getting a cacched value when possible. + + This method isn't meant to be called externally, it's mean to be + wrapped by other methods like compiles() and links(). + """ + args = self.build_wrapper_args(env, extra_args, dependencies, CompileCheckMode(mode)) + if disable_cache or want_output: + with self.compile(code, extra_args=args, mode=mode, want_output=want_output, temp_dir=env.scratch_dir) as r: + yield r + else: + with self.cached_compile(code, env.coredata, extra_args=args, mode=mode, temp_dir=env.scratch_dir) as r: + yield r + + def compiles(self, code: 'mesonlib.FileOrString', env: 'Environment', *, + extra_args: T.Union[None, T.List[str], CompilerArgs, T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None, + mode: str = 'compile', + disable_cache: bool = False) -> T.Tuple[bool, bool]: + with self._build_wrapper(code, env, extra_args, dependencies, mode, disable_cache=disable_cache) as p: + return p.returncode == 0, p.cached + + def links(self, code: 'mesonlib.FileOrString', env: 'Environment', *, + compiler: T.Optional['Compiler'] = None, + extra_args: T.Union[None, T.List[str], CompilerArgs, T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None, + mode: str = 'compile', + disable_cache: bool = False) -> T.Tuple[bool, bool]: + if compiler: + with compiler._build_wrapper(code, env, dependencies=dependencies, want_output=True) as r: + objfile = mesonlib.File.from_absolute_file(r.output_name) + return self.compiles(objfile, env, extra_args=extra_args, + dependencies=dependencies, mode='link', disable_cache=True) + + return self.compiles(code, env, extra_args=extra_args, + dependencies=dependencies, mode='link', disable_cache=disable_cache) + + def get_feature_args(self, kwargs: T.Dict[str, T.Any], build_to_src: str) -> T.List[str]: + """Used by D for extra language features.""" + # TODO: using a TypeDict here would improve this + raise EnvironmentException(f'{self.id} does not implement get_feature_args') + + def get_prelink_args(self, prelink_name: str, obj_list: T.List[str]) -> T.List[str]: + raise EnvironmentException(f'{self.id} does not know how to do prelinking.') + + def rsp_file_syntax(self) -> 'RSPFileSyntax': + """The format of the RSP file that this compiler supports. + + If `self.can_linker_accept_rsp()` returns True, then this needs to + be implemented + """ + return self.linker.rsp_file_syntax() + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + """Arguments required for a debug build.""" + return [] + + def get_no_warn_args(self) -> T.List[str]: + """Arguments to completely disable warnings.""" + return [] + + def needs_static_linker(self) -> bool: + raise NotImplementedError(f'There is no static linker for {self.language}') + + def get_preprocessor(self) -> Compiler: + """Get compiler's preprocessor. + """ + raise EnvironmentException(f'{self.get_id()} does not support preprocessor') + +def get_global_options(lang: str, + comp: T.Type[Compiler], + for_machine: MachineChoice, + env: 'Environment') -> 'KeyedOptionDictType': + """Retrieve options that apply to all compilers for a given language.""" + description = f'Extra arguments passed to the {lang}' + argkey = OptionKey('args', lang=lang, machine=for_machine) + largkey = argkey.evolve('link_args') + envkey = argkey.evolve('env_args') + + comp_key = argkey if argkey in env.options else envkey + + comp_options = env.options.get(comp_key, []) + link_options = env.options.get(largkey, []) + + cargs = coredata.UserArrayOption( + description + ' compiler', + comp_options, split_args=True, user_input=True, allow_dups=True) + + largs = coredata.UserArrayOption( + description + ' linker', + link_options, split_args=True, user_input=True, allow_dups=True) + + if comp.INVOKES_LINKER and comp_key == envkey: + # If the compiler acts as a linker driver, and we're using the + # environment variable flags for both the compiler and linker + # arguments, then put the compiler flags in the linker flags as well. + # This is how autotools works, and the env vars freature is for + # autotools compatibility. + largs.extend_value(comp_options) + + opts: 'KeyedOptionDictType' = {argkey: cargs, largkey: largs} + + return opts diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py new file mode 100644 index 0000000..4466a1b --- /dev/null +++ b/mesonbuild/compilers/cpp.py @@ -0,0 +1,890 @@ +# Copyright 2012-2017 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 __future__ import annotations + +import copy +import functools +import os.path +import typing as T + +from .. import coredata +from .. import mlog +from ..mesonlib import MesonException, version_compare, OptionKey + +from .compilers import ( + gnu_winlibs, + msvc_winlibs, + Compiler, +) +from .c_function_attributes import CXX_FUNC_ATTRIBUTES, C_FUNC_ATTRIBUTES +from .mixins.clike import CLikeCompiler +from .mixins.ccrx import CcrxCompiler +from .mixins.ti import TICompiler +from .mixins.arm import ArmCompiler, ArmclangCompiler +from .mixins.visualstudio import MSVCCompiler, ClangClCompiler +from .mixins.gnu import GnuCompiler, gnu_common_warning_args, gnu_cpp_warning_args +from .mixins.intel import IntelGnuLikeCompiler, IntelVisualStudioLikeCompiler +from .mixins.clang import ClangCompiler +from .mixins.elbrus import ElbrusCompiler +from .mixins.pgi import PGICompiler +from .mixins.emscripten import EmscriptenMixin + +if T.TYPE_CHECKING: + from .compilers import CompileCheckMode + from ..coredata import MutableKeyedOptionDictType, KeyedOptionDictType + from ..dependencies import Dependency + from ..envconfig import MachineInfo + from ..environment import Environment + from ..linkers import DynamicLinker + from ..mesonlib import MachineChoice + from ..programs import ExternalProgram + CompilerMixinBase = CLikeCompiler +else: + CompilerMixinBase = object + + +def non_msvc_eh_options(eh: str, args: T.List[str]) -> None: + if eh == 'none': + args.append('-fno-exceptions') + elif eh in {'s', 'c'}: + mlog.warning('non-MSVC compilers do not support ' + eh + ' exception handling.' + + 'You may want to set eh to \'default\'.') + +class CPPCompiler(CLikeCompiler, Compiler): + def attribute_check_func(self, name: str) -> str: + try: + return CXX_FUNC_ATTRIBUTES.get(name, C_FUNC_ATTRIBUTES[name]) + except KeyError: + raise MesonException(f'Unknown function attribute "{name}"') + + language = 'cpp' + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + # If a child ObjCPP class has already set it, don't set it ourselves + Compiler.__init__(self, ccache, exelist, version, for_machine, info, + is_cross=is_cross, linker=linker, + full_version=full_version) + CLikeCompiler.__init__(self, exe_wrapper) + + @staticmethod + def get_display_language() -> str: + return 'C++' + + def get_no_stdinc_args(self) -> T.List[str]: + return ['-nostdinc++'] + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + code = 'class breakCCompiler;int main(void) { return 0; }\n' + return self._sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code) + + def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: + # -fpermissive allows non-conforming code to compile which is necessary + # for many C++ checks. Particularly, the has_header_symbol check is + # too strict without this and always fails. + return super().get_compiler_check_args(mode) + ['-fpermissive'] + + def has_header_symbol(self, hname: str, symbol: str, prefix: str, + env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + # Check if it's a C-like symbol + found, cached = super().has_header_symbol(hname, symbol, prefix, env, + extra_args=extra_args, + dependencies=dependencies) + if found: + return True, cached + # Check if it's a class or a template + if extra_args is None: + extra_args = [] + t = f'''{prefix} + #include <{hname}> + using {symbol}; + int main(void) {{ return 0; }}''' + return self.compiles(t, env, extra_args=extra_args, + dependencies=dependencies) + + def _test_cpp_std_arg(self, cpp_std_value: str) -> bool: + # Test whether the compiler understands a -std=XY argument + assert cpp_std_value.startswith('-std=') + + # This test does not use has_multi_arguments() for two reasons: + # 1. has_multi_arguments() requires an env argument, which the compiler + # object does not have at this point. + # 2. even if it did have an env object, that might contain another more + # recent -std= argument, which might lead to a cascaded failure. + CPP_TEST = 'int i = static_cast<int>(0);' + with self.compile(CPP_TEST, extra_args=[cpp_std_value], mode='compile') as p: + if p.returncode == 0: + mlog.debug(f'Compiler accepts {cpp_std_value}:', 'YES') + return True + else: + mlog.debug(f'Compiler accepts {cpp_std_value}:', 'NO') + return False + + @functools.lru_cache() + def _find_best_cpp_std(self, cpp_std: str) -> str: + # The initial version mapping approach to make falling back + # from '-std=c++14' to '-std=c++1y' was too brittle. For instance, + # Apple's Clang uses a different versioning scheme to upstream LLVM, + # making the whole detection logic awfully brittle. Instead, let's + # just see if feeding GCC or Clang our '-std=' setting works, and + # if not, try the fallback argument. + CPP_FALLBACKS = { + 'c++11': 'c++0x', + 'gnu++11': 'gnu++0x', + 'c++14': 'c++1y', + 'gnu++14': 'gnu++1y', + 'c++17': 'c++1z', + 'gnu++17': 'gnu++1z', + 'c++20': 'c++2a', + 'gnu++20': 'gnu++2a', + } + + # Currently, remapping is only supported for Clang, Elbrus and GCC + assert self.id in frozenset(['clang', 'lcc', 'gcc', 'emscripten', 'armltdclang', 'intel-llvm']) + + if cpp_std not in CPP_FALLBACKS: + # 'c++03' and 'c++98' don't have fallback types + return '-std=' + cpp_std + + for i in (cpp_std, CPP_FALLBACKS[cpp_std]): + cpp_std_value = '-std=' + i + if self._test_cpp_std_arg(cpp_std_value): + return cpp_std_value + + raise MesonException(f'C++ Compiler does not support -std={cpp_std}') + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = super().get_options() + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts.update({ + key: coredata.UserComboOption( + 'C++ language standard to use', + ['none'], + 'none', + ), + }) + return opts + + +class ClangCPPCompiler(ClangCompiler, CPPCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + full_version: T.Optional[str] = None): + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + ClangCompiler.__init__(self, defines) + default_warn_args = ['-Wall', '-Winvalid-pch'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-Wpedantic'], + 'everything': ['-Weverything']} + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CPPCompiler.get_options(self) + key = OptionKey('key', machine=self.for_machine, lang=self.language) + opts.update({ + key.evolve('eh'): coredata.UserComboOption( + 'C++ exception handling type.', + ['none', 'default', 'a', 's', 'sc'], + 'default', + ), + key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True), + }) + opts[key.evolve('std')].choices = [ + 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', + 'c++2a', 'c++20', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z', + 'gnu++2a', 'gnu++20', + ] + if self.info.is_windows() or self.info.is_cygwin(): + opts.update({ + key.evolve('winlibs'): coredata.UserArrayOption( + 'Standard Win libraries to link against', + gnu_winlibs, + ), + }) + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value != 'none': + args.append(self._find_best_cpp_std(std.value)) + + non_msvc_eh_options(options[key.evolve('eh')].value, args) + + if not options[key.evolve('rtti')].value: + args.append('-fno-rtti') + + return args + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + if self.info.is_windows() or self.info.is_cygwin(): + # without a typedict mypy can't understand this. + key = OptionKey('winlibs', machine=self.for_machine, lang=self.language) + libs = options[key].value.copy() + assert isinstance(libs, list) + for l in libs: + assert isinstance(l, str) + return libs + return [] + + def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]: + # We need to apply the search prefix here, as these link arguments may + # be passed to a different compiler with a different set of default + # search paths, such as when using Clang for C/C++ and gfortran for + # fortran, + search_dirs: T.List[str] = [] + for d in self.get_compiler_dirs(env, 'libraries'): + search_dirs.append(f'-L{d}') + return search_dirs + ['-lstdc++'] + + +class ArmLtdClangCPPCompiler(ClangCPPCompiler): + + id = 'armltdclang' + + +class AppleClangCPPCompiler(ClangCPPCompiler): + def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]: + # We need to apply the search prefix here, as these link arguments may + # be passed to a different compiler with a different set of default + # search paths, such as when using Clang for C/C++ and gfortran for + # fortran, + search_dirs: T.List[str] = [] + for d in self.get_compiler_dirs(env, 'libraries'): + search_dirs.append(f'-L{d}') + return search_dirs + ['-lc++'] + + +class EmscriptenCPPCompiler(EmscriptenMixin, ClangCPPCompiler): + + id = 'emscripten' + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + full_version: T.Optional[str] = None): + if not is_cross: + raise MesonException('Emscripten compiler can only be used for cross compilation.') + if not version_compare(version, '>=1.39.19'): + raise MesonException('Meson requires Emscripten >= 1.39.19') + ClangCPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper=exe_wrapper, linker=linker, + defines=defines, full_version=full_version) + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value != 'none': + args.append(self._find_best_cpp_std(std.value)) + return args + + +class ArmclangCPPCompiler(ArmclangCompiler, CPPCompiler): + ''' + Keil armclang + ''' + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + ArmclangCompiler.__init__(self) + default_warn_args = ['-Wall', '-Winvalid-pch'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-Wpedantic'], + 'everything': ['-Weverything']} + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CPPCompiler.get_options(self) + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts.update({ + key.evolve('eh'): coredata.UserComboOption( + 'C++ exception handling type.', + ['none', 'default', 'a', 's', 'sc'], + 'default', + ), + }) + opts[key].choices = [ + 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'gnu++98', + 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17', + ] + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value != 'none': + args.append('-std=' + std.value) + + non_msvc_eh_options(options[key.evolve('eh')].value, args) + + return args + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + return [] + + +class GnuCPPCompiler(GnuCompiler, CPPCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + full_version: T.Optional[str] = None): + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + GnuCompiler.__init__(self, defines) + default_warn_args = ['-Wall', '-Winvalid-pch'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-Wpedantic'], + 'everything': (default_warn_args + ['-Wextra', '-Wpedantic'] + + self.supported_warn_args(gnu_common_warning_args) + + self.supported_warn_args(gnu_cpp_warning_args))} + + def get_options(self) -> 'MutableKeyedOptionDictType': + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts = CPPCompiler.get_options(self) + opts.update({ + key.evolve('eh'): coredata.UserComboOption( + 'C++ exception handling type.', + ['none', 'default', 'a', 's', 'sc'], + 'default', + ), + key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True), + key.evolve('debugstl'): coredata.UserBooleanOption( + 'STL debug mode', + False, + ) + }) + opts[key].choices = [ + 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', + 'c++2a', 'c++20', 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17', + 'gnu++1z', 'gnu++2a', 'gnu++20', + ] + if self.info.is_windows() or self.info.is_cygwin(): + opts.update({ + key.evolve('winlibs'): coredata.UserArrayOption( + 'Standard Win libraries to link against', + gnu_winlibs, + ), + }) + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value != 'none': + args.append(self._find_best_cpp_std(std.value)) + + non_msvc_eh_options(options[key.evolve('eh')].value, args) + + if not options[key.evolve('rtti')].value: + args.append('-fno-rtti') + + if options[key.evolve('debugstl')].value: + args.append('-D_GLIBCXX_DEBUG=1') + return args + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + if self.info.is_windows() or self.info.is_cygwin(): + # without a typedict mypy can't understand this. + key = OptionKey('winlibs', machine=self.for_machine, lang=self.language) + libs = options[key].value.copy() + assert isinstance(libs, list) + for l in libs: + assert isinstance(l, str) + return libs + return [] + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + return ['-fpch-preprocess', '-include', os.path.basename(header)] + + def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]: + # We need to apply the search prefix here, as these link arguments may + # be passed to a different compiler with a different set of default + # search paths, such as when using Clang for C/C++ and gfortran for + # fortran, + search_dirs: T.List[str] = [] + for d in self.get_compiler_dirs(env, 'libraries'): + search_dirs.append(f'-L{d}') + return ['-lstdc++'] + + +class PGICPPCompiler(PGICompiler, CPPCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + PGICompiler.__init__(self) + + +class NvidiaHPC_CPPCompiler(PGICompiler, CPPCompiler): + + id = 'nvidia_hpc' + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + PGICompiler.__init__(self) + + +class ElbrusCPPCompiler(ElbrusCompiler, CPPCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + full_version: T.Optional[str] = None): + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + ElbrusCompiler.__init__(self) + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CPPCompiler.get_options(self) + + cpp_stds = ['none', 'c++98', 'gnu++98'] + if version_compare(self.version, '>=1.20.00'): + cpp_stds += ['c++03', 'c++0x', 'c++11', 'gnu++03', 'gnu++0x', 'gnu++11'] + if version_compare(self.version, '>=1.21.00') and version_compare(self.version, '<1.22.00'): + cpp_stds += ['c++14', 'gnu++14', 'c++1y', 'gnu++1y'] + if version_compare(self.version, '>=1.22.00'): + cpp_stds += ['c++14', 'gnu++14'] + if version_compare(self.version, '>=1.23.00'): + cpp_stds += ['c++1y', 'gnu++1y'] + if version_compare(self.version, '>=1.24.00'): + cpp_stds += ['c++1z', 'c++17', 'gnu++1z', 'gnu++17'] + if version_compare(self.version, '>=1.25.00'): + cpp_stds += ['c++2a', 'gnu++2a'] + if version_compare(self.version, '>=1.26.00'): + cpp_stds += ['c++20', 'gnu++20'] + + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts.update({ + key.evolve('eh'): coredata.UserComboOption( + 'C++ exception handling type.', + ['none', 'default', 'a', 's', 'sc'], + 'default', + ), + key.evolve('debugstl'): coredata.UserBooleanOption( + 'STL debug mode', + False, + ), + }) + opts[key].choices = cpp_stds + return opts + + # Elbrus C++ compiler does not have lchmod, but there is only linker warning, not compiler error. + # So we should explicitly fail at this case. + def has_function(self, funcname: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + if funcname == 'lchmod': + return False, False + else: + return super().has_function(funcname, prefix, env, + extra_args=extra_args, + dependencies=dependencies) + + # Elbrus C++ compiler does not support RTTI, so don't check for it. + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value != 'none': + args.append(self._find_best_cpp_std(std.value)) + + non_msvc_eh_options(options[key.evolve('eh')].value, args) + + if options[key.evolve('debugstl')].value: + args.append('-D_GLIBCXX_DEBUG=1') + return args + + +class IntelCPPCompiler(IntelGnuLikeCompiler, CPPCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + IntelGnuLikeCompiler.__init__(self) + self.lang_header = 'c++-header' + default_warn_args = ['-Wall', '-w3', '-Wpch-messages'] + self.warn_args = {'0': [], + '1': default_warn_args + ['-diag-disable:remark'], + '2': default_warn_args + ['-Wextra', '-diag-disable:remark'], + '3': default_warn_args + ['-Wextra', '-diag-disable:remark'], + 'everything': default_warn_args + ['-Wextra']} + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CPPCompiler.get_options(self) + # Every Unix compiler under the sun seems to accept -std=c++03, + # with the exception of ICC. Instead of preventing the user from + # globally requesting C++03, we transparently remap it to C++98 + c_stds = ['c++98', 'c++03'] + g_stds = ['gnu++98', 'gnu++03'] + if version_compare(self.version, '>=15.0.0'): + c_stds += ['c++11', 'c++14'] + g_stds += ['gnu++11'] + if version_compare(self.version, '>=16.0.0'): + c_stds += ['c++17'] + if version_compare(self.version, '>=17.0.0'): + g_stds += ['gnu++14'] + if version_compare(self.version, '>=19.1.0'): + c_stds += ['c++2a'] + g_stds += ['gnu++2a'] + + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts.update({ + key.evolve('eh'): coredata.UserComboOption( + 'C++ exception handling type.', + ['none', 'default', 'a', 's', 'sc'], + 'default', + ), + key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True), + key.evolve('debugstl'): coredata.UserBooleanOption('STL debug mode', False), + }) + opts[key].choices = ['none'] + c_stds + g_stds + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value != 'none': + remap_cpp03 = { + 'c++03': 'c++98', + 'gnu++03': 'gnu++98' + } + args.append('-std=' + remap_cpp03.get(std.value, std.value)) + if options[key.evolve('eh')].value == 'none': + args.append('-fno-exceptions') + if not options[key.evolve('rtti')].value: + args.append('-fno-rtti') + if options[key.evolve('debugstl')].value: + args.append('-D_GLIBCXX_DEBUG=1') + return args + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + return [] + + +class IntelLLVMCPPCompiler(ClangCPPCompiler): + + id = 'intel-llvm' + + +class VisualStudioLikeCPPCompilerMixin(CompilerMixinBase): + + """Mixin for C++ specific method overrides in MSVC-like compilers.""" + + VC_VERSION_MAP = { + 'none': (True, None), + 'vc++11': (True, 11), + 'vc++14': (True, 14), + 'vc++17': (True, 17), + 'vc++20': (True, 20), + 'vc++latest': (True, "latest"), + 'c++11': (False, 11), + 'c++14': (False, 14), + 'c++17': (False, 17), + 'c++20': (False, 20), + 'c++latest': (False, "latest"), + } + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + # need a typeddict for this + key = OptionKey('winlibs', machine=self.for_machine, lang=self.language) + return T.cast('T.List[str]', options[key].value[:]) + + def _get_options_impl(self, opts: 'MutableKeyedOptionDictType', cpp_stds: T.List[str]) -> 'MutableKeyedOptionDictType': + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts.update({ + key.evolve('eh'): coredata.UserComboOption( + 'C++ exception handling type.', + ['none', 'default', 'a', 's', 'sc'], + 'default', + ), + key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True), + key.evolve('winlibs'): coredata.UserArrayOption( + 'Windows libs to link against.', + msvc_winlibs, + ), + }) + opts[key.evolve('std')].choices = cpp_stds + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + + eh = options[key.evolve('eh')] + if eh.value == 'default': + args.append('/EHsc') + elif eh.value == 'none': + args.append('/EHs-c-') + else: + args.append('/EH' + eh.value) + + if not options[key.evolve('rtti')].value: + args.append('/GR-') + + permissive, ver = self.VC_VERSION_MAP[options[key].value] + + if ver is not None: + args.append(f'/std:c++{ver}') + + if not permissive: + args.append('/permissive-') + + return args + + def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: + # XXX: this is a hack because so much GnuLike stuff is in the base CPPCompiler class. + return Compiler.get_compiler_check_args(self, mode) + + +class CPP11AsCPP14Mixin(CompilerMixinBase): + + """Mixin class for VisualStudio and ClangCl to replace C++11 std with C++14. + + This is a limitation of Clang and MSVC that ICL doesn't share. + """ + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + # Note: there is no explicit flag for supporting C++11; we attempt to do the best we can + # which means setting the C++ standard version to C++14, in compilers that support it + # (i.e., after VS2015U3) + # if one is using anything before that point, one cannot set the standard. + key = OptionKey('std', machine=self.for_machine, lang=self.language) + if options[key].value in {'vc++11', 'c++11'}: + mlog.warning(self.id, 'does not support C++11;', + 'attempting best effort; setting the standard to C++14', once=True) + # Don't mutate anything we're going to change, we need to use + # deepcopy since we're messing with members, and we can't simply + # copy the members because the option proxy doesn't support it. + options = copy.deepcopy(options) + if options[key].value == 'vc++11': + options[key].value = 'vc++14' + else: + options[key].value = 'c++14' + return super().get_option_compile_args(options) + + +class VisualStudioCPPCompiler(CPP11AsCPP14Mixin, VisualStudioLikeCPPCompilerMixin, MSVCCompiler, CPPCompiler): + + id = 'msvc' + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', target: str, + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + MSVCCompiler.__init__(self, target) + + # By default, MSVC has a broken __cplusplus define that pretends to be c++98: + # https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=msvc-160 + # Pass the flag to enable a truthful define, if possible. + if version_compare(self.version, '>= 19.14.26428'): + self.always_args = self.always_args + ['/Zc:__cplusplus'] + + def get_options(self) -> 'MutableKeyedOptionDictType': + cpp_stds = ['none', 'c++11', 'vc++11'] + # Visual Studio 2015 and later + if version_compare(self.version, '>=19'): + cpp_stds.extend(['c++14', 'c++latest', 'vc++latest']) + # Visual Studio 2017 and later + if version_compare(self.version, '>=19.11'): + cpp_stds.extend(['vc++14', 'c++17', 'vc++17']) + if version_compare(self.version, '>=19.29'): + cpp_stds.extend(['c++20', 'vc++20']) + return self._get_options_impl(super().get_options(), cpp_stds) + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + key = OptionKey('std', machine=self.for_machine, lang=self.language) + if options[key].value != 'none' and version_compare(self.version, '<19.00.24210'): + mlog.warning('This version of MSVC does not support cpp_std arguments') + options = copy.copy(options) + options[key].value = 'none' + + args = super().get_option_compile_args(options) + + if version_compare(self.version, '<19.11'): + try: + i = args.index('/permissive-') + except ValueError: + return args + del args[i] + return args + +class ClangClCPPCompiler(CPP11AsCPP14Mixin, VisualStudioLikeCPPCompilerMixin, ClangClCompiler, CPPCompiler): + + id = 'clang-cl' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', target: str, + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CPPCompiler.__init__(self, [], exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + ClangClCompiler.__init__(self, target) + + def get_options(self) -> 'MutableKeyedOptionDictType': + cpp_stds = ['none', 'c++11', 'vc++11', 'c++14', 'vc++14', 'c++17', 'vc++17', 'c++20', 'vc++20', 'c++latest'] + return self._get_options_impl(super().get_options(), cpp_stds) + + +class IntelClCPPCompiler(VisualStudioLikeCPPCompilerMixin, IntelVisualStudioLikeCompiler, CPPCompiler): + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', target: str, + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CPPCompiler.__init__(self, [], exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + IntelVisualStudioLikeCompiler.__init__(self, target) + + def get_options(self) -> 'MutableKeyedOptionDictType': + # This has only been tested with version 19.0, + cpp_stds = ['none', 'c++11', 'vc++11', 'c++14', 'vc++14', 'c++17', 'vc++17', 'c++latest'] + return self._get_options_impl(super().get_options(), cpp_stds) + + def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: + # XXX: this is a hack because so much GnuLike stuff is in the base CPPCompiler class. + return IntelVisualStudioLikeCompiler.get_compiler_check_args(self, mode) + + +class IntelLLVMClCPPCompiler(IntelClCPPCompiler): + + id = 'intel-llvm-cl' + + +class ArmCPPCompiler(ArmCompiler, CPPCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + ArmCompiler.__init__(self) + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CPPCompiler.get_options(self) + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c++03', 'c++11'] + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value == 'c++11': + args.append('--cpp11') + elif std.value == 'c++03': + args.append('--cpp') + return args + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + return [] + + def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: + return [] + + +class CcrxCPPCompiler(CcrxCompiler, CPPCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + CcrxCompiler.__init__(self) + + # Override CCompiler.get_always_args + def get_always_args(self) -> T.List[str]: + return ['-nologo', '-lang=cpp'] + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + return [] + + def get_compile_only_args(self) -> T.List[str]: + return [] + + def get_output_args(self, target: str) -> T.List[str]: + return [f'-output=obj={target}'] + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + return [] + + def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: + return [] + +class TICPPCompiler(TICompiler, CPPCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + TICompiler.__init__(self) + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = CPPCompiler.get_options(self) + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c++03'] + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value != 'none': + args.append('--' + std.value) + return args + + def get_always_args(self) -> T.List[str]: + return [] + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + return [] + +class C2000CPPCompiler(TICPPCompiler): + # Required for backwards compat with projects created before ti-cgt support existed + id = 'c2000' diff --git a/mesonbuild/compilers/cs.py b/mesonbuild/compilers/cs.py new file mode 100644 index 0000000..14fcfd7 --- /dev/null +++ b/mesonbuild/compilers/cs.py @@ -0,0 +1,154 @@ +# Copyright 2012-2017 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 __future__ import annotations + +import os.path, subprocess +import textwrap +import typing as T + +from ..mesonlib import EnvironmentException +from ..linkers import RSPFileSyntax + +from .compilers import Compiler, mono_buildtype_args +from .mixins.islinker import BasicLinkerIsCompilerMixin + +if T.TYPE_CHECKING: + from ..envconfig import MachineInfo + from ..environment import Environment + from ..mesonlib import MachineChoice + +cs_optimization_args = { + 'plain': [], + '0': [], + 'g': [], + '1': ['-optimize+'], + '2': ['-optimize+'], + '3': ['-optimize+'], + 's': ['-optimize+'], + } # type: T.Dict[str, T.List[str]] + + +class CsCompiler(BasicLinkerIsCompilerMixin, Compiler): + + language = 'cs' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + info: 'MachineInfo', runner: T.Optional[str] = None): + super().__init__([], exelist, version, for_machine, info) + self.runner = runner + + @classmethod + def get_display_language(cls) -> str: + return 'C sharp' + + def get_always_args(self) -> T.List[str]: + return ['/nologo'] + + def get_linker_always_args(self) -> T.List[str]: + return ['/nologo'] + + def get_output_args(self, fname: str) -> T.List[str]: + return ['-out:' + fname] + + def get_link_args(self, fname: str) -> T.List[str]: + return ['-r:' + fname] + + def get_werror_args(self) -> T.List[str]: + return ['-warnaserror'] + + def get_pic_args(self) -> T.List[str]: + return [] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:2] == '-L': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + if i[:5] == '-lib:': + parameter_list[idx] = i[:5] + os.path.normpath(os.path.join(build_dir, i[5:])) + + return parameter_list + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + return [] + + def get_pch_name(self, header_name: str) -> str: + return '' + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + src = 'sanity.cs' + obj = 'sanity.exe' + source_name = os.path.join(work_dir, src) + with open(source_name, 'w', encoding='utf-8') as ofile: + ofile.write(textwrap.dedent(''' + public class Sanity { + static public void Main () { + } + } + ''')) + pc = subprocess.Popen(self.exelist + self.get_always_args() + [src], cwd=work_dir) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('C# compiler %s can not compile programs.' % self.name_string()) + if self.runner: + cmdlist = [self.runner, obj] + else: + cmdlist = [os.path.join(work_dir, obj)] + pe = subprocess.Popen(cmdlist, cwd=work_dir) + pe.wait() + if pe.returncode != 0: + raise EnvironmentException('Executables created by Mono compiler %s are not runnable.' % self.name_string()) + + def needs_static_linker(self) -> bool: + return False + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return mono_buildtype_args[buildtype] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return ['-debug'] if is_debug else [] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return cs_optimization_args[optimization_level] + + +class MonoCompiler(CsCompiler): + + id = 'mono' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + info: 'MachineInfo'): + super().__init__(exelist, version, for_machine, info, runner='mono') + + def rsp_file_syntax(self) -> 'RSPFileSyntax': + return RSPFileSyntax.GCC + + +class VisualStudioCsCompiler(CsCompiler): + + id = 'csc' + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + res = mono_buildtype_args[buildtype] + if not self.info.is_windows(): + tmp = [] + for flag in res: + if flag == '-debug': + flag = '-debug:portable' + tmp.append(flag) + res = tmp + return res + + def rsp_file_syntax(self) -> 'RSPFileSyntax': + return RSPFileSyntax.MSVC diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py new file mode 100644 index 0000000..d008d0c --- /dev/null +++ b/mesonbuild/compilers/cuda.py @@ -0,0 +1,791 @@ +# Copyright 2012-2017 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 __future__ import annotations + +import enum +import os.path +import string +import typing as T + +from .. import coredata +from .. import mlog +from ..mesonlib import ( + EnvironmentException, Popen_safe, OptionOverrideProxy, + is_windows, LibType, OptionKey, version_compare, +) +from .compilers import (Compiler, cuda_buildtype_args, cuda_optimization_args, + cuda_debug_args) + +if T.TYPE_CHECKING: + from .compilers import CompileCheckMode + from ..build import BuildTarget + from ..coredata import MutableKeyedOptionDictType, KeyedOptionDictType + from ..dependencies import Dependency + from ..environment import Environment # noqa: F401 + from ..envconfig import MachineInfo + from ..linkers import DynamicLinker + from ..mesonlib import MachineChoice + from ..programs import ExternalProgram + + +class _Phase(enum.Enum): + + COMPILER = 'compiler' + LINKER = 'linker' + + +class CudaCompiler(Compiler): + + LINKER_PREFIX = '-Xlinker=' + language = 'cuda' + + # NVCC flags taking no arguments. + _FLAG_PASSTHRU_NOARGS = { + # NVCC --long-option, NVCC -short-option CUDA Toolkit 11.2.1 Reference + '--objdir-as-tempdir', '-objtemp', # 4.2.1.2 + '--generate-dependency-targets', '-MP', # 4.2.1.12 + '--allow-unsupported-compiler', '-allow-unsupported-compiler', # 4.2.1.14 + '--link', # 4.2.2.1 + '--lib', '-lib', # 4.2.2.2 + '--device-link', '-dlink', # 4.2.2.3 + '--device-c', '-dc', # 4.2.2.4 + '--device-w', '-dw', # 4.2.2.5 + '--cuda', '-cuda', # 4.2.2.6 + '--compile', '-c', # 4.2.2.7 + '--fatbin', '-fatbin', # 4.2.2.8 + '--cubin', '-cubin', # 4.2.2.9 + '--ptx', '-ptx', # 4.2.2.10 + '--preprocess', '-E', # 4.2.2.11 + '--generate-dependencies', '-M', # 4.2.2.12 + '--generate-nonsystem-dependencies', '-MM', # 4.2.2.13 + '--generate-dependencies-with-compile', '-MD', # 4.2.2.14 + '--generate-nonsystem-dependencies-with-compile', '-MMD', # 4.2.2.15 + '--run', # 4.2.2.16 + '--profile', '-pg', # 4.2.3.1 + '--debug', '-g', # 4.2.3.2 + '--device-debug', '-G', # 4.2.3.3 + '--extensible-whole-program', '-ewp', # 4.2.3.4 + '--generate-line-info', '-lineinfo', # 4.2.3.5 + '--dlink-time-opt', '-dlto', # 4.2.3.8 + '--no-exceptions', '-noeh', # 4.2.3.11 + '--shared', '-shared', # 4.2.3.12 + '--no-host-device-initializer-list', '-nohdinitlist', # 4.2.3.15 + '--expt-relaxed-constexpr', '-expt-relaxed-constexpr', # 4.2.3.16 + '--extended-lambda', '-extended-lambda', # 4.2.3.17 + '--expt-extended-lambda', '-expt-extended-lambda', # 4.2.3.18 + '--m32', '-m32', # 4.2.3.20 + '--m64', '-m64', # 4.2.3.21 + '--forward-unknown-to-host-compiler', '-forward-unknown-to-host-compiler', # 4.2.5.1 + '--forward-unknown-to-host-linker', '-forward-unknown-to-host-linker', # 4.2.5.2 + '--dont-use-profile', '-noprof', # 4.2.5.3 + '--dryrun', '-dryrun', # 4.2.5.5 + '--verbose', '-v', # 4.2.5.6 + '--keep', '-keep', # 4.2.5.7 + '--save-temps', '-save-temps', # 4.2.5.9 + '--clean-targets', '-clean', # 4.2.5.10 + '--no-align-double', # 4.2.5.16 + '--no-device-link', '-nodlink', # 4.2.5.17 + '--allow-unsupported-compiler', '-allow-unsupported-compiler', # 4.2.5.18 + '--use_fast_math', '-use_fast_math', # 4.2.7.7 + '--extra-device-vectorization', '-extra-device-vectorization', # 4.2.7.12 + '--compile-as-tools-patch', '-astoolspatch', # 4.2.7.13 + '--keep-device-functions', '-keep-device-functions', # 4.2.7.14 + '--disable-warnings', '-w', # 4.2.8.1 + '--source-in-ptx', '-src-in-ptx', # 4.2.8.2 + '--restrict', '-restrict', # 4.2.8.3 + '--Wno-deprecated-gpu-targets', '-Wno-deprecated-gpu-targets', # 4.2.8.4 + '--Wno-deprecated-declarations', '-Wno-deprecated-declarations', # 4.2.8.5 + '--Wreorder', '-Wreorder', # 4.2.8.6 + '--Wdefault-stream-launch', '-Wdefault-stream-launch', # 4.2.8.7 + '--Wext-lambda-captures-this', '-Wext-lambda-captures-this', # 4.2.8.8 + '--display-error-number', '-err-no', # 4.2.8.10 + '--resource-usage', '-res-usage', # 4.2.8.14 + '--help', '-h', # 4.2.8.15 + '--version', '-V', # 4.2.8.16 + '--list-gpu-code', '-code-ls', # 4.2.8.20 + '--list-gpu-arch', '-arch-ls', # 4.2.8.21 + } + # Dictionary of NVCC flags taking either one argument or a comma-separated list. + # Maps --long to -short options, because the short options are more GCC-like. + _FLAG_LONG2SHORT_WITHARGS = { + '--output-file': '-o', # 4.2.1.1 + '--pre-include': '-include', # 4.2.1.3 + '--library': '-l', # 4.2.1.4 + '--define-macro': '-D', # 4.2.1.5 + '--undefine-macro': '-U', # 4.2.1.6 + '--include-path': '-I', # 4.2.1.7 + '--system-include': '-isystem', # 4.2.1.8 + '--library-path': '-L', # 4.2.1.9 + '--output-directory': '-odir', # 4.2.1.10 + '--dependency-output': '-MF', # 4.2.1.11 + '--compiler-bindir': '-ccbin', # 4.2.1.13 + '--archiver-binary': '-arbin', # 4.2.1.15 + '--cudart': '-cudart', # 4.2.1.16 + '--cudadevrt': '-cudadevrt', # 4.2.1.17 + '--libdevice-directory': '-ldir', # 4.2.1.18 + '--target-directory': '-target-dir', # 4.2.1.19 + '--optimization-info': '-opt-info', # 4.2.3.6 + '--optimize': '-O', # 4.2.3.7 + '--ftemplate-backtrace-limit': '-ftemplate-backtrace-limit', # 4.2.3.9 + '--ftemplate-depth': '-ftemplate-depth', # 4.2.3.10 + '--x': '-x', # 4.2.3.13 + '--std': '-std', # 4.2.3.14 + '--machine': '-m', # 4.2.3.19 + '--compiler-options': '-Xcompiler', # 4.2.4.1 + '--linker-options': '-Xlinker', # 4.2.4.2 + '--archive-options': '-Xarchive', # 4.2.4.3 + '--ptxas-options': '-Xptxas', # 4.2.4.4 + '--nvlink-options': '-Xnvlink', # 4.2.4.5 + '--threads': '-t', # 4.2.5.4 + '--keep-dir': '-keep-dir', # 4.2.5.8 + '--run-args': '-run-args', # 4.2.5.11 + '--input-drive-prefix': '-idp', # 4.2.5.12 + '--dependency-drive-prefix': '-ddp', # 4.2.5.13 + '--drive-prefix': '-dp', # 4.2.5.14 + '--dependency-target-name': '-MT', # 4.2.5.15 + '--default-stream': '-default-stream', # 4.2.6.1 + '--gpu-architecture': '-arch', # 4.2.7.1 + '--gpu-code': '-code', # 4.2.7.2 + '--generate-code': '-gencode', # 4.2.7.3 + '--relocatable-device-code': '-rdc', # 4.2.7.4 + '--entries': '-e', # 4.2.7.5 + '--maxrregcount': '-maxrregcount', # 4.2.7.6 + '--ftz': '-ftz', # 4.2.7.8 + '--prec-div': '-prec-div', # 4.2.7.9 + '--prec-sqrt': '-prec-sqrt', # 4.2.7.10 + '--fmad': '-fmad', # 4.2.7.11 + '--Werror': '-Werror', # 4.2.8.9 + '--diag-error': '-diag-error', # 4.2.8.11 + '--diag-suppress': '-diag-suppress', # 4.2.8.12 + '--diag-warn': '-diag-warn', # 4.2.8.13 + '--options-file': '-optf', # 4.2.8.17 + '--time': '-time', # 4.2.8.18 + '--qpp-config': '-qpp-config', # 4.2.8.19 + } + # Reverse map -short to --long options. + _FLAG_SHORT2LONG_WITHARGS = {v: k for k, v in _FLAG_LONG2SHORT_WITHARGS.items()} + + id = 'nvcc' + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, exe_wrapper: T.Optional['ExternalProgram'], + host_compiler: Compiler, info: 'MachineInfo', + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + super().__init__(ccache, exelist, version, for_machine, info, linker=linker, full_version=full_version, is_cross=is_cross) + self.exe_wrapper = exe_wrapper + self.host_compiler = host_compiler + self.base_options = host_compiler.base_options + self.warn_args = {level: self._to_host_flags(flags) for level, flags in host_compiler.warn_args.items()} + + @classmethod + def _shield_nvcc_list_arg(cls, arg: str, listmode: bool = True) -> str: + r""" + Shield an argument against both splitting by NVCC's list-argument + parse logic, and interpretation by any shell. + + NVCC seems to consider every comma , that is neither escaped by \ nor inside + a double-quoted string a split-point. Single-quotes do not provide protection + against splitting; In fact, after splitting they are \-escaped. Unfortunately, + double-quotes don't protect against shell expansion. What follows is a + complex dance to accommodate everybody. + """ + + SQ = "'" + DQ = '"' + CM = "," + BS = "\\" + DQSQ = DQ+SQ+DQ + quotable = set(string.whitespace+'"$`\\') + + if CM not in arg or not listmode: + if SQ not in arg: + # If any of the special characters "$`\ or whitespace are present, single-quote. + # Otherwise return bare. + if set(arg).intersection(quotable): + return SQ+arg+SQ + else: + return arg # Easy case: no splits, no quoting. + else: + # There are single quotes. Double-quote them, and single-quote the + # strings between them. + l = [cls._shield_nvcc_list_arg(s) for s in arg.split(SQ)] + l = sum([[s, DQSQ] for s in l][:-1], []) # Interleave l with DQSQs + return ''.join(l) + else: + # A comma is present, and list mode was active. + # We apply (what we guess is) the (primitive) NVCC splitting rule: + l = [''] + instring = False + argit = iter(arg) + for c in argit: + if c == CM and not instring: + l.append('') + elif c == DQ: + l[-1] += c + instring = not instring + elif c == BS: + try: + l[-1] += next(argit) + except StopIteration: + break + else: + l[-1] += c + + # Shield individual strings, without listmode, then return them with + # escaped commas between them. + l = [cls._shield_nvcc_list_arg(s, listmode=False) for s in l] + return r'\,'.join(l) + + @classmethod + def _merge_flags(cls, flags: T.List[str]) -> T.List[str]: + r""" + The flags to NVCC gets exceedingly verbose and unreadable when too many of them + are shielded with -Xcompiler. Merge consecutive -Xcompiler-wrapped arguments + into one. + """ + if len(flags) <= 1: + return flags + flagit = iter(flags) + xflags = [] + + def is_xcompiler_flag_isolated(flag: str) -> bool: + return flag == '-Xcompiler' + + def is_xcompiler_flag_glued(flag: str) -> bool: + return flag.startswith('-Xcompiler=') + + def is_xcompiler_flag(flag: str) -> bool: + return is_xcompiler_flag_isolated(flag) or is_xcompiler_flag_glued(flag) + + def get_xcompiler_val(flag: str, flagit: T.Iterator[str]) -> str: + if is_xcompiler_flag_glued(flag): + return flag[len('-Xcompiler='):] + else: + try: + return next(flagit) + except StopIteration: + return "" + + ingroup = False + for flag in flagit: + if not is_xcompiler_flag(flag): + ingroup = False + xflags.append(flag) + elif ingroup: + xflags[-1] += ',' + xflags[-1] += get_xcompiler_val(flag, flagit) + elif is_xcompiler_flag_isolated(flag): + ingroup = True + xflags.append(flag) + xflags.append(get_xcompiler_val(flag, flagit)) + elif is_xcompiler_flag_glued(flag): + ingroup = True + xflags.append(flag) + else: + raise ValueError("-Xcompiler flag merging failed, unknown argument form!") + return xflags + + def _to_host_flags(self, flags: T.List[str], phase: _Phase = _Phase.COMPILER) -> T.List[str]: + """ + Translate generic "GCC-speak" plus particular "NVCC-speak" flags to NVCC flags. + + NVCC's "short" flags have broad similarities to the GCC standard, but have + gratuitous, irritating differences. + """ + + xflags = [] + flagit = iter(flags) + + for flag in flagit: + # The CUDA Toolkit Documentation, in 4.1. Command Option Types and Notation, + # specifies that NVCC does not parse the standard flags as GCC does. It has + # its own strategy, to wit: + # + # nvcc recognizes three types of command options: boolean options, single + # value options, and list options. + # + # Boolean options do not have an argument; they are either specified on a + # command line or not. Single value options must be specified at most once, + # and list options may be repeated. Examples of each of these option types + # are, respectively: --verbose (switch to verbose mode), --output-file + # (specify output file), and --include-path (specify include path). + # + # Single value options and list options must have arguments, which must + # follow the name of the option itself by either one of more spaces or an + # equals character. When a one-character short name such as -I, -l, and -L + # is used, the value of the option may also immediately follow the option + # itself without being separated by spaces or an equal character. The + # individual values of list options may be separated by commas in a single + # instance of the option, or the option may be repeated, or any + # combination of these two cases. + # + # One strange consequence of this choice is that directory and filenames that + # contain commas (',') cannot be passed to NVCC (at least, not as easily as + # in GCC). Another strange consequence is that it is legal to supply flags + # such as + # + # -lpthread,rt,dl,util + # -l pthread,rt,dl,util + # -l=pthread,rt,dl,util + # + # and each of the above alternatives is equivalent to GCC-speak + # + # -lpthread -lrt -ldl -lutil + # -l pthread -l rt -l dl -l util + # -l=pthread -l=rt -l=dl -l=util + # + # *With the exception of commas in the name*, GCC-speak for these list flags + # is a strict subset of NVCC-speak, so we passthrough those flags. + # + # The -D macro-define flag is documented as somehow shielding commas from + # splitting a definition. Balanced parentheses, braces and single-quotes + # around the comma are not sufficient, but balanced double-quotes are. The + # shielding appears to work with -l, -I, -L flags as well, for instance. + # + # Since our goal is to replicate GCC-speak as much as possible, we check for + # commas in all list-arguments and shield them with double-quotes. We make + # an exception for -D (where this would be value-changing) and -U (because + # it isn't possible to define a macro with a comma in the name). + + if flag in self._FLAG_PASSTHRU_NOARGS: + xflags.append(flag) + continue + + # Handle breakup of flag-values into a flag-part and value-part. + if flag[:1] not in '-/': + # This is not a flag. It's probably a file input. Pass it through. + xflags.append(flag) + continue + elif flag[:1] == '/': + # This is ambiguously either an MVSC-style /switch or an absolute path + # to a file. For some magical reason the following works acceptably in + # both cases. + wrap = '"' if ',' in flag else '' + xflags.append(f'-X{phase.value}={wrap}{flag}{wrap}') + continue + elif len(flag) >= 2 and flag[0] == '-' and flag[1] in 'IDULlmOxmte': + # This is a single-letter short option. These options (with the + # exception of -o) are allowed to receive their argument with neither + # space nor = sign before them. Detect and separate them in that event. + if flag[2:3] == '': # -I something + try: + val = next(flagit) + except StopIteration: + pass + elif flag[2:3] == '=': # -I=something + val = flag[3:] + else: # -Isomething + val = flag[2:] + flag = flag[:2] # -I + elif flag in self._FLAG_LONG2SHORT_WITHARGS or \ + flag in self._FLAG_SHORT2LONG_WITHARGS: + # This is either -o or a multi-letter flag, and it is receiving its + # value isolated. + try: + val = next(flagit) # -o something + except StopIteration: + pass + elif flag.split('=', 1)[0] in self._FLAG_LONG2SHORT_WITHARGS or \ + flag.split('=', 1)[0] in self._FLAG_SHORT2LONG_WITHARGS: + # This is either -o or a multi-letter flag, and it is receiving its + # value after an = sign. + flag, val = flag.split('=', 1) # -o=something + # Some dependencies (e.g., BoostDependency) add unspaced "-isystem/usr/include" arguments + elif flag.startswith('-isystem'): + val = flag[8:].strip() + flag = flag[:8] + else: + # This is a flag, and it's foreign to NVCC. + # + # We do not know whether this GCC-speak flag takes an isolated + # argument. Assuming it does not (the vast majority indeed don't), + # wrap this argument in an -Xcompiler flag and send it down to NVCC. + if flag == '-ffast-math': + xflags.append('-use_fast_math') + xflags.append('-Xcompiler='+flag) + elif flag == '-fno-fast-math': + xflags.append('-ftz=false') + xflags.append('-prec-div=true') + xflags.append('-prec-sqrt=true') + xflags.append('-Xcompiler='+flag) + elif flag == '-freciprocal-math': + xflags.append('-prec-div=false') + xflags.append('-Xcompiler='+flag) + elif flag == '-fno-reciprocal-math': + xflags.append('-prec-div=true') + xflags.append('-Xcompiler='+flag) + else: + xflags.append('-Xcompiler='+self._shield_nvcc_list_arg(flag)) + # The above should securely handle GCC's -Wl, -Wa, -Wp, arguments. + continue + + assert val is not None # Should only trip if there is a missing argument. + + # Take care of the various NVCC-supported flags that need special handling. + flag = self._FLAG_LONG2SHORT_WITHARGS.get(flag, flag) + + if flag in {'-include', '-isystem', '-I', '-L', '-l'}: + # These flags are known to GCC, but list-valued in NVCC. They potentially + # require double-quoting to prevent NVCC interpreting the flags as lists + # when GCC would not have done so. + # + # We avoid doing this quoting for -D to avoid redefining macros and for + # -U because it isn't possible to define a macro with a comma in the name. + # -U with comma arguments is impossible in GCC-speak (and thus unambiguous + #in NVCC-speak, albeit unportable). + if len(flag) == 2: + xflags.append(flag+self._shield_nvcc_list_arg(val)) + elif flag == '-isystem' and val in self.host_compiler.get_default_include_dirs(): + # like GnuLikeCompiler, we have to filter out include directories specified + # with -isystem that overlap with the host compiler's search path + pass + else: + xflags.append(flag) + xflags.append(self._shield_nvcc_list_arg(val)) + elif flag == '-O': + # Handle optimization levels GCC knows about that NVCC does not. + if val == 'fast': + xflags.append('-O3') + xflags.append('-use_fast_math') + xflags.append('-Xcompiler') + xflags.append(flag+val) + elif val in {'s', 'g', 'z'}: + xflags.append('-Xcompiler') + xflags.append(flag+val) + else: + xflags.append(flag+val) + elif flag in {'-D', '-U', '-m', '-t'}: + xflags.append(flag+val) # For style, keep glued. + elif flag in {'-std'}: + xflags.append(flag+'='+val) # For style, keep glued. + else: + xflags.append(flag) + xflags.append(val) + + return self._merge_flags(xflags) + + def needs_static_linker(self) -> bool: + return False + + def thread_link_flags(self, environment: 'Environment') -> T.List[str]: + return self._to_host_flags(self.host_compiler.thread_link_flags(environment), _Phase.LINKER) + + def sanity_check(self, work_dir: str, env: 'Environment') -> None: + mlog.debug('Sanity testing ' + self.get_display_language() + ' compiler:', ' '.join(self.exelist)) + mlog.debug('Is cross compiler: %s.' % str(self.is_cross)) + + sname = 'sanitycheckcuda.cu' + code = r''' + #include <cuda_runtime.h> + #include <stdio.h> + + __global__ void kernel (void) {} + + int main(void){ + struct cudaDeviceProp prop; + int count, i; + cudaError_t ret = cudaGetDeviceCount(&count); + if(ret != cudaSuccess){ + fprintf(stderr, "%d\n", (int)ret); + }else{ + for(i=0;i<count;i++){ + if(cudaGetDeviceProperties(&prop, i) == cudaSuccess){ + fprintf(stdout, "%d.%d\n", prop.major, prop.minor); + } + } + } + fflush(stderr); + fflush(stdout); + return 0; + } + ''' + binname = sname.rsplit('.', 1)[0] + binname += '_cross' if self.is_cross else '' + source_name = os.path.join(work_dir, sname) + binary_name = os.path.join(work_dir, binname + '.exe') + with open(source_name, 'w', encoding='utf-8') as ofile: + ofile.write(code) + + # The Sanity Test for CUDA language will serve as both a sanity test + # and a native-build GPU architecture detection test, useful later. + # + # For this second purpose, NVCC has very handy flags, --run and + # --run-args, that allow one to run an application with the + # environment set up properly. Of course, this only works for native + # builds; For cross builds we must still use the exe_wrapper (if any). + self.detected_cc = '' + flags = [] + + # Disable warnings, compile with statically-linked runtime for minimum + # reliance on the system. + flags += ['-w', '-cudart', 'static', source_name] + + # Use the -ccbin option, if available, even during sanity checking. + # Otherwise, on systems where CUDA does not support the default compiler, + # NVCC becomes unusable. + flags += self.get_ccbin_args(env.coredata.options) + + # If cross-compiling, we can't run the sanity check, only compile it. + if self.is_cross and self.exe_wrapper is None: + # Linking cross built apps is painful. You can't really + # tell if you should use -nostdlib or not and for example + # on OSX the compiler binary is the same but you need + # a ton of compiler flags to differentiate between + # arm and x86_64. So just compile. + flags += self.get_compile_only_args() + flags += self.get_output_args(binary_name) + + # Compile sanity check + cmdlist = self.exelist + flags + mlog.debug('Sanity check compiler command line: ', ' '.join(cmdlist)) + pc, stdo, stde = Popen_safe(cmdlist, cwd=work_dir) + mlog.debug('Sanity check compile stdout: ') + mlog.debug(stdo) + mlog.debug('-----\nSanity check compile stderr:') + mlog.debug(stde) + mlog.debug('-----') + if pc.returncode != 0: + raise EnvironmentException(f'Compiler {self.name_string()} can not compile programs.') + + # Run sanity check (if possible) + if self.is_cross: + if self.exe_wrapper is None: + return + else: + cmdlist = self.exe_wrapper.get_command() + [binary_name] + else: + cmdlist = self.exelist + ['--run', '"' + binary_name + '"'] + mlog.debug('Sanity check run command line: ', ' '.join(cmdlist)) + pe, stdo, stde = Popen_safe(cmdlist, cwd=work_dir) + mlog.debug('Sanity check run stdout: ') + mlog.debug(stdo) + mlog.debug('-----\nSanity check run stderr:') + mlog.debug(stde) + mlog.debug('-----') + pe.wait() + if pe.returncode != 0: + raise EnvironmentException(f'Executables created by {self.language} compiler {self.name_string()} are not runnable.') + + # Interpret the result of the sanity test. + # As mentioned above, it is not only a sanity test but also a GPU + # architecture detection test. + if stde == '': + self.detected_cc = stdo + else: + mlog.debug('cudaGetDeviceCount() returned ' + stde) + + def has_header_symbol(self, hname: str, symbol: str, prefix: str, + env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + if extra_args is None: + extra_args = [] + fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol} + # Check if it's a C-like symbol + t = '''{prefix} + #include <{header}> + int main(void) {{ + /* If it's not defined as a macro, try to use as a symbol */ + #ifndef {symbol} + {symbol}; + #endif + return 0; + }}''' + found, cached = self.compiles(t.format_map(fargs), env, extra_args=extra_args, dependencies=dependencies) + if found: + return True, cached + # Check if it's a class or a template + t = '''{prefix} + #include <{header}> + using {symbol}; + int main(void) {{ + return 0; + }}''' + return self.compiles(t.format_map(fargs), env, extra_args=extra_args, dependencies=dependencies) + + _CPP14_VERSION = '>=9.0' + _CPP17_VERSION = '>=11.0' + _CPP20_VERSION = '>=12.0' + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = super().get_options() + std_key = OptionKey('std', machine=self.for_machine, lang=self.language) + ccbindir_key = OptionKey('ccbindir', machine=self.for_machine, lang=self.language) + + cpp_stds = ['none', 'c++03', 'c++11'] + if version_compare(self.version, self._CPP14_VERSION): + cpp_stds += ['c++14'] + if version_compare(self.version, self._CPP17_VERSION): + cpp_stds += ['c++17'] + if version_compare(self.version, self._CPP20_VERSION): + cpp_stds += ['c++20'] + + opts.update({ + std_key: coredata.UserComboOption('C++ language standard to use with CUDA', + cpp_stds, 'none'), + ccbindir_key: coredata.UserStringOption('CUDA non-default toolchain directory to use (-ccbin)', + ''), + }) + return opts + + def _to_host_compiler_options(self, options: 'KeyedOptionDictType') -> 'KeyedOptionDictType': + """ + Convert an NVCC Option set to a host compiler's option set. + """ + + # We must strip the -std option from the host compiler option set, as NVCC has + # its own -std flag that may not agree with the host compiler's. + host_options = {key: options.get(key, opt) for key, opt in self.host_compiler.get_options().items()} + std_key = OptionKey('std', machine=self.for_machine, lang=self.host_compiler.language) + overrides = {std_key: 'none'} + return OptionOverrideProxy(overrides, host_options) + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = self.get_ccbin_args(options) + # On Windows, the version of the C++ standard used by nvcc is dictated by + # the combination of CUDA version and MSVC version; the --std= is thus ignored + # and attempting to use it will result in a warning: https://stackoverflow.com/a/51272091/741027 + if not is_windows(): + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value != 'none': + args.append('--std=' + std.value) + + return args + self._to_host_flags(self.host_compiler.get_option_compile_args(self._to_host_compiler_options(options))) + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = self.get_ccbin_args(options) + return args + self._to_host_flags(self.host_compiler.get_option_link_args(self._to_host_compiler_options(options)), _Phase.LINKER) + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, + darwin_versions: T.Tuple[str, str]) -> T.List[str]: + return self._to_host_flags(self.host_compiler.get_soname_args( + env, prefix, shlib_name, suffix, soversion, darwin_versions), _Phase.LINKER) + + def get_compile_only_args(self) -> T.List[str]: + return ['-c'] + + def get_no_optimization_args(self) -> T.List[str]: + return ['-O0'] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + # alternatively, consider simply redirecting this to the host compiler, which would + # give us more control over options like "optimize for space" (which nvcc doesn't support): + # return self._to_host_flags(self.host_compiler.get_optimization_args(optimization_level)) + return cuda_optimization_args[optimization_level] + + def sanitizer_compile_args(self, value: str) -> T.List[str]: + return self._to_host_flags(self.host_compiler.sanitizer_compile_args(value)) + + def sanitizer_link_args(self, value: str) -> T.List[str]: + return self._to_host_flags(self.host_compiler.sanitizer_link_args(value)) + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return cuda_debug_args[is_debug] + + def get_werror_args(self) -> T.List[str]: + return ['-Werror=cross-execution-space-call,deprecated-declarations,reorder'] + + def get_warn_args(self, level: str) -> T.List[str]: + return self.warn_args[level] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + # nvcc doesn't support msvc's "Edit and Continue" PDB format; "downgrade" to + # a regular PDB to avoid cl's warning to that effect (D9025 : overriding '/ZI' with '/Zi') + host_args = ['/Zi' if arg == '/ZI' else arg for arg in self.host_compiler.get_buildtype_args(buildtype)] + return cuda_buildtype_args[buildtype] + self._to_host_flags(host_args) + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + if path == '': + path = '.' + return ['-isystem=' + path] if is_system else ['-I' + path] + + def get_compile_debugfile_args(self, rel_obj: str, pch: bool = False) -> T.List[str]: + return self._to_host_flags(self.host_compiler.get_compile_debugfile_args(rel_obj, pch)) + + def get_link_debugfile_args(self, targetfile: str) -> T.List[str]: + return self._to_host_flags(self.host_compiler.get_link_debugfile_args(targetfile), _Phase.LINKER) + + def get_depfile_suffix(self) -> str: + return 'd' + + def get_buildtype_linker_args(self, buildtype: str) -> T.List[str]: + return self._to_host_flags(self.host_compiler.get_buildtype_linker_args(buildtype), _Phase.LINKER) + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str, + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + (rpath_args, rpath_dirs_to_remove) = self.host_compiler.build_rpath_args( + env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + return (self._to_host_flags(rpath_args, _Phase.LINKER), rpath_dirs_to_remove) + + def linker_to_compiler_args(self, args: T.List[str]) -> T.List[str]: + return args + + def get_pic_args(self) -> T.List[str]: + return self._to_host_flags(self.host_compiler.get_pic_args()) + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + return [] + + def get_output_args(self, target: str) -> T.List[str]: + return ['-o', target] + + def get_std_exe_link_args(self) -> T.List[str]: + return self._to_host_flags(self.host_compiler.get_std_exe_link_args(), _Phase.LINKER) + + def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], + libtype: LibType = LibType.PREFER_SHARED) -> T.Optional[T.List[str]]: + return ['-l' + libname] # FIXME + + def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: + return self._to_host_flags(self.host_compiler.get_crt_compile_args(crt_val, buildtype)) + + def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]: + # nvcc defaults to static, release version of msvc runtime and provides no + # native option to override it; override it with /NODEFAULTLIB + host_link_arg_overrides = [] + host_crt_compile_args = self.host_compiler.get_crt_compile_args(crt_val, buildtype) + if any(arg in {'/MDd', '/MD', '/MTd'} for arg in host_crt_compile_args): + host_link_arg_overrides += ['/NODEFAULTLIB:LIBCMT.lib'] + return self._to_host_flags(host_link_arg_overrides + self.host_compiler.get_crt_link_args(crt_val, buildtype), _Phase.LINKER) + + def get_target_link_args(self, target: 'BuildTarget') -> T.List[str]: + return self._to_host_flags(super().get_target_link_args(target), _Phase.LINKER) + + def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]: + return self._to_host_flags(super().get_dependency_compile_args(dep)) + + def get_dependency_link_args(self, dep: 'Dependency') -> T.List[str]: + return self._to_host_flags(super().get_dependency_link_args(dep), _Phase.LINKER) + + def get_ccbin_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + key = OptionKey('ccbindir', machine=self.for_machine, lang=self.language) + ccbindir = options[key].value + if isinstance(ccbindir, str) and ccbindir != '': + return [self._shield_nvcc_list_arg('-ccbin='+ccbindir, False)] + else: + return [] + + def get_profile_generate_args(self) -> T.List[str]: + return ['-Xcompiler=' + x for x in self.host_compiler.get_profile_generate_args()] + + def get_profile_use_args(self) -> T.List[str]: + return ['-Xcompiler=' + x for x in self.host_compiler.get_profile_use_args()] + + def get_disable_assert_args(self) -> T.List[str]: + return self.host_compiler.get_disable_assert_args() diff --git a/mesonbuild/compilers/cython.py b/mesonbuild/compilers/cython.py new file mode 100644 index 0000000..9bbfebe --- /dev/null +++ b/mesonbuild/compilers/cython.py @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright © 2021 Intel Corporation +from __future__ import annotations + +"""Abstraction for Cython language compilers.""" + +import typing as T + +from .. import coredata +from ..mesonlib import EnvironmentException, OptionKey, version_compare +from .compilers import Compiler + +if T.TYPE_CHECKING: + from ..coredata import MutableKeyedOptionDictType, KeyedOptionDictType + from ..environment import Environment + + +class CythonCompiler(Compiler): + + """Cython Compiler.""" + + language = 'cython' + id = 'cython' + + def needs_static_linker(self) -> bool: + # We transpile into C, so we don't need any linker + return False + + def get_always_args(self) -> T.List[str]: + return ['--fast-fail'] + + def get_werror_args(self) -> T.List[str]: + return ['-Werror'] + + def get_output_args(self, outputname: str) -> T.List[str]: + return ['-o', outputname] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + # Cython doesn't have optimization levels itself, the underlying + # compiler might though + return [] + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + if version_compare(self.version, '>=0.29.33'): + return ['-M'] + return [] + + def get_depfile_suffix(self) -> str: + return 'dep' + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + code = 'print("hello world")' + with self.cached_compile(code, environment.coredata) as p: + if p.returncode != 0: + raise EnvironmentException(f'Cython compiler {self.id!r} cannot compile programs') + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + # Cython doesn't implement this, but Meson requires an implementation + return [] + + def get_pic_args(self) -> T.List[str]: + # We can lie here, it's fine + return [] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + new: T.List[str] = [] + for i in parameter_list: + new.append(i) + + return new + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = super().get_options() + opts.update({ + OptionKey('version', machine=self.for_machine, lang=self.language): coredata.UserComboOption( + 'Python version to target', + ['2', '3'], + '3', + ), + OptionKey('language', machine=self.for_machine, lang=self.language): coredata.UserComboOption( + 'Output C or C++ files', + ['c', 'cpp'], + 'c', + ) + }) + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args: T.List[str] = [] + key = options[OptionKey('version', machine=self.for_machine, lang=self.language)] + args.append(f'-{key.value}') + lang = options[OptionKey('language', machine=self.for_machine, lang=self.language)] + if lang.value == 'cpp': + args.append('--cplus') + return args diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py new file mode 100644 index 0000000..90c0498 --- /dev/null +++ b/mesonbuild/compilers/d.py @@ -0,0 +1,1022 @@ +# Copyright 2012-2022 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 __future__ import annotations + +import os.path +import re +import subprocess +import typing as T + +from .. import mesonlib +from .. import mlog +from ..arglist import CompilerArgs +from ..linkers import RSPFileSyntax +from ..mesonlib import ( + EnvironmentException, version_compare, OptionKey, is_windows +) + +from . import compilers +from .compilers import ( + d_dmd_buildtype_args, + d_gdc_buildtype_args, + d_ldc_buildtype_args, + clike_debug_args, + Compiler, + CompileCheckMode, +) +from .mixins.gnu import GnuCompiler +from .mixins.gnu import gnu_common_warning_args + +if T.TYPE_CHECKING: + from ..dependencies import Dependency + from ..programs import ExternalProgram + from ..envconfig import MachineInfo + from ..environment import Environment + from ..linkers import DynamicLinker + from ..mesonlib import MachineChoice + + CompilerMixinBase = Compiler +else: + CompilerMixinBase = object + +d_feature_args = {'gcc': {'unittest': '-funittest', + 'debug': '-fdebug', + 'version': '-fversion', + 'import_dir': '-J' + }, + 'llvm': {'unittest': '-unittest', + 'debug': '-d-debug', + 'version': '-d-version', + 'import_dir': '-J' + }, + 'dmd': {'unittest': '-unittest', + 'debug': '-debug', + 'version': '-version', + 'import_dir': '-J' + } + } # type: T.Dict[str, T.Dict[str, str]] + +ldc_optimization_args = {'plain': [], + '0': [], + 'g': [], + '1': ['-O1'], + '2': ['-O2'], + '3': ['-O3'], + 's': ['-Oz'], + } # type: T.Dict[str, T.List[str]] + +dmd_optimization_args = {'plain': [], + '0': [], + 'g': [], + '1': ['-O'], + '2': ['-O'], + '3': ['-O'], + 's': ['-O'], + } # type: T.Dict[str, T.List[str]] + + +class DmdLikeCompilerMixin(CompilerMixinBase): + + """Mixin class for DMD and LDC. + + LDC has a number of DMD like arguments, and this class allows for code + sharing between them as makes sense. + """ + + def __init__(self, dmd_frontend_version: T.Optional[str]): + if dmd_frontend_version is None: + self._dmd_has_depfile = False + else: + # -makedeps switch introduced in 2.095 frontend + self._dmd_has_depfile = version_compare(dmd_frontend_version, ">=2.095.0") + + if T.TYPE_CHECKING: + mscrt_args = {} # type: T.Dict[str, T.List[str]] + + def _get_target_arch_args(self) -> T.List[str]: ... + + LINKER_PREFIX = '-L=' + + def get_output_args(self, outputname: str) -> T.List[str]: + return ['-of=' + outputname] + + def get_linker_output_args(self, outputname: str) -> T.List[str]: + return ['-of=' + outputname] + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + if path == "": + path = "." + return ['-I=' + path] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:3] == '-I=': + parameter_list[idx] = i[:3] + os.path.normpath(os.path.join(build_dir, i[3:])) + if i[:4] == '-L-L': + parameter_list[idx] = i[:4] + os.path.normpath(os.path.join(build_dir, i[4:])) + if i[:5] == '-L=-L': + parameter_list[idx] = i[:5] + os.path.normpath(os.path.join(build_dir, i[5:])) + if i[:6] == '-Wl,-L': + parameter_list[idx] = i[:6] + os.path.normpath(os.path.join(build_dir, i[6:])) + + return parameter_list + + def get_warn_args(self, level: str) -> T.List[str]: + return ['-wi'] + + def get_werror_args(self) -> T.List[str]: + return ['-w'] + + def get_coverage_args(self) -> T.List[str]: + return ['-cov'] + + def get_coverage_link_args(self) -> T.List[str]: + return [] + + def get_preprocess_only_args(self) -> T.List[str]: + return ['-E'] + + def get_compile_only_args(self) -> T.List[str]: + return ['-c'] + + def get_depfile_suffix(self) -> str: + return 'deps' + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + if self._dmd_has_depfile: + return [f'-makedeps={outfile}'] + return [] + + def get_pic_args(self) -> T.List[str]: + if self.info.is_windows(): + return [] + return ['-fPIC'] + + def get_feature_args(self, kwargs: T.Dict[str, T.Any], build_to_src: str) -> T.List[str]: + # TODO: using a TypeDict here would improve this + res = [] + # get_feature_args can be called multiple times for the same target when there is generated source + # so we have to copy the kwargs (target.d_features) dict before popping from it + kwargs = kwargs.copy() + if 'unittest' in kwargs: + unittest = kwargs.pop('unittest') + unittest_arg = d_feature_args[self.id]['unittest'] + if not unittest_arg: + raise EnvironmentException('D compiler %s does not support the "unittest" feature.' % self.name_string()) + if unittest: + res.append(unittest_arg) + + if 'debug' in kwargs: + debug_level = -1 + debugs = kwargs.pop('debug') + if not isinstance(debugs, list): + debugs = [debugs] + + debug_arg = d_feature_args[self.id]['debug'] + if not debug_arg: + raise EnvironmentException('D compiler %s does not support conditional debug identifiers.' % self.name_string()) + + # Parse all debug identifiers and the largest debug level identifier + for d in debugs: + if isinstance(d, int): + if d > debug_level: + debug_level = d + elif isinstance(d, str) and d.isdigit(): + if int(d) > debug_level: + debug_level = int(d) + else: + res.append(f'{debug_arg}={d}') + + if debug_level >= 0: + res.append(f'{debug_arg}={debug_level}') + + if 'versions' in kwargs: + version_level = -1 + versions = kwargs.pop('versions') + if not isinstance(versions, list): + versions = [versions] + + version_arg = d_feature_args[self.id]['version'] + if not version_arg: + raise EnvironmentException('D compiler %s does not support conditional version identifiers.' % self.name_string()) + + # Parse all version identifiers and the largest version level identifier + for v in versions: + if isinstance(v, int): + if v > version_level: + version_level = v + elif isinstance(v, str) and v.isdigit(): + if int(v) > version_level: + version_level = int(v) + else: + res.append(f'{version_arg}={v}') + + if version_level >= 0: + res.append(f'{version_arg}={version_level}') + + if 'import_dirs' in kwargs: + import_dirs = kwargs.pop('import_dirs') + if not isinstance(import_dirs, list): + import_dirs = [import_dirs] + + import_dir_arg = d_feature_args[self.id]['import_dir'] + if not import_dir_arg: + raise EnvironmentException('D compiler %s does not support the "string import directories" feature.' % self.name_string()) + for idir_obj in import_dirs: + basedir = idir_obj.get_curdir() + for idir in idir_obj.get_incdirs(): + bldtreedir = os.path.join(basedir, idir) + # Avoid superfluous '/.' at the end of paths when d is '.' + if idir not in ('', '.'): + expdir = bldtreedir + else: + expdir = basedir + srctreedir = os.path.join(build_to_src, expdir) + res.append(f'{import_dir_arg}{srctreedir}') + res.append(f'{import_dir_arg}{bldtreedir}') + + if kwargs: + raise EnvironmentException('Unknown D compiler feature(s) selected: %s' % ', '.join(kwargs.keys())) + + return res + + def get_buildtype_linker_args(self, buildtype: str) -> T.List[str]: + if buildtype != 'plain': + return self._get_target_arch_args() + return [] + + def gen_import_library_args(self, implibname: str) -> T.List[str]: + return self.linker.import_library_args(implibname) + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str, + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + if self.info.is_windows(): + return ([], set()) + + # GNU ld, solaris ld, and lld acting like GNU ld + if self.linker.id.startswith('ld'): + # The way that dmd and ldc pass rpath to gcc is different than we would + # do directly, each argument -rpath and the value to rpath, need to be + # split into two separate arguments both prefaced with the -L=. + args = [] + (rpath_args, rpath_dirs_to_remove) = super().build_rpath_args( + env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + for r in rpath_args: + if ',' in r: + a, b = r.split(',', maxsplit=1) + args.append(a) + args.append(self.LINKER_PREFIX + b) + else: + args.append(r) + return (args, rpath_dirs_to_remove) + + return super().build_rpath_args( + env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + + @classmethod + def _translate_args_to_nongnu(cls, args: T.List[str], info: MachineInfo, link_id: str) -> T.List[str]: + # Translate common arguments to flags the LDC/DMD compilers + # can understand. + # The flags might have been added by pkg-config files, + # and are therefore out of the user's control. + dcargs = [] + # whether we hit a linker argument that expect another arg + # see the comment in the "-L" section + link_expect_arg = False + link_flags_with_arg = [ + '-rpath', '-rpath-link', '-soname', '-compatibility_version', '-current_version', + ] + for arg in args: + # Translate OS specific arguments first. + osargs = [] # type: T.List[str] + if info.is_windows(): + osargs = cls.translate_arg_to_windows(arg) + elif info.is_darwin(): + osargs = cls._translate_arg_to_osx(arg) + if osargs: + dcargs.extend(osargs) + continue + + # Translate common D arguments here. + if arg == '-pthread': + continue + if arg.startswith('-fstack-protector'): + continue + if arg.startswith('-D') and not (arg == '-D' or arg.startswith(('-Dd', '-Df'))): + # ignore all '-D*' flags (like '-D_THREAD_SAFE') + # unless they are related to documentation + continue + if arg.startswith('-Wl,'): + # Translate linker arguments here. + linkargs = arg[arg.index(',') + 1:].split(',') + for la in linkargs: + dcargs.append('-L=' + la.strip()) + continue + elif arg.startswith(('-link-defaultlib', '-linker', '-link-internally', '-linkonce-templates', '-lib')): + # these are special arguments to the LDC linker call, + # arguments like "-link-defaultlib-shared" do *not* + # denote a library to be linked, but change the default + # Phobos/DRuntime linking behavior, while "-linker" sets the + # default linker. + dcargs.append(arg) + continue + elif arg.startswith('-l'): + # translate library link flag + dcargs.append('-L=' + arg) + continue + elif arg.startswith('-isystem'): + # translate -isystem system include path + # this flag might sometimes be added by C library Cflags via + # pkg-config. + # NOTE: -isystem and -I are not 100% equivalent, so this is just + # a workaround for the most common cases. + if arg.startswith('-isystem='): + dcargs.append('-I=' + arg[9:]) + else: + dcargs.append('-I' + arg[8:]) + continue + elif arg.startswith('-idirafter'): + # same as -isystem, but appends the path instead + if arg.startswith('-idirafter='): + dcargs.append('-I=' + arg[11:]) + else: + dcargs.append('-I' + arg[10:]) + continue + elif arg.startswith('-L'): + # The D linker expect library search paths in the form of -L=-L/path (the '=' is optional). + # + # This function receives a mix of arguments already prepended + # with -L for the D linker driver and other linker arguments. + # The arguments starting with -L can be: + # - library search path (with or without a second -L) + # - it can come from pkg-config (a single -L) + # - or from the user passing linker flags (-L-L would be expected) + # - arguments like "-L=-rpath" that expect a second argument (also prepended with -L) + # - arguments like "-L=@rpath/xxx" without a second argument (on Apple platform) + # - arguments like "-L=/SUBSYSTEM:CONSOLE (for Windows linker) + # + # The logic that follows tries to detect all these cases (some may be missing) + # in order to prepend a -L only for the library search paths with a single -L + + if arg.startswith('-L='): + suffix = arg[3:] + else: + suffix = arg[2:] + + if link_expect_arg: + # flags like rpath and soname expect a path or filename respectively, + # we must not alter it (i.e. prefixing with -L for a lib search path) + dcargs.append(arg) + link_expect_arg = False + continue + + if suffix in link_flags_with_arg: + link_expect_arg = True + + if suffix.startswith('-') or suffix.startswith('@'): + # this is not search path + dcargs.append(arg) + continue + + # linker flag such as -L=/DEBUG must pass through + if info.is_windows() and link_id == 'link' and suffix.startswith('/'): + dcargs.append(arg) + continue + + # Make sure static library files are passed properly to the linker. + if arg.endswith('.a') or arg.endswith('.lib'): + if len(suffix) > 0 and not suffix.startswith('-'): + dcargs.append('-L=' + suffix) + continue + + dcargs.append('-L=' + arg) + continue + elif not arg.startswith('-') and arg.endswith(('.a', '.lib')): + # ensure static libraries are passed through to the linker + dcargs.append('-L=' + arg) + continue + else: + dcargs.append(arg) + + return dcargs + + @classmethod + def translate_arg_to_windows(cls, arg: str) -> T.List[str]: + args = [] + if arg.startswith('-Wl,'): + # Translate linker arguments here. + linkargs = arg[arg.index(',') + 1:].split(',') + for la in linkargs: + if la.startswith('--out-implib='): + # Import library name + args.append('-L=/IMPLIB:' + la[13:].strip()) + elif arg.startswith('-mscrtlib='): + args.append(arg) + mscrtlib = arg[10:].lower() + if cls is LLVMDCompiler: + # Default crt libraries for LDC2 must be excluded for other + # selected crt options. + if mscrtlib != 'libcmt': + args.append('-L=/NODEFAULTLIB:libcmt') + args.append('-L=/NODEFAULTLIB:libvcruntime') + + # Fixes missing definitions for printf-functions in VS2017 + if mscrtlib.startswith('msvcrt'): + args.append('-L=/DEFAULTLIB:legacy_stdio_definitions.lib') + + return args + + @classmethod + def _translate_arg_to_osx(cls, arg: str) -> T.List[str]: + args = [] + if arg.startswith('-install_name'): + args.append('-L=' + arg) + return args + + @classmethod + def _unix_args_to_native(cls, args: T.List[str], info: MachineInfo, link_id: str = '') -> T.List[str]: + return cls._translate_args_to_nongnu(args, info, link_id) + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + ddebug_args = [] + if is_debug: + ddebug_args = [d_feature_args[self.id]['debug']] + + return clike_debug_args[is_debug] + ddebug_args + + def _get_crt_args(self, crt_val: str, buildtype: str) -> T.List[str]: + if not self.info.is_windows(): + return [] + + if crt_val in self.mscrt_args: + return self.mscrt_args[crt_val] + assert crt_val in {'from_buildtype', 'static_from_buildtype'} + + dbg = 'mdd' + rel = 'md' + if crt_val == 'static_from_buildtype': + dbg = 'mtd' + rel = 'mt' + + # Match what build type flags used to do. + if buildtype == 'plain': + return [] + elif buildtype == 'debug': + return self.mscrt_args[dbg] + elif buildtype == 'debugoptimized': + return self.mscrt_args[rel] + elif buildtype == 'release': + return self.mscrt_args[rel] + elif buildtype == 'minsize': + return self.mscrt_args[rel] + else: + assert buildtype == 'custom' + raise EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".') + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, + darwin_versions: T.Tuple[str, str]) -> T.List[str]: + sargs = super().get_soname_args(env, prefix, shlib_name, suffix, + soversion, darwin_versions) + + # LDC and DMD actually do use a linker, but they proxy all of that with + # their own arguments + if self.linker.id.startswith('ld.'): + soargs = [] + for arg in sargs: + a, b = arg.split(',', maxsplit=1) + soargs.append(a) + soargs.append(self.LINKER_PREFIX + b) + return soargs + elif self.linker.id.startswith('ld64'): + soargs = [] + for arg in sargs: + if not arg.startswith(self.LINKER_PREFIX): + soargs.append(self.LINKER_PREFIX + arg) + else: + soargs.append(arg) + return soargs + else: + return sargs + + def get_allow_undefined_link_args(self) -> T.List[str]: + args = self.linker.get_allow_undefined_args() + if self.info.is_darwin(): + # On macOS we're passing these options to the C compiler, but + # they're linker options and need -Wl, so clang/gcc knows what to + # do with them. I'm assuming, but don't know for certain, that + # ldc/dmd do some kind of mapping internally for arguments they + # understand, but pass arguments they don't understand directly. + args = [a.replace('-L=', '-Xcc=-Wl,') for a in args] + return args + + +class DCompilerArgs(CompilerArgs): + prepend_prefixes = ('-I', '-L') + dedup2_prefixes = ('-I', ) + + +class DCompiler(Compiler): + mscrt_args = { + 'none': ['-mscrtlib='], + 'md': ['-mscrtlib=msvcrt'], + 'mdd': ['-mscrtlib=msvcrtd'], + 'mt': ['-mscrtlib=libcmt'], + 'mtd': ['-mscrtlib=libcmtd'], + } + + language = 'd' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + info: 'MachineInfo', arch: str, *, + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None, + is_cross: bool = False): + super().__init__([], exelist, version, for_machine, info, linker=linker, + full_version=full_version, is_cross=is_cross) + self.arch = arch + self.exe_wrapper = exe_wrapper + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + source_name = os.path.join(work_dir, 'sanity.d') + output_name = os.path.join(work_dir, 'dtest') + with open(source_name, 'w', encoding='utf-8') as ofile: + ofile.write('''void main() { }''') + pc = subprocess.Popen(self.exelist + self.get_output_args(output_name) + self._get_target_arch_args() + [source_name], cwd=work_dir) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('D compiler %s can not compile programs.' % self.name_string()) + if self.is_cross: + if self.exe_wrapper is None: + # Can't check if the binaries run so we have to assume they do + return + cmdlist = self.exe_wrapper.get_command() + [output_name] + else: + cmdlist = [output_name] + if subprocess.call(cmdlist) != 0: + raise EnvironmentException('Executables created by D compiler %s are not runnable.' % self.name_string()) + + def needs_static_linker(self) -> bool: + return True + + def get_depfile_suffix(self) -> str: + return 'deps' + + def get_pic_args(self) -> T.List[str]: + if self.info.is_windows(): + return [] + return ['-fPIC'] + + def get_feature_args(self, kwargs: T.Dict[str, T.Any], build_to_src: str) -> T.List[str]: + # TODO: using a TypeDict here would improve this + res = [] + # get_feature_args can be called multiple times for the same target when there is generated source + # so we have to copy the kwargs (target.d_features) dict before popping from it + kwargs = kwargs.copy() + if 'unittest' in kwargs: + unittest = kwargs.pop('unittest') + unittest_arg = d_feature_args[self.id]['unittest'] + if not unittest_arg: + raise EnvironmentException('D compiler %s does not support the "unittest" feature.' % self.name_string()) + if unittest: + res.append(unittest_arg) + + if 'debug' in kwargs: + debug_level = -1 + debugs = kwargs.pop('debug') + if not isinstance(debugs, list): + debugs = [debugs] + + debug_arg = d_feature_args[self.id]['debug'] + if not debug_arg: + raise EnvironmentException('D compiler %s does not support conditional debug identifiers.' % self.name_string()) + + # Parse all debug identifiers and the largest debug level identifier + for d in debugs: + if isinstance(d, int): + if d > debug_level: + debug_level = d + elif isinstance(d, str) and d.isdigit(): + if int(d) > debug_level: + debug_level = int(d) + else: + res.append(f'{debug_arg}={d}') + + if debug_level >= 0: + res.append(f'{debug_arg}={debug_level}') + + if 'versions' in kwargs: + version_level = -1 + versions = kwargs.pop('versions') + if not isinstance(versions, list): + versions = [versions] + + version_arg = d_feature_args[self.id]['version'] + if not version_arg: + raise EnvironmentException('D compiler %s does not support conditional version identifiers.' % self.name_string()) + + # Parse all version identifiers and the largest version level identifier + for v in versions: + if isinstance(v, int): + if v > version_level: + version_level = v + elif isinstance(v, str) and v.isdigit(): + if int(v) > version_level: + version_level = int(v) + else: + res.append(f'{version_arg}={v}') + + if version_level >= 0: + res.append(f'{version_arg}={version_level}') + + if 'import_dirs' in kwargs: + import_dirs = kwargs.pop('import_dirs') + if not isinstance(import_dirs, list): + import_dirs = [import_dirs] + + import_dir_arg = d_feature_args[self.id]['import_dir'] + if not import_dir_arg: + raise EnvironmentException('D compiler %s does not support the "string import directories" feature.' % self.name_string()) + for idir_obj in import_dirs: + basedir = idir_obj.get_curdir() + for idir in idir_obj.get_incdirs(): + bldtreedir = os.path.join(basedir, idir) + # Avoid superfluous '/.' at the end of paths when d is '.' + if idir not in ('', '.'): + expdir = bldtreedir + else: + expdir = basedir + srctreedir = os.path.join(build_to_src, expdir) + res.append(f'{import_dir_arg}{srctreedir}') + res.append(f'{import_dir_arg}{bldtreedir}') + + if kwargs: + raise EnvironmentException('Unknown D compiler feature(s) selected: %s' % ', '.join(kwargs.keys())) + + return res + + def get_buildtype_linker_args(self, buildtype: str) -> T.List[str]: + if buildtype != 'plain': + return self._get_target_arch_args() + return [] + + def compiler_args(self, args: T.Optional[T.Iterable[str]] = None) -> DCompilerArgs: + return DCompilerArgs(self, args) + + def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: + return self.compiles('int i;\n', env, extra_args=args) + + def _get_target_arch_args(self) -> T.List[str]: + # LDC2 on Windows targets to current OS architecture, but + # it should follow the target specified by the MSVC toolchain. + if self.info.is_windows(): + if self.arch == 'x86_64': + return ['-m64'] + return ['-m32'] + return [] + + def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: + return [] + + def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]: + return [] + + def _get_compile_extra_args(self, extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]], None] = None) -> T.List[str]: + args = self._get_target_arch_args() + if extra_args: + if callable(extra_args): + extra_args = extra_args(CompileCheckMode.COMPILE) + if isinstance(extra_args, list): + args.extend(extra_args) + elif isinstance(extra_args, str): + args.append(extra_args) + return args + + def run(self, code: 'mesonlib.FileOrString', env: 'Environment', *, + extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]], None] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> compilers.RunResult: + need_exe_wrapper = env.need_exe_wrapper(self.for_machine) + if need_exe_wrapper and self.exe_wrapper is None: + raise compilers.CrossNoRunException('Can not run test applications in this cross environment.') + extra_args = self._get_compile_extra_args(extra_args) + with self._build_wrapper(code, env, extra_args, dependencies, mode='link', want_output=True) as p: + if p.returncode != 0: + mlog.debug(f'Could not compile test file {p.input_name}: {p.returncode}\n') + return compilers.RunResult(False) + if need_exe_wrapper: + cmdlist = self.exe_wrapper.get_command() + [p.output_name] + else: + cmdlist = [p.output_name] + try: + pe, so, se = mesonlib.Popen_safe(cmdlist) + except Exception as e: + mlog.debug(f'Could not run: {cmdlist} (error: {e})\n') + return compilers.RunResult(False) + + mlog.debug('Program stdout:\n') + mlog.debug(so) + mlog.debug('Program stderr:\n') + mlog.debug(se) + return compilers.RunResult(True, pe.returncode, so, se) + + def sizeof(self, typename: str, prefix: str, env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: + if extra_args is None: + extra_args = [] + t = f''' + import std.stdio : writeln; + {prefix} + void main() {{ + writeln(({typename}).sizeof); + }} + ''' + res = self.run(t, env, extra_args=extra_args, + dependencies=dependencies) + if not res.compiled: + return -1 + if res.returncode != 0: + raise mesonlib.EnvironmentException('Could not run sizeof test binary.') + return int(res.stdout) + + def alignment(self, typename: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: + if extra_args is None: + extra_args = [] + t = f''' + import std.stdio : writeln; + {prefix} + void main() {{ + writeln(({typename}).alignof); + }} + ''' + res = self.run(t, env, extra_args=extra_args, + dependencies=dependencies) + if not res.compiled: + raise mesonlib.EnvironmentException('Could not compile alignment test.') + if res.returncode != 0: + raise mesonlib.EnvironmentException('Could not run alignment test binary.') + align = int(res.stdout) + if align == 0: + raise mesonlib.EnvironmentException(f'Could not determine alignment of {typename}. Sorry. You might want to file a bug.') + return align + + def has_header(self, hname: str, prefix: str, env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None, + disable_cache: bool = False) -> T.Tuple[bool, bool]: + + extra_args = self._get_compile_extra_args(extra_args) + code = f'''{prefix} + import {hname}; + ''' + return self.compiles(code, env, extra_args=extra_args, + dependencies=dependencies, mode='compile', disable_cache=disable_cache) + +class GnuDCompiler(GnuCompiler, DCompiler): + + # we mostly want DCompiler, but that gives us the Compiler.LINKER_PREFIX instead + LINKER_PREFIX = GnuCompiler.LINKER_PREFIX + id = 'gcc' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + info: 'MachineInfo', arch: str, *, + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None, + is_cross: bool = False): + DCompiler.__init__(self, exelist, version, for_machine, info, arch, + exe_wrapper=exe_wrapper, linker=linker, + full_version=full_version, is_cross=is_cross) + GnuCompiler.__init__(self, {}) + default_warn_args = ['-Wall', '-Wdeprecated'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-Wpedantic'], + 'everything': (default_warn_args + ['-Wextra', '-Wpedantic'] + + self.supported_warn_args(gnu_common_warning_args))} + + self.base_options = { + OptionKey(o) for o in [ + 'b_colorout', 'b_sanitize', 'b_staticpic', 'b_vscrt', + 'b_coverage', 'b_pgo', 'b_ndebug']} + + self._has_color_support = version_compare(self.version, '>=4.9') + # dependencies were implemented before, but broken - support was fixed in GCC 7.1+ + # (and some backported versions) + self._has_deps_support = version_compare(self.version, '>=7.1') + + def get_colorout_args(self, colortype: str) -> T.List[str]: + if self._has_color_support: + super().get_colorout_args(colortype) + return [] + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + if self._has_deps_support: + return super().get_dependency_gen_args(outtarget, outfile) + return [] + + def get_warn_args(self, level: str) -> T.List[str]: + return self.warn_args[level] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return d_gdc_buildtype_args[buildtype] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:2] == '-I' or i[:2] == '-L': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + + return parameter_list + + def get_allow_undefined_link_args(self) -> T.List[str]: + return self.linker.get_allow_undefined_args() + + def get_linker_always_args(self) -> T.List[str]: + args = super().get_linker_always_args() + if self.info.is_windows(): + return args + return args + ['-shared-libphobos'] + + def get_disable_assert_args(self) -> T.List[str]: + return ['-frelease'] + +# LDC uses the DMD frontend code to parse and analyse the code. +# It then uses LLVM for the binary code generation and optimizations. +# This function retrieves the dmd frontend version, which determines +# the common features between LDC and DMD. +# We need the complete version text because the match is not on first line +# of version_output +def find_ldc_dmd_frontend_version(version_output: T.Optional[str]) -> T.Optional[str]: + if version_output is None: + return None + version_regex = re.search(r'DMD v(\d+\.\d+\.\d+)', version_output) + if version_regex: + return version_regex.group(1) + return None + +class LLVMDCompiler(DmdLikeCompilerMixin, DCompiler): + + id = 'llvm' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + info: 'MachineInfo', arch: str, *, + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None, + is_cross: bool = False, version_output: T.Optional[str] = None): + DCompiler.__init__(self, exelist, version, for_machine, info, arch, + exe_wrapper=exe_wrapper, linker=linker, + full_version=full_version, is_cross=is_cross) + DmdLikeCompilerMixin.__init__(self, dmd_frontend_version=find_ldc_dmd_frontend_version(version_output)) + self.base_options = {OptionKey(o) for o in ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug']} + + def get_colorout_args(self, colortype: str) -> T.List[str]: + if colortype == 'always': + return ['-enable-color'] + return [] + + def get_warn_args(self, level: str) -> T.List[str]: + if level in {'2', '3'}: + return ['-wi', '-dw'] + elif level == '1': + return ['-wi'] + return [] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + if buildtype != 'plain': + return self._get_target_arch_args() + d_ldc_buildtype_args[buildtype] + return d_ldc_buildtype_args[buildtype] + + def get_pic_args(self) -> T.List[str]: + return ['-relocation-model=pic'] + + def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]: + return self._get_crt_args(crt_val, buildtype) + + def unix_args_to_native(self, args: T.List[str]) -> T.List[str]: + return self._unix_args_to_native(args, self.info, self.linker.id) + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return ldc_optimization_args[optimization_level] + + @classmethod + def use_linker_args(cls, linker: str, version: str) -> T.List[str]: + return [f'-linker={linker}'] + + def get_linker_always_args(self) -> T.List[str]: + args = super().get_linker_always_args() + if self.info.is_windows(): + return args + return args + ['-link-defaultlib-shared'] + + def get_disable_assert_args(self) -> T.List[str]: + return ['--release'] + + def rsp_file_syntax(self) -> RSPFileSyntax: + # We use `mesonlib.is_windows` here because we want to know what the + # build machine is, not the host machine. This really means we would + # have the Environment not the MachineInfo in the compiler. + return RSPFileSyntax.MSVC if is_windows() else RSPFileSyntax.GCC + + +class DmdDCompiler(DmdLikeCompilerMixin, DCompiler): + + id = 'dmd' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + info: 'MachineInfo', arch: str, *, + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None, + is_cross: bool = False): + DCompiler.__init__(self, exelist, version, for_machine, info, arch, + exe_wrapper=exe_wrapper, linker=linker, + full_version=full_version, is_cross=is_cross) + DmdLikeCompilerMixin.__init__(self, version) + self.base_options = {OptionKey(o) for o in ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug']} + + def get_colorout_args(self, colortype: str) -> T.List[str]: + if colortype == 'always': + return ['-color=on'] + return [] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + if buildtype != 'plain': + return self._get_target_arch_args() + d_dmd_buildtype_args[buildtype] + return d_dmd_buildtype_args[buildtype] + + def get_std_exe_link_args(self) -> T.List[str]: + if self.info.is_windows(): + # DMD links against D runtime only when main symbol is found, + # so these needs to be inserted when linking static D libraries. + if self.arch == 'x86_64': + return ['phobos64.lib'] + elif self.arch == 'x86_mscoff': + return ['phobos32mscoff.lib'] + return ['phobos.lib'] + return [] + + def get_std_shared_lib_link_args(self) -> T.List[str]: + libname = 'libphobos2.so' + if self.info.is_windows(): + if self.arch == 'x86_64': + libname = 'phobos64.lib' + elif self.arch == 'x86_mscoff': + libname = 'phobos32mscoff.lib' + else: + libname = 'phobos.lib' + return ['-shared', '-defaultlib=' + libname] + + def _get_target_arch_args(self) -> T.List[str]: + # DMD32 and DMD64 on 64-bit Windows defaults to 32-bit (OMF). + # Force the target to 64-bit in order to stay consistent + # across the different platforms. + if self.info.is_windows(): + if self.arch == 'x86_64': + return ['-m64'] + elif self.arch == 'x86_mscoff': + return ['-m32mscoff'] + return ['-m32'] + return [] + + def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: + return self._get_crt_args(crt_val, buildtype) + + def unix_args_to_native(self, args: T.List[str]) -> T.List[str]: + return self._unix_args_to_native(args, self.info, self.linker.id) + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return dmd_optimization_args[optimization_level] + + def can_linker_accept_rsp(self) -> bool: + return False + + def get_linker_always_args(self) -> T.List[str]: + args = super().get_linker_always_args() + if self.info.is_windows(): + return args + return args + ['-defaultlib=phobos2', '-debuglib=phobos2'] + + def get_disable_assert_args(self) -> T.List[str]: + return ['-release'] + + def rsp_file_syntax(self) -> RSPFileSyntax: + return RSPFileSyntax.MSVC diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py new file mode 100644 index 0000000..1f37833 --- /dev/null +++ b/mesonbuild/compilers/detect.py @@ -0,0 +1,1317 @@ +# Copyright 2012-2022 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 __future__ import annotations + +from ..mesonlib import ( + MesonException, EnvironmentException, MachineChoice, join_args, + search_version, is_windows, Popen_safe, windows_proof_rm, +) +from ..envconfig import BinaryTable +from .. import mlog + +from ..linkers import guess_win_linker, guess_nix_linker + +import subprocess +import platform +import re +import shutil +import tempfile +import os +import typing as T + +if T.TYPE_CHECKING: + from .compilers import Compiler + from .c import CCompiler + from .cpp import CPPCompiler + from .fortran import FortranCompiler + from .rust import RustCompiler + from ..linkers import StaticLinker + from ..environment import Environment + from ..programs import ExternalProgram + + +# Default compilers and linkers +# ============================= + +defaults: T.Dict[str, T.List[str]] = {} + +# List of potential compilers. +if is_windows(): + # Intel C and C++ compiler is icl on Windows, but icc and icpc elsewhere. + # Search for icl before cl, since Intel "helpfully" provides a + # cl.exe that returns *exactly the same thing* that microsofts + # cl.exe does, and if icl is present, it's almost certainly what + # you want. + defaults['c'] = ['icl', 'cl', 'cc', 'gcc', 'clang', 'clang-cl', 'pgcc'] + # There is currently no pgc++ for Windows, only for Mac and Linux. + defaults['cpp'] = ['icl', 'cl', 'c++', 'g++', 'clang++', 'clang-cl'] + defaults['fortran'] = ['ifort', 'gfortran', 'flang', 'pgfortran', 'g95'] + # Clang and clang++ are valid, but currently unsupported. + defaults['objc'] = ['cc', 'gcc'] + defaults['objcpp'] = ['c++', 'g++'] + defaults['cs'] = ['csc', 'mcs'] +else: + if platform.machine().lower() == 'e2k': + defaults['c'] = ['cc', 'gcc', 'lcc', 'clang'] + defaults['cpp'] = ['c++', 'g++', 'l++', 'clang++'] + defaults['objc'] = ['clang'] + defaults['objcpp'] = ['clang++'] + else: + defaults['c'] = ['cc', 'gcc', 'clang', 'nvc', 'pgcc', 'icc', 'icx'] + defaults['cpp'] = ['c++', 'g++', 'clang++', 'nvc++', 'pgc++', 'icpc', 'icpx'] + defaults['objc'] = ['cc', 'gcc', 'clang'] + defaults['objcpp'] = ['c++', 'g++', 'clang++'] + defaults['fortran'] = ['gfortran', 'flang', 'nvfortran', 'pgfortran', 'ifort', 'ifx', 'g95'] + defaults['cs'] = ['mcs', 'csc'] +defaults['d'] = ['ldc2', 'ldc', 'gdc', 'dmd'] +defaults['java'] = ['javac'] +defaults['cuda'] = ['nvcc'] +defaults['rust'] = ['rustc'] +defaults['swift'] = ['swiftc'] +defaults['vala'] = ['valac'] +defaults['cython'] = ['cython', 'cython3'] # Official name is cython, but Debian renamed it to cython3. +defaults['static_linker'] = ['ar', 'gar'] +defaults['strip'] = ['strip'] +defaults['vs_static_linker'] = ['lib'] +defaults['clang_cl_static_linker'] = ['llvm-lib'] +defaults['cuda_static_linker'] = ['nvlink'] +defaults['gcc_static_linker'] = ['gcc-ar'] +defaults['clang_static_linker'] = ['llvm-ar'] +defaults['nasm'] = ['nasm', 'yasm'] + + +def compiler_from_language(env: 'Environment', lang: str, for_machine: MachineChoice) -> T.Optional[Compiler]: + lang_map: T.Dict[str, T.Callable[['Environment', MachineChoice], Compiler]] = { + 'c': detect_c_compiler, + 'cpp': detect_cpp_compiler, + 'objc': detect_objc_compiler, + 'cuda': detect_cuda_compiler, + 'objcpp': detect_objcpp_compiler, + 'java': detect_java_compiler, + 'cs': detect_cs_compiler, + 'vala': detect_vala_compiler, + 'd': detect_d_compiler, + 'rust': detect_rust_compiler, + 'fortran': detect_fortran_compiler, + 'swift': detect_swift_compiler, + 'cython': detect_cython_compiler, + 'nasm': detect_nasm_compiler, + 'masm': detect_masm_compiler, + } + return lang_map[lang](env, for_machine) if lang in lang_map else None + +def detect_compiler_for(env: 'Environment', lang: str, for_machine: MachineChoice) -> T.Optional[Compiler]: + comp = compiler_from_language(env, lang, for_machine) + if comp is not None: + assert comp.for_machine == for_machine + env.coredata.process_new_compiler(lang, comp, env) + return comp + + +# Helpers +# ======= + +def _get_compilers(env: 'Environment', lang: str, for_machine: MachineChoice) -> T.Tuple[T.List[T.List[str]], T.List[str], T.Optional['ExternalProgram']]: + ''' + The list of compilers is detected in the exact same way for + C, C++, ObjC, ObjC++, Fortran, CS so consolidate it here. + ''' + value = env.lookup_binary_entry(for_machine, lang) + if value is not None: + comp, ccache = BinaryTable.parse_entry(value) + # Return value has to be a list of compiler 'choices' + compilers = [comp] + else: + if not env.machines.matches_build_machine(for_machine): + raise EnvironmentException(f'{lang!r} compiler binary not defined in cross or native file') + compilers = [[x] for x in defaults[lang]] + ccache = BinaryTable.detect_compiler_cache() + + if env.machines.matches_build_machine(for_machine): + exe_wrap: T.Optional[ExternalProgram] = None + else: + exe_wrap = env.get_exe_wrapper() + + return compilers, ccache, exe_wrap + +def _handle_exceptions( + exceptions: T.Mapping[str, T.Union[Exception, str]], + binaries: T.List[T.List[str]], + bintype: str = 'compiler') -> T.NoReturn: + errmsg = f'Unknown {bintype}(s): {binaries}' + if exceptions: + errmsg += '\nThe following exception(s) were encountered:' + for c, e in exceptions.items(): + errmsg += f'\nRunning `{c}` gave "{e}"' + raise EnvironmentException(errmsg) + + +# Linker specific +# =============== + +def detect_static_linker(env: 'Environment', compiler: Compiler) -> StaticLinker: + from . import d + from ..linkers import linkers + linker = env.lookup_binary_entry(compiler.for_machine, 'ar') + if linker is not None: + trials = [linker] + else: + default_linkers = [[l] for l in defaults['static_linker']] + if compiler.language == 'cuda': + trials = [defaults['cuda_static_linker']] + default_linkers + elif compiler.get_argument_syntax() == 'msvc': + trials = [defaults['vs_static_linker'], defaults['clang_cl_static_linker']] + elif compiler.id == 'gcc': + # Use gcc-ar if available; needed for LTO + trials = [defaults['gcc_static_linker']] + default_linkers + elif compiler.id == 'clang': + # Use llvm-ar if available; needed for LTO + trials = [defaults['clang_static_linker']] + default_linkers + elif compiler.language == 'd': + # Prefer static linkers over linkers used by D compilers + if is_windows(): + trials = [defaults['vs_static_linker'], defaults['clang_cl_static_linker'], compiler.get_linker_exelist()] + else: + trials = default_linkers + elif compiler.id == 'intel-cl' and compiler.language == 'c': # why not cpp? Is this a bug? + # Intel has it's own linker that acts like microsoft's lib + trials = [['xilib']] + elif is_windows() and compiler.id == 'pgi': # this handles cpp / nvidia HPC, in addition to just c/fortran + trials = [['ar']] # For PGI on Windows, "ar" is just a wrapper calling link/lib. + else: + trials = default_linkers + popen_exceptions = {} + for linker in trials: + linker_name = os.path.basename(linker[0]) + + if any(os.path.basename(x) in {'lib', 'lib.exe', 'llvm-lib', 'llvm-lib.exe', 'xilib', 'xilib.exe'} for x in linker): + arg = '/?' + elif linker_name in {'ar2000', 'ar2000.exe', 'ar430', 'ar430.exe', 'armar', 'armar.exe'}: + arg = '?' + else: + arg = '--version' + try: + p, out, err = Popen_safe(linker + [arg]) + except OSError as e: + popen_exceptions[join_args(linker + [arg])] = e + continue + if "xilib: executing 'lib'" in err: + return linkers.IntelVisualStudioLinker(linker, getattr(compiler, 'machine', None)) + if '/OUT:' in out.upper() or '/OUT:' in err.upper(): + return linkers.VisualStudioLinker(linker, getattr(compiler, 'machine', None)) + if 'ar-Error-Unknown switch: --version' in err: + return linkers.PGIStaticLinker(linker) + if p.returncode == 0 and 'armar' in linker_name: + return linkers.ArmarLinker(linker) + if 'DMD32 D Compiler' in out or 'DMD64 D Compiler' in out: + assert isinstance(compiler, d.DCompiler) + return linkers.DLinker(linker, compiler.arch) + if 'LDC - the LLVM D compiler' in out: + assert isinstance(compiler, d.DCompiler) + return linkers.DLinker(linker, compiler.arch, rsp_syntax=compiler.rsp_file_syntax()) + if 'GDC' in out and ' based on D ' in out: + assert isinstance(compiler, d.DCompiler) + return linkers.DLinker(linker, compiler.arch) + if err.startswith('Renesas') and 'rlink' in linker_name: + return linkers.CcrxLinker(linker) + if out.startswith('GNU ar') and 'xc16-ar' in linker_name: + return linkers.Xc16Linker(linker) + if 'Texas Instruments Incorporated' in out: + if 'ar2000' in linker_name: + return linkers.C2000Linker(linker) + else: + return linkers.TILinker(linker) + if out.startswith('The CompCert'): + return linkers.CompCertLinker(linker) + if p.returncode == 0: + return linkers.ArLinker(compiler.for_machine, linker) + if p.returncode == 1 and err.startswith('usage'): # OSX + return linkers.AppleArLinker(compiler.for_machine, linker) + if p.returncode == 1 and err.startswith('Usage'): # AIX + return linkers.AIXArLinker(linker) + if p.returncode == 1 and err.startswith('ar: bad option: --'): # Solaris + return linkers.ArLinker(compiler.for_machine, linker) + _handle_exceptions(popen_exceptions, trials, 'linker') + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + + +# Compilers +# ========= + + +def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: MachineChoice, *, override_compiler: T.Optional[T.List[str]] = None) -> Compiler: + """Shared implementation for finding the C or C++ compiler to use. + + the override_compiler option is provided to allow compilers which use + the compiler (GCC or Clang usually) as their shared linker, to find + the linker they need. + """ + from . import c, cpp + from ..linkers import linkers + popen_exceptions: T.Dict[str, T.Union[Exception, str]] = {} + compilers, ccache, exe_wrap = _get_compilers(env, lang, for_machine) + if override_compiler is not None: + compilers = [override_compiler] + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + cls: T.Union[T.Type[CCompiler], T.Type[CPPCompiler]] + + for compiler in compilers: + if isinstance(compiler, str): + compiler = [compiler] + compiler_name = os.path.basename(compiler[0]) + + if any(os.path.basename(x) in {'cl', 'cl.exe', 'clang-cl', 'clang-cl.exe'} for x in compiler): + # Watcom C provides it's own cl.exe clone that mimics an older + # version of Microsoft's compiler. Since Watcom's cl.exe is + # just a wrapper, we skip using it if we detect its presence + # so as not to confuse Meson when configuring for MSVC. + # + # Additionally the help text of Watcom's cl.exe is paged, and + # the binary will not exit without human intervention. In + # practice, Meson will block waiting for Watcom's cl.exe to + # exit, which requires user input and thus will never exit. + if 'WATCOM' in os.environ: + def sanitize(p: str) -> str: + return os.path.normcase(os.path.abspath(p)) + + watcom_cls = [sanitize(os.path.join(os.environ['WATCOM'], 'BINNT', 'cl')), + sanitize(os.path.join(os.environ['WATCOM'], 'BINNT', 'cl.exe')), + sanitize(os.path.join(os.environ['WATCOM'], 'BINNT64', 'cl')), + sanitize(os.path.join(os.environ['WATCOM'], 'BINNT64', 'cl.exe'))] + found_cl = sanitize(shutil.which('cl')) + if found_cl in watcom_cls: + mlog.debug('Skipping unsupported cl.exe clone at:', found_cl) + continue + arg = '/?' + elif 'armcc' in compiler_name: + arg = '--vsn' + elif 'ccrx' in compiler_name: + arg = '-v' + elif 'xc16' in compiler_name: + arg = '--version' + elif 'ccomp' in compiler_name: + arg = '-version' + elif compiler_name in {'cl2000', 'cl2000.exe', 'cl430', 'cl430.exe', 'armcl', 'armcl.exe'}: + # TI compiler + arg = '-version' + elif compiler_name in {'icl', 'icl.exe'}: + # if you pass anything to icl you get stuck in a pager + arg = '' + else: + arg = '--version' + + cmd = compiler + [arg] + try: + mlog.debug('-----') + mlog.debug(f'Detecting compiler via: {join_args(cmd)}') + p, out, err = Popen_safe(cmd) + mlog.debug(f'compiler returned {p}') + mlog.debug(f'compiler stdout:\n{out}') + mlog.debug(f'compiler stderr:\n{err}') + except OSError as e: + popen_exceptions[join_args(cmd)] = e + continue + + if 'ccrx' in compiler_name: + out = err + + full_version = out.split('\n', 1)[0] + version = search_version(out) + + guess_gcc_or_lcc: T.Optional[str] = None + if 'Free Software Foundation' in out or 'xt-' in out: + guess_gcc_or_lcc = 'gcc' + if 'e2k' in out and 'lcc' in out: + guess_gcc_or_lcc = 'lcc' + if 'Microchip Technology' in out: + # this output has "Free Software Foundation" in its version + guess_gcc_or_lcc = None + + if guess_gcc_or_lcc: + defines = _get_gnu_compiler_defines(compiler) + if not defines: + popen_exceptions[join_args(compiler)] = 'no pre-processor defines' + continue + + if guess_gcc_or_lcc == 'lcc': + version = _get_lcc_version_from_defines(defines) + cls = c.ElbrusCCompiler if lang == 'c' else cpp.ElbrusCPPCompiler + else: + version = _get_gnu_version_from_defines(defines) + cls = c.GnuCCompiler if lang == 'c' else cpp.GnuCPPCompiler + + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + + return cls( + ccache, compiler, version, for_machine, is_cross, + info, exe_wrap, defines=defines, full_version=full_version, + linker=linker) + + if 'Emscripten' in out: + cls = c.EmscriptenCCompiler if lang == 'c' else cpp.EmscriptenCPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + + # emcc requires a file input in order to pass arguments to the + # linker. It'll exit with an error code, but still print the + # linker version. + with tempfile.NamedTemporaryFile(suffix='.c') as f: + cmd = compiler + [cls.LINKER_PREFIX + "--version", f.name] + _, o, _ = Popen_safe(cmd) + + linker = linkers.WASMDynamicLinker( + compiler, for_machine, cls.LINKER_PREFIX, + [], version=search_version(o)) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, linker=linker, full_version=full_version) + + if 'Arm C/C++/Fortran Compiler' in out: + arm_ver_match = re.search(r'version (\d+)\.(\d+)\.?(\d+)? \(build number (\d+)\)', out) + assert arm_ver_match is not None, 'for mypy' # because mypy *should* be complaning that this could be None + version = '.'.join([x for x in arm_ver_match.groups() if x is not None]) + if lang == 'c': + cls = c.ArmLtdClangCCompiler + elif lang == 'cpp': + cls = cpp.ArmLtdClangCPPCompiler + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, linker=linker) + if 'armclang' in out: + # The compiler version is not present in the first line of output, + # instead it is present in second line, startswith 'Component:'. + # So, searching for the 'Component' in out although we know it is + # present in second line, as we are not sure about the + # output format in future versions + arm_ver_match = re.search('.*Component.*', out) + if arm_ver_match is None: + popen_exceptions[join_args(compiler)] = 'version string not found' + continue + arm_ver_str = arm_ver_match.group(0) + # Override previous values + version = search_version(arm_ver_str) + full_version = arm_ver_str + cls = c.ArmclangCCompiler if lang == 'c' else cpp.ArmclangCPPCompiler + linker = linkers.ArmClangDynamicLinker(for_machine, version=version) + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + if 'CL.EXE COMPATIBILITY' in out: + # if this is clang-cl masquerading as cl, detect it as cl, not + # clang + arg = '--version' + try: + p, out, err = Popen_safe(compiler + [arg]) + except OSError as e: + popen_exceptions[join_args(compiler + [arg])] = e + version = search_version(out) + match = re.search('^Target: (.*?)-', out, re.MULTILINE) + if match: + target = match.group(1) + else: + target = 'unknown target' + cls = c.ClangClCCompiler if lang == 'c' else cpp.ClangClCPPCompiler + linker = guess_win_linker(env, ['lld-link'], cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, target, + exe_wrap, linker=linker) + if 'clang' in out or 'Clang' in out: + linker = None + + defines = _get_clang_compiler_defines(compiler) + + # Even if the for_machine is darwin, we could be using vanilla + # clang. + if 'Apple' in out: + cls = c.AppleClangCCompiler if lang == 'c' else cpp.AppleClangCPPCompiler + else: + cls = c.ClangCCompiler if lang == 'c' else cpp.ClangCPPCompiler + + if 'windows' in out or env.machines[for_machine].is_windows(): + # If we're in a MINGW context this actually will use a gnu + # style ld, but for clang on "real" windows we'll use + # either link.exe or lld-link.exe + try: + linker = guess_win_linker(env, compiler, cls, version, for_machine, invoked_directly=False) + except MesonException: + pass + if linker is None: + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, defines=defines, full_version=full_version, linker=linker) + + if 'Intel(R) C++ Intel(R)' in err: + version = search_version(err) + target = 'x86' if 'IA-32' in err else 'x86_64' + cls = c.IntelClCCompiler if lang == 'c' else cpp.IntelClCPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, target, + exe_wrap, linker=linker) + if 'Intel(R) oneAPI DPC++/C++ Compiler for applications' in err: + version = search_version(err) + target = 'x86' if 'IA-32' in err else 'x86_64' + cls = c.IntelLLVMClCCompiler if lang == 'c' else cpp.IntelLLVMClCPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, target, + exe_wrap, linker=linker) + if 'Microsoft' in out or 'Microsoft' in err: + # Latest versions of Visual Studio print version + # number to stderr but earlier ones print version + # on stdout. Why? Lord only knows. + # Check both outputs to figure out version. + for lookat in [err, out]: + version = search_version(lookat) + if version != 'unknown version': + break + else: + raise EnvironmentException(f'Failed to detect MSVC compiler version: stderr was\n{err!r}') + cl_signature = lookat.split('\n', maxsplit=1)[0] + match = re.search(r'.*(x86|x64|ARM|ARM64)([^_A-Za-z0-9]|$)', cl_signature) + if match: + target = match.group(1) + else: + m = f'Failed to detect MSVC compiler target architecture: \'cl /?\' output is\n{cl_signature}' + raise EnvironmentException(m) + cls = c.VisualStudioCCompiler if lang == 'c' else cpp.VisualStudioCPPCompiler + linker = guess_win_linker(env, ['link'], cls, version, for_machine) + # As of this writing, CCache does not support MSVC but sccache does. + if 'sccache' not in ccache: + ccache = [] + return cls( + ccache, compiler, version, for_machine, is_cross, info, target, + exe_wrap, full_version=cl_signature, linker=linker) + if 'PGI Compilers' in out: + cls = c.PGICCompiler if lang == 'c' else cpp.PGICPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.PGIDynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) + return cls( + ccache, compiler, version, for_machine, is_cross, + info, exe_wrap, linker=linker) + if 'NVIDIA Compilers and Tools' in out: + cls = c.NvidiaHPC_CCompiler if lang == 'c' else cpp.NvidiaHPC_CPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.NvidiaHPC_DynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) + return cls( + ccache, compiler, version, for_machine, is_cross, + info, exe_wrap, linker=linker) + if '(ICC)' in out: + cls = c.IntelCCompiler if lang == 'c' else cpp.IntelCPPCompiler + l = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=l) + if 'Intel(R) oneAPI' in out: + cls = c.IntelLLVMCCompiler if lang == 'c' else cpp.IntelLLVMCPPCompiler + l = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=l) + if 'TMS320C2000 C/C++' in out or 'MSP430 C/C++' in out or 'TI ARM C/C++ Compiler' in out: + lnk: T.Union[T.Type[linkers.C2000DynamicLinker], T.Type[linkers.TIDynamicLinker]] + if 'TMS320C2000 C/C++' in out: + cls = c.C2000CCompiler if lang == 'c' else cpp.C2000CPPCompiler + lnk = linkers.C2000DynamicLinker + else: + cls = c.TICCompiler if lang == 'c' else cpp.TICPPCompiler + lnk = linkers.TIDynamicLinker + + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = lnk(compiler, for_machine, version=version) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + if 'ARM' in out: + cls = c.ArmCCompiler if lang == 'c' else cpp.ArmCPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.ArmDynamicLinker(for_machine, version=version) + return cls( + ccache, compiler, version, for_machine, is_cross, + info, exe_wrap, full_version=full_version, linker=linker) + if 'RX Family' in out: + cls = c.CcrxCCompiler if lang == 'c' else cpp.CcrxCPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.CcrxDynamicLinker(for_machine, version=version) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'Microchip Technology' in out: + cls = c.Xc16CCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.Xc16DynamicLinker(for_machine, version=version) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'CompCert' in out: + cls = c.CompCertCCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.CompCertDynamicLinker(for_machine, version=version) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException(f'Unknown compiler {compilers}') + +def detect_c_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + return _detect_c_or_cpp_compiler(env, 'c', for_machine) + +def detect_cpp_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + return _detect_c_or_cpp_compiler(env, 'cpp', for_machine) + +def detect_cuda_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from .cuda import CudaCompiler + from ..linkers.linkers import CudaLinker + popen_exceptions = {} + is_cross = env.is_cross_build(for_machine) + compilers, ccache, exe_wrap = _get_compilers(env, 'cuda', for_machine) + info = env.machines[for_machine] + for compiler in compilers: + arg = '--version' + try: + p, out, err = Popen_safe(compiler + [arg]) + except OSError as e: + popen_exceptions[join_args(compiler + [arg])] = e + continue + # Example nvcc printout: + # + # nvcc: NVIDIA (R) Cuda compiler driver + # Copyright (c) 2005-2018 NVIDIA Corporation + # Built on Sat_Aug_25_21:08:01_CDT_2018 + # Cuda compilation tools, release 10.0, V10.0.130 + # + # search_version() first finds the "10.0" after "release", + # rather than the more precise "10.0.130" after "V". + # The patch version number is occasionally important; For + # instance, on Linux, + # - CUDA Toolkit 8.0.44 requires NVIDIA Driver 367.48 + # - CUDA Toolkit 8.0.61 requires NVIDIA Driver 375.26 + # Luckily, the "V" also makes it very simple to extract + # the full version: + version = out.strip().rsplit('V', maxsplit=1)[-1] + cpp_compiler = detect_cpp_compiler(env, for_machine) + cls = CudaCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = CudaLinker(compiler, for_machine, CudaCompiler.LINKER_PREFIX, [], version=CudaLinker.parse_version()) + return cls(ccache, compiler, version, for_machine, is_cross, exe_wrap, host_compiler=cpp_compiler, info=info, linker=linker) + raise EnvironmentException(f'Could not find suitable CUDA compiler: "{"; ".join([" ".join(c) for c in compilers])}"') + +def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from . import fortran + from ..linkers import linkers + popen_exceptions: T.Dict[str, T.Union[Exception, str]] = {} + compilers, ccache, exe_wrap = _get_compilers(env, 'fortran', for_machine) + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + cls: T.Type[FortranCompiler] + for compiler in compilers: + for arg in ['--version', '-V']: + try: + p, out, err = Popen_safe(compiler + [arg]) + except OSError as e: + popen_exceptions[join_args(compiler + [arg])] = e + continue + + version = search_version(out) + full_version = out.split('\n', 1)[0] + + guess_gcc_or_lcc: T.Optional[str] = None + if 'GNU Fortran' in out: + guess_gcc_or_lcc = 'gcc' + if 'e2k' in out and 'lcc' in out: + guess_gcc_or_lcc = 'lcc' + + if guess_gcc_or_lcc: + defines = _get_gnu_compiler_defines(compiler) + if not defines: + popen_exceptions[join_args(compiler)] = 'no pre-processor defines' + continue + if guess_gcc_or_lcc == 'lcc': + version = _get_lcc_version_from_defines(defines) + cls = fortran.ElbrusFortranCompiler + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, defines, full_version=full_version, linker=linker) + else: + version = _get_gnu_version_from_defines(defines) + cls = fortran.GnuFortranCompiler + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, defines, full_version=full_version, linker=linker) + + if 'Arm C/C++/Fortran Compiler' in out: + cls = fortran.ArmLtdFlangFortranCompiler + arm_ver_match = re.search(r'version (\d+)\.(\d+)\.?(\d+)? \(build number (\d+)\)', out) + assert arm_ver_match is not None, 'for mypy' # because mypy *should* be complaning that this could be None + version = '.'.join([x for x in arm_ver_match.groups() if x is not None]) + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, linker=linker) + if 'G95' in out: + cls = fortran.G95FortranCompiler + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'Sun Fortran' in err: + version = search_version(err) + cls = fortran.SunFortranCompiler + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'Intel(R) Fortran Compiler for applications' in err: + version = search_version(err) + target = 'x86' if 'IA-32' in err else 'x86_64' + cls = fortran.IntelLLVMClFortranCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, + target, exe_wrap, linker=linker) + + if 'Intel(R) Visual Fortran' in err or 'Intel(R) Fortran' in err: + version = search_version(err) + target = 'x86' if 'IA-32' in err else 'x86_64' + cls = fortran.IntelClFortranCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, + target, exe_wrap, linker=linker) + + if 'ifort (IFORT)' in out: + cls = fortran.IntelFortranCompiler + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'ifx (IFORT)' in out: + cls = fortran.IntelLLVMFortranCompiler + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'PathScale EKOPath(tm)' in err: + return fortran.PathScaleFortranCompiler( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version) + + if 'PGI Compilers' in out: + cls = fortran.PGIFortranCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.PGIDynamicLinker(compiler, for_machine, + cls.LINKER_PREFIX, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, exe_wrap, + full_version=full_version, linker=linker) + + if 'NVIDIA Compilers and Tools' in out: + cls = fortran.NvidiaHPC_FortranCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.PGIDynamicLinker(compiler, for_machine, + cls.LINKER_PREFIX, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, exe_wrap, + full_version=full_version, linker=linker) + + if 'flang' in out or 'clang' in out: + cls = fortran.FlangFortranCompiler + linker = guess_nix_linker(env, + compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'Open64 Compiler Suite' in err: + cls = fortran.Open64FortranCompiler + linker = guess_nix_linker(env, + compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'NAG Fortran' in err: + full_version = err.split('\n', 1)[0] + version = full_version.split()[-1] + cls = fortran.NAGFortranCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.NAGDynamicLinker( + compiler, for_machine, cls.LINKER_PREFIX, [], + version=version) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +def detect_objc_compiler(env: 'Environment', for_machine: MachineChoice) -> 'Compiler': + return _detect_objc_or_objcpp_compiler(env, 'objc', for_machine) + +def detect_objcpp_compiler(env: 'Environment', for_machine: MachineChoice) -> 'Compiler': + return _detect_objc_or_objcpp_compiler(env, 'objcpp', for_machine) + +def _detect_objc_or_objcpp_compiler(env: 'Environment', lang: str, for_machine: MachineChoice) -> 'Compiler': + from . import objc, objcpp + popen_exceptions: T.Dict[str, T.Union[Exception, str]] = {} + compilers, ccache, exe_wrap = _get_compilers(env, lang, for_machine) + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + comp: T.Union[T.Type[objc.ObjCCompiler], T.Type[objcpp.ObjCPPCompiler]] + + for compiler in compilers: + arg = ['--version'] + try: + p, out, err = Popen_safe(compiler + arg) + except OSError as e: + popen_exceptions[join_args(compiler + arg)] = e + continue + version = search_version(out) + if 'Free Software Foundation' in out: + defines = _get_gnu_compiler_defines(compiler) + if not defines: + popen_exceptions[join_args(compiler)] = 'no pre-processor defines' + continue + version = _get_gnu_version_from_defines(defines) + comp = objc.GnuObjCCompiler if lang == 'objc' else objcpp.GnuObjCPPCompiler + linker = guess_nix_linker(env, compiler, comp, version, for_machine) + return comp( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, defines, linker=linker) + if 'clang' in out: + linker = None + defines = _get_clang_compiler_defines(compiler) + if not defines: + popen_exceptions[join_args(compiler)] = 'no pre-processor defines' + continue + if 'Apple' in out: + comp = objc.AppleClangObjCCompiler if lang == 'objc' else objcpp.AppleClangObjCPPCompiler + else: + comp = objc.ClangObjCCompiler if lang == 'objc' else objcpp.ClangObjCPPCompiler + if 'windows' in out or env.machines[for_machine].is_windows(): + # If we're in a MINGW context this actually will use a gnu style ld + try: + linker = guess_win_linker(env, compiler, comp, version, for_machine) + except MesonException: + pass + + if not linker: + linker = guess_nix_linker(env, compiler, comp, version, for_machine) + return comp( + ccache, compiler, version, for_machine, + is_cross, info, exe_wrap, linker=linker, defines=defines) + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +def detect_java_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from .java import JavaCompiler + exelist = env.lookup_binary_entry(for_machine, 'java') + info = env.machines[for_machine] + if exelist is None: + # TODO support fallback + exelist = [defaults['java'][0]] + + try: + p, out, err = Popen_safe(exelist + ['-version']) + except OSError: + raise EnvironmentException('Could not execute Java compiler: {}'.format(join_args(exelist))) + if 'javac' in out or 'javac' in err: + version = search_version(err if 'javac' in err else out) + if not version or version == 'unknown version': + parts = (err if 'javac' in err else out).split() + if len(parts) > 1: + version = parts[1] + comp_class = JavaCompiler + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class(exelist, version, for_machine, info) + raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) + +def detect_cs_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from . import cs + compilers, ccache, exe_wrap = _get_compilers(env, 'cs', for_machine) + popen_exceptions = {} + info = env.machines[for_machine] + for comp in compilers: + try: + p, out, err = Popen_safe(comp + ['--version']) + except OSError as e: + popen_exceptions[join_args(comp + ['--version'])] = e + continue + + version = search_version(out) + cls: T.Type[cs.CsCompiler] + if 'Mono' in out: + cls = cs.MonoCompiler + elif "Visual C#" in out: + cls = cs.VisualStudioCsCompiler + else: + continue + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + return cls(comp, version, for_machine, info) + + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +def detect_cython_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + """Search for a cython compiler.""" + from .cython import CythonCompiler + compilers, _, _ = _get_compilers(env, 'cython', MachineChoice.BUILD) + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + + popen_exceptions: T.Dict[str, Exception] = {} + for comp in compilers: + try: + err = Popen_safe(comp + ['-V'])[2] + except OSError as e: + popen_exceptions[join_args(comp + ['-V'])] = e + continue + + version = search_version(err) + if 'Cython' in err: + comp_class = CythonCompiler + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class([], comp, version, for_machine, info, is_cross=is_cross) + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +def detect_vala_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from .vala import ValaCompiler + exelist = env.lookup_binary_entry(MachineChoice.BUILD, 'vala') + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + if exelist is None: + # TODO support fallback + exelist = [defaults['vala'][0]] + + try: + p, out = Popen_safe(exelist + ['--version'])[0:2] + except OSError: + raise EnvironmentException('Could not execute Vala compiler: {}'.format(join_args(exelist))) + version = search_version(out) + if 'Vala' in out: + comp_class = ValaCompiler + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class(exelist, version, for_machine, is_cross, info) + raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) + +def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> RustCompiler: + from . import rust + from ..linkers import linkers + popen_exceptions = {} # type: T.Dict[str, Exception] + compilers, _, exe_wrap = _get_compilers(env, 'rust', for_machine) + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + + cc = detect_c_compiler(env, for_machine) + is_link_exe = isinstance(cc.linker, linkers.VisualStudioLikeLinkerMixin) + override = env.lookup_binary_entry(for_machine, 'rust_ld') + + for compiler in compilers: + arg = ['--version'] + try: + out = Popen_safe(compiler + arg)[1] + except OSError as e: + popen_exceptions[join_args(compiler + arg)] = e + continue + + version = search_version(out) + cls: T.Type[RustCompiler] = rust.RustCompiler + + # Clippy is a wrapper around rustc, but it doesn't have rustc in it's + # output. We can otherwise treat it as rustc. + if 'clippy' in out: + out = 'rustc' + cls = rust.ClippyRustCompiler + + if 'rustc' in out: + # On Linux and mac rustc will invoke gcc (clang for mac + # presumably) and it can do this windows, for dynamic linking. + # this means the easiest way to C compiler for dynamic linking. + # figure out what linker to use is to just get the value of the + # C compiler and use that as the basis of the rust linker. + # However, there are two things we need to change, if CC is not + # the default use that, and second add the necessary arguments + # to rust to use -fuse-ld + + if any(a.startswith('linker=') for a in compiler): + mlog.warning( + 'Please do not put -C linker= in your compiler ' + 'command, set rust_ld=command in your cross file ' + 'or use the RUST_LD environment variable, otherwise meson ' + 'will override your selection.') + + compiler = compiler.copy() # avoid mutating the original list + + if override is None: + extra_args: T.Dict[str, T.Union[str, bool]] = {} + always_args: T.List[str] = [] + if is_link_exe: + compiler.extend(cls.use_linker_args(cc.linker.exelist[0], '')) + extra_args['direct'] = True + extra_args['machine'] = cc.linker.machine + else: + exelist = cc.linker.exelist + cc.linker.get_always_args() + if 'ccache' in exelist[0]: + del exelist[0] + c = exelist.pop(0) + compiler.extend(cls.use_linker_args(c, '')) + + # Also ensure that we pass any extra arguments to the linker + for l in exelist: + compiler.extend(['-C', f'link-arg={l}']) + + # This trickery with type() gets us the class of the linker + # so we can initialize a new copy for the Rust Compiler + # TODO rewrite this without type: ignore + assert cc.linker is not None, 'for mypy' + if is_link_exe: + linker = type(cc.linker)(for_machine, always_args, exelist=cc.linker.exelist, # type: ignore + version=cc.linker.version, **extra_args) # type: ignore + else: + linker = type(cc.linker)(compiler, for_machine, cc.LINKER_PREFIX, + always_args=always_args, version=cc.linker.version, + **extra_args) + elif 'link' in override[0]: + linker = guess_win_linker(env, + override, cls, version, for_machine, use_linker_prefix=False) + # rustc takes linker arguments without a prefix, and + # inserts the correct prefix itself. + assert isinstance(linker, linkers.VisualStudioLikeLinkerMixin) + linker.direct = True + compiler.extend(cls.use_linker_args(linker.exelist[0], '')) + else: + # On linux and macos rust will invoke the c compiler for + # linking, on windows it will use lld-link or link.exe. + # we will simply ask for the C compiler that corresponds to + # it, and use that. + cc = _detect_c_or_cpp_compiler(env, 'c', for_machine, override_compiler=override) + linker = cc.linker + + # Of course, we're not going to use any of that, we just + # need it to get the proper arguments to pass to rustc + c = linker.exelist[1] if linker.exelist[0].endswith('ccache') else linker.exelist[0] + compiler.extend(cls.use_linker_args(c, '')) + + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + return cls( + compiler, version, for_machine, is_cross, info, exe_wrap, + linker=linker) + + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from . import c, d + info = env.machines[for_machine] + + # Detect the target architecture, required for proper architecture handling on Windows. + # MSVC compiler is required for correct platform detection. + c_compiler = {'c': detect_c_compiler(env, for_machine)} + is_msvc = isinstance(c_compiler['c'], c.VisualStudioCCompiler) + if not is_msvc: + c_compiler = {} + + # Import here to avoid circular imports + from ..environment import detect_cpu_family + arch = detect_cpu_family(c_compiler) + if is_msvc and arch == 'x86': + arch = 'x86_mscoff' + + popen_exceptions = {} + is_cross = env.is_cross_build(for_machine) + compilers, ccache, exe_wrap = _get_compilers(env, 'd', for_machine) + cls: T.Type[d.DCompiler] + for exelist in compilers: + # Search for a D compiler. + # We prefer LDC over GDC unless overridden with the DC + # environment variable because LDC has a much more + # up to date language version at time (2016). + if os.path.basename(exelist[-1]).startswith(('ldmd', 'gdmd')): + raise EnvironmentException( + f'Meson does not support {exelist[-1]} as it is only a DMD frontend for another compiler.' + 'Please provide a valid value for DC or unset it so that Meson can resolve the compiler by itself.') + try: + p, out = Popen_safe(exelist + ['--version'])[0:2] + except OSError as e: + popen_exceptions[join_args(exelist + ['--version'])] = e + continue + version = search_version(out) + full_version = out.split('\n', 1)[0] + + if 'LLVM D compiler' in out: + cls = d.LLVMDCompiler + # LDC seems to require a file + # We cannot use NamedTemproraryFile on windows, its documented + # to not work for our uses. So, just use mkstemp and only have + # one path for simplicity. + o, f = tempfile.mkstemp('.d') + os.close(o) + + try: + if info.is_windows() or info.is_cygwin(): + objfile = os.path.basename(f)[:-1] + 'obj' + linker = guess_win_linker(env, + exelist, + cls, full_version, for_machine, + use_linker_prefix=True, invoked_directly=False, + extra_args=[f]) + else: + # LDC writes an object file to the current working directory. + # Clean it up. + objfile = os.path.basename(f)[:-1] + 'o' + linker = guess_nix_linker(env, + exelist, cls, full_version, for_machine, + extra_args=[f]) + finally: + windows_proof_rm(f) + windows_proof_rm(objfile) + + return cls( + exelist, version, for_machine, info, arch, + full_version=full_version, linker=linker, version_output=out) + elif 'gdc' in out: + cls = d.GnuDCompiler + linker = guess_nix_linker(env, exelist, cls, version, for_machine) + return cls( + exelist, version, for_machine, info, arch, + exe_wrapper=exe_wrap, is_cross=is_cross, + full_version=full_version, linker=linker) + elif 'The D Language Foundation' in out or 'Digital Mars' in out: + cls = d.DmdDCompiler + # DMD seems to require a file + # We cannot use NamedTemproraryFile on windows, its documented + # to not work for our uses. So, just use mkstemp and only have + # one path for simplicity. + o, f = tempfile.mkstemp('.d') + os.close(o) + + # DMD as different detection logic for x86 and x86_64 + arch_arg = '-m64' if arch == 'x86_64' else '-m32' + + try: + if info.is_windows() or info.is_cygwin(): + objfile = os.path.basename(f)[:-1] + 'obj' + linker = guess_win_linker(env, + exelist, cls, full_version, for_machine, + invoked_directly=False, extra_args=[f, arch_arg]) + else: + objfile = os.path.basename(f)[:-1] + 'o' + linker = guess_nix_linker(env, + exelist, cls, full_version, for_machine, + extra_args=[f, arch_arg]) + finally: + windows_proof_rm(f) + windows_proof_rm(objfile) + + return cls( + exelist, version, for_machine, info, arch, + full_version=full_version, linker=linker) + raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) + + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +def detect_swift_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from .swift import SwiftCompiler + exelist = env.lookup_binary_entry(for_machine, 'swift') + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + if exelist is None: + # TODO support fallback + exelist = [defaults['swift'][0]] + + try: + p, _, err = Popen_safe(exelist + ['-v']) + except OSError: + raise EnvironmentException('Could not execute Swift compiler: {}'.format(join_args(exelist))) + version = search_version(err) + if 'Swift' in err: + # As for 5.0.1 swiftc *requires* a file to check the linker: + with tempfile.NamedTemporaryFile(suffix='.swift') as f: + cls = SwiftCompiler + linker = guess_nix_linker(env, + exelist, cls, version, for_machine, + extra_args=[f.name]) + return cls( + exelist, version, for_machine, is_cross, info, linker=linker) + + raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) + +def detect_nasm_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from .asm import NasmCompiler, YasmCompiler + compilers, _, _ = _get_compilers(env, 'nasm', for_machine) + is_cross = env.is_cross_build(for_machine) + + # We need a C compiler to properly detect the machine info and linker + cc = detect_c_compiler(env, for_machine) + if not is_cross: + from ..environment import detect_machine_info + info = detect_machine_info({'c': cc}) + else: + info = env.machines[for_machine] + + popen_exceptions: T.Dict[str, Exception] = {} + for comp in compilers: + if comp == ['nasm'] and is_windows() and not shutil.which(comp[0]): + # nasm is not in PATH on Windows by default + default_path = os.path.join(os.environ['ProgramFiles'], 'NASM') + comp[0] = shutil.which(comp[0], path=default_path) or comp[0] + try: + output = Popen_safe(comp + ['--version'])[1] + except OSError as e: + popen_exceptions[' '.join(comp + ['--version'])] = e + continue + + version = search_version(output) + if 'NASM' in output: + comp_class = NasmCompiler + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) + elif 'yasm' in output: + comp_class = YasmCompiler + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +def detect_masm_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + # We need a C compiler to properly detect the machine info and linker + is_cross = env.is_cross_build(for_machine) + cc = detect_c_compiler(env, for_machine) + if not is_cross: + from ..environment import detect_machine_info + info = detect_machine_info({'c': cc}) + else: + info = env.machines[for_machine] + + from .asm import MasmCompiler, MasmARMCompiler + comp_class: T.Type[Compiler] + if info.cpu_family == 'x86': + comp = ['ml'] + comp_class = MasmCompiler + arg = '/?' + elif info.cpu_family == 'x86_64': + comp = ['ml64'] + comp_class = MasmCompiler + arg = '/?' + elif info.cpu_family == 'arm': + comp = ['armasm'] + comp_class = MasmARMCompiler + arg = '-h' + elif info.cpu_family == 'aarch64': + comp = ['armasm64'] + comp_class = MasmARMCompiler + arg = '-h' + else: + raise EnvironmentException(f'Platform {info.cpu_family} not supported by MASM') + + popen_exceptions: T.Dict[str, Exception] = {} + try: + output = Popen_safe(comp + [arg])[2] + version = search_version(output) + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) + except OSError as e: + popen_exceptions[' '.join(comp + [arg])] = e + _handle_exceptions(popen_exceptions, [comp]) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +# GNU/Clang defines and version +# ============================= + +def _get_gnu_compiler_defines(compiler: T.List[str]) -> T.Dict[str, str]: + """ + Detect GNU compiler platform type (Apple, MinGW, Unix) + """ + # Arguments to output compiler pre-processor defines to stdout + # gcc, g++, and gfortran all support these arguments + args = compiler + ['-E', '-dM', '-'] + mlog.debug(f'Running command: {join_args(args)}') + p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE) + if p.returncode != 0: + raise EnvironmentException('Unable to detect GNU compiler type:\n' + f'Compiler stdout:\n{output}\n-----\n' + f'Compiler stderr:\n{error}\n-----\n') + # Parse several lines of the type: + # `#define ___SOME_DEF some_value` + # and extract `___SOME_DEF` + defines: T.Dict[str, str] = {} + for line in output.split('\n'): + if not line: + continue + d, *rest = line.split(' ', 2) + if d != '#define': + continue + if len(rest) == 1: + defines[rest[0]] = '' + if len(rest) == 2: + defines[rest[0]] = rest[1] + return defines + +def _get_clang_compiler_defines(compiler: T.List[str]) -> T.Dict[str, str]: + """ + Get the list of Clang pre-processor defines + """ + args = compiler + ['-E', '-dM', '-'] + mlog.debug(f'Running command: {join_args(args)}') + p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE) + if p.returncode != 0: + raise EnvironmentException('Unable to get clang pre-processor defines:\n' + f'Compiler stdout:\n{output}\n-----\n' + f'Compiler stderr:\n{error}\n-----\n') + defines: T.Dict[str, str] = {} + for line in output.split('\n'): + if not line: + continue + d, *rest = line.split(' ', 2) + if d != '#define': + continue + if len(rest) == 1: + defines[rest[0]] = '' + if len(rest) == 2: + defines[rest[0]] = rest[1] + return defines + +def _get_gnu_version_from_defines(defines: T.Dict[str, str]) -> str: + dot = '.' + major = defines.get('__GNUC__', '0') + minor = defines.get('__GNUC_MINOR__', '0') + patch = defines.get('__GNUC_PATCHLEVEL__', '0') + return dot.join((major, minor, patch)) + +def _get_lcc_version_from_defines(defines: T.Dict[str, str]) -> str: + dot = '.' + generation_and_major = defines.get('__LCC__', '100') + generation = generation_and_major[:1] + major = generation_and_major[1:] + minor = defines.get('__LCC_MINOR__', '0') + return dot.join((generation, major, minor)) diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py new file mode 100644 index 0000000..90ca010 --- /dev/null +++ b/mesonbuild/compilers/fortran.py @@ -0,0 +1,546 @@ +# Copyright 2012-2017 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 __future__ import annotations + +import typing as T +import os + +from .. import coredata +from .compilers import ( + clike_debug_args, + Compiler, +) +from .mixins.clike import CLikeCompiler +from .mixins.gnu import ( + GnuCompiler, gnulike_buildtype_args, gnu_optimization_args +) +from .mixins.intel import IntelGnuLikeCompiler, IntelVisualStudioLikeCompiler +from .mixins.clang import ClangCompiler +from .mixins.elbrus import ElbrusCompiler +from .mixins.pgi import PGICompiler + +from mesonbuild.mesonlib import ( + version_compare, MesonException, + LibType, OptionKey, +) + +if T.TYPE_CHECKING: + from ..coredata import MutableKeyedOptionDictType, KeyedOptionDictType + from ..dependencies import Dependency + from ..envconfig import MachineInfo + from ..environment import Environment + from ..linkers import DynamicLinker + from ..mesonlib import MachineChoice + from ..programs import ExternalProgram + from .compilers import CompileCheckMode + + +class FortranCompiler(CLikeCompiler, Compiler): + + language = 'fortran' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + Compiler.__init__(self, [], exelist, version, for_machine, info, + is_cross=is_cross, full_version=full_version, linker=linker) + CLikeCompiler.__init__(self, exe_wrapper) + + def has_function(self, funcname: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + raise MesonException('Fortran does not have "has_function" capability.\n' + 'It is better to test if a Fortran capability is working like:\n\n' + "meson.get_compiler('fortran').links('block; end block; end program')\n\n" + 'that example is to see if the compiler has Fortran 2008 Block element.') + + def _get_basic_compiler_args(self, env: 'Environment', mode: CompileCheckMode) -> T.Tuple[T.List[str], T.List[str]]: + cargs = env.coredata.get_external_args(self.for_machine, self.language) + largs = env.coredata.get_external_link_args(self.for_machine, self.language) + return cargs, largs + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + source_name = 'sanitycheckf.f90' + code = 'program main; print *, "Fortran compilation is working."; end program\n' + return self._sanity_check_impl(work_dir, environment, source_name, code) + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return gnulike_buildtype_args[buildtype] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return gnu_optimization_args[optimization_level] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return clike_debug_args[is_debug] + + def get_preprocess_only_args(self) -> T.List[str]: + return ['-cpp'] + super().get_preprocess_only_args() + + def get_module_incdir_args(self) -> T.Tuple[str, ...]: + return ('-I', ) + + def get_module_outdir_args(self, path: str) -> T.List[str]: + return ['-module', path] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:2] == '-I' or i[:2] == '-L': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + + return parameter_list + + def module_name_to_filename(self, module_name: str) -> str: + if '_' in module_name: # submodule + s = module_name.lower() + if self.id in {'gcc', 'intel', 'intel-cl'}: + filename = s.replace('_', '@') + '.smod' + elif self.id in {'pgi', 'flang'}: + filename = s.replace('_', '-') + '.mod' + else: + filename = s + '.mod' + else: # module + filename = module_name.lower() + '.mod' + + return filename + + def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], + libtype: LibType = LibType.PREFER_SHARED) -> T.Optional[T.List[str]]: + code = 'stop; end program' + return self._find_library_impl(libname, env, extra_dirs, code, libtype) + + def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: + return self._has_multi_arguments(args, env, 'stop; end program') + + def has_multi_link_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: + return self._has_multi_link_arguments(args, env, 'stop; end program') + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = super().get_options() + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts.update({ + key: coredata.UserComboOption( + 'Fortran language standard to use', + ['none'], + 'none', + ), + }) + return opts + + +class GnuFortranCompiler(GnuCompiler, FortranCompiler): + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + FortranCompiler.__init__(self, exelist, version, for_machine, + is_cross, info, exe_wrapper, linker=linker, + full_version=full_version) + GnuCompiler.__init__(self, defines) + default_warn_args = ['-Wall'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-Wpedantic', '-fimplicit-none'], + 'everything': default_warn_args + ['-Wextra', '-Wpedantic', '-fimplicit-none']} + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = FortranCompiler.get_options(self) + fortran_stds = ['legacy', 'f95', 'f2003'] + if version_compare(self.version, '>=4.4.0'): + fortran_stds += ['f2008'] + if version_compare(self.version, '>=8.0.0'): + fortran_stds += ['f2018'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none'] + fortran_stds + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value != 'none': + args.append('-std=' + std.value) + return args + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + # Disabled until this is fixed: + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=62162 + # return ['-cpp', '-MD', '-MQ', outtarget] + return [] + + def get_module_outdir_args(self, path: str) -> T.List[str]: + return ['-J' + path] + + def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]: + # We need to apply the search prefix here, as these link arguments may + # be passed to a different compiler with a different set of default + # search paths, such as when using Clang for C/C++ and gfortran for + # fortran, + search_dirs: T.List[str] = [] + for d in self.get_compiler_dirs(env, 'libraries'): + search_dirs.append(f'-L{d}') + return search_dirs + ['-lgfortran', '-lm'] + + def has_header(self, hname: str, prefix: str, env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None, + disable_cache: bool = False) -> T.Tuple[bool, bool]: + ''' + Derived from mixins/clike.py:has_header, but without C-style usage of + __has_include which breaks with GCC-Fortran 10: + https://github.com/mesonbuild/meson/issues/7017 + ''' + code = f'{prefix}\n#include <{hname}>' + return self.compiles(code, env, extra_args=extra_args, + dependencies=dependencies, mode='preprocess', disable_cache=disable_cache) + + +class ElbrusFortranCompiler(ElbrusCompiler, FortranCompiler): + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + FortranCompiler.__init__(self, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + ElbrusCompiler.__init__(self) + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = FortranCompiler.get_options(self) + fortran_stds = ['f95', 'f2003', 'f2008', 'gnu', 'legacy', 'f2008ts'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none'] + fortran_stds + return opts + + def get_module_outdir_args(self, path: str) -> T.List[str]: + return ['-J' + path] + + +class G95FortranCompiler(FortranCompiler): + + LINKER_PREFIX = '-Wl,' + id = 'g95' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + FortranCompiler.__init__(self, exelist, version, for_machine, + is_cross, info, exe_wrapper, linker=linker, + full_version=full_version) + default_warn_args = ['-Wall'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-pedantic'], + 'everything': default_warn_args + ['-Wextra', '-pedantic']} + + def get_module_outdir_args(self, path: str) -> T.List[str]: + return ['-fmod=' + path] + + def get_no_warn_args(self) -> T.List[str]: + # FIXME: Confirm that there's no compiler option to disable all warnings + return [] + + +class SunFortranCompiler(FortranCompiler): + + LINKER_PREFIX = '-Wl,' + id = 'sun' + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + return ['-fpp'] + + def get_always_args(self) -> T.List[str]: + return [] + + def get_warn_args(self, level: str) -> T.List[str]: + return [] + + def get_module_incdir_args(self) -> T.Tuple[str, ...]: + return ('-M', ) + + def get_module_outdir_args(self, path: str) -> T.List[str]: + return ['-moddir=' + path] + + def openmp_flags(self) -> T.List[str]: + return ['-xopenmp'] + + +class IntelFortranCompiler(IntelGnuLikeCompiler, FortranCompiler): + + file_suffixes = ('f90', 'f', 'for', 'ftn', 'fpp', ) + id = 'intel' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + FortranCompiler.__init__(self, exelist, version, for_machine, + is_cross, info, exe_wrapper, linker=linker, + full_version=full_version) + # FIXME: Add support for OS X and Windows in detect_fortran_compiler so + # we are sent the type of compiler + IntelGnuLikeCompiler.__init__(self) + default_warn_args = ['-warn', 'general', '-warn', 'truncated_source'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-warn', 'unused'], + '3': ['-warn', 'all'], + 'everything': ['-warn', 'all']} + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = FortranCompiler.get_options(self) + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018'] + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'} + if std.value != 'none': + args.append('-stand=' + stds[std.value]) + return args + + def get_preprocess_only_args(self) -> T.List[str]: + return ['-cpp', '-EP'] + + def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]: + # TODO: needs default search path added + return ['-lifcore', '-limf'] + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + return ['-gen-dep=' + outtarget, '-gen-depformat=make'] + + +class IntelLLVMFortranCompiler(IntelFortranCompiler): + + id = 'intel-llvm' + + +class IntelClFortranCompiler(IntelVisualStudioLikeCompiler, FortranCompiler): + + file_suffixes = ('f90', 'f', 'for', 'ftn', 'fpp', ) + always_args = ['/nologo'] + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', target: str, + exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + FortranCompiler.__init__(self, exelist, version, for_machine, + is_cross, info, exe_wrapper, linker=linker, + full_version=full_version) + IntelVisualStudioLikeCompiler.__init__(self, target) + + default_warn_args = ['/warn:general', '/warn:truncated_source'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['/warn:unused'], + '3': ['/warn:all'], + 'everything': ['/warn:all']} + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = FortranCompiler.get_options(self) + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018'] + return opts + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'} + if std.value != 'none': + args.append('/stand:' + stds[std.value]) + return args + + def get_module_outdir_args(self, path: str) -> T.List[str]: + return ['/module:' + path] + + +class IntelLLVMClFortranCompiler(IntelClFortranCompiler): + + id = 'intel-llvm-cl' + +class PathScaleFortranCompiler(FortranCompiler): + + id = 'pathscale' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + FortranCompiler.__init__(self, exelist, version, for_machine, + is_cross, info, exe_wrapper, linker=linker, + full_version=full_version) + default_warn_args = ['-fullwarn'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args, + '3': default_warn_args, + 'everything': default_warn_args} + + def openmp_flags(self) -> T.List[str]: + return ['-mp'] + + +class PGIFortranCompiler(PGICompiler, FortranCompiler): + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + FortranCompiler.__init__(self, exelist, version, for_machine, + is_cross, info, exe_wrapper, linker=linker, + full_version=full_version) + PGICompiler.__init__(self) + + default_warn_args = ['-Minform=inform'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args, + '3': default_warn_args + ['-Mdclchk'], + 'everything': default_warn_args + ['-Mdclchk']} + + def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]: + # TODO: needs default search path added + return ['-lpgf90rtl', '-lpgf90', '-lpgf90_rpm1', '-lpgf902', + '-lpgf90rtl', '-lpgftnrtl', '-lrt'] + + +class NvidiaHPC_FortranCompiler(PGICompiler, FortranCompiler): + + id = 'nvidia_hpc' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + FortranCompiler.__init__(self, exelist, version, for_machine, + is_cross, info, exe_wrapper, linker=linker, + full_version=full_version) + PGICompiler.__init__(self) + + default_warn_args = ['-Minform=inform'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args, + '3': default_warn_args + ['-Mdclchk'], + 'everything': default_warn_args + ['-Mdclchk']} + + +class FlangFortranCompiler(ClangCompiler, FortranCompiler): + + id = 'flang' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + FortranCompiler.__init__(self, exelist, version, for_machine, + is_cross, info, exe_wrapper, linker=linker, + full_version=full_version) + ClangCompiler.__init__(self, {}) + default_warn_args = ['-Minform=inform'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args, + '3': default_warn_args, + 'everything': default_warn_args} + + def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]: + # We need to apply the search prefix here, as these link arguments may + # be passed to a different compiler with a different set of default + # search paths, such as when using Clang for C/C++ and gfortran for + # fortran, + # XXX: Untested.... + search_dirs: T.List[str] = [] + for d in self.get_compiler_dirs(env, 'libraries'): + search_dirs.append(f'-L{d}') + return search_dirs + ['-lflang', '-lpgmath'] + +class ArmLtdFlangFortranCompiler(FlangFortranCompiler): + + id = 'armltdflang' + +class Open64FortranCompiler(FortranCompiler): + + id = 'open64' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + FortranCompiler.__init__(self, exelist, version, for_machine, + is_cross, info, exe_wrapper, linker=linker, + full_version=full_version) + default_warn_args = ['-fullwarn'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args, + '3': default_warn_args, + 'everything': default_warn_args} + + def openmp_flags(self) -> T.List[str]: + return ['-mp'] + + +class NAGFortranCompiler(FortranCompiler): + + id = 'nagfor' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + FortranCompiler.__init__(self, exelist, version, for_machine, + is_cross, info, exe_wrapper, linker=linker, + full_version=full_version) + # Warnings are on by default; -w disables (by category): + self.warn_args = { + '0': ['-w=all'], + '1': [], + '2': [], + '3': [], + 'everything': [], + } + + def get_always_args(self) -> T.List[str]: + return self.get_nagfor_quiet(self.version) + + def get_module_outdir_args(self, path: str) -> T.List[str]: + return ['-mdir', path] + + @staticmethod + def get_nagfor_quiet(version: str) -> T.List[str]: + return ['-quiet'] if version_compare(version, '>=7100') else [] + + def get_pic_args(self) -> T.List[str]: + return ['-PIC'] + + def get_preprocess_only_args(self) -> T.List[str]: + return ['-fpp'] + + def get_std_exe_link_args(self) -> T.List[str]: + return self.get_always_args() + + def openmp_flags(self) -> T.List[str]: + return ['-openmp'] diff --git a/mesonbuild/compilers/java.py b/mesonbuild/compilers/java.py new file mode 100644 index 0000000..ebae509 --- /dev/null +++ b/mesonbuild/compilers/java.py @@ -0,0 +1,125 @@ +# Copyright 2012-2017 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 __future__ import annotations + +import os +import os.path +import shutil +import subprocess +import textwrap +import typing as T + +from ..mesonlib import EnvironmentException +from .compilers import Compiler, java_buildtype_args +from .mixins.islinker import BasicLinkerIsCompilerMixin + +if T.TYPE_CHECKING: + from ..envconfig import MachineInfo + from ..environment import Environment + from ..mesonlib import MachineChoice + +class JavaCompiler(BasicLinkerIsCompilerMixin, Compiler): + + language = 'java' + id = 'unknown' + + _WARNING_LEVELS: T.Dict[str, T.List[str]] = { + '0': ['-nowarn'], + '1': ['-Xlint:all'], + '2': ['-Xlint:all', '-Xdoclint:all'], + '3': ['-Xlint:all', '-Xdoclint:all'], + } + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + info: 'MachineInfo', full_version: T.Optional[str] = None): + super().__init__([], exelist, version, for_machine, info, full_version=full_version) + self.javarunner = 'java' + + def get_warn_args(self, level: str) -> T.List[str]: + return self._WARNING_LEVELS[level] + + def get_werror_args(self) -> T.List[str]: + return ['-Werror'] + + def get_no_warn_args(self) -> T.List[str]: + return ['-nowarn'] + + def get_output_args(self, outputname: str) -> T.List[str]: + if outputname == '': + outputname = './' + return ['-d', outputname, '-s', outputname] + + def get_pic_args(self) -> T.List[str]: + return [] + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + return [] + + def get_pch_name(self, name: str) -> str: + return '' + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return java_buildtype_args[buildtype] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i in {'-cp', '-classpath', '-sourcepath'} and idx + 1 < len(parameter_list): + path_list = parameter_list[idx + 1].split(os.pathsep) + path_list = [os.path.normpath(os.path.join(build_dir, x)) for x in path_list] + parameter_list[idx + 1] = os.pathsep.join(path_list) + + return parameter_list + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + src = 'SanityCheck.java' + obj = 'SanityCheck' + source_name = os.path.join(work_dir, src) + with open(source_name, 'w', encoding='utf-8') as ofile: + ofile.write(textwrap.dedent( + '''class SanityCheck { + public static void main(String[] args) { + int i; + } + } + ''')) + pc = subprocess.Popen(self.exelist + [src], cwd=work_dir) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException(f'Java compiler {self.name_string()} can not compile programs.') + runner = shutil.which(self.javarunner) + if runner: + cmdlist = [runner, obj] + pe = subprocess.Popen(cmdlist, cwd=work_dir) + pe.wait() + if pe.returncode != 0: + raise EnvironmentException(f'Executables created by Java compiler {self.name_string()} are not runnable.') + else: + m = "Java Virtual Machine wasn't found, but it's needed by Meson. " \ + "Please install a JRE.\nIf you have specific needs where this " \ + "requirement doesn't make sense, please open a bug at " \ + "https://github.com/mesonbuild/meson/issues/new and tell us " \ + "all about it." + raise EnvironmentException(m) + + def needs_static_linker(self) -> bool: + return False + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return [] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + if is_debug: + return ['-g'] + return ['-g:none'] diff --git a/mesonbuild/compilers/mixins/__init__.py b/mesonbuild/compilers/mixins/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/mesonbuild/compilers/mixins/__init__.py diff --git a/mesonbuild/compilers/mixins/arm.py b/mesonbuild/compilers/mixins/arm.py new file mode 100644 index 0000000..5d0fb87 --- /dev/null +++ b/mesonbuild/compilers/mixins/arm.py @@ -0,0 +1,199 @@ +# Copyright 2012-2020 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 __future__ import annotations + +"""Representations specific to the arm family of compilers.""" + +import os +import typing as T + +from ... import mesonlib +from ...linkers import ArmClangDynamicLinker +from ...mesonlib import OptionKey +from ..compilers import clike_debug_args +from .clang import clang_color_args + +if T.TYPE_CHECKING: + from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object + +arm_buildtype_args = { + 'plain': [], + 'debug': [], + 'debugoptimized': [], + 'release': [], + 'minsize': [], + 'custom': [], +} # type: T.Dict[str, T.List[str]] + +arm_optimization_args = { + 'plain': [], + '0': ['-O0'], + 'g': ['-g'], + '1': ['-O1'], + '2': [], # Compiler defaults to -O2 + '3': ['-O3', '-Otime'], + 's': ['-O3'], # Compiler defaults to -Ospace +} # type: T.Dict[str, T.List[str]] + +armclang_buildtype_args = { + 'plain': [], + 'debug': [], + 'debugoptimized': [], + 'release': [], + 'minsize': [], + 'custom': [], +} # type: T.Dict[str, T.List[str]] + +armclang_optimization_args = { + 'plain': [], + '0': [], # Compiler defaults to -O0 + 'g': ['-g'], + '1': ['-O1'], + '2': ['-O2'], + '3': ['-O3'], + 's': ['-Oz'] +} # type: T.Dict[str, T.List[str]] + + +class ArmCompiler(Compiler): + + """Functionality that is common to all ARM family compilers.""" + + id = 'arm' + + def __init__(self) -> None: + if not self.is_cross: + raise mesonlib.EnvironmentException('armcc supports only cross-compilation.') + default_warn_args = [] # type: T.List[str] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + [], + '3': default_warn_args + [], + 'everything': default_warn_args + []} # type: T.Dict[str, T.List[str]] + # Assembly + self.can_compile_suffixes.add('s') + + def get_pic_args(self) -> T.List[str]: + # FIXME: Add /ropi, /rwpi, /fpic etc. qualifiers to --apcs + return [] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return arm_buildtype_args[buildtype] + + # Override CCompiler.get_always_args + def get_always_args(self) -> T.List[str]: + return [] + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + return ['--depend_target', outtarget, '--depend', outfile, '--depend_single_line'] + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + # FIXME: Add required arguments + # NOTE from armcc user guide: + # "Support for Precompiled Header (PCH) files is deprecated from ARM Compiler 5.05 + # onwards on all platforms. Note that ARM Compiler on Windows 8 never supported + # PCH files." + return [] + + def get_pch_suffix(self) -> str: + # NOTE from armcc user guide: + # "Support for Precompiled Header (PCH) files is deprecated from ARM Compiler 5.05 + # onwards on all platforms. Note that ARM Compiler on Windows 8 never supported + # PCH files." + return 'pch' + + def thread_flags(self, env: 'Environment') -> T.List[str]: + return [] + + def get_coverage_args(self) -> T.List[str]: + return [] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return arm_optimization_args[optimization_level] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return clike_debug_args[is_debug] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:2] == '-I' or i[:2] == '-L': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + + return parameter_list + + +class ArmclangCompiler(Compiler): + ''' + This is the Keil armclang. + ''' + + id = 'armclang' + + def __init__(self) -> None: + if not self.is_cross: + raise mesonlib.EnvironmentException('armclang supports only cross-compilation.') + # Check whether 'armlink' is available in path + if not isinstance(self.linker, ArmClangDynamicLinker): + raise mesonlib.EnvironmentException(f'Unsupported Linker {self.linker.exelist}, must be armlink') + if not mesonlib.version_compare(self.version, '==' + self.linker.version): + raise mesonlib.EnvironmentException('armlink version does not match with compiler version') + self.base_options = { + OptionKey(o) for o in + ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage', + 'b_ndebug', 'b_staticpic', 'b_colorout']} + # Assembly + self.can_compile_suffixes.add('s') + + def get_pic_args(self) -> T.List[str]: + # PIC support is not enabled by default for ARM, + # if users want to use it, they need to add the required arguments explicitly + return [] + + def get_colorout_args(self, colortype: str) -> T.List[str]: + return clang_color_args[colortype][:] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return armclang_buildtype_args[buildtype] + + def get_pch_suffix(self) -> str: + return 'gch' + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + # Workaround for Clang bug http://llvm.org/bugs/show_bug.cgi?id=15136 + # This flag is internal to Clang (or at least not documented on the man page) + # so it might change semantics at any time. + return ['-include-pch', os.path.join(pch_dir, self.get_pch_name(header))] + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + return ['-MD', '-MT', outtarget, '-MF', outfile] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return armclang_optimization_args[optimization_level] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return clike_debug_args[is_debug] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:2] == '-I' or i[:2] == '-L': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + + return parameter_list diff --git a/mesonbuild/compilers/mixins/ccrx.py b/mesonbuild/compilers/mixins/ccrx.py new file mode 100644 index 0000000..1c22214 --- /dev/null +++ b/mesonbuild/compilers/mixins/ccrx.py @@ -0,0 +1,134 @@ +# Copyright 2012-2019 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 __future__ import annotations + +"""Representations specific to the Renesas CC-RX compiler family.""" + +import os +import typing as T + +from ...mesonlib import EnvironmentException + +if T.TYPE_CHECKING: + from ...envconfig import MachineInfo + from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object + +ccrx_buildtype_args = { + 'plain': [], + 'debug': [], + 'debugoptimized': [], + 'release': [], + 'minsize': [], + 'custom': [], +} # type: T.Dict[str, T.List[str]] + +ccrx_optimization_args = { + '0': ['-optimize=0'], + 'g': ['-optimize=0'], + '1': ['-optimize=1'], + '2': ['-optimize=2'], + '3': ['-optimize=max'], + 's': ['-optimize=2', '-size'] +} # type: T.Dict[str, T.List[str]] + +ccrx_debug_args = { + False: [], + True: ['-debug'] +} # type: T.Dict[bool, T.List[str]] + + +class CcrxCompiler(Compiler): + + if T.TYPE_CHECKING: + is_cross = True + can_compile_suffixes = set() # type: T.Set[str] + + id = 'ccrx' + + def __init__(self) -> None: + if not self.is_cross: + raise EnvironmentException('ccrx supports only cross-compilation.') + # Assembly + self.can_compile_suffixes.add('src') + default_warn_args = [] # type: T.List[str] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + [], + '3': default_warn_args + [], + 'everything': default_warn_args + []} # type: T.Dict[str, T.List[str]] + + def get_pic_args(self) -> T.List[str]: + # PIC support is not enabled by default for CCRX, + # if users want to use it, they need to add the required arguments explicitly + return [] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return ccrx_buildtype_args[buildtype] + + def get_pch_suffix(self) -> str: + return 'pch' + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + return [] + + def thread_flags(self, env: 'Environment') -> T.List[str]: + return [] + + def get_coverage_args(self) -> T.List[str]: + return [] + + def get_no_stdinc_args(self) -> T.List[str]: + return [] + + def get_no_stdlib_link_args(self) -> T.List[str]: + return [] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return ccrx_optimization_args[optimization_level] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return ccrx_debug_args[is_debug] + + @classmethod + def _unix_args_to_native(cls, args: T.List[str], info: MachineInfo) -> T.List[str]: + result = [] + for i in args: + if i.startswith('-D'): + i = '-define=' + i[2:] + if i.startswith('-I'): + i = '-include=' + i[2:] + if i.startswith('-Wl,-rpath='): + continue + elif i == '--print-search-dirs': + continue + elif i.startswith('-L'): + continue + elif not i.startswith('-lib=') and i.endswith(('.a', '.lib')): + i = '-lib=' + i + result.append(i) + return result + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:9] == '-include=': + parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:])) + + return parameter_list diff --git a/mesonbuild/compilers/mixins/clang.py b/mesonbuild/compilers/mixins/clang.py new file mode 100644 index 0000000..cdb4c23 --- /dev/null +++ b/mesonbuild/compilers/mixins/clang.py @@ -0,0 +1,180 @@ +# Copyright 2019-2022 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 __future__ import annotations + +"""Abstractions for the LLVM/Clang compiler family.""" + +import os +import shutil +import typing as T + +from ... import mesonlib +from ...linkers import AppleDynamicLinker, ClangClDynamicLinker, LLVMDynamicLinker, GnuGoldDynamicLinker, \ + MoldDynamicLinker +from ...mesonlib import OptionKey +from ..compilers import CompileCheckMode +from .gnu import GnuLikeCompiler + +if T.TYPE_CHECKING: + from ...environment import Environment + from ...dependencies import Dependency # noqa: F401 + +clang_color_args = { + 'auto': ['-fcolor-diagnostics'], + 'always': ['-fcolor-diagnostics'], + 'never': ['-fno-color-diagnostics'], +} # type: T.Dict[str, T.List[str]] + +clang_optimization_args = { + 'plain': [], + '0': ['-O0'], + 'g': ['-Og'], + '1': ['-O1'], + '2': ['-O2'], + '3': ['-O3'], + 's': ['-Oz'], +} # type: T.Dict[str, T.List[str]] + +class ClangCompiler(GnuLikeCompiler): + + id = 'clang' + + def __init__(self, defines: T.Optional[T.Dict[str, str]]): + super().__init__() + self.defines = defines or {} + self.base_options.update( + {OptionKey('b_colorout'), OptionKey('b_lto_threads'), OptionKey('b_lto_mode'), OptionKey('b_thinlto_cache'), + OptionKey('b_thinlto_cache_dir')}) + + # TODO: this really should be part of the linker base_options, but + # linkers don't have base_options. + if isinstance(self.linker, AppleDynamicLinker): + self.base_options.add(OptionKey('b_bitcode')) + # All Clang backends can also do LLVM IR + self.can_compile_suffixes.add('ll') + + def get_colorout_args(self, colortype: str) -> T.List[str]: + return clang_color_args[colortype][:] + + def has_builtin_define(self, define: str) -> bool: + return define in self.defines + + def get_builtin_define(self, define: str) -> T.Optional[str]: + return self.defines.get(define) + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return clang_optimization_args[optimization_level] + + def get_pch_suffix(self) -> str: + return 'pch' + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + # Workaround for Clang bug http://llvm.org/bugs/show_bug.cgi?id=15136 + # This flag is internal to Clang (or at least not documented on the man page) + # so it might change semantics at any time. + return ['-include-pch', os.path.join(pch_dir, self.get_pch_name(header))] + + def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: + # Clang is different than GCC, it will return True when a symbol isn't + # defined in a header. Specifically this seems to have something to do + # with functions that may be in a header on some systems, but not all of + # them. `strlcat` specifically with can trigger this. + myargs: T.List[str] = ['-Werror=implicit-function-declaration'] + if mode is CompileCheckMode.COMPILE: + myargs.extend(['-Werror=unknown-warning-option', '-Werror=unused-command-line-argument']) + if mesonlib.version_compare(self.version, '>=3.6.0'): + myargs.append('-Werror=ignored-optimization-argument') + return super().get_compiler_check_args(mode) + myargs + + def has_function(self, funcname: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + if extra_args is None: + extra_args = [] + # Starting with XCode 8, we need to pass this to force linker + # visibility to obey OS X/iOS/tvOS minimum version targets with + # -mmacosx-version-min, -miphoneos-version-min, -mtvos-version-min etc. + # https://github.com/Homebrew/homebrew-core/issues/3727 + # TODO: this really should be communicated by the linker + if isinstance(self.linker, AppleDynamicLinker) and mesonlib.version_compare(self.version, '>=8.0'): + extra_args.append('-Wl,-no_weak_imports') + return super().has_function(funcname, prefix, env, extra_args=extra_args, + dependencies=dependencies) + + def openmp_flags(self) -> T.List[str]: + if mesonlib.version_compare(self.version, '>=3.8.0'): + return ['-fopenmp'] + elif mesonlib.version_compare(self.version, '>=3.7.0'): + return ['-fopenmp=libomp'] + else: + # Shouldn't work, but it'll be checked explicitly in the OpenMP dependency. + return [] + + @classmethod + def use_linker_args(cls, linker: str, version: str) -> T.List[str]: + # Clang additionally can use a linker specified as a path, which GCC + # (and other gcc-like compilers) cannot. This is because clang (being + # llvm based) is retargetable, while GCC is not. + # + + # qcld: Qualcomm Snapdragon linker, based on LLVM + if linker == 'qcld': + return ['-fuse-ld=qcld'] + if linker == 'mold': + return ['-fuse-ld=mold'] + + if shutil.which(linker): + if not shutil.which(linker): + raise mesonlib.MesonException( + f'Cannot find linker {linker}.') + return [f'-fuse-ld={linker}'] + return super().use_linker_args(linker, version) + + def get_has_func_attribute_extra_args(self, name: str) -> T.List[str]: + # Clang only warns about unknown or ignored attributes, so force an + # error. + return ['-Werror=attributes'] + + def get_coverage_link_args(self) -> T.List[str]: + return ['--coverage'] + + def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]: + args: T.List[str] = [] + if mode == 'thin': + # ThinLTO requires the use of gold, lld, ld64, lld-link or mold 1.1+ + if isinstance(self.linker, (MoldDynamicLinker)): + # https://github.com/rui314/mold/commit/46995bcfc3e3113133620bf16445c5f13cd76a18 + if not mesonlib.version_compare(self.linker.version, '>=1.1'): + raise mesonlib.MesonException("LLVM's ThinLTO requires mold 1.1+") + elif not isinstance(self.linker, (AppleDynamicLinker, ClangClDynamicLinker, LLVMDynamicLinker, GnuGoldDynamicLinker)): + raise mesonlib.MesonException(f"LLVM's ThinLTO only works with gold, lld, lld-link, ld64 or mold, not {self.linker.id}") + args.append(f'-flto={mode}') + else: + assert mode == 'default', 'someone forgot to wire something up' + args.extend(super().get_lto_compile_args(threads=threads)) + return args + + def get_lto_link_args(self, *, threads: int = 0, mode: str = 'default', + thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: + args = self.get_lto_compile_args(threads=threads, mode=mode) + if mode == 'thin' and thinlto_cache_dir is not None: + # We check for ThinLTO linker support above in get_lto_compile_args, and all of them support + # get_thinlto_cache_args as well + args.extend(self.linker.get_thinlto_cache_args(thinlto_cache_dir)) + # In clang -flto-jobs=0 means auto, and is the default if unspecified, just like in meson + if threads > 0: + if not mesonlib.version_compare(self.version, '>=4.0.0'): + raise mesonlib.MesonException('clang support for LTO threads requires clang >=4.0') + args.append(f'-flto-jobs={threads}') + return args diff --git a/mesonbuild/compilers/mixins/clike.py b/mesonbuild/compilers/mixins/clike.py new file mode 100644 index 0000000..d44dd4d --- /dev/null +++ b/mesonbuild/compilers/mixins/clike.py @@ -0,0 +1,1349 @@ +# Copyright 2012-2022 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 __future__ import annotations + + +"""Mixin classes to be shared between C and C++ compilers. + +Without this we'll end up with awful diamond inherintance problems. The goal +of this is to have mixin's, which are classes that are designed *not* to be +standalone, they only work through inheritance. +""" + +import collections +import functools +import glob +import itertools +import os +import re +import subprocess +import copy +import typing as T +from pathlib import Path + +from ... import arglist +from ... import mesonlib +from ... import mlog +from ...linkers import GnuLikeDynamicLinkerMixin, SolarisDynamicLinker, CompCertDynamicLinker +from ...mesonlib import LibType +from ...coredata import OptionKey +from .. import compilers +from ..compilers import CompileCheckMode +from .visualstudio import VisualStudioLikeCompiler + +if T.TYPE_CHECKING: + from ...dependencies import Dependency + from ..._typing import ImmutableListProtocol + from ...environment import Environment + from ...compilers.compilers import Compiler + from ...programs import ExternalProgram +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object + +GROUP_FLAGS = re.compile(r'''\.so (?:\.[0-9]+)? (?:\.[0-9]+)? (?:\.[0-9]+)?$ | + ^(?:-Wl,)?-l | + \.a$''', re.X) + +class CLikeCompilerArgs(arglist.CompilerArgs): + prepend_prefixes = ('-I', '-L') + dedup2_prefixes = ('-I', '-isystem', '-L', '-D', '-U') + + # NOTE: not thorough. A list of potential corner cases can be found in + # https://github.com/mesonbuild/meson/pull/4593#pullrequestreview-182016038 + dedup1_prefixes = ('-l', '-Wl,-l', '-Wl,--export-dynamic') + dedup1_suffixes = ('.lib', '.dll', '.so', '.dylib', '.a') + dedup1_args = ('-c', '-S', '-E', '-pipe', '-pthread') + + def to_native(self, copy: bool = False) -> T.List[str]: + # This seems to be allowed, but could never work? + assert isinstance(self.compiler, compilers.Compiler), 'How did you get here' + + # Check if we need to add --start/end-group for circular dependencies + # between static libraries, and for recursively searching for symbols + # needed by static libraries that are provided by object files or + # shared libraries. + self.flush_pre_post() + if copy: + new = self.copy() + else: + new = self + # This covers all ld.bfd, ld.gold, ld.gold, and xild on Linux, which + # all act like (or are) gnu ld + # TODO: this could probably be added to the DynamicLinker instead + if isinstance(self.compiler.linker, (GnuLikeDynamicLinkerMixin, SolarisDynamicLinker, CompCertDynamicLinker)): + group_start = -1 + group_end = -1 + for i, each in enumerate(new): + if not GROUP_FLAGS.search(each): + continue + group_end = i + if group_start < 0: + # First occurrence of a library + group_start = i + if group_start >= 0: + # Last occurrence of a library + new.insert(group_end + 1, '-Wl,--end-group') + new.insert(group_start, '-Wl,--start-group') + # Remove system/default include paths added with -isystem + default_dirs = self.compiler.get_default_include_dirs() + if default_dirs: + real_default_dirs = [self._cached_realpath(i) for i in default_dirs] + bad_idx_list = [] # type: T.List[int] + for i, each in enumerate(new): + if not each.startswith('-isystem'): + continue + + # Remove the -isystem and the path if the path is a default path + if (each == '-isystem' and + i < (len(new) - 1) and + self._cached_realpath(new[i + 1]) in real_default_dirs): + bad_idx_list += [i, i + 1] + elif each.startswith('-isystem=') and self._cached_realpath(each[9:]) in real_default_dirs: + bad_idx_list += [i] + elif self._cached_realpath(each[8:]) in real_default_dirs: + bad_idx_list += [i] + for i in reversed(bad_idx_list): + new.pop(i) + return self.compiler.unix_args_to_native(new._container) + + @staticmethod + @functools.lru_cache(maxsize=None) + def _cached_realpath(arg: str) -> str: + return os.path.realpath(arg) + + def __repr__(self) -> str: + self.flush_pre_post() + return f'CLikeCompilerArgs({self.compiler!r}, {self._container!r})' + + +class CLikeCompiler(Compiler): + + """Shared bits for the C and CPP Compilers.""" + + if T.TYPE_CHECKING: + warn_args = {} # type: T.Dict[str, T.List[str]] + + # TODO: Replace this manual cache with functools.lru_cache + find_library_cache = {} # type: T.Dict[T.Tuple[T.Tuple[str, ...], str, T.Tuple[str, ...], str, LibType], T.Optional[T.List[str]]] + find_framework_cache = {} # type: T.Dict[T.Tuple[T.Tuple[str, ...], str, T.Tuple[str, ...], bool], T.Optional[T.List[str]]] + internal_libs = arglist.UNIXY_COMPILER_INTERNAL_LIBS + + def __init__(self, exe_wrapper: T.Optional['ExternalProgram'] = None): + # If a child ObjC or CPP class has already set it, don't set it ourselves + self.can_compile_suffixes.add('h') + # If the exe wrapper was not found, pretend it wasn't set so that the + # sanity check is skipped and compiler checks use fallbacks. + if not exe_wrapper or not exe_wrapper.found() or not exe_wrapper.get_command(): + self.exe_wrapper = None + else: + self.exe_wrapper = exe_wrapper + # Lazy initialized in get_preprocessor() + self.preprocessor: T.Optional[Compiler] = None + + def compiler_args(self, args: T.Optional[T.Iterable[str]] = None) -> CLikeCompilerArgs: + # This is correct, mypy just doesn't understand co-operative inheritance + return CLikeCompilerArgs(self, args) + + def needs_static_linker(self) -> bool: + return True # When compiling static libraries, so yes. + + def get_always_args(self) -> T.List[str]: + ''' + Args that are always-on for all C compilers other than MSVC + ''' + return self.get_largefile_args() + + def get_no_stdinc_args(self) -> T.List[str]: + return ['-nostdinc'] + + def get_no_stdlib_link_args(self) -> T.List[str]: + return ['-nostdlib'] + + def get_warn_args(self, level: str) -> T.List[str]: + # TODO: this should be an enum + return self.warn_args[level] + + def get_no_warn_args(self) -> T.List[str]: + # Almost every compiler uses this for disabling warnings + return ['-w'] + + def get_depfile_suffix(self) -> str: + return 'd' + + def get_preprocess_only_args(self) -> T.List[str]: + return ['-E', '-P'] + + def get_compile_only_args(self) -> T.List[str]: + return ['-c'] + + def get_no_optimization_args(self) -> T.List[str]: + return ['-O0'] + + def get_output_args(self, outputname: str) -> T.List[str]: + return ['-o', outputname] + + def get_werror_args(self) -> T.List[str]: + return ['-Werror'] + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + if path == '': + path = '.' + if is_system: + return ['-isystem', path] + return ['-I' + path] + + def get_compiler_dirs(self, env: 'Environment', name: str) -> T.List[str]: + ''' + Get dirs from the compiler, either `libraries:` or `programs:` + ''' + return [] + + @functools.lru_cache() + def _get_library_dirs(self, env: 'Environment', + elf_class: T.Optional[int] = None) -> 'ImmutableListProtocol[str]': + # TODO: replace elf_class with enum + dirs = self.get_compiler_dirs(env, 'libraries') + if elf_class is None or elf_class == 0: + return dirs + + # if we do have an elf class for 32-bit or 64-bit, we want to check that + # the directory in question contains libraries of the appropriate class. Since + # system directories aren't mixed, we only need to check one file for each + # directory and go by that. If we can't check the file for some reason, assume + # the compiler knows what it's doing, and accept the directory anyway. + retval = [] + for d in dirs: + files = [f for f in os.listdir(d) if f.endswith('.so') and os.path.isfile(os.path.join(d, f))] + # if no files, accept directory and move on + if not files: + retval.append(d) + continue + + for f in files: + file_to_check = os.path.join(d, f) + try: + with open(file_to_check, 'rb') as fd: + header = fd.read(5) + # if file is not an ELF file, it's weird, but accept dir + # if it is elf, and the class matches, accept dir + if header[1:4] != b'ELF' or int(header[4]) == elf_class: + retval.append(d) + # at this point, it's an ELF file which doesn't match the + # appropriate elf_class, so skip this one + # stop scanning after the first successful read + break + except OSError: + # Skip the file if we can't read it + pass + + return retval + + def get_library_dirs(self, env: 'Environment', + elf_class: T.Optional[int] = None) -> T.List[str]: + """Wrap the lru_cache so that we return a new copy and don't allow + mutation of the cached value. + """ + return self._get_library_dirs(env, elf_class).copy() + + @functools.lru_cache() + def _get_program_dirs(self, env: 'Environment') -> 'ImmutableListProtocol[str]': + ''' + Programs used by the compiler. Also where toolchain DLLs such as + libstdc++-6.dll are found with MinGW. + ''' + return self.get_compiler_dirs(env, 'programs') + + def get_program_dirs(self, env: 'Environment') -> T.List[str]: + return self._get_program_dirs(env).copy() + + def get_pic_args(self) -> T.List[str]: + return ['-fPIC'] + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + return ['-include', os.path.basename(header)] + + def get_pch_name(self, name: str) -> str: + return os.path.basename(name) + '.' + self.get_pch_suffix() + + def get_default_include_dirs(self) -> T.List[str]: + return [] + + def gen_export_dynamic_link_args(self, env: 'Environment') -> T.List[str]: + return self.linker.export_dynamic_args(env) + + def gen_import_library_args(self, implibname: str) -> T.List[str]: + return self.linker.import_library_args(implibname) + + def _sanity_check_impl(self, work_dir: str, environment: 'Environment', + sname: str, code: str) -> None: + mlog.debug('Sanity testing ' + self.get_display_language() + ' compiler:', mesonlib.join_args(self.exelist)) + mlog.debug(f'Is cross compiler: {self.is_cross!s}.') + + source_name = os.path.join(work_dir, sname) + binname = sname.rsplit('.', 1)[0] + mode = CompileCheckMode.LINK + if self.is_cross: + binname += '_cross' + if self.exe_wrapper is None: + # Linking cross built C/C++ apps is painful. You can't really + # tell if you should use -nostdlib or not and for example + # on OSX the compiler binary is the same but you need + # a ton of compiler flags to differentiate between + # arm and x86_64. So just compile. + mode = CompileCheckMode.COMPILE + cargs, largs = self._get_basic_compiler_args(environment, mode) + extra_flags = cargs + self.linker_to_compiler_args(largs) + + # Is a valid executable output for all toolchains and platforms + binname += '.exe' + # Write binary check source + binary_name = os.path.join(work_dir, binname) + with open(source_name, 'w', encoding='utf-8') as ofile: + ofile.write(code) + # Compile sanity check + # NOTE: extra_flags must be added at the end. On MSVC, it might contain a '/link' argument + # after which all further arguments will be passed directly to the linker + cmdlist = self.exelist + [sname] + self.get_output_args(binname) + extra_flags + pc, stdo, stde = mesonlib.Popen_safe(cmdlist, cwd=work_dir) + mlog.debug('Sanity check compiler command line:', mesonlib.join_args(cmdlist)) + mlog.debug('Sanity check compile stdout:') + mlog.debug(stdo) + mlog.debug('-----\nSanity check compile stderr:') + mlog.debug(stde) + mlog.debug('-----') + if pc.returncode != 0: + raise mesonlib.EnvironmentException(f'Compiler {self.name_string()} can not compile programs.') + # Run sanity check + if self.is_cross: + if self.exe_wrapper is None: + # Can't check if the binaries run so we have to assume they do + return + cmdlist = self.exe_wrapper.get_command() + [binary_name] + else: + cmdlist = [binary_name] + mlog.debug('Running test binary command: ', mesonlib.join_args(cmdlist)) + try: + # fortran code writes to stdout + pe = subprocess.run(cmdlist, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except Exception as e: + raise mesonlib.EnvironmentException(f'Could not invoke sanity test executable: {e!s}.') + if pe.returncode != 0: + raise mesonlib.EnvironmentException(f'Executables created by {self.language} compiler {self.name_string()} are not runnable.') + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + code = 'int main(void) { int class=0; return class; }\n' + return self._sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) + + def check_header(self, hname: str, prefix: str, env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + code = f'''{prefix} + #include <{hname}>''' + return self.compiles(code, env, extra_args=extra_args, + dependencies=dependencies) + + def has_header(self, hname: str, prefix: str, env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None, + disable_cache: bool = False) -> T.Tuple[bool, bool]: + code = f'''{prefix} + #ifdef __has_include + #if !__has_include("{hname}") + #error "Header '{hname}' could not be found" + #endif + #else + #include <{hname}> + #endif''' + return self.compiles(code, env, extra_args=extra_args, + dependencies=dependencies, mode='preprocess', disable_cache=disable_cache) + + def has_header_symbol(self, hname: str, symbol: str, prefix: str, + env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + t = f'''{prefix} + #include <{hname}> + int main(void) {{ + /* If it's not defined as a macro, try to use as a symbol */ + #ifndef {symbol} + {symbol}; + #endif + return 0; + }}''' + return self.compiles(t, env, extra_args=extra_args, + dependencies=dependencies) + + def _get_basic_compiler_args(self, env: 'Environment', mode: CompileCheckMode) -> T.Tuple[T.List[str], T.List[str]]: + cargs = [] # type: T.List[str] + largs = [] # type: T.List[str] + if mode is CompileCheckMode.LINK: + # Sometimes we need to manually select the CRT to use with MSVC. + # One example is when trying to do a compiler check that involves + # linking with static libraries since MSVC won't select a CRT for + # us in that case and will error out asking us to pick one. + try: + crt_val = env.coredata.options[OptionKey('b_vscrt')].value + buildtype = env.coredata.options[OptionKey('buildtype')].value + cargs += self.get_crt_compile_args(crt_val, buildtype) + except (KeyError, AttributeError): + pass + + # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS and CPPFLAGS from the env + sys_args = env.coredata.get_external_args(self.for_machine, self.language) + if isinstance(sys_args, str): + sys_args = [sys_args] + # Apparently it is a thing to inject linker flags both + # via CFLAGS _and_ LDFLAGS, even though the former are + # also used during linking. These flags can break + # argument checks. Thanks, Autotools. + cleaned_sys_args = self.remove_linkerlike_args(sys_args) + cargs += cleaned_sys_args + + if mode is CompileCheckMode.LINK: + ld_value = env.lookup_binary_entry(self.for_machine, self.language + '_ld') + if ld_value is not None: + largs += self.use_linker_args(ld_value[0], self.version) + + # Add LDFLAGS from the env + sys_ld_args = env.coredata.get_external_link_args(self.for_machine, self.language) + # CFLAGS and CXXFLAGS go to both linking and compiling, but we want them + # to only appear on the command line once. Remove dupes. + largs += [x for x in sys_ld_args if x not in sys_args] + + cargs += self.get_compiler_args_for_mode(mode) + return cargs, largs + + def build_wrapper_args(self, env: 'Environment', + extra_args: T.Union[None, arglist.CompilerArgs, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], + dependencies: T.Optional[T.List['Dependency']], + mode: CompileCheckMode = CompileCheckMode.COMPILE) -> arglist.CompilerArgs: + # TODO: the caller should handle the listfing of these arguments + if extra_args is None: + extra_args = [] + else: + # TODO: we want to do this in the caller + extra_args = mesonlib.listify(extra_args) + extra_args = mesonlib.listify([e(mode.value) if callable(e) else e for e in extra_args]) + + if dependencies is None: + dependencies = [] + elif not isinstance(dependencies, collections.abc.Iterable): + # TODO: we want to ensure the front end does the listifing here + dependencies = [dependencies] + # Collect compiler arguments + cargs = self.compiler_args() # type: arglist.CompilerArgs + largs = [] # type: T.List[str] + for d in dependencies: + # Add compile flags needed by dependencies + cargs += d.get_compile_args() + if mode is CompileCheckMode.LINK: + # Add link flags needed to find dependencies + largs += d.get_link_args() + + ca, la = self._get_basic_compiler_args(env, mode) + cargs += ca + largs += la + + cargs += self.get_compiler_check_args(mode) + + # on MSVC compiler and linker flags must be separated by the "/link" argument + # at this point, the '/link' argument may already be part of extra_args, otherwise, it is added here + if self.linker_to_compiler_args([]) == ['/link'] and largs != [] and '/link' not in extra_args: + extra_args += ['/link'] + + args = cargs + extra_args + largs + return args + + def run(self, code: 'mesonlib.FileOrString', env: 'Environment', *, + extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]], None] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> compilers.RunResult: + need_exe_wrapper = env.need_exe_wrapper(self.for_machine) + if need_exe_wrapper and self.exe_wrapper is None: + raise compilers.CrossNoRunException('Can not run test applications in this cross environment.') + with self._build_wrapper(code, env, extra_args, dependencies, mode='link', want_output=True) as p: + if p.returncode != 0: + mlog.debug(f'Could not compile test file {p.input_name}: {p.returncode}\n') + return compilers.RunResult(False) + if need_exe_wrapper: + cmdlist = self.exe_wrapper.get_command() + [p.output_name] + else: + cmdlist = [p.output_name] + try: + pe, so, se = mesonlib.Popen_safe(cmdlist) + except Exception as e: + mlog.debug(f'Could not run: {cmdlist} (error: {e})\n') + return compilers.RunResult(False) + + mlog.debug('Program stdout:\n') + mlog.debug(so) + mlog.debug('Program stderr:\n') + mlog.debug(se) + return compilers.RunResult(True, pe.returncode, so, se) + + def _compile_int(self, expression: str, prefix: str, env: 'Environment', + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], + dependencies: T.Optional[T.List['Dependency']]) -> bool: + t = f'''#include <stdio.h> + {prefix} + int main(void) {{ static int a[1-2*!({expression})]; a[0]=0; return 0; }}''' + return self.compiles(t, env, extra_args=extra_args, + dependencies=dependencies)[0] + + def cross_compute_int(self, expression: str, low: T.Optional[int], high: T.Optional[int], + guess: T.Optional[int], prefix: str, env: 'Environment', + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: + # Try user's guess first + if isinstance(guess, int): + if self._compile_int(f'{expression} == {guess}', prefix, env, extra_args, dependencies): + return guess + + # If no bounds are given, compute them in the limit of int32 + maxint = 0x7fffffff + minint = -0x80000000 + if not isinstance(low, int) or not isinstance(high, int): + if self._compile_int(f'{expression} >= 0', prefix, env, extra_args, dependencies): + low = cur = 0 + while self._compile_int(f'{expression} > {cur}', prefix, env, extra_args, dependencies): + low = cur + 1 + if low > maxint: + raise mesonlib.EnvironmentException('Cross-compile check overflowed') + cur = min(cur * 2 + 1, maxint) + high = cur + else: + high = cur = -1 + while self._compile_int(f'{expression} < {cur}', prefix, env, extra_args, dependencies): + high = cur - 1 + if high < minint: + raise mesonlib.EnvironmentException('Cross-compile check overflowed') + cur = max(cur * 2, minint) + low = cur + else: + # Sanity check limits given by user + if high < low: + raise mesonlib.EnvironmentException('high limit smaller than low limit') + condition = f'{expression} <= {high} && {expression} >= {low}' + if not self._compile_int(condition, prefix, env, extra_args, dependencies): + raise mesonlib.EnvironmentException('Value out of given range') + + # Binary search + while low != high: + cur = low + int((high - low) / 2) + if self._compile_int(f'{expression} <= {cur}', prefix, env, extra_args, dependencies): + high = cur + else: + low = cur + 1 + + return low + + def compute_int(self, expression: str, low: T.Optional[int], high: T.Optional[int], + guess: T.Optional[int], prefix: str, env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], + dependencies: T.Optional[T.List['Dependency']] = None) -> int: + if extra_args is None: + extra_args = [] + if self.is_cross: + return self.cross_compute_int(expression, low, high, guess, prefix, env, extra_args, dependencies) + t = f'''#include<stdio.h> + {prefix} + int main(void) {{ + printf("%ld\\n", (long)({expression})); + return 0; + }}''' + res = self.run(t, env, extra_args=extra_args, + dependencies=dependencies) + if not res.compiled: + return -1 + if res.returncode != 0: + raise mesonlib.EnvironmentException('Could not run compute_int test binary.') + return int(res.stdout) + + def cross_sizeof(self, typename: str, prefix: str, env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: + if extra_args is None: + extra_args = [] + t = f'''#include <stdio.h> + {prefix} + int main(void) {{ + {typename} something; + return 0; + }}''' + if not self.compiles(t, env, extra_args=extra_args, + dependencies=dependencies)[0]: + return -1 + return self.cross_compute_int(f'sizeof({typename})', None, None, None, prefix, env, extra_args, dependencies) + + def sizeof(self, typename: str, prefix: str, env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: + if extra_args is None: + extra_args = [] + if self.is_cross: + return self.cross_sizeof(typename, prefix, env, extra_args=extra_args, + dependencies=dependencies) + t = f'''#include<stdio.h> + {prefix} + int main(void) {{ + printf("%ld\\n", (long)(sizeof({typename}))); + return 0; + }}''' + res = self.run(t, env, extra_args=extra_args, + dependencies=dependencies) + if not res.compiled: + return -1 + if res.returncode != 0: + raise mesonlib.EnvironmentException('Could not run sizeof test binary.') + return int(res.stdout) + + def cross_alignment(self, typename: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: + if extra_args is None: + extra_args = [] + t = f'''#include <stdio.h> + {prefix} + int main(void) {{ + {typename} something; + return 0; + }}''' + if not self.compiles(t, env, extra_args=extra_args, + dependencies=dependencies)[0]: + return -1 + t = f'''#include <stddef.h> + {prefix} + struct tmp {{ + char c; + {typename} target; + }};''' + return self.cross_compute_int('offsetof(struct tmp, target)', None, None, None, t, env, extra_args, dependencies) + + def alignment(self, typename: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: + if extra_args is None: + extra_args = [] + if self.is_cross: + return self.cross_alignment(typename, prefix, env, extra_args=extra_args, + dependencies=dependencies) + t = f'''#include <stdio.h> + #include <stddef.h> + {prefix} + struct tmp {{ + char c; + {typename} target; + }}; + int main(void) {{ + printf("%d", (int)offsetof(struct tmp, target)); + return 0; + }}''' + res = self.run(t, env, extra_args=extra_args, + dependencies=dependencies) + if not res.compiled: + raise mesonlib.EnvironmentException('Could not compile alignment test.') + if res.returncode != 0: + raise mesonlib.EnvironmentException('Could not run alignment test binary.') + align = int(res.stdout) + if align == 0: + raise mesonlib.EnvironmentException(f'Could not determine alignment of {typename}. Sorry. You might want to file a bug.') + return align + + def get_define(self, dname: str, prefix: str, env: 'Environment', + extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], + dependencies: T.Optional[T.List['Dependency']], + disable_cache: bool = False) -> T.Tuple[str, bool]: + delim = '"MESON_GET_DEFINE_DELIMITER"' + code = f''' + {prefix} + #ifndef {dname} + # define {dname} + #endif + {delim}\n{dname}''' + args = self.build_wrapper_args(env, extra_args, dependencies, + mode=CompileCheckMode.PREPROCESS).to_native() + func = functools.partial(self.cached_compile, code, env.coredata, extra_args=args, mode='preprocess') + if disable_cache: + func = functools.partial(self.compile, code, extra_args=args, mode='preprocess', temp_dir=env.scratch_dir) + with func() as p: + cached = p.cached + if p.returncode != 0: + raise mesonlib.EnvironmentException(f'Could not get define {dname!r}') + # Get the preprocessed value after the delimiter, + # minus the extra newline at the end and + # merge string literals. + return self._concatenate_string_literals(p.stdout.split(delim + '\n')[-1][:-1]), cached + + def get_return_value(self, fname: str, rtype: str, prefix: str, + env: 'Environment', extra_args: T.Optional[T.List[str]], + dependencies: T.Optional[T.List['Dependency']]) -> T.Union[str, int]: + # TODO: rtype should be an enum. + # TODO: maybe we can use overload to tell mypy when this will return int vs str? + if rtype == 'string': + fmt = '%s' + cast = '(char*)' + elif rtype == 'int': + fmt = '%lli' + cast = '(long long int)' + else: + raise AssertionError(f'BUG: Unknown return type {rtype!r}') + code = f'''{prefix} + #include <stdio.h> + int main(void) {{ + printf ("{fmt}", {cast} {fname}()); + return 0; + }}''' + res = self.run(code, env, extra_args=extra_args, dependencies=dependencies) + if not res.compiled: + raise mesonlib.EnvironmentException(f'Could not get return value of {fname}()') + if rtype == 'string': + return res.stdout + elif rtype == 'int': + try: + return int(res.stdout.strip()) + except ValueError: + raise mesonlib.EnvironmentException(f'Return value of {fname}() is not an int') + assert False, 'Unreachable' + + @staticmethod + def _no_prototype_templ() -> T.Tuple[str, str]: + """ + Try to find the function without a prototype from a header by defining + our own dummy prototype and trying to link with the C library (and + whatever else the compiler links in by default). This is very similar + to the check performed by Autoconf for AC_CHECK_FUNCS. + """ + # Define the symbol to something else since it is defined by the + # includes or defines listed by the user or by the compiler. This may + # include, for instance _GNU_SOURCE which must be defined before + # limits.h, which includes features.h + # Then, undef the symbol to get rid of it completely. + head = ''' + #define {func} meson_disable_define_of_{func} + {prefix} + #include <limits.h> + #undef {func} + ''' + # Override any GCC internal prototype and declare our own definition for + # the symbol. Use char because that's unlikely to be an actual return + # value for a function which ensures that we override the definition. + head += ''' + #ifdef __cplusplus + extern "C" + #endif + char {func} (void); + ''' + # The actual function call + main = ''' + int main(void) {{ + return {func} (); + }}''' + return head, main + + @staticmethod + def _have_prototype_templ() -> T.Tuple[str, str]: + """ + Returns a head-er and main() call that uses the headers listed by the + user for the function prototype while checking if a function exists. + """ + # Add the 'prefix', aka defines, includes, etc that the user provides + # This may include, for instance _GNU_SOURCE which must be defined + # before limits.h, which includes features.h + head = '{prefix}\n#include <limits.h>\n' + # We don't know what the function takes or returns, so return it as an int. + # Just taking the address or comparing it to void is not enough because + # compilers are smart enough to optimize it away. The resulting binary + # is not run so we don't care what the return value is. + main = '''\nint main(void) {{ + void *a = (void*) &{func}; + long long b = (long long) a; + return (int) b; + }}''' + return head, main + + def has_function(self, funcname: str, prefix: str, env: 'Environment', *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + """Determine if a function exists. + + First, this function looks for the symbol in the default libraries + provided by the compiler (stdlib + a few others usually). If that + fails, it checks if any of the headers specified in the prefix provide + an implementation of the function, and if that fails, it checks if it's + implemented as a compiler-builtin. + """ + if extra_args is None: + extra_args = [] + + # Short-circuit if the check is already provided by the cross-info file + varname = 'has function ' + funcname + varname = varname.replace(' ', '_') + if self.is_cross: + val = env.properties.host.get(varname, None) + if val is not None: + if isinstance(val, bool): + return val, False + raise mesonlib.EnvironmentException(f'Cross variable {varname} is not a boolean.') + + # TODO: we really need a protocol for this, + # + # class StrProto(typing.Protocol): + # def __str__(self) -> str: ... + fargs = {'prefix': prefix, 'func': funcname} # type: T.Dict[str, T.Union[str, bool, int]] + + # glibc defines functions that are not available on Linux as stubs that + # fail with ENOSYS (such as e.g. lchmod). In this case we want to fail + # instead of detecting the stub as a valid symbol. + # We already included limits.h earlier to ensure that these are defined + # for stub functions. + stubs_fail = ''' + #if defined __stub_{func} || defined __stub___{func} + fail fail fail this function is not going to work + #endif + ''' + + # If we have any includes in the prefix supplied by the user, assume + # that the user wants us to use the symbol prototype defined in those + # includes. If not, then try to do the Autoconf-style check with + # a dummy prototype definition of our own. + # This is needed when the linker determines symbol availability from an + # SDK based on the prototype in the header provided by the SDK. + # Ignoring this prototype would result in the symbol always being + # marked as available. + if '#include' in prefix: + head, main = self._have_prototype_templ() + else: + head, main = self._no_prototype_templ() + templ = head + stubs_fail + main + + res, cached = self.links(templ.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies) + if res: + return True, cached + + # MSVC does not have compiler __builtin_-s. + if self.get_id() in {'msvc', 'intel-cl'}: + return False, False + + # Detect function as a built-in + # + # Some functions like alloca() are defined as compiler built-ins which + # are inlined by the compiler and you can't take their address, so we + # need to look for them differently. On nice compilers like clang, we + # can just directly use the __has_builtin() macro. + fargs['no_includes'] = '#include' not in prefix + is_builtin = funcname.startswith('__builtin_') + fargs['is_builtin'] = is_builtin + fargs['__builtin_'] = '' if is_builtin else '__builtin_' + t = '''{prefix} + int main(void) {{ + + /* With some toolchains (MSYS2/mingw for example) the compiler + * provides various builtins which are not really implemented and + * fall back to the stdlib where they aren't provided and fail at + * build/link time. In case the user provides a header, including + * the header didn't lead to the function being defined, and the + * function we are checking isn't a builtin itself we assume the + * builtin is not functional and we just error out. */ + #if !{no_includes:d} && !defined({func}) && !{is_builtin:d} + #error "No definition for {__builtin_}{func} found in the prefix" + #endif + + #ifdef __has_builtin + #if !__has_builtin({__builtin_}{func}) + #error "{__builtin_}{func} not found" + #endif + #elif ! defined({func}) + {__builtin_}{func}; + #endif + return 0; + }}''' + return self.links(t.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies) + + def has_members(self, typename: str, membernames: T.List[str], + prefix: str, env: 'Environment', *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + if extra_args is None: + extra_args = [] + # Create code that accesses all members + members = ''.join(f'foo.{member};\n' for member in membernames) + t = f'''{prefix} + void bar(void) {{ + {typename} foo; + {members} + }}''' + return self.compiles(t, env, extra_args=extra_args, + dependencies=dependencies) + + def has_type(self, typename: str, prefix: str, env: 'Environment', + extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], *, + dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: + t = f'''{prefix} + void bar(void) {{ + sizeof({typename}); + }}''' + return self.compiles(t, env, extra_args=extra_args, + dependencies=dependencies) + + def _symbols_have_underscore_prefix_searchbin(self, env: 'Environment') -> bool: + ''' + Check if symbols have underscore prefix by compiling a small test binary + and then searching the binary for the string, + ''' + symbol_name = b'meson_uscore_prefix' + code = '''#ifdef __cplusplus + extern "C" { + #endif + void ''' + symbol_name.decode() + ''' (void) {} + #ifdef __cplusplus + } + #endif + ''' + args = self.get_compiler_check_args(CompileCheckMode.COMPILE) + n = '_symbols_have_underscore_prefix_searchbin' + with self._build_wrapper(code, env, extra_args=args, mode='compile', want_output=True, temp_dir=env.scratch_dir) as p: + if p.returncode != 0: + raise RuntimeError(f'BUG: Unable to compile {n!r} check: {p.stderr}') + if not os.path.isfile(p.output_name): + raise RuntimeError(f'BUG: Can\'t find compiled test code for {n!r} check') + with open(p.output_name, 'rb') as o: + for line in o: + # Check if the underscore form of the symbol is somewhere + # in the output file. + if b'_' + symbol_name in line: + mlog.debug("Underscore prefix check found prefixed function in binary") + return True + # Else, check if the non-underscored form is present + elif symbol_name in line: + mlog.debug("Underscore prefix check found non-prefixed function in binary") + return False + raise RuntimeError(f'BUG: {n!r} check did not find symbol string in binary') + + def _symbols_have_underscore_prefix_define(self, env: 'Environment') -> T.Optional[bool]: + ''' + Check if symbols have underscore prefix by querying the + __USER_LABEL_PREFIX__ define that most compilers provide + for this. Return if functions have underscore prefix or None + if it was not possible to determine, like when the compiler + does not set the define or the define has an unexpected value. + ''' + delim = '"MESON_HAVE_UNDERSCORE_DELIMITER" ' + code = f''' + #ifndef __USER_LABEL_PREFIX__ + #define MESON_UNDERSCORE_PREFIX unsupported + #else + #define MESON_UNDERSCORE_PREFIX __USER_LABEL_PREFIX__ + #endif + {delim}MESON_UNDERSCORE_PREFIX + ''' + with self._build_wrapper(code, env, mode='preprocess', want_output=False, temp_dir=env.scratch_dir) as p: + if p.returncode != 0: + raise RuntimeError(f'BUG: Unable to preprocess _symbols_have_underscore_prefix_define check: {p.stdout}') + symbol_prefix = p.stdout.partition(delim)[-1].rstrip() + + mlog.debug(f'Queried compiler for function prefix: __USER_LABEL_PREFIX__ is "{symbol_prefix!s}"') + if symbol_prefix == '_': + return True + elif symbol_prefix == '': + return False + else: + return None + + def _symbols_have_underscore_prefix_list(self, env: 'Environment') -> T.Optional[bool]: + ''' + Check if symbols have underscore prefix by consulting a hardcoded + list of cases where we know the results. + Return if functions have underscore prefix or None if unknown. + ''' + m = env.machines[self.for_machine] + # Darwin always uses the underscore prefix, not matter what + if m.is_darwin(): + return True + # Windows uses the underscore prefix on x86 (32bit) only + if m.is_windows() or m.is_cygwin(): + return m.cpu_family == 'x86' + return None + + def symbols_have_underscore_prefix(self, env: 'Environment') -> bool: + ''' + Check if the compiler prefixes an underscore to global C symbols + ''' + # First, try to query the compiler directly + result = self._symbols_have_underscore_prefix_define(env) + if result is not None: + return result + + # Else, try to consult a hardcoded list of cases we know + # absolutely have an underscore prefix + result = self._symbols_have_underscore_prefix_list(env) + if result is not None: + return result + + # As a last resort, try search in a compiled binary, which is the + # most unreliable way of checking this, see #5482 + return self._symbols_have_underscore_prefix_searchbin(env) + + def _get_patterns(self, env: 'Environment', prefixes: T.List[str], suffixes: T.List[str], shared: bool = False) -> T.List[str]: + patterns = [] # type: T.List[str] + for p in prefixes: + for s in suffixes: + patterns.append(p + '{}.' + s) + if shared and env.machines[self.for_machine].is_openbsd(): + # Shared libraries on OpenBSD can be named libfoo.so.X.Y: + # https://www.openbsd.org/faq/ports/specialtopics.html#SharedLibs + # + # This globbing is probably the best matching we can do since regex + # is expensive. It's wrong in many edge cases, but it will match + # correctly-named libraries and hopefully no one on OpenBSD names + # their files libfoo.so.9a.7b.1.0 + for p in prefixes: + patterns.append(p + '{}.so.[0-9]*.[0-9]*') + return patterns + + def get_library_naming(self, env: 'Environment', libtype: LibType, strict: bool = False) -> T.Tuple[str, ...]: + ''' + Get library prefixes and suffixes for the target platform ordered by + priority + ''' + stlibext = ['a'] + # We've always allowed libname to be both `foo` and `libfoo`, and now + # people depend on it. Also, some people use prebuilt `foo.so` instead + # of `libfoo.so` for unknown reasons, and may also want to create + # `foo.so` by setting name_prefix to '' + if strict and not isinstance(self, VisualStudioLikeCompiler): # lib prefix is not usually used with msvc + prefixes = ['lib'] + else: + prefixes = ['lib', ''] + # Library suffixes and prefixes + if env.machines[self.for_machine].is_darwin(): + shlibext = ['dylib', 'so'] + elif env.machines[self.for_machine].is_windows(): + # FIXME: .lib files can be import or static so we should read the + # file, figure out which one it is, and reject the wrong kind. + if isinstance(self, VisualStudioLikeCompiler): + shlibext = ['lib'] + else: + shlibext = ['dll.a', 'lib', 'dll'] + # Yep, static libraries can also be foo.lib + stlibext += ['lib'] + elif env.machines[self.for_machine].is_cygwin(): + shlibext = ['dll', 'dll.a'] + prefixes = ['cyg'] + prefixes + else: + # Linux/BSDs + shlibext = ['so'] + # Search priority + if libtype is LibType.PREFER_SHARED: + patterns = self._get_patterns(env, prefixes, shlibext, True) + patterns.extend([x for x in self._get_patterns(env, prefixes, stlibext, False) if x not in patterns]) + elif libtype is LibType.PREFER_STATIC: + patterns = self._get_patterns(env, prefixes, stlibext, False) + patterns.extend([x for x in self._get_patterns(env, prefixes, shlibext, True) if x not in patterns]) + elif libtype is LibType.SHARED: + patterns = self._get_patterns(env, prefixes, shlibext, True) + else: + assert libtype is LibType.STATIC + patterns = self._get_patterns(env, prefixes, stlibext, False) + return tuple(patterns) + + @staticmethod + def _sort_shlibs_openbsd(libs: T.List[str]) -> T.List[str]: + filtered = [] # type: T.List[str] + for lib in libs: + # Validate file as a shared library of type libfoo.so.X.Y + ret = lib.rsplit('.so.', maxsplit=1) + if len(ret) != 2: + continue + try: + float(ret[1]) + except ValueError: + continue + filtered.append(lib) + float_cmp = lambda x: float(x.rsplit('.so.', maxsplit=1)[1]) + return sorted(filtered, key=float_cmp, reverse=True) + + @classmethod + def _get_trials_from_pattern(cls, pattern: str, directory: str, libname: str) -> T.List[Path]: + f = Path(directory) / pattern.format(libname) + # Globbing for OpenBSD + if '*' in pattern: + # NOTE: globbing matches directories and broken symlinks + # so we have to do an isfile test on it later + return [Path(x) for x in cls._sort_shlibs_openbsd(glob.glob(str(f)))] + return [f] + + @staticmethod + def _get_file_from_list(env: 'Environment', paths: T.List[Path]) -> Path: + ''' + We just check whether the library exists. We can't do a link check + because the library might have unresolved symbols that require other + libraries. On macOS we check if the library matches our target + architecture. + ''' + # If not building on macOS for Darwin, do a simple file check + if not env.machines.host.is_darwin() or not env.machines.build.is_darwin(): + for p in paths: + if p.is_file(): + return p + # Run `lipo` and check if the library supports the arch we want + for p in paths: + if not p.is_file(): + continue + archs = mesonlib.darwin_get_object_archs(str(p)) + if archs and env.machines.host.cpu_family in archs: + return p + else: + mlog.debug(f'Rejected {p}, supports {archs} but need {env.machines.host.cpu_family}') + return None + + @functools.lru_cache() + def output_is_64bit(self, env: 'Environment') -> bool: + ''' + returns true if the output produced is 64-bit, false if 32-bit + ''' + return self.sizeof('void *', '', env) == 8 + + def _find_library_real(self, libname: str, env: 'Environment', extra_dirs: T.List[str], code: str, libtype: LibType) -> T.Optional[T.List[str]]: + # First try if we can just add the library as -l. + # Gcc + co seem to prefer builtin lib dirs to -L dirs. + # Only try to find std libs if no extra dirs specified. + # The built-in search procedure will always favour .so and then always + # search for .a. This is only allowed if libtype is LibType.PREFER_SHARED + if ((not extra_dirs and libtype is LibType.PREFER_SHARED) or + libname in self.internal_libs): + cargs = ['-l' + libname] + largs = self.get_linker_always_args() + self.get_allow_undefined_link_args() + extra_args = cargs + self.linker_to_compiler_args(largs) + + if self.links(code, env, extra_args=extra_args, disable_cache=True)[0]: + return cargs + # Don't do a manual search for internal libs + if libname in self.internal_libs: + return None + # Not found or we want to use a specific libtype? Try to find the + # library file itself. + patterns = self.get_library_naming(env, libtype) + # try to detect if we are 64-bit or 32-bit. If we can't + # detect, we will just skip path validity checks done in + # get_library_dirs() call + try: + if self.output_is_64bit(env): + elf_class = 2 + else: + elf_class = 1 + except (mesonlib.MesonException, KeyError): # TODO evaluate if catching KeyError is wanted here + elf_class = 0 + # Search in the specified dirs, and then in the system libraries + for d in itertools.chain(extra_dirs, self.get_library_dirs(env, elf_class)): + for p in patterns: + trials = self._get_trials_from_pattern(p, d, libname) + if not trials: + continue + trial = self._get_file_from_list(env, trials) + if not trial: + continue + if libname.startswith('lib') and trial.name.startswith(libname): + mlog.warning(f'find_library({libname!r}) starting in "lib" only works by accident and is not portable') + return [trial.as_posix()] + return None + + def _find_library_impl(self, libname: str, env: 'Environment', extra_dirs: T.List[str], + code: str, libtype: LibType) -> T.Optional[T.List[str]]: + # These libraries are either built-in or invalid + if libname in self.ignore_libs: + return [] + if isinstance(extra_dirs, str): + extra_dirs = [extra_dirs] + key = (tuple(self.exelist), libname, tuple(extra_dirs), code, libtype) + if key not in self.find_library_cache: + value = self._find_library_real(libname, env, extra_dirs, code, libtype) + self.find_library_cache[key] = value + else: + value = self.find_library_cache[key] + if value is None: + return None + return value.copy() + + def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], + libtype: LibType = LibType.PREFER_SHARED) -> T.Optional[T.List[str]]: + code = 'int main(void) { return 0; }\n' + return self._find_library_impl(libname, env, extra_dirs, code, libtype) + + def find_framework_paths(self, env: 'Environment') -> T.List[str]: + ''' + These are usually /Library/Frameworks and /System/Library/Frameworks, + unless you select a particular macOS SDK with the -isysroot flag. + You can also add to this by setting -F in CFLAGS. + ''' + # TODO: this really needs to be *AppleClang*, not just any clang. + if self.id != 'clang': + raise mesonlib.MesonException('Cannot find framework path with non-clang compiler') + # Construct the compiler command-line + commands = self.get_exelist(ccache=False) + ['-v', '-E', '-'] + commands += self.get_always_args() + # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env + commands += env.coredata.get_external_args(self.for_machine, self.language) + mlog.debug('Finding framework path by running: ', ' '.join(commands), '\n') + os_env = os.environ.copy() + os_env['LC_ALL'] = 'C' + _, _, stde = mesonlib.Popen_safe(commands, env=os_env, stdin=subprocess.PIPE) + paths = [] # T.List[str] + for line in stde.split('\n'): + if '(framework directory)' not in line: + continue + # line is of the form: + # ` /path/to/framework (framework directory)` + paths.append(line[:-21].strip()) + return paths + + def _find_framework_real(self, name: str, env: 'Environment', extra_dirs: T.List[str], allow_system: bool) -> T.Optional[T.List[str]]: + code = 'int main(void) { return 0; }' + link_args = [] + for d in extra_dirs: + link_args += ['-F' + d] + # We can pass -Z to disable searching in the system frameworks, but + # then we must also pass -L/usr/lib to pick up libSystem.dylib + extra_args = [] if allow_system else ['-Z', '-L/usr/lib'] + link_args += ['-framework', name] + if self.links(code, env, extra_args=(extra_args + link_args), disable_cache=True)[0]: + return link_args + return None + + def _find_framework_impl(self, name: str, env: 'Environment', extra_dirs: T.List[str], + allow_system: bool) -> T.Optional[T.List[str]]: + if isinstance(extra_dirs, str): + extra_dirs = [extra_dirs] + key = (tuple(self.exelist), name, tuple(extra_dirs), allow_system) + if key in self.find_framework_cache: + value = self.find_framework_cache[key] + else: + value = self._find_framework_real(name, env, extra_dirs, allow_system) + self.find_framework_cache[key] = value + if value is None: + return None + return value.copy() + + def find_framework(self, name: str, env: 'Environment', extra_dirs: T.List[str], + allow_system: bool = True) -> T.Optional[T.List[str]]: + ''' + Finds the framework with the specified name, and returns link args for + the same or returns None when the framework is not found. + ''' + # TODO: should probably check for macOS? + return self._find_framework_impl(name, env, extra_dirs, allow_system) + + def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: + # TODO: does this belong here or in GnuLike or maybe PosixLike? + return [] + + def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]: + # TODO: does this belong here or in GnuLike or maybe PosixLike? + return [] + + def thread_flags(self, env: 'Environment') -> T.List[str]: + # TODO: does this belong here or in GnuLike or maybe PosixLike? + host_m = env.machines[self.for_machine] + if host_m.is_haiku() or host_m.is_darwin(): + return [] + return ['-pthread'] + + def linker_to_compiler_args(self, args: T.List[str]) -> T.List[str]: + return args.copy() + + def has_arguments(self, args: T.List[str], env: 'Environment', code: str, + mode: str) -> T.Tuple[bool, bool]: + return self.compiles(code, env, extra_args=args, mode=mode) + + def _has_multi_arguments(self, args: T.List[str], env: 'Environment', code: str) -> T.Tuple[bool, bool]: + new_args = [] # type: T.List[str] + for arg in args: + # some compilers, e.g. GCC, don't warn for unsupported warning-disable + # flags, so when we are testing a flag like "-Wno-forgotten-towel", also + # check the equivalent enable flag too "-Wforgotten-towel" + if arg.startswith('-Wno-'): + new_args.append('-W' + arg[5:]) + if arg.startswith('-Wl,'): + mlog.warning(f'{arg} looks like a linker argument, ' + 'but has_argument and other similar methods only ' + 'support checking compiler arguments. Using them ' + 'to check linker arguments are never supported, ' + 'and results are likely to be wrong regardless of ' + 'the compiler you are using. has_link_argument or ' + 'other similar method can be used instead.') + new_args.append(arg) + return self.has_arguments(new_args, env, code, mode='compile') + + def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: + return self._has_multi_arguments(args, env, 'extern int i;\nint i;\n') + + def _has_multi_link_arguments(self, args: T.List[str], env: 'Environment', code: str) -> T.Tuple[bool, bool]: + # First time we check for link flags we need to first check if we have + # --fatal-warnings, otherwise some linker checks could give some + # false positive. + args = self.linker.fatal_warnings() + args + args = self.linker_to_compiler_args(args) + return self.has_arguments(args, env, code, mode='link') + + def has_multi_link_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: + return self._has_multi_link_arguments(args, env, 'int main(void) { return 0; }\n') + + @staticmethod + def _concatenate_string_literals(s: str) -> str: + pattern = re.compile(r'(?P<pre>.*([^\\]")|^")(?P<str1>([^\\"]|\\.)*)"\s+"(?P<str2>([^\\"]|\\.)*)(?P<post>".*)') + ret = s + m = pattern.match(ret) + while m: + ret = ''.join(m.group('pre', 'str1', 'str2', 'post')) + m = pattern.match(ret) + return ret + + def get_has_func_attribute_extra_args(self, name: str) -> T.List[str]: + # Most compilers (such as GCC and Clang) only warn about unknown or + # ignored attributes, so force an error. Overridden in GCC and Clang + # mixins. + return ['-Werror'] + + def has_func_attribute(self, name: str, env: 'Environment') -> T.Tuple[bool, bool]: + # Just assume that if we're not on windows that dllimport and dllexport + # don't work + m = env.machines[self.for_machine] + if not (m.is_windows() or m.is_cygwin()): + if name in {'dllimport', 'dllexport'}: + return False, False + + return self.compiles(self.attribute_check_func(name), env, + extra_args=self.get_has_func_attribute_extra_args(name)) + + def get_disable_assert_args(self) -> T.List[str]: + return ['-DNDEBUG'] + + @functools.lru_cache(maxsize=None) + def can_compile(self, src: 'mesonlib.FileOrString') -> bool: + # Files we preprocess can be anything, e.g. .in + if self.mode == 'PREPROCESSOR': + return True + return super().can_compile(src) + + def get_preprocessor(self) -> Compiler: + if not self.preprocessor: + self.preprocessor = copy.copy(self) + self.preprocessor.exelist = self.exelist + self.get_preprocess_to_file_args() + self.preprocessor.mode = 'PREPROCESSOR' + self.modes.append(self.preprocessor) + return self.preprocessor diff --git a/mesonbuild/compilers/mixins/compcert.py b/mesonbuild/compilers/mixins/compcert.py new file mode 100644 index 0000000..a0394a9 --- /dev/null +++ b/mesonbuild/compilers/mixins/compcert.py @@ -0,0 +1,137 @@ +# Copyright 2012-2019 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 __future__ import annotations + +"""Representations specific to the CompCert C compiler family.""" + +import os +import re +import typing as T + +if T.TYPE_CHECKING: + from envconfig import MachineInfo + from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object + +ccomp_buildtype_args = { + 'plain': [''], + 'debug': ['-O0', '-g'], + 'debugoptimized': ['-O0', '-g'], + 'release': ['-O3'], + 'minsize': ['-Os'], + 'custom': ['-Obranchless'], +} # type: T.Dict[str, T.List[str]] + +ccomp_optimization_args = { + 'plain': [], + '0': ['-O0'], + 'g': ['-O0'], + '1': ['-O1'], + '2': ['-O2'], + '3': ['-O3'], + 's': ['-Os'] +} # type: T.Dict[str, T.List[str]] + +ccomp_debug_args = { + False: [], + True: ['-g'] +} # type: T.Dict[bool, T.List[str]] + +# As of CompCert 20.04, these arguments should be passed to the underlying gcc linker (via -WUl,<arg>) +# There are probably (many) more, but these are those used by picolibc +ccomp_args_to_wul = [ + r"^-ffreestanding$", + r"^-r$" +] # type: T.List[str] + +class CompCertCompiler(Compiler): + + id = 'ccomp' + + def __init__(self) -> None: + # Assembly + self.can_compile_suffixes.add('s') + default_warn_args = [] # type: T.List[str] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + [], + '3': default_warn_args + [], + 'everything': default_warn_args + []} # type: T.Dict[str, T.List[str]] + + def get_always_args(self) -> T.List[str]: + return [] + + def get_pic_args(self) -> T.List[str]: + # As of now, CompCert does not support PIC + return [] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return ccomp_buildtype_args[buildtype] + + def get_pch_suffix(self) -> str: + return 'pch' + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + return [] + + @classmethod + def _unix_args_to_native(cls, args: T.List[str], info: MachineInfo) -> T.List[str]: + "Always returns a copy that can be independently mutated" + patched_args = [] # type: T.List[str] + for arg in args: + added = 0 + for ptrn in ccomp_args_to_wul: + if re.match(ptrn, arg): + patched_args.append('-WUl,' + arg) + added = 1 + if not added: + patched_args.append(arg) + return patched_args + + def thread_flags(self, env: 'Environment') -> T.List[str]: + return [] + + def get_preprocess_only_args(self) -> T.List[str]: + return ['-E'] + + def get_compile_only_args(self) -> T.List[str]: + return ['-c'] + + def get_coverage_args(self) -> T.List[str]: + return [] + + def get_no_stdinc_args(self) -> T.List[str]: + return ['-nostdinc'] + + def get_no_stdlib_link_args(self) -> T.List[str]: + return ['-nostdlib'] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return ccomp_optimization_args[optimization_level] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return ccomp_debug_args[is_debug] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:9] == '-I': + parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:])) + + return parameter_list diff --git a/mesonbuild/compilers/mixins/elbrus.py b/mesonbuild/compilers/mixins/elbrus.py new file mode 100644 index 0000000..872649b --- /dev/null +++ b/mesonbuild/compilers/mixins/elbrus.py @@ -0,0 +1,101 @@ +# Copyright 2019 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 __future__ import annotations + +"""Abstractions for the Elbrus family of compilers.""" + +import os +import typing as T +import subprocess +import re + +from .gnu import GnuLikeCompiler +from .gnu import gnu_optimization_args +from ...mesonlib import Popen_safe, OptionKey + +if T.TYPE_CHECKING: + from ...environment import Environment + from ...coredata import KeyedOptionDictType + + +class ElbrusCompiler(GnuLikeCompiler): + # Elbrus compiler is nearly like GCC, but does not support + # PCH, LTO, sanitizers and color output as of version 1.21.x. + + id = 'lcc' + + def __init__(self) -> None: + super().__init__() + self.base_options = {OptionKey(o) for o in ['b_pgo', 'b_coverage', 'b_ndebug', 'b_staticpic', 'b_lundef', 'b_asneeded']} + default_warn_args = ['-Wall'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-Wpedantic'], + 'everything': default_warn_args + ['-Wextra', '-Wpedantic']} + + # FIXME: use _build_wrapper to call this so that linker flags from the env + # get applied + def get_library_dirs(self, env: 'Environment', elf_class: T.Optional[int] = None) -> T.List[str]: + os_env = os.environ.copy() + os_env['LC_ALL'] = 'C' + stdo = Popen_safe(self.get_exelist(ccache=False) + ['--print-search-dirs'], env=os_env)[1] + for line in stdo.split('\n'): + if line.startswith('libraries:'): + # lcc does not include '=' in --print-search-dirs output. Also it could show nonexistent dirs. + libstr = line.split(' ', 1)[1] + return [os.path.realpath(p) for p in libstr.split(':') if os.path.exists(p)] + return [] + + def get_program_dirs(self, env: 'Environment') -> T.List[str]: + os_env = os.environ.copy() + os_env['LC_ALL'] = 'C' + stdo = Popen_safe(self.get_exelist(ccache=False) + ['--print-search-dirs'], env=os_env)[1] + for line in stdo.split('\n'): + if line.startswith('programs:'): + # lcc does not include '=' in --print-search-dirs output. + libstr = line.split(' ', 1)[1] + return [os.path.realpath(p) for p in libstr.split(':')] + return [] + + def get_default_include_dirs(self) -> T.List[str]: + os_env = os.environ.copy() + os_env['LC_ALL'] = 'C' + p = subprocess.Popen(self.get_exelist(ccache=False) + ['-xc', '-E', '-v', '-'], env=os_env, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stderr = p.stderr.read().decode('utf-8', errors='replace') + includes = [] + for line in stderr.split('\n'): + if line.lstrip().startswith('--sys_include'): + includes.append(re.sub(r'\s*\\$', '', re.sub(r'^\s*--sys_include\s*', '', line))) + return includes + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return gnu_optimization_args[optimization_level] + + def get_prelink_args(self, prelink_name: str, obj_list: T.List[str]) -> T.List[str]: + return ['-r', '-nodefaultlibs', '-nostartfiles', '-o', prelink_name] + obj_list + + def get_pch_suffix(self) -> str: + # Actually it's not supported for now, but probably will be supported in future + return 'pch' + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + std = options[OptionKey('std', lang=self.language, machine=self.for_machine)] + if std.value != 'none': + args.append('-std=' + std.value) + return args + + def openmp_flags(self) -> T.List[str]: + return ['-fopenmp'] diff --git a/mesonbuild/compilers/mixins/emscripten.py b/mesonbuild/compilers/mixins/emscripten.py new file mode 100644 index 0000000..77b1c33 --- /dev/null +++ b/mesonbuild/compilers/mixins/emscripten.py @@ -0,0 +1,101 @@ +# Copyright 2019 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 __future__ import annotations + +"""Provides a mixin for shared code between C and C++ Emscripten compilers.""" + +import os.path +import typing as T + +from ... import coredata +from ... import mesonlib +from ...mesonlib import OptionKey +from ...mesonlib import LibType + +if T.TYPE_CHECKING: + from ...environment import Environment + from ...compilers.compilers import Compiler + from ...dependencies import Dependency +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object + + +def wrap_js_includes(args: T.List[str]) -> T.List[str]: + final_args = [] + for i in args: + if i.endswith('.js') and not i.startswith('-'): + final_args += ['--js-library', i] + else: + final_args += [i] + return final_args + +class EmscriptenMixin(Compiler): + + def _get_compile_output(self, dirname: str, mode: str) -> str: + # In pre-processor mode, the output is sent to stdout and discarded + if mode == 'preprocess': + return None + # Unlike sane toolchains, emcc infers the kind of output from its name. + # This is the only reason why this method is overridden; compiler tests + # do not work well with the default exe/obj suffices. + if mode == 'link': + suffix = 'js' + else: + suffix = 'o' + return os.path.join(dirname, 'output.' + suffix) + + def thread_link_flags(self, env: 'Environment') -> T.List[str]: + args = ['-pthread'] + count: int = env.coredata.options[OptionKey('thread_count', lang=self.language, machine=self.for_machine)].value + if count: + args.append(f'-sPTHREAD_POOL_SIZE={count}') + return args + + def get_options(self) -> 'coredata.MutableKeyedOptionDictType': + opts = super().get_options() + key = OptionKey('thread_count', machine=self.for_machine, lang=self.language) + opts.update({ + key: coredata.UserIntegerOption( + 'Number of threads to use in web assembly, set to 0 to disable', + (0, None, 4), # Default was picked at random + ), + }) + + return opts + + @classmethod + def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]: + return wrap_js_includes(super().native_args_to_unix(args)) + + def get_dependency_link_args(self, dep: 'Dependency') -> T.List[str]: + return wrap_js_includes(super().get_dependency_link_args(dep)) + + def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], + libtype: LibType = LibType.PREFER_SHARED) -> T.Optional[T.List[str]]: + if not libname.endswith('.js'): + return super().find_library(libname, env, extra_dirs, libtype) + if os.path.isabs(libname): + if os.path.exists(libname): + return [libname] + if len(extra_dirs) == 0: + raise mesonlib.EnvironmentException('Looking up Emscripten JS libraries requires either an absolute path or specifying extra_dirs.') + for d in extra_dirs: + abs_path = os.path.join(d, libname) + if os.path.exists(abs_path): + return [abs_path] + return None diff --git a/mesonbuild/compilers/mixins/gnu.py b/mesonbuild/compilers/mixins/gnu.py new file mode 100644 index 0000000..022e7fd --- /dev/null +++ b/mesonbuild/compilers/mixins/gnu.py @@ -0,0 +1,649 @@ +# Copyright 2019-2022 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 __future__ import annotations + +"""Provides mixins for GNU compilers and GNU-like compilers.""" + +import abc +import functools +import os +import multiprocessing +import pathlib +import re +import subprocess +import typing as T + +from ... import mesonlib +from ... import mlog +from ...mesonlib import OptionKey + +if T.TYPE_CHECKING: + from ..._typing import ImmutableListProtocol + from ...environment import Environment + from ..compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object + +# XXX: prevent circular references. +# FIXME: this really is a posix interface not a c-like interface +clike_debug_args = { + False: [], + True: ['-g'], +} # type: T.Dict[bool, T.List[str]] + +gnulike_buildtype_args = { + 'plain': [], + 'debug': [], + 'debugoptimized': [], + 'release': [], + 'minsize': [], + 'custom': [], +} # type: T.Dict[str, T.List[str]] + +gnu_optimization_args = { + 'plain': [], + '0': ['-O0'], + 'g': ['-Og'], + '1': ['-O1'], + '2': ['-O2'], + '3': ['-O3'], + 's': ['-Os'], +} # type: T.Dict[str, T.List[str]] + +gnulike_instruction_set_args = { + 'mmx': ['-mmmx'], + 'sse': ['-msse'], + 'sse2': ['-msse2'], + 'sse3': ['-msse3'], + 'ssse3': ['-mssse3'], + 'sse41': ['-msse4.1'], + 'sse42': ['-msse4.2'], + 'avx': ['-mavx'], + 'avx2': ['-mavx2'], + 'neon': ['-mfpu=neon'], +} # type: T.Dict[str, T.List[str]] + +gnu_symbol_visibility_args = { + '': [], + 'default': ['-fvisibility=default'], + 'internal': ['-fvisibility=internal'], + 'hidden': ['-fvisibility=hidden'], + 'protected': ['-fvisibility=protected'], + 'inlineshidden': ['-fvisibility=hidden', '-fvisibility-inlines-hidden'], +} # type: T.Dict[str, T.List[str]] + +gnu_color_args = { + 'auto': ['-fdiagnostics-color=auto'], + 'always': ['-fdiagnostics-color=always'], + 'never': ['-fdiagnostics-color=never'], +} # type: T.Dict[str, T.List[str]] + +# Warnings collected from the GCC source and documentation. This is an +# objective set of all the warnings flags that apply to general projects: the +# only ones omitted are those that require a project-specific value, or are +# related to non-standard or legacy language support. This behaves roughly +# like -Weverything in clang. Warnings implied by -Wall, -Wextra, or +# higher-level warnings already enabled here are not included in these lists to +# keep them as short as possible. History goes back to GCC 3.0.0, everything +# earlier is considered historical and listed under version 0.0.0. + +# GCC warnings for all C-family languages +# Omitted non-general warnings: +# -Wabi= +# -Waggregate-return +# -Walloc-size-larger-than=BYTES +# -Walloca-larger-than=BYTES +# -Wframe-larger-than=BYTES +# -Wlarger-than=BYTES +# -Wstack-usage=BYTES +# -Wsystem-headers +# -Wtrampolines +# -Wvla-larger-than=BYTES +# +# Omitted warnings enabled elsewhere in meson: +# -Winvalid-pch (GCC 3.4.0) +gnu_common_warning_args = { + "0.0.0": [ + "-Wcast-qual", + "-Wconversion", + "-Wfloat-equal", + "-Wformat=2", + "-Winline", + "-Wmissing-declarations", + "-Wredundant-decls", + "-Wshadow", + "-Wundef", + "-Wuninitialized", + "-Wwrite-strings", + ], + "3.0.0": [ + "-Wdisabled-optimization", + "-Wpacked", + "-Wpadded", + ], + "3.3.0": [ + "-Wmultichar", + "-Wswitch-default", + "-Wswitch-enum", + "-Wunused-macros", + ], + "4.0.0": [ + "-Wmissing-include-dirs", + ], + "4.1.0": [ + "-Wunsafe-loop-optimizations", + "-Wstack-protector", + ], + "4.2.0": [ + "-Wstrict-overflow=5", + ], + "4.3.0": [ + "-Warray-bounds=2", + "-Wlogical-op", + "-Wstrict-aliasing=3", + "-Wvla", + ], + "4.6.0": [ + "-Wdouble-promotion", + "-Wsuggest-attribute=const", + "-Wsuggest-attribute=noreturn", + "-Wsuggest-attribute=pure", + "-Wtrampolines", + ], + "4.7.0": [ + "-Wvector-operation-performance", + ], + "4.8.0": [ + "-Wsuggest-attribute=format", + ], + "4.9.0": [ + "-Wdate-time", + ], + "5.1.0": [ + "-Wformat-signedness", + "-Wnormalized=nfc", + ], + "6.1.0": [ + "-Wduplicated-cond", + "-Wnull-dereference", + "-Wshift-negative-value", + "-Wshift-overflow=2", + "-Wunused-const-variable=2", + ], + "7.1.0": [ + "-Walloca", + "-Walloc-zero", + "-Wformat-overflow=2", + "-Wformat-truncation=2", + "-Wstringop-overflow=3", + ], + "7.2.0": [ + "-Wduplicated-branches", + ], + "8.1.0": [ + "-Wattribute-alias=2", + "-Wcast-align=strict", + "-Wsuggest-attribute=cold", + "-Wsuggest-attribute=malloc", + ], + "10.1.0": [ + "-Wanalyzer-too-complex", + "-Warith-conversion", + ], + "12.1.0": [ + "-Wbidi-chars=ucn", + "-Wopenacc-parallelism", + "-Wtrivial-auto-var-init", + ], +} # type: T.Dict[str, T.List[str]] + +# GCC warnings for C +# Omitted non-general or legacy warnings: +# -Wc11-c2x-compat +# -Wc90-c99-compat +# -Wc99-c11-compat +# -Wdeclaration-after-statement +# -Wtraditional +# -Wtraditional-conversion +gnu_c_warning_args = { + "0.0.0": [ + "-Wbad-function-cast", + "-Wmissing-prototypes", + "-Wnested-externs", + "-Wstrict-prototypes", + ], + "3.4.0": [ + "-Wold-style-definition", + "-Winit-self", + ], + "4.1.0": [ + "-Wc++-compat", + ], + "4.5.0": [ + "-Wunsuffixed-float-constants", + ], +} # type: T.Dict[str, T.List[str]] + +# GCC warnings for C++ +# Omitted non-general or legacy warnings: +# -Wc++0x-compat +# -Wc++1z-compat +# -Wc++2a-compat +# -Wctad-maybe-unsupported +# -Wnamespaces +# -Wtemplates +gnu_cpp_warning_args = { + "0.0.0": [ + "-Wctor-dtor-privacy", + "-Weffc++", + "-Wnon-virtual-dtor", + "-Wold-style-cast", + "-Woverloaded-virtual", + "-Wsign-promo", + ], + "4.0.1": [ + "-Wstrict-null-sentinel", + ], + "4.6.0": [ + "-Wnoexcept", + ], + "4.7.0": [ + "-Wzero-as-null-pointer-constant", + ], + "4.8.0": [ + "-Wabi-tag", + "-Wuseless-cast", + ], + "4.9.0": [ + "-Wconditionally-supported", + ], + "5.1.0": [ + "-Wsuggest-final-methods", + "-Wsuggest-final-types", + "-Wsuggest-override", + ], + "6.1.0": [ + "-Wmultiple-inheritance", + "-Wplacement-new=2", + "-Wvirtual-inheritance", + ], + "7.1.0": [ + "-Waligned-new=all", + "-Wnoexcept-type", + "-Wregister", + ], + "8.1.0": [ + "-Wcatch-value=3", + "-Wextra-semi", + ], + "9.1.0": [ + "-Wdeprecated-copy-dtor", + "-Wredundant-move", + ], + "10.1.0": [ + "-Wcomma-subscript", + "-Wmismatched-tags", + "-Wredundant-tags", + "-Wvolatile", + ], + "11.1.0": [ + "-Wdeprecated-enum-enum-conversion", + "-Wdeprecated-enum-float-conversion", + "-Winvalid-imported-macros", + ], +} # type: T.Dict[str, T.List[str]] + +# GCC warnings for Objective C and Objective C++ +# Omitted non-general or legacy warnings: +# -Wtraditional +# -Wtraditional-conversion +gnu_objc_warning_args = { + "0.0.0": [ + "-Wselector", + ], + "3.3": [ + "-Wundeclared-selector", + ], + "4.1.0": [ + "-Wassign-intercept", + "-Wstrict-selector-match", + ], +} # type: T.Dict[str, T.List[str]] + + +@functools.lru_cache(maxsize=None) +def gnulike_default_include_dirs(compiler: T.Tuple[str, ...], lang: str) -> 'ImmutableListProtocol[str]': + lang_map = { + 'c': 'c', + 'cpp': 'c++', + 'objc': 'objective-c', + 'objcpp': 'objective-c++' + } + if lang not in lang_map: + return [] + lang = lang_map[lang] + env = os.environ.copy() + env["LC_ALL"] = 'C' + cmd = list(compiler) + [f'-x{lang}', '-E', '-v', '-'] + _, stdout, _ = mesonlib.Popen_safe(cmd, stderr=subprocess.STDOUT, env=env) + parse_state = 0 + paths = [] # type: T.List[str] + for line in stdout.split('\n'): + line = line.strip(' \n\r\t') + if parse_state == 0: + if line == '#include "..." search starts here:': + parse_state = 1 + elif parse_state == 1: + if line == '#include <...> search starts here:': + parse_state = 2 + else: + paths.append(line) + elif parse_state == 2: + if line == 'End of search list.': + break + else: + paths.append(line) + if not paths: + mlog.warning('No include directory found parsing "{cmd}" output'.format(cmd=" ".join(cmd))) + # Append a normalized copy of paths to make path lookup easier + paths += [os.path.normpath(x) for x in paths] + return paths + + +class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta): + """ + GnuLikeCompiler is a common interface to all compilers implementing + the GNU-style commandline interface. This includes GCC, Clang + and ICC. Certain functionality between them is different and requires + that the actual concrete subclass define their own implementation. + """ + + LINKER_PREFIX = '-Wl,' + + def __init__(self) -> None: + self.base_options = { + OptionKey(o) for o in ['b_pch', 'b_lto', 'b_pgo', 'b_coverage', + 'b_ndebug', 'b_staticpic', 'b_pie']} + if not (self.info.is_windows() or self.info.is_cygwin() or self.info.is_openbsd()): + self.base_options.add(OptionKey('b_lundef')) + if not self.info.is_windows() or self.info.is_cygwin(): + self.base_options.add(OptionKey('b_asneeded')) + if not self.info.is_hurd(): + self.base_options.add(OptionKey('b_sanitize')) + # All GCC-like backends can do assembly + self.can_compile_suffixes.add('s') + + def get_pic_args(self) -> T.List[str]: + if self.info.is_windows() or self.info.is_cygwin() or self.info.is_darwin(): + return [] # On Window and OS X, pic is always on. + return ['-fPIC'] + + def get_pie_args(self) -> T.List[str]: + return ['-fPIE'] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return gnulike_buildtype_args[buildtype] + + @abc.abstractmethod + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + pass + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return clike_debug_args[is_debug] + + @abc.abstractmethod + def get_pch_suffix(self) -> str: + pass + + def split_shlib_to_parts(self, fname: str) -> T.Tuple[str, str]: + return os.path.dirname(fname), fname + + def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: + return gnulike_instruction_set_args.get(instruction_set, None) + + def get_default_include_dirs(self) -> T.List[str]: + return gnulike_default_include_dirs(tuple(self.get_exelist(ccache=False)), self.language).copy() + + @abc.abstractmethod + def openmp_flags(self) -> T.List[str]: + pass + + def gnu_symbol_visibility_args(self, vistype: str) -> T.List[str]: + if vistype == 'inlineshidden' and self.language not in {'cpp', 'objcpp'}: + vistype = 'hidden' + return gnu_symbol_visibility_args[vistype] + + def gen_vs_module_defs_args(self, defsfile: str) -> T.List[str]: + if not isinstance(defsfile, str): + raise RuntimeError('Module definitions file should be str') + # On Windows targets, .def files may be specified on the linker command + # line like an object file. + if self.info.is_windows() or self.info.is_cygwin(): + return [defsfile] + # For other targets, discard the .def file. + return [] + + def get_argument_syntax(self) -> str: + return 'gcc' + + def get_profile_generate_args(self) -> T.List[str]: + return ['-fprofile-generate'] + + def get_profile_use_args(self) -> T.List[str]: + return ['-fprofile-use'] + + def get_gui_app_args(self, value: bool) -> T.List[str]: + return ['-mwindows' if value else '-mconsole'] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:2] == '-I' or i[:2] == '-L': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + + return parameter_list + + @functools.lru_cache() + def _get_search_dirs(self, env: 'Environment') -> str: + extra_args = ['--print-search-dirs'] + with self._build_wrapper('', env, extra_args=extra_args, + dependencies=None, mode='compile', + want_output=True) as p: + return p.stdout + + def _split_fetch_real_dirs(self, pathstr: str) -> T.List[str]: + # We need to use the path separator used by the compiler for printing + # lists of paths ("gcc --print-search-dirs"). By default + # we assume it uses the platform native separator. + pathsep = os.pathsep + + # clang uses ':' instead of ';' on Windows https://reviews.llvm.org/D61121 + # so we need to repair things like 'C:\foo:C:\bar' + if pathsep == ';': + pathstr = re.sub(r':([^/\\])', r';\1', pathstr) + + # pathlib treats empty paths as '.', so filter those out + paths = [p for p in pathstr.split(pathsep) if p] + + result = [] + for p in paths: + # GCC returns paths like this: + # /usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/lib + # It would make sense to normalize them to get rid of the .. parts + # Sadly when you are on a merged /usr fs it also kills these: + # /lib/x86_64-linux-gnu + # since /lib is a symlink to /usr/lib. This would mean + # paths under /lib would be considered not a "system path", + # which is wrong and breaks things. Store everything, just to be sure. + pobj = pathlib.Path(p) + unresolved = pobj.as_posix() + if pobj.exists(): + if unresolved not in result: + result.append(unresolved) + try: + resolved = pathlib.Path(p).resolve().as_posix() + if resolved not in result: + result.append(resolved) + except FileNotFoundError: + pass + return result + + def get_compiler_dirs(self, env: 'Environment', name: str) -> T.List[str]: + ''' + Get dirs from the compiler, either `libraries:` or `programs:` + ''' + stdo = self._get_search_dirs(env) + for line in stdo.split('\n'): + if line.startswith(name + ':'): + return self._split_fetch_real_dirs(line.split('=', 1)[1]) + return [] + + def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]: + # This provides a base for many compilers, GCC and Clang override this + # for their specific arguments + return ['-flto'] + + def sanitizer_compile_args(self, value: str) -> T.List[str]: + if value == 'none': + return [] + args = ['-fsanitize=' + value] + if 'address' in value: # for -fsanitize=address,undefined + args.append('-fno-omit-frame-pointer') + return args + + def get_output_args(self, target: str) -> T.List[str]: + return ['-o', target] + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + return ['-MD', '-MQ', outtarget, '-MF', outfile] + + def get_compile_only_args(self) -> T.List[str]: + return ['-c'] + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + if not path: + path = '.' + if is_system: + return ['-isystem' + path] + return ['-I' + path] + + @classmethod + def use_linker_args(cls, linker: str, version: str) -> T.List[str]: + if linker not in {'gold', 'bfd', 'lld'}: + raise mesonlib.MesonException( + f'Unsupported linker, only bfd, gold, and lld are supported, not {linker}.') + return [f'-fuse-ld={linker}'] + + def get_coverage_args(self) -> T.List[str]: + return ['--coverage'] + + def get_preprocess_to_file_args(self) -> T.List[str]: + # We want to allow preprocessing files with any extension, such as + # foo.c.in. In that case we need to tell GCC/CLANG to treat them as + # assembly file. + return self.get_preprocess_only_args() + ['-x', 'assembler-with-cpp'] + + +class GnuCompiler(GnuLikeCompiler): + """ + GnuCompiler represents an actual GCC in its many incarnations. + Compilers imitating GCC (Clang/Intel) should use the GnuLikeCompiler ABC. + """ + id = 'gcc' + + def __init__(self, defines: T.Optional[T.Dict[str, str]]): + super().__init__() + self.defines = defines or {} + self.base_options.update({OptionKey('b_colorout'), OptionKey('b_lto_threads')}) + + def get_colorout_args(self, colortype: str) -> T.List[str]: + if mesonlib.version_compare(self.version, '>=4.9.0'): + return gnu_color_args[colortype][:] + return [] + + def get_warn_args(self, level: str) -> T.List[str]: + # Mypy doesn't understand cooperative inheritance + args = super().get_warn_args(level) + if mesonlib.version_compare(self.version, '<4.8.0') and '-Wpedantic' in args: + # -Wpedantic was added in 4.8.0 + # https://gcc.gnu.org/gcc-4.8/changes.html + args[args.index('-Wpedantic')] = '-pedantic' + return args + + def supported_warn_args(self, warn_args_by_version: T.Dict[str, T.List[str]]) -> T.List[str]: + result = [] + for version, warn_args in warn_args_by_version.items(): + if mesonlib.version_compare(self.version, '>=' + version): + result += warn_args + return result + + def has_builtin_define(self, define: str) -> bool: + return define in self.defines + + def get_builtin_define(self, define: str) -> T.Optional[str]: + if define in self.defines: + return self.defines[define] + return None + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return gnu_optimization_args[optimization_level] + + def get_pch_suffix(self) -> str: + return 'gch' + + def openmp_flags(self) -> T.List[str]: + return ['-fopenmp'] + + def has_arguments(self, args: T.List[str], env: 'Environment', code: str, + mode: str) -> T.Tuple[bool, bool]: + # For some compiler command line arguments, the GNU compilers will + # emit a warning on stderr indicating that an option is valid for a + # another language, but still complete with exit_success + with self._build_wrapper(code, env, args, None, mode) as p: + result = p.returncode == 0 + if self.language in {'cpp', 'objcpp'} and 'is valid for C/ObjC' in p.stderr: + result = False + if self.language in {'c', 'objc'} and 'is valid for C++/ObjC++' in p.stderr: + result = False + return result, p.cached + + def get_has_func_attribute_extra_args(self, name: str) -> T.List[str]: + # GCC only warns about unknown or ignored attributes, so force an + # error. + return ['-Werror=attributes'] + + def get_prelink_args(self, prelink_name: str, obj_list: T.List[str]) -> T.List[str]: + return ['-r', '-o', prelink_name] + obj_list + + def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]: + if threads == 0: + if mesonlib.version_compare(self.version, '>= 10.0'): + return ['-flto=auto'] + # This matches clang's behavior of using the number of cpus + return [f'-flto={multiprocessing.cpu_count()}'] + elif threads > 0: + return [f'-flto={threads}'] + return super().get_lto_compile_args(threads=threads) + + @classmethod + def use_linker_args(cls, linker: str, version: str) -> T.List[str]: + if linker == 'mold' and mesonlib.version_compare(version, '>=12.0.1'): + return ['-fuse-ld=mold'] + return super().use_linker_args(linker, version) + + def get_profile_use_args(self) -> T.List[str]: + return super().get_profile_use_args() + ['-fprofile-correction'] diff --git a/mesonbuild/compilers/mixins/intel.py b/mesonbuild/compilers/mixins/intel.py new file mode 100644 index 0000000..b793fa8 --- /dev/null +++ b/mesonbuild/compilers/mixins/intel.py @@ -0,0 +1,185 @@ +# Copyright 2019 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 __future__ import annotations + +"""Abstractions for the Intel Compiler families. + +Intel provides both a posix/gcc-like compiler (ICC) for MacOS and Linux, +with Meson mixin IntelGnuLikeCompiler. +For Windows, the Intel msvc-like compiler (ICL) Meson mixin +is IntelVisualStudioLikeCompiler. +""" + +import os +import typing as T + +from ... import mesonlib +from ..compilers import CompileCheckMode +from .gnu import GnuLikeCompiler +from .visualstudio import VisualStudioLikeCompiler + +# XXX: avoid circular dependencies +# TODO: this belongs in a posix compiler class +# NOTE: the default Intel optimization is -O2, unlike GNU which defaults to -O0. +# this can be surprising, particularly for debug builds, so we specify the +# default as -O0. +# https://software.intel.com/en-us/cpp-compiler-developer-guide-and-reference-o +# https://software.intel.com/en-us/cpp-compiler-developer-guide-and-reference-g +# https://software.intel.com/en-us/fortran-compiler-developer-guide-and-reference-o +# https://software.intel.com/en-us/fortran-compiler-developer-guide-and-reference-g +# https://software.intel.com/en-us/fortran-compiler-developer-guide-and-reference-traceback +# https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html + + +class IntelGnuLikeCompiler(GnuLikeCompiler): + """ + Tested on linux for ICC 14.0.3, 15.0.6, 16.0.4, 17.0.1, 19.0 + debugoptimized: -g -O2 + release: -O3 + minsize: -O2 + """ + + BUILD_ARGS = { + 'plain': [], + 'debug': ["-g", "-traceback"], + 'debugoptimized': ["-g", "-traceback"], + 'release': [], + 'minsize': [], + 'custom': [], + } # type: T.Dict[str, T.List[str]] + + OPTIM_ARGS: T.Dict[str, T.List[str]] = { + 'plain': [], + '0': ['-O0'], + 'g': ['-O0'], + '1': ['-O1'], + '2': ['-O2'], + '3': ['-O3'], + 's': ['-Os'], + } + id = 'intel' + + def __init__(self) -> None: + super().__init__() + # As of 19.0.0 ICC doesn't have sanitizer, color, or lto support. + # + # It does have IPO, which serves much the same purpose as LOT, but + # there is an unfortunate rule for using IPO (you can't control the + # name of the output file) which break assumptions meson makes + self.base_options = {mesonlib.OptionKey(o) for o in [ + 'b_pch', 'b_lundef', 'b_asneeded', 'b_pgo', 'b_coverage', + 'b_ndebug', 'b_staticpic', 'b_pie']} + self.lang_header = 'none' + + def get_pch_suffix(self) -> str: + return 'pchi' + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + return ['-pch', '-pch_dir', os.path.join(pch_dir), '-x', + self.lang_header, '-include', header, '-x', 'none'] + + def get_pch_name(self, header_name: str) -> str: + return os.path.basename(header_name) + '.' + self.get_pch_suffix() + + def openmp_flags(self) -> T.List[str]: + if mesonlib.version_compare(self.version, '>=15.0.0'): + return ['-qopenmp'] + else: + return ['-openmp'] + + def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: + extra_args = [ + '-diag-error', '10006', # ignoring unknown option + '-diag-error', '10148', # Option not supported + '-diag-error', '10155', # ignoring argument required + '-diag-error', '10156', # ignoring not argument allowed + '-diag-error', '10157', # Ignoring argument of the wrong type + '-diag-error', '10158', # Argument must be separate. Can be hit by trying an option like -foo-bar=foo when -foo=bar is a valid option but -foo-bar isn't + ] + return super().get_compiler_check_args(mode) + extra_args + + def get_profile_generate_args(self) -> T.List[str]: + return ['-prof-gen=threadsafe'] + + def get_profile_use_args(self) -> T.List[str]: + return ['-prof-use'] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return self.BUILD_ARGS[buildtype] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return self.OPTIM_ARGS[optimization_level] + + def get_has_func_attribute_extra_args(self, name: str) -> T.List[str]: + return ['-diag-error', '1292'] + + +class IntelVisualStudioLikeCompiler(VisualStudioLikeCompiler): + + """Abstractions for ICL, the Intel compiler on Windows.""" + + BUILD_ARGS = { + 'plain': [], + 'debug': ["/Zi", "/traceback"], + 'debugoptimized': ["/Zi", "/traceback"], + 'release': [], + 'minsize': [], + 'custom': [], + } # type: T.Dict[str, T.List[str]] + + OPTIM_ARGS: T.Dict[str, T.List[str]] = { + 'plain': [], + '0': ['/Od'], + 'g': ['/Od'], + '1': ['/O1'], + '2': ['/O2'], + '3': ['/O3'], + 's': ['/Os'], + } + + id = 'intel-cl' + + def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: + args = super().get_compiler_check_args(mode) + if mode is not CompileCheckMode.LINK: + args.extend([ + '/Qdiag-error:10006', # ignoring unknown option + '/Qdiag-error:10148', # Option not supported + '/Qdiag-error:10155', # ignoring argument required + '/Qdiag-error:10156', # ignoring not argument allowed + '/Qdiag-error:10157', # Ignoring argument of the wrong type + '/Qdiag-error:10158', # Argument must be separate. Can be hit by trying an option like -foo-bar=foo when -foo=bar is a valid option but -foo-bar isn't + ]) + return args + + def get_toolset_version(self) -> T.Optional[str]: + # ICL provides a cl.exe that returns the version of MSVC it tries to + # emulate, so we'll get the version from that and pass it to the same + # function the real MSVC uses to calculate the toolset version. + _, _, err = mesonlib.Popen_safe(['cl.exe']) + v1, v2, *_ = mesonlib.search_version(err).split('.') + version = int(v1 + v2) + return self._calculate_toolset_version(version) + + def openmp_flags(self) -> T.List[str]: + return ['/Qopenmp'] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return self.BUILD_ARGS[buildtype] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return self.OPTIM_ARGS[optimization_level] + + def get_pch_base_name(self, header: str) -> str: + return os.path.basename(header) diff --git a/mesonbuild/compilers/mixins/islinker.py b/mesonbuild/compilers/mixins/islinker.py new file mode 100644 index 0000000..711e3e3 --- /dev/null +++ b/mesonbuild/compilers/mixins/islinker.py @@ -0,0 +1,130 @@ +# Copyright 2019 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 __future__ import annotations + +"""Mixins for compilers that *are* linkers. + +While many compilers (such as gcc and clang) are used by meson to dispatch +linker commands and other (like MSVC) are not, a few (such as DMD) actually +are both the linker and compiler in one binary. This module provides mixin +classes for those cases. +""" + +import typing as T + +from ...mesonlib import EnvironmentException, MesonException, is_windows + +if T.TYPE_CHECKING: + from ...coredata import KeyedOptionDictType + from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object + + +class BasicLinkerIsCompilerMixin(Compiler): + + """Provides a baseline of methods that a linker would implement. + + In every case this provides a "no" or "empty" answer. If a compiler + implements any of these it needs a different mixin or to override that + functionality itself. + """ + + def sanitizer_link_args(self, value: str) -> T.List[str]: + return [] + + def get_lto_link_args(self, *, threads: int = 0, mode: str = 'default', + thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: + return [] + + def can_linker_accept_rsp(self) -> bool: + return is_windows() + + def get_linker_exelist(self) -> T.List[str]: + return self.exelist.copy() + + def get_linker_output_args(self, outputname: str) -> T.List[str]: + return [] + + def get_linker_always_args(self) -> T.List[str]: + return [] + + def get_linker_lib_prefix(self) -> str: + return '' + + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + return [] + + def has_multi_link_args(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: + return False, False + + def get_link_debugfile_args(self, targetfile: str) -> T.List[str]: + return [] + + def get_std_shared_lib_link_args(self) -> T.List[str]: + return [] + + def get_std_shared_module_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + return self.get_std_shared_lib_link_args() + + def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: + raise EnvironmentException(f'Linker {self.id} does not support link_whole') + + def get_allow_undefined_link_args(self) -> T.List[str]: + raise EnvironmentException(f'Linker {self.id} does not support allow undefined') + + def get_pie_link_args(self) -> T.List[str]: + raise EnvironmentException(f'Linker {self.id} does not support position-independent executable') + + def get_undefined_link_args(self) -> T.List[str]: + return [] + + def get_coverage_link_args(self) -> T.List[str]: + return [] + + def no_undefined_link_args(self) -> T.List[str]: + return [] + + def bitcode_args(self) -> T.List[str]: + raise MesonException("This linker doesn't support bitcode bundles") + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, + darwin_versions: T.Tuple[str, str]) -> T.List[str]: + raise MesonException("This linker doesn't support soname args") + + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: T.Tuple[str, ...], build_rpath: str, + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + return ([], set()) + + def get_asneeded_args(self) -> T.List[str]: + return [] + + def get_buildtype_linker_args(self, buildtype: str) -> T.List[str]: + return [] + + def get_link_debugfile_name(self, targetfile: str) -> str: + return '' + + def thread_flags(self, env: 'Environment') -> T.List[str]: + return [] + + def thread_link_flags(self, env: 'Environment') -> T.List[str]: + return [] diff --git a/mesonbuild/compilers/mixins/pgi.py b/mesonbuild/compilers/mixins/pgi.py new file mode 100644 index 0000000..2fa736c --- /dev/null +++ b/mesonbuild/compilers/mixins/pgi.py @@ -0,0 +1,113 @@ +# Copyright 2019 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 __future__ import annotations + +"""Abstractions for the PGI family of compilers.""" + +import typing as T +import os +from pathlib import Path + +from ..compilers import clike_debug_args, clike_optimization_args +from ...mesonlib import OptionKey + +if T.TYPE_CHECKING: + from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object + +pgi_buildtype_args = { + 'plain': [], + 'debug': [], + 'debugoptimized': [], + 'release': [], + 'minsize': [], + 'custom': [], +} # type: T.Dict[str, T.List[str]] + + +class PGICompiler(Compiler): + + id = 'pgi' + + def __init__(self) -> None: + self.base_options = {OptionKey('b_pch')} + + default_warn_args = ['-Minform=inform'] + self.warn_args: T.Dict[str, T.List[str]] = { + '0': [], + '1': default_warn_args, + '2': default_warn_args, + '3': default_warn_args, + 'everything': default_warn_args + } + + def get_module_incdir_args(self) -> T.Tuple[str]: + return ('-module', ) + + def get_no_warn_args(self) -> T.List[str]: + return ['-silent'] + + def gen_import_library_args(self, implibname: str) -> T.List[str]: + return [] + + def get_pic_args(self) -> T.List[str]: + # PGI -fPIC is Linux only. + if self.info.is_linux(): + return ['-fPIC'] + return [] + + def openmp_flags(self) -> T.List[str]: + return ['-mp'] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return pgi_buildtype_args[buildtype] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return clike_optimization_args[optimization_level] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return clike_debug_args[is_debug] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:2] == '-I' or i[:2] == '-L': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + return parameter_list + + def get_always_args(self) -> T.List[str]: + return [] + + def get_pch_suffix(self) -> str: + # PGI defaults to .pch suffix for PCH on Linux and Windows with --pch option + return 'pch' + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + # PGI supports PCH for C++ only. + hdr = Path(pch_dir).resolve().parent / header + if self.language == 'cpp': + return ['--pch', + '--pch_dir', str(hdr.parent), + f'-I{hdr.parent}'] + else: + return [] + + def thread_flags(self, env: 'Environment') -> T.List[str]: + # PGI cannot accept -pthread, it's already threaded + return [] diff --git a/mesonbuild/compilers/mixins/ti.py b/mesonbuild/compilers/mixins/ti.py new file mode 100644 index 0000000..950c97f --- /dev/null +++ b/mesonbuild/compilers/mixins/ti.py @@ -0,0 +1,151 @@ +# Copyright 2012-2019 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 __future__ import annotations + +"""Representations specific to the Texas Instruments compiler family.""" + +import os +import typing as T + +from ...mesonlib import EnvironmentException + +if T.TYPE_CHECKING: + from ...envconfig import MachineInfo + from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object + +ti_buildtype_args = { + 'plain': [], + 'debug': [], + 'debugoptimized': [], + 'release': [], + 'minsize': [], + 'custom': [], +} # type: T.Dict[str, T.List[str]] + +ti_optimization_args = { + 'plain': [], + '0': ['-O0'], + 'g': ['-Ooff'], + '1': ['-O1'], + '2': ['-O2'], + '3': ['-O3'], + 's': ['-O4'] +} # type: T.Dict[str, T.List[str]] + +ti_debug_args = { + False: [], + True: ['-g'] +} # type: T.Dict[bool, T.List[str]] + + +class TICompiler(Compiler): + + id = 'ti' + + def __init__(self) -> None: + if not self.is_cross: + raise EnvironmentException('TI compilers only support cross-compilation.') + + self.can_compile_suffixes.add('asm') # Assembly + self.can_compile_suffixes.add('cla') # Control Law Accelerator (CLA) used in C2000 + + default_warn_args = [] # type: T.List[str] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + [], + '3': default_warn_args + [], + 'everything': default_warn_args + []} # type: T.Dict[str, T.List[str]] + + def get_pic_args(self) -> T.List[str]: + # PIC support is not enabled by default for TI compilers, + # if users want to use it, they need to add the required arguments explicitly + return [] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return ti_buildtype_args[buildtype] + + def get_pch_suffix(self) -> str: + return 'pch' + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + return [] + + def thread_flags(self, env: 'Environment') -> T.List[str]: + return [] + + def get_coverage_args(self) -> T.List[str]: + return [] + + def get_no_stdinc_args(self) -> T.List[str]: + return [] + + def get_no_stdlib_link_args(self) -> T.List[str]: + return [] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return ti_optimization_args[optimization_level] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return ti_debug_args[is_debug] + + def get_compile_only_args(self) -> T.List[str]: + return [] + + def get_no_optimization_args(self) -> T.List[str]: + return ['-Ooff'] + + def get_output_args(self, target: str) -> T.List[str]: + return [f'--output_file={target}'] + + def get_werror_args(self) -> T.List[str]: + return ['--emit_warnings_as_errors'] + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + if path == '': + path = '.' + return ['-I=' + path] + + @classmethod + def _unix_args_to_native(cls, args: T.List[str], info: MachineInfo) -> T.List[str]: + result = [] + for i in args: + if i.startswith('-D'): + i = '--define=' + i[2:] + if i.startswith('-Wl,-rpath='): + continue + elif i == '--print-search-dirs': + continue + elif i.startswith('-L'): + continue + result.append(i) + return result + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:15] == '--include_path=': + parameter_list[idx] = i[:15] + os.path.normpath(os.path.join(build_dir, i[15:])) + if i[:2] == '-I': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + + return parameter_list + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + return ['--preproc_with_compile', f'--preproc_dependency={outfile}'] diff --git a/mesonbuild/compilers/mixins/visualstudio.py b/mesonbuild/compilers/mixins/visualstudio.py new file mode 100644 index 0000000..864d3b4 --- /dev/null +++ b/mesonbuild/compilers/mixins/visualstudio.py @@ -0,0 +1,500 @@ +# Copyright 2019 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 __future__ import annotations + +"""Abstractions to simplify compilers that implement an MSVC compatible +interface. +""" + +import abc +import os +import typing as T + +from ... import arglist +from ... import mesonlib +from ... import mlog + +if T.TYPE_CHECKING: + from ...environment import Environment + from ...dependencies import Dependency + from .clike import CLikeCompiler as Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object + +vs32_instruction_set_args = { + 'mmx': ['/arch:SSE'], # There does not seem to be a flag just for MMX + 'sse': ['/arch:SSE'], + 'sse2': ['/arch:SSE2'], + 'sse3': ['/arch:AVX'], # VS leaped from SSE2 directly to AVX. + 'sse41': ['/arch:AVX'], + 'sse42': ['/arch:AVX'], + 'avx': ['/arch:AVX'], + 'avx2': ['/arch:AVX2'], + 'neon': None, +} # T.Dicst[str, T.Optional[T.List[str]]] + +# The 64 bit compiler defaults to /arch:avx. +vs64_instruction_set_args = { + 'mmx': ['/arch:AVX'], + 'sse': ['/arch:AVX'], + 'sse2': ['/arch:AVX'], + 'sse3': ['/arch:AVX'], + 'ssse3': ['/arch:AVX'], + 'sse41': ['/arch:AVX'], + 'sse42': ['/arch:AVX'], + 'avx': ['/arch:AVX'], + 'avx2': ['/arch:AVX2'], + 'neon': None, +} # T.Dicst[str, T.Optional[T.List[str]]] + +msvc_optimization_args = { + 'plain': [], + '0': ['/Od'], + 'g': [], # No specific flag to optimize debugging, /Zi or /ZI will create debug information + '1': ['/O1'], + '2': ['/O2'], + '3': ['/O2', '/Gw'], + 's': ['/O1', '/Gw'], +} # type: T.Dict[str, T.List[str]] + +msvc_debug_args = { + False: [], + True: ['/Zi'] +} # type: T.Dict[bool, T.List[str]] + + +class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): + + """A common interface for all compilers implementing an MSVC-style + interface. + + A number of compilers attempt to mimic MSVC, with varying levels of + success, such as Clang-CL and ICL (the Intel C/C++ Compiler for Windows). + This class implements as much common logic as possible. + """ + + std_warn_args = ['/W3'] + std_opt_args = ['/O2'] + ignore_libs = arglist.UNIXY_COMPILER_INTERNAL_LIBS + ['execinfo'] + internal_libs = [] # type: T.List[str] + + crt_args = { + 'none': [], + 'md': ['/MD'], + 'mdd': ['/MDd'], + 'mt': ['/MT'], + 'mtd': ['/MTd'], + } # type: T.Dict[str, T.List[str]] + + # /showIncludes is needed for build dependency tracking in Ninja + # See: https://ninja-build.org/manual.html#_deps + # Assume UTF-8 sources by default, but self.unix_args_to_native() removes it + # if `/source-charset` is set too. + # It is also dropped if Visual Studio 2013 or earlier is used, since it would + # not be supported in that case. + always_args = ['/nologo', '/showIncludes', '/utf-8'] + warn_args = { + '0': [], + '1': ['/W2'], + '2': ['/W3'], + '3': ['/W4'], + 'everything': ['/Wall'], + } # type: T.Dict[str, T.List[str]] + + INVOKES_LINKER = False + + def __init__(self, target: str): + self.base_options = {mesonlib.OptionKey(o) for o in ['b_pch', 'b_ndebug', 'b_vscrt']} # FIXME add lto, pgo and the like + self.target = target + self.is_64 = ('x64' in target) or ('x86_64' in target) + # do some canonicalization of target machine + if 'x86_64' in target: + self.machine = 'x64' + elif '86' in target: + self.machine = 'x86' + elif 'aarch64' in target: + self.machine = 'arm64' + elif 'arm' in target: + self.machine = 'arm' + else: + self.machine = target + if mesonlib.version_compare(self.version, '>=19.28.29910'): # VS 16.9.0 includes cl 19.28.29910 + self.base_options.add(mesonlib.OptionKey('b_sanitize')) + assert self.linker is not None + self.linker.machine = self.machine + + # Override CCompiler.get_always_args + def get_always_args(self) -> T.List[str]: + # TODO: use ImmutableListProtocol[str] here instead + return self.always_args.copy() + + def get_pch_suffix(self) -> str: + return 'pch' + + def get_pch_name(self, header: str) -> str: + chopped = os.path.basename(header).split('.')[:-1] + chopped.append(self.get_pch_suffix()) + pchname = '.'.join(chopped) + return pchname + + def get_pch_base_name(self, header: str) -> str: + # This needs to be implemented by inheriting classes + raise NotImplementedError + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + base = self.get_pch_base_name(header) + pchname = self.get_pch_name(header) + return ['/FI' + base, '/Yu' + base, '/Fp' + os.path.join(pch_dir, pchname)] + + def get_preprocess_only_args(self) -> T.List[str]: + return ['/EP'] + + def get_preprocess_to_file_args(self) -> T.List[str]: + return ['/EP', '/P'] + + def get_compile_only_args(self) -> T.List[str]: + return ['/c'] + + def get_no_optimization_args(self) -> T.List[str]: + return ['/Od', '/Oi-'] + + def sanitizer_compile_args(self, value: str) -> T.List[str]: + if value == 'none': + return [] + if value != 'address': + raise mesonlib.MesonException('VS only supports address sanitizer at the moment.') + return ['/fsanitize=address'] + + def get_output_args(self, target: str) -> T.List[str]: + if self.mode == 'PREPROCESSOR': + return ['/Fi' + target] + if target.endswith('.exe'): + return ['/Fe' + target] + return ['/Fo' + target] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return [] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return msvc_debug_args[is_debug] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + args = msvc_optimization_args[optimization_level] + if mesonlib.version_compare(self.version, '<18.0'): + args = [arg for arg in args if arg != '/Gw'] + return args + + def linker_to_compiler_args(self, args: T.List[str]) -> T.List[str]: + return ['/link'] + args + + def get_pic_args(self) -> T.List[str]: + return [] # PIC is handled by the loader on Windows + + def gen_vs_module_defs_args(self, defsfile: str) -> T.List[str]: + if not isinstance(defsfile, str): + raise RuntimeError('Module definitions file should be str') + # With MSVC, DLLs only export symbols that are explicitly exported, + # so if a module defs file is specified, we use that to export symbols + return ['/DEF:' + defsfile] + + def gen_pch_args(self, header: str, source: str, pchname: str) -> T.Tuple[str, T.List[str]]: + objname = os.path.splitext(pchname)[0] + '.obj' + return objname, ['/Yc' + header, '/Fp' + pchname, '/Fo' + objname] + + def openmp_flags(self) -> T.List[str]: + return ['/openmp'] + + def openmp_link_flags(self) -> T.List[str]: + return [] + + # FIXME, no idea what these should be. + def thread_flags(self, env: 'Environment') -> T.List[str]: + return [] + + @classmethod + def unix_args_to_native(cls, args: T.List[str]) -> T.List[str]: + result: T.List[str] = [] + for i in args: + # -mms-bitfields is specific to MinGW-GCC + # -pthread is only valid for GCC + if i in {'-mms-bitfields', '-pthread'}: + continue + if i.startswith('-LIBPATH:'): + i = '/LIBPATH:' + i[9:] + elif i.startswith('-L'): + i = '/LIBPATH:' + i[2:] + # Translate GNU-style -lfoo library name to the import library + elif i.startswith('-l'): + name = i[2:] + if name in cls.ignore_libs: + # With MSVC, these are provided by the C runtime which is + # linked in by default + continue + else: + i = name + '.lib' + elif i.startswith('-isystem'): + # just use /I for -isystem system include path s + if i.startswith('-isystem='): + i = '/I' + i[9:] + else: + i = '/I' + i[8:] + elif i.startswith('-idirafter'): + # same as -isystem, but appends the path instead + if i.startswith('-idirafter='): + i = '/I' + i[11:] + else: + i = '/I' + i[10:] + # -pthread in link flags is only used on Linux + elif i == '-pthread': + continue + # cl.exe does not allow specifying both, so remove /utf-8 that we + # added automatically in the case the user overrides it manually. + elif (i.startswith('/source-charset:') + or i.startswith('/execution-charset:') + or i == '/validate-charset-'): + try: + result.remove('/utf-8') + except ValueError: + pass + result.append(i) + return result + + @classmethod + def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]: + result = [] + for arg in args: + if arg.startswith(('/LIBPATH:', '-LIBPATH:')): + result.append('-L' + arg[9:]) + elif arg.endswith(('.a', '.lib')) and not os.path.isabs(arg): + result.append('-l' + arg) + else: + result.append(arg) + return result + + def get_werror_args(self) -> T.List[str]: + return ['/WX'] + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + if path == '': + path = '.' + # msvc does not have a concept of system header dirs. + return ['-I' + path] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:2] == '-I' or i[:2] == '/I': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + elif i[:9] == '/LIBPATH:': + parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:])) + + return parameter_list + + # Visual Studio is special. It ignores some arguments it does not + # understand and you can't tell it to error out on those. + # http://stackoverflow.com/questions/15259720/how-can-i-make-the-microsoft-c-compiler-treat-unknown-flags-as-errors-rather-t + def has_arguments(self, args: T.List[str], env: 'Environment', code: str, mode: str) -> T.Tuple[bool, bool]: + warning_text = '4044' if mode == 'link' else '9002' + with self._build_wrapper(code, env, extra_args=args, mode=mode) as p: + if p.returncode != 0: + return False, p.cached + return not (warning_text in p.stderr or warning_text in p.stdout), p.cached + + def get_compile_debugfile_args(self, rel_obj: str, pch: bool = False) -> T.List[str]: + pdbarr = rel_obj.split('.')[:-1] + pdbarr += ['pdb'] + args = ['/Fd' + '.'.join(pdbarr)] + return args + + def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: + if self.is_64: + return vs64_instruction_set_args.get(instruction_set, None) + return vs32_instruction_set_args.get(instruction_set, None) + + def _calculate_toolset_version(self, version: int) -> T.Optional[str]: + if version < 1310: + return '7.0' + elif version < 1400: + return '7.1' # (Visual Studio 2003) + elif version < 1500: + return '8.0' # (Visual Studio 2005) + elif version < 1600: + return '9.0' # (Visual Studio 2008) + elif version < 1700: + return '10.0' # (Visual Studio 2010) + elif version < 1800: + return '11.0' # (Visual Studio 2012) + elif version < 1900: + return '12.0' # (Visual Studio 2013) + elif version < 1910: + return '14.0' # (Visual Studio 2015) + elif version < 1920: + return '14.1' # (Visual Studio 2017) + elif version < 1930: + return '14.2' # (Visual Studio 2019) + elif version < 1940: + return '14.3' # (Visual Studio 2022) + mlog.warning(f'Could not find toolset for version {self.version!r}') + return None + + def get_toolset_version(self) -> T.Optional[str]: + # See boost/config/compiler/visualc.cpp for up to date mapping + try: + version = int(''.join(self.version.split('.')[0:2])) + except ValueError: + return None + return self._calculate_toolset_version(version) + + def get_default_include_dirs(self) -> T.List[str]: + if 'INCLUDE' not in os.environ: + return [] + return os.environ['INCLUDE'].split(os.pathsep) + + def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: + if crt_val in self.crt_args: + return self.crt_args[crt_val] + assert crt_val in {'from_buildtype', 'static_from_buildtype'} + dbg = 'mdd' + rel = 'md' + if crt_val == 'static_from_buildtype': + dbg = 'mtd' + rel = 'mt' + # Match what build type flags used to do. + if buildtype == 'plain': + return [] + elif buildtype == 'debug': + return self.crt_args[dbg] + elif buildtype == 'debugoptimized': + return self.crt_args[rel] + elif buildtype == 'release': + return self.crt_args[rel] + elif buildtype == 'minsize': + return self.crt_args[rel] + else: + assert buildtype == 'custom' + raise mesonlib.EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".') + + def has_func_attribute(self, name: str, env: 'Environment') -> T.Tuple[bool, bool]: + # MSVC doesn't have __attribute__ like Clang and GCC do, so just return + # false without compiling anything + return name in {'dllimport', 'dllexport'}, False + + def get_argument_syntax(self) -> str: + return 'msvc' + + def symbols_have_underscore_prefix(self, env: 'Environment') -> bool: + ''' + Check if the compiler prefixes an underscore to global C symbols. + + This overrides the Clike method, as for MSVC checking the + underscore prefix based on the compiler define never works, + so do not even try. + ''' + # Try to consult a hardcoded list of cases we know + # absolutely have an underscore prefix + result = self._symbols_have_underscore_prefix_list(env) + if result is not None: + return result + + # As a last resort, try search in a compiled binary + return self._symbols_have_underscore_prefix_searchbin(env) + + +class MSVCCompiler(VisualStudioLikeCompiler): + + """Specific to the Microsoft Compilers.""" + + id = 'msvc' + + def __init__(self, target: str): + super().__init__(target) + + # Visual Studio 2013 and erlier don't support the /utf-8 argument. + # We want to remove it. We also want to make an explicit copy so we + # don't mutate class constant state + if mesonlib.version_compare(self.version, '<19.00') and '/utf-8' in self.always_args: + self.always_args = [r for r in self.always_args if r != '/utf-8'] + + def get_compile_debugfile_args(self, rel_obj: str, pch: bool = False) -> T.List[str]: + args = super().get_compile_debugfile_args(rel_obj, pch) + # When generating a PDB file with PCH, all compile commands write + # to the same PDB file. Hence, we need to serialize the PDB + # writes using /FS since we do parallel builds. This slows down the + # build obviously, which is why we only do this when PCH is on. + # This was added in Visual Studio 2013 (MSVC 18.0). Before that it was + # always on: https://msdn.microsoft.com/en-us/library/dn502518.aspx + if pch and mesonlib.version_compare(self.version, '>=18.0'): + args = ['/FS'] + args + return args + + # Override CCompiler.get_always_args + # We want to drop '/utf-8' for Visual Studio 2013 and earlier + def get_always_args(self) -> T.List[str]: + return self.always_args + + def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: + if self.version.split('.')[0] == '16' and instruction_set == 'avx': + # VS documentation says that this exists and should work, but + # it does not. The headers do not contain AVX intrinsics + # and they can not be called. + return None + return super().get_instruction_set_args(instruction_set) + + def get_pch_base_name(self, header: str) -> str: + return os.path.basename(header) + + +class ClangClCompiler(VisualStudioLikeCompiler): + + """Specific to Clang-CL.""" + + id = 'clang-cl' + + def __init__(self, target: str): + super().__init__(target) + + # Assembly + self.can_compile_suffixes.add('s') + + def has_arguments(self, args: T.List[str], env: 'Environment', code: str, mode: str) -> T.Tuple[bool, bool]: + if mode != 'link': + args = args + ['-Werror=unknown-argument', '-Werror=unknown-warning-option'] + return super().has_arguments(args, env, code, mode) + + def get_toolset_version(self) -> T.Optional[str]: + # XXX: what is the right thing to do here? + return '14.1' + + def get_pch_base_name(self, header: str) -> str: + return header + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + if path == '': + path = '.' + return ['/clang:-isystem' + path] if is_system else ['-I' + path] + + def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]: + if dep.get_include_type() == 'system': + converted = [] + for i in dep.get_compile_args(): + if i.startswith('-isystem'): + converted += ['/clang:' + i] + else: + converted += [i] + return converted + else: + return dep.get_compile_args() diff --git a/mesonbuild/compilers/mixins/xc16.py b/mesonbuild/compilers/mixins/xc16.py new file mode 100644 index 0000000..09949a2 --- /dev/null +++ b/mesonbuild/compilers/mixins/xc16.py @@ -0,0 +1,132 @@ +# Copyright 2012-2019 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 __future__ import annotations + +"""Representations specific to the Microchip XC16 C compiler family.""" + +import os +import typing as T + +from ...mesonlib import EnvironmentException + +if T.TYPE_CHECKING: + from ...envconfig import MachineInfo + from ...environment import Environment + from ...compilers.compilers import Compiler +else: + # This is a bit clever, for mypy we pretend that these mixins descend from + # Compiler, so we get all of the methods and attributes defined for us, but + # for runtime we make them descend from object (which all classes normally + # do). This gives up DRYer type checking, with no runtime impact + Compiler = object + +xc16_buildtype_args = { + 'plain': [], + 'debug': [], + 'debugoptimized': [], + 'release': [], + 'minsize': [], + 'custom': [], +} # type: T.Dict[str, T.List[str]] + +xc16_optimization_args = { + 'plain': [], + '0': ['-O0'], + 'g': ['-O0'], + '1': ['-O1'], + '2': ['-O2'], + '3': ['-O3'], + 's': ['-Os'] +} # type: T.Dict[str, T.List[str]] + +xc16_debug_args = { + False: [], + True: [] +} # type: T.Dict[bool, T.List[str]] + + +class Xc16Compiler(Compiler): + + id = 'xc16' + + def __init__(self) -> None: + if not self.is_cross: + raise EnvironmentException('xc16 supports only cross-compilation.') + # Assembly + self.can_compile_suffixes.add('s') + default_warn_args = [] # type: T.List[str] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + [], + '3': default_warn_args + [], + 'everything': default_warn_args + []} # type: T.Dict[str, T.List[str]] + + def get_always_args(self) -> T.List[str]: + return [] + + def get_pic_args(self) -> T.List[str]: + # PIC support is not enabled by default for xc16, + # if users want to use it, they need to add the required arguments explicitly + return [] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return xc16_buildtype_args[buildtype] + + def get_pch_suffix(self) -> str: + return 'pch' + + def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: + return [] + + def thread_flags(self, env: 'Environment') -> T.List[str]: + return [] + + def get_coverage_args(self) -> T.List[str]: + return [] + + def get_no_stdinc_args(self) -> T.List[str]: + return ['-nostdinc'] + + def get_no_stdlib_link_args(self) -> T.List[str]: + return ['--nostdlib'] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return xc16_optimization_args[optimization_level] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return xc16_debug_args[is_debug] + + @classmethod + def _unix_args_to_native(cls, args: T.List[str], info: MachineInfo) -> T.List[str]: + result = [] + for i in args: + if i.startswith('-D'): + i = '-D' + i[2:] + if i.startswith('-I'): + i = '-I' + i[2:] + if i.startswith('-Wl,-rpath='): + continue + elif i == '--print-search-dirs': + continue + elif i.startswith('-L'): + continue + result.append(i) + return result + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:9] == '-I': + parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:])) + + return parameter_list diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py new file mode 100644 index 0000000..83dcaad --- /dev/null +++ b/mesonbuild/compilers/objc.py @@ -0,0 +1,114 @@ +# Copyright 2012-2017 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 __future__ import annotations + +import typing as T + +from .. import coredata +from ..mesonlib import OptionKey + +from .compilers import Compiler +from .mixins.clike import CLikeCompiler +from .mixins.gnu import GnuCompiler, gnu_common_warning_args, gnu_objc_warning_args +from .mixins.clang import ClangCompiler + +if T.TYPE_CHECKING: + from ..programs import ExternalProgram + from ..envconfig import MachineInfo + from ..environment import Environment + from ..linkers import DynamicLinker + from ..mesonlib import MachineChoice + + +class ObjCCompiler(CLikeCompiler, Compiler): + + language = 'objc' + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrap: T.Optional['ExternalProgram'], + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + Compiler.__init__(self, ccache, exelist, version, for_machine, info, + is_cross=is_cross, full_version=full_version, + linker=linker) + CLikeCompiler.__init__(self, exe_wrap) + + @staticmethod + def get_display_language() -> str: + return 'Objective-C' + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + code = '#import<stddef.h>\nint main(void) { return 0; }\n' + return self._sanity_check_impl(work_dir, environment, 'sanitycheckobjc.m', code) + + +class GnuObjCCompiler(GnuCompiler, ObjCCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrapper: T.Optional['ExternalProgram'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + ObjCCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + GnuCompiler.__init__(self, defines) + default_warn_args = ['-Wall', '-Winvalid-pch'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-Wpedantic'], + 'everything': (default_warn_args + ['-Wextra', '-Wpedantic'] + + self.supported_warn_args(gnu_common_warning_args) + + self.supported_warn_args(gnu_objc_warning_args))} + + +class ClangObjCCompiler(ClangCompiler, ObjCCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrapper: T.Optional['ExternalProgram'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + ObjCCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + ClangCompiler.__init__(self, defines) + default_warn_args = ['-Wall', '-Winvalid-pch'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-Wpedantic'], + 'everything': ['-Weverything']} + + def get_options(self) -> 'coredata.MutableKeyedOptionDictType': + opts = super().get_options() + opts.update({ + OptionKey('std', machine=self.for_machine, lang='c'): coredata.UserComboOption( + 'C language standard to use', + ['none', 'c89', 'c99', 'c11', 'c17', 'gnu89', 'gnu99', 'gnu11', 'gnu17'], + 'none', + ) + }) + return opts + + def get_option_compile_args(self, options: 'coredata.KeyedOptionDictType') -> T.List[str]: + args = [] + std = options[OptionKey('std', machine=self.for_machine, lang='c')] + if std.value != 'none': + args.append('-std=' + std.value) + return args + +class AppleClangObjCCompiler(ClangObjCCompiler): + + """Handle the differences between Apple's clang and vanilla clang.""" diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py new file mode 100644 index 0000000..1f9f756 --- /dev/null +++ b/mesonbuild/compilers/objcpp.py @@ -0,0 +1,115 @@ +# Copyright 2012-2017 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 __future__ import annotations + +import typing as T + +from .. import coredata +from ..mesonlib import OptionKey + +from .mixins.clike import CLikeCompiler +from .compilers import Compiler +from .mixins.gnu import GnuCompiler, gnu_common_warning_args, gnu_objc_warning_args +from .mixins.clang import ClangCompiler + +if T.TYPE_CHECKING: + from ..programs import ExternalProgram + from ..envconfig import MachineInfo + from ..environment import Environment + from ..linkers import DynamicLinker + from ..mesonlib import MachineChoice + +class ObjCPPCompiler(CLikeCompiler, Compiler): + + language = 'objcpp' + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrap: T.Optional['ExternalProgram'], + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + Compiler.__init__(self, ccache, exelist, version, for_machine, info, + is_cross=is_cross, full_version=full_version, + linker=linker) + CLikeCompiler.__init__(self, exe_wrap) + + @staticmethod + def get_display_language() -> str: + return 'Objective-C++' + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + code = '#import<stdio.h>\nclass MyClass;int main(void) { return 0; }\n' + return self._sanity_check_impl(work_dir, environment, 'sanitycheckobjcpp.mm', code) + + +class GnuObjCPPCompiler(GnuCompiler, ObjCPPCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrapper: T.Optional['ExternalProgram'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + ObjCPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + GnuCompiler.__init__(self, defines) + default_warn_args = ['-Wall', '-Winvalid-pch'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-Wpedantic'], + 'everything': (default_warn_args + ['-Wextra', '-Wpedantic'] + + self.supported_warn_args(gnu_common_warning_args) + + self.supported_warn_args(gnu_objc_warning_args))} + + +class ClangObjCPPCompiler(ClangCompiler, ObjCPPCompiler): + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrapper: T.Optional['ExternalProgram'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + ObjCPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, + info, exe_wrapper, linker=linker, full_version=full_version) + ClangCompiler.__init__(self, defines) + default_warn_args = ['-Wall', '-Winvalid-pch'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-Wpedantic'], + 'everything': ['-Weverything']} + + def get_options(self) -> 'coredata.MutableKeyedOptionDictType': + opts = super().get_options() + opts.update({ + OptionKey('std', machine=self.for_machine, lang='cpp'): coredata.UserComboOption( + 'C++ language standard to use', + ['none', 'c++98', 'c++11', 'c++14', 'c++17', 'gnu++98', 'gnu++11', 'gnu++14', 'gnu++17'], + 'none', + ) + }) + return opts + + def get_option_compile_args(self, options: 'coredata.KeyedOptionDictType') -> T.List[str]: + args = [] + std = options[OptionKey('std', machine=self.for_machine, lang='cpp')] + if std.value != 'none': + args.append('-std=' + std.value) + return args + + +class AppleClangObjCPPCompiler(ClangObjCPPCompiler): + + """Handle the differences between Apple's clang and vanilla clang.""" diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py new file mode 100644 index 0000000..9e5ebc8 --- /dev/null +++ b/mesonbuild/compilers/rust.py @@ -0,0 +1,208 @@ +# Copyright 2012-2022 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 __future__ import annotations + +import subprocess, os.path +import textwrap +import typing as T + +from .. import coredata +from ..mesonlib import EnvironmentException, MesonException, Popen_safe, OptionKey +from .compilers import Compiler, rust_buildtype_args, clike_debug_args + +if T.TYPE_CHECKING: + from ..coredata import MutableKeyedOptionDictType, KeyedOptionDictType + from ..envconfig import MachineInfo + from ..environment import Environment # noqa: F401 + from ..linkers import DynamicLinker + from ..mesonlib import MachineChoice + from ..programs import ExternalProgram + + +rust_optimization_args = { + 'plain': [], + '0': [], + 'g': ['-C', 'opt-level=0'], + '1': ['-C', 'opt-level=1'], + '2': ['-C', 'opt-level=2'], + '3': ['-C', 'opt-level=3'], + 's': ['-C', 'opt-level=s'], +} # type: T.Dict[str, T.List[str]] + +class RustCompiler(Compiler): + + # rustc doesn't invoke the compiler itself, it doesn't need a LINKER_PREFIX + language = 'rust' + id = 'rustc' + + _WARNING_LEVELS: T.Dict[str, T.List[str]] = { + '0': ['-A', 'warnings'], + '1': [], + '2': [], + '3': ['-W', 'warnings'], + } + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', + exe_wrapper: T.Optional['ExternalProgram'] = None, + full_version: T.Optional[str] = None, + linker: T.Optional['DynamicLinker'] = None): + super().__init__([], exelist, version, for_machine, info, + is_cross=is_cross, full_version=full_version, + linker=linker) + self.exe_wrapper = exe_wrapper + self.base_options.add(OptionKey('b_colorout')) + if 'link' in self.linker.id: + self.base_options.add(OptionKey('b_vscrt')) + + def needs_static_linker(self) -> bool: + return False + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + source_name = os.path.join(work_dir, 'sanity.rs') + output_name = os.path.join(work_dir, 'rusttest') + with open(source_name, 'w', encoding='utf-8') as ofile: + ofile.write(textwrap.dedent( + '''fn main() { + } + ''')) + pc = subprocess.Popen(self.exelist + ['-o', output_name, source_name], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=work_dir) + _stdo, _stde = pc.communicate() + assert isinstance(_stdo, bytes) + assert isinstance(_stde, bytes) + stdo = _stdo.decode('utf-8', errors='replace') + stde = _stde.decode('utf-8', errors='replace') + if pc.returncode != 0: + raise EnvironmentException('Rust compiler {} can not compile programs.\n{}\n{}'.format( + self.name_string(), + stdo, + stde)) + if self.is_cross: + if self.exe_wrapper is None: + # Can't check if the binaries run so we have to assume they do + return + cmdlist = self.exe_wrapper.get_command() + [output_name] + else: + cmdlist = [output_name] + pe = subprocess.Popen(cmdlist, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + pe.wait() + if pe.returncode != 0: + raise EnvironmentException('Executables created by Rust compiler %s are not runnable.' % self.name_string()) + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + return ['--dep-info', outfile] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return rust_buildtype_args[buildtype] + + def get_sysroot(self) -> str: + cmd = self.get_exelist(ccache=False) + ['--print', 'sysroot'] + p, stdo, stde = Popen_safe(cmd) + return stdo.split('\n', maxsplit=1)[0] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return clike_debug_args[is_debug] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return rust_optimization_args[optimization_level] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:2] == '-L': + for j in ['dependency', 'crate', 'native', 'framework', 'all']: + combined_len = len(j) + 3 + if i[:combined_len] == f'-L{j}=': + parameter_list[idx] = i[:combined_len] + os.path.normpath(os.path.join(build_dir, i[combined_len:])) + break + + return parameter_list + + def get_output_args(self, outputname: str) -> T.List[str]: + return ['-o', outputname] + + @classmethod + def use_linker_args(cls, linker: str, version: str) -> T.List[str]: + return ['-C', f'linker={linker}'] + + # Rust does not have a use_linker_args because it dispatches to a gcc-like + # C compiler for dynamic linking, as such we invoke the C compiler's + # use_linker_args method instead. + + def get_options(self) -> 'MutableKeyedOptionDictType': + key = OptionKey('std', machine=self.for_machine, lang=self.language) + return { + key: coredata.UserComboOption( + 'Rust edition to use', + ['none', '2015', '2018', '2021'], + 'none', + ), + } + + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] + if std.value != 'none': + args.append('--edition=' + std.value) + return args + + def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: + # Rust handles this for us, we don't need to do anything + return [] + + def get_colorout_args(self, colortype: str) -> T.List[str]: + if colortype in {'always', 'never', 'auto'}: + return [f'--color={colortype}'] + raise MesonException(f'Invalid color type for rust {colortype}') + + def get_linker_always_args(self) -> T.List[str]: + args: T.List[str] = [] + for a in super().get_linker_always_args(): + args.extend(['-C', f'link-arg={a}']) + return args + + def get_werror_args(self) -> T.List[str]: + # Use -D warnings, which makes every warning not explicitly allowed an + # error + return ['-D', 'warnings'] + + def get_warn_args(self, level: str) -> T.List[str]: + # TODO: I'm not really sure what to put here, Rustc doesn't have warning + return self._WARNING_LEVELS[level] + + def get_no_warn_args(self) -> T.List[str]: + return self._WARNING_LEVELS["0"] + + def get_pic_args(self) -> T.List[str]: + # This defaults to + return ['-C', 'relocation-model=pic'] + + def get_pie_args(self) -> T.List[str]: + # Rustc currently has no way to toggle this, it's controlled by whether + # pic is on by rustc + return [] + + +class ClippyRustCompiler(RustCompiler): + + """Clippy is a linter that wraps Rustc. + + This just provides us a different id + """ + + id = 'clippy-driver rustc' diff --git a/mesonbuild/compilers/swift.py b/mesonbuild/compilers/swift.py new file mode 100644 index 0000000..ec4c7a3 --- /dev/null +++ b/mesonbuild/compilers/swift.py @@ -0,0 +1,130 @@ +# Copyright 2012-2017 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 __future__ import annotations + +import subprocess, os.path +import typing as T + +from ..mesonlib import EnvironmentException + +from .compilers import Compiler, swift_buildtype_args, clike_debug_args + +if T.TYPE_CHECKING: + from ..envconfig import MachineInfo + from ..environment import Environment + from ..linkers import DynamicLinker + from ..mesonlib import MachineChoice + +swift_optimization_args = { + 'plain': [], + '0': [], + 'g': [], + '1': ['-O'], + '2': ['-O'], + '3': ['-O'], + 's': ['-O'], +} # type: T.Dict[str, T.List[str]] + +class SwiftCompiler(Compiler): + + LINKER_PREFIX = ['-Xlinker'] + language = 'swift' + id = 'llvm' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo', full_version: T.Optional[str] = None, + linker: T.Optional['DynamicLinker'] = None): + super().__init__([], exelist, version, for_machine, info, + is_cross=is_cross, full_version=full_version, + linker=linker) + self.version = version + + def needs_static_linker(self) -> bool: + return True + + def get_werror_args(self) -> T.List[str]: + return ['--fatal-warnings'] + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + return ['-emit-dependencies'] + + def depfile_for_object(self, objfile: str) -> T.Optional[str]: + return os.path.splitext(objfile)[0] + '.' + self.get_depfile_suffix() + + def get_depfile_suffix(self) -> str: + return 'd' + + def get_output_args(self, target: str) -> T.List[str]: + return ['-o', target] + + def get_header_import_args(self, headername: str) -> T.List[str]: + return ['-import-objc-header', headername] + + def get_warn_args(self, level: str) -> T.List[str]: + return [] + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return swift_buildtype_args[buildtype] + + def get_std_exe_link_args(self) -> T.List[str]: + return ['-emit-executable'] + + def get_module_args(self, modname: str) -> T.List[str]: + return ['-module-name', modname] + + def get_mod_gen_args(self) -> T.List[str]: + return ['-emit-module'] + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + return ['-I' + path] + + def get_compile_only_args(self) -> T.List[str]: + return ['-c'] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:2] == '-I' or i[:2] == '-L': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + + return parameter_list + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + src = 'swifttest.swift' + source_name = os.path.join(work_dir, src) + output_name = os.path.join(work_dir, 'swifttest') + extra_flags: T.List[str] = [] + extra_flags += environment.coredata.get_external_args(self.for_machine, self.language) + if self.is_cross: + extra_flags += self.get_compile_only_args() + else: + extra_flags += environment.coredata.get_external_link_args(self.for_machine, self.language) + with open(source_name, 'w', encoding='utf-8') as ofile: + ofile.write('''print("Swift compilation is working.") +''') + pc = subprocess.Popen(self.exelist + extra_flags + ['-emit-executable', '-o', output_name, src], cwd=work_dir) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('Swift compiler %s can not compile programs.' % self.name_string()) + if self.is_cross: + # Can't check if the binaries run so we have to assume they do + return + if subprocess.call(output_name) != 0: + raise EnvironmentException('Executables created by Swift compiler %s are not runnable.' % self.name_string()) + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return clike_debug_args[is_debug] + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return swift_optimization_args[optimization_level] diff --git a/mesonbuild/compilers/vala.py b/mesonbuild/compilers/vala.py new file mode 100644 index 0000000..e56a9fc --- /dev/null +++ b/mesonbuild/compilers/vala.py @@ -0,0 +1,140 @@ +# Copyright 2012-2017 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 __future__ import annotations + +import os.path +import typing as T + +from .. import mlog +from ..mesonlib import EnvironmentException, version_compare, OptionKey + +from .compilers import Compiler, LibType + +if T.TYPE_CHECKING: + from ..envconfig import MachineInfo + from ..environment import Environment + from ..mesonlib import MachineChoice + +class ValaCompiler(Compiler): + + language = 'vala' + id = 'valac' + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + is_cross: bool, info: 'MachineInfo'): + super().__init__([], exelist, version, for_machine, info, is_cross=is_cross) + self.version = version + self.base_options = {OptionKey('b_colorout')} + + def needs_static_linker(self) -> bool: + return False # Because compiles into C. + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return [] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return ['--debug'] if is_debug else [] + + def get_output_args(self, target: str) -> T.List[str]: + return [] # Because compiles into C. + + def get_compile_only_args(self) -> T.List[str]: + return [] # Because compiles into C. + + def get_pic_args(self) -> T.List[str]: + return [] + + def get_pie_args(self) -> T.List[str]: + return [] + + def get_pie_link_args(self) -> T.List[str]: + return [] + + def get_always_args(self) -> T.List[str]: + return ['-C'] + + def get_warn_args(self, warning_level: str) -> T.List[str]: + return [] + + def get_no_warn_args(self) -> T.List[str]: + return ['--disable-warnings'] + + def get_werror_args(self) -> T.List[str]: + return ['--fatal-warnings'] + + def get_colorout_args(self, colortype: str) -> T.List[str]: + if version_compare(self.version, '>=0.37.1'): + return ['--color=' + colortype] + return [] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:9] == '--girdir=': + parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:])) + if i[:10] == '--vapidir=': + parameter_list[idx] = i[:10] + os.path.normpath(os.path.join(build_dir, i[10:])) + if i[:13] == '--includedir=': + parameter_list[idx] = i[:13] + os.path.normpath(os.path.join(build_dir, i[13:])) + if i[:14] == '--metadatadir=': + parameter_list[idx] = i[:14] + os.path.normpath(os.path.join(build_dir, i[14:])) + + return parameter_list + + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + code = 'class MesonSanityCheck : Object { }' + extra_flags: T.List[str] = [] + extra_flags += environment.coredata.get_external_args(self.for_machine, self.language) + if self.is_cross: + extra_flags += self.get_compile_only_args() + else: + extra_flags += environment.coredata.get_external_link_args(self.for_machine, self.language) + with self.cached_compile(code, environment.coredata, extra_args=extra_flags, mode='compile') as p: + if p.returncode != 0: + msg = f'Vala compiler {self.name_string()!r} can not compile programs' + raise EnvironmentException(msg) + + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + if buildtype in {'debug', 'debugoptimized', 'minsize'}: + return ['--debug'] + return [] + + def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], + libtype: LibType = LibType.PREFER_SHARED) -> T.Optional[T.List[str]]: + if extra_dirs and isinstance(extra_dirs, str): + extra_dirs = [extra_dirs] + # Valac always looks in the default vapi dir, so only search there if + # no extra dirs are specified. + if not extra_dirs: + code = 'class MesonFindLibrary : Object { }' + args: T.List[str] = [] + args += env.coredata.get_external_args(self.for_machine, self.language) + vapi_args = ['--pkg', libname] + args += vapi_args + with self.cached_compile(code, env.coredata, extra_args=args, mode='compile') as p: + if p.returncode == 0: + return vapi_args + # Not found? Try to find the vapi file itself. + for d in extra_dirs: + vapi = os.path.join(d, libname + '.vapi') + if os.path.isfile(vapi): + return [vapi] + mlog.debug(f'Searched {extra_dirs!r} and {libname!r} wasn\'t found') + return None + + def thread_flags(self, env: 'Environment') -> T.List[str]: + return [] + + def thread_link_flags(self, env: 'Environment') -> T.List[str]: + return [] |