whatcanGOwrong

This commit is contained in:
2024-09-19 21:38:24 -04:00
commit d0ae4d841d
17908 changed files with 4096831 additions and 0 deletions
@@ -0,0 +1,113 @@
// Copyright 2009 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.
/*
Godoc extracts and generates documentation for Go programs.
It runs as a web server and presents the documentation as a
web page.
godoc -http=:6060
Usage:
godoc [flag]
The flags are:
-v
verbose mode
-timestamps=true
show timestamps with directory listings
-index
enable identifier and full text search index
(no search box is shown if -index is not set)
-index_files=""
glob pattern specifying index files; if not empty,
the index is read from these files in sorted order
-index_throttle=0.75
index throttle value; a value of 0 means no time is allocated
to the indexer (the indexer will never finish), a value of 1.0
means that index creation is running at full throttle (other
goroutines may get no time while the index is built)
-index_interval=0
interval of indexing; a value of 0 sets it to 5 minutes, a
negative value indexes only once at startup
-play=false
enable playground
-links=true
link identifiers to their declarations
-write_index=false
write index to a file; the file name must be specified with
-index_files
-maxresults=10000
maximum number of full text search results shown
(no full text index is built if maxresults <= 0)
-notes="BUG"
regular expression matching note markers to show
(e.g., "BUG|TODO", ".*")
-goroot=$GOROOT
Go root directory
-http=addr
HTTP service address (e.g., '127.0.0.1:6060' or just ':6060')
-templates=""
directory containing alternate template files; if set,
the directory may provide alternative template files
for the files in $GOROOT/lib/godoc
-url=path
print to standard output the data that would be served by
an HTTP request for path
-zip=""
zip file providing the file system to serve; disabled if empty
By default, godoc looks at the packages it finds via $GOROOT and $GOPATH (if set).
This behavior can be altered by providing an alternative $GOROOT with the -goroot
flag.
When the -index flag is set, a search index is maintained.
The index is created at startup.
The index contains both identifier and full text search information (searchable
via regular expressions). The maximum number of full text search results shown
can be set with the -maxresults flag; if set to 0, no full text results are
shown, and only an identifier index but no full text search index is created.
By default, godoc uses the system's GOOS/GOARCH. You can provide the URL parameters
"GOOS" and "GOARCH" to set the output on the web page for the target system.
The presentation mode of web pages served by godoc can be controlled with the
"m" URL parameter; it accepts a comma-separated list of flag names as value:
all show documentation for all declarations, not just the exported ones
methods show all embedded methods, not just those of unexported anonymous fields
src show the original source code rather than the extracted documentation
flat present flat (not indented) directory listings using full paths
For instance, https://golang.org/pkg/math/big/?m=all shows the documentation
for all (not just the exported) declarations of package big.
By default, godoc serves files from the file system of the underlying OS.
Instead, a .zip file may be provided via the -zip flag, which contains
the file system to serve. The file paths stored in the .zip file must use
slash ('/') as path separator; and they must be unrooted. $GOROOT (or -goroot)
must be set to the .zip file directory path containing the Go root directory.
For instance, for a .zip file created by the command:
zip -r go.zip $HOME/go
one may run godoc as follows:
godoc -http=:6060 -zip=go.zip -goroot=$HOME/go
Godoc documentation is converted to HTML or to text using the go/doc package;
see https://golang.org/pkg/go/doc/#ToHTML for the exact rules.
Godoc also shows example code that is runnable by the testing package;
see https://golang.org/pkg/testing/#hdr-Examples for the conventions.
See "Godoc: documenting Go code" for how to write good comments for godoc:
https://golang.org/doc/articles/godoc_documenting_go_code.html
Deprecated: godoc cannot select what version of a package is displayed.
Instead, use golang.org/x/pkgsite/cmd/pkgsite.
*/
package main // import "golang.org/x/tools/cmd/godoc"
@@ -0,0 +1,464 @@
// Copyright 2013 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 (
"bytes"
"context"
"fmt"
"go/build"
"io"
"net"
"net/http"
"os"
"os/exec"
"regexp"
"runtime"
"strings"
"sync"
"testing"
"time"
"golang.org/x/tools/go/packages/packagestest"
"golang.org/x/tools/internal/testenv"
)
func TestMain(m *testing.M) {
if os.Getenv("GODOC_TEST_IS_GODOC") != "" {
main()
os.Exit(0)
}
// Inform subprocesses that they should run the cmd/godoc main instead of
// running tests. It's a close approximation to building and running the real
// command, and much less complicated and expensive to build and clean up.
os.Setenv("GODOC_TEST_IS_GODOC", "1")
os.Exit(m.Run())
}
var exe struct {
path string
err error
once sync.Once
}
func godocPath(t *testing.T) string {
if !testenv.HasExec() {
t.Skipf("skipping test: exec not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
}
exe.once.Do(func() {
exe.path, exe.err = os.Executable()
})
if exe.err != nil {
t.Fatal(exe.err)
}
return exe.path
}
func serverAddress(t *testing.T) string {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
ln, err = net.Listen("tcp6", "[::1]:0")
}
if err != nil {
t.Fatal(err)
}
defer ln.Close()
return ln.Addr().String()
}
func waitForServerReady(t *testing.T, ctx context.Context, cmd *exec.Cmd, addr string) {
waitForServer(t, ctx,
fmt.Sprintf("http://%v/", addr),
"Go Documentation Server",
false)
}
func waitForSearchReady(t *testing.T, ctx context.Context, cmd *exec.Cmd, addr string) {
waitForServer(t, ctx,
fmt.Sprintf("http://%v/search?q=FALLTHROUGH", addr),
"The list of tokens.",
false)
}
func waitUntilScanComplete(t *testing.T, ctx context.Context, addr string) {
waitForServer(t, ctx,
fmt.Sprintf("http://%v/pkg", addr),
"Scan is not yet complete",
// setting reverse as true, which means this waits
// until the string is not returned in the response anymore
true)
}
const pollInterval = 50 * time.Millisecond
// waitForServer waits for server to meet the required condition,
// failing the test if ctx is canceled before that occurs.
func waitForServer(t *testing.T, ctx context.Context, url, match string, reverse bool) {
start := time.Now()
for {
if ctx.Err() != nil {
t.Helper()
t.Fatalf("server failed to respond in %v", time.Since(start))
}
time.Sleep(pollInterval)
res, err := http.Get(url)
if err != nil {
continue
}
body, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil || res.StatusCode != http.StatusOK {
continue
}
switch {
case !reverse && bytes.Contains(body, []byte(match)),
reverse && !bytes.Contains(body, []byte(match)):
return
}
}
}
// hasTag checks whether a given release tag is contained in the current version
// of the go binary.
func hasTag(t string) bool {
for _, v := range build.Default.ReleaseTags {
if t == v {
return true
}
}
return false
}
func TestURL(t *testing.T) {
if runtime.GOOS == "plan9" {
t.Skip("skipping on plan9; fails to start up quickly enough")
}
bin := godocPath(t)
testcase := func(url string, contents string) func(t *testing.T) {
return func(t *testing.T) {
stdout, stderr := new(bytes.Buffer), new(bytes.Buffer)
args := []string{fmt.Sprintf("-url=%s", url)}
cmd := testenv.Command(t, bin, args...)
cmd.Stdout = stdout
cmd.Stderr = stderr
cmd.Args[0] = "godoc"
// Set GOPATH variable to a non-existing absolute path
// and GOPROXY=off to disable module fetches.
// We cannot just unset GOPATH variable because godoc would default it to ~/go.
// (We don't want the indexer looking at the local workspace during tests.)
cmd.Env = append(os.Environ(),
"GOPATH=/does_not_exist",
"GOPROXY=off",
"GO111MODULE=off")
if err := cmd.Run(); err != nil {
t.Fatalf("failed to run godoc -url=%q: %s\nstderr:\n%s", url, err, stderr)
}
if !strings.Contains(stdout.String(), contents) {
t.Errorf("did not find substring %q in output of godoc -url=%q:\n%s", contents, url, stdout)
}
}
}
t.Run("index", testcase("/", "These packages are part of the Go Project but outside the main Go tree."))
t.Run("fmt", testcase("/pkg/fmt", "Package fmt implements formatted I/O"))
}
// Basic integration test for godoc HTTP interface.
func TestWeb(t *testing.T) {
bin := godocPath(t)
for _, x := range packagestest.All {
t.Run(x.Name(), func(t *testing.T) {
testWeb(t, x, bin, false)
})
}
}
// Basic integration test for godoc HTTP interface.
func TestWebIndex(t *testing.T) {
t.Skip("slow test of to-be-deleted code (golang/go#59056)")
if testing.Short() {
t.Skip("skipping slow test in -short mode")
}
bin := godocPath(t)
testWeb(t, packagestest.GOPATH, bin, true)
}
// Basic integration test for godoc HTTP interface.
func testWeb(t *testing.T, x packagestest.Exporter, bin string, withIndex bool) {
switch runtime.GOOS {
case "plan9":
t.Skip("skipping on plan9: fails to start up quickly enough")
case "android", "ios":
t.Skip("skipping on mobile: lacks GOROOT/api in test environment")
}
// Write a fake GOROOT/GOPATH with some third party packages.
e := packagestest.Export(t, x, []packagestest.Module{
{
Name: "godoc.test/repo1",
Files: map[string]interface{}{
"a/a.go": `// Package a is a package in godoc.test/repo1.
package a; import _ "godoc.test/repo2/a"; const Name = "repo1a"`,
"b/b.go": `package b; const Name = "repo1b"`,
},
},
{
Name: "godoc.test/repo2",
Files: map[string]interface{}{
"a/a.go": `package a; const Name = "repo2a"`,
"b/b.go": `package b; const Name = "repo2b"`,
},
},
})
defer e.Cleanup()
// Start the server.
addr := serverAddress(t)
args := []string{fmt.Sprintf("-http=%s", addr)}
if withIndex {
args = append(args, "-index", "-index_interval=-1s")
}
cmd := testenv.Command(t, bin, args...)
cmd.Dir = e.Config.Dir
cmd.Env = e.Config.Env
cmdOut := new(strings.Builder)
cmd.Stdout = cmdOut
cmd.Stderr = cmdOut
cmd.Args[0] = "godoc"
if err := cmd.Start(); err != nil {
t.Fatalf("failed to start godoc: %s", err)
}
ctx, cancel := context.WithCancel(context.Background())
go func() {
err := cmd.Wait()
t.Logf("%v: %v", cmd, err)
cancel()
}()
defer func() {
// Shut down the server cleanly if possible.
if runtime.GOOS == "windows" {
cmd.Process.Kill() // Windows doesn't support os.Interrupt.
} else {
cmd.Process.Signal(os.Interrupt)
}
<-ctx.Done()
t.Logf("server output:\n%s", cmdOut)
}()
if withIndex {
waitForSearchReady(t, ctx, cmd, addr)
} else {
waitForServerReady(t, ctx, cmd, addr)
waitUntilScanComplete(t, ctx, addr)
}
tests := []struct {
path string
contains []string // substring
match []string // regexp
notContains []string
needIndex bool
releaseTag string // optional release tag that must be in go/build.ReleaseTags
}{
{
path: "/",
contains: []string{
"Go Documentation Server",
"Standard library",
"These packages are part of the Go Project but outside the main Go tree.",
},
},
{
path: "/pkg/fmt/",
contains: []string{"Package fmt implements formatted I/O"},
},
{
path: "/src/fmt/",
contains: []string{"scan_test.go"},
},
{
path: "/src/fmt/print.go",
contains: []string{"// Println formats using"},
},
{
path: "/pkg",
contains: []string{
"Standard library",
"Package fmt implements formatted I/O",
"Third party",
"Package a is a package in godoc.test/repo1.",
},
notContains: []string{
"internal/syscall",
"cmd/gc",
},
},
{
path: "/pkg/?m=all",
contains: []string{
"Standard library",
"Package fmt implements formatted I/O",
"internal/syscall/?m=all",
},
notContains: []string{
"cmd/gc",
},
},
{
path: "/search?q=ListenAndServe",
contains: []string{
"/src",
},
notContains: []string{
"/pkg/bootstrap",
},
needIndex: true,
},
{
path: "/pkg/strings/",
contains: []string{
`href="/src/strings/strings.go"`,
},
},
{
path: "/cmd/compile/internal/amd64/",
contains: []string{
`href="/src/cmd/compile/internal/amd64/ssa.go"`,
},
},
{
path: "/pkg/math/bits/",
contains: []string{
`Added in Go 1.9`,
},
},
{
path: "/pkg/net/",
contains: []string{
`// IPv6 scoped addressing zone; added in Go 1.1`,
},
},
{
path: "/pkg/net/http/httptrace/",
match: []string{
`Got1xxResponse.*// Go 1\.11`,
},
releaseTag: "go1.11",
},
// Verify we don't add version info to a struct field added the same time
// as the struct itself:
{
path: "/pkg/net/http/httptrace/",
match: []string{
`(?m)GotFirstResponseByte func\(\)\s*$`,
},
},
// Remove trailing periods before adding semicolons:
{
path: "/pkg/database/sql/",
contains: []string{
"The number of connections currently in use; added in Go 1.11",
"The number of idle connections; added in Go 1.11",
},
releaseTag: "go1.11",
},
// Third party packages.
{
path: "/pkg/godoc.test/repo1/a",
contains: []string{`const <span id="Name">Name</span> = &#34;repo1a&#34;`},
},
{
path: "/pkg/godoc.test/repo2/b",
contains: []string{`const <span id="Name">Name</span> = &#34;repo2b&#34;`},
},
}
for _, test := range tests {
if test.needIndex && !withIndex {
continue
}
url := fmt.Sprintf("http://%s%s", addr, test.path)
resp, err := http.Get(url)
if err != nil {
t.Errorf("GET %s failed: %s", url, err)
continue
}
body, err := io.ReadAll(resp.Body)
strBody := string(body)
resp.Body.Close()
if err != nil {
t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp)
}
isErr := false
for _, substr := range test.contains {
if test.releaseTag != "" && !hasTag(test.releaseTag) {
continue
}
if !bytes.Contains(body, []byte(substr)) {
t.Errorf("GET %s: wanted substring %q in body", url, substr)
isErr = true
}
}
for _, re := range test.match {
if test.releaseTag != "" && !hasTag(test.releaseTag) {
continue
}
if ok, err := regexp.MatchString(re, strBody); !ok || err != nil {
if err != nil {
t.Fatalf("Bad regexp %q: %v", re, err)
}
t.Errorf("GET %s: wanted to match %s in body", url, re)
isErr = true
}
}
for _, substr := range test.notContains {
if bytes.Contains(body, []byte(substr)) {
t.Errorf("GET %s: didn't want substring %q in body", url, substr)
isErr = true
}
}
if isErr {
t.Errorf("GET %s: got:\n%s", url, body)
}
}
}
// Test for golang.org/issue/35476.
func TestNoMainModule(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in -short mode")
}
if runtime.GOOS == "plan9" {
t.Skip("skipping on plan9; for consistency with other tests that build godoc binary")
}
bin := godocPath(t)
tempDir := t.TempDir()
// Run godoc in an empty directory with module mode explicitly on,
// so that 'go env GOMOD' reports os.DevNull.
cmd := testenv.Command(t, bin, "-url=/")
cmd.Dir = tempDir
cmd.Env = append(os.Environ(), "GO111MODULE=on")
var stderr bytes.Buffer
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
t.Fatalf("godoc command failed: %v\nstderr=%q", err, stderr.String())
}
if strings.Contains(stderr.String(), "go mod download") {
t.Errorf("stderr contains 'go mod download', is that intentional?\nstderr=%q", stderr.String())
}
}
@@ -0,0 +1,30 @@
// 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"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
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
}
out, err := exec.Command("go", "env", "GOROOT").Output()
if err != nil {
return def
}
return strings.TrimSpace(string(out))
}
@@ -0,0 +1,98 @@
// Copyright 2010 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 (
"encoding/json"
"go/format"
"log"
"net/http"
"text/template"
"golang.org/x/tools/godoc"
"golang.org/x/tools/godoc/redirect"
"golang.org/x/tools/godoc/vfs"
_ "golang.org/x/tools/playground" // register "/compile" playground redirect
)
var (
pres *godoc.Presentation
fs = vfs.NameSpace{}
)
func registerHandlers(pres *godoc.Presentation) {
if pres == nil {
panic("nil Presentation")
}
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/" {
http.Redirect(w, req, "/pkg/", http.StatusFound)
return
}
pres.ServeHTTP(w, req)
})
mux.Handle("/pkg/C/", redirect.Handler("/cmd/cgo/"))
mux.HandleFunc("/fmt", fmtHandler)
redirect.Register(mux)
http.Handle("/", mux)
}
func readTemplate(name string) *template.Template {
if pres == nil {
panic("no global Presentation set yet")
}
path := "lib/godoc/" + name
// use underlying file system fs to read the template file
// (cannot use template ParseFile functions directly)
data, err := vfs.ReadFile(fs, path)
if err != nil {
log.Fatal("readTemplate: ", err)
}
// be explicit with errors (for app engine use)
t, err := template.New(name).Funcs(pres.FuncMap()).Parse(string(data))
if err != nil {
log.Fatal("readTemplate: ", err)
}
return t
}
func readTemplates(p *godoc.Presentation) {
p.CallGraphHTML = readTemplate("callgraph.html")
p.DirlistHTML = readTemplate("dirlist.html")
p.ErrorHTML = readTemplate("error.html")
p.ExampleHTML = readTemplate("example.html")
p.GodocHTML = readTemplate("godoc.html")
p.ImplementsHTML = readTemplate("implements.html")
p.MethodSetHTML = readTemplate("methodset.html")
p.PackageHTML = readTemplate("package.html")
p.PackageRootHTML = readTemplate("packageroot.html")
p.SearchHTML = readTemplate("search.html")
p.SearchDocHTML = readTemplate("searchdoc.html")
p.SearchCodeHTML = readTemplate("searchcode.html")
p.SearchTxtHTML = readTemplate("searchtxt.html")
}
type fmtResponse struct {
Body string
Error string
}
// fmtHandler takes a Go program in its "body" form value, formats it with
// standard gofmt formatting, and writes a fmtResponse as a JSON object.
func fmtHandler(w http.ResponseWriter, r *http.Request) {
resp := new(fmtResponse)
body, err := format.Source([]byte(r.FormValue("body")))
if err != nil {
resp.Error = err.Error()
} else {
resp.Body = string(body)
}
w.Header().Set("Content-type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(resp)
}
@@ -0,0 +1,504 @@
// Copyright 2009 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.
// godoc: Go Documentation Server
// Web server tree:
//
// http://godoc/ redirect to /pkg/
// http://godoc/src/ serve files from $GOROOT/src; .go gets pretty-printed
// http://godoc/cmd/ serve documentation about commands
// http://godoc/pkg/ serve documentation about packages
// (idea is if you say import "compress/zlib", you go to
// http://godoc/pkg/compress/zlib)
//
package main
import (
"archive/zip"
"bytes"
"context"
"encoding/json"
"errors"
_ "expvar" // to serve /debug/vars
"flag"
"fmt"
"go/build"
"io"
"log"
"net/http"
_ "net/http/pprof" // to serve /debug/pprof/*
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
"golang.org/x/tools/godoc"
"golang.org/x/tools/godoc/static"
"golang.org/x/tools/godoc/vfs"
"golang.org/x/tools/godoc/vfs/gatefs"
"golang.org/x/tools/godoc/vfs/mapfs"
"golang.org/x/tools/godoc/vfs/zipfs"
"golang.org/x/tools/internal/gocommand"
)
const defaultAddr = "localhost:6060" // default webserver address
var (
// file system to serve
// (with e.g.: zip -r go.zip $GOROOT -i \*.go -i \*.html -i \*.css -i \*.js -i \*.txt -i \*.c -i \*.h -i \*.s -i \*.png -i \*.jpg -i \*.sh -i favicon.ico)
zipfile = flag.String("zip", "", "zip file providing the file system to serve; disabled if empty")
// file-based index
writeIndex = flag.Bool("write_index", false, "write index to a file; the file name must be specified with -index_files")
// network
httpAddr = flag.String("http", defaultAddr, "HTTP service address")
// layout control
urlFlag = flag.String("url", "", "print HTML for named URL")
verbose = flag.Bool("v", false, "verbose mode")
// file system roots
// TODO(gri) consider the invariant that goroot always end in '/'
goroot = flag.String("goroot", findGOROOT(), "Go root directory")
// layout control
showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
templateDir = flag.String("templates", "", "load templates/JS/CSS from disk in this directory")
showPlayground = flag.Bool("play", false, "enable playground")
declLinks = flag.Bool("links", true, "link identifiers to their declarations")
// search index
indexEnabled = flag.Bool("index", false, "enable search index")
indexFiles = flag.String("index_files", "", "glob pattern specifying index files; if not empty, the index is read from these files in sorted order")
indexInterval = flag.Duration("index_interval", 0, "interval of indexing; 0 for default (5m), negative to only index once at startup")
maxResults = flag.Int("maxresults", 10000, "maximum number of full text search results shown")
indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle")
// source code notes
notesRx = flag.String("notes", "BUG", "regular expression matching note markers to show")
)
// An httpResponseRecorder is an http.ResponseWriter
type httpResponseRecorder struct {
body *bytes.Buffer
header http.Header
code int
}
func (w *httpResponseRecorder) Header() http.Header { return w.header }
func (w *httpResponseRecorder) Write(b []byte) (int, error) { return w.body.Write(b) }
func (w *httpResponseRecorder) WriteHeader(code int) { w.code = code }
func usage() {
fmt.Fprintf(os.Stderr, "usage: godoc -http="+defaultAddr+"\n")
flag.PrintDefaults()
os.Exit(2)
}
func loggingHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
log.Printf("%s\t%s", req.RemoteAddr, req.URL)
h.ServeHTTP(w, req)
})
}
func handleURLFlag() {
// Try up to 10 fetches, following redirects.
urlstr := *urlFlag
for i := 0; i < 10; i++ {
// Prepare request.
u, err := url.Parse(urlstr)
if err != nil {
log.Fatal(err)
}
req := &http.Request{
URL: u,
}
// Invoke default HTTP handler to serve request
// to our buffering httpWriter.
w := &httpResponseRecorder{code: 200, header: make(http.Header), body: new(bytes.Buffer)}
http.DefaultServeMux.ServeHTTP(w, req)
// Return data, error, or follow redirect.
switch w.code {
case 200: // ok
os.Stdout.Write(w.body.Bytes())
return
case 301, 302, 303, 307: // redirect
redirect := w.header.Get("Location")
if redirect == "" {
log.Fatalf("HTTP %d without Location header", w.code)
}
urlstr = redirect
default:
log.Fatalf("HTTP error %d", w.code)
}
}
log.Fatalf("too many redirects")
}
func initCorpus(corpus *godoc.Corpus) {
err := corpus.Init()
if err != nil {
log.Fatal(err)
}
}
func main() {
flag.Usage = usage
flag.Parse()
// Check usage.
if flag.NArg() > 0 {
fmt.Fprintln(os.Stderr, `Unexpected arguments. Use "go doc" for command-line help output instead. For example, "go doc fmt.Printf".`)
usage()
}
if *httpAddr == "" && *urlFlag == "" && !*writeIndex {
fmt.Fprintln(os.Stderr, "At least one of -http, -url, or -write_index must be set to a non-zero value.")
usage()
}
// Set the resolved goroot.
vfs.GOROOT = *goroot
fsGate := make(chan bool, 20)
// Determine file system to use.
if *zipfile == "" {
// use file system of underlying OS
rootfs := gatefs.New(vfs.OS(*goroot), fsGate)
fs.Bind("/", rootfs, "/", vfs.BindReplace)
} else {
// use file system specified via .zip file (path separator must be '/')
rc, err := zip.OpenReader(*zipfile)
if err != nil {
log.Fatalf("%s: %s\n", *zipfile, err)
}
defer rc.Close() // be nice (e.g., -writeIndex mode)
fs.Bind("/", zipfs.New(rc, *zipfile), *goroot, vfs.BindReplace)
}
if *templateDir != "" {
fs.Bind("/lib/godoc", vfs.OS(*templateDir), "/", vfs.BindBefore)
fs.Bind("/favicon.ico", vfs.OS(*templateDir), "/favicon.ico", vfs.BindReplace)
} else {
fs.Bind("/lib/godoc", mapfs.New(static.Files), "/", vfs.BindReplace)
fs.Bind("/favicon.ico", mapfs.New(static.Files), "/favicon.ico", vfs.BindReplace)
}
// Get the GOMOD value, use it to determine if godoc is being invoked in module mode.
goModFile, err := goMod()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to determine go env GOMOD value: %v", err)
goModFile = "" // Fall back to GOPATH mode.
}
if goModFile != "" {
fmt.Printf("using module mode; GOMOD=%s\n", goModFile)
// Detect whether to use vendor mode or not.
vendorEnabled, mainModVendor, err := gocommand.VendorEnabled(context.Background(), gocommand.Invocation{}, &gocommand.Runner{})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to determine if vendoring is enabled: %v", err)
os.Exit(1)
}
if vendorEnabled {
// Bind the root directory of the main module.
fs.Bind(path.Join("/src", mainModVendor.Path), gatefs.New(vfs.OS(mainModVendor.Dir), fsGate), "/", vfs.BindAfter)
// Bind the vendor directory.
//
// Note that in module mode, vendor directories in locations
// other than the main module's root directory are ignored.
// See https://golang.org/ref/mod#vendoring.
vendorDir := filepath.Join(mainModVendor.Dir, "vendor")
fs.Bind("/src", gatefs.New(vfs.OS(vendorDir), fsGate), "/", vfs.BindAfter)
} else {
// Try to download dependencies that are not in the module cache in order to
// show their documentation.
// This may fail if module downloading is disallowed (GOPROXY=off) or due to
// limited connectivity, in which case we print errors to stderr and show
// documentation only for packages that are available.
fillModuleCache(os.Stderr, goModFile)
// Determine modules in the build list.
mods, err := buildList(goModFile)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to determine the build list of the main module: %v", err)
os.Exit(1)
}
// Bind module trees into Go root.
for _, m := range mods {
if m.Dir == "" {
// Module is not available in the module cache, skip it.
continue
}
dst := path.Join("/src", m.Path)
fs.Bind(dst, gatefs.New(vfs.OS(m.Dir), fsGate), "/", vfs.BindAfter)
}
}
} else {
fmt.Println("using GOPATH mode")
// Bind $GOPATH trees into Go root.
for _, p := range filepath.SplitList(build.Default.GOPATH) {
fs.Bind("/src", gatefs.New(vfs.OS(p), fsGate), "/src", vfs.BindAfter)
}
}
var corpus *godoc.Corpus
if goModFile != "" {
corpus = godoc.NewCorpus(moduleFS{fs})
} else {
corpus = godoc.NewCorpus(fs)
}
corpus.Verbose = *verbose
corpus.MaxResults = *maxResults
corpus.IndexEnabled = *indexEnabled
if *maxResults == 0 {
corpus.IndexFullText = false
}
corpus.IndexFiles = *indexFiles
corpus.IndexDirectory = func(dir string) bool {
return dir != "/pkg" && !strings.HasPrefix(dir, "/pkg/")
}
corpus.IndexThrottle = *indexThrottle
corpus.IndexInterval = *indexInterval
if *writeIndex || *urlFlag != "" {
corpus.IndexThrottle = 1.0
corpus.IndexEnabled = true
initCorpus(corpus)
} else {
go initCorpus(corpus)
}
// Initialize the version info before readTemplates, which saves
// the map value in a method value.
corpus.InitVersionInfo()
pres = godoc.NewPresentation(corpus)
pres.ShowTimestamps = *showTimestamps
pres.ShowPlayground = *showPlayground
pres.DeclLinks = *declLinks
if *notesRx != "" {
pres.NotesRx = regexp.MustCompile(*notesRx)
}
readTemplates(pres)
registerHandlers(pres)
if *writeIndex {
// Write search index and exit.
if *indexFiles == "" {
log.Fatal("no index file specified")
}
log.Println("initialize file systems")
*verbose = true // want to see what happens
corpus.UpdateIndex()
log.Println("writing index file", *indexFiles)
f, err := os.Create(*indexFiles)
if err != nil {
log.Fatal(err)
}
index, _ := corpus.CurrentIndex()
_, err = index.WriteTo(f)
if err != nil {
log.Fatal(err)
}
log.Println("done")
return
}
// Print content that would be served at the URL *urlFlag.
if *urlFlag != "" {
handleURLFlag()
return
}
var handler http.Handler = http.DefaultServeMux
if *verbose {
log.Printf("Go Documentation Server")
log.Printf("version = %s", runtime.Version())
log.Printf("address = %s", *httpAddr)
log.Printf("goroot = %s", *goroot)
switch {
case !*indexEnabled:
log.Print("search index disabled")
case *maxResults > 0:
log.Printf("full text index enabled (maxresults = %d)", *maxResults)
default:
log.Print("identifier search index enabled")
}
fs.Fprint(os.Stderr)
handler = loggingHandler(handler)
}
// Initialize search index.
if *indexEnabled {
go corpus.RunIndexer()
}
// Start http server.
if *verbose {
log.Println("starting HTTP server")
}
if err := http.ListenAndServe(*httpAddr, handler); err != nil {
log.Fatalf("ListenAndServe %s: %v", *httpAddr, err)
}
}
// goMod returns the go env GOMOD value in the current directory
// by invoking the go command.
//
// GOMOD is documented at https://golang.org/cmd/go/#hdr-Environment_variables:
//
// The absolute path to the go.mod of the main module,
// or the empty string if not using modules.
func goMod() (string, error) {
out, err := exec.Command("go", "env", "-json", "GOMOD").Output()
if ee := (*exec.ExitError)(nil); errors.As(err, &ee) {
return "", fmt.Errorf("go command exited unsuccessfully: %v\n%s", ee.ProcessState.String(), ee.Stderr)
} else if err != nil {
return "", err
}
var env struct {
GoMod string
}
err = json.Unmarshal(out, &env)
if err != nil {
return "", err
}
return env.GoMod, nil
}
// fillModuleCache does a best-effort attempt to fill the module cache
// with all dependencies of the main module in the current directory
// by invoking the go command. Module download logs are streamed to w.
// If there are any problems encountered, they are also written to w.
// It should only be used in module mode, when vendor mode isn't on.
//
// See https://golang.org/cmd/go/#hdr-Download_modules_to_local_cache.
func fillModuleCache(w io.Writer, goMod string) {
if goMod == os.DevNull {
// No module requirements, nothing to do.
return
}
cmd := exec.Command("go", "mod", "download", "-json")
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = w
err := cmd.Run()
if ee := (*exec.ExitError)(nil); errors.As(err, &ee) && ee.ExitCode() == 1 {
// Exit code 1 from this command means there were some
// non-empty Error values in the output. Print them to w.
fmt.Fprintf(w, "documentation for some packages is not shown:\n")
for dec := json.NewDecoder(&out); ; {
var m struct {
Path string // Module path.
Version string // Module version.
Error string // Error loading module.
}
err := dec.Decode(&m)
if err == io.EOF {
break
} else if err != nil {
fmt.Fprintf(w, "error decoding JSON object from go mod download -json: %v\n", err)
continue
}
if m.Error == "" {
continue
}
fmt.Fprintf(w, "\tmodule %s@%s is not in the module cache and there was a problem downloading it: %s\n", m.Path, m.Version, m.Error)
}
} else if err != nil {
fmt.Fprintf(w, "there was a problem filling module cache: %v\n", err)
}
}
type mod struct {
Path string // Module path.
Dir string // Directory holding files for this module, if any.
}
// buildList determines the build list in the current directory
// by invoking the go command. It should only be used in module mode,
// when vendor mode isn't on.
//
// See https://golang.org/cmd/go/#hdr-The_main_module_and_the_build_list.
func buildList(goMod string) ([]mod, error) {
if goMod == os.DevNull {
// Empty build list.
return nil, nil
}
out, err := exec.Command("go", "list", "-m", "-json", "all").Output()
if ee := (*exec.ExitError)(nil); errors.As(err, &ee) {
return nil, fmt.Errorf("go command exited unsuccessfully: %v\n%s", ee.ProcessState.String(), ee.Stderr)
} else if err != nil {
return nil, err
}
var mods []mod
for dec := json.NewDecoder(bytes.NewReader(out)); ; {
var m mod
err := dec.Decode(&m)
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
mods = append(mods, m)
}
return mods, nil
}
// moduleFS is a vfs.FileSystem wrapper used when godoc is running
// in module mode. It's needed so that packages inside modules are
// considered to be third party.
//
// It overrides the RootType method of the underlying filesystem
// and implements it using a heuristic based on the import path.
// If the first element of the import path does not contain a dot,
// that package is considered to be inside GOROOT. If it contains
// a dot, then that package is considered to be third party.
//
// TODO(dmitshur): The RootType abstraction works well when GOPATH
// workspaces are bound at their roots, but scales poorly in the
// general case. It should be replaced by a more direct solution
// for determining whether a package is third party or not.
type moduleFS struct{ vfs.FileSystem }
func (moduleFS) RootType(path string) vfs.RootType {
if !strings.HasPrefix(path, "/src/") {
return ""
}
domain := path[len("/src/"):]
if i := strings.Index(domain, "/"); i >= 0 {
domain = domain[:i]
}
if !strings.Contains(domain, ".") {
// No dot in the first element of import path
// suggests this is a package in GOROOT.
return vfs.RootTypeGoRoot
} else {
// A dot in the first element of import path
// suggests this is a third party package.
return vfs.RootTypeGoPath
}
}
func (fs moduleFS) String() string { return "module(" + fs.FileSystem.String() + ")" }