Skip to content

CLI 发布与分发

开发完 CLI 工具后,下一步就是让用户能够方便地安装和使用它。Go 的编译特性使得 CLI 分发相对简单——一个二进制文件即可运行。但要实现多平台支持、版本管理、自动更新、包管理器分发等,还需要一系列工具和流程。本篇将系统讲解 Go CLI 工具的发布与分发全流程。

一、跨平台编译

Go 原生支持交叉编译,通过 GOOS(目标操作系统)和 GOARCH(目标架构)两个环境变量控制编译目标。

1. 基本交叉编译

go
package main

import (
	"fmt"
	"runtime"
)

var (
	version   = "dev"
	commit    = "none"
	buildTime = "unknown"
)

func main() {
	fmt.Printf("myapp %s\n", version)
	fmt.Printf("commit: %s\n", commit)
	fmt.Printf("built:  %s\n", buildTime)
	fmt.Printf("platform: %s/%s\n", runtime.GOOS, runtime.GOARCH)
}

编译命令示例:

bash
# 编译 Linux amd64
GOOS=linux GOARCH=amd64 go build -o bin/myapp-linux-amd64 main.go

# 编译 macOS amd64 (Intel)
GOOS=darwin GOARCH=amd64 go build -o bin/myapp-darwin-amd64 main.go

# 编译 macOS arm64 (Apple Silicon)
GOOS=darwin GOARCH=arm64 go build -o bin/myapp-darwin-arm64 main.go

# 编译 Windows amd64
GOOS=windows GOARCH=amd64 go build -o bin/myapp-windows-amd64.exe main.go

# 编译 Linux ARM64 (树莓派等)
GOOS=linux GOARCH=arm64 go build -o bin/myapp-linux-arm64 main.go

2. 常用平台组合

GOOSGOARCH说明
linuxamd6464 位 Linux (最常见)
linuxarm64ARM64 Linux
linux38632 位 Linux
darwinamd64Intel Mac
darwinarm64Apple Silicon Mac
windowsamd6464 位 Windows
windows38632 位 Windows
freebsdamd64FreeBSD

3. CGO 的影响

默认情况下交叉编译会禁用 CGO(CGO_ENABLED=0),这意味着不能使用 C 语言库。如果你的项目依赖 CGO(如使用 SQLite),则需要安装对应平台的 C 工具链,交叉编译会复杂很多。建议 CLI 工具尽量使用纯 Go 实现,避免 CGO 依赖。

bash
# 纯 Go 编译(推荐,无 CGO 依赖)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o myapp main.go

二、构建脚本:Makefile

当需要支持多个平台时,手动执行编译命令太繁琐。Makefile 可以自动化构建流程。

makefile
# 变量定义
BINARY_NAME := myapp
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BUILD_TIME := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
LDFLAGS := -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.buildTime=$(BUILD_TIME)

# 目标平台列表
PLATFORMS := linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64

# 默认目标
.PHONY: build
build:
	go build -ldflags "$(LDFLAGS)" -o bin/$(BINARY_NAME) main.go

# 编译所有平台
.PHONY: build-all
build-all:
	@for platform in $(PLATFORMS); do \
		GOOS=$${platform%/*}; \
		GOARCH=$${platform#*/}; \
		OUTPUT=bin/$(BINARY_NAME)-$$GOOS-$$GOARCH; \
		if [ $$GOOS = windows ]; then \
			OUTPUT=$$OUTPUT.exe; \
		fi; \
		echo "Building $$OUTPUT..."; \
		CGO_ENABLED=0 GOOS=$$GOOS GOARCH=$$GOARCH go build -ldflags "$(LDFLAGS)" -o $$OUTPUT main.go; \
	done

# 清理
.PHONY: clean
clean:
	rm -rf bin/

# 运行测试
.PHONY: test
test:
	go test ./... -v -cover

# 安装
.PHONY: install
install:
	go install -ldflags "$(LDFLAGS)" main.go

# 生成自动补全脚本
.PHONY: completion
completion:
	@echo "生成 shell 补全脚本..."
	go run main.go completion bash > completion/myapp.bash
	go run main.go completion zsh > completion/_myapp
	go run main.go completion fish > completion/myapp.fish

使用方式:

