Skip to content

Commit 7657d2f

Browse files
authored
Add AST walking mode for dependency analyzer (#967)
* plugin * lint
1 parent a104708 commit 7657d2f

File tree

11 files changed

+639
-101
lines changed

11 files changed

+639
-101
lines changed

third_party/dependency_analyzer/src/main/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ load("//scala:scala.bzl", "scala_library_for_plugin_bootstrapping")
55
scala_library_for_plugin_bootstrapping(
66
name = "dependency_analyzer",
77
srcs = [
8+
"io/bazel/rulesscala/dependencyanalyzer/AstUsedJarFinder.scala",
89
"io/bazel/rulesscala/dependencyanalyzer/DependencyAnalyzer.scala",
910
"io/bazel/rulesscala/dependencyanalyzer/DependencyAnalyzerSettings.scala",
1011
"io/bazel/rulesscala/dependencyanalyzer/HighLevelCrawlUsedJarFinder.scala",
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package third_party.dependency_analyzer.src.main.io.bazel.rulesscala.dependencyanalyzer
2+
3+
import scala.reflect.io.AbstractFile
4+
import scala.tools.nsc.Global
5+
6+
class AstUsedJarFinder(
7+
global: Global
8+
) {
9+
import global._
10+
11+
def findUsedJars: Set[AbstractFile] = {
12+
val jars = collection.mutable.Set[AbstractFile]()
13+
14+
def handleType(tpe: Type): Unit = {
15+
val sym = tpe.typeSymbol
16+
val assocFile = sym.associatedFile
17+
if (assocFile.path.endsWith(".class"))
18+
assocFile.underlyingSource.foreach { source =>
19+
jars.add(source)
20+
}
21+
}
22+
23+
def exploreType(tpe: Type): Unit = {
24+
handleType(tpe)
25+
tpe.typeArgs.foreach(exploreType)
26+
}
27+
28+
def fullyExploreTree(tree: Tree): Unit = {
29+
exploreTree(tree)
30+
tree.foreach(exploreTree)
31+
}
32+
33+
def exploreTree(tree: Tree): Unit = {
34+
tree match {
35+
case node: TypeTree =>
36+
if (node.original != null) {
37+
node.original.foreach(fullyExploreTree)
38+
}
39+
case node: Literal =>
40+
node.value.value match {
41+
case tpe: Type =>
42+
exploreType(tpe)
43+
case _ =>
44+
}
45+
case _ =>
46+
}
47+
48+
if (tree.hasSymbolField) {
49+
tree.symbol.annotations.foreach { annot =>
50+
annot.tree.foreach(fullyExploreTree)
51+
}
52+
}
53+
if (tree.tpe != null) {
54+
exploreType(tree.tpe)
55+
}
56+
}
57+
58+
currentRun.units.foreach { unit =>
59+
unit.body.foreach(fullyExploreTree)
60+
}
61+
jars.toSet
62+
}
63+
}

third_party/dependency_analyzer/src/main/io/bazel/rulesscala/dependencyanalyzer/DependencyAnalyzer.scala

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,17 @@ class DependencyAnalyzer(val global: Global) extends Plugin {
1414
"compilation for issues including not directly including " +
1515
"dependencies which are directly included in the code, or " +
1616
"including unused dependencies."
17-
override val components = List[PluginComponent](Component)
17+
override val components =
18+
List[PluginComponent](
19+
new AnalyzerComponent(
20+
runsAfterPhase = "typer",
21+
handles = DependencyTrackingMethod.Ast
22+
),
23+
new AnalyzerComponent(
24+
runsAfterPhase = "jvm",
25+
handles = DependencyTrackingMethod.HighLevel
26+
)
27+
)
1828

1929
private val isWindows: Boolean = System.getProperty("os.name").toLowerCase.contains("windows")
2030
private var settings: DependencyAnalyzerSettings = null
@@ -27,34 +37,44 @@ class DependencyAnalyzer(val global: Global) extends Plugin {
2737
true
2838
}
2939

30-
private object Component extends PluginComponent {
31-
val global: DependencyAnalyzer.this.global.type =
40+
private class AnalyzerComponent(
41+
// Typer seems to be the better method at least for AST - it seems like
42+
// some things get eliminated in later phases. However, due to backwards
43+
// compatibility we have to preserve using jvm for the high-level-crawl
44+
// dependency tracking method
45+
runsAfterPhase: String,
46+
handles: DependencyTrackingMethod
47+
) extends PluginComponent {
48+
override val global: DependencyAnalyzer.this.global.type =
3249
DependencyAnalyzer.this.global
3350

34-
override val runsAfter = List("jvm")
51+
override val runsAfter = List(runsAfterPhase)
3552

36-
val phaseName = DependencyAnalyzer.this.name
53+
val phaseName = s"${DependencyAnalyzer.this.name}-post-$runsAfterPhase"
3754

3855
override def newPhase(prev: Phase): StdPhase = new StdPhase(prev) {
3956
override def run(): Unit = {
40-
4157
super.run()
42-
43-
val usedJars = findUsedJars
44-
val usedJarPaths = if (!isWindows) usedJars.map(_.path) else usedJars.map(_.path.replaceAll("\\\\", "/"))
45-
46-
if (settings.unusedDepsMode != AnalyzerMode.Off) {
47-
reportUnusedDepsFoundIn(usedJarPaths)
48-
}
49-
50-
if (settings.strictDepsMode != AnalyzerMode.Off) {
51-
reportIndirectTargetsFoundIn(usedJarPaths)
58+
if (settings.dependencyTrackingMethod == handles) {
59+
runAnalysis()
5260
}
5361
}
5462

5563
override def apply(unit: global.CompilationUnit): Unit = ()
5664
}
65+
}
66+
67+
private def runAnalysis(): Unit = {
68+
val usedJars = findUsedJars
69+
val usedJarPaths = if (!isWindows) usedJars.map(_.path) else usedJars.map(_.path.replaceAll("\\\\", "/"))
5770

71+
if (settings.unusedDepsMode != AnalyzerMode.Off) {
72+
reportUnusedDepsFoundIn(usedJarPaths)
73+
}
74+
75+
if (settings.strictDepsMode != AnalyzerMode.Off) {
76+
reportIndirectTargetsFoundIn(usedJarPaths)
77+
}
5878
}
5979

6080
private def reportIndirectTargetsFoundIn(usedJarPaths: Set[String]): Unit = {
@@ -112,6 +132,8 @@ class DependencyAnalyzer(val global: Global) extends Plugin {
112132
settings.dependencyTrackingMethod match {
113133
case DependencyTrackingMethod.HighLevel =>
114134
new HighLevelCrawlUsedJarFinder(global).findUsedJars
135+
case DependencyTrackingMethod.Ast =>
136+
new AstUsedJarFinder(global).findUsedJars
115137
}
116138
}
117139
}

third_party/dependency_analyzer/src/main/io/bazel/rulesscala/dependencyanalyzer/DependencyAnalyzerSettings.scala

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,19 @@ object AnalyzerMode {
1818
sealed trait AnalyzerMode
1919

2020
object DependencyTrackingMethod {
21-
case object HighLevel extends DependencyTrackingMethod
21+
case object HighLevel extends DependencyTrackingMethod("high-level")
22+
23+
/**
24+
* Discovers dependencies by crawling the AST.
25+
*/
26+
case object Ast extends DependencyTrackingMethod("ast")
2227

2328
def parse(mode: String): Option[DependencyTrackingMethod] = {
24-
mode match {
25-
case "high-level" => Some(HighLevel)
26-
case _ => None
27-
}
29+
Seq(HighLevel, Ast).find(_.name == mode)
2830
}
2931
}
3032

31-
sealed trait DependencyTrackingMethod
33+
sealed abstract class DependencyTrackingMethod(val name: String)
3234

3335
class TargetSet(
3436
prefix: String,

third_party/dependency_analyzer/src/test/BUILD

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,36 @@ licenses(["notice"]) # 3-clause BSD
22

33
load("//scala:scala.bzl", "scala_junit_test", "scala_test")
44

5+
common_jvm_flags = [
6+
"-Dplugin.jar.location=$(location //third_party/dependency_analyzer/src/main:dependency_analyzer)",
7+
"-Dscala.library.location=$(location //external:io_bazel_rules_scala/dependency/scala/scala_library)",
8+
]
9+
10+
scala_test(
11+
name = "ast_used_jar_finder_test",
12+
size = "small",
13+
srcs = [
14+
"io/bazel/rulesscala/dependencyanalyzer/AstUsedJarFinderTest.scala",
15+
],
16+
jvm_flags = common_jvm_flags,
17+
unused_dependency_checker_mode = "off",
18+
deps = [
19+
"//external:io_bazel_rules_scala/dependency/scala/scala_compiler",
20+
"//external:io_bazel_rules_scala/dependency/scala/scala_library",
21+
"//external:io_bazel_rules_scala/dependency/scala/scala_reflect",
22+
"//third_party/dependency_analyzer/src/main:dependency_analyzer",
23+
"//third_party/utils/src/test:test_util",
24+
"@scalac_rules_commons_io//jar",
25+
],
26+
)
27+
528
scala_test(
629
name = "strict_deps_test",
730
size = "small",
831
srcs = [
932
"io/bazel/rulesscala/dependencyanalyzer/StrictDepsTest.scala",
1033
],
11-
jvm_flags = [
12-
"-Dplugin.jar.location=$(location //third_party/dependency_analyzer/src/main:dependency_analyzer)",
13-
"-Dscala.library.location=$(location //external:io_bazel_rules_scala/dependency/scala/scala_library)",
34+
jvm_flags = common_jvm_flags + [
1435
"-Dguava.jar.location=$(location @com_google_guava_guava_21_0_with_file//jar)",
1536
"-Dapache.commons.jar.location=$(location @org_apache_commons_commons_lang_3_5_without_file//:linkable_org_apache_commons_commons_lang_3_5_without_file)",
1637
],
@@ -32,9 +53,7 @@ scala_test(
3253
srcs = [
3354
"io/bazel/rulesscala/dependencyanalyzer/UnusedDependencyCheckerTest.scala",
3455
],
35-
jvm_flags = [
36-
"-Dplugin.jar.location=$(location //third_party/dependency_analyzer/src/main:dependency_analyzer)",
37-
"-Dscala.library.location=$(location //external:io_bazel_rules_scala/dependency/scala/scala_library)",
56+
jvm_flags = common_jvm_flags + [
3857
"-Dapache.commons.jar.location=$(location @org_apache_commons_commons_lang_3_5_without_file//:linkable_org_apache_commons_commons_lang_3_5_without_file)",
3958
],
4059
unused_dependency_checker_mode = "off",

0 commit comments

Comments
 (0)