summaryrefslogtreecommitdiffstats
path: root/mesonbuild/compilers/mixins/visualstudio.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--mesonbuild/compilers/mixins/visualstudio.py500
1 files changed, 500 insertions, 0 deletions
diff --git a/mesonbuild/compilers/mixins/visualstudio.py b/mesonbuild/compilers/mixins/visualstudio.py
new file mode 100644
index 0000000..864d3b4
--- /dev/null
+++ b/mesonbuild/compilers/mixins/visualstudio.py
@@ -0,0 +1,500 @@
+# 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.
+from __future__ import annotations
+
+"""Abstractions to simplify compilers that implement an MSVC compatible
+interface.
+"""
+
+import abc
+import os
+import typing as T
+
+from ... import arglist
+from ... import mesonlib
+from ... import mlog
+
+if T.TYPE_CHECKING:
+ from ...environment import Environment
+ from ...dependencies import Dependency
+ from .clike import CLikeCompiler as Compiler
+else:
+ # This is a bit clever, for mypy we pretend that these mixins descend from
+ # Compiler, so we get all of the methods and attributes defined for us, but
+ # for runtime we make them descend from object (which all classes normally
+ # do). This gives up DRYer type checking, with no runtime impact
+ Compiler = object
+
+vs32_instruction_set_args = {
+ 'mmx': ['/arch:SSE'], # There does not seem to be a flag just for MMX
+ 'sse': ['/arch:SSE'],
+ 'sse2': ['/arch:SSE2'],
+ 'sse3': ['/arch:AVX'], # VS leaped from SSE2 directly to AVX.
+ 'sse41': ['/arch:AVX'],
+ 'sse42': ['/arch:AVX'],
+ 'avx': ['/arch:AVX'],
+ 'avx2': ['/arch:AVX2'],
+ 'neon': None,
+} # T.Dicst[str, T.Optional[T.List[str]]]
+
+# The 64 bit compiler defaults to /arch:avx.
+vs64_instruction_set_args = {
+ 'mmx': ['/arch:AVX'],
+ 'sse': ['/arch:AVX'],
+ 'sse2': ['/arch:AVX'],
+ 'sse3': ['/arch:AVX'],
+ 'ssse3': ['/arch:AVX'],
+ 'sse41': ['/arch:AVX'],
+ 'sse42': ['/arch:AVX'],
+ 'avx': ['/arch:AVX'],
+ 'avx2': ['/arch:AVX2'],
+ 'neon': None,
+} # T.Dicst[str, T.Optional[T.List[str]]]
+
+msvc_optimization_args = {
+ 'plain': [],
+ '0': ['/Od'],
+ 'g': [], # No specific flag to optimize debugging, /Zi or /ZI will create debug information
+ '1': ['/O1'],
+ '2': ['/O2'],
+ '3': ['/O2', '/Gw'],
+ 's': ['/O1', '/Gw'],
+} # type: T.Dict[str, T.List[str]]
+
+msvc_debug_args = {
+ False: [],
+ True: ['/Zi']
+} # type: T.Dict[bool, T.List[str]]
+
+
+class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta):
+
+ """A common interface for all compilers implementing an MSVC-style
+ interface.
+
+ A number of compilers attempt to mimic MSVC, with varying levels of
+ success, such as Clang-CL and ICL (the Intel C/C++ Compiler for Windows).
+ This class implements as much common logic as possible.
+ """
+
+ std_warn_args = ['/W3']
+ std_opt_args = ['/O2']
+ ignore_libs = arglist.UNIXY_COMPILER_INTERNAL_LIBS + ['execinfo']
+ internal_libs = [] # type: T.List[str]
+
+ crt_args = {
+ 'none': [],
+ 'md': ['/MD'],
+ 'mdd': ['/MDd'],
+ 'mt': ['/MT'],
+ 'mtd': ['/MTd'],
+ } # type: T.Dict[str, T.List[str]]
+
+ # /showIncludes is needed for build dependency tracking in Ninja
+ # See: https://ninja-build.org/manual.html#_deps
+ # Assume UTF-8 sources by default, but self.unix_args_to_native() removes it
+ # if `/source-charset` is set too.
+ # It is also dropped if Visual Studio 2013 or earlier is used, since it would
+ # not be supported in that case.
+ always_args = ['/nologo', '/showIncludes', '/utf-8']
+ warn_args = {
+ '0': [],
+ '1': ['/W2'],
+ '2': ['/W3'],
+ '3': ['/W4'],
+ 'everything': ['/Wall'],
+ } # type: T.Dict[str, T.List[str]]
+
+ INVOKES_LINKER = False
+
+ def __init__(self, target: str):
+ self.base_options = {mesonlib.OptionKey(o) for o in ['b_pch', 'b_ndebug', 'b_vscrt']} # FIXME add lto, pgo and the like
+ self.target = target
+ self.is_64 = ('x64' in target) or ('x86_64' in target)
+ # do some canonicalization of target machine
+ if 'x86_64' in target:
+ self.machine = 'x64'
+ elif '86' in target:
+ self.machine = 'x86'
+ elif 'aarch64' in target:
+ self.machine = 'arm64'
+ elif 'arm' in target:
+ self.machine = 'arm'
+ else:
+ self.machine = target
+ if mesonlib.version_compare(self.version, '>=19.28.29910'): # VS 16.9.0 includes cl 19.28.29910
+ self.base_options.add(mesonlib.OptionKey('b_sanitize'))
+ assert self.linker is not None
+ self.linker.machine = self.machine
+
+ # Override CCompiler.get_always_args
+ def get_always_args(self) -> T.List[str]:
+ # TODO: use ImmutableListProtocol[str] here instead
+ return self.always_args.copy()
+
+ def get_pch_suffix(self) -> str:
+ return 'pch'
+
+ def get_pch_name(self, header: str) -> str:
+ chopped = os.path.basename(header).split('.')[:-1]
+ chopped.append(self.get_pch_suffix())
+ pchname = '.'.join(chopped)
+ return pchname
+
+ def get_pch_base_name(self, header: str) -> str:
+ # This needs to be implemented by inheriting classes
+ raise NotImplementedError
+
+ def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]:
+ base = self.get_pch_base_name(header)
+ pchname = self.get_pch_name(header)
+ return ['/FI' + base, '/Yu' + base, '/Fp' + os.path.join(pch_dir, pchname)]
+
+ def get_preprocess_only_args(self) -> T.List[str]:
+ return ['/EP']
+
+ def get_preprocess_to_file_args(self) -> T.List[str]:
+ return ['/EP', '/P']
+
+ def get_compile_only_args(self) -> T.List[str]:
+ return ['/c']
+
+ def get_no_optimization_args(self) -> T.List[str]:
+ return ['/Od', '/Oi-']
+
+ def sanitizer_compile_args(self, value: str) -> T.List[str]:
+ if value == 'none':
+ return []
+ if value != 'address':
+ raise mesonlib.MesonException('VS only supports address sanitizer at the moment.')
+ return ['/fsanitize=address']
+
+ def get_output_args(self, target: str) -> T.List[str]:
+ if self.mode == 'PREPROCESSOR':
+ return ['/Fi' + target]
+ if target.endswith('.exe'):
+ return ['/Fe' + target]
+ return ['/Fo' + target]
+
+ def get_buildtype_args(self, buildtype: str) -> T.List[str]:
+ return []
+
+ def get_debug_args(self, is_debug: bool) -> T.List[str]:
+ return msvc_debug_args[is_debug]
+
+ def get_optimization_args(self, optimization_level: str) -> T.List[str]:
+ args = msvc_optimization_args[optimization_level]
+ if mesonlib.version_compare(self.version, '<18.0'):
+ args = [arg for arg in args if arg != '/Gw']
+ return args
+
+ def linker_to_compiler_args(self, args: T.List[str]) -> T.List[str]:
+ return ['/link'] + args
+
+ def get_pic_args(self) -> T.List[str]:
+ return [] # PIC is handled by the loader on Windows
+
+ def gen_vs_module_defs_args(self, defsfile: str) -> T.List[str]:
+ if not isinstance(defsfile, str):
+ raise RuntimeError('Module definitions file should be str')
+ # With MSVC, DLLs only export symbols that are explicitly exported,
+ # so if a module defs file is specified, we use that to export symbols
+ return ['/DEF:' + defsfile]
+
+ def gen_pch_args(self, header: str, source: str, pchname: str) -> T.Tuple[str, T.List[str]]:
+ objname = os.path.splitext(pchname)[0] + '.obj'
+ return objname, ['/Yc' + header, '/Fp' + pchname, '/Fo' + objname]
+
+ def openmp_flags(self) -> T.List[str]:
+ return ['/openmp']
+
+ def openmp_link_flags(self) -> T.List[str]:
+ return []
+
+ # FIXME, no idea what these should be.
+ def thread_flags(self, env: 'Environment') -> T.List[str]:
+ return []
+
+ @classmethod
+ def unix_args_to_native(cls, args: T.List[str]) -> T.List[str]:
+ result: T.List[str] = []
+ for i in args:
+ # -mms-bitfields is specific to MinGW-GCC
+ # -pthread is only valid for GCC
+ if i in {'-mms-bitfields', '-pthread'}:
+ continue
+ if i.startswith('-LIBPATH:'):
+ i = '/LIBPATH:' + i[9:]
+ elif i.startswith('-L'):
+ i = '/LIBPATH:' + i[2:]
+ # Translate GNU-style -lfoo library name to the import library
+ elif i.startswith('-l'):
+ name = i[2:]
+ if name in cls.ignore_libs:
+ # With MSVC, these are provided by the C runtime which is
+ # linked in by default
+ continue
+ else:
+ i = name + '.lib'
+ elif i.startswith('-isystem'):
+ # just use /I for -isystem system include path s
+ if i.startswith('-isystem='):
+ i = '/I' + i[9:]
+ else:
+ i = '/I' + i[8:]
+ elif i.startswith('-idirafter'):
+ # same as -isystem, but appends the path instead
+ if i.startswith('-idirafter='):
+ i = '/I' + i[11:]
+ else:
+ i = '/I' + i[10:]
+ # -pthread in link flags is only used on Linux
+ elif i == '-pthread':
+ continue
+ # cl.exe does not allow specifying both, so remove /utf-8 that we
+ # added automatically in the case the user overrides it manually.
+ elif (i.startswith('/source-charset:')
+ or i.startswith('/execution-charset:')
+ or i == '/validate-charset-'):
+ try:
+ result.remove('/utf-8')
+ except ValueError:
+ pass
+ result.append(i)
+ return result
+
+ @classmethod
+ def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]:
+ result = []
+ for arg in args:
+ if arg.startswith(('/LIBPATH:', '-LIBPATH:')):
+ result.append('-L' + arg[9:])
+ elif arg.endswith(('.a', '.lib')) and not os.path.isabs(arg):
+ result.append('-l' + arg)
+ else:
+ result.append(arg)
+ return result
+
+ def get_werror_args(self) -> T.List[str]:
+ return ['/WX']
+
+ def get_include_args(self, path: str, is_system: bool) -> T.List[str]:
+ if path == '':
+ path = '.'
+ # msvc does not have a concept of system header dirs.
+ return ['-I' + path]
+
+ def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]:
+ for idx, i in enumerate(parameter_list):
+ if i[:2] == '-I' or i[:2] == '/I':
+ parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:]))
+ elif i[:9] == '/LIBPATH:':
+ parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:]))
+
+ return parameter_list
+
+ # Visual Studio is special. It ignores some arguments it does not
+ # understand and you can't tell it to error out on those.
+ # http://stackoverflow.com/questions/15259720/how-can-i-make-the-microsoft-c-compiler-treat-unknown-flags-as-errors-rather-t
+ def has_arguments(self, args: T.List[str], env: 'Environment', code: str, mode: str) -> T.Tuple[bool, bool]:
+ warning_text = '4044' if mode == 'link' else '9002'
+ with self._build_wrapper(code, env, extra_args=args, mode=mode) as p:
+ if p.returncode != 0:
+ return False, p.cached
+ return not (warning_text in p.stderr or warning_text in p.stdout), p.cached
+
+ def get_compile_debugfile_args(self, rel_obj: str, pch: bool = False) -> T.List[str]:
+ pdbarr = rel_obj.split('.')[:-1]
+ pdbarr += ['pdb']
+ args = ['/Fd' + '.'.join(pdbarr)]
+ return args
+
+ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]:
+ if self.is_64:
+ return vs64_instruction_set_args.get(instruction_set, None)
+ return vs32_instruction_set_args.get(instruction_set, None)
+
+ def _calculate_toolset_version(self, version: int) -> T.Optional[str]:
+ if version < 1310:
+ return '7.0'
+ elif version < 1400:
+ return '7.1' # (Visual Studio 2003)
+ elif version < 1500:
+ return '8.0' # (Visual Studio 2005)
+ elif version < 1600:
+ return '9.0' # (Visual Studio 2008)
+ elif version < 1700:
+ return '10.0' # (Visual Studio 2010)
+ elif version < 1800:
+ return '11.0' # (Visual Studio 2012)
+ elif version < 1900:
+ return '12.0' # (Visual Studio 2013)
+ elif version < 1910:
+ return '14.0' # (Visual Studio 2015)
+ elif version < 1920:
+ return '14.1' # (Visual Studio 2017)
+ elif version < 1930:
+ return '14.2' # (Visual Studio 2019)
+ elif version < 1940:
+ return '14.3' # (Visual Studio 2022)
+ mlog.warning(f'Could not find toolset for version {self.version!r}')
+ return None
+
+ def get_toolset_version(self) -> T.Optional[str]:
+ # See boost/config/compiler/visualc.cpp for up to date mapping
+ try:
+ version = int(''.join(self.version.split('.')[0:2]))
+ except ValueError:
+ return None
+ return self._calculate_toolset_version(version)
+
+ def get_default_include_dirs(self) -> T.List[str]:
+ if 'INCLUDE' not in os.environ:
+ return []
+ return os.environ['INCLUDE'].split(os.pathsep)
+
+ def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]:
+ if crt_val in self.crt_args:
+ return self.crt_args[crt_val]
+ assert crt_val in {'from_buildtype', 'static_from_buildtype'}
+ dbg = 'mdd'
+ rel = 'md'
+ if crt_val == 'static_from_buildtype':
+ dbg = 'mtd'
+ rel = 'mt'
+ # Match what build type flags used to do.
+ if buildtype == 'plain':
+ return []
+ elif buildtype == 'debug':
+ return self.crt_args[dbg]
+ elif buildtype == 'debugoptimized':
+ return self.crt_args[rel]
+ elif buildtype == 'release':
+ return self.crt_args[rel]
+ elif buildtype == 'minsize':
+ return self.crt_args[rel]
+ else:
+ assert buildtype == 'custom'
+ raise mesonlib.EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".')
+
+ def has_func_attribute(self, name: str, env: 'Environment') -> T.Tuple[bool, bool]:
+ # MSVC doesn't have __attribute__ like Clang and GCC do, so just return
+ # false without compiling anything
+ return name in {'dllimport', 'dllexport'}, False
+
+ def get_argument_syntax(self) -> str:
+ return 'msvc'
+
+ def symbols_have_underscore_prefix(self, env: 'Environment') -> bool:
+ '''
+ Check if the compiler prefixes an underscore to global C symbols.
+
+ This overrides the Clike method, as for MSVC checking the
+ underscore prefix based on the compiler define never works,
+ so do not even try.
+ '''
+ # Try to consult a hardcoded list of cases we know
+ # absolutely have an underscore prefix
+ result = self._symbols_have_underscore_prefix_list(env)
+ if result is not None:
+ return result
+
+ # As a last resort, try search in a compiled binary
+ return self._symbols_have_underscore_prefix_searchbin(env)
+
+
+class MSVCCompiler(VisualStudioLikeCompiler):
+
+ """Specific to the Microsoft Compilers."""
+
+ id = 'msvc'
+
+ def __init__(self, target: str):
+ super().__init__(target)
+
+ # Visual Studio 2013 and erlier don't support the /utf-8 argument.
+ # We want to remove it. We also want to make an explicit copy so we
+ # don't mutate class constant state
+ if mesonlib.version_compare(self.version, '<19.00') and '/utf-8' in self.always_args:
+ self.always_args = [r for r in self.always_args if r != '/utf-8']
+
+ def get_compile_debugfile_args(self, rel_obj: str, pch: bool = False) -> T.List[str]:
+ args = super().get_compile_debugfile_args(rel_obj, pch)
+ # When generating a PDB file with PCH, all compile commands write
+ # to the same PDB file. Hence, we need to serialize the PDB
+ # writes using /FS since we do parallel builds. This slows down the
+ # build obviously, which is why we only do this when PCH is on.
+ # This was added in Visual Studio 2013 (MSVC 18.0). Before that it was
+ # always on: https://msdn.microsoft.com/en-us/library/dn502518.aspx
+ if pch and mesonlib.version_compare(self.version, '>=18.0'):
+ args = ['/FS'] + args
+ return args
+
+ # Override CCompiler.get_always_args
+ # We want to drop '/utf-8' for Visual Studio 2013 and earlier
+ def get_always_args(self) -> T.List[str]:
+ return self.always_args
+
+ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]:
+ if self.version.split('.')[0] == '16' and instruction_set == 'avx':
+ # VS documentation says that this exists and should work, but
+ # it does not. The headers do not contain AVX intrinsics
+ # and they can not be called.
+ return None
+ return super().get_instruction_set_args(instruction_set)
+
+ def get_pch_base_name(self, header: str) -> str:
+ return os.path.basename(header)
+
+
+class ClangClCompiler(VisualStudioLikeCompiler):
+
+ """Specific to Clang-CL."""
+
+ id = 'clang-cl'
+
+ def __init__(self, target: str):
+ super().__init__(target)
+
+ # Assembly
+ self.can_compile_suffixes.add('s')
+
+ def has_arguments(self, args: T.List[str], env: 'Environment', code: str, mode: str) -> T.Tuple[bool, bool]:
+ if mode != 'link':
+ args = args + ['-Werror=unknown-argument', '-Werror=unknown-warning-option']
+ return super().has_arguments(args, env, code, mode)
+
+ def get_toolset_version(self) -> T.Optional[str]:
+ # XXX: what is the right thing to do here?
+ return '14.1'
+
+ def get_pch_base_name(self, header: str) -> str:
+ return header
+
+ def get_include_args(self, path: str, is_system: bool) -> T.List[str]:
+ if path == '':
+ path = '.'
+ return ['/clang:-isystem' + path] if is_system else ['-I' + path]
+
+ def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]:
+ if dep.get_include_type() == 'system':
+ converted = []
+ for i in dep.get_compile_args():
+ if i.startswith('-isystem'):
+ converted += ['/clang:' + i]
+ else:
+ converted += [i]
+ return converted
+ else:
+ return dep.get_compile_args()