2013/06/29

Recent entries from same category

  1. Go のポインタの躓きやすい点
  2. TensorFlow Lite の Go binding を書いた。
  3. Lint ツールを Language Server に対応させるコマンド efm-langserver 作った。
  4. gocode やめます(そして Language Server へ)
  5. Golang だけでやる機械学習と画像分類

先日、Go言語開発チームはリポジトリ内にあった殆どのMakefileを削除した。私(訳者)は混乱したし不安にもなった。しかしそれは私がこれまでの習慣と異なる場面に遭遇した事による物だと気付いた。その事を色濃く書かれたいる記事があったので紹介したい。Go言語を知らない人でも面白く読めると思います。
Are You Fuckign Coding Me!? - The go tool
http://areyoufuckingcoding.me/2012/02/14/the-go-tool/
本訳を許諾してくれたnu7hatchに感謝したい。(Thanks to nu7hatch)

THE GO TOOL

毎週リリースされる最新版に新しいgoコマンドが導入され話題になっていたので、ちょっとこれについて書くことにした。 私は初め、このgo toolを統一しようというアイデアを聞いた時、少し懐疑的になり不安でいっぱいになった事を認めざるを得ない。 それが他の言語固有のパッケージマネージャの様にめちゃくちゃになるのではないかと心配した。 それらパッケージマネージャのほとんどは私の知る限り車輪の再発明であり、そのオペレーティングシステム上のパッケージマネージャと衝突していてシステム管理者を苦しませているのだ。 さらに言うと、私はmakefileが好きだった。本当に好きだった。単純明快だったしうまく動いた。 幸運にも新しいのgo toolが私の恐怖の全てを拭い去った!

繰り返すな...

新しいgo toolに関する情報の多くはgo nutsメーリングリストに流れている。 公式のgoのドキュメントにも現在、go toolを使ってのコードの書き方が短めの記事となって幾らか含まれている。

現時点ではドキュメントとのギャップがかなりあると思うので、新しいgo toolの採用と幾らかの便利なトリックを逸早く書くことが合理的だと思う。

設定よりも取り決め

私の最大の恐怖の源、その理由はRuby on Railsの経験によるものだ。 Railsをよく理解しる開発者は、ちょっとしたハックや、言ってしまえばルールに沿わないちょっとしたトリッキーな何かをしようとする場合、その全ての時間を費やす事に同意しなければならない。

IMPOSSIBRU!

しかし良い練習について説明して行こう。まず第一にgo toolの各々は一つの物事だけを行う。そしてそれは正しい。あるべき物はそこにある:

  • go build - パッケージのビルド
  • go get - 依存の解決とインストール
  • go test - テストスーツの実行とベンチマーク
  • go install - パッケージのインストール
  • go doc - ドキュメントの生成
  • go fmt - コードの整形
  • go run - アプリケーションのビルドと実行
  • go tool - その他ツールの呼び出し
  • その他...

Goのパッケージはビルドの設定といった物を全く持っていない。 makefileもない。その他、依存性の記述も無い。 じゃぁどの様になるのか。全てはソースコードから検索される。 マジックを起こすには、最初に行わなければならない事が一つある。 goのスタッフがどこにあるのかを明示する必要がある。 GOPATH環境変数でgoのツリーへのパスを定義する。 例えば、~/.bashrcの中での以下の様に:

GOPATH="/home/nu7/gocode"

...はgoのツリーが指定した場所に存在する事をgo toolに教えているのだ。 goのツリーって実際なに?と聞くかもしれない。簡単に言うと 君のソース、パッケージ、コマンドの全てが格納される場所だと答えておこう。 まぁ見なさい:

ls /home/nu7/gocode/
bin   pkg   src

全てのソースはsrcフォルダーの中に位置するであろう。これはアプリケーション、パッケージ、そして依存している物、全てのソースという意味だ。 pkgフォルダーはコンパイルされてインストールされたパッケージが含まれ、cmdにはコマンドがインストールされる。

GOPATH変数はPATHに非常に似ており、必要なだけ多数のgoパスを設定出来る。 君はそれらの内、最初の一つをメインである覚えておかなけいけない。なぜならgo installでインストールされる全てはそこに入るからだ。

依存の解決

依存を明示する設定ファイルは無い... じゃぁどうやってgo toolはそれをどこからダウンロードして、どこにインストールしているのかを知り得るんだよ! リポジトリだろって思った?ノー、そんな物は無い。 Goはimportpathと呼ばれる物を導入している。 まぁ見なさい:

import "github.com/nu7hatch/gouuid"

import pathはツーインワンだ。 リポジトリURLでもあり、パッケージがローカルにインストールされる場所へのパスでもある。 go getツールはどこにフェッチすべき依存物があるかをimport pathを見て知る。 またgo buildはローカルにあるそれらをインポートする場所を知る。

システムに依存している物をインストールするには次のようにgo getツールを使う必要がある:

$ go get package-name

