# 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.
from __future__
import print_function
import copy
import ntpath
import os
import posixpath
import re
import subprocess
import sys
import collections
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
# 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 = {
'DRIVER_PREFIX':
'',
'DRIVER_SUFFIX':
'.sys',
'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',
]
generator_filelist_paths =
None
# 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 = collections.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 'CompileAsWinRT' == setting:
return
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 _ConfigTargetVersion(config_data):
return config_data.get(
'msvs_target_version',
'Windows7')
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, version):
config_ver = config_data.get(
'msvs_windows_sdk_version')
vers = [config_ver]
if config_ver
else version.compatible_sdks
for ver
in vers:
for key
in [
r
'HKLM\Software\Microsoft\Microsoft SDKs\Windows\%s',
r
'HKLM\Software\Wow6432Node\Microsoft\Microsoft SDKs\Windows\%s']:
sdk_dir = MSVSVersion._RegistryGetValue(key % ver,
'InstallationFolder')
if not sdk_dir:
continue
version = MSVSVersion._RegistryGetValue(key % ver,
'ProductVersion')
or ''
# Find a matching entry in sdk_dir\include.
expected_sdk_dir=r
'%s\include' % sdk_dir
names = sorted([x
for x
in (os.listdir(expected_sdk_dir)
if os.path.isdir(expected_sdk_dir)
else []
)
if x.startswith(version)], reverse=
True)
if names:
return names[0]
else:
print(
'Warning: No include files found for '
'detected Windows SDK version %s' % (version))
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'].items():
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((
'Warning: MSVS may misinterpret the odd number of ' +
'quotes in ' + s), file=sys.stderr)
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()
if spec[
'type'] ==
'windows_driver':
toolset =
'WindowsKernelModeDriver10.0'
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.items():
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'].items():
_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
'windows_driver':
'5',
# .sys
'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'),
'windows_driver': (
'VCLinkerTool',
'Link',
'$(OutDir)',
'.sys'),
'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',
'windows_driver']:
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.items():
# Collapse settings with lists.
settings_fixed = {}
for setting, value
in settings.items():
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'].items():
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.items():
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'].items():
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'].items():
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'].items():
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'].items():
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'].items():
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'].items():
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]
fixed_dst = _FixPath(dst)
full_dst =
'"%s\\%s\\"' % (fixed_dst, outer_dir)
cmd =
'mkdir %s 2>nul & cd "%s" && xcopy /e /f /y "%s" %s' % (
full_dst, _FixPath(base_dir), outer_dir, full_dst)
copies.append(([src], [
'dummy_copies', dst], cmd,
'Copying %s to %s' % (src, fixed_dst)))
else:
fix_dst = _FixPath(cpy[
'destination'])
cmd =
'mkdir "%s" 2>nul & set ERRORLEVEL=0 & copy /Y "%s" "%s"' % (
fix_dst, _FixPath(src), _FixPath(dst))
copies.append(([src], [dst], cmd,
'Copying %s to %s' % (src, fix_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.items():
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.
--> --------------------
--> maximum size reached
--> --------------------