diff --git a/.github/workflows/message-codegen.yml b/.github/workflows/message-codegen.yml deleted file mode 100644 index c86d4ea9ce1..00000000000 --- a/.github/workflows/message-codegen.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: message-codegen -# CI integration check for the CDR message codegen layer. It runs the one test -# file under dimos/message_codegen/ against the published dimos-cdrgen library, -# on Linux and macOS, in a clean virtualenv without the dimOS dev environment. - -on: - pull_request: - paths: - - dimos/message_codegen/** - - native/cpp/include/dimos/native/cdr_codec.hpp - - docs/development/message-source-packages.md - - pyproject.toml - - uv.lock - - .github/workflows/message-codegen.yml - workflow_dispatch: - -permissions: - contents: read - -jobs: - integration: - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-14] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v7 - - uses: astral-sh/setup-uv@v10.0.1 - - name: Install published generator and consumer test tools - run: | - uv venv .venv --python 3.12 - uv pip install --python .venv/bin/python 'dimos-cdrgen==0.1.0' numpy build pytest pytest-asyncio pytest-env - - name: Verify SDK discovery and installed Python consumer without pip - run: | - .venv/bin/python -c 'import importlib.util; assert importlib.util.find_spec("pip") is None' - .venv/bin/python -m pytest --noconftest -o addopts='' -o 'markers=native_e2e: compiled native consumer acceptance' --strict-markers -m 'not native_e2e' -q dimos/message_codegen/test_integration.py - - name: Verify compiled C++ consumer - run: | - .venv/bin/python -m pytest --noconftest -o addopts='' -o 'markers=native_e2e: compiled native consumer acceptance' --strict-markers -m native_e2e -q dimos/message_codegen/test_integration.py - .venv/bin/python -c 'import importlib.util; assert importlib.util.find_spec("pip") is None; assert importlib.util.find_spec("catkin_pkg") is None' diff --git a/.gitignore b/.gitignore index 181e3ae4bbc..2644e75f80b 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,6 @@ optuna.db # policy blobs are loaded from outside the repository *.bin + +# Prepared from schema-sources.json; keep downloaded schemas out of Git. +/packages/dimos-generated/interfaces/ diff --git a/MANIFEST.in b/MANIFEST.in index 161b5666977..7d68ef988e6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -47,3 +47,8 @@ prune dimos/web/command-center-extension global-exclude test_*.py global-exclude conftest.py + +# Built-in message inputs are prepared automatically during source builds; the sdist is self-contained. +include packages/dimos-generated/schema-sources.json +recursive-include packages/dimos-generated/custom *.msg LICENSE +recursive-include packages/dimos-generated/interfaces *.msg *LICENSE* diff --git a/dimos/message_codegen/build.py b/dimos/message_codegen/build.py new file mode 100644 index 00000000000..f10fb51734f --- /dev/null +++ b/dimos/message_codegen/build.py @@ -0,0 +1,122 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""DimOS source-build inputs; imported only by packaging and preparation tools.""" + +from hashlib import sha256 +import os +from pathlib import Path, PurePosixPath +import shutil +from tempfile import TemporaryDirectory +from typing import Annotated, Literal + +from dimos_cdrgen.source_inputs import prepare_sources +from pydantic import AfterValidator, BaseModel, ConfigDict, Field + +ROOT = Path(__file__).resolve().parents[2] +PROJECT = ROOT / "packages/dimos-generated" + + +def _relative_path(value: str) -> str: + path = PurePosixPath(value) + if not path.parts or path.is_absolute() or ".." in path.parts: + raise ValueError("Unsafe source input path") + return value + + +RelativePath = Annotated[str, AfterValidator(_relative_path)] +Sha256 = Annotated[str, Field(pattern=r"^[0-9a-f]{64}$")] + + +class SourceFile(BaseModel): + model_config = ConfigDict(strict=True, extra="forbid") + + path: RelativePath + sha256: Sha256 + + +class SourceArchive(BaseModel): + model_config = ConfigDict(strict=True, extra="forbid") + + repository: str | None = None + revision: str | None = None + url: str + sha256: Sha256 + prefix: RelativePath + files: dict[RelativePath, SourceFile] + + +class SourceLock(BaseModel): + model_config = ConfigDict(strict=True, extra="forbid") + + format: Literal[1] + sources: list[SourceArchive] + + +def _matches(directory: Path, files: dict[str, str]) -> bool: + return all( + (directory / name).is_file() + and sha256((directory / name).read_bytes()).hexdigest() == digest + for name, digest in files.items() + ) + + +def prepare_builtin_sources( + cache: Path | None = None, *, offline: bool = False, project: Path = PROJECT +) -> Path: + cache = cache or Path( + os.environ.get("DIMOS_MESSAGE_SOURCE_CACHE", ROOT / "build/builtin-source-cache") + ) + lock = project / "schema-sources.json" + data = SourceLock.model_validate_json(lock.read_text()) + upstream = { + name: entry.sha256 for source in data.sources for name, entry in source.files.items() + } + custom_root = project / "custom/dimos_msgs" + custom = { + "dimos_msgs/msg/" + path.name: sha256(path.read_bytes()).hexdigest() + for path in (custom_root / "msg").glob("*.msg") + } + custom["licenses/dimos_msgs-LICENSE"] = sha256( + (custom_root / "LICENSE").read_bytes() + ).hexdigest() + expected = {**upstream, **custom} + target = project / "interfaces" + actual = {str(path.relative_to(target)) for path in target.rglob("*") if path.is_file()} + if actual == expected.keys() and _matches(target, expected): + return target + # Stage both downloaded and local definitions before replacing previous inputs. + with TemporaryDirectory(prefix=".interfaces-", dir=project) as directory: + stage = Path(directory) / "inputs" + if _matches(target, upstream): + stage.mkdir() + for name in upstream: + output = stage / name + output.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(target / name, output) + else: + prepare_sources(lock, stage, cache / "archives", offline=offline) + shutil.copytree(custom_root / "msg", stage / "dimos_msgs/msg") + (stage / "licenses").mkdir(exist_ok=True) + shutil.copyfile(custom_root / "LICENSE", stage / "licenses/dimos_msgs-LICENSE") + backup = Path(directory) / "previous" + if target.exists(): + target.rename(backup) + try: + stage.rename(target) + except OSError: + if backup.exists(): + backup.rename(target) + raise + return target diff --git a/dimos/message_codegen/test_build.py b/dimos/message_codegen/test_build.py new file mode 100644 index 00000000000..c2780f1c718 --- /dev/null +++ b/dimos/message_codegen/test_build.py @@ -0,0 +1,160 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Source-build preparation is verified, reusable and preserves previous outputs.""" + +from hashlib import sha256 +from io import BytesIO +import json +from pathlib import Path +import shutil +import tarfile + +from pydantic import ValidationError +import pytest + +from dimos.message_codegen.build import prepare_builtin_sources + + +@pytest.fixture +def source_project(tmp_path): + project = tmp_path / "project" + source = project / "custom/dimos_msgs/msg/Reading.msg" + source.parent.mkdir(parents=True) + source.write_text("float64 value\n") + (source.parents[1] / "LICENSE").write_text("Project license\n") + payload = BytesIO() + contents = b"uint32 sec\n" + with tarfile.open(fileobj=payload, mode="w:gz") as archive: + info = tarfile.TarInfo("upstream/stamp_msgs/msg/Stamp.msg") + info.size = len(contents) + archive.addfile(info, BytesIO(contents)) + data = payload.getvalue() + url = "https://example.test/messages.tar.gz" + lock = { + "format": 1, + "sources": [ + { + "url": url, + "sha256": sha256(data).hexdigest(), + "prefix": "upstream/", + "files": { + "stamp_msgs/msg/Stamp.msg": { + "path": "stamp_msgs/msg/Stamp.msg", + "sha256": sha256(contents).hexdigest(), + } + }, + } + ], + } + (project / "schema-sources.json").write_text(json.dumps(lock)) + cache = tmp_path / "cache" + archives = cache / "archives" + archives.mkdir(parents=True) + (archives / (sha256(url.encode()).hexdigest() + ".tar.gz")).write_bytes(data) + return project, cache, data + + +def test_cold_preparation_downloads_verified_inputs(source_project, mocker): + project, cache, data = source_project + shutil.rmtree(cache) + response = mocker.patch("requests.get").return_value.__enter__.return_value + response.content = data + + target = prepare_builtin_sources(cache, project=project) + + assert (target / "stamp_msgs/msg/Stamp.msg").read_text() == "uint32 sec\n" + assert (target / "dimos_msgs/msg/Reading.msg").read_text() == "float64 value\n" + assert (target / "licenses/dimos_msgs-LICENSE").read_text() == "Project license\n" + + +def test_prepared_inputs_and_local_rename_work_offline_without_archives(source_project): + project, cache, _ = source_project + target = prepare_builtin_sources(cache, offline=True, project=project) + shutil.rmtree(cache) + assert prepare_builtin_sources(cache, offline=True, project=project) == target + source = project / "custom/dimos_msgs/msg/Reading.msg" + source.unlink() + source.with_name("Changed.msg").write_text("float64 value\nstring label\n") + + prepare_builtin_sources(cache, offline=True, project=project) + + assert not (target / "dimos_msgs/msg/Reading.msg").exists() + assert (target / "dimos_msgs/msg/Changed.msg").read_text() == "float64 value\nstring label\n" + assert (target / "stamp_msgs/msg/Stamp.msg").read_text() == "uint32 sec\n" + + +def test_bad_source_hash_preserves_prepared_inputs(source_project): + project, cache, _ = source_project + target = prepare_builtin_sources(cache, offline=True, project=project) + path = project / "schema-sources.json" + lock = json.loads(path.read_text()) + lock["sources"][0]["files"]["stamp_msgs/msg/Stamp.msg"]["sha256"] = "0" * 64 + path.write_text(json.dumps(lock)) + + with pytest.raises(ValueError, match="SHA256 mismatch"): + prepare_builtin_sources(cache, offline=True, project=project) + + assert (target / "stamp_msgs/msg/Stamp.msg").read_text() == "uint32 sec\n" + assert (target / "dimos_msgs/msg/Reading.msg").read_text() == "float64 value\n" + + +def test_offline_cold_inputs_fail_without_creating_output(source_project): + project, cache, _ = source_project + shutil.rmtree(cache) + with pytest.raises(FileNotFoundError, match="not cached"): + prepare_builtin_sources(cache, offline=True, project=project) + assert not (project / "interfaces").exists() + + +@pytest.mark.parametrize("invalid", ["format", "path", "hash", "missing", "unknown"]) +def test_invalid_source_lock_preserves_inputs(source_project, invalid): + project, cache, _ = source_project + target = prepare_builtin_sources(cache, offline=True, project=project) + previous = (target / "stamp_msgs/msg/Stamp.msg").read_bytes() + lock_path = project / "schema-sources.json" + lock = json.loads(lock_path.read_text()) + if invalid == "format": + lock["format"] = 2 + elif invalid == "hash": + lock["sources"][0]["sha256"] = "invalid" + elif invalid == "missing": + del lock["sources"][0]["url"] + elif invalid == "unknown": + lock["sources"][0]["sha265"] = "0" * 64 + else: + files = lock["sources"][0]["files"] + files["../outside.msg"] = files.pop("stamp_msgs/msg/Stamp.msg") + lock_path.write_text(json.dumps(lock)) + with pytest.raises(ValidationError): + prepare_builtin_sources(cache, offline=True, project=project) + assert (target / "stamp_msgs/msg/Stamp.msg").read_bytes() == previous + + +def test_failed_publish_restores_previous_inputs(source_project, monkeypatch): + project, cache, _ = source_project + target = prepare_builtin_sources(cache, offline=True, project=project) + source = project / "custom/dimos_msgs/msg/Reading.msg" + source.write_text("string replacement\n") + rename = Path.rename + + def fail_publish(path, destination): + if path.name == "inputs" and destination == target: + raise OSError("publish failed") + return rename(path, destination) + + monkeypatch.setattr(Path, "rename", fail_publish) + with pytest.raises(OSError, match="publish failed"): + prepare_builtin_sources(cache, offline=True, project=project) + assert (target / "dimos_msgs/msg/Reading.msg").read_text() == "float64 value\n" diff --git a/dimos/message_codegen/test_bundled.py b/dimos/message_codegen/test_bundled.py new file mode 100644 index 00000000000..1b97990c5a1 --- /dev/null +++ b/dimos/message_codegen/test_bundled.py @@ -0,0 +1,175 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Acceptance for built-in messages owned by the main DimOS distribution. + +Build the root wheel and sdist first, then set DIMOS_BUNDLE_DIST and +DIMOS_BUNDLE_WHEELHOUSE. Python dependencies use only that local wheelhouse. Git-source acceptance +also downloads verified schema archives. Web assets follow the existing DimOS build policy. +""" + +import os +from pathlib import Path +import subprocess +import tarfile +import venv +import zipfile + +import pytest + + +def run(*command, cwd, env=None): + result = subprocess.run(command, cwd=cwd, env=env, capture_output=True, text=True, check=False) + assert result.returncode == 0, result.stdout + result.stderr + return result.stdout + + +@pytest.fixture(scope="module") +def artifacts(): + if "DIMOS_BUNDLE_DIST" not in os.environ: + pytest.skip("Set DIMOS_BUNDLE_DIST and DIMOS_BUNDLE_WHEELHOUSE for artifact acceptance") + dist = Path(os.environ["DIMOS_BUNDLE_DIST"]).resolve() + return ( + next(dist.glob("dimos-*.whl")), + next(dist.glob("dimos-*.tar.gz")), + Path(os.environ["DIMOS_BUNDLE_WHEELHOUSE"]).resolve(), + next(dist.glob("dimos-*.whl")).name.split("-")[1], + ) + + +def test_main_wheel_owns_messages_and_schema_closure(artifacts): + wheel, sdist, _, _ = artifacts + with zipfile.ZipFile(wheel) as archive: + names = set(archive.namelist()) + metadata = archive.read(next(n for n in names if n.endswith("/METADATA"))).decode() + assert "Requires-Dist: dimos-generated" not in metadata + assert "dimos/core/module.py" in names + assert "dimos/web/relay_bridge/_relay_dist/relay/main.ts" in names + if os.environ.get("DIMOS_ALLOW_MISSING_COCKPIT") != "1": + assert "dimos/web/relay_bridge/_relay_dist/sdk/dist/sdk.js" in names + assert "dimos/web/relay_bridge/_relay_dist/cockpit/dist/index.html" in names + for license in ( + Path(__file__).resolve().parents[2] / "packages/dimos-generated/interfaces/licenses" + ).glob("*"): + assert any( + ".dist-info/" in name and name.endswith("/" + license.name) for name in names + ) + assert not any(name.startswith("dimos_generated") for name in names) + assert "dimos/messages/std_msgs/msg/__init__.py" in names + assert "dimos/messages_schemas/schemas/std_msgs/msg/Header.msg" in names + assert "dimos/messages_schemas/schemas/builtin_interfaces/msg/Time.msg" in names + assert "dimos/messages_schemas/package/cpp/CMakeLists.txt" in names + assert "dimos/messages_schemas/package/rust/Cargo.toml" in names + with tarfile.open(sdist) as archive: + names = {name.split("/", 1)[-1] for name in archive.getnames()} + assert "packages/dimos-generated/interfaces/std_msgs/msg/Header.msg" in names + assert "packages/dimos-generated/interfaces/licenses/dimos_msgs-LICENSE" in names + + +@pytest.mark.parametrize("mode", ["wheel", "source", "editable", "git"]) +def test_installed_messages_and_external_header_identity(tmp_path, artifacts, mode): + wheel, sdist, wheelhouse, version = artifacts + environment = tmp_path / "venv" + venv.EnvBuilder(with_pip=True).create(environment) + python = str(environment / "bin/python") + pip = [python, "-m", "pip", "install", "--no-index", "--find-links", str(wheelhouse)] + run(*pip, "dimos-cdrgen==0.1.1", "setuptools>=70", "wheel", "pybind11", cwd=tmp_path) + if mode == "git": + root = Path(__file__).resolve().parents[2] + head = run("git", "rev-parse", "HEAD", cwd=root).strip() + source = f"git+{root.as_uri()}@{head}" + elif mode == "wheel": + source = wheel + else: + with tarfile.open(sdist) as archive: + archive.extractall(tmp_path / "source", filter="data") + source = next((tmp_path / "source").iterdir()) + env = {**os.environ, "DIMOS_OFFLINE": "0" if mode == "git" else "1"} + run( + *pip, + "--no-deps", + *(["-e"] if mode == "editable" else []), + str(source), + cwd=tmp_path, + env=env, + ) + # The wheel demo deliberately rejects editable paths outside site-packages. + if mode != "editable": + example = ( + Path(__file__).resolve().parents[2] / "examples/message-package/demo_installed_wheel.py" + ) + output = run(python, "-I", str(example), cwd=tmp_path) + assert "Messages: 73 types" in output + assert "PASS: dimos_msgs/msg/LineSegments3D" in output + project = tmp_path / "external" + definition = project / "interfaces/demo_msgs/msg/Reading.msg" + definition.parent.mkdir(parents=True) + definition.write_text("std_msgs/Header header\nfloat64 value\n") + (project / "pyproject.toml").write_text( + """[build-system] +requires = ["setuptools>=70", "wheel", "dimos-cdrgen==0.1.1", "dimos==0.0.14"] +build-backend = "setuptools.build_meta" +[project] +name = "external-reading" +version = "0.1.0" +dependencies = ["dimos==0.0.14", "dimos-cdrgen==0.1.1"] +[tool.dimos.messages] +python-module = "my_robot.messages" +[tool.dimos.messages.dependencies] +"dimos.messages" = "0.0.14" +""".replace("0.0.14", version) + ) + (project / "setup.py").write_text( + "from pathlib import Path\nfrom setuptools import setup\n" + "from dimos_cdrgen.source_package import setup_arguments\n" + "setup(**setup_arguments(Path(__file__).resolve().parent))\n" + ) + run(*pip, "--no-deps", "--no-build-isolation", str(project), cwd=tmp_path) + run( + python, + "-I", + "-c", + """from importlib.metadata import distribution, entry_points, PackageNotFoundError +import json +from importlib.util import find_spec +from dimos.messages.std_msgs.msg import Header +from dimos.messages.builtin_interfaces.msg import Time +from dimos.messages_schemas.provider import message_types, package_root +from my_robot.messages.demo_msgs.msg import Reading +from my_robot.messages_schemas.provider import package_root as external_root +from dimos_cdrgen.registry import encode, decode +assert distribution("dimos").version == "0.0.14" +assert find_spec("dimos_generated") is None +try: + distribution("dimos-generated") +except PackageNotFoundError: + pass +else: + raise AssertionError("Built-ins must have only the DimOS distribution owner") +assert len(message_types()) == 73 +assert len([ep for ep in entry_points(group="dimos.messages") if ep.name == "dimos"]) == 1 +header = Header(Time(17, 23), "map") +value = Reading(header=header, value=21.5) +assert type(value.header) is Header +assert type(decode(encode(value), Reading).header) is Header +assert decode(encode(value), Reading) == value +metadata = json.loads((package_root() / "message-package.json").read_text()) +assert metadata["version"] == "0.0.14" +assert (external_root() / "schemas/std_msgs/msg/Header.msg").is_file() +assert (external_root() / "schemas/builtin_interfaces/msg/Time.msg").is_file() +assert not (external_root() / "cpp/std_msgs").exists() +assert all("dimos-generated" not in req for req in distribution("external-reading").requires) +""".replace("0.0.14", version), + cwd=tmp_path, + ) diff --git a/dimos/message_codegen/test_integration.py b/dimos/message_codegen/test_integration.py index cbef553c448..e8f162334f6 100644 --- a/dimos/message_codegen/test_integration.py +++ b/dimos/message_codegen/test_integration.py @@ -15,13 +15,11 @@ """Verify the DimOS adapter and one installed consumer of published cdrgen.""" import json -import os from pathlib import Path import subprocess import sys from types import SimpleNamespace -from dimos_cdrgen.generate import generate import pytest from dimos.message_codegen import native_build @@ -62,12 +60,12 @@ def test_installed_message_consumer(tmp_path: Path) -> None: source.parent.mkdir(parents=True) source.write_text("int32 value\n") (project / "pyproject.toml").write_text("""[build-system] -requires = ["setuptools>=77", "wheel", "dimos-cdrgen==0.1.0"] +requires = ["setuptools>=77", "wheel", "dimos-cdrgen==0.1.1"] build-backend = "setuptools.build_meta" [project] name = "dimos-cdr-consumer" version = "0.1.0" -dependencies = ["dimos-cdrgen==0.1.0"] +dependencies = ["dimos-cdrgen==0.1.1"] [tool.dimos.messages] source = "interfaces" """) @@ -108,87 +106,3 @@ def test_installed_message_consumer(tmp_path: Path) -> None: """, cwd=tmp_path, ) - - -@pytest.fixture(scope="session") -def native_build_python(tmp_path_factory: pytest.TempPathFactory) -> Path: - # Native generator prerequisites are build tools, isolated from DimOS runtime deps. - # In particular, cdrgen0.1.0's PyYAML pin must not downgrade the runtime lock. - environment = tmp_path_factory.mktemp("cdr-native-toolchain") / "venv" - run("uv", "venv", "--python", sys.executable, str(environment), cwd=environment.parent) - python = environment / ("Scripts/python.exe" if os.name == "nt" else "bin/python") - offline_args = ("--offline",) if os.environ.get("DIMOS_OFFLINE") == "1" else () - run( - "uv", - "pip", - "install", - *offline_args, - "--python", - str(python), - "dimos-cdrgen[native]==0.1.0", - cwd=environment.parent, - ) - return python - - -@pytest.mark.native_e2e -def test_cpp_consumer_uses_dimos_header(tmp_path: Path, native_build_python: Path) -> None: - source = tmp_path / "interfaces/consumer_msgs/msg/Tick.msg" - source.parent.mkdir(parents=True) - source.write_text("int32 value\n") - generated = tmp_path / "generated" - generate( - [source.parents[2]], generated, ["consumer_msgs/msg/Tick"], "dimos_consumer", shared=True - ) - prefix_file = tmp_path / "native-prefix.txt" - run( - str(native_build_python), - "-c", - """import json -import os -from pathlib import Path -import sys -from dimos_cdrgen.native_build import prepare_cpp -sources = {name: Path(path) for name, path in json.loads(os.environ.get("DIMOS_NATIVE_SOURCE_DIRS", "{}")).items()} -prefix = prepare_cpp( - Path(sys.argv[1]), cache=Path(sys.argv[2]), - offline=os.environ.get("DIMOS_OFFLINE") == "1", source_dirs=sources or None, -) -Path(sys.argv[3]).write_text(str(prefix)) -""", - str(generated), - os.environ.get("DIMOS_NATIVE_TEST_CACHE", str(tmp_path / "native-cache")), - str(prefix_file), - cwd=tmp_path, - ) - prefix = Path(prefix_file.read_text()) - toolchain = native_build.write_cmake_toolchain(prefix, tmp_path / "toolchain.cmake") - include = Path(__file__).resolve().parents[2] / "native/cpp/include" - (tmp_path / "CMakeLists.txt").write_text(f'''cmake_minimum_required(VERSION 3.20) -project(dimos_cdr_consumer LANGUAGES CXX) -find_package(dimos_consumer CONFIG REQUIRED) -add_executable(consumer main.cpp) -target_compile_features(consumer PRIVATE cxx_std_17) -target_include_directories(consumer PRIVATE "{include}") -target_link_libraries(consumer PRIVATE dimos_consumer::messages) -''') - (tmp_path / "main.cpp").write_text("""#include -#include -#include -int main() { - const std::vector expected{0, 1, 0, 0, 17, 0, 0, 0}; - auto value = dimos::native::cdr_decode(expected); - return value.value == 17 && dimos::native::cdr_encode(value) == expected ? 0 : 1; -} -""") - run( - "cmake", - "-S", - str(tmp_path), - "-B", - str(tmp_path / "build"), - f"-DCMAKE_TOOLCHAIN_FILE={toolchain}", - cwd=tmp_path, - ) - run("cmake", "--build", str(tmp_path / "build"), "--parallel", "2", cwd=tmp_path) - run(str(tmp_path / "build/consumer"), cwd=tmp_path) diff --git a/dimos/skills/visual_navigation_skills.py b/dimos/skills/visual_navigation_skills.py index c3a15503d86..5b118a77f0a 100644 --- a/dimos/skills/visual_navigation_skills.py +++ b/dimos/skills/visual_navigation_skills.py @@ -25,7 +25,7 @@ from pydantic import Field -from dimos.perception.visual_servoing import ( # type: ignore[import-untyped] +from dimos.perception.visual_servoing import ( # type: ignore[import-not-found] VisualServoing, ) from dimos.skills.skills import AbstractRobotSkill diff --git a/dimos/stream/audio/node_simulated.py b/dimos/stream/audio/node_simulated.py index f6dbe616084..e40fcb85d0e 100644 --- a/dimos/stream/audio/node_simulated.py +++ b/dimos/stream/audio/node_simulated.py @@ -19,7 +19,7 @@ from reactivex import Observable, create, disposable from dimos.constants import DEFAULT_THREAD_JOIN_TIMEOUT -from dimos.stream.audio.abstract import ( # type: ignore[import-untyped] +from dimos.stream.audio.abstract import ( # type: ignore[import-not-found] AbstractAudioEmitter, AudioEvent, ) diff --git a/docs/development/message-source-packages.md b/docs/development/message-source-packages.md index 6d20d3f04ae..f3926dcf942 100644 --- a/docs/development/message-source-packages.md +++ b/docs/development/message-source-packages.md @@ -1,58 +1,95 @@ # CDR generation in dimOS -> **WIP: CDR migration.** This page documents the generator integration in -> [the first stack layer (#4247)](https://github.com/dimensionalOS/dimos/pull/4247), -> not a complete CDR message/runtime workflow on `main`. The generator dependency, -> dimOS adapters and verification commands below are implemented in this layer. -> Built-in message packaging depends on -> [the second layer (#4366)](https://github.com/dimensionalOS/dimos/pull/4366); -> the runtime encoding switch and CDR recording/viewer flows belong to later layers. -> Installing this layer alone does not switch the existing dimOS runtime to CDR. - -dimOS uses the published `dimos-cdrgen==0.1.0` package directly. Its parser, -generators, ownership checks, codecs and native build recipes live in the -[independent library](https://github.com/TomCC7/dimos-cdrgen). -See its [Python, C++ and Rust guides](https://tomcc7.github.io/dimos-cdrgen/) -for message package configuration and supported schema features. - -Install `dimos-cdrgen==0.1.0` in both the build and runtime dependencies of -message packages. Import its APIs directly: - -```python -from dimos_cdrgen.registry import decode, encode -from dimos_cdrgen.source_package import setup_arguments +**WIP:** Built-in messages work in standalone examples. CDR transport and application +support are incomplete. Existing code continues to use `dimos.msgs`. + +`setup.py` reads the definitions in `packages/dimos-generated` and calls +`dimos-cdrgen==0.1.1`. The wheel contains `dimos.messages`, schemas, and native +source code. The examples use these installed files. + +The [cdrgen guides](https://tomcc7.github.io/dimos-cdrgen/) describe its APIs and +supported schemas. `dimos.message_codegen.native_build.write_cmake_toolchain` +adds paths for the dimOS SDK. + +## Run the built-in examples + +Run all commands below from the repository root. Install `uv` and a C++ compiler +first; the compiler is needed by dimOS's existing A* extension even for a source +installation used only by Python. The examples use Python 3.12. + +### Install and run Python + +```sh skip +uv venv .venv-msg-review --python 3.12 +DIMOS_ALLOW_MISSING_COCKPIT=1 uv pip install --python .venv-msg-review/bin/python . +.venv-msg-review/bin/python -I examples/message-package/demo_installed_wheel.py +``` + +`DIMOS_ALLOW_MISSING_COCKPIT=1` is the existing option to install without prepared +web UI bundles. Omit it when building a complete distribution with those assets. +The example imports the installed package, prints the generated Python and +schema/native-source directories, and checks image and custom-message CDR roundtrips. +Expected output includes: + +```text +Generated Python messages: /lib/python3.12/site-packages/dimos/messages +Packaged schemas and native sources: /lib/python3.12/site-packages/dimos/messages_schemas/package +Messages: 73 types +PASS: sensor_msgs/msg/Image pixels, encoding and source stamp +PASS: dimos_msgs/msg/LineSegments3D geometry, weight and nested type identity +PASS: packaged definitions and C++/Rust build inputs; no compiled message libraries +``` + +The generated directories are installation outputs, not files to edit. Message +inputs live under `packages/dimos-generated` in the checkout. + +### Run C++ and Rust + +Install CMake and Rust/Cargo, then reuse the same environment: + +```sh skip +uv pip install --python .venv-msg-review/bin/python 'dimos-cdrgen[native]==0.1.1' +.venv-msg-review/bin/python -I examples/message-package/demo_native.py +``` + +This one command compiles the ordinary CMake and Cargo consumers in +`examples/message-package/native`. Python writes an installed built-in Header, +C++ appends `/cpp` to its frame ID, Rust appends `/rust`, and Python verifies +`map/cpp/rust` with the timestamp unchanged. Expected final output: + +```text +PASS: built-in Header Python -> C++ -> Rust -> Python ``` -There is no dimOS compatibility distribution. Existing consumers of -`dimos_message_build` or generic `dimos.message_codegen` APIs must change their -imports and dependency declarations to `dimos_cdrgen` and `dimos-cdrgen`. +The script also prints the installed native-source directory. Temporary example +files are cleaned up. Native support builds use `~/.cache/dimos/messages`; +`DIMOS_MESSAGE_NATIVE_CACHE` overrides that location. Set `CARGO_TARGET_DIR` to +retain Rust build artifacts. Cold builds download pinned dependencies; +`DIMOS_OFFLINE=1` requires populated native and Cargo caches. -dimOS retains two adapters: +### What installation does -- `dimos.message_codegen.native_build.write_cmake_toolchain` adds SDK discovery - for installed `dimos/_native/cpp` and source-checkout `native/cpp` layouts. - Import other build functions directly from `dimos_cdrgen.native_build`. -- `dimos/native/cdr_codec.hpp` exposes the library codec under `dimos::native` - for dimOS C++ consumers. +Definitions → setuptools calls cdrgen → Python/schema/native-source +artifacts in the dimOS wheel → installed consumers. Source installation prepares +verified definitions and generates artifacts. Wheel installation only installs +those artifacts. The sdist includes definitions needed to rebuild the wheel. -This first stack layer does not switch runtime transports or supply built-in -message catalogs. Those remain separate PRs. +Installation does not compile or test the C++/Rust message consumers. Running +`demo_native.py` performs that verification explicitly. These examples use files, +with no robot connection, transport or SDK pub/sub. No dedicated native-message +CI is required to install or run them. ## Verify the integration Use a Python environment with the published generator and test tools: -```sh -uv pip install 'dimos-cdrgen[native]==0.1.0' numpy build pytest pytest-asyncio pytest-env -python -m pytest --noconftest -o addopts='' \ - -o 'markers=native_e2e: compiled native consumer acceptance' \ - -q dimos/message_codegen/test_integration.py +```sh skip +uv pip install 'dimos-cdrgen==0.1.1' numpy build pytest pytest-asyncio pytest-env +python -m pytest --noconftest -o addopts='' -q dimos/message_codegen/test_integration.py ``` -The tests check SDK discovery, an installed message wheel built from a source -distribution, Python type identity and the dimOS C++ header against literal CDR -bytes. Native verification needs CMake and a C++17 compiler. It prepares pinned -native dependencies in a temporary cache; use `DIMOS_NATIVE_TEST_CACHE` to reuse -a writable cache, or `DIMOS_NATIVE_SOURCE_DIRS` with `DIMOS_OFFLINE=1` for -pre-fetched source directories. Generic generator regression tests stay in the -library repository. +These checks cover SDK path discovery and an installed Python message consumer. +Native message generation/compilation is not run by CI. To manually verify the +installed built-in messages across Python, C++ and Rust, run the +[built-in example above](#run-c-and-rust). +Generic generator regression tests belong to the cdrgen repository. diff --git a/examples/message-package/README.md b/examples/message-package/README.md new file mode 100644 index 00000000000..5228169e39a --- /dev/null +++ b/examples/message-package/README.md @@ -0,0 +1,11 @@ +# Built-in message examples + +**WIP:** CDR transport and application support are incomplete. + +- `demo_installed_wheel.py` checks installed Python messages and CDR roundtrips. +- `demo_native.py` sends a built-in Header through Python, C++, Rust, and back to Python. +- `native/` contains the CMake and Cargo source files for `demo_native.py`. + +Follow the [example tutorial](/docs/development/message-source-packages.md#run-the-built-in-examples) +for install commands, expected output, and generated-file locations. +The examples do not need an external message project or a robot. diff --git a/examples/message-package/demo_installed_wheel.py b/examples/message-package/demo_installed_wheel.py new file mode 100644 index 00000000000..0ad1208af0e --- /dev/null +++ b/examples/message-package/demo_installed_wheel.py @@ -0,0 +1,92 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Verify an installed message wheel from a clean environment outside the checkout.""" + +from importlib.metadata import distribution, entry_points +from pathlib import Path +import sys + +from dimos_cdrgen.registry import decode, encode +import numpy as np + +import dimos.messages +from dimos.messages.builtin_interfaces.msg import Time +from dimos.messages.dimos_msgs.msg import LineSegment3D, LineSegments3D +from dimos.messages.geometry_msgs.msg import Point +from dimos.messages.sensor_msgs.msg import Image +from dimos.messages.std_msgs.msg import Header +from dimos.messages_schemas.provider import message_types, package_root + + +def main() -> None: + assert sys.prefix != sys.base_prefix, "Run in the clean verification environment" + assert dimos.messages.__file__ is not None + installed = Path(dimos.messages.__file__).resolve() + assert installed.is_relative_to(Path(sys.prefix).resolve()), installed + owner = distribution("dimos") + assert "dimos/messages/std_msgs/msg/__init__.py" in {str(p) for p in owner.files} + assert len([ep for ep in entry_points(group="dimos.messages") if ep.name == "dimos"]) == 1 + print(f"Installed package: {installed}") + print(f"Generated Python messages: {installed.parent}") + print(f"Packaged schemas and native sources: {package_root()}") + print(f"Messages: {len(message_types())} types") + + image = Image( + header=Header(stamp=Time(sec=17, nanosec=23), frame_id="camera"), + height=2, + width=3, + encoding="rgb8", + is_bigendian=0, + step=9, + data=np.arange(18, dtype=np.uint8), + ) + restored_image = decode(encode(image), Image) + assert type(restored_image) is Image and type(restored_image.header) is Header + assert restored_image.header == Header(Time(17, 23), "camera") + assert (restored_image.height, restored_image.width, restored_image.step) == (2, 3, 9) + assert restored_image.encoding == "rgb8" and restored_image.is_bigendian == 0 + np.testing.assert_array_equal(restored_image.data, np.arange(18, dtype=np.uint8)) + print("PASS: sensor_msgs/msg/Image pixels, encoding and source stamp") + + segments = LineSegments3D( + header=Header(stamp=Time(sec=17, nanosec=23), frame_id="map"), + segments=[LineSegment3D(start=Point(1.0, 2.0, 3.0), end=Point(4.0, 5.0, 6.0), weight=0.75)], + ) + restored_segments = decode(encode(segments), LineSegments3D) + assert restored_segments == segments + assert type(restored_segments.header) is Header + assert type(restored_segments.segments[0].start) is Point + print("PASS: dimos_msgs/msg/LineSegments3D geometry, weight and nested type identity") + + resources = package_root() + for relative in ( + "schemas/sensor_msgs/msg/Image.msg", + "schemas/dimos_msgs/msg/LineSegments3D.msg", + "cpp/CMakeLists.txt", + "cpp/sensor_msgs/msg/Image.msg", + "rust/Cargo.toml", + "rust/build.rs", + "rust/interfaces/dimos_msgs/msg/LineSegments3D.msg", + ): + assert (resources / relative).is_file(), relative + assert not any( + path.suffix in {".so", ".a", ".dylib", ".dll", ".rlib", ".pyd"} + for path in resources.rglob("*") + ) + print("PASS: packaged definitions and C++/Rust build inputs; no compiled message libraries") + + +if __name__ == "__main__": + main() diff --git a/examples/message-package/demo_native.py b/examples/message-package/demo_native.py new file mode 100644 index 00000000000..9933af7c140 --- /dev/null +++ b/examples/message-package/demo_native.py @@ -0,0 +1,74 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Exchange an installed built-in Header through Python, C++ and Rust.""" + +import json +import os +from pathlib import Path +import shutil +import subprocess +from tempfile import TemporaryDirectory + +from dimos_cdrgen.native_build import prepare_cpp, write_cmake_toolchain +from dimos_cdrgen.registry import decode, encode + +from dimos.messages.builtin_interfaces.msg import Time +from dimos.messages.std_msgs.msg import Header +from dimos.messages_schemas.provider import package_root # type: ignore[import-untyped] + + +def main() -> None: + for tool in ("cmake", "cargo"): + if shutil.which(tool) is None: + raise SystemExit(f"Install {tool} before running this native example") + sources = package_root() + print(f"Installed native message sources: {sources}", flush=True) + offline = os.environ.get("DIMOS_OFFLINE") == "1" + cache = Path( + os.environ.get("DIMOS_MESSAGE_NATIVE_CACHE", Path.home() / ".cache/dimos/messages") + ) + prefix = prepare_cpp(sources, cache=cache, offline=offline) + with TemporaryDirectory(prefix="dimos-builtin-example-") as temporary: + project = Path(temporary) + shutil.copytree(Path(__file__).with_name("native"), project, dirs_exist_ok=True) + toolchain = write_cmake_toolchain(prefix, project / "toolchain.cmake") + (project / "python.cdr").write_bytes(encode(Header(Time(17, 23), "map"))) + commands = [ + ["cmake", "-S", ".", "-B", "build", f"-DCMAKE_TOOLCHAIN_FILE={toolchain}"], + ["cmake", "--build", "build", "--parallel", "2"], + ["build/header_relay", "python.cdr", "cpp.cdr"], + [ + "cargo", + "--config", + "patch.crates-io.dimos-generated-messages.path=" + + json.dumps(str(sources / "rust")), + "run", + *(["--offline"] if offline else []), + "--example", + "relay", + "--", + "cpp.cdr", + "rust.cdr", + ], + ] + for command in commands: + subprocess.run(command, cwd=project, check=True) + value = decode((project / "rust.cdr").read_bytes(), Header) + assert value == Header(Time(17, 23), "map/cpp/rust"), value + print("PASS: built-in Header Python -> C++ -> Rust -> Python") + + +if __name__ == "__main__": + main() diff --git a/examples/message-package/native/CMakeLists.txt b/examples/message-package/native/CMakeLists.txt new file mode 100644 index 00000000000..429ba104129 --- /dev/null +++ b/examples/message-package/native/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.20) +project(builtin_message_example LANGUAGES CXX) +find_package(dimos_generated CONFIG REQUIRED) +add_executable(header_relay relay.cpp) +target_link_libraries(header_relay PRIVATE dimos_generated::messages) diff --git a/examples/message-package/native/Cargo.toml b/examples/message-package/native/Cargo.toml new file mode 100644 index 00000000000..0fd1905670d --- /dev/null +++ b/examples/message-package/native/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "builtin-message-example" +version = "0.1.0" +edition = "2024" +license = "Apache-2.0" + +[workspace] + +[dependencies] +dimos-generated-messages = "=0.0.14" +re_cdr = "=0.1.0" + +[[example]] +name = "relay" +path = "relay.rs" diff --git a/examples/message-package/native/relay.cpp b/examples/message-package/native/relay.cpp new file mode 100644 index 00000000000..e2da087b5b9 --- /dev/null +++ b/examples/message-package/native/relay.cpp @@ -0,0 +1,20 @@ +// Copyright 2026 Dimensional Inc. +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include + +using Header = std_msgs::msg::Header; + +int main(int argc, char** argv) { + if (argc != 3) return 2; + std::ifstream input(argv[1], std::ios::binary); + const std::vector bytes{std::istreambuf_iterator(input), {}}; + auto value = dimos::cdr::cdr_decode
(bytes); + value.frame_id += "/cpp"; + const auto encoded = dimos::cdr::cdr_encode(value); + std::ofstream output(argv[2], std::ios::binary); + output.write(reinterpret_cast(encoded.data()), encoded.size()); + return output ? 0 : 1; +} diff --git a/examples/message-package/native/relay.rs b/examples/message-package/native/relay.rs new file mode 100644 index 00000000000..14dd8e8bd8e --- /dev/null +++ b/examples/message-package/native/relay.rs @@ -0,0 +1,30 @@ +// Copyright 2026 Dimensional Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use dimos_generated_messages::std_msgs::msg::header::Header; +use std::{env, fs}; + +fn main() -> Result<(), Box> { + let args: Vec<_> = env::args().collect(); + let bytes = fs::read(&args[1])?; + assert_eq!(&bytes[..4], &[0, 1, 0, 0]); + let (mut value, consumed): (Header, usize) = + re_cdr::from_bytes::<_, re_cdr::LittleEndian>(&bytes[4..])?; + assert_eq!(consumed, bytes.len() - 4); + value.frame_id.push_str("/rust"); + let mut output = vec![0, 1, 0, 0]; + output.extend(re_cdr::to_vec::<_, re_cdr::LittleEndian>(&value)?); + fs::write(&args[2], output)?; + Ok(()) +} diff --git a/packages/dimos-generated/README.md b/packages/dimos-generated/README.md new file mode 100644 index 00000000000..ad5c8fac1a8 --- /dev/null +++ b/packages/dimos-generated/README.md @@ -0,0 +1,23 @@ +# Built-in message sources + +**WIP:** These messages work in standalone examples. CDR transport and application +support are incomplete. Existing code continues to use `dimos.msgs`. + +The source files are: + +- `custom/dimos_msgs/`: message definitions owned by DimOS. +- `schema-sources.json`: upstream revisions, file hashes, and license paths. + +cdrgen discovers all `.msg` files in the prepared source directory. + +For source and editable installs, `setup.py` checks the definitions and calls +`dimos-cdrgen`. The wheel includes Python messages, schemas, native source code, +and licenses. Wheel installation does not generate code or compile native examples. +There is no separate `dimos-generated` distribution. + +See the [built-in example tutorial](/docs/development/message-source-packages.md) +for install commands, example commands, and output directories. + +After you change a definition, install the package again. +Then restart Python. Keep the upstream license files. +Encoding of bounded types remains unsupported. diff --git a/packages/dimos-generated/custom/dimos_msgs/LICENSE b/packages/dimos-generated/custom/dimos_msgs/LICENSE new file mode 100644 index 00000000000..5e2927e3adb --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/LICENSE @@ -0,0 +1,17 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + Copyright 2025 Dimensional Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/dimos-generated/custom/dimos_msgs/msg/BoundingBox2DArray.msg b/packages/dimos-generated/custom/dimos_msgs/msg/BoundingBox2DArray.msg new file mode 100644 index 00000000000..7360bdbaec4 --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/msg/BoundingBox2DArray.msg @@ -0,0 +1,5 @@ +# Copyright 2026 Dimensional Inc. +# SPDX-License-Identifier: Apache-2.0 + +std_msgs/Header header +vision_msgs/BoundingBox2D[] boxes diff --git a/packages/dimos-generated/custom/dimos_msgs/msg/BoundingBox3DArray.msg b/packages/dimos-generated/custom/dimos_msgs/msg/BoundingBox3DArray.msg new file mode 100644 index 00000000000..3bb3649298e --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/msg/BoundingBox3DArray.msg @@ -0,0 +1,5 @@ +# Copyright 2026 Dimensional Inc. +# SPDX-License-Identifier: Apache-2.0 + +std_msgs/Header header +vision_msgs/BoundingBox3D[] boxes diff --git a/packages/dimos-generated/custom/dimos_msgs/msg/EntityMarker.msg b/packages/dimos-generated/custom/dimos_msgs/msg/EntityMarker.msg new file mode 100644 index 00000000000..c4eb6dc7625 --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/msg/EntityMarker.msg @@ -0,0 +1,8 @@ +# Copyright 2026 Dimensional Inc. +# SPDX-License-Identifier: Apache-2.0 + +# A labeled world-space entity; rendering belongs to viewer adapters. +string entity_id +string label +string entity_type +geometry_msgs/Point position diff --git a/packages/dimos-generated/custom/dimos_msgs/msg/EntityMarkers.msg b/packages/dimos-generated/custom/dimos_msgs/msg/EntityMarkers.msg new file mode 100644 index 00000000000..a4ebd89ced5 --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/msg/EntityMarkers.msg @@ -0,0 +1,5 @@ +# Copyright 2026 Dimensional Inc. +# SPDX-License-Identifier: Apache-2.0 + +std_msgs/Header header +dimos_msgs/EntityMarker[] markers diff --git a/packages/dimos-generated/custom/dimos_msgs/msg/GraspCandidate.msg b/packages/dimos-generated/custom/dimos_msgs/msg/GraspCandidate.msg new file mode 100644 index 00000000000..480cb94c313 --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/msg/GraspCandidate.msg @@ -0,0 +1,6 @@ +# Copyright 2026 Dimensional Inc. +# SPDX-License-Identifier: Apache-2.0 + +# Target TCP pose and generator-local ranking score. +geometry_msgs/Pose pose +float64 score diff --git a/packages/dimos-generated/custom/dimos_msgs/msg/GraspCandidateArray.msg b/packages/dimos-generated/custom/dimos_msgs/msg/GraspCandidateArray.msg new file mode 100644 index 00000000000..5d0beb21198 --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/msg/GraspCandidateArray.msg @@ -0,0 +1,6 @@ +# Copyright 2026 Dimensional Inc. +# SPDX-License-Identifier: Apache-2.0 + +# Candidates share the input cloud's frame and timestamp. +std_msgs/Header header +GraspCandidate[] candidates diff --git a/packages/dimos-generated/custom/dimos_msgs/msg/ImuInfo.msg b/packages/dimos-generated/custom/dimos_msgs/msg/ImuInfo.msg new file mode 100644 index 00000000000..8a2ed049200 --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/msg/ImuInfo.msg @@ -0,0 +1,15 @@ +# Copyright 2026 Dimensional Inc. +# SPDX-License-Identifier: Apache-2.0 + +# Continuous-time IMU noise model. Extrinsics are supplied separately through TF. +std_msgs/Header header +# rad/s/sqrt(Hz) +float64 gyro_noise_density +# rad/s^2/sqrt(Hz) +float64 gyro_random_walk +# m/s^2/sqrt(Hz) +float64 accel_noise_density +# m/s^3/sqrt(Hz) +float64 accel_random_walk +# Delivered sample rate in Hz. +float64 frequency diff --git a/packages/dimos-generated/custom/dimos_msgs/msg/JointCommand.msg b/packages/dimos-generated/custom/dimos_msgs/msg/JointCommand.msg new file mode 100644 index 00000000000..8f5d1b68447 --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/msg/JointCommand.msg @@ -0,0 +1,6 @@ +# Copyright 2026 Dimensional Inc. +# SPDX-License-Identifier: Apache-2.0 + +# Joint position targets in radians, ordered by the controller's joint list. +std_msgs/Header header +float64[] positions diff --git a/packages/dimos-generated/custom/dimos_msgs/msg/LineSegment3D.msg b/packages/dimos-generated/custom/dimos_msgs/msg/LineSegment3D.msg new file mode 100644 index 00000000000..f026c5eef45 --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/msg/LineSegment3D.msg @@ -0,0 +1,6 @@ +# Copyright 2026 Dimensional Inc. +# SPDX-License-Identifier: Apache-2.0 + +geometry_msgs/Point start +geometry_msgs/Point end +float64 weight 1.0 diff --git a/packages/dimos-generated/custom/dimos_msgs/msg/LineSegments3D.msg b/packages/dimos-generated/custom/dimos_msgs/msg/LineSegments3D.msg new file mode 100644 index 00000000000..92947baed42 --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/msg/LineSegments3D.msg @@ -0,0 +1,6 @@ +# Copyright 2026 Dimensional Inc. +# SPDX-License-Identifier: Apache-2.0 + +# Weighted segments in a common coordinate frame. +std_msgs/Header header +LineSegment3D[] segments diff --git a/packages/dimos-generated/custom/dimos_msgs/msg/MotorCommandArray.msg b/packages/dimos-generated/custom/dimos_msgs/msg/MotorCommandArray.msg new file mode 100644 index 00000000000..e98e7ae9e8c --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/msg/MotorCommandArray.msg @@ -0,0 +1,11 @@ +# Copyright 2026 Dimensional Inc. +# SPDX-License-Identifier: Apache-2.0 + +# Per-joint hybrid commands. All arrays must have the same length. +# q: rad; dq: rad/s; tau: N*m. Gains follow the actuator controller's units. +std_msgs/Header header +float64[] q +float64[] dq +float64[] kp +float64[] kd +float64[] tau diff --git a/packages/dimos-generated/custom/dimos_msgs/msg/RobotState.msg b/packages/dimos-generated/custom/dimos_msgs/msg/RobotState.msg new file mode 100644 index 00000000000..36b399f85c8 --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/msg/RobotState.msg @@ -0,0 +1,15 @@ +# Copyright 2026 Dimensional Inc. +# SPDX-License-Identifier: Apache-2.0 + +# Controller feedback. TCP values retain the driver's x/y/z/roll/pitch/yaw convention. +std_msgs/Header header +int32 state +int32 mode +int32 error_code +int32 warn_code +int32 cmdnum +int32 mt_brake +int32 mt_able +float64[] tcp_pose +float64[] tcp_offset +float64[] joints diff --git a/packages/dimos-generated/custom/dimos_msgs/msg/TrajectoryStatus.msg b/packages/dimos-generated/custom/dimos_msgs/msg/TrajectoryStatus.msg new file mode 100644 index 00000000000..4baa51c0431 --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/msg/TrajectoryStatus.msg @@ -0,0 +1,15 @@ +# Copyright 2026 Dimensional Inc. +# SPDX-License-Identifier: Apache-2.0 + +std_msgs/Header header +uint8 IDLE=0 +uint8 EXECUTING=1 +uint8 COMPLETED=2 +uint8 ABORTED=3 +uint8 FAULT=4 +uint8 state +# Fraction completed, in [0, 1]. +float64 progress +builtin_interfaces/Duration time_elapsed +builtin_interfaces/Duration time_remaining +string error diff --git a/packages/dimos-generated/custom/dimos_msgs/msg/VideoStats.msg b/packages/dimos-generated/custom/dimos_msgs/msg/VideoStats.msg new file mode 100644 index 00000000000..3472623a7a7 --- /dev/null +++ b/packages/dimos-generated/custom/dimos_msgs/msg/VideoStats.msg @@ -0,0 +1,11 @@ +std_msgs/Header header +float64 fps +float64 kbps +uint32 width +uint32 height +float64 loss_pct +float64 jitter_buffer_ms +float64 decode_ms +uint64 frames_dropped +uint64 freezes +float64 e2e_latency_ms diff --git a/packages/dimos-generated/schema-sources.json b/packages/dimos-generated/schema-sources.json new file mode 100644 index 00000000000..65e2d266b09 --- /dev/null +++ b/packages/dimos-generated/schema-sources.json @@ -0,0 +1,343 @@ +{ + "format": 1, + "sources": [ + { + "repository": "ros2/common_interfaces", + "revision": "a941f14bb318d8d904505ed935ccbb97f24a70a4", + "url": "https://codeload.github.com/ros2/common_interfaces/tar.gz/a941f14bb318d8d904505ed935ccbb97f24a70a4", + "sha256": "4140cc780aec296bcd88c3049ba541783d40fbb7dd0e5919e621a85e314416ba", + "prefix": "common_interfaces-a941f14bb318d8d904505ed935ccbb97f24a70a4/", + "files": { + "licenses/ros2_common_interfaces_geometry_msgs_LICENSE": { + "path": "geometry_msgs/LICENSE", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" + }, + "geometry_msgs/msg/Point.msg": { + "path": "geometry_msgs/msg/Point.msg", + "sha256": "eb4c07e591cf0269ccb17245dcd4f998162feb09cc8add85fa082c8a30bfc91d" + }, + "geometry_msgs/msg/Point32.msg": { + "path": "geometry_msgs/msg/Point32.msg", + "sha256": "acade2dacb4f1f7988f34e8221079eb1c757f51e8ef48adb5651f7dc478fed59" + }, + "geometry_msgs/msg/PointStamped.msg": { + "path": "geometry_msgs/msg/PointStamped.msg", + "sha256": "c58b03c27604f45e9b0fd2f1b327dcbada7d6a389beb8c060d448abc25e76bfc" + }, + "geometry_msgs/msg/Polygon.msg": { + "path": "geometry_msgs/msg/Polygon.msg", + "sha256": "43679118501e3b294349c9ad73af9f03b476ef77c7096cd79b4ce46646823056" + }, + "geometry_msgs/msg/Pose.msg": { + "path": "geometry_msgs/msg/Pose.msg", + "sha256": "3eceb77c63ebbb544c11d7f991e475e9819e0cf534deb1a879392c1b3dc997de" + }, + "geometry_msgs/msg/PoseArray.msg": { + "path": "geometry_msgs/msg/PoseArray.msg", + "sha256": "3f58a91730c78dbc5ec5092131945ccdfeeb775aa0f02df7ff09f6c979b1d398" + }, + "geometry_msgs/msg/PoseStamped.msg": { + "path": "geometry_msgs/msg/PoseStamped.msg", + "sha256": "31898ab0eb64a6c44c27ab4959819a32ec3bc3ee847c8fe1b01c897d48e311e5" + }, + "geometry_msgs/msg/PoseWithCovariance.msg": { + "path": "geometry_msgs/msg/PoseWithCovariance.msg", + "sha256": "a22f234e91b9fcd4ba0c5b533496b43c62d7f6ade39082212ac9835679d29f94" + }, + "geometry_msgs/msg/Quaternion.msg": { + "path": "geometry_msgs/msg/Quaternion.msg", + "sha256": "298495eb67a139642ffd37cc9029eae085e6c39a8789828b7517868a487939ed" + }, + "geometry_msgs/msg/Transform.msg": { + "path": "geometry_msgs/msg/Transform.msg", + "sha256": "c8916279f0a429dd9561158bd2133bc4e2d780277c3c594116b240a27e7a2993" + }, + "geometry_msgs/msg/TransformStamped.msg": { + "path": "geometry_msgs/msg/TransformStamped.msg", + "sha256": "f6e137c205e9187ac1dafdfbace7435de58bb3779a55e9c6b52c2cb513785c42" + }, + "geometry_msgs/msg/Twist.msg": { + "path": "geometry_msgs/msg/Twist.msg", + "sha256": "ed471ef861f65af991b1f64d66ef941dd41dd4c0702f6222512b1a16c62d58b0" + }, + "geometry_msgs/msg/TwistStamped.msg": { + "path": "geometry_msgs/msg/TwistStamped.msg", + "sha256": "ffc9d5925412410b28d601c55cf386a0b3e4d5dba00920fbafb98d7201f582ca" + }, + "geometry_msgs/msg/TwistWithCovariance.msg": { + "path": "geometry_msgs/msg/TwistWithCovariance.msg", + "sha256": "5690a1f109d4962c73b5ca7f3673c48052d02f37b6a38fe2fb37c899cf555c45" + }, + "geometry_msgs/msg/Vector3.msg": { + "path": "geometry_msgs/msg/Vector3.msg", + "sha256": "d8b9ea9aad1f424faf4d2b94dae1e1368af1e5d59de11aeed019399cff7f9e1d" + }, + "geometry_msgs/msg/Wrench.msg": { + "path": "geometry_msgs/msg/Wrench.msg", + "sha256": "d9667abd3d2a67d5624113fa07a647b0d4806bf89d21c8a5cf914905a036bf25" + }, + "licenses/ros2_common_interfaces_nav_msgs_LICENSE": { + "path": "nav_msgs/LICENSE", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" + }, + "nav_msgs/msg/MapMetaData.msg": { + "path": "nav_msgs/msg/MapMetaData.msg", + "sha256": "6868a7d604c6cc033dd0adfac8a0adb0a6dedae1b7b140dbb47c83f3bcc6f140" + }, + "nav_msgs/msg/OccupancyGrid.msg": { + "path": "nav_msgs/msg/OccupancyGrid.msg", + "sha256": "3e54ec7e662bdd6d5528efab99c6e34ce3eed27ce628721fcdb4e5e1bbf8188b" + }, + "nav_msgs/msg/Odometry.msg": { + "path": "nav_msgs/msg/Odometry.msg", + "sha256": "bb00547ddf498cdbae46fd991d9d85a59f507e12e7cf01fb18a86f1111524f4f" + }, + "nav_msgs/msg/Path.msg": { + "path": "nav_msgs/msg/Path.msg", + "sha256": "fc1e2eff6c2bd63fb92b6cf29bad29873b026d7abe00d3a68965b40668bfdd5b" + }, + "licenses/ros2_common_interfaces_sensor_msgs_LICENSE": { + "path": "sensor_msgs/LICENSE", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" + }, + "sensor_msgs/msg/BatteryState.msg": { + "path": "sensor_msgs/msg/BatteryState.msg", + "sha256": "ce13430f3c107274fb7123a6d1e79a9f83974d78cb0be1721695129e49240b25" + }, + "sensor_msgs/msg/CameraInfo.msg": { + "path": "sensor_msgs/msg/CameraInfo.msg", + "sha256": "1fced83678f5f2708aff8540987594692452de9f0b43905ce3bfba689226f3aa" + }, + "sensor_msgs/msg/CompressedImage.msg": { + "path": "sensor_msgs/msg/CompressedImage.msg", + "sha256": "75ca1003981c407148ff94a3470760985dd69233b8287a5d52848d45a312f144" + }, + "sensor_msgs/msg/Image.msg": { + "path": "sensor_msgs/msg/Image.msg", + "sha256": "575cd3434771916c2b551db8ac484522ecef1d6105196e043a305887d2730704" + }, + "sensor_msgs/msg/Imu.msg": { + "path": "sensor_msgs/msg/Imu.msg", + "sha256": "5800b7c2e6d3afed966ae229b1559f6e762c792ad34a88e55ad4288d8bbbd34b" + }, + "sensor_msgs/msg/JointState.msg": { + "path": "sensor_msgs/msg/JointState.msg", + "sha256": "711d1771a002c7190e5bbe746a163a67477c1e2a45482a64f1fe339815ca6f5d" + }, + "sensor_msgs/msg/Joy.msg": { + "path": "sensor_msgs/msg/Joy.msg", + "sha256": "5a7d133e7dc82f531e03bd4ff92c9240a828bb6ceb6f19d5cd767d5372adb757" + }, + "sensor_msgs/msg/PointCloud2.msg": { + "path": "sensor_msgs/msg/PointCloud2.msg", + "sha256": "22699bfd6c68be148363a2e889408833a5a37d3464ea20032d1c757336b80f2a" + }, + "sensor_msgs/msg/PointField.msg": { + "path": "sensor_msgs/msg/PointField.msg", + "sha256": "6e6d5d51d2e464d3074b44b112f219e67fcdc0b280260d93f8cefd9710ec972e" + }, + "sensor_msgs/msg/RegionOfInterest.msg": { + "path": "sensor_msgs/msg/RegionOfInterest.msg", + "sha256": "9c07010ab5ffddd1202876a778b44c99cafa780bc0503041c3fd935870a14c6a" + }, + "licenses/ros2_common_interfaces_shape_msgs_LICENSE": { + "path": "shape_msgs/LICENSE", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" + }, + "shape_msgs/msg/SolidPrimitive.msg": { + "path": "shape_msgs/msg/SolidPrimitive.msg", + "sha256": "b3ec61569b2b4e2c37428e0625d4a360c00c95fc9ad4c94f9c8399225b98b2d8" + }, + "licenses/ros2_common_interfaces_std_msgs_LICENSE": { + "path": "std_msgs/LICENSE", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" + }, + "std_msgs/msg/Bool.msg": { + "path": "std_msgs/msg/Bool.msg", + "sha256": "d989b95f2ba14d15eff5b274b5d1f379b938aaea2157d8bd707f942457ae18bf" + }, + "std_msgs/msg/Empty.msg": { + "path": "std_msgs/msg/Empty.msg", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "std_msgs/msg/Float32.msg": { + "path": "std_msgs/msg/Float32.msg", + "sha256": "fb905efdb5c7902255cad5a6e4953158384238d4652d9e7ed68d134da052f3af" + }, + "std_msgs/msg/Header.msg": { + "path": "std_msgs/msg/Header.msg", + "sha256": "d2cea14630e39ed0fa5b432ba9160a7c38855b9427d3344f99e83e83facb53c2" + }, + "std_msgs/msg/Int32.msg": { + "path": "std_msgs/msg/Int32.msg", + "sha256": "4614a6d0154436fb9ff12b0b7637816f42a550e4f5e0c4705bedf11293b4f7a7" + }, + "std_msgs/msg/Int64.msg": { + "path": "std_msgs/msg/Int64.msg", + "sha256": "117752e7d1f0e4709749c04a9251dcece8545c0dac7bbb6fb8f31262d6ace238" + }, + "std_msgs/msg/Int8.msg": { + "path": "std_msgs/msg/Int8.msg", + "sha256": "2ad86a195e6c5d3415b5b48f4c248c45bfe05d636735f88d821c44ab660c7f6f" + }, + "std_msgs/msg/MultiArrayDimension.msg": { + "path": "std_msgs/msg/MultiArrayDimension.msg", + "sha256": "94927d0ee40f1a1d9bf7bdddbca803e8b6e6d315d70c7295a9b0d2d6c380abed" + }, + "std_msgs/msg/MultiArrayLayout.msg": { + "path": "std_msgs/msg/MultiArrayLayout.msg", + "sha256": "8f059b66610c5fe0532e3f12ff1022a10671caebf773f00816fd4c9303626914" + }, + "std_msgs/msg/String.msg": { + "path": "std_msgs/msg/String.msg", + "sha256": "2022b3f1c32b578ff8d54a9948a51a10778761e0ad4ba60e50ae51292ca16884" + }, + "std_msgs/msg/UInt32.msg": { + "path": "std_msgs/msg/UInt32.msg", + "sha256": "4db7deaede98bebcc6ddfa951cf4d55aaad57909f38a8c346a12ae25df2e58ae" + }, + "std_msgs/msg/UInt32MultiArray.msg": { + "path": "std_msgs/msg/UInt32MultiArray.msg", + "sha256": "3a25d8463602c0f3eff61a14c3c17c10ef819aadf7ef19179e95cfdaea07b3ce" + }, + "licenses/ros2_common_interfaces_trajectory_msgs_LICENSE": { + "path": "trajectory_msgs/LICENSE", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" + }, + "trajectory_msgs/msg/JointTrajectory.msg": { + "path": "trajectory_msgs/msg/JointTrajectory.msg", + "sha256": "d63cda4a5ec81824ed4acd737e6b5903449fb1a7e2f5cdcdcc4c49285f01117c" + }, + "trajectory_msgs/msg/JointTrajectoryPoint.msg": { + "path": "trajectory_msgs/msg/JointTrajectoryPoint.msg", + "sha256": "2358055d26d27c0c302e921f90d5aa59cb5feb3e22937f8ac98314b0c52ba8f4" + }, + "licenses/ros2_common_interfaces_visualization_msgs_LICENSE": { + "path": "visualization_msgs/LICENSE", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" + } + } + }, + { + "repository": "ros2/rcl_interfaces", + "revision": "7aa3caf43377ea6ad615bc1040832e2c7566bfbe", + "url": "https://codeload.github.com/ros2/rcl_interfaces/tar.gz/7aa3caf43377ea6ad615bc1040832e2c7566bfbe", + "sha256": "d984942035cd4df4f60c92d6038cecbfc3f5a01a1977c1b3199cee45cd67281c", + "prefix": "rcl_interfaces-7aa3caf43377ea6ad615bc1040832e2c7566bfbe/", + "files": { + "builtin_interfaces/msg/Duration.msg": { + "path": "builtin_interfaces/msg/Duration.msg", + "sha256": "57da13a4201e7d258a2931323145b953e47e46967494e62ba66c2faca4fbade2" + }, + "builtin_interfaces/msg/Time.msg": { + "path": "builtin_interfaces/msg/Time.msg", + "sha256": "dc5a01ddde3aecffee21eb24974fe77cc969f873218a2b1811aa0ac8a456b8bd" + }, + "licenses/ros2_rcl_interfaces_licenses_ros2_rcl_interfaces_LICENSE": { + "path": "LICENSE", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" + } + } + }, + { + "repository": "ros2/geometry2", + "revision": "f702874b1c8535d6a038230ab2cda0ba5d521ebd", + "url": "https://codeload.github.com/ros2/geometry2/tar.gz/f702874b1c8535d6a038230ab2cda0ba5d521ebd", + "sha256": "d24c3fb17cc0087b2fc7f0afac28ad74eee20c988aca4247c201f76a7031f621", + "prefix": "geometry2-f702874b1c8535d6a038230ab2cda0ba5d521ebd/", + "files": { + "licenses/ros2_geometry2_licenses_ros2_geometry2_LICENSE": { + "path": "LICENSE", + "sha256": "1b1e5ebcad699924203feced7205a4bbdf4f153d34065a19a8a4e107cf1e154b" + }, + "tf2_msgs/msg/TFMessage.msg": { + "path": "tf2_msgs/msg/TFMessage.msg", + "sha256": "f3fca610a59b31ee4c7b740522ecdd0fd8c76002170088111409e4df513f1dfc" + } + } + }, + { + "repository": "ros-perception/vision_msgs", + "revision": "adbf56af9f77e3c8ab2552e25dc99e4a8a77fb27", + "url": "https://codeload.github.com/ros-perception/vision_msgs/tar.gz/adbf56af9f77e3c8ab2552e25dc99e4a8a77fb27", + "sha256": "a2bd8a8a5b09eb2ed1c94f3bd163aafabced23df107a414414dfcb1f57358971", + "prefix": "vision_msgs-adbf56af9f77e3c8ab2552e25dc99e4a8a77fb27/", + "files": { + "licenses/ros-perception_vision_msgs_licenses_ros-perception_vision_msgs_LICENSE": { + "path": "LICENSE", + "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" + }, + "vision_msgs/msg/BoundingBox2D.msg": { + "path": "vision_msgs/msg/BoundingBox2D.msg", + "sha256": "74ed6347e3c8f74b884a81c5a36925c0e320836c1a5ea55df2b8d03574cc25b1" + }, + "vision_msgs/msg/BoundingBox3D.msg": { + "path": "vision_msgs/msg/BoundingBox3D.msg", + "sha256": "30502a6e247a84d87b4c2c0262fd07ce6ba3fa8d6d24e6e39c1522d7e469c450" + }, + "vision_msgs/msg/Detection2D.msg": { + "path": "vision_msgs/msg/Detection2D.msg", + "sha256": "868ffec733235c01bb5bfba3f3c528f193ea0fb3069650aaf95383a7fa9a8200" + }, + "vision_msgs/msg/Detection2DArray.msg": { + "path": "vision_msgs/msg/Detection2DArray.msg", + "sha256": "bb2eeef88b466103b89a7ae9f927164d2064e9d94e8455bb98ad2b69130331ae" + }, + "vision_msgs/msg/Detection3D.msg": { + "path": "vision_msgs/msg/Detection3D.msg", + "sha256": "68885eeededfc57231f171f2b3eb97f0d0a18580ca08307bc60b9b87216df985" + }, + "vision_msgs/msg/Detection3DArray.msg": { + "path": "vision_msgs/msg/Detection3DArray.msg", + "sha256": "077f34a634e6de6654265af5b26508eea1105bef72395cffae50fde61b713c41" + }, + "vision_msgs/msg/ObjectHypothesis.msg": { + "path": "vision_msgs/msg/ObjectHypothesis.msg", + "sha256": "6224cb18c032fe062d18859089444f4e1f311ab478e45d937fdfac18a980695b" + }, + "vision_msgs/msg/ObjectHypothesisWithPose.msg": { + "path": "vision_msgs/msg/ObjectHypothesisWithPose.msg", + "sha256": "162639c82b24a8fc4f3e93a4dc91704928d5cff9d39d169515bb748eccf4d573" + }, + "vision_msgs/msg/Point2D.msg": { + "path": "vision_msgs/msg/Point2D.msg", + "sha256": "6b99220dccc3193a603fb40d6a2880b4de67fb2175121c5683d0bde9f71e3733" + }, + "vision_msgs/msg/Pose2D.msg": { + "path": "vision_msgs/msg/Pose2D.msg", + "sha256": "0cdb3bc9872a5c3379a5122c14d03ec13d59b92752d3b444cc41592492258ab2" + } + } + }, + { + "repository": "ros2/rosidl", + "revision": "85fa592b698b0f665e3120f48fac0d35e2f7d8a4", + "url": "https://codeload.github.com/ros2/rosidl/tar.gz/85fa592b698b0f665e3120f48fac0d35e2f7d8a4", + "sha256": "ba5369bca27297f606bf3396a9e066140d07ce754d8659159148102b089e9510", + "prefix": "rosidl-85fa592b698b0f665e3120f48fac0d35e2f7d8a4/", + "files": { + "licenses/ros2_rosidl_licenses_ros2_rosidl_LICENSE": { + "path": "LICENSE", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" + } + } + }, + { + "repository": "foxglove/schemas", + "revision": "e86a94e9d4d259cdde71872f18336a7a055bf9ba", + "url": "https://codeload.github.com/foxglove/schemas/tar.gz/e86a94e9d4d259cdde71872f18336a7a055bf9ba", + "sha256": "dcfa8049d34214bd26d7a13f6a7da214e08228a65a0135bac52077c1ca12cf47", + "prefix": "foxglove-sdk-e86a94e9d4d259cdde71872f18336a7a055bf9ba/", + "files": { + "foxglove_msgs/msg/CompressedVideo.msg": { + "path": "schemas/ros2/CompressedVideo.msg", + "sha256": "83d044efaefc90291cdd56cd36b2ef6b190748c1df860d1eca7df77328174324" + }, + "licenses/foxglove_schemas_licenses_foxglove_schemas_LICENSE": { + "path": "LICENSE", + "sha256": "da11235665c17d4c1634072dae92b8ba1b38d6fdde2ccf19a6bbede33253f58d" + } + } + } + ] +} diff --git a/pyproject.toml b/pyproject.toml index dd1fda7677f..eaf0aadb912 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,6 +2,8 @@ requires = [ "setuptools>=70", "wheel", + "dimos-cdrgen[prepare]==0.1.1", + "pydantic>=2,<3", "pybind11>=2.12", "tomli>=2.0; python_version < '3.11'", ] @@ -19,9 +21,13 @@ build-backend = "setuptools.build_meta" # the sdist; keep its recursive-include extension list in sync with the globs # here. include-package-data = false +license-files = [ + "LICENSE*", "COPYING*", "NOTICE*", "AUTHORS*", + "packages/dimos-generated/interfaces/licenses/*", +] [tool.setuptools.packages.find] -where = ["."] +where = [".", "build/message-source/python"] include = ["dimos*"] # Web frontends / vendored node_modules: not part of the Python runtime # (MANIFEST.in already prunes these from the sdist; this keeps their stray @@ -35,6 +41,8 @@ exclude = [ ] [tool.setuptools.package-data] +"dimos.messages" = ["py.typed", "*.pyi", "**/*.pyi"] +"dimos.messages_schemas" = ["schemas.json", "schemas/**/*", "package/**/*"] # Recursive: matches the extension anywhere under dimos/. Add a line when code # starts loading a new data type. NOTE: ".txt" is intentionally scoped (a bare # "**/*.txt" would grab CMakeLists.txt from native build dirs). @@ -100,6 +108,7 @@ classifiers = [ ] dependencies = [ + "dimos-cdrgen==0.1.1", "matplotlib>=3.7.1", # Rerun initializes point-cloud colormaps for all streams. # Transport Protocols "dimos-lcm>=0.1.6", @@ -426,7 +435,7 @@ project-deps = [ ] tests = [ - "dimos-cdrgen==0.1.0", + "dimos-cdrgen==0.1.1", "build>=1.2.2", # Test runner "pytest==8.3.5", @@ -485,7 +494,7 @@ browser-tests = [ # Lint / format / type-check tooling for the dedicated `lint` CI job. lint = [ - "dimos-cdrgen==0.1.0", + "dimos-cdrgen==0.1.1", # Lint / format / type-check "ruff==0.14.3", "mypy==1.19.0", @@ -874,3 +883,12 @@ ignore = [ # Generated golden vectors (web/shared/fixtures/gen.ts); grows per panel kind. "web/shared/fixtures/manifests.json", ] + +[tool.dimos.messages] +source = "packages/dimos-generated/interfaces" +module = "dimos_generated" +python-module = "dimos.messages" +resources = ["packages/dimos-generated/schema-sources.json"] + +[project.entry-points."dimos.messages"] +dimos = "dimos.messages_schemas.provider" diff --git a/setup.py b/setup.py index 44a5ed83bcc..4fb6d2a8611 100644 --- a/setup.py +++ b/setup.py @@ -15,13 +15,19 @@ import fnmatch import os from pathlib import Path +import shutil import struct import sys +from dimos_cdrgen.source_package import SourceDistribution, setup_arguments from pybind11.setup_helpers import Pybind11Extension, build_ext from setuptools import find_packages, setup from setuptools.command.build_py import build_py as _build_py +# PEP 517 executes setup.py without putting the checkout on sys.path. +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from dimos.message_codegen.build import prepare_builtin_sources + def python_is_macos_universal_binary(executable: str | None = None) -> bool: """ @@ -91,6 +97,11 @@ def find_package_modules(self, package, package_dir): ] def run(self): + # Reconcile cached build trees after a namespace move or message deletion. + for name in ("dimos_generated", "dimos_generated_schemas", *message_roots): + directory = Path(self.build_lib) / name.replace(".", "/") + if directory.is_dir(): + shutil.rmtree(directory) super().run() if not getattr(self, "editable_mode", False): self._copy_relay_dist() @@ -162,9 +173,25 @@ def _copy_relay_dist(self): ), ] +# The shared generator reads the root project's version and message configuration. +# Source builds verify/prepare pinned inputs; sdists carry them for offline reuse. +prepare_builtin_sources(offline=os.environ.get("DIMOS_OFFLINE") == "1") +message_settings = setup_arguments(Path(__file__).resolve().parent) +message_source = message_settings["package_dir"][""] +message_roots = tuple(message_settings["package_data"]) + setup( - packages=find_packages(), - package_dir={"": "."}, + packages=find_packages() + message_settings["packages"], + package_dir={ + "": ".", + "dimos": "dimos", + **{name: f"{message_source}/{name.replace('.', '/')}" for name in message_roots}, + }, ext_modules=ext_modules, - cmdclass={"build_ext": build_ext, "build_py": build_py}, + cmdclass={ + **message_settings["cmdclass"], + "sdist": SourceDistribution, + "build_ext": build_ext, + "build_py": build_py, + }, ) diff --git a/stubs/a1z/__init__.pyi b/stubs/a1z/__init__.pyi index 732fa035073..0c6b15e69ad 100644 --- a/stubs/a1z/__init__.pyi +++ b/stubs/a1z/__init__.pyi @@ -1 +1 @@ -__file__: str +"""a1z package marker; module attributes are provided by the type checker.""" diff --git a/uv.lock b/uv.lock index 199d9a88263..7d130f46b37 100644 --- a/uv.lock +++ b/uv.lock @@ -1839,6 +1839,7 @@ name = "dimos" version = "0.0.14" source = { editable = "." } dependencies = [ + { name = "dimos-cdrgen" }, { name = "annotation-protocol" }, { name = "attrs" }, { name = "bleak" }, @@ -2517,6 +2518,7 @@ tests-self-hosted = [ [package.metadata] requires-dist = [ + { name = "dimos-cdrgen", specifier = "==0.1.1" }, { name = "aiohttp", marker = "extra == 'webrtc'", specifier = ">=3.9.0" }, { name = "aioquic", marker = "extra == 'web'", specifier = ">=1.2.0" }, { name = "aiortc", marker = "extra == 'webrtc'", specifier = ">=1.14.0" }, @@ -2687,7 +2689,7 @@ lint = [ { name = "chromadb", specifier = ">=1.0.0" }, { name = "deptry", specifier = "==0.25.1" }, { name = "dimos", extras = ["web", "visualization", "webrtc"] }, - { name = "dimos-cdrgen", specifier = "==0.1.0" }, + { name = "dimos-cdrgen", specifier = "==0.1.1" }, { name = "einops", specifier = ">=0.8.1" }, { name = "gdown", specifier = "==6.0.0" }, { name = "googlemaps", specifier = ">=4.10.0" }, @@ -2763,7 +2765,7 @@ tests = [ { name = "coverage", specifier = ">=7.0" }, { name = "dimos", extras = ["apriltag", "mapping", "drone", "cpu", "learning"] }, { name = "dimos", extras = ["web", "visualization", "webrtc"] }, - { name = "dimos-cdrgen", specifier = "==0.1.0" }, + { name = "dimos-cdrgen", specifier = "==0.1.1" }, { name = "einops", specifier = ">=0.8.1" }, { name = "gdown", specifier = "==6.0.0" }, { name = "googlemaps", specifier = ">=4.10.0" }, @@ -2824,7 +2826,7 @@ tests-self-hosted = [ { name = "dimos", extras = ["apriltag", "mapping", "drone", "cpu", "learning"] }, { name = "dimos", extras = ["manipulation", "misc", "unitree"] }, { name = "dimos", extras = ["web", "visualization", "webrtc"] }, - { name = "dimos-cdrgen", specifier = "==0.1.0" }, + { name = "dimos-cdrgen", specifier = "==0.1.1" }, { name = "einops", specifier = ">=0.8.1" }, { name = "gdown", specifier = "==6.0.0" }, { name = "googlemaps", specifier = ">=4.10.0" }, @@ -2879,7 +2881,7 @@ tests-self-hosted = [ [[package]] name = "dimos-cdrgen" -version = "0.1.0" +version = "0.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "empy" }, @@ -2889,9 +2891,9 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/1a/31327fa4bb2a240783d0b41e315be2a4485eabf07ab032f49c6328f5f51b/dimos_cdrgen-0.1.0.tar.gz", hash = "sha256:69ea4955e64a769d56d4bd5e0fff44c98f96c29c5608125cd011cef2740624f0", size = 75010, upload-time = "2026-10-15T05:00:09.663Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/74/fff6dcefc9591dd179b50931eda73f24f1b9673c1971099506e115bf33cc/dimos_cdrgen-0.1.1.tar.gz", hash = "sha256:a529745a35954f0725ad9479a0346917756447b194fa1509c3336d3eb4e86385", size = 76682, upload-time = "2026-10-19T22:29:59.807Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/9c/086701c3249ed2caabe83cea008c3e08bd6063c48a1df852fbca6aadff50/dimos_cdrgen-0.1.0-py3-none-any.whl", hash = "sha256:6731e8900f7e61af419b8c77599587f32677c9d64f27ac15f5e78be4b76bc011", size = 70183, upload-time = "2026-10-15T05:00:08.098Z" }, + { url = "https://files.pythonhosted.org/packages/1a/05/cb085e18549a5eced615840b2f607a9c342a8a49c69012964f0568aa309b/dimos_cdrgen-0.1.1-py3-none-any.whl", hash = "sha256:f85923440196a3028dbd974a913b0ef9320eb12b57770f722e96bc1645a351a2", size = 71015, upload-time = "2026-10-19T22:29:58.404Z" }, ] [[package]]