待った待った、ちょっと待った... このパッケージ名って何? これはインストールしたい依存物のパッケージ名だ。 例えばgoのソースとしてfooという名前のパッケージがあった場合、go get fooを呼び出す事で全ての依存物がインストールされる。 パッケージから直接このツールを使う事も出来る:

$ cd ~/gocode/src/foo
$ go get .

その他の全てのgo toolは同じ様に動作し、パッケージ、あるいはそのimport pathの指定から直接呼び出せる。 また ... ワイルドカード(3つのドット)を使ってネストされたパッケージのグループにおいても使う事ができ、fooパッケージがいくつかの入れ子のパッケージを含んでいる場合、それら依存する全てはこれをやるだけで同時にインストールすることが出来る:

$ go get ./...

goのツリーに指定した依存物が既にインストールされている場合、明示的に要求しない限り更新されない。 依存しているパッケージを更新するにはgo get-uフラグを付ける:

$ go get -u package-name

簡単でしょ?

依存地獄!

go toolを好きであると同時に恐れている、もう一つの取り決めがある。 Go toolはリポジトリのHEADバージョンをチェックして依存を解決する。 これはパッケージメンテナに後方互換性を維持する事を強要する。

GREEN MASTER MOTHERFUCKER! DO YOU HAVE IT?

グリーンマスターポリシーは私が仕事で常に主張していた事だった。 デフォルトブランチは人々がまず最初にチェックする物であり、したがってそれはグリーン(テストが全てパスした状態)であるべきでそれを動作させる為には少なくとも最新でなければならない! 一度公けに公開した、もしくは十分開発した段階ならば、後方互換性を持つべきだ。 我々は何かを廃止したり、パッチやマイナーバージョンである中でAPIの変更をする事は出来ない。

でも我々は練習の中でそれがどの様になるのかの全てを得て知る。 多くの人々は後方互換性なんて糞の様な物は提供しないし、彼らは遊び場としてデフォルトブランチを使う。 彼らの為に、そして新しいgo toolで平穏に暮らしたい開発者の為に、この取り決めのセットを

君がプログラマ人生を送る中で守るべきバカバカしいルール:

  • マスターブランチを常にグリーンにしとけよ!バカ
  • 新機能は別ブランチでやれ!ボケ
  • 一度コード公開しといて誰かが使ってるのにAPI変えるなクソがぁ!
  • API変えたいとか、どうして変える必要があるならメジャーバージョンで変えろよ。んでもってオリジナルブランチから派生ブランチに分けて作業しろよ!カス
  • もしどうしても、どうしても特別なタグやブランチ、もしくは依存としてコミットする必要があるなら、デフォルトに指定したリポジトリを自分でforkしてコミットに使えよ!このウンコが!
  • おいおい...簡単にしとけよ マザコン!

エルサレムの本を思い出しちゃうから何度も言わせないでくれ...

ビルドとインストール

Ok。goコマンドに話を戻そう。go buildコマンドはパッケージをコンパイルするのに使われる。 パッケージをビルドするだけでインストールはしない。 重要なのはパッケージはローカルソースツリーの中でチェックされなければならないという事。 代わりにリモートパッケージのインストールにはgo getが使われる:

$ go get github.com/nu7hatch/gouuid

ローカルのパッケージをインストールするためにはgo installツールが使われる。 これは最初にパッケージをビルドし(必要な場合)、$GOPATH/pkg、あるいは$GOPATH/cmd配下の物をインストールする。

go toolはフラグや特別な設定をしない限り特定のファイルをビルドから除外する事も出来る。 無視する為にやるべき事は、名前の先頭にアンダースコアを付けるだけ:

$ ls
_bar.go foo.go
$ go build .

上の例の_bar.goのはビルドからは無視される。

ふむ。この通り。これに関して言う事は何もない。先に進もう。

CGOによるC言語拡張

cgoコマンドによるC言語拡張の作成はかなり素晴らしい。 実際にC言語アプリケーションの殆どをビルドするのにcgoについてもっと知っておく必要は無く、go buildツールで十分なのだ。

素直に言うとcgoに関してはあまり言うことは無く、それらの殆どはだいたいドキュメント、そしてgo users wikiにある記事の中に記述されている。

まず言っておくべき事として私がそれが好きではない。オフィシャルの殆どの例で示される様にコメントの中にC言語のソースコードをそのまま配置している。 マジで本当に好きになれない。 主としてコードの量を最小化し、かつ単一のファイルで各例を示すために、サンプルがこの方法で提供されている事を知っておくべきだ。 現実のアプリケーションではC言語のコードはコメントブロックに配置すべきじゃないgo buildツールはパッケージ内の.h.cといったファイルをちゃんとスマートに扱ってくれるんだ。

例がいる? stdio.hにある関数printfを使って引数を画面に表示する単純なechoコマンドを書こう。 wikiページで言及されている様に、goはC言語の可変長引数の関数を呼び出す事は許していない。 よってprintf関数の小さなラッパを書かなければならない。 コードはgithubにある物の様になるだろう:

