Skip to content
Merged
Show file tree
Hide file tree
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
307 changes: 158 additions & 149 deletions .github/workflows/bump-version.yaml

Large diffs are not rendered by default.

190 changes: 182 additions & 8 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,27 @@ on:
push:
branches:
- "**"
- "!bump-version/**"
- "!dependabot/**"
tags:
- 'v[0-9]+\.[0-9]+\.[0-9]+-?**'
pull_request: {}

jobs:
# the settings of the repository's Makefile the other jobs need, e.g. whether it builds a cli
settings:
runs-on: ubuntu-latest
outputs:
cli: ${{ steps.settings.outputs.cli }}
steps:
- uses: actions/checkout@v7
- name: Check build
# the files synced from componentized/build are unchanged, see build/README.md
run: build/scripts/check-build.sh
- uses: actions-rust-lang/setup-rust-toolchain@v2
- name: Read settings
id: settings
run: echo "cli=$( make -s print-CLI_BIN )" >> "${GITHUB_OUTPUT}"

build:
runs-on: ubuntu-latest
steps:
Expand All @@ -23,10 +37,31 @@ jobs:
run: |
make tools
make -s tools-path >> "${GITHUB_PATH}"
- name: Sync wit
run: make wit
- name: Check for drift in generated wit
- name: Generate
# the wit dependencies, and anything else the repository generates, see `make generate`
run: make generate
- name: Check for drift in generated files
run: git diff --exit-code .
- name: Check crate version matches the interface version
# the crates inherit the workspace version, bumped with the interface by
# build/scripts/bump-version.sh. A repository without a workspace version has nothing to check.
run: |
interface_version=$( sed -n "s/^package ${GITHUB_REPOSITORY/\//:}@\(.*\);$/\1/p" wit/worlds.wit )
workspace_version=$( sed -n '/^\[workspace.package\]/,/^\[/s/^version = "\(.*\)"$/\1/p' Cargo.toml )
if [ -n "${workspace_version}" ] && [ "${workspace_version}" != "${interface_version}" ] ; then
echo "::error::the workspace version ${workspace_version} in Cargo.toml does not match the interface version ${interface_version}, see build/scripts/bump-version.sh"
exit 1
fi
- name: Check crates can be published
# the release workflow publishes the crates when a release is published, too late to find a
# crate can't be published. A cli is verified against the library being published with it.
run: |
read -ra crates <<< "$( make -s print-PUBLISH_CRATES )"
if [ "${#crates[@]}" -eq 0 ] ; then
echo "no crates to publish, see PUBLISH_CRATES"
exit 0
fi
cargo publish --dry-run --locked "${crates[@]/#/--package=}"
- name: Build components
run: make components
- name: Collect components.tar
Expand All @@ -40,13 +75,16 @@ jobs:
retention-days: 7
- name: Test
run: make test
- name: Integration test
# the tests that need more than cargo, e.g. a server in a container, see `make test-integration`
run: make test-integration
- name: Capture WIT
working-directory: ./target/components
run: |
dump_wit() {
echo "::group::$(basename "$1") ($(du -h "$1" | awk '{print $1}' ))"
wasm-tools component wit "$1"
echo "::endgroup::"
echo "::endgroup::"
}

# print interface.wasm first
Expand All @@ -57,16 +95,130 @@ jobs:
dump_wit "${component}"
done

