mirror of
https://github.com/pkivolowitz/asm_book.git
synced 2026-06-20 22:46:46 +08:00
Enforce macro canonical-vs-chapter-copy identity via sync script and CI
The repository ships a copy of apple-linux-convergence.S in each chapter directory that demonstrates assembly (11 copies at last count, plus the canonical one in macros/) so that readers browsing or downloading a single chapter from GitHub have the macros sitting right next to the sources that use them. That self-containment is worth keeping. Manual synchronization of 12 copies on every macro edit is not: all 11 are currently byte-identical to the canonical, but the first drift is a matter of when, not if, and diagnosing "which chapter broke when I added a new macro" after the fact is a bad time. This commit turns "the copies are in sync" from a hope into a machine-enforced invariant: - scripts/sync-macros.sh: walks macros/*.S, finds every file with the same basename anywhere else in the repo (excluding .git/ and macros/ itself), and overwrites any copy that differs. Idempotent; prints only the files it actually changed plus a summary. Uses only POSIX tools (find, cmp, cp, basename) plus bash builtins under a #!/usr/bin/env bash shebang. Verified working under both macOS bash 3.2.57 and zsh 5.9 on clean-tree and drift-repair paths. - .github/workflows/check-macros.yml: runs the sync script on every push and pull request, then fails the job if git diff --exit-code shows the script produced any uncommitted change. The failure message tells the author exactly what to do (run the script locally, commit the result). - macros/README.md: new "Source of truth" section marking the chapter copies as derived artifacts, pointing editors at the sync script, and stating that CI enforces the invariant. Rejected alternatives: - Symlinking each chapter copy to macros/apple-linux-convergence.S. Cheapest option (zero infrastructure) and git handles symlinks natively, but Windows checkouts without Developer Mode replace the symlink with a plain-text file containing the target path. This book's audience is overwhelmingly Linux and Apple Silicon, so the Windows hazard is mostly theoretical, but a sync-and-check approach works in every clone environment and makes the source-of-truth relationship explicit rather than implicit in a filesystem feature. - Having each chapter .include the canonical file via a relative path. Breaks the "self-contained chapter" property the copies exist to preserve; a reader who downloads one chapter gets a broken build because macros/ is not beside it. - Making the copies build-time artifacts (generated by make, not committed). Same problem: a reader browsing one chapter on GitHub no longer sees the macro file they need. Tests: - ./scripts/sync-macros.sh run on the current tree reports "macros already in sync (11 chapter copies checked)" and exits 0. - Injecting a trailing-line perturbation into a chapter copy and re-running the script: detects the drift, reports "synced: <path>", and restores the file to canonical. Verified under both bash and zsh, both paths.
This commit is contained in:
parent
88c8f496c4
commit
3144bc6dbb
3 changed files with 132 additions and 0 deletions
41
.github/workflows/check-macros.yml
vendored
Normal file
41
.github/workflows/check-macros.yml
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# check-macros.yml
|
||||
#
|
||||
# Verifies that every chapter-level copy of a macro file is byte-identical
|
||||
# to its canonical version in macros/. Runs scripts/sync-macros.sh and
|
||||
# fails the job if the script produced any change that was not already
|
||||
# committed.
|
||||
#
|
||||
# Rationale: the repo ships one copy of each macro file per chapter so
|
||||
# that each chapter directory remains self-contained on GitHub. Those
|
||||
# copies are derived artifacts; macros/*.S is the single source of
|
||||
# truth. This check turns "copies are in sync" from a convention into
|
||||
# a machine-enforced invariant, so drift cannot sneak into main.
|
||||
#
|
||||
# To fix a failure locally:
|
||||
# ./scripts/sync-macros.sh
|
||||
# git add -u
|
||||
# git commit --amend --no-edit # or a new commit, whichever you prefer
|
||||
|
||||
name: Check macro sync
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
check-macros:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run sync script
|
||||
run: ./scripts/sync-macros.sh
|
||||
|
||||
- name: Fail if any chapter copy drifted from canonical
|
||||
run: |
|
||||
if ! git diff --exit-code; then
|
||||
echo "::error::Chapter macro copies drifted from the canonical files in macros/."
|
||||
echo "::error::Run ./scripts/sync-macros.sh locally and commit the result."
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -6,6 +6,28 @@ on Apple Silicon and Linux machines without change.
|
|||
|
||||
The work is ongoing and subject to change.
|
||||
|
||||
## Source of truth
|
||||
|
||||
The files in this directory (`macros/*.S`) are the **canonical**
|
||||
versions of the macros. Every chapter directory that demonstrates
|
||||
assembly code keeps a copy of `apple-linux-convergence.S` alongside
|
||||
its sources, so that a reader browsing or downloading a single
|
||||
chapter on GitHub has the macros sitting right next to the `.S`
|
||||
files that use them.
|
||||
|
||||
Those chapter-level copies are **derived artifacts**. Do not edit
|
||||
them. Edit the file here in `macros/`, then run:
|
||||
|
||||
```
|
||||
./scripts/sync-macros.sh
|
||||
```
|
||||
|
||||
from the repository root to propagate the change to every chapter
|
||||
copy. A GitHub Actions job (`.github/workflows/check-macros.yml`)
|
||||
re-runs the sync script on every push and pull request and fails
|
||||
the build if any copy has drifted from canonical, so this invariant
|
||||
cannot silently break.
|
||||
|
||||
There are limits to what these macros can do. Variadic functions such as
|
||||
`printf()` must be handled via parallel code paths (i.e. use of `#if`).
|
||||
|
||||
|
|
|
|||
69
scripts/sync-macros.sh
Executable file
69
scripts/sync-macros.sh
Executable file
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# sync-macros.sh
|
||||
#
|
||||
# Propagate canonical macro files from macros/ into the per-chapter
|
||||
# copies scattered throughout the repository.
|
||||
#
|
||||
# Source of truth:
|
||||
# Any file in macros/*.S at the repository root.
|
||||
#
|
||||
# Targets:
|
||||
# Every file elsewhere in the repository whose basename matches a
|
||||
# file in macros/. The chapter copies exist so that each chapter
|
||||
# directory remains self-contained (a reader can download a single
|
||||
# chapter from GitHub and have the macros it needs sitting right
|
||||
# next to the source files). That self-containment is valuable;
|
||||
# manual synchronization of ~12 copies is not. This script lets us
|
||||
# have both.
|
||||
#
|
||||
# Operation:
|
||||
# For each canonical file, find every copy with the same basename
|
||||
# outside macros/ and overwrite it if (and only if) it differs.
|
||||
# Prints one line per file actually modified; prints nothing beyond
|
||||
# a final summary if everything was already in sync.
|
||||
#
|
||||
# Intended use:
|
||||
# Run after editing any file in macros/, then commit the changes
|
||||
# (the canonical file plus all updated copies). CI verifies this
|
||||
# was done by re-running the script and requiring `git diff
|
||||
# --exit-code` to be clean.
|
||||
#
|
||||
# Perry Kivolowitz
|
||||
# A Gentle Introduction to Assembly Language
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
if [ ! -d macros ]; then
|
||||
echo "error: macros/ directory not found at repo root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
changed=0
|
||||
checked=0
|
||||
|
||||
for canonical in macros/*.S; do
|
||||
[ -f "$canonical" ] || continue
|
||||
name="$(basename "$canonical")"
|
||||
|
||||
# Find every file in the repo with this basename, excluding the
|
||||
# canonical location itself and anything under .git/. The -type f
|
||||
# filter skips any stray symlinks a prior experiment may have left.
|
||||
while IFS= read -r copy; do
|
||||
checked=$((checked + 1))
|
||||
if ! cmp -s "$canonical" "$copy"; then
|
||||
cp "$canonical" "$copy"
|
||||
echo "synced: $copy <- $canonical"
|
||||
changed=$((changed + 1))
|
||||
fi
|
||||
done < <(find . -name "$name" -not -path './macros/*' -not -path './.git/*' -type f)
|
||||
done
|
||||
|
||||
if [ "$changed" -eq 0 ]; then
|
||||
echo "macros already in sync ($checked chapter copies checked)"
|
||||
else
|
||||
echo "synced $changed of $checked chapter copies"
|
||||
fi
|
||||
Loading…
Reference in a new issue