<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; 链接</title>
	<atom:link href="http://tonybai.com/tag/%e9%93%be%e6%8e%a5/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Mon, 08 Jun 2026 23:32:23 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>大项目构建太慢？Brad Fitzpatrick 提议引入 -cachelink 降低测试等待时间</title>
		<link>https://tonybai.com/2026/02/05/brad-fitzpatrick-cachelink-reduce-go-test-wait-time/</link>
		<comments>https://tonybai.com/2026/02/05/brad-fitzpatrick-cachelink-reduce-go-test-wait-time/#comments</comments>
		<pubDate>Wed, 04 Feb 2026 23:20:20 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[-cachelink]]></category>
		<category><![CDATA[BradFitzpatrick]]></category>
		<category><![CDATA[BuildBottleneck]]></category>
		<category><![CDATA[CacheManagement]]></category>
		<category><![CDATA[DistributedCI]]></category>
		<category><![CDATA[DistributedTesting]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[GoBuildCache]]></category>
		<category><![CDATA[GOCACHE]]></category>
		<category><![CDATA[GOCACHEPROG]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GoLanguage]]></category>
		<category><![CDATA[Go语言]]></category>
		<category><![CDATA[Linking]]></category>
		<category><![CDATA[monorepo]]></category>
		<category><![CDATA[PerformanceOptimization]]></category>
		<category><![CDATA[Sharding]]></category>
		<category><![CDATA[SoftwareEngineering]]></category>
		<category><![CDATA[SpaceTimeTradeoff]]></category>
		<category><![CDATA[SpeedUp]]></category>
		<category><![CDATA[TestBinary]]></category>
		<category><![CDATA[TestWaitTime]]></category>
		<category><![CDATA[分布式CI]]></category>
		<category><![CDATA[分布式测试]]></category>
		<category><![CDATA[分片]]></category>
		<category><![CDATA[单体仓库]]></category>
		<category><![CDATA[性能优化]]></category>
		<category><![CDATA[提速]]></category>
		<category><![CDATA[构建瓶颈]]></category>
		<category><![CDATA[构建缓存]]></category>
		<category><![CDATA[测试二进制文件]]></category>
		<category><![CDATA[测试等待时间]]></category>
		<category><![CDATA[空间换时间]]></category>
		<category><![CDATA[缓存管理]]></category>
		<category><![CDATA[软件工程]]></category>
		<category><![CDATA[链接]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5837</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/02/05/brad-fitzpatrick-cachelink-reduce-go-test-wait-time 大家好，我是Tony Bai。 在维护大型 Go 单体仓库（Monorepo）时，你是否遇到过这样的场景：明明只是修改了测试的运行参数（比如 -run 的正则），或者在不同的 CI 节点上运行同一个包的测试，却发现 go test 依然在缓慢地执行“链接（Linking）”步骤？ 对于代码量巨大的项目，链接过程往往是构建链条中最耗时的一环。为了解决这一痛点，Go 社区领袖、Tailscale 核心开发者 Brad Fitzpatrick 近日提交了 #77349 提案，建议引入 -cachelink 标志。这一看似微小的改动，有望在分布式测试和重复执行场景下，显著“挤出”原本被浪费的等待时间。 被忽视的瓶颈：重复链接的代价 Go 的构建缓存（GOCACHE）机制已经非常高效，它能很好地缓存编译阶段的中间产物（.a 文件）。但是，当你运行 go test 时，工具链的最后一步——将所有依赖链接成一个可执行的测试二进制文件——通常是“一次性”的。 这意味着，即使你的代码没有任何变动，只要测试指令稍有变化（例如多次运行 go test 但指定不同的测试用例），Go 工具链往往会重新触发链接器。 # 第一次运行：链接 + 执行 $ go test -run=^TestFoo$ ./pkg/ # 第二次运行（代码未变）：依然触发重新链接 + 执行 $ go test -run=^TestBar$ ./pkg/ [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/brad-fitzpatrick-cachelink-reduce-go-test-wait-time-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/02/05/brad-fitzpatrick-cachelink-reduce-go-test-wait-time">本文永久链接</a> &#8211; https://tonybai.com/2026/02/05/brad-fitzpatrick-cachelink-reduce-go-test-wait-time</p>
<p>大家好，我是Tony Bai。</p>
<p>在维护大型 Go 单体仓库（Monorepo）时，你是否遇到过这样的场景：明明只是修改了测试的运行参数（比如 -run 的正则），或者在不同的 CI 节点上运行同一个包的测试，却发现 go test 依然在缓慢地执行“链接（Linking）”步骤？</p>
<p>对于代码量巨大的项目，链接过程往往是构建链条中最耗时的一环。为了解决这一痛点，Go 社区领袖、Tailscale 核心开发者 Brad Fitzpatrick 近日提交了 <a href="https://github.com/golang/go/issues/77349">#77349 提案</a>，建议引入 -cachelink 标志。这一看似微小的改动，有望在分布式测试和重复执行场景下，显著“挤出”原本被浪费的等待时间。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/the-ultimate-guide-to-go-module-qr.png" alt="" /></p>
<h2>被忽视的瓶颈：重复链接的代价</h2>
<p>Go 的构建缓存（GOCACHE）机制已经非常高效，它能很好地缓存编译阶段的中间产物（.a 文件）。但是，当你运行 go test 时，工具链的最后一步——将所有依赖链接成一个可执行的测试二进制文件——通常是“一次性”的。</p>
<p>这意味着，即使你的代码没有任何变动，只要测试指令稍有变化（例如多次运行 go test 但指定不同的测试用例），Go 工具链往往会重新触发链接器。</p>
<pre><code class="bash"># 第一次运行：链接 + 执行
$ go test -run=^TestFoo$ ./pkg/

# 第二次运行（代码未变）：依然触发重新链接 + 执行
$ go test -run=^TestBar$ ./pkg/
</code></pre>
<p>对于依赖项数以千计的大型项目，链接过程可能长达数秒甚至更久。在本地频繁调试或 CI 流水线中，这些重复的秒数累积起来就是巨大的时间浪费。</p>
<h2>Brad 的解法：-cachelink</h2>
<p>Brad Fitzpatrick 的提案非常直接：允许将链接器输出的最终测试二进制文件，也写入 GOCACHE。</p>
<p>通过显式开启 -cachelink，go test 的行为将发生变化：</p>
<ol>
<li>它会基于构建输入（代码、依赖、环境变量等）计算哈希。</li>
<li>如果发现 GOCACHE 中已经存在已链接好的测试二进制文件。</li>
<li><strong>直接跳过链接步骤</strong>，复用该文件进行测试。</li>
</ol>
<p>这样，上述例子中的第二次调用将瞬间启动，因为最耗时的构建步骤被完全省去了。</p>
<h2>为什么不做成默认行为？</h2>
<p>既然能提速，为什么不默认开启？Brad 在提案讨论中给出了专业的权衡分析：</p>
<p><strong>空间 vs. 时间</strong>。</p>
<p>测试二进制文件通常包含完整的符号表和调试信息，体积比普通的中间对象文件大得多。如果默认缓存所有测试二进制文件，开发者的磁盘空间（GOCACHE）会迅速膨胀。因此，这是一个<strong>以空间换时间</strong>的策略，更适合由开发者根据项目规模手动开启，或者在 CI 环境中配置。</p>
<h2>分布式 CI 的“加速器”</h2>
<p>该提案真正的杀手级应用场景是 分布式 CI 系统。</p>
<p>许多大厂<a href="https://tonybai.com/2025/03/04/deep-dive-into-gocacheprog-custom-extensions-for-go-build-cache/">使用 GOCACHEPROG</a> 来在构建集群间共享缓存。在典型的 CI 流程中，测试任务往往会被分片（Sharding）到数十台机器上并发执行。</p>
<ul>
<li>现状：每一台机器拉取源码后，都需要各自进行一次链接操作，浪费计算资源。</li>
<li>引入 -cachelink 后：第一台完成构建的机器会将二进制文件上传到共享缓存。后续几十台机器直接下载该文件并运行，全集群的链接成本降为“1”。</li>
</ul>
<h2>不仅是 go test -c</h2>
<p>有经验的开发者可能会问：<em>“我为什么不直接用 go test -c 手动编译成二进制文件，然后分发运行呢？”</em></p>
<p>Brad 指出，手动管理二进制文件会绕过 Go 原生的测试结果缓存。而 -cachelink 的精妙之处在于，它既复用了二进制文件，又保留了 go test 完整的缓存与输出管理体验。你不需要编写复杂的脚本来管理这些文件，一切依然由 go 命令自动处理。</p>
<h2>小结</h2>
<p>目前，该提案已进入活跃评审阶段，并有了初步的代码实现。对于深受“构建慢”和“测试慢”困扰的大型项目维护者来说，这无疑是一个值得期待的性能优化利器。我们有望在 Go 1.27 或后续版本中见证它的落地。</p>
<p>资料链接：https://github.com/golang/go/issues/77349</p>
<hr />
<p><strong>聊聊你的构建之苦</strong></p>
<p>链接时间正在成为你的“带薪摸鱼”理由吗？在你的项目中，go test 运行一次通常需要多久？你为了缩短测试反馈周期，还尝试过哪些黑科技（比如 GOCACHEPROG）？</p>
<p>欢迎在评论区分享你的实战经验或吐槽！让我们一起期待 -cachelink 的落地。</p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/02/05/brad-fitzpatrick-cachelink-reduce-go-test-wait-time/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>探索Go gcflags的使用模式与完整参数选项列表</title>
		<link>https://tonybai.com/2025/01/22/gcflags-options-list-and-usage/</link>
		<comments>https://tonybai.com/2025/01/22/gcflags-options-list-and-usage/#comments</comments>
		<pubDate>Wed, 22 Jan 2025 10:37:43 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[cmd]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[gcflags]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[gobuild]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[gotoolchain]]></category>
		<category><![CDATA[ldflags]]></category>
		<category><![CDATA[linker]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[package-pattern]]></category>
		<category><![CDATA[std]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[内联]]></category>
		<category><![CDATA[包]]></category>
		<category><![CDATA[包模式]]></category>
		<category><![CDATA[工具链]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[模块]]></category>
		<category><![CDATA[汇编]]></category>
		<category><![CDATA[编译]]></category>
		<category><![CDATA[链接]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4466</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/01/22/gcflags-options-list-and-usage Go build是Go开发中不可或缺的构建工具，其中-gcflags参数为开发者提供了向编译器传递额外选项的能力。然而，关于-gcflags的完整参数选项和使用模式，官方文档多有局限，很多开发者对此了解不深。本文将系统性地解析-gcflags的完整参数来源以及其结合包模式（package pattern）的使用方法，供大家参考。 注：本文主要以-gcflags为例，其实go build的-ldflags参数与-gcflags在使用方法上如出一辙，唯一不同的是ldflags是将参数传递给go链接器。 gcflags是Go构建工具的一个标志，用于向Go编译器 (go tool compile) 传递额外的编译参数。通过它，开发者可以调整编译行为，例如禁用优化、生成调试信息或输出反汇编代码等。 Go build文档中关于-gcflags的说明很短小精悍： $go help build ... ... -gcflags '[pattern=]arg list' arguments to pass on each go tool compile invocation. -ldflags '[pattern=]arg list' arguments to pass on each go tool link invocation. ... ... The -asmflags, -gccgoflags, -gcflags, and -ldflags flags accept a [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/gcflags-options-list-and-usage-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/01/22/gcflags-options-list-and-usage">本文永久链接</a> &#8211; https://tonybai.com/2025/01/22/gcflags-options-list-and-usage</p>
<p>Go build是Go开发中不可或缺的构建工具，其中-gcflags参数为开发者提供了向编译器传递额外选项的能力。然而，关于-gcflags的完整参数选项和使用模式，官方文档多有局限，很多开发者对此了解不深。本文将系统性地解析-gcflags的完整参数来源以及其结合包模式（package pattern）的使用方法，供大家参考。</p>
<blockquote>
<p>注：本文主要以-gcflags为例，其实go build的-ldflags参数与-gcflags在使用方法上如出一辙，唯一不同的是ldflags是将参数传递给go链接器。</p>
</blockquote>
<p>gcflags是Go构建工具的一个标志，用于向Go编译器 (go tool compile) 传递额外的编译参数。通过它，开发者可以调整编译行为，例如禁用优化、生成调试信息或输出反汇编代码等。</p>
<p>Go build文档中关于-gcflags的说明很短小精悍：</p>
<pre><code>$go help build
... ...
    -gcflags '[pattern=]arg list'
        arguments to pass on each go tool compile invocation.
    -ldflags '[pattern=]arg list'
        arguments to pass on each go tool link invocation.
... ...

The -asmflags, -gccgoflags, -gcflags, and -ldflags flags accept a space-separated list of arguments to pass to an underlying tool during the build. To embed spaces in an element in the list, surround it with either single or double quotes. The argument list may be preceded by a package pattern and an equal sign, which restricts the use of that argument list to the building of packages matching that pattern (see 'go help packages' for a description of package patterns). Without a pattern, the argument list applies only to the packages named on the command line. The flags may be repeated with different patterns in order to specify different arguments for different sets of packages. If a package matches patterns given in multiple flags, the latest match on the command line wins. For example, 'go build -gcflags=-S fmt' prints the disassembly only for package fmt, while 'go build -gcflags=all=-S fmt' prints the disassembly for fmt and all its dependencies.

... ...
</code></pre>
<p>多数Go初学者初次看到上述关于gcflags的说明，都无法知道到底有哪些arg可用以及究竟如何使用gcflags，而<a href="https://pkg.go.dev/cmd/go">Go cmd文档</a>中关于gcflags的内容也仅限于上述这些。</p>
<p>我将大家遇到的主要问题总结为下面两条：</p>
<ul>
<li>gcflags的完整参数选项列表在哪里可以找到？</li>
<li>gcflags的使用模式，尤其是其中的package pattern应该如何正确使用？</li>
</ul>
<p>如果你能正确回答上述两个问题，那你就基本掌握了gcflags的使用，<strong>大可不必继续往下看了</strong>。</p>
<p>否则，我们就一起分别看一下这两个问题该如何解答。</p>
<p>在哪里能<strong>查找到gcflags可用的全部参数选项呢</strong>？go help build不行，<a href="https://pkg.go.dev/cmd/go">go command的web文档</a>中没有！甚至<a href="https://pkg.go.dev/cmd/compile">Go tool compile的web文档</a>中列举的gcflag的参数列表也是不全的(或者说是文档没有及时同步最新的参数列表变化)，也许我们应该提一个issue给Go团队^_^。</p>
<p>远在天边近在眼前！下面命令可以让-gcflag可用的参数选项完整列表尽收眼底：</p>
<pre><code>$go tool compile -h
usage: compile [options] file.go...
  -%    debug non-static initializers
  -+    compiling runtime
  -B    disable bounds checking
  -C    disable printing of columns in error messages
  -D path
        set relative path for local imports
  -E    debug symbol export
  -I directory
        add directory to import search path
  -K    debug missing line numbers
  -L    also show actual source file names in error messages for positions affected by //line directives
  -N    disable optimizations
  -S    print assembly listing
  -V    print version and exit
  -W    debug parse tree after type checking
  -asan
        build code compatible with C/C++ address sanitizer
  -asmhdr file
        write assembly header to file
... ...
</code></pre>
<p>同样，如果你要查看-ldflags的完整参数选项列表，你可以使用下面命令：</p>
<pre><code>$go tool link -h
usage: link [options] main.o
  -B note
        add an ELF NT_GNU_BUILD_ID note when using ELF; use "gobuildid" to generate it from the Go build ID
  -E entry
        set entry symbol name
  -H type
        set header type
  -I linker
        use linker as ELF dynamic linker
  -L directory
        add specified directory to library path
  -R quantum
        set address rounding quantum (default -1)
  -T int
        set the start address of text symbols (default -1)
  -V    print version and exit
  -X definition
        add string value definition of the form importpath.name=value
  -a    no-op (deprecated)
  -asan
        enable ASan interface
... ...
</code></pre>
<p>到这里，我们得到了第一个问题的答案。</p>
<p>接下来，我们<strong>再来看第二个问题</strong>：-gcflags的使用模式。</p>
<p>根据go help build的输出，我们知道-gcflags的使用形式如下：</p>
<pre><code>-gcflags '[pattern=]arg list'
</code></pre>
<p>其中：</p>
<ul>
<li>[pattern=]（可选）：包模式(package pattern)，用于作用范围控制，即限定参数仅应用于特定的包。如果省略此部分，则参数仅适用于命令行中指定的包。</li>
<li>arg list：参数选项列表，多个参数以空格分隔。</li>
</ul>
<p>对包模式有很好地理解并非是使用好gcflags的必要条件。但在一些复杂项目中，我们可能会通过包模式精确控制调试和优化，在这种情况下，对包模式有深入理解还是大有裨益的。</p>
<p>包模式是一种通过匹配规则指定目标包的方式，常见的包模式有几下几种：</p>
<ul>
<li>./&#8230;：匹配当前目录及其所有子目录中的包。</li>
<li>/DIR/&#8230;：匹配/DIR及其子目录中的包。</li>
<li>cmd/&#8230;：匹配Go仓库中cmd目录下的所有命令包。</li>
<li>github.com/user/repo/&#8230;：匹配该github仓库中的所有包。</li>
<li>all：GOPATH模式下，匹配的是所有GOPATH路径中的包，Go module模式下，all匹配主模块及其所有依赖的包（包括测试依赖）。</li>
<li>std：仅匹配标准库包。</li>
<li>cmd：匹配Go仓库中的Go命令及其内部包(internal)。</li>
</ul>
<p>基于上述关于gcflags使用形式以及包模式的说明，我们举几个示例来直观理解一下gcflags的用法：</p>
<ul>
<li>对单个包设置参数</li>
</ul>
<pre><code>$go build -gcflags=-S fmt
</code></pre>
<p>上述命令中的参数-S仅作用于fmt包，显示其反汇编代码。</p>
<ul>
<li>对特定模式(比如all/std等)的包设置参数</li>
</ul>
<pre><code>$go build -gcflags='all=-N -l'
</code></pre>
<p>在Go module模式下，参数-N和-l应用于当前主模块所有包及其依赖，禁用优化和内联。</p>
<ul>
<li>对不同包模式设置不同参数</li>
</ul>
<pre><code>$go build -gcflags='fmt=-S' -gcflags='net/http=-N'
</code></pre>
<p>Go build命令行中可以<strong>多次使用-gcflags</strong>，上述命令中的第一个gcflags对fmt包启用反汇编输出(-S)。第二个gcflags对net/http包禁用优化(-N)。</p>
<ul>
<li>模式的优先级</li>
</ul>
<pre><code>$go build -gcflags='all=-N' -gcflags='fmt=-S'
</code></pre>
<p>像上面命令中，两个gcflags都匹配了fmt包，或者说两个gcflags的作用范围都包含了fmt包，这种情况下哪些参数会对fmt包生效呢？Go规定：当一个包匹配多个模式时，以最后一个匹配的参数为准。在这个例子中，fmt包将只应用-S参数，而其他包应用-N参数。</p>
<p>到这里，我们完成了对两个关于gcflags问题的回答!</p>
<p>最后小结一下：</p>
<ul>
<li>gcflags(以及-ldflags)是Go构建工具中的重要选项，能极大提升调试和优化效率。</li>
<li>gcflags的完整的参数选项需通过底层工具获取，即go tool compile -h和go tool link -h。</li>
<li>对包模式的灵活使用能够精确控制gcflags参数的作用范围，为复杂项目提供了更大的自由度。</li>
</ul>
<p>通过本篇文章，希望你能掌握查看gcflags完整参数列表的方法以及gcflags的使用模式，并在构建和调试Go项目时能更加得心应手。</p>
<hr />
<p><a href="https://public.zsxq.com/groups/51284458844544">Gopher部落知识星球</a>在2025年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。并且，2025年将在星球首发“Go陷阱与缺陷”和“Go原理课”专栏！此外，我们还会加强星友之间的交流和互动。欢迎大家踊跃提问，分享心得，讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾<br />
。让我相聚在Gopher部落，享受coding的快乐! 欢迎大家踊跃加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com</p>
<p>我的联系方式：</p>
<ul>
<li>微博(暂不可用)：https://weibo.com/bigwhite20xx</li>
<li>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily</li>
<li>Gopher Daily Feed订阅 &#8211; https://gopherdaily.tonybai.com/feed</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/01/22/gcflags-options-list-and-usage/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Go包构建：专家也未必了解的文件选择细节</title>
		<link>https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package/</link>
		<comments>https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package/#comments</comments>
		<pubDate>Wed, 20 Nov 2024 22:57:08 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[aix]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[BSD]]></category>
		<category><![CDATA[buildconstraints]]></category>
		<category><![CDATA[buildtag]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[Darwin]]></category>
		<category><![CDATA[fmt]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.23]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[gobuild]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[golist]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[GOOS]]></category>
		<category><![CDATA[Go语言精进之路]]></category>
		<category><![CDATA[illumos]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[JS]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[ReleaseTags]]></category>
		<category><![CDATA[toolchain]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[wasm]]></category>
		<category><![CDATA[包]]></category>
		<category><![CDATA[工具链]]></category>
		<category><![CDATA[构建约束]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[编译]]></category>
		<category><![CDATA[链接]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4405</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package 在Go语言开发中，包（package）是代码组织的基本单位，也是基本的构建单元。Go编译器会将每个包构建成一个目标文件(.a)，然后通过链接器将这些目标文件链接在一起，形成最终的可执行程序。 尽管Go包的构建过程看似简单，但实际上蕴含着许多值得深入了解的细节。例如，当我们执行go build命令时，Go编译器是如何选择需要编译的源文件的？你可能会回答：“不就是通过文件名中的ARCH和OS标识以及构建约束（build constraints）来选择的吗？” 虽然你的答案并没有错，但如果我进一步提出以下问题，你是否还能给出确切的答案呢？ 假设一个Go源文件使用了如下的构建约束： //go:build unix package foo // ... ... 在执行GOOS=android go build时，这个文件是否会被编译？如果执行的是GOOS=aix go build呢？而“unix”究竟包含了哪些操作系统？ 再进一步，当一个源文件的文件名中包含ARCH和操作系统标识，并且文件内容中也使用了构建约束时，Go编译器会如何处理这些信息的优先级？ 即使是经验丰富的Go专家，对于上述在包构建过程中涉及的文件选择细节，可能也只能给出模糊的答案。 在实际开发中，我们常常需要针对不同操作系统和架构编写特定的代码，这意味着灵活性与复杂性并存。Go的构建约束和文件名约定虽然为我们提供了灵活性，但也带来了额外的复杂性。理解这些规则不仅有助于优化构建过程，还能有效避免潜在的错误和不必要的麻烦。 在这篇文章中，我将与大家探讨Go包构建过程中源文件选择的细节，包括文件名中ARCH和os标识约定和构建约束的作用，以及二者的优先级处理问题。希望通过这些内容，帮助开发者更好地掌握Go语言的构建机制，从而提高开发效率。 为了更好地说明Go包构建时的文件选择逻辑，我们先从Go包构建的一些“表象”说起。 注：在本文中，我们将使用Go 1.17引入的新版build constraints写法：//go:build ，之前的// +build aix darwin dragonfly freebsd js,wasm &#8230;写法已经不再被推荐使用。如果你想对旧版build constraints写法有一个全面了解以便与新写法对比，推荐阅读我的《Go语言精进之路：从新手到高手的编程思想、方法和技巧》第2册。 1. 表象 在Go工程中，通常一个目录对应一个Go包，每个Go包下可以存在多个以.go为后缀的Go源文件，这些源文件只能具有唯一的包名（测试源文件除外），以标准库fmt包为例，它的目录下的源文件列表如下(以Go 1.23.0源码为例)： $ls $GOROOT/src/fmt doc.go export_test.go print.go stringer_example_test.go errors.go fmt_test.go scan.go stringer_test.go errors_test.go format.go scan_test.go [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-source-file-selection-details-when-building-package-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package">本文永久链接</a> &#8211; https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package</p>
<p>在Go语言开发中，<a href="https://tonybai.com/2023/06/18/go-package-design-guide/">包（package）是代码组织的基本单位</a>，也是基本的构建单元。Go编译器会将每个包构建成一个目标文件(.a)，然后通过链接器将这些目标文件链接在一起，形成最终的可执行程序。</p>
<p>尽管Go包的构建过程看似简单，但实际上蕴含着许多值得深入了解的细节。例如，当我们执行go build命令时，Go编译器是如何选择需要编译的源文件的？你可能会回答：“不就是通过文件名中的ARCH和OS标识以及构建约束（build constraints）来选择的吗？” 虽然你的答案并没有错，但如果我进一步提出以下问题，你是否还能给出确切的答案呢？</p>
<p>假设一个Go源文件使用了如下的构建约束：</p>
<pre><code>//go:build unix

package foo
// ... ...
</code></pre>
<p>在执行GOOS=android go build时，这个文件是否会被编译？如果执行的是GOOS=aix go build呢？而“unix”究竟包含了哪些操作系统？</p>
<p>再进一步，当一个源文件的文件名中包含ARCH和操作系统标识，并且文件内容中也使用了构建约束时，Go编译器会如何处理这些信息的优先级？</p>
<p>即使是经验丰富的Go专家，对于上述在包构建过程中涉及的文件选择细节，可能也只能给出模糊的答案。</p>
<p>在实际开发中，我们常常需要针对不同操作系统和架构编写特定的代码，这意味着灵活性与复杂性并存。Go的构建约束和文件名约定虽然为我们提供了灵活性，但也带来了额外的复杂性。理解这些规则不仅有助于优化构建过程，还能有效避免潜在的错误和不必要的麻烦。</p>
<p>在这篇文章中，我将与大家探讨<strong>Go包构建过程中源文件选择的细节</strong>，包括文件名中ARCH和os标识约定和构建约束的作用，以及二者的优先级处理问题。希望通过这些内容，帮助开发者更好地掌握Go语言的构建机制，从而提高开发效率。</p>
<p>为了更好地说明Go包构建时的文件选择逻辑，我们先从Go包构建的一些“表象”说起。</p>
<blockquote>
<p>注：在本文中，我们将使用<a href="https://tonybai.com/2021/08/17/some-changes-in-go-1-17">Go 1.17</a>引入的新版build constraints写法：//go:build ，之前的// +build aix darwin dragonfly freebsd js,wasm &#8230;写法已经不再被推荐使用。如果你想对旧版build constraints写法有一个全面了解以便与新写法对比，推荐阅读我的<a href="https://book.douban.com/subject/35720729/">《Go语言精进之路：从新手到高手的编程思想、方法和技巧》第2册</a>。</p>
</blockquote>
<h2>1. 表象</h2>
<p>在Go工程中，通常一个目录对应一个Go包，每个Go包下可以存在多个以.go为后缀的Go源文件，这些源文件只能具有唯一的包名（测试源文件除外），以标准库fmt包为例，它的目录下的源文件列表如下(以<a href="https://tonybai.com/2024/08/19/some-changes-in-go-1-23/">Go 1.23.0</a>源码为例)：</p>
<pre><code>$ls $GOROOT/src/fmt
doc.go              export_test.go          print.go            stringer_example_test.go
errors.go           fmt_test.go         scan.go             stringer_test.go
errors_test.go          format.go           scan_test.go
example_test.go         gostringer_example_test.go  state_test.go
</code></pre>
<p>在这些文件中，哪些最终进入到了fmt包的目标文件(fmt.a)中呢？<strong>贴心的Go工具链</strong>为我们提供了查看方法：</p>
<pre><code>$go list -f '{{.GoFiles}}' fmt
[doc.go errors.go format.go print.go scan.go]
</code></pre>
<p>对于独立于目标ARCH和OS的fmt包来说，其Go源文件的选择似乎要简单一些。我们看到，除了包测试文件(xxx&#95;test.go)，其他文件都被编译到了最终的fmt包中。</p>
<p>我们再来看一个与目标ARCH和OS相关性较高的net包。除去子目录，这个包目录下的Go源文件数量大约有220多个，但在<strong>macOS/amd64</strong>下通过go list查看最终进入net包目标文件的文件，大约只有几十个：</p>
<pre><code>$go list -f '{{.GoFiles}}' net
[addrselect.go cgo_darwin.go cgo_unix.go cgo_unix_syscall.go conf.go dial.go dnsclient.go dnsclient_unix.go dnsconfig.go dnsconfig_unix.go error_posix.go error_unix.go fd_posix.go fd_unix.go file.go file_unix.go hook.go hook_unix.go hosts.go interface.go interface_bsd.go interface_darwin.go ip.go iprawsock.go iprawsock_posix.go ipsock.go ipsock_posix.go lookup.go lookup_unix.go mac.go mptcpsock_stub.go net.go netcgo_off.go netgo_off.go nss.go parse.go pipe.go port.go port_unix.go rawconn.go rlimit_unix.go sendfile_unix_alt.go sock_bsd.go sock_posix.go sockaddr_posix.go sockopt_bsd.go sockopt_posix.go sockoptip_bsdvar.go sockoptip_posix.go splice_stub.go sys_cloexec.go tcpsock.go tcpsock_posix.go tcpsock_unix.go tcpsockopt_darwin.go tcpsockopt_posix.go udpsock.go udpsock_posix.go unixsock.go unixsock_posix.go unixsock_readmsg_cloexec.go writev_unix.go]
</code></pre>
<p>接下来，我们跳出Go标准库，来看一个自定义的示例：</p>
<pre><code>$tree -F buildconstraints/demo1
buildconstraints/demo1
├── foo/
│   ├── f1_android.go
│   ├── f2_linux.go
│   └── f3_darwin.go
└── go.mod

// buildconstraints/demo1/foo/f1_android.go 

//go:build linux

package foo

func F1() {
}

// buildconstraints/demo1/foo/f2_linux.go
//go:build android

package foo

func F2() {
}

// buildconstraints/demo1/foo/f3_darwin.go
//go:build android

package foo

func F3() {
}
</code></pre>
<p>在GOOS=android下构建buildconstraints/demo1/foo这个包，哪些文件会被选出来呢，看下面输出结果：</p>
<pre><code>$GOOS=android go list -f '{{.GoFiles}}' github.com/bigwhite/demo1/foo
[f1_android.go f2_linux.go]
</code></pre>
<p>如果说前两个示例还好理解，那这第三个示例很可能会让很多开发者觉得有些“发蒙”。 别急，上面三个示例都是表象，接下来，我们就来仔细探索一下Go构建时的文件选择机制。</p>
<h2>2. 文件选择机制</h2>
<p>Go包构建时选择源文件的机制还是蛮繁琐的，我们需要从源码入手梳理出其主要逻辑，在Go 1.23版本中，Go包构建过程源文件选择逻辑的代码位于\$GOROOT/src/go/build/build.go中，这个源文件有2k多行，不过不用担心，我这里会替你把主要调用逻辑梳理为下图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-source-file-selection-details-when-building-package-2.png" alt="" /></p>
<p>函数Import调用Default.Import去获取包的详细信息，信息用build.Package结构表示：</p>
<pre><code>// $GOROOT/src/go/build/build.go
// A Package describes the Go package found in a directory.
  type Package struct {
      Dir           string   // directory containing package sources
      Name          string   // package name
      ImportComment string   // path in import comment on package statement
      Doc           string   // documentation synopsis
      ImportPath    string   // import path of package ("" if unknown)
      Root          string   // root of Go tree where this package lives
      SrcRoot       string   // package source root directory ("" if unknown)
      PkgRoot       string   // package install root directory ("" if unknown)
      PkgTargetRoot string   // architecture dependent install root directory ("" if unknown)
      BinDir        string   // command install directory ("" if unknown)
      Goroot        bool     // package found in Go root
      PkgObj        string   // installed .a file
      AllTags       []string // tags that can influence file selection in this directory
      ConflictDir   string   // this directory shadows Dir in $GOPATH
      BinaryOnly    bool     // cannot be rebuilt from source (has //go:binary-only-package comment)

      // Source files
      GoFiles           []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
      ... ...
</code></pre>
<p>其中的GoFiles就是参与Go包编译的源文件列表。</p>
<p>Default是默认的上下文信息，包括构建所需的默认goenv中几个环境变量，比如GOARCH、GOOS等的值：</p>
<pre><code>// Default is the default Context for builds.
// It uses the GOARCH, GOOS, GOROOT, and GOPATH environment variables
// if set, or else the compiled code's GOARCH, GOOS, and GOROOT.
var Default Context = defaultContext()
</code></pre>
<p>Context的Import方法代码行数很多，对于要了解文件选择细节的我们来说，其中最重要的调用是Context的matchFile方法。</p>
<p>matchFile正是那个<strong>用于确定某个Go源文件是否应该被选入最终包文件中的方法</strong>。它内部的逻辑可以分为两个主要步骤。</p>
<p>第一步是<strong>调用Context的goodOSArchFile方法对Go源文件的名字进行判定</strong>，goodOSArchFile方法的判定也有两个子步骤：</p>
<ul>
<li>判断名字中的OS和ARCH是否在Go支持的OS和ARCH列表中</li>
</ul>
<p>当前Go支持的OS和ARCH在syslist.go文件中有定义：</p>
<pre><code>// $GOROOT/src/go/build/syslist.go

// knownArch is the list of past, present, and future known GOARCH values.
// Do not remove from this list, as it is used for filename matching.
var knownArch = map[string]bool{
    "386":         true,
    "amd64":       true,
    "amd64p32":    true,
    "arm":         true,
    "armbe":       true,
    "arm64":       true,
    "arm64be":     true,
    "loong64":     true,
    "mips":        true,
    "mipsle":      true,
    "mips64":      true,
    "mips64le":    true,
    "mips64p32":   true,
    "mips64p32le": true,
    "ppc":         true,
    "ppc64":       true,
    "ppc64le":     true,
    "riscv":       true,
    "riscv64":     true,
    "s390":        true,
    "s390x":       true,
    "sparc":       true,
    "sparc64":     true,
    "wasm":        true,
}

// knownOS is the list of past, present, and future known GOOS values.
// Do not remove from this list, as it is used for filename matching.
// If you add an entry to this list, look at unixOS, below.
var knownOS = map[string]bool{
    "aix":       true,
    "android":   true,
    "darwin":    true,
    "dragonfly": true,
    "freebsd":   true,
    "hurd":      true,
    "illumos":   true,
    "ios":       true,
    "js":        true,
    "linux":     true,
    "nacl":      true,
    "netbsd":    true,
    "openbsd":   true,
    "plan9":     true,
    "solaris":   true,
    "wasip1":    true,
    "windows":   true,
    "zos":       true,
}
</code></pre>
<p>我们也可以通过下面命令查看：</p>
<pre><code>$go tool dist list
aix/ppc64
android/386
android/amd64
android/arm
android/arm64
darwin/amd64
darwin/arm64
dragonfly/amd64
freebsd/386
freebsd/amd64
freebsd/arm
freebsd/arm64
freebsd/riscv64
illumos/amd64
ios/amd64
ios/arm64
js/wasm
linux/386
linux/amd64
linux/arm
linux/arm64
linux/loong64
linux/mips
linux/mips64
linux/mips64le
linux/mipsle
linux/ppc64
linux/ppc64le
linux/riscv64
linux/s390x
netbsd/386
netbsd/amd64
netbsd/arm
netbsd/arm64
openbsd/386
openbsd/amd64
openbsd/arm
openbsd/arm64
openbsd/ppc64
openbsd/riscv64
plan9/386
plan9/amd64
plan9/arm
solaris/amd64
wasip1/wasm
windows/386
windows/amd64
windows/arm
windows/arm64
</code></pre>
<blockquote>
<p>注：像sock_bsd.go、sock_posix.go这样的Go源文件，虽然它们的文件名中包含posix、bsd等字样，但这些文件实际上只是普通的Go源文件。其文件名本身并不会影响Go包在构建时选择文件的结果。</p>
</blockquote>
<ul>
<li>调用matchTag来判定该Go源文件名字中的OS和ARCH是否与当前上下文信息中的OS和ARCH匹配</li>
</ul>
<p>Go支持的源文件名组成格式如下：</p>
<pre><code>  //  name_$(GOOS).*
  //  name_$(GOARCH).*
  //  name_$(GOOS)_$(GOARCH).*
  //  name_$(GOOS)_test.*
  //  name_$(GOARCH)_test.*
  //  name_$(GOOS)_$(GOARCH)_test.*
</code></pre>
<p>不过这里有三个例外，即：</p>
<p>如果上下文中的GOOS=android，那么文件名字中OS值为linux的Go源文件也算是匹配的；</p>
<p>如果上下文中的GOOS=illumos，那么文件名字中OS值为solaris的Go源文件也算是匹配的；</p>
<p>如果上下文中的GOOS=ios，那么文件名字中OS值为darwin的Go源文件也算是匹配的。</p>
<p>还有一个特殊处理，那就是当文件名字中OS值为unix时，该源文件可以匹配以下上下文中GOOS的值：</p>
<pre><code>// $GOROOT/src/go/build/syslist.go

// unixOS is the set of GOOS values matched by the "unix" build tag.
// This is not used for filename matching.
// This list also appears in cmd/dist/build.go and
// cmd/go/internal/imports/build.go.
var unixOS = map[string]bool{
    "aix":       true,
    "android":   true,
    "darwin":    true,
    "dragonfly": true,
    "freebsd":   true,
    "hurd":      true,
    "illumos":   true,
    "ios":       true,
    "linux":     true,
    "netbsd":    true,
    "openbsd":   true,
    "solaris":   true,
}
</code></pre>
<p>这里面列出os都是所谓的“类Unix”操作系统。</p>
<p>如果goodOSArchFile方法返回文件名匹配成功，那么<strong>第二步就是调用Context的shouldBuild方法对Go源文件中的build constraints进行判定</strong>，这个判定过程也是调用matchTag完成的，因此规则与上面对matchTag的说明一致。如果判定match成功，那么该源文件将会被Go编译器编译到最终的Go包目标文件中去。</p>
<p>下面我们结合文章第一节“表象”中的那个自定义示例来判定一下为何最终会输出那个结果。</p>
<h2>3. 示例分析</h2>
<p>在buildconstraints/demo1/foo包目录中，一共有三个Go源文件：</p>
<pre><code>$tree -F foo
foo
├── f1_android.go
├── f2_linux.go
└── f3_darwin.go
</code></pre>
<p>注意：当前我的系统为<strong>darwin/amd64</strong>，但我们使用了GOOS=android的环境变量。我们顺着上一节梳理出来的文件选择判定的主逻辑，对着三个文件逐一过一遍。</p>
<ul>
<li>f1_android.go</li>
</ul>
<p>首先用goodOSArchFile判定文件名是否匹配。当GOOS=android时，文件名中的os为android，文件名匹配成功，</p>
<p>然后用shouldBuild判定文件中的build constraints是否匹配。该文件的约束为linux，在上面matchTag的三个例外规则里提到过，当GOOS=android时，如果build constraints是linux，是可以匹配的。</p>
<p>因此，f1_android.go将出现在最终编译文件列表中。</p>
<ul>
<li>f2_linux.go</li>
</ul>
<p>首先用goodOSArchFile判定文件名是否匹配。当GOOS=android时，文件名中的os为linux，linux显然在go支持的os列表中，并且根据matchTag的例外规则，当GOOS=android时，文件名中的os为linux时是可以匹配的。</p>
<p>然后用shouldBuild判定文件中的build constraints是否匹配。该文件的约束为android，与GOOS相同，可以匹配。</p>
<p>因此，f2_linux.go将出现在最终编译文件列表中。</p>
<ul>
<li>f3_darwin.go</li>
</ul>
<p>首先用goodOSArchFile判定文件名是否匹配。当GOOS=android时，文件名中的os为darwin，虽然darwin在go支持的os列表中，但darwin与GOOS=android并不匹配，因此在goodOSArchFile这步中，f3_darwin.go就被“淘汰”掉了！即便f3_darwin.go中的build constraints为android。</p>
<p>因此，f3_darwin.go不会出现在最终编译文件列表中。</p>
<p>如果再增加一个源文件f4_unix.go，其内容为：</p>
<pre><code>//go:build android

func F4() {
}
</code></pre>
<p>这个f4_unix.go是否会出现在最终的包编译文件列表中呢？这个作为思考题留给大家了，也欢迎你在评论区留言，说说你的思考结果。</p>
<h2>4. 小结</h2>
<p>在Go语言的开发过程中，包的构建是核心环节之一，而源文件的选择则是构建过程中一个复杂且关键的细节。本文深入探讨了Go编译器在执行go build命令时，如何根据文件名中的架构（ARCH）和操作系统（OS）标识，以及构建约束（build constraints），来选择需要编译的源文件。</p>
<p>通过具体示例，本文展示了不同文件名和构建约束如何影响最终的编译结果，并揭示了Go编译器处理这些信息的优先级。理解这些内部机制不仅能帮助开发者优化构建过程，还能有效避免潜在的错误。希望本文的分析能够给大家带去帮助。</p>
<blockquote>
<p>注：限于篇幅，本文仅针对包编译文件选择最复杂的部分进行的探索，而像ReleaseTags(比如: go1.21等)、cgo、&#95;test.go后缀等比较明显的约束并未涉及，同时对于新版build constraints的运算符组合也未提及，感兴趣的童鞋可以参考<a href="https://pkg.go.dev/cmd/go#hdr-Build_constraints">go build constraints</a>官方文档查阅。</p>
</blockquote>
<p>本文涉及的源码可以在<a href="https://github.com/bigwhite/experiments/tree/master/buildconstraints">这里</a>下载。</p>
<h2>5. 参考资料</h2>
<ul>
<li><a href="https://pkg.go.dev/cmd/go#hdr-Build_constraints">Go build constraints</a> &#8211; https://pkg.go.dev/cmd/go#hdr-Build_constraints</li>
<li><a href="https://github.com/golang/go/issues/25348">proposal: cmd/go: allow &amp;&amp; and || operators and parentheses in build tags</a> &#8211; https://github.com/golang/go/issues/25348</li>
<li><a href="https://go.googlesource.com/proposal/+/master/design/draft-gobuild.md">Bug-resistant build constraints — Draft Design</a> &#8211; https://go.googlesource.com/proposal/+/master/design/draft-gobuild.md</li>
<li><a href="https://github.com/golang/go/issues/41184">cmd/go: continue conversion to bug-resistant //go:build constraints</a> &#8211; https://github.com/golang/go/issues/41184</li>
<li><a href="https://go.dev/doc/go1.17">Go 1.17 release notes</a> &#8211; https://go.dev/doc/go1.17</li>
<li><a href="https://github.com/golang/go/issues/45454">cmd/go: provide build tags for architecture environment variables</a> &#8211; https://github.com/golang/go/issues/45454</li>
</ul>
<hr />
<p><a href="https://public.zsxq.com/groups/51284458844544">Gopher部落知识星球</a>在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时，我们也会加强代码质量和最佳实践的分享，包括如何编写简洁、可读、可测试的Go代码。此外，我们还会加强星友之间的交流和互动。欢迎大家踊跃提问，分享心得，讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落，享受coding的快乐! 欢迎大家踊跃加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com</p>
<p>我的联系方式：</p>
<ul>
<li>微博(暂不可用)：https://weibo.com/bigwhite20xx</li>
<li>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily</li>
<li>Gopher Daily Feed订阅 &#8211; https://gopherdaily.tonybai.com/feed</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2024, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>编译Go应用的黑盒挑战：无源码只有.a文件，你能搞定吗？</title>
		<link>https://tonybai.com/2023/08/30/how-to-build-with-only-archive-in-go/</link>
		<comments>https://tonybai.com/2023/08/30/how-to-build-with-only-archive-in-go/#comments</comments>
		<pubDate>Wed, 30 Aug 2023 12:58:12 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[archive]]></category>
		<category><![CDATA[buildmode]]></category>
		<category><![CDATA[Compile]]></category>
		<category><![CDATA[fmt]]></category>
		<category><![CDATA[gcflags]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-build]]></category>
		<category><![CDATA[go1.20]]></category>
		<category><![CDATA[gobuild]]></category>
		<category><![CDATA[GOCACHE]]></category>
		<category><![CDATA[GODEBUG]]></category>
		<category><![CDATA[goinstall]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[gorun]]></category>
		<category><![CDATA[gotool]]></category>
		<category><![CDATA[Go语言精进之路]]></category>
		<category><![CDATA[importcfg]]></category>
		<category><![CDATA[ldflags]]></category>
		<category><![CDATA[library]]></category>
		<category><![CDATA[link]]></category>
		<category><![CDATA[Make]]></category>
		<category><![CDATA[Makefile]]></category>
		<category><![CDATA[Module]]></category>
		<category><![CDATA[source]]></category>
		<category><![CDATA[std]]></category>
		<category><![CDATA[uber]]></category>
		<category><![CDATA[zap]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[编译]]></category>
		<category><![CDATA[链接]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3974</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2023/08/30/how-to-build-with-only-archive-in-go 上周末，一个Gopher在微信上与我交流了一个有关Go程序编译的问题。他的述求说起来也不复杂，那就是合作公司提供的API包仅包括golang archive(使用go build -buildmode=archive构建的.a文件)，没有Go包的源码。如何将这个.a链接到项目构建出的最终可执行程序中呢？ 对于C、C++、Java程序员来说，仅提供静态链接库或动态链接库(包括头文件)、jar包而不提供源码的API是十分寻常的。但对于Go来说，仅提供Go包的archive(.a)文件，而不提供Go包源码的情况却是极其不常见的。究其原因，简单来说就是go build或go run不支持！ 注：《Go语言精进之路vo1》一书的第16条“理解Go语言的包导入”对Go的编译过程和原理做了系统说明。 那么真的就没有方法实现没有source、仅基于.a文件的Go应用构建了吗？也不是。的确有一些hack的方法可以实现这点，本文就来从技术角度来探讨一下这些hack方法，但并不推荐使用！ 1. 回顾go build不支持”no source, only .a” 我们首先来回顾一下go build在”no source, only .a”下的表现。为此，我们先建立一个实验环境，其目录和文件布局如下： // 没有外部依赖的api包: foo $tree goarchive-nodeps goarchive-nodeps ├── Makefile ├── foo.a ├── foo.go └── go.mod $tree library library └── github.com └── bigwhite └── foo.a // 依赖foo包的app工程 $tree app-link-foo app-link-foo ├── Makefile ├── go.mod [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/how-to-build-with-only-archive-in-go-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2023/08/30/how-to-build-with-only-archive-in-go">本文永久链接</a> &#8211; https://tonybai.com/2023/08/30/how-to-build-with-only-archive-in-go</p>
<p>上周末，一个Gopher在微信上与我交流了一个有关Go程序编译的问题。他的述求说起来也不复杂，那就是合作公司提供的API包仅包括golang archive(使用go build -buildmode=archive构建的.a文件)，没有Go包的源码。如何将这个.a链接到项目构建出的最终可执行程序中呢？</p>
<p>对于C、C++、Java程序员来说，仅提供静态链接库或<a href="https://tonybai.com/2011/07/07/also-talk-about-shared-library-2/">动态链接库</a>(包括头文件)、jar包而不提供源码的API是十分寻常的。但对于Go来说，仅提供Go包的archive(.a)文件，而不提供Go包源码的情况却是极其不常见的。究其原因，简单来说就是<strong>go build或go run不支持</strong>！</p>
<blockquote>
<p>注：<a href="https://item.jd.com/13694000.html">《Go语言精进之路vo1》</a>一书的第16条“理解Go语言的包导入”对Go的编译过程和原理做了系统说明。</p>
</blockquote>
<p>那么真的就没有方法实现没有source、仅基于.a文件的Go应用构建了吗？也不是。的确有一些hack的方法可以实现这点，本文就来从技术角度来探讨一下这些hack方法，但并<strong>不推荐使用</strong>！</p>
<h2>1. 回顾go build不支持”no source, only .a”</h2>
<p>我们首先来回顾一下go build在”no source, only .a”下的表现。为此，我们先建立一个实验环境，其目录和文件布局如下：</p>
<pre><code>// 没有外部依赖的api包: foo

$tree goarchive-nodeps
goarchive-nodeps
├── Makefile
├── foo.a
├── foo.go
└── go.mod

$tree library
library
└── github.com
    └── bigwhite
        └── foo.a

// 依赖foo包的app工程
$tree app-link-foo
app-link-foo
├── Makefile
├── go.mod
└── main.go
</code></pre>
<p>这里我们已经将app-link-foo依赖的foo.a构建了出来(通过go build -buildmode=arhive)，并放入了library对应的目录下。</p>
<blockquote>
<p>注：可通过ar -x foo.a命令可以查看foo.a的组成。</p>
</blockquote>
<p>现在我们使用go build来构建app-link-foo工程：</p>
<pre><code>$cd app-link-foo
$go build
main.go:6:2: no required module provides package github.com/bigwhite/foo; to add it:
    go get github.com/bigwhite/foo
</code></pre>
<p>我们看到：go build会分析app-link-foo的依赖，并要求获取其依赖的foo包的代码，但我们无法满足go build这一要求！</p>
<p>有人可能会说：go build支持向go build支持向compiler和linker传递参数，是不是将foo.a的位置告知compiler和linker就可以了呢？我们来试试：</p>
<pre><code>$go build -x -v -gcflags '-I /Users/tonybai/Go/src/github.com/bigwhite/experiments/build-with-archive-only/library' -ldflags '-L /Users/tonybai/Go/src/github.com/bigwhite/experiments/build-with-archive-only/library' -o main main.go
main.go:6:2: no required module provides package github.com/bigwhite/foo; to add it:
    go get github.com/bigwhite/foo
make: *** [build] Error 1
</code></pre>
<p>我们看到：即便向go build传入gcflags和ldflags参数，告知了foo.a的搜索路径，go build依然报错，仍然提示需要foo包的源码！也就是说go build还没到调用go tool compile和go tool link那一步就开始报错了！</p>
<p>go build不支持在无源码情况下链接.a，那么我们<strong>只能绕过go build</strong>了！</p>
<h2>2. 绕过go bulid</h2>
<p>认真读过<a href="https://item.jd.com/13694000.html">《Go语言精进之路vo1》</a>一书的朋友都会知道：go build实质是调用go tool compile和go tool link两个命令来完成go应用的构建过程的，使用go build -x -v可以查看到go build的详细构建过程。</p>
<p>接下来，我们就来扮演一下”go build”，以手动的方式分别调用go tool compile和go tool link，看看是否能达到无需依赖包源码就能成功构建的目标。</p>
<p>我们以foo.a这个自身没有外部依赖的go archive为例，用手动方式构建一下app-link-foo这个工程。</p>
<p>首先确保通过-buildmode=archive构建出的foo.a被正确放入library/github.com/bigwhite下面。</p>
<p>接下来，我们通过go tool compile编译一下app-link-foo：</p>
<pre><code>$cd app-link-foo
$go tool compile -I /Users/tonybai/Go/src/github.com/bigwhite/experiments/build-with-archive-only/library -o main.o main.go
</code></pre>
<p>我们看到：手动执行go tool compile在通过-I传入依赖库的.a文件时是可以正常编译出object file(目标文件)的。go tool compile的手册告诉我们-I选项为compile提供了搜索包导入路径的目录：</p>
<pre><code>$go tool compile -h
  ... ...
  -I directory
        add directory to import search path
  ... ...
</code></pre>
<p>接下来我们用go tool link将main.o和foo.a链接在一起形成可执行二进制文件main：</p>
<pre><code>$cd app-link-foo
$go tool link -L /Users/tonybai/Go/src/github.com/bigwhite/experiments/build-with-archive-only/library -o main main.o
</code></pre>
<p>通过go tool link并在-L传入foo.a的链接路径的情况下，我们成功地将main.o和foo.a链接在了一起，形成了最终的可执行文件main。</p>
<p>go tool link的-L选项为link提供了搜索.a的路径：</p>
<pre><code>$go tool link -h
  ... ...
  -L directory
        add specified directory to library path
  ... ...
</code></pre>
<p>执行一下编译链接后的二进制文件main，我们将看到与预期相同的输出结果：</p>
<pre><code>$./main
invoke foo.Add
11
</code></pre>
<p>有些童鞋在执行go tool compile时可能会遇到找不到fmt.a或fmt.o的错误！这是因为Go 1.20版本及以后，Go安装包默认将不会在\$GOROOT/pkg/\$GOOS_\$GOARCH下面安装标准库的.a文件集合，这样go tool compile在这个路径下面就找不到app-link-foo所依赖的fmt.a：</p>
<pre><code>➜  /Users/tonybai/.bin/go1.20/pkg git:(master) ✗ $ls
darwin_amd64/    include/    tool/
➜  /Users/tonybai/.bin/go1.20/pkg git:(master) ✗ $cd darwin_amd64
➜  /Users/tonybai/.bin/go1.20/pkg/darwin_amd64 git:(master) ✗ $ls
</code></pre>
<p>解决方法也很简单，那就是手动执行下面命令编译和安装一下标准库的.a文件：</p>
<pre><code>$GODEBUG=installgoroot=all  go install std

➜  /Users/tonybai/.bin/go1.20/pkg/darwin_amd64 git:(master) ✗ $ls
archive/    database/    fmt.a        index/        mime/        plugin.a    strconv.a    time/
bufio.a        debug/        go/        internal/    mime.a        reflect/    strings.a    time.a
bytes.a        embed.a        hash/        io/        net/        reflect.a    sync/        unicode/
compress/    encoding/    hash.a        io.a        net.a        regexp/        sync.a        unicode.a
container/    encoding.a    html/        log/        os/        regexp.a    syscall.a    vendor/
context.a    errors.a    html.a        log.a        os.a        runtime/    testing/
crypto/        expvar.a    image/        math/        path/        runtime.a    testing.a
crypto.a    flag.a        image.a        math.a        path.a        sort.a        text/
</code></pre>
<p>这样无论是go tool compile，还是go tool link都会找到对应的标准库包了！</p>
<p>在这个例子中，foo.a仅依赖标准库，没有依赖第三方库，这样相对简单一些。通常合作伙伴提供的.a中的包都是依赖第三方的包的，下面我们就来看看如果.a有第三方依赖，上面的编译链接方法是否还能奏效！</p>
<h2>3. 要链接的.a文件自身也依赖第三方包</h2>
<p>goarchive-with-deps目录下的bar.a就是一个自身也依赖第三方包的go archive文件，它依赖的是uber的<a href="https://tonybai.com/2021/07/14/uber-zap-advanced-usage">zap日志包</a>以及zap包的依赖链，下面是bar的go.mod文件的内容：</p>
<pre><code>// goarchive-with-deps/go.mod

module github.com/bigwhite/bar

go 1.20

require go.uber.org/zap v1.25.0

require go.uber.org/multierr v1.10.0
</code></pre>
<p>我们先来安装app-link-foo的思路来编译链接一下app-link-bar：</p>
<pre><code>$cd app-link-bar
$make
go tool compile -I /Users/tonybai/Go/src/github.com/bigwhite/experiments/build-with-archive-only/library -o main.o main.go
go tool link -L /Users/tonybai/Go/src/github.com/bigwhite/experiments/build-with-archive-only/library -o main main.o
/Users/tonybai/.bin/go1.20/pkg/tool/darwin_amd64/link: cannot open file /Users/tonybai/.bin/go1.20/pkg/darwin_amd64/go.uber.org/zap.o: open /Users/tonybai/.bin/go1.20/pkg/darwin_amd64/go.uber.org/zap.o: no such file or directory
make: *** [all] Error 1
</code></pre>
<p>上面报的错误符合预期，因为zap.a尚没有放入build-with-archive-only/library下面。接下来我们基于uber zap的源码构建出一个zap.a并放入指定目录。bar.a依赖的uber zap的版本为v1.25.0，于是我们git clone一下uber zap，checkout出v1.25.0并执行构建：</p>
<pre><code>$cd go/src/go.uber.org/zap
$go build -o zap.a -buildmode=archive .
$cp zap.a /Users/tonybai/Go/src/github.com/bigwhite/experiments/build-with-archive-only/library/go.uber.org/
</code></pre>
<p>再来编译一下app-link-bar：</p>
<pre><code>$make
go tool compile -I /Users/tonybai/Go/src/github.com/bigwhite/experiments/build-with-archive-only/library -o main.o main.go
go tool link -L /Users/tonybai/Go/src/github.com/bigwhite/experiments/build-with-archive-only/library -o main main.o
/Users/tonybai/.bin/go1.20/pkg/tool/darwin_amd64/link: fingerprint mismatch: go.uber.org/zap has b259b1e07032c6d9, import from github.com/bigwhite/bar expecting 8118f660c835360a
make: *** [all] Error 1
</code></pre>
<p>我们看到go tool link报错，提示“fingerprint mismatch”。这个错误的意思是bar.a期望的zap包的指纹与我们提供的在Library目录下的zap包的指纹不一致！</p>
<p>我们重新用go build -v -x来看一下bar.a的构建过程：</p>
<pre><code>$go build -x -v  -o bar.a -buildmode=archive
WORK=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build3367014838
github.com/bigwhite/bar
mkdir -p $WORK/b001/
cat &gt;/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build3367014838/b001/importcfg &lt;&lt; 'EOF' # internal
# import config
packagefile fmt=/Users/tonybai/Library/Caches/go-build/d3/d307b52dabc7d78a8ff219fb472fbc0b0a600038f43cd4c737914f8ccbd2bd70-d
packagefile go.uber.org/zap=/Users/tonybai/Library/Caches/go-build/00/006d48e40c867a336b9ac622478c1e5bf914e6a5986f649a096ebede3d117bba-d
EOF
cd /Users/tonybai/Go/src/github.com/bigwhite/experiments/build-with-archive-only/goarchive-with-deps
/Users/tonybai/.bin/go1.20/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=&gt;" -p github.com/bigwhite/bar -lang=go1.20 -complete -buildid mIMNOXMPJH00mEpw6WVc/mIMNOXMPJH00mEpw6WVc -goversion go1.20 -c=4 -nolocalimports -importcfg $WORK/b001/importcfg -pack ./bar.go
/Users/tonybai/.bin/go1.20/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cp $WORK/b001/_pkg_.a /Users/tonybai/Library/Caches/go-build/60/604b60360d384c49eb9c030a2726f02588f54375748ce1421e334bedfda2af47-d # internal
mv $WORK/b001/_pkg_.a bar.a
rm -r $WORK/b001/
</code></pre>
<p>我们看到在编译bar.a的过程中，go tool compile用的是-importcfg来得到的go.uber.org/zap的位置，而从打印的内容来看，go.uber.org/zap指向的是go module cache中的某个文件：packagefile go.uber.org/zap=/Users/tonybai/Library/Caches/go-build/00/006d48e40c867a336b9ac622478c1e5bf914e6a5986f649a096ebede3d117bba-d。</p>
<p>那是不是在build app-link-bar时也使用这个同样的go.uber.org/zap就可以成功通过go tool link的过程呢？我们来试一下：</p>
<pre><code>$cd app-link-bar
$make build-with-importcfg
go tool compile -importcfg import.link -o main.o main.go
go tool link -importcfg import.link -o main main.o

$./main
invoke foo.Add
{"level":"info","ts":1693203940.0701509,"caller":"goarchive-with-deps/bar.go:14","msg":"invoke bar.Add\n"}
11
</code></pre>
<p>使用-importcfg的确成功的编译链接了app-link-bar，其执行结果也符合预期！注意：这里我们放弃了之前使用的-I和-L，即便应用-I和-L，在与-importcfg联合使用时，go tool compile和link也会以-importcfg的信息为准！</p>
<p>现在还有一个问题摆在面前，那就是上述命令行中的import.link这个文件的内容是啥，又是如何生成的呢？这里的import.link文件十分“巨大”，有500多行，其内容大致如下：</p>
<pre><code>// app-link-bar/import.link

# import config
packagefile internal/goos=/Users/tonybai/Library/Caches/go-build/fa/facce9766a2b3c19364ee55c509863694b205190c504a3831cde7c208bb09f37-d
packagefile vendor/golang.org/x/crypto/chacha20=/Users/tonybai/Library/Caches/go-build/e0/e042b43b78d3596cc00e544a40a13e8cd6b566eb8f59c2d47aeb0bbcbd52aa56-d
... ...

packagefile github.com/bigwhite/bar=/Users/tonybai/Go/src/github.com/bigwhite/experiments/build-with-archive-only/library/github.com/bigwhite/bar.a
packagefile go.uber.org/zap=/Users/tonybai/Library/Caches/go-build/00/006d48e40c867a336b9ac622478c1e5bf914e6a5986f649a096ebede3d117bba-d
packagefile go.uber.org/zap/zapcore=/Users/tonybai/Library/Caches/go-build/e0/e0d81701b5d15628ce5bf174e5c1b7482c13ac3a3c868e9b054da8b1596eaace-d
packagefile go.uber.org/zap/internal/pool=/Users/tonybai/Library/Caches/go-build/bf/bfa96ebb89429b870e2c50c990c1945384e50d10ba354a3dab2b995a813c56a3-d
packagefile go.uber.org/zap/internal=/Users/tonybai/Library/Caches/go-build/33/33cb66c30939b8be915ddc1e237a04688f52c492d3ae58bfbc6196fff8b6b2b5-d
packagefile go.uber.org/zap/internal/bufferpool=/Users/tonybai/Library/Caches/go-build/68/68e58338a5acd96ee1733de78547720f26f4e13d8333defbc00099ac8560c8e8-d
packagefile go.uber.org/zap/buffer=/Users/tonybai/Library/Caches/go-build/7b/7bf00a1d4a69ddb1712366f45451890f3205b58ba49627ed4254acd9b0938ef8-d
packagefile go.uber.org/multierr=/Users/tonybai/Library/Caches/go-build/e7/e7cc278d56fc8262d9cf9de840a04aa675c75f8ac148e955c1ae9950c58c8034-d
packagefile go.uber.org/zap/internal/exit=/Users/tonybai/Library/Caches/go-build/18/187b2b490c810f37c3700132fba12b805e74bd3c59303972bcf74894a63de604-d
packagefile go.uber.org/zap/internal/color=/Users/tonybai/Library/Caches/go-build/e4/e419c93bea7ff2782b2047cf9e7ad37b07cf4a5a5b7f361bf968730e107a495b-d
</code></pre>
<p>这里包含了编译链接app-link-bar是依赖的标准库包、bar.a以及bar包依赖的所有第三方包的实际包.a文件的位置，显然这里用的大多数都是go module cache中的包缓存。</p>
<p>那么这个import.link如何得到呢？Go在golang.org/x/tools包中有一个<a href="https://raw.githubusercontent.com/golang/tools/master/internal/goroot/importcfg.go">importcfg.go文件</a>，基于该文件中的Importcfg函数可以获取标准库相关所有包的package link信息。我将该文件放在了build-with-archive-only/importcfg下了，大家可以自行取用。</p>
<p>importcfg生成了大部分package link，但仍会有一些bar.a依赖的第三方的包的link没有着落，go tool link在链接时会报错，根据报错信息中提供的包导入路径信息，比如：找不到go.uber.org/zap/internal/exit、go.uber.org/zap/internal/color，我们可以利用下面go list命令找到这些包的在本地go module cache中的link位置：</p>
<pre><code>$go list -export -e -f "{{.ImportPath}} {{.Export}}" go.uber.org/zap/internal/exit go.uber.org/zap/internal/color
go.uber.org/zap/internal/exit /Users/tonybai/Library/Caches/go-build/18/187b2b490c810f37c3700132fba12b805e74bd3c59303972bcf74894a63de604-d
go.uber.org/zap/internal/color /Users/tonybai/Library/Caches/go-build/e4/e419c93bea7ff2782b2047cf9e7ad37b07cf4a5a5b7f361bf968730e107a495b-d
</code></pre>
<p>然后可以手工将这些信息copy到import.link中。import.link文件就是在这样自动化+手工的过程中生成的（当然你完全可以自己编写一个工具，获取app-link-bar所需的所有package的link信息）。</p>
<h2>4. 小结</h2>
<p>到这里，我们通过hack的方法实现了在没有源码只有.a文件情况下的可执行程序的编译。</p>
<p>不过上述仅仅是纯技术上的探索，并非标准答案，也更非理想的答案。经过上述探索后，更巩固了我的观点：<strong>不要仅使用.a来构建go应用</strong>。</p>
<p>但非要这么做，如果你是.a的提供方，考虑fingerprint mismatch的情况，你估计要考虑在提供.a的同时，还要提供import.link、你构建.a时所有用到的go module cache的副本，并提供安装这些副本到目标主机上的脚本。这样你的.a用户才可能使用相同的依赖版本完成对.a文件的链接过程。</p>
<p>本文试验的代码都是在<a href="https://tonybai.com/2023/02/08/some-changes-in-go-1-20/">Go 1.20版本</a>下编译链接的。如果编译.a的Go版本与编译链接可执行文件的Go版本不同，是否会失败呢？这个问题就当做作业留个大家去探索了！</p>
<p>本文涉及的代码可以从<a href="https://github.com/bigwhite/experiments/blob/master/build-with-archive-only">这里</a>下载。</p>
<hr />
<p><a href="https://public.zsxq.com/groups/51284458844544">“Gopher部落”知识星球</a>旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！2023年，Gopher部落将进一步聚焦于如何编写雅、地道、可读、可测试的Go代码，关注代码质量并深入理解Go核心技术，并继续加强与星友的互动。欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com</p>
<p>我的联系方式：</p>
<ul>
<li>微博(暂不可用)：https://weibo.com/bigwhite20xx</li>
<li>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2023, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2023/08/30/how-to-build-with-only-archive-in-go/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go是否支持增量构建？我来告诉你！</title>
		<link>https://tonybai.com/2022/03/21/go-native-support-incremental-build/</link>
		<comments>https://tonybai.com/2022/03/21/go-native-support-incremental-build/#comments</comments>
		<pubDate>Mon, 21 Mar 2022 13:40:24 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[build-cache]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[CMake]]></category>
		<category><![CDATA[C语言]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-build]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[GOCACHE]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Go语言第一课]]></category>
		<category><![CDATA[Go语言精进之路]]></category>
		<category><![CDATA[import]]></category>
		<category><![CDATA[incremental-build]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JVM]]></category>
		<category><![CDATA[link]]></category>
		<category><![CDATA[linker]]></category>
		<category><![CDATA[Make]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[包]]></category>
		<category><![CDATA[增量构建]]></category>
		<category><![CDATA[目标文件]]></category>
		<category><![CDATA[链接]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3472</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2022/03/21/go-native-support-incremental-build Go语言以编译速度快闻名于码农界。这缘于Go在设计之初就选择抛弃其祖辈C语言的头文件包含机制，选择了以包(package)作为基本编译单元。Go语言的这种以包为基本构建单元的构建模型使得依赖分析变得十分简单，避免了C语言那种通过头文件分析依赖的巨大开销。在我的《Go语言精进之路》一书中，我也给出了Go编译速度快的三点具体原因，包括： Go要求每个源文件在开头处显式地列出所有依赖的包导入，这样Go编译器不必读取和处理整个文件就可以确定其依赖的包列表； Go要求包之间不能存在循环依赖，这样一个包的依赖关系便形成了一张有向无环图。由于无环，包可以被单独编译，也可以并行编译； 已编译的Go包对应的目标文件(file_name.o或package_name.a)中不仅记录了该包本身的导出符号信息，还记录了其所依赖包的导出符号信息。这样，Go编译器在编译某包P时，针对P依赖的每个包导入(比如：导入包Q)，只需读取一个目标文件即可（比如：Q包编译成的目标文件，该目标文件中已经包含了Q包的依赖包的导出信息），而无需再读取其他文件中的信息了。 不过近期有读者问到：Go是否支持增量构建(incremental build)？这是一个好问题，书中并未提到这方面内容。但语言编译器编译速度再快，如果没有增量构建，构建大型代码工程的时间也不会短。那么Go是否支持增量构建呢？在这篇文章中，我就来告诉你答案。 1. 什么是增量构建？ 提到构建(build)，我们通常所指的是静态编译型语言，比如：C、Go、Java等。Python等动态语言不需要构建，直接用解释器run即可。每种静态编译型编程语言通常都有自己的编译单元，比如Go的编译单元为一个package，c/c++的编译单元是一个c/c++源文件，java则以class为编译单元等。静态语言的构建就是将编译单元的源码编译为对应的中间目标文件(.o/.a/.class)，然后将这些目标文件通过链接器链接在一起形成最终可执行文件的过程。不过Java除外，java在编译过程没有链接环节，jvm加载class文件时会有一个链接过程。 那么问题来了：每次项目构建，项目中的所有源文件都要被重新编译一遍而形成新的中间目标文件吗？如果我只改动了一个源文件中的几行代码，项目中的其他源文件也要跟着重新编译一遍么？我们显然不希望这样浪费算力、浪费开发者时间的事情发生！ 为了避免这样的事情发生，“增量构建”被提了出来。简单来说就是每次构建仅重新编译变动了的编译单元以及对这些变动的编译单元有依赖的编译单元的源码！ 上图展示了一个项目的编译单元的依赖关系。当开发人员修改了编译单元C的源码后，如果该项目支持增量编译，那么再次构建这个项目时，仅变动的编译单元C的源码以及直接依赖C的B、间接依赖C的A会被重新编译，而D、E两个编译单元不会被重新编译，其中间目标文件会被链接器重用。 对增量编译的支持，有两种策略：一种是编程语言的编译器自身就支持，比如Rust。另外一种则是语言自身编译器不支持，需要通过第三方项目构建管理工具协助实现，最典型的就是C/C++与Make/CMake的组合。 那么Go语言的编译器go compiler(gc)是否本身就支持增量编译呢？是否需要通过外部项目构建管理工具协助呢？我们继续往下看。 2. 通过示例看Go是否支持增量构建 Go语言提供了统一的go工具链，在这个工具链中用于构建的命令只有一个，那就是go build。下面我们就通过一系列实例来验证一下Go是否原生支持增量构建。 该示例的项目结构如下： demo1/ ├── go.mod ├── main.go ├── pkg1/ │   └── pkg1.go └── pkg2/ └── pkg2.go a) 首次构建 在这个项目中，顶层的module为demo1，main包依赖pkg1包与pkg2包。我们先通过go build命令对该项目做首次构建，我们通过命令行参数-x -v输出构建的详细日志，以便于我们分析： $go build -x -v ### 笔者注：创建临时目录用于此次构建 WORK=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build1907281507 cd /Users/tonybai/test/go git status --porcelain cd [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-native-support-incremental-build-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2022/03/21/go-native-support-incremental-build">本文永久链接</a> &#8211; https://tonybai.com/2022/03/21/go-native-support-incremental-build</p>
