Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
name: Test
on: [push]
on:
push:
branches: [main]
tags: ["[0-9]+.[0-9]+.[0-9]+"]
pull_request: {}

permissions:
contents: read

concurrency:
group: test-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
release-site-tests:
Expand All @@ -14,6 +25,8 @@ jobs:
python-version: '3.13'
- run: python3 -m unittest discover -s tools -p release_site_test.py
- run: python3 -m unittest discover -s tools -p release_notes_test.py
- run: python3 -m unittest discover -s tools -p site_artwork_test.py
- run: python3 -m unittest discover -s tools -p infrastructure_test.py
- name: Verify configured documentation and generated links
env:
GH_TOKEN: ${{ github.token }}
Expand Down Expand Up @@ -47,6 +60,7 @@ jobs:
- uses: actions/checkout@v7
- uses: bazel-contrib/setup-bazel@0.19.0
with:
cache-save: ${{ github.ref == 'refs/heads/main' }}
bazelisk-cache: true
# Store build cache per workflow.
disk-cache: ${{ github.workflow }}
Expand All @@ -57,6 +71,9 @@ jobs:
bazel test //...

done:
permissions:
contents: read
actions: read
needs: [release-site-tests, pre-commit, test]
if: always()
runs-on: ubuntu-latest
Expand All @@ -82,6 +99,25 @@ jobs:
exit 1
fi
echo "done covers all $(grep -c . <<<"${declared}") workflow jobs."
- name: Report repository cache usage
if: always() && github.ref == 'refs/heads/main'
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
if gh cache list --limit 10000 --json key,ref,sizeInBytes >cache-inventory.json; then
python3 - <<'PYTHON' >>"${GITHUB_STEP_SUMMARY}"
import json
caches = json.load(open("cache-inventory.json", encoding="utf-8"))
size = sum(entry["sizeInBytes"] for entry in caches)
print(f"Actions caches: {len(caches)} entries, {size:,} compressed bytes.")
print("\nLargest entries:")
for entry in sorted(caches, key=lambda item: item["sizeInBytes"], reverse=True)[:10]:
print(f"- {entry['key']}: {entry['sizeInBytes']:,} bytes ({entry['ref']})")
PYTHON
else
echo "Cache inventory unavailable." >>"${GITHUB_STEP_SUMMARY}"
fi
- name: Fail if any dependency did not succeed
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: exit 1
1 change: 1 addition & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ jobs:
fi
mkdir -p public
rsync --archive --exclude='.git' site/ public/
python3 source/tools/site_artwork.py source/docs/assets public
- uses: actions/configure-pages@v6
- uses: actions/upload-pages-artifact@v5
with:
Expand Down
14 changes: 9 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@ name: Release
on:
push:
tags:
- "*.*.*"
- "[0-9]+.[0-9]+.[0-9]+"

jobs:
release:
uses: bazel-contrib/.github/.github/workflows/release_ruleset.yaml@v7
uses: bazel-contrib/.github/.github/workflows/release_ruleset.yaml@1d798ff015ed0696433e01e2c3ccbb2abefadad7 # v7.7.0
permissions:
attestations: write
contents: write
id-token: write
with:
release_files: bashtest-*.tar.gz
# Keep the GitHub release provisional until the secondary BCR
# publication process has completed.
prerelease: true
# action-gh-release v3 stages standard releases as drafts until all assets
# are uploaded, then publishes once. Immutable releases cannot be promoted
# from prerelease after publication.
prerelease: false
# The upstream release workflow cannot restrict saves to main. Keep its
# separate release build from populating tag-scoped caches.
mount_bazel_caches: false

# Mirror the release to the Bazel Central Registry (replaces the retired
# publish-to-bcr GitHub App). See .github/workflows/publish.yaml.
Expand Down
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ repos:

- repo: local
hooks:
- id: infrastructure-tests
name: Test publishing and CI policy
language: system
entry: python3 -m unittest discover -s tools -p '*_test.py'
files: ^(tools/|docs/assets/|release-site\.json$|\.github/workflows/)
pass_filenames: false
- id: uncomment-bazelmod-includes
name: uncomment-bazelmod-includes
description: |
Expand Down
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Agent and contributor rules - bashtest

