mirror of https://github.com/sbt/sbt.git
Migrate ComponentManager from librarymanagement
This migrates the `ComponentManager` from librarymanagement, and refactors it to clarify the double caching of the compiler bridge. The main purpose of this refactoring is to fix sbt/sbt#2539. As it stands, becaue Ivy cache directory is being used as the secondary cache of the compiled compiler bridge, it's annoying to work with the compiler bridge. Instead of using "Ivy cache", the new `ZincComponentManager` accepts `secondaryCacheDir: Option[File]`, which during test defaults to `target/zinc-components`. Fixes sbt/sbt#2539
This commit is contained in:
parent
c75a69bbb0
commit
818cee0156
|
|
@ -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))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue