cmd/godoc: use same GOROOT discovery as Go 1.10's cmd/go

Fixes golang/go#23445

Change-Id: I4c707107e636558b49ea9a1a8690723b06dda235
Reviewed-on: https://go-review.googlesource.com/118075
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/cmd/godoc/goroot.go b/cmd/godoc/goroot.go
new file mode 100644
index 0000000..998e869
--- /dev/null
+++ b/cmd/godoc/goroot.go
@@ -0,0 +1,74 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"os"
+	"path/filepath"
+	"runtime"
+)
+
+// Copies of functions from src/cmd/go/internal/cfg/cfg.go for
+// finding the GOROOT.
+// Keep them in sync until support is moved to a common place, if ever.
+
+func findGOROOT() string {
+	if env := os.Getenv("GOROOT"); env != "" {
+		return filepath.Clean(env)
+	}
+	def := filepath.Clean(runtime.GOROOT())
+	if runtime.Compiler == "gccgo" {
+		// gccgo has no real GOROOT, and it certainly doesn't
+		// depend on the executable's location.
+		return def
+	}
+	exe, err := os.Executable()
+	if err == nil {
+		exe, err = filepath.Abs(exe)
+		if err == nil {
+			if dir := filepath.Join(exe, "../.."); isGOROOT(dir) {
+				// If def (runtime.GOROOT()) and dir are the same
+				// directory, prefer the spelling used in def.
+				if isSameDir(def, dir) {
+					return def
+				}
+				return dir
+			}
+			exe, err = filepath.EvalSymlinks(exe)
+			if err == nil {
+				if dir := filepath.Join(exe, "../.."); isGOROOT(dir) {
+					if isSameDir(def, dir) {
+						return def
+					}
+					return dir
+				}
+			}
+		}
+	}
+	return def
+}
+
+// isGOROOT reports whether path looks like a GOROOT.
+//
+// It does this by looking for the path/pkg/tool directory,
+// which is necessary for useful operation of the cmd/go tool,
+// and is not typically present in a GOPATH.
+func isGOROOT(path string) bool {
+	stat, err := os.Stat(filepath.Join(path, "pkg", "tool"))
+	if err != nil {
+		return false
+	}
+	return stat.IsDir()
+}
+
+// isSameDir reports whether dir1 and dir2 are the same directory.
+func isSameDir(dir1, dir2 string) bool {
+	if dir1 == dir2 {
+		return true
+	}
+	info1, err1 := os.Stat(dir1)
+	info2, err2 := os.Stat(dir2)
+	return err1 == nil && err2 == nil && os.SameFile(info1, info2)
+}
diff --git a/cmd/godoc/main.go b/cmd/godoc/main.go
index 9e70469..567ac89 100644
--- a/cmd/godoc/main.go
+++ b/cmd/godoc/main.go
@@ -82,7 +82,7 @@
 
 	// file system roots
 	// TODO(gri) consider the invariant that goroot always end in '/'
-	goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory")
+	goroot = flag.String("goroot", findGOROOT(), "Go root directory")
 
 	// layout control
 	tabWidth       = flag.Int("tabwidth", 4, "tab width")