<p>Go语言<strong>以编译速度快闻名于码农界</strong>。这缘于Go在设计之初就选择抛弃其祖辈C语言的头文件包含机制，选择了以包(package)作为基本编译单元。Go语言的这种以包为基本构建单元的构建模型使得依赖分析变得十分简单，避免了C语言那种通过头文件分析依赖的巨大开销。在我的<a href="https://mp.weixin.qq.com/s/h-lsWUbKRPnzFLINub5uYw">《Go语言精进之路》</a>一书中，我也给出了Go编译速度快的三点具体原因，包括：</p>
<ul>
<li>Go要求每个源文件在开头处显式地列出所有依赖的包导入，这样Go编译器不必读取和处理整个文件就可以确定其依赖的包列表；</li>
<li>Go要求包之间不能存在循环依赖，这样一个包的依赖关系便形成了一张有向无环图。由于无环，包可以被单独编译，也可以并行编译；</li>
<li>已编译的Go包对应的目标文件(file_name.o或package_name.a)中不仅记录了该包本身的导出符号信息，还记录了其所依赖包的导出符号信息。这样，Go编译器在编译某包P时，针对P依赖的每个包导入(比如：导入包Q)，只需读取一个目标文件即可（比如：Q包编译成的目标文件，该目标文件中已经包含了Q包的依赖包的导出信息），而无需再读取其他文件中的信息了。 </li>
</ul>
<p>不过近期有读者问到：<strong>Go是否支持增量构建(incremental build)</strong>？这是一个好问题，书中并未提到这方面内容。但语言编译器编译速度再快，如果没有增量构建，构建大型代码工程的时间也不会短。那么Go是否支持增量构建呢？在这篇文章中，我就来告诉你答案。</p>
<h3>1. 什么是增量构建？</h3>
<p>提到构建(build)，我们通常所指的是静态编译型语言，比如：C、Go、Java等。Python等动态语言不需要构建，直接用解释器run即可。每种静态编译型编程语言通常都有自己的编译单元，比如Go的编译单元为一个package，c/c++的编译单元是一个c/c++源文件，java则以class为编译单元等。<strong>静态语言的构建就是将编译单元的源码编译为对应的中间目标文件(.o/.a/.class)，然后将这些目标文件通过链接器链接在一起形成最终可执行文件的过程</strong>。不过Java除外，java在编译过程没有链接环节，jvm加载class文件时会有一个链接过程。</p>
<p>那么问题来了：每次项目构建，项目中的所有源文件都要被重新编译一遍而形成新的中间目标文件吗？如果我只改动了一个源文件中的几行代码，项目中的其他源文件也要跟着重新编译一遍么？我们显然不希望这样浪费算力、浪费开发者时间的事情发生！</p>
<p>为了避免这样的事情发生，“增量构建”被提了出来。简单来说就是<strong>每次构建仅重新编译变动了的编译单元以及对这些变动的编译单元有依赖的编译单元的源码</strong>！</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-native-support-incremental-build-2.png" alt="" /></p>
<p>上图展示了一个项目的编译单元的依赖关系。当开发人员修改了编译单元C的源码后，如果该项目支持增量编译，那么再次构建这个项目时，仅变动的编译单元C的源码以及直接依赖C的B、间接依赖C的A会被重新编译，而D、E两个编译单元不会被重新编译，其中间目标文件会被链接器重用。</p>
<p>对增量编译的支持，有两种策略：一种是编程语言的编译器自身就支持，比如Rust。另外一种则是语言自身编译器不支持，需要通过第三方项目构建管理工具协助实现，最典型的就是C/C++与Make/CMake的组合。</p>
<p>那么Go语言的编译器go compiler(gc)是否本身就支持增量编译呢？是否需要通过外部项目构建管理工具协助呢？我们继续往下看。</p>
<h3>2. 通过示例看Go是否支持增量构建</h3>
<p>Go语言提供了统一的go工具链，在这个工具链中用于构建的命令只有一个，那就是go build。下面我们就通过一系列实例来验证一下Go是否原生支持增量构建。</p>
<p>该示例的项目结构如下：</p>
<pre><code>demo1/
├── go.mod
├── main.go
├── pkg1/
│   └── pkg1.go
└── pkg2/
    └── pkg2.go
