diff options
Diffstat (limited to 'mesonbuild/compilers/mixins/gnu.py')
-rw-r--r-- | mesonbuild/compilers/mixins/gnu.py | 649 |
1 files changed, 649 insertions, 0 deletions
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'] |