协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
mage-build-tool【开发与工程】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.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
name: mage-build-tool description: 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. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: coding triggers: mage, magefile, go build tool, mg package, main.go targets, how do i write build scripts in go, golang build automation, type-safe makefile archetypes: - tactical - generation anti_triggers: - brainstorming - vague ideation - code golf - over-engineering response_profile: verbosity: low directive_strength: high abstraction_level: operational role: implementation scope: implementation output-format: code content-types: - code - guidance - do-dont - examples related-skills: makefile, just-task-runner, coding-make, linux-make-build-system
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.
go install github.com/magefile/mage@latest and verify with mage --versionmagefile.go or mage/ directory at project root with target functionsfunc Build() error), private helpers in lowercase//mage:alias comments for short ergonomic command names (e.g., //mage:alias s=Serve)error from targets instead of calling os.Exit for proper Mage error reportingBUILD_OS and BUILD_ARCH environment variables before running mage for cross-compilationmage -l to list all available targets after adding new onesUse this skill when:
BUILD_OS, BUILD_ARCH, GOOS, GOARCH) without manual flag managementos/exec, io/ioutil, net/http) for build tasks instead of shelling out to external toolsAvoid this skill for:
%.o: %.c) or automatic dependency tracking based on file timestampsInstallation 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.
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")
}
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
}
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(".")
}
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()
}
error from targets instead of calling os.Exit — this gives Mage proper error reporting and stack traces//mage:alias comments for creating short names that are more ergonomic than full function names (e.g., //mage:alias s=Serve)BUILD_OS and BUILD_ARCH environment variables before running mage for cross-compilation buildsDefault variable in the magefile to specify the default target when running mage without arguments_ (e.g., _helper())err so Mage reports failures correctly; bare if err != nil {} without returning is a silent failureos/exec — prefer Go's os/exec or github.com/magefile/mage/sh package for cross-platform compatibility over string concatenation with backticks or sh -cfilepath.Join() instead of string concatenation with / or \-l, -f, --help, or --versionWhen this skill is active, produce the following output structure:
go.mod), existing build scripts (Makefile, shell scripts), and current CI pipeline configurationmagefile.go with target functions, aliases, error handling, and helper utilities matching the project's needss → Serve, ta → TestAll)BUILD_OS, BUILD_ARCH, BUILD_VERSION), ldflags injection strategy, and cross-compilation matrix| 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 |
Authoritative documentation links for the Mage build tool. The model follows markdown links at load time to resolve external references and inline content.