mirror of
https://github.com/golang/go
synced 2024-11-05 22:56:11 -07:00
f51c1a7cd2
The autocert.go file in x/tools was deleted in CL 162400, but some related variables were left behind. This change deletes them, since they are unused by now. (If you're feeling déjà vu, it's likely because this was done in x/website in CL 197957.) Updates golang/go#29206 Updates golang/go#32011 Change-Id: I070f711e8bbc7b54f1d169571356fdc24bc00137 Reviewed-on: https://go-review.googlesource.com/c/tools/+/208259 Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
511 lines
15 KiB
Go
511 lines
15 KiB
Go
// 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"
|
|
"encoding/json"
|
|
_ "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/analysis"
|
|
"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/xerrors"
|
|
)
|
|
|
|
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")
|
|
|
|
analysisFlag = flag.String("analysis", "", `comma-separated list of analyses to perform when in GOPATH mode (supported: type, pointer). See https://golang.org/lib/godoc/analysis/help.html`)
|
|
|
|
// 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)
|
|
} else {
|
|
fs.Bind("/lib/godoc", mapfs.New(static.Files), "/", 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)
|
|
|
|
if *analysisFlag != "" {
|
|
fmt.Fprintln(os.Stderr, "The -analysis flag is supported only in GOPATH mode at this time.")
|
|
fmt.Fprintln(os.Stderr, "See https://golang.org/issue/34473.")
|
|
usage()
|
|
}
|
|
|
|
// Try to download dependencies that are not in the module cache in order to
|
|
// 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 typeAnalysis, pointerAnalysis bool
|
|
if *analysisFlag != "" {
|
|
for _, a := range strings.Split(*analysisFlag, ",") {
|
|
switch a {
|
|
case "type":
|
|
typeAnalysis = true
|
|
case "pointer":
|
|
pointerAnalysis = true
|
|
default:
|
|
log.Fatalf("unknown analysis: %s", a)
|
|
}
|
|
}
|
|
}
|
|
|
|
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 type/pointer analysis.
|
|
if typeAnalysis || pointerAnalysis {
|
|
go analysis.Run(pointerAnalysis, &corpus.Analysis)
|
|
}
|
|
|
|
// 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); xerrors.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 when operating in module mode.
|
|
//
|
|
// 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); xerrors.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)
|
|
}
|
|
}
|
|
|
|
// buildList determines the build list in the current directory
|
|
// by invoking the go command. It should only be used when operating
|
|
// in module mode.
|
|
//
|
|
// 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); xerrors.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
|
|
}
|
|
|
|
type mod struct {
|
|
Path string // Module path.
|
|
Dir string // Directory holding files for this module, if any.
|
|
}
|
|
|
|
// 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() + ")" }
|