CUBE SUGAR CONTAINER

技術系のこと書きます。

Mac OS X で Golang に入門してみる

以前から Golang (Go 言語) にはポスト C 言語として興味があった。 マシン語を出力できる低レイヤー性にもかかわらず、スクリプト言語に近い記述しやすさも併せ持つというのはなかなかに魅力的だ。 今回はその Golang に関する概念を調べつつ開発環境を整えていくことにする。

環境は表題の通り Mac OS X を使っている。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.10.5
BuildVersion:   14F27

Golang をインストールする

まずは Golang は Homebrew を使ってインストールする。 Homebrew が入っていない場合には、あらかじめ入れておこう。

$ brew install go

これで Golang に関する様々な操作を行うことのできる go コマンドが使えるようになった。

$ go version
go version go1.5.1 darwin/amd64

Hello, World!

やっぱり最初はあれだよね、ということで以下のソースコードを用意する。 実行可能ファイルにビルドするソースコードの場合、必ず main という名前で package を宣言する。 その上で main() 関数を用意すると、そこがエントリポイントになる。

$ cat << EOF > helloworld.go
package main

import (
  "fmt"
)

func main() {
  fmt.Println("Hello, World!")
}
EOF

go run サブコマンドを使うことでソースコードをビルドすると同時に実行できる。

$ go run helloworld.go
Hello, World!

ばっちり。

作業スペース ($GOPATH) を用意する

先ほどのような単独のソースコードなら何も考える必要はないけど、ちょっと複雑なことをやろうとする場合には環境変数 $GOPATH を定義する必要がある。 これは Golang を使ったプロジェクトの作業スペースのように考えれば良いようだ。

以下のように適当なディレクトリを $GOPATH として定義した上で、その bin ディレクトリにパスを通しておく。 $GOPATH に指定するディレクトリは単なる作業スペースなので、本当に適当な場所を選んで構わない。

$ export GOPATH=$HOME/go
$ export PATH=$PATH:$GOPATH/bin

必要に応じてシェルの起動時に読み込むよう設定ファイルに残しておく。

$ echo 'export GOPATH=$HOME/go' >> ~/.zshrc
$ echo 'export PATH=$PATH:$GOPATH/bin' >> ~/.zshrc

次に、指定した $GOPATH のディレクトリを作成する。

$ mkdir -p ${GOPATH}

試しに先ほど作成したソースコードを $GOPATH を使ったバージョンで書いてみよう。 まず、ソースコードは全て src ディレクトリ以下に用意することになる。 src ディレクトリ以下はディレクトリを使って名前空間を分割する。 ソースコードを GitHub で管理する場合には、以下のように github.com の後にユーザ名、その後にプロジェクト名を入れる。

$ cd ${GOPATH}
$ mkdir -p src/github.com/example/helloworld

作成したディレクトリ (パッケージ) 以下にソースコードを用意する。

$ cat << EOF > src/github.com/example/helloworld/greet.go
package main

import (
  "fmt"
)

func main() {
  fmt.Println("Hello, World!")
}
EOF

go install サブコマンドを使ってソースコードをビルドすると共にインストールする。

$ go install github.com/example/helloworld

これで bin ディレクトリができると共にビルド済みのバイナリができる。

$ ls
bin src
$ ls bin
helloworld
$ file bin/helloworld
bin/helloworld: Mach-O 64-bit executable x86_64

実行してみよう。

$ helloworld
Hello, World!

いいかんじ。

ライブラリを作ってみる

先ほどのパッケージは実行可能ファイルにビルドするものだったので、今度は別のパッケージから利用されるライブラリを作ってみる。

新しく calc というパッケージ用のディレクトリを作成しよう。

$ mkdir -p src/github.com/example/calc

ふたつの変数を加算する Add という関数を定義したソースコードを用意する。

$ cat << EOF > src/github.com/example/calc/add.go
package calc

func Add(x int64, y int64) int64 {
  return x + y
}
EOF

go build コマンドで calc パッケージをビルドする。 特にエラーメッセージが出なければ上手くいっている。

$ go build github.com/example/calc

今度は helloworld パッケージの方に calc パッケージを利用したソースコードを用意する。 calc パッケージを $GOPATH/src 以下のディレクトリ構造を表した形で import している点に注目。

$ cat << EOF > src/github.com/example/helloworld/greet.go
package main

import (
  "fmt"
  "github.com/example/calc"
)

func main() {
  fmt.Printf("Add(%v, %v) = %v\n", 1, 2, calc.Add(1, 2))
}
EOF

