@@ -2,11 +2,12 @@ package dotty.tools.sbtplugin
2
2
3
3
import sbt ._
4
4
import sbt .Keys ._
5
- import sbt .librarymanagement .DependencyResolution
5
+ import sbt .librarymanagement ._
6
6
import sbt .internal .inc .ScalaInstance
7
7
import xsbti .compile ._
8
8
import java .net .URLClassLoader
9
9
import java .util .Optional
10
+ import scala .util .Properties .isJavaAtLeast
10
11
11
12
object DottyPlugin extends AutoPlugin {
12
13
object autoImport {
@@ -165,7 +166,13 @@ object DottyPlugin extends AutoPlugin {
165
166
166
167
scalaCompilerBridgeBinaryJar := Def .settingDyn {
167
168
if (isDotty.value) Def .task {
168
- val dottyBridgeArtifacts = fetchArtifactsOf(" dotty-sbt-bridge" , CrossVersion .disabled).value
169
+ val dottyBridgeArtifacts = fetchArtifactsOf(
170
+ dependencyResolution.value,
171
+ scalaModuleInfo.value,
172
+ updateConfiguration.value,
173
+ (unresolvedWarningConfiguration in update).value,
174
+ streams.value.log,
175
+ scalaOrganization.value % " dotty-sbt-bridge" % scalaVersion.value).allFiles
169
176
val jars = dottyBridgeArtifacts.filter(art => art.getName.startsWith(" dotty-sbt-bridge" ) && art.getName.endsWith(" .jar" )).toArray
170
177
if (jars.size == 0 )
171
178
throw new MessageOnlyException (" No jar found for dotty-sbt-bridge" )
@@ -187,41 +194,134 @@ object DottyPlugin extends AutoPlugin {
187
194
scalaBinaryVersion.value
188
195
},
189
196
197
+ // Ideally, we should have:
198
+ //
199
+ // 1. Nothing but the Java standard library on the _JVM_ bootclasspath
200
+ // (starting with Java 9 we cannot inspect it so we don't have a choice)
201
+ //
202
+ // 2. scala-library, dotty-library, dotty-compiler, dotty-doc on the _JVM_
203
+ // classpath, because we need all of those to actually run the compiler
204
+ // and the doc tool.
205
+ // NOTE: All of those should have the *same version* (equal to scalaVersion
206
+ // for everything but scala-library).
207
+ //
208
+ // 3. scala-library, dotty-library on the _compiler_ bootclasspath because
209
+ // user code should always have access to the symbols from these jars but
210
+ // should not be able to shadow them (the compiler bootclasspath has
211
+ // higher priority than the compiler classpath).
212
+ // NOTE: the versions of {scala,dotty}-library used here do not necessarily
213
+ // match the one used in 2. because a dependency of the current project might
214
+ // require more recent versions, this is OK.
215
+ //
216
+ // 4. every other dependency of the user project on the _compiler_
217
+ // classpath.
218
+ //
219
+ // Unfortunately, zinc assumes that the compiler bootclasspath is only
220
+ // made of one jar (scala-library), so to make this work we'll need to
221
+ // either change sbt's bootclasspath handling or wait until the
222
+ // dotty-library jar and scala-library jars are merged into one jar.
223
+ // Furthermore, zinc will put on the compiler bootclasspath the
224
+ // scala-library used on the JVM classpath, even if the current project
225
+ // transitively depends on a newer scala-library (this works because Scala
226
+ // 2 guarantees forward- and backward- binary compatibility, but we don't
227
+ // necessarily want to keep doing that in Scala 3).
228
+ // So for the moment, let's just put nothing at all on the compiler
229
+ // bootclasspath, and instead have scala-library and dotty-library on the
230
+ // compiler classpath. This means that user code could shadow symbols
231
+ // from these jars but we can live with that for now.
232
+ classpathOptions := {
233
+ val old = classpathOptions.value
234
+ if (isDotty.value)
235
+ old
236
+ .withAutoBoot(false ) // we don't put the library on the compiler bootclasspath (as explained above)
237
+ .withFilterLibrary(false ) // ...instead, we put it on the compiler classpath
238
+ else
239
+ old
240
+ },
241
+ // ... but when running under Java 8, we still need a compiler bootclasspath
242
+ // that contains the JVM bootclasspath, otherwise sbt incremental
243
+ // compilation breaks.
244
+ scalacOptions ++= {
245
+ if (isDotty.value && ! isJavaAtLeast(" 9" ))
246
+ Seq (" -bootclasspath" , sys.props(" sun.boot.class.path" ))
247
+ else
248
+ Seq ()
249
+ },
250
+ // If the current scalaVersion is N and we transitively depend on
251
+ // {scala, dotty}-{library, compiler, ...} M where M > N, we want the
252
+ // newest version on our compiler classpath, but sbt by default will
253
+ // instead rewrite all our dependencies to version N, the following line
254
+ // prevents this behavior.
255
+ scalaModuleInfo := {
256
+ val old = scalaModuleInfo.value
257
+ if (isDotty.value)
258
+ old.map(_.withOverrideScalaVersion(false ))
259
+ else
260
+ old
261
+ },
262
+ // Prevent sbt from creating a ScalaTool configuration
263
+ managedScalaInstance := {
264
+ val old = managedScalaInstance.value
265
+ if (isDotty.value)
266
+ false
267
+ else
268
+ old
269
+ },
270
+ // ... instead, we'll fetch the compiler and its dependencies ourselves.
190
271
scalaInstance := Def .taskDyn {
191
- val si = scalaInstance.value
192
- if (isDotty.value) {
193
- Def .task {
194
- val dottydocArtifacts = fetchArtifactsOf(" dotty-doc" , CrossVersion .binary).value
195
- val includeArtifact = (f : File ) => f.getName.endsWith(" .jar" )
196
- val dottydocJars = dottydocArtifacts.filter(includeArtifact).toArray
197
- val allJars = (si.allJars ++ dottydocJars).distinct
198
- val loader = new URLClassLoader (Path .toURLs(dottydocJars), si.loader)
199
- new ScalaInstance (si.version, loader, si.loaderLibraryOnly, si.libraryJar, si.compilerJar, allJars, si.explicitActual)
200
- }
201
- } else {
202
- Def .task { si }
272
+ if (isDotty.value) Def .task {
273
+ val updateReport =
274
+ fetchArtifactsOf(
275
+ dependencyResolution.value,
276
+ scalaModuleInfo.value,
277
+ updateConfiguration.value,
278
+ (unresolvedWarningConfiguration in update).value,
279
+ streams.value.log,
280
+ scalaOrganization.value %% " dotty-doc" % scalaVersion.value)
281
+ val scalaLibraryJar = getJar(updateReport,
282
+ " org.scala-lang" , " scala-library" , revision = AllPassFilter )
283
+ val dottyLibraryJar = getJar(updateReport,
284
+ scalaOrganization.value, s " dotty-library_ ${scalaBinaryVersion.value}" , scalaVersion.value)
285
+ val compilerJar = getJar(updateReport,
286
+ scalaOrganization.value, s " dotty-compiler_ ${scalaBinaryVersion.value}" , scalaVersion.value)
287
+ val allJars =
288
+ getJars(updateReport, AllPassFilter , AllPassFilter , AllPassFilter )
289
+
290
+ makeScalaInstance(
291
+ state.value,
292
+ scalaVersion.value,
293
+ scalaLibraryJar,
294
+ dottyLibraryJar,
295
+ compilerJar,
296
+ allJars
297
+ )
298
+ }
299
+ else Def .task {
300
+ // This should really be `old` with `val old = scalaInstance.value`
301
+ // above, except that this would force the original definition of the
302
+ // `scalaInstance` task to be computed when `isDotty` is true, which
303
+ // would fail because `managedScalaInstance` is false.
304
+ Defaults .scalaInstanceTask.value
203
305
}
204
306
}.value,
205
307
308
+ // Because managedScalaInstance is false, sbt won't add the standard library to our dependencies for us
309
+ libraryDependencies ++= {
310
+ if (isDotty.value && autoScalaLibrary.value)
311
+ Seq (scalaOrganization.value %% " dotty-library" % scalaVersion.value)
312
+ else
313
+ Seq ()
314
+ },
315
+
316
+ // Turns off the warning:
317
+ // [warn] Binary version (0.9.0-RC1) for dependency ...;0.9.0-RC1
318
+ // [warn] in ... differs from Scala binary version in project (0.9).
206
319
scalaModuleInfo := {
207
320
val old = scalaModuleInfo.value
208
- if (isDotty.value) {
209
- // Turns off the warning:
210
- // [warn] Binary version (0.9.0-RC1) for dependency ...;0.9.0-RC1
211
- // [warn] in ... differs from Scala binary version in project (0.9).
321
+ if (isDotty.value)
212
322
old.map(_.withCheckExplicit(false ))
213
- } else old
214
- },
215
-
216
- updateOptions := {
217
- val old = updateOptions.value
218
- if (isDotty.value) {
219
- // Turn off the warning:
220
- // circular dependency found:
221
- // ch.epfl.lamp#scala-library;0.9.0-RC1->ch.epfl.lamp#dotty-library_0.9;0.9.0-RC1->...
222
- // (This should go away once we merge dotty-library and scala-library in one artefact)
223
- old.withCircularDependencyLevel(sbt.librarymanagement.ivy.CircularDependencyLevel .Ignore )
224
- } else old
323
+ else
324
+ old
225
325
}
226
326
) ++ inConfig(Compile )(docSettings) ++ inConfig(Test )(docSettings)
227
327
}
@@ -236,23 +336,57 @@ object DottyPlugin extends AutoPlugin {
236
336
scalacOptions += " -from-tasty"
237
337
))
238
338
239
- /** Fetch artifacts for scalaOrganization.value %% moduleName % scalaVersion.value */
240
- private def fetchArtifactsOf (moduleName : String , crossVersion : CrossVersion ) = Def .task {
241
- val dependencyResolution = Keys .dependencyResolution.value
242
- val log = streams.value.log
243
- val scalaInfo = scalaModuleInfo.value
244
- val updateConfiguration = Keys .updateConfiguration.value
245
- val warningConfiguration = (unresolvedWarningConfiguration in update).value
339
+ /** Fetch artifacts for moduleID */
340
+ def fetchArtifactsOf (
341
+ dependencyRes : DependencyResolution ,
342
+ scalaInfo : Option [ScalaModuleInfo ],
343
+ updateConfig : UpdateConfiguration ,
344
+ warningConfig : UnresolvedWarningConfiguration ,
345
+ log : Logger ,
346
+ moduleID : ModuleID ): UpdateReport = {
347
+ val descriptor = dependencyRes.wrapDependencyInModule(moduleID, scalaInfo)
246
348
247
- val moduleID = (scalaOrganization.value % moduleName % scalaVersion.value).cross(crossVersion)
248
- val descriptor = dependencyResolution.wrapDependencyInModule(moduleID, scalaInfo)
249
-
250
- dependencyResolution.update(descriptor, updateConfiguration, warningConfiguration, log) match {
349
+ dependencyRes.update(descriptor, updateConfig, warningConfig, log) match {
251
350
case Right (report) =>
252
- report.allFiles
351
+ report
253
352
case _ =>
254
353
throw new MessageOnlyException (
255
- s " Couldn't retrieve ` ${scalaOrganization.value} %% $moduleName %% ${scalaVersion.value} `. " )
354
+ s " Couldn't retrieve ` $moduleID `. " )
256
355
}
257
356
}
357
+
358
+ /** Get all jars in updateReport that match the given filter. */
359
+ def getJars (updateReport : UpdateReport , organization : NameFilter , name : NameFilter , revision : NameFilter ): Seq [File ] = {
360
+ updateReport.select(
361
+ configurationFilter(Runtime .name),
362
+ moduleFilter(organization, name, revision),
363
+ artifactFilter(extension = " jar" )
364
+ )
365
+ }
366
+
367
+ /** Get the single jar in updateReport that match the given filter.
368
+ * If zero or more than one jar match, an exception will be thrown. */
369
+ def getJar (updateReport : UpdateReport , organization : NameFilter , name : NameFilter , revision : NameFilter ): File = {
370
+ val jars = getJars(updateReport, organization, name, revision)
371
+ assert(jars.size == 1 , s " There should only be one $name jar but found: $jars" )
372
+ jars.head
373
+ }
374
+
375
+ def makeScalaInstance (
376
+ state : State , dottyVersion : String , scalaLibrary : File , dottyLibrary : File , compiler : File , all : Seq [File ]
377
+ ): ScalaInstance = {
378
+ val loader = state.classLoaderCache(all.toList)
379
+ val loaderLibraryOnly = state.classLoaderCache(List (dottyLibrary, scalaLibrary))
380
+ new ScalaInstance (
381
+ dottyVersion,
382
+ loader,
383
+ loaderLibraryOnly,
384
+ scalaLibrary, // Should be a Seq also containing dottyLibrary but zinc
385
+ // doesn't support this, see comment above our redefinition
386
+ // of `classpathOption`
387
+ compiler,
388
+ all.toArray,
389
+ None )
390
+
391
+ }
258
392
}
0 commit comments