title: cgo 交叉编译
tags:


缘起

Go 语言除了语法精炼、并发支持好外,还有一个优点就是可以调用 C 代码。可以直接在 Go 源代码里写 C 代码,也可以引 C 语言的外部库。这样在性能遇到瓶颈的地方可以重写,或者某些功能 Go 和第三方还缺失,但 C 语言有现成的库就可以直接用了。

官方 Cgo 这块目前有一篇 博客命令行文档。比如 sqlite 的 golang 驱动 go-sqlite3 就是基于 Cgo 的实现。编译本地版本,Go 本身已经支持得非常好,基本不需要额外设置,直接通过 go build 编译即可,但是要想编译其他平台的二进制版本,就需要跨平台的 $(CC), $(CXX) 支持。

方案

按照 Cgo 的编译思路,基本思路就是必须有一个跨平台的 C/C++ 编译器才可能实现交叉编译。

macOS

  • 安装编译器

    1. brew install FiloSottile/musl-cross/musl-cross
    2. brew install mingw-w64
  • 编译

    Make 文件中指定 $(CC),$(GCC)musl-cross 提供的编译器,编译安装

注:此方案未验证,有兴趣的可以自行研究

docker

要想一次配置,到处运行,自然而然想到的就是 docker。基本思路就是通过 docker 配置好基础编译器,然后根据不同的项目挂载代码目录进行编译。karalabe/xgo 就做了类似的事情,非常满足我的需求。但是试用下来之后,发现几个问题:

  • 不支持 go mod 模式
  • 不能 (完整支持) 编译本地代码
  • 不支持最新的 golang 版本
  • 项目维护热度不足

无奈之下,只能自己 fork 下来定制了,自给自足。

修改如下:

  • 重新发布了 docker 镜像到 dockerhub
  • 移除了暂时用不上的 Android/iOS 版本支持,以减少 docker 镜像大小
  • 修改 xgo CLI 完整支持本地代码编译,--pkg 为子目录 main.go 所在路径
  • 修改基础镜像的 build.sh 支持相对路径以及 go mod 模式
  • 尝试了下使用 gomobile 编译 Android/iOS 版本,但是不如现有方式方便,暂时放到分支上了,后期根据情况看是否合并到主分支
  • go mod 模式下自动挂载 $GOPATH 到容器中,减少编译时间