</code></pre>
<h4>a) 首次构建</h4>
<p>在这个项目中，顶层的module为demo1，main包依赖pkg1包与pkg2包。我们先通过go build命令对该项目做首次构建，我们通过命令行参数-x -v输出构建的详细日志，以便于我们分析：</p>
<pre><code>$go build -x -v 

### 笔者注：创建临时目录用于此次构建

WORK=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build1907281507
cd /Users/tonybai/test/go
git status --porcelain
cd /Users/tonybai/test/go
git show -s --no-show-signature --format=%H:%ct
demo1/pkg2
demo1/pkg1
mkdir -p $WORK/b003/
mkdir -p $WORK/b002/
cat &gt;$WORK/b003/importcfg &lt;&lt; 'EOF' # internal
# import config
EOF

### 笔者注：编译demo1/pkg1和demo1/pkg2包

cd /Users/tonybai/test/go/incremental-build/demo1
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b003/_pkg_.a -trimpath "$WORK/b003=&gt;" -p demo1/pkg2 -lang=go1.18 -complete -buildid 4ixic55Fpug9OyS7vsew/4ixic55Fpug9OyS7vsew -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b003/importcfg -pack ./pkg2/pkg2.go
cat &gt;$WORK/b002/importcfg &lt;&lt; 'EOF' # internal
# import config
EOF
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b002/_pkg_.a -trimpath "$WORK/b002=&gt;" -p demo1/pkg1 -lang=go1.18 -complete -buildid jgyT36iBuu6-dYIzK5SD/jgyT36iBuu6-dYIzK5SD -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b002/importcfg -pack ./pkg1/pkg1.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b003/_pkg_.a # internal
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b002/_pkg_.a # internal

