Skip to content
Draft
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
33 changes: 33 additions & 0 deletions .github/scripts/message_wheel_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
# 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 the standalone built-in wheel without installing the robot runtime."""

import json
from pathlib import Path

import dimos_generated as messages
from dimos_generated_schemas.provider import package_root

assert messages.__dimos_version__ == "0.1.0"
header = messages.std_msgs.msg.Header()
assert type(header).decode(header.encode()).encode() == header.encode()
root = Path(package_root())
metadata = json.loads((root / "message-package.json").read_text())
assert metadata["shared"]
assert "std_msgs/msg/Header" in metadata["owned"]
assert (root / "schemas/std_msgs/msg/Header.msg").is_file()
assert (root / "lib/cmake/dimos_generated/dimos_generatedConfig.cmake").is_file()
assert (root / "rust/Cargo.toml").is_file()
47 changes: 47 additions & 0 deletions dimos/cli/commands/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 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.

"""Build external message packages without rebuilding the DimOS runtime."""

from importlib import import_module
from pathlib import Path
from subprocess import CalledProcessError

import typer


def build(
project: Path = typer.Option(Path("."), "--project", help="Message project directory."),
language: list[str] = typer.Option(
[], "--language", help="Build only this language; repeatable."
),
install: bool = typer.Option(
False, "--install", help="Also install the wheel into the active virtualenv."
),
offline: bool = typer.Option(
False, "--offline", help="Require cached native/Cargo dependencies; do not download."
),
) -> None:
"""Discover, generate and build this project's custom message packages."""
try:
core = import_module("dimos_message_project.build")
config = core.Project.load(project)
artifacts = core.build_project(
config, tuple(language) or None, install=install, offline=offline
)
for kind, path in artifacts.items():
typer.echo(f"{kind}: {path}")
except (ValueError, RuntimeError, FileNotFoundError, CalledProcessError) as error:
typer.echo(f"build: {error}", err=True)
raise typer.Exit(1) from error
5 changes: 3 additions & 2 deletions dimos/cli/dimos.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@
from dimos.cli.cloud import login as cloud_login, logout as cloud_logout, whoami as cloud_whoami
from dimos.cli.commands.apriltag import apriltag
from dimos.cli.commands.bake import bake
from dimos.cli.commands.build import build
from dimos.cli.commands.cameracalibrate import cameracalibrate
from dimos.cli.commands.data import data_app
from dimos.cli.commands.dataprep import dataprep_app
from dimos.cli.commands.docs import docs
from dimos.cli.commands.global_options import create_dynamic_callback
from dimos.cli.commands.graph import graph
from dimos.cli.commands.host import host_app
from dimos.cli.commands.info import list_blueprints, show_config
from dimos.cli.commands.lifecycle import log_cmd, restart, run, status, stop
from dimos.cli.commands.map import map_app
Expand Down Expand Up @@ -109,6 +109,7 @@ def cli_main() -> None:
main.add_typer(hardware_app, name="hardware")
main.add_typer(data_app, name="data")
main.add_typer(go2tool_app, name="go2tool")
main.command()(build)
main.command()(shell)
main.add_typer(cache_app, name="cache")
main.command("login")(cloud_login)
Expand All @@ -119,7 +120,6 @@ def cli_main() -> None:
main.command()(stop)
main.command("log")(log_cmd)
main.add_typer(mcp_app, name="mcp")
main.add_typer(host_app, name="host")
main.command("agent-send")(agent_send_cmd)
main.command()(restart)
main.command()(show_config)
Expand All @@ -141,6 +141,7 @@ def cli_main() -> None:
main.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})(top)
main.add_typer(topic_app, name="topic")
main.add_typer(map_app, name="map")

main.add_typer(dataprep_app, name="dataprep")

from dimos.memory.cli.app import mem_app
Expand Down
6 changes: 3 additions & 3 deletions dimos/hardware/sensors/lidar/fastlio2/cpp/flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

91 changes: 91 additions & 0 deletions dimos/message_codegen/test_builtin_artifacts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# 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.

import json
import os
from pathlib import Path
import subprocess
import tarfile

from dimos_cdrgen.definitions import Definitions
from dimos_cdrgen.native_build import prepare_cpp
from dimos_message_project.project import Project
import pytest

from dimos.message_codegen.native_build import write_cmake_toolchain


