Compare commits

...

10 Commits

Author SHA1 Message Date
EricX-Zhao 0d7c134b1f Enhance Docker workflows: update image namespace handling, add cache key computation, and refine vendor package installation 2026-06-24 15:36:23 +03:00
EricX-Zhao bbb61c3f5e Switch Docker Buildx driver from host network to docker 2026-06-22 15:19:30 +03:00
EricX-Zhao 557b1568a2 Tag Docker images with date-based IMAGE_TAG on push 2026-06-22 15:14:15 +03:00
EricX-Zhao 897961e2fb Set explicit workflow permissions 2026-06-22 15:12:51 +03:00
EricX-Zhao 63c5d7782f Normalize IMAGE_NAMESPACE to lowercase 2026-06-22 15:06:49 +03:00
EricX-Zhao 121eef5f69 Limit build workflow to manual dispatch only 2026-06-22 15:02:29 +03:00
EricX-Zhao 1d9a214fd6 Move cross_compile workflow to repo root and support manual dispatch 2026-06-22 14:57:35 +03:00
EricX-Zhao bc5de267f7 Add GitHub Actions workflow for building cross-compiled ROS 2 images
Create a CI pipeline that builds and optionally pushes Docker images
for cross-compilation targets (rk3588) and ROS 2 distributions, with
multi-stage builds for base, toolchain, vendor packages, and ROS 2.

