@@ -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
0 commit comments