Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d51ea2e
chore: introduce tests which should correctly-fail if a drive-letter …
maxnbk May 14, 2026
486b53c
fix: canonical_path uses abspath on windows, with updates to package_…
maxnbk May 14, 2026
6d74c1d
chore: introduce tests intended to validate the behavior of a windows…
maxnbk May 14, 2026
de5586c
feat: introduce a config defaulted-False config-flag and link-resolu…
maxnbk May 14, 2026
bc8e5a9
chore: introduce tests intended to validate the behavior of longpath-…
maxnbk May 14, 2026
745d371
fix: make resolve_links_on_windows config-flagged functionality longp…
maxnbk May 14, 2026
575bf72
chore: mock-helper for producing tests which fail against multi-roote…
maxnbk May 26, 2026
0139430
chore: introduce is_subdirectory regression guards for real_path migr…
maxnbk May 26, 2026
150effd
chore: introduce canonical_path idempotency regression guard
maxnbk May 26, 2026
274fe00
chore: add failing tests which represent os.path.realpath->real_path …
maxnbk May 26, 2026
2f66335
fix: add real_path helper to slice between abspath/realpath on Window…
maxnbk May 26, 2026
9f748bb
chore: clarify the explicit use-case of canonical_path for path norma…
maxnbk May 26, 2026
09a8584
chore: add test to windows which clarify/regression-guard against imp…
maxnbk May 26, 2026
30bbc32
chore: real_path tests as regression-guards and verification of platf…
maxnbk May 26, 2026
43ca76e
chore: update singular os.path.realpath-using test to test real_path …
maxnbk May 26, 2026
8ca2817
fix: update all os.path.realpath-using call-sites to use real_path in…
maxnbk May 26, 2026
f621d4d
chore: add tests for intermediate-directory symlink resolution in _wi…
maxnbk Jul 17, 2026
98f4493
fix: re-walk prefix components when _windows_realpath resolves a syml…
maxnbk Jul 17, 2026
fad32a4
chore: add tests for junction detection in _windows_realpath
maxnbk Jul 17, 2026
97703d5
fix: add _is_link_or_junction helper for junction detection on Python…
maxnbk Jul 17, 2026
11cca12
chore: add tests for review-driven fixes
maxnbk Jul 26, 2026
d49fab7
fix: add prefix helpers, resolve_path, and harden _windows_realpath
maxnbk Jul 26, 2026
23be581
fix: resolve symlinks at call sites without UNC expansion
maxnbk Jul 26, 2026
2d0151e
fix: add samefile fallback for legacy UNC/drive-letter handle mismatch
maxnbk Jul 26, 2026
30f9a41
fix: parenthesize implicit string concat in platform_.py (ISC004)
maxnbk Jul 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/rez/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ def _parse_env_var(self, value):
"suite_alias_prefix_char": Char,
"cache_packages_path": OptionalStr,
"package_definition_python_path": OptionalStr,
"resolve_links_on_windows": Bool,
"tmpdir": OptionalStr,
"context_tmpdir": OptionalStr,
"default_shell": OptionalStr,
Expand Down
6 changes: 3 additions & 3 deletions src/rez/resolved_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from rez.utils.formatting import columnise, PackageRequest, ENV_VAR_REGEX, \
header_comment, minor_header_comment
from rez.utils.data_utils import deep_del
from rez.utils.filesystem import TempDirs, is_subdirectory, canonical_path
from rez.utils.filesystem import TempDirs, is_subdirectory, canonical_path, real_path
from rez.utils.memcached import pool_memcached_connections
from rez.utils.logging_ import print_debug, print_error, print_warning
from rez.utils.which import which
Expand Down Expand Up @@ -1896,8 +1896,8 @@ def _adjust_variant_for_bundling(cls, handle: dict, out: bool) -> None:

if is_subdirectory(repo_path, bundle_path):
vars_["location"] = os.path.relpath(
os.path.realpath(repo_path),
os.path.realpath(bundle_path)
real_path(repo_path),
real_path(bundle_path)
)

# serializing in, make repo absolute
Expand Down
20 changes: 20 additions & 0 deletions src/rez/rezconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,26 @@
# For further information, see :ref:`package-definition-sharing-code`.
package_definition_python_path = None

# On Windows, whether to resolve symbolic links and junction points when
# normalising filesystem paths (primarily inside ``canonical_path``).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# normalising filesystem paths (primarily inside ``canonical_path``).
# normalising filesystem paths (primarily inside :func:`canonical_path`).

