# 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 pathlib import re import subprocess import typing from ... import mesonlib from ... import mlog if typing.TYPE_CHECKING: from ..compilers import CompilerType from ...coredata import UserOption # noqa: F401 from ...environment import Environment # XXX: prevent circular references. # FIXME: this really is a posix interface not a c-like interface clike_debug_args = { False: [], True: ['-g'], } # type: typing.Dict[bool, typing.List[str]] gnulike_buildtype_args = { 'plain': [], 'debug': [], 'debugoptimized': [], 'release': [], 'minsize': [], 'custom': [], } # type: typing.Dict[str, typing.List[str]] apple_buildtype_linker_args = { 'plain': [], 'debug': [], 'debugoptimized': [], 'release': [], 'minsize': [], 'custom': [], } # type: typing.Dict[str, typing.List[str]] gnulike_buildtype_linker_args = { 'plain': [], 'debug': [], 'debugoptimized': [], 'release': ['-Wl,-O1'], 'minsize': [], 'custom': [], } # type: typing.Dict[str, typing.List[str]] gnu_optimization_args = { '0': [], 'g': ['-Og'], '1': ['-O1'], '2': ['-O2'], '3': ['-O3'], 's': ['-Os'], } # type: typing.Dict[str, typing.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: typing.Dict[str, typing.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: typing.Dict[str, typing.List[str]] gnu_color_args = { 'auto': ['-fdiagnostics-color=auto'], 'always': ['-fdiagnostics-color=always'], 'never': ['-fdiagnostics-color=never'], } # type: typing.Dict[str, typing.List[str]] def get_macos_dylib_install_name(prefix: str, shlib_name: str, suffix: str, soversion: str) -> str: install_name = prefix + shlib_name if soversion is not None: install_name += '.' + soversion install_name += '.dylib' return '@rpath/' + install_name def get_gcc_soname_args(compiler_type: 'CompilerType', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: typing.Tuple[str, str], is_shared_module: bool) -> typing.List[str]: if compiler_type.is_standard_compiler: sostr = '' if soversion is None else '.' + soversion return ['-Wl,-soname,%s%s.%s%s' % (prefix, shlib_name, suffix, sostr)] elif compiler_type.is_windows_compiler: # For PE/COFF the soname argument has no effect with GNU LD return [] elif compiler_type.is_osx_compiler: if is_shared_module: return [] name = get_macos_dylib_install_name(prefix, shlib_name, suffix, soversion) args = ['-install_name', name] if darwin_versions: args += ['-compatibility_version', darwin_versions[0], '-current_version', darwin_versions[1]] return args else: raise RuntimeError('Not implemented yet.') # TODO: The result from calling compiler should be cached. So that calling this # function multiple times don't add latency. def gnulike_default_include_dirs(compiler: typing.List[str], lang: str) -> typing.List[str]: if lang == 'cpp': lang = 'c++' env = os.environ.copy() env["LC_ALL"] = 'C' cmd = compiler + ['-x{}'.format(lang), '-E', '-v', '-'] p = subprocess.Popen( cmd, stdin=subprocess.DEVNULL, stderr=subprocess.PIPE, stdout=subprocess.PIPE, env=env ) stderr = p.stderr.read().decode('utf-8', errors='replace') parse_state = 0 paths = [] for line in stderr.split('\n'): 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[1:]) elif parse_state == 2: if line == 'End of search list.': break else: paths.append(line[1:]) if not paths: mlog.warning('No include directory found parsing "{cmd}" output'.format(cmd=" ".join(cmd))) return paths class GnuLikeCompiler(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. """ def __init__(self, compiler_type: 'CompilerType'): self.compiler_type = compiler_type self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage', 'b_ndebug', 'b_staticpic', 'b_pie'] if (not self.compiler_type.is_osx_compiler and not self.compiler_type.is_windows_compiler and not mesonlib.is_openbsd()): self.base_options.append('b_lundef') if not self.compiler_type.is_windows_compiler: self.base_options.append('b_asneeded') # All GCC-like backends can do assembly self.can_compile_suffixes.add('s') def get_asneeded_args(self) -> str: # GNU ld cannot be installed on macOS # https://github.com/Homebrew/homebrew-core/issues/17794#issuecomment-328174395 # Hence, we don't need to differentiate between OS and ld # for the sake of adding as-needed support if self.compiler_type.is_osx_compiler: return '-Wl,-dead_strip_dylibs' else: return '-Wl,--as-needed' def get_pic_args(self) -> typing.List[str]: if self.compiler_type.is_osx_compiler or self.compiler_type.is_windows_compiler: return [] # On Window and OS X, pic is always on. return ['-fPIC'] def get_pie_args(self) -> typing.List[str]: return ['-fPIE'] def get_pie_link_args(self) -> typing.List[str]: return ['-pie'] def get_buildtype_args(self, buildtype: str) -> typing.List[str]: return gnulike_buildtype_args[buildtype] @abc.abstractmethod def get_optimization_args(self, optimization_level: str) -> typing.List[str]: raise NotImplementedError("get_optimization_args not implemented") def get_debug_args(self, is_debug: bool) -> typing.List[str]: return clike_debug_args[is_debug] def get_buildtype_linker_args(self, buildtype: str) -> typing.List[str]: if self.compiler_type.is_osx_compiler: return apple_buildtype_linker_args[buildtype] return gnulike_buildtype_linker_args[buildtype] @abc.abstractmethod def get_pch_suffix(self) -> str: raise NotImplementedError("get_pch_suffix not implemented") def split_shlib_to_parts(self, fname: str) -> typing.Tuple[str, str]: return os.path.dirname(fname), fname # We're doing argument proxying here, I don't think there's anyway to # accurately model this without copying the real signature def get_soname_args(self, *args: typing.Any) -> typing.List[str]: return get_gcc_soname_args(self.compiler_type, *args) def get_std_shared_lib_link_args(self) -> typing.List[str]: return ['-shared'] def get_std_shared_module_link_args(self, options: typing.Dict[str, 'UserOption[typing.Any]']) -> typing.List[str]: if self.compiler_type.is_osx_compiler: return ['-bundle', '-Wl,-undefined,dynamic_lookup'] return ['-shared'] def get_link_whole_for(self, args: typing.List[str]) -> typing.List[str]: if self.compiler_type.is_osx_compiler: result = [] # type: typing.List[str] for a in args: result += ['-Wl,-force_load', a] return result return ['-Wl,--whole-archive'] + args + ['-Wl,--no-whole-archive'] def get_instruction_set_args(self, instruction_set: str) -> typing.Optional[typing.List[str]]: return gnulike_instruction_set_args.get(instruction_set, None) def get_default_include_dirs(self) -> typing.List[str]: return gnulike_default_include_dirs(self.exelist, self.language) @abc.abstractmethod def openmp_flags(self) -> typing.List[str]: raise NotImplementedError("openmp_flags not implemented") def gnu_symbol_visibility_args(self, vistype: str) -> typing.List[str]: return gnu_symbol_visibility_args[vistype] def gen_vs_module_defs_args(self, defsfile: str) -> typing.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.compiler_type.is_windows_compiler: return [defsfile] # For other targets, discard the .def file. return [] def get_argument_syntax(self) -> str: return 'gcc' def get_profile_generate_args(self) -> typing.List[str]: return ['-fprofile-generate'] def get_profile_use_args(self) -> typing.List[str]: return ['-fprofile-use', '-fprofile-correction'] def get_allow_undefined_link_args(self) -> typing.List[str]: if self.compiler_type.is_osx_compiler: # Apple ld return ['-Wl,-undefined,dynamic_lookup'] elif self.compiler_type.is_windows_compiler: # For PE/COFF this is impossible return [] elif mesonlib.is_sunos(): return [] else: # GNU ld and LLVM lld return ['-Wl,--allow-shlib-undefined'] def get_gui_app_args(self, value: bool) -> typing.List[str]: if self.compiler_type.is_windows_compiler: return ['-mwindows' if value else '-mconsole'] return [] def compute_parameters_with_absolute_paths(self, parameter_list: typing.List[str], build_dir: str) -> typing.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'] stdo = None with self._build_wrapper('', env, extra_args=extra_args, dependencies=None, mode='compile', want_output=True) as p: stdo = p.stdo return stdo def _split_fetch_real_dirs(self, pathstr: str) -> typing.Tuple[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 tuple(result) def get_compiler_dirs(self, env: 'Environment', name: str) -> typing.Tuple[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 () 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, compiler_type: 'CompilerType', defines: typing.Dict[str, str]): super().__init__(compiler_type) self.id = 'gcc' self.defines = defines or {} self.base_options.append('b_colorout') def get_colorout_args(self, colortype: str) -> typing.List[str]: if mesonlib.version_compare(self.version, '>=4.9.0'): return gnu_color_args[colortype][:] return [] def get_warn_args(self, level: str) -> typing.List[str]: 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) -> typing.Optional[str]: if define in self.defines: return self.defines[define] return None def get_optimization_args(self, optimization_level: str) -> typing.List[str]: return gnu_optimization_args[optimization_level] def get_pch_suffix(self) -> str: return 'gch' def openmp_flags(self) -> typing.List[str]: return ['-fopenmp']