Golang的构建模式(buildmode)指的是编译器如何编译源码构建出相关的对象文件,最常见的情况下就是生成一个可执行的二进制文件。然而,其实golang的buildmode还有很多有趣的用法……
在 go build 和 go install 命令中,我们可以指定 -buildmode 参数来让编译器构建出特定的对象文件。通过命令 go help buildmode,可以看到其支持的选项:
-buildmode=archive -buildmode=c-archive -buildmode=c-shared -buildmode=default -buildmode=shared -buildmode=exe -buildmode=pie -buildmode=plugin
排除掉最常用的 default,接下来我们就挑选几个比较有意思的来详细讲解一下。
说明:
Build the listed main package, plus all packages it imports, into a C archive file. The only callable symbols will be those functions exported using a cgo //export comment. Requires exactly one main package to be listed.
c-archive 也就是将 package main 中导出的方法(// export 标记)编译成 .a 文件,这样其它 c 程序就可以静态链接该文件,并调用其中的方法。
Go示例:
首先写一个简单 add.go :
package main import "fmt" import "C" func main(){} //export Add func Add(a, b int) int{ fmt.Printf("%d + %d = %d\n", a, b, a+ b) return a+b }
然后使用 -buildmode=c-archive 编译:
$ go build -buildmode=c-archive add.go
生成两个文件: add.a 和 add.h,查看一下文件的类型:
$ file add.a add.h // output add.a: current ar archive random library add.h: c program text, ASCII text
在 add.h 中我们看到 Add() 函数的定义:
#ifdef __cplusplus extern "C" { #endif extern GoInt Add(GoInt p0, GoInt p1); #ifdef __cplusplus } #endif
C示例:
接下来,我们再写一个简单的 C 程序:
# include "add.h" int main(void) { Add(1, 2); return 0; }
加上 add.a 文件编译一下:
$ cc myadd.c add.a
生成一个可执行文件 a.out,执行 ./a.out:
$ ./a.out // output 1 + 2 = 3
说明:
Build the listed main package, plus all packages it imports, into a C shared library. The only callable symbols will
be those functions exported using a cgo //export comment. Requires exactly one main package to be listed.
c-shared 也就是将 package main 中导出的方法(// export 标记)编译成一个动态链接库(.so 或 .dll 文件),这样其它 c 程序就可以调用其中的方法。
Go示例:
继续使用上面的 add.go,改用 -buildmode=c-shared 来编译:
$ go build -buildmode=c-shared -o add.so add.go
这次生成了 add.so 和 add.h 两个文件:
$ file add.so add.h // output add.so: Mach-O 64-bit dynamically linked shared library x86_64 add.h: c program text, ASCII text
C示例:
继续使用上面的 myadd.c进行编译:
$ cc myadd.c add.so
同样是生成了 a.out 可执行文件,但是留意一下前后两次的体积:
- 使用 add.so 编译生成的 a.out 才 8.2KB;
- 使用 add.a 编译生成的 a.out 有 1.8MB;
如果我们将 add.so 删除,再运行 a.out:
dyld: Library not loaded: add.so Referenced from: /Users/jachua/go/src/buildmode/c-archive/./a.out Reason: image not found [1] 94945 abort ./a.out
另外,我们也可以指定环境变量 LD_LIBRARY_PATH 告诉操作系统到哪里去寻找动态链接库:
// Linux $ LD_LIBRARY_PATH=./lib ./out // macOS $ DYLD_LIBRARY_PATH=./lib ./out
版本要求:
G0 1.5
-dynlink
G0 1.6
Go 1.10
c-sharedlinux/ppc64lewindows/386windows/amd64piedarwin/amd64pluginlinux/ppc64ledarwin/amd64
Go 1.11
c-sharedc-archivefreebsd/amd64
说明:
Combine all the listed non-main packages into a single shared library that will be used when building with the -linkshared option. Packages named main are ignored.
shared 与 c-shared 类似,不过它是用来给 golang 构建动态链接库的。它将 非main 的package 编译为动态链接库,并在构建其他 go程序时使用 -linkshared 参数指定。
示例:
首先我们写一个非常简单的 hello.go:
package main import "fmt" func main(){ fmt.Println("hello world") }
然后,我们需要将 golang 的所有标准库 std 编译安装为 shared:
// -buildmode=shared 暂不支持 macOS $ go install -buildmode=shared std
接着再用 -linkshared 编译 hello.go:
$ go build -linkshared hello.go
可以看到生成的可执行文件体积才 20KB ,相比正常的 go build hello.go 生成的 1.9MB 小非常多。我们可以使用 ldd 命令来查看调用链:
$ ldd hello linux-vdso.so.1 (0x00007ffcb0db9000) libstd.so => /usr/local/go/pkg/linux_amd64_dynlink/libstd.so (0x00007f2d5c1cb000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2d5bdda000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f2d5bbd6000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f2d5b9b7000) /lib64/ld-linux-x86-64.so.2 (0x00007f2d5eb96000)
当然如果缺少了其中某个链接库或者版本不匹配,都将导致无法正常运行,所以一般情况下这种构建模式很少使用。
说明:
Build the listed main packages, plus all packages that they import, into a Go plugin. Packages not named main are ignored.
plugin 模式是 golang 1.8 才推出的一个特殊的构建方式,它将 package main 编译为一个 go 插件,并可在运行时动态加载。
示例:
首先,我们来写一个简单的 greeting:
package main import "fmt" type greeting string func (g greeting) Greet() { fmt.Println("hello world") } var Greeter greeting
然后将其编译为一个 go 插件:
$ go build -buildmode=plugin -o greeter.so greeter.go $ file greeter.so greeter.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=f5729749535c706a6095994f9510da92ccc8f9c6, not stripped
再使用 golang 官方的 plugin 库来调用这个插件:
package main import ( "fmt" "os" "plugin" ) type Greeter interface { Greet() } func main() { plug, err := plugin.Open("./greet/greeter.so") if err != nil { fmt.Println(err) os.Exit(1) } symGreeter, err := plug.Lookup("Greeter") if err != nil { fmt.Println(err) os.Exit(1) } var greeter Greeter greeter, ok := symGreeter.(Greeter) if !ok { fmt.Println(err) os.Exit(1) } greeter.Greet() }
版本说明:
Go 1.8
pluginplugin
G0 1.10
c-sharedlinux/ppc64lewindows/386windows/amd64piedarwin/amd64pluginlinux/ppc64ledarwin/amd64
参考: