diff --git a/config/win/BUILD.gn b/config/win/BUILD.gn index 8bedbf581..a4aaa255f 100644 --- a/config/win/BUILD.gn +++ b/config/win/BUILD.gn @@ -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. diff --git a/config/win/get_visual_studio_path.py b/config/win/get_visual_studio_path.py new file mode 100644 index 000000000..bd1ab198a --- /dev/null +++ b/config/win/get_visual_studio_path.py @@ -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 ' + 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 diff --git a/config/win/visual_studio_version.gni b/config/win/visual_studio_version.gni new file mode 100644 index 000000000..79b58418b --- /dev/null +++ b/config/win/visual_studio_version.gni @@ -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") +} + diff --git a/gyp_chromium b/gyp_chromium index 2fc59f736..1b9328caf 100755 --- a/gyp_chromium +++ b/gyp_chromium @@ -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() diff --git a/toolchain/win/BUILD.gn b/toolchain/win/BUILD.gn index ceb0ca8d5..b567745a6 100644 --- a/toolchain/win/BUILD.gn +++ b/toolchain/win/BUILD.gn @@ -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" diff --git a/toolchain/win/setup_toolchain.py b/toolchain/win/setup_toolchain.py index 162c2e16c..bceafd121 100644 --- a/toolchain/win/setup_toolchain.py +++ b/toolchain/win/setup_toolchain.py @@ -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 ' + 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)