1 # Copyright (c) 2012 Google Inc. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
15 import gyp.easy_xml as easy_xml
16 import gyp.MSVSNew as MSVSNew
17 import gyp.MSVSProject as MSVSProject
18 import gyp.MSVSSettings as MSVSSettings
19 import gyp.MSVSToolFile as MSVSToolFile
20 import gyp.MSVSUserFile as MSVSUserFile
21 import gyp.MSVSUtil as MSVSUtil
22 import gyp.MSVSVersion as MSVSVersion
23 from gyp.common import GypError
25 # TODO: Remove once bots are on 2.7, http://crbug.com/241769
26 def _import_OrderedDict():
29 return collections.OrderedDict
30 except AttributeError:
31 import gyp.ordered_dict
32 return gyp.ordered_dict.OrderedDict
33 OrderedDict = _import_OrderedDict()
36 # Regular expression for validating Visual Studio GUIDs. If the GUID
37 # contains lowercase hex letters, MSVS will be fine. However,
38 # IncrediBuild BuildConsole will parse the solution file, but then
39 # silently skip building the target causing hard to track down errors.
40 # Note that this only happens with the BuildConsole, and does not occur
41 # if IncrediBuild is executed from inside Visual Studio. This regex
42 # validates that the string looks like a GUID with all uppercase hex
44 VALID_MSVS_GUID_CHARS = re.compile('^[A-F0-9\-]+$')
47 generator_default_variables = {
48 'EXECUTABLE_PREFIX': '',
49 'EXECUTABLE_SUFFIX': '.exe',
50 'STATIC_LIB_PREFIX': '',
51 'SHARED_LIB_PREFIX': '',
52 'STATIC_LIB_SUFFIX': '.lib',
53 'SHARED_LIB_SUFFIX': '.dll',
54 'INTERMEDIATE_DIR': '$(IntDir)',
55 'SHARED_INTERMEDIATE_DIR': '$(OutDir)obj/global_intermediate',
57 'PRODUCT_DIR': '$(OutDir)',
58 'LIB_DIR': '$(OutDir)lib',
59 'RULE_INPUT_ROOT': '$(InputName)',
60 'RULE_INPUT_DIRNAME': '$(InputDir)',
61 'RULE_INPUT_EXT': '$(InputExt)',
62 'RULE_INPUT_NAME': '$(InputFileName)',
63 'RULE_INPUT_PATH': '$(InputPath)',
64 'CONFIGURATION_NAME': '$(ConfigurationName)',
68 # The msvs specific sections that hold paths
69 generator_additional_path_sections = [
75 generator_additional_non_configuration_keys = [
80 'msvs_external_builder',
81 'msvs_external_builder_out_dir',
82 'msvs_external_builder_build_cmd',
83 'msvs_external_builder_clean_cmd',
87 # List of precompiled header related keys.
89 'msvs_precompiled_header',
90 'msvs_precompiled_source',
94 cached_username = None
100 # Based on http://code.activestate.com/recipes/576694/.
101 class OrderedSet(collections.MutableSet):
102 def __init__(self, iterable=None):
104 end += [None, end, end] # sentinel node for doubly linked list
105 self.map = {} # key --> [key, prev, next]
106 if iterable is not None:
112 def discard(self, key):
114 key, prev, next = self.map.pop(key)
118 def __contains__(self, key):
119 return key in self.map
122 if key not in self.map:
125 curr[2] = end[1] = self.map[key] = [key, curr, end]
127 def update(self, iterable):
135 while curr is not end:
140 # TODO(gspencer): Switch the os.environ calls to be
141 # win32api.GetDomainName() and win32api.GetUserName() once the
142 # python version in depot_tools has been updated to work on Vista
144 def _GetDomainAndUserName():
145 if sys.platform not in ('win32', 'cygwin'):
146 return ('DOMAIN', 'USERNAME')
147 global cached_username
149 if not cached_domain or not cached_username:
150 domain = os.environ.get('USERDOMAIN')
151 username = os.environ.get('USERNAME')
152 if not domain or not username:
153 call = subprocess.Popen(['net', 'config', 'Workstation'],
154 stdout=subprocess.PIPE)
155 config = call.communicate()[0]
156 username_re = re.compile('^User name\s+(\S+)', re.MULTILINE)
157 username_match = username_re.search(config)
159 username = username_match.group(1)
160 domain_re = re.compile('^Logon domain\s+(\S+)', re.MULTILINE)
161 domain_match = domain_re.search(config)
163 domain = domain_match.group(1)
164 cached_domain = domain
165 cached_username = username
166 return (cached_domain, cached_username)
168 fixpath_prefix = None
171 def _NormalizedSource(source):
172 """Normalize the path.
174 But not if that gets rid of a variable, as this may expand to something
175 larger than one directory.
178 source: The path to be normalize.d
183 normalized = os.path.normpath(source)
184 if source.count('$') == normalized.count('$'):
190 """Convert paths to a form that will make sense in a vcproj file.
193 path: The path to convert, may contain / etc.
195 The path with all slashes made into backslashes.
197 if fixpath_prefix and path and not os.path.isabs(path) and not path[0] == '$':
198 path = os.path.join(fixpath_prefix, path)
199 path = path.replace('/', '\\')
200 path = _NormalizedSource(path)
201 if path and path[-1] == '\\':
206 def _FixPaths(paths):
207 """Fix each of the paths of the list."""
208 return [_FixPath(i) for i in paths]
211 def _ConvertSourcesToFilterHierarchy(sources, prefix=None, excluded=None,
213 """Converts a list split source file paths into a vcproj folder hierarchy.
216 sources: A list of source file paths split.
217 prefix: A list of source file path layers meant to apply to each of sources.
218 excluded: A set of excluded files.
221 A hierarchy of filenames and MSVSProject.Filter objects that matches the
222 layout of the source tree.
224 _ConvertSourcesToFilterHierarchy([['a', 'bob1.c'], ['b', 'bob2.c']],
227 [MSVSProject.Filter('a', contents=['joe\\a\\bob1.c']),
228 MSVSProject.Filter('b', contents=['joe\\b\\bob2.c'])]
230 if not prefix: prefix = []
233 # Gather files into the final result, excluded, or folders.
236 filename = _NormalizedSource('\\'.join(prefix + s))
237 if filename in excluded:
238 excluded_result.append(filename)
240 result.append(filename)
242 contents = _ConvertSourcesToFilterHierarchy([s[1:]], prefix + [s[0]],
244 list_excluded=list_excluded)
245 contents = MSVSProject.Filter(s[0], contents=contents)
246 result.append(contents)
247 # Add a folder for excluded files.
248 if excluded_result and list_excluded:
249 excluded_folder = MSVSProject.Filter('_excluded_files',
250 contents=excluded_result)
251 result.append(excluded_folder)
255 def _ToolAppend(tools, tool_name, setting, value, only_if_unset=False):
257 _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset)
260 def _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset=False):
261 # TODO(bradnelson): ugly hack, fix this more generally!!!
262 if 'Directories' in setting or 'Dependencies' in setting:
263 if type(value) == str:
264 value = value.replace('/', '\\')
266 value = [i.replace('/', '\\') for i in value]
267 if not tools.get(tool_name):
268 tools[tool_name] = dict()
269 tool = tools[tool_name]
270 if tool.get(setting):
271 if only_if_unset: return
272 if type(tool[setting]) == list and type(value) == list:
273 tool[setting] += value
276 'Appending "%s" to a non-list setting "%s" for tool "%s" is '
277 'not allowed, previous value: %s' % (
278 value, setting, tool_name, str(tool[setting])))
280 tool[setting] = value
283 def _ConfigPlatform(config_data):
284 return config_data.get('msvs_configuration_platform', 'Win32')
287 def _ConfigBaseName(config_name, platform_name):
288 if config_name.endswith('_' + platform_name):
289 return config_name[0:-len(platform_name) - 1]
294 def _ConfigFullName(config_name, config_data):
295 platform_name = _ConfigPlatform(config_data)
296 return '%s|%s' % (_ConfigBaseName(config_name, platform_name), platform_name)
299 def _BuildCommandLineForRuleRaw(spec, cmd, cygwin_shell, has_input_path,
300 quote_cmd, do_setup_env):
302 if [x for x in cmd if '$(InputDir)' in x]:
303 input_dir_preamble = (
304 'set INPUTDIR=$(InputDir)\n'
305 'set INPUTDIR=%INPUTDIR:$(ProjectDir)=%\n'
306 'set INPUTDIR=%INPUTDIR:~0,-1%\n'
309 input_dir_preamble = ''
312 # Find path to cygwin.
313 cygwin_dir = _FixPath(spec.get('msvs_cygwin_dirs', ['.'])[0])
316 direct_cmd = [i.replace('$(IntDir)',
317 '`cygpath -m "${INTDIR}"`') for i in direct_cmd]
318 direct_cmd = [i.replace('$(OutDir)',
319 '`cygpath -m "${OUTDIR}"`') for i in direct_cmd]
320 direct_cmd = [i.replace('$(InputDir)',
321 '`cygpath -m "${INPUTDIR}"`') for i in direct_cmd]
323 direct_cmd = [i.replace('$(InputPath)',
324 '`cygpath -m "${INPUTPATH}"`')
326 direct_cmd = ['\\"%s\\"' % i.replace('"', '\\\\\\"') for i in direct_cmd]
327 # direct_cmd = gyp.common.EncodePOSIXShellList(direct_cmd)
328 direct_cmd = ' '.join(direct_cmd)
329 # TODO(quote): regularize quoting path names throughout the module
332 cmd += 'call "$(ProjectDir)%(cygwin_dir)s\\setup_env.bat" && '
333 cmd += 'set CYGWIN=nontsec&& '
334 if direct_cmd.find('NUMBER_OF_PROCESSORS') >= 0:
335 cmd += 'set /a NUMBER_OF_PROCESSORS_PLUS_1=%%NUMBER_OF_PROCESSORS%%+1&& '
336 if direct_cmd.find('INTDIR') >= 0:
337 cmd += 'set INTDIR=$(IntDir)&& '
338 if direct_cmd.find('OUTDIR') >= 0:
339 cmd += 'set OUTDIR=$(OutDir)&& '
340 if has_input_path and direct_cmd.find('INPUTPATH') >= 0:
341 cmd += 'set INPUTPATH=$(InputPath) && '
342 cmd += 'bash -c "%(cmd)s"'
343 cmd = cmd % {'cygwin_dir': cygwin_dir,
345 return input_dir_preamble + cmd
347 # Convert cat --> type to mimic unix.
351 command = [cmd[0].replace('/', '\\')]
352 # Add call before command to ensure that commands can be tied together one
353 # after the other without aborting in Incredibuild, since IB makes a bat
354 # file out of the raw command string, and some commands (like python) are
355 # actually batch files themselves.
356 command.insert(0, 'call')
358 # TODO(quote): This is a really ugly heuristic, and will miss path fixing
359 # for arguments like "--arg=path" or "/opt:path".
360 # If the argument starts with a slash or dash, it's probably a command line
362 arguments = [i if (i[:1] in "/-") else _FixPath(i) for i in cmd[1:]]
363 arguments = [i.replace('$(InputDir)', '%INPUTDIR%') for i in arguments]
364 arguments = [MSVSSettings.FixVCMacroSlashes(i) for i in arguments]
366 # Support a mode for using cmd directly.
367 # Convert any paths to native form (first element is used directly).
368 # TODO(quote): regularize quoting path names throughout the module
369 arguments = ['"%s"' % i for i in arguments]
370 # Collapse into a single command.
371 return input_dir_preamble + ' '.join(command + arguments)
374 def _BuildCommandLineForRule(spec, rule, has_input_path, do_setup_env):
375 # Currently this weird argument munging is used to duplicate the way a
376 # python script would need to be run as part of the chrome tree.
377 # Eventually we should add some sort of rule_default option to set this
378 # per project. For now the behavior chrome needs is the default.
379 mcs = rule.get('msvs_cygwin_shell')
381 mcs = int(spec.get('msvs_cygwin_shell', 1))
382 elif isinstance(mcs, str):
384 quote_cmd = int(rule.get('msvs_quote_cmd', 1))
385 return _BuildCommandLineForRuleRaw(spec, rule['action'], mcs, has_input_path,
386 quote_cmd, do_setup_env=do_setup_env)
389 def _AddActionStep(actions_dict, inputs, outputs, description, command):
390 """Merge action into an existing list of actions.
392 Care must be taken so that actions which have overlapping inputs either don't
393 get assigned to the same input, or get collapsed into one.
396 actions_dict: dictionary keyed on input name, which maps to a list of
397 dicts describing the actions attached to that input file.
398 inputs: list of inputs
399 outputs: list of outputs
400 description: description of the action
401 command: command line to execute
403 # Require there to be at least one input (call sites will ensure this).
409 'description': description,
413 # Pick where to stick this action.
414 # While less than optimal in terms of build time, attach them to the first
416 chosen_input = inputs[0]
419 if chosen_input not in actions_dict:
420 actions_dict[chosen_input] = []
421 actions_dict[chosen_input].append(action)
424 def _AddCustomBuildToolForMSVS(p, spec, primary_input,
425 inputs, outputs, description, cmd):
426 """Add a custom build tool to execute something.
429 p: the target project
430 spec: the target project dict
431 primary_input: input file to attach the build tool to
432 inputs: list of inputs
433 outputs: list of outputs
434 description: description of the action
435 cmd: command line to execute
437 inputs = _FixPaths(inputs)
438 outputs = _FixPaths(outputs)
439 tool = MSVSProject.Tool(
441 {'Description': description,
442 'AdditionalDependencies': ';'.join(inputs),
443 'Outputs': ';'.join(outputs),
446 # Add to the properties of primary input for each config.
447 for config_name, c_data in spec['configurations'].iteritems():
448 p.AddFileConfig(_FixPath(primary_input),
449 _ConfigFullName(config_name, c_data), tools=[tool])
452 def _AddAccumulatedActionsToMSVS(p, spec, actions_dict):
453 """Add actions accumulated into an actions_dict, merging as needed.
456 p: the target project
457 spec: the target project dict
458 actions_dict: dictionary keyed on input name, which maps to a list of
459 dicts describing the actions attached to that input file.
461 for primary_input in actions_dict:
462 inputs = OrderedSet()
463 outputs = OrderedSet()
466 for action in actions_dict[primary_input]:
467 inputs.update(OrderedSet(action['inputs']))
468 outputs.update(OrderedSet(action['outputs']))
469 descriptions.append(action['description'])
470 commands.append(action['command'])
471 # Add the custom build step for one input file.
472 description = ', and also '.join(descriptions)
473 command = '\r\n'.join(commands)
474 _AddCustomBuildToolForMSVS(p, spec,
475 primary_input=primary_input,
478 description=description,
482 def _RuleExpandPath(path, input_file):
483 """Given the input file to which a rule applied, string substitute a path.
486 path: a path to string expand
487 input_file: the file to which the rule applied.
489 The string substituted path.
491 path = path.replace('$(InputName)',
492 os.path.splitext(os.path.split(input_file)[1])[0])
493 path = path.replace('$(InputDir)', os.path.dirname(input_file))
494 path = path.replace('$(InputExt)',
495 os.path.splitext(os.path.split(input_file)[1])[1])
496 path = path.replace('$(InputFileName)', os.path.split(input_file)[1])
497 path = path.replace('$(InputPath)', input_file)
501 def _FindRuleTriggerFiles(rule, sources):
502 """Find the list of files which a particular rule applies to.
505 rule: the rule in question
506 sources: the set of all known source files for this project
508 The list of sources that trigger a particular rule.
510 return rule.get('rule_sources', [])
513 def _RuleInputsAndOutputs(rule, trigger_file):
514 """Find the inputs and outputs generated by a rule.
517 rule: the rule in question.
518 trigger_file: the main trigger for this rule.
520 The pair of (inputs, outputs) involved in this rule.
522 raw_inputs = _FixPaths(rule.get('inputs', []))
523 raw_outputs = _FixPaths(rule.get('outputs', []))
524 inputs = OrderedSet()
525 outputs = OrderedSet()
526 inputs.add(trigger_file)
528 inputs.add(_RuleExpandPath(i, trigger_file))
529 for o in raw_outputs:
530 outputs.add(_RuleExpandPath(o, trigger_file))
531 return (inputs, outputs)
534 def _GenerateNativeRulesForMSVS(p, rules, output_dir, spec, options):
535 """Generate a native rules file.
538 p: the target project
539 rules: the set of rules to include
540 output_dir: the directory in which the project/gyp resides
541 spec: the project dict
542 options: global generator options
544 rules_filename = '%s%s.rules' % (spec['target_name'],
546 rules_file = MSVSToolFile.Writer(os.path.join(output_dir, rules_filename),
550 rule_name = r['rule_name']
551 rule_ext = r['extension']
552 inputs = _FixPaths(r.get('inputs', []))
553 outputs = _FixPaths(r.get('outputs', []))
554 # Skip a rule with no action and no inputs.
555 if 'action' not in r and not r.get('rule_sources', []):
557 cmd = _BuildCommandLineForRule(spec, r, has_input_path=True,
559 rules_file.AddCustomBuildRule(name=rule_name,
560 description=r.get('message', rule_name),
561 extensions=[rule_ext],
562 additional_dependencies=inputs,
565 # Write out rules file.
566 rules_file.WriteIfChanged()
568 # Add rules file to project.
569 p.AddToolFile(rules_filename)
572 def _Cygwinify(path):
573 path = path.replace('$(OutDir)', '$(OutDirCygwin)')
574 path = path.replace('$(IntDir)', '$(IntDirCygwin)')
578 def _GenerateExternalRules(rules, output_dir, spec,
579 sources, options, actions_to_add):
580 """Generate an external makefile to do a set of rules.
583 rules: the list of rules to include
584 output_dir: path containing project and gyp files
585 spec: project specification data
586 sources: set of sources known
587 options: global generator options
588 actions_to_add: The list of actions we will add to.
590 filename = '%s_rules%s.mk' % (spec['target_name'], options.suffix)
591 mk_file = gyp.common.WriteOnDiff(os.path.join(output_dir, filename))
592 # Find cygwin style versions of some paths.
593 mk_file.write('OutDirCygwin:=$(shell cygpath -u "$(OutDir)")\n')
594 mk_file.write('IntDirCygwin:=$(shell cygpath -u "$(IntDir)")\n')
595 # Gather stuff needed to emit all: target.
596 all_inputs = OrderedSet()
597 all_outputs = OrderedSet()
598 all_output_dirs = OrderedSet()
601 trigger_files = _FindRuleTriggerFiles(rule, sources)
602 for tf in trigger_files:
603 inputs, outputs = _RuleInputsAndOutputs(rule, tf)
604 all_inputs.update(OrderedSet(inputs))
605 all_outputs.update(OrderedSet(outputs))
606 # Only use one target from each rule as the dependency for
607 # 'all' so we don't try to build each rule multiple times.
608 first_outputs.append(list(outputs)[0])
609 # Get the unique output directories for this rule.
610 output_dirs = [os.path.split(i)[0] for i in outputs]
611 for od in output_dirs:
612 all_output_dirs.add(od)
613 first_outputs_cyg = [_Cygwinify(i) for i in first_outputs]
614 # Write out all: target, including mkdir for each output directory.
615 mk_file.write('all: %s\n' % ' '.join(first_outputs_cyg))
616 for od in all_output_dirs:
618 mk_file.write('\tmkdir -p `cygpath -u "%s"`\n' % od)
620 # Define how each output is generated.
622 trigger_files = _FindRuleTriggerFiles(rule, sources)
623 for tf in trigger_files:
624 # Get all the inputs and outputs for this rule for this trigger file.
625 inputs, outputs = _RuleInputsAndOutputs(rule, tf)
626 inputs = [_Cygwinify(i) for i in inputs]
627 outputs = [_Cygwinify(i) for i in outputs]
628 # Prepare the command line for this rule.
629 cmd = [_RuleExpandPath(c, tf) for c in rule['action']]
630 cmd = ['"%s"' % i for i in cmd]
632 # Add it to the makefile.
633 mk_file.write('%s: %s\n' % (' '.join(outputs), ' '.join(inputs)))
634 mk_file.write('\t%s\n\n' % cmd)
638 # Add makefile to list of sources.
639 sources.add(filename)
640 # Add a build action to call makefile.
644 '-j', '${NUMBER_OF_PROCESSORS_PLUS_1}',
646 cmd = _BuildCommandLineForRuleRaw(spec, cmd, True, False, True, True)
647 # Insert makefile as 0'th input, so it gets the action attached there,
648 # as this is easier to understand from in the IDE.
649 all_inputs = list(all_inputs)
650 all_inputs.insert(0, filename)
651 _AddActionStep(actions_to_add,
652 inputs=_FixPaths(all_inputs),
653 outputs=_FixPaths(all_outputs),
654 description='Running external rules for %s' %
659 def _EscapeEnvironmentVariableExpansion(s):
660 """Escapes % characters.
662 Escapes any % characters so that Windows-style environment variable
663 expansions will leave them alone.
664 See http://connect.microsoft.com/VisualStudio/feedback/details/106127/cl-d-name-text-containing-percentage-characters-doesnt-compile
665 to understand why we have to do this.
668 s: The string to be escaped.
673 s = s.replace('%', '%%')
677 quote_replacer_regex = re.compile(r'(\\*)"')
680 def _EscapeCommandLineArgumentForMSVS(s):
681 """Escapes a Windows command-line argument.
683 So that the Win32 CommandLineToArgv function will turn the escaped result back
684 into the original string.
685 See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
686 ("Parsing C++ Command-Line Arguments") to understand why we have to do
690 s: the string to be escaped.
696 # For a literal quote, CommandLineToArgv requires an odd number of
697 # backslashes preceding it, and it produces half as many literal backslashes
698 # (rounded down). So we need to produce 2n+1 backslashes.
699 return 2 * match.group(1) + '\\"'
701 # Escape all quotes so that they are interpreted literally.
702 s = quote_replacer_regex.sub(_Replace, s)
703 # Now add unescaped quotes so that any whitespace is interpreted literally.
708 delimiters_replacer_regex = re.compile(r'(\\*)([,;]+)')
711 def _EscapeVCProjCommandLineArgListItem(s):
712 """Escapes command line arguments for MSVS.
714 The VCProj format stores string lists in a single string using commas and
715 semi-colons as separators, which must be quoted if they are to be
716 interpreted literally. However, command-line arguments may already have
717 quotes, and the VCProj parser is ignorant of the backslash escaping
718 convention used by CommandLineToArgv, so the command-line quotes and the
719 VCProj quotes may not be the same quotes. So to store a general
720 command-line argument in a VCProj list, we need to parse the existing
721 quoting according to VCProj's convention and quote any delimiters that are
722 not already quoted by that convention. The quotes that we add will also be
723 seen by CommandLineToArgv, so if backslashes precede them then we also have
724 to escape those backslashes according to the CommandLineToArgv
728 s: the string to be escaped.
734 # For a non-literal quote, CommandLineToArgv requires an even number of
735 # backslashes preceding it, and it produces half as many literal
736 # backslashes. So we need to produce 2n backslashes.
737 return 2 * match.group(1) + '"' + match.group(2) + '"'
739 segments = s.split('"')
740 # The unquoted segments are at the even-numbered indices.
741 for i in range(0, len(segments), 2):
742 segments[i] = delimiters_replacer_regex.sub(_Replace, segments[i])
743 # Concatenate back into a single string
744 s = '"'.join(segments)
745 if len(segments) % 2 == 0:
746 # String ends while still quoted according to VCProj's convention. This
747 # means the delimiter and the next list item that follow this one in the
748 # .vcproj file will be misinterpreted as part of this item. There is nothing
749 # we can do about this. Adding an extra quote would correct the problem in
750 # the VCProj but cause the same problem on the final command-line. Moving
751 # the item to the end of the list does works, but that's only possible if
752 # there's only one such item. Let's just warn the user.
753 print >> sys.stderr, ('Warning: MSVS may misinterpret the odd number of ' +
758 def _EscapeCppDefineForMSVS(s):
759 """Escapes a CPP define so that it will reach the compiler unaltered."""
760 s = _EscapeEnvironmentVariableExpansion(s)
761 s = _EscapeCommandLineArgumentForMSVS(s)
762 s = _EscapeVCProjCommandLineArgListItem(s)
763 # cl.exe replaces literal # characters with = in preprocesor definitions for
764 # some reason. Octal-encode to work around that.
765 s = s.replace('#', '\\%03o' % ord('#'))
769 quote_replacer_regex2 = re.compile(r'(\\+)"')
772 def _EscapeCommandLineArgumentForMSBuild(s):
773 """Escapes a Windows command-line argument for use by MSBuild."""
776 return (len(match.group(1)) / 2 * 4) * '\\' + '\\"'
778 # Escape all quotes so that they are interpreted literally.
779 s = quote_replacer_regex2.sub(_Replace, s)
783 def _EscapeMSBuildSpecialCharacters(s):
784 escape_dictionary = {
793 result = ''.join([escape_dictionary.get(c, c) for c in s])
797 def _EscapeCppDefineForMSBuild(s):
798 """Escapes a CPP define so that it will reach the compiler unaltered."""
799 s = _EscapeEnvironmentVariableExpansion(s)
800 s = _EscapeCommandLineArgumentForMSBuild(s)
801 s = _EscapeMSBuildSpecialCharacters(s)
802 # cl.exe replaces literal # characters with = in preprocesor definitions for
803 # some reason. Octal-encode to work around that.
804 s = s.replace('#', '\\%03o' % ord('#'))
808 def _GenerateRulesForMSVS(p, output_dir, options, spec,
809 sources, excluded_sources,
811 """Generate all the rules for a particular project.
815 output_dir: directory to emit rules to
816 options: global options passed to the generator
817 spec: the specification for this project
818 sources: the set of all known source files in this project
819 excluded_sources: the set of sources excluded from normal processing
820 actions_to_add: deferred list of actions to add in
822 rules = spec.get('rules', [])
823 rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
824 rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
826 # Handle rules that use a native rules file.
828 _GenerateNativeRulesForMSVS(p, rules_native, output_dir, spec, options)
830 # Handle external rules (non-native rules).
832 _GenerateExternalRules(rules_external, output_dir, spec,
833 sources, options, actions_to_add)
834 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
837 def _AdjustSourcesForRules(spec, rules, sources, excluded_sources):
838 # Add outputs generated by each rule (if applicable).
840 # Done if not processing outputs as sources.
841 if int(rule.get('process_outputs_as_sources', False)):
842 # Add in the outputs from this rule.
843 trigger_files = _FindRuleTriggerFiles(rule, sources)
844 for trigger_file in trigger_files:
845 inputs, outputs = _RuleInputsAndOutputs(rule, trigger_file)
846 inputs = OrderedSet(_FixPaths(inputs))
847 outputs = OrderedSet(_FixPaths(outputs))
848 inputs.remove(_FixPath(trigger_file))
849 sources.update(inputs)
850 if not spec.get('msvs_external_builder'):
851 excluded_sources.update(inputs)
852 sources.update(outputs)
855 def _FilterActionsFromExcluded(excluded_sources, actions_to_add):
856 """Take inputs with actions attached out of the list of exclusions.
859 excluded_sources: list of source files not to be built.
860 actions_to_add: dict of actions keyed on source file they're attached to.
862 excluded_sources with files that have actions attached removed.
864 must_keep = OrderedSet(_FixPaths(actions_to_add.keys()))
865 return [s for s in excluded_sources if s not in must_keep]
868 def _GetDefaultConfiguration(spec):
869 return spec['configurations'][spec['default_configuration']]
872 def _GetGuidOfProject(proj_path, spec):
873 """Get the guid for the project.
876 proj_path: Path of the vcproj or vcxproj file to generate.
877 spec: The target dictionary containing the properties of the target.
881 ValueError: if the specified GUID is invalid.
883 # Pluck out the default configuration.
884 default_config = _GetDefaultConfiguration(spec)
885 # Decide the guid of the project.
886 guid = default_config.get('msvs_guid')
888 if VALID_MSVS_GUID_CHARS.match(guid) is None:
889 raise ValueError('Invalid MSVS guid: "%s". Must match regex: "%s".' %
890 (guid, VALID_MSVS_GUID_CHARS.pattern))
892 guid = guid or MSVSNew.MakeGuid(proj_path)
896 def _GetMsbuildToolsetOfProject(proj_path, spec, version):
897 """Get the platform toolset for the project.
900 proj_path: Path of the vcproj or vcxproj file to generate.
901 spec: The target dictionary containing the properties of the target.
902 version: The MSVSVersion object.
904 the platform toolset string or None.
906 # Pluck out the default configuration.
907 default_config = _GetDefaultConfiguration(spec)
908 toolset = default_config.get('msbuild_toolset')
909 if not toolset and version.DefaultToolset():
910 toolset = version.DefaultToolset()
914 def _GenerateProject(project, options, version, generator_flags):
915 """Generates a vcproj file.
918 project: the MSVSProject object.
919 options: global generator options.
920 version: the MSVSVersion object.
921 generator_flags: dict of generator-specific flags.
923 A list of source files that cannot be found on disk.
925 default_config = _GetDefaultConfiguration(project.spec)
927 # Skip emitting anything if told to with msvs_existing_vcproj option.
928 if default_config.get('msvs_existing_vcproj'):
931 if version.UsesVcxproj():
932 return _GenerateMSBuildProject(project, options, version, generator_flags)
934 return _GenerateMSVSProject(project, options, version, generator_flags)
937 def _GenerateMSVSProject(project, options, version, generator_flags):
938 """Generates a .vcproj file. It may create .rules and .user files too.
941 project: The project object we will generate the file for.
942 options: Global options passed to the generator.
943 version: The VisualStudioVersion object.
944 generator_flags: dict of generator-specific flags.
947 gyp.common.EnsureDirExists(project.path)
949 platforms = _GetUniquePlatforms(spec)
950 p = MSVSProject.Writer(project.path, version, spec['target_name'],
951 project.guid, platforms)
953 # Get directory project file is in.
954 project_dir = os.path.split(project.path)[0]
955 gyp_path = _NormalizedSource(project.build_file)
956 relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
958 config_type = _GetMSVSConfigurationType(spec, project.build_file)
959 for config_name, config in spec['configurations'].iteritems():
960 _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config)
962 # Prepare list of sources and excluded sources.
963 gyp_file = os.path.split(project.build_file)[1]
964 sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
969 _GenerateRulesForMSVS(p, project_dir, options, spec,
970 sources, excluded_sources,
972 list_excluded = generator_flags.get('msvs_list_excluded_files', True)
973 sources, excluded_sources, excluded_idl = (
974 _AdjustSourcesAndConvertToFilterHierarchy(
975 spec, options, project_dir, sources, excluded_sources, list_excluded))
978 missing_sources = _VerifySourcesExist(sources, project_dir)
981 _AddToolFilesToMSVS(p, spec)
982 _HandlePreCompiledHeaders(p, sources, spec)
983 _AddActions(actions_to_add, spec, relative_path_of_gyp_file)
984 _AddCopies(actions_to_add, spec)
985 _WriteMSVSUserFile(project.path, version, spec)
987 # NOTE: this stanza must appear after all actions have been decided.
988 # Don't excluded sources with actions attached, or they won't run.
989 excluded_sources = _FilterActionsFromExcluded(
990 excluded_sources, actions_to_add)
991 _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
993 _AddAccumulatedActionsToMSVS(p, spec, actions_to_add)
998 return missing_sources
1001 def _GetUniquePlatforms(spec):
1002 """Returns the list of unique platforms for this spec, e.g ['win32', ...].
1005 spec: The target dictionary containing the properties of the target.
1007 The MSVSUserFile object created.
1009 # Gather list of unique platforms.
1010 platforms = OrderedSet()
1011 for configuration in spec['configurations']:
1012 platforms.add(_ConfigPlatform(spec['configurations'][configuration]))
1013 platforms = list(platforms)
1017 def _CreateMSVSUserFile(proj_path, version, spec):
1018 """Generates a .user file for the user running this Gyp program.
1021 proj_path: The path of the project file being created. The .user file
1022 shares the same path (with an appropriate suffix).
1023 version: The VisualStudioVersion object.
1024 spec: The target dictionary containing the properties of the target.
1026 The MSVSUserFile object created.
1028 (domain, username) = _GetDomainAndUserName()
1029 vcuser_filename = '.'.join([proj_path, domain, username, 'user'])
1030 user_file = MSVSUserFile.Writer(vcuser_filename, version,
1031 spec['target_name'])
1035 def _GetMSVSConfigurationType(spec, build_file):
1036 """Returns the configuration type for this project.
1038 It's a number defined by Microsoft. May raise an exception.
1041 spec: The target dictionary containing the properties of the target.
1042 build_file: The path of the gyp file.
1044 An integer, the configuration type.
1048 'executable': '1', # .exe
1049 'shared_library': '2', # .dll
1050 'loadable_module': '2', # .dll
1051 'static_library': '4', # .lib
1052 'none': '10', # Utility type
1055 if spec.get('type'):
1056 raise GypError('Target type %s is not a valid target type for '
1057 'target %s in %s.' %
1058 (spec['type'], spec['target_name'], build_file))
1060 raise GypError('Missing type field for target %s in %s.' %
1061 (spec['target_name'], build_file))
1065 def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config):
1066 """Adds a configuration to the MSVS project.
1068 Many settings in a vcproj file are specific to a configuration. This
1069 function the main part of the vcproj file that's configuration specific.
1072 p: The target project being generated.
1073 spec: The target dictionary containing the properties of the target.
1074 config_type: The configuration type, a number as defined by Microsoft.
1075 config_name: The name of the configuration.
1076 config: The dictionary that defines the special processing to be done
1077 for this configuration.
1079 # Get the information for this configuration
1080 include_dirs, resource_include_dirs = _GetIncludeDirs(config)
1081 libraries = _GetLibraries(spec)
1082 library_dirs = _GetLibraryDirs(config)
1083 out_file, vc_tool, _ = _GetOutputFilePathAndTool(spec, msbuild=False)
1084 defines = _GetDefines(config)
1085 defines = [_EscapeCppDefineForMSVS(d) for d in defines]
1086 disabled_warnings = _GetDisabledWarnings(config)
1087 prebuild = config.get('msvs_prebuild')
1088 postbuild = config.get('msvs_postbuild')
1089 def_file = _GetModuleDefinition(spec)
1090 precompiled_header = config.get('msvs_precompiled_header')
1092 # Prepare the list of tools as a dictionary.
1094 # Add in user specified msvs_settings.
1095 msvs_settings = config.get('msvs_settings', {})
1096 MSVSSettings.ValidateMSVSSettings(msvs_settings)
1098 # Prevent default library inheritance from the environment.
1099 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', ['$(NOINHERIT)'])
1101 for tool in msvs_settings:
1102 settings = config['msvs_settings'][tool]
1103 for setting in settings:
1104 _ToolAppend(tools, tool, setting, settings[setting])
1105 # Add the information to the appropriate tool
1106 _ToolAppend(tools, 'VCCLCompilerTool',
1107 'AdditionalIncludeDirectories', include_dirs)
1108 _ToolAppend(tools, 'VCResourceCompilerTool',
1109 'AdditionalIncludeDirectories', resource_include_dirs)
1111 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', libraries)
1112 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalLibraryDirectories',
1115 _ToolAppend(tools, vc_tool, 'OutputFile', out_file, only_if_unset=True)
1117 _ToolAppend(tools, 'VCCLCompilerTool', 'PreprocessorDefinitions', defines)
1118 _ToolAppend(tools, 'VCResourceCompilerTool', 'PreprocessorDefinitions',
1120 # Change program database directory to prevent collisions.
1121 _ToolAppend(tools, 'VCCLCompilerTool', 'ProgramDataBaseFileName',
1122 '$(IntDir)$(ProjectName)\\vc80.pdb', only_if_unset=True)
1123 # Add disabled warnings.
1124 _ToolAppend(tools, 'VCCLCompilerTool',
1125 'DisableSpecificWarnings', disabled_warnings)
1127 _ToolAppend(tools, 'VCPreBuildEventTool', 'CommandLine', prebuild)
1129 _ToolAppend(tools, 'VCPostBuildEventTool', 'CommandLine', postbuild)
1130 # Turn on precompiled headers if appropriate.
1131 if precompiled_header:
1132 precompiled_header = os.path.split(precompiled_header)[1]
1133 _ToolAppend(tools, 'VCCLCompilerTool', 'UsePrecompiledHeader', '2')
1134 _ToolAppend(tools, 'VCCLCompilerTool',
1135 'PrecompiledHeaderThrough', precompiled_header)
1136 _ToolAppend(tools, 'VCCLCompilerTool',
1137 'ForcedIncludeFiles', precompiled_header)
1138 # Loadable modules don't generate import libraries;
1139 # tell dependent projects to not expect one.
1140 if spec['type'] == 'loadable_module':
1141 _ToolAppend(tools, 'VCLinkerTool', 'IgnoreImportLibrary', 'true')
1142 # Set the module definition file if any.
1144 _ToolAppend(tools, 'VCLinkerTool', 'ModuleDefinitionFile', def_file)
1146 _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name)
1149 def _GetIncludeDirs(config):
1150 """Returns the list of directories to be used for #include directives.
1153 config: The dictionary that defines the special processing to be done
1154 for this configuration.
1156 The list of directory paths.
1158 # TODO(bradnelson): include_dirs should really be flexible enough not to
1159 # require this sort of thing.
1161 config.get('include_dirs', []) +
1162 config.get('msvs_system_include_dirs', []))
1163 resource_include_dirs = config.get('resource_include_dirs', include_dirs)
1164 include_dirs = _FixPaths(include_dirs)
1165 resource_include_dirs = _FixPaths(resource_include_dirs)
1166 return include_dirs, resource_include_dirs
1169 def _GetLibraryDirs(config):
1170 """Returns the list of directories to be used for library search paths.
1173 config: The dictionary that defines the special processing to be done
1174 for this configuration.
1176 The list of directory paths.
1179 library_dirs = config.get('library_dirs', [])
1180 library_dirs = _FixPaths(library_dirs)
1184 def _GetLibraries(spec):
1185 """Returns the list of libraries for this configuration.
1188 spec: The target dictionary containing the properties of the target.
1190 The list of directory paths.
1192 libraries = spec.get('libraries', [])
1193 # Strip out -l, as it is not used on windows (but is needed so we can pass
1194 # in libraries that are assumed to be in the default library path).
1195 # Also remove duplicate entries, leaving only the last duplicate, while
1197 found = OrderedSet()
1198 unique_libraries_list = []
1199 for entry in reversed(libraries):
1200 library = re.sub('^\-l', '', entry)
1201 if not os.path.splitext(library)[1]:
1203 if library not in found:
1205 unique_libraries_list.append(library)
1206 unique_libraries_list.reverse()
1207 return unique_libraries_list
1210 def _GetOutputFilePathAndTool(spec, msbuild):
1211 """Returns the path and tool to use for this target.
1213 Figures out the path of the file this spec will create and the name of
1214 the VC tool that will create it.
1217 spec: The target dictionary containing the properties of the target.
1219 A triple of (file path, name of the vc tool, name of the msbuild tool)
1221 # Select a name for the output file.
1226 'executable': ('VCLinkerTool', 'Link', '$(OutDir)', '.exe'),
1227 'shared_library': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
1228 'loadable_module': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
1229 'static_library': ('VCLibrarianTool', 'Lib', '$(OutDir)lib\\', '.lib'),
1231 output_file_props = output_file_map.get(spec['type'])
1232 if output_file_props and int(spec.get('msvs_auto_output_file', 1)):
1233 vc_tool, msbuild_tool, out_dir, suffix = output_file_props
1234 if spec.get('standalone_static_library', 0):
1235 out_dir = '$(OutDir)'
1236 out_dir = spec.get('product_dir', out_dir)
1237 product_extension = spec.get('product_extension')
1238 if product_extension:
1239 suffix = '.' + product_extension
1241 suffix = '$(TargetExt)'
1242 prefix = spec.get('product_prefix', '')
1243 product_name = spec.get('product_name', '$(ProjectName)')
1244 out_file = ntpath.join(out_dir, prefix + product_name + suffix)
1245 return out_file, vc_tool, msbuild_tool
1248 def _GetOutputTargetExt(spec):
1249 """Returns the extension for this target, including the dot
1251 If product_extension is specified, set target_extension to this to avoid
1252 MSB8012, returns None otherwise. Ignores any target_extension settings in
1256 spec: The target dictionary containing the properties of the target.
1258 A string with the extension, or None
1260 target_extension = spec.get('product_extension')
1261 if target_extension:
1262 return '.' + target_extension
1266 def _GetDefines(config):
1267 """Returns the list of preprocessor definitions for this configuation.
1270 config: The dictionary that defines the special processing to be done
1271 for this configuration.
1273 The list of preprocessor definitions.
1276 for d in config.get('defines', []):
1278 fd = '='.join([str(dpart) for dpart in d])
1285 def _GetDisabledWarnings(config):
1286 return [str(i) for i in config.get('msvs_disabled_warnings', [])]
1289 def _GetModuleDefinition(spec):
1291 if spec['type'] in ['shared_library', 'loadable_module', 'executable']:
1292 def_files = [s for s in spec.get('sources', []) if s.endswith('.def')]
1293 if len(def_files) == 1:
1294 def_file = _FixPath(def_files[0])
1297 'Multiple module definition files in one target, target %s lists '
1298 'multiple .def files: %s' % (
1299 spec['target_name'], ' '.join(def_files)))
1303 def _ConvertToolsToExpectedForm(tools):
1304 """Convert tools to a form expected by Visual Studio.
1307 tools: A dictionary of settings; the tool name is the key.
1309 A list of Tool objects.
1312 for tool, settings in tools.iteritems():
1313 # Collapse settings with lists.
1315 for setting, value in settings.iteritems():
1316 if type(value) == list:
1317 if ((tool == 'VCLinkerTool' and
1318 setting == 'AdditionalDependencies') or
1319 setting == 'AdditionalOptions'):
1320 settings_fixed[setting] = ' '.join(value)
1322 settings_fixed[setting] = ';'.join(value)
1324 settings_fixed[setting] = value
1326 tool_list.append(MSVSProject.Tool(tool, settings_fixed))
1330 def _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name):
1331 """Add to the project file the configuration specified by config.
1334 p: The target project being generated.
1335 spec: the target project dict.
1336 tools: A dictionary of settings; the tool name is the key.
1337 config: The dictionary that defines the special processing to be done
1338 for this configuration.
1339 config_type: The configuration type, a number as defined by Microsoft.
1340 config_name: The name of the configuration.
1342 attributes = _GetMSVSAttributes(spec, config, config_type)
1343 # Add in this configuration.
1344 tool_list = _ConvertToolsToExpectedForm(tools)
1345 p.AddConfig(_ConfigFullName(config_name, config),
1346 attrs=attributes, tools=tool_list)
1349 def _GetMSVSAttributes(spec, config, config_type):
1350 # Prepare configuration attributes.
1352 source_attrs = config.get('msvs_configuration_attributes', {})
1353 for a in source_attrs:
1354 prepared_attrs[a] = source_attrs[a]
1356 vsprops_dirs = config.get('msvs_props', [])
1357 vsprops_dirs = _FixPaths(vsprops_dirs)
1359 prepared_attrs['InheritedPropertySheets'] = ';'.join(vsprops_dirs)
1360 # Set configuration type.
1361 prepared_attrs['ConfigurationType'] = config_type
1362 output_dir = prepared_attrs.get('OutputDirectory',
1363 '$(SolutionDir)$(ConfigurationName)')
1364 prepared_attrs['OutputDirectory'] = _FixPath(output_dir) + '\\'
1365 if 'IntermediateDirectory' not in prepared_attrs:
1366 intermediate = '$(ConfigurationName)\\obj\\$(ProjectName)'
1367 prepared_attrs['IntermediateDirectory'] = _FixPath(intermediate) + '\\'
1369 intermediate = _FixPath(prepared_attrs['IntermediateDirectory']) + '\\'
1370 intermediate = MSVSSettings.FixVCMacroSlashes(intermediate)
1371 prepared_attrs['IntermediateDirectory'] = intermediate
1372 return prepared_attrs
1375 def _AddNormalizedSources(sources_set, sources_array):
1376 sources_set.update(_NormalizedSource(s) for s in sources_array)
1379 def _PrepareListOfSources(spec, generator_flags, gyp_file):
1380 """Prepare list of sources and excluded sources.
1382 Besides the sources specified directly in the spec, adds the gyp file so
1383 that a change to it will cause a re-compile. Also adds appropriate sources
1384 for actions and copies. Assumes later stage will un-exclude files which
1385 have custom build steps attached.
1388 spec: The target dictionary containing the properties of the target.
1389 gyp_file: The name of the gyp file.
1391 A pair of (list of sources, list of excluded sources).
1392 The sources will be relative to the gyp file.
1394 sources = OrderedSet()
1395 _AddNormalizedSources(sources, spec.get('sources', []))
1396 excluded_sources = OrderedSet()
1397 # Add in the gyp file.
1398 if not generator_flags.get('standalone'):
1399 sources.add(gyp_file)
1401 # Add in 'action' inputs and outputs.
1402 for a in spec.get('actions', []):
1403 inputs = a['inputs']
1404 inputs = [_NormalizedSource(i) for i in inputs]
1405 # Add all inputs to sources and excluded sources.
1406 inputs = OrderedSet(inputs)
1407 sources.update(inputs)
1408 if not spec.get('msvs_external_builder'):
1409 excluded_sources.update(inputs)
1410 if int(a.get('process_outputs_as_sources', False)):
1411 _AddNormalizedSources(sources, a.get('outputs', []))
1412 # Add in 'copies' inputs and outputs.
1413 for cpy in spec.get('copies', []):
1414 _AddNormalizedSources(sources, cpy.get('files', []))
1415 return (sources, excluded_sources)
1418 def _AdjustSourcesAndConvertToFilterHierarchy(
1419 spec, options, gyp_dir, sources, excluded_sources, list_excluded):
1420 """Adjusts the list of sources and excluded sources.
1422 Also converts the sets to lists.
1425 spec: The target dictionary containing the properties of the target.
1426 options: Global generator options.
1427 gyp_dir: The path to the gyp file being processed.
1428 sources: A set of sources to be included for this project.
1429 excluded_sources: A set of sources to be excluded for this project.
1431 A trio of (list of sources, list of excluded sources,
1432 path of excluded IDL file)
1434 # Exclude excluded sources coming into the generator.
1435 excluded_sources.update(OrderedSet(spec.get('sources_excluded', [])))
1436 # Add excluded sources into sources for good measure.
1437 sources.update(excluded_sources)
1438 # Convert to proper windows form.
1439 # NOTE: sources goes from being a set to a list here.
1440 # NOTE: excluded_sources goes from being a set to a list here.
1441 sources = _FixPaths(sources)
1442 # Convert to proper windows form.
1443 excluded_sources = _FixPaths(excluded_sources)
1445 excluded_idl = _IdlFilesHandledNonNatively(spec, sources)
1447 precompiled_related = _GetPrecompileRelatedFiles(spec)
1448 # Find the excluded ones, minus the precompiled header related ones.
1449 fully_excluded = [i for i in excluded_sources if i not in precompiled_related]
1451 # Convert to folders and the right slashes.
1452 sources = [i.split('\\') for i in sources]
1453 sources = _ConvertSourcesToFilterHierarchy(sources, excluded=fully_excluded,
1454 list_excluded=list_excluded)
1456 # Prune filters with a single child to flatten ugly directory structures
1457 # such as ../../src/modules/module1 etc.
1458 while len(sources) == 1 and isinstance(sources[0], MSVSProject.Filter):
1459 sources = sources[0].contents
1461 return sources, excluded_sources, excluded_idl
1464 def _IdlFilesHandledNonNatively(spec, sources):
1465 # If any non-native rules use 'idl' as an extension exclude idl files.
1466 # Gather a list here to use later.
1468 for rule in spec.get('rules', []):
1469 if rule['extension'] == 'idl' and int(rule.get('msvs_external_rule', 0)):
1473 excluded_idl = [i for i in sources if i.endswith('.idl')]
1479 def _GetPrecompileRelatedFiles(spec):
1480 # Gather a list of precompiled header related sources.
1481 precompiled_related = []
1482 for _, config in spec['configurations'].iteritems():
1483 for k in precomp_keys:
1486 precompiled_related.append(_FixPath(f))
1487 return precompiled_related
1490 def _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
1492 exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
1493 for file_name, excluded_configs in exclusions.iteritems():
1494 if (not list_excluded and
1495 len(excluded_configs) == len(spec['configurations'])):
1496 # If we're not listing excluded files, then they won't appear in the
1497 # project, so don't try to configure them to be excluded.
1500 for config_name, config in excluded_configs:
1501 p.AddFileConfig(file_name, _ConfigFullName(config_name, config),
1502 {'ExcludedFromBuild': 'true'})
1505 def _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl):
1507 # Exclude excluded sources from being built.
1508 for f in excluded_sources:
1509 excluded_configs = []
1510 for config_name, config in spec['configurations'].iteritems():
1511 precomped = [_FixPath(config.get(i, '')) for i in precomp_keys]
1512 # Don't do this for ones that are precompiled header related.
1513 if f not in precomped:
1514 excluded_configs.append((config_name, config))
1515 exclusions[f] = excluded_configs
1516 # If any non-native rules use 'idl' as an extension exclude idl files.
1518 for f in excluded_idl:
1519 excluded_configs = []
1520 for config_name, config in spec['configurations'].iteritems():
1521 excluded_configs.append((config_name, config))
1522 exclusions[f] = excluded_configs
1526 def _AddToolFilesToMSVS(p, spec):
1527 # Add in tool files (rules).
1528 tool_files = OrderedSet()
1529 for _, config in spec['configurations'].iteritems():
1530 for f in config.get('msvs_tool_files', []):
1532 for f in tool_files:
1536 def _HandlePreCompiledHeaders(p, sources, spec):
1537 # Pre-compiled header source stubs need a different compiler flag
1538 # (generate precompiled header) and any source file not of the same
1539 # kind (i.e. C vs. C++) as the precompiled header source stub needs
1540 # to have use of precompiled headers disabled.
1541 extensions_excluded_from_precompile = []
1542 for config_name, config in spec['configurations'].iteritems():
1543 source = config.get('msvs_precompiled_source')
1545 source = _FixPath(source)
1546 # UsePrecompiledHeader=1 for if using precompiled headers.
1547 tool = MSVSProject.Tool('VCCLCompilerTool',
1548 {'UsePrecompiledHeader': '1'})
1549 p.AddFileConfig(source, _ConfigFullName(config_name, config),
1551 basename, extension = os.path.splitext(source)
1552 if extension == '.c':
1553 extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
1555 extensions_excluded_from_precompile = ['.c']
1556 def DisableForSourceTree(source_tree):
1557 for source in source_tree:
1558 if isinstance(source, MSVSProject.Filter):
1559 DisableForSourceTree(source.contents)
1561 basename, extension = os.path.splitext(source)
1562 if extension in extensions_excluded_from_precompile:
1563 for config_name, config in spec['configurations'].iteritems():
1564 tool = MSVSProject.Tool('VCCLCompilerTool',
1565 {'UsePrecompiledHeader': '0',
1566 'ForcedIncludeFiles': '$(NOINHERIT)'})
1567 p.AddFileConfig(_FixPath(source),
1568 _ConfigFullName(config_name, config),
1570 # Do nothing if there was no precompiled source.
1571 if extensions_excluded_from_precompile:
1572 DisableForSourceTree(sources)
1575 def _AddActions(actions_to_add, spec, relative_path_of_gyp_file):
1577 actions = spec.get('actions', [])
1578 # Don't setup_env every time. When all the actions are run together in one
1579 # batch file in VS, the PATH will grow too long.
1580 # Membership in this set means that the cygwin environment has been set up,
1581 # and does not need to be set up again.
1582 have_setup_env = set()
1584 # Attach actions to the gyp file if nothing else is there.
1585 inputs = a.get('inputs') or [relative_path_of_gyp_file]
1586 attached_to = inputs[0]
1587 need_setup_env = attached_to not in have_setup_env
1588 cmd = _BuildCommandLineForRule(spec, a, has_input_path=False,
1589 do_setup_env=need_setup_env)
1590 have_setup_env.add(attached_to)
1592 _AddActionStep(actions_to_add,
1594 outputs=a.get('outputs', []),
1595 description=a.get('message', a['action_name']),
1599 def _WriteMSVSUserFile(project_path, version, spec):
1600 # Add run_as and test targets.
1601 if 'run_as' in spec:
1602 run_as = spec['run_as']
1603 action = run_as.get('action', [])
1604 environment = run_as.get('environment', [])
1605 working_directory = run_as.get('working_directory', '.')
1606 elif int(spec.get('test', 0)):
1607 action = ['$(TargetPath)', '--gtest_print_time']
1609 working_directory = '.'
1611 return # Nothing to add
1612 # Write out the user file.
1613 user_file = _CreateMSVSUserFile(project_path, version, spec)
1614 for config_name, c_data in spec['configurations'].iteritems():
1615 user_file.AddDebugSettings(_ConfigFullName(config_name, c_data),
1616 action, environment, working_directory)
1617 user_file.WriteIfChanged()
1620 def _AddCopies(actions_to_add, spec):
1621 copies = _GetCopies(spec)
1622 for inputs, outputs, cmd, description in copies:
1623 _AddActionStep(actions_to_add, inputs=inputs, outputs=outputs,
1624 description=description, command=cmd)
1627 def _GetCopies(spec):
1630 for cpy in spec.get('copies', []):
1631 for src in cpy.get('files', []):
1632 dst = os.path.join(cpy['destination'], os.path.basename(src))
1633 # _AddCustomBuildToolForMSVS() will call _FixPath() on the inputs and
1634 # outputs, so do the same for our generated command line.
1635 if src.endswith('/'):
1637 base_dir = posixpath.split(src_bare)[0]
1638 outer_dir = posixpath.split(src_bare)[1]
1639 cmd = 'cd "%s" && xcopy /e /f /y "%s" "%s\\%s\\"' % (
1640 _FixPath(base_dir), outer_dir, _FixPath(dst), outer_dir)
1641 copies.append(([src], ['dummy_copies', dst], cmd,
1642 'Copying %s to %s' % (src, dst)))
1644 cmd = 'mkdir "%s" 2>nul & set ERRORLEVEL=0 & copy /Y "%s" "%s"' % (
1645 _FixPath(cpy['destination']), _FixPath(src), _FixPath(dst))
1646 copies.append(([src], [dst], cmd, 'Copying %s to %s' % (src, dst)))
1650 def _GetPathDict(root, path):
1651 # |path| will eventually be empty (in the recursive calls) if it was initially
1652 # relative; otherwise it will eventually end up as '\', 'D:\', etc.
1653 if not path or path.endswith(os.sep):
1655 parent, folder = os.path.split(path)
1656 parent_dict = _GetPathDict(root, parent)
1657 if folder not in parent_dict:
1658 parent_dict[folder] = dict()
1659 return parent_dict[folder]
1662 def _DictsToFolders(base_path, bucket, flat):
1663 # Convert to folders recursively.
1665 for folder, contents in bucket.iteritems():
1666 if type(contents) == dict:
1667 folder_children = _DictsToFolders(os.path.join(base_path, folder),
1670 children += folder_children
1672 folder_children = MSVSNew.MSVSFolder(os.path.join(base_path, folder),
1673 name='(' + folder + ')',
1674 entries=folder_children)
1675 children.append(folder_children)
1677 children.append(contents)
1681 def _CollapseSingles(parent, node):
1682 # Recursively explorer the tree of dicts looking for projects which are
1683 # the sole item in a folder which has the same name as the project. Bring
1684 # such projects up one level.
1685 if (type(node) == dict and
1687 node.keys()[0] == parent + '.vcproj'):
1688 return node[node.keys()[0]]
1689 if type(node) != dict:
1692 node[child] = _CollapseSingles(child, node[child])
1696 def _GatherSolutionFolders(sln_projects, project_objects, flat):
1698 # Convert into a tree of dicts on path.
1699 for p in sln_projects:
1700 gyp_file, target = gyp.common.ParseQualifiedTarget(p)[0:2]
1701 gyp_dir = os.path.dirname(gyp_file)
1702 path_dict = _GetPathDict(root, gyp_dir)
1703 path_dict[target + '.vcproj'] = project_objects[p]
1704 # Walk down from the top until we hit a folder that has more than one entry.
1705 # In practice, this strips the top-level "src/" dir from the hierarchy in
1707 while len(root) == 1 and type(root[root.keys()[0]]) == dict:
1708 root = root[root.keys()[0]]
1710 root = _CollapseSingles('', root)
1711 # Merge buckets until everything is a root entry.
1712 return _DictsToFolders('', root, flat)
1715 def _GetPathOfProject(qualified_target, spec, options, msvs_version):
1716 default_config = _GetDefaultConfiguration(spec)
1717 proj_filename = default_config.get('msvs_existing_vcproj')
1718 if not proj_filename:
1719 proj_filename = (spec['target_name'] + options.suffix +
1720 msvs_version.ProjectExtension())
1722 build_file = gyp.common.BuildFile(qualified_target)
1723 proj_path = os.path.join(os.path.dirname(build_file), proj_filename)
1725 if options.generator_output:
1726 project_dir_path = os.path.dirname(os.path.abspath(proj_path))
1727 proj_path = os.path.join(options.generator_output, proj_path)
1728 fix_prefix = gyp.common.RelativePath(project_dir_path,
1729 os.path.dirname(proj_path))
1730 return proj_path, fix_prefix
1733 def _GetPlatformOverridesOfProject(spec):
1734 # Prepare a dict indicating which project configurations are used for which
1735 # solution configurations for this target.
1736 config_platform_overrides = {}
1737 for config_name, c in spec['configurations'].iteritems():
1738 config_fullname = _ConfigFullName(config_name, c)
1739 platform = c.get('msvs_target_platform', _ConfigPlatform(c))
1740 fixed_config_fullname = '%s|%s' % (
1741 _ConfigBaseName(config_name, _ConfigPlatform(c)), platform)
1742 config_platform_overrides[config_fullname] = fixed_config_fullname
1743 return config_platform_overrides
1746 def _CreateProjectObjects(target_list, target_dicts, options, msvs_version):
1747 """Create a MSVSProject object for the targets found in target list.
1750 target_list: the list of targets to generate project objects for.
1751 target_dicts: the dictionary of specifications.
1752 options: global generator options.
1753 msvs_version: the MSVSVersion object.
1755 A set of created projects, keyed by target.
1757 global fixpath_prefix
1758 # Generate each project.
1760 for qualified_target in target_list:
1761 spec = target_dicts[qualified_target]
1762 if spec['toolset'] != 'target':
1764 'Multiple toolsets not supported in msvs build (target %s)' %
1766 proj_path, fixpath_prefix = _GetPathOfProject(qualified_target, spec,
1767 options, msvs_version)
1768 guid = _GetGuidOfProject(proj_path, spec)
1769 overrides = _GetPlatformOverridesOfProject(spec)
1770 build_file = gyp.common.BuildFile(qualified_target)
1771 # Create object for this project.
1772 obj = MSVSNew.MSVSProject(
1774 name=spec['target_name'],
1777 build_file=build_file,
1778 config_platform_overrides=overrides,
1779 fixpath_prefix=fixpath_prefix)
1780 # Set project toolset if any (MS build only)
1781 if msvs_version.UsesVcxproj():
1782 obj.set_msbuild_toolset(
1783 _GetMsbuildToolsetOfProject(proj_path, spec, msvs_version))
1784 projects[qualified_target] = obj
1785 # Set all the dependencies, but not if we are using an external builder like
1787 for project in projects.values():
1788 if not project.spec.get('msvs_external_builder'):
1789 deps = project.spec.get('dependencies', [])
1790 deps = [projects[d] for d in deps]
1791 project.set_dependencies(deps)
1795 def _InitNinjaFlavor(options, target_list, target_dicts):
1796 """Initialize targets for the ninja flavor.
1798 This sets up the necessary variables in the targets to generate msvs projects
1799 that use ninja as an external builder. The variables in the spec are only set
1800 if they have not been set. This allows individual specs to override the
1801 default values initialized here.
1803 options: Options provided to the generator.
1804 target_list: List of target pairs: 'base/base.gyp:base'.
1805 target_dicts: Dict of target properties keyed on target pair.
1807 for qualified_target in target_list:
1808 spec = target_dicts[qualified_target]
1809 if spec.get('msvs_external_builder'):
1810 # The spec explicitly defined an external builder, so don't change it.
1813 path_to_ninja = spec.get('msvs_path_to_ninja', 'ninja.exe')
1815 spec['msvs_external_builder'] = 'ninja'
1816 if not spec.get('msvs_external_builder_out_dir'):
1817 spec['msvs_external_builder_out_dir'] = \
1818 options.depth + '/out/$(Configuration)'
1819 if not spec.get('msvs_external_builder_build_cmd'):
1820 spec['msvs_external_builder_build_cmd'] = [
1826 if not spec.get('msvs_external_builder_clean_cmd'):
1827 spec['msvs_external_builder_clean_cmd'] = [
1837 def CalculateVariables(default_variables, params):
1838 """Generated variables that require params to be known."""
1840 generator_flags = params.get('generator_flags', {})
1842 # Select project file format version (if unset, default to auto detecting).
1843 msvs_version = MSVSVersion.SelectVisualStudioVersion(
1844 generator_flags.get('msvs_version', 'auto'))
1845 # Stash msvs_version for later (so we don't have to probe the system twice).
1846 params['msvs_version'] = msvs_version
1848 # Set a variable so conditions can be based on msvs_version.
1849 default_variables['MSVS_VERSION'] = msvs_version.ShortName()
1851 # To determine processor word size on Windows, in addition to checking
1852 # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
1853 # process), it is also necessary to check PROCESSOR_ARCITEW6432 (which
1854 # contains the actual word size of the system when running thru WOW64).
1855 if (os.environ.get('PROCESSOR_ARCHITECTURE', '').find('64') >= 0 or
1856 os.environ.get('PROCESSOR_ARCHITEW6432', '').find('64') >= 0):
1857 default_variables['MSVS_OS_BITS'] = 64
1859 default_variables['MSVS_OS_BITS'] = 32
1861 if gyp.common.GetFlavor(params) == 'ninja':
1862 default_variables['SHARED_INTERMEDIATE_DIR'] = '$(OutDir)gen'
1865 def PerformBuild(data, configurations, params):
1866 options = params['options']
1867 msvs_version = params['msvs_version']
1868 devenv = os.path.join(msvs_version.path, 'Common7', 'IDE', 'devenv.com')
1870 for build_file, build_file_dict in data.iteritems():
1871 (build_file_root, build_file_ext) = os.path.splitext(build_file)
1872 if build_file_ext != '.gyp':
1874 sln_path = build_file_root + options.suffix + '.sln'
1875 if options.generator_output:
1876 sln_path = os.path.join(options.generator_output, sln_path)
1878 for config in configurations:
1879 arguments = [devenv, sln_path, '/Build', config]
1880 print 'Building [%s]: %s' % (config, arguments)
1881 rtn = subprocess.check_call(arguments)
1884 def GenerateOutput(target_list, target_dicts, data, params):
1885 """Generate .sln and .vcproj files.
1887 This is the entry point for this generator.
1889 target_list: List of target pairs: 'base/base.gyp:base'.
1890 target_dicts: Dict of target properties keyed on target pair.
1891 data: Dictionary containing per .gyp data.
1893 global fixpath_prefix
1895 options = params['options']
1897 # Get the project file format version back out of where we stashed it in
1898 # GeneratorCalculatedVariables.
1899 msvs_version = params['msvs_version']
1901 generator_flags = params.get('generator_flags', {})
1903 # Optionally shard targets marked with 'msvs_shard': SHARD_COUNT.
1904 (target_list, target_dicts) = MSVSUtil.ShardTargets(target_list, target_dicts)
1906 # Optionally use the large PDB workaround for targets marked with
1907 # 'msvs_large_pdb': 1.
1908 (target_list, target_dicts) = MSVSUtil.InsertLargePdbShims(
1909 target_list, target_dicts, generator_default_variables)
1911 # Optionally configure each spec to use ninja as the external builder.
1912 if params.get('flavor') == 'ninja':
1913 _InitNinjaFlavor(options, target_list, target_dicts)
1915 # Prepare the set of configurations.
1917 for qualified_target in target_list:
1918 spec = target_dicts[qualified_target]
1919 for config_name, config in spec['configurations'].iteritems():
1920 configs.add(_ConfigFullName(config_name, config))
1921 configs = list(configs)
1923 # Figure out all the projects that will be generated and their guids
1924 project_objects = _CreateProjectObjects(target_list, target_dicts, options,
1927 # Generate each project.
1928 missing_sources = []
1929 for project in project_objects.values():
1930 fixpath_prefix = project.fixpath_prefix
1931 missing_sources.extend(_GenerateProject(project, options, msvs_version,
1933 fixpath_prefix = None
1935 for build_file in data:
1936 # Validate build_file extension
1937 if not build_file.endswith('.gyp'):
1939 sln_path = os.path.splitext(build_file)[0] + options.suffix + '.sln'
1940 if options.generator_output:
1941 sln_path = os.path.join(options.generator_output, sln_path)
1942 # Get projects in the solution, and their dependents.
1943 sln_projects = gyp.common.BuildFileTargets(target_list, build_file)
1944 sln_projects += gyp.common.DeepDependencyTargets(target_dicts, sln_projects)
1945 # Create folder hierarchy.
1946 root_entries = _GatherSolutionFolders(
1947 sln_projects, project_objects, flat=msvs_version.FlatSolution())
1949 sln = MSVSNew.MSVSSolution(sln_path,
1950 entries=root_entries,
1952 websiteProperties=False,
1953 version=msvs_version)
1957 error_message = "Missing input files:\n" + \
1958 '\n'.join(set(missing_sources))
1959 if generator_flags.get('msvs_error_on_missing_sources', False):
1960 raise GypError(error_message)
1962 print >> sys.stdout, "Warning: " + error_message
1965 def _GenerateMSBuildFiltersFile(filters_path, source_files,
1966 extension_to_rule_name):
1967 """Generate the filters file.
1969 This file is used by Visual Studio to organize the presentation of source
1973 filters_path: The path of the file to be created.
1974 source_files: The hierarchical structure of all the sources.
1975 extension_to_rule_name: A dictionary mapping file extensions to rules.
1979 _AppendFiltersForMSBuild('', source_files, extension_to_rule_name,
1980 filter_group, source_group)
1982 content = ['Project',
1983 {'ToolsVersion': '4.0',
1984 'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
1986 ['ItemGroup'] + filter_group,
1987 ['ItemGroup'] + source_group
1989 easy_xml.WriteXmlIfChanged(content, filters_path, pretty=True, win32=True)
1990 elif os.path.exists(filters_path):
1991 # We don't need this filter anymore. Delete the old filter file.
1992 os.unlink(filters_path)
1995 def _AppendFiltersForMSBuild(parent_filter_name, sources,
1996 extension_to_rule_name,
1997 filter_group, source_group):
1998 """Creates the list of filters and sources to be added in the filter file.
2001 parent_filter_name: The name of the filter under which the sources are
2003 sources: The hierarchy of filters and sources to process.
2004 extension_to_rule_name: A dictionary mapping file extensions to rules.
2005 filter_group: The list to which filter entries will be appended.
2006 source_group: The list to which source entries will be appeneded.
2008 for source in sources:
2009 if isinstance(source, MSVSProject.Filter):
2010 # We have a sub-filter. Create the name of that sub-filter.
2011 if not parent_filter_name:
2012 filter_name = source.name
2014 filter_name = '%s\\%s' % (parent_filter_name, source.name)
2015 # Add the filter to the group.
2016 filter_group.append(
2017 ['Filter', {'Include': filter_name},
2018 ['UniqueIdentifier', MSVSNew.MakeGuid(source.name)]])
2019 # Recurse and add its dependents.
2020 _AppendFiltersForMSBuild(filter_name, source.contents,
2021 extension_to_rule_name,
2022 filter_group, source_group)
2024 # It's a source. Create a source entry.
2025 _, element = _MapFileToMsBuildSourceType(source, extension_to_rule_name)
2026 source_entry = [element, {'Include': source}]
2027 # Specify the filter it is part of, if any.
2028 if parent_filter_name:
2029 source_entry.append(['Filter', parent_filter_name])
2030 source_group.append(source_entry)
2033 def _MapFileToMsBuildSourceType(source, extension_to_rule_name):
2034 """Returns the group and element type of the source file.
2037 source: The source file name.
2038 extension_to_rule_name: A dictionary mapping file extensions to rules.
2041 A pair of (group this file should be part of, the label of element)
2043 _, ext = os.path.splitext(source)
2044 if ext in extension_to_rule_name:
2046 element = extension_to_rule_name[ext]
2047 elif ext in ['.cc', '.cpp', '.c', '.cxx']:
2049 element = 'ClCompile'
2050 elif ext in ['.h', '.hxx']:
2052 element = 'ClInclude'
2055 element = 'ResourceCompile'
2062 return (group, element)
2065 def _GenerateRulesForMSBuild(output_dir, options, spec,
2066 sources, excluded_sources,
2067 props_files_of_rules, targets_files_of_rules,
2068 actions_to_add, extension_to_rule_name):
2069 # MSBuild rules are implemented using three files: an XML file, a .targets
2070 # file and a .props file.
2071 # See http://blogs.msdn.com/b/vcblog/archive/2010/04/21/quick-help-on-vs2010-custom-build-rule.aspx
2073 rules = spec.get('rules', [])
2074 rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
2075 rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
2078 for rule in rules_native:
2079 # Skip a rule with no action and no inputs.
2080 if 'action' not in rule and not rule.get('rule_sources', []):
2082 msbuild_rule = MSBuildRule(rule, spec)
2083 msbuild_rules.append(msbuild_rule)
2084 extension_to_rule_name[msbuild_rule.extension] = msbuild_rule.rule_name
2086 base = spec['target_name'] + options.suffix
2087 props_name = base + '.props'
2088 targets_name = base + '.targets'
2089 xml_name = base + '.xml'
2091 props_files_of_rules.add(props_name)
2092 targets_files_of_rules.add(targets_name)
2094 props_path = os.path.join(output_dir, props_name)
2095 targets_path = os.path.join(output_dir, targets_name)
2096 xml_path = os.path.join(output_dir, xml_name)
2098 _GenerateMSBuildRulePropsFile(props_path, msbuild_rules)
2099 _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules)
2100 _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules)
2103 _GenerateExternalRules(rules_external, output_dir, spec,
2104 sources, options, actions_to_add)
2105 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
2108 class MSBuildRule(object):
2109 """Used to store information used to generate an MSBuild rule.
2112 rule_name: The rule name, sanitized to use in XML.
2113 target_name: The name of the target.
2114 after_targets: The name of the AfterTargets element.
2115 before_targets: The name of the BeforeTargets element.
2116 depends_on: The name of the DependsOn element.
2117 compute_output: The name of the ComputeOutput element.
2118 dirs_to_make: The name of the DirsToMake element.
2119 inputs: The name of the _inputs element.
2120 tlog: The name of the _tlog element.
2121 extension: The extension this rule applies to.
2122 description: The message displayed when this rule is invoked.
2123 additional_dependencies: A string listing additional dependencies.
2124 outputs: The outputs of this rule.
2125 command: The command used to run the rule.
2128 def __init__(self, rule, spec):
2129 self.display_name = rule['rule_name']
2130 # Assure that the rule name is only characters and numbers
2131 self.rule_name = re.sub(r'\W', '_', self.display_name)
2132 # Create the various element names, following the example set by the
2133 # Visual Studio 2008 to 2010 conversion. I don't know if VS2010
2134 # is sensitive to the exact names.
2135 self.target_name = '_' + self.rule_name
2136 self.after_targets = self.rule_name + 'AfterTargets'
2137 self.before_targets = self.rule_name + 'BeforeTargets'
2138 self.depends_on = self.rule_name + 'DependsOn'
2139 self.compute_output = 'Compute%sOutput' % self.rule_name
2140 self.dirs_to_make = self.rule_name + 'DirsToMake'
2141 self.inputs = self.rule_name + '_inputs'
2142 self.tlog = self.rule_name + '_tlog'
2143 self.extension = rule['extension']
2144 if not self.extension.startswith('.'):
2145 self.extension = '.' + self.extension
2147 self.description = MSVSSettings.ConvertVCMacrosToMSBuild(
2148 rule.get('message', self.rule_name))
2149 old_additional_dependencies = _FixPaths(rule.get('inputs', []))
2150 self.additional_dependencies = (
2151 ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
2152 for i in old_additional_dependencies]))
2153 old_outputs = _FixPaths(rule.get('outputs', []))
2154 self.outputs = ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
2155 for i in old_outputs])
2156 old_command = _BuildCommandLineForRule(spec, rule, has_input_path=True,
2158 self.command = MSVSSettings.ConvertVCMacrosToMSBuild(old_command)
2161 def _GenerateMSBuildRulePropsFile(props_path, msbuild_rules):
2162 """Generate the .props file."""
2163 content = ['Project',
2164 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'}]
2165 for rule in msbuild_rules:
2168 {'Condition': "'$(%s)' == '' and '$(%s)' == '' and "
2169 "'$(ConfigurationType)' != 'Makefile'" % (rule.before_targets,
2172 [rule.before_targets, 'Midl'],
2173 [rule.after_targets, 'CustomBuild'],
2177 {'Condition': "'$(ConfigurationType)' != 'Makefile'"},
2178 '_SelectedFiles;$(%s)' % rule.depends_on
2181 ['ItemDefinitionGroup',
2183 ['CommandLineTemplate', rule.command],
2184 ['Outputs', rule.outputs],
2185 ['ExecutionDescription', rule.description],
2186 ['AdditionalDependencies', rule.additional_dependencies],
2190 easy_xml.WriteXmlIfChanged(content, props_path, pretty=True, win32=True)
2193 def _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules):
2194 """Generate the .targets file."""
2195 content = ['Project',
2196 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
2201 ['PropertyPageSchema',
2202 {'Include': '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'}
2205 for rule in msbuild_rules:
2207 ['AvailableItemName',
2208 {'Include': rule.rule_name},
2209 ['Targets', rule.target_name],
2211 content.append(item_group)
2213 for rule in msbuild_rules:
2216 {'TaskName': rule.rule_name,
2217 'TaskFactory': 'XamlTaskFactory',
2218 'AssemblyName': 'Microsoft.Build.Tasks.v4.0'
2220 ['Task', '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'],
2222 for rule in msbuild_rules:
2223 rule_name = rule.rule_name
2224 target_outputs = '%%(%s.Outputs)' % rule_name
2225 target_inputs = ('%%(%s.Identity);%%(%s.AdditionalDependencies);'
2226 '$(MSBuildProjectFile)') % (rule_name, rule_name)
2227 rule_inputs = '%%(%s.Identity)' % rule_name
2228 extension_condition = ("'%(Extension)'=='.obj' or "
2229 "'%(Extension)'=='.res' or "
2230 "'%(Extension)'=='.rsc' or "
2231 "'%(Extension)'=='.lib'")
2234 {'Condition': "'@(SelectedFiles)' != ''"},
2236 {'Remove': '@(%s)' % rule_name,
2237 'Condition': "'%(Identity)' != '@(SelectedFiles)'"
2243 [rule.inputs, {'Include': '%%(%s.AdditionalDependencies)' % rule_name}]
2248 {'Include': '%%(%s.Outputs)' % rule_name,
2249 'Condition': ("'%%(%s.Outputs)' != '' and "
2250 "'%%(%s.ExcludedFromBuild)' != 'true'" %
2251 (rule_name, rule_name))
2253 ['Source', "@(%s, '|')" % rule_name],
2254 ['Inputs', "@(%s -> '%%(Fullpath)', ';')" % rule.inputs],
2259 {'Importance': 'High',
2260 'Text': '%%(%s.ExecutionDescription)' % rule_name
2263 write_tlog_section = [
2265 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2266 "'true'" % (rule.tlog, rule.tlog),
2267 'File': '$(IntDir)$(ProjectName).write.1.tlog',
2268 'Lines': "^%%(%s.Source);@(%s->'%%(Fullpath)')" % (rule.tlog,
2272 read_tlog_section = [
2274 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2275 "'true'" % (rule.tlog, rule.tlog),
2276 'File': '$(IntDir)$(ProjectName).read.1.tlog',
2277 'Lines': "^%%(%s.Source);%%(%s.Inputs)" % (rule.tlog, rule.tlog)
2280 command_and_input_section = [
2282 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2283 "'true'" % (rule_name, rule_name),
2284 'CommandLineTemplate': '%%(%s.CommandLineTemplate)' % rule_name,
2285 'AdditionalOptions': '%%(%s.AdditionalOptions)' % rule_name,
2286 'Inputs': rule_inputs
2291 {'Name': rule.target_name,
2292 'BeforeTargets': '$(%s)' % rule.before_targets,
2293 'AfterTargets': '$(%s)' % rule.after_targets,
2294 'Condition': "'@(%s)' != ''" % rule_name,
2295 'DependsOnTargets': '$(%s);%s' % (rule.depends_on,
2296 rule.compute_output),
2297 'Outputs': target_outputs,
2298 'Inputs': target_inputs
2306 command_and_input_section,
2309 ['ComputeLinkInputsTargets',
2310 '$(ComputeLinkInputsTargets);',
2311 '%s;' % rule.compute_output
2313 ['ComputeLibInputsTargets',
2314 '$(ComputeLibInputsTargets);',
2315 '%s;' % rule.compute_output
2319 {'Name': rule.compute_output,
2320 'Condition': "'@(%s)' != ''" % rule_name
2324 {'Condition': "'@(%s)' != '' and "
2325 "'%%(%s.ExcludedFromBuild)' != 'true'" % (rule_name, rule_name),
2326 'Include': '%%(%s.Outputs)' % rule_name
2330 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2331 'Condition': extension_condition
2335 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2336 'Condition': extension_condition
2340 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2341 'Condition': extension_condition
2346 {'Directories': ("@(%s->'%%(RootDir)%%(Directory)')" %
2352 easy_xml.WriteXmlIfChanged(content, targets_path, pretty=True, win32=True)
2355 def _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules):
2356 # Generate the .xml file
2358 'ProjectSchemaDefinitions',
2359 {'xmlns': ('clr-namespace:Microsoft.Build.Framework.XamlTypes;'
2360 'assembly=Microsoft.Build.Framework'),
2361 'xmlns:x': 'http://schemas.microsoft.com/winfx/2006/xaml',
2362 'xmlns:sys': 'clr-namespace:System;assembly=mscorlib',
2363 'xmlns:transformCallback':
2364 'Microsoft.Cpp.Dev10.ConvertPropertyCallback'
2367 for rule in msbuild_rules:
2370 {'Name': rule.rule_name,
2371 'PageTemplate': 'tool',
2372 'DisplayName': rule.display_name,
2377 {'Persistence': 'ProjectFile',
2378 'ItemType': rule.rule_name
2384 {'Name': 'General'},
2385 ['Category.DisplayName',
2386 ['sys:String', 'General'],
2390 {'Name': 'Command Line',
2391 'Subtype': 'CommandLine'
2393 ['Category.DisplayName',
2394 ['sys:String', 'Command Line'],
2398 ['StringListProperty',
2400 'Category': 'Command Line',
2401 'IsRequired': 'true',
2404 ['StringListProperty.DataSource',
2406 {'Persistence': 'ProjectFile',
2407 'ItemType': rule.rule_name,
2408 'SourceType': 'Item'
2414 {'Name': 'CommandLineTemplate',
2415 'DisplayName': 'Command Line',
2417 'IncludeInCommandLine': 'False'
2420 ['DynamicEnumProperty',
2421 {'Name': rule.before_targets,
2422 'Category': 'General',
2423 'EnumProvider': 'Targets',
2424 'IncludeInCommandLine': 'False'
2426 ['DynamicEnumProperty.DisplayName',
2427 ['sys:String', 'Execute Before'],
2429 ['DynamicEnumProperty.Description',
2430 ['sys:String', 'Specifies the targets for the build customization'
2434 ['DynamicEnumProperty.ProviderSettings',
2437 'Value': '^%s|^Compute' % rule.before_targets
2441 ['DynamicEnumProperty.DataSource',
2443 {'Persistence': 'ProjectFile',
2444 'HasConfigurationCondition': 'true'
2449 ['DynamicEnumProperty',
2450 {'Name': rule.after_targets,
2451 'Category': 'General',
2452 'EnumProvider': 'Targets',
2453 'IncludeInCommandLine': 'False'
2455 ['DynamicEnumProperty.DisplayName',
2456 ['sys:String', 'Execute After'],
2458 ['DynamicEnumProperty.Description',
2459 ['sys:String', ('Specifies the targets for the build customization'
2463 ['DynamicEnumProperty.ProviderSettings',
2466 'Value': '^%s|^Compute' % rule.after_targets
2470 ['DynamicEnumProperty.DataSource',
2472 {'Persistence': 'ProjectFile',
2474 'HasConfigurationCondition': 'true'
2479 ['StringListProperty',
2481 'DisplayName': 'Outputs',
2483 'IncludeInCommandLine': 'False'
2487 {'Name': 'ExecutionDescription',
2488 'DisplayName': 'Execution Description',
2490 'IncludeInCommandLine': 'False'
2493 ['StringListProperty',
2494 {'Name': 'AdditionalDependencies',
2495 'DisplayName': 'Additional Dependencies',
2496 'IncludeInCommandLine': 'False',
2501 {'Subtype': 'AdditionalOptions',
2502 'Name': 'AdditionalOptions',
2503 'Category': 'Command Line'
2505 ['StringProperty.DisplayName',
2506 ['sys:String', 'Additional Options'],
2508 ['StringProperty.Description',
2509 ['sys:String', 'Additional Options'],
2514 {'Name': rule.rule_name,
2515 'DisplayName': rule.display_name
2519 {'Name': '*' + rule.extension,
2520 'ContentType': rule.rule_name
2524 {'Name': rule.rule_name,
2526 'ItemType': rule.rule_name
2530 easy_xml.WriteXmlIfChanged(content, xml_path, pretty=True, win32=True)
2533 def _GetConfigurationAndPlatform(name, settings):
2534 configuration = name.rsplit('_', 1)[0]
2535 platform = settings.get('msvs_configuration_platform', 'Win32')
2536 return (configuration, platform)
2539 def _GetConfigurationCondition(name, settings):
2540 return (r"'$(Configuration)|$(Platform)'=='%s|%s'" %
2541 _GetConfigurationAndPlatform(name, settings))
2544 def _GetMSBuildProjectConfigurations(configurations):
2545 group = ['ItemGroup', {'Label': 'ProjectConfigurations'}]
2546 for (name, settings) in sorted(configurations.iteritems()):
2547 configuration, platform = _GetConfigurationAndPlatform(name, settings)
2548 designation = '%s|%s' % (configuration, platform)
2550 ['ProjectConfiguration', {'Include': designation},
2551 ['Configuration', configuration],
2552 ['Platform', platform]])
2556 def _GetMSBuildGlobalProperties(spec, guid, gyp_file_name):
2557 namespace = os.path.splitext(gyp_file_name)[0]
2559 ['PropertyGroup', {'Label': 'Globals'},
2560 ['ProjectGuid', guid],
2561 ['Keyword', 'Win32Proj'],
2562 ['RootNamespace', namespace],
2567 def _GetMSBuildConfigurationDetails(spec, build_file):
2569 for name, settings in spec['configurations'].iteritems():
2570 msbuild_attributes = _GetMSBuildAttributes(spec, settings, build_file)
2571 condition = _GetConfigurationCondition(name, settings)
2572 character_set = msbuild_attributes.get('CharacterSet')
2573 _AddConditionalProperty(properties, condition, 'ConfigurationType',
2574 msbuild_attributes['ConfigurationType'])
2576 _AddConditionalProperty(properties, condition, 'CharacterSet',
2578 return _GetMSBuildPropertyGroup(spec, 'Configuration', properties)
2581 def _GetMSBuildLocalProperties(msbuild_toolset):
2582 # Currently the only local property we support is PlatformToolset
2586 ['PropertyGroup', {'Label': 'Locals'},
2587 ['PlatformToolset', msbuild_toolset],
2593 def _GetMSBuildPropertySheets(configurations):
2594 user_props = r'$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props'
2595 additional_props = {}
2596 props_specified = False
2597 for name, settings in sorted(configurations.iteritems()):
2598 configuration = _GetConfigurationCondition(name, settings)
2599 if settings.has_key('msbuild_props'):
2600 additional_props[configuration] = _FixPaths(settings['msbuild_props'])
2601 props_specified = True
2603 additional_props[configuration] = ''
2605 if not props_specified:
2608 {'Label': 'PropertySheets'},
2610 {'Project': user_props,
2611 'Condition': "exists('%s')" % user_props,
2612 'Label': 'LocalAppDataPlatform'
2619 for condition, props in additional_props.iteritems():
2622 {'Label': 'PropertySheets',
2623 'Condition': condition
2626 {'Project': user_props,
2627 'Condition': "exists('%s')" % user_props,
2628 'Label': 'LocalAppDataPlatform'
2632 for props_file in props:
2633 import_group.append(['Import', {'Project':props_file}])
2634 sheets.append(import_group)
2637 def _ConvertMSVSBuildAttributes(spec, config, build_file):
2638 config_type = _GetMSVSConfigurationType(spec, build_file)
2639 msvs_attributes = _GetMSVSAttributes(spec, config, config_type)
2640 msbuild_attributes = {}
2641 for a in msvs_attributes:
2642 if a in ['IntermediateDirectory', 'OutputDirectory']:
2643 directory = MSVSSettings.ConvertVCMacrosToMSBuild(msvs_attributes[a])
2644 if not directory.endswith('\\'):
2646 msbuild_attributes[a] = directory
2647 elif a == 'CharacterSet':
2648 msbuild_attributes[a] = _ConvertMSVSCharacterSet(msvs_attributes[a])
2649 elif a == 'ConfigurationType':
2650 msbuild_attributes[a] = _ConvertMSVSConfigurationType(msvs_attributes[a])
2652 print 'Warning: Do not know how to convert MSVS attribute ' + a
2653 return msbuild_attributes
2656 def _ConvertMSVSCharacterSet(char_set):
2657 if char_set.isdigit():
2666 def _ConvertMSVSConfigurationType(config_type):
2667 if config_type.isdigit():
2670 '2': 'DynamicLibrary',
2671 '4': 'StaticLibrary',
2677 def _GetMSBuildAttributes(spec, config, build_file):
2678 if 'msbuild_configuration_attributes' not in config:
2679 msbuild_attributes = _ConvertMSVSBuildAttributes(spec, config, build_file)
2682 config_type = _GetMSVSConfigurationType(spec, build_file)
2683 config_type = _ConvertMSVSConfigurationType(config_type)
2684 msbuild_attributes = config.get('msbuild_configuration_attributes', {})
2685 msbuild_attributes.setdefault('ConfigurationType', config_type)
2686 output_dir = msbuild_attributes.get('OutputDirectory',
2687 '$(SolutionDir)$(Configuration)')
2688 msbuild_attributes['OutputDirectory'] = _FixPath(output_dir) + '\\'
2689 if 'IntermediateDirectory' not in msbuild_attributes:
2690 intermediate = _FixPath('$(Configuration)') + '\\'
2691 msbuild_attributes['IntermediateDirectory'] = intermediate
2692 if 'CharacterSet' in msbuild_attributes:
2693 msbuild_attributes['CharacterSet'] = _ConvertMSVSCharacterSet(
2694 msbuild_attributes['CharacterSet'])
2695 if 'TargetName' not in msbuild_attributes:
2696 prefix = spec.get('product_prefix', '')
2697 product_name = spec.get('product_name', '$(ProjectName)')
2698 target_name = prefix + product_name
2699 msbuild_attributes['TargetName'] = target_name
2701 if spec.get('msvs_external_builder'):
2702 external_out_dir = spec.get('msvs_external_builder_out_dir', '.')
2703 msbuild_attributes['OutputDirectory'] = _FixPath(external_out_dir) + '\\'
2705 # Make sure that 'TargetPath' matches 'Lib.OutputFile' or 'Link.OutputFile'
2706 # (depending on the tool used) to avoid MSB8012 warning.
2707 msbuild_tool_map = {
2708 'executable': 'Link',
2709 'shared_library': 'Link',
2710 'loadable_module': 'Link',
2711 'static_library': 'Lib',
2713 msbuild_tool = msbuild_tool_map.get(spec['type'])
2715 msbuild_settings = config['finalized_msbuild_settings']
2716 out_file = msbuild_settings[msbuild_tool].get('OutputFile')
2718 msbuild_attributes['TargetPath'] = _FixPath(out_file)
2719 target_ext = msbuild_settings[msbuild_tool].get('TargetExt')
2721 msbuild_attributes['TargetExt'] = target_ext
2723 return msbuild_attributes
2726 def _GetMSBuildConfigurationGlobalProperties(spec, configurations, build_file):
2727 # TODO(jeanluc) We could optimize out the following and do it only if
2728 # there are actions.
2729 # TODO(jeanluc) Handle the equivalent of setting 'CYGWIN=nontsec'.
2731 cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.'])[0]
2733 cyg_path = '$(MSBuildProjectDirectory)\\%s\\bin\\' % _FixPath(cygwin_dirs)
2734 new_paths.append(cyg_path)
2735 # TODO(jeanluc) Change the convention to have both a cygwin_dir and a
2737 python_path = cyg_path.replace('cygwin\\bin', 'python_26')
2738 new_paths.append(python_path)
2740 new_paths = '$(ExecutablePath);' + ';'.join(new_paths)
2743 for (name, configuration) in sorted(configurations.iteritems()):
2744 condition = _GetConfigurationCondition(name, configuration)
2745 attributes = _GetMSBuildAttributes(spec, configuration, build_file)
2746 msbuild_settings = configuration['finalized_msbuild_settings']
2747 _AddConditionalProperty(properties, condition, 'IntDir',
2748 attributes['IntermediateDirectory'])
2749 _AddConditionalProperty(properties, condition, 'OutDir',
2750 attributes['OutputDirectory'])
2751 _AddConditionalProperty(properties, condition, 'TargetName',
2752 attributes['TargetName'])
2754 if attributes.get('TargetPath'):
2755 _AddConditionalProperty(properties, condition, 'TargetPath',
2756 attributes['TargetPath'])
2757 if attributes.get('TargetExt'):
2758 _AddConditionalProperty(properties, condition, 'TargetExt',
2759 attributes['TargetExt'])
2762 _AddConditionalProperty(properties, condition, 'ExecutablePath',
2764 tool_settings = msbuild_settings.get('', {})
2765 for name, value in sorted(tool_settings.iteritems()):
2766 formatted_value = _GetValueFormattedForMSBuild('', name, value)
2767 _AddConditionalProperty(properties, condition, name, formatted_value)
2768 return _GetMSBuildPropertyGroup(spec, None, properties)
2771 def _AddConditionalProperty(properties, condition, name, value):
2772 """Adds a property / conditional value pair to a dictionary.
2775 properties: The dictionary to be modified. The key is the name of the
2776 property. The value is itself a dictionary; its key is the value and
2777 the value a list of condition for which this value is true.
2778 condition: The condition under which the named property has the value.
2779 name: The name of the property.
2780 value: The value of the property.
2782 if name not in properties:
2783 properties[name] = {}
2784 values = properties[name]
2785 if value not in values:
2787 conditions = values[value]
2788 conditions.append(condition)
2791 # Regex for msvs variable references ( i.e. $(FOO) ).
2792 MSVS_VARIABLE_REFERENCE = re.compile('\$\(([a-zA-Z_][a-zA-Z0-9_]*)\)')
2795 def _GetMSBuildPropertyGroup(spec, label, properties):
2796 """Returns a PropertyGroup definition for the specified properties.
2799 spec: The target project dict.
2800 label: An optional label for the PropertyGroup.
2801 properties: The dictionary to be converted. The key is the name of the
2802 property. The value is itself a dictionary; its key is the value and
2803 the value a list of condition for which this value is true.
2805 group = ['PropertyGroup']
2807 group.append({'Label': label})
2808 num_configurations = len(spec['configurations'])
2810 # Use a definition of edges such that user_of_variable -> used_varible.
2811 # This happens to be easier in this case, since a variable's
2812 # definition contains all variables it references in a single string.
2814 for value in sorted(properties[node].keys()):
2815 # Add to edges all $(...) references to variables.
2817 # Variable references that refer to names not in properties are excluded
2818 # These can exist for instance to refer built in definitions like
2821 # Self references are ignored. Self reference is used in a few places to
2822 # append to the default value. I.e. PATH=$(PATH);other_path
2823 edges.update(set([v for v in MSVS_VARIABLE_REFERENCE.findall(value)
2824 if v in properties and v != node]))
2826 properties_ordered = gyp.common.TopologicallySorted(
2827 properties.keys(), GetEdges)
2828 # Walk properties in the reverse of a topological sort on
2829 # user_of_variable -> used_variable as this ensures variables are
2830 # defined before they are used.
2831 # NOTE: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))
2832 for name in reversed(properties_ordered):
2833 values = properties[name]
2834 for value, conditions in sorted(values.iteritems()):
2835 if len(conditions) == num_configurations:
2836 # If the value is the same all configurations,
2837 # just add one unconditional entry.
2838 group.append([name, value])
2840 for condition in conditions:
2841 group.append([name, {'Condition': condition}, value])
2845 def _GetMSBuildToolSettingsSections(spec, configurations):
2847 for (name, configuration) in sorted(configurations.iteritems()):
2848 msbuild_settings = configuration['finalized_msbuild_settings']
2849 group = ['ItemDefinitionGroup',
2850 {'Condition': _GetConfigurationCondition(name, configuration)}
2852 for tool_name, tool_settings in sorted(msbuild_settings.iteritems()):
2853 # Skip the tool named '' which is a holder of global settings handled
2854 # by _GetMSBuildConfigurationGlobalProperties.
2858 for name, value in sorted(tool_settings.iteritems()):
2859 formatted_value = _GetValueFormattedForMSBuild(tool_name, name,
2861 tool.append([name, formatted_value])
2863 groups.append(group)
2867 def _FinalizeMSBuildSettings(spec, configuration):
2868 if 'msbuild_settings' in configuration:
2870 msbuild_settings = configuration['msbuild_settings']
2871 MSVSSettings.ValidateMSBuildSettings(msbuild_settings)
2874 msvs_settings = configuration.get('msvs_settings', {})
2875 msbuild_settings = MSVSSettings.ConvertToMSBuildSettings(msvs_settings)
2876 include_dirs, resource_include_dirs = _GetIncludeDirs(configuration)
2877 libraries = _GetLibraries(spec)
2878 library_dirs = _GetLibraryDirs(configuration)
2879 out_file, _, msbuild_tool = _GetOutputFilePathAndTool(spec, msbuild=True)
2880 target_ext = _GetOutputTargetExt(spec)
2881 defines = _GetDefines(configuration)
2883 # Visual Studio 2010 has TR1
2884 defines = [d for d in defines if d != '_HAS_TR1=0']
2885 # Warn of ignored settings
2886 ignored_settings = ['msvs_prebuild', 'msvs_postbuild', 'msvs_tool_files']
2887 for ignored_setting in ignored_settings:
2888 value = configuration.get(ignored_setting)
2890 print ('Warning: The automatic conversion to MSBuild does not handle '
2891 '%s. Ignoring setting of %s' % (ignored_setting, str(value)))
2893 defines = [_EscapeCppDefineForMSBuild(d) for d in defines]
2894 disabled_warnings = _GetDisabledWarnings(configuration)
2895 # TODO(jeanluc) Validate & warn that we don't translate
2896 # prebuild = configuration.get('msvs_prebuild')
2897 # postbuild = configuration.get('msvs_postbuild')
2898 def_file = _GetModuleDefinition(spec)
2899 precompiled_header = configuration.get('msvs_precompiled_header')
2901 # Add the information to the appropriate tool
2902 # TODO(jeanluc) We could optimize and generate these settings only if
2903 # the corresponding files are found, e.g. don't generate ResourceCompile
2904 # if you don't have any resources.
2905 _ToolAppend(msbuild_settings, 'ClCompile',
2906 'AdditionalIncludeDirectories', include_dirs)
2907 _ToolAppend(msbuild_settings, 'ResourceCompile',
2908 'AdditionalIncludeDirectories', resource_include_dirs)
2909 # Add in libraries, note that even for empty libraries, we want this
2910 # set, to prevent inheriting default libraries from the enviroment.
2911 _ToolSetOrAppend(msbuild_settings, 'Link', 'AdditionalDependencies',
2913 _ToolAppend(msbuild_settings, 'Link', 'AdditionalLibraryDirectories',
2916 _ToolAppend(msbuild_settings, msbuild_tool, 'OutputFile', out_file,
2919 _ToolAppend(msbuild_settings, msbuild_tool, 'TargetExt', target_ext,
2922 _ToolAppend(msbuild_settings, 'ClCompile',
2923 'PreprocessorDefinitions', defines)
2924 _ToolAppend(msbuild_settings, 'ResourceCompile',
2925 'PreprocessorDefinitions', defines)
2926 # Add disabled warnings.
2927 _ToolAppend(msbuild_settings, 'ClCompile',
2928 'DisableSpecificWarnings', disabled_warnings)
2929 # Turn on precompiled headers if appropriate.
2930 if precompiled_header:
2931 precompiled_header = os.path.split(precompiled_header)[1]
2932 _ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'Use')
2933 _ToolAppend(msbuild_settings, 'ClCompile',
2934 'PrecompiledHeaderFile', precompiled_header)
2935 _ToolAppend(msbuild_settings, 'ClCompile',
2936 'ForcedIncludeFiles', [precompiled_header])
2937 # Loadable modules don't generate import libraries;
2938 # tell dependent projects to not expect one.
2939 if spec['type'] == 'loadable_module':
2940 _ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'true')
2941 # Set the module definition file if any.
2943 _ToolAppend(msbuild_settings, 'Link', 'ModuleDefinitionFile', def_file)
2944 configuration['finalized_msbuild_settings'] = msbuild_settings
2947 def _GetValueFormattedForMSBuild(tool_name, name, value):
2948 if type(value) == list:
2949 # For some settings, VS2010 does not automatically extends the settings
2950 # TODO(jeanluc) Is this what we want?
2951 if name in ['AdditionalIncludeDirectories',
2952 'AdditionalLibraryDirectories',
2953 'AdditionalOptions',
2955 'DisableSpecificWarnings',
2956 'PreprocessorDefinitions']:
2957 value.append('%%(%s)' % name)
2958 # For most tools, entries in a list should be separated with ';' but some
2959 # settings use a space. Check for those first.
2961 'ClCompile': ['AdditionalOptions'],
2962 'Link': ['AdditionalOptions'],
2963 'Lib': ['AdditionalOptions']}
2964 if tool_name in exceptions and name in exceptions[tool_name]:
2968 formatted_value = char.join(
2969 [MSVSSettings.ConvertVCMacrosToMSBuild(i) for i in value])
2971 formatted_value = MSVSSettings.ConvertVCMacrosToMSBuild(value)
2972 return formatted_value
2975 def _VerifySourcesExist(sources, root_dir):
2976 """Verifies that all source files exist on disk.
2978 Checks that all regular source files, i.e. not created at run time,
2979 exist on disk. Missing files cause needless recompilation but no otherwise
2983 sources: A recursive list of Filter/file names.
2984 root_dir: The root directory for the relative path names.
2986 A list of source files that cannot be found on disk.
2988 missing_sources = []
2989 for source in sources:
2990 if isinstance(source, MSVSProject.Filter):
2991 missing_sources.extend(_VerifySourcesExist(source.contents, root_dir))
2993 if '$' not in source:
2994 full_path = os.path.join(root_dir, source)
2995 if not os.path.exists(full_path):
2996 missing_sources.append(full_path)
2997 return missing_sources
3000 def _GetMSBuildSources(spec, sources, exclusions, extension_to_rule_name,
3001 actions_spec, sources_handled_by_action, list_excluded):
3002 groups = ['none', 'midl', 'include', 'compile', 'resource', 'rule']
3003 grouped_sources = {}
3005 grouped_sources[g] = []
3007 _AddSources2(spec, sources, exclusions, grouped_sources,
3008 extension_to_rule_name, sources_handled_by_action, list_excluded)
3011 if grouped_sources[g]:
3012 sources.append(['ItemGroup'] + grouped_sources[g])
3014 sources.append(['ItemGroup'] + actions_spec)
3018 def _AddSources2(spec, sources, exclusions, grouped_sources,
3019 extension_to_rule_name, sources_handled_by_action,
3021 extensions_excluded_from_precompile = []
3022 for source in sources:
3023 if isinstance(source, MSVSProject.Filter):
3024 _AddSources2(spec, source.contents, exclusions, grouped_sources,
3025 extension_to_rule_name, sources_handled_by_action,
3028 if not source in sources_handled_by_action:
3030 excluded_configurations = exclusions.get(source, [])
3031 if len(excluded_configurations) == len(spec['configurations']):
3032 detail.append(['ExcludedFromBuild', 'true'])
3034 for config_name, configuration in sorted(excluded_configurations):
3035 condition = _GetConfigurationCondition(config_name, configuration)
3036 detail.append(['ExcludedFromBuild',
3037 {'Condition': condition},
3039 # Add precompile if needed
3040 for config_name, configuration in spec['configurations'].iteritems():
3041 precompiled_source = configuration.get('msvs_precompiled_source', '')
3042 if precompiled_source != '':
3043 precompiled_source = _FixPath(precompiled_source)
3044 if not extensions_excluded_from_precompile:
3045 # If the precompiled header is generated by a C source, we must
3046 # not try to use it for C++ sources, and vice versa.
3047 basename, extension = os.path.splitext(precompiled_source)
3048 if extension == '.c':
3049 extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
3051 extensions_excluded_from_precompile = ['.c']
3053 if precompiled_source == source:
3054 condition = _GetConfigurationCondition(config_name, configuration)
3055 detail.append(['PrecompiledHeader',
3056 {'Condition': condition},
3060 # Turn off precompiled header usage for source files of a
3061 # different type than the file that generated the
3062 # precompiled header.
3063 for extension in extensions_excluded_from_precompile:
3064 if source.endswith(extension):
3065 detail.append(['PrecompiledHeader', ''])
3066 detail.append(['ForcedIncludeFiles', ''])
3068 group, element = _MapFileToMsBuildSourceType(source,
3069 extension_to_rule_name)
3070 grouped_sources[group].append([element, {'Include': source}] + detail)
3073 def _GetMSBuildProjectReferences(project):
3075 if project.dependencies:
3076 group = ['ItemGroup']
3077 for dependency in project.dependencies:
3078 guid = dependency.guid
3079 project_dir = os.path.split(project.path)[0]
3080 relative_path = gyp.common.RelativePath(dependency.path, project_dir)
3081 project_ref = ['ProjectReference',
3082 {'Include': relative_path},
3084 ['ReferenceOutputAssembly', 'false']
3086 for config in dependency.spec.get('configurations', {}).itervalues():
3087 # If it's disabled in any config, turn it off in the reference.
3088 if config.get('msvs_2010_disable_uldi_when_referenced', 0):
3089 project_ref.append(['UseLibraryDependencyInputs', 'false'])
3091 group.append(project_ref)
3092 references.append(group)
3096 def _GenerateMSBuildProject(project, options, version, generator_flags):
3098 configurations = spec['configurations']
3099 project_dir, project_file_name = os.path.split(project.path)
3100 gyp.common.EnsureDirExists(project.path)
3101 # Prepare list of sources and excluded sources.
3102 gyp_path = _NormalizedSource(project.build_file)
3103 relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
3105 gyp_file = os.path.split(project.build_file)[1]
3106 sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
3110 props_files_of_rules = set()
3111 targets_files_of_rules = set()
3112 extension_to_rule_name = {}
3113 list_excluded = generator_flags.get('msvs_list_excluded_files', True)
3115 # Don't generate rules if we are using an external builder like ninja.
3116 if not spec.get('msvs_external_builder'):
3117 _GenerateRulesForMSBuild(project_dir, options, spec,
3118 sources, excluded_sources,
3119 props_files_of_rules, targets_files_of_rules,
3120 actions_to_add, extension_to_rule_name)
3122 rules = spec.get('rules', [])
3123 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
3125 sources, excluded_sources, excluded_idl = (
3126 _AdjustSourcesAndConvertToFilterHierarchy(spec, options,
3127 project_dir, sources,
3131 # Don't add actions if we are using an external builder like ninja.
3132 if not spec.get('msvs_external_builder'):
3133 _AddActions(actions_to_add, spec, project.build_file)
3134 _AddCopies(actions_to_add, spec)
3136 # NOTE: this stanza must appear after all actions have been decided.
3137 # Don't excluded sources with actions attached, or they won't run.
3138 excluded_sources = _FilterActionsFromExcluded(
3139 excluded_sources, actions_to_add)
3141 exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
3142 actions_spec, sources_handled_by_action = _GenerateActionsForMSBuild(
3143 spec, actions_to_add)
3145 _GenerateMSBuildFiltersFile(project.path + '.filters', sources,
3146 extension_to_rule_name)
3147 missing_sources = _VerifySourcesExist(sources, project_dir)
3149 for configuration in configurations.itervalues():
3150 _FinalizeMSBuildSettings(spec, configuration)
3152 # Add attributes to root element
3154 import_default_section = [
3155 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.Default.props'}]]
3156 import_cpp_props_section = [
3157 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.props'}]]
3158 import_cpp_targets_section = [
3159 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.targets'}]]
3160 macro_section = [['PropertyGroup', {'Label': 'UserMacros'}]]
3164 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003',
3165 'ToolsVersion': version.ProjectVersion(),
3166 'DefaultTargets': 'Build'
3169 content += _GetMSBuildProjectConfigurations(configurations)
3170 content += _GetMSBuildGlobalProperties(spec, project.guid, project_file_name)
3171 content += import_default_section
3172 content += _GetMSBuildConfigurationDetails(spec, project.build_file)
3173 content += _GetMSBuildLocalProperties(project.msbuild_toolset)
3174 content += import_cpp_props_section
3175 content += _GetMSBuildExtensions(props_files_of_rules)
3176 content += _GetMSBuildPropertySheets(configurations)
3177 content += macro_section
3178 content += _GetMSBuildConfigurationGlobalProperties(spec, configurations,
3180 content += _GetMSBuildToolSettingsSections(spec, configurations)
3181 content += _GetMSBuildSources(
3182 spec, sources, exclusions, extension_to_rule_name, actions_spec,
3183 sources_handled_by_action, list_excluded)
3184 content += _GetMSBuildProjectReferences(project)
3185 content += import_cpp_targets_section
3186 content += _GetMSBuildExtensionTargets(targets_files_of_rules)
3188 if spec.get('msvs_external_builder'):
3189 content += _GetMSBuildExternalBuilderTargets(spec)
3191 # TODO(jeanluc) File a bug to get rid of runas. We had in MSVS:
3192 # has_run_as = _WriteMSVSUserFile(project.path, version, spec)
3194 easy_xml.WriteXmlIfChanged(content, project.path, pretty=True, win32=True)
3196 return missing_sources
3199 def _GetMSBuildExternalBuilderTargets(spec):
3200 """Return a list of MSBuild targets for external builders.
3202 Right now, only "Build" and "Clean" targets are generated.
3205 spec: The gyp target spec.
3207 List of MSBuild 'Target' specs.
3209 build_cmd = _BuildCommandLineForRuleRaw(
3210 spec, spec['msvs_external_builder_build_cmd'],
3211 False, False, False, False)
3212 build_target = ['Target', {'Name': 'Build'}]
3213 build_target.append(['Exec', {'Command': build_cmd}])
3215 clean_cmd = _BuildCommandLineForRuleRaw(
3216 spec, spec['msvs_external_builder_clean_cmd'],
3217 False, False, False, False)
3218 clean_target = ['Target', {'Name': 'Clean'}]
3219 clean_target.append(['Exec', {'Command': clean_cmd}])
3221 return [build_target, clean_target]
3224 def _GetMSBuildExtensions(props_files_of_rules):
3225 extensions = ['ImportGroup', {'Label': 'ExtensionSettings'}]
3226 for props_file in props_files_of_rules:
3227 extensions.append(['Import', {'Project': props_file}])
3231 def _GetMSBuildExtensionTargets(targets_files_of_rules):
3232 targets_node = ['ImportGroup', {'Label': 'ExtensionTargets'}]
3233 for targets_file in sorted(targets_files_of_rules):
3234 targets_node.append(['Import', {'Project': targets_file}])
3235 return [targets_node]
3238 def _GenerateActionsForMSBuild(spec, actions_to_add):
3239 """Add actions accumulated into an actions_to_add, merging as needed.
3242 spec: the target project dict
3243 actions_to_add: dictionary keyed on input name, which maps to a list of
3244 dicts describing the actions attached to that input file.
3247 A pair of (action specification, the sources handled by this action).
3249 sources_handled_by_action = OrderedSet()
3251 for primary_input, actions in actions_to_add.iteritems():
3252 inputs = OrderedSet()
3253 outputs = OrderedSet()
3256 for action in actions:
3257 inputs.update(OrderedSet(action['inputs']))
3258 outputs.update(OrderedSet(action['outputs']))
3259 descriptions.append(action['description'])
3260 cmd = action['command']
3261 # For most actions, add 'call' so that actions that invoke batch files
3262 # return and continue executing. msbuild_use_call provides a way to
3263 # disable this but I have not seen any adverse effect from doing that
3265 if action.get('msbuild_use_call', True):
3267 commands.append(cmd)
3268 # Add the custom build action for one input file.
3269 description = ', and also '.join(descriptions)
3271 # We can't join the commands simply with && because the command line will
3272 # get too long. See also _AddActions: cygwin's setup_env mustn't be called
3273 # for every invocation or the command that sets the PATH will grow too
3276 '\r\nif %errorlevel% neq 0 exit /b %errorlevel%\r\n'.join(commands))
3277 _AddMSBuildAction(spec,
3283 sources_handled_by_action,
3285 return actions_spec, sources_handled_by_action
3288 def _AddMSBuildAction(spec, primary_input, inputs, outputs, cmd, description,
3289 sources_handled_by_action, actions_spec):
3290 command = MSVSSettings.ConvertVCMacrosToMSBuild(cmd)
3291 primary_input = _FixPath(primary_input)
3292 inputs_array = _FixPaths(inputs)
3293 outputs_array = _FixPaths(outputs)
3294 additional_inputs = ';'.join([i for i in inputs_array
3295 if i != primary_input])
3296 outputs = ';'.join(outputs_array)
3297 sources_handled_by_action.add(primary_input)
3298 action_spec = ['CustomBuild', {'Include': primary_input}]
3300 # TODO(jeanluc) 'Document' for all or just if as_sources?
3301 [['FileType', 'Document'],
3302 ['Command', command],
3303 ['Message', description],
3304 ['Outputs', outputs]
3306 if additional_inputs:
3307 action_spec.append(['AdditionalInputs', additional_inputs])
3308 actions_spec.append(action_spec)