2013-07-16 22:02:35 -06:00
// 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:
//
2019-11-18 14:16:45 -07:00
// http://godoc/ redirect to /pkg/
2013-07-16 22:02:35 -06:00
// 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"
2018-10-10 21:16:23 -06:00
"bytes"
2019-09-23 16:16:04 -06:00
"encoding/json"
2013-07-16 22:02:35 -06:00
_ "expvar" // to serve /debug/vars
"flag"
"fmt"
"go/build"
2019-09-23 16:16:04 -06:00
"io"
2013-07-16 22:02:35 -06:00
"log"
"net/http"
_ "net/http/pprof" // to serve /debug/pprof/*
"net/url"
"os"
2019-09-23 16:16:04 -06:00
"os/exec"
"path"
2013-07-16 22:02:35 -06:00
"path/filepath"
"regexp"
"runtime"
2014-03-14 16:58:22 -06:00
"strings"
2013-07-16 23:02:27 -06:00
2014-11-09 14:50:40 -07:00
"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"
2019-09-23 16:16:04 -06:00
"golang.org/x/xerrors"
2013-07-16 22:02:35 -06:00
)
2018-10-10 20:21:09 -06:00
const defaultAddr = "localhost:6060" // default webserver address
2013-07-16 22:02:35 -06:00
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" )
2019-09-23 16:16:04 -06:00
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 ` )
2014-03-14 16:58:22 -06:00
2013-07-16 22:02:35 -06:00
// network
2018-10-10 20:21:09 -06:00
httpAddr = flag . String ( "http" , defaultAddr , "HTTP service address" )
2013-07-16 22:02:35 -06:00
// layout control
urlFlag = flag . String ( "url" , "" , "print HTML for named URL" )
2013-07-17 21:51:17 -06:00
verbose = flag . Bool ( "v" , false , "verbose mode" )
// file system roots
// TODO(gri) consider the invariant that goroot always end in '/'
2018-06-11 17:34:51 -06:00
goroot = flag . String ( "goroot" , findGOROOT ( ) , "Go root directory" )
2013-07-17 21:51:17 -06:00
// layout control
showTimestamps = flag . Bool ( "timestamps" , false , "show timestamps with directory listings" )
2017-01-07 16:09:41 -07:00
templateDir = flag . String ( "templates" , "" , "load templates/JS/CSS from disk in this directory" )
2018-10-18 01:21:06 -06:00
showPlayground = flag . Bool ( "play" , false , "enable playground" )
2013-07-17 21:51:17 -06:00
declLinks = flag . Bool ( "links" , true , "link identifiers to their declarations" )
// search index
2014-12-19 12:05:29 -07:00
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" )
2015-03-06 04:24:21 -07:00
indexInterval = flag . Duration ( "index_interval" , 0 , "interval of indexing; 0 for default (5m), negative to only index once at startup" )
2013-07-17 21:51:17 -06:00
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" )
2013-07-16 22:02:35 -06:00
)
2018-10-10 21:16:23 -06:00
// 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 }
2019-02-15 11:30:09 -07:00
func ( w * httpResponseRecorder ) Write ( b [ ] byte ) ( int , error ) { return w . body . Write ( b ) }
2018-10-10 21:16:23 -06:00
func ( w * httpResponseRecorder ) WriteHeader ( code int ) { w . code = code }
2013-07-16 22:02:35 -06:00
func usage ( ) {
2018-10-10 20:21:09 -06:00
fmt . Fprintf ( os . Stderr , "usage: godoc -http=" + defaultAddr + "\n" )
2013-07-16 22:02:35 -06:00
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 )
} )
}
2013-07-17 21:51:17 -06:00
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.
2018-10-10 21:16:23 -06:00
w := & httpResponseRecorder { code : 200 , header : make ( http . Header ) , body : new ( bytes . Buffer ) }
2013-07-17 21:51:17 -06:00
http . DefaultServeMux . ServeHTTP ( w , req )
// Return data, error, or follow redirect.
2018-10-10 21:16:23 -06:00
switch w . code {
2013-07-17 21:51:17 -06:00
case 200 : // ok
2018-10-10 21:16:23 -06:00
os . Stdout . Write ( w . body . Bytes ( ) )
2013-07-17 21:51:17 -06:00
return
case 301 , 302 , 303 , 307 : // redirect
2018-10-10 21:16:23 -06:00
redirect := w . header . Get ( "Location" )
2013-07-17 21:51:17 -06:00
if redirect == "" {
2018-10-10 21:16:23 -06:00
log . Fatalf ( "HTTP %d without Location header" , w . code )
2013-07-17 21:51:17 -06:00
}
urlstr = redirect
default :
2018-10-10 21:16:23 -06:00
log . Fatalf ( "HTTP error %d" , w . code )
2013-07-17 21:51:17 -06:00
}
}
log . Fatalf ( "too many redirects" )
}
2018-02-09 21:29:54 -07:00
func initCorpus ( corpus * godoc . Corpus ) {
err := corpus . Init ( )
if err != nil {
log . Fatal ( err )
}
}
2013-07-16 22:02:35 -06:00
func main ( ) {
flag . Usage = usage
flag . Parse ( )
2018-12-12 17:26:57 -07:00
// Check usage.
if flag . NArg ( ) > 0 {
2018-12-19 18:12:42 -07:00
fmt . Fprintln ( os . Stderr , ` Unexpected arguments. Use "go doc" for command-line help output instead. For example, "go doc fmt.Printf". ` )
2017-02-06 17:07:17 -07:00
usage ( )
}
2019-02-15 11:30:09 -07:00
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." )
2013-07-16 22:02:35 -06:00
usage ( )
}
2018-10-10 20:21:09 -06:00
// Set the resolved goroot.
2018-08-22 23:24:26 -06:00
vfs . GOROOT = * goroot
2018-10-10 20:21:09 -06:00
fsGate := make ( chan bool , 20 )
2013-12-16 12:25:50 -07:00
2013-07-16 22:02:35 -06:00
// Determine file system to use.
if * zipfile == "" {
// use file system of underlying OS
2014-09-10 07:02:54 -06:00
rootfs := gatefs . New ( vfs . OS ( * goroot ) , fsGate )
fs . Bind ( "/" , rootfs , "/" , vfs . BindReplace )
2013-07-16 22:02:35 -06:00
} 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)
2013-07-17 21:51:17 -06:00
fs . Bind ( "/" , zipfs . New ( rc , * zipfile ) , * goroot , vfs . BindReplace )
2013-07-16 22:02:35 -06:00
}
2013-10-02 22:29:16 -06:00
if * templateDir != "" {
fs . Bind ( "/lib/godoc" , vfs . OS ( * templateDir ) , "/" , vfs . BindBefore )
} else {
fs . Bind ( "/lib/godoc" , mapfs . New ( static . Files ) , "/" , vfs . BindReplace )
}
2013-07-16 22:02:35 -06:00
2019-09-23 16:16:04 -06:00
// 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.
2019-11-13 08:39:05 -07:00
fillModuleCache ( os . Stderr , goModFile )
2019-09-23 16:16:04 -06:00
// Determine modules in the build list.
2019-11-20 21:33:35 -07:00
mods , err := buildList ( goModFile )
2019-09-23 16:16:04 -06:00
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 )
}
2013-07-16 22:02:35 -06:00
}
2014-03-14 16:58:22 -06:00
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 )
}
}
}
2019-09-23 16:16:04 -06:00
var corpus * godoc . Corpus
if goModFile != "" {
corpus = godoc . NewCorpus ( moduleFS { fs } )
} else {
corpus = godoc . NewCorpus ( fs )
}
2013-07-18 18:27:53 -06:00
corpus . Verbose = * verbose
2013-11-21 09:55:42 -07:00
corpus . MaxResults = * maxResults
2018-10-10 20:21:09 -06:00
corpus . IndexEnabled = * indexEnabled
2013-11-21 09:55:42 -07:00
if * maxResults == 0 {
corpus . IndexFullText = false
}
2013-07-17 17:52:45 -06:00
corpus . IndexFiles = * indexFiles
2019-11-18 14:16:45 -07:00
corpus . IndexDirectory = func ( dir string ) bool {
return dir != "/pkg" && ! strings . HasPrefix ( dir , "/pkg/" )
}
2013-08-27 16:16:31 -06:00
corpus . IndexThrottle = * indexThrottle
2015-03-06 04:24:21 -07:00
corpus . IndexInterval = * indexInterval
2019-02-15 11:30:09 -07:00
if * writeIndex || * urlFlag != "" {
2013-07-17 21:51:17 -06:00
corpus . IndexThrottle = 1.0
2014-06-13 00:49:32 -06:00
corpus . IndexEnabled = true
2018-10-11 16:56:43 -06:00
initCorpus ( corpus )
} else {
go initCorpus ( corpus )
2013-07-17 21:51:17 -06:00
}
2013-07-17 17:52:45 -06:00
2017-12-22 18:30:39 -07:00
// Initialize the version info before readTemplates, which saves
// the map value in a method value.
corpus . InitVersionInfo ( )
2013-07-17 17:52:45 -06:00
pres = godoc . NewPresentation ( corpus )
2013-07-17 21:14:09 -06:00
pres . ShowTimestamps = * showTimestamps
pres . ShowPlayground = * showPlayground
pres . DeclLinks = * declLinks
2013-07-17 21:51:17 -06:00
if * notesRx != "" {
pres . NotesRx = regexp . MustCompile ( * notesRx )
}
2013-07-17 21:14:09 -06:00
2018-10-10 20:21:09 -06:00
readTemplates ( pres )
2013-07-18 22:02:03 -06:00
registerHandlers ( pres )
2013-07-17 21:14:09 -06:00
2013-07-16 22:02:35 -06:00
if * writeIndex {
// Write search index and exit.
2013-07-17 21:14:09 -06:00
if * indexFiles == "" {
2013-07-16 22:02:35 -06:00
log . Fatal ( "no index file specified" )
}
log . Println ( "initialize file systems" )
* verbose = true // want to see what happens
2013-07-17 21:14:09 -06:00
corpus . UpdateIndex ( )
2013-07-16 22:02:35 -06:00
2013-07-17 21:14:09 -06:00
log . Println ( "writing index file" , * indexFiles )
f , err := os . Create ( * indexFiles )
2013-07-16 22:02:35 -06:00
if err != nil {
log . Fatal ( err )
}
2013-07-17 21:14:09 -06:00
index , _ := corpus . CurrentIndex ( )
2013-11-12 16:46:25 -07:00
_ , err = index . WriteTo ( f )
2013-07-16 22:02:35 -06:00
if err != nil {
log . Fatal ( err )
}
log . Println ( "done" )
return
}
// Print content that would be served at the URL *urlFlag.
if * urlFlag != "" {
2013-07-17 21:51:17 -06:00
handleURLFlag ( )
return
2013-07-16 22:02:35 -06:00
}
2018-10-10 20:21:09 -06:00
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" )
2016-11-30 16:15:23 -07:00
}
2018-10-10 20:21:09 -06:00
fs . Fprint ( os . Stderr )
handler = loggingHandler ( handler )
}
2016-11-30 16:15:23 -07:00
2018-10-10 20:21:09 -06:00
// Initialize search index.
if * indexEnabled {
go corpus . RunIndexer ( )
}
2013-07-16 22:02:35 -06:00
2018-10-10 20:21:09 -06:00
// Start type/pointer analysis.
if typeAnalysis || pointerAnalysis {
go analysis . Run ( pointerAnalysis , & corpus . Analysis )
2013-07-16 22:02:35 -06:00
}
2018-10-10 20:21:09 -06:00
// 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 )
2013-07-16 22:02:35 -06:00
}
}
2016-11-30 16:15:23 -07:00
2019-09-23 16:16:04 -06:00
// 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.
2019-11-13 08:39:05 -07:00
func fillModuleCache ( w io . Writer , goMod string ) {
if goMod == os . DevNull {
// No module requirements, nothing to do.
return
}
2019-09-23 16:16:04 -06:00
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.
2019-11-20 21:33:35 -07:00
func buildList ( goMod string ) ( [ ] mod , error ) {
if goMod == os . DevNull {
// Empty build list.
return nil , nil
}
2019-09-23 16:16:04 -06:00
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 ( ) + ")" }