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
4 changes: 4 additions & 0 deletions .github/scripts/check_sdist.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@

# Paths below the dimos-<version>/ root that every release sdist must carry.
REQUIRED = (
"dimos/message_codegen/schemas/sensor_msgs/msg/Image.msg",
"dimos/message_codegen/templates/dimos_cdr.hpp",
"dimos/message_codegen/_vendor/rosidl_parser.py",
"scripts/setup_message_codegen.sh",
"web/cockpit/dist/index.html",
"web/deno.lock",
"web/relay/main.ts",
Expand Down
6 changes: 6 additions & 0 deletions .github/scripts/wheel_smoke.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from pathlib import Path
import urllib.request

from dimos.message_codegen.registry import message_types
from dimos.navigation.replanning_a_star.min_cost_astar_ext import min_cost_astar_cpp # noqa: F401
from dimos.web.relay_bridge import locate
from dimos.web.relay_bridge.relay_process import RelayProcess
Expand All @@ -44,6 +45,11 @@


def main() -> None:
types = message_types()
image_type = types["sensor_msgs/msg/Image"]
value = image_type(height=1, width=2, step=2, encoding="mono8", data=[3, 7])
assert list(image_type.decode(value.encode()).data) == [3, 7]
assert "MSG: std_msgs/msg/Header" in image_type.schema
dist = Path(locate.__file__).resolve().parent / "_relay_dist"
for rel in REQUIRED:
if not (dist / rel).is_file():
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,8 @@ jobs:
sudo apt-get install -y portaudio19-dev libturbojpeg ffmpeg
- name: Fetch the go2_short replay dataset
run: git lfs pull --include="data/.lfs/go2_short.db.tar.gz" --exclude=""
- name: Build standalone CDR dependency
run: bash scripts/setup_message_codegen.sh
- name: Install dependencies
run: uv sync --group tests --group browser-tests --frozen
- name: Install Playwright browsers # the e2e runs in both engines
Expand Down Expand Up @@ -709,6 +711,8 @@ jobs:
- name: Provision native modules from Cachix
if: steps.native-marker.outputs.cache-hit == 'true'
run: python3 bin/build-native-modules --link-results .cachix-marker/links.txt
- name: Build standalone CDR dependency
run: bash scripts/setup_message_codegen.sh
- name: Install dependencies
run: uv sync --group tests --frozen
- name: Run tests
Expand Down Expand Up @@ -842,6 +846,8 @@ jobs:
run: |
uv venv .codecov-venv
uv pip install --python .codecov-venv/bin/python codecov-cli==11.2.8
- name: Build standalone CDR dependency
run: bash scripts/setup_message_codegen.sh
- name: Install dependencies
run: uv sync --group tests-self-hosted --frozen
- name: Install web_browser e2e dependencies (macOS)
Expand Down Expand Up @@ -1089,6 +1095,8 @@ jobs:
libpango-1.0-0 libxcomposite1 libxdamage1 libxkbcommon0 libxrandr2 \
`# Rerun viewer pulls in X11 keyboard bindings when DISPLAY is set.` \
libxkbcommon-x11-0
- name: Build standalone CDR dependency
run: bash scripts/setup_message_codegen.sh
- name: Install dependencies
run: uv sync --group tests-self-hosted --frozen
- name: Build C++ extensions in-place
Expand Down
13 changes: 12 additions & 1 deletion .github/workflows/message-codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
- examples/message-codegen/**
- native/rust/dimos-lcm-transport/**
- scripts/*message*codegen*
- scripts/test_message_packages.sh
- scripts/vendor_message_definitions.py
- .github/workflows/message-codegen.yml
- .github/docker/message-codegen-reference.Dockerfile
Expand All @@ -28,12 +29,22 @@ jobs:
run: |
test ! -d /opt/ros
uv venv .venv --python 3.12
uv pip install --python .venv/bin/python pybind11==3.0.1 rosbags==0.11.0 numpy pytest pytest-asyncio pytest-env lcm-dimos-fork
uv pip install --python .venv/bin/python pybind11==3.0.1 rosbags==0.11.0 numpy pytest pytest-asyncio pytest-env lcm-dimos-fork setuptools wheel
.venv/bin/python -c "import importlib.util; assert importlib.util.find_spec('rclpy') is None"
- name: Build the pinned standalone CDR library
run: bash scripts/setup_message_codegen.sh
- name: Generate, build, test, and demonstrate all three languages
run: bash scripts/test_message_codegen.sh
- name: Build and consume wheel, sdist, CMake, and Cargo packages
run: bash scripts/test_message_packages.sh
- name: Retain external application and packages
uses: actions/upload-artifact@v7
with:
name: message-package-evidence
path: |
build/message-codegen/external-app/evidence/
build/message-codegen/external-app/dist/
build/message-codegen/external-app/generated/rust/target/package/*.crate
- name: Retain executable demo evidence
if: always()
uses: actions/upload-artifact@v7
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/release-build-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ on:
- .github/scripts/wheel_smoke.py
- pyproject.toml
- setup.py
- dimos/message_codegen/**
- scripts/setup_message_codegen.sh
- MANIFEST.in
- web/cockpit/**
- web/sdk/**
Expand All @@ -31,6 +33,7 @@ jobs:
env:
# Keep in sync with the build-wheels env in release.yml
CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_34
CIBW_BEFORE_BUILD: "bash {project}/scripts/setup_message_codegen.sh"
CIBW_TEST_COMMAND: "python {project}/.github/scripts/wheel_smoke.py"
steps:
- name: Checkout
Expand Down
35 changes: 34 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ jobs:
CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_34 # open3d>=0.19 requires glibc 2.31+
CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_34 # open3d>=0.19 requires glibc 2.31+
# Asserts the packaged relay + Cockpit and serves them from the installed wheel.
CIBW_BEFORE_BUILD: "bash {project}/scripts/setup_message_codegen.sh"
CIBW_TEST_COMMAND: "python {project}/.github/scripts/wheel_smoke.py"
MACOSX_DEPLOYMENT_TARGET: "11.0"
steps:
Expand All @@ -141,6 +142,26 @@ jobs:
name: wheels-${{ matrix.os }}-${{ matrix.arch }}
path: wheelhouse/*.whl

build-message-packages:
needs: validate
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10.0.1
- uses: dtolnay/rust-toolchain@stable
- run: uv venv .venv --python 3.12
- run: bash scripts/setup_message_codegen.sh
- name: Package CMake headers, schemas, and native Rust sources
env:
MESSAGE_VERSION: ${{ needs.validate.outputs.version }}
run: bash scripts/package_messages.sh "$MESSAGE_VERSION"
- uses: actions/upload-artifact@v7
with:
name: native-message-packages
path: build/message-codegen/release/dist/*

publish-testpypi:
if: ${{ inputs.dry-run }}
needs: [validate, build-sdist, build-wheels]
Expand All @@ -155,6 +176,12 @@ jobs:
- name: Download wheels/sdist
uses: actions/download-artifact@v8
with:
name: sdist
path: dist
- name: Download wheels
uses: actions/download-artifact@v8
with:
pattern: wheels-*
path: dist
merge-multiple: true
- name: Install uv
Expand Down Expand Up @@ -184,6 +211,12 @@ jobs:
- name: Download wheels/sdist
uses: actions/download-artifact@v8
with:
name: sdist
path: dist
- name: Download wheels
uses: actions/download-artifact@v8
with:
pattern: wheels-*
path: dist
merge-multiple: true
- name: Install uv
Expand All @@ -199,7 +232,7 @@ jobs:

tag-and-release:
if: ${{ ! inputs.dry-run }}
needs: [validate, publish-pypi]
needs: [validate, publish-pypi, build-message-packages]
runs-on: ubuntu-latest
environment: release-tag
permissions:
Expand Down
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ global-exclude .DS_Store
# runtime data and must stay out. Add a new extension here when code starts
# loading a new data type — never go back to per-package globs.
recursive-include dimos *.yaml *.yml *.json *.urdf *.html *.css *.js *.svg *.tcss
graft dimos/message_codegen/schemas
graft dimos/message_codegen/templates
include dimos/message_codegen/_vendor/*.pyi
include scripts/setup_message_codegen.sh

# --- Exclusions (must come after the includes above so they win) ---
# Test fixtures must never ship.
Expand Down
16 changes: 16 additions & 0 deletions dimos/message_codegen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,19 @@ Applications and builds never run the maintenance downloader.
This work is being delivered through the `replace-lcm-message-encoding` OpenSpec
change. The generated pipeline is under development; the old runtime message APIs
have not yet been replaced.

## Distribution

Pass `--package` to emit a setuptools source project in `python/`, alongside the
CMake project and Cargo crate. `--python-module` gives independent message
packages distinct extension names; `--version` sets the package version.
The Python sdist contains the generator, its pinned parser, and all definition
inputs. Building the sdist regenerates source without ROS. The wheel contains
native code plus definitions, licenses, and the `dimos.messages` provider.

DimOS's own wheel uses the same `MessageBuildExt` and generator from `setup.py`.
The release workflow preserves the existing Linux x86_64/aarch64 and macOS arm64
wheel matrix and adds CMake/schema and Cargo source packages to GitHub releases.
Source developers run `bash scripts/setup_message_codegen.sh` before building;
`scripts/install.sh --mode dev` does this during dependency setup. Ordinary wheel
users need no compiler, generator run, or ROS installation.
33 changes: 33 additions & 0 deletions dimos/message_codegen/builtin_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 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.

"""The message definitions and native types shipped in the DimOS wheel."""

from importlib import import_module
from pathlib import Path
from typing import Any

from dimos.message_codegen.definitions import Definitions


def schema_root() -> Path:
return Path(__file__).with_name("schemas")


def message_types() -> dict[str, Any]:
extension = import_module("dimos_generated")
return {
message.name: getattr(getattr(extension, message.package).msg, message.short_name)
for message in Definitions([]).resolve()
}
24 changes: 24 additions & 0 deletions dimos/message_codegen/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

from __future__ import annotations

from dataclasses import asdict
from hashlib import sha256
import json
import math
from typing import Any
Expand Down Expand Up @@ -201,6 +203,22 @@ def validation(message: Message) -> list[str]:


def generate(messages: tuple[Message, ...]) -> str:
guards = {
message.name: "DIMOS_MESSAGE_"
+ sha256(
json.dumps(
[
message.name,
[asdict(field) for field in message.fields],
[asdict(constant) for constant in message.constants],
],
sort_keys=True,
).encode()
)
.hexdigest()
.upper()
for message in messages
}
lines = [
"// Generated from ROS2 .msg definitions. Do not edit.",
"#pragma once",
Expand All @@ -215,6 +233,8 @@ def generate(messages: tuple[Message, ...]) -> str:
'#include "dimos_cdr.hpp"',
]
for message in messages:
guard = guards[message.name] + "_TYPE"
lines.extend([f"#ifndef {guard}", f"#define {guard}"])
namespace = qualified(message.name.rsplit("/", 1)[0])
name = identifier(message.short_name)
lines.extend([f"namespace {namespace} {{", f"struct {name} {{"])
Expand All @@ -236,9 +256,12 @@ def generate(messages: tuple[Message, ...]) -> str:
lines.append("void validate() const {")
lines.extend(validation(message))
lines.extend(["}", f'static constexpr const char* msg_name = "{message.name}";', "};", "}"])
lines.append("#endif")

lines.append("namespace eprosima::fastcdr {")
for message in messages:
guard = guards[message.name] + "_CODEC"
lines.extend([f"#ifndef {guard}", f"#define {guard}"])
name = qualified(message.name)
lines.extend(
[
Expand Down Expand Up @@ -269,5 +292,6 @@ def generate(messages: tuple[Message, ...]) -> str:
if not message.fields:
lines.append("uint8_t unused; cdr >> unused;")
lines.extend(["value.validate();", "}"])
lines.append("#endif")
lines.append("}")
return "\n".join(lines) + "\n"
11 changes: 9 additions & 2 deletions dimos/message_codegen/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
InvalidValue,
parse_message_string,
)
from dimos.message_codegen.providers import schema_roots

BUNDLED_SCHEMAS = Path(__file__).with_name("schemas")

Expand Down Expand Up @@ -142,9 +143,15 @@ def parse_message(path: Path) -> Message:
class Definitions:
"""A closed set of local definitions with dependency-first resolution."""

def __init__(self, roots: Iterable[Path], *, bundled: bool = True) -> None:
def __init__(
self, roots: Iterable[Path], *, bundled: bool = True, installed: bool = False
) -> None:
self._messages: dict[str, Message] = {}
for root in ([BUNDLED_SCHEMAS] if bundled else []) + list(roots):
for root in (
([BUNDLED_SCHEMAS] if bundled else [])
+ list(roots)
+ (list(schema_roots()) if installed else [])
):
if not root.is_dir():
raise ValueError(f"Schema root does not exist: {root}")
for path in sorted(root.rglob("*.msg")):
Expand Down
Loading
Loading