GoPLS Viewer

Home|gopls/cmd/godoc/main.go
1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// godoc: Go Documentation Server
6
7// Web server tree:
8//
9//    http://godoc/        redirect to /pkg/
10//    http://godoc/src/    serve files from $GOROOT/src; .go gets pretty-printed
11//    http://godoc/cmd/    serve documentation about commands
12//    http://godoc/pkg/    serve documentation about packages
13//                (idea is if you say import "compress/zlib", you go to
14//                http://godoc/pkg/compress/zlib)
15//
16
17package main
18
19import (
20    "archive/zip"
21    "bytes"
22    "context"
23    "encoding/json"
24    "errors"
25    _ "expvar" // to serve /debug/vars
26    "flag"
27    "fmt"
28    "go/build"
29    "io"
30    "log"
31    "net/http"
32    _ "net/http/pprof" // to serve /debug/pprof/*
33    "net/url"
34    "os"
35    "path"
36    "path/filepath"
37    "regexp"
38    "runtime"
39    "strings"
40
41    exec "golang.org/x/sys/execabs"
42
43    "golang.org/x/tools/godoc"
44    "golang.org/x/tools/godoc/static"
45    "golang.org/x/tools/godoc/vfs"
46    "golang.org/x/tools/godoc/vfs/gatefs"
47    "golang.org/x/tools/godoc/vfs/mapfs"
48    "golang.org/x/tools/godoc/vfs/zipfs"
49    "golang.org/x/tools/internal/gocommand"
50)
51
52const defaultAddr = "localhost:6060" // default webserver address
53
54var (
55    // file system to serve
56    // (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)
57    zipfile = flag.String("zip""""zip file providing the file system to serve; disabled if empty")
58
59    // file-based index
60    writeIndex = flag.Bool("write_index"false"write index to a file; the file name must be specified with -index_files")
61
62    // network
63    httpAddr = flag.String("http"defaultAddr"HTTP service address")
64
65    // layout control
66    urlFlag = flag.String("url""""print HTML for named URL")
67
68    verbose = flag.Bool("v"false"verbose mode")
69
70    // file system roots
71    // TODO(gri) consider the invariant that goroot always end in '/'
72    goroot = flag.String("goroot"findGOROOT(), "Go root directory")
73
74    // layout control
75    showTimestamps = flag.Bool("timestamps"false"show timestamps with directory listings")
76    templateDir    = flag.String("templates""""load templates/JS/CSS from disk in this directory")
77    showPlayground = flag.Bool("play"false"enable playground")
78    declLinks      = flag.Bool("links"true"link identifiers to their declarations")
79
80    // search index
81    indexEnabled  = flag.Bool("index"false"enable search index")
82    indexFiles    = flag.String("index_files""""glob pattern specifying index files; if not empty, the index is read from these files in sorted order")
83    indexInterval = flag.Duration("index_interval"0"interval of indexing; 0 for default (5m), negative to only index once at startup")
84    maxResults    = flag.Int("maxresults"10000"maximum number of full text search results shown")
85    indexThrottle = flag.Float64("index_throttle"0.75"index throttle value; 0.0 = no time allocated, 1.0 = full throttle")
86
87    // source code notes
88    notesRx = flag.String("notes""BUG""regular expression matching note markers to show")
89)
90
91// An httpResponseRecorder is an http.ResponseWriter
92type httpResponseRecorder struct {
93    body   *bytes.Buffer
94    header http.Header
95    code   int
96}
97
98func (w *httpResponseRecorderHeader() http.Header         { return w.header }
99func (w *httpResponseRecorderWrite(b []byte) (interror) { return w.body.Write(b) }
100func (w *httpResponseRecorderWriteHeader(code int)        { w.code = code }
101
102func usage() {
103    fmt.Fprintf(os.Stderr"usage: godoc -http="+defaultAddr+"\n")
104    flag.PrintDefaults()
105    os.Exit(2)
106}
107
108func loggingHandler(h http.Handlerhttp.Handler {
109    return http.HandlerFunc(func(w http.ResponseWriterreq *http.Request) {
110        log.Printf("%s\t%s"req.RemoteAddrreq.URL)
111        h.ServeHTTP(wreq)
112    })
113}
114
115func handleURLFlag() {
116    // Try up to 10 fetches, following redirects.
117    urlstr := *urlFlag
118    for i := 0i < 10i++ {
119        // Prepare request.
120        uerr := url.Parse(urlstr)
121        if err != nil {
122            log.Fatal(err)
123        }
124        req := &http.Request{
125            URLu,
126        }
127
128        // Invoke default HTTP handler to serve request
129        // to our buffering httpWriter.
130        w := &httpResponseRecorder{code200headermake(http.Header), bodynew(bytes.Buffer)}
131        http.DefaultServeMux.ServeHTTP(wreq)
132
133        // Return data, error, or follow redirect.
134        switch w.code {
135        case 200// ok
136            os.Stdout.Write(w.body.Bytes())
137            return
138        case 301302303307// redirect
139            redirect := w.header.Get("Location")
140            if redirect == "" {
141                log.Fatalf("HTTP %d without Location header"w.code)
142            }
143            urlstr = redirect
144        default:
145            log.Fatalf("HTTP error %d"w.code)
146        }
147    }
148    log.Fatalf("too many redirects")
149}
150
151func initCorpus(corpus *godoc.Corpus) {
152    err := corpus.Init()
153    if err != nil {
154        log.Fatal(err)
155    }
156}
157
158func main() {
159    flag.Usage = usage
160    flag.Parse()
161
162    // Check usage.
163    if flag.NArg() > 0 {
164        fmt.Fprintln(os.Stderr`Unexpected arguments. Use "go doc" for command-line help output instead. For example, "go doc fmt.Printf".`)
165        usage()
166    }
167    if *httpAddr == "" && *urlFlag == "" && !*writeIndex {
168        fmt.Fprintln(os.Stderr"At least one of -http, -url, or -write_index must be set to a non-zero value.")
169        usage()
170    }
171
172    // Set the resolved goroot.
173    vfs.GOROOT = *goroot
174
175    fsGate := make(chan bool20)
176
177    // Determine file system to use.
178    if *zipfile == "" {
179        // use file system of underlying OS
180        rootfs := gatefs.New(vfs.OS(*goroot), fsGate)
181        fs.Bind("/"rootfs"/"vfs.BindReplace)
182    } else {
183        // use file system specified via .zip file (path separator must be '/')
184        rcerr := zip.OpenReader(*zipfile)
185        if err != nil {
186            log.Fatalf("%s: %s\n", *zipfileerr)
187        }
188        defer rc.Close() // be nice (e.g., -writeIndex mode)
189        fs.Bind("/"zipfs.New(rc, *zipfile), *gorootvfs.BindReplace)
190    }
191    if *templateDir != "" {
192        fs.Bind("/lib/godoc"vfs.OS(*templateDir), "/"vfs.BindBefore)
193        fs.Bind("/favicon.ico"vfs.OS(*templateDir), "/favicon.ico"vfs.BindReplace)
194    } else {
195        fs.Bind("/lib/godoc"mapfs.New(static.Files), "/"vfs.BindReplace)
196        fs.Bind("/favicon.ico"mapfs.New(static.Files), "/favicon.ico"vfs.BindReplace)
197    }
198
199    // Get the GOMOD value, use it to determine if godoc is being invoked in module mode.
200    goModFileerr := goMod()
201    if err != nil {
202        fmt.Fprintf(os.Stderr"failed to determine go env GOMOD value: %v"err)
203        goModFile = "" // Fall back to GOPATH mode.
204    }
205
206    if goModFile != "" {
207        fmt.Printf("using module mode; GOMOD=%s\n"goModFile)
208
209        // Detect whether to use vendor mode or not.
210        vendorEnabledmainModVendorerr := gocommand.VendorEnabled(context.Background(), gocommand.Invocation{}, &gocommand.Runner{})
211        if err != nil {
212            fmt.Fprintf(os.Stderr"failed to determine if vendoring is enabled: %v"err)
213            os.Exit(1)
214        }
215        if vendorEnabled {
216            // Bind the root directory of the main module.
217            fs.Bind(path.Join("/src"mainModVendor.Path), gatefs.New(vfs.OS(mainModVendor.Dir), fsGate), "/"vfs.BindAfter)
218
219            // Bind the vendor directory.
220            //
221            // Note that in module mode, vendor directories in locations
222            // other than the main module's root directory are ignored.
223            // See https://golang.org/ref/mod#vendoring.
224            vendorDir := filepath.Join(mainModVendor.Dir"vendor")
225            fs.Bind("/src"gatefs.New(vfs.OS(vendorDir), fsGate), "/"vfs.BindAfter)
226
227        } else {
228            // Try to download dependencies that are not in the module cache in order to
229            // to show their documentation.
230            // This may fail if module downloading is disallowed (GOPROXY=off) or due to
231            // limited connectivity, in which case we print errors to stderr and show
232            // documentation only for packages that are available.
233            fillModuleCache(os.StderrgoModFile)
234
235            // Determine modules in the build list.
236            modserr := buildList(goModFile)
237            if err != nil {
238                fmt.Fprintf(os.Stderr"failed to determine the build list of the main module: %v"err)
239                os.Exit(1)
240            }
241
242            // Bind module trees into Go root.
243            for _m := range mods {
244                if m.Dir == "" {
245                    // Module is not available in the module cache, skip it.
246                    continue
247                }
248                dst := path.Join("/src"m.Path)
249                fs.Bind(dstgatefs.New(vfs.OS(m.Dir), fsGate), "/"vfs.BindAfter)
250            }
251        }
252    } else {
253        fmt.Println("using GOPATH mode")
254
255        // Bind $GOPATH trees into Go root.
256        for _p := range filepath.SplitList(build.Default.GOPATH) {
257            fs.Bind("/src"gatefs.New(vfs.OS(p), fsGate), "/src"vfs.BindAfter)
258        }
259    }
260
261    var corpus *godoc.Corpus
262    if goModFile != "" {
263        corpus = godoc.NewCorpus(moduleFS{fs})
264    } else {
265        corpus = godoc.NewCorpus(fs)
266    }
267    corpus.Verbose = *verbose
268    corpus.MaxResults = *maxResults
269    corpus.IndexEnabled = *indexEnabled
270    if *maxResults == 0 {
271        corpus.IndexFullText = false
272    }
273    corpus.IndexFiles = *indexFiles
274    corpus.IndexDirectory = func(dir stringbool {
275        return dir != "/pkg" && !strings.HasPrefix(dir"/pkg/")
276    }
277    corpus.IndexThrottle = *indexThrottle
278    corpus.IndexInterval = *indexInterval
279    if *writeIndex || *urlFlag != "" {
280        corpus.IndexThrottle = 1.0
281        corpus.IndexEnabled = true
282        initCorpus(corpus)
283    } else {
284        go initCorpus(corpus)
285    }
286
287    // Initialize the version info before readTemplates, which saves
288    // the map value in a method value.
289    corpus.InitVersionInfo()
290
291    pres = godoc.NewPresentation(corpus)
292    pres.ShowTimestamps = *showTimestamps
293    pres.ShowPlayground = *showPlayground
294    pres.DeclLinks = *declLinks
295    if *notesRx != "" {
296        pres.NotesRx = regexp.MustCompile(*notesRx)
297    }
298
299    readTemplates(pres)
300    registerHandlers(pres)
301
302    if *writeIndex {
303        // Write search index and exit.
304        if *indexFiles == "" {
305            log.Fatal("no index file specified")
306        }
307
308        log.Println("initialize file systems")
309        *verbose = true // want to see what happens
310
311        corpus.UpdateIndex()
312
313        log.Println("writing index file", *indexFiles)
314        ferr := os.Create(*indexFiles)
315        if err != nil {
316            log.Fatal(err)
317        }
318        index_ := corpus.CurrentIndex()
319        _err = index.WriteTo(f)
320        if err != nil {
321            log.Fatal(err)
322        }
323
324        log.Println("done")
325        return
326    }
327
328    // Print content that would be served at the URL *urlFlag.
329    if *urlFlag != "" {
330        handleURLFlag()
331        return
332    }
333
334    var handler http.Handler = http.DefaultServeMux
335    if *verbose {
336        log.Printf("Go Documentation Server")
337        log.Printf("version = %s"runtime.Version())
338        log.Printf("address = %s", *httpAddr)
339        log.Printf("goroot = %s", *goroot)
340        switch {
341        case !*indexEnabled:
342            log.Print("search index disabled")
343        case *maxResults > 0:
344            log.Printf("full text index enabled (maxresults = %d)", *maxResults)
345        default:
346            log.Print("identifier search index enabled")
347        }
348        fs.Fprint(os.Stderr)
349        handler = loggingHandler(handler)
350    }
351
352    // Initialize search index.
353    if *indexEnabled {
354        go corpus.RunIndexer()
355    }
356
357    // Start http server.
358    if *verbose {
359        log.Println("starting HTTP server")
360    }
361    if err := http.ListenAndServe(*httpAddrhandler); err != nil {
362        log.Fatalf("ListenAndServe %s: %v", *httpAddrerr)
363    }
364}
365
366// goMod returns the go env GOMOD value in the current directory
367// by invoking the go command.
368//
369// GOMOD is documented at https://golang.org/cmd/go/#hdr-Environment_variables:
370//
371//    The absolute path to the go.mod of the main module,
372//    or the empty string if not using modules.
373func goMod() (stringerror) {
374    outerr := exec.Command("go""env""-json""GOMOD").Output()
375    if ee := (*exec.ExitError)(nil); errors.As(err, &ee) {
376        return ""fmt.Errorf("go command exited unsuccessfully: %v\n%s"ee.ProcessState.String(), ee.Stderr)
377    } else if err != nil {
378        return ""err
379    }
380    var env struct {
381        GoMod string
382    }
383    err = json.Unmarshal(out, &env)
384    if err != nil {
385        return ""err
386    }
387    return env.GoModnil
388}
389
390// fillModuleCache does a best-effort attempt to fill the module cache
391// with all dependencies of the main module in the current directory
392// by invoking the go command. Module download logs are streamed to w.
393// If there are any problems encountered, they are also written to w.
394// It should only be used in module mode, when vendor mode isn't on.
395//
396// See https://golang.org/cmd/go/#hdr-Download_modules_to_local_cache.
397func fillModuleCache(w io.WritergoMod string) {
398    if goMod == os.DevNull {
399        // No module requirements, nothing to do.
400        return
401    }
402
403    cmd := exec.Command("go""mod""download""-json")
404    var out bytes.Buffer
405    cmd.Stdout = &out
406    cmd.Stderr = w
407    err := cmd.Run()
408    if ee := (*exec.ExitError)(nil); errors.As(err, &ee) && ee.ExitCode() == 1 {
409        // Exit code 1 from this command means there were some
410        // non-empty Error values in the output. Print them to w.
411        fmt.Fprintf(w"documentation for some packages is not shown:\n")
412        for dec := json.NewDecoder(&out); ; {
413            var m struct {
414                Path    string // Module path.
415                Version string // Module version.
416                Error   string // Error loading module.
417            }
418            err := dec.Decode(&m)
419            if err == io.EOF {
420                break
421            } else if err != nil {
422                fmt.Fprintf(w"error decoding JSON object from go mod download -json: %v\n"err)
423                continue
424            }
425            if m.Error == "" {
426                continue
427            }
428            fmt.Fprintf(w"\tmodule %s@%s is not in the module cache and there was a problem downloading it: %s\n"m.Pathm.Versionm.Error)
429        }
430    } else if err != nil {
431        fmt.Fprintf(w"there was a problem filling module cache: %v\n"err)
432    }
433}
434
435type mod struct {
436    Path string // Module path.
437    Dir  string // Directory holding files for this module, if any.
438}
439
440// buildList determines the build list in the current directory
441// by invoking the go command. It should only be used in module mode,
442// when vendor mode isn't on.
443//
444// See https://golang.org/cmd/go/#hdr-The_main_module_and_the_build_list.
445func buildList(goMod string) ([]moderror) {
446    if goMod == os.DevNull {
447        // Empty build list.
448        return nilnil
449    }
450
451    outerr := exec.Command("go""list""-m""-json""all").Output()
452    if ee := (*exec.ExitError)(nil); errors.As(err, &ee) {
453        return nilfmt.Errorf("go command exited unsuccessfully: %v\n%s"ee.ProcessState.String(), ee.Stderr)
454    } else if err != nil {
455        return nilerr
456    }
457    var mods []mod
458    for dec := json.NewDecoder(bytes.NewReader(out)); ; {
459        var m mod
460        err := dec.Decode(&m)
461        if err == io.EOF {
462            break
463        } else if err != nil {
464            return nilerr
465        }
466        mods = append(modsm)
467    }
468    return modsnil
469}
470
471// moduleFS is a vfs.FileSystem wrapper used when godoc is running
472// in module mode. It's needed so that packages inside modules are
473// considered to be third party.
474//
475// It overrides the RootType method of the underlying filesystem
476// and implements it using a heuristic based on the import path.
477// If the first element of the import path does not contain a dot,
478// that package is considered to be inside GOROOT. If it contains
479// a dot, then that package is considered to be third party.
480//
481// TODO(dmitshur): The RootType abstraction works well when GOPATH
482// workspaces are bound at their roots, but scales poorly in the
483// general case. It should be replaced by a more direct solution
484// for determining whether a package is third party or not.
485type moduleFS struct{ vfs.FileSystem }
486
487func (moduleFSRootType(path stringvfs.RootType {
488    if !strings.HasPrefix(path"/src/") {
489        return ""
490    }
491    domain := path[len("/src/"):]
492    if i := strings.Index(domain"/"); i >= 0 {
493        domain = domain[:i]
494    }
495    if !strings.Contains(domain".") {
496        // No dot in the first element of import path
497        // suggests this is a package in GOROOT.
498        return vfs.RootTypeGoRoot
499    } else {
500        // A dot in the first element of import path
501        // suggests this is a third party package.
502        return vfs.RootTypeGoPath
503    }
504}
505func (fs moduleFSString() string { return "module(" + fs.FileSystem.String() + ")" }
506
MembersX
httpResponseRecorder.Header
loggingHandler
main.BlockStmt.BlockStmt.RangeStmt_7899.m
main.corpus
buildList.out
errors
gocommand
httpResponseRecorder.WriteHeader
main
main.BlockStmt.vendorEnabled
fillModuleCache.cmd
buildList.mods
path
httpResponseRecorder.Write.w
handleURLFlag.i
handleURLFlag.BlockStmt.err
main.BlockStmt.f
buildList.ee
buildList.BlockStmt.err
main.fsGate
main.BlockStmt.RangeStmt_8231.p
main.BlockStmt._
fillModuleCache.ee
flag
url
httpResponseRecorder
handleURLFlag.BlockStmt.req
initCorpus.err
fillModuleCache.BlockStmt.dec
moduleFS.String.fs
mapfs
httpResponseRecorder.body
goMod.ee
fillModuleCache.w
fillModuleCache.goMod
fillModuleCache.out
buildList
httpResponseRecorder.code
httpResponseRecorder.Header.w
main.err
buildList.err
moduleFS.RootType.path
usage
main.BlockStmt.mainModVendor
main.handler
moduleFS.RootType
zip
main.BlockStmt.rc
fillModuleCache
fillModuleCache.BlockStmt.BlockStmt.err
buildList.BlockStmt.m
defaultAddr
httpResponseRecorder.WriteHeader.w
httpResponseRecorder.WriteHeader.code
main.BlockStmt.BlockStmt.RangeStmt_7899.BlockStmt.dst
main.BlockStmt.index
httpResponseRecorder.header
loggingHandler.h
main.BlockStmt.BlockStmt.err
goMod.err
mod.Path
httpResponseRecorder.Write
httpResponseRecorder.Write.b
initCorpus
main.BlockStmt.rootfs
main.goModFile
buildList.dec
moduleFS.String
static
gatefs
handleURLFlag
handleURLFlag.urlstr
handleURLFlag.BlockStmt.u
main.BlockStmt.err
mod.Dir
zipfs
handleURLFlag.BlockStmt.w
handleURLFlag.BlockStmt.BlockStmt.redirect
main.BlockStmt.BlockStmt.vendorDir
goMod.out
goMod
moduleFS
io
initCorpus.corpus
main.BlockStmt.BlockStmt.mods
fillModuleCache.err
mod
buildList.goMod
moduleFS.RootType.i
Members
X