@@ -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 .taskDyn {
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,19 +194,95 @@ 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+ // Meanwhile, let's just put nothing at all on the compiler
224+ // bootclasspath, and instead put scala-library and dotty-library on the
225+ // compiler classpath. This means that user code could shadow symbols
226+ // from these jars but we can live with that for now.
227+ classpathOptions := {
228+ val old = classpathOptions.value
229+ if (isDotty.value)
230+ old
231+ .withAutoBoot(false ) // we don't put the library on the compiler bootclasspath (as explained above)
232+ .withFilterLibrary(false ) // ...instead, we put it on the compiler classpath
233+ else
234+ old
235+ },
236+ // ... but when running under Java 8, we still need a compiler bootclasspath
237+ // that contains the JVM bootclasspath, otherwise sbt incremental
238+ // compilation breaks.
239+ scalacOptions ++= {
240+ if (isDotty.value && ! isJavaAtLeast(" 9" ))
241+ Seq (" -bootclasspath" , sys.props(" sun.boot.class.path" ))
242+ else
243+ Seq ()
244+ },
245+ managedScalaInstance := {
246+ val old = managedScalaInstance.value
247+ if (isDotty.value)
248+ false
249+ else
250+ old
251+ },
190252 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 }
253+ if (isDotty.value) Def .task {
254+ val updateReport =
255+ fetchArtifactsOf(
256+ dependencyResolution.value,
257+ scalaModuleInfo.value,
258+ updateConfiguration.value,
259+ (unresolvedWarningConfiguration in update).value,
260+ streams.value.log,
261+ scalaOrganization.value %% " dotty-doc" % scalaVersion.value)
262+ val scalaLibraryJar = getJar(updateReport,
263+ " org.scala-lang" , " scala-library" , revision = AllPassFilter )
264+ val dottyLibraryJar = getJar(updateReport,
265+ scalaOrganization.value, s " dotty-library_ ${scalaBinaryVersion.value}" , scalaVersion.value)
266+ val compilerJar = getJar(updateReport,
267+ scalaOrganization.value, s " dotty-compiler_ ${scalaBinaryVersion.value}" , scalaVersion.value)
268+ val allJars =
269+ getJars(updateReport, AllPassFilter , AllPassFilter , AllPassFilter )
270+
271+ makeScalaInstance(
272+ state.value,
273+ scalaVersion.value,
274+ scalaLibraryJar,
275+ dottyLibraryJar,
276+ compilerJar,
277+ allJars
278+ )
279+ }
280+ else Def .task {
281+ // This should really be `old` with `val old = scalaInstance.value`
282+ // above, except that this would force the original definition of the
283+ // `scalaInstance` task to be computed when `isDotty` is true, which
284+ // would fail because `managedScalaInstance` is false.
285+ Defaults .scalaInstanceTask.value
203286 }
204287 }.value,
205288
@@ -220,7 +303,7 @@ object DottyPlugin extends AutoPlugin {
220303 // circular dependency found:
221304 // ch.epfl.lamp#scala-library;0.9.0-RC1->ch.epfl.lamp#dotty-library_0.9;0.9.0-RC1->...
222305 // (This should go away once we merge dotty-library and scala-library in one artefact)
223- old.withCircularDependencyLevel(sbt.librarymanagement. ivy.CircularDependencyLevel .Ignore )
306+ old.withCircularDependencyLevel(ivy.CircularDependencyLevel .Ignore )
224307 } else old
225308 }
226309 ) ++ inConfig(Compile )(docSettings) ++ inConfig(Test )(docSettings)
@@ -236,23 +319,57 @@ object DottyPlugin extends AutoPlugin {
236319 scalacOptions += " -from-tasty"
237320 ))
238321
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
246-
247- val moduleID = (scalaOrganization.value % moduleName % scalaVersion.value).cross(crossVersion)
248- val descriptor = dependencyResolution.wrapDependencyInModule(moduleID, scalaInfo)
322+ /** Fetch artifacts for moduleID */
323+ def fetchArtifactsOf (
324+ dependencyRes : DependencyResolution ,
325+ scalaInfo : Option [ScalaModuleInfo ],
326+ updateConfig : UpdateConfiguration ,
327+ warningConfig : UnresolvedWarningConfiguration ,
328+ log : Logger ,
329+ moduleID : ModuleID ): UpdateReport = {
330+ val descriptor = dependencyRes.wrapDependencyInModule(moduleID, scalaInfo)
249331
250- dependencyResolution .update(descriptor, updateConfiguration, warningConfiguration , log) match {
332+ dependencyRes .update(descriptor, updateConfig, warningConfig , log) match {
251333 case Right (report) =>
252- report.allFiles
334+ report
253335 case _ =>
254336 throw new MessageOnlyException (
255- s " Couldn't retrieve ` ${scalaOrganization.value} %% $moduleName %% ${scalaVersion.value} `. " )
337+ s " Couldn't retrieve ` $moduleID `. " )
256338 }
257339 }
340+
341+ /** Get all jars in updateReport that match the given filter. */
342+ def getJars (updateReport : UpdateReport , organization : NameFilter , name : NameFilter , revision : NameFilter ): Seq [File ] = {
343+ updateReport.select(
344+ configurationFilter(Runtime .name),
345+ moduleFilter(organization, name, revision),
346+ artifactFilter(extension = " jar" )
347+ )
348+ }
349+
350+ /** Get the single jar in updateReport that match the given filter.
351+ * If zero or more than one jar match, an exception will be thrown. */
352+ def getJar (updateReport : UpdateReport , organization : NameFilter , name : NameFilter , revision : NameFilter ): File = {
353+ val jars = getJars(updateReport, organization, name, revision)
354+ assert(jars.size == 1 , s " There should only be one $name jar but found: $jars" )
355+ jars.head
356+ }
357+
358+ def makeScalaInstance (
359+ state : State , dottyVersion : String , scalaLibrary : File , dottyLibrary : File , compiler : File , all : Seq [File ]
360+ ): ScalaInstance = {
361+ val loader = state.classLoaderCache(all.toList)
362+ val loaderLibraryOnly = state.classLoaderCache(List (dottyLibrary, scalaLibrary))
363+ new ScalaInstance (
364+ dottyVersion,
365+ loader,
366+ loaderLibraryOnly,
367+ scalaLibrary, // Should be a Seq also containing dottyLibrary but zinc
368+ // doesn't support this, see comment above our redefinition
369+ // of `classpathOption`
370+ compiler,
371+ all.toArray,
372+ None )
373+
374+ }
258375}
0 commit comments