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.
14 import gyp.easy_xml as easy_xml
15 import gyp.MSVSNew as MSVSNew
16 import gyp.MSVSProject as MSVSProject
17 import gyp.MSVSSettings as MSVSSettings
18 import gyp.MSVSToolFile as MSVSToolFile
19 import gyp.MSVSUserFile as MSVSUserFile
20 import gyp.MSVSUtil as MSVSUtil
21 import gyp.MSVSVersion as MSVSVersion
22 from gyp.common import GypError
25 # Regular expression for validating Visual Studio GUIDs. If the GUID
26 # contains lowercase hex letters, MSVS will be fine. However,
27 # IncrediBuild BuildConsole will parse the solution file, but then
28 # silently skip building the target causing hard to track down errors.
29 # Note that this only happens with the BuildConsole, and does not occur
30 # if IncrediBuild is executed from inside Visual Studio. This regex
31 # validates that the string looks like a GUID with all uppercase hex
33 VALID_MSVS_GUID_CHARS = re.compile('^[A-F0-9\-]+$')
36 generator_default_variables = {
37 'EXECUTABLE_PREFIX': '',
38 'EXECUTABLE_SUFFIX': '.exe',
39 'STATIC_LIB_PREFIX': '',
40 'SHARED_LIB_PREFIX': '',
41 'STATIC_LIB_SUFFIX': '.lib',
42 'SHARED_LIB_SUFFIX': '.dll',
43 'INTERMEDIATE_DIR': '$(IntDir)',
44 'SHARED_INTERMEDIATE_DIR': '$(OutDir)obj/global_intermediate',
46 'PRODUCT_DIR': '$(OutDir)',
47 'LIB_DIR': '$(OutDir)lib',
48 'RULE_INPUT_ROOT': '$(InputName)',
49 'RULE_INPUT_DIRNAME': '$(InputDir)',
50 'RULE_INPUT_EXT': '$(InputExt)',
51 'RULE_INPUT_NAME': '$(InputFileName)',
52 'RULE_INPUT_PATH': '$(InputPath)',
53 'CONFIGURATION_NAME': '$(ConfigurationName)',
57 # The msvs specific sections that hold paths
58 generator_additional_path_sections = [
64 generator_additional_non_configuration_keys = [
69 'msvs_external_builder',
70 'msvs_external_builder_out_dir',
71 'msvs_external_builder_build_cmd',
72 'msvs_external_builder_clean_cmd',
76 # List of precompiled header related keys.
78 'msvs_precompiled_header',
79 'msvs_precompiled_source',
83 cached_username = None
89 # TODO(gspencer): Switch the os.environ calls to be
90 # win32api.GetDomainName() and win32api.GetUserName() once the
91 # python version in depot_tools has been updated to work on Vista
93 def _GetDomainAndUserName():
94 if sys.platform not in ('win32', 'cygwin'):
95 return ('DOMAIN', 'USERNAME')
96 global cached_username
98 if not cached_domain or not cached_username:
99 domain = os.environ.get('USERDOMAIN')
100 username = os.environ.get('USERNAME')
101 if not domain or not username:
102 call = subprocess.Popen(['net', 'config', 'Workstation'],
103 stdout=subprocess.PIPE)
104 config = call.communicate()[0]
105 username_re = re.compile('^User name\s+(\S+)', re.MULTILINE)
106 username_match = username_re.search(config)
108 username = username_match.group(1)
109 domain_re = re.compile('^Logon domain\s+(\S+)', re.MULTILINE)
110 domain_match = domain_re.search(config)
112 domain = domain_match.group(1)
113 cached_domain = domain
114 cached_username = username
115 return (cached_domain, cached_username)
117 fixpath_prefix = None
120 def _NormalizedSource(source):
121 """Normalize the path.
123 But not if that gets rid of a variable, as this may expand to something
124 larger than one directory.
127 source: The path to be normalize.d
132 normalized = os.path.normpath(source)
133 if source.count('$') == normalized.count('$'):
139 """Convert paths to a form that will make sense in a vcproj file.
142 path: The path to convert, may contain / etc.
144 The path with all slashes made into backslashes.
146 if fixpath_prefix and path and not os.path.isabs(path) and not path[0] == '$':
147 path = os.path.join(fixpath_prefix, path)
148 path = path.replace('/', '\\')
149 path = _NormalizedSource(path)
150 if path and path[-1] == '\\':
155 def _FixPaths(paths):
156 """Fix each of the paths of the list."""
157 return [_FixPath(i) for i in paths]
160 def _ConvertSourcesToFilterHierarchy(sources, prefix=None, excluded=None,
162 """Converts a list split source file paths into a vcproj folder hierarchy.
165 sources: A list of source file paths split.
166 prefix: A list of source file path layers meant to apply to each of sources.
167 excluded: A set of excluded files.
170 A hierarchy of filenames and MSVSProject.Filter objects that matches the
171 layout of the source tree.
173 _ConvertSourcesToFilterHierarchy([['a', 'bob1.c'], ['b', 'bob2.c']],
176 [MSVSProject.Filter('a', contents=['joe\\a\\bob1.c']),
177 MSVSProject.Filter('b', contents=['joe\\b\\bob2.c'])]
179 if not prefix: prefix = []
183 # Gather files into the final result, excluded, or folders.
186 filename = _NormalizedSource('\\'.join(prefix + s))
187 if filename in excluded:
188 excluded_result.append(filename)
190 result.append(filename)
192 if not folders.get(s[0]):
194 folders[s[0]].append(s[1:])
195 # Add a folder for excluded files.
196 if excluded_result and list_excluded:
197 excluded_folder = MSVSProject.Filter('_excluded_files',
198 contents=excluded_result)
199 result.append(excluded_folder)
200 # Populate all the folders.
202 contents = _ConvertSourcesToFilterHierarchy(folders[f], prefix=prefix + [f],
204 list_excluded=list_excluded)
205 contents = MSVSProject.Filter(f, contents=contents)
206 result.append(contents)
211 def _ToolAppend(tools, tool_name, setting, value, only_if_unset=False):
213 _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset)
216 def _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset=False):
217 # TODO(bradnelson): ugly hack, fix this more generally!!!
218 if 'Directories' in setting or 'Dependencies' in setting:
219 if type(value) == str:
220 value = value.replace('/', '\\')
222 value = [i.replace('/', '\\') for i in value]
223 if not tools.get(tool_name):
224 tools[tool_name] = dict()
225 tool = tools[tool_name]
226 if tool.get(setting):
227 if only_if_unset: return
228 if type(tool[setting]) == list and type(value) == list:
229 tool[setting] += value
232 'Appending "%s" to a non-list setting "%s" for tool "%s" is '
233 'not allowed, previous value: %s' % (
234 value, setting, tool_name, str(tool[setting])))
236 tool[setting] = value
239 def _ConfigPlatform(config_data):
240 return config_data.get('msvs_configuration_platform', 'Win32')
243 def _ConfigBaseName(config_name, platform_name):
244 if config_name.endswith('_' + platform_name):
245 return config_name[0:-len(platform_name) - 1]
250 def _ConfigFullName(config_name, config_data):
251 platform_name = _ConfigPlatform(config_data)
252 return '%s|%s' % (_ConfigBaseName(config_name, platform_name), platform_name)
255 def _BuildCommandLineForRuleRaw(spec, cmd, cygwin_shell, has_input_path,
256 quote_cmd, do_setup_env):
258 if [x for x in cmd if '$(InputDir)' in x]:
259 input_dir_preamble = (
260 'set INPUTDIR=$(InputDir)\n'
261 'set INPUTDIR=%INPUTDIR:$(ProjectDir)=%\n'
262 'set INPUTDIR=%INPUTDIR:~0,-1%\n'
265 input_dir_preamble = ''
268 # Find path to cygwin.
269 cygwin_dir = _FixPath(spec.get('msvs_cygwin_dirs', ['.'])[0])
272 direct_cmd = [i.replace('$(IntDir)',
273 '`cygpath -m "${INTDIR}"`') for i in direct_cmd]
274 direct_cmd = [i.replace('$(OutDir)',
275 '`cygpath -m "${OUTDIR}"`') for i in direct_cmd]
276 direct_cmd = [i.replace('$(InputDir)',
277 '`cygpath -m "${INPUTDIR}"`') for i in direct_cmd]
279 direct_cmd = [i.replace('$(InputPath)',
280 '`cygpath -m "${INPUTPATH}"`')
282 direct_cmd = ['\\"%s\\"' % i.replace('"', '\\\\\\"') for i in direct_cmd]
283 # direct_cmd = gyp.common.EncodePOSIXShellList(direct_cmd)
284 direct_cmd = ' '.join(direct_cmd)
285 # TODO(quote): regularize quoting path names throughout the module
288 cmd += 'call "$(ProjectDir)%(cygwin_dir)s\\setup_env.bat" && '
289 cmd += 'set CYGWIN=nontsec&& '
290 if direct_cmd.find('NUMBER_OF_PROCESSORS') >= 0:
291 cmd += 'set /a NUMBER_OF_PROCESSORS_PLUS_1=%%NUMBER_OF_PROCESSORS%%+1&& '
292 if direct_cmd.find('INTDIR') >= 0:
293 cmd += 'set INTDIR=$(IntDir)&& '
294 if direct_cmd.find('OUTDIR') >= 0:
295 cmd += 'set OUTDIR=$(OutDir)&& '
296 if has_input_path and direct_cmd.find('INPUTPATH') >= 0:
297 cmd += 'set INPUTPATH=$(InputPath) && '
298 cmd += 'bash -c "%(cmd)s"'
299 cmd = cmd % {'cygwin_dir': cygwin_dir,
301 return input_dir_preamble + cmd
303 # Convert cat --> type to mimic unix.
307 command = [cmd[0].replace('/', '\\')]
308 # Add call before command to ensure that commands can be tied together one
309 # after the other without aborting in Incredibuild, since IB makes a bat
310 # file out of the raw command string, and some commands (like python) are
311 # actually batch files themselves.
312 command.insert(0, 'call')
314 # TODO(quote): This is a really ugly heuristic, and will miss path fixing
315 # for arguments like "--arg=path" or "/opt:path".
316 # If the argument starts with a slash or dash, it's probably a command line
318 arguments = [i if (i[:1] in "/-") else _FixPath(i) for i in cmd[1:]]
319 arguments = [i.replace('$(InputDir)', '%INPUTDIR%') for i in arguments]
320 arguments = [MSVSSettings.FixVCMacroSlashes(i) for i in arguments]
322 # Support a mode for using cmd directly.
323 # Convert any paths to native form (first element is used directly).
324 # TODO(quote): regularize quoting path names throughout the module
325 arguments = ['"%s"' % i for i in arguments]
326 # Collapse into a single command.
327 return input_dir_preamble + ' '.join(command + arguments)
330 def _BuildCommandLineForRule(spec, rule, has_input_path, do_setup_env):
331 # Currently this weird argument munging is used to duplicate the way a
332 # python script would need to be run as part of the chrome tree.
333 # Eventually we should add some sort of rule_default option to set this
334 # per project. For now the behavior chrome needs is the default.
335 mcs = rule.get('msvs_cygwin_shell')
337 mcs = int(spec.get('msvs_cygwin_shell', 1))
338 elif isinstance(mcs, str):
340 quote_cmd = int(rule.get('msvs_quote_cmd', 1))
341 return _BuildCommandLineForRuleRaw(spec, rule['action'], mcs, has_input_path,
342 quote_cmd, do_setup_env=do_setup_env)
345 def _AddActionStep(actions_dict, inputs, outputs, description, command):
346 """Merge action into an existing list of actions.
348 Care must be taken so that actions which have overlapping inputs either don't
349 get assigned to the same input, or get collapsed into one.
352 actions_dict: dictionary keyed on input name, which maps to a list of
353 dicts describing the actions attached to that input file.
354 inputs: list of inputs
355 outputs: list of outputs
356 description: description of the action
357 command: command line to execute
359 # Require there to be at least one input (call sites will ensure this).
365 'description': description,
369 # Pick where to stick this action.
370 # While less than optimal in terms of build time, attach them to the first
372 chosen_input = inputs[0]
375 if chosen_input not in actions_dict:
376 actions_dict[chosen_input] = []
377 actions_dict[chosen_input].append(action)
380 def _AddCustomBuildToolForMSVS(p, spec, primary_input,
381 inputs, outputs, description, cmd):
382 """Add a custom build tool to execute something.
385 p: the target project
386 spec: the target project dict
387 primary_input: input file to attach the build tool to
388 inputs: list of inputs
389 outputs: list of outputs
390 description: description of the action
391 cmd: command line to execute
393 inputs = _FixPaths(inputs)
394 outputs = _FixPaths(outputs)
395 tool = MSVSProject.Tool(
397 {'Description': description,
398 'AdditionalDependencies': ';'.join(inputs),
399 'Outputs': ';'.join(outputs),
402 # Add to the properties of primary input for each config.
403 for config_name, c_data in spec['configurations'].iteritems():
404 p.AddFileConfig(_FixPath(primary_input),
405 _ConfigFullName(config_name, c_data), tools=[tool])
408 def _AddAccumulatedActionsToMSVS(p, spec, actions_dict):
409 """Add actions accumulated into an actions_dict, merging as needed.
412 p: the target project
413 spec: the target project dict
414 actions_dict: dictionary keyed on input name, which maps to a list of
415 dicts describing the actions attached to that input file.
417 for primary_input in actions_dict:
422 for action in actions_dict[primary_input]:
423 inputs.update(set(action['inputs']))
424 outputs.update(set(action['outputs']))
425 descriptions.append(action['description'])
426 commands.append(action['command'])
427 # Add the custom build step for one input file.
428 description = ', and also '.join(descriptions)
429 command = '\r\n'.join(commands)
430 _AddCustomBuildToolForMSVS(p, spec,
431 primary_input=primary_input,
434 description=description,
438 def _RuleExpandPath(path, input_file):
439 """Given the input file to which a rule applied, string substitute a path.
442 path: a path to string expand
443 input_file: the file to which the rule applied.
445 The string substituted path.
447 path = path.replace('$(InputName)',
448 os.path.splitext(os.path.split(input_file)[1])[0])
449 path = path.replace('$(InputDir)', os.path.dirname(input_file))
450 path = path.replace('$(InputExt)',
451 os.path.splitext(os.path.split(input_file)[1])[1])
452 path = path.replace('$(InputFileName)', os.path.split(input_file)[1])
453 path = path.replace('$(InputPath)', input_file)
457 def _FindRuleTriggerFiles(rule, sources):
458 """Find the list of files which a particular rule applies to.
461 rule: the rule in question
462 sources: the set of all known source files for this project
464 The list of sources that trigger a particular rule.
466 return rule.get('rule_sources', [])
469 def _RuleInputsAndOutputs(rule, trigger_file):
470 """Find the inputs and outputs generated by a rule.
473 rule: the rule in question.
474 trigger_file: the main trigger for this rule.
476 The pair of (inputs, outputs) involved in this rule.
478 raw_inputs = _FixPaths(rule.get('inputs', []))
479 raw_outputs = _FixPaths(rule.get('outputs', []))
482 inputs.add(trigger_file)
484 inputs.add(_RuleExpandPath(i, trigger_file))
485 for o in raw_outputs:
486 outputs.add(_RuleExpandPath(o, trigger_file))
487 return (inputs, outputs)
490 def _GenerateNativeRulesForMSVS(p, rules, output_dir, spec, options):
491 """Generate a native rules file.
494 p: the target project
495 rules: the set of rules to include
496 output_dir: the directory in which the project/gyp resides
497 spec: the project dict
498 options: global generator options
500 rules_filename = '%s%s.rules' % (spec['target_name'],
502 rules_file = MSVSToolFile.Writer(os.path.join(output_dir, rules_filename),
506 rule_name = r['rule_name']
507 rule_ext = r['extension']
508 inputs = _FixPaths(r.get('inputs', []))
509 outputs = _FixPaths(r.get('outputs', []))
510 # Skip a rule with no action and no inputs.
511 if 'action' not in r and not r.get('rule_sources', []):
513 cmd = _BuildCommandLineForRule(spec, r, has_input_path=True,
515 rules_file.AddCustomBuildRule(name=rule_name,
516 description=r.get('message', rule_name),
517 extensions=[rule_ext],
518 additional_dependencies=inputs,
521 # Write out rules file.
522 rules_file.WriteIfChanged()
524 # Add rules file to project.
525 p.AddToolFile(rules_filename)
528 def _Cygwinify(path):
529 path = path.replace('$(OutDir)', '$(OutDirCygwin)')
530 path = path.replace('$(IntDir)', '$(IntDirCygwin)')
534 def _GenerateExternalRules(rules, output_dir, spec,
535 sources, options, actions_to_add):
536 """Generate an external makefile to do a set of rules.
539 rules: the list of rules to include
540 output_dir: path containing project and gyp files
541 spec: project specification data
542 sources: set of sources known
543 options: global generator options
544 actions_to_add: The list of actions we will add to.
546 filename = '%s_rules%s.mk' % (spec['target_name'], options.suffix)
547 mk_file = gyp.common.WriteOnDiff(os.path.join(output_dir, filename))
548 # Find cygwin style versions of some paths.
549 mk_file.write('OutDirCygwin:=$(shell cygpath -u "$(OutDir)")\n')
550 mk_file.write('IntDirCygwin:=$(shell cygpath -u "$(IntDir)")\n')
551 # Gather stuff needed to emit all: target.
554 all_output_dirs = set()
557 trigger_files = _FindRuleTriggerFiles(rule, sources)
558 for tf in trigger_files:
559 inputs, outputs = _RuleInputsAndOutputs(rule, tf)
560 all_inputs.update(set(inputs))
561 all_outputs.update(set(outputs))
562 # Only use one target from each rule as the dependency for
563 # 'all' so we don't try to build each rule multiple times.
564 first_outputs.append(list(outputs)[0])
565 # Get the unique output directories for this rule.
566 output_dirs = [os.path.split(i)[0] for i in outputs]
567 for od in output_dirs:
568 all_output_dirs.add(od)
569 first_outputs_cyg = [_Cygwinify(i) for i in first_outputs]
570 # Write out all: target, including mkdir for each output directory.
571 mk_file.write('all: %s\n' % ' '.join(first_outputs_cyg))
572 for od in all_output_dirs:
574 mk_file.write('\tmkdir -p `cygpath -u "%s"`\n' % od)
576 # Define how each output is generated.
578 trigger_files = _FindRuleTriggerFiles(rule, sources)
579 for tf in trigger_files:
580 # Get all the inputs and outputs for this rule for this trigger file.
581 inputs, outputs = _RuleInputsAndOutputs(rule, tf)
582 inputs = [_Cygwinify(i) for i in inputs]
583 outputs = [_Cygwinify(i) for i in outputs]
584 # Prepare the command line for this rule.
585 cmd = [_RuleExpandPath(c, tf) for c in rule['action']]
586 cmd = ['"%s"' % i for i in cmd]
588 # Add it to the makefile.
589 mk_file.write('%s: %s\n' % (' '.join(outputs), ' '.join(inputs)))
590 mk_file.write('\t%s\n\n' % cmd)
594 # Add makefile to list of sources.
595 sources.add(filename)
596 # Add a build action to call makefile.
600 '-j', '${NUMBER_OF_PROCESSORS_PLUS_1}',
602 cmd = _BuildCommandLineForRuleRaw(spec, cmd, True, False, True, True)
603 # Insert makefile as 0'th input, so it gets the action attached there,
604 # as this is easier to understand from in the IDE.
605 all_inputs = list(all_inputs)
606 all_inputs.insert(0, filename)
607 _AddActionStep(actions_to_add,
608 inputs=_FixPaths(all_inputs),
609 outputs=_FixPaths(all_outputs),
610 description='Running external rules for %s' %
615 def _EscapeEnvironmentVariableExpansion(s):
616 """Escapes % characters.
618 Escapes any % characters so that Windows-style environment variable
619 expansions will leave them alone.
620 See http://connect.microsoft.com/VisualStudio/feedback/details/106127/cl-d-name-text-containing-percentage-characters-doesnt-compile
621 to understand why we have to do this.
624 s: The string to be escaped.
629 s = s.replace('%', '%%')
633 quote_replacer_regex = re.compile(r'(\\*)"')
636 def _EscapeCommandLineArgumentForMSVS(s):
637 """Escapes a Windows command-line argument.
639 So that the Win32 CommandLineToArgv function will turn the escaped result back
640 into the original string.
641 See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
642 ("Parsing C++ Command-Line Arguments") to understand why we have to do
646 s: the string to be escaped.
652 # For a literal quote, CommandLineToArgv requires an odd number of
653 # backslashes preceding it, and it produces half as many literal backslashes
654 # (rounded down). So we need to produce 2n+1 backslashes.
655 return 2 * match.group(1) + '\\"'
657 # Escape all quotes so that they are interpreted literally.
658 s = quote_replacer_regex.sub(_Replace, s)
659 # Now add unescaped quotes so that any whitespace is interpreted literally.
664 delimiters_replacer_regex = re.compile(r'(\\*)([,;]+)')
667 def _EscapeVCProjCommandLineArgListItem(s):
668 """Escapes command line arguments for MSVS.
670 The VCProj format stores string lists in a single string using commas and
671 semi-colons as separators, which must be quoted if they are to be
672 interpreted literally. However, command-line arguments may already have
673 quotes, and the VCProj parser is ignorant of the backslash escaping
674 convention used by CommandLineToArgv, so the command-line quotes and the
675 VCProj quotes may not be the same quotes. So to store a general
676 command-line argument in a VCProj list, we need to parse the existing
677 quoting according to VCProj's convention and quote any delimiters that are
678 not already quoted by that convention. The quotes that we add will also be
679 seen by CommandLineToArgv, so if backslashes precede them then we also have
680 to escape those backslashes according to the CommandLineToArgv
684 s: the string to be escaped.
690 # For a non-literal quote, CommandLineToArgv requires an even number of
691 # backslashes preceding it, and it produces half as many literal
692 # backslashes. So we need to produce 2n backslashes.
693 return 2 * match.group(1) + '"' + match.group(2) + '"'
695 segments = s.split('"')
696 # The unquoted segments are at the even-numbered indices.
697 for i in range(0, len(segments), 2):
698 segments[i] = delimiters_replacer_regex.sub(_Replace, segments[i])
699 # Concatenate back into a single string
700 s = '"'.join(segments)
701 if len(segments) % 2 == 0:
702 # String ends while still quoted according to VCProj's convention. This
703 # means the delimiter and the next list item that follow this one in the
704 # .vcproj file will be misinterpreted as part of this item. There is nothing
705 # we can do about this. Adding an extra quote would correct the problem in
706 # the VCProj but cause the same problem on the final command-line. Moving
707 # the item to the end of the list does works, but that's only possible if
708 # there's only one such item. Let's just warn the user.
709 print >> sys.stderr, ('Warning: MSVS may misinterpret the odd number of ' +
714 def _EscapeCppDefineForMSVS(s):
715 """Escapes a CPP define so that it will reach the compiler unaltered."""
716 s = _EscapeEnvironmentVariableExpansion(s)
717 s = _EscapeCommandLineArgumentForMSVS(s)
718 s = _EscapeVCProjCommandLineArgListItem(s)
719 # cl.exe replaces literal # characters with = in preprocesor definitions for
720 # some reason. Octal-encode to work around that.
721 s = s.replace('#', '\\%03o' % ord('#'))
725 quote_replacer_regex2 = re.compile(r'(\\+)"')
728 def _EscapeCommandLineArgumentForMSBuild(s):
729 """Escapes a Windows command-line argument for use by MSBuild."""
732 return (len(match.group(1)) / 2 * 4) * '\\' + '\\"'
734 # Escape all quotes so that they are interpreted literally.
735 s = quote_replacer_regex2.sub(_Replace, s)
739 def _EscapeMSBuildSpecialCharacters(s):
740 escape_dictionary = {
749 result = ''.join([escape_dictionary.get(c, c) for c in s])
753 def _EscapeCppDefineForMSBuild(s):
754 """Escapes a CPP define so that it will reach the compiler unaltered."""
755 s = _EscapeEnvironmentVariableExpansion(s)
756 s = _EscapeCommandLineArgumentForMSBuild(s)
757 s = _EscapeMSBuildSpecialCharacters(s)
758 # cl.exe replaces literal # characters with = in preprocesor definitions for
759 # some reason. Octal-encode to work around that.
760 s = s.replace('#', '\\%03o' % ord('#'))
764 def _GenerateRulesForMSVS(p, output_dir, options, spec,
765 sources, excluded_sources,
767 """Generate all the rules for a particular project.
771 output_dir: directory to emit rules to
772 options: global options passed to the generator
773 spec: the specification for this project
774 sources: the set of all known source files in this project
775 excluded_sources: the set of sources excluded from normal processing
776 actions_to_add: deferred list of actions to add in
778 rules = spec.get('rules', [])
779 rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
780 rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
782 # Handle rules that use a native rules file.
784 _GenerateNativeRulesForMSVS(p, rules_native, output_dir, spec, options)
786 # Handle external rules (non-native rules).
788 _GenerateExternalRules(rules_external, output_dir, spec,
789 sources, options, actions_to_add)
790 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
793 def _AdjustSourcesForRules(spec, rules, sources, excluded_sources):
794 # Add outputs generated by each rule (if applicable).
796 # Done if not processing outputs as sources.
797 if int(rule.get('process_outputs_as_sources', False)):
798 # Add in the outputs from this rule.
799 trigger_files = _FindRuleTriggerFiles(rule, sources)
800 for trigger_file in trigger_files:
801 inputs, outputs = _RuleInputsAndOutputs(rule, trigger_file)
802 inputs = set(_FixPaths(inputs))
803 outputs = set(_FixPaths(outputs))
804 inputs.remove(_FixPath(trigger_file))
805 sources.update(inputs)
806 if not spec.get('msvs_external_builder'):
807 excluded_sources.update(inputs)
808 sources.update(outputs)
811 def _FilterActionsFromExcluded(excluded_sources, actions_to_add):
812 """Take inputs with actions attached out of the list of exclusions.
815 excluded_sources: list of source files not to be built.
816 actions_to_add: dict of actions keyed on source file they're attached to.
818 excluded_sources with files that have actions attached removed.
820 must_keep = set(_FixPaths(actions_to_add.keys()))
821 return [s for s in excluded_sources if s not in must_keep]
824 def _GetDefaultConfiguration(spec):
825 return spec['configurations'][spec['default_configuration']]
828 def _GetGuidOfProject(proj_path, spec):
829 """Get the guid for the project.
832 proj_path: Path of the vcproj or vcxproj file to generate.
833 spec: The target dictionary containing the properties of the target.
837 ValueError: if the specified GUID is invalid.
839 # Pluck out the default configuration.
840 default_config = _GetDefaultConfiguration(spec)
841 # Decide the guid of the project.
842 guid = default_config.get('msvs_guid')
844 if VALID_MSVS_GUID_CHARS.match(guid) is None:
845 raise ValueError('Invalid MSVS guid: "%s". Must match regex: "%s".' %
846 (guid, VALID_MSVS_GUID_CHARS.pattern))
848 guid = guid or MSVSNew.MakeGuid(proj_path)
852 def _GetMsbuildToolsetOfProject(proj_path, spec, version):
853 """Get the platform toolset for the project.
856 proj_path: Path of the vcproj or vcxproj file to generate.
857 spec: The target dictionary containing the properties of the target.
858 version: The MSVSVersion object.
860 the platform toolset string or None.
862 # Pluck out the default configuration.
863 default_config = _GetDefaultConfiguration(spec)
864 toolset = default_config.get('msbuild_toolset')
865 if not toolset and version.DefaultToolset():
866 toolset = version.DefaultToolset()
870 def _GenerateProject(project, options, version, generator_flags):
871 """Generates a vcproj file.
874 project: the MSVSProject object.
875 options: global generator options.
876 version: the MSVSVersion object.
877 generator_flags: dict of generator-specific flags.
879 A list of source files that cannot be found on disk.
881 default_config = _GetDefaultConfiguration(project.spec)
883 # Skip emitting anything if told to with msvs_existing_vcproj option.
884 if default_config.get('msvs_existing_vcproj'):
887 if version.UsesVcxproj():
888 return _GenerateMSBuildProject(project, options, version, generator_flags)
890 return _GenerateMSVSProject(project, options, version, generator_flags)
893 def _GenerateMSVSProject(project, options, version, generator_flags):
894 """Generates a .vcproj file. It may create .rules and .user files too.
897 project: The project object we will generate the file for.
898 options: Global options passed to the generator.
899 version: The VisualStudioVersion object.
900 generator_flags: dict of generator-specific flags.
903 vcproj_dir = os.path.dirname(project.path)
904 if vcproj_dir and not os.path.exists(vcproj_dir):
905 os.makedirs(vcproj_dir)
907 platforms = _GetUniquePlatforms(spec)
908 p = MSVSProject.Writer(project.path, version, spec['target_name'],
909 project.guid, platforms)
911 # Get directory project file is in.
912 project_dir = os.path.split(project.path)[0]
913 gyp_path = _NormalizedSource(project.build_file)
914 relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
916 config_type = _GetMSVSConfigurationType(spec, project.build_file)
917 for config_name, config in spec['configurations'].iteritems():
918 _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config)
920 # Prepare list of sources and excluded sources.
921 gyp_file = os.path.split(project.build_file)[1]
922 sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
927 _GenerateRulesForMSVS(p, project_dir, options, spec,
928 sources, excluded_sources,
930 list_excluded = generator_flags.get('msvs_list_excluded_files', True)
931 sources, excluded_sources, excluded_idl = (
932 _AdjustSourcesAndConvertToFilterHierarchy(
933 spec, options, project_dir, sources, excluded_sources, list_excluded))
936 missing_sources = _VerifySourcesExist(sources, project_dir)
939 _AddToolFilesToMSVS(p, spec)
940 _HandlePreCompiledHeaders(p, sources, spec)
941 _AddActions(actions_to_add, spec, relative_path_of_gyp_file)
942 _AddCopies(actions_to_add, spec)
943 _WriteMSVSUserFile(project.path, version, spec)
945 # NOTE: this stanza must appear after all actions have been decided.
946 # Don't excluded sources with actions attached, or they won't run.
947 excluded_sources = _FilterActionsFromExcluded(
948 excluded_sources, actions_to_add)
949 _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
951 _AddAccumulatedActionsToMSVS(p, spec, actions_to_add)
956 return missing_sources
959 def _GetUniquePlatforms(spec):
960 """Returns the list of unique platforms for this spec, e.g ['win32', ...].
963 spec: The target dictionary containing the properties of the target.
965 The MSVSUserFile object created.
967 # Gather list of unique platforms.
969 for configuration in spec['configurations']:
970 platforms.add(_ConfigPlatform(spec['configurations'][configuration]))
971 platforms = list(platforms)
975 def _CreateMSVSUserFile(proj_path, version, spec):
976 """Generates a .user file for the user running this Gyp program.
979 proj_path: The path of the project file being created. The .user file
980 shares the same path (with an appropriate suffix).
981 version: The VisualStudioVersion object.
982 spec: The target dictionary containing the properties of the target.
984 The MSVSUserFile object created.
986 (domain, username) = _GetDomainAndUserName()
987 vcuser_filename = '.'.join([proj_path, domain, username, 'user'])
988 user_file = MSVSUserFile.Writer(vcuser_filename, version,
993 def _GetMSVSConfigurationType(spec, build_file):
994 """Returns the configuration type for this project.
996 It's a number defined by Microsoft. May raise an exception.
999 spec: The target dictionary containing the properties of the target.
1000 build_file: The path of the gyp file.
1002 An integer, the configuration type.
1006 'executable': '1', # .exe
1007 'shared_library': '2', # .dll
1008 'loadable_module': '2', # .dll
1009 'static_library': '4', # .lib
1010 'none': '10', # Utility type
1013 if spec.get('type'):
1014 raise GypError('Target type %s is not a valid target type for '
1015 'target %s in %s.' %
1016 (spec['type'], spec['target_name'], build_file))
1018 raise GypError('Missing type field for target %s in %s.' %
1019 (spec['target_name'], build_file))
1023 def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config):
1024 """Adds a configuration to the MSVS project.
1026 Many settings in a vcproj file are specific to a configuration. This
1027 function the main part of the vcproj file that's configuration specific.
1030 p: The target project being generated.
1031 spec: The target dictionary containing the properties of the target.
1032 config_type: The configuration type, a number as defined by Microsoft.
1033 config_name: The name of the configuration.
1034 config: The dictionary that defines the special processing to be done
1035 for this configuration.
1037 # Get the information for this configuration
1038 include_dirs, resource_include_dirs = _GetIncludeDirs(config)
1039 libraries = _GetLibraries(spec)
1040 library_dirs = _GetLibraryDirs(config)
1041 out_file, vc_tool, _ = _GetOutputFilePathAndTool(spec, msbuild=False)
1042 defines = _GetDefines(config)
1043 defines = [_EscapeCppDefineForMSVS(d) for d in defines]
1044 disabled_warnings = _GetDisabledWarnings(config)
1045 prebuild = config.get('msvs_prebuild')
1046 postbuild = config.get('msvs_postbuild')
1047 def_file = _GetModuleDefinition(spec)
1048 precompiled_header = config.get('msvs_precompiled_header')
1050 # Prepare the list of tools as a dictionary.
1052 # Add in user specified msvs_settings.
1053 msvs_settings = config.get('msvs_settings', {})
1054 MSVSSettings.ValidateMSVSSettings(msvs_settings)
1056 # Prevent default library inheritance from the environment.
1057 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', ['$(NOINHERIT)'])
1059 for tool in msvs_settings:
1060 settings = config['msvs_settings'][tool]
1061 for setting in settings:
1062 _ToolAppend(tools, tool, setting, settings[setting])
1063 # Add the information to the appropriate tool
1064 _ToolAppend(tools, 'VCCLCompilerTool',
1065 'AdditionalIncludeDirectories', include_dirs)
1066 _ToolAppend(tools, 'VCResourceCompilerTool',
1067 'AdditionalIncludeDirectories', resource_include_dirs)
1069 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', libraries)
1070 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalLibraryDirectories',
1073 _ToolAppend(tools, vc_tool, 'OutputFile', out_file, only_if_unset=True)
1075 _ToolAppend(tools, 'VCCLCompilerTool', 'PreprocessorDefinitions', defines)
1076 _ToolAppend(tools, 'VCResourceCompilerTool', 'PreprocessorDefinitions',
1078 # Change program database directory to prevent collisions.
1079 _ToolAppend(tools, 'VCCLCompilerTool', 'ProgramDataBaseFileName',
1080 '$(IntDir)$(ProjectName)\\vc80.pdb', only_if_unset=True)
1081 # Add disabled warnings.
1082 _ToolAppend(tools, 'VCCLCompilerTool',
1083 'DisableSpecificWarnings', disabled_warnings)
1085 _ToolAppend(tools, 'VCPreBuildEventTool', 'CommandLine', prebuild)
1087 _ToolAppend(tools, 'VCPostBuildEventTool', 'CommandLine', postbuild)
1088 # Turn on precompiled headers if appropriate.
1089 if precompiled_header:
1090 precompiled_header = os.path.split(precompiled_header)[1]
1091 _ToolAppend(tools, 'VCCLCompilerTool', 'UsePrecompiledHeader', '2')
1092 _ToolAppend(tools, 'VCCLCompilerTool',
1093 'PrecompiledHeaderThrough', precompiled_header)
1094 _ToolAppend(tools, 'VCCLCompilerTool',
1095 'ForcedIncludeFiles', precompiled_header)
1096 # Loadable modules don't generate import libraries;
1097 # tell dependent projects to not expect one.
1098 if spec['type'] == 'loadable_module':
1099 _ToolAppend(tools, 'VCLinkerTool', 'IgnoreImportLibrary', 'true')
1100 # Set the module definition file if any.
1102 _ToolAppend(tools, 'VCLinkerTool', 'ModuleDefinitionFile', def_file)
1104 _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name)
1107 def _GetIncludeDirs(config):
1108 """Returns the list of directories to be used for #include directives.
1111 config: The dictionary that defines the special processing to be done
1112 for this configuration.
1114 The list of directory paths.
1116 # TODO(bradnelson): include_dirs should really be flexible enough not to
1117 # require this sort of thing.
1119 config.get('include_dirs', []) +
1120 config.get('msvs_system_include_dirs', []))
1121 resource_include_dirs = config.get('resource_include_dirs', include_dirs)
1122 include_dirs = _FixPaths(include_dirs)
1123 resource_include_dirs = _FixPaths(resource_include_dirs)
1124 return include_dirs, resource_include_dirs
1127 def _GetLibraryDirs(config):
1128 """Returns the list of directories to be used for library search paths.
1131 config: The dictionary that defines the special processing to be done
1132 for this configuration.
1134 The list of directory paths.
1137 library_dirs = config.get('library_dirs', [])
1138 library_dirs = _FixPaths(library_dirs)
1142 def _GetLibraries(spec):
1143 """Returns the list of libraries for this configuration.
1146 spec: The target dictionary containing the properties of the target.
1148 The list of directory paths.
1150 libraries = spec.get('libraries', [])
1151 # Strip out -l, as it is not used on windows (but is needed so we can pass
1152 # in libraries that are assumed to be in the default library path).
1153 # Also remove duplicate entries, leaving only the last duplicate, while
1156 unique_libraries_list = []
1157 for entry in reversed(libraries):
1158 library = re.sub('^\-l', '', entry)
1159 if not os.path.splitext(library)[1]:
1161 if library not in found:
1163 unique_libraries_list.append(library)
1164 unique_libraries_list.reverse()
1165 return unique_libraries_list
1168 def _GetOutputFilePathAndTool(spec, msbuild):
1169 """Returns the path and tool to use for this target.
1171 Figures out the path of the file this spec will create and the name of
1172 the VC tool that will create it.
1175 spec: The target dictionary containing the properties of the target.
1177 A triple of (file path, name of the vc tool, name of the msbuild tool)
1179 # Select a name for the output file.
1184 'executable': ('VCLinkerTool', 'Link', '$(OutDir)', '.exe'),
1185 'shared_library': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
1186 'loadable_module': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
1187 'static_library': ('VCLibrarianTool', 'Lib', '$(OutDir)lib\\', '.lib'),
1189 output_file_props = output_file_map.get(spec['type'])
1190 if output_file_props and int(spec.get('msvs_auto_output_file', 1)):
1191 vc_tool, msbuild_tool, out_dir, suffix = output_file_props
1192 if spec.get('standalone_static_library', 0):
1193 out_dir = '$(OutDir)'
1194 out_dir = spec.get('product_dir', out_dir)
1195 product_extension = spec.get('product_extension')
1196 if product_extension:
1197 suffix = '.' + product_extension
1199 suffix = '$(TargetExt)'
1200 prefix = spec.get('product_prefix', '')
1201 product_name = spec.get('product_name', '$(ProjectName)')
1202 out_file = ntpath.join(out_dir, prefix + product_name + suffix)
1203 return out_file, vc_tool, msbuild_tool
1206 def _GetOutputTargetExt(spec):
1207 """Returns the extension for this target, including the dot
1209 If product_extension is specified, set target_extension to this to avoid
1210 MSB8012, returns None otherwise. Ignores any target_extension settings in
1214 spec: The target dictionary containing the properties of the target.
1216 A string with the extension, or None
1218 target_extension = spec.get('product_extension')
1219 if target_extension:
1220 return '.' + target_extension
1224 def _GetDefines(config):
1225 """Returns the list of preprocessor definitions for this configuation.
1228 config: The dictionary that defines the special processing to be done
1229 for this configuration.
1231 The list of preprocessor definitions.
1234 for d in config.get('defines', []):
1236 fd = '='.join([str(dpart) for dpart in d])
1243 def _GetDisabledWarnings(config):
1244 return [str(i) for i in config.get('msvs_disabled_warnings', [])]
1247 def _GetModuleDefinition(spec):
1249 if spec['type'] in ['shared_library', 'loadable_module', 'executable']:
1250 def_files = [s for s in spec.get('sources', []) if s.endswith('.def')]
1251 if len(def_files) == 1:
1252 def_file = _FixPath(def_files[0])
1255 'Multiple module definition files in one target, target %s lists '
1256 'multiple .def files: %s' % (
1257 spec['target_name'], ' '.join(def_files)))
1261 def _ConvertToolsToExpectedForm(tools):
1262 """Convert tools to a form expected by Visual Studio.
1265 tools: A dictionary of settings; the tool name is the key.
1267 A list of Tool objects.
1270 for tool, settings in tools.iteritems():
1271 # Collapse settings with lists.
1273 for setting, value in settings.iteritems():
1274 if type(value) == list:
1275 if ((tool == 'VCLinkerTool' and
1276 setting == 'AdditionalDependencies') or
1277 setting == 'AdditionalOptions'):
1278 settings_fixed[setting] = ' '.join(value)
1280 settings_fixed[setting] = ';'.join(value)
1282 settings_fixed[setting] = value
1284 tool_list.append(MSVSProject.Tool(tool, settings_fixed))
1288 def _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name):
1289 """Add to the project file the configuration specified by config.
1292 p: The target project being generated.
1293 spec: the target project dict.
1294 tools: A dictionary of settings; the tool name is the key.
1295 config: The dictionary that defines the special processing to be done
1296 for this configuration.
1297 config_type: The configuration type, a number as defined by Microsoft.
1298 config_name: The name of the configuration.
1300 attributes = _GetMSVSAttributes(spec, config, config_type)
1301 # Add in this configuration.
1302 tool_list = _ConvertToolsToExpectedForm(tools)
1303 p.AddConfig(_ConfigFullName(config_name, config),
1304 attrs=attributes, tools=tool_list)
1307 def _GetMSVSAttributes(spec, config, config_type):
1308 # Prepare configuration attributes.
1310 source_attrs = config.get('msvs_configuration_attributes', {})
1311 for a in source_attrs:
1312 prepared_attrs[a] = source_attrs[a]
1314 vsprops_dirs = config.get('msvs_props', [])
1315 vsprops_dirs = _FixPaths(vsprops_dirs)
1317 prepared_attrs['InheritedPropertySheets'] = ';'.join(vsprops_dirs)
1318 # Set configuration type.
1319 prepared_attrs['ConfigurationType'] = config_type
1320 output_dir = prepared_attrs.get('OutputDirectory',
1321 '$(SolutionDir)$(ConfigurationName)')
1322 prepared_attrs['OutputDirectory'] = _FixPath(output_dir) + '\\'
1323 if 'IntermediateDirectory' not in prepared_attrs:
1324 intermediate = '$(ConfigurationName)\\obj\\$(ProjectName)'
1325 prepared_attrs['IntermediateDirectory'] = _FixPath(intermediate) + '\\'
1327 intermediate = _FixPath(prepared_attrs['IntermediateDirectory']) + '\\'
1328 intermediate = MSVSSettings.FixVCMacroSlashes(intermediate)
1329 prepared_attrs['IntermediateDirectory'] = intermediate
1330 return prepared_attrs
1333 def _AddNormalizedSources(sources_set, sources_array):
1334 sources = [_NormalizedSource(s) for s in sources_array]
1335 sources_set.update(set(sources))
1338 def _PrepareListOfSources(spec, generator_flags, gyp_file):
1339 """Prepare list of sources and excluded sources.
1341 Besides the sources specified directly in the spec, adds the gyp file so
1342 that a change to it will cause a re-compile. Also adds appropriate sources
1343 for actions and copies. Assumes later stage will un-exclude files which
1344 have custom build steps attached.
1347 spec: The target dictionary containing the properties of the target.
1348 gyp_file: The name of the gyp file.
1350 A pair of (list of sources, list of excluded sources).
1351 The sources will be relative to the gyp file.
1354 _AddNormalizedSources(sources, spec.get('sources', []))
1355 excluded_sources = set()
1356 # Add in the gyp file.
1357 if not generator_flags.get('standalone'):
1358 sources.add(gyp_file)
1360 # Add in 'action' inputs and outputs.
1361 for a in spec.get('actions', []):
1362 inputs = a['inputs']
1363 inputs = [_NormalizedSource(i) for i in inputs]
1364 # Add all inputs to sources and excluded sources.
1365 inputs = set(inputs)
1366 sources.update(inputs)
1367 if not spec.get('msvs_external_builder'):
1368 excluded_sources.update(inputs)
1369 if int(a.get('process_outputs_as_sources', False)):
1370 _AddNormalizedSources(sources, a.get('outputs', []))
1371 # Add in 'copies' inputs and outputs.
1372 for cpy in spec.get('copies', []):
1373 _AddNormalizedSources(sources, cpy.get('files', []))
1374 return (sources, excluded_sources)
1377 def _AdjustSourcesAndConvertToFilterHierarchy(
1378 spec, options, gyp_dir, sources, excluded_sources, list_excluded):
1379 """Adjusts the list of sources and excluded sources.
1381 Also converts the sets to lists.
1384 spec: The target dictionary containing the properties of the target.
1385 options: Global generator options.
1386 gyp_dir: The path to the gyp file being processed.
1387 sources: A set of sources to be included for this project.
1388 excluded_sources: A set of sources to be excluded for this project.
1390 A trio of (list of sources, list of excluded sources,
1391 path of excluded IDL file)
1393 # Exclude excluded sources coming into the generator.
1394 excluded_sources.update(set(spec.get('sources_excluded', [])))
1395 # Add excluded sources into sources for good measure.
1396 sources.update(excluded_sources)
1397 # Convert to proper windows form.
1398 # NOTE: sources goes from being a set to a list here.
1399 # NOTE: excluded_sources goes from being a set to a list here.
1400 sources = _FixPaths(sources)
1401 # Convert to proper windows form.
1402 excluded_sources = _FixPaths(excluded_sources)
1404 excluded_idl = _IdlFilesHandledNonNatively(spec, sources)
1406 precompiled_related = _GetPrecompileRelatedFiles(spec)
1407 # Find the excluded ones, minus the precompiled header related ones.
1408 fully_excluded = [i for i in excluded_sources if i not in precompiled_related]
1410 # Convert to folders and the right slashes.
1411 sources = [i.split('\\') for i in sources]
1412 sources = _ConvertSourcesToFilterHierarchy(sources, excluded=fully_excluded,
1413 list_excluded=list_excluded)
1415 return sources, excluded_sources, excluded_idl
1418 def _IdlFilesHandledNonNatively(spec, sources):
1419 # If any non-native rules use 'idl' as an extension exclude idl files.
1420 # Gather a list here to use later.
1422 for rule in spec.get('rules', []):
1423 if rule['extension'] == 'idl' and int(rule.get('msvs_external_rule', 0)):
1427 excluded_idl = [i for i in sources if i.endswith('.idl')]
1433 def _GetPrecompileRelatedFiles(spec):
1434 # Gather a list of precompiled header related sources.
1435 precompiled_related = []
1436 for _, config in spec['configurations'].iteritems():
1437 for k in precomp_keys:
1440 precompiled_related.append(_FixPath(f))
1441 return precompiled_related
1444 def _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
1446 exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
1447 for file_name, excluded_configs in exclusions.iteritems():
1448 if (not list_excluded and
1449 len(excluded_configs) == len(spec['configurations'])):
1450 # If we're not listing excluded files, then they won't appear in the
1451 # project, so don't try to configure them to be excluded.
1454 for config_name, config in excluded_configs:
1455 p.AddFileConfig(file_name, _ConfigFullName(config_name, config),
1456 {'ExcludedFromBuild': 'true'})
1459 def _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl):
1461 # Exclude excluded sources from being built.
1462 for f in excluded_sources:
1463 excluded_configs = []
1464 for config_name, config in spec['configurations'].iteritems():
1465 precomped = [_FixPath(config.get(i, '')) for i in precomp_keys]
1466 # Don't do this for ones that are precompiled header related.
1467 if f not in precomped:
1468 excluded_configs.append((config_name, config))
1469 exclusions[f] = excluded_configs
1470 # If any non-native rules use 'idl' as an extension exclude idl files.
1472 for f in excluded_idl:
1473 excluded_configs = []
1474 for config_name, config in spec['configurations'].iteritems():
1475 excluded_configs.append((config_name, config))
1476 exclusions[f] = excluded_configs
1480 def _AddToolFilesToMSVS(p, spec):
1481 # Add in tool files (rules).
1483 for _, config in spec['configurations'].iteritems():
1484 for f in config.get('msvs_tool_files', []):
1486 for f in tool_files:
1490 def _HandlePreCompiledHeaders(p, sources, spec):
1491 # Pre-compiled header source stubs need a different compiler flag
1492 # (generate precompiled header) and any source file not of the same
1493 # kind (i.e. C vs. C++) as the precompiled header source stub needs
1494 # to have use of precompiled headers disabled.
1495 extensions_excluded_from_precompile = []
1496 for config_name, config in spec['configurations'].iteritems():
1497 source = config.get('msvs_precompiled_source')
1499 source = _FixPath(source)
1500 # UsePrecompiledHeader=1 for if using precompiled headers.
1501 tool = MSVSProject.Tool('VCCLCompilerTool',
1502 {'UsePrecompiledHeader': '1'})
1503 p.AddFileConfig(source, _ConfigFullName(config_name, config),
1505 basename, extension = os.path.splitext(source)
1506 if extension == '.c':
1507 extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
1509 extensions_excluded_from_precompile = ['.c']
1510 def DisableForSourceTree(source_tree):
1511 for source in source_tree:
1512 if isinstance(source, MSVSProject.Filter):
1513 DisableForSourceTree(source.contents)
1515 basename, extension = os.path.splitext(source)
1516 if extension in extensions_excluded_from_precompile:
1517 for config_name, config in spec['configurations'].iteritems():
1518 tool = MSVSProject.Tool('VCCLCompilerTool',
1519 {'UsePrecompiledHeader': '0',
1520 'ForcedIncludeFiles': '$(NOINHERIT)'})
1521 p.AddFileConfig(_FixPath(source),
1522 _ConfigFullName(config_name, config),
1524 # Do nothing if there was no precompiled source.
1525 if extensions_excluded_from_precompile:
1526 DisableForSourceTree(sources)
1529 def _AddActions(actions_to_add, spec, relative_path_of_gyp_file):
1531 actions = spec.get('actions', [])
1532 # Don't setup_env every time. When all the actions are run together in one
1533 # batch file in VS, the PATH will grow too long.
1534 # Membership in this set means that the cygwin environment has been set up,
1535 # and does not need to be set up again.
1536 have_setup_env = set()
1538 # Attach actions to the gyp file if nothing else is there.
1539 inputs = a.get('inputs') or [relative_path_of_gyp_file]
1540 attached_to = inputs[0]
1541 need_setup_env = attached_to not in have_setup_env
1542 cmd = _BuildCommandLineForRule(spec, a, has_input_path=False,
1543 do_setup_env=need_setup_env)
1544 have_setup_env.add(attached_to)
1546 _AddActionStep(actions_to_add,
1548 outputs=a.get('outputs', []),
1549 description=a.get('message', a['action_name']),
1553 def _WriteMSVSUserFile(project_path, version, spec):
1554 # Add run_as and test targets.
1555 if 'run_as' in spec:
1556 run_as = spec['run_as']
1557 action = run_as.get('action', [])
1558 environment = run_as.get('environment', [])
1559 working_directory = run_as.get('working_directory', '.')
1560 elif int(spec.get('test', 0)):
1561 action = ['$(TargetPath)', '--gtest_print_time']
1563 working_directory = '.'
1565 return # Nothing to add
1566 # Write out the user file.
1567 user_file = _CreateMSVSUserFile(project_path, version, spec)
1568 for config_name, c_data in spec['configurations'].iteritems():
1569 user_file.AddDebugSettings(_ConfigFullName(config_name, c_data),
1570 action, environment, working_directory)
1571 user_file.WriteIfChanged()
1574 def _AddCopies(actions_to_add, spec):
1575 copies = _GetCopies(spec)
1576 for inputs, outputs, cmd, description in copies:
1577 _AddActionStep(actions_to_add, inputs=inputs, outputs=outputs,
1578 description=description, command=cmd)
1581 def _GetCopies(spec):
1584 for cpy in spec.get('copies', []):
1585 for src in cpy.get('files', []):
1586 dst = os.path.join(cpy['destination'], os.path.basename(src))
1587 # _AddCustomBuildToolForMSVS() will call _FixPath() on the inputs and
1588 # outputs, so do the same for our generated command line.
1589 if src.endswith('/'):
1591 base_dir = posixpath.split(src_bare)[0]
1592 outer_dir = posixpath.split(src_bare)[1]
1593 cmd = 'cd "%s" && xcopy /e /f /y "%s" "%s\\%s\\"' % (
1594 _FixPath(base_dir), outer_dir, _FixPath(dst), outer_dir)
1595 copies.append(([src], ['dummy_copies', dst], cmd,
1596 'Copying %s to %s' % (src, dst)))
1598 cmd = 'mkdir "%s" 2>nul & set ERRORLEVEL=0 & copy /Y "%s" "%s"' % (
1599 _FixPath(cpy['destination']), _FixPath(src), _FixPath(dst))
1600 copies.append(([src], [dst], cmd, 'Copying %s to %s' % (src, dst)))
1604 def _GetPathDict(root, path):
1605 # |path| will eventually be empty (in the recursive calls) if it was initially
1606 # relative; otherwise it will eventually end up as '\', 'D:\', etc.
1607 if not path or path.endswith(os.sep):
1609 parent, folder = os.path.split(path)
1610 parent_dict = _GetPathDict(root, parent)
1611 if folder not in parent_dict:
1612 parent_dict[folder] = dict()
1613 return parent_dict[folder]
1616 def _DictsToFolders(base_path, bucket, flat):
1617 # Convert to folders recursively.
1619 for folder, contents in bucket.iteritems():
1620 if type(contents) == dict:
1621 folder_children = _DictsToFolders(os.path.join(base_path, folder),
1624 children += folder_children
1626 folder_children = MSVSNew.MSVSFolder(os.path.join(base_path, folder),
1627 name='(' + folder + ')',
1628 entries=folder_children)
1629 children.append(folder_children)
1631 children.append(contents)
1635 def _CollapseSingles(parent, node):
1636 # Recursively explorer the tree of dicts looking for projects which are
1637 # the sole item in a folder which has the same name as the project. Bring
1638 # such projects up one level.
1639 if (type(node) == dict and
1641 node.keys()[0] == parent + '.vcproj'):
1642 return node[node.keys()[0]]
1643 if type(node) != dict:
1646 node[child] = _CollapseSingles(child, node[child])
1650 def _GatherSolutionFolders(sln_projects, project_objects, flat):
1652 # Convert into a tree of dicts on path.
1653 for p in sln_projects:
1654 gyp_file, target = gyp.common.ParseQualifiedTarget(p)[0:2]
1655 gyp_dir = os.path.dirname(gyp_file)
1656 path_dict = _GetPathDict(root, gyp_dir)
1657 path_dict[target + '.vcproj'] = project_objects[p]
1658 # Walk down from the top until we hit a folder that has more than one entry.
1659 # In practice, this strips the top-level "src/" dir from the hierarchy in
1661 while len(root) == 1 and type(root[root.keys()[0]]) == dict:
1662 root = root[root.keys()[0]]
1664 root = _CollapseSingles('', root)
1665 # Merge buckets until everything is a root entry.
1666 return _DictsToFolders('', root, flat)
1669 def _GetPathOfProject(qualified_target, spec, options, msvs_version):
1670 default_config = _GetDefaultConfiguration(spec)
1671 proj_filename = default_config.get('msvs_existing_vcproj')
1672 if not proj_filename:
1673 proj_filename = (spec['target_name'] + options.suffix +
1674 msvs_version.ProjectExtension())
1676 build_file = gyp.common.BuildFile(qualified_target)
1677 proj_path = os.path.join(os.path.dirname(build_file), proj_filename)
1679 if options.generator_output:
1680 project_dir_path = os.path.dirname(os.path.abspath(proj_path))
1681 proj_path = os.path.join(options.generator_output, proj_path)
1682 fix_prefix = gyp.common.RelativePath(project_dir_path,
1683 os.path.dirname(proj_path))
1684 return proj_path, fix_prefix
1687 def _GetPlatformOverridesOfProject(spec):
1688 # Prepare a dict indicating which project configurations are used for which
1689 # solution configurations for this target.
1690 config_platform_overrides = {}
1691 for config_name, c in spec['configurations'].iteritems():
1692 config_fullname = _ConfigFullName(config_name, c)
1693 platform = c.get('msvs_target_platform', _ConfigPlatform(c))
1694 fixed_config_fullname = '%s|%s' % (
1695 _ConfigBaseName(config_name, _ConfigPlatform(c)), platform)
1696 config_platform_overrides[config_fullname] = fixed_config_fullname
1697 return config_platform_overrides
1700 def _CreateProjectObjects(target_list, target_dicts, options, msvs_version):
1701 """Create a MSVSProject object for the targets found in target list.
1704 target_list: the list of targets to generate project objects for.
1705 target_dicts: the dictionary of specifications.
1706 options: global generator options.
1707 msvs_version: the MSVSVersion object.
1709 A set of created projects, keyed by target.
1711 global fixpath_prefix
1712 # Generate each project.
1714 for qualified_target in target_list:
1715 spec = target_dicts[qualified_target]
1716 if spec['toolset'] != 'target':
1718 'Multiple toolsets not supported in msvs build (target %s)' %
1720 proj_path, fixpath_prefix = _GetPathOfProject(qualified_target, spec,
1721 options, msvs_version)
1722 guid = _GetGuidOfProject(proj_path, spec)
1723 overrides = _GetPlatformOverridesOfProject(spec)
1724 build_file = gyp.common.BuildFile(qualified_target)
1725 # Create object for this project.
1726 obj = MSVSNew.MSVSProject(
1728 name=spec['target_name'],
1731 build_file=build_file,
1732 config_platform_overrides=overrides,
1733 fixpath_prefix=fixpath_prefix)
1734 # Set project toolset if any (MS build only)
1735 if msvs_version.UsesVcxproj():
1736 obj.set_msbuild_toolset(
1737 _GetMsbuildToolsetOfProject(proj_path, spec, msvs_version))
1738 projects[qualified_target] = obj
1739 # Set all the dependencies, but not if we are using an external builder like
1741 for project in projects.values():
1742 if not project.spec.get('msvs_external_builder'):
1743 deps = project.spec.get('dependencies', [])
1744 deps = [projects[d] for d in deps]
1745 project.set_dependencies(deps)
1749 def _InitNinjaFlavor(options, target_list, target_dicts):
1750 """Initialize targets for the ninja flavor.
1752 This sets up the necessary variables in the targets to generate msvs projects
1753 that use ninja as an external builder. The variables in the spec are only set
1754 if they have not been set. This allows individual specs to override the
1755 default values initialized here.
1757 options: Options provided to the generator.
1758 target_list: List of target pairs: 'base/base.gyp:base'.
1759 target_dicts: Dict of target properties keyed on target pair.
1761 for qualified_target in target_list:
1762 spec = target_dicts[qualified_target]
1763 if spec.get('msvs_external_builder'):
1764 # The spec explicitly defined an external builder, so don't change it.
1767 path_to_ninja = spec.get('msvs_path_to_ninja', 'ninja.exe')
1769 spec['msvs_external_builder'] = 'ninja'
1770 if not spec.get('msvs_external_builder_out_dir'):
1771 spec['msvs_external_builder_out_dir'] = \
1772 options.depth + '/out/$(Configuration)'
1773 if not spec.get('msvs_external_builder_build_cmd'):
1774 spec['msvs_external_builder_build_cmd'] = [
1780 if not spec.get('msvs_external_builder_clean_cmd'):
1781 spec['msvs_external_builder_clean_cmd'] = [
1791 def CalculateVariables(default_variables, params):
1792 """Generated variables that require params to be known."""
1794 generator_flags = params.get('generator_flags', {})
1796 # Select project file format version (if unset, default to auto detecting).
1797 msvs_version = MSVSVersion.SelectVisualStudioVersion(
1798 generator_flags.get('msvs_version', 'auto'))
1799 # Stash msvs_version for later (so we don't have to probe the system twice).
1800 params['msvs_version'] = msvs_version
1802 # Set a variable so conditions can be based on msvs_version.
1803 default_variables['MSVS_VERSION'] = msvs_version.ShortName()
1805 # To determine processor word size on Windows, in addition to checking
1806 # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
1807 # process), it is also necessary to check PROCESSOR_ARCITEW6432 (which
1808 # contains the actual word size of the system when running thru WOW64).
1809 if (os.environ.get('PROCESSOR_ARCHITECTURE', '').find('64') >= 0 or
1810 os.environ.get('PROCESSOR_ARCHITEW6432', '').find('64') >= 0):
1811 default_variables['MSVS_OS_BITS'] = 64
1813 default_variables['MSVS_OS_BITS'] = 32
1815 if gyp.common.GetFlavor(params) == 'ninja':
1816 default_variables['SHARED_INTERMEDIATE_DIR'] = '$(OutDir)gen'
1819 def PerformBuild(data, configurations, params):
1820 options = params['options']
1821 msvs_version = params['msvs_version']
1822 devenv = os.path.join(msvs_version.path, 'Common7', 'IDE', 'devenv.com')
1824 for build_file, build_file_dict in data.iteritems():
1825 (build_file_root, build_file_ext) = os.path.splitext(build_file)
1826 if build_file_ext != '.gyp':
1828 sln_path = build_file_root + options.suffix + '.sln'
1829 if options.generator_output:
1830 sln_path = os.path.join(options.generator_output, sln_path)
1832 for config in configurations:
1833 arguments = [devenv, sln_path, '/Build', config]
1834 print 'Building [%s]: %s' % (config, arguments)
1835 rtn = subprocess.check_call(arguments)
1838 def GenerateOutput(target_list, target_dicts, data, params):
1839 """Generate .sln and .vcproj files.
1841 This is the entry point for this generator.
1843 target_list: List of target pairs: 'base/base.gyp:base'.
1844 target_dicts: Dict of target properties keyed on target pair.
1845 data: Dictionary containing per .gyp data.
1847 global fixpath_prefix
1849 options = params['options']
1851 # Get the project file format version back out of where we stashed it in
1852 # GeneratorCalculatedVariables.
1853 msvs_version = params['msvs_version']
1855 generator_flags = params.get('generator_flags', {})
1857 # Optionally shard targets marked with 'msvs_shard': SHARD_COUNT.
1858 (target_list, target_dicts) = MSVSUtil.ShardTargets(target_list, target_dicts)
1860 # Optionally use the large PDB workaround for targets marked with
1861 # 'msvs_large_pdb': 1.
1862 (target_list, target_dicts) = MSVSUtil.InsertLargePdbShims(
1863 target_list, target_dicts, generator_default_variables)
1865 # Optionally configure each spec to use ninja as the external builder.
1866 if params.get('flavor') == 'ninja':
1867 _InitNinjaFlavor(options, target_list, target_dicts)
1869 # Prepare the set of configurations.
1871 for qualified_target in target_list:
1872 spec = target_dicts[qualified_target]
1873 for config_name, config in spec['configurations'].iteritems():
1874 configs.add(_ConfigFullName(config_name, config))
1875 configs = list(configs)
1877 # Figure out all the projects that will be generated and their guids
1878 project_objects = _CreateProjectObjects(target_list, target_dicts, options,
1881 # Generate each project.
1882 missing_sources = []
1883 for project in project_objects.values():
1884 fixpath_prefix = project.fixpath_prefix
1885 missing_sources.extend(_GenerateProject(project, options, msvs_version,
1887 fixpath_prefix = None
1889 for build_file in data:
1890 # Validate build_file extension
1891 if not build_file.endswith('.gyp'):
1893 sln_path = os.path.splitext(build_file)[0] + options.suffix + '.sln'
1894 if options.generator_output:
1895 sln_path = os.path.join(options.generator_output, sln_path)
1896 # Get projects in the solution, and their dependents.
1897 sln_projects = gyp.common.BuildFileTargets(target_list, build_file)
1898 sln_projects += gyp.common.DeepDependencyTargets(target_dicts, sln_projects)
1899 # Create folder hierarchy.
1900 root_entries = _GatherSolutionFolders(
1901 sln_projects, project_objects, flat=msvs_version.FlatSolution())
1903 sln = MSVSNew.MSVSSolution(sln_path,
1904 entries=root_entries,
1906 websiteProperties=False,
1907 version=msvs_version)
1911 error_message = "Missing input files:\n" + \
1912 '\n'.join(set(missing_sources))
1913 if generator_flags.get('msvs_error_on_missing_sources', False):
1914 raise GypError(error_message)
1916 print >> sys.stdout, "Warning: " + error_message
1919 def _GenerateMSBuildFiltersFile(filters_path, source_files,
1920 extension_to_rule_name):
1921 """Generate the filters file.
1923 This file is used by Visual Studio to organize the presentation of source
1927 filters_path: The path of the file to be created.
1928 source_files: The hierarchical structure of all the sources.
1929 extension_to_rule_name: A dictionary mapping file extensions to rules.
1933 _AppendFiltersForMSBuild('', source_files, extension_to_rule_name,
1934 filter_group, source_group)
1936 content = ['Project',
1937 {'ToolsVersion': '4.0',
1938 'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
1940 ['ItemGroup'] + filter_group,
1941 ['ItemGroup'] + source_group
1943 easy_xml.WriteXmlIfChanged(content, filters_path, pretty=True, win32=True)
1944 elif os.path.exists(filters_path):
1945 # We don't need this filter anymore. Delete the old filter file.
1946 os.unlink(filters_path)
1949 def _AppendFiltersForMSBuild(parent_filter_name, sources,
1950 extension_to_rule_name,
1951 filter_group, source_group):
1952 """Creates the list of filters and sources to be added in the filter file.
1955 parent_filter_name: The name of the filter under which the sources are
1957 sources: The hierarchy of filters and sources to process.
1958 extension_to_rule_name: A dictionary mapping file extensions to rules.
1959 filter_group: The list to which filter entries will be appended.
1960 source_group: The list to which source entries will be appeneded.
1962 for source in sources:
1963 if isinstance(source, MSVSProject.Filter):
1964 # We have a sub-filter. Create the name of that sub-filter.
1965 if not parent_filter_name:
1966 filter_name = source.name
1968 filter_name = '%s\\%s' % (parent_filter_name, source.name)
1969 # Add the filter to the group.
1970 filter_group.append(
1971 ['Filter', {'Include': filter_name},
1972 ['UniqueIdentifier', MSVSNew.MakeGuid(source.name)]])
1973 # Recurse and add its dependents.
1974 _AppendFiltersForMSBuild(filter_name, source.contents,
1975 extension_to_rule_name,
1976 filter_group, source_group)
1978 # It's a source. Create a source entry.
1979 _, element = _MapFileToMsBuildSourceType(source, extension_to_rule_name)
1980 source_entry = [element, {'Include': source}]
1981 # Specify the filter it is part of, if any.
1982 if parent_filter_name:
1983 source_entry.append(['Filter', parent_filter_name])
1984 source_group.append(source_entry)
1987 def _MapFileToMsBuildSourceType(source, extension_to_rule_name):
1988 """Returns the group and element type of the source file.
1991 source: The source file name.
1992 extension_to_rule_name: A dictionary mapping file extensions to rules.
1995 A pair of (group this file should be part of, the label of element)
1997 _, ext = os.path.splitext(source)
1998 if ext in extension_to_rule_name:
2000 element = extension_to_rule_name[ext]
2001 elif ext in ['.cc', '.cpp', '.c', '.cxx']:
2003 element = 'ClCompile'
2004 elif ext in ['.h', '.hxx']:
2006 element = 'ClInclude'
2009 element = 'ResourceCompile'
2016 return (group, element)
2019 def _GenerateRulesForMSBuild(output_dir, options, spec,
2020 sources, excluded_sources,
2021 props_files_of_rules, targets_files_of_rules,
2022 actions_to_add, extension_to_rule_name):
2023 # MSBuild rules are implemented using three files: an XML file, a .targets
2024 # file and a .props file.
2025 # See http://blogs.msdn.com/b/vcblog/archive/2010/04/21/quick-help-on-vs2010-custom-build-rule.aspx
2027 rules = spec.get('rules', [])
2028 rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
2029 rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
2032 for rule in rules_native:
2033 # Skip a rule with no action and no inputs.
2034 if 'action' not in rule and not rule.get('rule_sources', []):
2036 msbuild_rule = MSBuildRule(rule, spec)
2037 msbuild_rules.append(msbuild_rule)
2038 extension_to_rule_name[msbuild_rule.extension] = msbuild_rule.rule_name
2040 base = spec['target_name'] + options.suffix
2041 props_name = base + '.props'
2042 targets_name = base + '.targets'
2043 xml_name = base + '.xml'
2045 props_files_of_rules.add(props_name)
2046 targets_files_of_rules.add(targets_name)
2048 props_path = os.path.join(output_dir, props_name)
2049 targets_path = os.path.join(output_dir, targets_name)
2050 xml_path = os.path.join(output_dir, xml_name)
2052 _GenerateMSBuildRulePropsFile(props_path, msbuild_rules)
2053 _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules)
2054 _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules)
2057 _GenerateExternalRules(rules_external, output_dir, spec,
2058 sources, options, actions_to_add)
2059 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
2062 class MSBuildRule(object):
2063 """Used to store information used to generate an MSBuild rule.
2066 rule_name: The rule name, sanitized to use in XML.
2067 target_name: The name of the target.
2068 after_targets: The name of the AfterTargets element.
2069 before_targets: The name of the BeforeTargets element.
2070 depends_on: The name of the DependsOn element.
2071 compute_output: The name of the ComputeOutput element.
2072 dirs_to_make: The name of the DirsToMake element.
2073 inputs: The name of the _inputs element.
2074 tlog: The name of the _tlog element.
2075 extension: The extension this rule applies to.
2076 description: The message displayed when this rule is invoked.
2077 additional_dependencies: A string listing additional dependencies.
2078 outputs: The outputs of this rule.
2079 command: The command used to run the rule.
2082 def __init__(self, rule, spec):
2083 self.display_name = rule['rule_name']
2084 # Assure that the rule name is only characters and numbers
2085 self.rule_name = re.sub(r'\W', '_', self.display_name)
2086 # Create the various element names, following the example set by the
2087 # Visual Studio 2008 to 2010 conversion. I don't know if VS2010
2088 # is sensitive to the exact names.
2089 self.target_name = '_' + self.rule_name
2090 self.after_targets = self.rule_name + 'AfterTargets'
2091 self.before_targets = self.rule_name + 'BeforeTargets'
2092 self.depends_on = self.rule_name + 'DependsOn'
2093 self.compute_output = 'Compute%sOutput' % self.rule_name
2094 self.dirs_to_make = self.rule_name + 'DirsToMake'
2095 self.inputs = self.rule_name + '_inputs'
2096 self.tlog = self.rule_name + '_tlog'
2097 self.extension = rule['extension']
2098 if not self.extension.startswith('.'):
2099 self.extension = '.' + self.extension
2101 self.description = MSVSSettings.ConvertVCMacrosToMSBuild(
2102 rule.get('message', self.rule_name))
2103 old_additional_dependencies = _FixPaths(rule.get('inputs', []))
2104 self.additional_dependencies = (
2105 ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
2106 for i in old_additional_dependencies]))
2107 old_outputs = _FixPaths(rule.get('outputs', []))
2108 self.outputs = ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
2109 for i in old_outputs])
2110 old_command = _BuildCommandLineForRule(spec, rule, has_input_path=True,
2112 self.command = MSVSSettings.ConvertVCMacrosToMSBuild(old_command)
2115 def _GenerateMSBuildRulePropsFile(props_path, msbuild_rules):
2116 """Generate the .props file."""
2117 content = ['Project',
2118 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'}]
2119 for rule in msbuild_rules:
2122 {'Condition': "'$(%s)' == '' and '$(%s)' == '' and "
2123 "'$(ConfigurationType)' != 'Makefile'" % (rule.before_targets,
2126 [rule.before_targets, 'Midl'],
2127 [rule.after_targets, 'CustomBuild'],
2131 {'Condition': "'$(ConfigurationType)' != 'Makefile'"},
2132 '_SelectedFiles;$(%s)' % rule.depends_on
2135 ['ItemDefinitionGroup',
2137 ['CommandLineTemplate', rule.command],
2138 ['Outputs', rule.outputs],
2139 ['ExecutionDescription', rule.description],
2140 ['AdditionalDependencies', rule.additional_dependencies],
2144 easy_xml.WriteXmlIfChanged(content, props_path, pretty=True, win32=True)
2147 def _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules):
2148 """Generate the .targets file."""
2149 content = ['Project',
2150 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
2155 ['PropertyPageSchema',
2156 {'Include': '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'}
2159 for rule in msbuild_rules:
2161 ['AvailableItemName',
2162 {'Include': rule.rule_name},
2163 ['Targets', rule.target_name],
2165 content.append(item_group)
2167 for rule in msbuild_rules:
2170 {'TaskName': rule.rule_name,
2171 'TaskFactory': 'XamlTaskFactory',
2172 'AssemblyName': 'Microsoft.Build.Tasks.v4.0'
2174 ['Task', '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'],
2176 for rule in msbuild_rules:
2177 rule_name = rule.rule_name
2178 target_outputs = '%%(%s.Outputs)' % rule_name
2179 target_inputs = ('%%(%s.Identity);%%(%s.AdditionalDependencies);'
2180 '$(MSBuildProjectFile)') % (rule_name, rule_name)
2181 rule_inputs = '%%(%s.Identity)' % rule_name
2182 extension_condition = ("'%(Extension)'=='.obj' or "
2183 "'%(Extension)'=='.res' or "
2184 "'%(Extension)'=='.rsc' or "
2185 "'%(Extension)'=='.lib'")
2188 {'Condition': "'@(SelectedFiles)' != ''"},
2190 {'Remove': '@(%s)' % rule_name,
2191 'Condition': "'%(Identity)' != '@(SelectedFiles)'"
2197 [rule.inputs, {'Include': '%%(%s.AdditionalDependencies)' % rule_name}]
2202 {'Include': '%%(%s.Outputs)' % rule_name,
2203 'Condition': ("'%%(%s.Outputs)' != '' and "
2204 "'%%(%s.ExcludedFromBuild)' != 'true'" %
2205 (rule_name, rule_name))
2207 ['Source', "@(%s, '|')" % rule_name],
2208 ['Inputs', "@(%s -> '%%(Fullpath)', ';')" % rule.inputs],
2213 {'Importance': 'High',
2214 'Text': '%%(%s.ExecutionDescription)' % rule_name
2217 write_tlog_section = [
2219 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2220 "'true'" % (rule.tlog, rule.tlog),
2221 'File': '$(IntDir)$(ProjectName).write.1.tlog',
2222 'Lines': "^%%(%s.Source);@(%s->'%%(Fullpath)')" % (rule.tlog,
2226 read_tlog_section = [
2228 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2229 "'true'" % (rule.tlog, rule.tlog),
2230 'File': '$(IntDir)$(ProjectName).read.1.tlog',
2231 'Lines': "^%%(%s.Source);%%(%s.Inputs)" % (rule.tlog, rule.tlog)
2234 command_and_input_section = [
2236 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2237 "'true'" % (rule_name, rule_name),
2238 'CommandLineTemplate': '%%(%s.CommandLineTemplate)' % rule_name,
2239 'AdditionalOptions': '%%(%s.AdditionalOptions)' % rule_name,
2240 'Inputs': rule_inputs
2245 {'Name': rule.target_name,
2246 'BeforeTargets': '$(%s)' % rule.before_targets,
2247 'AfterTargets': '$(%s)' % rule.after_targets,
2248 'Condition': "'@(%s)' != ''" % rule_name,
2249 'DependsOnTargets': '$(%s);%s' % (rule.depends_on,
2250 rule.compute_output),
2251 'Outputs': target_outputs,
2252 'Inputs': target_inputs
2260 command_and_input_section,
2263 ['ComputeLinkInputsTargets',
2264 '$(ComputeLinkInputsTargets);',
2265 '%s;' % rule.compute_output
2267 ['ComputeLibInputsTargets',
2268 '$(ComputeLibInputsTargets);',
2269 '%s;' % rule.compute_output
2273 {'Name': rule.compute_output,
2274 'Condition': "'@(%s)' != ''" % rule_name
2278 {'Condition': "'@(%s)' != '' and "
2279 "'%%(%s.ExcludedFromBuild)' != 'true'" % (rule_name, rule_name),
2280 'Include': '%%(%s.Outputs)' % rule_name
2284 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2285 'Condition': extension_condition
2289 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2290 'Condition': extension_condition
2294 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2295 'Condition': extension_condition
2300 {'Directories': ("@(%s->'%%(RootDir)%%(Directory)')" %
2306 easy_xml.WriteXmlIfChanged(content, targets_path, pretty=True, win32=True)
2309 def _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules):
2310 # Generate the .xml file
2312 'ProjectSchemaDefinitions',
2313 {'xmlns': ('clr-namespace:Microsoft.Build.Framework.XamlTypes;'
2314 'assembly=Microsoft.Build.Framework'),
2315 'xmlns:x': 'http://schemas.microsoft.com/winfx/2006/xaml',
2316 'xmlns:sys': 'clr-namespace:System;assembly=mscorlib',
2317 'xmlns:transformCallback':
2318 'Microsoft.Cpp.Dev10.ConvertPropertyCallback'
2321 for rule in msbuild_rules:
2324 {'Name': rule.rule_name,
2325 'PageTemplate': 'tool',
2326 'DisplayName': rule.display_name,
2331 {'Persistence': 'ProjectFile',
2332 'ItemType': rule.rule_name
2338 {'Name': 'General'},
2339 ['Category.DisplayName',
2340 ['sys:String', 'General'],
2344 {'Name': 'Command Line',
2345 'Subtype': 'CommandLine'
2347 ['Category.DisplayName',
2348 ['sys:String', 'Command Line'],
2352 ['StringListProperty',
2354 'Category': 'Command Line',
2355 'IsRequired': 'true',
2358 ['StringListProperty.DataSource',
2360 {'Persistence': 'ProjectFile',
2361 'ItemType': rule.rule_name,
2362 'SourceType': 'Item'
2368 {'Name': 'CommandLineTemplate',
2369 'DisplayName': 'Command Line',
2371 'IncludeInCommandLine': 'False'
2374 ['DynamicEnumProperty',
2375 {'Name': rule.before_targets,
2376 'Category': 'General',
2377 'EnumProvider': 'Targets',
2378 'IncludeInCommandLine': 'False'
2380 ['DynamicEnumProperty.DisplayName',
2381 ['sys:String', 'Execute Before'],
2383 ['DynamicEnumProperty.Description',
2384 ['sys:String', 'Specifies the targets for the build customization'
2388 ['DynamicEnumProperty.ProviderSettings',
2391 'Value': '^%s|^Compute' % rule.before_targets
2395 ['DynamicEnumProperty.DataSource',
2397 {'Persistence': 'ProjectFile',
2398 'HasConfigurationCondition': 'true'
2403 ['DynamicEnumProperty',
2404 {'Name': rule.after_targets,
2405 'Category': 'General',
2406 'EnumProvider': 'Targets',
2407 'IncludeInCommandLine': 'False'
2409 ['DynamicEnumProperty.DisplayName',
2410 ['sys:String', 'Execute After'],
2412 ['DynamicEnumProperty.Description',
2413 ['sys:String', ('Specifies the targets for the build customization'
2417 ['DynamicEnumProperty.ProviderSettings',
2420 'Value': '^%s|^Compute' % rule.after_targets
2424 ['DynamicEnumProperty.DataSource',
2426 {'Persistence': 'ProjectFile',
2428 'HasConfigurationCondition': 'true'
2433 ['StringListProperty',
2435 'DisplayName': 'Outputs',
2437 'IncludeInCommandLine': 'False'
2441 {'Name': 'ExecutionDescription',
2442 'DisplayName': 'Execution Description',
2444 'IncludeInCommandLine': 'False'
2447 ['StringListProperty',
2448 {'Name': 'AdditionalDependencies',
2449 'DisplayName': 'Additional Dependencies',
2450 'IncludeInCommandLine': 'False',
2455 {'Subtype': 'AdditionalOptions',
2456 'Name': 'AdditionalOptions',
2457 'Category': 'Command Line'
2459 ['StringProperty.DisplayName',
2460 ['sys:String', 'Additional Options'],
2462 ['StringProperty.Description',
2463 ['sys:String', 'Additional Options'],
2468 {'Name': rule.rule_name,
2469 'DisplayName': rule.display_name
2473 {'Name': '*' + rule.extension,
2474 'ContentType': rule.rule_name
2478 {'Name': rule.rule_name,
2480 'ItemType': rule.rule_name
2484 easy_xml.WriteXmlIfChanged(content, xml_path, pretty=True, win32=True)
2487 def _GetConfigurationAndPlatform(name, settings):
2488 configuration = name.rsplit('_', 1)[0]
2489 platform = settings.get('msvs_configuration_platform', 'Win32')
2490 return (configuration, platform)
2493 def _GetConfigurationCondition(name, settings):
2494 return (r"'$(Configuration)|$(Platform)'=='%s|%s'" %
2495 _GetConfigurationAndPlatform(name, settings))
2498 def _GetMSBuildProjectConfigurations(configurations):
2499 group = ['ItemGroup', {'Label': 'ProjectConfigurations'}]
2500 for (name, settings) in sorted(configurations.iteritems()):
2501 configuration, platform = _GetConfigurationAndPlatform(name, settings)
2502 designation = '%s|%s' % (configuration, platform)
2504 ['ProjectConfiguration', {'Include': designation},
2505 ['Configuration', configuration],
2506 ['Platform', platform]])
2510 def _GetMSBuildGlobalProperties(spec, guid, gyp_file_name):
2511 namespace = os.path.splitext(gyp_file_name)[0]
2513 ['PropertyGroup', {'Label': 'Globals'},
2514 ['ProjectGuid', guid],
2515 ['Keyword', 'Win32Proj'],
2516 ['RootNamespace', namespace],
2521 def _GetMSBuildConfigurationDetails(spec, build_file):
2523 for name, settings in spec['configurations'].iteritems():
2524 msbuild_attributes = _GetMSBuildAttributes(spec, settings, build_file)
2525 condition = _GetConfigurationCondition(name, settings)
2526 character_set = msbuild_attributes.get('CharacterSet')
2527 _AddConditionalProperty(properties, condition, 'ConfigurationType',
2528 msbuild_attributes['ConfigurationType'])
2530 _AddConditionalProperty(properties, condition, 'CharacterSet',
2532 return _GetMSBuildPropertyGroup(spec, 'Configuration', properties)
2535 def _GetMSBuildLocalProperties(msbuild_toolset):
2536 # Currently the only local property we support is PlatformToolset
2540 ['PropertyGroup', {'Label': 'Locals'},
2541 ['PlatformToolset', msbuild_toolset],
2547 def _GetMSBuildPropertySheets(configurations):
2548 user_props = r'$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props'
2549 additional_props = {}
2550 props_specified = False
2551 for name, settings in sorted(configurations.iteritems()):
2552 configuration = _GetConfigurationCondition(name, settings)
2553 if settings.has_key('msbuild_props'):
2554 additional_props[configuration] = _FixPaths(settings['msbuild_props'])
2555 props_specified = True
2557 additional_props[configuration] = ''
2559 if not props_specified:
2562 {'Label': 'PropertySheets'},
2564 {'Project': user_props,
2565 'Condition': "exists('%s')" % user_props,
2566 'Label': 'LocalAppDataPlatform'
2573 for condition, props in additional_props.iteritems():
2576 {'Label': 'PropertySheets',
2577 'Condition': condition
2580 {'Project': user_props,
2581 'Condition': "exists('%s')" % user_props,
2582 'Label': 'LocalAppDataPlatform'
2586 for props_file in props:
2587 import_group.append(['Import', {'Project':props_file}])
2588 sheets.append(import_group)
2591 def _ConvertMSVSBuildAttributes(spec, config, build_file):
2592 config_type = _GetMSVSConfigurationType(spec, build_file)
2593 msvs_attributes = _GetMSVSAttributes(spec, config, config_type)
2594 msbuild_attributes = {}
2595 for a in msvs_attributes:
2596 if a in ['IntermediateDirectory', 'OutputDirectory']:
2597 directory = MSVSSettings.ConvertVCMacrosToMSBuild(msvs_attributes[a])
2598 if not directory.endswith('\\'):
2600 msbuild_attributes[a] = directory
2601 elif a == 'CharacterSet':
2602 msbuild_attributes[a] = _ConvertMSVSCharacterSet(msvs_attributes[a])
2603 elif a == 'ConfigurationType':
2604 msbuild_attributes[a] = _ConvertMSVSConfigurationType(msvs_attributes[a])
2606 print 'Warning: Do not know how to convert MSVS attribute ' + a
2607 return msbuild_attributes
2610 def _ConvertMSVSCharacterSet(char_set):
2611 if char_set.isdigit():
2620 def _ConvertMSVSConfigurationType(config_type):
2621 if config_type.isdigit():
2624 '2': 'DynamicLibrary',
2625 '4': 'StaticLibrary',
2631 def _GetMSBuildAttributes(spec, config, build_file):
2632 if 'msbuild_configuration_attributes' not in config:
2633 msbuild_attributes = _ConvertMSVSBuildAttributes(spec, config, build_file)
2636 config_type = _GetMSVSConfigurationType(spec, build_file)
2637 config_type = _ConvertMSVSConfigurationType(config_type)
2638 msbuild_attributes = config.get('msbuild_configuration_attributes', {})
2639 msbuild_attributes.setdefault('ConfigurationType', config_type)
2640 output_dir = msbuild_attributes.get('OutputDirectory',
2641 '$(SolutionDir)$(Configuration)')
2642 msbuild_attributes['OutputDirectory'] = _FixPath(output_dir) + '\\'
2643 if 'IntermediateDirectory' not in msbuild_attributes:
2644 intermediate = _FixPath('$(Configuration)') + '\\'
2645 msbuild_attributes['IntermediateDirectory'] = intermediate
2646 if 'CharacterSet' in msbuild_attributes:
2647 msbuild_attributes['CharacterSet'] = _ConvertMSVSCharacterSet(
2648 msbuild_attributes['CharacterSet'])
2649 if 'TargetName' not in msbuild_attributes:
2650 prefix = spec.get('product_prefix', '')
2651 product_name = spec.get('product_name', '$(ProjectName)')
2652 target_name = prefix + product_name
2653 msbuild_attributes['TargetName'] = target_name
2655 if spec.get('msvs_external_builder'):
2656 external_out_dir = spec.get('msvs_external_builder_out_dir', '.')
2657 msbuild_attributes['OutputDirectory'] = _FixPath(external_out_dir) + '\\'
2659 # Make sure that 'TargetPath' matches 'Lib.OutputFile' or 'Link.OutputFile'
2660 # (depending on the tool used) to avoid MSB8012 warning.
2661 msbuild_tool_map = {
2662 'executable': 'Link',
2663 'shared_library': 'Link',
2664 'loadable_module': 'Link',
2665 'static_library': 'Lib',
2667 msbuild_tool = msbuild_tool_map.get(spec['type'])
2669 msbuild_settings = config['finalized_msbuild_settings']
2670 out_file = msbuild_settings[msbuild_tool].get('OutputFile')
2672 msbuild_attributes['TargetPath'] = _FixPath(out_file)
2673 target_ext = msbuild_settings[msbuild_tool].get('TargetExt')
2675 msbuild_attributes['TargetExt'] = target_ext
2677 return msbuild_attributes
2680 def _GetMSBuildConfigurationGlobalProperties(spec, configurations, build_file):
2681 # TODO(jeanluc) We could optimize out the following and do it only if
2682 # there are actions.
2683 # TODO(jeanluc) Handle the equivalent of setting 'CYGWIN=nontsec'.
2685 cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.'])[0]
2687 cyg_path = '$(MSBuildProjectDirectory)\\%s\\bin\\' % _FixPath(cygwin_dirs)
2688 new_paths.append(cyg_path)
2689 # TODO(jeanluc) Change the convention to have both a cygwin_dir and a
2691 python_path = cyg_path.replace('cygwin\\bin', 'python_26')
2692 new_paths.append(python_path)
2694 new_paths = '$(ExecutablePath);' + ';'.join(new_paths)
2697 for (name, configuration) in sorted(configurations.iteritems()):
2698 condition = _GetConfigurationCondition(name, configuration)
2699 attributes = _GetMSBuildAttributes(spec, configuration, build_file)
2700 msbuild_settings = configuration['finalized_msbuild_settings']
2701 _AddConditionalProperty(properties, condition, 'IntDir',
2702 attributes['IntermediateDirectory'])
2703 _AddConditionalProperty(properties, condition, 'OutDir',
2704 attributes['OutputDirectory'])
2705 _AddConditionalProperty(properties, condition, 'TargetName',
2706 attributes['TargetName'])
2708 if attributes.get('TargetPath'):
2709 _AddConditionalProperty(properties, condition, 'TargetPath',
2710 attributes['TargetPath'])
2711 if attributes.get('TargetExt'):
2712 _AddConditionalProperty(properties, condition, 'TargetExt',
2713 attributes['TargetExt'])
2716 _AddConditionalProperty(properties, condition, 'ExecutablePath',
2718 tool_settings = msbuild_settings.get('', {})
2719 for name, value in sorted(tool_settings.iteritems()):
2720 formatted_value = _GetValueFormattedForMSBuild('', name, value)
2721 _AddConditionalProperty(properties, condition, name, formatted_value)
2722 return _GetMSBuildPropertyGroup(spec, None, properties)
2725 def _AddConditionalProperty(properties, condition, name, value):
2726 """Adds a property / conditional value pair to a dictionary.
2729 properties: The dictionary to be modified. The key is the name of the
2730 property. The value is itself a dictionary; its key is the value and
2731 the value a list of condition for which this value is true.
2732 condition: The condition under which the named property has the value.
2733 name: The name of the property.
2734 value: The value of the property.
2736 if name not in properties:
2737 properties[name] = {}
2738 values = properties[name]
2739 if value not in values:
2741 conditions = values[value]
2742 conditions.append(condition)
2745 # Regex for msvs variable references ( i.e. $(FOO) ).
2746 MSVS_VARIABLE_REFERENCE = re.compile('\$\(([a-zA-Z_][a-zA-Z0-9_]*)\)')
2749 def _GetMSBuildPropertyGroup(spec, label, properties):
2750 """Returns a PropertyGroup definition for the specified properties.
2753 spec: The target project dict.
2754 label: An optional label for the PropertyGroup.
2755 properties: The dictionary to be converted. The key is the name of the
2756 property. The value is itself a dictionary; its key is the value and
2757 the value a list of condition for which this value is true.
2759 group = ['PropertyGroup']
2761 group.append({'Label': label})
2762 num_configurations = len(spec['configurations'])
2764 # Use a definition of edges such that user_of_variable -> used_varible.
2765 # This happens to be easier in this case, since a variable's
2766 # definition contains all variables it references in a single string.
2768 for value in sorted(properties[node].keys()):
2769 # Add to edges all $(...) references to variables.
2771 # Variable references that refer to names not in properties are excluded
2772 # These can exist for instance to refer built in definitions like
2775 # Self references are ignored. Self reference is used in a few places to
2776 # append to the default value. I.e. PATH=$(PATH);other_path
2777 edges.update(set([v for v in MSVS_VARIABLE_REFERENCE.findall(value)
2778 if v in properties and v != node]))
2780 properties_ordered = gyp.common.TopologicallySorted(
2781 properties.keys(), GetEdges)
2782 # Walk properties in the reverse of a topological sort on
2783 # user_of_variable -> used_variable as this ensures variables are
2784 # defined before they are used.
2785 # NOTE: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))
2786 for name in reversed(properties_ordered):
2787 values = properties[name]
2788 for value, conditions in sorted(values.iteritems()):
2789 if len(conditions) == num_configurations:
2790 # If the value is the same all configurations,
2791 # just add one unconditional entry.
2792 group.append([name, value])
2794 for condition in conditions:
2795 group.append([name, {'Condition': condition}, value])
2799 def _GetMSBuildToolSettingsSections(spec, configurations):
2801 for (name, configuration) in sorted(configurations.iteritems()):
2802 msbuild_settings = configuration['finalized_msbuild_settings']
2803 group = ['ItemDefinitionGroup',
2804 {'Condition': _GetConfigurationCondition(name, configuration)}
2806 for tool_name, tool_settings in sorted(msbuild_settings.iteritems()):
2807 # Skip the tool named '' which is a holder of global settings handled
2808 # by _GetMSBuildConfigurationGlobalProperties.
2812 for name, value in sorted(tool_settings.iteritems()):
2813 formatted_value = _GetValueFormattedForMSBuild(tool_name, name,
2815 tool.append([name, formatted_value])
2817 groups.append(group)
2821 def _FinalizeMSBuildSettings(spec, configuration):
2822 if 'msbuild_settings' in configuration:
2824 msbuild_settings = configuration['msbuild_settings']
2825 MSVSSettings.ValidateMSBuildSettings(msbuild_settings)
2828 msvs_settings = configuration.get('msvs_settings', {})
2829 msbuild_settings = MSVSSettings.ConvertToMSBuildSettings(msvs_settings)
2830 include_dirs, resource_include_dirs = _GetIncludeDirs(configuration)
2831 libraries = _GetLibraries(spec)
2832 library_dirs = _GetLibraryDirs(configuration)
2833 out_file, _, msbuild_tool = _GetOutputFilePathAndTool(spec, msbuild=True)
2834 target_ext = _GetOutputTargetExt(spec)
2835 defines = _GetDefines(configuration)
2837 # Visual Studio 2010 has TR1
2838 defines = [d for d in defines if d != '_HAS_TR1=0']
2839 # Warn of ignored settings
2840 ignored_settings = ['msvs_prebuild', 'msvs_postbuild', 'msvs_tool_files']
2841 for ignored_setting in ignored_settings:
2842 value = configuration.get(ignored_setting)
2844 print ('Warning: The automatic conversion to MSBuild does not handle '
2845 '%s. Ignoring setting of %s' % (ignored_setting, str(value)))
2847 defines = [_EscapeCppDefineForMSBuild(d) for d in defines]
2848 disabled_warnings = _GetDisabledWarnings(configuration)
2849 # TODO(jeanluc) Validate & warn that we don't translate
2850 # prebuild = configuration.get('msvs_prebuild')
2851 # postbuild = configuration.get('msvs_postbuild')
2852 def_file = _GetModuleDefinition(spec)
2853 precompiled_header = configuration.get('msvs_precompiled_header')
2855 # Add the information to the appropriate tool
2856 # TODO(jeanluc) We could optimize and generate these settings only if
2857 # the corresponding files are found, e.g. don't generate ResourceCompile
2858 # if you don't have any resources.
2859 _ToolAppend(msbuild_settings, 'ClCompile',
2860 'AdditionalIncludeDirectories', include_dirs)
2861 _ToolAppend(msbuild_settings, 'ResourceCompile',
2862 'AdditionalIncludeDirectories', resource_include_dirs)
2863 # Add in libraries, note that even for empty libraries, we want this
2864 # set, to prevent inheriting default libraries from the enviroment.
2865 _ToolSetOrAppend(msbuild_settings, 'Link', 'AdditionalDependencies',
2867 _ToolAppend(msbuild_settings, 'Link', 'AdditionalLibraryDirectories',
2870 _ToolAppend(msbuild_settings, msbuild_tool, 'OutputFile', out_file,
2873 _ToolAppend(msbuild_settings, msbuild_tool, 'TargetExt', target_ext,
2876 _ToolAppend(msbuild_settings, 'ClCompile',
2877 'PreprocessorDefinitions', defines)
2878 _ToolAppend(msbuild_settings, 'ResourceCompile',
2879 'PreprocessorDefinitions', defines)
2880 # Add disabled warnings.
2881 _ToolAppend(msbuild_settings, 'ClCompile',
2882 'DisableSpecificWarnings', disabled_warnings)
2883 # Turn on precompiled headers if appropriate.
2884 if precompiled_header:
2885 precompiled_header = os.path.split(precompiled_header)[1]
2886 _ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'Use')
2887 _ToolAppend(msbuild_settings, 'ClCompile',
2888 'PrecompiledHeaderFile', precompiled_header)
2889 _ToolAppend(msbuild_settings, 'ClCompile',
2890 'ForcedIncludeFiles', [precompiled_header])
2891 # Loadable modules don't generate import libraries;
2892 # tell dependent projects to not expect one.
2893 if spec['type'] == 'loadable_module':
2894 _ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'true')
2895 # Set the module definition file if any.
2897 _ToolAppend(msbuild_settings, 'Link', 'ModuleDefinitionFile', def_file)
2898 configuration['finalized_msbuild_settings'] = msbuild_settings
2901 def _GetValueFormattedForMSBuild(tool_name, name, value):
2902 if type(value) == list:
2903 # For some settings, VS2010 does not automatically extends the settings
2904 # TODO(jeanluc) Is this what we want?
2905 if name in ['AdditionalIncludeDirectories',
2906 'AdditionalLibraryDirectories',
2907 'AdditionalOptions',
2909 'DisableSpecificWarnings',
2910 'PreprocessorDefinitions']:
2911 value.append('%%(%s)' % name)
2912 # For most tools, entries in a list should be separated with ';' but some
2913 # settings use a space. Check for those first.
2915 'ClCompile': ['AdditionalOptions'],
2916 'Link': ['AdditionalOptions'],
2917 'Lib': ['AdditionalOptions']}
2918 if tool_name in exceptions and name in exceptions[tool_name]:
2922 formatted_value = char.join(
2923 [MSVSSettings.ConvertVCMacrosToMSBuild(i) for i in value])
2925 formatted_value = MSVSSettings.ConvertVCMacrosToMSBuild(value)
2926 return formatted_value
2929 def _VerifySourcesExist(sources, root_dir):
2930 """Verifies that all source files exist on disk.
2932 Checks that all regular source files, i.e. not created at run time,
2933 exist on disk. Missing files cause needless recompilation but no otherwise
2937 sources: A recursive list of Filter/file names.
2938 root_dir: The root directory for the relative path names.
2940 A list of source files that cannot be found on disk.
2942 missing_sources = []
2943 for source in sources:
2944 if isinstance(source, MSVSProject.Filter):
2945 missing_sources.extend(_VerifySourcesExist(source.contents, root_dir))
2947 if '$' not in source:
2948 full_path = os.path.join(root_dir, source)
2949 if not os.path.exists(full_path):
2950 missing_sources.append(full_path)
2951 return missing_sources
2954 def _GetMSBuildSources(spec, sources, exclusions, extension_to_rule_name,
2955 actions_spec, sources_handled_by_action, list_excluded):
2956 groups = ['none', 'midl', 'include', 'compile', 'resource', 'rule']
2957 grouped_sources = {}
2959 grouped_sources[g] = []
2961 _AddSources2(spec, sources, exclusions, grouped_sources,
2962 extension_to_rule_name, sources_handled_by_action, list_excluded)
2965 if grouped_sources[g]:
2966 sources.append(['ItemGroup'] + grouped_sources[g])
2968 sources.append(['ItemGroup'] + actions_spec)
2972 def _AddSources2(spec, sources, exclusions, grouped_sources,
2973 extension_to_rule_name, sources_handled_by_action,
2975 extensions_excluded_from_precompile = []
2976 for source in sources:
2977 if isinstance(source, MSVSProject.Filter):
2978 _AddSources2(spec, source.contents, exclusions, grouped_sources,
2979 extension_to_rule_name, sources_handled_by_action,
2982 if not source in sources_handled_by_action:
2984 excluded_configurations = exclusions.get(source, [])
2985 if len(excluded_configurations) == len(spec['configurations']):
2986 detail.append(['ExcludedFromBuild', 'true'])
2988 for config_name, configuration in sorted(excluded_configurations):
2989 condition = _GetConfigurationCondition(config_name, configuration)
2990 detail.append(['ExcludedFromBuild',
2991 {'Condition': condition},
2993 # Add precompile if needed
2994 for config_name, configuration in spec['configurations'].iteritems():
2995 precompiled_source = configuration.get('msvs_precompiled_source', '')
2996 if precompiled_source != '':
2997 precompiled_source = _FixPath(precompiled_source)
2998 if not extensions_excluded_from_precompile:
2999 # If the precompiled header is generated by a C source, we must
3000 # not try to use it for C++ sources, and vice versa.
3001 basename, extension = os.path.splitext(precompiled_source)
3002 if extension == '.c':
3003 extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
3005 extensions_excluded_from_precompile = ['.c']
3007 if precompiled_source == source:
3008 condition = _GetConfigurationCondition(config_name, configuration)
3009 detail.append(['PrecompiledHeader',
3010 {'Condition': condition},
3014 # Turn off precompiled header usage for source files of a
3015 # different type than the file that generated the
3016 # precompiled header.
3017 for extension in extensions_excluded_from_precompile:
3018 if source.endswith(extension):
3019 detail.append(['PrecompiledHeader', ''])
3020 detail.append(['ForcedIncludeFiles', ''])
3022 group, element = _MapFileToMsBuildSourceType(source,
3023 extension_to_rule_name)
3024 grouped_sources[group].append([element, {'Include': source}] + detail)
3027 def _GetMSBuildProjectReferences(project):
3029 if project.dependencies:
3030 group = ['ItemGroup']
3031 for dependency in project.dependencies:
3032 guid = dependency.guid
3033 project_dir = os.path.split(project.path)[0]
3034 relative_path = gyp.common.RelativePath(dependency.path, project_dir)
3035 project_ref = ['ProjectReference',
3036 {'Include': relative_path},
3038 ['ReferenceOutputAssembly', 'false']
3040 for config in dependency.spec.get('configurations', {}).itervalues():
3041 # If it's disabled in any config, turn it off in the reference.
3042 if config.get('msvs_2010_disable_uldi_when_referenced', 0):
3043 project_ref.append(['UseLibraryDependencyInputs', 'false'])
3045 group.append(project_ref)
3046 references.append(group)
3050 def _GenerateMSBuildProject(project, options, version, generator_flags):
3052 configurations = spec['configurations']
3053 project_dir, project_file_name = os.path.split(project.path)
3054 msbuildproj_dir = os.path.dirname(project.path)
3055 if msbuildproj_dir and not os.path.exists(msbuildproj_dir):
3056 os.makedirs(msbuildproj_dir)
3057 # Prepare list of sources and excluded sources.
3058 gyp_path = _NormalizedSource(project.build_file)
3059 relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
3061 gyp_file = os.path.split(project.build_file)[1]
3062 sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
3066 props_files_of_rules = set()
3067 targets_files_of_rules = set()
3068 extension_to_rule_name = {}
3069 list_excluded = generator_flags.get('msvs_list_excluded_files', True)
3071 # Don't generate rules if we are using an external builder like ninja.
3072 if not spec.get('msvs_external_builder'):
3073 _GenerateRulesForMSBuild(project_dir, options, spec,
3074 sources, excluded_sources,
3075 props_files_of_rules, targets_files_of_rules,
3076 actions_to_add, extension_to_rule_name)
3078 rules = spec.get('rules', [])
3079 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
3081 sources, excluded_sources, excluded_idl = (
3082 _AdjustSourcesAndConvertToFilterHierarchy(spec, options,
3083 project_dir, sources,
3087 # Don't add actions if we are using an external builder like ninja.
3088 if not spec.get('msvs_external_builder'):
3089 _AddActions(actions_to_add, spec, project.build_file)
3090 _AddCopies(actions_to_add, spec)
3092 # NOTE: this stanza must appear after all actions have been decided.
3093 # Don't excluded sources with actions attached, or they won't run.
3094 excluded_sources = _FilterActionsFromExcluded(
3095 excluded_sources, actions_to_add)
3097 exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
3098 actions_spec, sources_handled_by_action = _GenerateActionsForMSBuild(
3099 spec, actions_to_add)
3101 _GenerateMSBuildFiltersFile(project.path + '.filters', sources,
3102 extension_to_rule_name)
3103 missing_sources = _VerifySourcesExist(sources, project_dir)
3105 for configuration in configurations.itervalues():
3106 _FinalizeMSBuildSettings(spec, configuration)
3108 # Add attributes to root element
3110 import_default_section = [
3111 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.Default.props'}]]
3112 import_cpp_props_section = [
3113 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.props'}]]
3114 import_cpp_targets_section = [
3115 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.targets'}]]
3116 macro_section = [['PropertyGroup', {'Label': 'UserMacros'}]]
3120 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003',
3121 'ToolsVersion': version.ProjectVersion(),
3122 'DefaultTargets': 'Build'
3125 content += _GetMSBuildProjectConfigurations(configurations)
3126 content += _GetMSBuildGlobalProperties(spec, project.guid, project_file_name)
3127 content += import_default_section
3128 content += _GetMSBuildConfigurationDetails(spec, project.build_file)
3129 content += _GetMSBuildLocalProperties(project.msbuild_toolset)
3130 content += import_cpp_props_section
3131 content += _GetMSBuildExtensions(props_files_of_rules)
3132 content += _GetMSBuildPropertySheets(configurations)
3133 content += macro_section
3134 content += _GetMSBuildConfigurationGlobalProperties(spec, configurations,
3136 content += _GetMSBuildToolSettingsSections(spec, configurations)
3137 content += _GetMSBuildSources(
3138 spec, sources, exclusions, extension_to_rule_name, actions_spec,
3139 sources_handled_by_action, list_excluded)
3140 content += _GetMSBuildProjectReferences(project)
3141 content += import_cpp_targets_section
3142 content += _GetMSBuildExtensionTargets(targets_files_of_rules)
3144 if spec.get('msvs_external_builder'):
3145 content += _GetMSBuildExternalBuilderTargets(spec)
3147 # TODO(jeanluc) File a bug to get rid of runas. We had in MSVS:
3148 # has_run_as = _WriteMSVSUserFile(project.path, version, spec)
3150 easy_xml.WriteXmlIfChanged(content, project.path, pretty=True, win32=True)
3152 return missing_sources
3155 def _GetMSBuildExternalBuilderTargets(spec):
3156 """Return a list of MSBuild targets for external builders.
3158 Right now, only "Build" and "Clean" targets are generated.
3161 spec: The gyp target spec.
3163 List of MSBuild 'Target' specs.
3165 build_cmd = _BuildCommandLineForRuleRaw(
3166 spec, spec['msvs_external_builder_build_cmd'],
3167 False, False, False, False)
3168 build_target = ['Target', {'Name': 'Build'}]
3169 build_target.append(['Exec', {'Command': build_cmd}])
3171 clean_cmd = _BuildCommandLineForRuleRaw(
3172 spec, spec['msvs_external_builder_clean_cmd'],
3173 False, False, False, False)
3174 clean_target = ['Target', {'Name': 'Clean'}]
3175 clean_target.append(['Exec', {'Command': clean_cmd}])
3177 return [build_target, clean_target]
3180 def _GetMSBuildExtensions(props_files_of_rules):
3181 extensions = ['ImportGroup', {'Label': 'ExtensionSettings'}]
3182 for props_file in props_files_of_rules:
3183 extensions.append(['Import', {'Project': props_file}])
3187 def _GetMSBuildExtensionTargets(targets_files_of_rules):
3188 targets_node = ['ImportGroup', {'Label': 'ExtensionTargets'}]
3189 for targets_file in sorted(targets_files_of_rules):
3190 targets_node.append(['Import', {'Project': targets_file}])
3191 return [targets_node]
3194 def _GenerateActionsForMSBuild(spec, actions_to_add):
3195 """Add actions accumulated into an actions_to_add, merging as needed.
3198 spec: the target project dict
3199 actions_to_add: dictionary keyed on input name, which maps to a list of
3200 dicts describing the actions attached to that input file.
3203 A pair of (action specification, the sources handled by this action).
3205 sources_handled_by_action = set()
3207 for primary_input, actions in actions_to_add.iteritems():
3212 for action in actions:
3213 inputs.update(set(action['inputs']))
3214 outputs.update(set(action['outputs']))
3215 descriptions.append(action['description'])
3216 cmd = action['command']
3217 # For most actions, add 'call' so that actions that invoke batch files
3218 # return and continue executing. msbuild_use_call provides a way to
3219 # disable this but I have not seen any adverse effect from doing that
3221 if action.get('msbuild_use_call', True):
3223 commands.append(cmd)
3224 # Add the custom build action for one input file.
3225 description = ', and also '.join(descriptions)
3227 # We can't join the commands simply with && because the command line will
3228 # get too long. See also _AddActions: cygwin's setup_env mustn't be called
3229 # for every invocation or the command that sets the PATH will grow too
3232 '\r\nif %errorlevel% neq 0 exit /b %errorlevel%\r\n'.join(commands))
3233 _AddMSBuildAction(spec,
3239 sources_handled_by_action,
3241 return actions_spec, sources_handled_by_action
3244 def _AddMSBuildAction(spec, primary_input, inputs, outputs, cmd, description,
3245 sources_handled_by_action, actions_spec):
3246 command = MSVSSettings.ConvertVCMacrosToMSBuild(cmd)
3247 primary_input = _FixPath(primary_input)
3248 inputs_array = _FixPaths(inputs)
3249 outputs_array = _FixPaths(outputs)
3250 additional_inputs = ';'.join([i for i in inputs_array
3251 if i != primary_input])
3252 outputs = ';'.join(outputs_array)
3253 sources_handled_by_action.add(primary_input)
3254 action_spec = ['CustomBuild', {'Include': primary_input}]
3256 # TODO(jeanluc) 'Document' for all or just if as_sources?
3257 [['FileType', 'Document'],
3258 ['Command', command],
3259 ['Message', description],
3260 ['Outputs', outputs]
3262 if additional_inputs:
3263 action_spec.append(['AdditionalInputs', additional_inputs])
3264 actions_spec.append(action_spec)