name: Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-*'
permissions:
contents: write
actions: read
id-token: write
jobs:
build-c-api:
name: Build C API (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- arch: x86_64
runner: ubuntu-22.04
- arch: aarch64
runner: ubuntu-22.04-arm
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- name: Install Rust toolchain
run: rustup toolchain install stable --profile minimal
- name: Build release
run: cargo build --release
- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Package C API
run: |
VERSION=${{ steps.version.outputs.version }}
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
PATCH=$(echo "$VERSION" | cut -d. -f3)
PKG_NAME="edgefirst-schemas-linux_${{ matrix.arch }}-${VERSION}"
mkdir -p "${PKG_NAME}/lib/pkgconfig" "${PKG_NAME}/include/edgefirst"
cp README.md LICENSE "${PKG_NAME}/"
cp include/edgefirst/schemas.h "${PKG_NAME}/include/edgefirst/"
# Versioned shared library with chained symlinks (GNU/Linux convention).
# The ELF DT_SONAME is libedgefirst_schemas.so.${MAJOR}; the runtime
# loader opens that name and walks this chain to the real file:
# .so symlink -> .so.${MAJOR}
# .so.${MAJOR} symlink -> .so.${MAJOR}.${MINOR}
# .so.${MAJOR}.${MINOR} symlink -> .so.${MAJOR}.${MINOR}.${PATCH}
# .so.${MAJOR}.${MINOR}.${PATCH} real file
cp target/release/libedgefirst_schemas.so "${PKG_NAME}/lib/libedgefirst_schemas.so.${MAJOR}.${MINOR}.${PATCH}"
ln -s "libedgefirst_schemas.so.${MAJOR}.${MINOR}.${PATCH}" "${PKG_NAME}/lib/libedgefirst_schemas.so.${MAJOR}.${MINOR}"
ln -s "libedgefirst_schemas.so.${MAJOR}.${MINOR}" "${PKG_NAME}/lib/libedgefirst_schemas.so.${MAJOR}"
ln -s "libedgefirst_schemas.so.${MAJOR}" "${PKG_NAME}/lib/libedgefirst_schemas.so"
cp target/release/libedgefirst_schemas.a "${PKG_NAME}/lib/"
sed "s/@VERSION@/${VERSION}/" \
edgefirst-schemas.pc.in > "${PKG_NAME}/lib/pkgconfig/edgefirst-schemas.pc"
zip -r --symlinks "${PKG_NAME}.zip" "${PKG_NAME}"
- name: Upload C API artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f with:
name: c-api-${{ matrix.arch }}
path: edgefirst-schemas-linux_${{ matrix.arch }}-${{ steps.version.outputs.version }}.zip
collect-debian:
name: Collect Debian Packages
runs-on: ubuntu-latest
steps:
- name: Wait for ROS workflow
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Waiting for ROS workflow on commit ${GITHUB_SHA}..."
for i in $(seq 1 30); do
RUN_ID=$(gh run list --repo "$GITHUB_REPOSITORY" --workflow ros.yml \
--commit "$GITHUB_SHA" --json databaseId --jq '.[0].databaseId')
if [ -n "$RUN_ID" ]; then
break
fi
echo " Attempt $i/30 — ROS workflow not found yet, retrying in 10s..."
sleep 10
done
if [ -z "$RUN_ID" ]; then
echo "::error::ROS workflow run not found for commit ${GITHUB_SHA}"
exit 1
fi
echo "Found ROS workflow run: ${RUN_ID}"
gh run watch "$RUN_ID" --repo "$GITHUB_REPOSITORY" --exit-status
- name: Download Debian packages
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RUN_ID=$(gh run list --repo "$GITHUB_REPOSITORY" --workflow ros.yml \
--commit "$GITHUB_SHA" --json databaseId --jq '.[0].databaseId')
gh run download "$RUN_ID" --repo "$GITHUB_REPOSITORY" \
--pattern 'ros-*-edgefirst-msgs_*' --dir packages
find packages -name '*.deb' -exec mv {} . \;
echo "Downloaded Debian packages:"
ls -lh *.deb
- name: Upload Debian artifacts
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f with:
name: debian-packages
path: "*.deb"
create-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: [build-c-api, collect-debian]
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 with:
fetch-depth: 0
- name: Extract version from tag
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/}
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Version: $VERSION"
- name: Extract changelog for version
run: |
# Strip 'v' prefix — CHANGELOG.md uses bare version numbers
VERSION="${{ steps.version.outputs.version }}"
BARE_VERSION="${VERSION#v}"
# Extract the section for this version from CHANGELOG.md
CHANGELOG_CONTENT=$(awk "/## \[$BARE_VERSION\]/,/## \[/" CHANGELOG.md | sed '$d' | tail -n +2)
if [ -z "$CHANGELOG_CONTENT" ]; then
CHANGELOG_CONTENT="Release $VERSION"
fi
echo "$CHANGELOG_CONTENT" > release_notes.md
- name: Generate SBOM
run: |
bash .github/scripts/generate_sbom.sh || echo "SBOM generation skipped"
- name: Download all artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with:
path: release-assets
- name: Collect release files
run: |
mkdir -p release-files
find release-assets -type f \( -name '*.zip' -o -name '*.deb' \) -exec mv {} release-files/ \;
[ -f sbom.json ] && cp sbom.json release-files/
echo "Release files:"
ls -lh release-files/
- name: Create GitHub Release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b with:
name: ${{ steps.version.outputs.version }}
body_path: release_notes.md
draft: false
prerelease: ${{ contains(steps.version.outputs.version, '-') }}
files: release-files/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-crates:
name: Publish to crates.io
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- name: Install Rust toolchain
run: rustup toolchain install stable --profile minimal
- name: Install cargo-workspaces
uses: taiki-e/install-action@0bc4cd8a3e21db4cb9519857377b0c8c5e150de5 with:
tool: cargo-workspaces
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo workspaces publish --from-git
wait-for-wheels:
name: Wait for Wheels
runs-on: ubuntu-latest
steps:
- name: Wait for wheels.yml
uses: lewagon/wait-on-check-action@84840133801bb697fed25fd0e21078b85f5831a0 with:
ref: ${{ github.sha }}
check-name: 'Verify Wheels'
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 15
checks-discovery-timeout: 600
publish-pypi:
name: Publish to PyPI
runs-on: ubuntu-latest
needs: [wait-for-wheels]
environment: pypi
permissions:
id-token: write
steps:
- name: Download wheel artifacts from wheels.yml
uses: dawidd6/action-download-artifact@8a338493df3d275e4a7a63bcff3b8fe97e51a927 with:
workflow: wheels.yml
commit: ${{ github.sha }}
name: wheels-*
name_is_regexp: true
path: dist/
merge_multiple: true
- name: Download sdist artifact from wheels.yml
uses: dawidd6/action-download-artifact@8a338493df3d275e4a7a63bcff3b8fe97e51a927 with:
workflow: wheels.yml
commit: ${{ github.sha }}
name: sdist
path: dist/
- name: Inventory artifacts
run: |
echo "Files to publish:"
ls -lh dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e with:
packages-dir: dist/
attach-wheels-to-release:
name: Attach Wheels to GitHub Release
runs-on: ubuntu-latest
needs: [create-release, wait-for-wheels]
steps:
- name: Download wheel artifacts
uses: dawidd6/action-download-artifact@8a338493df3d275e4a7a63bcff3b8fe97e51a927 with:
workflow: wheels.yml
commit: ${{ github.sha }}
name: wheels-*
name_is_regexp: true
path: wheels/
merge_multiple: true
- name: Download sdist artifact
uses: dawidd6/action-download-artifact@8a338493df3d275e4a7a63bcff3b8fe97e51a927 with:
workflow: wheels.yml
commit: ${{ github.sha }}
name: sdist
path: wheels/
- name: Upload to release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b with:
tag_name: ${{ github.ref_name }}
files: |
wheels/*.whl
wheels/*.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}