(or something like that. I don't remember off the top of my head if you need to use the full absolute function name (rez.utils.filesystem.canonical_path) or if sphinx will figure it out by itself.

#
# When ``False`` (default), rez uses ``os.path.abspath``, which normalises

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to mention os.path.abspath here since you already explain what it does? (but I would keep the last reference to it at the end of the paragraph as that will help the users know when to use it or not.

# separators and ``.``/``..`` components without following symlinks and without
# expanding mapped drive letters to their UNC equivalents. This preserves the
# path style supplied by the caller (drive-letter input, drive-letter output,
# UNC input, UNC output), effectively defaulting to pre-Python-3.8 behaviour of
# ``os.path.realpath`` on Windows.
#
# When ``True``, rez performs a component-by-component walk using
# ``os.path.islink`` / ``os.readlink``. This resolves actual symlinks and
# junction points without the drive-letter-to-UNC side-effect that
# ``os.path.realpath`` introduced in Python 3.8. Useful when package
# repositories are accessed through directory symlinks or junctions.
#
# This setting is a no-op on non-Windows platforms, which always resolve
# symlinks via ``os.path.realpath``.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# symlinks via ``os.path.realpath``.
# symlinks via ``os.path.realpath``.
#
# .. versionadded:: 3.5.0

(or whatever the next version will be)

(I'll push a PR to add tests that will catch this problem moving forward).

resolve_links_on_windows = False


###############################################################################
# Extensions
Expand Down
14 changes: 10 additions & 4 deletions src/rez/serialise.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from rez.package_resources import package_rex_keys
from rez.utils.scope import ScopeContext
from rez.utils.sourcecode import SourceCode, early, late, include
from rez.utils.filesystem import TempDirs
from rez.utils.filesystem import TempDirs, real_path, resolve_path
from rez.utils.data_utils import ModifyList
from rez.exceptions import ResourceError, InvalidPackageError
from rez.utils.memcached import memcached
Expand Down Expand Up @@ -66,7 +66,13 @@ def open_file_for_write(filepath, mode=None):
yield stream
content = stream.getvalue()

filepath = os.path.realpath(filepath)
# Use real_path for the cache key so it matches load_from_file's key.
# Separately resolve symlinks for the write target so atomic_write
# replaces the symlink's target, not the symlink itself. Use
# resolve_path (not os.path.realpath) to avoid expanding drive letters
# to UNC paths on Windows.
cache_key = real_path(filepath)
filepath = resolve_path(cache_key)
tmpdir = tmpdir_manager.mkdtemp()
cache_filepath = os.path.join(tmpdir, os.path.basename(filepath))

Expand Down Expand Up @@ -103,7 +109,7 @@ def open_file_for_write(filepath, mode=None):
with open(cache_filepath, 'w', encoding="utf-8") as f:
f.write(content)

file_cache[filepath] = cache_filepath
file_cache[cache_key] = cache_filepath


def load_from_file(filepath: str, format_=FileFormat.py, update_data_callback=None,
Expand All @@ -123,7 +129,7 @@ def load_from_file(filepath: str, format_=FileFormat.py, update_data_callback=No
Returns:
dict:
"""
filepath = os.path.realpath(filepath)
filepath = real_path(filepath)
cache_filepath = file_cache.get(filepath)

if cache_filepath:
Expand Down
26 changes: 22 additions & 4 deletions src/rez/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from __future__ import annotations

from rez.utils.execution import create_forwarding_script
from rez.utils.filesystem import real_path, resolve_path
from rez.exceptions import SuiteError, ResolvedContextError
from rez.resolved_context import ResolvedContext
from rez.utils.data_utils import cached_property
Expand Down Expand Up @@ -458,14 +459,31 @@ def save(self, path, verbose: bool = False):
at `path`, then it will be overwritten. Otherwise, if `path`
exists, an error is raised.
"""
path = os.path.realpath(path)
path = real_path(path)
if os.path.exists(path):
if self.load_path and self.load_path == path:
is_same_suite = False
if self.load_path:
# Use samefile() instead of raw string comparison so that
# symlinks, junctions, and case-only differences on Windows
# are handled correctly.
try:
is_same_suite = os.path.samefile(self.load_path, path)
except OSError:
is_same_suite = (self.load_path == path)

if is_same_suite:
if verbose:
print("saving over previous suite...")
for context_name in self.context_names:
self.context(context_name) # load before dir deleted
safe_rmtree(path)
# Resolve symlinks/junctions before rmtree so we remove the
# directory contents rather than severing the link itself.
# On non-Windows this always resolves. On Windows, resolution
# only happens when resolve_links_on_windows is enabled;
# otherwise, rmtree operates on the path as-is (which may
# sever a symlink -- the user can enable the flag if needed).
rmtree_target = resolve_path(path)
safe_rmtree(rmtree_target)
else:
raise SuiteError("Cannot save, path exists: %r" % path)

Expand Down Expand Up @@ -528,7 +546,7 @@ def load(cls, path: str) -> Suite:
raise SuiteError("Failed loading suite: %s" % str(e))

s = cls.from_dict(data)
s.load_path = os.path.realpath(path)
s.load_path = real_path(path)
return s

@classmethod
Expand Down
3 changes: 2 additions & 1 deletion src/rez/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from rez.utils.platform_ import platform_
from rez.exceptions import RezSystemError
from rez.utils.data_utils import cached_property
from rez.utils.filesystem import real_path


class System(object):
Expand Down Expand Up @@ -242,7 +243,7 @@ def rez_bin_path(self):

validation_file = os.path.join(binpath, ".rez_production_install")
if os.path.exists(validation_file):
return os.path.realpath(binpath)
return real_path(binpath)

return None

Expand Down
Loading
Loading