Go中的交叉编译问题

 更新时间:2023年11月22日 14:49:38   作者:机器铃砍菜刀  
这篇文章主要介绍了Go中的交叉编译问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

交叉编译是指在一个硬件平台生成另一个硬件平台的可执行文件。而Go提供了非常方便的交叉编译方式。

如何编译

Go交叉编译,涉及到几个环境变量的设置: GOARCH、GOOS和CGO_ENABLED。

  • GOARCH:编译目标平台的硬件体系架构(amd64, 386, arm, ppc64等)。
  • GOOS:编译目标平台上的操作系统(darwin, freebsd, linux, windows)。
  • CGO_ENABLED:代表是否开启CGO,1表示开启,0表示禁用。由于CGO不能支持交叉编译,所以需要禁用。

GO中env的具体环境变量的注释,可通过输入命令go help environment查看。

 ~ $ go help environment
...
  GOARCH
    The architecture, or processor, for which to compile code.
    Examples are amd64, 386, arm, ppc64.
...
  GOOS
    The operating system for which to compile code.
    Examples are linux, darwin, windows, netbsd.
...
  CGO_ENABLED
    Whether the cgo command is supported. Either 0 or 1.

Mac 下编译 Linux 和 Windows 64位可执行程序

export CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
export CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go

Linux 下编译 Mac 和 Windows 64位可执行程序

export CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go
export CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go

Windows 下编译 Mac 和 Linux 64位可执行程序

SET CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go
SET CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go

其他平台或32位系统类似,这里就不再赘述。

GO是如何做到交叉编译?

Go交叉编译的实现通过在文件顶部增加构建标记,进行选择编译。

// +build

注:Go源码里的编译器源码位于$GOROOT/src/cmd/compile路径下,链接器源码位于$GOROOT/src/link路径下。

我们的切入点从Go编译器的main函数为入口,代码位于$GOROOT/src/cmd/compile/main.go。

以环境变量GOARCH为例,看一下Go编译器是如何通过构建标记来选择对应的体系架构目标进行编译。

package main
​
// 引用了Go所能支持的所有架构体系库代码,根据GOARCH选择对应的体系代码
import (  
  "cmd/compile/internal/amd64"
  "cmd/compile/internal/arm"
  "cmd/compile/internal/arm64"
 ....
  "cmd/compile/internal/x86"
...
)
​
// 初始化代码
var archInits = map[string]func(*gc.Arch){
  "386":      x86.Init,
  "amd64":    amd64.Init,
  "arm":      arm.Init,
  "arm64":    arm64.Init,
...
}
​
func main() {
  // disable timestamps for reproducible output
  log.SetFlags(0)
  log.SetPrefix("compile: ")
​
// 通过objabi.GOARCH选择对应的架构体系
  archInit, ok := archInits[objabi.GOARCH]
...
  gc.Main(archInit)
...
}

objabi.GOARCH是$GOROOT/src/cmd/internal/objabi/util.go中的变量GOARCH。

var (
  defaultGOROOT string // set by linker
​
...
  GOROOT   = envOr("GOROOT", defaultGOROOT)
  GOARCH   = envOr("GOARCH", defaultGOARCH)
  GOOS     = envOr("GOOS", defaultGOOS)
...
)

defaultGOARCH是runtime包里的GOARCH值,如下所示。

// Code generated by go tool dist; DO NOT EDIT.
​
package objabi
​
import "runtime"
​
...
const defaultGOOS = runtime.GOOS
const defaultGOARCH = runtime.GOARCH
...

而该值又是通过sys.GOARCH赋值。$GOROOT/src/runtime/extern.go。

// GOARCH is the running program's architecture target:
// one of 386, amd64, arm, s390x, and so on.
const GOARCH string = sys.GOARCH

终于来到了重点!$GOROOT/src/runtime/internal/sys/agoarch_amd64.go

// Code generated by gengoos.go using 'go generate'. DO NOT EDIT.
​
// 我的机器平台是amd64,且未对GOARCH的值做修改。因此这里的构建标签是amd64.
// +build amd64
​
package sys
​
const GOARCH = `amd64`

通过构建amd64的编译标签,从而控制了Go编译时需要选择对应的架构代码。即:如果不是amd64,例如arm,那对应的编译代码就是$GOROOT/src/runtime/internal/sys/agoarch_arm.go。

如何利用交叉编译?

虽然golang 可以跨平台编译,但却无法解决系统的差异性。在靠近底层逻辑的项目中,我们需要直接调用操作系统函数,例如同样是实现IO多路服用,在darwin系统调用kqueue,而linux系统需调用epoll。

