go-assets でシングルバイナリにまとめる
//go:embed
ディレクティブが追加されたため,この記事は不要になった。
一応,過去の記録として残しておく。ここのところ小ネタばかりでゴメンペコン。
Go 言語の利点のひとつは単一の実行モジュール(バイナリ・ファイル)でアプリケーションを deploy できる点にある。 しかし,実行モジュール以外のファイル(たとえば固定の辞書ファイルやテンプレート・ファイルなど)がセットになっている場合はそうもいかなくなる。
そこで,実行モジュール以外の外部ファイルをソースコードに取り込んでマージすることにより,全部ひっくるめてシングルバイナリにする方法が考えられた。
ファイル内容を取り込めばその分バイナリのサイズが大きくなってしまうが,元々 Go 言語の実行モジュールはモノリシックな構造でファイルサイズが大きいので,組み込みなど計算リソースに制限がある場合を除けば,大した問題にはならないと思われる。
外部ファイルをマージする方法として,以下のパッケージが有名なようだ。
ただし jteeuwen/go-bindata のほうは awesome-go から削除されるなどちょっとアレな感じになってるみたいで,今後使うなら jessevdk/go-assets のほうがいいかもしれない。
というわけで,この記事では jessevdk/go-assets の使い方を簡単に紹介する。
【2019-09-15 追記】
jessevdk/go-assets は長い間メンテナンスされてないらしい。 というわけで,以下の記事を書いてみた。
まずはファイルを用意
今回のフォルダ・ファイル構成はこんな感じ。
hello/
│ hello.go
│
└─html/
index.html
このうち html/index.html
をマージしたい。
中身はこんな感じ。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hello World!</title>
</head>
<body>
<p>Hello World!</p>
</body>
</html>
go-assets-builder のインストール
index.html
ファイルを Go 言語コードに変換するために jessevdk/go-assets-builder をインストールする。
バイナリは提供されていないので,ここは素直に go get
コマンドを使う。
$ go get -v github.com/jessevdk/go-assets-builder
-h
オプションで見るとこんな感じ。
$ go-assets-builder -h
Usage:
go-assets-builder.exe [OPTIONS] FILES...
Application Options:
-p, -package: The package name to generate the assets for (default: main)
-v, -variable: The name of the generated asset tree (default: Assets)
-s, -strip-prefix: Strip the specified prefix from all paths
-o, -output: File to write output to, or - to write to stdout (default: -)
Help Options:
-? Show this help message
-h, -help Show this help message
ソース・コードの生成
早速 hello/
フォルダ直下で go-assets-builder
を動かしてみる。
$ go-assets-builder html/ > assets.go
これで assets.go
ファイルが生成された。
中身はこんな感じ。
package main
import (
"time"
"github.com/jessevdk/go-assets"
)
var _Assets950a25363eee220f7d8ce234bcc0b349e4ea9072 = "<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\t<title>Hello World!</title>\n</head>\n<body>\n\t<p>Hello World!</p>\n</body>\n</html>"
// Assets returns go-assets FileSystem
var Assets = assets.NewFileSystem(map[string][]string{"/html": []string{"index.html"}, "/": []string{"html"}}, map[string]*assets.File{
"/": &assets.File{
Path: "/",
FileMode: 0x800001ff,
Mtime: time.Unix(1518523695, 1518523695933225200),
Data: nil,
}, "/html": &assets.File{
Path: "/html",
FileMode: 0x800001ff,
Mtime: time.Unix(1518522609, 1518522609437368800),
Data: nil,
}, "/html/index.html": &assets.File{
Path: "/html/index.html",
FileMode: 0x1b6,
Mtime: time.Unix(1518523737, 1518523737979915400),
Data: []byte(_Assets950a25363eee220f7d8ce234bcc0b349e4ea9072),
}}, "")
パッケージ名(既定は main
)は -p
オプションで,変数名(既定は Assets
)は -v
オプションで変更できる。
上の例では index.html
ファイルのパスは実際のフォルダ構成のまま /html/index.html
となっているが, html/
フォルダ直下をドキュメント・ルートにするのであれば -s="/html"
とする。
$ go-assets-builder -p data -v Docs -s="/html" html
package data
import (
"time"
"github.com/jessevdk/go-assets"
)
var _Docs950a25363eee220f7d8ce234bcc0b349e4ea9072 = "<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\t<title>Hello World!</title>\n</head>\n<body>\n\t<p>Hello World!</p>\n</body>\n</html>"
// Docs returns go-assets FileSystem
var Docs = assets.NewFileSystem(map[string][]string{"/": []string{"index.html"}}, map[string]*assets.File{
"/": &assets.File{
Path: "/",
FileMode: 0x800001ff,
Mtime: time.Unix(1518522609, 1518522609437368800),
Data: nil,
}, "/index.html": &assets.File{
Path: "/index.html",
FileMode: 0x1b6,
Mtime: time.Unix(1518523737, 1518523737979915400),
Data: []byte(_Docs950a25363eee220f7d8ce234bcc0b349e4ea9072),
}}, "")
組み込んだファイルを読む
生成したソース・コードで定義した Assets
変数を使って hello.go
ファイルの main()
関数は以下のように書ける。
package main
import (
"fmt"
"io"
"os"
)
func main() {
f, err := Assets.Open("/html/index.html")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
io.Copy(os.Stdout, f)
}
では,実行してみよう。 こんな感じになる。
$ go run hello.go assets.go
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hello World!</title>
</head>
<body>
<p>Hello World!</p>
</body>
</html>
まぁ,テキストを表示してるだけだけど。
ちなみに assets
.FileSystem
は http
.FileSystem
インタフェースと互換がある。
以下は http
.FileSystem
インタフェースの定義。
type FileSystem interface {
Open(name string) (File, error)
}
従って assets
.FileSystem
を使って以下のような簡易 Web サーバも組める。
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
fmt.Println("Open http://localhost:3000/html/")
fmt.Println("Press ctrl+c to stop")
http.Handle("/", http.FileServer(Assets))
if err := http.ListenAndServe(":3000", nil); err != nil {
fmt.Fprintln(os.Stderr, err)
}
}
go generate コマンドによるソース・コードの生成
//
コメントに go:generate
マーカを使うことにより go generate
コマンドで go-assets-builder
コマンドを呼び出せる。
package main
import (
"fmt"
"net/http"
"os"
)
//go:generate go-assets-builder -s="/html" -o assets.go html/
func main() {
fmt.Println("Open http://localhost:3000/")
fmt.Println("Press ctrl+c to stop")
http.Handle("/", http.FileServer(Assets))
if err := http.ListenAndServe(":3000", nil); err != nil {
fmt.Fprintln(os.Stderr, err)
}
}
$ go generate
$ go run hello.go assets.go
Open http://localhost:3000/
Press ctrl+c to stop
go generate
コマンドは明示的に行う必要があるので注意。
ビルドでソース・コード生成を含めて自動化したいなら make とか導入する必要があるかも。
ブックマーク
参考図書
- 改訂2版 みんなのGo言語
- 松木 雅幸 (著), mattn (著), 藤原 俊一郎 (著), 中島 大一 (著), 上田 拓也 (著), 牧 大輔 (著), 鈴木 健太 (著)
- 技術評論社 2019-08-01 (Release 2019-08-01)
- Kindle版
- B07VPSXF6N (ASIN)
- 評価
改訂2版の目玉は7章の「データベースの扱い方」が追加されたことだろう。他の章では,大まかな構成は1版と同じだが細かい部分が変わっていて Go 1.12 への言及まであるのには驚いた。
- プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
- 丸善出版 2016-06-20
- 単行本(ソフトカバー)
- 4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN), 9784621300251 (ISBN)
- 評価
著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。