GN: Autodetect Visual Studio versions

This searches the local system for Visual Studio versions like GYP. It enables specifically requesting one version, as well as overriding the directory like GYP.

BUG=
R=scottmg@chromium.org

Review URL: https://codereview.chromium.org/126073005

git-svn-id: http://src.chromium.org/svn/trunk/src/build@243612 4ff67af0-8c30-449e-8e8b-ad334ec8d88c
This commit is contained in:
brettw@chromium.org 2014-01-08 18:46:44 +00:00
Родитель c4dfd98310
Коммит d26952a6e6
6 изменённых файлов: 259 добавлений и 68 удалений

Просмотреть файл

@ -2,13 +2,13 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/config/win/visual_studio_version.gni")
declare_args() {
# Full path to the Windows SDK, not including a backslash at the end.
# This value is the default location, override if you have a different
# installation location.
windows_sdk_path = "C:\Program Files (x86)\Windows Kits\8.0"
# Full path to the Visual Studio installation, not including a backslash
# at the end.
visual_studio_path = "C:\Program Files (x86)\Microsoft Visual Studio 10.0"
}
# Compiler setup for the Windows SDK. Applied to all targets.

Просмотреть файл

@ -0,0 +1,175 @@
# Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import errno
import os
import re
import subprocess
import sys
"""
This script searches for Visual Studio versions on the current system.
Pass in the preferred VS version on the command line, or pass "auto" for
autodetect.
This script prints a string containing the VS root directory. On failure it
returns the empty string.
"""
def _ConvertToCygpath(path):
"""Convert to cygwin path if we are using cygwin."""
if sys.platform == 'cygwin':
p = subprocess.Popen(['cygpath', path], stdout=subprocess.PIPE)
path = p.communicate()[0].strip()
return path
def _RegistryQueryBase(sysdir, key, value):
"""Use reg.exe to read a particular key.
While ideally we might use the win32 module, we would like gyp to be
python neutral, so for instance cygwin python lacks this module.
Arguments:
sysdir: The system subdirectory to attempt to launch reg.exe from.
key: The registry key to read from.
value: The particular value to read.
Return:
stdout from reg.exe, or None for failure.
"""
# Setup params to pass to and attempt to launch reg.exe
cmd = [os.path.join(os.environ.get('WINDIR', ''), sysdir, 'reg.exe'),
'query', key]
if value:
cmd.extend(['/v', value])
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# Obtain the stdout from reg.exe, reading to the end so p.returncode is valid
# Note that the error text may be in [1] in some cases
text = p.communicate()[0]
# Check return code from reg.exe; officially 0==success and 1==error
if p.returncode:
return None
return text
def _RegistryQuery(key, value=None):
"""Use reg.exe to read a particular key through _RegistryQueryBase.
First tries to launch from %WinDir%\Sysnative to avoid WoW64 redirection. If
that fails, it falls back to System32. Sysnative is available on Vista and
up and available on Windows Server 2003 and XP through KB patch 942589. Note
that Sysnative will always fail if using 64-bit python due to it being a
virtual directory and System32 will work correctly in the first place.
KB 942589 - http://support.microsoft.com/kb/942589/en-us.
Arguments:
key: The registry key.
value: The particular registry value to read (optional).
Return:
stdout from reg.exe, or None for failure.
"""
text = None
try:
text = _RegistryQueryBase('Sysnative', key, value)
except OSError, e:
if e.errno == errno.ENOENT:
text = _RegistryQueryBase('System32', key, value)
else:
raise
return text
def _RegistryGetValue(key, value):
"""Use reg.exe to obtain the value of a registry key.
Args:
key: The registry key.
value: The particular registry value to read.
Return:
contents of the registry key's value, or None on failure.
"""
text = _RegistryQuery(key, value)
if not text:
return None
# Extract value.
match = re.search(r'REG_\w+\s+([^\r]+)\r\n', text)
if not match:
return None
return match.group(1)
def _DetectVisualStudioVersion(versions_to_check, force_express):
"""Gets the path of the preferred Visual Studio version.
Returns:
The base path of Visual Studio based on the registry and a quick check if
devenv.exe exists.
Possibilities are:
2010(e) - Visual Studio 2010 (10)
2012(e) - Visual Studio 2012 (11)
2013(e) - Visual Studio 2013 (12)
Where (e) is e for express editions of MSVS and blank otherwise.
"""
versions = []
for version in versions_to_check:
# Old method of searching for which VS version is installed
# We don't use the 2010-encouraged-way because we also want to get the
# path to the binaries, which it doesn't offer.
keys = [r'HKLM\Software\Microsoft\VisualStudio\%s' % version,
r'HKLM\Software\Wow6432Node\Microsoft\VisualStudio\%s' % version,
r'HKLM\Software\Microsoft\VCExpress\%s' % version,
r'HKLM\Software\Wow6432Node\Microsoft\VCExpress\%s' % version]
for index in range(len(keys)):
path = _RegistryGetValue(keys[index], 'InstallDir')
if not path:
continue
path = _ConvertToCygpath(path)
# Check for full.
full_path = os.path.join(path, 'devenv.exe')
express_path = os.path.join(path, '*express.exe')
if not force_express and os.path.exists(full_path):
return os.path.normpath(os.path.join(path, '..', '..'))
# Check for express.
elif glob.glob(express_path):
return os.path.normpath(os.path.join(path, '..', '..'))
# The old method above does not work when only SDK is installed.
keys = [r'HKLM\Software\Microsoft\VisualStudio\SxS\VC7',
r'HKLM\Software\Wow6432Node\Microsoft\VisualStudio\SxS\VC7']
for index in range(len(keys)):
path = _RegistryGetValue(keys[index], version)
if not path:
continue
path = _ConvertToCygpath(path)
return os.path.normpath(os.path.join(path, '..'))
return None
if len(sys.argv) != 2:
print 'Usage: get_visual_studio_path.py <version>'
print 'Use "auto" for the version to autodetect.'
sys.exit(2)
version_map = {
'auto': ('10.0', '12.0', '11.0'),
'2010': ('10.0',),
'2010e': ('10.0',),
'2012': ('11.0',),
'2012e': ('11.0',),
'2013': ('12.0',),
'2013e': ('12.0',),
}
requested_version = sys.argv[1]
vs_path = _DetectVisualStudioVersion(version_map[requested_version],
'e' in requested_version)
if not vs_path:
# No Visual Studio version detected.
print '""' # Return empty string to .gn file.
sys.exit(1);
# Return Visual Studio path to the .gn file.
print '"%s"' % vs_path

