Go Build、Go Moudle、Go Get等Golang程序编译和构建过程中遇到的问题

AI 摘要: 在go编译过程中遇到unrecognized relocation错误

1. 构建过程中遇到错误记录

1.1. unrecognized relocation (0x2a) in section `.text'

在 go 编译过程中遇到

1
2
3
4
/usr/local/go/pkg/tool/linux_amd64/link: running g++ failed: exit status 1
/usr/bin/ld: /tmp/go-link-811565351/000013.o: unrecognized relocation (0x2a) in section `.text'
/usr/bin/ld: final link failed: Bad value
collect2: error: ld returned 1 exit status

2. 常见 go 命令行相关命令

2.1. 交叉编译

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 查看GOOS、Arch等信息
go tool dist list | grep linux

// 直接交叉构建
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go


// 比如利用shell交叉构建多环境二进制制品:
#!/usr/bin/bash
archs=(amd64 arm64 ppc64le ppc64 s390x)
for arch in ${archs[@]}
do
    env GOOS=linux GOARCH=${arch} go build -o bin_${arch}
done

2.2. go build 涉及点

Go Path 相关:

  1. GOPATH: 包含 Go 源码的 path 路径,Unix 通过:分割多个路径,Win 通过;分割
  2. src/: 源码
  3. pkg/: 安装 objects,期内每个 OS 和 Arch 有其 subdir,模式pkg/GOOS_GOARCH,接着跟着包名路径
  4. bin/: 编译文件存储路径
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 示例
$ go env GOPATH
/private/data/go

$ ll /data/go
total 0
drwxr-xr-x 41 lupingguo staff 1.3K Nov 12 18:01 bin
drwxr-xr-x  5 lupingguo staff  160 Jul 16  2020 pkg
drwxr-xr-x 15 lupingguo staff  480 Sep 21 12:06 src

$ pkg内除了含GOOS_GOARCH,也有mod和sumdb内容,mod内含通过module方式管理包的,比如`/data/go/pkg/mod/github.com/webview/webview@v0.0.0-20210330151455-f540d88dde4e`
── pkg
│   ├── darwin_amd64
│   ├── mod
│   └── sumdb

/home/user/gocode/
    src/
        foo/
            bar/               (go code in package bar)
                x.go
            quux/              (go code in package main)
                y.go
    bin/
        quux                   (installed command)
    pkg/
        linux_amd64/
            foo/
                bar.a          (installed package object)

2.3. 构建约束

参考: https://tkstorm.com/gopl-book/ch10/ch10-07.html

1
2
// +build linux darwin
// 通过上面的注释,表明文件仅在对应平台构建,

3. go module 管理

3.1. 模块感知

模块感知模式下,该go命令使用go.mod文件来查找版本化依赖项,通常从模块缓存中加载包,如果模块丢失,则下载模块。

模块感知模式下,GOPATH无意义了,但仍然存储下载的依赖放在GOPATH/pkg/mod下,并且安装到GOPATH/bin(当GOBIN未设置时候)

  1. GOPATH模式下,该go命令忽略模块,它在vendor目录中GOPATH查找并查找依赖项。
  2. Go 1.16 开始,默认情况下启用模块感知模式,无论 go.mod文件是否存在
  3. 模块感知模式可以与控制GO111MODULE环境变量,它可以被设置为on,off,或auto
    1. GO111MODULE=off,则该go命令将忽略go.mod文件并以GOPATH模式运行
    2. GO111MODULE=on 或 未设置,go即使没有go.mod文件存在,该命令也会以模块感知模式运行
    3. GO111MODULE=auto,在Go 1.15及更低版本中,这是默认行为
      1. 如果go.mod文件存在于当前目录或任何父目录中,则以模块感知模式运行
      2. 如果go mod子命令和go install会一个以模块感知模式运行,即使没有go.mod文件存在

3.1.1. 所有加载包相关信息的均为模块感知的

参考: https://go.dev/ref/mod#mod-commands

1
2
3
4
5
6
7
8
9
go build
go fix
go generate
go get
go install
go list
go run
go test
go vet

3.1.2. go get

用途: go get命令更新主模块go.mod文件中的模块依赖关系,然后构建和安装命令行中列出的包

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 升级指定模块 Upgrade a specific module.
$ go get -d golang.org/x/net

# 升级main模块,扩展为一组包
$ go get -d -u ./...

# 升级/降级到指定包的指定版本,可以指示特定版本 ( v0.3.0)、版本前缀 ( v0.3)、分支或标记名称 ( master)、修订版 ( 1234abcd) 或特殊查询之一latest,upgrade, patch, 或none
$ go get -d golang.org/x/text@v0.3.2
$ go get -d golang.org/x/text@master

