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