- 投稿日:2021-03-05T12:14:55+09:00
Goで自作のパッケージをインポートする
自分用にメモ
「スターティングGo言語」を進めていましたが、Goモジュールのサポートを使って自作パッケージをインポートしてみたかったのでやってみました。
Goのバージョンは1.13以上にしてくださいgo env
terminal$ go env GO111MODULE onディレクトリ構成
zoo ├── animals │ ├── elephant.go │ ├── monkey.go │ └── rabbit.go └── main.goanimalsパッケージ
呼び出すと食べ物を返す
elephant.gopackage animals func ElephantFeed() string { return "Grass" }monkey.gopackage animals func MonkeyFeed() string { return "Banana" }rabbit.gopackage animals func RabbitFeed() string { return "Carrot" }mainパッケージ
main.gopackage main import ( "fmt" "go_mod/animals" ) func main() { fmt.Println(animals.ElephantFeed()) fmt.Println(animals.MonkeyFeed()) fmt.Println(animals.RabbitFeed()) }go.modの作成
terminalzooディレクトリで $ go mod init go_mod go: creating new go.mod: module go_mod 確認 $ cat go.mod module go_mod go 1.15go buildの実行
terminal$ go build /hoge/zoo/main.go
go run main.go
terminal$ go run main.go Grass Banana Carrot
- 投稿日:2021-03-05T11:16:55+09:00
[Go] ソースコードを静的解析してコードを自動生成する
はじめに
コード以外の情報を元にして自動生成したことはあったのですが、コードを静的解析した内容をもとに自動生成するのは初めてだったので、調べた内容をまとめる記事を書きました。
今回は、特定のパッケージ配下にある構造体全てにメソッドを生やすコードを生成しようと思います。
※typeで型定義した構造体のみ生成対象としますまた、今回説明する内容を実装したサンプルリポジトリも作成しましたので、必要であればそちらも参照してください。
コードを解析
go/typesを使用して型情報を取得
goは標準でコードを静的解析するためのライブラリが整えられていますが、その中でも
go/types
は、型情報を取得するのに使えます。以下は、パッケージ配下に存在する、typeで型定義した構造体の名前の一覧を取得する処理です
structNames := []string{} // 生成方法は後述 var pkg *types.Package // パッケージ配下に存在する各要素の名前を取得 for _, name := range pkg.Scope().Names() { // typeで定義しているかどうかチェック obj, ok := pkg.Scope().Lookup(name).(*types.TypeName) if !ok { continue } // structかどうかチェック if _, ok := obj.Type().Underlying().(*types.Struct); !ok { continue } structNames = append(structNames, obj.Name()) }*types.Packageの生成
*types.Package
を使用して型情報を取得できるとわかったところで、肝心の*types.Package
の生成方法ですが、パッケージ解析を実施するためのライブラリであるx/tools/go/packagesから取得することができるので、こちら経由で取得します。なお、同様の機能を持つライブラリとしてgolang.org/x/tools/go/loaderがありますが、こちらはモジュールをサポートしていないため非推奨であり、代わりに
x/tools/go/packages
を使用するよう促されています。例) bytesパッケージの型情報を取得する
cfg := &packages.Config{ // 型情報を取得するモードを指定する。必要であれば他のモードも追加して情報を取得可能 Mode: packages.NeedTypes | packages.NeedTypesInfo, } // パッケージ情報をロード pPkgs, _ := packages.Load(cfg, "bytes") for _, pPkg := range pPkgs { var pkg *types.Package = pPkg.Types // 以降型情報の解析 }これで
*types.Package
を取得できるので、後は前述したとおり型情報を解析していけばOKです。
今回は例としてbytes
パッケージを直接指定しましたが、他にも様々な指定方法があるようです。コードを生成
コードを解析して必要な情報が得られたら、コード生成です。
今回はテキストテンプレートを使用してコードを生成します。func main() { packageName := "pkg" structNames := []string{"A", "B"} templData := struct { PackageName string StructNames []string }{ PackageName: packageName, StructNames: structNames, } var w bytes.Buffer tmpl := template.Must(template.New("mytemplate").Parse(templStr)) tmpl.Execute(&w, templData) // 以下、ファイル出力など... } const templStr = `// Code generated by gen/genmethods.go; DO NOT EDIT. package {{ .PackageName }} import "fmt" {{ range $structName := .StructNames }} // PrintType 型情報を標準出力する func (s {{ $structName }}) PrintType() { fmt.Printf("%T\n", s) } {{ end }} `実行したら以下のようなコードを生成できます。
// Code generated by gen/genmethods.go; DO NOT EDIT. package pkg import "fmt" // PrintType 型情報を標準出力する func (s A) PrintType() { fmt.Printf("%T\n", s) } // PrintType 型情報を標準出力する func (s B) PrintType() { fmt.Printf("%T\n", s) }ソースコードのフォーマット(format.Source)やパッケージのimportを実施する処理(imports.Process)も用意されているので、必要であれば使いましょう。(サンプルリポジトリでは両方使っています)
リンク集
- go/types関連
- /tools/go/packages関連
- コード生成関連
- 投稿日:2021-03-05T01:23:59+09:00
Go moduleを使ってプロジェクトを複数パッケージに分割する
初めに
golangやlambdaに入門したくて、GoとSAMで学ぶAWS Lambdaを読み進めてみました。
しかし、本書ではパッケージの依存管理にdepを使っていて、最近主流になってきたGo moduleを使っていなかったので、これを使ってlambdaのプロジェクトを複数パッケージに分割してみようと思います。ディレクトリ構成
aws samで出来るlambdaを考えます。(samについての解説は省略します)
dynamoDBにName
をフィールドにもつPerson
を保存してみます。以下のように
handler
(lambdaのエントリーポイントになるところ)、db
(DBに接続するところ)、model
(DBから取り出したデータをパースする構造体の置き場)の3つに分けてみました。
ディレクトリ構成はテキトーなのでご了承ください(笑). ├── template.yaml ├── db │ ├── db.go │ └── go.mod ├── go.mod ├── handler │ └── main.go └── model ├── go.mod └── model.go実装
まずは
handler/main.go
とルート直下のgo.mod
です
ここはdbのパッケージに依存しています。handler/main.gopackage main import ( "github.com/masamichhhi/my-project/db" ) func main() { db.PutPerson() }go.modmodule github.com/masamichhhi/my-project go 1.14 require github.com/masamichhhi/my-project/db v0.0.0-00010101000000-000000000000 replace ( github.com/masamichhhi/my-project/db => ./db github.com/masamichhhi/my-project/model => ./model )この
replace
を書くことで、相対パスで指定したディレクトリを好きな名前でインポートできます。次に、
db
です
ここでdynamoDBに接続する処理を書きます。
公式のSDKよりも簡単にdynamoDBを使えるguregu/dynamo
を使います。db/db.gopackage db import ( "os" "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/guregu/dynamo" "github.com/masamichhhi/my-project/model" ) var ( Region = os.Getenv("REGION") ) func PutPerson() { creds := credentials.NewStaticCredentials(os.Getenv("AWS_ACCEESS_KEY"), os.Getenv("AWS_SECRET_ACCEESS_KEY"), "") sess, _ := session.NewSession(&aws.Config{ Credentials: creds, Region: aws.String(Region)}, ) newPerson := model.Person{Name: "Gopher"} db := dynamo.New(sess) table := db.Table("person-table") err := table.Put(newPerson).Run() if err != nil { fmt.Println("エラー発生") }else{ fmt.Println("成功!") } }go.modmodule github.com/masamichhhi/my-project go 1.14 require ( github.com/github.com/masamichhhi/my-project/model v0.0.0-00010101000000-000000000000 ) replace github.com/masamichhhi/my-project/model => ../model最後に
model
です。model/model.gopackage model type Person struct { Name string `dynamo:"name"` }model/go.modmodule "github.com/masamichhhi/my-project/model" go 1.14これでパッケージを分割できたと思います!
参考
Go moduleのローカルパッケージに多層の依存をもたせられない
↑(僕の質問に答えてくれた方、ありがとうございます?♂️)