第六章:开发技能
1 Go 命令:go test 工具详解
接下来几篇文章,我将介绍 下 Golag 中有关测试相关的一些文章。
在学习如何编写测试代码之前,需要先了解一下Go 提供的测试工具 :go test
go test 本身可以携带很多的参数,熟悉这些参数,可以让我们的测试过程更加方便。
下面就根据场景来解释一下常用的几个参数:
(由于下一节才会讲到如何编写测试代码,所以请好结合下一篇文章进行学习)
1、运行整个项目的测试文件
$ go test
PASS
ok _/home/wangbm/golang/math 0.003s
2、只运行某个测试文件( math_test.go, math.go 是一对,缺一不可,前后顺序可对调)
$ go test math_test.go math.go
ok command-line-arguments 0.002s
3、加 -v 查看详细的结果
$ go test math_test.go math.go
=== RUN TestAdd
TestAdd: main_test.go:22: the result is ok
TestAdd: main_test.go:22: the result is ok
TestAdd: main_test.go:22: the result is ok
TestAdd: main_test.go:22: the result is ok
TestAdd: main_test.go:22: the result is ok
--- PASS: TestAdd (0.00s)
PASS
ok command-line-arguments 0.003s
4、只测试某个函数,-run 支持正则,如下例子中 TestAdd,如果还有一个测试函数为 TestAdd02,那么它也会被运行。
$ go test -v -run="TestAdd"
=== RUN TestAdd
TestAdd: math_test.go:22: the result is ok
TestAdd: math_test.go:22: the result is ok
TestAdd: math_test.go:22: the result is ok
TestAdd: math_test.go:22: the result is ok
TestAdd: math_test.go:22: the result is ok
--- PASS: TestAdd (0.00s)
PASS
ok _/home/wangbm/golang/math 0.003s
5、生成 test 的二进制文件:加 -c 参数
$ go test -v -run="TestAdd" -c
$
$ ls -l
total 3208
-rw-r--r-- 1 root root 95 May 25 20:56 math.go
-rwxr-xr-x 1 root root 3272760 May 25 21:00 math.test
-rw-r--r-- 1 root root 525 May 25 20:56 math_test.go
6、执行这个 test 测试文件:加 -o 参数
$ go test -v -o math.test
=== RUN TestAdd
TestAdd: math_test.go:22: the result is ok
TestAdd: math_test.go:22: the result is ok
TestAdd: math_test.go:22: the result is ok
TestAdd: math_test.go:22: the result is ok
TestAdd: math_test.go:22: the result is ok
--- PASS: TestAdd (0.00s)
=== RUN TestAum
TestAum: math_test.go:30: 6
--- PASS: TestAum (0.00s)
PASS
ok _/home/wangbm/golang/math 0.002s
7、只测试安装/重新安装 依赖包,而不运行代码:加 -i 参数
# 这里没有输出
$ go test -i
2 单元测试:如何进行单元测试?
在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。
程序单元是应用的最小可测试部件,一般来说都是对某一个函数方法进行测试,以尽可能的保证没有问题或者问题可被我们预知。为了达到这个目的,我们可以使用各种手段、逻辑,模拟不同的场景进行测试。
那么我们如何在写 Golang 代码时,进行单元测试呢?
由于实在是太简单了,我这里直接上例子吧
2.1 单元测试
准备两个 Go 文件
math.go
package math
func Add(x,y int) int {
return x+y
}
math_test.go
package math
import "testing"
func TestAdd(t *testing.T) {
t.Log(Add(1, 2))
}
然后使用 go test 工具去执行
$ go test .
ok _/home/wangbm/golang/math 0.003s
从上面这个例子中,可以总结中几点 Go 语言测试框架要遵循的规则
- 单元测试代码的 go文件必须以_test.go结尾,而前面最好是被测试的文件名(不过并不是强制的),比如要测试 math.go 测试文件名就为 math_test.go
- 单元测试的函数名必须以Test开头,后面直接跟要测试的函数名,比如要测试 Add函数,单元测试的函数名就得是 TestAdd
- 单元测试的函数必须接收一个指向testing.T类型的指针,并且不能返回任何值。
2.2 表组测试
Add(1, 2) 是一次单元测试的场景,而 Add(2, 4) ,Add(3, 6) 又是另外两种单元测试的场景。
对于多种输入场景的测试,我们可以同时放在 TestAdd 里进行测试,这种测试方法就是表组测试。
修改 math_test.go 如下
package math
import "testing"
func TestAdd(t *testing.T) {
sum:=Add(1,2)
if sum == 3 {
t.Log("the result is ok")
} else {
t.Fatal("the result is wrong")
}
sum=Add(2,4)
if sum == 6 {
t.Log("the result is ok")
} else {
t.Fatal("the result is wrong")
}
}
执行 go test
$ go test . -v
=== RUN TestAdd
TestAdd: math_test.go:8: the result is ok
TestAdd: math_test.go:15: the result is ok
--- PASS: TestAdd (0.00s)
PASS
ok _/home/wangbm/golang/math 0.003s
稍微如果输入的场景实在太多(比如下面用的五组输入),用上面的方法,可能需要写很多重复的代码,这时候可以利用 表格测试法
package math
import "testing"
type TestTable struct {
xarg int
yarg int
}
func TestAdd(t *testing.T){
tables := []TestTable{
{1,2},
{2,4},
{4,8},
{5,10},
{6,12},
}
for _, table := range tables{
result := Add(table.xarg, table.yarg)
if result == (table.xarg + table.yarg){
t.Log("the result is ok")
} else {
t.Fatal("the result is wrong")
}
}
}
执行 go test
$ go test . -v
=== RUN TestAdd
TestAdd: math_test.go:22: the result is ok
TestAdd: math_test.go:22: the result is ok
TestAdd: math_test.go:22: the result is ok
TestAdd: math_test.go:22: the result is ok
TestAdd: math_test.go:22: the result is ok
--- PASS: TestAdd (0.00s)
PASS
ok _/home/wangbm/golang/math 0.002s
3 调试技巧:使用 GDB 调试 Go 程序
做为新手,熟练掌握一个好的调试工具,对于我们学习语言或者排查问题的时候,非常有帮助。
你如果使用 VS Code 或者 Goland ,可以直接上手,我就不再写这方面的文章了。
其实相比有用户界面的 IDE 调试工具,我更喜欢简单直接的命令行调试,原因有三点:
- 速度快,个人感觉在 Windows 下速度巨慢
- 依赖少,在 Linux 服务器上 也能轻松调试
- 指令简单,我习惯只使用快捷键就能操作
如果你有和我一样的感受和习惯,可以看下今天的文章,介绍的是 GDB 调试工具。
3.1 下载安装 Go
在 Linux 上进行调试,那咱所以得先安装 Go ,由于第一节里只讲了 Windows 的下载安装,并没有讲到在 Linux 上如何安装。所以这里要先讲一下,已经安装过了可以直接跳过。
首先在 go 下载页面上(https://golang.org/dl/),查看并复制源码包的的下载地址
登陆 linux 机器 ,使用 wget 下载
$ wget https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz
将下载的源码包解压到 /usr/local 目录下,并设置环境变量
[root@localhost ~]# tar -C /usr/local -xzf go1.14.2.linux-amd64.tar.gz
[root@localhost ~]#
[root@localhost ~]# export PATH=$PATH:/usr/local/go/bin
[root@localhost ~]# which go
/usr/local/go/bin/go
[root@localhost ~]#
[root@localhost ~]# go version
go version go1.14.2 linux/amd64
[root@localhost ~]#
3.2 开始进行调试
调试使用的是 GDB (好像要求版本 7.1 + ),使用前,请先确保你的机器上已经安装 GDB
[root@localhost code]# which gdb
/usr/bin/gdb
准备就绪后,先在目录下写一个测试文件
package main
import "fmt"
func main(){
msg := "hello, world"
fmt.Println(msg)
}
然后执行 如下命令进行编译,里面有好多个参数,有疑问的可以自行搜索引擎
# 关闭内联优化,方便调试
$ go build -gcflags "-N -l" demo.go
# 发布版本删除调试符号
go build -ldflags “-s -w”
最后使用 GDB 命令进入调试界面
# 如果你喜欢这种界面的话,用这条命令
$ gdb -tui demo
# 如果你跟我一样不喜欢不习惯用界面,就使用这个命令
$ gdb demo
完整操作如下:
进入 GDB 调试界面后,并不是立即可用,你先需要回车,然后再你敲入几行命令,调试窗口就会出现代码。
(gdb) b main.main # 在 main 包里的 main 函数 加断点
Breakpoint 1 at 0x4915c0: file /home/wangbm/code/demo.go, line 5.
(gdb) run # 执行进程
Starting program: /home/wangbm/code/demo
Breakpoint 1, main.main () at /home/wangbm/code/demo.go:5
(gdb)
3.3 详解调试指令
要熟练使用 GDB ,你得熟悉的掌握它的指令,这里列举一下
- r:run,执行程序
- n:next,下一步,不进入函数
- s:step,下一步,会进入函数
- b:breakponit,设置断点
- l:list,查看源码
- c:continue,继续执行到下一断点
- bt:backtrace,查看当前调用栈
- p:print,打印查看变量
- q:quit,退出 GDB
- whatis:查看对象类型
- info breakpoints:查看所有的断点
- info locals:查看局部变量
- info args:查看函数的参数值及要返回的变量值
- info frame:堆栈帧信息
- info goroutines:查看 goroutines 信息。在使用前 ,需要注意先执行 source /usr/local/go/src/runtime/runtime-gdb.py
- goroutine 1 bt:查看指定序号的 goroutine 调用堆栈
- 回车:重复执行上一次操作
其中有几个指令的使用比较灵活
比如 l - list,查看代码
# 查看指定行数上下5行
(gdb) l 8
# 查看指定范围的行数
(gdb) l 5:8
# 查看指定文件的行数上下5行
l demo.go:8
# 可以查看函数,记得加包名
l main.main
把上面的 l 换成 b ,大多数也同样适用
# 在指定行打断点
(gdb) b 8
# 在指定指定文件的行打断点
b demo.go:8
# 在指定函数打断点,记得加包名
b main.main
还有 p - print,打印变量
# 查看变量
(gdb) p var
# 查看对象长度或容量
(gdb) p $len(var)
(gdb) p $cap(var)
# 查看对象的动态类型
(gdb) p $dtype(var)
(gdb) iface var
# 举例如下
(gdb) p i
$4 = {str = "cbb"}
(gdb) whatis i
type = regexp.input
(gdb) p $dtype(i)
$26 = (struct regexp.inputBytes *) 0xf8400b4930
(gdb) iface i
regexp.input: struct regexp.inputBytes *
以上就是关于 GDB 的使用方法,非常简单,可以自己手动敲下体验一下。
4 Go 命令: Go 命令指南
4.1 基本命令
查看版本
$ go version
go version go1.14 darwin/amd64
查看环境变量
$ go env
仅截取部分内容
设置环境变量
$ go env -w GOPATH=/usr/loca
4.2 执行 Go 程序
当前热门的编程语言 Python ,可以不用编译成 二进制文件,就可以直接运行。
但 Go 语言程序的执行,必须得先编译再执行。通常来说有如下两种方法
- 先使用 go build 编译成二进制文件,再执行这个二进制文件
- 使用 go run “直接”运行,这个命令还是会去编译,但是不会在当前目录下生成二进制文件,而是编译成临时文件后直接运行。
4.3 编译文件
将 .go 文件编译成可执行文件,可以使用 go build
如下图所示,helloworld 文件夹下,包含两个 .go 文件,它们都归属于同一个包。
当使用 go build 可指定包里所有的文件,就你下面这样,默认会以第一个文件(main.go)名生成可执行文件(main)。
当然,你也可以不指定,此时生成的可执行文件是以 文件夹名命名
当然你也可以手动指定这个可执行文件名
以上是编译单个文件,当然也可以编译多个文件
4.4 清除编译文件
使用 go install 或 go install 有可能会生成很多的文件,如可执行文件,归档文件等,它们的后缀名非常多,有 .exe, .a, .test,.o,.so,.5 ,.6,.8,如果要手动一个一个去清理他们,可以说是相当麻烦的,这里你可以通过使用 go clean 一键清理。
实际开发中go clean命令使用的可能不是很多,一般都是利用go clean命令清除编译文件,然后再将源码递交到 github 上,方便对于源码的管理。
go clean 有不少的参数:
- -i:清除关联的安装的包和可运行文件,也就是通过go install安装的文件;
- -n: 把需要执行的清除命令打印出来,但是不执行,这样就可以很容易的知道底层是如何运行的;
- -r: 循环的清除在 import 中引入的包;
- -x: 打印出来执行的详细命令,其实就是 -n 打印的执行版本;
- -cache: 删除所有go build命令的缓存
- -testcache: 删除当前包所有的测试结果
4.5 下载代码包
在 Golang 中,除了可以从官方网站(golang.org)下载包之外,还可以从一些代码仓库中下载,诸如 github.com,bitbucket.org 这样的代码托管网站。
go get 这条命令,你以后会最经常用到,它可以借助代码管理工具通过远程拉取或更新代码包及其依赖包,并自动完成编译和安装。整个过程就像安装一个 App 一样简单。
这个命令可以动态获取远程代码包,目前支持的有 BitBucket、GitHub、Google Code 和 Launchpad。在使用 go get 命令前,需要安装与远程包匹配的代码管理工具,如 Git、SVN等。
go get 会根据域名的不同,使用不同的工具去拉取代码包,具体可参考下图
下载和安装,原本是两个动作,但使用 go get 后,它默认会将下载(源码包)和安装(go install)合并起来,当然你也可以通过参数指定将拆散它们。
在终端执行 go help get,会弹出 go get 的帮助文档,我这里汉化总结一下,来帮助大家学习。
go get [-d] [-f] [-t] [-u] [-v] [-fix] [-insecure] [build flags] [packages]
其中几个参数详解如下
- -u:用于下载指定的路径包及其依赖包,默认情况下,不会下载本地已经存在的,只会下载本地不存在的代码包。就是口中常说的更新包 比如:go get -u github.com/jinzhu/gorm。会把最新的 gorm 包下载到你本地
- -d:让命令程序只执行下载动作,而不执行安装动作。
- -t让命令程序同时下载并安装指定的代码包中的测试源码文件中依赖的代码包
- -fix命令程序在下载代码包后先执行修正动作,而后再进行编译和安装。比如,我的代码是用1.7 开发的,现在go 版本已经是1.13 了,有些包已经发生了变化,那么我们在使用go get命令的时候可以加入-fix标记。这个标记的作用是在检出代码包之后,先对该代码包中不符合Go语言1.7版本的语言规范的语法进行修正,然后再下载它的依赖包,最后再对它们进行编译和安装。
- -v打印出那些下载的代码包的名字
- -f仅在使用-u标记时才有效。该标记会让命令程序忽略掉对已下载代码包的导入路径的检查。如果下载并安装的代码包所属的项目是你从别人那里Fork过来的,那么这样做就尤为重要了
- -x打印出整个过程使用了哪些命令
- -insecure 允许命令程序使用非安全的scheme(如HTTP)去下载指定的代码包。如果你用的代码仓库(如公司内部的Gitlab)没有HTTPS支持,可以添加此标记。请在确定安全的情况下使用它。(记得 使用工具 git 时,有个版本就是 http 升级为了https)
参数有点多,咱一个一个来。
指定 -d,只下载源码包而不进行安装
由于此时,我们已经下载了 logging 包,当你再次执行 go get 时,并不会重复下载,只有当你指定 -u 时,不管你需不需要更新,都会触发重新下载强制更新。
如果你想看,下载这个过程用到了哪几个命令,可以指定 -x 参数
最后,你可能想说,为什么 golang 里的包含这么长,好难记呀,其实这个路径是有讲究的
这样不同的人开发的包即使使用同一个名,也不会冲突了。
下载的包,可能有不同的版本,如何指定版本下载呢?
# 拉取最新
go get github.com/foo
# 最新的次要版本或者修订版本(x.y.z, z是修订版本号, y是次要版本号)
go get -u github.com/foo
# 升级到最新的修订版本
go get -u=patch github.com/foo
# 指定版本,若存在tag,则代行使用
go get github.com/foo@v1.2.3
# 指定分支
go get github.com/foo@master
# 指定git提交的hash值
go get github.com/foo@e3702bed2
4.6 安装代码包
go install 这个命令,如果你安装的是一个可执行文件(包名是 main),它会生成可执行文件到 bin 目录下。这点和 go build 很相似,不同的是,go build 编译生成的可执行文件放在当前目录,而 go install 会将可执行文件统一放至 $GOPATH/bin 目录下。
如果你安装的是一个库,它会将这个库安装到 pkg 目录下,生成 .a 为后缀的文件。
4.7 格式化 go 文件
Go语言的开发团队制定了统一的官方代码风格,并且推出了 gofmt 工具(gofmt 或 go fmt)来帮助开发者格式化他们的代码到统一的风格。
gofmt 是一个 cli 程序,会优先读取标准输入,如果传入了文件路径的话,会格式化这个文件,如果传入一个目录,会格式化目录中所有 .go 文件,如果不传参数,会格式化当前目录下的所有 .go 文件。
5 性能分析: pprof 工具的简单使用
pprof 是 Go 程序性能分析常用的工具,关于 pprof 有很多的包,它们分别是:
- runtime/pprof:Go 的内置库,比较基础,不常用
- pkg/profile:对 runtime/pprof 进行简化,只需要一行代码即可,等程序运行结束后才能分析
- net/http/pprof:最好用的库,可以暴露 http 服务实时获取分析
本文,我仅使用 pkg/profile 进行演示,后面有机会再来补充 net/http/pprof 的,关于这些包的详细使用,可以查看你不知道的 Go 之 pprof,写得非常详细
5.1 准备工作
1 准备依赖
下载 pprof
go get github.com/pkg/profile
安装 graphviz ,后面可视化分析要用
# centos
yum install -y graphviz
# ubuntu
apt-get install graphviz
# mac
brew install graphviz
2 准备代码
先创建临时项目
go mod init iswbm.com/demo-pprof
然后准备两个文件:
- pprof-cpu.go (待补充)
- pprof-mem.go
package main
import (
"github.com/pkg/profile"
"math/rand"
)
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func randomString(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
func concat(n int) string {
s := ""
for i := 0; i < n; i++ {
s += randomString(n)
}
return s
}
func main() {
defer profile.Start(profile.MemProfile, profile.MemProfileRate(1)).Stop()
concat(100)
}
5.2 开始分析
以分析内存为例,直接运行该 pprof-mem.go,会生成两个 mem.pprof 文件
$ go run pprof-cpu.go
2022/01/19 06:53:02 profile: memory profiling enabled (rate 1), /tmp/profile645664769/mem.pprof
2022/01/19 06:53:02 profile: memory profiling disabled, /tmp/profile645664769/mem.pprof
1 命令行界面分析
指定这两个文件运行分析
$ go tool pprof /tmp/profile645664769/mem.pprof
再敲入 top,就可以看到分析的结果,可以看到 98% 的内存都是由 main.concat 函数产生的。
2 可视化界面分析
然后指定这两个文件运行分析
$ go tool pprof -http=0.0.0.0:9999 /tmp/profile645664769/mem.pprof
Serving web UI on http://0.0.0.0:9999
http://0.0.0.0:9999
在浏览器访问该地址
- 本地电脑,就访问:http://localhost:9999
- 服务器,就访问:http://host-ip:9999
然后就可以看到像下面这样的可视化界面,一下子就可以看出 main.concat 的区域最大
6 使用 -ldflags 实现动态信息注入
在查看一些工具的版本时,我们时常能看到版本信息非常多,连 git 的 commit id 都有
~ ➤ docker version
Client:
Cloud integration: v1.0.22
Version: 20.10.11
API version: 1.41
Go version: go1.16.10
Git commit: dea9396
Built: Thu Nov 18 00:36:09 2021
OS/Arch: darwin/arm64
Context: default
Experimental: true
最值得关注的是很多信息在每次构建时都会发生变化,如果这些信息是写死在代码中的变量里的,那意味着每次构建都要修改代码,一般情况下都不允许随意代码,构建时的代码应与 git 版本分支上保持一致。
6.1 实现动态信息注入
那 Go 程序又是如何实现这种个性化信息的动态注入呢?
在 go build 命令里有一个 -ldflags 参数,该参数可以接收 -X importpath.name=value 形式的值,该值就是实现信息动态注入的核心入口。
以下面一段例子来演示
- 先定义 version,buildTime,osArch 三个变量
- 然后将这三个变量的值打印出来
package main
import "fmt"
var (
version string
buildTime string
osArch string
)
func main() {
fmt.Printf("Version: %s\nBuilt: %s\nOS/Arch: %s\n", version, buildTime, osArch)
}
由于我们只是声明了变量,但没有对其赋值,因为三个变量的值都是零值,也就是空字符串。
~ ➤ go run main.go
Version:
Built:
OS/Arch:
此时,我给 run 或者 build 加上如下的 -ldflags 参数,Go 的编译器就能接收到并赋值给我们指定的变量
~ ➤ go run -ldflags "-X 'main.version=0.1' -X 'main.buildTime=2022-03-25' -X 'main.osArch=darwin/amd64'" main.go
Version: 0.1
Built: 2022-03-25
OS/Arch: darwin/amd64
我们只要编译一次,后续执行二进制文件就不用再指定这么长的一长参数了
~ ➤ go build -ldflags "-X 'main.version=0.1' -X 'main.buildTime=2022-03-25' -X 'main.osArch=darwin/amd64'" main.go
~ ➤
~ ➤ ./main
Version: 0.1
Built: 2022-03-25
OS/Arch: darwin/amd64
6.2 实际开发项目
上面为了方便学习,主程序直接将版本信息直接打印出来了,实际上应该指定 version 参数再打印。
有了前面的基础知识,下边就演示一下正常开发中如何来注入版本信息
首先,初始化项目
go mod init github.com/iswbm/demo
然后创建 main.go
package main
import (
"fmt"
"os"
"github.com/iswbm/demo/utils"
)
func main() {
args := os.Args
if len(args) >= 2 && args[1] == "version" {
v := utils.GetVersion()
fmt.Printf("Version: %s\nGitBranch: %s\nCommitId: %s\nBuild Date: %s\nGo Version: %s\nOS/Arch: %s\n", v.Version, v.GitBranch, v.GitCommit, v.BuildDate, v.GoVersion, v.Platform)
} else {
fmt.Printf("Version(hard code): %s\n", "0.1")
}
}
再创建 utils/version.go
package utils
import (
"fmt"
"runtime"
)
var (
version string
gitBranch string
gitTag string
gitCommit string
gitTreeState string
buildDate string
)
type Info struct {
Version string `json:"version"`
GitBranch string `json:"gitBranch"`
GitTag string `json:"gitTag"`
GitCommit string `json:"gitCommit"`
GitTreeState string `json:"gitTreeState"`
BuildDate string `json:"buildDate"`
GoVersion string `json:"goVersion"`
Compiler string `json:"compiler"`
Platform string `json:"platform"`
}
func (info Info) String() string {
return info.GitCommit
}
func GetVersion() Info {
return Info{
Version: version,
GitBranch: gitBranch,
GitTag: gitTag,
GitCommit: gitCommit,
GitTreeState: gitTreeState,
BuildDate: buildDate,
GoVersion: runtime.Version(),
Compiler: runtime.Compiler,
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
}
}
最后,使用如下命令去编译
go build -ldflags "-X 'github.com/iswbm/demo/utils.version=0.1' -X 'github.com/iswbm/demo/utils.gitBranch=test' -X 'github.com/iswbm/demo/utils.gitTag=test' -X 'github.com/iswbm/demo/utils.gitCommit=test' -X 'github.com/iswbm/demo/utils.buildDate=2022-03-25' -X 'github.com/iswbm/demo/utils.osArch=darwin/amd64'"
编译好后,可以运行一下看效果
6.3 使用 Makekfile
上面在编译的时候,需要指定一大串的参数,相信你已经崩溃了吧?
更合理的做法,是将这些参数 Makefile 来管理维护,在 Makefile 中可以用 shell 命令去获取一些 git 的信息,比如下面这样子
# gitTag
gitTag=$(git log --pretty=format:'%h' -n 1)
# commitID
gitCommit=$(git rev-parse --short HEAD)
# gitBranch
gitBranch=$(git rev-parse --abbrev-ref HEAD)
我先在该项目下初始化 Git 仓库
# 初始化
git init .
# 添加所有文件到暂存区
git add -A
# 提交 commit
git commit -m "init repo"
然后编写出如下的 Makefile 到项目的根目录
BINARY="demo"
VERSION=0.0.1
BUILD=`date +%F`
SHELL := /bin/bash
versionDir="github.com/iswbm/demo/utils"
gitTag=$(shell git log --pretty=format:'%h' -n 1)
gitBranch=$(shell git rev-parse --abbrev-ref HEAD)
buildDate=$(shell TZ=Asia/Shanghai date +%FT%T%z)
gitCommit=$(shell git rev-parse --short HEAD)
ldflags="-s -w -X ${versionDir}.version=${VERSION} -X ${versionDir}.gitBranch=${gitBranch} -X '${versionDir}.gitTag=${gitTag}' -X '${versionDir}.gitCommit=${gitCommit}' -X '${versionDir}.buildDate=${buildDate}'"
default:
@echo "build the ${BINARY}"
@GOOS=linux GOARCH=amd64 go build -ldflags ${ldflags} -o build/${BINARY}.linux -tags=jsoniter
@go build -ldflags ${ldflags} -o build/${BINARY}.mac -tags=jsoniter
@echo "build done."
接下来就可以直接使用 make 命令,编译出 mac 和 linux 两个版本的二进制执行文件
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了