Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: interceptors system #86

Merged
merged 1 commit into from
Dec 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,8 +4,11 @@ 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() {
Expand All @@ -14,7 +17,13 @@ func main() {
os.Exit(1)
}

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