• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

【Go命令教程】2. go build

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

go build 命令用于编译我们 指定的  源码文件代码包 以及它们的依赖包

例如,如果我们在执行 go build 命令时不后跟任何代码包,那么命令将试图编译当前目录所对应的代码包。例如,我们想编译 goc2p 项目的代码包 logging。其中一个方法是进入 logging 目录并直接执行该命令:

hc@ubt:~/golang/goc2p/src/logging$ go build

因为在代码包 logging 中只有库源码文件和测试源码文件,所以在执行 go build 命令之后不会在当前目录和 goc2p 项目的 pkg 目录中产生任何文件。

插播:Go 语言的源码文件有三大类,即:命令源码文件库源码文件 和 测试源码文件。他们的功用各不相同,而写法也各有各的特点。命令源码文件总是作为可执行的程序的入口。库源码文件一般用于集中放置各种待被使用的程序实体(全局常量、全局变量、接口、结构体、函数等等)。而测试源码文件主要用于对前两种源码文件中的程序实体的功能和性能进行测试。另外,后者也可以用于展现前两者中程序的使用方法。

另外一种编译 logging 包的方法是:

hc@ubt:~/golang/goc2p/src$ go build logging

在这里,我们把代码包 logging 的导入路径作为参数传递给 go build 命令。另一个例子:如果我们要编译代码包 cnet/ctcp,只需要在任意目录下执行命令 go build cnet/ctcp 即可。

插播:之所以这样的编译方法可以正常执行,是因为我们已经在环境变量 GOPATH 中加入了 goc2p 项目的根目录(即 ~/golang/goc2p/)。这时,goc2p 项目的根目录就成为了一个工作区目录。只有这样,Go 语言才能正确识别我们提供的 goc2p 项目中某个代码包的导入路径。而代码包的导入路径是指,相对于 Go 语言自身的源码目录(即 $GOROOT/src)或我们在环境变量 GOPATH 中指定的某个目录的 src 子目录下的子路径。例如,这里的代码包 logging 的绝对路径是 ~/golang/goc2p/src/logging。而不论 goc2p 项目的根文件夹被放在哪儿,logging 包的导入路径都是 logging。显而易见,我们在称呼一个代码包的时候总是以其导入路径作为其称谓。

言归正传,除了上面的简单用法,我们还可以同时编译多个 Go 源码文件:

hc@ubt:~/golang/goc2p/src$ go build logging/base.go logging/console_logger.go logging/log_manager.go logging/tag.go
# 编译多个go文件
#

但是,使用这种方法会有一个限制。作为参数的多个 Go 源码文件必须在同一个目录中。也就是说,如果我们想用这种方法既编译 logging 包又编译 basic 包是不可能的。不过别担心,在需要的时候,那些被编译目标依赖的代码包会被 go build 命令自动的编译。例如,如果有一个导入路径为 app 的代码包,同时依赖了 logging 包和 basic 包。那么在执行 go build app 的时候,该命令就会自动的在编译app包之前去检查 logging 包和 basic 包的编译状态。如果发现它们的编译结果文件不是最新的,那么该命令就会先去的编译这两个代码包,然后再编译 app 包。

注意,go build 命令在编译只包含库源码文件的代码包(或者同时编译多个代码包)时,只会做检查性的编译,而不会输出任何结果文件。

另外,go build 命令既不能编译包含多个命令源码文件的代码包,也不能同时编译多个命令源码文件。因为,如果把多个命令源码文件作为一个整体看待,那么每个文件中的 main 函数就属于重名函数,在编译时会抛出重复定义错误。假如,在 goc2p 项目的代码包 cmd(此代码包仅用于示例目的,并不会永久存在于该项目中)中包含有两个命令源码文件 showds.go 和 initpkg_demo.go,那么我们在使用 go build 命令同时编译它们时就会失败。示例如下:

hc@ubt:~/golang/goc2p/src/cmd$ go build showds.go initpkg_demo.go
# command-line-arguments
./initpkg_demo.go:19: main redeclared in this block
        previous declaration at ./showds.go:56

请注意上面示例中的 command-line-arguments。在这个位置上应该显示的是作为编译目标的源码文件所属的代码包的导入路径。但是,这里显示的并不是它们所属的代码包的导入路径 cmd。这是因为,命令程序在分析参数的时候如果发现第一个参数是 Go 源码文件而不是代码包,则会在内部生成一个虚拟代码包。这个虚拟代码包的导入路径和名称都会是 command-line-arguments。在其他基于编译流程的命令程序中也有与之一致的操作,比如 go install 命令和 go run 命令。

另一方面,如果我们编译的多个属于 main 包的源码文件中没有 main 函数的声明,那么就会使编译器立即报出 “未定义 main 函数声明” 的错误并中止编译。换句话说,在我们同时编译多个 main 包的源码文件时,要保证其中有且仅有一个 main 函数声明,否则编译是无法成功的。

