Go 源码编译流程

Go 编译源代码需要经过编译,链接过程。通过以下示例看 Go (Go 版本为 v1.24.1)语言是如何编译源码的。

首先,代码目录结构如下:

➜  demo1 git:(main) ✗ tree ./
./
├── cmd
│   └── app1
│   └── main.go
└── pkg
└── pkg1
└── pkg1.go // main.go
package main import "github.com/TroyXia/effective-go-book/chapter3/demo1/pkg/pkg1" func main() {
pkg1.Func1()
} // pkg1.go
package pkg1 import "fmt" func Func1() {
fmt.Println("pkg1.Func1 invoked")
}

编译 app1 main.go 为可执行文件:

➜  demo1 git:(main) ✗ go build -x -v ./cmd/app1
WORK=/var/folders/xl/5zdz2b514tg_fmsn0k9h0b200000gn/T/go-build2927962297 ...
cat >/var/folders/xl/5zdz2b514tg_fmsn0k9h0b200000gn/T/go-build2927962297/b001/importcfg.link << 'EOF' # internal
packagefile github.com/TroyXia/effective-go-book/chapter3/demo1/cmd/app1=/Users/hxia/Library/Caches/go-build/4c/4c4a8646f05704995bdb9ecacc59745b0c87e54e0b3f134ebc787ffa86349e24-d
...
modinfo "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tgithub.com/TroyXia/effective-go-book/chapter3/demo1/cmd/app1\nmod\tgithub.com/TroyXia/effective-go-book\tv0.0.0-20250726040104-4c9ceff7dcec+dirty\t\nbuild\t-buildmode=exe\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1\nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=arm64\nbuild\tGOOS=darwin\nbuild\tGOARM64=v8.0\nbuild\tvcs=git\nbuild\tvcs.revision=4c9ceff7dcecb5af5c6e76869f02e15c5a71d2b2\nbuild\tvcs.time=2025-07-26T04:01:04Z\nbuild\tvcs.modified=true\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF mkdir -p $WORK/b001/exe/
cd .
GOROOT='/Users/hxia/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.4.darwin-arm64' /Users/hxia/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.4.darwin-arm64/pkg/tool/darwin_arm64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=pie -buildid=CoYhGGCXfSwQX5HjxsS7/ocfceuFgMPhkI7fMX0t9/gBNCu5T0ndxhcTWp_FbS/CoYhGGCXfSwQX5HjxsS7 -extld=clang /Users/hxia/Library/Caches/go-build/4c/4c4a8646f05704995bdb9ecacc59745b0c87e54e0b3f134ebc787ffa86349e24-d
/Users/hxia/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.4.darwin-arm64/pkg/tool/darwin_arm64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out app1
rm -rf $WORK/b001/

go build 命令对应用/包进行编译。其中,-x 选项打印编译的执行命令,-v 选项打印编译时的包名。

go build 输出信息可以看出,编译主要分为以下几步:

  1. 构建工作空间 WORK 目录,该目录在编译完会删除。-work 选项可以保留工作空间(不过临时的编译文件会被删除)。
  2. 构造编译的链接包信息 importcfg.link
  3. 链接器 link 链接 importcfg.link 中的包目标文件(.a 文件)为可执行文件 a.out

上述流程中少了编译过程,那么编译是在哪里发生的呢?

go build 中添加 -a 选项,-a 选项会重新编译所依赖的包(部分内容和上述编译输出类似,为减少冗余,这里重点关注编译部分):

cd /Users/hxia/project/effective-go-book/chapter3/demo1
/Users/hxia/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.4.darwin-arm64/pkg/tool/darwin_arm64/compile -o $WORK/b013/_pkg_.a -trimpath "$WORK/b013=>" -p internal/byteorder -lang=go1.24 -std -complete -buildid c6rtfNe70CDveX5CsxT3/c6rtfNe70CDveX5CsxT3 -goversion go1.24.4 -c=4 -shared -nolocalimports -importcfg $WORK/b013/importcfg -pack /Users/hxia/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.4.darwin-arm64/src/internal/byteorder/byteorder.go
/Users/hxia/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.4.darwin-arm64/pkg/tool/darwin_arm64/compile -o $WORK/b015/_pkg_.a -trimpath "$WORK/b015=>" -p internal/coverage/rtcov -lang=go1.24 -std -complete -buildid w3eNR5jLvNw3wS6UVtCI/w3eNR5jLvNw3wS6UVtCI -goversion go1.24.4 -c=4 -shared -nolocalimports -importcfg $WORK/b015/importcfg -pack /Users/hxia/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.4.darwin-arm64/src/internal/coverage/rtcov/rtcov.go
/Users/hxia/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.4.darwin-arm64/pkg/tool/darwin_arm64/compile -o $WORK/b016/_pkg_.a -trimpath "$WORK/b016=>" -p internal/godebugs -lang=go1.24 -std -complete -buildid n8Me6R799Ql_sSkrNPi9/n8Me6R799Ql_sSkrNPi9 -goversion go1.24.4 -c=4 -shared -nolocalimports -importcfg $WORK/b016/importcfg -pack /Users/hxia/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.4.darwin-arm64/src/internal/godebugs/table.go

