Skip to content
Closed
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
10 changes: 10 additions & 0 deletions .github/actions/run-tests/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,16 @@ run_tests() {
./isaaclab.sh -p -c \"import importlib.metadata,json,os,pathlib; from packaging.version import Version; manifest=json.loads(pathlib.Path(os.environ['TEST_WHEELHOUSE_MANIFEST']).read_text(encoding='utf-8')); expected=manifest.get('ovphysx_version'); actual=importlib.metadata.version('ovphysx'); print(f'Resolved ovphysx package version: {actual}'); print(f'Wheelhouse manifest ovphysx version: {expected}'); import ovphysx; runtime=getattr(ovphysx, '__version__', actual); print(f'Imported ovphysx runtime version: {runtime}'); raise SystemExit(0 if Version(actual) == Version(expected) and Version(runtime) == Version(expected) else f'ovphysx version mismatch: installed {actual}, import {runtime}, manifest {expected}')\"
;;
esac
case \" \${TEST_WHEELHOUSE_PACKAGES} \" in
*\" ovrtx \"*)
./isaaclab.sh -p -c \"import hashlib,importlib.metadata,json,os,pathlib; from packaging.version import Version; manifest=json.loads(pathlib.Path(os.environ['TEST_WHEELHOUSE_MANIFEST']).read_text(encoding='utf-8')); record=manifest.get('ovrtx') or manifest.get('packages', {}).get('ovrtx', {}); wheel=pathlib.Path(os.environ['TEST_WHEELHOUSE_PATH']) / record.get('file', ''); expected=record.get('version') or manifest.get('ovrtx_version'); actual=importlib.metadata.version('ovrtx'); digest=hashlib.file_digest(wheel.open('rb'), 'sha256').hexdigest(); import ovrtx; from ovrtx import _version; expected_ref=record.get('source_ref'); expected_sha=record.get('source_sha'); print(f'Resolved ovrtx package version: {actual}'); print(f'Wheelhouse manifest ovrtx version: {expected}'); print(f'Wheelhouse ovrtx SHA-256: {digest}'); print(f'Imported ovrtx provenance: {_version.gitbranch}@{_version.githash}'); raise SystemExit(0 if Version(actual) == Version(expected) and digest == record.get('sha256') and _version.gitbranch == expected_ref and _version.githash == expected_sha else f'ovrtx wheelhouse mismatch: installed {actual}, manifest {expected}, sha {digest}, provenance {_version.gitbranch}@{_version.githash}')\"
;;
esac
case \" \${TEST_WHEELHOUSE_PACKAGES} \" in
*\" ovstage \"*)
./isaaclab.sh -p -c \"import hashlib,importlib.metadata,json,os,pathlib; from packaging.version import Version; manifest=json.loads(pathlib.Path(os.environ['TEST_WHEELHOUSE_MANIFEST']).read_text(encoding='utf-8')); record=manifest.get('ovstage') or manifest.get('packages', {}).get('ovstage', {}); wheel=pathlib.Path(os.environ['TEST_WHEELHOUSE_PATH']) / record.get('file', ''); expected=record.get('version') or manifest.get('ovstage_version'); actual=importlib.metadata.version('ovstage'); digest=hashlib.file_digest(wheel.open('rb'), 'sha256').hexdigest(); import ovstage; runtime=getattr(ovstage, '__version__', actual); print(f'Resolved ovstage package version: {actual}'); print(f'Wheelhouse manifest ovstage version: {expected}'); print(f'Wheelhouse ovstage SHA-256: {digest}'); raise SystemExit(0 if Version(actual) == Version(expected) and Version(runtime) == Version(expected) and digest == record.get('sha256') else f'ovstage wheelhouse mismatch: installed {actual}, import {runtime}, manifest {expected}, sha {digest}')\"
;;
esac
fi
if [ -n \"\${TEST_EXTRA_PIP_PACKAGES:-}\" ]; then
echo \"Installing extra pip packages: \${TEST_EXTRA_PIP_PACKAGES}\"
Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ on:
- develop
- 'release/**'
pull_request:
types: [opened, synchronize, reopened]
types: [opened, synchronize, reopened, labeled]
branches:
- main
- develop
Expand All @@ -77,6 +77,7 @@ env:
jobs:
changes:
name: Detect Changes
if: github.event.action != 'labeled' || github.event.label.name == 'ci:run-ovstage'
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down Expand Up @@ -855,15 +856,17 @@ jobs:
container-name: "isaac-lab-rendering-correctness-kitless-legacy-test"
omni-github-test-type: "rendering-correctness-kitless-legacy"

# ovstage is post-merge only; it never ran on pull requests.
# ovstage is post-merge by default. Maintainers can opt a PR into this expensive lane with
# the ci:run-ovstage label when validating renderer or OVStage integration changes.
test-rendering-correctness-kitless-ovstage:
name: "rendering-correctness-kitless (ovstage)"
runs-on: [self-hosted, gpu]
timeout-minutes: 120
continue-on-error: ${{ github.event_name == 'workflow_dispatch' }}
needs: [build, config]
if: >-
github.event_name != 'pull_request' &&
(github.event_name != 'pull_request' ||
contains(github.event.pull_request.labels.*.name, 'ci:run-ovstage')) &&
needs.build.result == 'success'
env:
USE_OVPHYSX_WHEELHOUSE: ${{ needs.config.outputs.ovphysx_wheelhouse_image != '' && ((github.event_name == 'pull_request' && github.base_ref == 'develop' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name != 'pull_request' && github.ref_name == 'develop')) }}
Expand Down
5 changes: 5 additions & 0 deletions source/isaaclab_ov/changelog.d/esekkin-ovrtx-pairing.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Added
^^^^^

* Added an OVRTX/OVStage version compatibility check before native renderer construction. OVRTX 0.4
requires OVStage 0.1, while OVRTX 0.5 requires ``ovstage>=0.2.0.370625,<0.3``.
73 changes: 72 additions & 1 deletion source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
#
# SPDX-License-Identifier: BSD-3-Clause

"""Version compatibility between the public OVRTX 0.4 API and OVRTX 0.5 and later.
"""Version compatibility between OVRTX, OVStage, and Isaac Lab.

OVRTX 0.4 keys ``frame.render_vars`` by render-var source name (``"LdrColor"``), while
0.5 keys it by the authored RenderVar prim path (``"/Render/Vars/LdrColor"``). The
installed version cannot change while the process runs, so the key form is resolved once
at import and published as :data:`RENDER_VAR_FRAME_KEYS`; per-frame code indexes that
mapping instead of re-checking the version.

OVRTX and OVStage also expose a native ABI boundary that package metadata does not constrain.
The supported pair is checked before renderer construction so an incompatible install fails with
an actionable Python exception instead of aborting in native OVStage population.

The public extras stay pinned to ``ovrtx==0.4.1.364340``; a missing or unparsable install
keeps the 0.4 key form.
"""
Expand All @@ -30,6 +34,12 @@

# First OVRTX version that keys ``frame.render_vars`` by RenderVar prim path.
_PRIM_PATH_RENDER_VARS_VERSION = Version("0.5")
_OVRTX_04_MIN_VERSION = Version("0.4")
_OVRTX_05_MIN_VERSION = Version("0.5")
_OVRTX_06_MIN_VERSION = Version("0.6")
_OVSTAGE_01_MIN_VERSION = Version("0.1.1.355824")
_OVSTAGE_02_MIN_VERSION = Version("0.2.0.370625")
_OVSTAGE_03_MIN_VERSION = Version("0.3")


def detect_ovrtx_version() -> Version | None:
Expand All @@ -54,6 +64,64 @@ def detect_ovrtx_version() -> Version | None:
return None


def detect_ovstage_version() -> Version | None:
"""Return the installed ``ovstage`` version.

Returns:
The installed version, or ``None`` when ``ovstage`` is absent or its version string
cannot be parsed.
"""
try:
raw = importlib.metadata.version("ovstage")
except importlib.metadata.PackageNotFoundError:
return None
try:
return Version(raw)
except InvalidVersion:
logger.warning("Could not parse ovstage version %r.", raw)
return None


def validate_ovrtx_ovstage_compatibility(
ovrtx_version: Version | None,
ovstage_version: Version | None,
) -> None:
"""Validate the native OVRTX and OVStage package pairing.

Args:
ovrtx_version: Installed OVRTX version, or ``None`` when it cannot be resolved.
ovstage_version: Installed OVStage version, or ``None`` when it cannot be resolved.

Raises:
RuntimeError: If a known OVRTX release line is paired with an unsupported OVStage version.
"""
required_ovstage: str | None = None
is_compatible = True
if ovrtx_version is not None and _OVRTX_04_MIN_VERSION <= ovrtx_version < _OVRTX_05_MIN_VERSION:
required_ovstage = ">=0.1.1.355824,<0.2"
is_compatible = (
ovstage_version is not None and _OVSTAGE_01_MIN_VERSION <= ovstage_version < _OVSTAGE_02_MIN_VERSION
)
elif ovrtx_version is not None and _OVRTX_05_MIN_VERSION <= ovrtx_version < _OVRTX_06_MIN_VERSION:
required_ovstage = ">=0.2.0.370625,<0.3"
is_compatible = (
ovstage_version is not None and _OVSTAGE_02_MIN_VERSION <= ovstage_version < _OVSTAGE_03_MIN_VERSION
)

if required_ovstage is not None and not is_compatible:
installed_ovstage = str(ovstage_version) if ovstage_version is not None else "missing or unparsable"
raise RuntimeError(
"Unsupported OVRTX/OVStage package combination: "
f"ovrtx {ovrtx_version} requires ovstage{required_ovstage}, but found {installed_ovstage}. "
"Install a compatible pair before creating OVRTXRenderer."
)


def validate_installed_ovrtx_ovstage() -> None:
"""Validate the installed OVRTX and OVStage distributions."""
validate_ovrtx_ovstage_compatibility(OVRTX_VERSION, OVSTAGE_VERSION)


def uses_prim_path_render_vars(version: Version | None) -> bool:
"""Return whether ``version`` keys ``frame.render_vars`` by RenderVar prim path.

Expand Down Expand Up @@ -85,5 +153,8 @@ def build_render_var_frame_keys(version: Version | None) -> Mapping[str, str]:
OVRTX_VERSION: Version | None = detect_ovrtx_version()
"""Installed OVRTX version, or ``None`` when it is unavailable or unparsable."""

OVSTAGE_VERSION: Version | None = detect_ovstage_version()
"""Installed OVStage version, or ``None`` when it is unavailable or unparsable."""

RENDER_VAR_FRAME_KEYS: Mapping[str, str] = build_render_var_frame_keys(OVRTX_VERSION)
"""Maps render-var source name to its ``frame.render_vars`` key for the installed OVRTX."""
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
decode_stable_id_map,
decode_stable_id_semantic_id_map,
)
from .ovrtx_compat import RENDER_VAR_FRAME_KEYS
from .ovrtx_compat import RENDER_VAR_FRAME_KEYS, validate_installed_ovrtx_ovstage
from .ovrtx_mapping import cuda_device_id
from .ovrtx_renderer_cfg import OVRTXRendererCfg
from .ovrtx_renderer_kernels import (
Expand Down Expand Up @@ -322,6 +322,7 @@ def supported_output_types(self) -> dict[RenderBufferKind, RenderBufferSpec]:
}

def __init__(self, cfg: OVRTXRendererCfg):
validate_installed_ovrtx_ovstage()
self.cfg = cfg
self._device = "cuda:0" # default; overridden by create_render_data(spec)
self._render_product_paths = []
Expand Down
59 changes: 59 additions & 0 deletions source/isaaclab_ov/test/test_ovrtx_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,19 @@
RENDER_VAR_FRAME_KEYS,
build_render_var_frame_keys,
detect_ovrtx_version,
detect_ovstage_version,
uses_prim_path_render_vars,
validate_ovrtx_ovstage_compatibility,
)
from isaaclab_ov.renderers.ovrtx_usd import render_var_prim_paths_by_source # noqa: E402
else:
RENDER_VAR_FRAME_KEYS = None
build_render_var_frame_keys = None
detect_ovstage_version = None
detect_ovrtx_version = None
render_var_prim_paths_by_source = None
uses_prim_path_render_vars = None
validate_ovrtx_ovstage_compatibility = None


