一探鹅厂研发小哥的 GO语言工具链学习笔记

一探鹅厂研发小哥的 GO语言工具链学习笔记

导语:Golang 语言的优势非常吸引初期团队作为主要语言选型,并且 Golang 自己带有完整的工具链。但是一些公司内的项目管理和 Golang 部分设计初衷存在一些背离,因此期望能够深入 Golang 的工具链使其和谐。本文并非编译原理探究,主要是解决工具链应用场景。

0x01 Perpare

Environment variables

  • GOOS:程序构建环境的目标操作系统。
  • GOARCH:程序构建环境的目标计算架构。
  • GOHOSTOS:程序运行环境的目标操作系统。
  • GOHOSTARCH:程序运行环境的目标计算架构。
  • GOROOT:Golang 安装根目录。
  • GOPATH:Golang 工作区目录,可以包含多个目录,每一个工作区目录之下包含三个子文件夹。
workspace // GOPATH
 |-- bin // 默认的可执行文件安装目录
 |-- pkg // 默认的库归档文件安装目录
 |-- src // 源代码目录
  • GOBIN:Golang 可执行文件安装目录。(:warning:不建议 PATH 包含该目录)
  • 如果设置了 GOBIN,编译后的可执行文件将不会安装到 GOPATH 下的 bin 目录。
  • 如果 GOPATH 包含了多个工作区目录,必须设置 GOBIN。

Commands

  • compiler
  • go build:compile packages and dependencies
  • go install:compile and install packages and dependencies、
  • go clean:remove object files
  • go run:compile and run Go program
  • go test:test packages
  • packager
  • go get:download and install packages and dependencies
  • go mod: module maintenance
  • go list:list packages
  • analyzer
  • go vet:run go tool vet on packages
  • go tool pprof

0x02 Build Procedure

run && build && install

通过 go ${subcommand} -x 观察编译过程,三个子命令都是先创建临时工作区目录,使用 $GOROOT/libexec/pkg/tool/$GOOS_$GOARCH/compliego tool complie) 工具编译生成名为 command-line-arguments 库文件,然后再通过 $GOROOT/libexec/pkg/tool/$GOOS_$GOARCH/linkgo tool link) 工具链接生成目标可执行文件。

  • go run
  • 仅针对 package main 的 Go 文件,且不支持 testing Go 文件。
  • 生成的相关库文件与可执行文件仅保留在临时工作区目录,并且会直接执行目标可执行文件。
  • go build
  • 针对 package main 的 Go 文件会生成可执行文件,并且将可执行文件复制到当前目录(不是源代码所在目录)。
  • 针对其他 Go 文件只会做检查性的编译,不会链接生成可执行文件,也不会将库文件复制到当前目录。
  • go install
  • 针对 package main 的 Go 文件会生成可执行文件,并且将相关库文件以包目录结构复制到 $GOPATH/pkg/ 目录,可执行文件将直接复制到 $GOPATH/bin$GOBIN 目录。
  • 针对其他 Go 文件会将相关库文件以包目录结构复制到 $GOPATH/pkg/ 目录。

学习笔记

  • Golang 已经提供了非常完整的工具链支持,还可以使用 go tool compliego tool link定制个性化的编译和链接过程。
  • 其中 go buildgo run 等子命令还支持 -o 选项可以指定生成的可执行文件的输出目录,不完全依赖 GOPATHGOBIN
  • Golang 也提供了编译动态连接库的模式,可以满足特定场景的需求。
  • Golang 通过 GOOSGOARCH 两个环境变量可以指定交叉编译目标系统和平台,前提是当前安装的 Golang 工具链已经包含目标平台交叉编译工具链。

一探鹅厂研发小哥的 GO语言工具链学习笔记

0x03 Package Maneragement

GOPATH && go get

  • GOPATH 是工作区目录,编译过程中会按照 GOPATH 中路径的顺序寻找对应的依赖包。
  • go get 会根据包地址将包代码下载到 GOPATH 中首个路径的 src 目录。

这是最初版本的 Golang 包管理,可以通过 GOPATH 管理业务代码与依赖库目录。

Godeps && vendor

  • 2013 年,Google 提供了 godep 工具,扫描项目代码生成 Godeps.json 依赖关系文件,并将项目的依赖库源代码复制保存在 Godeps/_workspace 目录(后期也使用 vendor 目录),并将该目录作为 GOPATH 解决包依赖问题。
  • 2016 年,Google 发布 Golang 1.6 版本,增加了 vendor 目录作为 GOPATH 的默认路径,社区提供了多种依赖管理方案,以 glide 和 govendor 为主要代表。

Google 官方开始介入 Golang 包管理乱象,但是解决方案并未能够得到普遍共识。

go modules / vgo

  • vgo 只是 go modules 在正式发布之前的代号,Google 在 Golang 1.11 版本提供了新的包管理官方解决方案。
  • go mod 工具通过维护 go.mod 文件管理包依赖,并支持版本管理。
  • go mod 工具不再完全依赖 GOPATH,同时提供 GOPATH 兼容模式。

go modules 的一般使用过程。


