gorc runs the go command with per-project command definitions from a go.rc file. A companion gofmt shim does the same for formatting, applying format commands from a gofmt.rc file.
A go.rc file records how a project's go commands are meant to be run:
test (
CGO_ENABLED=0 go test $GOARGS -shuffle=on -timeout=5s -count=10 $GOFLAGS
CGO_ENABLED=1 go test $GOARGS -shuffle=on -timeout=30s -race $GOFLAGS
)
vet go tool lesiw.io/tools/cmd/vet $GOARGS $GOFLAGS
With gorc installed, running a defined verb runs the configured commands in its place:
$ go test ./...
go.rc: CGO_ENABLED=0 go test ./... -shuffle=on -timeout=5s -count=10
go.rc: CGO_ENABLED=1 go test ./... -shuffle=on -timeout=30s -race
Each line is printed as its command starts, so output follows the command that produced it, and the prefix names the file the command came from: go.rc or go.rc.local.
Every other invocation passes through to the real go command unchanged.
go install lesiw.io/gorc/go@latestThis installs a binary named go into the Go bin directory. That directory must come before the Go distribution in PATH:
export PATH="$(go env GOPATH)/bin:$PATH"To uninstall, delete the binary:
rm "$(go env GOPATH)/bin/go"The go command searches for a file named go.rc in the current directory, then in each parent directory. A go.rc that fails to parse is reported to standard error and the invocation passes through to the real go command. Each line defines a verb and the command that runs when the verb is invoked:
vet go tool lesiw.io/tools/cmd/vet
Multiple commands for one verb may be grouped go.mod-style. The group runs in order, stopping at the first failure:
test (
CGO_ENABLED=0 go test -shuffle=on
CGO_ENABLED=1 go test -shuffle=on -race
)
Verbs are not limited to the go command's own. New verbs define new subcommands:
clerk go tool lesiw.io/tools/cmd/clerk lib
A defined verb runs its commands exactly as written in go.rc.
Arguments after the verb do not carry over on their own: with no
further configuration, go test -run TestFoo runs the configured
test commands and the -run flag is not passed along.
Each line expands variable references written as $NAME or
${NAME}, the same forms go:generate directives accept, before
the line is split into words. Two names are set by the
interception itself and let a command state where the invocation's
arguments belong: $GOARGS expands to the invocation's arguments,
such as package patterns, and $GOFLAGS expands to its flags
along with their values, in any argument order. A flag the go
command does not document is an error, unless it follows the
arguments, as flags bound for a test binary do. Every other name
takes its value from the environment:
test go test $GOARGS -shuffle=on -count=10 $GOFLAGS
Here go test ./... -run TestFoo runs
go test ./... -shuffle=on -count=10 -run TestFoo. Placement sets
precedence: flags written after $GOFLAGS cannot be overridden by
the caller, while flags written before it can be. This also keeps
editor and IDE test runners working: their -run, -timeout, and
-json flags flow into the configured commands rather than being
dropped.
Because expansion happens before the line is split into words, a reference that expands to several arguments becomes several words.
Commands are not interpreted by a shell. There are no pipes,
redirections, or globs. Words are split on
spaces and tabs, a word beginning with a double quote is read as a
Go string literal, and // introduces a comment — the same rules
as go:generate directives. A command may begin with environment
variable assignments of the form KEY=VALUE, KEY='VALUE', or
KEY="VALUE".
A file named go.rc.local, typically kept out of version control, overrides the go.rc in its directory verb by verb: a verb defined in both files runs the go.rc.local commands. This suits per-machine adjustments, such as a longer test timeout on a slow machine. A go.rc.local also works on its own, defining personal verbs in a project that has no go.rc of its own.
Running something the project's commands do not express at all remains a deliberate act, stated by bypassing go.rc:
GORC=0 go test -count=1 ./goSetting GORC to any non-empty value disables interception:
GORC=0 go test ./...Intercepted commands run with GORC=1 set, so go commands they invoke pass through to the real go command rather than being intercepted again.
gorc also provides a gofmt shim, installed separately into the same PATH position:
go install lesiw.io/gorc/gofmt@latestIt reads a file named gofmt.rc, found by the same upward search as go.rc. A gofmt.rc has no verbs: it is a list of format commands, one per line, in the order they run:
gofmt -s
go tool goimports -local=lesiw.io
Each command must read Go source on standard input and write the
formatted result to standard output. The shim pipes source through
every command in order and presents the result through the
standard gofmt interface: with no arguments, standard input is
formatted to standard output; file arguments are formatted
individually; directory arguments are searched recursively for .go
files. The -l flag lists files whose formatting differs, -w
rewrites them in place, and -d prints diffs. Other gofmt flags
have no effect of their own: $GOFMTFLAGS expands to them, so a
command states where the invocation's formatting flags belong:
gofmt -s $GOFMTFLAGS
Anything that shells out to gofmt — editors, scripts, agents — gets the project's formatter pipeline instead of plain gofmt, with no configuration of its own.
Setting GOFMTRC to any non-empty value disables interception, and intercepted commands run with GOFMTRC=1 set, so a gofmt command listed in gofmt.rc runs the real gofmt rather than being intercepted again. Without a gofmt.rc, every invocation passes through unchanged to the next gofmt in PATH.
Most Go projects need no build tool beyond the go command: go build, go test, go vet, and go generate work the same way in every project, and both people and their tools reach for these verbs first. Makefiles and their successors go unread and unused in Go projects for exactly that reason — and with them goes any project- specific strictness they were meant to encode, such as test timeouts, shuffle settings, or race detector passes.
go.rc keeps the verbs everyone already uses and lets a project state precisely what those verbs mean, without introducing a shell-interpreted build language.