Merge pull request #4655 from bdbaddog/fix_new_ninja_package
[scons.git] / SCons / Tool / MSCommon / MSVC / Util.py
blob0da58e1e4dbd24579f7c6d42a17072e3cbb00081
1 # MIT License
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.
24 """
25 Helper functions for Microsoft Visual C/C++.
26 """
28 import os
29 import pathlib
30 import re
32 from collections import (
33 namedtuple,
36 from ..common import debug
38 from . import Config
41 # call _initialize method upon class definition completion
43 class AutoInitialize:
44 def __init_subclass__(cls, **kwargs):
45 super().__init_subclass__(**kwargs)
46 if hasattr(cls, '_initialize') and callable(getattr(cls, '_initialize', None)):
47 cls._initialize()
49 # path utilities
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,)
57 def listdir_dirs(p):
58 """
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.
62 Args:
63 p: str
64 directory path
66 Returns:
67 list[tuple[str,str]]: a list of tuples
69 """
70 dirs = []
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))
76 return dirs
78 def resolve_path(p, ignore_drivespec=True):
79 """
80 Make path absolute resolving any symlinks
82 Args:
83 p: str
84 system path
85 ignore_drivespec: bool
86 ignore drive specifications when True
88 Returns:
89 str: absolute path with symlinks resolved
91 """
93 if p:
95 if ignore_drivespec and _RE_DRIVESPEC.match(p):
96 # don't attempt to resolve drive specification (e.g., C:)
97 pass
98 else:
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)
103 try:
104 p = str(pathlib.Path(p).resolve())
105 except OSError as e:
106 debug(
107 'caught exception: path=%s, exception=%s(%s)',
108 repr(p), type(e).__name__, repr(str(e))
111 return p
113 def normalize_path(
115 strip=True,
116 preserve_trailing=False,
117 expand=False,
118 realpath=True,
119 ignore_drivespec=True,
122 Normalize path
124 Args:
125 p: str
126 system path
127 strip: bool
128 remove leading and trailing whitespace when True
129 preserve_trailing: bool
130 preserve trailing path separator when True
131 expand: bool
132 apply expanduser and expandvars when True
133 realpath: bool
134 make the path absolute resolving any symlinks when True
135 ignore_drivespec: bool
136 ignore drive specifications for realpath when True
138 Returns:
139 str: normalized path
143 if p and strip:
144 p = p.strip()
146 if p:
148 trailing = bool(preserve_trailing and p.endswith(_OS_PATH_SEPS))
150 if expand:
151 p = os.path.expanduser(p)
152 p = os.path.expandvars(p)
154 p = os.path.normpath(p)
156 if realpath:
157 p = resolve_path(p, ignore_drivespec=ignore_drivespec)
159 p = os.path.normcase(p)
161 if trailing:
162 p += os.path.sep
164 return 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'''^
175 (?P<version>(?:
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
180 (?P<suffix>[A-Z]+)*
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
186 )$''', re.VERBOSE)
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
191 )$''', re.VERBOSE)
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'''^
200 (?P<version>(?:
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.
212 Args:
213 version: str
214 version specification
216 Returns:
217 str: the version number prefix
220 rval = ''
221 if version:
222 m = re_version_prefix.match(version)
223 if m:
224 rval = m.group('version')
225 return rval
227 def get_msvc_version_prefix(version):
229 Get the msvc version number prefix from a string.
231 Args:
232 version: str
233 version specification
235 Returns:
236 str: the msvc version number prefix
239 rval = ''
240 if version:
241 m = re_msvc_version_prefix.match(version)
242 if m:
243 rval = m.group('version')
244 return rval
246 def get_msvc_version_prefix_suffix(version):
248 Get the msvc version number prefix and suffix from a string.
250 Args:
251 version: str
252 version specification
254 Returns:
255 (str, str): the msvc version prefix and suffix
258 prefix = suffix = ''
259 if version:
260 m = re_msvc_version.match(version)
261 if m:
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:
269 rval = False
270 if toolset_version:
271 if re_toolset_full.match(toolset_version):
272 rval = True
273 return rval
275 def is_toolset_140(toolset_version) -> bool:
276 rval = False
277 if toolset_version:
278 if re_toolset_140.match(toolset_version):
279 rval = True
280 return rval
282 def is_toolset_sxs(toolset_version) -> bool:
283 rval = False
284 if toolset_version:
285 if re_toolset_sxs.match(toolset_version):
286 rval = True
287 return rval
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.
305 Tuple fields:
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'))
314 Args:
315 vcver: str
316 msvc version specification
318 Returns:
319 None or MSVCVersionComponents namedtuple:
322 if not vcver:
323 return None
325 m = re_msvc_version.match(vcver)
326 if not m:
327 return None
329 vs_def = Config.MSVC_VERSION_SUFFIX.get(vcver)
330 if not vs_def:
331 return None
333 msvc_version = 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.
376 Args:
377 version: str
378 version specification
380 Returns:
381 None or MSVCExtendedVersionComponents namedtuple:
384 if not version:
385 return None
387 m = re_extended_version.match(version)
388 if not m:
389 return None
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)
396 if not vc_verstr:
397 return None
399 vc_buildseries_def = Config.MSVC_BUILDSERIES_EXTERNAL.get(vc_verstr)
400 if not vc_buildseries_def:
401 return None
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)
410 if not vs_def:
411 return None
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,
434 version = version,
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.
454 Tuple fields:
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'))
462 Args:
463 version: str
464 sdk version specification
466 Returns:
467 None or MSVCSDKVersionComponents namedtuple:
470 if not version:
471 return None
473 m = re_msvc_sdk_version.match(version)
474 if not m:
475 return None
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