1# Copyright 2019 The meson development team 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Provides mixins for GNU compilers and GNU-like compilers.""" 16 17import abc 18import functools 19import os 20import multiprocessing 21import pathlib 22import re 23import subprocess 24import typing as T 25 26from ... import mesonlib 27from ... import mlog 28from ...mesonlib import OptionKey 29 30if T.TYPE_CHECKING: 31 from ..._typing import ImmutableListProtocol 32 from ...environment import Environment 33 from ..compilers import Compiler 34else: 35 # This is a bit clever, for mypy we pretend that these mixins descend from 36 # Compiler, so we get all of the methods and attributes defined for us, but 37 # for runtime we make them descend from object (which all classes normally 38 # do). This gives up DRYer type checking, with no runtime impact 39 Compiler = object 40 41# XXX: prevent circular references. 42# FIXME: this really is a posix interface not a c-like interface 43clike_debug_args = { 44 False: [], 45 True: ['-g'], 46} # type: T.Dict[bool, T.List[str]] 47 48gnulike_buildtype_args = { 49 'plain': [], 50 'debug': [], 51 'debugoptimized': [], 52 'release': [], 53 'minsize': [], 54 'custom': [], 55} # type: T.Dict[str, T.List[str]] 56 57gnu_optimization_args = { 58 '0': [], 59 'g': ['-Og'], 60 '1': ['-O1'], 61 '2': ['-O2'], 62 '3': ['-O3'], 63 's': ['-Os'], 64} # type: T.Dict[str, T.List[str]] 65 66gnulike_instruction_set_args = { 67 'mmx': ['-mmmx'], 68 'sse': ['-msse'], 69 'sse2': ['-msse2'], 70 'sse3': ['-msse3'], 71 'ssse3': ['-mssse3'], 72 'sse41': ['-msse4.1'], 73 'sse42': ['-msse4.2'], 74 'avx': ['-mavx'], 75 'avx2': ['-mavx2'], 76 'neon': ['-mfpu=neon'], 77} # type: T.Dict[str, T.List[str]] 78 79gnu_symbol_visibility_args = { 80 '': [], 81 'default': ['-fvisibility=default'], 82 'internal': ['-fvisibility=internal'], 83 'hidden': ['-fvisibility=hidden'], 84 'protected': ['-fvisibility=protected'], 85 'inlineshidden': ['-fvisibility=hidden', '-fvisibility-inlines-hidden'], 86} # type: T.Dict[str, T.List[str]] 87 88gnu_color_args = { 89 'auto': ['-fdiagnostics-color=auto'], 90 'always': ['-fdiagnostics-color=always'], 91 'never': ['-fdiagnostics-color=never'], 92} # type: T.Dict[str, T.List[str]] 93 94 95@functools.lru_cache(maxsize=None) 96def gnulike_default_include_dirs(compiler: T.Tuple[str, ...], lang: str) -> 'ImmutableListProtocol[str]': 97 lang_map = { 98 'c': 'c', 99 'cpp': 'c++', 100 'objc': 'objective-c', 101 'objcpp': 'objective-c++' 102 } 103 if lang not in lang_map: 104 return [] 105 lang = lang_map[lang] 106 env = os.environ.copy() 107 env["LC_ALL"] = 'C' 108 cmd = list(compiler) + [f'-x{lang}', '-E', '-v', '-'] 109 p = subprocess.Popen( 110 cmd, 111 stdin=subprocess.DEVNULL, 112 stderr=subprocess.STDOUT, 113 stdout=subprocess.PIPE, 114 env=env 115 ) 116 stdout = p.stdout.read().decode('utf-8', errors='replace') 117 parse_state = 0 118 paths = [] # type: T.List[str] 119 for line in stdout.split('\n'): 120 line = line.strip(' \n\r\t') 121 if parse_state == 0: 122 if line == '#include "..." search starts here:': 123 parse_state = 1 124 elif parse_state == 1: 125 if line == '#include <...> search starts here:': 126 parse_state = 2 127 else: 128 paths.append(line) 129 elif parse_state == 2: 130 if line == 'End of search list.': 131 break 132 else: 133 paths.append(line) 134 if not paths: 135 mlog.warning('No include directory found parsing "{cmd}" output'.format(cmd=" ".join(cmd))) 136 # Append a normalized copy of paths to make path lookup easier 137 paths += [os.path.normpath(x) for x in paths] 138 return paths 139 140 141class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta): 142 """ 143 GnuLikeCompiler is a common interface to all compilers implementing 144 the GNU-style commandline interface. This includes GCC, Clang 145 and ICC. Certain functionality between them is different and requires 146 that the actual concrete subclass define their own implementation. 147 """ 148 149 LINKER_PREFIX = '-Wl,' 150 151 def __init__(self) -> None: 152 self.base_options = { 153 OptionKey(o) for o in ['b_pch', 'b_lto', 'b_pgo', 'b_coverage', 154 'b_ndebug', 'b_staticpic', 'b_pie']} 155 if not (self.info.is_windows() or self.info.is_cygwin() or self.info.is_openbsd()): 156 self.base_options.add(OptionKey('b_lundef')) 157 if not self.info.is_windows() or self.info.is_cygwin(): 158 self.base_options.add(OptionKey('b_asneeded')) 159 if not self.info.is_hurd(): 160 self.base_options.add(OptionKey('b_sanitize')) 161 # All GCC-like backends can do assembly 162 self.can_compile_suffixes.add('s') 163 164 def get_pic_args(self) -> T.List[str]: 165 if self.info.is_windows() or self.info.is_cygwin() or self.info.is_darwin(): 166 return [] # On Window and OS X, pic is always on. 167 return ['-fPIC'] 168 169 def get_pie_args(self) -> T.List[str]: 170 return ['-fPIE'] 171 172 def get_buildtype_args(self, buildtype: str) -> T.List[str]: 173 return gnulike_buildtype_args[buildtype] 174 175 @abc.abstractmethod 176 def get_optimization_args(self, optimization_level: str) -> T.List[str]: 177 pass 178 179 def get_debug_args(self, is_debug: bool) -> T.List[str]: 180 return clike_debug_args[is_debug] 181 182 @abc.abstractmethod 183 def get_pch_suffix(self) -> str: 184 pass 185 186 def split_shlib_to_parts(self, fname: str) -> T.Tuple[str, str]: 187 return os.path.dirname(fname), fname 188 189 def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: 190 return gnulike_instruction_set_args.get(instruction_set, None) 191 192 def get_default_include_dirs(self) -> T.List[str]: 193 return gnulike_default_include_dirs(tuple(self.exelist), self.language).copy() 194 195 @abc.abstractmethod 196 def openmp_flags(self) -> T.List[str]: 197 pass 198 199 def gnu_symbol_visibility_args(self, vistype: str) -> T.List[str]: 200 return gnu_symbol_visibility_args[vistype] 201 202 def gen_vs_module_defs_args(self, defsfile: str) -> T.List[str]: 203 if not isinstance(defsfile, str): 204 raise RuntimeError('Module definitions file should be str') 205 # On Windows targets, .def files may be specified on the linker command 206 # line like an object file. 207 if self.info.is_windows() or self.info.is_cygwin(): 208 return [defsfile] 209 # For other targets, discard the .def file. 210 return [] 211 212 def get_argument_syntax(self) -> str: 213 return 'gcc' 214 215 def get_profile_generate_args(self) -> T.List[str]: 216 return ['-fprofile-generate'] 217 218 def get_profile_use_args(self) -> T.List[str]: 219 return ['-fprofile-use', '-fprofile-correction'] 220 221 def get_gui_app_args(self, value: bool) -> T.List[str]: 222 return ['-mwindows' if value else '-mconsole'] 223 224 def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: 225 for idx, i in enumerate(parameter_list): 226 if i[:2] == '-I' or i[:2] == '-L': 227 parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) 228 229 return parameter_list 230 231 @functools.lru_cache() 232 def _get_search_dirs(self, env: 'Environment') -> str: 233 extra_args = ['--print-search-dirs'] 234 with self._build_wrapper('', env, extra_args=extra_args, 235 dependencies=None, mode='compile', 236 want_output=True) as p: 237 return p.stdout 238 239 def _split_fetch_real_dirs(self, pathstr: str) -> T.List[str]: 240 # We need to use the path separator used by the compiler for printing 241 # lists of paths ("gcc --print-search-dirs"). By default 242 # we assume it uses the platform native separator. 243 pathsep = os.pathsep 244 245 # clang uses ':' instead of ';' on Windows https://reviews.llvm.org/D61121 246 # so we need to repair things like 'C:\foo:C:\bar' 247 if pathsep == ';': 248 pathstr = re.sub(r':([^/\\])', r';\1', pathstr) 249 250 # pathlib treats empty paths as '.', so filter those out 251 paths = [p for p in pathstr.split(pathsep) if p] 252 253 result = [] 254 for p in paths: 255 # GCC returns paths like this: 256 # /usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/lib 257 # It would make sense to normalize them to get rid of the .. parts 258 # Sadly when you are on a merged /usr fs it also kills these: 259 # /lib/x86_64-linux-gnu 260 # since /lib is a symlink to /usr/lib. This would mean 261 # paths under /lib would be considered not a "system path", 262 # which is wrong and breaks things. Store everything, just to be sure. 263 pobj = pathlib.Path(p) 264 unresolved = pobj.as_posix() 265 if pobj.exists(): 266 if unresolved not in result: 267 result.append(unresolved) 268 try: 269 resolved = pathlib.Path(p).resolve().as_posix() 270 if resolved not in result: 271 result.append(resolved) 272 except FileNotFoundError: 273 pass 274 return result 275 276 def get_compiler_dirs(self, env: 'Environment', name: str) -> T.List[str]: 277 ''' 278 Get dirs from the compiler, either `libraries:` or `programs:` 279 ''' 280 stdo = self._get_search_dirs(env) 281 for line in stdo.split('\n'): 282 if line.startswith(name + ':'): 283 return self._split_fetch_real_dirs(line.split('=', 1)[1]) 284 return [] 285 286 def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]: 287 # This provides a base for many compilers, GCC and Clang override this 288 # for their specific arguments 289 return ['-flto'] 290 291 def sanitizer_compile_args(self, value: str) -> T.List[str]: 292 if value == 'none': 293 return [] 294 args = ['-fsanitize=' + value] 295 if 'address' in value: # for -fsanitize=address,undefined 296 args.append('-fno-omit-frame-pointer') 297 return args 298 299 def get_output_args(self, target: str) -> T.List[str]: 300 return ['-o', target] 301 302 def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: 303 return ['-MD', '-MQ', outtarget, '-MF', outfile] 304 305 def get_compile_only_args(self) -> T.List[str]: 306 return ['-c'] 307 308 def get_include_args(self, path: str, is_system: bool) -> T.List[str]: 309 if not path: 310 path = '.' 311 if is_system: 312 return ['-isystem' + path] 313 return ['-I' + path] 314 315 @classmethod 316 def use_linker_args(cls, linker: str) -> T.List[str]: 317 if linker not in {'gold', 'bfd', 'lld'}: 318 raise mesonlib.MesonException( 319 f'Unsupported linker, only bfd, gold, and lld are supported, not {linker}.') 320 return [f'-fuse-ld={linker}'] 321 322 def get_coverage_args(self) -> T.List[str]: 323 return ['--coverage'] 324 325 326class GnuCompiler(GnuLikeCompiler): 327 """ 328 GnuCompiler represents an actual GCC in its many incarnations. 329 Compilers imitating GCC (Clang/Intel) should use the GnuLikeCompiler ABC. 330 """ 331 332 def __init__(self, defines: T.Optional[T.Dict[str, str]]): 333 super().__init__() 334 self.id = 'gcc' 335 self.defines = defines or {} 336 self.base_options.update({OptionKey('b_colorout'), OptionKey('b_lto_threads')}) 337 338 def get_colorout_args(self, colortype: str) -> T.List[str]: 339 if mesonlib.version_compare(self.version, '>=4.9.0'): 340 return gnu_color_args[colortype][:] 341 return [] 342 343 def get_warn_args(self, level: str) -> T.List[str]: 344 # Mypy doesn't understand cooperative inheritance 345 args = super().get_warn_args(level) 346 if mesonlib.version_compare(self.version, '<4.8.0') and '-Wpedantic' in args: 347 # -Wpedantic was added in 4.8.0 348 # https://gcc.gnu.org/gcc-4.8/changes.html 349 args[args.index('-Wpedantic')] = '-pedantic' 350 return args 351 352 def has_builtin_define(self, define: str) -> bool: 353 return define in self.defines 354 355 def get_builtin_define(self, define: str) -> T.Optional[str]: 356 if define in self.defines: 357 return self.defines[define] 358 return None 359 360 def get_optimization_args(self, optimization_level: str) -> T.List[str]: 361 return gnu_optimization_args[optimization_level] 362 363 def get_pch_suffix(self) -> str: 364 return 'gch' 365 366 def openmp_flags(self) -> T.List[str]: 367 return ['-fopenmp'] 368 369 def has_arguments(self, args: T.List[str], env: 'Environment', code: str, 370 mode: str) -> T.Tuple[bool, bool]: 371 # For some compiler command line arguments, the GNU compilers will 372 # emit a warning on stderr indicating that an option is valid for a 373 # another language, but still complete with exit_success 374 with self._build_wrapper(code, env, args, None, mode) as p: 375 result = p.returncode == 0 376 if self.language in {'cpp', 'objcpp'} and 'is valid for C/ObjC' in p.stderr: 377 result = False 378 if self.language in {'c', 'objc'} and 'is valid for C++/ObjC++' in p.stderr: 379 result = False 380 return result, p.cached 381 382 def get_has_func_attribute_extra_args(self, name: str) -> T.List[str]: 383 # GCC only warns about unknown or ignored attributes, so force an 384 # error. 385 return ['-Werror=attributes'] 386 387 def get_prelink_args(self, prelink_name: str, obj_list: T.List[str]) -> T.List[str]: 388 return ['-r', '-o', prelink_name] + obj_list 389 390 def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]: 391 if threads == 0: 392 if mesonlib.version_compare(self.version, '>= 10.0'): 393 return ['-flto=auto'] 394 # This matches clang's behavior of using the number of cpus 395 return [f'-flto={multiprocessing.cpu_count()}'] 396 elif threads > 0: 397 return [f'-flto={threads}'] 398 return super().get_lto_compile_args(threads=threads) 399