go/packagesについて
概要
Go 1.11のリリースから、パッケージ解析等の目的でGoのパッケージをロードするためのライブラリx/tools/go/packages
がGo Toolsに追加された(リリースノート)。このライブラリはGo 1.11から導入されたmodulesシステムをサポートしている。まだ標準ライブラリには入っていないが、これはmodules非対応のgo/build
を代替するものである。
ところで、Go Toolsにはパッケージ読み込みのためのライブラリ(x/tools/go/loader
)が既に存在する。公式のアナウンス等は無くこのパッケージの立ち位置は不明瞭だが、x/tools/go/loader
に関するissueコメントや、loader.Program
を使用するssautil.CreateProgram
がdeprecatedになったことから、今後はx/tools/go/loader
ではなくx/tools/go/packages
を使うべきだろう。
使い方
パッケージのロードには、まずpackages.Config
構造体に諸設定を記述する。もっとも重要なのはMode
メンバで、ローダーによる解析のレベルをLoadMode
型の値を代入して指定する。以下に挙げるが、下に行くほどレベルが深く、ローダーによって返却されるpackages.Package
構造体に含まれる情報も増える。
LoadFiles
: パッケージに所属する*.goファイルの一覧を得るLoadImports
: インポートの依存解析を行うLoadTypes
: 宣言レベルでの型解析を行うLoadSyntax
: 指定したパッケージについてASTの構築・型解析を行うLoadAllSyntax
: 指定したパッケージ及びそれらの依存パッケージにおいてASTの構築・型解析を行う
設定をpackages.Load
に与えて呼び出す。第2引数以降で読み込むパッケージを指定するが、これにはGoのビルドシステムの引数で用いられる[packages]
パターンを基本的に用いる。[packages]
パターンの詳細はgo help packages
に記述がある。実際、ライブラリ内部ではgo list
コマンドを実行している。
[packages]
パターン以外にも、"patternName=string"
の書式で詳細なクエリを指定することができる。現在公式に実装されているクエリはfile
とpattern
で、前者は"file=path/to/file.go"
の形式でpath/to/file.go
を含むパッケージを指定する。後者は"pattern=string"
の形式で、string
が[packages]
パターンとしてgo list
に渡される。これはエスケープの用途で用いる。第3のクエリとして"name=identifier"
クエリがあり、パッケージ名での指定ができる(例:"name=rand"
でmath/rand
とcrypto/rand
を読み込む)。これを書いた時点では、name
クエリは実装途中であるとドキュメントに記載されていたが、既に動くものが実装されている。
packages.Load
は読み込んだパッケージのリストを*packages.Package
のスライスとして返却する。パッケージの順番及び数はpackages.Load
の第2引数以降で指定したパターンと必ずしも一致しないので注意する。
サンプル
以下のプログラムはtestdata/t.go
をx/tools/go/packages
によってruntime
パッケージとともに読み込み、SSA形式に変換してからx/tools/go/interp
のAPIによってインタプリタ実行する。
main.go
package main
import (
"go/types"
"log"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/interp"
"golang.org/x/tools/go/ssa/ssautil"
)
func main() {
target := "testdata/t.go"
// interp.Interpretで実行する対象は、依存パッケージを含めすべて
// SSA形式に変換されている必要があるため、LoadAllSyntaxモードを指定する
cfg := &packages.Config{
Mode: packages.LoadAllSyntax,
Tests: false,
}
// runtimeモジュールはinterp.Interpretによる実行の際に必要。
// 大抵依存パッケージの推移的閉包に含まれているため、明示的に指定しなくても動くことがある
pkgs, err := packages.Load(cfg, "name=runtime", target)
if err != nil {
log.Fatalf("failed to load package: %v", err)
return
}
// SSA形式に変換
ssaProg, ssaPkgs := ssautil.AllPackages(pkgs, 0)
ssaProg.Build()
// mainパッケージを探す
var mainPkg *ssa.Package
for _, ssaPkg := range ssaPkgs {
if ssaPkg.Pkg.Name() == "main" {
mainPkg = ssaPkg
}
}
// 実行
interp.Interpret(mainPkg, 0, &types.StdSizes{WordSize: 8, MaxAlign: 8}, "hoge", []string{})
}
testdata/t.go
package main
import "fmt"
func main() {
fmt.Println("hello golang")
}
実行結果
$ go run main.go
hello golang
参考
gopackages
コマンドのソース https://github.com/golang/tools/blob/master/go/packages/gopackages/main.go