相同功能可以编写类似xxx_windows.go xxx.Linux.go文件,根据操作系统编译对应源文件,而不是在文件中用if else规划执行路径。

交叉编译同样可以理解为条件编译,通过构建的build标签,选择需要编译进最终执行二进制文件的代码。

这里给一个简单的条件编译示例,如下。

代码文件

  • go.mod
  • main.go
  • myfunc.go

main.go:程序入口,调用位于myfunc.go中的speak函数。

package main
​
import "fmt"
​
func main() {
  fmt.Println("mike")
  speak("hello")
}

myfunc.go: 构建了build标签,需要build命令 带上-tag speak,该代码才能被编译。

//+build speak
​
package main
​
func speak(s string) {
  println("speak:", s)
}

执行命令

$ go build -o main
$ ./main

输出

mike

可以看到,在main函数中的speak()函数并没有被执行,因为myfunc.go没有被编译。

如果需要将myfunc.go编译进最终的执行代码,则执行命令

$ go build -tags speak -o main
$ ./main

输出

$ mike
$ speak: hello

上述条件编译示例对你是否有启发呢?

举例:

项目开发中,如果想打印程序中的某些信息以便调试,而又不想打印相关代码生成到最终的可执行文件中,那么条件编译便可派上用场。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Golan中 new() 、 make() 和简短声明符的区别和使用

    Golan中 new() 、 make() 和简短声明符的区别和使用

    Go语言中的new()、make()和简短声明符的区别和使用,new()用于分配内存并返回指针,make()用于初始化切片、映射和通道,并返回初始化后的对象,简短声明符:=可以简化变量声明和初始化过程,感兴趣的朋友一起看看吧
    2025-01-01
  • Go常问的一些面试题汇总(附答案)

    Go常问的一些面试题汇总(附答案)

    通常我们去面试肯定会有些不错的Golang的面试题目的,所以总结下,让其他Golang开发者也可以查看到,同时也用来检测自己的能力和提醒自己的不足之处,这篇文章主要给大家介绍了关于Go常问的一些面试题以及答案的相关资料,需要的朋友可以参考下
    2023-10-10
  • go 异常处理panic和recover的简单实践

    go 异常处理panic和recover的简单实践

    在Go语言中,异常处理主要通过panic和recover这两个内建函数来实现,本文主要介绍了go异常处理panic和recover的简单实践,具有一定的参考价值,感兴趣的可以了解一下
    2025-04-04
  • Golang HTTP服务超时控制实现原理分析

    Golang HTTP服务超时控制实现原理分析

    这篇文章主要介绍了Golang HTTP服务超时控制实现原理,HTTP服务的超时控制是保障服务高可用性的重要措施之一,由于HTTP服务可能会遇到网络延迟,资源瓶颈等问题,因此需要对请求进行超时控制,以避免服务雪崩等问题,需要的朋友可以参考下
    2023-05-05
  • 详解golang中Context超时控制与原理

    详解golang中Context超时控制与原理

    Context本身的含义是上下文,我们可以理解为它内部携带了超时信息、退出信号,以及其他一些上下文相关的值,本文给大家详细介绍了golang中Context超时控制与原理,文中有相关的代码示例供大家参考,需要的朋友可以参考下
    2024-01-01
  • Go 语言中的指针的使用

    Go 语言中的指针的使用

    在Go语言中,指针是存储另一变量内存地址的变量,通过&操作符获取变量地址,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-09-09
  • Golang使用channel实现一个优雅退出功能

    Golang使用channel实现一个优雅退出功能

    最近补 Golang channel 方面八股的时候发现用 channel 实现一个优雅退出功能好像不是很难,之前写的 HTTP 框架刚好也不支持优雅退出功能,于是就参考了 Hertz 优雅退出方面的代码,为我的 PIANO 补足了这个 feature
    2023-03-03
  • Go语言学习之映射(map)的用法详解

    Go语言学习之映射(map)的用法详解

    Map是一种无序的键值对的集合。这篇文章主要为大家详细介绍了Go语言中映射的用法,文中的示例代码讲解详细,对我们学习Go语言有一定的帮助,需要的可以参考一下
    2022-04-04
  • Go 项目目录布局保姆级教程

    Go 项目目录布局保姆级教程

    这篇文章主要为大家介绍了Go 项目目录布局保姆级教程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • go logger不侵入业务代码使用slog替换zap并实现callerSkip详解

    go logger不侵入业务代码使用slog替换zap并实现callerSkip详解

    这篇文章主要为大家介绍了go logger不侵入业务代码使用slog替换zap并实现callerSkip详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09

最新评论