# 移除版本依赖
$ go get -d golang.org/x/text@none

两个不同版本中需要一个模块(在命令行参数中明确指定或满足升级和降级),go get将报告错误。在go get选择了一组新版本后,它会检查任何新选择的模块版本或任何提供命令行中命名的包的模块是否已被收回或弃用,在Go 1.18中,go get更专注于管理go.mod. 该-d标志已弃用,默认即始终启用

3.1.3. go install

Go 1.15 及更低版本不支持使用go install

用途: go install命令构建并安装由命令行上的路径命名的包。可执行文件(main包)安装到GOBIN的环境变量,其默认值为$GOPATH/bin或者$HOME/go/bin,可执行文件在$GOROOT安装在$GOROOT/bin$GOTOOLDIR代替$GOBIN,不可执行的包被构建和缓存但不安装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 安装最新版本的程序,在当前目录忽略go.mod
$ go install golang.org/x/tools/gopls@latest

# 安装指定版本的应用程序
$ go install golang.org/x/tools/gopls@v0.6.4

# 在当前目录下安装应用
$ go install golang.org/x/tools/gopls

# 在文件路径下安装应用程序
$ go install ./cmd/...

3.1.4. go list -m

1
2
3
4
# -m标志导致go list列出模块而不是包
$ go list -m all
$ go list -m -versions example.com/m
$ go list -m -json example.com/m@latest

3.2. go.mod 范例

1
2
3
4
5
6
7
8
9
module example.com/my/thing

go 1.12

require example.com/other/thing v1.0.2
require example.com/new/thing/v2 v2.3.4
exclude example.com/old/thing v1.2.3
replace example.com/bad/thing v1.4.5 => example.com/good/thing v1.4.5
retract [v1.9.0, v1.9.5]

3.3. 模块版本号

参考: https://go.dev/doc/modules/version-numbers

模块版本号的含义,模块版本号的每一部分来表示版本的稳定性和向后兼容性:

  • 开发中:v0.x.x,表示模块仍处于开发阶段且不稳定,没有向后兼容保证
  • 主版本: v1.x.x,主版本有重大更新,不保证向后 API 兼容
  • 次版本: vx.4.x, 主版本无大改动,但有功能变化,保证后项兼容和稳定性
  • 修订版: vx.x.1, 不影响模块公共 API,保证后项兼容和稳定
  • 预发版: vx.x.x-beta.2,预发布历程碑,如 alpha 或 beta,不提供稳定性保证

3.4. Go Proxy 包下载逻辑

假设该 go 命令正在寻找提供 package 的模块golang.org/x/net/html 假定配置的 GoProxy 设置: https://corp.example.com,https://proxy.golang.org

  1. https://corp.example.com/(并行):

    • 索取最新版本 golang.org/x/net/html
    • 索取最新版本 golang.org/x/net
    • 索取最新版本 golang.org/x
    • 索取最新版本 golang.org
  2. https://proxy.golang.org/,如果所有请求https://corp.example.com/ 都以404410 失败:

    • 索取最新版本 golang.org/x/net/html
    • 索取最新版本 golang.org/x/net
    • 索取最新版本 golang.org/x
    • 索取最新版本 golang.org

找到合适的模块后,该 go 命令将使用新模块的路径和版本将新需求添加 到主模块的go.mod文件中,将在相同的版本中使用相同的模块。如果解析的包不是由主模块中的包导入的,新的需求会有 // indirect注释。

3.5. 最小版本选择(MVS)

参考: https://go.dev/ref/mod#minimal-version-selection

MVS 在依赖的有向图上运行,用 go.mod 指定,每个顶点代表一个模块版本,每条边代表最低要求版本,使用 require 指令指定,replace、exclude 修改图形;

通过go list -m all检测构建列表

3.5.1. 最小版本选择 Case

MVS 返回一个包含粗体版本的构建列表:A 1.2、B 1.2、C 1.4 和 D 1.2。请注意,可以使用更高版本的 B 和 D,但 MVS 不会选择它们,因为不需要它们。

3.5.2. 替换 -replace

其中 C 1.4 已被 R 替换。R 依赖于 D 1.3 而不是 D 1.2,因此 MVS 返回包含 A 1.2、B 1.2、C 1.4(替换为 R)和 D 1.3 的构建列表

3.5.3. 排除 - exclude

C 1.3 已被排除。MVS 的行为就像 A 1.2 需要 C 1.4(下一个更高版本)而不是 C 1.3。

3.5.4. 升级 - go get -u

