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