|
| 1 | + 5.1.1 go build |
| 2 | + |
| 3 | + |
| 4 | + |
| 5 | +```go build```命令用来编译指定的代码或代码包及它们的依赖包。 |
| 6 | + |
| 7 | +例如,如果我们在执行```go build```命令时不后跟任何代码包,那么命令将试图编译当前目录所对应的代码包。例如,我们想编译goc2p项目的代码包```logging```。其中一个方法是进入```logging```目录并直接执行该命令: |
| 8 | + |
| 9 | + hc@ubt:~/golang/goc2p/src/logging$ go build |
| 10 | + |
| 11 | +因为在代码包```logging```中只有库源码文件和测试源码文件,所以在执行```go build```命令之后不会在当前目录和goc2p项目的pkg目录中产生任何文件。 |
| 12 | + |
| 13 | +还有另外一种编译```logging```包的方式: |
| 14 | + |
| 15 | + hc@ubt:~/golang/goc2p/src$ go build logging |
| 16 | + |
| 17 | +在这里,我们把代码包```logging```的导入路径作为参数传递给```go build```命令。又例如,如果我们要编译代码包```cnet/ctcp```,只需要在任意目录下执行命令```go build cnet/ctcp```即可。 |
| 18 | + |
| 19 | +当然,我们也可以通过指定多个Go源码文件来完成编译行为: |
| 20 | + |
| 21 | + hc@ubt:~/golang/goc2p/src$ go build logging/base.go logging/console_logger.go logging/log_manager.go logging/tag.go |
| 22 | + |
| 23 | +但是,使用这种方法会有一个限制。作为参数的多个Go源码文件必须在同一个目录中。也就是说,如果我们想用一个命令既编译```logging```包又编译```basic```包是不可能的。不过别担心,```go build```命令能够在需要的时候去编译它们。假设有一个导入路径为```app```的代码包,同时依赖了```logging```包和```basic```包。那么在执行命令```go build app```的时候,该工具就会自动的在编译```app```包之前去编译它的所有依赖包,包括```logging```包和```basic```包。 |
| 24 | + |
| 25 | +注意,```go build```命令既不能编译包含多个命令源码文件的代码包,也不能同时编译多个命令源码文件。因为,如果把多个命令源码文件作为一个整体看待,那么每个文件中的main函数就属于重名函数,在编译时会抛出重复定义错误。假如,在goc2p项目的代码包```cmd```(此代码包仅用于示例目的,并不会永久存在于该项目中)中包含有两个命令源码文件showds.go和initpkg_demo.go,那么我们在使用```go build```命令同时编译它们时就会失败。示例如下: |
| 26 | + |
| 27 | + hc@ubt:~/golang/goc2p/src/cmd$ go build showds.go initpkg_demo.go |
| 28 | + # command-line-arguments |
| 29 | + ./initpkg_demo.go:19: main redeclared in this block |
| 30 | + previous declaration at ./showds.go:56 |
| 31 | + |
| 32 | +请注意上面示例中的“command-line-arguments”。在这个位置上应该显示的是作为参数的源码文件所属代码包的导入路径。但是,这里显示的并不是它们所属的代码包的导入路径```cmd```。这是因为,命令程序在分析参数的时候如果发现第一个参数是Go源码文件而不是代码包,则会在内部生成一个虚拟代码包。这个虚拟代码包的导入路径和名称都会是“command-line-arguments”。在其他基于编译流程的命令程序中也有与之一致的操作。比如```go install```命令和```go run```命令。 |
| 33 | + |
| 34 | +现在我们使用```go build```命令编译单一命令源码文件。我们在执行命令时加入一个标记```-v```。这个标记的意义在于可以使命令把执行过程中构建的包名打印出来。我们会在稍后对这个标记进行详细说明。现在我们先来看一个示例: |
| 35 | + |
| 36 | + hc@ubt:~/golang/goc2p/src/basic/pkginit$ ls |
| 37 | + initpkg_demo.go |
| 38 | + hc@ubt:~/golang/goc2p/src/basic/pkginit$ go build -v initpkg_demo.go |
| 39 | + command-line-arguments |
| 40 | + hc@ubt:~/golang/goc2p/src/basic/pkginit$ ls |
| 41 | + initpkg_demo initpkg_demo.go |
| 42 | + |
| 43 | +我们在执行命令```go build -v initpkg_demo.go ```之后被打印出的“command-line-arguments”就是命令程序为命令源码文件initpkg_demo.go生成的虚拟代码包的包名。 |
| 44 | + |
| 45 | +```go build```命令会把编译命令源码文件后生成的结果文件存放到执行该命令时所在的目录下。这个所说的结果文件就是与命令源码文件对应的可执行文件。它的名称会与命令源码文件的主文件名相同。 |
| 46 | + |
| 47 | +我们可以自定义生成的可执行文件的名字,示例如下: |
| 48 | + |
| 49 | + hc@ubt:~/golang/goc2p/src/basic/pkginit$ go build -o initpkg initpkg_demo.go |
| 50 | + hc@ubt:~/golang/goc2p/src/basic/pkginit$ ls |
| 51 | + initpkg initpkg_demo.go |
| 52 | + |
| 53 | +使用```-o```标记可以指定输出文件(在这个示例中是可执行文件)的名称。它是最常用的一个```go build```命令标记。但需要注意的是,当使用标记```-o```的时候,不能同时对多个代码包进行编译。 |
| 54 | + |
| 55 | +除此之外,还有一些标记在我们日常开发过程中可能会被用到。如下表。 |
| 56 | + |
| 57 | +_表0-1 ```go build```命令的常用标记说明_ |
| 58 | + |
| 59 | +<table class="table table-bordered table-striped table-condensed"> |
| 60 | + <tr> |
| 61 | + <th width=25%> |
| 62 | + 标记名称 |
| 63 | + </th> |
| 64 | + <th> |
| 65 | + 标记描述 |
| 66 | + </th> |
| 67 | + </tr> |
| 68 | + <tr> |
| 69 | + <td> |
| 70 | + -o |
| 71 | + </td> |
| 72 | + <td> |
| 73 | + 指定输出文件。 |
| 74 | + </td> |
| 75 | + </tr> |
| 76 | + <tr> |
| 77 | + <td> |
| 78 | + -a |
| 79 | + </td> |
| 80 | + <td> |
| 81 | + 强行对所有涉及到的代码包(包括标准库中的代码包)进行重新构建,即使它们已经是最新的了。 |
| 82 | + </td> |
| 83 | + </tr> |
| 84 | + <tr> |
| 85 | + <td> |
| 86 | + -n |
| 87 | + </td> |
| 88 | + <td> |
| 89 | + 打印构建期间所用到的其它命令,但是并不真正执行它们。 |
| 90 | + </td> |
| 91 | + </tr> |
| 92 | + <tr> |
| 93 | + <td> |
| 94 | + -p n |
| 95 | + </td> |
| 96 | + <td> |
| 97 | + 构建的并行数量(n)。默认情况下并行数量与CPU数量相同。 |
| 98 | + </td> |
| 99 | + </tr> |
| 100 | + <tr> |
| 101 | + <td> |
| 102 | + -race |
| 103 | + </td> |
| 104 | + <td> |
| 105 | + 开启数据竞争检测。此标记目前仅在linux/amd64、darwin/amd64和windows/amd64平台下被支持。 |
| 106 | + </td> |
| 107 | + </tr> |
| 108 | + <tr> |
| 109 | + <td> |
| 110 | + -v |
| 111 | + </td> |
| 112 | + <td> |
| 113 | + 打印出被构建的代码包的名字。 |
| 114 | + </td> |
| 115 | + </tr> |
| 116 | + <tr> |
| 117 | + <td> |
| 118 | + -wrok |
| 119 | + </td> |
| 120 | + <td> |
| 121 | + 打印出临时工作目录的名字,并且取消在构建完成后对它的删除操作。 |
| 122 | + </td> |
| 123 | + </tr> |
| 124 | + <tr> |
| 125 | + <td> |
| 126 | + -x |
| 127 | + </td> |
| 128 | + <td> |
| 129 | + 打印出构建期间所用到的其它命令。 |
| 130 | + </td> |
| 131 | + </tr> |
| 132 | +</table> |
| 133 | + |
| 134 | +我们在这里忽略了一些并不常用的或作用于编译器或连接器的标记。在本小节的最后将会对这些标记进行简单的说明。如果读者有兴趣,也可以查看Go语言的官方文档以获取相关信息。 |
| 135 | + |
| 136 | +下面我们就用其中几个标记来查看一下在构建代码包```logging```时创建的临时工作目录的路径: |
| 137 | + |
| 138 | + hc@ubt:~/golang/goc2p/src$ go build -v -work logging |
| 139 | + WORK=/tmp/go-build888760008 |
| 140 | + logging |
| 141 | + |
| 142 | +上面命令的结果输出的第一行是为了编译```logging```包,Go创建的一个临时工作目录,这个目录被创建到了Linux的临时目录下。输出的第二行是对标记```-v```的响应,意味着这个命令执行时仅编译了```logging```包。关于临时工作目录的用途和内容,我们会在讲解```go run```命令和```go test```命令的时候详细说明。 |
| 143 | + |
| 144 | +现在我们再来看看如果强制重新编译会涉及到哪些代码包: |
| 145 | + |
| 146 | + hc@ubt:~/golang/goc2p/src$ go build -a -v -work logging |
| 147 | + WORK=/tmp/go-build929017331 |
| 148 | + runtime |
| 149 | + errors |
| 150 | + sync/atomic |
| 151 | + math |
| 152 | + unicode/utf8 |
| 153 | + unicode |
| 154 | + sync |
| 155 | + io |
| 156 | + syscall |
| 157 | + strings |
| 158 | + time |
| 159 | + strconv |
| 160 | + os |
| 161 | + reflect |
| 162 | + fmt |
| 163 | + log |
| 164 | + logging |
| 165 | + |
| 166 | +怎么会多编译了这么多代码包呢?代码包```logging```中的代码直接依赖了标准库中的```runtime```包、```strings```包、```fmt```包和```log```包。那么其他的代码包为什么也会被重新编译呢? |
| 167 | + |
| 168 | +从代码包编译的角度来说,如果代码包A依赖代码包B,则称代码包B是代码包A的依赖代码包(以下简称依赖包),代码包A是代码包B的触发代码包(以下简称触发包)。 |
| 169 | + |
| 170 | +```go build```命令在执行时,编译程序会先查找目标代码包的所有依赖包,以及这些依赖包的依赖包,直至找到最深层的依赖包为止。在此过程中,如果发现有循环依赖的情况,编译程序就会输出错误信息并立即退出。此过程完成之后,所有的依赖关系形成了一棵含有重复元素的依赖树。对于依赖树中的一个节点(代码包),其直接分支节点(依赖包),是按照代码包导入路径的字典序从左到右排列的。最左边的分支节点会最先被编译。编译程序会依此设定每个代码包的编译优先级。 |
| 171 | + |
| 172 | +执行```go build```命令的计算机如果是多CPU的,那么编译代码包的顺序可能会有一些不确定性。但一定会满足这样的约束条件:```依赖代码包 -> 当前代码包 -> 触发代码包```。 |
| 173 | + |
| 174 | +标记```-p n```可以限制编译代码包时的并发数量,```n```默认为当前计算机的CPU数量。如果在执行```go build````命令时加入标记```-p 1```,就可以保证代码包编译顺序严格按照预先设定好的优先级进行。现在我们再来构建```logging```包: |
| 175 | + |
| 176 | + hc@ubt:~/golang/goc2p/src$ go build -a -v -work -p 1 logging |
| 177 | + WORK=/tmp/go-build114039681 |
| 178 | + runtime |
| 179 | + errors |
| 180 | + sync/atomic |
| 181 | + sync |
| 182 | + io |
| 183 | + math |
| 184 | + syscall |
| 185 | + time |
| 186 | + os |
| 187 | + unicode/utf8 |
| 188 | + strconv |
| 189 | + reflect |
| 190 | + fmt |
| 191 | + log |
| 192 | + unicode |
| 193 | + strings |
| 194 | + logging |
| 195 | + |
| 196 | + |
| 197 | +我们可以认为,以上示例中所显示的代码包的顺序,就是```logging```包直接或间接依赖的代码包按照优先级从高到低的排序。 |
| 198 | + |
| 199 | +另外,如果在命令中加入标记```-n```,则编译程序只会输出所用到的命令而不会真正运行。在这种情况下,编译过程不会使用并发模式。 |
| 200 | + |
| 201 | +关于```go build```命令可接受但不常用的标记的说明如下: |
| 202 | + |
| 203 | ++ -ccflags:需要传递给每一个5c、6c或者8c编译器的参数的列表。 |
| 204 | + |
| 205 | ++ -compiler:指定作为运行时编译器的编译器名称。其值可以为gccgo或gc。 |
| 206 | + |
| 207 | ++ -gccgoflags:需要传递给每一个gccgo编译器或链接器的参数的列表。 |
| 208 | + |
| 209 | ++ -gcflags:需要传递给每一个5g、6g或者8g编译器的参数的列表。 |
| 210 | + |
| 211 | ++ -installsuffix;为了使当前的输出的目录与默认的编译输出目录分离,可以使用这个标记。此标记的值会作为结果文件的父目录名称的后缀。实际上,如果使用了```-race```标记,这个值会被自动设置为```race```。如果同时使用了这两个标记,则会在```-installsuffix```标记的值的后面再加上```_race```,以此来作为实际使用的后缀。 |
| 212 | + |
| 213 | ++ -ldflags:需要传递给每一个5l、6l或者8l链接器的参数的列表。 |
| 214 | + |
| 215 | ++ -tags:在实际编译期间需要考虑满足的编译标签(也可被称为编译约束)的列表。可以查看代码包go/build的文档已获得更多的关于编译标签的信息。 |
| 216 | + |
| 217 | +其中,gccgo是GNU项目针对于Go语言出品的编译器。GNU是一个众所周知的自由软件工程项目。在开源软件界不应该有人不知道它。好吧,如果你确实不知道它,赶紧去google吧。 |
| 218 | + |
| 219 | +gc是Go语言的官方编译器。5g、6g和8g分别是gc编译器在x86(32bit)计算架构、x86-64(64bit)计算架构和ARM计算架构的计算机上的编译程序。 |
| 220 | + |
| 221 | +5c、6c和8c分别是Plan 9版本的gc编译器在x86(32bit)计算架构、x86-64(64bit)计算架构和ARM计算架构的计算机上的编译程序。而Plan 9是一个分布式操作系统,由贝尔实验室的计算科学研究中心在1980年代中期至2002年开发,以作为UNIX的后继者。Plan 9操作系统与Go语言的渊源很深。Go语言的三个设计者Robert Griesemer、Rob Pike和Ken Thompson不但是C语言和Unix操作系统的设计者,也同样是Plan 9操作系统的开发者。 |
| 222 | + |
| 223 | +5l、6l和8l分别是在x86(32bit)计算架构、x86-64(64bit)计算架构和ARM计算架构的计算机上的链接器。 |
| 224 | + |
| 225 | +顺便提一下,Go语言的专用环境变量GOARCH对应于x86(32bit)计算架构、x86-64(64bit)计算架构和ARM计算架构的值分别为386、amd64和arm。 |
0 commit comments