Files
goget/docs/development.md
T

254 lines
7.5 KiB
Markdown

# Development
This guide covers the day-to-day workflow for working on **goget** — building,
testing, releasing, and contributing.
## Requirements
- **Go 1.26+**
- **Platform:** Linux (amd64, arm64, riscv64, loong64) or
FreeBSD (amd64, arm64, riscv64)
- **`just`** — task runner (`brew install just` / `apt install just` /
`pacman -S just`)
- **`groff`** *(optional)* — for `just man` manpage validation
All Go source files carry the build constraint `//go:build linux || freebsd`.
goget does not build or run on macOS or Windows.
## First-time Setup
```bash
git clone https://codeberg.org/petrbalvin/goget.git
cd goget
just install # go mod download
just test # verify everything works on your machine
just build # produce bin/goget
```
A successful `just build` produces a static `bin/goget` binary with no
runtime dependencies beyond the Go standard library and `golang.org/x/`
packages.
## Task Reference
Every task is defined in [`justfile`](../justfile) and is the canonical
way to interact with the project.
| Task | What it does |
|------|--------------|
| `just` | List all recipes (default) |
| `just install` | `go mod download` |
| `just run` | `go run ./cmd/goget` (development loop) |
| `just build` | `gofmt -l` check + `go vet ./...` + build `bin/goget` |
| `just test` | `go test -race -count=1 ./...` |
| `just uninstall` | Remove `bin/` and `coverage.out` |
| `just man` | Validate `man/goget.1` with `groff` (skips if absent) |
| `just install-man` | Install manpage to `~/.local/share/man/man1/` (gzipped, XDG) |
| `just uninstall-man` | Remove installed manpage |
`just build` is **strict**: it runs `gofmt -l` (any output is a failure)
plus `go vet ./...`. Both must report zero warnings before the binary
compiles. This is exactly what CI checks; running it locally first
saves a round-trip.
## Development Loop
```mermaid
graph TD
Edit[Edit source in cmd/, internal/, pkg/]
Test[just test]
Vet[just build runs gofmt + vet + build]
Man[just man - groff validation]
Commit[Conventional Commits message]
Push[Push to development branch]
CI[Forgejo CI: test.yml]
Edit --> Test
Test -->|fail| Edit
Test --> Vet
Vet -->|fail| Edit
Vet --> Man
Man -->|fail| Edit
Man --> Commit
Commit --> Push
Push --> CI
CI -->|fail| Edit
CI -->|pass| Merge[Merge / open PR]
```
Typical iteration:
```bash
# Make a focused change.
$EDITOR cmd/goget/flags.go
# Quick check that compile + format + vet pass.
just build
# Run the relevant tests with the race detector.
just test
# Validate any manpage edits.
just man
```
## Building from Source
`just build` does three things in order:
1. Copies `man/goget.1` into `cmd/goget/man-page-data/` — the manpage is
embedded into the binary via `//go:embed`, and `//go:embed` can only
reference files within the embedding package's directory.
2. Runs `gofmt -l` over `cmd/`, `internal/`, and `pkg/`. Any output is a
formatting violation that breaks the build.
3. Runs `go vet ./...` — must report zero warnings.
4. Compiles `bin/goget` with `-ldflags "-s -w"` (strips DWARF).
The resulting binary is fully static. `ldd bin/goget` should report
`not a dynamic executable` on Linux.
## Testing
```bash
just test # full suite, race detector, count=1
go test ./internal/protocol/gemini/... # focused
go test -run TestFoo ./internal/core/ # one test
```
Conventions:
- Tests live next to the code as `*_test.go`.
- Use table-driven tests for parsers, validators, and protocol handlers.
- Cover both happy path and error path.
- Test names follow `TestFunctionName_Scenario`.
- Fuzz tests use `testing.F` and live next to the code they exercise.
The race detector is **always** on in CI. New code that introduces a
data race must be fixed, not silenced.
## Manpage Authoring
The single source of truth is `man/goget.1` (troff). Editing this file is
what most users eventually read via `man goget` and what
`--generate-man-page` prints.
Workflow:
```bash
$EDITOR man/goget.1
just man # validates the troff
just build # copies the file into cmd/goget/man-page-data/
just test # cmd/goget/man_page_test.go asserts every CLI flag
# is documented and the embedded source still parses
```
`cmd/goget/man_page_test.go` enforces documentation coverage — adding a
new flag to `defineFlags` without documenting it in `man/goget.1` breaks
the build.
## Logging
The project uses `internal/log` for structured output. Prefer `log.Infof`,
`log.Errorf`, etc. over ad-hoc `fmt.Fprintf(os.Stderr, ...)` or
`cli.Print*`. The backlog tracks a migration of the remaining ~140 legacy
callsites to `internal/log`.
## Branching & Releases
```mermaid
graph LR
Feature[Feature branches]
Development[development]
Main[main]
Tag[vX.Y.Z tag]
Release[Forgejo release]
Feature --> Development
Development --> Main
Main --> Tag
Tag --> Release
```
- **`development`** — the only branch that receives day-to-day work.
All pull requests target `development`.
- **`main`** — only changes when `development` is merged for a release.
Never commit directly to `main`.
- **Tags** — `vX.Y.Z` tags trigger the release workflow.
### Cutting a Release
1. Bump `Version` in [`internal/core/version.go`](../internal/core/version.go).
2. Move the top `## [development]` section in
[`CHANGELOG.md`](../CHANGELOG.md) to `## [X.Y.Z] — YYYY-MM-DD`,
inserting a fresh empty `## [development]` section above it.
3. Ensure CI is green on `development`.
4. Merge `development``main`.
5. Tag and push:
```bash
git tag -a vX.Y.Z -m "goget vX.Y.Z"
git push origin main
git push origin vX.Y.Z
```
6. The release workflow builds binaries for all seven platform targets,
extracts the matching `CHANGELOG.md` section, and publishes the
release on Codeberg.
## CI
Two Forgejo Actions workflows live under
[`.forgejo/workflows/`](../.forgejo/workflows):
### `test.yml`
Triggered on every push to `development` and on every pull request
targeting `development`.
| Job | What it does |
|-----|--------------|
| `vet` | `gofmt -l` check + `go vet ./...` (no output = pass) |
| `test` | `go test -race -count=1 ./...` + coverage gate |
Run the same locally before opening a PR:
```bash
go vet ./...
just test
```
### `release.yml`
Triggered by pushing a `v*` tag. Verifies that the tag matches
`internal/core.Version`, runs the quality gate, builds static binaries
for all seven targets, and publishes the release.
Requires a `FORGEJO_TOKEN` secret with release-creation permissions on
the runner.
## Code Style
- Follow [Effective Go](https://go.dev/doc/effective_go).
- Keep lines under 100 characters.
- `camelCase` for locals, `PascalCase` for exported symbols.
- Prefix every file with a one-line comment describing its purpose.
- Use `//go:build linux || freebsd` on every source file.
- Use long CLI flags only (`--header`, not `-H`).
- Stick to the Go standard library and `golang.org/x/`. External
dependencies require an explicit justification.
- Error messages: English, lowercase, no trailing period, always with
context (e.g. `open config: permission denied`).
- Run `gofmt` before committing.
## Reporting Bugs
Open an issue at
<https://codeberg.org/petrbalvin/goget/issues> with:
- `goget --version` output.
- Operating system and architecture.
- The full command used and its output.
- Expected vs actual behaviour.
For **security issues**, email
[opensource@petrbalvin.org](mailto:opensource@petrbalvin.org) instead of
opening a public issue.