blob: 1d9a26ba04d38de1cc334ddc60d0b4d236f49940 [file] [log] [blame]
// Copyright 2016 The LUCI Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package cli
import (
bb ""
pb ""
func cmdAdd(p Params) *subcommands.Command {
return &subcommands.Command{
UsageLine: `add [flags] [BUILDER [[BUILDER...]]`,
ShortDesc: "add builds",
LongDesc: doc(`
Add a build for each BUILDER argument.
A BUILDER must have format "<project>/<bucket>/<builder>", for
example "chromium/try/linux-rel".
If no builders were specified on the command line, they are read
from stdin.
Example: add linux-rel and mac-rel builds to chromium/ci bucket using Shell expansion.
bb add chromium/ci/{linux-rel,mac-rel}
CommandRun: func() subcommands.CommandRun {
r := &addRun{}
r.clsFlag.Register(&r.Flags, doc(`
CL URL as input for the builds. Can be specified multiple times.
Example: add a linux-rel tryjob for CL 1539021
bb add -cl chromium/try/linux-rel
r.commitFlag.Register(&r.Flags, doc(`
Commit URL as input to the builds.
Example: build a specific revision
bb add -commit chromium/ci/linux-rel
Example: build latest chromium/src revision
bb add -commit chromium/ci/linux-rel
r.Flags.StringVar(&r.ref, "ref", "refs/heads/master", "Git ref for the -commit that specifies a commit hash.")
r.tagsFlag.Register(&r.Flags, doc(`
Build tags. Can be specified multiple times.
Example: add a build with tags "a:1" and "b:2".
bb add -t a:1 -t b:2 chromium/try/linux-rel
r.Flags.BoolVar(&r.experimental, "exp", false, doc(`
(deprecated) Mark the builds as experimental.
Identical and lower precedence to the preferred:
-ex +`+bb.ExperimentNonProduction+`
r.experimentsFlag.Register(&r.Flags, doc(`
Adds or removes an experiment from the build.
Must have the form `+"`[+-]experiment_name`"+`.
* +experiment_name adds the experiment to the build.
* -experiment_name prevents the experiment from being set on the build.
Well-known experiments:
* `+bb.ExperimentNonProduction+`
* `+bb.ExperimentBBCanarySoftware+`
* `+bb.ExperimentBBAgent+`
* `+bb.ExperimentRecipePY3+`
* `+bb.ExperimentUseRealms+`
r.Flags.Var(PropertiesFlag(&, "p", doc(`
Input properties for the build.
If a flag value starts with @, properties are read from the JSON file at the
path that follows @. Example:
bb add -p @my_properties.json chromium/try/linux-rel
This form can be used only in the first flag value.
Otherwise, a flag value must have name=value form.
If the property value is valid JSON, then it is parsed as JSON;
otherwise treated as a string. Example:
bb add -p foo=1 -p 'bar={"a": 2}' chromium/try/linux-rel
Different property names can be specified multiple times.
r.Flags.BoolVar(&r.canary, "canary", false, doc(`
(deprecated) Force the build to use canary infrastructure.
Identical and lower precedence to the preferred:
-ex +`+bb.ExperimentBBCanarySoftware+`
r.Flags.BoolVar(&r.noCanary, "nocanary", false, doc(`
(deprecated) Force the build to NOT use canary infrastructure.
Identical and lower precedence to the preferred:
-ex -`+bb.ExperimentBBCanarySoftware+`
r.Flags.StringVar(&r.swarmingParentRunID, "swarming-parent-run-id", "", doc(`
Establish parent->child relationship between provided swarming task (parent)
and the build to be triggered (child).
Provided value must be an ID of the swarming task sharing the same
swarming server as the build being created. If parent task completes
before the newly created build does, then swarming server will
forcefully terminate the build.
This makes the child build lifetime bounded by the lifetime of the given swarming task.
return r
type addRun struct {
ref string
experimental bool
canary, noCanary bool
properties structpb.Struct
swarmingParentRunID string
func (r *addRun) Run(a subcommands.Application, args []string, env subcommands.Env) int {
if r.canary && r.noCanary {
fmt.Fprintf(os.Stderr, "-canary and -nocanary are mutually exclusive\n")
return 1
ctx := cli.GetContext(a, r, env)
if err := r.initClients(ctx); err != nil {
return r.done(ctx, err)
baseReq, err := r.prepareBaseRequest(ctx)
if err != nil {
return r.done(ctx, err)
i := int32(0)
return r.PrintAndDone(ctx, args, argOrder, func(ctx context.Context, builder string) (*pb.Build, error) {
req := proto.Clone(baseReq).(*pb.ScheduleBuildRequest)
// PrintAndDone callback is executed concurrently.
req.RequestId += fmt.Sprintf("-%d", atomic.AddInt32(&i, 1))
var err error
req.Builder, err = protoutil.ParseBuilderID(builder)
if err != nil {
return nil, err
return r.client.ScheduleBuild(ctx, req, expectedCodeRPCOption)
func (r *addRun) prepareBaseRequest(ctx context.Context) (*pb.ScheduleBuildRequest, error) {
ret := &pb.ScheduleBuildRequest{
RequestId: uuid.New().String(),
Tags: r.Tags(),
Fields: &field_mask.FieldMask{Paths: []string{"*"}},
Properties: &,
Swarming: &pb.ScheduleBuildRequest_Swarming{ParentRunId: r.swarmingParentRunID},
Experiments: r.experiments,
switch {
case r.canary:
ret.Experiments[bb.ExperimentBBCanarySoftware] = true
logging.Warningf(ctx, "-canary is deprecated, setting experiment +%s", bb.ExperimentBBCanarySoftware)
case r.noCanary:
ret.Experiments[bb.ExperimentBBCanarySoftware] = false
logging.Warningf(ctx, "-canary is deprecated, setting experiment -%s", bb.ExperimentBBCanarySoftware)
if r.experimental {
ret.Experiments[bb.ExperimentNonProduction] = true
logging.Warningf(ctx, "-exp is deprecated, setting experiment +%s", bb.ExperimentNonProduction)
var err error
if ret.GerritChanges, err = r.retrieveCLs(ctx, r.httpClient, !kRequirePatchset); err != nil {
return nil, err
if ret.GitilesCommit, err = r.retrieveCommit(ctx, r.httpClient); err != nil {
return nil, err
if ret.GitilesCommit != nil && ret.GitilesCommit.Ref == "" {
ret.GitilesCommit.Ref = r.ref
return ret, nil