1
2
3
4
5
6
7 package doc
8
9 import (
10 "context"
11 "errors"
12 "fmt"
13 "net"
14 "net/url"
15 "os"
16 "os/exec"
17 "path/filepath"
18 "strings"
19
20 "cmd/go/internal/base"
21 "cmd/go/internal/cfg"
22 "cmd/go/internal/load"
23 "cmd/go/internal/modload"
24 "cmd/go/internal/work"
25 )
26
27
28
29
30
31
32
33
34
35 const pkgsiteCmdInternalDocVersion = "v0.0.0-20260722200430-7c7eca789259"
36
37
38
39
40
41 func pickUnusedPort() (int, error) {
42 l, err := net.Listen("tcp", "localhost:0")
43 if err != nil {
44 return 0, err
45 }
46 port := l.Addr().(*net.TCPAddr).Port
47 if err := l.Close(); err != nil {
48 return 0, err
49 }
50 return port, nil
51 }
52
53
54 func buildPkgsite(ctx context.Context) string {
55 load.ClearPackageCache()
56 loader := modload.NewLoader()
57
58
59 loader.ForceUseModules = true
60 loader.RootMode = modload.NoRoot
61 loader.AllowMissingModuleImports()
62 modload.Init(loader)
63
64 work.BuildInit(loader)
65 b := work.NewBuilder("", loader.VendorDirOrEmpty)
66 defer func() {
67 if err := b.Close(); err != nil {
68 base.Fatal(err)
69 }
70 }()
71
72 version := pkgsiteCmdInternalDocVersion
73 if os.Getenv("TEST_GODOC_BUILD_ONLY") != "" {
74 version = "v0.1.0"
75 }
76 pkgVers := "golang.org/x/pkgsite/cmd/internal/doc@" + version
77 pkgOpts := load.PackageOpts{MainOnly: true}
78 pkgs, err := load.PackagesAndErrorsOutsideModule(loader, ctx, pkgOpts, []string{pkgVers})
79 if err != nil {
80 base.Fatal(err)
81 }
82 if len(pkgs) == 0 {
83 base.Fatalf("go: internal error: no packages loaded for %s", pkgVers)
84 }
85 if len(pkgs) > 1 {
86 base.Fatalf("go: internal error: pattern %s matches multiple packages", pkgVers)
87 }
88 p := pkgs[0]
89 p.Internal.OmitDebug = true
90 p.Internal.ExeName = p.DefaultExecName()
91 load.CheckPackageErrors([]*load.Package{p})
92
93 a := b.LinkAction(loader, work.ModeBuild, work.ModeBuild, p)
94 a.CacheExecutable = true
95 b.Do(ctx, a)
96
97
98
99 if cached := a.CachedExecutable(); cached != "" {
100 return cached
101 }
102 return a.BuiltTarget()
103 }
104
105 func doPkgsite(ctx context.Context, urlPath, fragment string) error {
106 port, err := pickUnusedPort()
107 if err != nil {
108 return fmt.Errorf("failed to find port for documentation server: %v", err)
109 }
110 addr := fmt.Sprintf("localhost:%d", port)
111 path, err := url.JoinPath("http://"+addr, urlPath)
112 if err != nil {
113 return fmt.Errorf("internal error: failed to construct url: %v", err)
114 }
115 if fragment != "" {
116 path += "#" + fragment
117 }
118
119 if file := os.Getenv("TEST_GODOC_URL_FILE"); file != "" {
120 return os.WriteFile(file, []byte(path+"\n"), 0666)
121 }
122
123
124
125
126 base.StartSigHandlers()
127
128
129 env := os.Environ()
130 vars, err := runCmd(env, goCmd(), "env", "GOPROXY", "GOMODCACHE")
131 fields := strings.Fields(vars)
132 if err == nil && len(fields) == 2 {
133 goproxy, gomodcache := fields[0], fields[1]
134 gomodcache = filepath.Join(gomodcache, "cache", "download")
135
136
137
138 if strings.HasPrefix(gomodcache, "/") {
139 gomodcache = "file://" + gomodcache
140 } else {
141 gomodcache = "file:///" + filepath.ToSlash(gomodcache)
142 }
143 env = append(env, "GOPROXY="+gomodcache+","+goproxy)
144 }
145
146 pkgsite := buildPkgsite(ctx)
147 if os.Getenv("TEST_GODOC_BUILD_ONLY") != "" {
148 if _, err := os.Stat(pkgsite); err != nil {
149 return fmt.Errorf("built pkgsite binary does not exist: %w", err)
150 }
151 return nil
152 }
153 cmd := exec.Command(pkgsite, "-gorepo", cfg.GOROOT, "-http", addr, "-open", path)
154 cmd.Env = env
155 cmd.Stdout = os.Stderr
156 cmd.Stderr = os.Stderr
157
158 if err := cmd.Run(); err != nil {
159 if ee, ok := errors.AsType[*exec.ExitError](err); ok {
160
161
162
163
164 os.Exit(ee.ExitCode())
165 }
166 return err
167 }
168
169 return nil
170 }
171
View as plain text