可以看到 compile 编译包为 _pkg_.a 目标文件。该文件会作为缓存存在于 go env 缓存目录:

➜  demo1 git:(main) ✗ go env | grep cache -i
GOCACHE='/Users/hxia/Library/Caches/go-build' // 进入缓存目录,缓存目录中存放的是编译包的目标文件(静态库,以 -a 结尾)和依赖描述文件(以 -d 结尾,记录包之间的依赖关系和编译参数)
➜ go-build pwd
/Users/hxia/Library/Caches/go-build
➜ go-build cd 04
➜ 04 ls
040220f430a0f504e501643d86f6973881ff5c1ddac97cda7419062ffebf2c9a-d 0466cee1e7f91da1b64696743011bf7315f38d07f501651dd54095fc0f4f25a2-a

看到这里大致理解了,之所以刚开始没有出现编译是因为依赖包已经编译好加载到缓存中了,其引用的是缓存中的依赖描述文件 /Users/hxia/Library/Caches/go-build/4c/4c4a8646f05704995bdb9ecacc59745b0c87e54e0b3f134ebc787ffa86349e24-d

Go 1.9 源码编译流程

如上节所示,在 Go 1.11 版本之后的静态库都是加载到缓存中。这里以 Go 1.9 版本为例更直观的查看编译过程。

首先通过 go install 安装包,go install 会编译包为静态库。如下:

# ./go install -x github.com/TroyXia/effective-go-book/chapter3/demo1/pkg/pkg1
...
/root/hxia/go/go/pkg/tool/linux_amd64/compile -o $WORK/github.com/TroyXia/effective-go-book/chapter3/demo1/pkg/pkg1.a -trimpath $WORK -goversion go1.9.7 -p github.com/TroyXia/effective-go-book/chapter3/demo1/pkg/pkg1 -complete -buildid 31988b53e81eee1d2b33fbaf9b29322314015d4a -D _/root/go/src/github.com/TroyXia/effective-go-book/chapter3/demo1/pkg/pkg1 -I $WORK -pack ./pkg1.go
mkdir -p /root/go/pkg/linux_amd64/github.com/TroyXia/effective-go-book/chapter3/demo1/pkg/
cp $WORK/github.com/TroyXia/effective-go-book/chapter3/demo1/pkg/pkg1.a /root/go/pkg/linux_amd64/github.com/TroyXia/effective-go-book/chapter3/demo1/pkg/pkg1.a

go install 调用 compile 编译包 github.com/TroyXia/effective-go-book/chapter3/demo1/pkg/pkg1 为静态库 $WORK/github.com/TroyXia/effective-go-book/chapter3/demo1/pkg/pkg1.a,接着 cp 拷贝该静态库到 /root/go/pkg/linux_amd64/github.com/TroyXia/effective-go-book/chapter3/demo1/pkg/pkg1.a

继续编 go 应用查看应用是如何加载 pkg1 静态库的:

# ./go build -x -v github.com/TroyXia/effective-go-book/chapter3/demo1/cmd/app1
WORK=/tmp/go-build326903613
...
/root/hxia/go/go/pkg/tool/linux_amd64/compile -o $WORK/github.com/TroyXia/effective-go-book/chapter3/demo1/cmd/app1.a -trimpath $WORK -goversion go1.9.7 -p main -complete -buildid 4994d4113529a2a372aab06efa535b1ffff36829 -D _/root/go/src/github.com/TroyXia/effective-go-book/chapter3/demo1/cmd/app1 -I $WORK -I /root/go/pkg/linux_amd64 -pack ./main.go
cd . /root/hxia/go/go/pkg/tool/linux_amd64/link -o $WORK/github.com/TroyXia/effective-go-book/chapter3/demo1/cmd/app1/_obj/exe/a.out -L $WORK -L /root/go/pkg/linux_amd64 -extld=gcc -buildmode=exe -buildid=4994d4113529a2a372aab06efa535b1ffff36829 $WORK/github.com/TroyXia/effective-go-book/chapter3/demo1/cmd/app1.a
cp $WORK/github.com/TroyXia/effective-go-book/chapter3/demo1/cmd/app1/_obj/exe/a.out app1