publish:
if: github.event_name == 'push' && ( startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' )
# the cli built from the repository, see CLI_BIN, packaged for each platform as cargo binstall
# expects, e.g. constants-aarch64-apple-darwin.tar.gz
cli:
needs:
- settings
if: needs.settings.outputs.cli != ''
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-26.04
target: x86_64-unknown-linux-musl
- os: ubuntu-26.04-arm
target: aarch64-unknown-linux-musl
- os: macos-26
target: aarch64-apple-darwin
- os: macos-26-intel
target: x86_64-apple-darwin
- os: windows-2025
target: x86_64-pc-windows-msvc
- os: windows-11-arm
target: aarch64-pc-windows-msvc
# each runner builds for its own platform, linux builds are static with musl
runs-on: ${{ matrix.os }}
# bash on windows too, where pwsh is the default, the steps read CLI_BIN from the environment. The
# windows packaging steps are pwsh, reading it as `${env:CLI_BIN}`.
defaults:
run:
shell: bash
env:
CLI_BIN: ${{ needs.settings.outputs.cli }}
steps:
- uses: actions/checkout@v7
- uses: actions-rust-lang/setup-rust-toolchain@v2
with:
target: ${{ matrix.target }}
- name: Build cli
run: cargo build --release --locked --bin "${CLI_BIN}" --target ${{ matrix.target }}
- name: Package cli
if: runner.os != 'Windows'
run: |
mkdir -p dist
tar -czvf "dist/${CLI_BIN}-${{ matrix.target }}.tar.gz" -C target/${{ matrix.target }}/release "${CLI_BIN}" -C "${PWD}" LICENSE NOTICE README.md
- name: Package cli
if: runner.os == 'Windows'
shell: pwsh
run: |
New-Item -ItemType Directory -Force dist
Compress-Archive -Path "target/${{ matrix.target }}/release/${env:CLI_BIN}.exe", LICENSE, NOTICE, README.md -DestinationPath "dist/${env:CLI_BIN}-${{ matrix.target }}.zip"
# the smoke test runs the cli from the package, as a user would
- name: Unpack cli
if: runner.os != 'Windows'
run: |
mkdir -p smoke
tar -xzvf "dist/${CLI_BIN}-${{ matrix.target }}.tar.gz" -C smoke
- name: Unpack cli
if: runner.os == 'Windows'
shell: pwsh
run: Expand-Archive -Path "dist/${env:CLI_BIN}-${{ matrix.target }}.zip" -DestinationPath smoke
- name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
- name: Smoke test cli
# the repository's scripts/smoke-test-cli.sh runs the unpacked cli, given its path and a
# directory for its files
run: |
if [ ! -f scripts/smoke-test-cli.sh ] ; then
echo "::warning::no scripts/smoke-test-cli.sh to smoke test ${CLI_BIN}"
exit 0
fi
bash scripts/smoke-test-cli.sh "./smoke/${CLI_BIN}" smoke
- name: Upload cli
uses: actions/upload-artifact@v7
with:
name: cli-${{ matrix.target }}
path: dist/*
if-no-files-found: error
retention-days: 7

# a single check for branch protection to require, rather than each job and matrix entry. It always
# runs, a skipped required check counts as passing, and fails when any job it needs did not succeed.
# The cli is skipped for a repository without one.
ci:
if: always()
needs:
- settings
- build
- cli
runs-on: ubuntu-latest
steps:
- name: Check jobs succeeded
env:
RESULTS: ${{ toJSON(needs.*.result) }}
CLI: ${{ needs.settings.outputs.cli }}
CLI_RESULT: ${{ needs.cli.result }}
run: |
echo "results: ${RESULTS}"
if [ -z "${CLI}" ] && [ "${CLI_RESULT}" = "skipped" ] ; then
RESULTS=$( jq -c 'map(select(. != "skipped"))' <<< "${RESULTS}" )
fi
if jq -e 'any(. != "success")' <<< "${RESULTS}" > /dev/null ; then
echo "::error::a required job did not succeed"
exit 1
fi

publish:
# the ci job's result rather than the default success(), which is false when the cli is skipped,
# even though the ci job succeeded without it
if: ${{ !cancelled() && needs.ci.result == 'success' && github.event_name == 'push' && ( startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' ) }}
needs:
- settings
- ci
permissions:
contents: write
packages: write
id-token: write
runs-on: ubuntu-latest
env:
# the repository variable DRY_RUN=true goes through publishing without publishing anything:
# nothing is pushed or signed, and no release is drafted
DRY_RUN: ${{ vars.DRY_RUN == 'true' }}
steps:
- name: Dry run
if: env.DRY_RUN == 'true'
run: echo "::notice::a dry run, the repository variable DRY_RUN is true, nothing is published"
- uses: actions/checkout@v7
- uses: actions-rust-lang/setup-rust-toolchain@v2
- name: Install cargo binstall
Expand All @@ -81,6 +233,13 @@ jobs:
uses: actions/download-artifact@v8
with:
name: components.tar
- name: Download cli
if: needs.settings.outputs.cli != ''
uses: actions/download-artifact@v8
with:
pattern: cli-*
path: dist
merge-multiple: true
- name: Extract components
run: mkdir -p target/components && tar -xvf components.tar -C target/components
- name: Get interface version
Expand All @@ -90,7 +249,19 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
id: tag_version
run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
- name: Check tag matches the workspace version
# cargo binstall finds the cli archives at the release tagged with the cli crate's version,
# and publishing the release publishes the crates to crates.io at that version. A repository
# without a workspace version has nothing to check.
if: startsWith(github.ref, 'refs/tags/')
run: |
workspace_version=$( sed -n '/^\[workspace.package\]/,/^\[/s/^version = "\(.*\)"$/\1/p' Cargo.toml )
if [ -n "${workspace_version}" ] && [ "v${workspace_version}" != "${{ steps.tag_version.outputs.VERSION }}" ] ; then
echo "::error::tag ${{ steps.tag_version.outputs.VERSION }} does not match the workspace version ${workspace_version} in Cargo.toml"
exit 1
fi
- name: Login to GitHub Container Registry
if: env.DRY_RUN != 'true'
uses: docker/login-action@v4
with:
registry: ghcr.io
Expand All @@ -105,6 +276,7 @@ jobs:
REPOSITORY: "ghcr.io/${{ github.repository }}"
VERSION: "${{ case(github.ref == 'refs/heads/main', steps.interface_version.outputs.VERSION, steps.tag_version.outputs.VERSION) }}"
- name: Draft release notes
if: startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && contains(steps.interface_version.outputs.VERSION, '-'))
run: |
{
echo "## Published components"
Expand All @@ -118,12 +290,14 @@ jobs:
} > "${RUNNER_TEMP}/release-notes.md"
cat "${RUNNER_TEMP}/release-notes.md"
- name: Draft GitHub Release
if: startsWith(github.ref, 'refs/tags/')
if: startsWith(github.ref, 'refs/tags/') && ( env.DRY_RUN != 'true' || github.repository == 'componentized/build' )
uses: softprops/action-gh-release@v3
with:
draft: true
body_path: ${{ runner.temp }}/release-notes.md
# the cli archives of a repository with a cli
files: |
components.tar
${{ needs.settings.outputs.cli != '' && 'dist/*' || '' }}
fail_on_unmatched_files: true
token: ${{ secrets.GITHUB_TOKEN }}
56 changes: 56 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Release

on:
release:
types:
- published

jobs:
# the crates of a repository that publishes crates, see PUBLISH_CRATES
crates:
runs-on: ubuntu-latest
permissions:
contents: read
# crates.io trusted publishing exchanges a GitHub OIDC token for a short lived crates.io token
id-token: write
env:
# the repository variable DRY_RUN=true checks the crates with `cargo publish --dry-run` rather
# than publishing them
DRY_RUN: ${{ vars.DRY_RUN == 'true' }}
steps:
- uses: actions/checkout@v7
- uses: actions-rust-lang/setup-rust-toolchain@v2
- name: Read settings
id: settings
run: |
crates=$( make -s print-PUBLISH_CRATES )
[ -z "${crates}" ] && echo "no crates to publish, see PUBLISH_CRATES"
echo "crates=${crates}" >> "${GITHUB_OUTPUT}"
- name: Check crate version matches the release
# the crates inherit the workspace version
if: steps.settings.outputs.crates != ''
run: |
tag="${{ github.event.release.tag_name }}"
workspace_version=$( sed -n '/^\[workspace.package\]/,/^\[/s/^version = "\(.*\)"$/\1/p' Cargo.toml )
if [ "v${workspace_version}" != "${tag}" ] ; then
echo "::error::release ${tag} does not match the workspace version ${workspace_version} in Cargo.toml"
exit 1
fi
- name: Authenticate with crates.io
if: steps.settings.outputs.crates != '' && env.DRY_RUN != 'true'
id: auth
uses: rust-lang/crates-io-auth-action@v1
- name: Publish crates
# cargo publishes a library before the cli that depends on it
if: steps.settings.outputs.crates != ''
run: |
read -ra crates <<< "${CRATES}"
dry_run=()
if [ "${DRY_RUN}" = "true" ] ; then
echo "::notice::a dry run, the repository variable DRY_RUN is true, the crates are not published"
dry_run=(--dry-run)
fi
cargo publish --locked "${dry_run[@]}" "${crates[@]/#/--package=}"
env:
CRATES: ${{ steps.settings.outputs.crates }}
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
Loading
Loading