できたら helloworld パッケージをインストールする。

$ go install github.com/example/helloworld

実行してみよう。

$ helloworld
Add(1, 2) = 3

ばっちり。

ライブラリを毎回コンパイルしなくて済むようにする

先ほどの状態ではビルド時にライブラリを毎回コンパイルすることになるはず。 これをやめさせるにも実行可能ファイルを作ったのと同様に go install サブコマンドを使う。

$ go install github.com/example/calc

これで pkg ディレクトリ以下にコンパイル内容がキャッシュされる。

$ ls
bin pkg src
$ tree pkg
pkg
└── darwin_amd64
    └── github.com
        └── example
            └── calc.a

3 directories, 1 file

蛇足だけど、上記の tree コマンドが入っていない場合は Homebrew でインストールしよう。

$ brew install tree

ユニットテストを書いてみる

最近のプログラミングにはテストがないとお話にならない。 Golang にはユニットテスト用の機能も同梱されている。

テスト用のソースコードは、テスト対象のソースコードの名前に _test をつけた形で用意する。 テストの関数には (t *testing.T) というシグネチャを持たせよう。 そして、t.Errorf() 関数が呼び出されるとテストが失敗したことを意味している。

$ cat << EOF > src/github.com/example/calc/add_test.go
package calc

import "testing"

func TestAdd(t *testing.T) {
  const x, y, expected = 1, 2, 3
  result := Add(x, y)
  if result != expected {
    t.Errorf("Add(%v, %v) = %v, want: %v", x, y, result, expected)
  }
}
EOF

go test サブコマンドでパッケージを指定すると、自動的にテストが実行される。

$ go test github.com/example/calc
ok      github.com/example/calc 0.006s

ちなみに、テストコードを失敗するようにいじって実行した場合の結果は次の通り。

$ go test github.com/example/calc
--- FAIL: TestAdd (0.00s)
    add_test.go:9: Add(1, 2) = 3, want: -1
FAIL
FAIL    github.com/example/calc 0.006s

サードパーティ製のライブラリを利用する

標準ライブラリだけでは不足している機能を補う上で、サードパーティ製のライブラリは重要となる。

サードパーティ製のライブラリは go get サブコマンドを使うと簡単にインストールできる。 例えば GitHub で公開されている JSON を扱うためのライブラリ go-simplejson をインストールするには、以下のようにする。

$ go get github.com/bitly/go-simplejson

すると、src 以下に go-simplejson がダウンロードされる。 そう、つまり Golang の場合サードパーティ製ライブラリをインストールするというのは、結局のところソースコードを src 以下に展開するということに過ぎないらしい。

$ ls src/github.com/bitly
go-simplejson

もちろんダウンロードされるだけでなくビルドも行われる。

$ tree pkg
pkg
└── darwin_amd64
    └── github.com
        ├── bitly
        │   └── go-simplejson.a
        └── example
            └── calc.a

4 directories, 2 files

インストールした go-simplejson を使ってみよう。 使い方は自分で先ほど作った calc パッケージと変わらない。 src 以下のディレクトリ構造の表現をそのまま import するだけだ。

$ cat << EOF > src/github.com/example/helloworld/greet.go
package main

import (
  "fmt"
  "github.com/bitly/go-simplejson"
)

func main() {
  json := simplejson.New()
  json.Set("message", "Hello, World!")

  b, _ := json.EncodePretty()

  fmt.Printf("%s\n", b)
}
EOF

ビルドして実行してみよう。

$ go install github.com/example/helloworld
$ helloworld
{
  "message": "Hello, World!"
}

いいかんじ。

自作のプロジェクトを GitHub で管理する

もうお分かりだろうけど、ようするに src 以下のディレクトリを GitHub に突っ込めばいいだけ。

$ cd ${GOPATH}/src/github.com/example/calc
$ git init
$ git add -A
$ git commit -m "Initial commit"
$ git remote add origin git@github.com:example/calc.git
$ git push origin master

gdb でデバッグする

さすがに printf デバッグするのはつらすぎるのでデバッガを導入する。

gdb をインストールする

Homebrew で gdb をインストールしよう。

$ brew tap homebrew/dupes
$ brew install gdb

ただ、ここからの道のりがちょっとばかし長い。 何故なら gdb は Gatekeeper によって実行が制限されている。 そこで、まずは gdb を自己署名証明書でサインする必要がある。

自己署名証明書を作る

まずはキーチェーンアクセスを開く。

$ open "/Applications/Utilities/Keychain Access.app"

f:id:momijiame:20151006224622p:plain

