Go Path 与目录结构
Go path 是一组包含 Go 源码的目录树,用于解析在标准库中找不到的导入。默认值是 GOPATH 环境变量的值,按操作系统习惯解释为路径列表:在 Unix 上是冒号分隔的字符串,在 Windows 上是分号分隔的字符串。
Go path 中列出的每个目录都必须有约定的结构:
- src/ 目录存放源代码,
src之下的路径决定导入路径或可执行文件名; - pkg/ 目录存放安装后的包对象,与 Go 源码树一样,每个目标操作系统与架构组合都有自己的子目录(
pkg/GOOS_GOARCH); - bin/ 目录存放编译好的命令,命令名只取源码目录的最后一段,而不是完整路径。
举一个例子来说明这种映射关系。假设 GOPATH=/home/user/gocode:
/home/user/gocode/
src/
foo/
bar/ (package bar 的 go 代码)
x.go
quux/ (package main 的 go 代码)
y.go
bin/
quux (安装后的可执行命令)
pkg/
linux_amd64/
foo/
bar.a (安装后的包对象)
可以看到,源码位于 DIR/src/foo/bar 的包可以按 foo/bar 导入,编译产物安装到 DIR/pkg/GOOS_GOARCH/foo/bar.a。源码位于 DIR/src/foo/quux 的命令会被安装为 DIR/bin/quux,而不是 DIR/bin/foo/quux——foo/ 被剥离了,这样才能把 DIR/bin 直接加入 PATH 使用。
构建约束(build tag)
构建约束也叫 build tag,是一行以特定注释形式出现的标记,用于列出文件被包含进该包的条件。它可以出现在任何类型的源文件中,但必须靠近文件顶部,前面只能有空行和其他行注释。这意味着在 Go 文件中,构建约束必须写在 package 子句之前。
为了与包的文档注释区分开,一系列构建约束后面必须跟一个空行。
求值规则:或、与、非
构建约束的求值方式是:按空格分隔的选项之间是「或」,每个选项内部按逗号分隔的条件之间是「与」,条件前加感叹号表示取反。例如:
// +build linux,386 darwin,!cgo
对应的布尔表达式是:
(linux AND 386) OR (darwin AND (NOT cgo))
一个文件可以有多条构建约束,整体约束是各条约束的「与」:
// +build linux darwin
// +build 386
对应:
(linux OR darwin) AND 386
满足条件的词有哪些
在一次特定构建中,下列词被视为满足:
- 目标操作系统,如 runtime.GOOS 所拼写;
- 目标架构,如 runtime.GOARCH 所拼写;
- 所使用的编译器,即 gc 或 gccgo;
- 当 CgoEnabled 为真时的 cgo;
- 从某一 Go 版本起的版本标记,例如 go1.1、go1.2 依此类推;
- 构建上下文 BuildTags 中列出的任何额外词。
文件名的隐式约束
除了显式写构建约束,文件名本身也可以携带约束。如果文件名在去掉扩展名和可能的 _test 后缀之后,匹配以下模式:
*_GOOS
*_GOARCH
*_GOOS_GOARCH
例如 source_windows_amd64.go,那么该文件被认为隐含要求这些条件,这是对文件内显式约束的补充。命名 dns_windows.go 会让文件只在为 Windows 构建该包时被包含;同理 math_386.s 只在构建 32 位 x86 版本时被包含。
要让文件不参与构建,可以这样写:
// +build ignore
任何其他不满足的词都可以,但 ignore 是约定俗成的写法。
再看两个实用的组合。只在启用 cgo 且仅在 Linux 与 OS X 上构建:
// +build linux,cgo darwin,cgo
这样的文件通常与另一个文件搭配,后者为其他系统实现默认功能,其约束写作:
// +build !linux,!darwin !cgo
另外,使用 GOOS=android 时,除了匹配 android 的标记与文件外,还会匹配 GOOS=linux 的标记与文件。这一点在移动端开发中容易踩坑。
仅二进制包
可以在不包含编译该包所用源码的情况下,以二进制形式分发包。做法是让包附带一个未被构建约束排除的源文件,其中包含 //go:binary-only-package 注释。
与构建约束类似,该注释必须靠近文件顶部,前面只能有空行和其他行注释,并且后面要跟一个空行,以便与包文档分开。不同之处在于,这个注释只在非测试的 Go 源文件中被识别。
因此,仅二进制包的最小源码是:
//go:binary-only-package
package mypkg
源码中可以包含额外的 Go 代码,这些代码永远不会被编译,但会被 godoc 之类的工具处理,可能对最终用户文档有用。
小结
构建约束是 Go 用来表达「这段代码在什么环境下生效」的机制。理解它的或与非求值规则,以及文件名带来的隐式约束,能避免大量「在我机器上能编过」的困惑;而仅二进制包则为商业分发提供了一种不暴露源码的选项。
| src 目录 | 存放源码,目录路径决定导入路径或可执行命令名 |
|---|---|
| pkg 目录 | 存放安装后的包对象,按 GOOS_GOARCH 分子目录 |
| bin 目录 | 存放编译命令,命令名只取源码目录最后一段 |
| 求值规则 | 空格分隔为或,逗号分隔为与,感叹号表示取反,多条约束之间为与 |
| 隐式约束 | 文件名匹配 *_GOOS / *_GOARCH / *_GOOS_GOARCH 时自动附加对应约束 |