Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
5 changes: 5 additions & 0 deletions source/isaaclab/changelog.d/flatten-wheel-package.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Fixed
^^^^^

* Fixed the Isaac Lab wheel to install core modules in a flat ``isaaclab`` package instead of
exposing a nested source package through a modified package search path.
25 changes: 25 additions & 0 deletions source/isaaclab/isaaclab/_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Resolve paths shared by source checkouts and installed wheels."""

from pathlib import Path


def _resolve_isaaclab_root() -> Path:
"""Return the directory containing Isaac Lab runtime resources."""
package_root = Path(__file__).resolve().parent
if (package_root / "apps").is_dir():
return package_root

for parent in package_root.parents:
if (parent / "apps").is_dir() and (parent / "source" / "isaaclab").is_dir():
return parent

raise RuntimeError(f"Could not locate the Isaac Lab root from {package_root}")


ISAACLAB_ROOT = _resolve_isaaclab_root()
"""Directory containing Isaac Lab runtime resources."""
3 changes: 2 additions & 1 deletion source/isaaclab/isaaclab/app/app_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

SimulationApp = getattr(isaacsim, "SimulationApp", None)

from isaaclab._paths import ISAACLAB_ROOT
from isaaclab.app.loading_screen import report_activity
from isaaclab.app.logging_utils import apply_python_logging_level, resolve_python_logging_level
from isaaclab.app.settings_manager import get_settings_manager, initialize_carb_settings
Expand Down Expand Up @@ -1180,7 +1181,7 @@ def _resolve_experience_file(self, launcher_args: dict):
) from e
kit_app_exp_path = os.path.join(os.path.dirname(_isaacsim_for_paths.__file__), "apps")
os.environ["EXP_PATH"] = kit_app_exp_path
isaaclab_app_exp_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), *[".."] * 4, "apps")
isaaclab_app_exp_path = str(ISAACLAB_ROOT / "apps")
# For Isaac Sim 4.5 compatibility, we use the 4.5 app files in a different folder
# if launcher_args.get("use_isaacsim_45", False):
if self.is_isaac_sim_version_5():
Expand Down
3 changes: 2 additions & 1 deletion source/isaaclab/isaaclab/benchmark/microbenchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pathlib import Path
from types import MappingProxyType

from isaaclab._paths import ISAACLAB_ROOT
from isaaclab.cli.utils import run_python_command


Expand Down Expand Up @@ -62,7 +63,7 @@ def repository_root(cls) -> Path:
Returns:
Repository root containing the backend benchmark entrypoints.
"""
return Path(__file__).parents[4]
return ISAACLAB_ROOT

@classmethod
def physics_variants(cls) -> tuple[str, ...]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
import subprocess
import sys

from isaaclab._paths import ISAACLAB_ROOT
from isaaclab.benchmark.interfaces import MeasurementData, MeasurementDataRecorder
from isaaclab.benchmark.measurements import DictMetadata, StringMetadata

# Path to the repository root.
_REPO_ROOT = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), *[".."] * 6))
# Path to the source checkout or installed wheel resources.
_REPO_ROOT = str(ISAACLAB_ROOT)


class VersionInfoRecorder(MeasurementDataRecorder):
Expand Down
3 changes: 1 addition & 2 deletions source/isaaclab/isaaclab/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
from pathlib import Path
from typing import IO, Any

# Path to Isaac Lab installation.
ISAACLAB_ROOT = Path(__file__).parents[4].resolve()
from isaaclab._paths import ISAACLAB_ROOT

# Default path to look for Isaac Sim is _isaac_sim symlink.
DEFAULT_ISAAC_SIM_PATH = ISAACLAB_ROOT / "_isaac_sim"
Expand Down
6 changes: 3 additions & 3 deletions source/isaaclab/isaaclab/utils/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

from filelock import FileLock

from isaaclab._paths import ISAACLAB_ROOT

logger = logging.getLogger(__name__)

_UDIM_RE = re.compile(r"<UDIM>", re.IGNORECASE)
Expand All @@ -42,9 +44,7 @@
)


_KIT_EXPERIENCE_PATH = os.path.normpath(
os.path.join(os.path.dirname(__file__), *([".."] * 4), "apps", "isaaclab.python.kit")
)
_KIT_EXPERIENCE_PATH = str(ISAACLAB_ROOT / "apps" / "isaaclab.python.kit")

