1# Copyright (c) 2012 Google Inc. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5
6import ntpath
7import os
8import posixpath
9import re
10import subprocess
11import sys
12
13from collections import OrderedDict
14
15import gyp.common
16import gyp.easy_xml as easy_xml
17import gyp.generator.ninja as ninja_generator
18import gyp.MSVSNew as MSVSNew
19import gyp.MSVSProject as MSVSProject
20import gyp.MSVSSettings as MSVSSettings
21import gyp.MSVSToolFile as MSVSToolFile
22import gyp.MSVSUserFile as MSVSUserFile
23import gyp.MSVSUtil as MSVSUtil
24import gyp.MSVSVersion as MSVSVersion
25from gyp.common import GypError
26from gyp.common import OrderedSet
27
28
29# Regular expression for validating Visual Studio GUIDs.  If the GUID
30# contains lowercase hex letters, MSVS will be fine. However,
31# IncrediBuild BuildConsole will parse the solution file, but then
32# silently skip building the target causing hard to track down errors.
33# Note that this only happens with the BuildConsole, and does not occur
34# if IncrediBuild is executed from inside Visual Studio.  This regex
35# validates that the string looks like a GUID with all uppercase hex
36# letters.
37VALID_MSVS_GUID_CHARS = re.compile(r"^[A-F0-9\-]+$")
38
39generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested()
40
41generator_default_variables = {
42    "DRIVER_PREFIX": "",
43    "DRIVER_SUFFIX": ".sys",
44    "EXECUTABLE_PREFIX": "",
45    "EXECUTABLE_SUFFIX": ".exe",
46    "STATIC_LIB_PREFIX": "",
47    "SHARED_LIB_PREFIX": "",
48    "STATIC_LIB_SUFFIX": ".lib",
49    "SHARED_LIB_SUFFIX": ".dll",
50    "INTERMEDIATE_DIR": "$(IntDir)",
51    "SHARED_INTERMEDIATE_DIR": "$(OutDir)/obj/global_intermediate",
52    "OS": "win",
53    "PRODUCT_DIR": "$(OutDir)",
54    "LIB_DIR": "$(OutDir)lib",
55    "RULE_INPUT_ROOT": "$(InputName)",
56    "RULE_INPUT_DIRNAME": "$(InputDir)",
57    "RULE_INPUT_EXT": "$(InputExt)",
58    "RULE_INPUT_NAME": "$(InputFileName)",
59    "RULE_INPUT_PATH": "$(InputPath)",
60    "CONFIGURATION_NAME": "$(ConfigurationName)",
61}
62
63
64# The msvs specific sections that hold paths
65generator_additional_path_sections = [
66    "msvs_cygwin_dirs",
67    "msvs_props",
68]
69
70
71generator_additional_non_configuration_keys = [
72    "msvs_cygwin_dirs",
73    "msvs_cygwin_shell",
74    "msvs_large_pdb",
75    "msvs_shard",
76    "msvs_external_builder",
77    "msvs_external_builder_out_dir",
78    "msvs_external_builder_build_cmd",
79    "msvs_external_builder_clean_cmd",
80    "msvs_external_builder_clcompile_cmd",
81    "msvs_enable_winrt",
82    "msvs_requires_importlibrary",
83    "msvs_enable_winphone",
84    "msvs_application_type_revision",
85    "msvs_target_platform_version",
86    "msvs_target_platform_minversion",
87]
88
89generator_filelist_paths = None
90
91# List of precompiled header related keys.
92precomp_keys = [
93    "msvs_precompiled_header",
94    "msvs_precompiled_source",
95]
96
97
98cached_username = None
99
100
101cached_domain = None
102
103
104# TODO(gspencer): Switch the os.environ calls to be
105# win32api.GetDomainName() and win32api.GetUserName() once the
106# python version in depot_tools has been updated to work on Vista
107# 64-bit.
108def _GetDomainAndUserName():
109    if sys.platform not in ("win32", "cygwin"):
110        return ("DOMAIN", "USERNAME")
111    global cached_username
112    global cached_domain
113    if not cached_domain or not cached_username:
114        domain = os.environ.get("USERDOMAIN")
115        username = os.environ.get("USERNAME")
116        if not domain or not username:
117            call = subprocess.Popen(
118                ["net", "config", "Workstation"], stdout=subprocess.PIPE
119            )
120            config = call.communicate()[0].decode("utf-8")
121            username_re = re.compile(r"^User name\s+(\S+)", re.MULTILINE)
122            username_match = username_re.search(config)
123            if username_match:
124                username = username_match.group(1)
125            domain_re = re.compile(r"^Logon domain\s+(\S+)", re.MULTILINE)
126            domain_match = domain_re.search(config)
127            if domain_match:
128                domain = domain_match.group(1)
129        cached_domain = domain
130        cached_username = username
131    return (cached_domain, cached_username)
132
133
134fixpath_prefix = None
135
136
137def _NormalizedSource(source):
138    """Normalize the path.
139
140  But not if that gets rid of a variable, as this may expand to something
141  larger than one directory.
142
143  Arguments:
144      source: The path to be normalize.d
145
146  Returns:
147      The normalized path.
148  """
149    normalized = os.path.normpath(source)
150    if source.count("$") == normalized.count("$"):
151        source = normalized
152    return source
153
154
155def _FixPath(path, separator="\\"):
156    """Convert paths to a form that will make sense in a vcproj file.
157
158  Arguments:
159    path: The path to convert, may contain / etc.
160  Returns:
161    The path with all slashes made into backslashes.
162  """
163    if (
164        fixpath_prefix
165        and path
166        and not os.path.isabs(path)
167        and not path[0] == "$"
168        and not _IsWindowsAbsPath(path)
169    ):
170        path = os.path.join(fixpath_prefix, path)
171    if separator == "\\":
172        path = path.replace("/", "\\")
173    path = _NormalizedSource(path)
174    if separator == "/":
175        path = path.replace("\\", "/")
176    if path and path[-1] == separator:
177        path = path[:-1]
178    return path
179
180
181def _IsWindowsAbsPath(path):
182    """
183  On Cygwin systems Python needs a little help determining if a path
184  is an absolute Windows path or not, so that
185  it does not treat those as relative, which results in bad paths like:
186  '..\\C:\\<some path>\\some_source_code_file.cc'
187  """
188    return path.startswith("c:") or path.startswith("C:")
189
190
191def _FixPaths(paths, separator="\\"):
192    """Fix each of the paths of the list."""
193    return [_FixPath(i, separator) for i in paths]
194
195
196def _ConvertSourcesToFilterHierarchy(
197    sources, prefix=None, excluded=None, list_excluded=True, msvs_version=None
198):
199    """Converts a list split source file paths into a vcproj folder hierarchy.
200
201  Arguments:
202    sources: A list of source file paths split.
203    prefix: A list of source file path layers meant to apply to each of sources.
204    excluded: A set of excluded files.
205    msvs_version: A MSVSVersion object.
206
207  Returns:
208    A hierarchy of filenames and MSVSProject.Filter objects that matches the
209    layout of the source tree.
210    For example:
211    _ConvertSourcesToFilterHierarchy([['a', 'bob1.c'], ['b', 'bob2.c']],
212                                     prefix=['joe'])
213    -->
214    [MSVSProject.Filter('a', contents=['joe\\a\\bob1.c']),
215     MSVSProject.Filter('b', contents=['joe\\b\\bob2.c'])]
216  """
217    if not prefix:
218        prefix = []
219    result = []
220    excluded_result = []
221    folders = OrderedDict()
222    # Gather files into the final result, excluded, or folders.
223    for s in sources:
224        if len(s) == 1:
225            filename = _NormalizedSource("\\".join(prefix + s))
226            if filename in excluded:
227                excluded_result.append(filename)
228            else:
229                result.append(filename)
230        elif msvs_version and not msvs_version.UsesVcxproj():
231            # For MSVS 2008 and earlier, we need to process all files before walking
232            # the sub folders.
233            if not folders.get(s[0]):
234                folders[s[0]] = []
235            folders[s[0]].append(s[1:])
236        else:
237            contents = _ConvertSourcesToFilterHierarchy(
238                [s[1:]],
239                prefix + [s[0]],
240                excluded=excluded,
241                list_excluded=list_excluded,
242                msvs_version=msvs_version,
243            )
244            contents = MSVSProject.Filter(s[0], contents=contents)
245            result.append(contents)
246    # Add a folder for excluded files.
247    if excluded_result and list_excluded:
248        excluded_folder = MSVSProject.Filter(
249            "_excluded_files", contents=excluded_result
250        )
251        result.append(excluded_folder)
252
253    if msvs_version and msvs_version.UsesVcxproj():
254        return result
255
256    # Populate all the folders.
257    for f in folders:
258        contents = _ConvertSourcesToFilterHierarchy(
259            folders[f],
260            prefix=prefix + [f],
261            excluded=excluded,
262            list_excluded=list_excluded,
263            msvs_version=msvs_version,
264        )
265        contents = MSVSProject.Filter(f, contents=contents)
266        result.append(contents)
267    return result
268
269
270def _ToolAppend(tools, tool_name, setting, value, only_if_unset=False):
271    if not value:
272        return
273    _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset)
274
275
276def _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset=False):
277    # TODO(bradnelson): ugly hack, fix this more generally!!!
278    if "Directories" in setting or "Dependencies" in setting:
279        if type(value) == str:
280            value = value.replace("/", "\\")
281        else:
282            value = [i.replace("/", "\\") for i in value]
283    if not tools.get(tool_name):
284        tools[tool_name] = dict()
285    tool = tools[tool_name]
286    if "CompileAsWinRT" == setting:
287        return
288    if tool.get(setting):
289        if only_if_unset:
290            return
291        if type(tool[setting]) == list and type(value) == list:
292            tool[setting] += value
293        else:
294            raise TypeError(
295                'Appending "%s" to a non-list setting "%s" for tool "%s" is '
296                "not allowed, previous value: %s"
297                % (value, setting, tool_name, str(tool[setting]))
298            )
299    else:
300        tool[setting] = value
301
302
303def _ConfigTargetVersion(config_data):
304    return config_data.get("msvs_target_version", "Windows7")
305
306
307def _ConfigPlatform(config_data):
308    return config_data.get("msvs_configuration_platform", "Win32")
309
310
311def _ConfigBaseName(config_name, platform_name):
312    if config_name.endswith("_" + platform_name):
313        return config_name[0 : -len(platform_name) - 1]
314    else:
315        return config_name
316
317
318def _ConfigFullName(config_name, config_data):
319    platform_name = _ConfigPlatform(config_data)
320    return f"{_ConfigBaseName(config_name, platform_name)}|{platform_name}"
321
322
323def _ConfigWindowsTargetPlatformVersion(config_data, version):
324    target_ver = config_data.get("msvs_windows_target_platform_version")
325    if target_ver and re.match(r"^\d+", target_ver):
326        return target_ver
327    config_ver = config_data.get("msvs_windows_sdk_version")
328    vers = [config_ver] if config_ver else version.compatible_sdks
329    for ver in vers:
330        for key in [
331            r"HKLM\Software\Microsoft\Microsoft SDKs\Windows\%s",
332            r"HKLM\Software\Wow6432Node\Microsoft\Microsoft SDKs\Windows\%s",
333        ]:
334            sdk_dir = MSVSVersion._RegistryGetValue(key % ver, "InstallationFolder")
335            if not sdk_dir:
336                continue
337            version = MSVSVersion._RegistryGetValue(key % ver, "ProductVersion") or ""
338            # Find a matching entry in sdk_dir\include.
339            expected_sdk_dir = r"%s\include" % sdk_dir
340            names = sorted(
341                (
342                    x
343                    for x in (
344                        os.listdir(expected_sdk_dir)
345                        if os.path.isdir(expected_sdk_dir)
346                        else []
347                    )
348                    if x.startswith(version)
349                ),
350                reverse=True,
351            )
352            if names:
353                return names[0]
354            else:
355                print(
356                    "Warning: No include files found for detected "
357                    "Windows SDK version %s" % (version),
358                    file=sys.stdout,
359                )
360
361
362def _BuildCommandLineForRuleRaw(
363    spec, cmd, cygwin_shell, has_input_path, quote_cmd, do_setup_env
364):
365
366    if [x for x in cmd if "$(InputDir)" in x]:
367        input_dir_preamble = (
368            "set INPUTDIR=$(InputDir)\n"
369            "if NOT DEFINED INPUTDIR set INPUTDIR=.\\\n"
370            "set INPUTDIR=%INPUTDIR:~0,-1%\n"
371        )
372    else:
373        input_dir_preamble = ""
374
375    if cygwin_shell:
376        # Find path to cygwin.
377        cygwin_dir = _FixPath(spec.get("msvs_cygwin_dirs", ["."])[0])
378        # Prepare command.
379        direct_cmd = cmd
380        direct_cmd = [
381            i.replace("$(IntDir)", '`cygpath -m "${INTDIR}"`') for i in direct_cmd
382        ]
383        direct_cmd = [
384            i.replace("$(OutDir)", '`cygpath -m "${OUTDIR}"`') for i in direct_cmd
385        ]
386        direct_cmd = [
387            i.replace("$(InputDir)", '`cygpath -m "${INPUTDIR}"`') for i in direct_cmd
388        ]
389        if has_input_path:
390            direct_cmd = [
391                i.replace("$(InputPath)", '`cygpath -m "${INPUTPATH}"`')
392                for i in direct_cmd
393            ]
394        direct_cmd = ['\\"%s\\"' % i.replace('"', '\\\\\\"') for i in direct_cmd]
395        # direct_cmd = gyp.common.EncodePOSIXShellList(direct_cmd)
396        direct_cmd = " ".join(direct_cmd)
397        # TODO(quote):  regularize quoting path names throughout the module
398        cmd = ""
399        if do_setup_env:
400            cmd += 'call "$(ProjectDir)%(cygwin_dir)s\\setup_env.bat" && '
401        cmd += "set CYGWIN=nontsec&& "
402        if direct_cmd.find("NUMBER_OF_PROCESSORS") >= 0:
403            cmd += "set /a NUMBER_OF_PROCESSORS_PLUS_1=%%NUMBER_OF_PROCESSORS%%+1&& "
404        if direct_cmd.find("INTDIR") >= 0:
405            cmd += "set INTDIR=$(IntDir)&& "
406        if direct_cmd.find("OUTDIR") >= 0:
407            cmd += "set OUTDIR=$(OutDir)&& "
408        if has_input_path and direct_cmd.find("INPUTPATH") >= 0:
409            cmd += "set INPUTPATH=$(InputPath) && "
410        cmd += 'bash -c "%(cmd)s"'
411        cmd = cmd % {"cygwin_dir": cygwin_dir, "cmd": direct_cmd}
412        return input_dir_preamble + cmd
413    else:
414        # Convert cat --> type to mimic unix.
415        if cmd[0] == "cat":
416            command = ["type"]
417        else:
418            command = [cmd[0].replace("/", "\\")]
419        # Add call before command to ensure that commands can be tied together one
420        # after the other without aborting in Incredibuild, since IB makes a bat
421        # file out of the raw command string, and some commands (like python) are
422        # actually batch files themselves.
423        command.insert(0, "call")
424        # Fix the paths
425        # TODO(quote): This is a really ugly heuristic, and will miss path fixing
426        #              for arguments like "--arg=path", arg=path, or "/opt:path".
427        # If the argument starts with a slash or dash, or contains an equal sign,
428        # it's probably a command line switch.
429        # Return the path with forward slashes because the command using it might
430        # not support backslashes.
431        arguments = [
432            i if (i[:1] in "/-" or "=" in i) else _FixPath(i, "/")
433            for i in cmd[1:]
434        ]
435        arguments = [i.replace("$(InputDir)", "%INPUTDIR%") for i in arguments]
436        arguments = [MSVSSettings.FixVCMacroSlashes(i) for i in arguments]
437        if quote_cmd:
438            # Support a mode for using cmd directly.
439            # Convert any paths to native form (first element is used directly).
440            # TODO(quote):  regularize quoting path names throughout the module
441            arguments = ['"%s"' % i for i in arguments]
442        # Collapse into a single command.
443        return input_dir_preamble + " ".join(command + arguments)
444
445
446def _BuildCommandLineForRule(spec, rule, has_input_path, do_setup_env):
447    # Currently this weird argument munging is used to duplicate the way a
448    # python script would need to be run as part of the chrome tree.
449    # Eventually we should add some sort of rule_default option to set this
450    # per project. For now the behavior chrome needs is the default.
451    mcs = rule.get("msvs_cygwin_shell")
452    if mcs is None:
453        mcs = int(spec.get("msvs_cygwin_shell", 1))
454    elif isinstance(mcs, str):
455        mcs = int(mcs)
456    quote_cmd = int(rule.get("msvs_quote_cmd", 1))
457    return _BuildCommandLineForRuleRaw(
458        spec, rule["action"], mcs, has_input_path, quote_cmd, do_setup_env=do_setup_env
459    )
460
461
462def _AddActionStep(actions_dict, inputs, outputs, description, command):
463    """Merge action into an existing list of actions.
464
465  Care must be taken so that actions which have overlapping inputs either don't
466  get assigned to the same input, or get collapsed into one.
467
468  Arguments:
469    actions_dict: dictionary keyed on input name, which maps to a list of
470      dicts describing the actions attached to that input file.
471    inputs: list of inputs
472    outputs: list of outputs
473    description: description of the action
474    command: command line to execute
475  """
476    # Require there to be at least one input (call sites will ensure this).
477    assert inputs
478
479    action = {
480        "inputs": inputs,
481        "outputs": outputs,
482        "description": description,
483        "command": command,
484    }
485
486    # Pick where to stick this action.
487    # While less than optimal in terms of build time, attach them to the first
488    # input for now.
489    chosen_input = inputs[0]
490
491    # Add it there.
492    if chosen_input not in actions_dict:
493        actions_dict[chosen_input] = []
494    actions_dict[chosen_input].append(action)
495
496
497def _AddCustomBuildToolForMSVS(
498    p, spec, primary_input, inputs, outputs, description, cmd
499):
500    """Add a custom build tool to execute something.
501
502  Arguments:
503    p: the target project
504    spec: the target project dict
505    primary_input: input file to attach the build tool to
506    inputs: list of inputs
507    outputs: list of outputs
508    description: description of the action
509    cmd: command line to execute
510  """
511    inputs = _FixPaths(inputs)
512    outputs = _FixPaths(outputs)
513    tool = MSVSProject.Tool(
514        "VCCustomBuildTool",
515        {
516            "Description": description,
517            "AdditionalDependencies": ";".join(inputs),
518            "Outputs": ";".join(outputs),
519            "CommandLine": cmd,
520        },
521    )
522    # Add to the properties of primary input for each config.
523    for config_name, c_data in spec["configurations"].items():
524        p.AddFileConfig(
525            _FixPath(primary_input), _ConfigFullName(config_name, c_data), tools=[tool]
526        )
527
528
529def _AddAccumulatedActionsToMSVS(p, spec, actions_dict):
530    """Add actions accumulated into an actions_dict, merging as needed.
531
532  Arguments:
533    p: the target project
534    spec: the target project dict
535    actions_dict: dictionary keyed on input name, which maps to a list of
536        dicts describing the actions attached to that input file.
537  """
538    for primary_input in actions_dict:
539        inputs = OrderedSet()
540        outputs = OrderedSet()
541        descriptions = []
542        commands = []
543        for action in actions_dict[primary_input]:
544            inputs.update(OrderedSet(action["inputs"]))
545            outputs.update(OrderedSet(action["outputs"]))
546            descriptions.append(action["description"])
547            commands.append(action["command"])
548        # Add the custom build step for one input file.
549        description = ", and also ".join(descriptions)
550        command = "\r\n".join(commands)
551        _AddCustomBuildToolForMSVS(
552            p,
553            spec,
554            primary_input=primary_input,
555            inputs=inputs,
556            outputs=outputs,
557            description=description,
558            cmd=command,
559        )
560
561
562def _RuleExpandPath(path, input_file):
563    """Given the input file to which a rule applied, string substitute a path.
564
565  Arguments:
566    path: a path to string expand
567    input_file: the file to which the rule applied.
568  Returns:
569    The string substituted path.
570  """
571    path = path.replace(
572        "$(InputName)", os.path.splitext(os.path.split(input_file)[1])[0]
573    )
574    path = path.replace("$(InputDir)", os.path.dirname(input_file))
575    path = path.replace(
576        "$(InputExt)", os.path.splitext(os.path.split(input_file)[1])[1]
577    )
578    path = path.replace("$(InputFileName)", os.path.split(input_file)[1])
579    path = path.replace("$(InputPath)", input_file)
580    return path
581
582
583def _FindRuleTriggerFiles(rule, sources):
584    """Find the list of files which a particular rule applies to.
585
586  Arguments:
587    rule: the rule in question
588    sources: the set of all known source files for this project
589  Returns:
590    The list of sources that trigger a particular rule.
591  """
592    return rule.get("rule_sources", [])
593
594
595def _RuleInputsAndOutputs(rule, trigger_file):
596    """Find the inputs and outputs generated by a rule.
597
598  Arguments:
599    rule: the rule in question.
600    trigger_file: the main trigger for this rule.
601  Returns:
602    The pair of (inputs, outputs) involved in this rule.
603  """
604    raw_inputs = _FixPaths(rule.get("inputs", []))
605    raw_outputs = _FixPaths(rule.get("outputs", []))
606    inputs = OrderedSet()
607    outputs = OrderedSet()
608    inputs.add(trigger_file)
609    for i in raw_inputs:
610        inputs.add(_RuleExpandPath(i, trigger_file))
611    for o in raw_outputs:
612        outputs.add(_RuleExpandPath(o, trigger_file))
613    return (inputs, outputs)
614
615
616def _GenerateNativeRulesForMSVS(p, rules, output_dir, spec, options):
617    """Generate a native rules file.
618
619  Arguments:
620    p: the target project
621    rules: the set of rules to include
622    output_dir: the directory in which the project/gyp resides
623    spec: the project dict
624    options: global generator options
625  """
626    rules_filename = "{}{}.rules".format(spec["target_name"], options.suffix)
627    rules_file = MSVSToolFile.Writer(
628        os.path.join(output_dir, rules_filename), spec["target_name"]
629    )
630    # Add each rule.
631    for r in rules:
632        rule_name = r["rule_name"]
633        rule_ext = r["extension"]
634        inputs = _FixPaths(r.get("inputs", []))
635        outputs = _FixPaths(r.get("outputs", []))
636        # Skip a rule with no action and no inputs.
637        if "action" not in r and not r.get("rule_sources", []):
638            continue
639        cmd = _BuildCommandLineForRule(spec, r, has_input_path=True, do_setup_env=True)
640        rules_file.AddCustomBuildRule(
641            name=rule_name,
642            description=r.get("message", rule_name),
643            extensions=[rule_ext],
644            additional_dependencies=inputs,
645            outputs=outputs,
646            cmd=cmd,
647        )
648    # Write out rules file.
649    rules_file.WriteIfChanged()
650
651    # Add rules file to project.
652    p.AddToolFile(rules_filename)
653
654
655def _Cygwinify(path):
656    path = path.replace("$(OutDir)", "$(OutDirCygwin)")
657    path = path.replace("$(IntDir)", "$(IntDirCygwin)")
658    return path
659
660
661def _GenerateExternalRules(rules, output_dir, spec, sources, options, actions_to_add):
662    """Generate an external makefile to do a set of rules.
663
664  Arguments:
665    rules: the list of rules to include
666    output_dir: path containing project and gyp files
667    spec: project specification data
668    sources: set of sources known
669    options: global generator options
670    actions_to_add: The list of actions we will add to.
671  """
672    filename = "{}_rules{}.mk".format(spec["target_name"], options.suffix)
673    mk_file = gyp.common.WriteOnDiff(os.path.join(output_dir, filename))
674    # Find cygwin style versions of some paths.
675    mk_file.write('OutDirCygwin:=$(shell cygpath -u "$(OutDir)")\n')
676    mk_file.write('IntDirCygwin:=$(shell cygpath -u "$(IntDir)")\n')
677    # Gather stuff needed to emit all: target.
678    all_inputs = OrderedSet()
679    all_outputs = OrderedSet()
680    all_output_dirs = OrderedSet()
681    first_outputs = []
682    for rule in rules:
683        trigger_files = _FindRuleTriggerFiles(rule, sources)
684        for tf in trigger_files:
685            inputs, outputs = _RuleInputsAndOutputs(rule, tf)
686            all_inputs.update(OrderedSet(inputs))
687            all_outputs.update(OrderedSet(outputs))
688            # Only use one target from each rule as the dependency for
689            # 'all' so we don't try to build each rule multiple times.
690            first_outputs.append(list(outputs)[0])
691            # Get the unique output directories for this rule.
692            output_dirs = [os.path.split(i)[0] for i in outputs]
693            for od in output_dirs:
694                all_output_dirs.add(od)
695    first_outputs_cyg = [_Cygwinify(i) for i in first_outputs]
696    # Write out all: target, including mkdir for each output directory.
697    mk_file.write("all: %s\n" % " ".join(first_outputs_cyg))
698    for od in all_output_dirs:
699        if od:
700            mk_file.write('\tmkdir -p `cygpath -u "%s"`\n' % od)
701    mk_file.write("\n")
702    # Define how each output is generated.
703    for rule in rules:
704        trigger_files = _FindRuleTriggerFiles(rule, sources)
705        for tf in trigger_files:
706            # Get all the inputs and outputs for this rule for this trigger file.
707            inputs, outputs = _RuleInputsAndOutputs(rule, tf)
708            inputs = [_Cygwinify(i) for i in inputs]
709            outputs = [_Cygwinify(i) for i in outputs]
710            # Prepare the command line for this rule.
711            cmd = [_RuleExpandPath(c, tf) for c in rule["action"]]
712            cmd = ['"%s"' % i for i in cmd]
713            cmd = " ".join(cmd)
714            # Add it to the makefile.
715            mk_file.write("{}: {}\n".format(" ".join(outputs), " ".join(inputs)))
716            mk_file.write("\t%s\n\n" % cmd)
717    # Close up the file.
718    mk_file.close()
719
720    # Add makefile to list of sources.
721    sources.add(filename)
722    # Add a build action to call makefile.
723    cmd = [
724        "make",
725        "OutDir=$(OutDir)",
726        "IntDir=$(IntDir)",
727        "-j",
728        "${NUMBER_OF_PROCESSORS_PLUS_1}",
729        "-f",
730        filename,
731    ]
732    cmd = _BuildCommandLineForRuleRaw(spec, cmd, True, False, True, True)
733    # Insert makefile as 0'th input, so it gets the action attached there,
734    # as this is easier to understand from in the IDE.
735    all_inputs = list(all_inputs)
736    all_inputs.insert(0, filename)
737    _AddActionStep(
738        actions_to_add,
739        inputs=_FixPaths(all_inputs),
740        outputs=_FixPaths(all_outputs),
741        description="Running external rules for %s" % spec["target_name"],
742        command=cmd,
743    )
744
745
746def _EscapeEnvironmentVariableExpansion(s):
747    """Escapes % characters.
748
749  Escapes any % characters so that Windows-style environment variable
750  expansions will leave them alone.
751  See http://connect.microsoft.com/VisualStudio/feedback/details/106127/cl-d-name-text-containing-percentage-characters-doesnt-compile
752  to understand why we have to do this.
753
754  Args:
755      s: The string to be escaped.
756
757  Returns:
758      The escaped string.
759  """  # noqa: E731,E123,E501
760    s = s.replace("%", "%%")
761    return s
762
763
764quote_replacer_regex = re.compile(r'(\\*)"')
765
766
767def _EscapeCommandLineArgumentForMSVS(s):
768    """Escapes a Windows command-line argument.
769
770  So that the Win32 CommandLineToArgv function will turn the escaped result back
771  into the original string.
772  See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
773  ("Parsing C++ Command-Line Arguments") to understand why we have to do
774  this.
775
776  Args:
777      s: the string to be escaped.
778  Returns:
779      the escaped string.
780  """
781
782    def _Replace(match):
783        # For a literal quote, CommandLineToArgv requires an odd number of
784        # backslashes preceding it, and it produces half as many literal backslashes
785        # (rounded down). So we need to produce 2n+1 backslashes.
786        return 2 * match.group(1) + '\\"'
787
788    # Escape all quotes so that they are interpreted literally.
789    s = quote_replacer_regex.sub(_Replace, s)
790    # Now add unescaped quotes so that any whitespace is interpreted literally.
791    s = '"' + s + '"'
792    return s
793
794
795delimiters_replacer_regex = re.compile(r"(\\*)([,;]+)")
796
797
798def _EscapeVCProjCommandLineArgListItem(s):
799    """Escapes command line arguments for MSVS.
800
801  The VCProj format stores string lists in a single string using commas and
802  semi-colons as separators, which must be quoted if they are to be
803  interpreted literally. However, command-line arguments may already have
804  quotes, and the VCProj parser is ignorant of the backslash escaping
805  convention used by CommandLineToArgv, so the command-line quotes and the
806  VCProj quotes may not be the same quotes. So to store a general
807  command-line argument in a VCProj list, we need to parse the existing
808  quoting according to VCProj's convention and quote any delimiters that are
809  not already quoted by that convention. The quotes that we add will also be
810  seen by CommandLineToArgv, so if backslashes precede them then we also have
811  to escape those backslashes according to the CommandLineToArgv
812  convention.
813
814  Args:
815      s: the string to be escaped.
816  Returns:
817      the escaped string.
818  """
819
820    def _Replace(match):
821        # For a non-literal quote, CommandLineToArgv requires an even number of
822        # backslashes preceding it, and it produces half as many literal
823        # backslashes. So we need to produce 2n backslashes.
824        return 2 * match.group(1) + '"' + match.group(2) + '"'
825
826    segments = s.split('"')
827    # The unquoted segments are at the even-numbered indices.
828    for i in range(0, len(segments), 2):
829        segments[i] = delimiters_replacer_regex.sub(_Replace, segments[i])
830    # Concatenate back into a single string
831    s = '"'.join(segments)
832    if len(segments) % 2 == 0:
833        # String ends while still quoted according to VCProj's convention. This
834        # means the delimiter and the next list item that follow this one in the
835        # .vcproj file will be misinterpreted as part of this item. There is nothing
836        # we can do about this. Adding an extra quote would correct the problem in
837        # the VCProj but cause the same problem on the final command-line. Moving
838        # the item to the end of the list does works, but that's only possible if
839        # there's only one such item. Let's just warn the user.
840        print(
841            "Warning: MSVS may misinterpret the odd number of " + "quotes in " + s,
842            file=sys.stderr,
843        )
844    return s
845
846
847def _EscapeCppDefineForMSVS(s):
848    """Escapes a CPP define so that it will reach the compiler unaltered."""
849    s = _EscapeEnvironmentVariableExpansion(s)
850    s = _EscapeCommandLineArgumentForMSVS(s)
851    s = _EscapeVCProjCommandLineArgListItem(s)
852    # cl.exe replaces literal # characters with = in preprocessor definitions for
853    # some reason. Octal-encode to work around that.
854    s = s.replace("#", "\\%03o" % ord("#"))
855    return s
856
857
858quote_replacer_regex2 = re.compile(r'(\\+)"')
859
860
861def _EscapeCommandLineArgumentForMSBuild(s):
862    """Escapes a Windows command-line argument for use by MSBuild."""
863
864    def _Replace(match):
865        return (len(match.group(1)) / 2 * 4) * "\\" + '\\"'
866
867    # Escape all quotes so that they are interpreted literally.
868    s = quote_replacer_regex2.sub(_Replace, s)
869    return s
870
871
872def _EscapeMSBuildSpecialCharacters(s):
873    escape_dictionary = {
874        "%": "%25",
875        "$": "%24",
876        "@": "%40",
877        "'": "%27",
878        ";": "%3B",
879        "?": "%3F",
880        "*": "%2A",
881    }
882    result = "".join([escape_dictionary.get(c, c) for c in s])
883    return result
884
885
886def _EscapeCppDefineForMSBuild(s):
887    """Escapes a CPP define so that it will reach the compiler unaltered."""
888    s = _EscapeEnvironmentVariableExpansion(s)
889    s = _EscapeCommandLineArgumentForMSBuild(s)
890    s = _EscapeMSBuildSpecialCharacters(s)
891    # cl.exe replaces literal # characters with = in preprocessor definitions for
892    # some reason. Octal-encode to work around that.
893    s = s.replace("#", "\\%03o" % ord("#"))
894    return s
895
896
897def _GenerateRulesForMSVS(
898    p, output_dir, options, spec, sources, excluded_sources, actions_to_add
899):
900    """Generate all the rules for a particular project.
901
902  Arguments:
903    p: the project
904    output_dir: directory to emit rules to
905    options: global options passed to the generator
906    spec: the specification for this project
907    sources: the set of all known source files in this project
908    excluded_sources: the set of sources excluded from normal processing
909    actions_to_add: deferred list of actions to add in
910  """
911    rules = spec.get("rules", [])
912    rules_native = [r for r in rules if not int(r.get("msvs_external_rule", 0))]
913    rules_external = [r for r in rules if int(r.get("msvs_external_rule", 0))]
914
915    # Handle rules that use a native rules file.
916    if rules_native:
917        _GenerateNativeRulesForMSVS(p, rules_native, output_dir, spec, options)
918
919    # Handle external rules (non-native rules).
920    if rules_external:
921        _GenerateExternalRules(
922            rules_external, output_dir, spec, sources, options, actions_to_add
923        )
924    _AdjustSourcesForRules(rules, sources, excluded_sources, False)
925
926
927def _AdjustSourcesForRules(rules, sources, excluded_sources, is_msbuild):
928    # Add outputs generated by each rule (if applicable).
929    for rule in rules:
930        # Add in the outputs from this rule.
931        trigger_files = _FindRuleTriggerFiles(rule, sources)
932        for trigger_file in trigger_files:
933            # Remove trigger_file from excluded_sources to let the rule be triggered
934            # (e.g. rule trigger ax_enums.idl is added to excluded_sources
935            # because it's also in an action's inputs in the same project)
936            excluded_sources.discard(_FixPath(trigger_file))
937            # Done if not processing outputs as sources.
938            if int(rule.get("process_outputs_as_sources", False)):
939                inputs, outputs = _RuleInputsAndOutputs(rule, trigger_file)
940                inputs = OrderedSet(_FixPaths(inputs))
941                outputs = OrderedSet(_FixPaths(outputs))
942                inputs.remove(_FixPath(trigger_file))
943                sources.update(inputs)
944                if not is_msbuild:
945                    excluded_sources.update(inputs)
946                sources.update(outputs)
947
948
949def _FilterActionsFromExcluded(excluded_sources, actions_to_add):
950    """Take inputs with actions attached out of the list of exclusions.
951
952  Arguments:
953    excluded_sources: list of source files not to be built.
954    actions_to_add: dict of actions keyed on source file they're attached to.
955  Returns:
956    excluded_sources with files that have actions attached removed.
957  """
958    must_keep = OrderedSet(_FixPaths(actions_to_add.keys()))
959    return [s for s in excluded_sources if s not in must_keep]
960
961
962def _GetDefaultConfiguration(spec):
963    return spec["configurations"][spec["default_configuration"]]
964
965
966def _GetGuidOfProject(proj_path, spec):
967    """Get the guid for the project.
968
969  Arguments:
970    proj_path: Path of the vcproj or vcxproj file to generate.
971    spec: The target dictionary containing the properties of the target.
972  Returns:
973    the guid.
974  Raises:
975    ValueError: if the specified GUID is invalid.
976  """
977    # Pluck out the default configuration.
978    default_config = _GetDefaultConfiguration(spec)
979    # Decide the guid of the project.
980    guid = default_config.get("msvs_guid")
981    if guid:
982        if VALID_MSVS_GUID_CHARS.match(guid) is None:
983            raise ValueError(
984                'Invalid MSVS guid: "%s".  Must match regex: "%s".'
985                % (guid, VALID_MSVS_GUID_CHARS.pattern)
986            )
987        guid = "{%s}" % guid
988    guid = guid or MSVSNew.MakeGuid(proj_path)
989    return guid
990
991
992def _GetMsbuildToolsetOfProject(proj_path, spec, version):
993    """Get the platform toolset for the project.
994
995  Arguments:
996    proj_path: Path of the vcproj or vcxproj file to generate.
997    spec: The target dictionary containing the properties of the target.
998    version: The MSVSVersion object.
999  Returns:
1000    the platform toolset string or None.
1001  """
1002    # Pluck out the default configuration.
1003    default_config = _GetDefaultConfiguration(spec)
1004    toolset = default_config.get("msbuild_toolset")
1005    if not toolset and version.DefaultToolset():
1006        toolset = version.DefaultToolset()
1007    if spec["type"] == "windows_driver":
1008        toolset = "WindowsKernelModeDriver10.0"
1009    return toolset
1010
1011
1012def _GenerateProject(project, options, version, generator_flags, spec):
1013    """Generates a vcproj file.
1014
1015  Arguments:
1016    project: the MSVSProject object.
1017    options: global generator options.
1018    version: the MSVSVersion object.
1019    generator_flags: dict of generator-specific flags.
1020  Returns:
1021    A list of source files that cannot be found on disk.
1022  """
1023    default_config = _GetDefaultConfiguration(project.spec)
1024
1025    # Skip emitting anything if told to with msvs_existing_vcproj option.
1026    if default_config.get("msvs_existing_vcproj"):
1027        return []
1028
1029    if version.UsesVcxproj():
1030        return _GenerateMSBuildProject(project, options, version, generator_flags, spec)
1031    else:
1032        return _GenerateMSVSProject(project, options, version, generator_flags)
1033
1034
1035def _GenerateMSVSProject(project, options, version, generator_flags):
1036    """Generates a .vcproj file.  It may create .rules and .user files too.
1037
1038  Arguments:
1039    project: The project object we will generate the file for.
1040    options: Global options passed to the generator.
1041    version: The VisualStudioVersion object.
1042    generator_flags: dict of generator-specific flags.
1043  """
1044    spec = project.spec
1045    gyp.common.EnsureDirExists(project.path)
1046
1047    platforms = _GetUniquePlatforms(spec)
1048    p = MSVSProject.Writer(
1049        project.path, version, spec["target_name"], project.guid, platforms
1050    )
1051
1052    # Get directory project file is in.
1053    project_dir = os.path.split(project.path)[0]
1054    gyp_path = _NormalizedSource(project.build_file)
1055    relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
1056
1057    config_type = _GetMSVSConfigurationType(spec, project.build_file)
1058    for config_name, config in spec["configurations"].items():
1059        _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config)
1060
1061    # Prepare list of sources and excluded sources.
1062    gyp_file = os.path.split(project.build_file)[1]
1063    sources, excluded_sources = _PrepareListOfSources(spec, generator_flags, gyp_file)
1064
1065    # Add rules.
1066    actions_to_add = {}
1067    _GenerateRulesForMSVS(
1068        p, project_dir, options, spec, sources, excluded_sources, actions_to_add
1069    )
1070    list_excluded = generator_flags.get("msvs_list_excluded_files", True)
1071    sources, excluded_sources, excluded_idl = _AdjustSourcesAndConvertToFilterHierarchy(
1072        spec, options, project_dir, sources, excluded_sources, list_excluded, version
1073    )
1074
1075    # Add in files.
1076    missing_sources = _VerifySourcesExist(sources, project_dir)
1077    p.AddFiles(sources)
1078
1079    _AddToolFilesToMSVS(p, spec)
1080    _HandlePreCompiledHeaders(p, sources, spec)
1081    _AddActions(actions_to_add, spec, relative_path_of_gyp_file)
1082    _AddCopies(actions_to_add, spec)
1083    _WriteMSVSUserFile(project.path, version, spec)
1084
1085    # NOTE: this stanza must appear after all actions have been decided.
1086    # Don't excluded sources with actions attached, or they won't run.
1087    excluded_sources = _FilterActionsFromExcluded(excluded_sources, actions_to_add)
1088    _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl, list_excluded)
1089    _AddAccumulatedActionsToMSVS(p, spec, actions_to_add)
1090
1091    # Write it out.
1092    p.WriteIfChanged()
1093
1094    return missing_sources
1095
1096
1097def _GetUniquePlatforms(spec):
1098    """Returns the list of unique platforms for this spec, e.g ['win32', ...].
1099
1100  Arguments:
1101    spec: The target dictionary containing the properties of the target.
1102  Returns:
1103    The MSVSUserFile object created.
1104  """
1105    # Gather list of unique platforms.
1106    platforms = OrderedSet()
1107    for configuration in spec["configurations"]:
1108        platforms.add(_ConfigPlatform(spec["configurations"][configuration]))
1109    platforms = list(platforms)
1110    return platforms
1111
1112
1113def _CreateMSVSUserFile(proj_path, version, spec):
1114    """Generates a .user file for the user running this Gyp program.
1115
1116  Arguments:
1117    proj_path: The path of the project file being created.  The .user file
1118               shares the same path (with an appropriate suffix).
1119    version: The VisualStudioVersion object.
1120    spec: The target dictionary containing the properties of the target.
1121  Returns:
1122    The MSVSUserFile object created.
1123  """
1124    (domain, username) = _GetDomainAndUserName()
1125    vcuser_filename = ".".join([proj_path, domain, username, "user"])
1126    user_file = MSVSUserFile.Writer(vcuser_filename, version, spec["target_name"])
1127    return user_file
1128
1129
1130def _GetMSVSConfigurationType(spec, build_file):
1131    """Returns the configuration type for this project.
1132
1133  It's a number defined by Microsoft.  May raise an exception.
1134
1135  Args:
1136      spec: The target dictionary containing the properties of the target.
1137      build_file: The path of the gyp file.
1138  Returns:
1139      An integer, the configuration type.
1140  """
1141    try:
1142        config_type = {
1143            "executable": "1",  # .exe
1144            "shared_library": "2",  # .dll
1145            "loadable_module": "2",  # .dll
1146            "static_library": "4",  # .lib
1147            "windows_driver": "5",  # .sys
1148            "none": "10",  # Utility type
1149        }[spec["type"]]
1150    except KeyError:
1151        if spec.get("type"):
1152            raise GypError(
1153                "Target type %s is not a valid target type for "
1154                "target %s in %s." % (spec["type"], spec["target_name"], build_file)
1155            )
1156        else:
1157            raise GypError(
1158                "Missing type field for target %s in %s."
1159                % (spec["target_name"], build_file)
1160            )
1161    return config_type
1162
1163
1164def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config):
1165    """Adds a configuration to the MSVS project.
1166
1167  Many settings in a vcproj file are specific to a configuration.  This
1168  function the main part of the vcproj file that's configuration specific.
1169
1170  Arguments:
1171    p: The target project being generated.
1172    spec: The target dictionary containing the properties of the target.
1173    config_type: The configuration type, a number as defined by Microsoft.
1174    config_name: The name of the configuration.
1175    config: The dictionary that defines the special processing to be done
1176            for this configuration.
1177  """
1178    # Get the information for this configuration
1179    include_dirs, midl_include_dirs, resource_include_dirs = _GetIncludeDirs(config)
1180    libraries = _GetLibraries(spec)
1181    library_dirs = _GetLibraryDirs(config)
1182    out_file, vc_tool, _ = _GetOutputFilePathAndTool(spec, msbuild=False)
1183    defines = _GetDefines(config)
1184    defines = [_EscapeCppDefineForMSVS(d) for d in defines]
1185    disabled_warnings = _GetDisabledWarnings(config)
1186    prebuild = config.get("msvs_prebuild")
1187    postbuild = config.get("msvs_postbuild")
1188    def_file = _GetModuleDefinition(spec)
1189    precompiled_header = config.get("msvs_precompiled_header")
1190
1191    # Prepare the list of tools as a dictionary.
1192    tools = dict()
1193    # Add in user specified msvs_settings.
1194    msvs_settings = config.get("msvs_settings", {})
1195    MSVSSettings.ValidateMSVSSettings(msvs_settings)
1196
1197    # Prevent default library inheritance from the environment.
1198    _ToolAppend(tools, "VCLinkerTool", "AdditionalDependencies", ["$(NOINHERIT)"])
1199
1200    for tool in msvs_settings:
1201        settings = config["msvs_settings"][tool]
1202        for setting in settings:
1203            _ToolAppend(tools, tool, setting, settings[setting])
1204    # Add the information to the appropriate tool
1205    _ToolAppend(tools, "VCCLCompilerTool", "AdditionalIncludeDirectories", include_dirs)
1206    _ToolAppend(tools, "VCMIDLTool", "AdditionalIncludeDirectories", midl_include_dirs)
1207    _ToolAppend(
1208        tools,
1209        "VCResourceCompilerTool",
1210        "AdditionalIncludeDirectories",
1211        resource_include_dirs,
1212    )
1213    # Add in libraries.
1214    _ToolAppend(tools, "VCLinkerTool", "AdditionalDependencies", libraries)
1215    _ToolAppend(tools, "VCLinkerTool", "AdditionalLibraryDirectories", library_dirs)
1216    if out_file:
1217        _ToolAppend(tools, vc_tool, "OutputFile", out_file, only_if_unset=True)
1218    # Add defines.
1219    _ToolAppend(tools, "VCCLCompilerTool", "PreprocessorDefinitions", defines)
1220    _ToolAppend(tools, "VCResourceCompilerTool", "PreprocessorDefinitions", defines)
1221    # Change program database directory to prevent collisions.
1222    _ToolAppend(
1223        tools,
1224        "VCCLCompilerTool",
1225        "ProgramDataBaseFileName",
1226        "$(IntDir)$(ProjectName)\\vc80.pdb",
1227        only_if_unset=True,
1228    )
1229    # Add disabled warnings.
1230    _ToolAppend(tools, "VCCLCompilerTool", "DisableSpecificWarnings", disabled_warnings)
1231    # Add Pre-build.
1232    _ToolAppend(tools, "VCPreBuildEventTool", "CommandLine", prebuild)
1233    # Add Post-build.
1234    _ToolAppend(tools, "VCPostBuildEventTool", "CommandLine", postbuild)
1235    # Turn on precompiled headers if appropriate.
1236    if precompiled_header:
1237        precompiled_header = os.path.split(precompiled_header)[1]
1238        _ToolAppend(tools, "VCCLCompilerTool", "UsePrecompiledHeader", "2")
1239        _ToolAppend(
1240            tools, "VCCLCompilerTool", "PrecompiledHeaderThrough", precompiled_header
1241        )
1242        _ToolAppend(tools, "VCCLCompilerTool", "ForcedIncludeFiles", precompiled_header)
1243    # Loadable modules don't generate import libraries;
1244    # tell dependent projects to not expect one.
1245    if spec["type"] == "loadable_module":
1246        _ToolAppend(tools, "VCLinkerTool", "IgnoreImportLibrary", "true")
1247    # Set the module definition file if any.
1248    if def_file:
1249        _ToolAppend(tools, "VCLinkerTool", "ModuleDefinitionFile", def_file)
1250
1251    _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name)
1252
1253
1254def _GetIncludeDirs(config):
1255    """Returns the list of directories to be used for #include directives.
1256
1257  Arguments:
1258    config: The dictionary that defines the special processing to be done
1259            for this configuration.
1260  Returns:
1261    The list of directory paths.
1262  """
1263    # TODO(bradnelson): include_dirs should really be flexible enough not to
1264    #                   require this sort of thing.
1265    include_dirs = config.get("include_dirs", []) + config.get(
1266        "msvs_system_include_dirs", []
1267    )
1268    midl_include_dirs = config.get("midl_include_dirs", []) + config.get(
1269        "msvs_system_include_dirs", []
1270    )
1271    resource_include_dirs = config.get("resource_include_dirs", include_dirs)
1272    include_dirs = _FixPaths(include_dirs)
1273    midl_include_dirs = _FixPaths(midl_include_dirs)
1274    resource_include_dirs = _FixPaths(resource_include_dirs)
1275    return include_dirs, midl_include_dirs, resource_include_dirs
1276
1277
1278def _GetLibraryDirs(config):
1279    """Returns the list of directories to be used for library search paths.
1280
1281  Arguments:
1282    config: The dictionary that defines the special processing to be done
1283            for this configuration.
1284  Returns:
1285    The list of directory paths.
1286  """
1287
1288    library_dirs = config.get("library_dirs", [])
1289    library_dirs = _FixPaths(library_dirs)
1290    return library_dirs
1291
1292
1293def _GetLibraries(spec):
1294    """Returns the list of libraries for this configuration.
1295
1296  Arguments:
1297    spec: The target dictionary containing the properties of the target.
1298  Returns:
1299    The list of directory paths.
1300  """
1301    libraries = spec.get("libraries", [])
1302    # Strip out -l, as it is not used on windows (but is needed so we can pass
1303    # in libraries that are assumed to be in the default library path).
1304    # Also remove duplicate entries, leaving only the last duplicate, while
1305    # preserving order.
1306    found = OrderedSet()
1307    unique_libraries_list = []
1308    for entry in reversed(libraries):
1309        library = re.sub(r"^\-l", "", entry)
1310        if not os.path.splitext(library)[1]:
1311            library += ".lib"
1312        if library not in found:
1313            found.add(library)
1314            unique_libraries_list.append(library)
1315    unique_libraries_list.reverse()
1316    return unique_libraries_list
1317
1318
1319def _GetOutputFilePathAndTool(spec, msbuild):
1320    """Returns the path and tool to use for this target.
1321
1322  Figures out the path of the file this spec will create and the name of
1323  the VC tool that will create it.
1324
1325  Arguments:
1326    spec: The target dictionary containing the properties of the target.
1327  Returns:
1328    A triple of (file path, name of the vc tool, name of the msbuild tool)
1329  """
1330    # Select a name for the output file.
1331    out_file = ""
1332    vc_tool = ""
1333    msbuild_tool = ""
1334    output_file_map = {
1335        "executable": ("VCLinkerTool", "Link", "$(OutDir)", ".exe"),
1336        "shared_library": ("VCLinkerTool", "Link", "$(OutDir)", ".dll"),
1337        "loadable_module": ("VCLinkerTool", "Link", "$(OutDir)", ".dll"),
1338        "windows_driver": ("VCLinkerTool", "Link", "$(OutDir)", ".sys"),
1339        "static_library": ("VCLibrarianTool", "Lib", "$(OutDir)lib\\", ".lib"),
1340    }
1341    output_file_props = output_file_map.get(spec["type"])
1342    if output_file_props and int(spec.get("msvs_auto_output_file", 1)):
1343        vc_tool, msbuild_tool, out_dir, suffix = output_file_props
1344        if spec.get("standalone_static_library", 0):
1345            out_dir = "$(OutDir)"
1346        out_dir = spec.get("product_dir", out_dir)
1347        product_extension = spec.get("product_extension")
1348        if product_extension:
1349            suffix = "." + product_extension
1350        elif msbuild:
1351            suffix = "$(TargetExt)"
1352        prefix = spec.get("product_prefix", "")
1353        product_name = spec.get("product_name", "$(ProjectName)")
1354        out_file = ntpath.join(out_dir, prefix + product_name + suffix)
1355    return out_file, vc_tool, msbuild_tool
1356
1357
1358def _GetOutputTargetExt(spec):
1359    """Returns the extension for this target, including the dot
1360
1361  If product_extension is specified, set target_extension to this to avoid
1362  MSB8012, returns None otherwise. Ignores any target_extension settings in
1363  the input files.
1364
1365  Arguments:
1366    spec: The target dictionary containing the properties of the target.
1367  Returns:
1368    A string with the extension, or None
1369  """
1370    target_extension = spec.get("product_extension")
1371    if target_extension:
1372        return "." + target_extension
1373    return None
1374
1375
1376def _GetDefines(config):
1377    """Returns the list of preprocessor definitions for this configuration.
1378
1379  Arguments:
1380    config: The dictionary that defines the special processing to be done
1381            for this configuration.
1382  Returns:
1383    The list of preprocessor definitions.
1384  """
1385    defines = []
1386    for d in config.get("defines", []):
1387        if type(d) == list:
1388            fd = "=".join([str(dpart) for dpart in d])
1389        else:
1390            fd = str(d)
1391        defines.append(fd)
1392    return defines
1393
1394
1395def _GetDisabledWarnings(config):
1396    return [str(i) for i in config.get("msvs_disabled_warnings", [])]
1397
1398
1399def _GetModuleDefinition(spec):
1400    def_file = ""
1401    if spec["type"] in [
1402        "shared_library",
1403        "loadable_module",
1404        "executable",
1405        "windows_driver",
1406    ]:
1407        def_files = [s for s in spec.get("sources", []) if s.endswith(".def")]
1408        if len(def_files) == 1:
1409            def_file = _FixPath(def_files[0])
1410        elif def_files:
1411            raise ValueError(
1412                "Multiple module definition files in one target, target %s lists "
1413                "multiple .def files: %s" % (spec["target_name"], " ".join(def_files))
1414            )
1415    return def_file
1416
1417
1418def _ConvertToolsToExpectedForm(tools):
1419    """Convert tools to a form expected by Visual Studio.
1420
1421  Arguments:
1422    tools: A dictionary of settings; the tool name is the key.
1423  Returns:
1424    A list of Tool objects.
1425  """
1426    tool_list = []
1427    for tool, settings in tools.items():
1428        # Collapse settings with lists.
1429        settings_fixed = {}
1430        for setting, value in settings.items():
1431            if type(value) == list:
1432                if (
1433                    tool == "VCLinkerTool" and setting == "AdditionalDependencies"
1434                ) or setting == "AdditionalOptions":
1435                    settings_fixed[setting] = " ".join(value)
1436                else:
1437                    settings_fixed[setting] = ";".join(value)
1438            else:
1439                settings_fixed[setting] = value
1440        # Add in this tool.
1441        tool_list.append(MSVSProject.Tool(tool, settings_fixed))
1442    return tool_list
1443
1444
1445def _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name):
1446    """Add to the project file the configuration specified by config.
1447
1448  Arguments:
1449    p: The target project being generated.
1450    spec: the target project dict.
1451    tools: A dictionary of settings; the tool name is the key.
1452    config: The dictionary that defines the special processing to be done
1453            for this configuration.
1454    config_type: The configuration type, a number as defined by Microsoft.
1455    config_name: The name of the configuration.
1456  """
1457    attributes = _GetMSVSAttributes(spec, config, config_type)
1458    # Add in this configuration.
1459    tool_list = _ConvertToolsToExpectedForm(tools)
1460    p.AddConfig(_ConfigFullName(config_name, config), attrs=attributes, tools=tool_list)
1461
1462
1463def _GetMSVSAttributes(spec, config, config_type):
1464    # Prepare configuration attributes.
1465    prepared_attrs = {}
1466    source_attrs = config.get("msvs_configuration_attributes", {})
1467    for a in source_attrs:
1468        prepared_attrs[a] = source_attrs[a]
1469    # Add props files.
1470    vsprops_dirs = config.get("msvs_props", [])
1471    vsprops_dirs = _FixPaths(vsprops_dirs)
1472    if vsprops_dirs:
1473        prepared_attrs["InheritedPropertySheets"] = ";".join(vsprops_dirs)
1474    # Set configuration type.
1475    prepared_attrs["ConfigurationType"] = config_type
1476    output_dir = prepared_attrs.get(
1477        "OutputDirectory", "$(SolutionDir)$(ConfigurationName)"
1478    )
1479    prepared_attrs["OutputDirectory"] = _FixPath(output_dir) + "\\"
1480    if "IntermediateDirectory" not in prepared_attrs:
1481        intermediate = "$(ConfigurationName)\\obj\\$(ProjectName)"
1482        prepared_attrs["IntermediateDirectory"] = _FixPath(intermediate) + "\\"
1483    else:
1484        intermediate = _FixPath(prepared_attrs["IntermediateDirectory"]) + "\\"
1485        intermediate = MSVSSettings.FixVCMacroSlashes(intermediate)
1486        prepared_attrs["IntermediateDirectory"] = intermediate
1487    return prepared_attrs
1488
1489
1490def _AddNormalizedSources(sources_set, sources_array):
1491    sources_set.update(_NormalizedSource(s) for s in sources_array)
1492
1493
1494def _PrepareListOfSources(spec, generator_flags, gyp_file):
1495    """Prepare list of sources and excluded sources.
1496
1497  Besides the sources specified directly in the spec, adds the gyp file so
1498  that a change to it will cause a re-compile. Also adds appropriate sources
1499  for actions and copies. Assumes later stage will un-exclude files which
1500  have custom build steps attached.
1501
1502  Arguments:
1503    spec: The target dictionary containing the properties of the target.
1504    gyp_file: The name of the gyp file.
1505  Returns:
1506    A pair of (list of sources, list of excluded sources).
1507    The sources will be relative to the gyp file.
1508  """
1509    sources = OrderedSet()
1510    _AddNormalizedSources(sources, spec.get("sources", []))
1511    excluded_sources = OrderedSet()
1512    # Add in the gyp file.
1513    if not generator_flags.get("standalone"):
1514        sources.add(gyp_file)
1515
1516    # Add in 'action' inputs and outputs.
1517    for a in spec.get("actions", []):
1518        inputs = a["inputs"]
1519        inputs = [_NormalizedSource(i) for i in inputs]
1520        # Add all inputs to sources and excluded sources.
1521        inputs = OrderedSet(inputs)
1522        sources.update(inputs)
1523        if not spec.get("msvs_external_builder"):
1524            excluded_sources.update(inputs)
1525        if int(a.get("process_outputs_as_sources", False)):
1526            _AddNormalizedSources(sources, a.get("outputs", []))
1527    # Add in 'copies' inputs and outputs.
1528    for cpy in spec.get("copies", []):
1529        _AddNormalizedSources(sources, cpy.get("files", []))
1530    return (sources, excluded_sources)
1531
1532
1533def _AdjustSourcesAndConvertToFilterHierarchy(
1534    spec, options, gyp_dir, sources, excluded_sources, list_excluded, version
1535):
1536    """Adjusts the list of sources and excluded sources.
1537
1538  Also converts the sets to lists.
1539
1540  Arguments:
1541    spec: The target dictionary containing the properties of the target.
1542    options: Global generator options.
1543    gyp_dir: The path to the gyp file being processed.
1544    sources: A set of sources to be included for this project.
1545    excluded_sources: A set of sources to be excluded for this project.
1546    version: A MSVSVersion object.
1547  Returns:
1548    A trio of (list of sources, list of excluded sources,
1549               path of excluded IDL file)
1550  """
1551    # Exclude excluded sources coming into the generator.
1552    excluded_sources.update(OrderedSet(spec.get("sources_excluded", [])))
1553    # Add excluded sources into sources for good measure.
1554    sources.update(excluded_sources)
1555    # Convert to proper windows form.
1556    # NOTE: sources goes from being a set to a list here.
1557    # NOTE: excluded_sources goes from being a set to a list here.
1558    sources = _FixPaths(sources)
1559    # Convert to proper windows form.
1560    excluded_sources = _FixPaths(excluded_sources)
1561
1562    excluded_idl = _IdlFilesHandledNonNatively(spec, sources)
1563
1564    precompiled_related = _GetPrecompileRelatedFiles(spec)
1565    # Find the excluded ones, minus the precompiled header related ones.
1566    fully_excluded = [i for i in excluded_sources if i not in precompiled_related]
1567
1568    # Convert to folders and the right slashes.
1569    sources = [i.split("\\") for i in sources]
1570    sources = _ConvertSourcesToFilterHierarchy(
1571        sources,
1572        excluded=fully_excluded,
1573        list_excluded=list_excluded,
1574        msvs_version=version,
1575    )
1576
1577    # Prune filters with a single child to flatten ugly directory structures
1578    # such as ../../src/modules/module1 etc.
1579    if version.UsesVcxproj():
1580        while (
1581            all([isinstance(s, MSVSProject.Filter) for s in sources])
1582            and len({s.name for s in sources}) == 1
1583        ):
1584            assert all([len(s.contents) == 1 for s in sources])
1585            sources = [s.contents[0] for s in sources]
1586    else:
1587        while len(sources) == 1 and isinstance(sources[0], MSVSProject.Filter):
1588            sources = sources[0].contents
1589
1590    return sources, excluded_sources, excluded_idl
1591
1592
1593def _IdlFilesHandledNonNatively(spec, sources):
1594    # If any non-native rules use 'idl' as an extension exclude idl files.
1595    # Gather a list here to use later.
1596    using_idl = False
1597    for rule in spec.get("rules", []):
1598        if rule["extension"] == "idl" and int(rule.get("msvs_external_rule", 0)):
1599            using_idl = True
1600            break
1601    if using_idl:
1602        excluded_idl = [i for i in sources if i.endswith(".idl")]
1603    else:
1604        excluded_idl = []
1605    return excluded_idl
1606
1607
1608def _GetPrecompileRelatedFiles(spec):
1609    # Gather a list of precompiled header related sources.
1610    precompiled_related = []
1611    for _, config in spec["configurations"].items():
1612        for k in precomp_keys:
1613            f = config.get(k)
1614            if f:
1615                precompiled_related.append(_FixPath(f))
1616    return precompiled_related
1617
1618
1619def _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl, list_excluded):
1620    exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
1621    for file_name, excluded_configs in exclusions.items():
1622        if not list_excluded and len(excluded_configs) == len(spec["configurations"]):
1623            # If we're not listing excluded files, then they won't appear in the
1624            # project, so don't try to configure them to be excluded.
1625            pass
1626        else:
1627            for config_name, config in excluded_configs:
1628                p.AddFileConfig(
1629                    file_name,
1630                    _ConfigFullName(config_name, config),
1631                    {"ExcludedFromBuild": "true"},
1632                )
1633
1634
1635def _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl):
1636    exclusions = {}
1637    # Exclude excluded sources from being built.
1638    for f in excluded_sources:
1639        excluded_configs = []
1640        for config_name, config in spec["configurations"].items():
1641            precomped = [_FixPath(config.get(i, "")) for i in precomp_keys]
1642            # Don't do this for ones that are precompiled header related.
1643            if f not in precomped:
1644                excluded_configs.append((config_name, config))
1645        exclusions[f] = excluded_configs
1646    # If any non-native rules use 'idl' as an extension exclude idl files.
1647    # Exclude them now.
1648    for f in excluded_idl:
1649        excluded_configs = []
1650        for config_name, config in spec["configurations"].items():
1651            excluded_configs.append((config_name, config))
1652        exclusions[f] = excluded_configs
1653    return exclusions
1654
1655
1656def _AddToolFilesToMSVS(p, spec):
1657    # Add in tool files (rules).
1658    tool_files = OrderedSet()
1659    for _, config in spec["configurations"].items():
1660        for f in config.get("msvs_tool_files", []):
1661            tool_files.add(f)
1662    for f in tool_files:
1663        p.AddToolFile(f)
1664
1665
1666def _HandlePreCompiledHeaders(p, sources, spec):
1667    # Pre-compiled header source stubs need a different compiler flag
1668    # (generate precompiled header) and any source file not of the same
1669    # kind (i.e. C vs. C++) as the precompiled header source stub needs
1670    # to have use of precompiled headers disabled.
1671    extensions_excluded_from_precompile = []
1672    for config_name, config in spec["configurations"].items():
1673        source = config.get("msvs_precompiled_source")
1674        if source:
1675            source = _FixPath(source)
1676            # UsePrecompiledHeader=1 for if using precompiled headers.
1677            tool = MSVSProject.Tool("VCCLCompilerTool", {"UsePrecompiledHeader": "1"})
1678            p.AddFileConfig(
1679                source, _ConfigFullName(config_name, config), {}, tools=[tool]
1680            )
1681            basename, extension = os.path.splitext(source)
1682            if extension == ".c":
1683                extensions_excluded_from_precompile = [".cc", ".cpp", ".cxx"]
1684            else:
1685                extensions_excluded_from_precompile = [".c"]
1686
1687    def DisableForSourceTree(source_tree):
1688        for source in source_tree:
1689            if isinstance(source, MSVSProject.Filter):
1690                DisableForSourceTree(source.contents)
1691            else:
1692                basename, extension = os.path.splitext(source)
1693                if extension in extensions_excluded_from_precompile:
1694                    for config_name, config in spec["configurations"].items():
1695                        tool = MSVSProject.Tool(
1696                            "VCCLCompilerTool",
1697                            {
1698                                "UsePrecompiledHeader": "0",
1699                                "ForcedIncludeFiles": "$(NOINHERIT)",
1700                            },
1701                        )
1702                        p.AddFileConfig(
1703                            _FixPath(source),
1704                            _ConfigFullName(config_name, config),
1705                            {},
1706                            tools=[tool],
1707                        )
1708
1709    # Do nothing if there was no precompiled source.
1710    if extensions_excluded_from_precompile:
1711        DisableForSourceTree(sources)
1712
1713
1714def _AddActions(actions_to_add, spec, relative_path_of_gyp_file):
1715    # Add actions.
1716    actions = spec.get("actions", [])
1717    # Don't setup_env every time. When all the actions are run together in one
1718    # batch file in VS, the PATH will grow too long.
1719    # Membership in this set means that the cygwin environment has been set up,
1720    # and does not need to be set up again.
1721    have_setup_env = set()
1722    for a in actions:
1723        # Attach actions to the gyp file if nothing else is there.
1724        inputs = a.get("inputs") or [relative_path_of_gyp_file]
1725        attached_to = inputs[0]
1726        need_setup_env = attached_to not in have_setup_env
1727        cmd = _BuildCommandLineForRule(
1728            spec, a, has_input_path=False, do_setup_env=need_setup_env
1729        )
1730        have_setup_env.add(attached_to)
1731        # Add the action.
1732        _AddActionStep(
1733            actions_to_add,
1734            inputs=inputs,
1735            outputs=a.get("outputs", []),
1736            description=a.get("message", a["action_name"]),
1737            command=cmd,
1738        )
1739
1740
1741def _WriteMSVSUserFile(project_path, version, spec):
1742    # Add run_as and test targets.
1743    if "run_as" in spec:
1744        run_as = spec["run_as"]
1745        action = run_as.get("action", [])
1746        environment = run_as.get("environment", [])
1747        working_directory = run_as.get("working_directory", ".")
1748    elif int(spec.get("test", 0)):
1749        action = ["$(TargetPath)", "--gtest_print_time"]
1750        environment = []
1751        working_directory = "."
1752    else:
1753        return  # Nothing to add
1754    # Write out the user file.
1755    user_file = _CreateMSVSUserFile(project_path, version, spec)
1756    for config_name, c_data in spec["configurations"].items():
1757        user_file.AddDebugSettings(
1758            _ConfigFullName(config_name, c_data), action, environment, working_directory
1759        )
1760    user_file.WriteIfChanged()
1761
1762
1763def _AddCopies(actions_to_add, spec):
1764    copies = _GetCopies(spec)
1765    for inputs, outputs, cmd, description in copies:
1766        _AddActionStep(
1767            actions_to_add,
1768            inputs=inputs,
1769            outputs=outputs,
1770            description=description,
1771            command=cmd,
1772        )
1773
1774
1775def _GetCopies(spec):
1776    copies = []
1777    # Add copies.
1778    for cpy in spec.get("copies", []):
1779        for src in cpy.get("files", []):
1780            dst = os.path.join(cpy["destination"], os.path.basename(src))
1781            # _AddCustomBuildToolForMSVS() will call _FixPath() on the inputs and
1782            # outputs, so do the same for our generated command line.
1783            if src.endswith("/"):
1784                src_bare = src[:-1]
1785                base_dir = posixpath.split(src_bare)[0]
1786                outer_dir = posixpath.split(src_bare)[1]
1787                fixed_dst = _FixPath(dst)
1788                full_dst = f'"{fixed_dst}\\{outer_dir}\\"'
1789                cmd = 'mkdir {} 2>nul & cd "{}" && xcopy /e /f /y "{}" {}'.format(
1790                    full_dst,
1791                    _FixPath(base_dir),
1792                    outer_dir,
1793                    full_dst,
1794                )
1795                copies.append(
1796                    (
1797                        [src],
1798                        ["dummy_copies", dst],
1799                        cmd,
1800                        f"Copying {src} to {fixed_dst}",
1801                    )
1802                )
1803            else:
1804                fix_dst = _FixPath(cpy["destination"])
1805                cmd = 'mkdir "{}" 2>nul & set ERRORLEVEL=0 & copy /Y "{}" "{}"'.format(
1806                    fix_dst,
1807                    _FixPath(src),
1808                    _FixPath(dst),
1809                )
1810                copies.append(([src], [dst], cmd, f"Copying {src} to {fix_dst}"))
1811    return copies
1812
1813
1814def _GetPathDict(root, path):
1815    # |path| will eventually be empty (in the recursive calls) if it was initially
1816    # relative; otherwise it will eventually end up as '\', 'D:\', etc.
1817    if not path or path.endswith(os.sep):
1818        return root
1819    parent, folder = os.path.split(path)
1820    parent_dict = _GetPathDict(root, parent)
1821    if folder not in parent_dict:
1822        parent_dict[folder] = dict()
1823    return parent_dict[folder]
1824
1825
1826def _DictsToFolders(base_path, bucket, flat):
1827    # Convert to folders recursively.
1828    children = []
1829    for folder, contents in bucket.items():
1830        if type(contents) == dict:
1831            folder_children = _DictsToFolders(
1832                os.path.join(base_path, folder), contents, flat
1833            )
1834            if flat:
1835                children += folder_children
1836            else:
1837                folder_children = MSVSNew.MSVSFolder(
1838                    os.path.join(base_path, folder),
1839                    name="(" + folder + ")",
1840                    entries=folder_children,
1841                )
1842                children.append(folder_children)
1843        else:
1844            children.append(contents)
1845    return children
1846
1847
1848def _CollapseSingles(parent, node):
1849    # Recursively explorer the tree of dicts looking for projects which are
1850    # the sole item in a folder which has the same name as the project. Bring
1851    # such projects up one level.
1852    if type(node) == dict and len(node) == 1 and next(iter(node)) == parent + ".vcproj":
1853        return node[next(iter(node))]
1854    if type(node) != dict:
1855        return node
1856    for child in node:
1857        node[child] = _CollapseSingles(child, node[child])
1858    return node
1859
1860
1861def _GatherSolutionFolders(sln_projects, project_objects, flat):
1862    root = {}
1863    # Convert into a tree of dicts on path.
1864    for p in sln_projects:
1865        gyp_file, target = gyp.common.ParseQualifiedTarget(p)[0:2]
1866        if p.endswith("#host"):
1867            target += "_host"
1868        gyp_dir = os.path.dirname(gyp_file)
1869        path_dict = _GetPathDict(root, gyp_dir)
1870        path_dict[target + ".vcproj"] = project_objects[p]
1871    # Walk down from the top until we hit a folder that has more than one entry.
1872    # In practice, this strips the top-level "src/" dir from the hierarchy in
1873    # the solution.
1874    while len(root) == 1 and type(root[next(iter(root))]) == dict:
1875        root = root[next(iter(root))]
1876    # Collapse singles.
1877    root = _CollapseSingles("", root)
1878    # Merge buckets until everything is a root entry.
1879    return _DictsToFolders("", root, flat)
1880
1881
1882def _GetPathOfProject(qualified_target, spec, options, msvs_version):
1883    default_config = _GetDefaultConfiguration(spec)
1884    proj_filename = default_config.get("msvs_existing_vcproj")
1885    if not proj_filename:
1886        proj_filename = spec["target_name"]
1887        if spec["toolset"] == "host":
1888            proj_filename += "_host"
1889        proj_filename = proj_filename + options.suffix + msvs_version.ProjectExtension()
1890
1891    build_file = gyp.common.BuildFile(qualified_target)
1892    proj_path = os.path.join(os.path.dirname(build_file), proj_filename)
1893    fix_prefix = None
1894    if options.generator_output:
1895        project_dir_path = os.path.dirname(os.path.abspath(proj_path))
1896        proj_path = os.path.join(options.generator_output, proj_path)
1897        fix_prefix = gyp.common.RelativePath(
1898            project_dir_path, os.path.dirname(proj_path)
1899        )
1900    return proj_path, fix_prefix
1901
1902
1903def _GetPlatformOverridesOfProject(spec):
1904    # Prepare a dict indicating which project configurations are used for which
1905    # solution configurations for this target.
1906    config_platform_overrides = {}
1907    for config_name, c in spec["configurations"].items():
1908        config_fullname = _ConfigFullName(config_name, c)
1909        platform = c.get("msvs_target_platform", _ConfigPlatform(c))
1910        fixed_config_fullname = "{}|{}".format(
1911            _ConfigBaseName(config_name, _ConfigPlatform(c)),
1912            platform,
1913        )
1914        if spec["toolset"] == "host" and generator_supports_multiple_toolsets:
1915            fixed_config_fullname = f"{config_name}|x64"
1916        config_platform_overrides[config_fullname] = fixed_config_fullname
1917    return config_platform_overrides
1918
1919
1920def _CreateProjectObjects(target_list, target_dicts, options, msvs_version):
1921    """Create a MSVSProject object for the targets found in target list.
1922
1923  Arguments:
1924    target_list: the list of targets to generate project objects for.
1925    target_dicts: the dictionary of specifications.
1926    options: global generator options.
1927    msvs_version: the MSVSVersion object.
1928  Returns:
1929    A set of created projects, keyed by target.
1930  """
1931    global fixpath_prefix
1932    # Generate each project.
1933    projects = {}
1934    for qualified_target in target_list:
1935        spec = target_dicts[qualified_target]
1936        proj_path, fixpath_prefix = _GetPathOfProject(
1937            qualified_target, spec, options, msvs_version
1938        )
1939        guid = _GetGuidOfProject(proj_path, spec)
1940        overrides = _GetPlatformOverridesOfProject(spec)
1941        build_file = gyp.common.BuildFile(qualified_target)
1942        # Create object for this project.
1943        target_name = spec["target_name"]
1944        if spec["toolset"] == "host":
1945            target_name += "_host"
1946        obj = MSVSNew.MSVSProject(
1947            proj_path,
1948            name=target_name,
1949            guid=guid,
1950            spec=spec,
1951            build_file=build_file,
1952            config_platform_overrides=overrides,
1953            fixpath_prefix=fixpath_prefix,
1954        )
1955        # Set project toolset if any (MS build only)
1956        if msvs_version.UsesVcxproj():
1957            obj.set_msbuild_toolset(
1958                _GetMsbuildToolsetOfProject(proj_path, spec, msvs_version)
1959            )
1960        projects[qualified_target] = obj
1961    # Set all the dependencies, but not if we are using an external builder like
1962    # ninja
1963    for project in projects.values():
1964        if not project.spec.get("msvs_external_builder"):
1965            deps = project.spec.get("dependencies", [])
1966            deps = [projects[d] for d in deps]
1967            project.set_dependencies(deps)
1968    return projects
1969
1970
1971def _InitNinjaFlavor(params, target_list, target_dicts):
1972    """Initialize targets for the ninja flavor.
1973
1974  This sets up the necessary variables in the targets to generate msvs projects
1975  that use ninja as an external builder. The variables in the spec are only set
1976  if they have not been set. This allows individual specs to override the
1977  default values initialized here.
1978  Arguments:
1979    params: Params provided to the generator.
1980    target_list: List of target pairs: 'base/base.gyp:base'.
1981    target_dicts: Dict of target properties keyed on target pair.
1982  """
1983    for qualified_target in target_list:
1984        spec = target_dicts[qualified_target]
1985        if spec.get("msvs_external_builder"):
1986            # The spec explicitly defined an external builder, so don't change it.
1987            continue
1988
1989        path_to_ninja = spec.get("msvs_path_to_ninja", "ninja.exe")
1990
1991        spec["msvs_external_builder"] = "ninja"
1992        if not spec.get("msvs_external_builder_out_dir"):
1993            gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target)
1994            gyp_dir = os.path.dirname(gyp_file)
1995            configuration = "$(Configuration)"
1996            if params.get("target_arch") == "x64":
1997                configuration += "_x64"
1998            if params.get("target_arch") == "arm64":
1999                configuration += "_arm64"
2000            spec["msvs_external_builder_out_dir"] = os.path.join(
2001                gyp.common.RelativePath(params["options"].toplevel_dir, gyp_dir),
2002                ninja_generator.ComputeOutputDir(params),
2003                configuration,
2004            )
2005        if not spec.get("msvs_external_builder_build_cmd"):
2006            spec["msvs_external_builder_build_cmd"] = [
2007                path_to_ninja,
2008                "-C",
2009                "$(OutDir)",
2010                "$(ProjectName)",
2011            ]
2012        if not spec.get("msvs_external_builder_clean_cmd"):
2013            spec["msvs_external_builder_clean_cmd"] = [
2014                path_to_ninja,
2015                "-C",
2016                "$(OutDir)",
2017                "-tclean",
2018                "$(ProjectName)",
2019            ]
2020
2021
2022def CalculateVariables(default_variables, params):
2023    """Generated variables that require params to be known."""
2024
2025    generator_flags = params.get("generator_flags", {})
2026
2027    # Select project file format version (if unset, default to auto detecting).
2028    msvs_version = MSVSVersion.SelectVisualStudioVersion(
2029        generator_flags.get("msvs_version", "auto")
2030    )
2031    # Stash msvs_version for later (so we don't have to probe the system twice).
2032    params["msvs_version"] = msvs_version
2033
2034    # Set a variable so conditions can be based on msvs_version.
2035    default_variables["MSVS_VERSION"] = msvs_version.ShortName()
2036
2037    # To determine processor word size on Windows, in addition to checking
2038    # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
2039    # process), it is also necessary to check PROCESSOR_ARCITEW6432 (which
2040    # contains the actual word size of the system when running thru WOW64).
2041    if (
2042        os.environ.get("PROCESSOR_ARCHITECTURE", "").find("64") >= 0
2043        or os.environ.get("PROCESSOR_ARCHITEW6432", "").find("64") >= 0
2044    ):
2045        default_variables["MSVS_OS_BITS"] = 64
2046    else:
2047        default_variables["MSVS_OS_BITS"] = 32
2048
2049    if gyp.common.GetFlavor(params) == "ninja":
2050        default_variables["SHARED_INTERMEDIATE_DIR"] = "$(OutDir)gen"
2051
2052
2053def PerformBuild(data, configurations, params):
2054    options = params["options"]
2055    msvs_version = params["msvs_version"]
2056    devenv = os.path.join(msvs_version.path, "Common7", "IDE", "devenv.com")
2057
2058    for build_file, build_file_dict in data.items():
2059        (build_file_root, build_file_ext) = os.path.splitext(build_file)
2060        if build_file_ext != ".gyp":
2061            continue
2062        sln_path = build_file_root + options.suffix + ".sln"
2063        if options.generator_output:
2064            sln_path = os.path.join(options.generator_output, sln_path)
2065
2066    for config in configurations:
2067        arguments = [devenv, sln_path, "/Build", config]
2068        print(f"Building [{config}]: {arguments}")
2069        subprocess.check_call(arguments)
2070
2071
2072def CalculateGeneratorInputInfo(params):
2073    if params.get("flavor") == "ninja":
2074        toplevel = params["options"].toplevel_dir
2075        qualified_out_dir = os.path.normpath(
2076            os.path.join(
2077                toplevel,
2078                ninja_generator.ComputeOutputDir(params),
2079                "gypfiles-msvs-ninja",
2080            )
2081        )
2082
2083        global generator_filelist_paths
2084        generator_filelist_paths = {
2085            "toplevel": toplevel,
2086            "qualified_out_dir": qualified_out_dir,
2087        }
2088
2089
2090def GenerateOutput(target_list, target_dicts, data, params):
2091    """Generate .sln and .vcproj files.
2092
2093  This is the entry point for this generator.
2094  Arguments:
2095    target_list: List of target pairs: 'base/base.gyp:base'.
2096    target_dicts: Dict of target properties keyed on target pair.
2097    data: Dictionary containing per .gyp data.
2098  """
2099    global fixpath_prefix
2100
2101    options = params["options"]
2102
2103    # Get the project file format version back out of where we stashed it in
2104    # GeneratorCalculatedVariables.
2105    msvs_version = params["msvs_version"]
2106
2107    generator_flags = params.get("generator_flags", {})
2108
2109    # Optionally shard targets marked with 'msvs_shard': SHARD_COUNT.
2110    (target_list, target_dicts) = MSVSUtil.ShardTargets(target_list, target_dicts)
2111
2112    # Optionally use the large PDB workaround for targets marked with
2113    # 'msvs_large_pdb': 1.
2114    (target_list, target_dicts) = MSVSUtil.InsertLargePdbShims(
2115        target_list, target_dicts, generator_default_variables
2116    )
2117
2118    # Optionally configure each spec to use ninja as the external builder.
2119    if params.get("flavor") == "ninja":
2120        _InitNinjaFlavor(params, target_list, target_dicts)
2121
2122    # Prepare the set of configurations.
2123    configs = set()
2124    for qualified_target in target_list:
2125        spec = target_dicts[qualified_target]
2126        for config_name, config in spec["configurations"].items():
2127            config_name = _ConfigFullName(config_name, config)
2128            configs.add(config_name)
2129            if config_name == "Release|arm64":
2130                configs.add("Release|x64")
2131    configs = list(configs)
2132
2133    # Figure out all the projects that will be generated and their guids
2134    project_objects = _CreateProjectObjects(
2135        target_list, target_dicts, options, msvs_version
2136    )
2137
2138    # Generate each project.
2139    missing_sources = []
2140    for project in project_objects.values():
2141        fixpath_prefix = project.fixpath_prefix
2142        missing_sources.extend(
2143            _GenerateProject(project, options, msvs_version, generator_flags, spec)
2144        )
2145    fixpath_prefix = None
2146
2147    for build_file in data:
2148        # Validate build_file extension
2149        target_only_configs = configs
2150        if generator_supports_multiple_toolsets:
2151            target_only_configs = [i for i in configs if i.endswith("arm64")]
2152        if not build_file.endswith(".gyp"):
2153            continue
2154        sln_path = os.path.splitext(build_file)[0] + options.suffix + ".sln"
2155        if options.generator_output:
2156            sln_path = os.path.join(options.generator_output, sln_path)
2157        # Get projects in the solution, and their dependents.
2158        sln_projects = gyp.common.BuildFileTargets(target_list, build_file)
2159        sln_projects += gyp.common.DeepDependencyTargets(target_dicts, sln_projects)
2160        # Create folder hierarchy.
2161        root_entries = _GatherSolutionFolders(
2162            sln_projects, project_objects, flat=msvs_version.FlatSolution()
2163        )
2164        # Create solution.
2165        sln = MSVSNew.MSVSSolution(
2166            sln_path,
2167            entries=root_entries,
2168            variants=target_only_configs,
2169            websiteProperties=False,
2170            version=msvs_version,
2171        )
2172        sln.Write()
2173
2174    if missing_sources:
2175        error_message = "Missing input files:\n" + "\n".join(set(missing_sources))
2176        if generator_flags.get("msvs_error_on_missing_sources", False):
2177            raise GypError(error_message)
2178        else:
2179            print("Warning: " + error_message, file=sys.stdout)
2180
2181
2182def _GenerateMSBuildFiltersFile(
2183    filters_path,
2184    source_files,
2185    rule_dependencies,
2186    extension_to_rule_name,
2187    platforms,
2188    toolset,
2189):
2190    """Generate the filters file.
2191
2192  This file is used by Visual Studio to organize the presentation of source
2193  files into folders.
2194
2195  Arguments:
2196      filters_path: The path of the file to be created.
2197      source_files: The hierarchical structure of all the sources.
2198      extension_to_rule_name: A dictionary mapping file extensions to rules.
2199  """
2200    filter_group = []
2201    source_group = []
2202    _AppendFiltersForMSBuild(
2203        "",
2204        source_files,
2205        rule_dependencies,
2206        extension_to_rule_name,
2207        platforms,
2208        toolset,
2209        filter_group,
2210        source_group,
2211    )
2212    if filter_group:
2213        content = [
2214            "Project",
2215            {
2216                "ToolsVersion": "4.0",
2217                "xmlns": "http://schemas.microsoft.com/developer/msbuild/2003",
2218            },
2219            ["ItemGroup"] + filter_group,
2220            ["ItemGroup"] + source_group,
2221        ]
2222        easy_xml.WriteXmlIfChanged(content, filters_path, pretty=True, win32=True)
2223    elif os.path.exists(filters_path):
2224        # We don't need this filter anymore.  Delete the old filter file.
2225        os.unlink(filters_path)
2226
2227
2228def _AppendFiltersForMSBuild(
2229    parent_filter_name,
2230    sources,
2231    rule_dependencies,
2232    extension_to_rule_name,
2233    platforms,
2234    toolset,
2235    filter_group,
2236    source_group,
2237):
2238    """Creates the list of filters and sources to be added in the filter file.
2239
2240  Args:
2241      parent_filter_name: The name of the filter under which the sources are
2242          found.
2243      sources: The hierarchy of filters and sources to process.
2244      extension_to_rule_name: A dictionary mapping file extensions to rules.
2245      filter_group: The list to which filter entries will be appended.
2246      source_group: The list to which source entries will be appended.
2247  """
2248    for source in sources:
2249        if isinstance(source, MSVSProject.Filter):
2250            # We have a sub-filter.  Create the name of that sub-filter.
2251            if not parent_filter_name:
2252                filter_name = source.name
2253            else:
2254                filter_name = f"{parent_filter_name}\\{source.name}"
2255            # Add the filter to the group.
2256            filter_group.append(
2257                [
2258                    "Filter",
2259                    {"Include": filter_name},
2260                    ["UniqueIdentifier", MSVSNew.MakeGuid(source.name)],
2261                ]
2262            )
2263            # Recurse and add its dependents.
2264            _AppendFiltersForMSBuild(
2265                filter_name,
2266                source.contents,
2267                rule_dependencies,
2268                extension_to_rule_name,
2269                platforms,
2270                toolset,
2271                filter_group,
2272                source_group,
2273            )
2274        else:
2275            # It's a source.  Create a source entry.
2276            _, element = _MapFileToMsBuildSourceType(
2277                source, rule_dependencies, extension_to_rule_name, platforms, toolset
2278            )
2279            source_entry = [element, {"Include": source}]
2280            # Specify the filter it is part of, if any.
2281            if parent_filter_name:
2282                source_entry.append(["Filter", parent_filter_name])
2283            source_group.append(source_entry)
2284
2285
2286def _MapFileToMsBuildSourceType(
2287    source, rule_dependencies, extension_to_rule_name, platforms, toolset
2288):
2289    """Returns the group and element type of the source file.
2290
2291  Arguments:
2292      source: The source file name.
2293      extension_to_rule_name: A dictionary mapping file extensions to rules.
2294
2295  Returns:
2296      A pair of (group this file should be part of, the label of element)
2297  """
2298    _, ext = os.path.splitext(source)
2299    ext = ext.lower()
2300    if ext in extension_to_rule_name:
2301        group = "rule"
2302        element = extension_to_rule_name[ext]
2303    elif ext in [".cc", ".cpp", ".c", ".cxx", ".mm"]:
2304        group = "compile"
2305        element = "ClCompile"
2306    elif ext in [".h", ".hxx"]:
2307        group = "include"
2308        element = "ClInclude"
2309    elif ext == ".rc":
2310        group = "resource"
2311        element = "ResourceCompile"
2312    elif ext in [".s", ".asm"]:
2313        group = "masm"
2314        element = "MASM"
2315        if "arm64" in platforms and toolset == "target":
2316            element = "MARMASM"
2317    elif ext == ".idl":
2318        group = "midl"
2319        element = "Midl"
2320    elif source in rule_dependencies:
2321        group = "rule_dependency"
2322        element = "CustomBuild"
2323    else:
2324        group = "none"
2325        element = "None"
2326    return (group, element)
2327
2328
2329def _GenerateRulesForMSBuild(
2330    output_dir,
2331    options,
2332    spec,
2333    sources,
2334    excluded_sources,
2335    props_files_of_rules,
2336    targets_files_of_rules,
2337    actions_to_add,
2338    rule_dependencies,
2339    extension_to_rule_name,
2340):
2341    # MSBuild rules are implemented using three files: an XML file, a .targets
2342    # file and a .props file.
2343    # For more details see:
2344    # https://devblogs.microsoft.com/cppblog/quick-help-on-vs2010-custom-build-rule/
2345    rules = spec.get("rules", [])
2346    rules_native = [r for r in rules if not int(r.get("msvs_external_rule", 0))]
2347    rules_external = [r for r in rules if int(r.get("msvs_external_rule", 0))]
2348
2349    msbuild_rules = []
2350    for rule in rules_native:
2351        # Skip a rule with no action and no inputs.
2352        if "action" not in rule and not rule.get("rule_sources", []):
2353            continue
2354        msbuild_rule = MSBuildRule(rule, spec)
2355        msbuild_rules.append(msbuild_rule)
2356        rule_dependencies.update(msbuild_rule.additional_dependencies.split(";"))
2357        extension_to_rule_name[msbuild_rule.extension] = msbuild_rule.rule_name
2358    if msbuild_rules:
2359        base = spec["target_name"] + options.suffix
2360        props_name = base + ".props"
2361        targets_name = base + ".targets"
2362        xml_name = base + ".xml"
2363
2364        props_files_of_rules.add(props_name)
2365        targets_files_of_rules.add(targets_name)
2366
2367        props_path = os.path.join(output_dir, props_name)
2368        targets_path = os.path.join(output_dir, targets_name)
2369        xml_path = os.path.join(output_dir, xml_name)
2370
2371        _GenerateMSBuildRulePropsFile(props_path, msbuild_rules)
2372        _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules)
2373        _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules)
2374
2375    if rules_external:
2376        _GenerateExternalRules(
2377            rules_external, output_dir, spec, sources, options, actions_to_add
2378        )
2379    _AdjustSourcesForRules(rules, sources, excluded_sources, True)
2380
2381
2382class MSBuildRule:
2383    """Used to store information used to generate an MSBuild rule.
2384
2385  Attributes:
2386    rule_name: The rule name, sanitized to use in XML.
2387    target_name: The name of the target.
2388    after_targets: The name of the AfterTargets element.
2389    before_targets: The name of the BeforeTargets element.
2390    depends_on: The name of the DependsOn element.
2391    compute_output: The name of the ComputeOutput element.
2392    dirs_to_make: The name of the DirsToMake element.
2393    inputs: The name of the _inputs element.
2394    tlog: The name of the _tlog element.
2395    extension: The extension this rule applies to.
2396    description: The message displayed when this rule is invoked.
2397    additional_dependencies: A string listing additional dependencies.
2398    outputs: The outputs of this rule.
2399    command: The command used to run the rule.
2400  """
2401
2402    def __init__(self, rule, spec):
2403        self.display_name = rule["rule_name"]
2404        # Assure that the rule name is only characters and numbers
2405        self.rule_name = re.sub(r"\W", "_", self.display_name)
2406        # Create the various element names, following the example set by the
2407        # Visual Studio 2008 to 2010 conversion.  I don't know if VS2010
2408        # is sensitive to the exact names.
2409        self.target_name = "_" + self.rule_name
2410        self.after_targets = self.rule_name + "AfterTargets"
2411        self.before_targets = self.rule_name + "BeforeTargets"
2412        self.depends_on = self.rule_name + "DependsOn"
2413        self.compute_output = "Compute%sOutput" % self.rule_name
2414        self.dirs_to_make = self.rule_name + "DirsToMake"
2415        self.inputs = self.rule_name + "_inputs"
2416        self.tlog = self.rule_name + "_tlog"
2417        self.extension = rule["extension"]
2418        if not self.extension.startswith("."):
2419            self.extension = "." + self.extension
2420
2421        self.description = MSVSSettings.ConvertVCMacrosToMSBuild(
2422            rule.get("message", self.rule_name)
2423        )
2424        old_additional_dependencies = _FixPaths(rule.get("inputs", []))
2425        self.additional_dependencies = ";".join(
2426            [
2427                MSVSSettings.ConvertVCMacrosToMSBuild(i)
2428                for i in old_additional_dependencies
2429            ]
2430        )
2431        old_outputs = _FixPaths(rule.get("outputs", []))
2432        self.outputs = ";".join(
2433            [MSVSSettings.ConvertVCMacrosToMSBuild(i) for i in old_outputs]
2434        )
2435        old_command = _BuildCommandLineForRule(
2436            spec, rule, has_input_path=True, do_setup_env=True
2437        )
2438        self.command = MSVSSettings.ConvertVCMacrosToMSBuild(old_command)
2439
2440
2441def _GenerateMSBuildRulePropsFile(props_path, msbuild_rules):
2442    """Generate the .props file."""
2443    content = [
2444        "Project",
2445        {"xmlns": "http://schemas.microsoft.com/developer/msbuild/2003"},
2446    ]
2447    for rule in msbuild_rules:
2448        content.extend(
2449            [
2450                [
2451                    "PropertyGroup",
2452                    {
2453                        "Condition": "'$(%s)' == '' and '$(%s)' == '' and "
2454                        "'$(ConfigurationType)' != 'Makefile'"
2455                        % (rule.before_targets, rule.after_targets)
2456                    },
2457                    [rule.before_targets, "Midl"],
2458                    [rule.after_targets, "CustomBuild"],
2459                ],
2460                [
2461                    "PropertyGroup",
2462                    [
2463                        rule.depends_on,
2464                        {"Condition": "'$(ConfigurationType)' != 'Makefile'"},
2465                        "_SelectedFiles;$(%s)" % rule.depends_on,
2466                    ],
2467                ],
2468                [
2469                    "ItemDefinitionGroup",
2470                    [
2471                        rule.rule_name,
2472                        ["CommandLineTemplate", rule.command],
2473                        ["Outputs", rule.outputs],
2474                        ["ExecutionDescription", rule.description],
2475                        ["AdditionalDependencies", rule.additional_dependencies],
2476                    ],
2477                ],
2478            ]
2479        )
2480    easy_xml.WriteXmlIfChanged(content, props_path, pretty=True, win32=True)
2481
2482
2483def _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules):
2484    """Generate the .targets file."""
2485    content = [
2486        "Project",
2487        {"xmlns": "http://schemas.microsoft.com/developer/msbuild/2003"},
2488    ]
2489    item_group = [
2490        "ItemGroup",
2491        [
2492            "PropertyPageSchema",
2493            {"Include": "$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml"},
2494        ],
2495    ]
2496    for rule in msbuild_rules:
2497        item_group.append(
2498            [
2499                "AvailableItemName",
2500                {"Include": rule.rule_name},
2501                ["Targets", rule.target_name],
2502            ]
2503        )
2504    content.append(item_group)
2505
2506    for rule in msbuild_rules:
2507        content.append(
2508            [
2509                "UsingTask",
2510                {
2511                    "TaskName": rule.rule_name,
2512                    "TaskFactory": "XamlTaskFactory",
2513                    "AssemblyName": "Microsoft.Build.Tasks.v4.0",
2514                },
2515                ["Task", "$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml"],
2516            ]
2517        )
2518    for rule in msbuild_rules:
2519        rule_name = rule.rule_name
2520        target_outputs = "%%(%s.Outputs)" % rule_name
2521        target_inputs = (
2522            "%%(%s.Identity);%%(%s.AdditionalDependencies);" "$(MSBuildProjectFile)"
2523        ) % (rule_name, rule_name)
2524        rule_inputs = "%%(%s.Identity)" % rule_name
2525        extension_condition = (
2526            "'%(Extension)'=='.obj' or "
2527            "'%(Extension)'=='.res' or "
2528            "'%(Extension)'=='.rsc' or "
2529            "'%(Extension)'=='.lib'"
2530        )
2531        remove_section = [
2532            "ItemGroup",
2533            {"Condition": "'@(SelectedFiles)' != ''"},
2534            [
2535                rule_name,
2536                {
2537                    "Remove": "@(%s)" % rule_name,
2538                    "Condition": "'%(Identity)' != '@(SelectedFiles)'",
2539                },
2540            ],
2541        ]
2542        inputs_section = [
2543            "ItemGroup",
2544            [rule.inputs, {"Include": "%%(%s.AdditionalDependencies)" % rule_name}],
2545        ]
2546        logging_section = [
2547            "ItemGroup",
2548            [
2549                rule.tlog,
2550                {
2551                    "Include": "%%(%s.Outputs)" % rule_name,
2552                    "Condition": (
2553                        "'%%(%s.Outputs)' != '' and "
2554                        "'%%(%s.ExcludedFromBuild)' != 'true'" % (rule_name, rule_name)
2555                    ),
2556                },
2557                ["Source", "@(%s, '|')" % rule_name],
2558                ["Inputs", "@(%s -> '%%(Fullpath)', ';')" % rule.inputs],
2559            ],
2560        ]
2561        message_section = [
2562            "Message",
2563            {"Importance": "High", "Text": "%%(%s.ExecutionDescription)" % rule_name},
2564        ]
2565        write_tlog_section = [
2566            "WriteLinesToFile",
2567            {
2568                "Condition": "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2569                "'true'" % (rule.tlog, rule.tlog),
2570                "File": "$(IntDir)$(ProjectName).write.1.tlog",
2571                "Lines": "^%%(%s.Source);@(%s->'%%(Fullpath)')"
2572                % (rule.tlog, rule.tlog),
2573            },
2574        ]
2575        read_tlog_section = [
2576            "WriteLinesToFile",
2577            {
2578                "Condition": "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2579                "'true'" % (rule.tlog, rule.tlog),
2580                "File": "$(IntDir)$(ProjectName).read.1.tlog",
2581                "Lines": f"^%({rule.tlog}.Source);%({rule.tlog}.Inputs)",
2582            },
2583        ]
2584        command_and_input_section = [
2585            rule_name,
2586            {
2587                "Condition": "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2588                "'true'" % (rule_name, rule_name),
2589                "EchoOff": "true",
2590                "StandardOutputImportance": "High",
2591                "StandardErrorImportance": "High",
2592                "CommandLineTemplate": "%%(%s.CommandLineTemplate)" % rule_name,
2593                "AdditionalOptions": "%%(%s.AdditionalOptions)" % rule_name,
2594                "Inputs": rule_inputs,
2595            },
2596        ]
2597        content.extend(
2598            [
2599                [
2600                    "Target",
2601                    {
2602                        "Name": rule.target_name,
2603                        "BeforeTargets": "$(%s)" % rule.before_targets,
2604                        "AfterTargets": "$(%s)" % rule.after_targets,
2605                        "Condition": "'@(%s)' != ''" % rule_name,
2606                        "DependsOnTargets": "$(%s);%s"
2607                        % (rule.depends_on, rule.compute_output),
2608                        "Outputs": target_outputs,
2609                        "Inputs": target_inputs,
2610                    },
2611                    remove_section,
2612                    inputs_section,
2613                    logging_section,
2614                    message_section,
2615                    write_tlog_section,
2616                    read_tlog_section,
2617                    command_and_input_section,
2618                ],
2619                [
2620                    "PropertyGroup",
2621                    [
2622                        "ComputeLinkInputsTargets",
2623                        "$(ComputeLinkInputsTargets);",
2624                        "%s;" % rule.compute_output,
2625                    ],
2626                    [
2627                        "ComputeLibInputsTargets",
2628                        "$(ComputeLibInputsTargets);",
2629                        "%s;" % rule.compute_output,
2630                    ],
2631                ],
2632                [
2633                    "Target",
2634                    {
2635                        "Name": rule.compute_output,
2636                        "Condition": "'@(%s)' != ''" % rule_name,
2637                    },
2638                    [
2639                        "ItemGroup",
2640                        [
2641                            rule.dirs_to_make,
2642                            {
2643                                "Condition": "'@(%s)' != '' and "
2644                                "'%%(%s.ExcludedFromBuild)' != 'true'"
2645                                % (rule_name, rule_name),
2646                                "Include": "%%(%s.Outputs)" % rule_name,
2647                            },
2648                        ],
2649                        [
2650                            "Link",
2651                            {
2652                                "Include": "%%(%s.Identity)" % rule.dirs_to_make,
2653                                "Condition": extension_condition,
2654                            },
2655                        ],
2656                        [
2657                            "Lib",
2658                            {
2659                                "Include": "%%(%s.Identity)" % rule.dirs_to_make,
2660                                "Condition": extension_condition,
2661                            },
2662                        ],
2663                        [
2664                            "ImpLib",
2665                            {
2666                                "Include": "%%(%s.Identity)" % rule.dirs_to_make,
2667                                "Condition": extension_condition,
2668                            },
2669                        ],
2670                    ],
2671                    [
2672                        "MakeDir",
2673                        {
2674                            "Directories": (
2675                                "@(%s->'%%(RootDir)%%(Directory)')" % rule.dirs_to_make
2676                            )
2677                        },
2678                    ],
2679                ],
2680            ]
2681        )
2682    easy_xml.WriteXmlIfChanged(content, targets_path, pretty=True, win32=True)
2683
2684
2685def _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules):
2686    # Generate the .xml file
2687    content = [
2688        "ProjectSchemaDefinitions",
2689        {
2690            "xmlns": (
2691                "clr-namespace:Microsoft.Build.Framework.XamlTypes;"
2692                "assembly=Microsoft.Build.Framework"
2693            ),
2694            "xmlns:x": "http://schemas.microsoft.com/winfx/2006/xaml",
2695            "xmlns:sys": "clr-namespace:System;assembly=mscorlib",
2696            "xmlns:transformCallback": "Microsoft.Cpp.Dev10.ConvertPropertyCallback",
2697        },
2698    ]
2699    for rule in msbuild_rules:
2700        content.extend(
2701            [
2702                [
2703                    "Rule",
2704                    {
2705                        "Name": rule.rule_name,
2706                        "PageTemplate": "tool",
2707                        "DisplayName": rule.display_name,
2708                        "Order": "200",
2709                    },
2710                    [
2711                        "Rule.DataSource",
2712                        [
2713                            "DataSource",
2714                            {"Persistence": "ProjectFile", "ItemType": rule.rule_name},
2715                        ],
2716                    ],
2717                    [
2718                        "Rule.Categories",
2719                        [
2720                            "Category",
2721                            {"Name": "General"},
2722                            ["Category.DisplayName", ["sys:String", "General"]],
2723                        ],
2724                        [
2725                            "Category",
2726                            {"Name": "Command Line", "Subtype": "CommandLine"},
2727                            ["Category.DisplayName", ["sys:String", "Command Line"]],
2728                        ],
2729                    ],
2730                    [
2731                        "StringListProperty",
2732                        {
2733                            "Name": "Inputs",
2734                            "Category": "Command Line",
2735                            "IsRequired": "true",
2736                            "Switch": " ",
2737                        },
2738                        [
2739                            "StringListProperty.DataSource",
2740                            [
2741                                "DataSource",
2742                                {
2743                                    "Persistence": "ProjectFile",
2744                                    "ItemType": rule.rule_name,
2745                                    "SourceType": "Item",
2746                                },
2747                            ],
2748                        ],
2749                    ],
2750                    [
2751                        "StringProperty",
2752                        {
2753                            "Name": "CommandLineTemplate",
2754                            "DisplayName": "Command Line",
2755                            "Visible": "False",
2756                            "IncludeInCommandLine": "False",
2757                        },
2758                    ],
2759                    [
2760                        "DynamicEnumProperty",
2761                        {
2762                            "Name": rule.before_targets,
2763                            "Category": "General",
2764                            "EnumProvider": "Targets",
2765                            "IncludeInCommandLine": "False",
2766                        },
2767                        [
2768                            "DynamicEnumProperty.DisplayName",
2769                            ["sys:String", "Execute Before"],
2770                        ],
2771                        [
2772                            "DynamicEnumProperty.Description",
2773                            [
2774                                "sys:String",
2775                                "Specifies the targets for the build customization"
2776                                " to run before.",
2777                            ],
2778                        ],
2779                        [
2780                            "DynamicEnumProperty.ProviderSettings",
2781                            [
2782                                "NameValuePair",
2783                                {
2784                                    "Name": "Exclude",
2785                                    "Value": "^%s|^Compute" % rule.before_targets,
2786                                },
2787                            ],
2788                        ],
2789                        [
2790                            "DynamicEnumProperty.DataSource",
2791                            [
2792                                "DataSource",
2793                                {
2794                                    "Persistence": "ProjectFile",
2795                                    "HasConfigurationCondition": "true",
2796                                },
2797                            ],
2798                        ],
2799                    ],
2800                    [
2801                        "DynamicEnumProperty",
2802                        {
2803                            "Name": rule.after_targets,
2804                            "Category": "General",
2805                            "EnumProvider": "Targets",
2806                            "IncludeInCommandLine": "False",
2807                        },
2808                        [
2809                            "DynamicEnumProperty.DisplayName",
2810                            ["sys:String", "Execute After"],
2811                        ],
2812                        [
2813                            "DynamicEnumProperty.Description",
2814                            [
2815                                "sys:String",
2816                                (
2817                                    "Specifies the targets for the build customization"
2818                                    " to run after."
2819                                ),
2820                            ],
2821                        ],
2822                        [
2823                            "DynamicEnumProperty.ProviderSettings",
2824                            [
2825                                "NameValuePair",
2826                                {
2827                                    "Name": "Exclude",
2828                                    "Value": "^%s|^Compute" % rule.after_targets,
2829                                },
2830                            ],
2831                        ],
2832                        [
2833                            "DynamicEnumProperty.DataSource",
2834                            [
2835                                "DataSource",
2836                                {
2837                                    "Persistence": "ProjectFile",
2838                                    "ItemType": "",
2839                                    "HasConfigurationCondition": "true",
2840                                },
2841                            ],
2842                        ],
2843                    ],
2844                    [
2845                        "StringListProperty",
2846                        {
2847                            "Name": "Outputs",
2848                            "DisplayName": "Outputs",
2849                            "Visible": "False",
2850                            "IncludeInCommandLine": "False",
2851                        },
2852                    ],
2853                    [
2854                        "StringProperty",
2855                        {
2856                            "Name": "ExecutionDescription",
2857                            "DisplayName": "Execution Description",
2858                            "Visible": "False",
2859                            "IncludeInCommandLine": "False",
2860                        },
2861                    ],
2862                    [
2863                        "StringListProperty",
2864                        {
2865                            "Name": "AdditionalDependencies",
2866                            "DisplayName": "Additional Dependencies",
2867                            "IncludeInCommandLine": "False",
2868                            "Visible": "false",
2869                        },
2870                    ],
2871                    [
2872                        "StringProperty",
2873                        {
2874                            "Subtype": "AdditionalOptions",
2875                            "Name": "AdditionalOptions",
2876                            "Category": "Command Line",
2877                        },
2878                        [
2879                            "StringProperty.DisplayName",
2880                            ["sys:String", "Additional Options"],
2881                        ],
2882                        [
2883                            "StringProperty.Description",
2884                            ["sys:String", "Additional Options"],
2885                        ],
2886                    ],
2887                ],
2888                [
2889                    "ItemType",
2890                    {"Name": rule.rule_name, "DisplayName": rule.display_name},
2891                ],
2892                [
2893                    "FileExtension",
2894                    {"Name": "*" + rule.extension, "ContentType": rule.rule_name},
2895                ],
2896                [
2897                    "ContentType",
2898                    {
2899                        "Name": rule.rule_name,
2900                        "DisplayName": "",
2901                        "ItemType": rule.rule_name,
2902                    },
2903                ],
2904            ]
2905        )
2906    easy_xml.WriteXmlIfChanged(content, xml_path, pretty=True, win32=True)
2907
2908
2909def _GetConfigurationAndPlatform(name, settings, spec):
2910    configuration = name.rsplit("_", 1)[0]
2911    platform = settings.get("msvs_configuration_platform", "Win32")
2912    if spec["toolset"] == "host" and platform == "arm64":
2913        platform = "x64"  # Host-only tools are always built for x64
2914    return (configuration, platform)
2915
2916
2917def _GetConfigurationCondition(name, settings, spec):
2918    return r"'$(Configuration)|$(Platform)'=='%s|%s'" % _GetConfigurationAndPlatform(
2919        name, settings, spec
2920    )
2921
2922
2923def _GetMSBuildProjectConfigurations(configurations, spec):
2924    group = ["ItemGroup", {"Label": "ProjectConfigurations"}]
2925    for (name, settings) in sorted(configurations.items()):
2926        configuration, platform = _GetConfigurationAndPlatform(name, settings, spec)
2927        designation = f"{configuration}|{platform}"
2928        group.append(
2929            [
2930                "ProjectConfiguration",
2931                {"Include": designation},
2932                ["Configuration", configuration],
2933                ["Platform", platform],
2934            ]
2935        )
2936    return [group]
2937
2938
2939def _GetMSBuildGlobalProperties(spec, version, guid, gyp_file_name):
2940    namespace = os.path.splitext(gyp_file_name)[0]
2941    properties = [
2942        [
2943            "PropertyGroup",
2944            {"Label": "Globals"},
2945            ["ProjectGuid", guid],
2946            ["Keyword", "Win32Proj"],
2947            ["RootNamespace", namespace],
2948            ["IgnoreWarnCompileDuplicatedFilename", "true"],
2949        ]
2950    ]
2951
2952    if (
2953        os.environ.get("PROCESSOR_ARCHITECTURE") == "AMD64"
2954        or os.environ.get("PROCESSOR_ARCHITEW6432") == "AMD64"
2955    ):
2956        properties[0].append(["PreferredToolArchitecture", "x64"])
2957
2958    if spec.get("msvs_target_platform_version"):
2959        target_platform_version = spec.get("msvs_target_platform_version")
2960        properties[0].append(["WindowsTargetPlatformVersion", target_platform_version])
2961        if spec.get("msvs_target_platform_minversion"):
2962            target_platform_minversion = spec.get("msvs_target_platform_minversion")
2963            properties[0].append(
2964                ["WindowsTargetPlatformMinVersion", target_platform_minversion]
2965            )
2966        else:
2967            properties[0].append(
2968                ["WindowsTargetPlatformMinVersion", target_platform_version]
2969            )
2970
2971    if spec.get("msvs_enable_winrt"):
2972        properties[0].append(["DefaultLanguage", "en-US"])
2973        properties[0].append(["AppContainerApplication", "true"])
2974        if spec.get("msvs_application_type_revision"):
2975            app_type_revision = spec.get("msvs_application_type_revision")
2976            properties[0].append(["ApplicationTypeRevision", app_type_revision])
2977        else:
2978            properties[0].append(["ApplicationTypeRevision", "8.1"])
2979        if spec.get("msvs_enable_winphone"):
2980            properties[0].append(["ApplicationType", "Windows Phone"])
2981        else:
2982            properties[0].append(["ApplicationType", "Windows Store"])
2983
2984    platform_name = None
2985    msvs_windows_sdk_version = None
2986    for configuration in spec["configurations"].values():
2987        platform_name = platform_name or _ConfigPlatform(configuration)
2988        msvs_windows_sdk_version = (
2989            msvs_windows_sdk_version
2990            or _ConfigWindowsTargetPlatformVersion(configuration, version)
2991        )
2992        if platform_name and msvs_windows_sdk_version:
2993            break
2994    if msvs_windows_sdk_version:
2995        properties[0].append(
2996            ["WindowsTargetPlatformVersion", str(msvs_windows_sdk_version)]
2997        )
2998    elif version.compatible_sdks:
2999        raise GypError(
3000            "%s requires any SDK of %s version, but none were found"
3001            % (version.description, version.compatible_sdks)
3002        )
3003
3004    if platform_name == "ARM":
3005        properties[0].append(["WindowsSDKDesktopARMSupport", "true"])
3006
3007    return properties
3008
3009
3010def _GetMSBuildConfigurationDetails(spec, build_file):
3011    properties = {}
3012    for name, settings in spec["configurations"].items():
3013        msbuild_attributes = _GetMSBuildAttributes(spec, settings, build_file)
3014        condition = _GetConfigurationCondition(name, settings, spec)
3015        character_set = msbuild_attributes.get("CharacterSet")
3016        config_type = msbuild_attributes.get("ConfigurationType")
3017        _AddConditionalProperty(properties, condition, "ConfigurationType", config_type)
3018        if config_type == "Driver":
3019            _AddConditionalProperty(properties, condition, "DriverType", "WDM")
3020            _AddConditionalProperty(
3021                properties, condition, "TargetVersion", _ConfigTargetVersion(settings)
3022            )
3023        if character_set:
3024            if "msvs_enable_winrt" not in spec:
3025                _AddConditionalProperty(
3026                    properties, condition, "CharacterSet", character_set
3027                )
3028    return _GetMSBuildPropertyGroup(spec, "Configuration", properties)
3029
3030
3031def _GetMSBuildLocalProperties(msbuild_toolset):
3032    # Currently the only local property we support is PlatformToolset
3033    properties = {}
3034    if msbuild_toolset:
3035        properties = [
3036            [
3037                "PropertyGroup",
3038                {"Label": "Locals"},
3039                ["PlatformToolset", msbuild_toolset],
3040            ]
3041        ]
3042    return properties
3043
3044
3045def _GetMSBuildPropertySheets(configurations, spec):
3046    user_props = r"$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"
3047    additional_props = {}
3048    props_specified = False
3049    for name, settings in sorted(configurations.items()):
3050        configuration = _GetConfigurationCondition(name, settings, spec)
3051        if "msbuild_props" in settings:
3052            additional_props[configuration] = _FixPaths(settings["msbuild_props"])
3053            props_specified = True
3054        else:
3055            additional_props[configuration] = ""
3056
3057    if not props_specified:
3058        return [
3059            [
3060                "ImportGroup",
3061                {"Label": "PropertySheets"},
3062                [
3063                    "Import",
3064                    {
3065                        "Project": user_props,
3066                        "Condition": "exists('%s')" % user_props,
3067                        "Label": "LocalAppDataPlatform",
3068                    },
3069                ],
3070            ]
3071        ]
3072    else:
3073        sheets = []
3074        for condition, props in additional_props.items():
3075            import_group = [
3076                "ImportGroup",
3077                {"Label": "PropertySheets", "Condition": condition},
3078                [
3079                    "Import",
3080                    {
3081                        "Project": user_props,
3082                        "Condition": "exists('%s')" % user_props,
3083                        "Label": "LocalAppDataPlatform",
3084                    },
3085                ],
3086            ]
3087            for props_file in props:
3088                import_group.append(["Import", {"Project": props_file}])
3089            sheets.append(import_group)
3090        return sheets
3091
3092
3093def _ConvertMSVSBuildAttributes(spec, config, build_file):
3094    config_type = _GetMSVSConfigurationType(spec, build_file)
3095    msvs_attributes = _GetMSVSAttributes(spec, config, config_type)
3096    msbuild_attributes = {}
3097    for a in msvs_attributes:
3098        if a in ["IntermediateDirectory", "OutputDirectory"]:
3099            directory = MSVSSettings.ConvertVCMacrosToMSBuild(msvs_attributes[a])
3100            if not directory.endswith("\\"):
3101                directory += "\\"
3102            msbuild_attributes[a] = directory
3103        elif a == "CharacterSet":
3104            msbuild_attributes[a] = _ConvertMSVSCharacterSet(msvs_attributes[a])
3105        elif a == "ConfigurationType":
3106            msbuild_attributes[a] = _ConvertMSVSConfigurationType(msvs_attributes[a])
3107        else:
3108            print("Warning: Do not know how to convert MSVS attribute " + a)
3109    return msbuild_attributes
3110
3111
3112def _ConvertMSVSCharacterSet(char_set):
3113    if char_set.isdigit():
3114        char_set = {"0": "MultiByte", "1": "Unicode", "2": "MultiByte"}[char_set]
3115    return char_set
3116
3117
3118def _ConvertMSVSConfigurationType(config_type):
3119    if config_type.isdigit():
3120        config_type = {
3121            "1": "Application",
3122            "2": "DynamicLibrary",
3123            "4": "StaticLibrary",
3124            "5": "Driver",
3125            "10": "Utility",
3126        }[config_type]
3127    return config_type
3128
3129
3130def _GetMSBuildAttributes(spec, config, build_file):
3131    if "msbuild_configuration_attributes" not in config:
3132        msbuild_attributes = _ConvertMSVSBuildAttributes(spec, config, build_file)
3133
3134    else:
3135        config_type = _GetMSVSConfigurationType(spec, build_file)
3136        config_type = _ConvertMSVSConfigurationType(config_type)
3137        msbuild_attributes = config.get("msbuild_configuration_attributes", {})
3138        msbuild_attributes.setdefault("ConfigurationType", config_type)
3139        output_dir = msbuild_attributes.get(
3140            "OutputDirectory", "$(SolutionDir)$(Configuration)"
3141        )
3142        msbuild_attributes["OutputDirectory"] = _FixPath(output_dir) + "\\"
3143        if "IntermediateDirectory" not in msbuild_attributes:
3144            intermediate = _FixPath("$(Configuration)") + "\\"
3145            msbuild_attributes["IntermediateDirectory"] = intermediate
3146        if "CharacterSet" in msbuild_attributes:
3147            msbuild_attributes["CharacterSet"] = _ConvertMSVSCharacterSet(
3148                msbuild_attributes["CharacterSet"]
3149            )
3150    if "TargetName" not in msbuild_attributes:
3151        prefix = spec.get("product_prefix", "")
3152        product_name = spec.get("product_name", "$(ProjectName)")
3153        target_name = prefix + product_name
3154        msbuild_attributes["TargetName"] = target_name
3155    if "TargetExt" not in msbuild_attributes and "product_extension" in spec:
3156        ext = spec.get("product_extension")
3157        msbuild_attributes["TargetExt"] = "." + ext
3158
3159    if spec.get("msvs_external_builder"):
3160        external_out_dir = spec.get("msvs_external_builder_out_dir", ".")
3161        msbuild_attributes["OutputDirectory"] = _FixPath(external_out_dir) + "\\"
3162
3163    # Make sure that 'TargetPath' matches 'Lib.OutputFile' or 'Link.OutputFile'
3164    # (depending on the tool used) to avoid MSB8012 warning.
3165    msbuild_tool_map = {
3166        "executable": "Link",
3167        "shared_library": "Link",
3168        "loadable_module": "Link",
3169        "windows_driver": "Link",
3170        "static_library": "Lib",
3171    }
3172    msbuild_tool = msbuild_tool_map.get(spec["type"])
3173    if msbuild_tool:
3174        msbuild_settings = config["finalized_msbuild_settings"]
3175        out_file = msbuild_settings[msbuild_tool].get("OutputFile")
3176        if out_file:
3177            msbuild_attributes["TargetPath"] = _FixPath(out_file)
3178        target_ext = msbuild_settings[msbuild_tool].get("TargetExt")
3179        if target_ext:
3180            msbuild_attributes["TargetExt"] = target_ext
3181
3182    return msbuild_attributes
3183
3184
3185def _GetMSBuildConfigurationGlobalProperties(spec, configurations, build_file):
3186    # TODO(jeanluc) We could optimize out the following and do it only if
3187    # there are actions.
3188    # TODO(jeanluc) Handle the equivalent of setting 'CYGWIN=nontsec'.
3189    new_paths = []
3190    cygwin_dirs = spec.get("msvs_cygwin_dirs", ["."])[0]
3191    if cygwin_dirs:
3192        cyg_path = "$(MSBuildProjectDirectory)\\%s\\bin\\" % _FixPath(cygwin_dirs)
3193        new_paths.append(cyg_path)
3194        # TODO(jeanluc) Change the convention to have both a cygwin_dir and a
3195        # python_dir.
3196        python_path = cyg_path.replace("cygwin\\bin", "python_26")
3197        new_paths.append(python_path)
3198        if new_paths:
3199            new_paths = "$(ExecutablePath);" + ";".join(new_paths)
3200
3201    properties = {}
3202    for (name, configuration) in sorted(configurations.items()):
3203        condition = _GetConfigurationCondition(name, configuration, spec)
3204        attributes = _GetMSBuildAttributes(spec, configuration, build_file)
3205        msbuild_settings = configuration["finalized_msbuild_settings"]
3206        _AddConditionalProperty(
3207            properties, condition, "IntDir", attributes["IntermediateDirectory"]
3208        )
3209        _AddConditionalProperty(
3210            properties, condition, "OutDir", attributes["OutputDirectory"]
3211        )
3212        _AddConditionalProperty(
3213            properties, condition, "TargetName", attributes["TargetName"]
3214        )
3215        if "TargetExt" in attributes:
3216            _AddConditionalProperty(
3217                properties, condition, "TargetExt", attributes["TargetExt"]
3218            )
3219
3220        if attributes.get("TargetPath"):
3221            _AddConditionalProperty(
3222                properties, condition, "TargetPath", attributes["TargetPath"]
3223            )
3224        if attributes.get("TargetExt"):
3225            _AddConditionalProperty(
3226                properties, condition, "TargetExt", attributes["TargetExt"]
3227            )
3228
3229        if new_paths:
3230            _AddConditionalProperty(properties, condition, "ExecutablePath", new_paths)
3231        tool_settings = msbuild_settings.get("", {})
3232        for name, value in sorted(tool_settings.items()):
3233            formatted_value = _GetValueFormattedForMSBuild("", name, value)
3234            _AddConditionalProperty(properties, condition, name, formatted_value)
3235    return _GetMSBuildPropertyGroup(spec, None, properties)
3236
3237
3238def _AddConditionalProperty(properties, condition, name, value):
3239    """Adds a property / conditional value pair to a dictionary.
3240
3241  Arguments:
3242    properties: The dictionary to be modified.  The key is the name of the
3243        property.  The value is itself a dictionary; its key is the value and
3244        the value a list of condition for which this value is true.
3245    condition: The condition under which the named property has the value.
3246    name: The name of the property.
3247    value: The value of the property.
3248  """
3249    if name not in properties:
3250        properties[name] = {}
3251    values = properties[name]
3252    if value not in values:
3253        values[value] = []
3254    conditions = values[value]
3255    conditions.append(condition)
3256
3257
3258# Regex for msvs variable references ( i.e. $(FOO) ).
3259MSVS_VARIABLE_REFERENCE = re.compile(r"\$\(([a-zA-Z_][a-zA-Z0-9_]*)\)")
3260
3261
3262def _GetMSBuildPropertyGroup(spec, label, properties):
3263    """Returns a PropertyGroup definition for the specified properties.
3264
3265  Arguments:
3266    spec: The target project dict.
3267    label: An optional label for the PropertyGroup.
3268    properties: The dictionary to be converted.  The key is the name of the
3269        property.  The value is itself a dictionary; its key is the value and
3270        the value a list of condition for which this value is true.
3271  """
3272    group = ["PropertyGroup"]
3273    if label:
3274        group.append({"Label": label})
3275    num_configurations = len(spec["configurations"])
3276
3277    def GetEdges(node):
3278        # Use a definition of edges such that user_of_variable -> used_varible.
3279        # This happens to be easier in this case, since a variable's
3280        # definition contains all variables it references in a single string.
3281        edges = set()
3282        for value in sorted(properties[node].keys()):
3283            # Add to edges all $(...) references to variables.
3284            #
3285            # Variable references that refer to names not in properties are excluded
3286            # These can exist for instance to refer built in definitions like
3287            # $(SolutionDir).
3288            #
3289            # Self references are ignored. Self reference is used in a few places to
3290            # append to the default value. I.e. PATH=$(PATH);other_path
3291            edges.update(
3292                {
3293                    v
3294                    for v in MSVS_VARIABLE_REFERENCE.findall(value)
3295                    if v in properties and v != node
3296                }
3297            )
3298        return edges
3299
3300    properties_ordered = gyp.common.TopologicallySorted(properties.keys(), GetEdges)
3301    # Walk properties in the reverse of a topological sort on
3302    # user_of_variable -> used_variable as this ensures variables are
3303    # defined before they are used.
3304    # NOTE: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))
3305    for name in reversed(properties_ordered):
3306        values = properties[name]
3307        for value, conditions in sorted(values.items()):
3308            if len(conditions) == num_configurations:
3309                # If the value is the same all configurations,
3310                # just add one unconditional entry.
3311                group.append([name, value])
3312            else:
3313                for condition in conditions:
3314                    group.append([name, {"Condition": condition}, value])
3315    return [group]
3316
3317
3318def _GetMSBuildToolSettingsSections(spec, configurations):
3319    groups = []
3320    for (name, configuration) in sorted(configurations.items()):
3321        msbuild_settings = configuration["finalized_msbuild_settings"]
3322        group = [
3323            "ItemDefinitionGroup",
3324            {"Condition": _GetConfigurationCondition(name, configuration, spec)},
3325        ]
3326        for tool_name, tool_settings in sorted(msbuild_settings.items()):
3327            # Skip the tool named '' which is a holder of global settings handled
3328            # by _GetMSBuildConfigurationGlobalProperties.
3329            if tool_name:
3330                if tool_settings:
3331                    tool = [tool_name]
3332                    for name, value in sorted(tool_settings.items()):
3333                        formatted_value = _GetValueFormattedForMSBuild(
3334                            tool_name, name, value
3335                        )
3336                        tool.append([name, formatted_value])
3337                    group.append(tool)
3338        groups.append(group)
3339    return groups
3340
3341
3342def _FinalizeMSBuildSettings(spec, configuration):
3343    if "msbuild_settings" in configuration:
3344        converted = False
3345        msbuild_settings = configuration["msbuild_settings"]
3346        MSVSSettings.ValidateMSBuildSettings(msbuild_settings)
3347    else:
3348        converted = True
3349        msvs_settings = configuration.get("msvs_settings", {})
3350        msbuild_settings = MSVSSettings.ConvertToMSBuildSettings(msvs_settings)
3351    include_dirs, midl_include_dirs, resource_include_dirs = _GetIncludeDirs(
3352        configuration
3353    )
3354    libraries = _GetLibraries(spec)
3355    library_dirs = _GetLibraryDirs(configuration)
3356    out_file, _, msbuild_tool = _GetOutputFilePathAndTool(spec, msbuild=True)
3357    target_ext = _GetOutputTargetExt(spec)
3358    defines = _GetDefines(configuration)
3359    if converted:
3360        # Visual Studio 2010 has TR1
3361        defines = [d for d in defines if d != "_HAS_TR1=0"]
3362        # Warn of ignored settings
3363        ignored_settings = ["msvs_tool_files"]
3364        for ignored_setting in ignored_settings:
3365            value = configuration.get(ignored_setting)
3366            if value:
3367                print(
3368                    "Warning: The automatic conversion to MSBuild does not handle "
3369                    "%s.  Ignoring setting of %s" % (ignored_setting, str(value))
3370                )
3371
3372    defines = [_EscapeCppDefineForMSBuild(d) for d in defines]
3373    disabled_warnings = _GetDisabledWarnings(configuration)
3374    prebuild = configuration.get("msvs_prebuild")
3375    postbuild = configuration.get("msvs_postbuild")
3376    def_file = _GetModuleDefinition(spec)
3377    precompiled_header = configuration.get("msvs_precompiled_header")
3378
3379    # Add the information to the appropriate tool
3380    # TODO(jeanluc) We could optimize and generate these settings only if
3381    # the corresponding files are found, e.g. don't generate ResourceCompile
3382    # if you don't have any resources.
3383    _ToolAppend(
3384        msbuild_settings, "ClCompile", "AdditionalIncludeDirectories", include_dirs
3385    )
3386    _ToolAppend(
3387        msbuild_settings, "Midl", "AdditionalIncludeDirectories", midl_include_dirs
3388    )
3389    _ToolAppend(
3390        msbuild_settings,
3391        "ResourceCompile",
3392        "AdditionalIncludeDirectories",
3393        resource_include_dirs,
3394    )
3395    # Add in libraries, note that even for empty libraries, we want this
3396    # set, to prevent inheriting default libraries from the environment.
3397    _ToolSetOrAppend(msbuild_settings, "Link", "AdditionalDependencies", libraries)
3398    _ToolAppend(msbuild_settings, "Link", "AdditionalLibraryDirectories", library_dirs)
3399    if out_file:
3400        _ToolAppend(
3401            msbuild_settings, msbuild_tool, "OutputFile", out_file, only_if_unset=True
3402        )
3403    if target_ext:
3404        _ToolAppend(
3405            msbuild_settings, msbuild_tool, "TargetExt", target_ext, only_if_unset=True
3406        )
3407    # Add defines.
3408    _ToolAppend(msbuild_settings, "ClCompile", "PreprocessorDefinitions", defines)
3409    _ToolAppend(msbuild_settings, "ResourceCompile", "PreprocessorDefinitions", defines)
3410    # Add disabled warnings.
3411    _ToolAppend(
3412        msbuild_settings, "ClCompile", "DisableSpecificWarnings", disabled_warnings
3413    )
3414    # Turn on precompiled headers if appropriate.
3415    if precompiled_header:
3416        precompiled_header = os.path.split(precompiled_header)[1]
3417        _ToolAppend(msbuild_settings, "ClCompile", "PrecompiledHeader", "Use")
3418        _ToolAppend(
3419            msbuild_settings, "ClCompile", "PrecompiledHeaderFile", precompiled_header
3420        )
3421        _ToolAppend(
3422            msbuild_settings, "ClCompile", "ForcedIncludeFiles", [precompiled_header]
3423        )
3424    else:
3425        _ToolAppend(msbuild_settings, "ClCompile", "PrecompiledHeader", "NotUsing")
3426    # Turn off WinRT compilation
3427    _ToolAppend(msbuild_settings, "ClCompile", "CompileAsWinRT", "false")
3428    # Turn on import libraries if appropriate
3429    if spec.get("msvs_requires_importlibrary"):
3430        _ToolAppend(msbuild_settings, "", "IgnoreImportLibrary", "false")
3431    # Loadable modules don't generate import libraries;
3432    # tell dependent projects to not expect one.
3433    if spec["type"] == "loadable_module":
3434        _ToolAppend(msbuild_settings, "", "IgnoreImportLibrary", "true")
3435    # Set the module definition file if any.
3436    if def_file:
3437        _ToolAppend(msbuild_settings, "Link", "ModuleDefinitionFile", def_file)
3438    configuration["finalized_msbuild_settings"] = msbuild_settings
3439    if prebuild:
3440        _ToolAppend(msbuild_settings, "PreBuildEvent", "Command", prebuild)
3441    if postbuild:
3442        _ToolAppend(msbuild_settings, "PostBuildEvent", "Command", postbuild)
3443
3444
3445def _GetValueFormattedForMSBuild(tool_name, name, value):
3446    if type(value) == list:
3447        # For some settings, VS2010 does not automatically extends the settings
3448        # TODO(jeanluc) Is this what we want?
3449        if name in [
3450            "AdditionalIncludeDirectories",
3451            "AdditionalLibraryDirectories",
3452            "AdditionalOptions",
3453            "DelayLoadDLLs",
3454            "DisableSpecificWarnings",
3455            "PreprocessorDefinitions",
3456        ]:
3457            value.append("%%(%s)" % name)
3458        # For most tools, entries in a list should be separated with ';' but some
3459        # settings use a space.  Check for those first.
3460        exceptions = {
3461            "ClCompile": ["AdditionalOptions"],
3462            "Link": ["AdditionalOptions"],
3463            "Lib": ["AdditionalOptions"],
3464        }
3465        if tool_name in exceptions and name in exceptions[tool_name]:
3466            char = " "
3467        else:
3468            char = ";"
3469        formatted_value = char.join(
3470            [MSVSSettings.ConvertVCMacrosToMSBuild(i) for i in value]
3471        )
3472    else:
3473        formatted_value = MSVSSettings.ConvertVCMacrosToMSBuild(value)
3474    return formatted_value
3475
3476
3477def _VerifySourcesExist(sources, root_dir):
3478    """Verifies that all source files exist on disk.
3479
3480  Checks that all regular source files, i.e. not created at run time,
3481  exist on disk.  Missing files cause needless recompilation but no otherwise
3482  visible errors.
3483
3484  Arguments:
3485    sources: A recursive list of Filter/file names.
3486    root_dir: The root directory for the relative path names.
3487  Returns:
3488    A list of source files that cannot be found on disk.
3489  """
3490    missing_sources = []
3491    for source in sources:
3492        if isinstance(source, MSVSProject.Filter):
3493            missing_sources.extend(_VerifySourcesExist(source.contents, root_dir))
3494        else:
3495            if "$" not in source:
3496                full_path = os.path.join(root_dir, source)
3497                if not os.path.exists(full_path):
3498                    missing_sources.append(full_path)
3499    return missing_sources
3500
3501
3502def _GetMSBuildSources(
3503    spec,
3504    sources,
3505    exclusions,
3506    rule_dependencies,
3507    extension_to_rule_name,
3508    actions_spec,
3509    sources_handled_by_action,
3510    list_excluded,
3511):
3512    groups = [
3513        "none",
3514        "masm",
3515        "midl",
3516        "include",
3517        "compile",
3518        "resource",
3519        "rule",
3520        "rule_dependency",
3521    ]
3522    grouped_sources = {}
3523    for g in groups:
3524        grouped_sources[g] = []
3525
3526    _AddSources2(
3527        spec,
3528        sources,
3529        exclusions,
3530        grouped_sources,
3531        rule_dependencies,
3532        extension_to_rule_name,
3533        sources_handled_by_action,
3534        list_excluded,
3535    )
3536    sources = []
3537    for g in groups:
3538        if grouped_sources[g]:
3539            sources.append(["ItemGroup"] + grouped_sources[g])
3540    if actions_spec:
3541        sources.append(["ItemGroup"] + actions_spec)
3542    return sources
3543
3544
3545def _AddSources2(
3546    spec,
3547    sources,
3548    exclusions,
3549    grouped_sources,
3550    rule_dependencies,
3551    extension_to_rule_name,
3552    sources_handled_by_action,
3553    list_excluded,
3554):
3555    extensions_excluded_from_precompile = []
3556    for source in sources:
3557        if isinstance(source, MSVSProject.Filter):
3558            _AddSources2(
3559                spec,
3560                source.contents,
3561                exclusions,
3562                grouped_sources,
3563                rule_dependencies,
3564                extension_to_rule_name,
3565                sources_handled_by_action,
3566                list_excluded,
3567            )
3568        else:
3569            if source not in sources_handled_by_action:
3570                detail = []
3571                excluded_configurations = exclusions.get(source, [])
3572                if len(excluded_configurations) == len(spec["configurations"]):
3573                    detail.append(["ExcludedFromBuild", "true"])
3574                else:
3575                    for config_name, configuration in sorted(excluded_configurations):
3576                        condition = _GetConfigurationCondition(
3577                            config_name, configuration
3578                        )
3579                        detail.append(
3580                            ["ExcludedFromBuild", {"Condition": condition}, "true"]
3581                        )
3582                # Add precompile if needed
3583                for config_name, configuration in spec["configurations"].items():
3584                    precompiled_source = configuration.get(
3585                        "msvs_precompiled_source", ""
3586                    )
3587                    if precompiled_source != "":
3588                        precompiled_source = _FixPath(precompiled_source)
3589                        if not extensions_excluded_from_precompile:
3590                            # If the precompiled header is generated by a C source,
3591                            # we must not try to use it for C++ sources,
3592                            # and vice versa.
3593                            basename, extension = os.path.splitext(precompiled_source)
3594                            if extension == ".c":
3595                                extensions_excluded_from_precompile = [
3596                                    ".cc",
3597                                    ".cpp",
3598                                    ".cxx",
3599                                ]
3600                            else:
3601                                extensions_excluded_from_precompile = [".c"]
3602
3603                    if precompiled_source == source:
3604                        condition = _GetConfigurationCondition(
3605                            config_name, configuration, spec
3606                        )
3607                        detail.append(
3608                            ["PrecompiledHeader", {"Condition": condition}, "Create"]
3609                        )
3610                    else:
3611                        # Turn off precompiled header usage for source files of a
3612                        # different type than the file that generated the
3613                        # precompiled header.
3614                        for extension in extensions_excluded_from_precompile:
3615                            if source.endswith(extension):
3616                                detail.append(["PrecompiledHeader", ""])
3617                                detail.append(["ForcedIncludeFiles", ""])
3618
3619                group, element = _MapFileToMsBuildSourceType(
3620                    source,
3621                    rule_dependencies,
3622                    extension_to_rule_name,
3623                    _GetUniquePlatforms(spec),
3624                    spec["toolset"],
3625                )
3626                if group == "compile" and not os.path.isabs(source):
3627                    # Add an <ObjectFileName> value to support duplicate source
3628                    # file basenames, except for absolute paths to avoid paths
3629                    # with more than 260 characters.
3630                    file_name = os.path.splitext(source)[0] + ".obj"
3631                    if file_name.startswith("..\\"):
3632                        file_name = re.sub(r"^(\.\.\\)+", "", file_name)
3633                    elif file_name.startswith("$("):
3634                        file_name = re.sub(r"^\$\([^)]+\)\\", "", file_name)
3635                    detail.append(["ObjectFileName", "$(IntDir)\\" + file_name])
3636                grouped_sources[group].append([element, {"Include": source}] + detail)
3637
3638
3639def _GetMSBuildProjectReferences(project):
3640    references = []
3641    if project.dependencies:
3642        group = ["ItemGroup"]
3643        added_dependency_set = set()
3644        for dependency in project.dependencies:
3645            dependency_spec = dependency.spec
3646            should_skip_dep = False
3647            if project.spec["toolset"] == "target":
3648                if dependency_spec["toolset"] == "host":
3649                    if dependency_spec["type"] == "static_library":
3650                        should_skip_dep = True
3651            if dependency.name.startswith("run_"):
3652                should_skip_dep = False
3653            if should_skip_dep:
3654                continue
3655
3656            canonical_name = dependency.name.replace("_host", "")
3657            added_dependency_set.add(canonical_name)
3658            guid = dependency.guid
3659            project_dir = os.path.split(project.path)[0]
3660            relative_path = gyp.common.RelativePath(dependency.path, project_dir)
3661            project_ref = [
3662                "ProjectReference",
3663                {"Include": relative_path},
3664                ["Project", guid],
3665                ["ReferenceOutputAssembly", "false"],
3666            ]
3667            for config in dependency.spec.get("configurations", {}).values():
3668                if config.get("msvs_use_library_dependency_inputs", 0):
3669                    project_ref.append(["UseLibraryDependencyInputs", "true"])
3670                    break
3671                # If it's disabled in any config, turn it off in the reference.
3672                if config.get("msvs_2010_disable_uldi_when_referenced", 0):
3673                    project_ref.append(["UseLibraryDependencyInputs", "false"])
3674                    break
3675            group.append(project_ref)
3676        references.append(group)
3677    return references
3678
3679
3680def _GenerateMSBuildProject(project, options, version, generator_flags, spec):
3681    spec = project.spec
3682    configurations = spec["configurations"]
3683    toolset = spec["toolset"]
3684    project_dir, project_file_name = os.path.split(project.path)
3685    gyp.common.EnsureDirExists(project.path)
3686    # Prepare list of sources and excluded sources.
3687
3688    gyp_file = os.path.split(project.build_file)[1]
3689    sources, excluded_sources = _PrepareListOfSources(spec, generator_flags, gyp_file)
3690    # Add rules.
3691    actions_to_add = {}
3692    props_files_of_rules = set()
3693    targets_files_of_rules = set()
3694    rule_dependencies = set()
3695    extension_to_rule_name = {}
3696    list_excluded = generator_flags.get("msvs_list_excluded_files", True)
3697    platforms = _GetUniquePlatforms(spec)
3698
3699    # Don't generate rules if we are using an external builder like ninja.
3700    if not spec.get("msvs_external_builder"):
3701        _GenerateRulesForMSBuild(
3702            project_dir,
3703            options,
3704            spec,
3705            sources,
3706            excluded_sources,
3707            props_files_of_rules,
3708            targets_files_of_rules,
3709            actions_to_add,
3710            rule_dependencies,
3711            extension_to_rule_name,
3712        )
3713    else:
3714        rules = spec.get("rules", [])
3715        _AdjustSourcesForRules(rules, sources, excluded_sources, True)
3716
3717    sources, excluded_sources, excluded_idl = _AdjustSourcesAndConvertToFilterHierarchy(
3718        spec, options, project_dir, sources, excluded_sources, list_excluded, version
3719    )
3720
3721    # Don't add actions if we are using an external builder like ninja.
3722    if not spec.get("msvs_external_builder"):
3723        _AddActions(actions_to_add, spec, project.build_file)
3724        _AddCopies(actions_to_add, spec)
3725
3726        # NOTE: this stanza must appear after all actions have been decided.
3727        # Don't excluded sources with actions attached, or they won't run.
3728        excluded_sources = _FilterActionsFromExcluded(excluded_sources, actions_to_add)
3729
3730    exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
3731    actions_spec, sources_handled_by_action = _GenerateActionsForMSBuild(
3732        spec, actions_to_add
3733    )
3734
3735    _GenerateMSBuildFiltersFile(
3736        project.path + ".filters",
3737        sources,
3738        rule_dependencies,
3739        extension_to_rule_name,
3740        platforms,
3741        toolset,
3742    )
3743    missing_sources = _VerifySourcesExist(sources, project_dir)
3744
3745    for configuration in configurations.values():
3746        _FinalizeMSBuildSettings(spec, configuration)
3747
3748    # Add attributes to root element
3749
3750    import_default_section = [
3751        ["Import", {"Project": r"$(VCTargetsPath)\Microsoft.Cpp.Default.props"}]
3752    ]
3753    import_cpp_props_section = [
3754        ["Import", {"Project": r"$(VCTargetsPath)\Microsoft.Cpp.props"}]
3755    ]
3756    import_cpp_targets_section = [
3757        ["Import", {"Project": r"$(VCTargetsPath)\Microsoft.Cpp.targets"}]
3758    ]
3759    import_masm_props_section = [
3760        ["Import", {"Project": r"$(VCTargetsPath)\BuildCustomizations\masm.props"}]
3761    ]
3762    import_masm_targets_section = [
3763        ["Import", {"Project": r"$(VCTargetsPath)\BuildCustomizations\masm.targets"}]
3764    ]
3765    import_marmasm_props_section = [
3766        ["Import", {"Project": r"$(VCTargetsPath)\BuildCustomizations\marmasm.props"}]
3767    ]
3768    import_marmasm_targets_section = [
3769        ["Import", {"Project": r"$(VCTargetsPath)\BuildCustomizations\marmasm.targets"}]
3770    ]
3771    macro_section = [["PropertyGroup", {"Label": "UserMacros"}]]
3772
3773    content = [
3774        "Project",
3775        {
3776            "xmlns": "http://schemas.microsoft.com/developer/msbuild/2003",
3777            "ToolsVersion": version.ProjectVersion(),
3778            "DefaultTargets": "Build",
3779        },
3780    ]
3781
3782    content += _GetMSBuildProjectConfigurations(configurations, spec)
3783    content += _GetMSBuildGlobalProperties(
3784        spec, version, project.guid, project_file_name
3785    )
3786    content += import_default_section
3787    content += _GetMSBuildConfigurationDetails(spec, project.build_file)
3788    if spec.get("msvs_enable_winphone"):
3789        content += _GetMSBuildLocalProperties("v120_wp81")
3790    else:
3791        content += _GetMSBuildLocalProperties(project.msbuild_toolset)
3792    content += import_cpp_props_section
3793    content += import_masm_props_section
3794    if "arm64" in platforms and toolset == "target":
3795        content += import_marmasm_props_section
3796    content += _GetMSBuildExtensions(props_files_of_rules)
3797    content += _GetMSBuildPropertySheets(configurations, spec)
3798    content += macro_section
3799    content += _GetMSBuildConfigurationGlobalProperties(
3800        spec, configurations, project.build_file
3801    )
3802    content += _GetMSBuildToolSettingsSections(spec, configurations)
3803    content += _GetMSBuildSources(
3804        spec,
3805        sources,
3806        exclusions,
3807        rule_dependencies,
3808        extension_to_rule_name,
3809        actions_spec,
3810        sources_handled_by_action,
3811        list_excluded,
3812    )
3813    content += _GetMSBuildProjectReferences(project)
3814    content += import_cpp_targets_section
3815    content += import_masm_targets_section
3816    if "arm64" in platforms and toolset == "target":
3817        content += import_marmasm_targets_section
3818    content += _GetMSBuildExtensionTargets(targets_files_of_rules)
3819
3820    if spec.get("msvs_external_builder"):
3821        content += _GetMSBuildExternalBuilderTargets(spec)
3822
3823    # TODO(jeanluc) File a bug to get rid of runas.  We had in MSVS:
3824    # has_run_as = _WriteMSVSUserFile(project.path, version, spec)
3825
3826    easy_xml.WriteXmlIfChanged(content, project.path, pretty=True, win32=True)
3827
3828    return missing_sources
3829
3830
3831def _GetMSBuildExternalBuilderTargets(spec):
3832    """Return a list of MSBuild targets for external builders.
3833
3834  The "Build" and "Clean" targets are always generated.  If the spec contains
3835  'msvs_external_builder_clcompile_cmd', then the "ClCompile" target will also
3836  be generated, to support building selected C/C++ files.
3837
3838  Arguments:
3839    spec: The gyp target spec.
3840  Returns:
3841    List of MSBuild 'Target' specs.
3842  """
3843    build_cmd = _BuildCommandLineForRuleRaw(
3844        spec, spec["msvs_external_builder_build_cmd"], False, False, False, False
3845    )
3846    build_target = ["Target", {"Name": "Build"}]
3847    build_target.append(["Exec", {"Command": build_cmd}])
3848
3849    clean_cmd = _BuildCommandLineForRuleRaw(
3850        spec, spec["msvs_external_builder_clean_cmd"], False, False, False, False
3851    )
3852    clean_target = ["Target", {"Name": "Clean"}]
3853    clean_target.append(["Exec", {"Command": clean_cmd}])
3854
3855    targets = [build_target, clean_target]
3856
3857    if spec.get("msvs_external_builder_clcompile_cmd"):
3858        clcompile_cmd = _BuildCommandLineForRuleRaw(
3859            spec,
3860            spec["msvs_external_builder_clcompile_cmd"],
3861            False,
3862            False,
3863            False,
3864            False,
3865        )
3866        clcompile_target = ["Target", {"Name": "ClCompile"}]
3867        clcompile_target.append(["Exec", {"Command": clcompile_cmd}])
3868        targets.append(clcompile_target)
3869
3870    return targets
3871
3872
3873def _GetMSBuildExtensions(props_files_of_rules):
3874    extensions = ["ImportGroup", {"Label": "ExtensionSettings"}]
3875    for props_file in props_files_of_rules:
3876        extensions.append(["Import", {"Project": props_file}])
3877    return [extensions]
3878
3879
3880def _GetMSBuildExtensionTargets(targets_files_of_rules):
3881    targets_node = ["ImportGroup", {"Label": "ExtensionTargets"}]
3882    for targets_file in sorted(targets_files_of_rules):
3883        targets_node.append(["Import", {"Project": targets_file}])
3884    return [targets_node]
3885
3886
3887def _GenerateActionsForMSBuild(spec, actions_to_add):
3888    """Add actions accumulated into an actions_to_add, merging as needed.
3889
3890  Arguments:
3891    spec: the target project dict
3892    actions_to_add: dictionary keyed on input name, which maps to a list of
3893        dicts describing the actions attached to that input file.
3894
3895  Returns:
3896    A pair of (action specification, the sources handled by this action).
3897  """
3898    sources_handled_by_action = OrderedSet()
3899    actions_spec = []
3900    for primary_input, actions in actions_to_add.items():
3901        if generator_supports_multiple_toolsets:
3902            primary_input = primary_input.replace(".exe", "_host.exe")
3903        inputs = OrderedSet()
3904        outputs = OrderedSet()
3905        descriptions = []
3906        commands = []
3907        for action in actions:
3908
3909            def fixup_host_exe(i):
3910                if "$(OutDir)" in i:
3911                    i = i.replace(".exe", "_host.exe")
3912                return i
3913
3914            if generator_supports_multiple_toolsets:
3915                action["inputs"] = [fixup_host_exe(i) for i in action["inputs"]]
3916            inputs.update(OrderedSet(action["inputs"]))
3917            outputs.update(OrderedSet(action["outputs"]))
3918            descriptions.append(action["description"])
3919            cmd = action["command"]
3920            if generator_supports_multiple_toolsets:
3921                cmd = cmd.replace(".exe", "_host.exe")
3922            # For most actions, add 'call' so that actions that invoke batch files
3923            # return and continue executing.  msbuild_use_call provides a way to
3924            # disable this but I have not seen any adverse effect from doing that
3925            # for everything.
3926            if action.get("msbuild_use_call", True):
3927                cmd = "call " + cmd
3928            commands.append(cmd)
3929        # Add the custom build action for one input file.
3930        description = ", and also ".join(descriptions)
3931
3932        # We can't join the commands simply with && because the command line will
3933        # get too long. See also _AddActions: cygwin's setup_env mustn't be called
3934        # for every invocation or the command that sets the PATH will grow too
3935        # long.
3936        command = "\r\n".join(
3937            [c + "\r\nif %errorlevel% neq 0 exit /b %errorlevel%" for c in commands]
3938        )
3939        _AddMSBuildAction(
3940            spec,
3941            primary_input,
3942            inputs,
3943            outputs,
3944            command,
3945            description,
3946            sources_handled_by_action,
3947            actions_spec,
3948        )
3949    return actions_spec, sources_handled_by_action
3950
3951
3952def _AddMSBuildAction(
3953    spec,
3954    primary_input,
3955    inputs,
3956    outputs,
3957    cmd,
3958    description,
3959    sources_handled_by_action,
3960    actions_spec,
3961):
3962    command = MSVSSettings.ConvertVCMacrosToMSBuild(cmd)
3963    primary_input = _FixPath(primary_input)
3964    inputs_array = _FixPaths(inputs)
3965    outputs_array = _FixPaths(outputs)
3966    additional_inputs = ";".join([i for i in inputs_array if i != primary_input])
3967    outputs = ";".join(outputs_array)
3968    sources_handled_by_action.add(primary_input)
3969    action_spec = ["CustomBuild", {"Include": primary_input}]
3970    action_spec.extend(
3971        # TODO(jeanluc) 'Document' for all or just if as_sources?
3972        [
3973            ["FileType", "Document"],
3974            ["Command", command],
3975            ["Message", description],
3976            ["Outputs", outputs],
3977        ]
3978    )
3979    if additional_inputs:
3980        action_spec.append(["AdditionalInputs", additional_inputs])
3981    actions_spec.append(action_spec)
3982