diff --git a/build.sbt b/build.sbt index eb222f5d9ffa..ddb2d0273300 100644 --- a/build.sbt +++ b/build.sbt @@ -8,7 +8,6 @@ val `dotty-compiler-bootstrapped` = Build.`dotty-compiler-bootstrapped` val `dotty-library` = Build.`dotty-library` val `dotty-library-bootstrapped` = Build.`dotty-library-bootstrapped` val `dotty-sbt-bridge` = Build.`dotty-sbt-bridge` -val `dotty-sbt-bridge-bootstrapped` = Build.`dotty-sbt-bridge-bootstrapped` val `dotty-language-server` = Build.`dotty-language-server` val `dotty-bench` = Build.`dotty-bench` val `dotty-bench-bootstrapped` = Build.`dotty-bench-bootstrapped` diff --git a/compiler/src/dotty/tools/dotc/reporting/AbstractReporter.scala b/compiler/src/dotty/tools/dotc/reporting/AbstractReporter.scala new file mode 100644 index 000000000000..630df31014f7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/reporting/AbstractReporter.scala @@ -0,0 +1,8 @@ +package dotty.tools +package dotc +package reporting + +/** + * This class mixes in a few standard traits, so that it is easier to extend from Java. + */ +abstract class AbstractReporter extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering diff --git a/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 4d775eaa3bc5..8eb9dda8f7b1 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -13,7 +13,7 @@ import diagnostic.messages.{ Error, ConditionalWarning } class ConsoleReporter( reader: BufferedReader = Console.in, writer: PrintWriter = new PrintWriter(Console.err, true) -) extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering { +) extends AbstractReporter { import MessageContainer._ diff --git a/project/Build.scala b/project/Build.scala index 67882cb4f2b7..7bbc21f1f01c 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -134,7 +134,7 @@ object Build { "-language:existentials,higherKinds,implicitConversions" ), - javacOptions ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"), + javacOptions in (Compile, compile) ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"), // Change this to true if you want to bootstrap using a published non-bootstrapped compiler bootstrapFromPublishedJars := false, @@ -209,10 +209,14 @@ object Build { testOptions in Test += Tests.Argument(TestFrameworks.JUnit, "-a", "-v") ) - // Settings used for projects compiled only with Scala 2 - lazy val commonScala2Settings = commonSettings ++ Seq( + // Settings used for projects compiled only with Java + lazy val commonJavaSettings = commonSettings ++ Seq( version := dottyVersion, - scalaVersion := scalacVersion + scalaVersion := scalacVersion, + // Do not append Scala versions to the generated artifacts + crossPaths := false, + // Do not depend on the Scala library + autoScalaLibrary := false ) // Settings used when compiling dotty using Scala 2 @@ -226,17 +230,9 @@ object Build { version := dottyVersion, scalaVersion := dottyNonBootstrappedVersion, - // Avoid having to run `dotty-sbt-bridge/publishLocal` before compiling a bootstrapped project - scalaCompilerBridgeSource := - (dottyOrganization %% "dotty-sbt-bridge" % dottyVersion) - .artifacts(Artifact.sources("dotty-sbt-bridge").withUrl( - // We cannot use the `packageSrc` task because a setting cannot depend - // on a task. Instead, we make `compile` below depend on the bridge `packageSrc` - Some((artifactPath in (`dotty-sbt-bridge`, Compile, packageSrc)).value.toURI.toURL))), - compile in Compile := (compile in Compile) - .dependsOn(packageSrc in (`dotty-sbt-bridge`, Compile)) - .dependsOn(compile in (`dotty-sbt-bridge`, Compile)) - .value, + scalaCompilerBridgeBinaryJar := { + Some((packageBin in (`dotty-sbt-bridge`, Compile)).value) + }, // Use the same name as the non-bootstrapped projects for the artifacts moduleName ~= { _.stripSuffix("-bootstrapped") }, @@ -359,13 +355,6 @@ object Build { // currently refers to dotty in its scripted task and "aggregate" does not take by-name // parameters: https://github.com/sbt/sbt/issues/2200 lazy val dottySbtBridgeRef = LocalProject("dotty-sbt-bridge") - // Same thing for the bootstrapped version - lazy val dottySbtBridgeBootstrappedRef = LocalProject("dotty-sbt-bridge-bootstrapped") - - def dottySbtBridgeReference(implicit mode: Mode): LocalProject = mode match { - case NonBootstrapped => dottySbtBridgeRef - case _ => dottySbtBridgeBootstrappedRef - } // The root project: // - aggregates other projects so that "compile", "test", etc are run on all projects at once. @@ -375,15 +364,7 @@ object Build { lazy val `dotty-bootstrapped` = project.asDottyRoot(Bootstrapped) lazy val `dotty-interfaces` = project.in(file("interfaces")). - settings(commonScala2Settings). // Java-only project, so this is fine - settings( - // Do not append Scala versions to the generated artifacts - crossPaths := false, - // Do not depend on the Scala library - autoScalaLibrary := false, - //Remove javac invalid options in Compile doc - javacOptions in (Compile, doc) --= Seq("-Xlint:unchecked", "-Xlint:deprecation") - ) + settings(commonJavaSettings) private lazy val dottydocClasspath = Def.task { val jars = (packageAll in `dotty-compiler`).value @@ -798,67 +779,19 @@ object Build { case Bootstrapped => `dotty-library-bootstrapped` } - // until sbt/sbt#2402 is fixed (https://github.com/sbt/sbt/issues/2402) - lazy val cleanSbtBridge = TaskKey[Unit]("cleanSbtBridge", "delete dotty-sbt-bridge cache") - - def cleanSbtBridgeImpl(): Unit = { - val home = System.getProperty("user.home") - val sbtOrg = "org.scala-sbt" - val bridgePattern = s"*dotty-sbt-bridge*$dottyVersion*" - - IO.delete((file(home) / ".sbt" / "1.0" / "zinc" / sbtOrg * bridgePattern).get) - IO.delete((file(home) / ".sbt" / "boot" * "scala-*" / sbtOrg / "sbt" * "*" * bridgePattern).get) - } - - lazy val dottySbtBridgeSettings = Seq( - cleanSbtBridge := { - cleanSbtBridgeImpl() - }, - compile in Compile := { - val log = streams.value.log - val prev = (previousCompile in Compile).value.analysis.orElse(null) - val cur = (compile in Compile).value - if (prev != cur) { - log.info("Cleaning the dotty-sbt-bridge cache because it was recompiled.") - cleanSbtBridgeImpl() - } - cur - }, - description := "sbt compiler bridge for Dotty", - resolvers += Resolver.typesafeIvyRepo("releases"), // For org.scala-sbt:api - libraryDependencies ++= Seq( - Dependencies.`compiler-interface` % Provided, - (Dependencies.`zinc-api-info` % Test).withDottyCompat(scalaVersion.value) - ), - // The sources should be published with crossPaths := false since they - // need to be compiled by the project using the bridge. - crossPaths := false, - - // Don't publish any binaries for the bridge because of the above - publishArtifact in (Compile, packageBin) := false, - - fork in Test := true, - parallelExecution in Test := false - ) + lazy val `dotty-sbt-bridge` = project.in(file("sbt-bridge")). + dependsOn(dottyCompiler(NonBootstrapped) % Provided). + dependsOn(dottyDoc(NonBootstrapped) % Provided). + settings(commonJavaSettings). + settings( + description := "sbt compiler bridge for Dotty", + libraryDependencies ++= Seq( + Dependencies.`compiler-interface` % Provided, + (Dependencies.`zinc-api-info` % Test).withDottyCompat(scalaVersion.value) + ), - lazy val `dotty-sbt-bridge` = project.in(file("sbt-bridge")).asDottySbtBridge(NonBootstrapped) - lazy val `dotty-sbt-bridge-bootstrapped` = project.in(file("sbt-bridge")).asDottySbtBridge(Bootstrapped) - .settings( - // Tweak -Yscala2-unpickler to allow some sbt dependencies used in tests - /* - scalacOptions in Test := { - val oldOptions = (scalacOptions in Test).value - val i = oldOptions.indexOf("-Yscala2-unpickler") - assert(i != -1) - val oldValue = oldOptions(i + 1) - - val attList = (dependencyClasspath in Test).value - val sbtIo = findLib(attList, "org.scala-sbt/io") - val zincApiInfo = findLib(attList, "zinc-apiinfo") - - oldOptions.updated(i + 1, s"$sbtIo:$zincApiInfo:$oldValue") - } - */ + fork in Test := true, + parallelExecution in Test := false ) lazy val `dotty-language-server` = project.in(file("language-server")). @@ -1017,7 +950,7 @@ object Build { scriptedLaunchOpts ++= ivyPaths.value.ivyHome.map("-Dsbt.ivy.home=" + _.getAbsolutePath).toList, scriptedBufferLog := true, scripted := scripted.dependsOn( - publishLocal in `dotty-sbt-bridge-bootstrapped`, + publishLocal in `dotty-sbt-bridge`, publishLocal in `dotty-interfaces`, publishLocal in `dotty-compiler-bootstrapped`, publishLocal in `dotty-library-bootstrapped`, @@ -1285,7 +1218,7 @@ object Build { // FIXME: we do not aggregate `bin` because its tests delete jars, thus breaking other tests def asDottyRoot(implicit mode: Mode): Project = project.withCommonSettings. - aggregate(`dotty-interfaces`, dottyLibrary, dottyCompiler, dottyDoc, dottySbtBridgeReference). + aggregate(`dotty-interfaces`, dottyLibrary, dottyCompiler, dottyDoc, `dotty-sbt-bridge`). bootstrappedAggregate(`scala-library`, `scala-compiler`, `scala-reflect`, scalap, `dotty-language-server`). dependsOn(dottyCompiler). dependsOn(dottyLibrary). @@ -1309,11 +1242,6 @@ object Build { dependsOn(dottyCompiler, dottyCompiler % "test->test"). settings(dottyDocSettings) - def asDottySbtBridge(implicit mode: Mode): Project = project.withCommonSettings. - dependsOn(dottyCompiler % Provided). - dependsOn(dottyDoc % Provided). - settings(dottySbtBridgeSettings) - def asDottyBench(implicit mode: Mode): Project = project.withCommonSettings. dependsOn(dottyCompiler). settings(commonBenchmarkSettings). diff --git a/sbt-bridge/src/xsbt/CachedCompilerImpl.java b/sbt-bridge/src/xsbt/CachedCompilerImpl.java new file mode 100644 index 000000000000..caaecf8ca732 --- /dev/null +++ b/sbt-bridge/src/xsbt/CachedCompilerImpl.java @@ -0,0 +1,74 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt; + +import xsbti.AnalysisCallback; +import xsbti.Logger; +import xsbti.Reporter; +import xsbti.Severity; +import xsbti.compile.*; + +import java.io.File; + +import dotty.tools.dotc.core.Contexts.Context; +import dotty.tools.dotc.core.Contexts.ContextBase; +import dotty.tools.dotc.Main; +import dotty.tools.dotc.interfaces.*; + +import java.net.URLClassLoader; + +public class CachedCompilerImpl implements CachedCompiler { + private final String[] args; + private final Output output; + private final String[] outputArgs; + + public CachedCompilerImpl(String[] args, Output output) { + super(); + this.args = args; + this.output = output; + + if (!(output instanceof SingleOutput)) + throw new IllegalArgumentException("output should be a SingleOutput, was a " + output.getClass().getName()); + + this.outputArgs = + new String[] { "-d", ((SingleOutput) output).getOutputDirectory().getAbsolutePath().toString() }; + } + + public String[] commandArguments(File[] sources) { + String[] sortedSourcesAbsolute = new String[sources.length]; + for (int i = 0; i < sources.length; i++) + sortedSourcesAbsolute[i] = sources[i].getAbsolutePath(); + java.util.Arrays.sort(sortedSourcesAbsolute); + + // Concatenate outputArgs, args and sortedSourcesAbsolute + String[] result = new String[outputArgs.length + args.length + sortedSourcesAbsolute.length]; + int j = 0; + for (int i = 0; i < outputArgs.length; i++, j++) + result[j] = outputArgs[i]; + for (int i = 0; i < args.length; i++, j++) + result[j] = args[i]; + for (int i = 0; i < sortedSourcesAbsolute.length; i++, j++) + result[j] = sortedSourcesAbsolute[i]; + + return result; + } + + synchronized public void run(File[] sources, DependencyChanges changes, AnalysisCallback callback, Logger log, Reporter delegate, CompileProgress progress) { + log.debug(() -> { + String msg = "Calling Dotty compiler with arguments (CompilerInterface):"; + for (String arg : args) + msg = msg + "\n\t" + arg; + return msg; + }); + + Context ctx = new ContextBase().initialCtx().fresh() + .setSbtCallback(callback) + .setReporter(new DelegatingReporter(delegate)); + + dotty.tools.dotc.reporting.Reporter reporter = Main.process(commandArguments(sources), ctx); + if (reporter.hasErrors()) { + throw new InterfaceCompileFailed(args, new Problem[0]); + } + } +} diff --git a/sbt-bridge/src/xsbt/CompilerClassLoader.java b/sbt-bridge/src/xsbt/CompilerClassLoader.java new file mode 100644 index 000000000000..ec0e32049f97 --- /dev/null +++ b/sbt-bridge/src/xsbt/CompilerClassLoader.java @@ -0,0 +1,122 @@ +package xsbt; + +import java.lang.reflect.Field; + +import java.net.URL; +import java.net.URLClassLoader; + +import java.util.WeakHashMap; + +/** + * A classloader to run the compiler + *