### 笔者注：将编译demo1/pkg1和demo1/pkg2包得到的目标文件缓存到gocache中

cp $WORK/b003/_pkg_.a /Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d # internal
cp $WORK/b002/_pkg_.a /Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d # internal

runtime
mkdir -p $WORK/b004/
cat &gt;$WORK/b004/go_asm.h &lt;&lt; 'EOF' # internal
EOF
cd /Users/tonybai/.bin/go1.18rc1/src/runtime
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -gensymabis -o $WORK/b004/symabis ./asm.s ./asm_amd64.s ./duff_amd64.s ./memclr_amd64.s ./memmove_amd64.s ./preempt_amd64.s ./rt0_darwin_amd64.s ./sys_darwin_amd64.s
cat &gt;$WORK/b004/importcfg &lt;&lt; 'EOF' # internal
# import config
packagefile internal/abi=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/abi.a
packagefile internal/bytealg=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/bytealg.a
packagefile internal/cpu=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/cpu.a
packagefile internal/goarch=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goarch.a
packagefile internal/goexperiment=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goexperiment.a
packagefile internal/goos=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goos.a
packagefile runtime/internal/atomic=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/atomic.a
packagefile runtime/internal/math=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/math.a
packagefile runtime/internal/sys=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/sys.a
EOF

### 笔者注：由于笔者在执行build前使用go clean -cache将所有cache清空，因此这里go build会重新编译Go运行时库并缓存到gocache中

cd /Users/tonybai/test/go/incremental-build/demo1
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b004/_pkg_.a -trimpath "$WORK/b004=&gt;" -p runtime -std -+ -buildid cjuCOFTfsWmpOEnkAPsP/cjuCOFTfsWmpOEnkAPsP -goversion go1.18rc1 -symabis $WORK/b004/symabis -c=4 -nolocalimports -importcfg $WORK/b004/importcfg -pack -asmhdr $WORK/b004/go_asm.h /Users/tonybai/.bin/go1.18rc1/src/runtime/alg.go /Users/tonybai/.bin/go1.18rc1/src/runtime/asan0.go /Users/tonybai/.bin/go1.18rc1/src/runtime/atomic_pointer.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cgo.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cgocall.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cgocallback.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cgocheck.go /Users/tonybai/.bin/go1.18rc1/src/runtime/chan.go /Users/tonybai/.bin/go1.18rc1/src/runtime/checkptr.go /Users/tonybai/.bin/go1.18rc1/src/runtime/compiler.go /Users/tonybai/.bin/go1.18rc1/src/runtime/complex.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cpuflags.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cpuflags_amd64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cpuprof.go /Users/tonybai/.bin/go1.18rc1/src/runtime/cputicks.go /Users/tonybai/.bin/go1.18rc1/src/runtime/debug.go /Users/tonybai/.bin/go1.18rc1/src/runtime/debugcall.go /Users/tonybai/.bin/go1.18rc1/src/runtime/debuglog.go /Users/tonybai/.bin/go1.18rc1/src/runtime/debuglog_off.go /Users/tonybai/.bin/go1.18rc1/src/runtime/defs_darwin_amd64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/env_posix.go /Users/tonybai/.bin/go1.18rc1/src/runtime/error.go /Users/tonybai/.bin/go1.18rc1/src/runtime/extern.go /Users/tonybai/.bin/go1.18rc1/src/runtime/fastlog2.go /Users/tonybai/.bin/go1.18rc1/src/runtime/fastlog2table.go /Users/tonybai/.bin/go1.18rc1/src/runtime/float.go /Users/tonybai/.bin/go1.18rc1/src/runtime/hash64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/heapdump.go /Users/tonybai/.bin/go1.18rc1/src/runtime/histogram.go /Users/tonybai/.bin/go1.18rc1/src/runtime/iface.go /Users/tonybai/.bin/go1.18rc1/src/runtime/lfstack.go /Users/tonybai/.bin/go1.18rc1/src/runtime/lfstack_64bit.go /Users/tonybai/.bin/go1.18rc1/src/runtime/lock_sema.go /Users/tonybai/.bin/go1.18rc1/src/runtime/lockrank.go /Users/tonybai/.bin/go1.18rc1/src/runtime/lockrank_off.go /Users/tonybai/.bin/go1.18rc1/src/runtime/malloc.go /Users/tonybai/.bin/go1.18rc1/src/runtime/map.go /Users/tonybai/.bin/go1.18rc1/src/runtime/map_fast32.go /Users/tonybai/.bin/go1.18rc1/src/runtime/map_fast64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/map_faststr.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mbarrier.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mbitmap.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mcache.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mcentral.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mcheckmark.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mem_darwin.go /Users/tonybai/.bin/go1.18rc1/src/runtime/metrics.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mfinal.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mfixalloc.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mgc.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mgcmark.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mgcpacer.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mgcscavenge.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mgcstack.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mgcsweep.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mgcwork.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mheap.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mpagealloc.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mpagealloc_64bit.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mpagecache.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mpallocbits.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mprof.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mranges.go /Users/tonybai/.bin/go1.18rc1/src/runtime/msan0.go /Users/tonybai/.bin/go1.18rc1/src/runtime/msize.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mspanset.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mstats.go /Users/tonybai/.bin/go1.18rc1/src/runtime/mwbbuf.go /Users/tonybai/.bin/go1.18rc1/src/runtime/nbpipe_pipe.go /Users/tonybai/.bin/go1.18rc1/src/runtime/netpoll.go /Users/tonybai/.bin/go1.18rc1/src/runtime/netpoll_kqueue.go /Users/tonybai/.bin/go1.18rc1/src/runtime/os_darwin.go /Users/tonybai/.bin/go1.18rc1/src/runtime/os_nonopenbsd.go /Users/tonybai/.bin/go1.18rc1/src/runtime/panic.go /Users/tonybai/.bin/go1.18rc1/src/runtime/plugin.go /Users/tonybai/.bin/go1.18rc1/src/runtime/preempt.go /Users/tonybai/.bin/go1.18rc1/src/runtime/preempt_nonwindows.go /Users/tonybai/.bin/go1.18rc1/src/runtime/print.go /Users/tonybai/.bin/go1.18rc1/src/runtime/proc.go /Users/tonybai/.bin/go1.18rc1/src/runtime/profbuf.go /Users/tonybai/.bin/go1.18rc1/src/runtime/proflabel.go /Users/tonybai/.bin/go1.18rc1/src/runtime/race0.go /Users/tonybai/.bin/go1.18rc1/src/runtime/rdebug.go /Users/tonybai/.bin/go1.18rc1/src/runtime/relax_stub.go /Users/tonybai/.bin/go1.18rc1/src/runtime/runtime.go /Users/tonybai/.bin/go1.18rc1/src/runtime/runtime1.go /Users/tonybai/.bin/go1.18rc1/src/runtime/runtime2.go /Users/tonybai/.bin/go1.18rc1/src/runtime/rwmutex.go /Users/tonybai/.bin/go1.18rc1/src/runtime/select.go /Users/tonybai/.bin/go1.18rc1/src/runtime/sema.go /Users/tonybai/.bin/go1.18rc1/src/runtime/signal_amd64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/signal_darwin.go /Users/tonybai/.bin/go1.18rc1/src/runtime/signal_darwin_amd64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/signal_unix.go /Users/tonybai/.bin/go1.18rc1/src/runtime/sigqueue.go /Users/tonybai/.bin/go1.18rc1/src/runtime/sizeclasses.go /Users/tonybai/.bin/go1.18rc1/src/runtime/slice.go /Users/tonybai/.bin/go1.18rc1/src/runtime/softfloat64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/stack.go /Users/tonybai/.bin/go1.18rc1/src/runtime/string.go /Users/tonybai/.bin/go1.18rc1/src/runtime/stubs.go /Users/tonybai/.bin/go1.18rc1/src/runtime/stubs_amd64.go /Users/tonybai/.bin/go1.18rc1/src/runtime/stubs_nonlinux.go /Users/tonybai/.bin/go1.18rc1/src/runtime/symtab.go /Users/tonybai/.bin/go1.18rc1/src/runtime/sys_darwin.go /Users/tonybai/.bin/go1.18rc1/src/runtime/sys_libc.go /Users/tonybai/.bin/go1.18rc1/src/runtime/sys_nonppc64x.go /Users/tonybai/.bin/go1.18rc1/src/runtime/sys_x86.go /Users/tonybai/.bin/go1.18rc1/src/runtime/time.go /Users/tonybai/.bin/go1.18rc1/src/runtime/time_nofake.go /Users/tonybai/.bin/go1.18rc1/src/runtime/timestub.go /Users/tonybai/.bin/go1.18rc1/src/runtime/tls_stub.go /Users/tonybai/.bin/go1.18rc1/src/runtime/trace.go /Users/tonybai/.bin/go1.18rc1/src/runtime/traceback.go /Users/tonybai/.bin/go1.18rc1/src/runtime/type.go /Users/tonybai/.bin/go1.18rc1/src/runtime/typekind.go /Users/tonybai/.bin/go1.18rc1/src/runtime/utf8.go /Users/tonybai/.bin/go1.18rc1/src/runtime/vdso_in_none.go /Users/tonybai/.bin/go1.18rc1/src/runtime/write_err.go
cd /Users/tonybai/.bin/go1.18rc1/src/runtime
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/asm.o ./asm.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/asm_amd64.o ./asm_amd64.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/duff_amd64.o ./duff_amd64.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/memclr_amd64.o ./memclr_amd64.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/memmove_amd64.o ./memmove_amd64.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/preempt_amd64.o ./preempt_amd64.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/rt0_darwin_amd64.o ./rt0_darwin_amd64.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/asm -p runtime -trimpath "$WORK/b004=&gt;" -I $WORK/b004/ -I /Users/tonybai/.bin/go1.18rc1/pkg/include -D GOOS_darwin -D GOARCH_amd64 -compiling-runtime -D GOAMD64_v1 -o $WORK/b004/sys_darwin_amd64.o ./sys_darwin_amd64.s
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/pack r $WORK/b004/_pkg_.a $WORK/b004/asm.o $WORK/b004/asm_amd64.o $WORK/b004/duff_amd64.o $WORK/b004/memclr_amd64.o $WORK/b004/memmove_amd64.o $WORK/b004/preempt_amd64.o $WORK/b004/rt0_darwin_amd64.o $WORK/b004/sys_darwin_amd64.o # internal
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b004/_pkg_.a # internal
cp $WORK/b004/_pkg_.a /Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d # internal
demo1
mkdir -p $WORK/b001/
cat &gt;$WORK/b001/importcfg &lt;&lt; 'EOF' # internal
# import config
packagefile demo1/pkg1=$WORK/b002/_pkg_.a
packagefile demo1/pkg2=$WORK/b003/_pkg_.a
packagefile runtime=$WORK/b004/_pkg_.a
EOF

### 笔者注：编译main包并缓存

