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