+ * A CompilerClassLoader is constructed from a list of `urls` that need to be on + * the classpath to run the compiler and the classloader used by sbt. + *

+ * To understand why a custom classloader is needed for the compiler, let us + * describe some alternatives that wouldn't work. + *

+ *

+ * Our solution is to implement a subclass of URLClassLoader with no parent, instead + * we override `loadClass` to load the `xsbti.*` interfaces from `sbtLoader`. + */ +public class CompilerClassLoader extends URLClassLoader { + private final ClassLoader sbtLoader; + + public CompilerClassLoader(URL[] urls, ClassLoader sbtLoader) { + super(urls, null); + this.sbtLoader = sbtLoader; + } + + @Override + public Class loadClass(String className, boolean resolve) throws ClassNotFoundException { + if (className.startsWith("xsbti.")) { + // We can't use the loadClass overload with two arguments because it's + // protected, but we can do the same by hand (the classloader instance + // from which we call resolveClass does not matter). + Class c = sbtLoader.loadClass(className); + if (resolve) + resolveClass(c); + return c; + } else { + return super.loadClass(className, resolve); + } + } + + /** + * Cache the result of `fixBridgeLoader`. + *

+ * Reusing ClassLoaders is important for warm performance since otherwise the + * JIT code cache for the compiler will be discarded between every call to + * the sbt `compile` task. + */ + private static WeakHashMap fixedLoaderCache = new WeakHashMap<>(); + + /** + * Fix the compiler bridge ClassLoader + *

+ * Soundtrack: https://www.youtube.com/watch?v=imamcajBEJs + *

+ * The classloader that we get from sbt looks like: + *

+ * URLClassLoader(bridgeURLs, + * DualLoader(scalaLoader, notXsbtiFilter, sbtLoader, xsbtiFilter)) + *

+ * DualLoader will load the `xsbti.*` interfaces using `sbtLoader` and + * everything else with `scalaLoader`. Once we have loaded the dotty Main + * class using `scalaLoader`, subsequent classes in the dotty compiler will + * also be loaded by `scalaLoader` and _not_ by the DualLoader. But the sbt + * compiler phases are part of dotty and still need access to the `xsbti.*` + * interfaces in `sbtLoader`, therefore DualLoader does not work for us + * (this issue is not present with scalac because the sbt phases are + * currently defined in the compiler bridge itself, not in scalac). + *

+ * CompilerClassLoader is a replacement for DualLoader. Until we can fix + * this in sbt proper, we need to use reflection to construct our own + * fixed classloader: + *

+ * URLClassLoader(bridgeURLs, + * CompilerClassLoader(scalaLoader.getURLs, sbtLoader)) + * + * @param bridgeLoader The classloader that sbt uses to load the compiler bridge + * @return A fixed classloader that works with dotty + */ + synchronized public static ClassLoader fixBridgeLoader(ClassLoader bridgeLoader) { + return fixedLoaderCache.computeIfAbsent(bridgeLoader, k -> computeFixedLoader(k)); + } + + private static ClassLoader computeFixedLoader(ClassLoader bridgeLoader) { + URLClassLoader urlBridgeLoader = (URLClassLoader) bridgeLoader; + ClassLoader dualLoader = urlBridgeLoader.getParent(); + Class dualLoaderClass = dualLoader.getClass(); + + try { + // DualLoader.parentA and DualLoader.parentB are private + Field parentAField = dualLoaderClass.getDeclaredField("parentA"); + parentAField.setAccessible(true); + Field parentBField = dualLoaderClass.getDeclaredField("parentB"); + parentBField.setAccessible(true); + URLClassLoader scalaLoader = (URLClassLoader) parentAField.get(dualLoader); + URLClassLoader sbtLoader = (URLClassLoader) parentBField.get(dualLoader); + + URL[] bridgeURLs = urlBridgeLoader.getURLs(); + return new URLClassLoader(bridgeURLs, + new CompilerClassLoader(scalaLoader.getURLs(), sbtLoader)); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/sbt-bridge/src/xsbt/CompilerClassLoader.scala b/sbt-bridge/src/xsbt/CompilerClassLoader.scala deleted file mode 100644 index 071141dcfac3..000000000000 --- a/sbt-bridge/src/xsbt/CompilerClassLoader.scala +++ /dev/null @@ -1,104 +0,0 @@ -package xsbt - -import java.net.{URL, URLClassLoader} - -import scala.collection.mutable - -/** A classloader to run the compiler - * - * A CompilerClassLoader is constructed from a list of `urls` that need to be on - * the classpath to run the compiler and the classloader used by sbt. - * - * To understand why a custom classloader is needed for the compiler, let us - * describe some alternatives that wouldn't work. - * - `new URLClassLoader(urls)`: - * The compiler contains sbt phases that callback to sbt using the `xsbti.*` - * interfaces. If `urls` does not contain the sbt interfaces we'll get a - * `ClassNotFoundException` in the compiler when we try to use them, if - * `urls` does contain the interfaces we'll get a `ClassCastException` or a - * `LinkageError` because if the same class is loaded by two different - * classloaders, they are considered distinct by the JVM. - * - `new URLClassLoader(urls, sbtLoader)`: - * Because of the JVM delegation model, this means that we will only load - * a class from `urls` if it's not present in the parent `sbtLoader`, but - * sbt uses its own version of the scala compiler and scala library which - * is not the one we need to run the compiler. - * - * Our solution is to implement a subclass of URLClassLoader with no parent, instead - * we override `loadClass` to load the `xsbti.*` interfaces from `sbtLoader`. - */ -class CompilerClassLoader(urls: Array[URL], sbtLoader: ClassLoader) - extends URLClassLoader(urls, null) { - override def loadClass(className: String, resolve: Boolean): Class[_] = - if (className.startsWith("xsbti.")) { - // We can't use the loadClass overload with two arguments because it's - // protected, but we can do the same by hand (the classloader instance - // from which we call resolveClass does not matter). - val c = sbtLoader.loadClass(className) - if (resolve) - resolveClass(c) - c - } else { - super.loadClass(className, resolve) - } -} - -object CompilerClassLoader { - /** Cache the result of `fixBridgeLoader`. - * - * Reusing ClassLoaders is important for warm performance since otherwise the - * JIT code cache for the compiler will be discarded between every call to - * the sbt `compile` task. - */ - private[this] val fixedLoaderCache = new mutable.WeakHashMap[ClassLoader, ClassLoader] - - /** Fix the compiler bridge ClassLoader - * - * Soundtrack: https://www.youtube.com/watch?v=imamcajBEJs - * - * The classloader that we get from sbt looks like: - * - * URLClassLoader(bridgeURLs, - * DualLoader(scalaLoader, notXsbtiFilter, sbtLoader, xsbtiFilter)) - * - * DualLoader will load the `xsbti.*` interfaces using `sbtLoader` and - * everything else with `scalaLoader`. Once we have loaded the dotty Main - * class using `scalaLoader`, subsequent classes in the dotty compiler will - * also be loaded by `scalaLoader` and _not_ by the DualLoader. But the sbt - * compiler phases are part of dotty and still need access to the `xsbti.*` - * interfaces in `sbtLoader`, therefore DualLoader does not work for us - * (this issue is not present with scalac because the sbt phases are - * currently defined in the compiler bridge itself, not in scalac). - * - * CompilerClassLoader is a replacement for DualLoader. Until we can fix - * this in sbt proper, we need to use reflection to construct our own - * fixed classloader: - * - * URLClassLoader(bridgeURLs, - * CompilerClassLoader(scalaLoader.getURLs, sbtLoader)) - * - * @param bridgeLoader The classloader that sbt uses to load the compiler bridge - * @return A fixed classloader that works with dotty - */ - def fixBridgeLoader(bridgeLoader: ClassLoader): ClassLoader = synchronized { - fixedLoaderCache.getOrElseUpdate(bridgeLoader, computeFixedLoader(bridgeLoader)) - } - - private[this] def computeFixedLoader(bridgeLoader: ClassLoader) = bridgeLoader match { - case bridgeLoader: URLClassLoader => - val dualLoader = bridgeLoader.getParent - val dualLoaderClass = dualLoader.getClass - - // DualLoader#parentA and DualLoader#parentB are private - val parentAField = dualLoaderClass.getDeclaredField("parentA") - parentAField.setAccessible(true) - val parentBField = dualLoaderClass.getDeclaredField("parentB") - parentBField.setAccessible(true) - val scalaLoader = parentAField.get(dualLoader).asInstanceOf[URLClassLoader] - val sbtLoader = parentBField.get(dualLoader).asInstanceOf[URLClassLoader] - - val bridgeURLs = bridgeLoader.getURLs - new URLClassLoader(bridgeURLs, - new CompilerClassLoader(scalaLoader.getURLs, sbtLoader)) - } -} diff --git a/sbt-bridge/src/xsbt/CompilerInterface.java b/sbt-bridge/src/xsbt/CompilerInterface.java new file mode 100644 index 000000000000..10b90adbb0e3 --- /dev/null +++ b/sbt-bridge/src/xsbt/CompilerInterface.java @@ -0,0 +1,43 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt; + +import xsbti.AnalysisCallback; +import xsbti.Logger; +import xsbti.Reporter; +import xsbti.Severity; +import xsbti.compile.*; + +import java.io.File; + +import dotty.tools.dotc.core.Contexts.ContextBase; +import dotty.tools.dotc.Main; +import dotty.tools.dotc.interfaces.*; + +import java.lang.reflect.InvocationTargetException; +import java.net.URLClassLoader; + +public final class CompilerInterface { + public CachedCompiler newCompiler(String[] options, Output output, Logger initialLog, Reporter initialDelegate) { + // The classloader that sbt uses to load the compiler bridge is broken + // (see CompilerClassLoader#fixBridgeLoader for details). To workaround + // this we construct our own ClassLoader and then run the following code + // with it: + // new CachedCompilerImpl(options, output) + + try { + ClassLoader bridgeLoader = this.getClass().getClassLoader(); + ClassLoader fixedLoader = CompilerClassLoader.fixBridgeLoader(bridgeLoader); + Class cciClass = fixedLoader.loadClass("xsbt.CachedCompilerImpl"); + return (CachedCompiler) cciClass.getConstructors()[0].newInstance(options, output); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public void run(File[] sources, DependencyChanges changes, AnalysisCallback callback, Logger log, + Reporter delegate, CompileProgress progress, CachedCompiler cached) { + cached.run(sources, changes, callback, log, delegate, progress); + } +} diff --git a/sbt-bridge/src/xsbt/CompilerInterface.scala b/sbt-bridge/src/xsbt/CompilerInterface.scala deleted file mode 100644 index ac828b996957..000000000000 --- a/sbt-bridge/src/xsbt/CompilerInterface.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2008, 2009 Mark Harrah - */ -package xsbt - -import xsbti.{ AnalysisCallback, Logger, Reporter, Severity } -import xsbti.compile._ -import java.io.File - -import dotty.tools.dotc.core.Contexts.ContextBase -import dotty.tools.dotc.{ Main => DottyMain } -import dotty.tools.dotc.interfaces._ - -import java.net.URLClassLoader - -final class CompilerInterface { - def newCompiler(options: Array[String], output: Output, initialLog: xsbti.Logger, - initialDelegate: xsbti.Reporter): CachedCompiler = { - // The classloader that sbt uses to load the compiler bridge is broken - // (see CompilerClassLoader#fixBridgeLoader for details). To workaround - // this we construct our own ClassLoader and then run the following code - // with it: - // new CachedCompilerImpl(options, output) - - val bridgeLoader = getClass.getClassLoader - val fixedLoader = CompilerClassLoader.fixBridgeLoader(bridgeLoader) - val cciClass = fixedLoader.loadClass("xsbt.CachedCompilerImpl") - cciClass.getConstructors.head - .newInstance(options, output) - .asInstanceOf[CachedCompiler] - } - - def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, - delegate: Reporter, progress: CompileProgress, cached: CachedCompiler): Unit = - cached.run(sources, changes, callback, log, delegate, progress) -} - -class CachedCompilerImpl(args: Array[String], output: Output) extends CachedCompiler { - val outputArgs = - output match { - case multi: MultipleOutput => - ??? - case single: SingleOutput => - List("-d", single.getOutputDirectory.getAbsolutePath.toString) - } - - def commandArguments(sources: Array[File]): Array[String] = - (outputArgs ++ args.toList ++ sources.map(_.getAbsolutePath).sortWith(_ < _)).toArray[String] - - def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, progress: CompileProgress): Unit = synchronized { - run(sources.toList, changes, callback, log, delegate, progress) - } - private[this] def run(sources: List[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, compileProgress: CompileProgress): Unit = { - log.debug(() => args.mkString("Calling Dotty compiler with arguments (CompilerInterface):\n\t", "\n\t", "")) - val ctx = (new ContextBase).initialCtx.fresh - .setSbtCallback(callback) - .setReporter(new DelegatingReporter(delegate)) - - val cl = getClass.getClassLoader.asInstanceOf[URLClassLoader] - - val reporter = DottyMain.process(commandArguments(sources.toArray), ctx) - if (reporter.hasErrors) { - throw new InterfaceCompileFailed(args, Array()) - } - } -} - -class InterfaceCompileFailed(override val arguments: Array[String], override val problems: Array[xsbti.Problem]) extends xsbti.CompileFailed { - override val toString = "Compilation failed" -} diff --git a/sbt-bridge/src/xsbt/ConsoleInterface.java b/sbt-bridge/src/xsbt/ConsoleInterface.java new file mode 100644 index 000000000000..3467e6abc226 --- /dev/null +++ b/sbt-bridge/src/xsbt/ConsoleInterface.java @@ -0,0 +1,55 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt; + +import java.util.ArrayList; + +import scala.Some; + +import xsbti.Logger; + +import dotty.tools.dotc.core.Contexts.Context; +import dotty.tools.repl.ReplDriver; +import dotty.tools.repl.State; + +public class ConsoleInterface { + public String[] commandArguments(String[] args, String bootClasspathString, String classpathString, Logger log) { + return args; + } + + public void run( + String[] args, + String bootClasspathString, + String classpathString, + String initialCommands, + String cleanupCommands, + ClassLoader loader, + String[] bindNames, + Object[] bindValues, + Logger log + ) { + ArrayList completeArgsList = new ArrayList<>(); + for (String arg : args) + completeArgsList.add(arg); + if (!bootClasspathString.isEmpty()) { + completeArgsList.add("-bootclasspath"); + completeArgsList.add(bootClasspathString); + } + completeArgsList.add("-classpath"); + completeArgsList.add(classpathString); + String[] completeArgs = completeArgsList.toArray(args); + + ReplDriver driver = new ReplDriver(completeArgs, System.out, Some.apply(loader)); + + State state = driver.initialState(); + assert bindNames.length == bindValues.length; + for (int i = 0; i < bindNames.length; i++) + state = driver.bind(bindNames[i], bindValues[i], state); + + state = driver.run(initialCommands, state); + // TODO handle failure during initialisation + state = driver.runUntilQuit(state); + driver.run(cleanupCommands, state); + } +} diff --git a/sbt-bridge/src/xsbt/ConsoleInterface.scala b/sbt-bridge/src/xsbt/ConsoleInterface.scala deleted file mode 100644 index 34004528fea4..000000000000 --- a/sbt-bridge/src/xsbt/ConsoleInterface.scala +++ /dev/null @@ -1,47 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2008, 2009 Mark Harrah - */ -package xsbt - -import xsbti.Logger - -import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.repl.ReplDriver - -class ConsoleInterface { - def commandArguments( - args: Array[String], - bootClasspathString: String, - classpathString: String, - log: Logger - ): Array[String] = args - - def run(args: Array[String], - bootClasspathString: String, - classpathString: String, - initialCommands: String, - cleanupCommands: String, - loader: ClassLoader, - bindNames: Array[String], - bindValues: Array[Any], - log: Logger - ): Unit = { - val completeArgs = - args ++ { - if (bootClasspathString.isEmpty) Array.empty[String] - else Array("-bootclasspath", bootClasspathString) - } ++ - Array("-classpath", classpathString) - - val driver = new ReplDriver(completeArgs, classLoader = Some(loader)) - - val s0 = (bindNames, bindValues).zipped.foldLeft(driver.initialState) { - case (state, (name, value)) => driver.bind(name, value)(state) - } - - val s1 = driver.run(initialCommands)(s0) - // TODO handle failure during initialisation - val s2 = driver.runUntilQuit(s1) - driver.run(cleanupCommands)(s2) - } -} diff --git a/sbt-bridge/src/xsbt/DelegatingReporter.java b/sbt-bridge/src/xsbt/DelegatingReporter.java new file mode 100644 index 000000000000..98671a9aff24 --- /dev/null +++ b/sbt-bridge/src/xsbt/DelegatingReporter.java @@ -0,0 +1,129 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt; + +import java.util.Optional; + +import xsbti.Position; +import xsbti.Severity; + +import dotty.tools.*; +import dotty.tools.dotc.*; +import dotty.tools.dotc.interfaces.Diagnostic; +import dotty.tools.dotc.util.SourceFile; +import dotty.tools.dotc.util.SourcePosition; +import dotty.tools.dotc.reporting.*; +import dotty.tools.dotc.reporting.diagnostic.Message; +import dotty.tools.dotc.reporting.diagnostic.MessageContainer; +import dotty.tools.dotc.reporting.diagnostic.messages; +import dotty.tools.dotc.core.Contexts.*; + +import static dotty.tools.dotc.reporting.diagnostic.MessageContainer.*; + +final public class DelegatingReporter extends AbstractReporter { + private final xsbti.Reporter delegate; + + private static final Position noPosition = new Position() { + public Optional sourceFile() { + return Optional.empty(); + } + public Optional sourcePath() { + return Optional.empty(); + } + public Optional line() { + return Optional.empty(); + } + public String lineContent() { + return ""; + } + public Optional offset() { + return Optional.empty(); + } + public Optional pointer() { + return Optional.empty(); + } + public Optional pointerSpace() { + return Optional.empty(); + } + }; + + public DelegatingReporter(xsbti.Reporter delegate) { + super(); + this.delegate = delegate; + } + + @Override + public void printSummary(Context ctx) { + delegate.printSummary(); + } + + public void doReport(MessageContainer cont, Context ctx) { + Severity severity; + switch (cont.level()) { + case Diagnostic.ERROR: + severity = Severity.Error; + break; + case Diagnostic.WARNING: + severity = Severity.Warn; + break; + case Diagnostic.INFO: + severity = Severity.Info; + break; + default: + throw new IllegalArgumentException("Bad diagnostic level: " + cont.level()); + } + + Position position; + if (cont.pos().exists()) { + SourcePosition pos = cont.pos(); + SourceFile src = pos.source(); + position = new Position() { + public Optional sourceFile() { + return Optional.ofNullable(src.file().file()); + } + public Optional sourcePath() { + return Optional.ofNullable(src.file().path()); + } + public Optional line() { + return Optional.of(pos.line()); + } + public String lineContent() { + String line = pos.lineContent(); + if (line.endsWith("\r\n")) + return line.substring(0, line.length() - 2); + else if (line.endsWith("\n") || line.endsWith("\u000c")) + return line.substring(0, line.length() - 1); + else + return line; + } + public Optional offset() { + return Optional.of(pos.point()); + } + public Optional pointer() { + return Optional.of(pos.point() - src.startOfLine(pos.point())); + } + public Optional pointerSpace() { + String lineContent = this.lineContent(); + int pointer = this.pointer().get(); + StringBuilder result = new StringBuilder(); + for (int i = 0; i < pointer; i++) + result.append(lineContent.charAt(i) == '\t' ? '\t' : ' '); + return Optional.of(result.toString()); + } + }; + } else { + position = noPosition; + } + + Message message = cont.contained(); + StringBuilder rendered = new StringBuilder(); + rendered.append(messageAndPos(message, cont.pos(), diagnosticLevel(cont), ctx)); + boolean shouldExplain = new MessageContainer.MessageContext(ctx).shouldExplain(cont); + if (shouldExplain && !message.explanation().isEmpty()) { + rendered.append(explanation(message, ctx)); + } + + delegate.log(new Problem(position, message.msg(), severity, rendered.toString())); + } +} diff --git a/sbt-bridge/src/xsbt/DelegatingReporter.scala b/sbt-bridge/src/xsbt/DelegatingReporter.scala deleted file mode 100644 index d97af019837d..000000000000 --- a/sbt-bridge/src/xsbt/DelegatingReporter.scala +++ /dev/null @@ -1,73 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2008, 2009 Mark Harrah - */ -package xsbt - -import dotty.tools._ -import dotc._ -import reporting._ -import reporting.diagnostic.MessageContainer -import reporting.diagnostic.messages -import core.Contexts._ -import xsbti.{Position, Severity} -import java.util.Optional - -final class DelegatingReporter(delegate: xsbti.Reporter) extends Reporter - with UniqueMessagePositions - with HideNonSensicalMessages - with MessageRendering { - import MessageContainer._ - - override def printSummary(implicit ctx: Context): Unit = delegate.printSummary() - - def doReport(cont: MessageContainer)(implicit ctx: Context): Unit = { - val severity = - cont match { - case _: messages.Error => Severity.Error - case _: messages.Warning => Severity.Warn - case _ => Severity.Info - } - - val position = - if (cont.pos.exists) { - val pos = cont.pos - val src = pos.source - new Position { - val sourceFile: Optional[java.io.File] = maybe(Option(src.file.file)) - val sourcePath: Optional[String] = maybe(Option(src.file.path)) - val line: Optional[Integer] = Optional.of(pos.line) - val lineContent: String = pos.lineContent.stripLineEnd - val offset: Optional[Integer] = Optional.of(pos.point) - val pointer: Optional[Integer] = Optional.of(pos.point - src.startOfLine(pos.point)) - val pointerSpace: Optional[String] = Optional.of( - ((lineContent: Seq[Char]).take(pointer.get).map { case '\t' => '\t'; case x => ' ' }).mkString - ) - } - } else - noPosition - - val message = cont.contained() - val rendered = new StringBuilder() - rendered.append(messageAndPos(message, cont.pos, diagnosticLevel(cont))) - if (ctx.shouldExplain(cont) && message.explanation.nonEmpty) { - rendered.append(explanation(message)) - } - - delegate.log(Problem(position, message.msg, severity, rendered.toString)) - } - - private[this] def maybe[T](opt: Option[T]): Optional[T] = opt match { - case None => Optional.empty[T] - case Some(s) => Optional.of[T](s) - } - - private[this] val noPosition = new Position { - val line: Optional[Integer] = Optional.empty[Integer] - val lineContent: String = "" - val offset: Optional[Integer] = Optional.empty[Integer] - val pointer: Optional[Integer] = Optional.empty[Integer] - val pointerSpace: Optional[String] = Optional.empty[String] - val sourceFile: Optional[java.io.File] = Optional.empty[java.io.File] - val sourcePath: Optional[String] = Optional.empty[String] - } -} diff --git a/sbt-bridge/src/xsbt/DottydocRunner.java b/sbt-bridge/src/xsbt/DottydocRunner.java new file mode 100644 index 000000000000..48883ab13e3c --- /dev/null +++ b/sbt-bridge/src/xsbt/DottydocRunner.java @@ -0,0 +1,88 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt; + +import xsbti.Logger; +import xsbti.Severity; + +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.ArrayList; + +import dotty.tools.dotc.core.Contexts.Context; +import dotty.tools.dotc.core.Contexts.ContextBase; +import dotty.tools.dotc.reporting.Reporter; + +public class DottydocRunner { + private final String[] args0; + private final Logger log; + private final xsbti.Reporter delegate; + + public DottydocRunner(String[] args0, Logger log, xsbti.Reporter delegate) { + super(); + this.args0 = args0; + this.log = log; + this.delegate = delegate; + } + + public void run() { + log.debug(() -> { + StringBuilder msg = + new StringBuilder("Calling Dottydoc with arguments (ScaladocInterface):"); + for (String arg : args0) { + msg.append("\n\t"); + msg.append(arg); + } + return msg.toString(); + }); + + // When running with `-from-tasty`, remove the source files from arg list. + String[] args; + boolean fromTasty = false; + for (String arg : args0) { + if ("-from-tasty".equals(arg)) { + fromTasty = true; + break; + } + } + if (fromTasty) { + ArrayList excluded = new ArrayList<>(args0.length); + ArrayList retained = new ArrayList<>(args0.length); + for (String arg : args0) { + if ((arg.endsWith(".scala") || arg.endsWith(".java")) && Files.exists(Paths.get(arg))) + excluded.add(arg); + else + retained.add(arg); + } + log.debug(() -> { + StringBuilder msg = + new StringBuilder("Running `-from-tasty`, excluding source files:"); + for (String arg : excluded) { + msg.append("\n\t"); + msg.append(arg); + } + return msg.toString(); + }); + args = retained.toArray(args0); + } else { + args = args0; + } + + Context ctx = new ContextBase().initialCtx().fresh() + .setReporter(new DelegatingReporter(delegate)); + + try { + Class dottydocMainClass = Class.forName("dotty.tools.dottydoc.Main"); + Method processMethod = dottydocMainClass.getMethod("process", args.getClass(), Context.class); // args.getClass() is String[] + Reporter reporter = (Reporter) processMethod.invoke(null, args, ctx); + if (reporter.hasErrors()) + throw new InterfaceCompileFailed(args, new xsbti.Problem[0]); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } +} diff --git a/sbt-bridge/src/xsbt/InterfaceCompileFailed.java b/sbt-bridge/src/xsbt/InterfaceCompileFailed.java new file mode 100644 index 000000000000..1bc8056b053d --- /dev/null +++ b/sbt-bridge/src/xsbt/InterfaceCompileFailed.java @@ -0,0 +1,30 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt; + +import xsbti.Problem; + +public class InterfaceCompileFailed extends xsbti.CompileFailed { + private final String[] _arguments; + private final Problem[] _problems; + + public InterfaceCompileFailed(String[] arguments, Problem[] problems) { + super(); + this._arguments = arguments; + this._problems = problems; + } + + public String[] arguments() { + return _arguments; + } + + public Problem[] problems() { + return _problems; + } + + @Override + public String toString() { + return "Compilation failed"; + } +} diff --git a/sbt-bridge/src/xsbt/Problem.java b/sbt-bridge/src/xsbt/Problem.java new file mode 100644 index 000000000000..7594476a29cf --- /dev/null +++ b/sbt-bridge/src/xsbt/Problem.java @@ -0,0 +1,45 @@ +package xsbt; + +import java.util.Optional; +import xsbti.Position; +import xsbti.Severity; + +final public class Problem implements xsbti.Problem { + private final Position _position; + private final String _message; + private final Severity _severity; + private final Optional _rendered; + + public Problem(Position position, String message, Severity severity, String rendered) { + super(); + this._position = position; + this._message = message; + this._severity = severity; + this._rendered = Optional.of(rendered); + } + + public String category() { + return ""; + } + + public Position position() { + return _position; + } + + public String message() { + return _message; + } + + public Severity severity() { + return _severity; + } + + public Optional rendered() { + return _rendered; + } + + @Override + public String toString() { + return "Problem(" + _position + ", " + _message + ", " + _severity + ", " + _rendered + ")"; + } +} diff --git a/sbt-bridge/src/xsbt/Problem.scala b/sbt-bridge/src/xsbt/Problem.scala deleted file mode 100644 index 991dbc2bf308..000000000000 --- a/sbt-bridge/src/xsbt/Problem.scala +++ /dev/null @@ -1,12 +0,0 @@ -package xsbt - -import java.util.Optional -import xsbti.{Position, Severity} - -final case class Problem(override val position: Position, - override val message: String, - override val severity: Severity, - rendered0: String) extends xsbti.Problem { - override val category = "" - override val rendered = Optional.of(rendered0) -} diff --git a/sbt-bridge/src/xsbt/ScaladocInterface.java b/sbt-bridge/src/xsbt/ScaladocInterface.java new file mode 100644 index 000000000000..7b47a7feb1aa --- /dev/null +++ b/sbt-bridge/src/xsbt/ScaladocInterface.java @@ -0,0 +1,13 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt; + +import xsbti.Logger; +import xsbti.Reporter; + +public class ScaladocInterface { + public void run(String[] args, Logger log, xsbti.Reporter delegate) { + new DottydocRunner(args, log, delegate).run(); + } +} diff --git a/sbt-bridge/src/xsbt/ScaladocInterface.scala b/sbt-bridge/src/xsbt/ScaladocInterface.scala deleted file mode 100644 index 09e785f3c4a6..000000000000 --- a/sbt-bridge/src/xsbt/ScaladocInterface.scala +++ /dev/null @@ -1,46 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2008, 2009 Mark Harrah - */ -package xsbt - -import xsbti.{ Logger, Severity } -import java.net.URL -import java.util.Optional -import java.nio.file.{Files, Paths} - -import dotty.tools.dotc.core.Contexts.{ Context, ContextBase } -import dotty.tools.dotc.reporting.Reporter - -class ScaladocInterface { - def run(args: Array[String], log: Logger, delegate: xsbti.Reporter) = { - new DottydocRunner(args, log, delegate).run() - } -} - -class DottydocRunner(args0: Array[String], log: Logger, delegate: xsbti.Reporter) { - def run(): Unit = { - log.debug(() => args0.mkString("Calling Dottydoc with arguments (ScaladocInterface):\n\t", "\n\t", "")) - - val args = { - // When running with `-from-tasty`, remove the source files from arg list. - if (args0.contains("-from-tasty")) { - val (excluded, retained) = - args0.partition { arg => - (arg.endsWith(".scala") || arg.endsWith(".java")) && Files.exists(Paths.get(arg)) - } - log.debug(() => excluded.mkString("Running `-from-tasty`, excluding source files:\n\t", "\n\t", "")) - retained - } else args0 - } - - val ctx = (new ContextBase).initialCtx.fresh - .setReporter(new DelegatingReporter(delegate)) - - val dottydocMainClass = Class.forName("dotty.tools.dottydoc.Main") - val processMethod = dottydocMainClass.getMethod("process", classOf[Array[String]], classOf[Context]) - val reporter = processMethod.invoke(null, args, ctx).asInstanceOf[Reporter] - if (reporter.hasErrors) { - throw new InterfaceCompileFailed(args, Array()) - } - } -} diff --git a/sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala b/sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala index fcd52a3ff815..593867b58b31 100644 --- a/sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala +++ b/sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala @@ -163,14 +163,21 @@ object DottyPlugin extends AutoPlugin { inc }, - scalaCompilerBridgeSource := { - val scalaBridge = scalaCompilerBridgeSource.value - val dottyBridge = (scalaOrganization.value % "dotty-sbt-bridge" % scalaVersion.value).withConfigurations(Some(Configurations.Compile.name)).sources() - if (isDotty.value) - dottyBridge - else - scalaBridge - }, + scalaCompilerBridgeBinaryJar := Def.taskDyn { + if (isDotty.value) Def.task { + val dottyBridgeArtifacts = fetchArtifactsOf("dotty-sbt-bridge", CrossVersion.disabled).value + val jars = dottyBridgeArtifacts.filter(art => art.getName.startsWith("dotty-sbt-bridge") && art.getName.endsWith(".jar")).toArray + if (jars.size == 0) + throw new MessageOnlyException("No jar found for dotty-sbt-bridge") + else if (jars.size > 1) + throw new MessageOnlyException(s"Multiple jars found for dotty-sbt-bridge: ${jars.toList}") + else + jars.headOption + } + else Def.task { + None: Option[File] + } + }.value, // Needed for RCs publishing scalaBinaryVersion := { @@ -184,7 +191,7 @@ object DottyPlugin extends AutoPlugin { val si = scalaInstance.value if (isDotty.value) { Def.task { - val dottydocArtifacts = fetchArtifactsOf("dotty-doc").value + val dottydocArtifacts = fetchArtifactsOf("dotty-doc", CrossVersion.binary).value val includeArtifact = (f: File) => f.getName.endsWith(".jar") val dottydocJars = dottydocArtifacts.filter(includeArtifact).toArray val allJars = (si.allJars ++ dottydocJars).distinct @@ -229,15 +236,15 @@ object DottyPlugin extends AutoPlugin { scalacOptions += "-from-tasty" )) - /** Fetch artefacts for scalaOrganization.value %% moduleName % scalaVersion.value */ - private def fetchArtifactsOf(moduleName: String) = Def.task { + /** Fetch artifacts for scalaOrganization.value %% moduleName % scalaVersion.value */ + private def fetchArtifactsOf(moduleName: String, crossVersion: CrossVersion) = Def.task { val dependencyResolution = Keys.dependencyResolution.value val log = streams.value.log val scalaInfo = scalaModuleInfo.value val updateConfiguration = Keys.updateConfiguration.value val warningConfiguration = (unresolvedWarningConfiguration in update).value - val moduleID = (scalaOrganization.value %% moduleName % scalaVersion.value).cross(CrossVersion.binary) + val moduleID = (scalaOrganization.value % moduleName % scalaVersion.value).cross(crossVersion) val descriptor = dependencyResolution.wrapDependencyInModule(moduleID, scalaInfo) dependencyResolution.update(descriptor, updateConfiguration, warningConfiguration, log) match {