模块B可能从1.2升级到1.3,C可能从1.3升级到1.4,D可能从1.2升级到1.3。升级(和降级)可能会添加或删除间接依赖项。在这种情况下,E 1.1 和 F 1.1 出现在升级后的构建列表中,因为 B 1.3 需要 E 1.1。为了保留升级,该go命令会更新go.mod. 它将对 B 的要求更改为 1.3 版。它还将添加对 C 1.4 和 D 1.3 的要求和// indirect注释,因为否则不会选择这些版本。

3.5.5. 降级

假设发现 C 1.4 有问题,所以我们降级到 C 1.3。C 1.4 从模块图中删除。B 1.2 也被删除,因为它需要 C 1.4 或更高。主模块对B的要求改为1.1

3.6. go.mod 文件

参考:

每个 go module 都有一个 go.mod 文件定义,描述了模块的属性,包括对其他模块和 Go 版本的依赖关系,模块属性包括:

  • 模块名: 描述当前模块的路径,和模块版本唯一标识一个唯一的模块,通常能够被go tool下载,如果模块版本 v2 或更后,则尾部加上版本号,如/v2: module example.com/mymodule/v2
  • Go 版本: 说明当前模块需要的最低 Go 版本,go 1.14表示最小版本是go 1.14
  • 模块路径: 用于 go 工具从下载位置,如代码仓库,和版本号结合一起当做唯一标识符
  • 模块依赖: 当前模块所依赖其他模块的最低版本列表
  • 模块替代: 用另一个模块版本或本地路径替代 require 的模块,或者排除指定版本依赖的模块
  • 模块 go.mod 文件初始化: go mod init example/mymodule
  • 常用命令: go get go mod tidy go mod edit

3.6.1. 模块名

1
2
# 通常为仓库地址,如果没有仓库地址,考虑用company-name
go mod init <company-name>/stringtools

3.6.2. 依赖 - require

1
2
3
4
5
6
# 格式
require 模块路径 模块版本
require example.com/othermodule v1.2.3

# 在其存储库中尚未标记的版本
require example.com/othermodule v0.0.0-20200921210052-fa0125251cc4

3.6.3. 替换 - replace

replace 指令在以下情况下很有用:

  • 正在开发一个新模块,其代码尚未在存储库中,但想使用本地版本对客户端进行测试
  • 已经确定了依赖项的问题,克隆了依赖项的存储库,并正在使用本地存储库测试修复程序

一些 replace 的示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# example.com/othermodule 的任何版本都被替换为其代码的指定分支
replace example.com/othermodule => example.com/myfork/othermodule v1.2.3-fixed

# 指定应使用版本 v1.2.3 而不是模块的任何其他版本
replace example.com/othermodule => example.com/othermodule v1.2.3

# 仅将模块版本 v1.2.5 替换为同一模块的版本 v1.2.3
replace example.com/othermodule v1.2.5 => example.com/othermodule v1.2.3

# 指定应使用本地目录替换模块的所有版本
replace example.com/othermodule => ../othermodule

# 指定本地目录应仅用作 v1.2.5 的替代品
replace example.com/othermodule v1.2.5 => ../othermodule

3.6.4. 排除 - exclude

可以使用它来排除具有无效校验和的模块版本,通过 go 命令: go mod edit -exclude=example.com/theirmodule@v1.3.0

1
2
3
4
5
6
# 语法
exclude 模块路径 模块版本
exclude module-path module-version

# 指定要从当前模块的依赖关系图中排除的模块或模块版本
exclude example.com/theirmodule v1.3.0

3.6.5. 回收 - retract

当一个版本发布过早或发布后发现一个严重的问题指令非常有用

1
2
3
4
5
# 撤回单个版本
retract v1.1.0 // Published accidentally.

# 收回一系列版本
retract [v1.0.0,v1.0.5] // Build broken on some platforms.

3.7. 管理依赖

参考: https://go.dev/doc/modules/managing-dependencies

3.8. 帮助命令

1
2
3
# 帮助
go help command-name
go help mod tidy

3.9. 开发与发布Go Module

参考:

3.9.1. 发布Module

  1. 进入到模块根目录
  2. go mod tidy,移除无效依赖
  3. go test ./...
  4. 对项目进行用新版本 tag 打包:
1
2
$ git commit -m "mymodule: changes for v0.1.0"
$ git tag v0.1.0
  1. 发布到远程仓库
1
$ git push origin v0.1.0
  1. 通过go list验证模块可用
1
$ GOPROXY=proxy.golang.org go list -m example.com/mymodule@v0.1.0
  1. 在其他项目通过go get获取指定版本
1
$ go get example.com/mymodule@v0.1.0