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