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