Skip to content

Commit

Permalink
Merge pull request #4434 from jcbrill/jbrill-msvc-normalize
Browse files Browse the repository at this point in the history
MSVC: update msvc path normalization utility functions
  • Loading branch information
bdbaddog authored Oct 16, 2023
2 parents 83f8e5f + 4282867 commit a95d096
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 20 deletions.
3 changes: 1 addition & 2 deletions SCons/Tool/MSCommon/MSVC/Config.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@
namedtuple,
)

from . import Util

from .Exceptions import (
MSVCInternalError,
)
Expand Down Expand Up @@ -319,6 +317,7 @@


def verify():
from . import Util
from .. import vc
for msvc_version in vc._VCVER:
if msvc_version not in MSVC_VERSION_SUFFIX:
Expand Down
4 changes: 2 additions & 2 deletions SCons/Tool/MSCommon/MSVC/Registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def registry_query_path(key, val, suffix, expand: bool=True):
extval = val + '\\' + suffix if suffix else val
qpath = read_value(key, extval, expand=expand)
if qpath and os.path.exists(qpath):
qpath = Util.process_path(qpath)
qpath = Util.normalize_path(qpath)
else:
qpath = None
return (qpath, key, val, extval)
Expand All @@ -81,7 +81,7 @@ def microsoft_query_paths(suffix, usrval=None, expand: bool=True):
extval = val + '\\' + suffix if suffix else val
qpath = read_value(key, extval, expand=expand)
if qpath and os.path.exists(qpath):
qpath = Util.process_path(qpath)
qpath = Util.normalize_path(qpath)
if qpath not in paths:
paths.append(qpath)
records.append((qpath, key, val, extval, usrval))
Expand Down
87 changes: 83 additions & 4 deletions SCons/Tool/MSCommon/MSVC/Util.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,25 @@
"""

import os
import pathlib
import re

from collections import (
namedtuple,
)

from ..common import debug

from . import Config

# 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,)

def listdir_dirs(p):
"""
Return a list of tuples for each subdirectory of the given directory path.
Expand All @@ -57,22 +66,92 @@ def listdir_dirs(p):
dirs.append((dir_name, dir_path))
return dirs

def process_path(p):
def resolve_path(p, ignore_drivespec=True):
"""
Normalize a system path
Make path absolute resolving any symlinks
Args:
p: str
system path
ignore_drivespec: bool
ignore drive specifications when True
Returns:
str: normalized system path
str: absolute path with symlinks resolved
"""

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)

p = os.path.normpath(p)
p = os.path.realpath(p)

if realpath:
p = resolve_path(p, ignore_drivespec=ignore_drivespec)

p = os.path.normcase(p)

if trailing:
p += os.path.sep

return p

# msvc version and msvc toolset version regexes
Expand Down
103 changes: 93 additions & 10 deletions SCons/Tool/MSCommon/MSVC/UtilTests.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,36 +28,119 @@
import unittest
import os
import re
import sys
import pathlib

from SCons.Tool.MSCommon.MSVC import Config
from SCons.Tool.MSCommon.MSVC import Util
from SCons.Tool.MSCommon.MSVC import WinSDK

def resolve(p):
p = os.path.abspath(p)
p = str(pathlib.Path(p).resolve())
return p

def normalize(*comps):
p = os.path.join(*comps)
p = os.path.normpath(p)
p = os.path.normcase(p)
return p

class Data:

UTIL_PARENT_DIR = os.path.join(os.path.dirname(Util.__file__), os.pardir)
IS_WINDOWS = sys.platform == 'win32'

CWD = os.getcwd()

UTIL_MODULE = os.path.dirname(Util.__file__)
UTIL_MODULE_PARENT = os.path.join(UTIL_MODULE, os.pardir)

HOME = pathlib.Path.home()
HOMEDRIVE = HOME.drive
HOMEPATH = str(HOME)

REALPATH_CWD = resolve(CWD)

REALPATH_UTIL_MODULE = resolve(UTIL_MODULE)
REALPATH_UTIL_MODULE_PARENT = resolve(UTIL_MODULE_PARENT)

REALPATH_HOMEPATH = resolve(HOMEPATH)
REALPATH_HOMEPATH_PARENT = resolve(os.path.join(HOMEPATH, os.pardir))
REALPATH_HOMEDRIVE = resolve(HOMEDRIVE)
REALPATH_HOMEDRIVE_CWD = resolve(HOMEDRIVE)

class UtilTests(unittest.TestCase):

def test_listdir_dirs(self) -> None:
func = Util.listdir_dirs
for dirname, expect in [
(None, False), ('', False), ('doesnotexist.xyz.abc', False),
(Data.UTIL_PARENT_DIR, True),
(Data.UTIL_MODULE_PARENT, True),
]:
dirs = func(dirname)
self.assertTrue((len(dirs) > 0) == expect, "{}({}): {}".format(
func.__name__, repr(dirname), 'list is empty' if expect else 'list is not empty'
))

