1 # Copyright (c) 2012 Google Inc. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
15 import gyp.easy_xml as easy_xml
16 import gyp.MSVSNew as MSVSNew
17 import gyp.MSVSProject as MSVSProject
18 import gyp.MSVSSettings as MSVSSettings
19 import gyp.MSVSToolFile as MSVSToolFile
20 import gyp.MSVSUserFile as MSVSUserFile
21 import gyp.MSVSUtil as MSVSUtil
22 import gyp.MSVSVersion as MSVSVersion
23 from gyp.common import GypError
25 # TODO: Remove once bots are on 2.7, http://crbug.com/241769
26 def _import_OrderedDict():
29 return collections.OrderedDict
30 except AttributeError:
31 import gyp.ordered_dict
32 return gyp.ordered_dict.OrderedDict
33 OrderedDict = _import_OrderedDict()
36 # Regular expression for validating Visual Studio GUIDs. If the GUID
37 # contains lowercase hex letters, MSVS will be fine. However,
38 # IncrediBuild BuildConsole will parse the solution file, but then
39 # silently skip building the target causing hard to track down errors.
40 # Note that this only happens with the BuildConsole, and does not occur
41 # if IncrediBuild is executed from inside Visual Studio. This regex
42 # validates that the string looks like a GUID with all uppercase hex
44 VALID_MSVS_GUID_CHARS = re.compile('^[A-F0-9\-]+$')
47 generator_default_variables = {
48 'EXECUTABLE_PREFIX': '',
49 'EXECUTABLE_SUFFIX': '.exe',
50 'STATIC_LIB_PREFIX': '',
51 'SHARED_LIB_PREFIX': '',
52 'STATIC_LIB_SUFFIX': '.lib',
53 'SHARED_LIB_SUFFIX': '.dll',
54 'INTERMEDIATE_DIR': '$(IntDir)',
55 'SHARED_INTERMEDIATE_DIR': '$(OutDir)obj/global_intermediate',
57 'PRODUCT_DIR': '$(OutDir)',
58 'LIB_DIR': '$(OutDir)lib',
59 'RULE_INPUT_ROOT': '$(InputName)',
60 'RULE_INPUT_DIRNAME': '$(InputDir)',
61 'RULE_INPUT_EXT': '$(InputExt)',
62 'RULE_INPUT_NAME': '$(InputFileName)',
63 'RULE_INPUT_PATH': '$(InputPath)',
64 'CONFIGURATION_NAME': '$(ConfigurationName)',
68 # The msvs specific sections that hold paths
69 generator_additional_path_sections = [
75 generator_additional_non_configuration_keys = [
80 'msvs_external_builder',
81 'msvs_external_builder_out_dir',
82 'msvs_external_builder_build_cmd',
83 'msvs_external_builder_clean_cmd',
87 # List of precompiled header related keys.
89 'msvs_precompiled_header',
90 'msvs_precompiled_source',
94 cached_username = None
100 # Based on http://code.activestate.com/recipes/576694/.
101 class OrderedSet(collections.MutableSet):
102 def __init__(self, iterable=None):
104 end += [None, end, end] # sentinel node for doubly linked list
105 self.map = {} # key --> [key, prev, next]
106 if iterable is not None:
112 def discard(self, key):
114 key, prev, next = self.map.pop(key)
118 def __contains__(self, key):
119 return key in self.map
122 if key not in self.map:
125 curr[2] = end[1] = self.map[key] = [key, curr, end]
127 def update(self, iterable):
135 while curr is not end:
140 # TODO(gspencer): Switch the os.environ calls to be
141 # win32api.GetDomainName() and win32api.GetUserName() once the
142 # python version in depot_tools has been updated to work on Vista
144 def _GetDomainAndUserName():
145 if sys.platform not in ('win32', 'cygwin'):
146 return ('DOMAIN', 'USERNAME')
147 global cached_username
149 if not cached_domain or not cached_username:
150 domain = os.environ.get('USERDOMAIN')
151 username = os.environ.get('USERNAME')
152 if not domain or not username:
153 call = subprocess.Popen(['net', 'config', 'Workstation'],
154 stdout=subprocess.PIPE)
155 config = call.communicate()[0]
156 username_re = re.compile('^User name\s+(\S+)', re.MULTILINE)
157 username_match = username_re.search(config)
159 username = username_match.group(1)
160 domain_re = re.compile('^Logon domain\s+(\S+)', re.MULTILINE)
161 domain_match = domain_re.search(config)
163 domain = domain_match.group(1)
164 cached_domain = domain
165 cached_username = username
166 return (cached_domain, cached_username)
168 fixpath_prefix = None
171 def _NormalizedSource(source):
172 """Normalize the path.
174 But not if that gets rid of a variable, as this may expand to something
175 larger than one directory.
178 source: The path to be normalize.d
183 normalized = os.path.normpath(source)
184 if source.count('$') == normalized.count('$'):
190 """Convert paths to a form that will make sense in a vcproj file.
193 path: The path to convert, may contain / etc.
195 The path with all slashes made into backslashes.
197 if fixpath_prefix and path and not os.path.isabs(path) and not path[0] == '$':
198 path = os.path.join(fixpath_prefix, path)
199 path = path.replace('/', '\\')
200 path = _NormalizedSource(path)
201 if path and path[-1] == '\\':
206 def _FixPaths(paths):
207 """Fix each of the paths of the list."""
208 return [_FixPath(i) for i in paths]
211 def _ConvertSourcesToFilterHierarchy(sources, prefix=None, excluded=None,
212 list_excluded=True, msvs_version=None):
213 """Converts a list split source file paths into a vcproj folder hierarchy.
216 sources: A list of source file paths split.
217 prefix: A list of source file path layers meant to apply to each of sources.
218 excluded: A set of excluded files.
219 msvs_version: A MSVSVersion object.
222 A hierarchy of filenames and MSVSProject.Filter objects that matches the
223 layout of the source tree.
225 _ConvertSourcesToFilterHierarchy([['a', 'bob1.c'], ['b', 'bob2.c']],
228 [MSVSProject.Filter('a', contents=['joe\\a\\bob1.c']),
229 MSVSProject.Filter('b', contents=['joe\\b\\bob2.c'])]
231 if not prefix: prefix = []
234 folders = OrderedDict()
235 # Gather files into the final result, excluded, or folders.
238 filename = _NormalizedSource('\\'.join(prefix + s))
239 if filename in excluded:
240 excluded_result.append(filename)
242 result.append(filename)
243 elif msvs_version and not msvs_version.UsesVcxproj():
244 # For MSVS 2008 and earlier, we need to process all files before walking
246 if not folders.get(s[0]):
248 folders[s[0]].append(s[1:])
250 contents = _ConvertSourcesToFilterHierarchy([s[1:]], prefix + [s[0]],
252 list_excluded=list_excluded,
253 msvs_version=msvs_version)
254 contents = MSVSProject.Filter(s[0], contents=contents)
255 result.append(contents)
256 # Add a folder for excluded files.
257 if excluded_result and list_excluded:
258 excluded_folder = MSVSProject.Filter('_excluded_files',
259 contents=excluded_result)
260 result.append(excluded_folder)
262 if msvs_version and msvs_version.UsesVcxproj():
265 # Populate all the folders.
267 contents = _ConvertSourcesToFilterHierarchy(folders[f], prefix=prefix + [f],
269 list_excluded=list_excluded)
270 contents = MSVSProject.Filter(f, contents=contents)
271 result.append(contents)
275 def _ToolAppend(tools, tool_name, setting, value, only_if_unset=False):
277 _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset)
280 def _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset=False):
281 # TODO(bradnelson): ugly hack, fix this more generally!!!
282 if 'Directories' in setting or 'Dependencies' in setting:
283 if type(value) == str:
284 value = value.replace('/', '\\')
286 value = [i.replace('/', '\\') for i in value]
287 if not tools.get(tool_name):
288 tools[tool_name] = dict()
289 tool = tools[tool_name]
290 if tool.get(setting):
291 if only_if_unset: return
292 if type(tool[setting]) == list and type(value) == list:
293 tool[setting] += value
296 'Appending "%s" to a non-list setting "%s" for tool "%s" is '
297 'not allowed, previous value: %s' % (
298 value, setting, tool_name, str(tool[setting])))
300 tool[setting] = value
303 def _ConfigPlatform(config_data):
304 return config_data.get('msvs_configuration_platform', 'Win32')
307 def _ConfigBaseName(config_name, platform_name):
308 if config_name.endswith('_' + platform_name):
309 return config_name[0:-len(platform_name) - 1]
314 def _ConfigFullName(config_name, config_data):
315 platform_name = _ConfigPlatform(config_data)
316 return '%s|%s' % (_ConfigBaseName(config_name, platform_name), platform_name)
319 def _BuildCommandLineForRuleRaw(spec, cmd, cygwin_shell, has_input_path,
320 quote_cmd, do_setup_env):
322 if [x for x in cmd if '$(InputDir)' in x]:
323 input_dir_preamble = (
324 'set INPUTDIR=$(InputDir)\n'
325 'set INPUTDIR=%INPUTDIR:$(ProjectDir)=%\n'
326 'set INPUTDIR=%INPUTDIR:~0,-1%\n'
329 input_dir_preamble = ''
332 # Find path to cygwin.
333 cygwin_dir = _FixPath(spec.get('msvs_cygwin_dirs', ['.'])[0])
336 direct_cmd = [i.replace('$(IntDir)',
337 '`cygpath -m "${INTDIR}"`') for i in direct_cmd]
338 direct_cmd = [i.replace('$(OutDir)',
339 '`cygpath -m "${OUTDIR}"`') for i in direct_cmd]
340 direct_cmd = [i.replace('$(InputDir)',
341 '`cygpath -m "${INPUTDIR}"`') for i in direct_cmd]
343 direct_cmd = [i.replace('$(InputPath)',
344 '`cygpath -m "${INPUTPATH}"`')
346 direct_cmd = ['\\"%s\\"' % i.replace('"', '\\\\\\"') for i in direct_cmd]
347 # direct_cmd = gyp.common.EncodePOSIXShellList(direct_cmd)
348 direct_cmd = ' '.join(direct_cmd)
349 # TODO(quote): regularize quoting path names throughout the module
352 cmd += 'call "$(ProjectDir)%(cygwin_dir)s\\setup_env.bat" && '
353 cmd += 'set CYGWIN=nontsec&& '
354 if direct_cmd.find('NUMBER_OF_PROCESSORS') >= 0:
355 cmd += 'set /a NUMBER_OF_PROCESSORS_PLUS_1=%%NUMBER_OF_PROCESSORS%%+1&& '
356 if direct_cmd.find('INTDIR') >= 0:
357 cmd += 'set INTDIR=$(IntDir)&& '
358 if direct_cmd.find('OUTDIR') >= 0:
359 cmd += 'set OUTDIR=$(OutDir)&& '
360 if has_input_path and direct_cmd.find('INPUTPATH') >= 0:
361 cmd += 'set INPUTPATH=$(InputPath) && '
362 cmd += 'bash -c "%(cmd)s"'
363 cmd = cmd % {'cygwin_dir': cygwin_dir,
365 return input_dir_preamble + cmd
367 # Convert cat --> type to mimic unix.
371 command = [cmd[0].replace('/', '\\')]
372 # Add call before command to ensure that commands can be tied together one
373 # after the other without aborting in Incredibuild, since IB makes a bat
374 # file out of the raw command string, and some commands (like python) are
375 # actually batch files themselves.
376 command.insert(0, 'call')
378 # TODO(quote): This is a really ugly heuristic, and will miss path fixing
379 # for arguments like "--arg=path" or "/opt:path".
380 # If the argument starts with a slash or dash, it's probably a command line
382 arguments = [i if (i[:1] in "/-") else _FixPath(i) for i in cmd[1:]]
383 arguments = [i.replace('$(InputDir)', '%INPUTDIR%') for i in arguments]
384 arguments = [MSVSSettings.FixVCMacroSlashes(i) for i in arguments]
386 # Support a mode for using cmd directly.
387 # Convert any paths to native form (first element is used directly).
388 # TODO(quote): regularize quoting path names throughout the module
389 arguments = ['"%s"' % i for i in arguments]
390 # Collapse into a single command.
391 return input_dir_preamble + ' '.join(command + arguments)
394 def _BuildCommandLineForRule(spec, rule, has_input_path, do_setup_env):
395 # Currently this weird argument munging is used to duplicate the way a
396 # python script would need to be run as part of the chrome tree.
397 # Eventually we should add some sort of rule_default option to set this
398 # per project. For now the behavior chrome needs is the default.
399 mcs = rule.get('msvs_cygwin_shell')
401 mcs = int(spec.get('msvs_cygwin_shell', 1))
402 elif isinstance(mcs, str):
404 quote_cmd = int(rule.get('msvs_quote_cmd', 1))
405 return _BuildCommandLineForRuleRaw(spec, rule['action'], mcs, has_input_path,
406 quote_cmd, do_setup_env=do_setup_env)
409 def _AddActionStep(actions_dict, inputs, outputs, description, command):
410 """Merge action into an existing list of actions.
412 Care must be taken so that actions which have overlapping inputs either don't
413 get assigned to the same input, or get collapsed into one.
416 actions_dict: dictionary keyed on input name, which maps to a list of
417 dicts describing the actions attached to that input file.
418 inputs: list of inputs
419 outputs: list of outputs
420 description: description of the action
421 command: command line to execute
423 # Require there to be at least one input (call sites will ensure this).
429 'description': description,
433 # Pick where to stick this action.
434 # While less than optimal in terms of build time, attach them to the first
436 chosen_input = inputs[0]
439 if chosen_input not in actions_dict:
440 actions_dict[chosen_input] = []
441 actions_dict[chosen_input].append(action)
444 def _AddCustomBuildToolForMSVS(p, spec, primary_input,
445 inputs, outputs, description, cmd):
446 """Add a custom build tool to execute something.
449 p: the target project
450 spec: the target project dict
451 primary_input: input file to attach the build tool to
452 inputs: list of inputs
453 outputs: list of outputs
454 description: description of the action
455 cmd: command line to execute
457 inputs = _FixPaths(inputs)
458 outputs = _FixPaths(outputs)
459 tool = MSVSProject.Tool(
461 {'Description': description,
462 'AdditionalDependencies': ';'.join(inputs),
463 'Outputs': ';'.join(outputs),
466 # Add to the properties of primary input for each config.
467 for config_name, c_data in spec['configurations'].iteritems():
468 p.AddFileConfig(_FixPath(primary_input),
469 _ConfigFullName(config_name, c_data), tools=[tool])
472 def _AddAccumulatedActionsToMSVS(p, spec, actions_dict):
473 """Add actions accumulated into an actions_dict, merging as needed.
476 p: the target project
477 spec: the target project dict
478 actions_dict: dictionary keyed on input name, which maps to a list of
479 dicts describing the actions attached to that input file.
481 for primary_input in actions_dict:
482 inputs = OrderedSet()
483 outputs = OrderedSet()
486 for action in actions_dict[primary_input]:
487 inputs.update(OrderedSet(action['inputs']))
488 outputs.update(OrderedSet(action['outputs']))
489 descriptions.append(action['description'])
490 commands.append(action['command'])
491 # Add the custom build step for one input file.
492 description = ', and also '.join(descriptions)
493 command = '\r\n'.join(commands)
494 _AddCustomBuildToolForMSVS(p, spec,
495 primary_input=primary_input,
498 description=description,
502 def _RuleExpandPath(path, input_file):
503 """Given the input file to which a rule applied, string substitute a path.
506 path: a path to string expand
507 input_file: the file to which the rule applied.
509 The string substituted path.
511 path = path.replace('$(InputName)',
512 os.path.splitext(os.path.split(input_file)[1])[0])
513 path = path.replace('$(InputDir)', os.path.dirname(input_file))
514 path = path.replace('$(InputExt)',
515 os.path.splitext(os.path.split(input_file)[1])[1])
516 path = path.replace('$(InputFileName)', os.path.split(input_file)[1])
517 path = path.replace('$(InputPath)', input_file)
521 def _FindRuleTriggerFiles(rule, sources):
522 """Find the list of files which a particular rule applies to.
525 rule: the rule in question
526 sources: the set of all known source files for this project
528 The list of sources that trigger a particular rule.
530 return rule.get('rule_sources', [])
533 def _RuleInputsAndOutputs(rule, trigger_file):
534 """Find the inputs and outputs generated by a rule.
537 rule: the rule in question.
538 trigger_file: the main trigger for this rule.
540 The pair of (inputs, outputs) involved in this rule.
542 raw_inputs = _FixPaths(rule.get('inputs', []))
543 raw_outputs = _FixPaths(rule.get('outputs', []))
544 inputs = OrderedSet()
545 outputs = OrderedSet()
546 inputs.add(trigger_file)
548 inputs.add(_RuleExpandPath(i, trigger_file))
549 for o in raw_outputs:
550 outputs.add(_RuleExpandPath(o, trigger_file))
551 return (inputs, outputs)
554 def _GenerateNativeRulesForMSVS(p, rules, output_dir, spec, options):
555 """Generate a native rules file.
558 p: the target project
559 rules: the set of rules to include
560 output_dir: the directory in which the project/gyp resides
561 spec: the project dict
562 options: global generator options
564 rules_filename = '%s%s.rules' % (spec['target_name'],
566 rules_file = MSVSToolFile.Writer(os.path.join(output_dir, rules_filename),
570 rule_name = r['rule_name']
571 rule_ext = r['extension']
572 inputs = _FixPaths(r.get('inputs', []))
573 outputs = _FixPaths(r.get('outputs', []))
574 # Skip a rule with no action and no inputs.
575 if 'action' not in r and not r.get('rule_sources', []):
577 cmd = _BuildCommandLineForRule(spec, r, has_input_path=True,
579 rules_file.AddCustomBuildRule(name=rule_name,
580 description=r.get('message', rule_name),
581 extensions=[rule_ext],
582 additional_dependencies=inputs,
585 # Write out rules file.
586 rules_file.WriteIfChanged()
588 # Add rules file to project.
589 p.AddToolFile(rules_filename)
592 def _Cygwinify(path):
593 path = path.replace('$(OutDir)', '$(OutDirCygwin)')
594 path = path.replace('$(IntDir)', '$(IntDirCygwin)')
598 def _GenerateExternalRules(rules, output_dir, spec,
599 sources, options, actions_to_add):
600 """Generate an external makefile to do a set of rules.
603 rules: the list of rules to include
604 output_dir: path containing project and gyp files
605 spec: project specification data
606 sources: set of sources known
607 options: global generator options
608 actions_to_add: The list of actions we will add to.
610 filename = '%s_rules%s.mk' % (spec['target_name'], options.suffix)
611 mk_file = gyp.common.WriteOnDiff(os.path.join(output_dir, filename))
612 # Find cygwin style versions of some paths.
613 mk_file.write('OutDirCygwin:=$(shell cygpath -u "$(OutDir)")\n')
614 mk_file.write('IntDirCygwin:=$(shell cygpath -u "$(IntDir)")\n')
615 # Gather stuff needed to emit all: target.
616 all_inputs = OrderedSet()
617 all_outputs = OrderedSet()
618 all_output_dirs = OrderedSet()
621 trigger_files = _FindRuleTriggerFiles(rule, sources)
622 for tf in trigger_files:
623 inputs, outputs = _RuleInputsAndOutputs(rule, tf)
624 all_inputs.update(OrderedSet(inputs))
625 all_outputs.update(OrderedSet(outputs))
626 # Only use one target from each rule as the dependency for
627 # 'all' so we don't try to build each rule multiple times.
628 first_outputs.append(list(outputs)[0])
629 # Get the unique output directories for this rule.
630 output_dirs = [os.path.split(i)[0] for i in outputs]
631 for od in output_dirs:
632 all_output_dirs.add(od)
633 first_outputs_cyg = [_Cygwinify(i) for i in first_outputs]
634 # Write out all: target, including mkdir for each output directory.
635 mk_file.write('all: %s\n' % ' '.join(first_outputs_cyg))
636 for od in all_output_dirs:
638 mk_file.write('\tmkdir -p `cygpath -u "%s"`\n' % od)
640 # Define how each output is generated.
642 trigger_files = _FindRuleTriggerFiles(rule, sources)
643 for tf in trigger_files:
644 # Get all the inputs and outputs for this rule for this trigger file.
645 inputs, outputs = _RuleInputsAndOutputs(rule, tf)
646 inputs = [_Cygwinify(i) for i in inputs]
647 outputs = [_Cygwinify(i) for i in outputs]
648 # Prepare the command line for this rule.
649 cmd = [_RuleExpandPath(c, tf) for c in rule['action']]
650 cmd = ['"%s"' % i for i in cmd]
652 # Add it to the makefile.
653 mk_file.write('%s: %s\n' % (' '.join(outputs), ' '.join(inputs)))
654 mk_file.write('\t%s\n\n' % cmd)
658 # Add makefile to list of sources.
659 sources.add(filename)
660 # Add a build action to call makefile.
664 '-j', '${NUMBER_OF_PROCESSORS_PLUS_1}',
666 cmd = _BuildCommandLineForRuleRaw(spec, cmd, True, False, True, True)
667 # Insert makefile as 0'th input, so it gets the action attached there,
668 # as this is easier to understand from in the IDE.
669 all_inputs = list(all_inputs)
670 all_inputs.insert(0, filename)
671 _AddActionStep(actions_to_add,
672 inputs=_FixPaths(all_inputs),
673 outputs=_FixPaths(all_outputs),
674 description='Running external rules for %s' %
679 def _EscapeEnvironmentVariableExpansion(s):
680 """Escapes % characters.
682 Escapes any % characters so that Windows-style environment variable
683 expansions will leave them alone.
684 See http://connect.microsoft.com/VisualStudio/feedback/details/106127/cl-d-name-text-containing-percentage-characters-doesnt-compile
685 to understand why we have to do this.
688 s: The string to be escaped.
693 s = s.replace('%', '%%')
697 quote_replacer_regex = re.compile(r'(\\*)"')
700 def _EscapeCommandLineArgumentForMSVS(s):
701 """Escapes a Windows command-line argument.
703 So that the Win32 CommandLineToArgv function will turn the escaped result back
704 into the original string.
705 See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
706 ("Parsing C++ Command-Line Arguments") to understand why we have to do
710 s: the string to be escaped.
716 # For a literal quote, CommandLineToArgv requires an odd number of
717 # backslashes preceding it, and it produces half as many literal backslashes
718 # (rounded down). So we need to produce 2n+1 backslashes.
719 return 2 * match.group(1) + '\\"'
721 # Escape all quotes so that they are interpreted literally.
722 s = quote_replacer_regex.sub(_Replace, s)
723 # Now add unescaped quotes so that any whitespace is interpreted literally.
728 delimiters_replacer_regex = re.compile(r'(\\*)([,;]+)')
731 def _EscapeVCProjCommandLineArgListItem(s):
732 """Escapes command line arguments for MSVS.
734 The VCProj format stores string lists in a single string using commas and
735 semi-colons as separators, which must be quoted if they are to be
736 interpreted literally. However, command-line arguments may already have
737 quotes, and the VCProj parser is ignorant of the backslash escaping
738 convention used by CommandLineToArgv, so the command-line quotes and the
739 VCProj quotes may not be the same quotes. So to store a general
740 command-line argument in a VCProj list, we need to parse the existing
741 quoting according to VCProj's convention and quote any delimiters that are
742 not already quoted by that convention. The quotes that we add will also be
743 seen by CommandLineToArgv, so if backslashes precede them then we also have
744 to escape those backslashes according to the CommandLineToArgv
748 s: the string to be escaped.
754 # For a non-literal quote, CommandLineToArgv requires an even number of
755 # backslashes preceding it, and it produces half as many literal
756 # backslashes. So we need to produce 2n backslashes.
757 return 2 * match.group(1) + '"' + match.group(2) + '"'
759 segments = s.split('"')
760 # The unquoted segments are at the even-numbered indices.
761 for i in range(0, len(segments), 2):
762 segments[i] = delimiters_replacer_regex.sub(_Replace, segments[i])
763 # Concatenate back into a single string
764 s = '"'.join(segments)
765 if len(segments) % 2 == 0:
766 # String ends while still quoted according to VCProj's convention. This
767 # means the delimiter and the next list item that follow this one in the
768 # .vcproj file will be misinterpreted as part of this item. There is nothing
769 # we can do about this. Adding an extra quote would correct the problem in
770 # the VCProj but cause the same problem on the final command-line. Moving
771 # the item to the end of the list does works, but that's only possible if
772 # there's only one such item. Let's just warn the user.
773 print >> sys.stderr, ('Warning: MSVS may misinterpret the odd number of ' +
778 def _EscapeCppDefineForMSVS(s):
779 """Escapes a CPP define so that it will reach the compiler unaltered."""
780 s = _EscapeEnvironmentVariableExpansion(s)
781 s = _EscapeCommandLineArgumentForMSVS(s)
782 s = _EscapeVCProjCommandLineArgListItem(s)
783 # cl.exe replaces literal # characters with = in preprocesor definitions for
784 # some reason. Octal-encode to work around that.
785 s = s.replace('#', '\\%03o' % ord('#'))
789 quote_replacer_regex2 = re.compile(r'(\\+)"')
792 def _EscapeCommandLineArgumentForMSBuild(s):
793 """Escapes a Windows command-line argument for use by MSBuild."""
796 return (len(match.group(1)) / 2 * 4) * '\\' + '\\"'
798 # Escape all quotes so that they are interpreted literally.
799 s = quote_replacer_regex2.sub(_Replace, s)
803 def _EscapeMSBuildSpecialCharacters(s):
804 escape_dictionary = {
813 result = ''.join([escape_dictionary.get(c, c) for c in s])
817 def _EscapeCppDefineForMSBuild(s):
818 """Escapes a CPP define so that it will reach the compiler unaltered."""
819 s = _EscapeEnvironmentVariableExpansion(s)
820 s = _EscapeCommandLineArgumentForMSBuild(s)
821 s = _EscapeMSBuildSpecialCharacters(s)
822 # cl.exe replaces literal # characters with = in preprocesor definitions for
823 # some reason. Octal-encode to work around that.
824 s = s.replace('#', '\\%03o' % ord('#'))
828 def _GenerateRulesForMSVS(p, output_dir, options, spec,
829 sources, excluded_sources,
831 """Generate all the rules for a particular project.
835 output_dir: directory to emit rules to
836 options: global options passed to the generator
837 spec: the specification for this project
838 sources: the set of all known source files in this project
839 excluded_sources: the set of sources excluded from normal processing
840 actions_to_add: deferred list of actions to add in
842 rules = spec.get('rules', [])
843 rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
844 rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
846 # Handle rules that use a native rules file.
848 _GenerateNativeRulesForMSVS(p, rules_native, output_dir, spec, options)
850 # Handle external rules (non-native rules).
852 _GenerateExternalRules(rules_external, output_dir, spec,
853 sources, options, actions_to_add)
854 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
857 def _AdjustSourcesForRules(spec, rules, sources, excluded_sources):
858 # Add outputs generated by each rule (if applicable).
860 # Done if not processing outputs as sources.
861 if int(rule.get('process_outputs_as_sources', False)):
862 # Add in the outputs from this rule.
863 trigger_files = _FindRuleTriggerFiles(rule, sources)
864 for trigger_file in trigger_files:
865 inputs, outputs = _RuleInputsAndOutputs(rule, trigger_file)
866 inputs = OrderedSet(_FixPaths(inputs))
867 outputs = OrderedSet(_FixPaths(outputs))
868 inputs.remove(_FixPath(trigger_file))
869 sources.update(inputs)
870 if not spec.get('msvs_external_builder'):
871 excluded_sources.update(inputs)
872 sources.update(outputs)
875 def _FilterActionsFromExcluded(excluded_sources, actions_to_add):
876 """Take inputs with actions attached out of the list of exclusions.
879 excluded_sources: list of source files not to be built.
880 actions_to_add: dict of actions keyed on source file they're attached to.
882 excluded_sources with files that have actions attached removed.
884 must_keep = OrderedSet(_FixPaths(actions_to_add.keys()))
885 return [s for s in excluded_sources if s not in must_keep]
888 def _GetDefaultConfiguration(spec):
889 return spec['configurations'][spec['default_configuration']]
892 def _GetGuidOfProject(proj_path, spec):
893 """Get the guid for the project.
896 proj_path: Path of the vcproj or vcxproj file to generate.
897 spec: The target dictionary containing the properties of the target.
901 ValueError: if the specified GUID is invalid.
903 # Pluck out the default configuration.
904 default_config = _GetDefaultConfiguration(spec)
905 # Decide the guid of the project.
906 guid = default_config.get('msvs_guid')
908 if VALID_MSVS_GUID_CHARS.match(guid) is None:
909 raise ValueError('Invalid MSVS guid: "%s". Must match regex: "%s".' %
910 (guid, VALID_MSVS_GUID_CHARS.pattern))
912 guid = guid or MSVSNew.MakeGuid(proj_path)
916 def _GetMsbuildToolsetOfProject(proj_path, spec, version):
917 """Get the platform toolset for the project.
920 proj_path: Path of the vcproj or vcxproj file to generate.
921 spec: The target dictionary containing the properties of the target.
922 version: The MSVSVersion object.
924 the platform toolset string or None.
926 # Pluck out the default configuration.
927 default_config = _GetDefaultConfiguration(spec)
928 toolset = default_config.get('msbuild_toolset')
929 if not toolset and version.DefaultToolset():
930 toolset = version.DefaultToolset()
934 def _GenerateProject(project, options, version, generator_flags):
935 """Generates a vcproj file.
938 project: the MSVSProject object.
939 options: global generator options.
940 version: the MSVSVersion object.
941 generator_flags: dict of generator-specific flags.
943 A list of source files that cannot be found on disk.
945 default_config = _GetDefaultConfiguration(project.spec)
947 # Skip emitting anything if told to with msvs_existing_vcproj option.
948 if default_config.get('msvs_existing_vcproj'):
951 if version.UsesVcxproj():
952 return _GenerateMSBuildProject(project, options, version, generator_flags)
954 return _GenerateMSVSProject(project, options, version, generator_flags)
957 def _GenerateMSVSProject(project, options, version, generator_flags):
958 """Generates a .vcproj file. It may create .rules and .user files too.
961 project: The project object we will generate the file for.
962 options: Global options passed to the generator.
963 version: The VisualStudioVersion object.
964 generator_flags: dict of generator-specific flags.
967 gyp.common.EnsureDirExists(project.path)
969 platforms = _GetUniquePlatforms(spec)
970 p = MSVSProject.Writer(project.path, version, spec['target_name'],
971 project.guid, platforms)
973 # Get directory project file is in.
974 project_dir = os.path.split(project.path)[0]
975 gyp_path = _NormalizedSource(project.build_file)
976 relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
978 config_type = _GetMSVSConfigurationType(spec, project.build_file)
979 for config_name, config in spec['configurations'].iteritems():
980 _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config)
982 # Prepare list of sources and excluded sources.
983 gyp_file = os.path.split(project.build_file)[1]
984 sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
989 _GenerateRulesForMSVS(p, project_dir, options, spec,
990 sources, excluded_sources,
992 list_excluded = generator_flags.get('msvs_list_excluded_files', True)
993 sources, excluded_sources, excluded_idl = (
994 _AdjustSourcesAndConvertToFilterHierarchy(spec, options, project_dir,
995 sources, excluded_sources,
996 list_excluded, version))
999 missing_sources = _VerifySourcesExist(sources, project_dir)
1002 _AddToolFilesToMSVS(p, spec)
1003 _HandlePreCompiledHeaders(p, sources, spec)
1004 _AddActions(actions_to_add, spec, relative_path_of_gyp_file)
1005 _AddCopies(actions_to_add, spec)
1006 _WriteMSVSUserFile(project.path, version, spec)
1008 # NOTE: this stanza must appear after all actions have been decided.
1009 # Don't excluded sources with actions attached, or they won't run.
1010 excluded_sources = _FilterActionsFromExcluded(
1011 excluded_sources, actions_to_add)
1012 _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
1014 _AddAccumulatedActionsToMSVS(p, spec, actions_to_add)
1019 return missing_sources
1022 def _GetUniquePlatforms(spec):
1023 """Returns the list of unique platforms for this spec, e.g ['win32', ...].
1026 spec: The target dictionary containing the properties of the target.
1028 The MSVSUserFile object created.
1030 # Gather list of unique platforms.
1031 platforms = OrderedSet()
1032 for configuration in spec['configurations']:
1033 platforms.add(_ConfigPlatform(spec['configurations'][configuration]))
1034 platforms = list(platforms)
1038 def _CreateMSVSUserFile(proj_path, version, spec):
1039 """Generates a .user file for the user running this Gyp program.
1042 proj_path: The path of the project file being created. The .user file
1043 shares the same path (with an appropriate suffix).
1044 version: The VisualStudioVersion object.
1045 spec: The target dictionary containing the properties of the target.
1047 The MSVSUserFile object created.
1049 (domain, username) = _GetDomainAndUserName()
1050 vcuser_filename = '.'.join([proj_path, domain, username, 'user'])
1051 user_file = MSVSUserFile.Writer(vcuser_filename, version,
1052 spec['target_name'])
1056 def _GetMSVSConfigurationType(spec, build_file):
1057 """Returns the configuration type for this project.
1059 It's a number defined by Microsoft. May raise an exception.
1062 spec: The target dictionary containing the properties of the target.
1063 build_file: The path of the gyp file.
1065 An integer, the configuration type.
1069 'executable': '1', # .exe
1070 'shared_library': '2', # .dll
1071 'loadable_module': '2', # .dll
1072 'static_library': '4', # .lib
1073 'none': '10', # Utility type
1076 if spec.get('type'):
1077 raise GypError('Target type %s is not a valid target type for '
1078 'target %s in %s.' %
1079 (spec['type'], spec['target_name'], build_file))
1081 raise GypError('Missing type field for target %s in %s.' %
1082 (spec['target_name'], build_file))
1086 def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config):
1087 """Adds a configuration to the MSVS project.
1089 Many settings in a vcproj file are specific to a configuration. This
1090 function the main part of the vcproj file that's configuration specific.
1093 p: The target project being generated.
1094 spec: The target dictionary containing the properties of the target.
1095 config_type: The configuration type, a number as defined by Microsoft.
1096 config_name: The name of the configuration.
1097 config: The dictionary that defines the special processing to be done
1098 for this configuration.
1100 # Get the information for this configuration
1101 include_dirs, resource_include_dirs = _GetIncludeDirs(config)
1102 libraries = _GetLibraries(spec)
1103 library_dirs = _GetLibraryDirs(config)
1104 out_file, vc_tool, _ = _GetOutputFilePathAndTool(spec, msbuild=False)
1105 defines = _GetDefines(config)
1106 defines = [_EscapeCppDefineForMSVS(d) for d in defines]
1107 disabled_warnings = _GetDisabledWarnings(config)
1108 prebuild = config.get('msvs_prebuild')
1109 postbuild = config.get('msvs_postbuild')
1110 def_file = _GetModuleDefinition(spec)
1111 precompiled_header = config.get('msvs_precompiled_header')
1113 # Prepare the list of tools as a dictionary.
1115 # Add in user specified msvs_settings.
1116 msvs_settings = config.get('msvs_settings', {})
1117 MSVSSettings.ValidateMSVSSettings(msvs_settings)
1119 # Prevent default library inheritance from the environment.
1120 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', ['$(NOINHERIT)'])
1122 for tool in msvs_settings:
1123 settings = config['msvs_settings'][tool]
1124 for setting in settings:
1125 _ToolAppend(tools, tool, setting, settings[setting])
1126 # Add the information to the appropriate tool
1127 _ToolAppend(tools, 'VCCLCompilerTool',
1128 'AdditionalIncludeDirectories', include_dirs)
1129 _ToolAppend(tools, 'VCResourceCompilerTool',
1130 'AdditionalIncludeDirectories', resource_include_dirs)
1132 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', libraries)
1133 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalLibraryDirectories',
1136 _ToolAppend(tools, vc_tool, 'OutputFile', out_file, only_if_unset=True)
1138 _ToolAppend(tools, 'VCCLCompilerTool', 'PreprocessorDefinitions', defines)
1139 _ToolAppend(tools, 'VCResourceCompilerTool', 'PreprocessorDefinitions',
1141 # Change program database directory to prevent collisions.
1142 _ToolAppend(tools, 'VCCLCompilerTool', 'ProgramDataBaseFileName',
1143 '$(IntDir)$(ProjectName)\\vc80.pdb', only_if_unset=True)
1144 # Add disabled warnings.
1145 _ToolAppend(tools, 'VCCLCompilerTool',
1146 'DisableSpecificWarnings', disabled_warnings)
1148 _ToolAppend(tools, 'VCPreBuildEventTool', 'CommandLine', prebuild)
1150 _ToolAppend(tools, 'VCPostBuildEventTool', 'CommandLine', postbuild)
1151 # Turn on precompiled headers if appropriate.
1152 if precompiled_header:
1153 precompiled_header = os.path.split(precompiled_header)[1]
1154 _ToolAppend(tools, 'VCCLCompilerTool', 'UsePrecompiledHeader', '2')
1155 _ToolAppend(tools, 'VCCLCompilerTool',
1156 'PrecompiledHeaderThrough', precompiled_header)
1157 _ToolAppend(tools, 'VCCLCompilerTool',
1158 'ForcedIncludeFiles', precompiled_header)
1159 # Loadable modules don't generate import libraries;
1160 # tell dependent projects to not expect one.
1161 if spec['type'] == 'loadable_module':
1162 _ToolAppend(tools, 'VCLinkerTool', 'IgnoreImportLibrary', 'true')
1163 # Set the module definition file if any.
1165 _ToolAppend(tools, 'VCLinkerTool', 'ModuleDefinitionFile', def_file)
1167 _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name)
1170 def _GetIncludeDirs(config):
1171 """Returns the list of directories to be used for #include directives.
1174 config: The dictionary that defines the special processing to be done
1175 for this configuration.
1177 The list of directory paths.
1179 # TODO(bradnelson): include_dirs should really be flexible enough not to
1180 # require this sort of thing.
1182 config.get('include_dirs', []) +
1183 config.get('msvs_system_include_dirs', []))
1184 resource_include_dirs = config.get('resource_include_dirs', include_dirs)
1185 include_dirs = _FixPaths(include_dirs)
1186 resource_include_dirs = _FixPaths(resource_include_dirs)
1187 return include_dirs, resource_include_dirs
1190 def _GetLibraryDirs(config):
1191 """Returns the list of directories to be used for library search paths.
1194 config: The dictionary that defines the special processing to be done
1195 for this configuration.
1197 The list of directory paths.
1200 library_dirs = config.get('library_dirs', [])
1201 library_dirs = _FixPaths(library_dirs)
1205 def _GetLibraries(spec):
1206 """Returns the list of libraries for this configuration.
1209 spec: The target dictionary containing the properties of the target.
1211 The list of directory paths.
1213 libraries = spec.get('libraries', [])
1214 # Strip out -l, as it is not used on windows (but is needed so we can pass
1215 # in libraries that are assumed to be in the default library path).
1216 # Also remove duplicate entries, leaving only the last duplicate, while
1218 found = OrderedSet()
1219 unique_libraries_list = []
1220 for entry in reversed(libraries):
1221 library = re.sub('^\-l', '', entry)
1222 if not os.path.splitext(library)[1]:
1224 if library not in found:
1226 unique_libraries_list.append(library)
1227 unique_libraries_list.reverse()
1228 return unique_libraries_list
1231 def _GetOutputFilePathAndTool(spec, msbuild):
1232 """Returns the path and tool to use for this target.
1234 Figures out the path of the file this spec will create and the name of
1235 the VC tool that will create it.
1238 spec: The target dictionary containing the properties of the target.
1240 A triple of (file path, name of the vc tool, name of the msbuild tool)
1242 # Select a name for the output file.
1247 'executable': ('VCLinkerTool', 'Link', '$(OutDir)', '.exe'),
1248 'shared_library': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
1249 'loadable_module': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
1250 'static_library': ('VCLibrarianTool', 'Lib', '$(OutDir)lib\\', '.lib'),
1252 output_file_props = output_file_map.get(spec['type'])
1253 if output_file_props and int(spec.get('msvs_auto_output_file', 1)):
1254 vc_tool, msbuild_tool, out_dir, suffix = output_file_props
1255 if spec.get('standalone_static_library', 0):
1256 out_dir = '$(OutDir)'
1257 out_dir = spec.get('product_dir', out_dir)
1258 product_extension = spec.get('product_extension')
1259 if product_extension:
1260 suffix = '.' + product_extension
1262 suffix = '$(TargetExt)'
1263 prefix = spec.get('product_prefix', '')
1264 product_name = spec.get('product_name', '$(ProjectName)')
1265 out_file = ntpath.join(out_dir, prefix + product_name + suffix)
1266 return out_file, vc_tool, msbuild_tool
1269 def _GetOutputTargetExt(spec):
1270 """Returns the extension for this target, including the dot
1272 If product_extension is specified, set target_extension to this to avoid
1273 MSB8012, returns None otherwise. Ignores any target_extension settings in
1277 spec: The target dictionary containing the properties of the target.
1279 A string with the extension, or None
1281 target_extension = spec.get('product_extension')
1282 if target_extension:
1283 return '.' + target_extension
1287 def _GetDefines(config):
1288 """Returns the list of preprocessor definitions for this configuation.
1291 config: The dictionary that defines the special processing to be done
1292 for this configuration.
1294 The list of preprocessor definitions.
1297 for d in config.get('defines', []):
1299 fd = '='.join([str(dpart) for dpart in d])
1306 def _GetDisabledWarnings(config):
1307 return [str(i) for i in config.get('msvs_disabled_warnings', [])]
1310 def _GetModuleDefinition(spec):
1312 if spec['type'] in ['shared_library', 'loadable_module', 'executable']:
1313 def_files = [s for s in spec.get('sources', []) if s.endswith('.def')]
1314 if len(def_files) == 1:
1315 def_file = _FixPath(def_files[0])
1318 'Multiple module definition files in one target, target %s lists '
1319 'multiple .def files: %s' % (
1320 spec['target_name'], ' '.join(def_files)))
1324 def _ConvertToolsToExpectedForm(tools):
1325 """Convert tools to a form expected by Visual Studio.
1328 tools: A dictionary of settings; the tool name is the key.
1330 A list of Tool objects.
1333 for tool, settings in tools.iteritems():
1334 # Collapse settings with lists.
1336 for setting, value in settings.iteritems():
1337 if type(value) == list:
1338 if ((tool == 'VCLinkerTool' and
1339 setting == 'AdditionalDependencies') or
1340 setting == 'AdditionalOptions'):
1341 settings_fixed[setting] = ' '.join(value)
1343 settings_fixed[setting] = ';'.join(value)
1345 settings_fixed[setting] = value
1347 tool_list.append(MSVSProject.Tool(tool, settings_fixed))
1351 def _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name):
1352 """Add to the project file the configuration specified by config.
1355 p: The target project being generated.
1356 spec: the target project dict.
1357 tools: A dictionary of settings; the tool name is the key.
1358 config: The dictionary that defines the special processing to be done
1359 for this configuration.
1360 config_type: The configuration type, a number as defined by Microsoft.
1361 config_name: The name of the configuration.
1363 attributes = _GetMSVSAttributes(spec, config, config_type)
1364 # Add in this configuration.
1365 tool_list = _ConvertToolsToExpectedForm(tools)
1366 p.AddConfig(_ConfigFullName(config_name, config),
1367 attrs=attributes, tools=tool_list)
1370 def _GetMSVSAttributes(spec, config, config_type):
1371 # Prepare configuration attributes.
1373 source_attrs = config.get('msvs_configuration_attributes', {})
1374 for a in source_attrs:
1375 prepared_attrs[a] = source_attrs[a]
1377 vsprops_dirs = config.get('msvs_props', [])
1378 vsprops_dirs = _FixPaths(vsprops_dirs)
1380 prepared_attrs['InheritedPropertySheets'] = ';'.join(vsprops_dirs)
1381 # Set configuration type.
1382 prepared_attrs['ConfigurationType'] = config_type
1383 output_dir = prepared_attrs.get('OutputDirectory',
1384 '$(SolutionDir)$(ConfigurationName)')
1385 prepared_attrs['OutputDirectory'] = _FixPath(output_dir) + '\\'
1386 if 'IntermediateDirectory' not in prepared_attrs:
1387 intermediate = '$(ConfigurationName)\\obj\\$(ProjectName)'
1388 prepared_attrs['IntermediateDirectory'] = _FixPath(intermediate) + '\\'
1390 intermediate = _FixPath(prepared_attrs['IntermediateDirectory']) + '\\'
1391 intermediate = MSVSSettings.FixVCMacroSlashes(intermediate)
1392 prepared_attrs['IntermediateDirectory'] = intermediate
1393 return prepared_attrs
1396 def _AddNormalizedSources(sources_set, sources_array):
1397 sources_set.update(_NormalizedSource(s) for s in sources_array)
1400 def _PrepareListOfSources(spec, generator_flags, gyp_file):
1401 """Prepare list of sources and excluded sources.
1403 Besides the sources specified directly in the spec, adds the gyp file so
1404 that a change to it will cause a re-compile. Also adds appropriate sources
1405 for actions and copies. Assumes later stage will un-exclude files which
1406 have custom build steps attached.
1409 spec: The target dictionary containing the properties of the target.
1410 gyp_file: The name of the gyp file.
1412 A pair of (list of sources, list of excluded sources).
1413 The sources will be relative to the gyp file.
1415 sources = OrderedSet()
1416 _AddNormalizedSources(sources, spec.get('sources', []))
1417 excluded_sources = OrderedSet()
1418 # Add in the gyp file.
1419 if not generator_flags.get('standalone'):
1420 sources.add(gyp_file)
1422 # Add in 'action' inputs and outputs.
1423 for a in spec.get('actions', []):
1424 inputs = a['inputs']
1425 inputs = [_NormalizedSource(i) for i in inputs]
1426 # Add all inputs to sources and excluded sources.
1427 inputs = OrderedSet(inputs)
1428 sources.update(inputs)
1429 if not spec.get('msvs_external_builder'):
1430 excluded_sources.update(inputs)
1431 if int(a.get('process_outputs_as_sources', False)):
1432 _AddNormalizedSources(sources, a.get('outputs', []))
1433 # Add in 'copies' inputs and outputs.
1434 for cpy in spec.get('copies', []):
1435 _AddNormalizedSources(sources, cpy.get('files', []))
1436 return (sources, excluded_sources)
1439 def _AdjustSourcesAndConvertToFilterHierarchy(
1440 spec, options, gyp_dir, sources, excluded_sources, list_excluded, version):
1441 """Adjusts the list of sources and excluded sources.
1443 Also converts the sets to lists.
1446 spec: The target dictionary containing the properties of the target.
1447 options: Global generator options.
1448 gyp_dir: The path to the gyp file being processed.
1449 sources: A set of sources to be included for this project.
1450 excluded_sources: A set of sources to be excluded for this project.
1451 version: A MSVSVersion object.
1453 A trio of (list of sources, list of excluded sources,
1454 path of excluded IDL file)
1456 # Exclude excluded sources coming into the generator.
1457 excluded_sources.update(OrderedSet(spec.get('sources_excluded', [])))
1458 # Add excluded sources into sources for good measure.
1459 sources.update(excluded_sources)
1460 # Convert to proper windows form.
1461 # NOTE: sources goes from being a set to a list here.
1462 # NOTE: excluded_sources goes from being a set to a list here.
1463 sources = _FixPaths(sources)
1464 # Convert to proper windows form.
1465 excluded_sources = _FixPaths(excluded_sources)
1467 excluded_idl = _IdlFilesHandledNonNatively(spec, sources)
1469 precompiled_related = _GetPrecompileRelatedFiles(spec)
1470 # Find the excluded ones, minus the precompiled header related ones.
1471 fully_excluded = [i for i in excluded_sources if i not in precompiled_related]
1473 # Convert to folders and the right slashes.
1474 sources = [i.split('\\') for i in sources]
1475 sources = _ConvertSourcesToFilterHierarchy(sources, excluded=fully_excluded,
1476 list_excluded=list_excluded,
1477 msvs_version=version)
1479 # Prune filters with a single child to flatten ugly directory structures
1480 # such as ../../src/modules/module1 etc.
1481 while len(sources) == 1 and isinstance(sources[0], MSVSProject.Filter):
1482 sources = sources[0].contents
1484 return sources, excluded_sources, excluded_idl
1487 def _IdlFilesHandledNonNatively(spec, sources):
1488 # If any non-native rules use 'idl' as an extension exclude idl files.
1489 # Gather a list here to use later.
1491 for rule in spec.get('rules', []):
1492 if rule['extension'] == 'idl' and int(rule.get('msvs_external_rule', 0)):
1496 excluded_idl = [i for i in sources if i.endswith('.idl')]
1502 def _GetPrecompileRelatedFiles(spec):
1503 # Gather a list of precompiled header related sources.
1504 precompiled_related = []
1505 for _, config in spec['configurations'].iteritems():
1506 for k in precomp_keys:
1509 precompiled_related.append(_FixPath(f))
1510 return precompiled_related
1513 def _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
1515 exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
1516 for file_name, excluded_configs in exclusions.iteritems():
1517 if (not list_excluded and
1518 len(excluded_configs) == len(spec['configurations'])):
1519 # If we're not listing excluded files, then they won't appear in the
1520 # project, so don't try to configure them to be excluded.
1523 for config_name, config in excluded_configs:
1524 p.AddFileConfig(file_name, _ConfigFullName(config_name, config),
1525 {'ExcludedFromBuild': 'true'})
1528 def _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl):
1530 # Exclude excluded sources from being built.
1531 for f in excluded_sources:
1532 excluded_configs = []
1533 for config_name, config in spec['configurations'].iteritems():
1534 precomped = [_FixPath(config.get(i, '')) for i in precomp_keys]
1535 # Don't do this for ones that are precompiled header related.
1536 if f not in precomped:
1537 excluded_configs.append((config_name, config))
1538 exclusions[f] = excluded_configs
1539 # If any non-native rules use 'idl' as an extension exclude idl files.
1541 for f in excluded_idl:
1542 excluded_configs = []
1543 for config_name, config in spec['configurations'].iteritems():
1544 excluded_configs.append((config_name, config))
1545 exclusions[f] = excluded_configs
1549 def _AddToolFilesToMSVS(p, spec):
1550 # Add in tool files (rules).
1551 tool_files = OrderedSet()
1552 for _, config in spec['configurations'].iteritems():
1553 for f in config.get('msvs_tool_files', []):
1555 for f in tool_files:
1559 def _HandlePreCompiledHeaders(p, sources, spec):
1560 # Pre-compiled header source stubs need a different compiler flag
1561 # (generate precompiled header) and any source file not of the same
1562 # kind (i.e. C vs. C++) as the precompiled header source stub needs
1563 # to have use of precompiled headers disabled.
1564 extensions_excluded_from_precompile = []
1565 for config_name, config in spec['configurations'].iteritems():
1566 source = config.get('msvs_precompiled_source')
1568 source = _FixPath(source)
1569 # UsePrecompiledHeader=1 for if using precompiled headers.
1570 tool = MSVSProject.Tool('VCCLCompilerTool',
1571 {'UsePrecompiledHeader': '1'})
1572 p.AddFileConfig(source, _ConfigFullName(config_name, config),
1574 basename, extension = os.path.splitext(source)
1575 if extension == '.c':
1576 extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
1578 extensions_excluded_from_precompile = ['.c']
1579 def DisableForSourceTree(source_tree):
1580 for source in source_tree:
1581 if isinstance(source, MSVSProject.Filter):
1582 DisableForSourceTree(source.contents)
1584 basename, extension = os.path.splitext(source)
1585 if extension in extensions_excluded_from_precompile:
1586 for config_name, config in spec['configurations'].iteritems():
1587 tool = MSVSProject.Tool('VCCLCompilerTool',
1588 {'UsePrecompiledHeader': '0',
1589 'ForcedIncludeFiles': '$(NOINHERIT)'})
1590 p.AddFileConfig(_FixPath(source),
1591 _ConfigFullName(config_name, config),
1593 # Do nothing if there was no precompiled source.
1594 if extensions_excluded_from_precompile:
1595 DisableForSourceTree(sources)
1598 def _AddActions(actions_to_add, spec, relative_path_of_gyp_file):
1600 actions = spec.get('actions', [])
1601 # Don't setup_env every time. When all the actions are run together in one
1602 # batch file in VS, the PATH will grow too long.
1603 # Membership in this set means that the cygwin environment has been set up,
1604 # and does not need to be set up again.
1605 have_setup_env = set()
1607 # Attach actions to the gyp file if nothing else is there.
1608 inputs = a.get('inputs') or [relative_path_of_gyp_file]
1609 attached_to = inputs[0]
1610 need_setup_env = attached_to not in have_setup_env
1611 cmd = _BuildCommandLineForRule(spec, a, has_input_path=False,
1612 do_setup_env=need_setup_env)
1613 have_setup_env.add(attached_to)
1615 _AddActionStep(actions_to_add,
1617 outputs=a.get('outputs', []),
1618 description=a.get('message', a['action_name']),
1622 def _WriteMSVSUserFile(project_path, version, spec):
1623 # Add run_as and test targets.
1624 if 'run_as' in spec:
1625 run_as = spec['run_as']
1626 action = run_as.get('action', [])
1627 environment = run_as.get('environment', [])
1628 working_directory = run_as.get('working_directory', '.')
1629 elif int(spec.get('test', 0)):
1630 action = ['$(TargetPath)', '--gtest_print_time']
1632 working_directory = '.'
1634 return # Nothing to add
1635 # Write out the user file.
1636 user_file = _CreateMSVSUserFile(project_path, version, spec)
1637 for config_name, c_data in spec['configurations'].iteritems():
1638 user_file.AddDebugSettings(_ConfigFullName(config_name, c_data),
1639 action, environment, working_directory)
1640 user_file.WriteIfChanged()
1643 def _AddCopies(actions_to_add, spec):
1644 copies = _GetCopies(spec)
1645 for inputs, outputs, cmd, description in copies:
1646 _AddActionStep(actions_to_add, inputs=inputs, outputs=outputs,
1647 description=description, command=cmd)
1650 def _GetCopies(spec):
1653 for cpy in spec.get('copies', []):
1654 for src in cpy.get('files', []):
1655 dst = os.path.join(cpy['destination'], os.path.basename(src))
1656 # _AddCustomBuildToolForMSVS() will call _FixPath() on the inputs and
1657 # outputs, so do the same for our generated command line.
1658 if src.endswith('/'):
1660 base_dir = posixpath.split(src_bare)[0]
1661 outer_dir = posixpath.split(src_bare)[1]
1662 cmd = 'cd "%s" && xcopy /e /f /y "%s" "%s\\%s\\"' % (
1663 _FixPath(base_dir), outer_dir, _FixPath(dst), outer_dir)
1664 copies.append(([src], ['dummy_copies', dst], cmd,
1665 'Copying %s to %s' % (src, dst)))
1667 cmd = 'mkdir "%s" 2>nul & set ERRORLEVEL=0 & copy /Y "%s" "%s"' % (
1668 _FixPath(cpy['destination']), _FixPath(src), _FixPath(dst))
1669 copies.append(([src], [dst], cmd, 'Copying %s to %s' % (src, dst)))
1673 def _GetPathDict(root, path):
1674 # |path| will eventually be empty (in the recursive calls) if it was initially
1675 # relative; otherwise it will eventually end up as '\', 'D:\', etc.
1676 if not path or path.endswith(os.sep):
1678 parent, folder = os.path.split(path)
1679 parent_dict = _GetPathDict(root, parent)
1680 if folder not in parent_dict:
1681 parent_dict[folder] = dict()
1682 return parent_dict[folder]
1685 def _DictsToFolders(base_path, bucket, flat):
1686 # Convert to folders recursively.
1688 for folder, contents in bucket.iteritems():
1689 if type(contents) == dict:
1690 folder_children = _DictsToFolders(os.path.join(base_path, folder),
1693 children += folder_children
1695 folder_children = MSVSNew.MSVSFolder(os.path.join(base_path, folder),
1696 name='(' + folder + ')',
1697 entries=folder_children)
1698 children.append(folder_children)
1700 children.append(contents)
1704 def _CollapseSingles(parent, node):
1705 # Recursively explorer the tree of dicts looking for projects which are
1706 # the sole item in a folder which has the same name as the project. Bring
1707 # such projects up one level.
1708 if (type(node) == dict and
1710 node.keys()[0] == parent + '.vcproj'):
1711 return node[node.keys()[0]]
1712 if type(node) != dict:
1715 node[child] = _CollapseSingles(child, node[child])
1719 def _GatherSolutionFolders(sln_projects, project_objects, flat):
1721 # Convert into a tree of dicts on path.
1722 for p in sln_projects:
1723 gyp_file, target = gyp.common.ParseQualifiedTarget(p)[0:2]
1724 gyp_dir = os.path.dirname(gyp_file)
1725 path_dict = _GetPathDict(root, gyp_dir)
1726 path_dict[target + '.vcproj'] = project_objects[p]
1727 # Walk down from the top until we hit a folder that has more than one entry.
1728 # In practice, this strips the top-level "src/" dir from the hierarchy in
1730 while len(root) == 1 and type(root[root.keys()[0]]) == dict:
1731 root = root[root.keys()[0]]
1733 root = _CollapseSingles('', root)
1734 # Merge buckets until everything is a root entry.
1735 return _DictsToFolders('', root, flat)
1738 def _GetPathOfProject(qualified_target, spec, options, msvs_version):
1739 default_config = _GetDefaultConfiguration(spec)
1740 proj_filename = default_config.get('msvs_existing_vcproj')
1741 if not proj_filename:
1742 proj_filename = (spec['target_name'] + options.suffix +
1743 msvs_version.ProjectExtension())
1745 build_file = gyp.common.BuildFile(qualified_target)
1746 proj_path = os.path.join(os.path.dirname(build_file), proj_filename)
1748 if options.generator_output:
1749 project_dir_path = os.path.dirname(os.path.abspath(proj_path))
1750 proj_path = os.path.join(options.generator_output, proj_path)
1751 fix_prefix = gyp.common.RelativePath(project_dir_path,
1752 os.path.dirname(proj_path))
1753 return proj_path, fix_prefix
1756 def _GetPlatformOverridesOfProject(spec):
1757 # Prepare a dict indicating which project configurations are used for which
1758 # solution configurations for this target.
1759 config_platform_overrides = {}
1760 for config_name, c in spec['configurations'].iteritems():
1761 config_fullname = _ConfigFullName(config_name, c)
1762 platform = c.get('msvs_target_platform', _ConfigPlatform(c))
1763 fixed_config_fullname = '%s|%s' % (
1764 _ConfigBaseName(config_name, _ConfigPlatform(c)), platform)
1765 config_platform_overrides[config_fullname] = fixed_config_fullname
1766 return config_platform_overrides
1769 def _CreateProjectObjects(target_list, target_dicts, options, msvs_version):
1770 """Create a MSVSProject object for the targets found in target list.
1773 target_list: the list of targets to generate project objects for.
1774 target_dicts: the dictionary of specifications.
1775 options: global generator options.
1776 msvs_version: the MSVSVersion object.
1778 A set of created projects, keyed by target.
1780 global fixpath_prefix
1781 # Generate each project.
1783 for qualified_target in target_list:
1784 spec = target_dicts[qualified_target]
1785 if spec['toolset'] != 'target':
1787 'Multiple toolsets not supported in msvs build (target %s)' %
1789 proj_path, fixpath_prefix = _GetPathOfProject(qualified_target, spec,
1790 options, msvs_version)
1791 guid = _GetGuidOfProject(proj_path, spec)
1792 overrides = _GetPlatformOverridesOfProject(spec)
1793 build_file = gyp.common.BuildFile(qualified_target)
1794 # Create object for this project.
1795 obj = MSVSNew.MSVSProject(
1797 name=spec['target_name'],
1800 build_file=build_file,
1801 config_platform_overrides=overrides,
1802 fixpath_prefix=fixpath_prefix)
1803 # Set project toolset if any (MS build only)
1804 if msvs_version.UsesVcxproj():
1805 obj.set_msbuild_toolset(
1806 _GetMsbuildToolsetOfProject(proj_path, spec, msvs_version))
1807 projects[qualified_target] = obj
1808 # Set all the dependencies, but not if we are using an external builder like
1810 for project in projects.values():
1811 if not project.spec.get('msvs_external_builder'):
1812 deps = project.spec.get('dependencies', [])
1813 deps = [projects[d] for d in deps]
1814 project.set_dependencies(deps)
1818 def _InitNinjaFlavor(options, target_list, target_dicts):
1819 """Initialize targets for the ninja flavor.
1821 This sets up the necessary variables in the targets to generate msvs projects
1822 that use ninja as an external builder. The variables in the spec are only set
1823 if they have not been set. This allows individual specs to override the
1824 default values initialized here.
1826 options: Options provided to the generator.
1827 target_list: List of target pairs: 'base/base.gyp:base'.
1828 target_dicts: Dict of target properties keyed on target pair.
1830 for qualified_target in target_list:
1831 spec = target_dicts[qualified_target]
1832 if spec.get('msvs_external_builder'):
1833 # The spec explicitly defined an external builder, so don't change it.
1836 path_to_ninja = spec.get('msvs_path_to_ninja', 'ninja.exe')
1838 spec['msvs_external_builder'] = 'ninja'
1839 if not spec.get('msvs_external_builder_out_dir'):
1840 spec['msvs_external_builder_out_dir'] = \
1841 options.depth + '/out/$(Configuration)'
1842 if not spec.get('msvs_external_builder_build_cmd'):
1843 spec['msvs_external_builder_build_cmd'] = [
1849 if not spec.get('msvs_external_builder_clean_cmd'):
1850 spec['msvs_external_builder_clean_cmd'] = [
1860 def CalculateVariables(default_variables, params):
1861 """Generated variables that require params to be known."""
1863 generator_flags = params.get('generator_flags', {})
1865 # Select project file format version (if unset, default to auto detecting).
1866 msvs_version = MSVSVersion.SelectVisualStudioVersion(
1867 generator_flags.get('msvs_version', 'auto'))
1868 # Stash msvs_version for later (so we don't have to probe the system twice).
1869 params['msvs_version'] = msvs_version
1871 # Set a variable so conditions can be based on msvs_version.
1872 default_variables['MSVS_VERSION'] = msvs_version.ShortName()
1874 # To determine processor word size on Windows, in addition to checking
1875 # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
1876 # process), it is also necessary to check PROCESSOR_ARCITEW6432 (which
1877 # contains the actual word size of the system when running thru WOW64).
1878 if (os.environ.get('PROCESSOR_ARCHITECTURE', '').find('64') >= 0 or
1879 os.environ.get('PROCESSOR_ARCHITEW6432', '').find('64') >= 0):
1880 default_variables['MSVS_OS_BITS'] = 64
1882 default_variables['MSVS_OS_BITS'] = 32
1884 if gyp.common.GetFlavor(params) == 'ninja':
1885 default_variables['SHARED_INTERMEDIATE_DIR'] = '$(OutDir)gen'
1888 def PerformBuild(data, configurations, params):
1889 options = params['options']
1890 msvs_version = params['msvs_version']
1891 devenv = os.path.join(msvs_version.path, 'Common7', 'IDE', 'devenv.com')
1893 for build_file, build_file_dict in data.iteritems():
1894 (build_file_root, build_file_ext) = os.path.splitext(build_file)
1895 if build_file_ext != '.gyp':
1897 sln_path = build_file_root + options.suffix + '.sln'
1898 if options.generator_output:
1899 sln_path = os.path.join(options.generator_output, sln_path)
1901 for config in configurations:
1902 arguments = [devenv, sln_path, '/Build', config]
1903 print 'Building [%s]: %s' % (config, arguments)
1904 rtn = subprocess.check_call(arguments)
1907 def GenerateOutput(target_list, target_dicts, data, params):
1908 """Generate .sln and .vcproj files.
1910 This is the entry point for this generator.
1912 target_list: List of target pairs: 'base/base.gyp:base'.
1913 target_dicts: Dict of target properties keyed on target pair.
1914 data: Dictionary containing per .gyp data.
1916 global fixpath_prefix
1918 options = params['options']
1920 # Get the project file format version back out of where we stashed it in
1921 # GeneratorCalculatedVariables.
1922 msvs_version = params['msvs_version']
1924 generator_flags = params.get('generator_flags', {})
1926 # Optionally shard targets marked with 'msvs_shard': SHARD_COUNT.
1927 (target_list, target_dicts) = MSVSUtil.ShardTargets(target_list, target_dicts)
1929 # Optionally use the large PDB workaround for targets marked with
1930 # 'msvs_large_pdb': 1.
1931 (target_list, target_dicts) = MSVSUtil.InsertLargePdbShims(
1932 target_list, target_dicts, generator_default_variables)
1934 # Optionally configure each spec to use ninja as the external builder.
1935 if params.get('flavor') == 'ninja':
1936 _InitNinjaFlavor(options, target_list, target_dicts)
1938 # Prepare the set of configurations.
1940 for qualified_target in target_list:
1941 spec = target_dicts[qualified_target]
1942 for config_name, config in spec['configurations'].iteritems():
1943 configs.add(_ConfigFullName(config_name, config))
1944 configs = list(configs)
1946 # Figure out all the projects that will be generated and their guids
1947 project_objects = _CreateProjectObjects(target_list, target_dicts, options,
1950 # Generate each project.
1951 missing_sources = []
1952 for project in project_objects.values():
1953 fixpath_prefix = project.fixpath_prefix
1954 missing_sources.extend(_GenerateProject(project, options, msvs_version,
1956 fixpath_prefix = None
1958 for build_file in data:
1959 # Validate build_file extension
1960 if not build_file.endswith('.gyp'):
1962 sln_path = os.path.splitext(build_file)[0] + options.suffix + '.sln'
1963 if options.generator_output:
1964 sln_path = os.path.join(options.generator_output, sln_path)
1965 # Get projects in the solution, and their dependents.
1966 sln_projects = gyp.common.BuildFileTargets(target_list, build_file)
1967 sln_projects += gyp.common.DeepDependencyTargets(target_dicts, sln_projects)
1968 # Create folder hierarchy.
1969 root_entries = _GatherSolutionFolders(
1970 sln_projects, project_objects, flat=msvs_version.FlatSolution())
1972 sln = MSVSNew.MSVSSolution(sln_path,
1973 entries=root_entries,
1975 websiteProperties=False,
1976 version=msvs_version)
1980 error_message = "Missing input files:\n" + \
1981 '\n'.join(set(missing_sources))
1982 if generator_flags.get('msvs_error_on_missing_sources', False):
1983 raise GypError(error_message)
1985 print >> sys.stdout, "Warning: " + error_message
1988 def _GenerateMSBuildFiltersFile(filters_path, source_files,
1989 extension_to_rule_name):
1990 """Generate the filters file.
1992 This file is used by Visual Studio to organize the presentation of source
1996 filters_path: The path of the file to be created.
1997 source_files: The hierarchical structure of all the sources.
1998 extension_to_rule_name: A dictionary mapping file extensions to rules.
2002 _AppendFiltersForMSBuild('', source_files, extension_to_rule_name,
2003 filter_group, source_group)
2005 content = ['Project',
2006 {'ToolsVersion': '4.0',
2007 'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
2009 ['ItemGroup'] + filter_group,
2010 ['ItemGroup'] + source_group
2012 easy_xml.WriteXmlIfChanged(content, filters_path, pretty=True, win32=True)
2013 elif os.path.exists(filters_path):
2014 # We don't need this filter anymore. Delete the old filter file.
2015 os.unlink(filters_path)
2018 def _AppendFiltersForMSBuild(parent_filter_name, sources,
2019 extension_to_rule_name,
2020 filter_group, source_group):
2021 """Creates the list of filters and sources to be added in the filter file.
2024 parent_filter_name: The name of the filter under which the sources are
2026 sources: The hierarchy of filters and sources to process.
2027 extension_to_rule_name: A dictionary mapping file extensions to rules.
2028 filter_group: The list to which filter entries will be appended.
2029 source_group: The list to which source entries will be appeneded.
2031 for source in sources:
2032 if isinstance(source, MSVSProject.Filter):
2033 # We have a sub-filter. Create the name of that sub-filter.
2034 if not parent_filter_name:
2035 filter_name = source.name
2037 filter_name = '%s\\%s' % (parent_filter_name, source.name)
2038 # Add the filter to the group.
2039 filter_group.append(
2040 ['Filter', {'Include': filter_name},
2041 ['UniqueIdentifier', MSVSNew.MakeGuid(source.name)]])
2042 # Recurse and add its dependents.
2043 _AppendFiltersForMSBuild(filter_name, source.contents,
2044 extension_to_rule_name,
2045 filter_group, source_group)
2047 # It's a source. Create a source entry.
2048 _, element = _MapFileToMsBuildSourceType(source, extension_to_rule_name)
2049 source_entry = [element, {'Include': source}]
2050 # Specify the filter it is part of, if any.
2051 if parent_filter_name:
2052 source_entry.append(['Filter', parent_filter_name])
2053 source_group.append(source_entry)
2056 def _MapFileToMsBuildSourceType(source, extension_to_rule_name):
2057 """Returns the group and element type of the source file.
2060 source: The source file name.
2061 extension_to_rule_name: A dictionary mapping file extensions to rules.
2064 A pair of (group this file should be part of, the label of element)
2066 _, ext = os.path.splitext(source)
2067 if ext in extension_to_rule_name:
2069 element = extension_to_rule_name[ext]
2070 elif ext in ['.cc', '.cpp', '.c', '.cxx']:
2072 element = 'ClCompile'
2073 elif ext in ['.h', '.hxx']:
2075 element = 'ClInclude'
2078 element = 'ResourceCompile'
2085 return (group, element)
2088 def _GenerateRulesForMSBuild(output_dir, options, spec,
2089 sources, excluded_sources,
2090 props_files_of_rules, targets_files_of_rules,
2091 actions_to_add, extension_to_rule_name):
2092 # MSBuild rules are implemented using three files: an XML file, a .targets
2093 # file and a .props file.
2094 # See http://blogs.msdn.com/b/vcblog/archive/2010/04/21/quick-help-on-vs2010-custom-build-rule.aspx
2096 rules = spec.get('rules', [])
2097 rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
2098 rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
2101 for rule in rules_native:
2102 # Skip a rule with no action and no inputs.
2103 if 'action' not in rule and not rule.get('rule_sources', []):
2105 msbuild_rule = MSBuildRule(rule, spec)
2106 msbuild_rules.append(msbuild_rule)
2107 extension_to_rule_name[msbuild_rule.extension] = msbuild_rule.rule_name
2109 base = spec['target_name'] + options.suffix
2110 props_name = base + '.props'
2111 targets_name = base + '.targets'
2112 xml_name = base + '.xml'
2114 props_files_of_rules.add(props_name)
2115 targets_files_of_rules.add(targets_name)
2117 props_path = os.path.join(output_dir, props_name)
2118 targets_path = os.path.join(output_dir, targets_name)
2119 xml_path = os.path.join(output_dir, xml_name)
2121 _GenerateMSBuildRulePropsFile(props_path, msbuild_rules)
2122 _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules)
2123 _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules)
2126 _GenerateExternalRules(rules_external, output_dir, spec,
2127 sources, options, actions_to_add)
2128 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
2131 class MSBuildRule(object):
2132 """Used to store information used to generate an MSBuild rule.
2135 rule_name: The rule name, sanitized to use in XML.
2136 target_name: The name of the target.
2137 after_targets: The name of the AfterTargets element.
2138 before_targets: The name of the BeforeTargets element.
2139 depends_on: The name of the DependsOn element.
2140 compute_output: The name of the ComputeOutput element.
2141 dirs_to_make: The name of the DirsToMake element.
2142 inputs: The name of the _inputs element.
2143 tlog: The name of the _tlog element.
2144 extension: The extension this rule applies to.
2145 description: The message displayed when this rule is invoked.
2146 additional_dependencies: A string listing additional dependencies.
2147 outputs: The outputs of this rule.
2148 command: The command used to run the rule.
2151 def __init__(self, rule, spec):
2152 self.display_name = rule['rule_name']
2153 # Assure that the rule name is only characters and numbers
2154 self.rule_name = re.sub(r'\W', '_', self.display_name)
2155 # Create the various element names, following the example set by the
2156 # Visual Studio 2008 to 2010 conversion. I don't know if VS2010
2157 # is sensitive to the exact names.
2158 self.target_name = '_' + self.rule_name
2159 self.after_targets = self.rule_name + 'AfterTargets'
2160 self.before_targets = self.rule_name + 'BeforeTargets'
2161 self.depends_on = self.rule_name + 'DependsOn'
2162 self.compute_output = 'Compute%sOutput' % self.rule_name
2163 self.dirs_to_make = self.rule_name + 'DirsToMake'
2164 self.inputs = self.rule_name + '_inputs'
2165 self.tlog = self.rule_name + '_tlog'
2166 self.extension = rule['extension']
2167 if not self.extension.startswith('.'):
2168 self.extension = '.' + self.extension
2170 self.description = MSVSSettings.ConvertVCMacrosToMSBuild(
2171 rule.get('message', self.rule_name))
2172 old_additional_dependencies = _FixPaths(rule.get('inputs', []))
2173 self.additional_dependencies = (
2174 ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
2175 for i in old_additional_dependencies]))
2176 old_outputs = _FixPaths(rule.get('outputs', []))
2177 self.outputs = ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
2178 for i in old_outputs])
2179 old_command = _BuildCommandLineForRule(spec, rule, has_input_path=True,
2181 self.command = MSVSSettings.ConvertVCMacrosToMSBuild(old_command)
2184 def _GenerateMSBuildRulePropsFile(props_path, msbuild_rules):
2185 """Generate the .props file."""
2186 content = ['Project',
2187 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'}]
2188 for rule in msbuild_rules:
2191 {'Condition': "'$(%s)' == '' and '$(%s)' == '' and "
2192 "'$(ConfigurationType)' != 'Makefile'" % (rule.before_targets,
2195 [rule.before_targets, 'Midl'],
2196 [rule.after_targets, 'CustomBuild'],
2200 {'Condition': "'$(ConfigurationType)' != 'Makefile'"},
2201 '_SelectedFiles;$(%s)' % rule.depends_on
2204 ['ItemDefinitionGroup',
2206 ['CommandLineTemplate', rule.command],
2207 ['Outputs', rule.outputs],
2208 ['ExecutionDescription', rule.description],
2209 ['AdditionalDependencies', rule.additional_dependencies],
2213 easy_xml.WriteXmlIfChanged(content, props_path, pretty=True, win32=True)
2216 def _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules):
2217 """Generate the .targets file."""
2218 content = ['Project',
2219 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
2224 ['PropertyPageSchema',
2225 {'Include': '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'}
2228 for rule in msbuild_rules:
2230 ['AvailableItemName',
2231 {'Include': rule.rule_name},
2232 ['Targets', rule.target_name],
2234 content.append(item_group)
2236 for rule in msbuild_rules:
2239 {'TaskName': rule.rule_name,
2240 'TaskFactory': 'XamlTaskFactory',
2241 'AssemblyName': 'Microsoft.Build.Tasks.v4.0'
2243 ['Task', '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'],
2245 for rule in msbuild_rules:
2246 rule_name = rule.rule_name
2247 target_outputs = '%%(%s.Outputs)' % rule_name
2248 target_inputs = ('%%(%s.Identity);%%(%s.AdditionalDependencies);'
2249 '$(MSBuildProjectFile)') % (rule_name, rule_name)
2250 rule_inputs = '%%(%s.Identity)' % rule_name
2251 extension_condition = ("'%(Extension)'=='.obj' or "
2252 "'%(Extension)'=='.res' or "
2253 "'%(Extension)'=='.rsc' or "
2254 "'%(Extension)'=='.lib'")
2257 {'Condition': "'@(SelectedFiles)' != ''"},
2259 {'Remove': '@(%s)' % rule_name,
2260 'Condition': "'%(Identity)' != '@(SelectedFiles)'"
2266 [rule.inputs, {'Include': '%%(%s.AdditionalDependencies)' % rule_name}]
2271 {'Include': '%%(%s.Outputs)' % rule_name,
2272 'Condition': ("'%%(%s.Outputs)' != '' and "
2273 "'%%(%s.ExcludedFromBuild)' != 'true'" %
2274 (rule_name, rule_name))
2276 ['Source', "@(%s, '|')" % rule_name],
2277 ['Inputs', "@(%s -> '%%(Fullpath)', ';')" % rule.inputs],
2282 {'Importance': 'High',
2283 'Text': '%%(%s.ExecutionDescription)' % rule_name
2286 write_tlog_section = [
2288 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2289 "'true'" % (rule.tlog, rule.tlog),
2290 'File': '$(IntDir)$(ProjectName).write.1.tlog',
2291 'Lines': "^%%(%s.Source);@(%s->'%%(Fullpath)')" % (rule.tlog,
2295 read_tlog_section = [
2297 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2298 "'true'" % (rule.tlog, rule.tlog),
2299 'File': '$(IntDir)$(ProjectName).read.1.tlog',
2300 'Lines': "^%%(%s.Source);%%(%s.Inputs)" % (rule.tlog, rule.tlog)
2303 command_and_input_section = [
2305 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2306 "'true'" % (rule_name, rule_name),
2307 'CommandLineTemplate': '%%(%s.CommandLineTemplate)' % rule_name,
2308 'AdditionalOptions': '%%(%s.AdditionalOptions)' % rule_name,
2309 'Inputs': rule_inputs
2314 {'Name': rule.target_name,
2315 'BeforeTargets': '$(%s)' % rule.before_targets,
2316 'AfterTargets': '$(%s)' % rule.after_targets,
2317 'Condition': "'@(%s)' != ''" % rule_name,
2318 'DependsOnTargets': '$(%s);%s' % (rule.depends_on,
2319 rule.compute_output),
2320 'Outputs': target_outputs,
2321 'Inputs': target_inputs
2329 command_and_input_section,
2332 ['ComputeLinkInputsTargets',
2333 '$(ComputeLinkInputsTargets);',
2334 '%s;' % rule.compute_output
2336 ['ComputeLibInputsTargets',
2337 '$(ComputeLibInputsTargets);',
2338 '%s;' % rule.compute_output
2342 {'Name': rule.compute_output,
2343 'Condition': "'@(%s)' != ''" % rule_name
2347 {'Condition': "'@(%s)' != '' and "
2348 "'%%(%s.ExcludedFromBuild)' != 'true'" % (rule_name, rule_name),
2349 'Include': '%%(%s.Outputs)' % rule_name
2353 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2354 'Condition': extension_condition
2358 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2359 'Condition': extension_condition
2363 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2364 'Condition': extension_condition
2369 {'Directories': ("@(%s->'%%(RootDir)%%(Directory)')" %
2375 easy_xml.WriteXmlIfChanged(content, targets_path, pretty=True, win32=True)
2378 def _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules):
2379 # Generate the .xml file
2381 'ProjectSchemaDefinitions',
2382 {'xmlns': ('clr-namespace:Microsoft.Build.Framework.XamlTypes;'
2383 'assembly=Microsoft.Build.Framework'),
2384 'xmlns:x': 'http://schemas.microsoft.com/winfx/2006/xaml',
2385 'xmlns:sys': 'clr-namespace:System;assembly=mscorlib',
2386 'xmlns:transformCallback':
2387 'Microsoft.Cpp.Dev10.ConvertPropertyCallback'
2390 for rule in msbuild_rules:
2393 {'Name': rule.rule_name,
2394 'PageTemplate': 'tool',
2395 'DisplayName': rule.display_name,
2400 {'Persistence': 'ProjectFile',
2401 'ItemType': rule.rule_name
2407 {'Name': 'General'},
2408 ['Category.DisplayName',
2409 ['sys:String', 'General'],
2413 {'Name': 'Command Line',
2414 'Subtype': 'CommandLine'
2416 ['Category.DisplayName',
2417 ['sys:String', 'Command Line'],
2421 ['StringListProperty',
2423 'Category': 'Command Line',
2424 'IsRequired': 'true',
2427 ['StringListProperty.DataSource',
2429 {'Persistence': 'ProjectFile',
2430 'ItemType': rule.rule_name,
2431 'SourceType': 'Item'
2437 {'Name': 'CommandLineTemplate',
2438 'DisplayName': 'Command Line',
2440 'IncludeInCommandLine': 'False'
2443 ['DynamicEnumProperty',
2444 {'Name': rule.before_targets,
2445 'Category': 'General',
2446 'EnumProvider': 'Targets',
2447 'IncludeInCommandLine': 'False'
2449 ['DynamicEnumProperty.DisplayName',
2450 ['sys:String', 'Execute Before'],
2452 ['DynamicEnumProperty.Description',
2453 ['sys:String', 'Specifies the targets for the build customization'
2457 ['DynamicEnumProperty.ProviderSettings',
2460 'Value': '^%s|^Compute' % rule.before_targets
2464 ['DynamicEnumProperty.DataSource',
2466 {'Persistence': 'ProjectFile',
2467 'HasConfigurationCondition': 'true'
2472 ['DynamicEnumProperty',
2473 {'Name': rule.after_targets,
2474 'Category': 'General',
2475 'EnumProvider': 'Targets',
2476 'IncludeInCommandLine': 'False'
2478 ['DynamicEnumProperty.DisplayName',
2479 ['sys:String', 'Execute After'],
2481 ['DynamicEnumProperty.Description',
2482 ['sys:String', ('Specifies the targets for the build customization'
2486 ['DynamicEnumProperty.ProviderSettings',
2489 'Value': '^%s|^Compute' % rule.after_targets
2493 ['DynamicEnumProperty.DataSource',
2495 {'Persistence': 'ProjectFile',
2497 'HasConfigurationCondition': 'true'
2502 ['StringListProperty',
2504 'DisplayName': 'Outputs',
2506 'IncludeInCommandLine': 'False'
2510 {'Name': 'ExecutionDescription',
2511 'DisplayName': 'Execution Description',
2513 'IncludeInCommandLine': 'False'
2516 ['StringListProperty',
2517 {'Name': 'AdditionalDependencies',
2518 'DisplayName': 'Additional Dependencies',
2519 'IncludeInCommandLine': 'False',
2524 {'Subtype': 'AdditionalOptions',
2525 'Name': 'AdditionalOptions',
2526 'Category': 'Command Line'
2528 ['StringProperty.DisplayName',
2529 ['sys:String', 'Additional Options'],
2531 ['StringProperty.Description',
2532 ['sys:String', 'Additional Options'],
2537 {'Name': rule.rule_name,
2538 'DisplayName': rule.display_name
2542 {'Name': '*' + rule.extension,
2543 'ContentType': rule.rule_name
2547 {'Name': rule.rule_name,
2549 'ItemType': rule.rule_name
2553 easy_xml.WriteXmlIfChanged(content, xml_path, pretty=True, win32=True)
2556 def _GetConfigurationAndPlatform(name, settings):
2557 configuration = name.rsplit('_', 1)[0]
2558 platform = settings.get('msvs_configuration_platform', 'Win32')
2559 return (configuration, platform)
2562 def _GetConfigurationCondition(name, settings):
2563 return (r"'$(Configuration)|$(Platform)'=='%s|%s'" %
2564 _GetConfigurationAndPlatform(name, settings))
2567 def _GetMSBuildProjectConfigurations(configurations):
2568 group = ['ItemGroup', {'Label': 'ProjectConfigurations'}]
2569 for (name, settings) in sorted(configurations.iteritems()):
2570 configuration, platform = _GetConfigurationAndPlatform(name, settings)
2571 designation = '%s|%s' % (configuration, platform)
2573 ['ProjectConfiguration', {'Include': designation},
2574 ['Configuration', configuration],
2575 ['Platform', platform]])
2579 def _GetMSBuildGlobalProperties(spec, guid, gyp_file_name):
2580 namespace = os.path.splitext(gyp_file_name)[0]
2582 ['PropertyGroup', {'Label': 'Globals'},
2583 ['ProjectGuid', guid],
2584 ['Keyword', 'Win32Proj'],
2585 ['RootNamespace', namespace],
2590 def _GetMSBuildConfigurationDetails(spec, build_file):
2592 for name, settings in spec['configurations'].iteritems():
2593 msbuild_attributes = _GetMSBuildAttributes(spec, settings, build_file)
2594 condition = _GetConfigurationCondition(name, settings)
2595 character_set = msbuild_attributes.get('CharacterSet')
2596 _AddConditionalProperty(properties, condition, 'ConfigurationType',
2597 msbuild_attributes['ConfigurationType'])
2599 _AddConditionalProperty(properties, condition, 'CharacterSet',
2601 return _GetMSBuildPropertyGroup(spec, 'Configuration', properties)
2604 def _GetMSBuildLocalProperties(msbuild_toolset):
2605 # Currently the only local property we support is PlatformToolset
2609 ['PropertyGroup', {'Label': 'Locals'},
2610 ['PlatformToolset', msbuild_toolset],
2616 def _GetMSBuildPropertySheets(configurations):
2617 user_props = r'$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props'
2618 additional_props = {}
2619 props_specified = False
2620 for name, settings in sorted(configurations.iteritems()):
2621 configuration = _GetConfigurationCondition(name, settings)
2622 if settings.has_key('msbuild_props'):
2623 additional_props[configuration] = _FixPaths(settings['msbuild_props'])
2624 props_specified = True
2626 additional_props[configuration] = ''
2628 if not props_specified:
2631 {'Label': 'PropertySheets'},
2633 {'Project': user_props,
2634 'Condition': "exists('%s')" % user_props,
2635 'Label': 'LocalAppDataPlatform'
2642 for condition, props in additional_props.iteritems():
2645 {'Label': 'PropertySheets',
2646 'Condition': condition
2649 {'Project': user_props,
2650 'Condition': "exists('%s')" % user_props,
2651 'Label': 'LocalAppDataPlatform'
2655 for props_file in props:
2656 import_group.append(['Import', {'Project':props_file}])
2657 sheets.append(import_group)
2660 def _ConvertMSVSBuildAttributes(spec, config, build_file):
2661 config_type = _GetMSVSConfigurationType(spec, build_file)
2662 msvs_attributes = _GetMSVSAttributes(spec, config, config_type)
2663 msbuild_attributes = {}
2664 for a in msvs_attributes:
2665 if a in ['IntermediateDirectory', 'OutputDirectory']:
2666 directory = MSVSSettings.ConvertVCMacrosToMSBuild(msvs_attributes[a])
2667 if not directory.endswith('\\'):
2669 msbuild_attributes[a] = directory
2670 elif a == 'CharacterSet':
2671 msbuild_attributes[a] = _ConvertMSVSCharacterSet(msvs_attributes[a])
2672 elif a == 'ConfigurationType':
2673 msbuild_attributes[a] = _ConvertMSVSConfigurationType(msvs_attributes[a])
2675 print 'Warning: Do not know how to convert MSVS attribute ' + a
2676 return msbuild_attributes
2679 def _ConvertMSVSCharacterSet(char_set):
2680 if char_set.isdigit():
2689 def _ConvertMSVSConfigurationType(config_type):
2690 if config_type.isdigit():
2693 '2': 'DynamicLibrary',
2694 '4': 'StaticLibrary',
2700 def _GetMSBuildAttributes(spec, config, build_file):
2701 if 'msbuild_configuration_attributes' not in config:
2702 msbuild_attributes = _ConvertMSVSBuildAttributes(spec, config, build_file)
2705 config_type = _GetMSVSConfigurationType(spec, build_file)
2706 config_type = _ConvertMSVSConfigurationType(config_type)
2707 msbuild_attributes = config.get('msbuild_configuration_attributes', {})
2708 msbuild_attributes.setdefault('ConfigurationType', config_type)
2709 output_dir = msbuild_attributes.get('OutputDirectory',
2710 '$(SolutionDir)$(Configuration)')
2711 msbuild_attributes['OutputDirectory'] = _FixPath(output_dir) + '\\'
2712 if 'IntermediateDirectory' not in msbuild_attributes:
2713 intermediate = _FixPath('$(Configuration)') + '\\'
2714 msbuild_attributes['IntermediateDirectory'] = intermediate
2715 if 'CharacterSet' in msbuild_attributes:
2716 msbuild_attributes['CharacterSet'] = _ConvertMSVSCharacterSet(
2717 msbuild_attributes['CharacterSet'])
2718 if 'TargetName' not in msbuild_attributes:
2719 prefix = spec.get('product_prefix', '')
2720 product_name = spec.get('product_name', '$(ProjectName)')
2721 target_name = prefix + product_name
2722 msbuild_attributes['TargetName'] = target_name
2724 if spec.get('msvs_external_builder'):
2725 external_out_dir = spec.get('msvs_external_builder_out_dir', '.')
2726 msbuild_attributes['OutputDirectory'] = _FixPath(external_out_dir) + '\\'
2728 # Make sure that 'TargetPath' matches 'Lib.OutputFile' or 'Link.OutputFile'
2729 # (depending on the tool used) to avoid MSB8012 warning.
2730 msbuild_tool_map = {
2731 'executable': 'Link',
2732 'shared_library': 'Link',
2733 'loadable_module': 'Link',
2734 'static_library': 'Lib',
2736 msbuild_tool = msbuild_tool_map.get(spec['type'])
2738 msbuild_settings = config['finalized_msbuild_settings']
2739 out_file = msbuild_settings[msbuild_tool].get('OutputFile')
2741 msbuild_attributes['TargetPath'] = _FixPath(out_file)
2742 target_ext = msbuild_settings[msbuild_tool].get('TargetExt')
2744 msbuild_attributes['TargetExt'] = target_ext
2746 return msbuild_attributes
2749 def _GetMSBuildConfigurationGlobalProperties(spec, configurations, build_file):
2750 # TODO(jeanluc) We could optimize out the following and do it only if
2751 # there are actions.
2752 # TODO(jeanluc) Handle the equivalent of setting 'CYGWIN=nontsec'.
2754 cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.'])[0]
2756 cyg_path = '$(MSBuildProjectDirectory)\\%s\\bin\\' % _FixPath(cygwin_dirs)
2757 new_paths.append(cyg_path)
2758 # TODO(jeanluc) Change the convention to have both a cygwin_dir and a
2760 python_path = cyg_path.replace('cygwin\\bin', 'python_26')
2761 new_paths.append(python_path)
2763 new_paths = '$(ExecutablePath);' + ';'.join(new_paths)
2766 for (name, configuration) in sorted(configurations.iteritems()):
2767 condition = _GetConfigurationCondition(name, configuration)
2768 attributes = _GetMSBuildAttributes(spec, configuration, build_file)
2769 msbuild_settings = configuration['finalized_msbuild_settings']
2770 _AddConditionalProperty(properties, condition, 'IntDir',
2771 attributes['IntermediateDirectory'])
2772 _AddConditionalProperty(properties, condition, 'OutDir',
2773 attributes['OutputDirectory'])
2774 _AddConditionalProperty(properties, condition, 'TargetName',
2775 attributes['TargetName'])
2777 if attributes.get('TargetPath'):
2778 _AddConditionalProperty(properties, condition, 'TargetPath',
2779 attributes['TargetPath'])
2780 if attributes.get('TargetExt'):
2781 _AddConditionalProperty(properties, condition, 'TargetExt',
2782 attributes['TargetExt'])
2785 _AddConditionalProperty(properties, condition, 'ExecutablePath',
2787 tool_settings = msbuild_settings.get('', {})
2788 for name, value in sorted(tool_settings.iteritems()):
2789 formatted_value = _GetValueFormattedForMSBuild('', name, value)
2790 _AddConditionalProperty(properties, condition, name, formatted_value)
2791 return _GetMSBuildPropertyGroup(spec, None, properties)
2794 def _AddConditionalProperty(properties, condition, name, value):
2795 """Adds a property / conditional value pair to a dictionary.
2798 properties: The dictionary to be modified. The key is the name of the
2799 property. The value is itself a dictionary; its key is the value and
2800 the value a list of condition for which this value is true.
2801 condition: The condition under which the named property has the value.
2802 name: The name of the property.
2803 value: The value of the property.
2805 if name not in properties:
2806 properties[name] = {}
2807 values = properties[name]
2808 if value not in values:
2810 conditions = values[value]
2811 conditions.append(condition)
2814 # Regex for msvs variable references ( i.e. $(FOO) ).
2815 MSVS_VARIABLE_REFERENCE = re.compile('\$\(([a-zA-Z_][a-zA-Z0-9_]*)\)')
2818 def _GetMSBuildPropertyGroup(spec, label, properties):
2819 """Returns a PropertyGroup definition for the specified properties.
2822 spec: The target project dict.
2823 label: An optional label for the PropertyGroup.
2824 properties: The dictionary to be converted. The key is the name of the
2825 property. The value is itself a dictionary; its key is the value and
2826 the value a list of condition for which this value is true.
2828 group = ['PropertyGroup']
2830 group.append({'Label': label})
2831 num_configurations = len(spec['configurations'])
2833 # Use a definition of edges such that user_of_variable -> used_varible.
2834 # This happens to be easier in this case, since a variable's
2835 # definition contains all variables it references in a single string.
2837 for value in sorted(properties[node].keys()):
2838 # Add to edges all $(...) references to variables.
2840 # Variable references that refer to names not in properties are excluded
2841 # These can exist for instance to refer built in definitions like
2844 # Self references are ignored. Self reference is used in a few places to
2845 # append to the default value. I.e. PATH=$(PATH);other_path
2846 edges.update(set([v for v in MSVS_VARIABLE_REFERENCE.findall(value)
2847 if v in properties and v != node]))
2849 properties_ordered = gyp.common.TopologicallySorted(
2850 properties.keys(), GetEdges)
2851 # Walk properties in the reverse of a topological sort on
2852 # user_of_variable -> used_variable as this ensures variables are
2853 # defined before they are used.
2854 # NOTE: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))
2855 for name in reversed(properties_ordered):
2856 values = properties[name]
2857 for value, conditions in sorted(values.iteritems()):
2858 if len(conditions) == num_configurations:
2859 # If the value is the same all configurations,
2860 # just add one unconditional entry.
2861 group.append([name, value])
2863 for condition in conditions:
2864 group.append([name, {'Condition': condition}, value])
2868 def _GetMSBuildToolSettingsSections(spec, configurations):
2870 for (name, configuration) in sorted(configurations.iteritems()):
2871 msbuild_settings = configuration['finalized_msbuild_settings']
2872 group = ['ItemDefinitionGroup',
2873 {'Condition': _GetConfigurationCondition(name, configuration)}
2875 for tool_name, tool_settings in sorted(msbuild_settings.iteritems()):
2876 # Skip the tool named '' which is a holder of global settings handled
2877 # by _GetMSBuildConfigurationGlobalProperties.
2881 for name, value in sorted(tool_settings.iteritems()):
2882 formatted_value = _GetValueFormattedForMSBuild(tool_name, name,
2884 tool.append([name, formatted_value])
2886 groups.append(group)
2890 def _FinalizeMSBuildSettings(spec, configuration):
2891 if 'msbuild_settings' in configuration:
2893 msbuild_settings = configuration['msbuild_settings']
2894 MSVSSettings.ValidateMSBuildSettings(msbuild_settings)
2897 msvs_settings = configuration.get('msvs_settings', {})
2898 msbuild_settings = MSVSSettings.ConvertToMSBuildSettings(msvs_settings)
2899 include_dirs, resource_include_dirs = _GetIncludeDirs(configuration)
2900 libraries = _GetLibraries(spec)
2901 library_dirs = _GetLibraryDirs(configuration)
2902 out_file, _, msbuild_tool = _GetOutputFilePathAndTool(spec, msbuild=True)
2903 target_ext = _GetOutputTargetExt(spec)
2904 defines = _GetDefines(configuration)
2906 # Visual Studio 2010 has TR1
2907 defines = [d for d in defines if d != '_HAS_TR1=0']
2908 # Warn of ignored settings
2909 ignored_settings = ['msvs_prebuild', 'msvs_postbuild', 'msvs_tool_files']
2910 for ignored_setting in ignored_settings:
2911 value = configuration.get(ignored_setting)
2913 print ('Warning: The automatic conversion to MSBuild does not handle '
2914 '%s. Ignoring setting of %s' % (ignored_setting, str(value)))
2916 defines = [_EscapeCppDefineForMSBuild(d) for d in defines]
2917 disabled_warnings = _GetDisabledWarnings(configuration)
2918 # TODO(jeanluc) Validate & warn that we don't translate
2919 # prebuild = configuration.get('msvs_prebuild')
2920 # postbuild = configuration.get('msvs_postbuild')
2921 def_file = _GetModuleDefinition(spec)
2922 precompiled_header = configuration.get('msvs_precompiled_header')
2924 # Add the information to the appropriate tool
2925 # TODO(jeanluc) We could optimize and generate these settings only if
2926 # the corresponding files are found, e.g. don't generate ResourceCompile
2927 # if you don't have any resources.
2928 _ToolAppend(msbuild_settings, 'ClCompile',
2929 'AdditionalIncludeDirectories', include_dirs)
2930 _ToolAppend(msbuild_settings, 'ResourceCompile',
2931 'AdditionalIncludeDirectories', resource_include_dirs)
2932 # Add in libraries, note that even for empty libraries, we want this
2933 # set, to prevent inheriting default libraries from the enviroment.
2934 _ToolSetOrAppend(msbuild_settings, 'Link', 'AdditionalDependencies',
2936 _ToolAppend(msbuild_settings, 'Link', 'AdditionalLibraryDirectories',
2939 _ToolAppend(msbuild_settings, msbuild_tool, 'OutputFile', out_file,
2942 _ToolAppend(msbuild_settings, msbuild_tool, 'TargetExt', target_ext,
2945 _ToolAppend(msbuild_settings, 'ClCompile',
2946 'PreprocessorDefinitions', defines)
2947 _ToolAppend(msbuild_settings, 'ResourceCompile',
2948 'PreprocessorDefinitions', defines)
2949 # Add disabled warnings.
2950 _ToolAppend(msbuild_settings, 'ClCompile',
2951 'DisableSpecificWarnings', disabled_warnings)
2952 # Turn on precompiled headers if appropriate.
2953 if precompiled_header:
2954 precompiled_header = os.path.split(precompiled_header)[1]
2955 _ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'Use')
2956 _ToolAppend(msbuild_settings, 'ClCompile',
2957 'PrecompiledHeaderFile', precompiled_header)
2958 _ToolAppend(msbuild_settings, 'ClCompile',
2959 'ForcedIncludeFiles', [precompiled_header])
2960 # Loadable modules don't generate import libraries;
2961 # tell dependent projects to not expect one.
2962 if spec['type'] == 'loadable_module':
2963 _ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'true')
2964 # Set the module definition file if any.
2966 _ToolAppend(msbuild_settings, 'Link', 'ModuleDefinitionFile', def_file)
2967 configuration['finalized_msbuild_settings'] = msbuild_settings
2970 def _GetValueFormattedForMSBuild(tool_name, name, value):
2971 if type(value) == list:
2972 # For some settings, VS2010 does not automatically extends the settings
2973 # TODO(jeanluc) Is this what we want?
2974 if name in ['AdditionalIncludeDirectories',
2975 'AdditionalLibraryDirectories',
2976 'AdditionalOptions',
2978 'DisableSpecificWarnings',
2979 'PreprocessorDefinitions']:
2980 value.append('%%(%s)' % name)
2981 # For most tools, entries in a list should be separated with ';' but some
2982 # settings use a space. Check for those first.
2984 'ClCompile': ['AdditionalOptions'],
2985 'Link': ['AdditionalOptions'],
2986 'Lib': ['AdditionalOptions']}
2987 if tool_name in exceptions and name in exceptions[tool_name]:
2991 formatted_value = char.join(
2992 [MSVSSettings.ConvertVCMacrosToMSBuild(i) for i in value])
2994 formatted_value = MSVSSettings.ConvertVCMacrosToMSBuild(value)
2995 return formatted_value
2998 def _VerifySourcesExist(sources, root_dir):
2999 """Verifies that all source files exist on disk.
3001 Checks that all regular source files, i.e. not created at run time,
3002 exist on disk. Missing files cause needless recompilation but no otherwise
3006 sources: A recursive list of Filter/file names.
3007 root_dir: The root directory for the relative path names.
3009 A list of source files that cannot be found on disk.
3011 missing_sources = []
3012 for source in sources:
3013 if isinstance(source, MSVSProject.Filter):
3014 missing_sources.extend(_VerifySourcesExist(source.contents, root_dir))
3016 if '$' not in source:
3017 full_path = os.path.join(root_dir, source)
3018 if not os.path.exists(full_path):
3019 missing_sources.append(full_path)
3020 return missing_sources
3023 def _GetMSBuildSources(spec, sources, exclusions, extension_to_rule_name,
3024 actions_spec, sources_handled_by_action, list_excluded):
3025 groups = ['none', 'midl', 'include', 'compile', 'resource', 'rule']
3026 grouped_sources = {}
3028 grouped_sources[g] = []
3030 _AddSources2(spec, sources, exclusions, grouped_sources,
3031 extension_to_rule_name, sources_handled_by_action, list_excluded)
3034 if grouped_sources[g]:
3035 sources.append(['ItemGroup'] + grouped_sources[g])
3037 sources.append(['ItemGroup'] + actions_spec)
3041 def _AddSources2(spec, sources, exclusions, grouped_sources,
3042 extension_to_rule_name, sources_handled_by_action,
3044 extensions_excluded_from_precompile = []
3045 for source in sources:
3046 if isinstance(source, MSVSProject.Filter):
3047 _AddSources2(spec, source.contents, exclusions, grouped_sources,
3048 extension_to_rule_name, sources_handled_by_action,
3051 if not source in sources_handled_by_action:
3053 excluded_configurations = exclusions.get(source, [])
3054 if len(excluded_configurations) == len(spec['configurations']):
3055 detail.append(['ExcludedFromBuild', 'true'])
3057 for config_name, configuration in sorted(excluded_configurations):
3058 condition = _GetConfigurationCondition(config_name, configuration)
3059 detail.append(['ExcludedFromBuild',
3060 {'Condition': condition},
3062 # Add precompile if needed
3063 for config_name, configuration in spec['configurations'].iteritems():
3064 precompiled_source = configuration.get('msvs_precompiled_source', '')
3065 if precompiled_source != '':
3066 precompiled_source = _FixPath(precompiled_source)
3067 if not extensions_excluded_from_precompile:
3068 # If the precompiled header is generated by a C source, we must
3069 # not try to use it for C++ sources, and vice versa.
3070 basename, extension = os.path.splitext(precompiled_source)
3071 if extension == '.c':
3072 extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
3074 extensions_excluded_from_precompile = ['.c']
3076 if precompiled_source == source:
3077 condition = _GetConfigurationCondition(config_name, configuration)
3078 detail.append(['PrecompiledHeader',
3079 {'Condition': condition},
3083 # Turn off precompiled header usage for source files of a
3084 # different type than the file that generated the
3085 # precompiled header.
3086 for extension in extensions_excluded_from_precompile:
3087 if source.endswith(extension):
3088 detail.append(['PrecompiledHeader', ''])
3089 detail.append(['ForcedIncludeFiles', ''])
3091 group, element = _MapFileToMsBuildSourceType(source,
3092 extension_to_rule_name)
3093 grouped_sources[group].append([element, {'Include': source}] + detail)
3096 def _GetMSBuildProjectReferences(project):
3098 if project.dependencies:
3099 group = ['ItemGroup']
3100 for dependency in project.dependencies:
3101 guid = dependency.guid
3102 project_dir = os.path.split(project.path)[0]
3103 relative_path = gyp.common.RelativePath(dependency.path, project_dir)
3104 project_ref = ['ProjectReference',
3105 {'Include': relative_path},
3107 ['ReferenceOutputAssembly', 'false']
3109 for config in dependency.spec.get('configurations', {}).itervalues():
3110 # If it's disabled in any config, turn it off in the reference.
3111 if config.get('msvs_2010_disable_uldi_when_referenced', 0):
3112 project_ref.append(['UseLibraryDependencyInputs', 'false'])
3114 group.append(project_ref)
3115 references.append(group)
3119 def _GenerateMSBuildProject(project, options, version, generator_flags):
3121 configurations = spec['configurations']
3122 project_dir, project_file_name = os.path.split(project.path)
3123 gyp.common.EnsureDirExists(project.path)
3124 # Prepare list of sources and excluded sources.
3125 gyp_path = _NormalizedSource(project.build_file)
3126 relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
3128 gyp_file = os.path.split(project.build_file)[1]
3129 sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
3133 props_files_of_rules = set()
3134 targets_files_of_rules = set()
3135 extension_to_rule_name = {}
3136 list_excluded = generator_flags.get('msvs_list_excluded_files', True)
3138 # Don't generate rules if we are using an external builder like ninja.
3139 if not spec.get('msvs_external_builder'):
3140 _GenerateRulesForMSBuild(project_dir, options, spec,
3141 sources, excluded_sources,
3142 props_files_of_rules, targets_files_of_rules,
3143 actions_to_add, extension_to_rule_name)
3145 rules = spec.get('rules', [])
3146 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
3148 sources, excluded_sources, excluded_idl = (
3149 _AdjustSourcesAndConvertToFilterHierarchy(spec, options,
3150 project_dir, sources,
3152 list_excluded, version))
3154 # Don't add actions if we are using an external builder like ninja.
3155 if not spec.get('msvs_external_builder'):
3156 _AddActions(actions_to_add, spec, project.build_file)
3157 _AddCopies(actions_to_add, spec)
3159 # NOTE: this stanza must appear after all actions have been decided.
3160 # Don't excluded sources with actions attached, or they won't run.
3161 excluded_sources = _FilterActionsFromExcluded(
3162 excluded_sources, actions_to_add)
3164 exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
3165 actions_spec, sources_handled_by_action = _GenerateActionsForMSBuild(
3166 spec, actions_to_add)
3168 _GenerateMSBuildFiltersFile(project.path + '.filters', sources,
3169 extension_to_rule_name)
3170 missing_sources = _VerifySourcesExist(sources, project_dir)
3172 for configuration in configurations.itervalues():
3173 _FinalizeMSBuildSettings(spec, configuration)
3175 # Add attributes to root element
3177 import_default_section = [
3178 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.Default.props'}]]
3179 import_cpp_props_section = [
3180 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.props'}]]
3181 import_cpp_targets_section = [
3182 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.targets'}]]
3183 macro_section = [['PropertyGroup', {'Label': 'UserMacros'}]]
3187 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003',
3188 'ToolsVersion': version.ProjectVersion(),
3189 'DefaultTargets': 'Build'
3192 content += _GetMSBuildProjectConfigurations(configurations)
3193 content += _GetMSBuildGlobalProperties(spec, project.guid, project_file_name)
3194 content += import_default_section
3195 content += _GetMSBuildConfigurationDetails(spec, project.build_file)
3196 content += _GetMSBuildLocalProperties(project.msbuild_toolset)
3197 content += import_cpp_props_section
3198 content += _GetMSBuildExtensions(props_files_of_rules)
3199 content += _GetMSBuildPropertySheets(configurations)
3200 content += macro_section
3201 content += _GetMSBuildConfigurationGlobalProperties(spec, configurations,
3203 content += _GetMSBuildToolSettingsSections(spec, configurations)
3204 content += _GetMSBuildSources(
3205 spec, sources, exclusions, extension_to_rule_name, actions_spec,
3206 sources_handled_by_action, list_excluded)
3207 content += _GetMSBuildProjectReferences(project)
3208 content += import_cpp_targets_section
3209 content += _GetMSBuildExtensionTargets(targets_files_of_rules)
3211 if spec.get('msvs_external_builder'):
3212 content += _GetMSBuildExternalBuilderTargets(spec)
3214 # TODO(jeanluc) File a bug to get rid of runas. We had in MSVS:
3215 # has_run_as = _WriteMSVSUserFile(project.path, version, spec)
3217 easy_xml.WriteXmlIfChanged(content, project.path, pretty=True, win32=True)
3219 return missing_sources
3222 def _GetMSBuildExternalBuilderTargets(spec):
3223 """Return a list of MSBuild targets for external builders.
3225 Right now, only "Build" and "Clean" targets are generated.
3228 spec: The gyp target spec.
3230 List of MSBuild 'Target' specs.
3232 build_cmd = _BuildCommandLineForRuleRaw(
3233 spec, spec['msvs_external_builder_build_cmd'],
3234 False, False, False, False)
3235 build_target = ['Target', {'Name': 'Build'}]
3236 build_target.append(['Exec', {'Command': build_cmd}])
3238 clean_cmd = _BuildCommandLineForRuleRaw(
3239 spec, spec['msvs_external_builder_clean_cmd'],
3240 False, False, False, False)
3241 clean_target = ['Target', {'Name': 'Clean'}]
3242 clean_target.append(['Exec', {'Command': clean_cmd}])
3244 return [build_target, clean_target]
3247 def _GetMSBuildExtensions(props_files_of_rules):
3248 extensions = ['ImportGroup', {'Label': 'ExtensionSettings'}]
3249 for props_file in props_files_of_rules:
3250 extensions.append(['Import', {'Project': props_file}])
3254 def _GetMSBuildExtensionTargets(targets_files_of_rules):
3255 targets_node = ['ImportGroup', {'Label': 'ExtensionTargets'}]
3256 for targets_file in sorted(targets_files_of_rules):
3257 targets_node.append(['Import', {'Project': targets_file}])
3258 return [targets_node]
3261 def _GenerateActionsForMSBuild(spec, actions_to_add):
3262 """Add actions accumulated into an actions_to_add, merging as needed.
3265 spec: the target project dict
3266 actions_to_add: dictionary keyed on input name, which maps to a list of
3267 dicts describing the actions attached to that input file.
3270 A pair of (action specification, the sources handled by this action).
3272 sources_handled_by_action = OrderedSet()
3274 for primary_input, actions in actions_to_add.iteritems():
3275 inputs = OrderedSet()
3276 outputs = OrderedSet()
3279 for action in actions:
3280 inputs.update(OrderedSet(action['inputs']))
3281 outputs.update(OrderedSet(action['outputs']))
3282 descriptions.append(action['description'])
3283 cmd = action['command']
3284 # For most actions, add 'call' so that actions that invoke batch files
3285 # return and continue executing. msbuild_use_call provides a way to
3286 # disable this but I have not seen any adverse effect from doing that
3288 if action.get('msbuild_use_call', True):
3290 commands.append(cmd)
3291 # Add the custom build action for one input file.
3292 description = ', and also '.join(descriptions)
3294 # We can't join the commands simply with && because the command line will
3295 # get too long. See also _AddActions: cygwin's setup_env mustn't be called
3296 # for every invocation or the command that sets the PATH will grow too
3299 '\r\nif %errorlevel% neq 0 exit /b %errorlevel%\r\n'.join(commands))
3300 _AddMSBuildAction(spec,
3306 sources_handled_by_action,
3308 return actions_spec, sources_handled_by_action
3311 def _AddMSBuildAction(spec, primary_input, inputs, outputs, cmd, description,
3312 sources_handled_by_action, actions_spec):
3313 command = MSVSSettings.ConvertVCMacrosToMSBuild(cmd)
3314 primary_input = _FixPath(primary_input)
3315 inputs_array = _FixPaths(inputs)
3316 outputs_array = _FixPaths(outputs)
3317 additional_inputs = ';'.join([i for i in inputs_array
3318 if i != primary_input])
3319 outputs = ';'.join(outputs_array)
3320 sources_handled_by_action.add(primary_input)
3321 action_spec = ['CustomBuild', {'Include': primary_input}]
3323 # TODO(jeanluc) 'Document' for all or just if as_sources?
3324 [['FileType', 'Document'],
3325 ['Command', command],
3326 ['Message', description],
3327 ['Outputs', outputs]
3329 if additional_inputs:
3330 action_spec.append(['AdditionalInputs', additional_inputs])
3331 actions_spec.append(action_spec)