Repository navigation
Add support for frugal distros packages #18
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
| # Binaries built from a pull request, for testing a change without releasing | |
| # one. Comment on the pull request: | |
| # | |
| # /build every target | |
| # /build x86_64-linux one target | |
| # /build x86_64-linux aarch64-linux a few | |
| # | |
| # The build finishes with a comment linking each artifact and its b3sum. | |
| name: soar pr build | |
| concurrency: | |
| group: "${{ github.workflow }}-${{ github.event.issue.number }}" | |
| cancel-in-progress: true | |
| on: | |
| issue_comment: | |
| types: [created] | |
| permissions: {} | |
| # Only the build job checks out anything, so `gh` has no git remote to read the | |
| # repository from and has to be told which one it is working in. | |
| env: | |
| GH_REPO: ${{ github.repository }} | |
| jobs: | |
| request: | |
| name: Read the request | |
| # A build runs the pull request's own code, including its build scripts, so | |
| # asking for one is as good as pushing to a branch here. Only someone who | |
| # could do that anyway gets to ask. | |
| if: >- | |
| github.event.issue.pull_request && | |
| startsWith(github.event.comment.body, '/build') && | |
| contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| outputs: | |
| matrix: ${{ steps.targets.outputs.matrix }} | |
| sha: ${{ steps.head.outputs.sha }} | |
| short_sha: ${{ steps.head.outputs.short_sha }} | |
| steps: | |
| - name: Choose the targets | |
| id: targets | |
| env: | |
| # Read from the environment rather than interpolated into the script, | |
| # which would run whatever the comment happens to contain. | |
| BODY: ${{ github.event.comment.body }} | |
| run: | | |
| all=(aarch64-linux riscv64-linux x86_64-linux) | |
| triple_for() { | |
| case "$1" in | |
| aarch64-linux) echo aarch64-unknown-linux-musl ;; | |
| riscv64-linux) echo riscv64gc-unknown-linux-musl ;; | |
| x86_64-linux) echo x86_64-unknown-linux-musl ;; | |
| *) return 1 ;; | |
| esac | |
| } | |
| read -ra words <<< "$(printf '%s' "$BODY" | tr -d '\r' | head -n 1)" | |
| # `/buildsomething` is somebody else's command, not a bare `/build`. | |
| if [ "${words[0]}" != "/build" ]; then | |
| echo "not a build request; ignoring" | |
| exit 0 | |
| fi | |
| asked=("${words[@]:1}") | |
| if [ ${#asked[@]} -eq 0 ]; then | |
| asked=("${all[@]}") | |
| fi | |
| entries=() | |
| chosen=() | |
| for name in "${asked[@]}"; do | |
| if ! triple=$(triple_for "$name"); then | |
| echo "unknown=$name" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # A name asked for twice would build twice and collide on upload. | |
| case " ${chosen[*]} " in | |
| *" $name "*) continue ;; | |
| esac | |
| chosen+=("$name") | |
| entries+=("{\"NAME\":\"$name\",\"TARGET\":\"$triple\"}") | |
| done | |
| printf 'matrix=[%s]\n' "$(IFS=,; echo "${entries[*]}")" >> "$GITHUB_OUTPUT" | |
| echo "building: ${chosen[*]}" | |
| - name: Say which targets exist | |
| if: ${{ steps.targets.outputs.unknown }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR: ${{ github.event.issue.number }} | |
| UNKNOWN: ${{ steps.targets.outputs.unknown }} | |
| run: | | |
| gh pr comment "$PR" --body "$(printf 'No target named `%s`. Pick from `aarch64-linux`, `riscv64-linux`, `x86_64-linux`, or say `/build` on its own to build all three.' "$UNKNOWN")" | |
| exit 1 | |
| - name: Acknowledge the comment | |
| if: ${{ steps.targets.outputs.matrix }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| COMMENT: ${{ github.event.comment.url }} | |
| run: gh api --silent -X POST "$COMMENT/reactions" -f content=eyes | |
| - name: Resolve the head commit | |
| id: head | |
| if: ${{ steps.targets.outputs.matrix }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| PR: ${{ github.event.issue.number }} | |
| run: | | |
| sha=$(gh api "repos/$REPO/pulls/$PR" --jq .head.sha) | |
| echo "sha=$sha" >> "$GITHUB_OUTPUT" | |
| echo "short_sha=${sha:0:7}" >> "$GITHUB_OUTPUT" | |
| build: | |
| name: Build ${{ matrix.build.NAME }} | |
| needs: request | |
| if: ${{ needs.request.outputs.matrix }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| build: ${{ fromJSON(needs.request.outputs.matrix) }} | |
| steps: | |
| - name: Checkout the pull request | |
| uses: actions/checkout@v5 | |
| with: | |
| # The commit itself, so the binary matches what the comment reports | |
| # even if the pull request is pushed to while this runs. | |
| ref: ${{ needs.request.outputs.sha }} | |
| persist-credentials: false | |
| - name: Install dependencies | |
| shell: bash | |
| run: | | |
| sudo apt update -y | |
| sudo apt install b3sum findutils file -y | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.build.TARGET }} | |
| # The released binary, rather than a build from git that spends minutes | |
| # compiling before anything of ours does. | |
| - name: Install Cross | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: cross@0.2.5 | |
| - name: Build | |
| env: | |
| RUSTFLAGS: "-C target-feature=+crt-static \ | |
| -C link-self-contained=yes \ | |
| -C link-arg=-Wl,--build-id=none" | |
| # A released cross asks for images tagged with its own version, and | |
| # 0.2.5 is old enough that the riscv64 musl image was never published | |
| # under it. Naming the tag is what keeps that target buildable, and it | |
| # is the same `main` that a cross built from git resolves to, so these | |
| # binaries come out of the images the releases are built in. | |
| CROSS_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_IMAGE: ghcr.io/cross-rs/aarch64-unknown-linux-musl:main | |
| CROSS_TARGET_RISCV64GC_UNKNOWN_LINUX_MUSL_IMAGE: ghcr.io/cross-rs/riscv64gc-unknown-linux-musl:main | |
| CROSS_TARGET_X86_64_UNKNOWN_LINUX_MUSL_IMAGE: ghcr.io/cross-rs/x86_64-unknown-linux-musl:main | |
| run: cross build --release -F self --locked --target "${{ matrix.build.TARGET }}" --jobs="$(($(nproc)+1))" --verbose | |
| - name: Create build artifacts | |
| env: | |
| ARTIFACT: "soar-${{ matrix.build.NAME }}" | |
| shell: bash | |
| run: | | |
| cp "target/${{ matrix.build.TARGET }}/release/soar" "${ARTIFACT}" | |
| b3sum "${ARTIFACT}" > "${ARTIFACT}.b3sum" | |
| printf "\nFile: ${ARTIFACT}\n Type: $(file -b "${ARTIFACT}")\n B3sum: $(cut -d' ' -f1 "${ARTIFACT}.b3sum")\n Size: $(du -bh "${ARTIFACT}" | cut -f1)\n" | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: soar-${{ matrix.build.NAME }}-${{ needs.request.outputs.short_sha }} | |
| path: soar-${{ matrix.build.NAME }}* | |
| report: | |
| name: Report back | |
| needs: [request, build] | |
| if: ${{ !cancelled() && needs.request.outputs.matrix }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: read | |
| pull-requests: write | |
| steps: | |
| # Only for the checksums. A build that produced nothing leaves this with | |
| # nothing to fetch, which is what the comment is for. | |
| - name: Download the artifacts | |
| continue-on-error: true | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: soar-* | |
| path: artifacts | |
| - name: Comment with the result | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR: ${{ github.event.issue.number }} | |
| REPO: ${{ github.repository }} | |
| RUN: ${{ github.run_id }} | |
| SERVER: ${{ github.server_url }} | |
| SHA: ${{ needs.request.outputs.sha }} | |
| SHORT_SHA: ${{ needs.request.outputs.short_sha }} | |
| RESULT: ${{ needs.build.result }} | |
| run: | | |
| run_url="$SERVER/$REPO/actions/runs/$RUN" | |
| commit_url="$SERVER/$REPO/commit/$SHA" | |
| # Each target builds on its own, so the run as a whole failing does | |
| # not mean every target did. The artifacts that exist are listed | |
| # either way. | |
| gh api "repos/$REPO/actions/runs/$RUN/artifacts" \ | |
| --jq '.artifacts[] | [.name, .id] | @tsv' > built.tsv | |
| { | |
| if [ ! -s built.tsv ]; then | |
| printf 'Build of [`%s`](%s) %s, with nothing to show for it. See the [run](%s).\n' \ | |
| "$SHORT_SHA" "$commit_url" "$RESULT" "$run_url" | |
| else | |
| printf 'Built [`%s`](%s):\n\n' "$SHORT_SHA" "$commit_url" | |
| printf '| target | artifact | b3sum |\n| --- | --- | --- |\n' | |
| while IFS=$'\t' read -r name id; do | |
| > | |
| > | |
| sum=$(cut -d' ' -f1 "artifacts/$name/soar-$target.b3sum" 2>/dev/null || true) | |
| printf '| `%s` | [%s](%s/artifacts/%s) | `%s` |\n' \ | |
| "$target" "$name" "$run_url" "$id" "${sum:-unknown}" | |
| done < built.tsv | |
| if [ "$RESULT" != "success" ]; then | |
| printf '\nSome targets did not build. See the [run](%s).\n' "$run_url" | |
| fi | |
| printf '\nDownloading an artifact needs a GitHub login, and it arrives as a zip. ' | |
| printf 'They expire with this repository'"'"'s retention setting.\n' | |
| fi | |
| } > comment.md | |
| gh pr comment "$PR" --body-file comment.md |