diff options
Diffstat (limited to 'meson/mesonbuild/compilers/mixins/gnu.py')
-rw-r--r-- | meson/mesonbuild/compilers/mixins/gnu.py | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/meson/mesonbuild/compilers/mixins/gnu.py b/meson/mesonbuild/compilers/mixins/gnu.py new file mode 100644 index 000000000..bc40af494 --- /dev/null +++ b/meson/mesonbuild/compilers/mixins/gnu.py @@ -0,0 +1,398 @@ +# 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. + +"""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 = { + '0': [], + '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]] + + +@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', '-'] + p = subprocess.Popen( + cmd, + stdin=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + env=env + ) + stdout = p.stdout.read().decode('utf-8', errors='replace') + 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.exelist), self.language).copy() + + @abc.abstractmethod + def openmp_flags(self) -> T.List[str]: + pass + + def gnu_symbol_visibility_args(self, vistype: str) -> T.List[str]: + 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', '-fprofile-correction'] + + 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) -> 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'] + + +class GnuCompiler(GnuLikeCompiler): + """ + GnuCompiler represents an actual GCC in its many incarnations. + Compilers imitating GCC (Clang/Intel) should use the GnuLikeCompiler ABC. + """ + + def __init__(self, defines: T.Optional[T.Dict[str, str]]): + super().__init__() + self.id = 'gcc' + 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 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) |