build命令简述

在Golang中,build过程主要由go build执行。它完成了源码的编译与可执行文件的生成。

go build接收参数为.go文件或目录,默认情况下编译当前目录下所有.go文件。在main包下执行会生成相应的可执行文件,在非main包下,它会做一些检查,生成的库文件放在缓存目录下,在工作目录下并无新文件生成。

介绍build选项

编译流程的演示需要go build提供的几个选项协助,执行go help build查看。如下:

$ go help build
...

-n 不执行地打印流程中用到的命令
-x 执行并打印流程中用到的命令,要注意下它与-n选项的区别
-work 打印编译时的临时目录路径,并在结束时不删除它。默认情况下,编译结束会删除该临时目录。

...

这几个选项也适用于go run命令。有没有觉得和sh命令选项类似,可见计算机里的很多知识都是相通的。

打印执行流程

使用 -n 选项在命令不执行的情况下,查看go build的执行流程,如下:

root@windows10-jack:/mnt/d/dev/php/magook/trunk/server/golang/project/demo2# go build -n hello.go

#
# command-line-arguments
#

mkdir -p $WORK/b001/
cat >$WORK/b001/_gomod_.go << 'EOF' # internal
package main
import _ "unsafe"
//go:linkname __debug_modinfo__ runtime.modinfo
var __debug_modinfo__ = "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tcommand-line-arguments\nmod\tcommand-line-arguments\t(devel)\t\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
        EOF
cat >$WORK/b001/importcfg << 'EOF' # internal
# import config
packagefile fmt=/usr/local/go/pkg/linux_amd64/fmt.a
packagefile runtime=/usr/local/go/pkg/linux_amd64/runtime.a
EOF
cd /mnt/d/dev/php/magook/trunk/server/golang/project/demo2
/usr/local/go/pkg/tool/linux_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -complete -buildid p3Q2TDqIBiwga6vUpe6w/p3Q2TDqIBiwga6vUpe6w -goversion go1.14.7 -D _/mnt/d/dev/php/magook/trunk/server/golang/project/demo2 -importcfg $WORK/b001/importcfg -pack -c=4 ./hello.go $WORK/b001/_gomod_.go
/usr/local/go/pkg/tool/linux_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cat >$WORK/b001/importcfg.link << 'EOF' # internal
packagefile command-line-arguments=$WORK/b001/_pkg_.a
......
packagefile internal/race=/usr/local/go/pkg/linux_amd64/internal/race.a
EOF
mkdir -p $WORK/b001/exe/
cd .
/usr/local/go/pkg/tool/linux_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=8VMWOSPI__rSSJ59c7zb/p3Q2TDqIBiwga6vUpe6w/p3Q2TDqIBiwga6vUpe6w/8VMWOSPI__rSSJ59c7zb -extld=gcc $WORK/b001/_pkg_.a
/usr/local/go/pkg/tool/linux_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out hello

过程看起来很乱,仔细观看下来可以发现主要由几部分组成,分别是:

  • 创建临时目录,mkdir -p &dollar;WORK/b001/
  • 查找依赖信息,cat >$WORK/b001/importcfg << ...
  • 执行源代码编译,/usr/local/go/pkg/tool/linux_amd64/compile ...
  • 收集链接库文件,cat >$WORK/b001/importcfg.link << ...
  • 生成可执行文件,/usr/local/go/pkg/tool/linux_amd64/link -o ...
  • 移动可执行文件,mv $WORK/b001/exe/a.out hello

如此一解释,build 的流程就很清晰了。如果是熟悉c/c++开发的朋友,会发现这个过程似曾相识。当然,相比之下c/c++还会多出一步预处理。

再来优化下之前的流程图,如下:

源码 --> 编译 --> 可执行文件 --> 执行输出

我们把build过程细化成两部分,compile与link,即编译和链接。此处用到了两个很重要的命令,complie和link。它们都是属于go tool的子命令。

说说run的流程

理解了build过程,run就很好理解了。我们使用go run -x hello.go 查看执行过程,如下:

WORK=/tmp/go-build555630986
mkdir -p $WORK/b001/
cat >$WORK/b001/_gomod_.go << 'EOF' # internal
package main
import _ "unsafe"
//go:linkname __debug_modinfo__ runtime.modinfo
var __debug_modinfo__ = "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tcommand-line-arguments\nmod\tcommand-line-arguments\t(devel)\t\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
        EOF
cat >$WORK/b001/importcfg << 'EOF' # internal
# import config
packagefile fmt=/usr/local/go/pkg/linux_amd64/fmt.a
packagefile runtime=/usr/local/go/pkg/linux_amd64/runtime.a
EOF
cd /mnt/d/dev/php/magook/trunk/server/golang/project/demo2
/usr/local/go/pkg/tool/linux_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -complete -buildid X7vV4ij19xBSsrIrN7v7/X7vV4ij19xBSsrIrN7v7 -dwarf=false -goversion go1.14.7 -D _/mnt/d/dev/php/magook/trunk/server/golang/project/demo2 -importcfg $WORK/b001/importcfg -pack -c=4 ./hello.go $WORK/b001/_gomod_.go
/usr/local/go/pkg/tool/linux_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cp $WORK/b001/_pkg_.a /root/.cache/go-build/ca/ca1017e4a5186490f1dcdcd1c4ac6b339b900f400b32bc81d1d4630e8ae27852-d # internal
cat >$WORK/b001/importcfg.link << 'EOF' # internal
packagefile command-line-arguments=$WORK/b001/_pkg_.a
......
packagefile internal/race=/usr/local/go/pkg/linux_amd64/internal/race.a
EOF
mkdir -p $WORK/b001/exe/
cd .
/usr/local/go/pkg/tool/linux_amd64/link -o $WORK/b001/exe/hello -importcfg $WORK/b001/importcfg.link -s -w -buildmode=exe -buildid=QcX5MsXak3ZUST00hP5B/X7vV4ij19xBSsrIrN7v7/swx-yVH8t2gfunZk5dvR/QcX5MsXak3ZUST00hP5B -extld=gcc $WORK/b001/_pkg_.a
$WORK/b001/exe/hello
Hello World

重点看结尾部分,与build不同的是,在link生成hello文件后,并没有把它移动到当前目录,而是通过$WORK/b001/exe/hello执行了程序。加上编译,画出如下流程图:

源码 --> 编译 --> 连接 --> 可执行文件 --> 执行输出

到此,run的整个流程到此就很清晰了。

并且由输出可以看到,第一行就打印了WORK的地址,默认会被清理的,如果想保留,需要追加-work参数,即go run -x -work hello.go