Skip to content

Commit 360522d

Browse files
authored
[Wheel] Support self-contained package and Git-source installs (#7438)
# Description Make the aggregate Isaac Lab package self-contained and directly installable from a Git revision. The wheel previously exposed the canonical core package through a nested `isaaclab/source/isaaclab/isaaclab` tree and a runtime `isaaclab.__path__` mutation. In addition, downstream projects could not use the repository as a normal Git dependency: pointing a package manager at `source/isaaclab` built only the dependency-free leaf package and omitted repository-level runtime resources such as `apps/isaaclab.python.kit`. This PR: - installs the canonical core modules directly in the top-level `isaaclab` package; - resolves bundled resources consistently in source checkouts and installed packages; - exposes `tools/wheel_builder` as a PEP 517 package source; - stages Git-source builds through the same aggregate package assembly used by release wheels; - preserves the aggregate wheel’s top-level `main()` and `bootstrap_kernel()` compatibility APIs; - routes the installed console command through the compatibility dispatcher, including `--generate-vscode-settings`; - retains the root `pyproject.toml` as the single source of truth for third-party dependencies and extras; - bundles the sibling `isaaclab_*` packages, Kit experience files, package data, and template resources; and - keeps the existing `tools/wheel_builder/build.sh` workflow by sharing its staging implementation. No dependencies are added to the individual Isaac Lab subpackage manifests, and no public Python API is changed. ## Downstream use cases External task repositories and applications can now depend on one aggregate `isaaclab` package at an unreleased commit or tag. They no longer need: - a sibling Isaac Lab checkout at a fixed local path; - separate source declarations for every `isaaclab_*` package; - a copied list of Isaac Lab's transitive third-party dependencies; or - a downstream workaround for the missing `apps/isaaclab.python.kit` resource. A downstream uv project can declare: ```toml [project] dependencies = ["isaaclab"] [tool.uv.sources] isaaclab = { git = "https://github.com/isaac-sim/IsaacLab.git", rev = "<commit-or-tag>", subdirectory = "tools/wheel_builder", } ``` Pinning `rev` to a commit or tag makes the complete Isaac Lab code and dependency metadata reproducible in the downstream lockfile. A branch such as `develop` also works, with the usual behavior that a future lockfile update may select a newer revision. This is particularly useful for downstream task packages that need changes from `develop` before the next Isaac Lab wheel release, and for CI systems that should resolve the complete environment without provisioning a separate source checkout. ## Implementation - The aggregate wheel uses a conventional flat `isaaclab` package instead of extending `isaaclab.__path__` at runtime. The resource resolver lives in the conventionally named `isaaclab.paths` module. - The compatibility dispatcher now lives in the canonical package, so source and aggregate builds share `isaaclab.__main__`; the generated console entry point targets that dispatcher rather than bypassing legacy options. - A small PEP 517 backend implements wheel, sdist, and metadata hooks for Git-source consumers. - Package staging was moved from shell commands into a shared Python helper used by both the PEP 517 backend and the existing build script. - The generated aggregate metadata continues to read dependencies and extras from the repository root and removes only the `isaaclab_*` workspace self-references because those packages are bundled in the artifact. - The installed package includes `apps/*.kit`, so asset-root discovery and `AppLauncher` use the same experience files as a source checkout. ## Type of change - Bug fix (non-breaking change which fixes the installed package layout) - New feature (Git-source installation for downstream projects) ## Release backport - [ ] <!-- backport-active-release --> Backport this pull request to the active release branch after it merges into `develop` ## Validation - Resolved and installed a fresh uv project whose only dependency was `isaaclab` from a pinned Git revision and the `tools/wheel_builder` subdirectory. - Verified imports for `isaaclab`, `isaaclab_assets`, `isaaclab_newton`, `isaaclab_rl`, and `isaaclab_tasks` from that fresh environment. - Verified the Git-built installation contains `apps/isaaclab.python.kit`, resolves the default asset root, and runs `isaaclab --help`. - Built both a wheel and an sdist through the new PEP 517 entry point. - Built the wheel through the existing `tools/wheel_builder/build.sh` entry point. - Ran focused CLI, wheel-metadata, and asset tests (77 passed), including regressions for the legacy VS Code option and top-level compatibility APIs. - Inspected the wheel to verify that core modules use the flat layout, `isaaclab/paths.py` replaces `_paths.py`, the console entry point targets `isaaclab.__main__:main`, and the Kit experience files are included. - Ruff, formatting, and all applicable pre-commit hooks passed. The repository-wide changelog hook reports pre-existing fragment differences on the branch; this PR includes the required `isaaclab` changelog fragments. ## Screenshots Not applicable. ## Checklist - [x] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [x] I have run the applicable pre-commit checks - [x] I have documented the Git-source installation path - [x] My changes generate no new runtime warnings - [x] I have added tests that exercise the aggregate build and installed resource layout - [x] I have added a changelog fragment under `source/isaaclab/changelog.d/` - [x] My name already exists in `CONTRIBUTORS.md`
1 parent 9be9102 commit 360522d

20 files changed

Lines changed: 332 additions & 216 deletions

File tree

docs/source/setup/installation/index.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,24 @@ To create a project built on Isaac Lab, see :ref:`template-generator`.
568568

569569
Isaac Lab wheels are published for major releases, not every patch release.
570570

571+
Installing an unreleased Git revision
572+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
573+
574+
The aggregate package can also be built directly from an Isaac Lab Git revision. Point uv at the
575+
``tools/wheel_builder`` subdirectory so it uses the same dependency metadata and packaged runtime
576+
resources as a released wheel:
577+
578+
.. code-block:: toml
579+
580+
[project]
581+
dependencies = ["isaaclab"]
582+
583+
[tool.uv.sources]
584+
isaaclab = { git = "https://github.com/isaac-sim/IsaacLab.git", rev = "<git-revision>", subdirectory = "tools/wheel_builder" }
585+
586+
Use a commit hash or release tag for reproducible environments. A branch name is accepted, but
587+
updating the lockfile can then select a newer Isaac Lab revision and dependency set.
588+
571589
Choose how you want uv to manage the dependency. Both workflows start with the base
572590
``isaaclab`` package; add optional capabilities only when your project needs them.
573591

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fixed
2+
^^^^^
3+
4+
* Fixed the Isaac Lab wheel to install core modules in a flat ``isaaclab`` package instead of
5+
exposing a nested source package through a modified package search path.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Added
2+
^^^^^
3+
4+
* Added a PEP 517 build target under ``tools/wheel_builder`` so package managers can install the aggregate Isaac Lab
5+
package directly from a Git source without duplicating Isaac Lab's dependency list downstream.

source/isaaclab/isaaclab/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,25 @@ def _expose_mujoco_usd_schemas():
131131
__version__ = importlib.metadata.version("isaaclab")
132132
except importlib.metadata.PackageNotFoundError:
133133
__version__ = "0.0.0"
134+
135+
136+
# TODO(myurasov-nv): bootstrap_kernel() is ported from the internal GitLab wheel builder
137+
# for backwards compatibility. It is not called currently, but may be needed if Isaac Sim
138+
# requires explicit kernel bootstrapping before use. Remove once confirmed unnecessary.
139+
def bootstrap_kernel():
140+
"""Import Isaac Sim so it can initialize its kernel when available."""
141+
isaaclab_path = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
142+
if importlib.util.find_spec("isaacsim") is not None:
143+
import isaacsim # noqa: F401
144+
145+
if importlib.util.find_spec("carb") is not None:
146+
import carb
147+
148+
carb.log_info(f"Isaac Lab path: {isaaclab_path}")
149+
150+
151+
def main():
152+
"""Run the ``isaaclab`` command through its compatibility dispatcher."""
153+
from isaaclab.__main__ import main as _main
154+
155+
sys.exit(_main())
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
import tomllib
1313

14+
from isaaclab.paths import ISAACLAB_ROOT
15+
1416
VSCODE_SETTINGS_TEMPLATE = """
1517
{
1618
"editor.rulers": [120],
@@ -115,7 +117,7 @@ def _get_paths(base_path: str, mock_python_modules: bool = False) -> list[str]:
115117
for folder in ["exts", "extscache", "extsDeprecated", "extsUser"]:
116118
extensions_paths.extend(_get_paths(os.path.join(isaacsim_path, folder), mock_python_modules=True))
117119
# - isaaclab
118-
isaaclab_path = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
120+
isaaclab_path = str(ISAACLAB_ROOT)
119121
for folder in ["source"]:
120122
extensions_paths.extend(_get_paths(os.path.join(isaaclab_path, folder), mock_python_modules=True))
121123

source/isaaclab/isaaclab/app/app_launcher.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from isaaclab.app.loading_screen import report_activity
3737
from isaaclab.app.logging_utils import apply_python_logging_level, resolve_python_logging_level
3838
from isaaclab.app.settings_manager import get_settings_manager, initialize_carb_settings
39+
from isaaclab.paths import ISAACLAB_ROOT
3940
from isaaclab.utils._device import set_cuda_device
4041
from isaaclab.utils.renderers import ISAAC_RTX_SHOW_ALL_PARTITIONS_BY_DEFAULT_SETTING
4142

@@ -1180,7 +1181,7 @@ def _resolve_experience_file(self, launcher_args: dict):
11801181
) from e
11811182
kit_app_exp_path = os.path.join(os.path.dirname(_isaacsim_for_paths.__file__), "apps")
11821183
os.environ["EXP_PATH"] = kit_app_exp_path
1183-
isaaclab_app_exp_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), *[".."] * 4, "apps")
1184+
isaaclab_app_exp_path = str(ISAACLAB_ROOT / "apps")
11841185
# For Isaac Sim 4.5 compatibility, we use the 4.5 app files in a different folder
11851186
# if launcher_args.get("use_isaacsim_45", False):
11861187
if self.is_isaac_sim_version_5():

source/isaaclab/isaaclab/benchmark/microbenchmark.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from types import MappingProxyType
1515

1616
from isaaclab.cli.utils import run_python_command
17+
from isaaclab.paths import ISAACLAB_ROOT
1718

1819

1920
@dataclass(frozen=True)
@@ -62,7 +63,7 @@ def repository_root(cls) -> Path:
6263
Returns:
6364
Repository root containing the backend benchmark entrypoints.
6465
"""
65-
return Path(__file__).parents[4]
66+
return ISAACLAB_ROOT
6667

6768
@classmethod
6869
def physics_variants(cls) -> tuple[str, ...]:

source/isaaclab/isaaclab/benchmark/recorders/record_version_info.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010

1111
from isaaclab.benchmark.interfaces import MeasurementData, MeasurementDataRecorder
1212
from isaaclab.benchmark.measurements import DictMetadata, StringMetadata
13+
from isaaclab.paths import ISAACLAB_ROOT
1314

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

1718

1819
class VersionInfoRecorder(MeasurementDataRecorder):

source/isaaclab/isaaclab/cli/utils.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
from pathlib import Path
1414
from typing import IO, Any
1515

16-
# Path to Isaac Lab installation.
17-
ISAACLAB_ROOT = Path(__file__).parents[4].resolve()
16+
from isaaclab.paths import ISAACLAB_ROOT
1817

1918
# Default path to look for Isaac Sim is _isaac_sim symlink.
2019
DEFAULT_ISAAC_SIM_PATH = ISAACLAB_ROOT / "_isaac_sim"

source/isaaclab/isaaclab/paths.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
"""Resolve paths shared by source checkouts and installed wheels."""
7+
8+
from pathlib import Path
9+
10+
11+
def _resolve_isaaclab_root() -> Path:
12+
"""Return the directory containing Isaac Lab runtime resources."""
13+
package_root = Path(__file__).resolve().parent
14+
if (package_root / "apps").is_dir():
15+
return package_root
16+
17+
for parent in package_root.parents:
18+
if parent / "source" / "isaaclab" / "isaaclab" == package_root:
19+
return parent
20+
21+
raise RuntimeError(f"Could not locate the Isaac Lab root from {package_root}")
22+
23+
24+
ISAACLAB_ROOT = _resolve_isaaclab_root()
25+
"""Directory containing Isaac Lab runtime resources."""

0 commit comments

Comments
 (0)