Skip to content

Commit

Permalink
feat: interceptors system (#86)
Browse files Browse the repository at this point in the history
Signed-off-by: Thulio Ferraz Assis <thulio@aspect.dev>
  • Loading branch information
f0rmiga authored Dec 13, 2021
1 parent 9fdc72f commit e7b1aeb
Show file tree
Hide file tree
Showing 24 changed files with 451 additions and 194 deletions.
2 changes: 1 addition & 1 deletion cmd/aspect/build/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ go_library(
"//pkg/aspect/build",
"//pkg/aspecterrors",
"//pkg/bazel",
"//pkg/interceptors",
"//pkg/ioutils",
"//pkg/pathutils",
"//pkg/plugin/system",
"//pkg/plugin/system/bep",
"@com_github_spf13_cobra//:cobra",
Expand Down
57 changes: 31 additions & 26 deletions cmd/aspect/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Not licensed for re-use.
package build

import (
"context"
"errors"
"fmt"

Expand All @@ -16,8 +17,8 @@ import (
"aspect.build/cli/pkg/aspect/build"
"aspect.build/cli/pkg/aspecterrors"
"aspect.build/cli/pkg/bazel"
"aspect.build/cli/pkg/interceptors"
"aspect.build/cli/pkg/ioutils"
"aspect.build/cli/pkg/pathutils"
"aspect.build/cli/pkg/plugin/system"
"aspect.build/cli/pkg/plugin/system/bep"
)
Expand All @@ -38,38 +39,42 @@ func NewBuildCmd(
pluginSystem system.PluginSystem,
bzl bazel.Bazel,
) *cobra.Command {
cmd := &cobra.Command{
return &cobra.Command{
Use: "build",
Short: "Builds the specified targets, using the options.",
Long: "Invokes bazel build on the specified targets. " +
"See 'bazel help target-syntax' for details and examples on how to specify targets to build.",
RunE: pathutils.InvokeCmdInsideWorkspace(func(workspaceRoot string, cmd *cobra.Command, args []string) (exitErr error) {
isInteractiveMode, err := cmd.Root().PersistentFlags().GetBool(rootFlags.InteractiveFlagName)
if err != nil {
return err
}
RunE: interceptors.Run(
[]interceptors.Interceptor{
interceptors.WorkspaceRootInterceptor(),
pluginSystem.BESBackendInterceptor(),
},
func(ctx context.Context, cmd *cobra.Command, args []string) (exitErr error) {
isInteractiveMode, err := cmd.Root().PersistentFlags().GetBool(rootFlags.InteractiveFlagName)
if err != nil {
return err
}

// TODO(f0rmiga): test this post-build hook.
defer func() {
errs := pluginSystem.ExecutePostBuild(isInteractiveMode).Errors()
if len(errs) > 0 {
for _, err := range errs {
fmt.Fprintf(streams.Stderr, "Error: failed to run build command: %v\n", err)
}
var err *aspecterrors.ExitError
if errors.As(exitErr, &err) {
err.ExitCode = 1
// TODO(f0rmiga): test this post-build hook.
defer func() {
errs := pluginSystem.ExecutePostBuild(isInteractiveMode).Errors()
if len(errs) > 0 {
for _, err := range errs {
fmt.Fprintf(streams.Stderr, "Error: failed to run build command: %v\n", err)
}
var err *aspecterrors.ExitError
if errors.As(exitErr, &err) {
err.ExitCode = 1
}
}
}
}()
}()

bzl.SetWorkspaceRoot(workspaceRoot)
b := build.New(streams, bzl)
return pluginSystem.WithBESBackend(cmd.Context(), func(besBackend bep.BESBackend) error {
workspaceRoot := ctx.Value(interceptors.WorkspaceRootKey).(string)
bzl.SetWorkspaceRoot(workspaceRoot)
b := build.New(streams, bzl)
besBackend := ctx.Value(system.BESBackendInterceptorKey).(bep.BESBackend)
return b.Run(args, besBackend)
})
}),
},
),
}

return cmd
}
2 changes: 1 addition & 1 deletion cmd/aspect/clean/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ go_library(
deps = [
"//pkg/aspect/clean",
"//pkg/bazel",
"//pkg/interceptors",
"//pkg/ioutils",
"//pkg/pathutils",
"@com_github_mattn_go_isatty//:go-isatty",
"@com_github_spf13_cobra//:cobra",
],
Expand Down
25 changes: 16 additions & 9 deletions cmd/aspect/clean/clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ Not licensed for re-use.
package clean

import (
"context"
"os"

"github.com/mattn/go-isatty"
"github.com/spf13/cobra"

"aspect.build/cli/pkg/aspect/clean"
"aspect.build/cli/pkg/bazel"
"aspect.build/cli/pkg/interceptors"
"aspect.build/cli/pkg/ioutils"
"aspect.build/cli/pkg/pathutils"
)

// NewDefaultCleanCmd creates a new default clean cobra command.
Expand Down Expand Up @@ -61,14 +62,20 @@ Workaround inconistent state:
Such problems are fixable and these bugs are a high priority.
If you ever find an incorrect incremental build, please file a bug report,
and only use clean as a temporary workaround.`,
RunE: pathutils.InvokeCmdInsideWorkspace(func(workspaceRoot string, cmd *cobra.Command, args []string) (exitErr error) {
bzl.SetWorkspaceRoot(workspaceRoot)
isInteractive := isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
c := clean.NewDefault(bzl, isInteractive)
c.Expunge = expunge
c.ExpungeAsync = expungeAsync
return c.Run(cmd, args)
}),
RunE: interceptors.Run(
[]interceptors.Interceptor{
interceptors.WorkspaceRootInterceptor(),
},
func(ctx context.Context, cmd *cobra.Command, args []string) (exitErr error) {
workspaceRoot := ctx.Value(interceptors.WorkspaceRootKey).(string)
bzl.SetWorkspaceRoot(workspaceRoot)
isInteractive := isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
c := clean.NewDefault(bzl, isInteractive)
c.Expunge = expunge
c.ExpungeAsync = expungeAsync
return c.Run(cmd, args)
},
),
}

cmd.PersistentFlags().BoolVarP(&expunge, "expunge", "", false, `Remove the entire output_base tree.
Expand Down
2 changes: 1 addition & 1 deletion cmd/aspect/info/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//pkg/aspect/info",
"//pkg/interceptors",
"//pkg/ioutils",
"//pkg/pathutils",
"@com_github_spf13_cobra//:cobra",
],
)
9 changes: 7 additions & 2 deletions cmd/aspect/info/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
"github.com/spf13/cobra"

"aspect.build/cli/pkg/aspect/info"
"aspect.build/cli/pkg/interceptors"
"aspect.build/cli/pkg/ioutils"
"aspect.build/cli/pkg/pathutils"
)

func NewDefaultInfoCmd() *cobra.Command {
Expand Down Expand Up @@ -43,7 +43,12 @@ the bazel User Manual, and can be programmatically obtained with
See also 'bazel version' for more detailed bazel version
information.`,
Args: cobra.MaximumNArgs(1),
RunE: pathutils.InvokeCmdInsideWorkspace(v.Run),
RunE: interceptors.Run(
[]interceptors.Interceptor{
interceptors.WorkspaceRootInterceptor(),
},
v.Run,
),
}

cmd.PersistentFlags().BoolVarP(&v.ShowMakeEnv, "show_make_env", "", false, `include the set of key/value pairs in the "Make" environment,
Expand Down
2 changes: 1 addition & 1 deletion cmd/aspect/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ go_library(
deps = [
"//pkg/aspect/test",
"//pkg/bazel",
"//pkg/interceptors",
"//pkg/ioutils",
"//pkg/pathutils",
"@com_github_spf13_cobra//:cobra",
],
)
24 changes: 15 additions & 9 deletions cmd/aspect/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@ Not licensed for re-use
package test

import (
"context"

"github.com/spf13/cobra"

"aspect.build/cli/pkg/aspect/test"
"aspect.build/cli/pkg/bazel"
"aspect.build/cli/pkg/interceptors"
"aspect.build/cli/pkg/ioutils"
"aspect.build/cli/pkg/pathutils"
)

func NewDefaultTestCmd() *cobra.Command {
return NewTestCmd(ioutils.DefaultStreams, bazel.New())
}

func NewTestCmd(streams ioutils.Streams, bzl bazel.Bazel) *cobra.Command {
cmd := &cobra.Command{
return &cobra.Command{
Use: "test",
Short: "Builds the specified targets and runs all test targets among them.",
Long: `Builds the specified targets and runs all test targets among them (test targets
Expand All @@ -34,12 +36,16 @@ don't forget to pass all your 'build' options to 'test' too.
See 'bazel help target-syntax' for details and examples on how to
specify targets.
`,
RunE: pathutils.InvokeCmdInsideWorkspace(func(workspaceRoot string, cmd *cobra.Command, args []string) (exitErr error) {
bzl.SetWorkspaceRoot(workspaceRoot)
t := test.New(streams, bzl)
return t.Run(cmd, args)
}),
RunE: interceptors.Run(
[]interceptors.Interceptor{
interceptors.WorkspaceRootInterceptor(),
},
func(ctx context.Context, cmd *cobra.Command, args []string) (exitErr error) {
workspaceRoot := ctx.Value(interceptors.WorkspaceRootKey).(string)
bzl.SetWorkspaceRoot(workspaceRoot)
t := test.New(streams, bzl)
return t.Run(cmd, args)
},
),
}

return cmd
}
2 changes: 2 additions & 0 deletions cmd/docgen/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ go_library(
visibility = ["//visibility:private"],
deps = [
"//cmd/aspect/root",
"//pkg/ioutils",
"//pkg/plugin/system",
"@com_github_spf13_cobra//doc",
],
)
Expand Down
13 changes: 11 additions & 2 deletions cmd/docgen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,25 @@ import (
"log"
"os"

"aspect.build/cli/cmd/aspect/root"
"github.com/spf13/cobra/doc"

"aspect.build/cli/cmd/aspect/root"
"aspect.build/cli/pkg/ioutils"
"aspect.build/cli/pkg/plugin/system"
)

func main() {
if len(os.Args) != 2 {
log.Fatal("Usage: cmd/docgen /path/to/outdir")
}

err := doc.GenMarkdownTree(root.NewDefaultRootCmd(nil), os.Args[1])
pluginSystem := system.NewPluginSystem()
if err := pluginSystem.Configure(ioutils.DefaultStreams); err != nil {
log.Fatal(err)
}
defer pluginSystem.TearDown()

err := doc.GenMarkdownTree(root.NewDefaultRootCmd(pluginSystem), os.Args[1])
if err != nil {
log.Fatal(err)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/aspect/info/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ go_library(
deps = [
"//pkg/aspecterrors",
"//pkg/bazel",
"//pkg/interceptors",
"//pkg/ioutils",
"@com_github_spf13_cobra//:cobra",
],
Expand Down
6 changes: 5 additions & 1 deletion pkg/aspect/info/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ Not licensed for re-use.
package info

import (
"context"

"aspect.build/cli/pkg/bazel"
"aspect.build/cli/pkg/interceptors"
"aspect.build/cli/pkg/ioutils"
"github.com/spf13/cobra"

Expand All @@ -26,14 +29,15 @@ func New(streams ioutils.Streams) *Info {
}
}

func (v *Info) Run(workspaceRoot string, _ *cobra.Command, args []string) error {
func (v *Info) Run(ctx context.Context, _ *cobra.Command, args []string) error {
bazelCmd := []string{"info"}
if v.ShowMakeEnv {
// Propagate the flag
bazelCmd = append(bazelCmd, "--show_make_env")
}
bazelCmd = append(bazelCmd, args...)
bzl := bazel.New()
workspaceRoot := ctx.Value(interceptors.WorkspaceRootKey).(string)
bzl.SetWorkspaceRoot(workspaceRoot)

if exitCode, err := bzl.Spawn(bazelCmd); exitCode != 0 {
Expand Down
30 changes: 30 additions & 0 deletions pkg/interceptors/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "interceptors",
srcs = [
"run.go",
"workspace.go",
],
importpath = "aspect.build/cli/pkg/interceptors",
visibility = ["//visibility:public"],
deps = [
"//pkg/pathutils",
"@com_github_spf13_cobra//:cobra",
],
)

go_test(
name = "interceptors_test",
srcs = [
"run_test.go",
"workspace_test.go",
],
embed = [":interceptors"],
deps = [
"//pkg/pathutils/mock",
"@com_github_golang_mock//gomock",
"@com_github_onsi_gomega//:gomega",
"@com_github_spf13_cobra//:cobra",
],
)
40 changes: 40 additions & 0 deletions pkg/interceptors/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
Copyright © 2021 Aspect Build Systems Inc
Not licensed for re-use.
*/

package interceptors

import (
"context"

"github.com/spf13/cobra"
)

// RunEFn matches the cobra command.RunE signature.
type RunEFn func(cmd *cobra.Command, args []string) error

// RunEContextFn is the signature based on RunEFn that injects the context as
// an argument.
type RunEContextFn func(ctx context.Context, cmd *cobra.Command, args []string) error

// Interceptor represents an interceptor in the CLI command chain. It's
// represented as a function signature.
type Interceptor func(ctx context.Context, cmd *cobra.Command, args []string, next RunEContextFn) error

// Run returns a function that matches the cobra RunE signature. It assembles
// the interceptors and main command to be run in the correct sequence.
func Run(interceptors []Interceptor, fn RunEContextFn) RunEFn {
return func(cmd *cobra.Command, args []string) error {
current := fn
for i := len(interceptors) - 1; i > 0; i-- {
j := i
next := current
current = func(ctx context.Context, cmd *cobra.Command, args []string) error {
return interceptors[j](ctx, cmd, args, next)
}
}
return interceptors[0](cmd.Context(), cmd, args, current)
}
}
Loading

0 comments on commit e7b1aeb

Please sign in to comment.