From 0ce23cace8a1a6935fb4e5ad6f104a64b397a5aa Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Fri, 17 Sep 2021 17:54:55 -0700 Subject: [PATCH 1/2] feat: add clean command --- cmd/aspect/build/build.go | 2 +- cmd/aspect/clean/BUILD.bazel | 14 +++++++ cmd/aspect/clean/clean.go | 77 ++++++++++++++++++++++++++++++++++ cmd/aspect/docs/docs.go | 4 +- cmd/aspect/root/BUILD.bazel | 1 + cmd/aspect/root/root.go | 2 + docs/BUILD.bazel | 1 + docs/aspect.md | 3 +- docs/aspect_build.md | 2 +- docs/aspect_clean.md | 68 ++++++++++++++++++++++++++++++ docs/aspect_docs.md | 4 +- pkg/aspect/clean/BUILD.bazel | 27 ++++++++++++ pkg/aspect/clean/clean.go | 64 ++++++++++++++++++++++++++++ pkg/aspect/clean/clean_test.go | 74 ++++++++++++++++++++++++++++++++ 14 files changed, 336 insertions(+), 7 deletions(-) create mode 100644 cmd/aspect/clean/BUILD.bazel create mode 100644 cmd/aspect/clean/clean.go create mode 100644 docs/aspect_clean.md create mode 100644 pkg/aspect/clean/BUILD.bazel create mode 100644 pkg/aspect/clean/clean.go create mode 100644 pkg/aspect/clean/clean_test.go diff --git a/cmd/aspect/build/build.go b/cmd/aspect/build/build.go index 670654b64..8d9dbac67 100644 --- a/cmd/aspect/build/build.go +++ b/cmd/aspect/build/build.go @@ -31,7 +31,7 @@ func NewBuildCmd( cmd := &cobra.Command{ Use: "build", - Short: "Builds the specified targets, using the options.", + 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: b.Run, diff --git a/cmd/aspect/clean/BUILD.bazel b/cmd/aspect/clean/BUILD.bazel new file mode 100644 index 000000000..df6edafac --- /dev/null +++ b/cmd/aspect/clean/BUILD.bazel @@ -0,0 +1,14 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "clean", + srcs = ["clean.go"], + importpath = "aspect.build/cli/cmd/aspect/clean", + visibility = ["//visibility:public"], + deps = [ + "//pkg/aspect/clean", + "//pkg/bazel", + "//pkg/ioutils", + "@com_github_spf13_cobra//:cobra", + ], +) diff --git a/cmd/aspect/clean/clean.go b/cmd/aspect/clean/clean.go new file mode 100644 index 000000000..1a52528ce --- /dev/null +++ b/cmd/aspect/clean/clean.go @@ -0,0 +1,77 @@ +/* +Copyright © 2021 Aspect Build Systems Inc + +Not licensed for re-use. +*/ + +package clean + +import ( + "github.com/spf13/cobra" + + "aspect.build/cli/pkg/aspect/clean" + "aspect.build/cli/pkg/bazel" + "aspect.build/cli/pkg/ioutils" +) + +// NewDefaultCleanCmd creates a new clean cobra command with the default +// dependencies. +func NewDefaultCleanCmd() *cobra.Command { + return NewCleanCmd(ioutils.DefaultStreams, bazel.New()) +} + +// NewCleanCmd creates a new clean cobra command. +func NewCleanCmd( + streams ioutils.Streams, + bzl bazel.Spawner, +) *cobra.Command { + b := clean.New(streams, bzl) + + cmd := &cobra.Command{ + Use: "clean", + Short: "Removes the output tree", + Long: `Removes bazel-created output, including all object files, and bazel metadata. + +clean deletes the output directories for all build configurations performed by +this Bazel instance, or the entire working tree created by this Bazel instance, +and resets internal caches. + +If executed without any command-line options, then the output directory for all +configurations will be cleaned. + +Recall that each Bazel instance is associated with a single workspace, +thus the clean command will delete all outputs from all builds you've +done with that Bazel instance in that workspace. + +NOTE: clean is primarily intended for reclaiming disk space for workspaces +that are no longer needed. +It causes all subsequent builds to be non-incremental. +If this is not your intent, consider these alternatives: + +Do a one-off non-incremental build: + bazel --output_base=$(mktemp -d) ... + +Force repository rules to re-execute: + bazel sync --configure + +Workaround inconistent state: + Bazel's incremental rebuilds are designed to be correct, so clean + should never be required due to inconsistencies in the build. + 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: b.Run, + } + + cmd.PersistentFlags().BoolVarP(&b.Expunge, "expunge", "", false, `Remove the entire output_base tree. +This removes all build output, external repositories, +and temp files created by Bazel. +It also stops the Bazel server after the clean, +equivalent to the shutdown command.`) + + cmd.PersistentFlags().BoolVarP(&b.ExpungeAsync, "expunge_async", "", false, `Expunge in the background. +It is safe to invoke a Bazel command in the same +workspace while the asynchronous expunge continues to run. +Note, however, that this may introduce IO contention.`) + return cmd +} diff --git a/cmd/aspect/docs/docs.go b/cmd/aspect/docs/docs.go index 91612ca4b..fd6359bb4 100644 --- a/cmd/aspect/docs/docs.go +++ b/cmd/aspect/docs/docs.go @@ -24,8 +24,8 @@ func NewDocsCmd(streams ioutils.Streams) *cobra.Command { Use: "docs", Short: "Open documentation in the browser", Long: `Given a selected topic, open the relevant API docs in a browser window. - The mechanism of choosing the browser to open is documented at https://github.com/pkg/browser - By default, opens docs.bazel.build`, +The mechanism of choosing the browser to open is documented at https://github.com/pkg/browser +By default, opens docs.bazel.build`, RunE: v.Run, } diff --git a/cmd/aspect/root/BUILD.bazel b/cmd/aspect/root/BUILD.bazel index b7c3b244f..d186a7598 100644 --- a/cmd/aspect/root/BUILD.bazel +++ b/cmd/aspect/root/BUILD.bazel @@ -10,6 +10,7 @@ go_library( ], deps = [ "//cmd/aspect/build", + "//cmd/aspect/clean", "//cmd/aspect/docs", "//cmd/aspect/info", "//cmd/aspect/version", diff --git a/cmd/aspect/root/root.go b/cmd/aspect/root/root.go index 5b3213e0b..608513e52 100644 --- a/cmd/aspect/root/root.go +++ b/cmd/aspect/root/root.go @@ -16,6 +16,7 @@ import ( "github.com/spf13/viper" "aspect.build/cli/cmd/aspect/build" + "aspect.build/cli/cmd/aspect/clean" "aspect.build/cli/cmd/aspect/docs" "aspect.build/cli/cmd/aspect/info" "aspect.build/cli/cmd/aspect/version" @@ -69,6 +70,7 @@ func NewRootCmd(streams ioutils.Streams, defaultInteractive bool) *cobra.Command // ### Child commands // IMPORTANT: when adding a new command, also update the _DOCS list in /docs/BUILD.bazel cmd.AddCommand(build.NewDefaultBuildCmd()) + cmd.AddCommand(clean.NewDefaultCleanCmd()) cmd.AddCommand(version.NewDefaultVersionCmd()) cmd.AddCommand(docs.NewDefaultDocsCmd()) cmd.AddCommand(info.NewDefaultInfoCmd()) diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel index 3abc99db6..95a8cc448 100644 --- a/docs/BUILD.bazel +++ b/docs/BUILD.bazel @@ -5,6 +5,7 @@ load("@bazel_skylib//rules:write_file.bzl", "write_file") _DOCS = [ "aspect.md", "aspect_build.md", + "aspect_clean.md", "aspect_docs.md", "aspect_info.md", "aspect_version.md", diff --git a/docs/aspect.md b/docs/aspect.md index 69358f293..d86bc3887 100644 --- a/docs/aspect.md +++ b/docs/aspect.md @@ -16,7 +16,8 @@ Aspect CLI is a better frontend for running bazel ### SEE ALSO -* [aspect build](aspect_build.md) - Builds the specified targets, using the options. +* [aspect build](aspect_build.md) - Builds the specified targets, using the options +* [aspect clean](aspect_clean.md) - Removes the output tree * [aspect docs](aspect_docs.md) - Open documentation in the browser * [aspect info](aspect_info.md) - Displays runtime info about the bazel server * [aspect version](aspect_version.md) - Print the version of aspect CLI as well as tools it invokes diff --git a/docs/aspect_build.md b/docs/aspect_build.md index 7dd414da7..5a8b18aec 100644 --- a/docs/aspect_build.md +++ b/docs/aspect_build.md @@ -1,6 +1,6 @@ ## aspect build -Builds the specified targets, using the options. +Builds the specified targets, using the options ### Synopsis diff --git a/docs/aspect_clean.md b/docs/aspect_clean.md new file mode 100644 index 000000000..3f40876ac --- /dev/null +++ b/docs/aspect_clean.md @@ -0,0 +1,68 @@ +## aspect clean + +Removes the output tree + +### Synopsis + +Removes bazel-created output, including all object files, and bazel metadata. + +clean deletes the output directories for all build configurations performed by +this Bazel instance, or the entire working tree created by this Bazel instance, +and resets internal caches. + +If executed without any command-line options, then the output directory for all +configurations will be cleaned. + +Recall that each Bazel instance is associated with a single workspace, +thus the clean command will delete all outputs from all builds you've +done with that Bazel instance in that workspace. + +NOTE: clean is primarily intended for reclaiming disk space for workspaces +that are no longer needed. +It causes all subsequent builds to be non-incremental. +If this is not your intent, consider these alternatives: + +Do a one-off non-incremental build: + bazel --output_base=$(mktemp -d) ... + +Force repository rules to re-execute: + bazel sync --configure + +Workaround inconistent state: + Bazel's incremental rebuilds are designed to be correct, so clean + should never be required due to inconsistencies in the build. + 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. + +``` +aspect clean [flags] +``` + +### Options + +``` + --expunge Remove the entire output_base tree. + This removes all build output, external repositories, + and temp files created by Bazel. + It also stops the Bazel server after the clean, + equivalent to the shutdown command. + --expunge_async Expunge in the background. + It is safe to invoke a Bazel command in the same + workspace while the asynchronous expunge continues to run. + Note, however, that this may introduce IO contention. + -h, --help help for clean +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.aspect.yaml) + --interactive Interactive mode (e.g. prompts for user input) +``` + +### SEE ALSO + +* [aspect](aspect.md) - Aspect.build bazel wrapper + +###### Auto generated by spf13/cobra diff --git a/docs/aspect_docs.md b/docs/aspect_docs.md index 8533a3ea1..d5a636dac 100644 --- a/docs/aspect_docs.md +++ b/docs/aspect_docs.md @@ -5,8 +5,8 @@ Open documentation in the browser ### Synopsis Given a selected topic, open the relevant API docs in a browser window. - The mechanism of choosing the browser to open is documented at https://github.com/pkg/browser - By default, opens docs.bazel.build +The mechanism of choosing the browser to open is documented at https://github.com/pkg/browser +By default, opens docs.bazel.build ``` aspect docs [flags] diff --git a/pkg/aspect/clean/BUILD.bazel b/pkg/aspect/clean/BUILD.bazel new file mode 100644 index 000000000..281fd6f96 --- /dev/null +++ b/pkg/aspect/clean/BUILD.bazel @@ -0,0 +1,27 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "clean", + srcs = ["clean.go"], + importpath = "aspect.build/cli/pkg/aspect/clean", + visibility = ["//visibility:public"], + deps = [ + "//pkg/aspecterrors", + "//pkg/bazel", + "//pkg/ioutils", + "@com_github_spf13_cobra//:cobra", + ], +) + +go_test( + name = "clean_test", + srcs = ["clean_test.go"], + deps = [ + ":clean", + "//pkg/aspecterrors", + "//pkg/bazel/mock", + "//pkg/ioutils", + "@com_github_golang_mock//gomock", + "@com_github_onsi_gomega//:gomega", + ], +) diff --git a/pkg/aspect/clean/clean.go b/pkg/aspect/clean/clean.go new file mode 100644 index 000000000..745063059 --- /dev/null +++ b/pkg/aspect/clean/clean.go @@ -0,0 +1,64 @@ +/* +Copyright © 2021 Aspect Build Systems Inc + +Not licensed for re-use. +*/ + +package clean + +import ( + "github.com/spf13/cobra" + + "aspect.build/cli/pkg/aspecterrors" + "aspect.build/cli/pkg/bazel" + "aspect.build/cli/pkg/ioutils" +) + +// Clean represents the aspect clean command. +type Clean struct { + ioutils.Streams + bzl bazel.Spawner + + Expunge bool + ExpungeAsync bool +} + +// New creates a Clean command. +func New( + streams ioutils.Streams, + bzl bazel.Spawner, +) *Clean { + return &Clean{ + Streams: streams, + bzl: bzl, + } +} + +// Run runs the aspect build command. +func (c *Clean) Run(_ *cobra.Command, _ []string) error { + // TODO(alex): when interactive, prompt the user: + // First time running aspect clean? + // Then ask, why do you want to clean? + // - reclaim disk space? + // - workaround inconsistent state + // - experiment with a one-off non-incremental build + // then ask + // do you want to see this wizard again next time? + // and if not, record in the cache file to inhibit next time + cmd := []string{"clean"} + if c.Expunge { + cmd = append(cmd, "--expunge") + } + if c.ExpungeAsync { + cmd = append(cmd, "--expunge_async") + } + if exitCode, err := c.bzl.Spawn(cmd); exitCode != 0 { + err = &aspecterrors.ExitError{ + Err: err, + ExitCode: exitCode, + } + return err + } + + return nil +} diff --git a/pkg/aspect/clean/clean_test.go b/pkg/aspect/clean/clean_test.go new file mode 100644 index 000000000..51a98608f --- /dev/null +++ b/pkg/aspect/clean/clean_test.go @@ -0,0 +1,74 @@ +/* +Copyright © 2021 Aspect Build Systems Inc + +Not licensed for re-use. +*/ + +package clean_test + +import ( + "testing" + + "github.com/golang/mock/gomock" + . "github.com/onsi/gomega" + + "aspect.build/cli/pkg/aspect/clean" + "aspect.build/cli/pkg/bazel/mock" + "aspect.build/cli/pkg/ioutils" +) + +func TestClean(t *testing.T) { + + t.Run("clean calls bazel clean", func(t *testing.T) { + g := NewGomegaWithT(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + spawner := mock.NewMockSpawner(ctrl) + spawner. + EXPECT(). + Spawn([]string{"clean"}). + Return(0, nil) + + b := clean.New(ioutils.Streams{}, spawner) + err := b.Run(nil, []string{}) + + g.Expect(err).To(BeNil()) + }) + + t.Run("clean expunge calls bazel clean expunge", func(t *testing.T) { + g := NewGomegaWithT(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + spawner := mock.NewMockSpawner(ctrl) + spawner. + EXPECT(). + Spawn([]string{"clean", "--expunge"}). + Return(0, nil) + + b := clean.New(ioutils.Streams{}, spawner) + b.Expunge = true + err := b.Run(nil, []string{}) + + g.Expect(err).To(BeNil()) + }) + + t.Run("clean expunge_async calls bazel clean expunge_async", func(t *testing.T) { + g := NewGomegaWithT(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + spawner := mock.NewMockSpawner(ctrl) + spawner. + EXPECT(). + Spawn([]string{"clean", "--expunge_async"}). + Return(0, nil) + + b := clean.New(ioutils.Streams{}, spawner) + b.ExpungeAsync = true + err := b.Run(nil, []string{}) + + g.Expect(err).To(BeNil()) + }) +} From 603e3020b302f62d24070e9314b094faa21b5591 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Fri, 24 Sep 2021 15:40:26 -0700 Subject: [PATCH 2/2] style: end short help with a period --- cmd/aspect/build/build.go | 2 +- cmd/aspect/clean/clean.go | 2 +- cmd/aspect/docs/docs.go | 2 +- cmd/aspect/info/info.go | 2 +- cmd/aspect/version/version.go | 2 +- docs/aspect.md | 10 +++++----- docs/aspect_build.md | 2 +- docs/aspect_clean.md | 2 +- docs/aspect_docs.md | 2 +- docs/aspect_info.md | 2 +- docs/aspect_version.md | 2 +- 11 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cmd/aspect/build/build.go b/cmd/aspect/build/build.go index 8d9dbac67..670654b64 100644 --- a/cmd/aspect/build/build.go +++ b/cmd/aspect/build/build.go @@ -31,7 +31,7 @@ func NewBuildCmd( cmd := &cobra.Command{ Use: "build", - Short: "Builds the specified targets, using the options", + 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: b.Run, diff --git a/cmd/aspect/clean/clean.go b/cmd/aspect/clean/clean.go index 1a52528ce..a039256b3 100644 --- a/cmd/aspect/clean/clean.go +++ b/cmd/aspect/clean/clean.go @@ -29,7 +29,7 @@ func NewCleanCmd( cmd := &cobra.Command{ Use: "clean", - Short: "Removes the output tree", + Short: "Removes the output tree.", Long: `Removes bazel-created output, including all object files, and bazel metadata. clean deletes the output directories for all build configurations performed by diff --git a/cmd/aspect/docs/docs.go b/cmd/aspect/docs/docs.go index fd6359bb4..ca71e9f65 100644 --- a/cmd/aspect/docs/docs.go +++ b/cmd/aspect/docs/docs.go @@ -22,7 +22,7 @@ func NewDocsCmd(streams ioutils.Streams) *cobra.Command { cmd := &cobra.Command{ Use: "docs", - Short: "Open documentation in the browser", + Short: "Open documentation in the browser.", Long: `Given a selected topic, open the relevant API docs in a browser window. The mechanism of choosing the browser to open is documented at https://github.com/pkg/browser By default, opens docs.bazel.build`, diff --git a/cmd/aspect/info/info.go b/cmd/aspect/info/info.go index 24102eec5..0ce183c04 100644 --- a/cmd/aspect/info/info.go +++ b/cmd/aspect/info/info.go @@ -22,7 +22,7 @@ func NewInfoCmd(streams ioutils.Streams) *cobra.Command { cmd := &cobra.Command{ Use: "info", - Short: "Displays runtime info about the bazel server", + Short: "Displays runtime info about the bazel server.", Long: `Displays information about the state of the bazel process in the form of several "key: value" pairs. This includes the locations of several output directories. Because some of the diff --git a/cmd/aspect/version/version.go b/cmd/aspect/version/version.go index 6d35ef162..bd8e4439c 100644 --- a/cmd/aspect/version/version.go +++ b/cmd/aspect/version/version.go @@ -26,7 +26,7 @@ func NewVersionCmd(streams ioutils.Streams) *cobra.Command { cmd := &cobra.Command{ Use: "version", - Short: "Print the version of aspect CLI as well as tools it invokes", + Short: "Print the version of aspect CLI as well as tools it invokes.", Long: `Prints version info on colon-separated lines, just like bazel does`, RunE: v.Run, } diff --git a/docs/aspect.md b/docs/aspect.md index d86bc3887..f40087bc1 100644 --- a/docs/aspect.md +++ b/docs/aspect.md @@ -16,10 +16,10 @@ Aspect CLI is a better frontend for running bazel ### SEE ALSO -* [aspect build](aspect_build.md) - Builds the specified targets, using the options -* [aspect clean](aspect_clean.md) - Removes the output tree -* [aspect docs](aspect_docs.md) - Open documentation in the browser -* [aspect info](aspect_info.md) - Displays runtime info about the bazel server -* [aspect version](aspect_version.md) - Print the version of aspect CLI as well as tools it invokes +* [aspect build](aspect_build.md) - Builds the specified targets, using the options. +* [aspect clean](aspect_clean.md) - Removes the output tree. +* [aspect docs](aspect_docs.md) - Open documentation in the browser. +* [aspect info](aspect_info.md) - Displays runtime info about the bazel server. +* [aspect version](aspect_version.md) - Print the version of aspect CLI as well as tools it invokes. ###### Auto generated by spf13/cobra diff --git a/docs/aspect_build.md b/docs/aspect_build.md index 5a8b18aec..7dd414da7 100644 --- a/docs/aspect_build.md +++ b/docs/aspect_build.md @@ -1,6 +1,6 @@ ## aspect build -Builds the specified targets, using the options +Builds the specified targets, using the options. ### Synopsis diff --git a/docs/aspect_clean.md b/docs/aspect_clean.md index 3f40876ac..7904e09d7 100644 --- a/docs/aspect_clean.md +++ b/docs/aspect_clean.md @@ -1,6 +1,6 @@ ## aspect clean -Removes the output tree +Removes the output tree. ### Synopsis diff --git a/docs/aspect_docs.md b/docs/aspect_docs.md index d5a636dac..c044affcf 100644 --- a/docs/aspect_docs.md +++ b/docs/aspect_docs.md @@ -1,6 +1,6 @@ ## aspect docs -Open documentation in the browser +Open documentation in the browser. ### Synopsis diff --git a/docs/aspect_info.md b/docs/aspect_info.md index 5f326205e..ed33ea199 100644 --- a/docs/aspect_info.md +++ b/docs/aspect_info.md @@ -1,6 +1,6 @@ ## aspect info -Displays runtime info about the bazel server +Displays runtime info about the bazel server. ### Synopsis diff --git a/docs/aspect_version.md b/docs/aspect_version.md index 7e3c086de..5b0466949 100644 --- a/docs/aspect_version.md +++ b/docs/aspect_version.md @@ -1,6 +1,6 @@ ## aspect version -Print the version of aspect CLI as well as tools it invokes +Print the version of aspect CLI as well as tools it invokes. ### Synopsis