def test_detect_ovrtx_version_reads_distribution_metadata(monkeypatch: pytest.MonkeyPatch):
Expand All @@ -58,6 +62,61 @@ def test_detect_ovrtx_version_returns_none_for_unparseable_version(monkeypatch:
assert detect_ovrtx_version() is None


def test_detect_ovstage_version_reads_distribution_metadata(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(importlib.metadata, "version", lambda name: "0.2.0.370625")
assert detect_ovstage_version() == Version("0.2.0.370625")


def test_detect_ovstage_version_returns_none_when_uninstalled(monkeypatch: pytest.MonkeyPatch):
def _missing(name: str) -> str:
raise importlib.metadata.PackageNotFoundError(name)

monkeypatch.setattr(importlib.metadata, "version", _missing)
assert detect_ovstage_version() is None


def test_detect_ovstage_version_returns_none_for_unparseable_version(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(importlib.metadata, "version", lambda name: "internal-build")
assert detect_ovstage_version() is None


@pytest.mark.parametrize(
("ovrtx_version", "ovstage_version"),
[
(None, None),
(Version("0.4.1.364340"), Version("0.1.1.355824")),
(Version("0.4.9"), Version("0.1.9")),
(Version("0.5.0.0"), Version("0.2.0.370625")),
(Version("0.5.9"), Version("0.2.9")),
(Version("0.6"), None),
],
)
def test_validate_ovrtx_ovstage_compatibility_accepts_supported_pairs(
ovrtx_version: Version | None,
ovstage_version: Version | None,
):
validate_ovrtx_ovstage_compatibility(ovrtx_version, ovstage_version)


@pytest.mark.parametrize(
("ovrtx_version", "ovstage_version", "required"),
[
(Version("0.4.1.364340"), None, "ovstage>=0.1.1.355824,<0.2"),
(Version("0.4.1.364340"), Version("0.2.0.370625"), "ovstage>=0.1.1.355824,<0.2"),
(Version("0.5.0.0"), None, "ovstage>=0.2.0.370625,<0.3"),
(Version("0.5.0.0"), Version("0.1.1.355824"), "ovstage>=0.2.0.370625,<0.3"),
(Version("0.5.0.0"), Version("0.3"), "ovstage>=0.2.0.370625,<0.3"),
],
)
def test_validate_ovrtx_ovstage_compatibility_rejects_unsupported_pairs(
ovrtx_version: Version,
ovstage_version: Version | None,
required: str,
):
with pytest.raises(RuntimeError, match=required):
validate_ovrtx_ovstage_compatibility(ovrtx_version, ovstage_version)


@pytest.mark.parametrize(
("version", "expected"),
[
Expand Down
1 change: 1 addition & 0 deletions source/isaaclab_ov/test/test_ovrtx_renderer_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def __init__(self, **kwargs):
monkeypatch.setattr(ovrtx_renderer_module, "RendererConfig", RecordingRendererConfig)
monkeypatch.setattr(ovrtx_renderer_module, "Renderer", lambda config: object()) # noqa: ARG005
monkeypatch.setattr(ovrtx_renderer_module, "ovrtx_use_ovstage_enabled", lambda: False)
monkeypatch.setattr(ovrtx_renderer_module, "validate_installed_ovrtx_ovstage", lambda: None)

renderer = OVRTXRenderer(OVRTXRendererCfg())

Expand Down
Loading