From c446dcbe0c54b58b73a85549406d17fcce199da4 Mon Sep 17 00:00:00 2001 From: Simon Dick Date: Wed, 2 Sep 2026 14:02:19 +0100 Subject: [PATCH] Add tag-driven release process (midge pattern) src/version.h is now the single source of truth for the version and the $VER build string (DEVSOAK_VERSTAG), replacing the hand-edited literal in main.c. scripts/verify-version.sh gates a release tag against src/version.h and devsoak.readme (including a date-bump check) before .github/workflows/release.yml hands off to sidick/amiga-workflows' aminet-release.yml for the GitHub Release + gated Aminet (dev/misc) publish. Makefile gains a `dist` target that rebuilds the binary, greps it for the exact $VER string to catch a stale build, and packages devsoak/devsoak.quirks/README.md/LICENSE into dist/devsoak.lha via a pinned lha built from source (same rationale as midge: distro lha packages are extract-only or don't compile). Also adds the missing LICENSE (BSD 2-Clause, matching devsoak.readme's existing licence claim) and a Releases section in README.md. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01EbDNgMg5EyWbEBRUvAoqP4 --- .github/workflows/release.yml | 38 ++++++++++++++++++++++ .gitignore | 1 + LICENSE | 25 +++++++++++++++ Makefile | 41 ++++++++++++++++++++++-- README.md | 29 +++++++++++++++++ devsoak.readme | 59 +++++++++++++++++++++++++++++++++++ scripts/verify-version.sh | 40 ++++++++++++++++++++++++ src/main.c | 4 +-- src/version.h | 24 ++++++++++++++ 9 files changed, 257 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 LICENSE create mode 100644 devsoak.readme create mode 100755 scripts/verify-version.sh create mode 100644 src/version.h diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0e889da --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,38 @@ +name: Release + +# Tag-driven releases: a release PR bumps src/version.h + devsoak.readme +# through normal review/CI, then pushing the matching v tag runs +# this. verify-version confirms the pushed tag actually matches those two +# files; release then delegates the build -> GitHub release -> Aminet- +# upload-behind-required-review pipeline to sidick/amiga-workflows' +# aminet-release.yml. The $VER-embedded-in-binary check lives in the +# Makefile's own `dist` target. + +on: + push: + tags: ['v*'] + +jobs: + verify-version: + name: Verify tag matches src/version.h / devsoak.readme + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - run: ./scripts/verify-version.sh "${{ github.ref_name }}" + + release: + name: Build, release, and (on approval) publish to Aminet + needs: verify-version + # A reusable workflow's own `permissions:` can only narrow, never + # widen, what the calling job already has - aminet-release.yml's + # `gh release create` step needs contents: write, so it has to be + # granted here too, not just relied on inside that workflow. + permissions: + contents: write + uses: sidick/amiga-workflows/.github/workflows/aminet-release.yml@v1 + with: + tag: ${{ github.ref_name }} + project-name: devsoak + lha-path: dist/devsoak.lha + readme-path: dist/devsoak.readme + category: dev/misc diff --git a/.gitignore b/.gitignore index a850571..c250898 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ devsoak *.hdf *.uaem site/ +dist/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fd6e542 --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2026, Simon Dick + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile index b045c4c..16dad65 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ TARGET := devsoak DOCKER_IMAGE := stefanreinauer/amiga-gcc:gcc-v16.1 HOST_CC := $(shell command -v m68k-amigaos-gcc 2>/dev/null) -.PHONY: all clean version devsoak-native +.PHONY: all clean version devsoak-native dist ifeq ($(HOST_CC),) @@ -52,4 +52,41 @@ $(OBJDIR): mkdir -p $(OBJDIR) clean: - rm -rf $(OBJDIR) $(TARGET) + rm -rf $(OBJDIR) $(TARGET) dist + +# --- dist: assemble the Aminet upload pair (archive + .readme) -------------- +# +# Homebrew's/Ubuntu's `lha` is Lhasa (extract-only) and the last real lha +# *release* tag no longer builds with modern compilers, so a pinned master +# commit is built from source into dist/tools/ instead. Override with a +# known-good archiver: `make dist LHA=/path/to/real/lha`. +LHA_REPO := https://github.com/jca02266/lha.git +LHA_COMMIT := 86094cb56aba34de45668f39f74fcfb61e9d7fb6 +LHA ?= dist/tools/lha + +dist/tools/lha: + @mkdir -p dist/tools + rm -rf dist/tools/lha-src + git clone -q $(LHA_REPO) dist/tools/lha-src + cd dist/tools/lha-src && \ + git -c advice.detachedHead=false checkout -q $(LHA_COMMIT) && \ + autoreconf -fi >/dev/null 2>&1 && ./configure >/dev/null && \ + $(MAKE) >/dev/null + cp dist/tools/lha-src/src/lha dist/tools/lha + rm -rf dist/tools/lha-src + +# Rebuilds the binary itself (the release workflow's dist job runs `make +# dist` standalone). The $VER grep confirms the binary just built embeds the +# CURRENT src/version.h DEVSOAK_VERSION; the release workflow's tag-vs-source +# check (scripts/verify-version.sh) separately confirms the tag matches +# src/version.h, closing the loop: tag == src/version.h == the binary. +dist: $(LHA) + rm -f $(TARGET) + $(MAKE) all + @v=$$(sed -n 's/^#define DEVSOAK_VERSION[[:space:]]*"\(.*\)"$$/\1/p' src/version.h); \ + grep -aqF "\$$VER: devsoak $$v (" $(TARGET) || { echo "dist: $(TARGET) lacks \"\$$VER: devsoak $$v (...)\" - stale build/?"; exit 1; } + rm -rf dist/devsoak + mkdir -p dist/devsoak + cp $(TARGET) devsoak.quirks README.md LICENSE dist/devsoak/ + cp devsoak.readme dist/ + cd dist && $(abspath $(LHA)) aq devsoak.lha devsoak diff --git a/README.md b/README.md index 0004eeb..a9437d6 100644 --- a/README.md +++ b/README.md @@ -467,3 +467,32 @@ partitions on the driver and run filesystem-level stress (e.g. FileSystemStressTest from Aminet) while devsoak works a separate range of the same unit — filesystems pick different command dialects and catch "works with everything except PFS3" bugs. + +## Releases + +`src/version.h` is the single source of truth for the version: bump +`DEVSOAK_VERSION` and `DEVSOAK_VERSION_DATE` there and the matching +`Version:` field in `devsoak.readme`, land that as a normal reviewed PR, +then push a `v` tag (e.g. `v0.1`) to `main`. That tag drives +`.github/workflows/release.yml`: + +1. `scripts/verify-version.sh` refuses the release if the tag doesn't match + both `src/version.h` and `devsoak.readme`, or if the `$VER` date wasn't + bumped since the previous release. +2. `make dist` rebuilds the binary, greps it for the exact + `$VER: devsoak (...)` string (a stale `obj/`/`devsoak` would + otherwise ship silently), and packs `devsoak`, `devsoak.quirks`, + `README.md` and `LICENSE` into `dist/devsoak.lha` alongside + `dist/devsoak.readme`. +3. A GitHub Release is published with the archive attached, then (behind a + required-reviewer gate on the `aminet` environment) the same archive is + uploaded to Aminet under `dev/misc`. + +The `$VER` string (`$VER: devsoak ()`) is what AmigaOS's +`Version` shell command and utilities like VersionScanner or Aminet's own +tooling read; check it with `Version devsoak FULL` or, from a host build, +`strings devsoak | grep '\$VER'`. + +## License + +BSD 2-Clause. See [`LICENSE`](LICENSE). diff --git a/devsoak.readme b/devsoak.readme new file mode 100644 index 0000000..16f1a6a --- /dev/null +++ b/devsoak.readme @@ -0,0 +1,59 @@ +Short: Destructive soak/correctness tester for trackdisk drivers +Author: simond@irrelevant.org (Simon Dick) +Uploader: simond@irrelevant.org (Simon Dick) +Type: dev/misc +Version: 0.1 +Architecture: m68k-amigaos >= 3.0; m68k-aros +Requires: 68000 + +devsoak is a destructive correctness and soak tester for trackdisk-style +AmigaOS block device drivers. One run answers two questions: does every +command the driver accepts do the right thing - including the edge cases +(bounds, alignment, zero length, the 64-bit offset high word, stale ETD +change counts, unsupported commands) - and does it keep doing the right +thing for hours while several tasks issue overlapping reads, writes and +housekeeping commands with multiple requests in flight. + +devsoak works a fixed sector range so it can hold per-sector state and +prove *content* correctness under sustained concurrent load, not just +whether a driver accepts a command. It complements devtest +(https://github.com/cdhooper/amiga_devtest), which probes acceptance and +sweeps whole devices. + +This release ships: + + devsoak - the tester, a single static binary + devsoak.quirks - the seed quirks file (known driver-specific behaviour + to skip, expect, or pin rather than treat as a bug - + see README.md's Quirks section); devsoak looks for + this file beside its own binary + +devsoak is destructive: it will overwrite the sector range you point it +at. Never run it against a range holding data you want to keep. + +Requirements +------------ + - AmigaOS 3.0+ (KS 1.3 is also supported at reduced dialect coverage) + or AROS m68k, 68000 or better. + - A block device to test, and a sector range on it you do not need. + +Quick start +----------- + devsoak scsi.device 0 -d -r 0,8M -t 30s -y + devsoak lide.device 0 -d -r 0,64M -w 4 -q 4 -t 8h -A 15 -y + +See https://sidick.github.io/devsoak/ for the full CLI reference, the +quirks-file format, and the fingerprinting workflow. + +AI disclosure +------------- + devsoak is written largely by an AI coding agent (Anthropic's Claude / + Claude Code), under human direction, review and emulator/on-hardware + testing. Full source is open (BSD 2-Clause) for review. + +Documentation, source and licence +--------------------------------- + Free and open source, BSD 2-Clause. Full documentation (versioned per + release), source and issue tracker: + https://sidick.github.io/devsoak/ + https://github.com/sidick/devsoak diff --git a/scripts/verify-version.sh b/scripts/verify-version.sh new file mode 100755 index 0000000..66f96e2 --- /dev/null +++ b/scripts/verify-version.sh @@ -0,0 +1,40 @@ +#!/bin/sh +# Verifies a release tag (e.g. v0.1) matches src/version.h and devsoak.readme +# before release.yml hands off to sidick/amiga-workflows' aminet-release.yml +# - the calling repo's own version-file format is project-specific, so this +# check stays here rather than in the shared workflow (see that repo's own +# aminet-release.yml header comment for why). +# +# Usage: scripts/verify-version.sh vX.Y +set -eu + +tag_ref="${1:?usage: verify-version.sh }" +tag="${tag_ref#v}" + +src=$(sed -n 's/^#define DEVSOAK_VERSION[[:space:]]*"\(.*\)"$/\1/p' src/version.h) +readme=$(sed -n 's/^Version:[[:space:]]*\(.*\)$/\1/p' devsoak.readme) +date=$(sed -n 's/^#define DEVSOAK_VERSION_DATE[[:space:]]*"\(.*\)"$/\1/p' src/version.h) + +echo "tag=$tag src/version.h=$src ($date) devsoak.readme=$readme" +[ "$tag" = "$src" ] || { echo "::error file=src/version.h::Tag v$tag does not match DEVSOAK_VERSION \"$src\""; exit 1; } +[ "$tag" = "$readme" ] || { echo "::error file=devsoak.readme::Tag v$tag does not match Version: \"$readme\""; exit 1; } + +case "$date" in + [0-9][0-9].[0-9][0-9].[0-9][0-9][0-9][0-9]) ;; + *) echo "::error file=src/version.h::DEVSOAK_VERSION_DATE \"$date\" is not DD.MM.YYYY"; exit 1 ;; +esac + +# A release PR that bumps DEVSOAK_VERSION but forgets the date would +# otherwise ship a binary whose $VER string still carries the previous +# release's date. Compare against the previous release tag's own +# src/version.h (skipped gracefully if there isn't one, e.g. this is the +# first release). +prev_tag=$(git tag -l 'v*' --sort=-v:refname | grep -vFx "v$tag" | head -n1 || true) +if [ -n "$prev_tag" ]; then + prev_date=$(git show "$prev_tag:src/version.h" 2>/dev/null | \ + sed -n 's/^#define DEVSOAK_VERSION_DATE[[:space:]]*"\(.*\)"$/\1/p') + if [ -n "$prev_date" ] && [ "$date" = "$prev_date" ]; then + echo "::error file=src/version.h::DEVSOAK_VERSION_DATE \"$date\" is unchanged since $prev_tag - bump it too" + exit 1 + fi +fi diff --git a/src/main.c b/src/main.c index a1bc93b..5012102 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ */ #include "devsoak.h" +#include "version.h" #include #include @@ -15,8 +16,7 @@ struct Config cfg; struct DevUnderTest dev; -static const char verstag[] __attribute__((used)) = - "$VER: devsoak 0.1 (01.09.2026)"; +DEVSOAK_VERSTAG /* args.c, deliberately not declared in devsoak.h (internal to this program) */ extern LONG args_parse(int argc, char **argv); diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..b1170ab --- /dev/null +++ b/src/version.h @@ -0,0 +1,24 @@ +#ifndef DEVSOAK_VERSION_H +#define DEVSOAK_VERSION_H + +/* Single source of truth for the release version. + * + * A release PR bumps DEVSOAK_VERSION and DEVSOAK_VERSION_DATE here and the + * matching `Version:` field in devsoak.readme; the tag-driven release + * workflow (.github/workflows/release.yml) refuses a v that does not + * match both. + * + * DEVSOAK_VERSION_DATE is the AmigaOS $VER date,
.. per + * https://wiki.amigaos.net/wiki/Version_Strings */ +#define DEVSOAK_VERSION "0.1" +#define DEVSOAK_VERSION_DATE "01.09.2026" + +/* Embedded AmigaOS version string, findable by the shell `Version` command. + * The leading "\0" guards against an adjacent string in the binary running + * into ours; `used` keeps the otherwise-unreferenced constant out of the + * optimiser's reach. */ +#define DEVSOAK_VERSTAG \ + static const char verstag[] __attribute__((used)) = \ + "\0$VER: devsoak " DEVSOAK_VERSION " (" DEVSOAK_VERSION_DATE ")"; + +#endif