golang cross build

2 minute read

Cross build

BUILD_DIR = output
APP = sshanger
GOOS ?= windows
GOARCH ?= amd64
STRIP ?= true  # 默认启用
# 统一转换为小写,避免大小写问题
STRIP := $(shell echo $(STRIP) | tr '[:upper:]' '[:lower:]')

# 自动检测当前系统类型
ifeq ($(OS),Windows_NT)
    HOST_OS := windows
else
    UNAME_S := $(shell uname -s)
    ifeq ($(UNAME_S),Darwin)
        HOST_OS := darwin
    else
        HOST_OS := linux
    endif
endif

# 自动检测当前系统架构
UNAME_M := $(shell uname -m)
ifeq ($(UNAME_M),x86_64)
    HOST_ARCH := amd64
else ifeq ($(UNAME_M),arm64)
    HOST_ARCH := arm64
else ifneq (,$(findstring arm,$(UNAME_M)))
    HOST_ARCH := arm
else
    HOST_ARCH := $(UNAME_M)
endif

# 获取git信息(兼容无git仓库的情况)
VERSION := $(shell git branch --show-current 2>/dev/null || echo "unknown")
CommitHash := $(shell git log -1 --pretty=format:"%H" 2>/dev/null || echo "unknown")

# 输出文件后缀
suffix := $(if $(filter $(GOOS),windows),.exe,)

# 交叉编译工具链配置
ifeq ($(HOST_OS),darwin)
    ifeq ($(GOOS),windows)
        CC := $(if $(filter $(HOST_ARCH),arm64),/opt/homebrew/bin/x86_64-w64-mingw32-gcc,x86_64-w64-mingw32-gcc)
        CXX := $(if $(filter $(HOST_ARCH),arm64),/opt/homebrew/bin/x86_64-w64-mingw32-g++,x86_64-w64-mingw32-g++)
    else ifeq ($(GOOS),linux)
        CC := $(if $(filter $(HOST_ARCH),arm64),/opt/homebrew/bin/x86_64-linux-musl-gcc,x86_64-linux-musl-gcc)
        CXX := $(if $(filter $(HOST_ARCH),arm64),/opt/homebrew/bin/x86_64-linux-musl-g++,x86_64-linux-musl-g++)
    else ifeq ($(GOOS),darwin)
        CC := clang
        CXX := clang++
    endif
else ifeq ($(HOST_OS),linux)
    ifeq ($(GOOS),windows)
        CC := x86_64-w64-mingw32-gcc
        CXX := x86_64-w64-mingw32-g++
    else ifeq ($(GOOS),linux)
        CC := gcc
        CXX := g++
    endif
else ifeq ($(HOST_OS),windows)
    ifeq ($(GOOS),windows)
        CC := x86_64-w64-mingw32-gcc
        CXX := x86_64-w64-mingw32-g++
    else
        $(error "Cross-compiling from Windows to non-Windows is not supported")
    endif
endif

# 链接选项(根据 STRIP 控制 -s -w)
ifeq ($(STRIP),true)
    GO_LDFLAGS := -s -w -X main.Version=$(VERSION) -X main.GitCommit=$(CommitHash)
else
    GO_LDFLAGS := -X main.Version=$(VERSION) -X main.GitCommit=$(CommitHash)
    $(info "注意: 未启用二进制文件剥离 (-s -w),生成的文件体积会更大")
endif

# Go 编译前缀
PREFIX := CGO_ENABLED=1 CC=$(CC) CXX=$(CXX) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -x -v

