Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
22 changes: 20 additions & 2 deletions docs/source/setup/installation/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,24 @@ Install ``uv``, clone Isaac Lab, and start a workflow:
:: Play a policy
uv run isaaclab play --rl_library rsl_rl --task Isaac-Cartpole-Direct --viz newton

.. tab-item:: :icon:`fa-brands fa-apple` macOS (Apple silicon)
:sync: macos-arm64

macOS support is experimental and CPU-only. It covers the Newton backend without Isaac Sim;
Isaac Sim, the OV PhysX and OV RTX backends, and the ``importers`` extra are unavailable.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Suggestion · Style Consistency — Docs overstate importers extra removal

pyproject.toml excludes only isaacsim-asset-isolated on darwin; tinyobjloader still resolves, so uv run --extra importers installs on macOS. Saying the whole extra is unavailable contradicts the metadata, and the changelog fragment repeats the claim. Reword both to state that the standalone Isaac Sim asset importer is unavailable.


.. code-block:: bash

curl -LsSf https://astral.sh/uv/install.sh | sh

.. isaaclab-clone-commands::

.. code-block:: bash

# Newton backend on the CPU
uv run isaaclab train --rl_library rsl_rl \
--task Isaac-Cartpole-Direct --device cpu physics=newton_mjwarp
Comment on lines +242 to +243

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Agent remains on CUDA

In this macOS example, --device cpu sets the simulation device, but the RSL-RL agent keeps its cuda:0 default. Non-distributed training does not copy the simulation device to the agent, so building the policy fails on a CPU-only Mac unless the separate runner-fallback change is present.


``uv run`` installs the core dependencies automatically. The ``--extra <name>``
option includes the selected optional integration in the command's environment. Place it
before ``isaaclab``; for example, ``--extra ov`` installs both ovphysx and ovrtx
Expand All @@ -243,8 +261,8 @@ See :ref:`installation-optional-extras` for the available extras.
``uv run --extra <name> <command>`` syncs the selected extra into the project environment
and then runs the command.

The source checkout selects PyTorch's CUDA 13.0 build on Linux x86_64, Linux aarch64, and Windows.
No additional command flags are needed.
The source checkout selects PyTorch's CUDA 13.0 build on Linux x86_64, Linux aarch64, and Windows,
and the PyPI build on macOS. No additional command flags are needed.
The published wheel pins the PyTorch versions, but downstream uv projects must configure their own
PyTorch indexes because uv does not inherit a dependency project's ``tool.uv.sources`` settings.