Просмотреть файл

@ -0,0 +1,36 @@
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
declare_args() {
# Version of Visual Studio to use.
#
# Possible values:
# - "auto"
# - "2010"
# - "2010e"
# - "2012"
# - "2012e"
# - "2013"
# - "2013e"
#
# The "e" variants denote "Express". The default, "auto" will find the
# preferred one of all Visual Studios installed on the current system.
visual_studio_version = "auto"
# The path to use as the root of a Visual Studio install. If nonempty,
# this path will be used instead of any autodetected install locations,
# which allows you to have the Visual Studio files in a directory without
# actually "installing" it.
#
# When empty, we will autodetect the best Visual Studio path to use. The
# autodetected one is usually something like
# "C:\Program Files (x86)\Microsoft Visual Studio 10.0".
visual_studio_path = ""
}
if (visual_studio_path == "") {
visual_studio_path = exec_script("get_visual_studio_path.py",
[ visual_studio_version ], "value")
}

Просмотреть файл

@ -177,6 +177,15 @@ def GetArgsStringForGN(supplemental_files):
if v in vars_dict:
gn_args += ' ' + v + '=' + EscapeStringForGN(vars_dict[v])
# Some other flags come from GYP environment variables.
gyp_msvs_version = os.environ.get('GYP_MSVS_VERSION', '')
if gyp_msvs_version:
gn_args += ' visual_studio_version=' + EscapeStringForGN(gyp_msvs_version)
gyp_msvs_override_path = os.environ.get('GYP_MSVS_OVERRIDE_PATH', '')
if gyp_msvs_override_path:
gn_args += ' visual_studio_override_path=' + \
EscapeStringForGN(gyp_msvs_override_path)
# Set the GYP flag so BUILD files know they're being invoked in GYP mode.
gn_args += ' is_gyp=true'
return gn_args.strip()

Просмотреть файл

@ -7,23 +7,17 @@ import("//build/toolchain/goma.gni")
# Should only be running on Windows.
assert(is_win)
import("//build/config/win/visual_studio_version.gni")
# Setup the Visual Studio state.
#
# Its argument is the location to write the environment files.
# It will write "environment.x86" and "environment.x64" to this directory,
# and return a list to us.
#
# The list contains the include path as its only element. (I'm expecting to
# add more so it's currently a list inside a list.)
#exec_script("get_msvc_config.py",
# [relative_root_output_dir],
# "value")
# This will save the environment block and and copy the gyp-win-tool to the
# build directory. We pass in the source file of the win tool.
gyp_win_tool_source =
rebase_path("//tools/gyp/pylib/gyp/win_tool.py", ".", root_build_dir)
exec_script("setup_toolchain.py", [ gyp_win_tool_source ], "value")
# Its arguments are the VS path and the compiler wrapper tool. It will write
# "environment.x86" and "environment.x64" to the build directory and return a
# list to us.
gyp_win_tool_path = rebase_path("//tools/gyp/pylib/gyp/win_tool.py", ".",
root_build_dir)
exec_script("setup_toolchain.py", [ visual_studio_path, gyp_win_tool_path ],
"string")
stamp_command = "$python_path gyp-win-tool stamp \$out"
copy_command = "$python_path gyp-win-tool recursive-mirror \$in \$out"