现在我们使用 go build 命令编译单一命令源码文件。我们在执行命令时加入一个标记 -v。这个标记的意义在于可以使命令把执行过程中构建的包名打印出来。我们会在稍后对这个标记进行详细说明。现在我们先来看一个示例:

hc@ubt:~/golang/goc2p/src/basic/pkginit$ ls
initpkg_demo.go
hc@ubt:~/golang/goc2p/src/basic/pkginit$ go build -v initpkg_demo.go
command-line-arguments
hc@ubt:~/golang/goc2p/src/basic/pkginit$ ls
initpkg_demo  initpkg_demo.go

我们在执行命令 go build -v initpkg_demo.go 之后被打印出的 command-line-arguments” 就是命令程序为命令源码文件 initpkg_demo.go 生成的虚拟代码包的包名。顺带说一句,

命令 go build 会把编译命令源码文件后生成的结果文件存放到执行该命令时所在的目录下。这个所说的结果文件就是与命令源码文件对应的可执行文件。它的名称会与命令源码文件的主文件名相同。

顺便说一下,如果我们有多个声明为属于 main 包的源码文件,且其中只有一个文件声明了 main 函数的话,那么是可以使用 go build 命令同时编译它们的。在这种情况下,不包含 main 函数声明的那几个源码文件会被视为库源码文件(理所当然)。如此编译之后的结果文件的名称将会与我们指定的编译目标中最左边的那个源码文件的主文件名相同。

其实,除了让 Go 语言编译器自行决定可执行文件的名称,我们还可以自定义它。示例如下:

hc@ubt:~/golang/goc2p/src/basic/pkginit$ go build -o initpkg initpkg_demo.go
hc@ubt:~/golang/goc2p/src/basic/pkginit$ ls
initpkg    initpkg_demo.go

使用 -o 标记可以指定输出文件(在这个示例中指的是可执行文件)的名称。它是最常用的一个 go build 命令标记。但需要注意的是,当使用标记 -o 的时候,不能同时对多个代码包进行编译。

标记 -i 会使 go build 命令安装那些编译目标依赖的且还未被安装的代码包。这里的安装意味着产生与代码包对应的归档文件,并将其放置到当前工作区目录的 pkg 子目录的相应子目录中。在默认情况下,这些代码包是不会被安装的。

除此之外,还有一些标记不但受到 go build 命令的支持,而且对于后面会提到的 go install、go run、go test 等命令同样是有效的。下表列出了其中比较常用的标记。

表0-1 go build命令的常用标记说明

标记名称 标记描述
-a 强行对所有涉及到的代码包(包含标准库中的代码包)进行重新构建,即使它们已经是最新的了。
-n 打印编译期间所用到的其它命令,但是并不真正执行它们。
-p n 指定编译过程中执行各任务的并行数量(确切地说应该是并发数量)。在默认情况下,该数量等于CPU的逻辑核数。但是在 darwin/arm 平台(即 iPhone 和 iPad 所用的平台)下,该数量默认是1。
-race 开启竞态条件的检测。不过此标记目前仅在 linux/amd64、freebsd/amd64、darwin/amd64和windows/amd64 平台下受到支持。
-v 打印出那些被编译的代码包的名字。
-work 打印出编译时生成的临时工作目录的路径,并在编译结束时保留它。在默认情况下,编译结束时会删除该目录。
-x 打印编译期间所用到的其它命令。注意它与-n标记的区别。

我们在这里忽略了一些并不常用的或作用于编译器或连接器的标记。在本小节的最后将会对这些标记进行简单的说明。如果读者有兴趣,也可以查看 Go 语言的官方文档以获取相关信息。

下面我们就用其中几个标记来查看一下在构建代码包logging时创建的临时工作目录的路径:

hc@ubt:~/golang/goc2p/src$ go build -v -work logging
WORK=/tmp/go-build888760008
logging

上面命令的结果输出的第一行是为了编译 logging 包,Go 创建的一个临时工作目录,这个目录被创建到了 Linux 的临时目录下。输出的第二行是对标记 -v 的响应。这意味着此次命令执行时仅编译了logging 包。关于临时工作目录的用途和内容,我们会在讲解 go run 命令和 go test 命令的时候详细说明。

现在我们再来看看如果强制重新编译会涉及到哪些代码包:

hc@ubt:~/golang/goc2p/src$ go build -a -v -work logging
WORK=/tmp/go-build929017331
runtime
errors
sync/atomic
math
unicode/utf8
unicode
sync
io
syscall
strings
time
strconv
reflect
os
fmt
log
logging

怎么会多编译了这么多代码包呢?可以确定的是,代码包 logging 中的代码直接依赖了标准库中的 runtime 包、strings 包、fmt 包和 log 包。那么其他的代码包为什么也会被重新编译呢?