Expand Down
47 changes: 34 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ dependencies = [
"pillow>=12.3.0",
"botocore", # omni.replicator.core S3 backend
"starlette>=1.3.1", # livestream; minimum patched for current security advisories
"omniverseclient==2.74.0",
# no macOS wheels; isaaclab.utils.assets reads HTTP(S) assets directly without it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Remote Cartpole assets cannot load

On macOS, the documented Cartpole task needs a remote USD file. This change excludes omniverseclient, but the current asset downloader still imports omni.client to fetch remote files. The load raises ModuleNotFoundError, so training cannot start unless the separate HTTP asset-fallback change is present.

"omniverseclient==2.74.0 ; sys_platform != 'darwin'",
"filelock",
"lazy_loader>=0.4",
# ----- project template generator -----
Expand All @@ -72,7 +73,9 @@ dependencies = [
# packages so another complete ``pxr`` runtime cannot co-install with it. Two providers in
# one environment overwrite each other's files, and removing either then breaks ``pxr``.
# usd-exchange 3.0.0 vendors USD 26.08; the isaacsim 6.1 importer pins are overridden below.
"usd-exchange==3.0.0",
"usd-exchange==3.0.0 ; sys_platform != 'darwin'",
# usd-exchange ships no macOS wheels, so macOS uses usd-core as its only pxr provider.
"usd-core ; sys_platform == 'darwin'",
# avoid broken hf-xet pre-release cached on NVIDIA Artifactory
"hf-xet>=1.4.1,<2.0.0 ; platform_machine == 'x86_64' or platform_machine == 'AMD64' or platform_machine == 'aarch64'",
# ----- tasks -----
Expand Down Expand Up @@ -117,7 +120,7 @@ tetrahedralization = ["pytetwild[all]>=0.3.0,<0.4"]
video = ["moviepy>=1.0.3,<2.0.0.dev0"]

importers = [
"isaacsim-asset-isolated==6.1.0.0",
"isaacsim-asset-isolated==6.1.0.0 ; sys_platform != 'darwin'", # depends on usd-optimize, which has no macOS wheels
"tinyobjloader==2.0.0rc13",
]

Expand Down Expand Up @@ -175,11 +178,17 @@ rerun = [
"pyarrow==23.0.1",
]

isaacsim = ["isaacsim[all,extscache]==6.1.0.0"]
# Isaac Sim does not support macOS.
isaacsim = ["isaacsim[all,extscache]==6.1.0.0 ; sys_platform != 'darwin'"]

ov = ["ovphysx==0.6.3", "ovrtx==0.5.0.377615", "ovstage==0.2.0.377349"]
ovphysx = ["ovphysx==0.6.3", "ovstage==0.2.0.377349"]
ovrtx = ["ovrtx==0.5.0.377615", "ovstage==0.2.0.377349"]
# The OV runtimes ship no macOS wheels.
ov = [
"ovphysx==0.6.3 ; sys_platform != 'darwin'",
"ovrtx==0.5.0.377615 ; sys_platform != 'darwin'",
"ovstage==0.2.0.377349 ; sys_platform != 'darwin'",
]
ovphysx = ["ovphysx==0.6.3 ; sys_platform != 'darwin'", "ovstage==0.2.0.377349 ; sys_platform != 'darwin'"]
ovrtx = ["ovrtx==0.5.0.377615 ; sys_platform != 'darwin'", "ovstage==0.2.0.377349 ; sys_platform != 'darwin'"]

mimic = [
"isaaclab-mimic",
Expand Down Expand Up @@ -397,12 +406,13 @@ index-strategy = "unsafe-best-match"
prerelease = "allow"
# Platforms Isaac Lab supports (docs/source/setup/installation/index.rst and the
# install CI matrix): Ubuntu 22.04 x86_64, Linux aarch64 (DGX Spark / Jetson), and
# Windows 11 x86_64. Restricting the resolution universe keeps macOS and other
# unsupported wheels out of uv.lock.
# Windows 11 x86_64, plus experimental CPU-only macOS on Apple silicon. Restricting the
# resolution universe keeps other unsupported wheels out of uv.lock.
environments = [
"sys_platform == 'linux' and platform_machine == 'x86_64'",
"sys_platform == 'linux' and platform_machine == 'aarch64'",
"sys_platform == 'win32' and platform_machine == 'AMD64'",
"sys_platform == 'darwin' and platform_machine == 'arm64'",
]
# Isaac Lab owns the Newton, MuJoCo, and torch versions. Overrides replace requirements
# for every requester, unlike constraints, which only intersect. The ``isaacsim`` and
Expand All @@ -416,7 +426,8 @@ override-dependencies = [
# Force the Newton-matched schemas over isaacsim's ==0.2.0 pin.
"newton-usd-schemas>=0.5.0",
# isaacsim-asset-isolated and isaacsim-robot-schema 6.1 pin usd-exchange ==2.3.0.
"usd-exchange==3.0.0",
# Overrides replace markers too, so this one repeats the macOS exclusion.
"usd-exchange==3.0.0 ; sys_platform != 'darwin'",
"torch==2.12.0",
"torchvision==0.27.0",
# ovphysx caps packaging at <24 and isaacsim-core pins ==26.0; both caps are
Expand Down Expand Up @@ -448,9 +459,19 @@ isaaclab = { path = "source/isaaclab", editable = true }
"isaaclab-tasks-experimental" = { path = "source/isaaclab_tasks_experimental", editable = true }
"isaaclab-teleop" = { path = "source/isaaclab_teleop", editable = true }
"isaaclab-visualizers" = { path = "source/isaaclab_visualizers", editable = true }
torch = { index = "pytorch-cu130" }
torchaudio = { index = "pytorch-cu130" }
torchvision = { index = "pytorch-cu130" }
# macOS has no CUDA build; PyPI ships its CPU/MPS wheels.
torch = [
{ index = "pytorch-cu130", marker = "sys_platform != 'darwin'" },
{ index = "pypi-public", marker = "sys_platform == 'darwin'" },
]
torchaudio = [
{ index = "pytorch-cu130", marker = "sys_platform != 'darwin'" },
{ index = "pypi-public", marker = "sys_platform == 'darwin'" },
]
torchvision = [
{ index = "pytorch-cu130", marker = "sys_platform != 'darwin'" },
{ index = "pypi-public", marker = "sys_platform == 'darwin'" },
]

[tool.uv.pip]
index-strategy = "unsafe-best-match"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Added
^^^^^

* Added experimental CPU-only macOS (Apple silicon) support to the source checkout's uv environment.
It installs PyPI's PyTorch build and ``usd-core``, and skips Isaac Sim, the OV runtimes,
``omniverseclient``, and the ``importers`` extra, none of which ship macOS wheels.
62 changes: 51 additions & 11 deletions source/isaaclab/test/cli/test_source_package_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,46 @@

import pytest
import tomllib
from packaging.markers import Marker
from packaging.requirements import Requirement

pytestmark = pytest.mark.unit


_CUDA_ENVIRONMENTS = (
{"sys_platform": "linux", "platform_machine": "x86_64"},
{"sys_platform": "linux", "platform_machine": "aarch64"},
{"sys_platform": "win32", "platform_machine": "AMD64"},
)
"""Supported CUDA platforms from ``[tool.uv].environments``."""

_MACOS_ENVIRONMENT = {"sys_platform": "darwin", "platform_machine": "arm64"}
"""Supported macOS platform from ``[tool.uv].environments``, which runs on the CPU only."""


def _applies(marker: str | None, environment: dict[str, str]) -> bool:
"""Whether a requirement or lock marker applies in ``environment``."""
return marker is None or Marker(marker).evaluate(environment)


@pytest.mark.parametrize("name", ["torch", "torchvision", "torchaudio"])
def test_resolved_torch_stack_supports_blackwell(source_checkout_root: Path, name: str):
"""All supported platforms need CUDA 13 wheels; PyTorch 2.12's cu126 excludes Blackwell."""
"""CUDA platforms need CUDA 13 wheels; PyTorch 2.12's cu126 excludes Blackwell. macOS uses PyPI's build."""
with (source_checkout_root / "uv.lock").open("rb") as f:
lock = tomllib.load(f)

packages = [package for package in lock["package"] if package["name"] == name]
assert packages
assert all(package["version"].endswith("+cu130") for package in packages)
assert all(package["source"]["registry"] == "https://download.pytorch.org/whl/cu130" for package in packages)
cuda = [
package
for package in packages
if any(_applies(m, env) for m in package["resolution-markers"] for env in _CUDA_ENVIRONMENTS)
]
assert cuda
assert all(package["version"].endswith("+cu130") for package in cuda)
assert all(package["source"]["registry"] == "https://download.pytorch.org/whl/cu130" for package in cuda)
assert all(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Warning · Test Quality — macOS torch assertion passes vacuously

The CUDA half is guarded by assert cuda, but this all(...) iterates packages if package not in cuda, which is True for an empty sequence. If the darwin environment is dropped or the lock stops forking these entries, the documented "macOS resolves PyPI torch" contract goes unverified while the test still passes. Assert that the non-CUDA selection is non-empty before checking its registry, mirroring assert cuda.

package["source"]["registry"] == "https://pypi.org/simple" for package in packages if package not in cuda
)
Comment on lines +52 to +54

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Missing macOS torch goes undetected

If a macOS torch, torchvision, or torchaudio entry disappears from the lockfile, this assertion checks an empty set and still passes. Assert that each package resolves for macOS so the test catches a missing CPU stack.



def _requirement_name(requirement: str) -> str:
Expand All @@ -50,17 +76,31 @@ def test_resolved_environment_has_no_second_usd_provider(source_checkout_root: P
``newton[importers]``, ``mujoco[usd]`` and ``warp-lang[examples]`` all require it, so selecting
any of them would reinstate the overlap that the direct dependencies avoid. Checking the lock
catches that, where checking ``pyproject.toml`` alone would not.

``usd-exchange`` ships no macOS wheels, so macOS installs ``usd-core`` as its only provider instead.
"""
with (source_checkout_root / "uv.lock").open("rb") as f:
lock = tomllib.load(f)

direct_names = [_requirement_name(dep) for dep in _root_project(source_checkout_root)["dependencies"]]
assert [name for name in direct_names if name in ("usd-core", "usd-exchange")] == ["usd-exchange"]

locked = {package["name"] for package in lock["package"]}

assert "usd-core" not in locked
assert "usd-exchange" in locked
providers = [
Requirement(dep)
for dep in _root_project(source_checkout_root)["dependencies"]
if _requirement_name(dep) in ("usd-core", "usd-exchange")
]
for environment in (*_CUDA_ENVIRONMENTS, _MACOS_ENVIRONMENT):
installed = [req.name for req in providers if _applies(str(req.marker) if req.marker else None, environment)]
assert installed == (["usd-core"] if environment is _MACOS_ENVIRONMENT else ["usd-exchange"])

usd_core_edges = [
dep
for package in lock["package"]
for deps in [package.get("dependencies", []), *package.get("optional-dependencies", {}).values()]
for dep in deps
if dep["name"] == "usd-core"
]
assert usd_core_edges
assert not any(_applies(dep.get("marker"), env) for dep in usd_core_edges for env in _CUDA_ENVIRONMENTS)
assert "usd-exchange" in {package["name"] for package in lock["package"]}
Comment on lines +102 to +103

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Second macOS USD provider goes undetected

This test rules out usd-core on CUDA platforms, but it does not rule out usd-exchange on macOS. If a transitive dependency adds usd-exchange there, both packages could install the same pxr files while this test passes. Check the lockfile dependencies for both providers on each supported platform.



def test_standalone_importers_are_opt_in(source_checkout_root: Path):
Expand Down
13 changes: 8 additions & 5 deletions source/isaaclab/test/cli/test_uv_run_pyproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,19 +128,22 @@ def test_version_single_source_matches_literal_pins(source_checkout_root: Path):
optional = pyproject["project"]["optional-dependencies"]
overrides = pyproject["tool"]["uv"]["override-dependencies"]

# Isaac Sim and the OV runtimes ship no macOS wheels.
not_macos = " ; sys_platform != 'darwin'"

# Isaac Sim extra mirrors the table; it is the only place the wheel is pinned.
assert optional["isaacsim"] == [f"isaacsim[all,extscache]=={versions['isaacsim']}"]
assert optional["isaacsim"] == [f"isaacsim[all,extscache]=={versions['isaacsim']}{not_macos}"]

# OV extras mirror the table. Table values may be an exact version ("1.2.3",
# mirrored as ``pkg==1.2.3``) or a range spec (">=1.2.3", mirrored as ``pkg>=1.2.3``).
def spec(package: str) -> str:
value = versions[package]
return f"{package}=={value}" if value[0].isdigit() else f"{package}{value}"

assert spec("ovphysx") in optional["ovphysx"]
assert spec("ovrtx") in optional["ovrtx"]
assert spec("ovstage") in optional["ovphysx"]
assert spec("ovstage") in optional["ovrtx"]
assert spec("ovphysx") + not_macos in optional["ovphysx"]
assert spec("ovrtx") + not_macos in optional["ovrtx"]
assert spec("ovstage") + not_macos in optional["ovphysx"]
assert spec("ovstage") + not_macos in optional["ovrtx"]

# CI installs OVRTX through a generic pip-package input (a bare ``pip install
# ovrtx`` ignores this ceiling). Each such install must therefore be pinned:
Expand Down
2 changes: 1 addition & 1 deletion source/isaaclab/test/install_ci/uv_pip/uv-overrides.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mujoco~=3.12.0
mujoco-warp~=3.12.0
newton[sim] @ git+https://github.com/newton-physics/newton.git@release-1.6
newton-usd-schemas>=0.5.0
usd-exchange==3.0.0
usd-exchange==3.0.0 ; sys_platform != 'darwin'
torch==2.12.0
torchvision==0.27.0
packaging>=20,<27
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Changed
^^^^^^^

* Changed the ``ovstage`` dependency to skip macOS, which it ships no wheels for, so the package
installs in the macOS uv environment.
2 changes: 1 addition & 1 deletion source/isaaclab_ov/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ requires-python = ">=3.12"
# Only interpackage dependencies should go here.
dependencies = [
"isaaclab",
"ovstage==0.2.0.377349",
"ovstage==0.2.0.377349 ; sys_platform != 'darwin'",
]

[tool.uv.sources]
Expand Down
2 changes: 1 addition & 1 deletion tools/wheel_builder/uv-overrides.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mujoco~=3.12.0
mujoco-warp~=3.12.0
newton[sim] @ git+https://github.com/newton-physics/newton.git@release-1.6
newton-usd-schemas>=0.5.0
usd-exchange==3.0.0
usd-exchange==3.0.0 ; sys_platform != 'darwin'
torch==2.12.0
torchvision==0.27.0
packaging>=20,<27
Loading
Loading