refactor: use buildozer as lib (#36)

diff --git a/go.bzl b/go.bzl
index 55ea223..695c1e0 100644
--- a/go.bzl
+++ b/go.bzl
@@ -56,9 +56,10 @@
     )
     go_repository(
         name = "com_github_bazelbuild_buildtools",
+        build_naming_convention = "go_default_library",
         importpath = "github.com/bazelbuild/buildtools",
-        sum = "h1:Et1IIXrXwhpDvR5wH9REPEZ0sUtzUoJSq19nfmBqzBY=",
-        version = "v0.0.0-20200718160251-b1667ff58f71",
+        sum = "h1:hWvEw/36XcpZzKZB2LBYhKSGt72ETiIhudjxd637+4w=",
+        version = "v0.0.0-20210920153738-d6daef01a1a2",
     )
 
     go_repository(
@@ -1010,6 +1011,13 @@
         version = "v0.2.0",
     )
     go_repository(
+        name = "net_starlark_go",
+        importpath = "go.starlark.net",
+        sum = "h1:xwwDQW5We85NaTk2APgoN9202w/l0DVGp+GZMfsrh7s=",
+        version = "v0.0.0-20210223155950-e043a3d3c984",
+    )
+
+    go_repository(
         name = "org_golang_google_api",
         importpath = "google.golang.org/api",
         sum = "h1:Q3Ui3V3/CVinFWFiW39Iw0kMuVrRzYX0wN6OPFp0lTA=",
diff --git a/go.mod b/go.mod
index c669003..4f4adb5 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@
 require (
 	github.com/bazelbuild/bazel-gazelle v0.23.0
 	github.com/bazelbuild/bazelisk v1.10.1
+	github.com/bazelbuild/buildtools v0.0.0-20210920153738-d6daef01a1a2
 	github.com/bazelbuild/rules_go v0.28.0
 	github.com/fatih/color v1.12.0
 	github.com/golang/mock v1.3.1
diff --git a/go.sum b/go.sum
index 3c4d288..9b57883 100644
--- a/go.sum
+++ b/go.sum
@@ -24,6 +24,8 @@
 github.com/bazelbuild/bazelisk v1.10.1 h1:2EWA2lRrt/k8B5ASt0mlTDQ+7mnzvKdF6ShNWLbk0o0=
 github.com/bazelbuild/bazelisk v1.10.1/go.mod h1:s3ZIQZj3l9iCk/03rBgjhYvqz0c5SPRvoQCZuz8Lw/4=
 github.com/bazelbuild/buildtools v0.0.0-20200718160251-b1667ff58f71/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU=
+github.com/bazelbuild/buildtools v0.0.0-20210920153738-d6daef01a1a2 h1:hWvEw/36XcpZzKZB2LBYhKSGt72ETiIhudjxd637+4w=
+github.com/bazelbuild/buildtools v0.0.0-20210920153738-d6daef01a1a2/go.mod h1:689QdV3hBP7Vo9dJMmzhoYIyo/9iMhEmHkJcnaPRCbo=
 github.com/bazelbuild/rules_go v0.0.0-20190719190356-6dae44dc5cab/go.mod h1:MC23Dc/wkXEyk3Wpq6lCqz0ZAYOZDw2DR5y3N1q2i7M=
 github.com/bazelbuild/rules_go v0.28.0 h1:fNtx0dJpG5ENGdMj3/GICoi/7z+ixB3IIW5rERTzOgM=
 github.com/bazelbuild/rules_go v0.28.0/go.mod h1:MC23Dc/wkXEyk3Wpq6lCqz0ZAYOZDw2DR5y3N1q2i7M=
@@ -87,6 +89,7 @@
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
@@ -96,6 +99,8 @@
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -267,6 +272,7 @@
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.starlark.net v0.0.0-20210223155950-e043a3d3c984/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
@@ -425,6 +431,7 @@
 google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
diff --git a/pkg/aspect/build/build.go b/pkg/aspect/build/build.go
index 65cb217..2118226 100644
--- a/pkg/aspect/build/build.go
+++ b/pkg/aspect/build/build.go
@@ -50,8 +50,8 @@
 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(ctx context.Context) {
-		errs := b.hooks.ExecutePostBuild(ctx).Errors()
+	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)
@@ -61,12 +61,12 @@
 				err.ExitCode = 1
 			}
 		}
