@@ -2,11 +2,12 @@ package dotty.tools.sbtplugin
22
33import sbt ._
44import sbt .Keys ._
5- import sbt .librarymanagement .DependencyResolution
5+ import sbt .librarymanagement ._
66import sbt .internal .inc .ScalaInstance
77import xsbti .compile ._
88import java .net .URLClassLoader
99import java .util .Optional
10+ import scala .util .Properties .isJavaAtLeast
1011
1112object DottyPlugin extends AutoPlugin {
1213 object autoImport {
@@ -165,7 +166,13 @@ object DottyPlugin extends AutoPlugin {
165166
166167 scalaCompilerBridgeBinaryJar := Def .settingDyn {
167168 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
169176 val jars = dottyBridgeArtifacts.filter(art => art.getName.startsWith(" dotty-sbt-bridge" ) && art.getName.endsWith(" .jar" )).toArray
170177 if (jars.size == 0 )
171178 throw new MessageOnlyException (" No jar found for dotty-sbt-bridge" )
@@ -187,41 +194,134 @@ object DottyPlugin extends AutoPlugin {
187194 scalaBinaryVersion.value
188195 },
189196
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.
190271 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
203305 }
204306 }.value,
205307
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).
206319 scalaModuleInfo := {
207320 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)
212322 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
225325 }
226326 ) ++ inConfig(Compile )(docSettings) ++ inConfig(Test )(docSettings)
227327 }
@@ -236,23 +336,57 @@ object DottyPlugin extends AutoPlugin {
236336 scalacOptions += " -from-tasty"
237337 ))
238338
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)
246348
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 {
251350 case Right (report) =>
252- report.allFiles
351+ report
253352 case _ =>
254353 throw new MessageOnlyException (
255- s " Couldn't retrieve ` ${scalaOrganization.value} %% $moduleName %% ${scalaVersion.value} `. " )
354+ s " Couldn't retrieve ` $moduleID `. " )
256355 }
257356 }
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+ }
258392}
0 commit comments