3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 Helper functions for Microsoft Visual C/C++.
32 from collections
import (
36 from ..common
import debug
41 # call _initialize method upon class definition completion
44 def __init_subclass__(cls
, **kwargs
):
45 super().__init
_subclass
__(**kwargs
)
46 if hasattr(cls
, '_initialize') and callable(getattr(cls
, '_initialize', None)):
51 # windows drive specification (e.g., 'C:')
52 _RE_DRIVESPEC
= re
.compile(r
'^[A-Za-z][:]$', re
.IGNORECASE
)
54 # windows path separators
55 _OS_PATH_SEPS
= (os
.path
.sep
, os
.path
.altsep
) if os
.path
.altsep
else (os
.path
.sep
,)
59 Return a list of tuples for each subdirectory of the given directory path.
60 Each tuple is comprised of the subdirectory name and the qualified subdirectory path.
67 list[tuple[str,str]]: a list of tuples
71 if p
and os
.path
.exists(p
) and os
.path
.isdir(p
):
72 for dir_name
in os
.listdir(p
):
73 dir_path
= os
.path
.join(p
, dir_name
)
74 if os
.path
.isdir(dir_path
):
75 dirs
.append((dir_name
, dir_path
))
78 def resolve_path(p
, ignore_drivespec
=True):
80 Make path absolute resolving any symlinks
85 ignore_drivespec: bool
86 ignore drive specifications when True
89 str: absolute path with symlinks resolved
95 if ignore_drivespec
and _RE_DRIVESPEC
.match(p
):
96 # don't attempt to resolve drive specification (e.g., C:)
99 # both abspath and resolve necessary for an unqualified file name
100 # on a mapped network drive in order to return a mapped drive letter
101 # path rather than a UNC path.
102 p
= os
.path
.abspath(p
)
104 p
= str(pathlib
.Path(p
).resolve())
107 'caught exception: path=%s, exception=%s(%s)',
108 repr(p
), type(e
).__name
__, repr(str(e
))
116 preserve_trailing
=False,
119 ignore_drivespec
=True,
128 remove leading and trailing whitespace when True
129 preserve_trailing: bool
130 preserve trailing path separator when True
132 apply expanduser and expandvars when True
134 make the path absolute resolving any symlinks when True
135 ignore_drivespec: bool
136 ignore drive specifications for realpath when True
148 trailing
= bool(preserve_trailing
and p
.endswith(_OS_PATH_SEPS
))
151 p
= os
.path
.expanduser(p
)
152 p
= os
.path
.expandvars(p
)
154 p
= os
.path
.normpath(p
)
157 p
= resolve_path(p
, ignore_drivespec
=ignore_drivespec
)
159 p
= os
.path
.normcase(p
)
166 # msvc version and msvc toolset version regexes
168 re_version_prefix
= re
.compile('^(?P<version>[0-9]+(?:[.][0-9]+)*)(?![.]).*$')
170 re_msvc_version_prefix
= re
.compile(r
'^(?P<version>[1-9][0-9]?[.][0-9]).*$')
172 re_msvc_version
= re
.compile(r
'^(?P<msvc_version>[1-9][0-9]?[.][0-9])(?P<suffix>[A-Z]+)*$', re
.IGNORECASE
)
174 re_extended_version
= re
.compile(r
'''^
176 ([1-9][0-9]?[.][0-9]{1,2})| # XX.Y - XX.YY
177 ([1-9][0-9][.][0-9]{2}[.][0-9]{1,5})| # XX.YY.Z - XX.YY.ZZZZZ
178 ([1-9][0-9][.][0-9]{2}[.][0-9]{2}[.][0-9]{1,2}) # XX.YY.AA.B - XX.YY.AA.BB
181 $''', re
.IGNORECASE | re
.VERBOSE
)
183 re_toolset_full
= re
.compile(r
'''^(?:
184 (?:[1-9][0-9][.][0-9]{1,2})| # XX.Y - XX.YY
185 (?:[1-9][0-9][.][0-9]{2}[.][0-9]{1,5}) # XX.YY.Z - XX.YY.ZZZZZ
188 re_toolset_140
= re
.compile(r
'''^(?:
189 (?:14[.]0{1,2})| # 14.0 - 14.00
190 (?:14[.]0{2}[.]0{1,5}) # 14.00.0 - 14.00.00000
193 re_toolset_sxs
= re
.compile(
194 r
'^[1-9][0-9][.][0-9]{2}[.][0-9]{2}[.][0-9]{1,2}$' # MM.mm.VV.vv format
197 # msvc sdk version regexes
199 re_msvc_sdk_version
= re
.compile(r
'''^
201 ([1-9][0-9]?[.][0-9])| # XX.Y
202 ([1-9][0-9][.][0-9]{1}[.][0-9]{5}[.][0-9]{1,2}) # XX.Y.ZZZZZ.A - XX.Y.ZZZZZ.AA
204 $''', re
.IGNORECASE | re
.VERBOSE
)
206 # version prefix utilities
208 def get_version_prefix(version
):
210 Get the version number prefix from a string.
214 version specification
217 str: the version number prefix
222 m
= re_version_prefix
.match(version
)
224 rval
= m
.group('version')
227 def get_msvc_version_prefix(version
):
229 Get the msvc version number prefix from a string.
233 version specification
236 str: the msvc version number prefix
241 m
= re_msvc_version_prefix
.match(version
)
243 rval
= m
.group('version')
246 def get_msvc_version_prefix_suffix(version
):
248 Get the msvc version number prefix and suffix from a string.
252 version specification
255 (str, str): the msvc version prefix and suffix
260 m
= re_msvc_version
.match(version
)
262 prefix
= m
.group('msvc_version')
263 suffix
= m
.group('suffix') if m
.group('suffix') else ''
264 return prefix
, suffix
266 # toolset version query utilities
268 def is_toolset_full(toolset_version
) -> bool:
271 if re_toolset_full
.match(toolset_version
):
275 def is_toolset_140(toolset_version
) -> bool:
278 if re_toolset_140
.match(toolset_version
):
282 def is_toolset_sxs(toolset_version
) -> bool:
285 if re_toolset_sxs
.match(toolset_version
):
289 # msvc version and msvc toolset version decomposition utilties
291 _MSVC_VERSION_COMPONENTS_DEFINITION
= namedtuple('MSVCVersionComponentsDefinition', [
292 'msvc_version', # msvc version (e.g., '14.1Exp')
293 'msvc_verstr', # msvc version numeric string (e.g., '14.1')
294 'msvc_suffix', # msvc version component type (e.g., 'Exp')
295 'msvc_vernum', # msvc version floating point number (e.g, 14.1)
296 'msvc_major', # msvc major version integer number (e.g., 14)
297 'msvc_minor', # msvc minor version integer number (e.g., 1)
298 'msvc_comps', # msvc version components tuple (e.g., ('14', '1'))
301 def msvc_version_components(vcver
):
303 Decompose an msvc version into components.
306 msvc_version: msvc version (e.g., '14.1Exp')
307 msvc_verstr: msvc version numeric string (e.g., '14.1')
308 msvc_suffix: msvc version component type (e.g., 'Exp')
309 msvc_vernum: msvc version floating point number (e.g., 14.1)
310 msvc_major: msvc major version integer number (e.g., 14)
311 msvc_minor: msvc minor version integer number (e.g., 1)
312 msvc_comps: msvc version components tuple (e.g., ('14', '1'))
316 msvc version specification
319 None or MSVCVersionComponents namedtuple:
325 m
= re_msvc_version
.match(vcver
)
329 vs_def
= Config
.MSVC_VERSION_SUFFIX
.get(vcver
)
334 msvc_verstr
= m
.group('msvc_version')
335 msvc_suffix
= m
.group('suffix') if m
.group('suffix') else ''
336 msvc_vernum
= float(msvc_verstr
)
338 msvc_comps
= tuple(msvc_verstr
.split('.'))
339 msvc_major
, msvc_minor
= (int(x
) for x
in msvc_comps
)
341 msvc_version_components_def
= _MSVC_VERSION_COMPONENTS_DEFINITION(
342 msvc_version
= msvc_version
,
343 msvc_verstr
= msvc_verstr
,
344 msvc_suffix
= msvc_suffix
,
345 msvc_vernum
= msvc_vernum
,
346 msvc_major
= msvc_major
,
347 msvc_minor
= msvc_minor
,
348 msvc_comps
= msvc_comps
,
351 return msvc_version_components_def
353 _MSVC_EXTENDED_VERSION_COMPONENTS_DEFINITION
= namedtuple('MSVCExtendedVersionComponentsDefinition', [
354 'msvc_version', # msvc version (e.g., '14.1Exp')
355 'msvc_verstr', # msvc version numeric string (e.g., '14.1')
356 'msvc_suffix', # msvc version component type (e.g., 'Exp')
357 'msvc_suffix_rank', # msvc version component rank (0, 1)
358 'msvc_vernum', # msvc version floating point number (e.g, 14.1)
359 'msvc_major', # msvc major version integer number (e.g., 14)
360 'msvc_minor', # msvc minor version integer number (e.g., 1)
361 'msvc_comps', # msvc version components tuple (e.g., ('14', '1'))
362 'msvc_buildtools', # msvc build tools
363 'msvc_buildtools_num', # msvc build tools integer number
364 'msvc_buildseries', # msvc build series
365 'msvc_buildseries_num', # msvc build series floating point number
366 'msvc_toolset_version', # msvc toolset version
367 'msvc_toolset_comps', # msvc toolset version components
368 'msvc_toolset_is_sxs', # msvc toolset version is sxs
369 'version', # msvc version or msvc toolset version
372 def msvc_extended_version_components(version
):
374 Decompose an msvc version or msvc toolset version into components.
378 version specification
381 None or MSVCExtendedVersionComponents namedtuple:
387 m
= re_extended_version
.match(version
)
391 msvc_toolset_version
= m
.group('version')
392 msvc_toolset_comps
= tuple(msvc_toolset_version
.split('.'))
393 msvc_toolset_is_sxs
= is_toolset_sxs(msvc_toolset_version
)
395 vc_verstr
= get_msvc_version_prefix(msvc_toolset_version
)
399 vc_buildseries_def
= Config
.MSVC_BUILDSERIES_EXTERNAL
.get(vc_verstr
)
400 if not vc_buildseries_def
:
403 vc_buildtools_def
= Config
.VC_BUILDTOOLS_MAP
[vc_buildseries_def
.vc_buildseries
]
405 msvc_verstr
= vc_buildtools_def
.msvc_version
406 msvc_suffix
= m
.group('suffix') if m
.group('suffix') else ''
407 msvc_version
= msvc_verstr
+ msvc_suffix
409 vs_def
= Config
.MSVC_VERSION_SUFFIX
.get(msvc_version
)
413 msvc_vernum
= float(msvc_verstr
)
415 msvc_comps
= tuple(msvc_verstr
.split('.'))
416 msvc_major
, msvc_minor
= (int(x
) for x
in msvc_comps
)
418 msvc_extended_version_components_def
= _MSVC_EXTENDED_VERSION_COMPONENTS_DEFINITION(
419 msvc_version
= msvc_version
,
420 msvc_verstr
= msvc_verstr
,
421 msvc_suffix
= msvc_suffix
,
422 msvc_suffix_rank
= 0 if not msvc_suffix
else 1,
423 msvc_vernum
= msvc_vernum
,
424 msvc_major
= msvc_major
,
425 msvc_minor
= msvc_minor
,
426 msvc_comps
= msvc_comps
,
427 msvc_buildtools
= vc_buildtools_def
.msvc_version
,
428 msvc_buildtools_num
= vc_buildtools_def
.msvc_version_numeric
,
429 msvc_buildseries
= vc_buildseries_def
.vc_version
,
430 msvc_buildseries_num
= vc_buildseries_def
.vc_version_numeric
,
431 msvc_toolset_version
= msvc_toolset_version
,
432 msvc_toolset_comps
= msvc_toolset_comps
,
433 msvc_toolset_is_sxs
= msvc_toolset_is_sxs
,
437 return msvc_extended_version_components_def
439 # msvc sdk version decomposition utilties
441 _MSVC_SDK_VERSION_COMPONENTS_DEFINITION
= namedtuple('MSVCSDKVersionComponentsDefinition', [
442 'sdk_version', # sdk version (e.g., '10.0.20348.0')
443 'sdk_verstr', # sdk version numeric string (e.g., '10.0')
444 'sdk_vernum', # sdk version floating point number (e.g, 10.0)
445 'sdk_major', # sdk major version integer number (e.g., 10)
446 'sdk_minor', # sdk minor version integer number (e.g., 0)
447 'sdk_comps', # sdk version components tuple (e.g., ('10', '0', '20348', '0'))
450 def msvc_sdk_version_components(version
):
452 Decompose an msvc sdk version into components.
455 sdk_version: sdk version (e.g., '10.0.20348.0')
456 sdk_verstr: sdk version numeric string (e.g., '10.0')
457 sdk_vernum: sdk version floating point number (e.g., 10.0)
458 sdk_major: sdk major version integer number (e.g., 10)
459 sdk_minor: sdk minor version integer number (e.g., 0)
460 sdk_comps: sdk version components tuple (e.g., ('10', '0', '20348', '0'))
464 sdk version specification
467 None or MSVCSDKVersionComponents namedtuple:
473 m
= re_msvc_sdk_version
.match(version
)
477 sdk_version
= version
478 sdk_comps
= tuple(sdk_version
.split('.'))
479 sdk_verstr
= '.'.join(sdk_comps
[:2])
480 sdk_vernum
= float(sdk_verstr
)
482 sdk_major
, sdk_minor
= (int(x
) for x
in sdk_comps
[:2])
484 msvc_sdk_version_components_def
= _MSVC_SDK_VERSION_COMPONENTS_DEFINITION(
485 sdk_version
= sdk_version
,
486 sdk_verstr
= sdk_verstr
,
487 sdk_vernum
= sdk_vernum
,
488 sdk_major
= sdk_major
,
489 sdk_minor
= sdk_minor
,
490 sdk_comps
= sdk_comps
,
493 return msvc_sdk_version_components_def