Skip to content
Merged
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
273 changes: 273 additions & 0 deletions .github/workflows/release-publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
name: Flame Release Publish

on:
release:
types: [published]

permissions:
contents: read

concurrency:
group: flame-release-publish-${{ github.event.release.tag_name }}
cancel-in-progress: false

env:
CLICOLOR_FORCE: 1
IMAGE_REGISTRY: docker.io/xflops

jobs:
release-metadata:
name: Release Metadata
runs-on: ubuntu-latest
outputs:
cargo-version: ${{ steps.metadata.outputs.cargo-version }}
docker-tag: ${{ steps.metadata.outputs.docker-tag }}
publish-latest: ${{ steps.metadata.outputs.publish-latest }}
python-version: ${{ steps.metadata.outputs.python-version }}
stdng-version: ${{ steps.metadata.outputs.stdng-version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}

- name: Validate release metadata
id: metadata
env:
RELEASE_PRERELEASE: ${{ github.event.release.prerelease }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
python3 <<'PY'
import os
import tomllib
from pathlib import Path

tag = os.environ["RELEASE_TAG"]
if not tag.startswith("v"):
raise SystemExit(f"release tag must start with 'v': {tag}")

cargo_version = tomllib.loads(Path("sdk/rust/Cargo.toml").read_text())["package"]["version"]
macros_version = tomllib.loads(Path("sdk/rust/macros/Cargo.toml").read_text())["package"]["version"]
python_version = tomllib.loads(Path("sdk/python/pyproject.toml").read_text())["project"]["version"]
stdng_version = tomllib.loads(Path("stdng/Cargo.toml").read_text())["package"]["version"]

expected_cargo = tag[1:]
expected_python = expected_cargo.replace("-rc", "rc")

if cargo_version != expected_cargo:
raise SystemExit(f"flame-rs version {cargo_version} does not match release tag {tag}")
if macros_version != cargo_version:
raise SystemExit(
f"flame-rs-macros version {macros_version} does not match flame-rs {cargo_version}"
)
if python_version != expected_python:
raise SystemExit(
f"flamepy version {python_version} does not match release tag {tag}; expected {expected_python}"
)

publish_latest = str(os.environ["RELEASE_PRERELEASE"].lower() != "true").lower()
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
output.write(f"cargo-version={cargo_version}\n")
output.write(f"docker-tag={tag}\n")
output.write(f"publish-latest={publish_latest}\n")
output.write(f"python-version={python_version}\n")
output.write(f"stdng-version={stdng_version}\n")
PY

publish-python:
name: Publish flamepy
runs-on: ubuntu-latest
needs: release-metadata
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Build flamepy
run: |
python -m pip install --upgrade pip uv
cd sdk/python
uv build --out-dir ../../dist/flamepy

- name: Publish flamepy to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/flamepy
password: ${{ secrets.PYPI_API_TOKEN }}
skip-existing: true

- name: Verify flamepy from PyPI
env:
PYTHON_VERSION: ${{ needs.release-metadata.outputs.python-version }}
run: |
set -euo pipefail
for attempt in $(seq 1 30); do
if uv run --no-project --with "flamepy==${PYTHON_VERSION}" \
python -c "import flamepy; assert flamepy.__version__ == '${PYTHON_VERSION}'; print(flamepy.__version__)"; then
exit 0
fi
echo "flamepy ${PYTHON_VERSION} is not available yet; retrying (${attempt}/30)"
sleep 10
done
echo "flamepy ${PYTHON_VERSION} did not become available on PyPI"
exit 1

publish-rust:
name: Publish Rust crates
runs-on: ubuntu-latest
needs: release-metadata
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
CARGO_VERSION: ${{ needs.release-metadata.outputs.cargo-version }}
STDNG_VERSION: ${{ needs.release-metadata.outputs.stdng-version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}

- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler pkg-config libssl-dev

- name: Package and publish Rust crates
run: |
set -euo pipefail

crate_exists() {
local crate="$1"
local version="$2"
curl -fsS "https://crates.io/api/v1/crates/${crate}/${version}" >/dev/null 2>&1
}

wait_for_crate() {
local crate="$1"
local version="$2"
for attempt in $(seq 1 30); do
if crate_exists "${crate}" "${version}"; then
echo "${crate} ${version} is available on crates.io"
return 0
fi
echo "${crate} ${version} is not available yet; retrying (${attempt}/30)"
sleep 10
done
echo "${crate} ${version} did not become available on crates.io"
return 1
}

publish_crate() {
local crate="$1"
local version="$2"
local manifest="$3"
shift 3

if crate_exists "${crate}" "${version}"; then
echo "${crate} ${version} is already published; skipping publish"
return 0
fi

cargo publish --manifest-path "${manifest}" --token "${CARGO_REGISTRY_TOKEN}" "$@"
wait_for_crate "${crate}" "${version}"
}

cargo package --manifest-path stdng/Cargo.toml
publish_crate stdng "${STDNG_VERSION}" stdng/Cargo.toml
wait_for_crate stdng "${STDNG_VERSION}"

cargo package --manifest-path sdk/rust/macros/Cargo.toml
publish_crate flame-rs-macros "${CARGO_VERSION}" sdk/rust/macros/Cargo.toml
wait_for_crate flame-rs-macros "${CARGO_VERSION}"

cargo package --manifest-path sdk/rust/Cargo.toml --features macros
publish_crate flame-rs "${CARGO_VERSION}" sdk/rust/Cargo.toml --features macros
wait_for_crate flame-rs "${CARGO_VERSION}"

publish-images:
name: Publish ${{ matrix.image }}
runs-on: ubuntu-latest
needs: release-metadata
strategy:
fail-fast: false
matrix:
include:
- image: flame-session-manager
dockerfile: docker/Dockerfile.fsm
- image: flame-object-cache
dockerfile: docker/Dockerfile.foc
- image: flame-executor-manager
dockerfile: docker/Dockerfile.fem
- image: flame-console
dockerfile: docker/Dockerfile.console
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Collect Docker metadata
id: docker-meta
uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE_REGISTRY }}/${{ matrix.image }}
tags: |
type=raw,value=${{ needs.release-metadata.outputs.docker-tag }}
type=raw,value=latest,enable=${{ needs.release-metadata.outputs.publish-latest }}

- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
file: ${{ matrix.dockerfile }}
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.docker-meta.outputs.tags }}
labels: ${{ steps.docker-meta.outputs.labels }}
cache-from: type=gha,scope=${{ matrix.image }}
cache-to: type=gha,mode=max,scope=${{ matrix.image }}
provenance: false

- name: Verify pushed image manifest
env:
DOCKER_TAG: ${{ needs.release-metadata.outputs.docker-tag }}
PUBLISH_LATEST: ${{ needs.release-metadata.outputs.publish-latest }}
run: |
set -euo pipefail

inspect_manifest() {
local ref="$1"
local manifest
manifest="$(docker buildx imagetools inspect "${ref}")"
printf '%s\n' "${manifest}"
printf '%s\n' "${manifest}" | grep -q 'linux/amd64'
printf '%s\n' "${manifest}" | grep -q 'linux/arm64'
}

image="${IMAGE_REGISTRY}/${{ matrix.image }}"
inspect_manifest "${image}:${DOCKER_TAG}"
if [ "${PUBLISH_LATEST}" = "true" ]; then
inspect_manifest "${image}:latest"
fi
Loading