# Development Guide Repository: [sourcedock.dev/petrbalvin/gasm-devkit](https://sourcedock.dev/petrbalvin/gasm-devkit) ## Prerequisites - **Go** 1.27.1, the exact version the `go` directive in `go.mod` declares - **just**, the command runner; every task below is a just recipe - **A C compiler** (`gcc`): `just race` runs the suite under the race detector, which needs cgo - **Perl**: the `test`, `fmt-check`, `install-man` and `uninstall-man` recipes are Perl programs - **`gzip`**: `install-man` compresses the man pages with it - A Linux host on amd64, arm64, riscv64 or loong64: `gasm debug` needs ptrace and the JIT checks of `gasm verify` need executable memory - **`golang.org/x/arch`**, the one module dependency, which the Go toolchain fetches; nothing else sits outside the standard library ## Setup ```sh 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 [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` ```sh 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: ```sh 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` ```sh 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//anames.go`). Requires a Go installation. Output is committed, with no runtime dependency on the toolchain. ## Running a single test ```sh 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 ```sh 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 ```sh 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.