次に、メニューバーから以下を選択して証明書の作成ウィンドウを開く。

キーチェーンアクセス > 証明書アシスタント > 証明書を作成

適当な名前をつけてコード署名の証明書を作ろう。 今回は以下のようにした。

  • 名前: selfsigned.gdb
  • 固有名のタイプ: 自己署名ルート
  • 証明書のタイプ: コード署名

f:id:momijiame:20151006224832p:plain

以下のような警告画面が出るが、そのまま「続ける」を選択する。

f:id:momijiame:20151006224954p:plain

作成した証明書を、「ログイン」から「システム」の項目に移動させる

f:id:momijiame:20151006225140p:plain

移動できたら、証明書をダブルクリックする。 開いたウィンドウの「信頼」を展開して、「コード署名」の項目を「常に信頼」にする。

f:id:momijiame:20151006225418p:plain

何か聞かれるので「許可」する。

f:id:momijiame:20151006225523p:plain

これでやっとターミナルに戻ってこられる。 長かった…。

証明書で gdb にサインをする

一連の作業が終わったら、念のため taskgated のプロセスを再起動するために kill する。

$ sudo kill $(pgrep taskgated)

作成した証明書で gdb にサインする。

$ sudo codesign -s selfsigned.gdb $(which gdb)

gdb で Golang のコードをデバッグする

まずはデバッグしたいパッケージをデバッグ用の情報を残す形でビルドし直す。

$ go install -gcflags "-N -l" github.com/example/helloworld

あとは普通に gdb を使うだけ。 ポイントはブレークポイントの打ち先を main() 関数にする場合は、パッケージ名も含めて main.main にする必要がある点かな。

$ gdb -q $(which helloworld)
Reading symbols from /Users/amedama/go/bin/helloworld...done.
Loading Go Runtime support.
(gdb) b main.main
Breakpoint 1 at 0x2040: file /Users/amedama/go/src/github.com/example/helloworld/greet.go, line 8.
(gdb) run
Starting program: /Users/amedama/go/bin/helloworld 
[New Thread 0x114b of process 2029]
[New Thread 0x1203 of process 2029]
[New Thread 0x1303 of process 2029]
[New Thread 0x1403 of process 2029]

Breakpoint 1, main.main ()
    at /Users/amedama/go/src/github.com/example/helloworld/greet.go:8
8  func main() {
(gdb) n
9    json := simplejson.New()
(gdb) n
10   json.Set("message", "Hello, World!")
(gdb) c
Continuing.
{
  "message": "Hello, World!"
}
[Inferior 1 (process 2029) exited normally]
(gdb) quit

複数の作業スペース ($GOPATH) を持ちたい場合

作業スペース ($GOPATH) を何らかのポリシーで分けて用意した上で、それを切り替えて使いたい場合もありそう。 そんなときは direnv を使うのが楽っぽいかな。

direnv の使い方については、以下の記事で解説している。

blog.amedama.jp

まずは direnv をインストールする。

$ brew install direnv

次に、$GOPATH にしたいディレクトリを作成する。

$ mkdir -p /tmp/example

そこに、direnv の設定ファイルを作成する。 ${PWD} を $GOPATH に指定しているところがポイント。

$ cat << EOF > /tmp/example/.envrc
export GOPATH=\${PWD}
export PATH=\$GOPATH/bin:\$PATH
EOF

direnv の管理対象に作成したディレクトリを追加する。

$ direnv allow /tmp/example

そのディレクトリに移動すると設定が読み込まれて $GOPATH が書き換わった上でパスが通る。

$ cd /tmp/example
direnv: loading .envrc
direnv: export ~GOPATH ~PATH

go get サブコマンドを使うと、先ほどまで使っていた $HOME/go の代わりに現在のディレクトリの src 以下にパッケージがインストールされることがわかる。

$ go get github.com/bitly/go-simplejson
$ tree src
src
└── github.com
    └── bitly
        └── go-simplejson
            ├── LICENSE
            ├── README.md
            ├── simplejson.go
            ├── simplejson_go10.go
            ├── simplejson_go10_test.go
            ├── simplejson_go11.go
            ├── simplejson_go11_test.go
            └── simplejson_test.go

3 directories, 8 files

完璧。

まとめ

今回は Mac OS X で Golang を使った開発環境の下地を整えてみた。 パッケージやライブラリの作成からユニットテストの書き方、サードパーティ製ライブラリの利用、ソースコードのデバッグなどなど、ひとまず最初に気になるようなところはおさえることができたかな?