Skip to content

Commit 4fefaaa

Browse files
Jamie5ittaiz
authored andcommitted
Merge dependency_analyzer and unused_dependency_checker (#954)
* Merge dependency_analyzer and unused_dependency_checker We merge dependency_analyer and unused_dependency_checker projects because of the code they have in common, and because we will be adding additional forms of checking which would be complex with two different projects going on. * split
1 parent 2a76ab3 commit 4fefaaa

File tree

17 files changed

+348
-336
lines changed

17 files changed

+348
-336
lines changed

scala/private/common_attributes.bzl

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,6 @@ common_attrs.update({
6666
],
6767
mandatory = False,
6868
),
69-
"_unused_dependency_checker_plugin": attr.label(
70-
default = Label(
71-
"@io_bazel_rules_scala//third_party/unused_dependency_checker/src/main:unused_dependency_checker",
72-
),
73-
allow_files = [".jar"],
74-
mandatory = False,
75-
),
7669
"unused_dependency_checker_ignored_targets": attr.label_list(default = []),
7770
"_code_coverage_instrumentation_worker": attr.label(
7871
default = "@io_bazel_rules_scala//src/java/io/bazel/rulesscala/coverage/instrumenter",

scala/private/rule_impls.bzl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def compile_scala(
6666
input_plugins = plugins
6767
plugins = _collect_plugin_paths(plugins)
6868
internal_plugin_jars = []
69-
dependency_analyzer_mode = "off"
69+
strict_deps_mode = "off"
7070
compiler_classpath_jars = cjars
7171
optional_scalac_args = ""
7272
classpath_resources = []
@@ -75,7 +75,7 @@ def compile_scala(
7575

7676
if is_dependency_analyzer_on(ctx):
7777
# "off" mode is used as a feature toggle, that preserves original behaviour
78-
dependency_analyzer_mode = ctx.fragments.java.strict_java_deps
78+
strict_deps_mode = ctx.fragments.java.strict_java_deps
7979
dep_plugin = ctx.attr._dependency_analyzer_plugin
8080
plugins = depset(transitive = [plugins, dep_plugin.files])
8181
internal_plugin_jars = ctx.files._dependency_analyzer_plugin
@@ -102,9 +102,9 @@ CurrentTarget: {current_target}
102102
)
103103

104104
elif unused_dependency_checker_mode != "off":
105-
unused_dependency_plugin = ctx.attr._unused_dependency_checker_plugin
106-
plugins = depset(transitive = [plugins, unused_dependency_plugin.files])
107-
internal_plugin_jars = ctx.files._unused_dependency_checker_plugin
105+
dependency_analyzer_plugin = ctx.attr._dependency_analyzer_plugin
106+
plugins = depset(transitive = [plugins, dependency_analyzer_plugin.files])
107+
internal_plugin_jars = ctx.files._dependency_analyzer_plugin
108108

109109
cjars_list = cjars.to_list()
110110
direct_jars = _join_path(cjars_list)
@@ -152,7 +152,7 @@ ResourceSources: {resource_sources}
152152
ResourceJars: {resource_jars}
153153
ScalacOpts: {scala_opts}
154154
SourceJars: {srcjars}
155-
DependencyAnalyzerMode: {dependency_analyzer_mode}
155+
StrictDepsMode: {strict_deps_mode}
156156
UnusedDependencyCheckerMode: {unused_dependency_checker_mode}
157157
StatsfileOutput: {statsfile_output}
158158
""".format(
@@ -170,7 +170,7 @@ StatsfileOutput: {statsfile_output}
170170
resource_targets = ",".join([p[0] for p in resource_paths]),
171171
resource_sources = ",".join([p[1] for p in resource_paths]),
172172
resource_jars = _join_path(resource_jars),
173-
dependency_analyzer_mode = dependency_analyzer_mode,
173+
strict_deps_mode = strict_deps_mode,
174174
unused_dependency_checker_mode = unused_dependency_checker_mode,
175175
statsfile_output = statsfile.path,
176176
)

src/java/io/bazel/rulesscala/scalac/CompileOptions.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class CompileOptions {
2424
public final String[] ignoredTargets;
2525
public final String[] indirectJars;
2626
public final String[] indirectTargets;
27-
public final String dependencyAnalyzerMode;
27+
public final String strictDepsMode;
2828
public final String unusedDependencyCheckerMode;
2929
public final String currentTarget;
3030
public final String statsfile;
@@ -59,7 +59,7 @@ public CompileOptions(List<String> args) {
5959
indirectJars = getCommaList(argMap, "IndirectJars");
6060
indirectTargets = getCommaList(argMap, "IndirectTargets");
6161

62-
dependencyAnalyzerMode = getOrElse(argMap, "DependencyAnalyzerMode", "off");
62+
strictDepsMode = getOrElse(argMap, "StrictDepsMode", "off");
6363
unusedDependencyCheckerMode = getOrElse(argMap, "UnusedDependencyCheckerMode", "off");
6464
currentTarget = getOrElse(argMap, "CurrentTarget", "NA");
6565

src/java/io/bazel/rulesscala/scalac/ScalacProcessor.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -173,16 +173,17 @@ private static boolean isModeEnabled(String mode) {
173173
private static String[] getPluginParamsFrom(CompileOptions ops) {
174174
ArrayList<String> pluginParams = new ArrayList<>(0);
175175

176-
if (isModeEnabled(ops.dependencyAnalyzerMode)) {
176+
if (isModeEnabled(ops.strictDepsMode)) {
177177
String[] indirectTargets = encodeBazelTargets(ops.indirectTargets);
178178
String currentTarget = encodeBazelTarget(ops.currentTarget);
179179

180180
String[] dependencyAnalyzerParams = {
181181
"-P:dependency-analyzer:direct-jars:" + String.join(":", ops.directJars),
182182
"-P:dependency-analyzer:indirect-jars:" + String.join(":", ops.indirectJars),
183183
"-P:dependency-analyzer:indirect-targets:" + String.join(":", indirectTargets),
184-
"-P:dependency-analyzer:mode:" + ops.dependencyAnalyzerMode,
184+
"-P:dependency-analyzer:strict-deps-mode:" + ops.strictDepsMode,
185185
"-P:dependency-analyzer:current-target:" + currentTarget,
186+
"-P:dependency-analyzer:dependency-tracking-method:" + "high-level",
186187
};
187188
pluginParams.addAll(Arrays.asList(dependencyAnalyzerParams));
188189
} else if (isModeEnabled(ops.unusedDependencyCheckerMode)) {
@@ -191,11 +192,12 @@ private static String[] getPluginParamsFrom(CompileOptions ops) {
191192
String currentTarget = encodeBazelTarget(ops.currentTarget);
192193

193194
String[] unusedDependencyCheckerParams = {
194-
"-P:unused-dependency-checker:direct-jars:" + String.join(":", ops.directJars),
195-
"-P:unused-dependency-checker:direct-targets:" + String.join(":", directTargets),
196-
"-P:unused-dependency-checker:ignored-targets:" + String.join(":", ignoredTargets),
197-
"-P:unused-dependency-checker:mode:" + ops.unusedDependencyCheckerMode,
198-
"-P:unused-dependency-checker:current-target:" + currentTarget,
195+
"-P:dependency-analyzer:direct-jars:" + String.join(":", ops.directJars),
196+
"-P:dependency-analyzer:direct-targets:" + String.join(":", directTargets),
197+
"-P:dependency-analyzer:unused-deps-ignored-targets:" + String.join(":", ignoredTargets),
198+
"-P:dependency-analyzer:unused-deps-mode:" + ops.unusedDependencyCheckerMode,
199+
"-P:dependency-analyzer:current-target:" + currentTarget,
200+
"-P:dependency-analyzer:dependency-tracking-method:" + "high-level",
199201
};
200202
pluginParams.addAll(Arrays.asList(unusedDependencyCheckerParams));
201203
}

third_party/dependency_analyzer/src/main/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ scala_library_for_plugin_bootstrapping(
66
name = "dependency_analyzer",
77
srcs = [
88
"io/bazel/rulesscala/dependencyanalyzer/DependencyAnalyzer.scala",
9+
"io/bazel/rulesscala/dependencyanalyzer/DependencyAnalyzerSettings.scala",
10+
"io/bazel/rulesscala/dependencyanalyzer/HighLevelCrawlUsedJarFinder.scala",
11+
"io/bazel/rulesscala/dependencyanalyzer/OptionsParser.scala",
912
],
1013
resources = ["resources/scalac-plugin.xml"],
1114
visibility = ["//visibility:public"],
Lines changed: 79 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,36 @@
11
package third_party.dependency_analyzer.src.main.io.bazel.rulesscala.dependencyanalyzer
22

33
import scala.reflect.io.AbstractFile
4-
import scala.tools.nsc.plugins.{Plugin, PluginComponent}
5-
import scala.tools.nsc.{Global, Phase}
4+
import scala.tools.nsc.plugins.Plugin
5+
import scala.tools.nsc.plugins.PluginComponent
6+
import scala.tools.nsc.Global
7+
import scala.tools.nsc.Phase
68

79
class DependencyAnalyzer(val global: Global) extends Plugin {
810

9-
val name = "dependency-analyzer"
10-
val description =
11-
"Analyzes the used dependencies and fails the compilation " +
12-
"if they are not explicitly used as direct dependencies (only declared transitively)"
13-
val components = List[PluginComponent](Component)
14-
15-
var indirect: Map[String, String] = Map.empty
16-
var direct: Set[String] = Set.empty
17-
var analyzerMode: String = "error"
18-
var currentTarget: String = "NA"
19-
20-
override def processOptions(options: List[String], error: (String) => Unit): Unit = {
21-
var indirectJars: Seq[String] = Seq.empty
22-
var indirectTargets: Seq[String] = Seq.empty
23-
24-
for (option <- options) {
25-
option.split(":").toList match {
26-
case "direct-jars" :: data => direct = data.toSet
27-
case "indirect-jars" :: data => indirectJars = data;
28-
case "indirect-targets" :: data => indirectTargets = data.map(_.replace(";", ":"))
29-
case "current-target" :: target => currentTarget = target.map(_.replace(";", ":")).head
30-
case "mode" :: mode => analyzerMode = mode.head
31-
case unknown :: _ => error(s"unknown param $unknown")
32-
case Nil =>
33-
}
34-
}
35-
indirect = indirectJars.zip(indirectTargets).toMap
11+
override val name = "dependency-analyzer"
12+
override val description =
13+
"Analyzes the used dependencies. Can check and warn or fail the " +
14+
"compilation for issues including not directly including " +
15+
"dependencies which are directly included in the code, or " +
16+
"including unused dependencies."
17+
override val components = List[PluginComponent](Component)
18+
19+
private val isWindows: Boolean = System.getProperty("os.name").toLowerCase.contains("windows")
20+
private var settings: DependencyAnalyzerSettings = null
21+
22+
override def init(
23+
options: List[String],
24+
error: String => Unit
25+
): Boolean = {
26+
settings = DependencyAnalyzerSettings.parseSettings(options = options, error = error)
27+
true
3628
}
3729

38-
3930
private object Component extends PluginComponent {
4031
val global: DependencyAnalyzer.this.global.type =
4132
DependencyAnalyzer.this.global
4233

43-
import global._
44-
4534
override val runsAfter = List("jvm")
4635

4736
val phaseName = DependencyAnalyzer.this.name
@@ -52,59 +41,77 @@ class DependencyAnalyzer(val global: Global) extends Plugin {
5241
super.run()
5342

5443
val usedJars = findUsedJars
44+
val usedJarPaths = if (!isWindows) usedJars.map(_.path) else usedJars.map(_.path.replaceAll("\\\\", "/"))
5545

56-
warnOnIndirectTargetsFoundIn(usedJars)
57-
}
46+
if (settings.unusedDepsMode != AnalyzerMode.Off) {
47+
reportUnusedDepsFoundIn(usedJarPaths)
48+
}
5849

59-
private def warnOnIndirectTargetsFoundIn(usedJars: Set[AbstractFile]) = {
60-
for (usedJar <- usedJars;
61-
usedJarPath = usedJar.path;
62-
target <- indirect.get(usedJarPath) if !direct.contains(usedJarPath)) {
63-
val errorMessage =
64-
s"""Target '$target' is used but isn't explicitly declared, please add it to the deps.
65-
|You can use the following buildozer command:
66-
|buildozer 'add deps $target' $currentTarget""".stripMargin
67-
68-
analyzerMode match {
69-
case "error" => reporter.error(NoPosition, errorMessage)
70-
case "warn" => reporter.warning(NoPosition, errorMessage)
71-
}
50+
if (settings.strictDepsMode != AnalyzerMode.Off) {
51+
reportIndirectTargetsFoundIn(usedJarPaths)
7252
}
7353
}
7454

75-
override def apply(unit: CompilationUnit): Unit = ()
55+
override def apply(unit: global.CompilationUnit): Unit = ()
7656
}
7757

7858
}
7959

80-
import global._
81-
82-
private def findUsedJars: Set[AbstractFile] = {
83-
val jars = collection.mutable.Set[AbstractFile]()
84-
85-
def walkTopLevels(root: Symbol): Unit = {
86-
def safeInfo(sym: Symbol): Type =
87-
if (sym.hasRawInfo && sym.rawInfo.isComplete) sym.info else NoType
88-
89-
def packageClassOrSelf(sym: Symbol): Symbol =
90-
if (sym.hasPackageFlag && !sym.isModuleClass) sym.moduleClass else sym
91-
92-
for (x <- safeInfo(packageClassOrSelf(root)).decls) {
93-
if (x == root) ()
94-
else if (x.hasPackageFlag) walkTopLevels(x)
95-
else if (x.owner != root) { // exclude package class members
96-
if (x.hasRawInfo && x.rawInfo.isComplete) {
97-
val assocFile = x.associatedFile
98-
if (assocFile.path.endsWith(".class") && assocFile.underlyingSource.isDefined)
99-
assocFile.underlyingSource.foreach(jars += _)
100-
}
60+
private def reportIndirectTargetsFoundIn(usedJarPaths: Set[String]): Unit = {
61+
val errors =
62+
usedJarPaths
63+
.filterNot(settings.directTargetSet.jarSet.contains)
64+
.flatMap(settings.indirectTargetSet.targetFromJarOpt)
65+
.map { target =>
66+
s"""Target '$target' is used but isn't explicitly declared, please add it to the deps.
67+
|You can use the following buildozer command:
68+
|buildozer 'add deps $target' ${settings.currentTarget}""".stripMargin
10169
}
70+
71+
warnOrError(settings.strictDepsMode, errors)
72+
}
73+
74+
private def reportUnusedDepsFoundIn(usedJarPaths: Set[String]): Unit = {
75+
val directJarPaths = settings.directTargetSet.jarSet
76+
77+
val usedTargets =
78+
usedJarPaths.flatMap(settings.directTargetSet.targetFromJarOpt)
79+
80+
val unusedTargets = directJarPaths
81+
// This .get is safe because [jar] was gotten from [directJarPaths]
82+
// which is the set of keys of the direct targets.
83+
.filter(jar => !usedTargets.contains(settings.directTargetSet.targetFromJarOpt(jar).get))
84+
.flatMap(settings.directTargetSet.targetFromJarOpt)
85+
.diff(settings.ignoredUnusedDependencyTargets)
86+
87+
val toWarnOrError =
88+
unusedTargets.map { target =>
89+
s"""Target '$target' is specified as a dependency to ${settings.currentTarget} but isn't used, please remove it from the deps.
90+
|You can use the following buildozer command:
91+
|buildozer 'remove deps $target' ${settings.currentTarget}
92+
|""".stripMargin
10293
}
94+
95+
warnOrError(settings.unusedDepsMode, toWarnOrError)
96+
}
97+
98+
private def warnOrError(
99+
analyzerMode: AnalyzerMode,
100+
errors: Set[String]
101+
): Unit = {
102+
val reportFunction: String => Unit = analyzerMode match {
103+
case AnalyzerMode.Error => global.reporter.error(global.NoPosition, _)
104+
case AnalyzerMode.Warn => global.reporter.warning(global.NoPosition, _)
105+
case AnalyzerMode.Off => _ => ()
103106
}
104107

105-
exitingTyper {
106-
walkTopLevels(RootClass)
108+
errors.foreach(reportFunction)
109+
}
110+
111+
private def findUsedJars: Set[AbstractFile] = {
112+
settings.dependencyTrackingMethod match {
113+
case DependencyTrackingMethod.HighLevel =>
114+
new HighLevelCrawlUsedJarFinder(global).findUsedJars
107115
}
108-
jars.toSet
109116
}
110117
}

0 commit comments

Comments
 (0)