diff --git a/.github/workflows/publish-crates.yml b/.github/workflows/publish-crates.yml index a0b20fca4..852b9f003 100644 --- a/.github/workflows/publish-crates.yml +++ b/.github/workflows/publish-crates.yml @@ -7,44 +7,91 @@ on: release: types: [published] + workflow_dispatch: + inputs: + version: + description: "Release tag to publish (E.G. v0.10.0-rc.1, v0.10.0). Corresponding GitHub release must already exist." + required: true + type: string + +concurrency: + group: "${{ github.workflow }} @ ${{ inputs.version || github.event.release.tag_name }}" + cancel-in-progress: false + +env: + RELEASE_TAG: ${{ inputs.version || github.event.release.tag_name }} + jobs: publish-release: name: Cargo publish release - runs-on: Linux-ARM64-Runner + runs-on: warp-ubuntu-latest-x64-8x if: ${{ github.repository_owner == '0xMiden' }} environment: publish-crates steps: + - name: Validate release tag + run: | + set -euo pipefail + + if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$ ]]; then + echo "::error::Release tags must look like v1.2.3 or v1.2.3-rc.1" + exit 1 + fi + - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 persist-credentials: false - ref: ${{ github.event.release.tag_name }} - - uses: ./.github/actions/install-rocksdb - - uses: ./.github/actions/install-protobuf-compiler - - name: Log release info - env: - RELEASE_TAG: ${{ github.event.release.tag_name }} - run: | - echo "Publishing release ${RELEASE_TAG}" - echo "Commit: $(git rev-parse HEAD)" - - name: Cleanup large tools for build space - uses: ./.github/actions/cleanup-runner + ref: ${{ env.RELEASE_TAG }} + - name: Install dependencies run: sudo apt-get update && sudo apt-get install -y jq - - name: Update Rust toolchain + + - name: Verify GitHub release exists + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + if ! gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" >/dev/null; then + echo "::error::GitHub release ${RELEASE_TAG} does not exist. Refusing to publish crates." + exit 1 + fi + + - name: Rustup run: rustup toolchain install --no-self-update - - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - - uses: taiki-e/install-action@055f5df8c3f65ea01cd41e9dc855becd88953486 # v2.75.18 - with: - tool: cargo-binstall@1.16.6 - # - name: Install cargo-msrv - # run: cargo binstall --no-confirm --force cargo-msrv - # - name: Check MSRV for each workspace member - # run: | - # export PATH="$HOME/.cargo/bin:$PATH" - # ./scripts/check-msrv.sh - - name: Run cargo publish - run: cargo publish --workspace + + - name: Publish unpublished crates env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: | + set -euo pipefail + + version="${RELEASE_TAG#v}" + metadata="$(cargo metadata --no-deps --format-version 1)" + + mapfile -t publishable_crates < <( + jq -r '[.packages[] | select(.publish != []) | .name] | sort | .[]' <<< "${metadata}" + ) + + exclude_args=() + remaining_crates=() + + for crate in "${publishable_crates[@]}"; do + if cargo info --quiet "${crate}@${version}" >/dev/null 2>&1; then + echo "${crate} ${version} already exists on crates.io; excluding it." + exclude_args+=(--exclude "${crate}") + else + remaining_crates+=("${crate}") + fi + done + + if [[ "${#remaining_crates[@]}" -eq 0 ]]; then + echo "All publishable crates already exist on crates.io for ${version}." + exit 0 + fi + + echo "Publishing crates that are not yet on crates.io:" + printf ' %s\n' "${remaining_crates[@]}" + + cargo publish --workspace --no-verify --locked "${exclude_args[@]}" diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml deleted file mode 100644 index b01434109..000000000 --- a/.github/workflows/publish-docker.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: Publish Docker Images - -on: - release: - types: [prereleased, released] - - workflow_dispatch: - inputs: - version: - description: "Version to release (E.G. v0.10.0-rc.1, v0.10.0). Corresponding git tag must already exist." - required: true - type: string - -env: - version: ${{ inputs.version || github.ref_name }} - registry: ghcr.io - DOCKER_PLATFORMS: linux/amd64,linux/arm64 - -permissions: - id-token: write - contents: write - packages: write - attestations: write - -jobs: - publish: - runs-on: warp-ubuntu-latest-x64-8x - environment: publish-docker - strategy: - matrix: - include: - - component: node - bin: miden-node - port: 57291 - - component: validator - bin: miden-validator - port: 50101 - - component: ntx-builder - bin: miden-ntx-builder - port: 50301 - - component: remote-prover - bin: miden-remote-prover - port: 50051 - - component: network-monitor - bin: miden-network-monitor - port: 3000 - name: Publish ${{ matrix.component }} ${{ inputs.version }} - steps: - - name: Checkout repo - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: ${{ env.version }} - fetch-depth: 0 - persist-credentials: false - - - name: Prepare image metadata - id: metadata - run: | - echo "created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" - echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - - - name: Log in to the Container registry - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 - with: - registry: ${{ env.registry }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push Docker image - id: push - uses: WarpBuilds/build-push-action@ec038a9f4b87a7c7ccb50ac7a26a90d46a610a81 # v6.0.9 - with: - context: . - push: true - file: ./Dockerfile - platforms: ${{ env.DOCKER_PLATFORMS }} - profile-name: ${{ vars.WARPBUILD_DOCKER_BUILDER_PROFILE }} - pull: true - build-args: | - BIN=${{ matrix.bin }} - PORT=${{ matrix.port }} - CREATED=${{ steps.metadata.outputs.created }} - VERSION=${{ env.version }} - COMMIT=${{ steps.metadata.outputs.commit }} - tags: ${{ env.registry }}/0xmiden/miden-${{ matrix.component }}:${{ env.version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..cc99eca60 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,334 @@ +name: Release + +on: + push: + tags: + - "v*" + +concurrency: + group: "${{ github.workflow }} @ ${{ github.ref }}" + cancel-in-progress: false + +permissions: + contents: read + +env: + REGISTRY: ghcr.io + DOCKER_PLATFORMS: linux/amd64,linux/arm64 + +jobs: + preflight: + name: Preflight + runs-on: ubuntu-24.04 + permissions: + contents: read + outputs: + commit: ${{ steps.release.outputs.commit }} + created: ${{ steps.release.outputs.created }} + tag: ${{ steps.release.outputs.tag }} + version: ${{ steps.release.outputs.version }} + prerelease: ${{ steps.release.outputs.prerelease }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.ref_name }} + fetch-depth: 0 + persist-credentials: false + + - name: Rustup + run: rustup toolchain install --no-self-update + + - name: Validate release tag and version + id: release + env: + TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + + if [[ ! "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$ ]]; then + echo "::error::Release tags must look like v1.2.3 or v1.2.3-rc.1" + exit 1 + fi + + version="${TAG#v}" + metadata="$(cargo metadata --no-deps --format-version 1)" + mapfile -t versions < <(jq -r '[.packages[] | select(.publish != []) | .version] | unique | .[]' <<< "${metadata}") + + if [[ "${#versions[@]}" -ne 1 ]]; then + printf '%s\n' "${versions[@]}" + echo "::error::Publishable workspace packages do not share exactly one version" + exit 1 + fi + + if [[ "${versions[0]}" != "${version}" ]]; then + echo "::error::Release tag ${TAG} does not match workspace package version ${versions[0]}" + exit 1 + fi + + prerelease=false + if [[ "${version}" == *-* ]]; then + prerelease=true + fi + + { + # Keep Docker metadata identical between dry-run and publish builds so the + # publish build can reuse the dry-run build cache instead of invalidating + # the final image layers with a new timestamp. + echo "commit=$(git rev-parse HEAD)" + echo "created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" + echo "tag=${TAG}" + echo "version=${version}" + echo "prerelease=${prerelease}" + } >> "${GITHUB_OUTPUT}" + + - name: Ensure GitHub release does not exist + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.release.outputs.tag }} + run: | + set -euo pipefail + + if gh release view "${TAG}" >/dev/null 2>&1; then + echo "::error::GitHub release ${TAG} already exists. Leaving the existing release and tag untouched." + exit 1 + fi + + dry-run-crates: + name: Dry-run crates.io publish + runs-on: warp-ubuntu-latest-x64-8x + needs: preflight + if: ${{ github.repository_owner == '0xMiden' }} + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + ref: ${{ needs.preflight.outputs.tag }} + fetch-depth: 0 + persist-credentials: false + + - uses: ./.github/actions/install-rocksdb + - uses: ./.github/actions/install-protobuf-compiler + + - name: Cleanup large tools for build space + uses: ./.github/actions/cleanup-runner + + - name: Rustup + run: rustup toolchain install --no-self-update + + - uses: WarpBuilds/rust-cache@9d0cc3090d9c87de74ea67617b246e978735b1a1 # v2.9.1 + with: + shared-key: release-dry-run-crates + save-if: false + + - name: Dry-run cargo publish + run: cargo publish --workspace --dry-run --locked + + check-msrv: + name: Check MSRV + runs-on: warp-ubuntu-latest-x64-8x + needs: preflight + if: ${{ github.repository_owner == '0xMiden' }} + # Deliberately use stable Rust instead of rust-toolchain.toml because + # cargo-msrv itself may require a newer compiler than the repo MSRV. + env: + RUSTUP_TOOLCHAIN: stable + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + ref: ${{ needs.preflight.outputs.tag }} + fetch-depth: 0 + persist-credentials: false + + - uses: ./.github/actions/install-rocksdb + - uses: ./.github/actions/install-protobuf-compiler + + - name: Cleanup large tools for build space + uses: ./.github/actions/cleanup-runner + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y jq + + - name: Rustup + run: rustup toolchain install --no-self-update stable + + - uses: WarpBuilds/rust-cache@9d0cc3090d9c87de74ea67617b246e978735b1a1 # v2.9.1 + with: + shared-key: release-msrv + save-if: false + + - name: Install cargo-binstall + uses: taiki-e/install-action@055f5df8c3f65ea01cd41e9dc855becd88953486 # v2.75.18 + with: + tool: cargo-binstall@1.16.6 + + - name: Install cargo-msrv + run: cargo binstall --no-confirm cargo-msrv + + - name: Check MSRV + run: ./scripts/check-msrv.sh + + dry-run-docker: + name: Dry-run Docker ${{ matrix.component }} + runs-on: warp-ubuntu-latest-x64-8x + needs: preflight + if: ${{ github.repository_owner == '0xMiden' }} + permissions: + contents: read + id-token: write + strategy: + fail-fast: false + matrix: + include: + - component: node + bin: miden-node + port: 57291 + - component: validator + bin: miden-validator + port: 50101 + - component: ntx-builder + bin: miden-ntx-builder + port: 50301 + - component: remote-prover + bin: miden-remote-prover + port: 50051 + - component: network-monitor + bin: miden-network-monitor + port: 3000 + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.preflight.outputs.tag }} + fetch-depth: 0 + persist-credentials: false + + - name: Build Docker image + uses: WarpBuilds/build-push-action@ec038a9f4b87a7c7ccb50ac7a26a90d46a610a81 # v6.0.9 + with: + context: . + push: false + file: ./Dockerfile + platforms: ${{ env.DOCKER_PLATFORMS }} + profile-name: ${{ vars.WARPBUILD_DOCKER_BUILDER_PROFILE }} + pull: true + build-args: | + BIN=${{ matrix.bin }} + PORT=${{ matrix.port }} + CREATED=${{ needs.preflight.outputs.created }} + VERSION=${{ needs.preflight.outputs.tag }} + COMMIT=${{ needs.preflight.outputs.commit }} + + publish-docker: + name: Publish Docker ${{ matrix.component }} + runs-on: warp-ubuntu-latest-x64-8x + needs: [preflight, dry-run-crates, check-msrv, dry-run-docker] + if: ${{ github.repository_owner == '0xMiden' }} + environment: publish-docker + permissions: + id-token: write + contents: read + packages: write + attestations: write + strategy: + fail-fast: false + matrix: + include: + - component: node + bin: miden-node + port: 57291 + - component: validator + bin: miden-validator + port: 50101 + - component: ntx-builder + bin: miden-ntx-builder + port: 50301 + - component: remote-prover + bin: miden-remote-prover + port: 50051 + - component: network-monitor + bin: miden-network-monitor + port: 3000 + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.preflight.outputs.tag }} + fetch-depth: 0 + persist-credentials: false + + - name: Ensure GitHub release still does not exist + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ needs.preflight.outputs.tag }} + run: | + set -euo pipefail + + if gh release view "${TAG}" >/dev/null 2>&1; then + echo "::error::GitHub release ${TAG} already exists. Refusing to publish Docker images." + exit 1 + fi + + - name: Log in to the Container registry + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + uses: WarpBuilds/build-push-action@ec038a9f4b87a7c7ccb50ac7a26a90d46a610a81 # v6.0.9 + with: + context: . + push: true + file: ./Dockerfile + platforms: ${{ env.DOCKER_PLATFORMS }} + profile-name: ${{ vars.WARPBUILD_DOCKER_BUILDER_PROFILE }} + pull: false + build-args: | + BIN=${{ matrix.bin }} + PORT=${{ matrix.port }} + CREATED=${{ needs.preflight.outputs.created }} + VERSION=${{ needs.preflight.outputs.tag }} + COMMIT=${{ needs.preflight.outputs.commit }} + tags: ${{ env.REGISTRY }}/0xmiden/miden-${{ matrix.component }}:${{ needs.preflight.outputs.tag }} + + publish-github-release: + name: Publish GitHub release + runs-on: ubuntu-24.04 + needs: [preflight, publish-docker] + if: ${{ github.repository_owner == '0xMiden' }} + permissions: + contents: write + steps: + - name: Re-check GitHub release absence + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ needs.preflight.outputs.tag }} + run: | + set -euo pipefail + + if gh release view "${TAG}" >/dev/null 2>&1; then + echo "::error::GitHub release ${TAG} already exists. Leaving it untouched." + exit 1 + fi + + - name: Create release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PRERELEASE: ${{ needs.preflight.outputs.prerelease }} + TAG: ${{ needs.preflight.outputs.tag }} + run: | + set -euo pipefail + + prerelease_flag=() + if [[ "${PRERELEASE}" == "true" ]]; then + prerelease_flag=(--prerelease) + fi + + gh release create "${TAG}" \ + --verify-tag \ + --title "${TAG}" \ + --notes "Release ${TAG}" \ + "${prerelease_flag[@]}"