Skip to content

lesiw/gorc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

lesiw.io/gorc

Go Reference CI Release Go Version Discord License

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.

Installation

go install lesiw.io/gorc/go@latest

This 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.rc file

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".

go.rc.local

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 ./go

GORC

Setting 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.

gofmt

gorc also provides a gofmt shim, installed separately into the same PATH position:

go install lesiw.io/gorc/gofmt@latest

It 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.

Motivation

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.