1. 概述
版本管理,是软件的必需功能之一,对于软件的开发、部署和维护都至关重要。
关于Golang程序的版本管理,流行的做法是基于ldflags
,感觉比较hack。个人比较认可go embed
,代码可读性更强。
这里分别介绍一下两种做法,大家可以根据需要,灵活选择。
2. 基于ldflags
2.1. 基本原理
go build
,可以用-gcflags
给go编译器
传入参数,也就是传给go tool compile
的参数,因此可以用go tool compile --help
查看所有可用的参数。
go build
,用-ldflags
给go链接器
传入参数,实际是给go tool link
的参数,可以用go tool link --help
查看可用的参数。
常用-X
来指定版本号等编译时才决定的参数值。例如,代码中定义var buildVer string
,然后,在编译时用go build -ldflags "-X main.buildVer=1.0" ...
来赋值。
注意-X
只能给string类型变量
赋值。
% go tool link --help
...
-X definition
add string value definition of the form importpath.name=value
-s disable symbol table
-w disable DWARF generation
...
- -X importpath.name=value 编译期设置变量的值
- -s disable symbol table 禁用符号表
- -w disable DWARF generation 禁用调试信息
2.2. 实现
package main
import (
"fmt"
)
var (
VERSION string
)
func main() {
fmt.Printf("Version:[%s]\n", VERSION)
}
% go build -ldflags "-X main.VERSION=v1.0.0-alpha1" test_version.go
% ./test_version
Version:[v1.0.0-alpha1]
3. 基于go embed
3.1. 基本原理
Go编译的程序非常适合部署
,如果没有通过CGO引用其它的库的话,我们一般编译出来的可执行二进制文件都是单个的文件,非常适合复制和部署。在实际使用中,除了二进制文件,可能还需要一些配置文件
,或者静态文件
,比如html模板、静态的图片、CSS、javascript
等文件,如何这些文件也能打进到二进制文件中,那就太美妙,我们只需复制、按照单个的可执行文件即可。
一些开源的项目很久以前就开始做这方面的工作,比如gobuffalo/packr、markbates/pkger、rakyll/statik、knadh/stuffbin等等,但是不管怎么说这些都是第三方提供的功能,如果Go官方能内建支持就好了。2019末一个提案被提出issue#35950,期望Go官方编译器支持嵌入静态文件。后来Russ Cox专门写了一个设计文档Go command support for embedded static assets, 并最终实现了它。
Go 1.16中包含了go embed的功能,支持嵌入为string
, byte slice
和embed.FS
三种类型,这三种类型的别名(alias)和命名类型(如type S string)都不可以
。
直接看下官方给的例子:
package main
import (
"embed"
)
//go:embed hello.txt
var s string
//go:embed hello.txt
var b []byte
//go:embed hello.txt
var f embed.FS
func main() {
print(s)
print(string(b))
data, _ := f.ReadFile("hello.txt")
print(string(data))
}
Go embed 简明教程 (colobu.com)
Go 1.16 推出 Embedding Files - 小惡魔 - AppleBOY (wu-boy.com)
3.2. 实现
基于以上例子,我们很容易设计一套软件版本维护方案。
% tree .
.
├── dockerfile # 两阶段的docker image生成方法
├── go.mod
├── go.sum
├── main.go # go:embed 嵌入version
├── makefile # 执行go:build
├── release.sh # release,先执行bumpversion,再执行docker build
├── src
│ ├── controllers
│ │ ├── contract
│ │ ├── routes.go
│ │ └── v1
│ ├── libs
│ ├── models
│ ├── services
│ └── version.txt # 软件版本文件
├── .bumpversion.cfg # 软件版本文件
- 依赖
安装bumpversion,pip install bumpversion
- .bumpversion.cfg
[bumpversion]
current_version = 0.0.11
[bumpversion:file:src/version.txt]
search = {current_version}
replace = {new_version}
- src/version.txt
0.0.11
- main.go
...
//go:embed src/version.txt
var version string
...
- release.sh
#!/bin/bash
set -e
if [ $# -ne 1 ]
then
echo "Bumpversion options in [patch/minor/major]"
exit 1
fi
# pip install bumpversion
OLD_VERSION=$(cat src/version.txt)
bumpversion --allow-dirty --no-tag --no-commit "$1"
VERSION=$(cat src/version.txt)
echo "Bump Version: $OLD_VERSION -> $VERSION"
DOCKER_IMAGE_NAME=registry.cn-beijing.aliyuncs.com/shuzhang/master:$VERSION
docker build . \
-t "$DOCKER_IMAGE_NAME"
docker push "$DOCKER_IMAGE_NAME"
- dockerfile
FROM golang:1.20 AS builder
ENV GOPROXY=https://goproxy.cn,direct
#ENV GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
WORKDIR /code
COPY . /code
RUN make
##########################
FROM debian:10.9
LABEL maintainer="xxx@163.com"
# ENV GOMAXPROCS=8
ENV PATH=/app:$PATH
WORKDIR /app
COPY --from=builder /code/bin /app/
COPY conf /app/conf
ENTRYPOINT ["master"]
- makefile
BINARY_FILE=bin/master
all: test build
compile:
echo "Compiling for every OS and Platform"
GOOS=linux GOARCH=amd64 go build -o ${BINARY_FILE}-linux-amd64 main.go
# GOOS=freebsd GOARCH=amd64 go build -o ${BINARY_FILE}-freebsd-amd64 main.go
# GOOS=windows GOARCH=amd64 go build -o ${BINARY_FILE}-windows-amd64 main.go
# GOOS=darwin GOARCH=amd64 go build -o ${BINARY_FILE}-darwin-amd64 main.go
build:
go build -o ${BINARY_FILE} main.go
test:
go test -v ./...
run:
go run main.go
install: build
cp ./${BINARY_FILE} ${GOPATH}/bin
deps:
go mod download
clean:
go clean
rm ${BINARY_FILE}*
3.3. 使用方法
- 开发或debug,直接
make
或make compile
即可 - 发布的话,直接
bash release.sh
。docker image的版本号和golang二进制程序的版本一致,赞!
4. 综上
go:embed
,是golang1.16引入的新功能,它确保部署简单和程序的完整性。
其应用场景很多,例如配置文件
、网站的静态文件
、golang模板文件
、数据库迁移
等。
大家可以根据需求使用。