cmd/doc: use golang.org/x/pkgsite/cmd/internal/doc to start server

This change switches the pkgsite command invoked to start a pkgsite
server from golang.org/x/pkgsite/cmd/pkgsite to
golang.org/x/pkgsite/cmd/internal/doc. The doc command is a simplified
version of cmd/pkgsite that changes some options to improve the user
experience. For example, it limits logging informational log messages,
doesn't always expect to find modules (for example if we're outside of a
module getting documentation for the standard library), and it takes the
address of the page to open in the browser (which simplifies waiting for
the server to start listening).

Fixes #68106

Change-Id: I667a49d03823242fa1aff333ecb1c0f198e92412
Reviewed-on: https://go-review.googlesource.com/c/go/+/674158
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
Michael Matloob 2025-05-19 15:31:37 -04:00
parent 546761aff4
commit 609197b406

View file

@ -44,7 +44,6 @@ package main
import (
"bytes"
"context"
"errors"
"flag"
"fmt"
@ -53,17 +52,13 @@ import (
"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"
)
@ -203,36 +198,14 @@ func listUserPath(userPath string) (string, error) {
}
func doPkgsite(userPath string, 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.
// Assemble url to open on the browser, to point to documentation of
// the requested object.
importPath := pkg.build.ImportPath
if importPath == "." {
// go/build couldn't determine the import path, probably
@ -251,16 +224,33 @@ func doPkgsite(userPath string, pkg *Package, symbol, method string) error {
if object != "" {
path = path + "#" + object
}
if ok := browser.Open(path); !ok {
cmd.Cancel()
cmd.Wait()
return errors.New("failed to open browser")
// 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...)
const version = "v0.0.0-20250520201116-40659211760d"
cmd := exec.Command("go", "run", "golang.org/x/pkgsite/cmd/internal/doc@"+version,
"-gorepo", buildCtx.GOROOT,
"-http", addr,
"-open", path)
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
var ee *exec.ExitError
if errors.As(err, &ee) {
// Exit with the same exit status as pkgsite to avoid
// printing of "exit status" error messages.
// Any relevant messages have already been printed
// to stdout or stderr.
os.Exit(ee.ExitCode())
}
return err
}
// 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()
return nil
}
// pickUnusedPort finds an unused port by trying to listen on port 0
@ -279,24 +269,6 @@ func pickUnusedPort() (int, error) {
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