界面已本地化;技能介绍与说明文档按所选语言由 AI 自动翻译,代码、命令、路径和专业术语保留原文,以原作者文档为准。
探索技能
开发与工程 · SKILL.md

mage-build-tool

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 快照 · 自动分类 · 以原作者文档为准

技能说明

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


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

  • Install Mage via go install github.com/magefile/mage@latest and verify with mage --version
  • Create magefile.go or mage/ directory at project root with target functions
  • Name public targets starting with uppercase (e.g., func Build() error), private helpers in lowercase
  • Use //mage:alias comments for short ergonomic command names (e.g., //mage:alias s=Serve)
  • Return error from targets instead of calling os.Exit for proper Mage error reporting
  • Set BUILD_OS and BUILD_ARCH environment variables before running mage for cross-compilation
  • Run mage -l to list all available targets after adding new ones

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

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

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

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

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

  5. 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:

  1. Project Analysis — Identify Go version, module structure (go.mod), existing build scripts (Makefile, shell scripts), and current CI pipeline configuration
  2. Magefile Draft — Complete magefile.go with target functions, aliases, error handling, and helper utilities matching the project's needs
  3. Alias Map — Table mapping short command names to full function names for team reference (e.g., s → Serve, ta → TestAll)
  4. Build Configuration — Environment variables needed (BUILD_OS, BUILD_ARCH, BUILD_VERSION), ldflags injection strategy, and cross-compilation matrix
  5. CI/CD Integration — Pipeline configuration showing how to install and run Mage in CI (GitHub Actions, GitLab CI, or Jenkins)

Related Skills

SkillPurpose
makefileGNU Make for projects not using Go or needing pattern rules and file dependency tracking
just-task-runnerCross-platform task runner when you prefer YAML-style recipes over Go code
linux-make-build-systemGNU 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.

我的收藏