关于 go 命令
Go 发行版包含一个名为“go
”的命令,它可以自动下载、构建、安装和测试 Go 包和命令。本文档将讨论我们为何编写这个新命令、它是什么、它不是什么,以及如何使用它。
动机
您可能看过早期 Go 的演讲,其中 Rob Pike 曾开玩笑说,Go 的想法是在等待一个大型 Google 服务器编译时产生的。这确实是 Go 的动机:构建一种语言,能够很好地用于构建 Google 编写和运行的大型软件。从一开始就很清楚,这样的语言必须提供一种清晰表达代码库之间依赖关系的方法,因此出现了包分组和显式的导入块。从一开始也很清楚,您可能希望用任意语法来描述正在导入的代码;这就是为什么导入路径是字符串字面量的原因。
Go 从一开始的一个明确目标就是,仅凭源代码本身的信息就能构建 Go 代码,而无需编写 makefile 或其许多现代替代品。如果 Go 需要一个配置文件来解释如何构建您的程序,那么 Go 就失败了。
起初,还没有 Go 编译器,最初的开发专注于构建一个编译器,然后为其构建库。为了方便起见,我们推迟了使用 make 和编写 makefile 来自动化构建 Go 代码。当编译单个包涉及多次调用 Go 编译器时,我们甚至使用了一个程序为我们编写 makefile。如果您深入挖掘存储库历史,就可以找到它。
新 go 命令的目的是让我们回归这个理想,即 Go 程序应该无需任何配置或开发者额外的努力(除了编写必要的导入语句)即可编译。
配置与约定
实现无配置系统的简单性的方法是建立约定。该系统仅在其遵循这些约定的范围内运行。当我们首次推出 Go 时,许多人发布的包为了被使用,必须安装在特定的位置、使用特定的名称、借助特定的构建工具。这是可以理解的:在大多数其他语言中都是这样工作的。在过去的几年里,我们一直提醒人们注意 goinstall
命令(现在已被 go get
取代)及其约定:首先,导入路径以一种已知的方式从源代码的 URL 派生;其次,在本地文件系统中存储源代码的位置以一种已知的方式从导入路径派生;第三,源代码树中的每个目录都对应一个包;第四,该包仅使用源代码中的信息进行构建。如今,绝大多数包都遵循这些约定。Go 生态系统因此变得更简单、更强大。
我们收到了许多要求,允许在包目录中提供一个 makefile,以提供比源代码中多一点额外的配置。但这会引入新的规则。由于我们没有满足这些要求,因此我们能够编写 go 命令并消除对 make 或任何其他构建系统的使用。
重要的是要理解,go 命令不是一个通用的构建工具。它无法配置,并且除了 Go 包之外,它不尝试构建任何东西。这些是重要的简化假设:它们不仅简化了实现,更重要的是,简化了工具本身的使用。
Go 的约定
go
命令要求代码遵循几个关键的、成熟的约定。
第一,导入路径以一种已知的方式从源代码的 URL 派生。对于 Bitbucket、GitHub、Google Code 和 Launchpad,存储库的根目录通过存储库的主 URL 识别,不包含 https://
前缀。子目录是通过在该路径上添加来命名的。例如,Google 日志包 glog
的源代码通过运行以下命令获得:
git clone https://github.com/golang/glog因此,glog 包的导入路径是“
github.com/golang/glog
”。
这些路径比较长,但作为回报,我们获得了导入路径的自动管理命名空间,以及像 go 命令这样的工具能够查看不熟悉的导入路径并推断出源代码的获取位置的能力。
第二,在本地文件系统中存储源代码的位置以一种已知的方式从导入路径派生,具体为 $GOPATH/src/<import-path>
。如果未设置,$GOPATH
默认为用户主目录中名为 go
的子目录。如果 $GOPATH
被设置为路径列表,go 命令会尝试搜索该列表中的每个目录下的 <dir>/src/<import-path>
。
按照约定,这些树中的每个目录都包含一个名为“bin
”的顶级目录,用于存放编译好的可执行文件,一个名为“pkg
”的顶级目录,用于存放可被导入的编译好的包,以及“src
”目录,用于存放包的源文件。强制执行此结构使我们能够保持每个目录树的独立性:编译形式和源代码始终在一起。
这些命名约定还使我们能够反向工作,从目录名到其导入路径。这个映射对于 go 命令的许多子命令很重要,如下文所示。
第三,源代码树中的每个目录对应一个包。通过将目录限制为一个包,我们不必创建混合导入路径,即先指定目录,然后指定该目录内的包。此外,大多数文件管理工具和用户界面都以目录为基本单位进行操作。将 Go 的基本单元——包——与文件系统结构绑定,意味着文件系统工具就成了 Go 包工具。复制、移动或删除一个包相当于复制、移动或删除一个目录。
第四,每个包仅使用源文件中存在的信息进行构建。这使得工具更能适应不断变化的构建环境和条件。例如,如果我们允许额外的配置,如编译器标志或命令行配方,那么每次构建工具更改时都需要更新该配置;它还将与特定工具链的使用紧密绑定。
开始使用 go 命令
最后,快速浏览一下如何使用 go 命令。如上所述,Unix 上的默认 $GOPATH
是 $HOME/go
。我们将在此存储程序。要使用其他位置,您可以设置 $GOPATH
;有关详细信息,请参阅 如何编写 Go 代码。
我们首先添加一些源代码。假设我们要使用 codesearch 项目中的索引库以及一个左倾红黑树。我们可以使用“go get
”子命令安装两者:
$ go get github.com/google/codesearch/index $ go get github.com/petar/GoLLRB/llrb $
这两个项目现在都已下载并安装到 $HOME/go
中,其中包含 src/github.com/google/codesearch/index/
和 src/github.com/petar/GoLLRB/llrb/
这两个目录,以及这些库及其依赖项的编译好的包(在 pkg/
中)。
因为我们使用版本控制系统(Mercurial 和 Git)来签出源代码,所以源代码树还包含相应存储库中的其他文件,例如相关包。“go list
”子命令列出与其参数对应的导入路径,而模式“./...
”表示从当前目录(“./
”)开始,查找该目录下的所有包(“...
”)
$ cd $HOME/go/src $ go list ./... github.com/google/codesearch/cmd/cgrep github.com/google/codesearch/cmd/cindex github.com/google/codesearch/cmd/csearch github.com/google/codesearch/index github.com/google/codesearch/regexp github.com/google/codesearch/sparse github.com/petar/GoLLRB/example github.com/petar/GoLLRB/llrb $
我们也可以测试这些包:
$ go test ./... ? github.com/google/codesearch/cmd/cgrep [no test files] ? github.com/google/codesearch/cmd/cindex [no test files] ? github.com/google/codesearch/cmd/csearch [no test files] ok github.com/google/codesearch/index 0.203s ok github.com/google/codesearch/regexp 0.017s ? github.com/google/codesearch/sparse [no test files] ? github.com/petar/GoLLRB/example [no test files] ok github.com/petar/GoLLRB/llrb 0.231s $
如果 go 子命令在未列出路径的情况下被调用,它将操作当前目录:
$ cd github.com/google/codesearch/regexp $ go list github.com/google/codesearch/regexp $ go test -v === RUN TestNstateEnc --- PASS: TestNstateEnc (0.00s) === RUN TestMatch --- PASS: TestMatch (0.00s) === RUN TestGrep --- PASS: TestGrep (0.00s) PASS ok github.com/google/codesearch/regexp 0.018s $ go install $
“go install
”子命令将包的最新副本安装到 pkg 目录。因为 go 命令可以分析依赖图,“go install
”还会递归地安装该包所导入但已过期的任何包。
请注意,“go install
”能够确定当前目录中包的导入路径名称,这是由于目录命名约定。如果我们能够选择存储源代码的目录名称,并且我们可能不会选择如此长的名称,那会更方便,但这种能力会增加工具的额外配置和复杂性。键入一两个额外的目录名是为了换取更高的简洁性和强大的功能,这是值得的。
局限性
如上所述,go 命令不是一个通用的构建工具。特别是,它没有任何在构建 *期间* 生成 Go 源文件的功能,尽管它确实提供了 go
generate
,它可以自动化在构建 *之前* 创建 Go 文件。对于更高级的构建设置,您可能需要编写一个 makefile(或您选择的构建工具的配置文件)来运行任何创建 Go 文件的工具,然后将这些生成的源文件提交到您的存储库。这会给您(包作者)带来更多工作,但对您的用户来说工作量大大减少,他们可以使用“go get
”而无需获取和构建任何额外的工具。
更多信息
有关更多信息,请阅读 如何编写 Go 代码,并参阅 go 命令文档。