关于 go 命令

Go 发行版包含一个名为“go”的命令,它可以自动下载、构建、安装和测试 Go 软件包和命令。本文档讨论了我们编写新命令的原因、它是什么、它不是什么以及如何使用它。

动机

您可能已经看过 Rob Pike 在早期的 Go 演讲中开玩笑说,Go 的想法是在等待大型 Google 服务器编译时产生的。这确实是 Go 的动机:构建一种非常适合构建 Google 编写和运行的大型软件的语言。从一开始就很清楚,这种语言必须提供一种明确表达代码库之间依赖关系的方法,因此有了包分组和显式导入块。从一开始也很清楚,您可能需要任意语法来描述要导入的代码;这就是导入路径是字符串文字的原因。

Go 从一开始的一个明确目标就是能够仅使用源代码本身中找到的信息来构建 Go 代码,而不需要编写 Makefile 或许多现代 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 命令子命令非常重要,我们将在下面看到。

第三,源代码树中的每个目录对应一个包。通过将目录限制为一个包,我们不必创建混合导入路径,该路径首先指定目录,然后指定该目录中的包。此外,大多数文件管理工具和 UI 都以目录为基本单元进行操作。将基本 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 命令文档