# 平台构建通用函数
define build-platform
    $(eval _GOOS := $(1))
    $(eval _GOARCH := $(2))
    $(eval _SUFFIX := $(if $(filter $(_GOOS),windows),.exe,))
    $(eval _IS_CMD := $(filter $(3),cmd))  # 是否构建控制工具

    @echo "Building $(if $(_IS_CMD),control tool,main program) for $(_GOOS)/$(_GOARCH)..."

    # 设置编译器(覆盖全局变量,确保正确)
    $(eval _CC := )
    $(eval _CXX := )
    $(if $(filter $(_GOOS),windows), \
        $(eval _CC := $(if $(and $(filter $(HOST_OS),darwin), $(filter $(HOST_ARCH),arm64)), \
                         /opt/homebrew/bin/x86_64-w64-mingw32-gcc, x86_64-w64-mingw32-gcc)), \
        $(eval _CXX := $(if $(and $(filter $(HOST_OS),darwin), $(filter $(HOST_ARCH),arm64)), \
                          /opt/homebrew/bin/x86_64-w64-mingw32-g++, x86_64-w64-mingw32-g++)))
    $(if $(filter $(_GOOS),linux), \
        $(eval _CC := $(if $(and $(filter $(HOST_OS),darwin), $(filter $(HOST_ARCH),arm64)), \
                         /opt/homebrew/bin/x86_64-linux-musl-gcc, gcc)), \
        $(eval _CXX := $(if $(and $(filter $(HOST_OS),darwin), $(filter $(HOST_ARCH),arm64)), \
                          /opt/homebrew/bin/x86_64-linux-musl-g++, g++)))
    $(if $(filter $(_GOOS),darwin), \
        $(eval _CC := clang), $(eval _CXX := clang++))

    # 输出路径和源码
    $(eval _OUTPUT := $(BUILD_DIR)/$(APP)$(if $(_IS_CMD),ctl,)-$(_GOOS)-$(_GOARCH)$(_SUFFIX))
    $(eval _SOURCE := $(if $(_IS_CMD),sshanger/cmd/hangerctl,main.go))

    # 执行编译
    CGO_ENABLED=1 \
    CC=$(_CC) \
    CXX=$(_CXX) \
    GOOS=$(_GOOS) \
    GOARCH=$(_GOARCH) \
    go build -x -v \
    -ldflags "$(GO_LDFLAGS)" \
    -o $(_OUTPUT) \
    $(_SOURCE)
endef

# 目标定义(全部声明为伪目标,避免文件冲突)
.PHONY: all version clean build build-debug \
        windows linux linux-arm64 mac mac-arm64 \
        cmd cmd-debug bugfix run ctl help

# 默认目标:显示帮助
all: help

## version: 打印版本和系统信息
version:
	@echo "Branch: $(VERSION)"
	@echo "CommitHash: $(CommitHash)"
	@echo "Host OS: $(HOST_OS)"
	@echo "Host Arch: $(HOST_ARCH)"
	@echo "Target OS: $(GOOS)"
	@echo "Target Arch: $(GOARCH)"

## clean: 清理编译产物
clean:
	@echo "Cleaning $(BUILD_DIR)..."
	@rm -rf $(BUILD_DIR)

# 创建输出目录
$(BUILD_DIR):
	@mkdir -p $(BUILD_DIR)

$(BUILD_DIR)/bugfix:
	@mkdir -p $(BUILD_DIR)/bugfix

## build: 构建当前平台版本(默认linux/amd64,可指定GOOS/GOARCH)
build: $(BUILD_DIR)
	$(call build-platform,$(GOOS),$(GOARCH))

## build-debug: 构建带调试信息的版本(禁用 -s -w)
build-debug:
	$(MAKE) build STRIP=false  # 强制传递 STRIP=false

## windows: 构建Windows/amd64版本
windows: $(BUILD_DIR)
	$(call build-platform,windows,amd64)

## linux: 构建Linux/amd64版本
linux: $(BUILD_DIR)
	$(call build-platform,linux,amd64)

## linux-arm64: 构建Linux/arm64版本
linux-arm64: $(BUILD_DIR)
	$(call build-platform,linux,arm64)

## mac: 构建macOS/amd64版本(Intel)
mac: $(BUILD_DIR)
	$(call build-platform,darwin,amd64)

## mac-arm64: 构建macOS/arm64版本(Apple Silicon)
mac-arm64: $(BUILD_DIR)
	$(call build-platform,darwin,arm64)

## cmd: 构建控制工具(支持跨平台,如 make cmd GOOS=windows)
cmd: $(BUILD_DIR)
	$(call build-platform,$(GOOS),$(GOARCH),cmd)

## cmd-debug: 构建带调试信息的控制工具
cmd-debug:
	$(MAKE) cmd STRIP=false  # 强制传递 STRIP=false

## bugfix: 构建bugfix版本(需指定id,如 make bugfix id=123)
bugfix: $(BUILD_DIR)/bugfix
	@echo "Building bugfix version $(id)..."
	$(PREFIX) -ldflags "$(GO_LDFLAGS)" -o $(BUILD_DIR)/bugfix/$(APP)-bugfix-$(id)-$(GOOS)-$(GOARCH)$(suffix)

## run: 直接运行主程序(带竞态检测)
run:
	go run -race main.go

## ctl: 直接运行控制工具
ctl:
	go run sshanger/cmd/hangerctl

## help: 显示帮助信息
help:
	@echo "Usage: make [target]\n"
	@sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/  /'

Comments