blob: 54376fcee4333d28712b80331158bf5792f46de3 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package cli
import (
"context"
"flag"
"fmt"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/artifacts"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/device"
)
type repeatableBuildKind int
const (
builderNameKind repeatableBuildKind = iota
buildIdKind
fuchsiaBuildDirKind
productBundleDirKind
)
// If the useLatestFfx flag is set to true then use the latest ffx tool,
// else the ffx tool from the build will be used.
type BuildConfig struct {
archiveConfig *ArchiveConfig
deviceConfig *DeviceConfig
prefix string
builderName string
buildID string
fuchsiaBuildDir string
productBundleDir string
useLatestFfx bool
}
func NewBuildConfig(
fs *flag.FlagSet,
archiveConfig *ArchiveConfig,
deviceConfig *DeviceConfig,
defaultBuildID string,
) *BuildConfig {
return NewBuildConfigWithPrefix(fs, archiveConfig, deviceConfig, defaultBuildID, "", false)
}
func NewBuildConfigWithPrefix(
fs *flag.FlagSet,
archiveConfig *ArchiveConfig,
deviceConfig *DeviceConfig,
defaultBuildID string,
prefix string,
useLatestFfx bool,
) *BuildConfig {
c := &BuildConfig{
prefix: prefix,
archiveConfig: archiveConfig,
deviceConfig: deviceConfig,
useLatestFfx: useLatestFfx,
}
fs.StringVar(&c.builderName, fmt.Sprintf("%sbuilder-name", prefix), "", "Pave to the latest version of this builder")
fs.StringVar(&c.buildID, fmt.Sprintf("%sbuild-id", prefix), defaultBuildID, "Pave to this specific build id")
fs.StringVar(&c.fuchsiaBuildDir, fmt.Sprintf("%sfuchsia-build-dir", prefix), "", "Pave to the build in this fuchsia build output directory")
fs.StringVar(&c.productBundleDir, fmt.Sprintf("%sproduct-bundle-dir", prefix), "", "Flash to the build in this product bundle directory")
return c
}
func (c *BuildConfig) Validate() error {
defined := 0
for _, s := range []string{
c.builderName,
c.buildID,
c.fuchsiaBuildDir,
c.productBundleDir,
} {
if s != "" {
defined += 1
}
}
if defined > 1 {
return fmt.Errorf("--%sbuilder-name, --%sbuild-id, --%sfuchsia-build-dir, and %sproduct-bundle-dir are mutually exclusive",
c.prefix,
c.prefix,
c.prefix,
c.prefix,
)
}
return nil
}
func (c *BuildConfig) GetBuilder() (*artifacts.Builder, error) {
if c.builderName == "" {
return nil, fmt.Errorf("builder not specified")
}
return c.archiveConfig.BuildArchive().GetBuilder(c.builderName), nil
}
func (c *BuildConfig) getBuildID(ctx context.Context) (string, error) {
if c.builderName != "" && c.buildID == "" {
b, err := c.GetBuilder()
if err != nil {
return "", err
}
id, err := b.GetLatestBuildID(ctx)
if err != nil {
return "", fmt.Errorf("failed to lookup build id: %w", err)
}
c.buildID = id
}
return c.buildID, nil
}
func (c *BuildConfig) GetBuild(
ctx context.Context,
deviceClient *device.Client,
outputDir string,
) (artifacts.Build, error) {
buildID, err := c.getBuildID(ctx)
if err != nil {
return nil, err
}
var build artifacts.Build
if buildID != "" {
ffxPath := ""
if c.useLatestFfx {
ffxPath = c.deviceConfig.ffxPath
}
build, err = c.archiveConfig.BuildArchive().GetBuildByID(
ctx,
buildID,
outputDir,
ffxPath,
)
} else if c.fuchsiaBuildDir != "" {
build, err = artifacts.NewFuchsiaDirBuild(c.fuchsiaBuildDir), nil
} else if c.productBundleDir != "" {
build, err = artifacts.NewProductBundleDirBuild(c.productBundleDir), nil
}
if err != nil {
return nil, err
}
return build, nil
}
type RepeatableBuildConfig struct {
archiveConfig *ArchiveConfig
deviceConfig *DeviceConfig
defaultBuildID string
builds []repeatableBuild
}
func NewRepeatableBuildConfig(
fs *flag.FlagSet,
archiveConfig *ArchiveConfig,
deviceConfig *DeviceConfig,
defaultBuildID string,
prefix string,
) *RepeatableBuildConfig {
c := &RepeatableBuildConfig{
archiveConfig: archiveConfig,
deviceConfig: deviceConfig,
defaultBuildID: defaultBuildID,
}
fs.Var(
repeatableBuildVar{c: c, kind: builderNameKind},
fmt.Sprintf("%sbuilder-name", prefix),
"Pave to the latest version of this builder",
)
fs.Var(
repeatableBuildVar{c: c, kind: buildIdKind},
fmt.Sprintf("%sbuild-id", prefix),
"Pave to this specific build id",
)
fs.Var(
repeatableBuildVar{c: c, kind: fuchsiaBuildDirKind},
fmt.Sprintf("%sfuchsia-build-dir", prefix),
"Pave to the build in this fuchsia build output directory",
)
fs.Var(
repeatableBuildVar{c: c, kind: productBundleDirKind},
fmt.Sprintf("%sproduct-bundle-dir", prefix),
"Update to the latest version of this builder",
)
return c
}
func (c *RepeatableBuildConfig) GetBuilds(
ctx context.Context,
deviceClient *device.Client,
outputDir string,
) ([]artifacts.Build, error) {
var builds []artifacts.Build
for _, b := range c.builds {
switch b.kind {
case builderNameKind:
builder := c.archiveConfig.BuildArchive().GetBuilder(b.value)
buildID, err := builder.GetLatestBuildID(ctx)
if err != nil {
return nil, fmt.Errorf("failed to lookup build id: %w", err)
}
build, err := c.archiveConfig.BuildArchive().GetBuildByID(
ctx,
buildID,
outputDir,
c.deviceConfig.ffxPath,
)
if err != nil {
return nil, err
}
builds = append(builds, build)
case buildIdKind:
build, err := c.archiveConfig.BuildArchive().GetBuildByID(
ctx,
b.value,
outputDir,
c.deviceConfig.ffxPath,
)
if err != nil {
return nil, err
}
builds = append(builds, build)
case fuchsiaBuildDirKind:
build := artifacts.NewFuchsiaDirBuild(b.value)
builds = append(builds, build)
case productBundleDirKind:
build := artifacts.NewProductBundleDirBuild(b.value)
builds = append(builds, build)
}
}
// Append the last build id as our final upgrade.
if c.defaultBuildID != "" {
build, err := c.archiveConfig.BuildArchive().GetBuildByID(
ctx,
c.defaultBuildID,
outputDir,
c.deviceConfig.ffxPath,
)
if err != nil {
return nil, err
}
builds = append(builds, build)
}
return builds, nil
}
type repeatableBuild struct {
kind repeatableBuildKind
value string
}
type repeatableBuildVar struct {
c *RepeatableBuildConfig
kind repeatableBuildKind
}
func (v repeatableBuildVar) String() string {
if v.c != nil {
return fmt.Sprintf("%+v", v.c.builds)
}
return ""
}
func (v repeatableBuildVar) Set(s string) error {
if s == "" {
return fmt.Errorf("%s value cannot be empty", v.kind)
}
v.c.builds = append(v.c.builds, repeatableBuild{kind: v.kind, value: s})
return nil
}