diff --git a/docker/Dockerfile.base b/docker/Dockerfile.base index d3544b871890..e093e841621e 100644 --- a/docker/Dockerfile.base +++ b/docker/Dockerfile.base @@ -101,25 +101,8 @@ RUN touch /bin/nvidia-smi && \ mkdir -p /var/run/nvidia-persistenced && \ touch /var/run/nvidia-persistenced/socket -# On arm64, temporarily install swig for the nlopt source build, keep it -# through the Isaac Lab dependency install, then purge it before the layer ends. -# This lets ./isaaclab.sh --install see nlopt as already satisfied without -# leaving swig in the final container filesystem. RUN --mount=type=cache,target=${DOCKER_USER_HOME}/.cache/pip \ - if [ "$(dpkg --print-architecture)" = "arm64" ]; then \ - apt-get update && \ - apt-get install -y --no-install-recommends swig && \ - ${ISAACLAB_PATH}/isaaclab.sh -p -m pip install setuptools wheel numpy && \ - ${ISAACLAB_PATH}/isaaclab.sh -p -m pip install --no-build-isolation nlopt==2.6.2; \ - fi && \ - ${ISAACLAB_PATH}/isaaclab.sh --install && \ - if [ "$(dpkg --print-architecture)" = "arm64" ]; then \ - ${ISAACLAB_PATH}/isaaclab.sh -p -c "import nlopt" && \ - apt-get purge -y --auto-remove swig && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* && \ - if command -v swig >/dev/null 2>&1; then exit 1; fi; \ - fi + ${ISAACLAB_PATH}/isaaclab.sh --install # HACK: Remove install of quadprog dependency RUN ${ISAACLAB_PATH}/isaaclab.sh -p -m pip uninstall -y quadprog diff --git a/docker/Dockerfile.curobo b/docker/Dockerfile.curobo index 2c8b2c14af71..7890bd6df352 100644 --- a/docker/Dockerfile.curobo +++ b/docker/Dockerfile.curobo @@ -149,25 +149,8 @@ RUN rm -rf ${ISAACSIM_ROOT_PATH}/kit/python/lib/python3.12/site-packages/pip* && ${ISAACLAB_PATH}/_isaac_sim/kit/python/bin/python3 get-pip.py && \ rm get-pip.py -# On arm64, temporarily install swig for the nlopt source build, keep it -# through the Isaac Lab dependency install, then purge it before the layer ends. -# This lets ./isaaclab.sh --install see nlopt as already satisfied without -# leaving swig in the final container filesystem. RUN --mount=type=cache,target=${DOCKER_USER_HOME}/.cache/pip \ - if [ "$(dpkg --print-architecture)" = "arm64" ]; then \ - apt-get update && \ - apt-get install -y --no-install-recommends swig && \ - ${ISAACLAB_PATH}/isaaclab.sh -p -m pip install setuptools wheel numpy && \ - ${ISAACLAB_PATH}/isaaclab.sh -p -m pip install --no-build-isolation nlopt==2.6.2; \ - fi && \ - ${ISAACLAB_PATH}/isaaclab.sh --install && \ - if [ "$(dpkg --print-architecture)" = "arm64" ]; then \ - ${ISAACLAB_PATH}/isaaclab.sh -p -c "import nlopt" && \ - apt-get purge -y --auto-remove swig && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* && \ - if command -v swig >/dev/null 2>&1; then exit 1; fi; \ - fi + ${ISAACLAB_PATH}/isaaclab.sh --install # HACK: Uninstall quadprog as it causes issues with some reinforcement learning frameworks RUN ${ISAACLAB_PATH}/isaaclab.sh -p -m pip uninstall -y quadprog diff --git a/source/isaaclab/changelog.d/fix-arm-nlopt-install.rst b/source/isaaclab/changelog.d/fix-arm-nlopt-install.rst new file mode 100644 index 000000000000..15343e41393d --- /dev/null +++ b/source/isaaclab/changelog.d/fix-arm-nlopt-install.rst @@ -0,0 +1,12 @@ +Fixed +^^^^^ + +* Fixed ``./isaaclab.sh --install`` unnecessarily building ``nlopt==2.6.2`` on + ARM Linux (e.g. DGX Spark), where it fails with CMake 4.x. The ARM-only + ``nlopt`` pre-install and its temporary ``swig`` install were removed because + ``nlopt`` is only pulled in by Linux x86_64 dependencies. + +* Fixed unavoidable ARM source builds such as ``egl-probe==1.0.2`` failing when + CMake 4.x rejects policy compatibility older than 3.5. The installer now + temporarily sets ``CMAKE_POLICY_VERSION_MINIMUM=3.5`` during ARM dependency + builds. diff --git a/source/isaaclab/isaaclab/cli/commands/install.py b/source/isaaclab/isaaclab/cli/commands/install.py index b43052b79508..3c459d203e62 100644 --- a/source/isaaclab/isaaclab/cli/commands/install.py +++ b/source/isaaclab/isaaclab/cli/commands/install.py @@ -7,6 +7,8 @@ import re import shutil import sys +from collections.abc import Iterator +from contextlib import contextmanager from pathlib import Path import tomllib @@ -26,6 +28,33 @@ from .misc import command_vscode_settings +@contextmanager +def _arm_cmake_policy_compatibility() -> Iterator[None]: + """Allow legacy CMake projects to configure during ARM dependency installs. + + CMake 4 removed compatibility with policy versions older than 3.5. Some + transitive ARM dependencies, including ``nlopt==2.6.2`` and + ``egl-probe==1.0.2``, still declare an older minimum. CMake provides this + environment variable specifically so users can configure such unmaintained + third-party projects without patching their sources. + """ + if not is_arm(): + yield + return + + variable = "CMAKE_POLICY_VERSION_MINIMUM" + saved_value = os.environ.get(variable) + os.environ[variable] = "3.5" + print_info(f"ARM install sandbox: temporarily setting {variable}=3.5 for legacy dependency builds.") + try: + yield + finally: + if saved_value is None: + os.environ.pop(variable, None) + else: + os.environ[variable] = saved_value + + def _install_system_deps() -> None: """install system dependencies""" if is_windows(): @@ -169,41 +198,6 @@ def _maybe_uninstall_prebundled_torch( ) -def _maybe_preinstall_arm_nlopt(python_exe: str, pip_cmd: list[str]) -> None: - """Pre-install ``nlopt==2.6.2`` on ARM Linux to skip the source-build fallback. - - There is no aarch64 manylinux wheel for the ``nlopt 2.6.2`` version pinned - by ``isaacteleop[retargeters]``, so pip falls back to a CMake source build - that hides the host-Python ``numpy`` from its isolated build env. Mirror - the docker/Dockerfile.base arm64 step: install ``setuptools wheel numpy`` - in the host Python first, then ``--no-build-isolation`` install nlopt so - later submodule installs see it as already satisfied. - """ - if is_windows() or not is_arm(): - return - - probe_result = run_command( - [ - python_exe, - "-c", - "import importlib.metadata as metadata; import nlopt; " - "raise SystemExit(0 if metadata.version('nlopt') == '2.6.2' else 1)", - ], - check=False, - capture_output=True, - text=True, - ) - if probe_result.returncode == 0: - print_info("nlopt==2.6.2 is already installed on ARM.") - return - - print_info("Pre-installing nlopt==2.6.2 on ARM (no-build-isolation)...") - print_info(" step 1/2: ensure setuptools/wheel/numpy are importable for the no-build-isolation backend") - run_command(pip_cmd + ["install", "setuptools", "wheel", "numpy"]) - print_info(" step 2/2: install nlopt==2.6.2 with --no-build-isolation") - run_command(pip_cmd + ["install", "--no-build-isolation", "nlopt==2.6.2"]) - - # Dependency stack required by isaaclab.controllers.pink_ik. Pinocchio is installed # via the cmeel ``pin`` wheel, which provides the ``pinocchio`` Python module under # ``cmeel.prefix/lib/python3.12/site-packages/`` and registers it on sys.path via a @@ -1066,70 +1060,68 @@ def append_submodules_once(package_dirs: tuple[str, ...]) -> None: # leave new dangling symlinks in Isaac Sim's prebundles (nvbugs 6343978). dangling_symlinks_before = _find_dangling_prebundle_symlinks() - try: - # Upgrade pip first to avoid compatibility issues (skip when using uv). - if not using_uv: - print_info("Upgrading pip...") - run_command(pip_cmd + ["install", "--upgrade", "pip"]) - - # Pin setuptools to avoid issues with pkg_resources removal in 82.0.0. - run_command(pip_cmd + ["install", "setuptools<82.0.0"]) - - # On ARM Linux pre-install nlopt to dodge its from-source build fallback. - _maybe_preinstall_arm_nlopt(python_exe, pip_cmd) - - # Drop pip-installed torch if Isaac Sim's deprecated ML prebundle would shadow it. - _maybe_uninstall_prebundled_torch(python_exe, pip_cmd, using_uv, probe_env=probe_env) - - # Install Isaac Sim if requested. - if install_isaacsim: - _install_isaacsim() - - # Install pytorch (version based on arch). - _ensure_cuda_torch() - - # Install all submodules (core set + any explicitly requested optional ones). - _install_isaaclab_submodules(submodules_to_install) - - # Install requested optional submodule dependency extras. - if optional_submodule_extra_dependencies: - print_info("Installing optional submodule dependencies...") - for submodule_name, selector in optional_submodule_extra_dependencies: - _install_optional_submodule_extra_dependencies(submodule_name, selector) - - # Install requested extra feature dependencies. - if extra_features: - print_info("Installing extra feature dependencies...") - for feature_name, selector in extra_features: - _install_extra_feature(feature_name, selector) - - # In some rare cases, torch might not be installed properly by setup.py, add one more check here. - # Can prevent that from happening. - _ensure_cuda_torch() - - # Ensure Pink IK's runtime dependencies are actually importable. The kit-bundled - # ``pin-pink`` in recent Isaac Sim images can cause transitive dependencies from - # ``pip install -e source/isaaclab`` to be silently skipped. - _ensure_pink_ik_dependencies_installed(python_exe, pip_cmd, probe_env=probe_env) - - # Repoint prebundled packages in Isaac Sim to the environment's copies so - # the active venv/conda versions are always loaded regardless of PYTHONPATH - # ordering (e.g. torch+cu130 in venv vs torch+cu128 in prebundle on aarch64). - _repoint_prebundle_packages() - - # Fail loud if any pip operation above broke Isaac Sim's cross-extension - # symlink farms. Prebundle deletions on their own are routine (pip - # replaces those packages in site-packages, which shadows the prebundle - # at runtime); only newly dangling symlinks break extension startup. - _assert_no_new_dangling_prebundle_symlinks(dangling_symlinks_before) - - finally: - # Restore LD_PRELOAD if we cleared it. - if saved_ld_preload: - os.environ["LD_PRELOAD"] = saved_ld_preload - # Restore PYTHONPATH if we filtered it. - if saved_pythonpath is not None: - os.environ["PYTHONPATH"] = saved_pythonpath + with _arm_cmake_policy_compatibility(): + try: + # Upgrade pip first to avoid compatibility issues (skip when using uv). + if not using_uv: + print_info("Upgrading pip...") + run_command(pip_cmd + ["install", "--upgrade", "pip"]) + + # Pin setuptools to avoid issues with pkg_resources removal in 82.0.0. + run_command(pip_cmd + ["install", "setuptools<82.0.0"]) + + # Drop pip-installed torch if Isaac Sim's deprecated ML prebundle would shadow it. + _maybe_uninstall_prebundled_torch(python_exe, pip_cmd, using_uv, probe_env=probe_env) + + # Install Isaac Sim if requested. + if install_isaacsim: + _install_isaacsim() + + # Install pytorch (version based on arch). + _ensure_cuda_torch() + + # Install all submodules (core set + any explicitly requested optional ones). + _install_isaaclab_submodules(submodules_to_install) + + # Install requested optional submodule dependency extras. + if optional_submodule_extra_dependencies: + print_info("Installing optional submodule dependencies...") + for submodule_name, selector in optional_submodule_extra_dependencies: + _install_optional_submodule_extra_dependencies(submodule_name, selector) + + # Install requested extra feature dependencies. + if extra_features: + print_info("Installing extra feature dependencies...") + for feature_name, selector in extra_features: + _install_extra_feature(feature_name, selector) + + # In some rare cases, torch might not be installed properly by setup.py, add one more check here. + # Can prevent that from happening. + _ensure_cuda_torch() + + # Ensure Pink IK's runtime dependencies are actually importable. The kit-bundled + # ``pin-pink`` in recent Isaac Sim images can cause transitive dependencies from + # ``pip install -e source/isaaclab`` to be silently skipped. + _ensure_pink_ik_dependencies_installed(python_exe, pip_cmd, probe_env=probe_env) + + # Repoint prebundled packages in Isaac Sim to the environment's copies so + # the active venv/conda versions are always loaded regardless of PYTHONPATH + # ordering (e.g. torch+cu130 in venv vs torch+cu128 in prebundle on aarch64). + _repoint_prebundle_packages() + + # Fail loud if any pip operation above broke Isaac Sim's cross-extension + # symlink farms. Prebundle deletions on their own are routine (pip + # replaces those packages in site-packages, which shadows the prebundle + # at runtime); only newly dangling symlinks break extension startup. + _assert_no_new_dangling_prebundle_symlinks(dangling_symlinks_before) + + finally: + # Restore LD_PRELOAD if we cleared it. + if saved_ld_preload: + os.environ["LD_PRELOAD"] = saved_ld_preload + # Restore PYTHONPATH if we filtered it. + if saved_pythonpath is not None: + os.environ["PYTHONPATH"] = saved_pythonpath # Install vscode update unless we're in docker. if not (os.path.exists("/.dockerenv") or os.path.exists("/run/.containerenv")): diff --git a/source/isaaclab/test/cli/test_install_command_parsing.py b/source/isaaclab/test/cli/test_install_command_parsing.py index 27281ec013f4..67892a1958dc 100644 --- a/source/isaaclab/test/cli/test_install_command_parsing.py +++ b/source/isaaclab/test/cli/test_install_command_parsing.py @@ -153,12 +153,12 @@ def test_optional_submodules_not_in_core(self): # Functions that must be mocked to prevent actual system calls. _PATCHES = [ f"{_INSTALL_MODULE}._install_system_deps", + f"{_INSTALL_MODULE}._arm_cmake_policy_compatibility", f"{_INSTALL_MODULE}._install_isaaclab_submodules", f"{_INSTALL_MODULE}._install_extra_feature", f"{_INSTALL_MODULE}._install_optional_submodule_extra_dependencies", f"{_INSTALL_MODULE}._install_isaacsim", f"{_INSTALL_MODULE}._ensure_cuda_torch", - f"{_INSTALL_MODULE}._maybe_preinstall_arm_nlopt", f"{_INSTALL_MODULE}._maybe_uninstall_prebundled_torch", f"{_INSTALL_MODULE}._ensure_pink_ik_dependencies_installed", f"{_INSTALL_MODULE}._repoint_prebundle_packages", @@ -205,6 +205,10 @@ def _run(self, install_type: str): return mocks + def test_wraps_dependency_installs_with_arm_cmake_compatibility(self): + mocks = self._run("all") + mocks["_arm_cmake_policy_compatibility"].assert_called_once_with() + # --- "all" --- def test_all_installs_core_plus_optional_submodules(self): diff --git a/source/isaaclab/test/cli/test_install_commands.py b/source/isaaclab/test/cli/test_install_commands.py index af1ba751c2aa..0ab562faadd7 100644 --- a/source/isaaclab/test/cli/test_install_commands.py +++ b/source/isaaclab/test/cli/test_install_commands.py @@ -10,6 +10,7 @@ - Isaac Sim installation methods: local _isaac_sim symlink, pip-installed isaacsim, none """ +import os import subprocess from contextlib import contextmanager from pathlib import Path @@ -69,6 +70,44 @@ def _make_site_packages( return site_pkgs +# --------------------------------------------------------------------------- +# _arm_cmake_policy_compatibility +# --------------------------------------------------------------------------- + + +class TestArmCmakePolicyCompatibility: + """Tests for legacy CMake dependency builds on ARM.""" + + def test_sets_policy_minimum_during_arm_install(self): + """ARM installs allow nlopt and egl-probe CMake projects to configure.""" + with ( + mock.patch("isaaclab.cli.commands.install.is_arm", return_value=True), + mock.patch.dict("os.environ", {"PATH": "/bin"}, clear=True), + ): + with install_cmd._arm_cmake_policy_compatibility(): + assert os.environ["CMAKE_POLICY_VERSION_MINIMUM"] == "3.5" + assert "CMAKE_POLICY_VERSION_MINIMUM" not in os.environ + + def test_restores_existing_policy_minimum_after_arm_install(self): + """An existing user setting is restored after the installer finishes.""" + with ( + mock.patch("isaaclab.cli.commands.install.is_arm", return_value=True), + mock.patch.dict("os.environ", {"CMAKE_POLICY_VERSION_MINIMUM": "3.10"}, clear=True), + ): + with install_cmd._arm_cmake_policy_compatibility(): + assert os.environ["CMAKE_POLICY_VERSION_MINIMUM"] == "3.5" + assert os.environ["CMAKE_POLICY_VERSION_MINIMUM"] == "3.10" + + def test_leaves_environment_unchanged_outside_arm(self): + """Non-ARM installs do not receive the compatibility setting.""" + with ( + mock.patch("isaaclab.cli.commands.install.is_arm", return_value=False), + mock.patch.dict("os.environ", {}, clear=True), + ): + with install_cmd._arm_cmake_policy_compatibility(): + assert "CMAKE_POLICY_VERSION_MINIMUM" not in os.environ + + # --------------------------------------------------------------------------- # _install_isaaclab_submodules targeted dependency upgrades # --------------------------------------------------------------------------- diff --git a/source/isaaclab/test/cli/test_uv_run_pyproject.py b/source/isaaclab/test/cli/test_uv_run_pyproject.py index 076dc63a8959..a26e8fd265a2 100644 --- a/source/isaaclab/test/cli/test_uv_run_pyproject.py +++ b/source/isaaclab/test/cli/test_uv_run_pyproject.py @@ -111,10 +111,7 @@ def test_ci_ovrtx_installs_match_source_package_requirement(): """CI must not bypass the OVRTX version range declared by the source package.""" expected_requirement = _ovrtx_requirement_from_setup() - for workflow_path in ( - ".github/workflows/build.yaml", - ".github/workflows/daily-compatibility.yml", - ): + for workflow_path in (".github/workflows/build.yaml",): workflow = (_repo_root() / workflow_path).read_text(encoding="utf-8") ovrtx_install_lines = [ line.strip() for line in workflow.splitlines() if "extra-pip-packages:" in line and "ovrtx" in line