Also ignore diagnostic_remote_logging package in the Lyrical ROS 2
build.
2026-06-22 14:51:44 +03:00
EricX-Zhao 917f4e3064 Add opencv_contrib support and configure extra modules path in CMakeLists.txt 2026-06-19 10:35:41 +03:00
EricX-Zhao 13378d7c4f Refactor VIO dev container configuration: move Claude mounts to vio.yaml and update volume definitions 2026-05-12 10:47:53 +03:00
6 changed files with 276 additions and 10 deletions
+238
View File
@@ -0,0 +1,238 @@
name: Build Cross-Compiled ROS 2 Images
permissions:
contents: read
packages: write
on:
workflow_dispatch:
inputs:
platform:
description: "Target platform"
required: true
type: choice
options: [rk3588]
default: rk3588
ros_distro:
description: "ROS 2 distribution"
required: true
type: choice
options: [rolling, jazzy, lyrical]
default: lyrical
push_images:
description: "Push built images to registry"
required: false
type: boolean
default: false
env:
REGISTRY: ghcr.io
jobs:
build:
runs-on: ubuntu-22.04
strategy:
matrix:
platform: ${{ fromJSON(format('["{0}"]', inputs.platform)) }}
ros_distro: ${{ fromJSON(format('["{0}"]', inputs.ros_distro)) }}
fail-fast: false
steps:
- name: Set image namespace and tag
run: |
echo "IMAGE_NAMESPACE=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
echo "IMAGE_TAG=$(date +%Y%m%d)" >> $GITHUB_ENV
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# ── Compute cache keys ────────────────────────────────────────────
# Each key = hash(own files) + upstream key, so any upstream change
# transitively invalidates all downstream stages.
- name: Compute cache keys
id: keys
run: |
CC_BASE_KEY=$(sha256sum cross_compile/cc_base.Dockerfile | cut -d' ' -f1)
CC_TOOLCHAIN_FILES=$(find cross_compile/${{ matrix.platform }} -type f | sort | xargs sha256sum | sha256sum | cut -d' ' -f1)
CC_TOOLCHAIN_KEY=$(echo "$CC_TOOLCHAIN_FILES $CC_BASE_KEY" | sha256sum | cut -d' ' -f1)
VENDOR_FILES=$(find cross_compile/vendor_packages -type f | sort | xargs sha256sum | sha256sum | cut -d' ' -f1)
VENDOR_KEY=$(echo "$VENDOR_FILES $CC_TOOLCHAIN_KEY" | sha256sum | cut -d' ' -f1)
ROS2_FILES=$(find cross_compile/ros2/${{ matrix.ros_distro }} cross_compile/ros2/ros_cpp -type f | sort | xargs sha256sum | sha256sum | cut -d' ' -f1)
ROS2_KEY=$(echo "$ROS2_FILES $VENDOR_KEY" | sha256sum | cut -d' ' -f1)
echo "cc_base=$CC_BASE_KEY" >> $GITHUB_OUTPUT
echo "cc_toolchain=$CC_TOOLCHAIN_KEY" >> $GITHUB_OUTPUT
echo "vendor=$VENDOR_KEY" >> $GITHUB_OUTPUT
echo "ros2=$ROS2_KEY" >> $GITHUB_OUTPUT
# ── Stage 1: cc-base ─────────────────────────────────────────────
- name: Check cc-base cache
id: cc-base
run: |
EXPECTED="${{ steps.keys.outputs.cc_base }}"
if docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/cc-base:latest 2>/dev/null; then
docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/cc-base:latest cc-base:latest
CACHED=$(docker inspect cc-base:latest --format '{{ index .Config.Labels "cache.key" }}' 2>/dev/null || echo "")
[ "$CACHED" = "$EXPECTED" ] && echo "hit=true" >> $GITHUB_OUTPUT || echo "hit=false" >> $GITHUB_OUTPUT
else
echo "hit=false" >> $GITHUB_OUTPUT
fi
- name: Build cc-base
if: steps.cc-base.outputs.hit != 'true'
run: |
docker compose -f cross_compile/compose.yaml build cc-base
TMPDIR=$(mktemp -d)
docker build \
--label "cache.key=${{ steps.keys.outputs.cc_base }}" \
-t cc-base:latest -f - "$TMPDIR" << 'EOF'
FROM cc-base:latest
EOF
rm -rf "$TMPDIR"
- name: Push cc-base
if: inputs.push_images
run: |
docker tag cc-base:latest \
${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/cc-base:${{ env.IMAGE_TAG }}
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/cc-base:${{ env.IMAGE_TAG }}
docker tag cc-base:latest \
${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/cc-base:latest
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/cc-base:latest
# ── Stage 2: cc-toolchain ─────────────────────────────────────────
- name: Check cc-toolchain cache
id: cc-toolchain
run: |
EXPECTED="${{ steps.keys.outputs.cc_toolchain }}"
REMOTE="${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-toolchain:latest"
LOCAL="${{ matrix.platform }}/cc-toolchain:latest"
if docker pull "$REMOTE" 2>/dev/null; then
docker tag "$REMOTE" "$LOCAL"
CACHED=$(docker inspect "$LOCAL" --format '{{ index .Config.Labels "cache.key" }}' 2>/dev/null || echo "")
[ "$CACHED" = "$EXPECTED" ] && echo "hit=true" >> $GITHUB_OUTPUT || echo "hit=false" >> $GITHUB_OUTPUT
else
echo "hit=false" >> $GITHUB_OUTPUT
fi
- name: Build cc-toolchain (${{ matrix.platform }})
if: steps.cc-toolchain.outputs.hit != 'true'
run: |
PLATFORM=${{ matrix.platform }} docker compose -f cross_compile/compose.yaml build cc-toolchain
TMPDIR=$(mktemp -d)
docker build \
--label "cache.key=${{ steps.keys.outputs.cc_toolchain }}" \
-t ${{ matrix.platform }}/cc-toolchain:latest -f - "$TMPDIR" << 'EOF'
FROM ${{ matrix.platform }}/cc-toolchain:latest
EOF
rm -rf "$TMPDIR"
- name: Push cc-toolchain
if: inputs.push_images
run: |
docker tag ${{ matrix.platform }}/cc-toolchain:latest \
${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-toolchain:${{ env.IMAGE_TAG }}
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-toolchain:${{ env.IMAGE_TAG }}
docker tag ${{ matrix.platform }}/cc-toolchain:latest \
${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-toolchain:latest
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-toolchain:latest
# ── Stage 3: vendor-packages ──────────────────────────────────────
- name: Check vendor-packages cache
id: vendor
run: |
EXPECTED="${{ steps.keys.outputs.vendor }}"
REMOTE="${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-vendor-packages:latest"
LOCAL="${{ matrix.platform }}/cc-vendor-packages:latest"
if docker pull "$REMOTE" 2>/dev/null; then
docker tag "$REMOTE" "$LOCAL"
CACHED=$(docker inspect "$LOCAL" --format '{{ index .Config.Labels "cache.key" }}' 2>/dev/null || echo "")
[ "$CACHED" = "$EXPECTED" ] && echo "hit=true" >> $GITHUB_OUTPUT || echo "hit=false" >> $GITHUB_OUTPUT
else
echo "hit=false" >> $GITHUB_OUTPUT
fi
- name: Build vendor-packages (${{ matrix.platform }})
if: steps.vendor.outputs.hit != 'true'
run: |
PLATFORM=${{ matrix.platform }} docker compose -f cross_compile/compose.yaml build vendor-packages
TMPDIR=$(mktemp -d)
docker build \
--label "cache.key=${{ steps.keys.outputs.vendor }}" \
-t ${{ matrix.platform }}/cc-vendor-packages:latest -f - "$TMPDIR" << 'EOF'
FROM ${{ matrix.platform }}/cc-vendor-packages:latest
EOF
rm -rf "$TMPDIR"
- name: Push vendor-packages
if: inputs.push_images
run: |
docker tag ${{ matrix.platform }}/cc-vendor-packages:latest \
${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-vendor-packages:${{ env.IMAGE_TAG }}
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-vendor-packages:${{ env.IMAGE_TAG }}
docker tag ${{ matrix.platform }}/cc-vendor-packages:latest \
${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-vendor-packages:latest
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-vendor-packages:latest
# ── Stage 4: ros2 ─────────────────────────────────────────────────
- name: Check ros2 cache
id: ros2
run: |
EXPECTED="${{ steps.keys.outputs.ros2 }}"
REMOTE="${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-ros-${{ matrix.ros_distro }}:latest"
LOCAL="${{ matrix.platform }}/cc-ros-${{ matrix.ros_distro }}:latest"
if docker pull "$REMOTE" 2>/dev/null; then
docker tag "$REMOTE" "$LOCAL"
CACHED=$(docker inspect "$LOCAL" --format '{{ index .Config.Labels "cache.key" }}' 2>/dev/null || echo "")
[ "$CACHED" = "$EXPECTED" ] && echo "hit=true" >> $GITHUB_OUTPUT || echo "hit=false" >> $GITHUB_OUTPUT
else
echo "hit=false" >> $GITHUB_OUTPUT
fi
- name: Build ROS 2 (${{ matrix.ros_distro }})
if: steps.ros2.outputs.hit != 'true'
run: |
PLATFORM=${{ matrix.platform }} ROS_DISTRO=${{ matrix.ros_distro }} docker compose -f cross_compile/compose.yaml build ros2
TMPDIR=$(mktemp -d)
docker build \
--label "cache.key=${{ steps.keys.outputs.ros2 }}" \
-t ${{ matrix.platform }}/cc-ros-${{ matrix.ros_distro }}:latest -f - "$TMPDIR" << 'EOF'
FROM ${{ matrix.platform }}/cc-ros-${{ matrix.ros_distro }}:latest
EOF
rm -rf "$TMPDIR"
- name: Push ROS 2 image
if: inputs.push_images
run: |
docker tag ${{ matrix.platform }}/cc-ros-${{ matrix.ros_distro }}:latest \
${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-ros-${{ matrix.ros_distro }}:${{ env.IMAGE_TAG }}
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-ros-${{ matrix.ros_distro }}:${{ env.IMAGE_TAG }}
docker tag ${{ matrix.platform }}/cc-ros-${{ matrix.ros_distro }}:latest \
${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-ros-${{ matrix.ros_distro }}:latest
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.platform }}/cc-ros-${{ matrix.ros_distro }}:latest
# ── Summary ───────────────────────────────────────────────────────
- name: Image summary
if: always()
run: |
echo "### Built Images :rocket:" >> $GITHUB_STEP_SUMMARY
docker images \
--filter "reference=cc-*" \
--filter "reference=*/cc-*" \
--format "| \`{{.Repository}}:{{.Tag}}\` | {{.Size}} |" \
>> $GITHUB_STEP_SUMMARY
@@ -26,7 +26,8 @@ RUN patch -p1 -d /workspace/src/ros2/rclcpp < /workspace/patch/memory_strategy.p
RUN touch src/ros2/geometry2/tf2_bullet/COLCON_IGNORE \ RUN touch src/ros2/geometry2/tf2_bullet/COLCON_IGNORE \
&& touch src/ros2/rosbag2/rosbag2_storage_sqlite3/COLCON_IGNORE \ && touch src/ros2/rosbag2/rosbag2_storage_sqlite3/COLCON_IGNORE \
&& touch src/ros-perception/image_transport_plugins/theora_image_transport/COLCON_IGNORE && touch src/ros-perception/image_transport_plugins/theora_image_transport/COLCON_IGNORE \
&& touch src/diagnostics/diagnostic_remote_logging/COLCON_IGNORE
RUN MAKEFLAGS="-j6" colcon build \ RUN MAKEFLAGS="-j6" colcon build \
--executor parallel \ --executor parallel \
@@ -56,6 +57,8 @@ RUN mv _reindexer.cpython-310-x86_64-linux-gnu.so _reindexer.cpython-310-aarch64
RUN mv _writer.cpython-310-x86_64-linux-gnu.so _writer.cpython-310-aarch64-linux-gnu.so RUN mv _writer.cpython-310-x86_64-linux-gnu.so _writer.cpython-310-aarch64-linux-gnu.so
RUN mv _message_definitions.cpython-310-x86_64-linux-gnu.so _message_definitions.cpython-310-aarch64-linux-gnu.so RUN mv _message_definitions.cpython-310-x86_64-linux-gnu.so _message_definitions.cpython-310-aarch64-linux-gnu.so
RUN mv _reader.cpython-310-x86_64-linux-gnu.so _reader.cpython-310-aarch64-linux-gnu.so RUN mv _reader.cpython-310-x86_64-linux-gnu.so _reader.cpython-310-aarch64-linux-gnu.so
WORKDIR ${TARGET_SYSROOT}/lib/python3.10/site-packages/rosidl_buffer
RUN mv _rosidl_buffer_py.cpython-310-x86_64-linux-gnu.so _rosidl_buffer_py.cpython-310-aarch64-linux-gnu.so
FROM ${CC_PLATFORM_IMAGE} AS rmw_zenoh_builder FROM ${CC_PLATFORM_IMAGE} AS rmw_zenoh_builder
@@ -20,6 +20,22 @@ FetchContent_Declare(
URL https://github.com/opencv/opencv/archive/refs/tags/4.12.0.tar.gz URL https://github.com/opencv/opencv/archive/refs/tags/4.12.0.tar.gz
) )
FetchContent_Declare(
opencv_contrib
URL https://github.com/opencv/opencv_contrib/archive/refs/tags/4.12.0.tar.gz
)
# 先单独 populate contrib:只解压,不当子工程 configure(它根目录没有 CMakeLists.txt
FetchContent_GetProperties(opencv_contrib)
if(NOT opencv_contrib_POPULATED)
FetchContent_Populate(opencv_contrib)
endif()
# 必须是 CACHE PATH,且要在 MakeAvailable(opencv) 之前设
set(OPENCV_EXTRA_MODULES_PATH "${opencv_contrib_SOURCE_DIR}/modules"
CACHE PATH "" FORCE)
set(WITH_ADE OFF) set(WITH_ADE OFF)
set(WITH_GTK OFF) set(WITH_GTK OFF)
set(WITH_ITT OFF) set(WITH_ITT OFF)
@@ -122,11 +122,18 @@ ENV CC=aarch64-none-linux-gnu-gcc \
HOSTARCH=aarch64-none-linux-gnu \ HOSTARCH=aarch64-none-linux-gnu \
BUILDARCH=x86_64-linux-gnu BUILDARCH=x86_64-linux-gnu
# export CC=aarch64-none-linux-gnu-gcc
# export CXX=aarch64-none-linux-gnu-g++
# export AR=aarch64-none-linux-gnu-ar
# export RANLIB=aarch64-none-linux-gnu-ranlib
# export HOSTARCH=aarch64-none-linux-gnu
# export BUILDARCH=x86_64-linux-gn
RUN CFLAGS="-fPIC" LDFLAGS="-fPIC" ./configure \ RUN CFLAGS="-fPIC" LDFLAGS="-fPIC" ./configure \
--host=aarch64-none-linux-gnu \ --host=aarch64-none-linux-gnu \
--build=x86_64-linux-gnu \ --build=x86_64-linux-gnu \
--prefix=${TARGET_SYSROOT} \ --prefix=${TARGET_SYSROOT} \
--with-build-python=python3.12 \ --with-build-python=python3.10 \
--disable-ipv6 \ --disable-ipv6 \
--enable-optimizations \ --enable-optimizations \
--with-openssl=${TARGET_SYSROOT} \ --with-openssl=${TARGET_SYSROOT} \
@@ -137,11 +144,13 @@ RUN CFLAGS="-fPIC" LDFLAGS="-fPIC" ./configure \
# https://github.com/benfogle/crossenv # https://github.com/benfogle/crossenv
# cross compile python packages # cross compile python packages
WORKDIR /tmp WORKDIR /tmp
# /opt/python3.10/venv/build/bin/pip install 'setuptools==68.2.2' wheel
RUN python3 -m pip install crossenv \ RUN python3 -m pip install crossenv \
&& python3 -m crossenv ${TARGET_SYSROOT}/bin/python3 venv \ && python3 -m crossenv ${TARGET_SYSROOT}/bin/python3 venv \
&& . venv/bin/activate \ && . venv/bin/activate \
&& pip -v install numpy==1.21.5 lark==1.1.1 packaging typing-extensions --prefix=${TARGET_SYSROOT} && pip -v install numpy==1.21.5 lark==1.1.1 packaging typing-extensions --prefix=${TARGET_SYSROOT} \
&& ./venv/build/bin/pip install 'setuptools==68.2.2' wheel \
&& pip -v install empy==3.3.4 pyyaml psutil argcomplete --prefix=${TARGET_SYSROOT}
FROM ${CC_PLATFORM_IMAGE} AS final FROM ${CC_PLATFORM_IMAGE} AS final
-5
View File
@@ -14,11 +14,6 @@
"no_proxy": "localhost,127.0.0.1" "no_proxy": "localhost,127.0.0.1"
}, },
"mounts": [
"source=${localEnv:HOME}/.claude,target=/root/.claude,type=bind,consistency=cached",
"source=${localEnv:HOME}/.claude.json,target=/root/.claude.json,type=bind,consistency=cached"
],
"customizations": { "customizations": {
"vscode": { "vscode": {
"extensions": [ "extensions": [
+5
View File
@@ -11,6 +11,7 @@ services:
- ${WORKSPACE}:/workspace - ${WORKSPACE}:/workspace
- ${DATASET}:/dataset - ${DATASET}:/dataset
- ${DATASET_EXT}:/dataset_ext - ${DATASET_EXT}:/dataset_ext
- claude-code-global:/root/.claude
vio-dev-rk3588: vio-dev-rk3588:
extends: extends:
file: ../common.yaml file: ../common.yaml
@@ -18,3 +19,7 @@ services:
container_name: vio-dev-rk3588 container_name: vio-dev-rk3588
volumes: volumes:
- ${WORKSPACE}:/workspace - ${WORKSPACE}:/workspace
volumes:
claude-code-global:
external: true