blob: dc344cbbcac4ccae742984a988e6198df902748d [file] [log] [blame] [edit]
// Copyright 2025 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 !cmd_go_bootstrap
package doc
import (
"errors"
"fmt"
"net"
"net/url"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
)
// 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 doPkgsite(urlPath, fragment string) error {
port, err := pickUnusedPort()
if err != nil {
return fmt.Errorf("failed to find port for documentation server: %v", err)
}
addr := fmt.Sprintf("localhost:%d", port)
path, err := url.JoinPath("http://"+addr, urlPath)
if err != nil {
return fmt.Errorf("internal error: failed to construct url: %v", err)
}
if fragment != "" {
path += "#" + fragment
}
// 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...)
// Prepend the local download cache to GOPROXY to get around deprecation checks.
env := os.Environ()
vars, err := runCmd(env, goCmd(), "env", "GOPROXY", "GOMODCACHE")
fields := strings.Fields(vars)
if err == nil && len(fields) == 2 {
goproxy, gomodcache := fields[0], fields[1]
gomodcache = filepath.Join(gomodcache, "cache", "download")
// Convert absolute path to file URL. pkgsite will not accept
// Windows absolute paths because they look like a host:path remote.
// TODO(golang.org/issue/32456): use url.FromFilePath when implemented.
if strings.HasPrefix(gomodcache, "/") {
gomodcache = "file://" + gomodcache
} else {
gomodcache = "file:///" + filepath.ToSlash(gomodcache)
}
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", buildCtx.GOROOT,
"-http", addr,
"-open", path)
cmd.Env = env
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
if ee, ok := errors.AsType[*exec.ExitError](err); ok {
// 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
}
return nil
}