Upgrade to Pro — share decks privately, control downloads, hide ads and more …

embedパッケージを深掘りする / Deep Dive into embed Package...

task4233
January 18, 2025

embedパッケージを深掘りする / Deep Dive into embed Package in Go

Gopher's Gatheringでの発表資料です。
ref: https://connpass.com/event/329963/

Go1.16で追加されたembedパッケージ関連の機能について、関連機能の概要と内部実装についてシェアしました。

task4233

January 18, 2025
Tweet

More Decks by task4233

Other Decks in Technology

Transcript

  1. 6 想定対象者 ・Go言語の初心者から経験豊富な方まで (ref: イベント概要) 発表の目的 ・embedパッケージの概要を理解してもらうこと ・//go:embed ディレクティブで指定されたファイルが  バイナリへ埋め込まれるまでの流れを掴んでもらうこと

    注意 ・非本質的な説明を省きます  ・全て話すには時間がかかりすぎるので  ・後ほどブログで詳細な説明を出す予定 ・Go1.23.5の情報をベースに扱います 発表に際して
  2. 10 1. 単一ファイルの埋め込み: string、[]byte型の変数へ 2. 複数ファイルの埋め込み: embed.FS型の変数へ 2種類の埋め込み方法 import _

    "embed" //go:embed assets/hello.txt var hello string //go:embed assets/hello.txt var helloBytes []byte func main() { println(hello) println(helloBytes) } 1: 単一ファイルの埋め込み import "embed" //go:embed assets/* var assets embed.FS func main() { var b []byte // 説明用にエラーハンドリングは省略 b, _ = assets.ReadFile("hello.txt") println(b) } 2: 複数ファイルの埋め込み ※単一ファイルのみの指定も可能
  3. 11 1. 単一ファイルの埋め込み: string、[]byte型の変数へ 2. 複数ファイルの埋め込み: embed.FS型の変数へ embedパッケージの 2つの利用例 import

    _ "embed" //go:embed assets/hello.txt var hello string //go:embed assets/hello.txt var helloBytes []byte func main() { println(hello) println(helloBytes) } 1: 単一ファイルの埋め込み import "embed" //go:embed assets/* var assets embed.FS func main() { var b []byte // 説明用にエラーハンドリングは省略 b, _ = assets.ReadFile("hello.txt") println(b) } 2: 複数ファイルの埋め込み ※単一ファイルのみの指定も可能 string 型、[]byte型の変数へ 単一ファイルを埋め込む
  4. 12 1. 単一ファイルの埋め込み: string、[]byte型の変数へ 2. 複数ファイルの埋め込み: embed.FS型の変数へ embedパッケージの 2つの利用例 import

    _ "embed" //go:embed assets/hello.txt var hello string //go:embed assets/hello.txt var helloBytes []byte func main() { println(hello) println(helloBytes) } 1: 単一ファイルの埋め込み import "embed" //go:embed assets/* var assets embed.FS func main() { var b []byte // 説明用にエラーハンドリングは省略 b, _ = assets.ReadFile("hello.txt") println(b) } 2: 複数ファイルの埋め込み ※単一ファイルのみの指定も可能 embed.FS 型の変数へ 複数ファイルを埋め込む
  5. 13 埋め込みファイルの指定方法 // import _ "embed" import "embed" // 単一ファイルの埋め込み

    //go:embed assets/hello.txt //go:embed assets/hello.txt //go:embed assets/h*.*t // コメントと空白行を間に書いても OK var hello string // 複数ファイルの埋め込み //go:embed main.go go.mod //go:embed "secret.txt" //go:embed `quoted.txt` var assets embed.FS embedパッケージのimportが必要 ・string 型、 []byte 型への埋め込み  ではblank importをする必要がある blank importをやめるためのProposalも ・https://github.com/golang/go/issues/62417
  6. 14 埋め込みファイルの指定方法 // import _ "embed" import "embed" // 単一ファイルの埋め込み

    //go:embed assets/hello.txt //go:embed assets/hello.txt //go:embed assets/h*.*t // コメントと空白行を間に書いても OK var hello string // 複数ファイルの埋め込み //go:embed main.go go.mod //go:embed "secret.txt" //go:embed `quoted.txt` var assets embed.FS //go:embed ディレクティブでファイル指定 ・ go:embed の後にスペースを挟んで  パスを書く 変な書き方も出来る ・間の // 形式のコメントと空白行 ・複数回の同じディレクティブ ・ワイルドカード(*)の利用
  7. 15 埋め込みファイルの指定方法 // import _ "embed" import "embed" // 単一ファイルの埋め込み

    //go:embed assets/hello.txt //go:embed assets/hello.txt //go:embed assets/h*.*t // コメントと空白行を間に書いても OK var hello string // 複数ファイルの埋め込み //go:embed main.go go.mod //go:embed "secret.txt" //go:embed `quoted.txt` var assets embed.FS //go:embed ディレクティブでファイル指定 ・ go:embed の後にスペースを挟む 変な書き方 ・間にはコメントと空白行を入れても良い ・同じディレクティブを2回書いても良い ・ワイルドカードを利用しても良い 単一ファイルの埋め込み ・//go:embed assets/hello.txt  を1行書いた時と同じ意味 ※assetsディレクトリ内で h*.*t に マッチするファイルが1つのみの場合
  8. 16 埋め込みファイルの指定方法 // import _ "embed" import "embed" // 単一ファイルの埋め込み

    //go:embed assets/hello.txt //go:embed assets/hello.txt //go:embed assets/h*.*t // コメントと空白行を間に書いても OK var hello string // 複数ファイルの埋め込み //go:embed main.go go.mod //go:embed "secret.txt" //go:embed `quoted.txt` var assets embed.FS 他のファイル指定方法 ・複数行のディレクティブ ・スペース区切りのパス ・ダブルクオート(")で囲まれたパス ・バッククオート(`)で囲まれたパス
  9. 17 下記の記号を含むパスは埋め込めない ・ *, <, >, ?, `, ’, |,

    /. \, : ディレクトリ内のドット ( . )およびアンダースコア ( _ )始まりのファイルは 基本的に無視される ・assets/.env など ・指定したい場合はパスの前に all: プレフィックスをつける  例) //go:embed all:assets/* 対象ファイルが 1つも存在しないパスは //go:embed ディレクティブで 指定できない ・上記の無視されるファイルのみで構成されたファイルツリー ・空ディレクトリを指定したパス 埋め込みファイル指定における注意点
  10. 18 io/fsはファイルシステムを抽象化したパッケージ ・https://pkg.go.dev/io/fs ・embedパッケージと同様にGo 1.16で標準パッケージ入りした 👏 embed.FSはio/fsのインタフェースを実装している type FS interface

    { Open(name string) (File, error) } type ReadDirFS interface { FS ReadDir(name string) ([]DirEntry, error) } type ReadFileFS interface { FS ReadFile(name string) ([]byte, error) } Go1.23.5で実装されているインタフェース
  11. 20 ・$ go list コマンドでembedパッケージの関連情報を取得できる 埋め込まれた対象ファイルの確認 コマンド 実行結果の例 列挙されるもの $

    go list -f '{{.EmbedPatterns}}' [assets/*] //go:embed ディレクティブで 指定されたパターン (testは対象外) $ go list -f '{{.EmbedFiles}}' [assets/icon.png assets/icon.jpg] 埋め込み対象となったファイル一覧 (testは対象外) $ go list -f '{{.XTestEmbedPatterns}}' [assets/*] //go:embed ディレクティブで 指定されたパターン (testのみ) 関連情報の取得用コマンド一覧
  12. 24 Goコンパイラ処理は以下の3ステージに大別される ・フロントエンド : ソースコードを解析して中間表現(IR: コンパイラAST)を 構築するステージ ・ミドルエンド  : IRを最適化するステージ

    ・バックエンド  : 最適化されたIRを機械語へ変換するステージ Goコンパイラ処理の概要 1. 字句解析 2. 構文解析 3. 型チェック 4. IRの構築 1. IRに対する最適化 2. IR(コンパイラAST)  の巡回 1. SSA形式の構築 2. 機械語生成 フロントエンド ミドルエンド バックエンド
  13. 25 Goコンパイラ処理の概要 フロントエンド ミドルエンド バックエンド //go:embed ディレクティブ (pragma)の解釈 //go:embed ディレクティブの

    チェック バイナリへの 埋め込み 1. 字句解析 2. 構文解析 3. 型チェック 4. IRの構築 1. IRに対する最適化 2. IR(コンパイラAST)  の巡回 1. SSA形式の構築 2. 機械語生成
  14. 26 フロントエンドでの処理 フロントエンド ミドルエンド バックエンド //go:embed ディレクティブ (pragma)の解釈 //go:embed ディレクティブの

    チェック バイナリへの 埋め込み 1. 字句解析 2. 構文解析 3. 型チェック 4. IRの構築 1. IRに対する最適化 2. IR(コンパイラAST)  の巡回 1. SSA形式の構築 2. 機械語生成
  15. 27 フロントエンドでの処理 フロントエンド ミドルエンド バックエンド //go:embed ディレクティブ (pragma)の解釈 //go:embed ディレクティブの

    チェック バイナリへの 埋め込み 1. 字句解析 2. 構文解析 3. 型チェック 4. IRの構築 1. IRに対する最適化 2. IR(コンパイラAST)  の巡回 1. SSA形式の構築 2. 機械語生成 🤔
  16. 28 言語仕様の一部ではないので、 Goの言語仕様 に書かれていない ・ALGOL 68の pragmats に由来し、Cで #pragma が採用されてから

     この名称が浸透した ・機能として濫用することは望ましくないとコメントされている(ref) Goでは以下のようなコメントが Pragmaとして扱われる ・//go: で始まるコメント ・//line で始まるコメント これらのコメントを処理するハンドラを Pragma Handlerと呼ぶ Pragmaはコンパイラ /インタプリタ用のディレクティブ
  17. 29 ソースコードを Goの言語仕様 に沿ってパースする部分が主に関連する ・パース時に // で始まる文字列が来た場合  ・Pragma Handlerによって後続文字列をパースする   ・後続文字列に

    go:embed が来た場合    ・後続するパス部分をパース   ・パースされたembed用のpragmaを pragma.Embedsに格納 ・パース時に var による変数宣言が来た場合  ・var 宣言に、上記のpragmaをセットする //go:embed ディレクティブの解釈 日本語はわかりづらい......
  18. 42 //go:embed hello.txt var hello string 具体例でパーサの気持ちになる varDecl.Pragma = pragmaEmbeds{

    {pos, []string{"hello.txt"}}, } varDeclにpragma情報が セットされた👏
  19. 44 ソースコードで考える : parseGoEmbedの実装(ref) パス指定のパターン パース前 (args string) パース後 ([]string,

    error) スペース区切り "a.txt b/c.go" []string{"a.txt", "b/c.go"}, nil ダブルクオート "\"hoge fuga.go\"" []string{"hoge fuga.go"}, nil バッククオート "`hoge fuga.go`" []string{"hoge fuga.go"}, nil
  20. 46 ミドルエンドでの処理 1. 字句解析 2. 構文解析 3. 型チェック 4. IRの構築

    1. SSA形式の構築 2. 機械語生成 フロントエンド ミドルエンド バックエンド //go:embed ディレクティブ (pragma)の解釈 //go:embed ディレクティブの チェック バイナリへの 埋め込み 1. IRに対する最適化 2. IR(コンパイラAST)  の巡回
  21. 47 ミドルエンドでの処理 1. 字句解析 2. 構文解析 3. 型チェック 4. IRの構築

    1. SSA形式の構築 2. 機械語生成 フロントエンド ミドルエンド バックエンド //go:embed ディレクティブ (pragma)の解釈 //go:embed ディレクティブの チェック バイナリへの 埋め込み 1. IRに対する最適化 2. IR(コンパイラAST)  の巡回 🤔
  22. 57 バックエンドでの処理 1. 字句解析 2. 構文解析 3. 型チェック 4. IRの構築

    1. ASTに対する最適化 2. ASTの巡回 1. SSA形式の構築 2. 機械語生成 フロントエンド ミドルエンド バックエンド //go:embed ディレクティブ (pragma)の解釈 //go:embed ディレクティブの チェック バイナリへの 埋め込み
  23. 62 ファイルを読み込む前に、ファイルサイズを見てから処理を分けている ・0B < size ≦ 1024B: 読み取ってstringシンボルとして保持 ・1024B <

    size < 2GB: 読み取らずにfileStringシンボルとして保持 ・size ≧ 2GB: シンボル埋め込みができない  ・対象のシンボルではサイズがint32で管理されているので(ref)  ・int32の最大値は2^31-1=2,147,483,647≧2*10^9 => 2GB 埋め込むファイルのシンボル
  24. 68 ・関連Issues  ・静的ファイル埋め込み機能の提起: https://github.com/golang/go/issues/35950   ・Prototype: https://github.com/golang/go/issues/41191   ・io/fsパッケージ: https://github.com/golang/go/issues/41190  ・後続(抜粋)   ・import

    _ embedをimport embedで書けるように: https://github.com/golang/go/issues/62417   ・モジュールパスとファイル名にunicodeを使えるように: https://github.com/golang/go/issues/67562   ・map[string]でembedできるように: https://github.com/golang/go/issues/69595   ・-embed=compress flagが利用できるように: https://github.com/golang/go/issues/61322   ・親ディレクトリもembedできるように: https://github.com/golang/go/issues/58519   ・書き込み可能にできるように: https://github.com/golang/go/issues/45757   ・リモートリソースの埋め込み: https://github.com/golang/go/issues/56401 ・Draft Design  ・Design Doc: https://go.googlesource.com/proposal/+/master/design/draft-embed.md  ・Video: https://youtu.be/rmS-oWcBZaI  ・Implementation: https://go-review.googlesource.com/c/go/+/243945 ・反応  ・Hacker News: https://news.ycombinator.com/item?id=23933966 4. Appendix
  25. 69 • Introduction to the Go compiler • https://qiita.com/junjis0203/items/616c00086eb336153f4f#appendgroupメソッドと解析メソッド •

    https://gist.github.com/junjis0203/d97c4fc5384ccaff231b60c2c9e6d2b4#file-montecarlo-file_sturcted-go • https://github.com/golang/go/tree/master/src/cmd/compile • https://zenn.dev/sasakiki/articles/f2ebdf66a524e4 • https://qiita.com/propella/items/9d7b1dece283cf462cb9 • https://cipepser.hatenablog.com/entry/go-tool-compile • https://www.altoros.com/blog/golang-internals-part-3-the-linker-object-files-and-relocations/ • https://zenn.dev/ngicks/articles/go-virtual-mesh-fs-for-os-copyfs • https://zenn.dev/hsaki/articles/godoc-asm-ja • https://github.com/golang/go/commit/4dfb5d91a86dfcc046ced03cee6e844df0751e41#diff-93e5af6a86a5745929c27e317b1 58e986257f9cf4b6904f6e01e13f9bbebaabeL11 • https://qiita.com/junjis0203/items/616c00086eb336153f4f#cmdcompileinternalgc • https://zenn.dev/rescuenow/articles/aeb7f2e8c110d0 • https://qiita.com/sonatard/items/7b9b376f3420879a00d6 • https://zenn.dev/hiroyukim/books/a21fbd6eb9fdda/viewer/981184 • https://future-architect.github.io/articles/20210208/ • https://zenn.dev/dqneo/articles/ce9459676a3303#embedタグがある場合のファイルの埋め込み 4. Appendix - 参考文献