bash
make build        # 编译当前平台
make build-all    # 编译所有平台
make test         # 运行测试
make clean        # 清理构建产物
make install      # 安装到 $GOPATH/bin

在 Windows 上可以使用 make(通过 Chocolatey 或 WSL 安装),也可以用 PowerShell 脚本替代。GoReleaser(后面会讲)可以完全替代 Makefile 的跨平台编译功能。

三、版本信息注入:-ldflags

-ldflags 是 Go 链接器参数,可以在编译时将变量值注入到二进制文件中。最典型的用途是注入版本号、提交哈希和构建时间。

1. 原理

在 Go 代码中定义包级变量(默认值),编译时通过 -ldflags 覆盖:

go
package main

import (
	"fmt"
	"runtime"
)

// 这些变量在编译时通过 -ldflags 注入
var (
	version   = "dev"     // 版本号
	commit    = "none"    // 提交哈希
	buildTime = "unknown" // 构建时间
	goVersion = "unknown" // Go 版本
)

func main() {
	fmt.Printf("myapp\n")
	fmt.Printf("  version:    %s\n", version)
	fmt.Printf("  commit:     %s\n", commit)
	fmt.Printf("  build time: %s\n", buildTime)
	fmt.Printf("  go version: %s\n", goVersion)
	fmt.Printf("  platform:   %s/%s\n", runtime.GOOS, runtime.GOARCH)
}

2. 编译时注入

bash
# 获取版本信息
VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev")
COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
GO_VERSION=$(go version | awk '{print $3}')

# 编译并注入变量
go build -ldflags "\
  -X main.version=$VERSION \
  -X main.commit=$COMMIT \
  -X main.buildTime=$BUILD_TIME \
  -X main.goVersion=$GO_VERSION" \
  -o myapp main.go

3. 注入到子包

如果变量定义在子包中,需要使用完整导入路径:

go
// pkg/version/version.go
package version

var (
	Version   = "dev"
	Commit    = "none"
	BuildTime = "unknown"
)
bash
# 注意路径格式:-X <import-path>.<variable>=<value>
go build -ldflags "\
  -X myapp/pkg/version.Version=$VERSION \
  -X myapp/pkg/version.Commit=$COMMIT \
  -X myapp/pkg/version.BuildTime=$BUILD_TIME" \
  -o myapp main.go

4. 在 Cobra 中使用

go
package main

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var (
	version = "dev"
	commit  = "none"
	date    = "unknown"
)

func main() {
	rootCmd := &cobra.Command{
		Use:   "myapp",
		Short: "一个 CLI 工具",
	}

	versionCmd := &cobra.Command{
		Use:   "version",
		Short: "显示版本信息",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Printf("myapp %s\n", version)
			fmt.Printf("  commit: %s\n", commit)
			fmt.Printf("  date:   %s\n", date)
		},
	}

	// 也可以设置 rootCmd.Version,使 --version 自动可用
	rootCmd.Version = version
	rootCmd.SetVersionTemplate("myapp {{.Version}}\n")

	rootCmd.AddCommand(versionCmd)
	rootCmd.Execute()
}

四、GoReleaser:自动化发布工具

GoReleaser 是 Go 生态中最流行的发布自动化工具。它可以从 git tag 出发,自动完成:多平台编译、打包、生成 changelog、创建 GitHub Release、发布到 Homebrew/Docker 等。

1. 安装 GoReleaser

bash
# 方式一:go install
go install github.com/goreleaser/goreleaser@latest

# 方式二:Homebrew (macOS)
brew install goreleaser

# 方式三:Scoop (Windows)
scoop install goreleaser

2. 初始化配置

在项目根目录执行:

bash
goreleaser init

这会生成 .goreleaser.yaml 配置文件。

3. .goreleaser.yaml 配置详解

下面是一个完整的配置示例:

yaml
# 项目信息
project_name: myapp

# 编译前检查
before:
  hooks:
    - go mod tidy
    - go test ./...

# 编译配置
builds:
  - id: myapp
    main: ./main.go
    binary: myapp
    env:
      - CGO_ENABLED=0
    goos:
      - linux
      - darwin
      - windows
    goarch:
      - amd64
      - arm64
    ldflags:
      - -s -w  # 去除调试信息,减小体积
      - -X main.version={{.Version}}
      - -X main.commit={{.Commit}}
      - -X main.date={{.Date}}