echo.h:

#ifndef _ECHO_H_
#define _ECHO_H_

#include <stdio.h>

void echo(char*);

#endif /* _ECHO_H_ */

echo.c:

#include "echo.h"

void echo(char* s)
{
    printf("%s\n", s);
}

echo.go:

package main

/*
#include <stdlib.h>
#include "echo.h"
*/
import "C"

import (
    "flag"
    "unsafe"
    "strings"
)

func main() {
    flag.Parse()
    cs := C.CString(strings.Join(flag.Args(), " "))
    C.echo(cs)
    C.free(unsafe.Pointer(cs))
}

これで君もgo buildツールでシームレスにビルド出来る様になる。 パッケージにある全てのC言語ファイルを認識し、コンパイルする。 つまりはこれだけでいいんだ!

NOT BAD

プラットフォーム固有のビルド

もう一つ素晴らしく興味深い事にgo buildはプラットフォーム固有のファイルについてのコンパイルをハンドリング出来る事だ。ファイルを名前から解析している。(こんな感じ: file_GOOS_GOARCH.go もしくは file_GOARCH.go):

foo_darwin_amd64.go
foo_386.go
foo.go

この機能はC言語のファイルでもちゃんと動く:

foo_amd64.c
foo_386.c
foo.h
foo.go

ドキュメントの中で言われている通り、これらの機能は必要ない。しかし go toolが単純なわりに如何に柔軟なのかを色濃く表している事を言及しておきたい。

Ok。でも君はきっとこう聞くだろう。もしコンパイラのフラグや幾つかの設定など、何かトリッキーな事が必要な場合は?と

救うべきMakefile!

そう、makefileを使う事を恐れてはいけない! これは拡張設定による取り決めや、先行条件等に対処する為の、最も簡単で全く便利な方法だ。 Makefileは、C言語の拡張にだけ役立つ物ではなく、色んなパッケージにおいても同様に適用出来る(例えばwebrocketのトップレベルでは作業を楽にする為にmakefileを使っている)。

より明示的な例... それをベースとしているコアパッケージやコマンドラインツー ルを含んでいるアプリケーションを想像してみて欲しい。 このechoの例だとモジュールが多くてもやり遂げられるだろう:

echo/
pkg/
echo/
echo.c
echo.h
echo.go
cmd/
echo/
echo.go

pkg/echoパッケージはC言語のprintf関数を再利用出 来る様にラップし、そのソースは前述の例と殆ど同じであるとしよう。 cmd/echoコマンドはコアパッケージを使って画面に何かを出力する実行モジュールとしよう。 cmd/echoコマンドはこんな感じになるだろう:

package main

import (
    "github.com/nu7hatch/cgoecho2/pkg/echo"
    "flag"
)

func main() {
    flag.Parse()
    echo.Echo(flag.Args()...)
}

Note: slice... の意味を知らない人々のために言うと、引数の数に相当する変数へsliceをマップする物だ。Rubyで言うと*argsの様な物だ。

話を戻そう。Makefileが必要なパッケージの為に私達が用意しなければならない簡単な物、それは以下の様な物だ:

all: echo-pkg echo-cmd

echo-pkg:
    go build ./pkg/echo

echo-cmd:
    go build ./cmd/echo

これによりmakeを呼び出すだけで両方を瞬時にコンパイルでき、もっと重要な事を言うとリモートからそれらをインストールするのにもgoコマンドを使う事が出来るんだ。:

$ go get github.com/nu7hatch/cgoecho2/cmd/echo

もちろんこれは非常に単純な例だ。ソースの全てを俊敏にビルドするにはワイルドカード使う事も出来る。慌てずに:

$ go build ./...

しかし、多くのパッケージおよび(または)コマンドを含む巨大なアプリケーションを配布するのはトリッキーになりがちだ。 その際makefileやシェルスクリプト、君の好きな他のビルドツールを使うのは妥当な事なのだ。

まとめ

ハッキリと言おう。私は新しいgo toolをマジで愛してやまない。! 初め、それを触って遊んでいる間に山の様に問題に遭遇した。 しかしほとんどの問題は、他のパッケージマネージャ/ツールを使用する間に、私が得てきた幾つかの悪い習慣によって引き起こされた物だった。 はぁ...最近、go-nutsのIRCチャネルで愚かな質問を沢山してしまったよ。 そして私が得る答えは馬鹿らしく明白で単純だった...

以前使っていたeasy_installrubygemsbuilderといったこれまでのツールを担う物と考えていている。 私の心の中に一枚だけ写真がある... あぁ、でも嫌いになられそうなので公開しない方がよさそうだ。 :) 代わりといってはなんだが、新しいgo toolを私がどの様に感じているかをお見せしよう...

...

私はgoが正しい向に向かっている事を見る事が出来てとてもうれしい。今日はこの辺にしておく。ごきげんよう。Gophers!

Posted at by | Edit


blog comments powered by Disqus