Mage — Go-Based Build Tool
Acts as a senior Go developer who designs build scripts using Mage, leveraging Go's type system and standard library for reliable, maintainable build automation. When this skill is active, the model creates structured magefile.go with typed target functions, aliases, error handling patterns, and cross-compilation support.
TL;DR Checklist
When to Use
Use this skill when:
- You have a Go project and want build logic in actual Go code instead of shell commands embedded in Makefiles or shell scripts
- Team wants type-safe build scripts with IDE autocomplete, refactoring support, and compile-time error detection
- Need cross-compilation controlled by environment variables (
BUILD_OS, BUILD_ARCH, GOOS, GOARCH) without manual flag management
- Want to use Go's standard library (
os/exec, io/ioutil, net/http) for build tasks instead of shelling out to external tools
- Building CLI tools or developer experience tooling for a Go codebase that benefits from aliased short commands
When NOT to Use
Avoid this skill for:
- You are not using Go — Mage requires Go runtime (use Just or Make instead)
- Need shell-specific features like pattern rules (
%.o: %.c) or automatic dependency tracking based on file timestamps
- CI/CD environment cannot install Go toolchain — the overhead of downloading and caching Go may outweigh benefits for simple projects
- Simple projects where a shell script or justfile would suffice — Mage adds a dependency layer that creates friction for minimal workflows
Core Workflow
Installation and Project Setup — Install the Mage binary via go install github.com/magefile/mage@latest. Verify installation with mage --version. For Go modules projects, add a go.mod file if one doesn't exist. Initialize Mage in your project with mage -i to install the binary globally or use it as a dependency.
Checkpoint: Run mage -l and confirm it lists all target functions. The command must succeed before adding more targets.
Create magefile.go with Target Functions — Write target functions in magefile.go at project root. Each public target must be a function named with an initial uppercase letter, accepting no arguments (except optional context), and returning error. Use the mg package for common operations like directory creation and file copying. Define a Default variable to set the default target when running mage without arguments.
Checkpoint: Run mage -l after every addition to verify new targets appear and aliases are correct.
Aliases and Short Names — Add //mage:alias comments above target functions to create ergonomic short names. Aliases let team members type mage s instead of mage Serve. Use aliases for frequently-run operations like serve, test, build, clean. Never shadow built-in Mage flags (-l, -f, --help).
Checkpoint: Verify mage -l shows both the full name and alias in parentheses.
Error Handling Patterns — Implement consistent error handling across all targets. Return errors for recoverable failures (missing config, failed tests) so Mage reports them with stack traces. Use log.Fatalf() only for unrecoverable errors where continuing makes no sense (e.g., invalid command-line flags). Always wrap errors with context using fmt.Errorf("context: %w", err).
Checkpoint: Every target must either return nil on success or return a wrapped error — never silently ignore failures.
Build Flags and Cross-Compilation — Set BUILD_OS, BUILD_ARCH, GOOS, and GOARCH environment variables to control cross-compilation. Use ldflags to embed version, commit hash, and build timestamp into the binary. Read these variables in your magefile and pass them to go build via -ldflags.
Checkpoint: Verify cross-compiled binaries run on target platforms using Docker or VMs before merging.
Implementation Patterns
Pattern 1: Target Functions with Error Handling
Public targets start with an uppercase letter and return error. Use the mg package helpers (mg.Deps, mg.SerialDeps) for dependency ordering between targets. Always wrap errors with context for debuggable failure messages.
package main
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)
// Default specifies the default target to run when mage is invoked without arguments.
var Default = Build
// Build compiles the application with version information embedded via ldflags.
func Build() error {
fmt.Println("Building application...")
version := getVersion()
commit := getCommit()
timestamp := getTimestamp()
ldflags := fmt.Sprintf("-X main.version=%s -X main.commit=%s -X main.timestamp=%s",
version, commit, timestamp)
cmd := exec.Command("go", "build", "-ldflags", ldflags, "-o", binPath(), "./cmd/app")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("build failed: %w", err)
}
fmt.Printf("Build complete: %s\n", binPath())
return nil
}
// Run starts the application locally with hot-reload for development.
func Run() error {
mg.SerialDeps(EnsureDeps, Build)
cmd := exec.Command(filepath.Join(".", binName()), "--config", "config.yaml")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = projectRoot()
fmt.Println("Starting application...")
return cmd.Run()
}
// Test runs all unit and integration tests with coverage reporting.
func Test() error {
mg.SerialDeps(EnsureDeps)
fmt.Println("Running unit tests...")
if err := sh.RunV("go", "test", "-race", "-coverprofile=coverage.out", "./..."); err != nil {
return fmt.Errorf("unit tests failed: %w", err)
}
fmt.Println("Running integration tests...")
if err := sh.RunV("go", "test", "-tags=integration", "-count=1", "./internal/integration/..."); err != nil {
return fmt.Errorf("integration tests failed: %w", err)
}
fmt.Println("Generating coverage report...")
return sh.RunV("go", "tool", "cover", "-html=coverage.out", "-o", "coverage.html")
}
Pattern 2: Aliases and Short Command Names
Use //mage:alias comments to create short ergonomic command names. This is the primary way Mage compensates for Go's verbose function naming convention. Group aliases by frequency — daily operations get the shortest names.
package main
import (
"github.com/magefile/mage/mg"
)
// Serve starts the development server with hot reload.
//mage:alias s
func Serve() error {
mg.SerialDeps(EnsureDeps, Build)
return sh.RunV("go", "run", "./cmd/app")
}
// Test runs unit tests for the current package only.
//mage:alias t
func Test() error {
return runTests("unit")
}
// TestAll runs all tests including integration suites.
//mage:alias ta
func TestAll() error {
return runTests("all")
}
// Build compiles the binary for the current platform.
//mage:alias b
func Build() error {
return buildBinary("")
}
// Clean removes all generated files and binaries.
//mage:alias c
func Clean() error {
return sh.RunV("go", "clean", "-cache", "-testcache", "-modcache")
}
// Deploy pushes the application to the target environment.
//mage:alias d
func Deploy() error {
mg.SerialDeps(EnsureDeps, Test)
fmt.Println("Deploying to production...")
return sh.RunV("./scripts/deploy.sh")
}
// Lint checks code style and runs static analysis.
//mage:alias l
func Lint() error {
if err := sh.RunV("golangci-lint", "run", "./..."); err != nil {
return fmt.Errorf("lint failed: %w", err)
}
fmt.Println("Lint passed ✓")
return nil
}
// All runs the full CI pipeline locally (lint → test → build).
//mage:alias a
func All() error {
mg.SerialDeps(Lint, Test, Build)
fmt.Println("All checks passed ✓")
return nil
}
Pattern 3: Environment-Aware Build Configuration with Cross-Compilation
Use environment variables BUILD_OS, BUILD_ARCH, BUILD_GOOS, and BUILD_GOARCH to control cross-compilation. Inject version information via -ldflags. Use conditional logic based on these variables to customize build output.
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
// CrossCompile builds the application for a different OS/ARCH than the host.
// Set BUILD_OS and BUILD_ARCH environment variables before running:
// BUILD_OS=linux BUILD_ARCH=amd64 mage crossCompile
func CrossCompile() error {
targetOS := os.Getenv("BUILD_OS")
if targetOS == "" {
targetOS = runtime.GOOS
}
targetArch := os.Getenv("BUILD_ARCH")
if targetArch == "" {
targetArch = runtime.GOARCH
}
fmt.Printf("Cross-compiling for %s/%s\n", targetOS, targetArch)
// Validate supported targets
validTargets := map[string]map[string]bool{
"darwin": {"amd64": true, "arm64": true},
"linux": {"amd64": true, "arm64": true, "386": true},
"windows": {"amd64": true, "386": true},
}
if !validTargets[targetOS][targetArch] {
return fmt.Errorf("unsupported target: %s/%s (supported: %+v)",
targetOS, targetArch, validTargets)
}
version := getVersion()
commit := getCommit()
timestamp := getTimestamp()
outputDir := filepath.Join("dist", fmt.Sprintf("%s-%s", targetOS, targetArch))
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("create output dir: %w", err)
}
binaryName := appName()
if targetOS == "windows" {
binaryName += ".exe"
}
outputPath := filepath.Join(outputDir, binaryName)
ldflags := fmt.Sprintf("-X main.version=%s -X main.commit=%s -X main.timestamp=%s",
version, commit, timestamp)
env := os.Environ()
cmd := exec.Command("go", "build",
"-o", outputPath,
"-ldflags", ldflags,
"-GOOS="+targetOS,
"-GOARCH="+targetArch,
"./cmd/app",
)
cmd.Env = append(env,
fmt.Sprintf("GOOS=%s", targetOS),
fmt.Sprintf("GOARCH=%s", targetArch),
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("cross-compile %s/%s: %w", targetOS, targetArch, err)
}
fmt.Printf("Binary written to: %s\n", outputPath)
return nil
}
// getVersion returns the application version from git tags or a default.
func getVersion() string {
if v := os.Getenv("BUILD_VERSION"); v != "" {
return v
}
out, err := exec.Command("git", "describe", "--tags", "--always").Output()
if err != nil {
return "dev"
}
return strings.TrimSpace(string(out))
}
// getCommit returns the current git commit hash.
func getCommit() string {
out, err := exec.Command("git", "rev-parse", "--short", "HEAD").Output()
if err != nil {
return "unknown"
}
return strings.TrimSpace(string(out))
}
// getTimestamp returns the build timestamp in RFC3339 format.
func getTimestamp() string {
out, err := exec.Command("date", "-u", "+%Y-%m-%dT%H:%M:%SZ").Output()
if err != nil {
return "unknown"
}
return strings.TrimSpace(string(out))
}
// appName returns the application name from the module path.
func appName() string {
return filepath.Base(projectRoot())
}
// projectRoot returns the project root directory (parent of magefile.go).
func projectRoot() string {
return filepath.Dir(".")
}
Pattern 4: Helper Functions and Shared Utilities
Private helper functions stay lowercase so Mage ignores them. Use them to factor out common build logic — file path computation, directory creation, dependency installation. Keep the helper package scoped to magefile.go.
package main
import (
"fmt"
"os"
"path/filepath"
)
// ensureDeps checks that required tools and directories exist before running targets.
func ensureDeps() error {
required := []string{"golangci-lint"}
for _, tool := range required {
if !toolExists(tool) {
fmt.Printf("Warning: %s not found, some targets may fail\n", tool)
}
}
dirs := []string{binDir(), distDir()}
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("ensure directory %s: %w", dir, err)
}
}
return nil
}
func toolExists(name string) bool {
_, err := exec.LookPath(name)
return err == nil
}
// Path helpers — always use filepath.Join for cross-platform compatibility.
func binDir() string { return filepath.Join(projectRoot(), "bin") }
func distDir() string { return filepath.Join(projectRoot(), "dist") }
func binPath() string { return filepath.Join(binDir(), appName()) }
func binName() string {
if runtime.GOOS == "windows" {
return appName() + ".exe"
}
return appName()
}
Constraints
MUST DO
- Name public target functions starting with an uppercase letter so Mage recognizes them as executable targets
- Return
error from targets instead of calling os.Exit — this gives Mage proper error reporting and stack traces
- Use
//mage:alias comments for creating short names that are more ergonomic than full function names (e.g., //mage:alias s=Serve)
- Set
BUILD_OS and BUILD_ARCH environment variables before running mage for cross-compilation builds
- Include a
Default variable in the magefile to specify the default target when running mage without arguments
MUST NOT DO
- Don't put private helper functions with uppercase names — Mage will try to execute them as targets. Use lowercase for helpers or prefix with
_ (e.g., _helper())
- Don't ignore errors in target functions — always return
err so Mage reports failures correctly; bare if err != nil {} without returning is a silent failure
- Don't use raw shell strings without
os/exec — prefer Go's os/exec or github.com/magefile/mage/sh package for cross-platform compatibility over string concatenation with backticks or sh -c
- Don't hardcode platform-specific paths — always use
filepath.Join() instead of string concatenation with / or \
- Don't create aliases that shadow standard Mage commands like
-l, -f, --help, or --version
Output Template
When this skill is active, produce the following output structure:
- Project Analysis — Identify Go version, module structure (
go.mod), existing build scripts (Makefile, shell scripts), and current CI pipeline configuration
- Magefile Draft — Complete
magefile.go with target functions, aliases, error handling, and helper utilities matching the project's needs
- Alias Map — Table mapping short command names to full function names for team reference (e.g.,
s → Serve, ta → TestAll)
- Build Configuration — Environment variables needed (
BUILD_OS, BUILD_ARCH, BUILD_VERSION), ldflags injection strategy, and cross-compilation matrix
- CI/CD Integration — Pipeline configuration showing how to install and run Mage in CI (GitHub Actions, GitLab CI, or Jenkins)
Related Skills
| Skill |
Purpose |
makefile |
GNU Make for projects not using Go or needing pattern rules and file dependency tracking |
just-task-runner |
Cross-platform task runner when you prefer YAML-style recipes over Go code |
linux-make-build-system |
GNU Make with cross-compilation support and complex dependency graphs |
Live References
Authoritative documentation links for the Mage build tool. The model follows markdown links at load time to resolve external references and inline content.
1---2name: mage-build-tool3description: Implements Mage build automation using Go code as build scripts, providing type-safe targets, aliases, build flags, environment-aware builds, and cross-compilation for development workflows.4license: MIT5---67891011# Mage — Go-Based Build Tool1213Acts as a senior Go developer who designs build scripts using Mage, leveraging Go's type system and standard library for reliable, maintainable build automation. When this skill is active, the model creates structured `magefile.go` with typed target functions, aliases, error handling patterns, and cross-compilation support.1415## TL;DR Checklist1617- [ ] Install Mage via `go install github.com/magefile/mage@latest` and verify with `mage --version`18- [ ] Create `magefile.go` or `mage/` directory at project root with target functions19- [ ] Name public targets starting with uppercase (e.g., `func Build() error`), private helpers in lowercase20- [ ] Use `//mage:alias` comments for short ergonomic command names (e.g., `//mage:alias s=Serve`)21- [ ] Return `error` from targets instead of calling `os.Exit` for proper Mage error reporting22- [ ] Set `BUILD_OS` and `BUILD_ARCH` environment variables before running mage for cross-compilation23- [ ] Run `mage -l` to list all available targets after adding new ones2425---2627## When to Use2829Use this skill when:3031- You have a Go project and want build logic in actual Go code instead of shell commands embedded in Makefiles or shell scripts32- Team wants type-safe build scripts with IDE autocomplete, refactoring support, and compile-time error detection33- Need cross-compilation controlled by environment variables (`BUILD_OS`, `BUILD_ARCH`, `GOOS`, `GOARCH`) without manual flag management34- Want to use Go's standard library (`os/exec`, `io/ioutil`, `net/http`) for build tasks instead of shelling out to external tools35- Building CLI tools or developer experience tooling for a Go codebase that benefits from aliased short commands3637---3839## When NOT to Use4041Avoid this skill for:4243- You are not using Go — Mage requires Go runtime (use Just or Make instead)44- Need shell-specific features like pattern rules (`%.o: %.c`) or automatic dependency tracking based on file timestamps45- CI/CD environment cannot install Go toolchain — the overhead of downloading and caching Go may outweigh benefits for simple projects46- Simple projects where a shell script or justfile would suffice — Mage adds a dependency layer that creates friction for minimal workflows4748---4950## Core Workflow51521. **Installation and Project Setup** — Install the Mage binary via `go install github.com/magefile/mage@latest`. Verify installation with `mage --version`. For Go modules projects, add a `go.mod` file if one doesn't exist. Initialize Mage in your project with `mage -i` to install the binary globally or use it as a dependency.53 **Checkpoint:** Run `mage -l` and confirm it lists all target functions. The command must succeed before adding more targets.54552. **Create magefile.go with Target Functions** — Write target functions in `magefile.go` at project root. Each public target must be a function named with an initial uppercase letter, accepting no arguments (except optional context), and returning `error`. Use the `mg` package for common operations like directory creation and file copying. Define a `Default` variable to set the default target when running `mage` without arguments.56 **Checkpoint:** Run `mage -l` after every addition to verify new targets appear and aliases are correct.57583. **Aliases and Short Names** — Add `//mage:alias` comments above target functions to create ergonomic short names. Aliases let team members type `mage s` instead of `mage Serve`. Use aliases for frequently-run operations like `serve`, `test`, `build`, `clean`. Never shadow built-in Mage flags (`-l`, `-f`, `--help`).59 **Checkpoint:** Verify `mage -l` shows both the full name and alias in parentheses.60614. **Error Handling Patterns** — Implement consistent error handling across all targets. Return errors for recoverable failures (missing config, failed tests) so Mage reports them with stack traces. Use `log.Fatalf()` only for unrecoverable errors where continuing makes no sense (e.g., invalid command-line flags). Always wrap errors with context using `fmt.Errorf("context: %w", err)`.62 **Checkpoint:** Every target must either return `nil` on success or return a wrapped error — never silently ignore failures.63645. **Build Flags and Cross-Compilation** — Set `BUILD_OS`, `BUILD_ARCH`, `GOOS`, and `GOARCH` environment variables to control cross-compilation. Use `ldflags` to embed version, commit hash, and build timestamp into the binary. Read these variables in your magefile and pass them to `go build` via `-ldflags`.65 **Checkpoint:** Verify cross-compiled binaries run on target platforms using Docker or VMs before merging.6667---6869## Implementation Patterns7071### Pattern 1: Target Functions with Error Handling7273Public targets start with an uppercase letter and return `error`. Use the `mg` package helpers (`mg.Deps`, `mg.SerialDeps`) for dependency ordering between targets. Always wrap errors with context for debuggable failure messages.7475```go76package main7778import (79 "fmt"80 "log"81 "os"82 "os/exec"83 "path/filepath"8485 "github.com/magefile/mage/mg"86 "github.com/magefile/mage/sh"87)8889// Default specifies the default target to run when mage is invoked without arguments.90var Default = Build9192// Build compiles the application with version information embedded via ldflags.93func Build() error {94 fmt.Println("Building application...")9596 version := getVersion()97 commit := getCommit()98 timestamp := getTimestamp()99100 ldflags := fmt.Sprintf("-X main.version=%s -X main.commit=%s -X main.timestamp=%s",101 version, commit, timestamp)102103 cmd := exec.Command("go", "build", "-ldflags", ldflags, "-o", binPath(), "./cmd/app")104 cmd.Stdout = os.Stdout105 cmd.Stderr = os.Stderr106107 if err := cmd.Run(); err != nil {108 return fmt.Errorf("build failed: %w", err)109 }110111 fmt.Printf("Build complete: %s\n", binPath())112 return nil113}114115// Run starts the application locally with hot-reload for development.116func Run() error {117 mg.SerialDeps(EnsureDeps, Build)118119 cmd := exec.Command(filepath.Join(".", binName()), "--config", "config.yaml")120 cmd.Stdout = os.Stdout121 cmd.Stderr = os.Stderr122 cmd.Dir = projectRoot()123124 fmt.Println("Starting application...")125 return cmd.Run()126}127128// Test runs all unit and integration tests with coverage reporting.129func Test() error {130 mg.SerialDeps(EnsureDeps)131132 fmt.Println("Running unit tests...")133 if err := sh.RunV("go", "test", "-race", "-coverprofile=coverage.out", "./..."); err != nil {134 return fmt.Errorf("unit tests failed: %w", err)135 }136137 fmt.Println("Running integration tests...")138 if err := sh.RunV("go", "test", "-tags=integration", "-count=1", "./internal/integration/..."); err != nil {139 return fmt.Errorf("integration tests failed: %w", err)140 }141142 fmt.Println("Generating coverage report...")143 return sh.RunV("go", "tool", "cover", "-html=coverage.out", "-o", "coverage.html")144}145```146147### Pattern 2: Aliases and Short Command Names148149Use `//mage:alias` comments to create short ergonomic command names. This is the primary way Mage compensates for Go's verbose function naming convention. Group aliases by frequency — daily operations get the shortest names.150151```go152package main153154import (155 "github.com/magefile/mage/mg"156)157158// Serve starts the development server with hot reload.159//mage:alias s160func Serve() error {161 mg.SerialDeps(EnsureDeps, Build)162 return sh.RunV("go", "run", "./cmd/app")163}164165// Test runs unit tests for the current package only.166//mage:alias t167func Test() error {168 return runTests("unit")169}170171// TestAll runs all tests including integration suites.172//mage:alias ta173func TestAll() error {174 return runTests("all")175}176177// Build compiles the binary for the current platform.178//mage:alias b179func Build() error {180 return buildBinary("")181}182183// Clean removes all generated files and binaries.184//mage:alias c185func Clean() error {186 return sh.RunV("go", "clean", "-cache", "-testcache", "-modcache")187}188189// Deploy pushes the application to the target environment.190//mage:alias d191func Deploy() error {192 mg.SerialDeps(EnsureDeps, Test)193 fmt.Println("Deploying to production...")194 return sh.RunV("./scripts/deploy.sh")195}196197// Lint checks code style and runs static analysis.198//mage:alias l199func Lint() error {200 if err := sh.RunV("golangci-lint", "run", "./..."); err != nil {201 return fmt.Errorf("lint failed: %w", err)202 }203 fmt.Println("Lint passed ✓")204 return nil205}206207// All runs the full CI pipeline locally (lint → test → build).208//mage:alias a209func All() error {210 mg.SerialDeps(Lint, Test, Build)211 fmt.Println("All checks passed ✓")212 return nil213}214```215216### Pattern 3: Environment-Aware Build Configuration with Cross-Compilation217218Use environment variables `BUILD_OS`, `BUILD_ARCH`, `BUILD_GOOS`, and `BUILD_GOARCH` to control cross-compilation. Inject version information via `-ldflags`. Use conditional logic based on these variables to customize build output.219220```go221package main222223import (224 "fmt"225 "os"226 "os/exec"227 "path/filepath"228 "runtime"229 "strings"230)231232// CrossCompile builds the application for a different OS/ARCH than the host.233// Set BUILD_OS and BUILD_ARCH environment variables before running:234// BUILD_OS=linux BUILD_ARCH=amd64 mage crossCompile235func CrossCompile() error {236 targetOS := os.Getenv("BUILD_OS")237 if targetOS == "" {238 targetOS = runtime.GOOS239 }240241 targetArch := os.Getenv("BUILD_ARCH")242 if targetArch == "" {243 targetArch = runtime.GOARCH244 }245246 fmt.Printf("Cross-compiling for %s/%s\n", targetOS, targetArch)247248 // Validate supported targets249 validTargets := map[string]map[string]bool{250 "darwin": {"amd64": true, "arm64": true},251 "linux": {"amd64": true, "arm64": true, "386": true},252 "windows": {"amd64": true, "386": true},253 }254255 if !validTargets[targetOS][targetArch] {256 return fmt.Errorf("unsupported target: %s/%s (supported: %+v)",257 targetOS, targetArch, validTargets)258 }259260 version := getVersion()261 commit := getCommit()262 timestamp := getTimestamp()263264 outputDir := filepath.Join("dist", fmt.Sprintf("%s-%s", targetOS, targetArch))265 if err := os.MkdirAll(outputDir, 0755); err != nil {266 return fmt.Errorf("create output dir: %w", err)267 }268269 binaryName := appName()270 if targetOS == "windows" {271 binaryName += ".exe"272 }273274 outputPath := filepath.Join(outputDir, binaryName)275276 ldflags := fmt.Sprintf("-X main.version=%s -X main.commit=%s -X main.timestamp=%s",277 version, commit, timestamp)278279 env := os.Environ()280 cmd := exec.Command("go", "build",281 "-o", outputPath,282 "-ldflags", ldflags,283 "-GOOS="+targetOS,284 "-GOARCH="+targetArch,285 "./cmd/app",286 )287 cmd.Env = append(env,288 fmt.Sprintf("GOOS=%s", targetOS),289 fmt.Sprintf("GOARCH=%s", targetArch),290 )291 cmd.Stdout = os.Stdout292 cmd.Stderr = os.Stderr293294 if err := cmd.Run(); err != nil {295 return fmt.Errorf("cross-compile %s/%s: %w", targetOS, targetArch, err)296 }297298 fmt.Printf("Binary written to: %s\n", outputPath)299 return nil300}301302// getVersion returns the application version from git tags or a default.303func getVersion() string {304 if v := os.Getenv("BUILD_VERSION"); v != "" {305 return v306 }307 out, err := exec.Command("git", "describe", "--tags", "--always").Output()308 if err != nil {309 return "dev"310 }311 return strings.TrimSpace(string(out))312}313314// getCommit returns the current git commit hash.315func getCommit() string {316 out, err := exec.Command("git", "rev-parse", "--short", "HEAD").Output()317 if err != nil {318 return "unknown"319 }320 return strings.TrimSpace(string(out))321}322323// getTimestamp returns the build timestamp in RFC3339 format.324func getTimestamp() string {325 out, err := exec.Command("date", "-u", "+%Y-%m-%dT%H:%M:%SZ").Output()326 if err != nil {327 return "unknown"328 }329 return strings.TrimSpace(string(out))330}331332// appName returns the application name from the module path.333func appName() string {334 return filepath.Base(projectRoot())335}336337// projectRoot returns the project root directory (parent of magefile.go).338func projectRoot() string {339 return filepath.Dir(".")340}341```342343### Pattern 4: Helper Functions and Shared Utilities344345Private helper functions stay lowercase so Mage ignores them. Use them to factor out common build logic — file path computation, directory creation, dependency installation. Keep the helper package scoped to `magefile.go`.346347```go348package main349350import (351 "fmt"352 "os"353 "path/filepath"354)355356// ensureDeps checks that required tools and directories exist before running targets.357func ensureDeps() error {358 required := []string{"golangci-lint"}359 for _, tool := range required {360 if !toolExists(tool) {361 fmt.Printf("Warning: %s not found, some targets may fail\n", tool)362 }363 }364365 dirs := []string{binDir(), distDir()}366 for _, dir := range dirs {367 if err := os.MkdirAll(dir, 0755); err != nil {368 return fmt.Errorf("ensure directory %s: %w", dir, err)369 }370 }371372 return nil373}374375func toolExists(name string) bool {376 _, err := exec.LookPath(name)377 return err == nil378}379380// Path helpers — always use filepath.Join for cross-platform compatibility.381func binDir() string { return filepath.Join(projectRoot(), "bin") }382func distDir() string { return filepath.Join(projectRoot(), "dist") }383func binPath() string { return filepath.Join(binDir(), appName()) }384func binName() string {385 if runtime.GOOS == "windows" {386 return appName() + ".exe"387 }388 return appName()389}390```391392---393394## Constraints395396### MUST DO397- Name public target functions starting with an uppercase letter so Mage recognizes them as executable targets398- Return `error` from targets instead of calling `os.Exit` — this gives Mage proper error reporting and stack traces399- Use `//mage:alias` comments for creating short names that are more ergonomic than full function names (e.g., `//mage:alias s=Serve`)400- Set `BUILD_OS` and `BUILD_ARCH` environment variables before running mage for cross-compilation builds401- Include a `Default` variable in the magefile to specify the default target when running `mage` without arguments402403### MUST NOT DO404- Don't put private helper functions with uppercase names — Mage will try to execute them as targets. Use lowercase for helpers or prefix with `_` (e.g., `_helper()`)405- Don't ignore errors in target functions — always return `err` so Mage reports failures correctly; bare `if err != nil {}` without returning is a silent failure406- Don't use raw shell strings without `os/exec` — prefer Go's `os/exec` or `github.com/magefile/mage/sh` package for cross-platform compatibility over string concatenation with backticks or `sh -c`407- Don't hardcode platform-specific paths — always use `filepath.Join()` instead of string concatenation with `/` or `\`408- Don't create aliases that shadow standard Mage commands like `-l`, `-f`, `--help`, or `--version`409410---411412## Output Template413414When this skill is active, produce the following output structure:4154161. **Project Analysis** — Identify Go version, module structure (`go.mod`), existing build scripts (Makefile, shell scripts), and current CI pipeline configuration4172. **Magefile Draft** — Complete `magefile.go` with target functions, aliases, error handling, and helper utilities matching the project's needs4183. **Alias Map** — Table mapping short command names to full function names for team reference (e.g., `s → Serve`, `ta → TestAll`)4194. **Build Configuration** — Environment variables needed (`BUILD_OS`, `BUILD_ARCH`, `BUILD_VERSION`), ldflags injection strategy, and cross-compilation matrix4205. **CI/CD Integration** — Pipeline configuration showing how to install and run Mage in CI (GitHub Actions, GitLab CI, or Jenkins)421422---423424## Related Skills425426| Skill | Purpose |427|---|---|428| `makefile` | GNU Make for projects not using Go or needing pattern rules and file dependency tracking |429| `just-task-runner` | Cross-platform task runner when you prefer YAML-style recipes over Go code |430| `linux-make-build-system` | GNU Make with cross-compilation support and complex dependency graphs |431432---433434## Live References435436> Authoritative documentation links for the Mage build tool. The model follows markdown links at load time to resolve external references and inline content.437438- [Mage Official Documentation](https://magefile.org/)439- [Mage GitHub Repository](https://github.com/magefile/mage)440- [Mage Quick Start Guide](https://github.com/magefile/mage#quick-start)441- [Mage Examples Repository](https://github.com/magefile/mage/tree/master/examples)442- [Mage mg Package Reference](https://pkg.go.dev/github.com/magefile/mage/mg)