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