diff --git a/.github/actions/ci-env/action.yml b/.github/actions/ci-env/action.yml deleted file mode 100644 index a577a08..0000000 --- a/.github/actions/ci-env/action.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: CI env setup - -description: Set Rust env vars - -runs: - using: "composite" - steps: - # `cargo` and `rustc` read these from the environment, so they must be - # exported for the caller's later steps rather than passed as step outputs. - # Every value written here is a literal. - - run: | # zizmor: ignore[github-env] - echo "CARGO_TERM_COLOR=always" | tee -a $GITHUB_ENV - - # Disable incremental compilation. - # - # Incremental compilation is useful as part of an edit-build-test-edit cycle, - # as it lets the compiler avoid recompiling code that hasn't changed. However, - # on CI, we're not making small edits; we're almost always building the entire - # project from scratch. Thus, incremental compilation on CI actually - # introduces *additional* overhead to support making future builds - # faster...but no future builds will ever occur in any given CI environment. - # - # See https://matklad.github.io/2021/09/04/fast-rust-builds.html#ci-workflow - # for details. - echo "CARGO_INCREMENTAL=0" | tee -a $GITHUB_ENV - - # Allow more retries for network requests in cargo (downloading crates) and - # rustup (installing toolchains). This should help to reduce flaky CI failures - # from transient network timeouts or other issues. - echo "CARGO_NET_RETRY=10" | tee -a $GITHUB_ENV - - echo "CARGO_MAX_RETRIES=10" | tee -a $GITHUB_ENV - - # Don't emit giant backtraces in the CI logs. - echo "RUST_BACKTRACE=short" | tee -a $GITHUB_ENV - - echo "RUSTFLAGS=-D warnings" | tee -a $GITHUB_ENV - shell: bash diff --git a/.github/actions/codecov/action.yml b/.github/actions/codecov/action.yml new file mode 100644 index 0000000..1de3e65 --- /dev/null +++ b/.github/actions/codecov/action.yml @@ -0,0 +1,37 @@ +name: Generate and deploy Codecov results +description: >- + Collect llvm-cov coverage data and upload it to Codecov. Assumes the repo + is already checked out with `submodules: recursive` and a Rust toolchain + with caching is already set up, e.g. via + `actions-rust-lang/setup-rust-toolchain`. + +inputs: + packages: + description: List of prerequisite Ubuntu packages, separated by whitespace + required: false + default: '' +runs: + using: composite + steps: + - uses: $/.github/actions/install-deps + if: inputs.packages != '' + with: + packages: "${{ inputs.packages }}" + - shell: bash + run: rustup component add llvm-tools-preview + - uses: taiki-e/install-action@nextest + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + - name: Clean the workspace + shell: bash + run: cargo llvm-cov clean --workspace + - name: Build + shell: bash + run: cargo build --workspace --release + - name: Collect coverage data + shell: bash + run: cargo llvm-cov nextest --lcov --output-path lcov.info --profile ci --release --workspace + - name: Upload coverage data to codecov + uses: codecov/codecov-action@v7 + with: + files: lcov.info diff --git a/.github/actions/create-issue/action.yml b/.github/actions/create-issue/action.yml new file mode 100644 index 0000000..fd266cf --- /dev/null +++ b/.github/actions/create-issue/action.yml @@ -0,0 +1,49 @@ +name: Create or update issue +description: + Create an issue in the current repository, updating the body of an existing + open issue with the same title instead of filing a duplicate. Requires + `issues` write permission and the gh CLI on the runner. + +inputs: + title: + description: Issue title, also used to match an existing open issue + required: true + body: + description: + Issue body text. Exactly one of `body` and `body-file` must be set. + required: false + default: '' + body-file: + description: + Path to a file containing the issue body, e.g. a generated report + required: false + default: '' + labels: + description: + Comma-separated labels to apply on creation. Best-effort — a label that + cannot be applied logs a warning instead of failing. + required: false + default: '' + update-existing: + description: Update the existing open issue's body instead of skipping it + required: false + default: 'true' + token: + description: GitHub token with `issues` write access + required: false + default: ${{ github.token }} + +runs: + using: composite + steps: + - name: Create or update issue + shell: bash + env: + GH_TOKEN: ${{ inputs.token }} + INPUT_TITLE: ${{ inputs.title }} + INPUT_BODY: ${{ inputs.body }} + INPUT_BODY_FILE: ${{ inputs.body-file }} + INPUT_LABELS: ${{ inputs.labels }} + INPUT_UPDATE_EXISTING: ${{ inputs.update-existing }} + ACTION_PATH: ${{ github.action_path }} + run: python3 "$ACTION_PATH/create_issue.py" diff --git a/.github/actions/create-issue/create_issue.py b/.github/actions/create-issue/create_issue.py new file mode 100644 index 0000000..9499c56 --- /dev/null +++ b/.github/actions/create-issue/create_issue.py @@ -0,0 +1,56 @@ +"""Create or update a GitHub issue via the gh CLI. + +Reads its configuration from the INPUT_* environment variables set in +action.yml. +""" + +import json +import os +import subprocess +import sys + + +def gh(*args): + return subprocess.run( + ["gh", *args], check=True, text=True, stdout=subprocess.PIPE + ).stdout + + +def main(): + repo = os.environ["GITHUB_REPOSITORY"] + title = os.environ["INPUT_TITLE"] + labels = os.environ.get("INPUT_LABELS", "") + update_existing = os.environ.get("INPUT_UPDATE_EXISTING", "true") == "true" + + body = os.environ.get("INPUT_BODY", "") + body_file = os.environ.get("INPUT_BODY_FILE", "") + if bool(body) == bool(body_file): + sys.exit("::error::Set exactly one of `body` and `body-file`") + if body_file: + with open(body_file) as f: + body = f.read() + + issues = json.loads( + gh("issue", "list", "--repo", repo, "--state", "open", + "--limit", "100", "--json", "number,title") + ) + existing = [issue["number"] for issue in issues if issue["title"] == title] + if existing: + if update_existing: + gh("issue", "edit", str(existing[0]), "--repo", repo, "--body", body) + print(f"Updated existing issue #{existing[0]}") + else: + print(f"Open issue #{existing[0]} already exists, skipping") + return + + url = gh("issue", "create", "--repo", repo, "--title", title, "--body", body).strip() + print(f"Created issue {url}") + if labels: + try: + gh("issue", "edit", url, "--repo", repo, "--add-label", labels) + except subprocess.CalledProcessError: + print(f"::warning::Could not apply labels {labels!r}") + + +if __name__ == "__main__": + main() diff --git a/.github/actions/gpu-bench/action.yml b/.github/actions/gpu-bench/action.yml new file mode 100644 index 0000000..19c669f --- /dev/null +++ b/.github/actions/gpu-bench/action.yml @@ -0,0 +1,165 @@ +name: Comparative benchmarks on GPU +description: >- + Run comparative criterion benchmarks against the base branch on a CUDA GPU, + posting the results as a commit comment. On a regression >= 10%, open an + issue instead of committing the bench result to `gh-pages`; the merge is + not blocked either way. Performs its own checkouts, so no prior checkout is + needed, but assumes a Rust toolchain with caching is already set up, e.g. + via `actions-rust-lang/setup-rust-toolchain`. Run on the `merge_group` trigger only, from a job with a + self-hosted Nvidia GPU runner (typically labeled `gpu-bench`) and + `contents` and `issues` write permissions — `contents: write` covers the + `gh-pages` push and the commit comment. Prerequisites — `cuda` Cargo + features, benchmarks formatted for `criterion-table` via + `${REPOSITORY_NAME}_BENCH_OUTPUT=commit-comment` (e.g. + `$LURK_BENCH_OUTPUT=commit-comment`), and a pre-existing `gh-pages` branch. + +inputs: + packages: + description: List of prerequisite Ubuntu packages, separated by whitespace + required: false + default: '' +runs: + using: composite + steps: + - uses: $/.github/actions/gpu-setup + with: + gpu-framework: 'cuda' + - uses: $/.github/actions/install-deps + if: inputs.packages != '' + with: + packages: "${{ inputs.packages }}" + # `git-auto-commit-action` below pushes to `gh-pages` with the credentials + # this checkout persists, so they cannot be disabled here. + - uses: actions/checkout@v7 # zizmor: ignore[artipacked] + - name: Install criterion + shell: bash + run: | + cargo install cargo-criterion + cargo install criterion-table + - name: Set env vars + shell: bash + run: | + REPOSITORY_NAME=$(echo '${{ github.repository }}' | awk -F'/' '{ print toupper($2) }') + echo "${REPOSITORY_NAME}_BENCH_OUTPUT=commit-comment" | tee -a $GITHUB_ENV + echo "BASE_COMMIT=${{ github.event.merge_group.base_sha }}" | tee -a $GITHUB_ENV + echo "GPU_ID=$(echo $GPU_NAME | awk '{ print $NF }')" | tee -a $GITHUB_ENV + # Checkout gh-pages to check for cached bench result + - name: Checkout gh-pages + uses: actions/checkout@v7 + with: + ref: gh-pages + path: gh-pages + persist-credentials: false + - name: Check for cached bench result + id: cached-bench + shell: bash + run: | + if [ -f "$BASE_COMMIT-$GPU_ID.json" ] + then + echo "cached=true" | tee -a $GITHUB_OUTPUT + cp "$BASE_COMMIT-$GPU_ID.json" "../$BASE_COMMIT.json" + else + echo "cached=false" | tee -a $GITHUB_OUTPUT + fi + working-directory: ${{ github.workspace }}/gh-pages + # Checkout base branch for comparative bench + - uses: actions/checkout@v7 + if: steps.cached-bench.outputs.cached == 'false' + with: + ref: ${{ github.base_ref }} + path: ${{ github.base_ref }} + persist-credentials: false + - name: Run GPU bench on base branch + if: steps.cached-bench.outputs.cached == 'false' + shell: bash + run: | + # Run benchmark + cargo criterion --features "cuda" --message-format=json > "$BASE_COMMIT.json" + # Copy bench output to PR branch + cp "$BASE_COMMIT.json" .. + working-directory: ${{ github.workspace }}/${{ github.base_ref }} + - name: Run GPU bench on PR branch + shell: bash + run: | + cargo criterion --features "cuda" --message-format=json > ${{ github.sha }}.json + cp ${{ github.sha }}.json .. + working-directory: ${{ github.workspace }}/benches + - name: copy the benchmark template and prepare it with data + shell: bash + run: | + cp .github/tables.toml . + # Get CPU model + CPU_MODEL=$(grep '^model name' /proc/cpuinfo | head -1 | awk -F ': ' '{ print $2 }') + # Get num vCPUS + NUM_VCPUS="$(nproc --all) vCPUs" + # Get total RAM in GB + TOTAL_RAM=$(grep MemTotal /proc/meminfo | awk '{$2=$2/(1024^2); print int($2), "GB RAM";}') + + # Use conditionals to ensure that only non-empty variables are inserted + [[ ! -z "$GPU_NAME" ]] && sed -i "/^\"\"\"$/i $GPU_NAME" tables.toml + [[ ! -z "$CPU_MODEL" ]] && sed -i "/^\"\"\"$/i $CPU_MODEL" tables.toml + [[ ! -z "$NUM_VCPUS" ]] && sed -i "/^\"\"\"$/i $NUM_VCPUS" tables.toml + [[ ! -z "$TOTAL_RAM" ]] && sed -i "/^\"\"\"$/i $TOTAL_RAM" tables.toml + sed -i "/^\"\"\"$/i Workflow run: $GITHUB_SERVER_URL/$REPO/actions/runs/$RUN_ID" tables.toml + working-directory: ${{ github.workspace }} + env: + REPO: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} + # Create a `criterion-table` and write in commit comment + - name: Run `criterion-table` + shell: bash + run: cat "$BASE_COMMIT.json" "$GITHUB_SHA.json" | criterion-table > BENCHMARKS.md + - name: Write bench on commit comment + uses: peter-evans/commit-comment@v4 + with: + body-path: BENCHMARKS.md + # Check for a slowdown >= 10%. If so, open an issue but don't block merge. + # Reported as a step output because `continue-on-error` is silently + # ignored on composite action steps, so a failing step would abort here. + - name: Check for perf regression + id: regression-check + shell: bash + run: | + regressions=$(awk -F'[*x]' '/slower/{print $12}' BENCHMARKS.md) + + echo $regressions + + regression=false + for r in $regressions + do + if (( $(echo "$r >= 1.10" | bc -l) )) + then + regression=true + fi + done + echo "regression=$regression" | tee -a $GITHUB_OUTPUT + # Not possible to use ${{ github.event.number }} with the `merge_group` trigger + - name: Get PR number from merge branch + shell: bash + env: + HEAD_REF: ${{ github.event.merge_group.head_ref }} + run: | + echo "PR_NUMBER=$(echo "$HEAD_REF" | sed -e 's/.*pr-\(.*\)-.*/\1/')" | tee -a $GITHUB_ENV + - name: Open issue on regression + if: steps.regression-check.outputs.regression == 'true' + uses: $/.github/actions/create-issue + with: + title: ':rotating_light: Performance regression detected for PR #${{ env.PR_NUMBER }}' + labels: P-Performance,automated issue + body: | + Regression >= 10% found during merge for PR #${{ env.PR_NUMBER }} + Commit: ${{ github.sha }} + Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + - name: Remove old base bench + shell: bash + run: | + rm "$BASE_COMMIT.json" + mv "$GITHUB_SHA.json" "$GITHUB_SHA-$GPU_ID.json" + working-directory: ${{ github.workspace }} + - name: Commit bench result to `gh-pages` branch if no regression + if: steps.regression-check.outputs.regression != 'true' + uses: stefanzweifel/git-auto-commit-action@v7 + with: + branch: gh-pages + commit_message: '[automated] GPU Benchmark from PR #${{ env.PR_NUMBER }}' + file_pattern: '${{ github.sha }}-${{ env.GPU_ID }}.json' diff --git a/.github/actions/gpu-ci/action.yml b/.github/actions/gpu-ci/action.yml new file mode 100644 index 0000000..26499de --- /dev/null +++ b/.github/actions/gpu-ci/action.yml @@ -0,0 +1,50 @@ +name: GPU CI tests +description: >- + Rust tests on a CUDA or OpenCL GPU. Assumes the repo is already checked out + with `submodules: recursive` and a Rust toolchain with caching is already + set up, e.g. via `actions-rust-lang/setup-rust-toolchain`. Prerequisites — a self-hosted Nvidia GPU + runner with CUDA enabled attached to the caller repo (typically labeled + `gpu-ci`), and the `cuda` (plus `opencl` if selected) Cargo features. We + expect dependents to run this on the `pull_request` and `merge_group` + triggers, gating the job with `if: github.event_name == 'merge_group'` so + it shows as a skipped status check on the PR, then runs once on the merge + queue's merge commit when attempting to merge. + +inputs: + gpu-framework: + description: GPU framework to test, either 'cuda' or 'opencl' + required: false + default: 'cuda' + features: + description: + Comma-separated list of features to run in addition to the GPU framework + features + required: false + default: '' + packages: + description: List of prerequisite Ubuntu packages, separated by whitespace + required: false + default: '' +runs: + using: composite + steps: + - uses: $/.github/actions/gpu-setup + with: + gpu-framework: ${{ inputs.gpu-framework }} + - uses: $/.github/actions/install-deps + if: inputs.packages != '' + with: + packages: "${{ inputs.packages }}" + - uses: taiki-e/install-action@nextest + - name: GPU tests + shell: bash + env: + FEATURES: ${{ inputs.features }} + GPU_FRAMEWORK: ${{ inputs.gpu-framework }} + run: | + if [[ "$GPU_FRAMEWORK" == "opencl" ]]; then + gpu_features="cuda,opencl" + else + gpu_features="cuda" + fi + cargo nextest run --profile ci --cargo-profile dev-ci --features "$gpu_features,$FEATURES" diff --git a/.github/actions/links-check/action.yml b/.github/actions/links-check/action.yml new file mode 100644 index 0000000..111f50e --- /dev/null +++ b/.github/actions/links-check/action.yml @@ -0,0 +1,37 @@ +name: Check documentation links +description: >- + Run lychee over the repo's documentation links. If `fail-fast` is 'false', + opens an issue with the report instead of failing the job. Assumes the repo + is already checked out. The calling job needs `issues: write` when + `fail-fast` is 'false'. + +inputs: + fail-fast: + description: Whether to error on failure instead of opening an issue + required: false + default: 'true' + token: + description: GitHub token, needs `issues` write access when `fail-fast` is 'false' + required: false + default: ${{ github.token }} + +runs: + using: composite + steps: + - name: Link Checker + id: lychee + uses: lycheeverse/lychee-action@v2.9.0 + with: + fail: ${{ inputs.fail-fast }} + env: + GITHUB_TOKEN: ${{ inputs.token }} + # lychee stopped exporting `lychee_exit_code` to the environment in v2; + # the exit code is only available as a step output. + - name: Open issue on failure if `fail-fast` input is false + if: steps.lychee.outputs.exit_code != 0 && inputs.fail-fast != 'true' + uses: $/.github/actions/create-issue + with: + token: ${{ inputs.token }} + title: Link Checker Report + body-file: ./lychee/out.md + labels: report,automated issue diff --git a/.github/actions/lint-workflows/action.yml b/.github/actions/lint-workflows/action.yml index 9f9c8c5..aea3a89 100644 --- a/.github/actions/lint-workflows/action.yml +++ b/.github/actions/lint-workflows/action.yml @@ -21,6 +21,16 @@ runs: - uses: raven-actions/actionlint@v2 env: SHELLCHECK_OPTS: -S ${{ inputs.shellcheck-severity }} + with: + # actionlint doesn't know the `$/` self-repository syntax yet and + # misparses it as {owner}/{repo}@{ref} + flags: -ignore 'specifying action "\$/.+" in invalid format' + - name: Shellcheck composite action scripts + shell: bash + env: + SEVERITY: ${{ inputs.shellcheck-severity }} + ACTION_PATH: ${{ github.action_path }} + run: python3 "$ACTION_PATH/shellcheck_actions.py" # Note that `continue-on-error` is silently ignored on composite action # steps, so any zizmor finding fails the calling job. Suppress findings # that don't apply via `.github/zizmor.yml` or `# zizmor: ignore` comments. diff --git a/.github/actions/lint-workflows/shellcheck_actions.py b/.github/actions/lint-workflows/shellcheck_actions.py new file mode 100644 index 0000000..bef8c55 --- /dev/null +++ b/.github/actions/lint-workflows/shellcheck_actions.py @@ -0,0 +1,40 @@ +"""Shellcheck the bash `run:` blocks of composite actions. + +actionlint can't parse composite action manifests (rhysd/actionlint#46), +so their scripts are extracted and batched through one shellcheck +invocation here, with `${{ }}` expressions masked the way actionlint +masks them in workflow scripts. +""" + +import json +import os +import re +import subprocess +import sys +import tempfile +from pathlib import Path + + +def main(): + severity = os.environ.get("SEVERITY", "warning") + out = Path(tempfile.mkdtemp()) + scripts = [] + for manifest in sorted(Path(".github/actions").glob("*/action.y*ml")): + parsed = subprocess.run( + ["yq", "-o=json", ".", manifest], check=True, text=True, stdout=subprocess.PIPE + ) + for i, step in enumerate(json.loads(parsed.stdout)["runs"]["steps"]): + if step.get("shell") == "bash": + script = out / f"{manifest.parent.name}-{i}.sh" + script.write_text(re.sub(r"\$\{\{.*?\}\}", "EXPR", step["run"])) + scripts.append(script) + if scripts: + sys.exit( + subprocess.run( + ["shellcheck", f"--severity={severity}", "--shell=bash", *scripts] + ).returncode + ) + + +if __name__ == "__main__": + main() diff --git a/.github/actions/lints/action.yml b/.github/actions/lints/action.yml new file mode 100644 index 0000000..64bdedf --- /dev/null +++ b/.github/actions/lints/action.yml @@ -0,0 +1,50 @@ +name: Check lints and code quality +description: + Rustfmt, clippy, and doctests. Assumes the repo is already checked out and + a Rust toolchain with caching is already set up, e.g. via + `actions-rust-lang/setup-rust-toolchain`. + +inputs: + packages: + description: List of prerequisite Ubuntu packages, separated by whitespace + required: false + default: '' + nightly-fmt: + description: + Runs `cargo fmt +nightly`, for use with nightly config options in + `rustfmt.toml` + required: false + default: 'false' +runs: + using: composite + steps: + - uses: $/.github/actions/install-deps + if: inputs.packages != '' + with: + packages: "${{ inputs.packages }}" + - shell: bash + run: rustup component add rustfmt clippy + - if: inputs.nightly-fmt == 'true' + shell: bash + run: rustup toolchain install nightly --component rustfmt + - name: Check Rustfmt Code Style + shell: bash + env: + NIGHTLY_FMT: ${{ inputs.nightly-fmt }} + run: | + if [[ "$NIGHTLY_FMT" == "true" ]]; then + cargo +nightly fmt --all -- --check + else + cargo fmt --all -- --check + fi + - name: Check clippy warnings + shell: bash + run: | + if cargo --list | grep -q xclippy; then + cargo xclippy -Dwarnings + else + cargo clippy -Dwarnings + fi + - name: Doctests + shell: bash + run: cargo test --doc --workspace diff --git a/.github/actions/msrv/action.yml b/.github/actions/msrv/action.yml new file mode 100644 index 0000000..00504ab --- /dev/null +++ b/.github/actions/msrv/action.yml @@ -0,0 +1,26 @@ +name: Check MSRV +description: + Check the MSRV (aka `rust-version`) in `Cargo.toml` is valid. Assumes the + repo is already checked out and a Rust toolchain with caching is already + set up, e.g. via `actions-rust-lang/setup-rust-toolchain`. Does not + currently work with Cargo workspaces, see + https://github.com/argumentcomputer/ci-workflows/issues/8 + +inputs: + packages: + description: List of prerequisite Ubuntu packages, separated by whitespace + required: false + default: '' +runs: + using: composite + steps: + - uses: $/.github/actions/install-deps + if: inputs.packages != '' + with: + packages: "${{ inputs.packages }}" + - name: Install cargo-msrv + shell: bash + run: cargo install cargo-msrv + - name: Check Rust MSRV + shell: bash + run: cargo msrv verify diff --git a/.github/actions/release-pr/action.yml b/.github/actions/release-pr/action.yml index 9e5b5a8..777abae 100644 --- a/.github/actions/release-pr/action.yml +++ b/.github/actions/release-pr/action.yml @@ -52,7 +52,14 @@ inputs: runs: using: "composite" steps: - - uses: dtolnay/rust-toolchain@stable + # Rust is incidental here, so provision a toolchain rather than requiring + # one from the caller. Empty `rustflags` so the install doesn't fail on + # `-D warnings`. + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + cache: false + matcher: false + rustflags: '' - run: cargo install tq-rs shell: bash @@ -198,7 +205,7 @@ runs: fi bump_version() { - cd "$1" + cd "$1" || exit 1 OLD_VERSION=$(grep -oP 'version = "\K[^"]+' Cargo.toml | head -n1) if [[ "${CRATE_VERSION}" > "$OLD_VERSION" ]]; then sed -i "s/version = \"$OLD_VERSION\"/version = \"${CRATE_VERSION}\"/" Cargo.toml @@ -206,7 +213,7 @@ runs: echo "New version is not greater than the current version for $1. Aborting..." exit 1 fi - cd ${{ github.workspace }}/${INPUTS_PATH} + cd "${{ github.workspace }}/${INPUTS_PATH}" || exit 1 } while IFS= read -r path; do diff --git a/.github/actions/repo-sync/action.yml b/.github/actions/repo-sync/action.yml new file mode 100644 index 0000000..4ada993 --- /dev/null +++ b/.github/actions/repo-sync/action.yml @@ -0,0 +1,49 @@ +name: Sync from upstream repo +description: + Force-sync a branch from its upstream repository, opening an issue asking + for a manual sync when the push is rejected — e.g. when upstream changed + files under `.github/workflows/`, which `GITHUB_TOKEN` cannot push. The + calling job needs `contents` and `issues` write permissions. No checkout is + required. + +inputs: + repository: + description: Upstream repository formatted as "owner/repo", e.g. "argumentcomputer/ix" + required: true + branch: + description: + Branch to sync. `gh repo sync` takes a single branch name, so the + upstream and the mirror must share it. + required: false + default: 'main' + token: + description: GitHub token with `contents` and `issues` write access + required: false + default: ${{ github.token }} + +runs: + using: composite + steps: + # Pushes made with `GITHUB_TOKEN` don't trigger `on: push` workflows in + # the fork, so a sync can't start a recursive run + - name: repo-sync + shell: bash + run: gh repo sync "$GITHUB_REPOSITORY" --source "$INPUTS_REPOSITORY" --branch "$INPUTS_BRANCH" --force + env: + GH_TOKEN: ${{ inputs.token }} + INPUTS_REPOSITORY: ${{ inputs.repository }} + INPUTS_BRANCH: ${{ inputs.branch }} + - uses: $/.github/actions/create-issue + if: failure() + with: + token: ${{ inputs.token }} + title: "chore: manual sync required from ${{ inputs.repository }}" + labels: automated-issue + body: | + The scheduled sync of `${{ inputs.branch }}` from [`${{ inputs.repository }}`](https://github.com/${{ inputs.repository }}) failed. + + The usual cause is an upstream change under `.github/workflows/`, which `GITHUB_TOKEN` is not allowed to push. Review the incoming changes, then sync manually with the "Sync fork" button or a token carrying the `workflow` scope. Close this issue once the branch is synced. + + Check the [failed sync run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for error details. + + This issue was raised by `https://github.com/argumentcomputer/ci-workflows/tree/main/.github/actions/repo-sync`. diff --git a/.github/actions/rust-version-check/action.yml b/.github/actions/rust-version-check/action.yml new file mode 100644 index 0000000..119723f --- /dev/null +++ b/.github/actions/rust-version-check/action.yml @@ -0,0 +1,52 @@ +name: Rust version check +description: >- + Check whether the Rust version specified in `rust-toolchain.toml` is out of + date with the latest stable, opening an issue if so. Compares the full + `..` of `rustup show` with `rustup check`, because the + patch version auto-updates if unspecified in `rust-toolchain.toml`. Assumes + the repo is already checked out. The calling job needs `issues: write`. + +inputs: + token: + description: GitHub token with `issues` write access + required: false + default: ${{ github.token }} + +runs: + using: composite + steps: + # `rustup show` below must report the `rust-toolchain.toml` version while + # `rustup check` reports the latest stable, so both must be installed + - name: Install latest stable + shell: bash + run: rustup toolchain install stable + + - name: Parse rust-toolchain.toml + shell: bash + run: echo "TOOLCHAIN_VERSION=$(rustup show | grep rustc | awk '{ print $2 }')" | tee -a $GITHUB_ENV + + - name: Get latest stable Rust version + shell: bash + run: echo "RUST_VERSION=$(rustup check | grep stable | awk '{print $(NF-2)}')" | tee -a $GITHUB_ENV + + - name: Compare Rust versions + shell: bash + run: | + if [[ $TOOLCHAIN_VERSION < $RUST_VERSION ]]; then + echo "VERSION_MISMATCH=true" | tee -a $GITHUB_ENV + else + echo "VERSION_MISMATCH=false" | tee -a $GITHUB_ENV + fi + + - uses: $/.github/actions/create-issue + if: env.VERSION_MISMATCH == 'true' + with: + token: ${{ inputs.token }} + title: "chore: rust toolchain needs an upgrade" + labels: debt,automated-issues + body: | + The rust version specified in `rust-toolchain.toml` (${{ env.TOOLCHAIN_VERSION }}) is out of date with the latest stable (${{ env.RUST_VERSION }}). + + Check the [rust version check](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) workflow for details. + + This issue was raised by the workflow at ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/workflow. diff --git a/.github/actions/typos/action.yml b/.github/actions/typos/action.yml new file mode 100644 index 0000000..2ec1e07 --- /dev/null +++ b/.github/actions/typos/action.yml @@ -0,0 +1,73 @@ +name: Check for typos +description: + Run `typos --write-changes` and open a pull request with the fixes, + listing any unfixable typos in the PR body. Assumes the repo is already + checked out. The calling job needs `contents` and `pull-requests` write + permissions. The PR is created with `GITHUB_TOKEN`, so CI does not run on + it automatically — a maintainer reviews and clicks "Approve workflow". + +inputs: + token: + description: GitHub token with `contents` and `pull-requests` write access + required: false + default: ${{ github.token }} +runs: + using: composite + steps: + # Rust is incidental here, so provision a toolchain rather than requiring + # one from the caller. Empty `rustflags` so the install doesn't fail on + # `-D warnings`. + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + rustflags: '' + - name: Install typos binary + shell: bash + run: cargo +stable install typos-cli + - name: Check typos and write suggestions + id: typo-check + shell: bash + run: | + typos --write-changes > _typos.txt || true + if [[ `git status --porcelain --untracked-files=no` ]]; then + echo "typos=true" | tee -a $GITHUB_OUTPUT + else + echo "typos=false" | tee -a $GITHUB_OUTPUT + fi + - name: Create file for PR + if: steps.typo-check.outputs.typos == 'true' + shell: bash + run: | + printf '%s\n' "Fixes typos found by running \`typos --write-changes\` + Commit: ${{ github.sha }} + Workflow run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" > _body.md + if [[ -s _typos.txt ]]; then + printf "## Unfixed typos\n" >> _body.md + printf "Reviewers: Please manually fix & commit the following typos:\n\`\`\`\n" >> _body.md + cat _typos.txt >> _body.md + printf "\`\`\`\n" >> _body.md + rm _typos.txt + fi + printf '%s\n' "> [!NOTE] + > If a false positive is found, please add it to \`_typos.toml\` as per the [documentation](https://github.com/crate-ci/typos/tree/master?tab=readme-ov-file#false-positives)" >> _body.md + # Checks which file types should be committed with typo corrections + # Git pathspecs cause errors if the given pattern doesn't exist, e.g. `git add -- **/*.txt` without any `.txt` files + - name: Check for common file types + if: steps.typo-check.outputs.typos == 'true' + id: file-types + shell: bash + run: | + FILE_PATHS=":!*\_body.md,$(git status --porcelain | awk -F. '{OFS=""; print "**/*."$NF}' | sort -u | paste -sd,)" + echo "paths=$FILE_PATHS" | tee -a $GITHUB_OUTPUT + - name: Create pull request + uses: peter-evans/create-pull-request@v8 + if: steps.typo-check.outputs.typos == 'true' + with: + token: ${{ inputs.token }} + commit-message: '[automated] Fix typos' + title: '[automated] Fix typos' + branch: 'patch/fix-typos' + delete-branch: true + body-path: ./_body.md + labels: automated issue, documentation + # Required in order to exclude the `_body.md` file from the PR + add-paths: ${{ steps.file-types.outputs.paths }} diff --git a/.github/actions/unused-deps/action.yml b/.github/actions/unused-deps/action.yml new file mode 100644 index 0000000..f9858df --- /dev/null +++ b/.github/actions/unused-deps/action.yml @@ -0,0 +1,53 @@ +name: Unused dependency check +description: >- + Run cargo-udeps and open an issue if unused dependencies are found. Assumes + the repo is already checked out and a Rust toolchain with caching is + already set up, e.g. via `actions-rust-lang/setup-rust-toolchain`. The calling job needs `issues: write`. + +inputs: + features: + description: Comma-separated list of features to check + required: false + default: '' + packages: + description: List of prerequisite Ubuntu packages, separated by whitespace + required: false + default: '' + token: + description: GitHub token with `issues` write access + required: false + default: ${{ github.token }} +runs: + using: composite + steps: + - uses: $/.github/actions/install-deps + if: inputs.packages != '' + with: + packages: "${{ inputs.packages }}" + - uses: taiki-e/install-action@cargo-udeps + # Normally running cargo-udeps requires use of a nightly compiler + # In order to have a more stable and less noisy experience, lets instead + # opt to use the stable toolchain specified via the 'rust-toolchain' file + # and instead enable nightly features via 'RUSTC_BOOTSTRAP' + - name: run cargo-udeps + shell: bash + run: RUSTC_BOOTSTRAP=1 cargo udeps --workspace --all-targets --features "${INPUTS_FEATURES}" + env: + INPUTS_FEATURES: ${{ inputs.features }} + - uses: $/.github/actions/create-issue + if: failure() + with: + token: ${{ inputs.token }} + title: "chore: some installed deps are not needed" + labels: automated-issue + body: | + Some dependencies specified in `Cargo.toml` are not needed. + + Check the [unused dependencies sanity check](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) workflow for details. + + This issue was raised by `https://github.com/argumentcomputer/ci-workflows/tree/main/.github/actions/unused-deps`. + + > **Note** + > If this is a false positive, please refer to the [`cargo-udeps` docs][cargo-udeps-docs] on how to ignore the dependencies. + + [cargo-udeps-docs]: https://github.com/est31/cargo-udeps#ignoring-some-of-the-dependencies diff --git a/.github/actions/wasm/action.yml b/.github/actions/wasm/action.yml new file mode 100644 index 0000000..d718f70 --- /dev/null +++ b/.github/actions/wasm/action.yml @@ -0,0 +1,23 @@ +name: Wasm build +description: + Build the workspace for the `wasm32-unknown-unknown` target. Assumes the + repo is already checked out and a Rust toolchain with caching is already + set up, e.g. via `actions-rust-lang/setup-rust-toolchain`. + +inputs: + packages: + description: List of prerequisite Ubuntu packages, separated by whitespace + required: false + default: '' +runs: + using: composite + steps: + - uses: $/.github/actions/install-deps + if: inputs.packages != '' + with: + packages: "${{ inputs.packages }}" + - shell: bash + run: rustup target add wasm32-unknown-unknown + - name: Wasm build + shell: bash + run: cargo build --target wasm32-unknown-unknown diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a1203f0..43b33f0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,11 @@ version: 2 updates: - package-ecosystem: "github-actions" - directory: "/" + # "/" is a special case meaning `.github/workflows/`; composite action + # directories must be listed separately for their dependencies to be seen + directories: + - "/" + - "/.github/actions/**" schedule: interval: "weekly" cooldown: diff --git a/.github/templates/UNUSED_DEPS_ISSUE.md b/.github/templates/UNUSED_DEPS_ISSUE.md deleted file mode 100644 index a153047..0000000 --- a/.github/templates/UNUSED_DEPS_ISSUE.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: "chore: some installed deps are not needed" -labels: automated-issue ---- - -Some dependencies specified in `Cargo.toml` are not needed. - -Check the [unused dependencies sanity check]({{env.WORKFLOW_URL}}) workflow for details. - -This issue was raised by the workflow at `https://github.com/argumentcomputer/ci-workflows/tree/main/.github/workflows/unused-deps.yml`. - -> **Note** -> If this is a false positive, please refer to the [`cargo-udeps` docs][cargo-udeps-docs] on how to ignore the dependencies. - -[cargo-udeps-docs]: https://github.com/est31/cargo-udeps#ignoring-some-of-the-dependencies diff --git a/.github/templates/VERSION_CHECK.md b/.github/templates/VERSION_CHECK.md deleted file mode 100644 index cfa2c38..0000000 --- a/.github/templates/VERSION_CHECK.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: "chore: rust toolchain needs an upgrade" -labels: debt, automated-issues ---- - -The rust version specified in `rust-toolchain.toml` ({{env.TOOLCHAIN_VERSION}}) is out of date with the latest stable ({{env.RUST_VERSION}}). - -Check the [rust version check]({{env.WORKFLOW_URL}}) workflow for details. - -This issue was raised by the workflow at {{env.WORKFLOW_FILE}}. diff --git a/.github/workflows/actions-lint.yml b/.github/workflows/actions-lint.yml index f5b2513..8ce5fde 100644 --- a/.github/workflows/actions-lint.yml +++ b/.github/workflows/actions-lint.yml @@ -21,4 +21,4 @@ jobs: - uses: actions/checkout@v7 with: persist-credentials: false - - uses: ./.github/actions/lint-workflows + - uses: $/.github/actions/lint-workflows diff --git a/.github/workflows/bench-pr-comment.yml b/.github/workflows/bench-pr-comment.yml deleted file mode 100644 index 590187d..0000000 --- a/.github/workflows/bench-pr-comment.yml +++ /dev/null @@ -1,206 +0,0 @@ -# Creates a PR benchmark comment with a comparison to the base branch -# -# USER NOTE: If you want to use a GPU runner with CUDA acceleration, you must specify `!gpu-benchmark` or `--features cuda` (see below syntax) -# -# Usage: -# ``` -# --bench --features -# ENV_A=a -# ENV_B=b -# ``` -# -# Notes -# - There can be multiple instances of `--bench `, each will spawn a new matrix job and associated PR comment -# - If only `` is passed as input, then the workflow will run with the caller's `default-benches` and `default-env` inputs -# -# Restrictions -# - Only for use with `issue_comment` trigger on a PR -# - If the `cuda` feature is specified, there must be a self-hosted runner attached to the repo with the `gpu-bench` label -name: Benchmark pull requests - -on: - workflow_call: - inputs: - # Comma-separated list of runner labels used for benchmarks when `cuda` feature is not activated - # E.g. "ubuntu-latest", "self-hosted,gpu-bench" The latter will run on a GPU machine but not actually use the GPU - # To use the GPU you must set `--features cuda`, which will always run on a `["self-hosted", "gpu-bench"]` runner - default-runner: - type: string - required: false - default: 'ubuntu-latest' - # Comma-separated list of default benchmarks when they are unspecified in the comment body - default-benches: - type: string - required: true - # Whitespace-separated list of default env vars, set regardless of comment body - default-env: - type: string - required: false - # List of prerequisite Ubuntu packages, separated by whitespace - packages: - required: false - type: string - -jobs: - setup: - name: Set up benchmark parameters - runs-on: ubuntu-latest - env: - GPU_BENCHMARK: ${{ contains(github.event.comment.body, '!gpu-benchmark') }} - outputs: - # Default runner formatted for JSON parsing - runner: ${{ steps.format-runner.outputs.runner }} - # Benches specified by `--bench ` repeated for each bench - benches: ${{ steps.bench-params.outputs.benches }} - # Features specified by `--features ` - features: ${{ steps.bench-params.outputs.features }} - # Env vars specified by `ENV_VAR=`, starting on the second line of the `issue_comment` input - # Separated by whitespace but ideally newlines for readability - env-vars: ${{ steps.bench-params.outputs.env-vars }} - # Flag to denote the `cuda` feature is active, which means we need a self-hosted GPU runner - cuda: ${{ steps.bench-params.outputs.cuda }} - # `benchmark` or `gpu-benchmark`, used for debugging and comment output - command: ${{ steps.bench-params.outputs.command }} - - steps: - - name: Format default runner string - id: format-runner - run: | - # Parse `default-runner` if it's a list of strings (e.g. `"self-hosted,gpu-bench") - RUNNER=$(echo ${INPUTS_DEFAULT_RUNNER} | awk -F"," -v q=\" '{for (i=0; i> $GITHUB_OUTPUT - echo "$RUNNER" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - env: - INPUTS_DEFAULT_RUNNER: ${{ inputs.default-runner }} - - name: Parse PR comment body - id: bench-params - env: - COMMENT_BODY: ${{ github.event.comment.body }} - INPUTS_DEFAULT_BENCHES: ${{ inputs.default-benches }} - run: | - # Parse `issue_comment` body - printf '%s' "$COMMENT_BODY" > comment.txt - BENCH_COMMAND=$(head -n 1 comment.txt) - echo "$BENCH_COMMAND" - - # Get each input bench name and format as quoted list - BENCHES=$(echo $BENCH_COMMAND | awk -v q=\" '{for (i=1; i<=NF; i++) {if ($i ~ /^--bench/) {print q$(i+1)q","}}}') - if [[ -z $BENCHES ]]; then - # Add quotes to each default bench name in comma-separated list for `fromJSON` parsing - BENCHES=$(echo ${INPUTS_DEFAULT_BENCHES} | awk -F"," -v q=\" '{for (i=0; i> $GITHUB_OUTPUT - echo "$BENCHES" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - # Get the list of features to run on each benchmark - FEATURES=$(echo $BENCH_COMMAND | awk '{for (i=1; i<=NF; i++) {if ($i ~ /^--features/) {print $(i+1) }}}') - if [[ ${GPU_BENCHMARK} = 'true' || $(echo $FEATURES | grep -s cuda) ]]; then - echo "cuda=true" | tee -a $GITHUB_OUTPUT - COMMAND="gpu-benchmark" - # Add the "cuda" feature if not already specified - if echo "$FEATURES" | grep -vq "cuda" 2>/dev/null; then - FEATURES="${FEATURES},cuda" - fi - else - COMMAND="benchmark" - fi - echo "command=$COMMAND" | tee -a $GITHUB_OUTPUT - echo "features=$FEATURES" | tee -a $GITHUB_OUTPUT - # Can't persist env vars between jobs, so we pass them as an output and set them in the next job - echo "env-vars=$(tail -n +2 comment.txt)" | tee -a $GITHUB_OUTPUT - - benchmark: - needs: [ setup ] - # Uses a self-hosted GPU runner if the `cuda` feature is specified, otherwise uses the default runner - runs-on: ${{ (needs.setup.outputs.cuda) && fromJSON('[ "self-hosted", "gpu-bench" ]') || fromJSON(needs.setup.outputs.runner) }} - strategy: - matrix: - # Runs a job for each benchmark specified in the `issue_comment` input - bench: ${{ fromJSON(needs.setup.outputs.benches) }} - steps: - # When using the `cuda` feature, several GPU-related env vars are set by the `gpu-setup` action below. - # Thus there is no need to set them here. These inputs are mainly for benchmark parameters such as `LURK_RC` - - name: Set env vars - env: - DEFAULT_ENV: ${{ inputs.default-env }} - COMMENT_ENV: ${{ needs.setup.outputs.env-vars }} - run: | - # Trims newlines that may arise from `$GITHUB_OUTPUT` - # Both lists are deliberately unquoted so they word-split into - # individual `NAME=VALUE` pairs. - # shellcheck disable=SC2086 - for var in $DEFAULT_ENV - do - echo "$(echo $var | tr -d '\n')" | tee -a $GITHUB_ENV - done - # Overrides default env vars with those specified in the `issue_comment` input if identically named - # shellcheck disable=SC2086 - for var in $COMMENT_ENV - do - echo "$(echo $var | tr -d '\n')" | tee -a $GITHUB_ENV - done - - uses: actions/checkout@v7 - with: - repository: argumentcomputer/ci-workflows - persist-credentials: false - - uses: ./.github/actions/gpu-setup - if: ${{ needs.setup.outputs.cuda }} - with: - gpu-framework: 'cuda' - - uses: ./.github/actions/ci-env - - uses: ./.github/actions/install-deps - if: inputs.packages != '' - with: - packages: "${{ inputs.packages }}" - # Get base branch of the PR - - uses: xt0rted/pull-request-comment-branch@v3 - id: comment-branch - - uses: actions/checkout@v7 - with: - persist-credentials: false - - name: Checkout PR branch - run: gh pr checkout $PR_NUMBER - env: - GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ github.event.issue.number }} - # Install dependencies - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - # Run the comparative benchmark and comment output on the PR - - uses: boa-dev/criterion-compare-action@v3 - with: - # Note: Removing `benchName` causes `criterion` `save-baseline` errors: - # https://github.com/boa-dev/criterion-compare-action#troubleshooting - # Optional. Compare only this benchmark target - benchName: ${{ matrix.bench }} - # Optional. Features activated in the benchmark - features: "${{ needs.setup.outputs.features }}" - # Needed. The name of the branch to compare with - branchName: ${{ steps.comment-branch.outputs.base_ref }} - - name: Comment on successful run - if: success() - uses: peter-evans/create-or-update-comment@v5 - with: - issue-number: ${{ github.event.issue.number }} - body: | - `!${{ needs.setup.outputs.command }}` action succeeded! :rocket: - - https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - - - name: Comment on failing run - if: failure() - uses: peter-evans/create-or-update-comment@v5 - with: - issue-number: ${{ github.event.issue.number }} - body: | - `!${{ needs.setup.outputs.command }}` action failed :x: - - https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} diff --git a/.github/workflows/check-lurk-compiles.yml b/.github/workflows/check-lurk-compiles.yml deleted file mode 100644 index d50794f..0000000 --- a/.github/workflows/check-lurk-compiles.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Check upstream `lurk-rs` compiles - -on: - workflow_call: - inputs: - runner: - required: false - default: 'ubuntu-latest' - type: string - # List of prerequisite Ubuntu packages, separated by whitespace - packages: - required: false - type: string - -jobs: - check-lurk-compiles: - if: github.event_name == 'pull_request' - runs-on: ${{ inputs.runner }} - steps: - - uses: actions/checkout@v7 - with: - repository: argumentcomputer/ci-workflows - persist-credentials: false - - uses: ./.github/actions/ci-env - - uses: ./.github/actions/install-deps - if: inputs.packages != '' - with: - packages: "${{ inputs.packages }}" - - uses: actions/checkout@v7 - with: - persist-credentials: false - - uses: actions/checkout@v7 - with: - repository: argumentcomputer/lurk-rs - path: ./lurk-rs - submodules: recursive - persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - name: Patch Cargo.toml - working-directory: ${{ github.workspace }}/lurk-rs - run: | - URL=https://github.com/${{ github.repository }} - # the dependency we want to patch is usually the same as the package, but - # we e.g. want to override dependency 'nova' with an 'arecibo' package - DEPENDENCY=$(grep "git = \"$URL\"" Cargo.toml | awk '{ print $1 }') - PACKAGE=$(grep "git = \"$URL\"" Cargo.toml | grep -oP 'package = "\K[^"]*'| cat) - echo "[patch.'$URL']" >> Cargo.toml - if [ ! -z "$PACKAGE" ]; - then - echo "$DEPENDENCY = { path='../', package='$PACKAGE' }" >> Cargo.toml - else - echo "$DEPENDENCY = { path='../' }" >> Cargo.toml - fi - - name: Check Lurk-rs types don't break spectacularly - working-directory: ${{ github.workspace }}/lurk-rs - run: cargo check --workspace --tests --benches --examples diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml deleted file mode 100644 index 89e315d..0000000 --- a/.github/workflows/codecov.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Generate and deploy Codecov results - -on: - workflow_call: - inputs: - runner: - required: false - default: 'ubuntu-latest' - type: string - # List of prerequisite Ubuntu packages, separated by whitespace - packages: - required: false - type: string - -jobs: - codecov-grcov: - name: Generate code coverage - runs-on: ${{ inputs.runner }} - strategy: - fail-fast: true - steps: - - uses: actions/checkout@v7 - with: - repository: argumentcomputer/ci-workflows - persist-credentials: false - - uses: ./.github/actions/ci-env - - uses: ./.github/actions/install-deps - if: inputs.packages != '' - with: - packages: "${{ inputs.packages }}" - - uses: actions/checkout@v7 - with: - submodules: recursive - persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - with: - components: llvm-tools-preview - - uses: Swatinem/rust-cache@v2 - - uses: taiki-e/install-action@nextest - - name: Install cargo-llvm-cov - uses: taiki-e/install-action@cargo-llvm-cov - - name: Clean the workspace - run: cargo llvm-cov clean --workspace - - name: Build - run: cargo build --workspace --release - - name: Collect coverage data - run: cargo llvm-cov nextest --lcov --output-path lcov.info --profile ci --release --workspace - - name: Upload coverage data to codecov - uses: codecov/codecov-action@v7 - with: - files: lcov.info diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9e826ad..2add0e8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,20 +3,20 @@ name: Generate and deploy crate docs on: workflow_call: +permissions: {} + jobs: docs: name: Generate crate documentation runs-on: ubuntu-latest + # `contents: write` for the `gh-pages` deploy + permissions: + contents: write steps: - - uses: actions/checkout@v7 - with: - repository: argumentcomputer/ci-workflows - persist-credentials: false - - uses: ./.github/actions/ci-env - uses: actions/checkout@v7 with: persist-credentials: false - - uses: dtolnay/rust-toolchain@stable + - uses: actions-rust-lang/setup-rust-toolchain@v1 - name: Generate documentation env: RUSTDOCFLAGS: "--enable-index-page -Zunstable-options" diff --git a/.github/workflows/gpu-bench.yml b/.github/workflows/gpu-bench.yml deleted file mode 100644 index 539bce6..0000000 --- a/.github/workflows/gpu-bench.yml +++ /dev/null @@ -1,169 +0,0 @@ -# Run final tests only when attempting to merge, shown as skipped status checks beforehand -# Prerequisites -# - Self-hosted Nvidia GPU runner with `gpu-bench` tag in caller repo -# - `cuda` Cargo features -# - Pre-existing `gh-pages` branch -# - Run on `merge_group` trigger only -name: Comparative benchmarks on GPU - -on: - workflow_call: - inputs: - # List of prerequisite Ubuntu packages, separated by whitespace - packages: - required: false - type: string - -jobs: - # Run comparative benchmark against base branch, open issue on regression - gpu-benchmark: - runs-on: [self-hosted, gpu-bench] - steps: - # Set up GPU - - uses: actions/checkout@v7 - with: - repository: argumentcomputer/ci-workflows - persist-credentials: false - - uses: ./.github/actions/gpu-setup - with: - gpu-framework: 'cuda' - - uses: ./.github/actions/ci-env - - uses: ./.github/actions/install-deps - if: inputs.packages != '' - with: - packages: "${{ inputs.packages }}" - # `git-auto-commit-action` below pushes to `gh-pages` with the credentials - # this checkout persists, so they cannot be disabled here. - - uses: actions/checkout@v7 # zizmor: ignore[artipacked] - # Install dependencies - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - name: Install criterion - run: | - cargo install cargo-criterion - cargo install criterion-table - # Requires benchmarks to be formatted for `criterion-table` using `${REPOSITORY_NAME}_BENCH_OUTPUT=commit-comment` - # e.g. `$LURK_BENCH_OUTPUT=commit-comment` - - name: Set env vars - run: | - REPOSITORY_NAME=$(echo '${{ github.repository }}' | awk -F'/' '{ print toupper($2) }') - echo "${REPOSITORY_NAME}_BENCH_OUTPUT=commit-comment" | tee -a $GITHUB_ENV - echo "BASE_COMMIT=${{ github.event.merge_group.base_sha }}" | tee -a $GITHUB_ENV - echo "GPU_ID=$(echo $GPU_NAME | awk '{ print $NF }')" | tee -a $GITHUB_ENV - # Checkout gh-pages to check for cached bench result - - name: Checkout gh-pages - uses: actions/checkout@v7 - with: - ref: gh-pages - path: gh-pages - persist-credentials: false - - name: Check for cached bench result - id: cached-bench - run: | - if [ -f "$BASE_COMMIT-$GPU_ID.json" ] - then - echo "cached=true" | tee -a $GITHUB_OUTPUT - cp "$BASE_COMMIT-$GPU_ID.json" "../$BASE_COMMIT.json" - else - echo "cached=false" | tee -a $GITHUB_OUTPUT - fi - working-directory: ${{ github.workspace }}/gh-pages - # Checkout base branch for comparative bench - - uses: actions/checkout@v7 - if: steps.cached-bench.outputs.cached == 'false' - with: - ref: ${{ github.base_ref }} - path: ${{ github.base_ref }} - persist-credentials: false - - name: Run GPU bench on base branch - if: steps.cached-bench.outputs.cached == 'false' - run: | - # Run benchmark - cargo criterion --features "cuda" --message-format=json > "$BASE_COMMIT.json" - # Copy bench output to PR branch - cp "$BASE_COMMIT.json" .. - working-directory: ${{ github.workspace }}/${{ github.base_ref }} - - name: Run GPU bench on PR branch - run: | - cargo criterion --features "cuda" --message-format=json > ${{ github.sha }}.json - cp ${{ github.sha }}.json .. - working-directory: ${{ github.workspace }}/benches - - name: copy the benchmark template and prepare it with data - run: | - cp .github/tables.toml . - # Get CPU model - CPU_MODEL=$(grep '^model name' /proc/cpuinfo | head -1 | awk -F ': ' '{ print $2 }') - # Get num vCPUS - NUM_VCPUS="$(nproc --all) vCPUs" - # Get total RAM in GB - TOTAL_RAM=$(grep MemTotal /proc/meminfo | awk '{$2=$2/(1024^2); print int($2), "GB RAM";}') - - # Use conditionals to ensure that only non-empty variables are inserted - [[ ! -z "$GPU_NAME" ]] && sed -i "/^\"\"\"$/i $GPU_NAME" tables.toml - [[ ! -z "$CPU_MODEL" ]] && sed -i "/^\"\"\"$/i $CPU_MODEL" tables.toml - [[ ! -z "$NUM_VCPUS" ]] && sed -i "/^\"\"\"$/i $NUM_VCPUs" tables.toml - [[ ! -z "$TOTAL_RAM" ]] && sed -i "/^\"\"\"$/i $TOTAL_RAM" tables.toml - sed -i "/^\"\"\"$/i Workflow run: $GITHUB_SERVER_URL/$REPO/actions/runs/$RUN_ID" tables.toml - working-directory: ${{ github.workspace }} - env: - REPO: ${{ github.repository }} - RUN_ID: ${{ github.run_id }} - # Create a `criterion-table` and write in commit comment - - name: Run `criterion-table` - run: cat "$BASE_COMMIT.json" "$GITHUB_SHA.json" | criterion-table > BENCHMARKS.md - - name: Write bench on commit comment - uses: peter-evans/commit-comment@v4 - with: - body-path: BENCHMARKS.md - # Check for a slowdown >= 10%. If so, open an issue but don't block merge - - name: Check for perf regression - id: regression-check - run: | - regressions=$(awk -F'[*x]' '/slower/{print $12}' BENCHMARKS.md) - - echo $regressions - - for r in $regressions - do - if (( $(echo "$r >= 1.10" | bc -l) )) - then - exit 1 - fi - done - continue-on-error: true - # Not possible to use ${{ github.event.number }} with the `merge_group` trigger - - name: Get PR number from merge branch - env: - HEAD_REF: ${{ github.event.merge_group.head_ref }} - run: | - echo "PR_NUMBER=$(echo "$HEAD_REF" | sed -e 's/.*pr-\(.*\)-.*/\1/')" | tee -a $GITHUB_ENV - - name: Create file for issue - if: steps.regression-check.outcome == 'failure' - run: | - printf '%s\n' "Regression >= 10% found during merge for PR #$PR_NUMBER - Commit: $GITHUB_SHA - Workflow run: $GITHUB_SERVER_URL/$REPO/actions/runs/$RUN_ID" > ./_body.md - env: - REPO: ${{ github.repository }} - RUN_ID: ${{ github.run_id }} - - name: Open issue on regression - if: steps.regression-check.outcome == 'failure' - uses: peter-evans/create-issue-from-file@v6 - with: - title: ':rotating_light: Performance regression detected for PR #${{ env.PR_NUMBER }}' - content-filepath: ./_body.md - labels: | - P-Performance - automated issue - - name: Remove old base bench - run: | - rm "$BASE_COMMIT.json" - mv "$GITHUB_SHA.json" "$GITHUB_SHA-$GPU_ID.json" - working-directory: ${{ github.workspace }} - - name: Commit bench result to `gh-pages` branch if no regression - if: steps.regression-check.outcome != 'failure' - uses: stefanzweifel/git-auto-commit-action@v7 - with: - branch: gh-pages - commit_message: '[automated] GPU Benchmark from PR #${{ env.PR_NUMBER }}' - file_pattern: '${{ github.sha }}-${{ env.GPU_ID }}.json' diff --git a/.github/workflows/gpu-ci-cuda.yml b/.github/workflows/gpu-ci-cuda.yml deleted file mode 100644 index 8deae4e..0000000 --- a/.github/workflows/gpu-ci-cuda.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Prerequisites -# - Self-hosted Nvidia GPU runner with CUDA enabled -# - Runner attached in caller repo with `gpu-ci` label -# - `cuda` Cargo feature -name: GPU CI Tests with CUDA - -on: - # We expect dependents to call this with the `pull_request` and `merge_group` trigger - # This will show as a skipped status check on the PR, and then run once when attempting to merge - workflow_call: - inputs: - # comma-separated list of features to run in addition to `cuda` - features: - required: false - default: "" - type: string - # List of prerequisite Ubuntu packages, separated by whitespace - packages: - required: false - type: string - -jobs: - cuda: - name: Rust tests on CUDA - if: github.event_name != 'pull_request' || github.event.action == 'enqueued' - runs-on: [self-hosted, gpu-ci] - steps: - - uses: actions/checkout@v7 - with: - repository: argumentcomputer/ci-workflows - persist-credentials: false - - uses: ./.github/actions/gpu-setup - with: - gpu-framework: 'cuda' - - uses: ./.github/actions/ci-env - - uses: ./.github/actions/install-deps - if: inputs.packages != '' - with: - packages: "${{ inputs.packages }}" - - uses: actions/checkout@v7 - with: - submodules: recursive - persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@nextest - - uses: Swatinem/rust-cache@v2 - - name: CUDA tests - env: - FEATURES: ${{ inputs.features }} - run: | - cargo nextest run --profile ci --cargo-profile dev-ci --features "cuda,$FEATURES" diff --git a/.github/workflows/gpu-ci-opencl.yml b/.github/workflows/gpu-ci-opencl.yml deleted file mode 100644 index 2c3e69c..0000000 --- a/.github/workflows/gpu-ci-opencl.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Prerequisites -# - Self-hosted Nvidia GPU runner with CUDA enabled -# - Runner attached in caller repo with `gpu-ci` label -# - `cuda` and `opencl` Cargo features -name: GPU CI Tests with OpenCL - -on: - # We expect dependents to call this with the `pull_request` and `merge_group` trigger - # This will show as a skipped status check on the PR, and then run once when attempting to merge - workflow_call: - inputs: - # comma-separated list of features to run in addition to `cuda`/`opencl` - features: - required: false - default: "" - type: string - # List of prerequisite Ubuntu packages, separated by whitespace - packages: - required: false - type: string - -jobs: - opencl: - name: Rust tests on OpenCL - if: github.event_name != 'pull_request' || github.event.action == 'enqueued' - runs-on: [self-hosted, gpu-ci] - steps: - - uses: actions/checkout@v7 - with: - repository: argumentcomputer/ci-workflows - persist-credentials: false - - uses: ./.github/actions/gpu-setup - with: - gpu-framework: 'opencl' - - uses: ./.github/actions/ci-env - - uses: ./.github/actions/install-deps - if: inputs.packages != '' - with: - packages: "${{ inputs.packages }}" - - uses: actions/checkout@v7 - with: - submodules: recursive - persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@nextest - - uses: Swatinem/rust-cache@v2 - - name: OpenCL tests - env: - FEATURES: ${{ inputs.features }} - run: | - cargo nextest run --profile ci --cargo-profile dev-ci --features "cuda,opencl,$FEATURES" diff --git a/.github/workflows/licenses-audits.yml b/.github/workflows/licenses-audits.yml index 54d13bd..d4547f4 100644 --- a/.github/workflows/licenses-audits.yml +++ b/.github/workflows/licenses-audits.yml @@ -4,8 +4,12 @@ name: cargo-deny on: workflow_call: +permissions: {} + jobs: cargo-deny: + permissions: + contents: read name: cargo-deny (advisories, licenses, bans, ...) runs-on: ubuntu-latest steps: diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml deleted file mode 100644 index 0572139..0000000 --- a/.github/workflows/links-check.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Check documentation links - -on: - workflow_call: - inputs: - # Whether or not to error on failure - # If false, opens an issue instead - fail-fast: - required: false - default: true - type: boolean - -permissions: - contents: read - -jobs: - linkChecker: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - name: Link Checker - id: lychee - uses: lycheeverse/lychee-action@v2.9.0 - with: - fail: ${{ inputs.fail-fast }} - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - # lychee stopped exporting `lychee_exit_code` to the environment in v2; - # the exit code is only available as a step output. - - name: Open issue on failure if `fail-fast` input is false - if: steps.lychee.outputs.exit_code != 0 && inputs.fail-fast != true - uses: peter-evans/create-issue-from-file@v6 - with: - title: Link Checker Report - content-filepath: ./lychee/out.md - labels: report, automated issue diff --git a/.github/workflows/lints.yml b/.github/workflows/lints.yml deleted file mode 100644 index c382359..0000000 --- a/.github/workflows/lints.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Check lints and code quality - -on: - workflow_call: - inputs: - # List of prerequisite Ubuntu packages, separated by whitespace - packages: - required: false - type: string - # Runs `cargo fmt +nightly`, for use with nightly config options in `rustfmt.toml` - nightly-fmt: - required: false - type: boolean - -jobs: - # Rustfmt, clippy, and doctests - lints: - runs-on: ubuntu-latest - strategy: - fail-fast: false - steps: - - uses: actions/checkout@v7 - with: - repository: argumentcomputer/ci-workflows - persist-credentials: false - - uses: ./.github/actions/ci-env - - uses: ./.github/actions/install-deps - if: inputs.packages != '' - with: - packages: "${{ inputs.packages }}" - - uses: actions/checkout@v7 - with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt, clippy - - uses: dtolnay/rust-toolchain@nightly - if: inputs.nightly-fmt - with: - components: rustfmt - - uses: Swatinem/rust-cache@v2 - - name: Check Rustfmt Code Style - run: | - if [[ "${{ inputs.nightly-fmt }}" == "true" ]]; then - cargo +nightly fmt --all -- --check - else - cargo fmt --all -- --check - fi - - name: Check clippy warnings - run: | - if cargo --list | grep -q xclippy; then - cargo xclippy -Dwarnings - else - cargo clippy -Dwarnings - fi - - name: Doctests - run: cargo test --doc --workspace diff --git a/.github/workflows/msrv.yml b/.github/workflows/msrv.yml deleted file mode 100644 index b8a0391..0000000 --- a/.github/workflows/msrv.yml +++ /dev/null @@ -1,35 +0,0 @@ -# NOTE: Does not currently work with Cargo workspaces -# See https://github.com/argumentcomputer/ci-workflows/issues/8 -name: Check MSRV - -on: - workflow_call: - inputs: - # List of prerequisite Ubuntu packages, separated by whitespace - packages: - required: false - type: string - -jobs: - # Check MSRV (aka `rust-version`) in `Cargo.toml` is valid - msrv: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - repository: argumentcomputer/ci-workflows - persist-credentials: false - - uses: ./.github/actions/ci-env - - uses: ./.github/actions/install-deps - if: inputs.packages != '' - with: - packages: "${{ inputs.packages }}" - - uses: actions/checkout@v7 - with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - name: Install cargo-msrv - run: cargo install cargo-msrv - - name: Check Rust MSRV - run: cargo msrv verify diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml deleted file mode 100644 index 4eca8e0..0000000 --- a/.github/workflows/repo-sync.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Sync changes from upstream repo - -on: - workflow_call: - inputs: - # Input must be formatted as "owner/repo", e.g. "argumentcomputer/lurk-rs" - repository: - required: true - type: string - # `gh repo sync` takes a single branch name, so the upstream and the - # mirror must share it. - branch: - required: false - default: 'main' - type: string - secrets: - TOKEN_APP_ID: - required: true - TOKEN_APP_PRIVATE_KEY: - required: true - -# `secrets.GITHUB_TOKEN` cannot carry the `workflow` scope, so it is rejected -# whenever an upstream commit touches `.github/workflows/**`. A GitHub App -# installation token with Workflows: write can push those commits. -permissions: {} - -jobs: - repo-sync: - name: Sync changes from upstream - runs-on: ubuntu-latest - steps: - - uses: actions/create-github-app-token@v3 - id: generate-token - with: - client-id: ${{ secrets.TOKEN_APP_ID }} - private-key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }} - permission-contents: write - permission-workflows: write - # `github.repository` is the caller's repo, not this one, and the token is - # scoped to it by default. - - name: repo-sync - run: gh repo sync ${{ github.repository }} --source ${INPUTS_REPOSITORY} --branch ${INPUTS_BRANCH} --force - env: - GH_TOKEN: ${{ steps.generate-token.outputs.token }} - INPUTS_REPOSITORY: ${{ inputs.repository }} - INPUTS_BRANCH: ${{ inputs.branch }} diff --git a/.github/workflows/rust-version-check.yml b/.github/workflows/rust-version-check.yml deleted file mode 100644 index 039b17b..0000000 --- a/.github/workflows/rust-version-check.yml +++ /dev/null @@ -1,53 +0,0 @@ -# Checks whether Rust version specified in `rust-toolchain.toml` is out of date with latest stable -# Compares the full `..` of `rustup show` with `rustup check` -# This is because the patch version will auto-update if unspecified in `rust-toolchain.toml` -name: Rust Version Check - -on: - workflow_call: - -jobs: - rust-version-check: - runs-on: ubuntu-latest - steps: - - name: Check out repository - uses: actions/checkout@v7 - with: - persist-credentials: false - - - name: Check out `ci-workflows` - uses: actions/checkout@v7 - with: - repository: argumentcomputer/ci-workflows - path: ci-workflows - persist-credentials: false - - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - - - name: Parse rust-toolchain.toml - run: echo "TOOLCHAIN_VERSION=$(rustup show | grep rustc | awk '{ print $2 }')" | tee -a $GITHUB_ENV - - - name: Get latest stable Rust version - run: echo "RUST_VERSION=$(rustup check | grep stable | awk '{print $(NF-2)}')" | tee -a $GITHUB_ENV - - - name: Compare Rust versions - run: | - if [[ $TOOLCHAIN_VERSION < $RUST_VERSION ]]; then - echo "VERSION_MISMATCH=true" | tee -a $GITHUB_ENV - else - echo "VERSION_MISMATCH=false" | tee -a $GITHUB_ENV - fi - - # Open issue if crate Rust version is out of date with latest stable - - uses: JasonEtco/create-an-issue@v2 - if: env.VERSION_MISMATCH == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TOOLCHAIN_VERSION: ${{ env.TOOLCHAIN_VERSION }} - RUST_VERSION: ${{ env.RUST_VERSION }} - WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - WORKFLOW_FILE: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/workflow - with: - update_existing: true - filename: ci-workflows/.github/templates/VERSION_CHECK.md diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..a39d984 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,138 @@ +# Smoke tests for this repo's composite actions +name: Test actions + +on: + push: + branches: main + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: {} + +jobs: + # Exercises the create/update/invalid-input paths against a stub `gh` that + # records its calls, so no real issues are touched + create-issue: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Install stub gh + run: | + mkdir -p "$RUNNER_TEMP/stub-bin" + cat > "$RUNNER_TEMP/stub-bin/gh" <<'EOF' + #!/usr/bin/env bash + echo "gh $*" >> "$GH_CALL_LOG" + case "$1 $2" in + "issue list") cat "$GH_ISSUES" ;; + "issue create") echo "https://github.com/example/repo/issues/1" ;; + esac + EOF + chmod +x "$RUNNER_TEMP/stub-bin/gh" + echo "$RUNNER_TEMP/stub-bin" >> "$GITHUB_PATH" + echo "GH_CALL_LOG=$RUNNER_TEMP/gh-calls.log" | tee -a "$GITHUB_ENV" + echo "GH_ISSUES=$RUNNER_TEMP/issues.json" | tee -a "$GITHUB_ENV" + echo "[]" > "$RUNNER_TEMP/issues.json" + - name: Create a new issue + uses: $/.github/actions/create-issue + with: + title: Test issue + labels: test-label + body: Test body + - name: Update an existing issue with the same title + run: | + echo '[{"number": 7, "title": "Test issue"}, {"number": 9, "title": "other"}]' > "$GH_ISSUES" + - uses: $/.github/actions/create-issue + with: + title: Test issue + body: Updated body + - name: Reject setting both body and body-file + id: invalid + continue-on-error: true + uses: $/.github/actions/create-issue + with: + title: Test issue + body: Test body + body-file: README.md + - name: Assert recorded gh calls + env: + INVALID_OUTCOME: ${{ steps.invalid.outcome }} + run: | + cat "$GH_CALL_LOG" + set -x + [[ "$INVALID_OUTCOME" == failure ]] + grep -qF -- "issue create --repo $GITHUB_REPOSITORY --title Test issue --body Test body" "$GH_CALL_LOG" + grep -qF -- "issue edit https://github.com/example/repo/issues/1 --repo $GITHUB_REPOSITORY --add-label test-label" "$GH_CALL_LOG" + grep -qF -- "issue edit 7 --repo $GITHUB_REPOSITORY --body Updated body" "$GH_CALL_LOG" + # The invalid input errors out before any gh call, so only the two + # earlier action runs listed issues + [[ "$(grep -cF -- "issue list" "$GH_CALL_LOG")" == 2 ]] + + # Runs the Rust CI actions on a generated fixture crate via the documented + # caller pattern: checkout, then toolchain setup, then the action + rust-actions: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Generate fixture crate + run: cargo init --lib --name fixture . + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + # Don't save caches from test fixtures + cache: false + - uses: $/.github/actions/lints + - uses: $/.github/actions/wasm + - uses: $/.github/actions/unused-deps + + # An up-to-date pin must not open an issue; an outdated pin must open one + # with the parsed versions. Issue calls go to the same stub `gh` as above. + rust-version-check: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Install stub gh + run: | + mkdir -p "$RUNNER_TEMP/stub-bin" + cat > "$RUNNER_TEMP/stub-bin/gh" <<'EOF' + #!/usr/bin/env bash + echo "gh $*" >> "$GH_CALL_LOG" + case "$1 $2" in + "issue list") echo "[]" ;; + "issue create") echo "https://github.com/example/repo/issues/1" ;; + esac + EOF + chmod +x "$RUNNER_TEMP/stub-bin/gh" + echo "$RUNNER_TEMP/stub-bin" >> "$GITHUB_PATH" + echo "GH_CALL_LOG=$RUNNER_TEMP/gh-calls.log" | tee -a "$GITHUB_ENV" + - name: Pin an up-to-date toolchain + run: printf '[toolchain]\nchannel = "stable"\n' > rust-toolchain.toml + - uses: $/.github/actions/rust-version-check + - name: Pin an outdated toolchain + run: | + printf '[toolchain]\nchannel = "1.70.0"\n' > rust-toolchain.toml + # Install it so `rustup show` reports it as the active version + rustup toolchain install 1.70.0 --profile minimal + - uses: $/.github/actions/rust-version-check + - name: Assert only the outdated pin opened an issue + run: | + touch "$GH_CALL_LOG" + cat "$GH_CALL_LOG" + set -x + [[ "$(grep -cF -- "issue create" "$GH_CALL_LOG")" == 1 ]] + grep -qF -- "issue create --repo $GITHUB_REPOSITORY --title chore: rust toolchain needs an upgrade" "$GH_CALL_LOG" + # The parsed toolchain version made it into the issue body + grep -qF -- "(1.70.0)" "$GH_CALL_LOG" diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml deleted file mode 100644 index 209ec20..0000000 --- a/.github/workflows/typos.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Check for typos - -on: - # Supported triggers: - # `workflow_dispatch` and nightly, e.g.: - # schedule: - # - cron: "0 0 * * *" - workflow_call: - -jobs: - typo-check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - name: Install typos binary - run: cargo +stable install typos-cli - - name: Check typos and write suggestions - id: typo-check - run: | - typos --write-changes > _typos.txt || true - if [[ `git status --porcelain --untracked-files=no` ]]; then - echo "typos=true" | tee -a $GITHUB_OUTPUT - else - echo "typos=false" | tee -a $GITHUB_OUTPUT - fi - - name: Create file for PR - if: steps.typo-check.outputs.typos == 'true' - run: | - printf '%s\n' "Fixes typos found by running \`typos --write-changes\` - Commit: ${{ github.sha }} - Workflow run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" > _body.md - if [[ -s _typos.txt ]]; then - printf "## Unfixed typos\n" >> _body.md - printf "Reviewers: Please manually fix & commit the following typos:\n\`\`\`\n" >> _body.md - cat _typos.txt >> _body.md - printf "\`\`\`\n" >> _body.md - rm _typos.txt - fi - printf '%s\n' "> [!NOTE] - > If a false positive is found, please add it to \`_typos.toml\` as per the [documentation](https://github.com/crate-ci/typos/tree/master?tab=readme-ov-file#false-positives)" >> _body.md - # Checks which file types should be committed with typo corrections - # Git pathspecs cause errors if the given pattern doesn't exist, e.g. `git add -- **/*.txt` without any `.txt` files - - name: Check for common file types - if: steps.typo-check.outputs.typos == 'true' - id: file-types - run: | - FILE_PATHS=":!*\_body.md,$(git status --porcelain | awk -F. '{OFS=""; print "**/*."$NF}' | sort -u | paste -sd,)" - echo "paths=$FILE_PATHS" | tee -a $GITHUB_OUTPUT - - uses: actions/create-github-app-token@v3 - if: steps.typo-check.outputs.typos == 'true' - id: generate-token - with: - client-id: ${{ secrets.TOKEN_APP_ID }} - private-key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }} - permission-contents: write - permission-pull-requests: write - - name: Create pull request - uses: peter-evans/create-pull-request@v8 - if: steps.typo-check.outputs.typos == 'true' - with: - token: ${{ steps.generate-token.outputs.token }} - commit-message: '[automated] Fix typos' - title: '[automated] Fix typos' - branch: 'patch/fix-typos' - delete-branch: true - body-path: ./_body.md - labels: automated issue, documentation - # Required in order to exclude the `_body.md` file from the PR - add-paths: ${{ steps.file-types.outputs.paths }} diff --git a/.github/workflows/unused-deps.yml b/.github/workflows/unused-deps.yml deleted file mode 100644 index 9518f1e..0000000 --- a/.github/workflows/unused-deps.yml +++ /dev/null @@ -1,57 +0,0 @@ -# Runs unused dependency check for crate consumers. - -name: Unused dependency check - -on: - # we expect dependents to call this on a nightly basis - # schedule: - # - cron: "0 0 * * *" - workflow_call: - inputs: - # comma-separated list of features to check - features: - required: false - default: "" - type: string - packages: - required: false - type: string - -env: - CARGO_TERM_COLOR: always - -jobs: - unused-dependencies: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - uses: actions/checkout@v7 - with: - repository: argumentcomputer/ci-workflows - path: ci-workflows - persist-credentials: false - - uses: ./ci-workflows/.github/actions/install-deps - if: inputs.packages != '' - with: - packages: "${{ inputs.packages }}" - - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@cargo-udeps - # Normally running cargo-udeps requires use of a nightly compiler - # In order to have a more stable and less noisy experience, lets instead - # opt to use the stable toolchain specified via the 'rust-toolchain' file - # and instead enable nightly features via 'RUSTC_BOOTSTRAP' - - name: run cargo-udeps - run: RUSTC_BOOTSTRAP=1 cargo udeps --workspace --all-targets --features "${INPUTS_FEATURES}" - env: - INPUTS_FEATURES: ${{ inputs.features }} - - uses: JasonEtco/create-an-issue@v2 - if: ${{ failure() }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WORKFLOW_URL: - ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - with: - update_existing: true - filename: ci-workflows/.github/templates/UNUSED_DEPS_ISSUE.md diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml deleted file mode 100644 index cfedb25..0000000 --- a/.github/workflows/wasm.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Wasm build - -on: - workflow_call: - inputs: - # List of prerequisite Ubuntu packages, separated by whitespace - packages: - required: false - type: string -jobs: - wasm-build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - repository: argumentcomputer/ci-workflows - persist-credentials: false - - uses: ./.github/actions/ci-env - - uses: ./.github/actions/install-deps - if: inputs.packages != '' - with: - packages: "${{ inputs.packages }}" - - uses: actions/checkout@v7 - with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - run: rustup target add wasm32-unknown-unknown - - name: Wasm build - run: cargo build --target wasm32-unknown-unknown