流程比较清晰,compile 编译 app 包到静态库 $WORK/github.com/TroyXia/effective-go-book/chapter3/demo1/cmd/app1.a(其中包括了应用依赖的 pkg1.a 静态库)。最终通过 link 链接静态库成可执行文件 a.out

这里有一个问题是 app 依赖的静态库是 go install 提前编译好的 pkg1.a 还是 $WORK 目录下的静态库呢?

我们更新 pkg1.go 的内容,查看内容变化后静态库链接的是旧的还是更新后的 pkg1.a

package pkg

import "fmt"

func Func1() {
fmt.Println("pkg1.Func1 invoked already")
}

编译后运行应用输出 pkg1.Func1 invoked alreadygo install 提前编译的静态库并未被链接。

因为在 link 链接这里指定的链接目录顺序为 -L $WORK -L /root/go/pkg/linux_amd64。链接器现在 $WORK 搜索链接的静态库,如果没有找到再到 /root/go/pkg/linux_amd64 目录下搜索,而 go install 编译的静态库是在 /root/go/pkg/linux_amd64 目录下。

调换链接器的链接库目录顺序,手动执行,输出 pkg1.Func1 invoked。代码编译用的是老的静态库。

静态可执行文件

继续看静态可执行文件。在 macOS 系统编译应用为可执行文件。如下:

➜  demo1 git:(main) ✗ go build -o app cmd/app1/main.go
➜ demo1 git:(main) ✗ ls -la
total 4600
drwxr-xr-x@ 5 hxia staff 160 Jul 27 21:52 .
drwxr-xr-x@ 3 hxia staff 96 Jul 26 11:51 ..
-rwxr-xr-x@ 1 hxia staff 2351842 Jul 27 21:52 app
drwxr-xr-x@ 3 hxia staff 96 Jul 26 11:52 cmd
drwxr-xr-x@ 3 hxia staff 96 Jul 26 11:52 pkg
➜ demo1 git:(main) ✗ otool -L app
app:
/usr/lib/libSystem.B.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libresolv.9.dylib (compatibility version 0.0.0, current version 0.0.0)

otool 显示静态可执行文件还需要调用动态库 libSystem.B.dyliblibresolv.9.dylib,这个静态可执行文件看起来不静态啊。

这是由于 macOS 操作系统的缘故,编译的可执行文件并不是完整的静态可执行。

通过 go build 编译成 Linux amd64 系统的可执行文件 app:

GOOS=linux GOARCH=amd64 go build -o app cmd/app1/main.go 

// 拷贝 app 到 Linux amd64 机器上

// ldd 查看 app 是否依赖动态库
# ldd app
不是动态可执行文件

在 Linux amd64 机器上可以看到编译出的是完整的静态可执行文件,可以直接运行。

参考资料


