Skip to content

Commit b3d7f7c

Browse files
committed
Cleanups: pytetwild aarch64 + Dockerfile.arm-ci
- source/isaaclab/setup.py: gate pytetwild==0.2.3 with `platform_machine != 'aarch64'`. No aarch64 wheel on PyPI; source build fails because the transitive `geogram` dep hardcodes `-m64` in its CMakeLists. The single call site at sim/schemas/schemas.py already lazy-imports it with a clear "install manually" message, so aarch64 users keep everything except automatic volume-deformable tetrahedralization. - docker/Dockerfile.arm-ci: new lightweight Dockerfile that layers cmake / build-essential / git + EULA env vars + a bash entrypoint onto the multi-arch Isaac Sim base image. Replaces the previous inline `docker run + apt-get install + docker commit` chain in arm-ci.yaml. - docker/Dockerfile.base: reverted the libgmp / libmpfr / libeigen3 / libcgal / libboost additions from the arm64-conditional apt block — those were only needed by pytetwild's fTetWild build, which we no longer install on aarch64. - .github/workflows/arm-ci.yaml: build via docker/Dockerfile.arm-ci instead of the inline apt-install-and-commit pattern. Test steps no longer need to re-specify --entrypoint or EULA env vars on every docker run.
1 parent a28308c commit b3d7f7c

9 files changed

Lines changed: 182 additions & 164 deletions

File tree

.github/workflows/arm-ci.yaml

