godocが無いと言われたあと、godocインストールに権限が無いと言われた話

前提

$ go version
go version go1.6.2 darwin/amd64

はじめに

そもそも go docgodoc が別ものだということに気づいていなかった。2つは別物。 そもそも取得する引数なども異なる。

go doc

$ go doc -h
Usage of [go] doc:
.. snip

Flags:
  -c    symbol matching honors case (paths not affected)
  -cmd
        show symbols with package docs even if package is a command
  -u    show unexported symbols as well as exported
exit status 2

godoc

$ godoc -h
usage: godoc package [name ...]
    godoc -http=:6060
  -analysis string
        comma-separated list of analyses to perform (supported: type, pointer). See http://golang.org/lib/godoc/analysis/help.html
.. snip
  -zip string
        zip file providing the file system to serve; disabled if empty

godoc をインストールした時にエラーが出る

godoc が無いということで go get を使ってインストールしようとしたところ以下のようなエラー。

$  go get golang.org/x/tools/cmd/godoc
go install golang.org/x/tools/cmd/godoc: open /opt/local/lib/go/bin/godoc: permission de

暫定的な解決策

多くのブログや記事では、godocは $GOROOT にインストールされるものだから仕方がないという風潮。

stackoverflow.com

以下のような形でインストールを薦める感じだった(幾つかある回答の中では個人的にはこれが一番無難だと思った)。

$ sudo -E go get golang.org/x/tools/cmd/godoc

もちろん、この場合ではインストールは正常に終了する。 $GOROOT/bin/godoc が存在するようになる。ただ、このようにしてしまうとPATHの管理がだるい(なるべくPATHに追加する場所は少なくしておきたいという気持ち)。

# 存在はする
$ go tool dist env
CC="/usr/bin/clang"
CC_FOR_TARGET="/usr/bin/clang"
GOROOT="/opt/local/lib/go"
GOBIN="/opt/local/lib/go/bin"
GOARCH="amd64"
GOOS="darwin"
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOTOOLDIR="/opt/local/lib/go/pkg/tool/darwin_amd64"
$ GOROOTBIN=`go tool dist env | ruby -ne '$_ =~ /GOROOT="(\S+)"/ && $1.display'`/bin
$ ls $GOROOTBIN
go
godoc
gofmt

$GOROOT 以下にインストールしたくない。

なにやらwikiに以下の様なことが書いてある

Why does go get work for some packages and report permission denied in $GOROOT for some others (with GOPATH set properly)?

If you at any point installed the package in GOROOT (either by having no GOPATH set or by including GOROOT itself in GOPATH) then there might still be a directory in $GOROOT (which is always checked first) that is overriding your GOPATH. To verify, run go list -f {{.Dir}} importpath and if it reports a directory under $GOPATH try deleting that first.

これとは直接関係ないかもだったけれど。とりあえずコードを読んでみることにした。すると幾つかのパッケージに関しては独自に処理が行われているようだった。

$GOROOT/src/cmd/go/pkg.go

// goTools is a map of Go program import path to install target directory.
var goTools = map[string]targetDir{
    "cmd/addr2line":                        toTool,
    "cmd/api":                              toTool,
// snip...
    "golang.org/x/tools/cmd/godoc":         toBin,
    "code.google.com/p/go.tools/cmd/cover": stalePath,
    "code.google.com/p/go.tools/cmd/godoc": stalePath,
    "code.google.com/p/go.tools/cmd/vet":   stalePath,
}


