feat: add GitHub Actions for CI verification workflow #160
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
| # This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist | |
| # | |
| # Copyright 2022-2024, axodotdev | |
| # SPDX-License-Identifier: MIT or Apache-2.0 | |
| # | |
| # CI that: | |
| # | |
| # * checks for a Git Tag that looks like a release | |
| # * builds artifacts with dist (archives, installers, hashes) | |
| # * uploads those artifacts to temporary workflow zip | |
| # * on success, uploads the artifacts to a GitHub Release | |
| # | |
| # Note that the GitHub Release will be created with a generated | |
| # title/body based on your changelogs. | |
| name: Release | |
| permissions: | |
| "contents": "write" | |
| # This task will run whenever you push a git tag that looks like a version | |
| # like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. | |
| # Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where | |
| # PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION | |
| # must be a Cargo-style SemVer Version (must have at least major.minor.patch). | |
| # | |
| # If PACKAGE_NAME is specified, then the announcement will be for that | |
| # package (erroring out if it doesn't have the given version or isn't dist-able). | |
| # | |
| # If PACKAGE_NAME isn't specified, then the announcement will be for all | |
| # (dist-able) packages in the workspace with that version (this mode is | |
| # intended for workspaces with only one dist-able package, or with all dist-able | |
| # packages versioned/released in lockstep). | |
| # | |
| # If you push multiple tags at once, separate instances of this workflow will | |
| # spin up, creating an independent announcement for each one. However, GitHub | |
| # will hard limit this to 3 tags per commit, as it will assume more tags is a | |
| # mistake. | |
| # | |
| # If there's a prerelease-style suffix to the version, then the release(s) | |
| # will be marked as a prerelease. | |
| on: | |
| pull_request: | |
| push: | |
| tags: | |
| - '**[0-9]+.[0-9]+.[0-9]+*' | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: 'Release tag (e.g., v0.1.2)' | |
| required: true | |
| type: string | |
| jobs: | |
| # Run 'dist plan' (or host) to determine what tasks we need to do | |
| plan: | |
| runs-on: "ubuntu-22.04" | |
| outputs: | |
| val: ${{ steps.plan.outputs.manifest }} | |
| tag: ${{ github.event.inputs.tag || (!github.event.pull_request && github.ref_name) || '' }} | |
| tag-flag: ${{ github.event.inputs.tag && format('--tag={0}', github.event.inputs.tag) || (!github.event.pull_request && format('--tag={0}', github.ref_name)) || '' }} | |
| publishing: ${{ github.event.inputs.tag != '' || (!github.event.pull_request && github.ref_type == 'tag') }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.inputs.tag || github.ref }} | |
| submodules: recursive | |
| persist-credentials: false | |
| - name: Verify version consistency | |
| if: ${{ github.event.inputs.tag != '' }} | |
| run: | | |
| CARGO_VERSION=$(grep "^version" Cargo.toml | sed 's/version = "\(.*\)"/\1/') | |
| TAG_VERSION="${{ github.event.inputs.tag }}" | |
| # Remove 'v' prefix if present | |
| TAG_VERSION_CLEAN=$(echo "$TAG_VERSION" | sed 's/^v//') | |
| echo "Cargo.toml version: $CARGO_VERSION" | |
| echo "Tag version (clean): $TAG_VERSION_CLEAN" | |
| if [ "$CARGO_VERSION" != "$TAG_VERSION_CLEAN" ]; then | |
| echo "⚠️ Version mismatch!" | |
| echo "Cargo.toml has version $CARGO_VERSION but tag is $TAG_VERSION" | |
| echo "You may need to:" | |
| echo "1. Update Cargo.toml to version $TAG_VERSION_CLEAN, or" | |
| echo "2. Use tag v$CARGO_VERSION instead" | |
| exit 1 | |
| else | |
| echo "✅ Version matches between Cargo.toml and tag" | |
| fi | |
| - name: Install dist | |
| # we specify bash to get pipefail; it guards against the `curl` command | |
| # failing. otherwise `sh` won't catch that `curl` returned non-0 | |
| shell: bash | |
| run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.3/cargo-dist-installer.sh | sh" | |
| - name: Cache dist | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: cargo-dist-cache | |
| path: ~/.cargo/bin/dist | |
| # sure would be cool if github gave us proper conditionals... | |
| # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible | |
| # functionality based on whether this is a pull_request, and whether it's from a fork. | |
| # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* | |
| # but also really annoying to build CI around when it needs secrets to work right.) | |
| - id: plan | |
| run: | | |
| echo "Event name: ${{ github.event_name }}" | |
| echo "Ref type: ${{ github.ref_type }}" | |
| echo "Ref name: ${{ github.ref_name }}" | |
| echo "Manual tag input: ${{ github.event.inputs.tag }}" | |
| echo "Current Cargo.toml version:" | |
| grep "^version" Cargo.toml || echo "No version found in Cargo.toml" | |
| echo "Existing tags:" | |
| git tag -l | head -10 || echo "No tags found" | |
| if [ -n "${{ github.event.inputs.tag }}" ]; then | |
| echo "Manual workflow dispatch with tag: ${{ github.event.inputs.tag }}" | |
| dist host --steps=create --tag="${{ github.event.inputs.tag }}" --output-format=json > plan-dist-manifest.json | |
| elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref_type }}" = "tag" ]; then | |
| echo "Automatic tag push with tag: ${{ github.ref_name }}" | |
| dist host --steps=create --tag="${{ github.ref_name }}" --output-format=json > plan-dist-manifest.json | |
| else | |
| echo "Pull request or other event - using plan mode" | |
| dist plan --output-format=json > plan-dist-manifest.json | |
| fi | |
| echo "dist ran successfully" | |
| cat plan-dist-manifest.json | |
| echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" | |
| - name: "Upload dist-manifest.json" | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: artifacts-plan-dist-manifest | |
| path: plan-dist-manifest.json | |
| # Build and packages all the platform-specific things | |
| build-local-artifacts: | |
| name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) | |
| # Let the initial task tell us to not run (currently very blunt) | |
| needs: | |
| - plan | |
| if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} | |
| strategy: | |
| fail-fast: false | |
| # Target platforms/runners are computed by dist in create-release. | |
| # Each member of the matrix has the following arguments: | |
| # | |
| # - runner: the github runner | |
| # - dist-args: cli flags to pass to dist | |
| # - install-dist: expression to run to install dist on the runner | |
| # | |
| # Typically there will be: | |
| # - 1 "global" task that builds universal installers | |
| # - N "local" tasks that build each platform's binaries and platform-specific installers | |
| matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} | |
| runs-on: ${{ matrix.runner }} | |
| container: ${{ matrix.container && matrix.container.image || null }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json | |
| PKG_CONFIG_ALLOW_CROSS: 1 | |
| PKG_CONFIG_ALLOW_SYSTEM_LIBS: 1 | |
| PKG_CONFIG_ALLOW_SYSTEM_CFLAGS: 1 | |
| OPENSSL_NO_VENDOR: 1 | |
| steps: | |
| - name: enable windows longpaths | |
| run: | | |
| git config --global core.longpaths true | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.inputs.tag || github.ref }} | |
| persist-credentials: false | |
| submodules: recursive | |
| - name: Install system dependencies for keyring | |
| if: ${{ contains(matrix.runner, 'ubuntu') && contains(join(matrix.targets, ' '), 'x86_64-unknown-linux-gnu') }} | |
| run: | | |
| if command -v apt-get > /dev/null 2>&1; then | |
| # Ubuntu/Debian - only install for x86_64 Linux builds | |
| sudo apt-get update | |
| sudo apt-get install -y libdbus-1-dev pkg-config build-essential | |
| elif command -v dnf > /dev/null 2>&1; then | |
| # Fedora/RHEL | |
| sudo dnf install -y dbus-devel pkgconf-pkg-config gcc | |
| elif command -v yum > /dev/null 2>&1; then | |
| # CentOS/older RHEL | |
| sudo yum install -y dbus-devel pkgconfig gcc | |
| fi | |
| # Verify installation | |
| pkg-config --exists dbus-1 && echo "✓ dbus-1 found" || echo "✗ dbus-1 not found" | |
| pkg-config --modversion dbus-1 || echo "Could not get dbus-1 version" | |
| - name: Install Rust non-interactively if not already installed | |
| if: ${{ matrix.container }} | |
| run: | | |
| if ! command -v cargo > /dev/null 2>&1; then | |
| curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| # Source the cargo environment | |
| source "$HOME/.cargo/env" | |
| fi | |
| # Ensure we have the target for cross-compilation if needed | |
| if [ -n "${{ matrix.targets }}" ]; then | |
| for target in ${{ join(matrix.targets, ' ') }}; do | |
| rustup target add "$target" || true | |
| done | |
| fi | |
| - name: Install dist | |
| run: ${{ matrix.install_dist.run }} | |
| # Get the dist-manifest | |
| - name: Fetch local artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: artifacts-* | |
| path: target/distrib/ | |
| merge-multiple: true | |
| - name: Install dependencies | |
| run: | | |
| ${{ matrix.packages_install }} | |
| - name: Configure environment for cross-compilation | |
| shell: bash | |
| run: | | |
| # Set environment variables to avoid pkg-config cross-compilation issues | |
| echo "PKG_CONFIG_ALLOW_CROSS=1" >> $GITHUB_ENV | |
| echo "PKG_CONFIG_ALLOW_SYSTEM_LIBS=1" >> $GITHUB_ENV | |
| echo "PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1" >> $GITHUB_ENV | |
| echo "OPENSSL_NO_VENDOR=1" >> $GITHUB_ENV | |
| # Force use of rustls instead of OpenSSL | |
| echo "CARGO_NET_GIT_FETCH_WITH_CLI=true" >> $GITHUB_ENV | |
| # Set comprehensive PKG_CONFIG_PATH only for x86_64 Linux builds that need dbus | |
| if [[ "${{ matrix.runner }}" == *"ubuntu"* ]] && [[ "${{ join(matrix.targets, ' ') }}" == *"x86_64-unknown-linux-gnu"* ]]; then | |
| PKG_PATHS="/usr/lib/pkgconfig:/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig:/usr/lib64/pkgconfig:/usr/local/lib/pkgconfig:/usr/local/lib64/pkgconfig" | |
| echo "PKG_CONFIG_PATH=$PKG_PATHS" >> $GITHUB_ENV | |
| echo "Setting PKG_CONFIG_PATH to: $PKG_PATHS" | |
| # Verify dbus-1.pc exists | |
| for path in $(echo "$PKG_PATHS" | tr ':' ' '); do | |
| if [ -f "$path/dbus-1.pc" ]; then | |
| echo "✓ Found dbus-1.pc in $path" | |
| fi | |
| done | |
| else | |
| echo "Skipping PKG_CONFIG_PATH setup for cross-compilation target: ${{ join(matrix.targets, ' ') }}" | |
| fi | |
| # Verify that we're using rustls-tls in Cargo.toml | |
| if grep -q 'features.*rustls-tls' Cargo.toml; then | |
| echo "✓ Using rustls-tls - no system OpenSSL required" | |
| else | |
| echo "⚠ Warning: rustls-tls not found in Cargo.toml" | |
| fi | |
| - name: Build artifacts | |
| run: | | |
| # Actually do builds and make zips and whatnot | |
| dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json | |
| echo "dist ran successfully" | |
| - id: cargo-dist | |
| name: Post-build | |
| # We force bash here just because github makes it really hard to get values up | |
| # to "real" actions without writing to env-vars, and writing to env-vars has | |
| # inconsistent syntax between shell and powershell. | |
| shell: bash | |
| run: | | |
| # Parse out what we just built and upload it to scratch storage | |
| echo "paths<<EOF" >> "$GITHUB_OUTPUT" | |
| dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT" | |
| echo "EOF" >> "$GITHUB_OUTPUT" | |
| cp dist-manifest.json "$BUILD_MANIFEST_NAME" | |
| - name: "Upload artifacts" | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: artifacts-build-local-${{ join(matrix.targets, '_') }} | |
| path: | | |
| ${{ steps.cargo-dist.outputs.paths }} | |
| ${{ env.BUILD_MANIFEST_NAME }} | |
| # Build and package all the platform-agnostic(ish) things | |
| build-global-artifacts: | |
| needs: | |
| - plan | |
| - build-local-artifacts | |
| runs-on: "ubuntu-22.04" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.inputs.tag || github.ref }} | |
| persist-credentials: false | |
| submodules: recursive | |
| - name: Install cached dist | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: cargo-dist-cache | |
| path: ~/.cargo/bin/ | |
| - run: chmod +x ~/.cargo/bin/dist | |
| # Get all the local artifacts for the global tasks to use (for e.g. checksums) | |
| - name: Fetch local artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: artifacts-* | |
| path: target/distrib/ | |
| merge-multiple: true | |
| - id: cargo-dist | |
| shell: bash | |
| run: | | |
| dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json | |
| echo "dist ran successfully" | |
| # Parse out what we just built and upload it to scratch storage | |
| echo "paths<<EOF" >> "$GITHUB_OUTPUT" | |
| jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" | |
| echo "EOF" >> "$GITHUB_OUTPUT" | |
| cp dist-manifest.json "$BUILD_MANIFEST_NAME" | |
| - name: "Upload artifacts" | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: artifacts-build-global | |
| path: | | |
| ${{ steps.cargo-dist.outputs.paths }} | |
| ${{ env.BUILD_MANIFEST_NAME }} | |
| # Determines if we should publish/announce | |
| host: | |
| needs: | |
| - plan | |
| - build-local-artifacts | |
| - build-global-artifacts | |
| # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) | |
| if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| runs-on: "ubuntu-22.04" | |
| outputs: | |
| val: ${{ steps.host.outputs.manifest }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.inputs.tag || github.ref }} | |
| persist-credentials: false | |
| submodules: recursive | |
| - name: Install cached dist | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: cargo-dist-cache | |
| path: ~/.cargo/bin/ | |
| - run: chmod +x ~/.cargo/bin/dist | |
| # Fetch artifacts from scratch-storage | |
| - name: Fetch artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: artifacts-* | |
| path: target/distrib/ | |
| merge-multiple: true | |
| - id: host | |
| shell: bash | |
| run: | | |
| dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json | |
| echo "artifacts uploaded and released successfully" | |
| cat dist-manifest.json | |
| echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" | |
| - name: "Upload dist-manifest.json" | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| # Overwrite the previous copy | |
| name: artifacts-dist-manifest | |
| path: dist-manifest.json | |
| # Create a GitHub Release while uploading all files to it | |
| - name: "Download GitHub Artifacts" | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: artifacts-* | |
| path: artifacts | |
| merge-multiple: true | |
| - name: Cleanup | |
| run: | | |
| # Remove the granular manifests | |
| rm -f artifacts/*-dist-manifest.json | |
| - name: Create GitHub Release | |
| env: | |
| PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" | |
| ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}" | |
| ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}" | |
| RELEASE_COMMIT: "${{ github.sha }}" | |
| run: | | |
| # Write and read notes from a file to avoid quoting breaking things | |
| echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt | |
| gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* | |
| announce: | |
| needs: | |
| - plan | |
| - host | |
| # use "always() && ..." to allow us to wait for all publish jobs while | |
| # still allowing individual publish jobs to skip themselves (for prereleases). | |
| # "host" however must run to completion, no skipping allowed! | |
| if: ${{ always() && needs.host.result == 'success' }} | |
| runs-on: "ubuntu-22.04" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| submodules: recursive |