build
介绍
- Go语言中使用 go build 命令用于编译代码。在包的编译过程中,若有必要,会同时编译与之相关联的包。
- Go语言的编译速度非常快。Go 1.9 版本后默认利用Go语言的并发特性进行函数粒度的并发编译。
- go build 有很多种编译方法,如无参数编译、文件列表编译、指定包编译等。
- 还可以附加参数,按使用频率排列,可以显示更多的编译信息和更多的操作:
-v | 编译时显示包名 |
---|---|
-o | 编译时指定可执行文件名 |
-p n | 开启并发编译,默认情况下n值为 CPU 逻辑核数 |
-a | 强制重新构建 |
-n | 打印编译时会用到的所有命令,但不编译 |
-x | 打印编译时会用到的所有命令,并编译 |
-race | 开启竞态检测,竞态检测 |
无参数编译
介绍
- 该模式会自动获得需要编译的包进行编译。也是我们最常用的编译方式。
示例
目录关系:
main.go:.
└── src
└── chapter11
└── gobuild
├── lib.go
└── main.go
lib.go:package main
import (
"fmt"
)
func main() {
// 同包的函数
pkgFunc()
fmt.Println("hello world")
}
编译package main
import "fmt"
func pkgFunc() {
fmt.Println("call pkgFunc")
}
$ cd src/chapter11/gobuild/
$ go build
$ ls
gobuild lib.go main.go
$ ./gobuild
call pkgFunc
hello world
文件列表编译
介绍
- 编译同目录的多个源码文件时,可以在 go build 的后面提供多个文件名。该方式更适合使用Go语言编写的只有少量文件的工具。
- 编译后的可执行文件名默认为文件列表中第一个源码文件名。如果需要指定输出可执行文件名,使用
-o
参数。 文件列表中的每个文件必须是同一个包的 Go 源码。也就是说,不能像 C++ 一样将所有工程的 Go 源码使用文件列表方式进行编译。编译复杂工程时需要用“指定包编译”的方式。
go build file1.go file2.go……
示例
目录结构同无参数编译。
$ go build -o myexec main.go lib.go
$ ls
lib.go main.go myexec
$ ./myexec
call pkgFunc
hello world
指定包编译
介绍
设置 GOPATH 后,可以直接根据包名进行编译,即便包内文件被增(加)删(除)也不影响编译指令。
示例
目录关系:
.
└── src
└── chapter11
└──goinstall
├── main.go
└── mypkg
└── mypkg.go
main.go:
package main
import (
"chapter11/goinstall/mypkg"
"fmt"
)
func main() {
mypkg.CustomPkgFunc()
fmt.Println("hello world")
}
mypkg.go:
package mypkg
import "fmt"
func CustomPkgFunc() {
fmt.Println("call CustomPkgFunc")
}
编译
$ export GOPATH=/home/davy/golangbook/code
$ go build -o main chapter11/goinstall
$ ./goinstall
call CustomPkgFunc
hello world
run
介绍
- 该命令会编译源码,并且直接执行源码的 main() 函数。
- 该命令不会在当前目录下生成任何文件,可执行文件被放在临时文件中被执行,工作目录被设置为当前目录。
- 在该命令后部可以添加参数,这部分参数会作为代码可以接受的命令行输入提供给程序。
示例
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("args:", os.Args)
}
$ go run main.go --filename xxx.go
args: [/tmp/go-build006874658/command-line-arguments/_obj/exe/main--filename xxx.go]
install
介绍
- 同 build 命令类似,附加参数绝大多数都可以与 go build 通用,无法使用
-o
附加参数进行自定义。 - 该命令是建立在 GOPATH 上的,无法在独立的目录里使用 go install。
- 这个命令在内部实际上分成了两步操作:
- 第一步是生产编译的中间文件放在 GOPATH 的 pkg 目录下(.a包)。
- 第二步会把编译好的执行文件放到 GOPATH 的 bin 目录。可执行文件的名称来自于编译时的包名。
示例
编译完成后的目录结构如下:$ export GOPATH=/home/davy/golangbook/code
$ go install chapter11/goinstall
.
├── bin
│ └── goinstall
├── pkg
│ └── linux_amd64
│ └── chapter11
│ └── goinstall
│ └── mypkg.a
└── src
└── chapter11
├── gobuild
│ ├── lib.go
│ └── main.go
└── goinstall
├── main.go
└── mypkg
└── mypkg.go
clean
介绍
- 命令就像 Java 中的
maven clean
命令一样,会清除掉编译过程中产生的一些文件。移除当前源码包和关联源码包里面编译生成的文件,这些文件包括以下几种:
- 执行
go build
命令时在当前目录下生成的与包名或者 Go 源码文件同名的可执行文件。在 Windows 下,则是与包名或者 Go 源码文件同名且带有“.exe”后缀的文件。 - 执行
go test
命令并加入-c
标记时在当前目录下生成的以包名加“.test”后缀为名的文件。在 Windows 下,则是以包名加“.test.exe”后缀的文件。 - 执行
go install
命令安装当前代码包时产生的结果文件。如果当前代码包中只包含库源码文件,则结果文件指的就是在工作区 pkg 目录下相应的归档文件。如果当前代码包中只包含一个命令源码文件,则结果文件指的就是在工作区 bin 目录下的可执行文件。 - 在编译 Go 或 C 源码文件时遗留在相应目录中的文件或目录 。包括:“_obj”和“_test”目录,名称为“_testmain.go”、“test.out”、“build.out”或“a.out”的文件,名称以“.5”、“.6”、“.8”、“.a”、“.o”或“.so”为后缀的文件。这些目录和文件是在执行
go build
命令时生成在临时目录中的。
- 实际开发中该命令使用的可能不是很多,一般都使用命令清除编译文件,然后再将源码递交到 github 上。
- 支持参数:
| -i | 清除关联的安装的包和可运行文件,也就是通过
go install
安装的文件 | | —- | —- | | -n | 把需要执行的清除命令打印出来,但不真正执行 | | -x | 把需要执行的清除命令打印出来,并执行 | | -r | 循环的清除在 import 中引入的包 | | -cache | 删除所有go build
命令的缓存 | | -testcache | 删除当前包所有的测试结果 |
示例
go clean -x
rm -f code code.exe code.test code.test.exe main main.exe
fmt
介绍
- Go语言的开发团队制定了统一的官方代码风格,并且推出了 gofmt 工具来帮助开发者格式化他们的代码到统一的风格。
- gofmt 是一个 cli 程序,会优先读取标准输入,如果传入了文件路径的话,会格式化这个文件,如果传入一个目录,会格式化目录中所有 .go 文件,如果不传参数,会格式化当前目录下的所有 .go 文件。
- gofmt 使用 tab 来表示缩进,并且对行宽度无限制,如果手动对代码进行了换行,gofmt 不会强制把代码格式化回一行。
- 参数如下:
| 标记名称 | 标记描述 |
| —- | —- |
| -l | 仅把那些不符合格式化规范的、需要被命令程序改写的源码文件的绝对路径打印到标准输出。而不是把改写后的全部内容都打印到标准输出。 |
| -w | 把改写后的内容直接写入到文件中,而不是作为结果打印到标准输出。 |
| -r | 添加形如“a[b:len(a)] -> a[b:]”的重写规则。如果我们需要自定义某些额外的格式化规则,就需要用到它。 |
| -s | 简化文件中的代码。在默认情况下,该值为false |
| -d | 只把改写前后内容的对比信息作为结果打印到标准输出。而不是把改写后的全部内容都打印到标准输出。
命令程序将使用 diff 命令对内容进行比对。在 Windows 操作系统下可能没有 diff 命令,需要另行安装。 | | -e | 打印所有的语法错误到标准输出。如果不使用此标记,则只会打印每行的第 1 个错误且只打印前 10 个错误。 | | -comments | 是否保留源码文件中的注释。在默认情况下,此标记会被隐式的使用,并且值为 true。 | | -tabwidth | 此标记用于设置代码中缩进所使用的空格数量,默认值为 8。要使此标记生效,需要使用“-tabs”标记并把值设置为 false。 | | -tabs | 是否使用 tab(’\t’)来代替空格表示缩进。在默认情况下,此标记会被隐式的使用,并且值为 true。 | | -cpuprofile | 是否开启 CPU 使用情况记录,并将记录内容保存在此标记值所指的文件中。 |
-s 简化
去除数组、切片、Map 初始化时不必要的类型声明
[]T{T{}, T{}}
[]T{{}, {}}
去除数组切片操作时不必要的索引指定
s[a:len(s)]
s[a:]
去除循环时非必要的变量赋值
for x, _ = range v {...}
for x = range v {...}
for _ = range v {...}
for range v {...}
-f 重写
示例规则:
gofmt -w -r "a + b -> b + a" main.go
func main() {
a := 1
b := 2
c := a + b
fmt.Println(c)
}
func main() {
a := 1
b := 2
c := b + a
fmt.Println(c)
}
go fmt
- Go语言中还有一个go fmt 命令,go fmt 命令是 gofmt 的简单封装。
- go fmt 在调用 gofmt 时添加了
-l -w
参数,相当于执行了gofmt -l -w
。 - 该命令本身只有两个可选参数。如果需要更细化的配置,需要直接执行 gofmt 命令。
-n
仅打印出内部要执行的go fmt
的命令;-x
命令既打印出go fmt
命令又执行它。
get
⚠️:该命令已过时,推荐使用 go module (version>= 1.13)来处理依赖。见:go module
介绍
- go get 命令可以借助代码管理工具通过远程拉取或更新代码包及其依赖包,并自动完成编译和安装。
这个命令可以动态获取远程代码包,在使用 go get 命令前,需要安装与远程包匹配的代码管理工具,下载源码包的 go 工具会自动根据不同的域名(也支持自定义域名)调用不同的源码工具(并同时把这些命令加入你的 PATH 中): | BitBucket | Mercurial Git | | —- | —- | | GitHub | Git | | Google Code Project Hosting | Git, Mercurial, Subversion | | Launchpad | Bazaar |
该命令参数中需要提供一个包名。运行后需要两步:第一步是下载源码包,第二步是执行 go install。
- 参数介绍: | -v | 显示操作流程的日志及信息,方便检查错误 | | —- | —- | | -d | 只下载不安装 | | -u | 强制使用网络去下载丢失的包,但不会更新已经存在的包 | | -f | 只有在你包含了 -u 参数的时候才有效,不让 -u 去验证 import 中的每一个都已经获取了,这对于本地 fork 的包特别有用 | | -t | 同时也下载需要为运行测试所需要的包 | | -fix | 在获取源码之后先运行 fix | | -insecure | 允许使用不安全的 HTTP 方式进行下载操作 |
示例
使用前,请确保 GOPATH 已经设置。Go 1.8 版本之后,GOPATH 默认在用户目录的 go 文件夹下。
go get github.com/davyxu/tabtoy
pprof
介绍
- 工具链中的 go pprof 可帮助开发者快速分析及定位各种性能问题,如 CPU 消耗、内存分配及阻塞分析。
- 性能分析首先需要使用 runtime.pprof 包嵌入到待分析程序的入口和结束处。runtime.pprof 包在运行时对程序进行每秒 100 次的采样,最少采样 1 秒。然后将生成的数据输出,让开发者写入文件或者其他媒介上进行分析。
- go pprof 工具链配合 Graphviz 图形化工具可以将 runtime.pprof 包生成的数据转换为 PDF 格式,以图片的方式展示程序的性能分析结果。
Graphviz
- Graphviz 是一套通过文本描述的方法生成图形的工具包。描述文本的语言叫做 DOT。
- 在 www.graphviz.org 网站可以获取到最新的 Graphviz 各平台的安装包。
- CentOS 下,可以使用 yum 指令直接安装: yum install graphiviz
profile
- runtime.pprof 提供基础的运行时分析的驱动,但是这套接口使用起来还不是太方便,例如:
- 输出数据使用 io.Writer 接口,虽然扩展性很强,但是对于实际使用不够方便,不支持写入文件。
- 默认配置项较为复杂。
- 很多第三方的包在系统包 runtime.pprof 的技术上进行便利性封装,让整个测试过程更为方便。这里使用 github.com/pkg/profile 包进行例子展示: go get github.com/pkg/profile
示例
下面代码故意制造了一个性能问题,同时使用 github.com/pkg/profile 包进行性能分析。
package main
import (
"github.com/pkg/profile"
"time"
)
func joinSlice() []string {
var arr []string
for i := 0; i < 100000; i++ {
// 故意造成多次的切片添加(append)操作, 由于每次操作可能会有内存重新分配和移动, 性能较低
arr = append(arr, "arr")
}
return arr
}
func main() {
// 开始性能分析, 返回一个停止接口
stopper := profile.Start(profile.CPUProfile, profile.ProfilePath("."))
// 在main()结束时停止性能分析
defer stopper.Stop()
// 分析的核心逻辑
joinSlice()
// 让程序至少运行1秒
time.Sleep(time.Second)
}
编译并运行:
// 编译
$ go build -o cpu cpu.go
// 运行,在当前目录输出 cpu.pprof 文件
$ ./cpu
// 使用 go tool 工具链输入 cpu.pprof 和 cpu 可执行文件,生成 PDF 格式的输出文件,将输出文件重定向为 cpu.pdf 文件。
// 这个过程中会调用 Graphviz 工具,Windows 下需将 Graphviz 的可执行目录添加到环境变量 PATH 中。
$ go tool pprof --pdf cpu cpu.pprof > cpu.pdf
最终生成 cpu.pdf 文件,使用 PDF 查看器打开文件,观察后发现下图所示的某个地方可能存在瓶颈:
图中的每一个框为一个函数调用的路径,第 3 个方框中 joinSlice 函数耗费了 50% 的 CPU 时间,存在性能瓶颈。重新优化代码,在已知切片元素数量的情况下直接分配内存,代码如下:
func joinSlice() []string {
const count = 100000
var arr []string = make([]string, count)
for i := 0; i < count; i++ {
arr[i] = "arr"
}
return arr
}
重新运行上面的代码进行性能分析,最终得到的 cpu.pdf 中将不会再有耗时部分。
generate
介绍
go generate
命令是在Go语言 1.4 版本里面新添加的一个命令,当运行该命令时,它将扫描与当前包相关的源代码文件,找出所有包含//go:generate
的特殊注释,提取并执行该特殊注释后面的命令。- 使用该命令时有以下几点需要注意:
- 该特殊注释必须在 .go 源码文件中;
- 每个源码文件可以包含多个 generate 特殊注释;
- 运行
go generate
命令时,才会执行特殊注释后面的命令; - 当
go generate
命令执行出错时,将终止程序的运行; - 特殊注释必须以
//go:generate
开头,双斜线后面没有空格。
- 在下面这些场景下,我们会使用
go generate
命令:- yacc:从 .y 文件生成 .go 文件;
- protobufs:从 protocol buffer 定义文件(.proto)生成 .pb.go 文件;
- Unicode:从 UnicodeData.txt 生成 Unicode 表;
- HTML:将 HTML 文件嵌入到 go 源码;
- bindata:将形如 JPEG 这样的文件转成 go 代码中的字节数组。
- string 方法:为类似枚举常量这样的类型生成 String() 方法;
- 宏:为既定的泛型包生成特定的实现,比如用于 ints 的 sort.Ints。
- 执行
go generate
命令时,也可以使用一些环境变量,如下所示:- $GOARCH 体系架构(arm、amd64 等);
- $GOOS 当前的 OS 环境(linux、windows 等);
- $GOFILE 当前处理中的文件名;
- $GOLINE 当前命令在文件中的行号;
- $GOPACKAGE 当前处理文件的包名;
- $DOLLAR 固定的
$
,不清楚具体用途。
go generate
命令格式如下所示:
go generate [-run regexp] [-n] [-v] [-x] [command] [build flags] [file.go... | packages]
参数说明如下:
- -run 正则表达式匹配命令行,仅执行匹配的命令;
- -v 输出被处理的包名和源文件名;
- -n 显示不执行命令;
- -x 显示并执行命令;
- command 可以是在环境变量 PATH 中的任何命令。
示例
package main
import "fmt"
//go:generate go run main.go
//go:generate go version
func main() {
fmt.Println("http://c.biancheng.net/golang/")
}
运行:
go generate -x
go run main.go
http://c.biancheng.net/golang/
go version
go version go1.13.6 windows/amd64
通过运行结果可以看出//go:generate
之后的命令成功运行了。
test
见 test 测试