Skip to content

Commit

Permalink
feat: clean command prompts for use case (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeagle authored Sep 30, 2021
1 parent 2fde7d7 commit 5e3a061
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 36 deletions.
1 change: 1 addition & 0 deletions cmd/aspect/clean/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ go_library(
"//pkg/aspect/clean",
"//pkg/bazel",
"//pkg/ioutils",
"@com_github_mattn_go_isatty//:go-isatty",
"@com_github_spf13_cobra//:cobra",
],
)
19 changes: 6 additions & 13 deletions cmd/aspect/clean/clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,18 @@ Not licensed for re-use.
package clean

import (
"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/ioutils"
)

// NewDefaultCleanCmd creates a new clean cobra command with the default
// dependencies.
// NewDefaultCleanCmd creates a new clean cobra command.
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)
isInteractive := isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
b := clean.NewDefault(isInteractive)

cmd := &cobra.Command{
Use: "clean",
Expand Down
3 changes: 3 additions & 0 deletions pkg/aspect/clean/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ go_library(
"//pkg/aspecterrors",
"//pkg/bazel",
"//pkg/ioutils",
"@com_github_manifoldco_promptui//:promptui",
"@com_github_spf13_cobra//:cobra",
"@com_github_spf13_viper//:viper",
],
)

Expand All @@ -22,5 +24,6 @@ go_test(
"//pkg/ioutils",
"@com_github_golang_mock//gomock",
"@com_github_onsi_gomega//:gomega",
"@com_github_spf13_viper//:viper",
],
)
127 changes: 114 additions & 13 deletions pkg/aspect/clean/clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,61 @@ Not licensed for re-use.
package clean

import (
"fmt"

"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"github.com/spf13/viper"

"aspect.build/cli/pkg/aspecterrors"
"aspect.build/cli/pkg/bazel"
"aspect.build/cli/pkg/ioutils"
)

const (
skipPromptKey = "clean.skip_prompt"

ReclaimOption = "Reclaim disk space for this workspace (same as bazel clean)"
ReclaimAllOption = "Reclaim disk space for all Bazel workspaces"
NonIncrementalOption = "Prepare to perform a non-incremental build"
InvalidateReposOption = "Invalidate all repository rules, causing them to recreate external repos"
WorkaroundOption = "Workaround inconsistent state in the output tree"

outputBaseHint = `It's faster to perform a non-incremental build by choosing a different output base.
Instead of running 'clean' you should use the --output_base flag.
Run 'aspect help clean' for more info.
`
syncHint = `It's faster to invalidate repository rules by using the sync command.
Instead of running 'clean' you should run 'aspect sync --configure'
Run 'aspect help clean' for more info.
`
fileIssueHint = `Bazel is a correct build tool, and it should not be possible to get inconstent state.
We highly recommend you file a bug reporting this problem so that the offending rule
implementation can be fixed.
`

rememberLine1 = "You can skip this prompt to make 'aspect clean' behave the same as 'bazel clean'\n"
rememberLine2 = "Remember this choice and skip the prompt in the future"
)

type SelectRunner interface {
Run() (int, string, error)
}

type PromptRunner interface {
Run() (string, error)
}

// Clean represents the aspect clean command.
type Clean struct {
ioutils.Streams
bzl bazel.Spawner
bzl bazel.Spawner
isInteractiveMode bool

Behavior SelectRunner
Workaround PromptRunner
Remember PromptRunner
Prefs viper.Viper

Expunge bool
ExpungeAsync bool
Expand All @@ -27,24 +71,81 @@ type Clean struct {
func New(
streams ioutils.Streams,
bzl bazel.Spawner,
) *Clean {
isInteractiveMode bool) *Clean {
return &Clean{
Streams: streams,
bzl: bzl,
Streams: streams,
isInteractiveMode: isInteractiveMode,
bzl: bzl,
}
}

func NewDefault(isInteractive bool) *Clean {
c := New(
ioutils.DefaultStreams,
bazel.New(),
isInteractive)
c.Behavior = &promptui.Select{
Label: "Clean can have a few behaviors. Which do you want?",
Items: []string{
ReclaimOption,
ReclaimAllOption,
NonIncrementalOption,
InvalidateReposOption,
WorkaroundOption,
},
}
c.Workaround = &promptui.Prompt{
Label: "Temporarily workaround the bug by deleting the output folder",
IsConfirm: true,
}
c.Remember = &promptui.Prompt{
Label: rememberLine2,
IsConfirm: true,
}
c.Prefs = *viper.GetViper()
return c
}

// 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
skip := c.Prefs.GetBool(skipPromptKey)
if c.isInteractiveMode && !skip {

_, chosen, err := c.Behavior.Run()

if err != nil {
return fmt.Errorf("prompt failed: %w", err)
}

switch chosen {

case ReclaimOption:
// Allow user to opt-out of our fancy "clean" command and just behave like bazel
fmt.Fprint(c.Streams.Stdout, rememberLine1)
if _, err := c.Remember.Run(); err == nil {
c.Prefs.Set(skipPromptKey, "true")
if err := c.Prefs.WriteConfig(); err != nil {
return fmt.Errorf("failed to update config file: %w", err)
}
}
case ReclaimAllOption:
fmt.Fprint(c.Streams.Stdout, "Sorry, this is not implemented yet: discover all bazel workspaces on the machine\n")
return nil
case NonIncrementalOption:
fmt.Fprint(c.Streams.Stdout, outputBaseHint)
return nil
case InvalidateReposOption:
fmt.Fprint(c.Streams.Stdout, syncHint)
return nil
case WorkaroundOption:
fmt.Fprint(c.Streams.Stdout, fileIssueHint)
_, err := c.Workaround.Run()
if err != nil {
return fmt.Errorf("prompt failed: %w", err)
}
}
}

cmd := []string{"clean"}
if c.Expunge {
cmd = append(cmd, "--expunge")
Expand Down
Loading

0 comments on commit 5e3a061

Please sign in to comment.