@@ -490,6 +490,42 @@ def to_posixpath(path: str):
490490 return posixpath .sep .join (path .split (ntpath .sep ))
491491
492492
493+ def _windows_realpath (path : str ) -> str :
494+ """Resolve symlinks and junctions on Windows without expanding mapped drives.
495+
496+ ``os.path.realpath`` on Python 3.8+ Windows uses ``GetFinalPathNameByHandle``
497+ which expands mapped drive letters (e.g. ``N:\\ ``) to their underlying UNC
498+ server paths. This function resolves only actual filesystem symlinks and
499+ junction points, walking the path component-by-component so that the drive
500+ root (drive-letter or UNC prefix) is never touched.
501+ """
502+ path = os .path .normpath (os .path .abspath (path ))
503+ drive , rest = os .path .splitdrive (path )
504+ # Preserve the root separator so UNC paths keep their leading "\\" and
505+ # drive-letter paths keep their "\".
506+ result = drive + (os .sep if rest .startswith (os .sep ) else "" )
507+ for part in rest .lstrip (os .sep ).split (os .sep ):
508+ if not part :
509+ continue
510+ candidate = os .path .join (result , part )
511+ depth = 0
512+ while os .path .islink (candidate ) and depth < 40 :
513+ target = os .readlink (candidate )
514+ # os.readlink on Windows may return an extended-length path
515+ # (\\?\C:\... or \\?\UNC\server\share\...). Strip the prefix so
516+ # subsequent abspath/normpath calls produce ordinary paths.
517+ if target .startswith ("\\ \\ ?\\ UNC\\ " ):
518+ target = "\\ \\ " + target [8 :]
519+ elif target .startswith ("\\ \\ ?\\ " ):
520+ target = target [4 :]
521+ if not os .path .isabs (target ):
522+ target = os .path .join (os .path .dirname (candidate ), target )
523+ candidate = os .path .normpath (os .path .abspath (target ))
524+ depth += 1
525+ result = candidate
526+ return result
527+
528+
493529def canonical_path (path : str , platform = None ):
494530 r""" Resolves symlinks, and formats filepath.
495531
@@ -508,12 +544,19 @@ def canonical_path(path: str, platform=None):
508544 platform = platform_
509545
510546 # On Windows, os.path.realpath from py3.8 onwards silently converts drive
511- # lettered paths to their UNC equivalents (N:\ → \\server\share\). By default,
512- # use abspath instead to preserve the caller's form.
547+ # lettered paths to their UNC equivalents (N:\ → \\server\share\).
513548 # We check sys.platform rather than the platform argument because this is
514549 # an OS-level behaviour of os.path.realpath, not a user-configurable choice.
515550 if sys .platform == "win32" :
516- path = os .path .normpath (os .path .abspath (path ))
551+ # Lazy import avoids a circular dependency (config imports filesystem).
552+ from rez .config import config # noqa: PLC0415
553+ if config .resolve_links_on_windows :
554+ path = _windows_realpath (path )
555+ else :
556+ # Default: abspath preserves the caller's path form (drive-letter
557+ # stays drive-letter, UNC stays UNC) and restores the pre-3.8
558+ # behaviour where realpath on Windows was equivalent to abspath.
559+ path = os .path .normpath (os .path .abspath (path ))
517560 else :
518561 path = os .path .normpath (os .path .realpath (path ))
519562
0 commit comments