"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs.py" (8 Mar 2017, 131038 Bytes) of archive /windows/misc/atom-windows.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

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