def test_builtin_native_artifact_version_and_relocated_consumer(tmp_path):
location = os.environ.get("DIMOS_BUILTIN_PACKAGE")
if not location:
pytest.skip("Set DIMOS_BUILTIN_PACKAGE for native source artifact acceptance")
output = Path(location).resolve()
root = Path(__file__).resolve().parents[2]
version = Project.load(root / "packages/dimos-generated").version
metadata = json.loads((output / "message-package.json").read_text())
assert metadata["version"] == version
assert metadata["shared"]
assert set(metadata["owned"]) == {
message.name for message in Definitions([], installed=True).resolve()
}
assert (output / "dist" / f"dimos-generated-messages-{version}.crate").is_file()
relocated = tmp_path / "relocated"
relocated.mkdir()
with tarfile.open(output / "dist" / f"dimos-messages-sources-{version}.tar.gz") as archive:
archive.extractall(relocated, filter="data")
assert json.loads((relocated / "message-package.json").read_text()) == metadata
sources = {
name: Path(path)
for name, path in json.loads(os.environ.get("DIMOS_NATIVE_SOURCE_DIRS", "{}")).items()
}
cache = os.environ.get("DIMOS_NATIVE_TEST_CACHE")
prefix = prepare_cpp(
relocated,
cache=Path(cache) if cache else None,
offline=os.environ.get("DIMOS_OFFLINE") == "1",
source_dirs=sources or None,
)
toolchain = write_cmake_toolchain(prefix, tmp_path / "toolchain.cmake")
source = tmp_path / "app"
source.mkdir()
(source / "CMakeLists.txt").write_text(f"""cmake_minimum_required(VERSION 3.20)
project(external_builtin LANGUAGES CXX)
find_package(dimos_generated {version} EXACT CONFIG REQUIRED)
add_executable(check main.cpp)
target_compile_features(check PRIVATE cxx_std_17)
target_include_directories(check PRIVATE "{root}/native/cpp/include")
target_link_libraries(check PRIVATE dimos_generated::messages)
""")
(source / "main.cpp").write_text("""#include <std_msgs/msg/header.hpp>
#include <dimos/native/cdr_codec.hpp>
int main() {
std_msgs::msg::Header h; h.frame_id = "outside-checkout"; h.stamp.sec = 17;
auto result = dimos::native::cdr_decode<std_msgs::msg::Header>(dimos::native::cdr_encode(h));
return result.frame_id == h.frame_id && result.stamp.sec == 17 ? 0 : 1;
}
""")
build = tmp_path / "build"
subprocess.run(
[
"cmake",
"-S",
str(source),
"-B",
str(build),
f"-DCMAKE_TOOLCHAIN_FILE={toolchain}",
],
check=True,
)
subprocess.run(["cmake", "--build", str(build), "--parallel", "2"], check=True)
subprocess.run([str(build / "check")], check=True)
2 changes: 1 addition & 1 deletion dimos/message_codegen/test_builtin_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def test_unlisted_sources_do_not_expand_the_public_catalog(tmp_path):
manifest = json.loads(
(source / "dimos_generated_schemas/package/message-package.json").read_text()
)
assert len(manifest["owned"]) == 73
assert len(manifest["owned"]) == 78
assert "extra_msgs/msg/NotExported" not in manifest["owned"]
assert "builtin_interfaces/msg/Time" in manifest["owned"]

Expand Down
2 changes: 1 addition & 1 deletion dimos/message_codegen/test_builtin_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
assert type(r.header) is Header and r == m
assert type(pickle.loads(pickle.dumps(m)).header) is Header
assert 'MSG: std_msgs/Header' in schema(m.__msgtype__)
assert len(message_types()) == 73
assert len(message_types()) == 78
assert 'visualization_msgs/msg/Marker' not in message_types()
root = Path(package_root())
assert (root / 'schema-sources.json').is_file()
Expand Down
6 changes: 3 additions & 3 deletions dimos/message_codegen/test_builtin_preparation.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from dimos_cdrgen.source_package import configuration, prepare as generate
import pytest

from scripts.prepare_builtin_sources import prepare

Check failure on line 25 in dimos/message_codegen/test_builtin_preparation.py

View workflow job for this annotation

GitHub Actions / lint

DEP003

'scripts' imported but it is a transitive dependency


def test_unprepared_product_fails_and_cached_preparation_is_complete(tmp_path, monkeypatch):
Expand All @@ -48,12 +48,12 @@

monkeypatch.setattr("dimos_cdrgen.source_inputs.requests.get", no_network)
inputs = prepare(cache, offline=True, project=project)
assert len(list(inputs.rglob("*.msg"))) == 73
assert len(list(inputs.rglob("*.msg"))) == 78
output = generate(project)
resources = output / "dimos_generated_schemas/package"
manifest = json.loads((resources / "message-package.json").read_text())
assert len(manifest["owned"]) == 73
assert len(list(project.glob("custom/dimos_msgs/msg/*.msg"))) == 14
assert len(manifest["owned"]) == 78
assert len(list(project.glob("custom/dimos_msgs/msg/*.msg"))) == 19
lock = json.loads((project / "schema-sources.json").read_text())
assert all(len(item["sha256"]) == 64 for item in lock["sources"])
assert (resources / "dimos_msgs-LICENSE").is_file()
Expand Down
36 changes: 36 additions & 0 deletions dimos/message_codegen/test_documented_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 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.

from pathlib import Path
import re


def test_documented_sources_match_compiled_example():
root = Path(__file__).resolve().parents[2]
document = (root / "docs/development/messages-external-project.md").read_text()
blocks = re.findall(r"<!-- source: ([^ ]+) -->\n```[^\n]*\n(.*?)```", document, re.S)
assert len(blocks) >= 8
for name, snippet in blocks:
source = (root / name).read_text()
if name.endswith((".py", ".cpp", ".rs")):
lines = source.splitlines()
comment = "#" if name.endswith(".py") else "//"
while lines and (lines[0].startswith(comment) or not lines[0].strip()):
lines.pop(0)
if lines and lines[0].startswith(chr(34) * 3):
lines.pop(0)
while lines and not lines[0].strip():
lines.pop(0)
source = "\n".join(lines) + "\n"
assert snippet == source, name
Loading
Loading