blob: 879a94a5ddb175ac837cae3c9bb9a18dca4d5011 [file] [log] [blame]
// Package build_chrome builds Chrome browser given a chromium
// commit and a device target.
//
// build_chrome also supports gerrit patches and non-chromium
// commits.
package build_chrome
import (
"context"
"fmt"
"net/http"
"github.com/google/uuid"
"go.skia.org/infra/go/auth"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/pinpoint/go/backends"
"go.skia.org/infra/pinpoint/go/bot_configs"
"golang.org/x/oauth2/google"
buildbucketpb "go.chromium.org/luci/buildbucket/proto"
swarmingV1 "go.chromium.org/luci/common/api/swarming/swarming/v1"
)
// BuildChromeClient is a buildbucket client to build Chrome.
type BuildChromeClient interface {
// SearchOrBuild starts a new Build if it doesn't exist, or it will fetch
// the existing one that matches the build parameters.
SearchOrBuild(ctx context.Context, pinpointJobID, commit, device string, deps map[string]interface{}, patches []*buildbucketpb.GerritChange) (int64, error)
// GetStatus returns the Build status.
GetStatus(context.Context, int64) (buildbucketpb.Status, error)
// RetrieveCAS retrieves CAS from the build.
RetrieveCAS(context.Context, int64, string) (*swarmingV1.SwarmingRpcsCASReference, error)
// CancelBuild cancels the ongoing build.
CancelBuild(context.Context, int64, string) error
}
// These constants define default fields used in a buildbucket
// ScheduleBuildRequest for Pinpoint Chrome builds
const (
ScheduleReqClobber string = "clobber"
ScheduleReqDeps string = "deps_revision_overrides"
ScheduleReqGit string = "git_repo"
ScheduleReqRev string = "revision"
ScheduleReqStage string = "staging"
)
// buildChromeImpl implements BuildChromeClient to build Chrome.
type buildChromeImpl struct {
backends.BuildbucketClient
}
// New returns buildChromeImpl.
//
// buildChromeImpl is an authenticated LUCI Buildbucket client instance. Although skia has their
// own buildbucket wrapper type, it cannot build Chrome at a specific commit.
func New(ctx context.Context) (*buildChromeImpl, error) {
// Create authenticated HTTP client.
httpClientTokenSource, err := google.DefaultTokenSource(ctx, auth.ScopeReadOnly)
if err != nil {
return nil, fmt.Errorf("Problem setting up default token source: %s", err)
}
c := httputils.DefaultClientConfig().WithTokenSource(httpClientTokenSource).With2xxOnly().Client()
bc := backends.DefaultClientConfig().WithClient(c)
return &buildChromeImpl{
BuildbucketClient: bc,
}, nil
}
// NewWithClient returns buildChromeImpl.
// This is introduced for pinpoint.go, and New() is retained for compatibility to workflows/internal/build_chrome.go.
// TODO(jeffyoon@): One should be deprecated for the other.
func NewWithClient(c *http.Client) *buildChromeImpl {
bc := backends.DefaultClientConfig().WithClient(c)
return &buildChromeImpl{
BuildbucketClient: bc,
}
}
// searchBuild looks for an existing buildbucket build using the
// builder and the commit and returns the build ID and status of the build
// TODO(b/315215756): add support for non-chromium commits. A non-chromium build, such
// as this one: https://ci.chromium.org/ui/p/chrome/builders/try/Android%20arm64%20Compile%20Perf/117084/overview,
// is not easily searchable with the existing buildbucket API. The key commit information
// is written in deps_revision_overrides under Input properties. A working solution would
// be to fetch all the builds under the chromium buildset and hash, and then iterate
// through each build for the correct deps_revision_overrides. A better solution
// would be to add the non-chromium commit info to the tags and query the tags.
func (bci *buildChromeImpl) searchBuild(ctx context.Context, builder, commit string, deps map[string]interface{}, patches []*buildbucketpb.GerritChange) (int64, error) {
// search Pinpoint for build
build, err := bci.GetSingleBuild(ctx, builder, backends.DefaultBucket, commit, deps, patches)
if err != nil {
return 0, skerr.Wrapf(err, "Error searching buildbucket")
}
if build != nil {
return build.Id, nil
}
// Search waterfall for build if there is an appropriate waterfall
// builder and no gerrit patches. We search waterfall after Pinpoint,
// because waterfall builders lag behind main. A user could try to
// request a build via Pinpoint before waterfall has the chance to
// build the same commit.
sklog.Debugf("SearchBuild: search waterfall builder %s for build", backends.PinpointWaterfall[builder])
build, err = bci.GetBuildFromWaterfall(ctx, builder, commit)
if err != nil {
return 0, skerr.Wrapf(err, "Failed to find build with CI equivalent.")
}
if len(build.GetInput().GetGerritChanges()) > 0 {
return 0, nil
}
if build != nil {
sklog.Debugf("SearchBuild: build %d found in Waterfall builder", build.Id)
return build.Id, nil
}
sklog.Debug("SearchBuild: build could not be found")
return 0, nil
}
// SearchOrBuild implements BuildChromeClient interface
func (bci *buildChromeImpl) SearchOrBuild(ctx context.Context, pinpointJobID, commit, device string, deps map[string]interface{}, patches []*buildbucketpb.GerritChange) (int64, error) {
builder, err := bot_configs.GetBotConfig(device, false)
if err != nil {
return 0, err
}
buildId, err := bci.searchBuild(ctx, builder.Builder, commit, deps, patches)
// We can ignore the error here since we only need to know if there is an existing build.
if err == nil && buildId != 0 {
return buildId, nil
}
// if the ongoing build failed or the build was not found, start new build
requestID := uuid.New().String()
build, err := bci.StartChromeBuild(ctx, pinpointJobID, requestID, builder.Builder, commit, deps, patches)
if err != nil {
return 0, skerr.Wrapf(err, "Failed to start a build")
}
return build.Id, nil
}
// RetrieveCAS implements BuildChromeClient interface
func (bci *buildChromeImpl) RetrieveCAS(ctx context.Context, buildID int64, target string) (*swarmingV1.SwarmingRpcsCASReference, error) {
ref, err := bci.GetCASReference(ctx, buildID, target)
if err != nil {
return nil, skerr.Wrapf(err, "Could not find the CAS outputs to build %d", buildID)
}
return ref, nil
}
// CancelBuild implements BuildChromeClient interface
func (bci *buildChromeImpl) CancelBuild(ctx context.Context, buildID int64, summary string) error {
return bci.CancelBuild(ctx, buildID, summary)
}
// GetStatus implements BuildChromeClient interface
// TODO(b/315215756): switch from polling to pub sub
func (bci *buildChromeImpl) GetStatus(ctx context.Context, buildID int64) (buildbucketpb.Status, error) {
return bci.GetBuildStatus(ctx, buildID)
}