magus v0.4.2 is out. See what's new
¶ View generated markdown
8 min read

go

The go spell wires the Go toolchain into a magusfile: each op forks a go (or gofmt) subcommand directly, with no shell. Lint and vulnerability scanning run as go tool invocations so they resolve from the module's tool block rather than PATH.

Runtime name: go (source spells/golang/)

Version probe (go): go version

Version probe (golangci-lint): golangci-lint --version

Version probe (govulncheck): govulncheck -version

Passing arguments to ops

Every op is invoked as go["<op>"](ctx, opts?). The first argument is the target's context, which is what carries the execution environment; the optional options map shapes the command itself:

Key Type Description Source
args [str] Extra arguments appended to the resolved command, replacing any trailing defaults the op declares (go-test's ./...), so passing args also states the scope. Omit it and a bare go["<op>"]() keeps the defaults and forwards magus run <target> -- <extra> to the tool automatically; pass it to set the arguments explicitly, which replaces that passthrough. source
stdin str Data written to the command's standard input. source

Working directory and environment are NOT options: they ride the context, as go["<op>"](ctx.withCwd("sub")) and go["<op>"](ctx.withEnv({"CGO_ENABLED": "0"})). Only the context reaches the cache key, so an option-table cwd or env would change what the tool did while the key said otherwise; passing either as an option is an error.

Charms (the :charm suffix, e.g. magus run test:rw) are orthogonal: they patch the base argv, while these options add to it. See Charms.

go-build

Command: go build

Example

// Wire go-build into a `build` target. `magus run build` forks `go build`.
import "magus";
import "magus/spell/go";

magus\project({ "spells": [go] });

export fun build(ctx: magus\Context, args: [str]) > void {
    go["go-build"](ctx);
}

go-clean

Command: go clean ./...

Example

// Wire go-clean into a `clean` target: `magus run clean` forks `go clean ./...`.
import "magus";
import "magus/spell/go";

magus\project({ "spells": [go] });

export fun clean(ctx: magus\Context, args: [str]) > void {
    go["go-clean"](ctx);
}

go-fmt

Command: gofmt -l .

rw

Replaces -l with -w.

JSON Patch
[
  {
    "op": "replace",
    "path": "/0",
    "value": "-w"
  }
]

Example

// go-fmt lists misformatted files; the rw charm rewrites them in place.
// `magus run format` checks, `magus run format:rw` applies gofmt.
import "magus";
import "magus/spell/go";

magus\project({ "spells": [go] });

export fun format(ctx: magus\Context, args: [str]) > void {
    go["go-fmt"](ctx);
}

go-generate

Command: go generate ./...

Example

// Wire go-generate into a `generate` target: `magus run generate` forks
// `go generate ./...`.
import "magus";
import "magus/spell/go";

magus\project({ "spells": [go] });

export fun generate(ctx: magus\Context, args: [str]) > void {
    go["go-generate"](ctx);
}

go-mod-edit

Edit is offline: it "reads only go.mod; it does not look up information about the modules involved" (go help mod edit), so the same tree always yields the same bytes. The write charm is therefore rw, not relock.

Command: go mod edit -print

rw

Drops -print.

JSON Patch
[
  {
    "op": "remove",
    "path": "/2"
  }
]

go-mod-json

Captures Go's structured module view for the spell's higher-level Buzz helper. This is deliberately a separate read-only op: -json and -print are distinct Go modes, while go-mod-edit remains the one command that applies derived edits.

Command: go mod edit -json

go-mod-tidy

Tidy resolves against the module proxy, and an import go.mod does not require yet arrives at latest, so its result turns on what upstream serves today rather than on this tree alone. The write charm is therefore relock, not rw.

Command: go mod tidy --diff

relock

Drops --diff.

JSON Patch
[
  {
    "op": "remove",
    "path": "/2"
  }
]

Example

// go-mod-tidy checks go.mod/go.sum with --diff (CI-safe); the rw charm drops
// --diff so `magus run tidy:rw` applies the changes.
import "magus";
import "magus/spell/go";

magus\project({ "spells": [go] });

export fun tidy(ctx: magus\Context, args: [str]) > void {
    go["go-mod-tidy"](ctx);
}

go-run

Command: go run

Example

// Run a repo-local Go tool through the spell instead of proc.exec. go-run has no
// useful bare form: name the package and its flags via the "args" option, which
// append after `go run`. This forks `go run ./cmd/gen-docs -out ./docs`.
import "magus";
import "magus/spell/go";

magus\project({ "spells": [go] });

export fun generate(ctx: magus\Context, args: [str]) > void {
    go["go-run"](ctx, {"args": ["./cmd/gen-docs", "-out", "./docs"]});
}

go-test

./... is a default, not a fixed arg: a magusfile that passes args replaces it, so one package's tests need not compile every test binary in the module. A bare go.test() (and magus run <t> -- <extra> forwarding) still runs the whole tree.

Command: go test ./...

cd

Appends -covermode=atomic, appends -coverprofile=coverage.out.

JSON Patch
[
  {
    "op": "add",
    "path": "/-",
    "value": "-covermode=atomic"
  },
  {
    "op": "add",
    "path": "/-",
    "value": "-coverprofile=coverage.out"
  }
]

debug

Appends -v.

JSON Patch
[
  {
    "op": "add",
    "path": "/-",
    "value": "-v"
  }
]

Example

// go-test runs the suite; here with the race detector. Explicit args replace the
// op's `./...` default, so the scope rides along with the flags and `magus run test`
// forks `go test -race ./...`. The cd charm (`magus run test:cd`) adds the atomic
// coverage profile a CD pipeline ships.
import "magus";
import "magus/spell/go";

magus\project({ "spells": [go] });

export fun test(ctx: magus\Context, args: [str]) > void {
    go["go-test"](ctx, { "args": ["-race", "./..."] });
}

go-vet

Command: go vet ./...

Example

// go-vet is static analysis, so it composes into the canonical `lint` target
// (alongside golangci-lint) rather than a bespoke `vet` target. `magus run lint`
// forks `go vet ./...`.
import "magus";
import "magus/spell/go";

magus\project({ "spells": [go] });

export fun lint(ctx: magus\Context, args: [str]) > void {
    go["go-vet"](ctx);
}

golangci-lint

Invoked directly rather than through go tool: golangci-lint generates no code, so it has none of the generator/runtime lockstep that keeps protoc-gen-go pinned in go.mod. go tool golangci-lint also required the binary in the module's tool block, and a workspace that had not put it there got "no such tool" - the op could not run at all. On PATH it is pinned by whatever the workspace uses (mise, asdf, a system package), and the spell's version probe records which.

Command: golangci-lint run ./...

debug

Appends -v.

JSON Patch
[
  {
    "op": "add",
    "path": "/-",
    "value": "-v"
  }
]

rw

Inserts --fix.

JSON Patch
[
  {
    "op": "add",
    "path": "/1",
    "value": "--fix"
  }
]

Example

// golangci-lint runs as a `go tool` (resolved from go.mod's tool block). The rw
// charm inserts --fix, so `magus run lint:rw` applies the autofixable findings.
import "magus";
import "magus/spell/go";

magus\project({ "spells": [go] });

export fun lint(ctx: magus\Context, args: [str]) > void {
    go["golangci-lint"](ctx);
}

govulncheck

Invoked directly rather than through go tool, for the same reason as golangCILint above: go tool govulncheck requires the binary in the module's tool block, and a workspace that had not put it there got "no such tool" - so the op could not run at all. On PATH it is pinned by whatever the workspace uses, and mgs_getVersionProbes records which.

Command: govulncheck ./...

Example

// govulncheck scans the module's call graph for known vulnerabilities, run as a
// `go tool` so it resolves from go.mod's tool block. Security scanning is static
// analysis, so it composes into the canonical `lint` target - not a bespoke
// `audit`/`security` target. (A slow scan can instead be gated in `ci`.)
import "magus";
import "magus/spell/go";

magus\project({ "spells": [go] });

export fun lint(ctx: magus\Context, args: [str]) > void {
    go["govulncheck"](ctx);
}

scip

Command: scip-go --output $MAGUS_SYMBOL_INDEX

generatedspells/golang/spell.buzzgospellgolangbuildtestlinttools
Last updated (16372805)
Earlier changes on this page (7)

Full history ↗ · Blame source ↗

Glossary

Workspace

The magus root directory that owns a set of projects and shared config; the unit magus operates over. See workspace.

Project

A directory magus recognizes as a unit of work (it has a magusfile); the unit of caching, scheduling, and dependency tracking. See workspace.

Magusfile

The magusfile.buzz that declares a project's targets (as export funs) and binds its spells. See targets.

Target

A named operation (build, test, ...) you invoke with magus run <target>; it may compose a spell's tool-native operations and depend on other targets. See targets.

Op

A single tool-native command a target composes (long form: operation); the middle of the work hierarchy (Spell to Op to Target). See operations.

Spell

A language/runtime adapter (e.g. go, md) that maps generic targets onto a toolchain's real commands. See spells.

Charm

An execution modifier attached with : (lint:rw) that changes how a target runs, not which one; the built-in rw flips a check-only target to mutate in place, and ci always strips it. See charms.

Ward

A coded diagnostic that inspects a resolved op and nudges or blocks an anti-pattern before it runs. See wards.

Module

A magus stdlib namespace a magusfile imports for host capabilities: filesystem, exec, vcs, and more. See the module reference.

Buzz

The language magusfiles are written in (the .buzz engine). See engines.

Cache

The content-addressed store magus consults before running a target, so unchanged work is skipped. See cache.

CI

An ordinary magusfile-defined target you compose yourself with magus\needs - magus does not hardcode its stages. Magus.RunCI treats it specially only in that it strips the rw charm, it is the anchor magus affected ci keys off, and a selected scope with no project declaring it is a load error rather than a silent no-op. See targets.

Conventions

Placeholders

Angle brackets mark a value you replace with your own - never type the brackets:

magus run <target>
magus completion <shell>    # e.g. bash, zsh, fish

<target>, <path>, <shell>, <name> and the like are stand-ins, not literal text.