diff --git a/internal/zinc-ivy-integration/src/main/java/xsbti/compile/ZincBridgeProvider.java b/internal/zinc-ivy-integration/src/main/java/xsbti/compile/ZincBridgeProvider.java new file mode 100644 index 000000000..abf3be3d6 --- /dev/null +++ b/internal/zinc-ivy-integration/src/main/java/xsbti/compile/ZincBridgeProvider.java @@ -0,0 +1,80 @@ +/* + * 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 xsbti.compile; + +import sbt.internal.inc.ZincComponentCompiler; +import sbt.internal.inc.ZincComponentManager; +import sbt.librarymanagement.DependencyResolution; +import sbt.librarymanagement.Resolver; +import scala.None$; +import xsbti.ComponentProvider; +import xsbti.GlobalLock; +import xsbti.Logger; + +import java.io.File; + +public interface ZincBridgeProvider { + /** + * Returns an ivy resolver to resolve dependencies locally in the default `.ivy2/local`. + *
+ * For those users interested in using Internet resolvers like Maven Central, you can + * instantiate them via {@link Resolver#mavenCentral()} et al. + * + * @return A local ivy resolver. + */ + public static Resolver getLocalResolver() { + return ZincComponentCompiler.LocalResolver(); + } + + /** + * Returns a global lock that does nothing but calling the callable to synchronize + * across threads. The lock file is used to resolve and download dependencies via ivy. + *
+ * This operation is necessary to invoke {@link ZincBridgeProvider#getProvider(File, GlobalLock, ComponentProvider, IvyConfiguration, Logger)}. + * + * @return A default global lock. + */ + public static GlobalLock getDefaultLock() { + return ZincComponentCompiler.getDefaultLock(); + } + + /** + * Returns a default component provider that retrieves and installs component managers + * (like the compiled bridge sources) under a given target directory. + *
+ * This is the most simplistic implementation of a component provider. If you need more + * advanced feature, like management of component via proxies (for companies) or access to + * other servers, you need to implement your own component provider. + * + * @param componentsRoot The directory in which components will be installed and retrieved. + * @return A default component provider. + */ + public static ComponentProvider getDefaultComponentProvider(File componentsRoot) { + return ZincComponentCompiler.getDefaultComponentProvider(componentsRoot); + } + + /** + * Get a compiler bridge provider that allows the user to fetch Scala and a compiled bridge. + * + * @param scalaJarsTarget The place where the downloaded Scala jars should be placed. + * @param lock The lock file used internally by Ivy to synchronize the dependency resolution. + * @param componentProvider A provider capable of retrieving existing components or installing + * new ones. The component provider manages compiled bridge sources. + * @param dependencyResolution The library management module to use to retrieve the bridge. + * @param logger The logger. + * @return A compiler bridge provider capable of fetching scala jars and the compiler bridge. + */ + public static CompilerBridgeProvider getProvider(File scalaJarsTarget, + GlobalLock lock, + ComponentProvider componentProvider, + DependencyResolution dependencyResolution, + Logger logger) { + ZincComponentManager manager = new ZincComponentManager(lock, componentProvider, None$.empty(), logger); + return ZincComponentCompiler.interfaceProvider(manager, dependencyResolution, scalaJarsTarget); + } +} 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/IfMissing.scala b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/IfMissing.scala new file mode 100644 index 000000000..1936799a0 --- /dev/null +++ b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/IfMissing.scala @@ -0,0 +1,21 @@ +/* + * 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 + +sealed trait IfMissing + +object IfMissing { + def fail: IfMissing = Fail + + /** f is expected to call ZincComponentManager.define. */ + def define(useSecondaryCache: Boolean, f: => Unit): IfMissing = new Define(useSecondaryCache, f) + object Fail extends IfMissing + final class Define(val useSecondaryCache: Boolean, define: => Unit) extends IfMissing { + def run(): Unit = define + } +} 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 new file mode 100644 index 000000000..78e05b6c2 --- /dev/null +++ b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ResourceLoader.scala @@ -0,0 +1,29 @@ +/* + * 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 + +import java.util.Properties + +/** Defines utilities to load Java properties from the JVM. */ +private[inc] object ResourceLoader { + def getPropertiesFor(resource: String, classLoader: ClassLoader): Properties = { + val properties = new java.util.Properties + val propertiesStream = getClass.getResource(resource).openStream + 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 new file mode 100644 index 000000000..0e1f73024 --- /dev/null +++ b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala @@ -0,0 +1,339 @@ +/* + * 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 +package internal +package inc + +import java.io.File +import java.util.concurrent.Callable + +import sbt.internal.inc.classpath.ClasspathUtilities +import sbt.io.IO +import sbt.internal.librarymanagement._ +import sbt.internal.util.FullLogger +import sbt.librarymanagement._ +import sbt.librarymanagement.syntax._ +import sbt.util.{ InterfaceUtil, Logger } +import xsbti.{ ComponentProvider, GlobalLock } +import xsbti.compile.{ ClasspathOptionsUtil, CompilerBridgeProvider } + +private[sbt] object ZincComponentCompiler { + final val binSeparator = "-bin_" + final val javaClassVersion = System.getProperty("java.class.version") + + private[inc] final val sbtOrgTemp = JsonUtil.sbtOrgTemp + private[inc] final val modulePrefixTemp = "temp-module-" + + private final val ZincVersionPropertyFile = "/incrementalcompiler.version.properties" + private final val ZincVersionProperty = "version" + private[sbt] final lazy val incrementalVersion: String = { + val cl = this.getClass.getClassLoader + ResourceLoader.getPropertiesFor(ZincVersionPropertyFile, cl).getProperty(ZincVersionProperty) + } + + private val CompileConf = Some(Configurations.Compile.name) + private[sbt] def getDefaultBridgeModule(scalaVersion: String): ModuleID = { + def compilerBridgeId(scalaVersion: String) = { + scalaVersion match { + case sc if (sc startsWith "2.10.") => "compiler-bridge_2.10" + case sc if (sc startsWith "2.11.") => "compiler-bridge_2.11" + case sc if (sc startsWith "2.12.") => "compiler-bridge_2.12" + case "2.13.0-M1" => "compiler-bridge_2.12" + case _ => "compiler-bridge_2.13" + } + } + import xsbti.ArtifactInfo.SbtOrganization + val bridgeId = compilerBridgeId(scalaVersion) + ModuleID(SbtOrganization, bridgeId, incrementalVersion) + .withConfigurations(CompileConf) + .sources() + } + + /** Defines the internal implementation of a bridge provider. */ + private class ZincCompilerBridgeProvider( + userProvidedBridgeSources: Option[ModuleID], + manager: ZincComponentManager, + dependencyResolution: DependencyResolution, + scalaJarsTarget: File + ) extends CompilerBridgeProvider { + + /** + * Defines a richer interface for Scala users that want to pass in an explicit module id. + * + * Note that this method cannot be defined in [[CompilerBridgeProvider]] because [[ModuleID]] + * is a Scala-defined class to which the compiler bridge cannot depend on. + */ + def compiledBridge(bridgeSources: ModuleID, + scalaInstance: xsbti.compile.ScalaInstance, + logger: xsbti.Logger): File = { + import InterfaceUtil.{ toSupplier => f0 } + val autoClasspath = ClasspathOptionsUtil.auto + val raw = new RawCompiler(scalaInstance, autoClasspath, logger) + val zinc = + new ZincComponentCompiler(raw, manager, dependencyResolution, bridgeSources, logger) + logger.debug(f0(s"Getting $bridgeSources for Scala ${scalaInstance.version}")) + zinc.compiledBridgeJar + } + + override def fetchCompiledBridge(scalaInstance: xsbti.compile.ScalaInstance, + logger: xsbti.Logger): File = { + val scalaVersion = scalaInstance.actualVersion() + val bridgeSources = userProvidedBridgeSources getOrElse getDefaultBridgeModule(scalaVersion) + compiledBridge(bridgeSources, scalaInstance, logger) + } + + private 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 UnresolvedWarning.unresolvedWarningLines + val fullLogger = new FullLogger(logger) + val CompileConf = Some(Configurations.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 moduleDescriptorConfiguration = + ModuleDescriptorConfiguration(wrapper, ModuleInfo(wrapper.name)) + .withDependencies(dependencies) + .withConfigurations(ZincLMHelper.DefaultConfigurations) + + val moduleDescriptor = dependencyResolution.moduleDescriptor(moduleDescriptorConfiguration) + ZincLMHelper.update(dependencyResolution, + moduleDescriptor, + scalaJarsTarget, + noSource = true, + fullLogger) match { + case Left(uw) => + val unresolvedLines = unresolvedWarningLines.showLines(uw).mkString("\n") + 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 fetchScalaInstance(scalaVersion: String, + logger: xsbti.Logger): xsbti.compile.ScalaInstance = { + val scalaArtifacts = getScalaArtifacts(scalaVersion, logger) + val scalaCompiler = scalaArtifacts.compiler + val scalaLibrary = scalaArtifacts.library + val jarsToLoad = (scalaCompiler +: scalaLibrary +: scalaArtifacts.others).toArray + assert(jarsToLoad.forall(_.exists), "One or more jar(s) in the Scala instance do not exist.") + val loaderLibraryOnly = ClasspathUtilities.toLoader(Vector(scalaLibrary)) + val loader = ClasspathUtilities.toLoader(jarsToLoad.toVector filterNot { _ == scalaLibrary }, + loaderLibraryOnly) + val properties = ResourceLoader.getSafePropertiesFor("compiler.properties", loader) + val loaderVersion = Option(properties.getProperty("version.number")) + val scalaV = loaderVersion.getOrElse("unknown") + new ScalaInstance(scalaV, + loader, + loaderLibraryOnly, + scalaLibrary, + scalaCompiler, + jarsToLoad, + loaderVersion) + } + } + + // Used by ZincUtil. + def interfaceProvider(compilerBridgeSource: ModuleID, + manager: ZincComponentManager, + dependencyResolution: DependencyResolution, + scalaJarsTarget: File): CompilerBridgeProvider = + new ZincCompilerBridgeProvider(Some(compilerBridgeSource), + manager, + dependencyResolution, + scalaJarsTarget) + + def interfaceProvider(manager: ZincComponentManager, + dependencyResolution: DependencyResolution, + scalaJarsTarget: File): CompilerBridgeProvider = + new ZincCompilerBridgeProvider(None, manager, dependencyResolution, scalaJarsTarget) + + private final val LocalIvy = s"$${user.home}/.ivy2/local/${Resolver.localBasePattern}" + final val LocalResolver: Resolver = { + val toUse = Vector(LocalIvy) + val ivyPatterns = Patterns().withIsMavenCompatible(false) + val finalPatterns = ivyPatterns.withIvyPatterns(toUse).withArtifactPatterns(toUse) + FileRepository("local", Resolver.defaultFileConfiguration, finalPatterns) + } + + def getDefaultLock: GlobalLock = new GlobalLock { + override def apply[T](file: File, callable: Callable[T]): T = callable.call() + } + + /** Defines a default component provider that manages the component in a given directory. */ + private final class DefaultComponentProvider(targetDir: File) extends ComponentProvider { + import sbt.io.syntax._ + private val LockFile = targetDir / "lock" + override def lockFile(): File = LockFile + override def componentLocation(id: String): File = targetDir / id + override def component(componentID: String): Array[File] = + IO.listFiles(targetDir / componentID) + override def defineComponent(componentID: String, files: Array[File]): Unit = + files.foreach(f => IO.copyFile(f, targetDir / componentID / f.getName)) + override def addToComponent(componentID: String, files: Array[File]): Boolean = { + defineComponent(componentID, files) + true + } + } + + def getDefaultComponentProvider(targetDir: File): ComponentProvider = { + require(targetDir.isDirectory) + new DefaultComponentProvider(targetDir) + } + +} + +/** + * Component compiler which is able to to retrieve the compiler bridge sources + * `sourceModule` using a `DependencyResolution` instance. + * The compiled classes are cached using the provided component manager according + * to the actualVersion field of the RawCompiler. + */ +private[inc] class ZincComponentCompiler( + compiler: RawCompiler, + manager: ZincComponentManager, + dependencyResolution: DependencyResolution, + bridgeSources: ModuleID, + log: Logger +) { + import sbt.internal.util.{ BufferedLogger, FullLogger } + private final val buffered = new BufferedLogger(FullLogger(log)) + + def compiledBridgeJar: File = { + val jarBinaryName = createBridgeSourcesID(bridgeSources) + manager.file(jarBinaryName)(IfMissing.define(true, compileAndInstall(jarBinaryName))) + } + + /** + * Returns the id for the compiler interface component. + * + * The ID contains the following parts: + * - The organization, name and revision. + * - The bin separator to make clear the jar represents binaries. + * - The Scala version for which the compiler interface is meant to. + * - The JVM class version. + * + * Example: "org.scala-sbt-compiler-bridge-1.0.0-bin_2.11.7__50.0". + * + * @param sources The moduleID representing the compiler bridge sources. + * @return The complete jar identifier for the bridge sources. + */ + private def createBridgeSourcesID(sources: ModuleID): String = { + import ZincComponentCompiler.{ binSeparator, javaClassVersion } + val id = s"${sources.organization}-${sources.name}-${sources.revision}" + val scalaVersion = compiler.scalaInstance.actualVersion() + s"$id$binSeparator${scalaVersion}__$javaClassVersion" + } + + /** + * 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. + * + * @param compilerBridgeId The identifier for the compiler bridge sources. + */ + private def compileAndInstall(compilerBridgeId: String): Unit = { + import UnresolvedWarning.unresolvedWarningLines + val moduleForBridge = + dependencyResolution.wrapDependencyInModule(bridgeSources) + IO.withTemporaryDirectory { binaryDirectory => + val target = new File(binaryDirectory, s"$compilerBridgeId.jar") + buffered bufferQuietly { + IO.withTemporaryDirectory { retrieveDirectory => + ZincLMHelper.update(dependencyResolution, + moduleForBridge, + retrieveDirectory, + false, + buffered) match { + case Left(uw) => + val mod = bridgeSources.toString + val unresolvedLines = unresolvedWarningLines.showLines(uw).mkString("\n") + val unretrievedMessage = s"The compiler bridge sources $mod could not be retrieved." + throw new InvalidComponent(s"$unretrievedMessage\n$unresolvedLines") + + case Right(allArtifacts) => + val (srcs, xsbtiJars) = allArtifacts.partition(_.getName.endsWith("-sources.jar")) + val toCompileID = bridgeSources.name + AnalyzingCompiler.compileSources(srcs, target, xsbtiJars, toCompileID, compiler, log) + manager.define(compilerBridgeId, Seq(target)) + } + } + + } + } + } + +} + +private object ZincLMHelper { + + private final val warningConf = UnresolvedWarningConfiguration() + private final val defaultRetrievePattern = Resolver.defaultRetrievePattern + private[inc] final val DefaultConfigurations: Vector[Configuration] = + Vector(Configurations.Component, Configurations.Compile) + + private[inc] def update(dependencyResolution: DependencyResolution, + module: ModuleDescriptor, + retrieveDirectory: File, + noSource: Boolean = false, + logger: Logger): Either[UnresolvedWarning, Vector[File]] = { + val updateConfiguration = defaultUpdateConfiguration(retrieveDirectory, noSource) + val dependencies = prettyPrintDependency(module) + logger.info(s"Attempting to fetch $dependencies.") + dependencyResolution.update(module, updateConfiguration, warningConf, logger) match { + case Left(unresolvedWarning) => + logger.debug(s"Couldn't retrieve module(s) ${prettyPrintDependency(module)}.") + Left(unresolvedWarning) + + case Right(updateReport) => + val allFiles = updateReport.allFiles + logger.debug(s"Files retrieved for ${prettyPrintDependency(module)}:") + logger.debug(allFiles mkString ", ") + Right(allFiles) + } + } + + private def defaultUpdateConfiguration( + targetDir: File, + noSource: Boolean + ): UpdateConfiguration = { + val retrieve = RetrieveConfiguration() + .withRetrieveDirectory(targetDir) + .withOutputPattern(defaultRetrievePattern) + val logLevel = UpdateLogging.DownloadOnly + val defaultExcluded = Set("doc") + val finalExcluded = if (noSource) defaultExcluded + "src" else defaultExcluded + val artifactFilter = ArtifactTypeFilter.forbid(finalExcluded) + UpdateConfiguration() + .withRetrieveManaged(retrieve) + .withLogging(logLevel) + .withArtifactFilter(artifactFilter) + } + + private def prettyPrintDependency(module: ModuleDescriptor): String = { + module.directDependencies + .map { m => + // Pretty print the module as `ModuleIDExtra.toStringImpl` does. + s"${m.organization}:${m.name}:${m.revision}" + } + .mkString(", ") + } + +} diff --git a/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincComponentManager.scala b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincComponentManager.scala new file mode 100644 index 000000000..0db0011a3 --- /dev/null +++ b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincComponentManager.scala @@ -0,0 +1,131 @@ +/* + * 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 +package internal +package inc + +import java.io.File +import java.util.concurrent.Callable + +import sbt.internal.util.FullLogger +import sbt.io.IO + +/** + * A component manager provides access to the pieces of zinc that are distributed as components. + * Compiler bridge is distributed as a source jar so that it can be compiled against a specific + * version of Scala. + * + * The component manager provides services to install and retrieve components to the local filesystem. + * This is used for compiled source jars so that the compilation need not be repeated for other projects on the same + * machine. + */ +class ZincComponentManager(globalLock: xsbti.GlobalLock, + provider: xsbti.ComponentProvider, + secondaryCacheDir: Option[File], + log0: xsbti.Logger) { + val log = new FullLogger(log0) + + /** Get all of the files for component 'id', throwing an exception if no files exist for the component. */ + def files(id: String)(ifMissing: IfMissing): Iterable[File] = { + def notFound = invalid("Could not find required component '" + id + "'") + def getOrElse(orElse: => Iterable[File]): Iterable[File] = { + val existing = provider.component(id) + if (existing.isEmpty) orElse + else existing + } + + def createAndCache = { + ifMissing match { + case IfMissing.Fail => notFound + case d: IfMissing.Define => + d.run() // this is expected to have called define. + if (d.useSecondaryCache) { + cacheToSecondaryCache(id) + } + getOrElse(notFound) + } + } + + def fromSecondary: Iterable[File] = { + lockSecondaryCache { + update(id) + getOrElse(createAndCache) + }.getOrElse(notFound) + } + + lockLocalCache(getOrElse(fromSecondary)) + } + + /** Get the file for component 'id', throwing an exception if no files or multiple files exist for the component. */ + def file(id: String)(ifMissing: IfMissing): File = { + files(id)(ifMissing).toList match { + case x :: Nil => x + case xs => + invalid("Expected single file for component '" + id + "', found: " + xs.mkString(", ")) + } + } + + /** Associate a component id to a series of jars. */ + def define(id: String, files: Iterable[File]): Unit = + lockLocalCache(provider.defineComponent(id, files.toSeq.toArray)) + + /** This is used to lock the local cache in project/boot/. By checking the local cache first, we can avoid grabbing a global lock. */ + private def lockLocalCache[T](action: => T): T = lock(provider.lockFile)(action) + + /** This is used to ensure atomic access to components in the global Ivy cache.*/ + private def lockSecondaryCache[T](action: => T): Option[T] = + secondaryCacheDir map { dir => + val lockFile = new File(dir, ".sbt.cache.lock") + lock(lockFile)(action) + } + private def lock[T](file: File)(action: => T): T = + globalLock(file, new Callable[T] { def call = action }) + + private def invalid(msg: String) = throw new InvalidComponent(msg) + + /** Retrieve the file for component 'id' from the secondary cache. */ + private def update(id: String): Unit = { + secondaryCacheDir foreach { dir => + val file = seondaryCacheFile(id, dir) + if (file.exists) { + define(id, Seq(file)) + } + } + } + + /** Install the files for component 'id' to the secondary cache. */ + private def cacheToSecondaryCache(id: String): Unit = { + val fromPrimaryCache = file(id)(IfMissing.fail) + secondaryCacheDir match { + case Some(dir) => + val file = seondaryCacheFile(id, dir) + IO.copyFile(fromPrimaryCache, file) + case _ => () + } + () + } + private val sbtOrg = xsbti.ArtifactInfo.SbtOrganization + private def seondaryCacheFile(id: String, dir: File): File = { + val fileName = id + "-" + ZincComponentManager.stampedVersion + ".jar" + new File(new File(dir, sbtOrg), fileName) + } +} + +object ZincComponentManager { + lazy val (version, timestamp) = { + val properties = new java.util.Properties + val propertiesStream = versionResource.openStream + try { properties.load(propertiesStream) } finally { propertiesStream.close() } + (properties.getProperty("version"), properties.getProperty("timestamp")) + } + lazy val stampedVersion = version + "_" + timestamp + + import java.net.URL + private def versionResource: URL = + getClass.getResource("/incrementalcompiler.version.properties") +} diff --git a/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincLmUtil.scala b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincLmUtil.scala new file mode 100644 index 000000000..4c3e915d2 --- /dev/null +++ b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincLmUtil.scala @@ -0,0 +1,49 @@ +/* + * 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 + +import java.io.File +import java.net.URLClassLoader + +import sbt.librarymanagement.{ DependencyResolution, ModuleID } +import sbt.internal.inc.classpath.ClassLoaderCache +import xsbti._ +import xsbti.compile._ + +object ZincLmUtil { + import xsbti.compile.ScalaInstance + + /** + * Instantiate a Scala compiler that is instrumented to analyze dependencies. + * This Scala compiler is useful to create your own instance of incremental + * compilation. + */ + def scalaCompiler( + scalaInstance: ScalaInstance, + classpathOptions: ClasspathOptions, + globalLock: GlobalLock, + componentProvider: ComponentProvider, + secondaryCacheDir: Option[File], + dependencyResolution: DependencyResolution, + compilerBridgeSource: ModuleID, + scalaJarsTarget: File, + log: Logger + ): AnalyzingCompiler = { + val compilerBridgeProvider = ZincComponentCompiler.interfaceProvider( + compilerBridgeSource, + new ZincComponentManager(globalLock, componentProvider, secondaryCacheDir, log), + dependencyResolution, + scalaJarsTarget, + ) + val loader = Some(new ClassLoaderCache(new URLClassLoader(new Array(0)))) + new AnalyzingCompiler(scalaInstance, compilerBridgeProvider, classpathOptions, _ => (), loader) + } + + def getDefaultBridgeModule(scalaVersion: String): ModuleID = + ZincComponentCompiler.getDefaultBridgeModule(scalaVersion) +} diff --git a/internal/zinc-ivy-integration/src/test/scala/sbt/internal/inc/IvyBridgeProviderSpecification.scala b/internal/zinc-ivy-integration/src/test/scala/sbt/internal/inc/IvyBridgeProviderSpecification.scala new file mode 100644 index 000000000..d0e9c8339 --- /dev/null +++ b/internal/zinc-ivy-integration/src/test/scala/sbt/internal/inc/IvyBridgeProviderSpecification.scala @@ -0,0 +1,61 @@ +package sbt.internal.inc + +import java.io.File + +import sbt.io.syntax._ +import sbt.librarymanagement._ +import sbt.librarymanagement.ivy._ +import sbt.util.Logger +import xsbti.compile.CompilerBridgeProvider + +/** + * Base class for test suites that must be able to fetch and compile the compiler bridge. + * + * This is a very good example on how to instantiate the compiler bridge provider. + */ +abstract class IvyBridgeProviderSpecification extends UnitSpec with AbstractBridgeProviderTestkit { + def currentBase: File = new File(".") + def currentTarget: File = currentBase / "target" / "ivyhome" + def currentManaged: File = currentBase / "target" / "lib_managed" + + val resolvers = Array( + ZincComponentCompiler.LocalResolver, + Resolver.mavenCentral, + MavenRepository("scala-integration", + "https://scala-ci.typesafe.com/artifactory/scala-integration/") + ) + private val ivyConfiguration = + getDefaultConfiguration(currentBase, currentTarget, resolvers, log) + + def secondaryCacheDirectory: File = { + val target = file("target").getAbsoluteFile + target / "zinc-components" + } + + def getZincProvider(targetDir: File, log: Logger): CompilerBridgeProvider = { + val lock = ZincComponentCompiler.getDefaultLock + val secondaryCache = Some(secondaryCacheDirectory) + val componentProvider = ZincComponentCompiler.getDefaultComponentProvider(targetDir) + val manager = new ZincComponentManager(lock, componentProvider, secondaryCache, log) + val dependencyResolution = IvyDependencyResolution(ivyConfiguration) + ZincComponentCompiler.interfaceProvider(manager, dependencyResolution, currentManaged) + } + + private def getDefaultConfiguration(baseDirectory: File, + ivyHome: File, + resolvers0: Array[Resolver], + log: xsbti.Logger): InlineIvyConfiguration = { + import sbt.io.syntax._ + val resolvers = resolvers0.toVector + val chainResolver = ChainedResolver("zinc-chain", resolvers) + InlineIvyConfiguration() + .withPaths(IvyPaths(baseDirectory, Some(ivyHome))) + .withResolvers(resolvers) + .withModuleConfigurations(Vector(ModuleConfiguration("*", chainResolver))) + .withLock(None) + .withChecksums(Vector.empty) + .withResolutionCacheDir(ivyHome / "resolution-cache") + .withUpdateOptions(UpdateOptions()) + .withLog(log) + } +} diff --git a/internal/zinc-ivy-integration/src/test/scala/sbt/internal/inc/ZincComponentCompilerSpec.scala b/internal/zinc-ivy-integration/src/test/scala/sbt/internal/inc/ZincComponentCompilerSpec.scala new file mode 100644 index 000000000..75e04d8ff --- /dev/null +++ b/internal/zinc-ivy-integration/src/test/scala/sbt/internal/inc/ZincComponentCompilerSpec.scala @@ -0,0 +1,41 @@ +package sbt.internal.inc + +import sbt.internal.util.ConsoleLogger +import sbt.io.IO + +class ZincComponentCompilerSpec extends IvyBridgeProviderSpecification { + val scala2105 = "2.10.5" + val scala2106 = "2.10.6" + val scala2118 = "2.11.8" + val scala21111 = "2.11.11" + val scala2121 = "2.12.1" + val scala2122 = "2.12.2" + val scala2123 = "2.12.3" + val scala2130RC1 = "2.13.0-RC1" + def isJava8: Boolean = sys.props("java.specification.version") == "1.8" + + val logger = ConsoleLogger() + it should "compile the bridge for Scala 2.10.5 and 2.10.6" in { + if (isJava8) { + IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2105) should exist) + IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2106) should exist) + } else () + } + + it should "compile the bridge for Scala 2.11.8 and 2.11.11" in { + if (isJava8) { + IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2118) should exist) + IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala21111) should exist) + } else () + } + + it should "compile the bridge for Scala 2.12.2" in { + IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2121) should exist) + IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2122) should exist) + IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2123) should exist) + } + + it should "compile the bridge for Scala 2.13.0-RC1" in { + IO.withTemporaryDirectory(t => getCompilerBridge(t, logger, scala2130RC1) should exist) + } +}