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(r'^[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',
87 'msvs_requires_importlibrary',
88 'msvs_enable_winphone',
89 'msvs_application_type_revision',
90 'msvs_target_platform_version',
91 'msvs_target_platform_minversion',
95 # List of precompiled header related keys.
97 'msvs_precompiled_header',
98 'msvs_precompiled_source',
102 cached_username = None
108 # TODO(gspencer): Switch the os.environ calls to be
109 # win32api.GetDomainName() and win32api.GetUserName() once the
110 # python version in depot_tools has been updated to work on Vista
112 def _GetDomainAndUserName():
113 if sys.platform not in ('win32', 'cygwin'):
114 return ('DOMAIN', 'USERNAME')
115 global cached_username
117 if not cached_domain or not cached_username:
118 domain = os.environ.get('USERDOMAIN')
119 username = os.environ.get('USERNAME')
120 if not domain or not username:
121 call = subprocess.Popen(['net', 'config', 'Workstation'],
122 stdout=subprocess.PIPE)
123 config = call.communicate()[0]
124 username_re = re.compile(r'^User name\s+(\S+)', re.MULTILINE)
125 username_match = username_re.search(config)
127 username = username_match.group(1)
128 domain_re = re.compile(r'^Logon domain\s+(\S+)', re.MULTILINE)
129 domain_match = domain_re.search(config)
131 domain = domain_match.group(1)
132 cached_domain = domain
133 cached_username = username
134 return (cached_domain, cached_username)
136 fixpath_prefix = None
139 def _NormalizedSource(source):
140 """Normalize the path.
142 But not if that gets rid of a variable, as this may expand to something
143 larger than one directory.
146 source: The path to be normalize.d
151 normalized = os.path.normpath(source)
152 if source.count('$') == normalized.count('$'):
158 """Convert paths to a form that will make sense in a vcproj file.
161 path: The path to convert, may contain / etc.
163 The path with all slashes made into backslashes.
165 if fixpath_prefix and path and not os.path.isabs(path) and not path[0] == '$':
166 path = os.path.join(fixpath_prefix, path)
167 path = path.replace('/', '\\')
168 path = _NormalizedSource(path)
169 if path and path[-1] == '\\':
174 def _FixPaths(paths):
175 """Fix each of the paths of the list."""
176 return [_FixPath(i) for i in paths]
179 def _ConvertSourcesToFilterHierarchy(sources, prefix=None, excluded=None,
180 list_excluded=True, msvs_version=None):
181 """Converts a list split source file paths into a vcproj folder hierarchy.
184 sources: A list of source file paths split.
185 prefix: A list of source file path layers meant to apply to each of sources.
186 excluded: A set of excluded files.
187 msvs_version: A MSVSVersion object.
190 A hierarchy of filenames and MSVSProject.Filter objects that matches the
191 layout of the source tree.
193 _ConvertSourcesToFilterHierarchy([['a', 'bob1.c'], ['b', 'bob2.c']],
196 [MSVSProject.Filter('a', contents=['joe\\a\\bob1.c']),
197 MSVSProject.Filter('b', contents=['joe\\b\\bob2.c'])]
199 if not prefix: prefix = []
202 folders = OrderedDict()
203 # Gather files into the final result, excluded, or folders.
206 filename = _NormalizedSource('\\'.join(prefix + s))
207 if filename in excluded:
208 excluded_result.append(filename)
210 result.append(filename)
211 elif msvs_version and not msvs_version.UsesVcxproj():
212 # For MSVS 2008 and earlier, we need to process all files before walking
214 if not folders.get(s[0]):
216 folders[s[0]].append(s[1:])
218 contents = _ConvertSourcesToFilterHierarchy([s[1:]], prefix + [s[0]],
220 list_excluded=list_excluded,
221 msvs_version=msvs_version)
222 contents = MSVSProject.Filter(s[0], contents=contents)
223 result.append(contents)
224 # Add a folder for excluded files.
225 if excluded_result and list_excluded:
226 excluded_folder = MSVSProject.Filter('_excluded_files',
227 contents=excluded_result)
228 result.append(excluded_folder)
230 if msvs_version and msvs_version.UsesVcxproj():
233 # Populate all the folders.
235 contents = _ConvertSourcesToFilterHierarchy(folders[f], prefix=prefix + [f],
237 list_excluded=list_excluded,
238 msvs_version=msvs_version)
239 contents = MSVSProject.Filter(f, contents=contents)
240 result.append(contents)
244 def _ToolAppend(tools, tool_name, setting, value, only_if_unset=False):
246 _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset)
249 def _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset=False):
250 # TODO(bradnelson): ugly hack, fix this more generally!!!
251 if 'Directories' in setting or 'Dependencies' in setting:
252 if type(value) == str:
253 value = value.replace('/', '\\')
255 value = [i.replace('/', '\\') for i in value]
256 if not tools.get(tool_name):
257 tools[tool_name] = dict()
258 tool = tools[tool_name]
259 if tool.get(setting):
260 if only_if_unset: return
261 if type(tool[setting]) == list and type(value) == list:
262 tool[setting] += value
265 'Appending "%s" to a non-list setting "%s" for tool "%s" is '
266 'not allowed, previous value: %s' % (
267 value, setting, tool_name, str(tool[setting])))
269 tool[setting] = value
272 def _ConfigPlatform(config_data):
273 return config_data.get('msvs_configuration_platform', 'Win32')
276 def _ConfigBaseName(config_name, platform_name):
277 if config_name.endswith('_' + platform_name):
278 return config_name[0:-len(platform_name) - 1]
283 def _ConfigFullName(config_name, config_data):
284 platform_name = _ConfigPlatform(config_data)
285 return '%s|%s' % (_ConfigBaseName(config_name, platform_name), platform_name)
288 def _BuildCommandLineForRuleRaw(spec, cmd, cygwin_shell, has_input_path,
289 quote_cmd, do_setup_env):
291 if [x for x in cmd if '$(InputDir)' in x]:
292 input_dir_preamble = (
293 'set INPUTDIR=$(InputDir)\n'
294 'if NOT DEFINED INPUTDIR set INPUTDIR=.\\\n'
295 'set INPUTDIR=%INPUTDIR:~0,-1%\n'
298 input_dir_preamble = ''
301 # Find path to cygwin.
302 cygwin_dir = _FixPath(spec.get('msvs_cygwin_dirs', ['.'])[0])
305 direct_cmd = [i.replace('$(IntDir)',
306 '`cygpath -m "${INTDIR}"`') for i in direct_cmd]
307 direct_cmd = [i.replace('$(OutDir)',
308 '`cygpath -m "${OUTDIR}"`') for i in direct_cmd]
309 direct_cmd = [i.replace('$(InputDir)',
310 '`cygpath -m "${INPUTDIR}"`') for i in direct_cmd]
312 direct_cmd = [i.replace('$(InputPath)',
313 '`cygpath -m "${INPUTPATH}"`')
315 direct_cmd = ['\\"%s\\"' % i.replace('"', '\\\\\\"') for i in direct_cmd]
316 # direct_cmd = gyp.common.EncodePOSIXShellList(direct_cmd)
317 direct_cmd = ' '.join(direct_cmd)
318 # TODO(quote): regularize quoting path names throughout the module
321 cmd += 'call "$(ProjectDir)%(cygwin_dir)s\\setup_env.bat" && '
322 cmd += 'set CYGWIN=nontsec&& '
323 if direct_cmd.find('NUMBER_OF_PROCESSORS') >= 0:
324 cmd += 'set /a NUMBER_OF_PROCESSORS_PLUS_1=%%NUMBER_OF_PROCESSORS%%+1&& '
325 if direct_cmd.find('INTDIR') >= 0:
326 cmd += 'set INTDIR=$(IntDir)&& '
327 if direct_cmd.find('OUTDIR') >= 0:
328 cmd += 'set OUTDIR=$(OutDir)&& '
329 if has_input_path and direct_cmd.find('INPUTPATH') >= 0:
330 cmd += 'set INPUTPATH=$(InputPath) && '
331 cmd += 'bash -c "%(cmd)s"'
332 cmd = cmd % {'cygwin_dir': cygwin_dir,
334 return input_dir_preamble + cmd
336 # Convert cat --> type to mimic unix.
340 command = [cmd[0].replace('/', '\\')]
341 # Add call before command to ensure that commands can be tied together one
342 # after the other without aborting in Incredibuild, since IB makes a bat
343 # file out of the raw command string, and some commands (like python) are
344 # actually batch files themselves.
345 command.insert(0, 'call')
347 # TODO(quote): This is a really ugly heuristic, and will miss path fixing
348 # for arguments like "--arg=path" or "/opt:path".
349 # If the argument starts with a slash or dash, it's probably a command line
351 arguments = [i if (i[:1] in "/-") else _FixPath(i) for i in cmd[1:]]
352 arguments = [i.replace('$(InputDir)', '%INPUTDIR%') for i in arguments]
353 arguments = [MSVSSettings.FixVCMacroSlashes(i) for i in arguments]
355 # Support a mode for using cmd directly.
356 # Convert any paths to native form (first element is used directly).
357 # TODO(quote): regularize quoting path names throughout the module
358 arguments = ['"%s"' % i for i in arguments]
359 # Collapse into a single command.
360 return input_dir_preamble + ' '.join(command + arguments)
363 def _BuildCommandLineForRule(spec, rule, has_input_path, do_setup_env):
364 # Currently this weird argument munging is used to duplicate the way a
365 # python script would need to be run as part of the chrome tree.
366 # Eventually we should add some sort of rule_default option to set this
367 # per project. For now the behavior chrome needs is the default.
368 mcs = rule.get('msvs_cygwin_shell')
370 mcs = int(spec.get('msvs_cygwin_shell', 1))
371 elif isinstance(mcs, str):
373 quote_cmd = int(rule.get('msvs_quote_cmd', 1))
374 return _BuildCommandLineForRuleRaw(spec, rule['action'], mcs, has_input_path,
375 quote_cmd, do_setup_env=do_setup_env)
378 def _AddActionStep(actions_dict, inputs, outputs, description, command):
379 """Merge action into an existing list of actions.
381 Care must be taken so that actions which have overlapping inputs either don't
382 get assigned to the same input, or get collapsed into one.
385 actions_dict: dictionary keyed on input name, which maps to a list of
386 dicts describing the actions attached to that input file.
387 inputs: list of inputs
388 outputs: list of outputs
389 description: description of the action
390 command: command line to execute
392 # Require there to be at least one input (call sites will ensure this).
398 'description': description,
402 # Pick where to stick this action.
403 # While less than optimal in terms of build time, attach them to the first
405 chosen_input = inputs[0]
408 if chosen_input not in actions_dict:
409 actions_dict[chosen_input] = []
410 actions_dict[chosen_input].append(action)
413 def _AddCustomBuildToolForMSVS(p, spec, primary_input,
414 inputs, outputs, description, cmd):
415 """Add a custom build tool to execute something.
418 p: the target project
419 spec: the target project dict
420 primary_input: input file to attach the build tool to
421 inputs: list of inputs
422 outputs: list of outputs
423 description: description of the action
424 cmd: command line to execute
426 inputs = _FixPaths(inputs)
427 outputs = _FixPaths(outputs)
428 tool = MSVSProject.Tool(
430 {'Description': description,
431 'AdditionalDependencies': ';'.join(inputs),
432 'Outputs': ';'.join(outputs),
435 # Add to the properties of primary input for each config.
436 for config_name, c_data in spec['configurations'].iteritems():
437 p.AddFileConfig(_FixPath(primary_input),
438 _ConfigFullName(config_name, c_data), tools=[tool])
441 def _AddAccumulatedActionsToMSVS(p, spec, actions_dict):
442 """Add actions accumulated into an actions_dict, merging as needed.
445 p: the target project
446 spec: the target project dict
447 actions_dict: dictionary keyed on input name, which maps to a list of
448 dicts describing the actions attached to that input file.
450 for primary_input in actions_dict:
451 inputs = OrderedSet()
452 outputs = OrderedSet()
455 for action in actions_dict[primary_input]:
456 inputs.update(OrderedSet(action['inputs']))
457 outputs.update(OrderedSet(action['outputs']))
458 descriptions.append(action['description'])
459 commands.append(action['command'])
460 # Add the custom build step for one input file.
461 description = ', and also '.join(descriptions)
462 command = '\r\n'.join(commands)
463 _AddCustomBuildToolForMSVS(p, spec,
464 primary_input=primary_input,
467 description=description,
471 def _RuleExpandPath(path, input_file):
472 """Given the input file to which a rule applied, string substitute a path.
475 path: a path to string expand
476 input_file: the file to which the rule applied.
478 The string substituted path.
480 path = path.replace('$(InputName)',
481 os.path.splitext(os.path.split(input_file)[1])[0])
482 path = path.replace('$(InputDir)', os.path.dirname(input_file))
483 path = path.replace('$(InputExt)',
484 os.path.splitext(os.path.split(input_file)[1])[1])
485 path = path.replace('$(InputFileName)', os.path.split(input_file)[1])
486 path = path.replace('$(InputPath)', input_file)
490 def _FindRuleTriggerFiles(rule, sources):
491 """Find the list of files which a particular rule applies to.
494 rule: the rule in question
495 sources: the set of all known source files for this project
497 The list of sources that trigger a particular rule.
499 return rule.get('rule_sources', [])
502 def _RuleInputsAndOutputs(rule, trigger_file):
503 """Find the inputs and outputs generated by a rule.
506 rule: the rule in question.
507 trigger_file: the main trigger for this rule.
509 The pair of (inputs, outputs) involved in this rule.
511 raw_inputs = _FixPaths(rule.get('inputs', []))
512 raw_outputs = _FixPaths(rule.get('outputs', []))
513 inputs = OrderedSet()
514 outputs = OrderedSet()
515 inputs.add(trigger_file)
517 inputs.add(_RuleExpandPath(i, trigger_file))
518 for o in raw_outputs:
519 outputs.add(_RuleExpandPath(o, trigger_file))
520 return (inputs, outputs)
523 def _GenerateNativeRulesForMSVS(p, rules, output_dir, spec, options):
524 """Generate a native rules file.
527 p: the target project
528 rules: the set of rules to include
529 output_dir: the directory in which the project/gyp resides
530 spec: the project dict
531 options: global generator options
533 rules_filename = '%s%s.rules' % (spec['target_name'],
535 rules_file = MSVSToolFile.Writer(os.path.join(output_dir, rules_filename),
539 rule_name = r['rule_name']
540 rule_ext = r['extension']
541 inputs = _FixPaths(r.get('inputs', []))
542 outputs = _FixPaths(r.get('outputs', []))
543 # Skip a rule with no action and no inputs.
544 if 'action' not in r and not r.get('rule_sources', []):
546 cmd = _BuildCommandLineForRule(spec, r, has_input_path=True,
548 rules_file.AddCustomBuildRule(name=rule_name,
549 description=r.get('message', rule_name),
550 extensions=[rule_ext],
551 additional_dependencies=inputs,
554 # Write out rules file.
555 rules_file.WriteIfChanged()
557 # Add rules file to project.
558 p.AddToolFile(rules_filename)
561 def _Cygwinify(path):
562 path = path.replace('$(OutDir)', '$(OutDirCygwin)')
563 path = path.replace('$(IntDir)', '$(IntDirCygwin)')
567 def _GenerateExternalRules(rules, output_dir, spec,
568 sources, options, actions_to_add):
569 """Generate an external makefile to do a set of rules.
572 rules: the list of rules to include
573 output_dir: path containing project and gyp files
574 spec: project specification data
575 sources: set of sources known
576 options: global generator options
577 actions_to_add: The list of actions we will add to.
579 filename = '%s_rules%s.mk' % (spec['target_name'], options.suffix)
580 mk_file = gyp.common.WriteOnDiff(os.path.join(output_dir, filename))
581 # Find cygwin style versions of some paths.
582 mk_file.write('OutDirCygwin:=$(shell cygpath -u "$(OutDir)")\n')
583 mk_file.write('IntDirCygwin:=$(shell cygpath -u "$(IntDir)")\n')
584 # Gather stuff needed to emit all: target.
585 all_inputs = OrderedSet()
586 all_outputs = OrderedSet()
587 all_output_dirs = OrderedSet()
590 trigger_files = _FindRuleTriggerFiles(rule, sources)
591 for tf in trigger_files:
592 inputs, outputs = _RuleInputsAndOutputs(rule, tf)
593 all_inputs.update(OrderedSet(inputs))
594 all_outputs.update(OrderedSet(outputs))
595 # Only use one target from each rule as the dependency for
596 # 'all' so we don't try to build each rule multiple times.
597 first_outputs.append(list(outputs)[0])
598 # Get the unique output directories for this rule.
599 output_dirs = [os.path.split(i)[0] for i in outputs]
600 for od in output_dirs:
601 all_output_dirs.add(od)
602 first_outputs_cyg = [_Cygwinify(i) for i in first_outputs]
603 # Write out all: target, including mkdir for each output directory.
604 mk_file.write('all: %s\n' % ' '.join(first_outputs_cyg))
605 for od in all_output_dirs:
607 mk_file.write('\tmkdir -p `cygpath -u "%s"`\n' % od)
609 # Define how each output is generated.
611 trigger_files = _FindRuleTriggerFiles(rule, sources)
612 for tf in trigger_files:
613 # Get all the inputs and outputs for this rule for this trigger file.
614 inputs, outputs = _RuleInputsAndOutputs(rule, tf)
615 inputs = [_Cygwinify(i) for i in inputs]
616 outputs = [_Cygwinify(i) for i in outputs]
617 # Prepare the command line for this rule.
618 cmd = [_RuleExpandPath(c, tf) for c in rule['action']]
619 cmd = ['"%s"' % i for i in cmd]
621 # Add it to the makefile.
622 mk_file.write('%s: %s\n' % (' '.join(outputs), ' '.join(inputs)))
623 mk_file.write('\t%s\n\n' % cmd)
627 # Add makefile to list of sources.
628 sources.add(filename)
629 # Add a build action to call makefile.
633 '-j', '${NUMBER_OF_PROCESSORS_PLUS_1}',
635 cmd = _BuildCommandLineForRuleRaw(spec, cmd, True, False, True, True)
636 # Insert makefile as 0'th input, so it gets the action attached there,
637 # as this is easier to understand from in the IDE.
638 all_inputs = list(all_inputs)
639 all_inputs.insert(0, filename)
640 _AddActionStep(actions_to_add,
641 inputs=_FixPaths(all_inputs),
642 outputs=_FixPaths(all_outputs),
643 description='Running external rules for %s' %
648 def _EscapeEnvironmentVariableExpansion(s):
649 """Escapes % characters.
651 Escapes any % characters so that Windows-style environment variable
652 expansions will leave them alone.
653 See http://connect.microsoft.com/VisualStudio/feedback/details/106127/cl-d-name-text-containing-percentage-characters-doesnt-compile
654 to understand why we have to do this.
657 s: The string to be escaped.
662 s = s.replace('%', '%%')
666 quote_replacer_regex = re.compile(r'(\\*)"')
669 def _EscapeCommandLineArgumentForMSVS(s):
670 """Escapes a Windows command-line argument.
672 So that the Win32 CommandLineToArgv function will turn the escaped result back
673 into the original string.
674 See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
675 ("Parsing C++ Command-Line Arguments") to understand why we have to do
679 s: the string to be escaped.
685 # For a literal quote, CommandLineToArgv requires an odd number of
686 # backslashes preceding it, and it produces half as many literal backslashes
687 # (rounded down). So we need to produce 2n+1 backslashes.
688 return 2 * match.group(1) + '\\"'
690 # Escape all quotes so that they are interpreted literally.
691 s = quote_replacer_regex.sub(_Replace, s)
692 # Now add unescaped quotes so that any whitespace is interpreted literally.
697 delimiters_replacer_regex = re.compile(r'(\\*)([,;]+)')
700 def _EscapeVCProjCommandLineArgListItem(s):
701 """Escapes command line arguments for MSVS.
703 The VCProj format stores string lists in a single string using commas and
704 semi-colons as separators, which must be quoted if they are to be
705 interpreted literally. However, command-line arguments may already have
706 quotes, and the VCProj parser is ignorant of the backslash escaping
707 convention used by CommandLineToArgv, so the command-line quotes and the
708 VCProj quotes may not be the same quotes. So to store a general
709 command-line argument in a VCProj list, we need to parse the existing
710 quoting according to VCProj's convention and quote any delimiters that are
711 not already quoted by that convention. The quotes that we add will also be
712 seen by CommandLineToArgv, so if backslashes precede them then we also have
713 to escape those backslashes according to the CommandLineToArgv
717 s: the string to be escaped.
723 # For a non-literal quote, CommandLineToArgv requires an even number of
724 # backslashes preceding it, and it produces half as many literal
725 # backslashes. So we need to produce 2n backslashes.
726 return 2 * match.group(1) + '"' + match.group(2) + '"'
728 segments = s.split('"')
729 # The unquoted segments are at the even-numbered indices.
730 for i in range(0, len(segments), 2):
731 segments[i] = delimiters_replacer_regex.sub(_Replace, segments[i])
732 # Concatenate back into a single string
733 s = '"'.join(segments)
734 if len(segments) % 2 == 0:
735 # String ends while still quoted according to VCProj's convention. This
736 # means the delimiter and the next list item that follow this one in the
737 # .vcproj file will be misinterpreted as part of this item. There is nothing
738 # we can do about this. Adding an extra quote would correct the problem in
739 # the VCProj but cause the same problem on the final command-line. Moving
740 # the item to the end of the list does works, but that's only possible if
741 # there's only one such item. Let's just warn the user.
742 print >> sys.stderr, ('Warning: MSVS may misinterpret the odd number of ' +
747 def _EscapeCppDefineForMSVS(s):
748 """Escapes a CPP define so that it will reach the compiler unaltered."""
749 s = _EscapeEnvironmentVariableExpansion(s)
750 s = _EscapeCommandLineArgumentForMSVS(s)
751 s = _EscapeVCProjCommandLineArgListItem(s)
752 # cl.exe replaces literal # characters with = in preprocesor definitions for
753 # some reason. Octal-encode to work around that.
754 s = s.replace('#', '\\%03o' % ord('#'))
758 quote_replacer_regex2 = re.compile(r'(\\+)"')
761 def _EscapeCommandLineArgumentForMSBuild(s):
762 """Escapes a Windows command-line argument for use by MSBuild."""
765 return (len(match.group(1)) / 2 * 4) * '\\' + '\\"'
767 # Escape all quotes so that they are interpreted literally.
768 s = quote_replacer_regex2.sub(_Replace, s)
772 def _EscapeMSBuildSpecialCharacters(s):
773 escape_dictionary = {
782 result = ''.join([escape_dictionary.get(c, c) for c in s])
786 def _EscapeCppDefineForMSBuild(s):
787 """Escapes a CPP define so that it will reach the compiler unaltered."""
788 s = _EscapeEnvironmentVariableExpansion(s)
789 s = _EscapeCommandLineArgumentForMSBuild(s)
790 s = _EscapeMSBuildSpecialCharacters(s)
791 # cl.exe replaces literal # characters with = in preprocesor definitions for
792 # some reason. Octal-encode to work around that.
793 s = s.replace('#', '\\%03o' % ord('#'))
797 def _GenerateRulesForMSVS(p, output_dir, options, spec,
798 sources, excluded_sources,
800 """Generate all the rules for a particular project.
804 output_dir: directory to emit rules to
805 options: global options passed to the generator
806 spec: the specification for this project
807 sources: the set of all known source files in this project
808 excluded_sources: the set of sources excluded from normal processing
809 actions_to_add: deferred list of actions to add in
811 rules = spec.get('rules', [])
812 rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
813 rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
815 # Handle rules that use a native rules file.
817 _GenerateNativeRulesForMSVS(p, rules_native, output_dir, spec, options)
819 # Handle external rules (non-native rules).
821 _GenerateExternalRules(rules_external, output_dir, spec,
822 sources, options, actions_to_add)
823 _AdjustSourcesForRules(rules, sources, excluded_sources, False)
826 def _AdjustSourcesForRules(rules, sources, excluded_sources, is_msbuild):
827 # Add outputs generated by each rule (if applicable).
829 # Add in the outputs from this rule.
830 trigger_files = _FindRuleTriggerFiles(rule, sources)
831 for trigger_file in trigger_files:
832 # Remove trigger_file from excluded_sources to let the rule be triggered
833 # (e.g. rule trigger ax_enums.idl is added to excluded_sources
834 # because it's also in an action's inputs in the same project)
835 excluded_sources.discard(_FixPath(trigger_file))
836 # Done if not processing outputs as sources.
837 if int(rule.get('process_outputs_as_sources', False)):
838 inputs, outputs = _RuleInputsAndOutputs(rule, trigger_file)
839 inputs = OrderedSet(_FixPaths(inputs))
840 outputs = OrderedSet(_FixPaths(outputs))
841 inputs.remove(_FixPath(trigger_file))
842 sources.update(inputs)
844 excluded_sources.update(inputs)
845 sources.update(outputs)
848 def _FilterActionsFromExcluded(excluded_sources, actions_to_add):
849 """Take inputs with actions attached out of the list of exclusions.
852 excluded_sources: list of source files not to be built.
853 actions_to_add: dict of actions keyed on source file they're attached to.
855 excluded_sources with files that have actions attached removed.
857 must_keep = OrderedSet(_FixPaths(actions_to_add.keys()))
858 return [s for s in excluded_sources if s not in must_keep]
861 def _GetDefaultConfiguration(spec):
862 return spec['configurations'][spec['default_configuration']]
865 def _GetGuidOfProject(proj_path, spec):
866 """Get the guid for the project.
869 proj_path: Path of the vcproj or vcxproj file to generate.
870 spec: The target dictionary containing the properties of the target.
874 ValueError: if the specified GUID is invalid.
876 # Pluck out the default configuration.
877 default_config = _GetDefaultConfiguration(spec)
878 # Decide the guid of the project.
879 guid = default_config.get('msvs_guid')
881 if VALID_MSVS_GUID_CHARS.match(guid) is None:
882 raise ValueError('Invalid MSVS guid: "%s". Must match regex: "%s".' %
883 (guid, VALID_MSVS_GUID_CHARS.pattern))
885 guid = guid or MSVSNew.MakeGuid(proj_path)
889 def _GetMsbuildToolsetOfProject(proj_path, spec, version):
890 """Get the platform toolset for the project.
893 proj_path: Path of the vcproj or vcxproj file to generate.
894 spec: The target dictionary containing the properties of the target.
895 version: The MSVSVersion object.
897 the platform toolset string or None.
899 # Pluck out the default configuration.
900 default_config = _GetDefaultConfiguration(spec)
901 toolset = default_config.get('msbuild_toolset')
902 if not toolset and version.DefaultToolset():
903 toolset = version.DefaultToolset()
907 def _GenerateProject(project, options, version, generator_flags):
908 """Generates a vcproj file.
911 project: the MSVSProject object.
912 options: global generator options.
913 version: the MSVSVersion object.
914 generator_flags: dict of generator-specific flags.
916 A list of source files that cannot be found on disk.
918 default_config = _GetDefaultConfiguration(project.spec)
920 # Skip emitting anything if told to with msvs_existing_vcproj option.
921 if default_config.get('msvs_existing_vcproj'):
924 if version.UsesVcxproj():
925 return _GenerateMSBuildProject(project, options, version, generator_flags)
927 return _GenerateMSVSProject(project, options, version, generator_flags)
930 # TODO: Avoid code duplication with _ValidateSourcesForOSX in make.py.
931 def _ValidateSourcesForMSVSProject(spec, version):
932 """Makes sure if duplicate basenames are not specified in the source list.
935 spec: The target dictionary containing the properties of the target.
936 version: The VisualStudioVersion object.
938 # This validation should not be applied to MSVC2010 and later.
939 assert not version.UsesVcxproj()
941 # TODO: Check if MSVC allows this for loadable_module targets.
942 if spec.get('type', None) not in ('static_library', 'shared_library'):
944 sources = spec.get('sources', [])
946 for source in sources:
947 name, ext = os.path.splitext(source)
948 is_compiled_file = ext in [
949 '.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.s', '.S']
950 if not is_compiled_file:
952 basename = os.path.basename(name) # Don't include extension.
953 basenames.setdefault(basename, []).append(source)
956 for basename, files in basenames.iteritems():
958 error += ' %s: %s\n' % (basename, ' '.join(files))
961 print('static library %s has several files with the same basename:\n' %
962 spec['target_name'] + error + 'MSVC08 cannot handle that.')
963 raise GypError('Duplicate basenames in sources section, see list above')
966 def _GenerateMSVSProject(project, options, version, generator_flags):
967 """Generates a .vcproj file. It may create .rules and .user files too.
970 project: The project object we will generate the file for.
971 options: Global options passed to the generator.
972 version: The VisualStudioVersion object.
973 generator_flags: dict of generator-specific flags.
976 gyp.common.EnsureDirExists(project.path)
978 platforms = _GetUniquePlatforms(spec)
979 p = MSVSProject.Writer(project.path, version, spec['target_name'],
980 project.guid, platforms)
982 # Get directory project file is in.
983 project_dir = os.path.split(project.path)[0]
984 gyp_path = _NormalizedSource(project.build_file)
985 relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
987 config_type = _GetMSVSConfigurationType(spec, project.build_file)
988 for config_name, config in spec['configurations'].iteritems():
989 _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config)
991 # MSVC08 and prior version cannot handle duplicate basenames in the same
993 # TODO: Take excluded sources into consideration if possible.
994 _ValidateSourcesForMSVSProject(spec, version)
996 # Prepare list of sources and excluded sources.
997 gyp_file = os.path.split(project.build_file)[1]
998 sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
1003 _GenerateRulesForMSVS(p, project_dir, options, spec,
1004 sources, excluded_sources,
1006 list_excluded = generator_flags.get('msvs_list_excluded_files', True)
1007 sources, excluded_sources, excluded_idl = (
1008 _AdjustSourcesAndConvertToFilterHierarchy(spec, options, project_dir,
1009 sources, excluded_sources,
1010 list_excluded, version))
1013 missing_sources = _VerifySourcesExist(sources, project_dir)
1016 _AddToolFilesToMSVS(p, spec)
1017 _HandlePreCompiledHeaders(p, sources, spec)
1018 _AddActions(actions_to_add, spec, relative_path_of_gyp_file)
1019 _AddCopies(actions_to_add, spec)
1020 _WriteMSVSUserFile(project.path, version, spec)
1022 # NOTE: this stanza must appear after all actions have been decided.
1023 # Don't excluded sources with actions attached, or they won't run.
1024 excluded_sources = _FilterActionsFromExcluded(
1025 excluded_sources, actions_to_add)
1026 _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
1028 _AddAccumulatedActionsToMSVS(p, spec, actions_to_add)
1033 return missing_sources
1036 def _GetUniquePlatforms(spec):
1037 """Returns the list of unique platforms for this spec, e.g ['win32', ...].
1040 spec: The target dictionary containing the properties of the target.
1042 The MSVSUserFile object created.
1044 # Gather list of unique platforms.
1045 platforms = OrderedSet()
1046 for configuration in spec['configurations']:
1047 platforms.add(_ConfigPlatform(spec['configurations'][configuration]))
1048 platforms = list(platforms)
1052 def _CreateMSVSUserFile(proj_path, version, spec):
1053 """Generates a .user file for the user running this Gyp program.
1056 proj_path: The path of the project file being created. The .user file
1057 shares the same path (with an appropriate suffix).
1058 version: The VisualStudioVersion object.
1059 spec: The target dictionary containing the properties of the target.
1061 The MSVSUserFile object created.
1063 (domain, username) = _GetDomainAndUserName()
1064 vcuser_filename = '.'.join([proj_path, domain, username, 'user'])
1065 user_file = MSVSUserFile.Writer(vcuser_filename, version,
1066 spec['target_name'])
1070 def _GetMSVSConfigurationType(spec, build_file):
1071 """Returns the configuration type for this project.
1073 It's a number defined by Microsoft. May raise an exception.
1076 spec: The target dictionary containing the properties of the target.
1077 build_file: The path of the gyp file.
1079 An integer, the configuration type.
1083 'executable': '1', # .exe
1084 'shared_library': '2', # .dll
1085 'loadable_module': '2', # .dll
1086 'static_library': '4', # .lib
1087 'none': '10', # Utility type
1090 if spec.get('type'):
1091 raise GypError('Target type %s is not a valid target type for '
1092 'target %s in %s.' %
1093 (spec['type'], spec['target_name'], build_file))
1095 raise GypError('Missing type field for target %s in %s.' %
1096 (spec['target_name'], build_file))
1100 def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config):
1101 """Adds a configuration to the MSVS project.
1103 Many settings in a vcproj file are specific to a configuration. This
1104 function the main part of the vcproj file that's configuration specific.
1107 p: The target project being generated.
1108 spec: The target dictionary containing the properties of the target.
1109 config_type: The configuration type, a number as defined by Microsoft.
1110 config_name: The name of the configuration.
1111 config: The dictionary that defines the special processing to be done
1112 for this configuration.
1114 # Get the information for this configuration
1115 include_dirs, midl_include_dirs, resource_include_dirs = \
1116 _GetIncludeDirs(config)
1117 libraries = _GetLibraries(spec)
1118 library_dirs = _GetLibraryDirs(config)
1119 out_file, vc_tool, _ = _GetOutputFilePathAndTool(spec, msbuild=False)
1120 defines = _GetDefines(config)
1121 defines = [_EscapeCppDefineForMSVS(d) for d in defines]
1122 disabled_warnings = _GetDisabledWarnings(config)
1123 prebuild = config.get('msvs_prebuild')
1124 postbuild = config.get('msvs_postbuild')
1125 def_file = _GetModuleDefinition(spec)
1126 precompiled_header = config.get('msvs_precompiled_header')
1128 # Prepare the list of tools as a dictionary.
1130 # Add in user specified msvs_settings.
1131 msvs_settings = config.get('msvs_settings', {})
1132 MSVSSettings.ValidateMSVSSettings(msvs_settings)
1134 # Prevent default library inheritance from the environment.
1135 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', ['$(NOINHERIT)'])
1137 for tool in msvs_settings:
1138 settings = config['msvs_settings'][tool]
1139 for setting in settings:
1140 _ToolAppend(tools, tool, setting, settings[setting])
1141 # Add the information to the appropriate tool
1142 _ToolAppend(tools, 'VCCLCompilerTool',
1143 'AdditionalIncludeDirectories', include_dirs)
1144 _ToolAppend(tools, 'VCMIDLTool',
1145 'AdditionalIncludeDirectories', midl_include_dirs)
1146 _ToolAppend(tools, 'VCResourceCompilerTool',
1147 'AdditionalIncludeDirectories', resource_include_dirs)
1149 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', libraries)
1150 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalLibraryDirectories',
1153 _ToolAppend(tools, vc_tool, 'OutputFile', out_file, only_if_unset=True)
1155 _ToolAppend(tools, 'VCCLCompilerTool', 'PreprocessorDefinitions', defines)
1156 _ToolAppend(tools, 'VCResourceCompilerTool', 'PreprocessorDefinitions',
1158 # Change program database directory to prevent collisions.
1159 _ToolAppend(tools, 'VCCLCompilerTool', 'ProgramDataBaseFileName',
1160 '$(IntDir)$(ProjectName)\\vc80.pdb', only_if_unset=True)
1161 # Add disabled warnings.
1162 _ToolAppend(tools, 'VCCLCompilerTool',
1163 'DisableSpecificWarnings', disabled_warnings)
1165 _ToolAppend(tools, 'VCPreBuildEventTool', 'CommandLine', prebuild)
1167 _ToolAppend(tools, 'VCPostBuildEventTool', 'CommandLine', postbuild)
1168 # Turn on precompiled headers if appropriate.
1169 if precompiled_header:
1170 precompiled_header = os.path.split(precompiled_header)[1]
1171 _ToolAppend(tools, 'VCCLCompilerTool', 'UsePrecompiledHeader', '2')
1172 _ToolAppend(tools, 'VCCLCompilerTool',
1173 'PrecompiledHeaderThrough', precompiled_header)
1174 _ToolAppend(tools, 'VCCLCompilerTool',
1175 'ForcedIncludeFiles', precompiled_header)
1176 # Loadable modules don't generate import libraries;
1177 # tell dependent projects to not expect one.
1178 if spec['type'] == 'loadable_module':
1179 _ToolAppend(tools, 'VCLinkerTool', 'IgnoreImportLibrary', 'true')
1180 # Set the module definition file if any.
1182 _ToolAppend(tools, 'VCLinkerTool', 'ModuleDefinitionFile', def_file)
1184 _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name)
1187 def _GetIncludeDirs(config):
1188 """Returns the list of directories to be used for #include directives.
1191 config: The dictionary that defines the special processing to be done
1192 for this configuration.
1194 The list of directory paths.
1196 # TODO(bradnelson): include_dirs should really be flexible enough not to
1197 # require this sort of thing.
1199 config.get('include_dirs', []) +
1200 config.get('msvs_system_include_dirs', []))
1201 midl_include_dirs = (
1202 config.get('midl_include_dirs', []) +
1203 config.get('msvs_system_include_dirs', []))
1204 resource_include_dirs = config.get('resource_include_dirs', include_dirs)
1205 include_dirs = _FixPaths(include_dirs)
1206 midl_include_dirs = _FixPaths(midl_include_dirs)
1207 resource_include_dirs = _FixPaths(resource_include_dirs)
1208 return include_dirs, midl_include_dirs, resource_include_dirs
1211 def _GetLibraryDirs(config):
1212 """Returns the list of directories to be used for library search paths.
1215 config: The dictionary that defines the special processing to be done
1216 for this configuration.
1218 The list of directory paths.
1221 library_dirs = config.get('library_dirs', [])
1222 library_dirs = _FixPaths(library_dirs)
1226 def _GetLibraries(spec):
1227 """Returns the list of libraries for this configuration.
1230 spec: The target dictionary containing the properties of the target.
1232 The list of directory paths.
1234 libraries = spec.get('libraries', [])
1235 # Strip out -l, as it is not used on windows (but is needed so we can pass
1236 # in libraries that are assumed to be in the default library path).
1237 # Also remove duplicate entries, leaving only the last duplicate, while
1239 found = OrderedSet()
1240 unique_libraries_list = []
1241 for entry in reversed(libraries):
1242 library = re.sub(r'^\-l', '', entry)
1243 if not os.path.splitext(library)[1]:
1245 if library not in found:
1247 unique_libraries_list.append(library)
1248 unique_libraries_list.reverse()
1249 return unique_libraries_list
1252 def _GetOutputFilePathAndTool(spec, msbuild):
1253 """Returns the path and tool to use for this target.
1255 Figures out the path of the file this spec will create and the name of
1256 the VC tool that will create it.
1259 spec: The target dictionary containing the properties of the target.
1261 A triple of (file path, name of the vc tool, name of the msbuild tool)
1263 # Select a name for the output file.
1268 'executable': ('VCLinkerTool', 'Link', '$(OutDir)', '.exe'),
1269 'shared_library': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
1270 'loadable_module': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
1271 'static_library': ('VCLibrarianTool', 'Lib', '$(OutDir)lib\\', '.lib'),
1273 output_file_props = output_file_map.get(spec['type'])
1274 if output_file_props and int(spec.get('msvs_auto_output_file', 1)):
1275 vc_tool, msbuild_tool, out_dir, suffix = output_file_props
1276 if spec.get('standalone_static_library', 0):
1277 out_dir = '$(OutDir)'
1278 out_dir = spec.get('product_dir', out_dir)
1279 product_extension = spec.get('product_extension')
1280 if product_extension:
1281 suffix = '.' + product_extension
1283 suffix = '$(TargetExt)'
1284 prefix = spec.get('product_prefix', '')
1285 product_name = spec.get('product_name', '$(ProjectName)')
1286 out_file = ntpath.join(out_dir, prefix + product_name + suffix)
1287 return out_file, vc_tool, msbuild_tool
1290 def _GetOutputTargetExt(spec):
1291 """Returns the extension for this target, including the dot
1293 If product_extension is specified, set target_extension to this to avoid
1294 MSB8012, returns None otherwise. Ignores any target_extension settings in
1298 spec: The target dictionary containing the properties of the target.
1300 A string with the extension, or None
1302 target_extension = spec.get('product_extension')
1303 if target_extension:
1304 return '.' + target_extension
1308 def _GetDefines(config):
1309 """Returns the list of preprocessor definitions for this configuation.
1312 config: The dictionary that defines the special processing to be done
1313 for this configuration.
1315 The list of preprocessor definitions.
1318 for d in config.get('defines', []):
1320 fd = '='.join([str(dpart) for dpart in d])
1327 def _GetDisabledWarnings(config):
1328 return [str(i) for i in config.get('msvs_disabled_warnings', [])]
1331 def _GetModuleDefinition(spec):
1333 if spec['type'] in ['shared_library', 'loadable_module', 'executable']:
1334 def_files = [s for s in spec.get('sources', []) if s.endswith('.def')]
1335 if len(def_files) == 1:
1336 def_file = _FixPath(def_files[0])
1339 'Multiple module definition files in one target, target %s lists '
1340 'multiple .def files: %s' % (
1341 spec['target_name'], ' '.join(def_files)))
1345 def _ConvertToolsToExpectedForm(tools):
1346 """Convert tools to a form expected by Visual Studio.
1349 tools: A dictionary of settings; the tool name is the key.
1351 A list of Tool objects.
1354 for tool, settings in tools.iteritems():
1355 # Collapse settings with lists.
1357 for setting, value in settings.iteritems():
1358 if type(value) == list:
1359 if ((tool == 'VCLinkerTool' and
1360 setting == 'AdditionalDependencies') or
1361 setting == 'AdditionalOptions'):
1362 settings_fixed[setting] = ' '.join(value)
1364 settings_fixed[setting] = ';'.join(value)
1366 settings_fixed[setting] = value
1368 tool_list.append(MSVSProject.Tool(tool, settings_fixed))
1372 def _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name):
1373 """Add to the project file the configuration specified by config.
1376 p: The target project being generated.
1377 spec: the target project dict.
1378 tools: A dictionary of settings; the tool name is the key.
1379 config: The dictionary that defines the special processing to be done
1380 for this configuration.
1381 config_type: The configuration type, a number as defined by Microsoft.
1382 config_name: The name of the configuration.
1384 attributes = _GetMSVSAttributes(spec, config, config_type)
1385 # Add in this configuration.
1386 tool_list = _ConvertToolsToExpectedForm(tools)
1387 p.AddConfig(_ConfigFullName(config_name, config),
1388 attrs=attributes, tools=tool_list)
1391 def _GetMSVSAttributes(spec, config, config_type):
1392 # Prepare configuration attributes.
1394 source_attrs = config.get('msvs_configuration_attributes', {})
1395 for a in source_attrs:
1396 prepared_attrs[a] = source_attrs[a]
1398 vsprops_dirs = config.get('msvs_props', [])
1399 vsprops_dirs = _FixPaths(vsprops_dirs)
1401 prepared_attrs['InheritedPropertySheets'] = ';'.join(vsprops_dirs)
1402 # Set configuration type.
1403 prepared_attrs['ConfigurationType'] = config_type
1404 output_dir = prepared_attrs.get('OutputDirectory',
1405 '$(SolutionDir)$(ConfigurationName)')
1406 prepared_attrs['OutputDirectory'] = _FixPath(output_dir) + '\\'
1407 if 'IntermediateDirectory' not in prepared_attrs:
1408 intermediate = '$(ConfigurationName)\\obj\\$(ProjectName)'
1409 prepared_attrs['IntermediateDirectory'] = _FixPath(intermediate) + '\\'
1411 intermediate = _FixPath(prepared_attrs['IntermediateDirectory']) + '\\'
1412 intermediate = MSVSSettings.FixVCMacroSlashes(intermediate)
1413 prepared_attrs['IntermediateDirectory'] = intermediate
1414 return prepared_attrs
1417 def _AddNormalizedSources(sources_set, sources_array):
1418 sources_set.update(_NormalizedSource(s) for s in sources_array)
1421 def _PrepareListOfSources(spec, generator_flags, gyp_file):
1422 """Prepare list of sources and excluded sources.
1424 Besides the sources specified directly in the spec, adds the gyp file so
1425 that a change to it will cause a re-compile. Also adds appropriate sources
1426 for actions and copies. Assumes later stage will un-exclude files which
1427 have custom build steps attached.
1430 spec: The target dictionary containing the properties of the target.
1431 gyp_file: The name of the gyp file.
1433 A pair of (list of sources, list of excluded sources).
1434 The sources will be relative to the gyp file.
1436 sources = OrderedSet()
1437 _AddNormalizedSources(sources, spec.get('sources', []))
1438 excluded_sources = OrderedSet()
1439 # Add in the gyp file.
1440 if not generator_flags.get('standalone'):
1441 sources.add(gyp_file)
1443 # Add in 'action' inputs and outputs.
1444 for a in spec.get('actions', []):
1445 inputs = a['inputs']
1446 inputs = [_NormalizedSource(i) for i in inputs]
1447 # Add all inputs to sources and excluded sources.
1448 inputs = OrderedSet(inputs)
1449 sources.update(inputs)
1450 if not spec.get('msvs_external_builder'):
1451 excluded_sources.update(inputs)
1452 if int(a.get('process_outputs_as_sources', False)):
1453 _AddNormalizedSources(sources, a.get('outputs', []))
1454 # Add in 'copies' inputs and outputs.
1455 for cpy in spec.get('copies', []):
1456 _AddNormalizedSources(sources, cpy.get('files', []))
1457 return (sources, excluded_sources)
1460 def _AdjustSourcesAndConvertToFilterHierarchy(
1461 spec, options, gyp_dir, sources, excluded_sources, list_excluded, version):
1462 """Adjusts the list of sources and excluded sources.
1464 Also converts the sets to lists.
1467 spec: The target dictionary containing the properties of the target.
1468 options: Global generator options.
1469 gyp_dir: The path to the gyp file being processed.
1470 sources: A set of sources to be included for this project.
1471 excluded_sources: A set of sources to be excluded for this project.
1472 version: A MSVSVersion object.
1474 A trio of (list of sources, list of excluded sources,
1475 path of excluded IDL file)
1477 # Exclude excluded sources coming into the generator.
1478 excluded_sources.update(OrderedSet(spec.get('sources_excluded', [])))
1479 # Add excluded sources into sources for good measure.
1480 sources.update(excluded_sources)
1481 # Convert to proper windows form.
1482 # NOTE: sources goes from being a set to a list here.
1483 # NOTE: excluded_sources goes from being a set to a list here.
1484 sources = _FixPaths(sources)
1485 # Convert to proper windows form.
1486 excluded_sources = _FixPaths(excluded_sources)
1488 excluded_idl = _IdlFilesHandledNonNatively(spec, sources)
1490 precompiled_related = _GetPrecompileRelatedFiles(spec)
1491 # Find the excluded ones, minus the precompiled header related ones.
1492 fully_excluded = [i for i in excluded_sources if i not in precompiled_related]
1494 # Convert to folders and the right slashes.
1495 sources = [i.split('\\') for i in sources]
1496 sources = _ConvertSourcesToFilterHierarchy(sources, excluded=fully_excluded,
1497 list_excluded=list_excluded,
1498 msvs_version=version)
1500 # Prune filters with a single child to flatten ugly directory structures
1501 # such as ../../src/modules/module1 etc.
1502 if version.UsesVcxproj():
1503 while all([isinstance(s, MSVSProject.Filter) for s in sources]) \
1504 and len(set([s.name for s in sources])) == 1:
1505 assert all([len(s.contents) == 1 for s in sources])
1506 sources = [s.contents[0] for s in sources]
1508 while len(sources) == 1 and isinstance(sources[0], MSVSProject.Filter):
1509 sources = sources[0].contents
1511 return sources, excluded_sources, excluded_idl
1514 def _IdlFilesHandledNonNatively(spec, sources):
1515 # If any non-native rules use 'idl' as an extension exclude idl files.
1516 # Gather a list here to use later.
1518 for rule in spec.get('rules', []):
1519 if rule['extension'] == 'idl' and int(rule.get('msvs_external_rule', 0)):
1523 excluded_idl = [i for i in sources if i.endswith('.idl')]
1529 def _GetPrecompileRelatedFiles(spec):
1530 # Gather a list of precompiled header related sources.
1531 precompiled_related = []
1532 for _, config in spec['configurations'].iteritems():
1533 for k in precomp_keys:
1536 precompiled_related.append(_FixPath(f))
1537 return precompiled_related
1540 def _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
1542 exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
1543 for file_name, excluded_configs in exclusions.iteritems():
1544 if (not list_excluded and
1545 len(excluded_configs) == len(spec['configurations'])):
1546 # If we're not listing excluded files, then they won't appear in the
1547 # project, so don't try to configure them to be excluded.
1550 for config_name, config in excluded_configs:
1551 p.AddFileConfig(file_name, _ConfigFullName(config_name, config),
1552 {'ExcludedFromBuild': 'true'})
1555 def _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl):
1557 # Exclude excluded sources from being built.
1558 for f in excluded_sources:
1559 excluded_configs = []
1560 for config_name, config in spec['configurations'].iteritems():
1561 precomped = [_FixPath(config.get(i, '')) for i in precomp_keys]
1562 # Don't do this for ones that are precompiled header related.
1563 if f not in precomped:
1564 excluded_configs.append((config_name, config))
1565 exclusions[f] = excluded_configs
1566 # If any non-native rules use 'idl' as an extension exclude idl files.
1568 for f in excluded_idl:
1569 excluded_configs = []
1570 for config_name, config in spec['configurations'].iteritems():
1571 excluded_configs.append((config_name, config))
1572 exclusions[f] = excluded_configs
1576 def _AddToolFilesToMSVS(p, spec):
1577 # Add in tool files (rules).
1578 tool_files = OrderedSet()
1579 for _, config in spec['configurations'].iteritems():
1580 for f in config.get('msvs_tool_files', []):
1582 for f in tool_files:
1586 def _HandlePreCompiledHeaders(p, sources, spec):
1587 # Pre-compiled header source stubs need a different compiler flag
1588 # (generate precompiled header) and any source file not of the same
1589 # kind (i.e. C vs. C++) as the precompiled header source stub needs
1590 # to have use of precompiled headers disabled.
1591 extensions_excluded_from_precompile = []
1592 for config_name, config in spec['configurations'].iteritems():
1593 source = config.get('msvs_precompiled_source')
1595 source = _FixPath(source)
1596 # UsePrecompiledHeader=1 for if using precompiled headers.
1597 tool = MSVSProject.Tool('VCCLCompilerTool',
1598 {'UsePrecompiledHeader': '1'})
1599 p.AddFileConfig(source, _ConfigFullName(config_name, config),
1601 basename, extension = os.path.splitext(source)
1602 if extension == '.c':
1603 extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
1605 extensions_excluded_from_precompile = ['.c']
1606 def DisableForSourceTree(source_tree):
1607 for source in source_tree:
1608 if isinstance(source, MSVSProject.Filter):
1609 DisableForSourceTree(source.contents)
1611 basename, extension = os.path.splitext(source)
1612 if extension in extensions_excluded_from_precompile:
1613 for config_name, config in spec['configurations'].iteritems():
1614 tool = MSVSProject.Tool('VCCLCompilerTool',
1615 {'UsePrecompiledHeader': '0',
1616 'ForcedIncludeFiles': '$(NOINHERIT)'})
1617 p.AddFileConfig(_FixPath(source),
1618 _ConfigFullName(config_name, config),
1620 # Do nothing if there was no precompiled source.
1621 if extensions_excluded_from_precompile:
1622 DisableForSourceTree(sources)
1625 def _AddActions(actions_to_add, spec, relative_path_of_gyp_file):
1627 actions = spec.get('actions', [])
1628 # Don't setup_env every time. When all the actions are run together in one
1629 # batch file in VS, the PATH will grow too long.
1630 # Membership in this set means that the cygwin environment has been set up,
1631 # and does not need to be set up again.
1632 have_setup_env = set()
1634 # Attach actions to the gyp file if nothing else is there.
1635 inputs = a.get('inputs') or [relative_path_of_gyp_file]
1636 attached_to = inputs[0]
1637 need_setup_env = attached_to not in have_setup_env
1638 cmd = _BuildCommandLineForRule(spec, a, has_input_path=False,
1639 do_setup_env=need_setup_env)
1640 have_setup_env.add(attached_to)
1642 _AddActionStep(actions_to_add,
1644 outputs=a.get('outputs', []),
1645 description=a.get('message', a['action_name']),
1649 def _WriteMSVSUserFile(project_path, version, spec):
1650 # Add run_as and test targets.
1651 if 'run_as' in spec:
1652 run_as = spec['run_as']
1653 action = run_as.get('action', [])
1654 environment = run_as.get('environment', [])
1655 working_directory = run_as.get('working_directory', '.')
1656 elif int(spec.get('test', 0)):
1657 action = ['$(TargetPath)', '--gtest_print_time']
1659 working_directory = '.'
1661 return # Nothing to add
1662 # Write out the user file.
1663 user_file = _CreateMSVSUserFile(project_path, version, spec)
1664 for config_name, c_data in spec['configurations'].iteritems():
1665 user_file.AddDebugSettings(_ConfigFullName(config_name, c_data),
1666 action, environment, working_directory)
1667 user_file.WriteIfChanged()
1670 def _AddCopies(actions_to_add, spec):
1671 copies = _GetCopies(spec)
1672 for inputs, outputs, cmd, description in copies:
1673 _AddActionStep(actions_to_add, inputs=inputs, outputs=outputs,
1674 description=description, command=cmd)
1677 def _GetCopies(spec):
1680 for cpy in spec.get('copies', []):
1681 for src in cpy.get('files', []):
1682 dst = os.path.join(cpy['destination'], os.path.basename(src))
1683 # _AddCustomBuildToolForMSVS() will call _FixPath() on the inputs and
1684 # outputs, so do the same for our generated command line.
1685 if src.endswith('/'):
1687 base_dir = posixpath.split(src_bare)[0]
1688 outer_dir = posixpath.split(src_bare)[1]
1689 cmd = 'cd "%s" && xcopy /e /f /y "%s" "%s\\%s\\"' % (
1690 _FixPath(base_dir), outer_dir, _FixPath(dst), outer_dir)
1691 copies.append(([src], ['dummy_copies', dst], cmd,
1692 'Copying %s to %s' % (src, dst)))
1694 cmd = 'mkdir "%s" 2>nul & set ERRORLEVEL=0 & copy /Y "%s" "%s"' % (
1695 _FixPath(cpy['destination']), _FixPath(src), _FixPath(dst))
1696 copies.append(([src], [dst], cmd, 'Copying %s to %s' % (src, dst)))
1700 def _GetPathDict(root, path):
1701 # |path| will eventually be empty (in the recursive calls) if it was initially
1702 # relative; otherwise it will eventually end up as '\', 'D:\', etc.
1703 if not path or path.endswith(os.sep):
1705 parent, folder = os.path.split(path)
1706 parent_dict = _GetPathDict(root, parent)
1707 if folder not in parent_dict:
1708 parent_dict[folder] = dict()
1709 return parent_dict[folder]
1712 def _DictsToFolders(base_path, bucket, flat):
1713 # Convert to folders recursively.
1715 for folder, contents in bucket.iteritems():
1716 if type(contents) == dict:
1717 folder_children = _DictsToFolders(os.path.join(base_path, folder),
1720 children += folder_children
1722 folder_children = MSVSNew.MSVSFolder(os.path.join(base_path, folder),
1723 name='(' + folder + ')',
1724 entries=folder_children)
1725 children.append(folder_children)
1727 children.append(contents)
1731 def _CollapseSingles(parent, node):
1732 # Recursively explorer the tree of dicts looking for projects which are
1733 # the sole item in a folder which has the same name as the project. Bring
1734 # such projects up one level.
1735 if (type(node) == dict and
1737 node.keys()[0] == parent + '.vcproj'):
1738 return node[node.keys()[0]]
1739 if type(node) != dict:
1742 node[child] = _CollapseSingles(child, node[child])
1746 def _GatherSolutionFolders(sln_projects, project_objects, flat):
1748 # Convert into a tree of dicts on path.
1749 for p in sln_projects:
1750 gyp_file, target = gyp.common.ParseQualifiedTarget(p)[0:2]
1751 gyp_dir = os.path.dirname(gyp_file)
1752 path_dict = _GetPathDict(root, gyp_dir)
1753 path_dict[target + '.vcproj'] = project_objects[p]
1754 # Walk down from the top until we hit a folder that has more than one entry.
1755 # In practice, this strips the top-level "src/" dir from the hierarchy in
1757 while len(root) == 1 and type(root[root.keys()[0]]) == dict:
1758 root = root[root.keys()[0]]
1760 root = _CollapseSingles('', root)
1761 # Merge buckets until everything is a root entry.
1762 return _DictsToFolders('', root, flat)
1765 def _GetPathOfProject(qualified_target, spec, options, msvs_version):
1766 default_config = _GetDefaultConfiguration(spec)
1767 proj_filename = default_config.get('msvs_existing_vcproj')
1768 if not proj_filename:
1769 proj_filename = (spec['target_name'] + options.suffix +
1770 msvs_version.ProjectExtension())
1772 build_file = gyp.common.BuildFile(qualified_target)
1773 proj_path = os.path.join(os.path.dirname(build_file), proj_filename)
1775 if options.generator_output:
1776 project_dir_path = os.path.dirname(os.path.abspath(proj_path))
1777 proj_path = os.path.join(options.generator_output, proj_path)
1778 fix_prefix = gyp.common.RelativePath(project_dir_path,
1779 os.path.dirname(proj_path))
1780 return proj_path, fix_prefix
1783 def _GetPlatformOverridesOfProject(spec):
1784 # Prepare a dict indicating which project configurations are used for which
1785 # solution configurations for this target.
1786 config_platform_overrides = {}
1787 for config_name, c in spec['configurations'].iteritems():
1788 config_fullname = _ConfigFullName(config_name, c)
1789 platform = c.get('msvs_target_platform', _ConfigPlatform(c))
1790 fixed_config_fullname = '%s|%s' % (
1791 _ConfigBaseName(config_name, _ConfigPlatform(c)), platform)
1792 config_platform_overrides[config_fullname] = fixed_config_fullname
1793 return config_platform_overrides
1796 def _CreateProjectObjects(target_list, target_dicts, options, msvs_version):
1797 """Create a MSVSProject object for the targets found in target list.
1800 target_list: the list of targets to generate project objects for.
1801 target_dicts: the dictionary of specifications.
1802 options: global generator options.
1803 msvs_version: the MSVSVersion object.
1805 A set of created projects, keyed by target.
1807 global fixpath_prefix
1808 # Generate each project.
1810 for qualified_target in target_list:
1811 spec = target_dicts[qualified_target]
1812 if spec['toolset'] != 'target':
1814 'Multiple toolsets not supported in msvs build (target %s)' %
1816 proj_path, fixpath_prefix = _GetPathOfProject(qualified_target, spec,
1817 options, msvs_version)
1818 guid = _GetGuidOfProject(proj_path, spec)
1819 overrides = _GetPlatformOverridesOfProject(spec)
1820 build_file = gyp.common.BuildFile(qualified_target)
1821 # Create object for this project.
1822 obj = MSVSNew.MSVSProject(
1824 name=spec['target_name'],
1827 build_file=build_file,
1828 config_platform_overrides=overrides,
1829 fixpath_prefix=fixpath_prefix)
1830 # Set project toolset if any (MS build only)
1831 if msvs_version.UsesVcxproj():
1832 obj.set_msbuild_toolset(
1833 _GetMsbuildToolsetOfProject(proj_path, spec, msvs_version))
1834 projects[qualified_target] = obj
1835 # Set all the dependencies, but not if we are using an external builder like
1837 for project in projects.values():
1838 if not project.spec.get('msvs_external_builder'):
1839 deps = project.spec.get('dependencies', [])
1840 deps = [projects[d] for d in deps]
1841 project.set_dependencies(deps)
1845 def _InitNinjaFlavor(params, target_list, target_dicts):
1846 """Initialize targets for the ninja flavor.
1848 This sets up the necessary variables in the targets to generate msvs projects
1849 that use ninja as an external builder. The variables in the spec are only set
1850 if they have not been set. This allows individual specs to override the
1851 default values initialized here.
1853 params: Params provided to the generator.
1854 target_list: List of target pairs: 'base/base.gyp:base'.
1855 target_dicts: Dict of target properties keyed on target pair.
1857 for qualified_target in target_list:
1858 spec = target_dicts[qualified_target]
1859 if spec.get('msvs_external_builder'):
1860 # The spec explicitly defined an external builder, so don't change it.
1863 path_to_ninja = spec.get('msvs_path_to_ninja', 'ninja.exe')
1865 spec['msvs_external_builder'] = 'ninja'
1866 if not spec.get('msvs_external_builder_out_dir'):
1867 gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target)
1868 gyp_dir = os.path.dirname(gyp_file)
1869 configuration = '$(Configuration)'
1870 if params.get('target_arch') == 'x64':
1871 configuration += '_x64'
1872 spec['msvs_external_builder_out_dir'] = os.path.join(
1873 gyp.common.RelativePath(params['options'].toplevel_dir, gyp_dir),
1874 ninja_generator.ComputeOutputDir(params),
1876 if not spec.get('msvs_external_builder_build_cmd'):
1877 spec['msvs_external_builder_build_cmd'] = [
1883 if not spec.get('msvs_external_builder_clean_cmd'):
1884 spec['msvs_external_builder_clean_cmd'] = [
1893 def CalculateVariables(default_variables, params):
1894 """Generated variables that require params to be known."""
1896 generator_flags = params.get('generator_flags', {})
1898 # Select project file format version (if unset, default to auto detecting).
1899 msvs_version = MSVSVersion.SelectVisualStudioVersion(
1900 generator_flags.get('msvs_version', 'auto'))
1901 # Stash msvs_version for later (so we don't have to probe the system twice).
1902 params['msvs_version'] = msvs_version
1904 # Set a variable so conditions can be based on msvs_version.
1905 default_variables['MSVS_VERSION'] = msvs_version.ShortName()
1907 # To determine processor word size on Windows, in addition to checking
1908 # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
1909 # process), it is also necessary to check PROCESSOR_ARCITEW6432 (which
1910 # contains the actual word size of the system when running thru WOW64).
1911 if (os.environ.get('PROCESSOR_ARCHITECTURE', '').find('64') >= 0 or
1912 os.environ.get('PROCESSOR_ARCHITEW6432', '').find('64') >= 0):
1913 default_variables['MSVS_OS_BITS'] = 64
1915 default_variables['MSVS_OS_BITS'] = 32
1917 if gyp.common.GetFlavor(params) == 'ninja':
1918 default_variables['SHARED_INTERMEDIATE_DIR'] = '$(OutDir)gen'
1921 def PerformBuild(data, configurations, params):
1922 options = params['options']
1923 msvs_version = params['msvs_version']
1924 devenv = os.path.join(msvs_version.path, 'Common7', 'IDE', 'devenv.com')
1926 for build_file, build_file_dict in data.iteritems():
1927 (build_file_root, build_file_ext) = os.path.splitext(build_file)
1928 if build_file_ext != '.gyp':
1930 sln_path = build_file_root + options.suffix + '.sln'
1931 if options.generator_output:
1932 sln_path = os.path.join(options.generator_output, sln_path)
1934 for config in configurations:
1935 arguments = [devenv, sln_path, '/Build', config]
1936 print 'Building [%s]: %s' % (config, arguments)
1937 rtn = subprocess.check_call(arguments)
1940 def GenerateOutput(target_list, target_dicts, data, params):
1941 """Generate .sln and .vcproj files.
1943 This is the entry point for this generator.
1945 target_list: List of target pairs: 'base/base.gyp:base'.
1946 target_dicts: Dict of target properties keyed on target pair.
1947 data: Dictionary containing per .gyp data.
1949 global fixpath_prefix
1951 options = params['options']
1953 # Get the project file format version back out of where we stashed it in
1954 # GeneratorCalculatedVariables.
1955 msvs_version = params['msvs_version']
1957 generator_flags = params.get('generator_flags', {})
1959 # Optionally shard targets marked with 'msvs_shard': SHARD_COUNT.
1960 (target_list, target_dicts) = MSVSUtil.ShardTargets(target_list, target_dicts)
1962 # Optionally use the large PDB workaround for targets marked with
1963 # 'msvs_large_pdb': 1.
1964 (target_list, target_dicts) = MSVSUtil.InsertLargePdbShims(
1965 target_list, target_dicts, generator_default_variables)
1967 # Optionally configure each spec to use ninja as the external builder.
1968 if params.get('flavor') == 'ninja':
1969 _InitNinjaFlavor(params, target_list, target_dicts)
1971 # Prepare the set of configurations.
1973 for qualified_target in target_list:
1974 spec = target_dicts[qualified_target]
1975 for config_name, config in spec['configurations'].iteritems():
1976 configs.add(_ConfigFullName(config_name, config))
1977 configs = list(configs)
1979 # Figure out all the projects that will be generated and their guids
1980 project_objects = _CreateProjectObjects(target_list, target_dicts, options,
1983 # Generate each project.
1984 missing_sources = []
1985 for project in project_objects.values():
1986 fixpath_prefix = project.fixpath_prefix
1987 missing_sources.extend(_GenerateProject(project, options, msvs_version,
1989 fixpath_prefix = None
1991 for build_file in data:
1992 # Validate build_file extension
1993 if not build_file.endswith('.gyp'):
1995 sln_path = os.path.splitext(build_file)[0] + options.suffix + '.sln'
1996 if options.generator_output:
1997 sln_path = os.path.join(options.generator_output, sln_path)
1998 # Get projects in the solution, and their dependents.
1999 sln_projects = gyp.common.BuildFileTargets(target_list, build_file)
2000 sln_projects += gyp.common.DeepDependencyTargets(target_dicts, sln_projects)
2001 # Create folder hierarchy.
2002 root_entries = _GatherSolutionFolders(
2003 sln_projects, project_objects, flat=msvs_version.FlatSolution())
2005 sln = MSVSNew.MSVSSolution(sln_path,
2006 entries=root_entries,
2008 websiteProperties=False,
2009 version=msvs_version)
2013 error_message = "Missing input files:\n" + \
2014 '\n'.join(set(missing_sources))
2015 if generator_flags.get('msvs_error_on_missing_sources', False):
2016 raise GypError(error_message)
2018 print >> sys.stdout, "Warning: " + error_message
2021 def _GenerateMSBuildFiltersFile(filters_path, source_files,
2022 rule_dependencies, extension_to_rule_name):
2023 """Generate the filters file.
2025 This file is used by Visual Studio to organize the presentation of source
2029 filters_path: The path of the file to be created.
2030 source_files: The hierarchical structure of all the sources.
2031 extension_to_rule_name: A dictionary mapping file extensions to rules.
2035 _AppendFiltersForMSBuild('', source_files, rule_dependencies,
2036 extension_to_rule_name, filter_group, source_group)
2038 content = ['Project',
2039 {'ToolsVersion': '4.0',
2040 'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
2042 ['ItemGroup'] + filter_group,
2043 ['ItemGroup'] + source_group
2045 easy_xml.WriteXmlIfChanged(content, filters_path, pretty=True, win32=True)
2046 elif os.path.exists(filters_path):
2047 # We don't need this filter anymore. Delete the old filter file.
2048 os.unlink(filters_path)
2051 def _AppendFiltersForMSBuild(parent_filter_name, sources, rule_dependencies,
2052 extension_to_rule_name,
2053 filter_group, source_group):
2054 """Creates the list of filters and sources to be added in the filter file.
2057 parent_filter_name: The name of the filter under which the sources are
2059 sources: The hierarchy of filters and sources to process.
2060 extension_to_rule_name: A dictionary mapping file extensions to rules.
2061 filter_group: The list to which filter entries will be appended.
2062 source_group: The list to which source entries will be appeneded.
2064 for source in sources:
2065 if isinstance(source, MSVSProject.Filter):
2066 # We have a sub-filter. Create the name of that sub-filter.
2067 if not parent_filter_name:
2068 filter_name = source.name
2070 filter_name = '%s\\%s' % (parent_filter_name, source.name)
2071 # Add the filter to the group.
2072 filter_group.append(
2073 ['Filter', {'Include': filter_name},
2074 ['UniqueIdentifier', MSVSNew.MakeGuid(source.name)]])
2075 # Recurse and add its dependents.
2076 _AppendFiltersForMSBuild(filter_name, source.contents,
2077 rule_dependencies, extension_to_rule_name,
2078 filter_group, source_group)
2080 # It's a source. Create a source entry.
2081 _, element = _MapFileToMsBuildSourceType(source, rule_dependencies,
2082 extension_to_rule_name)
2083 source_entry = [element, {'Include': source}]
2084 # Specify the filter it is part of, if any.
2085 if parent_filter_name:
2086 source_entry.append(['Filter', parent_filter_name])
2087 source_group.append(source_entry)
2090 def _MapFileToMsBuildSourceType(source, rule_dependencies,
2091 extension_to_rule_name):
2092 """Returns the group and element type of the source file.
2095 source: The source file name.
2096 extension_to_rule_name: A dictionary mapping file extensions to rules.
2099 A pair of (group this file should be part of, the label of element)
2101 _, ext = os.path.splitext(source)
2102 if ext in extension_to_rule_name:
2104 element = extension_to_rule_name[ext]
2105 elif ext in ['.cc', '.cpp', '.c', '.cxx']:
2107 element = 'ClCompile'
2108 elif ext in ['.h', '.hxx']:
2110 element = 'ClInclude'
2113 element = 'ResourceCompile'
2120 elif source in rule_dependencies:
2121 group = 'rule_dependency'
2122 element = 'CustomBuild'
2126 return (group, element)
2129 def _GenerateRulesForMSBuild(output_dir, options, spec,
2130 sources, excluded_sources,
2131 props_files_of_rules, targets_files_of_rules,
2132 actions_to_add, rule_dependencies,
2133 extension_to_rule_name):
2134 # MSBuild rules are implemented using three files: an XML file, a .targets
2135 # file and a .props file.
2136 # See http://blogs.msdn.com/b/vcblog/archive/2010/04/21/quick-help-on-vs2010-custom-build-rule.aspx
2138 rules = spec.get('rules', [])
2139 rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
2140 rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
2143 for rule in rules_native:
2144 # Skip a rule with no action and no inputs.
2145 if 'action' not in rule and not rule.get('rule_sources', []):
2147 msbuild_rule = MSBuildRule(rule, spec)
2148 msbuild_rules.append(msbuild_rule)
2149 rule_dependencies.update(msbuild_rule.additional_dependencies.split(';'))
2150 extension_to_rule_name[msbuild_rule.extension] = msbuild_rule.rule_name
2152 base = spec['target_name'] + options.suffix
2153 props_name = base + '.props'
2154 targets_name = base + '.targets'
2155 xml_name = base + '.xml'
2157 props_files_of_rules.add(props_name)
2158 targets_files_of_rules.add(targets_name)
2160 props_path = os.path.join(output_dir, props_name)
2161 targets_path = os.path.join(output_dir, targets_name)
2162 xml_path = os.path.join(output_dir, xml_name)
2164 _GenerateMSBuildRulePropsFile(props_path, msbuild_rules)
2165 _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules)
2166 _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules)
2169 _GenerateExternalRules(rules_external, output_dir, spec,
2170 sources, options, actions_to_add)
2171 _AdjustSourcesForRules(rules, sources, excluded_sources, True)
2174 class MSBuildRule(object):
2175 """Used to store information used to generate an MSBuild rule.
2178 rule_name: The rule name, sanitized to use in XML.
2179 target_name: The name of the target.
2180 after_targets: The name of the AfterTargets element.
2181 before_targets: The name of the BeforeTargets element.
2182 depends_on: The name of the DependsOn element.
2183 compute_output: The name of the ComputeOutput element.
2184 dirs_to_make: The name of the DirsToMake element.
2185 inputs: The name of the _inputs element.
2186 tlog: The name of the _tlog element.
2187 extension: The extension this rule applies to.
2188 description: The message displayed when this rule is invoked.
2189 additional_dependencies: A string listing additional dependencies.
2190 outputs: The outputs of this rule.
2191 command: The command used to run the rule.
2194 def __init__(self, rule, spec):
2195 self.display_name = rule['rule_name']
2196 # Assure that the rule name is only characters and numbers
2197 self.rule_name = re.sub(r'\W', '_', self.display_name)
2198 # Create the various element names, following the example set by the
2199 # Visual Studio 2008 to 2010 conversion. I don't know if VS2010
2200 # is sensitive to the exact names.
2201 self.target_name = '_' + self.rule_name
2202 self.after_targets = self.rule_name + 'AfterTargets'
2203 self.before_targets = self.rule_name + 'BeforeTargets'
2204 self.depends_on = self.rule_name + 'DependsOn'
2205 self.compute_output = 'Compute%sOutput' % self.rule_name
2206 self.dirs_to_make = self.rule_name + 'DirsToMake'
2207 self.inputs = self.rule_name + '_inputs'
2208 self.tlog = self.rule_name + '_tlog'
2209 self.extension = rule['extension']
2210 if not self.extension.startswith('.'):
2211 self.extension = '.' + self.extension
2213 self.description = MSVSSettings.ConvertVCMacrosToMSBuild(
2214 rule.get('message', self.rule_name))
2215 old_additional_dependencies = _FixPaths(rule.get('inputs', []))
2216 self.additional_dependencies = (
2217 ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
2218 for i in old_additional_dependencies]))
2219 old_outputs = _FixPaths(rule.get('outputs', []))
2220 self.outputs = ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
2221 for i in old_outputs])
2222 old_command = _BuildCommandLineForRule(spec, rule, has_input_path=True,
2224 self.command = MSVSSettings.ConvertVCMacrosToMSBuild(old_command)
2227 def _GenerateMSBuildRulePropsFile(props_path, msbuild_rules):
2228 """Generate the .props file."""
2229 content = ['Project',
2230 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'}]
2231 for rule in msbuild_rules:
2234 {'Condition': "'$(%s)' == '' and '$(%s)' == '' and "
2235 "'$(ConfigurationType)' != 'Makefile'" % (rule.before_targets,
2238 [rule.before_targets, 'Midl'],
2239 [rule.after_targets, 'CustomBuild'],
2243 {'Condition': "'$(ConfigurationType)' != 'Makefile'"},
2244 '_SelectedFiles;$(%s)' % rule.depends_on
2247 ['ItemDefinitionGroup',
2249 ['CommandLineTemplate', rule.command],
2250 ['Outputs', rule.outputs],
2251 ['ExecutionDescription', rule.description],
2252 ['AdditionalDependencies', rule.additional_dependencies],
2256 easy_xml.WriteXmlIfChanged(content, props_path, pretty=True, win32=True)
2259 def _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules):
2260 """Generate the .targets file."""
2261 content = ['Project',
2262 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
2267 ['PropertyPageSchema',
2268 {'Include': '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'}
2271 for rule in msbuild_rules:
2273 ['AvailableItemName',
2274 {'Include': rule.rule_name},
2275 ['Targets', rule.target_name],
2277 content.append(item_group)
2279 for rule in msbuild_rules:
2282 {'TaskName': rule.rule_name,
2283 'TaskFactory': 'XamlTaskFactory',
2284 'AssemblyName': 'Microsoft.Build.Tasks.v4.0'
2286 ['Task', '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'],
2288 for rule in msbuild_rules:
2289 rule_name = rule.rule_name
2290 target_outputs = '%%(%s.Outputs)' % rule_name
2291 target_inputs = ('%%(%s.Identity);%%(%s.AdditionalDependencies);'
2292 '$(MSBuildProjectFile)') % (rule_name, rule_name)
2293 rule_inputs = '%%(%s.Identity)' % rule_name
2294 extension_condition = ("'%(Extension)'=='.obj' or "
2295 "'%(Extension)'=='.res' or "
2296 "'%(Extension)'=='.rsc' or "
2297 "'%(Extension)'=='.lib'")
2300 {'Condition': "'@(SelectedFiles)' != ''"},
2302 {'Remove': '@(%s)' % rule_name,
2303 'Condition': "'%(Identity)' != '@(SelectedFiles)'"
2309 [rule.inputs, {'Include': '%%(%s.AdditionalDependencies)' % rule_name}]
2314 {'Include': '%%(%s.Outputs)' % rule_name,
2315 'Condition': ("'%%(%s.Outputs)' != '' and "
2316 "'%%(%s.ExcludedFromBuild)' != 'true'" %
2317 (rule_name, rule_name))
2319 ['Source', "@(%s, '|')" % rule_name],
2320 ['Inputs', "@(%s -> '%%(Fullpath)', ';')" % rule.inputs],
2325 {'Importance': 'High',
2326 'Text': '%%(%s.ExecutionDescription)' % rule_name
2329 write_tlog_section = [
2331 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2332 "'true'" % (rule.tlog, rule.tlog),
2333 'File': '$(IntDir)$(ProjectName).write.1.tlog',
2334 'Lines': "^%%(%s.Source);@(%s->'%%(Fullpath)')" % (rule.tlog,
2338 read_tlog_section = [
2340 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2341 "'true'" % (rule.tlog, rule.tlog),
2342 'File': '$(IntDir)$(ProjectName).read.1.tlog',
2343 'Lines': "^%%(%s.Source);%%(%s.Inputs)" % (rule.tlog, rule.tlog)
2346 command_and_input_section = [
2348 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2349 "'true'" % (rule_name, rule_name),
2351 'StandardOutputImportance': 'High',
2352 'StandardErrorImportance': 'High',
2353 'CommandLineTemplate': '%%(%s.CommandLineTemplate)' % rule_name,
2354 'AdditionalOptions': '%%(%s.AdditionalOptions)' % rule_name,
2355 'Inputs': rule_inputs
2360 {'Name': rule.target_name,
2361 'BeforeTargets': '$(%s)' % rule.before_targets,
2362 'AfterTargets': '$(%s)' % rule.after_targets,
2363 'Condition': "'@(%s)' != ''" % rule_name,
2364 'DependsOnTargets': '$(%s);%s' % (rule.depends_on,
2365 rule.compute_output),
2366 'Outputs': target_outputs,
2367 'Inputs': target_inputs
2375 command_and_input_section,
2378 ['ComputeLinkInputsTargets',
2379 '$(ComputeLinkInputsTargets);',
2380 '%s;' % rule.compute_output
2382 ['ComputeLibInputsTargets',
2383 '$(ComputeLibInputsTargets);',
2384 '%s;' % rule.compute_output
2388 {'Name': rule.compute_output,
2389 'Condition': "'@(%s)' != ''" % rule_name
2393 {'Condition': "'@(%s)' != '' and "
2394 "'%%(%s.ExcludedFromBuild)' != 'true'" % (rule_name, rule_name),
2395 'Include': '%%(%s.Outputs)' % rule_name
2399 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2400 'Condition': extension_condition
2404 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2405 'Condition': extension_condition
2409 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2410 'Condition': extension_condition
2415 {'Directories': ("@(%s->'%%(RootDir)%%(Directory)')" %
2421 easy_xml.WriteXmlIfChanged(content, targets_path, pretty=True, win32=True)
2424 def _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules):
2425 # Generate the .xml file
2427 'ProjectSchemaDefinitions',
2428 {'xmlns': ('clr-namespace:Microsoft.Build.Framework.XamlTypes;'
2429 'assembly=Microsoft.Build.Framework'),
2430 'xmlns:x': 'http://schemas.microsoft.com/winfx/2006/xaml',
2431 'xmlns:sys': 'clr-namespace:System;assembly=mscorlib',
2432 'xmlns:transformCallback':
2433 'Microsoft.Cpp.Dev10.ConvertPropertyCallback'
2436 for rule in msbuild_rules:
2439 {'Name': rule.rule_name,
2440 'PageTemplate': 'tool',
2441 'DisplayName': rule.display_name,
2446 {'Persistence': 'ProjectFile',
2447 'ItemType': rule.rule_name
2453 {'Name': 'General'},
2454 ['Category.DisplayName',
2455 ['sys:String', 'General'],
2459 {'Name': 'Command Line',
2460 'Subtype': 'CommandLine'
2462 ['Category.DisplayName',
2463 ['sys:String', 'Command Line'],
2467 ['StringListProperty',
2469 'Category': 'Command Line',
2470 'IsRequired': 'true',
2473 ['StringListProperty.DataSource',
2475 {'Persistence': 'ProjectFile',
2476 'ItemType': rule.rule_name,
2477 'SourceType': 'Item'
2483 {'Name': 'CommandLineTemplate',
2484 'DisplayName': 'Command Line',
2486 'IncludeInCommandLine': 'False'
2489 ['DynamicEnumProperty',
2490 {'Name': rule.before_targets,
2491 'Category': 'General',
2492 'EnumProvider': 'Targets',
2493 'IncludeInCommandLine': 'False'
2495 ['DynamicEnumProperty.DisplayName',
2496 ['sys:String', 'Execute Before'],
2498 ['DynamicEnumProperty.Description',
2499 ['sys:String', 'Specifies the targets for the build customization'
2503 ['DynamicEnumProperty.ProviderSettings',
2506 'Value': '^%s|^Compute' % rule.before_targets
2510 ['DynamicEnumProperty.DataSource',
2512 {'Persistence': 'ProjectFile',
2513 'HasConfigurationCondition': 'true'
2518 ['DynamicEnumProperty',
2519 {'Name': rule.after_targets,
2520 'Category': 'General',
2521 'EnumProvider': 'Targets',
2522 'IncludeInCommandLine': 'False'
2524 ['DynamicEnumProperty.DisplayName',
2525 ['sys:String', 'Execute After'],
2527 ['DynamicEnumProperty.Description',
2528 ['sys:String', ('Specifies the targets for the build customization'
2532 ['DynamicEnumProperty.ProviderSettings',
2535 'Value': '^%s|^Compute' % rule.after_targets
2539 ['DynamicEnumProperty.DataSource',
2541 {'Persistence': 'ProjectFile',
2543 'HasConfigurationCondition': 'true'
2548 ['StringListProperty',
2550 'DisplayName': 'Outputs',
2552 'IncludeInCommandLine': 'False'
2556 {'Name': 'ExecutionDescription',
2557 'DisplayName': 'Execution Description',
2559 'IncludeInCommandLine': 'False'
2562 ['StringListProperty',
2563 {'Name': 'AdditionalDependencies',
2564 'DisplayName': 'Additional Dependencies',
2565 'IncludeInCommandLine': 'False',
2570 {'Subtype': 'AdditionalOptions',
2571 'Name': 'AdditionalOptions',
2572 'Category': 'Command Line'
2574 ['StringProperty.DisplayName',
2575 ['sys:String', 'Additional Options'],
2577 ['StringProperty.Description',
2578 ['sys:String', 'Additional Options'],
2583 {'Name': rule.rule_name,
2584 'DisplayName': rule.display_name
2588 {'Name': '*' + rule.extension,
2589 'ContentType': rule.rule_name
2593 {'Name': rule.rule_name,
2595 'ItemType': rule.rule_name
2599 easy_xml.WriteXmlIfChanged(content, xml_path, pretty=True, win32=True)
2602 def _GetConfigurationAndPlatform(name, settings):
2603 configuration = name.rsplit('_', 1)[0]
2604 platform = settings.get('msvs_configuration_platform', 'Win32')
2605 return (configuration, platform)
2608 def _GetConfigurationCondition(name, settings):
2609 return (r"'$(Configuration)|$(Platform)'=='%s|%s'" %
2610 _GetConfigurationAndPlatform(name, settings))
2613 def _GetMSBuildProjectConfigurations(configurations):
2614 group = ['ItemGroup', {'Label': 'ProjectConfigurations'}]
2615 for (name, settings) in sorted(configurations.iteritems()):
2616 configuration, platform = _GetConfigurationAndPlatform(name, settings)
2617 designation = '%s|%s' % (configuration, platform)
2619 ['ProjectConfiguration', {'Include': designation},
2620 ['Configuration', configuration],
2621 ['Platform', platform]])
2625 def _GetMSBuildGlobalProperties(spec, guid, gyp_file_name):
2626 namespace = os.path.splitext(gyp_file_name)[0]
2628 ['PropertyGroup', {'Label': 'Globals'},
2629 ['ProjectGuid', guid],
2630 ['Keyword', 'Win32Proj'],
2631 ['RootNamespace', namespace],
2632 ['IgnoreWarnCompileDuplicatedFilename', 'true'],
2636 if os.environ.get('PROCESSOR_ARCHITECTURE') == 'AMD64' or \
2637 os.environ.get('PROCESSOR_ARCHITEW6432') == 'AMD64':
2638 properties[0].append(['PreferredToolArchitecture', 'x64'])
2640 if spec.get('msvs_enable_winrt'):
2641 properties[0].append(['DefaultLanguage', 'en-US'])
2642 properties[0].append(['AppContainerApplication', 'true'])
2643 if spec.get('msvs_application_type_revision'):
2644 app_type_revision = spec.get('msvs_application_type_revision')
2645 properties[0].append(['ApplicationTypeRevision', app_type_revision])
2647 properties[0].append(['ApplicationTypeRevision', '8.1'])
2649 if spec.get('msvs_target_platform_version'):
2650 target_platform_version = spec.get('msvs_target_platform_version')
2651 properties[0].append(['WindowsTargetPlatformVersion',
2652 target_platform_version])
2653 if spec.get('msvs_target_platform_minversion'):
2654 target_platform_minversion = spec.get('msvs_target_platform_minversion')
2655 properties[0].append(['WindowsTargetPlatformMinVersion',
2656 target_platform_minversion])
2658 properties[0].append(['WindowsTargetPlatformMinVersion',
2659 target_platform_version])
2660 if spec.get('msvs_enable_winphone'):
2661 properties[0].append(['ApplicationType', 'Windows Phone'])
2663 properties[0].append(['ApplicationType', 'Windows Store'])
2667 def _GetMSBuildConfigurationDetails(spec, build_file):
2669 for name, settings in spec['configurations'].iteritems():
2670 msbuild_attributes = _GetMSBuildAttributes(spec, settings, build_file)
2671 condition = _GetConfigurationCondition(name, settings)
2672 character_set = msbuild_attributes.get('CharacterSet')
2673 _AddConditionalProperty(properties, condition, 'ConfigurationType',
2674 msbuild_attributes['ConfigurationType'])
2676 if 'msvs_enable_winrt' not in spec :
2677 _AddConditionalProperty(properties, condition, 'CharacterSet',
2679 return _GetMSBuildPropertyGroup(spec, 'Configuration', properties)
2682 def _GetMSBuildLocalProperties(msbuild_toolset):
2683 # Currently the only local property we support is PlatformToolset
2687 ['PropertyGroup', {'Label': 'Locals'},
2688 ['PlatformToolset', msbuild_toolset],
2694 def _GetMSBuildPropertySheets(configurations):
2695 user_props = r'$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props'
2696 additional_props = {}
2697 props_specified = False
2698 for name, settings in sorted(configurations.iteritems()):
2699 configuration = _GetConfigurationCondition(name, settings)
2700 if settings.has_key('msbuild_props'):
2701 additional_props[configuration] = _FixPaths(settings['msbuild_props'])
2702 props_specified = True
2704 additional_props[configuration] = ''
2706 if not props_specified:
2709 {'Label': 'PropertySheets'},
2711 {'Project': user_props,
2712 'Condition': "exists('%s')" % user_props,
2713 'Label': 'LocalAppDataPlatform'
2720 for condition, props in additional_props.iteritems():
2723 {'Label': 'PropertySheets',
2724 'Condition': condition
2727 {'Project': user_props,
2728 'Condition': "exists('%s')" % user_props,
2729 'Label': 'LocalAppDataPlatform'
2733 for props_file in props:
2734 import_group.append(['Import', {'Project':props_file}])
2735 sheets.append(import_group)
2738 def _ConvertMSVSBuildAttributes(spec, config, build_file):
2739 config_type = _GetMSVSConfigurationType(spec, build_file)
2740 msvs_attributes = _GetMSVSAttributes(spec, config, config_type)
2741 msbuild_attributes = {}
2742 for a in msvs_attributes:
2743 if a in ['IntermediateDirectory', 'OutputDirectory']:
2744 directory = MSVSSettings.ConvertVCMacrosToMSBuild(msvs_attributes[a])
2745 if not directory.endswith('\\'):
2747 msbuild_attributes[a] = directory
2748 elif a == 'CharacterSet':
2749 msbuild_attributes[a] = _ConvertMSVSCharacterSet(msvs_attributes[a])
2750 elif a == 'ConfigurationType':
2751 msbuild_attributes[a] = _ConvertMSVSConfigurationType(msvs_attributes[a])
2753 print 'Warning: Do not know how to convert MSVS attribute ' + a
2754 return msbuild_attributes
2757 def _ConvertMSVSCharacterSet(char_set):
2758 if char_set.isdigit():
2767 def _ConvertMSVSConfigurationType(config_type):
2768 if config_type.isdigit():
2771 '2': 'DynamicLibrary',
2772 '4': 'StaticLibrary',
2778 def _GetMSBuildAttributes(spec, config, build_file):
2779 if 'msbuild_configuration_attributes' not in config:
2780 msbuild_attributes = _ConvertMSVSBuildAttributes(spec, config, build_file)
2783 config_type = _GetMSVSConfigurationType(spec, build_file)
2784 config_type = _ConvertMSVSConfigurationType(config_type)
2785 msbuild_attributes = config.get('msbuild_configuration_attributes', {})
2786 msbuild_attributes.setdefault('ConfigurationType', config_type)
2787 output_dir = msbuild_attributes.get('OutputDirectory',
2788 '$(SolutionDir)$(Configuration)')
2789 msbuild_attributes['OutputDirectory'] = _FixPath(output_dir) + '\\'
2790 if 'IntermediateDirectory' not in msbuild_attributes:
2791 intermediate = _FixPath('$(Configuration)') + '\\'
2792 msbuild_attributes['IntermediateDirectory'] = intermediate
2793 if 'CharacterSet' in msbuild_attributes:
2794 msbuild_attributes['CharacterSet'] = _ConvertMSVSCharacterSet(
2795 msbuild_attributes['CharacterSet'])
2796 if 'TargetName' not in msbuild_attributes:
2797 prefix = spec.get('product_prefix', '')
2798 product_name = spec.get('product_name', '$(ProjectName)')
2799 target_name = prefix + product_name
2800 msbuild_attributes['TargetName'] = target_name
2801 if 'TargetExt' not in msbuild_attributes and 'product_extension' in spec:
2802 ext = spec.get('product_extension')
2803 msbuild_attributes['TargetExt'] = '.' + ext
2805 if spec.get('msvs_external_builder'):
2806 external_out_dir = spec.get('msvs_external_builder_out_dir', '.')
2807 msbuild_attributes['OutputDirectory'] = _FixPath(external_out_dir) + '\\'
2809 # Make sure that 'TargetPath' matches 'Lib.OutputFile' or 'Link.OutputFile'
2810 # (depending on the tool used) to avoid MSB8012 warning.
2811 msbuild_tool_map = {
2812 'executable': 'Link',
2813 'shared_library': 'Link',
2814 'loadable_module': 'Link',
2815 'static_library': 'Lib',
2817 msbuild_tool = msbuild_tool_map.get(spec['type'])
2819 msbuild_settings = config['finalized_msbuild_settings']
2820 out_file = msbuild_settings[msbuild_tool].get('OutputFile')
2822 msbuild_attributes['TargetPath'] = _FixPath(out_file)
2823 target_ext = msbuild_settings[msbuild_tool].get('TargetExt')
2825 msbuild_attributes['TargetExt'] = target_ext
2827 return msbuild_attributes
2830 def _GetMSBuildConfigurationGlobalProperties(spec, configurations, build_file):
2831 # TODO(jeanluc) We could optimize out the following and do it only if
2832 # there are actions.
2833 # TODO(jeanluc) Handle the equivalent of setting 'CYGWIN=nontsec'.
2835 cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.'])[0]
2837 cyg_path = '$(MSBuildProjectDirectory)\\%s\\bin\\' % _FixPath(cygwin_dirs)
2838 new_paths.append(cyg_path)
2839 # TODO(jeanluc) Change the convention to have both a cygwin_dir and a
2841 python_path = cyg_path.replace('cygwin\\bin', 'python_26')
2842 new_paths.append(python_path)
2844 new_paths = '$(ExecutablePath);' + ';'.join(new_paths)
2847 for (name, configuration) in sorted(configurations.iteritems()):
2848 condition = _GetConfigurationCondition(name, configuration)
2849 attributes = _GetMSBuildAttributes(spec, configuration, build_file)
2850 msbuild_settings = configuration['finalized_msbuild_settings']
2851 _AddConditionalProperty(properties, condition, 'IntDir',
2852 attributes['IntermediateDirectory'])
2853 _AddConditionalProperty(properties, condition, 'OutDir',
2854 attributes['OutputDirectory'])
2855 _AddConditionalProperty(properties, condition, 'TargetName',
2856 attributes['TargetName'])
2857 if 'TargetExt' in attributes:
2858 _AddConditionalProperty(properties, condition, 'TargetExt',
2859 attributes['TargetExt'])
2861 if attributes.get('TargetPath'):
2862 _AddConditionalProperty(properties, condition, 'TargetPath',
2863 attributes['TargetPath'])
2864 if attributes.get('TargetExt'):
2865 _AddConditionalProperty(properties, condition, 'TargetExt',
2866 attributes['TargetExt'])
2869 _AddConditionalProperty(properties, condition, 'ExecutablePath',
2871 tool_settings = msbuild_settings.get('', {})
2872 for name, value in sorted(tool_settings.iteritems()):
2873 formatted_value = _GetValueFormattedForMSBuild('', name, value)
2874 _AddConditionalProperty(properties, condition, name, formatted_value)
2875 return _GetMSBuildPropertyGroup(spec, None, properties)
2878 def _AddConditionalProperty(properties, condition, name, value):
2879 """Adds a property / conditional value pair to a dictionary.
2882 properties: The dictionary to be modified. The key is the name of the
2883 property. The value is itself a dictionary; its key is the value and
2884 the value a list of condition for which this value is true.
2885 condition: The condition under which the named property has the value.
2886 name: The name of the property.
2887 value: The value of the property.
2889 if name not in properties:
2890 properties[name] = {}
2891 values = properties[name]
2892 if value not in values:
2894 conditions = values[value]
2895 conditions.append(condition)
2898 # Regex for msvs variable references ( i.e. $(FOO) ).
2899 MSVS_VARIABLE_REFERENCE = re.compile(r'\$\(([a-zA-Z_][a-zA-Z0-9_]*)\)')
2902 def _GetMSBuildPropertyGroup(spec, label, properties):
2903 """Returns a PropertyGroup definition for the specified properties.
2906 spec: The target project dict.
2907 label: An optional label for the PropertyGroup.
2908 properties: The dictionary to be converted. The key is the name of the
2909 property. The value is itself a dictionary; its key is the value and
2910 the value a list of condition for which this value is true.
2912 group = ['PropertyGroup']
2914 group.append({'Label': label})
2915 num_configurations = len(spec['configurations'])
2917 # Use a definition of edges such that user_of_variable -> used_varible.
2918 # This happens to be easier in this case, since a variable's
2919 # definition contains all variables it references in a single string.
2921 for value in sorted(properties[node].keys()):
2922 # Add to edges all $(...) references to variables.
2924 # Variable references that refer to names not in properties are excluded
2925 # These can exist for instance to refer built in definitions like
2928 # Self references are ignored. Self reference is used in a few places to
2929 # append to the default value. I.e. PATH=$(PATH);other_path
2930 edges.update(set([v for v in MSVS_VARIABLE_REFERENCE.findall(value)
2931 if v in properties and v != node]))
2933 properties_ordered = gyp.common.TopologicallySorted(
2934 properties.keys(), GetEdges)
2935 # Walk properties in the reverse of a topological sort on
2936 # user_of_variable -> used_variable as this ensures variables are
2937 # defined before they are used.
2938 # NOTE: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))
2939 for name in reversed(properties_ordered):
2940 values = properties[name]
2941 for value, conditions in sorted(values.iteritems()):
2942 if len(conditions) == num_configurations:
2943 # If the value is the same all configurations,
2944 # just add one unconditional entry.
2945 group.append([name, value])
2947 for condition in conditions:
2948 group.append([name, {'Condition': condition}, value])
2952 def _GetMSBuildToolSettingsSections(spec, configurations):
2954 for (name, configuration) in sorted(configurations.iteritems()):
2955 msbuild_settings = configuration['finalized_msbuild_settings']
2956 group = ['ItemDefinitionGroup',
2957 {'Condition': _GetConfigurationCondition(name, configuration)}
2959 for tool_name, tool_settings in sorted(msbuild_settings.iteritems()):
2960 # Skip the tool named '' which is a holder of global settings handled
2961 # by _GetMSBuildConfigurationGlobalProperties.
2965 for name, value in sorted(tool_settings.iteritems()):
2966 formatted_value = _GetValueFormattedForMSBuild(tool_name, name,
2968 tool.append([name, formatted_value])
2970 groups.append(group)
2974 def _FinalizeMSBuildSettings(spec, configuration):
2975 if 'msbuild_settings' in configuration:
2977 msbuild_settings = configuration['msbuild_settings']
2978 MSVSSettings.ValidateMSBuildSettings(msbuild_settings)
2981 msvs_settings = configuration.get('msvs_settings', {})
2982 msbuild_settings = MSVSSettings.ConvertToMSBuildSettings(msvs_settings)
2983 include_dirs, midl_include_dirs, resource_include_dirs = \
2984 _GetIncludeDirs(configuration)
2985 libraries = _GetLibraries(spec)
2986 library_dirs = _GetLibraryDirs(configuration)
2987 out_file, _, msbuild_tool = _GetOutputFilePathAndTool(spec, msbuild=True)
2988 target_ext = _GetOutputTargetExt(spec)
2989 defines = _GetDefines(configuration)
2991 # Visual Studio 2010 has TR1
2992 defines = [d for d in defines if d != '_HAS_TR1=0']
2993 # Warn of ignored settings
2994 ignored_settings = ['msvs_tool_files']
2995 for ignored_setting in ignored_settings:
2996 value = configuration.get(ignored_setting)
2998 print ('Warning: The automatic conversion to MSBuild does not handle '
2999 '%s. Ignoring setting of %s' % (ignored_setting, str(value)))
3001 defines = [_EscapeCppDefineForMSBuild(d) for d in defines]
3002 disabled_warnings = _GetDisabledWarnings(configuration)
3003 prebuild = configuration.get('msvs_prebuild')
3004 postbuild = configuration.get('msvs_postbuild')
3005 def_file = _GetModuleDefinition(spec)
3006 precompiled_header = configuration.get('msvs_precompiled_header')
3008 # Add the information to the appropriate tool
3009 # TODO(jeanluc) We could optimize and generate these settings only if
3010 # the corresponding files are found, e.g. don't generate ResourceCompile
3011 # if you don't have any resources.
3012 _ToolAppend(msbuild_settings, 'ClCompile',
3013 'AdditionalIncludeDirectories', include_dirs)
3014 _ToolAppend(msbuild_settings, 'Midl',
3015 'AdditionalIncludeDirectories', midl_include_dirs)
3016 _ToolAppend(msbuild_settings, 'ResourceCompile',
3017 'AdditionalIncludeDirectories', resource_include_dirs)
3018 # Add in libraries, note that even for empty libraries, we want this
3019 # set, to prevent inheriting default libraries from the enviroment.
3020 _ToolSetOrAppend(msbuild_settings, 'Link', 'AdditionalDependencies',
3022 _ToolAppend(msbuild_settings, 'Link', 'AdditionalLibraryDirectories',
3025 _ToolAppend(msbuild_settings, msbuild_tool, 'OutputFile', out_file,
3028 _ToolAppend(msbuild_settings, msbuild_tool, 'TargetExt', target_ext,
3031 _ToolAppend(msbuild_settings, 'ClCompile',
3032 'PreprocessorDefinitions', defines)
3033 _ToolAppend(msbuild_settings, 'ResourceCompile',
3034 'PreprocessorDefinitions', defines)
3035 # Add disabled warnings.
3036 _ToolAppend(msbuild_settings, 'ClCompile',
3037 'DisableSpecificWarnings', disabled_warnings)
3038 # Turn on precompiled headers if appropriate.
3039 if precompiled_header:
3040 precompiled_header = os.path.split(precompiled_header)[1]
3041 _ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'Use')
3042 _ToolAppend(msbuild_settings, 'ClCompile',
3043 'PrecompiledHeaderFile', precompiled_header)
3044 _ToolAppend(msbuild_settings, 'ClCompile',
3045 'ForcedIncludeFiles', [precompiled_header])
3047 _ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'NotUsing')
3048 # Turn off WinRT compilation
3049 _ToolAppend(msbuild_settings, 'ClCompile', 'CompileAsWinRT', 'false')
3050 # Turn on import libraries if appropriate
3051 if spec.get('msvs_requires_importlibrary'):
3052 _ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'false')
3053 # Loadable modules don't generate import libraries;
3054 # tell dependent projects to not expect one.
3055 if spec['type'] == 'loadable_module':
3056 _ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'true')
3057 # Set the module definition file if any.
3059 _ToolAppend(msbuild_settings, 'Link', 'ModuleDefinitionFile', def_file)
3060 configuration['finalized_msbuild_settings'] = msbuild_settings
3062 _ToolAppend(msbuild_settings, 'PreBuildEvent', 'Command', prebuild)
3064 _ToolAppend(msbuild_settings, 'PostBuildEvent', 'Command', postbuild)
3067 def _GetValueFormattedForMSBuild(tool_name, name, value):
3068 if type(value) == list:
3069 # For some settings, VS2010 does not automatically extends the settings
3070 # TODO(jeanluc) Is this what we want?
3071 if name in ['AdditionalIncludeDirectories',
3072 'AdditionalLibraryDirectories',
3073 'AdditionalOptions',
3075 'DisableSpecificWarnings',
3076 'PreprocessorDefinitions']:
3077 value.append('%%(%s)' % name)
3078 # For most tools, entries in a list should be separated with ';' but some
3079 # settings use a space. Check for those first.
3081 'ClCompile': ['AdditionalOptions'],
3082 'Link': ['AdditionalOptions'],
3083 'Lib': ['AdditionalOptions']}
3084 if tool_name in exceptions and name in exceptions[tool_name]:
3088 formatted_value = char.join(
3089 [MSVSSettings.ConvertVCMacrosToMSBuild(i) for i in value])
3091 formatted_value = MSVSSettings.ConvertVCMacrosToMSBuild(value)
3092 return formatted_value
3095 def _VerifySourcesExist(sources, root_dir):
3096 """Verifies that all source files exist on disk.
3098 Checks that all regular source files, i.e. not created at run time,
3099 exist on disk. Missing files cause needless recompilation but no otherwise
3103 sources: A recursive list of Filter/file names.
3104 root_dir: The root directory for the relative path names.
3106 A list of source files that cannot be found on disk.
3108 missing_sources = []
3109 for source in sources:
3110 if isinstance(source, MSVSProject.Filter):
3111 missing_sources.extend(_VerifySourcesExist(source.contents, root_dir))
3113 if '$' not in source:
3114 full_path = os.path.join(root_dir, source)
3115 if not os.path.exists(full_path):
3116 missing_sources.append(full_path)
3117 return missing_sources
3120 def _GetMSBuildSources(spec, sources, exclusions, rule_dependencies,
3121 extension_to_rule_name, actions_spec,
3122 sources_handled_by_action, list_excluded):
3123 groups = ['none', 'masm', 'midl', 'include', 'compile', 'resource', 'rule',
3125 grouped_sources = {}
3127 grouped_sources[g] = []
3129 _AddSources2(spec, sources, exclusions, grouped_sources,
3130 rule_dependencies, extension_to_rule_name,
3131 sources_handled_by_action, list_excluded)
3134 if grouped_sources[g]:
3135 sources.append(['ItemGroup'] + grouped_sources[g])
3137 sources.append(['ItemGroup'] + actions_spec)
3141 def _AddSources2(spec, sources, exclusions, grouped_sources,
3142 rule_dependencies, extension_to_rule_name,
3143 sources_handled_by_action,
3145 extensions_excluded_from_precompile = []
3146 for source in sources:
3147 if isinstance(source, MSVSProject.Filter):
3148 _AddSources2(spec, source.contents, exclusions, grouped_sources,
3149 rule_dependencies, extension_to_rule_name,
3150 sources_handled_by_action,
3153 if not source in sources_handled_by_action:
3155 excluded_configurations = exclusions.get(source, [])
3156 if len(excluded_configurations) == len(spec['configurations']):
3157 detail.append(['ExcludedFromBuild', 'true'])
3159 for config_name, configuration in sorted(excluded_configurations):
3160 condition = _GetConfigurationCondition(config_name, configuration)
3161 detail.append(['ExcludedFromBuild',
3162 {'Condition': condition},
3164 # Add precompile if needed
3165 for config_name, configuration in spec['configurations'].iteritems():
3166 precompiled_source = configuration.get('msvs_precompiled_source', '')
3167 if precompiled_source != '':
3168 precompiled_source = _FixPath(precompiled_source)
3169 if not extensions_excluded_from_precompile:
3170 # If the precompiled header is generated by a C source, we must
3171 # not try to use it for C++ sources, and vice versa.
3172 basename, extension = os.path.splitext(precompiled_source)
3173 if extension == '.c':
3174 extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
3176 extensions_excluded_from_precompile = ['.c']
3178 if precompiled_source == source:
3179 condition = _GetConfigurationCondition(config_name, configuration)
3180 detail.append(['PrecompiledHeader',
3181 {'Condition': condition},
3185 # Turn off precompiled header usage for source files of a
3186 # different type than the file that generated the
3187 # precompiled header.
3188 for extension in extensions_excluded_from_precompile:
3189 if source.endswith(extension):
3190 detail.append(['PrecompiledHeader', ''])
3191 detail.append(['ForcedIncludeFiles', ''])
3193 group, element = _MapFileToMsBuildSourceType(source, rule_dependencies,
3194 extension_to_rule_name)
3195 grouped_sources[group].append([element, {'Include': source}] + detail)
3198 def _GetMSBuildProjectReferences(project):
3200 if project.dependencies:
3201 group = ['ItemGroup']
3202 for dependency in project.dependencies:
3203 guid = dependency.guid
3204 project_dir = os.path.split(project.path)[0]
3205 relative_path = gyp.common.RelativePath(dependency.path, project_dir)
3206 project_ref = ['ProjectReference',
3207 {'Include': relative_path},
3209 ['ReferenceOutputAssembly', 'false']
3211 for config in dependency.spec.get('configurations', {}).itervalues():
3212 # If it's disabled in any config, turn it off in the reference.
3213 if config.get('msvs_2010_disable_uldi_when_referenced', 0):
3214 project_ref.append(['UseLibraryDependencyInputs', 'false'])
3216 group.append(project_ref)
3217 references.append(group)
3221 def _GenerateMSBuildProject(project, options, version, generator_flags):
3223 configurations = spec['configurations']
3224 project_dir, project_file_name = os.path.split(project.path)
3225 gyp.common.EnsureDirExists(project.path)
3226 # Prepare list of sources and excluded sources.
3227 gyp_path = _NormalizedSource(project.build_file)
3228 relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
3230 gyp_file = os.path.split(project.build_file)[1]
3231 sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
3235 props_files_of_rules = set()
3236 targets_files_of_rules = set()
3237 rule_dependencies = set()
3238 extension_to_rule_name = {}
3239 list_excluded = generator_flags.get('msvs_list_excluded_files', True)
3241 # Don't generate rules if we are using an external builder like ninja.
3242 if not spec.get('msvs_external_builder'):
3243 _GenerateRulesForMSBuild(project_dir, options, spec,
3244 sources, excluded_sources,
3245 props_files_of_rules, targets_files_of_rules,
3246 actions_to_add, rule_dependencies,
3247 extension_to_rule_name)
3249 rules = spec.get('rules', [])
3250 _AdjustSourcesForRules(rules, sources, excluded_sources, True)
3252 sources, excluded_sources, excluded_idl = (
3253 _AdjustSourcesAndConvertToFilterHierarchy(spec, options,
3254 project_dir, sources,
3256 list_excluded, version))
3258 # Don't add actions if we are using an external builder like ninja.
3259 if not spec.get('msvs_external_builder'):
3260 _AddActions(actions_to_add, spec, project.build_file)
3261 _AddCopies(actions_to_add, spec)
3263 # NOTE: this stanza must appear after all actions have been decided.
3264 # Don't excluded sources with actions attached, or they won't run.
3265 excluded_sources = _FilterActionsFromExcluded(
3266 excluded_sources, actions_to_add)
3268 exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
3269 actions_spec, sources_handled_by_action = _GenerateActionsForMSBuild(
3270 spec, actions_to_add)
3272 _GenerateMSBuildFiltersFile(project.path + '.filters', sources,
3274 extension_to_rule_name)
3275 missing_sources = _VerifySourcesExist(sources, project_dir)
3277 for configuration in configurations.itervalues():
3278 _FinalizeMSBuildSettings(spec, configuration)
3280 # Add attributes to root element
3282 import_default_section = [
3283 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.Default.props'}]]
3284 import_cpp_props_section = [
3285 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.props'}]]
3286 import_cpp_targets_section = [
3287 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.targets'}]]
3288 import_masm_props_section = [
3290 {'Project': r'$(VCTargetsPath)\BuildCustomizations\masm.props'}]]
3291 import_masm_targets_section = [
3293 {'Project': r'$(VCTargetsPath)\BuildCustomizations\masm.targets'}]]
3294 macro_section = [['PropertyGroup', {'Label': 'UserMacros'}]]
3298 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003',
3299 'ToolsVersion': version.ProjectVersion(),
3300 'DefaultTargets': 'Build'
3303 content += _GetMSBuildProjectConfigurations(configurations)
3304 content += _GetMSBuildGlobalProperties(spec, project.guid, project_file_name)
3305 content += import_default_section
3306 content += _GetMSBuildConfigurationDetails(spec, project.build_file)
3307 if spec.get('msvs_enable_winphone'):
3308 content += _GetMSBuildLocalProperties('v120_wp81')
3310 content += _GetMSBuildLocalProperties(project.msbuild_toolset)
3311 content += import_cpp_props_section
3312 content += import_masm_props_section
3313 content += _GetMSBuildExtensions(props_files_of_rules)
3314 content += _GetMSBuildPropertySheets(configurations)
3315 content += macro_section
3316 content += _GetMSBuildConfigurationGlobalProperties(spec, configurations,
3318 content += _GetMSBuildToolSettingsSections(spec, configurations)
3319 content += _GetMSBuildSources(
3320 spec, sources, exclusions, rule_dependencies, extension_to_rule_name,
3321 actions_spec, sources_handled_by_action, list_excluded)
3322 content += _GetMSBuildProjectReferences(project)
3323 content += import_cpp_targets_section
3324 content += import_masm_targets_section
3325 content += _GetMSBuildExtensionTargets(targets_files_of_rules)
3327 if spec.get('msvs_external_builder'):
3328 content += _GetMSBuildExternalBuilderTargets(spec)
3330 # TODO(jeanluc) File a bug to get rid of runas. We had in MSVS:
3331 # has_run_as = _WriteMSVSUserFile(project.path, version, spec)
3333 easy_xml.WriteXmlIfChanged(content, project.path, pretty=True, win32=True)
3335 return missing_sources
3338 def _GetMSBuildExternalBuilderTargets(spec):
3339 """Return a list of MSBuild targets for external builders.
3341 The "Build" and "Clean" targets are always generated. If the spec contains
3342 'msvs_external_builder_clcompile_cmd', then the "ClCompile" target will also
3343 be generated, to support building selected C/C++ files.
3346 spec: The gyp target spec.
3348 List of MSBuild 'Target' specs.
3350 build_cmd = _BuildCommandLineForRuleRaw(
3351 spec, spec['msvs_external_builder_build_cmd'],
3352 False, False, False, False)
3353 build_target = ['Target', {'Name': 'Build'}]
3354 build_target.append(['Exec', {'Command': build_cmd}])
3356 clean_cmd = _BuildCommandLineForRuleRaw(
3357 spec, spec['msvs_external_builder_clean_cmd'],
3358 False, False, False, False)
3359 clean_target = ['Target', {'Name': 'Clean'}]
3360 clean_target.append(['Exec', {'Command': clean_cmd}])
3362 targets = [build_target, clean_target]
3364 if spec.get('msvs_external_builder_clcompile_cmd'):
3365 clcompile_cmd = _BuildCommandLineForRuleRaw(
3366 spec, spec['msvs_external_builder_clcompile_cmd'],
3367 False, False, False, False)
3368 clcompile_target = ['Target', {'Name': 'ClCompile'}]
3369 clcompile_target.append(['Exec', {'Command': clcompile_cmd}])
3370 targets.append(clcompile_target)
3375 def _GetMSBuildExtensions(props_files_of_rules):
3376 extensions = ['ImportGroup', {'Label': 'ExtensionSettings'}]
3377 for props_file in props_files_of_rules:
3378 extensions.append(['Import', {'Project': props_file}])
3382 def _GetMSBuildExtensionTargets(targets_files_of_rules):
3383 targets_node = ['ImportGroup', {'Label': 'ExtensionTargets'}]
3384 for targets_file in sorted(targets_files_of_rules):
3385 targets_node.append(['Import', {'Project': targets_file}])
3386 return [targets_node]
3389 def _GenerateActionsForMSBuild(spec, actions_to_add):
3390 """Add actions accumulated into an actions_to_add, merging as needed.
3393 spec: the target project dict
3394 actions_to_add: dictionary keyed on input name, which maps to a list of
3395 dicts describing the actions attached to that input file.
3398 A pair of (action specification, the sources handled by this action).
3400 sources_handled_by_action = OrderedSet()
3402 for primary_input, actions in actions_to_add.iteritems():
3403 inputs = OrderedSet()
3404 outputs = OrderedSet()
3407 for action in actions:
3408 inputs.update(OrderedSet(action['inputs']))
3409 outputs.update(OrderedSet(action['outputs']))
3410 descriptions.append(action['description'])
3411 cmd = action['command']
3412 # For most actions, add 'call' so that actions that invoke batch files
3413 # return and continue executing. msbuild_use_call provides a way to
3414 # disable this but I have not seen any adverse effect from doing that
3416 if action.get('msbuild_use_call', True):
3418 commands.append(cmd)
3419 # Add the custom build action for one input file.
3420 description = ', and also '.join(descriptions)
3422 # We can't join the commands simply with && because the command line will
3423 # get too long. See also _AddActions: cygwin's setup_env mustn't be called
3424 # for every invocation or the command that sets the PATH will grow too
3426 command = '\r\n'.join([c + '\r\nif %errorlevel% neq 0 exit /b %errorlevel%'
3428 _AddMSBuildAction(spec,
3434 sources_handled_by_action,
3436 return actions_spec, sources_handled_by_action
3439 def _AddMSBuildAction(spec, primary_input, inputs, outputs, cmd, description,
3440 sources_handled_by_action, actions_spec):
3441 command = MSVSSettings.ConvertVCMacrosToMSBuild(cmd)
3442 primary_input = _FixPath(primary_input)
3443 inputs_array = _FixPaths(inputs)
3444 outputs_array = _FixPaths(outputs)
3445 additional_inputs = ';'.join([i for i in inputs_array
3446 if i != primary_input])
3447 outputs = ';'.join(outputs_array)
3448 sources_handled_by_action.add(primary_input)
3449 action_spec = ['CustomBuild', {'Include': primary_input}]
3451 # TODO(jeanluc) 'Document' for all or just if as_sources?
3452 [['FileType', 'Document'],
3453 ['Command', command],
3454 ['Message', description],
3455 ['Outputs', outputs]
3457 if additional_inputs:
3458 action_spec.append(['AdditionalInputs', additional_inputs])
3459 actions_spec.append(action_spec)