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