cd /Users/tonybai/test/go/incremental-build/demo1
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=&gt;" -p main -lang=go1.18 -complete -buildid ZhPqHmBh6WQ6HFsDI1Yh/ZhPqHmBh6WQ6HFsDI1Yh -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b001/importcfg -pack ./main.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cp $WORK/b001/_pkg_.a /Users/tonybai/Library/Caches/go-build/e8/e86257379cbdd59856f799594b63f3bb33ae89011955fee50e6fe90d3809ce5a-d # internal
cat &gt;$WORK/b001/importcfg.link &lt;&lt; 'EOF' # internal
packagefile demo1=$WORK/b001/_pkg_.a
packagefile demo1/pkg1=$WORK/b002/_pkg_.a
packagefile demo1/pkg2=$WORK/b003/_pkg_.a
packagefile runtime=$WORK/b004/_pkg_.a
packagefile internal/abi=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/abi.a
packagefile internal/bytealg=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/bytealg.a
packagefile internal/cpu=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/cpu.a
packagefile internal/goarch=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goarch.a
packagefile internal/goexperiment=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goexperiment.a
packagefile internal/goos=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goos.a
packagefile runtime/internal/atomic=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/atomic.a
packagefile runtime/internal/math=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/math.a
packagefile runtime/internal/sys=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/sys.a
modinfo "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tdemo1\nmod\tdemo1\t(devel)\t\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1\nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=amd64\nbuild\tGOOS=darwin\nbuild\tGOAMD64=v1\nbuild\tvcs=git\nbuild\tvcs.revision=6534186d4b5b80c6c056237191fc703fa99cd19e\nbuild\tvcs.time=2022-03-12T13:52:57Z\nbuild\tvcs.modified=true\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
mkdir -p $WORK/b001/exe/
cd .

### 笔者注：执行链接过程

/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=mzN3WRwHiNhsESy6r89L/ZhPqHmBh6WQ6HFsDI1Yh/Nvx0U2gM2zWzj7FTESXk/mzN3WRwHiNhsESy6r89L -extld=clang $WORK/b001/_pkg_.a
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal

### 笔者注：将构建出来的可执行文件放到正确位置并改名
mv $WORK/b001/exe/a.out demo1
rm -r $WORK/b001/
</code></pre>
<h4>b) 删除可执行文件后，再次构建</h4>
<p>接下来我们删除之前构建出来的可执行文件demo1，然后再执行一次go build：</p>
<pre><code>$go build -x -v
WORK=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build3889005616
cd /Users/tonybai/test/go
git status --porcelain
cd /Users/tonybai/test/go
git show -s --no-show-signature --format=%H:%ct
mkdir -p $WORK/b001/
cat &gt;$WORK/b001/importcfg.link &lt;&lt; 'EOF' # internal

### 笔者注：这次构建直接使用了上一次缓存的各个包的缓存结果 

packagefile demo1=/Users/tonybai/Library/Caches/go-build/e8/e86257379cbdd59856f799594b63f3bb33ae89011955fee50e6fe90d3809ce5a-d
packagefile demo1/pkg1=/Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d
packagefile demo1/pkg2=/Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d
packagefile runtime=/Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d

packagefile internal/abi=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/abi.a
packagefile internal/bytealg=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/bytealg.a
packagefile internal/cpu=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/cpu.a
packagefile internal/goarch=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goarch.a
packagefile internal/goexperiment=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goexperiment.a
packagefile internal/goos=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goos.a
packagefile runtime/internal/atomic=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/atomic.a
packagefile runtime/internal/math=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/math.a
packagefile runtime/internal/sys=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/sys.a
modinfo "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tdemo1\nmod\tdemo1\t(devel)\t\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1\nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=amd64\nbuild\tGOOS=darwin\nbuild\tGOAMD64=v1\nbuild\tvcs=git\nbuild\tvcs.revision=6534186d4b5b80c6c056237191fc703fa99cd19e\nbuild\tvcs.time=2022-03-12T13:52:57Z\nbuild\tvcs.modified=true\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
mkdir -p $WORK/b001/exe/
cd .
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=mzN3WRwHiNhsESy6r89L/ZhPqHmBh6WQ6HFsDI1Yh/Nvx0U2gM2zWzj7FTESXk/mzN3WRwHiNhsESy6r89L -extld=clang /Users/tonybai/Library/Caches/go-build/e8/e86257379cbdd59856f799594b63f3bb33ae89011955fee50e6fe90d3809ce5a-d
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out demo1
rm -r $WORK/b001/
</code></pre>
<p>通过go build命令输出的日志我们看到：go build并没有重新编译各个包中的源文件，而是直接使用上一次构建缓存在cache中的demo1、demo1/pkg1和demo1/pkg2进行链接并输出最终可执行文件。初步判断，Go编译器是可以识别出项目中的源文件是否发生了改变并决定是否对其重新编译的。</p>
<h4>c) 新增pkg3</h4>
<p>我们为demo1新增pkg3，并在main.go中调用pkg3包中的函数，相当于建立了一个对pkg3的依赖，然后我们再来build一下该项目：</p>
<pre><code>$go build -x -v
WORK=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build3890553968
cd /Users/tonybai/test/go
git status --porcelain
cd /Users/tonybai/test/go
git show -s --no-show-signature --format=%H:%ct
demo1/pkg3
mkdir -p $WORK/b004/
cat &gt;$WORK/b004/importcfg &lt;&lt; 'EOF' # internal
# import config
EOF

### 笔者注：构建pkg3

cd /Users/tonybai/test/go/incremental-build/demo1
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b004/_pkg_.a -trimpath "$WORK/b004=&gt;" -p demo1/pkg3 -lang=go1.18 -complete -buildid yVeHBkrjxeJ1Ib-jc5Fu/yVeHBkrjxeJ1Ib-jc5Fu -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b004/importcfg -pack ./pkg3/pkg3.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b004/_pkg_.a # internal
cp $WORK/b004/_pkg_.a /Users/tonybai/Library/Caches/go-build/2c/2c02674d62c50d4f2b8439c9314ef51b3e211d45d4114fa495fdd0e20c43440d-d # internal
demo1
mkdir -p $WORK/b001/
cat &gt;$WORK/b001/importcfg &lt;&lt; 'EOF' # internal
# import config

### 笔者注：直接重用demo1/pkg1和demo1/pkg2在cache中的目标文件

packagefile demo1/pkg1=/Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d
packagefile demo1/pkg2=/Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d
packagefile demo1/pkg3=$WORK/b004/_pkg_.a
packagefile runtime=/Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d
EOF

### 笔者注：重新编译main.go

/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=&gt;" -p main -lang=go1.18 -complete -buildid Jii_iiylmm9d82X_Mzem/Jii_iiylmm9d82X_Mzem -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b001/importcfg -pack ./main.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cp $WORK/b001/_pkg_.a /Users/tonybai/Library/Caches/go-build/52/52b5b7e233ac17201702c26f1da97c5a23e42e68f74040d576905323a016f66e-d # internal
cat &gt;$WORK/b001/importcfg.link &lt;&lt; 'EOF' # internal
packagefile demo1=$WORK/b001/_pkg_.a
packagefile demo1/pkg1=/Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d
packagefile demo1/pkg2=/Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d
packagefile demo1/pkg3=$WORK/b004/_pkg_.a
packagefile runtime=/Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d
packagefile internal/abi=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/abi.a
packagefile internal/bytealg=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/bytealg.a
packagefile internal/cpu=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/cpu.a
packagefile internal/goarch=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goarch.a
packagefile internal/goexperiment=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goexperiment.a
packagefile internal/goos=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goos.a
packagefile runtime/internal/atomic=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/atomic.a
packagefile runtime/internal/math=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/math.a
packagefile runtime/internal/sys=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/sys.a
modinfo "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tdemo1\nmod\tdemo1\t(devel)\t\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1\nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=amd64\nbuild\tGOOS=darwin\nbuild\tGOAMD64=v1\nbuild\tvcs=git\nbuild\tvcs.revision=6534186d4b5b80c6c056237191fc703fa99cd19e\nbuild\tvcs.time=2022-03-12T13:52:57Z\nbuild\tvcs.modified=true\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
mkdir -p $WORK/b001/exe/
cd .
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=GM4wTB4eDZmuIuIaQgup/Jii_iiylmm9d82X_Mzem/5aVh7LKgEkk3c4g5_WBq/GM4wTB4eDZmuIuIaQgup -extld=clang $WORK/b001/_pkg_.a
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out demo1
rm -r $WORK/b001/
</code></pre>
<p>我们看到Go只是编译了新增的pkg3以及依赖pkg3的main.go，pkg1和pkg2包并未被重新编译，而是直接使用了缓存在gocache中的中间目标文件。</p>
<h4>d) 重新编译单个变更的源文件还是重新编译整个包？</h4>
<p>如果一个go package包含多个源文件，当某一个源文件发生内容变化时，go编译器是只会编译该源文件还是整个包呢？我们来验证一下。</p>
<p>我们为pkg3添加另外一个源文件pkg3_1.go，然后做一次构建。之后再修改pkg3_1.go，再做构建：</p>
<pre><code>$go build -x -v
WORK=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build213842995
cd /Users/tonybai/test/go
git status --porcelain
cd /Users/tonybai/test/go
git show -s --no-show-signature --format=%H:%ct
demo1/pkg3
mkdir -p $WORK/b004/
cat &gt;$WORK/b004/importcfg &lt;&lt; 'EOF' # internal
# import config
EOF
cd /Users/tonybai/test/go/incremental-build/demo1

### 笔者注：编译pkg3包

/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b004/_pkg_.a -trimpath "$WORK/b004=&gt;" -p demo1/pkg3 -lang=go1.18 -complete -buildid pX9UOIUBAZfMKmMgHv3q/pX9UOIUBAZfMKmMgHv3q -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b004/importcfg -pack ./pkg3/pkg3.go ./pkg3/pkg3_1.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b004/_pkg_.a # internal
cp $WORK/b004/_pkg_.a /Users/tonybai/Library/Caches/go-build/0c/0c3ce444d214c6c2999ba01b01eb4888c7864947d88bfcf63a41db4ac44002c2-d # internal
demo1
mkdir -p $WORK/b001/
cat &gt;$WORK/b001/importcfg &lt;&lt; 'EOF' # internal
# import config
packagefile demo1/pkg1=/Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d
packagefile demo1/pkg2=/Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d
packagefile demo1/pkg3=$WORK/b004/_pkg_.a
packagefile runtime=/Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d
EOF

### 笔者注：编译依赖pkg3包的main.go

/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=&gt;" -p main -lang=go1.18 -complete -buildid cQBq3r5n1_wurKrb8Xmq/cQBq3r5n1_wurKrb8Xmq -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b001/importcfg -pack ./main.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cp $WORK/b001/_pkg_.a /Users/tonybai/Library/Caches/go-build/e2/e2818b14455f4dd54caf5f731c7b3b6b8254a37a8912e73c33b327771069bde7-d # internal
cat &gt;$WORK/b001/importcfg.link &lt;&lt; 'EOF' # internal
packagefile demo1=$WORK/b001/_pkg_.a
packagefile demo1/pkg1=/Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d
packagefile demo1/pkg2=/Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d
packagefile demo1/pkg3=$WORK/b004/_pkg_.a
packagefile runtime=/Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d
packagefile internal/abi=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/abi.a
packagefile internal/bytealg=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/bytealg.a
packagefile internal/cpu=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/cpu.a
packagefile internal/goarch=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goarch.a
packagefile internal/goexperiment=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goexperiment.a
packagefile internal/goos=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goos.a
packagefile runtime/internal/atomic=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/atomic.a
packagefile runtime/internal/math=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/math.a
packagefile runtime/internal/sys=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/sys.a
modinfo "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tdemo1\nmod\tdemo1\t(devel)\t\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1\nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=amd64\nbuild\tGOOS=darwin\nbuild\tGOAMD64=v1\nbuild\tvcs=git\nbuild\tvcs.revision=6534186d4b5b80c6c056237191fc703fa99cd19e\nbuild\tvcs.time=2022-03-12T13:52:57Z\nbuild\tvcs.modified=true\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
mkdir -p $WORK/b001/exe/
cd .
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=qvFsK1K1jm8CeANRL3a2/cQBq3r5n1_wurKrb8Xmq/BB3nEx0b9edm7IM0XGAQ/qvFsK1K1jm8CeANRL3a2 -extld=clang $WORK/b001/_pkg_.a
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out demo1
rm -r $WORK/b001/
</code></pre>
<p>我们看到：虽然只修改了pkg3包下面的源文件pkg3_1.go，但go build还是会将整个包的所有源文件都重新编译一次。依赖pkg3包的main.go也会被随之重新编译。就此可以证实，<strong>Go的增量编译是以Go包为基本单位的，而不是以单个源文件为单位的</strong>。这与go工具缓存在gocache中的中间目标文件(pkg.a)<strong>以包为单位的</strong>是一致的。</p>
<h4>e) 当间接依赖的包发生了变动</h4>
<p>前面的示例展示的都是直接依赖包发生变动后，增量构建涵盖的编译单元范畴。如果某个包的间接依赖包发生变化，该包是否会参与增量构建呢？答案是肯定的。我们继续用示例来证明一下。</p>
<p>我们为该demo1项目增加pkg4包，并使得pkg3依赖pkg4。这样就会出现main.go直接依赖pkg3包，间接依赖pkg4包的情况。我们在添加完pkg4包后，进行一次构建。之后修改pkg4包的部分内容，然后再执行构建，其输出日志如下：</p>
<pre><code>$go build -x -v
WORK=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build2817187631
cd /Users/tonybai/test/go
git status --porcelain
cd /Users/tonybai/test/go
git show -s --no-show-signature --format=%H:%ct
demo1/pkg4
mkdir -p $WORK/b005/
cat &gt;$WORK/b005/importcfg &lt;&lt; 'EOF' # internal
# import config
EOF

### 笔者注：编译demo1/pkg4包

cd /Users/tonybai/test/go/incremental-build/demo1
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b005/_pkg_.a -trimpath "$WORK/b005=&gt;" -p demo1/pkg4 -lang=go1.18 -complete -buildid AIv0TfCgKL2o00SexGru/AIv0TfCgKL2o00SexGru -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b005/importcfg -pack ./pkg4/pkg4.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b005/_pkg_.a # internal
cp $WORK/b005/_pkg_.a /Users/tonybai/Library/Caches/go-build/7e/7e2b8f229f8ca2f1ee315438f61cad5421bb5af9ed155e88a460faca806f4f90-d # internal
demo1/pkg3
mkdir -p $WORK/b004/
cat &gt;$WORK/b004/importcfg &lt;&lt; 'EOF' # internal
# import config
packagefile demo1/pkg4=$WORK/b005/_pkg_.a
EOF

### 笔者注：编译直接依赖demo1/pkg4包的demo1/pkg3包

/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b004/_pkg_.a -trimpath "$WORK/b004=&gt;" -p demo1/pkg3 -lang=go1.18 -complete -buildid ObVmRzLu3J1liPWzEiXx/ObVmRzLu3J1liPWzEiXx -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b004/importcfg -pack ./pkg3/pkg3.go ./pkg3/pkg3_1.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b004/_pkg_.a # internal
cp $WORK/b004/_pkg_.a /Users/tonybai/Library/Caches/go-build/4f/4f69cad7558ecf297799e29353bc802415785847c195555c22151f52abe1d9d9-d # internal
demo1
mkdir -p $WORK/b001/
cat &gt;$WORK/b001/importcfg &lt;&lt; 'EOF' # internal
# import config
packagefile demo1/pkg1=/Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d
packagefile demo1/pkg2=/Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d
packagefile demo1/pkg3=$WORK/b004/_pkg_.a
packagefile runtime=/Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d
EOF

### 笔者注：编译间接依赖demo1/pkg4包的main.go