# 归档配置
archives:
  - id: default
    name_template: >-
      {{ .ProjectName }}_
      {{- .Version }}_
      {{- if eq .Os "darwin" }}macos
      {{- else }}{{ .Os }}{{ end }}_
      {{- .Arch }}
    format_overrides:
      - goos: windows
        formats: [zip]
    files:
      - README.md
      - LICENSE
      - completions/*

# 生成 changelog
changelog:
  sort: asc
  filters:
    exclude:
      - '^docs:'
      - '^test:'
      - '^chore:'
      - Merge pull request
      - Merge branch

# GitHub Release
release:
  github:
    owner: yourusername
    name: myapp
  draft: false
  prerelease: auto
  name_template: "{{.TagName}} ({{.Date}})"

# Homebrew Tap
brews:
  - name: myapp
    repository:
      owner: yourusername
      name: homebrew-tap
    homepage: https://github.com/yourusername/myapp
    description: "A CLI tool built with Go"
    test: |
      system "#{bin}/myapp", "--version"
    install: |
      bin.install "myapp"
      bash_completion.install "completions/myapp.bash" => "myapp"
      zsh_completion.install "completions/_myapp" => "_myapp"

# Docker 镜像
dockers:
  - image_templates:
      - "yourusername/myapp:latest"
      - "yourusername/myapp:{{ .Tag }}"
      - "yourusername/myapp:v{{ .Major }}.{{ .Minor }}"
    dockerfile: Dockerfile
    use: buildx
    build_flag_templates:
      - "--pull"
      - "--label=org.opencontainers.image.title={{.ProjectName}}"
      - "--label=org.opencontainers.image.version={{.Version}}"
      - "--label=org.opencontainers.image.source={{.GitURL}}"

# 生成 SBOM (软件物料清单)
sboms:
  - artifacts: archive

# 快照模式(不发布,仅测试)
snapshot:
  name_template: "{{ incpatch .Version }}-next"

# 校验和
checksum:
  name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"

4. 本地测试

在正式发布前,可以用 --snapshot 模式本地测试构建流程:

bash
# 快照构建(不需要 git tag,不会发布)
goreleaser release --snapshot --clean

# 仅构建,跳过发布
goreleaser build --clean

# 检查配置
goreleaser check

5. 正式发布

GoReleaser 依赖 git tag 触发发布:

bash
# 1. 打标签
git tag v1.0.0
git push origin v1.0.0

# 2. 设置 GitHub Token
export GITHUB_TOKEN=ghp_xxxxxxxxxxxx

# 3. 发布
goreleaser release --clean

GoReleaser 会自动执行:编译多平台二进制 → 打包归档 → 生成 changelog → 创建 GitHub Release → 上传产物 → 发布 Homebrew/Docker。

6. CI/CD 集成

在 GitHub Actions 中集成 GoReleaser(.github/workflows/release.yml):

yaml
name: release

on:
  push:
    tags:
      - "v*"

permissions:
  contents: write
  packages: write

jobs:
  goreleaser:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: 1.23

      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@v6
        with:
          distribution: goreleaser
          version: latest
          args: release --clean
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          # 如果要推送到 Homebrew Tap 仓库,需要额外 token
          HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}

五、语义化版本管理

语义化版本(Semantic Versioning,SemVer)是软件版本号的规范。格式为 MAJOR.MINOR.PATCH

  • MAJOR:不兼容的 API 变更(破坏性更新)
  • MINOR:向后兼容的功能新增
  • PATCH:向后兼容的 Bug 修复

示例:v1.2.3 表示主版本 1、次版本 2、补丁版本 3。

1. Git Tag 规范

bash
# 发布 v1.0.0
git tag -a v1.0.0 -m "首个正式版本"
git push origin v1.0.0

# Bug 修复
git tag -a v1.0.1 -m "修复登录问题"
git push origin v1.0.1

# 新功能
git tag -a v1.1.0 -m "新增导出功能"
git push origin v1.1.0

# 破坏性变更
git tag -a v2.0.0 -m "重构 API"
git push origin v2.0.0

2. 预发布版本

bash
# Alpha 版本
git tag v1.0.0-alpha.1

# Beta 版本
git tag v1.0.0-beta.1

# RC (Release Candidate)
git tag v1.0.0-rc.1

六、更新检查机制

CLI 工具应该让用户知道是否有新版本可用。常见的实现方式是在运行时(或定期)检查 GitHub Release 的最新版本。

go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"

	"github.com/spf13/cobra"
)

var version = "1.0.0"

// GitHubRelease 表示 GitHub Release 的响应
type GitHubRelease struct {
	TagName string `json:"tag_name"`
	Name    string `json:"name"`
	HTMLURL string `json:"html_url"`
}

// checkUpdate 检查是否有新版本
func checkUpdate(currentVersion string) (*GitHubRelease, bool, error) {
	url := "https://api.github.com/repos/yourusername/myapp/releases/latest"

	client := &http.Client{Timeout: 5 * time.Second}
	resp, err := client.Get(url)
	if err != nil {
		return nil, false, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, false, fmt.Errorf("GitHub API 返回 %d", resp.StatusCode)
	}

	var release GitHubRelease
	if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
		return nil, false, err
	}

	// 比较版本号(简化版,实际应使用 semver 库)
	latest := release.TagName
	if latest == "" {
		return nil, false, nil
	}

	// 去掉 v 前缀
	if latest[0] == 'v' {
		latest = latest[1:]
	}

	hasUpdate := latest != currentVersion && latest != ""
	return &release, hasUpdate, nil
}

func main() {
	rootCmd := &cobra.Command{
		Use:   "myapp",
		Short: "一个支持更新检查的 CLI 工具",
		PersistentPreRun: func(cmd *cobra.Command, args []string) {
			// 异步检查更新(不阻塞主流程)
			go func() {
				release, hasUpdate, err := checkUpdate(version)
				if err != nil || !hasUpdate {
					return
				}
				fmt.Fprintf(os.Stderr, "\n⚠ 有新版本可用: %s (当前: v%s)\n", release.TagName, version)
				fmt.Fprintf(os.Stderr, "  下载: %s\n\n", release.HTMLURL)
			}()
		},
	}

	versionCmd := &cobra.Command{
		Use:   "version",
		Short: "显示版本信息并检查更新",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Printf("当前版本: v%s\n", version)

			fmt.Println("正在检查更新...")
			release, hasUpdate, err := checkUpdate(version)
			if err != nil {
				fmt.Fprintf(os.Stderr, "检查更新失败: %v\n", err)
				return
			}

			if hasUpdate {
				fmt.Printf("发现新版本: %s\n", release.TagName)
				fmt.Printf("下载地址: %s\n", release.HTMLURL)
			} else {
				fmt.Println("已是最新版本")
			}
		},
	}

	rootCmd.AddCommand(versionCmd)
	rootCmd.Execute()
}

七、安装脚本

提供一个安装脚本,让用户一行命令安装:

go
package main

import (
	"fmt"
	"os"
)

// 这个程序演示安装脚本的生成逻辑
// 实际的安装脚本是一个 shell 脚本,由用户通过 curl | bash 执行
func main() {
	// 生成 install.sh 脚本内容
	installScript := generateInstallScript()
	
	fmt.Println("生成的 install.sh 脚本:")
	fmt.Println("========================================")
	fmt.Println(installScript)
	fmt.Println("========================================")
	fmt.Println()
	fmt.Println("用户安装方式:")
	fmt.Println("  curl -fsSL https://raw.githubusercontent.com/yourusername/myapp/main/install.sh | bash")
}

func generateInstallScript() string {
	return `#!/bin/sh
# myapp 安装脚本
# 用法: curl -fsSL https://raw.githubusercontent.com/yourusername/myapp/main/install.sh | bash

set -e

# 配置
REPO="yourusername/myapp"
INSTALL_DIR="/usr/local/bin"
BINARY_NAME="myapp"

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'

info() { printf "${GREEN}[INFO]${NC} %s\n" "$1"; }
warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$1"; }
error() { printf "${RED}[ERROR]${NC} %s\n" "$1" >&2; exit 1; }

# 检测操作系统
detect_os() {
    case "$(uname -s)" in
        Linux*) OS="linux";;
        Darwin*) OS="darwin";;
        *) error "不支持的操作系统: $(uname -s)";;
    esac
}

# 检测架构
detect_arch() {
    case "$(uname -m)" in
        x86_64|amd64) ARCH="amd64";;
        arm64|aarch64) ARCH="arm64";;
        *) error "不支持的架构: $(uname -m)";;
    esac
}

# 获取最新版本
get_latest_version() {
    VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \
        | grep '"tag_name"' \
        | sed -E 's/.*"([^"]+)".*/\1/')
    
    if [ -z "$VERSION" ]; then
        error "无法获取最新版本号"
    fi
    info "最新版本: ${VERSION}"
}

