Skip to content

Commit e53d2fc

Browse files
committed
Repoint extras-qualified prebundle mirrors alongside the flat package
Isaac Sim ships some prebundled packages twice: a flat directory and an extras-qualified mirror, <pkg>[extras]/<pkg>-<version>-*/<pkg>, whose contents are per-file symlinks into the flat copy. Only the flat directory was repointed, so the mirror kept pointing at the shipped version's file list. MEASURED on the Isaac Sim image: newton[sim]/newton-1.5.0-py3-none-any/newton holds 842 symlinks, and 330 of their targets do not exist in the newton the pin installs, 232 of them under _src/solvers/kamino. Collapse each mirror to the same symlink the flat directory already gets, so one directory link replaces a per-file tree that goes stale on every bump. Mirrors are matched by name rather than glob, since newton[sim] is a valid character class that would otherwise match newtons.
1 parent abfa2b8 commit e53d2fc

3 files changed

Lines changed: 121 additions & 18 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Fixed
2+
^^^^^
3+
4+
* Fixed :func:`~isaaclab.cli.commands.install.command_install` leaving extras-qualified
5+
prebundle mirrors (``<pkg>[extras]/<pkg>-<version>-*/<pkg>``) pointing at the previously
6+
shipped file list, which broke Isaac Sim extensions whenever a package was repointed to a
7+
version that renamed or dropped files.

source/isaaclab/isaaclab/cli/commands/install.py

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,41 @@ def _discover_prebundle_dirs() -> set[Path]:
962962
return prebundle_dirs
963963

964964

