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