1
1
package third_party .dependency_analyzer .src .main .io .bazel .rulesscala .dependencyanalyzer
2
2
3
3
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
6
8
7
9
class DependencyAnalyzer (val global : Global ) extends Plugin {
8
10
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
36
28
}
37
29
38
-
39
30
private object Component extends PluginComponent {
40
31
val global : DependencyAnalyzer .this .global.type =
41
32
DependencyAnalyzer .this .global
42
33
43
- import global ._
44
-
45
34
override val runsAfter = List (" jvm" )
46
35
47
36
val phaseName = DependencyAnalyzer .this .name
@@ -52,59 +41,77 @@ class DependencyAnalyzer(val global: Global) extends Plugin {
52
41
super .run()
53
42
54
43
val usedJars = findUsedJars
44
+ val usedJarPaths = if (! isWindows) usedJars.map(_.path) else usedJars.map(_.path.replaceAll(" \\\\ " , " /" ))
55
45
56
- warnOnIndirectTargetsFoundIn(usedJars)
57
- }
46
+ if (settings.unusedDepsMode != AnalyzerMode .Off ) {
47
+ reportUnusedDepsFoundIn(usedJarPaths)
48
+ }
58
49
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)
72
52
}
73
53
}
74
54
75
- override def apply (unit : CompilationUnit ): Unit = ()
55
+ override def apply (unit : global. CompilationUnit ): Unit = ()
76
56
}
77
57
78
58
}
79
59
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
101
69
}
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
102
93
}
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 => _ => ()
103
106
}
104
107
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
107
115
}
108
- jars.toSet
109
116
}
110
117
}
0 commit comments