Embed files at compile time with go:embed

通过 go:embed 在编译时将文件嵌入

前言

记录使用到其中一项 Go 1.16 的 embed 功能,可以把任何文件在编译时就包进来,进而不用烦恼路径与环境问题。

使用时机

在写某项权限功能时会需要读取本地的 JSON 设置,我本可以直接读取文件使用它:

if data, err := os.ReadFile("./rbac.json"); err != nil {
panic(err)
}

但可能会遇到:

  • 部署到 Docker 后忘了把文件 COPY 进去
  • CI / 测试环境的工作目录不同
  • 二进制文件被放到其他机器执行,却找不到相对路径
  • 想做成单一可执行文件发布

单文件

我自己在跑测试环境时就觉得动态抓文件很麻烦。为了不让程序依赖外部文件设置,可以在编译时就透过 embed 包进 binary:

package constants
import _ "embed"
//go:embed rbac.json
var RBACJSON []byte
if err := json.Unmarshal(constants.RBACJSON, &parsed); err != nil {
println("ERROR: Failed to unmarshal embedded rbac.json:", err.Error())
return
}

这个像魔法的注解://go:embed 是一个编译指令(compiler directive),编译后,rbac.json 内容就已经存在 rbacData 变量中。

变量类型可以是:

  • string
  • []byte
  • embed.FS

多文件

package main
import (
_ "embed"
"fmt"
)
//go:embed messages/*.txt
var messages embed.FS
func main() {
files, _ := messages.ReadDir("messages")
for _, file := range files {
data, _ := messages.ReadFile("messages/" + file.Name())
fmt.Printf("File: %snContent: %snn", file.Name(), data)
}
}

总结

比较项os.ReadFileembed
文件来源运行时读取编译时嵌入
是否依赖路径
发布方式需携带文件单一二进制文件
适合场景动态变更文件静态配置文件

如果数据需要在 runtime 被修改,那就不适合使用 embed,但如果它是「版本的一部分」,那 embed 是更安全的选择。

  • embed 是编译时决定内容,修改文件后必须重新编译
  • 文件会增加 binary 体积,不适合嵌入大型资源(例如视频)
  • 不能嵌入项目外的文件

延伸阅读