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"""Abstractions to simplify compilers that implement an MSVC compatible 16interface. 17""" 18 19import abc 20import os 21import typing as T 22 23from ... import arglist 24from ... import mesonlib 25from ... import mlog 26 27if T.TYPE_CHECKING: 28 from ...environment import Environment 29 from .clike import CLikeCompiler as Compiler 30else: 31 # This is a bit clever, for mypy we pretend that these mixins descend from 32 # Compiler, so we get all of the methods and attributes defined for us, but 33 # for runtime we make them descend from object (which all classes normally 34 # do). This gives up DRYer type checking, with no runtime impact 35 Compiler = object 36 37vs32_instruction_set_args = { 38 'mmx': ['/arch:SSE'], # There does not seem to be a flag just for MMX 39 'sse': ['/arch:SSE'], 40 'sse2': ['/arch:SSE2'], 41 'sse3': ['/arch:AVX'], # VS leaped from SSE2 directly to AVX. 42 'sse41': ['/arch:AVX'], 43 'sse42': ['/arch:AVX'], 44 'avx': ['/arch:AVX'], 45 'avx2': ['/arch:AVX2'], 46 'neon': None, 47} # T.Dicst[str, T.Optional[T.List[str]]] 48 49# The 64 bit compiler defaults to /arch:avx. 50vs64_instruction_set_args = { 51 'mmx': ['/arch:AVX'], 52 'sse': ['/arch:AVX'], 53 'sse2': ['/arch:AVX'], 54 'sse3': ['/arch:AVX'], 55 'ssse3': ['/arch:AVX'], 56 'sse41': ['/arch:AVX'], 57 'sse42': ['/arch:AVX'], 58 'avx': ['/arch:AVX'], 59 'avx2': ['/arch:AVX2'], 60 'neon': None, 61} # T.Dicst[str, T.Optional[T.List[str]]] 62 63msvc_optimization_args = { 64 '0': ['/Od'], 65 'g': [], # No specific flag to optimize debugging, /Zi or /ZI will create debug information 66 '1': ['/O1'], 67 '2': ['/O2'], 68 '3': ['/O2', '/Gw'], 69 's': ['/O1', '/Gw'], 70} # type: T.Dict[str, T.List[str]] 71 72msvc_debug_args = { 73 False: [], 74 True: ['/Zi'] 75} # type: T.Dict[bool, T.List[str]] 76 77 78class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): 79 80 """A common interface for all compilers implementing an MSVC-style 81 interface. 82 83 A number of compilers attempt to mimic MSVC, with varying levels of 84 success, such as Clang-CL and ICL (the Intel C/C++ Compiler for Windows). 85 This class implements as much common logic as possible. 86 """ 87 88 std_warn_args = ['/W3'] 89 std_opt_args = ['/O2'] 90 ignore_libs = arglist.UNIXY_COMPILER_INTERNAL_LIBS + ['execinfo'] 91 internal_libs = [] # type: T.List[str] 92 93 crt_args = { 94 'none': [], 95 'md': ['/MD'], 96 'mdd': ['/MDd'], 97 'mt': ['/MT'], 98 'mtd': ['/MTd'], 99 } # type: T.Dict[str, T.List[str]] 100 101 # /showIncludes is needed for build dependency tracking in Ninja 102 # See: https://ninja-build.org/manual.html#_deps 103 # Assume UTF-8 sources by default, but self.unix_args_to_native() removes it 104 # if `/source-charset` is set too. 105 always_args = ['/nologo', '/showIncludes', '/utf-8'] 106 warn_args = { 107 '0': [], 108 '1': ['/W2'], 109 '2': ['/W3'], 110 '3': ['/W4'], 111 } # type: T.Dict[str, T.List[str]] 112 113 INVOKES_LINKER = False 114 115 def __init__(self, target: str): 116 self.base_options = {mesonlib.OptionKey(o) for o in ['b_pch', 'b_ndebug', 'b_vscrt']} # FIXME add lto, pgo and the like 117 self.target = target 118 self.is_64 = ('x64' in target) or ('x86_64' in target) 119 # do some canonicalization of target machine 120 if 'x86_64' in target: 121 self.machine = 'x64' 122 elif '86' in target: 123 self.machine = 'x86' 124 elif 'aarch64' in target: 125 self.machine = 'arm64' 126 elif 'arm' in target: 127 self.machine = 'arm' 128 else: 129 self.machine = target 130 if mesonlib.version_compare(self.version, '>=19.28.29910'): # VS 16.9.0 includes cl 19.28.29910 131 self.base_options.add(mesonlib.OptionKey('b_sanitize')) 132 assert self.linker is not None 133 self.linker.machine = self.machine 134 135 # Override CCompiler.get_always_args 136 def get_always_args(self) -> T.List[str]: 137 return self.always_args 138 139 def get_pch_suffix(self) -> str: 140 return 'pch' 141 142 def get_pch_name(self, header: str) -> str: 143 chopped = os.path.basename(header).split('.')[:-1] 144 chopped.append(self.get_pch_suffix()) 145 pchname = '.'.join(chopped) 146 return pchname 147 148 def get_pch_base_name(self, header: str) -> str: 149 # This needs to be implemented by inheriting classes 150 raise NotImplementedError 151 152 def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: 153 base = self.get_pch_base_name(header) 154 pchname = self.get_pch_name(header) 155 return ['/FI' + base, '/Yu' + base, '/Fp' + os.path.join(pch_dir, pchname)] 156 157 def get_preprocess_only_args(self) -> T.List[str]: 158 return ['/EP'] 159 160 def get_compile_only_args(self) -> T.List[str]: 161 return ['/c'] 162 163 def get_no_optimization_args(self) -> T.List[str]: 164 return ['/Od', '/Oi-'] 165 166 def sanitizer_compile_args(self, value: str) -> T.List[str]: 167 if value == 'none': 168 return [] 169 if value != 'address': 170 raise mesonlib.MesonException('VS only supports address sanitizer at the moment.') 171 return ['/fsanitize=address'] 172 173 def get_output_args(self, target: str) -> T.List[str]: 174 if target.endswith('.exe'): 175 return ['/Fe' + target] 176 return ['/Fo' + target] 177 178 def get_buildtype_args(self, buildtype: str) -> T.List[str]: 179 return [] 180 181 def get_debug_args(self, is_debug: bool) -> T.List[str]: 182 return msvc_debug_args[is_debug] 183 184 def get_optimization_args(self, optimization_level: str) -> T.List[str]: 185 args = msvc_optimization_args[optimization_level] 186 if mesonlib.version_compare(self.version, '<18.0'): 187 args = [arg for arg in args if arg != '/Gw'] 188 return args 189 190 def linker_to_compiler_args(self, args: T.List[str]) -> T.List[str]: 191 return ['/link'] + args 192 193 def get_pic_args(self) -> T.List[str]: 194 return [] # PIC is handled by the loader on Windows 195 196 def gen_vs_module_defs_args(self, defsfile: str) -> T.List[str]: 197 if not isinstance(defsfile, str): 198 raise RuntimeError('Module definitions file should be str') 199 # With MSVC, DLLs only export symbols that are explicitly exported, 200 # so if a module defs file is specified, we use that to export symbols 201 return ['/DEF:' + defsfile] 202 203 def gen_pch_args(self, header: str, source: str, pchname: str) -> T.Tuple[str, T.List[str]]: 204 objname = os.path.splitext(pchname)[0] + '.obj' 205 return objname, ['/Yc' + header, '/Fp' + pchname, '/Fo' + objname] 206 207 def openmp_flags(self) -> T.List[str]: 208 return ['/openmp'] 209 210 def openmp_link_flags(self) -> T.List[str]: 211 return [] 212 213 # FIXME, no idea what these should be. 214 def thread_flags(self, env: 'Environment') -> T.List[str]: 215 return [] 216 217 @classmethod 218 def unix_args_to_native(cls, args: T.List[str]) -> T.List[str]: 219 result: T.List[str] = [] 220 for i in args: 221 # -mms-bitfields is specific to MinGW-GCC 222 # -pthread is only valid for GCC 223 if i in ('-mms-bitfields', '-pthread'): 224 continue 225 if i.startswith('-LIBPATH:'): 226 i = '/LIBPATH:' + i[9:] 227 elif i.startswith('-L'): 228 i = '/LIBPATH:' + i[2:] 229 # Translate GNU-style -lfoo library name to the import library 230 elif i.startswith('-l'): 231 name = i[2:] 232 if name in cls.ignore_libs: 233 # With MSVC, these are provided by the C runtime which is 234 # linked in by default 235 continue 236 else: 237 i = name + '.lib' 238 elif i.startswith('-isystem'): 239 # just use /I for -isystem system include path s 240 if i.startswith('-isystem='): 241 i = '/I' + i[9:] 242 else: 243 i = '/I' + i[8:] 244 elif i.startswith('-idirafter'): 245 # same as -isystem, but appends the path instead 246 if i.startswith('-idirafter='): 247 i = '/I' + i[11:] 248 else: 249 i = '/I' + i[10:] 250 # -pthread in link flags is only used on Linux 251 elif i == '-pthread': 252 continue 253 # cl.exe does not allow specifying both, so remove /utf-8 that we 254 # added automatically in the case the user overrides it manually. 255 elif i.startswith('/source-charset:') or i.startswith('/execution-charset:'): 256 try: 257 result.remove('/utf-8') 258 except ValueError: 259 pass 260 result.append(i) 261 return result 262 263 @classmethod 264 def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]: 265 result = [] 266 for arg in args: 267 if arg.startswith(('/LIBPATH:', '-LIBPATH:')): 268 result.append('-L' + arg[9:]) 269 elif arg.endswith(('.a', '.lib')) and not os.path.isabs(arg): 270 result.append('-l' + arg) 271 else: 272 result.append(arg) 273 return result 274 275 def get_werror_args(self) -> T.List[str]: 276 return ['/WX'] 277 278 def get_include_args(self, path: str, is_system: bool) -> T.List[str]: 279 if path == '': 280 path = '.' 281 # msvc does not have a concept of system header dirs. 282 return ['-I' + path] 283 284 def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: 285 for idx, i in enumerate(parameter_list): 286 if i[:2] == '-I' or i[:2] == '/I': 287 parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) 288 elif i[:9] == '/LIBPATH:': 289 parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:])) 290 291 return parameter_list 292 293 # Visual Studio is special. It ignores some arguments it does not 294 # understand and you can't tell it to error out on those. 295 # http://stackoverflow.com/questions/15259720/how-can-i-make-the-microsoft-c-compiler-treat-unknown-flags-as-errors-rather-t 296 def has_arguments(self, args: T.List[str], env: 'Environment', code: str, mode: str) -> T.Tuple[bool, bool]: 297 warning_text = '4044' if mode == 'link' else '9002' 298 with self._build_wrapper(code, env, extra_args=args, mode=mode) as p: 299 if p.returncode != 0: 300 return False, p.cached 301 return not(warning_text in p.stderr or warning_text in p.stdout), p.cached 302 303 def get_compile_debugfile_args(self, rel_obj: str, pch: bool = False) -> T.List[str]: 304 pdbarr = rel_obj.split('.')[:-1] 305 pdbarr += ['pdb'] 306 args = ['/Fd' + '.'.join(pdbarr)] 307 return args 308 309 def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: 310 if self.is_64: 311 return vs64_instruction_set_args.get(instruction_set, None) 312 return vs32_instruction_set_args.get(instruction_set, None) 313 314 def _calculate_toolset_version(self, version: int) -> T.Optional[str]: 315 if version < 1310: 316 return '7.0' 317 elif version < 1400: 318 return '7.1' # (Visual Studio 2003) 319 elif version < 1500: 320 return '8.0' # (Visual Studio 2005) 321 elif version < 1600: 322 return '9.0' # (Visual Studio 2008) 323 elif version < 1700: 324 return '10.0' # (Visual Studio 2010) 325 elif version < 1800: 326 return '11.0' # (Visual Studio 2012) 327 elif version < 1900: 328 return '12.0' # (Visual Studio 2013) 329 elif version < 1910: 330 return '14.0' # (Visual Studio 2015) 331 elif version < 1920: 332 return '14.1' # (Visual Studio 2017) 333 elif version < 1930: 334 return '14.2' # (Visual Studio 2019) 335 mlog.warning(f'Could not find toolset for version {self.version!r}') 336 return None 337 338 def get_toolset_version(self) -> T.Optional[str]: 339 # See boost/config/compiler/visualc.cpp for up to date mapping 340 try: 341 version = int(''.join(self.version.split('.')[0:2])) 342 except ValueError: 343 return None 344 return self._calculate_toolset_version(version) 345 346 def get_default_include_dirs(self) -> T.List[str]: 347 if 'INCLUDE' not in os.environ: 348 return [] 349 return os.environ['INCLUDE'].split(os.pathsep) 350 351 def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: 352 if crt_val in self.crt_args: 353 return self.crt_args[crt_val] 354 assert crt_val in ['from_buildtype', 'static_from_buildtype'] 355 dbg = 'mdd' 356 rel = 'md' 357 if crt_val == 'static_from_buildtype': 358 dbg = 'mtd' 359 rel = 'mt' 360 # Match what build type flags used to do. 361 if buildtype == 'plain': 362 return [] 363 elif buildtype == 'debug': 364 return self.crt_args[dbg] 365 elif buildtype == 'debugoptimized': 366 return self.crt_args[rel] 367 elif buildtype == 'release': 368 return self.crt_args[rel] 369 elif buildtype == 'minsize': 370 return self.crt_args[rel] 371 else: 372 assert buildtype == 'custom' 373 raise mesonlib.EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".') 374 375 def has_func_attribute(self, name: str, env: 'Environment') -> T.Tuple[bool, bool]: 376 # MSVC doesn't have __attribute__ like Clang and GCC do, so just return 377 # false without compiling anything 378 return name in ['dllimport', 'dllexport'], False 379 380 def get_argument_syntax(self) -> str: 381 return 'msvc' 382 383 384class MSVCCompiler(VisualStudioLikeCompiler): 385 386 """Specific to the Microsoft Compilers.""" 387 388 def __init__(self, target: str): 389 super().__init__(target) 390 self.id = 'msvc' 391 392 def get_compile_debugfile_args(self, rel_obj: str, pch: bool = False) -> T.List[str]: 393 args = super().get_compile_debugfile_args(rel_obj, pch) 394 # When generating a PDB file with PCH, all compile commands write 395 # to the same PDB file. Hence, we need to serialize the PDB 396 # writes using /FS since we do parallel builds. This slows down the 397 # build obviously, which is why we only do this when PCH is on. 398 # This was added in Visual Studio 2013 (MSVC 18.0). Before that it was 399 # always on: https://msdn.microsoft.com/en-us/library/dn502518.aspx 400 if pch and mesonlib.version_compare(self.version, '>=18.0'): 401 args = ['/FS'] + args 402 return args 403 404 def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: 405 if self.version.split('.')[0] == '16' and instruction_set == 'avx': 406 # VS documentation says that this exists and should work, but 407 # it does not. The headers do not contain AVX intrinsics 408 # and they can not be called. 409 return None 410 return super().get_instruction_set_args(instruction_set) 411 412 def get_pch_base_name(self, header: str) -> str: 413 return os.path.basename(header) 414 415 416class ClangClCompiler(VisualStudioLikeCompiler): 417 418 """Specific to Clang-CL.""" 419 420 def __init__(self, target: str): 421 super().__init__(target) 422 self.id = 'clang-cl' 423 424 # Assembly 425 self.can_compile_suffixes.add('s') 426 427 def has_arguments(self, args: T.List[str], env: 'Environment', code: str, mode: str) -> T.Tuple[bool, bool]: 428 if mode != 'link': 429 args = args + ['-Werror=unknown-argument'] 430 return super().has_arguments(args, env, code, mode) 431 432 def get_toolset_version(self) -> T.Optional[str]: 433 # XXX: what is the right thing to do here? 434 return '14.1' 435 436 def get_pch_base_name(self, header: str) -> str: 437 return header 438