Просмотреть файл

@ -2,10 +2,23 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import errno
import os
import re
import subprocess
import sys
"""
Copies the given "win tool" (which the toolchain uses to wrap compiler
invocations) and the environment blocks for the 32-bit and 64-bit builds on
Windows to the build directory.
The arguments are the visual studio install location and the location of the
win tool. The script assumes that the root build directory is the current dir
and the files will be written to the current directory.
"""
def ExtractImportantEnvironment():
"""Extracts environment variables required for the toolchain from the
current environment."""
@ -36,48 +49,6 @@ def ExtractImportantEnvironment():
return result
# VC setup will add a path like this in 32-bit mode:
# c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\BIN
# And this in 64-bit mode:
# c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\BIN\amd64
# Note that in 64-bit it's duplicated but the 64-bit one comes first.
#
# What we get as the path when running this will depend on which VS setup
# script you've run. The following two functions try to do this.
# For 32-bit compiles remove anything that ends in "\VC\WIN\amd64".
def FixupPath32(path):
find_64 = re.compile("VC\\\\BIN\\\\amd64\\\\*$", flags=re.IGNORECASE)
for i in range(len(path)):
if find_64.search(path[i]):
# Found 32-bit path, insert the 64-bit one immediately before it.
dir_64 = path[i].rstrip("\\")
dir_64 = dir_64[:len(dir_64) - 6] # Trim off "\amd64".
path[i] = dir_64
break
return path
# For 64-bit compiles, append anything ending in "\VC\BIN" with "\amd64" as
# long as that thing isn't already in the list, and append it immediately
# before the non-amd64-one.
def FixupPath64(path):
find_32 = re.compile("VC\\\\BIN\\\\*$", flags=re.IGNORECASE)
for i in range(len(path)):
if find_32.search(path[i]):
# Found 32-bit path, insert the 64-bit one immediately before it.
dir_32 = path[i]
if dir_32[len(dir_32) - 1] == '\\':
dir_64 = dir_32 + "amd64"
else:
dir_64 = dir_32 + "\\amd64"
path.insert(i, dir_64)
break
return path
def FormatAsEnvironmentBlock(envvar_dict):
"""Format as an 'environment block' directly suitable for CreateProcess.
Briefly this is a list of key=value\0, terminated by an additional \0. See
@ -89,6 +60,7 @@ def FormatAsEnvironmentBlock(envvar_dict):
block += nul
return block
def CopyTool(source_path):
"""Copies the given tool to the current directory, including a warning not
to edit it."""
@ -102,22 +74,27 @@ def CopyTool(source_path):
'# Generated by setup_toolchain.py do not edit.\n']
+ tool_source[1:]))
if len(sys.argv) != 3:
print 'Usage setup_toolchain.py <visual studio path> <win tool path>'
sys.exit(2)
vs_path = sys.argv[1]
tool_source = sys.argv[2]
# Find the tool source, it's the first argument, and copy it.
if len(sys.argv) != 2:
print "Need one argument (win_tool source path)."
sys.exit(1)
CopyTool(sys.argv[1])
CopyTool(tool_source)
important_env_vars = ExtractImportantEnvironment()
path = important_env_vars["PATH"].split(";")
important_env_vars["PATH"] = ";".join(FixupPath32(path))
# Add 32-bit compiler path to the beginning and write the block.
path32 = [os.path.join(vs_path, "VC\\BIN")] + path
important_env_vars["PATH"] = ";".join(path32)
environ = FormatAsEnvironmentBlock(important_env_vars)
with open('environment.x86', 'wb') as env_file:
env_file.write(environ)
important_env_vars["PATH"] = ";".join(FixupPath64(path))
# Add 64-bit compiler path to the beginning and write the block.
path64 = [os.path.join(vs_path, "VC\\BIN\\amd64")] + path
important_env_vars["PATH"] = ";".join(path64)
environ = FormatAsEnvironmentBlock(important_env_vars)
with open('environment.x64', 'wb') as env_file:
env_file.write(environ)