Refactor: block tree and fork choice #79
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # | |
| # 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" | |