3495 lines
130 KiB
Python
3495 lines
130 KiB
Python
# Copyright (c) 2012 Google Inc. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import copy
|
|
import ntpath
|
|
import os
|
|
import posixpath
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
import gyp.common
|
|
import gyp.easy_xml as easy_xml
|
|
import gyp.generator.ninja as ninja_generator
|
|
import gyp.MSVSNew as MSVSNew
|
|
import gyp.MSVSProject as MSVSProject
|
|
import gyp.MSVSSettings as MSVSSettings
|
|
import gyp.MSVSToolFile as MSVSToolFile
|
|
import gyp.MSVSUserFile as MSVSUserFile
|
|
import gyp.MSVSUtil as MSVSUtil
|
|
import gyp.MSVSVersion as MSVSVersion
|
|
from gyp.common import GypError
|
|
from gyp.common import OrderedSet
|
|
|
|
# TODO: Remove once bots are on 2.7, http://crbug.com/241769
|
|
def _import_OrderedDict():
|
|
import collections
|
|
try:
|
|
return collections.OrderedDict
|
|
except AttributeError:
|
|
import gyp.ordered_dict
|
|
return gyp.ordered_dict.OrderedDict
|
|
OrderedDict = _import_OrderedDict()
|
|
|
|
|
|
# Regular expression for validating Visual Studio GUIDs. If the GUID
|
|
# contains lowercase hex letters, MSVS will be fine. However,
|
|
# IncrediBuild BuildConsole will parse the solution file, but then
|
|
# silently skip building the target causing hard to track down errors.
|
|
# Note that this only happens with the BuildConsole, and does not occur
|
|
# if IncrediBuild is executed from inside Visual Studio. This regex
|
|
# validates that the string looks like a GUID with all uppercase hex
|
|
# letters.
|
|
VALID_MSVS_GUID_CHARS = re.compile(r'^[A-F0-9\-]+$')
|
|
|
|
|
|
generator_default_variables = {
|
|
'EXECUTABLE_PREFIX': '',
|
|
'EXECUTABLE_SUFFIX': '.exe',
|
|
'STATIC_LIB_PREFIX': '',
|
|
'SHARED_LIB_PREFIX': '',
|
|
'STATIC_LIB_SUFFIX': '.lib',
|
|
'SHARED_LIB_SUFFIX': '.dll',
|
|
'INTERMEDIATE_DIR': '$(IntDir)',
|
|
'SHARED_INTERMEDIATE_DIR': '$(OutDir)obj/global_intermediate',
|
|
'OS': 'win',
|
|
'PRODUCT_DIR': '$(OutDir)',
|
|
'LIB_DIR': '$(OutDir)lib',
|
|
'RULE_INPUT_ROOT': '$(InputName)',
|
|
'RULE_INPUT_DIRNAME': '$(InputDir)',
|
|
'RULE_INPUT_EXT': '$(InputExt)',
|
|
'RULE_INPUT_NAME': '$(InputFileName)',
|
|
'RULE_INPUT_PATH': '$(InputPath)',
|
|
'CONFIGURATION_NAME': '$(ConfigurationName)',
|
|
}
|
|
|
|
|
|
# The msvs specific sections that hold paths
|
|
generator_additional_path_sections = [
|
|
'msvs_cygwin_dirs',
|
|
'msvs_props',
|
|
]
|
|
|
|
|
|
generator_additional_non_configuration_keys = [
|
|
'msvs_cygwin_dirs',
|
|
'msvs_cygwin_shell',
|
|
'msvs_large_pdb',
|
|
'msvs_shard',
|
|
'msvs_external_builder',
|
|
'msvs_external_builder_out_dir',
|
|
'msvs_external_builder_build_cmd',
|
|
'msvs_external_builder_clean_cmd',
|
|
'msvs_external_builder_clcompile_cmd',
|
|
'msvs_enable_winrt',
|
|
'msvs_requires_importlibrary',
|
|
'msvs_enable_winphone',
|
|
'msvs_application_type_revision',
|
|
'msvs_target_platform_version',
|
|
'msvs_target_platform_minversion',
|
|
]
|
|
|
|
|
|
# List of precompiled header related keys.
|
|
precomp_keys = [
|
|
'msvs_precompiled_header',
|
|
'msvs_precompiled_source',
|
|
]
|
|
|
|
|
|
cached_username = None
|
|
|
|
|
|
cached_domain = None
|
|
|
|
|
|
# TODO(gspencer): Switch the os.environ calls to be
|
|
# win32api.GetDomainName() and win32api.GetUserName() once the
|
|
# python version in depot_tools has been updated to work on Vista
|
|
# 64-bit.
|
|
def _GetDomainAndUserName():
|
|
if sys.platform not in ('win32', 'cygwin'):
|
|
return ('DOMAIN', 'USERNAME')
|
|
global cached_username
|
|
global cached_domain
|
|
if not cached_domain or not cached_username:
|
|
domain = os.environ.get('USERDOMAIN')
|
|
username = os.environ.get('USERNAME')
|
|
if not domain or not username:
|
|
call = subprocess.Popen(['net', 'config', 'Workstation'],
|
|
stdout=subprocess.PIPE)
|
|
config = call.communicate()[0]
|
|
username_re = re.compile(r'^User name\s+(\S+)', re.MULTILINE)
|
|
username_match = username_re.search(config)
|
|
if username_match:
|
|
username = username_match.group(1)
|
|
domain_re = re.compile(r'^Logon domain\s+(\S+)', re.MULTILINE)
|
|
domain_match = domain_re.search(config)
|
|
if domain_match:
|
|
domain = domain_match.group(1)
|
|
cached_domain = domain
|
|
cached_username = username
|
|
return (cached_domain, cached_username)
|
|
|
|
fixpath_prefix = None
|
|
|
|
|
|
def _NormalizedSource(source):
|
|
"""Normalize the path.
|
|
|
|
But not if that gets rid of a variable, as this may expand to something
|
|
larger than one directory.
|
|
|
|
Arguments:
|
|
source: The path to be normalize.d
|
|
|
|
Returns:
|
|
The normalized path.
|
|
"""
|
|
normalized = os.path.normpath(source)
|
|
if source.count('$') == normalized.count('$'):
|
|
source = normalized
|
|
return source
|
|
|
|
|
|
def _FixPath(path):
|
|
"""Convert paths to a form that will make sense in a vcproj file.
|
|
|
|
Arguments:
|
|
path: The path to convert, may contain / etc.
|
|
Returns:
|
|
The path with all slashes made into backslashes.
|
|
"""
|
|
if fixpath_prefix and path and not os.path.isabs(path) and not path[0] == '$':
|
|
path = os.path.join(fixpath_prefix, path)
|
|
path = path.replace('/', '\\')
|
|
path = _NormalizedSource(path)
|
|
if path and path[-1] == '\\':
|
|
path = path[:-1]
|
|
return path
|
|
|
|
|
|
def _FixPaths(paths):
|
|
"""Fix each of the paths of the list."""
|
|
return [_FixPath(i) for i in paths]
|
|
|
|
|
|
def _ConvertSourcesToFilterHierarchy(sources, prefix=None, excluded=None,
|
|
list_excluded=True, msvs_version=None):
|
|
"""Converts a list split source file paths into a vcproj folder hierarchy.
|
|
|
|
Arguments:
|
|
sources: A list of source file paths split.
|
|
prefix: A list of source file path layers meant to apply to each of sources.
|
|
excluded: A set of excluded files.
|
|
msvs_version: A MSVSVersion object.
|
|
|
|
Returns:
|
|
A hierarchy of filenames and MSVSProject.Filter objects that matches the
|
|
layout of the source tree.
|
|
For example:
|
|
_ConvertSourcesToFilterHierarchy([['a', 'bob1.c'], ['b', 'bob2.c']],
|
|
prefix=['joe'])
|
|
-->
|
|
[MSVSProject.Filter('a', contents=['joe\\a\\bob1.c']),
|
|
MSVSProject.Filter('b', contents=['joe\\b\\bob2.c'])]
|
|
"""
|
|
if not prefix: prefix = []
|
|
result = []
|
|
excluded_result = []
|
|
folders = OrderedDict()
|
|
# Gather files into the final result, excluded, or folders.
|
|
for s in sources:
|
|
if len(s) == 1:
|
|
filename = _NormalizedSource('\\'.join(prefix + s))
|
|
if filename in excluded:
|
|
excluded_result.append(filename)
|
|
else:
|
|
result.append(filename)
|
|
elif msvs_version and not msvs_version.UsesVcxproj():
|
|
# For MSVS 2008 and earlier, we need to process all files before walking
|
|
# the sub folders.
|
|
if not folders.get(s[0]):
|
|
folders[s[0]] = []
|
|
folders[s[0]].append(s[1:])
|
|
else:
|
|
contents = _ConvertSourcesToFilterHierarchy([s[1:]], prefix + [s[0]],
|
|
excluded=excluded,
|
|
list_excluded=list_excluded,
|
|
msvs_version=msvs_version)
|
|
contents = MSVSProject.Filter(s[0], contents=contents)
|
|
result.append(contents)
|
|
# Add a folder for excluded files.
|
|
if excluded_result and list_excluded:
|
|
excluded_folder = MSVSProject.Filter('_excluded_files',
|
|
contents=excluded_result)
|
|
result.append(excluded_folder)
|
|
|
|
if msvs_version and msvs_version.UsesVcxproj():
|
|
return result
|
|
|
|
# Populate all the folders.
|
|
for f in folders:
|
|
contents = _ConvertSourcesToFilterHierarchy(folders[f], prefix=prefix + [f],
|
|
excluded=excluded,
|
|
list_excluded=list_excluded,
|
|
msvs_version=msvs_version)
|
|
contents = MSVSProject.Filter(f, contents=contents)
|
|
result.append(contents)
|
|
return result
|
|
|
|
|
|
def _ToolAppend(tools, tool_name, setting, value, only_if_unset=False):
|
|
if not value: return
|
|
_ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset)
|
|
|
|
|
|
def _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset=False):
|
|
# TODO(bradnelson): ugly hack, fix this more generally!!!
|
|
if 'Directories' in setting or 'Dependencies' in setting:
|
|
if type(value) == str:
|
|
value = value.replace('/', '\\')
|
|
else:
|
|
value = [i.replace('/', '\\') for i in value]
|
|
if not tools.get(tool_name):
|
|
tools[tool_name] = dict()
|
|
tool = tools[tool_name]
|
|
if tool.get(setting):
|
|
if only_if_unset: return
|
|
if type(tool[setting]) == list and type(value) == list:
|
|
tool[setting] += value
|
|
else:
|
|
raise TypeError(
|
|
'Appending "%s" to a non-list setting "%s" for tool "%s" is '
|
|
'not allowed, previous value: %s' % (
|
|
value, setting, tool_name, str(tool[setting])))
|
|
else:
|
|
tool[setting] = value
|
|
|
|
|
|
def _ConfigPlatform(config_data):
|
|
return config_data.get('msvs_configuration_platform', 'Win32')
|
|
|
|
|
|
def _ConfigBaseName(config_name, platform_name):
|
|
if config_name.endswith('_' + platform_name):
|
|
return config_name[0:-len(platform_name) - 1]
|
|
else:
|
|
return config_name
|
|
|
|
|
|
def _ConfigFullName(config_name, config_data):
|
|
platform_name = _ConfigPlatform(config_data)
|
|
return '%s|%s' % (_ConfigBaseName(config_name, platform_name), platform_name)
|
|
|
|
|
|
def _ConfigWindowsTargetPlatformVersion(config_data):
|
|
ver = config_data.get('msvs_windows_target_platform_version')
|
|
if not ver or re.match(r'^\d+', ver):
|
|
return ver
|
|
for key in [r'HKLM\Software\Microsoft\Microsoft SDKs\Windows\%s',
|
|
r'HKLM\Software\Wow6432Node\Microsoft\Microsoft SDKs\Windows\%s']:
|
|
sdkdir = MSVSVersion._RegistryGetValue(key % ver, 'InstallationFolder')
|
|
if not sdkdir:
|
|
continue
|
|
version = MSVSVersion._RegistryGetValue(key % ver, 'ProductVersion') or ''
|
|
# find a matching entry in sdkdir\include
|
|
names = sorted([x for x in os.listdir(r'%s\include' % sdkdir) \
|
|
if x.startswith(version)], reverse = True)
|
|
return names[0]
|
|
|
|
|
|
def _BuildCommandLineForRuleRaw(spec, cmd, cygwin_shell, has_input_path,
|
|
quote_cmd, do_setup_env):
|
|
|
|
if [x for x in cmd if '$(InputDir)' in x]:
|
|
input_dir_preamble = (
|
|
'set INPUTDIR=$(InputDir)\n'
|
|
'if NOT DEFINED INPUTDIR set INPUTDIR=.\\\n'
|
|
'set INPUTDIR=%INPUTDIR:~0,-1%\n'
|
|
)
|
|
else:
|
|
input_dir_preamble = ''
|
|
|
|
if cygwin_shell:
|
|
# Find path to cygwin.
|
|
cygwin_dir = _FixPath(spec.get('msvs_cygwin_dirs', ['.'])[0])
|
|
# Prepare command.
|
|
direct_cmd = cmd
|
|
direct_cmd = [i.replace('$(IntDir)',
|
|
'`cygpath -m "${INTDIR}"`') for i in direct_cmd]
|
|
direct_cmd = [i.replace('$(OutDir)',
|
|
'`cygpath -m "${OUTDIR}"`') for i in direct_cmd]
|
|
direct_cmd = [i.replace('$(InputDir)',
|
|
'`cygpath -m "${INPUTDIR}"`') for i in direct_cmd]
|
|
if has_input_path:
|
|
direct_cmd = [i.replace('$(InputPath)',
|
|
'`cygpath -m "${INPUTPATH}"`')
|
|
for i in direct_cmd]
|
|
direct_cmd = ['\\"%s\\"' % i.replace('"', '\\\\\\"') for i in direct_cmd]
|
|
# direct_cmd = gyp.common.EncodePOSIXShellList(direct_cmd)
|
|
direct_cmd = ' '.join(direct_cmd)
|
|
# TODO(quote): regularize quoting path names throughout the module
|
|
cmd = ''
|
|
if do_setup_env:
|
|
cmd += 'call "$(ProjectDir)%(cygwin_dir)s\\setup_env.bat" && '
|
|
cmd += 'set CYGWIN=nontsec&& '
|
|
if direct_cmd.find('NUMBER_OF_PROCESSORS') >= 0:
|
|
cmd += 'set /a NUMBER_OF_PROCESSORS_PLUS_1=%%NUMBER_OF_PROCESSORS%%+1&& '
|
|
if direct_cmd.find('INTDIR') >= 0:
|
|
cmd += 'set INTDIR=$(IntDir)&& '
|
|
if direct_cmd.find('OUTDIR') >= 0:
|
|
cmd += 'set OUTDIR=$(OutDir)&& '
|
|
if has_input_path and direct_cmd.find('INPUTPATH') >= 0:
|
|
cmd += 'set INPUTPATH=$(InputPath) && '
|
|
cmd += 'bash -c "%(cmd)s"'
|
|
cmd = cmd % {'cygwin_dir': cygwin_dir,
|
|
'cmd': direct_cmd}
|
|
return input_dir_preamble + cmd
|
|
else:
|
|
# Convert cat --> type to mimic unix.
|
|
if cmd[0] == 'cat':
|
|
command = ['type']
|
|
else:
|
|
command = [cmd[0].replace('/', '\\')]
|
|
# Add call before command to ensure that commands can be tied together one
|
|
# after the other without aborting in Incredibuild, since IB makes a bat
|
|
# file out of the raw command string, and some commands (like python) are
|
|
# actually batch files themselves.
|
|
command.insert(0, 'call')
|
|
# Fix the paths
|
|
# TODO(quote): This is a really ugly heuristic, and will miss path fixing
|
|
# for arguments like "--arg=path" or "/opt:path".
|
|
# If the argument starts with a slash or dash, it's probably a command line
|
|
# switch
|
|
arguments = [i if (i[:1] in "/-") else _FixPath(i) for i in cmd[1:]]
|
|
arguments = [i.replace('$(InputDir)', '%INPUTDIR%') for i in arguments]
|
|
arguments = [MSVSSettings.FixVCMacroSlashes(i) for i in arguments]
|
|
if quote_cmd:
|
|
# Support a mode for using cmd directly.
|
|
# Convert any paths to native form (first element is used directly).
|
|
# TODO(quote): regularize quoting path names throughout the module
|
|
arguments = ['"%s"' % i for i in arguments]
|
|
# Collapse into a single command.
|
|
return input_dir_preamble + ' '.join(command + arguments)
|
|
|
|
|
|
def _BuildCommandLineForRule(spec, rule, has_input_path, do_setup_env):
|
|
# Currently this weird argument munging is used to duplicate the way a
|
|
# python script would need to be run as part of the chrome tree.
|
|
# Eventually we should add some sort of rule_default option to set this
|
|
# per project. For now the behavior chrome needs is the default.
|
|
mcs = rule.get('msvs_cygwin_shell')
|
|
if mcs is None:
|
|
mcs = int(spec.get('msvs_cygwin_shell', 1))
|
|
elif isinstance(mcs, str):
|
|
mcs = int(mcs)
|
|
quote_cmd = int(rule.get('msvs_quote_cmd', 1))
|
|
return _BuildCommandLineForRuleRaw(spec, rule['action'], mcs, has_input_path,
|
|
quote_cmd, do_setup_env=do_setup_env)
|
|
|
|
|
|
def _AddActionStep(actions_dict, inputs, outputs, description, command):
|
|
"""Merge action into an existing list of actions.
|
|
|
|
Care must be taken so that actions which have overlapping inputs either don't
|
|
get assigned to the same input, or get collapsed into one.
|
|
|
|
Arguments:
|
|
actions_dict: dictionary keyed on input name, which maps to a list of
|
|
dicts describing the actions attached to that input file.
|
|
inputs: list of inputs
|
|
outputs: list of outputs
|
|
description: description of the action
|
|
command: command line to execute
|
|
"""
|
|
# Require there to be at least one input (call sites will ensure this).
|
|
assert inputs
|
|
|
|
action = {
|
|
'inputs': inputs,
|
|
'outputs': outputs,
|
|
'description': description,
|
|
'command': command,
|
|
}
|
|
|
|
# Pick where to stick this action.
|
|
# While less than optimal in terms of build time, attach them to the first
|
|
# input for now.
|
|
chosen_input = inputs[0]
|
|
|
|
# Add it there.
|
|
if chosen_input not in actions_dict:
|
|
actions_dict[chosen_input] = []
|
|
actions_dict[chosen_input].append(action)
|
|
|
|
|
|
def _AddCustomBuildToolForMSVS(p, spec, primary_input,
|
|
inputs, outputs, description, cmd):
|
|
"""Add a custom build tool to execute something.
|
|
|
|
Arguments:
|
|
p: the target project
|
|
spec: the target project dict
|
|
primary_input: input file to attach the build tool to
|
|
inputs: list of inputs
|
|
outputs: list of outputs
|
|
description: description of the action
|
|
cmd: command line to execute
|
|
"""
|
|
inputs = _FixPaths(inputs)
|
|
outputs = _FixPaths(outputs)
|
|
tool = MSVSProject.Tool(
|
|
'VCCustomBuildTool',
|
|
{'Description': description,
|
|
'AdditionalDependencies': ';'.join(inputs),
|
|
'Outputs': ';'.join(outputs),
|
|
'CommandLine': cmd,
|
|
})
|
|
# Add to the properties of primary input for each config.
|
|
for config_name, c_data in spec['configurations'].iteritems():
|
|
p.AddFileConfig(_FixPath(primary_input),
|
|
_ConfigFullName(config_name, c_data), tools=[tool])
|
|
|
|
|
|
def _AddAccumulatedActionsToMSVS(p, spec, actions_dict):
|
|
"""Add actions accumulated into an actions_dict, merging as needed.
|
|
|
|
Arguments:
|
|
p: the target project
|
|
spec: the target project dict
|
|
actions_dict: dictionary keyed on input name, which maps to a list of
|
|
dicts describing the actions attached to that input file.
|
|
"""
|
|
for primary_input in actions_dict:
|
|
inputs = OrderedSet()
|
|
outputs = OrderedSet()
|
|
descriptions = []
|
|
commands = []
|
|
for action in actions_dict[primary_input]:
|
|
inputs.update(OrderedSet(action['inputs']))
|
|
outputs.update(OrderedSet(action['outputs']))
|
|
descriptions.append(action['description'])
|
|
commands.append(action['command'])
|
|
# Add the custom build step for one input file.
|
|
description = ', and also '.join(descriptions)
|
|
command = '\r\n'.join(commands)
|
|
_AddCustomBuildToolForMSVS(p, spec,
|
|
primary_input=primary_input,
|
|
inputs=inputs,
|
|
outputs=outputs,
|
|
description=description,
|
|
cmd=command)
|
|
|
|
|
|
def _RuleExpandPath(path, input_file):
|
|
"""Given the input file to which a rule applied, string substitute a path.
|
|
|
|
Arguments:
|
|
path: a path to string expand
|
|
input_file: the file to which the rule applied.
|
|
Returns:
|
|
The string substituted path.
|
|
"""
|
|
path = path.replace('$(InputName)',
|
|
os.path.splitext(os.path.split(input_file)[1])[0])
|
|
path = path.replace('$(InputDir)', os.path.dirname(input_file))
|
|
path = path.replace('$(InputExt)',
|
|
os.path.splitext(os.path.split(input_file)[1])[1])
|
|
path = path.replace('$(InputFileName)', os.path.split(input_file)[1])
|
|
path = path.replace('$(InputPath)', input_file)
|
|
return path
|
|
|
|
|
|
def _FindRuleTriggerFiles(rule, sources):
|
|
"""Find the list of files which a particular rule applies to.
|
|
|
|
Arguments:
|
|
rule: the rule in question
|
|
sources: the set of all known source files for this project
|
|
Returns:
|
|
The list of sources that trigger a particular rule.
|
|
"""
|
|
return rule.get('rule_sources', [])
|
|
|
|
|
|
def _RuleInputsAndOutputs(rule, trigger_file):
|
|
"""Find the inputs and outputs generated by a rule.
|
|
|
|
Arguments:
|
|
rule: the rule in question.
|
|
trigger_file: the main trigger for this rule.
|
|
Returns:
|
|
The pair of (inputs, outputs) involved in this rule.
|
|
"""
|
|
raw_inputs = _FixPaths(rule.get('inputs', []))
|
|
raw_outputs = _FixPaths(rule.get('outputs', []))
|
|
inputs = OrderedSet()
|
|
outputs = OrderedSet()
|
|
inputs.add(trigger_file)
|
|
for i in raw_inputs:
|
|
inputs.add(_RuleExpandPath(i, trigger_file))
|
|
for o in raw_outputs:
|
|
outputs.add(_RuleExpandPath(o, trigger_file))
|
|
return (inputs, outputs)
|
|
|
|
|
|
def _GenerateNativeRulesForMSVS(p, rules, output_dir, spec, options):
|
|
"""Generate a native rules file.
|
|
|
|
Arguments:
|
|
p: the target project
|
|
rules: the set of rules to include
|
|
output_dir: the directory in which the project/gyp resides
|
|
spec: the project dict
|
|
options: global generator options
|
|
"""
|
|
rules_filename = '%s%s.rules' % (spec['target_name'],
|
|
options.suffix)
|
|
rules_file = MSVSToolFile.Writer(os.path.join(output_dir, rules_filename),
|
|
spec['target_name'])
|
|
# Add each rule.
|
|
for r in rules:
|
|
rule_name = r['rule_name']
|
|
rule_ext = r['extension']
|
|
inputs = _FixPaths(r.get('inputs', []))
|
|
outputs = _FixPaths(r.get('outputs', []))
|
|
# Skip a rule with no action and no inputs.
|
|
if 'action' not in r and not r.get('rule_sources', []):
|
|
continue
|
|
cmd = _BuildCommandLineForRule(spec, r, has_input_path=True,
|
|
do_setup_env=True)
|
|
rules_file.AddCustomBuildRule(name=rule_name,
|
|
description=r.get('message', rule_name),
|
|
extensions=[rule_ext],
|
|
additional_dependencies=inputs,
|
|
outputs=outputs,
|
|
cmd=cmd)
|
|
# Write out rules file.
|
|
rules_file.WriteIfChanged()
|
|
|
|
# Add rules file to project.
|
|
p.AddToolFile(rules_filename)
|
|
|
|
|
|
def _Cygwinify(path):
|
|
path = path.replace('$(OutDir)', '$(OutDirCygwin)')
|
|
path = path.replace('$(IntDir)', '$(IntDirCygwin)')
|
|
return path
|
|
|
|
|
|
def _GenerateExternalRules(rules, output_dir, spec,
|
|
sources, options, actions_to_add):
|
|
"""Generate an external makefile to do a set of rules.
|
|
|
|
Arguments:
|
|
rules: the list of rules to include
|
|
output_dir: path containing project and gyp files
|
|
spec: project specification data
|
|
sources: set of sources known
|
|
options: global generator options
|
|
actions_to_add: The list of actions we will add to.
|
|
"""
|
|
filename = '%s_rules%s.mk' % (spec['target_name'], options.suffix)
|
|
mk_file = gyp.common.WriteOnDiff(os.path.join(output_dir, filename))
|
|
# Find cygwin style versions of some paths.
|
|
mk_file.write('OutDirCygwin:=$(shell cygpath -u "$(OutDir)")\n')
|
|
mk_file.write('IntDirCygwin:=$(shell cygpath -u "$(IntDir)")\n')
|
|
# Gather stuff needed to emit all: target.
|
|
all_inputs = OrderedSet()
|
|
all_outputs = OrderedSet()
|
|
all_output_dirs = OrderedSet()
|
|
first_outputs = []
|
|
for rule in rules:
|
|
trigger_files = _FindRuleTriggerFiles(rule, sources)
|
|
for tf in trigger_files:
|
|
inputs, outputs = _RuleInputsAndOutputs(rule, tf)
|
|
all_inputs.update(OrderedSet(inputs))
|
|
all_outputs.update(OrderedSet(outputs))
|
|
# Only use one target from each rule as the dependency for
|
|
# 'all' so we don't try to build each rule multiple times.
|
|
first_outputs.append(list(outputs)[0])
|
|
# Get the unique output directories for this rule.
|
|
output_dirs = [os.path.split(i)[0] for i in outputs]
|
|
for od in output_dirs:
|
|
all_output_dirs.add(od)
|
|
first_outputs_cyg = [_Cygwinify(i) for i in first_outputs]
|
|
# Write out all: target, including mkdir for each output directory.
|
|
mk_file.write('all: %s\n' % ' '.join(first_outputs_cyg))
|
|
for od in all_output_dirs:
|
|
if od:
|
|
mk_file.write('\tmkdir -p `cygpath -u "%s"`\n' % od)
|
|
mk_file.write('\n')
|
|
# Define how each output is generated.
|
|
for rule in rules:
|
|
trigger_files = _FindRuleTriggerFiles(rule, sources)
|
|
for tf in trigger_files:
|
|
# Get all the inputs and outputs for this rule for this trigger file.
|
|
inputs, outputs = _RuleInputsAndOutputs(rule, tf)
|
|
inputs = [_Cygwinify(i) for i in inputs]
|
|
outputs = [_Cygwinify(i) for i in outputs]
|
|
# Prepare the command line for this rule.
|
|
cmd = [_RuleExpandPath(c, tf) for c in rule['action']]
|
|
cmd = ['"%s"' % i for i in cmd]
|
|
cmd = ' '.join(cmd)
|
|
# Add it to the makefile.
|
|
mk_file.write('%s: %s\n' % (' '.join(outputs), ' '.join(inputs)))
|
|
mk_file.write('\t%s\n\n' % cmd)
|
|
# Close up the file.
|
|
mk_file.close()
|
|
|
|
# Add makefile to list of sources.
|
|
sources.add(filename)
|
|
# Add a build action to call makefile.
|
|
cmd = ['make',
|
|
'OutDir=$(OutDir)',
|
|
'IntDir=$(IntDir)',
|
|
'-j', '${NUMBER_OF_PROCESSORS_PLUS_1}',
|
|
'-f', filename]
|
|
cmd = _BuildCommandLineForRuleRaw(spec, cmd, True, False, True, True)
|
|
# Insert makefile as 0'th input, so it gets the action attached there,
|
|
# as this is easier to understand from in the IDE.
|
|
all_inputs = list(all_inputs)
|
|
all_inputs.insert(0, filename)
|
|
_AddActionStep(actions_to_add,
|
|
inputs=_FixPaths(all_inputs),
|
|
outputs=_FixPaths(all_outputs),
|
|
description='Running external rules for %s' %
|
|
spec['target_name'],
|
|
command=cmd)
|
|
|
|
|
|
def _EscapeEnvironmentVariableExpansion(s):
|
|
"""Escapes % characters.
|
|
|
|
Escapes any % characters so that Windows-style environment variable
|
|
expansions will leave them alone.
|
|
See http://connect.microsoft.com/VisualStudio/feedback/details/106127/cl-d-name-text-containing-percentage-characters-doesnt-compile
|
|
to understand why we have to do this.
|
|
|
|
Args:
|
|
s: The string to be escaped.
|
|
|
|
Returns:
|
|
The escaped string.
|
|
"""
|
|
s = s.replace('%', '%%')
|
|
return s
|
|
|
|
|
|
quote_replacer_regex = re.compile(r'(\\*)"')
|
|
|
|
|
|
def _EscapeCommandLineArgumentForMSVS(s):
|
|
"""Escapes a Windows command-line argument.
|
|
|
|
So that the Win32 CommandLineToArgv function will turn the escaped result back
|
|
into the original string.
|
|
See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
|
|
("Parsing C++ Command-Line Arguments") to understand why we have to do
|
|
this.
|
|
|
|
Args:
|
|
s: the string to be escaped.
|
|
Returns:
|
|
the escaped string.
|
|
"""
|
|
|
|
def _Replace(match):
|
|
# For a literal quote, CommandLineToArgv requires an odd number of
|
|
# backslashes preceding it, and it produces half as many literal backslashes
|
|
# (rounded down). So we need to produce 2n+1 backslashes.
|
|
return 2 * match.group(1) + '\\"'
|
|
|
|
# Escape all quotes so that they are interpreted literally.
|
|
s = quote_replacer_regex.sub(_Replace, s)
|
|
# Now add unescaped quotes so that any whitespace is interpreted literally.
|
|
s = '"' + s + '"'
|
|
return s
|
|
|
|
|
|
delimiters_replacer_regex = re.compile(r'(\\*)([,;]+)')
|
|
|
|
|
|
def _EscapeVCProjCommandLineArgListItem(s):
|
|
"""Escapes command line arguments for MSVS.
|
|
|
|
The VCProj format stores string lists in a single string using commas and
|
|
semi-colons as separators, which must be quoted if they are to be
|
|
interpreted literally. However, command-line arguments may already have
|
|
quotes, and the VCProj parser is ignorant of the backslash escaping
|
|
convention used by CommandLineToArgv, so the command-line quotes and the
|
|
VCProj quotes may not be the same quotes. So to store a general
|
|
command-line argument in a VCProj list, we need to parse the existing
|
|
quoting according to VCProj's convention and quote any delimiters that are
|
|
not already quoted by that convention. The quotes that we add will also be
|
|
seen by CommandLineToArgv, so if backslashes precede them then we also have
|
|
to escape those backslashes according to the CommandLineToArgv
|
|
convention.
|
|
|
|
Args:
|
|
s: the string to be escaped.
|
|
Returns:
|
|
the escaped string.
|
|
"""
|
|
|
|
def _Replace(match):
|
|
# For a non-literal quote, CommandLineToArgv requires an even number of
|
|
# backslashes preceding it, and it produces half as many literal
|
|
# backslashes. So we need to produce 2n backslashes.
|
|
return 2 * match.group(1) + '"' + match.group(2) + '"'
|
|
|
|
segments = s.split('"')
|
|
# The unquoted segments are at the even-numbered indices.
|
|
for i in range(0, len(segments), 2):
|
|
segments[i] = delimiters_replacer_regex.sub(_Replace, segments[i])
|
|
# Concatenate back into a single string
|
|
s = '"'.join(segments)
|
|
if len(segments) % 2 == 0:
|
|
# String ends while still quoted according to VCProj's convention. This
|
|
# means the delimiter and the next list item that follow this one in the
|
|
# .vcproj file will be misinterpreted as part of this item. There is nothing
|
|
# we can do about this. Adding an extra quote would correct the problem in
|
|
# the VCProj but cause the same problem on the final command-line. Moving
|
|
# the item to the end of the list does works, but that's only possible if
|
|
# there's only one such item. Let's just warn the user.
|
|
print >> sys.stderr, ('Warning: MSVS may misinterpret the odd number of ' +
|
|
'quotes in ' + s)
|
|
return s
|
|
|
|
|
|
def _EscapeCppDefineForMSVS(s):
|
|
"""Escapes a CPP define so that it will reach the compiler unaltered."""
|
|
s = _EscapeEnvironmentVariableExpansion(s)
|
|
s = _EscapeCommandLineArgumentForMSVS(s)
|
|
s = _EscapeVCProjCommandLineArgListItem(s)
|
|
# cl.exe replaces literal # characters with = in preprocesor definitions for
|
|
# some reason. Octal-encode to work around that.
|
|
s = s.replace('#', '\\%03o' % ord('#'))
|
|
return s
|
|
|
|
|
|
quote_replacer_regex2 = re.compile(r'(\\+)"')
|
|
|
|
|
|
def _EscapeCommandLineArgumentForMSBuild(s):
|
|
"""Escapes a Windows command-line argument for use by MSBuild."""
|
|
|
|
def _Replace(match):
|
|
return (len(match.group(1)) / 2 * 4) * '\\' + '\\"'
|
|
|
|
# Escape all quotes so that they are interpreted literally.
|
|
s = quote_replacer_regex2.sub(_Replace, s)
|
|
return s
|
|
|
|
|
|
def _EscapeMSBuildSpecialCharacters(s):
|
|
escape_dictionary = {
|
|
'%': '%25',
|
|
'$': '%24',
|
|
'@': '%40',
|
|
"'": '%27',
|
|
';': '%3B',
|
|
'?': '%3F',
|
|
'*': '%2A'
|
|
}
|
|
result = ''.join([escape_dictionary.get(c, c) for c in s])
|
|
return result
|
|
|
|
|
|
def _EscapeCppDefineForMSBuild(s):
|
|
"""Escapes a CPP define so that it will reach the compiler unaltered."""
|
|
s = _EscapeEnvironmentVariableExpansion(s)
|
|
s = _EscapeCommandLineArgumentForMSBuild(s)
|
|
s = _EscapeMSBuildSpecialCharacters(s)
|
|
# cl.exe replaces literal # characters with = in preprocesor definitions for
|
|
# some reason. Octal-encode to work around that.
|
|
s = s.replace('#', '\\%03o' % ord('#'))
|
|
return s
|
|
|
|
|
|
def _GenerateRulesForMSVS(p, output_dir, options, spec,
|
|
sources, excluded_sources,
|
|
actions_to_add):
|
|
"""Generate all the rules for a particular project.
|
|
|
|
Arguments:
|
|
p: the project
|
|
output_dir: directory to emit rules to
|
|
options: global options passed to the generator
|
|
spec: the specification for this project
|
|
sources: the set of all known source files in this project
|
|
excluded_sources: the set of sources excluded from normal processing
|
|
actions_to_add: deferred list of actions to add in
|
|
"""
|
|
rules = spec.get('rules', [])
|
|
rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
|
|
rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
|
|
|
|
# Handle rules that use a native rules file.
|
|
if rules_native:
|
|
_GenerateNativeRulesForMSVS(p, rules_native, output_dir, spec, options)
|
|
|
|
# Handle external rules (non-native rules).
|
|
if rules_external:
|
|
_GenerateExternalRules(rules_external, output_dir, spec,
|
|
sources, options, actions_to_add)
|
|
_AdjustSourcesForRules(rules, sources, excluded_sources, False)
|
|
|
|
|
|
def _AdjustSourcesForRules(rules, sources, excluded_sources, is_msbuild):
|
|
# Add outputs generated by each rule (if applicable).
|
|
for rule in rules:
|
|
# Add in the outputs from this rule.
|
|
trigger_files = _FindRuleTriggerFiles(rule, sources)
|
|
for trigger_file in trigger_files:
|
|
# Remove trigger_file from excluded_sources to let the rule be triggered
|
|
# (e.g. rule trigger ax_enums.idl is added to excluded_sources
|
|
# because it's also in an action's inputs in the same project)
|
|
excluded_sources.discard(_FixPath(trigger_file))
|
|
# Done if not processing outputs as sources.
|
|
if int(rule.get('process_outputs_as_sources', False)):
|
|
inputs, outputs = _RuleInputsAndOutputs(rule, trigger_file)
|
|
inputs = OrderedSet(_FixPaths(inputs))
|
|
outputs = OrderedSet(_FixPaths(outputs))
|
|
inputs.remove(_FixPath(trigger_file))
|
|
sources.update(inputs)
|
|
if not is_msbuild:
|
|
excluded_sources.update(inputs)
|
|
sources.update(outputs)
|
|
|
|
|
|
def _FilterActionsFromExcluded(excluded_sources, actions_to_add):
|
|
"""Take inputs with actions attached out of the list of exclusions.
|
|
|
|
Arguments:
|
|
excluded_sources: list of source files not to be built.
|
|
actions_to_add: dict of actions keyed on source file they're attached to.
|
|
Returns:
|
|
excluded_sources with files that have actions attached removed.
|
|
"""
|
|
must_keep = OrderedSet(_FixPaths(actions_to_add.keys()))
|
|
return [s for s in excluded_sources if s not in must_keep]
|
|
|
|
|
|
def _GetDefaultConfiguration(spec):
|
|
return spec['configurations'][spec['default_configuration']]
|
|
|
|
|
|
def _GetGuidOfProject(proj_path, spec):
|
|
"""Get the guid for the project.
|
|
|
|
Arguments:
|
|
proj_path: Path of the vcproj or vcxproj file to generate.
|
|
spec: The target dictionary containing the properties of the target.
|
|
Returns:
|
|
the guid.
|
|
Raises:
|
|
ValueError: if the specified GUID is invalid.
|
|
"""
|
|
# Pluck out the default configuration.
|
|
default_config = _GetDefaultConfiguration(spec)
|
|
# Decide the guid of the project.
|
|
guid = default_config.get('msvs_guid')
|
|
if guid:
|
|
if VALID_MSVS_GUID_CHARS.match(guid) is None:
|
|
raise ValueError('Invalid MSVS guid: "%s". Must match regex: "%s".' %
|
|
(guid, VALID_MSVS_GUID_CHARS.pattern))
|
|
guid = '{%s}' % guid
|
|
guid = guid or MSVSNew.MakeGuid(proj_path)
|
|
return guid
|
|
|
|
|
|
def _GetMsbuildToolsetOfProject(proj_path, spec, version):
|
|
"""Get the platform toolset for the project.
|
|
|
|
Arguments:
|
|
proj_path: Path of the vcproj or vcxproj file to generate.
|
|
spec: The target dictionary containing the properties of the target.
|
|
version: The MSVSVersion object.
|
|
Returns:
|
|
the platform toolset string or None.
|
|
"""
|
|
# Pluck out the default configuration.
|
|
default_config = _GetDefaultConfiguration(spec)
|
|
toolset = default_config.get('msbuild_toolset')
|
|
if not toolset and version.DefaultToolset():
|
|
toolset = version.DefaultToolset()
|
|
return toolset
|
|
|
|
|
|
def _GenerateProject(project, options, version, generator_flags):
|
|
"""Generates a vcproj file.
|
|
|
|
Arguments:
|
|
project: the MSVSProject object.
|
|
options: global generator options.
|
|
version: the MSVSVersion object.
|
|
generator_flags: dict of generator-specific flags.
|
|
Returns:
|
|
A list of source files that cannot be found on disk.
|
|
"""
|
|
default_config = _GetDefaultConfiguration(project.spec)
|
|
|
|
# Skip emitting anything if told to with msvs_existing_vcproj option.
|
|
if default_config.get('msvs_existing_vcproj'):
|
|
return []
|
|
|
|
if version.UsesVcxproj():
|
|
return _GenerateMSBuildProject(project, options, version, generator_flags)
|
|
else:
|
|
return _GenerateMSVSProject(project, options, version, generator_flags)
|
|
|
|
|
|
# TODO: Avoid code duplication with _ValidateSourcesForOSX in make.py.
|
|
def _ValidateSourcesForMSVSProject(spec, version):
|
|
"""Makes sure if duplicate basenames are not specified in the source list.
|
|
|
|
Arguments:
|
|
spec: The target dictionary containing the properties of the target.
|
|
version: The VisualStudioVersion object.
|
|
"""
|
|
# This validation should not be applied to MSVC2010 and later.
|
|
assert not version.UsesVcxproj()
|
|
|
|
# TODO: Check if MSVC allows this for loadable_module targets.
|
|
if spec.get('type', None) not in ('static_library', 'shared_library'):
|
|
return
|
|
sources = spec.get('sources', [])
|
|
basenames = {}
|
|
for source in sources:
|
|
name, ext = os.path.splitext(source)
|
|
is_compiled_file = ext in [
|
|
'.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.s', '.S']
|
|
if not is_compiled_file:
|
|
continue
|
|
basename = os.path.basename(name) # Don't include extension.
|
|
basenames.setdefault(basename, []).append(source)
|
|
|
|
error = ''
|
|
for basename, files in basenames.iteritems():
|
|
if len(files) > 1:
|
|
error += ' %s: %s\n' % (basename, ' '.join(files))
|
|
|
|
if error:
|
|
print('static library %s has several files with the same basename:\n' %
|
|
spec['target_name'] + error + 'MSVC08 cannot handle that.')
|
|
raise GypError('Duplicate basenames in sources section, see list above')
|
|
|
|
|
|
def _GenerateMSVSProject(project, options, version, generator_flags):
|
|
"""Generates a .vcproj file. It may create .rules and .user files too.
|
|
|
|
Arguments:
|
|
project: The project object we will generate the file for.
|
|
options: Global options passed to the generator.
|
|
version: The VisualStudioVersion object.
|
|
generator_flags: dict of generator-specific flags.
|
|
"""
|
|
spec = project.spec
|
|
gyp.common.EnsureDirExists(project.path)
|
|
|
|
platforms = _GetUniquePlatforms(spec)
|
|
p = MSVSProject.Writer(project.path, version, spec['target_name'],
|
|
project.guid, platforms)
|
|
|
|
# Get directory project file is in.
|
|
project_dir = os.path.split(project.path)[0]
|
|
gyp_path = _NormalizedSource(project.build_file)
|
|
relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
|
|
|
|
config_type = _GetMSVSConfigurationType(spec, project.build_file)
|
|
for config_name, config in spec['configurations'].iteritems():
|
|
_AddConfigurationToMSVSProject(p, spec, config_type, config_name, config)
|
|
|
|
# MSVC08 and prior version cannot handle duplicate basenames in the same
|
|
# target.
|
|
# TODO: Take excluded sources into consideration if possible.
|
|
_ValidateSourcesForMSVSProject(spec, version)
|
|
|
|
# Prepare list of sources and excluded sources.
|
|
gyp_file = os.path.split(project.build_file)[1]
|
|
sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
|
|
gyp_file)
|
|
|
|
# Add rules.
|
|
actions_to_add = {}
|
|
_GenerateRulesForMSVS(p, project_dir, options, spec,
|
|
sources, excluded_sources,
|
|
actions_to_add)
|
|
list_excluded = generator_flags.get('msvs_list_excluded_files', True)
|
|
sources, excluded_sources, excluded_idl = (
|
|
_AdjustSourcesAndConvertToFilterHierarchy(spec, options, project_dir,
|
|
sources, excluded_sources,
|
|
list_excluded, version))
|
|
|
|
# Add in files.
|
|
missing_sources = _VerifySourcesExist(sources, project_dir)
|
|
p.AddFiles(sources)
|
|
|
|
_AddToolFilesToMSVS(p, spec)
|
|
_HandlePreCompiledHeaders(p, sources, spec)
|
|
_AddActions(actions_to_add, spec, relative_path_of_gyp_file)
|
|
_AddCopies(actions_to_add, spec)
|
|
_WriteMSVSUserFile(project.path, version, spec)
|
|
|
|
# NOTE: this stanza must appear after all actions have been decided.
|
|
# Don't excluded sources with actions attached, or they won't run.
|
|
excluded_sources = _FilterActionsFromExcluded(
|
|
excluded_sources, actions_to_add)
|
|
_ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
|
|
list_excluded)
|
|
_AddAccumulatedActionsToMSVS(p, spec, actions_to_add)
|
|
|
|
# Write it out.
|
|
p.WriteIfChanged()
|
|
|
|
return missing_sources
|
|
|
|
|
|
def _GetUniquePlatforms(spec):
|
|
"""Returns the list of unique platforms for this spec, e.g ['win32', ...].
|
|
|
|
Arguments:
|
|
spec: The target dictionary containing the properties of the target.
|
|
Returns:
|
|
The MSVSUserFile object created.
|
|
"""
|
|
# Gather list of unique platforms.
|
|
platforms = OrderedSet()
|
|
for configuration in spec['configurations']:
|
|
platforms.add(_ConfigPlatform(spec['configurations'][configuration]))
|
|
platforms = list(platforms)
|
|
return platforms
|
|
|
|
|
|
def _CreateMSVSUserFile(proj_path, version, spec):
|
|
"""Generates a .user file for the user running this Gyp program.
|
|
|
|
Arguments:
|
|
proj_path: The path of the project file being created. The .user file
|
|
shares the same path (with an appropriate suffix).
|
|
version: The VisualStudioVersion object.
|
|
spec: The target dictionary containing the properties of the target.
|
|
Returns:
|
|
The MSVSUserFile object created.
|
|
"""
|
|
(domain, username) = _GetDomainAndUserName()
|
|
vcuser_filename = '.'.join([proj_path, domain, username, 'user'])
|
|
user_file = MSVSUserFile.Writer(vcuser_filename, version,
|
|
spec['target_name'])
|
|
return user_file
|
|
|
|
|
|
def _GetMSVSConfigurationType(spec, build_file):
|
|
"""Returns the configuration type for this project.
|
|
|
|
It's a number defined by Microsoft. May raise an exception.
|
|
|
|
Args:
|
|
spec: The target dictionary containing the properties of the target.
|
|
build_file: The path of the gyp file.
|
|
Returns:
|
|
An integer, the configuration type.
|
|
"""
|
|
try:
|
|
config_type = {
|
|
'executable': '1', # .exe
|
|
'shared_library': '2', # .dll
|
|
'loadable_module': '2', # .dll
|
|
'static_library': '4', # .lib
|
|
'none': '10', # Utility type
|
|
}[spec['type']]
|
|
except KeyError:
|
|
if spec.get('type'):
|
|
raise GypError('Target type %s is not a valid target type for '
|
|
'target %s in %s.' %
|
|
(spec['type'], spec['target_name'], build_file))
|
|
else:
|
|
raise GypError('Missing type field for target %s in %s.' %
|
|
(spec['target_name'], build_file))
|
|
return config_type
|
|
|
|
|
|
def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config):
|
|
"""Adds a configuration to the MSVS project.
|
|
|
|
Many settings in a vcproj file are specific to a configuration. This
|
|
function the main part of the vcproj file that's configuration specific.
|
|
|
|
Arguments:
|
|
p: The target project being generated.
|
|
spec: The target dictionary containing the properties of the target.
|
|
config_type: The configuration type, a number as defined by Microsoft.
|
|
config_name: The name of the configuration.
|
|
config: The dictionary that defines the special processing to be done
|
|
for this configuration.
|
|
"""
|
|
# Get the information for this configuration
|
|
include_dirs, midl_include_dirs, resource_include_dirs = \
|
|
_GetIncludeDirs(config)
|
|
libraries = _GetLibraries(spec)
|
|
library_dirs = _GetLibraryDirs(config)
|
|
out_file, vc_tool, _ = _GetOutputFilePathAndTool(spec, msbuild=False)
|
|
defines = _GetDefines(config)
|
|
defines = [_EscapeCppDefineForMSVS(d) for d in defines]
|
|
disabled_warnings = _GetDisabledWarnings(config)
|
|
prebuild = config.get('msvs_prebuild')
|
|
postbuild = config.get('msvs_postbuild')
|
|
def_file = _GetModuleDefinition(spec)
|
|
precompiled_header = config.get('msvs_precompiled_header')
|
|
|
|
# Prepare the list of tools as a dictionary.
|
|
tools = dict()
|
|
# Add in user specified msvs_settings.
|
|
msvs_settings = config.get('msvs_settings', {})
|
|
MSVSSettings.ValidateMSVSSettings(msvs_settings)
|
|
|
|
# Prevent default library inheritance from the environment.
|
|
_ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', ['$(NOINHERIT)'])
|
|
|
|
for tool in msvs_settings:
|
|
settings = config['msvs_settings'][tool]
|
|
for setting in settings:
|
|
_ToolAppend(tools, tool, setting, settings[setting])
|
|
# Add the information to the appropriate tool
|
|
_ToolAppend(tools, 'VCCLCompilerTool',
|
|
'AdditionalIncludeDirectories', include_dirs)
|
|
_ToolAppend(tools, 'VCMIDLTool',
|
|
'AdditionalIncludeDirectories', midl_include_dirs)
|
|
_ToolAppend(tools, 'VCResourceCompilerTool',
|
|
'AdditionalIncludeDirectories', resource_include_dirs)
|
|
# Add in libraries.
|
|
_ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', libraries)
|
|
_ToolAppend(tools, 'VCLinkerTool', 'AdditionalLibraryDirectories',
|
|
library_dirs)
|
|
if out_file:
|
|
_ToolAppend(tools, vc_tool, 'OutputFile', out_file, only_if_unset=True)
|
|
# Add defines.
|
|
_ToolAppend(tools, 'VCCLCompilerTool', 'PreprocessorDefinitions', defines)
|
|
_ToolAppend(tools, 'VCResourceCompilerTool', 'PreprocessorDefinitions',
|
|
defines)
|
|
# Change program database directory to prevent collisions.
|
|
_ToolAppend(tools, 'VCCLCompilerTool', 'ProgramDataBaseFileName',
|
|
'$(IntDir)$(ProjectName)\\vc80.pdb', only_if_unset=True)
|
|
# Add disabled warnings.
|
|
_ToolAppend(tools, 'VCCLCompilerTool',
|
|
'DisableSpecificWarnings', disabled_warnings)
|
|
# Add Pre-build.
|
|
_ToolAppend(tools, 'VCPreBuildEventTool', 'CommandLine', prebuild)
|
|
# Add Post-build.
|
|
_ToolAppend(tools, 'VCPostBuildEventTool', 'CommandLine', postbuild)
|
|
# Turn on precompiled headers if appropriate.
|
|
if precompiled_header:
|
|
precompiled_header = os.path.split(precompiled_header)[1]
|
|
_ToolAppend(tools, 'VCCLCompilerTool', 'UsePrecompiledHeader', '2')
|
|
_ToolAppend(tools, 'VCCLCompilerTool',
|
|
'PrecompiledHeaderThrough', precompiled_header)
|
|
_ToolAppend(tools, 'VCCLCompilerTool',
|
|
'ForcedIncludeFiles', precompiled_header)
|
|
# Loadable modules don't generate import libraries;
|
|
# tell dependent projects to not expect one.
|
|
if spec['type'] == 'loadable_module':
|
|
_ToolAppend(tools, 'VCLinkerTool', 'IgnoreImportLibrary', 'true')
|
|
# Set the module definition file if any.
|
|
if def_file:
|
|
_ToolAppend(tools, 'VCLinkerTool', 'ModuleDefinitionFile', def_file)
|
|
|
|
_AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name)
|
|
|
|
|
|
def _GetIncludeDirs(config):
|
|
"""Returns the list of directories to be used for #include directives.
|
|
|
|
Arguments:
|
|
config: The dictionary that defines the special processing to be done
|
|
for this configuration.
|
|
Returns:
|
|
The list of directory paths.
|
|
"""
|
|
# TODO(bradnelson): include_dirs should really be flexible enough not to
|
|
# require this sort of thing.
|
|
include_dirs = (
|
|
config.get('include_dirs', []) +
|
|
config.get('msvs_system_include_dirs', []))
|
|
midl_include_dirs = (
|
|
config.get('midl_include_dirs', []) +
|
|
config.get('msvs_system_include_dirs', []))
|
|
resource_include_dirs = config.get('resource_include_dirs', include_dirs)
|
|
include_dirs = _FixPaths(include_dirs)
|
|
midl_include_dirs = _FixPaths(midl_include_dirs)
|
|
resource_include_dirs = _FixPaths(resource_include_dirs)
|
|
return include_dirs, midl_include_dirs, resource_include_dirs
|
|
|
|
|
|
def _GetLibraryDirs(config):
|
|
"""Returns the list of directories to be used for library search paths.
|
|
|
|
Arguments:
|
|
config: The dictionary that defines the special processing to be done
|
|
for this configuration.
|
|
Returns:
|
|
The list of directory paths.
|
|
"""
|
|
|
|
library_dirs = config.get('library_dirs', [])
|
|
library_dirs = _FixPaths(library_dirs)
|
|
return library_dirs
|
|
|
|
|
|
def _GetLibraries(spec):
|
|
"""Returns the list of libraries for this configuration.
|
|
|
|
Arguments:
|
|
spec: The target dictionary containing the properties of the target.
|
|
Returns:
|
|
The list of directory paths.
|
|
"""
|
|
libraries = spec.get('libraries', [])
|
|
# Strip out -l, as it is not used on windows (but is needed so we can pass
|
|
# in libraries that are assumed to be in the default library path).
|
|
# Also remove duplicate entries, leaving only the last duplicate, while
|
|
# preserving order.
|
|
found = OrderedSet()
|
|
unique_libraries_list = []
|
|
for entry in reversed(libraries):
|
|
library = re.sub(r'^\-l', '', entry)
|
|
if not os.path.splitext(library)[1]:
|
|
library += '.lib'
|
|
if library not in found:
|
|
found.add(library)
|
|
unique_libraries_list.append(library)
|
|
unique_libraries_list.reverse()
|
|
return unique_libraries_list
|
|
|
|
|
|
def _GetOutputFilePathAndTool(spec, msbuild):
|
|
"""Returns the path and tool to use for this target.
|
|
|
|
Figures out the path of the file this spec will create and the name of
|
|
the VC tool that will create it.
|
|
|
|
Arguments:
|
|
spec: The target dictionary containing the properties of the target.
|
|
Returns:
|
|
A triple of (file path, name of the vc tool, name of the msbuild tool)
|
|
"""
|
|
# Select a name for the output file.
|
|
out_file = ''
|
|
vc_tool = ''
|
|
msbuild_tool = ''
|
|
output_file_map = {
|
|
'executable': ('VCLinkerTool', 'Link', '$(OutDir)', '.exe'),
|
|
'shared_library': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
|
|
'loadable_module': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'),
|
|
'static_library': ('VCLibrarianTool', 'Lib', '$(OutDir)lib\\', '.lib'),
|
|
}
|
|
output_file_props = output_file_map.get(spec['type'])
|
|
if output_file_props and int(spec.get('msvs_auto_output_file', 1)):
|
|
vc_tool, msbuild_tool, out_dir, suffix = output_file_props
|
|
if spec.get('standalone_static_library', 0):
|
|
out_dir = '$(OutDir)'
|
|
out_dir = spec.get('product_dir', out_dir)
|
|
product_extension = spec.get('product_extension')
|
|
if product_extension:
|
|
suffix = '.' + product_extension
|
|
elif msbuild:
|
|
suffix = '$(TargetExt)'
|
|
prefix = spec.get('product_prefix', '')
|
|
product_name = spec.get('product_name', '$(ProjectName)')
|
|
out_file = ntpath.join(out_dir, prefix + product_name + suffix)
|
|
return out_file, vc_tool, msbuild_tool
|
|
|
|
|
|
def _GetOutputTargetExt(spec):
|
|
"""Returns the extension for this target, including the dot
|
|
|
|
If product_extension is specified, set target_extension to this to avoid
|
|
MSB8012, returns None otherwise. Ignores any target_extension settings in
|
|
the input files.
|
|
|
|
Arguments:
|
|
spec: The target dictionary containing the properties of the target.
|
|
Returns:
|
|
A string with the extension, or None
|
|
"""
|
|
target_extension = spec.get('product_extension')
|
|
if target_extension:
|
|
return '.' + target_extension
|
|
return None
|
|
|
|
|
|
def _GetDefines(config):
|
|
"""Returns the list of preprocessor definitions for this configuation.
|
|
|
|
Arguments:
|
|
config: The dictionary that defines the special processing to be done
|
|
for this configuration.
|
|
Returns:
|
|
The list of preprocessor definitions.
|
|
"""
|
|
defines = []
|
|
for d in config.get('defines', []):
|
|
if type(d) == list:
|
|
fd = '='.join([str(dpart) for dpart in d])
|
|
else:
|
|
fd = str(d)
|
|
defines.append(fd)
|
|
return defines
|
|
|
|
|
|
def _GetDisabledWarnings(config):
|
|
return [str(i) for i in config.get('msvs_disabled_warnings', [])]
|
|
|
|
|
|
def _GetModuleDefinition(spec):
|
|
def_file = ''
|
|
if spec['type'] in ['shared_library', 'loadable_module', 'executable']:
|
|
def_files = [s for s in spec.get('sources', []) if s.endswith('.def')]
|
|
if len(def_files) == 1:
|
|
def_file = _FixPath(def_files[0])
|
|
elif def_files:
|
|
raise ValueError(
|
|
'Multiple module definition files in one target, target %s lists '
|
|
'multiple .def files: %s' % (
|
|
spec['target_name'], ' '.join(def_files)))
|
|
return def_file
|
|
|
|
|
|
def _ConvertToolsToExpectedForm(tools):
|
|
"""Convert tools to a form expected by Visual Studio.
|
|
|
|
Arguments:
|
|
tools: A dictionary of settings; the tool name is the key.
|
|
Returns:
|
|
A list of Tool objects.
|
|
"""
|
|
tool_list = []
|
|
for tool, settings in tools.iteritems():
|
|
# Collapse settings with lists.
|
|
settings_fixed = {}
|
|
for setting, value in settings.iteritems():
|
|
if type(value) == list:
|
|
if ((tool == 'VCLinkerTool' and
|
|
setting == 'AdditionalDependencies') or
|
|
setting == 'AdditionalOptions'):
|
|
settings_fixed[setting] = ' '.join(value)
|
|
else:
|
|
settings_fixed[setting] = ';'.join(value)
|
|
else:
|
|
settings_fixed[setting] = value
|
|
# Add in this tool.
|
|
tool_list.append(MSVSProject.Tool(tool, settings_fixed))
|
|
return tool_list
|
|
|
|
|
|
def _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name):
|
|
"""Add to the project file the configuration specified by config.
|
|
|
|
Arguments:
|
|
p: The target project being generated.
|
|
spec: the target project dict.
|
|
tools: A dictionary of settings; the tool name is the key.
|
|
config: The dictionary that defines the special processing to be done
|
|
for this configuration.
|
|
config_type: The configuration type, a number as defined by Microsoft.
|
|
config_name: The name of the configuration.
|
|
"""
|
|
attributes = _GetMSVSAttributes(spec, config, config_type)
|
|
# Add in this configuration.
|
|
tool_list = _ConvertToolsToExpectedForm(tools)
|
|
p.AddConfig(_ConfigFullName(config_name, config),
|
|
attrs=attributes, tools=tool_list)
|
|
|
|
|
|
def _GetMSVSAttributes(spec, config, config_type):
|
|
# Prepare configuration attributes.
|
|
prepared_attrs = {}
|
|
source_attrs = config.get('msvs_configuration_attributes', {})
|
|
for a in source_attrs:
|
|
prepared_attrs[a] = source_attrs[a]
|
|
# Add props files.
|
|
vsprops_dirs = config.get('msvs_props', [])
|
|
vsprops_dirs = _FixPaths(vsprops_dirs)
|
|
if vsprops_dirs:
|
|
prepared_attrs['InheritedPropertySheets'] = ';'.join(vsprops_dirs)
|
|
# Set configuration type.
|
|
prepared_attrs['ConfigurationType'] = config_type
|
|
output_dir = prepared_attrs.get('OutputDirectory',
|
|
'$(SolutionDir)$(ConfigurationName)')
|
|
prepared_attrs['OutputDirectory'] = _FixPath(output_dir) + '\\'
|
|
if 'IntermediateDirectory' not in prepared_attrs:
|
|
intermediate = '$(ConfigurationName)\\obj\\$(ProjectName)'
|
|
prepared_attrs['IntermediateDirectory'] = _FixPath(intermediate) + '\\'
|
|
else:
|
|
intermediate = _FixPath(prepared_attrs['IntermediateDirectory']) + '\\'
|
|
intermediate = MSVSSettings.FixVCMacroSlashes(intermediate)
|
|
prepared_attrs['IntermediateDirectory'] = intermediate
|
|
return prepared_attrs
|
|
|
|
|
|
def _AddNormalizedSources(sources_set, sources_array):
|
|
sources_set.update(_NormalizedSource(s) for s in sources_array)
|
|
|
|
|
|
def _PrepareListOfSources(spec, generator_flags, gyp_file):
|
|
"""Prepare list of sources and excluded sources.
|
|
|
|
Besides the sources specified directly in the spec, adds the gyp file so
|
|
that a change to it will cause a re-compile. Also adds appropriate sources
|
|
for actions and copies. Assumes later stage will un-exclude files which
|
|
have custom build steps attached.
|
|
|
|
Arguments:
|
|
spec: The target dictionary containing the properties of the target.
|
|
gyp_file: The name of the gyp file.
|
|
Returns:
|
|
A pair of (list of sources, list of excluded sources).
|
|
The sources will be relative to the gyp file.
|
|
"""
|
|
sources = OrderedSet()
|
|
_AddNormalizedSources(sources, spec.get('sources', []))
|
|
excluded_sources = OrderedSet()
|
|
# Add in the gyp file.
|
|
if not generator_flags.get('standalone'):
|
|
sources.add(gyp_file)
|
|
|
|
# Add in 'action' inputs and outputs.
|
|
for a in spec.get('actions', []):
|
|
inputs = a['inputs']
|
|
inputs = [_NormalizedSource(i) for i in inputs]
|
|
# Add all inputs to sources and excluded sources.
|
|
inputs = OrderedSet(inputs)
|
|
sources.update(inputs)
|
|
if not spec.get('msvs_external_builder'):
|
|
excluded_sources.update(inputs)
|
|
if int(a.get('process_outputs_as_sources', False)):
|
|
_AddNormalizedSources(sources, a.get('outputs', []))
|
|
# Add in 'copies' inputs and outputs.
|
|
for cpy in spec.get('copies', []):
|
|
_AddNormalizedSources(sources, cpy.get('files', []))
|
|
return (sources, excluded_sources)
|
|
|
|
|
|
def _AdjustSourcesAndConvertToFilterHierarchy(
|
|
spec, options, gyp_dir, sources, excluded_sources, list_excluded, version):
|
|
"""Adjusts the list of sources and excluded sources.
|
|
|
|
Also converts the sets to lists.
|
|
|
|
Arguments:
|
|
spec: The target dictionary containing the properties of the target.
|
|
options: Global generator options.
|
|
gyp_dir: The path to the gyp file being processed.
|
|
sources: A set of sources to be included for this project.
|
|
excluded_sources: A set of sources to be excluded for this project.
|
|
version: A MSVSVersion object.
|
|
Returns:
|
|
A trio of (list of sources, list of excluded sources,
|
|
path of excluded IDL file)
|
|
"""
|
|
# Exclude excluded sources coming into the generator.
|
|
excluded_sources.update(OrderedSet(spec.get('sources_excluded', [])))
|
|
# Add excluded sources into sources for good measure.
|
|
sources.update(excluded_sources)
|
|
# Convert to proper windows form.
|
|
# NOTE: sources goes from being a set to a list here.
|
|
# NOTE: excluded_sources goes from being a set to a list here.
|
|
sources = _FixPaths(sources)
|
|
# Convert to proper windows form.
|
|
excluded_sources = _FixPaths(excluded_sources)
|
|
|
|
excluded_idl = _IdlFilesHandledNonNatively(spec, sources)
|
|
|
|
precompiled_related = _GetPrecompileRelatedFiles(spec)
|
|
# Find the excluded ones, minus the precompiled header related ones.
|
|
fully_excluded = [i for i in excluded_sources if i not in precompiled_related]
|
|
|
|
# Convert to folders and the right slashes.
|
|
sources = [i.split('\\') for i in sources]
|
|
sources = _ConvertSourcesToFilterHierarchy(sources, excluded=fully_excluded,
|
|
list_excluded=list_excluded,
|
|
msvs_version=version)
|
|
|
|
# Prune filters with a single child to flatten ugly directory structures
|
|
# such as ../../src/modules/module1 etc.
|
|
if version.UsesVcxproj():
|
|
while all([isinstance(s, MSVSProject.Filter) for s in sources]) \
|
|
and len(set([s.name for s in sources])) == 1:
|
|
assert all([len(s.contents) == 1 for s in sources])
|
|
sources = [s.contents[0] for s in sources]
|
|
else:
|
|
while len(sources) == 1 and isinstance(sources[0], MSVSProject.Filter):
|
|
sources = sources[0].contents
|
|
|
|
return sources, excluded_sources, excluded_idl
|
|
|
|
|
|
def _IdlFilesHandledNonNatively(spec, sources):
|
|
# If any non-native rules use 'idl' as an extension exclude idl files.
|
|
# Gather a list here to use later.
|
|
using_idl = False
|
|
for rule in spec.get('rules', []):
|
|
if rule['extension'] == 'idl' and int(rule.get('msvs_external_rule', 0)):
|
|
using_idl = True
|
|
break
|
|
if using_idl:
|
|
excluded_idl = [i for i in sources if i.endswith('.idl')]
|
|
else:
|
|
excluded_idl = []
|
|
return excluded_idl
|
|
|
|
|
|
def _GetPrecompileRelatedFiles(spec):
|
|
# Gather a list of precompiled header related sources.
|
|
precompiled_related = []
|
|
for _, config in spec['configurations'].iteritems():
|
|
for k in precomp_keys:
|
|
f = config.get(k)
|
|
if f:
|
|
precompiled_related.append(_FixPath(f))
|
|
return precompiled_related
|
|
|
|
|
|
def _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
|
|
list_excluded):
|
|
exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
|
|
for file_name, excluded_configs in exclusions.iteritems():
|
|
if (not list_excluded and
|
|
len(excluded_configs) == len(spec['configurations'])):
|
|
# If we're not listing excluded files, then they won't appear in the
|
|
# project, so don't try to configure them to be excluded.
|
|
pass
|
|
else:
|
|
for config_name, config in excluded_configs:
|
|
p.AddFileConfig(file_name, _ConfigFullName(config_name, config),
|
|
{'ExcludedFromBuild': 'true'})
|
|
|
|
|
|
def _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl):
|
|
exclusions = {}
|
|
# Exclude excluded sources from being built.
|
|
for f in excluded_sources:
|
|
excluded_configs = []
|
|
for config_name, config in spec['configurations'].iteritems():
|
|
precomped = [_FixPath(config.get(i, '')) for i in precomp_keys]
|
|
# Don't do this for ones that are precompiled header related.
|
|
if f not in precomped:
|
|
excluded_configs.append((config_name, config))
|
|
exclusions[f] = excluded_configs
|
|
# If any non-native rules use 'idl' as an extension exclude idl files.
|
|
# Exclude them now.
|
|
for f in excluded_idl:
|
|
excluded_configs = []
|
|
for config_name, config in spec['configurations'].iteritems():
|
|
excluded_configs.append((config_name, config))
|
|
exclusions[f] = excluded_configs
|
|
return exclusions
|
|
|
|
|
|
def _AddToolFilesToMSVS(p, spec):
|
|
# Add in tool files (rules).
|
|
tool_files = OrderedSet()
|
|
for _, config in spec['configurations'].iteritems():
|
|
for f in config.get('msvs_tool_files', []):
|
|
tool_files.add(f)
|
|
for f in tool_files:
|
|
p.AddToolFile(f)
|
|
|
|
|
|
def _HandlePreCompiledHeaders(p, sources, spec):
|
|
# Pre-compiled header source stubs need a different compiler flag
|
|
# (generate precompiled header) and any source file not of the same
|
|
# kind (i.e. C vs. C++) as the precompiled header source stub needs
|
|
# to have use of precompiled headers disabled.
|
|
extensions_excluded_from_precompile = []
|
|
for config_name, config in spec['configurations'].iteritems():
|
|
source = config.get('msvs_precompiled_source')
|
|
if source:
|
|
source = _FixPath(source)
|
|
# UsePrecompiledHeader=1 for if using precompiled headers.
|
|
tool = MSVSProject.Tool('VCCLCompilerTool',
|
|
{'UsePrecompiledHeader': '1'})
|
|
p.AddFileConfig(source, _ConfigFullName(config_name, config),
|
|
{}, tools=[tool])
|
|
basename, extension = os.path.splitext(source)
|
|
if extension == '.c':
|
|
extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
|
|
else:
|
|
extensions_excluded_from_precompile = ['.c']
|
|
def DisableForSourceTree(source_tree):
|
|
for source in source_tree:
|
|
if isinstance(source, MSVSProject.Filter):
|
|
DisableForSourceTree(source.contents)
|
|
else:
|
|
basename, extension = os.path.splitext(source)
|
|
if extension in extensions_excluded_from_precompile:
|
|
for config_name, config in spec['configurations'].iteritems():
|
|
tool = MSVSProject.Tool('VCCLCompilerTool',
|
|
{'UsePrecompiledHeader': '0',
|
|
'ForcedIncludeFiles': '$(NOINHERIT)'})
|
|
p.AddFileConfig(_FixPath(source),
|
|
_ConfigFullName(config_name, config),
|
|
{}, tools=[tool])
|
|
# Do nothing if there was no precompiled source.
|
|
if extensions_excluded_from_precompile:
|
|
DisableForSourceTree(sources)
|
|
|
|
|
|
def _AddActions(actions_to_add, spec, relative_path_of_gyp_file):
|
|
# Add actions.
|
|
actions = spec.get('actions', [])
|
|
# Don't setup_env every time. When all the actions are run together in one
|
|
# batch file in VS, the PATH will grow too long.
|
|
# Membership in this set means that the cygwin environment has been set up,
|
|
# and does not need to be set up again.
|
|
have_setup_env = set()
|
|
for a in actions:
|
|
# Attach actions to the gyp file if nothing else is there.
|
|
inputs = a.get('inputs') or [relative_path_of_gyp_file]
|
|
attached_to = inputs[0]
|
|
need_setup_env = attached_to not in have_setup_env
|
|
cmd = _BuildCommandLineForRule(spec, a, has_input_path=False,
|
|
do_setup_env=need_setup_env)
|
|
have_setup_env.add(attached_to)
|
|
# Add the action.
|
|
_AddActionStep(actions_to_add,
|
|
inputs=inputs,
|
|
outputs=a.get('outputs', []),
|
|
description=a.get('message', a['action_name']),
|
|
command=cmd)
|
|
|
|
|
|
def _WriteMSVSUserFile(project_path, version, spec):
|
|
# Add run_as and test targets.
|
|
if 'run_as' in spec:
|
|
run_as = spec['run_as']
|
|
action = run_as.get('action', [])
|
|
environment = run_as.get('environment', [])
|
|
working_directory = run_as.get('working_directory', '.')
|
|
elif int(spec.get('test', 0)):
|
|
action = ['$(TargetPath)', '--gtest_print_time']
|
|
environment = []
|
|
working_directory = '.'
|
|
else:
|
|
return # Nothing to add
|
|
# Write out the user file.
|
|
user_file = _CreateMSVSUserFile(project_path, version, spec)
|
|
for config_name, c_data in spec['configurations'].iteritems():
|
|
user_file.AddDebugSettings(_ConfigFullName(config_name, c_data),
|
|
action, environment, working_directory)
|
|
user_file.WriteIfChanged()
|
|
|
|
|
|
def _AddCopies(actions_to_add, spec):
|
|
copies = _GetCopies(spec)
|
|
for inputs, outputs, cmd, description in copies:
|
|
_AddActionStep(actions_to_add, inputs=inputs, outputs=outputs,
|
|
description=description, command=cmd)
|
|
|
|
|
|
def _GetCopies(spec):
|
|
copies = []
|
|
# Add copies.
|
|
for cpy in spec.get('copies', []):
|
|
for src in cpy.get('files', []):
|
|
dst = os.path.join(cpy['destination'], os.path.basename(src))
|
|
# _AddCustomBuildToolForMSVS() will call _FixPath() on the inputs and
|
|
# outputs, so do the same for our generated command line.
|
|
if src.endswith('/'):
|
|
src_bare = src[:-1]
|
|
base_dir = posixpath.split(src_bare)[0]
|
|
outer_dir = posixpath.split(src_bare)[1]
|
|
cmd = 'cd "%s" && xcopy /e /f /y "%s" "%s\\%s\\"' % (
|
|
_FixPath(base_dir), outer_dir, _FixPath(dst), outer_dir)
|
|
copies.append(([src], ['dummy_copies', dst], cmd,
|
|
'Copying %s to %s' % (src, dst)))
|
|
else:
|
|
cmd = 'mkdir "%s" 2>nul & set ERRORLEVEL=0 & copy /Y "%s" "%s"' % (
|
|
_FixPath(cpy['destination']), _FixPath(src), _FixPath(dst))
|
|
copies.append(([src], [dst], cmd, 'Copying %s to %s' % (src, dst)))
|
|
return copies
|
|
|
|
|
|
def _GetPathDict(root, path):
|
|
# |path| will eventually be empty (in the recursive calls) if it was initially
|
|
# relative; otherwise it will eventually end up as '\', 'D:\', etc.
|
|
if not path or path.endswith(os.sep):
|
|
return root
|
|
parent, folder = os.path.split(path)
|
|
parent_dict = _GetPathDict(root, parent)
|
|
if folder not in parent_dict:
|
|
parent_dict[folder] = dict()
|
|
return parent_dict[folder]
|
|
|
|
|
|
def _DictsToFolders(base_path, bucket, flat):
|
|
# Convert to folders recursively.
|
|
children = []
|
|
for folder, contents in bucket.iteritems():
|
|
if type(contents) == dict:
|
|
folder_children = _DictsToFolders(os.path.join(base_path, folder),
|
|
contents, flat)
|
|
if flat:
|
|
children += folder_children
|
|
else:
|
|
folder_children = MSVSNew.MSVSFolder(os.path.join(base_path, folder),
|
|
name='(' + folder + ')',
|
|
entries=folder_children)
|
|
children.append(folder_children)
|
|
else:
|
|
children.append(contents)
|
|
return children
|
|
|
|
|
|
def _CollapseSingles(parent, node):
|
|
# Recursively explorer the tree of dicts looking for projects which are
|
|
# the sole item in a folder which has the same name as the project. Bring
|
|
# such projects up one level.
|
|
if (type(node) == dict and
|
|
len(node) == 1 and
|
|
node.keys()[0] == parent + '.vcproj'):
|
|
return node[node.keys()[0]]
|
|
if type(node) != dict:
|
|
return node
|
|
for child in node:
|
|
node[child] = _CollapseSingles(child, node[child])
|
|
return node
|
|
|
|
|
|
def _GatherSolutionFolders(sln_projects, project_objects, flat):
|
|
root = {}
|
|
# Convert into a tree of dicts on path.
|
|
for p in sln_projects:
|
|
gyp_file, target = gyp.common.ParseQualifiedTarget(p)[0:2]
|
|
gyp_dir = os.path.dirname(gyp_file)
|
|
path_dict = _GetPathDict(root, gyp_dir)
|
|
path_dict[target + '.vcproj'] = project_objects[p]
|
|
# Walk down from the top until we hit a folder that has more than one entry.
|
|
# In practice, this strips the top-level "src/" dir from the hierarchy in
|
|
# the solution.
|
|
while len(root) == 1 and type(root[root.keys()[0]]) == dict:
|
|
root = root[root.keys()[0]]
|
|
# Collapse singles.
|
|
root = _CollapseSingles('', root)
|
|
# Merge buckets until everything is a root entry.
|
|
return _DictsToFolders('', root, flat)
|
|
|
|
|
|
def _GetPathOfProject(qualified_target, spec, options, msvs_version):
|
|
default_config = _GetDefaultConfiguration(spec)
|
|
proj_filename = default_config.get('msvs_existing_vcproj')
|
|
if not proj_filename:
|
|
proj_filename = (spec['target_name'] + options.suffix +
|
|
msvs_version.ProjectExtension())
|
|
|
|
build_file = gyp.common.BuildFile(qualified_target)
|
|
proj_path = os.path.join(os.path.dirname(build_file), proj_filename)
|
|
fix_prefix = None
|
|
if options.generator_output:
|
|
project_dir_path = os.path.dirname(os.path.abspath(proj_path))
|
|
proj_path = os.path.join(options.generator_output, proj_path)
|
|
fix_prefix = gyp.common.RelativePath(project_dir_path,
|
|
os.path.dirname(proj_path))
|
|
return proj_path, fix_prefix
|
|
|
|
|
|
def _GetPlatformOverridesOfProject(spec):
|
|
# Prepare a dict indicating which project configurations are used for which
|
|
# solution configurations for this target.
|
|
config_platform_overrides = {}
|
|
for config_name, c in spec['configurations'].iteritems():
|
|
config_fullname = _ConfigFullName(config_name, c)
|
|
platform = c.get('msvs_target_platform', _ConfigPlatform(c))
|
|
fixed_config_fullname = '%s|%s' % (
|
|
_ConfigBaseName(config_name, _ConfigPlatform(c)), platform)
|
|
config_platform_overrides[config_fullname] = fixed_config_fullname
|
|
return config_platform_overrides
|
|
|
|
|
|
def _CreateProjectObjects(target_list, target_dicts, options, msvs_version):
|
|
"""Create a MSVSProject object for the targets found in target list.
|
|
|
|
Arguments:
|
|
target_list: the list of targets to generate project objects for.
|
|
target_dicts: the dictionary of specifications.
|
|
options: global generator options.
|
|
msvs_version: the MSVSVersion object.
|
|
Returns:
|
|
A set of created projects, keyed by target.
|
|
"""
|
|
global fixpath_prefix
|
|
# Generate each project.
|
|
projects = {}
|
|
for qualified_target in target_list:
|
|
spec = target_dicts[qualified_target]
|
|
if spec['toolset'] != 'target':
|
|
raise GypError(
|
|
'Multiple toolsets not supported in msvs build (target %s)' %
|
|
qualified_target)
|
|
proj_path, fixpath_prefix = _GetPathOfProject(qualified_target, spec,
|
|
options, msvs_version)
|
|
guid = _GetGuidOfProject(proj_path, spec)
|
|
overrides = _GetPlatformOverridesOfProject(spec)
|
|
build_file = gyp.common.BuildFile(qualified_target)
|
|
# Create object for this project.
|
|
obj = MSVSNew.MSVSProject(
|
|
proj_path,
|
|
name=spec['target_name'],
|
|
guid=guid,
|
|
spec=spec,
|
|
build_file=build_file,
|
|
config_platform_overrides=overrides,
|
|
fixpath_prefix=fixpath_prefix)
|
|
# Set project toolset if any (MS build only)
|
|
if msvs_version.UsesVcxproj():
|
|
obj.set_msbuild_toolset(
|
|
_GetMsbuildToolsetOfProject(proj_path, spec, msvs_version))
|
|
projects[qualified_target] = obj
|
|
# Set all the dependencies, but not if we are using an external builder like
|
|
# ninja
|
|
for project in projects.values():
|
|
if not project.spec.get('msvs_external_builder'):
|
|
deps = project.spec.get('dependencies', [])
|
|
deps = [projects[d] for d in deps]
|
|
project.set_dependencies(deps)
|
|
return projects
|
|
|
|
|
|
def _InitNinjaFlavor(params, target_list, target_dicts):
|
|
"""Initialize targets for the ninja flavor.
|
|
|
|
This sets up the necessary variables in the targets to generate msvs projects
|
|
that use ninja as an external builder. The variables in the spec are only set
|
|
if they have not been set. This allows individual specs to override the
|
|
default values initialized here.
|
|
Arguments:
|
|
params: Params provided to the generator.
|
|
target_list: List of target pairs: 'base/base.gyp:base'.
|
|
target_dicts: Dict of target properties keyed on target pair.
|
|
"""
|
|
for qualified_target in target_list:
|
|
spec = target_dicts[qualified_target]
|
|
if spec.get('msvs_external_builder'):
|
|
# The spec explicitly defined an external builder, so don't change it.
|
|
continue
|
|
|
|
path_to_ninja = spec.get('msvs_path_to_ninja', 'ninja.exe')
|
|
|
|
spec['msvs_external_builder'] = 'ninja'
|
|
if not spec.get('msvs_external_builder_out_dir'):
|
|
gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target)
|
|
gyp_dir = os.path.dirname(gyp_file)
|
|
configuration = '$(Configuration)'
|
|
if params.get('target_arch') == 'x64':
|
|
configuration += '_x64'
|
|
spec['msvs_external_builder_out_dir'] = os.path.join(
|
|
gyp.common.RelativePath(params['options'].toplevel_dir, gyp_dir),
|
|
ninja_generator.ComputeOutputDir(params),
|
|
configuration)
|
|
if not spec.get('msvs_external_builder_build_cmd'):
|
|
spec['msvs_external_builder_build_cmd'] = [
|
|
path_to_ninja,
|
|
'-C',
|
|
'$(OutDir)',
|
|
'$(ProjectName)',
|
|
]
|
|
if not spec.get('msvs_external_builder_clean_cmd'):
|
|
spec['msvs_external_builder_clean_cmd'] = [
|
|
path_to_ninja,
|
|
'-C',
|
|
'$(OutDir)',
|
|
'-tclean',
|
|
'$(ProjectName)',
|
|
]
|
|
|
|
|
|
def CalculateVariables(default_variables, params):
|
|
"""Generated variables that require params to be known."""
|
|
|
|
generator_flags = params.get('generator_flags', {})
|
|
|
|
# Select project file format version (if unset, default to auto detecting).
|
|
msvs_version = MSVSVersion.SelectVisualStudioVersion(
|
|
generator_flags.get('msvs_version', 'auto'))
|
|
# Stash msvs_version for later (so we don't have to probe the system twice).
|
|
params['msvs_version'] = msvs_version
|
|
|
|
# Set a variable so conditions can be based on msvs_version.
|
|
default_variables['MSVS_VERSION'] = msvs_version.ShortName()
|
|
|
|
# To determine processor word size on Windows, in addition to checking
|
|
# PROCESSOR_ARCHITECTURE (which reflects the word size of the current
|
|
# process), it is also necessary to check PROCESSOR_ARCITEW6432 (which
|
|
# contains the actual word size of the system when running thru WOW64).
|
|
if (os.environ.get('PROCESSOR_ARCHITECTURE', '').find('64') >= 0 or
|
|
os.environ.get('PROCESSOR_ARCHITEW6432', '').find('64') >= 0):
|
|
default_variables['MSVS_OS_BITS'] = 64
|
|
else:
|
|
default_variables['MSVS_OS_BITS'] = 32
|
|
|
|
if gyp.common.GetFlavor(params) == 'ninja':
|
|
default_variables['SHARED_INTERMEDIATE_DIR'] = '$(OutDir)gen'
|
|
|
|
|
|
def PerformBuild(data, configurations, params):
|
|
options = params['options']
|
|
msvs_version = params['msvs_version']
|
|
devenv = os.path.join(msvs_version.path, 'Common7', 'IDE', 'devenv.com')
|
|
|
|
for build_file, build_file_dict in data.iteritems():
|
|
(build_file_root, build_file_ext) = os.path.splitext(build_file)
|
|
if build_file_ext != '.gyp':
|
|
continue
|
|
sln_path = build_file_root + options.suffix + '.sln'
|
|
if options.generator_output:
|
|
sln_path = os.path.join(options.generator_output, sln_path)
|
|
|
|
for config in configurations:
|
|
arguments = [devenv, sln_path, '/Build', config]
|
|
print 'Building [%s]: %s' % (config, arguments)
|
|
rtn = subprocess.check_call(arguments)
|
|
|
|
|
|
def GenerateOutput(target_list, target_dicts, data, params):
|
|
"""Generate .sln and .vcproj files.
|
|
|
|
This is the entry point for this generator.
|
|
Arguments:
|
|
target_list: List of target pairs: 'base/base.gyp:base'.
|
|
target_dicts: Dict of target properties keyed on target pair.
|
|
data: Dictionary containing per .gyp data.
|
|
"""
|
|
global fixpath_prefix
|
|
|
|
options = params['options']
|
|
|
|
# Get the project file format version back out of where we stashed it in
|
|
# GeneratorCalculatedVariables.
|
|
msvs_version = params['msvs_version']
|
|
|
|
generator_flags = params.get('generator_flags', {})
|
|
|
|
# Optionally shard targets marked with 'msvs_shard': SHARD_COUNT.
|
|
(target_list, target_dicts) = MSVSUtil.ShardTargets(target_list, target_dicts)
|
|
|
|
# Optionally use the large PDB workaround for targets marked with
|
|
# 'msvs_large_pdb': 1.
|
|
(target_list, target_dicts) = MSVSUtil.InsertLargePdbShims(
|
|
target_list, target_dicts, generator_default_variables)
|
|
|
|
# Optionally configure each spec to use ninja as the external builder.
|
|
if params.get('flavor') == 'ninja':
|
|
_InitNinjaFlavor(params, target_list, target_dicts)
|
|
|
|
# Prepare the set of configurations.
|
|
configs = set()
|
|
for qualified_target in target_list:
|
|
spec = target_dicts[qualified_target]
|
|
for config_name, config in spec['configurations'].iteritems():
|
|
configs.add(_ConfigFullName(config_name, config))
|
|
configs = list(configs)
|
|
|
|
# Figure out all the projects that will be generated and their guids
|
|
project_objects = _CreateProjectObjects(target_list, target_dicts, options,
|
|
msvs_version)
|
|
|
|
# Generate each project.
|
|
missing_sources = []
|
|
for project in project_objects.values():
|
|
fixpath_prefix = project.fixpath_prefix
|
|
missing_sources.extend(_GenerateProject(project, options, msvs_version,
|
|
generator_flags))
|
|
fixpath_prefix = None
|
|
|
|
for build_file in data:
|
|
# Validate build_file extension
|
|
if not build_file.endswith('.gyp'):
|
|
continue
|
|
sln_path = os.path.splitext(build_file)[0] + options.suffix + '.sln'
|
|
if options.generator_output:
|
|
sln_path = os.path.join(options.generator_output, sln_path)
|
|
# Get projects in the solution, and their dependents.
|
|
sln_projects = gyp.common.BuildFileTargets(target_list, build_file)
|
|
sln_projects += gyp.common.DeepDependencyTargets(target_dicts, sln_projects)
|
|
# Create folder hierarchy.
|
|
root_entries = _GatherSolutionFolders(
|
|
sln_projects, project_objects, flat=msvs_version.FlatSolution())
|
|
# Create solution.
|
|
sln = MSVSNew.MSVSSolution(sln_path,
|
|
entries=root_entries,
|
|
variants=configs,
|
|
websiteProperties=False,
|
|
version=msvs_version)
|
|
sln.Write()
|
|
|
|
if missing_sources:
|
|
error_message = "Missing input files:\n" + \
|
|
'\n'.join(set(missing_sources))
|
|
if generator_flags.get('msvs_error_on_missing_sources', False):
|
|
raise GypError(error_message)
|
|
else:
|
|
print >> sys.stdout, "Warning: " + error_message
|
|
|
|
|
|
def _GenerateMSBuildFiltersFile(filters_path, source_files,
|
|
rule_dependencies, extension_to_rule_name):
|
|
"""Generate the filters file.
|
|
|
|
This file is used by Visual Studio to organize the presentation of source
|
|
files into folders.
|
|
|
|
Arguments:
|
|
filters_path: The path of the file to be created.
|
|
source_files: The hierarchical structure of all the sources.
|
|
extension_to_rule_name: A dictionary mapping file extensions to rules.
|
|
"""
|
|
filter_group = []
|
|
source_group = []
|
|
_AppendFiltersForMSBuild('', source_files, rule_dependencies,
|
|
extension_to_rule_name, filter_group, source_group)
|
|
if filter_group:
|
|
content = ['Project',
|
|
{'ToolsVersion': '4.0',
|
|
'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
|
|
},
|
|
['ItemGroup'] + filter_group,
|
|
['ItemGroup'] + source_group
|
|
]
|
|
easy_xml.WriteXmlIfChanged(content, filters_path, pretty=True, win32=True)
|
|
elif os.path.exists(filters_path):
|
|
# We don't need this filter anymore. Delete the old filter file.
|
|
os.unlink(filters_path)
|
|
|
|
|
|
def _AppendFiltersForMSBuild(parent_filter_name, sources, rule_dependencies,
|
|
extension_to_rule_name,
|
|
filter_group, source_group):
|
|
"""Creates the list of filters and sources to be added in the filter file.
|
|
|
|
Args:
|
|
parent_filter_name: The name of the filter under which the sources are
|
|
found.
|
|
sources: The hierarchy of filters and sources to process.
|
|
extension_to_rule_name: A dictionary mapping file extensions to rules.
|
|
filter_group: The list to which filter entries will be appended.
|
|
source_group: The list to which source entries will be appeneded.
|
|
"""
|
|
for source in sources:
|
|
if isinstance(source, MSVSProject.Filter):
|
|
# We have a sub-filter. Create the name of that sub-filter.
|
|
if not parent_filter_name:
|
|
filter_name = source.name
|
|
else:
|
|
filter_name = '%s\\%s' % (parent_filter_name, source.name)
|
|
# Add the filter to the group.
|
|
filter_group.append(
|
|
['Filter', {'Include': filter_name},
|
|
['UniqueIdentifier', MSVSNew.MakeGuid(source.name)]])
|
|
# Recurse and add its dependents.
|
|
_AppendFiltersForMSBuild(filter_name, source.contents,
|
|
rule_dependencies, extension_to_rule_name,
|
|
filter_group, source_group)
|
|
else:
|
|
# It's a source. Create a source entry.
|
|
_, element = _MapFileToMsBuildSourceType(source, rule_dependencies,
|
|
extension_to_rule_name)
|
|
source_entry = [element, {'Include': source}]
|
|
# Specify the filter it is part of, if any.
|
|
if parent_filter_name:
|
|
source_entry.append(['Filter', parent_filter_name])
|
|
source_group.append(source_entry)
|
|
|
|
|
|
def _MapFileToMsBuildSourceType(source, rule_dependencies,
|
|
extension_to_rule_name):
|
|
"""Returns the group and element type of the source file.
|
|
|
|
Arguments:
|
|
source: The source file name.
|
|
extension_to_rule_name: A dictionary mapping file extensions to rules.
|
|
|
|
Returns:
|
|
A pair of (group this file should be part of, the label of element)
|
|
"""
|
|
_, ext = os.path.splitext(source)
|
|
if ext in extension_to_rule_name:
|
|
group = 'rule'
|
|
element = extension_to_rule_name[ext]
|
|
elif ext in ['.cc', '.cpp', '.c', '.cxx', '.mm']:
|
|
group = 'compile'
|
|
element = 'ClCompile'
|
|
elif ext in ['.h', '.hxx']:
|
|
group = 'include'
|
|
element = 'ClInclude'
|
|
elif ext == '.rc':
|
|
group = 'resource'
|
|
element = 'ResourceCompile'
|
|
elif ext == '.asm':
|
|
group = 'masm'
|
|
element = 'MASM'
|
|
elif ext == '.idl':
|
|
group = 'midl'
|
|
element = 'Midl'
|
|
elif source in rule_dependencies:
|
|
group = 'rule_dependency'
|
|
element = 'CustomBuild'
|
|
else:
|
|
group = 'none'
|
|
element = 'None'
|
|
return (group, element)
|
|
|
|
|
|
def _GenerateRulesForMSBuild(output_dir, options, spec,
|
|
sources, excluded_sources,
|
|
props_files_of_rules, targets_files_of_rules,
|
|
actions_to_add, rule_dependencies,
|
|
extension_to_rule_name):
|
|
# MSBuild rules are implemented using three files: an XML file, a .targets
|
|
# file and a .props file.
|
|
# See http://blogs.msdn.com/b/vcblog/archive/2010/04/21/quick-help-on-vs2010-custom-build-rule.aspx
|
|
# for more details.
|
|
rules = spec.get('rules', [])
|
|
rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
|
|
rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
|
|
|
|
msbuild_rules = []
|
|
for rule in rules_native:
|
|
# Skip a rule with no action and no inputs.
|
|
if 'action' not in rule and not rule.get('rule_sources', []):
|
|
continue
|
|
msbuild_rule = MSBuildRule(rule, spec)
|
|
msbuild_rules.append(msbuild_rule)
|
|
rule_dependencies.update(msbuild_rule.additional_dependencies.split(';'))
|
|
extension_to_rule_name[msbuild_rule.extension] = msbuild_rule.rule_name
|
|
if msbuild_rules:
|
|
base = spec['target_name'] + options.suffix
|
|
props_name = base + '.props'
|
|
targets_name = base + '.targets'
|
|
xml_name = base + '.xml'
|
|
|
|
props_files_of_rules.add(props_name)
|
|
targets_files_of_rules.add(targets_name)
|
|
|
|
props_path = os.path.join(output_dir, props_name)
|
|
targets_path = os.path.join(output_dir, targets_name)
|
|
xml_path = os.path.join(output_dir, xml_name)
|
|
|
|
_GenerateMSBuildRulePropsFile(props_path, msbuild_rules)
|
|
_GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules)
|
|
_GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules)
|
|
|
|
if rules_external:
|
|
_GenerateExternalRules(rules_external, output_dir, spec,
|
|
sources, options, actions_to_add)
|
|
_AdjustSourcesForRules(rules, sources, excluded_sources, True)
|
|
|
|
|
|
class MSBuildRule(object):
|
|
"""Used to store information used to generate an MSBuild rule.
|
|
|
|
Attributes:
|
|
rule_name: The rule name, sanitized to use in XML.
|
|
target_name: The name of the target.
|
|
after_targets: The name of the AfterTargets element.
|
|
before_targets: The name of the BeforeTargets element.
|
|
depends_on: The name of the DependsOn element.
|
|
compute_output: The name of the ComputeOutput element.
|
|
dirs_to_make: The name of the DirsToMake element.
|
|
inputs: The name of the _inputs element.
|
|
tlog: The name of the _tlog element.
|
|
extension: The extension this rule applies to.
|
|
description: The message displayed when this rule is invoked.
|
|
additional_dependencies: A string listing additional dependencies.
|
|
outputs: The outputs of this rule.
|
|
command: The command used to run the rule.
|
|
"""
|
|
|
|
def __init__(self, rule, spec):
|
|
self.display_name = rule['rule_name']
|
|
# Assure that the rule name is only characters and numbers
|
|
self.rule_name = re.sub(r'\W', '_', self.display_name)
|
|
# Create the various element names, following the example set by the
|
|
# Visual Studio 2008 to 2010 conversion. I don't know if VS2010
|
|
# is sensitive to the exact names.
|
|
self.target_name = '_' + self.rule_name
|
|
self.after_targets = self.rule_name + 'AfterTargets'
|
|
self.before_targets = self.rule_name + 'BeforeTargets'
|
|
self.depends_on = self.rule_name + 'DependsOn'
|
|
self.compute_output = 'Compute%sOutput' % self.rule_name
|
|
self.dirs_to_make = self.rule_name + 'DirsToMake'
|
|
self.inputs = self.rule_name + '_inputs'
|
|
self.tlog = self.rule_name + '_tlog'
|
|
self.extension = rule['extension']
|
|
if not self.extension.startswith('.'):
|
|
self.extension = '.' + self.extension
|
|
|
|
self.description = MSVSSettings.ConvertVCMacrosToMSBuild(
|
|
rule.get('message', self.rule_name))
|
|
old_additional_dependencies = _FixPaths(rule.get('inputs', []))
|
|
self.additional_dependencies = (
|
|
';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
|
|
for i in old_additional_dependencies]))
|
|
old_outputs = _FixPaths(rule.get('outputs', []))
|
|
self.outputs = ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i)
|
|
for i in old_outputs])
|
|
old_command = _BuildCommandLineForRule(spec, rule, has_input_path=True,
|
|
do_setup_env=True)
|
|
self.command = MSVSSettings.ConvertVCMacrosToMSBuild(old_command)
|
|
|
|
|
|
def _GenerateMSBuildRulePropsFile(props_path, msbuild_rules):
|
|
"""Generate the .props file."""
|
|
content = ['Project',
|
|
{'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'}]
|
|
for rule in msbuild_rules:
|
|
content.extend([
|
|
['PropertyGroup',
|
|
{'Condition': "'$(%s)' == '' and '$(%s)' == '' and "
|
|
"'$(ConfigurationType)' != 'Makefile'" % (rule.before_targets,
|
|
rule.after_targets)
|
|
},
|
|
[rule.before_targets, 'Midl'],
|
|
[rule.after_targets, 'CustomBuild'],
|
|
],
|
|
['PropertyGroup',
|
|
[rule.depends_on,
|
|
{'Condition': "'$(ConfigurationType)' != 'Makefile'"},
|
|
'_SelectedFiles;$(%s)' % rule.depends_on
|
|
],
|
|
],
|
|
['ItemDefinitionGroup',
|
|
[rule.rule_name,
|
|
['CommandLineTemplate', rule.command],
|
|
['Outputs', rule.outputs],
|
|
['ExecutionDescription', rule.description],
|
|
['AdditionalDependencies', rule.additional_dependencies],
|
|
],
|
|
]
|
|
])
|
|
easy_xml.WriteXmlIfChanged(content, props_path, pretty=True, win32=True)
|
|
|
|
|
|
def _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules):
|
|
"""Generate the .targets file."""
|
|
content = ['Project',
|
|
{'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'
|
|
}
|
|
]
|
|
item_group = [
|
|
'ItemGroup',
|
|
['PropertyPageSchema',
|
|
{'Include': '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'}
|
|
]
|
|
]
|
|
for rule in msbuild_rules:
|
|
item_group.append(
|
|
['AvailableItemName',
|
|
{'Include': rule.rule_name},
|
|
['Targets', rule.target_name],
|
|
])
|
|
content.append(item_group)
|
|
|
|
for rule in msbuild_rules:
|
|
content.append(
|
|
['UsingTask',
|
|
{'TaskName': rule.rule_name,
|
|
'TaskFactory': 'XamlTaskFactory',
|
|
'AssemblyName': 'Microsoft.Build.Tasks.v4.0'
|
|
},
|
|
['Task', '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'],
|
|
])
|
|
for rule in msbuild_rules:
|
|
rule_name = rule.rule_name
|
|
target_outputs = '%%(%s.Outputs)' % rule_name
|
|
target_inputs = ('%%(%s.Identity);%%(%s.AdditionalDependencies);'
|
|
'$(MSBuildProjectFile)') % (rule_name, rule_name)
|
|
rule_inputs = '%%(%s.Identity)' % rule_name
|
|
extension_condition = ("'%(Extension)'=='.obj' or "
|
|
"'%(Extension)'=='.res' or "
|
|
"'%(Extension)'=='.rsc' or "
|
|
"'%(Extension)'=='.lib'")
|
|
remove_section = [
|
|
'ItemGroup',
|
|
{'Condition': "'@(SelectedFiles)' != ''"},
|
|
[rule_name,
|
|
{'Remove': '@(%s)' % rule_name,
|
|
'Condition': "'%(Identity)' != '@(SelectedFiles)'"
|
|
}
|
|
]
|
|
]
|
|
inputs_section = [
|
|
'ItemGroup',
|
|
[rule.inputs, {'Include': '%%(%s.AdditionalDependencies)' % rule_name}]
|
|
]
|
|
logging_section = [
|
|
'ItemGroup',
|
|
[rule.tlog,
|
|
{'Include': '%%(%s.Outputs)' % rule_name,
|
|
'Condition': ("'%%(%s.Outputs)' != '' and "
|
|
"'%%(%s.ExcludedFromBuild)' != 'true'" %
|
|
(rule_name, rule_name))
|
|
},
|
|
['Source', "@(%s, '|')" % rule_name],
|
|
['Inputs', "@(%s -> '%%(Fullpath)', ';')" % rule.inputs],
|
|
],
|
|
]
|
|
message_section = [
|
|
'Message',
|
|
{'Importance': 'High',
|
|
'Text': '%%(%s.ExecutionDescription)' % rule_name
|
|
}
|
|
]
|
|
write_tlog_section = [
|
|
'WriteLinesToFile',
|
|
{'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
|
|
"'true'" % (rule.tlog, rule.tlog),
|
|
'File': '$(IntDir)$(ProjectName).write.1.tlog',
|
|
'Lines': "^%%(%s.Source);@(%s->'%%(Fullpath)')" % (rule.tlog,
|
|
rule.tlog)
|
|
}
|
|
]
|
|
read_tlog_section = [
|
|
'WriteLinesToFile',
|
|
{'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
|
|
"'true'" % (rule.tlog, rule.tlog),
|
|
'File': '$(IntDir)$(ProjectName).read.1.tlog',
|
|
'Lines': "^%%(%s.Source);%%(%s.Inputs)" % (rule.tlog, rule.tlog)
|
|
}
|
|
]
|
|
command_and_input_section = [
|
|
rule_name,
|
|
{'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != "
|
|
"'true'" % (rule_name, rule_name),
|
|
'EchoOff': 'true',
|
|
'StandardOutputImportance': 'High',
|
|
'StandardErrorImportance': 'High',
|
|
'CommandLineTemplate': '%%(%s.CommandLineTemplate)' % rule_name,
|
|
'AdditionalOptions': '%%(%s.AdditionalOptions)' % rule_name,
|
|
'Inputs': rule_inputs
|
|
}
|
|
]
|
|
content.extend([
|
|
['Target',
|
|
{'Name': rule.target_name,
|
|
'BeforeTargets': '$(%s)' % rule.before_targets,
|
|
'AfterTargets': '$(%s)' % rule.after_targets,
|
|
'Condition': "'@(%s)' != ''" % rule_name,
|
|
'DependsOnTargets': '$(%s);%s' % (rule.depends_on,
|
|
rule.compute_output),
|
|
'Outputs': target_outputs,
|
|
'Inputs': target_inputs
|
|
},
|
|
remove_section,
|
|
inputs_section,
|
|
logging_section,
|
|
message_section,
|
|
write_tlog_section,
|
|
read_tlog_section,
|
|
command_and_input_section,
|
|
],
|
|
['PropertyGroup',
|
|
['ComputeLinkInputsTargets',
|
|
'$(ComputeLinkInputsTargets);',
|
|
'%s;' % rule.compute_output
|
|
],
|
|
['ComputeLibInputsTargets',
|
|
'$(ComputeLibInputsTargets);',
|
|
'%s;' % rule.compute_output
|
|
],
|
|
],
|
|
['Target',
|
|
{'Name': rule.compute_output,
|
|
'Condition': "'@(%s)' != ''" % rule_name
|
|
},
|
|
['ItemGroup',
|
|
[rule.dirs_to_make,
|
|
{'Condition': "'@(%s)' != '' and "
|
|
"'%%(%s.ExcludedFromBuild)' != 'true'" % (rule_name, rule_name),
|
|
'Include': '%%(%s.Outputs)' % rule_name
|
|
}
|
|
],
|
|
['Link',
|
|
{'Include': '%%(%s.Identity)' % rule.dirs_to_make,
|
|
'Condition': extension_condition
|
|
}
|
|
],
|
|
['Lib',
|
|
{'Include': '%%(%s.Identity)' % rule.dirs_to_make,
|
|
'Condition': extension_condition
|
|
}
|
|
],
|
|
['ImpLib',
|
|
{'Include': '%%(%s.Identity)' % rule.dirs_to_make,
|
|
'Condition': extension_condition
|
|
}
|
|
],
|
|
],
|
|
['MakeDir',
|
|
{'Directories': ("@(%s->'%%(RootDir)%%(Directory)')" %
|
|
rule.dirs_to_make)
|
|
}
|
|
]
|
|
],
|
|
])
|
|
easy_xml.WriteXmlIfChanged(content, targets_path, pretty=True, win32=True)
|
|
|
|
|
|
def _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules):
|
|
# Generate the .xml file
|
|
content = [
|
|
'ProjectSchemaDefinitions',
|
|
{'xmlns': ('clr-namespace:Microsoft.Build.Framework.XamlTypes;'
|
|
'assembly=Microsoft.Build.Framework'),
|
|
'xmlns:x': 'http://schemas.microsoft.com/winfx/2006/xaml',
|
|
'xmlns:sys': 'clr-namespace:System;assembly=mscorlib',
|
|
'xmlns:transformCallback':
|
|
'Microsoft.Cpp.Dev10.ConvertPropertyCallback'
|
|
}
|
|
]
|
|
for rule in msbuild_rules:
|
|
content.extend([
|
|
['Rule',
|
|
{'Name': rule.rule_name,
|
|
'PageTemplate': 'tool',
|
|
'DisplayName': rule.display_name,
|
|
'Order': '200'
|
|
},
|
|
['Rule.DataSource',
|
|
['DataSource',
|
|
{'Persistence': 'ProjectFile',
|
|
'ItemType': rule.rule_name
|
|
}
|
|
]
|
|
],
|
|
['Rule.Categories',
|
|
['Category',
|
|
{'Name': 'General'},
|
|
['Category.DisplayName',
|
|
['sys:String', 'General'],
|
|
],
|
|
],
|
|
['Category',
|
|
{'Name': 'Command Line',
|
|
'Subtype': 'CommandLine'
|
|
},
|
|
['Category.DisplayName',
|
|
['sys:String', 'Command Line'],
|
|
],
|
|
],
|
|
],
|
|
['StringListProperty',
|
|
{'Name': 'Inputs',
|
|
'Category': 'Command Line',
|
|
'IsRequired': 'true',
|
|
'Switch': ' '
|
|
},
|
|
['StringListProperty.DataSource',
|
|
['DataSource',
|
|
{'Persistence': 'ProjectFile',
|
|
'ItemType': rule.rule_name,
|
|
'SourceType': 'Item'
|
|
}
|
|
]
|
|
],
|
|
],
|
|
['StringProperty',
|
|
{'Name': 'CommandLineTemplate',
|
|
'DisplayName': 'Command Line',
|
|
'Visible': 'False',
|
|
'IncludeInCommandLine': 'False'
|
|
}
|
|
],
|
|
['DynamicEnumProperty',
|
|
{'Name': rule.before_targets,
|
|
'Category': 'General',
|
|
'EnumProvider': 'Targets',
|
|
'IncludeInCommandLine': 'False'
|
|
},
|
|
['DynamicEnumProperty.DisplayName',
|
|
['sys:String', 'Execute Before'],
|
|
],
|
|
['DynamicEnumProperty.Description',
|
|
['sys:String', 'Specifies the targets for the build customization'
|
|
' to run before.'
|
|
],
|
|
],
|
|
['DynamicEnumProperty.ProviderSettings',
|
|
['NameValuePair',
|
|
{'Name': 'Exclude',
|
|
'Value': '^%s|^Compute' % rule.before_targets
|
|
}
|
|
]
|
|
],
|
|
['DynamicEnumProperty.DataSource',
|
|
['DataSource',
|
|
{'Persistence': 'ProjectFile',
|
|
'HasConfigurationCondition': 'true'
|
|
}
|
|
]
|
|
],
|
|
],
|
|
['DynamicEnumProperty',
|
|
{'Name': rule.after_targets,
|
|
'Category': 'General',
|
|
'EnumProvider': 'Targets',
|
|
'IncludeInCommandLine': 'False'
|
|
},
|
|
['DynamicEnumProperty.DisplayName',
|
|
['sys:String', 'Execute After'],
|
|
],
|
|
['DynamicEnumProperty.Description',
|
|
['sys:String', ('Specifies the targets for the build customization'
|
|
' to run after.')
|
|
],
|
|
],
|
|
['DynamicEnumProperty.ProviderSettings',
|
|
['NameValuePair',
|
|
{'Name': 'Exclude',
|
|
'Value': '^%s|^Compute' % rule.after_targets
|
|
}
|
|
]
|
|
],
|
|
['DynamicEnumProperty.DataSource',
|
|
['DataSource',
|
|
{'Persistence': 'ProjectFile',
|
|
'ItemType': '',
|
|
'HasConfigurationCondition': 'true'
|
|
}
|
|
]
|
|
],
|
|
],
|
|
['StringListProperty',
|
|
{'Name': 'Outputs',
|
|
'DisplayName': 'Outputs',
|
|
'Visible': 'False',
|
|
'IncludeInCommandLine': 'False'
|
|
}
|
|
],
|
|
['StringProperty',
|
|
{'Name': 'ExecutionDescription',
|
|
'DisplayName': 'Execution Description',
|
|
'Visible': 'False',
|
|
'IncludeInCommandLine': 'False'
|
|
}
|
|
],
|
|
['StringListProperty',
|
|
{'Name': 'AdditionalDependencies',
|
|
'DisplayName': 'Additional Dependencies',
|
|
'IncludeInCommandLine': 'False',
|
|
'Visible': 'false'
|
|
}
|
|
],
|
|
['StringProperty',
|
|
{'Subtype': 'AdditionalOptions',
|
|
'Name': 'AdditionalOptions',
|
|
'Category': 'Command Line'
|
|
},
|
|
['StringProperty.DisplayName',
|
|
['sys:String', 'Additional Options'],
|
|
],
|
|
['StringProperty.Description',
|
|
['sys:String', 'Additional Options'],
|
|
],
|
|
],
|
|
],
|
|
['ItemType',
|
|
{'Name': rule.rule_name,
|
|
'DisplayName': rule.display_name
|
|
}
|
|
],
|
|
['FileExtension',
|
|
{'Name': '*' + rule.extension,
|
|
'ContentType': rule.rule_name
|
|
}
|
|
],
|
|
['ContentType',
|
|
{'Name': rule.rule_name,
|
|
'DisplayName': '',
|
|
'ItemType': rule.rule_name
|
|
}
|
|
]
|
|
])
|
|
easy_xml.WriteXmlIfChanged(content, xml_path, pretty=True, win32=True)
|
|
|
|
|
|
def _GetConfigurationAndPlatform(name, settings):
|
|
configuration = name.rsplit('_', 1)[0]
|
|
platform = settings.get('msvs_configuration_platform', 'Win32')
|
|
return (configuration, platform)
|
|
|
|
|
|
def _GetConfigurationCondition(name, settings):
|
|
return (r"'$(Configuration)|$(Platform)'=='%s|%s'" %
|
|
_GetConfigurationAndPlatform(name, settings))
|
|
|
|
|
|
def _GetMSBuildProjectConfigurations(configurations):
|
|
group = ['ItemGroup', {'Label': 'ProjectConfigurations'}]
|
|
for (name, settings) in sorted(configurations.iteritems()):
|
|
configuration, platform = _GetConfigurationAndPlatform(name, settings)
|
|
designation = '%s|%s' % (configuration, platform)
|
|
group.append(
|
|
['ProjectConfiguration', {'Include': designation},
|
|
['Configuration', configuration],
|
|
['Platform', platform]])
|
|
return [group]
|
|
|
|
|
|
def _GetMSBuildGlobalProperties(spec, guid, gyp_file_name):
|
|
namespace = os.path.splitext(gyp_file_name)[0]
|
|
properties = [
|
|
['PropertyGroup', {'Label': 'Globals'},
|
|
['ProjectGuid', guid],
|
|
['Keyword', 'Win32Proj'],
|
|
['RootNamespace', namespace],
|
|
['IgnoreWarnCompileDuplicatedFilename', 'true'],
|
|
]
|
|
]
|
|
|
|
if os.environ.get('PROCESSOR_ARCHITECTURE') == 'AMD64' or \
|
|
os.environ.get('PROCESSOR_ARCHITEW6432') == 'AMD64':
|
|
properties[0].append(['PreferredToolArchitecture', 'x64'])
|
|
|
|
if spec.get('msvs_enable_winrt'):
|
|
properties[0].append(['DefaultLanguage', 'en-US'])
|
|
properties[0].append(['AppContainerApplication', 'true'])
|
|
if spec.get('msvs_application_type_revision'):
|
|
app_type_revision = spec.get('msvs_application_type_revision')
|
|
properties[0].append(['ApplicationTypeRevision', app_type_revision])
|
|
else:
|
|
properties[0].append(['ApplicationTypeRevision', '8.1'])
|
|
|
|
if spec.get('msvs_target_platform_version'):
|
|
target_platform_version = spec.get('msvs_target_platform_version')
|
|
properties[0].append(['WindowsTargetPlatformVersion',
|
|
target_platform_version])
|
|
if spec.get('msvs_target_platform_minversion'):
|
|
target_platform_minversion = spec.get('msvs_target_platform_minversion')
|
|
properties[0].append(['WindowsTargetPlatformMinVersion',
|
|
target_platform_minversion])
|
|
else:
|
|
properties[0].append(['WindowsTargetPlatformMinVersion',
|
|
target_platform_version])
|
|
if spec.get('msvs_enable_winphone'):
|
|
properties[0].append(['ApplicationType', 'Windows Phone'])
|
|
else:
|
|
properties[0].append(['ApplicationType', 'Windows Store'])
|
|
|
|
platform_name = None
|
|
msvs_windows_target_platform_version = None
|
|
for configuration in spec['configurations'].itervalues():
|
|
platform_name = platform_name or _ConfigPlatform(configuration)
|
|
msvs_windows_target_platform_version = \
|
|
msvs_windows_target_platform_version or \
|
|
_ConfigWindowsTargetPlatformVersion(configuration)
|
|
if platform_name and msvs_windows_target_platform_version:
|
|
break
|
|
|
|
if platform_name == 'ARM':
|
|
properties[0].append(['WindowsSDKDesktopARMSupport', 'true'])
|
|
if msvs_windows_target_platform_version:
|
|
properties[0].append(['WindowsTargetPlatformVersion', \
|
|
str(msvs_windows_target_platform_version)])
|
|
|
|
return properties
|
|
|
|
def _GetMSBuildConfigurationDetails(spec, build_file):
|
|
properties = {}
|
|
for name, settings in spec['configurations'].iteritems():
|
|
msbuild_attributes = _GetMSBuildAttributes(spec, settings, build_file)
|
|
condition = _GetConfigurationCondition(name, settings)
|
|
character_set = msbuild_attributes.get('CharacterSet')
|
|
_AddConditionalProperty(properties, condition, 'ConfigurationType',
|
|
msbuild_attributes['ConfigurationType'])
|
|
if character_set:
|
|
if 'msvs_enable_winrt' not in spec :
|
|
_AddConditionalProperty(properties, condition, 'CharacterSet',
|
|
character_set)
|
|
return _GetMSBuildPropertyGroup(spec, 'Configuration', properties)
|
|
|
|
|
|
def _GetMSBuildLocalProperties(msbuild_toolset):
|
|
# Currently the only local property we support is PlatformToolset
|
|
properties = {}
|
|
if msbuild_toolset:
|
|
properties = [
|
|
['PropertyGroup', {'Label': 'Locals'},
|
|
['PlatformToolset', msbuild_toolset],
|
|
]
|
|
]
|
|
return properties
|
|
|
|
|
|
def _GetMSBuildPropertySheets(configurations):
|
|
user_props = r'$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props'
|
|
additional_props = {}
|
|
props_specified = False
|
|
for name, settings in sorted(configurations.iteritems()):
|
|
configuration = _GetConfigurationCondition(name, settings)
|
|
if settings.has_key('msbuild_props'):
|
|
additional_props[configuration] = _FixPaths(settings['msbuild_props'])
|
|
props_specified = True
|
|
else:
|
|
additional_props[configuration] = ''
|
|
|
|
if not props_specified:
|
|
return [
|
|
['ImportGroup',
|
|
{'Label': 'PropertySheets'},
|
|
['Import',
|
|
{'Project': user_props,
|
|
'Condition': "exists('%s')" % user_props,
|
|
'Label': 'LocalAppDataPlatform'
|
|
}
|
|
]
|
|
]
|
|
]
|
|
else:
|
|
sheets = []
|
|
for condition, props in additional_props.iteritems():
|
|
import_group = [
|
|
'ImportGroup',
|
|
{'Label': 'PropertySheets',
|
|
'Condition': condition
|
|
},
|
|
['Import',
|
|
{'Project': user_props,
|
|
'Condition': "exists('%s')" % user_props,
|
|
'Label': 'LocalAppDataPlatform'
|
|
}
|
|
]
|
|
]
|
|
for props_file in props:
|
|
import_group.append(['Import', {'Project':props_file}])
|
|
sheets.append(import_group)
|
|
return sheets
|
|
|
|
def _ConvertMSVSBuildAttributes(spec, config, build_file):
|
|
config_type = _GetMSVSConfigurationType(spec, build_file)
|
|
msvs_attributes = _GetMSVSAttributes(spec, config, config_type)
|
|
msbuild_attributes = {}
|
|
for a in msvs_attributes:
|
|
if a in ['IntermediateDirectory', 'OutputDirectory']:
|
|
directory = MSVSSettings.ConvertVCMacrosToMSBuild(msvs_attributes[a])
|
|
if not directory.endswith('\\'):
|
|
directory += '\\'
|
|
msbuild_attributes[a] = directory
|
|
elif a == 'CharacterSet':
|
|
msbuild_attributes[a] = _ConvertMSVSCharacterSet(msvs_attributes[a])
|
|
elif a == 'ConfigurationType':
|
|
msbuild_attributes[a] = _ConvertMSVSConfigurationType(msvs_attributes[a])
|
|
else:
|
|
print 'Warning: Do not know how to convert MSVS attribute ' + a
|
|
return msbuild_attributes
|
|
|
|
|
|
def _ConvertMSVSCharacterSet(char_set):
|
|
if char_set.isdigit():
|
|
char_set = {
|
|
'0': 'MultiByte',
|
|
'1': 'Unicode',
|
|
'2': 'MultiByte',
|
|
}[char_set]
|
|
return char_set
|
|
|
|
|
|
def _ConvertMSVSConfigurationType(config_type):
|
|
if config_type.isdigit():
|
|
config_type = {
|
|
'1': 'Application',
|
|
'2': 'DynamicLibrary',
|
|
'4': 'StaticLibrary',
|
|
'10': 'Utility'
|
|
}[config_type]
|
|
return config_type
|
|
|
|
|
|
def _GetMSBuildAttributes(spec, config, build_file):
|
|
if 'msbuild_configuration_attributes' not in config:
|
|
msbuild_attributes = _ConvertMSVSBuildAttributes(spec, config, build_file)
|
|
|
|
else:
|
|
config_type = _GetMSVSConfigurationType(spec, build_file)
|
|
config_type = _ConvertMSVSConfigurationType(config_type)
|
|
msbuild_attributes = config.get('msbuild_configuration_attributes', {})
|
|
msbuild_attributes.setdefault('ConfigurationType', config_type)
|
|
output_dir = msbuild_attributes.get('OutputDirectory',
|
|
'$(SolutionDir)$(Configuration)')
|
|
msbuild_attributes['OutputDirectory'] = _FixPath(output_dir) + '\\'
|
|
if 'IntermediateDirectory' not in msbuild_attributes:
|
|
intermediate = _FixPath('$(Configuration)') + '\\'
|
|
msbuild_attributes['IntermediateDirectory'] = intermediate
|
|
if 'CharacterSet' in msbuild_attributes:
|
|
msbuild_attributes['CharacterSet'] = _ConvertMSVSCharacterSet(
|
|
msbuild_attributes['CharacterSet'])
|
|
if 'TargetName' not in msbuild_attributes:
|
|
prefix = spec.get('product_prefix', '')
|
|
product_name = spec.get('product_name', '$(ProjectName)')
|
|
target_name = prefix + product_name
|
|
msbuild_attributes['TargetName'] = target_name
|
|
if 'TargetExt' not in msbuild_attributes and 'product_extension' in spec:
|
|
ext = spec.get('product_extension')
|
|
msbuild_attributes['TargetExt'] = '.' + ext
|
|
|
|
if spec.get('msvs_external_builder'):
|
|
external_out_dir = spec.get('msvs_external_builder_out_dir', '.')
|
|
msbuild_attributes['OutputDirectory'] = _FixPath(external_out_dir) + '\\'
|
|
|
|
# Make sure that 'TargetPath' matches 'Lib.OutputFile' or 'Link.OutputFile'
|
|
# (depending on the tool used) to avoid MSB8012 warning.
|
|
msbuild_tool_map = {
|
|
'executable': 'Link',
|
|
'shared_library': 'Link',
|
|
'loadable_module': 'Link',
|
|
'static_library': 'Lib',
|
|
}
|
|
msbuild_tool = msbuild_tool_map.get(spec['type'])
|
|
if msbuild_tool:
|
|
msbuild_settings = config['finalized_msbuild_settings']
|
|
out_file = msbuild_settings[msbuild_tool].get('OutputFile')
|
|
if out_file:
|
|
msbuild_attributes['TargetPath'] = _FixPath(out_file)
|
|
target_ext = msbuild_settings[msbuild_tool].get('TargetExt')
|
|
if target_ext:
|
|
msbuild_attributes['TargetExt'] = target_ext
|
|
|
|
return msbuild_attributes
|
|
|
|
|
|
def _GetMSBuildConfigurationGlobalProperties(spec, configurations, build_file):
|
|
# TODO(jeanluc) We could optimize out the following and do it only if
|
|
# there are actions.
|
|
# TODO(jeanluc) Handle the equivalent of setting 'CYGWIN=nontsec'.
|
|
new_paths = []
|
|
cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.'])[0]
|
|
if cygwin_dirs:
|
|
cyg_path = '$(MSBuildProjectDirectory)\\%s\\bin\\' % _FixPath(cygwin_dirs)
|
|
new_paths.append(cyg_path)
|
|
# TODO(jeanluc) Change the convention to have both a cygwin_dir and a
|
|
# python_dir.
|
|
python_path = cyg_path.replace('cygwin\\bin', 'python_26')
|
|
new_paths.append(python_path)
|
|
if new_paths:
|
|
new_paths = '$(ExecutablePath);' + ';'.join(new_paths)
|
|
|
|
properties = {}
|
|
for (name, configuration) in sorted(configurations.iteritems()):
|
|
condition = _GetConfigurationCondition(name, configuration)
|
|
attributes = _GetMSBuildAttributes(spec, configuration, build_file)
|
|
msbuild_settings = configuration['finalized_msbuild_settings']
|
|
_AddConditionalProperty(properties, condition, 'IntDir',
|
|
attributes['IntermediateDirectory'])
|
|
_AddConditionalProperty(properties, condition, 'OutDir',
|
|
attributes['OutputDirectory'])
|
|
_AddConditionalProperty(properties, condition, 'TargetName',
|
|
attributes['TargetName'])
|
|
if 'TargetExt' in attributes:
|
|
_AddConditionalProperty(properties, condition, 'TargetExt',
|
|
attributes['TargetExt'])
|
|
|
|
if attributes.get('TargetPath'):
|
|
_AddConditionalProperty(properties, condition, 'TargetPath',
|
|
attributes['TargetPath'])
|
|
if attributes.get('TargetExt'):
|
|
_AddConditionalProperty(properties, condition, 'TargetExt',
|
|
attributes['TargetExt'])
|
|
|
|
if new_paths:
|
|
_AddConditionalProperty(properties, condition, 'ExecutablePath',
|
|
new_paths)
|
|
tool_settings = msbuild_settings.get('', {})
|
|
for name, value in sorted(tool_settings.iteritems()):
|
|
formatted_value = _GetValueFormattedForMSBuild('', name, value)
|
|
_AddConditionalProperty(properties, condition, name, formatted_value)
|
|
return _GetMSBuildPropertyGroup(spec, None, properties)
|
|
|
|
|
|
def _AddConditionalProperty(properties, condition, name, value):
|
|
"""Adds a property / conditional value pair to a dictionary.
|
|
|
|
Arguments:
|
|
properties: The dictionary to be modified. The key is the name of the
|
|
property. The value is itself a dictionary; its key is the value and
|
|
the value a list of condition for which this value is true.
|
|
condition: The condition under which the named property has the value.
|
|
name: The name of the property.
|
|
value: The value of the property.
|
|
"""
|
|
if name not in properties:
|
|
properties[name] = {}
|
|
values = properties[name]
|
|
if value not in values:
|
|
values[value] = []
|
|
conditions = values[value]
|
|
conditions.append(condition)
|
|
|
|
|
|
# Regex for msvs variable references ( i.e. $(FOO) ).
|
|
MSVS_VARIABLE_REFERENCE = re.compile(r'\$\(([a-zA-Z_][a-zA-Z0-9_]*)\)')
|
|
|
|
|
|
def _GetMSBuildPropertyGroup(spec, label, properties):
|
|
"""Returns a PropertyGroup definition for the specified properties.
|
|
|
|
Arguments:
|
|
spec: The target project dict.
|
|
label: An optional label for the PropertyGroup.
|
|
properties: The dictionary to be converted. The key is the name of the
|
|
property. The value is itself a dictionary; its key is the value and
|
|
the value a list of condition for which this value is true.
|
|
"""
|
|
group = ['PropertyGroup']
|
|
if label:
|
|
group.append({'Label': label})
|
|
num_configurations = len(spec['configurations'])
|
|
def GetEdges(node):
|
|
# Use a definition of edges such that user_of_variable -> used_varible.
|
|
# This happens to be easier in this case, since a variable's
|
|
# definition contains all variables it references in a single string.
|
|
edges = set()
|
|
for value in sorted(properties[node].keys()):
|
|
# Add to edges all $(...) references to variables.
|
|
#
|
|
# Variable references that refer to names not in properties are excluded
|
|
# These can exist for instance to refer built in definitions like
|
|
# $(SolutionDir).
|
|
#
|
|
# Self references are ignored. Self reference is used in a few places to
|
|
# append to the default value. I.e. PATH=$(PATH);other_path
|
|
edges.update(set([v for v in MSVS_VARIABLE_REFERENCE.findall(value)
|
|
if v in properties and v != node]))
|
|
return edges
|
|
properties_ordered = gyp.common.TopologicallySorted(
|
|
properties.keys(), GetEdges)
|
|
# Walk properties in the reverse of a topological sort on
|
|
# user_of_variable -> used_variable as this ensures variables are
|
|
# defined before they are used.
|
|
# NOTE: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))
|
|
for name in reversed(properties_ordered):
|
|
values = properties[name]
|
|
for value, conditions in sorted(values.iteritems()):
|
|
if len(conditions) == num_configurations:
|
|
# If the value is the same all configurations,
|
|
# just add one unconditional entry.
|
|
group.append([name, value])
|
|
else:
|
|
for condition in conditions:
|
|
group.append([name, {'Condition': condition}, value])
|
|
return [group]
|
|
|
|
|
|
def _GetMSBuildToolSettingsSections(spec, configurations):
|
|
groups = []
|
|
for (name, configuration) in sorted(configurations.iteritems()):
|
|
msbuild_settings = configuration['finalized_msbuild_settings']
|
|
group = ['ItemDefinitionGroup',
|
|
{'Condition': _GetConfigurationCondition(name, configuration)}
|
|
]
|
|
for tool_name, tool_settings in sorted(msbuild_settings.iteritems()):
|
|
# Skip the tool named '' which is a holder of global settings handled
|
|
# by _GetMSBuildConfigurationGlobalProperties.
|
|
if tool_name:
|
|
if tool_settings:
|
|
tool = [tool_name]
|
|
for name, value in sorted(tool_settings.iteritems()):
|
|
formatted_value = _GetValueFormattedForMSBuild(tool_name, name,
|
|
value)
|
|
tool.append([name, formatted_value])
|
|
group.append(tool)
|
|
groups.append(group)
|
|
return groups
|
|
|
|
|
|
def _FinalizeMSBuildSettings(spec, configuration):
|
|
if 'msbuild_settings' in configuration:
|
|
converted = False
|
|
msbuild_settings = configuration['msbuild_settings']
|
|
MSVSSettings.ValidateMSBuildSettings(msbuild_settings)
|
|
else:
|
|
converted = True
|
|
msvs_settings = configuration.get('msvs_settings', {})
|
|
msbuild_settings = MSVSSettings.ConvertToMSBuildSettings(msvs_settings)
|
|
include_dirs, midl_include_dirs, resource_include_dirs = \
|
|
_GetIncludeDirs(configuration)
|
|
libraries = _GetLibraries(spec)
|
|
library_dirs = _GetLibraryDirs(configuration)
|
|
out_file, _, msbuild_tool = _GetOutputFilePathAndTool(spec, msbuild=True)
|
|
target_ext = _GetOutputTargetExt(spec)
|
|
defines = _GetDefines(configuration)
|
|
if converted:
|
|
# Visual Studio 2010 has TR1
|
|
defines = [d for d in defines if d != '_HAS_TR1=0']
|
|
# Warn of ignored settings
|
|
ignored_settings = ['msvs_tool_files']
|
|
for ignored_setting in ignored_settings:
|
|
value = configuration.get(ignored_setting)
|
|
if value:
|
|
print ('Warning: The automatic conversion to MSBuild does not handle '
|
|
'%s. Ignoring setting of %s' % (ignored_setting, str(value)))
|
|
|
|
defines = [_EscapeCppDefineForMSBuild(d) for d in defines]
|
|
disabled_warnings = _GetDisabledWarnings(configuration)
|
|
prebuild = configuration.get('msvs_prebuild')
|
|
postbuild = configuration.get('msvs_postbuild')
|
|
def_file = _GetModuleDefinition(spec)
|
|
precompiled_header = configuration.get('msvs_precompiled_header')
|
|
|
|
# Add the information to the appropriate tool
|
|
# TODO(jeanluc) We could optimize and generate these settings only if
|
|
# the corresponding files are found, e.g. don't generate ResourceCompile
|
|
# if you don't have any resources.
|
|
_ToolAppend(msbuild_settings, 'ClCompile',
|
|
'AdditionalIncludeDirectories', include_dirs)
|
|
_ToolAppend(msbuild_settings, 'Midl',
|
|
'AdditionalIncludeDirectories', midl_include_dirs)
|
|
_ToolAppend(msbuild_settings, 'ResourceCompile',
|
|
'AdditionalIncludeDirectories', resource_include_dirs)
|
|
# Add in libraries, note that even for empty libraries, we want this
|
|
# set, to prevent inheriting default libraries from the enviroment.
|
|
_ToolSetOrAppend(msbuild_settings, 'Link', 'AdditionalDependencies',
|
|
libraries)
|
|
_ToolAppend(msbuild_settings, 'Link', 'AdditionalLibraryDirectories',
|
|
library_dirs)
|
|
if out_file:
|
|
_ToolAppend(msbuild_settings, msbuild_tool, 'OutputFile', out_file,
|
|
only_if_unset=True)
|
|
if target_ext:
|
|
_ToolAppend(msbuild_settings, msbuild_tool, 'TargetExt', target_ext,
|
|
only_if_unset=True)
|
|
# Add defines.
|
|
_ToolAppend(msbuild_settings, 'ClCompile',
|
|
'PreprocessorDefinitions', defines)
|
|
_ToolAppend(msbuild_settings, 'ResourceCompile',
|
|
'PreprocessorDefinitions', defines)
|
|
# Add disabled warnings.
|
|
_ToolAppend(msbuild_settings, 'ClCompile',
|
|
'DisableSpecificWarnings', disabled_warnings)
|
|
# Turn on precompiled headers if appropriate.
|
|
if precompiled_header:
|
|
precompiled_header = os.path.split(precompiled_header)[1]
|
|
_ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'Use')
|
|
_ToolAppend(msbuild_settings, 'ClCompile',
|
|
'PrecompiledHeaderFile', precompiled_header)
|
|
_ToolAppend(msbuild_settings, 'ClCompile',
|
|
'ForcedIncludeFiles', [precompiled_header])
|
|
else:
|
|
_ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'NotUsing')
|
|
# Turn off WinRT compilation
|
|
_ToolAppend(msbuild_settings, 'ClCompile', 'CompileAsWinRT', 'false')
|
|
# Turn on import libraries if appropriate
|
|
if spec.get('msvs_requires_importlibrary'):
|
|
_ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'false')
|
|
# Loadable modules don't generate import libraries;
|
|
# tell dependent projects to not expect one.
|
|
if spec['type'] == 'loadable_module':
|
|
_ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'true')
|
|
# Set the module definition file if any.
|
|
if def_file:
|
|
_ToolAppend(msbuild_settings, 'Link', 'ModuleDefinitionFile', def_file)
|
|
configuration['finalized_msbuild_settings'] = msbuild_settings
|
|
if prebuild:
|
|
_ToolAppend(msbuild_settings, 'PreBuildEvent', 'Command', prebuild)
|
|
if postbuild:
|
|
_ToolAppend(msbuild_settings, 'PostBuildEvent', 'Command', postbuild)
|
|
|
|
|
|
def _GetValueFormattedForMSBuild(tool_name, name, value):
|
|
if type(value) == list:
|
|
# For some settings, VS2010 does not automatically extends the settings
|
|
# TODO(jeanluc) Is this what we want?
|
|
if name in ['AdditionalIncludeDirectories',
|
|
'AdditionalLibraryDirectories',
|
|
'AdditionalOptions',
|
|
'DelayLoadDLLs',
|
|
'DisableSpecificWarnings',
|
|
'PreprocessorDefinitions']:
|
|
value.append('%%(%s)' % name)
|
|
# For most tools, entries in a list should be separated with ';' but some
|
|
# settings use a space. Check for those first.
|
|
exceptions = {
|
|
'ClCompile': ['AdditionalOptions'],
|
|
'Link': ['AdditionalOptions'],
|
|
'Lib': ['AdditionalOptions']}
|
|
if tool_name in exceptions and name in exceptions[tool_name]:
|
|
char = ' '
|
|
else:
|
|
char = ';'
|
|
formatted_value = char.join(
|
|
[MSVSSettings.ConvertVCMacrosToMSBuild(i) for i in value])
|
|
else:
|
|
formatted_value = MSVSSettings.ConvertVCMacrosToMSBuild(value)
|
|
return formatted_value
|
|
|
|
|
|
def _VerifySourcesExist(sources, root_dir):
|
|
"""Verifies that all source files exist on disk.
|
|
|
|
Checks that all regular source files, i.e. not created at run time,
|
|
exist on disk. Missing files cause needless recompilation but no otherwise
|
|
visible errors.
|
|
|
|
Arguments:
|
|
sources: A recursive list of Filter/file names.
|
|
root_dir: The root directory for the relative path names.
|
|
Returns:
|
|
A list of source files that cannot be found on disk.
|
|
"""
|
|
missing_sources = []
|
|
for source in sources:
|
|
if isinstance(source, MSVSProject.Filter):
|
|
missing_sources.extend(_VerifySourcesExist(source.contents, root_dir))
|
|
else:
|
|
if '$' not in source:
|
|
full_path = os.path.join(root_dir, source)
|
|
if not os.path.exists(full_path):
|
|
missing_sources.append(full_path)
|
|
return missing_sources
|
|
|
|
|
|
def _GetMSBuildSources(spec, sources, exclusions, rule_dependencies,
|
|
extension_to_rule_name, actions_spec,
|
|
sources_handled_by_action, list_excluded):
|
|
groups = ['none', 'masm', 'midl', 'include', 'compile', 'resource', 'rule',
|
|
'rule_dependency']
|
|
grouped_sources = {}
|
|
for g in groups:
|
|
grouped_sources[g] = []
|
|
|
|
_AddSources2(spec, sources, exclusions, grouped_sources,
|
|
rule_dependencies, extension_to_rule_name,
|
|
sources_handled_by_action, list_excluded)
|
|
sources = []
|
|
for g in groups:
|
|
if grouped_sources[g]:
|
|
sources.append(['ItemGroup'] + grouped_sources[g])
|
|
if actions_spec:
|
|
sources.append(['ItemGroup'] + actions_spec)
|
|
return sources
|
|
|
|
|
|
def _AddSources2(spec, sources, exclusions, grouped_sources,
|
|
rule_dependencies, extension_to_rule_name,
|
|
sources_handled_by_action,
|
|
list_excluded):
|
|
extensions_excluded_from_precompile = []
|
|
for source in sources:
|
|
if isinstance(source, MSVSProject.Filter):
|
|
_AddSources2(spec, source.contents, exclusions, grouped_sources,
|
|
rule_dependencies, extension_to_rule_name,
|
|
sources_handled_by_action,
|
|
list_excluded)
|
|
else:
|
|
if not source in sources_handled_by_action:
|
|
detail = []
|
|
excluded_configurations = exclusions.get(source, [])
|
|
if len(excluded_configurations) == len(spec['configurations']):
|
|
detail.append(['ExcludedFromBuild', 'true'])
|
|
else:
|
|
for config_name, configuration in sorted(excluded_configurations):
|
|
condition = _GetConfigurationCondition(config_name, configuration)
|
|
detail.append(['ExcludedFromBuild',
|
|
{'Condition': condition},
|
|
'true'])
|
|
# Add precompile if needed
|
|
for config_name, configuration in spec['configurations'].iteritems():
|
|
precompiled_source = configuration.get('msvs_precompiled_source', '')
|
|
if precompiled_source != '':
|
|
precompiled_source = _FixPath(precompiled_source)
|
|
if not extensions_excluded_from_precompile:
|
|
# If the precompiled header is generated by a C source, we must
|
|
# not try to use it for C++ sources, and vice versa.
|
|
basename, extension = os.path.splitext(precompiled_source)
|
|
if extension == '.c':
|
|
extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx']
|
|
else:
|
|
extensions_excluded_from_precompile = ['.c']
|
|
|
|
if precompiled_source == source:
|
|
condition = _GetConfigurationCondition(config_name, configuration)
|
|
detail.append(['PrecompiledHeader',
|
|
{'Condition': condition},
|
|
'Create'
|
|
])
|
|
else:
|
|
# Turn off precompiled header usage for source files of a
|
|
# different type than the file that generated the
|
|
# precompiled header.
|
|
for extension in extensions_excluded_from_precompile:
|
|
if source.endswith(extension):
|
|
detail.append(['PrecompiledHeader', ''])
|
|
detail.append(['ForcedIncludeFiles', ''])
|
|
|
|
group, element = _MapFileToMsBuildSourceType(source, rule_dependencies,
|
|
extension_to_rule_name)
|
|
grouped_sources[group].append([element, {'Include': source}] + detail)
|
|
|
|
|
|
def _GetMSBuildProjectReferences(project):
|
|
references = []
|
|
if project.dependencies:
|
|
group = ['ItemGroup']
|
|
for dependency in project.dependencies:
|
|
guid = dependency.guid
|
|
project_dir = os.path.split(project.path)[0]
|
|
relative_path = gyp.common.RelativePath(dependency.path, project_dir)
|
|
project_ref = ['ProjectReference',
|
|
{'Include': relative_path},
|
|
['Project', guid],
|
|
['ReferenceOutputAssembly', 'false']
|
|
]
|
|
for config in dependency.spec.get('configurations', {}).itervalues():
|
|
if config.get('msvs_use_library_dependency_inputs', 0):
|
|
project_ref.append(['UseLibraryDependencyInputs', 'true'])
|
|
break
|
|
# If it's disabled in any config, turn it off in the reference.
|
|
if config.get('msvs_2010_disable_uldi_when_referenced', 0):
|
|
project_ref.append(['UseLibraryDependencyInputs', 'false'])
|
|
break
|
|
group.append(project_ref)
|
|
references.append(group)
|
|
return references
|
|
|
|
|
|
def _GenerateMSBuildProject(project, options, version, generator_flags):
|
|
spec = project.spec
|
|
configurations = spec['configurations']
|
|
project_dir, project_file_name = os.path.split(project.path)
|
|
gyp.common.EnsureDirExists(project.path)
|
|
# Prepare list of sources and excluded sources.
|
|
gyp_path = _NormalizedSource(project.build_file)
|
|
relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
|
|
|
|
gyp_file = os.path.split(project.build_file)[1]
|
|
sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
|
|
gyp_file)
|
|
# Add rules.
|
|
actions_to_add = {}
|
|
props_files_of_rules = set()
|
|
targets_files_of_rules = set()
|
|
rule_dependencies = set()
|
|
extension_to_rule_name = {}
|
|
list_excluded = generator_flags.get('msvs_list_excluded_files', True)
|
|
|
|
# Don't generate rules if we are using an external builder like ninja.
|
|
if not spec.get('msvs_external_builder'):
|
|
_GenerateRulesForMSBuild(project_dir, options, spec,
|
|
sources, excluded_sources,
|
|
props_files_of_rules, targets_files_of_rules,
|
|
actions_to_add, rule_dependencies,
|
|
extension_to_rule_name)
|
|
else:
|
|
rules = spec.get('rules', [])
|
|
_AdjustSourcesForRules(rules, sources, excluded_sources, True)
|
|
|
|
sources, excluded_sources, excluded_idl = (
|
|
_AdjustSourcesAndConvertToFilterHierarchy(spec, options,
|
|
project_dir, sources,
|
|
excluded_sources,
|
|
list_excluded, version))
|
|
|
|
# Don't add actions if we are using an external builder like ninja.
|
|
if not spec.get('msvs_external_builder'):
|
|
_AddActions(actions_to_add, spec, project.build_file)
|
|
_AddCopies(actions_to_add, spec)
|
|
|
|
# NOTE: this stanza must appear after all actions have been decided.
|
|
# Don't excluded sources with actions attached, or they won't run.
|
|
excluded_sources = _FilterActionsFromExcluded(
|
|
excluded_sources, actions_to_add)
|
|
|
|
exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
|
|
actions_spec, sources_handled_by_action = _GenerateActionsForMSBuild(
|
|
spec, actions_to_add)
|
|
|
|
_GenerateMSBuildFiltersFile(project.path + '.filters', sources,
|
|
rule_dependencies,
|
|
extension_to_rule_name)
|
|
missing_sources = _VerifySourcesExist(sources, project_dir)
|
|
|
|
for configuration in configurations.itervalues():
|
|
_FinalizeMSBuildSettings(spec, configuration)
|
|
|
|
# Add attributes to root element
|
|
|
|
import_default_section = [
|
|
['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.Default.props'}]]
|
|
import_cpp_props_section = [
|
|
['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.props'}]]
|
|
import_cpp_targets_section = [
|
|
['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.targets'}]]
|
|
import_masm_props_section = [
|
|
['Import',
|
|
{'Project': r'$(VCTargetsPath)\BuildCustomizations\masm.props'}]]
|
|
import_masm_targets_section = [
|
|
['Import',
|
|
{'Project': r'$(VCTargetsPath)\BuildCustomizations\masm.targets'}]]
|
|
macro_section = [['PropertyGroup', {'Label': 'UserMacros'}]]
|
|
|
|
content = [
|
|
'Project',
|
|
{'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003',
|
|
'ToolsVersion': version.ProjectVersion(),
|
|
'DefaultTargets': 'Build'
|
|
}]
|
|
|
|
content += _GetMSBuildProjectConfigurations(configurations)
|
|
content += _GetMSBuildGlobalProperties(spec, project.guid, project_file_name)
|
|
content += import_default_section
|
|
content += _GetMSBuildConfigurationDetails(spec, project.build_file)
|
|
if spec.get('msvs_enable_winphone'):
|
|
content += _GetMSBuildLocalProperties('v120_wp81')
|
|
else:
|
|
content += _GetMSBuildLocalProperties(project.msbuild_toolset)
|
|
content += import_cpp_props_section
|
|
content += import_masm_props_section
|
|
content += _GetMSBuildExtensions(props_files_of_rules)
|
|
content += _GetMSBuildPropertySheets(configurations)
|
|
content += macro_section
|
|
content += _GetMSBuildConfigurationGlobalProperties(spec, configurations,
|
|
project.build_file)
|
|
content += _GetMSBuildToolSettingsSections(spec, configurations)
|
|
content += _GetMSBuildSources(
|
|
spec, sources, exclusions, rule_dependencies, extension_to_rule_name,
|
|
actions_spec, sources_handled_by_action, list_excluded)
|
|
content += _GetMSBuildProjectReferences(project)
|
|
content += import_cpp_targets_section
|
|
content += import_masm_targets_section
|
|
content += _GetMSBuildExtensionTargets(targets_files_of_rules)
|
|
|
|
if spec.get('msvs_external_builder'):
|
|
content += _GetMSBuildExternalBuilderTargets(spec)
|
|
|
|
# TODO(jeanluc) File a bug to get rid of runas. We had in MSVS:
|
|
# has_run_as = _WriteMSVSUserFile(project.path, version, spec)
|
|
|
|
easy_xml.WriteXmlIfChanged(content, project.path, pretty=True, win32=True)
|
|
|
|
return missing_sources
|
|
|
|
|
|
def _GetMSBuildExternalBuilderTargets(spec):
|
|
"""Return a list of MSBuild targets for external builders.
|
|
|
|
The "Build" and "Clean" targets are always generated. If the spec contains
|
|
'msvs_external_builder_clcompile_cmd', then the "ClCompile" target will also
|
|
be generated, to support building selected C/C++ files.
|
|
|
|
Arguments:
|
|
spec: The gyp target spec.
|
|
Returns:
|
|
List of MSBuild 'Target' specs.
|
|
"""
|
|
build_cmd = _BuildCommandLineForRuleRaw(
|
|
spec, spec['msvs_external_builder_build_cmd'],
|
|
False, False, False, False)
|
|
build_target = ['Target', {'Name': 'Build'}]
|
|
build_target.append(['Exec', {'Command': build_cmd}])
|
|
|
|
clean_cmd = _BuildCommandLineForRuleRaw(
|
|
spec, spec['msvs_external_builder_clean_cmd'],
|
|
False, False, False, False)
|
|
clean_target = ['Target', {'Name': 'Clean'}]
|
|
clean_target.append(['Exec', {'Command': clean_cmd}])
|
|
|
|
targets = [build_target, clean_target]
|
|
|
|
if spec.get('msvs_external_builder_clcompile_cmd'):
|
|
clcompile_cmd = _BuildCommandLineForRuleRaw(
|
|
spec, spec['msvs_external_builder_clcompile_cmd'],
|
|
False, False, False, False)
|
|
clcompile_target = ['Target', {'Name': 'ClCompile'}]
|
|
clcompile_target.append(['Exec', {'Command': clcompile_cmd}])
|
|
targets.append(clcompile_target)
|
|
|
|
return targets
|
|
|
|
|
|
def _GetMSBuildExtensions(props_files_of_rules):
|
|
extensions = ['ImportGroup', {'Label': 'ExtensionSettings'}]
|
|
for props_file in props_files_of_rules:
|
|
extensions.append(['Import', {'Project': props_file}])
|
|
return [extensions]
|
|
|
|
|
|
def _GetMSBuildExtensionTargets(targets_files_of_rules):
|
|
targets_node = ['ImportGroup', {'Label': 'ExtensionTargets'}]
|
|
for targets_file in sorted(targets_files_of_rules):
|
|
targets_node.append(['Import', {'Project': targets_file}])
|
|
return [targets_node]
|
|
|
|
|
|
def _GenerateActionsForMSBuild(spec, actions_to_add):
|
|
"""Add actions accumulated into an actions_to_add, merging as needed.
|
|
|
|
Arguments:
|
|
spec: the target project dict
|
|
actions_to_add: dictionary keyed on input name, which maps to a list of
|
|
dicts describing the actions attached to that input file.
|
|
|
|
Returns:
|
|
A pair of (action specification, the sources handled by this action).
|
|
"""
|
|
sources_handled_by_action = OrderedSet()
|
|
actions_spec = []
|
|
for primary_input, actions in actions_to_add.iteritems():
|
|
inputs = OrderedSet()
|
|
outputs = OrderedSet()
|
|
descriptions = []
|
|
commands = []
|
|
for action in actions:
|
|
inputs.update(OrderedSet(action['inputs']))
|
|
outputs.update(OrderedSet(action['outputs']))
|
|
descriptions.append(action['description'])
|
|
cmd = action['command']
|
|
# For most actions, add 'call' so that actions that invoke batch files
|
|
# return and continue executing. msbuild_use_call provides a way to
|
|
# disable this but I have not seen any adverse effect from doing that
|
|
# for everything.
|
|
if action.get('msbuild_use_call', True):
|
|
cmd = 'call ' + cmd
|
|
commands.append(cmd)
|
|
# Add the custom build action for one input file.
|
|
description = ', and also '.join(descriptions)
|
|
|
|
# We can't join the commands simply with && because the command line will
|
|
# get too long. See also _AddActions: cygwin's setup_env mustn't be called
|
|
# for every invocation or the command that sets the PATH will grow too
|
|
# long.
|
|
command = '\r\n'.join([c + '\r\nif %errorlevel% neq 0 exit /b %errorlevel%'
|
|
for c in commands])
|
|
_AddMSBuildAction(spec,
|
|
primary_input,
|
|
inputs,
|
|
outputs,
|
|
command,
|
|
description,
|
|
sources_handled_by_action,
|
|
actions_spec)
|
|
return actions_spec, sources_handled_by_action
|
|
|
|
|
|
def _AddMSBuildAction(spec, primary_input, inputs, outputs, cmd, description,
|
|
sources_handled_by_action, actions_spec):
|
|
command = MSVSSettings.ConvertVCMacrosToMSBuild(cmd)
|
|
primary_input = _FixPath(primary_input)
|
|
inputs_array = _FixPaths(inputs)
|
|
outputs_array = _FixPaths(outputs)
|
|
additional_inputs = ';'.join([i for i in inputs_array
|
|
if i != primary_input])
|
|
outputs = ';'.join(outputs_array)
|
|
sources_handled_by_action.add(primary_input)
|
|
action_spec = ['CustomBuild', {'Include': primary_input}]
|
|
action_spec.extend(
|
|
# TODO(jeanluc) 'Document' for all or just if as_sources?
|
|
[['FileType', 'Document'],
|
|
['Command', command],
|
|
['Message', description],
|
|
['Outputs', outputs]
|
|
])
|
|
if additional_inputs:
|
|
action_spec.append(['AdditionalInputs', additional_inputs])
|
|
actions_spec.append(action_spec)
|