From 7c33d3d72d8c820dc50abcc5e1f8ba63eaa55760 Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Mon, 5 May 2025 11:40:18 -0600 Subject: [PATCH 1/6] Refactor Sweep code --- BUILD.bazel | 1 + language/scala/BUILD.bazel | 1 + language/scala/existing_scala_rule.go | 72 +----------- language/scala/lifecycle.go | 5 + language/scala/scala_package.go | 16 +++ pkg/scalaconfig/BUILD.bazel | 1 + pkg/scalaconfig/config.go | 50 ++------ pkg/sweep/BUILD.bazel | 25 ++++ pkg/sweep/sweep.go | 160 ++++++++++++++++++++++++++ 9 files changed, 222 insertions(+), 109 deletions(-) create mode 100644 pkg/sweep/BUILD.bazel create mode 100644 pkg/sweep/sweep.go diff --git a/BUILD.bazel b/BUILD.bazel index 2e18d681..dc16402e 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -130,6 +130,7 @@ package_filegroup( "//pkg/scalarule/mocks:filegroup", "//pkg/semanticdb:filegroup", "//pkg/starlarkeval:filegroup", + "//pkg/sweep:filegroup", "//pkg/testutil:filegroup", "//pkg/wildcardimport:filegroup", "//rules:filegroup", diff --git a/language/scala/BUILD.bazel b/language/scala/BUILD.bazel index 8f7f208b..4b21a6b1 100644 --- a/language/scala/BUILD.bazel +++ b/language/scala/BUILD.bazel @@ -49,6 +49,7 @@ go_library( "//pkg/scalaconfig", "//pkg/scalafiles", "//pkg/scalarule", + "//pkg/sweep", "//pkg/wildcardimport", "@bazel_gazelle//config:go_default_library", "@bazel_gazelle//label:go_default_library", diff --git a/language/scala/existing_scala_rule.go b/language/scala/existing_scala_rule.go index b726205b..8c16812e 100644 --- a/language/scala/existing_scala_rule.go +++ b/language/scala/existing_scala_rule.go @@ -6,16 +6,14 @@ import ( "strings" "github.com/bazelbuild/bazel-gazelle/config" - "github.com/bazelbuild/bazel-gazelle/label" "github.com/bazelbuild/bazel-gazelle/resolve" "github.com/bazelbuild/bazel-gazelle/rule" "github.com/bazelbuild/buildtools/build" - "github.com/stackb/scala-gazelle/pkg/bazel" - "github.com/stackb/scala-gazelle/pkg/collections" "github.com/stackb/scala-gazelle/pkg/protobuf" "github.com/stackb/scala-gazelle/pkg/scalaconfig" "github.com/stackb/scala-gazelle/pkg/scalarule" + "github.com/stackb/scala-gazelle/pkg/sweep" sppb "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/parse" ) @@ -81,11 +79,6 @@ func (s *existingScalaRuleProvider) ResolveRule(cfg *scalarule.Config, pkg scala log.Printf("skipping %s %s: unable to collect srcs: %v", r.Kind(), r.Name(), err) return nil } - // rule has no srcs. This is OK for binary rules, sometimes they only - // have a main_class. - // if !s.isBinary { - // return nil // no need to print a warning - // } } if scalaRule == nil { log.Panicln("scalaRule should not be nil!") @@ -157,71 +150,10 @@ func (s *existingScalaRule) Resolve(rctx *scalarule.ResolveContext, importsRaw i } if sc.ShouldSweepTransitiveDeps() { - if err := s.sweepTransitive("deps", rctx.Rule, rctx.From); err != nil { + if err := sweep.TransitiveAttr("deps", rctx.Rule, s.pkg.GenerateArgs().File, rctx.From); err != nil { log.Panicf("transitive sweep failed: %v", err) } } - -} - -// sweepTransitive iterates through deps marked "TRANSITIVE" and removes them if -// the target still builds without it. -func (s *existingScalaRule) sweepTransitive(attrName string, r *rule.Rule, from label.Label) error { - expr := r.Attr(attrName) - if expr == nil { - return nil - } - - deps, isList := expr.(*build.ListExpr) - if !isList { - return nil // some other condition we can't deal with - } - - file := s.pkg.GenerateArgs().File - - // target should build first time, otherwise we can't check accurately. - log.Println("🧱 transitive sweep:", from) - - if out, exitCode, _ := bazel.ExecCommand("bazel", "build", from.String()); exitCode != 0 { - log.Fatalln("sweep failed (must build cleanly on first attempt): %s", string(out)) - } - - for i := len(deps.List) - 1; i >= 0; i-- { - expr := deps.List[i] - switch t := expr.(type) { - case *build.StringExpr: - if len(t.Comments.Suffix) != 1 { - continue - } - if t.Comments.Suffix[0].Token != "# TRANSITIVE" { - continue - } - - dep, err := label.Parse(t.Value) - if err != nil { - return err - } - deps.List = collections.SliceRemoveIndex(deps.List, i) - - if err := file.Save(file.Path); err != nil { - return err - } - - if _, exitCode, _ := bazel.ExecCommand("bazel", "build", from.String()); exitCode == 0 { - log.Println("- 💩 junk:", dep) - } else { - log.Println("- 👑 keep:", dep) - deps.List = collections.SliceInsertAt(deps.List, i, expr) - } - } - - } - - if err := file.Save(file.Path); err != nil { - return err - } - - return nil } func makeRuleComments(pb *sppb.Rule) (comments []build.Comment) { diff --git a/language/scala/lifecycle.go b/language/scala/lifecycle.go index 755f5b57..73555ca5 100644 --- a/language/scala/lifecycle.go +++ b/language/scala/lifecycle.go @@ -40,6 +40,11 @@ func (sl *scalaLang) onEnd() { log.Fatalf("provider.OnEnd transition error %s: %v", provider.Name(), err) } } + for _, pkg := range sl.packages { + if err := pkg.OnEnd(); err != nil { + log.Fatalf("pkg.OnEnd transition error %s: %v", pkg.args.Rel, err) + } + } sl.dumpResolvedImportMap() sl.reportCoverage(log.Printf) diff --git a/language/scala/scala_package.go b/language/scala/scala_package.go index 84b6e277..875ecd7e 100644 --- a/language/scala/scala_package.go +++ b/language/scala/scala_package.go @@ -326,6 +326,22 @@ func (p *scalaPackage) infof(format string, args ...any) string { return fmt.Sprintf("INFO ["+p.args.Rel+"]: "+format, args...) } +// OnEnd is a lifecycle hook that gets called when the resolve phase has +// ended. +func (p *scalaPackage) OnEnd() error { + // if p.cfg.ShouldSweepTransitiveDeps() { + // // strip off the sweep directive if we got this far in the process + // if err := sweep.RemoveSweepDirective(p.args.File); err != nil { + // return err + // } + // // flip the keep_deps to false + // if err := sweep.SetKeepDepsDirective(p.args.File, false); err != nil { + // return err + // } + // } + return nil +} + func ruleContributesToCoverage(name string) bool { switch name { case "scala_files": diff --git a/pkg/scalaconfig/BUILD.bazel b/pkg/scalaconfig/BUILD.bazel index 1a44329d..f053dad4 100644 --- a/pkg/scalaconfig/BUILD.bazel +++ b/pkg/scalaconfig/BUILD.bazel @@ -10,6 +10,7 @@ go_library( "//pkg/collections", "//pkg/resolver", "//pkg/scalarule", + "//pkg/sweep", "@bazel_gazelle//config:go_default_library", "@bazel_gazelle//label:go_default_library", "@bazel_gazelle//resolve:go_default_library", diff --git a/pkg/scalaconfig/config.go b/pkg/scalaconfig/config.go index 1c5bcc66..b1a6b95a 100644 --- a/pkg/scalaconfig/config.go +++ b/pkg/scalaconfig/config.go @@ -18,12 +18,12 @@ import ( "github.com/stackb/scala-gazelle/pkg/collections" "github.com/stackb/scala-gazelle/pkg/resolver" "github.com/stackb/scala-gazelle/pkg/scalarule" + "github.com/stackb/scala-gazelle/pkg/sweep" ) type debugAnnotation int const scalaLangName = "scala" -const TransitiveCommentToken = "# TRANSITIVE" const ( DebugUnknown debugAnnotation = 0 @@ -57,17 +57,6 @@ const ( // gazelle:scala_fix_wildcard_imports .scala examples.aeron.api.proto._ scalaFixWildcardImportDirective = "scala_fix_wildcard_imports" - // Flag to preserve deps if the label is not known to be needed from the - // imports (legacy migration mode). - // - // gazelle:scala_keep_unknown_deps true - scalaKeepUnknownDepsDirective = "scala_keep_unknown_deps" - - // Turn on the dep sweeper - // - // gazelle:scala_sweep_transitive_deps true - scalaSweepTransitiveDepsDirective = "scala_sweep_transitive_deps" - // Configure a scala rule // // gazelle:scala_rule RULE_NAME ATTRIBUTE VALUE @@ -145,8 +134,8 @@ func DirectiveNames() []string { scalaDebugDirective, scalaLogLevelDirective, scalaDepsCleanerDirective, - scalaKeepUnknownDepsDirective, - scalaSweepTransitiveDepsDirective, + sweep.ScalaKeepUnknownDepsDirective, + sweep.ScalaSweepTransitiveDepsDirective, scalaFixWildcardImportDirective, scalaGenerateBuildFilesDirective, scalaRuleDirective, @@ -303,11 +292,11 @@ func (c *Config) ParseDirectives(directives []rule.Directive) error { if err := c.parseScalaRuleDirective(d); err != nil { return fmt.Errorf(`invalid directive: "gazelle:%s %s": %w`, d.Key, d.Value, err) } - case scalaKeepUnknownDepsDirective: + case sweep.ScalaKeepUnknownDepsDirective: if err := c.parseKeepUnknownDepsDirective(d); err != nil { return err } - case scalaSweepTransitiveDepsDirective: + case sweep.ScalaSweepTransitiveDepsDirective: if err := c.parseSweepTransitiveDepsDirective(d); err != nil { return err } @@ -418,11 +407,11 @@ func (c *Config) parseFixWildcardImport(d rule.Directive) { func (c *Config) parseKeepUnknownDepsDirective(d rule.Directive) error { parts := strings.Fields(d.Value) if len(parts) != 1 { - return fmt.Errorf("invalid gazelle:%s directive: expected [true|false], got %v", scalaKeepUnknownDepsDirective, parts) + return fmt.Errorf("invalid gazelle:%s directive: expected [true|false], got %v", sweep.ScalaKeepUnknownDepsDirective, parts) } keepUnknownDeps, err := strconv.ParseBool(parts[0]) if err != nil { - return fmt.Errorf("invalid gazelle:%s directive: %v", scalaKeepUnknownDepsDirective, err) + return fmt.Errorf("invalid gazelle:%s directive: %v", sweep.ScalaKeepUnknownDepsDirective, err) } c.keepUnknownDeps = keepUnknownDeps return nil @@ -431,11 +420,11 @@ func (c *Config) parseKeepUnknownDepsDirective(d rule.Directive) error { func (c *Config) parseSweepTransitiveDepsDirective(d rule.Directive) error { parts := strings.Fields(d.Value) if len(parts) != 1 { - return fmt.Errorf("invalid gazelle:%s directive: expected [true|false], got %v", scalaSweepTransitiveDepsDirective, parts) + return fmt.Errorf("invalid gazelle:%s directive: expected [true|false], got %v", sweep.ScalaSweepTransitiveDepsDirective, parts) } sweepTransitiveDeps, err := strconv.ParseBool(parts[0]) if err != nil { - return fmt.Errorf("invalid gazelle:%s directive: %v", scalaSweepTransitiveDepsDirective, err) + return fmt.Errorf("invalid gazelle:%s directive: %v", sweep.ScalaSweepTransitiveDepsDirective, err) } c.sweepTransitiveDeps = sweepTransitiveDeps return nil @@ -798,9 +787,9 @@ func (c *Config) mergeDeps(attrValue build.Expr, deps map[label.Label]bool, impo // sweeper process. Otherwise, only keep it if has already been // marked as TRANSITIVE. if c.ShouldSweepTransitiveDeps() { - dst.List = append(dst.List, makeTransitiveDep(dep)) + dst.List = append(dst.List, sweep.MakeTransitiveDep(dep)) } else { - if isTransitive(expr) { + if sweep.IsTransitiveDep(expr) { dst.List = append(dst.List, expr) } else { // one more caveat: preserve unmarked deps in legacy mode @@ -947,20 +936,3 @@ func setCommentPrefix(comment build.Comment, prefix string) build.Comment { comment.Token = "# " + prefix + strings.TrimSpace(strings.TrimPrefix(comment.Token, "#")) return comment } - -// isTransitive returns whether e is marked with a "# TRANSITIVE" comment. -func isTransitive(e build.Expr) bool { - for _, c := range e.Comment().Suffix { - text := strings.TrimSpace(c.Token) - if text == TransitiveCommentToken { - return true - } - } - return false -} - -func makeTransitiveDep(dep label.Label) *build.StringExpr { - expr := &build.StringExpr{Value: dep.String()} - expr.Comment().Suffix = append(expr.Comment().Suffix, build.Comment{Token: TransitiveCommentToken}) - return expr -} diff --git a/pkg/sweep/BUILD.bazel b/pkg/sweep/BUILD.bazel new file mode 100644 index 00000000..7879f083 --- /dev/null +++ b/pkg/sweep/BUILD.bazel @@ -0,0 +1,25 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@build_stack_scala_gazelle//rules:package_filegroup.bzl", "package_filegroup") + +go_library( + name = "sweep", + srcs = ["sweep.go"], + importpath = "github.com/stackb/scala-gazelle/pkg/sweep", + visibility = ["//visibility:public"], + deps = [ + "//pkg/bazel", + "//pkg/collections", + "@bazel_gazelle//label:go_default_library", + "@bazel_gazelle//rule:go_default_library", + "@com_github_bazelbuild_buildtools//build:go_default_library", + ], +) + +package_filegroup( + name = "filegroup", + srcs = [ + "BUILD.bazel", + "sweep.go", + ], + visibility = ["//visibility:public"], +) diff --git a/pkg/sweep/sweep.go b/pkg/sweep/sweep.go new file mode 100644 index 00000000..1e2cdf3d --- /dev/null +++ b/pkg/sweep/sweep.go @@ -0,0 +1,160 @@ +package sweep + +import ( + "bytes" + "fmt" + "log" + "os" + "strings" + + "github.com/bazelbuild/bazel-gazelle/label" + "github.com/bazelbuild/bazel-gazelle/rule" + "github.com/bazelbuild/buildtools/build" + "github.com/stackb/scala-gazelle/pkg/bazel" + "github.com/stackb/scala-gazelle/pkg/collections" +) + +const ( + // Turn on the dep sweeper + // + // gazelle:scala_sweep_transitive_deps true + ScalaSweepTransitiveDepsDirective = "scala_sweep_transitive_deps" + + // Flag to preserve deps if the label is not known to be needed from the + // imports (legacy migration mode). + // + // gazelle:scala_keep_unknown_deps true + ScalaKeepUnknownDepsDirective = "scala_keep_unknown_deps" +) + +const TransitiveCommentToken = "# TRANSITIVE" + +// TransitiveAttr iterates through deps marked "TRANSITIVE" and removes them if +// the target still builds without it. +func TransitiveAttr(attrName string, r *rule.Rule, file *rule.File, from label.Label) error { + expr := r.Attr(attrName) + if expr == nil { + return nil + } + + deps, isList := expr.(*build.ListExpr) + if !isList { + return nil // some other condition we can't deal with + } + + // target should build first time, otherwise we can't check accurately. + log.Println("🧱 transitive sweep:", from) + + if out, exitCode, _ := bazel.ExecCommand("bazel", "build", from.String()); exitCode != 0 { + log.Fatalf("sweep failed (must build cleanly on first attempt): %s", string(out)) + } + + for i := len(deps.List) - 1; i >= 0; i-- { + expr := deps.List[i] + switch t := expr.(type) { + case *build.StringExpr: + if len(t.Comments.Suffix) != 1 { + continue + } + if t.Comments.Suffix[0].Token != "# TRANSITIVE" { + continue + } + + dep, err := label.Parse(t.Value) + if err != nil { + return err + } + deps.List = collections.SliceRemoveIndex(deps.List, i) + + if err := file.Save(file.Path); err != nil { + return err + } + + if _, exitCode, _ := bazel.ExecCommand("bazel", "build", from.String()); exitCode == 0 { + log.Println("- 💩 junk:", dep) + } else { + log.Println("- 👑 keep:", dep) + deps.List = collections.SliceInsertAt(deps.List, i, expr) + } + } + + } + + if err := file.Save(file.Path); err != nil { + return err + } + + return nil +} + +func RemoveSweepDirective(file *rule.File) error { + if file == nil { + return nil + } + // if this file has the sweep directive, remove it + for _, d := range file.Directives { + if d.Key == ScalaSweepTransitiveDepsDirective && d.Value == "true" { + old := []byte(fmt.Sprintf("# gazelle:%s true\n", ScalaSweepTransitiveDepsDirective)) + new := []byte{'\n'} + file.Content = bytes.Replace(file.Content, old, new, -1) + // file.Sync() + if err := file.Save(file.Path); err != nil { + return err + } + // log.Panicln("saved it!", file.Path) + } + } + return nil +} + +func SetKeepDepsDirective(file *rule.File, value bool) error { + if file == nil { + return nil + } + // if this file has the sweep directive, remove it + for _, d := range file.Directives { + if d.Key == ScalaKeepUnknownDepsDirective { + // log.Panicln("found it!", file.Path) + old := []byte(fmt.Sprintf("# gazelle:%s %t", ScalaKeepUnknownDepsDirective, !value)) + new := []byte(fmt.Sprintf("# gazelle:%s %t", ScalaKeepUnknownDepsDirective, value)) + log.Println("OLD:", string(file.Content)) + file.Content = bytes.Replace(file.Content, old, new, -1) + // file.Sync() + log.Println("NEW:", string(file.Content)) + + if err := file.Save(file.Path); err != nil { + return err + } + stat, err := os.Stat(file.Path) + if err != nil { + return err + } + if err := os.WriteFile(file.Path, file.Content, stat.Mode()); err != nil { + return err + } + data, err := os.ReadFile(file.Path) + if err != nil { + return err + } + log.Panicln("saved it!", file.Path, "FILE DATA:\n", string(data)) + } + } + return nil +} + +func MakeTransitiveDep(dep label.Label) *build.StringExpr { + expr := &build.StringExpr{Value: dep.String()} + expr.Comment().Suffix = append(expr.Comment().Suffix, build.Comment{Token: TransitiveCommentToken}) + return expr +} + +// IsTransitiveDep returns whether e is marked with a "# TRANSITIVE" comment. +func IsTransitiveDep(e build.Expr) bool { + for _, c := range e.Comment().Suffix { + text := strings.TrimSpace(c.Token) + if text == TransitiveCommentToken { + return true + } + } + return false +} From bc1247cde0802e5a4917fe815bda0b77aaaa37f3 Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Wed, 7 May 2025 10:49:36 -0600 Subject: [PATCH 2/6] checkpoint fix deps list changed files --- build/stack/gazelle/scala/parse/file.pb.go | 47 ++++++---- build/stack/gazelle/scala/parse/file.proto | 2 + cmd/scalafileextract/scalafileextract.go | 7 ++ language/scala/scala_package.go | 18 ++-- pkg/scalaconfig/config.go | 60 ++++++------ pkg/sweep/BUILD.bazel | 8 +- pkg/sweep/fix.go | 74 +++++++++++++++ pkg/sweep/sweep.go | 104 ++++++++++++++------- 8 files changed, 228 insertions(+), 92 deletions(-) create mode 100644 pkg/sweep/fix.go diff --git a/build/stack/gazelle/scala/parse/file.pb.go b/build/stack/gazelle/scala/parse/file.pb.go index 934f126e..4ed09fd1 100644 --- a/build/stack/gazelle/scala/parse/file.pb.go +++ b/build/stack/gazelle/scala/parse/file.pb.go @@ -85,6 +85,7 @@ type File struct { Extends map[string]*ClassList `protobuf:"bytes,11,rep,name=extends,proto3" json:"extends,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Error string `protobuf:"bytes,13,opt,name=error,proto3" json:"error,omitempty"` Tree string `protobuf:"bytes,14,opt,name=tree,proto3" json:"tree,omitempty"` + Sha256 string `protobuf:"bytes,15,opt,name=sha256,proto3" json:"sha256,omitempty"` } func (x *File) Reset() { @@ -210,6 +211,13 @@ func (x *File) GetTree() string { return "" } +func (x *File) GetSha256() string { + if x != nil { + return x.Sha256 + } + return "" +} + type ClassList struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -269,7 +277,7 @@ var file_build_stack_gazelle_scala_parse_file_proto_rawDesc = []byte{ 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x70, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, - 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, 0xef, 0x03, 0x0a, 0x04, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x1a, + 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x87, 0x04, 0x0a, 0x04, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x02, @@ -293,24 +301,25 @@ var file_build_stack_gazelle_scala_parse_file_proto_rawDesc = []byte{ 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x74, - 0x72, 0x65, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x72, 0x65, 0x65, 0x1a, - 0x66, 0x0a, 0x0c, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x40, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x2a, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, - 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x70, 0x61, 0x72, - 0x73, 0x65, 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x25, 0x0a, 0x09, 0x43, 0x6c, 0x61, 0x73, 0x73, - 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x42, 0x6a, - 0x0a, 0x1f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, - 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x70, 0x61, 0x72, 0x73, - 0x65, 0x50, 0x01, 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x62, 0x2f, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2d, 0x67, 0x61, 0x7a, - 0x65, 0x6c, 0x6c, 0x65, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2f, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2f, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2f, 0x70, - 0x61, 0x72, 0x73, 0x65, 0x3b, 0x70, 0x61, 0x72, 0x73, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x72, 0x65, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x72, 0x65, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x1a, 0x66, 0x0a, 0x0c, 0x45, 0x78, 0x74, 0x65, 0x6e, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x40, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, + 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x70, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0x25, 0x0a, 0x09, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, + 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x42, 0x6a, 0x0a, 0x1f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, + 0x61, 0x6c, 0x61, 0x2e, 0x70, 0x61, 0x72, 0x73, 0x65, 0x50, 0x01, 0x5a, 0x45, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x62, 0x2f, 0x73, + 0x63, 0x61, 0x6c, 0x61, 0x2d, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2f, 0x62, 0x75, 0x69, + 0x6c, 0x64, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, + 0x2f, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2f, 0x70, 0x61, 0x72, 0x73, 0x65, 0x3b, 0x70, 0x61, 0x72, + 0x73, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/build/stack/gazelle/scala/parse/file.proto b/build/stack/gazelle/scala/parse/file.proto index 004030a6..71f2e500 100644 --- a/build/stack/gazelle/scala/parse/file.proto +++ b/build/stack/gazelle/scala/parse/file.proto @@ -42,6 +42,8 @@ message File { // tree is a JSON string representing the parse tree. This field is only // populated when specifically requested during parsing. string tree = 14; + // sha256 is the sha256 hash value of the file contents + string sha256 = 15; } // ClassList represents a set of files. diff --git a/cmd/scalafileextract/scalafileextract.go b/cmd/scalafileextract/scalafileextract.go index 6c0879f0..c608b1d5 100644 --- a/cmd/scalafileextract/scalafileextract.go +++ b/cmd/scalafileextract/scalafileextract.go @@ -200,6 +200,13 @@ func extract(parser *parser.ScalametaParser, dir string, sourceFiles []string) ( return nil, fmt.Errorf(file.Error) } file.Filename = filenames[file.Filename] + if false { + sha256, err := collections.FileSha256(file.Filename) + if err != nil { + return nil, err + } + file.Sha256 = sha256 + } } return response.Files, nil diff --git a/language/scala/scala_package.go b/language/scala/scala_package.go index 875ecd7e..b55d8754 100644 --- a/language/scala/scala_package.go +++ b/language/scala/scala_package.go @@ -20,6 +20,7 @@ import ( "github.com/stackb/scala-gazelle/pkg/resolver" "github.com/stackb/scala-gazelle/pkg/scalaconfig" "github.com/stackb/scala-gazelle/pkg/scalarule" + "github.com/stackb/scala-gazelle/pkg/sweep" ) const ( @@ -329,16 +330,13 @@ func (p *scalaPackage) infof(format string, args ...any) string { // OnEnd is a lifecycle hook that gets called when the resolve phase has // ended. func (p *scalaPackage) OnEnd() error { - // if p.cfg.ShouldSweepTransitiveDeps() { - // // strip off the sweep directive if we got this far in the process - // if err := sweep.RemoveSweepDirective(p.args.File); err != nil { - // return err - // } - // // flip the keep_deps to false - // if err := sweep.SetKeepDepsDirective(p.args.File, false); err != nil { - // return err - // } - // } + if p.cfg.ShouldFixDeps() { + fixer := sweep.NewDepFixer(p.args.Config, p.args.Rel) + + if err := fixer.Run(); err != nil { + return err + } + } return nil } diff --git a/pkg/scalaconfig/config.go b/pkg/scalaconfig/config.go index acc1c49a..7e0adae5 100644 --- a/pkg/scalaconfig/config.go +++ b/pkg/scalaconfig/config.go @@ -136,6 +136,7 @@ func DirectiveNames() []string { scalaDepsCleanerDirective, sweep.ScalaKeepUnknownDepsDirective, sweep.ScalaSweepTransitiveDepsDirective, + sweep.ScalaFixDepsDirective, scalaFixWildcardImportDirective, scalaGenerateBuildFilesDirective, scalaRuleDirective, @@ -159,6 +160,7 @@ type Config struct { generateBuildFiles bool keepUnknownDeps bool sweepTransitiveDeps bool + fixDeps bool logger zerolog.Logger logLevel zerolog.Level } @@ -207,6 +209,7 @@ func (c *Config) clone(config *config.Config, rel string) *Config { clone.generateBuildFiles = c.generateBuildFiles clone.keepUnknownDeps = c.keepUnknownDeps clone.sweepTransitiveDeps = c.sweepTransitiveDeps + clone.fixDeps = c.fixDeps for k, v := range c.annotations { clone.annotations[k] = v @@ -293,12 +296,22 @@ func (c *Config) ParseDirectives(directives []rule.Directive) error { return fmt.Errorf(`invalid directive: "gazelle:%s %s": %w`, d.Key, d.Value, err) } case sweep.ScalaKeepUnknownDepsDirective: - if err := c.parseKeepUnknownDepsDirective(d); err != nil { + if value, err := parseBoolDirective(d); err != nil { return err + } else { + c.keepUnknownDeps = value + } + case sweep.ScalaFixDepsDirective: + if value, err := parseBoolDirective(d); err != nil { + return err + } else { + c.fixDeps = value } case sweep.ScalaSweepTransitiveDepsDirective: - if err := c.parseSweepTransitiveDepsDirective(d); err != nil { + if value, err := parseBoolDirective(d); err != nil { return err + } else { + c.sweepTransitiveDeps = value } case scalaFixWildcardImportDirective: c.parseFixWildcardImport(d) @@ -404,32 +417,6 @@ func (c *Config) parseFixWildcardImport(d rule.Directive) { } } -func (c *Config) parseKeepUnknownDepsDirective(d rule.Directive) error { - parts := strings.Fields(d.Value) - if len(parts) != 1 { - return fmt.Errorf("invalid gazelle:%s directive: expected [true|false], got %v", sweep.ScalaKeepUnknownDepsDirective, parts) - } - keepUnknownDeps, err := strconv.ParseBool(parts[0]) - if err != nil { - return fmt.Errorf("invalid gazelle:%s directive: %v", sweep.ScalaKeepUnknownDepsDirective, err) - } - c.keepUnknownDeps = keepUnknownDeps - return nil -} - -func (c *Config) parseSweepTransitiveDepsDirective(d rule.Directive) error { - parts := strings.Fields(d.Value) - if len(parts) != 1 { - return fmt.Errorf("invalid gazelle:%s directive: expected [true|false], got %v", sweep.ScalaSweepTransitiveDepsDirective, parts) - } - sweepTransitiveDeps, err := strconv.ParseBool(parts[0]) - if err != nil { - return fmt.Errorf("invalid gazelle:%s directive: %v", sweep.ScalaSweepTransitiveDepsDirective, err) - } - c.sweepTransitiveDeps = sweepTransitiveDeps - return nil -} - func (c *Config) parseResolveFileSymbolNames(d rule.Directive) { parts := strings.Fields(d.Value) if len(parts) < 2 { @@ -626,6 +613,11 @@ func (c *Config) ShouldSweepTransitive(attrName string) bool { return c.sweepTransitiveDeps } +// ShouldFixDeps determines whether dep fixer should be applied. +func (c *Config) ShouldFixDeps() bool { + return c.fixDeps +} + // shouldKeepUnknownDeps determines whether non-managed deps should be // kept. func (c *Config) shouldKeepUnknownDeps() bool { @@ -911,6 +903,18 @@ func parseAnnotation(val string) debugAnnotation { } } +func parseBoolDirective(d rule.Directive) (bool, error) { + parts := strings.Fields(d.Value) + if len(parts) != 1 { + return false, fmt.Errorf("invalid gazelle:%s directive: expected [true|false], got %v", d.Key, parts) + } + value, err := strconv.ParseBool(parts[0]) + if err != nil { + return false, fmt.Errorf("invalid gazelle:%s directive: %v", d.Key, err) + } + return value, nil +} + func removeConflictResolver(slice []resolver.ConflictResolver, index int) []resolver.ConflictResolver { return append(slice[:index], slice[index+1:]...) } diff --git a/pkg/sweep/BUILD.bazel b/pkg/sweep/BUILD.bazel index 7879f083..974d5c7a 100644 --- a/pkg/sweep/BUILD.bazel +++ b/pkg/sweep/BUILD.bazel @@ -3,12 +3,17 @@ load("@build_stack_scala_gazelle//rules:package_filegroup.bzl", "package_filegro go_library( name = "sweep", - srcs = ["sweep.go"], + srcs = [ + "fix.go", + "sweep.go", + ], importpath = "github.com/stackb/scala-gazelle/pkg/sweep", visibility = ["//visibility:public"], deps = [ "//pkg/bazel", "//pkg/collections", + "//pkg/procutil", + "@bazel_gazelle//config:go_default_library", "@bazel_gazelle//label:go_default_library", "@bazel_gazelle//rule:go_default_library", "@com_github_bazelbuild_buildtools//build:go_default_library", @@ -19,6 +24,7 @@ package_filegroup( name = "filegroup", srcs = [ "BUILD.bazel", + "fix.go", "sweep.go", ], visibility = ["//visibility:public"], diff --git a/pkg/sweep/fix.go b/pkg/sweep/fix.go new file mode 100644 index 00000000..28e42b74 --- /dev/null +++ b/pkg/sweep/fix.go @@ -0,0 +1,74 @@ +package sweep + +import ( + "bufio" + "bytes" + "fmt" + "log" + "os/exec" + "strings" + + "github.com/bazelbuild/bazel-gazelle/config" + "github.com/stackb/scala-gazelle/pkg/procutil" +) + +type DepFixer struct { + repoDir string + pkg string +} + +func NewDepFixer(c *config.Config, pkg string) *DepFixer { + return &DepFixer{ + repoDir: c.RepoRoot, + pkg: pkg, + } +} + +func (d *DepFixer) Run() error { + files, err := d.listChangedScalaFiles() + if err != nil { + return err + } + + log.Println("changed files:", files) + + return nil +} + +func (d *DepFixer) listChangedScalaFiles() ([]string, error) { + args := []string{ + "--work-tree", + d.repoDir, + "ls-files", + "--others", + "--modified", + "--full-name", + "--", + fmt.Sprintf("%s/*.scala", d.pkg), + } + + cmd := exec.Command("git", args...) + output, err := cmd.CombinedOutput() + exitCode := procutil.CmdExitCode(cmd, err) + + if exitCode != 0 { + return nil, fmt.Errorf("git ls-files failed: %s", string(output)) + } + + return scanLsFilesOutput(output) +} + +func scanLsFilesOutput(output []byte) (files []string, err error) { + scanner := bufio.NewScanner(bytes.NewReader(output)) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + files = append(files, line) + } + if err := scanner.Err(); err != nil { + return nil, err + } + return +} diff --git a/pkg/sweep/sweep.go b/pkg/sweep/sweep.go index 1e2cdf3d..fa1d7a80 100644 --- a/pkg/sweep/sweep.go +++ b/pkg/sweep/sweep.go @@ -25,21 +25,42 @@ const ( // // gazelle:scala_keep_unknown_deps true ScalaKeepUnknownDepsDirective = "scala_keep_unknown_deps" + + // Flag to fixup the deps build building the target and parsing scalac output. + // + // gazelle:scala_fix_deps true + ScalaFixDepsDirective = "scala_fix_deps" ) const TransitiveCommentToken = "# TRANSITIVE" -// TransitiveAttr iterates through deps marked "TRANSITIVE" and removes them if -// the target still builds without it. -func TransitiveAttr(attrName string, r *rule.Rule, file *rule.File, from label.Label) error { +// TransitiveAttr iterates through deps marked "TRANSITIVE" and removes them if the +// target still builds without it. +func TransitiveAttr(attrName string, file *rule.File, r *rule.Rule, from label.Label) (junk []string, err error) { expr := r.Attr(attrName) if expr == nil { - return nil + return nil, nil } deps, isList := expr.(*build.ListExpr) if !isList { - return nil // some other condition we can't deal with + return nil, nil // some other condition we can't deal with + } + + // check that the deps have at least one unknown dep + var hasTransitiveDeps bool + for _, expr := range deps.List { + if str, ok := expr.(*build.StringExpr); ok { + for _, suffix := range str.Comment().Suffix { + if suffix.Token == TransitiveCommentToken { + hasTransitiveDeps = true + break + } + } + } + } + if !hasTransitiveDeps { + return nil, nil // nothing to do } // target should build first time, otherwise we can't check accurately. @@ -49,42 +70,50 @@ func TransitiveAttr(attrName string, r *rule.Rule, file *rule.File, from label.L log.Fatalf("sweep failed (must build cleanly on first attempt): %s", string(out)) } + // iterate the list backwards for i := len(deps.List) - 1; i >= 0; i-- { expr := deps.List[i] - switch t := expr.(type) { - case *build.StringExpr: - if len(t.Comments.Suffix) != 1 { - continue - } - if t.Comments.Suffix[0].Token != "# TRANSITIVE" { - continue - } - dep, err := label.Parse(t.Value) - if err != nil { - return err - } - deps.List = collections.SliceRemoveIndex(deps.List, i) - - if err := file.Save(file.Path); err != nil { - return err - } - - if _, exitCode, _ := bazel.ExecCommand("bazel", "build", from.String()); exitCode == 0 { - log.Println("- 💩 junk:", dep) - } else { - log.Println("- 👑 keep:", dep) - deps.List = collections.SliceInsertAt(deps.List, i, expr) + // look for transitive string dep expressions + dep, ok := expr.(*build.StringExpr) + if !ok { + continue + } + var isTransitiveDep bool + for _, suffix := range dep.Comment().Suffix { + if suffix.Token == TransitiveCommentToken { + isTransitiveDep = true + break } } + if !isTransitiveDep { + continue + } + // reference of original list in case it does not build + original := deps.List + // reset deps with this one spliced out + deps.List = collections.SliceRemoveIndex(deps.List, i) + // save file to reflect change + if err := file.Save(file.Path); err != nil { + return nil, err + } + // see if it still builds + if _, exitCode, _ := bazel.ExecCommand("bazel", "build", from.String()); exitCode == 0 { + log.Println("- 💩 junk:", dep.Value) + junk = append(junk, dep.Value) + } else { + log.Println("- 👑 keep:", dep.Value) + deps.List = original + } } + // final save with possible last change if err := file.Save(file.Path); err != nil { - return err + return nil, err } - return nil + return } func RemoveSweepDirective(file *rule.File) error { @@ -142,10 +171,8 @@ func SetKeepDepsDirective(file *rule.File, value bool) error { return nil } -func MakeTransitiveDep(dep label.Label) *build.StringExpr { - expr := &build.StringExpr{Value: dep.String()} - expr.Comment().Suffix = append(expr.Comment().Suffix, build.Comment{Token: TransitiveCommentToken}) - return expr +func MakeTransitiveComment() build.Comment { + return build.Comment{Token: TransitiveCommentToken} } // IsTransitiveDep returns whether e is marked with a "# TRANSITIVE" comment. @@ -158,3 +185,12 @@ func IsTransitiveDep(e build.Expr) bool { } return false } + +func HasTransitiveRuleComment(r *rule.Rule) bool { + for _, before := range r.Comments() { + if before == TransitiveCommentToken { + return true + } + } + return false +} From 00450119d43eda706fcdc67c069916e1e133ce8e Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Fri, 9 May 2025 12:09:43 -0600 Subject: [PATCH 3/6] checkpoint --- .../gazelle/scala/autokeep/autokeep.pb.go | 212 +++++++++++++----- .../gazelle/scala/autokeep/autokeep.proto | 6 + cmd/autokeep/BUILD.bazel | 5 +- cmd/autokeep/autokeep.go | 7 +- cmd/scalafileextract/scalafileextract.go | 2 +- language/scala/BUILD.bazel | 10 +- language/scala/cache.go | 60 ----- language/scala/existing_scala_rule.go | 74 +++--- language/scala/flags.go | 23 +- language/scala/flags_test.go | 137 ----------- language/scala/generate.go | 4 - language/scala/imports.go | 11 +- language/scala/known_rule_registry.go | 6 +- language/scala/known_scope_registry.go | 22 ++ language/scala/language.go | 17 +- language/scala/lifecycle.go | 7 +- language/scala/repair.go | 91 ++++++++ language/scala/scala_package.go | 65 ++---- language/scala/scala_package_test.go | 7 +- language/scala/scala_rule.go | 90 ++++++-- language/scala/scala_rule_test.go | 15 +- pkg/autokeep/BUILD.bazel | 2 + pkg/autokeep/deps.go | 21 +- pkg/autokeep/deps_test.go | 14 +- pkg/autokeep/scanner.go | 69 ++++-- pkg/collections/BUILD.bazel | 2 + pkg/collections/slice.go | 16 ++ pkg/collections/terminal.go | 1 + pkg/provider/source_provider.go | 47 +++- pkg/resolver/BUILD.bazel | 2 + pkg/resolver/import_map.go | 15 +- pkg/resolver/known_scope_registry.go | 17 ++ pkg/resolver/mocks/BUILD.bazel | 1 + pkg/resolver/mocks/ConflictResolver.go | 30 ++- pkg/resolver/mocks/Scope.go | 43 +++- pkg/resolver/mocks/SymbolProvider.go | 52 +++-- pkg/resolver/mocks/SymbolResolver.go | 20 +- pkg/resolver/mocks/Universe.go | 149 ++++++++++-- pkg/resolver/universe.go | 1 + pkg/scalaconfig/config.go | 22 +- pkg/scalafiles/BUILD.bazel | 1 - pkg/scalafiles/scala_files.go | 19 +- pkg/scalarule/config.go | 18 +- pkg/scalarule/package.go | 5 +- pkg/scalarule/rule.go | 6 +- pkg/sweep/BUILD.bazel | 6 +- pkg/sweep/fix.go | 187 +++++++++++++-- 47 files changed, 1061 insertions(+), 576 deletions(-) delete mode 100644 language/scala/cache.go create mode 100644 language/scala/known_scope_registry.go create mode 100644 language/scala/repair.go create mode 100644 pkg/collections/terminal.go create mode 100644 pkg/resolver/known_scope_registry.go diff --git a/build/stack/gazelle/scala/autokeep/autokeep.pb.go b/build/stack/gazelle/scala/autokeep/autokeep.pb.go index 0ffc597c..94d8187e 100644 --- a/build/stack/gazelle/scala/autokeep/autokeep.pb.go +++ b/build/stack/gazelle/scala/autokeep/autokeep.pb.go @@ -79,6 +79,7 @@ type ScalacError struct { // *ScalacError_MissingSymbol // *ScalacError_NotAMemberOfPackage // *ScalacError_BuildozerUnusedDep + // *ScalacError_NotFound Error isScalacError_Error `protobuf_oneof:"error"` } @@ -156,6 +157,13 @@ func (x *ScalacError) GetBuildozerUnusedDep() *BuildozerUnusedDep { return nil } +func (x *ScalacError) GetNotFound() *TypeNotFound { + if x, ok := x.GetError().(*ScalacError_NotFound); ok { + return x.NotFound + } + return nil +} + type isScalacError_Error interface { isScalacError_Error() } @@ -172,12 +180,18 @@ type ScalacError_BuildozerUnusedDep struct { BuildozerUnusedDep *BuildozerUnusedDep `protobuf:"bytes,5,opt,name=buildozer_unused_dep,json=buildozerUnusedDep,proto3,oneof"` } +type ScalacError_NotFound struct { + NotFound *TypeNotFound `protobuf:"bytes,6,opt,name=not_found,json=notFound,proto3,oneof"` +} + func (*ScalacError_MissingSymbol) isScalacError_Error() {} func (*ScalacError_NotAMemberOfPackage) isScalacError_Error() {} func (*ScalacError_BuildozerUnusedDep) isScalacError_Error() {} +func (*ScalacError_NotFound) isScalacError_Error() {} + type MissingSymbol struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -351,6 +365,61 @@ func (x *BuildozerUnusedDep) GetUnusedDep() string { return "" } +type TypeNotFound struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SourceFile string `protobuf:"bytes,1,opt,name=source_file,json=sourceFile,proto3" json:"source_file,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` +} + +func (x *TypeNotFound) Reset() { + *x = TypeNotFound{} + if protoimpl.UnsafeEnabled { + mi := &file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TypeNotFound) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TypeNotFound) ProtoMessage() {} + +func (x *TypeNotFound) ProtoReflect() protoreflect.Message { + mi := &file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TypeNotFound.ProtoReflect.Descriptor instead. +func (*TypeNotFound) Descriptor() ([]byte, []int) { + return file_build_stack_gazelle_scala_autokeep_autokeep_proto_rawDescGZIP(), []int{5} +} + +func (x *TypeNotFound) GetSourceFile() string { + if x != nil { + return x.SourceFile + } + return "" +} + +func (x *TypeNotFound) GetType() string { + if x != nil { + return x.Type + } + return "" +} + type RuleDeps struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -364,7 +433,7 @@ type RuleDeps struct { func (x *RuleDeps) Reset() { *x = RuleDeps{} if protoimpl.UnsafeEnabled { - mi := &file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes[5] + mi := &file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -377,7 +446,7 @@ func (x *RuleDeps) String() string { func (*RuleDeps) ProtoMessage() {} func (x *RuleDeps) ProtoReflect() protoreflect.Message { - mi := &file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes[5] + mi := &file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -390,7 +459,7 @@ func (x *RuleDeps) ProtoReflect() protoreflect.Message { // Deprecated: Use RuleDeps.ProtoReflect.Descriptor instead. func (*RuleDeps) Descriptor() ([]byte, []int) { - return file_build_stack_gazelle_scala_autokeep_autokeep_proto_rawDescGZIP(), []int{5} + return file_build_stack_gazelle_scala_autokeep_autokeep_proto_rawDescGZIP(), []int{6} } func (x *RuleDeps) GetLabel() string { @@ -426,7 +495,7 @@ type DeltaDeps struct { func (x *DeltaDeps) Reset() { *x = DeltaDeps{} if protoimpl.UnsafeEnabled { - mi := &file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes[6] + mi := &file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -439,7 +508,7 @@ func (x *DeltaDeps) String() string { func (*DeltaDeps) ProtoMessage() {} func (x *DeltaDeps) ProtoReflect() protoreflect.Message { - mi := &file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes[6] + mi := &file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -452,7 +521,7 @@ func (x *DeltaDeps) ProtoReflect() protoreflect.Message { // Deprecated: Use DeltaDeps.ProtoReflect.Descriptor instead. func (*DeltaDeps) Descriptor() ([]byte, []int) { - return file_build_stack_gazelle_scala_autokeep_autokeep_proto_rawDescGZIP(), []int{6} + return file_build_stack_gazelle_scala_autokeep_autokeep_proto_rawDescGZIP(), []int{7} } func (x *DeltaDeps) GetAdd() []*RuleDeps { @@ -486,7 +555,7 @@ var file_build_stack_gazelle_scala_autokeep_autokeep_proto_rawDesc = []byte{ 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x53, 0x63, 0x61, 0x6c, 0x61, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x0c, 0x73, 0x63, 0x61, 0x6c, - 0x61, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0x8d, 0x03, 0x0a, 0x0b, 0x53, 0x63, 0x61, + 0x61, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0xde, 0x03, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6c, 0x61, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x75, 0x6c, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, @@ -510,47 +579,57 @@ var file_build_stack_gazelle_scala_autokeep_autokeep_proto_rawDesc = []byte{ 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x6f, 0x7a, 0x65, 0x72, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x44, 0x65, 0x70, 0x48, 0x00, 0x52, 0x12, 0x62, 0x75, 0x69, - 0x6c, 0x64, 0x6f, 0x7a, 0x65, 0x72, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x44, 0x65, 0x70, 0x42, - 0x07, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x69, 0x0a, 0x0d, 0x4d, 0x69, 0x73, 0x73, - 0x69, 0x6e, 0x67, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, - 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x79, 0x6d, 0x62, - 0x6f, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x62, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x64, 0x42, 0x79, 0x22, 0x50, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x41, 0x4d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x4f, 0x66, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, - 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x22, 0x52, 0x0a, 0x12, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x6f, 0x7a, - 0x65, 0x72, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x44, 0x65, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x72, - 0x75, 0x6c, 0x65, 0x5f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x72, 0x75, 0x6c, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x6e, - 0x75, 0x73, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x44, 0x65, 0x70, 0x22, 0x53, 0x0a, 0x08, 0x52, 0x75, 0x6c, - 0x65, 0x44, 0x65, 0x70, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, - 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x70, 0x73, 0x22, 0x91, - 0x01, 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x44, 0x65, 0x70, 0x73, 0x12, 0x3e, 0x0a, 0x03, - 0x61, 0x64, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x62, 0x75, 0x69, 0x6c, + 0x6c, 0x64, 0x6f, 0x7a, 0x65, 0x72, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x44, 0x65, 0x70, 0x12, + 0x4f, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, + 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x4e, 0x6f, 0x74, 0x46, + 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, + 0x42, 0x07, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x69, 0x0a, 0x0d, 0x4d, 0x69, 0x73, + 0x73, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x79, 0x6d, + 0x62, 0x6f, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, + 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, + 0x65, 0x64, 0x42, 0x79, 0x22, 0x50, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x41, 0x4d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x4f, 0x66, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, + 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x22, 0x52, 0x0a, 0x12, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x6f, + 0x7a, 0x65, 0x72, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x44, 0x65, 0x70, 0x12, 0x1d, 0x0a, 0x0a, + 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x72, 0x75, 0x6c, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x75, + 0x6e, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x44, 0x65, 0x70, 0x22, 0x43, 0x0a, 0x0c, 0x54, 0x79, + 0x70, 0x65, 0x4e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, + 0x53, 0x0a, 0x08, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x65, 0x70, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x69, 0x6c, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, + 0x64, 0x65, 0x70, 0x73, 0x22, 0x91, 0x01, 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x44, 0x65, + 0x70, 0x73, 0x12, 0x3e, 0x0a, 0x03, 0x61, 0x64, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x2c, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, + 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, + 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x65, 0x70, 0x73, 0x52, 0x03, 0x61, + 0x64, 0x64, 0x12, 0x44, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, + 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x65, 0x70, 0x73, + 0x52, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x42, 0x73, 0x0a, 0x22, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, - 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x52, - 0x75, 0x6c, 0x65, 0x44, 0x65, 0x70, 0x73, 0x52, 0x03, 0x61, 0x64, 0x64, 0x12, 0x44, 0x0a, 0x06, - 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, - 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, - 0x70, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x65, 0x70, 0x73, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x6f, - 0x76, 0x65, 0x42, 0x73, 0x0a, 0x22, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, - 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x50, 0x01, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x62, 0x2f, 0x73, 0x63, - 0x61, 0x6c, 0x61, 0x2d, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2f, 0x62, 0x75, 0x69, 0x6c, - 0x64, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2f, - 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x3b, 0x61, - 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x50, 0x01, + 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x74, 0x61, + 0x63, 0x6b, 0x62, 0x2f, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2d, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, + 0x65, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x67, 0x61, + 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2f, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2f, 0x61, 0x75, 0x74, 0x6f, + 0x6b, 0x65, 0x65, 0x70, 0x3b, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -565,28 +644,30 @@ func file_build_stack_gazelle_scala_autokeep_autokeep_proto_rawDescGZIP() []byte return file_build_stack_gazelle_scala_autokeep_autokeep_proto_rawDescData } -var file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_build_stack_gazelle_scala_autokeep_autokeep_proto_goTypes = []interface{}{ (*Diagnostics)(nil), // 0: build.stack.gazelle.scala.autokeep.Diagnostics (*ScalacError)(nil), // 1: build.stack.gazelle.scala.autokeep.ScalacError (*MissingSymbol)(nil), // 2: build.stack.gazelle.scala.autokeep.MissingSymbol (*NotAMemberOfPackage)(nil), // 3: build.stack.gazelle.scala.autokeep.NotAMemberOfPackage (*BuildozerUnusedDep)(nil), // 4: build.stack.gazelle.scala.autokeep.BuildozerUnusedDep - (*RuleDeps)(nil), // 5: build.stack.gazelle.scala.autokeep.RuleDeps - (*DeltaDeps)(nil), // 6: build.stack.gazelle.scala.autokeep.DeltaDeps + (*TypeNotFound)(nil), // 5: build.stack.gazelle.scala.autokeep.TypeNotFound + (*RuleDeps)(nil), // 6: build.stack.gazelle.scala.autokeep.RuleDeps + (*DeltaDeps)(nil), // 7: build.stack.gazelle.scala.autokeep.DeltaDeps } var file_build_stack_gazelle_scala_autokeep_autokeep_proto_depIdxs = []int32{ 1, // 0: build.stack.gazelle.scala.autokeep.Diagnostics.scalac_errors:type_name -> build.stack.gazelle.scala.autokeep.ScalacError 2, // 1: build.stack.gazelle.scala.autokeep.ScalacError.missing_symbol:type_name -> build.stack.gazelle.scala.autokeep.MissingSymbol 3, // 2: build.stack.gazelle.scala.autokeep.ScalacError.not_a_member_of_package:type_name -> build.stack.gazelle.scala.autokeep.NotAMemberOfPackage 4, // 3: build.stack.gazelle.scala.autokeep.ScalacError.buildozer_unused_dep:type_name -> build.stack.gazelle.scala.autokeep.BuildozerUnusedDep - 5, // 4: build.stack.gazelle.scala.autokeep.DeltaDeps.add:type_name -> build.stack.gazelle.scala.autokeep.RuleDeps - 5, // 5: build.stack.gazelle.scala.autokeep.DeltaDeps.remove:type_name -> build.stack.gazelle.scala.autokeep.RuleDeps - 6, // [6:6] is the sub-list for method output_type - 6, // [6:6] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 5, // 4: build.stack.gazelle.scala.autokeep.ScalacError.not_found:type_name -> build.stack.gazelle.scala.autokeep.TypeNotFound + 6, // 5: build.stack.gazelle.scala.autokeep.DeltaDeps.add:type_name -> build.stack.gazelle.scala.autokeep.RuleDeps + 6, // 6: build.stack.gazelle.scala.autokeep.DeltaDeps.remove:type_name -> build.stack.gazelle.scala.autokeep.RuleDeps + 7, // [7:7] is the sub-list for method output_type + 7, // [7:7] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_build_stack_gazelle_scala_autokeep_autokeep_proto_init() } @@ -656,7 +737,7 @@ func file_build_stack_gazelle_scala_autokeep_autokeep_proto_init() { } } file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RuleDeps); i { + switch v := v.(*TypeNotFound); i { case 0: return &v.state case 1: @@ -668,6 +749,18 @@ func file_build_stack_gazelle_scala_autokeep_autokeep_proto_init() { } } file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RuleDeps); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_build_stack_gazelle_scala_autokeep_autokeep_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DeltaDeps); i { case 0: return &v.state @@ -684,6 +777,7 @@ func file_build_stack_gazelle_scala_autokeep_autokeep_proto_init() { (*ScalacError_MissingSymbol)(nil), (*ScalacError_NotAMemberOfPackage)(nil), (*ScalacError_BuildozerUnusedDep)(nil), + (*ScalacError_NotFound)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -691,7 +785,7 @@ func file_build_stack_gazelle_scala_autokeep_autokeep_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_build_stack_gazelle_scala_autokeep_autokeep_proto_rawDesc, NumEnums: 0, - NumMessages: 7, + NumMessages: 8, NumExtensions: 0, NumServices: 0, }, diff --git a/build/stack/gazelle/scala/autokeep/autokeep.proto b/build/stack/gazelle/scala/autokeep/autokeep.proto index 3bcdd50c..00f481e0 100644 --- a/build/stack/gazelle/scala/autokeep/autokeep.proto +++ b/build/stack/gazelle/scala/autokeep/autokeep.proto @@ -19,6 +19,7 @@ message ScalacError { MissingSymbol missing_symbol = 3; NotAMemberOfPackage not_a_member_of_package = 4; BuildozerUnusedDep buildozer_unused_dep = 5; + TypeNotFound not_found = 6; } } @@ -38,6 +39,11 @@ message BuildozerUnusedDep { string unused_dep = 2; } +message TypeNotFound { + string source_file = 1; + string type = 2; +} + message RuleDeps { string label = 1; string build_file = 2; diff --git a/cmd/autokeep/BUILD.bazel b/cmd/autokeep/BUILD.bazel index 97f279f2..6246d459 100644 --- a/cmd/autokeep/BUILD.bazel +++ b/cmd/autokeep/BUILD.bazel @@ -6,7 +6,10 @@ go_library( srcs = ["autokeep.go"], importpath = "github.com/stackb/scala-gazelle/cmd/autokeep", visibility = ["//visibility:private"], - deps = ["//pkg/autokeep"], + deps = [ + "//pkg/autokeep", + "//pkg/resolver", + ], ) go_binary( diff --git a/cmd/autokeep/autokeep.go b/cmd/autokeep/autokeep.go index 654f4b90..9f6ae313 100644 --- a/cmd/autokeep/autokeep.go +++ b/cmd/autokeep/autokeep.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/stackb/scala-gazelle/pkg/autokeep" + "github.com/stackb/scala-gazelle/pkg/resolver" ) // autokeep is a program that consumes a scala-gazelle cache file, runs 'bazel @@ -103,7 +104,11 @@ func run(cfg *config) error { } log.Printf("diagnostics: %+v", diagnostics) - keep := autokeep.MakeDeltaDeps(cfg.deps, diagnostics) + + // FIXME(pcj): how to derive the global scope again from this command? + scope := resolver.NewTrieScope() + + keep := autokeep.MakeDeltaDeps(diagnostics, scope, cfg.deps) log.Printf("keep: %+v", keep) if err := autokeep.ApplyDeltaDeps(keep, cfg.keep); err != nil { diff --git a/cmd/scalafileextract/scalafileextract.go b/cmd/scalafileextract/scalafileextract.go index c608b1d5..cdfb4ab2 100644 --- a/cmd/scalafileextract/scalafileextract.go +++ b/cmd/scalafileextract/scalafileextract.go @@ -106,7 +106,7 @@ func persistentWork(cfg *Config) error { batchCfg.Cwd = cfg.Cwd if err := batchWork(&batchCfg); err != nil { - return fmt.Errorf("performing persistent batch!: %v", err) + return fmt.Errorf("performing persistent batch: %v", err) } if err := protobuf.WriteDelimitedTo(&resp, os.Stdout); err != nil { diff --git a/language/scala/BUILD.bazel b/language/scala/BUILD.bazel index 4b21a6b1..af2811cb 100644 --- a/language/scala/BUILD.bazel +++ b/language/scala/BUILD.bazel @@ -7,7 +7,6 @@ load("@bazel_gazelle//:def.bzl", "gazelle_binary") go_library( name = "scala", srcs = [ - "cache.go", "configure.go", "conflict_resolver_registry.go", "coverage.go", @@ -21,11 +20,13 @@ go_library( "imports.go", "kinds.go", "known_rule_registry.go", + "known_scope_registry.go", "language.go", "lifecycle.go", "loads.go", "package_marker_rule.go", "progress.go", + "repair.go", "resolve.go", "scala_package.go", "scala_rule.go", @@ -86,10 +87,6 @@ gazelle_binary( go_test( name = "scala_test", srcs = [ - # "coverage_test.go", - # "diff_test.go", - # "existing_scala_rule_test.go", - # "flags_test.go", "coverage_test.go", "diff_test.go", "existing_scala_rule_test.go", @@ -134,7 +131,6 @@ package_filegroup( name = "filegroup", srcs = [ "BUILD.bazel", - "cache.go", "configure.go", "conflict_resolver_registry.go", "coverage.go", @@ -153,6 +149,7 @@ package_filegroup( "imports.go", "kinds.go", "known_rule_registry.go", + "known_scope_registry.go", "language.go", "language_test.go", "lifecycle.go", @@ -160,6 +157,7 @@ package_filegroup( "loads_test.go", "package_marker_rule.go", "progress.go", + "repair.go", "resolve.go", "scala.WORKSPACE", "scala_package.go", diff --git a/language/scala/cache.go b/language/scala/cache.go deleted file mode 100644 index 398a86a2..00000000 --- a/language/scala/cache.go +++ /dev/null @@ -1,60 +0,0 @@ -package scala - -import ( - "log" - "time" - - "github.com/bazelbuild/bazel-gazelle/label" - scpb "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/cache" - "github.com/stackb/scala-gazelle/pkg/parser" - "github.com/stackb/scala-gazelle/pkg/protobuf" -) - -const debugCache = false - -func (sl *scalaLang) readScalaRuleCacheFile() error { - t1 := time.Now() - - if err := protobuf.ReadFile(sl.cacheFileFlagValue, &sl.cache); err != nil { - return err - } - - if sl.cacheKeyFlagValue != sl.cache.Key { - if debugCache { - log.Printf("scala-gazelle cache invalidated! (want %q, got %q)", sl.cacheFileFlagValue, sl.cache.Key) - } - sl.cache = scpb.Cache{} - return nil - } - - parser.SortRules(sl.cache.Rules) - - for _, rule := range sl.cache.Rules { - from, err := label.Parse(rule.Label) - if err != nil { - return err - } - if err := sl.parser.LoadScalaRule(from, rule); err != nil { - return err - } - } - - if debugCache { - t2 := time.Since(t1).Round(1 * time.Millisecond) - log.Printf("Read cache %s (%d rules) %v", sl.cacheFileFlagValue, len(sl.cache.Rules), t2) - } - - return nil -} - -func (sl *scalaLang) writeScalaRuleCacheFile() error { - sl.cache.PackageCount = int32(len(sl.packages)) - sl.cache.Rules = sl.parser.ScalaRules() - sl.cache.Key = sl.cacheKeyFlagValue - - if debugCache { - log.Printf("Wrote scala-gazelle cache %s (%d rules)", sl.cacheFileFlagValue, len(sl.cache.Rules)) - } - - return protobuf.WriteFile(sl.cacheFileFlagValue, &sl.cache) -} diff --git a/language/scala/existing_scala_rule.go b/language/scala/existing_scala_rule.go index 43ebb87a..28c4735f 100644 --- a/language/scala/existing_scala_rule.go +++ b/language/scala/existing_scala_rule.go @@ -15,7 +15,6 @@ import ( "github.com/stackb/scala-gazelle/pkg/protobuf" "github.com/stackb/scala-gazelle/pkg/scalaconfig" "github.com/stackb/scala-gazelle/pkg/scalarule" - "github.com/stackb/scala-gazelle/pkg/sweep" sppb "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/parse" ) @@ -75,7 +74,7 @@ func (s *existingScalaRuleProvider) ProvideRule(cfg *scalarule.Config, pkg scala // ResolveRule implements the RuleResolver interface. func (s *existingScalaRuleProvider) ResolveRule(cfg *scalarule.Config, pkg scalarule.Package, r *rule.Rule) scalarule.RuleProvider { - scalaRule, err := pkg.ParseRule(r, "srcs") + scalaRule, err := pkg.NewScalaRule(r) if err != nil { if err != ErrRuleHasNoSrcs { log.Printf("skipping %s %s: unable to collect srcs: %v", r.Kind(), r.Name(), err) @@ -86,8 +85,9 @@ func (s *existingScalaRuleProvider) ResolveRule(cfg *scalarule.Config, pkg scala log.Panicln("scalaRule should not be nil!") } + // stash the scalaRule instance as the GazelleImportsKey so that it triggers + // the resolve phase. r.SetPrivateAttr(config.GazelleImportsKey, scalaRule) - r.SetPrivateAttr("_scala_files", scalaRule.Files()) return &existingScalaRule{cfg, pkg, r, scalaRule, s.isBinary, s.isLibrary, s.isTest} } @@ -131,40 +131,50 @@ func (s *existingScalaRule) Resolve(rctx *scalarule.ResolveContext, importsRaw i } sc := scalaconfig.Get(rctx.Config) - imports := scalaRule.ResolveImports(rctx) - sc.Imports(imports, rctx.Rule, "deps", rctx.From) - - commentsSrcs := rctx.Rule.AttrComments("srcs") - if commentsSrcs != nil { - commentsSrcs.Before = nil - if sc.ShouldAnnotateImports() { - scalaconfig.AnnotateImports(imports, commentsSrcs, "import: ") - } - if sc.ShouldAnnotateRule() { - ruleComments := makeRuleComments(scalaRule.pb) - commentsSrcs.Before = append(commentsSrcs.Before, ruleComments...) - } - } - if s.isLibrary { - exports := scalaRule.ResolveExports(rctx) - sc.Exports(exports, rctx.Rule, "exports", rctx.From) - } + resolveClosure := func() { + imports := scalaRule.ResolveImports(rctx) + + sc.MergeDepsAttr(imports, rctx.Rule, "deps", rctx.From) - if sc.ShouldSweepTransitive("deps") { - if !sweep.HasTransitiveRuleComment(rctx.Rule) { - if junk, err := sweep.TransitiveAttr("deps", rctx.File, rctx.Rule, rctx.From); err != nil { - log.Printf("warning: transitive sweep failed: %v", err) - } else { - if len(junk) > 0 { - log.Println(formatBuildozerRemoveDeps(rctx.From, junk)) - } + commentsSrcs := rctx.Rule.AttrComments("srcs") + if commentsSrcs != nil { + commentsSrcs.Before = nil + if sc.ShouldAnnotateImports() { + scalaconfig.AnnotateImports(imports, commentsSrcs, "import: ") + } + if sc.ShouldAnnotateRule() { + ruleComments := makeRuleComments(scalaRule.pb) + commentsSrcs.Before = append(commentsSrcs.Before, ruleComments...) } - rctx.Rule.AddComment(sweep.TransitiveCommentToken) - } else { - log.Println("> transitive sweep skipped (already done):", rctx.From) } + + if s.isLibrary { + exports := scalaRule.ResolveExports(rctx) + sc.MergeDepsAttr(exports, rctx.Rule, "exports", rctx.From) + } + + // if sc.ShouldSweepTransitive("deps") { + // if !sweep.HasTransitiveRuleComment(rctx.Rule) { + // if junk, err := sweep.TransitiveAttr("deps", rctx.File, rctx.Rule, rctx.From); err != nil { + // log.Printf("warning: transitive sweep failed: %v", err) + // } else { + // if len(junk) > 0 { + // log.Println(formatBuildozerRemoveDeps(rctx.From, junk)) + // } + // } + // rctx.Rule.AddComment(sweep.TransitiveCommentToken) + // } else { + // log.Println("> transitive sweep skipped (already done):", rctx.From) + // } + // } } + + // wow... this cannot be good programming practive, but I need to store the + // ability to re-resolve dependencies, so stashing this in the rule... + rctx.Rule.SetPrivateAttr("_scala_resolve_closure", resolveClosure) + + resolveClosure() } func makeRuleComments(pb *sppb.Rule) (comments []build.Comment) { diff --git a/language/scala/flags.go b/language/scala/flags.go index aa46720b..4b9f3fbe 100644 --- a/language/scala/flags.go +++ b/language/scala/flags.go @@ -1,10 +1,8 @@ package scala import ( - "errors" "flag" "fmt" - "io/fs" "log" "os" "path/filepath" @@ -28,8 +26,8 @@ const ( scalaGazelleCacheFileFlagName = "scala_gazelle_cache_file" scalaGazelleImportsFileFlagName = "scala_gazelle_imports_file" scalaGazelleDebugProcessFileFlagName = "scala_gazelle_debug_process" - scalaGazelleCacheKeyFlagName = "scala_gazelle_cache_key" scalaGazellePrintCacheKeyFlagName = "scala_gazelle_print_cache_key" + scalaGazelleFixDepsModeFlagName = "scala_gazelle_fix_deps_mode" cpuprofileFileFlagName = "cpuprofile_file" memprofileFileFlagName = "memprofile_file" logFileFlagName = "log_file" @@ -44,7 +42,6 @@ func (sl *scalaLang) RegisterFlags(flags *flag.FlagSet, cmd string, c *config.Co flags.BoolVar(&sl.existingScalaRuleCoverageFlagValue, existingScalaRuleCoverageFlagName, true, "report coverage statistics") flags.StringVar(&sl.cacheFileFlagValue, scalaGazelleCacheFileFlagName, "", "optional path a cache file (.json or .pb)") flags.StringVar(&sl.importsFileFlagValue, scalaGazelleImportsFileFlagName, "", "optional path to an imports file where resolved imports should be written (.json or .pb)") - flags.StringVar(&sl.cacheKeyFlagValue, scalaGazelleCacheKeyFlagName, "", "optional string that can be used to bust the cache file") flags.StringVar(&sl.cpuprofileFlagValue, cpuprofileFileFlagName, "", "optional path a cpuprofile file (.prof)") flags.StringVar(&sl.memprofileFlagValue, memprofileFileFlagName, "", "optional path a memory profile file (.prof)") flags.Var(&sl.symbolProviderNamesFlagValue, scalaSymbolProviderFlagName, "name of a symbol provider implementation to enable") @@ -53,6 +50,7 @@ func (sl *scalaLang) RegisterFlags(flags *flag.FlagSet, cmd string, c *config.Co flags.Var(&sl.existingScalaBinaryRulesFlagValue, existingScalaBinaryRuleFlagName, "LOAD%NAME mapping for a custom existing scala binary rule implementation (e.g. '@io_bazel_rules_scala//scala:scala.bzl%scalabinary'") flags.Var(&sl.existingScalaLibraryRulesFlagValue, existingScalaLibraryRuleFlagName, "LOAD%NAME mapping for a custom existing scala library rule implementation (e.g. '@io_bazel_rules_scala//scala:scala.bzl%scala_library'") flags.Var(&sl.existingScalaTestRulesFlagValue, existingScalaTestRuleFlagName, "LOAD%NAME mapping for a custom existing scala test rule implementation (e.g. '@io_bazel_rules_scala//scala:scala.bzl%scala_test'") + flags.Var(&sl.repairMode, scalaGazelleFixDepsModeFlagName, "optional deps repair mode (one of 'none', 'batch', 'watch')") sl.registerSymbolProviders(flags, cmd, c) sl.registerConflictResolvers(flags, cmd, c) @@ -78,6 +76,7 @@ func (sl *scalaLang) CheckFlags(flags *flag.FlagSet, c *config.Config) error { collections.PrintProcessIdForDelveAndWait() } + sl.repoRoot = c.RepoRoot sl.symbolResolver = newUniverseResolver(sl, sl.globalPackages) if err := sl.setupSymbolProviders(flags, c, sl.symbolProviderNamesFlagValue); err != nil { @@ -98,9 +97,6 @@ func (sl *scalaLang) CheckFlags(flags *flag.FlagSet, c *config.Config) error { if err := sl.setupExistingScalaTestRules(sl.existingScalaTestRulesFlagValue); err != nil { return err } - if err := sl.setupCache(); err != nil { - return err - } if err := sl.setupCpuProfiling(c.WorkDir); err != nil { return err } @@ -239,19 +235,6 @@ func (sl *scalaLang) setupExistingScalaTestRule(fqn, load, kind string) error { return sl.ruleProviderRegistry.RegisterProvider(fqn, provider) } -func (sl *scalaLang) setupCache() error { - if sl.cacheFileFlagValue != "" { - sl.cacheFileFlagValue = os.ExpandEnv(sl.cacheFileFlagValue) - if err := sl.readScalaRuleCacheFile(); err != nil { - // don't report error if the file does not exist yet - if !errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("reading cache file: %w", err) - } - } - } - return nil -} - func (sl *scalaLang) dumpResolvedImportMap() { if sl.importsFileFlagValue == "" { return diff --git a/language/scala/flags_test.go b/language/scala/flags_test.go index 9c8eb2d4..c04e54e3 100644 --- a/language/scala/flags_test.go +++ b/language/scala/flags_test.go @@ -1,153 +1,16 @@ package scala import ( - "flag" "fmt" - "os" - "strings" "testing" - "github.com/bazelbuild/bazel-gazelle/config" "github.com/bazelbuild/bazel-gazelle/rule" - "github.com/bazelbuild/bazel-gazelle/testtools" "github.com/google/go-cmp/cmp" "github.com/stackb/scala-gazelle/pkg/scalarule" "github.com/stackb/scala-gazelle/pkg/testutil" ) -func TestCacheFlags(t *testing.T) { - for name, tc := range map[string]struct { - args []string - files []testtools.FileSpec - wantErr error - check func(t *testing.T, tmpDir string, lang *scalaLang) - }{ - "scala_gazelle_cache_file": { - files: []testtools.FileSpec{ - { - Path: "maven_install.json", - Content: "{}", - }, - { - Path: "./cache.json", - Content: `{"package_count": 100}`, - }, - }, - args: []string{ - "-maven_install_json_file=./maven_install.json", - "-scala_gazelle_cache_file=${TEST_TMPDIR}/cache.json", - }, - check: func(t *testing.T, tmpDir string, lang *scalaLang) { - cacheFile := strings.TrimPrefix(strings.TrimPrefix(lang.cacheFileFlagValue, tmpDir), "/") - if diff := cmp.Diff("cache.json", cacheFile); diff != "" { - t.Errorf("cacheFile (-want got):\n%s", diff) - } - if diff := cmp.Diff(int32(100), lang.cache.PackageCount); diff != "" { - t.Errorf("PackageCount (-want got):\n%s", diff) - } - }, - }, - "scala_gazelle_print_cache_key_on": { - args: []string{ - "-scala_gazelle_cache_key=12345", - "-scala_gazelle_print_cache_key=true", - }, - check: func(t *testing.T, tmpDir string, lang *scalaLang) { - if diff := cmp.Diff("12345", lang.cacheKeyFlagValue); diff != "" { - t.Errorf("cacheKeyFlagValue (-want got):\n%s", diff) - } - if diff := cmp.Diff(true, lang.printCacheKey); diff != "" { - t.Errorf("printCacheKey (-want got):\n%s", diff) - } - }, - }, - "scala_gazelle_print_cache_key_off": { - args: []string{ - "-scala_gazelle_print_cache_key=false", - }, - check: func(t *testing.T, tmpDir string, lang *scalaLang) { - if diff := cmp.Diff(false, lang.printCacheKey); diff != "" { - t.Errorf("printCacheKey (-want got):\n%s", diff) - } - }, - }, - "scala_gazelle_cache_key__valid": { - files: []testtools.FileSpec{ - { - Path: "maven_install.json", - Content: "{}", - }, - { - Path: "./cache.json", - Content: `{"package_count": 100, "key": "1"}`, - }, - }, - args: []string{ - "-maven_install_json_file=./maven_install.json", - "-scala_gazelle_cache_file=${TEST_TMPDIR}/cache.json", - "-scala_gazelle_cache_key=1", - }, - check: func(t *testing.T, tmpDir string, lang *scalaLang) { - if lang.cache.PackageCount == 0 { - t.Error("expected cache to be valid!") - } - }, - }, - "scala_gazelle_cache_key__invalid": { - files: []testtools.FileSpec{ - { - Path: "maven_install.json", - Content: "{}", - }, - { - Path: "./cache.json", - Content: `{"package_count": 100, "key": "1"}`, - }, - }, - args: []string{ - "-maven_install_json_file=./maven_install.json", - "-scala_gazelle_cache_file=${TEST_TMPDIR}/cache.json", - "-scala_gazelle_cache_key=2", - }, - check: func(t *testing.T, tmpDir string, lang *scalaLang) { - if lang.cache.PackageCount != 0 { - t.Error("expected cache to be invalidated!") - } - }, - }, - } { - t.Run(name, func(t *testing.T) { - tmpDir, _, cleanup := testutil.MustPrepareTestFiles(t, tc.files) - if true { - defer cleanup() - } - - os.Setenv("TEST_TMPDIR", tmpDir) - lang := NewLanguage().(*scalaLang) - - fs := flag.NewFlagSet(scalaLangName, flag.ExitOnError) - c := &config.Config{ - WorkDir: tmpDir, - Exts: make(map[string]interface{}), - } - - lang.RegisterFlags(fs, "", c) - if err := fs.Parse(tc.args); err != nil { - t.Fatal(err) - } - - if err := lang.CheckFlags(fs, c); err != nil { - t.Fatal(err) - } - - if tc.check != nil { - tc.check(t, tmpDir, lang) - } - }) - } -} - func TestParseScalaExistingRules(t *testing.T) { for name, tc := range map[string]struct { providerNames []string diff --git a/language/scala/generate.go b/language/scala/generate.go index d3039633..148184d6 100644 --- a/language/scala/generate.go +++ b/language/scala/generate.go @@ -23,10 +23,6 @@ func (sl *scalaLang) GenerateRules(args language.GenerateArgs) (result language. return } - if sl.wantProgress && sl.cache.PackageCount > 0 { - writeGenerateProgress(sl.progress, len(sl.packages), int(sl.cache.PackageCount)) - } - logger := sl.logger.With().Str("rel", args.Rel).Logger() pkg := newScalaPackage(logger, args, sc, sl.ruleProviderRegistry, sl.parser, sl) sl.packages[args.Rel] = pkg diff --git a/language/scala/imports.go b/language/scala/imports.go index 515f18c4..0bbea231 100644 --- a/language/scala/imports.go +++ b/language/scala/imports.go @@ -9,14 +9,15 @@ import ( "github.com/bazelbuild/bazel-gazelle/label" scpb "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/cache" "github.com/stackb/scala-gazelle/pkg/protobuf" + "github.com/stackb/scala-gazelle/pkg/resolver" ) -func (sl *scalaLang) writeResolvedImportsMapFile(filename string) error { +func makeResolvedImports(scope resolver.Scope) *scpb.ResolvedImports { imports := &scpb.ResolvedImports{ Imports: make(map[string]string), } - for _, sym := range sl.globalScope.GetSymbols("") { + for _, sym := range scope.GetSymbols("") { dep := "NO_LABEL" if sym.Label != label.NoLabel { dep = sym.Label.String() @@ -24,6 +25,12 @@ func (sl *scalaLang) writeResolvedImportsMapFile(filename string) error { imports.Imports[sym.Name] = dep } + return imports +} + +func (sl *scalaLang) writeResolvedImportsMapFile(filename string) error { + imports := makeResolvedImports(sl.globalScope) + if filepath.Ext(filename) == ".txt" { f, err := os.Create(filename) if err != nil { diff --git a/language/scala/known_rule_registry.go b/language/scala/known_rule_registry.go index 2e444048..7aa6be75 100644 --- a/language/scala/known_rule_registry.go +++ b/language/scala/known_rule_registry.go @@ -7,15 +7,13 @@ import ( "github.com/bazelbuild/bazel-gazelle/rule" ) -// GetKnownRule implements part of the -// resolver.KnownRuleRegistry interface. +// GetKnownRule implements part of the resolver.KnownRuleRegistry interface. func (sl *scalaLang) GetKnownRule(from label.Label) (*rule.Rule, bool) { r, ok := sl.knownRules[from] return r, ok } -// PutKnownRule implements part of the -// resolver.KnownRuleRegistry interface. +// PutKnownRule implements part of the resolver.KnownRuleRegistry interface. func (sl *scalaLang) PutKnownRule(from label.Label, r *rule.Rule) error { if _, ok := sl.knownRules[from]; ok { return fmt.Errorf("duplicate known rule: %s", from) diff --git a/language/scala/known_scope_registry.go b/language/scala/known_scope_registry.go new file mode 100644 index 00000000..2dd6a042 --- /dev/null +++ b/language/scala/known_scope_registry.go @@ -0,0 +1,22 @@ +package scala + +import ( + "fmt" + + "github.com/stackb/scala-gazelle/pkg/resolver" +) + +// GetKnownScope implements part of the resolver.KnownScopeRegistry interface. +func (sl *scalaLang) GetKnownScope(name string) (resolver.Scope, bool) { + scope, ok := sl.knownScopes[name] + return scope, ok +} + +// PutKnownScope implements part of the resolver.KnownScopeRegistry interface. +func (sl *scalaLang) PutKnownScope(name string, scope resolver.Scope) error { + if _, ok := sl.knownScopes[name]; ok { + return fmt.Errorf("duplicate known rule: %s", name) + } + sl.knownScopes[name] = scope + return nil +} diff --git a/language/scala/language.go b/language/scala/language.go index d1a1ac32..82a26ab2 100644 --- a/language/scala/language.go +++ b/language/scala/language.go @@ -12,7 +12,6 @@ import ( "github.com/rs/zerolog" "github.com/stackb/rules_proto/pkg/protoc" - scpb "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/cache" "github.com/stackb/scala-gazelle/pkg/collections" "github.com/stackb/scala-gazelle/pkg/parser" "github.com/stackb/scala-gazelle/pkg/procutil" @@ -27,6 +26,8 @@ const scalaLangName = "scala" // scalaLang implements language.Language. type scalaLang struct { + // repoRoot is the config.RepoRoot + repoRoot string // debugProcessFlagValue halts processing and prints the PID for attaching a // delve debugger. debugProcessFlagValue bool @@ -56,12 +57,12 @@ type scalaLang struct { existingScalaLibraryRulesFlagValue collections.StringSlice // existingScalaLibraryRulesFlagValue is the value of the // existing_scala_test_rule repeatable flag - existingScalaTestRulesFlagValue collections.StringSlice + existingScalaTestRulesFlagValue collections.StringSlice + // repairMode for fixing scala deps + repairMode repairMode cpuprofileFlagValue string existingScalaRuleCoverageFlagValue bool memprofileFlagValue string - // cache is the loaded cache, if configured - cache scpb.Cache // ruleProviderRegistry is the rule registry implementation. This holds the // rules configured via gazelle directives by the user. ruleProviderRegistry scalarule.ProviderRegistry @@ -80,6 +81,8 @@ type scalaLang struct { progress mobyprogress.Output // knownRules is a map of all known generated rules knownRules map[label.Label]*rule.Rule + // knownScopes is a map of all known scopes + knownScopes map[string]resolver.Scope // conflictResolvers is a map of all known conflict resolver implementations conflictResolvers map[string]resolver.ConflictResolver // depsCleaners is a map of all known deps cleaner implementations @@ -97,7 +100,7 @@ type scalaLang struct { // sourceProvider is the sourceProvider implementation. sourceProvider *provider.SourceProvider // parser is the parser instance - parser *parser.MemoParser + parser parser.Parser // logFileName is the name of the log file // logFile is the open log logFile *os.File @@ -112,10 +115,10 @@ func NewLanguage() language.Language { lang := &scalaLang{ wantProgress: wantProgress(), - cache: scpb.Cache{}, globalScope: resolver.NewTrieScope(), globalPackages: resolver.NewTrieScope(), knownRules: make(map[label.Label]*rule.Rule), + knownScopes: make(map[string]resolver.Scope), conflictResolvers: make(map[string]resolver.ConflictResolver), depsCleaners: make(map[string]resolver.DepsCleaner), packages: make(map[string]*scalaPackage), @@ -135,7 +138,7 @@ func NewLanguage() language.Language { lang.sourceProvider = provider.NewSourceProvider(logger.With().Str("provider", "source").Logger(), progress) semanticProvider := provider.NewSemanticdbProvider(lang.sourceProvider) - lang.parser = parser.NewMemoParser(semanticProvider) + lang.parser = semanticProvider javaProvider := provider.NewJavaProvider() lang.AddSymbolProvider(lang.sourceProvider) diff --git a/language/scala/lifecycle.go b/language/scala/lifecycle.go index 73555ca5..00b827a6 100644 --- a/language/scala/lifecycle.go +++ b/language/scala/lifecycle.go @@ -23,12 +23,6 @@ func (sl *scalaLang) onResolve() { } else { sl.globalScope = scalaScope } - - if sl.cacheFileFlagValue != "" { - if err := sl.writeScalaRuleCacheFile(); err != nil { - log.Fatalf("failed to write cache: %v", err) - } - } } // onEnd is called when the last rule has been resolved. @@ -46,6 +40,7 @@ func (sl *scalaLang) onEnd() { } } + sl.repair() sl.dumpResolvedImportMap() sl.reportCoverage(log.Printf) sl.stopCpuProfiling() diff --git a/language/scala/repair.go b/language/scala/repair.go new file mode 100644 index 00000000..6e9cbba8 --- /dev/null +++ b/language/scala/repair.go @@ -0,0 +1,91 @@ +package scala + +import ( + "fmt" + "log" + + "github.com/bazelbuild/bazel-gazelle/label" + "github.com/bazelbuild/bazel-gazelle/rule" + "github.com/stackb/scala-gazelle/pkg/scalarule" + "github.com/stackb/scala-gazelle/pkg/sweep" +) + +type repairMode int + +const ( + RepairNone repairMode = iota + RepairBatch + RepairWatch +) + +// String partially implements the flag.Value interface. +func (i *repairMode) String() string { + switch *i { + case RepairNone: + return "none" + case RepairBatch: + return "batch" + case RepairWatch: + return "watch" + } + return "unknown" +} + +// Set implements the flag.Value interface. +func (i *repairMode) Set(value string) error { + switch value { + case "", "none": + *i = RepairNone + case "batch": + *i = RepairBatch + case "watch": + *i = RepairWatch + default: + return fmt.Errorf("unknown repair value: %s", value) + } + return nil +} + +func (sl *scalaLang) repair() { + if err := sl.repairDeps(sl.repairMode); err != nil { + log.Printf("warning: repair failed: %v", err) + } +} + +func (sl *scalaLang) repairDeps(mode repairMode) error { + switch mode { + case RepairBatch: + return sl.repairBatch() + case RepairWatch: + return sl.repairWatch() + default: + return nil + } +} + +func (sl *scalaLang) repairBatch() error { + rules := gatherResolvableScalaRuleMap(sl.knownRules) + imports := makeResolvedImports(sl.globalScope) + + fixer := sweep.NewDepFixer(sl.progress, sl.repoRoot, "", rules, imports.Imports, sl) + return fixer.Batch() +} + +func (sl *scalaLang) repairWatch() error { + return fmt.Errorf("repairWatch unimplemented") +} + +func gatherResolvableScalaRuleMap(knownRules map[label.Label]*rule.Rule) sweep.ResolvableScalaRuleMap { + scalaRules := make(sweep.ResolvableScalaRuleMap) + + for _, knownRule := range knownRules { + scalaRule, ok := scalarule.GetRule(knownRule) + if !ok { + continue + } + resolveFunc := knownRule.PrivateAttr("_scala_resolve_closure").(func()) + scalaRules[scalaRule] = resolveFunc + } + + return scalaRules +} diff --git a/language/scala/scala_package.go b/language/scala/scala_package.go index b55d8754..942aa2c4 100644 --- a/language/scala/scala_package.go +++ b/language/scala/scala_package.go @@ -3,8 +3,6 @@ package scala import ( "fmt" "log" - "path/filepath" - "strings" "github.com/bazelbuild/bazel-gazelle/config" "github.com/bazelbuild/bazel-gazelle/label" @@ -14,13 +12,10 @@ import ( "github.com/bazelbuild/bazel-gazelle/rule" "github.com/rs/zerolog" - sppb "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/parse" - "github.com/stackb/scala-gazelle/pkg/glob" "github.com/stackb/scala-gazelle/pkg/parser" "github.com/stackb/scala-gazelle/pkg/resolver" "github.com/stackb/scala-gazelle/pkg/scalaconfig" "github.com/stackb/scala-gazelle/pkg/scalarule" - "github.com/stackb/scala-gazelle/pkg/sweep" ) const ( @@ -232,53 +227,30 @@ func (s *scalaPackage) GeneratedRules() (rules []*rule.Rule) { return } -// ParseRule implements part of the scalarule.Package interface. -func (s *scalaPackage) ParseRule(r *rule.Rule, attrName string) (scalaRule scalarule.Rule, err error) { - dir := filepath.Join(s.repoRootDir(), s.args.Rel) - - // collect and filter .scala files from the `srcs` attribute. - srcs, err := glob.CollectFilenames(s.args.File, dir, r.Attr(attrName)) - if err != nil { - return nil, err - } - scalaSrcs := make([]string, 0, len(srcs)) - for _, src := range srcs { - if !strings.HasSuffix(src, ".scala") { - continue - } - scalaSrcs = append(scalaSrcs, src) - } - if len(scalaSrcs) == 0 { - err = ErrRuleHasNoSrcs - } - - logger := s.logger.With().Str("kind", r.Kind()).Str("name", r.Name()).Logger() - logger.Debug().Msgf("%d scala files collected from %s", len(scalaSrcs), attrName) - - from := s.cfg.MaybeRewrite(r.Kind(), label.Label{Pkg: s.args.Rel, Name: r.Name()}) - - rule := &sppb.Rule{ - Label: from.String(), - Kind: r.Kind(), - } - if len(scalaSrcs) > 0 { - rule, err = s.parser.ParseScalaRule(r.Kind(), from, dir, scalaSrcs...) - if err != nil { - logger.Warn().Err(err).Msg("parse error") - return nil, err - } - } +// NewScalaRule implements part of the scalarule.Package interface. +func (s *scalaPackage) NewScalaRule(r *rule.Rule) (scalarule.Rule, error) { ctx := &scalaRuleContext{ + repoRoot: s.repoRootDir(), + file: s.args.File, rule: r, scalaConfig: s.cfg, resolver: s.universe, scope: s.universe, + knownScopes: s.universe, + parser: s.parser, } - scalaRule = newScalaRule(logger, ctx, rule) + logger := s.logger.With().Str("kind", r.Kind()).Str("name", r.Name()).Logger() + from := s.cfg.MaybeRewrite(r.Kind(), label.Label{Pkg: s.args.Rel, Name: r.Name()}) + + scalaRule := newScalaRule(logger, ctx, from) - return + if err := scalaRule.ParseSrcs(); err != nil { + return nil, err + } + + return scalaRule, nil } // repoRootDir return the root directory of the repo. @@ -330,13 +302,6 @@ func (p *scalaPackage) infof(format string, args ...any) string { // OnEnd is a lifecycle hook that gets called when the resolve phase has // ended. func (p *scalaPackage) OnEnd() error { - if p.cfg.ShouldFixDeps() { - fixer := sweep.NewDepFixer(p.args.Config, p.args.Rel) - - if err := fixer.Run(); err != nil { - return err - } - } return nil } diff --git a/language/scala/scala_package_test.go b/language/scala/scala_package_test.go index d9d30ba8..c43cec58 100644 --- a/language/scala/scala_package_test.go +++ b/language/scala/scala_package_test.go @@ -15,10 +15,9 @@ import ( "github.com/stackb/scala-gazelle/pkg/scalaconfig" ) -func TestScalaPackageParseRule(t *testing.T) { +func TestScalaPackageNewScalaRule(t *testing.T) { for name, tc := range map[string]struct { rule *rule.Rule - attrName string wantFiles []*sppb.File wantErr string }{ @@ -50,7 +49,7 @@ func TestScalaPackageParseRule(t *testing.T) { } var gotErr string - got, gotError := pkg.ParseRule(tc.rule, tc.attrName) + got, gotError := pkg.NewScalaRule(tc.rule) if gotError != nil { gotErr = gotError.Error() } @@ -58,7 +57,7 @@ func TestScalaPackageParseRule(t *testing.T) { if diff := cmp.Diff(tc.wantErr, gotErr); diff != "" { t.Errorf("error (-want +got):\n%s", diff) } - if diff := cmp.Diff(tc.wantFiles, got.Files()); diff != "" { + if diff := cmp.Diff(tc.wantFiles, got.Rule().Files); diff != "" { t.Errorf("(-want +got):\n%s", diff) } }) diff --git a/language/scala/scala_rule.go b/language/scala/scala_rule.go index 871b15b5..69ca1e89 100644 --- a/language/scala/scala_rule.go +++ b/language/scala/scala_rule.go @@ -16,6 +16,8 @@ import ( "github.com/stackb/scala-gazelle/pkg/bazel" "github.com/stackb/scala-gazelle/pkg/collections" + "github.com/stackb/scala-gazelle/pkg/glob" + "github.com/stackb/scala-gazelle/pkg/parser" "github.com/stackb/scala-gazelle/pkg/procutil" "github.com/stackb/scala-gazelle/pkg/resolver" "github.com/stackb/scala-gazelle/pkg/scalaconfig" @@ -33,14 +35,21 @@ const ( ) type scalaRuleContext struct { + repoRoot string // the parent config scalaConfig *scalaconfig.Config // rule (lowercase) is the parent gazelle rule rule *rule.Rule + // file to which the rule belongs + file *rule.File // scope is a map of symbols that are outside the rule. scope resolver.Scope // the global import resolver resolver resolver.SymbolResolver + // the global scope registry + knownScopes resolver.KnownScopeRegistry + // instance of the scala rule parser + parser parser.Parser } type scalaRule struct { @@ -48,10 +57,10 @@ type scalaRule struct { logger zerolog.Logger // Rule is the pb representation pb *sppb.Rule - // files is a list of files, copied from pb.Files but sorted again - files []*sppb.File // ctx is the rule context ctx *scalaRuleContext + // from is the label for the rule + from label.Label // exports keyed by their import exports map[string]resolve.ImportSpec } @@ -64,32 +73,59 @@ func init() { } } -func newScalaRule( - logger zerolog.Logger, - ctx *scalaRuleContext, - rule *sppb.Rule, -) *scalaRule { - scalaRule := &scalaRule{ +func newScalaRule(logger zerolog.Logger, ctx *scalaRuleContext, from label.Label) *scalaRule { + return &scalaRule{ logger: logger, - pb: rule, - files: rule.Files, ctx: ctx, + from: from, exports: make(map[string]resolve.ImportSpec), + pb: &sppb.Rule{ + Label: from.String(), + Kind: ctx.rule.Kind(), + }, } +} - sort.Slice(scalaRule.files, func(i, j int) bool { - a := scalaRule.files[i] - b := scalaRule.files[j] - return a.Filename < b.Filename - }) +// ParseSrcs implements part of the scalarule.Rule interface +func (r *scalaRule) ParseSrcs() error { + dir := filepath.Join(r.ctx.repoRoot, r.from.Pkg) + + // collect and filter .scala files from the `srcs` attribute. + srcs, err := glob.CollectFilenames(r.ctx.file, dir, r.ctx.rule.Attr("srcs")) + if err != nil { + return err + } + scalaSrcs := make([]string, 0, len(srcs)) + for _, src := range srcs { + if !strings.HasSuffix(src, ".scala") { + continue + } + scalaSrcs = append(scalaSrcs, src) + } + if len(scalaSrcs) == 0 { + err = ErrRuleHasNoSrcs + } + + if len(scalaSrcs) > 0 { + rule, err := r.ctx.parser.ParseScalaRule(r.ctx.rule.Kind(), r.from, dir, scalaSrcs...) + if err != nil { + r.logger.Warn().Err(err).Msg("parse error") + return err + } + // TODO(pcj): use the pb.struct as-is, or just copy over the files? + r.pb.Files = rule.Files + r.pb.Sha256 = rule.Sha256 + } - if !isBinaryRule(ctx.rule.Kind()) { - for _, file := range scalaRule.files { - scalaRule.putExports(file) + r.logger.Debug().Msgf("%d scala files collected", len(scalaSrcs)) + + if !isBinaryRule(r.ctx.rule.Kind()) { + for _, file := range r.pb.Files { + r.putExports(file) } } - return scalaRule + return nil } // ResolveExports performs symbol resolution for exports of the rule. @@ -166,13 +202,13 @@ func (r *scalaRule) Imports(from label.Label) resolver.ImportMap { } // direct - for _, file := range r.files { + for _, file := range r.pb.Files { r.fileImports(imports, file, from) } // semantic add in semantic imports after direct ones to minimize the delta // between running gazelle with and without semanticdb info. - for _, file := range r.files { + for _, file := range r.pb.Files { r.fileSemanticImports(imports, file, from) } @@ -200,7 +236,7 @@ func (r *scalaRule) Imports(from label.Label) resolver.ImportMap { func (r *scalaRule) Exports(from label.Label) resolver.ImportMap { exports := resolver.NewImportMap() - for _, file := range r.files { + for _, file := range r.pb.Files { r.fileExports(file, exports, from) } @@ -348,6 +384,9 @@ func (r *scalaRule) fileImports(imports resolver.ImportMap, file *sppb.File, fro r.logger.Print(r.infof("%s scope:\n%s", file.Filename, scope.String())) } + // register the scope under workspace-relative path + r.ctx.knownScopes.PutKnownScope(file.Filename, scope) + // resolve extends clauses in the file. While these are probably duplicated // in the 'Names' slice, do it anyway. tokens := extendsKeysSorted(file.Extends) @@ -388,7 +427,12 @@ func (r *scalaRule) fileImports(imports resolver.ImportMap, file *sppb.File, fro // Files implements part of the scalarule.Rule interface. func (r *scalaRule) Files() []*sppb.File { - return r.files + return r.pb.Files +} + +// Rule implements part of the scalarule.Rule interface. +func (r *scalaRule) Rule() *sppb.Rule { + return r.pb } // Provides implements part of the scalarule.Rule interface. diff --git a/language/scala/scala_rule_test.go b/language/scala/scala_rule_test.go index bad992c9..b60ff292 100644 --- a/language/scala/scala_rule_test.go +++ b/language/scala/scala_rule_test.go @@ -80,13 +80,11 @@ func TestScalaRuleExports(t *testing.T) { scalaConfig: sc, resolver: universe, scope: universe, + knownScopes: universe, } - scalaRule := newScalaRule(zerolog.New(os.Stderr), ctx, &sppb.Rule{ - Label: tc.from.String(), - Kind: tc.rule.Kind(), - Files: tc.files, - }) + scalaRule := newScalaRule(zerolog.New(os.Stderr), ctx, tc.from) + scalaRule.Rule().Files = tc.files got := scalaRule.Provides() @@ -276,11 +274,8 @@ func TestScalaRuleImports(t *testing.T) { scope: universe, } - scalaRule := newScalaRule(zerolog.New(os.Stderr), ctx, &sppb.Rule{ - Label: tc.from.String(), - Kind: tc.rule.Kind(), - Files: tc.files, - }) + scalaRule := newScalaRule(zerolog.New(os.Stderr), ctx, tc.from) + scalaRule.Rule().Files = tc.files imports := scalaRule.Imports(tc.from) got := make([]string, len(imports.Keys())) diff --git a/pkg/autokeep/BUILD.bazel b/pkg/autokeep/BUILD.bazel index 33e65838..8694420b 100644 --- a/pkg/autokeep/BUILD.bazel +++ b/pkg/autokeep/BUILD.bazel @@ -12,7 +12,9 @@ go_library( deps = [ "//build/stack/gazelle/scala/autokeep", "//build/stack/gazelle/scala/cache", + "//build/stack/gazelle/scala/parse", "//pkg/protobuf", + "//pkg/resolver", "@bazel_gazelle//label:go_default_library", "@bazel_gazelle//rule:go_default_library", "@com_github_bazelbuild_buildtools//build:go_default_library", diff --git a/pkg/autokeep/deps.go b/pkg/autokeep/deps.go index abf92445..fe85742c 100644 --- a/pkg/autokeep/deps.go +++ b/pkg/autokeep/deps.go @@ -13,13 +13,16 @@ import ( "github.com/bazelbuild/buildtools/build" akpb "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/autokeep" scpb "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/cache" + sppb "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/parse" "github.com/stackb/scala-gazelle/pkg/protobuf" + "github.com/stackb/scala-gazelle/pkg/resolver" ) type DepsMap map[string]string +type FileMap map[string]*sppb.File -func MakeDeltaDeps(deps DepsMap, diagnostics *akpb.Diagnostics) *akpb.DeltaDeps { +func MakeDeltaDeps(diagnostics *akpb.Diagnostics, deps DepsMap, files FileMap, scopes resolver.KnownScopeRegistry) *akpb.DeltaDeps { rules := make(map[string]*akpb.RuleDeps) delta := new(akpb.DeltaDeps) for _, e := range diagnostics.ScalacErrors { @@ -30,6 +33,22 @@ func MakeDeltaDeps(deps DepsMap, diagnostics *akpb.Diagnostics) *akpb.DeltaDeps rule.BuildFile = e.BuildFile } switch t := e.Error.(type) { + case *akpb.ScalacError_NotFound: + if scope, ok := scopes.GetKnownScope(t.NotFound.SourceFile); ok { + if sym, ok := scope.GetSymbol(t.NotFound.Type); ok { + if sym.Label != label.NoLabel { + log.Println("MATCH: ", sym, "satisfies not found type", t.NotFound.Type) + if len(rule.Deps) == 0 { + delta.Add = append(delta.Add, rule) + } + rule.Deps = append(rule.Deps, sym.Label.String()) + } + } else { + log.Printf("MISS (scope not found): %s", t.NotFound.SourceFile) + } + } else { + log.Printf("MISS (unknown source file): %q", t.NotFound.SourceFile) + } case *akpb.ScalacError_MissingSymbol: sym := t.MissingSymbol.Symbol if label, ok := deps[sym]; ok { diff --git a/pkg/autokeep/deps_test.go b/pkg/autokeep/deps_test.go index 2e7304e2..b081a1d3 100644 --- a/pkg/autokeep/deps_test.go +++ b/pkg/autokeep/deps_test.go @@ -14,19 +14,19 @@ import ( func TestMakeDeltaDeps(t *testing.T) { for name, tc := range map[string]struct { - input *akpb.Diagnostics - deps DepsMap - want *akpb.DeltaDeps + diagnostics *akpb.Diagnostics + deps DepsMap + want *akpb.DeltaDeps }{ "degenerate": { - input: &akpb.Diagnostics{}, - want: &akpb.DeltaDeps{}, + diagnostics: &akpb.Diagnostics{}, + want: &akpb.DeltaDeps{}, }, "not-a-package": { deps: map[string]string{ "contoso.postswarm.SelectiveSpotSessionUtils": "//contoso/postswarm:selective_spot_session_utils_common_scala", }, - input: &akpb.Diagnostics{ + diagnostics: &akpb.Diagnostics{ ScalacErrors: []*akpb.ScalacError{ { RuleLabel: "//contoso/postswarm:grey_it", @@ -52,7 +52,7 @@ func TestMakeDeltaDeps(t *testing.T) { }, } { t.Run(name, func(t *testing.T) { - got := MakeDeltaDeps(tc.deps, tc.input) + got := MakeDeltaDeps(tc.diagnostics, tc.deps, nil, nil) if diff := cmp.Diff(tc.want, got, cmpopts.IgnoreUnexported( akpb.DeltaDeps{}, diff --git a/pkg/autokeep/scanner.go b/pkg/autokeep/scanner.go index 98404f96..5593d19e 100644 --- a/pkg/autokeep/scanner.go +++ b/pkg/autokeep/scanner.go @@ -20,6 +20,9 @@ var missingSymbolLine = regexp.MustCompile(`^(.*):\d+: error: Symbol 'type ([^'] // omnistac/postswarm/src/it/scala/omnistac/postswarm/grey/SelectiveSpottingTest.scala:22: error: [rewritten by -quickfix] object SelectiveSpotSessionUtils is not a member of package omnistac.postswarm var notAMemberOfPackageLine = regexp.MustCompile(`^(.*):\d+: error: .* object ([A-Z][_a-zA-Z0-9]*) is not a member of package (.*)$`) +// trumid/fix/common/testing/src/QuickfixTestUtils.scala:88: error: [rewritten by -quickfix] not found: type SimpleQuickfixSessionObject +var typeNotFoundLine = regexp.MustCompile(`^(.*):\d+: error: .* not found: type ([A-Z][_a-zA-Z0-9]*)$`) + // This symbol is required by 'class omnistac.gum.dao.TradingAccountDao.TradingAccountTable'. var symbolRequiredByLine = regexp.MustCompile(`^This symbol is required by '([^']+)'.$`) @@ -28,8 +31,18 @@ var buildozerLine = regexp.MustCompile(`^buildozer 'remove deps ([^']+)' (.*)$`) func ScanOutput(output []byte) (*akpb.Diagnostics, error) { diagnostics := new(akpb.Diagnostics) + + // scalacError is populated when we hit the first scalacErrorLine and + // becomes a contextual object upon which the state of future errors (lines + // following) depends. var scalacError *akpb.ScalacError - var missingSymbol *akpb.MissingSymbol + + addError := func(sce *akpb.ScalacError) { + sce.BuildFile = scalacError.BuildFile + sce.RuleLabel = scalacError.RuleLabel + diagnostics.ScalacErrors = append(diagnostics.ScalacErrors, sce) + } + scanner := bufio.NewScanner(bytes.NewReader(output)) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) @@ -42,31 +55,49 @@ func ScanOutput(output []byte) (*akpb.Diagnostics, error) { scalacError = new(akpb.ScalacError) scalacError.BuildFile = match[1] scalacError.RuleLabel = strings.TrimSuffix(match[2], "_testlib") - diagnostics.ScalacErrors = append(diagnostics.ScalacErrors, scalacError) - missingSymbol = nil } else if match := symbolRequiredByLine.FindStringSubmatch(line); match != nil { - missingSymbol.RequiredBy = match[1] + if len(diagnostics.ScalacErrors) > 0 { + lastError := diagnostics.ScalacErrors[len(diagnostics.ScalacErrors)-1] + if e, ok := lastError.Error.(*akpb.ScalacError_MissingSymbol); ok { + e.MissingSymbol.RequiredBy = match[1] + } + } } else if match := missingSymbolLine.FindStringSubmatch(line); match != nil { - scalacError.Error = &akpb.ScalacError_MissingSymbol{ - MissingSymbol: &akpb.MissingSymbol{ - SourceFile: match[1], - Symbol: match[2], + addError(&akpb.ScalacError{ + Error: &akpb.ScalacError_MissingSymbol{ + MissingSymbol: &akpb.MissingSymbol{ + SourceFile: match[1], + Symbol: match[2], + }, }, - } + }) + } else if match := typeNotFoundLine.FindStringSubmatch(line); match != nil { + addError(&akpb.ScalacError{ + Error: &akpb.ScalacError_NotFound{ + NotFound: &akpb.TypeNotFound{ + SourceFile: match[1], + Type: match[2], + }, + }, + }) } else if match := notAMemberOfPackageLine.FindStringSubmatch(line); match != nil { - scalacError.Error = &akpb.ScalacError_NotAMemberOfPackage{ - NotAMemberOfPackage: &akpb.NotAMemberOfPackage{ - Symbol: match[2], - PackageName: match[3], + addError(&akpb.ScalacError{ + Error: &akpb.ScalacError_NotAMemberOfPackage{ + NotAMemberOfPackage: &akpb.NotAMemberOfPackage{ + Symbol: match[2], + PackageName: match[3], + }, }, - } + }) } else if match := buildozerLine.FindStringSubmatch(line); match != nil { - scalacError.Error = &akpb.ScalacError_BuildozerUnusedDep{ - BuildozerUnusedDep: &akpb.BuildozerUnusedDep{ - UnusedDep: match[1], - RuleLabel: match[2], + addError(&akpb.ScalacError{ + Error: &akpb.ScalacError_BuildozerUnusedDep{ + BuildozerUnusedDep: &akpb.BuildozerUnusedDep{ + UnusedDep: match[1], + RuleLabel: match[2], + }, }, - } + }) } } if err := scanner.Err(); err != nil { diff --git a/pkg/collections/BUILD.bazel b/pkg/collections/BUILD.bazel index 1efb27c2..a8a63161 100644 --- a/pkg/collections/BUILD.bazel +++ b/pkg/collections/BUILD.bazel @@ -14,6 +14,7 @@ go_library( "slice.go", "string_slice.go", "string_stack.go", + "terminal.go", "uint32stack.go", ], importpath = "github.com/stackb/scala-gazelle/pkg/collections", @@ -35,6 +36,7 @@ package_filegroup( "slice.go", "string_slice.go", "string_stack.go", + "terminal.go", "uint32stack.go", ], visibility = ["//visibility:public"], diff --git a/pkg/collections/slice.go b/pkg/collections/slice.go index 4c05cf3c..dfe2d6a9 100644 --- a/pkg/collections/slice.go +++ b/pkg/collections/slice.go @@ -26,3 +26,19 @@ func SliceInsertAt[T any](slice []T, i int, value T) []T { return result } + +// SliceDeduplicate removes duplicate entries +func SliceDeduplicate[T any](in []T) (out []T) { + if len(in) == 0 { + return in + } + seen := make(map[any]bool) + for _, v := range in { + if seen[v] { + continue + } + seen[v] = true + out = append(out, v) + } + return +} diff --git a/pkg/collections/terminal.go b/pkg/collections/terminal.go new file mode 100644 index 00000000..0224625c --- /dev/null +++ b/pkg/collections/terminal.go @@ -0,0 +1 @@ +package collections diff --git a/pkg/provider/source_provider.go b/pkg/provider/source_provider.go index e7bf0e58..e14354b7 100644 --- a/pkg/provider/source_provider.go +++ b/pkg/provider/source_provider.go @@ -15,6 +15,7 @@ import ( "github.com/bazelbuild/buildtools/build" "github.com/rs/zerolog" + "github.com/stackb/scala-gazelle/pkg/collections" "github.com/stackb/scala-gazelle/pkg/parser" "github.com/stackb/scala-gazelle/pkg/procutil" "github.com/stackb/scala-gazelle/pkg/protobuf" @@ -58,6 +59,14 @@ type SourceProvider struct { scalaFilesetFilename string // scalaFiles is a mapping that is read from the scalaFilesetFilename, if present scalaFiles map[string]*sppb.File + // hasLifecycleEnded flags whether we have seen the OnEnd() call at least + // once. This affects whether we choose to double-check sha256 values + // during the haveFiles check. For the first go, assume all files we + // already have are up-to-date (don't check sha256's again). If it happens + // late, assume we are in watch/repair mode and the gazelle process is + // long-lived, possibly needing to reparse files that could be changed by a + // developer since the gazelle process strated. + hasLifecycleEnded bool } // Name implements part of the resolver.SymbolProvider interface. @@ -97,6 +106,7 @@ func (r *SourceProvider) OnResolve() error { // OnEnd implements part of the resolver.SymbolProvider interface. func (r *SourceProvider) OnEnd() error { + r.hasLifecycleEnded = true return nil } @@ -166,20 +176,41 @@ func (r *SourceProvider) ParseScalaRule(kind string, from label.Label, dir strin } func (r *SourceProvider) parseFiles(dir string, srcs []string, from label.Label, kind string) ([]*sppb.File, error) { - // haveFiles is the list of haveFiles we already have from pre-computed scalaFiles + // haveFiles is the list of haveFiles we already have from pre-computed + // scalaFiles whose sha256 values are up-to-date. var haveFiles []*sppb.File - // needFilenames is a mapping from the absolute to relative path + // needFilenames is a mapping from the absolute to relative path of the files we need to parse needFilenames := make(map[string]string) for _, src := range srcs { + abs := filepath.Join(dir, src) rel := filepath.Join(from.Pkg, src) if file, ok := r.scalaFiles[rel]; ok { - haveFiles = append(haveFiles, file) - // log.Println("✅ have:", rel) - } else { - abs := filepath.Join(dir, src) - needFilenames[abs] = rel + var isUpToDate bool + + if r.hasLifecycleEnded { + // this is a later pass during gazelle execution such as in + // watch/repair mode, we need to re-check sha256s + sha256, err := collections.FileSha256(abs) + if err != nil { + return nil, err + } + if sha256 == file.Sha256 { + isUpToDate = true + } + } else { + // this is the (normal) first pass during gazelle execution, no + // need to re-check sha256s + isUpToDate = true + } + + if isUpToDate { + haveFiles = append(haveFiles, file) + // log.Println("✅ have:", rel) + continue + } } + needFilenames[abs] = rel } if len(needFilenames) == 0 { return haveFiles, nil @@ -225,6 +256,8 @@ func (r *SourceProvider) parseFiles(dir string, srcs []string, from label.Label, panic("failed to map parsed file (having absolute path) back to relative path: this is a bug: " + file.Filename) } file.Filename = rel + // update saved cache + r.scalaFiles[rel] = file } return append(haveFiles, response.Files...), nil diff --git a/pkg/resolver/BUILD.bazel b/pkg/resolver/BUILD.bazel index a114fc95..209ddedf 100644 --- a/pkg/resolver/BUILD.bazel +++ b/pkg/resolver/BUILD.bazel @@ -17,6 +17,7 @@ go_library( "import.go", "import_map.go", "known_rule_registry.go", + "known_scope_registry.go", "label_name_rewrite_spec.go", "memo_symbol_resolver.go", "override_symbol_resolver.go", @@ -100,6 +101,7 @@ package_filegroup( "import_map.go", "import_map_test.go", "known_rule_registry.go", + "known_scope_registry.go", "label_name_rewrite_spec.go", "memo_symbol_resolver.go", "override_symbol_resolver.go", diff --git a/pkg/resolver/import_map.go b/pkg/resolver/import_map.go index 13b9dc53..f98f1b97 100644 --- a/pkg/resolver/import_map.go +++ b/pkg/resolver/import_map.go @@ -2,7 +2,6 @@ package resolver import ( "github.com/bazelbuild/bazel-gazelle/label" - "github.com/bazelbuild/buildtools/build" ) // ImportLabel is a pair of (Import,Label) @@ -11,16 +10,17 @@ type ImportLabel struct { Label label.Label } +// ImportMap is a map-like entity that is capable of producing a list of +// dependencies relative to a Label. type ImportMap interface { Keys() []string Values() []*Import Deps(from label.Label) map[label.Label]*ImportLabel Put(imp *Import) Get(name string) (*Import, bool) - Annotate(comments *build.Comments, accept func(imp *Import) bool) } -// OrderedImportMap is a map if imports keyed by the import string. +// OrderedImportMap is a map of imports keyed by the import string. type OrderedImportMap struct { values []*Import has map[string]bool @@ -108,12 +108,3 @@ func (imports *OrderedImportMap) Put(imp *Import) { imports.has[imp.Imp] = true imports.values = append(imports.values, imp) } - -func (imports *OrderedImportMap) Annotate(comments *build.Comments, accept func(imp *Import) bool) { - for _, imp := range imports.values { - if !accept(imp) { - continue - } - comments.Before = append(comments.Before, build.Comment{Token: "# " + imp.String()}) - } -} diff --git a/pkg/resolver/known_scope_registry.go b/pkg/resolver/known_scope_registry.go new file mode 100644 index 00000000..b84a0a90 --- /dev/null +++ b/pkg/resolver/known_scope_registry.go @@ -0,0 +1,17 @@ +package resolver + +// KnownScopeRegistry is an index of scopes keyed by their filename. For +// example, if one needed to resolve the symbol 'A' within file +// `a/b/c/Main.scala`, the registry could be used to gain the scope to perform +// this lookup. +type KnownScopeRegistry interface { + // GetScope does a lookup of the given label and returns the + // known rule. If not known `(nil, false)` is returned. + GetKnownScope(name string) (Scope, bool) + + // PutFileScope adds the given known rule to the registry. It is an + // error to attempt duplicate registration of the same rule twice. + // Implementations should use the google.golang.org/grpc/status.Errorf for + // error types. + PutKnownScope(name string, scope Scope) error +} diff --git a/pkg/resolver/mocks/BUILD.bazel b/pkg/resolver/mocks/BUILD.bazel index df15aa1c..3b4c4af0 100644 --- a/pkg/resolver/mocks/BUILD.bazel +++ b/pkg/resolver/mocks/BUILD.bazel @@ -20,6 +20,7 @@ go_library( "@bazel_gazelle//label:go_default_library", "@bazel_gazelle//resolve:go_default_library", "@bazel_gazelle//rule:go_default_library", + "@com_github_bazelbuild_buildtools//build:go_default_library", "@com_github_stretchr_testify//mock", ], ) diff --git a/pkg/resolver/mocks/ConflictResolver.go b/pkg/resolver/mocks/ConflictResolver.go index 4732892d..d411bc37 100644 --- a/pkg/resolver/mocks/ConflictResolver.go +++ b/pkg/resolver/mocks/ConflictResolver.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.16.0. DO NOT EDIT. +// Code generated by mockery v2.53.3. DO NOT EDIT. package mocks @@ -23,6 +23,10 @@ type ConflictResolver struct { func (_m *ConflictResolver) CheckFlags(fs *flag.FlagSet, c *config.Config) error { ret := _m.Called(fs, c) + if len(ret) == 0 { + panic("no return value specified for CheckFlags") + } + var r0 error if rf, ok := ret.Get(0).(func(*flag.FlagSet, *config.Config) error); ok { r0 = rf(fs, c) @@ -33,10 +37,14 @@ func (_m *ConflictResolver) CheckFlags(fs *flag.FlagSet, c *config.Config) error return r0 } -// Name provides a mock function with given fields: +// Name provides a mock function with no fields func (_m *ConflictResolver) Name() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Name") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -56,7 +64,15 @@ func (_m *ConflictResolver) RegisterFlags(fs *flag.FlagSet, cmd string, c *confi func (_m *ConflictResolver) ResolveConflict(universe resolver.Universe, r *rule.Rule, imports resolver.ImportMap, imp *resolver.Import, symbol *resolver.Symbol) (*resolver.Symbol, bool) { ret := _m.Called(universe, r, imports, imp, symbol) + if len(ret) == 0 { + panic("no return value specified for ResolveConflict") + } + var r0 *resolver.Symbol + var r1 bool + if rf, ok := ret.Get(0).(func(resolver.Universe, *rule.Rule, resolver.ImportMap, *resolver.Import, *resolver.Symbol) (*resolver.Symbol, bool)); ok { + return rf(universe, r, imports, imp, symbol) + } if rf, ok := ret.Get(0).(func(resolver.Universe, *rule.Rule, resolver.ImportMap, *resolver.Import, *resolver.Symbol) *resolver.Symbol); ok { r0 = rf(universe, r, imports, imp, symbol) } else { @@ -65,7 +81,6 @@ func (_m *ConflictResolver) ResolveConflict(universe resolver.Universe, r *rule. } } - var r1 bool if rf, ok := ret.Get(1).(func(resolver.Universe, *rule.Rule, resolver.ImportMap, *resolver.Import, *resolver.Symbol) bool); ok { r1 = rf(universe, r, imports, imp, symbol) } else { @@ -75,13 +90,12 @@ func (_m *ConflictResolver) ResolveConflict(universe resolver.Universe, r *rule. return r0, r1 } -type mockConstructorTestingTNewConflictResolver interface { +// NewConflictResolver creates a new instance of ConflictResolver. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewConflictResolver(t interface { mock.TestingT Cleanup(func()) -} - -// NewConflictResolver creates a new instance of ConflictResolver. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewConflictResolver(t mockConstructorTestingTNewConflictResolver) *ConflictResolver { +}) *ConflictResolver { mock := &ConflictResolver{} mock.Mock.Test(t) diff --git a/pkg/resolver/mocks/Scope.go b/pkg/resolver/mocks/Scope.go index 31b0e8fe..a2329acd 100644 --- a/pkg/resolver/mocks/Scope.go +++ b/pkg/resolver/mocks/Scope.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.16.0. DO NOT EDIT. +// Code generated by mockery v2.53.3. DO NOT EDIT. package mocks @@ -16,7 +16,15 @@ type Scope struct { func (_m *Scope) GetScope(name string) (resolver.Scope, bool) { ret := _m.Called(name) + if len(ret) == 0 { + panic("no return value specified for GetScope") + } + var r0 resolver.Scope + var r1 bool + if rf, ok := ret.Get(0).(func(string) (resolver.Scope, bool)); ok { + return rf(name) + } if rf, ok := ret.Get(0).(func(string) resolver.Scope); ok { r0 = rf(name) } else { @@ -25,7 +33,6 @@ func (_m *Scope) GetScope(name string) (resolver.Scope, bool) { } } - var r1 bool if rf, ok := ret.Get(1).(func(string) bool); ok { r1 = rf(name) } else { @@ -39,7 +46,15 @@ func (_m *Scope) GetScope(name string) (resolver.Scope, bool) { func (_m *Scope) GetSymbol(name string) (*resolver.Symbol, bool) { ret := _m.Called(name) + if len(ret) == 0 { + panic("no return value specified for GetSymbol") + } + var r0 *resolver.Symbol + var r1 bool + if rf, ok := ret.Get(0).(func(string) (*resolver.Symbol, bool)); ok { + return rf(name) + } if rf, ok := ret.Get(0).(func(string) *resolver.Symbol); ok { r0 = rf(name) } else { @@ -48,7 +63,6 @@ func (_m *Scope) GetSymbol(name string) (*resolver.Symbol, bool) { } } - var r1 bool if rf, ok := ret.Get(1).(func(string) bool); ok { r1 = rf(name) } else { @@ -62,6 +76,10 @@ func (_m *Scope) GetSymbol(name string) (*resolver.Symbol, bool) { func (_m *Scope) GetSymbols(prefix string) []*resolver.Symbol { ret := _m.Called(prefix) + if len(ret) == 0 { + panic("no return value specified for GetSymbols") + } + var r0 []*resolver.Symbol if rf, ok := ret.Get(0).(func(string) []*resolver.Symbol); ok { r0 = rf(prefix) @@ -78,6 +96,10 @@ func (_m *Scope) GetSymbols(prefix string) []*resolver.Symbol { func (_m *Scope) PutSymbol(known *resolver.Symbol) error { ret := _m.Called(known) + if len(ret) == 0 { + panic("no return value specified for PutSymbol") + } + var r0 error if rf, ok := ret.Get(0).(func(*resolver.Symbol) error); ok { r0 = rf(known) @@ -88,10 +110,14 @@ func (_m *Scope) PutSymbol(known *resolver.Symbol) error { return r0 } -// String provides a mock function with given fields: +// String provides a mock function with no fields func (_m *Scope) String() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for String") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -102,13 +128,12 @@ func (_m *Scope) String() string { return r0 } -type mockConstructorTestingTNewScope interface { +// NewScope creates a new instance of Scope. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewScope(t interface { mock.TestingT Cleanup(func()) -} - -// NewScope creates a new instance of Scope. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewScope(t mockConstructorTestingTNewScope) *Scope { +}) *Scope { mock := &Scope{} mock.Mock.Test(t) diff --git a/pkg/resolver/mocks/SymbolProvider.go b/pkg/resolver/mocks/SymbolProvider.go index acbcb9fa..2aa65f58 100644 --- a/pkg/resolver/mocks/SymbolProvider.go +++ b/pkg/resolver/mocks/SymbolProvider.go @@ -1,11 +1,12 @@ -// Code generated by mockery v2.16.0. DO NOT EDIT. +// Code generated by mockery v2.53.3. DO NOT EDIT. package mocks import ( - flag "flag" - config "github.com/bazelbuild/bazel-gazelle/config" + build "github.com/bazelbuild/buildtools/build" + + flag "flag" label "github.com/bazelbuild/bazel-gazelle/label" @@ -21,13 +22,17 @@ type SymbolProvider struct { mock.Mock } -// CanProvide provides a mock function with given fields: dep, knownRule -func (_m *SymbolProvider) CanProvide(dep label.Label, knownRule func(label.Label) (*rule.Rule, bool)) bool { - ret := _m.Called(dep, knownRule) +// CanProvide provides a mock function with given fields: dep, expr, knownRule, from +func (_m *SymbolProvider) CanProvide(dep *resolver.ImportLabel, expr build.Expr, knownRule func(label.Label) (*rule.Rule, bool), from label.Label) bool { + ret := _m.Called(dep, expr, knownRule, from) + + if len(ret) == 0 { + panic("no return value specified for CanProvide") + } var r0 bool - if rf, ok := ret.Get(0).(func(label.Label, func(label.Label) (*rule.Rule, bool)) bool); ok { - r0 = rf(dep, knownRule) + if rf, ok := ret.Get(0).(func(*resolver.ImportLabel, build.Expr, func(label.Label) (*rule.Rule, bool), label.Label) bool); ok { + r0 = rf(dep, expr, knownRule, from) } else { r0 = ret.Get(0).(bool) } @@ -39,6 +44,10 @@ func (_m *SymbolProvider) CanProvide(dep label.Label, knownRule func(label.Label func (_m *SymbolProvider) CheckFlags(fs *flag.FlagSet, c *config.Config, scope resolver.Scope) error { ret := _m.Called(fs, c, scope) + if len(ret) == 0 { + panic("no return value specified for CheckFlags") + } + var r0 error if rf, ok := ret.Get(0).(func(*flag.FlagSet, *config.Config, resolver.Scope) error); ok { r0 = rf(fs, c, scope) @@ -49,10 +58,14 @@ func (_m *SymbolProvider) CheckFlags(fs *flag.FlagSet, c *config.Config, scope r return r0 } -// Name provides a mock function with given fields: +// Name provides a mock function with no fields func (_m *SymbolProvider) Name() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Name") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -63,10 +76,14 @@ func (_m *SymbolProvider) Name() string { return r0 } -// OnEnd provides a mock function with given fields: +// OnEnd provides a mock function with no fields func (_m *SymbolProvider) OnEnd() error { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for OnEnd") + } + var r0 error if rf, ok := ret.Get(0).(func() error); ok { r0 = rf() @@ -77,10 +94,14 @@ func (_m *SymbolProvider) OnEnd() error { return r0 } -// OnResolve provides a mock function with given fields: +// OnResolve provides a mock function with no fields func (_m *SymbolProvider) OnResolve() error { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for OnResolve") + } + var r0 error if rf, ok := ret.Get(0).(func() error); ok { r0 = rf() @@ -96,13 +117,12 @@ func (_m *SymbolProvider) RegisterFlags(fs *flag.FlagSet, cmd string, c *config. _m.Called(fs, cmd, c) } -type mockConstructorTestingTNewSymbolProvider interface { +// NewSymbolProvider creates a new instance of SymbolProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSymbolProvider(t interface { mock.TestingT Cleanup(func()) -} - -// NewSymbolProvider creates a new instance of SymbolProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewSymbolProvider(t mockConstructorTestingTNewSymbolProvider) *SymbolProvider { +}) *SymbolProvider { mock := &SymbolProvider{} mock.Mock.Test(t) diff --git a/pkg/resolver/mocks/SymbolResolver.go b/pkg/resolver/mocks/SymbolResolver.go index 112daf98..cee759a5 100644 --- a/pkg/resolver/mocks/SymbolResolver.go +++ b/pkg/resolver/mocks/SymbolResolver.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.16.0. DO NOT EDIT. +// Code generated by mockery v2.53.3. DO NOT EDIT. package mocks @@ -22,7 +22,15 @@ type SymbolResolver struct { func (_m *SymbolResolver) ResolveSymbol(c *config.Config, ix *resolve.RuleIndex, from label.Label, lang string, sym string) (*resolver.Symbol, bool) { ret := _m.Called(c, ix, from, lang, sym) + if len(ret) == 0 { + panic("no return value specified for ResolveSymbol") + } + var r0 *resolver.Symbol + var r1 bool + if rf, ok := ret.Get(0).(func(*config.Config, *resolve.RuleIndex, label.Label, string, string) (*resolver.Symbol, bool)); ok { + return rf(c, ix, from, lang, sym) + } if rf, ok := ret.Get(0).(func(*config.Config, *resolve.RuleIndex, label.Label, string, string) *resolver.Symbol); ok { r0 = rf(c, ix, from, lang, sym) } else { @@ -31,7 +39,6 @@ func (_m *SymbolResolver) ResolveSymbol(c *config.Config, ix *resolve.RuleIndex, } } - var r1 bool if rf, ok := ret.Get(1).(func(*config.Config, *resolve.RuleIndex, label.Label, string, string) bool); ok { r1 = rf(c, ix, from, lang, sym) } else { @@ -41,13 +48,12 @@ func (_m *SymbolResolver) ResolveSymbol(c *config.Config, ix *resolve.RuleIndex, return r0, r1 } -type mockConstructorTestingTNewSymbolResolver interface { +// NewSymbolResolver creates a new instance of SymbolResolver. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSymbolResolver(t interface { mock.TestingT Cleanup(func()) -} - -// NewSymbolResolver creates a new instance of SymbolResolver. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewSymbolResolver(t mockConstructorTestingTNewSymbolResolver) *SymbolResolver { +}) *SymbolResolver { mock := &SymbolResolver{} mock.Mock.Test(t) diff --git a/pkg/resolver/mocks/Universe.go b/pkg/resolver/mocks/Universe.go index bc11dc6b..d33e147e 100644 --- a/pkg/resolver/mocks/Universe.go +++ b/pkg/resolver/mocks/Universe.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.16.0. DO NOT EDIT. +// Code generated by mockery v2.53.3. DO NOT EDIT. package mocks @@ -24,6 +24,10 @@ type Universe struct { func (_m *Universe) AddSymbolProvider(provider resolver.SymbolProvider) error { ret := _m.Called(provider) + if len(ret) == 0 { + panic("no return value specified for AddSymbolProvider") + } + var r0 error if rf, ok := ret.Get(0).(func(resolver.SymbolProvider) error); ok { r0 = rf(provider) @@ -38,7 +42,15 @@ func (_m *Universe) AddSymbolProvider(provider resolver.SymbolProvider) error { func (_m *Universe) GetConflictResolver(name string) (resolver.ConflictResolver, bool) { ret := _m.Called(name) + if len(ret) == 0 { + panic("no return value specified for GetConflictResolver") + } + var r0 resolver.ConflictResolver + var r1 bool + if rf, ok := ret.Get(0).(func(string) (resolver.ConflictResolver, bool)); ok { + return rf(name) + } if rf, ok := ret.Get(0).(func(string) resolver.ConflictResolver); ok { r0 = rf(name) } else { @@ -47,7 +59,6 @@ func (_m *Universe) GetConflictResolver(name string) (resolver.ConflictResolver, } } - var r1 bool if rf, ok := ret.Get(1).(func(string) bool); ok { r1 = rf(name) } else { @@ -61,7 +72,15 @@ func (_m *Universe) GetConflictResolver(name string) (resolver.ConflictResolver, func (_m *Universe) GetDepsCleaner(name string) (resolver.DepsCleaner, bool) { ret := _m.Called(name) + if len(ret) == 0 { + panic("no return value specified for GetDepsCleaner") + } + var r0 resolver.DepsCleaner + var r1 bool + if rf, ok := ret.Get(0).(func(string) (resolver.DepsCleaner, bool)); ok { + return rf(name) + } if rf, ok := ret.Get(0).(func(string) resolver.DepsCleaner); ok { r0 = rf(name) } else { @@ -70,7 +89,6 @@ func (_m *Universe) GetDepsCleaner(name string) (resolver.DepsCleaner, bool) { } } - var r1 bool if rf, ok := ret.Get(1).(func(string) bool); ok { r1 = rf(name) } else { @@ -84,7 +102,15 @@ func (_m *Universe) GetDepsCleaner(name string) (resolver.DepsCleaner, bool) { func (_m *Universe) GetKnownRule(from label.Label) (*rule.Rule, bool) { ret := _m.Called(from) + if len(ret) == 0 { + panic("no return value specified for GetKnownRule") + } + var r0 *rule.Rule + var r1 bool + if rf, ok := ret.Get(0).(func(label.Label) (*rule.Rule, bool)); ok { + return rf(from) + } if rf, ok := ret.Get(0).(func(label.Label) *rule.Rule); ok { r0 = rf(from) } else { @@ -93,7 +119,6 @@ func (_m *Universe) GetKnownRule(from label.Label) (*rule.Rule, bool) { } } - var r1 bool if rf, ok := ret.Get(1).(func(label.Label) bool); ok { r1 = rf(from) } else { @@ -103,11 +128,49 @@ func (_m *Universe) GetKnownRule(from label.Label) (*rule.Rule, bool) { return r0, r1 } +// GetKnownScope provides a mock function with given fields: name +func (_m *Universe) GetKnownScope(name string) (resolver.Scope, bool) { + ret := _m.Called(name) + + if len(ret) == 0 { + panic("no return value specified for GetKnownScope") + } + + var r0 resolver.Scope + var r1 bool + if rf, ok := ret.Get(0).(func(string) (resolver.Scope, bool)); ok { + return rf(name) + } + if rf, ok := ret.Get(0).(func(string) resolver.Scope); ok { + r0 = rf(name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(resolver.Scope) + } + } + + if rf, ok := ret.Get(1).(func(string) bool); ok { + r1 = rf(name) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + // GetScope provides a mock function with given fields: name func (_m *Universe) GetScope(name string) (resolver.Scope, bool) { ret := _m.Called(name) + if len(ret) == 0 { + panic("no return value specified for GetScope") + } + var r0 resolver.Scope + var r1 bool + if rf, ok := ret.Get(0).(func(string) (resolver.Scope, bool)); ok { + return rf(name) + } if rf, ok := ret.Get(0).(func(string) resolver.Scope); ok { r0 = rf(name) } else { @@ -116,7 +179,6 @@ func (_m *Universe) GetScope(name string) (resolver.Scope, bool) { } } - var r1 bool if rf, ok := ret.Get(1).(func(string) bool); ok { r1 = rf(name) } else { @@ -130,7 +192,15 @@ func (_m *Universe) GetScope(name string) (resolver.Scope, bool) { func (_m *Universe) GetSymbol(name string) (*resolver.Symbol, bool) { ret := _m.Called(name) + if len(ret) == 0 { + panic("no return value specified for GetSymbol") + } + var r0 *resolver.Symbol + var r1 bool + if rf, ok := ret.Get(0).(func(string) (*resolver.Symbol, bool)); ok { + return rf(name) + } if rf, ok := ret.Get(0).(func(string) *resolver.Symbol); ok { r0 = rf(name) } else { @@ -139,7 +209,6 @@ func (_m *Universe) GetSymbol(name string) (*resolver.Symbol, bool) { } } - var r1 bool if rf, ok := ret.Get(1).(func(string) bool); ok { r1 = rf(name) } else { @@ -153,6 +222,10 @@ func (_m *Universe) GetSymbol(name string) (*resolver.Symbol, bool) { func (_m *Universe) GetSymbols(prefix string) []*resolver.Symbol { ret := _m.Called(prefix) + if len(ret) == 0 { + panic("no return value specified for GetSymbols") + } + var r0 []*resolver.Symbol if rf, ok := ret.Get(0).(func(string) []*resolver.Symbol); ok { r0 = rf(prefix) @@ -169,6 +242,10 @@ func (_m *Universe) GetSymbols(prefix string) []*resolver.Symbol { func (_m *Universe) PutConflictResolver(name string, r resolver.ConflictResolver) error { ret := _m.Called(name, r) + if len(ret) == 0 { + panic("no return value specified for PutConflictResolver") + } + var r0 error if rf, ok := ret.Get(0).(func(string, resolver.ConflictResolver) error); ok { r0 = rf(name, r) @@ -183,6 +260,10 @@ func (_m *Universe) PutConflictResolver(name string, r resolver.ConflictResolver func (_m *Universe) PutDepsCleaner(name string, r resolver.DepsCleaner) error { ret := _m.Called(name, r) + if len(ret) == 0 { + panic("no return value specified for PutDepsCleaner") + } + var r0 error if rf, ok := ret.Get(0).(func(string, resolver.DepsCleaner) error); ok { r0 = rf(name, r) @@ -197,6 +278,10 @@ func (_m *Universe) PutDepsCleaner(name string, r resolver.DepsCleaner) error { func (_m *Universe) PutKnownRule(from label.Label, r *rule.Rule) error { ret := _m.Called(from, r) + if len(ret) == 0 { + panic("no return value specified for PutKnownRule") + } + var r0 error if rf, ok := ret.Get(0).(func(label.Label, *rule.Rule) error); ok { r0 = rf(from, r) @@ -207,10 +292,32 @@ func (_m *Universe) PutKnownRule(from label.Label, r *rule.Rule) error { return r0 } +// PutKnownScope provides a mock function with given fields: name, scope +func (_m *Universe) PutKnownScope(name string, scope resolver.Scope) error { + ret := _m.Called(name, scope) + + if len(ret) == 0 { + panic("no return value specified for PutKnownScope") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, resolver.Scope) error); ok { + r0 = rf(name, scope) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // PutSymbol provides a mock function with given fields: known func (_m *Universe) PutSymbol(known *resolver.Symbol) error { ret := _m.Called(known) + if len(ret) == 0 { + panic("no return value specified for PutSymbol") + } + var r0 error if rf, ok := ret.Get(0).(func(*resolver.Symbol) error); ok { r0 = rf(known) @@ -225,7 +332,15 @@ func (_m *Universe) PutSymbol(known *resolver.Symbol) error { func (_m *Universe) ResolveSymbol(c *config.Config, ix *resolve.RuleIndex, from label.Label, lang string, sym string) (*resolver.Symbol, bool) { ret := _m.Called(c, ix, from, lang, sym) + if len(ret) == 0 { + panic("no return value specified for ResolveSymbol") + } + var r0 *resolver.Symbol + var r1 bool + if rf, ok := ret.Get(0).(func(*config.Config, *resolve.RuleIndex, label.Label, string, string) (*resolver.Symbol, bool)); ok { + return rf(c, ix, from, lang, sym) + } if rf, ok := ret.Get(0).(func(*config.Config, *resolve.RuleIndex, label.Label, string, string) *resolver.Symbol); ok { r0 = rf(c, ix, from, lang, sym) } else { @@ -234,7 +349,6 @@ func (_m *Universe) ResolveSymbol(c *config.Config, ix *resolve.RuleIndex, from } } - var r1 bool if rf, ok := ret.Get(1).(func(*config.Config, *resolve.RuleIndex, label.Label, string, string) bool); ok { r1 = rf(c, ix, from, lang, sym) } else { @@ -244,10 +358,14 @@ func (_m *Universe) ResolveSymbol(c *config.Config, ix *resolve.RuleIndex, from return r0, r1 } -// String provides a mock function with given fields: +// String provides a mock function with no fields func (_m *Universe) String() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for String") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -258,10 +376,14 @@ func (_m *Universe) String() string { return r0 } -// SymbolProviders provides a mock function with given fields: +// SymbolProviders provides a mock function with no fields func (_m *Universe) SymbolProviders() []resolver.SymbolProvider { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for SymbolProviders") + } + var r0 []resolver.SymbolProvider if rf, ok := ret.Get(0).(func() []resolver.SymbolProvider); ok { r0 = rf() @@ -274,13 +396,12 @@ func (_m *Universe) SymbolProviders() []resolver.SymbolProvider { return r0 } -type mockConstructorTestingTNewUniverse interface { +// NewUniverse creates a new instance of Universe. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewUniverse(t interface { mock.TestingT Cleanup(func()) -} - -// NewUniverse creates a new instance of Universe. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewUniverse(t mockConstructorTestingTNewUniverse) *Universe { +}) *Universe { mock := &Universe{} mock.Mock.Test(t) diff --git a/pkg/resolver/universe.go b/pkg/resolver/universe.go index af73a58f..3f3dcce9 100644 --- a/pkg/resolver/universe.go +++ b/pkg/resolver/universe.go @@ -5,6 +5,7 @@ package resolver type Universe interface { SymbolProviderRegistry KnownRuleRegistry + KnownScopeRegistry ConflictResolverRegistry DepsCleanerRegistry Scope diff --git a/pkg/scalaconfig/config.go b/pkg/scalaconfig/config.go index 7e0adae5..b151c3a8 100644 --- a/pkg/scalaconfig/config.go +++ b/pkg/scalaconfig/config.go @@ -23,7 +23,10 @@ import ( type debugAnnotation int -const scalaLangName = "scala" +const ( + // scalaLangName is the scala language name + scalaLangName = "scala" +) const ( DebugUnknown debugAnnotation = 0 @@ -692,15 +695,8 @@ func (c *Config) MaybeRewrite(kind string, from label.Label) label.Label { return from } -func (c *Config) Imports(imports resolver.ImportMap, r *rule.Rule, attrName string, from label.Label) { - c.ruleAttrMergeDeps(imports, r, attrName, from) -} - -func (c *Config) Exports(exports resolver.ImportMap, r *rule.Rule, attrName string, from label.Label) { - c.ruleAttrMergeDeps(exports, r, attrName, from) -} - -func (c *Config) ruleAttrMergeDeps( +// MergeDepsAttr takes an importMap and the rule, and merges the imports into the attr. +func (c *Config) MergeDepsAttr( imports resolver.ImportMap, r *rule.Rule, attrName string, @@ -730,9 +726,9 @@ func (c *Config) ruleAttrMergeDeps( } } -// mergeDeps filters out a `deps` list. Extries are removed from the -// list if they can be parsed as dependency labels that have a provider. Others -// types of expressions are left as-is. +// mergeDeps filters out a `deps` list. Extries are removed from the list if +// they can be parsed as dependency labels that have a provider. Others types +// of expressions are left as-is. func (c *Config) mergeDeps(attrValue build.Expr, deps map[label.Label]bool, importLabels map[label.Label]*resolver.ImportLabel, attrName string, from label.Label) *build.ListExpr { var src *build.ListExpr if attrValue != nil { diff --git a/pkg/scalafiles/BUILD.bazel b/pkg/scalafiles/BUILD.bazel index fae30e49..40b54366 100644 --- a/pkg/scalafiles/BUILD.bazel +++ b/pkg/scalafiles/BUILD.bazel @@ -10,7 +10,6 @@ go_library( importpath = "github.com/stackb/scala-gazelle/pkg/scalafiles", visibility = ["//visibility:public"], deps = [ - "//build/stack/gazelle/scala/parse", "//pkg/collections", "//pkg/scalarule", "@bazel_gazelle//config:go_default_library", diff --git a/pkg/scalafiles/scala_files.go b/pkg/scalafiles/scala_files.go index e0d74c08..b7959033 100644 --- a/pkg/scalafiles/scala_files.go +++ b/pkg/scalafiles/scala_files.go @@ -10,8 +10,6 @@ import ( "github.com/stackb/scala-gazelle/pkg/collections" "github.com/stackb/scala-gazelle/pkg/scalarule" - - sppb "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/parse" ) const ( @@ -132,18 +130,13 @@ func (s *scalaFilesRule) Resolve(rctx *scalarule.ResolveContext, importsRaw inte } for _, rule := range s.pkg.GeneratedRules() { - scalaFiles := rule.PrivateAttr("_scala_files") - if debug { - log.Printf("%s: resolving with scala_files: %T", rctx.From, scalaFiles) + scalaRule, ok := scalarule.GetRule(rule) + if !ok { + continue } - if files, ok := scalaFiles.([]*sppb.File); ok { - if debug { - log.Printf("%s: resolving with %d files", rctx.From, len(files)) - } - for _, file := range files { - relativeToPkg := strings.TrimPrefix(file.Filename, s.pkg.GenerateArgs().Rel) - srcs = append(srcs, strings.TrimPrefix(relativeToPkg, "/")) - } + for _, file := range scalaRule.Rule().Files { + relativeToPkg := strings.TrimPrefix(file.Filename, s.pkg.GenerateArgs().Rel) + srcs = append(srcs, strings.TrimPrefix(relativeToPkg, "/")) } } if debug { diff --git a/pkg/scalarule/config.go b/pkg/scalarule/config.go index 66b8dc2e..348391c4 100644 --- a/pkg/scalarule/config.go +++ b/pkg/scalarule/config.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/bazelbuild/bazel-gazelle/config" + "github.com/bazelbuild/bazel-gazelle/rule" "github.com/rs/zerolog" "github.com/stackb/scala-gazelle/pkg/collections" @@ -38,8 +39,8 @@ type Config struct { Logger zerolog.Logger } -// NewConfig returns a pointer to a new Config config with the -// 'Enabled' bit set to true. +// NewConfig returns a pointer to a new config with the 'Enabled' bit set to +// true. func NewConfig(logger zerolog.Logger, config *config.Config, name string) *Config { return &Config{ Logger: logger, @@ -170,3 +171,16 @@ func (c *Config) ParseDirective(d, param, value string) error { return nil } + +// GetRule returns the scalarule.Rule interface that was previously stashed in +// the rule.Rule. +func GetRule(r *rule.Rule) (Rule, bool) { + value := r.PrivateAttr(config.GazelleImportsKey) + impl, ok := value.(Rule) + return impl, ok +} + +// PutRule stashes the scalarule.Rule interface in the rule.Rule. +func PutRule(r *rule.Rule, sr Rule) { + r.SetPrivateAttr(config.GazelleImportsKey, sr) +} diff --git a/pkg/scalarule/package.go b/pkg/scalarule/package.go index 5b7047d7..3bc3d277 100644 --- a/pkg/scalarule/package.go +++ b/pkg/scalarule/package.go @@ -8,9 +8,8 @@ import ( // Package is responsible for instantiating a Rule interface for the given // gazelle.Rule, parsing the attribute name given (typically 'srcs'). type Package interface { - // ParseRule parses the sources from the named attr (typically 'srcs') and - // created a new Rule. - ParseRule(r *grule.Rule, attrName string) (Rule, error) + // NewScalaRule creates new scalarule.Rule from the given rule.Rule. + NewScalaRule(r *grule.Rule) (Rule, error) // GenerateArgs returns the GenerateArgs for the package GenerateArgs() language.GenerateArgs // GeneratedRules returns a list of generated rules in the package. diff --git a/pkg/scalarule/rule.go b/pkg/scalarule/rule.go index 85d92e4b..86557684 100644 --- a/pkg/scalarule/rule.go +++ b/pkg/scalarule/rule.go @@ -10,6 +10,8 @@ import ( // Rule represents a collection of files with their imports and exports. type Rule interface { + // Parse evaluates the source glob and populates the files state + ParseSrcs() error // Exports returns the list of provided symbols that are importable by other // rules. Provides() []resolve.ImportSpec @@ -17,6 +19,6 @@ type Rule interface { Imports(from label.Label) resolver.ImportMap // Import returns the list of required exports for the rule. Exports(from label.Label) resolver.ImportMap - // Files returns the list of files in the Rule - Files() []*sppb.File + // Rule returns the protobuf representation of the rule + Rule() *sppb.Rule } diff --git a/pkg/sweep/BUILD.bazel b/pkg/sweep/BUILD.bazel index 974d5c7a..3093e583 100644 --- a/pkg/sweep/BUILD.bazel +++ b/pkg/sweep/BUILD.bazel @@ -10,13 +10,17 @@ go_library( importpath = "github.com/stackb/scala-gazelle/pkg/sweep", visibility = ["//visibility:public"], deps = [ + "//build/stack/gazelle/scala/parse", + "//pkg/autokeep", "//pkg/bazel", "//pkg/collections", "//pkg/procutil", - "@bazel_gazelle//config:go_default_library", + "//pkg/resolver", + "//pkg/scalarule", "@bazel_gazelle//label:go_default_library", "@bazel_gazelle//rule:go_default_library", "@com_github_bazelbuild_buildtools//build:go_default_library", + "@com_github_pcj_mobyprogress//:mobyprogress", ], ) diff --git a/pkg/sweep/fix.go b/pkg/sweep/fix.go index 28e42b74..392992bd 100644 --- a/pkg/sweep/fix.go +++ b/pkg/sweep/fix.go @@ -6,45 +6,197 @@ import ( "fmt" "log" "os/exec" + "path" + "sort" "strings" - "github.com/bazelbuild/bazel-gazelle/config" + "github.com/pcj/mobyprogress" + "github.com/stackb/scala-gazelle/pkg/autokeep" + "github.com/stackb/scala-gazelle/pkg/bazel" + "github.com/stackb/scala-gazelle/pkg/collections" "github.com/stackb/scala-gazelle/pkg/procutil" + "github.com/stackb/scala-gazelle/pkg/resolver" + "github.com/stackb/scala-gazelle/pkg/scalarule" + + sppb "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/parse" ) +type ResolvableScalaRuleMap map[scalarule.Rule]func() + type DepFixer struct { - repoDir string - pkg string + // progress is the progress interface + progress mobyprogress.Output + // repoRoot is the absolute path to the repository root + repoRoot string + // pkg is the relative path from repoDir + pkg string + // resolvers is a map that contains the resolver function per scala rule + resolvers ResolvableScalaRuleMap + // files is a map that helps identify the rule to which a file belongs + rules map[*sppb.File]scalarule.Rule + // files is a map that helps identify the file by filename + files map[string]*sppb.File + // scope is the (global) scope that should be used to resolve symbols + imports autokeep.DepsMap + // global knownScopes + knownScopes resolver.KnownScopeRegistry + // scala file Parser + } -func NewDepFixer(c *config.Config, pkg string) *DepFixer { - return &DepFixer{ - repoDir: c.RepoRoot, - pkg: pkg, +func NewDepFixer(progress mobyprogress.Output, repoRoot, pkg string, resolvers ResolvableScalaRuleMap, imports map[string]string, knownScopes resolver.KnownScopeRegistry) *DepFixer { + fixer := &DepFixer{ + progress: progress, + repoRoot: repoRoot, + pkg: pkg, + resolvers: resolvers, + rules: make(map[*sppb.File]scalarule.Rule), + files: make(map[string]*sppb.File), + imports: imports, + knownScopes: knownScopes, } + for r := range resolvers { + files := r.Rule().Files + for _, f := range files { + fixer.rules[f] = r + fixer.files[f.Filename] = f + } + } + return fixer } -func (d *DepFixer) Run() error { - files, err := d.listChangedScalaFiles() +func (d *DepFixer) Batch() error { + changedFiles, err := listChangedScalaFiles(d.repoRoot, d.pkg) if err != nil { return err } - log.Println("changed files:", files) + log.Println("changed files:", changedFiles) + + // toBuild helps gather the list of rules that need to be built based on the + // list of changed files + toBuild := make(map[string]scalarule.Rule) + + for _, filename := range changedFiles { + if file, ok := d.files[filename]; ok { + rule := d.rules[file] + toBuild[rule.Rule().Label] = rule + } else { + log.Println("no scala build rule known for:", file) + } + } + + for label, rule := range toBuild { + // re-parse the srcs + if err := rule.ParseSrcs(); err != nil { + return fmt.Errorf("parse %s failed: %v", label, err) + } + // call the resolver for the rule + d.resolvers[rule]() + } + if err := d.fix(toBuild); err != nil { + return fmt.Errorf("building rule: %v", err) + } + + return nil +} + +func (d *DepFixer) fix(toBuild map[string]scalarule.Rule) error { + labels := make([]string, 0, len(toBuild)) + for label := range toBuild { + labels = append(labels, label) + } + if len(labels) == 0 { + return nil + } + sort.Strings(labels) + + log.Println("fixing:", labels) + + writeBuildProgress(d.progress, fmt.Sprintf("> bazel build %s", strings.Join(labels, " "))) + + out, exitCode, _ := bazel.ExecCommand("bazel", "build", labels...) + if exitCode == 0 { + log.Println("builds cleanly (no action needed)") + return nil + } + + diagnostics, err := autokeep.ScanOutput(out) + if err != nil { + return fmt.Errorf("failed to scan build output: %v", err) + } + + delta := autokeep.MakeDeltaDeps(diagnostics, d.imports, d.files, d.knownScopes) + + toAdd := make(map[string][]string) + toRemove := make(map[string][]string) + + for _, ruleDeps := range delta.Add { + toBuild[ruleDeps.Label] = nil + toAdd[ruleDeps.Label] = append(toAdd[ruleDeps.Label], ruleDeps.Deps...) + } + for _, ruleDeps := range delta.Remove { + toBuild[ruleDeps.Label] = nil + toRemove[ruleDeps.Label] = append(toRemove[ruleDeps.Label], ruleDeps.Deps...) + } + + // if no actions could be derived, the build failed and we don't know what to do. + if len(toAdd) == 0 && len(toRemove) == 0 { + return fmt.Errorf("build failed, but the corrective action(s) could not be determined. Manual intervention is required:\n%s", string(out)) + } + + for ruleLabel, deps := range toAdd { + deps := collections.SliceDeduplicate(deps) + log.Printf("buildozer 'add deps %s' %s", strings.Join(deps, " "), ruleLabel) + if err := runBuildozer( + d.progress, + fmt.Sprintf("add deps %s", strings.Join(deps, " ")), + ruleLabel, + ); err != nil { + return err + } + } + for ruleLabel, deps := range toRemove { + deps := collections.SliceDeduplicate(deps) + log.Printf("buildozer 'remove deps %s' %s", strings.Join(deps, " "), ruleLabel) + if err := runBuildozer( + d.progress, + fmt.Sprintf("remove deps %s", strings.Join(deps, " ")), + ruleLabel, + ); err != nil { + return err + } + } + + return d.fix(toBuild) +} + +func runBuildozer(progress mobyprogress.Output, args ...string) error { + writeBuildProgress(progress, fmt.Sprintf("> buildozer %s", strings.Join(args, " "))) + + cmd := exec.Command("buildozer", args...) + cmd.Dir = bazel.GetBuildWorkspaceDirectory() + + output, err := cmd.CombinedOutput() + exitCode := procutil.CmdExitCode(cmd, err) + + if exitCode != 0 { + return fmt.Errorf("buildozer failed with exit code %d: %s", exitCode, string(output)) + } return nil } -func (d *DepFixer) listChangedScalaFiles() ([]string, error) { +func listChangedScalaFiles(repoDir, pkg string) ([]string, error) { args := []string{ "--work-tree", - d.repoDir, + repoDir, "ls-files", "--others", "--modified", "--full-name", "--", - fmt.Sprintf("%s/*.scala", d.pkg), + path.Join(pkg, "*.scala"), } cmd := exec.Command("git", args...) @@ -52,7 +204,7 @@ func (d *DepFixer) listChangedScalaFiles() ([]string, error) { exitCode := procutil.CmdExitCode(cmd, err) if exitCode != 0 { - return nil, fmt.Errorf("git ls-files failed: %s", string(output)) + return nil, fmt.Errorf("%s failed: %s", args, string(output)) } return scanLsFilesOutput(output) @@ -72,3 +224,10 @@ func scanLsFilesOutput(output []byte) (files []string, err error) { } return } + +func writeBuildProgress(output mobyprogress.Output, message string) { + output.WriteProgress(mobyprogress.Progress{ + ID: "build", + Message: message, + }) +} From 9c5c54fc35819d01d6dd8570cbbb687b606360a6 Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Tue, 13 May 2025 16:12:00 -0600 Subject: [PATCH 4/6] sweep: wip --- .../gazelle/scala/autokeep/autokeep.pb.go | 176 +++++++++-------- .../gazelle/scala/autokeep/autokeep.proto | 7 +- cmd/wildcardimportfixer/main.go | 6 +- go.mod | 3 +- go.sum | 4 + go_repos.bzl | 8 +- language/scala/environment.go | 13 +- language/scala/existing_scala_rule.go | 28 +-- language/scala/flags.go | 2 +- language/scala/repair.go | 37 +++- pkg/autokeep/deps.go | 58 ++++-- pkg/autokeep/deps_test.go | 5 +- pkg/autokeep/scanner.go | 1 + pkg/autokeep/scanner_test.go | 1 + pkg/resolver/trie_scope_test.go | 2 +- pkg/scalaconfig/config.go | 28 ++- pkg/sweep/BUILD.bazel | 2 + pkg/sweep/fix.go | 179 ++++++++++++----- pkg/sweep/sweep.go | 5 + pkg/sweep/transitive.go | 187 ++++++++++++++++++ 20 files changed, 559 insertions(+), 193 deletions(-) create mode 100644 pkg/sweep/transitive.go diff --git a/build/stack/gazelle/scala/autokeep/autokeep.pb.go b/build/stack/gazelle/scala/autokeep/autokeep.pb.go index 94d8187e..652396df 100644 --- a/build/stack/gazelle/scala/autokeep/autokeep.pb.go +++ b/build/stack/gazelle/scala/autokeep/autokeep.pb.go @@ -7,7 +7,6 @@ package autokeep import ( - _ "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/parse" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -260,8 +259,9 @@ type NotAMemberOfPackage struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - PackageName string `protobuf:"bytes,1,opt,name=package_name,json=packageName,proto3" json:"package_name,omitempty"` - Symbol string `protobuf:"bytes,2,opt,name=symbol,proto3" json:"symbol,omitempty"` + SourceFile string `protobuf:"bytes,1,opt,name=source_file,json=sourceFile,proto3" json:"source_file,omitempty"` + PackageName string `protobuf:"bytes,2,opt,name=package_name,json=packageName,proto3" json:"package_name,omitempty"` + Symbol string `protobuf:"bytes,3,opt,name=symbol,proto3" json:"symbol,omitempty"` } func (x *NotAMemberOfPackage) Reset() { @@ -296,6 +296,13 @@ func (*NotAMemberOfPackage) Descriptor() ([]byte, []int) { return file_build_stack_gazelle_scala_autokeep_autokeep_proto_rawDescGZIP(), []int{3} } +func (x *NotAMemberOfPackage) GetSourceFile() string { + if x != nil { + return x.SourceFile + } + return "" +} + func (x *NotAMemberOfPackage) GetPackageName() string { if x != nil { return x.PackageName @@ -546,90 +553,89 @@ var file_build_stack_gazelle_scala_autokeep_autokeep_proto_rawDesc = []byte{ 0x6b, 0x65, 0x65, 0x70, 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x22, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, - 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x1a, 0x2a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2f, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2f, 0x73, 0x63, 0x61, - 0x6c, 0x61, 0x2f, 0x70, 0x61, 0x72, 0x73, 0x65, 0x2f, 0x72, 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0x63, 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, - 0x63, 0x73, 0x12, 0x54, 0x0a, 0x0d, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x63, 0x5f, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x62, 0x75, 0x69, 0x6c, - 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, - 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x53, - 0x63, 0x61, 0x6c, 0x61, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x0c, 0x73, 0x63, 0x61, 0x6c, - 0x61, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0xde, 0x03, 0x0a, 0x0b, 0x53, 0x63, 0x61, - 0x6c, 0x61, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x75, 0x6c, 0x65, - 0x5f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x75, - 0x6c, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, - 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, - 0x6c, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x5a, 0x0a, 0x0e, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, - 0x67, 0x5f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, - 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, - 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, - 0x65, 0x65, 0x70, 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x6d, 0x62, 0x6f, - 0x6c, 0x48, 0x00, 0x52, 0x0d, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x6d, 0x62, - 0x6f, 0x6c, 0x12, 0x6f, 0x0a, 0x17, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x5f, 0x6d, 0x65, 0x6d, 0x62, - 0x65, 0x72, 0x5f, 0x6f, 0x66, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, - 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x4e, 0x6f, 0x74, 0x41, 0x4d, 0x65, 0x6d, - 0x62, 0x65, 0x72, 0x4f, 0x66, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x13, - 0x6e, 0x6f, 0x74, 0x41, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x4f, 0x66, 0x50, 0x61, 0x63, 0x6b, - 0x61, 0x67, 0x65, 0x12, 0x6a, 0x0a, 0x14, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x6f, 0x7a, 0x65, 0x72, - 0x5f, 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x36, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, - 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, - 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x6f, 0x7a, 0x65, 0x72, - 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x44, 0x65, 0x70, 0x48, 0x00, 0x52, 0x12, 0x62, 0x75, 0x69, - 0x6c, 0x64, 0x6f, 0x7a, 0x65, 0x72, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x44, 0x65, 0x70, 0x12, - 0x4f, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, - 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x4e, 0x6f, 0x74, 0x46, - 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, - 0x42, 0x07, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x69, 0x0a, 0x0d, 0x4d, 0x69, 0x73, - 0x73, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x79, 0x6d, - 0x62, 0x6f, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, - 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, - 0x65, 0x64, 0x42, 0x79, 0x22, 0x50, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x41, 0x4d, 0x65, 0x6d, 0x62, - 0x65, 0x72, 0x4f, 0x66, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, - 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x22, 0x52, 0x0a, 0x12, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x6f, - 0x7a, 0x65, 0x72, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x44, 0x65, 0x70, 0x12, 0x1d, 0x0a, 0x0a, + 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x22, 0x63, 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, 0x6e, + 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x54, 0x0a, 0x0d, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x63, + 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, + 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, + 0x65, 0x70, 0x2e, 0x53, 0x63, 0x61, 0x6c, 0x61, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x0c, + 0x73, 0x63, 0x61, 0x6c, 0x61, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0xde, 0x03, 0x0a, + 0x0b, 0x53, 0x63, 0x61, 0x6c, 0x61, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x72, 0x75, 0x6c, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x75, - 0x6e, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x44, 0x65, 0x70, 0x22, 0x43, 0x0a, 0x0c, 0x54, 0x79, - 0x70, 0x65, 0x4e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, - 0x53, 0x0a, 0x08, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x65, 0x70, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, - 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, - 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x69, 0x6c, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, - 0x64, 0x65, 0x70, 0x73, 0x22, 0x91, 0x01, 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x44, 0x65, - 0x70, 0x73, 0x12, 0x3e, 0x0a, 0x03, 0x61, 0x64, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x2c, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, - 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, - 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x65, 0x70, 0x73, 0x52, 0x03, 0x61, - 0x64, 0x64, 0x12, 0x44, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x52, 0x09, 0x72, 0x75, 0x6c, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x5a, 0x0a, 0x0e, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, - 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x65, 0x70, 0x73, - 0x52, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x42, 0x73, 0x0a, 0x22, 0x62, 0x75, 0x69, 0x6c, - 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, - 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x50, 0x01, - 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x74, 0x61, - 0x63, 0x6b, 0x62, 0x2f, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2d, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, - 0x65, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x67, 0x61, - 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2f, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2f, 0x61, 0x75, 0x74, 0x6f, - 0x6b, 0x65, 0x65, 0x70, 0x3b, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x53, + 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x48, 0x00, 0x52, 0x0d, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, + 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x6f, 0x0a, 0x17, 0x6e, 0x6f, 0x74, 0x5f, 0x61, 0x5f, + 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x6f, 0x66, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, + 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x4e, 0x6f, 0x74, + 0x41, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x4f, 0x66, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, + 0x48, 0x00, 0x52, 0x13, 0x6e, 0x6f, 0x74, 0x41, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x4f, 0x66, + 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x6a, 0x0a, 0x14, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x6f, 0x7a, 0x65, 0x72, 0x5f, 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x70, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, + 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, + 0x6f, 0x7a, 0x65, 0x72, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x44, 0x65, 0x70, 0x48, 0x00, 0x52, + 0x12, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x6f, 0x7a, 0x65, 0x72, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, + 0x44, 0x65, 0x70, 0x12, 0x4f, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, + 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, + 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x54, 0x79, 0x70, 0x65, + 0x4e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, + 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x69, 0x0a, + 0x0d, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x1f, + 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x69, + 0x72, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x42, 0x79, 0x22, 0x71, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x41, + 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x4f, 0x66, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, + 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x22, 0x52, 0x0a, 0x12, 0x42, + 0x75, 0x69, 0x6c, 0x64, 0x6f, 0x7a, 0x65, 0x72, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x44, 0x65, + 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x75, 0x6c, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x70, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x44, 0x65, 0x70, 0x22, + 0x43, 0x0a, 0x0c, 0x54, 0x79, 0x70, 0x65, 0x4e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x12, + 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x22, 0x53, 0x0a, 0x08, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x65, 0x70, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, + 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, + 0x64, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x70, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x70, 0x73, 0x22, 0x91, 0x01, 0x0a, 0x09, 0x44, 0x65, + 0x6c, 0x74, 0x61, 0x44, 0x65, 0x70, 0x73, 0x12, 0x3e, 0x0a, 0x03, 0x61, 0x64, 0x64, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, + 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, + 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x65, + 0x70, 0x73, 0x52, 0x03, 0x61, 0x64, 0x64, 0x12, 0x44, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, + 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, + 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x2e, 0x52, 0x75, 0x6c, + 0x65, 0x44, 0x65, 0x70, 0x73, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x42, 0x73, 0x0a, + 0x22, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x67, 0x61, 0x7a, + 0x65, 0x6c, 0x6c, 0x65, 0x2e, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x6b, + 0x65, 0x65, 0x70, 0x50, 0x01, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x62, 0x2f, 0x73, 0x63, 0x61, 0x6c, 0x61, 0x2d, 0x67, + 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2f, 0x73, 0x74, 0x61, + 0x63, 0x6b, 0x2f, 0x67, 0x61, 0x7a, 0x65, 0x6c, 0x6c, 0x65, 0x2f, 0x73, 0x63, 0x61, 0x6c, 0x61, + 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, 0x65, 0x70, 0x3b, 0x61, 0x75, 0x74, 0x6f, 0x6b, 0x65, + 0x65, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/build/stack/gazelle/scala/autokeep/autokeep.proto b/build/stack/gazelle/scala/autokeep/autokeep.proto index 00f481e0..51a87e37 100644 --- a/build/stack/gazelle/scala/autokeep/autokeep.proto +++ b/build/stack/gazelle/scala/autokeep/autokeep.proto @@ -2,8 +2,6 @@ syntax = "proto3"; package build.stack.gazelle.scala.autokeep; -import "build/stack/gazelle/scala/parse/rule.proto"; - option go_package = "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/autokeep;autokeep"; option java_package = "build.stack.gazelle.scala.autokeep"; option java_multiple_files = true; @@ -30,8 +28,9 @@ message MissingSymbol { } message NotAMemberOfPackage { - string package_name = 1; - string symbol = 2; + string source_file = 1; + string package_name = 2; + string symbol = 3; } message BuildozerUnusedDep { diff --git a/cmd/wildcardimportfixer/main.go b/cmd/wildcardimportfixer/main.go index 6bed3a2a..60e75258 100644 --- a/cmd/wildcardimportfixer/main.go +++ b/cmd/wildcardimportfixer/main.go @@ -66,7 +66,11 @@ func run(cfg *config) error { BazelExecutable: cfg.bazelExe, }) - symbols, err := fixer.Fix(cfg.ruleLabel, cfg.targetFilename, cfg.importPrefix) + symbols, err := fixer.Fix(&wildcardimport.FixConfig{ + RuleLabel: cfg.ruleLabel, + Filename: cfg.targetFilename, + ImportPrefix: cfg.importPrefix, + }) if err != nil { return err } diff --git a/go.mod b/go.mod index ea7e5beb..8bc8d266 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/proto v1.9.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect @@ -32,7 +33,7 @@ require ( github.com/rs/zerolog v1.34.0 // indirect github.com/stretchr/objx v0.1.0 // indirect golang.org/x/net v0.1.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.4.0 // indirect golang.org/x/time v0.2.0 // indirect golang.org/x/tools v0.2.0 // indirect diff --git a/go.sum b/go.sum index 804dd00e..39c683bc 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -163,6 +165,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/go_repos.bzl b/go_repos.bzl index 1f4184bb..97789535 100644 --- a/go_repos.bzl +++ b/go_repos.bzl @@ -252,8 +252,8 @@ def go_repositories(): name = "com_github_fsnotify_fsnotify", build_file_proto_mode = "disable_global", importpath = "github.com/fsnotify/fsnotify", - sum = "h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=", - version = "v1.6.0", + sum = "h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=", + version = "v1.9.0", ) go_repository( name = "com_github_ghodss_yaml", @@ -554,8 +554,8 @@ def go_repositories(): name = "org_golang_x_sys", build_file_proto_mode = "disable_global", importpath = "golang.org/x/sys", - sum = "h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=", - version = "v0.12.0", + sum = "h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=", + version = "v0.13.0", ) go_repository( name = "org_golang_x_term", diff --git a/language/scala/environment.go b/language/scala/environment.go index 9db1a297..96e1aeed 100644 --- a/language/scala/environment.go +++ b/language/scala/environment.go @@ -1,10 +1,21 @@ package scala -import "github.com/stackb/scala-gazelle/pkg/procutil" +import ( + "os" + + "github.com/stackb/scala-gazelle/pkg/procutil" +) const ( SCALA_GAZELLE_LOG_FILE = procutil.EnvVar("SCALA_GAZELLE_LOG_FILE") SCALA_GAZELLE_SHOW_COVERAGE = procutil.EnvVar("SCALA_GAZELLE_SHOW_COVERAGE") SCALA_GAZELLE_SHOW_PROGRESS = procutil.EnvVar("SCALA_GAZELLE_SHOW_PROGRESS") + SCALA_GAZELLE_WATCH_DIR = procutil.EnvVar("SCALA_GAZELLE_WATCH_DIR") TEST_TMPDIR = procutil.EnvVar("TEST_TMPDIR") ) + +func PrintEnv(printf func(format string, args ...any)) { + for _, env := range os.Environ() { + printf("env: %s", env) + } +} diff --git a/language/scala/existing_scala_rule.go b/language/scala/existing_scala_rule.go index 28c4735f..50ade17e 100644 --- a/language/scala/existing_scala_rule.go +++ b/language/scala/existing_scala_rule.go @@ -15,6 +15,7 @@ import ( "github.com/stackb/scala-gazelle/pkg/protobuf" "github.com/stackb/scala-gazelle/pkg/scalaconfig" "github.com/stackb/scala-gazelle/pkg/scalarule" + "github.com/stackb/scala-gazelle/pkg/sweep" sppb "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/parse" ) @@ -154,20 +155,13 @@ func (s *existingScalaRule) Resolve(rctx *scalarule.ResolveContext, importsRaw i sc.MergeDepsAttr(exports, rctx.Rule, "exports", rctx.From) } - // if sc.ShouldSweepTransitive("deps") { - // if !sweep.HasTransitiveRuleComment(rctx.Rule) { - // if junk, err := sweep.TransitiveAttr("deps", rctx.File, rctx.Rule, rctx.From); err != nil { - // log.Printf("warning: transitive sweep failed: %v", err) - // } else { - // if len(junk) > 0 { - // log.Println(formatBuildozerRemoveDeps(rctx.From, junk)) - // } - // } - // rctx.Rule.AddComment(sweep.TransitiveCommentToken) - // } else { - // log.Println("> transitive sweep skipped (already done):", rctx.From) - // } - // } + if sc.ShouldSweepTransitive(rctx.Rule, "deps") { + if _, err := sweep.TransitiveAttr("deps", rctx.File, rctx.Rule, rctx.From); err != nil { + log.Printf("warning: transitive sweep failed: %v", err) + } else { + sweep.RemoveSweepTransitiveDepsTag(rctx.Rule) + } + } } // wow... this cannot be good programming practive, but I need to store the @@ -175,6 +169,12 @@ func (s *existingScalaRule) Resolve(rctx *scalarule.ResolveContext, importsRaw i rctx.Rule.SetPrivateAttr("_scala_resolve_closure", resolveClosure) resolveClosure() + + if sc.ShouldRepairTransitiveDeps() { + log.Println("Marking", rctx.From) + // mark this rule as needing to be repaired + sweep.MarkForTransitiveRepair(rctx.Rule, rctx.From, sc.MaybeRewrite(rctx.Rule.Kind(), rctx.From)) + } } func makeRuleComments(pb *sppb.Rule) (comments []build.Comment) { diff --git a/language/scala/flags.go b/language/scala/flags.go index 4b9f3fbe..664a13d1 100644 --- a/language/scala/flags.go +++ b/language/scala/flags.go @@ -50,7 +50,7 @@ func (sl *scalaLang) RegisterFlags(flags *flag.FlagSet, cmd string, c *config.Co flags.Var(&sl.existingScalaBinaryRulesFlagValue, existingScalaBinaryRuleFlagName, "LOAD%NAME mapping for a custom existing scala binary rule implementation (e.g. '@io_bazel_rules_scala//scala:scala.bzl%scalabinary'") flags.Var(&sl.existingScalaLibraryRulesFlagValue, existingScalaLibraryRuleFlagName, "LOAD%NAME mapping for a custom existing scala library rule implementation (e.g. '@io_bazel_rules_scala//scala:scala.bzl%scala_library'") flags.Var(&sl.existingScalaTestRulesFlagValue, existingScalaTestRuleFlagName, "LOAD%NAME mapping for a custom existing scala test rule implementation (e.g. '@io_bazel_rules_scala//scala:scala.bzl%scala_test'") - flags.Var(&sl.repairMode, scalaGazelleFixDepsModeFlagName, "optional deps repair mode (one of 'none', 'batch', 'watch')") + flags.Var(&sl.repairMode, scalaGazelleFixDepsModeFlagName, "optional deps repair mode (one of 'none', 'batch', 'watch', 'transitive')") sl.registerSymbolProviders(flags, cmd, c) sl.registerConflictResolvers(flags, cmd, c) diff --git a/language/scala/repair.go b/language/scala/repair.go index 6e9cbba8..90c39c38 100644 --- a/language/scala/repair.go +++ b/language/scala/repair.go @@ -3,9 +3,11 @@ package scala import ( "fmt" "log" + "path" "github.com/bazelbuild/bazel-gazelle/label" "github.com/bazelbuild/bazel-gazelle/rule" + "github.com/stackb/scala-gazelle/pkg/procutil" "github.com/stackb/scala-gazelle/pkg/scalarule" "github.com/stackb/scala-gazelle/pkg/sweep" ) @@ -16,6 +18,7 @@ const ( RepairNone repairMode = iota RepairBatch RepairWatch + RepairTransitive ) // String partially implements the flag.Value interface. @@ -27,6 +30,8 @@ func (i *repairMode) String() string { return "batch" case RepairWatch: return "watch" + case RepairTransitive: + return "transitive" } return "unknown" } @@ -40,6 +45,8 @@ func (i *repairMode) Set(value string) error { *i = RepairBatch case "watch": *i = RepairWatch + case "transitive": + *i = RepairTransitive default: return fmt.Errorf("unknown repair value: %s", value) } @@ -47,6 +54,8 @@ func (i *repairMode) Set(value string) error { } func (sl *scalaLang) repair() { + PrintEnv(log.Printf) + if err := sl.repairDeps(sl.repairMode); err != nil { log.Printf("warning: repair failed: %v", err) } @@ -58,6 +67,8 @@ func (sl *scalaLang) repairDeps(mode repairMode) error { return sl.repairBatch() case RepairWatch: return sl.repairWatch() + case RepairTransitive: + return sl.repairTransitive() default: return nil } @@ -67,12 +78,34 @@ func (sl *scalaLang) repairBatch() error { rules := gatherResolvableScalaRuleMap(sl.knownRules) imports := makeResolvedImports(sl.globalScope) - fixer := sweep.NewDepFixer(sl.progress, sl.repoRoot, "", rules, imports.Imports, sl) + fixer := sweep.NewDepFixer(sl.progress, sl.repoRoot, "", rules, imports.Imports, sl, sl.globalScope) return fixer.Batch() } func (sl *scalaLang) repairWatch() error { - return fmt.Errorf("repairWatch unimplemented") + dir, ok := procutil.LookupEnv(SCALA_GAZELLE_WATCH_DIR) + if !ok { + return fmt.Errorf("error: %v must be set to the directory to watch", SCALA_GAZELLE_WATCH_DIR) + } + if !path.IsAbs(dir) { + dir = path.Join(sl.repoRoot, dir) + } + + rules := gatherResolvableScalaRuleMap(sl.knownRules) + imports := makeResolvedImports(sl.globalScope) + + fixer := sweep.NewDepFixer(sl.progress, sl.repoRoot, "", rules, imports.Imports, sl, sl.globalScope) + + return fixer.Watch(dir) +} + +func (sl *scalaLang) repairTransitive() error { + rules := gatherResolvableScalaRuleMap(sl.knownRules) + imports := makeResolvedImports(sl.globalScope) + + fixer := sweep.NewDepFixer(sl.progress, sl.repoRoot, "", rules, imports.Imports, sl, sl.globalScope) + + return fixer.Transitive() } func gatherResolvableScalaRuleMap(knownRules map[label.Label]*rule.Rule) sweep.ResolvableScalaRuleMap { diff --git a/pkg/autokeep/deps.go b/pkg/autokeep/deps.go index fe85742c..d6812c2e 100644 --- a/pkg/autokeep/deps.go +++ b/pkg/autokeep/deps.go @@ -22,7 +22,7 @@ import ( type DepsMap map[string]string type FileMap map[string]*sppb.File -func MakeDeltaDeps(diagnostics *akpb.Diagnostics, deps DepsMap, files FileMap, scopes resolver.KnownScopeRegistry) *akpb.DeltaDeps { +func MakeDeltaDeps(diagnostics *akpb.Diagnostics, deps DepsMap, files FileMap, scopes resolver.KnownScopeRegistry, globalScope resolver.Scope) *akpb.DeltaDeps { rules := make(map[string]*akpb.RuleDeps) delta := new(akpb.DeltaDeps) for _, e := range diagnostics.ScalacErrors { @@ -32,22 +32,31 @@ func MakeDeltaDeps(diagnostics *akpb.Diagnostics, deps DepsMap, files FileMap, s rule.Label = e.RuleLabel rule.BuildFile = e.BuildFile } + + lookup := func(sourceFile string, name string) (*resolver.Symbol, bool) { + scope, ok := scopes.GetKnownScope(sourceFile) + if !ok { + return nil, false + } + sym, ok := scope.GetSymbol(name) + if !ok { + return nil, false + } + return sym, true + } + switch t := e.Error.(type) { case *akpb.ScalacError_NotFound: - if scope, ok := scopes.GetKnownScope(t.NotFound.SourceFile); ok { - if sym, ok := scope.GetSymbol(t.NotFound.Type); ok { - if sym.Label != label.NoLabel { - log.Println("MATCH: ", sym, "satisfies not found type", t.NotFound.Type) - if len(rule.Deps) == 0 { - delta.Add = append(delta.Add, rule) - } - rule.Deps = append(rule.Deps, sym.Label.String()) + if sym, ok := lookup(t.NotFound.SourceFile, t.NotFound.Type); ok { + if sym.Label != label.NoLabel { + log.Println("MATCH: ", sym, "satisfies not found type", t.NotFound.Type) + if len(rule.Deps) == 0 { + delta.Add = append(delta.Add, rule) } - } else { - log.Printf("MISS (scope not found): %s", t.NotFound.SourceFile) + rule.Deps = append(rule.Deps, sym.Label.String()) } } else { - log.Printf("MISS (unknown source file): %q", t.NotFound.SourceFile) + log.Println("MISS (not found):", t.NotFound.SourceFile, t.NotFound.Type) } case *akpb.ScalacError_MissingSymbol: sym := t.MissingSymbol.Symbol @@ -61,16 +70,25 @@ func MakeDeltaDeps(diagnostics *akpb.Diagnostics, deps DepsMap, files FileMap, s log.Printf("MISS (missing symbol not found): %q", sym) } case *akpb.ScalacError_NotAMemberOfPackage: - sym := fmt.Sprintf("%s.%s", t.NotAMemberOfPackage.PackageName, t.NotAMemberOfPackage.Symbol) - if label, ok := deps[sym]; ok { - log.Println("MATCH: ", sym, "is provided by", label) - if len(rule.Deps) == 0 { - delta.Add = append(delta.Add, rule) + name := fmt.Sprintf("%s.%s", t.NotAMemberOfPackage.PackageName, t.NotAMemberOfPackage.Symbol) + if sym, ok := lookup(t.NotAMemberOfPackage.SourceFile, name); ok { + if sym.Label != label.NoLabel { + log.Println("MATCH: ", sym, "satisfies not a member of package type", name) + if len(rule.Deps) == 0 { + delta.Add = append(delta.Add, rule) + } + rule.Deps = append(rule.Deps, sym.Label.String()) } - rule.Deps = append(rule.Deps, label) - } else { - log.Printf("MISS (not found): %q (%d)", sym, len(deps)) } + // if label, ok := deps[sym]; ok { + // log.Println("MATCH: ", sym, "is provided by", label) + // if len(rule.Deps) == 0 { + // delta.Add = append(delta.Add, rule) + // } + // rule.Deps = append(rule.Deps, label) + // } else { + // log.Printf("MISS (not found): %q (%d)", sym, len(deps)) + // } case *akpb.ScalacError_BuildozerUnusedDep: delta.Remove = append(delta.Remove, rule) rule.Deps = append(rule.Deps, t.BuildozerUnusedDep.UnusedDep) diff --git a/pkg/autokeep/deps_test.go b/pkg/autokeep/deps_test.go index b081a1d3..3b8af948 100644 --- a/pkg/autokeep/deps_test.go +++ b/pkg/autokeep/deps_test.go @@ -8,8 +8,9 @@ import ( "github.com/bazelbuild/bazel-gazelle/testtools" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - akpb "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/autokeep" "github.com/stackb/scala-gazelle/pkg/testutil" + + akpb "github.com/stackb/scala-gazelle/build/stack/gazelle/scala/autokeep" ) func TestMakeDeltaDeps(t *testing.T) { @@ -52,7 +53,7 @@ func TestMakeDeltaDeps(t *testing.T) { }, } { t.Run(name, func(t *testing.T) { - got := MakeDeltaDeps(tc.diagnostics, tc.deps, nil, nil) + got := MakeDeltaDeps(tc.diagnostics, tc.deps, nil, nil, nil) if diff := cmp.Diff(tc.want, got, cmpopts.IgnoreUnexported( akpb.DeltaDeps{}, diff --git a/pkg/autokeep/scanner.go b/pkg/autokeep/scanner.go index 5593d19e..1a935f29 100644 --- a/pkg/autokeep/scanner.go +++ b/pkg/autokeep/scanner.go @@ -84,6 +84,7 @@ func ScanOutput(output []byte) (*akpb.Diagnostics, error) { addError(&akpb.ScalacError{ Error: &akpb.ScalacError_NotAMemberOfPackage{ NotAMemberOfPackage: &akpb.NotAMemberOfPackage{ + SourceFile: match[1], Symbol: match[2], PackageName: match[3], }, diff --git a/pkg/autokeep/scanner_test.go b/pkg/autokeep/scanner_test.go index c99abb30..29e6be65 100644 --- a/pkg/autokeep/scanner_test.go +++ b/pkg/autokeep/scanner_test.go @@ -43,6 +43,7 @@ omnistac/postswarm/src/it/scala/omnistac/postswarm/grey/SelectiveSpottingTest.sc BuildFile: "/Users/pcj/go/src/github.com/Omnistac/unity/omnistac/postswarm/BUILD.bazel", Error: &akpb.ScalacError_NotAMemberOfPackage{ NotAMemberOfPackage: &akpb.NotAMemberOfPackage{ + SourceFile: "omnistac/postswarm/src/it/scala/omnistac/postswarm/grey/SelectiveSpottingTest.scala", Symbol: "SelectiveSpotSessionUtils", PackageName: "omnistac.postswarm", }, diff --git a/pkg/resolver/trie_scope_test.go b/pkg/resolver/trie_scope_test.go index 1a8e1346..46785bf1 100644 --- a/pkg/resolver/trie_scope_test.go +++ b/pkg/resolver/trie_scope_test.go @@ -15,7 +15,7 @@ func makeSymbol(typ sppb.ImportType, name string, from label.Label) *Symbol { return &Symbol{ Type: typ, Name: name, - Label: label.NoLabel, + Label: from, Provider: "test", } } diff --git a/pkg/scalaconfig/config.go b/pkg/scalaconfig/config.go index b151c3a8..ddcdd104 100644 --- a/pkg/scalaconfig/config.go +++ b/pkg/scalaconfig/config.go @@ -139,6 +139,7 @@ func DirectiveNames() []string { scalaDepsCleanerDirective, sweep.ScalaKeepUnknownDepsDirective, sweep.ScalaSweepTransitiveDepsDirective, + sweep.ScalaRepairTransitiveDepsDirective, sweep.ScalaFixDepsDirective, scalaFixWildcardImportDirective, scalaGenerateBuildFilesDirective, @@ -163,6 +164,7 @@ type Config struct { generateBuildFiles bool keepUnknownDeps bool sweepTransitiveDeps bool + repairTransitiveDeps bool fixDeps bool logger zerolog.Logger logLevel zerolog.Level @@ -212,6 +214,7 @@ func (c *Config) clone(config *config.Config, rel string) *Config { clone.generateBuildFiles = c.generateBuildFiles clone.keepUnknownDeps = c.keepUnknownDeps clone.sweepTransitiveDeps = c.sweepTransitiveDeps + clone.repairTransitiveDeps = c.repairTransitiveDeps clone.fixDeps = c.fixDeps for k, v := range c.annotations { @@ -316,6 +319,12 @@ func (c *Config) ParseDirectives(directives []rule.Directive) error { } else { c.sweepTransitiveDeps = value } + case sweep.ScalaRepairTransitiveDepsDirective: + if value, err := parseBoolDirective(d); err != nil { + return err + } else { + c.repairTransitiveDeps = value + } case scalaFixWildcardImportDirective: c.parseFixWildcardImport(d) case resolveGlobDirective: @@ -609,10 +618,13 @@ func (c *Config) depSuffixComment(imp *resolver.Import) *build.Comment { // ShouldSweepTransitive determines whether non-managed deps (not generated, and // not marked with # keep) in the current package should be kept. -func (c *Config) ShouldSweepTransitive(attrName string) bool { +func (c *Config) ShouldSweepTransitive(r *rule.Rule, attrName string) bool { if attrName != "deps" { return false } + if sweep.HasSweepTransitiveDepsTag(r) { + return true + } return c.sweepTransitiveDeps } @@ -627,6 +639,12 @@ func (c *Config) shouldKeepUnknownDeps() bool { return c.keepUnknownDeps } +// ShouldRepairTransitiveDeps determines whether non-managed deps should be +// kept. +func (c *Config) ShouldRepairTransitiveDeps() bool { + return c.repairTransitiveDeps +} + // ShouldFixWildcardImport tests whether the given symbol name pattern // should be resolved within the scope of the given filename pattern. // resolveFileSymbolNameSpecs represent a whitelist; if no patterns match, false @@ -717,7 +735,7 @@ func (c *Config) MergeDepsAttr( // Merge the current list against the new incoming ones. If no deps remain, // delete the attr. - next := c.mergeDeps(r.Attr(attrName), deps, labels, attrName, from) + next := c.mergeDeps(deps, labels, attrName, r, from) if len(next.List) > 0 { r.SetAttr(attrName, next) @@ -729,7 +747,9 @@ func (c *Config) MergeDepsAttr( // mergeDeps filters out a `deps` list. Extries are removed from the list if // they can be parsed as dependency labels that have a provider. Others types // of expressions are left as-is. -func (c *Config) mergeDeps(attrValue build.Expr, deps map[label.Label]bool, importLabels map[label.Label]*resolver.ImportLabel, attrName string, from label.Label) *build.ListExpr { +func (c *Config) mergeDeps(deps map[label.Label]bool, importLabels map[label.Label]*resolver.ImportLabel, attrName string, r *rule.Rule, from label.Label) *build.ListExpr { + attrValue := r.Attr(attrName) + var src *build.ListExpr if attrValue != nil { if current, ok := attrValue.(*build.ListExpr); ok { @@ -777,7 +797,7 @@ func (c *Config) mergeDeps(attrValue build.Expr, deps map[label.Label]bool, impo // are in sweep mode, mark it, keep it and it will be checked by the // sweeper process. Otherwise, only keep it if has already been // marked as TRANSITIVE. - if c.ShouldSweepTransitive(attrName) { + if c.ShouldSweepTransitive(r, attrName) { // set as TRANSITIVE comment for sweeping if _, ok := expr.(*build.StringExpr); ok { expr.Comment().Suffix = []build.Comment{sweep.MakeTransitiveComment()} diff --git a/pkg/sweep/BUILD.bazel b/pkg/sweep/BUILD.bazel index 3093e583..217df48d 100644 --- a/pkg/sweep/BUILD.bazel +++ b/pkg/sweep/BUILD.bazel @@ -6,6 +6,7 @@ go_library( srcs = [ "fix.go", "sweep.go", + "transitive.go", ], importpath = "github.com/stackb/scala-gazelle/pkg/sweep", visibility = ["//visibility:public"], @@ -20,6 +21,7 @@ go_library( "@bazel_gazelle//label:go_default_library", "@bazel_gazelle//rule:go_default_library", "@com_github_bazelbuild_buildtools//build:go_default_library", + "@com_github_fsnotify_fsnotify//:fsnotify", "@com_github_pcj_mobyprogress//:mobyprogress", ], ) diff --git a/pkg/sweep/fix.go b/pkg/sweep/fix.go index 392992bd..f12e51ba 100644 --- a/pkg/sweep/fix.go +++ b/pkg/sweep/fix.go @@ -7,9 +7,9 @@ import ( "log" "os/exec" "path" - "sort" "strings" + "github.com/fsnotify/fsnotify" "github.com/pcj/mobyprogress" "github.com/stackb/scala-gazelle/pkg/autokeep" "github.com/stackb/scala-gazelle/pkg/bazel" @@ -40,11 +40,11 @@ type DepFixer struct { imports autokeep.DepsMap // global knownScopes knownScopes resolver.KnownScopeRegistry - // scala file Parser - + // the global scope + globalScope resolver.Scope } -func NewDepFixer(progress mobyprogress.Output, repoRoot, pkg string, resolvers ResolvableScalaRuleMap, imports map[string]string, knownScopes resolver.KnownScopeRegistry) *DepFixer { +func NewDepFixer(progress mobyprogress.Output, repoRoot, pkg string, resolvers ResolvableScalaRuleMap, imports map[string]string, knownScopes resolver.KnownScopeRegistry, globalScope resolver.Scope) *DepFixer { fixer := &DepFixer{ progress: progress, repoRoot: repoRoot, @@ -54,6 +54,7 @@ func NewDepFixer(progress mobyprogress.Output, repoRoot, pkg string, resolvers R files: make(map[string]*sppb.File), imports: imports, knownScopes: knownScopes, + globalScope: globalScope, } for r := range resolvers { files := r.Rule().Files @@ -65,12 +66,63 @@ func NewDepFixer(progress mobyprogress.Output, repoRoot, pkg string, resolvers R return fixer } +func (d *DepFixer) Watch(dir string) error { + // Create new watcher. + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + + // Start listening for events. + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + log.Println("event:", event) + if event.Has(fsnotify.Write) { + rel := strings.TrimPrefix(strings.TrimPrefix(event.Name, d.repoRoot), "/") + log.Println("modified file:", event.Name, rel) + if err := d.handleChangedFiles(rel); err != nil { + log.Println("watch error:", err) + } + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Println("error:", err) + } + } + }() + + err = watcher.Add(dir) + if err != nil { + log.Fatal(err) + } + + log.Println("watching", dir) + + // Block main goroutine forever. + <-make(chan struct{}) + + return nil +} + func (d *DepFixer) Batch() error { changedFiles, err := listChangedScalaFiles(d.repoRoot, d.pkg) if err != nil { return err } + return d.handleChangedFiles(changedFiles...) +} + +func (d *DepFixer) handleChangedFiles(changedFiles ...string) error { + log.Println("changed files:", changedFiles) // toBuild helps gather the list of rules that need to be built based on the @@ -86,7 +138,9 @@ func (d *DepFixer) Batch() error { } } + targets := make([]string, 0, len(toBuild)) for label, rule := range toBuild { + targets = append(targets, label) // re-parse the srcs if err := rule.ParseSrcs(); err != nil { return fmt.Errorf("parse %s failed: %v", label, err) @@ -94,30 +148,44 @@ func (d *DepFixer) Batch() error { // call the resolver for the rule d.resolvers[rule]() } - if err := d.fix(toBuild); err != nil { - return fmt.Errorf("building rule: %v", err) - } + // if err := d.Repair(targets...); err != nil { + // return fmt.Errorf("building rule: %v", err) + // } return nil } -func (d *DepFixer) fix(toBuild map[string]scalarule.Rule) error { - labels := make([]string, 0, len(toBuild)) - for label := range toBuild { - labels = append(labels, label) - } - if len(labels) == 0 { +type RepairHandler interface { + // Targets returns the list of build targets that should be repaired + Targets() []string + // Add is a callback when the given target should add (missing) deps + Add(target string, deps []string) + // Remove is a callback when the given target should remove (unused) deps + Remove(target string, deps []string) + // Apply signals that the current actions should be applied. + Apply(iteration int) error + // Done signals that the repair process has completed + Done() +} + +func (d *DepFixer) Repair(handler RepairHandler) error { + return d.repairIteration(handler, 1) +} + +func (d *DepFixer) repairIteration(handler RepairHandler, iteration int) error { + targets := collections.DeduplicateAndSort(handler.Targets()) + if len(targets) == 0 { return nil } - sort.Strings(labels) - log.Println("fixing:", labels) + log.Println("fixing:", targets) - writeBuildProgress(d.progress, fmt.Sprintf("> bazel build %s", strings.Join(labels, " "))) + writeBuildProgress(d.progress, fmt.Sprintf("> bazel build %s", strings.Join(targets, " "))) - out, exitCode, _ := bazel.ExecCommand("bazel", "build", labels...) + out, exitCode, _ := bazel.ExecCommand("bazel", "build", targets...) if exitCode == 0 { log.Println("builds cleanly (no action needed)") + handler.Done() return nil } @@ -126,17 +194,17 @@ func (d *DepFixer) fix(toBuild map[string]scalarule.Rule) error { return fmt.Errorf("failed to scan build output: %v", err) } - delta := autokeep.MakeDeltaDeps(diagnostics, d.imports, d.files, d.knownScopes) + delta := autokeep.MakeDeltaDeps(diagnostics, d.imports, d.files, d.knownScopes, d.globalScope) toAdd := make(map[string][]string) toRemove := make(map[string][]string) for _, ruleDeps := range delta.Add { - toBuild[ruleDeps.Label] = nil + targets = append(targets, ruleDeps.Label) toAdd[ruleDeps.Label] = append(toAdd[ruleDeps.Label], ruleDeps.Deps...) } for _, ruleDeps := range delta.Remove { - toBuild[ruleDeps.Label] = nil + targets = append(targets, ruleDeps.Label) toRemove[ruleDeps.Label] = append(toRemove[ruleDeps.Label], ruleDeps.Deps...) } @@ -146,45 +214,17 @@ func (d *DepFixer) fix(toBuild map[string]scalarule.Rule) error { } for ruleLabel, deps := range toAdd { - deps := collections.SliceDeduplicate(deps) - log.Printf("buildozer 'add deps %s' %s", strings.Join(deps, " "), ruleLabel) - if err := runBuildozer( - d.progress, - fmt.Sprintf("add deps %s", strings.Join(deps, " ")), - ruleLabel, - ); err != nil { - return err - } + handler.Add(ruleLabel, collections.SliceDeduplicate(deps)) } for ruleLabel, deps := range toRemove { - deps := collections.SliceDeduplicate(deps) - log.Printf("buildozer 'remove deps %s' %s", strings.Join(deps, " "), ruleLabel) - if err := runBuildozer( - d.progress, - fmt.Sprintf("remove deps %s", strings.Join(deps, " ")), - ruleLabel, - ); err != nil { - return err - } + handler.Remove(ruleLabel, collections.SliceDeduplicate(deps)) } - return d.fix(toBuild) -} - -func runBuildozer(progress mobyprogress.Output, args ...string) error { - writeBuildProgress(progress, fmt.Sprintf("> buildozer %s", strings.Join(args, " "))) - - cmd := exec.Command("buildozer", args...) - cmd.Dir = bazel.GetBuildWorkspaceDirectory() - - output, err := cmd.CombinedOutput() - exitCode := procutil.CmdExitCode(cmd, err) - - if exitCode != 0 { - return fmt.Errorf("buildozer failed with exit code %d: %s", exitCode, string(output)) + if err := handler.Apply(iteration); err != nil { + return err } - return nil + return d.repairIteration(handler, iteration+1) } func listChangedScalaFiles(repoDir, pkg string) ([]string, error) { @@ -231,3 +271,36 @@ func writeBuildProgress(output mobyprogress.Output, message string) { Message: message, }) } + +// log.Printf("buildozer 'add deps %s' %s", strings.Join(deps, " "), ruleLabel) +// if err := runBuildozer( +// d.progress, +// fmt.Sprintf("add deps %s", strings.Join(deps, " ")), +// ruleLabel, +// ); err != nil { +// return err +// } +// log.Printf("buildozer 'remove deps %s' %s", strings.Join(deps, " "), ruleLabel) +// if err := runBuildozer( +// d.progress, +// fmt.Sprintf("remove deps %s", strings.Join(deps, " ")), +// ruleLabel, +// ); err != nil { +// return err +// } + +// func runBuildozer(args ...string) error { +// // writeBuildProgress(progress, fmt.Sprintf("> buildozer %s", strings.Join(args, " "))) + +// cmd := exec.Command("buildozer", args...) +// cmd.Dir = bazel.GetBuildWorkspaceDirectory() + +// output, err := cmd.CombinedOutput() +// exitCode := procutil.CmdExitCode(cmd, err) + +// if exitCode != 0 { +// return fmt.Errorf("buildozer failed with exit code %d: %s", exitCode, string(output)) +// } + +// return nil +// } diff --git a/pkg/sweep/sweep.go b/pkg/sweep/sweep.go index fa1d7a80..c61cc5e7 100644 --- a/pkg/sweep/sweep.go +++ b/pkg/sweep/sweep.go @@ -20,6 +20,11 @@ const ( // gazelle:scala_sweep_transitive_deps true ScalaSweepTransitiveDepsDirective = "scala_sweep_transitive_deps" + // Turn on the dep repair + // + // gazelle:scala_repair_transitive_deps true + ScalaRepairTransitiveDepsDirective = "scala_repair_transitive_deps" + // Flag to preserve deps if the label is not known to be needed from the // imports (legacy migration mode). // diff --git a/pkg/sweep/transitive.go b/pkg/sweep/transitive.go new file mode 100644 index 00000000..b81318b6 --- /dev/null +++ b/pkg/sweep/transitive.go @@ -0,0 +1,187 @@ +package sweep + +import ( + "bytes" + "fmt" + "io" + "log" + "os/exec" + "sort" + "strings" + + "github.com/bazelbuild/bazel-gazelle/label" + "github.com/bazelbuild/bazel-gazelle/rule" +) + +const ( + REPAIRED = "REPAIRED" +) + +var ( + forRepair = make(map[string]*repairSpec) +) + +type repairSpec struct { + // rule is the rule object to be repaired + rule *rule.Rule + // from is the label as it would appear in a BUILD file + from label.Label + // target is the scala target label that would appear in a log (macro + // expansion), derived from label rewrites + target label.Label +} + +func MarkForTransitiveRepair(rule *rule.Rule, from label.Label, target label.Label) { + spec := &repairSpec{rule, from, target} + forRepair[from.String()] = spec + forRepair[target.String()] = spec +} + +func (d *DepFixer) Transitive() error { + targets := make([]string, 0, len(forRepair)) + for target := range forRepair { + targets = append(targets, target) + } + sort.Strings(targets) + + for _, target := range targets { + spec := forRepair[target] + + if hasRepairedComment(spec.rule) { + log.Printf("%s already repaired (skipping)", target) + } + + handler := NewTransitiveDeltaDepsHandler(spec) + + if err := d.Repair(handler); err != nil { + return fmt.Errorf("building rule: %v", err) + } + } + + return nil +} + +func hasRepairedComment(rule *rule.Rule) bool { + for _, s := range rule.Comments() { + if s == REPAIRED { + return true + } + } + return false +} + +func NewTransitiveDeltaDepsHandler(spec *repairSpec) *TransitiveDeltaDepsHandler { + return &TransitiveDeltaDepsHandler{spec: spec} +} + +type TransitiveDeltaDepsHandler struct { + spec *repairSpec + commands []string +} + +func (h *TransitiveDeltaDepsHandler) queueCommand(action, target string) { + command := fmt.Sprintf("%s|%s", action, target) + log.Println("buildozer:", command) + h.commands = append(h.commands, command) +} + +// Targets implements part of the RepairHandler interface +func (h *TransitiveDeltaDepsHandler) Targets() []string { + return []string{h.spec.from.String()} +} + +// Targets implements part of the RepairHandler interface +func (h *TransitiveDeltaDepsHandler) Add(target string, deps []string) { + if spec, ok := forRepair[target]; ok { + target = spec.from.String() + } + h.queueCommand("add deps "+strings.Join(deps, " "), target) + + for _, dep := range deps { + h.queueCommand("comment deps "+dep+" TRANSITIVE", target) + } +} + +// Remove implements part of the RepairHandler interface +func (h *TransitiveDeltaDepsHandler) Remove(target string, deps []string) { + if spec, ok := forRepair[target]; ok { + target = spec.from.String() + } + h.queueCommand("remove deps "+strings.Join(deps, " "), target) +} + +// Done implements part of the RepairHandler interface +func (h *TransitiveDeltaDepsHandler) Done() { + h.spec.rule.AddComment(REPAIRED) +} + +// TarApplygets implements part of the RepairHandler interface +func (h *TransitiveDeltaDepsHandler) Apply(iteration int) error { + return runBuildozerCommands(h.commands...) +} + +func runBuildozerCommands(commands ...string) error { + // Create the command + cmd := exec.Command("buildozer", "-f", "-") + + // Create a pipe to send input to the command + stdin, err := cmd.StdinPipe() + if err != nil { + return fmt.Errorf("creating stdin pipe: %v", err) + } + + // Create buffers to capture stdout and stderr + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + // Start the command + if err := cmd.Start(); err != nil { + return fmt.Errorf("starting command: %v", err) + } + + // Send input to the command + input := strings.Join(commands, "\n") + log.Println("buildozer input:\n", input) + _, err = io.WriteString(stdin, input) + if err != nil { + return fmt.Errorf("writing to stdin: %v", err) + } + + // Close stdin to signal that we're done sending input + stdin.Close() + + // Wait for the command to finish + if err := cmd.Wait(); err != nil { + return fmt.Errorf("waiting for command (%v): stderr: %s", err, stderr.String()) + } + + // Print the output + return nil +} + +func HasSweepTransitiveDepsTag(r *rule.Rule) bool { + for _, tag := range r.AttrStrings("tags") { + if tag == "sweep-transitive-deps" { + return true + } + } + return false +} + +func RemoveSweepTransitiveDepsTag(r *rule.Rule) { + tags := make([]string, 0) + + for _, tag := range r.AttrStrings("tags") { + if tag == "sweep-transitive-deps" { + continue + } + tags = append(tags, tag) + } + + if len(tags) == 0 { + r.DelAttr("tags") + } else { + r.SetAttr("tags", tags) + } +} From d752c2eeaae3590bb5453ff0eb54b28a8ae667c3 Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Wed, 14 May 2025 19:00:11 -0600 Subject: [PATCH 5/6] wip git commands --- language/scala/repair.go | 151 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 2 deletions(-) diff --git a/language/scala/repair.go b/language/scala/repair.go index 90c39c38..9262f491 100644 --- a/language/scala/repair.go +++ b/language/scala/repair.go @@ -3,10 +3,14 @@ package scala import ( "fmt" "log" + "os" + "os/exec" "path" + "strings" "github.com/bazelbuild/bazel-gazelle/label" "github.com/bazelbuild/bazel-gazelle/rule" + "github.com/stackb/scala-gazelle/pkg/bazel" "github.com/stackb/scala-gazelle/pkg/procutil" "github.com/stackb/scala-gazelle/pkg/scalarule" "github.com/stackb/scala-gazelle/pkg/sweep" @@ -21,6 +25,10 @@ const ( RepairTransitive ) +var ( + CI = procutil.EnvVar("CI") +) + // String partially implements the flag.Value interface. func (i *repairMode) String() string { switch *i { @@ -36,6 +44,13 @@ func (i *repairMode) String() string { return "unknown" } +func maybeGitCommitAndPushBuildFiles() error { + if !isCI() { + return nil + } + return gitCommitAndPushBuildFiles("build(gazelle): sweep transitive deps") +} + // Set implements the flag.Value interface. func (i *repairMode) Set(value string) error { switch value { @@ -54,14 +69,16 @@ func (i *repairMode) Set(value string) error { } func (sl *scalaLang) repair() { - PrintEnv(log.Printf) - if err := sl.repairDeps(sl.repairMode); err != nil { log.Printf("warning: repair failed: %v", err) } } func (sl *scalaLang) repairDeps(mode repairMode) error { + if err := maybeGitCommitAndPushBuildFiles(); err != nil { + return err + } + switch mode { case RepairBatch: return sl.repairBatch() @@ -122,3 +139,133 @@ func gatherResolvableScalaRuleMap(knownRules map[label.Label]*rule.Rule) sweep.R return scalaRules } + +func isCI() bool { + return procutil.LookupBoolEnv(CI, false) +} + +func gitCommit() error { + cmd := exec.Command("git", "commit", "-am", "build(gazelle): sweep transitive deps") + cmd.Dir = bazel.GetBuildWorkspaceDirectory() + + output, err := cmd.CombinedOutput() + exitCode := procutil.CmdExitCode(cmd, err) + if exitCode != 0 { + return fmt.Errorf("git commit failed: %v\n%s", err, string(output)) + } + + return nil +} + +func gitHasChanges() (bool, error) { + // Check for uncommitted changes + cmd := exec.Command("git", "diff", "--quiet", "HEAD") + cmd.Dir = bazel.GetBuildWorkspaceDirectory() + + err := cmd.Run() + exitCode := procutil.CmdExitCode(cmd, err) + + if exitCode == 0 { + // Exit code 0 means no changes (diff is empty) + return false, nil + } else if exitCode == 1 { + // Exit code 1 from git diff --quiet means changes exist + return true, nil + } else { + // Any other exit code indicates an error + output, _ := exec.Command("git", "diff", "HEAD").CombinedOutput() + return false, fmt.Errorf("git diff failed with exit code %d: %v\n%s", exitCode, err, string(output)) + } +} + +func gitPush() error { + cmd := exec.Command("git", "push", "origin", "HEAD") + cmd.Dir = bazel.GetBuildWorkspaceDirectory() + + output, err := cmd.CombinedOutput() + exitCode := procutil.CmdExitCode(cmd, err) + if exitCode != 0 { + return fmt.Errorf("git push failed: %v\n%s", err, string(output)) + } + + return nil +} + +// gitCommitAndPushBuildFiles commits only BUILD.bazel files with the given message +// and skips pre-commit hooks +func gitCommitAndPushBuildFiles(message string) error { + repoDir := bazel.GetBuildWorkspaceDirectory() + + // + // FIND CHANGED FILES + // + + // Find all changed BUILD.bazel files in the repository + findCmd := exec.Command("git", "ls-files", "--modified", "--exclude-standard", "*BUILD.bazel") + findCmd.Dir = repoDir + + buildFiles, findErr := findCmd.Output() + findExitCode := procutil.CmdExitCode(findCmd, findErr) + if findExitCode != 0 { + return fmt.Errorf("finding BUILD.bazel files failed: %v", findErr) + } + + // If no BUILD.bazel files found, nothing to commit + if len(strings.TrimSpace(string(buildFiles))) == 0 { + return nil + } + + // + // ADD FILES + // + + // Convert the output to a list of files + fileList := strings.Fields(string(buildFiles)) + + // Add all BUILD.bazel files in a single command + addCmd := exec.Command("git", append([]string{"add"}, fileList...)...) + addCmd.Dir = repoDir + + addOutput, addErr := addCmd.CombinedOutput() + addExitCode := procutil.CmdExitCode(addCmd, addErr) + if addExitCode != 0 { + return fmt.Errorf("git add failed: %v\n%s", addErr, string(addOutput)) + } + + // + // COMMIT + // + + // Then commit the staged changes, skipping pre-commit hooks + commitCmd := exec.Command("git", "commit", "-m", message, "--no-verify") + commitCmd.Dir = repoDir + + // Set environment variables to ensure pre-commit hooks are skipped + env := os.Environ() + commitCmd.Env = append(env, "SKIP=1", "GIT_SKIP_HOOKS=1") + + commitOutput, commitErr := commitCmd.CombinedOutput() + commitExitCode := procutil.CmdExitCode(commitCmd, commitErr) + if commitExitCode != 0 { + // Exit code 1 with "nothing to commit" message is normal if no changes were staged + if strings.Contains(string(commitOutput), "nothing to commit") { + return nil // No changes to commit, not an error + } + return fmt.Errorf("git commit failed: %v\n%s", commitErr, string(commitOutput)) + } + + // + // PUSH + // + + pushCmd := exec.Command("git", "push", "origin", "HEAD") + pushCmd.Dir = repoDir + + pushOutput, err := pushCmd.CombinedOutput() + exitCode := procutil.CmdExitCode(pushCmd, err) + if exitCode != 0 { + return fmt.Errorf("git push failed: %v\n%s", err, string(pushOutput)) + } + + return nil +} From 43c51e8428e059efaba12436a8ee361cb439a147 Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Thu, 15 May 2025 14:21:38 -0600 Subject: [PATCH 6/6] wip transitive deps cleanup --- language/scala/existing_scala_rule.go | 5 ++-- language/scala/repair.go | 16 +++++++++- pkg/scalaconfig/config.go | 12 ++++++-- pkg/sweep/sweep.go | 42 +++++++++++++++++++++++++++ pkg/sweep/transitive.go | 24 ++++++++++++--- 5 files changed, 89 insertions(+), 10 deletions(-) diff --git a/language/scala/existing_scala_rule.go b/language/scala/existing_scala_rule.go index 50ade17e..0b39ceec 100644 --- a/language/scala/existing_scala_rule.go +++ b/language/scala/existing_scala_rule.go @@ -156,10 +156,11 @@ func (s *existingScalaRule) Resolve(rctx *scalarule.ResolveContext, importsRaw i } if sc.ShouldSweepTransitive(rctx.Rule, "deps") { - if _, err := sweep.TransitiveAttr("deps", rctx.File, rctx.Rule, rctx.From); err != nil { + if _, err := sweep.UnknownAttr("deps", rctx.File, rctx.Rule, rctx.From); err != nil { log.Printf("warning: transitive sweep failed: %v", err) } else { - sweep.RemoveSweepTransitiveDepsTag(rctx.Rule) + // sweep.RemoveSweepTransitiveDepsTag(rctx.Rule) + sweep.AddPostGazelleBuildozerCommand("remove tags "+sweep.SweepTransitiveDepsTag, rctx.From.String()) } } } diff --git a/language/scala/repair.go b/language/scala/repair.go index 9262f491..fad4328e 100644 --- a/language/scala/repair.go +++ b/language/scala/repair.go @@ -26,7 +26,8 @@ const ( ) var ( - CI = procutil.EnvVar("CI") + CI = procutil.EnvVar("CI") + SCALA_GAZELLE_BUILDOZER_FILE = procutil.EnvVar("SCALA_GAZELLE_BUILDOZER_FILE") ) // String partially implements the flag.Value interface. @@ -69,11 +70,24 @@ func (i *repairMode) Set(value string) error { } func (sl *scalaLang) repair() { + if err := sl.maybeWritePostGazelleBuildozerFile(); err != nil { + log.Printf("post-gazelle buildozer file write error: %v", err) + } + if err := sl.repairDeps(sl.repairMode); err != nil { log.Printf("warning: repair failed: %v", err) } } +func (sl *scalaLang) maybeWritePostGazelleBuildozerFile() error { + if filename, ok := procutil.LookupEnv(SCALA_GAZELLE_BUILDOZER_FILE); ok { + if err := sweep.WritePostGazelleBuildozerFile(filename); err != nil { + return err + } + } + return nil +} + func (sl *scalaLang) repairDeps(mode repairMode) error { if err := maybeGitCommitAndPushBuildFiles(); err != nil { return err diff --git a/pkg/scalaconfig/config.go b/pkg/scalaconfig/config.go index ddcdd104..d14add12 100644 --- a/pkg/scalaconfig/config.go +++ b/pkg/scalaconfig/config.go @@ -761,6 +761,8 @@ func (c *Config) mergeDeps(deps map[label.Label]bool, importLabels map[label.Lab src = new(build.ListExpr) } + var unknown []string + var dst = new(build.ListExpr) for _, expr := range src.List { // try and parse the expression as a label @@ -799,9 +801,10 @@ func (c *Config) mergeDeps(deps map[label.Label]bool, importLabels map[label.Lab // marked as TRANSITIVE. if c.ShouldSweepTransitive(r, attrName) { // set as TRANSITIVE comment for sweeping - if _, ok := expr.(*build.StringExpr); ok { - expr.Comment().Suffix = []build.Comment{sweep.MakeTransitiveComment()} - } + // if _, ok := expr.(*build.StringExpr); ok { + // expr.Comment().Suffix = []build.Comment{sweep.MakeTransitiveComment()} + // } + unknown = append(unknown, dep.String()) dst.List = append(dst.List, expr) } else { if sweep.IsTransitiveDep(expr) { @@ -816,6 +819,9 @@ func (c *Config) mergeDeps(deps map[label.Label]bool, importLabels map[label.Lab continue } + // set a private attr so that they can be potentially removed later + r.SetPrivateAttr("_unknown_"+attrName, unknown) + // do we have a known provider for the dependency? If not, this // dependency is not "managed", so delete it. if !c.shouldKeep(expr, imp, from) { diff --git a/pkg/sweep/sweep.go b/pkg/sweep/sweep.go index c61cc5e7..2f9e6ffa 100644 --- a/pkg/sweep/sweep.go +++ b/pkg/sweep/sweep.go @@ -107,9 +107,11 @@ func TransitiveAttr(attrName string, file *rule.File, r *rule.Rule, from label.L if _, exitCode, _ := bazel.ExecCommand("bazel", "build", from.String()); exitCode == 0 { log.Println("- 💩 junk:", dep.Value) junk = append(junk, dep.Value) + AddPostGazelleBuildozerCommand("remove deps "+dep.Value, from.String()) } else { log.Println("- 👑 keep:", dep.Value) deps.List = original + AddPostGazelleBuildozerCommand("commaent deps "+dep.Value+" TRANSITIVE", from.String()) } } @@ -121,6 +123,46 @@ func TransitiveAttr(attrName string, file *rule.File, r *rule.Rule, from label.L return } +// UnknownAttr iterates through deps in the private attr given and removes them +// if the target still builds without it. +func UnknownAttr(attrName string, file *rule.File, r *rule.Rule, from label.Label) (junk []string, err error) { + unknown, ok := r.PrivateAttr("_unknown_" + attrName).([]string) + if !ok || len(unknown) == 0 { + return nil, nil + } + + // target should build first time, otherwise we can't check accurately. + log.Println("🧱 transitive sweep:", from, len(unknown)) + + if out, exitCode, _ := bazel.ExecCommand("bazel", "build", from.String()); exitCode != 0 { + log.Fatalf("sweep failed (must build cleanly on first attempt): %s", string(out)) + } + + // iterate the deps in the unknown list. Foreach one, remove it and then + // see if the target still builds. If so, put it in the + for _, dep := range unknown { + // remove this dep + if err := runBuildozerCommands(fmt.Sprintf("remove deps %s|%v", dep, from)); err != nil { + return nil, err + } + // see if it still builds + if _, exitCode, _ := bazel.ExecCommand("bazel", "build", from.String()); exitCode == 0 { + log.Println("- 💩 junk:", dep) + junk = append(junk, dep) + AddPostGazelleBuildozerCommand("remove deps "+dep, from.String()) + } else { + log.Println("- 👑 keep:", dep) + AddPostGazelleBuildozerCommand("comment deps "+dep+" TRANSITIVE", from.String()) + } + // restore it + if err := runBuildozerCommands(fmt.Sprintf("add deps %s|%v", dep, from)); err != nil { + return nil, err + } + } + + return +} + func RemoveSweepDirective(file *rule.File) error { if file == nil { return nil diff --git a/pkg/sweep/transitive.go b/pkg/sweep/transitive.go index b81318b6..c9989e1c 100644 --- a/pkg/sweep/transitive.go +++ b/pkg/sweep/transitive.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "log" + "os" "os/exec" "sort" "strings" @@ -14,13 +15,28 @@ import ( ) const ( - REPAIRED = "REPAIRED" + REPAIRED = "REPAIRED" + SweepTransitiveDepsTag = "sweep-transitive-deps" ) var ( - forRepair = make(map[string]*repairSpec) + forRepair = make(map[string]*repairSpec) + postGazelleBuildozerCommands = []string{} ) +func AddPostGazelleBuildozerCommand(action, target string) { + command := fmt.Sprintf("%s|%s", action, target) + postGazelleBuildozerCommands = append(postGazelleBuildozerCommands, command) +} + +func WritePostGazelleBuildozerFile(filename string) error { + if len(postGazelleBuildozerCommands) == 0 { + return nil + } + content := strings.Join(postGazelleBuildozerCommands, "\n") + return os.WriteFile(filename, []byte(content), os.ModePerm) +} + type repairSpec struct { // rule is the rule object to be repaired rule *rule.Rule @@ -162,7 +178,7 @@ func runBuildozerCommands(commands ...string) error { func HasSweepTransitiveDepsTag(r *rule.Rule) bool { for _, tag := range r.AttrStrings("tags") { - if tag == "sweep-transitive-deps" { + if tag == SweepTransitiveDepsTag { return true } } @@ -173,7 +189,7 @@ func RemoveSweepTransitiveDepsTag(r *rule.Rule) { tags := make([]string, 0) for _, tag := range r.AttrStrings("tags") { - if tag == "sweep-transitive-deps" { + if tag == SweepTransitiveDepsTag { continue } tags = append(tags, tag)