# 下载二进制
download_binary() {
    DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${BINARY_NAME}-${OS}-${ARCH}"
    
    if [ "$OS" = "windows" ]; then
        DOWNLOAD_URL="${DOWNLOAD_URL}.exe"
        BINARY_NAME="${BINARY_NAME}.exe"
    fi
    
    TMP_FILE="/tmp/${BINARY_NAME}"
    
    info "下载 ${DOWNLOAD_URL}"
    curl -fsSL "$DOWNLOAD_URL" -o "$TMP_FILE"
    chmod +x "$TMP_FILE"
}

# 安装
install_binary() {
    if [ -w "$INSTALL_DIR" ]; then
        mv "$TMP_FILE" "${INSTALL_DIR}/${BINARY_NAME}"
    else
        info "需要 sudo 权限安装到 ${INSTALL_DIR}"
        sudo mv "$TMP_FILE" "${INSTALL_DIR}/${BINARY_NAME}"
    fi
    
    info "已安装到 ${INSTALL_DIR}/${BINARY_NAME}"
}

# 验证
verify_install() {
    if command -v "$BINARY_NAME" >/dev/null 2>&1; then
        info "安装成功!"
        $BINARY_NAME --version
    else
        warn "${BINARY_NAME} 不在 PATH 中,请将 ${INSTALL_DIR} 加入 PATH"
    fi
}

