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
26 # Regular expression for validating Visual Studio GUIDs. If the GUID
27 # contains lowercase hex letters, MSVS will be fine. However,
28 # IncrediBuild BuildConsole will parse the solution file, but then
29 # silently skip building the target causing hard to track down errors.
30 # Note that this only happens with the BuildConsole, and does not occur
31 # if IncrediBuild is executed from inside Visual Studio. This regex
32 # validates that the string looks like a GUID with all uppercase hex
34 VALID_MSVS_GUID_CHARS = re.compile('^[A-F0-9\-]+$')
37 generator_default_variables = {
38 'EXECUTABLE_PREFIX': '',
39 'EXECUTABLE_SUFFIX': '.exe',
40 'STATIC_LIB_PREFIX': '',
41 'SHARED_LIB_PREFIX': '',
42 'STATIC_LIB_SUFFIX': '.lib',
43 'SHARED_LIB_SUFFIX': '.dll',
44 'INTERMEDIATE_DIR': '$(IntDir)',
45 'SHARED_INTERMEDIATE_DIR': '$(OutDir)obj/global_intermediate',
47 'PRODUCT_DIR': '$(OutDir)',
48 'LIB_DIR': '$(OutDir)lib',
49 'RULE_INPUT_ROOT': '$(InputName)',
50 'RULE_INPUT_DIRNAME': '$(InputDir)',
51 'RULE_INPUT_EXT': '$(InputExt)',
52 'RULE_INPUT_NAME': '$(InputFileName)',
53 'RULE_INPUT_PATH': '$(InputPath)',
54 'CONFIGURATION_NAME': '$(ConfigurationName)',
58 # The msvs specific sections that hold paths
59 generator_additional_path_sections = [
65 generator_additional_non_configuration_keys = [
70 'msvs_external_builder',
71 'msvs_external_builder_out_dir',
72 'msvs_external_builder_build_cmd',
73 'msvs_external_builder_clean_cmd',
77 # List of precompiled header related keys.
79 'msvs_precompiled_header',
80 'msvs_precompiled_source',
84 cached_username = None
90 # Based on http://code.activestate.com/recipes/576694/.
91 class OrderedSet(collections.MutableSet):
92 def __init__(self, iterable=None):
94 end += [None, end, end] # sentinel node for doubly linked list
95 self.map = {} # key --> [key, prev, next]
96 if iterable is not None:
102 def discard(self, key):
104 key, prev, next = self.map.pop(key)
108 def __contains__(self, key):
109 return key in self.map
112 if key not in self.map:
115 curr[2] = end[1] = self.map[key] = [key, curr, end]
117 def update(self, iterable):
125 while curr is not end:
130 # TODO(gspencer): Switch the os.environ calls to be
131 # win32api.GetDomainName() and win32api.GetUserName() once the
132 # python version in depot_tools has been updated to work on Vista
134 def _GetDomainAndUserName():
135 if sys.platform not in ('win32', 'cygwin'):
136 return ('DOMAIN', 'USERNAME')
137 global cached_username
139 if not cached_domain or not cached_username:
140 domain = os.environ.get('USERDOMAIN')
141 username = os.environ.get('USERNAME')
142 if not domain or not username:
143 call = subprocess.Popen(['net', 'config', 'Workstation'],
144 stdout=subprocess.PIPE)
145 config = call.communicate()[0]
146 username_re = re.compile('^User name\s+(\S+)', re.MULTILINE)
147 username_match = username_re.search(config)
149 username = username_match.group(1)
150 domain_re = re.compile('^Logon domain\s+(\S+)', re.MULTILINE)
151 domain_match = domain_re.search(config)
153 domain = domain_match.group(1)
154 cached_domain = domain
155 cached_username = username
156 return (cached_domain, cached_username)
158 fixpath_prefix = None
161 def _NormalizedSource(source):
162 """Normalize the path.
164 But not if that gets rid of a variable, as this may expand to something
165 larger than one directory.
168 source: The path to be normalize.d
173 normalized = os.path.normpath(source)
174 if source.count('$') == normalized.count('$'):
180 """Convert paths to a form that will make sense in a vcproj file.
183 path: The path to convert, may contain / etc.
185 The path with all slashes made into backslashes.
187 if fixpath_prefix and path and not os.path.isabs(path) and not path[0] == '$':
188 path = os.path.join(fixpath_prefix, path)
189 path = path.replace('/', '\\')
190 path = _NormalizedSource(path)
191 if path and path[-1] == '\\':
196 def _FixPaths(paths):
197 """Fix each of the paths of the list."""
198 return [_FixPath(i) for i in paths]
201 def _ConvertSourcesToFilterHierarchy(sources, prefix=None, excluded=None,
203 """Converts a list split source file paths into a vcproj folder hierarchy.
206 sources: A list of source file paths split.
207 prefix: A list of source file path layers meant to apply to each of sources.
208 excluded: A set of excluded files.
211 A hierarchy of filenames and MSVSProject.Filter objects that matches the
212 layout of the source tree.
214 _ConvertSourcesToFilterHierarchy([['a', 'bob1.c'], ['b', 'bob2.c']],
217 [MSVSProject.Filter('a', contents=['joe\\a\\bob1.c']),
218 MSVSProject.Filter('b', contents=['joe\\b\\bob2.c'])]
220 if not prefix: prefix = []
223 folders = collections.OrderedDict()
224 # Gather files into the final result, excluded, or folders.
227 filename = _NormalizedSource('\\'.join(prefix + s))
228 if filename in excluded:
229 excluded_result.append(filename)
231 result.append(filename)
233 if not folders.get(s[0]):
235 folders[s[0]].append(s[1:])
236 # Add a folder for excluded files.
237 if excluded_result and list_excluded:
238 excluded_folder = MSVSProject.Filter('_excluded_files',
239 contents=excluded_result)
240 result.append(excluded_folder)
241 # Populate all the folders.
243 contents = _ConvertSourcesToFilterHierarchy(folders[f], prefix=prefix + [f],
245 list_excluded=list_excluded)
246 contents = MSVSProject.Filter(f, contents=contents)
247 result.append(contents)
252 def _ToolAppend(tools, tool_name, setting, value, only_if_unset=False):
254 _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset)
257 def _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset=False):
258 # TODO(bradnelson): ugly hack, fix this more generally!!!
259 if 'Directories' in setting or 'Dependencies' in setting:
260 if type(value) == str:
261 value = value.replace('/', '\\')
263 value = [i.replace('/', '\\') for i in value]
264 if not tools.get(tool_name):
265 tools[tool_name] = dict()
266 tool = tools[tool_name]
267 if tool.get(setting):
268 if only_if_unset: return
269 if type(tool[setting]) == list and type(value) == list:
270 tool[setting] += value
273 'Appending "%s" to a non-list setting "%s" for tool "%s" is '
274 'not allowed, previous value: %s' % (
275 value, setting, tool_name, str(tool[setting])))
277 tool[setting] = value
280 def _ConfigPlatform(config_data):
281 return config_data.get('msvs_configuration_platform', 'Win32')
284 def _ConfigBaseName(config_name, platform_name):
285 if config_name.endswith('_' + platform_name):
286 return config_name[0:-len(platform_name) - 1]
291 def _ConfigFullName(config_name, config_data):
292 platform_name = _ConfigPlatform(config_data)
293 return '%s|%s' % (_ConfigBaseName(config_name, platform_name), platform_name)
296 def _BuildCommandLineForRuleRaw(spec, cmd, cygwin_shell, has_input_path,
297 quote_cmd, do_setup_env):
299 if [x for x in cmd if '$(InputDir)' in x]:
300 input_dir_preamble = (
301 'set INPUTDIR=$(InputDir)\n'
302 'set INPUTDIR=%INPUTDIR:$(ProjectDir)=%\n'
303 'set INPUTDIR=%INPUTDIR:~0,-1%\n'
306 input_dir_preamble = ''
309 # Find path to cygwin.
310 cygwin_dir = _FixPath(spec.get('msvs_cygwin_dirs', ['.'])[0])
313 direct_cmd = [i.replace('$(IntDir)',
314 '`cygpath -m "${INTDIR}"`') for i in direct_cmd]
315 direct_cmd = [i.replace('$(OutDir)',
316 '`cygpath -m "${OUTDIR}"`') for i in direct_cmd]
317 direct_cmd = [i.replace('$(InputDir)',
318 '`cygpath -m "${INPUTDIR}"`') for i in direct_cmd]
320 direct_cmd = [i.replace('$(InputPath)',
321 '`cygpath -m "${INPUTPATH}"`')
323 direct_cmd = ['\\"%s\\"' % i.replace('"', '\\\\\\"') for i in direct_cmd]
324 # direct_cmd = gyp.common.EncodePOSIXShellList(direct_cmd)
325 direct_cmd = ' '.join(direct_cmd)
326 # TODO(quote): regularize quoting path names throughout the module
329 cmd += 'call "$(ProjectDir)%(cygwin_dir)s\\setup_env.bat" && '
330 cmd += 'set CYGWIN=nontsec&& '
331 if direct_cmd.find('NUMBER_OF_PROCESSORS') >= 0:
332 cmd += 'set /a NUMBER_OF_PROCESSORS_PLUS_1=%%NUMBER_OF_PROCESSORS%%+1&& '
333 if direct_cmd.find('INTDIR') >= 0:
334 cmd += 'set INTDIR=$(IntDir)&& '
335 if direct_cmd.find('OUTDIR') >= 0:
336 cmd += 'set OUTDIR=$(OutDir)&& '
337 if has_input_path and direct_cmd.find('INPUTPATH') >= 0:
338 cmd += 'set INPUTPATH=$(InputPath) && '
339 cmd += 'bash -c "%(cmd)s"'
340 cmd = cmd % {'cygwin_dir': cygwin_dir,
342 return input_dir_preamble + cmd
344 # Convert cat --> type to mimic unix.
348 command = [cmd[0].replace('/', '\\')]
349 # Add call before command to ensure that commands can be tied together one
350 # after the other without aborting in Incredibuild, since IB makes a bat
351 # file out of the raw command string, and some commands (like python) are
352 # actually batch files themselves.
353 command.insert(0, 'call')
355 # TODO(quote): This is a really ugly heuristic, and will miss path fixing
356 # for arguments like "--arg=path" or "/opt:path".
357 # If the argument starts with a slash or dash, it's probably a command line
359 arguments = [i if (i[:1] in "/-") else _FixPath(i) for i in cmd[1:]]
360 arguments = [i.replace('$(InputDir)', '%INPUTDIR%') for i in arguments]
361 arguments = [MSVSSettings.FixVCMacroSlashes(i) for i in arguments]
363 # Support a mode for using cmd directly.
364 # Convert any paths to native form (first element is used directly).
365 # TODO(quote): regularize quoting path names throughout the module
366 arguments = ['"%s"' % i for i in arguments]
367 # Collapse into a single command.
368 return input_dir_preamble + ' '.join(command + arguments)
371 def _BuildCommandLineForRule(spec, rule, has_input_path, do_setup_env):
372 # Currently this weird argument munging is used to duplicate the way a
373 # python script would need to be run as part of the chrome tree.
374 # Eventually we should add some sort of rule_default option to set this
375 # per project. For now the behavior chrome needs is the default.
376 mcs = rule.get('msvs_cygwin_shell')
378 mcs = int(spec.get('msvs_cygwin_shell', 1))
379 elif isinstance(mcs, str):
381 quote_cmd = int(rule.get('msvs_quote_cmd', 1))
382 return _BuildCommandLineForRuleRaw(spec, rule['action'], mcs, has_input_path,
383 quote_cmd, do_setup_env=do_setup_env)
386 def _AddActionStep(actions_dict, inputs, outputs, description, command):
387 """Merge action into an existing list of actions.
389 Care must be taken so that actions which have overlapping inputs either don't
390 get assigned to the same input, or get collapsed into one.
393 actions_dict: dictionary keyed on input name, which maps to a list of
394 dicts describing the actions attached to that input file.
395 inputs: list of inputs
396 outputs: list of outputs
397 description: description of the action
398 command: command line to execute
400 # Require there to be at least one input (call sites will ensure this).
406 'description': description,
410 # Pick where to stick this action.
411 # While less than optimal in terms of build time, attach them to the first
413 chosen_input = inputs[0]
416 if chosen_input not in actions_dict:
417 actions_dict[chosen_input] = []
418 actions_dict[chosen_input].append(action)
421 def _AddCustomBuildToolForMSVS(p, spec, primary_input,
422 inputs, outputs, description, cmd):
423 """Add a custom build tool to execute something.
426 p: the target project
427 spec: the target project dict
428 primary_input: input file to attach the build tool to
429 inputs: list of inputs
430 outputs: list of outputs
431 description: description of the action
432 cmd: command line to execute
434 inputs = _FixPaths(inputs)
435 outputs = _FixPaths(outputs)
436 tool = MSVSProject.Tool(
438 {'Description': description,
439 'AdditionalDependencies': ';'.join(inputs),
440 'Outputs': ';'.join(outputs),
443 # Add to the properties of primary input for each config.
444 for config_name, c_data in spec['configurations'].iteritems():
445 p.AddFileConfig(_FixPath(primary_input),
446 _ConfigFullName(config_name, c_data), tools=[tool])
449 def _AddAccumulatedActionsToMSVS(p, spec, actions_dict):
450 """Add actions accumulated into an actions_dict, merging as needed.
453 p: the target project
454 spec: the target project dict
455 actions_dict: dictionary keyed on input name, which maps to a list of
456 dicts describing the actions attached to that input file.
458 for primary_input in actions_dict:
459 inputs = OrderedSet()
460 outputs = OrderedSet()
463 for action in actions_dict[primary_input]:
464 inputs.update(OrderedSet(action['inputs']))
465 outputs.update(OrderedSet(action['outputs']))
466 descriptions.append(action['description'])
467 commands.append(action['command'])
468 # Add the custom build step for one input file.
469 description = ', and also '.join(descriptions)
470 command = '\r\n'.join(commands)
471 _AddCustomBuildToolForMSVS(p, spec,
472 primary_input=primary_input,
475 description=description,
479 def _RuleExpandPath(path, input_file):
480 """Given the input file to which a rule applied, string substitute a path.
483 path: a path to string expand
484 input_file: the file to which the rule applied.
486 The string substituted path.
488 path = path.replace('$(InputName)',
489 os.path.splitext(os.path.split(input_file)[1])[0])
490 path = path.replace('$(InputDir)', os.path.dirname(input_file))
491 path = path.replace('$(InputExt)',
492 os.path.splitext(os.path.split(input_file)[1])[1])
493 path = path.replace('$(InputFileName)', os.path.split(input_file)[1])
494 path = path.replace('$(InputPath)', input_file)
498 def _FindRuleTriggerFiles(rule, sources):
499 """Find the list of files which a particular rule applies to.
502 rule: the rule in question
503 sources: the set of all known source files for this project
505 The list of sources that trigger a particular rule.
507 return rule.get('rule_sources', [])
510 def _RuleInputsAndOutputs(rule, trigger_file):
511 """Find the inputs and outputs generated by a rule.
514 rule: the rule in question.
515 trigger_file: the main trigger for this rule.
517 The pair of (inputs, outputs) involved in this rule.
519 raw_inputs = _FixPaths(rule.get('inputs', []))
520 raw_outputs = _FixPaths(rule.get('outputs', []))
521 inputs = OrderedSet()
522 outputs = OrderedSet()
523 inputs.add(trigger_file)
525 inputs.add(_RuleExpandPath(i, trigger_file))
526 for o in raw_outputs:
527 outputs.add(_RuleExpandPath(o, trigger_file))
528 return (inputs, outputs)
531 def _GenerateNativeRulesForMSVS(p, rules, output_dir, spec, options):
532 """Generate a native rules file.
535 p: the target project
536 rules: the set of rules to include
537 output_dir: the directory in which the project/gyp resides
538 spec: the project dict
539 options: global generator options
541 rules_filename = '%s%s.rules' % (spec['target_name'],
543 rules_file = MSVSToolFile.Writer(os.path.join(output_dir, rules_filename),
547 rule_name = r['rule_name']
548 rule_ext = r['extension']
549 inputs = _FixPaths(r.get('inputs', []))
550 outputs = _FixPaths(r.get('outputs', []))
551 # Skip a rule with no action and no inputs.
552 if 'action' not in r and not r.get('rule_sources', []):
554 cmd = _BuildCommandLineForRule(spec, r, has_input_path=True,
556 rules_file.AddCustomBuildRule(name=rule_name,
557 description=r.get('message', rule_name),
558 extensions=[rule_ext],
559 additional_dependencies=inputs,
562 # Write out rules file.
563 rules_file.WriteIfChanged()
565 # Add rules file to project.
566 p.AddToolFile(rules_filename)
569 def _Cygwinify(path):
570 path = path.replace('$(OutDir)', '$(OutDirCygwin)')
571 path = path.replace('$(IntDir)', '$(IntDirCygwin)')
575 def _GenerateExternalRules(rules, output_dir, spec,
576 sources, options, actions_to_add):
577 """Generate an external makefile to do a set of rules.
580 rules: the list of rules to include
581 output_dir: path containing project and gyp files
582 spec: project specification data
583 sources: set of sources known
584 options: global generator options
585 actions_to_add: The list of actions we will add to.
587 filename = '%s_rules%s.mk' % (spec['target_name'], options.suffix)
588 mk_file = gyp.common.WriteOnDiff(os.path.join(output_dir, filename))
589 # Find cygwin style versions of some paths.
590 mk_file.write('OutDirCygwin:=$(shell cygpath -u "$(OutDir)")\n')
591 mk_file.write('IntDirCygwin:=$(shell cygpath -u "$(IntDir)")\n')
592 # Gather stuff needed to emit all: target.
593 all_inputs = OrderedSet()
594 all_outputs = OrderedSet()
595 all_output_dirs = OrderedSet()
598 trigger_files = _FindRuleTriggerFiles(rule, sources)
599 for tf in trigger_files:
600 inputs, outputs = _RuleInputsAndOutputs(rule, tf)
601 all_inputs.update(OrderedSet(inputs))
602 all_outputs.update(OrderedSet(outputs))
603 # Only use one target from each rule as the dependency for
604 # 'all' so we don't try to build each rule multiple times.
605 first_outputs.append(list(outputs)[0])
606 # Get the unique output directories for this rule.
607 output_dirs = [os.path.split(i)[0] for i in outputs]
608 for od in output_dirs:
609 all_output_dirs.add(od)
610 first_outputs_cyg = [_Cygwinify(i) for i in first_outputs]
611 # Write out all: target, including mkdir for each output directory.
612 mk_file.write('all: %s\n' % ' '.join(first_outputs_cyg))
613 for od in all_output_dirs:
615 mk_file.write('\tmkdir -p `cygpath -u "%s"`\n' % od)
617 # Define how each output is generated.
619 trigger_files = _FindRuleTriggerFiles(rule, sources)
620 for tf in trigger_files:
621 # Get all the inputs and outputs for this rule for this trigger file.
622 inputs, outputs = _RuleInputsAndOutputs(rule, tf)
623 inputs = [_Cygwinify(i) for i in inputs]
624 outputs = [_Cygwinify(i) for i in outputs]
625 # Prepare the command line for this rule.
626 cmd = [_RuleExpandPath(c, tf) for c in rule['action']]
627 cmd = ['"%s"' % i for i in cmd]
629 # Add it to the makefile.
630 mk_file.write('%s: %s\n' % (' '.join(outputs), ' '.join(inputs)))
631 mk_file.write('\t%s\n\n' % cmd)
635 # Add makefile to list of sources.
636 sources.add(filename)
637 # Add a build action to call makefile.
641 '-j', '${NUMBER_OF_PROCESSORS_PLUS_1}',
643 cmd = _BuildCommandLineForRuleRaw(spec, cmd, True, False, True, True)
644 # Insert makefile as 0'th input, so it gets the action attached there,
645 # as this is easier to understand from in the IDE.
646 all_inputs = list(all_inputs)
647 all_inputs.insert(0, filename)
648 _AddActionStep(actions_to_add,
649 inputs=_FixPaths(all_inputs),
650 outputs=_FixPaths(all_outputs),
651 description='Running external rules for %s' %
656 def _EscapeEnvironmentVariableExpansion(s):
657 """Escapes % characters.
659 Escapes any % characters so that Windows-style environment variable
660 expansions will leave them alone.
661 See http://connect.microsoft.com/VisualStudio/feedback/details/106127/cl-d-name-text-containing-percentage-characters-doesnt-compile
662 to understand why we have to do this.
665 s: The string to be escaped.
670 s = s.replace('%', '%%')
674 quote_replacer_regex = re.compile(r'(\\*)"')
677 def _EscapeCommandLineArgumentForMSVS(s):
678 """Escapes a Windows command-line argument.
680 So that the Win32 CommandLineToArgv function will turn the escaped result back
681 into the original string.
682 See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
683 ("Parsing C++ Command-Line Arguments") to understand why we have to do
687 s: the string to be escaped.
693 # For a literal quote, CommandLineToArgv requires an odd number of
694 # backslashes preceding it, and it produces half as many literal backslashes
695 # (rounded down). So we need to produce 2n+1 backslashes.
696 return 2 * match.group(1) + '\\"'
698 # Escape all quotes so that they are interpreted literally.
699 s = quote_replacer_regex.sub(_Replace, s)
700 # Now add unescaped quotes so that any whitespace is interpreted literally.
705 delimiters_replacer_regex = re.compile(r'(\\*)([,;]+)')
708 def _EscapeVCProjCommandLineArgListItem(s):
709 """Escapes command line arguments for MSVS.
711 The VCProj format stores string lists in a single string using commas and
712 semi-colons as separators, which must be quoted if they are to be
713 interpreted literally. However, command-line arguments may already have
714 quotes, and the VCProj parser is ignorant of the backslash escaping
715 convention used by CommandLineToArgv, so the command-line quotes and the
716 VCProj quotes may not be the same quotes. So to store a general
717 command-line argument in a VCProj list, we need to parse the existing
718 quoting according to VCProj's convention and quote any delimiters that are
719 not already quoted by that convention. The quotes that we add will also be
720 seen by CommandLineToArgv, so if backslashes precede them then we also have
721 to escape those backslashes according to the CommandLineToArgv
725 s: the string to be escaped.
731 # For a non-literal quote, CommandLineToArgv requires an even number of
732 # backslashes preceding it, and it produces half as many literal
733 # backslashes. So we need to produce 2n backslashes.
734 return 2 * match.group(1) + '"' + match.group(2) + '"'
736 segments = s.split('"')
737 # The unquoted segments are at the even-numbered indices.
738 for i in range(0, len(segments), 2):
739 segments[i] = delimiters_replacer_regex.sub(_Replace, segments[i])
740 # Concatenate back into a single string
741 s = '"'.join(segments)
742 if len(segments) % 2 == 0:
743 # String ends while still quoted according to VCProj's convention. This
744 # means the delimiter and the next list item that follow this one in the
745 # .vcproj file will be misinterpreted as part of this item. There is nothing
746 # we can do about this. Adding an extra quote would correct the problem in
747 # the VCProj but cause the same problem on the final command-line. Moving
748 # the item to the end of the list does works, but that's only possible if
749 # there's only one such item. Let's just warn the user.
750 print >> sys.stderr, ('Warning: MSVS may misinterpret the odd number of ' +
755 def _EscapeCppDefineForMSVS(s):
756 """Escapes a CPP define so that it will reach the compiler unaltered."""
757 s = _EscapeEnvironmentVariableExpansion(s)
758 s = _EscapeCommandLineArgumentForMSVS(s)
759 s = _EscapeVCProjCommandLineArgListItem(s)
760 # cl.exe replaces literal # characters with = in preprocesor definitions for
761 # some reason. Octal-encode to work around that.
762 s = s.replace('#', '\\%03o' % ord('#'))
766 quote_replacer_regex2 = re.compile(r'(\\+)"')
769 def _EscapeCommandLineArgumentForMSBuild(s):
770 """Escapes a Windows command-line argument for use by MSBuild."""
773 return (len(match.group(1)) / 2 * 4) * '\\' + '\\"'
775 # Escape all quotes so that they are interpreted literally.
776 s = quote_replacer_regex2.sub(_Replace, s)
780 def _EscapeMSBuildSpecialCharacters(s):
781 escape_dictionary = {
790 result = ''.join([escape_dictionary.get(c, c) for c in s])
794 def _EscapeCppDefineForMSBuild(s):
795 """Escapes a CPP define so that it will reach the compiler unaltered."""
796 s = _EscapeEnvironmentVariableExpansion(s)
797 s = _EscapeCommandLineArgumentForMSBuild(s)
798 s = _EscapeMSBuildSpecialCharacters(s)
799 # cl.exe replaces literal # characters with = in preprocesor definitions for
800 # some reason. Octal-encode to work around that.
801 s = s.replace('#', '\\%03o' % ord('#'))
805 def _GenerateRulesForMSVS(p, output_dir, options, spec,
806 sources, excluded_sources,
808 """Generate all the rules for a particular project.
812 output_dir: directory to emit rules to
813 options: global options passed to the generator
814 spec: the specification for this project
815 sources: the set of all known source files in this project
816 excluded_sources: the set of sources excluded from normal processing
817 actions_to_add: deferred list of actions to add in
819 rules = spec.get('rules', [])
820 rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
821 rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
823 # Handle rules that use a native rules file.
825 _GenerateNativeRulesForMSVS(p, rules_native, output_dir, spec, options)
827 # Handle external rules (non-native rules).
829 _GenerateExternalRules(rules_external, output_dir, spec,
830 sources, options, actions_to_add)
831 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
834 def _AdjustSourcesForRules(spec, rules, sources, excluded_sources):
835 # Add outputs generated by each rule (if applicable).
837 # Done if not processing outputs as sources.
838 if int(rule.get('process_outputs_as_sources', False)):
839 # Add in the outputs from this rule.
840 trigger_files = _FindRuleTriggerFiles(rule, sources)
841 for trigger_file in trigger_files:
842 inputs, outputs = _RuleInputsAndOutputs(rule, trigger_file)
843 inputs = OrderedSet(_FixPaths(inputs))
844 outputs = OrderedSet(_FixPaths(outputs))
845 inputs.remove(_FixPath(trigger_file))
846 sources.update(inputs)
847 if not spec.get('msvs_external_builder'):
848 excluded_sources.update(inputs)
849 sources.update(outputs)
852 def _FilterActionsFromExcluded(excluded_sources, actions_to_add):
853 """Take inputs with actions attached out of the list of exclusions.
856 excluded_sources: list of source files not to be built.
857 actions_to_add: dict of actions keyed on source file they're attached to.
859 excluded_sources with files that have actions attached removed.
861 must_keep = OrderedSet(_FixPaths(actions_to_add.keys()))
862 return [s for s in excluded_sources if s not in must_keep]
865 def _GetDefaultConfiguration(spec):
866 return spec['configurations'][spec['default_configuration']]
869 def _GetGuidOfProject(proj_path, spec):
870 """Get the guid for the project.
873 proj_path: Path of the vcproj or vcxproj file to generate.
874 spec: The target dictionary containing the properties of the target.
878 ValueError: if the specified GUID is invalid.
880 # Pluck out the default configuration.
881 default_config = _GetDefaultConfiguration(spec)
882 # Decide the guid of the project.
883 guid = default_config.get('msvs_guid')
885 if VALID_MSVS_GUID_CHARS.match(guid) is None:
886 raise ValueError('Invalid MSVS guid: "%s". Must match regex: "%s".' %
887 (guid, VALID_MSVS_GUID_CHARS.pattern))
889 guid = guid or MSVSNew.MakeGuid(proj_path)
893 def _GetMsbuildToolsetOfProject(proj_path, spec, version):
894 """Get the platform toolset for the project.
897 proj_path: Path of the vcproj or vcxproj file to generate.
898 spec: The target dictionary containing the properties of the target.
899 version: The MSVSVersion object.
901 the platform toolset string or None.
903 # Pluck out the default configuration.
904 default_config = _GetDefaultConfiguration(spec)
905 toolset = default_config.get('msbuild_toolset')
906 if not toolset and version.DefaultToolset():
907 toolset = version.DefaultToolset()
911 def _GenerateProject(project, options, version, generator_flags):
912 """Generates a vcproj file.
915 project: the MSVSProject object.
916 options: global generator options.
917 version: the MSVSVersion object.
918 generator_flags: dict of generator-specific flags.
920 A list of source files that cannot be found on disk.
922 default_config = _GetDefaultConfiguration(project.spec)
924 # Skip emitting anything if told to with msvs_existing_vcproj option.
925 if default_config.get('msvs_existing_vcproj'):
928 if version.UsesVcxproj():
929 return _GenerateMSBuildProject(project, options, version, generator_flags)
931 return _GenerateMSVSProject(project, options, version, generator_flags)
934 def _GenerateMSVSProject(project, options, version, generator_flags):
935 """Generates a .vcproj file. It may create .rules and .user files too.
938 project: The project object we will generate the file for.
939 options: Global options passed to the generator.
940 version: The VisualStudioVersion object.
941 generator_flags: dict of generator-specific flags.
944 vcproj_dir = os.path.dirname(project.path)
945 if vcproj_dir and not os.path.exists(vcproj_dir):
946 os.makedirs(vcproj_dir)
948 platforms = _GetUniquePlatforms(spec)
949 p = MSVSProject.Writer(project.path, version, spec['target_name'],
950 project.guid, platforms)
952 # Get directory project file is in.
953 project_dir = os.path.split(project.path)[0]
954 gyp_path = _NormalizedSource(project.build_file)
955 relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
957 config_type = _GetMSVSConfigurationType(spec, project.build_file)
958 for config_name, config in spec['configurations'].iteritems():
959 _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config)
961 # Prepare list of sources and excluded sources.
962 gyp_file = os.path.split(project.build_file)[1]
963 sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
968 _GenerateRulesForMSVS(p, project_dir, options, spec,
969 sources, excluded_sources,
971 list_excluded = generator_flags.get('msvs_list_excluded_files', True)
972 sources, excluded_sources, excluded_idl = (
973 _AdjustSourcesAndConvertToFilterHierarchy(
974 spec, options, project_dir, sources, excluded_sources, list_excluded))
977 missing_sources = _VerifySourcesExist(sources, project_dir)
980 _AddToolFilesToMSVS(p, spec)
981 _HandlePreCompiledHeaders(p, sources, spec)
982 _AddActions(actions_to_add, spec, relative_path_of_gyp_file)
983 _AddCopies(actions_to_add, spec)
984 _WriteMSVSUserFile(project.path, version, spec)
986 # NOTE: this stanza must appear after all actions have been decided.
987 # Don't excluded sources with actions attached, or they won't run.
988 excluded_sources = _FilterActionsFromExcluded(
989 excluded_sources, actions_to_add)
990 _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
992 _AddAccumulatedActionsToMSVS(p, spec, actions_to_add)
997 return missing_sources
1000 def _GetUniquePlatforms(spec):
1001 """Returns the list of unique platforms for this spec, e.g ['win32', ...].
1004 spec: The target dictionary containing the properties of the target.
1006 The MSVSUserFile object created.
1008 # Gather list of unique platforms.
1009 platforms = OrderedSet()
1010 for configuration in spec['configurations']:
1011 platforms.add(_ConfigPlatform(spec['configurations'][configuration]))
1012 platforms = list(platforms)
1016 def _CreateMSVSUserFile(proj_path, version, spec):
1017 """Generates a .user file for the user running this Gyp program.
1020 proj_path: The path of the project file being created. The .user file
1021 shares the same path (with an appropriate suffix).
1022 version: The VisualStudioVersion object.
1023 spec: The target dictionary containing the properties of the target.
1025 The MSVSUserFile object created.
1027 (domain, username) = _GetDomainAndUserName()
1028 vcuser_filename = '.'.join([proj_path, domain, username, 'user'])
1029 user_file = MSVSUserFile.Writer(vcuser_filename, version,
1030 spec['target_name'])
1034 def _GetMSVSConfigurationType(spec, build_file):
1035 """Returns the configuration type for this project.
1037 It's a number defined by Microsoft. May raise an exception.
1040 spec: The target dictionary containing the properties of the target.
1041 build_file: The path of the gyp file.
1043 An integer, the configuration type.
1047 'executable': '1', # .exe
1048 'shared_library': '2', # .dll
1049 'loadable_module': '2', # .dll
1050 'static_library': '4', # .lib
1051 'none': '10', # Utility type
1054 if spec.get('type'):
1055 raise GypError('Target type %s is not a valid target type for '
1056 'target %s in %s.' %
1057 (spec['type'], spec['target_name'], build_file))
1059 raise GypError('Missing type field for target %s in %s.' %
1060 (spec['target_name'], build_file))
1064 def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config):
1065 """Adds a configuration to the MSVS project.
1067 Many settings in a vcproj file are specific to a configuration. This
1068 function the main part of the vcproj file that's configuration specific.
1071 p: The target project being generated.
1072 spec: The target dictionary containing the properties of the target.
1073 config_type: The configuration type, a number as defined by Microsoft.
1074 config_name: The name of the configuration.
1075 config: The dictionary that defines the special processing to be done
1076 for this configuration.
1078 # Get the information for this configuration
1079 include_dirs, resource_include_dirs = _GetIncludeDirs(config)
1080 libraries = _GetLibraries(spec)
1081 library_dirs = _GetLibraryDirs(config)
1082 out_file, vc_tool, _ = _GetOutputFilePathAndTool(spec, msbuild=False)
1083 defines = _GetDefines(config)
1084 defines = [_EscapeCppDefineForMSVS(d) for d in defines]
1085 disabled_warnings = _GetDisabledWarnings(config)
1086 prebuild = config.get('msvs_prebuild')
1087 postbuild = config.get('msvs_postbuild')
1088 def_file = _GetModuleDefinition(spec)
1089 precompiled_header = config.get('msvs_precompiled_header')
1091 # Prepare the list of tools as a dictionary.
1093 # Add in user specified msvs_settings.
1094 msvs_settings = config.get('msvs_settings', {})
1095 MSVSSettings.ValidateMSVSSettings(msvs_settings)
1097 # Prevent default library inheritance from the environment.
1098 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', ['$(NOINHERIT)'])
1100 for tool in msvs_settings:
1101 settings = config['msvs_settings'][tool]
1102 for setting in settings:
1103 _ToolAppend(tools, tool, setting, settings[setting])
1104 # Add the information to the appropriate tool
1105 _ToolAppend(tools, 'VCCLCompilerTool',
1106 'AdditionalIncludeDirectories', include_dirs)
1107 _ToolAppend(tools, 'VCResourceCompilerTool',
1108 'AdditionalIncludeDirectories', resource_include_dirs)
1110 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', libraries)
1111 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalLibraryDirectories',
1114 _ToolAppend(tools, vc_tool, 'OutputFile', out_file, only_if_unset=True)
1116 _ToolAppend(tools, 'VCCLCompilerTool', 'PreprocessorDefinitions', defines)
1117 _ToolAppend(tools, 'VCResourceCompilerTool', 'PreprocessorDefinitions',
1119 # Change program database directory to prevent collisions.
1120 _ToolAppend(tools, 'VCCLCompilerTool', 'ProgramDataBaseFileName',
1121 '$(IntDir)$(ProjectName)\\vc80.pdb', only_if_unset=True)
1122 # Add disabled warnings.
1123 _ToolAppend(tools, 'VCCLCompilerTool',
1124 'DisableSpecificWarnings', disabled_warnings)
1126 _ToolAppend(tools, 'VCPreBuildEventTool', 'CommandLine', prebuild)
1128 _ToolAppend(tools, 'VCPostBuildEventTool', 'CommandLine', postbuild)
1129 # Turn on precompiled headers if appropriate.
1130 if precompiled_header:
1131 precompiled_header = os.path.split(precompiled_header)[1]
1132 _ToolAppend(tools, 'VCCLCompilerTool', 'UsePrecompiledHeader', '2')
1133 _ToolAppend(tools, 'VCCLCompilerTool',
1134 'PrecompiledHeaderThrough', precompiled_header)
1135 _ToolAppend(tools, 'VCCLCompilerTool',
1136 'ForcedIncludeFiles', precompiled_header)
1137 # Loadable modules don't generate import libraries;
1138 # tell dependent projects to not expect one.
1139 if spec['type'] == 'loadable_module':
1140 _ToolAppend(tools, 'VCLinkerTool', 'IgnoreImportLibrary', 'true')
1141 # Set the module definition file if any.
1143 _ToolAppend(tools, 'VCLinkerTool', 'ModuleDefinitionFile', def_file)
1145 _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name)
1148 def _GetIncludeDirs(config):
1149 """Returns the list of directories to be used for #include directives.
1152 config: The dictionary that defines the special processing to be done
1153 for this configuration.
1155 The list of directory paths.
1157 # TODO(bradnelson): include_dirs should really be flexible enough not to
1158 # require this sort of thing.
1160 config.get('include_dirs', []) +
1161 config.get('msvs_system_include_dirs', []))
1162 resource_include_dirs = config.get('resource_include_dirs', include_dirs)
1163 include_dirs = _FixPaths(include_dirs)
1164 resource_include_dirs = _FixPaths(resource_include_dirs)
1165 return include_dirs, resource_include_dirs
1168 def _GetLibraryDirs(config):
1169 """Returns the list of directories to be used for library search paths.
1172 config: The dictionary that defines the special processing to be done
1173 for this configuration.
1175 The list of directory paths.
1178 library_dirs = config.get('library_dirs', [])
1179 library_dirs = _FixPaths(library_dirs)
1183 def _GetLibraries(spec):
1184 """Returns the list of libraries for this configuration.
1187 spec: The target dictionary containing the properties of the target.
1189 The list of directory paths.
1191 libraries = spec.get('libraries', [])
1192 # Strip out -l, as it is not used on windows (but is needed so we can pass
1193 # in libraries that are assumed to be in the default library path).
1194 # Also remove duplicate entries, leaving only the last duplicate, while
1196 found = OrderedSet()
1197 unique_libraries_list = []
1198 for entry in reversed(libraries):
1199 library = re.sub('^\-l', '', entry)
1200 if not os.path.splitext(library)[1]:
1202 if library not in found:
1204 unique_libraries_list.append(library)
1205 unique_libraries_list.reverse()
1206 return unique_libraries_list
1209 def _GetOutputFilePathAndTool(spec, msbuild):
1210 """Returns the path and tool to use for this target.
1212 Figures out the path of the file this spec will create and the name of
1213 the VC tool that will create it.
1216 spec: The target dictionary containing the properties of the target.
1218 A triple of (file path, name of the vc tool, name of the msbuild tool)
1220 # Select a name for the output file.
1225 'executable': ('VCLinkerTool', 'Link', '$(OutDir)', '.exe'),
1226 'shared_library': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
1227 'loadable_module': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
1228 'static_library': ('VCLibrarianTool', 'Lib', '$(OutDir)lib\\', '.lib'),
1230 output_file_props = output_file_map.get(spec['type'])
1231 if output_file_props and int(spec.get('msvs_auto_output_file', 1)):
1232 vc_tool, msbuild_tool, out_dir, suffix = output_file_props
1233 if spec.get('standalone_static_library', 0):
1234 out_dir = '$(OutDir)'
1235 out_dir = spec.get('product_dir', out_dir)
1236 product_extension = spec.get('product_extension')
1237 if product_extension:
1238 suffix = '.' + product_extension
1240 suffix = '$(TargetExt)'
1241 prefix = spec.get('product_prefix', '')
1242 product_name = spec.get('product_name', '$(ProjectName)')
1243 out_file = ntpath.join(out_dir, prefix + product_name + suffix)
1244 return out_file, vc_tool, msbuild_tool
1247 def _GetOutputTargetExt(spec):
1248 """Returns the extension for this target, including the dot
1250 If product_extension is specified, set target_extension to this to avoid
1251 MSB8012, returns None otherwise. Ignores any target_extension settings in
1255 spec: The target dictionary containing the properties of the target.
1257 A string with the extension, or None
1259 target_extension = spec.get('product_extension')
1260 if target_extension:
1261 return '.' + target_extension
1265 def _GetDefines(config):
1266 """Returns the list of preprocessor definitions for this configuation.
1269 config: The dictionary that defines the special processing to be done
1270 for this configuration.
1272 The list of preprocessor definitions.
1275 for d in config.get('defines', []):
1277 fd = '='.join([str(dpart) for dpart in d])
1284 def _GetDisabledWarnings(config):
1285 return [str(i) for i in config.get('msvs_disabled_warnings', [])]
1288 def _GetModuleDefinition(spec):
1290 if spec['type'] in ['shared_library', 'loadable_module', 'executable']:
1291 def_files = [s for s in spec.get('sources', []) if s.endswith('.def')]
1292 if len(def_files) == 1:
1293 def_file = _FixPath(def_files[0])
1296 'Multiple module definition files in one target, target %s lists '
1297 'multiple .def files: %s' % (
1298 spec['target_name'], ' '.join(def_files)))
1302 def _ConvertToolsToExpectedForm(tools):
1303 """Convert tools to a form expected by Visual Studio.
1306 tools: A dictionary of settings; the tool name is the key.
1308 A list of Tool objects.
1311 for tool, settings in tools.iteritems():
1312 # Collapse settings with lists.
1314 for setting, value in settings.iteritems():
1315 if type(value) == list:
1316 if ((tool == 'VCLinkerTool' and
1317 setting == 'AdditionalDependencies') or
1318 setting == 'AdditionalOptions'):
1319 settings_fixed[setting] = ' '.join(value)
1321 settings_fixed[setting] = ';'.join(value)
1323 settings_fixed[setting] = value
1325 tool_list.append(MSVSProject.Tool(tool, settings_fixed))
1329 def _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name):
1330 """Add to the project file the configuration specified by config.
1333 p: The target project being generated.
1334 spec: the target project dict.
1335 tools: A dictionary of settings; the tool name is the key.
1336 config: The dictionary that defines the special processing to be done
1337 for this configuration.
1338 config_type: The configuration type, a number as defined by Microsoft.
1339 config_name: The name of the configuration.
1341 attributes = _GetMSVSAttributes(spec, config, config_type)
1342 # Add in this configuration.
1343 tool_list = _ConvertToolsToExpectedForm(tools)
1344 p.AddConfig(_ConfigFullName(config_name, config),
1345 attrs=attributes, tools=tool_list)
1348 def _GetMSVSAttributes(spec, config, config_type):
1349 # Prepare configuration attributes.
1351 source_attrs = config.get('msvs_configuration_attributes', {})
1352 for a in source_attrs:
1353 prepared_attrs[a] = source_attrs[a]
1355 vsprops_dirs = config.get('msvs_props', [])
1356 vsprops_dirs = _FixPaths(vsprops_dirs)
1358 prepared_attrs['InheritedPropertySheets'] = ';'.join(vsprops_dirs)
1359 # Set configuration type.
1360 prepared_attrs['ConfigurationType'] = config_type
1361 output_dir = prepared_attrs.get('OutputDirectory',
1362 '$(SolutionDir)$(ConfigurationName)')
1363 prepared_attrs['OutputDirectory'] = _FixPath(output_dir) + '\\'
1364 if 'IntermediateDirectory' not in prepared_attrs:
1365 intermediate = '$(ConfigurationName)\\obj\\$(ProjectName)'
1366 prepared_attrs['IntermediateDirectory'] = _FixPath(intermediate) + '\\'
1368 intermediate = _FixPath(prepared_attrs['IntermediateDirectory']) + '\\'
1369 intermediate = MSVSSettings.FixVCMacroSlashes(intermediate)
1370 prepared_attrs['IntermediateDirectory'] = intermediate
1371 return prepared_attrs
1374 def _AddNormalizedSources(sources_set, sources_array):
1375 sources_set.update(_NormalizedSource(s) for s in sources_array)
1378 def _PrepareListOfSources(spec, generator_flags, gyp_file):
1379 """Prepare list of sources and excluded sources.
1381 Besides the sources specified directly in the spec, adds the gyp file so
1382 that a change to it will cause a re-compile. Also adds appropriate sources
1383 for actions and copies. Assumes later stage will un-exclude files which
1384 have custom build steps attached.
1387 spec: The target dictionary containing the properties of the target.
1388 gyp_file: The name of the gyp file.
1390 A pair of (list of sources, list of excluded sources).
1391 The sources will be relative to the gyp file.
1393 sources = OrderedSet()
1394 _AddNormalizedSources(sources, spec.get('sources', []))
1395 excluded_sources = OrderedSet()
1396 # Add in the gyp file.
1397 if not generator_flags.get('standalone'):
1398 sources.add(gyp_file)
1400 # Add in 'action' inputs and outputs.
1401 for a in spec.get('actions', []):
1402 inputs = a['inputs']
1403 inputs = [_NormalizedSource(i) for i in inputs]
1404 # Add all inputs to sources and excluded sources.
1405 inputs = OrderedSet(inputs)
1406 sources.update(inputs)
1407 if not spec.get('msvs_external_builder'):
1408 excluded_sources.update(inputs)
1409 if int(a.get('process_outputs_as_sources', False)):
1410 _AddNormalizedSources(sources, a.get('outputs', []))
1411 # Add in 'copies' inputs and outputs.
1412 for cpy in spec.get('copies', []):
1413 _AddNormalizedSources(sources, cpy.get('files', []))
1414 return (sources, excluded_sources)
1417 def _AdjustSourcesAndConvertToFilterHierarchy(
1418 spec, options, gyp_dir, sources, excluded_sources, list_excluded):
1419 """Adjusts the list of sources and excluded sources.
1421 Also converts the sets to lists.
1424 spec: The target dictionary containing the properties of the target.
1425 options: Global generator options.
1426 gyp_dir: The path to the gyp file being processed.
1427 sources: A set of sources to be included for this project.
1428 excluded_sources: A set of sources to be excluded for this project.
1430 A trio of (list of sources, list of excluded sources,
1431 path of excluded IDL file)
1433 # Exclude excluded sources coming into the generator.
1434 excluded_sources.update(OrderedSet(spec.get('sources_excluded', [])))
1435 # Add excluded sources into sources for good measure.
1436 sources.update(excluded_sources)
1437 # Convert to proper windows form.
1438 # NOTE: sources goes from being a set to a list here.
1439 # NOTE: excluded_sources goes from being a set to a list here.
1440 sources = _FixPaths(sources)
1441 # Convert to proper windows form.
1442 excluded_sources = _FixPaths(excluded_sources)
1444 excluded_idl = _IdlFilesHandledNonNatively(spec, sources)
1446 precompiled_related = _GetPrecompileRelatedFiles(spec)
1447 # Find the excluded ones, minus the precompiled header related ones.
1448 fully_excluded = [i for i in excluded_sources if i not in precompiled_related]
1450 # Convert to folders and the right slashes.
1451 sources = [i.split('\\') for i in sources]
1452 sources = _ConvertSourcesToFilterHierarchy(sources, excluded=fully_excluded,
1453 list_excluded=list_excluded)
1455 # Prune filters with a single child to flatten ugly directory structures
1456 # such as ../../src/modules/module1 etc.
1457 while len(sources) == 1 and isinstance(sources[0], MSVSProject.Filter):
1458 sources = sources[0].contents
1460 return sources, excluded_sources, excluded_idl
1463 def _IdlFilesHandledNonNatively(spec, sources):
1464 # If any non-native rules use 'idl' as an extension exclude idl files.
1465 # Gather a list here to use later.
1467 for rule in spec.get('rules', []):
1468 if rule['extension'] == 'idl' and int(rule.get('msvs_external_rule', 0)):
1472 excluded_idl = [i for i in sources if i.endswith('.idl')]
1478 def _GetPrecompileRelatedFiles(spec):
1479 # Gather a list of precompiled header related sources.
1480 precompiled_related = []
1481 for _, config in spec['configurations'].iteritems():
1482 for k in precomp_keys:
1485 precompiled_related.append(_FixPath(f))
1486 return precompiled_related
1489 def _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
1491 exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
1492 for file_name, excluded_configs in exclusions.iteritems():
1493 if (not list_excluded and
1494 len(excluded_configs) == len(spec['configurations'])):
1495 # If we're not listing excluded files, then they won't appear in the
1496 # project, so don't try to configure them to be excluded.
1499 for config_name, config in excluded_configs:
1500 p.AddFileConfig(file_name, _ConfigFullName(config_name, config),
1501 {'ExcludedFromBuild': 'true'})
1504 def _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl):
1506 # Exclude excluded sources from being built.
1507 for f in excluded_sources:
1508 excluded_configs = []
1509 for config_name, config in spec['configurations'].iteritems():
1510 precomped = [_FixPath(config.get(i, '')) for i in precomp_keys]
1511 # Don't do this for ones that are precompiled header related.
1512 if f not in precomped:
1513 excluded_configs.append((config_name, config))
1514 exclusions[f] = excluded_configs
1515 # If any non-native rules use 'idl' as an extension exclude idl files.
1517 for f in excluded_idl:
1518 excluded_configs = []
1519 for config_name, config in spec['configurations'].iteritems():
1520 excluded_configs.append((config_name, config))
1521 exclusions[f] = excluded_configs
1525 def _AddToolFilesToMSVS(p, spec):
1526 # Add in tool files (rules).
1527 tool_files = OrderedSet()
1528 for _, config in spec['configurations'].iteritems():
1529 for f in config.get('msvs_tool_files', []):
1531 for f in tool_files:
1535 def _HandlePreCompiledHeaders(p, sources, spec):
1536 # Pre-compiled header source stubs need a different compiler flag
1537 # (generate precompiled header) and any source file not of the same
1538 # kind (i.e. C vs. C++) as the precompiled header source stub needs
1539 # to have use of precompiled headers disabled.
1540 extensions_excluded_from_precompile = []
1541 for config_name, config in spec['configurations'].iteritems():
1542 source = config.get('msvs_precompiled_source')
1544 source = _FixPath(source)
1545 # UsePrecompiledHeader=1 for if using precompiled headers.
1546 tool = MSVSProject.Tool('VCCLCompilerTool',
1547 {'UsePrecompiledHeader': '1'})
1548 p.AddFileConfig(source, _ConfigFullName(config_name, config),
1550 basename, extension = os.path.splitext(source)
1551 if extension == '.c':
1552 extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
1554 extensions_excluded_from_precompile = ['.c']
1555 def DisableForSourceTree(source_tree):
1556 for source in source_tree:
1557 if isinstance(source, MSVSProject.Filter):
1558 DisableForSourceTree(source.contents)
1560 basename, extension = os.path.splitext(source)
1561 if extension in extensions_excluded_from_precompile:
1562 for config_name, config in spec['configurations'].iteritems():
1563 tool = MSVSProject.Tool('VCCLCompilerTool',
1564 {'UsePrecompiledHeader': '0',
1565 'ForcedIncludeFiles': '$(NOINHERIT)'})
1566 p.AddFileConfig(_FixPath(source),
1567 _ConfigFullName(config_name, config),
1569 # Do nothing if there was no precompiled source.
1570 if extensions_excluded_from_precompile:
1571 DisableForSourceTree(sources)
1574 def _AddActions(actions_to_add, spec, relative_path_of_gyp_file):
1576 actions = spec.get('actions', [])
1577 # Don't setup_env every time. When all the actions are run together in one
1578 # batch file in VS, the PATH will grow too long.
1579 # Membership in this set means that the cygwin environment has been set up,
1580 # and does not need to be set up again.
1581 have_setup_env = set()
1583 # Attach actions to the gyp file if nothing else is there.
1584 inputs = a.get('inputs') or [relative_path_of_gyp_file]
1585 attached_to = inputs[0]
1586 need_setup_env = attached_to not in have_setup_env
1587 cmd = _BuildCommandLineForRule(spec, a, has_input_path=False,
1588 do_setup_env=need_setup_env)
1589 have_setup_env.add(attached_to)
1591 _AddActionStep(actions_to_add,
1593 outputs=a.get('outputs', []),
1594 description=a.get('message', a['action_name']),
1598 def _WriteMSVSUserFile(project_path, version, spec):
1599 # Add run_as and test targets.
1600 if 'run_as' in spec:
1601 run_as = spec['run_as']
1602 action = run_as.get('action', [])
1603 environment = run_as.get('environment', [])
1604 working_directory = run_as.get('working_directory', '.')
1605 elif int(spec.get('test', 0)):
1606 action = ['$(TargetPath)', '--gtest_print_time']
1608 working_directory = '.'
1610 return # Nothing to add
1611 # Write out the user file.
1612 user_file = _CreateMSVSUserFile(project_path, version, spec)
1613 for config_name, c_data in spec['configurations'].iteritems():
1614 user_file.AddDebugSettings(_ConfigFullName(config_name, c_data),
1615 action, environment, working_directory)
1616 user_file.WriteIfChanged()
1619 def _AddCopies(actions_to_add, spec):
1620 copies = _GetCopies(spec)
1621 for inputs, outputs, cmd, description in copies:
1622 _AddActionStep(actions_to_add, inputs=inputs, outputs=outputs,
1623 description=description, command=cmd)
1626 def _GetCopies(spec):
1629 for cpy in spec.get('copies', []):
1630 for src in cpy.get('files', []):
1631 dst = os.path.join(cpy['destination'], os.path.basename(src))
1632 # _AddCustomBuildToolForMSVS() will call _FixPath() on the inputs and
1633 # outputs, so do the same for our generated command line.
1634 if src.endswith('/'):
1636 base_dir = posixpath.split(src_bare)[0]
1637 outer_dir = posixpath.split(src_bare)[1]
1638 cmd = 'cd "%s" && xcopy /e /f /y "%s" "%s\\%s\\"' % (
1639 _FixPath(base_dir), outer_dir, _FixPath(dst), outer_dir)
1640 copies.append(([src], ['dummy_copies', dst], cmd,
1641 'Copying %s to %s' % (src, dst)))
1643 cmd = 'mkdir "%s" 2>nul & set ERRORLEVEL=0 & copy /Y "%s" "%s"' % (
1644 _FixPath(cpy['destination']), _FixPath(src), _FixPath(dst))
1645 copies.append(([src], [dst], cmd, 'Copying %s to %s' % (src, dst)))
1649 def _GetPathDict(root, path):
1650 # |path| will eventually be empty (in the recursive calls) if it was initially
1651 # relative; otherwise it will eventually end up as '\', 'D:\', etc.
1652 if not path or path.endswith(os.sep):
1654 parent, folder = os.path.split(path)
1655 parent_dict = _GetPathDict(root, parent)
1656 if folder not in parent_dict:
1657 parent_dict[folder] = dict()
1658 return parent_dict[folder]
1661 def _DictsToFolders(base_path, bucket, flat):
1662 # Convert to folders recursively.
1664 for folder, contents in bucket.iteritems():
1665 if type(contents) == dict:
1666 folder_children = _DictsToFolders(os.path.join(base_path, folder),
1669 children += folder_children
1671 folder_children = MSVSNew.MSVSFolder(os.path.join(base_path, folder),
1672 name='(' + folder + ')',
1673 entries=folder_children)
1674 children.append(folder_children)
1676 children.append(contents)
1680 def _CollapseSingles(parent, node):
1681 # Recursively explorer the tree of dicts looking for projects which are
1682 # the sole item in a folder which has the same name as the project. Bring
1683 # such projects up one level.
1684 if (type(node) == dict and
1686 node.keys()[0] == parent + '.vcproj'):
1687 return node[node.keys()[0]]
1688 if type(node) != dict:
1691 node[child] = _CollapseSingles(child, node[child])
1695 def _GatherSolutionFolders(sln_projects, project_objects, flat):
1697 # Convert into a tree of dicts on path.
1698 for p in sln_projects:
1699 gyp_file, target = gyp.common.ParseQualifiedTarget(p)[0:2]
1700 gyp_dir = os.path.dirname(gyp_file)
1701 path_dict = _GetPathDict(root, gyp_dir)
1702 path_dict[target + '.vcproj'] = project_objects[p]
1703 # Walk down from the top until we hit a folder that has more than one entry.
1704 # In practice, this strips the top-level "src/" dir from the hierarchy in
1706 while len(root) == 1 and type(root[root.keys()[0]]) == dict:
1707 root = root[root.keys()[0]]
1709 root = _CollapseSingles('', root)
1710 # Merge buckets until everything is a root entry.
1711 return _DictsToFolders('', root, flat)
1714 def _GetPathOfProject(qualified_target, spec, options, msvs_version):
1715 default_config = _GetDefaultConfiguration(spec)
1716 proj_filename = default_config.get('msvs_existing_vcproj')
1717 if not proj_filename:
1718 proj_filename = (spec['target_name'] + options.suffix +
1719 msvs_version.ProjectExtension())
1721 build_file = gyp.common.BuildFile(qualified_target)
1722 proj_path = os.path.join(os.path.dirname(build_file), proj_filename)
1724 if options.generator_output:
1725 project_dir_path = os.path.dirname(os.path.abspath(proj_path))
1726 proj_path = os.path.join(options.generator_output, proj_path)
1727 fix_prefix = gyp.common.RelativePath(project_dir_path,
1728 os.path.dirname(proj_path))
1729 return proj_path, fix_prefix
1732 def _GetPlatformOverridesOfProject(spec):
1733 # Prepare a dict indicating which project configurations are used for which
1734 # solution configurations for this target.
1735 config_platform_overrides = {}
1736 for config_name, c in spec['configurations'].iteritems():
1737 config_fullname = _ConfigFullName(config_name, c)
1738 platform = c.get('msvs_target_platform', _ConfigPlatform(c))
1739 fixed_config_fullname = '%s|%s' % (
1740 _ConfigBaseName(config_name, _ConfigPlatform(c)), platform)
1741 config_platform_overrides[config_fullname] = fixed_config_fullname
1742 return config_platform_overrides
1745 def _CreateProjectObjects(target_list, target_dicts, options, msvs_version):
1746 """Create a MSVSProject object for the targets found in target list.
1749 target_list: the list of targets to generate project objects for.
1750 target_dicts: the dictionary of specifications.
1751 options: global generator options.
1752 msvs_version: the MSVSVersion object.
1754 A set of created projects, keyed by target.
1756 global fixpath_prefix
1757 # Generate each project.
1759 for qualified_target in target_list:
1760 spec = target_dicts[qualified_target]
1761 if spec['toolset'] != 'target':
1763 'Multiple toolsets not supported in msvs build (target %s)' %
1765 proj_path, fixpath_prefix = _GetPathOfProject(qualified_target, spec,
1766 options, msvs_version)
1767 guid = _GetGuidOfProject(proj_path, spec)
1768 overrides = _GetPlatformOverridesOfProject(spec)
1769 build_file = gyp.common.BuildFile(qualified_target)
1770 # Create object for this project.
1771 obj = MSVSNew.MSVSProject(
1773 name=spec['target_name'],
1776 build_file=build_file,
1777 config_platform_overrides=overrides,
1778 fixpath_prefix=fixpath_prefix)
1779 # Set project toolset if any (MS build only)
1780 if msvs_version.UsesVcxproj():
1781 obj.set_msbuild_toolset(
1782 _GetMsbuildToolsetOfProject(proj_path, spec, msvs_version))
1783 projects[qualified_target] = obj
1784 # Set all the dependencies, but not if we are using an external builder like
1786 for project in projects.values():
1787 if not project.spec.get('msvs_external_builder'):
1788 deps = project.spec.get('dependencies', [])
1789 deps = [projects[d] for d in deps]
1790 project.set_dependencies(deps)
1794 def _InitNinjaFlavor(options, target_list, target_dicts):
1795 """Initialize targets for the ninja flavor.
1797 This sets up the necessary variables in the targets to generate msvs projects
1798 that use ninja as an external builder. The variables in the spec are only set
1799 if they have not been set. This allows individual specs to override the
1800 default values initialized here.
1802 options: Options provided to the generator.
1803 target_list: List of target pairs: 'base/base.gyp:base'.
1804 target_dicts: Dict of target properties keyed on target pair.
1806 for qualified_target in target_list:
1807 spec = target_dicts[qualified_target]
1808 if spec.get('msvs_external_builder'):
1809 # The spec explicitly defined an external builder, so don't change it.
1812 path_to_ninja = spec.get('msvs_path_to_ninja', 'ninja.exe')
1814 spec['msvs_external_builder'] = 'ninja'
1815 if not spec.get('msvs_external_builder_out_dir'):
1816 spec['msvs_external_builder_out_dir'] = \
1817 options.depth + '/out/$(Configuration)'
1818 if not spec.get('msvs_external_builder_build_cmd'):
1819 spec['msvs_external_builder_build_cmd'] = [
1825 if not spec.get('msvs_external_builder_clean_cmd'):
1826 spec['msvs_external_builder_clean_cmd'] = [
1836 def CalculateVariables(default_variables, params):
1837 """Generated variables that require params to be known."""
1839 generator_flags = params.get('generator_flags', {})
1841 # Select project file format version (if unset, default to auto detecting).
1842 msvs_version = MSVSVersion.SelectVisualStudioVersion(
1843 generator_flags.get('msvs_version', 'auto'))
1844 # Stash msvs_version for later (so we don't have to probe the system twice).
1845 params['msvs_version'] = msvs_version
1847 # Set a variable so conditions can be based on msvs_version.
1848 default_variables['MSVS_VERSION'] = msvs_version.ShortName()
1850 # To determine processor word size on Windows, in addition to checking
1851 # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
1852 # process), it is also necessary to check PROCESSOR_ARCITEW6432 (which
1853 # contains the actual word size of the system when running thru WOW64).
1854 if (os.environ.get('PROCESSOR_ARCHITECTURE', '').find('64') >= 0 or
1855 os.environ.get('PROCESSOR_ARCHITEW6432', '').find('64') >= 0):
1856 default_variables['MSVS_OS_BITS'] = 64
1858 default_variables['MSVS_OS_BITS'] = 32
1860 if gyp.common.GetFlavor(params) == 'ninja':
1861 default_variables['SHARED_INTERMEDIATE_DIR'] = '$(OutDir)gen'
1864 def PerformBuild(data, configurations, params):
1865 options = params['options']
1866 msvs_version = params['msvs_version']
1867 devenv = os.path.join(msvs_version.path, 'Common7', 'IDE', 'devenv.com')
1869 for build_file, build_file_dict in data.iteritems():
1870 (build_file_root, build_file_ext) = os.path.splitext(build_file)
1871 if build_file_ext != '.gyp':
1873 sln_path = build_file_root + options.suffix + '.sln'
1874 if options.generator_output:
1875 sln_path = os.path.join(options.generator_output, sln_path)
1877 for config in configurations:
1878 arguments = [devenv, sln_path, '/Build', config]
1879 print 'Building [%s]: %s' % (config, arguments)
1880 rtn = subprocess.check_call(arguments)
1883 def GenerateOutput(target_list, target_dicts, data, params):
1884 """Generate .sln and .vcproj files.
1886 This is the entry point for this generator.
1888 target_list: List of target pairs: 'base/base.gyp:base'.
1889 target_dicts: Dict of target properties keyed on target pair.
1890 data: Dictionary containing per .gyp data.
1892 global fixpath_prefix
1894 options = params['options']
1896 # Get the project file format version back out of where we stashed it in
1897 # GeneratorCalculatedVariables.
1898 msvs_version = params['msvs_version']
1900 generator_flags = params.get('generator_flags', {})
1902 # Optionally shard targets marked with 'msvs_shard': SHARD_COUNT.
1903 (target_list, target_dicts) = MSVSUtil.ShardTargets(target_list, target_dicts)
1905 # Optionally use the large PDB workaround for targets marked with
1906 # 'msvs_large_pdb': 1.
1907 (target_list, target_dicts) = MSVSUtil.InsertLargePdbShims(
1908 target_list, target_dicts, generator_default_variables)
1910 # Optionally configure each spec to use ninja as the external builder.
1911 if params.get('flavor') == 'ninja':
1912 _InitNinjaFlavor(options, target_list, target_dicts)
1914 # Prepare the set of configurations.
1916 for qualified_target in target_list:
1917 spec = target_dicts[qualified_target]
1918 for config_name, config in spec['configurations'].iteritems():
1919 configs.add(_ConfigFullName(config_name, config))
1920 configs = list(configs)
1922 # Figure out all the projects that will be generated and their guids
1923 project_objects = _CreateProjectObjects(target_list, target_dicts, options,
1926 # Generate each project.
1927 missing_sources = []
1928 for project in project_objects.values():
1929 fixpath_prefix = project.fixpath_prefix
1930 missing_sources.extend(_GenerateProject(project, options, msvs_version,
1932 fixpath_prefix = None
1934 for build_file in data:
1935 # Validate build_file extension
1936 if not build_file.endswith('.gyp'):
1938 sln_path = os.path.splitext(build_file)[0] + options.suffix + '.sln'
1939 if options.generator_output:
1940 sln_path = os.path.join(options.generator_output, sln_path)
1941 # Get projects in the solution, and their dependents.
1942 sln_projects = gyp.common.BuildFileTargets(target_list, build_file)
1943 sln_projects += gyp.common.DeepDependencyTargets(target_dicts, sln_projects)
1944 # Create folder hierarchy.
1945 root_entries = _GatherSolutionFolders(
1946 sln_projects, project_objects, flat=msvs_version.FlatSolution())
1948 sln = MSVSNew.MSVSSolution(sln_path,
1949 entries=root_entries,
1951 websiteProperties=False,
1952 version=msvs_version)
1956 error_message = "Missing input files:\n" + \
1957 '\n'.join(set(missing_sources))
1958 if generator_flags.get('msvs_error_on_missing_sources', False):
1959 raise GypError(error_message)
1961 print >> sys.stdout, "Warning: " + error_message
1964 def _GenerateMSBuildFiltersFile(filters_path, source_files,
1965 extension_to_rule_name):
1966 """Generate the filters file.
1968 This file is used by Visual Studio to organize the presentation of source
1972 filters_path: The path of the file to be created.
1973 source_files: The hierarchical structure of all the sources.
1974 extension_to_rule_name: A dictionary mapping file extensions to rules.
1978 _AppendFiltersForMSBuild('', source_files, extension_to_rule_name,
1979 filter_group, source_group)
1981 content = ['Project',
1982 {'ToolsVersion': '4.0',
1983 'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
1985 ['ItemGroup'] + filter_group,
1986 ['ItemGroup'] + source_group
1988 easy_xml.WriteXmlIfChanged(content, filters_path, pretty=True, win32=True)
1989 elif os.path.exists(filters_path):
1990 # We don't need this filter anymore. Delete the old filter file.
1991 os.unlink(filters_path)
1994 def _AppendFiltersForMSBuild(parent_filter_name, sources,
1995 extension_to_rule_name,
1996 filter_group, source_group):
1997 """Creates the list of filters and sources to be added in the filter file.
2000 parent_filter_name: The name of the filter under which the sources are
2002 sources: The hierarchy of filters and sources to process.
2003 extension_to_rule_name: A dictionary mapping file extensions to rules.
2004 filter_group: The list to which filter entries will be appended.
2005 source_group: The list to which source entries will be appeneded.
2007 for source in sources:
2008 if isinstance(source, MSVSProject.Filter):
2009 # We have a sub-filter. Create the name of that sub-filter.
2010 if not parent_filter_name:
2011 filter_name = source.name
2013 filter_name = '%s\\%s' % (parent_filter_name, source.name)
2014 # Add the filter to the group.
2015 filter_group.append(
2016 ['Filter', {'Include': filter_name},
2017 ['UniqueIdentifier', MSVSNew.MakeGuid(source.name)]])
2018 # Recurse and add its dependents.
2019 _AppendFiltersForMSBuild(filter_name, source.contents,
2020 extension_to_rule_name,
2021 filter_group, source_group)
2023 # It's a source. Create a source entry.
2024 _, element = _MapFileToMsBuildSourceType(source, extension_to_rule_name)
2025 source_entry = [element, {'Include': source}]
2026 # Specify the filter it is part of, if any.
2027 if parent_filter_name:
2028 source_entry.append(['Filter', parent_filter_name])
2029 source_group.append(source_entry)
2032 def _MapFileToMsBuildSourceType(source, extension_to_rule_name):
2033 """Returns the group and element type of the source file.
2036 source: The source file name.
2037 extension_to_rule_name: A dictionary mapping file extensions to rules.
2040 A pair of (group this file should be part of, the label of element)
2042 _, ext = os.path.splitext(source)
2043 if ext in extension_to_rule_name:
2045 element = extension_to_rule_name[ext]
2046 elif ext in ['.cc', '.cpp', '.c', '.cxx']:
2048 element = 'ClCompile'
2049 elif ext in ['.h', '.hxx']:
2051 element = 'ClInclude'
2054 element = 'ResourceCompile'
2061 return (group, element)
2064 def _GenerateRulesForMSBuild(output_dir, options, spec,
2065 sources, excluded_sources,
2066 props_files_of_rules, targets_files_of_rules,
2067 actions_to_add, extension_to_rule_name):
2068 # MSBuild rules are implemented using three files: an XML file, a .targets
2069 # file and a .props file.
2070 # See http://blogs.msdn.com/b/vcblog/archive/2010/04/21/quick-help-on-vs2010-custom-build-rule.aspx
2072 rules = spec.get('rules', [])
2073 rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
2074 rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
2077 for rule in rules_native:
2078 # Skip a rule with no action and no inputs.
2079 if 'action' not in rule and not rule.get('rule_sources', []):
2081 msbuild_rule = MSBuildRule(rule, spec)
2082 msbuild_rules.append(msbuild_rule)
2083 extension_to_rule_name[msbuild_rule.extension] = msbuild_rule.rule_name
2085 base = spec['target_name'] + options.suffix
2086 props_name = base + '.props'
2087 targets_name = base + '.targets'
2088 xml_name = base + '.xml'
2090 props_files_of_rules.add(props_name)
2091 targets_files_of_rules.add(targets_name)
2093 props_path = os.path.join(output_dir, props_name)
2094 targets_path = os.path.join(output_dir, targets_name)
2095 xml_path = os.path.join(output_dir, xml_name)
2097 _GenerateMSBuildRulePropsFile(props_path, msbuild_rules)
2098 _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules)
2099 _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules)
2102 _GenerateExternalRules(rules_external, output_dir, spec,
2103 sources, options, actions_to_add)
2104 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
2107 class MSBuildRule(object):
2108 """Used to store information used to generate an MSBuild rule.
2111 rule_name: The rule name, sanitized to use in XML.
2112 target_name: The name of the target.
2113 after_targets: The name of the AfterTargets element.
2114 before_targets: The name of the BeforeTargets element.
2115 depends_on: The name of the DependsOn element.
2116 compute_output: The name of the ComputeOutput element.
2117 dirs_to_make: The name of the DirsToMake element.
2118 inputs: The name of the _inputs element.
2119 tlog: The name of the _tlog element.
2120 extension: The extension this rule applies to.
2121 description: The message displayed when this rule is invoked.
2122 additional_dependencies: A string listing additional dependencies.
2123 outputs: The outputs of this rule.
2124 command: The command used to run the rule.
2127 def __init__(self, rule, spec):
2128 self.display_name = rule['rule_name']
2129 # Assure that the rule name is only characters and numbers
2130 self.rule_name = re.sub(r'\W', '_', self.display_name)
2131 # Create the various element names, following the example set by the
2132 # Visual Studio 2008 to 2010 conversion. I don't know if VS2010
2133 # is sensitive to the exact names.
2134 self.target_name = '_' + self.rule_name
2135 self.after_targets = self.rule_name + 'AfterTargets'
2136 self.before_targets = self.rule_name + 'BeforeTargets'
2137 self.depends_on = self.rule_name + 'DependsOn'
2138 self.compute_output = 'Compute%sOutput' % self.rule_name
2139 self.dirs_to_make = self.rule_name + 'DirsToMake'
2140 self.inputs = self.rule_name + '_inputs'
2141 self.tlog = self.rule_name + '_tlog'
2142 self.extension = rule['extension']
2143 if not self.extension.startswith('.'):
2144 self.extension = '.' + self.extension
2146 self.description = MSVSSettings.ConvertVCMacrosToMSBuild(
2147 rule.get('message', self.rule_name))
2148 old_additional_dependencies = _FixPaths(rule.get('inputs', []))
2149 self.additional_dependencies = (
2150 ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
2151 for i in old_additional_dependencies]))
2152 old_outputs = _FixPaths(rule.get('outputs', []))
2153 self.outputs = ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
2154 for i in old_outputs])
2155 old_command = _BuildCommandLineForRule(spec, rule, has_input_path=True,
2157 self.command = MSVSSettings.ConvertVCMacrosToMSBuild(old_command)
2160 def _GenerateMSBuildRulePropsFile(props_path, msbuild_rules):
2161 """Generate the .props file."""
2162 content = ['Project',
2163 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'}]
2164 for rule in msbuild_rules:
2167 {'Condition': "'$(%s)' == '' and '$(%s)' == '' and "
2168 "'$(ConfigurationType)' != 'Makefile'" % (rule.before_targets,
2171 [rule.before_targets, 'Midl'],
2172 [rule.after_targets, 'CustomBuild'],
2176 {'Condition': "'$(ConfigurationType)' != 'Makefile'"},
2177 '_SelectedFiles;$(%s)' % rule.depends_on
2180 ['ItemDefinitionGroup',
2182 ['CommandLineTemplate', rule.command],
2183 ['Outputs', rule.outputs],
2184 ['ExecutionDescription', rule.description],
2185 ['AdditionalDependencies', rule.additional_dependencies],
2189 easy_xml.WriteXmlIfChanged(content, props_path, pretty=True, win32=True)
2192 def _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules):
2193 """Generate the .targets file."""
2194 content = ['Project',
2195 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
2200 ['PropertyPageSchema',
2201 {'Include': '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'}
2204 for rule in msbuild_rules:
2206 ['AvailableItemName',
2207 {'Include': rule.rule_name},
2208 ['Targets', rule.target_name],
2210 content.append(item_group)
2212 for rule in msbuild_rules:
2215 {'TaskName': rule.rule_name,
2216 'TaskFactory': 'XamlTaskFactory',
2217 'AssemblyName': 'Microsoft.Build.Tasks.v4.0'
2219 ['Task', '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'],
2221 for rule in msbuild_rules:
2222 rule_name = rule.rule_name
2223 target_outputs = '%%(%s.Outputs)' % rule_name
2224 target_inputs = ('%%(%s.Identity);%%(%s.AdditionalDependencies);'
2225 '$(MSBuildProjectFile)') % (rule_name, rule_name)
2226 rule_inputs = '%%(%s.Identity)' % rule_name
2227 extension_condition = ("'%(Extension)'=='.obj' or "
2228 "'%(Extension)'=='.res' or "
2229 "'%(Extension)'=='.rsc' or "
2230 "'%(Extension)'=='.lib'")
2233 {'Condition': "'@(SelectedFiles)' != ''"},
2235 {'Remove': '@(%s)' % rule_name,
2236 'Condition': "'%(Identity)' != '@(SelectedFiles)'"
2242 [rule.inputs, {'Include': '%%(%s.AdditionalDependencies)' % rule_name}]
2247 {'Include': '%%(%s.Outputs)' % rule_name,
2248 'Condition': ("'%%(%s.Outputs)' != '' and "
2249 "'%%(%s.ExcludedFromBuild)' != 'true'" %
2250 (rule_name, rule_name))
2252 ['Source', "@(%s, '|')" % rule_name],
2253 ['Inputs', "@(%s -> '%%(Fullpath)', ';')" % rule.inputs],
2258 {'Importance': 'High',
2259 'Text': '%%(%s.ExecutionDescription)' % rule_name
2262 write_tlog_section = [
2264 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2265 "'true'" % (rule.tlog, rule.tlog),
2266 'File': '$(IntDir)$(ProjectName).write.1.tlog',
2267 'Lines': "^%%(%s.Source);@(%s->'%%(Fullpath)')" % (rule.tlog,
2271 read_tlog_section = [
2273 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2274 "'true'" % (rule.tlog, rule.tlog),
2275 'File': '$(IntDir)$(ProjectName).read.1.tlog',
2276 'Lines': "^%%(%s.Source);%%(%s.Inputs)" % (rule.tlog, rule.tlog)
2279 command_and_input_section = [
2281 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2282 "'true'" % (rule_name, rule_name),
2283 'CommandLineTemplate': '%%(%s.CommandLineTemplate)' % rule_name,
2284 'AdditionalOptions': '%%(%s.AdditionalOptions)' % rule_name,
2285 'Inputs': rule_inputs
2290 {'Name': rule.target_name,
2291 'BeforeTargets': '$(%s)' % rule.before_targets,
2292 'AfterTargets': '$(%s)' % rule.after_targets,
2293 'Condition': "'@(%s)' != ''" % rule_name,
2294 'DependsOnTargets': '$(%s);%s' % (rule.depends_on,
2295 rule.compute_output),
2296 'Outputs': target_outputs,
2297 'Inputs': target_inputs
2305 command_and_input_section,
2308 ['ComputeLinkInputsTargets',
2309 '$(ComputeLinkInputsTargets);',
2310 '%s;' % rule.compute_output
2312 ['ComputeLibInputsTargets',
2313 '$(ComputeLibInputsTargets);',
2314 '%s;' % rule.compute_output
2318 {'Name': rule.compute_output,
2319 'Condition': "'@(%s)' != ''" % rule_name
2323 {'Condition': "'@(%s)' != '' and "
2324 "'%%(%s.ExcludedFromBuild)' != 'true'" % (rule_name, rule_name),
2325 'Include': '%%(%s.Outputs)' % rule_name
2329 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2330 'Condition': extension_condition
2334 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2335 'Condition': extension_condition
2339 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2340 'Condition': extension_condition
2345 {'Directories': ("@(%s->'%%(RootDir)%%(Directory)')" %
2351 easy_xml.WriteXmlIfChanged(content, targets_path, pretty=True, win32=True)
2354 def _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules):
2355 # Generate the .xml file
2357 'ProjectSchemaDefinitions',
2358 {'xmlns': ('clr-namespace:Microsoft.Build.Framework.XamlTypes;'
2359 'assembly=Microsoft.Build.Framework'),
2360 'xmlns:x': 'http://schemas.microsoft.com/winfx/2006/xaml',
2361 'xmlns:sys': 'clr-namespace:System;assembly=mscorlib',
2362 'xmlns:transformCallback':
2363 'Microsoft.Cpp.Dev10.ConvertPropertyCallback'
2366 for rule in msbuild_rules:
2369 {'Name': rule.rule_name,
2370 'PageTemplate': 'tool',
2371 'DisplayName': rule.display_name,
2376 {'Persistence': 'ProjectFile',
2377 'ItemType': rule.rule_name
2383 {'Name': 'General'},
2384 ['Category.DisplayName',
2385 ['sys:String', 'General'],
2389 {'Name': 'Command Line',
2390 'Subtype': 'CommandLine'
2392 ['Category.DisplayName',
2393 ['sys:String', 'Command Line'],
2397 ['StringListProperty',
2399 'Category': 'Command Line',
2400 'IsRequired': 'true',
2403 ['StringListProperty.DataSource',
2405 {'Persistence': 'ProjectFile',
2406 'ItemType': rule.rule_name,
2407 'SourceType': 'Item'
2413 {'Name': 'CommandLineTemplate',
2414 'DisplayName': 'Command Line',
2416 'IncludeInCommandLine': 'False'
2419 ['DynamicEnumProperty',
2420 {'Name': rule.before_targets,
2421 'Category': 'General',
2422 'EnumProvider': 'Targets',
2423 'IncludeInCommandLine': 'False'
2425 ['DynamicEnumProperty.DisplayName',
2426 ['sys:String', 'Execute Before'],
2428 ['DynamicEnumProperty.Description',
2429 ['sys:String', 'Specifies the targets for the build customization'
2433 ['DynamicEnumProperty.ProviderSettings',
2436 'Value': '^%s|^Compute' % rule.before_targets
2440 ['DynamicEnumProperty.DataSource',
2442 {'Persistence': 'ProjectFile',
2443 'HasConfigurationCondition': 'true'
2448 ['DynamicEnumProperty',
2449 {'Name': rule.after_targets,
2450 'Category': 'General',
2451 'EnumProvider': 'Targets',
2452 'IncludeInCommandLine': 'False'
2454 ['DynamicEnumProperty.DisplayName',
2455 ['sys:String', 'Execute After'],
2457 ['DynamicEnumProperty.Description',
2458 ['sys:String', ('Specifies the targets for the build customization'
2462 ['DynamicEnumProperty.ProviderSettings',
2465 'Value': '^%s|^Compute' % rule.after_targets
2469 ['DynamicEnumProperty.DataSource',
2471 {'Persistence': 'ProjectFile',
2473 'HasConfigurationCondition': 'true'
2478 ['StringListProperty',
2480 'DisplayName': 'Outputs',
2482 'IncludeInCommandLine': 'False'
2486 {'Name': 'ExecutionDescription',
2487 'DisplayName': 'Execution Description',
2489 'IncludeInCommandLine': 'False'
2492 ['StringListProperty',
2493 {'Name': 'AdditionalDependencies',
2494 'DisplayName': 'Additional Dependencies',
2495 'IncludeInCommandLine': 'False',
2500 {'Subtype': 'AdditionalOptions',
2501 'Name': 'AdditionalOptions',
2502 'Category': 'Command Line'
2504 ['StringProperty.DisplayName',
2505 ['sys:String', 'Additional Options'],
2507 ['StringProperty.Description',
2508 ['sys:String', 'Additional Options'],
2513 {'Name': rule.rule_name,
2514 'DisplayName': rule.display_name
2518 {'Name': '*' + rule.extension,
2519 'ContentType': rule.rule_name
2523 {'Name': rule.rule_name,
2525 'ItemType': rule.rule_name
2529 easy_xml.WriteXmlIfChanged(content, xml_path, pretty=True, win32=True)
2532 def _GetConfigurationAndPlatform(name, settings):
2533 configuration = name.rsplit('_', 1)[0]
2534 platform = settings.get('msvs_configuration_platform', 'Win32')
2535 return (configuration, platform)
2538 def _GetConfigurationCondition(name, settings):
2539 return (r"'$(Configuration)|$(Platform)'=='%s|%s'" %
2540 _GetConfigurationAndPlatform(name, settings))
2543 def _GetMSBuildProjectConfigurations(configurations):
2544 group = ['ItemGroup', {'Label': 'ProjectConfigurations'}]
2545 for (name, settings) in sorted(configurations.iteritems()):
2546 configuration, platform = _GetConfigurationAndPlatform(name, settings)
2547 designation = '%s|%s' % (configuration, platform)
2549 ['ProjectConfiguration', {'Include': designation},
2550 ['Configuration', configuration],
2551 ['Platform', platform]])
2555 def _GetMSBuildGlobalProperties(spec, guid, gyp_file_name):
2556 namespace = os.path.splitext(gyp_file_name)[0]
2558 ['PropertyGroup', {'Label': 'Globals'},
2559 ['ProjectGuid', guid],
2560 ['Keyword', 'Win32Proj'],
2561 ['RootNamespace', namespace],
2566 def _GetMSBuildConfigurationDetails(spec, build_file):
2568 for name, settings in spec['configurations'].iteritems():
2569 msbuild_attributes = _GetMSBuildAttributes(spec, settings, build_file)
2570 condition = _GetConfigurationCondition(name, settings)
2571 character_set = msbuild_attributes.get('CharacterSet')
2572 _AddConditionalProperty(properties, condition, 'ConfigurationType',
2573 msbuild_attributes['ConfigurationType'])
2575 _AddConditionalProperty(properties, condition, 'CharacterSet',
2577 return _GetMSBuildPropertyGroup(spec, 'Configuration', properties)
2580 def _GetMSBuildLocalProperties(msbuild_toolset):
2581 # Currently the only local property we support is PlatformToolset
2585 ['PropertyGroup', {'Label': 'Locals'},
2586 ['PlatformToolset', msbuild_toolset],
2592 def _GetMSBuildPropertySheets(configurations):
2593 user_props = r'$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props'
2594 additional_props = {}
2595 props_specified = False
2596 for name, settings in sorted(configurations.iteritems()):
2597 configuration = _GetConfigurationCondition(name, settings)
2598 if settings.has_key('msbuild_props'):
2599 additional_props[configuration] = _FixPaths(settings['msbuild_props'])
2600 props_specified = True
2602 additional_props[configuration] = ''
2604 if not props_specified:
2607 {'Label': 'PropertySheets'},
2609 {'Project': user_props,
2610 'Condition': "exists('%s')" % user_props,
2611 'Label': 'LocalAppDataPlatform'
2618 for condition, props in additional_props.iteritems():
2621 {'Label': 'PropertySheets',
2622 'Condition': condition
2625 {'Project': user_props,
2626 'Condition': "exists('%s')" % user_props,
2627 'Label': 'LocalAppDataPlatform'
2631 for props_file in props:
2632 import_group.append(['Import', {'Project':props_file}])
2633 sheets.append(import_group)
2636 def _ConvertMSVSBuildAttributes(spec, config, build_file):
2637 config_type = _GetMSVSConfigurationType(spec, build_file)
2638 msvs_attributes = _GetMSVSAttributes(spec, config, config_type)
2639 msbuild_attributes = {}
2640 for a in msvs_attributes:
2641 if a in ['IntermediateDirectory', 'OutputDirectory']:
2642 directory = MSVSSettings.ConvertVCMacrosToMSBuild(msvs_attributes[a])
2643 if not directory.endswith('\\'):
2645 msbuild_attributes[a] = directory
2646 elif a == 'CharacterSet':
2647 msbuild_attributes[a] = _ConvertMSVSCharacterSet(msvs_attributes[a])
2648 elif a == 'ConfigurationType':
2649 msbuild_attributes[a] = _ConvertMSVSConfigurationType(msvs_attributes[a])
2651 print 'Warning: Do not know how to convert MSVS attribute ' + a
2652 return msbuild_attributes
2655 def _ConvertMSVSCharacterSet(char_set):
2656 if char_set.isdigit():
2665 def _ConvertMSVSConfigurationType(config_type):
2666 if config_type.isdigit():
2669 '2': 'DynamicLibrary',
2670 '4': 'StaticLibrary',
2676 def _GetMSBuildAttributes(spec, config, build_file):
2677 if 'msbuild_configuration_attributes' not in config:
2678 msbuild_attributes = _ConvertMSVSBuildAttributes(spec, config, build_file)
2681 config_type = _GetMSVSConfigurationType(spec, build_file)
2682 config_type = _ConvertMSVSConfigurationType(config_type)
2683 msbuild_attributes = config.get('msbuild_configuration_attributes', {})
2684 msbuild_attributes.setdefault('ConfigurationType', config_type)
2685 output_dir = msbuild_attributes.get('OutputDirectory',
2686 '$(SolutionDir)$(Configuration)')
2687 msbuild_attributes['OutputDirectory'] = _FixPath(output_dir) + '\\'
2688 if 'IntermediateDirectory' not in msbuild_attributes:
2689 intermediate = _FixPath('$(Configuration)') + '\\'
2690 msbuild_attributes['IntermediateDirectory'] = intermediate
2691 if 'CharacterSet' in msbuild_attributes:
2692 msbuild_attributes['CharacterSet'] = _ConvertMSVSCharacterSet(
2693 msbuild_attributes['CharacterSet'])
2694 if 'TargetName' not in msbuild_attributes:
2695 prefix = spec.get('product_prefix', '')
2696 product_name = spec.get('product_name', '$(ProjectName)')
2697 target_name = prefix + product_name
2698 msbuild_attributes['TargetName'] = target_name
2700 if spec.get('msvs_external_builder'):
2701 external_out_dir = spec.get('msvs_external_builder_out_dir', '.')
2702 msbuild_attributes['OutputDirectory'] = _FixPath(external_out_dir) + '\\'
2704 # Make sure that 'TargetPath' matches 'Lib.OutputFile' or 'Link.OutputFile'
2705 # (depending on the tool used) to avoid MSB8012 warning.
2706 msbuild_tool_map = {
2707 'executable': 'Link',
2708 'shared_library': 'Link',
2709 'loadable_module': 'Link',
2710 'static_library': 'Lib',
2712 msbuild_tool = msbuild_tool_map.get(spec['type'])
2714 msbuild_settings = config['finalized_msbuild_settings']
2715 out_file = msbuild_settings[msbuild_tool].get('OutputFile')
2717 msbuild_attributes['TargetPath'] = _FixPath(out_file)
2718 target_ext = msbuild_settings[msbuild_tool].get('TargetExt')
2720 msbuild_attributes['TargetExt'] = target_ext
2722 return msbuild_attributes
2725 def _GetMSBuildConfigurationGlobalProperties(spec, configurations, build_file):
2726 # TODO(jeanluc) We could optimize out the following and do it only if
2727 # there are actions.
2728 # TODO(jeanluc) Handle the equivalent of setting 'CYGWIN=nontsec'.
2730 cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.'])[0]
2732 cyg_path = '$(MSBuildProjectDirectory)\\%s\\bin\\' % _FixPath(cygwin_dirs)
2733 new_paths.append(cyg_path)
2734 # TODO(jeanluc) Change the convention to have both a cygwin_dir and a
2736 python_path = cyg_path.replace('cygwin\\bin', 'python_26')
2737 new_paths.append(python_path)
2739 new_paths = '$(ExecutablePath);' + ';'.join(new_paths)
2742 for (name, configuration) in sorted(configurations.iteritems()):
2743 condition = _GetConfigurationCondition(name, configuration)
2744 attributes = _GetMSBuildAttributes(spec, configuration, build_file)
2745 msbuild_settings = configuration['finalized_msbuild_settings']
2746 _AddConditionalProperty(properties, condition, 'IntDir',
2747 attributes['IntermediateDirectory'])
2748 _AddConditionalProperty(properties, condition, 'OutDir',
2749 attributes['OutputDirectory'])
2750 _AddConditionalProperty(properties, condition, 'TargetName',
2751 attributes['TargetName'])
2753 if attributes.get('TargetPath'):
2754 _AddConditionalProperty(properties, condition, 'TargetPath',
2755 attributes['TargetPath'])
2756 if attributes.get('TargetExt'):
2757 _AddConditionalProperty(properties, condition, 'TargetExt',
2758 attributes['TargetExt'])
2761 _AddConditionalProperty(properties, condition, 'ExecutablePath',
2763 tool_settings = msbuild_settings.get('', {})
2764 for name, value in sorted(tool_settings.iteritems()):
2765 formatted_value = _GetValueFormattedForMSBuild('', name, value)
2766 _AddConditionalProperty(properties, condition, name, formatted_value)
2767 return _GetMSBuildPropertyGroup(spec, None, properties)
2770 def _AddConditionalProperty(properties, condition, name, value):
2771 """Adds a property / conditional value pair to a dictionary.
2774 properties: The dictionary to be modified. The key is the name of the
2775 property. The value is itself a dictionary; its key is the value and
2776 the value a list of condition for which this value is true.
2777 condition: The condition under which the named property has the value.
2778 name: The name of the property.
2779 value: The value of the property.
2781 if name not in properties:
2782 properties[name] = {}
2783 values = properties[name]
2784 if value not in values:
2786 conditions = values[value]
2787 conditions.append(condition)
2790 # Regex for msvs variable references ( i.e. $(FOO) ).
2791 MSVS_VARIABLE_REFERENCE = re.compile('\$\(([a-zA-Z_][a-zA-Z0-9_]*)\)')
2794 def _GetMSBuildPropertyGroup(spec, label, properties):
2795 """Returns a PropertyGroup definition for the specified properties.
2798 spec: The target project dict.
2799 label: An optional label for the PropertyGroup.
2800 properties: The dictionary to be converted. The key is the name of the
2801 property. The value is itself a dictionary; its key is the value and
2802 the value a list of condition for which this value is true.
2804 group = ['PropertyGroup']
2806 group.append({'Label': label})
2807 num_configurations = len(spec['configurations'])
2809 # Use a definition of edges such that user_of_variable -> used_varible.
2810 # This happens to be easier in this case, since a variable's
2811 # definition contains all variables it references in a single string.
2813 for value in sorted(properties[node].keys()):
2814 # Add to edges all $(...) references to variables.
2816 # Variable references that refer to names not in properties are excluded
2817 # These can exist for instance to refer built in definitions like
2820 # Self references are ignored. Self reference is used in a few places to
2821 # append to the default value. I.e. PATH=$(PATH);other_path
2822 edges.update(set([v for v in MSVS_VARIABLE_REFERENCE.findall(value)
2823 if v in properties and v != node]))
2825 properties_ordered = gyp.common.TopologicallySorted(
2826 properties.keys(), GetEdges)
2827 # Walk properties in the reverse of a topological sort on
2828 # user_of_variable -> used_variable as this ensures variables are
2829 # defined before they are used.
2830 # NOTE: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))
2831 for name in reversed(properties_ordered):
2832 values = properties[name]
2833 for value, conditions in sorted(values.iteritems()):
2834 if len(conditions) == num_configurations:
2835 # If the value is the same all configurations,
2836 # just add one unconditional entry.
2837 group.append([name, value])
2839 for condition in conditions:
2840 group.append([name, {'Condition': condition}, value])
2844 def _GetMSBuildToolSettingsSections(spec, configurations):
2846 for (name, configuration) in sorted(configurations.iteritems()):
2847 msbuild_settings = configuration['finalized_msbuild_settings']
2848 group = ['ItemDefinitionGroup',
2849 {'Condition': _GetConfigurationCondition(name, configuration)}
2851 for tool_name, tool_settings in sorted(msbuild_settings.iteritems()):
2852 # Skip the tool named '' which is a holder of global settings handled
2853 # by _GetMSBuildConfigurationGlobalProperties.
2857 for name, value in sorted(tool_settings.iteritems()):
2858 formatted_value = _GetValueFormattedForMSBuild(tool_name, name,
2860 tool.append([name, formatted_value])
2862 groups.append(group)
2866 def _FinalizeMSBuildSettings(spec, configuration):
2867 if 'msbuild_settings' in configuration:
2869 msbuild_settings = configuration['msbuild_settings']
2870 MSVSSettings.ValidateMSBuildSettings(msbuild_settings)
2873 msvs_settings = configuration.get('msvs_settings', {})
2874 msbuild_settings = MSVSSettings.ConvertToMSBuildSettings(msvs_settings)
2875 include_dirs, resource_include_dirs = _GetIncludeDirs(configuration)
2876 libraries = _GetLibraries(spec)
2877 library_dirs = _GetLibraryDirs(configuration)
2878 out_file, _, msbuild_tool = _GetOutputFilePathAndTool(spec, msbuild=True)
2879 target_ext = _GetOutputTargetExt(spec)
2880 defines = _GetDefines(configuration)
2882 # Visual Studio 2010 has TR1
2883 defines = [d for d in defines if d != '_HAS_TR1=0']
2884 # Warn of ignored settings
2885 ignored_settings = ['msvs_prebuild', 'msvs_postbuild', 'msvs_tool_files']
2886 for ignored_setting in ignored_settings:
2887 value = configuration.get(ignored_setting)
2889 print ('Warning: The automatic conversion to MSBuild does not handle '
2890 '%s. Ignoring setting of %s' % (ignored_setting, str(value)))
2892 defines = [_EscapeCppDefineForMSBuild(d) for d in defines]
2893 disabled_warnings = _GetDisabledWarnings(configuration)
2894 # TODO(jeanluc) Validate & warn that we don't translate
2895 # prebuild = configuration.get('msvs_prebuild')
2896 # postbuild = configuration.get('msvs_postbuild')
2897 def_file = _GetModuleDefinition(spec)
2898 precompiled_header = configuration.get('msvs_precompiled_header')
2900 # Add the information to the appropriate tool
2901 # TODO(jeanluc) We could optimize and generate these settings only if
2902 # the corresponding files are found, e.g. don't generate ResourceCompile
2903 # if you don't have any resources.
2904 _ToolAppend(msbuild_settings, 'ClCompile',
2905 'AdditionalIncludeDirectories', include_dirs)
2906 _ToolAppend(msbuild_settings, 'ResourceCompile',
2907 'AdditionalIncludeDirectories', resource_include_dirs)
2908 # Add in libraries, note that even for empty libraries, we want this
2909 # set, to prevent inheriting default libraries from the enviroment.
2910 _ToolSetOrAppend(msbuild_settings, 'Link', 'AdditionalDependencies',
2912 _ToolAppend(msbuild_settings, 'Link', 'AdditionalLibraryDirectories',
2915 _ToolAppend(msbuild_settings, msbuild_tool, 'OutputFile', out_file,
2918 _ToolAppend(msbuild_settings, msbuild_tool, 'TargetExt', target_ext,
2921 _ToolAppend(msbuild_settings, 'ClCompile',
2922 'PreprocessorDefinitions', defines)
2923 _ToolAppend(msbuild_settings, 'ResourceCompile',
2924 'PreprocessorDefinitions', defines)
2925 # Add disabled warnings.
2926 _ToolAppend(msbuild_settings, 'ClCompile',
2927 'DisableSpecificWarnings', disabled_warnings)
2928 # Turn on precompiled headers if appropriate.
2929 if precompiled_header:
2930 precompiled_header = os.path.split(precompiled_header)[1]
2931 _ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'Use')
2932 _ToolAppend(msbuild_settings, 'ClCompile',
2933 'PrecompiledHeaderFile', precompiled_header)
2934 _ToolAppend(msbuild_settings, 'ClCompile',
2935 'ForcedIncludeFiles', [precompiled_header])
2936 # Loadable modules don't generate import libraries;
2937 # tell dependent projects to not expect one.
2938 if spec['type'] == 'loadable_module':
2939 _ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'true')
2940 # Set the module definition file if any.
2942 _ToolAppend(msbuild_settings, 'Link', 'ModuleDefinitionFile', def_file)
2943 configuration['finalized_msbuild_settings'] = msbuild_settings
2946 def _GetValueFormattedForMSBuild(tool_name, name, value):
2947 if type(value) == list:
2948 # For some settings, VS2010 does not automatically extends the settings
2949 # TODO(jeanluc) Is this what we want?
2950 if name in ['AdditionalIncludeDirectories',
2951 'AdditionalLibraryDirectories',
2952 'AdditionalOptions',
2954 'DisableSpecificWarnings',
2955 'PreprocessorDefinitions']:
2956 value.append('%%(%s)' % name)
2957 # For most tools, entries in a list should be separated with ';' but some
2958 # settings use a space. Check for those first.
2960 'ClCompile': ['AdditionalOptions'],
2961 'Link': ['AdditionalOptions'],
2962 'Lib': ['AdditionalOptions']}
2963 if tool_name in exceptions and name in exceptions[tool_name]:
2967 formatted_value = char.join(
2968 [MSVSSettings.ConvertVCMacrosToMSBuild(i) for i in value])
2970 formatted_value = MSVSSettings.ConvertVCMacrosToMSBuild(value)
2971 return formatted_value
2974 def _VerifySourcesExist(sources, root_dir):
2975 """Verifies that all source files exist on disk.
2977 Checks that all regular source files, i.e. not created at run time,
2978 exist on disk. Missing files cause needless recompilation but no otherwise
2982 sources: A recursive list of Filter/file names.
2983 root_dir: The root directory for the relative path names.
2985 A list of source files that cannot be found on disk.
2987 missing_sources = []
2988 for source in sources:
2989 if isinstance(source, MSVSProject.Filter):
2990 missing_sources.extend(_VerifySourcesExist(source.contents, root_dir))
2992 if '$' not in source:
2993 full_path = os.path.join(root_dir, source)
2994 if not os.path.exists(full_path):
2995 missing_sources.append(full_path)
2996 return missing_sources
2999 def _GetMSBuildSources(spec, sources, exclusions, extension_to_rule_name,
3000 actions_spec, sources_handled_by_action, list_excluded):
3001 groups = ['none', 'midl', 'include', 'compile', 'resource', 'rule']
3002 grouped_sources = {}
3004 grouped_sources[g] = []
3006 _AddSources2(spec, sources, exclusions, grouped_sources,
3007 extension_to_rule_name, sources_handled_by_action, list_excluded)
3010 if grouped_sources[g]:
3011 sources.append(['ItemGroup'] + grouped_sources[g])
3013 sources.append(['ItemGroup'] + actions_spec)
3017 def _AddSources2(spec, sources, exclusions, grouped_sources,
3018 extension_to_rule_name, sources_handled_by_action,
3020 extensions_excluded_from_precompile = []
3021 for source in sources:
3022 if isinstance(source, MSVSProject.Filter):
3023 _AddSources2(spec, source.contents, exclusions, grouped_sources,
3024 extension_to_rule_name, sources_handled_by_action,
3027 if not source in sources_handled_by_action:
3029 excluded_configurations = exclusions.get(source, [])
3030 if len(excluded_configurations) == len(spec['configurations']):
3031 detail.append(['ExcludedFromBuild', 'true'])
3033 for config_name, configuration in sorted(excluded_configurations):
3034 condition = _GetConfigurationCondition(config_name, configuration)
3035 detail.append(['ExcludedFromBuild',
3036 {'Condition': condition},
3038 # Add precompile if needed
3039 for config_name, configuration in spec['configurations'].iteritems():
3040 precompiled_source = configuration.get('msvs_precompiled_source', '')
3041 if precompiled_source != '':
3042 precompiled_source = _FixPath(precompiled_source)
3043 if not extensions_excluded_from_precompile:
3044 # If the precompiled header is generated by a C source, we must
3045 # not try to use it for C++ sources, and vice versa.
3046 basename, extension = os.path.splitext(precompiled_source)
3047 if extension == '.c':
3048 extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
3050 extensions_excluded_from_precompile = ['.c']
3052 if precompiled_source == source:
3053 condition = _GetConfigurationCondition(config_name, configuration)
3054 detail.append(['PrecompiledHeader',
3055 {'Condition': condition},
3059 # Turn off precompiled header usage for source files of a
3060 # different type than the file that generated the
3061 # precompiled header.
3062 for extension in extensions_excluded_from_precompile:
3063 if source.endswith(extension):
3064 detail.append(['PrecompiledHeader', ''])
3065 detail.append(['ForcedIncludeFiles', ''])
3067 group, element = _MapFileToMsBuildSourceType(source,
3068 extension_to_rule_name)
3069 grouped_sources[group].append([element, {'Include': source}] + detail)
3072 def _GetMSBuildProjectReferences(project):
3074 if project.dependencies:
3075 group = ['ItemGroup']
3076 for dependency in project.dependencies:
3077 guid = dependency.guid
3078 project_dir = os.path.split(project.path)[0]
3079 relative_path = gyp.common.RelativePath(dependency.path, project_dir)
3080 project_ref = ['ProjectReference',
3081 {'Include': relative_path},
3083 ['ReferenceOutputAssembly', 'false']
3085 for config in dependency.spec.get('configurations', {}).itervalues():
3086 # If it's disabled in any config, turn it off in the reference.
3087 if config.get('msvs_2010_disable_uldi_when_referenced', 0):
3088 project_ref.append(['UseLibraryDependencyInputs', 'false'])
3090 group.append(project_ref)
3091 references.append(group)
3095 def _GenerateMSBuildProject(project, options, version, generator_flags):
3097 configurations = spec['configurations']
3098 project_dir, project_file_name = os.path.split(project.path)
3099 msbuildproj_dir = os.path.dirname(project.path)
3100 if msbuildproj_dir and not os.path.exists(msbuildproj_dir):
3101 os.makedirs(msbuildproj_dir)
3102 # Prepare list of sources and excluded sources.
3103 gyp_path = _NormalizedSource(project.build_file)
3104 relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
3106 gyp_file = os.path.split(project.build_file)[1]
3107 sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
3111 props_files_of_rules = set()
3112 targets_files_of_rules = set()
3113 extension_to_rule_name = {}
3114 list_excluded = generator_flags.get('msvs_list_excluded_files', True)
3116 # Don't generate rules if we are using an external builder like ninja.
3117 if not spec.get('msvs_external_builder'):
3118 _GenerateRulesForMSBuild(project_dir, options, spec,
3119 sources, excluded_sources,
3120 props_files_of_rules, targets_files_of_rules,
3121 actions_to_add, extension_to_rule_name)
3123 rules = spec.get('rules', [])
3124 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
3126 sources, excluded_sources, excluded_idl = (
3127 _AdjustSourcesAndConvertToFilterHierarchy(spec, options,
3128 project_dir, sources,
3132 # Don't add actions if we are using an external builder like ninja.
3133 if not spec.get('msvs_external_builder'):
3134 _AddActions(actions_to_add, spec, project.build_file)
3135 _AddCopies(actions_to_add, spec)
3137 # NOTE: this stanza must appear after all actions have been decided.
3138 # Don't excluded sources with actions attached, or they won't run.
3139 excluded_sources = _FilterActionsFromExcluded(
3140 excluded_sources, actions_to_add)
3142 exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
3143 actions_spec, sources_handled_by_action = _GenerateActionsForMSBuild(
3144 spec, actions_to_add)
3146 _GenerateMSBuildFiltersFile(project.path + '.filters', sources,
3147 extension_to_rule_name)
3148 missing_sources = _VerifySourcesExist(sources, project_dir)
3150 for configuration in configurations.itervalues():
3151 _FinalizeMSBuildSettings(spec, configuration)
3153 # Add attributes to root element
3155 import_default_section = [
3156 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.Default.props'}]]
3157 import_cpp_props_section = [
3158 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.props'}]]
3159 import_cpp_targets_section = [
3160 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.targets'}]]
3161 macro_section = [['PropertyGroup', {'Label': 'UserMacros'}]]
3165 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003',
3166 'ToolsVersion': version.ProjectVersion(),
3167 'DefaultTargets': 'Build'
3170 content += _GetMSBuildProjectConfigurations(configurations)
3171 content += _GetMSBuildGlobalProperties(spec, project.guid, project_file_name)
3172 content += import_default_section
3173 content += _GetMSBuildConfigurationDetails(spec, project.build_file)
3174 content += _GetMSBuildLocalProperties(project.msbuild_toolset)
3175 content += import_cpp_props_section
3176 content += _GetMSBuildExtensions(props_files_of_rules)
3177 content += _GetMSBuildPropertySheets(configurations)
3178 content += macro_section
3179 content += _GetMSBuildConfigurationGlobalProperties(spec, configurations,
3181 content += _GetMSBuildToolSettingsSections(spec, configurations)
3182 content += _GetMSBuildSources(
3183 spec, sources, exclusions, extension_to_rule_name, actions_spec,
3184 sources_handled_by_action, list_excluded)
3185 content += _GetMSBuildProjectReferences(project)
3186 content += import_cpp_targets_section
3187 content += _GetMSBuildExtensionTargets(targets_files_of_rules)
3189 if spec.get('msvs_external_builder'):
3190 content += _GetMSBuildExternalBuilderTargets(spec)
3192 # TODO(jeanluc) File a bug to get rid of runas. We had in MSVS:
3193 # has_run_as = _WriteMSVSUserFile(project.path, version, spec)
3195 easy_xml.WriteXmlIfChanged(content, project.path, pretty=True, win32=True)
3197 return missing_sources
3200 def _GetMSBuildExternalBuilderTargets(spec):
3201 """Return a list of MSBuild targets for external builders.
3203 Right now, only "Build" and "Clean" targets are generated.
3206 spec: The gyp target spec.
3208 List of MSBuild 'Target' specs.
3210 build_cmd = _BuildCommandLineForRuleRaw(
3211 spec, spec['msvs_external_builder_build_cmd'],
3212 False, False, False, False)
3213 build_target = ['Target', {'Name': 'Build'}]
3214 build_target.append(['Exec', {'Command': build_cmd}])
3216 clean_cmd = _BuildCommandLineForRuleRaw(
3217 spec, spec['msvs_external_builder_clean_cmd'],
3218 False, False, False, False)
3219 clean_target = ['Target', {'Name': 'Clean'}]
3220 clean_target.append(['Exec', {'Command': clean_cmd}])
3222 return [build_target, clean_target]
3225 def _GetMSBuildExtensions(props_files_of_rules):
3226 extensions = ['ImportGroup', {'Label': 'ExtensionSettings'}]
3227 for props_file in props_files_of_rules:
3228 extensions.append(['Import', {'Project': props_file}])
3232 def _GetMSBuildExtensionTargets(targets_files_of_rules):
3233 targets_node = ['ImportGroup', {'Label': 'ExtensionTargets'}]
3234 for targets_file in sorted(targets_files_of_rules):
3235 targets_node.append(['Import', {'Project': targets_file}])
3236 return [targets_node]
3239 def _GenerateActionsForMSBuild(spec, actions_to_add):
3240 """Add actions accumulated into an actions_to_add, merging as needed.
3243 spec: the target project dict
3244 actions_to_add: dictionary keyed on input name, which maps to a list of
3245 dicts describing the actions attached to that input file.
3248 A pair of (action specification, the sources handled by this action).
3250 sources_handled_by_action = OrderedSet()
3252 for primary_input, actions in actions_to_add.iteritems():
3253 inputs = OrderedSet()
3254 outputs = OrderedSet()
3257 for action in actions:
3258 inputs.update(OrderedSet(action['inputs']))
3259 outputs.update(OrderedSet(action['outputs']))
3260 descriptions.append(action['description'])
3261 cmd = action['command']
3262 # For most actions, add 'call' so that actions that invoke batch files
3263 # return and continue executing. msbuild_use_call provides a way to
3264 # disable this but I have not seen any adverse effect from doing that
3266 if action.get('msbuild_use_call', True):
3268 commands.append(cmd)
3269 # Add the custom build action for one input file.
3270 description = ', and also '.join(descriptions)
3272 # We can't join the commands simply with && because the command line will
3273 # get too long. See also _AddActions: cygwin's setup_env mustn't be called
3274 # for every invocation or the command that sets the PATH will grow too
3277 '\r\nif %errorlevel% neq 0 exit /b %errorlevel%\r\n'.join(commands))
3278 _AddMSBuildAction(spec,
3284 sources_handled_by_action,
3286 return actions_spec, sources_handled_by_action
3289 def _AddMSBuildAction(spec, primary_input, inputs, outputs, cmd, description,
3290 sources_handled_by_action, actions_spec):
3291 command = MSVSSettings.ConvertVCMacrosToMSBuild(cmd)
3292 primary_input = _FixPath(primary_input)
3293 inputs_array = _FixPaths(inputs)
3294 outputs_array = _FixPaths(outputs)
3295 additional_inputs = ';'.join([i for i in inputs_array
3296 if i != primary_input])
3297 outputs = ';'.join(outputs_array)
3298 sources_handled_by_action.add(primary_input)
3299 action_spec = ['CustomBuild', {'Include': primary_input}]
3301 # TODO(jeanluc) 'Document' for all or just if as_sources?
3302 [['FileType', 'Document'],
3303 ['Command', command],
3304 ['Message', description],
3305 ['Outputs', outputs]
3307 if additional_inputs:
3308 action_spec.append(['AdditionalInputs', additional_inputs])
3309 actions_spec.append(action_spec)