从代码包编译的角度来说,如果代码包A依赖代码包 B,则称代码包 B 是代码包 A 的依赖代码包(以下简称依赖包),代码包 A 是代码包 B 的触发代码包(以下简称触发包)。

go build 命令在执行时,编译程序会先查找目标代码包的所有依赖包,以及这些依赖包的依赖包,直至找到最深层的依赖包为止。在此过程中,如果发现有循环依赖的情况,编译程序就会输出错误信息并立即退出。此过程完成之后,所有的依赖关系也就形成了一棵含有重复元素的依赖树。对于依赖树中的一个节点(代码包)来说,它的直接分支节点(前者的依赖包),是按照代码包导入路径的字典序从左到右排列的。最左边的分支节点会最先被编译。编译程序会依此设定每个代码包的编译优先级。

执行 go build 命令的计算机如果拥有多个逻辑 CPU 核心,那么编译代码包的顺序可能会存在一些不确定性。但是,它一定会满足这样的约束条件:依赖代码包 -> 当前代码包 -> 触发代码包。

标记 -p n 可以限制编译过程中任务执行的并发数量,n 默认为当前计算机的 CPU 逻辑核数。如果在执行 go build 命令时加入标记 -p 1,那么就可以保证代码包编译顺序严格按照预先设定好的优先级进行。现在我们再来编译 logging 包:

hc@ubt:~/golang/goc2p/src$ go build -a -v -work -p 1 logging
WORK=/tmp/go-build114039681
runtime
errors
sync/atomic
sync
io
math
syscall
time
os
unicode/utf8
strconv
reflect
fmt
log
unicode
strings
logging

我们可以认为,以上示例中所显示的代码包的顺序,就是 logging 包直接或间接依赖的代码包按照优先级从高到低排列后的排序。

另外,如果在命令中加入标记 -n,那么编译程序只会输出所用到的命令而不会真正运行。在这种情况下,编译过程不会使用并发模式。

在本节的最后,我们对一些并不太常用的标记进行简要的说明:

  • -asmflags

此标记可以后跟另外一些标记,如 -D、-I、-S 等。这些后跟的标记用于控制 Go 语言编译器编译汇编语言文件时的行为。

  • -buildmode

此标记用于指定编译模式,使用方式如 -buildmode=default(这等同于默认情况下的设置)。此标记支持的编译模式目前有6种。借此,我们可以控制编译器在编译完成后生成静态链接库(即 .a 文件,也就是我们之前说的归档文件)、动态链接库(即 .so 文件)或/和可执行文件(在 Windows 下是 .exe 文件)。

  • -compiler

此标记用于指定当前使用的编译器的名称。其值可以为 gc 或 gccgo。其中,gc 编译器即为 Go 语言自带的编辑器,而 gccgo 编译器则为 GCC 提供的 Go 语言编译器。而 GCC 则是 GNU 项目出品的编译器套件。GNU 是一个众所周知的自由软件项目。在开源软件界不应该有人不知道它。好吧,如果你确实不知道它,赶紧去 google 吧。

  • -gccgoflags

此标记用于指定需要传递给 gccgo 编译器或链接器的标记的列表。

  • -gcflags

此标记用于指定需要传递给 go tool compile 命令的标记的列表。

  • -installsuffix

为了使当前的输出目录与默认的编译输出目录分离,可以使用这个标记。此标记的值会作为结果文件的父目录名称的后缀。其实,如果使用了 -race 标记,这个标记会被自动追加且其值会为 race。如果我们同时使用了 -race 标记和 -installsuffix,那么在 -installsuffix 标记的值的后面会再被追加 _race,并以此来作为实际使用的后缀。

  • -ldflags

此标记用于指定需要传递给 go tool link 命令的标记的列表。

  • -linkshared

此标记用于与 -buildmode=shared 一同使用。后者会使作为编译目标的非 main 代码包都被合并到一个动态链接库文件中,而前者则会在此之上进行链接操作。

  • -pkgdir

使用此标记可以指定一个目录。编译器会只从该目录中加载代码包的归档文件,并会把编译可能会生成的代码包归档文件放置在该目录下。

  • -tags

此标记用于指定在实际编译期间需要受理的编译标签(也可被称为编译约束)的列表。这些编译标签一般会作为源码文件开始处的注释的一部分,例如,在 $GOROOT/src/os/file_posix.go 开始处的注释为:

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows

最后一行注释即包含了与编译标签有关的内容。大家可以查看代码包 go/build 的文档已获得更多的关于编译标签的信息。

  • -toolexec

此标记可以让我们去自定义在编译期间使用一些 Go 语言自带工具(如 vet、asm 等)的方式。

 

 

摘自:

http://wiki.jikexueyuan.com/project/go-command-tutorial/0.1.html


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
开始使用 Go For It 吧,一个灵活的待办事项列表程序 | Linux 中国 ...发布时间:2022-07-10
下一篇:
Go语言Revel框架准备工作发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap