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  }