学习笔记

Golang 提供了官方的的包管理方案,可以从这些方案的发展过程中窥探到包管理的需求。

  • 简单的包获取方式:go get 很好的满足的包获取,同时也提供包别名方案。
  • 完整的包管理工具:godep 和 go mod 工具对于已获取的包能够很好的组织工程项目。
  • 便捷的包依赖分析:Godeps.json 和 go.mod 文件与相应工具支持包依赖分析。
  • 有效的包版本控制:go mod 已经支持包版本控制。

0x04 Thirdparty Dependences

Protobuf

  • protoc 生成 Go 文件,需要使用 protoc-gen-go 插件。如果需要支持 service 生成,需要使用 grpc 子插件。
  • Go 语言独立的包依赖,所以需要在 .proto 文件指定 go_package 选项。

学习笔记

  • protoc-gen-go 插件通过 --go_out=$key=$value{,...}:$outpath 形式接收插件参数。
  • plugins:子插件列表,例如 grpc 子插件。
  • import_prefix:包引用前缀。
  • import_path:包引用路径。
  • paths:包引入路径类型,import 与 source_relative 两种类型,默认 source_relative。
  • annotate_code:注释代码开关。
  • Protobuf FileDescriptor Options
  • go_package:指定生成 Golang 包的包名。

注意 Protobuf 包与 Golang 包相互关系,可以通过指定 Protobuf 文件中的 go_package 以及指定 protoc-gen-go 工具相关参数组织合理的工程目录。

cgo

  • 堆栈切换存在性能隐患。
  • 因为 C 语言的绑定,无法支持交叉编译。
  • Golang 相关调试工具将会无法深入调试。

学习笔记

不要轻易使用 cgo 技术,但是对于公司内部的公共组件使用可能这是唯一的解决方式,如果使用需要注意主机操作系统与系统库版本。

一探鹅厂研发小哥的 GO语言工具链学习笔记

0x05 Bazel && Blade

大型工程项目都会选择相应的构建系统来编译整个工程,Google Bazel 与 typhoon/blade 都是非常好的构建系统选型,现阶段都对 Golang 构建有相应的支持。

Bazel 已经很好的支持了 Golang 工程构建,并且还支持引入外部源,由于自己的工程并没有深入使用 Bazel 系统,所以此处并不作深入的分析。

Blade 现在对于 Golang 工程构建比较初级。

  • 需要在 go_config 中配置 go_home 配置作为 GOPATH,并且不能指定多个 GOPATH,也不接收通过环境变量直接指定的 GOPATH
  • Blade 实现 Golang 相关编译是通过调用 go buildgo installgo test 三个子命令,也并未对交叉编译有明确的支持。
  • 公司内的网络策略也不支持在代码编译机器获取外部包依赖,同时内部没有也不可能搭建 Golang 包镜像,所以 Blade 并未实现外源支持。
  • Protobuf Golang 编译目标需要明确指定 go_package,强约束工程目录组织结构。

题外话,为什么需要一个构建系统?如果使用构建系统,为什么选择 Blade 而非 Bazel?

  • 项目通过多语言实现,使用构建系统可以统一构建关系。配合协议描述语言生成代码工具,可以打通多语言交流壁垒。
  • Blade 是内部开源项目,部分功能满足公司内部特定场景需求。Bazel 适合类似 Google 统一代码库的超大型工程。两者差异化不大,所以选择了小而美,更适合公司内部使用的 Blade。

0xFF Final

通过对 Golang 工具链的学习了解,来尝试解决一下现阶段面临的问题。

  • 如何解决工程依赖的外部包?
  • 对于依赖的外部包只能保存在代码库,因为持续编译集成环境无法访问外网。相当于对外部依赖库进行 Snapshot。
  • 现阶段工程使用的是 Blade 构建系统,不能支持多个 GOPATH 指定,所以所有外部依赖包均保存在 go_home 所指定的目录。
  • git.code.oa.com 开源的 Golang 依赖包将会以 tencent.com 域名引用。
  • go modules 不是强制使用,但是为了项目标准化建议符合规范。
  • 如何构建当前 Golang 工程?
  • Protobuf Golang 目标构建分两个过程,首先是通过 protoc 工具指定 GOPATH 为输出目录生成 .pb.go 文件,其次是通过 Golang 编译工具链编译生成静态链接库。
  • Golang 工具链编译过程会编译依赖库,链接可执行文件的过程链接依赖库文件,并不需要 Blade 构建系统显式指定。但是,依赖的 proto_library 需要显式指定,因为 Golang 编译过程并不会生成 Protobuf 文件对应的 Go 文件。
  • 项目内实现的 go_library 要求强制显式声明,便于 proto_library 和 gen_rule 等依赖的传递。

Reference

1.《初探 Go 的编译命令执行过程》https://halfrost.com/go_command/

2.《GO 命令教程》https://github.com/hyper0x/go_command_tutorial

3.《Go 包管理的前世今生》https://www.infoq.cn/article/history-go-package-management

4.《初窥Go module》https://tonybai.com/2018/07/15/hello-go-module/

相关推荐