build: unify production crates and vendor the patched shell
Assisted-by: SurmAgent:OpenAI
This commit is contained in:
+310
@@ -0,0 +1,310 @@
|
||||
#
|
||||
# Based on https://github.com/release-plz/release-plz/blob/a5043c478d46d051c00e4fbc85036ac22510f07e/.github/workflows/cd.yml
|
||||
#
|
||||
# N.B. scripts/install/install.sh depends on details of this workflow: its path (attestations are
|
||||
# verified against it), the `brush-shell-v*` release tags, the `brush-<target>.tar.gz` and
|
||||
# `brush-<target>.sha256` asset names, the `brush` binary at the archive root, and the glibc
|
||||
# floor of the `*-linux-gnu` builds (2.34, set by the ubuntu-22.04 runners; the script picks the
|
||||
# gnu build only on glibc 2.34+ and the musl build otherwise). Changing any of these breaks the
|
||||
# install script for new releases; update it (and its tests) to match.
|
||||
#
|
||||
# Pushes to `main` also publish canary builds to ghcr.io/reubeno/brush-canary (see the
|
||||
# publish-canary job), which `install.sh --canary` installs from.
|
||||
#
|
||||
|
||||
name: CD # Continuous Deployment
|
||||
run-name: CD${{ github.event_name == 'release' && ' (release)' || (github.event_name == 'push' && github.repository == 'reubeno/brush') && ' (canary)' || ' (dry run)' }}
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
# Manual triggers don't actually publish but dry-run the builds.
|
||||
workflow_dispatch: null
|
||||
|
||||
# Run on PR in dry-run mode to make sure this workflow is still generally
|
||||
# working.
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
# Run on pushes into `main` to publish canary builds.
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
# Only allow one run of the workflow per branch / PR at a time.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_TERM_COLOR: always
|
||||
RUST_BACKTRACE: 1
|
||||
RUSTFLAGS: -D warnings
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
upload-docs:
|
||||
name: "Generate and upload documentation"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
if: github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.event_name == 'push' || (github.event_name == 'release' && github.repository_owner == 'reubeno' && startsWith(github.event.release.tag_name, 'brush-shell-v'))
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: "Checkout repository"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup toolchain install --profile minimal
|
||||
|
||||
- name: "Generate documentation distribution"
|
||||
run: cargo xtask gen docs dist --out brush-docs.tar.gz
|
||||
|
||||
- name: "Upload documentation to release"
|
||||
if: github.event_name == 'release'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
gh release upload ${GITHUB_RELEASE_TAG} \
|
||||
brush-docs.tar.gz \
|
||||
brush-docs.tar.gz.sha256 \
|
||||
brush-docs.tar.gz.sha512
|
||||
|
||||
- name: "Generate artifact attestation"
|
||||
if: github.event_name == 'release'
|
||||
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
||||
with:
|
||||
subject-path: "brush-docs.tar.gz*"
|
||||
|
||||
- name: "Upload documentation artifact (dry-run)"
|
||||
if: github.event_name != 'release'
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: brush-docs
|
||||
path: |
|
||||
brush-docs.tar.gz
|
||||
brush-docs.tar.gz.sha256
|
||||
brush-docs.tar.gz.sha512
|
||||
|
||||
upload-assets:
|
||||
name: ${{ matrix.target }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
if: github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.event_name == 'push' || (github.event_name == 'release' && github.repository_owner == 'reubeno' && startsWith(github.event.release.tag_name, 'brush-shell-v'))
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
# Run all jobs to completion regardless of errors.
|
||||
# This is useful because sometimes we fail to compile for a certain target.
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-2025
|
||||
force_dry_run: false
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
os: ubuntu-22.04
|
||||
force_dry_run: false
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-26-intel
|
||||
force_dry_run: false
|
||||
- target: x86_64-pc-windows-msvc
|
||||
os: windows-2025
|
||||
force_dry_run: false
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-26.04
|
||||
force_dry_run: false
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-22.04
|
||||
force_dry_run: false
|
||||
- target: aarch64-unknown-linux-musl
|
||||
os: ubuntu-26.04
|
||||
force_dry_run: false
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-26
|
||||
force_dry_run: false
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: "Checkout repository"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2.0.0
|
||||
with:
|
||||
cache: false
|
||||
# The cross ARM64 musl linker wrapper emits shell traces as linker warnings.
|
||||
# See https://github.com/cross-rs/cross/issues/1618.
|
||||
build-warnings: ${{ matrix.target == 'aarch64-unknown-linux-musl' && 'warn' || 'deny' }}
|
||||
|
||||
- name: "Setup cross-compiling toolchain"
|
||||
if: startsWith(matrix.os, 'ubuntu') && !contains(matrix.target, '-musl')
|
||||
uses: taiki-e/setup-cross-toolchain-action@12b7ad4acfa95a1476779d6c06699b96ec1691f8 # v1.42.0
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: "Install musl cross tools"
|
||||
if: contains(matrix.target, '-musl')
|
||||
uses: taiki-e/install-action@68a389c788b2cd04016ce8c80ee23b80d2ab3d68 # v2.88.1
|
||||
with:
|
||||
tool: cross
|
||||
|
||||
- name: "Workaround: clean cache"
|
||||
run: cargo clean
|
||||
|
||||
- name: "Install cargo-about"
|
||||
run: cargo install cargo-about --locked --features cli
|
||||
|
||||
- name: "Generate license notices"
|
||||
working-directory: brush-shell
|
||||
run: cargo about generate -o ../THIRD_PARTY_LICENSES.html about.hbs
|
||||
|
||||
- name: "Update build flags"
|
||||
if: endsWith(matrix.target, 'windows-msvc')
|
||||
run: echo "RUSTFLAGS=${RUSTFLAGS} -C target-feature=+crt-static" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: "Build and upload binaries to release"
|
||||
uses: taiki-e/upload-rust-binary-action@f0d45ae91ee7b8ee928de7a9d04d893a08bcbec6 # v1.30.2
|
||||
id: upload-release
|
||||
env:
|
||||
# Canary builds of `main` say so in `brush --version`.
|
||||
BRUSH_VERSION_SUFFIX: ${{ github.event_name == 'push' && '+canary' || '' }}
|
||||
# cross only forwards environment variables it's told about into its build container.
|
||||
CROSS_BUILD_ENV_PASSTHROUGH: BRUSH_VERSION_SUFFIX
|
||||
with:
|
||||
dry-run: ${{ github.event_name != 'release' || matrix.force_dry_run }}
|
||||
bin: brush
|
||||
locked: true
|
||||
target: ${{ matrix.target }}
|
||||
tar: unix
|
||||
zip: windows
|
||||
checksum: sha256,sha512
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
include: "LICENSE,THIRD_PARTY_LICENSES.html"
|
||||
|
||||
- name: "Generate artifact attestation"
|
||||
# Canary builds (pushes to `main`) are attested too, for `install.sh --canary` to verify.
|
||||
if: (github.event_name == 'release' && !matrix.force_dry_run) || (github.event_name == 'push' && github.repository == 'reubeno/brush')
|
||||
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
||||
with:
|
||||
subject-path: "${{ steps.upload-release.outputs.archive }}.*"
|
||||
|
||||
- name: "Upload artifacts (dry-run)"
|
||||
if: github.event_name != 'release' || matrix.force_dry_run
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: brush-${{ matrix.target }}
|
||||
path: "${{ steps.upload-release.outputs.archive }}.*"
|
||||
|
||||
# Publishes builds of `main` to ghcr.io/reubeno/brush-canary, where `install.sh --canary` finds
|
||||
# them. Each target's archive is pushed as a single-file OCI artifact tagged `<target>-<commit>`
|
||||
# and then, once every target's archive is in place, `<target>` (always the newest build).
|
||||
#
|
||||
# N.B. install.sh depends on the package name, the tags, and each manifest having the archive
|
||||
# as its only layer. It also shows users the build's revision and created annotations.
|
||||
publish-canary:
|
||||
name: "Publish canary builds"
|
||||
needs: upload-assets
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
if: github.event_name == 'push' && github.repository == 'reubeno/brush'
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: "Download build artifacts"
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
# brush-<target>, but not brush-docs.
|
||||
pattern: brush-*-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: "Install ORAS"
|
||||
uses: oras-project/setup-oras@5e2f2482b30b0cb3e14e67f89a80c88c659a7c63 # v2.0.3
|
||||
|
||||
- name: "Push to ghcr.io"
|
||||
env:
|
||||
PACKAGE: ghcr.io/reubeno/brush-canary
|
||||
REGISTRY_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
oras login ghcr.io --username "${GITHUB_ACTOR}" --password-stdin <<<"${REGISTRY_TOKEN}"
|
||||
|
||||
created="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
targets=()
|
||||
for archive in brush-*.tar.gz brush-*.zip; do
|
||||
target="${archive#brush-}"
|
||||
target="${target%.tar.gz}"
|
||||
target="${target%.zip}"
|
||||
case "${archive}" in
|
||||
*.zip) media_type="application/zip" ;;
|
||||
*) media_type="application/gzip" ;;
|
||||
esac
|
||||
|
||||
oras push "${PACKAGE}:${target}-${GITHUB_SHA}" \
|
||||
--artifact-type "application/vnd.reubeno.brush.canary.v1" \
|
||||
--annotation "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}" \
|
||||
--annotation "org.opencontainers.image.revision=${GITHUB_SHA}" \
|
||||
--annotation "org.opencontainers.image.created=${created}" \
|
||||
--annotation "org.opencontainers.image.description=Unreleased, unstable builds of brush from main." \
|
||||
"${archive}:${media_type}"
|
||||
targets+=("${target}")
|
||||
done
|
||||
|
||||
# Only now move each `<target>` tag, so it never points at a partially published build.
|
||||
for target in "${targets[@]}"; do
|
||||
oras tag "${PACKAGE}:${target}-${GITHUB_SHA}" "${target}"
|
||||
done
|
||||
|
||||
# Deletes old canary builds. Kept apart from publish-canary so that nothing here can push builds.
|
||||
prune-canary:
|
||||
name: "Prune old canary builds"
|
||||
needs: publish-canary
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: "Delete all but the newest ~100 builds"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
versions="/users/${GITHUB_REPOSITORY_OWNER}/packages/container/brush-canary/versions"
|
||||
|
||||
# Each build adds one version per target (8 of them), so keeping the newest 800 versions
|
||||
# keeps about 100 builds. Versions still tagged with a bare `<target>` (the newest build
|
||||
# of that target) are never deleted.
|
||||
gh api --paginate --slurp "${versions}?per_page=100" |
|
||||
jq -r 'add | sort_by(.created_at) | reverse | .[800:][]
|
||||
| select(.metadata.container.tags | all(test("-[0-9a-f]{40}$")))
|
||||
| .id' |
|
||||
while read -r id; do
|
||||
# GitHub refuses to delete public versions with over 5,000 downloads; don't fail over that.
|
||||
gh api --method DELETE "${versions}/${id}" || echo "::warning::could not delete ${versions}/${id}"
|
||||
done
|
||||
@@ -0,0 +1,138 @@
|
||||
name: "PR Reports"
|
||||
on:
|
||||
# We intentionally run this workflow in the context of the target of a PR; we are careful
|
||||
# an intentional about how we handle the data coming from the PR.
|
||||
workflow_run: # zizmor: ignore[dangerous-triggers]
|
||||
workflows: ["CI"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
report:
|
||||
name: "Report"
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
checks: write
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
# We only run this job if the workflow run that completed was triggered by a pull request.
|
||||
if: github.event.workflow_run.event == 'pull_request'
|
||||
|
||||
steps:
|
||||
- name: Extract PR number and create event file
|
||||
id: get-pr
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
// Find the workflow run that triggered this job.
|
||||
const workflowRun = await github.rest.actions.getWorkflowRun({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: context.payload.workflow_run.id,
|
||||
});
|
||||
|
||||
// Extract the source commit info.
|
||||
const { head_branch, head_sha, head_repository } = workflowRun.data;
|
||||
const head_repo_owner = head_repository.owner.login;
|
||||
core.setOutput('head_branch', head_branch);
|
||||
core.setOutput('head_sha', head_sha);
|
||||
core.setOutput('head_repo', head_repository.full_name);
|
||||
|
||||
// Try to find all PRs from that source.
|
||||
const { data: prs } = await github.rest.pulls.list({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
head: `${head_repo_owner}:${head_branch}`,
|
||||
per_page: 20
|
||||
});
|
||||
|
||||
// Filter PRs to find the one that targets the main branch.
|
||||
// NOTE: This only supports a target branch of 'main'.
|
||||
const filtered_prs = prs.filter(pr => pr.base.ref === 'main');
|
||||
|
||||
if (filtered_prs.length > 0) {
|
||||
const pr = filtered_prs[0];
|
||||
core.setOutput('pr_number', pr.number);
|
||||
|
||||
// Write out a mostly-stubbed event file.
|
||||
const eventData = {
|
||||
pull_request: {
|
||||
head: {
|
||||
sha: head_sha,
|
||||
repo: {
|
||||
full_name: head_repository.full_name
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fs.writeFileSync('event-file.json', JSON.stringify(eventData), 'utf-8');
|
||||
} else {
|
||||
core.setOutput('pr_number', '');
|
||||
}
|
||||
|
||||
- name: Download code coverage reports
|
||||
if: steps.get-pr.outputs.pr_number != ''
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
pattern: codecov-reports*
|
||||
merge-multiple: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
path: reports/
|
||||
|
||||
- name: Download performance reports
|
||||
if: steps.get-pr.outputs.pr_number != ''
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
pattern: perf-reports*
|
||||
merge-multiple: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
path: reports/
|
||||
|
||||
- name: Download test results
|
||||
if: steps.get-pr.outputs.pr_number != ''
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
pattern: test-reports*
|
||||
merge-multiple: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
path: reports/
|
||||
|
||||
- name: Show published reports
|
||||
continue-on-error: true
|
||||
if: steps.get-pr.outputs.pr_number != ''
|
||||
run: |
|
||||
ls -lR reports/
|
||||
|
||||
- name: "Publish test results"
|
||||
uses: EnricoMi/publish-unit-test-result-action@7622c5bf3d112a72ab4fa3951c2aa5723d8ab92d # v2.25.0
|
||||
if: steps.get-pr.outputs.pr_number != ''
|
||||
continue-on-error: true
|
||||
with:
|
||||
commit: ${{ github.event.workflow_run.head_sha }}
|
||||
event_file: event-file.json
|
||||
event_name: ${{ github.event.workflow_run.event }}
|
||||
files: reports/test-results-*.xml
|
||||
|
||||
- name: "Publish available .md reports to PR"
|
||||
uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
|
||||
if: steps.get-pr.outputs.pr_number != ''
|
||||
with:
|
||||
path: reports/*.md
|
||||
number: ${{ steps.get-pr.outputs.pr_number }}
|
||||
+848
@@ -0,0 +1,848 @@
|
||||
name: "CI"
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "release-plz.toml"
|
||||
push:
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- "**.md"
|
||||
- "LICENSE"
|
||||
- "release-plz.toml"
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
# Incremental compilation only pays off across repeated edit-build cycles.
|
||||
# Each CI job builds once, so it would only add compile time and bloat
|
||||
# target/. rust-cache sets this itself, but not every job here uses it.
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUST_BACKTRACE: 1
|
||||
CARGO_TERM_COLOR: always
|
||||
CLICOLOR: 1
|
||||
CLICOLOR_FORCE: 1
|
||||
# The toolchain that every job needing nightly uses: matrix legs that say
|
||||
# `toolchain: "nightly"` resolve to it, and check-deps uses it directly.
|
||||
# (`env` isn't available in `strategy.matrix`, hence the indirection.)
|
||||
#
|
||||
# NIGHTLY PIN (temporary): this is nightly-2026-10-31 (rustc 92b5b8c87,
|
||||
# 2026-10-29) rather than floating `nightly`. The next nightly,
|
||||
# nightly-2026-11-01 (rustc f9ceb4a4d), fails to compile brush-core with
|
||||
# "error[E0391]: cycle detected when computing when `ArithmeticTarget` needs
|
||||
# async drop": a rustc regression on mutually recursive enums held across an
|
||||
# `.await`, likely from rust-lang/rust#164987 (stable and beta are
|
||||
# unaffected). Once a nightly compiles brush again, set this back to
|
||||
# `nightly`. analyze-public-api doesn't use it and stays on floating
|
||||
# `nightly`: rustdoc doesn't hit the regression.
|
||||
NIGHTLY_TOOLCHAIN: nightly-2026-10-31
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
# Only allow one run of the workflow per branch / PR at a time.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Build and upload release binaries for all relevant architectures.
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Build for x86_64/linux target on native host.
|
||||
# N.B. We intentionally pin to Ubuntu 22.04 for now to increase
|
||||
# the range of distros that will be able to run the produced binaries.
|
||||
# Newer release of Ubuntu upgrade glibc to a point not yet supported
|
||||
# by some latest stable versions of distros.
|
||||
- host: "ubuntu-22.04"
|
||||
target: ""
|
||||
cross_command: ""
|
||||
cross_tool_to_install: ""
|
||||
os: "linux"
|
||||
arch: "x86_64"
|
||||
binary_name: "brush"
|
||||
extra_build_args: ""
|
||||
# Build for x86_64/linux using musl as target C runtime.
|
||||
# We don't bother pinning to an earlier version of Ubuntu because
|
||||
# the produced binary will statically link the C runtime anyhow.
|
||||
- host: "ubuntu-26.04"
|
||||
target: "x86_64-unknown-linux-musl"
|
||||
cross_command: "cross"
|
||||
cross_tool_to_install: "cross"
|
||||
os: "linux-musl"
|
||||
arch: "x86_64"
|
||||
binary_name: "brush"
|
||||
extra_build_args: ""
|
||||
# Build for aarch64/macos target on native host.
|
||||
- host: "macos-latest"
|
||||
target: ""
|
||||
cross_command: ""
|
||||
cross_tool_to_install: ""
|
||||
os: "macos"
|
||||
arch: "aarch64"
|
||||
required_tools: ""
|
||||
binary_name: "brush"
|
||||
extra_build_args: ""
|
||||
# Build for aarch64/linux target on x86_64/Linux host.
|
||||
- host: "ubuntu-26.04"
|
||||
target: "aarch64-unknown-linux-gnu"
|
||||
cross_command: "cross"
|
||||
cross_tool_to_install: "cross"
|
||||
os: "linux"
|
||||
arch: "aarch64"
|
||||
required_tools: "gcc-aarch64-linux-gnu"
|
||||
binary_name: "brush"
|
||||
extra_build_args: ""
|
||||
# Build for wasm32 target on x86_64/linux host.
|
||||
- host: "ubuntu-26.04"
|
||||
target: "wasm32-unknown-unknown"
|
||||
cross_command: "cross"
|
||||
cross_tool_to_install: "cross"
|
||||
os: "unknown"
|
||||
arch: "wasm32"
|
||||
required_tools: ""
|
||||
binary_name: "brush.wasm"
|
||||
extra_build_args: ""
|
||||
# Build for WASI-0.3 target on x86_64/linux host. wasm32-wasip3 first
|
||||
# ships prebuilt std in Rust 1.100; until rust-toolchain.toml is at
|
||||
# 1.100+, use nightly. Drop `toolchain` once it is.
|
||||
- host: "ubuntu-26.04"
|
||||
target: "wasm32-wasip3"
|
||||
cross_command: "cargo"
|
||||
cross_tool_to_install: ""
|
||||
os: "wasi-0.3"
|
||||
arch: "wasm32"
|
||||
required_tools: ""
|
||||
binary_name: "brush.wasm"
|
||||
extra_build_args: ""
|
||||
toolchain: "nightly"
|
||||
# Build for x86_64/windows target on x86_64/linux host.
|
||||
# cross 0.2.5's windows-gnu container exits 1, without an error, on ubuntu-26.04.
|
||||
- host: "ubuntu-24.04"
|
||||
target: "x86_64-pc-windows-gnu"
|
||||
cross_command: "cross"
|
||||
cross_tool_to_install: "cross"
|
||||
os: "windows"
|
||||
arch: "x86_64"
|
||||
required_tools: ""
|
||||
binary_name: "brush.exe"
|
||||
extra_build_args: ""
|
||||
# Build for x86_64/android target on x86_64/linux host.
|
||||
- host: "ubuntu-26.04"
|
||||
target: "x86_64-linux-android"
|
||||
cross_command: "cargo ndk"
|
||||
cross_tool_to_install: "cargo-ndk"
|
||||
os: "android"
|
||||
arch: "x86_64"
|
||||
required_tools: ""
|
||||
binary_name: "brush"
|
||||
extra_build_args: ""
|
||||
# x86_64/FreeBSD target on x86_64/linux host, via cross. Compile-check
|
||||
# only (check_only): cross's curated sysroot omits some base-system
|
||||
# libraries (libgeom/libkvm/...) that `sysinfo` links against, so the
|
||||
# final link of a real binary fails. Checking still validates that the
|
||||
# shell compiles for the target; producing a binary is a TODO (would
|
||||
# need a full sysroot with those libraries present).
|
||||
- host: "ubuntu-26.04"
|
||||
target: "x86_64-unknown-freebsd"
|
||||
cross_command: "cross"
|
||||
cross_tool_to_install: "cross"
|
||||
os: "freebsd"
|
||||
arch: "x86_64"
|
||||
required_tools: ""
|
||||
binary_name: "brush"
|
||||
extra_build_args: ""
|
||||
check_only: true
|
||||
# x86_64/NetBSD target on x86_64/linux host, via cross. Compile-check
|
||||
# only for the same reason as FreeBSD above.
|
||||
- host: "ubuntu-26.04"
|
||||
target: "x86_64-unknown-netbsd"
|
||||
cross_command: "cross"
|
||||
cross_tool_to_install: "cross"
|
||||
os: "netbsd"
|
||||
arch: "x86_64"
|
||||
required_tools: ""
|
||||
binary_name: "brush"
|
||||
extra_build_args: ""
|
||||
check_only: true
|
||||
# OpenBSD is tier-3 with no prebuilt std, so even compile-checking it
|
||||
# requires building std from source (-Zbuild-std, hence nightly +
|
||||
# rust-src + no rustup target). panic_abort is built explicitly because
|
||||
# our release profile sets panic = "abort" (plain -Zbuild-std only
|
||||
# builds std + panic_unwind). Compile-check only, like the other BSDs;
|
||||
# check links nothing, so no C cross-compiler/sysroot/linker is needed
|
||||
# (none of the workspace's C-compiling deps are in this default graph).
|
||||
- host: "ubuntu-26.04"
|
||||
target: "x86_64-unknown-openbsd"
|
||||
cross_command: "cargo"
|
||||
cross_tool_to_install: ""
|
||||
os: "openbsd"
|
||||
arch: "x86_64"
|
||||
required_tools: ""
|
||||
binary_name: "brush"
|
||||
extra_build_args: "-Zbuild-std=std,panic_abort"
|
||||
toolchain: "nightly"
|
||||
components: "rust-src"
|
||||
# std is built from source, so there's no prebuilt target to install.
|
||||
skip_rustup_target: true
|
||||
check_only: true
|
||||
|
||||
name: "${{ matrix.check_only && 'Check' || 'Build' }} (${{ matrix.arch }}/${{ matrix.os }})"
|
||||
runs-on: ${{ matrix.host }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up rust toolchain
|
||||
shell: bash
|
||||
env:
|
||||
# Empty -- meaning the toolchain pinned in rust-toolchain.toml -- for
|
||||
# every leg that doesn't set `toolchain` itself.
|
||||
TOOLCHAIN: ${{ matrix.toolchain == 'nightly' && env.NIGHTLY_TOOLCHAIN || matrix.toolchain }}
|
||||
TARGET: ${{ !matrix.skip_rustup_target && matrix.target || '' }}
|
||||
COMPONENTS: ${{ matrix.components }}
|
||||
run: |
|
||||
if [[ -n "${TOOLCHAIN}" ]]; then
|
||||
rustup toolchain install "${TOOLCHAIN}" --profile minimal
|
||||
# A directory override takes precedence over rust-toolchain.toml.
|
||||
rustup override set "${TOOLCHAIN}"
|
||||
else
|
||||
rustup toolchain install --profile minimal
|
||||
fi
|
||||
# Added separately: the nameless `rustup toolchain install` form
|
||||
# (driven by rust-toolchain.toml) silently ignores --target and
|
||||
# --component. These act on the now-active toolchain.
|
||||
if [[ -n "${TARGET}" ]]; then rustup target add "${TARGET}"; fi
|
||||
if [[ -n "${COMPONENTS}" ]]; then rustup component add "${COMPONENTS}"; fi
|
||||
|
||||
- name: Enable cargo cache
|
||||
uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||||
with:
|
||||
key: "${{ matrix.target }}"
|
||||
|
||||
- name: Install additional prerequisite tools
|
||||
if: ${{ matrix.required_tools != '' }}
|
||||
env:
|
||||
REQUIRED_TOOLS: ${{ matrix.required_tools }}
|
||||
run: sudo apt-get update -y && sudo apt-get install -y ${REQUIRED_TOOLS}
|
||||
|
||||
- name: Install cross-compilation toolchain
|
||||
if: ${{ matrix.cross_tool_to_install != '' }}
|
||||
uses: taiki-e/install-action@68a389c788b2cd04016ce8c80ee23b80d2ab3d68 # v2.88.1
|
||||
with:
|
||||
tool: ${{ matrix.cross_tool_to_install }}
|
||||
|
||||
- name: "Build (native)"
|
||||
if: ${{ matrix.target == '' }}
|
||||
env:
|
||||
EXTRA_BUILD_ARGS: ${{ matrix.extra_build_args }}
|
||||
run: cargo build --release --all-targets ${EXTRA_BUILD_ARGS}
|
||||
|
||||
- name: "${{ matrix.check_only && 'Check' || 'Build' }} (cross)"
|
||||
if: ${{ matrix.target != '' }}
|
||||
env:
|
||||
CROSS_COMMAND: ${{ matrix.cross_command }}
|
||||
CARGO_SUBCOMMAND: ${{ matrix.check_only && 'check' || 'build' }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
EXTRA_BUILD_ARGS: ${{ matrix.extra_build_args }}
|
||||
CARGO_NDK_PLATFORM: 24
|
||||
run: ${CROSS_COMMAND} ${CARGO_SUBCOMMAND} --release --target=${TARGET} ${EXTRA_BUILD_ARGS}
|
||||
|
||||
- name: "Upload binaries"
|
||||
# check_only targets don't produce a binary to upload.
|
||||
if: ${{ !matrix.check_only }}
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: binaries-${{ matrix.arch }}-${{ matrix.os }}
|
||||
path: target/${{ matrix.target }}/release/${{ matrix.binary_name }}
|
||||
|
||||
- name: "Upload integration test binaries"
|
||||
if: ${{ matrix.target == '' }}
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: integration-tests-${{ matrix.arch }}-${{ matrix.os }}
|
||||
path: |
|
||||
target/${{ matrix.target }}/release/deps/brush_*_tests-*
|
||||
!**/*.d
|
||||
|
||||
# Test functional correctness
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- host: "ubuntu-26.04"
|
||||
variant: "linux-x86_64"
|
||||
artifact_suffix: "linux-x86_64"
|
||||
name_suffix: "(linux/x86_64)"
|
||||
homebrew_supported: true
|
||||
coverage: true
|
||||
coverage_min_percent: 70
|
||||
|
||||
- host: "ubuntu-26.04-arm"
|
||||
variant: "linux-aarch64"
|
||||
artifact_suffix: "linux-aarch64"
|
||||
name_suffix: "(linux/aarch64)"
|
||||
homebrew_supported: false
|
||||
coverage: true
|
||||
coverage_min_percent: 70
|
||||
|
||||
- host: "macos-latest"
|
||||
variant: "macos"
|
||||
artifact_suffix: "-macos"
|
||||
name_suffix: "(macOS)"
|
||||
homebrew_supported: true
|
||||
coverage: true
|
||||
coverage_min_percent: 70
|
||||
|
||||
- host: "windows-2025"
|
||||
variant: "windows"
|
||||
artifact_suffix: "-windows"
|
||||
name_suffix: "(windows)"
|
||||
homebrew_supported: false
|
||||
coverage: true
|
||||
coverage_min_percent: 20
|
||||
|
||||
- host: "ubuntu-26.04"
|
||||
variant: "wasi"
|
||||
artifact_suffix: "-wasi"
|
||||
name_suffix: "(wasm32/wasi-0.3)"
|
||||
homebrew_supported: false
|
||||
coverage: false
|
||||
# See the wasm32-wasip3 build leg: nightly until Rust 1.100.
|
||||
toolchain: "nightly"
|
||||
extra_rust_targets: "wasm32-wasip3"
|
||||
wasi_runtime: "wasmtime"
|
||||
xtask_test_args: "--wasi"
|
||||
|
||||
name: "Test ${{ matrix.name_suffix }}"
|
||||
runs-on: ${{ matrix.host }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up rust toolchain
|
||||
env:
|
||||
# Empty -- meaning the toolchain pinned in rust-toolchain.toml -- for
|
||||
# every leg that doesn't set `toolchain` itself.
|
||||
TOOLCHAIN: ${{ matrix.toolchain == 'nightly' && env.NIGHTLY_TOOLCHAIN || matrix.toolchain }}
|
||||
TARGETS: ${{ matrix.extra_rust_targets }}
|
||||
# Targets/components are added separately: the nameless
|
||||
# `rustup toolchain install` form ignores --target and --component.
|
||||
run: |
|
||||
if [[ -n "${TOOLCHAIN}" ]]; then
|
||||
rustup toolchain install "${TOOLCHAIN}" --profile minimal
|
||||
# A directory override takes precedence over rust-toolchain.toml.
|
||||
rustup override set "${TOOLCHAIN}"
|
||||
else
|
||||
rustup toolchain install --profile minimal
|
||||
fi
|
||||
rustup component add llvm-tools-preview
|
||||
if [[ -n "${TARGETS}" ]]; then rustup target add "${TARGETS}"; fi
|
||||
|
||||
- name: Enable cargo cache
|
||||
uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@68a389c788b2cd04016ce8c80ee23b80d2ab3d68 # v2.88.1
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- name: Install cargo-llvm-cov
|
||||
if: ${{ matrix.coverage }}
|
||||
uses: taiki-e/install-action@68a389c788b2cd04016ce8c80ee23b80d2ab3d68 # v2.88.1
|
||||
with:
|
||||
tool: cargo-llvm-cov
|
||||
|
||||
- name: Install WASI runtime
|
||||
if: ${{ matrix.wasi_runtime }}
|
||||
uses: taiki-e/install-action@68a389c788b2cd04016ce8c80ee23b80d2ab3d68 # v2.88.1
|
||||
with:
|
||||
tool: ${{ matrix.wasi_runtime }}
|
||||
|
||||
- name: Set up Homebrew
|
||||
if: ${{ matrix.homebrew_supported }}
|
||||
id: set-up-homebrew
|
||||
# We ignore the stale-action-refs check because this action does not have any releases or tags.
|
||||
# We're forced to pick a specific commit.
|
||||
uses: Homebrew/actions/setup-homebrew@c262868c096d909fc278daa87ed9772967331ea3 # zizmor: ignore[stale-action-refs]
|
||||
with:
|
||||
stable: true
|
||||
|
||||
- name: "Install recent bash for tests"
|
||||
if: ${{ matrix.homebrew_supported }}
|
||||
run: |
|
||||
set -x
|
||||
|
||||
# Install it
|
||||
brew install bash
|
||||
|
||||
# Find the path
|
||||
BASH_PATH="$(brew --prefix bash)/bin/bash"
|
||||
|
||||
# Log what we're using
|
||||
echo "Using bash from: ${BASH_PATH}"
|
||||
echo "bash version:"
|
||||
${BASH_PATH} --version
|
||||
echo "BASH_PATH=${BASH_PATH}">>$GITHUB_ENV
|
||||
|
||||
- name: "Download recent bash-completion sources for tests"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: "scop/bash-completion"
|
||||
ref: "2.17.0"
|
||||
path: "bash-completion"
|
||||
|
||||
- name: "Setup bash-completion"
|
||||
run: echo "BASH_COMPLETION_PATH=${GITHUB_WORKSPACE}/bash-completion/bash_completion">>$GITHUB_ENV
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
VARIANT: ${{ matrix.variant }}
|
||||
run: |
|
||||
args="${{ matrix.xtask_test_args || '' }}"
|
||||
args="${args} --results-output ./test-results-${VARIANT}.xml"
|
||||
if [[ "${{ matrix.coverage }}" == "true" ]]; then
|
||||
args="${args} --coverage --coverage-output ./codecov-${VARIANT}.xml"
|
||||
fi
|
||||
cargo xtask test integration ${args}
|
||||
|
||||
- name: "Upload test results"
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
if: always()
|
||||
with:
|
||||
name: test-reports${{ matrix.artifact_suffix }}
|
||||
path: test-results-*.xml
|
||||
|
||||
- name: "Generate code coverage report"
|
||||
uses: clearlyip/code-coverage-report-action@110af9d4ec87b6706f182fd90e3102af9e5203bf # v7.0.0
|
||||
if: ${{ always() && matrix.coverage }}
|
||||
id: "code_coverage_report"
|
||||
with:
|
||||
artifact_download_workflow_names: "CI"
|
||||
artifact_name: coverage-%name%${{ matrix.artifact_suffix }}
|
||||
filename: codecov-${{ matrix.variant }}.xml
|
||||
overall_coverage_fail_threshold: ${{ matrix.coverage_min_percent }}
|
||||
only_list_changed_files: ${{ github.event_name == 'pull_request' }}
|
||||
fail_on_negative_difference: true
|
||||
negative_difference_by: "overall"
|
||||
negative_difference_threshold: 5
|
||||
|
||||
- name: "Upload code coverage report"
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
if: always()
|
||||
with:
|
||||
name: codecov-reports${{ matrix.artifact_suffix }}
|
||||
path: code-coverage-results.md
|
||||
|
||||
# Static analysis of the code.
|
||||
check:
|
||||
name: "Source code checks"
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
# Test the pinned toolchain as well as MSRV. The "stable" leg means the
|
||||
# toolchain pinned in rust-toolchain.toml, which Dependabot keeps at the
|
||||
# latest stable release; the label is kept so check names don't change.
|
||||
rust-version: ["stable", "1.88.0"]
|
||||
# Run checks on Linux, Windows, and macOS.
|
||||
os: ["ubuntu-26.04", "windows-2025", "macos-latest"]
|
||||
# Extra args for the build check, per toolchain. Spelled out for both
|
||||
# legs so it's clear that stable compiles every crate in the workspace.
|
||||
include:
|
||||
- rust-version: "stable"
|
||||
build_check_args: ""
|
||||
# The application-tier crates declare a higher MSRV than the
|
||||
# workspace-wide one, so they can't be built by the MSRV toolchain;
|
||||
# see docs/reference/msrv-policy.md.
|
||||
- rust-version: "1.88.0"
|
||||
build_check_args: "--workspace-msrv"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up rust toolchain (${{ matrix.rust-version }})
|
||||
shell: bash
|
||||
env:
|
||||
MSRV: ${{ matrix.rust-version != 'stable' && matrix.rust-version || '' }}
|
||||
run: |
|
||||
if [[ -n "${MSRV}" ]]; then
|
||||
rustup toolchain install "${MSRV}" --profile minimal --component clippy,rustfmt
|
||||
# A directory override takes precedence over rust-toolchain.toml.
|
||||
rustup override set "${MSRV}"
|
||||
else
|
||||
rustup toolchain install --profile minimal
|
||||
fi
|
||||
|
||||
- name: Enable cargo cache
|
||||
uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||||
|
||||
- name: Format check
|
||||
run: cargo xtask check fmt
|
||||
|
||||
- name: Check
|
||||
run: cargo xtask check build ${{ matrix.build_check_args }}
|
||||
|
||||
- name: Crate structure check
|
||||
run: cargo xtask check crates
|
||||
|
||||
- name: Clippy check
|
||||
if: matrix.rust-version == 'stable'
|
||||
run: cargo xtask check lint
|
||||
|
||||
# Check for unneeded dependencies.
|
||||
check-deps:
|
||||
name: "Check for unneeded dependencies"
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Overriding (not just installing) nightly means xtask builds with it too,
|
||||
# so the pinned toolchain doesn't have to be installed as well.
|
||||
- name: Set up nightly rust toolchain
|
||||
run: |
|
||||
rustup toolchain install "${NIGHTLY_TOOLCHAIN}" --profile minimal
|
||||
rustup override set "${NIGHTLY_TOOLCHAIN}"
|
||||
|
||||
- name: Install cargo-udeps
|
||||
uses: taiki-e/install-action@68a389c788b2cd04016ce8c80ee23b80d2ab3d68 # v2.88.1
|
||||
with:
|
||||
tool: cargo-udeps
|
||||
|
||||
# xtask runs `cargo +<toolchain> udeps`, which takes precedence over the override.
|
||||
- name: Check for unused dependencies
|
||||
run: cargo xtask check unused-deps --toolchain "${NIGHTLY_TOOLCHAIN}"
|
||||
|
||||
# Check that generated schemas are up-to-date
|
||||
check-schemas:
|
||||
name: "Check generated schemas"
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up rust toolchain
|
||||
run: rustup toolchain install --profile minimal
|
||||
|
||||
- name: Enable cargo cache
|
||||
uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||||
|
||||
- name: Check generated schemas
|
||||
run: cargo xtask check schemas
|
||||
|
||||
# Analyze public API surface
|
||||
analyze-public-api:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: "Analyze public API"
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Overriding (not just installing) nightly means xtask builds with it too,
|
||||
# so the pinned toolchain doesn't have to be installed as well.
|
||||
- name: Set up nightly rust toolchain
|
||||
run: |
|
||||
rustup toolchain install nightly --profile minimal
|
||||
rustup override set nightly
|
||||
|
||||
- name: Install cargo-public-api
|
||||
uses: taiki-e/install-action@68a389c788b2cd04016ce8c80ee23b80d2ab3d68 # v2.88.1
|
||||
with:
|
||||
tool: cargo-public-api
|
||||
|
||||
- name: Set up branches
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch origin main
|
||||
|
||||
- name: Compare public APIs with main
|
||||
run: cargo xtask analyze public-api --base origin/main --output-dir reports
|
||||
|
||||
- name: Upload test report
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: test-reports-public-api
|
||||
path: reports/*.md
|
||||
|
||||
# Performance analysis of the code.
|
||||
benchmark:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: "Benchmarks"
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Checkout PR sources
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
path: pr
|
||||
|
||||
- name: Checkout main sources
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
path: main
|
||||
ref: main
|
||||
|
||||
# main/ is built with its own pinned toolchain, which differs from the PR's
|
||||
# only when the PR changes rust-toolchain.toml.
|
||||
- name: Set up rust toolchain
|
||||
run: rustup toolchain install --profile minimal
|
||||
working-directory: pr
|
||||
|
||||
- name: Enable cargo cache
|
||||
uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||||
with:
|
||||
workspaces: |
|
||||
./pr
|
||||
./main
|
||||
|
||||
# Criterion keeps past results under target/criterion by default, which the cargo cache
|
||||
# saves; a later run on the branch then fails to load them as a baseline, and its errors
|
||||
# break the bencher-format output. CRITERION_HOME keeps results out of the cache.
|
||||
- name: Performance analysis on PR
|
||||
run: cargo xtask analyze bench --output benchmarks.txt
|
||||
working-directory: pr
|
||||
env:
|
||||
CRITERION_HOME: ${{ runner.temp }}/criterion-pr
|
||||
|
||||
- name: Performance analysis on main
|
||||
run: cargo bench --workspace --benches -- --output-format bencher | tee benchmarks.txt
|
||||
working-directory: main
|
||||
env:
|
||||
CRITERION_HOME: ${{ runner.temp }}/criterion-main
|
||||
|
||||
- name: Compare benchmark results
|
||||
run: |
|
||||
./pr/scripts/compare-benchmark-results.py -b main/benchmarks.txt -t pr/benchmarks.txt >benchmark-results.md
|
||||
|
||||
- name: Upload performance results
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: perf-reports
|
||||
path: |
|
||||
pr/benchmarks.txt
|
||||
main/benchmarks.txt
|
||||
benchmark-results.md
|
||||
|
||||
# Run real applications' own shell-integration suites (e2e/) against the release binary.
|
||||
# The default adapter set plus bash-completion, which is report-only: its failures show as a pass
|
||||
# rate in the dashboard without failing the job. blesh and bash-tests are opt-in (see
|
||||
# e2e/README.md).
|
||||
e2e-tests:
|
||||
name: "External tests / e2e suites"
|
||||
runs-on: ubuntu-26.04
|
||||
needs: build
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download prebuilt brush binaries
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: "binaries-x86_64-linux"
|
||||
path: "binaries"
|
||||
|
||||
- name: Setup downloads
|
||||
run: chmod +x binaries/*
|
||||
|
||||
- name: Set up rust toolchain
|
||||
run: rustup toolchain install --profile minimal
|
||||
|
||||
- name: Enable cargo cache
|
||||
uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||||
|
||||
- name: Run e2e suites
|
||||
run: |
|
||||
cargo xtask test --brush-path "${GITHUB_WORKSPACE}/binaries/brush" \
|
||||
e2e --with bash-completion --timeout 600 \
|
||||
--results-dir e2e-results --summary-output e2e-results/dashboard.md
|
||||
|
||||
# The dashboard goes to the run page as well as the PR comment; failing adapters' logs go
|
||||
# to the job log, so the comment stays short and the details are one click away.
|
||||
- name: Publish dashboard
|
||||
if: always()
|
||||
run: |
|
||||
dashboard=e2e-results/dashboard.md
|
||||
[[ -f "${dashboard}" ]] || exit 0
|
||||
# The blank line ends the last <details> block; without it the link stays raw HTML.
|
||||
printf '\n📎 [Run logs](%s/%s/actions/runs/%s) · artifact `e2e-logs`\n' \
|
||||
"${GITHUB_SERVER_URL}" "${GITHUB_REPOSITORY}" "${GITHUB_RUN_ID}" >> "${dashboard}"
|
||||
cat "${dashboard}" >> "${GITHUB_STEP_SUMMARY}"
|
||||
cp "${dashboard}" test-results-e2e.md
|
||||
for log in e2e-results/run-*/*/log.txt; do
|
||||
app=$(basename "$(dirname "${log}")")
|
||||
if grep -qE "^\| (❌|💥) \| ${app} \|" "${dashboard}"; then
|
||||
echo "::group::${app} log.txt"
|
||||
cat "${log}"
|
||||
echo "::endgroup::"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Upload dashboard
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: test-reports-e2e
|
||||
path: test-results-e2e.md
|
||||
|
||||
- name: Upload logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: e2e-logs
|
||||
path: e2e-results/
|
||||
retention-days: 7
|
||||
|
||||
# Test release binary on a variety of OS platforms.
|
||||
os-tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# N.B. We don't include Ubuntu because it's already covered by the initial test job.
|
||||
- container: "archlinux:latest"
|
||||
description: "Arch Linux/latest"
|
||||
prereqs_command: "pacman -Sy --noconfirm bash-completion iputils grep less sed util-linux which"
|
||||
|
||||
- container: "mcr.microsoft.com/azurelinux/core:4"
|
||||
description: "Azure Linux/4"
|
||||
prereqs_command: "dnf install -y bash-completion ca-certificates git iputils grep less procps-ng sed util-linux which"
|
||||
|
||||
- container: "debian:testing"
|
||||
description: "Debian/testing"
|
||||
prereqs_command: "apt-get update -y && apt-get install -y bash-completion bsdmainutils iputils-ping grep less sed which"
|
||||
|
||||
- container: "fedora:latest"
|
||||
description: "Fedora/latest"
|
||||
prereqs_command: "dnf install -y bash-completion iputils grep less sed util-linux which"
|
||||
|
||||
- container: "nixos/nix:latest"
|
||||
description: "NixOS/latest"
|
||||
prereqs_command: "nix --extra-experimental-features nix-command --extra-experimental-features flakes profile install nixpkgs#gnused nixpkgs#hexdump nixpkgs#bash-completion"
|
||||
|
||||
# NOTE: We use Tumbleweed to make sure we get a recent enough version of bash.
|
||||
- container: "opensuse/tumbleweed:latest"
|
||||
description: "openSUSE Tumbleweed/latest"
|
||||
prereqs_command: "zypper install -y bash-completion git which"
|
||||
|
||||
name: "OS target tests (${{ matrix.description }})"
|
||||
runs-on: ubuntu-26.04
|
||||
# The floating tags in the matrix above are deliberate: the whole point of
|
||||
# this job is to catch breakage against *current* distro releases, so
|
||||
# digest-pinning these images would defeat it.
|
||||
container: ${{ matrix.container }} # zizmor: ignore[unpinned-images]
|
||||
needs: build
|
||||
env:
|
||||
# Reference: https://github.com/Azure/azure-cli/issues/29835
|
||||
GNUPGHOME: /root/.gnupg
|
||||
steps:
|
||||
# Install prerequisites *first*; may need some of them for checkout itself.
|
||||
- name: Install prerequisites
|
||||
if: ${{ matrix.prereqs_command != '' }}
|
||||
env:
|
||||
PREREQS_COMMAND: ${{ matrix.prereqs_command }}
|
||||
run: bash -c "${PREREQS_COMMAND}"
|
||||
|
||||
# Workaround path issues on NixOS
|
||||
- name: "Apply workarounds: NixOS"
|
||||
if: ${{ matrix.container == 'nixos/nix:latest' }}
|
||||
run: |
|
||||
set -x
|
||||
|
||||
# Allow use of experimental features to avoid nix from complaining.
|
||||
export NIX_CONFIG="extra-experimental-features = nix-command flakes"
|
||||
|
||||
# The nixos/nix image ships no /etc/os-release (unlike a full NixOS install
|
||||
# and every other container we test against). Provide one so the test harness
|
||||
# can identify the OS, which it relies on for tests' `incompatible_os` tags.
|
||||
printf 'NAME=NixOS\nID=nixos\nPRETTY_NAME="NixOS (nix container)"\n' > /etc/os-release
|
||||
|
||||
# Hacks to get the mounted nodejs by github actions work as it's dynamically linked
|
||||
# https://github.com/actions/checkout/issues/334#issuecomment-716068696
|
||||
nix build --no-link --max-jobs 2 --cores 0 'nixpkgs#stdenv.cc.cc.lib' 'nixpkgs#glibc'
|
||||
echo "LD_LIBRARY_PATH=$(nix path-info 'nixpkgs#stdenv.cc.cc.lib')/lib" >> "$GITHUB_ENV"
|
||||
ln -s "$(nix path-info 'nixpkgs#glibc' --recursive | grep glibc | grep -v bin)/lib64" /lib64
|
||||
|
||||
# Provide alternate path for bash-completion
|
||||
echo "BASH_COMPLETION_PATH=$(nix path-info 'nixpkgs#bash-completion')/share/bash-completion/bash_completion">>"$GITHUB_ENV"
|
||||
|
||||
# Forcibly add system paths
|
||||
echo "BRUSH_TEST_PATH_VAR=$PATH">>"$GITHUB_ENV"
|
||||
|
||||
# Checkout sources for YAML-based test cases
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
path: sources
|
||||
|
||||
- name: Download prebuilt brush binaries
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: binaries-x86_64-linux
|
||||
path: binaries
|
||||
|
||||
- name: Download integration test binaries
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: integration-tests-x86_64-linux
|
||||
path: binaries
|
||||
|
||||
- name: Setup downloads
|
||||
run: |
|
||||
# N.B. Can't use -o pipefail because it's not supported on Debian.
|
||||
set -eux
|
||||
chmod +x binaries/*
|
||||
ls -l -R sources/brush-shell/tests
|
||||
ls -l binaries
|
||||
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
export BRUSH_PATH=$PWD/binaries/brush
|
||||
export BRUSH_VERBOSE=true
|
||||
|
||||
result=0
|
||||
for test_name in binaries/*tests*; do
|
||||
if [[ ${test_name} == *compat* ]]; then
|
||||
export BRUSH_TEST_CASES=$PWD/sources/brush-shell/tests/cases/compat
|
||||
elif [[ ${test_name} == *integration* ]]; then
|
||||
export BRUSH_TEST_CASES=$PWD/sources/brush-shell/tests/cases/brush
|
||||
elif [[ ${test_name} == *interactive* || ${test_name} == *version* ]]; then
|
||||
# TODO(tests): Re-enable these tests.
|
||||
echo "WARNING: skipping test: ${test_name}"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Running test: ${test_name}"
|
||||
chmod +x ${test_name}
|
||||
${test_name} || result=$?
|
||||
done
|
||||
|
||||
exit ${result}
|
||||
@@ -0,0 +1,55 @@
|
||||
name: "CodeQL Advanced"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
schedule:
|
||||
- cron: "35 1 * * 3"
|
||||
|
||||
permissions: {}
|
||||
|
||||
# Only allow one run of the workflow per branch / PR at a time.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
- language: python
|
||||
build-mode: none
|
||||
- language: rust
|
||||
build-mode: none
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
queries: security-extended,security-and-quality
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
@@ -0,0 +1,72 @@
|
||||
name: "Devcontainer"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
paths:
|
||||
- ".devcontainer/**"
|
||||
|
||||
permissions: {}
|
||||
|
||||
# Only allow one run of the workflow per branch / PR at a time.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "Build devcontainer"
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Pre-build dev container image
|
||||
uses: devcontainers/ci@513af61f4de4f75d37e4438f184ba4358f0fc1ca # v0.3.1900000450
|
||||
with:
|
||||
imageName: ghcr.io/reubeno/brush/devcontainer
|
||||
imageTag: latest
|
||||
cacheFrom: ghcr.io/reubeno/brush/devcontainer
|
||||
push: never
|
||||
|
||||
build_and_publish:
|
||||
name: "Build and publish devcontainer"
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Pre-build dev container image
|
||||
uses: devcontainers/ci@513af61f4de4f75d37e4438f184ba4358f0fc1ca # v0.3.1900000450
|
||||
with:
|
||||
imageName: ghcr.io/reubeno/brush/devcontainer
|
||||
imageTag: latest
|
||||
cacheFrom: ghcr.io/reubeno/brush/devcontainer
|
||||
push: filter
|
||||
refFilterForPush: refs/heads/main
|
||||
eventFilterForPush: push
|
||||
@@ -0,0 +1,59 @@
|
||||
name: "Docs"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
CARGO_TERM_COLOR: always
|
||||
CLICOLOR: 1
|
||||
CLICOLOR_FORCE: 1
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Only allow one run of the workflow per branch / PR at a time.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Generate docs content
|
||||
generate-docs:
|
||||
name: "Generate usage docs"
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up rust toolchain
|
||||
run: rustup toolchain install --profile minimal
|
||||
|
||||
- name: Enable cargo cache
|
||||
uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||||
|
||||
- name: Create dirs
|
||||
run: mkdir -p ./md ./man
|
||||
|
||||
- name: Build markdown info
|
||||
run: cargo xtask gen docs markdown --out ./md/brush.md
|
||||
|
||||
- name: Upload markdown docs
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: docs-markdown
|
||||
path: md
|
||||
|
||||
- name: Build man pages
|
||||
run: cargo xtask gen docs man --output-dir ./man
|
||||
|
||||
- name: Upload man pages
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: docs-man
|
||||
path: man
|
||||
@@ -0,0 +1,72 @@
|
||||
# Every hook defined in .pre-commit-config.yaml: file hygiene, spelling,
|
||||
# workflow analysis, link checking, the cargo-deny audit, and rustfmt. Runs the
|
||||
# same `cargo xtask check hooks` a contributor runs.
|
||||
#
|
||||
# This is its own workflow rather than a job in ci.yaml so that it can run on
|
||||
# every change -- including docs-only pushes, which ci.yaml skips -- without
|
||||
# dragging the build and test matrix along with it.
|
||||
name: "Pre-commit hooks"
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUST_BACKTRACE: 1
|
||||
CARGO_TERM_COLOR: always
|
||||
CLICOLOR: 1
|
||||
CLICOLOR_FORCE: 1
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Only allow one run of the workflow per branch / PR at a time.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
hooks:
|
||||
name: "Pre-commit hooks"
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up rust toolchain
|
||||
run: rustup toolchain install --profile minimal
|
||||
|
||||
- name: Enable cargo cache
|
||||
uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||||
|
||||
- name: Install prek
|
||||
uses: taiki-e/install-action@68a389c788b2cd04016ce8c80ee23b80d2ab3d68 # v2.88.1
|
||||
with:
|
||||
tool: prek
|
||||
|
||||
- name: Cache hook environments
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.cache/prek
|
||||
key: prek-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
restore-keys: prek-${{ runner.os }}-
|
||||
|
||||
- name: Prepare hooks
|
||||
run: prek prepare-hooks
|
||||
env:
|
||||
# Pass through the token in case 'cargo binstall' needs it
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run pre-commit hooks
|
||||
run: cargo xtask check hooks
|
||||
|
||||
# The fixing hooks rewrite files in place and then fail. Show what they
|
||||
# changed so the required fix is visible in the log.
|
||||
- name: Show changes made by hooks
|
||||
if: failure()
|
||||
run: git diff
|
||||
@@ -0,0 +1,77 @@
|
||||
name: "Install script"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "scripts/install/**"
|
||||
- ".github/workflows/install-script.yaml"
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- "scripts/install/**"
|
||||
- ".github/workflows/install-script.yaml"
|
||||
# Weekly, to catch release changes (e.g., renamed assets) that break the script.
|
||||
schedule:
|
||||
- cron: "0 6 * * 1"
|
||||
workflow_dispatch: null
|
||||
|
||||
permissions: {}
|
||||
|
||||
# Only allow one run of the workflow per branch / PR at a time.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: "Test (${{ matrix.name }})"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
attestations: read # needed for `gh attestation verify`
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: "linux-x86_64"
|
||||
os: ubuntu-26.04
|
||||
- name: "linux-aarch64"
|
||||
os: ubuntu-26.04-arm
|
||||
- name: "macos-aarch64"
|
||||
os: macos-latest
|
||||
- name: "macos-x86_64"
|
||||
os: macos-26-intel
|
||||
# musl + busybox userland, no gh.
|
||||
- name: "alpine"
|
||||
os: ubuntu-26.04
|
||||
container: "alpine:3"
|
||||
prepare: "apk add --no-cache bash curl"
|
||||
# glibc too old for the gnu build; exercises the musl fallback.
|
||||
- name: "almalinux-8"
|
||||
os: ubuntu-26.04
|
||||
container: "almalinux:8"
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container }}
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: "Install container prerequisites"
|
||||
if: ${{ matrix.prepare }}
|
||||
shell: sh # bash may not be installed yet
|
||||
run: ${{ matrix.prepare }}
|
||||
|
||||
- name: "Checkout repository"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
||||
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: uvx 'pytest==8.*' -p no:cacheprovider -v scripts/install/tests
|
||||
@@ -0,0 +1,42 @@
|
||||
name: "Shell scripts"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "**.sh"
|
||||
- ".github/workflows/shellcheck.yaml"
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- "**.sh"
|
||||
- ".github/workflows/shellcheck.yaml"
|
||||
workflow_dispatch: null
|
||||
|
||||
permissions: {}
|
||||
|
||||
# Only allow one run of the workflow per branch / PR at a time.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
shellcheck:
|
||||
name: "shellcheck"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 10
|
||||
|
||||
# Pinned, since findings differ across shellcheck versions.
|
||||
container: koalaman/shellcheck-alpine:v0.11.0@sha256:9955be09ea7f0dbf7ae942ac1f2094355bb30d96fffba0ec09f5432207544002
|
||||
|
||||
steps:
|
||||
- name: "Checkout repository"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run shellcheck"
|
||||
run: find . -name '*.sh' -not -path './.git/*' -exec shellcheck {} +
|
||||
@@ -0,0 +1,45 @@
|
||||
#
|
||||
# This workflow was based on https://docs.zizmor.sh/integrations/
|
||||
#
|
||||
|
||||
name: GitHub Actions Security Analysis with zizmor 🌈
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["**"]
|
||||
|
||||
permissions: {}
|
||||
|
||||
# N.B. This is the *online* zizmor pass: it runs the audits that need network
|
||||
# access and uploads SARIF to code scanning for stateful triage in the Security
|
||||
# tab, and it deliberately does not fail the build.
|
||||
#
|
||||
# The complementary offline pass -- which does fail, and fails fast -- is the
|
||||
# `zizmor` hook in .pre-commit-config.yaml, run in CI by hooks.yaml and locally
|
||||
# by `cargo xtask check hooks workflows`. The two are not redundant; do not
|
||||
# collapse them without checking which audits each one actually runs.
|
||||
|
||||
# Only allow one run of the workflow per branch / PR at a time.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
zizmor:
|
||||
name: zizmor latest via zizmor-action
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
security-events: write # needed for SARIF uploads
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Reporting pass: uploads SARIF to code scanning, giving us stateful
|
||||
# analysis and incremental triage in the Security tab. Does not fail
|
||||
# with non-zero result but reports them.
|
||||
- name: Run zizmor 🌈
|
||||
uses: zizmorcore/zizmor-action@53290badfa369cb0bf7ad16342cb62c2fa47ac7b # v0.8.0
|
||||
Reference in New Issue
Block a user