如何用Go构建Go

这篇文章基于2013年4月中旬我给悉尼Go用户组分享中描述Go构建的部分。

在邮件组和IRC频道中,经常有人询问有关 Go编译器,运行时和内部实现的文档。目前关于 Go内部实现权威的文档就是源代码,我鼓励大家都去看源代码。之前已经说过,自从 Go1.0发布之后,Go构建的过程已经固定下来,所以这篇文章很可能维持一段时间。

这篇文章从源代码开始,到充分测试的 Go安装过程结束,讲述了Go构建过程的九个步骤。为简单起见,所有这里提到的路径都相对于签出源代码的根目录 , $GOROOT/src 。

至于背景知识要求,你应该读读 golang.org上面的从源代码安装Go

第一步, all.bash

% cd $GOROOT/src
% ./all.bash

第一步有点虎头蛇尾,因为 all.bash会调用另外两个shell脚本: make.bash和run.bash .如果你用 Windows或者Plan9 ,步骤是一样的,只不过脚本名称分别以 .bat和.rc 结尾。以下叙述过程中,请用相应操作系统的扩展名进行替代。

第二步. make.bash

. ./make.bash --no-banner

make.bash 源于 all.bash ,因此调用exit会正常终止构建过程.make.bash 有三个主要功能, 第一个功能是验证正在编译Go的环境的完整性。完整性检查在过去几年内建立起来,目的主要是避免用已知的损坏了的工具构建或者在构建会失败的环境中构建。

第三步. cmd/dist

gcc -O2 -Wall -Werror -ggdb -o cmd/dist/dist -Icmd/dist cmd/dist/*.c

一旦完整性检查完成,make.bash 会编译 cmd/dist. cmd/dist 替代了Go1之前的基于Makefile的构建系统并管理pkg/runtime中少量的代码生成。cmd/dist 是一个C程序,这使得它能够利用系统的C编译器和头文件来处理大多数的主机平台检测问题.cmd/dist 总是会检测主机的操作系统和架构, $GOHOSTOS and $GOHOSTARCH. 如果你是交叉编译,这些变量的值可能会与设置的不一样。实际上,Go构建过程就是在建立一个交叉编译器,但大多数情况下宿主机和目标机平台是一致的。之后, make.bash 用引导参数调用 cmd/dist ,后者先编译编译器套件使用的支持库 lib9, libbio 和 libmach, 然后编译编译器自身。 这些工具也是用c写成的,用系统的c编译器编译。

echo "# Building compilers and Go bootstrap tool for host, $GOHOSTOS/$GOHOSTARCH."
buildall="-a"
if [ "$1" = "--no-clean" ]; then
 buildall=""
fi
./cmd/dist/dist bootstrap $buildall -v # builds go_bootstrap

cmd/dist 用编译器套件编译了 go 工具 go_bootstrap 的一个版本。 go_bootstrap 是一个不完整的 go 工具,比如为了避免对cgo的依赖,去掉了 pkg/net 。由于cmd/dist 工具包含将要编译的包或者库及其依赖的文件夹列表,所以要格外小心避免引入新的 cmd/go 的构建依赖。

第四步. go_bootstrap

现在go_bootstrap 已经构建完成, make.bash 的最后一步是用go_bootstrap 编译所有的Go标准库包括一个完整版本的go 工具。

echo "# Building packages and commands for $GOOS/$GOARCH."
"$GOTOOLDIR"/go_bootstrap install -gcflags "$GO_GCFLAGS" 
    -ldflags "$GO_LDFLAGS" -v std

第五步. run.bash

现在 make.bash 已经执行完了。执行控制又回到 all.bash 。接下来会调用run.bash 。run.bash的作用是编译并测试标准库、运行时和语言测试套件。

bash run.bash --no-rebuild

使用–no-rebuild 是因为 make.bash 和 run.bash 都会调用 go install -a std, 。为了避免重复调用,–no-rebuild 会跳过第二次 go install。

# allow all.bash to avoid double-build of everything
rebuild=true
if [ "$1" = "--no-rebuild" ]; then
 shift
else
 echo '# Building packages and commands.'
 time go install -a -v std
 echo
fi

第六步. go test -a std

echo '# Testing packages.'
 time go test std -short -timeout=$(expr 120 * $timeout_scale)s
 echo

run.bash 接下来运行标准库中所有包中的单元测试,这些单元测试用到了testing包。因为$GOPATH 和 $GOROOT 下的代码属于同一个命名空间,我们不能用go test …(这回测试$GOPATH中所有的包)。因此用一个别名std来声明标准库中的包,由于一些测试需要很长时间或者消耗很多内存,可以用-short标志来过滤这些测试。

第七步. runtime and cgo tests

run.bash 下一步运行了一系列的针对支持cgo平台的测试,少量基准测试,编译各种各样的和Go一起发杂项程序。慢慢地,这种杂项程序越来越多。因为我们发现,这些杂项程序在构建过程中不可或缺。

第八步. go run test

(xcd ../test
unset GOMAXPROCS
time go run run.go
) || exit $?

这是run.bash 的倒数第二个阶段。调用位于$GOROOT test 文件夹下编译器和运行时的测试用例。这些都是关于编译器和运行时自己的底层细节的测试用例。在测试语言规范的同时, test/bugs 和 test/fixedbugs 子文件夹下的测试用例独立运行以捕获之前已经发现并修复的问题.所有这些测试的驱动是 $GOROOT/test/run.go ,它会执行test文件夹下面每一个.go文件。一些.go文件会在第一行包含一些指令,这些指令会指示run.go ,比如退出程序或者抛出特定的输出序列。

第九步. go tool api

echo '# Checking API compatibility.'
go tool api -c $GOROOT/api/go1.txt,$GOROOT/api/go1.1.txt 
    -next $GOROOT/api/next.txt -except $GOROOT/api/except.txt

run.bash 的最后一步就是调用 api 工具。 api工具的作用是在Go1 2012年发布时执行Go1 API的约定,Go1 API包括导出符号、常量、函数、变量、类型、方法等等。 Go1把这些写在 api/go1.txt中,Go 1.1写在api/go1.1.txt 中。Go1.1之后,一个额外的文件 api/next.txt 指明了标准库和运行时新增符号。一旦Go1.2发布,这个文件会成为Go1.2的约定,并且会有一个新的next.txtT。还有一个包含Go1 约定异常的小文件 except.txt。修改这些文件不可掉以轻心。

其他贴士和技巧

你可能已经发现make.bash 在不运行测试用例时构建Go很有用,而 run.bash 用来构建和测试Go运行时。区别就是:前者可以用于交叉编译;如果你基于标准库开发,后者很有帮助。

更新: 感谢Russ Cox 和 Andrew Gerrand的反馈和建议。

相关的文章:

暂无评论

写评论