1
1
package scalafix .internal .sbt
2
2
3
+ import java .io .PrintStream
3
4
import java .net .URLClassLoader
5
+ import java .nio .file .Path
6
+ import java .{util => jutil }
4
7
5
8
import com .geirsson .coursiersmall .Repository
6
9
import sbt ._
7
10
import sbt .internal .sbtscalafix .Compat
8
- import scalafix .interfaces .{ScalafixArguments , Scalafix => ScalafixAPI }
11
+ import scalafix .interfaces .{Scalafix => ScalafixAPI , _ }
12
+ import scalafix .sbt .InvalidArgument
13
+
14
+ import scala .collection .JavaConverters ._
15
+ import scala .util .control .NonFatal
16
+
17
+ sealed trait Arg extends (ScalafixArguments => ScalafixArguments )
18
+
19
+ object Arg {
20
+
21
+ sealed trait CacheKey
22
+
23
+ case class ToolClasspath (classLoader : URLClassLoader )
24
+ extends Arg
25
+ with CacheKey {
26
+ override def apply (sa : ScalafixArguments ): ScalafixArguments =
27
+ // this effectively overrides any previous URLClassLoader
28
+ sa.withToolClasspath(classLoader)
29
+ }
30
+
31
+ case class Rules (rules : Seq [String ]) extends Arg with CacheKey {
32
+ override def apply (sa : ScalafixArguments ): ScalafixArguments =
33
+ sa.withRules(rules.asJava)
34
+ }
35
+
36
+ case class Paths (paths : Seq [Path ]) extends Arg { // this one is extracted/stamped directly
37
+ override def apply (sa : ScalafixArguments ): ScalafixArguments =
38
+ sa.withPaths(paths.asJava)
39
+ }
40
+
41
+ case class Config (file : Option [Path ]) extends Arg with CacheKey {
42
+ override def apply (sa : ScalafixArguments ): ScalafixArguments =
43
+ sa.withConfig(jutil.Optional .ofNullable(file.orNull))
44
+ }
45
+
46
+ case class ParsedArgs (args : Seq [String ]) extends Arg with CacheKey {
47
+ override def apply (sa : ScalafixArguments ): ScalafixArguments =
48
+ sa.withParsedArguments(args.asJava)
49
+ }
50
+
51
+ case class ScalaVersion (version : String ) extends Arg { // FIXME: with CacheKey {
52
+ override def apply (sa : ScalafixArguments ): ScalafixArguments =
53
+ sa.withScalaVersion(version)
54
+ }
55
+
56
+ case class ScalacOptions (options : Seq [String ]) extends Arg { // FIXME: with CacheKey {
57
+ override def apply (sa : ScalafixArguments ): ScalafixArguments =
58
+ sa.withScalacOptions(options.asJava)
59
+ }
60
+
61
+ case class Classpath (classpath : Seq [Path ]) extends Arg { // FIXME: with CacheKey {
62
+ override def apply (sa : ScalafixArguments ): ScalafixArguments =
63
+ sa.withClasspath(classpath.asJava)
64
+ }
65
+
66
+ case object NoCache extends Arg with CacheKey {
67
+ override def apply (sa : ScalafixArguments ): ScalafixArguments =
68
+ sa // caching is currently implemented in sbt-scalafix itself
69
+ }
70
+ }
9
71
10
72
class ScalafixInterface private (
11
- val args : ScalafixArguments ,
12
- private val toolClasspath : URLClassLoader
73
+ scalafixArguments : ScalafixArguments , // hide it to force usage of withArgs so we can intercept arguments
74
+ val args : Seq [ Arg ]
13
75
) {
14
76
15
- // Accumulates the classpath via classloader delegation, as args.withToolClasspath() only considers the last call.
77
+ private val lastToolClasspath = args.reverse
78
+ .collectFirst { case tcp : Arg .ToolClasspath => tcp }
79
+ .getOrElse(
80
+ throw new IllegalArgumentException (
81
+ " a base toolClasspath must be provided"
82
+ )
83
+ )
84
+ .classLoader
85
+
86
+ private def this (
87
+ api : ScalafixAPI ,
88
+ toolClasspath : URLClassLoader ,
89
+ mainCallback : ScalafixMainCallback ,
90
+ printStream : PrintStream
91
+ ) = this (
92
+ api
93
+ .newArguments()
94
+ .withMainCallback(mainCallback)
95
+ .withPrintStream(printStream)
96
+ .withToolClasspath(toolClasspath),
97
+ Seq (Arg .ToolClasspath (toolClasspath))
98
+ )
99
+
100
+ // Accumulates the classpath via classloader delegation, as only the last Arg.ToolClasspath is considered
16
101
//
17
102
// We effectively end up with the following class loader hierarchy:
18
103
// 1. Meta-project sbt class loader
19
104
// - bound to the sbt session
20
105
// 2. ScalafixInterfacesClassloader, loading `scalafix-interfaces` from its parent
21
106
// - bound to the sbt session
22
107
// 3. `scalafix-cli` JARs
108
+ // - passed in the constructor
23
109
// - bound to the sbt session
24
110
// 4. Global, external dependencies
25
111
// - present only if custom dependencies were defined
@@ -34,23 +120,40 @@ class ScalafixInterface private (
34
120
customResolvers : Seq [Repository ],
35
121
extraInternalDeps : Seq [File ]
36
122
): ScalafixInterface = {
37
- if (extraExternalDeps.isEmpty && extraInternalDeps.isEmpty) {
38
- this
39
- } else {
40
- val extraURLs = ScalafixCoursier
41
- .scalafixToolClasspath(
42
- extraExternalDeps,
43
- customResolvers
44
- ) ++ extraInternalDeps.map(_.toURI.toURL)
45
-
46
- val newToolClasspath =
47
- new URLClassLoader (extraURLs.toArray, toolClasspath)
48
- new ScalafixInterface (
49
- args.withToolClasspath(newToolClasspath),
50
- newToolClasspath
51
- )
123
+ val extraURLs = ScalafixCoursier
124
+ .scalafixToolClasspath(
125
+ extraExternalDeps,
126
+ customResolvers
127
+ ) ++ extraInternalDeps.map(_.toURI.toURL)
128
+
129
+ if (extraURLs.isEmpty) this
130
+ else {
131
+ val classpath = new URLClassLoader (extraURLs.toArray, lastToolClasspath)
132
+ withArgs(Arg .ToolClasspath (classpath))
52
133
}
53
134
}
135
+
136
+ def withArgs (args : Arg * ): ScalafixInterface = {
137
+ val newScalafixArguments = args.foldLeft(scalafixArguments) { (acc, arg) =>
138
+ try arg(acc)
139
+ catch { case NonFatal (e) => throw new InvalidArgument (e.getMessage) }
140
+ }
141
+ new ScalafixInterface (newScalafixArguments, this .args ++ args)
142
+ }
143
+
144
+ def run (): Seq [ScalafixError ] =
145
+ scalafixArguments.run().toSeq
146
+
147
+ def availableRules (): Seq [ScalafixRule ] =
148
+ scalafixArguments.availableRules().asScala
149
+
150
+ def rulesThatWillRun (): Seq [ScalafixRule ] =
151
+ try scalafixArguments.rulesThatWillRun().asScala
152
+ catch { case NonFatal (e) => throw new InvalidArgument (e.getMessage) }
153
+
154
+ def validate (): Option [ScalafixException ] =
155
+ Option (scalafixArguments.validate().orElse(null ))
156
+
54
157
}
55
158
56
159
object ScalafixInterface {
@@ -61,7 +164,8 @@ object ScalafixInterface {
61
164
def fromToolClasspath (
62
165
scalafixDependencies : Seq [ModuleID ],
63
166
scalafixCustomResolvers : Seq [Repository ],
64
- logger : Logger = Compat .ConsoleLogger (System .out)
167
+ logger : Logger = Compat .ConsoleLogger (System .out),
168
+ printStream : PrintStream = System .out
65
169
): () => ScalafixInterface =
66
170
new LazyValue ({ () =>
67
171
val jars = ScalafixCoursier .scalafixCliJars(scalafixCustomResolvers)
@@ -71,15 +175,11 @@ object ScalafixInterface {
71
175
val classloader = new URLClassLoader (urls, interfacesParent)
72
176
val api = ScalafixAPI .classloadInstance(classloader)
73
177
val callback = new ScalafixLogger (logger)
74
-
75
- val args = api
76
- .newArguments()
77
- .withMainCallback(callback)
78
-
79
- new ScalafixInterface (args, classloader).addToolClasspath(
80
- scalafixDependencies,
81
- scalafixCustomResolvers,
82
- Nil
83
- )
178
+ new ScalafixInterface (api, classloader, callback, printStream)
179
+ .addToolClasspath(
180
+ scalafixDependencies,
181
+ scalafixCustomResolvers,
182
+ Nil
183
+ )
84
184
})
85
185
}
0 commit comments