-	}(ctx)
+	}()
 
 	if err := b.besBackend.Setup(); err != nil {
 		return fmt.Errorf("failed to run build command: %w", err)
 	}
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+	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)
@@ -102,5 +102,5 @@
 	// itself to receive the Build Event Protocol events.
 	BEPEventCallback(event *buildv1.BuildEvent) error
 	// TODO(f0rmiga): test the build hooks after implementing the plugin system.
-	PostBuildHook(ctx context.Context) error
+	PostBuildHook() error
 }
diff --git a/pkg/aspect/clean/BUILD.bazel b/pkg/aspect/clean/BUILD.bazel
index 281fd6f..c004bb5 100644
--- a/pkg/aspect/clean/BUILD.bazel
+++ b/pkg/aspect/clean/BUILD.bazel
@@ -18,7 +18,6 @@
     srcs = ["clean_test.go"],
     deps = [
         ":clean",
-        "//pkg/aspecterrors",
         "//pkg/bazel/mock",
         "//pkg/ioutils",
         "@com_github_golang_mock//gomock",
diff --git a/pkg/hooks/hooks.go b/pkg/hooks/hooks.go
index dc7215d..c1ff33a 100644
--- a/pkg/hooks/hooks.go
+++ b/pkg/hooks/hooks.go
@@ -7,8 +7,6 @@
 package hooks
 
 import (
-	"context"
-
 	"aspect.build/cli/pkg/aspecterrors"
 )
 
@@ -26,11 +24,11 @@
 	hooks.postBuild.insert(fn)
 }
 
