Golang Build Process

Attention

这里的构建过程没有考虑 Golang Build Cache 的影响

Golang 的构建过程是典型的静态编译语言的编译过程,分为编译(compile)和链接(link)两个阶段。

包源码被编译为 .o 文件之后,会被 pack 工具打包成为 .a 文件,生成的 .a 会被放入临时构建文件夹,之后被链接成为可执行文件。

标准库的构建过程

标准库的源码存放在 $GOROOT/src 下,标准库编译的 .a 文件存放在 $GOROOT/pkg/ 下,构建时如果已经存在 .a 文件,则会直接链接 .a 文件,否则会从源文件中进行编译。如果想强制从标准库的源码进行重新编译,可以通过两种办法:

  1. 删除相应的 .a 的文件
  2. 使用 go build -a 参数,这会将所有的源码重新编译一边,所以构建时间可能很长

非标准库的构建过程

同标准库的构建过程不同,非标准库在构建的时候都会从源码进行构建。使用 go install 命令可以将包的 .a 文件安装到 $GOPATH/pkg/ 下,但在构建过程中会重新编译源码到 .a,重新编译的文件会被放入构建的临时目录,临时目录的下的 .a 优先级会高于 $GOPATH/pkg/ 中的 .a

程序构建过程

以下面结构的项目的为例,编译是使用命令 go build -x -v 输出详细构建日志:

$tree -F demo1
demo1
├── cmd/
   └── app1/
       └── main.go
└── pkg/
    └── pkg1/
        └── pkg1.go

程序 app1 的构建过程可分为下面几步:

  1. 建立临时工作目录,放入环境变量 WORK,之后的工作都已 $WORK 为当前目录
  2. 编译 app1 的依赖包 pkg1,将目标文件打包放入 $WORK/<demo1_pkg_path>/pkg/pkg1.a
  3. 编译 app1 的 main 包, 将目标文件打包后放入 $WORK/<demo1_pkg_path>/cmd/app1.a
  4. 链接器将 app1.a, pkg1.a 链接成为 $WORK/<demo1_pkg_path>/cmd/app1/_obj/exe/a.out
  5. a.out 复制到 go build 命令执行目录,并改名为 app1
指向原始笔记的链接

Golang Package Search Path

Golang 中有两部分的包搜索路径由两部分主成:

基础的搜索路径首先都包括 $GOROOT/src。在此基础上,按照 GO111MODULE 的设置不同还会增加不同的路径。

  1. GO111MODULE=off 使用传统的 gopath 模式,包基础搜索路径包含 $GOPATH/src/
  2. GO111MODULE=on 使用 gomod 模式,包基础搜索路径包含 $GOPATH/pkg/mod/

包导入路径就是每个源文件头部包导入部分的路径。基础路径组合包导入路径,就构成了源文件所有依赖包的源码搜索路径。以下面的例子为例:

package p1
 
import (
    "fmt"
    "time"
    "github.com/bigwhite/effective-go-book"
    "golang.org/x/text"
    "a/b/c"
    "./e/f/g"
)

该源码包的搜索路径集合在使用 gomod 模式时可以展开为下面这样:

- $GOROOT/src/fmt/
- $GOROOT/src/time/
- $GOROOT/src/github.com/bigwhite/effective-go-book/
- $GOROOT/src/golang.org/x/text/
- $GOROOT/src/a/b/c/
- $GOPATH/pkg/mod/github.com/bigwhite/effective-go-book/
- $GOPATH/pkg/mod/golang.org/x/text/
- $GOPATH/pkg/mod/a/b/c/
- $CWD/e/f/g

这里也可以看出代码中 import 中的其实是路径名,而不是包名,包名由路径中源文件头部的 package 关键字指定,其可能和路径名不同,只是 Golang 惯例是和路径名保持一致。

指向原始笔记的链接

如果一个源码文件中导入两个来源不同包名相同的依赖时,通过设置包的别名来解决冲突。

package main
 
import (
  "fmt"
  myfmt "github.com/q3yi/go/fmt"
)