Skip to content

Refactor: block tree and fork choice #79

Refactor: block tree and fork choice

Refactor: block tree and fork choice #79

Workflow file for this run

#
# Qlean-mini Docker Multi-arch Build
#
# This workflow builds Docker images for multiple architectures (ARM64, AMD64)
# and creates a unified multi-arch manifest for easy deployment.
#
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Manual Trigger (workflow_dispatch) UI Structure:
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#
# 📦 Dependencies Image (rebuild rarely, ~once per month)
# ☑ Build dependencies image (vcpkg libs)
# 📝 Dependencies tag: latest
#
# 🏗️ Main Build Configuration
# ☑ Build linux/amd64
# ☑ Build linux/arm64
#
# 🚀 Push & Tagging
# ☐ Push images to Docker Hub
# 📝 Custom tag: (e.g., v1.0.0, staging)
# ☐ Also push 'latest' tag
#
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Automatic Triggers:
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#
# 1. Push to master (regular commit):
# → Auto-build + push
# → Tags: commit_sha only (e.g., qdrvm/qlean-mini:59b2c37)
# → Latest: NOT pushed
#
# 2. Git tag push (release, e.g., v1.0.0):
# → Auto-build + push
# → Tags: commit_sha + tag_name + latest
# → Example: 59b2c37, v1.0.0, latest
#
# 3. Pull request:
# → Build only, no push
#
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Manual Trigger Examples (workflow_dispatch):
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#
# Scenario 1: Daily development (test build only)
# ☑ amd64 ☑ arm64 ☐ push ☐ build_deps → Uses deps:latest from registry
#
# Scenario 2: Production release (manual)
# ☑ build_deps deps_tag: v2 ☑ push custom_tag: v1.0.0 ☑ push_latest
#
# Scenario 3: Hotfix (ARM64 only, no deps rebuild)
# ☐ amd64 ☑ arm64 ☑ push custom_tag: hotfix-123
#
# Scenario 4: Rebuild dependencies only
# ☑ build_deps deps_tag: latest ☑ push (amd64/arm64 irrelevant)
#
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#
name: "Docker Build"
on:
push:
branches:
- master # Auto-build on master push
tags:
- '*' # Auto-build on any git tag (release)
pull_request:
branches:
- master
workflow_dispatch:
inputs:
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Dependencies Image (rebuild only when vcpkg.json changes)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
build_dependencies:
description: "📦 Build dependencies (vcpkg)"
type: boolean
required: false
default: false
deps_tag:
description: "📦 Dependencies tag (latest, v2, etc.)"
type: string
required: false
default: "latest"
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Main Build Configuration
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
build_amd64:
description: "🖥️ Build linux/amd64"
type: boolean
required: false
default: true
build_arm64:
description: "🍎 Build linux/arm64"
type: boolean
required: false
default: true
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Push & Tagging
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
push_to_registry:
description: "🚀 Push to Docker Hub"
type: boolean
required: false
default: true
custom_tag:
description: "🏷️ Custom tag (v1.0.0, dev, etc.)"
type: string
required: false
default: ""
push_latest:
description: "🏷️ Also push 'latest'"
type: boolean
required: false
default: false
env:
DOCKER_REGISTRY: qdrvm
DOCKER_IMAGE_NAME: qlean-mini
DOCKER_DEPS_TAG: ${{ inputs.deps_tag || 'latest' }}
GIT_COMMIT: ${{ github.sha }}
# Tagging logic:
# - Manual trigger: use inputs.custom_tag
# - Git tag (release): use tag name (e.g., v1.0.0)
# - Master/other: no custom tag
DOCKER_PUSH_TAG: ${{
github.event_name == 'workflow_dispatch' && inputs.custom_tag != '' && 'true' ||
startsWith(github.ref, 'refs/tags/') && 'true' ||
'false' }}
DOCKER_IMAGE_TAG: ${{
github.event_name == 'workflow_dispatch' && inputs.custom_tag ||
startsWith(github.ref, 'refs/tags/') && github.ref_name ||
'localBuild' }}
# Latest tag: only for git releases OR manual with push_latest enabled
DOCKER_PUSH_LATEST: ${{
startsWith(github.ref, 'refs/tags/') && 'true' ||
github.event_name == 'workflow_dispatch' && inputs.push_to_registry && inputs.push_latest && 'true' ||
'false' }}
# Auto-push configuration:
# - master branch: push (no latest)
# - git tags: push (with latest)
# - ci/docker: push (testing)
# - pull requests: no push
AUTO_PUSH: ${{
github.event_name == 'push' &&
(github.ref == 'refs/heads/master' ||
github.ref == 'refs/heads/ci/docker' ||
startsWith(github.ref, 'refs/tags/')) }}
IS_TAG: ${{ startsWith(github.ref, 'refs/tags/') }}
jobs:
setup_matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.matrix.outputs.matrix }}
should_build_deps: ${{ steps.config.outputs.should_build_deps }}
should_push: ${{ steps.config.outputs.should_push }}
is_multi_arch: ${{ steps.matrix.outputs.is_multi_arch }}
steps:
- name: "Checkout repository"
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: "Determine build configuration"
id: config
run: |
# Determine if we should build dependencies
BUILD_DEPS="false"
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
BUILD_DEPS="${{ inputs.build_dependencies }}"
fi
# For push events, check if vcpkg.json changed
if [[ "${{ github.event_name }}" == "push" ]] && [[ -n "${{ github.event.before }}" ]]; then
if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep -q "vcpkg.json"; then
echo "vcpkg.json changed, will build dependencies"
BUILD_DEPS="true"
fi
fi
echo "should_build_deps=$BUILD_DEPS" >> $GITHUB_OUTPUT
# Determine if we should push
SHOULD_PUSH="${{ env.AUTO_PUSH }}"
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
SHOULD_PUSH="${{ inputs.push_to_registry }}"
fi
echo "should_push=$SHOULD_PUSH" >> $GITHUB_OUTPUT
echo "Configuration:"
echo " Build dependencies: $BUILD_DEPS"
echo " Push to registry: $SHOULD_PUSH"
echo " Event: ${{ github.event_name }}"
echo ""
echo "Debug outputs:"
echo " should_build_deps=$BUILD_DEPS"
echo " should_push=$SHOULD_PUSH"
- name: "Generate build matrix"
id: matrix
uses: ./.github/actions/docker-matrix
with:
build_amd64: ${{ inputs.build_amd64 || 'true' }}
build_arm64: ${{ inputs.build_arm64 || 'true' }}
amd64_runner: ubuntu-24.04
arm64_runner: ubuntu-24.04-arm
- name: "Debug outputs"
run: |
echo "=== Build Configuration ==="
echo "Event: ${{ github.event_name }}"
echo "Ref: ${{ github.ref }}"
echo "Ref name: ${{ github.ref_name }}"
echo ""
echo "=== Docker Tags ==="
echo "Commit tag: ${GIT_COMMIT:0:7} (always pushed)"
if [[ "${DOCKER_PUSH_TAG}" == "true" ]]; then
echo "Custom tag: ${DOCKER_IMAGE_TAG} ✅"
else
echo "Custom tag: (none)"
fi
if [[ "${DOCKER_PUSH_LATEST}" == "true" ]]; then
echo "Latest tag: ✅ (will be pushed)"
else
echo "Latest tag: ❌ (not pushed)"
fi
echo ""
echo "=== Matrix Outputs ==="
echo "is_multi_arch: ${{ steps.matrix.outputs.is_multi_arch }}"
echo "should_push: ${{ steps.config.outputs.should_push }}"
echo "should_build_deps: ${{ steps.config.outputs.should_build_deps }}"
build_dependencies:
needs: setup_matrix
if: needs.setup_matrix.outputs.should_build_deps == 'true'
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup_matrix.outputs.matrix) }}
runs-on: ${{ matrix.runs_on }}
timeout-minutes: 180
env:
DOCKER_PLATFORM: ${{ matrix.platform }}
steps:
- name: "Checkout"
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: "Set up Docker Buildx"
uses: docker/setup-buildx-action@v3
- name: "Build dependencies image"
run: |
echo "Building dependencies for ${{ matrix.platform }}"
make docker_build_dependencies
- name: "Login to Docker Hub"
if: needs.setup_matrix.outputs.should_push == 'true'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: "Push platform-specific dependencies"
if: needs.setup_matrix.outputs.should_push == 'true'
run: |
make docker_push_platform_dependencies
build:
needs: [setup_matrix, build_dependencies]
if: always() && needs.setup_matrix.result == 'success' && (needs.build_dependencies.result == 'success' || needs.build_dependencies.result == 'skipped')
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup_matrix.outputs.matrix) }}
runs-on: ${{ matrix.runs_on }}
timeout-minutes: 180
env:
DOCKER_PLATFORM: ${{ matrix.platform }}
steps:
- name: "Checkout"
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: "Set up Docker Buildx"
uses: docker/setup-buildx-action@v3
- name: "Login to Docker Hub (for pulling dependencies)"
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: "Pull dependencies image"
run: |
# Determine platform suffix
if [[ "${{ matrix.platform }}" == "linux/arm64" ]]; then
PLATFORM_SUFFIX="-arm64"
elif [[ "${{ matrix.platform }}" == "linux/amd64" ]]; then
PLATFORM_SUFFIX="-amd64"
else
echo "ERROR: Unknown platform ${{ matrix.platform }}"
exit 1
fi
DEPS_IMAGE="${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}-dependencies:${DOCKER_DEPS_TAG}${PLATFORM_SUFFIX}"
echo "Checking for dependencies image: ${DEPS_IMAGE}"
if docker manifest inspect ${DEPS_IMAGE} > /dev/null 2>&1; then
echo "✓ Found platform-specific dependencies image"
echo "Pulling ${DEPS_IMAGE}..."
docker pull ${DEPS_IMAGE}
docker tag ${DEPS_IMAGE} ${DOCKER_IMAGE_NAME}-dependencies:${DOCKER_DEPS_TAG}
echo "✓ Tagged as ${DOCKER_IMAGE_NAME}-dependencies:${DOCKER_DEPS_TAG} for build"
else
echo "ERROR: Dependencies image not found in registry!"
echo "Image: ${DEPS_IMAGE}"
echo ""
echo "To build dependencies, either:"
echo " 1. Run workflow with 'Build dependencies image' = true"
echo " 2. Build and push manually:"
echo " DOCKER_PLATFORM=${{ matrix.platform }} make docker_build_dependencies"
echo " DOCKER_PLATFORM=${{ matrix.platform }} make docker_push_platform_dependencies"
exit 1
fi
- name: "Build builder and runtime images"
run: |
echo "Building for ${{ matrix.platform }}"
make docker_build_builder
make docker_build_runtime
- name: "Verify runtime image"
run: |
make docker_verify
- name: "Login to Docker Hub (for pushing)"
if: needs.setup_matrix.outputs.should_push == 'true'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: "Push platform-specific images"
if: needs.setup_matrix.outputs.should_push == 'true'
run: |
make docker_push_platform
create_manifest:
needs: [setup_matrix, build]
if: always() && !cancelled() && needs.build.result == 'success' && needs.setup_matrix.outputs.should_push == 'true' && needs.setup_matrix.outputs.is_multi_arch == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: "Debug conditions"
run: |
echo "=== Create Manifest Conditions ==="
echo "build.result: ${{ needs.build.result }}"
echo "should_push: ${{ needs.setup_matrix.outputs.should_push }}"
echo "is_multi_arch: ${{ needs.setup_matrix.outputs.is_multi_arch }}"
echo ""
if [[ "${{ needs.build.result }}" != "success" ]]; then
echo "❌ Build failed or skipped"
elif [[ "${{ needs.setup_matrix.outputs.should_push }}" != "true" ]]; then
echo "❌ Push disabled"
elif [[ "${{ needs.setup_matrix.outputs.is_multi_arch }}" != "true" ]]; then
echo "❌ Not multi-arch (only one platform built)"
else
echo "✅ All conditions met, creating manifest"
fi
- name: "Checkout repository"
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: "Login to Docker Hub"
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: "Create multi-arch manifest for runtime"
run: |
echo "Creating multi-arch manifest for runtime image..."
echo ""
echo "NOTE: This step creates a manifest from platform-specific images already in registry"
echo " Builder: NOT pushed (intermediate stage only)"
echo " Dependencies: platform-specific (no multi-arch manifest needed)"
echo ""
make docker_manifest_create
- name: "Verify manifests"
run: |
echo "=== Verifying multi-arch manifests ==="
SHORT_COMMIT=$(git rev-parse --short HEAD)
echo ""
echo "[1/N] Commit tag manifest (${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${SHORT_COMMIT}):"
MANIFEST=$(docker manifest inspect ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${SHORT_COMMIT})
echo "$MANIFEST" | jq -r '.manifests[] | " • \(.platform.os)/\(.platform.architecture)"' || echo "$MANIFEST"
if [[ "${DOCKER_PUSH_TAG}" == "true" ]]; then
echo ""
echo "[2/N] Custom tag manifest (${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${DOCKER_IMAGE_TAG}):"
MANIFEST=$(docker manifest inspect ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${DOCKER_IMAGE_TAG})
echo "$MANIFEST" | jq -r '.manifests[] | " • \(.platform.os)/\(.platform.architecture)"' || echo "$MANIFEST"
fi
if [[ "${DOCKER_PUSH_LATEST}" == "true" ]]; then
echo ""
echo "[3/N] Latest tag manifest (${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:latest):"
MANIFEST=$(docker manifest inspect ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:latest)
echo "$MANIFEST" | jq -r '.manifests[] | " • \(.platform.os)/\(.platform.architecture)"' || echo "$MANIFEST"
fi
echo ""
echo "NOTE: Builder image is NOT pushed to registry (intermediate build stage only)"
echo ""
echo "✅ All multi-arch manifests created successfully!"
- name: "Display final image tags"
run: |
SHORT_COMMIT=$(git rev-parse --short HEAD)
echo "=== Successfully pushed Docker images ==="
echo ""
echo "🐳 Runtime multi-arch images:"
echo " • ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${SHORT_COMMIT} (commit)"
if [[ "${DOCKER_PUSH_TAG}" == "true" ]]; then
echo " • ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${DOCKER_IMAGE_TAG} (custom tag)"
fi
if [[ "${DOCKER_PUSH_LATEST}" == "true" ]]; then
echo " • ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:latest"
fi
if [[ "${{ needs.setup_matrix.outputs.should_build_deps }}" == "true" ]]; then
echo ""
echo "📦 Dependencies images (platform-specific):"
echo " • ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}-dependencies:${DOCKER_DEPS_TAG}-arm64"
echo " • ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}-dependencies:${DOCKER_DEPS_TAG}-amd64"
echo " (Note: dependencies are NOT multi-arch, each platform uses its own)"
fi
echo ""
echo "📋 Quick commands:"
echo " Pull: docker pull ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${SHORT_COMMIT}"
echo " Run: docker run --rm ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${SHORT_COMMIT} --help"