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.generator.ninja as ninja_generator
16 import gyp.MSVSNew as MSVSNew
17 import gyp.MSVSProject as MSVSProject
18 import gyp.MSVSSettings as MSVSSettings
19 import gyp.MSVSToolFile as MSVSToolFile
20 import gyp.MSVSUserFile as MSVSUserFile
21 import gyp.MSVSUtil as MSVSUtil
22 import gyp.MSVSVersion as MSVSVersion
23 from gyp.common import GypError
24 from gyp.common import OrderedSet
26 # TODO: Remove once bots are on 2.7, http://crbug.com/241769
27 def _import_OrderedDict():
30 return collections.OrderedDict
31 except AttributeError:
32 import gyp.ordered_dict
33 return gyp.ordered_dict.OrderedDict
34 OrderedDict = _import_OrderedDict()
37 # Regular expression for validating Visual Studio GUIDs. If the GUID
38 # contains lowercase hex letters, MSVS will be fine. However,
39 # IncrediBuild BuildConsole will parse the solution file, but then
40 # silently skip building the target causing hard to track down errors.
41 # Note that this only happens with the BuildConsole, and does not occur
42 # if IncrediBuild is executed from inside Visual Studio. This regex
43 # validates that the string looks like a GUID with all uppercase hex
45 VALID_MSVS_GUID_CHARS = re.compile('^[A-F0-9\-]+$')
48 generator_default_variables = {
49 'EXECUTABLE_PREFIX': '',
50 'EXECUTABLE_SUFFIX': '.exe',
51 'STATIC_LIB_PREFIX': '',
52 'SHARED_LIB_PREFIX': '',
53 'STATIC_LIB_SUFFIX': '.lib',
54 'SHARED_LIB_SUFFIX': '.dll',
55 'INTERMEDIATE_DIR': '$(IntDir)',
56 'SHARED_INTERMEDIATE_DIR': '$(OutDir)obj/global_intermediate',
58 'PRODUCT_DIR': '$(OutDir)',
59 'LIB_DIR': '$(OutDir)lib',
60 'RULE_INPUT_ROOT': '$(InputName)',
61 'RULE_INPUT_DIRNAME': '$(InputDir)',
62 'RULE_INPUT_EXT': '$(InputExt)',
63 'RULE_INPUT_NAME': '$(InputFileName)',
64 'RULE_INPUT_PATH': '$(InputPath)',
65 'CONFIGURATION_NAME': '$(ConfigurationName)',
69 # The msvs specific sections that hold paths
70 generator_additional_path_sections = [
76 generator_additional_non_configuration_keys = [
81 'msvs_external_builder',
82 'msvs_external_builder_out_dir',
83 'msvs_external_builder_build_cmd',
84 'msvs_external_builder_clean_cmd',
85 'msvs_external_builder_clcompile_cmd',
89 # List of precompiled header related keys.
91 'msvs_precompiled_header',
92 'msvs_precompiled_source',
96 cached_username = None
102 # TODO(gspencer): Switch the os.environ calls to be
103 # win32api.GetDomainName() and win32api.GetUserName() once the
104 # python version in depot_tools has been updated to work on Vista
106 def _GetDomainAndUserName():
107 if sys.platform not in ('win32', 'cygwin'):
108 return ('DOMAIN', 'USERNAME')
109 global cached_username
111 if not cached_domain or not cached_username:
112 domain = os.environ.get('USERDOMAIN')
113 username = os.environ.get('USERNAME')
114 if not domain or not username:
115 call = subprocess.Popen(['net', 'config', 'Workstation'],
116 stdout=subprocess.PIPE)
117 config = call.communicate()[0]
118 username_re = re.compile('^User name\s+(\S+)', re.MULTILINE)
119 username_match = username_re.search(config)
121 username = username_match.group(1)
122 domain_re = re.compile('^Logon domain\s+(\S+)', re.MULTILINE)
123 domain_match = domain_re.search(config)
125 domain = domain_match.group(1)
126 cached_domain = domain
127 cached_username = username
128 return (cached_domain, cached_username)
130 fixpath_prefix = None
133 def _NormalizedSource(source):
134 """Normalize the path.
136 But not if that gets rid of a variable, as this may expand to something
137 larger than one directory.
140 source: The path to be normalize.d
145 normalized = os.path.normpath(source)
146 if source.count('$') == normalized.count('$'):
152 """Convert paths to a form that will make sense in a vcproj file.
155 path: The path to convert, may contain / etc.
157 The path with all slashes made into backslashes.
159 if fixpath_prefix and path and not os.path.isabs(path) and not path[0] == '$':
160 path = os.path.join(fixpath_prefix, path)
161 path = path.replace('/', '\\')
162 path = _NormalizedSource(path)
163 if path and path[-1] == '\\':
168 def _FixPaths(paths):
169 """Fix each of the paths of the list."""
170 return [_FixPath(i) for i in paths]
173 def _ConvertSourcesToFilterHierarchy(sources, prefix=None, excluded=None,
174 list_excluded=True, msvs_version=None):
175 """Converts a list split source file paths into a vcproj folder hierarchy.
178 sources: A list of source file paths split.
179 prefix: A list of source file path layers meant to apply to each of sources.
180 excluded: A set of excluded files.
181 msvs_version: A MSVSVersion object.
184 A hierarchy of filenames and MSVSProject.Filter objects that matches the
185 layout of the source tree.
187 _ConvertSourcesToFilterHierarchy([['a', 'bob1.c'], ['b', 'bob2.c']],
190 [MSVSProject.Filter('a', contents=['joe\\a\\bob1.c']),
191 MSVSProject.Filter('b', contents=['joe\\b\\bob2.c'])]
193 if not prefix: prefix = []
196 folders = OrderedDict()
197 # Gather files into the final result, excluded, or folders.
200 filename = _NormalizedSource('\\'.join(prefix + s))
201 if filename in excluded:
202 excluded_result.append(filename)
204 result.append(filename)
205 elif msvs_version and not msvs_version.UsesVcxproj():
206 # For MSVS 2008 and earlier, we need to process all files before walking
208 if not folders.get(s[0]):
210 folders[s[0]].append(s[1:])
212 contents = _ConvertSourcesToFilterHierarchy([s[1:]], prefix + [s[0]],
214 list_excluded=list_excluded,
215 msvs_version=msvs_version)
216 contents = MSVSProject.Filter(s[0], contents=contents)
217 result.append(contents)
218 # Add a folder for excluded files.
219 if excluded_result and list_excluded:
220 excluded_folder = MSVSProject.Filter('_excluded_files',
221 contents=excluded_result)
222 result.append(excluded_folder)
224 if msvs_version and msvs_version.UsesVcxproj():
227 # Populate all the folders.
229 contents = _ConvertSourcesToFilterHierarchy(folders[f], prefix=prefix + [f],
231 list_excluded=list_excluded,
232 msvs_version=msvs_version)
233 contents = MSVSProject.Filter(f, contents=contents)
234 result.append(contents)
238 def _ToolAppend(tools, tool_name, setting, value, only_if_unset=False):
240 _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset)
243 def _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset=False):
244 # TODO(bradnelson): ugly hack, fix this more generally!!!
245 if 'Directories' in setting or 'Dependencies' in setting:
246 if type(value) == str:
247 value = value.replace('/', '\\')
249 value = [i.replace('/', '\\') for i in value]
250 if not tools.get(tool_name):
251 tools[tool_name] = dict()
252 tool = tools[tool_name]
253 if tool.get(setting):
254 if only_if_unset: return
255 if type(tool[setting]) == list and type(value) == list:
256 tool[setting] += value
259 'Appending "%s" to a non-list setting "%s" for tool "%s" is '
260 'not allowed, previous value: %s' % (
261 value, setting, tool_name, str(tool[setting])))
263 tool[setting] = value
266 def _ConfigPlatform(config_data):
267 return config_data.get('msvs_configuration_platform', 'Win32')
270 def _ConfigBaseName(config_name, platform_name):
271 if config_name.endswith('_' + platform_name):
272 return config_name[0:-len(platform_name) - 1]
277 def _ConfigFullName(config_name, config_data):
278 platform_name = _ConfigPlatform(config_data)
279 return '%s|%s' % (_ConfigBaseName(config_name, platform_name), platform_name)
282 def _BuildCommandLineForRuleRaw(spec, cmd, cygwin_shell, has_input_path,
283 quote_cmd, do_setup_env):
285 if [x for x in cmd if '$(InputDir)' in x]:
286 input_dir_preamble = (
287 'set INPUTDIR=$(InputDir)\n'
288 'if NOT DEFINED INPUTDIR set INPUTDIR=.\\\n'
289 'set INPUTDIR=%INPUTDIR:~0,-1%\n'
292 input_dir_preamble = ''
295 # Find path to cygwin.
296 cygwin_dir = _FixPath(spec.get('msvs_cygwin_dirs', ['.'])[0])
299 direct_cmd = [i.replace('$(IntDir)',
300 '`cygpath -m "${INTDIR}"`') for i in direct_cmd]
301 direct_cmd = [i.replace('$(OutDir)',
302 '`cygpath -m "${OUTDIR}"`') for i in direct_cmd]
303 direct_cmd = [i.replace('$(InputDir)',
304 '`cygpath -m "${INPUTDIR}"`') for i in direct_cmd]
306 direct_cmd = [i.replace('$(InputPath)',
307 '`cygpath -m "${INPUTPATH}"`')
309 direct_cmd = ['\\"%s\\"' % i.replace('"', '\\\\\\"') for i in direct_cmd]
310 # direct_cmd = gyp.common.EncodePOSIXShellList(direct_cmd)
311 direct_cmd = ' '.join(direct_cmd)
312 # TODO(quote): regularize quoting path names throughout the module
315 cmd += 'call "$(ProjectDir)%(cygwin_dir)s\\setup_env.bat" && '
316 cmd += 'set CYGWIN=nontsec&& '
317 if direct_cmd.find('NUMBER_OF_PROCESSORS') >= 0:
318 cmd += 'set /a NUMBER_OF_PROCESSORS_PLUS_1=%%NUMBER_OF_PROCESSORS%%+1&& '
319 if direct_cmd.find('INTDIR') >= 0:
320 cmd += 'set INTDIR=$(IntDir)&& '
321 if direct_cmd.find('OUTDIR') >= 0:
322 cmd += 'set OUTDIR=$(OutDir)&& '
323 if has_input_path and direct_cmd.find('INPUTPATH') >= 0:
324 cmd += 'set INPUTPATH=$(InputPath) && '
325 cmd += 'bash -c "%(cmd)s"'
326 cmd = cmd % {'cygwin_dir': cygwin_dir,
328 return input_dir_preamble + cmd
330 # Convert cat --> type to mimic unix.
334 command = [cmd[0].replace('/', '\\')]
335 # Add call before command to ensure that commands can be tied together one
336 # after the other without aborting in Incredibuild, since IB makes a bat
337 # file out of the raw command string, and some commands (like python) are
338 # actually batch files themselves.
339 command.insert(0, 'call')
341 # TODO(quote): This is a really ugly heuristic, and will miss path fixing
342 # for arguments like "--arg=path" or "/opt:path".
343 # If the argument starts with a slash or dash, it's probably a command line
345 arguments = [i if (i[:1] in "/-") else _FixPath(i) for i in cmd[1:]]
346 arguments = [i.replace('$(InputDir)', '%INPUTDIR%') for i in arguments]
347 arguments = [MSVSSettings.FixVCMacroSlashes(i) for i in arguments]
349 # Support a mode for using cmd directly.
350 # Convert any paths to native form (first element is used directly).
351 # TODO(quote): regularize quoting path names throughout the module
352 arguments = ['"%s"' % i for i in arguments]
353 # Collapse into a single command.
354 return input_dir_preamble + ' '.join(command + arguments)
357 def _BuildCommandLineForRule(spec, rule, has_input_path, do_setup_env):
358 # Currently this weird argument munging is used to duplicate the way a
359 # python script would need to be run as part of the chrome tree.
360 # Eventually we should add some sort of rule_default option to set this
361 # per project. For now the behavior chrome needs is the default.
362 mcs = rule.get('msvs_cygwin_shell')
364 mcs = int(spec.get('msvs_cygwin_shell', 1))
365 elif isinstance(mcs, str):
367 quote_cmd = int(rule.get('msvs_quote_cmd', 1))
368 return _BuildCommandLineForRuleRaw(spec, rule['action'], mcs, has_input_path,
369 quote_cmd, do_setup_env=do_setup_env)
372 def _AddActionStep(actions_dict, inputs, outputs, description, command):
373 """Merge action into an existing list of actions.
375 Care must be taken so that actions which have overlapping inputs either don't
376 get assigned to the same input, or get collapsed into one.
379 actions_dict: dictionary keyed on input name, which maps to a list of
380 dicts describing the actions attached to that input file.
381 inputs: list of inputs
382 outputs: list of outputs
383 description: description of the action
384 command: command line to execute
386 # Require there to be at least one input (call sites will ensure this).
392 'description': description,
396 # Pick where to stick this action.
397 # While less than optimal in terms of build time, attach them to the first
399 chosen_input = inputs[0]
402 if chosen_input not in actions_dict:
403 actions_dict[chosen_input] = []
404 actions_dict[chosen_input].append(action)
407 def _AddCustomBuildToolForMSVS(p, spec, primary_input,
408 inputs, outputs, description, cmd):
409 """Add a custom build tool to execute something.
412 p: the target project
413 spec: the target project dict
414 primary_input: input file to attach the build tool to
415 inputs: list of inputs
416 outputs: list of outputs
417 description: description of the action
418 cmd: command line to execute
420 inputs = _FixPaths(inputs)
421 outputs = _FixPaths(outputs)
422 tool = MSVSProject.Tool(
424 {'Description': description,
425 'AdditionalDependencies': ';'.join(inputs),
426 'Outputs': ';'.join(outputs),
429 # Add to the properties of primary input for each config.
430 for config_name, c_data in spec['configurations'].iteritems():
431 p.AddFileConfig(_FixPath(primary_input),
432 _ConfigFullName(config_name, c_data), tools=[tool])
435 def _AddAccumulatedActionsToMSVS(p, spec, actions_dict):
436 """Add actions accumulated into an actions_dict, merging as needed.
439 p: the target project
440 spec: the target project dict
441 actions_dict: dictionary keyed on input name, which maps to a list of
442 dicts describing the actions attached to that input file.
444 for primary_input in actions_dict:
445 inputs = OrderedSet()
446 outputs = OrderedSet()
449 for action in actions_dict[primary_input]:
450 inputs.update(OrderedSet(action['inputs']))
451 outputs.update(OrderedSet(action['outputs']))
452 descriptions.append(action['description'])
453 commands.append(action['command'])
454 # Add the custom build step for one input file.
455 description = ', and also '.join(descriptions)
456 command = '\r\n'.join(commands)
457 _AddCustomBuildToolForMSVS(p, spec,
458 primary_input=primary_input,
461 description=description,
465 def _RuleExpandPath(path, input_file):
466 """Given the input file to which a rule applied, string substitute a path.
469 path: a path to string expand
470 input_file: the file to which the rule applied.
472 The string substituted path.
474 path = path.replace('$(InputName)',
475 os.path.splitext(os.path.split(input_file)[1])[0])
476 path = path.replace('$(InputDir)', os.path.dirname(input_file))
477 path = path.replace('$(InputExt)',
478 os.path.splitext(os.path.split(input_file)[1])[1])
479 path = path.replace('$(InputFileName)', os.path.split(input_file)[1])
480 path = path.replace('$(InputPath)', input_file)
484 def _FindRuleTriggerFiles(rule, sources):
485 """Find the list of files which a particular rule applies to.
488 rule: the rule in question
489 sources: the set of all known source files for this project
491 The list of sources that trigger a particular rule.
493 return rule.get('rule_sources', [])
496 def _RuleInputsAndOutputs(rule, trigger_file):
497 """Find the inputs and outputs generated by a rule.
500 rule: the rule in question.
501 trigger_file: the main trigger for this rule.
503 The pair of (inputs, outputs) involved in this rule.
505 raw_inputs = _FixPaths(rule.get('inputs', []))
506 raw_outputs = _FixPaths(rule.get('outputs', []))
507 inputs = OrderedSet()
508 outputs = OrderedSet()
509 inputs.add(trigger_file)
511 inputs.add(_RuleExpandPath(i, trigger_file))
512 for o in raw_outputs:
513 outputs.add(_RuleExpandPath(o, trigger_file))
514 return (inputs, outputs)
517 def _GenerateNativeRulesForMSVS(p, rules, output_dir, spec, options):
518 """Generate a native rules file.
521 p: the target project
522 rules: the set of rules to include
523 output_dir: the directory in which the project/gyp resides
524 spec: the project dict
525 options: global generator options
527 rules_filename = '%s%s.rules' % (spec['target_name'],
529 rules_file = MSVSToolFile.Writer(os.path.join(output_dir, rules_filename),
533 rule_name = r['rule_name']
534 rule_ext = r['extension']
535 inputs = _FixPaths(r.get('inputs', []))
536 outputs = _FixPaths(r.get('outputs', []))
537 # Skip a rule with no action and no inputs.
538 if 'action' not in r and not r.get('rule_sources', []):
540 cmd = _BuildCommandLineForRule(spec, r, has_input_path=True,
542 rules_file.AddCustomBuildRule(name=rule_name,
543 description=r.get('message', rule_name),
544 extensions=[rule_ext],
545 additional_dependencies=inputs,
548 # Write out rules file.
549 rules_file.WriteIfChanged()
551 # Add rules file to project.
552 p.AddToolFile(rules_filename)
555 def _Cygwinify(path):
556 path = path.replace('$(OutDir)', '$(OutDirCygwin)')
557 path = path.replace('$(IntDir)', '$(IntDirCygwin)')
561 def _GenerateExternalRules(rules, output_dir, spec,
562 sources, options, actions_to_add):
563 """Generate an external makefile to do a set of rules.
566 rules: the list of rules to include
567 output_dir: path containing project and gyp files
568 spec: project specification data
569 sources: set of sources known
570 options: global generator options
571 actions_to_add: The list of actions we will add to.
573 filename = '%s_rules%s.mk' % (spec['target_name'], options.suffix)
574 mk_file = gyp.common.WriteOnDiff(os.path.join(output_dir, filename))
575 # Find cygwin style versions of some paths.
576 mk_file.write('OutDirCygwin:=$(shell cygpath -u "$(OutDir)")\n')
577 mk_file.write('IntDirCygwin:=$(shell cygpath -u "$(IntDir)")\n')
578 # Gather stuff needed to emit all: target.
579 all_inputs = OrderedSet()
580 all_outputs = OrderedSet()
581 all_output_dirs = OrderedSet()
584 trigger_files = _FindRuleTriggerFiles(rule, sources)
585 for tf in trigger_files:
586 inputs, outputs = _RuleInputsAndOutputs(rule, tf)
587 all_inputs.update(OrderedSet(inputs))
588 all_outputs.update(OrderedSet(outputs))
589 # Only use one target from each rule as the dependency for
590 # 'all' so we don't try to build each rule multiple times.
591 first_outputs.append(list(outputs)[0])
592 # Get the unique output directories for this rule.
593 output_dirs = [os.path.split(i)[0] for i in outputs]
594 for od in output_dirs:
595 all_output_dirs.add(od)
596 first_outputs_cyg = [_Cygwinify(i) for i in first_outputs]
597 # Write out all: target, including mkdir for each output directory.
598 mk_file.write('all: %s\n' % ' '.join(first_outputs_cyg))
599 for od in all_output_dirs:
601 mk_file.write('\tmkdir -p `cygpath -u "%s"`\n' % od)
603 # Define how each output is generated.
605 trigger_files = _FindRuleTriggerFiles(rule, sources)
606 for tf in trigger_files:
607 # Get all the inputs and outputs for this rule for this trigger file.
608 inputs, outputs = _RuleInputsAndOutputs(rule, tf)
609 inputs = [_Cygwinify(i) for i in inputs]
610 outputs = [_Cygwinify(i) for i in outputs]
611 # Prepare the command line for this rule.
612 cmd = [_RuleExpandPath(c, tf) for c in rule['action']]
613 cmd = ['"%s"' % i for i in cmd]
615 # Add it to the makefile.
616 mk_file.write('%s: %s\n' % (' '.join(outputs), ' '.join(inputs)))
617 mk_file.write('\t%s\n\n' % cmd)
621 # Add makefile to list of sources.
622 sources.add(filename)
623 # Add a build action to call makefile.
627 '-j', '${NUMBER_OF_PROCESSORS_PLUS_1}',
629 cmd = _BuildCommandLineForRuleRaw(spec, cmd, True, False, True, True)
630 # Insert makefile as 0'th input, so it gets the action attached there,
631 # as this is easier to understand from in the IDE.
632 all_inputs = list(all_inputs)
633 all_inputs.insert(0, filename)
634 _AddActionStep(actions_to_add,
635 inputs=_FixPaths(all_inputs),
636 outputs=_FixPaths(all_outputs),
637 description='Running external rules for %s' %
642 def _EscapeEnvironmentVariableExpansion(s):
643 """Escapes % characters.
645 Escapes any % characters so that Windows-style environment variable
646 expansions will leave them alone.
647 See http://connect.microsoft.com/VisualStudio/feedback/details/106127/cl-d-name-text-containing-percentage-characters-doesnt-compile
648 to understand why we have to do this.
651 s: The string to be escaped.
656 s = s.replace('%', '%%')
660 quote_replacer_regex = re.compile(r'(\\*)"')
663 def _EscapeCommandLineArgumentForMSVS(s):
664 """Escapes a Windows command-line argument.
666 So that the Win32 CommandLineToArgv function will turn the escaped result back
667 into the original string.
668 See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
669 ("Parsing C++ Command-Line Arguments") to understand why we have to do
673 s: the string to be escaped.
679 # For a literal quote, CommandLineToArgv requires an odd number of
680 # backslashes preceding it, and it produces half as many literal backslashes
681 # (rounded down). So we need to produce 2n+1 backslashes.
682 return 2 * match.group(1) + '\\"'
684 # Escape all quotes so that they are interpreted literally.
685 s = quote_replacer_regex.sub(_Replace, s)
686 # Now add unescaped quotes so that any whitespace is interpreted literally.
691 delimiters_replacer_regex = re.compile(r'(\\*)([,;]+)')
694 def _EscapeVCProjCommandLineArgListItem(s):
695 """Escapes command line arguments for MSVS.
697 The VCProj format stores string lists in a single string using commas and
698 semi-colons as separators, which must be quoted if they are to be
699 interpreted literally. However, command-line arguments may already have
700 quotes, and the VCProj parser is ignorant of the backslash escaping
701 convention used by CommandLineToArgv, so the command-line quotes and the
702 VCProj quotes may not be the same quotes. So to store a general
703 command-line argument in a VCProj list, we need to parse the existing
704 quoting according to VCProj's convention and quote any delimiters that are
705 not already quoted by that convention. The quotes that we add will also be
706 seen by CommandLineToArgv, so if backslashes precede them then we also have
707 to escape those backslashes according to the CommandLineToArgv
711 s: the string to be escaped.
717 # For a non-literal quote, CommandLineToArgv requires an even number of
718 # backslashes preceding it, and it produces half as many literal
719 # backslashes. So we need to produce 2n backslashes.
720 return 2 * match.group(1) + '"' + match.group(2) + '"'
722 segments = s.split('"')
723 # The unquoted segments are at the even-numbered indices.
724 for i in range(0, len(segments), 2):
725 segments[i] = delimiters_replacer_regex.sub(_Replace, segments[i])
726 # Concatenate back into a single string
727 s = '"'.join(segments)
728 if len(segments) % 2 == 0:
729 # String ends while still quoted according to VCProj's convention. This
730 # means the delimiter and the next list item that follow this one in the
731 # .vcproj file will be misinterpreted as part of this item. There is nothing
732 # we can do about this. Adding an extra quote would correct the problem in
733 # the VCProj but cause the same problem on the final command-line. Moving
734 # the item to the end of the list does works, but that's only possible if
735 # there's only one such item. Let's just warn the user.
736 print >> sys.stderr, ('Warning: MSVS may misinterpret the odd number of ' +
741 def _EscapeCppDefineForMSVS(s):
742 """Escapes a CPP define so that it will reach the compiler unaltered."""
743 s = _EscapeEnvironmentVariableExpansion(s)
744 s = _EscapeCommandLineArgumentForMSVS(s)
745 s = _EscapeVCProjCommandLineArgListItem(s)
746 # cl.exe replaces literal # characters with = in preprocesor definitions for
747 # some reason. Octal-encode to work around that.
748 s = s.replace('#', '\\%03o' % ord('#'))
752 quote_replacer_regex2 = re.compile(r'(\\+)"')
755 def _EscapeCommandLineArgumentForMSBuild(s):
756 """Escapes a Windows command-line argument for use by MSBuild."""
759 return (len(match.group(1)) / 2 * 4) * '\\' + '\\"'
761 # Escape all quotes so that they are interpreted literally.
762 s = quote_replacer_regex2.sub(_Replace, s)
766 def _EscapeMSBuildSpecialCharacters(s):
767 escape_dictionary = {
776 result = ''.join([escape_dictionary.get(c, c) for c in s])
780 def _EscapeCppDefineForMSBuild(s):
781 """Escapes a CPP define so that it will reach the compiler unaltered."""
782 s = _EscapeEnvironmentVariableExpansion(s)
783 s = _EscapeCommandLineArgumentForMSBuild(s)
784 s = _EscapeMSBuildSpecialCharacters(s)
785 # cl.exe replaces literal # characters with = in preprocesor definitions for
786 # some reason. Octal-encode to work around that.
787 s = s.replace('#', '\\%03o' % ord('#'))
791 def _GenerateRulesForMSVS(p, output_dir, options, spec,
792 sources, excluded_sources,
794 """Generate all the rules for a particular project.
798 output_dir: directory to emit rules to
799 options: global options passed to the generator
800 spec: the specification for this project
801 sources: the set of all known source files in this project
802 excluded_sources: the set of sources excluded from normal processing
803 actions_to_add: deferred list of actions to add in
805 rules = spec.get('rules', [])
806 rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
807 rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
809 # Handle rules that use a native rules file.
811 _GenerateNativeRulesForMSVS(p, rules_native, output_dir, spec, options)
813 # Handle external rules (non-native rules).
815 _GenerateExternalRules(rules_external, output_dir, spec,
816 sources, options, actions_to_add)
817 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
820 def _AdjustSourcesForRules(spec, rules, sources, excluded_sources):
821 # Add outputs generated by each rule (if applicable).
823 # Add in the outputs from this rule.
824 trigger_files = _FindRuleTriggerFiles(rule, sources)
825 for trigger_file in trigger_files:
826 # Remove trigger_file from excluded_sources to let the rule be triggered
827 # (e.g. rule trigger ax_enums.idl is added to excluded_sources
828 # because it's also in an action's inputs in the same project)
829 excluded_sources.discard(_FixPath(trigger_file))
830 # Done if not processing outputs as sources.
831 if int(rule.get('process_outputs_as_sources', False)):
832 inputs, outputs = _RuleInputsAndOutputs(rule, trigger_file)
833 inputs = OrderedSet(_FixPaths(inputs))
834 outputs = OrderedSet(_FixPaths(outputs))
835 inputs.remove(_FixPath(trigger_file))
836 sources.update(inputs)
837 if not spec.get('msvs_external_builder'):
838 excluded_sources.update(inputs)
839 sources.update(outputs)
842 def _FilterActionsFromExcluded(excluded_sources, actions_to_add):
843 """Take inputs with actions attached out of the list of exclusions.
846 excluded_sources: list of source files not to be built.
847 actions_to_add: dict of actions keyed on source file they're attached to.
849 excluded_sources with files that have actions attached removed.
851 must_keep = OrderedSet(_FixPaths(actions_to_add.keys()))
852 return [s for s in excluded_sources if s not in must_keep]
855 def _GetDefaultConfiguration(spec):
856 return spec['configurations'][spec['default_configuration']]
859 def _GetGuidOfProject(proj_path, spec):
860 """Get the guid for the project.
863 proj_path: Path of the vcproj or vcxproj file to generate.
864 spec: The target dictionary containing the properties of the target.
868 ValueError: if the specified GUID is invalid.
870 # Pluck out the default configuration.
871 default_config = _GetDefaultConfiguration(spec)
872 # Decide the guid of the project.
873 guid = default_config.get('msvs_guid')
875 if VALID_MSVS_GUID_CHARS.match(guid) is None:
876 raise ValueError('Invalid MSVS guid: "%s". Must match regex: "%s".' %
877 (guid, VALID_MSVS_GUID_CHARS.pattern))
879 guid = guid or MSVSNew.MakeGuid(proj_path)
883 def _GetMsbuildToolsetOfProject(proj_path, spec, version):
884 """Get the platform toolset for the project.
887 proj_path: Path of the vcproj or vcxproj file to generate.
888 spec: The target dictionary containing the properties of the target.
889 version: The MSVSVersion object.
891 the platform toolset string or None.
893 # Pluck out the default configuration.
894 default_config = _GetDefaultConfiguration(spec)
895 toolset = default_config.get('msbuild_toolset')
896 if not toolset and version.DefaultToolset():
897 toolset = version.DefaultToolset()
901 def _GenerateProject(project, options, version, generator_flags):
902 """Generates a vcproj file.
905 project: the MSVSProject object.
906 options: global generator options.
907 version: the MSVSVersion object.
908 generator_flags: dict of generator-specific flags.
910 A list of source files that cannot be found on disk.
912 default_config = _GetDefaultConfiguration(project.spec)
914 # Skip emitting anything if told to with msvs_existing_vcproj option.
915 if default_config.get('msvs_existing_vcproj'):
918 if version.UsesVcxproj():
919 return _GenerateMSBuildProject(project, options, version, generator_flags)
921 return _GenerateMSVSProject(project, options, version, generator_flags)
924 # TODO: Avoid code duplication with _ValidateSourcesForOSX in make.py.
925 def _ValidateSourcesForMSVSProject(spec, version):
926 """Makes sure if duplicate basenames are not specified in the source list.
929 spec: The target dictionary containing the properties of the target.
930 version: The VisualStudioVersion object.
932 # This validation should not be applied to MSVC2010 and later.
933 assert not version.UsesVcxproj()
935 # TODO: Check if MSVC allows this for loadable_module targets.
936 if spec.get('type', None) not in ('static_library', 'shared_library'):
938 sources = spec.get('sources', [])
940 for source in sources:
941 name, ext = os.path.splitext(source)
942 is_compiled_file = ext in [
943 '.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.s', '.S']
944 if not is_compiled_file:
946 basename = os.path.basename(name) # Don't include extension.
947 basenames.setdefault(basename, []).append(source)
950 for basename, files in basenames.iteritems():
952 error += ' %s: %s\n' % (basename, ' '.join(files))
955 print('static library %s has several files with the same basename:\n' %
956 spec['target_name'] + error + 'MSVC08 cannot handle that.')
957 raise GypError('Duplicate basenames in sources section, see list above')
960 def _GenerateMSVSProject(project, options, version, generator_flags):
961 """Generates a .vcproj file. It may create .rules and .user files too.
964 project: The project object we will generate the file for.
965 options: Global options passed to the generator.
966 version: The VisualStudioVersion object.
967 generator_flags: dict of generator-specific flags.
970 gyp.common.EnsureDirExists(project.path)
972 platforms = _GetUniquePlatforms(spec)
973 p = MSVSProject.Writer(project.path, version, spec['target_name'],
974 project.guid, platforms)
976 # Get directory project file is in.
977 project_dir = os.path.split(project.path)[0]
978 gyp_path = _NormalizedSource(project.build_file)
979 relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
981 config_type = _GetMSVSConfigurationType(spec, project.build_file)
982 for config_name, config in spec['configurations'].iteritems():
983 _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config)
985 # MSVC08 and prior version cannot handle duplicate basenames in the same
987 # TODO: Take excluded sources into consideration if possible.
988 _ValidateSourcesForMSVSProject(spec, version)
990 # Prepare list of sources and excluded sources.
991 gyp_file = os.path.split(project.build_file)[1]
992 sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
997 _GenerateRulesForMSVS(p, project_dir, options, spec,
998 sources, excluded_sources,
1000 list_excluded = generator_flags.get('msvs_list_excluded_files', True)
1001 sources, excluded_sources, excluded_idl = (
1002 _AdjustSourcesAndConvertToFilterHierarchy(spec, options, project_dir,
1003 sources, excluded_sources,
1004 list_excluded, version))
1007 missing_sources = _VerifySourcesExist(sources, project_dir)
1010 _AddToolFilesToMSVS(p, spec)
1011 _HandlePreCompiledHeaders(p, sources, spec)
1012 _AddActions(actions_to_add, spec, relative_path_of_gyp_file)
1013 _AddCopies(actions_to_add, spec)
1014 _WriteMSVSUserFile(project.path, version, spec)
1016 # NOTE: this stanza must appear after all actions have been decided.
1017 # Don't excluded sources with actions attached, or they won't run.
1018 excluded_sources = _FilterActionsFromExcluded(
1019 excluded_sources, actions_to_add)
1020 _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
1022 _AddAccumulatedActionsToMSVS(p, spec, actions_to_add)
1027 return missing_sources
1030 def _GetUniquePlatforms(spec):
1031 """Returns the list of unique platforms for this spec, e.g ['win32', ...].
1034 spec: The target dictionary containing the properties of the target.
1036 The MSVSUserFile object created.
1038 # Gather list of unique platforms.
1039 platforms = OrderedSet()
1040 for configuration in spec['configurations']:
1041 platforms.add(_ConfigPlatform(spec['configurations'][configuration]))
1042 platforms = list(platforms)
1046 def _CreateMSVSUserFile(proj_path, version, spec):
1047 """Generates a .user file for the user running this Gyp program.
1050 proj_path: The path of the project file being created. The .user file
1051 shares the same path (with an appropriate suffix).
1052 version: The VisualStudioVersion object.
1053 spec: The target dictionary containing the properties of the target.
1055 The MSVSUserFile object created.
1057 (domain, username) = _GetDomainAndUserName()
1058 vcuser_filename = '.'.join([proj_path, domain, username, 'user'])
1059 user_file = MSVSUserFile.Writer(vcuser_filename, version,
1060 spec['target_name'])
1064 def _GetMSVSConfigurationType(spec, build_file):
1065 """Returns the configuration type for this project.
1067 It's a number defined by Microsoft. May raise an exception.
1070 spec: The target dictionary containing the properties of the target.
1071 build_file: The path of the gyp file.
1073 An integer, the configuration type.
1077 'executable': '1', # .exe
1078 'shared_library': '2', # .dll
1079 'loadable_module': '2', # .dll
1080 'static_library': '4', # .lib
1081 'none': '10', # Utility type
1084 if spec.get('type'):
1085 raise GypError('Target type %s is not a valid target type for '
1086 'target %s in %s.' %
1087 (spec['type'], spec['target_name'], build_file))
1089 raise GypError('Missing type field for target %s in %s.' %
1090 (spec['target_name'], build_file))
1094 def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config):
1095 """Adds a configuration to the MSVS project.
1097 Many settings in a vcproj file are specific to a configuration. This
1098 function the main part of the vcproj file that's configuration specific.
1101 p: The target project being generated.
1102 spec: The target dictionary containing the properties of the target.
1103 config_type: The configuration type, a number as defined by Microsoft.
1104 config_name: The name of the configuration.
1105 config: The dictionary that defines the special processing to be done
1106 for this configuration.
1108 # Get the information for this configuration
1109 include_dirs, resource_include_dirs = _GetIncludeDirs(config)
1110 libraries = _GetLibraries(spec)
1111 library_dirs = _GetLibraryDirs(config)
1112 out_file, vc_tool, _ = _GetOutputFilePathAndTool(spec, msbuild=False)
1113 defines = _GetDefines(config)
1114 defines = [_EscapeCppDefineForMSVS(d) for d in defines]
1115 disabled_warnings = _GetDisabledWarnings(config)
1116 prebuild = config.get('msvs_prebuild')
1117 postbuild = config.get('msvs_postbuild')
1118 def_file = _GetModuleDefinition(spec)
1119 precompiled_header = config.get('msvs_precompiled_header')
1121 # Prepare the list of tools as a dictionary.
1123 # Add in user specified msvs_settings.
1124 msvs_settings = config.get('msvs_settings', {})
1125 MSVSSettings.ValidateMSVSSettings(msvs_settings)
1127 # Prevent default library inheritance from the environment.
1128 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', ['$(NOINHERIT)'])
1130 for tool in msvs_settings:
1131 settings = config['msvs_settings'][tool]
1132 for setting in settings:
1133 _ToolAppend(tools, tool, setting, settings[setting])
1134 # Add the information to the appropriate tool
1135 _ToolAppend(tools, 'VCCLCompilerTool',
1136 'AdditionalIncludeDirectories', include_dirs)
1137 _ToolAppend(tools, 'VCResourceCompilerTool',
1138 'AdditionalIncludeDirectories', resource_include_dirs)
1140 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', libraries)
1141 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalLibraryDirectories',
1144 _ToolAppend(tools, vc_tool, 'OutputFile', out_file, only_if_unset=True)
1146 _ToolAppend(tools, 'VCCLCompilerTool', 'PreprocessorDefinitions', defines)
1147 _ToolAppend(tools, 'VCResourceCompilerTool', 'PreprocessorDefinitions',
1149 # Change program database directory to prevent collisions.
1150 _ToolAppend(tools, 'VCCLCompilerTool', 'ProgramDataBaseFileName',
1151 '$(IntDir)$(ProjectName)\\vc80.pdb', only_if_unset=True)
1152 # Add disabled warnings.
1153 _ToolAppend(tools, 'VCCLCompilerTool',
1154 'DisableSpecificWarnings', disabled_warnings)
1156 _ToolAppend(tools, 'VCPreBuildEventTool', 'CommandLine', prebuild)
1158 _ToolAppend(tools, 'VCPostBuildEventTool', 'CommandLine', postbuild)
1159 # Turn on precompiled headers if appropriate.
1160 if precompiled_header:
1161 precompiled_header = os.path.split(precompiled_header)[1]
1162 _ToolAppend(tools, 'VCCLCompilerTool', 'UsePrecompiledHeader', '2')
1163 _ToolAppend(tools, 'VCCLCompilerTool',
1164 'PrecompiledHeaderThrough', precompiled_header)
1165 _ToolAppend(tools, 'VCCLCompilerTool',
1166 'ForcedIncludeFiles', precompiled_header)
1167 # Loadable modules don't generate import libraries;
1168 # tell dependent projects to not expect one.
1169 if spec['type'] == 'loadable_module':
1170 _ToolAppend(tools, 'VCLinkerTool', 'IgnoreImportLibrary', 'true')
1171 # Set the module definition file if any.
1173 _ToolAppend(tools, 'VCLinkerTool', 'ModuleDefinitionFile', def_file)
1175 _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name)
1178 def _GetIncludeDirs(config):
1179 """Returns the list of directories to be used for #include directives.
1182 config: The dictionary that defines the special processing to be done
1183 for this configuration.
1185 The list of directory paths.
1187 # TODO(bradnelson): include_dirs should really be flexible enough not to
1188 # require this sort of thing.
1190 config.get('include_dirs', []) +
1191 config.get('msvs_system_include_dirs', []))
1192 resource_include_dirs = config.get('resource_include_dirs', include_dirs)
1193 include_dirs = _FixPaths(include_dirs)
1194 resource_include_dirs = _FixPaths(resource_include_dirs)
1195 return include_dirs, resource_include_dirs
1198 def _GetLibraryDirs(config):
1199 """Returns the list of directories to be used for library search paths.
1202 config: The dictionary that defines the special processing to be done
1203 for this configuration.
1205 The list of directory paths.
1208 library_dirs = config.get('library_dirs', [])
1209 library_dirs = _FixPaths(library_dirs)
1213 def _GetLibraries(spec):
1214 """Returns the list of libraries for this configuration.
1217 spec: The target dictionary containing the properties of the target.
1219 The list of directory paths.
1221 libraries = spec.get('libraries', [])
1222 # Strip out -l, as it is not used on windows (but is needed so we can pass
1223 # in libraries that are assumed to be in the default library path).
1224 # Also remove duplicate entries, leaving only the last duplicate, while
1226 found = OrderedSet()
1227 unique_libraries_list = []
1228 for entry in reversed(libraries):
1229 library = re.sub('^\-l', '', entry)
1230 if not os.path.splitext(library)[1]:
1232 if library not in found:
1234 unique_libraries_list.append(library)
1235 unique_libraries_list.reverse()
1236 return unique_libraries_list
1239 def _GetOutputFilePathAndTool(spec, msbuild):
1240 """Returns the path and tool to use for this target.
1242 Figures out the path of the file this spec will create and the name of
1243 the VC tool that will create it.
1246 spec: The target dictionary containing the properties of the target.
1248 A triple of (file path, name of the vc tool, name of the msbuild tool)
1250 # Select a name for the output file.
1255 'executable': ('VCLinkerTool', 'Link', '$(OutDir)', '.exe'),
1256 'shared_library': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
1257 'loadable_module': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
1258 'static_library': ('VCLibrarianTool', 'Lib', '$(OutDir)lib\\', '.lib'),
1260 output_file_props = output_file_map.get(spec['type'])
1261 if output_file_props and int(spec.get('msvs_auto_output_file', 1)):
1262 vc_tool, msbuild_tool, out_dir, suffix = output_file_props
1263 if spec.get('standalone_static_library', 0):
1264 out_dir = '$(OutDir)'
1265 out_dir = spec.get('product_dir', out_dir)
1266 product_extension = spec.get('product_extension')
1267 if product_extension:
1268 suffix = '.' + product_extension
1270 suffix = '$(TargetExt)'
1271 prefix = spec.get('product_prefix', '')
1272 product_name = spec.get('product_name', '$(ProjectName)')
1273 out_file = ntpath.join(out_dir, prefix + product_name + suffix)
1274 return out_file, vc_tool, msbuild_tool
1277 def _GetOutputTargetExt(spec):
1278 """Returns the extension for this target, including the dot
1280 If product_extension is specified, set target_extension to this to avoid
1281 MSB8012, returns None otherwise. Ignores any target_extension settings in
1285 spec: The target dictionary containing the properties of the target.
1287 A string with the extension, or None
1289 target_extension = spec.get('product_extension')
1290 if target_extension:
1291 return '.' + target_extension
1295 def _GetDefines(config):
1296 """Returns the list of preprocessor definitions for this configuation.
1299 config: The dictionary that defines the special processing to be done
1300 for this configuration.
1302 The list of preprocessor definitions.
1305 for d in config.get('defines', []):
1307 fd = '='.join([str(dpart) for dpart in d])
1314 def _GetDisabledWarnings(config):
1315 return [str(i) for i in config.get('msvs_disabled_warnings', [])]
1318 def _GetModuleDefinition(spec):
1320 if spec['type'] in ['shared_library', 'loadable_module', 'executable']:
1321 def_files = [s for s in spec.get('sources', []) if s.endswith('.def')]
1322 if len(def_files) == 1:
1323 def_file = _FixPath(def_files[0])
1326 'Multiple module definition files in one target, target %s lists '
1327 'multiple .def files: %s' % (
1328 spec['target_name'], ' '.join(def_files)))
1332 def _ConvertToolsToExpectedForm(tools):
1333 """Convert tools to a form expected by Visual Studio.
1336 tools: A dictionary of settings; the tool name is the key.
1338 A list of Tool objects.
1341 for tool, settings in tools.iteritems():
1342 # Collapse settings with lists.
1344 for setting, value in settings.iteritems():
1345 if type(value) == list:
1346 if ((tool == 'VCLinkerTool' and
1347 setting == 'AdditionalDependencies') or
1348 setting == 'AdditionalOptions'):
1349 settings_fixed[setting] = ' '.join(value)
1351 settings_fixed[setting] = ';'.join(value)
1353 settings_fixed[setting] = value
1355 tool_list.append(MSVSProject.Tool(tool, settings_fixed))
1359 def _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name):
1360 """Add to the project file the configuration specified by config.
1363 p: The target project being generated.
1364 spec: the target project dict.
1365 tools: A dictionary of settings; the tool name is the key.
1366 config: The dictionary that defines the special processing to be done
1367 for this configuration.
1368 config_type: The configuration type, a number as defined by Microsoft.
1369 config_name: The name of the configuration.
1371 attributes = _GetMSVSAttributes(spec, config, config_type)
1372 # Add in this configuration.
1373 tool_list = _ConvertToolsToExpectedForm(tools)
1374 p.AddConfig(_ConfigFullName(config_name, config),
1375 attrs=attributes, tools=tool_list)
1378 def _GetMSVSAttributes(spec, config, config_type):
1379 # Prepare configuration attributes.
1381 source_attrs = config.get('msvs_configuration_attributes', {})
1382 for a in source_attrs:
1383 prepared_attrs[a] = source_attrs[a]
1385 vsprops_dirs = config.get('msvs_props', [])
1386 vsprops_dirs = _FixPaths(vsprops_dirs)
1388 prepared_attrs['InheritedPropertySheets'] = ';'.join(vsprops_dirs)
1389 # Set configuration type.
1390 prepared_attrs['ConfigurationType'] = config_type
1391 output_dir = prepared_attrs.get('OutputDirectory',
1392 '$(SolutionDir)$(ConfigurationName)')
1393 prepared_attrs['OutputDirectory'] = _FixPath(output_dir) + '\\'
1394 if 'IntermediateDirectory' not in prepared_attrs:
1395 intermediate = '$(ConfigurationName)\\obj\\$(ProjectName)'
1396 prepared_attrs['IntermediateDirectory'] = _FixPath(intermediate) + '\\'
1398 intermediate = _FixPath(prepared_attrs['IntermediateDirectory']) + '\\'
1399 intermediate = MSVSSettings.FixVCMacroSlashes(intermediate)
1400 prepared_attrs['IntermediateDirectory'] = intermediate
1401 return prepared_attrs
1404 def _AddNormalizedSources(sources_set, sources_array):
1405 sources_set.update(_NormalizedSource(s) for s in sources_array)
1408 def _PrepareListOfSources(spec, generator_flags, gyp_file):
1409 """Prepare list of sources and excluded sources.
1411 Besides the sources specified directly in the spec, adds the gyp file so
1412 that a change to it will cause a re-compile. Also adds appropriate sources
1413 for actions and copies. Assumes later stage will un-exclude files which
1414 have custom build steps attached.
1417 spec: The target dictionary containing the properties of the target.
1418 gyp_file: The name of the gyp file.
1420 A pair of (list of sources, list of excluded sources).
1421 The sources will be relative to the gyp file.
1423 sources = OrderedSet()
1424 _AddNormalizedSources(sources, spec.get('sources', []))
1425 excluded_sources = OrderedSet()
1426 # Add in the gyp file.
1427 if not generator_flags.get('standalone'):
1428 sources.add(gyp_file)
1430 # Add in 'action' inputs and outputs.
1431 for a in spec.get('actions', []):
1432 inputs = a['inputs']
1433 inputs = [_NormalizedSource(i) for i in inputs]
1434 # Add all inputs to sources and excluded sources.
1435 inputs = OrderedSet(inputs)
1436 sources.update(inputs)
1437 if not spec.get('msvs_external_builder'):
1438 excluded_sources.update(inputs)
1439 if int(a.get('process_outputs_as_sources', False)):
1440 _AddNormalizedSources(sources, a.get('outputs', []))
1441 # Add in 'copies' inputs and outputs.
1442 for cpy in spec.get('copies', []):
1443 _AddNormalizedSources(sources, cpy.get('files', []))
1444 return (sources, excluded_sources)
1447 def _AdjustSourcesAndConvertToFilterHierarchy(
1448 spec, options, gyp_dir, sources, excluded_sources, list_excluded, version):
1449 """Adjusts the list of sources and excluded sources.
1451 Also converts the sets to lists.
1454 spec: The target dictionary containing the properties of the target.
1455 options: Global generator options.
1456 gyp_dir: The path to the gyp file being processed.
1457 sources: A set of sources to be included for this project.
1458 excluded_sources: A set of sources to be excluded for this project.
1459 version: A MSVSVersion object.
1461 A trio of (list of sources, list of excluded sources,
1462 path of excluded IDL file)
1464 # Exclude excluded sources coming into the generator.
1465 excluded_sources.update(OrderedSet(spec.get('sources_excluded', [])))
1466 # Add excluded sources into sources for good measure.
1467 sources.update(excluded_sources)
1468 # Convert to proper windows form.
1469 # NOTE: sources goes from being a set to a list here.
1470 # NOTE: excluded_sources goes from being a set to a list here.
1471 sources = _FixPaths(sources)
1472 # Convert to proper windows form.
1473 excluded_sources = _FixPaths(excluded_sources)
1475 excluded_idl = _IdlFilesHandledNonNatively(spec, sources)
1477 precompiled_related = _GetPrecompileRelatedFiles(spec)
1478 # Find the excluded ones, minus the precompiled header related ones.
1479 fully_excluded = [i for i in excluded_sources if i not in precompiled_related]
1481 # Convert to folders and the right slashes.
1482 sources = [i.split('\\') for i in sources]
1483 sources = _ConvertSourcesToFilterHierarchy(sources, excluded=fully_excluded,
1484 list_excluded=list_excluded,
1485 msvs_version=version)
1487 # Prune filters with a single child to flatten ugly directory structures
1488 # such as ../../src/modules/module1 etc.
1489 if version.UsesVcxproj():
1490 while all([isinstance(s, MSVSProject.Filter) for s in sources]) \
1491 and len(set([s.name for s in sources])) == 1:
1492 assert all([len(s.contents) == 1 for s in sources])
1493 sources = [s.contents[0] for s in sources]
1495 while len(sources) == 1 and isinstance(sources[0], MSVSProject.Filter):
1496 sources = sources[0].contents
1498 return sources, excluded_sources, excluded_idl
1501 def _IdlFilesHandledNonNatively(spec, sources):
1502 # If any non-native rules use 'idl' as an extension exclude idl files.
1503 # Gather a list here to use later.
1505 for rule in spec.get('rules', []):
1506 if rule['extension'] == 'idl' and int(rule.get('msvs_external_rule', 0)):
1510 excluded_idl = [i for i in sources if i.endswith('.idl')]
1516 def _GetPrecompileRelatedFiles(spec):
1517 # Gather a list of precompiled header related sources.
1518 precompiled_related = []
1519 for _, config in spec['configurations'].iteritems():
1520 for k in precomp_keys:
1523 precompiled_related.append(_FixPath(f))
1524 return precompiled_related
1527 def _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
1529 exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
1530 for file_name, excluded_configs in exclusions.iteritems():
1531 if (not list_excluded and
1532 len(excluded_configs) == len(spec['configurations'])):
1533 # If we're not listing excluded files, then they won't appear in the
1534 # project, so don't try to configure them to be excluded.
1537 for config_name, config in excluded_configs:
1538 p.AddFileConfig(file_name, _ConfigFullName(config_name, config),
1539 {'ExcludedFromBuild': 'true'})
1542 def _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl):
1544 # Exclude excluded sources from being built.
1545 for f in excluded_sources:
1546 excluded_configs = []
1547 for config_name, config in spec['configurations'].iteritems():
1548 precomped = [_FixPath(config.get(i, '')) for i in precomp_keys]
1549 # Don't do this for ones that are precompiled header related.
1550 if f not in precomped:
1551 excluded_configs.append((config_name, config))
1552 exclusions[f] = excluded_configs
1553 # If any non-native rules use 'idl' as an extension exclude idl files.
1555 for f in excluded_idl:
1556 excluded_configs = []
1557 for config_name, config in spec['configurations'].iteritems():
1558 excluded_configs.append((config_name, config))
1559 exclusions[f] = excluded_configs
1563 def _AddToolFilesToMSVS(p, spec):
1564 # Add in tool files (rules).
1565 tool_files = OrderedSet()
1566 for _, config in spec['configurations'].iteritems():
1567 for f in config.get('msvs_tool_files', []):
1569 for f in tool_files:
1573 def _HandlePreCompiledHeaders(p, sources, spec):
1574 # Pre-compiled header source stubs need a different compiler flag
1575 # (generate precompiled header) and any source file not of the same
1576 # kind (i.e. C vs. C++) as the precompiled header source stub needs
1577 # to have use of precompiled headers disabled.
1578 extensions_excluded_from_precompile = []
1579 for config_name, config in spec['configurations'].iteritems():
1580 source = config.get('msvs_precompiled_source')
1582 source = _FixPath(source)
1583 # UsePrecompiledHeader=1 for if using precompiled headers.
1584 tool = MSVSProject.Tool('VCCLCompilerTool',
1585 {'UsePrecompiledHeader': '1'})
1586 p.AddFileConfig(source, _ConfigFullName(config_name, config),
1588 basename, extension = os.path.splitext(source)
1589 if extension == '.c':
1590 extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
1592 extensions_excluded_from_precompile = ['.c']
1593 def DisableForSourceTree(source_tree):
1594 for source in source_tree:
1595 if isinstance(source, MSVSProject.Filter):
1596 DisableForSourceTree(source.contents)
1598 basename, extension = os.path.splitext(source)
1599 if extension in extensions_excluded_from_precompile:
1600 for config_name, config in spec['configurations'].iteritems():
1601 tool = MSVSProject.Tool('VCCLCompilerTool',
1602 {'UsePrecompiledHeader': '0',
1603 'ForcedIncludeFiles': '$(NOINHERIT)'})
1604 p.AddFileConfig(_FixPath(source),
1605 _ConfigFullName(config_name, config),
1607 # Do nothing if there was no precompiled source.
1608 if extensions_excluded_from_precompile:
1609 DisableForSourceTree(sources)
1612 def _AddActions(actions_to_add, spec, relative_path_of_gyp_file):
1614 actions = spec.get('actions', [])
1615 # Don't setup_env every time. When all the actions are run together in one
1616 # batch file in VS, the PATH will grow too long.
1617 # Membership in this set means that the cygwin environment has been set up,
1618 # and does not need to be set up again.
1619 have_setup_env = set()
1621 # Attach actions to the gyp file if nothing else is there.
1622 inputs = a.get('inputs') or [relative_path_of_gyp_file]
1623 attached_to = inputs[0]
1624 need_setup_env = attached_to not in have_setup_env
1625 cmd = _BuildCommandLineForRule(spec, a, has_input_path=False,
1626 do_setup_env=need_setup_env)
1627 have_setup_env.add(attached_to)
1629 _AddActionStep(actions_to_add,
1631 outputs=a.get('outputs', []),
1632 description=a.get('message', a['action_name']),
1636 def _WriteMSVSUserFile(project_path, version, spec):
1637 # Add run_as and test targets.
1638 if 'run_as' in spec:
1639 run_as = spec['run_as']
1640 action = run_as.get('action', [])
1641 environment = run_as.get('environment', [])
1642 working_directory = run_as.get('working_directory', '.')
1643 elif int(spec.get('test', 0)):
1644 action = ['$(TargetPath)', '--gtest_print_time']
1646 working_directory = '.'
1648 return # Nothing to add
1649 # Write out the user file.
1650 user_file = _CreateMSVSUserFile(project_path, version, spec)
1651 for config_name, c_data in spec['configurations'].iteritems():
1652 user_file.AddDebugSettings(_ConfigFullName(config_name, c_data),
1653 action, environment, working_directory)
1654 user_file.WriteIfChanged()
1657 def _AddCopies(actions_to_add, spec):
1658 copies = _GetCopies(spec)
1659 for inputs, outputs, cmd, description in copies:
1660 _AddActionStep(actions_to_add, inputs=inputs, outputs=outputs,
1661 description=description, command=cmd)
1664 def _GetCopies(spec):
1667 for cpy in spec.get('copies', []):
1668 for src in cpy.get('files', []):
1669 dst = os.path.join(cpy['destination'], os.path.basename(src))
1670 # _AddCustomBuildToolForMSVS() will call _FixPath() on the inputs and
1671 # outputs, so do the same for our generated command line.
1672 if src.endswith('/'):
1674 base_dir = posixpath.split(src_bare)[0]
1675 outer_dir = posixpath.split(src_bare)[1]
1676 cmd = 'cd "%s" && xcopy /e /f /y "%s" "%s\\%s\\"' % (
1677 _FixPath(base_dir), outer_dir, _FixPath(dst), outer_dir)
1678 copies.append(([src], ['dummy_copies', dst], cmd,
1679 'Copying %s to %s' % (src, dst)))
1681 cmd = 'mkdir "%s" 2>nul & set ERRORLEVEL=0 & copy /Y "%s" "%s"' % (
1682 _FixPath(cpy['destination']), _FixPath(src), _FixPath(dst))
1683 copies.append(([src], [dst], cmd, 'Copying %s to %s' % (src, dst)))
1687 def _GetPathDict(root, path):
1688 # |path| will eventually be empty (in the recursive calls) if it was initially
1689 # relative; otherwise it will eventually end up as '\', 'D:\', etc.
1690 if not path or path.endswith(os.sep):
1692 parent, folder = os.path.split(path)
1693 parent_dict = _GetPathDict(root, parent)
1694 if folder not in parent_dict:
1695 parent_dict[folder] = dict()
1696 return parent_dict[folder]
1699 def _DictsToFolders(base_path, bucket, flat):
1700 # Convert to folders recursively.
1702 for folder, contents in bucket.iteritems():
1703 if type(contents) == dict:
1704 folder_children = _DictsToFolders(os.path.join(base_path, folder),
1707 children += folder_children
1709 folder_children = MSVSNew.MSVSFolder(os.path.join(base_path, folder),
1710 name='(' + folder + ')',
1711 entries=folder_children)
1712 children.append(folder_children)
1714 children.append(contents)
1718 def _CollapseSingles(parent, node):
1719 # Recursively explorer the tree of dicts looking for projects which are
1720 # the sole item in a folder which has the same name as the project. Bring
1721 # such projects up one level.
1722 if (type(node) == dict and
1724 node.keys()[0] == parent + '.vcproj'):
1725 return node[node.keys()[0]]
1726 if type(node) != dict:
1729 node[child] = _CollapseSingles(child, node[child])
1733 def _GatherSolutionFolders(sln_projects, project_objects, flat):
1735 # Convert into a tree of dicts on path.
1736 for p in sln_projects:
1737 gyp_file, target = gyp.common.ParseQualifiedTarget(p)[0:2]
1738 gyp_dir = os.path.dirname(gyp_file)
1739 path_dict = _GetPathDict(root, gyp_dir)
1740 path_dict[target + '.vcproj'] = project_objects[p]
1741 # Walk down from the top until we hit a folder that has more than one entry.
1742 # In practice, this strips the top-level "src/" dir from the hierarchy in
1744 while len(root) == 1 and type(root[root.keys()[0]]) == dict:
1745 root = root[root.keys()[0]]
1747 root = _CollapseSingles('', root)
1748 # Merge buckets until everything is a root entry.
1749 return _DictsToFolders('', root, flat)
1752 def _GetPathOfProject(qualified_target, spec, options, msvs_version):
1753 default_config = _GetDefaultConfiguration(spec)
1754 proj_filename = default_config.get('msvs_existing_vcproj')
1755 if not proj_filename:
1756 proj_filename = (spec['target_name'] + options.suffix +
1757 msvs_version.ProjectExtension())
1759 build_file = gyp.common.BuildFile(qualified_target)
1760 proj_path = os.path.join(os.path.dirname(build_file), proj_filename)
1762 if options.generator_output:
1763 project_dir_path = os.path.dirname(os.path.abspath(proj_path))
1764 proj_path = os.path.join(options.generator_output, proj_path)
1765 fix_prefix = gyp.common.RelativePath(project_dir_path,
1766 os.path.dirname(proj_path))
1767 return proj_path, fix_prefix
1770 def _GetPlatformOverridesOfProject(spec):
1771 # Prepare a dict indicating which project configurations are used for which
1772 # solution configurations for this target.
1773 config_platform_overrides = {}
1774 for config_name, c in spec['configurations'].iteritems():
1775 config_fullname = _ConfigFullName(config_name, c)
1776 platform = c.get('msvs_target_platform', _ConfigPlatform(c))
1777 fixed_config_fullname = '%s|%s' % (
1778 _ConfigBaseName(config_name, _ConfigPlatform(c)), platform)
1779 config_platform_overrides[config_fullname] = fixed_config_fullname
1780 return config_platform_overrides
1783 def _CreateProjectObjects(target_list, target_dicts, options, msvs_version):
1784 """Create a MSVSProject object for the targets found in target list.
1787 target_list: the list of targets to generate project objects for.
1788 target_dicts: the dictionary of specifications.
1789 options: global generator options.
1790 msvs_version: the MSVSVersion object.
1792 A set of created projects, keyed by target.
1794 global fixpath_prefix
1795 # Generate each project.
1797 for qualified_target in target_list:
1798 spec = target_dicts[qualified_target]
1799 if spec['toolset'] != 'target':
1801 'Multiple toolsets not supported in msvs build (target %s)' %
1803 proj_path, fixpath_prefix = _GetPathOfProject(qualified_target, spec,
1804 options, msvs_version)
1805 guid = _GetGuidOfProject(proj_path, spec)
1806 overrides = _GetPlatformOverridesOfProject(spec)
1807 build_file = gyp.common.BuildFile(qualified_target)
1808 # Create object for this project.
1809 obj = MSVSNew.MSVSProject(
1811 name=spec['target_name'],
1814 build_file=build_file,
1815 config_platform_overrides=overrides,
1816 fixpath_prefix=fixpath_prefix)
1817 # Set project toolset if any (MS build only)
1818 if msvs_version.UsesVcxproj():
1819 obj.set_msbuild_toolset(
1820 _GetMsbuildToolsetOfProject(proj_path, spec, msvs_version))
1821 projects[qualified_target] = obj
1822 # Set all the dependencies, but not if we are using an external builder like
1824 for project in projects.values():
1825 if not project.spec.get('msvs_external_builder'):
1826 deps = project.spec.get('dependencies', [])
1827 deps = [projects[d] for d in deps]
1828 project.set_dependencies(deps)
1832 def _InitNinjaFlavor(params, target_list, target_dicts):
1833 """Initialize targets for the ninja flavor.
1835 This sets up the necessary variables in the targets to generate msvs projects
1836 that use ninja as an external builder. The variables in the spec are only set
1837 if they have not been set. This allows individual specs to override the
1838 default values initialized here.
1840 params: Params provided to the generator.
1841 target_list: List of target pairs: 'base/base.gyp:base'.
1842 target_dicts: Dict of target properties keyed on target pair.
1844 for qualified_target in target_list:
1845 spec = target_dicts[qualified_target]
1846 if spec.get('msvs_external_builder'):
1847 # The spec explicitly defined an external builder, so don't change it.
1850 path_to_ninja = spec.get('msvs_path_to_ninja', 'ninja.exe')
1852 spec['msvs_external_builder'] = 'ninja'
1853 if not spec.get('msvs_external_builder_out_dir'):
1854 gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target)
1855 gyp_dir = os.path.dirname(gyp_file)
1856 spec['msvs_external_builder_out_dir'] = os.path.join(
1857 gyp.common.RelativePath(params['options'].toplevel_dir, gyp_dir),
1858 ninja_generator.ComputeOutputDir(params),
1860 if not spec.get('msvs_external_builder_build_cmd'):
1861 spec['msvs_external_builder_build_cmd'] = [
1867 if not spec.get('msvs_external_builder_clean_cmd'):
1868 spec['msvs_external_builder_clean_cmd'] = [
1877 def CalculateVariables(default_variables, params):
1878 """Generated variables that require params to be known."""
1880 generator_flags = params.get('generator_flags', {})
1882 # Select project file format version (if unset, default to auto detecting).
1883 msvs_version = MSVSVersion.SelectVisualStudioVersion(
1884 generator_flags.get('msvs_version', 'auto'))
1885 # Stash msvs_version for later (so we don't have to probe the system twice).
1886 params['msvs_version'] = msvs_version
1888 # Set a variable so conditions can be based on msvs_version.
1889 default_variables['MSVS_VERSION'] = msvs_version.ShortName()
1891 # To determine processor word size on Windows, in addition to checking
1892 # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
1893 # process), it is also necessary to check PROCESSOR_ARCITEW6432 (which
1894 # contains the actual word size of the system when running thru WOW64).
1895 if (os.environ.get('PROCESSOR_ARCHITECTURE', '').find('64') >= 0 or
1896 os.environ.get('PROCESSOR_ARCHITEW6432', '').find('64') >= 0):
1897 default_variables['MSVS_OS_BITS'] = 64
1899 default_variables['MSVS_OS_BITS'] = 32
1901 if gyp.common.GetFlavor(params) == 'ninja':
1902 default_variables['SHARED_INTERMEDIATE_DIR'] = '$(OutDir)gen'
1905 def PerformBuild(data, configurations, params):
1906 options = params['options']
1907 msvs_version = params['msvs_version']
1908 devenv = os.path.join(msvs_version.path, 'Common7', 'IDE', 'devenv.com')
1910 for build_file, build_file_dict in data.iteritems():
1911 (build_file_root, build_file_ext) = os.path.splitext(build_file)
1912 if build_file_ext != '.gyp':
1914 sln_path = build_file_root + options.suffix + '.sln'
1915 if options.generator_output:
1916 sln_path = os.path.join(options.generator_output, sln_path)
1918 for config in configurations:
1919 arguments = [devenv, sln_path, '/Build', config]
1920 print 'Building [%s]: %s' % (config, arguments)
1921 rtn = subprocess.check_call(arguments)
1924 def GenerateOutput(target_list, target_dicts, data, params):
1925 """Generate .sln and .vcproj files.
1927 This is the entry point for this generator.
1929 target_list: List of target pairs: 'base/base.gyp:base'.
1930 target_dicts: Dict of target properties keyed on target pair.
1931 data: Dictionary containing per .gyp data.
1933 global fixpath_prefix
1935 options = params['options']
1937 # Get the project file format version back out of where we stashed it in
1938 # GeneratorCalculatedVariables.
1939 msvs_version = params['msvs_version']
1941 generator_flags = params.get('generator_flags', {})
1943 # Optionally shard targets marked with 'msvs_shard': SHARD_COUNT.
1944 (target_list, target_dicts) = MSVSUtil.ShardTargets(target_list, target_dicts)
1946 # Optionally use the large PDB workaround for targets marked with
1947 # 'msvs_large_pdb': 1.
1948 (target_list, target_dicts) = MSVSUtil.InsertLargePdbShims(
1949 target_list, target_dicts, generator_default_variables)
1951 # Optionally configure each spec to use ninja as the external builder.
1952 if params.get('flavor') == 'ninja':
1953 _InitNinjaFlavor(params, target_list, target_dicts)
1955 # Prepare the set of configurations.
1957 for qualified_target in target_list:
1958 spec = target_dicts[qualified_target]
1959 for config_name, config in spec['configurations'].iteritems():
1960 configs.add(_ConfigFullName(config_name, config))
1961 configs = list(configs)
1963 # Figure out all the projects that will be generated and their guids
1964 project_objects = _CreateProjectObjects(target_list, target_dicts, options,
1967 # Generate each project.
1968 missing_sources = []
1969 for project in project_objects.values():
1970 fixpath_prefix = project.fixpath_prefix
1971 missing_sources.extend(_GenerateProject(project, options, msvs_version,
1973 fixpath_prefix = None
1975 for build_file in data:
1976 # Validate build_file extension
1977 if not build_file.endswith('.gyp'):
1979 sln_path = os.path.splitext(build_file)[0] + options.suffix + '.sln'
1980 if options.generator_output:
1981 sln_path = os.path.join(options.generator_output, sln_path)
1982 # Get projects in the solution, and their dependents.
1983 sln_projects = gyp.common.BuildFileTargets(target_list, build_file)
1984 sln_projects += gyp.common.DeepDependencyTargets(target_dicts, sln_projects)
1985 # Create folder hierarchy.
1986 root_entries = _GatherSolutionFolders(
1987 sln_projects, project_objects, flat=msvs_version.FlatSolution())
1989 sln = MSVSNew.MSVSSolution(sln_path,
1990 entries=root_entries,
1992 websiteProperties=False,
1993 version=msvs_version)
1997 error_message = "Missing input files:\n" + \
1998 '\n'.join(set(missing_sources))
1999 if generator_flags.get('msvs_error_on_missing_sources', False):
2000 raise GypError(error_message)
2002 print >> sys.stdout, "Warning: " + error_message
2005 def _GenerateMSBuildFiltersFile(filters_path, source_files,
2006 extension_to_rule_name):
2007 """Generate the filters file.
2009 This file is used by Visual Studio to organize the presentation of source
2013 filters_path: The path of the file to be created.
2014 source_files: The hierarchical structure of all the sources.
2015 extension_to_rule_name: A dictionary mapping file extensions to rules.
2019 _AppendFiltersForMSBuild('', source_files, extension_to_rule_name,
2020 filter_group, source_group)
2022 content = ['Project',
2023 {'ToolsVersion': '4.0',
2024 'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
2026 ['ItemGroup'] + filter_group,
2027 ['ItemGroup'] + source_group
2029 easy_xml.WriteXmlIfChanged(content, filters_path, pretty=True, win32=True)
2030 elif os.path.exists(filters_path):
2031 # We don't need this filter anymore. Delete the old filter file.
2032 os.unlink(filters_path)
2035 def _AppendFiltersForMSBuild(parent_filter_name, sources,
2036 extension_to_rule_name,
2037 filter_group, source_group):
2038 """Creates the list of filters and sources to be added in the filter file.
2041 parent_filter_name: The name of the filter under which the sources are
2043 sources: The hierarchy of filters and sources to process.
2044 extension_to_rule_name: A dictionary mapping file extensions to rules.
2045 filter_group: The list to which filter entries will be appended.
2046 source_group: The list to which source entries will be appeneded.
2048 for source in sources:
2049 if isinstance(source, MSVSProject.Filter):
2050 # We have a sub-filter. Create the name of that sub-filter.
2051 if not parent_filter_name:
2052 filter_name = source.name
2054 filter_name = '%s\\%s' % (parent_filter_name, source.name)
2055 # Add the filter to the group.
2056 filter_group.append(
2057 ['Filter', {'Include': filter_name},
2058 ['UniqueIdentifier', MSVSNew.MakeGuid(source.name)]])
2059 # Recurse and add its dependents.
2060 _AppendFiltersForMSBuild(filter_name, source.contents,
2061 extension_to_rule_name,
2062 filter_group, source_group)
2064 # It's a source. Create a source entry.
2065 _, element = _MapFileToMsBuildSourceType(source, extension_to_rule_name)
2066 source_entry = [element, {'Include': source}]
2067 # Specify the filter it is part of, if any.
2068 if parent_filter_name:
2069 source_entry.append(['Filter', parent_filter_name])
2070 source_group.append(source_entry)
2073 def _MapFileToMsBuildSourceType(source, extension_to_rule_name):
2074 """Returns the group and element type of the source file.
2077 source: The source file name.
2078 extension_to_rule_name: A dictionary mapping file extensions to rules.
2081 A pair of (group this file should be part of, the label of element)
2083 _, ext = os.path.splitext(source)
2084 if ext in extension_to_rule_name:
2086 element = extension_to_rule_name[ext]
2087 elif ext in ['.cc', '.cpp', '.c', '.cxx']:
2089 element = 'ClCompile'
2090 elif ext in ['.h', '.hxx']:
2092 element = 'ClInclude'
2095 element = 'ResourceCompile'
2102 return (group, element)
2105 def _GenerateRulesForMSBuild(output_dir, options, spec,
2106 sources, excluded_sources,
2107 props_files_of_rules, targets_files_of_rules,
2108 actions_to_add, extension_to_rule_name):
2109 # MSBuild rules are implemented using three files: an XML file, a .targets
2110 # file and a .props file.
2111 # See http://blogs.msdn.com/b/vcblog/archive/2010/04/21/quick-help-on-vs2010-custom-build-rule.aspx
2113 rules = spec.get('rules', [])
2114 rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
2115 rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
2118 for rule in rules_native:
2119 # Skip a rule with no action and no inputs.
2120 if 'action' not in rule and not rule.get('rule_sources', []):
2122 msbuild_rule = MSBuildRule(rule, spec)
2123 msbuild_rules.append(msbuild_rule)
2124 extension_to_rule_name[msbuild_rule.extension] = msbuild_rule.rule_name
2126 base = spec['target_name'] + options.suffix
2127 props_name = base + '.props'
2128 targets_name = base + '.targets'
2129 xml_name = base + '.xml'
2131 props_files_of_rules.add(props_name)
2132 targets_files_of_rules.add(targets_name)
2134 props_path = os.path.join(output_dir, props_name)
2135 targets_path = os.path.join(output_dir, targets_name)
2136 xml_path = os.path.join(output_dir, xml_name)
2138 _GenerateMSBuildRulePropsFile(props_path, msbuild_rules)
2139 _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules)
2140 _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules)
2143 _GenerateExternalRules(rules_external, output_dir, spec,
2144 sources, options, actions_to_add)
2145 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
2148 class MSBuildRule(object):
2149 """Used to store information used to generate an MSBuild rule.
2152 rule_name: The rule name, sanitized to use in XML.
2153 target_name: The name of the target.
2154 after_targets: The name of the AfterTargets element.
2155 before_targets: The name of the BeforeTargets element.
2156 depends_on: The name of the DependsOn element.
2157 compute_output: The name of the ComputeOutput element.
2158 dirs_to_make: The name of the DirsToMake element.
2159 inputs: The name of the _inputs element.
2160 tlog: The name of the _tlog element.
2161 extension: The extension this rule applies to.
2162 description: The message displayed when this rule is invoked.
2163 additional_dependencies: A string listing additional dependencies.
2164 outputs: The outputs of this rule.
2165 command: The command used to run the rule.
2168 def __init__(self, rule, spec):
2169 self.display_name = rule['rule_name']
2170 # Assure that the rule name is only characters and numbers
2171 self.rule_name = re.sub(r'\W', '_', self.display_name)
2172 # Create the various element names, following the example set by the
2173 # Visual Studio 2008 to 2010 conversion. I don't know if VS2010
2174 # is sensitive to the exact names.
2175 self.target_name = '_' + self.rule_name
2176 self.after_targets = self.rule_name + 'AfterTargets'
2177 self.before_targets = self.rule_name + 'BeforeTargets'
2178 self.depends_on = self.rule_name + 'DependsOn'
2179 self.compute_output = 'Compute%sOutput' % self.rule_name
2180 self.dirs_to_make = self.rule_name + 'DirsToMake'
2181 self.inputs = self.rule_name + '_inputs'
2182 self.tlog = self.rule_name + '_tlog'
2183 self.extension = rule['extension']
2184 if not self.extension.startswith('.'):
2185 self.extension = '.' + self.extension
2187 self.description = MSVSSettings.ConvertVCMacrosToMSBuild(
2188 rule.get('message', self.rule_name))
2189 old_additional_dependencies = _FixPaths(rule.get('inputs', []))
2190 self.additional_dependencies = (
2191 ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
2192 for i in old_additional_dependencies]))
2193 old_outputs = _FixPaths(rule.get('outputs', []))
2194 self.outputs = ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
2195 for i in old_outputs])
2196 old_command = _BuildCommandLineForRule(spec, rule, has_input_path=True,
2198 self.command = MSVSSettings.ConvertVCMacrosToMSBuild(old_command)
2201 def _GenerateMSBuildRulePropsFile(props_path, msbuild_rules):
2202 """Generate the .props file."""
2203 content = ['Project',
2204 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'}]
2205 for rule in msbuild_rules:
2208 {'Condition': "'$(%s)' == '' and '$(%s)' == '' and "
2209 "'$(ConfigurationType)' != 'Makefile'" % (rule.before_targets,
2212 [rule.before_targets, 'Midl'],
2213 [rule.after_targets, 'CustomBuild'],
2217 {'Condition': "'$(ConfigurationType)' != 'Makefile'"},
2218 '_SelectedFiles;$(%s)' % rule.depends_on
2221 ['ItemDefinitionGroup',
2223 ['CommandLineTemplate', rule.command],
2224 ['Outputs', rule.outputs],
2225 ['ExecutionDescription', rule.description],
2226 ['AdditionalDependencies', rule.additional_dependencies],
2230 easy_xml.WriteXmlIfChanged(content, props_path, pretty=True, win32=True)
2233 def _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules):
2234 """Generate the .targets file."""
2235 content = ['Project',
2236 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
2241 ['PropertyPageSchema',
2242 {'Include': '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'}
2245 for rule in msbuild_rules:
2247 ['AvailableItemName',
2248 {'Include': rule.rule_name},
2249 ['Targets', rule.target_name],
2251 content.append(item_group)
2253 for rule in msbuild_rules:
2256 {'TaskName': rule.rule_name,
2257 'TaskFactory': 'XamlTaskFactory',
2258 'AssemblyName': 'Microsoft.Build.Tasks.v4.0'
2260 ['Task', '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'],
2262 for rule in msbuild_rules:
2263 rule_name = rule.rule_name
2264 target_outputs = '%%(%s.Outputs)' % rule_name
2265 target_inputs = ('%%(%s.Identity);%%(%s.AdditionalDependencies);'
2266 '$(MSBuildProjectFile)') % (rule_name, rule_name)
2267 rule_inputs = '%%(%s.Identity)' % rule_name
2268 extension_condition = ("'%(Extension)'=='.obj' or "
2269 "'%(Extension)'=='.res' or "
2270 "'%(Extension)'=='.rsc' or "
2271 "'%(Extension)'=='.lib'")
2274 {'Condition': "'@(SelectedFiles)' != ''"},
2276 {'Remove': '@(%s)' % rule_name,
2277 'Condition': "'%(Identity)' != '@(SelectedFiles)'"
2283 [rule.inputs, {'Include': '%%(%s.AdditionalDependencies)' % rule_name}]
2288 {'Include': '%%(%s.Outputs)' % rule_name,
2289 'Condition': ("'%%(%s.Outputs)' != '' and "
2290 "'%%(%s.ExcludedFromBuild)' != 'true'" %
2291 (rule_name, rule_name))
2293 ['Source', "@(%s, '|')" % rule_name],
2294 ['Inputs', "@(%s -> '%%(Fullpath)', ';')" % rule.inputs],
2299 {'Importance': 'High',
2300 'Text': '%%(%s.ExecutionDescription)' % rule_name
2303 write_tlog_section = [
2305 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2306 "'true'" % (rule.tlog, rule.tlog),
2307 'File': '$(IntDir)$(ProjectName).write.1.tlog',
2308 'Lines': "^%%(%s.Source);@(%s->'%%(Fullpath)')" % (rule.tlog,
2312 read_tlog_section = [
2314 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2315 "'true'" % (rule.tlog, rule.tlog),
2316 'File': '$(IntDir)$(ProjectName).read.1.tlog',
2317 'Lines': "^%%(%s.Source);%%(%s.Inputs)" % (rule.tlog, rule.tlog)
2320 command_and_input_section = [
2322 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2323 "'true'" % (rule_name, rule_name),
2324 'CommandLineTemplate': '%%(%s.CommandLineTemplate)' % rule_name,
2325 'AdditionalOptions': '%%(%s.AdditionalOptions)' % rule_name,
2326 'Inputs': rule_inputs
2331 {'Name': rule.target_name,
2332 'BeforeTargets': '$(%s)' % rule.before_targets,
2333 'AfterTargets': '$(%s)' % rule.after_targets,
2334 'Condition': "'@(%s)' != ''" % rule_name,
2335 'DependsOnTargets': '$(%s);%s' % (rule.depends_on,
2336 rule.compute_output),
2337 'Outputs': target_outputs,
2338 'Inputs': target_inputs
2346 command_and_input_section,
2349 ['ComputeLinkInputsTargets',
2350 '$(ComputeLinkInputsTargets);',
2351 '%s;' % rule.compute_output
2353 ['ComputeLibInputsTargets',
2354 '$(ComputeLibInputsTargets);',
2355 '%s;' % rule.compute_output
2359 {'Name': rule.compute_output,
2360 'Condition': "'@(%s)' != ''" % rule_name
2364 {'Condition': "'@(%s)' != '' and "
2365 "'%%(%s.ExcludedFromBuild)' != 'true'" % (rule_name, rule_name),
2366 'Include': '%%(%s.Outputs)' % rule_name
2370 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2371 'Condition': extension_condition
2375 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2376 'Condition': extension_condition
2380 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2381 'Condition': extension_condition
2386 {'Directories': ("@(%s->'%%(RootDir)%%(Directory)')" %
2392 easy_xml.WriteXmlIfChanged(content, targets_path, pretty=True, win32=True)
2395 def _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules):
2396 # Generate the .xml file
2398 'ProjectSchemaDefinitions',
2399 {'xmlns': ('clr-namespace:Microsoft.Build.Framework.XamlTypes;'
2400 'assembly=Microsoft.Build.Framework'),
2401 'xmlns:x': 'http://schemas.microsoft.com/winfx/2006/xaml',
2402 'xmlns:sys': 'clr-namespace:System;assembly=mscorlib',
2403 'xmlns:transformCallback':
2404 'Microsoft.Cpp.Dev10.ConvertPropertyCallback'
2407 for rule in msbuild_rules:
2410 {'Name': rule.rule_name,
2411 'PageTemplate': 'tool',
2412 'DisplayName': rule.display_name,
2417 {'Persistence': 'ProjectFile',
2418 'ItemType': rule.rule_name
2424 {'Name': 'General'},
2425 ['Category.DisplayName',
2426 ['sys:String', 'General'],
2430 {'Name': 'Command Line',
2431 'Subtype': 'CommandLine'
2433 ['Category.DisplayName',
2434 ['sys:String', 'Command Line'],
2438 ['StringListProperty',
2440 'Category': 'Command Line',
2441 'IsRequired': 'true',
2444 ['StringListProperty.DataSource',
2446 {'Persistence': 'ProjectFile',
2447 'ItemType': rule.rule_name,
2448 'SourceType': 'Item'
2454 {'Name': 'CommandLineTemplate',
2455 'DisplayName': 'Command Line',
2457 'IncludeInCommandLine': 'False'
2460 ['DynamicEnumProperty',
2461 {'Name': rule.before_targets,
2462 'Category': 'General',
2463 'EnumProvider': 'Targets',
2464 'IncludeInCommandLine': 'False'
2466 ['DynamicEnumProperty.DisplayName',
2467 ['sys:String', 'Execute Before'],
2469 ['DynamicEnumProperty.Description',
2470 ['sys:String', 'Specifies the targets for the build customization'
2474 ['DynamicEnumProperty.ProviderSettings',
2477 'Value': '^%s|^Compute' % rule.before_targets
2481 ['DynamicEnumProperty.DataSource',
2483 {'Persistence': 'ProjectFile',
2484 'HasConfigurationCondition': 'true'
2489 ['DynamicEnumProperty',
2490 {'Name': rule.after_targets,
2491 'Category': 'General',
2492 'EnumProvider': 'Targets',
2493 'IncludeInCommandLine': 'False'
2495 ['DynamicEnumProperty.DisplayName',
2496 ['sys:String', 'Execute After'],
2498 ['DynamicEnumProperty.Description',
2499 ['sys:String', ('Specifies the targets for the build customization'
2503 ['DynamicEnumProperty.ProviderSettings',
2506 'Value': '^%s|^Compute' % rule.after_targets
2510 ['DynamicEnumProperty.DataSource',
2512 {'Persistence': 'ProjectFile',
2514 'HasConfigurationCondition': 'true'
2519 ['StringListProperty',
2521 'DisplayName': 'Outputs',
2523 'IncludeInCommandLine': 'False'
2527 {'Name': 'ExecutionDescription',
2528 'DisplayName': 'Execution Description',
2530 'IncludeInCommandLine': 'False'
2533 ['StringListProperty',
2534 {'Name': 'AdditionalDependencies',
2535 'DisplayName': 'Additional Dependencies',
2536 'IncludeInCommandLine': 'False',
2541 {'Subtype': 'AdditionalOptions',
2542 'Name': 'AdditionalOptions',
2543 'Category': 'Command Line'
2545 ['StringProperty.DisplayName',
2546 ['sys:String', 'Additional Options'],
2548 ['StringProperty.Description',
2549 ['sys:String', 'Additional Options'],
2554 {'Name': rule.rule_name,
2555 'DisplayName': rule.display_name
2559 {'Name': '*' + rule.extension,
2560 'ContentType': rule.rule_name
2564 {'Name': rule.rule_name,
2566 'ItemType': rule.rule_name
2570 easy_xml.WriteXmlIfChanged(content, xml_path, pretty=True, win32=True)
2573 def _GetConfigurationAndPlatform(name, settings):
2574 configuration = name.rsplit('_', 1)[0]
2575 platform = settings.get('msvs_configuration_platform', 'Win32')
2576 return (configuration, platform)
2579 def _GetConfigurationCondition(name, settings):
2580 return (r"'$(Configuration)|$(Platform)'=='%s|%s'" %
2581 _GetConfigurationAndPlatform(name, settings))
2584 def _GetMSBuildProjectConfigurations(configurations):
2585 group = ['ItemGroup', {'Label': 'ProjectConfigurations'}]
2586 for (name, settings) in sorted(configurations.iteritems()):
2587 configuration, platform = _GetConfigurationAndPlatform(name, settings)
2588 designation = '%s|%s' % (configuration, platform)
2590 ['ProjectConfiguration', {'Include': designation},
2591 ['Configuration', configuration],
2592 ['Platform', platform]])
2596 def _GetMSBuildGlobalProperties(spec, guid, gyp_file_name):
2597 namespace = os.path.splitext(gyp_file_name)[0]
2599 ['PropertyGroup', {'Label': 'Globals'},
2600 ['ProjectGuid', guid],
2601 ['Keyword', 'Win32Proj'],
2602 ['RootNamespace', namespace],
2603 ['IgnoreWarnCompileDuplicatedFilename', 'true'],
2608 def _GetMSBuildConfigurationDetails(spec, build_file):
2610 for name, settings in spec['configurations'].iteritems():
2611 msbuild_attributes = _GetMSBuildAttributes(spec, settings, build_file)
2612 condition = _GetConfigurationCondition(name, settings)
2613 character_set = msbuild_attributes.get('CharacterSet')
2614 _AddConditionalProperty(properties, condition, 'ConfigurationType',
2615 msbuild_attributes['ConfigurationType'])
2617 _AddConditionalProperty(properties, condition, 'CharacterSet',
2619 return _GetMSBuildPropertyGroup(spec, 'Configuration', properties)
2622 def _GetMSBuildLocalProperties(msbuild_toolset):
2623 # Currently the only local property we support is PlatformToolset
2627 ['PropertyGroup', {'Label': 'Locals'},
2628 ['PlatformToolset', msbuild_toolset],
2634 def _GetMSBuildPropertySheets(configurations):
2635 user_props = r'$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props'
2636 additional_props = {}
2637 props_specified = False
2638 for name, settings in sorted(configurations.iteritems()):
2639 configuration = _GetConfigurationCondition(name, settings)
2640 if settings.has_key('msbuild_props'):
2641 additional_props[configuration] = _FixPaths(settings['msbuild_props'])
2642 props_specified = True
2644 additional_props[configuration] = ''
2646 if not props_specified:
2649 {'Label': 'PropertySheets'},
2651 {'Project': user_props,
2652 'Condition': "exists('%s')" % user_props,
2653 'Label': 'LocalAppDataPlatform'
2660 for condition, props in additional_props.iteritems():
2663 {'Label': 'PropertySheets',
2664 'Condition': condition
2667 {'Project': user_props,
2668 'Condition': "exists('%s')" % user_props,
2669 'Label': 'LocalAppDataPlatform'
2673 for props_file in props:
2674 import_group.append(['Import', {'Project':props_file}])
2675 sheets.append(import_group)
2678 def _ConvertMSVSBuildAttributes(spec, config, build_file):
2679 config_type = _GetMSVSConfigurationType(spec, build_file)
2680 msvs_attributes = _GetMSVSAttributes(spec, config, config_type)
2681 msbuild_attributes = {}
2682 for a in msvs_attributes:
2683 if a in ['IntermediateDirectory', 'OutputDirectory']:
2684 directory = MSVSSettings.ConvertVCMacrosToMSBuild(msvs_attributes[a])
2685 if not directory.endswith('\\'):
2687 msbuild_attributes[a] = directory
2688 elif a == 'CharacterSet':
2689 msbuild_attributes[a] = _ConvertMSVSCharacterSet(msvs_attributes[a])
2690 elif a == 'ConfigurationType':
2691 msbuild_attributes[a] = _ConvertMSVSConfigurationType(msvs_attributes[a])
2693 print 'Warning: Do not know how to convert MSVS attribute ' + a
2694 return msbuild_attributes
2697 def _ConvertMSVSCharacterSet(char_set):
2698 if char_set.isdigit():
2707 def _ConvertMSVSConfigurationType(config_type):
2708 if config_type.isdigit():
2711 '2': 'DynamicLibrary',
2712 '4': 'StaticLibrary',
2718 def _GetMSBuildAttributes(spec, config, build_file):
2719 if 'msbuild_configuration_attributes' not in config:
2720 msbuild_attributes = _ConvertMSVSBuildAttributes(spec, config, build_file)
2723 config_type = _GetMSVSConfigurationType(spec, build_file)
2724 config_type = _ConvertMSVSConfigurationType(config_type)
2725 msbuild_attributes = config.get('msbuild_configuration_attributes', {})
2726 msbuild_attributes.setdefault('ConfigurationType', config_type)
2727 output_dir = msbuild_attributes.get('OutputDirectory',
2728 '$(SolutionDir)$(Configuration)')
2729 msbuild_attributes['OutputDirectory'] = _FixPath(output_dir) + '\\'
2730 if 'IntermediateDirectory' not in msbuild_attributes:
2731 intermediate = _FixPath('$(Configuration)') + '\\'
2732 msbuild_attributes['IntermediateDirectory'] = intermediate
2733 if 'CharacterSet' in msbuild_attributes:
2734 msbuild_attributes['CharacterSet'] = _ConvertMSVSCharacterSet(
2735 msbuild_attributes['CharacterSet'])
2736 if 'TargetName' not in msbuild_attributes:
2737 prefix = spec.get('product_prefix', '')
2738 product_name = spec.get('product_name', '$(ProjectName)')
2739 target_name = prefix + product_name
2740 msbuild_attributes['TargetName'] = target_name
2742 if spec.get('msvs_external_builder'):
2743 external_out_dir = spec.get('msvs_external_builder_out_dir', '.')
2744 msbuild_attributes['OutputDirectory'] = _FixPath(external_out_dir) + '\\'
2746 # Make sure that 'TargetPath' matches 'Lib.OutputFile' or 'Link.OutputFile'
2747 # (depending on the tool used) to avoid MSB8012 warning.
2748 msbuild_tool_map = {
2749 'executable': 'Link',
2750 'shared_library': 'Link',
2751 'loadable_module': 'Link',
2752 'static_library': 'Lib',
2754 msbuild_tool = msbuild_tool_map.get(spec['type'])
2756 msbuild_settings = config['finalized_msbuild_settings']
2757 out_file = msbuild_settings[msbuild_tool].get('OutputFile')
2759 msbuild_attributes['TargetPath'] = _FixPath(out_file)
2760 target_ext = msbuild_settings[msbuild_tool].get('TargetExt')
2762 msbuild_attributes['TargetExt'] = target_ext
2764 return msbuild_attributes
2767 def _GetMSBuildConfigurationGlobalProperties(spec, configurations, build_file):
2768 # TODO(jeanluc) We could optimize out the following and do it only if
2769 # there are actions.
2770 # TODO(jeanluc) Handle the equivalent of setting 'CYGWIN=nontsec'.
2772 cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.'])[0]
2774 cyg_path = '$(MSBuildProjectDirectory)\\%s\\bin\\' % _FixPath(cygwin_dirs)
2775 new_paths.append(cyg_path)
2776 # TODO(jeanluc) Change the convention to have both a cygwin_dir and a
2778 python_path = cyg_path.replace('cygwin\\bin', 'python_26')
2779 new_paths.append(python_path)
2781 new_paths = '$(ExecutablePath);' + ';'.join(new_paths)
2784 for (name, configuration) in sorted(configurations.iteritems()):
2785 condition = _GetConfigurationCondition(name, configuration)
2786 attributes = _GetMSBuildAttributes(spec, configuration, build_file)
2787 msbuild_settings = configuration['finalized_msbuild_settings']
2788 _AddConditionalProperty(properties, condition, 'IntDir',
2789 attributes['IntermediateDirectory'])
2790 _AddConditionalProperty(properties, condition, 'OutDir',
2791 attributes['OutputDirectory'])
2792 _AddConditionalProperty(properties, condition, 'TargetName',
2793 attributes['TargetName'])
2795 if attributes.get('TargetPath'):
2796 _AddConditionalProperty(properties, condition, 'TargetPath',
2797 attributes['TargetPath'])
2798 if attributes.get('TargetExt'):
2799 _AddConditionalProperty(properties, condition, 'TargetExt',
2800 attributes['TargetExt'])
2803 _AddConditionalProperty(properties, condition, 'ExecutablePath',
2805 tool_settings = msbuild_settings.get('', {})
2806 for name, value in sorted(tool_settings.iteritems()):
2807 formatted_value = _GetValueFormattedForMSBuild('', name, value)
2808 _AddConditionalProperty(properties, condition, name, formatted_value)
2809 return _GetMSBuildPropertyGroup(spec, None, properties)
2812 def _AddConditionalProperty(properties, condition, name, value):
2813 """Adds a property / conditional value pair to a dictionary.
2816 properties: The dictionary to be modified. The key is the name of the
2817 property. The value is itself a dictionary; its key is the value and
2818 the value a list of condition for which this value is true.
2819 condition: The condition under which the named property has the value.
2820 name: The name of the property.
2821 value: The value of the property.
2823 if name not in properties:
2824 properties[name] = {}
2825 values = properties[name]
2826 if value not in values:
2828 conditions = values[value]
2829 conditions.append(condition)
2832 # Regex for msvs variable references ( i.e. $(FOO) ).
2833 MSVS_VARIABLE_REFERENCE = re.compile('\$\(([a-zA-Z_][a-zA-Z0-9_]*)\)')
2836 def _GetMSBuildPropertyGroup(spec, label, properties):
2837 """Returns a PropertyGroup definition for the specified properties.
2840 spec: The target project dict.
2841 label: An optional label for the PropertyGroup.
2842 properties: The dictionary to be converted. The key is the name of the
2843 property. The value is itself a dictionary; its key is the value and
2844 the value a list of condition for which this value is true.
2846 group = ['PropertyGroup']
2848 group.append({'Label': label})
2849 num_configurations = len(spec['configurations'])
2851 # Use a definition of edges such that user_of_variable -> used_varible.
2852 # This happens to be easier in this case, since a variable's
2853 # definition contains all variables it references in a single string.
2855 for value in sorted(properties[node].keys()):
2856 # Add to edges all $(...) references to variables.
2858 # Variable references that refer to names not in properties are excluded
2859 # These can exist for instance to refer built in definitions like
2862 # Self references are ignored. Self reference is used in a few places to
2863 # append to the default value. I.e. PATH=$(PATH);other_path
2864 edges.update(set([v for v in MSVS_VARIABLE_REFERENCE.findall(value)
2865 if v in properties and v != node]))
2867 properties_ordered = gyp.common.TopologicallySorted(
2868 properties.keys(), GetEdges)
2869 # Walk properties in the reverse of a topological sort on
2870 # user_of_variable -> used_variable as this ensures variables are
2871 # defined before they are used.
2872 # NOTE: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))
2873 for name in reversed(properties_ordered):
2874 values = properties[name]
2875 for value, conditions in sorted(values.iteritems()):
2876 if len(conditions) == num_configurations:
2877 # If the value is the same all configurations,
2878 # just add one unconditional entry.
2879 group.append([name, value])
2881 for condition in conditions:
2882 group.append([name, {'Condition': condition}, value])
2886 def _GetMSBuildToolSettingsSections(spec, configurations):
2888 for (name, configuration) in sorted(configurations.iteritems()):
2889 msbuild_settings = configuration['finalized_msbuild_settings']
2890 group = ['ItemDefinitionGroup',
2891 {'Condition': _GetConfigurationCondition(name, configuration)}
2893 for tool_name, tool_settings in sorted(msbuild_settings.iteritems()):
2894 # Skip the tool named '' which is a holder of global settings handled
2895 # by _GetMSBuildConfigurationGlobalProperties.
2899 for name, value in sorted(tool_settings.iteritems()):
2900 formatted_value = _GetValueFormattedForMSBuild(tool_name, name,
2902 tool.append([name, formatted_value])
2904 groups.append(group)
2908 def _FinalizeMSBuildSettings(spec, configuration):
2909 if 'msbuild_settings' in configuration:
2911 msbuild_settings = configuration['msbuild_settings']
2912 MSVSSettings.ValidateMSBuildSettings(msbuild_settings)
2915 msvs_settings = configuration.get('msvs_settings', {})
2916 msbuild_settings = MSVSSettings.ConvertToMSBuildSettings(msvs_settings)
2917 include_dirs, resource_include_dirs = _GetIncludeDirs(configuration)
2918 libraries = _GetLibraries(spec)
2919 library_dirs = _GetLibraryDirs(configuration)
2920 out_file, _, msbuild_tool = _GetOutputFilePathAndTool(spec, msbuild=True)
2921 target_ext = _GetOutputTargetExt(spec)
2922 defines = _GetDefines(configuration)
2924 # Visual Studio 2010 has TR1
2925 defines = [d for d in defines if d != '_HAS_TR1=0']
2926 # Warn of ignored settings
2927 ignored_settings = ['msvs_tool_files']
2928 for ignored_setting in ignored_settings:
2929 value = configuration.get(ignored_setting)
2931 print ('Warning: The automatic conversion to MSBuild does not handle '
2932 '%s. Ignoring setting of %s' % (ignored_setting, str(value)))
2934 defines = [_EscapeCppDefineForMSBuild(d) for d in defines]
2935 disabled_warnings = _GetDisabledWarnings(configuration)
2936 prebuild = configuration.get('msvs_prebuild')
2937 postbuild = configuration.get('msvs_postbuild')
2938 def_file = _GetModuleDefinition(spec)
2939 precompiled_header = configuration.get('msvs_precompiled_header')
2941 # Add the information to the appropriate tool
2942 # TODO(jeanluc) We could optimize and generate these settings only if
2943 # the corresponding files are found, e.g. don't generate ResourceCompile
2944 # if you don't have any resources.
2945 _ToolAppend(msbuild_settings, 'ClCompile',
2946 'AdditionalIncludeDirectories', include_dirs)
2947 _ToolAppend(msbuild_settings, 'ResourceCompile',
2948 'AdditionalIncludeDirectories', resource_include_dirs)
2949 # Add in libraries, note that even for empty libraries, we want this
2950 # set, to prevent inheriting default libraries from the enviroment.
2951 _ToolSetOrAppend(msbuild_settings, 'Link', 'AdditionalDependencies',
2953 _ToolAppend(msbuild_settings, 'Link', 'AdditionalLibraryDirectories',
2956 _ToolAppend(msbuild_settings, msbuild_tool, 'OutputFile', out_file,
2959 _ToolAppend(msbuild_settings, msbuild_tool, 'TargetExt', target_ext,
2962 _ToolAppend(msbuild_settings, 'ClCompile',
2963 'PreprocessorDefinitions', defines)
2964 _ToolAppend(msbuild_settings, 'ResourceCompile',
2965 'PreprocessorDefinitions', defines)
2966 # Add disabled warnings.
2967 _ToolAppend(msbuild_settings, 'ClCompile',
2968 'DisableSpecificWarnings', disabled_warnings)
2969 # Turn on precompiled headers if appropriate.
2970 if precompiled_header:
2971 precompiled_header = os.path.split(precompiled_header)[1]
2972 _ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'Use')
2973 _ToolAppend(msbuild_settings, 'ClCompile',
2974 'PrecompiledHeaderFile', precompiled_header)
2975 _ToolAppend(msbuild_settings, 'ClCompile',
2976 'ForcedIncludeFiles', [precompiled_header])
2977 # Loadable modules don't generate import libraries;
2978 # tell dependent projects to not expect one.
2979 if spec['type'] == 'loadable_module':
2980 _ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'true')
2981 # Set the module definition file if any.
2983 _ToolAppend(msbuild_settings, 'Link', 'ModuleDefinitionFile', def_file)
2984 configuration['finalized_msbuild_settings'] = msbuild_settings
2986 _ToolAppend(msbuild_settings, 'PreBuildEvent', 'Command', prebuild)
2988 _ToolAppend(msbuild_settings, 'PostBuildEvent', 'Command', postbuild)
2991 def _GetValueFormattedForMSBuild(tool_name, name, value):
2992 if type(value) == list:
2993 # For some settings, VS2010 does not automatically extends the settings
2994 # TODO(jeanluc) Is this what we want?
2995 if name in ['AdditionalIncludeDirectories',
2996 'AdditionalLibraryDirectories',
2997 'AdditionalOptions',
2999 'DisableSpecificWarnings',
3000 'PreprocessorDefinitions']:
3001 value.append('%%(%s)' % name)
3002 # For most tools, entries in a list should be separated with ';' but some
3003 # settings use a space. Check for those first.
3005 'ClCompile': ['AdditionalOptions'],
3006 'Link': ['AdditionalOptions'],
3007 'Lib': ['AdditionalOptions']}
3008 if tool_name in exceptions and name in exceptions[tool_name]:
3012 formatted_value = char.join(
3013 [MSVSSettings.ConvertVCMacrosToMSBuild(i) for i in value])
3015 formatted_value = MSVSSettings.ConvertVCMacrosToMSBuild(value)
3016 return formatted_value
3019 def _VerifySourcesExist(sources, root_dir):
3020 """Verifies that all source files exist on disk.
3022 Checks that all regular source files, i.e. not created at run time,
3023 exist on disk. Missing files cause needless recompilation but no otherwise
3027 sources: A recursive list of Filter/file names.
3028 root_dir: The root directory for the relative path names.
3030 A list of source files that cannot be found on disk.
3032 missing_sources = []
3033 for source in sources:
3034 if isinstance(source, MSVSProject.Filter):
3035 missing_sources.extend(_VerifySourcesExist(source.contents, root_dir))
3037 if '$' not in source:
3038 full_path = os.path.join(root_dir, source)
3039 if not os.path.exists(full_path):
3040 missing_sources.append(full_path)
3041 return missing_sources
3044 def _GetMSBuildSources(spec, sources, exclusions, extension_to_rule_name,
3045 actions_spec, sources_handled_by_action, list_excluded):
3046 groups = ['none', 'midl', 'include', 'compile', 'resource', 'rule']
3047 grouped_sources = {}
3049 grouped_sources[g] = []
3051 _AddSources2(spec, sources, exclusions, grouped_sources,
3052 extension_to_rule_name, sources_handled_by_action, list_excluded)
3055 if grouped_sources[g]:
3056 sources.append(['ItemGroup'] + grouped_sources[g])
3058 sources.append(['ItemGroup'] + actions_spec)
3062 def _AddSources2(spec, sources, exclusions, grouped_sources,
3063 extension_to_rule_name, sources_handled_by_action,
3065 extensions_excluded_from_precompile = []
3066 for source in sources:
3067 if isinstance(source, MSVSProject.Filter):
3068 _AddSources2(spec, source.contents, exclusions, grouped_sources,
3069 extension_to_rule_name, sources_handled_by_action,
3072 if not source in sources_handled_by_action:
3074 excluded_configurations = exclusions.get(source, [])
3075 if len(excluded_configurations) == len(spec['configurations']):
3076 detail.append(['ExcludedFromBuild', 'true'])
3078 for config_name, configuration in sorted(excluded_configurations):
3079 condition = _GetConfigurationCondition(config_name, configuration)
3080 detail.append(['ExcludedFromBuild',
3081 {'Condition': condition},
3083 # Add precompile if needed
3084 for config_name, configuration in spec['configurations'].iteritems():
3085 precompiled_source = configuration.get('msvs_precompiled_source', '')
3086 if precompiled_source != '':
3087 precompiled_source = _FixPath(precompiled_source)
3088 if not extensions_excluded_from_precompile:
3089 # If the precompiled header is generated by a C source, we must
3090 # not try to use it for C++ sources, and vice versa.
3091 basename, extension = os.path.splitext(precompiled_source)
3092 if extension == '.c':
3093 extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
3095 extensions_excluded_from_precompile = ['.c']
3097 if precompiled_source == source:
3098 condition = _GetConfigurationCondition(config_name, configuration)
3099 detail.append(['PrecompiledHeader',
3100 {'Condition': condition},
3104 # Turn off precompiled header usage for source files of a
3105 # different type than the file that generated the
3106 # precompiled header.
3107 for extension in extensions_excluded_from_precompile:
3108 if source.endswith(extension):
3109 detail.append(['PrecompiledHeader', ''])
3110 detail.append(['ForcedIncludeFiles', ''])
3112 group, element = _MapFileToMsBuildSourceType(source,
3113 extension_to_rule_name)
3114 grouped_sources[group].append([element, {'Include': source}] + detail)
3117 def _GetMSBuildProjectReferences(project):
3119 if project.dependencies:
3120 group = ['ItemGroup']
3121 for dependency in project.dependencies:
3122 guid = dependency.guid
3123 project_dir = os.path.split(project.path)[0]
3124 relative_path = gyp.common.RelativePath(dependency.path, project_dir)
3125 project_ref = ['ProjectReference',
3126 {'Include': relative_path},
3128 ['ReferenceOutputAssembly', 'false']
3130 for config in dependency.spec.get('configurations', {}).itervalues():
3131 # If it's disabled in any config, turn it off in the reference.
3132 if config.get('msvs_2010_disable_uldi_when_referenced', 0):
3133 project_ref.append(['UseLibraryDependencyInputs', 'false'])
3135 group.append(project_ref)
3136 references.append(group)
3140 def _GenerateMSBuildProject(project, options, version, generator_flags):
3142 configurations = spec['configurations']
3143 project_dir, project_file_name = os.path.split(project.path)
3144 gyp.common.EnsureDirExists(project.path)
3145 # Prepare list of sources and excluded sources.
3146 gyp_path = _NormalizedSource(project.build_file)
3147 relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
3149 gyp_file = os.path.split(project.build_file)[1]
3150 sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
3154 props_files_of_rules = set()
3155 targets_files_of_rules = set()
3156 extension_to_rule_name = {}
3157 list_excluded = generator_flags.get('msvs_list_excluded_files', True)
3159 # Don't generate rules if we are using an external builder like ninja.
3160 if not spec.get('msvs_external_builder'):
3161 _GenerateRulesForMSBuild(project_dir, options, spec,
3162 sources, excluded_sources,
3163 props_files_of_rules, targets_files_of_rules,
3164 actions_to_add, extension_to_rule_name)
3166 rules = spec.get('rules', [])
3167 _AdjustSourcesForRules(spec, rules, sources, excluded_sources)
3169 sources, excluded_sources, excluded_idl = (
3170 _AdjustSourcesAndConvertToFilterHierarchy(spec, options,
3171 project_dir, sources,
3173 list_excluded, version))
3175 # Don't add actions if we are using an external builder like ninja.
3176 if not spec.get('msvs_external_builder'):
3177 _AddActions(actions_to_add, spec, project.build_file)
3178 _AddCopies(actions_to_add, spec)
3180 # NOTE: this stanza must appear after all actions have been decided.
3181 # Don't excluded sources with actions attached, or they won't run.
3182 excluded_sources = _FilterActionsFromExcluded(
3183 excluded_sources, actions_to_add)
3185 exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
3186 actions_spec, sources_handled_by_action = _GenerateActionsForMSBuild(
3187 spec, actions_to_add)
3189 _GenerateMSBuildFiltersFile(project.path + '.filters', sources,
3190 extension_to_rule_name)
3191 missing_sources = _VerifySourcesExist(sources, project_dir)
3193 for configuration in configurations.itervalues():
3194 _FinalizeMSBuildSettings(spec, configuration)
3196 # Add attributes to root element
3198 import_default_section = [
3199 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.Default.props'}]]
3200 import_cpp_props_section = [
3201 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.props'}]]
3202 import_cpp_targets_section = [
3203 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.targets'}]]
3204 macro_section = [['PropertyGroup', {'Label': 'UserMacros'}]]
3208 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003',
3209 'ToolsVersion': version.ProjectVersion(),
3210 'DefaultTargets': 'Build'
3213 content += _GetMSBuildProjectConfigurations(configurations)
3214 content += _GetMSBuildGlobalProperties(spec, project.guid, project_file_name)
3215 content += import_default_section
3216 content += _GetMSBuildConfigurationDetails(spec, project.build_file)
3217 content += _GetMSBuildLocalProperties(project.msbuild_toolset)
3218 content += import_cpp_props_section
3219 content += _GetMSBuildExtensions(props_files_of_rules)
3220 content += _GetMSBuildPropertySheets(configurations)
3221 content += macro_section
3222 content += _GetMSBuildConfigurationGlobalProperties(spec, configurations,
3224 content += _GetMSBuildToolSettingsSections(spec, configurations)
3225 content += _GetMSBuildSources(
3226 spec, sources, exclusions, extension_to_rule_name, actions_spec,
3227 sources_handled_by_action, list_excluded)
3228 content += _GetMSBuildProjectReferences(project)
3229 content += import_cpp_targets_section
3230 content += _GetMSBuildExtensionTargets(targets_files_of_rules)
3232 if spec.get('msvs_external_builder'):
3233 content += _GetMSBuildExternalBuilderTargets(spec)
3235 # TODO(jeanluc) File a bug to get rid of runas. We had in MSVS:
3236 # has_run_as = _WriteMSVSUserFile(project.path, version, spec)
3238 easy_xml.WriteXmlIfChanged(content, project.path, pretty=True, win32=True)
3240 return missing_sources
3243 def _GetMSBuildExternalBuilderTargets(spec):
3244 """Return a list of MSBuild targets for external builders.
3246 The "Build" and "Clean" targets are always generated. If the spec contains
3247 'msvs_external_builder_clcompile_cmd', then the "ClCompile" target will also
3248 be generated, to support building selected C/C++ files.
3251 spec: The gyp target spec.
3253 List of MSBuild 'Target' specs.
3255 build_cmd = _BuildCommandLineForRuleRaw(
3256 spec, spec['msvs_external_builder_build_cmd'],
3257 False, False, False, False)
3258 build_target = ['Target', {'Name': 'Build'}]
3259 build_target.append(['Exec', {'Command': build_cmd}])
3261 clean_cmd = _BuildCommandLineForRuleRaw(
3262 spec, spec['msvs_external_builder_clean_cmd'],
3263 False, False, False, False)
3264 clean_target = ['Target', {'Name': 'Clean'}]
3265 clean_target.append(['Exec', {'Command': clean_cmd}])
3267 targets = [build_target, clean_target]
3269 if spec.get('msvs_external_builder_clcompile_cmd'):
3270 clcompile_cmd = _BuildCommandLineForRuleRaw(
3271 spec, spec['msvs_external_builder_clcompile_cmd'],
3272 False, False, False, False)
3273 clcompile_target = ['Target', {'Name': 'ClCompile'}]
3274 clcompile_target.append(['Exec', {'Command': clcompile_cmd}])
3275 targets.append(clcompile_target)
3280 def _GetMSBuildExtensions(props_files_of_rules):
3281 extensions = ['ImportGroup', {'Label': 'ExtensionSettings'}]
3282 for props_file in props_files_of_rules:
3283 extensions.append(['Import', {'Project': props_file}])
3287 def _GetMSBuildExtensionTargets(targets_files_of_rules):
3288 targets_node = ['ImportGroup', {'Label': 'ExtensionTargets'}]
3289 for targets_file in sorted(targets_files_of_rules):
3290 targets_node.append(['Import', {'Project': targets_file}])
3291 return [targets_node]
3294 def _GenerateActionsForMSBuild(spec, actions_to_add):
3295 """Add actions accumulated into an actions_to_add, merging as needed.
3298 spec: the target project dict
3299 actions_to_add: dictionary keyed on input name, which maps to a list of
3300 dicts describing the actions attached to that input file.
3303 A pair of (action specification, the sources handled by this action).
3305 sources_handled_by_action = OrderedSet()
3307 for primary_input, actions in actions_to_add.iteritems():
3308 inputs = OrderedSet()
3309 outputs = OrderedSet()
3312 for action in actions:
3313 inputs.update(OrderedSet(action['inputs']))
3314 outputs.update(OrderedSet(action['outputs']))
3315 descriptions.append(action['description'])
3316 cmd = action['command']
3317 # For most actions, add 'call' so that actions that invoke batch files
3318 # return and continue executing. msbuild_use_call provides a way to
3319 # disable this but I have not seen any adverse effect from doing that
3321 if action.get('msbuild_use_call', True):
3323 commands.append(cmd)
3324 # Add the custom build action for one input file.
3325 description = ', and also '.join(descriptions)
3327 # We can't join the commands simply with && because the command line will
3328 # get too long. See also _AddActions: cygwin's setup_env mustn't be called
3329 # for every invocation or the command that sets the PATH will grow too
3331 command = '\r\n'.join([c + '\r\nif %errorlevel% neq 0 exit /b %errorlevel%'
3333 _AddMSBuildAction(spec,
3339 sources_handled_by_action,
3341 return actions_spec, sources_handled_by_action
3344 def _AddMSBuildAction(spec, primary_input, inputs, outputs, cmd, description,
3345 sources_handled_by_action, actions_spec):
3346 command = MSVSSettings.ConvertVCMacrosToMSBuild(cmd)
3347 primary_input = _FixPath(primary_input)
3348 inputs_array = _FixPaths(inputs)
3349 outputs_array = _FixPaths(outputs)
3350 additional_inputs = ';'.join([i for i in inputs_array
3351 if i != primary_input])
3352 outputs = ';'.join(outputs_array)
3353 sources_handled_by_action.add(primary_input)
3354 action_spec = ['CustomBuild', {'Include': primary_input}]
3356 # TODO(jeanluc) 'Document' for all or just if as_sources?
3357 [['FileType', 'Document'],
3358 ['Command', command],
3359 ['Message', description],
3360 ['Outputs', outputs]
3362 if additional_inputs:
3363 action_spec.append(['AdditionalInputs', additional_inputs])
3364 actions_spec.append(action_spec)