示例

  • 安装修改版 xgo
    1. go get -u github.com/gythialy/xgo
  • 编写 Makefile

    1. MAINLDFLAGS="-X github.com/qlcchain/go-qlc/cmd/server/commands.Version=${SERVERVERSION} \
    2. -X github.com/qlcchain/go-qlc/cmd/server/commands.GitRev=${GITREV} \
    3. -X github.com/qlcchain/go-qlc/cmd/server/commands.BuildTime=${BUILDTIME} \
    4. -X github.com/qlcchain/go-qlc/cmd/server/commands.Mode=MainNet"
    5. SERVERMAIN = cmd/server/main.go
    6. gqlc-server:
    7. xgo --dest=$(BUILDDIR) -v --tags="mainnet sqlite_userauth" --ldflags=$(MAINLDFLAGS) --out=$(SERVERBINARY)-v$(SERVERVERSION)-$(GITREV) \
    8. --targets="windows-6.0/*,darwin-10.10/amd64,linux/amd64,linux/386,linux/arm64,linux/mips64, linux/mips64le" \
    9. --pkg=$(SERVERMAIN) .
    • --dest: 编译好的二进制文件存放目录
    • --out: 二进制名字
    • --targets: 编译支持的平台
    • --pkg: main 函数不在根目录的情况,需要指定
    • .: 代码所在目录

    完整 Makefile

    1. .PHONY: all clean build build-test confidant confidant-test
    2. .PHONY: gqlc-server gqlc-server-test
    3. .PHONY: gqlc-client
    4. .PHONY: deps
    5. # Check for required command tools to build or stop immediately
    6. EXECUTABLES = git go find pwd
    7. K := $(foreach exec,$(EXECUTABLES),\
    8. $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH)))
    9. # server
    10. SERVERVERSION ?= 1.2.6.6
    11. SERVERBINARY = gqlc
    12. SERVERTESTBINARY = gqlct
    13. SERVERMAIN = cmd/server/main.go
    14. # client
    15. CLIENTVERSION ?= 1.2.6.6
    16. CLIENTBINARY = gqlcc
    17. CLIENTMAIN = cmd/client/main.go
    18. BUILDDIR = build
    19. GITREV = $(shell git rev-parse --short HEAD)
    20. BUILDTIME = $(shell date +'%Y-%m-%d_%T')
    21. TARGET=windows-6.0/*,darwin-10.10/amd64,linux/amd64
    22. TARGET_CONFIDANT=linux/arm-7
    23. MAINLDFLAGS="-X github.com/qlcchain/go-qlc/cmd/server/commands.Version=${SERVERVERSION} \
    24. -X github.com/qlcchain/go-qlc/cmd/server/commands.GitRev=${GITREV} \
    25. -X github.com/qlcchain/go-qlc/cmd/server/commands.BuildTime=${BUILDTIME} \
    26. -X github.com/qlcchain/go-qlc/cmd/server/commands.Mode=MainNet"
    27. TESTLDFLAGS="-X github.com/qlcchain/go-qlc/cmd/server/commands.Version=${SERVERVERSION} \
    28. -X github.com/qlcchain/go-qlc/cmd/server/commands.GitRev=${GITREV} \
    29. -X github.com/qlcchain/go-qlc/cmd/server/commands.BuildTime=${BUILDTIME} \
    30. -X github.com/qlcchain/go-qlc/cmd/server/commands.Mode=TestNet"
    31. CLIENTLDFLAGS="-X github.com/qlcchain/go-qlc/cmd/client/commands.Version=${CLIENTVERSION} \
    32. -X github.com/qlcchain/go-qlc/cmd/client/commands.GitRev=${GITREV} \
    33. -X github.com/qlcchain/go-qlc/cmd/client/commands.BuildTime=${BUILDTIME}" \
    34. deps:
    35. go get -u golang.org/x/lint/golint
    36. go get -u github.com/gythialy/xgo
    37. go get -u github.com/git-chglog/git-chglog/cmd/git-chglog
    38. confidant:
    39. CGO_ENABLED=1 CC=/opt/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc GOARCH=arm GOARM=7 \
    40. GO111MODULE=on go build -tags "confidant" -ldflags $(MAINLDFLAGS) -v -i -o $(shell pwd)/$(BUILDDIR)/$(SERVERBINARY) $(shell pwd)/$(SERVERMAIN)
    41. @echo "Build $(SERVERBINARY) done."
    42. @echo "Run \"$(shell pwd)/$(BUILDDIR)/$(SERVERBINARY)\" to start $(SERVERBINARY)."
    43. CGO_ENABLED=1 CC=/home/lichao/ppr_cross/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc GOARCH=arm GOARM=7 \
    44. GO111MODULE=on go build -ldflags $(CLIENTLDFLAGS) -v -i -o $(shell pwd)/$(BUILDDIR)/$(CLIENTBINARY) $(shell pwd)/$(CLIENTMAIN)
    45. @echo "Build $(CLIENTBINARY) done."
    46. @echo "Run \"$(shell pwd)/$(BUILDDIR)/$(CLIENTBINARY)\" to start $(CLIENTBINARY)."
    47. confidant-test:
    48. CGO_ENABLED=1 CC=/opt/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc GOARCH=arm GOARM=7 \
    49. GO111MODULE=on go build -tags "confidant testnet" -ldflags $(MAINLDFLAGS) -v -i -o $(shell pwd)/$(BUILDDIR)/$(SERVERBINARY) $(shell pwd)/$(SERVERMAIN)
    50. @echo "Build $(SERVERBINARY) done."
    51. @echo "Run \"$(shell pwd)/$(BUILDDIR)/$(SERVERBINARY)\" to start $(SERVERBINARY)."
    52. CGO_ENABLED=1 CC=/home/lichao/ppr_cross/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc GOARCH=arm GOARM=7 \
    53. GO111MODULE=on go build -ldflags $(CLIENTLDFLAGS) -v -i -o $(shell pwd)/$(BUILDDIR)/$(CLIENTBINARY) $(shell pwd)/$(CLIENTMAIN)
    54. @echo "Build $(CLIENTBINARY) done."
    55. @echo "Run \"$(shell pwd)/$(BUILDDIR)/$(CLIENTBINARY)\" to start $(CLIENTBINARY)."
    56. build:
    57. GO111MODULE=on go build -ldflags $(MAINLDFLAGS) -v -i -o $(shell pwd)/$(BUILDDIR)/$(SERVERBINARY) $(shell pwd)/$(SERVERMAIN)
    58. @echo "Build $(SERVERBINARY) done."
    59. @echo "Run \"$(shell pwd)/$(BUILDDIR)/$(SERVERBINARY)\" to start $(SERVERBINARY)."
    60. GO111MODULE=on go build -ldflags $(CLIENTLDFLAGS) -v -i -o $(shell pwd)/$(BUILDDIR)/$(CLIENTBINARY) $(shell pwd)/$(CLIENTMAIN)
    61. @echo "Build $(CLIENTBINARY) done."
    62. @echo "Run \"$(shell pwd)/$(BUILDDIR)/$(CLIENTBINARY)\" to start $(CLIENTBINARY)."
    63. build-test:
    64. GO111MODULE=on go build -tags "testnet" -ldflags $(TESTLDFLAGS) -v -i -o $(shell pwd)/$(BUILDDIR)/$(SERVERBINARY) $(shell pwd)/$(SERVERMAIN)
    65. @echo "Build test server done."
    66. @echo "Run \"$(BUILDDIR)/$(SERVERBINARY)\" to start $(SERVERBINARY)."
    67. GO111MODULE=on go build -ldflags $(CLIENTLDFLAGS) -v -i -o $(shell pwd)/$(BUILDDIR)/$(CLIENTBINARY) $(shell pwd)/$(CLIENTMAIN)
    68. @echo "Build test client done."
    69. @echo "Run \"$(BUILDDIR)/$(CLIENTBINARY)\" to start $(CLIENTBINARY)."
    70. all: gqlc-server gqlc-server-test gqlc-client
    71. clean:
    72. rm -rf $(shell pwd)/$(BUILDDIR)/
    73. gqlc-server:
    74. xgo --dest=$(BUILDDIR) --ldflags=$(MAINLDFLAGS) --out=$(SERVERBINARY)-v$(SERVERVERSION)-$(GITREV) \
    75. --targets=$(TARGET) --pkg=$(SERVERMAIN) .
    76. xgo --dest=$(BUILDDIR) --tags="confidant" --ldflags=$(MAINLDFLAGS) --out=$(SERVERBINARY)-confidant-v$(SERVERVERSION)-$(GITREV) \
    77. --targets=$(TARGET_CONFIDANT) --pkg=$(SERVERMAIN) .
    78. gqlc-server-test:
    79. xgo --dest=$(BUILDDIR) --tags="testnet" --ldflags=$(TESTLDFLAGS) --out=$(SERVERTESTBINARY)-v$(SERVERVERSION)-$(GITREV) \
    80. --targets=$(TARGET) --pkg=$(SERVERMAIN) .
    81. xgo --dest=$(BUILDDIR) --tags="confidant testnet" --ldflags=$(TESTLDFLAGS) --out=$(SERVERTESTBINARY)-confidant-v$(SERVERVERSION)-$(GITREV) \
    82. --targets=$(TARGET_CONFIDANT) --pkg=$(SERVERMAIN) .
    83. gqlc-client:
    84. xgo --dest=$(BUILDDIR) --ldflags=$(CLIENTLDFLAGS) --out=$(CLIENTBINARY)-v$(CLIENTVERSION)-$(GITREV) \
    85. --targets="windows-6.0/amd64,darwin-10.10/amd64,linux/amd64" \
    86. --pkg=$(CLIENTMAIN) .
  • 编译

    1. make clean gqlc-server

结语

虽然通过 docker 的方式暂时解决了 cgo 的跨平台编译,但是还是挺折腾的,编译速度也不是特别理想。希望后期 Go 官方能有更完整的交叉的编译方案。

参考链接

---EOF---

ft_authoradmin  ft_create_time2019-11-18 12:28
 ft_update_time2019-11-18 12:29