/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=&gt;" -p main -lang=go1.18 -complete -buildid i543xzAqlwVlWgQBYhsS/i543xzAqlwVlWgQBYhsS -goversion go1.18rc1 -c=4 -nolocalimports -importcfg $WORK/b001/importcfg -pack ./main.go
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cp $WORK/b001/_pkg_.a /Users/tonybai/Library/Caches/go-build/67/67a5ff80f6dbbbe01fcf9efb4d6ff380cb27fd1723b06b209a17987f1c74f425-d # internal
cat &gt;$WORK/b001/importcfg.link &lt;&lt; 'EOF' # internal
packagefile demo1=$WORK/b001/_pkg_.a
packagefile demo1/pkg1=/Users/tonybai/Library/Caches/go-build/24/24519941f74b316c8e83f2d2462b62370692c5f56b04ec3df97e3124ff8b4633-d
packagefile demo1/pkg2=/Users/tonybai/Library/Caches/go-build/fe/fef7890aa0cf3bb97e872d2b49cd834a5fad87cd5d8bf052dca65e4cecb541d2-d
packagefile demo1/pkg3=$WORK/b004/_pkg_.a
packagefile runtime=/Users/tonybai/Library/Caches/go-build/0e/0e28018e12d646c32443e88953b839c7ba0be3198e6a61afc8a74c0b3e76696a-d
packagefile demo1/pkg4=$WORK/b005/_pkg_.a
packagefile internal/abi=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/abi.a
packagefile internal/bytealg=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/bytealg.a
packagefile internal/cpu=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/cpu.a
packagefile internal/goarch=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goarch.a
packagefile internal/goexperiment=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goexperiment.a
packagefile internal/goos=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/internal/goos.a
packagefile runtime/internal/atomic=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/atomic.a
packagefile runtime/internal/math=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/math.a
packagefile runtime/internal/sys=/Users/tonybai/.bin/go1.18rc1/pkg/darwin_amd64/runtime/internal/sys.a
modinfo "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tdemo1\nmod\tdemo1\t(devel)\t\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1\nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=amd64\nbuild\tGOOS=darwin\nbuild\tGOAMD64=v1\nbuild\tvcs=git\nbuild\tvcs.revision=6534186d4b5b80c6c056237191fc703fa99cd19e\nbuild\tvcs.time=2022-03-12T13:52:57Z\nbuild\tvcs.modified=true\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
mkdir -p $WORK/b001/exe/
cd .
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=5U4vzFHU8ahkEvKH4-CV/i543xzAqlwVlWgQBYhsS/GOFyatqByKmjWK1zLLiq/5U4vzFHU8ahkEvKH4-CV -extld=clang $WORK/b001/_pkg_.a
/Users/tonybai/.bin/go1.18rc1/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out demo1
rm -r $WORK/b001/
</code></pre>
<p>我们看到：pkg4包修改后，无论是直接依赖pkg4的包，还是间接依赖pkg4的包都会在下次增量构建时被重新编译。</p>
<h3>3. 小结</h3>
<p>由上面的示例我们看到：Go编译器是原生支持增量构建的，无需第三方构建管理工具的辅助。Go的增量构建是建立在<a href="https://tonybai.com/2018/02/17/some-changes-in-go-1-10">Go 1.10引入的build cache机制</a>的基础上的。Go的增量构建以Go包为单位，当Go包中的任一源文件发生变化时，Go都会对其进行重新构建，并且会连带构建所有直接或间接依赖该包的Go包。</p>
<p>本文示例源码在<a href="https://github.com/bigwhite/experiments/tree/master/incremental-build/demo1">这里</a>可以下载。</p>
<hr />
<p><a href="https://mp.weixin.qq.com/s/jUqAL7hf2GmMun64BJufEA">“Gopher部落”知识星球</a>旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！2022年，Gopher部落全面改版，将持续分享Go语言与Go应用领域的知识、技巧与实践，并增加诸多互动形式。欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-k8s-practice-with-qr.jpg" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>微信公众号：iamtonybai</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>“Gopher部落”知识星球：https://public.zsxq.com/groups/51284458844544</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2022, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2022/03/21/go-native-support-incremental-build/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Brooks、Wirth和Go[译]</title>
		<link>https://tonybai.com/2021/08/25/brooks-wirth-and-go/</link>
		<comments>https://tonybai.com/2021/08/25/brooks-wirth-and-go/#comments</comments>
		<pubDate>Wed, 25 Aug 2021 12:57:10 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[10000倍程序员]]></category>
		<category><![CDATA[10倍程序员]]></category>
		<category><![CDATA[algol68]]></category>
		<category><![CDATA[Brian W. Kernighan]]></category>
		<category><![CDATA[brooks]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[fortran]]></category>
		<category><![CDATA[FP]]></category>
		<category><![CDATA[GeneAmdahl]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gopl]]></category>
		<category><![CDATA[IBM]]></category>
		<category><![CDATA[IBM360]]></category>
		<category><![CDATA[IDE]]></category>
		<category><![CDATA[interlisp]]></category>
		<category><![CDATA[KenThompson]]></category>
		<category><![CDATA[mainframe]]></category>
		<category><![CDATA[NiklausWirth]]></category>
		<category><![CDATA[oberon]]></category>
		<category><![CDATA[Objective-C]]></category>
		<category><![CDATA[OOP]]></category>
		<category><![CDATA[Pascal]]></category>
		<category><![CDATA[RobertGriesemer]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[Smalltalk]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[TonyHoare]]></category>
		<category><![CDATA[人月神话]]></category>
		<category><![CDATA[函数式]]></category>
		<category><![CDATA[大型机]]></category>
		<category><![CDATA[弗雷德里克·布鲁克斯]]></category>
		<category><![CDATA[摩尔定律]]></category>
		<category><![CDATA[没有银弹]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[穿孔卡]]></category>
		<category><![CDATA[编译]]></category>
		<category><![CDATA[过程化]]></category>
		<category><![CDATA[链接]]></category>
		<category><![CDATA[面向对象]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3272</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2021/08/25/brooks-wirth-and-go 本文翻译自瑞典程序员Fredrik Holmqvist的博客文章《Brooks, Wirth and Go》。 现在是1975年。 程序员们带着FORTRAN代码回来了，不过使用的是穿孔卡片的形式。 图：记录代码的穿孔卡片(图片来自punchcardreader.com，译者加) 这些穿孔卡片被小心翼翼的送进大型机，它们被输入、读取、编译、链接并由计算机执行。当得到“文件名称规格错误”这个结果时，时间已经过去了两个多星期了。在这个阶段，很多人参与了代码的编写与制作并消耗了几周的工作时间。 与此同时，另一个用Smalltalk和Interlisp编程的工程师正直接在一个控制台中编写并运行他的实现。几秒钟后，他得到了程序的运行结果。接下来，他就在那里修复了错误并完成了这个任务。 上述这两种方法在周转时间上的差异是四个数量级。 忘了“10倍程序员(10X programmer)”吧，“10000倍程序员(10000X programmer)”怎么样？ 由于现代计算机的硬件已经发展到比将人类送上月球的计算机快几千亿倍，这些类型的差异已经急剧缩小了。那些即使是简单计算也要等待几个小时出结果的分时的日子已经一去不复返了。即使是手机也足够强大，可以完成人类在20世纪的所有计算结果。 图：摩尔定律(图片来自wikipedia，译者加) 但软件却可能没有这么大的进步。可以说，自ALGOL 68以来，在解决软件危机方面没有发生什么。也许更糟糕的是，我们（集体）从那个时代的巨头那里学到的东西太少。我想举例说明这些巨头中的两位，以及他们可以教给我们的经验。 弗雷德里克·布鲁克斯 1964年，IBM宣布了其迄今为止最雄心勃勃的项目：IBM 360。该项目由Gene Amdahl负责设计，弗雷德里克·布鲁克斯(Frederick Brooks)负责管理。 这是世界上第一台真正的可编程大型计算机，开启了计算机可以被重新编程以适应新问题的概念，而不是被更新的模型所取代。该系统结构引入了许多我们今天仍在使用的标准，如8位字节、32位字(word)等。 也许更有趣的是这个项目本身。该项目&#8230;&#8230;比最初想象的要昂贵得多。它将预算提高了200倍：从2500万美元提高到50亿美元。要知道，当时作为美国国家核武器研究的曼哈顿项目的预算才仅为20亿美元。 该项目遇到了你能想到的所有开发和管理问题。 多年以后，布鲁克斯决定，回答”为什么软件项目经常出错”这个问题的最好方法是把他的经验和IBM的教训写成一本书。那本书就是现在传说中的《人月神话》。 图：《人月神话》(译者加) 这也许是关于软件管理的最佳读物之一。其中有一篇文章是“没有银弹(No Silver Bullet)”，它指出： 无论是技术还是管理技术，都没有任何一种可以在十年内保证在生产力、可靠性和简单性方面有哪怕一个数量级的改进。 鉴于与穿孔卡的前辈相比，现代程序员可以很快纠正他们的错误，布鲁克斯认为，剩下的大部分复杂性是问题本身，而意外的复杂性大部分已经解决了。 这并不是说自60年代以来生产力没有提高，实际上恰恰相反。来看看下面的例子： 自由/开放源码软件 高速硬件 通用计算机 高性能编译器 全球互联网(Internet) 它们一起将我们的整体生产力推到了一个很高的水平。它们也重新引入了许多意外的复杂性，而这些复杂性是我们的前辈们在最初就很努力地消除的。(稍后会有更多关于这方面的内容) “现在的程序员已经不像以前那样高产了”。 这种将偶然的复杂性降低到最低限度的概念是我们很多问题的关键，没有比尼克劳斯·沃思(Niklaus Wirth)更能体现这一原则的了。 尼克劳斯·沃思 在创造了PASCAL、MODULA和MODULA-2之后，沃思开始着手开发OBERON系列语言，以便在他的Ceres工作站上建立他自己的Oberon操作系统。 如果说沃思在他的职业生涯中完成了很多伟大的事情，那就太轻描淡写了，而上面给出的例子只是他成就的一小部分。 他设法通过遵循一套原则来执行所有这些想法，这些原则可以总结如下： 你必须完全理解你的想法，才能完全实现它。 Oberon语言的出现是出于降低编程语言，特别是针对Modula的复杂性的考虑。这一努力产生了一种非常简洁的语言。Oberon的范围，它的功能和结构的数量，甚至比Pascal小。然而，它的功能却大大增强了。 这个人的结论是：Pascal太复杂了。 利用他发现的新力量，他在自己的硬件上从头开始建立了他的操作系统，这个操作系统仅有12K行源码，占用200K字节的空间资源。我们可以对比一下，Mac OSX拥有86M行源码，占用3GB的空间，并且是由世界上最富有的公司之一建立的。现在，也许OSX比Oberon的功能更完整，但肯定不是40000倍的关系。一路走来，有些东西已经失去了。 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/brooks-wirth-and-go-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2021/08/25/brooks-wirth-and-go">本文永久链接</a> &#8211; https://tonybai.com/2021/08/25/brooks-wirth-and-go</p>
<p>本文翻译自瑞典程序员Fredrik Holmqvist的博客文章<a href="https://www.fredrikholmqvist.com/posts/brooks-wirth-go/">《Brooks, Wirth and Go》</a>。</p>
<p>现在是1975年。</p>
<p>程序员们带着<a href="https://fortran-lang.org/">FORTRAN代码</a>回来了，不过使用的是穿孔卡片的形式。</p>
<p><img src="https://tonybai.com/wp-content/uploads/brooks-wirth-and-go-2.jpeg" alt="" /><br />
<center>图：记录代码的穿孔卡片(图片来自punchcardreader.com，译者加)</center></p>
<p>这些穿孔卡片被小心翼翼的送进大型机，它们被输入、读取、编译、链接并由计算机执行。当得到“文件名称规格错误”这个结果时，时间已经过去了两个多星期了。在这个阶段，很多人参与了代码的编写与制作并消耗了几周的工作时间。</p>
<p>与此同时，另一个用<a href="https://en.wikipedia.org/wiki/Smalltalk">Smalltalk</a>和<a href="https://en.wikipedia.org/wiki/Interlisp">Interlisp</a>编程的工程师正直接在一个控制台中编写并运行他的实现。几秒钟后，他得到了程序的运行结果。接下来，他就在那里修复了错误并完成了这个任务。</p>
<p>上述这两种方法在周转时间上的差异是<strong>四个数量级</strong>。</p>
<p>忘了“10倍程序员(10X programmer)”吧，“10000倍程序员(10000X programmer)”怎么样？</p>
<p>由于现代计算机的硬件已经发展到比将人类送上月球的计算机<a href="https://en.wikipedia.org/wiki/Moore%27s_law#/media/File:Moore's_Law_Transistor_Count_1970-2020.png">快几千亿倍</a>，这些类型的差异已经急剧缩小了。那些即使是简单计算也要等待几个小时出结果的分时的日子已经一去不复返了。即使是手机也足够强大，可以完成人类在20世纪的所有计算结果。</p>
<p><img src="https://tonybai.com/wp-content/uploads/brooks-wirth-and-go-3.png" alt="" /><br />
<center>图：摩尔定律(图片来自wikipedia，译者加)</center></p>
<p><strong>但软件却可能没有这么大的进步</strong>。可以说，自<a href="https://en.wikipedia.org/wiki/ALGOL_68">ALGOL 68</a>以来，在<a href="https://en.wikipedia.org/wiki/Software_crisis">解决软件危机</a>方面没有发生什么。也许更糟糕的是，我们（集体）从那个时代的巨头那里学到的东西太少。我想举例说明这些巨头中的两位，以及他们可以教给我们的经验。</p>
<h3>弗雷德里克·布鲁克斯</h3>
<p><img src="https://tonybai.com/wp-content/uploads/brooks-wirth-and-go-4.png" alt="" /></p>
<p>1964年，IBM宣布了其迄今为止最雄心勃勃的项目：<a href="https://en.wikipedia.org/wiki/IBM_System/360">IBM 360</a>。该项目由<a href="https://en.wikipedia.org/wiki/Gene_Amdahl">Gene Amdahl</a>负责设计，<a href="https://en.wikipedia.org/wiki/Fred_Brooks">弗雷德里克·布鲁克斯(Frederick Brooks)</a>负责管理。</p>
<p>这是世界上第一台真正的可编程大型计算机，开启了计算机可以被重新编程以适应新问题的概念，而不是被更新的模型所取代。该系统结构引入了许多我们今天仍在使用的标准，如<a href="https://en.wikipedia.org/wiki/IBM_System/360#Technical_description">8位字节、32位字(word)等</a>。</p>
<p>也许更有趣的是这个项目本身。该项目&#8230;&#8230;比最初想象的要昂贵得多。它将预算提高了200倍：从2500万美元提高到50亿美元。要知道，当时作为美国国家核武器研究的<a href="https://en.wikipedia.org/wiki/Manhattan_Project">曼哈顿项目</a>的预算才仅为20亿美元。</p>
<p><img src="https://tonybai.com/wp-content/uploads/brooks-wirth-and-go-6.png" alt="" /></p>
<p>该项目遇到了你能想到的所有开发和管理问题。</p>
<p>多年以后，布鲁克斯决定，回答”为什么软件项目经常出错”这个问题的最好方法是把他的经验和IBM的教训写成一本书。那本书就是现在传说中的<a href="https://book.douban.com/subject/1102259/">《人月神话》</a>。</p>
<p><img src="https://tonybai.com/wp-content/uploads/brooks-wirth-and-go-7.jpg" alt="" /><br />
<center>图：《人月神话》(译者加)</center></p>
<p>这也许是关于软件管理的最佳读物之一。其中有一篇文章是“没有银弹(No Silver Bullet)”，它指出：</p>
<blockquote>
<p>无论是技术还是管理技术，都没有任何一种可以在十年内保证在生产力、可靠性和简单性方面有哪怕一个数量级的改进。</p>
</blockquote>
<p>鉴于与穿孔卡的前辈相比，现代程序员可以很快纠正他们的错误，布鲁克斯认为，剩下的大部分复杂性是问题本身，而意外的复杂性大部分已经解决了。</p>
<p>这并不是说自60年代以来生产力没有提高，实际上恰恰相反。来看看下面的例子：</p>
<ul>
<li>自由/开放源码软件</li>
<li>高速硬件</li>
<li>通用计算机</li>
<li>高性能编译器</li>
<li>全球互联网(Internet)</li>
</ul>
<p>它们一起将我们的整体生产力推到了一个很高的水平。它们也重新引入了许多意外的复杂性，而这些复杂性是我们的前辈们在最初就很努力地消除的。(稍后会有更多关于这方面的内容)</p>
<p><a href="https://www.youtube.com/watch?v=EY6q5dv_B-o&amp;t=1360s">“现在的程序员已经不像以前那样高产了”</a>。</p>
<p>这种将偶然的复杂性降低到最低限度的概念是我们很多问题的关键，没有比<a href="https://en.wikipedia.org/wiki/Niklaus_Wirth">尼克劳斯·沃思(Niklaus Wirth)</a>更能体现这一原则的了。</p>
<h3>尼克劳斯·沃思</h3>
<p><img src="https://tonybai.com/wp-content/uploads/brooks-wirth-and-go-5.png" alt="" /></p>
<p>在创造了<a href="https://en.wikipedia.org/wiki/Pascal_(programming_language)">PASCAL</a>、<a href="https://en.wikipedia.org/wiki/Modula">MODULA</a>和<a href="https://en.wikipedia.org/wiki/Modula-2">MODULA-2</a>之后，沃思开始着手开发<a href="https://en.wikipedia.org/wiki/Oberon_(programming_language)">OBERON系列语言</a>，以便在他的Ceres<a href="https://en.wikipedia.org/wiki/Ceres_(workstation)">工作站</a>上建立他自己的Oberon<a href="https://en.wikipedia.org/wiki/Oberon_(operating_system)">操作系统</a>。</p>
<p>如果说沃思在他的职业生涯中完成了很多伟大的事情，那就太轻描淡写了，而<a href="https://en.wikipedia.org/wiki/Niklaus_Wirth#Biography">上面给出的例子只是他成就的一小部分</a>。</p>
<p>他设法通过遵循一套原则来执行所有这些想法，这些原则可以总结如下：</p>
<p>你必须完全理解你的想法，才能完全实现它。</p>
<blockquote>
<p>Oberon语言的出现是出于降低编程语言，特别是针对Modula的复杂性的考虑。这一努力产生了一种非常简洁的语言。Oberon的范围，它的功能和结构的数量，甚至比Pascal小。然而，它的功能却大大增强了。</p>
</blockquote>
<p>这个人的结论是：<strong>Pascal太复杂了</strong>。</p>
<p>利用他发现的新力量，他在自己的硬件上从头开始建立了他的操作系统，<a href="http://www.edm2.com/0608/oberon.html">这个操作系统仅有12K行源码，占用200K字节的空间资源</a>。我们可以对比一下，Mac OSX拥有86M行源码，占用3GB的空间，并且是由世界上最富有的公司之一建立的。现在，也许OSX比Oberon的功能更完整，但肯定不是40000倍的关系。一路走来，有些东西已经失去了。</p>
<p>布鲁克斯的“没有银弹”的概念和沃思的哲学在这里有交集：</p>
<blockquote>
<p>你不能通过增加你的语言的复杂性来减少你的问题的复杂性。</p>
</blockquote>
<p>你的语言的表面积越大(译：我理解指语言的设计目标越多)，就会有越多的<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/standards">风沙</a>（译注：风沙指语言的特性）来掩盖其本质。在某些时候，指针已经向前移动到循环开始的地方，因为旧的子集变成了新的，循环再次开始。</p>
<p>这种“少即是多”的概念让我想起了另一位巨人的一句同样性质的话：</p>
<blockquote>
<p>有两种构建软件设计的方法：一种是使其简单到明显没有缺陷，另一种是使其复杂到没有明显缺陷 &#8211; <a href="https://en.wikipedia.org/wiki/Tony_Hoare">Tony Hoare</a></p>
</blockquote>
<p>在理论上拒绝沃思的前提，必然会导致走向Hoare观点中的第二个方法。</p>
<blockquote>
<p>&#8216;在Objective-C和Swift之间的某个地方，你最终得到了一个来自过去的框架，一个来自未来的框架，以及现在的一个纠结的混乱。</p>
</blockquote>
<p>走这条路的代价是什么？</p>
<h3>束缚我们的石头</h3>
<h4>培训</h4>
<ul>
<li>学习一个新的操作系统，与你的技术绑定。</li>
<li>学习一个新的IDE，与你的技术绑定。</li>
<li>学习一个新的框架来取代已经工作的框架。</li>
<li>学习使用你的旧语言的新版本。</li>
</ul>
<p>你所有的旧技能都得益于你多年的经验，就像<a href="https://en.wikipedia.org/wiki/Ship_of_Theseus">特修斯之船</a>一样(译注：特修斯之船是一个思想实验，它提出了一个问题：一个已经更换了所有组件的物体从根本上是否与原物体是相同的)，到了一个时刻，这些技能所占的比重越来越小。经验应该增加价值，而不是减少它。</p>
<h4>仓鼠轮(译注：循环往复的重复工作)</h4>
<ul>
<li>以前工作的项目在更新后被破坏。</li>
<li>你所依赖的其他人以前工作的项目在更新后也会被破坏。</li>
<li>筛选几页的文档和StackOverflow的帖子，这些都不再有意义了。</li>
<li>不得不跟上新闻，以便预测你的下一个待命的头痛问题。</li>
</ul>
<p>被迫修复由你的项目、公司、客户或大陆以外的外部力量产生的问题，对任何人都没有帮助，尤其是对你。</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=lKXe3HUG2l4">乔-阿姆斯特朗的《我们所处的困境》</a></li>
<li><a href="https://blog.codinghorror.com/why-do-we-have-so-many-screwdrivers/">为什么我们有这么多螺丝刀？</a></li>
<li><a href="https://www.youtube.com/watch?v=kZRE7HIO3vk">三千万行的问题</a></li>
<li><a href="https://www.theregister.com/2016/03/23/npm_left_pad_chaos/">left pad的故事</a></li>
</ul>
<h4><a href="https://upload.wikimedia.org/wikipedia/en/e/e1/Think_of_the_children.webm">难道就没有人为年轻人着想吗？</a></h4>
<ul>
<li>这个行业是非常难学的。</li>
<li>除了厨房里的水槽，什么都有，这不是一个介绍新人的好方法。</li>
<li>花在学习工具上的时间本可以用来了解这个项目或学习一般的技能以延续到下一个项目。</li>
</ul>
<p>你所碰到的大多数后辈都被压倒了，感到困惑，并有压力要跟上不断变化的皇帝的衣服层。</p>
<ul>
<li><a href="https://dusted.codes/dotnet-for-beginners">如何向初学者教授.NET？</a></li>
<li><a href="https://hackernoon.com/how-it-feels-to-learn-javascript-in-2016-d3a717dd577f">在2016年学习JavaScript的感觉如何</a>。</li>
</ul>
<blockquote>
<p>裁缝被封为所有顾问的守护神，因为尽管他榨取了大量的费用，但他始终无法说服他的客户，让他们恍然大悟，他们的衣服没有皇帝。- Tony Hoare</p>
</blockquote>
<p>除了老人和可能的内核开发者之外，整个行业往往没有意识到，忽视或拒绝这一前提。相反，轮子的每一次旋转都会到达它开始的地方，并承诺会有新的开始。</p>
<p>幸运的是，也有例外的情况。这里就是其中之一。</p>
<h3>Go</h3>
<p>这种奇妙的、著名的”停留在70年代”的语言，满足了所有必要的条件，避免了大部分（如果不是全部）的问题，并从古老的语言中获得了灵感，但又颇具现代感。</p>
<ul>
<li>
<p>一蹴而就</p>
<ul>
<li>单一安装，没有许可证/注册/祭祀仪式。</li>
<li>可以在任何东西上运行，即使那东西是一台布满灰尘的旧笔记本电脑。</li>
<li>语言（相对而言）容易掌握。</li>
<li>直接的过程化编程(procedural programming)，不给自己贴上FP(函数式)和OOP(面向对象)标签。</li>
</ul>
</li>
<li>
<p>没有IDE的耦合。</p>
<ul>
<li>不需要购买许可证，工程师不会被过期的许可证困扰。</li>
<li>不需要重新培训工程师将文本输入文本文件。如果他们有几十年使用一个编辑器的经验，他们就可以使用它。</li>
<li>没有解决方案文件或复杂的需要IDE才能工作的构建系统。</li>
</ul>
</li>
<li>
<p>即时编译为静态二进制文件。</p>
<ul>
<li>不需要在项目编译时坐着什么都不做(译注：编译速度快，几乎无需等待)。</li>
<li>不需要为了把一种文本编译成另一种文本而把自己的所有内核旋转到100%。</li>
<li>通过运行一个单一的可执行文件进行部署。</li>
</ul>
</li>
<li>
<p>如果十年前能用，现在也能用。</p>
<ul>
<li>停留在70年代意味着自喇叭裤以来就没有突破性的变化。</li>
<li>阳光下的一切都包含在自带电池的标准库中。</li>
<li>每一行代码都是可检查的，没有闭源库。</li>
</ul>
</li>
</ul>
<p>它是由<a href="https://www.imooc.com/read/87/article/2320">Ken Thompson、Rob Pike和Robert Griesemer</a>（沃思的一个学生）设计的。该语言的<a href="http://www.gopl.io">入门书籍</a>是由<a href="https://en.wikipedia.org/wiki/Brian_Kernighan">Brian Kernighan撰写</a>的。很明显，这种语言是C语言的精神继承者。</p>
<p><img src="https://tonybai.com/wp-content/uploads/brooks-wirth-and-go-8.png" alt="" /><br />
<center>图：Go程序设计语言(图片来自gopl.io，译者加)</center></p>
<p>距离我第一次使用Go已经两年了，我想不出有什么比它更适合通用的软件开发了，尤其是在尊重自己和他人的时间方面。它是为数不多的可以让我自由编程的语言之一，而不需要向互联网咨询，也不需要向在这方面有更多经验的人催促那些应该是不言自明的事情。它没有那么多的魔力，没有那么多的隐藏，这就产生了更多更大的清晰性。没有惊喜，”<strong>它只是能工作</strong>“。</p>
<p>这并不是说每个人都有这种感觉，恰恰相反。<a href="https://github.com/ksimka/go-is-not-good">批评是很多的</a>。关于Go缺失功能的讨论（比如，<a href="https://blog.golang.org/generics-proposal">缺乏泛型</a>）已经持续了很多年（到现在已经超过十年了），我只能假设在不可预见的未来还会继续下去。</p>
<p>在这期间，我敦促你去试试这门语言。也许你会喜欢上它。</p>
<hr />
<p><a href="https://mp.weixin.qq.com/s/jUqAL7hf2GmMun64BJufEA">“Gopher部落”知识星球</a>正式转正（从试运营星球变成了正式星球）！“gopher部落”旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！部落目前虽小，但持续力很强。在2021年上半年，部落将策划两个专题系列分享，并且是部落独享哦：</p>
<ul>
<li>Go技术书籍的书摘和读书体会系列</li>
<li>Go与eBPF系列</li>
</ul>
<p>欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/202103/gopher-tribe-zsxq-card.png" alt="" /></p>
<p>Go技术专栏“<a href="https://www.imooc.com/read/87">改善Go语⾔编程质量的50个有效实践</a>”正在慕课网火热热销中！本专栏主要满足广大gopher关于Go语言进阶的需求，围绕如何写出地道且高质量Go代码给出50条有效实践建议，上线后收到一致好评！欢迎大家订<br />
阅！</p>
<p><img src="https://tonybai.com/wp-content/uploads/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网热卖中，欢迎小伙伴们订阅学习！</p>
<p><img src="https://tonybai.com/wp-content/uploads/imooc-k8s-practice-with-qr.jpg" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>微信公众号：iamtonybai</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>“Gopher部落”知识星球：https://public.zsxq.com/groups/51284458844544</li>
</ul>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2021, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2021/08/25/brooks-wirth-and-go/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>libiconv库链接问题一则</title>
		<link>https://tonybai.com/2013/04/25/a-libiconv-linkage-problem/</link>
		<comments>https://tonybai.com/2013/04/25/a-libiconv-linkage-problem/#comments</comments>
		<pubDate>Thu, 25 Apr 2013 10:04:34 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[Glibc]]></category>
		<category><![CDATA[GNU]]></category>
		<category><![CDATA[iconv]]></category>
		<category><![CDATA[ld]]></category>
		<category><![CDATA[libc]]></category>
		<category><![CDATA[libiconv]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Redhat]]></category>
		<category><![CDATA[Solaris]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[链接]]></category>
		<category><![CDATA[链接器]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1258</guid>
		<description><![CDATA[与在Solaris系统上不同，Linux的libc库中包含了libiconv库中函数的定义，因此在Linux上使用libiconv库相关函数，编译时是不需要显式-liconv的。但最近我的一位同事在某redhat enterprise server 5.6机器上编译程序时却遇到了找不到iconv库函数符号的链接问题，到底是怎样一回事呢？这里分享一下问题查找过程。 一、现场重现 这里借用一下这位同事的测试程序以及那台机器，重现一下问题过程： /*test.c */ &#8230; #include &#60;iconv.h&#62; int main(void) { &#160;&#160;&#160; int r; &#160;&#160;&#160; char *sin, *sout; &#160;&#160;&#160; size_t lenin, lenout; &#160;&#160;&#160; char *src = &#34;你好!&#34;; &#160;&#160;&#160; char dst[256] = {0}; &#160;&#160;&#160; iconv_t c_pt;&#160;&#160; &#160;&#160;&#160; sin = src; &#160;&#160;&#160; lenin = strlen(src)+1; &#160;&#160;&#160; sout = dst; &#160;&#160;&#160; lenout = 256; &#160;&#160;&#160; [...]]]></description>
			<content:encoded><![CDATA[<p>与在<a href="http://tonybai.com/2009/11/05/a-64bit-compiling-problem-on-x86-solaris/">Solaris</a>系统上不同，<a href="http://tonybai.com/2012/12/04/upgrade-ubuntu-to-1204-lts/">Linux</a>的libc库中包含了<a href="http://tonybai.com/2009/10/31/internal-code-transform-by-iconv/">libiconv</a>库中函数的定义，因此在Linux上使用libiconv库相关函数，编译时是不需要显式-liconv的。但最近我的一位同事在某redhat enterprise server 5.6机器上编译程序时却遇到了找不到iconv库函数符号的<a href="http://tonybai.com/2007/12/08/those-things-about-symbol-linkage/">链接问题</a>，到底是怎样一回事呢？这里分享一下问题查找过程。</p>
<p><b>一、现场重现</b></p>
<p>这里借用一下这位同事的测试程序以及那台机器，重现一下问题过程：<br />
	/*test.c */</p>
<p>&#8230;<br />
	<font face="Courier New">#include &lt;iconv.h&gt;</font></p>
<p><font face="Courier New">int main(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; int r;<br />
	&nbsp;&nbsp;&nbsp; char *sin, *sout;<br />
	&nbsp;&nbsp;&nbsp; size_t lenin, lenout;<br />
	&nbsp;&nbsp;&nbsp; char *src = &quot;你好!&quot;;<br />
	&nbsp;&nbsp;&nbsp; char dst[256] = {0};<br />
	&nbsp;&nbsp;&nbsp; iconv_t c_pt;&nbsp;&nbsp;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; sin = src;<br />
	&nbsp;&nbsp;&nbsp; lenin = strlen(src)+1;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; sout = dst;<br />
	&nbsp;&nbsp;&nbsp; lenout = 256;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if ((c_pt = iconv_open(&quot;UTF-8&quot;, &quot;GB2312&quot;)) == (iconv_t)(-1)){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;iconv_open error!. errno[%d].\n&quot;, errno);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if ((r = iconv(c_pt, (char **)&amp;sin, &amp;lenin, &amp;sout, &amp;lenout)) != 0){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;iconv error!. errno[%d].\n&quot;, r);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br />
	&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; iconv_close(c_pt);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; printf(&quot;SRC[%s], DST[%s].\n&quot;, src, dst);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return 0;<br />
	}</font></p>
<p>根据之前的经验，我们按如下命令编译该程序：</p>
<p><font face="Courier New">$&gt; gcc -g -o test test.c</font></p>
<p><font face="Courier New">/tmp/ccyQ5blC.o: In function `main&#39;:<br />
	/home/tonybai/tmp/test.c:28: undefined reference to `libiconv_open&#39;<br />
	/home/tonybai/tmp/test.c:33: undefined reference to `libiconv&#39;<br />
	/home/tonybai/tmp/test.c:38: undefined reference to `libiconv_close&#39;</font></p>
<p>咦，这是咋搞的呢？怎么找不到iconv库的符号！！！显式加上iconv的链接指示再试试。</p>
<p><font face="Courier New">$&gt; gcc -g -o test test.c -liconv</font></p>
<p>这回编译OK了。的确如那位同事所说出现了怪异的情况。</p>
<p><b>二、现场取证</b></p>
<p>惯性思维让我<b>首先</b>提出疑问：难道是这台机器上的<a href="http://www.gnu.org/s/libc/">libc</a>版本有差异，检查一下<a href="http://tonybai.com/2009/04/11/glibc-strlen-source-analysis/">libc</a>中是否定义了iconv相关符号。</p>
<p><font face="Courier New">$ nm /lib64/libc.so.6 |grep iconv<br />
	000000397141e040 T iconv<br />
	000000397141e1e0 T iconv_close<br />
	000000397141ddc0 T iconv_open</font></p>
<p>iconv的函数都定义了呀！怎么会链接不到？</p>
<p>我们<b>再来</b>看看已经编译成功的那个test到底连接到哪个iconv库了。</p>
<p><font face="Courier New">$ ldd test<br />
	&nbsp;&nbsp;&nbsp; linux-vdso.so.1 =&gt;&nbsp; (0x00007fff77d6b000)<br />
	&nbsp;&nbsp;&nbsp; libiconv.so.2 =&gt; /usr/local/lib/libiconv.so.2 (0x00002abbeb09e000)<br />
	&nbsp;&nbsp;&nbsp; libc.so.6 =&gt; /lib64/libc.so.6 (0&#215;0000003971400000)<br />
	&nbsp;&nbsp;&nbsp; /lib64/ld-linux-x86-64.so.2 (0&#215;0000003971000000)</font></p>
<p>哦，系统里居然在/usr/local/lib下面单独安装了一份libiconv。gcc显然是链接到这里的libiconv了，但gcc怎么会链接到这里了呢？</p>
<p><b>三、</b><b>大侦探的分析^_^</b></p>
<p><a href="http://tonybai.com/2006/03/14/explain-gcc-warning-options-by-examples/">Gcc</a>到底做了什么呢？我们看看其verbose的输出结果。</p>
<p><font face="Courier New">$ gcc -g -o test test.c -liconv -v<br />
	使用内建 specs。<br />
	目标：x86_64-redhat-linux<br />
	配置为：../configure &#8211;prefix=/usr &#8211;mandir=/usr/share/man &#8211;infodir=/usr/share/info &#8211;enable-shared &#8211;enable-threads=posix &#8211;enable-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; checking=release &#8211;with-system-zlib &#8211;enable-__cxa_atexit &#8211;disable-libunwind-exceptions &#8211;enable-libgcj-multifile &#8211;enable-languages=c,c++,&nbsp;&nbsp; objc,obj-c++,java,fortran,ada &#8211;enable-java-awt=gtk &#8211;disable-dssi &#8211;disable-plugin &#8211;with-java-home=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre &#8211;with-cpu=generic &#8211;host=x86_64-redhat-linux<br />
	线程模型：posix<br />
	gcc 版本 4.1.2 20080704 (Red Hat 4.1.2-50)<br />
	&nbsp;/usr/libexec/gcc/x86_64-redhat-linux/4.1.2/cc1 -quiet -v test.c -quiet -dumpbase test.c -mtune=generic -auxbase test -g -version -o /tmp/&nbsp;&nbsp;&nbsp;&nbsp; ccypZm0v.s<br />
	忽略不存在的目录&ldquo;/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../x86_64-redhat-linux/include&rdquo;<br />
	#include &quot;&#8230;&quot; 搜索从这里开始：<br />
	#include &lt;&#8230;&gt; 搜索从这里开始：<br />
	&nbsp;/usr/local/include<br />
	&nbsp;/usr/lib/gcc/x86_64-redhat-linux/4.1.2/include<br />
	&nbsp;/usr/include<br />
	搜索列表结束。<br />
	GNU C 版本 4.1.2 20080704 (Red Hat 4.1.2-50) (x86_64-redhat-linux)<br />
	&nbsp;&nbsp;&nbsp; 由 GNU C 版本 4.1.2 20080704 (Red Hat 4.1.2-50) 编译。<br />
	GGC 准则：&#8211;param ggc-min-expand=100 &#8211;param ggc-min-heapsize=131072<br />
	Compiler executable checksum: ef754737661c9c384f73674bd4e06594<br />
	&nbsp;as -V -Qy -o /tmp/ccaqvDgX.o /tmp/ccypZm0v.s<br />
	GNU assembler version 2.17.50.0.6-14.el5 (x86_64-redhat-linux) using BFD version 2.17.50.0.6-14.el5 20061020<br />
	&nbsp;/usr/libexec/gcc/x86_64-redhat-linux/4.1.2/collect2 &#8211;eh-frame-hdr -m elf_x86_64 &#8211;hash-style=gnu -dynamic-linker /lib64/ld-linux-x86-64.so.&nbsp; 2 -o test /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crti.o /usr/&nbsp;&nbsp; lib/gcc/x86_64-redhat-linux/4.1.2/crtbegin.o -L/usr/lib/gcc/x86_64-redhat-linux/4.1.2 -L/usr/lib/gcc/x86_64-redhat-linux/4.1.2 -L/usr/lib/gcc/ x86_64-redhat-linux/4.1.2/../../../../lib64 -L/lib/../lib64<br />
	-L/usr/lib/../lib64 /tmp/ccaqvDgX.o -liconv -lgcc &#8211;as-needed -lgcc_s &#8211;no-as-needed -lc -lgcc &#8211;as-needed -lgcc_s &#8211;no-as-needed /usr/lib/gcc/x86_64-redhat-linux/4.1.2/crtend.o /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crtn.o</font></p>
<p>从这个结果来看，gcc在search iconv.h这个头文件时，首先找到的是/usr/local/include/iconv.h，而不是/usr/include/iconv.h。这两个文件有啥不同么？</p>
<p>在/usr/local/include/iconv.h中，我找到如下代码：</p>
<p><font face="Courier New">&#8230;</font><br />
	<font face="Courier New">#ifndef LIBICONV_PLUG<br />
	#define iconv_open libiconv_open<br />
	#endif<br />
	extern iconv_t iconv_open (const char* tocode, const char* fromcode);<br />
	&#8230;</font></p>
<p>libiconv_open vs iconv_open，卧槽！！！再对比一下前面编译时输出的错误信息：</p>
<p><font face="Courier New">/tmp/ccyQ5blC.o: In function `main&#39;:<br />
	/home/tonybai/tmp/test.c:28: undefined reference to `libiconv_open&#39;<br />
	/home/tonybai/tmp/test.c:33: undefined reference to `libiconv&#39;<br />
	/home/tonybai/tmp/test.c:38: undefined reference to `libiconv_close&#39;</font></p>
<p>大侦探醒悟了！大侦探带你还原一下真实情况。</p>
<p>我们在执行<font face="Courier New">gcc -g -o test test.c</font>时， 根据gcc -v中include search dir的顺序，gcc首先search到的是/usr/local/include/iconv.h，而这里iconv_open等函数被预编译器替换成 了libiconv_open等加上了lib前缀的函数，而这些函数符号显然在libc中是无法找到的，libc中只有不带lib前缀的 iconv_open等函数的定义。大侦探也是一时眼拙了，没有细致查看gcc的编译错误信息中的内容，这就是问题所在！</p>
<p>而<font face="Courier New">gcc -g -o test test.c -liconv</font>为何可以顺利编译通过呢？gcc是如何找到/usr/local/lib下的libiconv的呢？大侦探再次为大家还原一下真相。</p>
<p>我们在执行<font face="Courier New">gcc -g -o test test.c -liconv</font>时，gcc同 样首先search到的是/usr/local/include/iconv.h，然后编译test.c源码，ok；接下来启动ld程序进行链接；ld找 到了libiconv，ld是怎么找到iconv的呢，libiconv在/usr/local/lib下，ld显然是到这个目录下search了。我们 通过执行下面命令可以知晓ld的默认搜索路径：</p>
<p><font face="Courier New">$&gt; ld -verbose|grep SEARCH<br />
	SEARCH_DIR(&quot;/usr/x86_64-redhat-linux/lib64&quot;); SEARCH_DIR(&quot;/usr/local/lib64&quot;); SEARCH_DIR(&quot;/lib64&quot;); SEARCH_DIR(&quot;/usr/lib64&quot;); SEARCH_DIR(&quot;/usr/x86_64-redhat-linux/lib&quot;); SEARCH_DIR(&quot;/usr/lib64&quot;); SEARCH_DIR(&quot;/usr/local/lib&quot;); SEARCH_DIR(&quot;/lib&quot;); SEARCH_DIR(&quot;/usr/lib&quot;);</font></p>
<p>ld的默认search路径中有/usr/local/lib(我之前一直是以为/usr/local/lib不是gcc/ld的默认搜索路径的)，因此找到libiconv就不足为奇了。</p>
<p><b>四、问题解决</b></p>
<p>我们不想显式的加上-liconv，那如何解决这个问题呢？我们是否可以强制gcc先找到/usr/include/iconv.h呢？我们先来做个试验。</p>
<p><font face="Courier New">$ gcc -g -o test test.c -liconv -I ~/include -v<br />
	&#8230;<br />
	忽略不存在的目录&ldquo;/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../x86_64-redhat-linux/include&rdquo;<br />
	#include &quot;&#8230;&quot; 搜索从这里开始：<br />
	#include &lt;&#8230;&gt; 搜索从这里开始：<br />
	&nbsp;<b>/home/</b><b>tonybai</b><b>/include</b><br />
	&nbsp;/usr/local/include<br />
	&nbsp;/usr/lib/gcc/x86_64-redhat-linux/4.1.2/include<br />
	&nbsp;/usr/include<br />
	搜索列表结束。</font></p>
<p><font face="Courier New">&#8230;</font></p>
<p>试验结果似乎让我们觉得可行，我们通过-I指定的路径被放在了第一的位置进行search。我们来尝试一下强制gcc先search /usr/include。</p>
<p><font face="Courier New">$ gcc -g -o test test.c -I ~/include -v<br />
	&#8230;<br />
	忽略不存在的目录&ldquo;/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../x86_64-redhat-linux/include&rdquo;<br />
	忽略重复的目录&ldquo;/usr/include&rdquo;<br />
	&nbsp; 因为它是一个重复了系统目录的非系统目录<br />
	#include &quot;&#8230;&quot; 搜索从这里开始：<br />
	#include &lt;&#8230;&gt; 搜索从这里开始：<br />
	&nbsp;/usr/local/include<br />
	&nbsp;/usr/lib/gcc/x86_64-redhat-linux/4.1.2/include<br />
	&nbsp;/usr/include<br />
	搜索列表结束。<br />
	&#8230;</font></p>
<p>糟糕！/usr/include被忽略了！还是从/usr/local/include开始，方案失败。</p>
<p>似乎剩下的唯一方案就是将/usr/local/lib下的那份libiconv卸载掉！那就这么做吧^_^！</p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/04/25/a-libiconv-linkage-problem/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Go与C语言的互操作</title>
		<link>https://tonybai.com/2012/09/26/interoperability-between-go-and-c/</link>
		<comments>https://tonybai.com/2012/09/26/interoperability-between-go-and-c/#comments</comments>
		<pubDate>Wed, 26 Sep 2012 06:21:15 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[enum]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[interoperability]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Slice]]></category>
		<category><![CDATA[struct]]></category>
		<category><![CDATA[union]]></category>
		<category><![CDATA[互操作]]></category>
		<category><![CDATA[共享库]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[数组]]></category>
		<category><![CDATA[枚举]]></category>
		<category><![CDATA[汇编]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[结构体]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[联合体]]></category>
		<category><![CDATA[链接]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1051</guid>
		<description><![CDATA[Go有强烈的C背景，除了语法具有继承性外，其设计者以及其设计目标都与C语言有着千丝万缕的联系。在Go与C语言互操作(Interoperability)方面，Go更是提供了强大的支持。尤其是在Go中使用C，你甚至可以直接在Go源文件中编写C代码，这是其他语言所无法望其项背的。 &#160; 在如下一些场景中，可能会涉及到Go与C的互操作： &#160; 1、提升局部代码性能时，用C替换一些Go代码。C之于Go，好比汇编之于C。 2、嫌Go内存GC性能不足，自己手动管理应用内存。 3、实现一些库的Go Wrapper。比如Oracle提供的C版本OCI，但Oracle并未提供Go版本的以及连接DB的协议细节，因此只能通过包装C &#160;OCI版本的方式以提供Go开发者使用。 4、Go导出函数供C开发者使用(目前这种需求应该很少见)。 5、Maybe more&#8230; &#160; 一、Go调用C代码的原理 &#160; 下面是一个短小的例子： package main &#160; // #include &#60;stdio.h&#62; // #include &#60;stdlib.h&#62; /* void print(char *str) { &#160; &#160; printf(&#34;%s\n&#34;, str); } */ import &#34;C&#34; &#160; import &#34;unsafe&#34; &#160; func main() { &#160; &#160; s := &#34;Hello Cgo&#34; &#160; &#160; cs := [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://golang.org">Go</a>有强烈的<a href="http://tonybai.com/tag/c">C</a>背景，除了语法具有继承性外，其设计者以及其设计目标都与C语言有着千丝万缕的联系。在Go与C语言互操作(<a href="http://en.wikipedia.org/wiki/Language_interoperability">Interoperability</a>)方面，Go更是提供了强大的支持。尤其是在Go中使用C，你甚至可以直接在Go源文件中编写C代码，这是其他语言所无法望其项背的。</p>
<div>
<div>&nbsp;</div>
<div>在如下一些场景中，可能会涉及到Go与C的互操作：</div>
<div>&nbsp;</div>
<div>1、提升局部代码性能时，用C替换一些Go代码。C之于Go，好比汇编之于C。</div>
<div>2、嫌Go内存GC性能不足，自己手动管理应用内存。</div>
<div>3、实现一些库的Go Wrapper。比如Oracle提供的C版本OCI，但Oracle并未提供Go版本的以及连接DB的协议细节，因此只能通过包装C &nbsp;OCI版本的方式以提供Go开发者使用。</div>
<div>4、Go导出函数供C开发者使用(目前这种需求应该很少见)。</div>
<div>5、Maybe more&#8230;</div>
<div>&nbsp;</div>
<div><strong>一、Go调用C代码的原理</strong></div>
<div>&nbsp;</div>
<div>下面是一个短小的例子：</div>
<div><span style="font-family:courier new,courier,monospace;">package main</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">// #include &lt;stdio.h&gt;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// #include &lt;stdlib.h&gt;</span></div>
<div><span style="font-family:courier new,courier,monospace;">/*</span></div>
<div><span style="font-family:courier new,courier,monospace;">void print(char *str) {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; printf(&quot;%s\n&quot;, str);</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div><span style="font-family:courier new,courier,monospace;">*/</span></div>
<div><span style="font-family:courier new,courier,monospace;">import &quot;C&quot;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">import &quot;unsafe&quot;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">func main() {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; s := &quot;Hello Cgo&quot;</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; cs := C.CString(s)</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; C.print(cs)</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; C.free(unsafe.Pointer(cs))</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div>与&quot;正常&quot;Go代码相比，上述代码有几处&quot;特殊&quot;的地方：</div>
<div>1) 在开头的注释中出现了C头文件的include字样</div>
<div>2) 在注释中定义了C函数print</div>
<div>3) import的一个名为C的&quot;包&quot;</div>
<div>4) 在main函数中居然调用了上述的那个C函数-print</div>
<div>&nbsp;</div>
<div>没错，这就是在Go源码中调用C代码的步骤，可以看出我们可直接在Go源码文件中编写C代码。</div>
<div>&nbsp;</div>
<div>首先，Go源码文件中的C代码是需要用注释包裹的，就像上面的include 头文件以及print函数定义；</div>
<div>其次，import &quot;C&quot;这个语句是必须的，而且其与上面的C代码之间不能用空行分隔，必须紧密相连。这里的&quot;C&quot;不是包名，而是一种类似名字空间的概念，或可以理解为伪包，C语言所有语法元素均在该伪包下面；</div>
<div>最后，访问C语法元素时都要在其前面加上伪包前缀，比如C.uint和上面代码中的C.print、C.free等。</div>
<div>&nbsp;</div>
<div>我们如何来编译这个go源文件呢？其实与&quot;正常&quot;Go源文件没啥区别，依旧可以直接通过go build或go run来编译和执行。但实际编译过程中，go调用了名为cgo的工具，cgo会识别和读取Go源文件中的C元素，并将其提取后交给C编译器编译，最后与Go源码编译后的目标文件链接成一个可执行程序。这样我们就不难理解为何Go源文件中的C代码要用注释包裹了，这些特殊的语法都是可以被Cgo识别并使用的。</div>
<div>&nbsp;</div>
<div><strong>二、在Go中使用C语言的类型</strong></div>
<div>&nbsp;</div>
<div>1、原生类型</div>
<div>&nbsp;</div>
<div>* 数值类型</div>
<div>在Go中可以用如下方式访问C原生的数值类型：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">C.char,</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.schar (signed char),</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.uchar (unsigned char),</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.short,</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.ushort (unsigned short),</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.int, C.uint (unsigned int),</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.long,</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.ulong (unsigned long),</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.longlong (long long),</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.ulonglong (unsigned long long),</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.float,</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.double</span></div>
<div>&nbsp;</div>
<div>Go的数值类型与C中的数值类型不是一一对应的。因此在使用对方类型变量时少不了显式转型操作，如Go doc中的这个例子：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">func Random() int {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; return int(C.random())//C.long -&gt; Go的int</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">func Seed(i int) {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; C.srandom(C.uint(i))//Go的uint -&gt; C的uint</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div>* 指针类型</div>
<div>原生数值类型的指针类型可按Go语法在类型前面加上*，比如var p *C.int。而void*比较特殊，用Go中的unsafe.Pointer表示。任何类型的指针值都可以转换为unsafe.Pointer类型，而unsafe.Pointer类型值也可以转换为任意类型的指针值。unsafe.Pointer还可以与uintptr这个类型做相互转换。由于unsafe.Pointer的指针类型无法做算术操作，转换为uintptr后可进行算术操作。</div>
<div>&nbsp;</div>
<div>* 字符串类型</div>
<div>C语言中并不存在正规的字符串类型，在C中用带结尾&#39;\0&#39;的字符数组来表示字符串；而在Go中，string类型是原生类型，因此在两种语言互操作是势必要做字符串类型的转换。</div>
<div>&nbsp;</div>
<div>通过C.CString函数，我们可以将Go的string类型转换为C的&quot;字符串&quot;类型，再传给C函数使用。就如我们在本文开篇例子中使用的那样：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">s := &quot;Hello Cgo\n&quot;</span></div>
<div><span style="font-family:courier new,courier,monospace;">cs := C.CString(s)</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.print(cs)</span></div>
<div>&nbsp;</div>
<div>不过这样转型后所得到的C字符串cs并不能由Go的gc所管理，我们必须手动释放cs所占用的内存，这就是为何例子中最后调用C.free释放掉cs的原因。在C内部分配的内存，Go中的GC是无法感知到的，因此要记着释放。</div>
<div>&nbsp;</div>
<div>通过C.GoString可将C的字符串(*C.char)转换为Go的string类型，例如：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">// #include &lt;stdio.h&gt;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// #include &lt;stdlib.h&gt;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// char *foo = &quot;hellofoo&quot;;</span></div>
<div><span style="font-family:courier new,courier,monospace;">import &quot;C&quot;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">import &quot;fmt&quot;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">func main() {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&#8230; &#8230;</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; fmt.Printf(&quot;%s\n&quot;, C.GoString(C.foo))</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div>* 数组类型</div>
<div>C语言中的数组与Go语言中的数组差异较大，后者是值类型，而前者与C中的指针大部分场合都可以随意转换。目前似乎无法直接显式的在两者之间进行转型，官方文档也没有说明。但我们可以通过编写转换函数，将C的数组转换为Go的Slice(由于Go中数组是值类型，其大小是静态的，转换为Slice更为通用一些)，下面是一个整型数组转换的例子：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">// int cArray[] = {1, 2, 3, 4, 5, 6, 7};</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">func CArrayToGoArray(cArray unsafe.Pointer, size int) (goArray []int) {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; p := uintptr(cArray)</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; for i :=0; i &lt; size; i++ {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; j := *(*int)(unsafe.Pointer(p))</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; goArray = append(goArray, j)</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; p += unsafe.Sizeof(j)</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; }</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; return</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">func main() {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &#8230; &#8230;</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; goArray := CArrayToGoArray(unsafe.Pointer(&amp;C.cArray[0]), 7)</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; fmt.Println(goArray)</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div>执行结果输出：[1 2 3 4 5 6 7]</div>
<div>&nbsp;</div>
<div>这里要注意的是：Go编译器并不能将C的cArray自动转换为数组的地址，所以不能像在C中使用数组那样将数组变量直接传递给函数，而是将数组第一个元素的地址传递给函数。</div>
<div>&nbsp;</div>
<div>2、自定义类型</div>
<div>&nbsp;</div>
<div>除了原生类型外，我们还可以访问C中的自定义类型。</div>
<div>&nbsp;</div>
<div>* 枚举(enum)</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">// enum color {</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp;RED,</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp;BLUE,</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp;YELLOW</span></div>
<div><span style="font-family:courier new,courier,monospace;">// };</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">var e, f, g C.enum_color = C.RED, C.BLUE, C.YELLOW</span></div>
<div><span style="font-family:courier new,courier,monospace;">fmt.Println(e, f, g)</span></div>
<div>&nbsp;</div>
<div>输出：0 1 2</div>
<div>&nbsp;</div>
<div>对于具名的C枚举类型，我们可以通过C.enum_xx来访问该类型。如果是匿名枚举，则似乎只能访问其字段了。</div>
<div>&nbsp;</div>
<div>* 结构体(struct)</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">// struct employee {</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp; char *id;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp; int &nbsp;age;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// };</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">id := C.CString(&quot;1247&quot;)</span></div>
<div><span style="font-family:courier new,courier,monospace;">var employee C.struct_employee = C.struct_employee{id, 21}</span></div>
<div><span style="font-family:courier new,courier,monospace;">fmt.Println(C.GoString(employee.id))</span></div>
<div><span style="font-family:courier new,courier,monospace;">fmt.Println(employee.age)</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.free(unsafe.Pointer(id))</span></div>
<div>&nbsp;</div>
<div>输出：</div>
<div>1247</div>
<div>21</div>
<div>&nbsp;</div>
<div>和enum类似，我们可以通过C.struct_xx来访问C中定义的结构体类型。</div>
<div>&nbsp;</div>
<div>* 联合体(union)</div>
<div>&nbsp;</div>
<div>这里我试图用与访问struct相同的方法来访问一个C的union：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">// #include &lt;stdio.h&gt;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// union bar {</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp; &nbsp; &nbsp;char &nbsp; c;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp; &nbsp; &nbsp;int &nbsp; &nbsp;i;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp; &nbsp; &nbsp;double d;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// };</span></div>
<div><span style="font-family:courier new,courier,monospace;">import &quot;C&quot;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">func main() {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; var b *C.union_bar = new(C.union_bar)</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; b.c = 4</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; fmt.Println(b)</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div>不过编译时，go却报错：b.c undefined (type *[8]byte has no field or method c)。从报错的信息来看，Go对待union与其他类型不同，似乎将union当成[N]byte来对待，其中N为union中最大字段的size(圆整后的)，因此我们可以按如下方式处理C.union_bar：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">func main() {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; var b *C.union_bar = new(C.union_bar)</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; b[0] = 13</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; b[1] = 17</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; fmt.Println(b)</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div>输出：&amp;[13 17 0 0 0 0 0 0]</div>
<div>&nbsp;</div>
<div>* typedef</div>
<div>在Go中访问使用用typedef定义的别名类型时，其访问方式与原实际类型访问方式相同。如：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">// typedef int myint;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">var a C.myint = 5</span></div>
<div><span style="font-family:courier new,courier,monospace;">fmt.Println(a)</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">// typedef struct employee myemployee;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">var m C.struct_myemployee</span></div>
<div>&nbsp;</div>
<div>从例子中可以看出，对原生类型的别名，直接访问这个新类型名即可。而对于复合类型的别名，需要根据原复合类型的访问方式对新别名进行访问，比如myemployee实际类型为struct，那么使用myemployee时也要加上struct_前缀。</div>
<div>&nbsp;</div>
<div><strong>三、Go中访问C的变量和函数</strong></div>
<div>&nbsp;</div>
<div>实际上上面的例子中我们已经演示了在Go中是如何访问C的变量和函数的，一般方法就是加上C前缀即可，对于C标准库中的函数尤其是这样。不过虽然我们可以在Go源码文件中直接定义C变量和C函数，但从代码结构上来讲，大量的在Go源码中编写C代码似乎不是那么&ldquo;专业&rdquo;。那如何将C函数和变量定义从Go源码中分离出去单独定义呢？我们很容易想到将C的代码以共享库的形式提供给Go源码。</div>
<div>&nbsp;</div>
<div>Cgo提供了#cgo指示符可以指定Go源码在编译后与哪些<a href="http://tonybai.com/2010/12/13/also-talk-about-shared-library/">共享库</a>进行链接。我们来看一下例子：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">package main</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">// #cgo LDFLAGS: -L ./ -lfoo</span></div>
<div><span style="font-family:courier new,courier,monospace;">// #include &lt;stdio.h&gt;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// #include &lt;stdlib.h&gt;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// #include &quot;foo.h&quot;</span></div>
<div><span style="font-family:courier new,courier,monospace;">import &quot;C&quot;</span></div>
<div><span style="font-family:courier new,courier,monospace;">import &quot;fmt&ldquo;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">func main() {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; fmt.Println(C.count)</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; C.foo()</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div>我们看到上面例子中通过#cgo指示符告诉go编译器链接当前目录下的libfoo共享库。C.count变量和C.foo函数的定义都在libfoo共享库中。我们来创建这个共享库：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">// foo.h</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">int count;</span></div>
<div><span style="font-family:courier new,courier,monospace;">void foo();</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">//foo.c</span></div>
<div><span style="font-family:courier new,courier,monospace;">#include &quot;foo.h&quot;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">int count = 6;</span></div>
<div><span style="font-family:courier new,courier,monospace;">void foo() {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; printf(&quot;I am foo!\n&quot;);</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">$&gt; gcc -c foo.c</span></div>
<div><span style="font-family:courier new,courier,monospace;">$&gt; ar rv libfoo.a foo.o</span></div>
<div>&nbsp;</div>
<div>我们首先创建一个静态共享库libfoo.a，不过在编译Go源文件时我们遇到了问题：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">$&gt; go build foo.go</span></div>
<div><span style="font-family:courier new,courier,monospace;"># command-line-arguments</span></div>
<div><span style="font-family:courier new,courier,monospace;">/tmp/go-build565913544/command-line-arguments.a(foo.cgo2.)(.text): foo: not defined</span></div>
<div><span style="font-family:courier new,courier,monospace;">foo(0): not defined</span></div>
<div>&nbsp;</div>
<div>提示foo函数未定义。通过-x选项打印出具体的编译细节，也未找出问题所在。不过在Go的问题列表中我发现了一个issue(http://code.google.com/p/go/issues/detail?id=3755)，上面提到了目前Go的版本不支持链接静态共享库。</div>
<div>&nbsp;</div>
<div>那我们来创建一个动态共享库试试：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">$&gt; gcc -c foo.c</span></div>
<div><span style="font-family:courier new,courier,monospace;">$&gt; gcc -shared -Wl,-soname,libfoo.so -o libfoo.so &nbsp;foo.o</span></div>
<div>&nbsp;</div>
<div>再编译foo.go，的确能够成功。执行foo。</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">$&gt; go build foo.go &amp;&amp; go</span></div>
<div><span style="font-family:courier new,courier,monospace;">6</span></div>
<div><span style="font-family:courier new,courier,monospace;">I am foo!</span></div>
<div>&nbsp;</div>
<div>还有一点值得注意，那就是Go支持多返回值，而C中并没不支持。因此当将C函数用在多返回值的调用中时，C的errno将作为err返回值返回，下面是个例子：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">package main</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">// #include &lt;stdlib.h&gt;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// #include &lt;stdio.h&gt;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// #include &lt;errno.h&gt;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// int foo(int i) {</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp;errno = 0;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp;if (i &gt; 5) {</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp; &nbsp; &nbsp;errno = 8;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp; &nbsp; &nbsp;return i &#8211; 5;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp;} else {</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp; &nbsp; &nbsp;return i;</span></div>
<div><span style="font-family:courier new,courier,monospace;">// &nbsp; &nbsp;}</span></div>
<div><span style="font-family:courier new,courier,monospace;">//}</span></div>
<div><span style="font-family:courier new,courier,monospace;">import &quot;C&quot;</span></div>
<div><span style="font-family:courier new,courier,monospace;">import &quot;fmt&quot;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">func main() {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; i, err := C.foo(C.int(8))</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; if err != nil {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(err)</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; } else {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(i)</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; }</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">$&gt; go run foo.go</span></div>
<div><span style="font-family:courier new,courier,monospace;">exec format error</span></div>
<div>&nbsp;</div>
<div>errno为8，其含义在errno.h中可以找到：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">#define ENOEXEC &nbsp; &nbsp; &nbsp;8 &nbsp;/* Exec format error */</span></div>
<div>&nbsp;</div>
<div>的确是&ldquo;exec format error&rdquo;。</div>
<div>&nbsp;</div>
<div><strong>四、C中使用Go函数</strong></div>
<div>&nbsp;</div>
<div>与在Go中使用C源码相比，在C中使用Go函数的场合较少。在Go中，可以使用&quot;export + 函数名&quot;来导出Go函数为C所使用，看一个简单例子：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">package main</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">/*</span></div>
<div><span style="font-family:courier new,courier,monospace;">#include &lt;stdio.h&gt;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">extern void GoExportedFunc();</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">void bar() {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; printf(&quot;I am bar!\n&quot;);</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; GoExportedFunc();</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div><span style="font-family:courier new,courier,monospace;">*/</span></div>
<div><span style="font-family:courier new,courier,monospace;">import &quot;C&quot;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">import &quot;fmt&quot;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">//export GoExportedFunc</span></div>
<div><span style="font-family:courier new,courier,monospace;">func GoExportedFunc() {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(&quot;I am a GoExportedFunc!&quot;)</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">func main() {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; C.bar()</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div>不过当我们编译该Go文件时，我们得到了如下错误信息：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;"># command-line-arguments</span></div>
<div><span style="font-family:courier new,courier,monospace;">/tmp/go-build163255970/command-line-arguments/_obj/bar.cgo2.o: In function `bar&#39;:</span></div>
<div><span style="font-family:courier new,courier,monospace;">./bar.go:7: multiple definition of `bar&#39;</span></div>
<div><span style="font-family:courier new,courier,monospace;">/tmp/go-build163255970/command-line-arguments/_obj/_cgo_export.o:/home/tonybai/test/go/bar.go:7: first defined here</span></div>
<div><span style="font-family:courier new,courier,monospace;">collect2: ld returned 1 exit status</span></div>
<div>&nbsp;</div>
<div>代码似乎没有任何问题，但就是无法通过编译，总是提示&ldquo;多重定义&rdquo;。翻看Cgo的文档，找到了些端倪。原来</div>
<div>&nbsp;</div>
<div><em>There is a limitation: if your program uses any //export directives, then the C code in the comment may only include declarations (extern int f();), not definitions (int f() { return 1; }).</em></div>
<div>&nbsp;</div>
<div>似乎是// extern int f()与//export f不能放在一个Go源文件中。我们把bar.go拆分成bar1.go和bar2.go两个文件：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">// bar1.go</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">package main</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">/*</span></div>
<div><span style="font-family:courier new,courier,monospace;">#include &lt;stdio.h&gt;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">extern void GoExportedFunc();</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">void bar() {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; printf(&quot;I am bar!\n&quot;);</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; GoExportedFunc();</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div><span style="font-family:courier new,courier,monospace;">*/</span></div>
<div><span style="font-family:courier new,courier,monospace;">import &quot;C&quot;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">func main() {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; C.bar()</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">// bar2.go</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">package main</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">import &quot;C&quot;</span></div>
<div><span style="font-family:courier new,courier,monospace;">import &quot;fmt&quot;</span></div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">//export GoExportedFunc</span></div>
<div><span style="font-family:courier new,courier,monospace;">func GoExportedFunc() {</span></div>
<div><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(&quot;I am a GoExportedFunc!&quot;)</span></div>
<div><span style="font-family:courier new,courier,monospace;">}</span></div>
<div>&nbsp;</div>
<div>编译执行：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">$&gt; go build -o bar bar1.go bar2.go</span></div>
<div><span style="font-family:courier new,courier,monospace;">$&gt; bar</span></div>
<div><span style="font-family:courier new,courier,monospace;">I am bar!</span></div>
<div><span style="font-family:courier new,courier,monospace;">I am a GoExportedFunc!</span></div>
<div>&nbsp;</div>
<div>个人觉得目前Go对于导出函数供C使用的功能还十分有限，两种语言的调用约定不同，类型无法一一对应以及Go中类似Gc这样的高级功能让导出Go函数这一功能难于完美实现，导出的函数依旧无法完全脱离Go的环境，因此实用性似乎有折扣。</div>
<div>&nbsp;</div>
<div><strong>五、其他</strong></div>
<div>&nbsp;</div>
<div>虽然Go提供了强大的与C互操作的功能，但目前依旧不完善，比如不支持在Go中直接调用可变个数参数的函数(issue975)，如printf(因此，文档中多用fputs)。</div>
<div>&nbsp;</div>
<div>这里的建议是：尽量缩小Go与C间互操作范围。</div>
<div>&nbsp;</div>
<div>什么意思呢？如果你在Go中使用C代码时，那么尽量在C代码中调用C函数。Go只使用你封装好的一个C函数最好。不要像下面代码这样：</div>
<div>&nbsp;</div>
<div><span style="font-family:courier new,courier,monospace;">C.fputs(&#8230;)</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.atoi(..)</span></div>
<div><span style="font-family:courier new,courier,monospace;">C.malloc(..)</span></div>
<div>&nbsp;</div>
<div>而是将这些C函数调用封装到一个C函数中，Go只知道这个C函数即可。</div>
<div>&nbsp;</div>
<div>C.foo(..)</div>
<div>&nbsp;</div>
<div>相反，在C中使用Go导出的函数也是一样。</div>
</div>
<p>&nbsp;</p>
<p style='text-align:left'>&copy; 2012, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2012/09/26/interoperability-between-go-and-c/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>分享一个Oracle OCI库的BUG</title>
		<link>https://tonybai.com/2009/07/31/a-bug-of-oracle-oci-lib/</link>
		<comments>https://tonybai.com/2009/07/31/a-bug-of-oracle-oci-lib/#comments</comments>
		<pubDate>Fri, 31 Jul 2009 03:10:51 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Bug]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Debug]]></category>
		<category><![CDATA[Oracle]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Solaris]]></category>
		<category><![CDATA[共享库]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[链接]]></category>

		<guid isPermaLink="false">http://tonybai.com/2009/07/31/%e5%88%86%e4%ba%ab%e4%b8%80%e4%b8%aaoracle-oci%e5%ba%93%e7%9a%84bug/</guid>
		<description><![CDATA[上周测试组反馈在一台HP X86-64主机Solaris 10 for X86环境下部署的应用无法连接Oracle数据库，错误码ORA-12154。而另外一个产品的部署在这台主机上的应用却能正常连接到数据库。本周安排专人对该问题进行查找，在先后排除了用户环境设置、Oracle数据库服务端等问题后，我们最终把目光集中在了Oracle客户端的OCI库上。<br /><br />
定位过程如下：<br />1、SQLPLUS可以访问数据库；<br />2、同环境下另一个应用可以访问数据库；<br />以...]]></description>
			<content:encoded><![CDATA[<p>上周测试组反馈在一台HP X86-64主机Solaris 10 for X86环境下部署的应用无法连接Oracle数据库，错误码ORA-12154。而另外一个产品的部署在这台主机上的应用却能正常连接到数据库。本周安排专人对该问题进行查找，在先后排除了用户环境设置、Oracle数据库服务端等问题后，我们最终把目光集中在了Oracle客户端的OCI库上。</p>
<p>定位过程如下：<br />1、SQLPLUS可以访问数据库；<br />2、同环境下另一个应用可以访问数据库；<br />以上证明用户环境和tnsnames.ora配置没有问题；<br />3、通过抓包未发现客户端有到Oracle服务端的链接和数据传输，所以该问题应该与Oracle Server端一毛钱关系都没有；<br />4、发现我们产品的应用使用的是32bit库编译的，而另外一个产品的应用使用的是64bit库，但两个产品底层调用都是一样的；<br />5、基本锁定是该主机上装的Oracle OCI 32bit库有bug；<br />6、我们的资深系统工程师在Oracle官方找到了该问题的根源；<br />7、安装新patch后，应用顺利连接到Oracle Server，问题解决。</p>
<p>Oracle官方对该问题的说明摘录如下：<br />Solaris x86-64: Running 32-bit Applications Connecting to Database Using TNS Naming Adapter Fails With Segmentation Fault (SIGSEGV) or ORA-12154 <br />&nbsp; Doc ID:&nbsp; 388631.1 Type:&nbsp; PROBLEM <br />&nbsp; Modified Date :&nbsp; 23-OCT-2007 Status:&nbsp; PUBLISHED </p>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br />Applies to: <br />Oracle Server &#8211; Enterprise Edition &#8211; Version: 10.2.0.2</p>
<p>Symptoms<br />Running 32-bit applications connecting to Database using TNS Naming Adapter Fails With Segmentation Violation (SIGSEGV)</p>
<p>Segmentation Fault(coredump)</p>
<p>Running 64-bit work as expected.</p>
<p>Other symptoms would be</p>
<p>ORA-12154: TNS:could not resolve the connect identifier specified</p>
<p>Cause<br />This has been identified to be caused by<br />Bug 5389730 10.2.0.1 32BIT OCI EXECUTABLES FAILS WITH ORA-12154 ON SOLARIS 10 X86-64(AMD64) </p>
<p>TNS Naming Adapter was not included within the 32-bit Naming Libraries.</p>
<p>Solution<br />This is fixed Oracle11g Client 11.0.</p>
<p>There exists patches for 10.2.0.2 and 10.2.0.3:</p>
<p>download and installPatch 5389730 with opatch or </p>
<p>To implement the solution manually, please execute the following steps:</p>
<p>Download Patch 5389730<br />cp $ORACLE_HOME/network/lib/ins_net_client.mk<br />$ORACLE_HOME/network/lib/ins_net_client.mk.prePatch_5389730<br />extract ins_net_client.mk into $ORACLE_HOME/network/lib/ins_net_client.mk<br />cd $ORACLE_HOME/network/lib<br />make -f ins_net_client.mk nnfgt.o<br />Which update (check this)<br />$ORACLE_HOME/lib/libn10.a and $ORACLE_HOME/lib32/libn10.a<br />make -f ins_net_client.mk client_sharedlib</p>
<p>which update (check this)<br />#$ORACLE_HOME/lib32/libclntsh.so<br />#$ORACLE_HOME/lib32/libclntsh.so.10.1<br />#$ORACLE_HOME/lib/libclntsh.so<br />#$ORACLE_HOME/lib/libclntsh.so.10.1</p>
<p>Check that executable is loading $ORACLE_HOME/lib32/libclntsh.so.10.1 by ldd &#8216;executable&#8217;</p>
<p>All dynamically linked applications that use libclntsh should work now. <br />Static linked applications, need to be relinked with the new libraries.</p>
<p style='text-align:left'>&copy; 2009, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2009/07/31/a-bug-of-oracle-oci-lib/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