# 主流程
main() {
    info "开始安装 ${BINARY_NAME}..."
    detect_os
    detect_arch
    info "系统: ${OS}/${ARCH}"
    get_latest_version
    download_binary
    install_binary
    verify_install
    info "完成!"
}

main
`
}

install.sh 放在仓库根目录,用户通过以下命令安装:

bash
curl -fsSL https://raw.githubusercontent.com/yourusername/myapp/main/install.sh | bash

八、完整示例:CLI 工具的完整发布流程

下面演示一个完整的项目结构和发布流程。项目结构如下:

myapp/
├── main.go              # 入口文件
├── go.mod
├── Makefile             # 构建脚本
├── .goreleaser.yaml     # GoReleaser 配置
├── install.sh           # 安装脚本
├── Dockerfile           # Docker 镜像配置
└── .github/
    └── workflows/
        └── release.yml  # CI/CD 发布流程

main.go:

go
package main

import (
	"fmt"
	"os"
	"runtime"

	"github.com/spf13/cobra"
)

// 编译时注入的版本信息
var (
	version   = "dev"
	commit    = "none"
	buildTime = "unknown"
)

func main() {
	rootCmd := &cobra.Command{
		Use:   "myapp",
		Short: "一个完整的 CLI 工具示例",
		Long:  "MyApp 是一个演示完整发布流程的 Go CLI 工具。",
		Version: version,
	}

	rootCmd.SetVersionTemplate(fmt.Sprintf(
		"myapp %s\n  commit: %s\n  built:  %s\n  go:     %s\n  os:     %s/%s\n",
		version, commit, buildTime, runtime.Version(), runtime.GOOS, runtime.GOARCH,
	))

	// hello 子命令
	helloCmd := &cobra.Command{
		Use:   "hello [name]",
		Short: "打招呼",
		Args:  cobra.MaximumNArgs(1),
		Run: func(cmd *cobra.Command, args []string) {
			name := "World"
			if len(args) > 0 {
				name = args[0]
			}
			verbose, _ := cmd.Flags().GetBool("verbose")
			if verbose {
				fmt.Printf("详细信息: version=%s, commit=%s\n", version, commit)
			}
			fmt.Printf("Hello, %s!\n", name)
		},
	}
	helloCmd.Flags().BoolP("verbose", "v", false, "显示详细信息")

	// info 子命令
	infoCmd := &cobra.Command{
		Use:   "info",
		Short: "显示系统信息",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Printf("操作系统: %s\n", runtime.GOOS)
			fmt.Printf("架构:     %s\n", runtime.GOARCH)
			fmt.Printf("CPU 核数: %d\n", runtime.NumCPU())
			fmt.Printf("Go 版本:  %s\n", runtime.Version())
		},
	}

	rootCmd.AddCommand(helloCmd, infoCmd)

	if err := rootCmd.Execute(); err != nil {
		os.Exit(1)
	}
}

Dockerfile:

dockerfile
# 构建阶段
FROM golang:1.23-alpine AS builder

WORKDIR /app

# 复制源码
COPY go.mod go.sum ./
RUN go mod download

COPY . .

# 编译
ARG VERSION=dev
ARG COMMIT=none
ARG BUILD_TIME=unknown

RUN CGO_ENABLED=0 go build \
    -ldflags "-s -w \
    -X main.version=${VERSION} \
    -X main.commit=${COMMIT} \
    -X main.buildTime=${BUILD_TIME}" \
    -o /myapp main.go

# 运行阶段
FROM alpine:3.19

RUN apk --no-cache add ca-certificates

COPY --from=builder /myapp /usr/local/bin/myapp

ENTRYPOINT ["myapp"]
CMD ["--help"]

本地构建 Docker 镜像:

bash
docker build \
  --build-arg VERSION=$(git describe --tags --always) \
  --build-arg COMMIT=$(git rev-parse --short HEAD) \
  --build-arg BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
  -t myapp:latest .

发布流程汇总:

bash
# 1. 确保代码通过测试
go test ./...

# 2. 更新版本(语义化版本)
# 2.1 修复 Bug → 补丁版本
git tag v1.0.1
# 2.2 新增功能 → 次版本
git tag v1.1.0
# 2.3 破坏性变更 → 主版本
git tag v2.0.0

# 3. 推送 tag 触发 GoReleaser
git push origin v1.0.0

# 4. GoReleaser 自动完成:
#    - 多平台编译(linux/darwin/windows × amd64/arm64)
#    - 注入版本信息
#    - 打包归档(tar.gz / zip)
#    - 生成 changelog
#    - 创建 GitHub Release
#    - 发布 Homebrew Tap
#    - 推送 Docker 镜像
#    - 生成 checksums.txt

# 5. 用户安装
# Homebrew
brew install yourusername/tap/myapp

# Docker
docker run --rm yourusername/myapp --version

# 安装脚本
curl -fsSL https://raw.githubusercontent.com/yourusername/myapp/main/install.sh | bash

# go install
go install github.com/yourusername/myapp@latest

九、小结

本篇系统讲解了 Go CLI 工具的发布与分发全流程:

  1. 跨平台编译:通过 GOOS/GOARCH 实现交叉编译,CGO_ENABLED=0 避免依赖问题。
  2. Makefile:自动化构建流程,支持多平台批量编译。
  3. 版本信息注入:通过 -ldflags -X 在编译时注入版本号、提交哈希、构建时间。
  4. GoReleaser:自动化发布工具,一键完成编译、打包、发布。
    • .goreleaser.yaml 配置编译目标、归档格式、changelog。
    • GitHub Release 自动创建。
    • Homebrew Tap 自动发布。
    • Docker 镜像自动推送。
    • CI/CD 集成通过 GitHub Actions。
  5. 语义化版本MAJOR.MINOR.PATCH 规范,支持 alpha/beta/rc 预发布。
  6. 更新检查:通过 GitHub API 检查最新版本,提示用户更新。
  7. 安装脚本:一行命令安装,自动检测平台架构。
  8. 完整流程:从代码到用户安装的完整链路,涵盖 Docker 镜像构建和多种分发渠道。

至此,Go CLI 开发系列教程完结。从标准库基础到 Cobra 框架、Viper 配置、交互式 CLI、Bubbletea TUI,再到发布分发,你已经掌握了构建生产级 Go CLI 工具的全部技能。