282013
 

在golang标准库中,有那么一类包,它们用于处理go项目目录结构、源码、语法、基本操作等。一般程序中可能用不到这些包,但在go工具链源码中用到了,之所以学习这些标准库,是为了更好的看go工具链的源码。首先我们来看收集go包信息的库:go/build

一、build包概述

该包文档中首先介绍了Go Path。如果对该部分还不清楚,可以看下文档的说明;或者官方其他文档;或者看 Go项目的目录结构。

如果你看过go源码,应该见到过类似这样的包注释:+build ignore。这是编译约束条件(Build Constraints),可以理解为条件编译。关于这部分的更多内容,稍后详细介绍。

二、类型和函数

1、ToolDir变量

var ToolDir = filepath.Join(runtime.GOROOT(), "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH)

该变量的值是go工具链的路径。6g/6l之类的工具,就在这个路径下

2、ArchChar函数

获得架构的字符表示。在之前的文章中介绍过。比如:x86 32bit用8表示;amd64用6表示等。该函数通过传入goarch,获得对应的架构字符。如:build.ArchChar(runtime.GOARCH)

3、IsLocalImport函数

判断是否为“本地导入“,类似”.”, “..”, “./foo”或者”../foo”。正式项目,一般不建议用本地导入,使用本地导入很多人会说找不到包。

4、Context类型

该类型为构建(build)指定上下文环境。比如:当前操作系统、架构、Go根目录、GOPATH等

该类型除了提供变量成员,还提供了函数成员,如果函数成员是nil,则会使用其他库提供的函数。

5、Package类型

描述Go包

三、主要类型的方法(包括实例化)

1、Context的实例化和方法

var Default Context = defaultContext()

这是默认实现,go build工具使用的就是这个默认实现。Default会使用GOARCH、GOOS、GOROOT和GOPATH环境变量,如果没设置,则使用安装runtime中的值。

1)Import方法

func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Package, error)

导入一个包,返回Package类型指针。path参数跟在代码中import path的path一样。srcDir是源码所在路径;而ImportMode类型,build包中提供了两种:FindOnly和AllowBinary。AllowBinary可以在包源码不存在的时候,编译好的包对象文件直接被引用。

之前,在论坛(关于go的代码组织)中讨论过这样一个问题:go build 的时候,如果依赖的包源码不存在,编译不成功,有一个解决办法是通过go tool 6g这种方式编译。现在,在你知道了AllowBinary参数之后,应该可以通过修改go工具源码来解决这个问题。在src/cmd/go目录中的pkg.go中225有这样的代码:

// TODO: After Go 1, decide when to pass build.AllowBinary here.
// See issue 3268 for mistakes to avoid.
bp, err := buildContext.Import(path, srcDir, 0)

通过查看Import的源码,可以知道包安装的细节,比如安装到哪里。

当目录不包含源码,如果出错,则返回NoGoError错误。

另外,build包提供了Import方法的一个简便方法,即:Import函数,默认调用Default的Import方法

2)ImportDir方法

内部实现:return ctxt.Import(“.”, dir, mode)

3)SrcDirs方法

列出GOROOT和GOPATH中的源码目录。比如,我没有设置GOPATH,执行结果如下:
fmt.Println(build.Default.SrcDirs()) // [ c:\Go\src\pkg]

2、Package的实例化和方法

Context的Import和ImportDir都会返回Package实例(*Package),当然,也可以直接实例化。

Package提供了一个方法,判断一个Package是否是命令,也就是是否是main包

func (p *Package) IsCommand() bool

3、关于Context和Package的字段

由于这两种类型字段很多,包文档中每个字段都有注释,在此不一一解释。

四、构建约束(build constraints)

或者叫条件编译(编译条件)

1、使用说明

在go源码中(src/pkg或src/cmd)搜索+build,发现有不少文件的开头有这样的注释
+build xxx

构建约束是一行以+build开始的注释。在+build之后列出了一些条件,在这个条件成立的时,该文件应该包含在包中(也就是应该被编译进包文件),约束可以出现在任何源文件中,也就是不限于go源文件。不过,这些条件必须在文件最顶部(正是代码的前面,也就是说,+build之前可以有其他注释),在+build注释之后,应该有一个空行(这是为了和package doc区分开)。

语法规则:

1)只允许是字母数字或_

2)多个条件之间,空格表示OR;逗号表示AND;叹号(!)表示NOT

3)一个文件可以有多个+build,它们之间的关系是AND。如:
// +build linux darwin
// +build 386
等价于
// +build (linux OR darwin) AND 386

4)预定义了一些条件:
runtime.GOOS、runtime.GOARCH、compiler(gc或gccgo)、cgo、context.BuildTags中的其他单词

5)如果一个文件名(不含后缀),以 *_GOOS, *_GOARCH, 或 *_GOOS_GOARCH结尾,它们隐式包含了 构建约束

6)当不想编译某个文件时,可以加上// +build ignore。这里的ignore可以是其他单词,只是ignore更能让人知道什么意思

更多详细信息,可以查看go/build/build.go文件中shouldBuild和match方法。

2、应用实例

除了*_GOOS这种预定义的应用,我们看一个实际的应用。

比如,项目中需要在测试环境输出Debug信息,一般通过一个变量(或常量)来控制是测试环境还是生产环境,比如:if DEBUG {},这样在生产环境每次也会进行这样的判断。在golang-nuts邮件列表中有人问过这样的问题,貌似没有讨论出更好的方法(想要跟C中条件编译一样的效果)。下面我们采用Build constraints来实现。

1)文件列表:main.go logger_debug.go logger_product.go
2)在main.go中简单的调用Debug()方法。
3)在logger_product.go中的Debug()是空实现,但是在文件开始加上// + build !debug
4)在logger_debug.go中的Debug()是需要输出的调试信息,同时在文件开始加上// + build debug

这样,在测试环境编译的时传递-tags参数:go build/install -tags “debug” logger。生产环境:go build/install logger就行了。

对于生产环境,不传递-tags时,为什么会编译logger_product.go呢?因为在go/build/build.go中的match方法中有这么一句:

if strings.HasPrefix(name, "!") { // negation
	return len(name) > 1 && !ctxt.match(name[1:])
}

也就是说,只要有!(不能只是!),tag不在BuildTags中时,总是会编译。

完整源码在github

五、使用实例

对go/build的使用实例,后续文章在介绍go build的时候会介绍。

18,635 浏览数

  1条评论 到 “标准库— 操作源码之收集go包信息:go/build”

评论 (1)
  1. 嘿嘿,好文细读,gowalker就是该包的实际用例。。~~

 评论

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>