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