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