aboutsummaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
authorMichael Matloob <matloob@golang.org>2024-09-03 11:19:39 -0400
committerMichael Matloob <matloob@golang.org>2025-02-03 09:26:03 -0800
commit66e6f5c9202ae98c3e1d3830972cd13559fb28f2 (patch)
treec0d0d01cada6cb5944d2b57f22db070446f754ea /src/cmd
parent39ceaf79614ac7a8874c086ec3c012464f710aca (diff)
downloadgo-66e6f5c9202ae98c3e1d3830972cd13559fb28f2.tar.xz
cmd/doc: add support for starting pkgsite instance for docs
This change adds a new flag "-http" to cmd/doc which enables starting a pkgsite instance. -http will start a pkgsite instance and navigate to the page for the requested package, at the anchor for the item requested. For #68106 Change-Id: Ic1c113795cb2e1035e99c89c8e972c799342385b Reviewed-on: https://go-review.googlesource.com/c/go/+/628175 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Sam Thanawalla <samthanawalla@google.com> Reviewed-by: Jonathan Amsterdam <jba@google.com> Reviewed-by: Alan Donovan <adonovan@google.com>
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/doc/main.go99
-rw-r--r--src/cmd/doc/signal_notunix.go13
-rw-r--r--src/cmd/doc/signal_unix.go14
3 files changed, 126 insertions, 0 deletions
diff --git a/src/cmd/doc/main.go b/src/cmd/doc/main.go
index 502de097f5..a199991c21 100644
--- a/src/cmd/doc/main.go
+++ b/src/cmd/doc/main.go
@@ -44,17 +44,26 @@ package main
import (
"bytes"
+ "context"
+ "errors"
"flag"
"fmt"
"go/build"
"go/token"
"io"
"log"
+ "net"
+ "net/http"
"os"
+ "os/exec"
+ "os/signal"
"path"
"path/filepath"
"strings"
+ "time"
+ "cmd/internal/browser"
+ "cmd/internal/quoted"
"cmd/internal/telemetry/counter"
)
@@ -66,6 +75,7 @@ var (
showCmd bool // -cmd flag
showSrc bool // -src flag
short bool // -short flag
+ serveHTTP bool // -http flag
)
// usage is a replacement usage function for the flags package.
@@ -107,6 +117,7 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) {
flagSet.BoolVar(&showCmd, "cmd", false, "show symbols with package docs even if package is a command")
flagSet.BoolVar(&showSrc, "src", false, "show source code for symbol")
flagSet.BoolVar(&short, "short", false, "one-line representation for each symbol")
+ flagSet.BoolVar(&serveHTTP, "http", false, "serve HTML docs over HTTP")
flagSet.Parse(args)
counter.Inc("doc/invocations")
counter.CountFlags("doc/flag:", *flag.CommandLine)
@@ -152,6 +163,9 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) {
panic(e)
}()
+ if serveHTTP {
+ return doPkgsite(pkg, symbol, method)
+ }
switch {
case symbol == "":
pkg.packageDoc() // The package exists, so we got some output.
@@ -168,6 +182,91 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) {
}
}
+func doPkgsite(pkg *Package, symbol, method string) error {
+ ctx := context.Background()
+
+ cmdline := "go run golang.org/x/pkgsite/cmd/pkgsite@latest -gorepo=" + buildCtx.GOROOT
+ words, err := quoted.Split(cmdline)
+ port, err := pickUnusedPort()
+ if err != nil {
+ return fmt.Errorf("failed to find port for documentation server: %v", err)
+ }
+ addr := fmt.Sprintf("localhost:%d", port)
+ words = append(words, fmt.Sprintf("-http=%s", addr))
+ cmd := exec.CommandContext(context.Background(), words[0], words[1:]...)
+ cmd.Stdout = os.Stderr
+ cmd.Stderr = os.Stderr
+ // Turn off the default signal handler for SIGINT (and SIGQUIT on Unix)
+ // and instead wait for the child process to handle the signal and
+ // exit before exiting ourselves.
+ signal.Ignore(signalsToIgnore...)
+
+ if err := cmd.Start(); err != nil {
+ return fmt.Errorf("starting pkgsite: %v", err)
+ }
+
+ // Wait for pkgsite to became available.
+ if !waitAvailable(ctx, addr) {
+ cmd.Cancel()
+ cmd.Wait()
+ return errors.New("could not connect to local documentation server")
+ }
+
+ // Open web browser.
+ path := path.Join("http://"+addr, pkg.build.ImportPath)
+ object := symbol
+ if symbol != "" && method != "" {
+ object = symbol + "." + method
+ }
+ if object != "" {
+ path = path + "#" + object
+ }
+ if ok := browser.Open(path); !ok {
+ cmd.Cancel()
+ cmd.Wait()
+ return errors.New("failed to open browser")
+ }
+
+ // Wait for child to terminate. We expect the child process to receive signals from
+ // this terminal and terminate in a timely manner, so this process will terminate
+ // soon after.
+ return cmd.Wait()
+}
+
+// pickUnusedPort finds an unused port by trying to listen on port 0
+// and letting the OS pick a port, then closing that connection and
+// returning that port number.
+// This is inherently racy.
+func pickUnusedPort() (int, error) {
+ l, err := net.Listen("tcp", "localhost:0")
+ if err != nil {
+ return 0, err
+ }
+ port := l.Addr().(*net.TCPAddr).Port
+ if err := l.Close(); err != nil {
+ return 0, err
+ }
+ return port, nil
+}
+
+func waitAvailable(ctx context.Context, addr string) bool {
+ ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
+ defer cancel()
+ for ctx.Err() == nil {
+ req, err := http.NewRequestWithContext(ctx, "HEAD", "http://"+addr, nil)
+ if err != nil {
+ log.Println(err)
+ return false
+ }
+ resp, err := http.DefaultClient.Do(req)
+ if err == nil {
+ resp.Body.Close()
+ return true
+ }
+ }
+ return false
+}
+
// failMessage creates a nicely formatted error message when there is no result to show.
func failMessage(paths []string, symbol, method string) error {
var b bytes.Buffer
diff --git a/src/cmd/doc/signal_notunix.go b/src/cmd/doc/signal_notunix.go
new file mode 100644
index 0000000000..3b8fa9e080
--- /dev/null
+++ b/src/cmd/doc/signal_notunix.go
@@ -0,0 +1,13 @@
+// Copyright 2012 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.
+
+//go:build plan9 || windows
+
+package main
+
+import (
+ "os"
+)
+
+var signalsToIgnore = []os.Signal{os.Interrupt}
diff --git a/src/cmd/doc/signal_unix.go b/src/cmd/doc/signal_unix.go
new file mode 100644
index 0000000000..52431c221b
--- /dev/null
+++ b/src/cmd/doc/signal_unix.go
@@ -0,0 +1,14 @@
+// Copyright 2012 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.
+
+//go:build unix || js || wasip1
+
+package main
+
+import (
+ "os"
+ "syscall"
+)
+
+var signalsToIgnore = []os.Signal{os.Interrupt, syscall.SIGQUIT}