# Isaac Sim resolves ``persistent.isaac.asset_root.default``, so it is read first. The
# legacy ``cloud`` setting is only consulted for experience files that predate it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
Tests:
- import isaaclab -> verify importable
- from isaaclab import __version__ -> verify version matches wheel filename
- inspect the wheel and isaaclab.__path__ -> verify the core package has a flat layout
- from isaaclab import _deprioritize_prebundle_paths -> verify wheel exports path sanitizer
- from isaaclab.app import AppLauncher -> verify importable
- from isaaclab.envs import VideoRecorderCfg -> verify importable
Expand All @@ -27,6 +28,7 @@

import glob
import shutil
import zipfile

import pytest
from utils import UV_Mixin, run_cmd
Expand Down Expand Up @@ -92,6 +94,26 @@ def test_isaaclab_version_matches_wheel(self):
f"isaaclab.__version__ mismatch: expected {expected_version}, got {imported_version}"
)

def test_isaaclab_package_has_flat_layout(self):
"""Verify core modules are installed directly under the top-level package."""
with zipfile.ZipFile(self._wheel) as wheel:
names = set(wheel.namelist())

assert "isaaclab/app/__init__.py" in names
nested_prefix = "isaaclab/source/isaaclab/isaaclab/"
assert not any(name.startswith(nested_prefix) for name in names)

result = self.run_in_uv_env(
[
"python",
"-c",
"import isaaclab; "
"from pathlib import Path; "
"assert list(isaaclab.__path__) == [str(Path(isaaclab.__file__).parent)]",
]
)
assert result.returncode == 0, f"isaaclab has multiple package roots:\n{result.stdout}\n{result.stderr}"

# from isaaclab import _deprioritize_prebundle_paths
def test_isaaclab_prebundle_path_sanitizer_exported(self):
"""Verify the wheel exports the prebundle path sanitizer used by AppLauncher."""
Expand Down
10 changes: 8 additions & 2 deletions tools/wheel_builder/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ cp -r tools/template "$BUILD_DIR/src/isaaclab/tools/"
# Ensure apps/ is discovered as a Python sub-package (it has no __init__.py)
find "$BUILD_DIR/src/isaaclab/apps" -type d -exec touch {}/__init__.py \;

# Install the core Python package in the conventional flat layout. Runtime resources
# remain under isaaclab/apps, isaaclab/source, and isaaclab/tools, but imports such as
# isaaclab.app resolve directly without extending isaaclab.__path__ at runtime.
CORE_PACKAGE="$BUILD_DIR/src/isaaclab/source/isaaclab/isaaclab"
cp -r "$CORE_PACKAGE/." "$BUILD_DIR/src/isaaclab/"
rm -rf "$CORE_PACKAGE"

# Promote sub-packages (isaaclab_assets, isaaclab_rl, etc.) to top-level
# so they are importable as e.g. `import isaaclab_assets`.
# Each extension has the structure: source/isaaclab_FOO/isaaclab_FOO/ (Python pkg)
Expand Down Expand Up @@ -83,8 +90,7 @@ find "$BUILD_DIR/src" -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null
find "$BUILD_DIR/src" -type d -name "*.egg-info" -exec rm -rf {} + 2>/dev/null || true
find "$BUILD_DIR/src" -name "*.pyc" -delete 2>/dev/null || true

# 2. Copy the custom res __init__.py and __main__.py
cp "$SELF_DIR/res/__init__.py" "$BUILD_DIR/src/isaaclab/"
# 2. Copy the custom __main__.py used by ``python -m isaaclab``
Comment thread
StafaH marked this conversation as resolved.
Outdated
cp "$SELF_DIR/res/__main__.py" "$BUILD_DIR/src/isaaclab/"

# 3. Generate pyproject.toml with dependencies from the root pyproject.toml
Expand Down
2 changes: 1 addition & 1 deletion tools/wheel_builder/gen_pyproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def _dedup(requirements: list[str]) -> list[str]:
lines.append("]")
lines.append("")
lines.append("[project.scripts]")
lines.append('isaaclab = "isaaclab:main"')
lines.append('isaaclab = "isaaclab.cli:cli"')
Comment thread
StafaH marked this conversation as resolved.
Outdated
lines.append("")
lines.append("[project.optional-dependencies]")
for name, dep_list in opt_deps.items():
Expand Down
116 changes: 0 additions & 116 deletions tools/wheel_builder/res/__init__.py

This file was deleted.

Loading