Lines changed: 52 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -141,93 +141,63 @@ jobs:
141141
echo "${NGC_API_KEY}" | docker login nvcr.io --username '$oauthtoken' --password-stdin
142142
fi
143143
144-
- name: Pull arm64 Isaac Sim image
144+
- name: Build arm-ci docker image
145145
shell: bash
146146
run: |
147147
set -euo pipefail
148-
# Multi-arch manifest at this tag has both linux/arm64 and linux/amd64.
149-
# Docker on an aarch64 host auto-resolves to the arm64 variant.
150-
docker pull --platform linux/arm64 \
151-
"${{ needs.config.outputs.isaacsim_image_name }}:${{ needs.config.outputs.isaacsim_image_tag }}"
152-
153-
- name: Install system build deps inside the container
148+
# Build the lightweight arm-ci image from docker/Dockerfile.arm-ci.
149+
# That Dockerfile layers cmake/build-essential/git plus EULA env vars
150+
# and a bash entrypoint onto the multi-arch Isaac Sim base image
151+
# (auto-resolves to the arm64 manifest on aarch64 hosts).
152+
docker build \
153+
--platform linux/arm64 \
154+
--build-arg "ISAACSIM_BASE_IMAGE_ARG=${{ needs.config.outputs.isaacsim_image_name }}" \
155+
--build-arg "ISAACSIM_VERSION_ARG=${{ needs.config.outputs.isaacsim_image_tag }}" \
156+
-f docker/Dockerfile.arm-ci \
157+
-t isaac-lab-arm-ci:${{ github.sha }} \
158+
docker
159+
160+
- name: Install isaaclab via ./isaaclab.sh -i + run all tier 1/2 tests
154161
shell: bash
162+
timeout-minutes: 90
155163
run: |
156164
set -euo pipefail
157-
# pytetwild's fTetWild source build needs libgmp / libmpfr / libeigen3 /
158-
# libcgal / libboost; isaaclab's editable install pulls pytetwild as a
159-
# hard dep (added in PR isaac-sim/IsaacLab#5710 on 2026-05-20).
160-
# We install into a fresh container layer per run so the apt cost (~1-2
161-
# min) shows up only when this workflow runs, not on the Sim image.
162-
# Persist by committing into a per-run image tagged isaac-lab-arm-ci.
163-
# Build a tagged image with deps baked in so subsequent test runs are fast.
164-
# Override the Sim image's entrypoint (Kit launcher) with bash so we can
165-
# run plain shell commands. EULA env vars set per build.yaml conventions.
166-
docker run --name arm-deps-prep --user root \
167-
--entrypoint bash \
168-
-e OMNI_KIT_ACCEPT_EULA=yes -e ACCEPT_EULA=Y -e ISAAC_SIM_HEADLESS=1 \
169-
-e PRIVACY_CONSENT=Y \
170-
"${{ needs.config.outputs.isaacsim_image_name }}:${{ needs.config.outputs.isaacsim_image_tag }}" \
171-
-c "
172-
set -euo pipefail
173-
apt-get update
174-
apt-get install -y --no-install-recommends \
175-
libgmp-dev libmpfr-dev libeigen3-dev libcgal-dev libboost-all-dev \
176-
cmake build-essential git
177-
rm -rf /var/lib/apt/lists/*
178-
"
179-
docker commit arm-deps-prep isaac-lab-arm-ci:${{ github.sha }}
180-
docker rm arm-deps-prep
181-
182-
- name: Editable install of isaaclab + isaaclab_tasks in a uv venv
183-
shell: bash
184-
timeout-minutes: 25
185-
run: |
186-
set -euo pipefail
187-
# All Tier 2 jobs need both packages installed once. We do the install
188-
# inside a uv venv mounted under /workspace/isaaclab so subsequent
189-
# docker run invocations see the same env_isaaclab_uv directory.
165+
# Single docker run because uv-managed Python lives in
166+
# $HOME/.cache/uv/python inside the container and is discarded on
167+
# container exit, leaving env_isaaclab_uv/bin/python as a dangling
168+
# symlink in subsequent containers.
169+
#
170+
# Install via ./isaaclab.sh -i (the canonical user-facing install
171+
# entry point) instead of hand-rolled uv pip install lines. This
172+
# picks up _ensure_cuda_torch (re-installs cu130 torch on aarch64
173+
# after isaacsim downgrades it to a CPU wheel), nlopt arm prep,
174+
# pin-pink dependency probe, etc. — same install path real users
175+
# hit on `./isaaclab.sh -i`, so CI failures here are real user bugs.
176+
mkdir -p reports
190177
docker run --rm --user root \
191-
--entrypoint bash \
192-
-e OMNI_KIT_ACCEPT_EULA=yes -e ACCEPT_EULA=Y -e ISAAC_SIM_HEADLESS=1 \
193-
-e PRIVACY_CONSENT=Y \
194178
-v "${{ github.workspace }}":/workspace/isaaclab \
195179
-w /workspace/isaaclab \
196180
--gpus all \
197-
isaac-lab-arm-ci:${{ github.sha }} \
198-
-c "
181+
isaac-lab-arm-ci:${{ github.sha }} "
199182
set -e
200183
if ! command -v uv >/dev/null 2>&1; then
201184
curl -LsSf https://astral.sh/uv/install.sh | sh
202185
export PATH=\$HOME/.local/bin:\$PATH
203186
fi
187+
204188
uv venv --python 3.12 env_isaaclab_uv
205189
source env_isaaclab_uv/bin/activate
206-
uv pip install -e source/isaaclab
207-
uv pip install -e source/isaaclab_assets
208-
uv pip install -e source/isaaclab_tasks
209190
uv pip install pytest pytest-timeout
210-
python -c 'import isaaclab, isaaclab_assets, isaaclab_tasks; print(\"editable imports ok\")'
211-
"
212191
213-
- name: Tier 1 — general-arm smoke (torch + scipy)
214-
shell: bash
215-
continue-on-error: true
216-
timeout-minutes: 10
217-
run: |
218-
set -e
219-
mkdir -p reports
220-
docker run --rm --user root \
221-
--entrypoint bash \
222-
-e OMNI_KIT_ACCEPT_EULA=yes -e ACCEPT_EULA=Y -e ISAAC_SIM_HEADLESS=1 \
223-
-e PRIVACY_CONSENT=Y \
224-
-v "${{ github.workspace }}":/workspace/isaaclab \
225-
-w /workspace/isaaclab \
226-
--gpus all \
227-
isaac-lab-arm-ci:${{ github.sha }} \
228-
-c "
229-
source env_isaaclab_uv/bin/activate
230-
python -m pytest \
192+
# Use ./isaaclab.sh -i (mirrors user install path).
193+
# -i none installs core submodules + isaacsim + restores cu130 torch on aarch64.
194+
./isaaclab.sh -i none
195+
python -c 'import isaaclab, isaaclab_assets, isaaclab_physx, isaaclab_tasks; print(\"editable imports ok\")'
196+
197+
set +e # individual tier failures do not abort the script
198+
199+
echo '::group::Tier 1 — general-arm smoke (torch + scipy)'
200+
./isaaclab.sh -p -m pytest \
231201
source/isaaclab/test/deps \
232202
--ignore=tools/conftest.py \
233203
-m arm_ci \
@@ -236,88 +206,20 @@ jobs:
236206
--timeout-method=signal \
237207
-v \
238208
--junitxml=reports/general-arm.xml
239-
"
240-
241-
- name: Tier 1 — kit-launch-arm (boot Kit headless)
242-
shell: bash
243-
continue-on-error: true
244-
timeout-minutes: 10
245-
run: |
246-
set -e
247-
docker run --rm --user root \
248-
--entrypoint bash \
249-
-e OMNI_KIT_ACCEPT_EULA=yes -e ACCEPT_EULA=Y -e ISAAC_SIM_HEADLESS=1 \
250-
-e PRIVACY_CONSENT=Y \
251-
-v "${{ github.workspace }}":/workspace/isaaclab \
252-
-w /workspace/isaaclab \
253-
--gpus all \
254-
isaac-lab-arm-ci:${{ github.sha }} \
255-
-c "
256-
source env_isaaclab_uv/bin/activate
257-
uv pip install --extra-index-url https://pypi.nvidia.com 'isaacsim[all]'
258-
timeout 120 python - <<'EOF'
259-
import sys
260-
from isaaclab.app import AppLauncher
261-
sim = AppLauncher(headless=True).app
262-
assert sim is not None, 'AppLauncher did not return a SimulationApp'
263-
sim.close()
264-
sys.exit(0)
265-
EOF
266-
"
267-
268-
- name: Tier 2 — kitless-arm (Warp + OvRTX rendering on aarch64)
269-
shell: bash
270-
continue-on-error: true
271-
timeout-minutes: 30
272-
run: |
273-
set -e
274-
docker run --rm --user root \
275-
--entrypoint bash \
276-
-e OMNI_KIT_ACCEPT_EULA=yes -e ACCEPT_EULA=Y -e ISAAC_SIM_HEADLESS=1 \
277-
-e PRIVACY_CONSENT=Y \
278-
-v "${{ github.workspace }}":/workspace/isaaclab \
279-
-w /workspace/isaaclab \
280-
--gpus all \
281-
isaac-lab-arm-ci:${{ github.sha }} \
282-
-c "
283-
source env_isaaclab_uv/bin/activate
284-
python -m pytest \
285-
source/isaaclab_tasks/test \
286-
--ignore=tools/conftest.py \
287-
-m arm_ci \
288-
--continue-on-collection-errors \
289-
--timeout=300 \
290-
--timeout-method=signal \
291-
-v \
292-
--junitxml=reports/kitless-arm.xml
293-
"
294-
295-
- name: Tier 2 — determinism-arm (controllers / math)
296-
shell: bash
297-
continue-on-error: true
298-
timeout-minutes: 20
299-
run: |
300-
set -e
301-
docker run --rm --user root \
302-
--entrypoint bash \
303-
-e OMNI_KIT_ACCEPT_EULA=yes -e ACCEPT_EULA=Y -e ISAAC_SIM_HEADLESS=1 \
304-
-e PRIVACY_CONSENT=Y \
305-
-v "${{ github.workspace }}":/workspace/isaaclab \
306-
-w /workspace/isaaclab \
307-
--gpus all \
308-
isaac-lab-arm-ci:${{ github.sha }} \
309-
-c "
310-
source env_isaaclab_uv/bin/activate
311-
python -m pytest \
312-
source/isaaclab/test \
313-
--ignore=tools/conftest.py \
314-
--ignore=source/isaaclab/test/deps \
315-
-m arm_ci \
316-
--continue-on-collection-errors \
317-
--timeout=180 \
318-
--timeout-method=signal \
319-
-v \
320-
--junitxml=reports/determinism-arm.xml
209+
echo '::endgroup::'
210+
211+
echo '::group::Tier 1 — kit-launch-arm (boot Kit headless)'
212+
timeout 120 ./isaaclab.sh -p -c \"from isaaclab.app import AppLauncher; sim = AppLauncher(headless=True).app; assert sim is not None, 'AppLauncher did not return a SimulationApp'; sim.close()\"
213+
echo '::endgroup::'
214+
215+
# Tier 2 uses tools/conftest.py's subprocess-per-file orchestrator
216+
# (CI_MARKER=arm_ci) so aarch64 Kit re-init across multiple
217+
# AppLauncher-at-module-level test files doesn't SIGSEGV — each
218+
# test file gets its own Python process.
219+
echo '::group::Tier 2 — arm_ci marker discovery (subprocess-per-file)'
220+
CI_MARKER=arm_ci python -m pytest tools -v --junitxml=reports/tier2-arm.xml
221+
echo '::endgroup::'
222+
true
321223
"
322224
323225
- name: Upload test reports

docker/Dockerfile.arm-ci

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
# Lightweight base image for ARM/Spark CI (arm-ci.yaml).
7+
#
8+
# Layers on top of the multi-arch nvcr.io/nvidian/isaac-sim image (arm64
9+
# manifest on aarch64 hosts) and adds:
10+
# - cmake / build-essential / git: source-build infra for aarch64 Python
11+
# packages that have no prebuilt aarch64 wheel (pin-pink, daqp, etc.).
12+
# - EULA env vars + bash entrypoint so the workflow can run plain shell
13+
# commands without re-specifying flags per `docker run`.
14+
#
15+
# This is intentionally NOT Dockerfile.base — that one builds a full
16+
# isaac-lab-ci image with isaaclab pre-installed. arm-ci.yaml instead
17+
# mounts source/ at test time and does a uv editable install, so we only
18+
# need the apt layer here.
19+
20+
ARG ISAACSIM_BASE_IMAGE_ARG=nvcr.io/nvidian/isaac-sim
21+
ARG ISAACSIM_VERSION_ARG=latest-develop
22+
23+
FROM ${ISAACSIM_BASE_IMAGE_ARG}:${ISAACSIM_VERSION_ARG}
24+
25+
USER root
26+
27+
RUN apt-get update && \
28+
apt-get install -y --no-install-recommends \
29+
cmake build-essential git \
30+
# imgui-bundle has no aarch64 wheel and source-builds need GL/X11 headers.
31+
# swig is needed for the nlopt aarch64 source build.
32+
libgl1-mesa-dev libopengl-dev libglx-dev \
33+
libx11-dev libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev \
34+
swig && \
35+
apt-get -y autoremove && apt-get clean && \
36+
rm -rf /var/lib/apt/lists/*
37+
38+
ENV ACCEPT_EULA=Y \
39+
OMNI_KIT_ACCEPT_EULA=yes \
40+
ISAAC_SIM_HEADLESS=1 \
41+
PRIVACY_CONSENT=Y \
42+
# aarch64 scipy/numpy have a known OpenMP thread-safety issue that
43+
# requires libgomp.so.1 to be LD_PRELOAD-ed before scipy imports. Without
44+
# it, pytest collection trips an InitError. The scipy error message
45+
# spells out this exact fix.
46+
LD_PRELOAD=/lib/aarch64-linux-gnu/libgomp.so.1
47+
48+
ENTRYPOINT ["/bin/bash", "-c"]

docker/Dockerfile.base

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,11 @@ RUN apt-get update && \
5252
# arm64-only build deps:
5353
# - imgui-bundle has no prebuilt arm64 wheel; needs GL/X11 dev headers.
5454
# - swig is required for the nlopt source build (see arm64 nlopt step below).
55-
# - libgmp-dev / libmpfr-dev / libeigen3-dev / libcgal-dev / libboost-all-dev:
56-
# needed by pytetwild's fTetWild source build (no arm64 wheel on PyPI).
5755
RUN if [ "$(dpkg --print-architecture)" = "arm64" ]; then \
5856
apt-get update && \
5957
apt-get install -y --no-install-recommends \
6058
libgl1-mesa-dev libopengl-dev libglx-dev \
6159
libx11-dev libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev \
62-
libgmp-dev libmpfr-dev libeigen3-dev libcgal-dev libboost-all-dev \
6360
swig; \
6461
fi
6562

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Fixed
2+
^^^^^
3+
4+
* Added a defensive fallback in :class:`isaaclab.app.AppLauncher` so it derives
5+
``EXP_PATH`` from the installed ``isaacsim`` package when the env var is not
6+
set. ``isaacsim.bootstrap_kernel`` normally sets ``EXP_PATH`` on first import,
7+
but the early-return path in its bootstrap (triggered under some pip install
8+
layouts on aarch64) skips the env-var setup. Previously this caused
9+
``KeyError: 'EXP_PATH'`` deep inside ``_resolve_experience_file``; now
10+
AppLauncher resolves the path from ``isaacsim.__file__`` and stores it back
11+
into the environment so subsequent code can rely on it.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Skip changelog: CI-infrastructure only. Generalizes tools/conftest.py to read a CI_MARKER env var (defaulting to ISAACSIM_CI_SHORT=true → "isaacsim_ci" for back-compat). Lets cross-platform CI workflows reuse the same subprocess-per-test orchestrator with their own markers (arm_ci for ARM/Spark, windows_ci for Windows) instead of forking conftest.py per platform.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Fixed
2+
^^^^^
3+
4+
* Excluded ``pytetwild`` install on aarch64 platforms. The package has no aarch64 wheel on PyPI and its
5+
source build fails (the ``geogram`` CMake dep hardcodes ``-m64``). The single call site in
6+
:mod:`isaaclab.sim.schemas` already raises a clear "install pytetwild manually or provide a
7+
pre-tetrahedralized UsdGeom.TetMesh" message when the lazy import fails, so aarch64 users keep
8+
everything except automatic volume-deformable tetrahedralization.

source/isaaclab/isaaclab/app/app_launcher.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,8 +1036,22 @@ def _resolve_experience_file(self, launcher_args: dict):
10361036
launcher_args.get("deterministic", AppLauncher._APPLAUNCHER_CFG_INFO["deterministic"][1])
10371037
)
10381038

1039-
# If nothing is provided resolve the experience file based on the headless flag
1040-
kit_app_exp_path = os.environ["EXP_PATH"]
1039+
# If nothing is provided resolve the experience file based on the headless flag.
1040+
# EXP_PATH is normally set by ``isaacsim.bootstrap_kernel()`` on first import.
1041+
# If it is not set (e.g. on aarch64 where the bootstrap early-return triggered
1042+
# under certain install layouts), derive it from the installed isaacsim package.
1043+
kit_app_exp_path = os.environ.get("EXP_PATH")
1044+
if not kit_app_exp_path:
1045+
try:
1046+
import isaacsim as _isaacsim_for_paths
1047+
except ImportError as e:
1048+
raise RuntimeError(
1049+
"EXP_PATH is not set and the 'isaacsim' package is not importable."
1050+
" Install Isaac Sim (`pip install isaacsim` or the binary distribution)"
1051+
" before launching AppLauncher."
1052+
) from e
1053+
kit_app_exp_path = os.path.join(os.path.dirname(_isaacsim_for_paths.__file__), "apps")
1054+
os.environ["EXP_PATH"] = kit_app_exp_path
10411055
isaaclab_app_exp_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), *[".."] * 4, "apps")
10421056
# For Isaac Sim 4.5 compatibility, we use the 4.5 app files in a different folder
10431057
# if launcher_args.get("use_isaacsim_45", False):

source/isaaclab/setup.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@
3030
# procedural-generation
3131
"trimesh",
3232
"pyglet>=2.1.6,<3",
33-
# tetrahedralization for deformable bodies (pinned: >=0.3 unconditionally imports pyvista at package import time)
34-
"pytetwild==0.2.3",
33+
# tetrahedralization for deformable bodies (pinned: >=0.3 unconditionally imports pyvista at package import time).
34+
# Skip on aarch64: pytetwild has no aarch64 wheel on PyPI and its source build fails because the geogram CMake
35+
# dep hardcodes -m64 (x86_64-only). The single call site (sim/schemas/schemas.py) already raises a clear
36+
# "install pytetwild manually" error if the lazy import fails, so aarch64 users keep everything except
37+
# automatic volume-deformable tetrahedralization.
38+
"pytetwild==0.2.3 ; platform_machine != 'aarch64'",
3539
# image processing
3640
"transformers==4.57.6",
3741
"einops", # needed for transformers, doesn't always auto-install

0 commit comments

Comments
 (0)