Skip to content

Commit ce417fc

Browse files
authored
Add gazelle:scala_sweep_transitive_deps (#139)
* Add gazelle:scala_sweep_transitive_deps * Add gazelle:scala_keep_unknown_deps * Improve log output * github actions: upgrade to cache v4 * upgrade to bazelbuild/setup-bazelisk@v3
1 parent e301ecb commit ce417fc

File tree

11 files changed

+222
-21
lines changed

11 files changed

+222
-21
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
steps:
2121
- uses: actions/checkout@v3
2222

23-
- uses: bazelbuild/setup-bazelisk@v2
23+
- uses: bazelbuild/setup-bazelisk@v3
2424

2525
- name: Mount bazel action cache
2626
uses: actions/cache@v4

.github/workflows/format.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
steps:
2121
- uses: actions/checkout@v3
2222

23-
- uses: bazelbuild/setup-bazelisk@v2
23+
- uses: bazelbuild/setup-bazelisk@v3
2424

2525
- name: Mount bazel action cache
2626
uses: actions/cache@v2

language/scala/existing_scala_rule.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import (
66
"strings"
77

88
"github.com/bazelbuild/bazel-gazelle/config"
9+
"github.com/bazelbuild/bazel-gazelle/label"
910
"github.com/bazelbuild/bazel-gazelle/resolve"
1011
"github.com/bazelbuild/bazel-gazelle/rule"
1112
"github.com/bazelbuild/buildtools/build"
1213

14+
"github.com/stackb/scala-gazelle/pkg/bazel"
15+
"github.com/stackb/scala-gazelle/pkg/collections"
1316
"github.com/stackb/scala-gazelle/pkg/protobuf"
1417
"github.com/stackb/scala-gazelle/pkg/scalaconfig"
1518
"github.com/stackb/scala-gazelle/pkg/scalarule"
@@ -153,6 +156,72 @@ func (s *existingScalaRule) Resolve(rctx *scalarule.ResolveContext, importsRaw i
153156
sc.Exports(exports, rctx.Rule, "exports", rctx.From)
154157
}
155158

159+
if sc.ShouldSweepTransitiveDeps() {
160+
if err := s.sweepTransitive("deps", rctx.Rule, rctx.From); err != nil {
161+
log.Panicf("transitive sweep failed: %v", err)
162+
}
163+
}
164+
165+
}
166+
167+
// sweepTransitive iterates through deps marked "TRANSITIVE" and removes them if
168+
// the target still builds without it.
169+
func (s *existingScalaRule) sweepTransitive(attrName string, r *rule.Rule, from label.Label) error {
170+
expr := r.Attr(attrName)
171+
if expr == nil {
172+
return nil
173+
}
174+
175+
deps, isList := expr.(*build.ListExpr)
176+
if !isList {
177+
return nil // some other condition we can't deal with
178+
}
179+
180+
file := s.pkg.GenerateArgs().File
181+
182+
// target should build first time, otherwise we can't check accurately.
183+
log.Println("🧱 transitive sweep:", from)
184+
185+
if out, exitCode, _ := bazel.ExecCommand("bazel", "build", from.String()); exitCode != 0 {
186+
log.Fatalln("sweep failed (must build cleanly on first attempt): %s", string(out))
187+
}
188+
189+
for i := len(deps.List) - 1; i >= 0; i-- {
190+
expr := deps.List[i]
191+
switch t := expr.(type) {
192+
case *build.StringExpr:
193+
if len(t.Comments.Suffix) != 1 {
194+
continue
195+
}
196+
if t.Comments.Suffix[0].Token != "# TRANSITIVE" {
197+
continue
198+
}
199+
200+
dep, err := label.Parse(t.Value)
201+
if err != nil {
202+
return err
203+
}
204+
deps.List = collections.SliceRemoveIndex(deps.List, i)
205+
206+
if err := file.Save(file.Path); err != nil {
207+
return err
208+
}
209+
210+
if _, exitCode, _ := bazel.ExecCommand("bazel", "build", from.String()); exitCode == 0 {
211+
log.Println("- 💩 junk:", dep)
212+
} else {
213+
log.Println("- 👑 keep:", dep)
214+
deps.List = collections.SliceInsertAt(deps.List, i, expr)
215+
}
216+
}
217+
218+
}
219+
220+
if err := file.Save(file.Path); err != nil {
221+
return err
222+
}
223+
224+
return nil
156225
}
157226

158227
func makeRuleComments(pb *sppb.Rule) (comments []build.Comment) {

language/scala/language_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ func ExampleLanguage_KnownDirectives() {
2323
// scala_debug
2424
// scala_log_level
2525
// scala_deps_cleaner
26+
// scala_keep_unknown_deps
27+
// scala_sweep_transitive_deps
2628
// scala_fix_wildcard_imports
2729
// scala_generate_build_files
2830
// scala_rule

language/scala/package_marker_rule.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import (
55
"github.com/pcj/mobyprogress"
66
)
77

8-
const packageMarkerRuleKind = "_package_marker"
8+
const packageMarkerRuleKind = "filegroup"
9+
const packageMarkerRuleName = "package_marker"
910

1011
// generatePackageMarkerRule creates a dummy rule that forces gazelle to run the
1112
// resolve phase at least once per package; used for tracking progress during
1213
// the resolve phase.
1314
func generatePackageMarkerRule(pkgNum int, pkg *scalaPackage) *rule.Rule {
14-
r := rule.NewRule(packageMarkerRuleKind, packageMarkerRuleKind)
15+
r := rule.NewRule(packageMarkerRuleKind, packageMarkerRuleName)
16+
r.SetAttr("srcs", []string{})
1517
r.SetPrivateAttr("n", pkgNum)
1618
r.SetPrivateAttr("pkg", pkg)
1719
return r

language/scala/testdata/maven_provider/BUILD.in

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ load("@io_bazel_rules_scala//scala:scala.bzl", "scala_library")
33
# gazelle:scala_rule scala_library implementation @io_bazel_rules_scala//scala:scala.bzl%scala_library
44
# gazelle:scala_debug imports dep_label_origin
55
# gazelle:resolve_with scala javax.xml._ javax.inject
6+
# gazelle:scala_keep_unknown_deps true
67

78
scala_library(
89
name = "app",
910
srcs = ["Main.scala"],
1011
deps = [
1112
# junit is required for compilation but we haven't specified
1213
# that @atlassian-public deps should be managed by the scala-gazelle
13-
# extension (so this is left alone despite no # keep directive)
14+
# extension (this is left alone despite no # keep directive because of
15+
# scala_keep_unknown_deps)
1416
"@atlassian-public//:junit_junit",
1517
],
1618
)

language/scala/testdata/maven_provider/BUILD.out

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ load("@io_bazel_rules_scala//scala:scala.bzl", "scala_library")
33
# gazelle:scala_rule scala_library implementation @io_bazel_rules_scala//scala:scala.bzl%scala_library
44
# gazelle:scala_debug imports dep_label_origin
55
# gazelle:resolve_with scala javax.xml._ javax.inject
6+
# gazelle:scala_keep_unknown_deps true
67

78
scala_library(
89
name = "app",
@@ -13,7 +14,8 @@ scala_library(
1314
deps = [
1415
# junit is required for compilation but we haven't specified
1516
# that @atlassian-public deps should be managed by the scala-gazelle
16-
# extension (so this is left alone despite no # keep directive)
17+
# extension (this is left alone despite no # keep directive because of
18+
# scala_keep_unknown_deps)
1719
"@atlassian-public//:junit_junit",
1820
"@maven//:javax_inject_javax_inject", # IMPLICIT (maven javax.inject <filename unknown>)
1921
"@maven//:xml_apis_xml_apis", # DIRECT (maven javax.xml Main.scala)

pkg/bazel/bazel.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package bazel
22

33
import (
4-
"log"
54
"os"
65
"os/exec"
76
"path"
@@ -33,13 +32,13 @@ func NewTmpDir(prefix string) (string, error) {
3332
return os.MkdirTemp("", prefix)
3433
}
3534

36-
func ExecCommand(bazelExe, command, label string) ([]byte, int, error) {
37-
args := []string{command, label}
35+
func ExecCommand(bazelExe, command string, labels ...string) ([]byte, int, error) {
36+
args := append([]string{command}, labels...)
3837

3938
cmd := exec.Command(bazelExe, args...)
4039
cmd.Dir = GetBuildWorkspaceDirectory()
4140

42-
log.Println("🧱", cmd.String())
41+
// log.Println("🧱", cmd.String())
4342
output, err := cmd.CombinedOutput()
4443
exitCode := procutil.CmdExitCode(cmd, err)
4544

pkg/collections/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ go_library(
1111
"net.go",
1212
"path_trie.go",
1313
"sha256.go",
14+
"slice.go",
1415
"string_slice.go",
1516
"string_stack.go",
1617
"uint32stack.go",
@@ -31,6 +32,7 @@ package_filegroup(
3132
"net.go",
3233
"path_trie.go",
3334
"sha256.go",
35+
"slice.go",
3436
"string_slice.go",
3537
"string_stack.go",
3638
"uint32stack.go",

pkg/collections/slice.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package collections
2+
3+
func SliceRemoveIndex[T any](slice []T, i int) []T {
4+
// Create a new slice with capacity one less than original
5+
result := make([]T, 0, len(slice)-1)
6+
7+
// Add all elements except the one at index i
8+
result = append(result, slice[:i]...)
9+
result = append(result, slice[i+1:]...)
10+
11+
return result
12+
}
13+
14+
func SliceInsertAt[T any](slice []T, i int, value T) []T {
15+
// Create a new slice with capacity one more than original
16+
result := make([]T, 0, len(slice)+1)
17+
18+
// Add elements before index i
19+
result = append(result, slice[:i]...)
20+
21+
// Add the new value
22+
result = append(result, value)
23+
24+
// Add elements after index i
25+
result = append(result, slice[i:]...)
26+
27+
return result
28+
}

0 commit comments

Comments
 (0)