golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/buildgo/buildgo.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package buildgo provides tools for pushing and building the Go 6 // distribution on buildlets. 7 package buildgo 8 9 import ( 10 "archive/tar" 11 "bytes" 12 "compress/gzip" 13 "context" 14 "fmt" 15 "io" 16 "log" 17 "net/http" 18 "path" 19 "time" 20 21 "golang.org/x/build/buildenv" 22 "golang.org/x/build/buildlet" 23 "golang.org/x/build/dashboard" 24 "golang.org/x/build/internal/spanlog" 25 ) 26 27 // BuilderRev is a build configuration type and a revision. 28 type BuilderRev struct { 29 Name string // e.g. "linux-amd64-race" 30 Rev string // lowercase hex core repo git hash 31 32 // optional sub-repository details (both must be present) 33 SubName string // e.g. "net" 34 SubRev string // lowercase hex sub-repo git hash 35 } 36 37 func (br BuilderRev) IsSubrepo() bool { 38 return br.SubName != "" 39 } 40 41 func (br BuilderRev) SubRevOrGoRev() string { 42 if br.SubRev != "" { 43 return br.SubRev 44 } 45 return br.Rev 46 } 47 48 func (br BuilderRev) RepoOrGo() string { 49 if br.SubName == "" { 50 return "go" 51 } 52 return br.SubName 53 } 54 55 // SnapshotObjectName is the cloud storage object name of the 56 // built Go tree for this builder and Go rev (not the sub-repo). 57 // The entries inside this tarball do not begin with "go/". 58 func (br *BuilderRev) SnapshotObjectName() string { 59 return fmt.Sprintf("%v/%v/%v.tar.gz", "go", br.Name, br.Rev) 60 } 61 62 // SnapshotURL is the absolute URL of the snapshot object (see above). 63 func (br *BuilderRev) SnapshotURL(buildEnv *buildenv.Environment) string { 64 return buildEnv.SnapshotURL(br.Name, br.Rev) 65 } 66 67 var TestHookSnapshotExists func(*BuilderRev) bool 68 69 // SnapshotExists reports whether the snapshot exists in storage. 70 // It returns potentially false negatives on network errors. 71 // Callers must not depend on this as more than an optimization. 72 func (br *BuilderRev) SnapshotExists(ctx context.Context, buildEnv *buildenv.Environment) bool { 73 if f := TestHookSnapshotExists; f != nil { 74 return f(br) 75 } 76 req, err := http.NewRequest("HEAD", br.SnapshotURL(buildEnv), nil) 77 if err != nil { 78 panic(err) 79 } 80 ctx, cancel := context.WithTimeout(ctx, 10*time.Second) 81 defer cancel() 82 res, err := http.DefaultClient.Do(req.WithContext(ctx)) 83 if err != nil { 84 log.Printf("SnapshotExists check: %v", err) 85 return false 86 } 87 return res.StatusCode == http.StatusOK 88 } 89 90 // A GoBuilder knows how to build a revision of Go with the given configuration. 91 type GoBuilder struct { 92 spanlog.Logger 93 BuilderRev 94 Conf *dashboard.BuildConfig 95 // Goroot is a Unix-style path relative to the work directory of the 96 // builder (e.g. "go"). 97 Goroot string 98 // GorootBootstrap is an optional absolute Unix-style path to the 99 // bootstrap toolchain, overriding the default. 100 GorootBootstrap string 101 // Force controls whether to use the -force flag when building Go. 102 // See go.dev/issue/56679. 103 Force bool 104 } 105 106 // RunMake builds the tool chain. 107 // goroot is relative to the workdir with forward slashes. 108 // w is the Writer to send build output to. 109 // remoteErr and err are as described at the top of this file. 110 func (gb GoBuilder) RunMake(ctx context.Context, bc buildlet.Client, w io.Writer) (remoteErr, err error) { 111 // Build the source code. 112 makeSpan := gb.CreateSpan("make", gb.Conf.MakeScript()) 113 env := append(gb.Conf.Env(), "GOBIN=") 114 if gb.GorootBootstrap != "" { 115 env = append(env, "GOROOT_BOOTSTRAP="+gb.GorootBootstrap) 116 } 117 makeArgs := gb.Conf.MakeScriptArgs() 118 if gb.Force { 119 makeArgs = append(makeArgs, "-force") 120 } 121 remoteErr, err = bc.Exec(ctx, path.Join(gb.Goroot, gb.Conf.MakeScript()), buildlet.ExecOpts{ 122 Output: w, 123 ExtraEnv: env, 124 Debug: true, 125 Args: makeArgs, 126 }) 127 if err != nil { 128 makeSpan.Done(err) 129 return nil, err 130 } 131 if remoteErr != nil { 132 makeSpan.Done(remoteErr) 133 return fmt.Errorf("make script failed: %v", remoteErr), nil 134 } 135 makeSpan.Done(nil) 136 137 // Need to run "go install -race std" before the snapshot + tests. 138 if pkgs := gb.Conf.GoInstallRacePackages(); len(pkgs) > 0 { 139 sp := gb.CreateSpan("install_race_std") 140 remoteErr, err = bc.Exec(ctx, path.Join(gb.Goroot, "bin/go"), buildlet.ExecOpts{ 141 Output: w, 142 ExtraEnv: append(gb.Conf.Env(), "GOBIN="), 143 Debug: true, 144 Args: append([]string{"install", "-race"}, pkgs...), 145 }) 146 if err != nil { 147 sp.Done(err) 148 return nil, err 149 } 150 if remoteErr != nil { 151 sp.Done(err) 152 return fmt.Errorf("go install -race std failed: %v", remoteErr), nil 153 } 154 sp.Done(nil) 155 } 156 157 if gb.Name == "linux-amd64-racecompile" { 158 return gb.runConcurrentGoBuildStdCmd(ctx, bc, w) 159 } 160 161 return nil, nil 162 } 163 164 // runConcurrentGoBuildStdCmd is a step specific only to the 165 // "linux-amd64-racecompile" builder to exercise the Go 1.9's new 166 // concurrent compilation. It re-builds the standard library and tools 167 // with -gcflags=-c=8 using a race-enabled cmd/compile and cmd/link 168 // (built by caller, RunMake, per builder config). 169 // The idea is that this might find data races in cmd/compile and cmd/link. 170 func (gb GoBuilder) runConcurrentGoBuildStdCmd(ctx context.Context, bc buildlet.Client, w io.Writer) (remoteErr, err error) { 171 span := gb.CreateSpan("go_build_c128_std_cmd") 172 remoteErr, err = bc.Exec(ctx, path.Join(gb.Goroot, "bin/go"), buildlet.ExecOpts{ 173 Output: w, 174 ExtraEnv: append(gb.Conf.Env(), "GOBIN="), 175 Debug: true, 176 Args: []string{"build", "-a", "-gcflags=-c=8", "std", "cmd"}, 177 }) 178 if err != nil { 179 span.Done(err) 180 return nil, err 181 } 182 if remoteErr != nil { 183 span.Done(remoteErr) 184 return fmt.Errorf("go build failed: %v", remoteErr), nil 185 } 186 span.Done(nil) 187 return nil, nil 188 } 189 190 // VersionTgz returns an io.Reader of a *.tar.gz file containing only 191 // a VERSION file containing the contents of the provided rev string. 192 func VersionTgz(rev string) io.Reader { 193 var buf bytes.Buffer 194 zw := gzip.NewWriter(&buf) 195 tw := tar.NewWriter(zw) 196 197 // Writing to a bytes.Buffer should never fail, so check 198 // errors with an explosion: 199 check := func(err error) { 200 if err != nil { 201 panic("previously assumed to never fail: " + err.Error()) 202 } 203 } 204 205 contents := fmt.Sprintf("devel " + rev) 206 check(tw.WriteHeader(&tar.Header{ 207 Name: "VERSION", 208 Mode: 0644, 209 Size: int64(len(contents)), 210 })) 211 _, err := io.WriteString(tw, contents) 212 check(err) 213 check(tw.Close()) 214 check(zw.Close()) 215 return bytes.NewReader(buf.Bytes()) 216 }