跳到主要内容
  1. Skills/
  2. 后端编程/

使用go build来编译go程序

··字数 1841·4 分钟
go howto

这篇文件准备揭开 go build 命令的面纱,帮助你了解如何通过 go build 来得到一个可执行程序。

同时,在上一篇文章中介绍使用 go modules 来管理 go 工程并编写第一个 helloworld 示例程序时,留下了一个悬念,为什么使用 hellohello 来作为程序文件的名称?

还是那个 helloworld #

下面仍然使用上文中的例子 helloworld

~/Projects/go/hello
➜  ls
go.mod        hellohello.go

其中 go.mod 内容如下:

// go.mod
module world

go 1.16

hellohello.go 代码如下:

// hellohello.go
package main

import "fmt"

func main() {
    fmt.Println("hello world 😀😀😀")
}

我们知道,使用 go run hellohello.go 可以编译并运行 hellohello.go 中所定义的逻辑。但是生成的可执行程序在操作系统的临时文件目录下, 如 临时文件目录/go-build2507969658/b001/exe/hellohello,这个可执行程序是用来测试的。

为了在当前目录下生成可执行程序,要使用开发工具链的 go build 工具。

  • go build hellohello.go

在这个 helloworld 示例程序目录下,执行 go build hellohello.go,会生成一个叫 hellohello 的可执行程序。如果你的文件名叫 helloworld,那么生成的可执行程序名称就为 helloworld

  • go build

helloworld 示例程序目录下,执行 go build,会生成一个叫 world 的可执行程序,这个名称是 go.mod 中定义的模块名称。 如果把模块名称定义为 world/youwu.today,那么生成的可执行程序叫 youwu.today

  • 其它的

以上只不过是为了方便的默认规则。当然,你还可以额外指定生成的可执行程序的名称,如使用go build -o 路径来指定路径:

~/Projects/go/hello
➜  go build -o .

~/Projects/go/hello
➜  ls

~/Projects/go/hello
➜  ls
go.mod        hellohello    hellohello.go world

使用 go build -o 名称 来指定生成的程序名:


~/Projects/go/hello
➜  go build -o h

~/Projects/go/hello
➜  ls
go.mod        h             hellohello    hellohello.go world

假设你的工程里面有多个 main 函数,需要生成多个可执行程序,那么使用 go build -o 输出名称 go文件的路径或位置。比如你可能会看到别人开源的 cli 工具,在 cmd 目录下有好多个子程序,使用了非常复杂的构建脚本来生成多个可执行程序,下次就知道了,通过一个 go build 无法完成的,使用 shell 或者 bat 脚本自己写一个执行脚本。

交叉编译 #

交叉编译( cross compiling )中的 交叉 cross,是 跨平台( cross platform ) 的意思。go 语言在设计的时候,就被设计成为可以在一个系统上编译,然后拿到其它系统系统上执行,类似于 java 的 jar 文件。不同的是,jar 文件是一个 字节码 class 文件的 zip 压缩包,它要在 java vm 虚拟机中解析运行。而 go 所编译出现的是独立的单个可以二进制可执行文件。

比如在 windows 上完成开发,然后编译交叉编译成多 windows 版本(本机开发测试)、linux 版本(生成部署)。 命令比较简单,指明 操作系统CPU架构 类型即可,如下,

在 windows 上编译可在 linux 上运行的 64 位可执行程序:

set GOOS=linux
set GOARCH=amd64
go build -o hello hello.go

在 linux 上编译可在 windows 上运行的 64 位可执行程序:

GOOS=windows GOARCH=amd64 go build -o hello.exe hello.go

在 macos 上的命令与 linux 相同,通过在命令前面添加环境变量。

不过,是否最终可以成功编译,还是要看编译的内容是否引用到操作系统的底层、CGO 等需要 C/C++ 语言编译环境。不过,如果只是写一般的 go 应用程序,上面的交叉编译是没有问题的。如果你碰到了无法正确编译,说明你碰到了不在命令本身而是层次更高的编程与三方库使用的问题。

GOOSGOARCH 的所有可能项,可以使用 go tool dist list 这个命令查看。

zsh ❯ go version
go version go1.20.2 darwin/amd64

zsh > go tool dist list
os 386 amd64 arm arm64
android
darwin
dragonfly
freebsd
illumos
ios
linux
netbsd
openbsd
plan9
solaris
windows

还有一些非 intel、ARM 架构的 芯片。比如 mips,不过在90年代,它非常流行,广泛用于嵌入式、街机、打印机、机顶盒、电视机等。loong64 是指龙芯,ppc 是指 powerpc。而 js/wasm 是指可以被 js调用的可浏览器运行或者 wasm 运行时中运行的 web 汇编。

  • aix/ppc64
  • freebsd/riscv64
  • js/wasm
  • linux/loong64
  • linux/mips
  • linux/mips64
  • linux/mips64le
  • linux/mipsle
  • linux/ppc64
  • linux/ppc64le
  • linux/riscv64
  • linux/s390x
  • openbsd/mips64

补充知识 #

若你是 go 编程新手,看了本文以及 go modules ,其中的 go rungo build 都没有向你指明要求。没错,就是 main package main function

这个隐含的规则非常关键,因为它会决定是否可以正常编译。

golang 的规定是这样的: 一个 go 程序中,如果定义了 main package main function,那么它会作为整个程序的入口,所有的用户过程都是从这里开始。

执行 go build 或者 go run 命令时,会查找指向目录是不是一个 main package,再查找这个 main package 是否有一个 main 函数。

举两个反例来辅助理解。

  • 修改为非 main package
// hellohello.go
package youwutoday

import "fmt"

func main() {
    fmt.Println("hello world 😀😀😀")
}

使用 go build 或者 go run 得到的结果表示,go build不会生成可执行程序(因为此时不是 main package),而 go run 则提示不是 main package 不能执行。

~/Projects/go/hello
➜  go build

~/Projects/go/hello
➜  ls
go.mod        hellohello.go

~/Projects/go/hello
➜  go run hellohello.go
go run: cannot run non-main package
  • 修改为无 main 函数
// hellohello.go
package main

import "fmt"

func youwutoday() {
    fmt.Println("hello world 😀😀😀")
}

go build 或者 go run 得到的结果来看,没有 main 函数根本无法继续。

➜  go run hellohello.go
# command-line-arguments
runtime.main_main·f: function main is undeclared in the main package

~/Projects/go/hello
➜  go build
# world
runtime.main_main·f: function main is undeclared in the main package