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