0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-11-30 00:56:44 +01:00
mongodb/site_scons/site_tools/vcredist.py

233 lines
8.2 KiB
Python

# Copyright 2019 MongoDB Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import re
import subprocess
import winreg
import SCons
def exists(env):
result = "msvc" in env["TOOLS"]
return result
# How to locate the Merge Modules path is described in:
#
# - VS2019: https://docs.microsoft.com/en-us/visualstudio/releases/2019/redistribution#visual-c-runtime-files
# - VS2017: https://docs.microsoft.com/en-us/visualstudio/productinfo/2017-redistribution-vs#visual-c-runtime-files
# - VS2015: https://docs.microsoft.com/en-us/visualstudio/productinfo/2015-redistribution-vs#visual-c-runtime
#
# However, please note that for VS2017 an VS2019, the documented paths are incorrect, per this
# discussion:
#
# - https://developercommunity.visualstudio.com/content/problem/828060/what-are-the-correct-location-to-search-for-vc-crt.html
#
# This tool uses the currently undocumented but correct paths.
# The keys are the values SCons accepts for TARGET_ARCH to name
# different windows targets, the values are the tag that VS uses
# for the associated redistributable for that platform.
#
# TODO: Expand this map as needed.
target_arch_expansion_map = {
"amd64": "x64",
"arm": None,
"arm64": "arm64",
"emt64": "x64",
"i386": "x86",
"x86": "x86",
"x86_64": "x64",
}
def _get_programfiles():
result = os.getenv("ProgramFiles(x86)")
# When we run this under cygwin, the environment is broken, fall
# back to hard coded C:\Program Files (x86)
if result is None:
result = "C:\\Program Files (x86)"
if not os.path.isdir(result):
return None
return result
def _get_merge_module_name_for_feature(env, feature):
version_components = env["MSVC_VERSION"].split(".")
return "Microsoft_VC{msvc_major}{msvc_minor}_{feature}_{target_arch}.msm".format(
msvc_major=version_components[0],
msvc_minor=version_components[1],
feature=feature,
target_arch=target_arch_expansion_map[env.subst("$TARGET_ARCH")],
)
def generate(env):
if not exists(env):
return
env.Tool("msvc")
env.AddMethod(_get_merge_module_name_for_feature, "GetMergeModuleNameForFeature")
# Obtain the major and minor versions of the curently configured MSVC
# and ensure that we are using a VC 14 based toolchain.
#
# Please see
# https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering
# for details on the various version numbers in play for
# the Microsoft toolchain.
msvc_major, msvc_minor = env["MSVC_VERSION"].split(".")
if msvc_major != "14":
return
# We may or may not need to figure out the path to Program files,
# depending on the various paths we take throught this logic.
programfilesx86 = None
# TODO: Getting this path is a start, but we should later provide
# an abstraction over the names of the merge modules
# themselves. They seem to have the form
# Microsoft_VC{msvc_major}{msvc_minor}_{Feature}_{target_arch}.msm. It
# would be useful to provide an env.MergeModuleNameFor('feature')
# that consulted the values we have found here and used
# TARGET_ARCH (normalized somehow) to select the right one.
mergemodulepath = None
# On VS2015 the merge modules are in the program files directory,
# not under the VS install dir.
if msvc_minor == "0":
if not programfilesx86:
programfilesx86 = _get_programfiles()
if not programfilesx86:
return
mergemodulepath = os.path.join(programfilesx86, "Common Files", "Merge Modules")
if os.path.isdir(mergemodulepath):
env["MSVS"]["VCREDISTMERGEMODULEPATH"] = mergemodulepath
if not "VSINSTALLDIR" in env["MSVS"]:
# Compute a VS version based on the VC version. VC 14.0 is VS 2015, VC
# 14.1 is VS 2017. Also compute the next theoretical version by
# incrementing the major version by 1. Then form a range from this
# that we can use as an argument to the -version flag to vswhere.
vs_version = int(msvc_major) + int(msvc_minor)
vs_version_next = vs_version + 1
vs_version_range = "[{vs_version}.0, {vs_version_next}.0)".format(
vs_version=vs_version, vs_version_next=vs_version_next
)
if not programfilesx86:
programfilesx86 = _get_programfiles()
if not programfilesx86:
return
# Use vswhere (it has a fixed stable path) to query where Visual Studio is installed.
env["MSVS"]["VSINSTALLDIR"] = (
subprocess.check_output(
[
os.path.join(
programfilesx86,
"Microsoft Visual Studio",
"Installer",
"vswhere.exe",
),
"-version",
vs_version_range,
"-property",
"installationPath",
"-nologo",
]
)
.decode("utf-8")
.strip()
)
vsinstall_dir = env["MSVS"]["VSINSTALLDIR"]
# Combine and set the full merge module path
redist_root = os.path.join(vsinstall_dir, "VC", "Redist", "MSVC")
if not os.path.isdir(redist_root):
return
env["MSVS"]["VCREDISTROOT"] = redist_root
# Check the registry key that has the runtime lib version
try:
# TOOO: This x64 needs to be abstracted away. Is it the host
# arch, or the target arch? My guess is host.
vsruntime_key_name = "SOFTWARE\\Microsoft\\VisualStudio\\{msvc_major}.0\\VC\\Runtimes\\x64".format(
msvc_major=msvc_major
)
vsruntime_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, vsruntime_key_name)
vslib_version, vslib_version_type = winreg.QueryValueEx(
vsruntime_key, "Version"
)
except WindowsError:
return
# Fallback to directory search if we don't find the expected version
redist_path = os.path.join(
redist_root, re.match("v(\d+\.\d+\.\d+)\.\d+", vslib_version).group(1)
)
if not os.path.isdir(redist_path):
redist_path = None
dirs = os.listdir(redist_root)
dirs.sort()
for dir in reversed(dirs):
candidate = os.path.join(redist_root, dir)
if os.path.isdir(candidate):
redist_path = candidate
break
else:
return
env["MSVS"]["VCREDISTPATH"] = redist_path
if mergemodulepath is None and msvc_minor != "0":
mergemodulepath = os.path.join(redist_path, "MergeModules")
if os.path.isdir(mergemodulepath):
env["MSVS"]["VCREDISTMERGEMODULEPATH"] = mergemodulepath
# Keep these in preference order. The way with the {} in between
# the dots appears to be the more modern form, but we select the
# older one when available to minimize disruption to existing
# automation that expects the redist executable embedded in our
# packages to have this shape. Some architectures, like arm64,
# don't appear to be provided under that syntax though, so we
# include the newer form for that purpose. If Microsoft ever stops
# providing the old form, we will automatically roll forward to
# the new form.
vcredist_search_template_sequence = [
"vcredist_{}.exe",
"vc_redist.{}.exe",
]
expansion = target_arch_expansion_map.get(env.subst("$TARGET_ARCH"), None)
if not expansion:
return
vcredist_candidates = [
c.format(expansion) for c in vcredist_search_template_sequence
]
for candidate in vcredist_candidates:
candidate = os.path.join(redist_path, candidate)
if os.path.isfile(candidate):
break
else:
return
env["MSVS"]["VCREDISTEXE"] = candidate