Source code for unisim.backend.isaacgym.dependencies
"""Runtime discovery for the out-of-process IsaacGym (Preview 4) worker.
IsaacGym only supports Python 3.6-3.8 and can never be installed into the
main UniLab environment (Python >= 3.10). Physics therefore runs in a
dedicated conda env created by ``scripts/tools/setup_isaacgym_env.sh`` under
``$UNISIM_ISAACGYM_HOME`` (default ``~/.cache/unisim/isaacgym``):
- ``miniconda3/envs/hsgym/bin/python3.8`` — the worker interpreter,
- ``miniconda3/envs/hsgym/lib`` — prepended to the worker's
``LD_LIBRARY_PATH`` (conda ``libstdcxx-ng`` fixes the GLIBCXX mismatch),
- ``miniconda3/envs/hsgym/bin`` — prepended to the worker's ``PATH`` so the
pip-installed ``ninja`` is reachable for the one-time gymtorch JIT compile,
- ``isaacgym/python`` — appended to the worker's ``sys.path``.
``UNISIM_ISAACGYM_PYTHON`` overrides the interpreter path explicitly.
The former ``UNILAB_*`` names remain accepted as a migration fallback.
"""
from __future__ import annotations
import os
from dataclasses import dataclass
from pathlib import Path
from unisim.optional import OptionalDependencyError
ENV_PYTHON = "UNISIM_ISAACGYM_PYTHON"
ENV_HOME = "UNISIM_ISAACGYM_HOME"
_LEGACY_ENV_PYTHON = "UNILAB_ISAACGYM_PYTHON"
_LEGACY_ENV_HOME = "UNILAB_ISAACGYM_HOME"
_CONDA_ENV_REL = Path("miniconda3") / "envs" / "hsgym"
_ISAACGYM_PYTHON_REL = Path("isaacgym") / "python"
_SETUP_HINT = (
"Install the dedicated IsaacGym worker environment with "
"scripts/tools/setup_isaacgym_env.sh (see the IsaacGym backend page in "
"docs/sphinx/source/*/2-user_guide/3-backends/4-isaacgym.md)."
)
[docs]
class IsaacGymDependencyError(OptionalDependencyError):
"""Raised with an actionable setup hint when the worker runtime is absent."""
[docs]
@dataclass(frozen=True)
class IsaacGymRuntime:
"""Resolved on-disk locations for the Python 3.8 IsaacGym worker."""
python: Path
isaacgym_python: Path
lib_path: Path
[docs]
def default_isaacgym_home() -> Path:
"""Return the default IsaacGym install root (including legacy override)."""
value = os.environ.get(ENV_HOME) or os.environ.get(_LEGACY_ENV_HOME)
return Path(value or "~/.cache/unisim/isaacgym").expanduser()
def _candidate_paths(home: Path) -> IsaacGymRuntime:
env_root = home / _CONDA_ENV_REL
return IsaacGymRuntime(
python=env_root / "bin" / "python3.8",
isaacgym_python=home / _ISAACGYM_PYTHON_REL,
lib_path=env_root / "lib",
)
[docs]
def resolve_isaacgym_runtime(python_override: str | None = None) -> IsaacGymRuntime:
"""Resolve the worker interpreter and IsaacGym package locations.
Raises:
IsaacGymDependencyError: If any required component is missing.
"""
home = default_isaacgym_home()
runtime = _candidate_paths(home)
if python_override:
python = Path(python_override).expanduser()
elif os.environ.get(ENV_PYTHON) or os.environ.get(_LEGACY_ENV_PYTHON):
python = Path(os.environ.get(ENV_PYTHON) or os.environ[_LEGACY_ENV_PYTHON]).expanduser()
else:
python = runtime.python
if not python.is_file():
raise IsaacGymDependencyError(
f"isaacgym backend could not find the Python 3.8 worker interpreter at "
f"{python}. {_SETUP_HINT}"
)
if not runtime.isaacgym_python.is_dir():
raise IsaacGymDependencyError(
f"isaacgym backend could not find the IsaacGym package directory at "
f"{runtime.isaacgym_python}. {_SETUP_HINT}"
)
if not runtime.lib_path.is_dir():
raise IsaacGymDependencyError(
f"isaacgym backend could not find the worker conda env lib directory at "
f"{runtime.lib_path}. {_SETUP_HINT}"
)
return IsaacGymRuntime(
python=python,
isaacgym_python=runtime.isaacgym_python,
lib_path=runtime.lib_path,
)
[docs]
def isaacgym_runtime_available() -> bool:
"""Return True when the worker runtime resolves without raising."""
try:
resolve_isaacgym_runtime()
except IsaacGymDependencyError:
return False
return True
[docs]
def build_worker_env(runtime: IsaacGymRuntime) -> dict[str, str]:
"""Build the worker process environment.
Prepends the conda env's ``lib`` to ``LD_LIBRARY_PATH`` (the
``libstdcxx-ng`` GLIBCXX fix and ``libpython3.8``) and its ``bin`` to
``PATH`` (the pip-installed ``ninja`` must be reachable for the one-time
gymtorch JIT compile on a fresh machine).
"""
env = dict(os.environ)
existing = env.get("LD_LIBRARY_PATH", "")
env["LD_LIBRARY_PATH"] = f"{runtime.lib_path}:{existing}" if existing else str(runtime.lib_path)
env_bin = str(runtime.lib_path.parent / "bin")
existing_path = env.get("PATH", "")
env["PATH"] = f"{env_bin}:{existing_path}" if existing_path else env_bin
return env
__all__ = [
"ENV_HOME",
"ENV_PYTHON",
"IsaacGymDependencyError",
"IsaacGymRuntime",
"build_worker_env",
"default_isaacgym_home",
"isaacgym_runtime_available",
"resolve_isaacgym_runtime",
]