20210305のGoに関する記事は3件です。

Goで自作のパッケージをインポートする

自分用にメモ

「スターティングGo言語」を進めていましたが、Goモジュールのサポートを使って自作パッケージをインポートしてみたかったのでやってみました。
Goのバージョンは1.13以上にしてください

go env

terminal
$ go env GO111MODULE
on

ディレクトリ構成

zoo
├── animals
│   ├── elephant.go
│   ├── monkey.go
│   └── rabbit.go
└── main.go

animalsパッケージ

呼び出すと食べ物を返す

elephant.go
package animals

func ElephantFeed() string {
    return "Grass"
}
monkey.go
package animals

func MonkeyFeed() string {
    return "Banana"
}
rabbit.go
package animals

func RabbitFeed() string {
    return "Carrot"
}

mainパッケージ

main.go
package main

import (
    "fmt"
    "go_mod/animals"
)

func main()  {
    fmt.Println(animals.ElephantFeed())
    fmt.Println(animals.MonkeyFeed())
    fmt.Println(animals.RabbitFeed())
}

go.modの作成

terminal
zooディレクトリで
$ go mod init go_mod
go: creating new go.mod: module go_mod

確認
$ cat go.mod
module go_mod

go 1.15

go buildの実行

terminal
$ go build /hoge/zoo/main.go

go run main.go

terminal
$ go run main.go
Grass
Banana
Carrot
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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)も用意されているので、必要であれば使いましょう。(サンプルリポジトリでは両方使っています)

リンク集

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.go
package main

import (
    "github.com/masamichhhi/my-project/db"
)

func main() {
    db.PutPerson()
}
go.mod
module 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.go
package 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.mod
module 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.go
package model

type Person struct {
    Name string  `dynamo:"name"`
}
model/go.mod
module "github.com/masamichhhi/my-project/model"

go 1.14

これでパッケージを分割できたと思います!

参考

Go moduleのローカルパッケージに多層の依存をもたせられない
↑(僕の質問に答えてくれた方、ありがとうございます?‍♂️)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む