1 # Copyright (c) 2012 Google Inc. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
14 import gyp.easy_xml as easy_xml
15 import gyp.generator.ninja as ninja_generator
16 import gyp.MSVSNew as MSVSNew
17 import gyp.MSVSProject as MSVSProject
18 import gyp.MSVSSettings as MSVSSettings
19 import gyp.MSVSToolFile as MSVSToolFile
20 import gyp.MSVSUserFile as MSVSUserFile
21 import gyp.MSVSUtil as MSVSUtil
22 import gyp.MSVSVersion as MSVSVersion
23 from gyp.common import GypError
24 from gyp.common import OrderedSet
26 # TODO: Remove once bots are on 2.7, http://crbug.com/241769
27 def _import_OrderedDict():
30 return collections.OrderedDict
31 except AttributeError:
32 import gyp.ordered_dict
33 return gyp.ordered_dict.OrderedDict
34 OrderedDict = _import_OrderedDict()
37 # Regular expression for validating Visual Studio GUIDs. If the GUID
38 # contains lowercase hex letters, MSVS will be fine. However,
39 # IncrediBuild BuildConsole will parse the solution file, but then
40 # silently skip building the target causing hard to track down errors.
41 # Note that this only happens with the BuildConsole, and does not occur
42 # if IncrediBuild is executed from inside Visual Studio. This regex
43 # validates that the string looks like a GUID with all uppercase hex
45 VALID_MSVS_GUID_CHARS = re.compile('^[A-F0-9\-]+$')
48 generator_default_variables = {
49 'EXECUTABLE_PREFIX': '',
50 'EXECUTABLE_SUFFIX': '.exe',
51 'STATIC_LIB_PREFIX': '',
52 'SHARED_LIB_PREFIX': '',
53 'STATIC_LIB_SUFFIX': '.lib',
54 'SHARED_LIB_SUFFIX': '.dll',
55 'INTERMEDIATE_DIR': '$(IntDir)',
56 'SHARED_INTERMEDIATE_DIR': '$(OutDir)obj/global_intermediate',
58 'PRODUCT_DIR': '$(OutDir)',
59 'LIB_DIR': '$(OutDir)lib',
60 'RULE_INPUT_ROOT': '$(InputName)',
61 'RULE_INPUT_DIRNAME': '$(InputDir)',
62 'RULE_INPUT_EXT': '$(InputExt)',
63 'RULE_INPUT_NAME': '$(InputFileName)',
64 'RULE_INPUT_PATH': '$(InputPath)',
65 'CONFIGURATION_NAME': '$(ConfigurationName)',
69 # The msvs specific sections that hold paths
70 generator_additional_path_sections = [
76 generator_additional_non_configuration_keys = [
81 'msvs_external_builder',
82 'msvs_external_builder_out_dir',
83 'msvs_external_builder_build_cmd',
84 'msvs_external_builder_clean_cmd',
85 'msvs_external_builder_clcompile_cmd',
87 'msvs_requires_importlibrary',
88 'msvs_enable_winphone',
92 # List of precompiled header related keys.
94 'msvs_precompiled_header',
95 'msvs_precompiled_source',
99 cached_username = None
105 # TODO(gspencer): Switch the os.environ calls to be
106 # win32api.GetDomainName() and win32api.GetUserName() once the
107 # python version in depot_tools has been updated to work on Vista
109 def _GetDomainAndUserName():
110 if sys.platform not in ('win32', 'cygwin'):
111 return ('DOMAIN', 'USERNAME')
112 global cached_username
114 if not cached_domain or not cached_username:
115 domain = os.environ.get('USERDOMAIN')
116 username = os.environ.get('USERNAME')
117 if not domain or not username:
118 call = subprocess.Popen(['net', 'config', 'Workstation'],
119 stdout=subprocess.PIPE)
120 config = call.communicate()[0]
121 username_re = re.compile('^User name\s+(\S+)', re.MULTILINE)
122 username_match = username_re.search(config)
124 username = username_match.group(1)
125 domain_re = re.compile('^Logon domain\s+(\S+)', re.MULTILINE)
126 domain_match = domain_re.search(config)
128 domain = domain_match.group(1)
129 cached_domain = domain
130 cached_username = username
131 return (cached_domain, cached_username)
133 fixpath_prefix = None
136 def _NormalizedSource(source):
137 """Normalize the path.
139 But not if that gets rid of a variable, as this may expand to something
140 larger than one directory.
143 source: The path to be normalize.d
148 normalized = os.path.normpath(source)
149 if source.count('$') == normalized.count('$'):
155 """Convert paths to a form that will make sense in a vcproj file.
158 path: The path to convert, may contain / etc.
160 The path with all slashes made into backslashes.
162 if fixpath_prefix and path and not os.path.isabs(path) and not path[0] == '$':
163 path = os.path.join(fixpath_prefix, path)
164 path = path.replace('/', '\\')
165 path = _NormalizedSource(path)
166 if path and path[-1] == '\\':
171 def _FixPaths(paths):
172 """Fix each of the paths of the list."""
173 return [_FixPath(i) for i in paths]
176 def _ConvertSourcesToFilterHierarchy(sources, prefix=None, excluded=None,
177 list_excluded=True, msvs_version=None):
178 """Converts a list split source file paths into a vcproj folder hierarchy.
181 sources: A list of source file paths split.
182 prefix: A list of source file path layers meant to apply to each of sources.
183 excluded: A set of excluded files.
184 msvs_version: A MSVSVersion object.
187 A hierarchy of filenames and MSVSProject.Filter objects that matches the
188 layout of the source tree.
190 _ConvertSourcesToFilterHierarchy([['a', 'bob1.c'], ['b', 'bob2.c']],
193 [MSVSProject.Filter('a', contents=['joe\\a\\bob1.c']),
194 MSVSProject.Filter('b', contents=['joe\\b\\bob2.c'])]
196 if not prefix: prefix = []
199 folders = OrderedDict()
200 # Gather files into the final result, excluded, or folders.
203 filename = _NormalizedSource('\\'.join(prefix + s))
204 if filename in excluded:
205 excluded_result.append(filename)
207 result.append(filename)
208 elif msvs_version and not msvs_version.UsesVcxproj():
209 # For MSVS 2008 and earlier, we need to process all files before walking
211 if not folders.get(s[0]):
213 folders[s[0]].append(s[1:])
215 contents = _ConvertSourcesToFilterHierarchy([s[1:]], prefix + [s[0]],
217 list_excluded=list_excluded,
218 msvs_version=msvs_version)
219 contents = MSVSProject.Filter(s[0], contents=contents)
220 result.append(contents)
221 # Add a folder for excluded files.
222 if excluded_result and list_excluded:
223 excluded_folder = MSVSProject.Filter('_excluded_files',
224 contents=excluded_result)
225 result.append(excluded_folder)
227 if msvs_version and msvs_version.UsesVcxproj():
230 # Populate all the folders.
232 contents = _ConvertSourcesToFilterHierarchy(folders[f], prefix=prefix + [f],
234 list_excluded=list_excluded,
235 msvs_version=msvs_version)
236 contents = MSVSProject.Filter(f, contents=contents)
237 result.append(contents)
241 def _ToolAppend(tools, tool_name, setting, value, only_if_unset=False):
243 _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset)
246 def _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset=False):
247 # TODO(bradnelson): ugly hack, fix this more generally!!!
248 if 'Directories' in setting or 'Dependencies' in setting:
249 if type(value) == str:
250 value = value.replace('/', '\\')
252 value = [i.replace('/', '\\') for i in value]
253 if not tools.get(tool_name):
254 tools[tool_name] = dict()
255 tool = tools[tool_name]
256 if tool.get(setting):
257 if only_if_unset: return
258 if type(tool[setting]) == list and type(value) == list:
259 tool[setting] += value
262 'Appending "%s" to a non-list setting "%s" for tool "%s" is '
263 'not allowed, previous value: %s' % (
264 value, setting, tool_name, str(tool[setting])))
266 tool[setting] = value
269 def _ConfigPlatform(config_data):
270 return config_data.get('msvs_configuration_platform', 'Win32')
273 def _ConfigBaseName(config_name, platform_name):
274 if config_name.endswith('_' + platform_name):
275 return config_name[0:-len(platform_name) - 1]
280 def _ConfigFullName(config_name, config_data):
281 platform_name = _ConfigPlatform(config_data)
282 return '%s|%s' % (_ConfigBaseName(config_name, platform_name), platform_name)
285 def _BuildCommandLineForRuleRaw(spec, cmd, cygwin_shell, has_input_path,
286 quote_cmd, do_setup_env):
288 if [x for x in cmd if '$(InputDir)' in x]:
289 input_dir_preamble = (
290 'set INPUTDIR=$(InputDir)\n'
291 'if NOT DEFINED INPUTDIR set INPUTDIR=.\\\n'
292 'set INPUTDIR=%INPUTDIR:~0,-1%\n'
295 input_dir_preamble = ''
298 # Find path to cygwin.
299 cygwin_dir = _FixPath(spec.get('msvs_cygwin_dirs', ['.'])[0])
302 direct_cmd = [i.replace('$(IntDir)',
303 '`cygpath -m "${INTDIR}"`') for i in direct_cmd]
304 direct_cmd = [i.replace('$(OutDir)',
305 '`cygpath -m "${OUTDIR}"`') for i in direct_cmd]
306 direct_cmd = [i.replace('$(InputDir)',
307 '`cygpath -m "${INPUTDIR}"`') for i in direct_cmd]
309 direct_cmd = [i.replace('$(InputPath)',
310 '`cygpath -m "${INPUTPATH}"`')
312 direct_cmd = ['\\"%s\\"' % i.replace('"', '\\\\\\"') for i in direct_cmd]
313 # direct_cmd = gyp.common.EncodePOSIXShellList(direct_cmd)
314 direct_cmd = ' '.join(direct_cmd)
315 # TODO(quote): regularize quoting path names throughout the module
318 cmd += 'call "$(ProjectDir)%(cygwin_dir)s\\setup_env.bat" && '
319 cmd += 'set CYGWIN=nontsec&& '
320 if direct_cmd.find('NUMBER_OF_PROCESSORS') >= 0:
321 cmd += 'set /a NUMBER_OF_PROCESSORS_PLUS_1=%%NUMBER_OF_PROCESSORS%%+1&& '
322 if direct_cmd.find('INTDIR') >= 0:
323 cmd += 'set INTDIR=$(IntDir)&& '
324 if direct_cmd.find('OUTDIR') >= 0:
325 cmd += 'set OUTDIR=$(OutDir)&& '
326 if has_input_path and direct_cmd.find('INPUTPATH') >= 0:
327 cmd += 'set INPUTPATH=$(InputPath) && '
328 cmd += 'bash -c "%(cmd)s"'
329 cmd = cmd % {'cygwin_dir': cygwin_dir,
331 return input_dir_preamble + cmd
333 # Convert cat --> type to mimic unix.
337 command = [cmd[0].replace('/', '\\')]
338 # Add call before command to ensure that commands can be tied together one
339 # after the other without aborting in Incredibuild, since IB makes a bat
340 # file out of the raw command string, and some commands (like python) are
341 # actually batch files themselves.
342 command.insert(0, 'call')
344 # TODO(quote): This is a really ugly heuristic, and will miss path fixing
345 # for arguments like "--arg=path" or "/opt:path".
346 # If the argument starts with a slash or dash, it's probably a command line
348 arguments = [i if (i[:1] in "/-") else _FixPath(i) for i in cmd[1:]]
349 arguments = [i.replace('$(InputDir)', '%INPUTDIR%') for i in arguments]
350 arguments = [MSVSSettings.FixVCMacroSlashes(i) for i in arguments]
352 # Support a mode for using cmd directly.
353 # Convert any paths to native form (first element is used directly).
354 # TODO(quote): regularize quoting path names throughout the module
355 arguments = ['"%s"' % i for i in arguments]
356 # Collapse into a single command.
357 return input_dir_preamble + ' '.join(command + arguments)
360 def _BuildCommandLineForRule(spec, rule, has_input_path, do_setup_env):
361 # Currently this weird argument munging is used to duplicate the way a
362 # python script would need to be run as part of the chrome tree.
363 # Eventually we should add some sort of rule_default option to set this
364 # per project. For now the behavior chrome needs is the default.
365 mcs = rule.get('msvs_cygwin_shell')
367 mcs = int(spec.get('msvs_cygwin_shell', 1))
368 elif isinstance(mcs, str):
370 quote_cmd = int(rule.get('msvs_quote_cmd', 1))
371 return _BuildCommandLineForRuleRaw(spec, rule['action'], mcs, has_input_path,
372 quote_cmd, do_setup_env=do_setup_env)
375 def _AddActionStep(actions_dict, inputs, outputs, description, command):
376 """Merge action into an existing list of actions.
378 Care must be taken so that actions which have overlapping inputs either don't
379 get assigned to the same input, or get collapsed into one.
382 actions_dict: dictionary keyed on input name, which maps to a list of
383 dicts describing the actions attached to that input file.
384 inputs: list of inputs
385 outputs: list of outputs
386 description: description of the action
387 command: command line to execute
389 # Require there to be at least one input (call sites will ensure this).
395 'description': description,
399 # Pick where to stick this action.
400 # While less than optimal in terms of build time, attach them to the first
402 chosen_input = inputs[0]
405 if chosen_input not in actions_dict:
406 actions_dict[chosen_input] = []
407 actions_dict[chosen_input].append(action)
410 def _AddCustomBuildToolForMSVS(p, spec, primary_input,
411 inputs, outputs, description, cmd):
412 """Add a custom build tool to execute something.
415 p: the target project
416 spec: the target project dict
417 primary_input: input file to attach the build tool to
418 inputs: list of inputs
419 outputs: list of outputs
420 description: description of the action
421 cmd: command line to execute
423 inputs = _FixPaths(inputs)
424 outputs = _FixPaths(outputs)
425 tool = MSVSProject.Tool(
427 {'Description': description,
428 'AdditionalDependencies': ';'.join(inputs),
429 'Outputs': ';'.join(outputs),
432 # Add to the properties of primary input for each config.
433 for config_name, c_data in spec['configurations'].iteritems():
434 p.AddFileConfig(_FixPath(primary_input),
435 _ConfigFullName(config_name, c_data), tools=[tool])
438 def _AddAccumulatedActionsToMSVS(p, spec, actions_dict):
439 """Add actions accumulated into an actions_dict, merging as needed.
442 p: the target project
443 spec: the target project dict
444 actions_dict: dictionary keyed on input name, which maps to a list of
445 dicts describing the actions attached to that input file.
447 for primary_input in actions_dict:
448 inputs = OrderedSet()
449 outputs = OrderedSet()
452 for action in actions_dict[primary_input]:
453 inputs.update(OrderedSet(action['inputs']))
454 outputs.update(OrderedSet(action['outputs']))
455 descriptions.append(action['description'])
456 commands.append(action['command'])
457 # Add the custom build step for one input file.
458 description = ', and also '.join(descriptions)
459 command = '\r\n'.join(commands)
460 _AddCustomBuildToolForMSVS(p, spec,
461 primary_input=primary_input,
464 description=description,
468 def _RuleExpandPath(path, input_file):
469 """Given the input file to which a rule applied, string substitute a path.
472 path: a path to string expand
473 input_file: the file to which the rule applied.
475 The string substituted path.
477 path = path.replace('$(InputName)',
478 os.path.splitext(os.path.split(input_file)[1])[0])
479 path = path.replace('$(InputDir)', os.path.dirname(input_file))
480 path = path.replace('$(InputExt)',
481 os.path.splitext(os.path.split(input_file)[1])[1])
482 path = path.replace('$(InputFileName)', os.path.split(input_file)[1])
483 path = path.replace('$(InputPath)', input_file)
487 def _FindRuleTriggerFiles(rule, sources):
488 """Find the list of files which a particular rule applies to.
491 rule: the rule in question
492 sources: the set of all known source files for this project
494 The list of sources that trigger a particular rule.
496 return rule.get('rule_sources', [])
499 def _RuleInputsAndOutputs(rule, trigger_file):
500 """Find the inputs and outputs generated by a rule.
503 rule: the rule in question.
504 trigger_file: the main trigger for this rule.
506 The pair of (inputs, outputs) involved in this rule.
508 raw_inputs = _FixPaths(rule.get('inputs', []))
509 raw_outputs = _FixPaths(rule.get('outputs', []))
510 inputs = OrderedSet()
511 outputs = OrderedSet()
512 inputs.add(trigger_file)
514 inputs.add(_RuleExpandPath(i, trigger_file))
515 for o in raw_outputs:
516 outputs.add(_RuleExpandPath(o, trigger_file))
517 return (inputs, outputs)
520 def _GenerateNativeRulesForMSVS(p, rules, output_dir, spec, options):
521 """Generate a native rules file.
524 p: the target project
525 rules: the set of rules to include
526 output_dir: the directory in which the project/gyp resides
527 spec: the project dict
528 options: global generator options
530 rules_filename = '%s%s.rules' % (spec['target_name'],
532 rules_file = MSVSToolFile.Writer(os.path.join(output_dir, rules_filename),
536 rule_name = r['rule_name']
537 rule_ext = r['extension']
538 inputs = _FixPaths(r.get('inputs', []))
539 outputs = _FixPaths(r.get('outputs', []))
540 # Skip a rule with no action and no inputs.
541 if 'action' not in r and not r.get('rule_sources', []):
543 cmd = _BuildCommandLineForRule(spec, r, has_input_path=True,
545 rules_file.AddCustomBuildRule(name=rule_name,
546 description=r.get('message', rule_name),
547 extensions=[rule_ext],
548 additional_dependencies=inputs,
551 # Write out rules file.
552 rules_file.WriteIfChanged()
554 # Add rules file to project.
555 p.AddToolFile(rules_filename)
558 def _Cygwinify(path):
559 path = path.replace('$(OutDir)', '$(OutDirCygwin)')
560 path = path.replace('$(IntDir)', '$(IntDirCygwin)')
564 def _GenerateExternalRules(rules, output_dir, spec,
565 sources, options, actions_to_add):
566 """Generate an external makefile to do a set of rules.
569 rules: the list of rules to include
570 output_dir: path containing project and gyp files
571 spec: project specification data
572 sources: set of sources known
573 options: global generator options
574 actions_to_add: The list of actions we will add to.
576 filename = '%s_rules%s.mk' % (spec['target_name'], options.suffix)
577 mk_file = gyp.common.WriteOnDiff(os.path.join(output_dir, filename))
578 # Find cygwin style versions of some paths.
579 mk_file.write('OutDirCygwin:=$(shell cygpath -u "$(OutDir)")\n')
580 mk_file.write('IntDirCygwin:=$(shell cygpath -u "$(IntDir)")\n')
581 # Gather stuff needed to emit all: target.
582 all_inputs = OrderedSet()
583 all_outputs = OrderedSet()
584 all_output_dirs = OrderedSet()
587 trigger_files = _FindRuleTriggerFiles(rule, sources)
588 for tf in trigger_files:
589 inputs, outputs = _RuleInputsAndOutputs(rule, tf)
590 all_inputs.update(OrderedSet(inputs))
591 all_outputs.update(OrderedSet(outputs))
592 # Only use one target from each rule as the dependency for
593 # 'all' so we don't try to build each rule multiple times.
594 first_outputs.append(list(outputs)[0])
595 # Get the unique output directories for this rule.
596 output_dirs = [os.path.split(i)[0] for i in outputs]
597 for od in output_dirs:
598 all_output_dirs.add(od)
599 first_outputs_cyg = [_Cygwinify(i) for i in first_outputs]
600 # Write out all: target, including mkdir for each output directory.
601 mk_file.write('all: %s\n' % ' '.join(first_outputs_cyg))
602 for od in all_output_dirs:
604 mk_file.write('\tmkdir -p `cygpath -u "%s"`\n' % od)
606 # Define how each output is generated.
608 trigger_files = _FindRuleTriggerFiles(rule, sources)
609 for tf in trigger_files:
610 # Get all the inputs and outputs for this rule for this trigger file.
611 inputs, outputs = _RuleInputsAndOutputs(rule, tf)
612 inputs = [_Cygwinify(i) for i in inputs]
613 outputs = [_Cygwinify(i) for i in outputs]
614 # Prepare the command line for this rule.
615 cmd = [_RuleExpandPath(c, tf) for c in rule['action']]
616 cmd = ['"%s"' % i for i in cmd]
618 # Add it to the makefile.
619 mk_file.write('%s: %s\n' % (' '.join(outputs), ' '.join(inputs)))
620 mk_file.write('\t%s\n\n' % cmd)
624 # Add makefile to list of sources.
625 sources.add(filename)
626 # Add a build action to call makefile.
630 '-j', '${NUMBER_OF_PROCESSORS_PLUS_1}',
632 cmd = _BuildCommandLineForRuleRaw(spec, cmd, True, False, True, True)
633 # Insert makefile as 0'th input, so it gets the action attached there,
634 # as this is easier to understand from in the IDE.
635 all_inputs = list(all_inputs)
636 all_inputs.insert(0, filename)
637 _AddActionStep(actions_to_add,
638 inputs=_FixPaths(all_inputs),
639 outputs=_FixPaths(all_outputs),
640 description='Running external rules for %s' %
645 def _EscapeEnvironmentVariableExpansion(s):
646 """Escapes % characters.
648 Escapes any % characters so that Windows-style environment variable
649 expansions will leave them alone.
650 See http://connect.microsoft.com/VisualStudio/feedback/details/106127/cl-d-name-text-containing-percentage-characters-doesnt-compile
651 to understand why we have to do this.
654 s: The string to be escaped.
659 s = s.replace('%', '%%')
663 quote_replacer_regex = re.compile(r'(\\*)"')
666 def _EscapeCommandLineArgumentForMSVS(s):
667 """Escapes a Windows command-line argument.
669 So that the Win32 CommandLineToArgv function will turn the escaped result back
670 into the original string.
671 See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
672 ("Parsing C++ Command-Line Arguments") to understand why we have to do
676 s: the string to be escaped.
682 # For a literal quote, CommandLineToArgv requires an odd number of
683 # backslashes preceding it, and it produces half as many literal backslashes
684 # (rounded down). So we need to produce 2n+1 backslashes.
685 return 2 * match.group(1) + '\\"'
687 # Escape all quotes so that they are interpreted literally.
688 s = quote_replacer_regex.sub(_Replace, s)
689 # Now add unescaped quotes so that any whitespace is interpreted literally.
694 delimiters_replacer_regex = re.compile(r'(\\*)([,;]+)')
697 def _EscapeVCProjCommandLineArgListItem(s):
698 """Escapes command line arguments for MSVS.
700 The VCProj format stores string lists in a single string using commas and
701 semi-colons as separators, which must be quoted if they are to be
702 interpreted literally. However, command-line arguments may already have
703 quotes, and the VCProj parser is ignorant of the backslash escaping
704 convention used by CommandLineToArgv, so the command-line quotes and the
705 VCProj quotes may not be the same quotes. So to store a general
706 command-line argument in a VCProj list, we need to parse the existing
707 quoting according to VCProj's convention and quote any delimiters that are
708 not already quoted by that convention. The quotes that we add will also be
709 seen by CommandLineToArgv, so if backslashes precede them then we also have
710 to escape those backslashes according to the CommandLineToArgv
714 s: the string to be escaped.
720 # For a non-literal quote, CommandLineToArgv requires an even number of
721 # backslashes preceding it, and it produces half as many literal
722 # backslashes. So we need to produce 2n backslashes.
723 return 2 * match.group(1) + '"' + match.group(2) + '"'
725 segments = s.split('"')
726 # The unquoted segments are at the even-numbered indices.
727 for i in range(0, len(segments), 2):
728 segments[i] = delimiters_replacer_regex.sub(_Replace, segments[i])
729 # Concatenate back into a single string
730 s = '"'.join(segments)
731 if len(segments) % 2 == 0:
732 # String ends while still quoted according to VCProj's convention. This
733 # means the delimiter and the next list item that follow this one in the
734 # .vcproj file will be misinterpreted as part of this item. There is nothing
735 # we can do about this. Adding an extra quote would correct the problem in
736 # the VCProj but cause the same problem on the final command-line. Moving
737 # the item to the end of the list does works, but that's only possible if
738 # there's only one such item. Let's just warn the user.
739 print >> sys.stderr, ('Warning: MSVS may misinterpret the odd number of ' +
744 def _EscapeCppDefineForMSVS(s):
745 """Escapes a CPP define so that it will reach the compiler unaltered."""
746 s = _EscapeEnvironmentVariableExpansion(s)
747 s = _EscapeCommandLineArgumentForMSVS(s)
748 s = _EscapeVCProjCommandLineArgListItem(s)
749 # cl.exe replaces literal # characters with = in preprocesor definitions for
750 # some reason. Octal-encode to work around that.
751 s = s.replace('#', '\\%03o' % ord('#'))
755 quote_replacer_regex2 = re.compile(r'(\\+)"')
758 def _EscapeCommandLineArgumentForMSBuild(s):
759 """Escapes a Windows command-line argument for use by MSBuild."""
762 return (len(match.group(1)) / 2 * 4) * '\\' + '\\"'
764 # Escape all quotes so that they are interpreted literally.
765 s = quote_replacer_regex2.sub(_Replace, s)
769 def _EscapeMSBuildSpecialCharacters(s):
770 escape_dictionary = {
779 result = ''.join([escape_dictionary.get(c, c) for c in s])
783 def _EscapeCppDefineForMSBuild(s):
784 """Escapes a CPP define so that it will reach the compiler unaltered."""
785 s = _EscapeEnvironmentVariableExpansion(s)
786 s = _EscapeCommandLineArgumentForMSBuild(s)
787 s = _EscapeMSBuildSpecialCharacters(s)
788 # cl.exe replaces literal # characters with = in preprocesor definitions for
789 # some reason. Octal-encode to work around that.
790 s = s.replace('#', '\\%03o' % ord('#'))
794 def _GenerateRulesForMSVS(p, output_dir, options, spec,
795 sources, excluded_sources,
797 """Generate all the rules for a particular project.
801 output_dir: directory to emit rules to
802 options: global options passed to the generator
803 spec: the specification for this project
804 sources: the set of all known source files in this project
805 excluded_sources: the set of sources excluded from normal processing
806 actions_to_add: deferred list of actions to add in
808 rules = spec.get('rules', [])
809 rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
810 rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
812 # Handle rules that use a native rules file.
814 _GenerateNativeRulesForMSVS(p, rules_native, output_dir, spec, options)
816 # Handle external rules (non-native rules).
818 _GenerateExternalRules(rules_external, output_dir, spec,
819 sources, options, actions_to_add)
820 _AdjustSourcesForRules(rules, sources, excluded_sources, False)
823 def _AdjustSourcesForRules(rules, sources, excluded_sources, is_msbuild):
824 # Add outputs generated by each rule (if applicable).
826 # Add in the outputs from this rule.
827 trigger_files = _FindRuleTriggerFiles(rule, sources)
828 for trigger_file in trigger_files:
829 # Remove trigger_file from excluded_sources to let the rule be triggered
830 # (e.g. rule trigger ax_enums.idl is added to excluded_sources
831 # because it's also in an action's inputs in the same project)
832 excluded_sources.discard(_FixPath(trigger_file))
833 # Done if not processing outputs as sources.
834 if int(rule.get('process_outputs_as_sources', False)):
835 inputs, outputs = _RuleInputsAndOutputs(rule, trigger_file)
836 inputs = OrderedSet(_FixPaths(inputs))
837 outputs = OrderedSet(_FixPaths(outputs))
838 inputs.remove(_FixPath(trigger_file))
839 sources.update(inputs)
841 excluded_sources.update(inputs)
842 sources.update(outputs)
845 def _FilterActionsFromExcluded(excluded_sources, actions_to_add):
846 """Take inputs with actions attached out of the list of exclusions.
849 excluded_sources: list of source files not to be built.
850 actions_to_add: dict of actions keyed on source file they're attached to.
852 excluded_sources with files that have actions attached removed.
854 must_keep = OrderedSet(_FixPaths(actions_to_add.keys()))
855 return [s for s in excluded_sources if s not in must_keep]
858 def _GetDefaultConfiguration(spec):
859 return spec['configurations'][spec['default_configuration']]
862 def _GetGuidOfProject(proj_path, spec):
863 """Get the guid for the project.
866 proj_path: Path of the vcproj or vcxproj file to generate.
867 spec: The target dictionary containing the properties of the target.
871 ValueError: if the specified GUID is invalid.
873 # Pluck out the default configuration.
874 default_config = _GetDefaultConfiguration(spec)
875 # Decide the guid of the project.
876 guid = default_config.get('msvs_guid')
878 if VALID_MSVS_GUID_CHARS.match(guid) is None:
879 raise ValueError('Invalid MSVS guid: "%s". Must match regex: "%s".' %
880 (guid, VALID_MSVS_GUID_CHARS.pattern))
882 guid = guid or MSVSNew.MakeGuid(proj_path)
886 def _GetMsbuildToolsetOfProject(proj_path, spec, version):
887 """Get the platform toolset for the project.
890 proj_path: Path of the vcproj or vcxproj file to generate.
891 spec: The target dictionary containing the properties of the target.
892 version: The MSVSVersion object.
894 the platform toolset string or None.
896 # Pluck out the default configuration.
897 default_config = _GetDefaultConfiguration(spec)
898 toolset = default_config.get('msbuild_toolset')
899 if not toolset and version.DefaultToolset():
900 toolset = version.DefaultToolset()
904 def _GenerateProject(project, options, version, generator_flags):
905 """Generates a vcproj file.
908 project: the MSVSProject object.
909 options: global generator options.
910 version: the MSVSVersion object.
911 generator_flags: dict of generator-specific flags.
913 A list of source files that cannot be found on disk.
915 default_config = _GetDefaultConfiguration(project.spec)
917 # Skip emitting anything if told to with msvs_existing_vcproj option.
918 if default_config.get('msvs_existing_vcproj'):
921 if version.UsesVcxproj():
922 return _GenerateMSBuildProject(project, options, version, generator_flags)
924 return _GenerateMSVSProject(project, options, version, generator_flags)
927 # TODO: Avoid code duplication with _ValidateSourcesForOSX in make.py.
928 def _ValidateSourcesForMSVSProject(spec, version):
929 """Makes sure if duplicate basenames are not specified in the source list.
932 spec: The target dictionary containing the properties of the target.
933 version: The VisualStudioVersion object.
935 # This validation should not be applied to MSVC2010 and later.
936 assert not version.UsesVcxproj()
938 # TODO: Check if MSVC allows this for loadable_module targets.
939 if spec.get('type', None) not in ('static_library', 'shared_library'):
941 sources = spec.get('sources', [])
943 for source in sources:
944 name, ext = os.path.splitext(source)
945 is_compiled_file = ext in [
946 '.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.s', '.S']
947 if not is_compiled_file:
949 basename = os.path.basename(name) # Don't include extension.
950 basenames.setdefault(basename, []).append(source)
953 for basename, files in basenames.iteritems():
955 error += ' %s: %s\n' % (basename, ' '.join(files))
958 print('static library %s has several files with the same basename:\n' %
959 spec['target_name'] + error + 'MSVC08 cannot handle that.')
960 raise GypError('Duplicate basenames in sources section, see list above')
963 def _GenerateMSVSProject(project, options, version, generator_flags):
964 """Generates a .vcproj file. It may create .rules and .user files too.
967 project: The project object we will generate the file for.
968 options: Global options passed to the generator.
969 version: The VisualStudioVersion object.
970 generator_flags: dict of generator-specific flags.
973 gyp.common.EnsureDirExists(project.path)
975 platforms = _GetUniquePlatforms(spec)
976 p = MSVSProject.Writer(project.path, version, spec['target_name'],
977 project.guid, platforms)
979 # Get directory project file is in.
980 project_dir = os.path.split(project.path)[0]
981 gyp_path = _NormalizedSource(project.build_file)
982 relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
984 config_type = _GetMSVSConfigurationType(spec, project.build_file)
985 for config_name, config in spec['configurations'].iteritems():
986 _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config)
988 # MSVC08 and prior version cannot handle duplicate basenames in the same
990 # TODO: Take excluded sources into consideration if possible.
991 _ValidateSourcesForMSVSProject(spec, version)
993 # Prepare list of sources and excluded sources.
994 gyp_file = os.path.split(project.build_file)[1]
995 sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
1000 _GenerateRulesForMSVS(p, project_dir, options, spec,
1001 sources, excluded_sources,
1003 list_excluded = generator_flags.get('msvs_list_excluded_files', True)
1004 sources, excluded_sources, excluded_idl = (
1005 _AdjustSourcesAndConvertToFilterHierarchy(spec, options, project_dir,
1006 sources, excluded_sources,
1007 list_excluded, version))
1010 missing_sources = _VerifySourcesExist(sources, project_dir)
1013 _AddToolFilesToMSVS(p, spec)
1014 _HandlePreCompiledHeaders(p, sources, spec)
1015 _AddActions(actions_to_add, spec, relative_path_of_gyp_file)
1016 _AddCopies(actions_to_add, spec)
1017 _WriteMSVSUserFile(project.path, version, spec)
1019 # NOTE: this stanza must appear after all actions have been decided.
1020 # Don't excluded sources with actions attached, or they won't run.
1021 excluded_sources = _FilterActionsFromExcluded(
1022 excluded_sources, actions_to_add)
1023 _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
1025 _AddAccumulatedActionsToMSVS(p, spec, actions_to_add)
1030 return missing_sources
1033 def _GetUniquePlatforms(spec):
1034 """Returns the list of unique platforms for this spec, e.g ['win32', ...].
1037 spec: The target dictionary containing the properties of the target.
1039 The MSVSUserFile object created.
1041 # Gather list of unique platforms.
1042 platforms = OrderedSet()
1043 for configuration in spec['configurations']:
1044 platforms.add(_ConfigPlatform(spec['configurations'][configuration]))
1045 platforms = list(platforms)
1049 def _CreateMSVSUserFile(proj_path, version, spec):
1050 """Generates a .user file for the user running this Gyp program.
1053 proj_path: The path of the project file being created. The .user file
1054 shares the same path (with an appropriate suffix).
1055 version: The VisualStudioVersion object.
1056 spec: The target dictionary containing the properties of the target.
1058 The MSVSUserFile object created.
1060 (domain, username) = _GetDomainAndUserName()
1061 vcuser_filename = '.'.join([proj_path, domain, username, 'user'])
1062 user_file = MSVSUserFile.Writer(vcuser_filename, version,
1063 spec['target_name'])
1067 def _GetMSVSConfigurationType(spec, build_file):
1068 """Returns the configuration type for this project.
1070 It's a number defined by Microsoft. May raise an exception.
1073 spec: The target dictionary containing the properties of the target.
1074 build_file: The path of the gyp file.
1076 An integer, the configuration type.
1080 'executable': '1', # .exe
1081 'shared_library': '2', # .dll
1082 'loadable_module': '2', # .dll
1083 'static_library': '4', # .lib
1084 'none': '10', # Utility type
1087 if spec.get('type'):
1088 raise GypError('Target type %s is not a valid target type for '
1089 'target %s in %s.' %
1090 (spec['type'], spec['target_name'], build_file))
1092 raise GypError('Missing type field for target %s in %s.' %
1093 (spec['target_name'], build_file))
1097 def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config):
1098 """Adds a configuration to the MSVS project.
1100 Many settings in a vcproj file are specific to a configuration. This
1101 function the main part of the vcproj file that's configuration specific.
1104 p: The target project being generated.
1105 spec: The target dictionary containing the properties of the target.
1106 config_type: The configuration type, a number as defined by Microsoft.
1107 config_name: The name of the configuration.
1108 config: The dictionary that defines the special processing to be done
1109 for this configuration.
1111 # Get the information for this configuration
1112 include_dirs, midl_include_dirs, resource_include_dirs = \
1113 _GetIncludeDirs(config)
1114 libraries = _GetLibraries(spec)
1115 library_dirs = _GetLibraryDirs(config)
1116 out_file, vc_tool, _ = _GetOutputFilePathAndTool(spec, msbuild=False)
1117 defines = _GetDefines(config)
1118 defines = [_EscapeCppDefineForMSVS(d) for d in defines]
1119 disabled_warnings = _GetDisabledWarnings(config)
1120 prebuild = config.get('msvs_prebuild')
1121 postbuild = config.get('msvs_postbuild')
1122 def_file = _GetModuleDefinition(spec)
1123 precompiled_header = config.get('msvs_precompiled_header')
1125 # Prepare the list of tools as a dictionary.
1127 # Add in user specified msvs_settings.
1128 msvs_settings = config.get('msvs_settings', {})
1129 MSVSSettings.ValidateMSVSSettings(msvs_settings)
1131 # Prevent default library inheritance from the environment.
1132 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', ['$(NOINHERIT)'])
1134 for tool in msvs_settings:
1135 settings = config['msvs_settings'][tool]
1136 for setting in settings:
1137 _ToolAppend(tools, tool, setting, settings[setting])
1138 # Add the information to the appropriate tool
1139 _ToolAppend(tools, 'VCCLCompilerTool',
1140 'AdditionalIncludeDirectories', include_dirs)
1141 _ToolAppend(tools, 'VCMIDLTool',
1142 'AdditionalIncludeDirectories', midl_include_dirs)
1143 _ToolAppend(tools, 'VCResourceCompilerTool',
1144 'AdditionalIncludeDirectories', resource_include_dirs)
1146 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', libraries)
1147 _ToolAppend(tools, 'VCLinkerTool', 'AdditionalLibraryDirectories',
1150 _ToolAppend(tools, vc_tool, 'OutputFile', out_file, only_if_unset=True)
1152 _ToolAppend(tools, 'VCCLCompilerTool', 'PreprocessorDefinitions', defines)
1153 _ToolAppend(tools, 'VCResourceCompilerTool', 'PreprocessorDefinitions',
1155 # Change program database directory to prevent collisions.
1156 _ToolAppend(tools, 'VCCLCompilerTool', 'ProgramDataBaseFileName',
1157 '$(IntDir)$(ProjectName)\\vc80.pdb', only_if_unset=True)
1158 # Add disabled warnings.
1159 _ToolAppend(tools, 'VCCLCompilerTool',
1160 'DisableSpecificWarnings', disabled_warnings)
1162 _ToolAppend(tools, 'VCPreBuildEventTool', 'CommandLine', prebuild)
1164 _ToolAppend(tools, 'VCPostBuildEventTool', 'CommandLine', postbuild)
1165 # Turn on precompiled headers if appropriate.
1166 if precompiled_header:
1167 precompiled_header = os.path.split(precompiled_header)[1]
1168 _ToolAppend(tools, 'VCCLCompilerTool', 'UsePrecompiledHeader', '2')
1169 _ToolAppend(tools, 'VCCLCompilerTool',
1170 'PrecompiledHeaderThrough', precompiled_header)
1171 _ToolAppend(tools, 'VCCLCompilerTool',
1172 'ForcedIncludeFiles', precompiled_header)
1173 # Loadable modules don't generate import libraries;
1174 # tell dependent projects to not expect one.
1175 if spec['type'] == 'loadable_module':
1176 _ToolAppend(tools, 'VCLinkerTool', 'IgnoreImportLibrary', 'true')
1177 # Set the module definition file if any.
1179 _ToolAppend(tools, 'VCLinkerTool', 'ModuleDefinitionFile', def_file)
1181 _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name)
1184 def _GetIncludeDirs(config):
1185 """Returns the list of directories to be used for #include directives.
1188 config: The dictionary that defines the special processing to be done
1189 for this configuration.
1191 The list of directory paths.
1193 # TODO(bradnelson): include_dirs should really be flexible enough not to
1194 # require this sort of thing.
1196 config.get('include_dirs', []) +
1197 config.get('msvs_system_include_dirs', []))
1198 midl_include_dirs = (
1199 config.get('midl_include_dirs', []) +
1200 config.get('msvs_system_include_dirs', []))
1201 resource_include_dirs = config.get('resource_include_dirs', include_dirs)
1202 include_dirs = _FixPaths(include_dirs)
1203 midl_include_dirs = _FixPaths(midl_include_dirs)
1204 resource_include_dirs = _FixPaths(resource_include_dirs)
1205 return include_dirs, midl_include_dirs, resource_include_dirs
1208 def _GetLibraryDirs(config):
1209 """Returns the list of directories to be used for library search paths.
1212 config: The dictionary that defines the special processing to be done
1213 for this configuration.
1215 The list of directory paths.
1218 library_dirs = config.get('library_dirs', [])
1219 library_dirs = _FixPaths(library_dirs)
1223 def _GetLibraries(spec):
1224 """Returns the list of libraries for this configuration.
1227 spec: The target dictionary containing the properties of the target.
1229 The list of directory paths.
1231 libraries = spec.get('libraries', [])
1232 # Strip out -l, as it is not used on windows (but is needed so we can pass
1233 # in libraries that are assumed to be in the default library path).
1234 # Also remove duplicate entries, leaving only the last duplicate, while
1236 found = OrderedSet()
1237 unique_libraries_list = []
1238 for entry in reversed(libraries):
1239 library = re.sub('^\-l', '', entry)
1240 if not os.path.splitext(library)[1]:
1242 if library not in found:
1244 unique_libraries_list.append(library)
1245 unique_libraries_list.reverse()
1246 return unique_libraries_list
1249 def _GetOutputFilePathAndTool(spec, msbuild):
1250 """Returns the path and tool to use for this target.
1252 Figures out the path of the file this spec will create and the name of
1253 the VC tool that will create it.
1256 spec: The target dictionary containing the properties of the target.
1258 A triple of (file path, name of the vc tool, name of the msbuild tool)
1260 # Select a name for the output file.
1265 'executable': ('VCLinkerTool', 'Link', '$(OutDir)', '.exe'),
1266 'shared_library': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
1267 'loadable_module': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
1268 'static_library': ('VCLibrarianTool', 'Lib', '$(OutDir)lib\\', '.lib'),
1270 output_file_props = output_file_map.get(spec['type'])
1271 if output_file_props and int(spec.get('msvs_auto_output_file', 1)):
1272 vc_tool, msbuild_tool, out_dir, suffix = output_file_props
1273 if spec.get('standalone_static_library', 0):
1274 out_dir = '$(OutDir)'
1275 out_dir = spec.get('product_dir', out_dir)
1276 product_extension = spec.get('product_extension')
1277 if product_extension:
1278 suffix = '.' + product_extension
1280 suffix = '$(TargetExt)'
1281 prefix = spec.get('product_prefix', '')
1282 product_name = spec.get('product_name', '$(ProjectName)')
1283 out_file = ntpath.join(out_dir, prefix + product_name + suffix)
1284 return out_file, vc_tool, msbuild_tool
1287 def _GetOutputTargetExt(spec):
1288 """Returns the extension for this target, including the dot
1290 If product_extension is specified, set target_extension to this to avoid
1291 MSB8012, returns None otherwise. Ignores any target_extension settings in
1295 spec: The target dictionary containing the properties of the target.
1297 A string with the extension, or None
1299 target_extension = spec.get('product_extension')
1300 if target_extension:
1301 return '.' + target_extension
1305 def _GetDefines(config):
1306 """Returns the list of preprocessor definitions for this configuation.
1309 config: The dictionary that defines the special processing to be done
1310 for this configuration.
1312 The list of preprocessor definitions.
1315 for d in config.get('defines', []):
1317 fd = '='.join([str(dpart) for dpart in d])
1324 def _GetDisabledWarnings(config):
1325 return [str(i) for i in config.get('msvs_disabled_warnings', [])]
1328 def _GetModuleDefinition(spec):
1330 if spec['type'] in ['shared_library', 'loadable_module', 'executable']:
1331 def_files = [s for s in spec.get('sources', []) if s.endswith('.def')]
1332 if len(def_files) == 1:
1333 def_file = _FixPath(def_files[0])
1336 'Multiple module definition files in one target, target %s lists '
1337 'multiple .def files: %s' % (
1338 spec['target_name'], ' '.join(def_files)))
1342 def _ConvertToolsToExpectedForm(tools):
1343 """Convert tools to a form expected by Visual Studio.
1346 tools: A dictionary of settings; the tool name is the key.
1348 A list of Tool objects.
1351 for tool, settings in tools.iteritems():
1352 # Collapse settings with lists.
1354 for setting, value in settings.iteritems():
1355 if type(value) == list:
1356 if ((tool == 'VCLinkerTool' and
1357 setting == 'AdditionalDependencies') or
1358 setting == 'AdditionalOptions'):
1359 settings_fixed[setting] = ' '.join(value)
1361 settings_fixed[setting] = ';'.join(value)
1363 settings_fixed[setting] = value
1365 tool_list.append(MSVSProject.Tool(tool, settings_fixed))
1369 def _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name):
1370 """Add to the project file the configuration specified by config.
1373 p: The target project being generated.
1374 spec: the target project dict.
1375 tools: A dictionary of settings; the tool name is the key.
1376 config: The dictionary that defines the special processing to be done
1377 for this configuration.
1378 config_type: The configuration type, a number as defined by Microsoft.
1379 config_name: The name of the configuration.
1381 attributes = _GetMSVSAttributes(spec, config, config_type)
1382 # Add in this configuration.
1383 tool_list = _ConvertToolsToExpectedForm(tools)
1384 p.AddConfig(_ConfigFullName(config_name, config),
1385 attrs=attributes, tools=tool_list)
1388 def _GetMSVSAttributes(spec, config, config_type):
1389 # Prepare configuration attributes.
1391 source_attrs = config.get('msvs_configuration_attributes', {})
1392 for a in source_attrs:
1393 prepared_attrs[a] = source_attrs[a]
1395 vsprops_dirs = config.get('msvs_props', [])
1396 vsprops_dirs = _FixPaths(vsprops_dirs)
1398 prepared_attrs['InheritedPropertySheets'] = ';'.join(vsprops_dirs)
1399 # Set configuration type.
1400 prepared_attrs['ConfigurationType'] = config_type
1401 output_dir = prepared_attrs.get('OutputDirectory',
1402 '$(SolutionDir)$(ConfigurationName)')
1403 prepared_attrs['OutputDirectory'] = _FixPath(output_dir) + '\\'
1404 if 'IntermediateDirectory' not in prepared_attrs:
1405 intermediate = '$(ConfigurationName)\\obj\\$(ProjectName)'
1406 prepared_attrs['IntermediateDirectory'] = _FixPath(intermediate) + '\\'
1408 intermediate = _FixPath(prepared_attrs['IntermediateDirectory']) + '\\'
1409 intermediate = MSVSSettings.FixVCMacroSlashes(intermediate)
1410 prepared_attrs['IntermediateDirectory'] = intermediate
1411 return prepared_attrs
1414 def _AddNormalizedSources(sources_set, sources_array):
1415 sources_set.update(_NormalizedSource(s) for s in sources_array)
1418 def _PrepareListOfSources(spec, generator_flags, gyp_file):
1419 """Prepare list of sources and excluded sources.
1421 Besides the sources specified directly in the spec, adds the gyp file so
1422 that a change to it will cause a re-compile. Also adds appropriate sources
1423 for actions and copies. Assumes later stage will un-exclude files which
1424 have custom build steps attached.
1427 spec: The target dictionary containing the properties of the target.
1428 gyp_file: The name of the gyp file.
1430 A pair of (list of sources, list of excluded sources).
1431 The sources will be relative to the gyp file.
1433 sources = OrderedSet()
1434 _AddNormalizedSources(sources, spec.get('sources', []))
1435 excluded_sources = OrderedSet()
1436 # Add in the gyp file.
1437 if not generator_flags.get('standalone'):
1438 sources.add(gyp_file)
1440 # Add in 'action' inputs and outputs.
1441 for a in spec.get('actions', []):
1442 inputs = a['inputs']
1443 inputs = [_NormalizedSource(i) for i in inputs]
1444 # Add all inputs to sources and excluded sources.
1445 inputs = OrderedSet(inputs)
1446 sources.update(inputs)
1447 if not spec.get('msvs_external_builder'):
1448 excluded_sources.update(inputs)
1449 if int(a.get('process_outputs_as_sources', False)):
1450 _AddNormalizedSources(sources, a.get('outputs', []))
1451 # Add in 'copies' inputs and outputs.
1452 for cpy in spec.get('copies', []):
1453 _AddNormalizedSources(sources, cpy.get('files', []))
1454 return (sources, excluded_sources)
1457 def _AdjustSourcesAndConvertToFilterHierarchy(
1458 spec, options, gyp_dir, sources, excluded_sources, list_excluded, version):
1459 """Adjusts the list of sources and excluded sources.
1461 Also converts the sets to lists.
1464 spec: The target dictionary containing the properties of the target.
1465 options: Global generator options.
1466 gyp_dir: The path to the gyp file being processed.
1467 sources: A set of sources to be included for this project.
1468 excluded_sources: A set of sources to be excluded for this project.
1469 version: A MSVSVersion object.
1471 A trio of (list of sources, list of excluded sources,
1472 path of excluded IDL file)
1474 # Exclude excluded sources coming into the generator.
1475 excluded_sources.update(OrderedSet(spec.get('sources_excluded', [])))
1476 # Add excluded sources into sources for good measure.
1477 sources.update(excluded_sources)
1478 # Convert to proper windows form.
1479 # NOTE: sources goes from being a set to a list here.
1480 # NOTE: excluded_sources goes from being a set to a list here.
1481 sources = _FixPaths(sources)
1482 # Convert to proper windows form.
1483 excluded_sources = _FixPaths(excluded_sources)
1485 excluded_idl = _IdlFilesHandledNonNatively(spec, sources)
1487 precompiled_related = _GetPrecompileRelatedFiles(spec)
1488 # Find the excluded ones, minus the precompiled header related ones.
1489 fully_excluded = [i for i in excluded_sources if i not in precompiled_related]
1491 # Convert to folders and the right slashes.
1492 sources = [i.split('\\') for i in sources]
1493 sources = _ConvertSourcesToFilterHierarchy(sources, excluded=fully_excluded,
1494 list_excluded=list_excluded,
1495 msvs_version=version)
1497 # Prune filters with a single child to flatten ugly directory structures
1498 # such as ../../src/modules/module1 etc.
1499 if version.UsesVcxproj():
1500 while all([isinstance(s, MSVSProject.Filter) for s in sources]) \
1501 and len(set([s.name for s in sources])) == 1:
1502 assert all([len(s.contents) == 1 for s in sources])
1503 sources = [s.contents[0] for s in sources]
1505 while len(sources) == 1 and isinstance(sources[0], MSVSProject.Filter):
1506 sources = sources[0].contents
1508 return sources, excluded_sources, excluded_idl
1511 def _IdlFilesHandledNonNatively(spec, sources):
1512 # If any non-native rules use 'idl' as an extension exclude idl files.
1513 # Gather a list here to use later.
1515 for rule in spec.get('rules', []):
1516 if rule['extension'] == 'idl' and int(rule.get('msvs_external_rule', 0)):
1520 excluded_idl = [i for i in sources if i.endswith('.idl')]
1526 def _GetPrecompileRelatedFiles(spec):
1527 # Gather a list of precompiled header related sources.
1528 precompiled_related = []
1529 for _, config in spec['configurations'].iteritems():
1530 for k in precomp_keys:
1533 precompiled_related.append(_FixPath(f))
1534 return precompiled_related
1537 def _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
1539 exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
1540 for file_name, excluded_configs in exclusions.iteritems():
1541 if (not list_excluded and
1542 len(excluded_configs) == len(spec['configurations'])):
1543 # If we're not listing excluded files, then they won't appear in the
1544 # project, so don't try to configure them to be excluded.
1547 for config_name, config in excluded_configs:
1548 p.AddFileConfig(file_name, _ConfigFullName(config_name, config),
1549 {'ExcludedFromBuild': 'true'})
1552 def _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl):
1554 # Exclude excluded sources from being built.
1555 for f in excluded_sources:
1556 excluded_configs = []
1557 for config_name, config in spec['configurations'].iteritems():
1558 precomped = [_FixPath(config.get(i, '')) for i in precomp_keys]
1559 # Don't do this for ones that are precompiled header related.
1560 if f not in precomped:
1561 excluded_configs.append((config_name, config))
1562 exclusions[f] = excluded_configs
1563 # If any non-native rules use 'idl' as an extension exclude idl files.
1565 for f in excluded_idl:
1566 excluded_configs = []
1567 for config_name, config in spec['configurations'].iteritems():
1568 excluded_configs.append((config_name, config))
1569 exclusions[f] = excluded_configs
1573 def _AddToolFilesToMSVS(p, spec):
1574 # Add in tool files (rules).
1575 tool_files = OrderedSet()
1576 for _, config in spec['configurations'].iteritems():
1577 for f in config.get('msvs_tool_files', []):
1579 for f in tool_files:
1583 def _HandlePreCompiledHeaders(p, sources, spec):
1584 # Pre-compiled header source stubs need a different compiler flag
1585 # (generate precompiled header) and any source file not of the same
1586 # kind (i.e. C vs. C++) as the precompiled header source stub needs
1587 # to have use of precompiled headers disabled.
1588 extensions_excluded_from_precompile = []
1589 for config_name, config in spec['configurations'].iteritems():
1590 source = config.get('msvs_precompiled_source')
1592 source = _FixPath(source)
1593 # UsePrecompiledHeader=1 for if using precompiled headers.
1594 tool = MSVSProject.Tool('VCCLCompilerTool',
1595 {'UsePrecompiledHeader': '1'})
1596 p.AddFileConfig(source, _ConfigFullName(config_name, config),
1598 basename, extension = os.path.splitext(source)
1599 if extension == '.c':
1600 extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
1602 extensions_excluded_from_precompile = ['.c']
1603 def DisableForSourceTree(source_tree):
1604 for source in source_tree:
1605 if isinstance(source, MSVSProject.Filter):
1606 DisableForSourceTree(source.contents)
1608 basename, extension = os.path.splitext(source)
1609 if extension in extensions_excluded_from_precompile:
1610 for config_name, config in spec['configurations'].iteritems():
1611 tool = MSVSProject.Tool('VCCLCompilerTool',
1612 {'UsePrecompiledHeader': '0',
1613 'ForcedIncludeFiles': '$(NOINHERIT)'})
1614 p.AddFileConfig(_FixPath(source),
1615 _ConfigFullName(config_name, config),
1617 # Do nothing if there was no precompiled source.
1618 if extensions_excluded_from_precompile:
1619 DisableForSourceTree(sources)
1622 def _AddActions(actions_to_add, spec, relative_path_of_gyp_file):
1624 actions = spec.get('actions', [])
1625 # Don't setup_env every time. When all the actions are run together in one
1626 # batch file in VS, the PATH will grow too long.
1627 # Membership in this set means that the cygwin environment has been set up,
1628 # and does not need to be set up again.
1629 have_setup_env = set()
1631 # Attach actions to the gyp file if nothing else is there.
1632 inputs = a.get('inputs') or [relative_path_of_gyp_file]
1633 attached_to = inputs[0]
1634 need_setup_env = attached_to not in have_setup_env
1635 cmd = _BuildCommandLineForRule(spec, a, has_input_path=False,
1636 do_setup_env=need_setup_env)
1637 have_setup_env.add(attached_to)
1639 _AddActionStep(actions_to_add,
1641 outputs=a.get('outputs', []),
1642 description=a.get('message', a['action_name']),
1646 def _WriteMSVSUserFile(project_path, version, spec):
1647 # Add run_as and test targets.
1648 if 'run_as' in spec:
1649 run_as = spec['run_as']
1650 action = run_as.get('action', [])
1651 environment = run_as.get('environment', [])
1652 working_directory = run_as.get('working_directory', '.')
1653 elif int(spec.get('test', 0)):
1654 action = ['$(TargetPath)', '--gtest_print_time']
1656 working_directory = '.'
1658 return # Nothing to add
1659 # Write out the user file.
1660 user_file = _CreateMSVSUserFile(project_path, version, spec)
1661 for config_name, c_data in spec['configurations'].iteritems():
1662 user_file.AddDebugSettings(_ConfigFullName(config_name, c_data),
1663 action, environment, working_directory)
1664 user_file.WriteIfChanged()
1667 def _AddCopies(actions_to_add, spec):
1668 copies = _GetCopies(spec)
1669 for inputs, outputs, cmd, description in copies:
1670 _AddActionStep(actions_to_add, inputs=inputs, outputs=outputs,
1671 description=description, command=cmd)
1674 def _GetCopies(spec):
1677 for cpy in spec.get('copies', []):
1678 for src in cpy.get('files', []):
1679 dst = os.path.join(cpy['destination'], os.path.basename(src))
1680 # _AddCustomBuildToolForMSVS() will call _FixPath() on the inputs and
1681 # outputs, so do the same for our generated command line.
1682 if src.endswith('/'):
1684 base_dir = posixpath.split(src_bare)[0]
1685 outer_dir = posixpath.split(src_bare)[1]
1686 cmd = 'cd "%s" && xcopy /e /f /y "%s" "%s\\%s\\"' % (
1687 _FixPath(base_dir), outer_dir, _FixPath(dst), outer_dir)
1688 copies.append(([src], ['dummy_copies', dst], cmd,
1689 'Copying %s to %s' % (src, dst)))
1691 cmd = 'mkdir "%s" 2>nul & set ERRORLEVEL=0 & copy /Y "%s" "%s"' % (
1692 _FixPath(cpy['destination']), _FixPath(src), _FixPath(dst))
1693 copies.append(([src], [dst], cmd, 'Copying %s to %s' % (src, dst)))
1697 def _GetPathDict(root, path):
1698 # |path| will eventually be empty (in the recursive calls) if it was initially
1699 # relative; otherwise it will eventually end up as '\', 'D:\', etc.
1700 if not path or path.endswith(os.sep):
1702 parent, folder = os.path.split(path)
1703 parent_dict = _GetPathDict(root, parent)
1704 if folder not in parent_dict:
1705 parent_dict[folder] = dict()
1706 return parent_dict[folder]
1709 def _DictsToFolders(base_path, bucket, flat):
1710 # Convert to folders recursively.
1712 for folder, contents in bucket.iteritems():
1713 if type(contents) == dict:
1714 folder_children = _DictsToFolders(os.path.join(base_path, folder),
1717 children += folder_children
1719 folder_children = MSVSNew.MSVSFolder(os.path.join(base_path, folder),
1720 name='(' + folder + ')',
1721 entries=folder_children)
1722 children.append(folder_children)
1724 children.append(contents)
1728 def _CollapseSingles(parent, node):
1729 # Recursively explorer the tree of dicts looking for projects which are
1730 # the sole item in a folder which has the same name as the project. Bring
1731 # such projects up one level.
1732 if (type(node) == dict and
1734 node.keys()[0] == parent + '.vcproj'):
1735 return node[node.keys()[0]]
1736 if type(node) != dict:
1739 node[child] = _CollapseSingles(child, node[child])
1743 def _GatherSolutionFolders(sln_projects, project_objects, flat):
1745 # Convert into a tree of dicts on path.
1746 for p in sln_projects:
1747 gyp_file, target = gyp.common.ParseQualifiedTarget(p)[0:2]
1748 gyp_dir = os.path.dirname(gyp_file)
1749 path_dict = _GetPathDict(root, gyp_dir)
1750 path_dict[target + '.vcproj'] = project_objects[p]
1751 # Walk down from the top until we hit a folder that has more than one entry.
1752 # In practice, this strips the top-level "src/" dir from the hierarchy in
1754 while len(root) == 1 and type(root[root.keys()[0]]) == dict:
1755 root = root[root.keys()[0]]
1757 root = _CollapseSingles('', root)
1758 # Merge buckets until everything is a root entry.
1759 return _DictsToFolders('', root, flat)
1762 def _GetPathOfProject(qualified_target, spec, options, msvs_version):
1763 default_config = _GetDefaultConfiguration(spec)
1764 proj_filename = default_config.get('msvs_existing_vcproj')
1765 if not proj_filename:
1766 proj_filename = (spec['target_name'] + options.suffix +
1767 msvs_version.ProjectExtension())
1769 build_file = gyp.common.BuildFile(qualified_target)
1770 proj_path = os.path.join(os.path.dirname(build_file), proj_filename)
1772 if options.generator_output:
1773 project_dir_path = os.path.dirname(os.path.abspath(proj_path))
1774 proj_path = os.path.join(options.generator_output, proj_path)
1775 fix_prefix = gyp.common.RelativePath(project_dir_path,
1776 os.path.dirname(proj_path))
1777 return proj_path, fix_prefix
1780 def _GetPlatformOverridesOfProject(spec):
1781 # Prepare a dict indicating which project configurations are used for which
1782 # solution configurations for this target.
1783 config_platform_overrides = {}
1784 for config_name, c in spec['configurations'].iteritems():
1785 config_fullname = _ConfigFullName(config_name, c)
1786 platform = c.get('msvs_target_platform', _ConfigPlatform(c))
1787 fixed_config_fullname = '%s|%s' % (
1788 _ConfigBaseName(config_name, _ConfigPlatform(c)), platform)
1789 config_platform_overrides[config_fullname] = fixed_config_fullname
1790 return config_platform_overrides
1793 def _CreateProjectObjects(target_list, target_dicts, options, msvs_version):
1794 """Create a MSVSProject object for the targets found in target list.
1797 target_list: the list of targets to generate project objects for.
1798 target_dicts: the dictionary of specifications.
1799 options: global generator options.
1800 msvs_version: the MSVSVersion object.
1802 A set of created projects, keyed by target.
1804 global fixpath_prefix
1805 # Generate each project.
1807 for qualified_target in target_list:
1808 spec = target_dicts[qualified_target]
1809 if spec['toolset'] != 'target':
1811 'Multiple toolsets not supported in msvs build (target %s)' %
1813 proj_path, fixpath_prefix = _GetPathOfProject(qualified_target, spec,
1814 options, msvs_version)
1815 guid = _GetGuidOfProject(proj_path, spec)
1816 overrides = _GetPlatformOverridesOfProject(spec)
1817 build_file = gyp.common.BuildFile(qualified_target)
1818 # Create object for this project.
1819 obj = MSVSNew.MSVSProject(
1821 name=spec['target_name'],
1824 build_file=build_file,
1825 config_platform_overrides=overrides,
1826 fixpath_prefix=fixpath_prefix)
1827 # Set project toolset if any (MS build only)
1828 if msvs_version.UsesVcxproj():
1829 obj.set_msbuild_toolset(
1830 _GetMsbuildToolsetOfProject(proj_path, spec, msvs_version))
1831 projects[qualified_target] = obj
1832 # Set all the dependencies, but not if we are using an external builder like
1834 for project in projects.values():
1835 if not project.spec.get('msvs_external_builder'):
1836 deps = project.spec.get('dependencies', [])
1837 deps = [projects[d] for d in deps]
1838 project.set_dependencies(deps)
1842 def _InitNinjaFlavor(params, target_list, target_dicts):
1843 """Initialize targets for the ninja flavor.
1845 This sets up the necessary variables in the targets to generate msvs projects
1846 that use ninja as an external builder. The variables in the spec are only set
1847 if they have not been set. This allows individual specs to override the
1848 default values initialized here.
1850 params: Params provided to the generator.
1851 target_list: List of target pairs: 'base/base.gyp:base'.
1852 target_dicts: Dict of target properties keyed on target pair.
1854 for qualified_target in target_list:
1855 spec = target_dicts[qualified_target]
1856 if spec.get('msvs_external_builder'):
1857 # The spec explicitly defined an external builder, so don't change it.
1860 path_to_ninja = spec.get('msvs_path_to_ninja', 'ninja.exe')
1862 spec['msvs_external_builder'] = 'ninja'
1863 if not spec.get('msvs_external_builder_out_dir'):
1864 gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target)
1865 gyp_dir = os.path.dirname(gyp_file)
1866 spec['msvs_external_builder_out_dir'] = os.path.join(
1867 gyp.common.RelativePath(params['options'].toplevel_dir, gyp_dir),
1868 ninja_generator.ComputeOutputDir(params),
1870 if not spec.get('msvs_external_builder_build_cmd'):
1871 spec['msvs_external_builder_build_cmd'] = [
1877 if not spec.get('msvs_external_builder_clean_cmd'):
1878 spec['msvs_external_builder_clean_cmd'] = [
1887 def CalculateVariables(default_variables, params):
1888 """Generated variables that require params to be known."""
1890 generator_flags = params.get('generator_flags', {})
1892 # Select project file format version (if unset, default to auto detecting).
1893 msvs_version = MSVSVersion.SelectVisualStudioVersion(
1894 generator_flags.get('msvs_version', 'auto'))
1895 # Stash msvs_version for later (so we don't have to probe the system twice).
1896 params['msvs_version'] = msvs_version
1898 # Set a variable so conditions can be based on msvs_version.
1899 default_variables['MSVS_VERSION'] = msvs_version.ShortName()
1901 # To determine processor word size on Windows, in addition to checking
1902 # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
1903 # process), it is also necessary to check PROCESSOR_ARCITEW6432 (which
1904 # contains the actual word size of the system when running thru WOW64).
1905 if (os.environ.get('PROCESSOR_ARCHITECTURE', '').find('64') >= 0 or
1906 os.environ.get('PROCESSOR_ARCHITEW6432', '').find('64') >= 0):
1907 default_variables['MSVS_OS_BITS'] = 64
1909 default_variables['MSVS_OS_BITS'] = 32
1911 if gyp.common.GetFlavor(params) == 'ninja':
1912 default_variables['SHARED_INTERMEDIATE_DIR'] = '$(OutDir)gen'
1915 def PerformBuild(data, configurations, params):
1916 options = params['options']
1917 msvs_version = params['msvs_version']
1918 devenv = os.path.join(msvs_version.path, 'Common7', 'IDE', 'devenv.com')
1920 for build_file, build_file_dict in data.iteritems():
1921 (build_file_root, build_file_ext) = os.path.splitext(build_file)
1922 if build_file_ext != '.gyp':
1924 sln_path = build_file_root + options.suffix + '.sln'
1925 if options.generator_output:
1926 sln_path = os.path.join(options.generator_output, sln_path)
1928 for config in configurations:
1929 arguments = [devenv, sln_path, '/Build', config]
1930 print 'Building [%s]: %s' % (config, arguments)
1931 rtn = subprocess.check_call(arguments)
1934 def GenerateOutput(target_list, target_dicts, data, params):
1935 """Generate .sln and .vcproj files.
1937 This is the entry point for this generator.
1939 target_list: List of target pairs: 'base/base.gyp:base'.
1940 target_dicts: Dict of target properties keyed on target pair.
1941 data: Dictionary containing per .gyp data.
1943 global fixpath_prefix
1945 options = params['options']
1947 # Get the project file format version back out of where we stashed it in
1948 # GeneratorCalculatedVariables.
1949 msvs_version = params['msvs_version']
1951 generator_flags = params.get('generator_flags', {})
1953 # Optionally shard targets marked with 'msvs_shard': SHARD_COUNT.
1954 (target_list, target_dicts) = MSVSUtil.ShardTargets(target_list, target_dicts)
1956 # Optionally use the large PDB workaround for targets marked with
1957 # 'msvs_large_pdb': 1.
1958 (target_list, target_dicts) = MSVSUtil.InsertLargePdbShims(
1959 target_list, target_dicts, generator_default_variables)
1961 # Optionally configure each spec to use ninja as the external builder.
1962 if params.get('flavor') == 'ninja':
1963 _InitNinjaFlavor(params, target_list, target_dicts)
1965 # Prepare the set of configurations.
1967 for qualified_target in target_list:
1968 spec = target_dicts[qualified_target]
1969 for config_name, config in spec['configurations'].iteritems():
1970 configs.add(_ConfigFullName(config_name, config))
1971 configs = list(configs)
1973 # Figure out all the projects that will be generated and their guids
1974 project_objects = _CreateProjectObjects(target_list, target_dicts, options,
1977 # Generate each project.
1978 missing_sources = []
1979 for project in project_objects.values():
1980 fixpath_prefix = project.fixpath_prefix
1981 missing_sources.extend(_GenerateProject(project, options, msvs_version,
1983 fixpath_prefix = None
1985 for build_file in data:
1986 # Validate build_file extension
1987 if not build_file.endswith('.gyp'):
1989 sln_path = os.path.splitext(build_file)[0] + options.suffix + '.sln'
1990 if options.generator_output:
1991 sln_path = os.path.join(options.generator_output, sln_path)
1992 # Get projects in the solution, and their dependents.
1993 sln_projects = gyp.common.BuildFileTargets(target_list, build_file)
1994 sln_projects += gyp.common.DeepDependencyTargets(target_dicts, sln_projects)
1995 # Create folder hierarchy.
1996 root_entries = _GatherSolutionFolders(
1997 sln_projects, project_objects, flat=msvs_version.FlatSolution())
1999 sln = MSVSNew.MSVSSolution(sln_path,
2000 entries=root_entries,
2002 websiteProperties=False,
2003 version=msvs_version)
2007 error_message = "Missing input files:\n" + \
2008 '\n'.join(set(missing_sources))
2009 if generator_flags.get('msvs_error_on_missing_sources', False):
2010 raise GypError(error_message)
2012 print >> sys.stdout, "Warning: " + error_message
2015 def _GenerateMSBuildFiltersFile(filters_path, source_files,
2016 rule_dependencies, extension_to_rule_name):
2017 """Generate the filters file.
2019 This file is used by Visual Studio to organize the presentation of source
2023 filters_path: The path of the file to be created.
2024 source_files: The hierarchical structure of all the sources.
2025 extension_to_rule_name: A dictionary mapping file extensions to rules.
2029 _AppendFiltersForMSBuild('', source_files, rule_dependencies,
2030 extension_to_rule_name, filter_group, source_group)
2032 content = ['Project',
2033 {'ToolsVersion': '4.0',
2034 'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
2036 ['ItemGroup'] + filter_group,
2037 ['ItemGroup'] + source_group
2039 easy_xml.WriteXmlIfChanged(content, filters_path, pretty=True, win32=True)
2040 elif os.path.exists(filters_path):
2041 # We don't need this filter anymore. Delete the old filter file.
2042 os.unlink(filters_path)
2045 def _AppendFiltersForMSBuild(parent_filter_name, sources, rule_dependencies,
2046 extension_to_rule_name,
2047 filter_group, source_group):
2048 """Creates the list of filters and sources to be added in the filter file.
2051 parent_filter_name: The name of the filter under which the sources are
2053 sources: The hierarchy of filters and sources to process.
2054 extension_to_rule_name: A dictionary mapping file extensions to rules.
2055 filter_group: The list to which filter entries will be appended.
2056 source_group: The list to which source entries will be appeneded.
2058 for source in sources:
2059 if isinstance(source, MSVSProject.Filter):
2060 # We have a sub-filter. Create the name of that sub-filter.
2061 if not parent_filter_name:
2062 filter_name = source.name
2064 filter_name = '%s\\%s' % (parent_filter_name, source.name)
2065 # Add the filter to the group.
2066 filter_group.append(
2067 ['Filter', {'Include': filter_name},
2068 ['UniqueIdentifier', MSVSNew.MakeGuid(source.name)]])
2069 # Recurse and add its dependents.
2070 _AppendFiltersForMSBuild(filter_name, source.contents,
2071 rule_dependencies, extension_to_rule_name,
2072 filter_group, source_group)
2074 # It's a source. Create a source entry.
2075 _, element = _MapFileToMsBuildSourceType(source, rule_dependencies,
2076 extension_to_rule_name)
2077 source_entry = [element, {'Include': source}]
2078 # Specify the filter it is part of, if any.
2079 if parent_filter_name:
2080 source_entry.append(['Filter', parent_filter_name])
2081 source_group.append(source_entry)
2084 def _MapFileToMsBuildSourceType(source, rule_dependencies,
2085 extension_to_rule_name):
2086 """Returns the group and element type of the source file.
2089 source: The source file name.
2090 extension_to_rule_name: A dictionary mapping file extensions to rules.
2093 A pair of (group this file should be part of, the label of element)
2095 _, ext = os.path.splitext(source)
2096 if ext in extension_to_rule_name:
2098 element = extension_to_rule_name[ext]
2099 elif ext in ['.cc', '.cpp', '.c', '.cxx']:
2101 element = 'ClCompile'
2102 elif ext in ['.h', '.hxx']:
2104 element = 'ClInclude'
2107 element = 'ResourceCompile'
2111 elif source in rule_dependencies:
2112 group = 'rule_dependency'
2113 element = 'CustomBuild'
2117 return (group, element)
2120 def _GenerateRulesForMSBuild(output_dir, options, spec,
2121 sources, excluded_sources,
2122 props_files_of_rules, targets_files_of_rules,
2123 actions_to_add, rule_dependencies,
2124 extension_to_rule_name):
2125 # MSBuild rules are implemented using three files: an XML file, a .targets
2126 # file and a .props file.
2127 # See http://blogs.msdn.com/b/vcblog/archive/2010/04/21/quick-help-on-vs2010-custom-build-rule.aspx
2129 rules = spec.get('rules', [])
2130 rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
2131 rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
2134 for rule in rules_native:
2135 # Skip a rule with no action and no inputs.
2136 if 'action' not in rule and not rule.get('rule_sources', []):
2138 msbuild_rule = MSBuildRule(rule, spec)
2139 msbuild_rules.append(msbuild_rule)
2140 rule_dependencies.update(msbuild_rule.additional_dependencies.split(';'))
2141 extension_to_rule_name[msbuild_rule.extension] = msbuild_rule.rule_name
2143 base = spec['target_name'] + options.suffix
2144 props_name = base + '.props'
2145 targets_name = base + '.targets'
2146 xml_name = base + '.xml'
2148 props_files_of_rules.add(props_name)
2149 targets_files_of_rules.add(targets_name)
2151 props_path = os.path.join(output_dir, props_name)
2152 targets_path = os.path.join(output_dir, targets_name)
2153 xml_path = os.path.join(output_dir, xml_name)
2155 _GenerateMSBuildRulePropsFile(props_path, msbuild_rules)
2156 _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules)
2157 _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules)
2160 _GenerateExternalRules(rules_external, output_dir, spec,
2161 sources, options, actions_to_add)
2162 _AdjustSourcesForRules(rules, sources, excluded_sources, True)
2165 class MSBuildRule(object):
2166 """Used to store information used to generate an MSBuild rule.
2169 rule_name: The rule name, sanitized to use in XML.
2170 target_name: The name of the target.
2171 after_targets: The name of the AfterTargets element.
2172 before_targets: The name of the BeforeTargets element.
2173 depends_on: The name of the DependsOn element.
2174 compute_output: The name of the ComputeOutput element.
2175 dirs_to_make: The name of the DirsToMake element.
2176 inputs: The name of the _inputs element.
2177 tlog: The name of the _tlog element.
2178 extension: The extension this rule applies to.
2179 description: The message displayed when this rule is invoked.
2180 additional_dependencies: A string listing additional dependencies.
2181 outputs: The outputs of this rule.
2182 command: The command used to run the rule.
2185 def __init__(self, rule, spec):
2186 self.display_name = rule['rule_name']
2187 # Assure that the rule name is only characters and numbers
2188 self.rule_name = re.sub(r'\W', '_', self.display_name)
2189 # Create the various element names, following the example set by the
2190 # Visual Studio 2008 to 2010 conversion. I don't know if VS2010
2191 # is sensitive to the exact names.
2192 self.target_name = '_' + self.rule_name
2193 self.after_targets = self.rule_name + 'AfterTargets'
2194 self.before_targets = self.rule_name + 'BeforeTargets'
2195 self.depends_on = self.rule_name + 'DependsOn'
2196 self.compute_output = 'Compute%sOutput' % self.rule_name
2197 self.dirs_to_make = self.rule_name + 'DirsToMake'
2198 self.inputs = self.rule_name + '_inputs'
2199 self.tlog = self.rule_name + '_tlog'
2200 self.extension = rule['extension']
2201 if not self.extension.startswith('.'):
2202 self.extension = '.' + self.extension
2204 self.description = MSVSSettings.ConvertVCMacrosToMSBuild(
2205 rule.get('message', self.rule_name))
2206 old_additional_dependencies = _FixPaths(rule.get('inputs', []))
2207 self.additional_dependencies = (
2208 ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
2209 for i in old_additional_dependencies]))
2210 old_outputs = _FixPaths(rule.get('outputs', []))
2211 self.outputs = ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
2212 for i in old_outputs])
2213 old_command = _BuildCommandLineForRule(spec, rule, has_input_path=True,
2215 self.command = MSVSSettings.ConvertVCMacrosToMSBuild(old_command)
2218 def _GenerateMSBuildRulePropsFile(props_path, msbuild_rules):
2219 """Generate the .props file."""
2220 content = ['Project',
2221 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'}]
2222 for rule in msbuild_rules:
2225 {'Condition': "'$(%s)' == '' and '$(%s)' == '' and "
2226 "'$(ConfigurationType)' != 'Makefile'" % (rule.before_targets,
2229 [rule.before_targets, 'Midl'],
2230 [rule.after_targets, 'CustomBuild'],
2234 {'Condition': "'$(ConfigurationType)' != 'Makefile'"},
2235 '_SelectedFiles;$(%s)' % rule.depends_on
2238 ['ItemDefinitionGroup',
2240 ['CommandLineTemplate', rule.command],
2241 ['Outputs', rule.outputs],
2242 ['ExecutionDescription', rule.description],
2243 ['AdditionalDependencies', rule.additional_dependencies],
2247 easy_xml.WriteXmlIfChanged(content, props_path, pretty=True, win32=True)
2250 def _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules):
2251 """Generate the .targets file."""
2252 content = ['Project',
2253 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
2258 ['PropertyPageSchema',
2259 {'Include': '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'}
2262 for rule in msbuild_rules:
2264 ['AvailableItemName',
2265 {'Include': rule.rule_name},
2266 ['Targets', rule.target_name],
2268 content.append(item_group)
2270 for rule in msbuild_rules:
2273 {'TaskName': rule.rule_name,
2274 'TaskFactory': 'XamlTaskFactory',
2275 'AssemblyName': 'Microsoft.Build.Tasks.v4.0'
2277 ['Task', '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'],
2279 for rule in msbuild_rules:
2280 rule_name = rule.rule_name
2281 target_outputs = '%%(%s.Outputs)' % rule_name
2282 target_inputs = ('%%(%s.Identity);%%(%s.AdditionalDependencies);'
2283 '$(MSBuildProjectFile)') % (rule_name, rule_name)
2284 rule_inputs = '%%(%s.Identity)' % rule_name
2285 extension_condition = ("'%(Extension)'=='.obj' or "
2286 "'%(Extension)'=='.res' or "
2287 "'%(Extension)'=='.rsc' or "
2288 "'%(Extension)'=='.lib'")
2291 {'Condition': "'@(SelectedFiles)' != ''"},
2293 {'Remove': '@(%s)' % rule_name,
2294 'Condition': "'%(Identity)' != '@(SelectedFiles)'"
2300 [rule.inputs, {'Include': '%%(%s.AdditionalDependencies)' % rule_name}]
2305 {'Include': '%%(%s.Outputs)' % rule_name,
2306 'Condition': ("'%%(%s.Outputs)' != '' and "
2307 "'%%(%s.ExcludedFromBuild)' != 'true'" %
2308 (rule_name, rule_name))
2310 ['Source', "@(%s, '|')" % rule_name],
2311 ['Inputs', "@(%s -> '%%(Fullpath)', ';')" % rule.inputs],
2316 {'Importance': 'High',
2317 'Text': '%%(%s.ExecutionDescription)' % rule_name
2320 write_tlog_section = [
2322 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2323 "'true'" % (rule.tlog, rule.tlog),
2324 'File': '$(IntDir)$(ProjectName).write.1.tlog',
2325 'Lines': "^%%(%s.Source);@(%s->'%%(Fullpath)')" % (rule.tlog,
2329 read_tlog_section = [
2331 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2332 "'true'" % (rule.tlog, rule.tlog),
2333 'File': '$(IntDir)$(ProjectName).read.1.tlog',
2334 'Lines': "^%%(%s.Source);%%(%s.Inputs)" % (rule.tlog, rule.tlog)
2337 command_and_input_section = [
2339 {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
2340 "'true'" % (rule_name, rule_name),
2341 'CommandLineTemplate': '%%(%s.CommandLineTemplate)' % rule_name,
2342 'AdditionalOptions': '%%(%s.AdditionalOptions)' % rule_name,
2343 'Inputs': rule_inputs
2348 {'Name': rule.target_name,
2349 'BeforeTargets': '$(%s)' % rule.before_targets,
2350 'AfterTargets': '$(%s)' % rule.after_targets,
2351 'Condition': "'@(%s)' != ''" % rule_name,
2352 'DependsOnTargets': '$(%s);%s' % (rule.depends_on,
2353 rule.compute_output),
2354 'Outputs': target_outputs,
2355 'Inputs': target_inputs
2363 command_and_input_section,
2366 ['ComputeLinkInputsTargets',
2367 '$(ComputeLinkInputsTargets);',
2368 '%s;' % rule.compute_output
2370 ['ComputeLibInputsTargets',
2371 '$(ComputeLibInputsTargets);',
2372 '%s;' % rule.compute_output
2376 {'Name': rule.compute_output,
2377 'Condition': "'@(%s)' != ''" % rule_name
2381 {'Condition': "'@(%s)' != '' and "
2382 "'%%(%s.ExcludedFromBuild)' != 'true'" % (rule_name, rule_name),
2383 'Include': '%%(%s.Outputs)' % rule_name
2387 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2388 'Condition': extension_condition
2392 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2393 'Condition': extension_condition
2397 {'Include': '%%(%s.Identity)' % rule.dirs_to_make,
2398 'Condition': extension_condition
2403 {'Directories': ("@(%s->'%%(RootDir)%%(Directory)')" %
2409 easy_xml.WriteXmlIfChanged(content, targets_path, pretty=True, win32=True)
2412 def _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules):
2413 # Generate the .xml file
2415 'ProjectSchemaDefinitions',
2416 {'xmlns': ('clr-namespace:Microsoft.Build.Framework.XamlTypes;'
2417 'assembly=Microsoft.Build.Framework'),
2418 'xmlns:x': 'http://schemas.microsoft.com/winfx/2006/xaml',
2419 'xmlns:sys': 'clr-namespace:System;assembly=mscorlib',
2420 'xmlns:transformCallback':
2421 'Microsoft.Cpp.Dev10.ConvertPropertyCallback'
2424 for rule in msbuild_rules:
2427 {'Name': rule.rule_name,
2428 'PageTemplate': 'tool',
2429 'DisplayName': rule.display_name,
2434 {'Persistence': 'ProjectFile',
2435 'ItemType': rule.rule_name
2441 {'Name': 'General'},
2442 ['Category.DisplayName',
2443 ['sys:String', 'General'],
2447 {'Name': 'Command Line',
2448 'Subtype': 'CommandLine'
2450 ['Category.DisplayName',
2451 ['sys:String', 'Command Line'],
2455 ['StringListProperty',
2457 'Category': 'Command Line',
2458 'IsRequired': 'true',
2461 ['StringListProperty.DataSource',
2463 {'Persistence': 'ProjectFile',
2464 'ItemType': rule.rule_name,
2465 'SourceType': 'Item'
2471 {'Name': 'CommandLineTemplate',
2472 'DisplayName': 'Command Line',
2474 'IncludeInCommandLine': 'False'
2477 ['DynamicEnumProperty',
2478 {'Name': rule.before_targets,
2479 'Category': 'General',
2480 'EnumProvider': 'Targets',
2481 'IncludeInCommandLine': 'False'
2483 ['DynamicEnumProperty.DisplayName',
2484 ['sys:String', 'Execute Before'],
2486 ['DynamicEnumProperty.Description',
2487 ['sys:String', 'Specifies the targets for the build customization'
2491 ['DynamicEnumProperty.ProviderSettings',
2494 'Value': '^%s|^Compute' % rule.before_targets
2498 ['DynamicEnumProperty.DataSource',
2500 {'Persistence': 'ProjectFile',
2501 'HasConfigurationCondition': 'true'
2506 ['DynamicEnumProperty',
2507 {'Name': rule.after_targets,
2508 'Category': 'General',
2509 'EnumProvider': 'Targets',
2510 'IncludeInCommandLine': 'False'
2512 ['DynamicEnumProperty.DisplayName',
2513 ['sys:String', 'Execute After'],
2515 ['DynamicEnumProperty.Description',
2516 ['sys:String', ('Specifies the targets for the build customization'
2520 ['DynamicEnumProperty.ProviderSettings',
2523 'Value': '^%s|^Compute' % rule.after_targets
2527 ['DynamicEnumProperty.DataSource',
2529 {'Persistence': 'ProjectFile',
2531 'HasConfigurationCondition': 'true'
2536 ['StringListProperty',
2538 'DisplayName': 'Outputs',
2540 'IncludeInCommandLine': 'False'
2544 {'Name': 'ExecutionDescription',
2545 'DisplayName': 'Execution Description',
2547 'IncludeInCommandLine': 'False'
2550 ['StringListProperty',
2551 {'Name': 'AdditionalDependencies',
2552 'DisplayName': 'Additional Dependencies',
2553 'IncludeInCommandLine': 'False',
2558 {'Subtype': 'AdditionalOptions',
2559 'Name': 'AdditionalOptions',
2560 'Category': 'Command Line'
2562 ['StringProperty.DisplayName',
2563 ['sys:String', 'Additional Options'],
2565 ['StringProperty.Description',
2566 ['sys:String', 'Additional Options'],
2571 {'Name': rule.rule_name,
2572 'DisplayName': rule.display_name
2576 {'Name': '*' + rule.extension,
2577 'ContentType': rule.rule_name
2581 {'Name': rule.rule_name,
2583 'ItemType': rule.rule_name
2587 easy_xml.WriteXmlIfChanged(content, xml_path, pretty=True, win32=True)
2590 def _GetConfigurationAndPlatform(name, settings):
2591 configuration = name.rsplit('_', 1)[0]
2592 platform = settings.get('msvs_configuration_platform', 'Win32')
2593 return (configuration, platform)
2596 def _GetConfigurationCondition(name, settings):
2597 return (r"'$(Configuration)|$(Platform)'=='%s|%s'" %
2598 _GetConfigurationAndPlatform(name, settings))
2601 def _GetMSBuildProjectConfigurations(configurations):
2602 group = ['ItemGroup', {'Label': 'ProjectConfigurations'}]
2603 for (name, settings) in sorted(configurations.iteritems()):
2604 configuration, platform = _GetConfigurationAndPlatform(name, settings)
2605 designation = '%s|%s' % (configuration, platform)
2607 ['ProjectConfiguration', {'Include': designation},
2608 ['Configuration', configuration],
2609 ['Platform', platform]])
2613 def _GetMSBuildGlobalProperties(spec, guid, gyp_file_name):
2614 namespace = os.path.splitext(gyp_file_name)[0]
2616 ['PropertyGroup', {'Label': 'Globals'},
2617 ['ProjectGuid', guid],
2618 ['Keyword', 'Win32Proj'],
2619 ['RootNamespace', namespace],
2620 ['IgnoreWarnCompileDuplicatedFilename', 'true'],
2624 if spec.get('msvs_enable_winrt'):
2625 properties[0].append(['DefaultLanguage', 'en-US'])
2626 properties[0].append(['AppContainerApplication', 'true'])
2627 properties[0].append(['ApplicationTypeRevision', '8.1'])
2629 if spec.get('msvs_enable_winphone'):
2630 properties[0].append(['ApplicationType', 'Windows Phone'])
2632 properties[0].append(['ApplicationType', 'Windows Store'])
2636 def _GetMSBuildConfigurationDetails(spec, build_file):
2638 for name, settings in spec['configurations'].iteritems():
2639 msbuild_attributes = _GetMSBuildAttributes(spec, settings, build_file)
2640 condition = _GetConfigurationCondition(name, settings)
2641 character_set = msbuild_attributes.get('CharacterSet')
2642 _AddConditionalProperty(properties, condition, 'ConfigurationType',
2643 msbuild_attributes['ConfigurationType'])
2645 if 'msvs_enable_winrt' not in spec :
2646 _AddConditionalProperty(properties, condition, 'CharacterSet',
2648 return _GetMSBuildPropertyGroup(spec, 'Configuration', properties)
2651 def _GetMSBuildLocalProperties(msbuild_toolset):
2652 # Currently the only local property we support is PlatformToolset
2656 ['PropertyGroup', {'Label': 'Locals'},
2657 ['PlatformToolset', msbuild_toolset],
2663 def _GetMSBuildPropertySheets(configurations):
2664 user_props = r'$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props'
2665 additional_props = {}
2666 props_specified = False
2667 for name, settings in sorted(configurations.iteritems()):
2668 configuration = _GetConfigurationCondition(name, settings)
2669 if settings.has_key('msbuild_props'):
2670 additional_props[configuration] = _FixPaths(settings['msbuild_props'])
2671 props_specified = True
2673 additional_props[configuration] = ''
2675 if not props_specified:
2678 {'Label': 'PropertySheets'},
2680 {'Project': user_props,
2681 'Condition': "exists('%s')" % user_props,
2682 'Label': 'LocalAppDataPlatform'
2689 for condition, props in additional_props.iteritems():
2692 {'Label': 'PropertySheets',
2693 'Condition': condition
2696 {'Project': user_props,
2697 'Condition': "exists('%s')" % user_props,
2698 'Label': 'LocalAppDataPlatform'
2702 for props_file in props:
2703 import_group.append(['Import', {'Project':props_file}])
2704 sheets.append(import_group)
2707 def _ConvertMSVSBuildAttributes(spec, config, build_file):
2708 config_type = _GetMSVSConfigurationType(spec, build_file)
2709 msvs_attributes = _GetMSVSAttributes(spec, config, config_type)
2710 msbuild_attributes = {}
2711 for a in msvs_attributes:
2712 if a in ['IntermediateDirectory', 'OutputDirectory']:
2713 directory = MSVSSettings.ConvertVCMacrosToMSBuild(msvs_attributes[a])
2714 if not directory.endswith('\\'):
2716 msbuild_attributes[a] = directory
2717 elif a == 'CharacterSet':
2718 msbuild_attributes[a] = _ConvertMSVSCharacterSet(msvs_attributes[a])
2719 elif a == 'ConfigurationType':
2720 msbuild_attributes[a] = _ConvertMSVSConfigurationType(msvs_attributes[a])
2722 print 'Warning: Do not know how to convert MSVS attribute ' + a
2723 return msbuild_attributes
2726 def _ConvertMSVSCharacterSet(char_set):
2727 if char_set.isdigit():
2736 def _ConvertMSVSConfigurationType(config_type):
2737 if config_type.isdigit():
2740 '2': 'DynamicLibrary',
2741 '4': 'StaticLibrary',
2747 def _GetMSBuildAttributes(spec, config, build_file):
2748 if 'msbuild_configuration_attributes' not in config:
2749 msbuild_attributes = _ConvertMSVSBuildAttributes(spec, config, build_file)
2752 config_type = _GetMSVSConfigurationType(spec, build_file)
2753 config_type = _ConvertMSVSConfigurationType(config_type)
2754 msbuild_attributes = config.get('msbuild_configuration_attributes', {})
2755 msbuild_attributes.setdefault('ConfigurationType', config_type)
2756 output_dir = msbuild_attributes.get('OutputDirectory',
2757 '$(SolutionDir)$(Configuration)')
2758 msbuild_attributes['OutputDirectory'] = _FixPath(output_dir) + '\\'
2759 if 'IntermediateDirectory' not in msbuild_attributes:
2760 intermediate = _FixPath('$(Configuration)') + '\\'
2761 msbuild_attributes['IntermediateDirectory'] = intermediate
2762 if 'CharacterSet' in msbuild_attributes:
2763 msbuild_attributes['CharacterSet'] = _ConvertMSVSCharacterSet(
2764 msbuild_attributes['CharacterSet'])
2765 if 'TargetName' not in msbuild_attributes:
2766 prefix = spec.get('product_prefix', '')
2767 product_name = spec.get('product_name', '$(ProjectName)')
2768 target_name = prefix + product_name
2769 msbuild_attributes['TargetName'] = target_name
2771 if spec.get('msvs_external_builder'):
2772 external_out_dir = spec.get('msvs_external_builder_out_dir', '.')
2773 msbuild_attributes['OutputDirectory'] = _FixPath(external_out_dir) + '\\'
2775 # Make sure that 'TargetPath' matches 'Lib.OutputFile' or 'Link.OutputFile'
2776 # (depending on the tool used) to avoid MSB8012 warning.
2777 msbuild_tool_map = {
2778 'executable': 'Link',
2779 'shared_library': 'Link',
2780 'loadable_module': 'Link',
2781 'static_library': 'Lib',
2783 msbuild_tool = msbuild_tool_map.get(spec['type'])
2785 msbuild_settings = config['finalized_msbuild_settings']
2786 out_file = msbuild_settings[msbuild_tool].get('OutputFile')
2788 msbuild_attributes['TargetPath'] = _FixPath(out_file)
2789 target_ext = msbuild_settings[msbuild_tool].get('TargetExt')
2791 msbuild_attributes['TargetExt'] = target_ext
2793 return msbuild_attributes
2796 def _GetMSBuildConfigurationGlobalProperties(spec, configurations, build_file):
2797 # TODO(jeanluc) We could optimize out the following and do it only if
2798 # there are actions.
2799 # TODO(jeanluc) Handle the equivalent of setting 'CYGWIN=nontsec'.
2801 cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.'])[0]
2803 cyg_path = '$(MSBuildProjectDirectory)\\%s\\bin\\' % _FixPath(cygwin_dirs)
2804 new_paths.append(cyg_path)
2805 # TODO(jeanluc) Change the convention to have both a cygwin_dir and a
2807 python_path = cyg_path.replace('cygwin\\bin', 'python_26')
2808 new_paths.append(python_path)
2810 new_paths = '$(ExecutablePath);' + ';'.join(new_paths)
2813 for (name, configuration) in sorted(configurations.iteritems()):
2814 condition = _GetConfigurationCondition(name, configuration)
2815 attributes = _GetMSBuildAttributes(spec, configuration, build_file)
2816 msbuild_settings = configuration['finalized_msbuild_settings']
2817 _AddConditionalProperty(properties, condition, 'IntDir',
2818 attributes['IntermediateDirectory'])
2819 _AddConditionalProperty(properties, condition, 'OutDir',
2820 attributes['OutputDirectory'])
2821 _AddConditionalProperty(properties, condition, 'TargetName',
2822 attributes['TargetName'])
2824 if attributes.get('TargetPath'):
2825 _AddConditionalProperty(properties, condition, 'TargetPath',
2826 attributes['TargetPath'])
2827 if attributes.get('TargetExt'):
2828 _AddConditionalProperty(properties, condition, 'TargetExt',
2829 attributes['TargetExt'])
2832 _AddConditionalProperty(properties, condition, 'ExecutablePath',
2834 tool_settings = msbuild_settings.get('', {})
2835 for name, value in sorted(tool_settings.iteritems()):
2836 formatted_value = _GetValueFormattedForMSBuild('', name, value)
2837 _AddConditionalProperty(properties, condition, name, formatted_value)
2838 return _GetMSBuildPropertyGroup(spec, None, properties)
2841 def _AddConditionalProperty(properties, condition, name, value):
2842 """Adds a property / conditional value pair to a dictionary.
2845 properties: The dictionary to be modified. The key is the name of the
2846 property. The value is itself a dictionary; its key is the value and
2847 the value a list of condition for which this value is true.
2848 condition: The condition under which the named property has the value.
2849 name: The name of the property.
2850 value: The value of the property.
2852 if name not in properties:
2853 properties[name] = {}
2854 values = properties[name]
2855 if value not in values:
2857 conditions = values[value]
2858 conditions.append(condition)
2861 # Regex for msvs variable references ( i.e. $(FOO) ).
2862 MSVS_VARIABLE_REFERENCE = re.compile('\$\(([a-zA-Z_][a-zA-Z0-9_]*)\)')
2865 def _GetMSBuildPropertyGroup(spec, label, properties):
2866 """Returns a PropertyGroup definition for the specified properties.
2869 spec: The target project dict.
2870 label: An optional label for the PropertyGroup.
2871 properties: The dictionary to be converted. The key is the name of the
2872 property. The value is itself a dictionary; its key is the value and
2873 the value a list of condition for which this value is true.
2875 group = ['PropertyGroup']
2877 group.append({'Label': label})
2878 num_configurations = len(spec['configurations'])
2880 # Use a definition of edges such that user_of_variable -> used_varible.
2881 # This happens to be easier in this case, since a variable's
2882 # definition contains all variables it references in a single string.
2884 for value in sorted(properties[node].keys()):
2885 # Add to edges all $(...) references to variables.
2887 # Variable references that refer to names not in properties are excluded
2888 # These can exist for instance to refer built in definitions like
2891 # Self references are ignored. Self reference is used in a few places to
2892 # append to the default value. I.e. PATH=$(PATH);other_path
2893 edges.update(set([v for v in MSVS_VARIABLE_REFERENCE.findall(value)
2894 if v in properties and v != node]))
2896 properties_ordered = gyp.common.TopologicallySorted(
2897 properties.keys(), GetEdges)
2898 # Walk properties in the reverse of a topological sort on
2899 # user_of_variable -> used_variable as this ensures variables are
2900 # defined before they are used.
2901 # NOTE: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))
2902 for name in reversed(properties_ordered):
2903 values = properties[name]
2904 for value, conditions in sorted(values.iteritems()):
2905 if len(conditions) == num_configurations:
2906 # If the value is the same all configurations,
2907 # just add one unconditional entry.
2908 group.append([name, value])
2910 for condition in conditions:
2911 group.append([name, {'Condition': condition}, value])
2915 def _GetMSBuildToolSettingsSections(spec, configurations):
2917 for (name, configuration) in sorted(configurations.iteritems()):
2918 msbuild_settings = configuration['finalized_msbuild_settings']
2919 group = ['ItemDefinitionGroup',
2920 {'Condition': _GetConfigurationCondition(name, configuration)}
2922 for tool_name, tool_settings in sorted(msbuild_settings.iteritems()):
2923 # Skip the tool named '' which is a holder of global settings handled
2924 # by _GetMSBuildConfigurationGlobalProperties.
2928 for name, value in sorted(tool_settings.iteritems()):
2929 formatted_value = _GetValueFormattedForMSBuild(tool_name, name,
2931 tool.append([name, formatted_value])
2933 groups.append(group)
2937 def _FinalizeMSBuildSettings(spec, configuration):
2938 if 'msbuild_settings' in configuration:
2940 msbuild_settings = configuration['msbuild_settings']
2941 MSVSSettings.ValidateMSBuildSettings(msbuild_settings)
2944 msvs_settings = configuration.get('msvs_settings', {})
2945 msbuild_settings = MSVSSettings.ConvertToMSBuildSettings(msvs_settings)
2946 include_dirs, midl_include_dirs, resource_include_dirs = \
2947 _GetIncludeDirs(configuration)
2948 libraries = _GetLibraries(spec)
2949 library_dirs = _GetLibraryDirs(configuration)
2950 out_file, _, msbuild_tool = _GetOutputFilePathAndTool(spec, msbuild=True)
2951 target_ext = _GetOutputTargetExt(spec)
2952 defines = _GetDefines(configuration)
2954 # Visual Studio 2010 has TR1
2955 defines = [d for d in defines if d != '_HAS_TR1=0']
2956 # Warn of ignored settings
2957 ignored_settings = ['msvs_tool_files']
2958 for ignored_setting in ignored_settings:
2959 value = configuration.get(ignored_setting)
2961 print ('Warning: The automatic conversion to MSBuild does not handle '
2962 '%s. Ignoring setting of %s' % (ignored_setting, str(value)))
2964 defines = [_EscapeCppDefineForMSBuild(d) for d in defines]
2965 disabled_warnings = _GetDisabledWarnings(configuration)
2966 prebuild = configuration.get('msvs_prebuild')
2967 postbuild = configuration.get('msvs_postbuild')
2968 def_file = _GetModuleDefinition(spec)
2969 precompiled_header = configuration.get('msvs_precompiled_header')
2971 # Add the information to the appropriate tool
2972 # TODO(jeanluc) We could optimize and generate these settings only if
2973 # the corresponding files are found, e.g. don't generate ResourceCompile
2974 # if you don't have any resources.
2975 _ToolAppend(msbuild_settings, 'ClCompile',
2976 'AdditionalIncludeDirectories', include_dirs)
2977 _ToolAppend(msbuild_settings, 'Midl',
2978 'AdditionalIncludeDirectories', midl_include_dirs)
2979 _ToolAppend(msbuild_settings, 'ResourceCompile',
2980 'AdditionalIncludeDirectories', resource_include_dirs)
2981 # Add in libraries, note that even for empty libraries, we want this
2982 # set, to prevent inheriting default libraries from the enviroment.
2983 _ToolSetOrAppend(msbuild_settings, 'Link', 'AdditionalDependencies',
2985 _ToolAppend(msbuild_settings, 'Link', 'AdditionalLibraryDirectories',
2988 _ToolAppend(msbuild_settings, msbuild_tool, 'OutputFile', out_file,
2991 _ToolAppend(msbuild_settings, msbuild_tool, 'TargetExt', target_ext,
2994 _ToolAppend(msbuild_settings, 'ClCompile',
2995 'PreprocessorDefinitions', defines)
2996 _ToolAppend(msbuild_settings, 'ResourceCompile',
2997 'PreprocessorDefinitions', defines)
2998 # Add disabled warnings.
2999 _ToolAppend(msbuild_settings, 'ClCompile',
3000 'DisableSpecificWarnings', disabled_warnings)
3001 # Turn on precompiled headers if appropriate.
3002 if precompiled_header:
3003 precompiled_header = os.path.split(precompiled_header)[1]
3004 _ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'Use')
3005 _ToolAppend(msbuild_settings, 'ClCompile',
3006 'PrecompiledHeaderFile', precompiled_header)
3007 _ToolAppend(msbuild_settings, 'ClCompile',
3008 'ForcedIncludeFiles', [precompiled_header])
3010 _ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'NotUsing')
3011 # Turn off WinRT compilation
3012 _ToolAppend(msbuild_settings, 'ClCompile', 'CompileAsWinRT', 'false')
3013 # Turn on import libraries if appropriate
3014 if spec.get('msvs_requires_importlibrary'):
3015 _ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'false')
3016 # Loadable modules don't generate import libraries;
3017 # tell dependent projects to not expect one.
3018 if spec['type'] == 'loadable_module':
3019 _ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'true')
3020 # Set the module definition file if any.
3022 _ToolAppend(msbuild_settings, 'Link', 'ModuleDefinitionFile', def_file)
3023 configuration['finalized_msbuild_settings'] = msbuild_settings
3025 _ToolAppend(msbuild_settings, 'PreBuildEvent', 'Command', prebuild)
3027 _ToolAppend(msbuild_settings, 'PostBuildEvent', 'Command', postbuild)
3030 def _GetValueFormattedForMSBuild(tool_name, name, value):
3031 if type(value) == list:
3032 # For some settings, VS2010 does not automatically extends the settings
3033 # TODO(jeanluc) Is this what we want?
3034 if name in ['AdditionalIncludeDirectories',
3035 'AdditionalLibraryDirectories',
3036 'AdditionalOptions',
3038 'DisableSpecificWarnings',
3039 'PreprocessorDefinitions']:
3040 value.append('%%(%s)' % name)
3041 # For most tools, entries in a list should be separated with ';' but some
3042 # settings use a space. Check for those first.
3044 'ClCompile': ['AdditionalOptions'],
3045 'Link': ['AdditionalOptions'],
3046 'Lib': ['AdditionalOptions']}
3047 if tool_name in exceptions and name in exceptions[tool_name]:
3051 formatted_value = char.join(
3052 [MSVSSettings.ConvertVCMacrosToMSBuild(i) for i in value])
3054 formatted_value = MSVSSettings.ConvertVCMacrosToMSBuild(value)
3055 return formatted_value
3058 def _VerifySourcesExist(sources, root_dir):
3059 """Verifies that all source files exist on disk.
3061 Checks that all regular source files, i.e. not created at run time,
3062 exist on disk. Missing files cause needless recompilation but no otherwise
3066 sources: A recursive list of Filter/file names.
3067 root_dir: The root directory for the relative path names.
3069 A list of source files that cannot be found on disk.
3071 missing_sources = []
3072 for source in sources:
3073 if isinstance(source, MSVSProject.Filter):
3074 missing_sources.extend(_VerifySourcesExist(source.contents, root_dir))
3076 if '$' not in source:
3077 full_path = os.path.join(root_dir, source)
3078 if not os.path.exists(full_path):
3079 missing_sources.append(full_path)
3080 return missing_sources
3083 def _GetMSBuildSources(spec, sources, exclusions, rule_dependencies,
3084 extension_to_rule_name, actions_spec,
3085 sources_handled_by_action, list_excluded):
3086 groups = ['none', 'midl', 'include', 'compile', 'resource', 'rule',
3088 grouped_sources = {}
3090 grouped_sources[g] = []
3092 _AddSources2(spec, sources, exclusions, grouped_sources,
3093 rule_dependencies, extension_to_rule_name,
3094 sources_handled_by_action, list_excluded)
3097 if grouped_sources[g]:
3098 sources.append(['ItemGroup'] + grouped_sources[g])
3100 sources.append(['ItemGroup'] + actions_spec)
3104 def _AddSources2(spec, sources, exclusions, grouped_sources,
3105 rule_dependencies, extension_to_rule_name,
3106 sources_handled_by_action,
3108 extensions_excluded_from_precompile = []
3109 for source in sources:
3110 if isinstance(source, MSVSProject.Filter):
3111 _AddSources2(spec, source.contents, exclusions, grouped_sources,
3112 rule_dependencies, extension_to_rule_name,
3113 sources_handled_by_action,
3116 if not source in sources_handled_by_action:
3118 excluded_configurations = exclusions.get(source, [])
3119 if len(excluded_configurations) == len(spec['configurations']):
3120 detail.append(['ExcludedFromBuild', 'true'])
3122 for config_name, configuration in sorted(excluded_configurations):
3123 condition = _GetConfigurationCondition(config_name, configuration)
3124 detail.append(['ExcludedFromBuild',
3125 {'Condition': condition},
3127 # Add precompile if needed
3128 for config_name, configuration in spec['configurations'].iteritems():
3129 precompiled_source = configuration.get('msvs_precompiled_source', '')
3130 if precompiled_source != '':
3131 precompiled_source = _FixPath(precompiled_source)
3132 if not extensions_excluded_from_precompile:
3133 # If the precompiled header is generated by a C source, we must
3134 # not try to use it for C++ sources, and vice versa.
3135 basename, extension = os.path.splitext(precompiled_source)
3136 if extension == '.c':
3137 extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
3139 extensions_excluded_from_precompile = ['.c']
3141 if precompiled_source == source:
3142 condition = _GetConfigurationCondition(config_name, configuration)
3143 detail.append(['PrecompiledHeader',
3144 {'Condition': condition},
3148 # Turn off precompiled header usage for source files of a
3149 # different type than the file that generated the
3150 # precompiled header.
3151 for extension in extensions_excluded_from_precompile:
3152 if source.endswith(extension):
3153 detail.append(['PrecompiledHeader', ''])
3154 detail.append(['ForcedIncludeFiles', ''])
3156 group, element = _MapFileToMsBuildSourceType(source, rule_dependencies,
3157 extension_to_rule_name)
3158 grouped_sources[group].append([element, {'Include': source}] + detail)
3161 def _GetMSBuildProjectReferences(project):
3163 if project.dependencies:
3164 group = ['ItemGroup']
3165 for dependency in project.dependencies:
3166 guid = dependency.guid
3167 project_dir = os.path.split(project.path)[0]
3168 relative_path = gyp.common.RelativePath(dependency.path, project_dir)
3169 project_ref = ['ProjectReference',
3170 {'Include': relative_path},
3172 ['ReferenceOutputAssembly', 'false']
3174 for config in dependency.spec.get('configurations', {}).itervalues():
3175 # If it's disabled in any config, turn it off in the reference.
3176 if config.get('msvs_2010_disable_uldi_when_referenced', 0):
3177 project_ref.append(['UseLibraryDependencyInputs', 'false'])
3179 group.append(project_ref)
3180 references.append(group)
3184 def _GenerateMSBuildProject(project, options, version, generator_flags):
3186 configurations = spec['configurations']
3187 project_dir, project_file_name = os.path.split(project.path)
3188 gyp.common.EnsureDirExists(project.path)
3189 # Prepare list of sources and excluded sources.
3190 gyp_path = _NormalizedSource(project.build_file)
3191 relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
3193 gyp_file = os.path.split(project.build_file)[1]
3194 sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
3198 props_files_of_rules = set()
3199 targets_files_of_rules = set()
3200 rule_dependencies = set()
3201 extension_to_rule_name = {}
3202 list_excluded = generator_flags.get('msvs_list_excluded_files', True)
3204 # Don't generate rules if we are using an external builder like ninja.
3205 if not spec.get('msvs_external_builder'):
3206 _GenerateRulesForMSBuild(project_dir, options, spec,
3207 sources, excluded_sources,
3208 props_files_of_rules, targets_files_of_rules,
3209 actions_to_add, rule_dependencies,
3210 extension_to_rule_name)
3212 rules = spec.get('rules', [])
3213 _AdjustSourcesForRules(rules, sources, excluded_sources, True)
3215 sources, excluded_sources, excluded_idl = (
3216 _AdjustSourcesAndConvertToFilterHierarchy(spec, options,
3217 project_dir, sources,
3219 list_excluded, version))
3221 # Don't add actions if we are using an external builder like ninja.
3222 if not spec.get('msvs_external_builder'):
3223 _AddActions(actions_to_add, spec, project.build_file)
3224 _AddCopies(actions_to_add, spec)
3226 # NOTE: this stanza must appear after all actions have been decided.
3227 # Don't excluded sources with actions attached, or they won't run.
3228 excluded_sources = _FilterActionsFromExcluded(
3229 excluded_sources, actions_to_add)
3231 exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
3232 actions_spec, sources_handled_by_action = _GenerateActionsForMSBuild(
3233 spec, actions_to_add)
3235 _GenerateMSBuildFiltersFile(project.path + '.filters', sources,
3237 extension_to_rule_name)
3238 missing_sources = _VerifySourcesExist(sources, project_dir)
3240 for configuration in configurations.itervalues():
3241 _FinalizeMSBuildSettings(spec, configuration)
3243 # Add attributes to root element
3245 import_default_section = [
3246 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.Default.props'}]]
3247 import_cpp_props_section = [
3248 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.props'}]]
3249 import_cpp_targets_section = [
3250 ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.targets'}]]
3251 macro_section = [['PropertyGroup', {'Label': 'UserMacros'}]]
3255 {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003',
3256 'ToolsVersion': version.ProjectVersion(),
3257 'DefaultTargets': 'Build'
3260 content += _GetMSBuildProjectConfigurations(configurations)
3261 content += _GetMSBuildGlobalProperties(spec, project.guid, project_file_name)
3262 content += import_default_section
3263 content += _GetMSBuildConfigurationDetails(spec, project.build_file)
3264 if spec.get('msvs_enable_winphone'):
3265 content += _GetMSBuildLocalProperties('v120_wp81')
3267 content += _GetMSBuildLocalProperties(project.msbuild_toolset)
3268 content += import_cpp_props_section
3269 content += _GetMSBuildExtensions(props_files_of_rules)
3270 content += _GetMSBuildPropertySheets(configurations)
3271 content += macro_section
3272 content += _GetMSBuildConfigurationGlobalProperties(spec, configurations,
3274 content += _GetMSBuildToolSettingsSections(spec, configurations)
3275 content += _GetMSBuildSources(
3276 spec, sources, exclusions, rule_dependencies, extension_to_rule_name,
3277 actions_spec, sources_handled_by_action, list_excluded)
3278 content += _GetMSBuildProjectReferences(project)
3279 content += import_cpp_targets_section
3280 content += _GetMSBuildExtensionTargets(targets_files_of_rules)
3282 if spec.get('msvs_external_builder'):
3283 content += _GetMSBuildExternalBuilderTargets(spec)
3285 # TODO(jeanluc) File a bug to get rid of runas. We had in MSVS:
3286 # has_run_as = _WriteMSVSUserFile(project.path, version, spec)
3288 easy_xml.WriteXmlIfChanged(content, project.path, pretty=True, win32=True)
3290 return missing_sources
3293 def _GetMSBuildExternalBuilderTargets(spec):
3294 """Return a list of MSBuild targets for external builders.
3296 The "Build" and "Clean" targets are always generated. If the spec contains
3297 'msvs_external_builder_clcompile_cmd', then the "ClCompile" target will also
3298 be generated, to support building selected C/C++ files.
3301 spec: The gyp target spec.
3303 List of MSBuild 'Target' specs.
3305 build_cmd = _BuildCommandLineForRuleRaw(
3306 spec, spec['msvs_external_builder_build_cmd'],
3307 False, False, False, False)
3308 build_target = ['Target', {'Name': 'Build'}]
3309 build_target.append(['Exec', {'Command': build_cmd}])
3311 clean_cmd = _BuildCommandLineForRuleRaw(
3312 spec, spec['msvs_external_builder_clean_cmd'],
3313 False, False, False, False)
3314 clean_target = ['Target', {'Name': 'Clean'}]
3315 clean_target.append(['Exec', {'Command': clean_cmd}])
3317 targets = [build_target, clean_target]
3319 if spec.get('msvs_external_builder_clcompile_cmd'):
3320 clcompile_cmd = _BuildCommandLineForRuleRaw(
3321 spec, spec['msvs_external_builder_clcompile_cmd'],
3322 False, False, False, False)
3323 clcompile_target = ['Target', {'Name': 'ClCompile'}]
3324 clcompile_target.append(['Exec', {'Command': clcompile_cmd}])
3325 targets.append(clcompile_target)
3330 def _GetMSBuildExtensions(props_files_of_rules):
3331 extensions = ['ImportGroup', {'Label': 'ExtensionSettings'}]
3332 for props_file in props_files_of_rules:
3333 extensions.append(['Import', {'Project': props_file}])
3337 def _GetMSBuildExtensionTargets(targets_files_of_rules):
3338 targets_node = ['ImportGroup', {'Label': 'ExtensionTargets'}]
3339 for targets_file in sorted(targets_files_of_rules):
3340 targets_node.append(['Import', {'Project': targets_file}])
3341 return [targets_node]
3344 def _GenerateActionsForMSBuild(spec, actions_to_add):
3345 """Add actions accumulated into an actions_to_add, merging as needed.
3348 spec: the target project dict
3349 actions_to_add: dictionary keyed on input name, which maps to a list of
3350 dicts describing the actions attached to that input file.
3353 A pair of (action specification, the sources handled by this action).
3355 sources_handled_by_action = OrderedSet()
3357 for primary_input, actions in actions_to_add.iteritems():
3358 inputs = OrderedSet()
3359 outputs = OrderedSet()
3362 for action in actions:
3363 inputs.update(OrderedSet(action['inputs']))
3364 outputs.update(OrderedSet(action['outputs']))
3365 descriptions.append(action['description'])
3366 cmd = action['command']
3367 # For most actions, add 'call' so that actions that invoke batch files
3368 # return and continue executing. msbuild_use_call provides a way to
3369 # disable this but I have not seen any adverse effect from doing that
3371 if action.get('msbuild_use_call', True):
3373 commands.append(cmd)
3374 # Add the custom build action for one input file.
3375 description = ', and also '.join(descriptions)
3377 # We can't join the commands simply with && because the command line will
3378 # get too long. See also _AddActions: cygwin's setup_env mustn't be called
3379 # for every invocation or the command that sets the PATH will grow too
3381 command = '\r\n'.join([c + '\r\nif %errorlevel% neq 0 exit /b %errorlevel%'
3383 _AddMSBuildAction(spec,
3389 sources_handled_by_action,
3391 return actions_spec, sources_handled_by_action
3394 def _AddMSBuildAction(spec, primary_input, inputs, outputs, cmd, description,
3395 sources_handled_by_action, actions_spec):
3396 command = MSVSSettings.ConvertVCMacrosToMSBuild(cmd)
3397 primary_input = _FixPath(primary_input)
3398 inputs_array = _FixPaths(inputs)
3399 outputs_array = _FixPaths(outputs)
3400 additional_inputs = ';'.join([i for i in inputs_array
3401 if i != primary_input])
3402 outputs = ';'.join(outputs_array)
3403 sources_handled_by_action.add(primary_input)
3404 action_spec = ['CustomBuild', {'Include': primary_input}]
3406 # TODO(jeanluc) 'Document' for all or just if as_sources?
3407 [['FileType', 'Document'],
3408 ['Command', command],
3409 ['Message', description],
3410 ['Outputs', outputs]
3412 if additional_inputs:
3413 action_spec.append(['AdditionalInputs', additional_inputs])
3414 actions_spec.append(action_spec)