Go 源码编译流程的更多相关文章

  1. spring5源码编译过程中必经的坑

    spring源码编译流程:Spring5 源码下载 第 一 步 : https://github.com/spring-projects/spring-framework/archive/v5.0.2 ...

  2. [笔记] Ubuntu 18.04源码编译安装OpenCV 4.0流程

    标准常规安装方法安装的OpenCV版本比较低,想尝鲜使用4.0版本,只好源码安装. 安装环境 OS:Ubuntu 18.04 64 bit 显卡:NVidia GTX 1080 CUDA:10.0 c ...

  3. hadoop-2.6.0源码编译问题汇总

    在上一篇文章中,介绍了hadoop-2.6.0源码编译的一般流程,因个人计算机环境的不同, 编译过程中难免会出现一些错误,下面是我编译过程中遇到的错误. 列举出来并附上我解决此错误的方法,希望对大家有 ...

  4. Ubantu16.04进行Android 8.0源码编译

    参考这篇博客 经过测试,8.0源码下载及编译之后,占用100多G的硬盘空间,尽量给ubantu系统多留一些硬盘空间,如果后续需要在编译好的源码上进行开发,需要预留更多的控件,为了防止后续出现文件权限问 ...

  5. Android源码浅析(四)——我在Android开发中常用到的adb命令,Linux命令,源码编译命令

    Android源码浅析(四)--我在Android开发中常用到的adb命令,Linux命令,源码编译命令 我自己平时开发的时候积累的一些命令,希望对你有所帮助 adb是什么?: adb的全称为Andr ...

  6. 【流媒体开发】VLC Media Player - Android 平台源码编译 与 二次开发详解 (提供详细800M下载好的编译源码及eclipse可调试播放器源码下载)

    作者 : 韩曙亮  博客地址 : http://blog.csdn.net/shulianghan/article/details/42707293 转载请注明出处 : http://blog.csd ...

  7. Python 多线程、多进程 (一)之 源码执行流程、GIL

    Python 多线程.多进程 (一)之 源码执行流程.GIL Python 多线程.多进程 (二)之 多线程.同步.通信 Python 多线程.多进程 (三)之 线程进程对比.多线程 一.python ...

  8. Android系统定制和源码开发以及源码编译(附视频)

    Android系统定制配套视频: 为了把Android系统源码定制和编译的课程讲完,从准备到录制完所有的视频,一共花去了近半年的时间,前前后后各种下载源码,编译源码,系统不兼容,版本适配,虚拟机配置困 ...

  9. Linux 下源码编译安装 vim 8.1

    前言 目前 linux 的各个发行版基本上都是带了一个 vi 编辑器的,而本文要说的 vim 编辑器对 vi 做了一些优化升级,更好用.当我们需要远程操作一台 linux 服务器的时候,只能使用命令行 ...

  10. 日常工作之Zabbix源码编译,兼容mysql5.6

    原文链接:http://www.leleblog.top/daily/more?id=6 Zabbix源码编译 环境: centOS7.mysql5.6.21(已存在). 任务简述: 服务器搭建zab ...

随机推荐

  1. Java Solon-MCP 实现 MCP 实践全解析:SSE 与 STDIO 通信模式详解

    参文参考自:https://blog.csdn.net/lingding_cn/article/details/147355620 一.MCP简介 MCP(Model Context Protocol ...

  2. XmlNamespaceManager与XmlDocument.NameTable浅述

    XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); XmlNamespaceManager nsmgr=new XmlNamespaceMan ...

  3. 基于Python运用PyComCAD进行Autocad二次开发实例汇集

    本文将对运用Pycomcad库(Pycomcad详见,https://github.com/JohnYang1210/PycomCAD)做的一些项目及其效果图进行展示(欢迎各位老铁pull reque ...

  4. Django批量创建Model实例

    1.前言: 将测试数据全部敲入数据库非常繁琐,而且如果与合作伙伴一起开发,部署,那么他们肯定也不想把时间花在一个一个录入数据的繁琐过程中,这时候,创建一个批量录入数据的脚本(population sc ...

  5. ChatGPT 相关资料

    ChatGPT是基于GPT-3.5的语言模型且并未开源.对ChatGPT的资料搜索主要来自于兄弟模型InstrucGPT的相关资料. 相比较于InstrucGPT,ChatGPT采用多轮对话形式,符合 ...

  6. Qt图像处理技术六:拉普拉斯锐化

    Qt图像处理技术六:拉普拉斯锐化 效果图 源码 由该公式得到下方卷积核 使用到的卷积核: //都把QImage转化为rgb888更好运算 QImage LaplaceSharpen(const QIm ...

  7. Java 自定义线程池的线程工厂

      本文分享创建线程工厂 ThreadFactory 的三种方式,以方便大家快速创建线程池,并通过线程工厂给每个创建出来的线程设置极富业务含义的名字. 线程池大小考虑因素   由于需要自定义线程池,故 ...

  8. CommonJS、ES 导出和导入模块

    以下代码制作展示,不能直接运行. CommonJS导出 // module.cjs // CJS默认导出 //module.exports = 'Hello world'; /*module.expo ...

  9. Keil watch中数据不更新解决办法

    watch数据不更新解决办法 在使用keil的watch窗口观察寄存器里面的值进行调试时,有时候里面的值就是不会更新,经测试打开View中最下面的那个按键即可

  10. Vue 学习笔记 [Part 3]

    作者:故事我忘了¢个人微信公众号:程序猿的月光宝盒 目录 〇.高阶函数 0.1 filter() 0.2 map() 0.3 reduce() 一. 表单绑定v-model 1.1. v-model的 ...