These rules apply to human and automated contributors. `RULES.md` owns code layout and API
compatibility; `STYLE_SH.md` owns shell conventions; `GIT_RULES.md` owns branch and PR operations.

Run `bazel test //...`, `python3 -m unittest discover -s tools -p '*_test.py'`, and
`pre-commit run --all-files` before proposing repository-wide changes. Add regression tests for
behavior changes. Preserve the supported operating systems in CI and keep direct Bazel dependencies
explicit. Keep developer-only dependencies separate from the published module where supported.

Never weaken lint rules or required checks to make CI pass. Do not commit Bazel outputs, local rc
files, caches, or generated release artifacts. Format Starlark with the pinned Buildifier hooks.
Use the existing shell test framework and its diagnostics. Keep public behavior documented in the
same change. Keep Markdown tables vertically aligned; generated docs retain their source of truth.

Versions in `MODULE.bazel` and `CHANGELOG.md` must agree. Use `tools/trigger_release.sh` for numeric
semantic-version release tags. Preserve the existing immutable GitHub release and BCR publication
flow. [Infrastructure guidance](docs/infrastructure.md) explains CI, caching, and site publishing.
7 changes: 6 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ All contributions are generally welcome as long as they fit in with the concepts

# Code Rules

All code must adhere to the [RULES.md](RULES.md) and mostly follows the [Google style](https://google.github.io/styleguide/). Where it diverges, clang-tidy rules are in effect as much as possible.
All code must adhere to the [RULES.md](RULES.md) and mostly follows the [Google style](https://google.github.io/styleguide/). Where it diverges, the checked-in pre-commit rules are authoritative.

# Workflow and language rules

Follow [AGENTS.md](AGENTS.md), [GIT_RULES.md](GIT_RULES.md), and [STYLE_SH.md](STYLE_SH.md).
Run `bazel test //...` and keep behavior changes covered by regression tests.

# Run pre-commit

Expand Down
105 changes: 105 additions & 0 deletions GIT_RULES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Git and pull-request rules

These rules are the source of truth for branch, pull-request, CI, and merge operations in this
repository. They apply to human and automated contributors. Repository protections are constraints
to satisfy, never obstacles to bypass.

## State-changing operations

Before pushing, rebasing, retargeting, merging, closing, reopening, or otherwise changing GitHub
state:

1. Fetch the current base branch and pull-request head.
2. Read the pull request's current head SHA, base, mergeability, review state, and required checks
from GitHub.
3. Confirm that the operation advances the stated goal and does not invalidate a ready pull request
or another change that depends on it.
4. Identify the exact branch, pull request, or run being changed. Never rely on a stale local or
remembered state.

Keep local and remote state distinct. A local branch containing current `main` does not prove that
the remote pull-request head is current. Push synchronization commits before treating later local
validation as authoritative.

Do not force-push, rebase, retarget, merge another branch into a pull request, cancel runs, or alter
a ready pull request speculatively. Such changes are appropriate only to resolve a demonstrated
failure, conflict, or dependency, and require validation of the resulting head.

## Pull-request descriptions

Every pull-request description has two layers, in this order:

1. A short human-readable explanation of the outcome and why it matters.
2. An `## AG;DR` section containing the implementation details, reasoning, validation, portability
notes, dependencies, and known limitations needed by reviewers or a future contributor.

Update the detailed section whenever a commit changes the implementation or validation. Keep the
human summary stable unless the outcome or motivation changes.

Descriptions must identify dependencies on other pull requests and any required merge order. Do
not claim that a check passed unless it ran against the current pushed head in the applicable
context.

## Review readiness and merging

A pull request is ready to merge only when all of the following are true:

- its intended base and head are current;
- it is approved and mergeable;
- all required checks for its current head have completed successfully;
- its description reflects the complete change;
- no unresolved dependency requires another pull request to merge first.

When a pull request is ready, merge it without changing its head, base, commits, or branch. Do not
restart, duplicate, bypass, transplant, or substitute required checks. Checks belong to their exact
pull-request context even when another commit has the same tree.

Enable auto-merge only after confirming the correct base and head, approval, mergeability, and
successful required checks. Auto-merge is a convenience, not a substitute for verifying readiness.

## Dependency ordering

Prefer small, independently reviewable pull requests. For a sequential tracked effort, keep one
pull request as the active merge gate and start the next item from updated `main` only after that
gate merges.

When changes must be stacked, record both ancestry and semantic dependencies. If a ready base has
one child, merge the base first; after GitHub retargets the child, require the child's checks to run
again against its new base. Do not delay a ready base for a child's now-obsolete pre-retarget run.

For multiple open pull requests, inspect more than their declared bases. Account for shared files,
APIs, build configuration, workflows, generated artifacts, and validation behavior. Merge independent
roots without invalidating ready work; serialize changes that overlap or alter one another's CI.
After every merge, push, retarget, or new commit, refresh GitHub state and recompute the order.

## CI and conflict recovery

Treat a failure as evidence to investigate, not a reason to weaken or bypass policy. Inspect the
failing job and reproduce it locally where practical. Fix the cause or document a narrowly scoped
exception permitted by repository policy, then rerun validation on the resulting pushed head.

For a conflicted pull request:

1. Verify that it is not already ready to merge.
2. Refresh its base and identify the exact conflicting changes.
3. Resolve the conflict on that pull request's branch, preserving compatible behavior.
4. Run validation appropriate to the combined change and push the resolution.
5. Update the description and treat all previous checks for the old head as obsolete.

Cancel only runs that are demonstrably obsolete. A slow, queued, or temporarily failing run does
not by itself justify replacing the head or starting duplicate runs.

## Completion checks

After a pull request merges:

1. Confirm GitHub reports it as merged and record the merge commit.
2. Verify that `main` contains the intended change.
3. Inspect the resulting `main` CI and address any regression before declaring the work complete or
advancing a dependent change.
4. Refresh remaining pull requests because bases, conflicts, and required checks may have changed.
5. Remove or leave remote branches according to repository settings; never delete unmerged work
without explicit authorization.

Work is complete only when the intended change is present on `main`, required post-merge automation
is healthy, and no promised dependent action remains.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# bashtest.sh - A Bazel shell test runner.
# bashtest.sh - A Bazel shell test runner. <img src="docs/assets/mboworks-logo.png" alt="MBO Works" width="64" height="64" align="right">

[Release website](https://mboworks.github.io/bashtest/)
[Release website](https://mboworks.github.io/bashtest/) · [Infrastructure and publishing](docs/infrastructure.md)

This shell test library provides Bazel macro rules to simplify shell testing.

Expand Down
3 changes: 3 additions & 0 deletions RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ Some rules for the code layout and its development.
* have documentation.
* API changes that are not backwards compatible should not occur in minor version changes.
* Undocumented and private/internal APIs may be changed in any way at any time.

Shared contributor workflow is in [AGENTS.md](AGENTS.md), branch and PR policy in
[GIT_RULES.md](GIT_RULES.md), and shell conventions in [STYLE_SH.md](STYLE_SH.md).
62 changes: 62 additions & 0 deletions STYLE_SH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Shell style

Follow the [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html) unless
this document or repository tooling says otherwise.

## Tooling

- Use Bash. Executable scripts start with `#!/usr/bin/env bash`, followed by the licence header.
- Format with the pinned `beautysh` pre-commit hook.
- Run ShellCheck and explain necessary local suppressions next to the affected code.
- Use `set -euo pipefail` in scripts unless a documented compatibility constraint prevents it.
- Quote expansions unless word splitting is intentional and documented.
- Make function variables `local` and use lower-case names for locals and functions.

The formatter and linter versions are pinned in [`.pre-commit-config.yaml`](.pre-commit-config.yaml).

## Functions and state

- A function returns data on standard output and diagnostics on standard error.
- Do not return results by mutating caller variables, global counters, the working directory, shell
options, or traps.
- A function whose stated purpose is an external effect may perform that effect; keep its scope
narrow and explicit.
- Avoid `eval`, `printf -v`, and string-built command lines. Use arrays for commands and arguments.
- Prefer small functions with one clear responsibility.

```sh
make_tree() {
local root
root="$(mktemp -d)"
mkdir -p "${root}/src"
echo "${root}"
}

tree="$(make_tree)"
```

## Conditions and loops

- Prefer `[[ ... ]]` for string and file tests and `(( ... ))` for arithmetic.
- Use `case` for multi-way string matching.
- Read lines with `while IFS= read -r line`; do not lose backslashes or surrounding whitespace.
- Do not parse structured data with fragile `grep`/`sed` pipelines when `jq`, `yq`, or a small
checked-in Python tool provides a reliable parser.

## Temporary files and cleanup

- Tests put temporary files below `${BASHTEST_TMPDIR}` and use `test_tmpdir name` for retained fixtures.
The framework owns their cleanup; test bodies must not add cleanup traps or recursive deletion.
- Quote and validate deletion targets. Never recursively delete an unresolved variable, a broad
workspace path, or a user directory.
- Framework/standalone scripts may use a cleanup trap only for resources they own, after the path is known.
- Bazel tests use `${TEST_TMPDIR}` for target-owned temporary output and resolve runfiles through
`${TEST_SRCDIR}` and `${TEST_WORKSPACE}`.

## Portability

- Support the Bash versions available on the macOS and Linux runners in the CI matrix.
- Do not assume GNU-only flags in scripts that run on macOS; branch on capabilities when necessary.
- Avoid `mapfile` where a script must run under the system Bash shipped with macOS.
- Preserve upper-case names for environment and Bazel runfile variables.
- Check required external programs early and fail with a useful message.
Binary file added docs/assets/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/favicon.ico
Binary file not shown.
Binary file added docs/assets/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/mboworks-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions docs/infrastructure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Infrastructure and publishing

Adapted from [proto PR 100](https://github.com/mboworks/proto/pull/100) and xff PRs 835–848,
excluding 841. This repository keeps its Starlark/shell test pipeline and native setup-bazel
cache support. C++ clang-tidy orchestration, sanitizer caches, and coverage history ordering do
not apply to its current jobs.

## CI and caching

Main and numeric release tags run push validation; branches run in pull-request context,
including forks, without duplicating the complete matrix on each push. Only superseded PR runs
are cancelled. The final gate retains release-site tests, pre-commit, and every test-matrix cell.

The pinned setup-bazel action retains native platform-specific cache paths, including Windows
where supported. In the Test workflow, PRs and release tags restore caches; only main saves them.
The separate upstream Release build disables cache mounting because it cannot restrict writes
to main. Bazelisk and
repository downloads remain cached because these small projects do not download the C++ LLVM
payload that motivated compiled-output-only caching in proto and xff. The action's existing
OS/architecture and build/dependency-hash keys remain intact. Exact cache hits are immutable;
source-only edits need not refresh these small build-configuration caches.

The final main job reports compressed repository cache usage and the ten largest entries.
Use these measurements before introducing stricter budgets or custom eviction. A missing
inventory is reported without replacing test failures. This inventory includes all refs and
may briefly lag post-job cache uploads; verify later runs before claiming savings.

## Publishing

The release publisher uses trusted main tooling and the tagged documentation snapshot.
The shared 64-pixel README logo and favicons match MBO Works' other repositories.
Favicons are inserted after staging the complete deployment copy; retained release snapshots
stay unchanged. Decoration is idempotent and resolves links from nested pages.

The shared release workflow is pinned to the same v7.7.0 commit as proto. Standard releases
are staged as drafts until archives and attestations are uploaded, then published once. Published
immutable prereleases cannot later be promoted. BCR publication follows release success and can
be retried independently; its failure does not require recreating a release or moving its tag.
Release helpers, numeric tags, and version agreement keep their existing contracts. No module
dependency versions change in this rollout.

## Contributor rules and verification

`AGENTS.md`, `GIT_RULES.md`, and `STYLE_SH.md` synchronize applicable shared rules. `RULES.md`
retains this repository's layout and API promises. Beautysh remains the shell formatter; no C++
style guide or competing shell formatter is introduced.

Run `bazel test //...`, `python3 -m unittest discover -s tools -p '*_test.py'`, and
`pre-commit run --all-files`. CI also builds the configured release documentation to validate
links and mappings before publication.
Loading
Loading