go generate を使った assets ディレクトリの静的組み込みバイナリ処理
モノリポで管理しているSPAなクライアント側の成果物 (dist) を gin で静的配布 できないかと思った際に調べた内容。 結局使用していないが、バイナリ化する手順としては使えそうなのでメモしておく。
ディレクトリは以下の様な形
├── Makefile
├── assets -> ../../client/back/dist
├── box
│ ├── blob.go
│ └── box.go
└─generator.go
Makefile
- まずは関係ないけど、Makefileって@書けば && バックスラッシュ連結とかいらんのかという発見
./...
指定で全てを対象に指定
generate:
@go generate ./...
@echo "[OK] Files added to embed box!"
generator.go
- 1行目はfmtをかけると自動付与された
// +build ignore
コメントにより通常ビルド対象から外れる設定- 生成される go ファイルは
blob.go
でbox
配下に出力されるf, err := os.Create(blobFileName)
や、embedFolder string = "../assets/"
から current directory はbox
配下の模様
//go:build ignore
// +build ignore
package main
import (
"bytes"
"fmt"
"go/format"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"text/template"
)
const (
blobFileName string = "blob.go"
embedFolder string = "../assets/"
)
// Define vars for build template
var conv = map[string]interface{}{"conv": fmtByteSlice}
var tmpl = template.Must(template.New("").Funcs(conv).Parse(`package box
// Code generated by go generate; DO NOT EDIT.
func init() {
{{- range $name, $file := . }}
box.Add("{{ $name }}", []byte{ {{ conv $file }} })
{{- end }}
}`),
)
func fmtByteSlice(s []byte) string {
builder := strings.Builder{}
for _, v := range s {
builder.WriteString(fmt.Sprintf("%d,", int(v)))
}
return builder.String()
}
func main() {
// Checking directory with files
if _, err := os.Stat(embedFolder); os.IsNotExist(err) {
log.Fatal("Configs directory does not exists!")
}
// Create map for filenames
configs := make(map[string][]byte)
// Walking through embed directory
err := filepath.Walk(embedFolder, func(path string, info os.FileInfo, err error) error {
relativePath := filepath.ToSlash(strings.TrimPrefix(path, embedFolder))
if info.IsDir() {
// Skip directories
log.Println(path, "is a directory, skipping...")
return nil
} else {
// If element is a simple file, embed
log.Println(path, "is a file, packing in...")
b, err := ioutil.ReadFile(path)
if err != nil {
// If file not reading
log.Printf("Error reading %s: %s", path, err)
return err
}
// Add file name to map
configs[relativePath] = b
}
return nil
})
if err != nil {
log.Fatal("Error walking through embed directory:", err)
}
// Create blob file
f, err := os.Create(blobFileName)
if err != nil {
log.Fatal("Error creating blob file:", err)
}
defer f.Close()
// Create buffer
builder := &bytes.Buffer{}
// Execute template
if err = tmpl.Execute(builder, configs); err != nil {
log.Fatal("Error executing template", err)
}
// Formatting generated code
data, err := format.Source(builder.Bytes())
if err != nil {
log.Fatal("Error formatting generated code", err)
}
// Writing blob file
if err = ioutil.WriteFile(blobFileName, data, os.ModePerm); err != nil {
log.Fatal("Error writing blob file", err)
}
}
box.go
//go:generate go run ../generator.go
でgo generate
時に実行される処理を記載する- プロジェクトルートから
./...
で全ファイルが対象として起動されたあと、box.go
を見つけて、 先頭に//go:generate...
があるからその後ろに定義しているコマンドを実行するような動き
- プロジェクトルートから
//go:generate go run ../generator.go
package box
type embedBox struct {
storage map[string][]byte
}
// Create new box for embed files
func newEmbedBox() *embedBox {
return &embedBox{storage: make(map[string][]byte)}
}
// Add a file to box
func (e *embedBox) Add(file string, content []byte) {
e.storage[file] = content
}
// Get file's content
func (e *embedBox) Get(file string) []byte {
if f, ok := e.storage[file]; ok {
return f
}
return nil
}
// Embed box expose
var box = newEmbedBox()
// Add a file content to box
func Add(file string, content []byte) {
box.Add(file, content)
}
// Get a file from box
func Get(file string) []byte {
return box.Get(file)
}
blob.go
go generate
後の成果物generator.go
の template 処理で書いている通り、box.Add
でバイナリデータをキー(相対パス)に紐付けるコードが生成されている
package box
// Code generated by go generate; DO NOT EDIT.
func init() {
box.Add("js/app.js", []byte{0, 0, ...
:
blob.goの使用方法
fmt.Println(string(box.Get("js/app.js")))