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