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