def test_process_path(self) -> None:
func = Util.process_path
for p, expect in [
(None, True), ('', True),
('doesnotexist.xyz.abc', False), (Data.UTIL_PARENT_DIR, False),
]:
rval = func(p)
self.assertTrue((p == rval) == expect, "{}({}): {}".format(
def test_resolve_path(self) -> None:
func = Util.resolve_path
# default kwargs:
# ignore_drivespec=True
test_list = [
(Data.UTIL_MODULE, Data.REALPATH_UTIL_MODULE, {}),
(os.path.join(Data.UTIL_MODULE, os.pardir), Data.REALPATH_UTIL_MODULE_PARENT, {}),
(Data.HOMEPATH, Data.REALPATH_HOMEPATH, {}),
(os.path.join(Data.HOMEPATH, os.pardir), Data.REALPATH_HOMEPATH_PARENT, {}),
]
if Data.IS_WINDOWS:
test_list.extend([
(Data.HOMEDRIVE, Data.HOMEDRIVE, {}),
(Data.HOMEDRIVE, Data.HOMEDRIVE, {'ignore_drivespec': True}),
(Data.HOMEDRIVE, Data.REALPATH_HOMEDRIVE_CWD, {'ignore_drivespec': False}),
])
for p, expect, kwargs in test_list:
rval = func(p, **kwargs)
# print(repr(p), repr(expect), repr(rval))
self.assertTrue(rval == expect, "{}({}): {}".format(
func.__name__, repr(p), repr(rval)
))

def test_normalize_path(self) -> None:
func = Util.normalize_path
# default kwargs:
# strip=True
# preserve_trailing=False
# expand=False
# realpath=True
# ignore_drivespec=True
test_list = [
(Data.UTIL_MODULE, normalize(Data.REALPATH_UTIL_MODULE), {}),
(os.path.join(Data.UTIL_MODULE, os.pardir), normalize(Data.REALPATH_UTIL_MODULE_PARENT), {}),
(None, None, {}),
('', '', {'realpath': False}),
('', '', {'realpath': True}),
('DoesNotExist.xyz.abc', normalize('DoesNotExist.xyz.abc'), {'realpath': False}),
('DoesNotExist.xyz.abc', normalize(Data.REALPATH_CWD, 'DoesNotExist.xyz.abc'), {'realpath': True}),
(' DoesNotExist.xyz.abc ', normalize(Data.REALPATH_CWD, 'DoesNotExist.xyz.abc'), {'realpath': True}),
(' ~ ', '~', {'realpath': False, 'expand': False}),
(' ~ ', normalize(Data.REALPATH_HOMEPATH), {'realpath': True, 'expand': True}),
]
if Data.IS_WINDOWS:
test_list.extend([
('DoesNotExist.xyz.abc/', normalize('DoesNotExist.xyz.abc') + os.path.sep, {'realpath': False, 'preserve_trailing': True}),
(' DoesNotExist.xyz.abc\\ ', normalize('DoesNotExist.xyz.abc') + os.path.sep, {'realpath': False, 'preserve_trailing': True}),
(' ~/ ', normalize(Data.REALPATH_HOMEPATH) + os.path.sep, {'realpath': True, 'expand': True, 'preserve_trailing': True}),
(' ~\\ ', normalize(Data.REALPATH_HOMEPATH) + os.path.sep, {'realpath': True, 'expand': True, 'preserve_trailing': True}),
(' ~/ ', normalize(Data.REALPATH_CWD, '~') + os.path.sep, {'realpath': True, 'expand': False, 'preserve_trailing': True}),
(' ~\\ ', normalize(Data.REALPATH_CWD, '~') + os.path.sep, {'realpath': True, 'expand': False, 'preserve_trailing': True}),
(Data.HOMEDRIVE, normalize(Data.HOMEDRIVE), {}),
(Data.HOMEDRIVE, normalize(Data.HOMEDRIVE), {'ignore_drivespec': True}),
(Data.HOMEDRIVE, normalize(Data.REALPATH_HOMEDRIVE_CWD), {'ignore_drivespec': False}),
])
for p, expect, kwargs in test_list:
rval = func(p, **kwargs)
# print(repr(p), repr(expect), repr(rval))
self.assertTrue(rval == expect, "{}({}): {}".format(
func.__name__, repr(p), repr(rval)
))

Expand Down
4 changes: 2 additions & 2 deletions SCons/Tool/MSCommon/MSVC/WinSDK.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def _sdk_10_layout(version):
if not version_nbr.startswith(folder_prefix):
continue

sdk_inc_path = Util.process_path(os.path.join(version_nbr_path, 'um'))
sdk_inc_path = Util.normalize_path(os.path.join(version_nbr_path, 'um'))
if not os.path.exists(sdk_inc_path):
continue

Expand Down Expand Up @@ -127,7 +127,7 @@ def _sdk_81_layout(version):

# msvc does not check for existence of root or other files

sdk_inc_path = Util.process_path(os.path.join(sdk_root, r'include\um'))
sdk_inc_path = Util.normalize_path(os.path.join(sdk_root, r'include\um'))
if not os.path.exists(sdk_inc_path):
continue

Expand Down

0 comments on commit a95d096

Please sign in to comment.