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