From 28c56bb5fb438d46d921a37f2e172ecf198f75c2 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Tue, 7 Apr 2026 17:15:10 -0400 Subject: cmd/go: build pkgsite doc command in same go command invocation Instead of running go run. This is enabled by Ian Alexander's work to remove global state in the loader package. This also fixes an issue where we can't build and run the pkgsite binary because, with our GOPROXY setting, if pkgsite module is available in the module cache at the requested version, but the pkgsite/cmd/internal/doc module isn't we'll give up trying to fetch the doc module. Now that the build and execution are separated, we can make sure the GOPROXY setting is only supplied to the running pkgsite binary, but not to the operations that build it. Fixes #78457 Change-Id: Id2a754fee12b68240243773f81e7d00b6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/763760 Reviewed-by: Michael Matloob Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI Auto-Submit: Michael Matloob Reviewed-by: Dmitri Shuralyov --- src/cmd/go/internal/doc/doc.go | 8 ++-- src/cmd/go/internal/doc/pkgsite.go | 59 ++++++++++++++++++++++++---- src/cmd/go/internal/doc/pkgsite_bootstrap.go | 4 +- 3 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/cmd/go/internal/doc/doc.go b/src/cmd/go/internal/doc/doc.go index 4376e3344e..d961c2a215 100644 --- a/src/cmd/go/internal/doc/doc.go +++ b/src/cmd/go/internal/doc/doc.go @@ -222,16 +222,16 @@ func do(ctx context.Context, writer io.Writer, flagSet *flag.FlagSet, args []str mod, err := runCmd(append(os.Environ(), "GOWORK=off"), "go", "list", "-m") if err == nil && mod != "" && mod != "command-line-arguments" { // If there's a module, go to the module's doc page. - return doPkgsite(mod, "") + return doPkgsite(ctx, mod, "") } gowork, err := runCmd(nil, "go", "env", "GOWORK") if err == nil && gowork != "" { // Outside a module, but in a workspace, go to the home page // with links to each of the modules' pages. - return doPkgsite("", "") + return doPkgsite(ctx, "", "") } // Outside a module or workspace, go to the documentation for the standard library. - return doPkgsite("std", "") + return doPkgsite(ctx, "std", "") } // If args are provided, we need to figure out which page to open on the pkgsite @@ -296,7 +296,7 @@ func do(ctx context.Context, writer io.Writer, flagSet *flag.FlagSet, args []str if err != nil { return err } - return doPkgsite(path, fragment) + return doPkgsite(ctx, path, fragment) } return nil } diff --git a/src/cmd/go/internal/doc/pkgsite.go b/src/cmd/go/internal/doc/pkgsite.go index 2c135cdc34..a2398ef397 100644 --- a/src/cmd/go/internal/doc/pkgsite.go +++ b/src/cmd/go/internal/doc/pkgsite.go @@ -7,17 +7,21 @@ package doc import ( + "context" "errors" "fmt" "net" "net/url" "os" "os/exec" - "os/signal" "path/filepath" "strings" + "cmd/go/internal/base" "cmd/go/internal/cfg" + "cmd/go/internal/load" + "cmd/go/internal/modload" + "cmd/go/internal/work" ) // pickUnusedPort finds an unused port by trying to listen on port 0 @@ -36,7 +40,49 @@ func pickUnusedPort() (int, error) { return port, nil } -func doPkgsite(urlPath, fragment string) error { +// buildPkgsite builds a pkgsite binary whose build may be cached. +func buildPkgsite(ctx context.Context) string { + loader := modload.NewLoader() + + // Set the builder to have no module root so we can build a pkg@version pattern. + loader.ForceUseModules = true + loader.RootMode = modload.NoRoot + loader.AllowMissingModuleImports() + modload.Init(loader) + + work.BuildInit(loader) + b := work.NewBuilder("", loader.VendorDirOrEmpty) + defer func() { + if err := b.Close(); err != nil { + base.Fatal(err) + } + }() + + const version = "v0.0.0-20251223195805-1a3bd3c788fe" + pkgVers := "golang.org/x/pkgsite/cmd/internal/doc@" + version + pkgOpts := load.PackageOpts{MainOnly: true} + pkgs, err := load.PackagesAndErrorsOutsideModule(loader, ctx, pkgOpts, []string{pkgVers}) + if err != nil { + base.Fatal(err) + } + if len(pkgs) == 0 { + base.Fatalf("go: internal error: no packages loaded for %s", pkgVers) + } + if len(pkgs) > 1 { + base.Fatalf("go: internal error: pattern %s matches multiple packages", pkgVers) + } + p := pkgs[0] + p.Internal.OmitDebug = true + p.Internal.ExeName = p.DefaultExecName() + load.CheckPackageErrors([]*load.Package{p}) + + a := b.LinkAction(loader, work.ModeBuild, work.ModeBuild, p) + a.CacheExecutable = true + b.Do(ctx, a) + return a.BuiltTarget() +} + +func doPkgsite(ctx context.Context, urlPath, fragment string) error { port, err := pickUnusedPort() if err != nil { return fmt.Errorf("failed to find port for documentation server: %v", err) @@ -57,7 +103,7 @@ func doPkgsite(urlPath, fragment string) error { // 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...) + base.StartSigHandlers() // Prepend the local download cache to GOPROXY to get around deprecation checks. env := os.Environ() @@ -77,11 +123,8 @@ func doPkgsite(urlPath, fragment string) error { env = append(env, "GOPROXY="+gomodcache+","+goproxy) } - const version = "v0.0.0-20251223195805-1a3bd3c788fe" - cmd := exec.Command(goCmd(), "run", "golang.org/x/pkgsite/cmd/internal/doc@"+version, - "-gorepo", cfg.GOROOT, - "-http", addr, - "-open", path) + pkgsite := buildPkgsite(ctx) + cmd := exec.Command(pkgsite, "-gorepo", cfg.GOROOT, "-http", addr, "-open", path) cmd.Env = env cmd.Stdout = os.Stderr cmd.Stderr = os.Stderr diff --git a/src/cmd/go/internal/doc/pkgsite_bootstrap.go b/src/cmd/go/internal/doc/pkgsite_bootstrap.go index 3c9f546957..cb72492cb5 100644 --- a/src/cmd/go/internal/doc/pkgsite_bootstrap.go +++ b/src/cmd/go/internal/doc/pkgsite_bootstrap.go @@ -8,4 +8,6 @@ package doc -func doPkgsite(string, string) error { return nil } +import "context" + +func doPkgsite(context.Context, string, string) error { return nil } -- cgit v1.3