• 隐藏侧边栏
  • 展开分类目录
  • 关注微信公众号
  • 我的GitHub
  • QQ:1753970025
Chen Jiehua

Golang的构建模式 

Golang的构建模式(buildmode)指的是编译器如何编译源码构建出相关的对象文件,最常见的情况下就是生成一个可执行的二进制文件。然而,其实golang的buildmode还有很多有趣的用法……

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,接下来我们就挑选几个比较有意思的来详细讲解一下。

c-archive C 静态链接库

说明:

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

c-shared C 动态链接库

说明:

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

For the amd64 architecture only, the compiler has a new option, -dynlink, that assists dynamic linking by supporting references to Go symbols defined in external shared libraries.

G0 1.6

The implementation of build modes started in Go 1.5 has been expanded to more systems. This release adds support for the c-shared mode on android/386android/amd64android/arm64linux/386, and linux/arm64; for the shared mode on linux/386linux/armlinux/amd64, and linux/ppc64le; and for the new pie mode (generating position-independent executables) on android/386android/amd64android/armandroid/arm64linux/386linux/amd64linux/armlinux/arm64, and linux/ppc64le. See the design document for details.

Go 1.10

The various build modes have been ported to more systems. Specifically, c-shared now works on linux/ppc64lewindows/386, and windows/amd64pie now works on darwin/amd64 and also forces the use of external linking on all systems; and plugin now works on linux/ppc64le and darwin/amd64.

Go 1.11

The build modes c-shared and c-archive are now supported on freebsd/amd64.

shared Go动态链接库

说明:

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)

当然如果缺少了其中某个链接库或者版本不匹配,都将导致无法正常运行,所以一般情况下这种构建模式很少使用。

plugin Go插件

说明:

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

Go now provides early support for plugins with a “plugin” build mode for generating plugins written in Go, and a new plugin package for loading such plugins at run time. Plugin support is currently only available on Linux. Please report any issues.

G0 1.10

The various build modes have been ported to more systems. Specifically, c-shared now works on linux/ppc64lewindows/386, and windows/amd64pie now works on darwin/amd64 and also forces the use of external linking on all systems; and plugin now works on linux/ppc64le and darwin/amd64.

 

参考:

码字很辛苦,转载请注明来自ChenJiehua《Golang的构建模式》

评论