965+
def _nested_prebundle_mirrors(prebundle_dir: Path, pkg_name: str) -> list[Path]:
966+
"""Return ``<pkg>[extras]/<pkg>-<version>-*/<pkg>`` mirrors of a prebundled package.
967+
968+
Isaac Sim's ``isaacsim.pip.newton`` prebundle, for example, holds both a flat ``newton``
969+
directory and ``newton[sim]/newton-1.5.0-py3-none-any/newton``, the latter built from
970+
per-file symlinks into the former. Only the flat copy is named in
971+
:data:`_PREBUNDLE_REPOINT_PACKAGES`, so the mirrors have to be discovered by shape.
972+
973+
Args:
974+
prebundle_dir: A ``pip_prebundle`` directory to search.
975+
pkg_name: Package directory name being repointed.
976+
977+
Returns:
978+
Existing mirror directories, which may be empty.
979+
"""
980+
mirrors: list[Path] = []
981+
# ``pkg_name`` may contain glob metacharacters once the extras suffix is appended
982+
# (``newton[sim]`` is a character class), so match by name rather than globbing.
983+
prefix = f"{pkg_name}["
984+
try:
985+
extras_dirs = [p for p in prebundle_dir.iterdir() if p.is_dir() and p.name.startswith(prefix)]
986+
except OSError:
987+
return mirrors
988+
for extras_dir in extras_dirs:
989+
try:
990+
wheel_dirs = [p for p in extras_dir.iterdir() if p.is_dir()]
991+
except OSError:
992+
continue
993+
for wheel_dir in wheel_dirs:
994+
candidate = wheel_dir / pkg_name
995+
if candidate.is_symlink() or candidate.is_dir():
996+
mirrors.append(candidate)
997+
return mirrors
998+
999+
9651000
def _find_dangling_prebundle_symlinks() -> set[Path]:
9661001
"""Find symlinks under Isaac Sim prebundles whose targets do not resolve.
9671002
@@ -1084,24 +1119,31 @@ def _repoint_prebundle_packages() -> None:
10841119
print_debug(f"Skipping repoint of {prebundled}: {venv_pkg} lacks CUDA subpackages (cudnn missing).")
10851120
continue
10861121

1087-
try:
1088-
# Already repointed to the right place — nothing to do.
1089-
if prebundled.is_symlink() and prebundled.resolve() == venv_pkg.resolve():
1090-
continue
1091-
# Replace the prebundled copy (a stale symlink or a real directory)
1092-
# with a symlink to the active environment. We remove rather than
1093-
# rename-to-``.bak``: the env copy is the symlink target, so the
1094-
# prebundle content is redundant, and renaming a directory on an
1095-
# overlayfs lower layer (Docker image build) fails with ``EXDEV``.
1096-
_force_remove(prebundled)
1097-
if use_symlinks:
1098-
prebundled.symlink_to(venv_pkg)
1099-
else:
1100-
shutil.copytree(venv_pkg, prebundled)
1101-
repointed += 1
1102-
print_debug(f"Repointed {prebundled} -> {venv_pkg}")
1103-
except OSError as exc:
1104-
print_warning(f"Could not repoint {prebundled}: {exc} — skipping.")
1122+
# Some Isaac Sim builds ship a second copy of the wheel beside the flat directory,
1123+
# under ``<pkg>[extras]/<pkg>-<version>-*/<pkg>``, whose contents are per-file
1124+
# symlinks back into the flat copy. That mirror encodes the shipped version's exact
1125+
# file list, so repointing only the flat directory leaves every symlink whose file
1126+
# the new version renamed or dropped dangling. Collapse each mirror to the same
1127+
# target: one symlink cannot go stale the way a per-file tree does.
1128+
for target in [prebundled, *_nested_prebundle_mirrors(prebundle_dir, pkg_name)]:
1129+
try:
1130+
# Already repointed to the right place — nothing to do.
1131+
if target.is_symlink() and target.resolve() == venv_pkg.resolve():
1132+
continue
1133+
# Replace the prebundled copy (a stale symlink or a real directory)
1134+
# with a symlink to the active environment. We remove rather than
1135+
# rename-to-``.bak``: the env copy is the symlink target, so the
1136+
# prebundle content is redundant, and renaming a directory on an
1137+
# overlayfs lower layer (Docker image build) fails with ``EXDEV``.
1138+
_force_remove(target)
1139+
if use_symlinks:
1140+
target.symlink_to(venv_pkg)
1141+
else:
1142+
shutil.copytree(venv_pkg, target)
1143+
repointed += 1
1144+
print_debug(f"Repointed {target} -> {venv_pkg}")
1145+
except OSError as exc:
1146+
print_warning(f"Could not repoint {target}: {exc} — skipping.")
11051147

11061148
if repointed:
11071149
print_info(

source/isaaclab/test/cli/test_install_prebundle.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from isaaclab.cli.commands.install import (
1919
_assert_no_new_dangling_prebundle_symlinks,
2020
_find_dangling_prebundle_symlinks,
21+
_nested_prebundle_mirrors,
2122
_torch_first_on_sys_path_is_prebundle,
2223
split_install_items,
2324
)
@@ -160,3 +161,56 @@ def test_non_package_dangles_warn_but_pass(self, tmp_path):
160161
(services / "test_module.py").symlink_to(core / "gone-test.py")
161162
with mock.patch("isaaclab.cli.commands.install._discover_prebundle_dirs", return_value={core, services}):
162163
_assert_no_new_dangling_prebundle_symlinks(set())
164+
165+
166+
# ---------------------------------------------------------------------------
167+
# _nested_prebundle_mirrors
168+
# ---------------------------------------------------------------------------
169+
170+
171+
class TestNestedPrebundleMirrors:
172+
"""Tests for :func:`_nested_prebundle_mirrors`.
173+
174+
Isaac Sim's ``isaacsim.pip.newton`` prebundle carries both a flat ``newton`` directory and
175+
an extras-qualified ``newton[sim]/newton-<version>-py3-none-any/newton`` mirror built from
176+
per-file symlinks. Repointing only the flat copy leaves the mirror pointing at the previous
177+
version's file list.
178+
"""
179+
180+
def test_extras_qualified_mirror_is_found(self, tmp_path):
181+
mirror = tmp_path / "newton[sim]" / "newton-1.5.0-py3-none-any" / "newton"
182+
mirror.mkdir(parents=True)
183+
184+
assert _nested_prebundle_mirrors(tmp_path, "newton") == [mirror]
185+
186+
def test_flat_package_is_not_reported_as_its_own_mirror(self, tmp_path):
187+
"""The caller already repoints the flat directory; returning it again would double-count."""
188+
(tmp_path / "newton").mkdir()
189+
190+
assert _nested_prebundle_mirrors(tmp_path, "newton") == []
191+
192+
def test_package_without_an_extras_directory_has_no_mirror(self, tmp_path):
193+
(tmp_path / "torch").mkdir()
194+
(tmp_path / "newton[sim]" / "newton-1.5.0-py3-none-any" / "newton").mkdir(parents=True)
195+
196+
assert _nested_prebundle_mirrors(tmp_path, "torch") == []
197+
198+
def test_bracket_in_the_name_is_matched_literally_not_as_a_glob(self, tmp_path):
199+
"""``newton[sim]`` is a valid glob character class, so name matching must not glob."""
200+
# ``newton[sim]`` as a class matches the single characters s, i and m.
201+
(tmp_path / "newtons" / "newton-1.5.0-py3-none-any" / "newton").mkdir(parents=True)
202+
real = tmp_path / "newton[sim]" / "newton-1.5.0-py3-none-any" / "newton"
203+
real.mkdir(parents=True)
204+
205+
assert _nested_prebundle_mirrors(tmp_path, "newton") == [real]
206+
207+
def test_a_mirror_that_is_a_symlink_is_still_reported(self, tmp_path):
208+
"""Repointing is idempotent, so an already-collapsed mirror must stay discoverable."""
209+
target = tmp_path / "env_newton"
210+
target.mkdir()
211+
wheel_dir = tmp_path / "newton[sim]" / "newton-1.5.0-py3-none-any"
212+
wheel_dir.mkdir(parents=True)
213+
mirror = wheel_dir / "newton"
214+
mirror.symlink_to(target)
215+
216+
assert _nested_prebundle_mirrors(tmp_path, "newton") == [mirror]

0 commit comments

Comments
 (0)