1# Copyright 2014-2016 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
15import copy
16import os
17import xml.dom.minidom
18import xml.etree.ElementTree as ET
19import uuid
20import typing as T
21from pathlib import Path, PurePath
22
23from . import backends
24from .. import build
25from .. import dependencies
26from .. import mlog
27from .. import compilers
28from ..interpreter import Interpreter
29from ..mesonlib import (
30    File, MesonException, replace_if_different, OptionKey, version_compare, MachineChoice
31)
32from ..environment import Environment, build_filename
33
34def autodetect_vs_version(build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]) -> backends.Backend:
35    vs_version = os.getenv('VisualStudioVersion', None)
36    vs_install_dir = os.getenv('VSINSTALLDIR', None)
37    if not vs_install_dir:
38        raise MesonException('Could not detect Visual Studio: Environment variable VSINSTALLDIR is not set!\n'
39                             'Are you running meson from the Visual Studio Developer Command Prompt?')
40    # VisualStudioVersion is set since Visual Studio 11.0, but sometimes
41    # vcvarsall.bat doesn't set it, so also use VSINSTALLDIR
42    if vs_version == '11.0' or 'Visual Studio 11' in vs_install_dir:
43        from mesonbuild.backend.vs2012backend import Vs2012Backend
44        return Vs2012Backend(build, interpreter)
45    if vs_version == '12.0' or 'Visual Studio 12' in vs_install_dir:
46        from mesonbuild.backend.vs2013backend import Vs2013Backend
47        return Vs2013Backend(build, interpreter)
48    if vs_version == '14.0' or 'Visual Studio 14' in vs_install_dir:
49        from mesonbuild.backend.vs2015backend import Vs2015Backend
50        return Vs2015Backend(build, interpreter)
51    if vs_version == '15.0' or 'Visual Studio 17' in vs_install_dir or \
52       'Visual Studio\\2017' in vs_install_dir:
53        from mesonbuild.backend.vs2017backend import Vs2017Backend
54        return Vs2017Backend(build, interpreter)
55    if vs_version == '16.0' or 'Visual Studio 19' in vs_install_dir or \
56       'Visual Studio\\2019' in vs_install_dir:
57        from mesonbuild.backend.vs2019backend import Vs2019Backend
58        return Vs2019Backend(build, interpreter)
59    if 'Visual Studio 10.0' in vs_install_dir:
60        return Vs2010Backend(build, interpreter)
61    raise MesonException('Could not detect Visual Studio using VisualStudioVersion: {!r} or VSINSTALLDIR: {!r}!\n'
62                         'Please specify the exact backend to use.'.format(vs_version, vs_install_dir))
63
64def split_o_flags_args(args):
65    """
66    Splits any /O args and returns them. Does not take care of flags overriding
67    previous ones. Skips non-O flag arguments.
68
69    ['/Ox', '/Ob1'] returns ['/Ox', '/Ob1']
70    ['/Oxj', '/MP'] returns ['/Ox', '/Oj']
71    """
72    o_flags = []
73    for arg in args:
74        if not arg.startswith('/O'):
75            continue
76        flags = list(arg[2:])
77        # Assume that this one can't be clumped with the others since it takes
78        # an argument itself
79        if 'b' in flags:
80            o_flags.append(arg)
81        else:
82            o_flags += ['/O' + f for f in flags]
83    return o_flags
84
85def generate_guid_from_path(path, path_type):
86    return str(uuid.uuid5(uuid.NAMESPACE_URL, 'meson-vs-' + path_type + ':' + str(path))).upper()
87
88class Vs2010Backend(backends.Backend):
89    def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]):
90        super().__init__(build, interpreter)
91        self.name = 'vs2010'
92        self.project_file_version = '10.0.30319.1'
93        self.platform_toolset = None
94        self.vs_version = '2010'
95        self.windows_target_platform_version = None
96        self.subdirs = {}
97        self.handled_target_deps = {}
98
99    def get_target_private_dir(self, target):
100        return os.path.join(self.get_target_dir(target), target.get_id())
101
102    def generate_custom_generator_commands(self, target, parent_node):
103        generator_output_files = []
104        custom_target_include_dirs = []
105        custom_target_output_files = []
106        target_private_dir = self.relpath(self.get_target_private_dir(target), self.get_target_dir(target))
107        down = self.target_to_build_root(target)
108        for genlist in target.get_generated_sources():
109            if isinstance(genlist, (build.CustomTarget, build.CustomTargetIndex)):
110                for i in genlist.get_outputs():
111                    # Path to the generated source from the current vcxproj dir via the build root
112                    ipath = os.path.join(down, self.get_target_dir(genlist), i)
113                    custom_target_output_files.append(ipath)
114                idir = self.relpath(self.get_target_dir(genlist), self.get_target_dir(target))
115                if idir not in custom_target_include_dirs:
116                    custom_target_include_dirs.append(idir)
117            else:
118                generator = genlist.get_generator()
119                exe = generator.get_exe()
120                infilelist = genlist.get_inputs()
121                outfilelist = genlist.get_outputs()
122                source_dir = os.path.join(down, self.build_to_src, genlist.subdir)
123                exe_arr = self.build_target_to_cmd_array(exe)
124                idgroup = ET.SubElement(parent_node, 'ItemGroup')
125                for i, curfile in enumerate(infilelist):
126                    if len(infilelist) == len(outfilelist):
127                        sole_output = os.path.join(target_private_dir, outfilelist[i])
128                    else:
129                        sole_output = ''
130                    infilename = os.path.join(down, curfile.rel_to_builddir(self.build_to_src))
131                    deps = self.get_custom_target_depend_files(genlist, True)
132                    base_args = generator.get_arglist(infilename)
133                    outfiles_rel = genlist.get_outputs_for(curfile)
134                    outfiles = [os.path.join(target_private_dir, of) for of in outfiles_rel]
135                    generator_output_files += outfiles
136                    args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output)
137                            for x in base_args]
138                    args = self.replace_outputs(args, target_private_dir, outfiles_rel)
139                    args = [x.replace("@SOURCE_DIR@", self.environment.get_source_dir())
140                             .replace("@BUILD_DIR@", target_private_dir)
141                            for x in args]
142                    args = [x.replace("@CURRENT_SOURCE_DIR@", source_dir) for x in args]
143                    args = [x.replace("@SOURCE_ROOT@", self.environment.get_source_dir())
144                             .replace("@BUILD_ROOT@", self.environment.get_build_dir())
145                            for x in args]
146                    args = [x.replace('\\', '/') for x in args]
147                    cmd = exe_arr + self.replace_extra_args(args, genlist)
148                    # Always use a wrapper because MSBuild eats random characters when
149                    # there are many arguments.
150                    tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
151                    cmd, _ = self.as_meson_exe_cmdline(
152                        cmd[0],
153                        cmd[1:],
154                        workdir=tdir_abs,
155                        capture=outfiles[0] if generator.capture else None,
156                        force_serialize=True
157                    )
158                    deps = cmd[-1:] + deps
159                    abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
160                    os.makedirs(abs_pdir, exist_ok=True)
161                    cbs = ET.SubElement(idgroup, 'CustomBuild', Include=infilename)
162                    ET.SubElement(cbs, 'Command').text = ' '.join(self.quote_arguments(cmd))
163                    ET.SubElement(cbs, 'Outputs').text = ';'.join(outfiles)
164                    ET.SubElement(cbs, 'AdditionalInputs').text = ';'.join(deps)
165        return generator_output_files, custom_target_output_files, custom_target_include_dirs
166
167    def generate(self):
168        target_machine = self.interpreter.builtin['target_machine'].cpu_family_method(None, None)
169        if target_machine == '64' or target_machine == 'x86_64':
170            # amd64 or x86_64
171            self.platform = 'x64'
172        elif target_machine == 'x86':
173            # x86
174            self.platform = 'Win32'
175        elif target_machine == 'aarch64' or target_machine == 'arm64':
176            target_cpu = self.interpreter.builtin['target_machine'].cpu_method(None, None)
177            if target_cpu == 'arm64ec':
178                self.platform = 'arm64ec'
179            else:
180                self.platform = 'arm64'
181        elif 'arm' in target_machine.lower():
182            self.platform = 'ARM'
183        else:
184            raise MesonException('Unsupported Visual Studio platform: ' + target_machine)
185
186        build_machine = self.interpreter.builtin['build_machine'].cpu_family_method(None, None)
187        if build_machine == '64' or build_machine == 'x86_64':
188            # amd64 or x86_64
189            self.build_platform = 'x64'
190        elif build_machine == 'x86':
191            # x86
192            self.build_platform = 'Win32'
193        elif build_machine == 'aarch64' or build_machine == 'arm64':
194            target_cpu = self.interpreter.builtin['build_machine'].cpu_method(None, None)
195            if target_cpu == 'arm64ec':
196                self.build_platform = 'arm64ec'
197            else:
198                self.build_platform = 'arm64'
199        elif 'arm' in build_machine.lower():
200            self.build_platform = 'ARM'
201        else:
202            raise MesonException('Unsupported Visual Studio platform: ' + build_machine)
203
204        self.buildtype = self.environment.coredata.get_option(OptionKey('buildtype'))
205        self.optimization = self.environment.coredata.get_option(OptionKey('optimization'))
206        self.debug = self.environment.coredata.get_option(OptionKey('debug'))
207        try:
208            self.sanitize = self.environment.coredata.get_option(OptionKey('b_sanitize'))
209        except MesonException:
210            self.sanitize = 'none'
211        sln_filename = os.path.join(self.environment.get_build_dir(), self.build.project_name + '.sln')
212        projlist = self.generate_projects()
213        self.gen_testproj('RUN_TESTS', os.path.join(self.environment.get_build_dir(), 'RUN_TESTS.vcxproj'))
214        self.gen_installproj('RUN_INSTALL', os.path.join(self.environment.get_build_dir(), 'RUN_INSTALL.vcxproj'))
215        self.gen_regenproj('REGEN', os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj'))
216        self.generate_solution(sln_filename, projlist)
217        self.generate_regen_info()
218        Vs2010Backend.touch_regen_timestamp(self.environment.get_build_dir())
219
220    @staticmethod
221    def get_regen_stampfile(build_dir: str) -> None:
222        return os.path.join(os.path.join(build_dir, Environment.private_dir), 'regen.stamp')
223
224    @staticmethod
225    def touch_regen_timestamp(build_dir: str) -> None:
226        with open(Vs2010Backend.get_regen_stampfile(build_dir), 'w', encoding='utf-8'):
227            pass
228
229    def get_vcvars_command(self):
230        has_arch_values = 'VSCMD_ARG_TGT_ARCH' in os.environ and 'VSCMD_ARG_HOST_ARCH' in os.environ
231
232        # Use vcvarsall.bat if we found it.
233        if 'VCINSTALLDIR' in os.environ:
234            vs_version = os.environ['VisualStudioVersion'] \
235                if 'VisualStudioVersion' in os.environ else None
236            relative_path = 'Auxiliary\\Build\\' if vs_version is not None and vs_version >= '15.0' else ''
237            script_path = os.environ['VCINSTALLDIR'] + relative_path + 'vcvarsall.bat'
238            if os.path.exists(script_path):
239                if has_arch_values:
240                    target_arch = os.environ['VSCMD_ARG_TGT_ARCH']
241                    host_arch = os.environ['VSCMD_ARG_HOST_ARCH']
242                else:
243                    target_arch = os.environ.get('Platform', 'x86')
244                    host_arch = target_arch
245                arch = host_arch + '_' + target_arch if host_arch != target_arch else target_arch
246                return f'"{script_path}" {arch}'
247
248        # Otherwise try the VS2017 Developer Command Prompt.
249        if 'VS150COMNTOOLS' in os.environ and has_arch_values:
250            script_path = os.environ['VS150COMNTOOLS'] + 'VsDevCmd.bat'
251            if os.path.exists(script_path):
252                return '"%s" -arch=%s -host_arch=%s' % \
253                    (script_path, os.environ['VSCMD_ARG_TGT_ARCH'], os.environ['VSCMD_ARG_HOST_ARCH'])
254        return ''
255
256    def get_obj_target_deps(self, obj_list):
257        result = {}
258        for o in obj_list:
259            if isinstance(o, build.ExtractedObjects):
260                result[o.target.get_id()] = o.target
261        return result.items()
262
263    def get_target_deps(self, t, recursive=False):
264        all_deps = {}
265        for target in t.values():
266            if isinstance(target, build.CustomTarget):
267                for d in target.get_target_dependencies():
268                    all_deps[d.get_id()] = d
269            elif isinstance(target, build.RunTarget):
270                for d in target.get_dependencies():
271                    all_deps[d.get_id()] = d
272            elif isinstance(target, build.BuildTarget):
273                for ldep in target.link_targets:
274                    if isinstance(ldep, build.CustomTargetIndex):
275                        all_deps[ldep.get_id()] = ldep.target
276                    else:
277                        all_deps[ldep.get_id()] = ldep
278                for ldep in target.link_whole_targets:
279                    if isinstance(ldep, build.CustomTargetIndex):
280                        all_deps[ldep.get_id()] = ldep.target
281                    else:
282                        all_deps[ldep.get_id()] = ldep
283                for obj_id, objdep in self.get_obj_target_deps(target.objects):
284                    all_deps[obj_id] = objdep
285            else:
286                raise MesonException('Unknown target type for target %s' % target)
287
288            for gendep in target.get_generated_sources():
289                if isinstance(gendep, build.CustomTarget):
290                    all_deps[gendep.get_id()] = gendep
291                elif isinstance(gendep, build.CustomTargetIndex):
292                    all_deps[gendep.target.get_id()] = gendep.target
293                else:
294                    generator = gendep.get_generator()
295                    gen_exe = generator.get_exe()
296                    if isinstance(gen_exe, build.Executable):
297                        all_deps[gen_exe.get_id()] = gen_exe
298                    for d in generator.depends:
299                        if isinstance(d, build.CustomTargetIndex):
300                            all_deps[d.get_id()] = d.target
301                        else:
302                            all_deps[d.get_id()] = d
303
304        if not t or not recursive:
305            return all_deps
306        ret = self.get_target_deps(all_deps, recursive)
307        ret.update(all_deps)
308        return ret
309
310    def generate_solution_dirs(self, ofile, parents):
311        prj_templ = 'Project("{%s}") = "%s", "%s", "{%s}"\n'
312        iterpaths = reversed(parents)
313        # Skip first path
314        next(iterpaths)
315        for path in iterpaths:
316            if path not in self.subdirs:
317                basename = path.name
318                identifier = generate_guid_from_path(path, 'subdir')
319                # top-level directories have None as their parent_dir
320                parent_dir = path.parent
321                parent_identifier = self.subdirs[parent_dir][0] \
322                    if parent_dir != PurePath('.') else None
323                self.subdirs[path] = (identifier, parent_identifier)
324                prj_line = prj_templ % (
325                    self.environment.coredata.lang_guids['directory'],
326                    basename, basename, self.subdirs[path][0])
327                ofile.write(prj_line)
328                ofile.write('EndProject\n')
329
330    def generate_solution(self, sln_filename, projlist):
331        default_projlist = self.get_build_by_default_targets()
332        sln_filename_tmp = sln_filename + '~'
333        with open(sln_filename_tmp, 'w', encoding='utf-8') as ofile:
334            ofile.write('Microsoft Visual Studio Solution File, Format '
335                        'Version 11.00\n')
336            ofile.write('# Visual Studio ' + self.vs_version + '\n')
337            prj_templ = 'Project("{%s}") = "%s", "%s", "{%s}"\n'
338            for prj in projlist:
339                coredata = self.environment.coredata
340                if coredata.get_option(OptionKey('layout')) == 'mirror':
341                    self.generate_solution_dirs(ofile, prj[1].parents)
342                target = self.build.targets[prj[0]]
343                lang = 'default'
344                if hasattr(target, 'compilers') and target.compilers:
345                    for lang_out in target.compilers.keys():
346                        lang = lang_out
347                        break
348                prj_line = prj_templ % (
349                    self.environment.coredata.lang_guids[lang],
350                    prj[0], prj[1], prj[2])
351                ofile.write(prj_line)
352                target_dict = {target.get_id(): target}
353                # Get recursive deps
354                recursive_deps = self.get_target_deps(
355                    target_dict, recursive=True)
356                ofile.write('EndProject\n')
357                for dep, target in recursive_deps.items():
358                    if prj[0] in default_projlist:
359                        default_projlist[dep] = target
360
361            test_line = prj_templ % (self.environment.coredata.lang_guids['default'],
362                                     'RUN_TESTS', 'RUN_TESTS.vcxproj',
363                                     self.environment.coredata.test_guid)
364            ofile.write(test_line)
365            ofile.write('EndProject\n')
366            regen_line = prj_templ % (self.environment.coredata.lang_guids['default'],
367                                      'REGEN', 'REGEN.vcxproj',
368                                      self.environment.coredata.regen_guid)
369            ofile.write(regen_line)
370            ofile.write('EndProject\n')
371            install_line = prj_templ % (self.environment.coredata.lang_guids['default'],
372                                        'RUN_INSTALL', 'RUN_INSTALL.vcxproj',
373                                        self.environment.coredata.install_guid)
374            ofile.write(install_line)
375            ofile.write('EndProject\n')
376            ofile.write('Global\n')
377            ofile.write('\tGlobalSection(SolutionConfigurationPlatforms) = '
378                        'preSolution\n')
379            ofile.write('\t\t%s|%s = %s|%s\n' %
380                        (self.buildtype, self.platform, self.buildtype,
381                         self.platform))
382            ofile.write('\tEndGlobalSection\n')
383            ofile.write('\tGlobalSection(ProjectConfigurationPlatforms) = '
384                        'postSolution\n')
385            ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
386                        (self.environment.coredata.regen_guid, self.buildtype,
387                         self.platform, self.buildtype, self.platform))
388            ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' %
389                        (self.environment.coredata.regen_guid, self.buildtype,
390                         self.platform, self.buildtype, self.platform))
391            # Create the solution configuration
392            for p in projlist:
393                if p[3] is MachineChoice.BUILD:
394                    config_platform = self.build_platform
395                else:
396                    config_platform = self.platform
397                # Add to the list of projects in this solution
398                ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
399                            (p[2], self.buildtype, self.platform,
400                             self.buildtype, config_platform))
401                if p[0] in default_projlist and \
402                   not isinstance(self.build.targets[p[0]], build.RunTarget):
403                    # Add to the list of projects to be built
404                    ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' %
405                                (p[2], self.buildtype, self.platform,
406                                 self.buildtype, config_platform))
407            ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
408                        (self.environment.coredata.test_guid, self.buildtype,
409                         self.platform, self.buildtype, self.platform))
410            ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
411                        (self.environment.coredata.install_guid, self.buildtype,
412                         self.platform, self.buildtype, self.platform))
413            ofile.write('\tEndGlobalSection\n')
414            ofile.write('\tGlobalSection(SolutionProperties) = preSolution\n')
415            ofile.write('\t\tHideSolutionNode = FALSE\n')
416            ofile.write('\tEndGlobalSection\n')
417            if self.subdirs:
418                ofile.write('\tGlobalSection(NestedProjects) = '
419                            'preSolution\n')
420                for p in projlist:
421                    if p[1].parent != PurePath('.'):
422                        ofile.write("\t\t{{{}}} = {{{}}}\n".format(p[2], self.subdirs[p[1].parent][0]))
423                for subdir in self.subdirs.values():
424                    if subdir[1]:
425                        ofile.write("\t\t{{{}}} = {{{}}}\n".format(subdir[0], subdir[1]))
426                ofile.write('\tEndGlobalSection\n')
427            ofile.write('EndGlobal\n')
428        replace_if_different(sln_filename, sln_filename_tmp)
429
430    def generate_projects(self):
431        startup_project = self.environment.coredata.options[OptionKey('backend_startup_project')].value
432        projlist = []
433        startup_idx = 0
434        for (i, (name, target)) in enumerate(self.build.targets.items()):
435            if startup_project and startup_project == target.get_basename():
436                startup_idx = i
437            outdir = Path(
438                self.environment.get_build_dir(),
439                self.get_target_dir(target)
440            )
441            outdir.mkdir(exist_ok=True, parents=True)
442            fname = name + '.vcxproj'
443            target_dir = PurePath(self.get_target_dir(target))
444            relname = target_dir / fname
445            projfile_path = outdir / fname
446            proj_uuid = self.environment.coredata.target_guids[name]
447            self.gen_vcxproj(target, str(projfile_path), proj_uuid)
448            projlist.append((name, relname, proj_uuid, target.for_machine))
449
450        # Put the startup project first in the project list
451        if startup_idx:
452            projlist = [projlist[startup_idx]] + projlist[0:startup_idx] + projlist[startup_idx + 1:-1]
453
454        return projlist
455
456    def split_sources(self, srclist):
457        sources = []
458        headers = []
459        objects = []
460        languages = []
461        for i in srclist:
462            if self.environment.is_header(i):
463                headers.append(i)
464            elif self.environment.is_object(i):
465                objects.append(i)
466            elif self.environment.is_source(i):
467                sources.append(i)
468                lang = self.lang_from_source_file(i)
469                if lang not in languages:
470                    languages.append(lang)
471            elif self.environment.is_library(i):
472                pass
473            else:
474                # Everything that is not an object or source file is considered a header.
475                headers.append(i)
476        return sources, headers, objects, languages
477
478    def target_to_build_root(self, target):
479        if self.get_target_dir(target) == '':
480            return ''
481
482        directories = os.path.normpath(self.get_target_dir(target)).split(os.sep)
483        return os.sep.join(['..'] * len(directories))
484
485    def quote_arguments(self, arr):
486        return ['"%s"' % i for i in arr]
487
488    def add_project_reference(self, root, include, projid, link_outputs=False):
489        ig = ET.SubElement(root, 'ItemGroup')
490        pref = ET.SubElement(ig, 'ProjectReference', Include=include)
491        ET.SubElement(pref, 'Project').text = '{%s}' % projid
492        if not link_outputs:
493            # Do not link in generated .lib files from dependencies automatically.
494            # We only use the dependencies for ordering and link in the generated
495            # objects and .lib files manually.
496            ET.SubElement(pref, 'LinkLibraryDependencies').text = 'false'
497
498    def add_target_deps(self, root, target):
499        target_dict = {target.get_id(): target}
500        for dep in self.get_target_deps(target_dict).values():
501            if dep.get_id() in self.handled_target_deps[target.get_id()]:
502                # This dependency was already handled manually.
503                continue
504            relpath = self.get_target_dir_relative_to(dep, target)
505            vcxproj = os.path.join(relpath, dep.get_id() + '.vcxproj')
506            tid = self.environment.coredata.target_guids[dep.get_id()]
507            self.add_project_reference(root, vcxproj, tid)
508
509    def create_basic_crap(self, target, guid):
510        project_name = target.name
511        root = ET.Element('Project', {'DefaultTargets': "Build",
512                                      'ToolsVersion': '4.0',
513                                      'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'})
514        confitems = ET.SubElement(root, 'ItemGroup', {'Label': 'ProjectConfigurations'})
515        prjconf = ET.SubElement(confitems, 'ProjectConfiguration',
516                                {'Include': self.buildtype + '|' + self.platform})
517        p = ET.SubElement(prjconf, 'Configuration')
518        p.text = self.buildtype
519        pl = ET.SubElement(prjconf, 'Platform')
520        pl.text = self.platform
521        globalgroup = ET.SubElement(root, 'PropertyGroup', Label='Globals')
522        guidelem = ET.SubElement(globalgroup, 'ProjectGuid')
523        guidelem.text = '{%s}' % guid
524        kw = ET.SubElement(globalgroup, 'Keyword')
525        kw.text = self.platform + 'Proj'
526        p = ET.SubElement(globalgroup, 'Platform')
527        p.text = self.platform
528        pname = ET.SubElement(globalgroup, 'ProjectName')
529        pname.text = project_name
530        if self.windows_target_platform_version:
531            ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version
532        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.Default.props')
533        type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration')
534        ET.SubElement(type_config, 'ConfigurationType')
535        ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte'
536        ET.SubElement(type_config, 'UseOfMfc').text = 'false'
537        if self.platform_toolset:
538            ET.SubElement(type_config, 'PlatformToolset').text = self.platform_toolset
539        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.props')
540        direlem = ET.SubElement(root, 'PropertyGroup')
541        fver = ET.SubElement(direlem, '_ProjectFileVersion')
542        fver.text = self.project_file_version
543        outdir = ET.SubElement(direlem, 'OutDir')
544        outdir.text = '.\\'
545        intdir = ET.SubElement(direlem, 'IntDir')
546        intdir.text = target.get_id() + '\\'
547        tname = ET.SubElement(direlem, 'TargetName')
548        tname.text = target.name
549        return root
550
551    def gen_run_target_vcxproj(self, target, ofname, guid):
552        root = self.create_basic_crap(target, guid)
553        depend_files = self.get_custom_target_depend_files(target)
554
555        if not target.command:
556            # This is an alias target and thus doesn't run any command. It's
557            # enough to emit the references to the other projects for them to
558            # be built/run/..., if necessary.
559            assert isinstance(target, build.AliasTarget)
560            assert len(depend_files) == 0
561        else:
562            assert not isinstance(target, build.AliasTarget)
563
564            target_env = self.get_run_target_env(target)
565            _, _, cmd_raw = self.eval_custom_target_command(target)
566            wrapper_cmd, _ = self.as_meson_exe_cmdline(target.command[0], cmd_raw[1:],
567                                                       force_serialize=True, env=target_env,
568                                                       verbose=True)
569            self.add_custom_build(root, 'run_target', ' '.join(self.quote_arguments(wrapper_cmd)),
570                                  deps=depend_files)
571
572        # The import is needed even for alias targets, otherwise the build
573        # target isn't defined
574        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
575        self.add_regen_dependency(root)
576        self.add_target_deps(root, target)
577        self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
578
579    def gen_custom_target_vcxproj(self, target, ofname, guid):
580        root = self.create_basic_crap(target, guid)
581        # We need to always use absolute paths because our invocation is always
582        # from the target dir, not the build root.
583        target.absolute_paths = True
584        (srcs, ofilenames, cmd) = self.eval_custom_target_command(target, True)
585        depend_files = self.get_custom_target_depend_files(target, True)
586        # Always use a wrapper because MSBuild eats random characters when
587        # there are many arguments.
588        tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
589        extra_bdeps = target.get_transitive_build_target_deps()
590        wrapper_cmd, _ = self.as_meson_exe_cmdline(target.command[0], cmd[1:],
591                                                   # All targets run from the target dir
592                                                   workdir=tdir_abs,
593                                                   extra_bdeps=extra_bdeps,
594                                                   capture=ofilenames[0] if target.capture else None,
595                                                   feed=srcs[0] if target.feed else None,
596                                                   force_serialize=True,
597                                                   env=target.env)
598        if target.build_always_stale:
599            # Use a nonexistent file to always consider the target out-of-date.
600            ofilenames += [self.nonexistent_file(os.path.join(self.environment.get_scratch_dir(),
601                                                 'outofdate.file'))]
602        self.add_custom_build(root, 'custom_target', ' '.join(self.quote_arguments(wrapper_cmd)),
603                              deps=wrapper_cmd[-1:] + srcs + depend_files, outputs=ofilenames,
604                              verify_files=not target.build_always_stale)
605        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
606        self.generate_custom_generator_commands(target, root)
607        self.add_regen_dependency(root)
608        self.add_target_deps(root, target)
609        self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
610
611    @classmethod
612    def lang_from_source_file(cls, src):
613        ext = src.split('.')[-1]
614        if ext in compilers.c_suffixes:
615            return 'c'
616        if ext in compilers.cpp_suffixes:
617            return 'cpp'
618        raise MesonException('Could not guess language from source file %s.' % src)
619
620    def add_pch(self, pch_sources, lang, inc_cl):
621        if lang in pch_sources:
622            self.use_pch(pch_sources, lang, inc_cl)
623
624    def create_pch(self, pch_sources, lang, inc_cl):
625        pch = ET.SubElement(inc_cl, 'PrecompiledHeader')
626        pch.text = 'Create'
627        self.add_pch_files(pch_sources, lang, inc_cl)
628
629    def use_pch(self, pch_sources, lang, inc_cl):
630        pch = ET.SubElement(inc_cl, 'PrecompiledHeader')
631        pch.text = 'Use'
632        header = self.add_pch_files(pch_sources, lang, inc_cl)
633        pch_include = ET.SubElement(inc_cl, 'ForcedIncludeFiles')
634        pch_include.text = header + ';%(ForcedIncludeFiles)'
635
636    def add_pch_files(self, pch_sources, lang, inc_cl):
637        header = os.path.basename(pch_sources[lang][0])
638        pch_file = ET.SubElement(inc_cl, 'PrecompiledHeaderFile')
639        # When USING PCHs, MSVC will not do the regular include
640        # directory lookup, but simply use a string match to find the
641        # PCH to use. That means the #include directive must match the
642        # pch_file.text used during PCH CREATION verbatim.
643        # When CREATING a PCH, MSVC will do the include directory
644        # lookup to find the actual PCH header to use. Thus, the PCH
645        # header must either be in the include_directories of the target
646        # or be in the same directory as the PCH implementation.
647        pch_file.text = header
648        pch_out = ET.SubElement(inc_cl, 'PrecompiledHeaderOutputFile')
649        pch_out.text = '$(IntDir)$(TargetName)-%s.pch' % lang
650        return header
651
652    def is_argument_with_msbuild_xml_entry(self, entry):
653        # Remove arguments that have a top level XML entry so
654        # they are not used twice.
655        # FIXME add args as needed.
656        if entry[1:].startswith('fsanitize'):
657            return True
658        return entry[1:].startswith('M')
659
660    def add_additional_options(self, lang, parent_node, file_args):
661        args = []
662        for arg in file_args[lang].to_native():
663            if self.is_argument_with_msbuild_xml_entry(arg):
664                continue
665            if arg == '%(AdditionalOptions)':
666                args.append(arg)
667            else:
668                args.append(self.escape_additional_option(arg))
669        ET.SubElement(parent_node, "AdditionalOptions").text = ' '.join(args)
670
671    def add_preprocessor_defines(self, lang, parent_node, file_defines):
672        defines = []
673        for define in file_defines[lang]:
674            if define == '%(PreprocessorDefinitions)':
675                defines.append(define)
676            else:
677                defines.append(self.escape_preprocessor_define(define))
678        ET.SubElement(parent_node, "PreprocessorDefinitions").text = ';'.join(defines)
679
680    def add_include_dirs(self, lang, parent_node, file_inc_dirs):
681        dirs = file_inc_dirs[lang]
682        ET.SubElement(parent_node, "AdditionalIncludeDirectories").text = ';'.join(dirs)
683
684    @staticmethod
685    def has_objects(objects, additional_objects, generated_objects):
686        # Ignore generated objects, those are automatically used by MSBuild because they are part of
687        # the CustomBuild Outputs.
688        return len(objects) + len(additional_objects) > 0
689
690    @staticmethod
691    def add_generated_objects(node, generated_objects):
692        # Do not add generated objects to project file. Those are automatically used by MSBuild, because
693        # they are part of the CustomBuild Outputs.
694        return
695
696    @staticmethod
697    def escape_preprocessor_define(define):
698        # See: https://msdn.microsoft.com/en-us/library/bb383819.aspx
699        table = str.maketrans({'%': '%25', '$': '%24', '@': '%40',
700                               "'": '%27', ';': '%3B', '?': '%3F', '*': '%2A',
701                               # We need to escape backslash because it'll be un-escaped by
702                               # Windows during process creation when it parses the arguments
703                               # Basically, this converts `\` to `\\`.
704                               '\\': '\\\\'})
705        return define.translate(table)
706
707    @staticmethod
708    def escape_additional_option(option):
709        # See: https://msdn.microsoft.com/en-us/library/bb383819.aspx
710        table = str.maketrans({'%': '%25', '$': '%24', '@': '%40',
711                               "'": '%27', ';': '%3B', '?': '%3F', '*': '%2A', ' ': '%20'})
712        option = option.translate(table)
713        # Since we're surrounding the option with ", if it ends in \ that will
714        # escape the " when the process arguments are parsed and the starting
715        # " will not terminate. So we escape it if that's the case.  I'm not
716        # kidding, this is how escaping works for process args on Windows.
717        if option.endswith('\\'):
718            option += '\\'
719        return f'"{option}"'
720
721    @staticmethod
722    def split_link_args(args):
723        """
724        Split a list of link arguments into three lists:
725        * library search paths
726        * library filenames (or paths)
727        * other link arguments
728        """
729        lpaths = []
730        libs = []
731        other = []
732        for arg in args:
733            if arg.startswith('/LIBPATH:'):
734                lpath = arg[9:]
735                # De-dup library search paths by removing older entries when
736                # a new one is found. This is necessary because unlike other
737                # search paths such as the include path, the library is
738                # searched for in the newest (right-most) search path first.
739                if lpath in lpaths:
740                    lpaths.remove(lpath)
741                lpaths.append(lpath)
742            elif arg.startswith(('/', '-')):
743                other.append(arg)
744            # It's ok if we miss libraries with non-standard extensions here.
745            # They will go into the general link arguments.
746            elif arg.endswith('.lib') or arg.endswith('.a'):
747                # De-dup
748                if arg not in libs:
749                    libs.append(arg)
750            else:
751                other.append(arg)
752        return lpaths, libs, other
753
754    def _get_cl_compiler(self, target):
755        for lang, c in target.compilers.items():
756            if lang in ('c', 'cpp'):
757                return c
758        # No source files, only objects, but we still need a compiler, so
759        # return a found compiler
760        if len(target.objects) > 0:
761            for lang, c in self.environment.coredata.compilers[target.for_machine].items():
762                if lang in ('c', 'cpp'):
763                    return c
764        raise MesonException('Could not find a C or C++ compiler. MSVC can only build C/C++ projects.')
765
766    def _prettyprint_vcxproj_xml(self, tree, ofname):
767        ofname_tmp = ofname + '~'
768        tree.write(ofname_tmp, encoding='utf-8', xml_declaration=True)
769
770        # ElementTree can not do prettyprinting so do it manually
771        doc = xml.dom.minidom.parse(ofname_tmp)
772        with open(ofname_tmp, 'w', encoding='utf-8') as of:
773            of.write(doc.toprettyxml())
774        replace_if_different(ofname, ofname_tmp)
775
776    def gen_vcxproj(self, target, ofname, guid):
777        mlog.debug('Generating vcxproj %s.' % target.name)
778        subsystem = 'Windows'
779        self.handled_target_deps[target.get_id()] = []
780        if isinstance(target, build.Executable):
781            conftype = 'Application'
782            if target.gui_app is not None:
783                if not target.gui_app:
784                    subsystem = 'Console'
785            else:
786                # If someone knows how to set the version properly,
787                # please send a patch.
788                subsystem = target.win_subsystem.split(',')[0]
789        elif isinstance(target, build.StaticLibrary):
790            conftype = 'StaticLibrary'
791        elif isinstance(target, build.SharedLibrary):
792            conftype = 'DynamicLibrary'
793        elif isinstance(target, build.CustomTarget):
794            return self.gen_custom_target_vcxproj(target, ofname, guid)
795        elif isinstance(target, build.RunTarget):
796            return self.gen_run_target_vcxproj(target, ofname, guid)
797        else:
798            raise MesonException('Unknown target type for %s' % target.get_basename())
799        # Prefix to use to access the build root from the vcxproj dir
800        down = self.target_to_build_root(target)
801        # Prefix to use to access the source tree's root from the vcxproj dir
802        proj_to_src_root = os.path.join(down, self.build_to_src)
803        # Prefix to use to access the source tree's subdir from the vcxproj dir
804        proj_to_src_dir = os.path.join(proj_to_src_root, self.get_target_dir(target))
805        (sources, headers, objects, languages) = self.split_sources(target.sources)
806        if self.is_unity(target):
807            sources = self.generate_unity_files(target, sources)
808        compiler = self._get_cl_compiler(target)
809        build_args = compiler.get_buildtype_args(self.buildtype)
810        build_args += compiler.get_optimization_args(self.optimization)
811        build_args += compiler.get_debug_args(self.debug)
812        build_args += compiler.sanitizer_compile_args(self.sanitize)
813        buildtype_link_args = compiler.get_buildtype_linker_args(self.buildtype)
814        vscrt_type = self.environment.coredata.options[OptionKey('b_vscrt')]
815        project_name = target.name
816        target_name = target.name
817        if target.for_machine is MachineChoice.BUILD:
818            platform = self.build_platform
819        else:
820            platform = self.platform
821        root = ET.Element('Project', {'DefaultTargets': "Build",
822                                      'ToolsVersion': '4.0',
823                                      'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'})
824        confitems = ET.SubElement(root, 'ItemGroup', {'Label': 'ProjectConfigurations'})
825        prjconf = ET.SubElement(confitems, 'ProjectConfiguration',
826                                {'Include': self.buildtype + '|' + platform})
827        p = ET.SubElement(prjconf, 'Configuration')
828        p.text = self.buildtype
829        pl = ET.SubElement(prjconf, 'Platform')
830        pl.text = platform
831        # Globals
832        globalgroup = ET.SubElement(root, 'PropertyGroup', Label='Globals')
833        guidelem = ET.SubElement(globalgroup, 'ProjectGuid')
834        guidelem.text = '{%s}' % guid
835        kw = ET.SubElement(globalgroup, 'Keyword')
836        kw.text = self.platform + 'Proj'
837        ns = ET.SubElement(globalgroup, 'RootNamespace')
838        ns.text = target_name
839        p = ET.SubElement(globalgroup, 'Platform')
840        p.text = platform
841        pname = ET.SubElement(globalgroup, 'ProjectName')
842        pname.text = project_name
843        if self.windows_target_platform_version:
844            ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version
845        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.Default.props')
846        # Start configuration
847        type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration')
848        ET.SubElement(type_config, 'ConfigurationType').text = conftype
849        ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte'
850        if self.platform_toolset:
851            ET.SubElement(type_config, 'PlatformToolset').text = self.platform_toolset
852        # FIXME: Meson's LTO support needs to be integrated here
853        ET.SubElement(type_config, 'WholeProgramOptimization').text = 'false'
854        # Let VS auto-set the RTC level
855        ET.SubElement(type_config, 'BasicRuntimeChecks').text = 'Default'
856        # Incremental linking increases code size
857        if '/INCREMENTAL:NO' in buildtype_link_args:
858            ET.SubElement(type_config, 'LinkIncremental').text = 'false'
859
860        # Build information
861        compiles = ET.SubElement(root, 'ItemDefinitionGroup')
862        clconf = ET.SubElement(compiles, 'ClCompile')
863        # CRT type; debug or release
864        if vscrt_type.value == 'from_buildtype':
865            if self.buildtype == 'debug':
866                ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
867                ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL'
868            else:
869                ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
870                ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDLL'
871        elif vscrt_type.value == 'static_from_buildtype':
872            if self.buildtype == 'debug':
873                ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
874                ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebug'
875            else:
876                ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
877                ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreaded'
878        elif vscrt_type.value == 'mdd':
879            ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
880            ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL'
881        elif vscrt_type.value == 'mt':
882            # FIXME, wrong
883            ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
884            ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreaded'
885        elif vscrt_type.value == 'mtd':
886            # FIXME, wrong
887            ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
888            ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebug'
889        else:
890            ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
891            ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDLL'
892        # Sanitizers
893        if '/fsanitize=address' in build_args:
894            ET.SubElement(type_config, 'EnableASAN').text = 'true'
895        # Debug format
896        if '/ZI' in build_args:
897            ET.SubElement(clconf, 'DebugInformationFormat').text = 'EditAndContinue'
898        elif '/Zi' in build_args:
899            ET.SubElement(clconf, 'DebugInformationFormat').text = 'ProgramDatabase'
900        elif '/Z7' in build_args:
901            ET.SubElement(clconf, 'DebugInformationFormat').text = 'OldStyle'
902        else:
903            ET.SubElement(clconf, 'DebugInformationFormat').text = 'None'
904        # Runtime checks
905        if '/RTC1' in build_args:
906            ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'EnableFastChecks'
907        elif '/RTCu' in build_args:
908            ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'UninitializedLocalUsageCheck'
909        elif '/RTCs' in build_args:
910            ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'StackFrameRuntimeCheck'
911        # Exception handling has to be set in the xml in addition to the "AdditionalOptions" because otherwise
912        # cl will give warning D9025: overriding '/Ehs' with cpp_eh value
913        if 'cpp' in target.compilers:
914            eh = self.environment.coredata.options[OptionKey('eh', machine=target.for_machine, lang='cpp')]
915            if eh.value == 'a':
916                ET.SubElement(clconf, 'ExceptionHandling').text = 'Async'
917            elif eh.value == 's':
918                ET.SubElement(clconf, 'ExceptionHandling').text = 'SyncCThrow'
919            elif eh.value == 'none':
920                ET.SubElement(clconf, 'ExceptionHandling').text = 'false'
921            else: # 'sc' or 'default'
922                ET.SubElement(clconf, 'ExceptionHandling').text = 'Sync'
923        # End configuration
924        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.props')
925        generated_files, custom_target_output_files, generated_files_include_dirs = self.generate_custom_generator_commands(target, root)
926        (gen_src, gen_hdrs, gen_objs, gen_langs) = self.split_sources(generated_files)
927        (custom_src, custom_hdrs, custom_objs, custom_langs) = self.split_sources(custom_target_output_files)
928        gen_src += custom_src
929        gen_hdrs += custom_hdrs
930        gen_langs += custom_langs
931        # Project information
932        direlem = ET.SubElement(root, 'PropertyGroup')
933        fver = ET.SubElement(direlem, '_ProjectFileVersion')
934        fver.text = self.project_file_version
935        outdir = ET.SubElement(direlem, 'OutDir')
936        outdir.text = '.\\'
937        intdir = ET.SubElement(direlem, 'IntDir')
938        intdir.text = target.get_id() + '\\'
939        tfilename = os.path.splitext(target.get_filename())
940        ET.SubElement(direlem, 'TargetName').text = tfilename[0]
941        ET.SubElement(direlem, 'TargetExt').text = tfilename[1]
942
943        # Arguments, include dirs, defines for all files in the current target
944        target_args = []
945        target_defines = []
946        target_inc_dirs = []
947        # Arguments, include dirs, defines passed to individual files in
948        # a target; perhaps because the args are language-specific
949        #
950        # file_args is also later split out into defines and include_dirs in
951        # case someone passed those in there
952        file_args = {l: c.compiler_args() for l, c in target.compilers.items()}
953        file_defines = {l: [] for l in target.compilers}
954        file_inc_dirs = {l: [] for l in target.compilers}
955        # The order in which these compile args are added must match
956        # generate_single_compile() and generate_basic_compiler_args()
957        for l, comp in target.compilers.items():
958            if l in file_args:
959                file_args[l] += compilers.get_base_compile_args(
960                    self.get_base_options_for_target(target), comp)
961                file_args[l] += comp.get_option_compile_args(
962                    self.environment.coredata.options)
963
964        # Add compile args added using add_project_arguments()
965        for l, args in self.build.projects_args[target.for_machine].get(target.subproject, {}).items():
966            if l in file_args:
967                file_args[l] += args
968        # Add compile args added using add_global_arguments()
969        # These override per-project arguments
970        for l, args in self.build.global_args[target.for_machine].items():
971            if l in file_args:
972                file_args[l] += args
973        # Compile args added from the env or cross file: CFLAGS/CXXFLAGS, etc. We want these
974        # to override all the defaults, but not the per-target compile args.
975        for l in file_args.keys():
976            opts = self.environment.coredata.options[OptionKey('args', machine=target.for_machine, lang=l)]
977            file_args[l] += opts.value
978        for args in file_args.values():
979            # This is where Visual Studio will insert target_args, target_defines,
980            # etc, which are added later from external deps (see below).
981            args += ['%(AdditionalOptions)', '%(PreprocessorDefinitions)', '%(AdditionalIncludeDirectories)']
982            # Add custom target dirs as includes automatically, but before
983            # target-specific include dirs. See _generate_single_compile() in
984            # the ninja backend for caveats.
985            args += ['-I' + arg for arg in generated_files_include_dirs]
986            # Add include dirs from the `include_directories:` kwarg on the target
987            # and from `include_directories:` of internal deps of the target.
988            #
989            # Target include dirs should override internal deps include dirs.
990            # This is handled in BuildTarget.process_kwargs()
991            #
992            # Include dirs from internal deps should override include dirs from
993            # external deps and must maintain the order in which they are
994            # specified. Hence, we must reverse so that the order is preserved.
995            #
996            # These are per-target, but we still add them as per-file because we
997            # need them to be looked in first.
998            for d in reversed(target.get_include_dirs()):
999                # reversed is used to keep order of includes
1000                for i in reversed(d.get_incdirs()):
1001                    curdir = os.path.join(d.get_curdir(), i)
1002                    args.append('-I' + self.relpath(curdir, target.subdir)) # build dir
1003                    args.append('-I' + os.path.join(proj_to_src_root, curdir)) # src dir
1004                for i in d.get_extra_build_dirs():
1005                    curdir = os.path.join(d.get_curdir(), i)
1006                    args.append('-I' + self.relpath(curdir, target.subdir))  # build dir
1007        # Add per-target compile args, f.ex, `c_args : ['/DFOO']`. We set these
1008        # near the end since these are supposed to override everything else.
1009        for l, args in target.extra_args.items():
1010            if l in file_args:
1011                file_args[l] += args
1012        # The highest priority includes. In order of directory search:
1013        # target private dir, target build dir, target source dir
1014        for args in file_args.values():
1015            t_inc_dirs = [self.relpath(self.get_target_private_dir(target),
1016                                       self.get_target_dir(target))]
1017            if target.implicit_include_directories:
1018                t_inc_dirs += ['.', proj_to_src_dir]
1019            args += ['-I' + arg for arg in t_inc_dirs]
1020
1021        # Split preprocessor defines and include directories out of the list of
1022        # all extra arguments. The rest go into %(AdditionalOptions).
1023        for l, args in file_args.items():
1024            for arg in args[:]:
1025                if arg.startswith(('-D', '/D')) or arg == '%(PreprocessorDefinitions)':
1026                    file_args[l].remove(arg)
1027                    # Don't escape the marker
1028                    if arg == '%(PreprocessorDefinitions)':
1029                        define = arg
1030                    else:
1031                        define = arg[2:]
1032                    # De-dup
1033                    if define not in file_defines[l]:
1034                        file_defines[l].append(define)
1035                elif arg.startswith(('-I', '/I')) or arg == '%(AdditionalIncludeDirectories)':
1036                    file_args[l].remove(arg)
1037                    # Don't escape the marker
1038                    if arg == '%(AdditionalIncludeDirectories)':
1039                        inc_dir = arg
1040                    else:
1041                        inc_dir = arg[2:]
1042                    # De-dup
1043                    if inc_dir not in file_inc_dirs[l]:
1044                        file_inc_dirs[l].append(inc_dir)
1045                    # Add include dirs to target as well so that "Go to Document" works in headers
1046                    if inc_dir not in target_inc_dirs:
1047                        target_inc_dirs.append(inc_dir)
1048
1049        # Split compile args needed to find external dependencies
1050        # Link args are added while generating the link command
1051        for d in reversed(target.get_external_deps()):
1052            # Cflags required by external deps might have UNIX-specific flags,
1053            # so filter them out if needed
1054            if isinstance(d, dependencies.OpenMPDependency):
1055                ET.SubElement(clconf, 'OpenMPSupport').text = 'true'
1056            else:
1057                d_compile_args = compiler.unix_args_to_native(d.get_compile_args())
1058                for arg in d_compile_args:
1059                    if arg.startswith(('-D', '/D')):
1060                        define = arg[2:]
1061                        # De-dup
1062                        if define in target_defines:
1063                            target_defines.remove(define)
1064                        target_defines.append(define)
1065                    elif arg.startswith(('-I', '/I')):
1066                        inc_dir = arg[2:]
1067                        # De-dup
1068                        if inc_dir not in target_inc_dirs:
1069                            target_inc_dirs.append(inc_dir)
1070                    else:
1071                        target_args.append(arg)
1072
1073        languages += gen_langs
1074        if '/Gw' in build_args:
1075            target_args.append('/Gw')
1076        if len(target_args) > 0:
1077            target_args.append('%(AdditionalOptions)')
1078            ET.SubElement(clconf, "AdditionalOptions").text = ' '.join(target_args)
1079        ET.SubElement(clconf, 'AdditionalIncludeDirectories').text = ';'.join(target_inc_dirs)
1080        target_defines.append('%(PreprocessorDefinitions)')
1081        ET.SubElement(clconf, 'PreprocessorDefinitions').text = ';'.join(target_defines)
1082        ET.SubElement(clconf, 'FunctionLevelLinking').text = 'true'
1083        # Warning level
1084        warning_level = self.get_option_for_target(OptionKey('warning_level'), target)
1085        ET.SubElement(clconf, 'WarningLevel').text = 'Level' + str(1 + int(warning_level))
1086        if self.get_option_for_target(OptionKey('werror'), target):
1087            ET.SubElement(clconf, 'TreatWarningAsError').text = 'true'
1088        # Optimization flags
1089        o_flags = split_o_flags_args(build_args)
1090        if '/Ox' in o_flags:
1091            ET.SubElement(clconf, 'Optimization').text = 'Full'
1092        elif '/O2' in o_flags:
1093            ET.SubElement(clconf, 'Optimization').text = 'MaxSpeed'
1094        elif '/O1' in o_flags:
1095            ET.SubElement(clconf, 'Optimization').text = 'MinSpace'
1096        elif '/Od' in o_flags:
1097            ET.SubElement(clconf, 'Optimization').text = 'Disabled'
1098        if '/Oi' in o_flags:
1099            ET.SubElement(clconf, 'IntrinsicFunctions').text = 'true'
1100        if '/Ob1' in o_flags:
1101            ET.SubElement(clconf, 'InlineFunctionExpansion').text = 'OnlyExplicitInline'
1102        elif '/Ob2' in o_flags:
1103            ET.SubElement(clconf, 'InlineFunctionExpansion').text = 'AnySuitable'
1104        # Size-preserving flags
1105        if '/Os' in o_flags:
1106            ET.SubElement(clconf, 'FavorSizeOrSpeed').text = 'Size'
1107        else:
1108            ET.SubElement(clconf, 'FavorSizeOrSpeed').text = 'Speed'
1109        # Note: SuppressStartupBanner is /NOLOGO and is 'true' by default
1110        pch_sources = {}
1111        if self.environment.coredata.options.get(OptionKey('b_pch')):
1112            for lang in ['c', 'cpp']:
1113                pch = target.get_pch(lang)
1114                if not pch:
1115                    continue
1116                if compiler.id == 'msvc':
1117                    if len(pch) == 1:
1118                        # Auto generate PCH.
1119                        src = os.path.join(down, self.create_msvc_pch_implementation(target, lang, pch[0]))
1120                        pch_header_dir = os.path.dirname(os.path.join(proj_to_src_dir, pch[0]))
1121                    else:
1122                        src = os.path.join(proj_to_src_dir, pch[1])
1123                        pch_header_dir = None
1124                    pch_sources[lang] = [pch[0], src, lang, pch_header_dir]
1125                else:
1126                    # I don't know whether its relevant but let's handle other compilers
1127                    # used with a vs backend
1128                    pch_sources[lang] = [pch[0], None, lang, None]
1129
1130        resourcecompile = ET.SubElement(compiles, 'ResourceCompile')
1131        ET.SubElement(resourcecompile, 'PreprocessorDefinitions')
1132
1133        # Linker options
1134        link = ET.SubElement(compiles, 'Link')
1135        extra_link_args = compiler.compiler_args()
1136        # FIXME: Can these buildtype linker args be added as tags in the
1137        # vcxproj file (similar to buildtype compiler args) instead of in
1138        # AdditionalOptions?
1139        extra_link_args += compiler.get_buildtype_linker_args(self.buildtype)
1140        # Generate Debug info
1141        if self.debug:
1142            self.generate_debug_information(link)
1143        else:
1144            ET.SubElement(link, 'GenerateDebugInformation').text = 'false'
1145        if not isinstance(target, build.StaticLibrary):
1146            if isinstance(target, build.SharedModule):
1147                options = self.environment.coredata.options
1148                extra_link_args += compiler.get_std_shared_module_link_args(options)
1149            # Add link args added using add_project_link_arguments()
1150            extra_link_args += self.build.get_project_link_args(compiler, target.subproject, target.for_machine)
1151            # Add link args added using add_global_link_arguments()
1152            # These override per-project link arguments
1153            extra_link_args += self.build.get_global_link_args(compiler, target.for_machine)
1154            # Link args added from the env: LDFLAGS, or the cross file. We want
1155            # these to override all the defaults but not the per-target link
1156            # args.
1157            extra_link_args += self.environment.coredata.get_external_link_args(target.for_machine, compiler.get_language())
1158            # Only non-static built targets need link args and link dependencies
1159            extra_link_args += target.link_args
1160            # External deps must be last because target link libraries may depend on them.
1161            for dep in target.get_external_deps():
1162                # Extend without reordering or de-dup to preserve `-L -l` sets
1163                # https://github.com/mesonbuild/meson/issues/1718
1164                if isinstance(dep, dependencies.OpenMPDependency):
1165                    ET.SubElement(clconf, 'OpenMPSuppport').text = 'true'
1166                else:
1167                    extra_link_args.extend_direct(dep.get_link_args())
1168            for d in target.get_dependencies():
1169                if isinstance(d, build.StaticLibrary):
1170                    for dep in d.get_external_deps():
1171                        if isinstance(dep, dependencies.OpenMPDependency):
1172                            ET.SubElement(clconf, 'OpenMPSuppport').text = 'true'
1173                        else:
1174                            extra_link_args.extend_direct(dep.get_link_args())
1175        # Add link args for c_* or cpp_* build options. Currently this only
1176        # adds c_winlibs and cpp_winlibs when building for Windows. This needs
1177        # to be after all internal and external libraries so that unresolved
1178        # symbols from those can be found here. This is needed when the
1179        # *_winlibs that we want to link to are static mingw64 libraries.
1180        extra_link_args += compiler.get_option_link_args(self.environment.coredata.options)
1181        (additional_libpaths, additional_links, extra_link_args) = self.split_link_args(extra_link_args.to_native())
1182
1183        # Add more libraries to be linked if needed
1184        for t in target.get_dependencies():
1185            if isinstance(t, build.CustomTargetIndex):
1186                # We don't need the actual project here, just the library name
1187                lobj = t
1188            else:
1189                lobj = self.build.targets[t.get_id()]
1190            linkname = os.path.join(down, self.get_target_filename_for_linking(lobj))
1191            if t in target.link_whole_targets:
1192                if compiler.id == 'msvc' and version_compare(compiler.version, '<19.00.23918'):
1193                    # Expand our object lists manually if we are on pre-Visual Studio 2015 Update 2
1194                    l = t.extract_all_objects(False)
1195
1196                    # Unforunately, we can't use self.object_filename_from_source()
1197                    gensrclist: T.List[File] = []
1198                    for gen in l.genlist:
1199                        for src in gen.get_outputs():
1200                            if self.environment.is_source(src) and not self.environment.is_header(src):
1201                                path = self.get_target_generated_dir(t, gen, src)
1202                                gen_src_ext = '.' + os.path.splitext(path)[1][1:]
1203                                extra_link_args.append(path[:-len(gen_src_ext)] + '.obj')
1204
1205                    for src in l.srclist:
1206                        obj_basename = None
1207                        if self.environment.is_source(src) and not self.environment.is_header(src):
1208                            obj_basename = self.object_filename_from_source(t, src)
1209                            target_private_dir = self.relpath(self.get_target_private_dir(t),
1210                                                              self.get_target_dir(t))
1211                            rel_obj = os.path.join(target_private_dir, obj_basename)
1212                            extra_link_args.append(rel_obj)
1213
1214                    extra_link_args.extend(self.flatten_object_list(t))
1215                else:
1216                    # /WHOLEARCHIVE:foo must go into AdditionalOptions
1217                    extra_link_args += compiler.get_link_whole_for(linkname)
1218                # To force Visual Studio to build this project even though it
1219                # has no sources, we include a reference to the vcxproj file
1220                # that builds this target. Technically we should add this only
1221                # if the current target has no sources, but it doesn't hurt to
1222                # have 'extra' references.
1223                trelpath = self.get_target_dir_relative_to(t, target)
1224                tvcxproj = os.path.join(trelpath, t.get_id() + '.vcxproj')
1225                tid = self.environment.coredata.target_guids[t.get_id()]
1226                self.add_project_reference(root, tvcxproj, tid, link_outputs=True)
1227                # Mark the dependency as already handled to not have
1228                # multiple references to the same target.
1229                self.handled_target_deps[target.get_id()].append(t.get_id())
1230            else:
1231                # Other libraries go into AdditionalDependencies
1232                if linkname not in additional_links:
1233                    additional_links.append(linkname)
1234        for lib in self.get_custom_target_provided_libraries(target):
1235            additional_links.append(self.relpath(lib, self.get_target_dir(target)))
1236        additional_objects = []
1237        for o in self.flatten_object_list(target, down):
1238            assert isinstance(o, str)
1239            additional_objects.append(o)
1240        for o in custom_objs:
1241            additional_objects.append(o)
1242
1243        if len(extra_link_args) > 0:
1244            extra_link_args.append('%(AdditionalOptions)')
1245            ET.SubElement(link, "AdditionalOptions").text = ' '.join(extra_link_args)
1246        if len(additional_libpaths) > 0:
1247            additional_libpaths.insert(0, '%(AdditionalLibraryDirectories)')
1248            ET.SubElement(link, 'AdditionalLibraryDirectories').text = ';'.join(additional_libpaths)
1249        if len(additional_links) > 0:
1250            additional_links.append('%(AdditionalDependencies)')
1251            ET.SubElement(link, 'AdditionalDependencies').text = ';'.join(additional_links)
1252        ofile = ET.SubElement(link, 'OutputFile')
1253        ofile.text = '$(OutDir)%s' % target.get_filename()
1254        subsys = ET.SubElement(link, 'SubSystem')
1255        subsys.text = subsystem
1256        if (isinstance(target, build.SharedLibrary) or isinstance(target, build.Executable)) and target.get_import_filename():
1257            # DLLs built with MSVC always have an import library except when
1258            # they're data-only DLLs, but we don't support those yet.
1259            ET.SubElement(link, 'ImportLibrary').text = target.get_import_filename()
1260        if isinstance(target, build.SharedLibrary):
1261            # Add module definitions file, if provided
1262            if target.vs_module_defs:
1263                relpath = os.path.join(down, target.vs_module_defs.rel_to_builddir(self.build_to_src))
1264                ET.SubElement(link, 'ModuleDefinitionFile').text = relpath
1265        if self.debug:
1266            pdb = ET.SubElement(link, 'ProgramDataBaseFileName')
1267            pdb.text = '$(OutDir}%s.pdb' % target_name
1268        targetmachine = ET.SubElement(link, 'TargetMachine')
1269        if target.for_machine is MachineChoice.BUILD:
1270            targetplatform = platform
1271        else:
1272            targetplatform = self.platform.lower()
1273        if targetplatform == 'win32':
1274            targetmachine.text = 'MachineX86'
1275        elif targetplatform == 'x64':
1276            targetmachine.text = 'MachineX64'
1277        elif targetplatform == 'arm':
1278            targetmachine.text = 'MachineARM'
1279        elif targetplatform == 'arm64':
1280            targetmachine.text = 'MachineARM64'
1281        elif targetplatform == 'arm64ec':
1282            targetmachine.text = 'MachineARM64EC'
1283        else:
1284            raise MesonException('Unsupported Visual Studio target machine: ' + targetplatform)
1285        # /nologo
1286        ET.SubElement(link, 'SuppressStartupBanner').text = 'true'
1287        # /release
1288        if not self.environment.coredata.get_option(OptionKey('debug')):
1289            ET.SubElement(link, 'SetChecksum').text = 'true'
1290
1291        meson_file_group = ET.SubElement(root, 'ItemGroup')
1292        ET.SubElement(meson_file_group, 'None', Include=os.path.join(proj_to_src_dir, build_filename))
1293
1294        # Visual Studio can't load projects that present duplicated items. Filter them out
1295        # by keeping track of already added paths.
1296        def path_normalize_add(path, lis):
1297            normalized = os.path.normcase(os.path.normpath(path))
1298            if normalized not in lis:
1299                lis.append(normalized)
1300                return True
1301            else:
1302                return False
1303
1304        previous_includes = []
1305        if len(headers) + len(gen_hdrs) + len(target.extra_files) + len(pch_sources) > 0:
1306            inc_hdrs = ET.SubElement(root, 'ItemGroup')
1307            for h in headers:
1308                relpath = os.path.join(down, h.rel_to_builddir(self.build_to_src))
1309                if path_normalize_add(relpath, previous_includes):
1310                    ET.SubElement(inc_hdrs, 'CLInclude', Include=relpath)
1311            for h in gen_hdrs:
1312                if path_normalize_add(h, previous_includes):
1313                    ET.SubElement(inc_hdrs, 'CLInclude', Include=h)
1314            for h in target.extra_files:
1315                relpath = os.path.join(down, h.rel_to_builddir(self.build_to_src))
1316                if path_normalize_add(relpath, previous_includes):
1317                    ET.SubElement(inc_hdrs, 'CLInclude', Include=relpath)
1318            for lang in pch_sources:
1319                h = pch_sources[lang][0]
1320                path = os.path.join(proj_to_src_dir, h)
1321                if path_normalize_add(path, previous_includes):
1322                    ET.SubElement(inc_hdrs, 'CLInclude', Include=path)
1323
1324        previous_sources = []
1325        if len(sources) + len(gen_src) + len(pch_sources) > 0:
1326            inc_src = ET.SubElement(root, 'ItemGroup')
1327            for s in sources:
1328                relpath = os.path.join(down, s.rel_to_builddir(self.build_to_src))
1329                if path_normalize_add(relpath, previous_sources):
1330                    inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=relpath)
1331                    lang = Vs2010Backend.lang_from_source_file(s)
1332                    self.add_pch(pch_sources, lang, inc_cl)
1333                    self.add_additional_options(lang, inc_cl, file_args)
1334                    self.add_preprocessor_defines(lang, inc_cl, file_defines)
1335                    self.add_include_dirs(lang, inc_cl, file_inc_dirs)
1336                    ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + self.object_filename_from_source(target, s)
1337            for s in gen_src:
1338                if path_normalize_add(s, previous_sources):
1339                    inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=s)
1340                    lang = Vs2010Backend.lang_from_source_file(s)
1341                    self.add_pch(pch_sources, lang, inc_cl)
1342                    self.add_additional_options(lang, inc_cl, file_args)
1343                    self.add_preprocessor_defines(lang, inc_cl, file_defines)
1344                    self.add_include_dirs(lang, inc_cl, file_inc_dirs)
1345                    s = File.from_built_file(target.get_subdir(), s)
1346                    ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + self.object_filename_from_source(target, s)
1347            for lang in pch_sources:
1348                impl = pch_sources[lang][1]
1349                if impl and path_normalize_add(impl, previous_sources):
1350                    inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=impl)
1351                    self.create_pch(pch_sources, lang, inc_cl)
1352                    self.add_additional_options(lang, inc_cl, file_args)
1353                    self.add_preprocessor_defines(lang, inc_cl, file_defines)
1354                    pch_header_dir = pch_sources[lang][3]
1355                    if pch_header_dir:
1356                        inc_dirs = copy.deepcopy(file_inc_dirs)
1357                        inc_dirs[lang] = [pch_header_dir] + inc_dirs[lang]
1358                    else:
1359                        inc_dirs = file_inc_dirs
1360                    self.add_include_dirs(lang, inc_cl, inc_dirs)
1361                    #XXX: Do we need to set the object file name name here too?
1362
1363        previous_objects = []
1364        if self.has_objects(objects, additional_objects, gen_objs):
1365            inc_objs = ET.SubElement(root, 'ItemGroup')
1366            for s in objects:
1367                relpath = os.path.join(down, s.rel_to_builddir(self.build_to_src))
1368                if path_normalize_add(relpath, previous_objects):
1369                    ET.SubElement(inc_objs, 'Object', Include=relpath)
1370            for s in additional_objects:
1371                if path_normalize_add(s, previous_objects):
1372                    ET.SubElement(inc_objs, 'Object', Include=s)
1373            self.add_generated_objects(inc_objs, gen_objs)
1374
1375        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
1376        self.add_regen_dependency(root)
1377        self.add_target_deps(root, target)
1378        self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
1379
1380    def gen_regenproj(self, project_name, ofname):
1381        root = ET.Element('Project', {'DefaultTargets': 'Build',
1382                                      'ToolsVersion': '4.0',
1383                                      'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'})
1384        confitems = ET.SubElement(root, 'ItemGroup', {'Label': 'ProjectConfigurations'})
1385        prjconf = ET.SubElement(confitems, 'ProjectConfiguration',
1386                                {'Include': self.buildtype + '|' + self.platform})
1387        p = ET.SubElement(prjconf, 'Configuration')
1388        p.text = self.buildtype
1389        pl = ET.SubElement(prjconf, 'Platform')
1390        pl.text = self.platform
1391        globalgroup = ET.SubElement(root, 'PropertyGroup', Label='Globals')
1392        guidelem = ET.SubElement(globalgroup, 'ProjectGuid')
1393        guidelem.text = '{%s}' % self.environment.coredata.regen_guid
1394        kw = ET.SubElement(globalgroup, 'Keyword')
1395        kw.text = self.platform + 'Proj'
1396        p = ET.SubElement(globalgroup, 'Platform')
1397        p.text = self.platform
1398        pname = ET.SubElement(globalgroup, 'ProjectName')
1399        pname.text = project_name
1400        if self.windows_target_platform_version:
1401            ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version
1402        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.Default.props')
1403        type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration')
1404        ET.SubElement(type_config, 'ConfigurationType').text = "Utility"
1405        ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte'
1406        ET.SubElement(type_config, 'UseOfMfc').text = 'false'
1407        if self.platform_toolset:
1408            ET.SubElement(type_config, 'PlatformToolset').text = self.platform_toolset
1409        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.props')
1410        direlem = ET.SubElement(root, 'PropertyGroup')
1411        fver = ET.SubElement(direlem, '_ProjectFileVersion')
1412        fver.text = self.project_file_version
1413        outdir = ET.SubElement(direlem, 'OutDir')
1414        outdir.text = '.\\'
1415        intdir = ET.SubElement(direlem, 'IntDir')
1416        intdir.text = 'regen-temp\\'
1417        tname = ET.SubElement(direlem, 'TargetName')
1418        tname.text = project_name
1419
1420        action = ET.SubElement(root, 'ItemDefinitionGroup')
1421        midl = ET.SubElement(action, 'Midl')
1422        ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
1423        ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
1424        ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
1425        ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
1426        ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
1427        ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
1428        regen_command = self.environment.get_build_command() + ['--internal', 'regencheck']
1429        cmd_templ = '''call %s > NUL
1430"%s" "%s"'''
1431        regen_command = cmd_templ % \
1432            (self.get_vcvars_command(), '" "'.join(regen_command), self.environment.get_scratch_dir())
1433        self.add_custom_build(root, 'regen', regen_command, deps=self.get_regen_filelist(),
1434                              outputs=[Vs2010Backend.get_regen_stampfile(self.environment.get_build_dir())],
1435                              msg='Checking whether solution needs to be regenerated.')
1436        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
1437        ET.SubElement(root, 'ImportGroup', Label='ExtensionTargets')
1438        self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
1439
1440    def gen_testproj(self, target_name, ofname):
1441        project_name = target_name
1442        root = ET.Element('Project', {'DefaultTargets': "Build",
1443                                      'ToolsVersion': '4.0',
1444                                      'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'})
1445        confitems = ET.SubElement(root, 'ItemGroup', {'Label': 'ProjectConfigurations'})
1446        prjconf = ET.SubElement(confitems, 'ProjectConfiguration',
1447                                {'Include': self.buildtype + '|' + self.platform})
1448        p = ET.SubElement(prjconf, 'Configuration')
1449        p.text = self.buildtype
1450        pl = ET.SubElement(prjconf, 'Platform')
1451        pl.text = self.platform
1452        globalgroup = ET.SubElement(root, 'PropertyGroup', Label='Globals')
1453        guidelem = ET.SubElement(globalgroup, 'ProjectGuid')
1454        guidelem.text = '{%s}' % self.environment.coredata.test_guid
1455        kw = ET.SubElement(globalgroup, 'Keyword')
1456        kw.text = self.platform + 'Proj'
1457        p = ET.SubElement(globalgroup, 'Platform')
1458        p.text = self.platform
1459        pname = ET.SubElement(globalgroup, 'ProjectName')
1460        pname.text = project_name
1461        if self.windows_target_platform_version:
1462            ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version
1463        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.Default.props')
1464        type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration')
1465        ET.SubElement(type_config, 'ConfigurationType')
1466        ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte'
1467        ET.SubElement(type_config, 'UseOfMfc').text = 'false'
1468        if self.platform_toolset:
1469            ET.SubElement(type_config, 'PlatformToolset').text = self.platform_toolset
1470        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.props')
1471        direlem = ET.SubElement(root, 'PropertyGroup')
1472        fver = ET.SubElement(direlem, '_ProjectFileVersion')
1473        fver.text = self.project_file_version
1474        outdir = ET.SubElement(direlem, 'OutDir')
1475        outdir.text = '.\\'
1476        intdir = ET.SubElement(direlem, 'IntDir')
1477        intdir.text = 'test-temp\\'
1478        tname = ET.SubElement(direlem, 'TargetName')
1479        tname.text = target_name
1480
1481        action = ET.SubElement(root, 'ItemDefinitionGroup')
1482        midl = ET.SubElement(action, 'Midl')
1483        ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
1484        ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
1485        ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
1486        ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
1487        ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
1488        ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
1489        # FIXME: No benchmarks?
1490        test_command = self.environment.get_build_command() + ['test', '--no-rebuild']
1491        if not self.environment.coredata.get_option(OptionKey('stdsplit')):
1492            test_command += ['--no-stdsplit']
1493        if self.environment.coredata.get_option(OptionKey('errorlogs')):
1494            test_command += ['--print-errorlogs']
1495        self.serialize_tests()
1496        self.add_custom_build(root, 'run_tests', '"%s"' % ('" "'.join(test_command)))
1497        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
1498        self.add_regen_dependency(root)
1499        self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
1500
1501    def gen_installproj(self, target_name, ofname):
1502        self.create_install_data_files()
1503        project_name = target_name
1504        root = ET.Element('Project', {'DefaultTargets': "Build",
1505                                      'ToolsVersion': '4.0',
1506                                      'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'})
1507        confitems = ET.SubElement(root, 'ItemGroup', {'Label': 'ProjectConfigurations'})
1508        prjconf = ET.SubElement(confitems, 'ProjectConfiguration',
1509                                {'Include': self.buildtype + '|' + self.platform})
1510        p = ET.SubElement(prjconf, 'Configuration')
1511        p.text = self.buildtype
1512        pl = ET.SubElement(prjconf, 'Platform')
1513        pl.text = self.platform
1514        globalgroup = ET.SubElement(root, 'PropertyGroup', Label='Globals')
1515        guidelem = ET.SubElement(globalgroup, 'ProjectGuid')
1516        guidelem.text = '{%s}' % self.environment.coredata.install_guid
1517        kw = ET.SubElement(globalgroup, 'Keyword')
1518        kw.text = self.platform + 'Proj'
1519        p = ET.SubElement(globalgroup, 'Platform')
1520        p.text = self.platform
1521        pname = ET.SubElement(globalgroup, 'ProjectName')
1522        pname.text = project_name
1523        if self.windows_target_platform_version:
1524            ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version
1525        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.Default.props')
1526        type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration')
1527        ET.SubElement(type_config, 'ConfigurationType')
1528        ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte'
1529        ET.SubElement(type_config, 'UseOfMfc').text = 'false'
1530        if self.platform_toolset:
1531            ET.SubElement(type_config, 'PlatformToolset').text = self.platform_toolset
1532        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.props')
1533        direlem = ET.SubElement(root, 'PropertyGroup')
1534        fver = ET.SubElement(direlem, '_ProjectFileVersion')
1535        fver.text = self.project_file_version
1536        outdir = ET.SubElement(direlem, 'OutDir')
1537        outdir.text = '.\\'
1538        intdir = ET.SubElement(direlem, 'IntDir')
1539        intdir.text = 'install-temp\\'
1540        tname = ET.SubElement(direlem, 'TargetName')
1541        tname.text = target_name
1542
1543        action = ET.SubElement(root, 'ItemDefinitionGroup')
1544        midl = ET.SubElement(action, 'Midl')
1545        ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
1546        ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
1547        ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
1548        ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
1549        ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
1550        ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
1551        install_command = self.environment.get_build_command() + ['install', '--no-rebuild']
1552        self.add_custom_build(root, 'run_install', '"%s"' % ('" "'.join(install_command)))
1553        ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
1554        self.add_regen_dependency(root)
1555        self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
1556
1557    def add_custom_build(self, node, rulename, command, deps=None, outputs=None, msg=None, verify_files=True):
1558        igroup = ET.SubElement(node, 'ItemGroup')
1559        rulefile = os.path.join(self.environment.get_scratch_dir(), rulename + '.rule')
1560        if not os.path.exists(rulefile):
1561            with open(rulefile, 'w', encoding='utf-8') as f:
1562                f.write("# Meson regen file.")
1563        custombuild = ET.SubElement(igroup, 'CustomBuild', Include=rulefile)
1564        if msg:
1565            message = ET.SubElement(custombuild, 'Message')
1566            message.text = msg
1567        if not verify_files:
1568            ET.SubElement(custombuild, 'VerifyInputsAndOutputsExist').text = 'false'
1569        cmd_templ = '''setlocal
1570%s
1571if %%errorlevel%% neq 0 goto :cmEnd
1572:cmEnd
1573endlocal & call :cmErrorLevel %%errorlevel%% & goto :cmDone
1574:cmErrorLevel
1575exit /b %%1
1576:cmDone
1577if %%errorlevel%% neq 0 goto :VCEnd'''
1578        ET.SubElement(custombuild, 'Command').text = cmd_templ % command
1579        if not outputs:
1580            # Use a nonexistent file to always consider the target out-of-date.
1581            outputs = [self.nonexistent_file(os.path.join(self.environment.get_scratch_dir(),
1582                                                          'outofdate.file'))]
1583        ET.SubElement(custombuild, 'Outputs').text = ';'.join(outputs)
1584        if deps:
1585            ET.SubElement(custombuild, 'AdditionalInputs').text = ';'.join(deps)
1586
1587    @staticmethod
1588    def nonexistent_file(prefix):
1589        i = 0
1590        file = prefix
1591        while os.path.exists(file):
1592            file = '%s%d' % (prefix, i)
1593        return file
1594
1595    def generate_debug_information(self, link):
1596        # valid values for vs2015 is 'false', 'true', 'DebugFastLink'
1597        ET.SubElement(link, 'GenerateDebugInformation').text = 'true'
1598
1599    def add_regen_dependency(self, root):
1600        regen_vcxproj = os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj')
1601        self.add_project_reference(root, regen_vcxproj, self.environment.coredata.regen_guid)
1602