mapnik/scons/scons-local-4.8.0/SCons/Tool/MSCommon/MSVC/Util.py

495 lines
15 KiB
Python
Raw Normal View History

2023-06-11 12:31:23 +02:00
# 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.
"""
Helper functions for Microsoft Visual C/C++.
"""
import os
import pathlib
2023-06-11 12:31:23 +02:00
import re
from collections import (
namedtuple,
)
from ..common import debug
2023-06-11 12:31:23 +02:00
from . import Config
2024-08-21 14:52:56 +02:00
# call _initialize method upon class definition completion
class AutoInitialize:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if hasattr(cls, '_initialize') and callable(getattr(cls, '_initialize', None)):
cls._initialize()
2023-06-11 12:31:23 +02:00
# path utilities
# windows drive specification (e.g., 'C:')
_RE_DRIVESPEC = re.compile(r'^[A-Za-z][:]$', re.IGNORECASE)
# windows path separators
_OS_PATH_SEPS = (os.path.sep, os.path.altsep) if os.path.altsep else (os.path.sep,)
2023-06-11 12:31:23 +02:00
def listdir_dirs(p):
"""
Return a list of tuples for each subdirectory of the given directory path.
Each tuple is comprised of the subdirectory name and the qualified subdirectory path.
Args:
p: str
directory path
Returns:
list[tuple[str,str]]: a list of tuples
"""
dirs = []
if p and os.path.exists(p) and os.path.isdir(p):
for dir_name in os.listdir(p):
dir_path = os.path.join(p, dir_name)
if os.path.isdir(dir_path):
dirs.append((dir_name, dir_path))
return dirs
def resolve_path(p, ignore_drivespec=True):
2023-06-11 12:31:23 +02:00
"""
Make path absolute resolving any symlinks
2023-06-11 12:31:23 +02:00
Args:
p: str
system path
ignore_drivespec: bool
ignore drive specifications when True
2023-06-11 12:31:23 +02:00
Returns:
str: absolute path with symlinks resolved
2023-06-11 12:31:23 +02:00
"""
2023-06-11 12:31:23 +02:00
if p:
if ignore_drivespec and _RE_DRIVESPEC.match(p):
# don't attempt to resolve drive specification (e.g., C:)
pass
else:
# both abspath and resolve necessary for an unqualified file name
# on a mapped network drive in order to return a mapped drive letter
# path rather than a UNC path.
p = os.path.abspath(p)
try:
p = str(pathlib.Path(p).resolve())
except OSError as e:
debug(
'caught exception: path=%s, exception=%s(%s)',
repr(p), type(e).__name__, repr(str(e))
)
return p
def normalize_path(
p,
strip=True,
preserve_trailing=False,
expand=False,
realpath=True,
ignore_drivespec=True,
):
"""
Normalize path
Args:
p: str
system path
strip: bool
remove leading and trailing whitespace when True
preserve_trailing: bool
preserve trailing path separator when True
expand: bool
apply expanduser and expandvars when True
realpath: bool
make the path absolute resolving any symlinks when True
ignore_drivespec: bool
ignore drive specifications for realpath when True
Returns:
str: normalized path
"""
if p and strip:
p = p.strip()
if p:
trailing = bool(preserve_trailing and p.endswith(_OS_PATH_SEPS))
if expand:
p = os.path.expanduser(p)
p = os.path.expandvars(p)
2023-06-11 12:31:23 +02:00
p = os.path.normpath(p)
if realpath:
p = resolve_path(p, ignore_drivespec=ignore_drivespec)
2023-06-11 12:31:23 +02:00
p = os.path.normcase(p)
if trailing:
p += os.path.sep
2023-06-11 12:31:23 +02:00
return p
# msvc version and msvc toolset version regexes
re_version_prefix = re.compile('^(?P<version>[0-9]+(?:[.][0-9]+)*)(?![.]).*$')
re_msvc_version_prefix = re.compile(r'^(?P<version>[1-9][0-9]?[.][0-9]).*$')
re_msvc_version = re.compile(r'^(?P<msvc_version>[1-9][0-9]?[.][0-9])(?P<suffix>[A-Z]+)*$', re.IGNORECASE)
re_extended_version = re.compile(r'''^
(?P<version>(?:
([1-9][0-9]?[.][0-9]{1,2})| # XX.Y - XX.YY
([1-9][0-9][.][0-9]{2}[.][0-9]{1,5})| # XX.YY.Z - XX.YY.ZZZZZ
([1-9][0-9][.][0-9]{2}[.][0-9]{2}[.][0-9]{1,2}) # XX.YY.AA.B - XX.YY.AA.BB
))
(?P<suffix>[A-Z]+)*
$''', re.IGNORECASE | re.VERBOSE)
re_toolset_full = re.compile(r'''^(?:
(?:[1-9][0-9][.][0-9]{1,2})| # XX.Y - XX.YY
(?:[1-9][0-9][.][0-9]{2}[.][0-9]{1,5}) # XX.YY.Z - XX.YY.ZZZZZ
)$''', re.VERBOSE)
re_toolset_140 = re.compile(r'''^(?:
(?:14[.]0{1,2})| # 14.0 - 14.00
(?:14[.]0{2}[.]0{1,5}) # 14.00.0 - 14.00.00000
)$''', re.VERBOSE)
re_toolset_sxs = re.compile(
r'^[1-9][0-9][.][0-9]{2}[.][0-9]{2}[.][0-9]{1,2}$' # MM.mm.VV.vv format
)
# msvc sdk version regexes
re_msvc_sdk_version = re.compile(r'''^
(?P<version>(?:
([1-9][0-9]?[.][0-9])| # XX.Y
([1-9][0-9][.][0-9]{1}[.][0-9]{5}[.][0-9]{1,2}) # XX.Y.ZZZZZ.A - XX.Y.ZZZZZ.AA
))
$''', re.IGNORECASE | re.VERBOSE)
# version prefix utilities
def get_version_prefix(version):
"""
Get the version number prefix from a string.
Args:
version: str
version specification
Returns:
str: the version number prefix
"""
rval = ''
if version:
m = re_version_prefix.match(version)
if m:
rval = m.group('version')
return rval
def get_msvc_version_prefix(version):
"""
Get the msvc version number prefix from a string.
Args:
version: str
version specification
Returns:
str: the msvc version number prefix
"""
rval = ''
if version:
m = re_msvc_version_prefix.match(version)
if m:
rval = m.group('version')
return rval
2024-08-21 14:52:56 +02:00
def get_msvc_version_prefix_suffix(version):
"""
Get the msvc version number prefix and suffix from a string.
Args:
version: str
version specification
Returns:
(str, str): the msvc version prefix and suffix
"""
prefix = suffix = ''
if version:
m = re_msvc_version.match(version)
if m:
prefix = m.group('msvc_version')
suffix = m.group('suffix') if m.group('suffix') else ''
return prefix, suffix
2023-06-11 12:31:23 +02:00
# toolset version query utilities
def is_toolset_full(toolset_version) -> bool:
2023-06-11 12:31:23 +02:00
rval = False
if toolset_version:
if re_toolset_full.match(toolset_version):
rval = True
return rval
def is_toolset_140(toolset_version) -> bool:
2023-06-11 12:31:23 +02:00
rval = False
if toolset_version:
if re_toolset_140.match(toolset_version):
rval = True
return rval
def is_toolset_sxs(toolset_version) -> bool:
2023-06-11 12:31:23 +02:00
rval = False
if toolset_version:
if re_toolset_sxs.match(toolset_version):
rval = True
return rval
# msvc version and msvc toolset version decomposition utilties
_MSVC_VERSION_COMPONENTS_DEFINITION = namedtuple('MSVCVersionComponentsDefinition', [
'msvc_version', # msvc version (e.g., '14.1Exp')
'msvc_verstr', # msvc version numeric string (e.g., '14.1')
'msvc_suffix', # msvc version component type (e.g., 'Exp')
'msvc_vernum', # msvc version floating point number (e.g, 14.1)
'msvc_major', # msvc major version integer number (e.g., 14)
'msvc_minor', # msvc minor version integer number (e.g., 1)
'msvc_comps', # msvc version components tuple (e.g., ('14', '1'))
])
def msvc_version_components(vcver):
"""
Decompose an msvc version into components.
Tuple fields:
msvc_version: msvc version (e.g., '14.1Exp')
msvc_verstr: msvc version numeric string (e.g., '14.1')
msvc_suffix: msvc version component type (e.g., 'Exp')
msvc_vernum: msvc version floating point number (e.g., 14.1)
msvc_major: msvc major version integer number (e.g., 14)
msvc_minor: msvc minor version integer number (e.g., 1)
msvc_comps: msvc version components tuple (e.g., ('14', '1'))
Args:
vcver: str
msvc version specification
Returns:
None or MSVCVersionComponents namedtuple:
"""
if not vcver:
return None
m = re_msvc_version.match(vcver)
if not m:
return None
vs_def = Config.MSVC_VERSION_SUFFIX.get(vcver)
if not vs_def:
return None
msvc_version = vcver
msvc_verstr = m.group('msvc_version')
msvc_suffix = m.group('suffix') if m.group('suffix') else ''
msvc_vernum = float(msvc_verstr)
msvc_comps = tuple(msvc_verstr.split('.'))
msvc_major, msvc_minor = (int(x) for x in msvc_comps)
2023-06-11 12:31:23 +02:00
msvc_version_components_def = _MSVC_VERSION_COMPONENTS_DEFINITION(
msvc_version = msvc_version,
msvc_verstr = msvc_verstr,
msvc_suffix = msvc_suffix,
msvc_vernum = msvc_vernum,
msvc_major = msvc_major,
msvc_minor = msvc_minor,
msvc_comps = msvc_comps,
)
return msvc_version_components_def
_MSVC_EXTENDED_VERSION_COMPONENTS_DEFINITION = namedtuple('MSVCExtendedVersionComponentsDefinition', [
2024-08-21 14:52:56 +02:00
'msvc_version', # msvc version (e.g., '14.1Exp')
'msvc_verstr', # msvc version numeric string (e.g., '14.1')
'msvc_suffix', # msvc version component type (e.g., 'Exp')
'msvc_suffix_rank', # msvc version component rank (0, 1)
'msvc_vernum', # msvc version floating point number (e.g, 14.1)
'msvc_major', # msvc major version integer number (e.g., 14)
'msvc_minor', # msvc minor version integer number (e.g., 1)
'msvc_comps', # msvc version components tuple (e.g., ('14', '1'))
'msvc_buildtools', # msvc build tools
'msvc_buildtools_num', # msvc build tools integer number
'msvc_buildseries', # msvc build series
'msvc_buildseries_num', # msvc build series floating point number
2023-06-11 12:31:23 +02:00
'msvc_toolset_version', # msvc toolset version
'msvc_toolset_comps', # msvc toolset version components
2024-08-21 14:52:56 +02:00
'msvc_toolset_is_sxs', # msvc toolset version is sxs
2023-06-11 12:31:23 +02:00
'version', # msvc version or msvc toolset version
])
def msvc_extended_version_components(version):
"""
Decompose an msvc version or msvc toolset version into components.
Args:
version: str
version specification
Returns:
None or MSVCExtendedVersionComponents namedtuple:
"""
if not version:
return None
m = re_extended_version.match(version)
if not m:
return None
msvc_toolset_version = m.group('version')
msvc_toolset_comps = tuple(msvc_toolset_version.split('.'))
2024-08-21 14:52:56 +02:00
msvc_toolset_is_sxs = is_toolset_sxs(msvc_toolset_version)
2023-06-11 12:31:23 +02:00
2024-08-21 14:52:56 +02:00
vc_verstr = get_msvc_version_prefix(msvc_toolset_version)
if not vc_verstr:
2023-06-11 12:31:23 +02:00
return None
2024-08-21 14:52:56 +02:00
vc_buildseries_def = Config.MSVC_BUILDSERIES_EXTERNAL.get(vc_verstr)
if not vc_buildseries_def:
return None
vc_buildtools_def = Config.VC_BUILDTOOLS_MAP[vc_buildseries_def.vc_buildseries]
msvc_verstr = vc_buildtools_def.msvc_version
2023-06-11 12:31:23 +02:00
msvc_suffix = m.group('suffix') if m.group('suffix') else ''
msvc_version = msvc_verstr + msvc_suffix
vs_def = Config.MSVC_VERSION_SUFFIX.get(msvc_version)
if not vs_def:
return None
msvc_vernum = float(msvc_verstr)
msvc_comps = tuple(msvc_verstr.split('.'))
msvc_major, msvc_minor = (int(x) for x in msvc_comps)
2023-06-11 12:31:23 +02:00
msvc_extended_version_components_def = _MSVC_EXTENDED_VERSION_COMPONENTS_DEFINITION(
msvc_version = msvc_version,
msvc_verstr = msvc_verstr,
msvc_suffix = msvc_suffix,
2024-08-21 14:52:56 +02:00
msvc_suffix_rank = 0 if not msvc_suffix else 1,
2023-06-11 12:31:23 +02:00
msvc_vernum = msvc_vernum,
msvc_major = msvc_major,
msvc_minor = msvc_minor,
msvc_comps = msvc_comps,
2024-08-21 14:52:56 +02:00
msvc_buildtools = vc_buildtools_def.msvc_version,
msvc_buildtools_num = vc_buildtools_def.msvc_version_numeric,
msvc_buildseries = vc_buildseries_def.vc_version,
msvc_buildseries_num = vc_buildseries_def.vc_version_numeric,
2023-06-11 12:31:23 +02:00
msvc_toolset_version = msvc_toolset_version,
msvc_toolset_comps = msvc_toolset_comps,
2024-08-21 14:52:56 +02:00
msvc_toolset_is_sxs = msvc_toolset_is_sxs,
2023-06-11 12:31:23 +02:00
version = version,
)
return msvc_extended_version_components_def
# msvc sdk version decomposition utilties
_MSVC_SDK_VERSION_COMPONENTS_DEFINITION = namedtuple('MSVCSDKVersionComponentsDefinition', [
'sdk_version', # sdk version (e.g., '10.0.20348.0')
'sdk_verstr', # sdk version numeric string (e.g., '10.0')
'sdk_vernum', # sdk version floating point number (e.g, 10.0)
'sdk_major', # sdk major version integer number (e.g., 10)
'sdk_minor', # sdk minor version integer number (e.g., 0)
'sdk_comps', # sdk version components tuple (e.g., ('10', '0', '20348', '0'))
])
def msvc_sdk_version_components(version):
"""
Decompose an msvc sdk version into components.
Tuple fields:
sdk_version: sdk version (e.g., '10.0.20348.0')
sdk_verstr: sdk version numeric string (e.g., '10.0')
sdk_vernum: sdk version floating point number (e.g., 10.0)
sdk_major: sdk major version integer number (e.g., 10)
sdk_minor: sdk minor version integer number (e.g., 0)
sdk_comps: sdk version components tuple (e.g., ('10', '0', '20348', '0'))
Args:
version: str
sdk version specification
Returns:
None or MSVCSDKVersionComponents namedtuple:
"""
if not version:
return None
m = re_msvc_sdk_version.match(version)
if not m:
return None
sdk_version = version
sdk_comps = tuple(sdk_version.split('.'))
sdk_verstr = '.'.join(sdk_comps[:2])
sdk_vernum = float(sdk_verstr)
sdk_major, sdk_minor = (int(x) for x in sdk_comps[:2])
2023-06-11 12:31:23 +02:00
msvc_sdk_version_components_def = _MSVC_SDK_VERSION_COMPONENTS_DEFINITION(
sdk_version = sdk_version,
sdk_verstr = sdk_verstr,
sdk_vernum = sdk_vernum,
sdk_major = sdk_major,
sdk_minor = sdk_minor,
sdk_comps = sdk_comps,
)
return msvc_sdk_version_components_def