func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package {

// 諸々条件分岐が存在 ...

        if p.build.BinDir != gobin && goTools[p.ImportPath] == toBin {
            // Override BinDir.
            // This is from a subrepo but installs to $GOROOT/bin
            // by default anyway (like godoc).
            p.target = filepath.Join(gorootBin, elem)

わりとびっくりしたけれど、特定のパッケージに対しての特別な処理が色々直書きされている(例えばgodocやgovetに対して古いpackageの場所が指定された時には警告を出すなど)。

とりあえず以下の様にすると $GOPATH/bin 以下にインストールできる。

$ GOBIN=$GOPATH/bin go install golang.org/x/tools/cmd/godoc

そもそも1.7では

上の内容は1.6で試した場合の話。試していないけれど。コードを読んだ限り、そもそも1.7では上のgodocの特別扱いした処理自体消えている模様。

memo

この辺りで色々あった(tweet)https://twitter.com/podhmo/status/771003702102466561

そう言えば、selfishというツール作っていました

これは何?

gistのuploadを手軽にするやつです。goの勉強のための習作でした。

経緯

以前から結構gistを頻繁に利用していて、特に複数ファイルをuploadしたい場合には、web画面からポチポチとファイルを指定していくのではつらすぎる感じがしてました。なので、今まではgistyというツールを使っていたのですが。おそらく利用するユーザー層と自分とは完全にはマッチしていない感じでちょっと不便だなとは思っていました。

gistにuploadする際に、複数のファイルを指定してuploadしたくはなると思います。これはgistyでもサポートしていて頻繁に利用していました。

$ gisty post x y z 

ところで、このuploadしたxに少し修正を加えた結果をuploadしたいとなった時に便利に取り扱う方法がなかったというのが問題でした。gistyはgistへのuploadと同時に ~/.gisty/ 以下などにuploadした内容をcloneしてくれるので、その中のファイルを変更しpushしてあげるとupdateできた(記憶が)あるのですが。そもそも、自分が変更したかったファイルは別の場所に隔離されたコピー(~/gisty/<gist id>/x などのことを言っている)ではなく、コピー対象となったファイル自体であることがほとんどなので上手くいきません。

また、新規のアップロードか既存のgistの更新なのか判断するためにgistのidを指定するのはどうでしょう?そもそもgistのidを覚えておいたりコピペしたりするのが嫌でした。

このため、今までの利用方法としては、特定のファイルの更新であっても新規にgistをuploadし直してました。(時間当たりのファイルの内容の遷移を追うことはできなくなりますが、そのあたりは利便性との兼ね合いで目をつむってました)

selfish

そんなわけでgoの勉強も兼ねて丁度良いということでgistのuploadをする自分用のツールを作ろうと思ったのでした。基本的には新規作成・更新・削除以上のことをしないのでそれしかサポートしていません。

インストール方法

インストール自体は以下でできます。

$ go get github.com/podhmo/selfish/cmd/selfish

色々調べた結果、 cmd 以下に実行コマンドのコードを置くという方法があるらしいということを知りなるほどと思いました。 (調べたメモ)

使い方

gistのidを管理するのが面倒だったので以下のようにaliasを指定できるようにしました。

$ selfish -alias mytest x y z
create success. (id="5639abca377b5c92061248666d38e6aa")
opening.. "https://gist.github.com/5639abca377b5c92061248666d38e6aa"

上の例では、mytestという名前で管理することになります。これで新規のgistが生成されます。gistyに倣ってgist作成後に作成したgistのページをブラウザで開きます。

また、さらにxに変更があった場合には再度以下の以下のコマンドを実行してください。

$ selfish -alias mytest -silent x y z
update success. (id="5639abca377b5c92061248666d38e6aa")

gist post後にブラウザで開かれるのが邪魔な場合には、 -silent を付けると抑制出来ます。

upload済みのgistを消したい場合には -delete を付けると消せます。

$ selfish -alias mytest -delete
deleted. (id="5639abca377b5c92061248666d38e6aa")

ちなみに -alias を指定しない場合には、 head というaliasで新規作成されます。(ただし -alias head と明示的にaliasを指定しない限り更新はされません)

結果

gistのrevisionが機能するようになった。嬉しい。

細かいこと

sqliteなど使うのはおおげさかなと思い使わない選択をしたのですが、ところで、LIFOみたいなものを雑に保持するのに何が良いんだろう?みたいなことを思ったりしました。