From 62a5037d23d7ba798eac978a1bf8f1e6da9e1b10 Mon Sep 17 00:00:00 2001 From: jvican Date: Fri, 19 May 2017 14:42:43 +0200 Subject: [PATCH] Add `getScalaInstance` to `CompilerBridgeProvider` --- .../main/scala/sbt/internal/inc/Errors.scala | 23 +++ .../sbt/internal/inc/InvalidComponent.scala | 12 -- .../sbt/internal/inc/ResourceLoader.scala | 9 + .../internal/inc/ZincComponentCompiler.scala | 167 ++++++++++++------ 4 files changed, 142 insertions(+), 69 deletions(-) create mode 100644 internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/Errors.scala delete mode 100644 internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/InvalidComponent.scala diff --git a/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/Errors.scala b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/Errors.scala new file mode 100644 index 000000000..c3c5e8a7f --- /dev/null +++ b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/Errors.scala @@ -0,0 +1,23 @@ +/* + * Zinc - The incremental compiler for Scala. + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * This software is released under the terms written in LICENSE. + */ + +package sbt.internal.inc + +class InvalidComponent(msg: String, cause: Throwable) extends RuntimeException(msg, cause) { + def this(msg: String) = this(msg, null) +} + +final class MissingScalaJar(msg: String, cause: Throwable) extends RuntimeException(msg, cause) { + def this(msg: String) = this(msg, null) +} + +object MissingScalaJar { + def missingTemplate(missing: String): String = + s"The $missing could not be found in your cache nor downloaded from the Internet." + def compiler: MissingScalaJar = new MissingScalaJar(missingTemplate("Scala compiler")) + def library: MissingScalaJar = new MissingScalaJar(missingTemplate("Scala library")) +} diff --git a/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/InvalidComponent.scala b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/InvalidComponent.scala deleted file mode 100644 index df0505abc..000000000 --- a/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/InvalidComponent.scala +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Zinc - The incremental compiler for Scala. - * Copyright 2011 - 2017, Lightbend, Inc. - * Copyright 2008 - 2010, Mark Harrah - * This software is released under the terms written in LICENSE. - */ - -package sbt.internal.inc - -class InvalidComponent(msg: String, cause: Throwable) extends RuntimeException(msg, cause) { - def this(msg: String) = this(msg, null) -} diff --git a/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ResourceLoader.scala b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ResourceLoader.scala index 4399747dc..78e05b6c2 100644 --- a/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ResourceLoader.scala +++ b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ResourceLoader.scala @@ -17,4 +17,13 @@ private[inc] object ResourceLoader { try { properties.load(propertiesStream) } finally { propertiesStream.close() } properties } + + def getSafePropertiesFor(resource: String, classLoader: ClassLoader): Properties = { + val properties = new Properties + val propertiesStream = classLoader.getResourceAsStream(resource) + try { properties.load(propertiesStream) } catch { case _: Exception => } finally { + if (propertiesStream ne null) propertiesStream.close() + } + properties + } } diff --git a/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala index 70ea5da24..bb266260f 100644 --- a/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala +++ b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala @@ -11,32 +11,17 @@ package inc import java.io.File +import sbt.internal.inc.classpath.ClasspathUtilities import sbt.io.{ Hash, IO } -import sbt.internal.librarymanagement.{ - InlineConfiguration, - IvyActions, - IvyConfiguration, - IvySbt, - JsonUtil, - LogicalClock, - RetrieveConfiguration, - UnresolvedWarning, - UnresolvedWarningConfiguration -} -import sbt.librarymanagement.{ - ArtifactTypeFilter, - Configurations, - ModuleID, - ModuleInfo, - Resolver, - UpdateConfiguration, - UpdateLogging, - UpdateOptions -} +import sbt.internal.librarymanagement._ +import sbt.internal.util.FullLogger +import sbt.librarymanagement._ import sbt.librarymanagement.syntax._ import sbt.util.{ InterfaceUtil, Logger } import xsbti.compile.CompilerBridgeProvider +import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader + private[sbt] object ZincComponentCompiler { final val binSeparator = "-bin_" final val javaClassVersion = System.getProperty("java.class.version") @@ -59,11 +44,60 @@ private[sbt] object ZincComponentCompiler { override def getCompiledBridge(scalaInstance: xsbti.compile.ScalaInstance, logger: xsbti.Logger): File = { val autoClasspath = ClasspathOptionsUtil.auto - val bridgeCompiler = new RawCompiler(scalaInstance, autoClasspath, logger) - val ivyComponent = - new ZincComponentCompiler(bridgeCompiler, manager, ivyConfiguration, bridgeModule, logger) + val raw = new RawCompiler(scalaInstance, autoClasspath, logger) + val zinc = new ZincComponentCompiler(raw, manager, ivyConfiguration, bridgeModule, logger) logger.debug(InterfaceUtil.f0(s"Getting $bridgeModule for Scala ${scalaInstance.version}")) - ivyComponent.getCompiledBridgeJar + zinc.getCompiledBridgeJar + } + + private final case class ScalaArtifacts(compiler: File, library: File, others: Vector[File]) + + private def getScalaArtifacts(scalaVersion: String, logger: xsbti.Logger): ScalaArtifacts = { + def isPrefixedWith(artifact: File, prefix: String) = artifact.getName.startsWith(prefix) + + import xsbti.ArtifactInfo._ + import Configurations.Compile + import UnresolvedWarning.unresolvedWarningLines + val fullLogger = new FullLogger(logger) + val CompileConf = Some(Compile.name) + val dummyModule = ModuleID(JsonUtil.sbtOrgTemp, s"tmp-scala-$scalaVersion", scalaVersion) + val scalaLibrary = ModuleID(ScalaOrganization, ScalaLibraryID, scalaVersion) + val scalaCompiler = ModuleID(ScalaOrganization, ScalaCompilerID, scalaVersion) + val dependencies = Vector(scalaLibrary, scalaCompiler).map(_.withConfigurations(CompileConf)) + val wrapper = dummyModule.withConfigurations(CompileConf) + val ivySbt: IvySbt = new IvySbt(ivyConfiguration) + val ivyModule = ZincIvyActions.getModule(ivySbt, wrapper, dependencies) + IO.withTemporaryDirectory { retrieveDirectory => + ZincIvyActions.update(ivyModule, retrieveDirectory, fullLogger) match { + case Left(uw) => + val unresolvedLines = unresolvedWarningLines.showLines(uw) + val unretrievedMessage = s"The Scala compiler and library could not be retrieved." + throw new InvalidComponent(s"$unretrievedMessage\n$unresolvedLines") + case Right(allArtifacts) => + val isScalaCompiler = (f: File) => isPrefixedWith(f, "scala-compiler-") + val isScalaLibrary = (f: File) => isPrefixedWith(f, "scala-library-") + val maybeScalaCompiler = allArtifacts.find(isScalaCompiler) + val maybeScalaLibrary = allArtifacts.find(isScalaLibrary) + val others = allArtifacts.filterNot(a => isScalaCompiler(a) || isScalaLibrary(a)) + val scalaCompiler = maybeScalaCompiler.getOrElse(throw MissingScalaJar.compiler) + val scalaLibrary = maybeScalaLibrary.getOrElse(throw MissingScalaJar.library) + ScalaArtifacts(scalaCompiler, scalaLibrary, others) + } + } + } + + override def getScalaInstance(scalaVersion: String, + logger: xsbti.Logger): xsbti.compile.ScalaInstance = { + import sbt.io.Path.toURLs + val scalaArtifacts = getScalaArtifacts(scalaVersion, logger) + val scalaCompiler = scalaArtifacts.compiler + val scalaLibrary = scalaArtifacts.library + val jarsToLoad = (scalaCompiler +: scalaLibrary +: scalaArtifacts.others).toArray + val loader = new URLClassLoader(toURLs(jarsToLoad), ClasspathUtilities.rootLoader) + val properties = ResourceLoader.getSafePropertiesFor("compiler.properties", loader) + val loaderVersion = Option(properties.getProperty("version.number")) + val scalaV = loaderVersion.getOrElse("unknown") + new ScalaInstance(scalaV, loader, scalaLibrary, scalaCompiler, jarsToLoad, loaderVersion) } } @@ -117,6 +151,19 @@ private[inc] class ZincComponentCompiler( s"$id$binSeparator${scalaVersion}__$javaClassVersion" } + /** + * Returns an ivy module that will wrap and download a given `moduleID`. + * + * @param moduleID The `moduleID` that needs to be wrapped in a dummy module to be downloaded. + */ + private[inc] def wrapDependencyInModule(moduleID: ModuleID): IvySbt#Module = { + import ZincComponentCompiler.{ sbtOrgTemp, modulePrefixTemp } + val sha1 = Hash.toHex(Hash(moduleID.name)) + val dummyID = ModuleID(sbtOrgTemp, s"$modulePrefixTemp$sha1", moduleID.revision) + .withConfigurations(moduleID.configurations) + ZincIvyActions.getModule(ivySbt, dummyID, Vector(moduleID)) + } + /** * Resolves the compiler bridge sources, compiles them and installs the sbt component * in the local filesystem to make sure that it's reused the next time is required. @@ -130,7 +177,7 @@ private[inc] class ZincComponentCompiler( val target = new File(binaryDirectory, s"$compilerBridgeId.jar") buffered bufferQuietly { IO.withTemporaryDirectory { retrieveDirectory => - update(ivyModuleForBridge, retrieveDirectory) match { + ZincIvyActions.update(ivyModuleForBridge, retrieveDirectory, buffered) match { case Left(uw) => val mod = bridgeSources.toString val unresolvedLines = unresolvedWarningLines.showLines(uw) @@ -149,34 +196,39 @@ private[inc] class ZincComponentCompiler( } } - /** - * Returns an ivy module that will wrap and download a given `moduleID`. - * - * @param moduleID The `moduleID` that needs to be wrapped in a dummy module to be downloaded. - */ - private def wrapDependencyInModule(moduleID: ModuleID): ivySbt.Module = { - def getModule(moduleID: ModuleID, deps: Vector[ModuleID], uo: UpdateOptions): ivySbt.Module = { - val moduleInfo = ModuleInfo(moduleID.name) - val componentIvySettings = InlineConfiguration( - validate = false, - ivyScala = None, - module = moduleID, - moduleInfo = moduleInfo, - dependencies = deps - ).withConfigurations(Vector(Configurations.Component)) - new ivySbt.Module(componentIvySettings) - } +} - import ZincComponentCompiler.{ sbtOrgTemp, modulePrefixTemp } - val sha1 = Hash.toHex(Hash(moduleID.name)) - val dummyID = ModuleID(sbtOrgTemp, s"$modulePrefixTemp$sha1", moduleID.revision) - .withConfigurations(moduleID.configurations) - val defaultUpdateOptions = UpdateOptions() - getModule(dummyID, Vector(moduleID), defaultUpdateOptions) +object ZincIvyActions { + type IvyModule = IvySbt#Module + + /** Define the default configurations for a Zinc module wrapper. */ + private final val DefaultConfigurations: Vector[Configuration] = + Vector(Configurations.Component, Configurations.Compile) + private final val DefaultUpdateOptions = UpdateOptions() + + /** + * Create a module from its dummy wrapper and its dependencies. + * + * @param wrapper The ModuleID wrapper that will download the dependencies. + * @param deps The dependencies to be downloaded. + * @return A fully-fledged ivy module to be used for [[IvyActions]]. + */ + private[inc] def getModule(ivySbt: IvySbt, + wrapper: ModuleID, + deps: Vector[ModuleID]): IvyModule = { + val moduleInfo = ModuleInfo(wrapper.name) + val componentIvySettings = InlineConfiguration( + validate = false, + ivyScala = None, + module = wrapper, + moduleInfo = moduleInfo, + dependencies = deps + ).withConfigurations(DefaultConfigurations) + new ivySbt.Module(componentIvySettings) } // The implementation of this method is linked to `wrapDependencyInModule` - private def prettyPrintDependency(module: ivySbt.Module): String = { + private def prettyPrintDependency(module: IvyModule): String = { module.moduleSettings match { case ic: InlineConfiguration => // Pretty print the module as `ModuleIDExtra.toStringImpl` does. @@ -196,22 +248,23 @@ private[inc] class ZincComponentCompiler( UpdateConfiguration(Some(retrieve), missingOk = false, logLevel, noDocs) } - private def update(module: ivySbt.Module, - retrieveDirectory: File): Either[UnresolvedWarning, Vector[File]] = { + private[inc] def update(module: IvyModule, + retrieveDirectory: File, + logger: Logger): Either[UnresolvedWarning, Vector[File]] = { import IvyActions.updateEither val updateConfiguration = defaultUpdateConfiguration(retrieveDirectory) val dependencies = prettyPrintDependency(module) - buffered.info(s"Attempting to fetch $dependencies. This operation may fail.") + logger.info(s"Attempting to fetch $dependencies. This operation may fail.") val clockForCache = LogicalClock.unknown - updateEither(module, updateConfiguration, warningConf, clockForCache, None, buffered) match { + updateEither(module, updateConfiguration, warningConf, clockForCache, None, logger) match { case Left(unresolvedWarning) => - buffered.debug(s"Couldn't retrieve module ${prettyPrintDependency(module)}.") + logger.debug(s"Couldn't retrieve module ${prettyPrintDependency(module)}.") Left(unresolvedWarning) case Right(updateReport) => val allFiles = updateReport.allFiles - buffered.debug(s"Files retrieved for ${prettyPrintDependency(module)}:") - buffered.debug(allFiles mkString ", ") + logger.debug(s"Files retrieved for ${prettyPrintDependency(module)}:") + logger.debug(allFiles mkString ", ") Right(allFiles) } }