-func (hooks *Hooks) ExecutePostBuild(ctx context.Context) *aspecterrors.ErrorList {
+func (hooks *Hooks) ExecutePostBuild() *aspecterrors.ErrorList {
 	errors := &aspecterrors.ErrorList{}
 	node := hooks.postBuild.head
 	for node != nil {
-		if err := node.fn.(PostBuildFn)(ctx); err != nil {
+		if err := node.fn.(PostBuildFn)(); err != nil {
 			errors.Insert(err)
 		}
 		node = node.next
@@ -38,7 +36,7 @@
 	return errors
 }
 
-type PostBuildFn func(ctx context.Context) error
+type PostBuildFn func() error
 
 type hookList struct {
 	head *hookNode
diff --git a/pkg/plugins/fix_visibility/BUILD.bazel b/pkg/plugins/fix_visibility/BUILD.bazel
index 61618ef..9366e2a 100644
--- a/pkg/plugins/fix_visibility/BUILD.bazel
+++ b/pkg/plugins/fix_visibility/BUILD.bazel
@@ -7,6 +7,7 @@
     visibility = ["//cmd/aspect/build:__pkg__"],
     deps = [
         "@bazel_gazelle//label:go_default_library",
+        "@com_github_bazelbuild_buildtools//edit:go_default_library",
         "@com_github_manifoldco_promptui//:promptui",
         "@com_github_mattn_go_isatty//:go-isatty",
         "@go_googleapis//google/devtools/build/v1:build_go_proto",
diff --git a/pkg/plugins/fix_visibility/plugin.go b/pkg/plugins/fix_visibility/plugin.go
index f9bcc35..90f549a 100644
--- a/pkg/plugins/fix_visibility/plugin.go
+++ b/pkg/plugins/fix_visibility/plugin.go
@@ -8,15 +8,14 @@
 
 import (
 	"bytes"
-	"context"
 	"fmt"
 	"io"
 	"os"
-	"os/exec"
 	"regexp"
-	"time"
+	"strings"
 
 	"github.com/bazelbuild/bazel-gazelle/label"
+	"github.com/bazelbuild/buildtools/edit"
 	"github.com/manifoldco/promptui"
 	isatty "github.com/mattn/go-isatty"
 	buildv1 "google.golang.org/genproto/googleapis/devtools/build/v1"
@@ -74,17 +73,11 @@
 
 const removePrivateVisibilityBuildozerCommand = "remove visibility //visibility:private"
 
-func (plugin *FixVisibilityPlugin) PostBuildHook(ctx context.Context) error {
+func (plugin *FixVisibilityPlugin) PostBuildHook() error {
 	if plugin.targetsToFix.size == 0 {
 		return nil
 	}
 
-	// TODO(f0rmiga): make the timeout configurable via the plugin configuration
-	// file.
-	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
-	defer cancel()
-
-	// TODO(f0rmiga): check if buildozer is installed, otherwise return an error.
 	for node := plugin.targetsToFix.head; node != nil; node = node.next {
 		fromLabel, err := label.Parse(node.from)
 		if err != nil {
@@ -92,7 +85,7 @@
 		}
 		fromLabel.Name = "__pkg__"
 
-		hasPrivateVisibility, err := plugin.hasPrivateVisibility(ctx, node.toFix)
+		hasPrivateVisibility, err := plugin.hasPrivateVisibility(node.toFix)
 		if err != nil {
 			return fmt.Errorf("failed to fix visibility: %w", err)
 		}
@@ -105,11 +98,11 @@
 
 		addVisibilityBuildozerCommand := fmt.Sprintf("add visibility %s", fromLabel)
 		if applyFix {
-			if _, err := plugin.buildozer.Run(ctx, addVisibilityBuildozerCommand, node.toFix); err != nil {
+			if _, err := plugin.buildozer.Run(addVisibilityBuildozerCommand, node.toFix); err != nil {
 				return fmt.Errorf("failed to fix visibility: %w", err)
 			}
 			if hasPrivateVisibility {
-				if _, err := plugin.buildozer.Run(ctx, removePrivateVisibilityBuildozerCommand, node.toFix); err != nil {
+				if _, err := plugin.buildozer.Run(removePrivateVisibilityBuildozerCommand, node.toFix); err != nil {
 					return fmt.Errorf("failed to fix visibility: %w", err)
 				}
 			}
@@ -125,8 +118,8 @@
 	return nil
 }
 
-func (plugin *FixVisibilityPlugin) hasPrivateVisibility(ctx context.Context, toFix string) (bool, error) {
-	visibility, err := plugin.buildozer.Run(ctx, "print visibility", toFix)
+func (plugin *FixVisibilityPlugin) hasPrivateVisibility(toFix string) (bool, error) {
+	visibility, err := plugin.buildozer.Run("print visibility", toFix)
 	if err != nil {
 		return false, fmt.Errorf("failed to check if target has private visibility: %w", err)
 	}
@@ -164,17 +157,23 @@
 }
 
 type Runner interface {
-	Run(ctx context.Context, args ...string) ([]byte, error)
+	Run(args ...string) ([]byte, error)
 }
 
 type buildozer struct{}
 
-func (b *buildozer) Run(ctx context.Context, args ...string) ([]byte, error) {
-	cmd := exec.CommandContext(ctx, "buildozer", args...)
+func (b *buildozer) Run(args ...string) ([]byte, error) {
 	var stdout bytes.Buffer
-	cmd.Stdout = &stdout
-	if err := cmd.Run(); err != nil {
-		return stdout.Bytes(), fmt.Errorf("failed to run buildozer: %w", err)
+	var stderr strings.Builder
+	edit.ShortenLabelsFlag = true
+	edit.DeleteWithComments = true
+	opts := &edit.Options{
+		OutWriter: &stdout,
+		ErrWriter: &stderr,
+		NumIO:     200,
+	}
+	if ret := edit.Buildozer(opts, args); ret != 0 {
+		return stdout.Bytes(), fmt.Errorf("failed to run buildozer: exit code %d: %s", ret, stderr.String())
 	}
 	return stdout.Bytes(), nil
 }