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