blob: fa45e9b9f664e92fe98cdfae613e811a8ebdfa3a [file]
/*
Copyright © 2021 Aspect Build Systems Inc
Not licensed for re-use.
*/
package build
import (
"context"
"errors"
"fmt"
"time"
"github.com/spf13/cobra"
buildeventstream "aspect.build/cli/bazel/buildeventstream/proto"
"aspect.build/cli/pkg/aspect/build/bep"
"aspect.build/cli/pkg/aspecterrors"
"aspect.build/cli/pkg/bazel"
"aspect.build/cli/pkg/hooks"
"aspect.build/cli/pkg/ioutils"
)
// Build represents the aspect build command.
type Build struct {
ioutils.Streams
bzl bazel.Spawner
besBackend bep.BESBackend
hooks *hooks.Hooks
}
// New creates a Build command.
func New(
streams ioutils.Streams,
bzl bazel.Spawner,
besBackend bep.BESBackend,
hooks *hooks.Hooks,
) *Build {
return &Build{
Streams: streams,
bzl: bzl,
besBackend: besBackend,
hooks: hooks,
}
}
// Run runs the aspect build command, calling `bazel build` with a local Build
// Event Protocol backend used by Aspect plugins to subscribe to build events.
func (b *Build) Run(ctx context.Context, cmd *cobra.Command, args []string) (exitErr error) {
// TODO(f0rmiga): this is a hook for the build command and should be discussed
// as part of the plugin design.
defer func() {
errs := b.hooks.ExecutePostBuild().Errors()
if len(errs) > 0 {
for _, err := range errs {
fmt.Fprintf(b.Streams.Stderr, "Error: failed to run build command: %v\n", err)
}
var err *aspecterrors.ExitError
if errors.As(exitErr, &err) {
err.ExitCode = 1
}
}
}()
if err := b.besBackend.Setup(); err != nil {
return fmt.Errorf("failed to run build command: %w", err)
}
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
if err := b.besBackend.ServeWait(ctx); err != nil {
return fmt.Errorf("failed to run build command: %w", err)
}
defer b.besBackend.GracefulStop()
besBackendFlag := fmt.Sprintf("--bes_backend=grpc://%s", b.besBackend.Addr())
exitCode, bazelErr := b.bzl.Spawn(append([]string{"build", besBackendFlag}, args...))
// Process the subscribers errors before the Bazel one.
subscriberErrors := b.besBackend.Errors()
if len(subscriberErrors) > 0 {
for _, err := range subscriberErrors {
fmt.Fprintf(b.Streams.Stderr, "Error: failed to run build command: %v\n", err)
}
exitCode = 1
}
if exitCode != 0 {
err := &aspecterrors.ExitError{ExitCode: exitCode}
if bazelErr != nil {
err.Err = bazelErr
}
return err
}
return nil
}
// Plugin defines only the methods for the build command.
type Plugin interface {
// BEPEventsSubscriber is used to verify whether an Aspect plugin registers
// itself to receive the Build Event Protocol events.
BEPEventCallback(event *buildeventstream.BuildEvent) error
// TODO(f0rmiga): test the build hooks after implementing the plugin system.
PostBuildHook() error
}