diff --git a/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ComponentCompiler.scala b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ComponentCompiler.scala index 4248a6246..9b2ec6d39 100644 --- a/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ComponentCompiler.scala +++ b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ComponentCompiler.scala @@ -6,9 +6,8 @@ package internal package inc import java.io.File -import scala.util.Try import sbt.io.{ Hash, IO } -import sbt.internal.librarymanagement._ +import sbt.internal.librarymanagement.{ IvyConfiguration, JsonUtil, IvySbt, InlineConfiguration, RetrieveConfiguration, IvyActions, UnresolvedWarningConfiguration, LogicalClock } import sbt.librarymanagement.{ Configurations, ModuleID, ModuleInfo, Resolver, UpdateConfiguration, UpdateLogging, UpdateOptions, ArtifactTypeFilter } import sbt.util.Logger import sbt.internal.util.{ BufferedLogger, CacheStore, FullLogger } @@ -19,7 +18,7 @@ private[sbt] object ComponentCompiler { val binSeparator = "-bin_" val javaVersion = System.getProperty("java.class.version") - def interfaceProvider(manager: ComponentManager, ivyConfiguration: IvyConfiguration, fileToStore: File => CacheStore, sourcesModule: ModuleID): CompilerBridgeProvider = new CompilerBridgeProvider { + def interfaceProvider(manager: ZincComponentManager, ivyConfiguration: IvyConfiguration, fileToStore: File => CacheStore, sourcesModule: ModuleID): CompilerBridgeProvider = new CompilerBridgeProvider { def apply(scalaInstance: xsbti.compile.ScalaInstance, log: Logger): File = { // this is the instance used to compile the interface component @@ -43,10 +42,8 @@ private[sbt] object ComponentCompiler { * The compiled classes are cached using the provided component manager according * to the actualVersion field of the RawCompiler. */ -private[inc] class IvyComponentCompiler(compiler: RawCompiler, manager: ComponentManager, ivyConfiguration: IvyConfiguration, fileToStore: File => CacheStore, sourcesModule: ModuleID, log: Logger) { +private[inc] class IvyComponentCompiler(compiler: RawCompiler, manager: ZincComponentManager, ivyConfiguration: IvyConfiguration, fileToStore: File => CacheStore, sourcesModule: ModuleID, log: Logger) { import ComponentCompiler._ - - private val sbtOrg = xsbti.ArtifactInfo.SbtOrganization // private val xsbtiInterfaceModuleName = "compiler-interface" // private val xsbtiInterfaceID = s"interface-$incrementalVersion" private val sbtOrgTemp = JsonUtil.sbtOrgTemp @@ -57,7 +54,7 @@ private[inc] class IvyComponentCompiler(compiler: RawCompiler, manager: Componen def apply(): File = { // binID is of the form "org.example-compilerbridge-1.0.0-bin_2.11.7__50.0" val binID = binaryID(s"${sourcesModule.organization}-${sourcesModule.name}-${sourcesModule.revision}") - manager.file(binID)(new IfMissing.Define(true, compileAndInstall(binID))) + manager.file(binID)(IfMissing.define(true, compileAndInstall(binID))) } private def binaryID(id: String): String = { @@ -82,7 +79,6 @@ private[inc] class IvyComponentCompiler(compiler: RawCompiler, manager: Componen val (sources, xsbtiJars) = allArtifacts partition (_.getName endsWith "-sources.jar") AnalyzingCompiler.compileSources(sources, targetJar, xsbtiJars, sourcesModule.name, compiler, log) manager.define(binID, Seq(targetJar)) - } } 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..61c0cbc41 --- /dev/null +++ b/internal/zinc-ivy-integration/src/main/scala/sbt/internal/inc/ZincComponentManager.scala @@ -0,0 +1,121 @@ +package sbt +package internal +package inc + +import java.io.File +import java.util.concurrent.Callable +import sbt.util.Logger +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], val log: Logger) { + /** 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 fromSecondary = + lockSecondaryCache { + update(id) + getOrElse(createAndCache) + } getOrElse notFound + def getOrElse(orElse: => Iterable[File]): Iterable[File] = + { + val existing = provider.component(id) + if (existing.isEmpty) orElse + else existing + } + def notFound = invalid("Could not find required component '" + id + "'") + 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) + } + lockLocalCache { getOrElse(fromSecondary) } + } + /** 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 }) + /** 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(", ")) + } + private def invalid(msg: String) = throw new InvalidComponent(msg) + + def define(id: String, files: Iterable[File]) = lockLocalCache { provider.defineComponent(id, files.toSeq.toArray) } + /** Retrieve the file for component 'id' from the secondary cache. */ + private def update(id: String): Unit = + { + secondaryCacheDir map { 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) + } +} +class InvalidComponent(msg: String, cause: Throwable) extends RuntimeException(msg, cause) { + def this(msg: String) = this(msg, null) +} +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 + } +} +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/test/scala/sbt/internal/inc/BridgeProviderSpecification.scala b/internal/zinc-ivy-integration/src/test/scala/sbt/internal/inc/BridgeProviderSpecification.scala index 869cb04f1..323f238d6 100644 --- a/internal/zinc-ivy-integration/src/test/scala/sbt/internal/inc/BridgeProviderSpecification.scala +++ b/internal/zinc-ivy-integration/src/test/scala/sbt/internal/inc/BridgeProviderSpecification.scala @@ -6,7 +6,7 @@ import java.util.Properties import java.util.concurrent.Callable import sbt.internal.inc.classpath.ClasspathUtilities -import sbt.internal.librarymanagement.{ JsonUtil, ComponentManager } +import sbt.internal.librarymanagement.JsonUtil import sbt.io.IO import sbt.io.syntax._ import sbt.librarymanagement.{ ModuleID, UpdateOptions, Resolver, Patterns, FileRepository, DefaultMavenRepository } @@ -27,13 +27,20 @@ abstract class BridgeProviderSpecification extends BaseIvySpecification { override def resolvers: Vector[Resolver] = Vector(realLocal, DefaultMavenRepository) private val ivyConfiguration = mkIvyConfiguration(UpdateOptions()) + def secondaryCacheDirectory: File = + { + val target = file("target").getAbsoluteFile + target / "zinc-components" + } + def secondaryCacheOpt: Option[File] = Some(secondaryCacheDirectory) + def getCompilerBridge(targetDir: File, log: Logger, scalaVersion: String): File = { val instance = scalaInstance(scalaVersion) val bridgeId = compilerBridgeId(scalaVersion) val sourceModule = ModuleID(xsbti.ArtifactInfo.SbtOrganization, bridgeId, ComponentCompiler.incrementalVersion).withConfigurations(Some("component")).sources() val raw = new RawCompiler(instance, ClasspathOptionsUtil.auto, log) - val manager = new ComponentManager(lock, provider(targetDir), None, log) + val manager = new ZincComponentManager(lock, provider(targetDir), secondaryCacheOpt, log) val componentCompiler = new IvyComponentCompiler(raw, manager, ivyConfiguration, fileToStore, sourceModule, log) val bridge = componentCompiler.apply()