6.7 KiB
Development Guide
Repository: sourcedock.dev/petrbalvin/gasm-devkit
Prerequisites
- Go 1.27.1, the exact version the
godirective ingo.moddeclares - just, the command runner; every task below is a just recipe
- A C compiler (
gcc):just raceruns the suite under the race detector, which needs cgo - Perl: the
test,fmt-check,install-mananduninstall-manrecipes are Perl programs gzip:install-mancompresses the man pages with it- A Linux host on amd64, arm64, riscv64 or loong64:
gasm debugneeds ptrace and the JIT checks ofgasm verifyneed executable memory golang.org/x/arch, the one module dependency, which the Go toolchain fetches; nothing else sits outside the standard library
Setup
git clone https://sourcedock.dev/petrbalvin/gasm-devkit.git
cd gasm-devkit
just build # compile bin/gasm, zero errors and zero warnings
just gates # build, fmt-check, vet, test, race: the definition of done
Recipes
Every recipe in the justfile, and what it does.
| Recipe | What it does |
|---|---|
default (bare just) |
prints the recipe list (@just --list) |
just build |
compiles bin/gasm with CGO_ENABLED=0 and stripped symbols; zero errors and zero warnings |
just test |
the test gate: the suite with -count=1, the coverage profile and the 80 % floor, then the CLI and debugger tests outside the profile |
just race |
the same suite under the race detector; the expensive one, so it runs once, inside gates |
just unit [packages] [run] |
fast, cached, scoped run for iterating: no race and no coverage, so an unchanged package reports instantly |
just fuzz <target> <pkg> [fuzztime] |
time-boxed fuzz of one target; the package is required, because go test -fuzz refuses more than one |
just bench [packages] |
benchmarks (-benchmem -count=5); on an idle machine only |
just fmt |
formats the tree in place with gofmt |
just fmt-check |
zero diff; prints nothing when everything is formatted, which is the shape the CI step wants |
just vet |
both static gates: go vet and go fix -diff |
just gates |
build, fmt-check, vet, test and race, in that order: the definition of done |
just clean |
removes the build artefacts, bin/ and coverage.out |
just install |
builds, then copies the binary into bindir (~/.local/bin) |
just uninstall |
removes the installed binary from bindir |
just install-man |
installs the man pages under docs/man into ~/.local/share/man/man1 (MANDIR overrides), gzip-compressed; not a gate |
just uninstall-man |
removes the installed man pages |
just run |
runs the CLI with go run -buildvcs=true; the recipe takes no arguments, so flags go through the package instead |
just dev |
the same as run; the project has no watcher to add |
just gen |
regenerates the arch instruction tables from the Go toolchain source; not a gate |
just test
go test -count=1 -timeout 10m -coverprofile=coverage.out \
./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... \
./lint/... ./lsp/... ./parser/... ./token/... ./verify/...
The suite runs over the logic packages (-count=1, so no cached pass
counts): arch, asm, ast, disasm, format, lexer, lint, lsp, parser,
token, verify. debug traces a live process and cmd/gasm is thin CLI
glue, so both sit outside the profile sweep, and a thin cmd/ in it
would drag the coverage total under the floor. Their tests still run, in
a second invocation without a profile:
go test -count=1 -timeout 10m ./cmd/... ./debug/...
That covers the CLI's exit codes and the guard that compares the manual pages with the binary's own help, and the debugger's architecture-neutral units. The floor fails if the total is below 80 %. CI runs the same two commands with the same ten-minute bound, so the number is the same everywhere.
just run
just run
go run -buildvcs=true ./cmd/gasm lint kernel_amd64.s
go run -buildvcs=true ./cmd/gasm verify --ground-truth kernel_amd64.s
The flag on go run is there because it does not stamp the build otherwise,
which --version would then report as (devel).
just gen
Regenerates the architecture instruction tables in arch/ by parsing the Go
toolchain's own assembler source
($GOROOT/src/cmd/internal/obj/<arch>/anames.go). Requires a Go
installation. Output is committed, with no runtime dependency on the
toolchain.
Running a single test
go test -run TestVexGroundTruth ./asm/
go test -run TestGroundTruthBasic ./verify/
go test -run TestGOObjectLinkAndRun ./asm/
go test -run TestFuzzWideCopy ./verify/
Add -v for the sub-test names, and -race when the change touches
concurrency. -count=1 defeats the test cache when a result looks stale.
Coverage
just test
go tool cover -func=coverage.out
The total: line is the number that matters, and it stays at 80 percent or
more.
Debugging the build
go build -gcflags='-m' ./... # inlining decisions
go build -gcflags='-S' ./... # what the compiler generated
go tool asm -S kernel_amd64.s # how the toolchain's assembler encodes a kernel
gasm dis kernel_amd64.s # what gasm makes of the same kernel
gasm tokens kernel_amd64.s # the token stream
gasm profile kernel_amd64.s # the basic blocks of each function
gasm verify --ground-truth is the differential check that ties the two
together: it compares gasm's bytes with go tool asm's, with the relocation
sites masked, so an encoding drift shows up as a byte difference rather than a
crash later.
Continuous integration
Workflows live in .gitea/workflows/ and run on the project's own runners:
Test on a push or pull request to development, race dispatched by hand, and
the release on a v* tag. They are written by hand rather than through
just, but they enforce the same set of gates minus the race detector, which
the shared runner cannot afford on a push; a green just gates locally is
therefore the fastest way to a green pipeline.
Releases
Releases are cut by merging development into main and tagging vX.Y.Z,
which triggers the release workflow: it builds the portable Linux targets,
takes the notes from the matching CHANGELOG.md section and uploads the
assets. SECURITY.md carries the supported-versions table, so that table
moves with the release; the pipeline refuses a tag the policy does not name.
The version is never injected. gasm --version prints what the
toolchain recorded in the build information: the tag on a tagged
checkout, a pseudo-version naming the commit below one, +dirty on a
dirty tree, and (devel) outside version control. There is no
-ldflags "-X" anywhere and no version constant in the source.