mapnik/scons/scons-local-4.8.1/SCons/Tool/MSCommon/MSVC/Kind.py
2024-09-09 10:56:17 +01:00

668 lines
20 KiB
Python
Vendored

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
Version kind categorization for Microsoft Visual C/C++.
"""
import os
import re
from collections import (
namedtuple,
)
from ..common import (
debug,
)
from . import Registry
from . import Util
from . import Dispatcher
Dispatcher.register_modulename(__name__)
# use express install for non-express msvc_version if no other installations found
USE_EXPRESS_FOR_NONEXPRESS = True
# productdir kind
VCVER_KIND_UNKNOWN = 0 # undefined
VCVER_KIND_DEVELOP = 1 # devenv binary
VCVER_KIND_EXPRESS = 2 # express binary
VCVER_KIND_BTDISPATCH = 3 # no ide binaries (buildtools dispatch folder)
VCVER_KIND_VCFORPYTHON = 4 # no ide binaries (2008/9.0)
VCVER_KIND_EXPRESS_WIN = 5 # express for windows binary (VSWinExpress)
VCVER_KIND_EXPRESS_WEB = 6 # express for web binary (VWDExpress)
VCVER_KIND_SDK = 7 # no ide binaries
VCVER_KIND_CMDLINE = 8 # no ide binaries
VCVER_KIND_STR = {
VCVER_KIND_UNKNOWN: '<Unknown>',
VCVER_KIND_DEVELOP: 'Develop',
VCVER_KIND_EXPRESS: 'Express',
VCVER_KIND_BTDISPATCH: 'BTDispatch',
VCVER_KIND_VCFORPYTHON: 'VCForPython',
VCVER_KIND_EXPRESS_WIN: 'Express-Win',
VCVER_KIND_EXPRESS_WEB: 'Express-Web',
VCVER_KIND_SDK: 'SDK',
VCVER_KIND_CMDLINE: 'CmdLine',
}
BITFIELD_KIND_DEVELOP = 0b_1000
BITFIELD_KIND_EXPRESS = 0b_0100
BITFIELD_KIND_EXPRESS_WIN = 0b_0010
BITFIELD_KIND_EXPRESS_WEB = 0b_0001
VCVER_KIND_PROGRAM = namedtuple("VCVerKindProgram", [
'kind', # relpath from pdir to vsroot
'program', # ide binaries
'bitfield',
])
#
IDE_PROGRAM_DEVENV_COM = VCVER_KIND_PROGRAM(
kind=VCVER_KIND_DEVELOP,
program='devenv.com',
bitfield=BITFIELD_KIND_DEVELOP,
)
IDE_PROGRAM_MSDEV_COM = VCVER_KIND_PROGRAM(
kind=VCVER_KIND_DEVELOP,
program='msdev.com',
bitfield=BITFIELD_KIND_DEVELOP,
)
IDE_PROGRAM_WDEXPRESS_EXE = VCVER_KIND_PROGRAM(
kind=VCVER_KIND_EXPRESS,
program='WDExpress.exe',
bitfield=BITFIELD_KIND_EXPRESS,
)
IDE_PROGRAM_VCEXPRESS_EXE = VCVER_KIND_PROGRAM(
kind=VCVER_KIND_EXPRESS,
program='VCExpress.exe',
bitfield=BITFIELD_KIND_EXPRESS,
)
IDE_PROGRAM_VSWINEXPRESS_EXE = VCVER_KIND_PROGRAM(
kind=VCVER_KIND_EXPRESS_WIN,
program='VSWinExpress.exe',
bitfield=BITFIELD_KIND_EXPRESS_WIN,
)
IDE_PROGRAM_VWDEXPRESS_EXE = VCVER_KIND_PROGRAM(
kind=VCVER_KIND_EXPRESS_WEB,
program='VWDExpress.exe',
bitfield=BITFIELD_KIND_EXPRESS_WEB,
)
# detection configuration
VCVER_KIND_DETECT = namedtuple("VCVerKindDetect", [
'root', # relpath from pdir to vsroot
'path', # vsroot to ide dir
'programs', # ide binaries
])
# detected binaries
VCVER_DETECT_BINARIES = namedtuple("VCVerDetectBinaries", [
'bitfields', # detect values
'have_dev', # develop ide binary
'have_exp', # express ide binary
'have_exp_win', # express windows ide binary
'have_exp_web', # express web ide binary
])
VCVER_DETECT_KIND = namedtuple("VCVerDetectKind", [
'skip', # skip vs root
'save', # save in case no other kind found
'kind', # internal kind
'binaries_t',
'extended',
])
# unknown value
_VCVER_DETECT_KIND_UNKNOWN = VCVER_DETECT_KIND(
skip=True,
save=False,
kind=VCVER_KIND_UNKNOWN,
binaries_t=VCVER_DETECT_BINARIES(
bitfields=0b0,
have_dev=False,
have_exp=False,
have_exp_win=False,
have_exp_web=False,
),
extended={},
)
#
_msvc_pdir_func = None
def register_msvc_version_pdir_func(func):
global _msvc_pdir_func
if func:
_msvc_pdir_func = func
_cache_vcver_kind_map = {}
def msvc_version_register_kind(msvc_version, kind_t) -> None:
global _cache_vcver_kind_map
if kind_t is None:
kind_t = _VCVER_DETECT_KIND_UNKNOWN
debug('msvc_version=%s, kind=%s', repr(msvc_version), repr(VCVER_KIND_STR[kind_t.kind]))
_cache_vcver_kind_map[msvc_version] = kind_t
def _msvc_version_kind_lookup(msvc_version, env=None):
global _cache_vcver_kind_map
global _msvc_pdir_func
if msvc_version not in _cache_vcver_kind_map:
_msvc_pdir_func(msvc_version, env)
kind_t = _cache_vcver_kind_map.get(msvc_version, _VCVER_DETECT_KIND_UNKNOWN)
debug(
'kind=%s, dev=%s, exp=%s, msvc_version=%s',
repr(VCVER_KIND_STR[kind_t.kind]),
kind_t.binaries_t.have_dev, kind_t.binaries_t.have_exp,
repr(msvc_version)
)
return kind_t
def msvc_version_is_btdispatch(msvc_version, env=None):
kind_t = _msvc_version_kind_lookup(msvc_version, env)
is_btdispatch = bool(kind_t.kind == VCVER_KIND_BTDISPATCH)
debug(
'is_btdispatch=%s, kind:%s, msvc_version=%s',
repr(is_btdispatch), repr(VCVER_KIND_STR[kind_t.kind]), repr(msvc_version)
)
return is_btdispatch
def msvc_version_is_express(msvc_version, env=None):
kind_t = _msvc_version_kind_lookup(msvc_version, env)
is_express = bool(kind_t.kind == VCVER_KIND_EXPRESS)
debug(
'is_express=%s, kind:%s, msvc_version=%s',
repr(is_express), repr(VCVER_KIND_STR[kind_t.kind]), repr(msvc_version)
)
return is_express
def msvc_version_is_vcforpython(msvc_version, env=None):
kind_t = _msvc_version_kind_lookup(msvc_version, env)
is_vcforpython = bool(kind_t.kind == VCVER_KIND_VCFORPYTHON)
debug(
'is_vcforpython=%s, kind:%s, msvc_version=%s',
repr(is_vcforpython), repr(VCVER_KIND_STR[kind_t.kind]), repr(msvc_version)
)
return is_vcforpython
def msvc_version_skip_uwp_target(env, msvc_version):
vernum = float(Util.get_msvc_version_prefix(msvc_version))
vernum_int = int(vernum * 10)
if vernum_int != 140:
return False
kind_t = _msvc_version_kind_lookup(msvc_version, env)
if kind_t.kind != VCVER_KIND_EXPRESS:
return False
target_arch = env.get('TARGET_ARCH')
uwp_is_supported = kind_t.extended.get('uwp_is_supported', {})
is_supported = uwp_is_supported.get(target_arch, True)
if is_supported:
return False
return True
def _pdir_detect_binaries(pdir, detect):
vs_root = os.path.join(pdir, detect.root)
ide_path = os.path.join(vs_root, detect.path)
bitfields = 0b_0000
for ide_program in detect.programs:
prog = os.path.join(ide_path, ide_program.program)
if not os.path.exists(prog):
continue
bitfields |= ide_program.bitfield
have_dev = bool(bitfields & BITFIELD_KIND_DEVELOP)
have_exp = bool(bitfields & BITFIELD_KIND_EXPRESS)
have_exp_win = bool(bitfields & BITFIELD_KIND_EXPRESS_WIN)
have_exp_web = bool(bitfields & BITFIELD_KIND_EXPRESS_WEB)
binaries_t = VCVER_DETECT_BINARIES(
bitfields=bitfields,
have_dev=have_dev,
have_exp=have_exp,
have_exp_win=have_exp_win,
have_exp_web=have_exp_web,
)
debug(
'vs_root=%s, dev=%s, exp=%s, exp_win=%s, exp_web=%s, pdir=%s',
repr(vs_root),
binaries_t.have_dev, binaries_t.have_exp,
binaries_t.have_exp_win, binaries_t.have_exp_web,
repr(pdir)
)
return vs_root, binaries_t
_cache_pdir_vswhere_kind = {}
def msvc_version_pdir_vswhere_kind(msvc_version, pdir, detect_t):
global _cache_pdir_vswhere_kind
vc_dir = os.path.normcase(os.path.normpath(pdir))
cache_key = (msvc_version, vc_dir)
rval = _cache_pdir_vswhere_kind.get(cache_key)
if rval is not None:
debug('cache=%s', repr(rval))
return rval
extended = {}
prefix, suffix = Util.get_msvc_version_prefix_suffix(msvc_version)
vs_root, binaries_t = _pdir_detect_binaries(pdir, detect_t)
if binaries_t.have_dev:
kind = VCVER_KIND_DEVELOP
elif binaries_t.have_exp:
kind = VCVER_KIND_EXPRESS
else:
kind = VCVER_KIND_CMDLINE
skip = False
save = False
if suffix != 'Exp' and kind == VCVER_KIND_EXPRESS:
skip = True
save = USE_EXPRESS_FOR_NONEXPRESS
elif suffix == 'Exp' and kind != VCVER_KIND_EXPRESS:
skip = True
kind_t = VCVER_DETECT_KIND(
skip=skip,
save=save,
kind=kind,
binaries_t=binaries_t,
extended=extended,
)
debug(
'skip=%s, save=%s, kind=%s, msvc_version=%s, pdir=%s',
kind_t.skip, kind_t.save, repr(VCVER_KIND_STR[kind_t.kind]),
repr(msvc_version), repr(pdir)
)
_cache_pdir_vswhere_kind[cache_key] = kind_t
return kind_t
# VS2015 buildtools batch file call detection
# vs2015 buildtools do not support sdk_version or UWP arguments
_VS2015BT_PATH = r'..\Microsoft Visual C++ Build Tools\vcbuildtools.bat'
_VS2015BT_REGEX_STR = ''.join([
r'^\s*if\s+exist\s+',
re.escape(fr'"%~dp0..\{_VS2015BT_PATH}"'),
r'\s+goto\s+setup_buildsku\s*$',
])
_VS2015BT_VCVARS_BUILDTOOLS = re.compile(_VS2015BT_REGEX_STR, re.IGNORECASE)
_VS2015BT_VCVARS_STOP = re.compile(r'^\s*[:]Setup_VS\s*$', re.IGNORECASE)
def _vs_buildtools_2015_vcvars(vcvars_file):
have_buildtools_vcvars = False
with open(vcvars_file) as fh:
for line in fh:
if _VS2015BT_VCVARS_BUILDTOOLS.match(line):
have_buildtools_vcvars = True
break
if _VS2015BT_VCVARS_STOP.match(line):
break
return have_buildtools_vcvars
def _vs_buildtools_2015(vs_root, vc_dir):
is_btdispatch = False
do_once = True
while do_once:
do_once = False
buildtools_file = os.path.join(vs_root, _VS2015BT_PATH)
have_buildtools = os.path.exists(buildtools_file)
debug('have_buildtools=%s', have_buildtools)
if not have_buildtools:
break
vcvars_file = os.path.join(vc_dir, 'vcvarsall.bat')
have_vcvars = os.path.exists(vcvars_file)
debug('have_vcvars=%s', have_vcvars)
if not have_vcvars:
break
have_buildtools_vcvars = _vs_buildtools_2015_vcvars(vcvars_file)
debug('have_buildtools_vcvars=%s', have_buildtools_vcvars)
if not have_buildtools_vcvars:
break
is_btdispatch = True
debug('is_btdispatch=%s', is_btdispatch)
return is_btdispatch
_VS2015EXP_VCVARS_LIBPATH = re.compile(
''.join([
r'^\s*\@if\s+exist\s+\"\%VCINSTALLDIR\%LIB\\store\\(amd64|arm)"\s+',
r'set (LIB|LIBPATH)=\%VCINSTALLDIR\%LIB\\store\\(amd64|arm);.*\%(LIB|LIBPATH)\%\s*$'
]),
re.IGNORECASE
)
_VS2015EXP_VCVARS_STOP = re.compile(r'^\s*[:]GetVSCommonToolsDir\s*$', re.IGNORECASE)
def _vs_express_2015_vcvars(vcvars_file):
n_libpath = 0
with open(vcvars_file) as fh:
for line in fh:
if _VS2015EXP_VCVARS_LIBPATH.match(line):
n_libpath += 1
elif _VS2015EXP_VCVARS_STOP.match(line):
break
have_uwp_fix = n_libpath >= 2
return have_uwp_fix
def _vs_express_2015(pdir):
have_uwp_amd64 = False
have_uwp_arm = False
vcvars_file = os.path.join(pdir, r'vcvarsall.bat')
if os.path.exists(vcvars_file):
vcvars_file = os.path.join(pdir, r'bin\x86_amd64\vcvarsx86_amd64.bat')
if os.path.exists(vcvars_file):
have_uwp_fix = _vs_express_2015_vcvars(vcvars_file)
if have_uwp_fix:
have_uwp_amd64 = True
vcvars_file = os.path.join(pdir, r'bin\x86_arm\vcvarsx86_arm.bat')
if os.path.exists(vcvars_file):
have_uwp_fix = _vs_express_2015_vcvars(vcvars_file)
if have_uwp_fix:
have_uwp_arm = True
debug('have_uwp_amd64=%s, have_uwp_arm=%s', have_uwp_amd64, have_uwp_arm)
return have_uwp_amd64, have_uwp_arm
# winsdk installed 2010 [7.1], 2008 [7.0, 6.1] folders
_REGISTRY_WINSDK_VERSIONS = {'10.0', '9.0'}
_cache_pdir_registry_winsdk = {}
def _msvc_version_pdir_registry_winsdk(msvc_version, pdir):
global _cache_pdir_registry_winsdk
# detect winsdk-only installations
#
# registry keys:
# [prefix]\VisualStudio\SxS\VS7\10.0 <undefined>
# [prefix]\VisualStudio\SxS\VC7\10.0 product directory
# [prefix]\VisualStudio\SxS\VS7\9.0 <undefined>
# [prefix]\VisualStudio\SxS\VC7\9.0 product directory
#
# winsdk notes:
# - winsdk installs do not define the common tools env var
# - the product dir is detected but the vcvars batch files will fail
# - regular installations populate the VS7 registry keys
#
vc_dir = os.path.normcase(os.path.normpath(pdir))
cache_key = (msvc_version, vc_dir)
rval = _cache_pdir_registry_winsdk.get(cache_key)
if rval is not None:
debug('cache=%s', repr(rval))
return rval
if msvc_version not in _REGISTRY_WINSDK_VERSIONS:
is_sdk = False
debug('is_sdk=%s, msvc_version=%s', is_sdk, repr(msvc_version))
else:
vc_dir = os.path.normcase(os.path.normpath(pdir))
vc_suffix = Registry.vstudio_sxs_vc7(msvc_version)
vc_qresults = [record[0] for record in Registry.microsoft_query_paths(vc_suffix)]
vc_root = os.path.normcase(os.path.normpath(vc_qresults[0])) if vc_qresults else None
if vc_dir != vc_root:
# registry vc path is not the current pdir
is_sdk = False
debug(
'is_sdk=%s, msvc_version=%s, pdir=%s, vc_root=%s',
is_sdk, repr(msvc_version), repr(vc_dir), repr(vc_root)
)
else:
# registry vc path is the current pdir
vs_suffix = Registry.vstudio_sxs_vs7(msvc_version)
vs_qresults = [record[0] for record in Registry.microsoft_query_paths(vs_suffix)]
vs_root = vs_qresults[0] if vs_qresults else None
is_sdk = bool(not vs_root and vc_root)
debug(
'is_sdk=%s, msvc_version=%s, vs_root=%s, vc_root=%s',
is_sdk, repr(msvc_version), repr(vs_root), repr(vc_root)
)
_cache_pdir_registry_winsdk[cache_key] = is_sdk
return is_sdk
_cache_pdir_registry_kind = {}
def msvc_version_pdir_registry_kind(msvc_version, pdir, detect_t, is_vcforpython=False):
global _cache_pdir_registry_kind
vc_dir = os.path.normcase(os.path.normpath(pdir))
cache_key = (msvc_version, vc_dir)
rval = _cache_pdir_registry_kind.get(cache_key)
if rval is not None:
debug('cache=%s', repr(rval))
return rval
extended = {}
prefix, suffix = Util.get_msvc_version_prefix_suffix(msvc_version)
vs_root, binaries_t = _pdir_detect_binaries(pdir, detect_t)
if binaries_t.have_dev:
kind = VCVER_KIND_DEVELOP
elif binaries_t.have_exp:
kind = VCVER_KIND_EXPRESS
elif msvc_version == '14.0' and _vs_buildtools_2015(vs_root, pdir):
kind = VCVER_KIND_BTDISPATCH
elif msvc_version == '9.0' and is_vcforpython:
kind = VCVER_KIND_VCFORPYTHON
elif binaries_t.have_exp_win:
kind = VCVER_KIND_EXPRESS_WIN
elif binaries_t.have_exp_web:
kind = VCVER_KIND_EXPRESS_WEB
elif _msvc_version_pdir_registry_winsdk(msvc_version, pdir):
kind = VCVER_KIND_SDK
else:
kind = VCVER_KIND_CMDLINE
skip = False
save = False
if kind in (VCVER_KIND_EXPRESS_WIN, VCVER_KIND_EXPRESS_WEB, VCVER_KIND_SDK):
skip = True
elif suffix != 'Exp' and kind == VCVER_KIND_EXPRESS:
skip = True
save = USE_EXPRESS_FOR_NONEXPRESS
elif suffix == 'Exp' and kind != VCVER_KIND_EXPRESS:
skip = True
if prefix == '14.0' and kind == VCVER_KIND_EXPRESS:
have_uwp_amd64, have_uwp_arm = _vs_express_2015(pdir)
uwp_is_supported = {
'x86': True,
'amd64': have_uwp_amd64,
'arm': have_uwp_arm,
}
extended['uwp_is_supported'] = uwp_is_supported
kind_t = VCVER_DETECT_KIND(
skip=skip,
save=save,
kind=kind,
binaries_t=binaries_t,
extended=extended,
)
debug(
'skip=%s, save=%s, kind=%s, msvc_version=%s, pdir=%s',
kind_t.skip, kind_t.save, repr(VCVER_KIND_STR[kind_t.kind]),
repr(msvc_version), repr(pdir)
)
_cache_pdir_registry_kind[cache_key] = kind_t
return kind_t
# queries
def get_msvc_version_kind(msvc_version, env=None):
kind_t = _msvc_version_kind_lookup(msvc_version, env)
kind_str = VCVER_KIND_STR[kind_t.kind]
debug(
'kind=%s, kind_str=%s, msvc_version=%s',
repr(kind_t.kind), repr(kind_str), repr(msvc_version)
)
return (kind_t.kind, kind_str)
def msvc_version_sdk_version_is_supported(msvc_version, env=None):
vernum = float(Util.get_msvc_version_prefix(msvc_version))
vernum_int = int(vernum * 10)
kind_t = _msvc_version_kind_lookup(msvc_version, env)
if vernum_int >= 141:
# VS2017 and later
is_supported = True
elif vernum_int == 140:
# VS2015:
# True: Develop, CmdLine
# False: Express, BTDispatch
is_supported = True
if kind_t.kind == VCVER_KIND_EXPRESS:
is_supported = False
elif kind_t.kind == VCVER_KIND_BTDISPATCH:
is_supported = False
else:
# VS2013 and earlier
is_supported = False
debug(
'is_supported=%s, msvc_version=%s, kind=%s',
is_supported, repr(msvc_version), repr(VCVER_KIND_STR[kind_t.kind])
)
return is_supported
def msvc_version_uwp_is_supported(msvc_version, target_arch=None, env=None):
vernum = float(Util.get_msvc_version_prefix(msvc_version))
vernum_int = int(vernum * 10)
kind_t = _msvc_version_kind_lookup(msvc_version, env)
is_target = False
if vernum_int >= 141:
# VS2017 and later
is_supported = True
elif vernum_int == 140:
# VS2015:
# True: Develop, CmdLine
# Maybe: Express
# False: BTDispatch
is_supported = True
if kind_t.kind == VCVER_KIND_EXPRESS:
uwp_is_supported = kind_t.extended.get('uwp_is_supported', {})
is_supported = uwp_is_supported.get(target_arch, True)
is_target = True
elif kind_t.kind == VCVER_KIND_BTDISPATCH:
is_supported = False
else:
# VS2013 and earlier
is_supported = False
debug(
'is_supported=%s, is_target=%s, msvc_version=%s, kind=%s, target_arch=%s',
is_supported, is_target, repr(msvc_version), repr(VCVER_KIND_STR[kind_t.kind]), repr(target_arch)
)
return is_supported, is_target
# reset cache
def reset() -> None:
global _cache_vcver_kind_map
global _cache_pdir_vswhere_kind
global _cache_pdir_registry_kind
global _cache_pdir_registry_winsdk
debug('')
_cache_vcver_kind_map = {}
_cache_pdir_vswhere_kind = {}
_cache_pdir_registry_kind = {}
_cache_pdir_registry_winsdk = {}