mirror of https://github.com/sbt/sbt.git
Synchronize caches
This commit is contained in:
parent
e88214efbd
commit
1d40e5e71d
|
|
@ -20,21 +20,18 @@ class ComponentCompiler(compiler: RawCompiler, manager: ComponentManager)
|
|||
def apply(id: String): File =
|
||||
{
|
||||
val binID = binaryID(id)
|
||||
try { manager.file(binID) }
|
||||
catch { case e: InvalidComponent => compileAndInstall(id, binID) }
|
||||
manager.file(binID)( new IfMissing.Define(true, compileAndInstall(id, binID)) )
|
||||
}
|
||||
def clearCache(id: String): Unit = manager.clearCache(binaryID(id))
|
||||
protected def binaryID(id: String) = id + binSeparator + compiler.scalaInstance.actualVersion
|
||||
protected def compileAndInstall(id: String, binID: String): File =
|
||||
protected def compileAndInstall(id: String, binID: String)
|
||||
{
|
||||
val srcID = id + srcExtension
|
||||
withTemporaryDirectory { binaryDirectory =>
|
||||
val targetJar = new File(binaryDirectory, id + ".jar")
|
||||
compileSources(manager.files(srcID), targetJar, id)
|
||||
compileSources(manager.files(srcID)(IfMissing.Fail), targetJar, id)
|
||||
manager.define(binID, Seq(targetJar))
|
||||
manager.cache(binID)
|
||||
}
|
||||
manager.file(binID)
|
||||
}
|
||||
/** Extract sources from source jars, compile them with the xsbti interfaces on the classpath, and package the compiled classes and
|
||||
* any resources from the source jars into a final jar.*/
|
||||
|
|
@ -45,7 +42,7 @@ class ComponentCompiler(compiler: RawCompiler, manager: ComponentManager)
|
|||
val extractedSources = (Set[File]() /: sourceJars) { (extracted, sourceJar)=> extracted ++ unzip(sourceJar, dir) }
|
||||
val (sourceFiles, resources) = extractedSources.partition(_.getName.endsWith(".scala"))
|
||||
withTemporaryDirectory { outputDirectory =>
|
||||
val xsbtiJars = manager.files(xsbtiID)
|
||||
val xsbtiJars = manager.files(xsbtiID)(IfMissing.Fail)
|
||||
manager.log.info("'" + id + "' not yet compiled for Scala " + compiler.scalaInstance.actualVersion + ". Compiling...")
|
||||
try { compiler(Set() ++ sourceFiles, Set() ++ xsbtiJars, outputDirectory, Nil, true) }
|
||||
catch { case e: xsbti.CompileFailed => throw new CompileFailed(e.arguments, "Error compiling sbt component '" + id + "'") }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package xsbt
|
||||
|
||||
import java.io.File
|
||||
import java.io.{File,FileOutputStream}
|
||||
import ComponentManager.lock
|
||||
|
||||
/** A component manager provides access to the pieces of xsbt that are distributed as components.
|
||||
* There are two types of components. The first type is compiled subproject jars with their dependencies.
|
||||
|
|
@ -14,33 +15,73 @@ import java.io.File
|
|||
class ComponentManager(provider: xsbti.ComponentProvider, val log: IvyLogger) extends NotNull
|
||||
{
|
||||
/** Get all of the files for component 'id', throwing an exception if no files exist for the component. */
|
||||
def files(id: String): Iterable[File] =
|
||||
def files(id: String)(ifMissing: IfMissing): Iterable[File] =
|
||||
{
|
||||
val existing = provider.component(id)
|
||||
val fs = if(existing.isEmpty) { update(id); provider.component(id) } else existing
|
||||
if(!fs.isEmpty) fs else invalid("Could not find required component '" + id + "'")
|
||||
def fromGlobal =
|
||||
lockGlobalCache {
|
||||
try { update(id); getOrElse(createAndCache) }
|
||||
catch { case e: NotInCache => createAndCache }
|
||||
}
|
||||
def getOrElse(orElse: => 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()
|
||||
if(d.cache) cache(id)
|
||||
getOrElse(notFound)
|
||||
}
|
||||
|
||||
lockLocalCache { getOrElse(fromGlobal) }
|
||||
}
|
||||
private def lockLocalCache[T](action: => T): T = lock("local cache", provider.lockFile, log) ( action )
|
||||
private def lockGlobalCache[T](action: => T): T = lock("global cache", IvyCache.lockFile, log)( action )
|
||||
/** Get the file for component 'id', throwing an exception if no files or multiple files exist for the component. */
|
||||
def file(id: String): File =
|
||||
files(id).toList match {
|
||||
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)
|
||||
private def invalid(e: NotInCache) = throw new InvalidComponent(e.getMessage, e)
|
||||
|
||||
def define(id: String, files: Iterable[File]) = provider.defineComponent(id, files.toSeq.toArray)
|
||||
def define(id: String, files: Iterable[File]) = lockLocalCache { provider.defineComponent(id, files.toSeq.toArray) }
|
||||
/** Retrieve the file for component 'id' from the local repository. */
|
||||
def update(id: String): Unit =
|
||||
try { IvyCache.withCachedJar(sbtModuleID(id), log)(jar => define(id, Seq(jar)) ) }
|
||||
catch { case e: NotInCache => invalid(e) }
|
||||
private def update(id: String): Unit = IvyCache.withCachedJar(sbtModuleID(id), log)(jar => define(id, Seq(jar)) )
|
||||
|
||||
def sbtModuleID(id: String) = ModuleID("org.scala-tools.sbt", id, xsbti.Versions.Sbt)
|
||||
private def sbtModuleID(id: String) = ModuleID("org.scala-tools.sbt", id, xsbti.Versions.Sbt)
|
||||
/** Install the files for component 'id' to the local repository. This is usually used after writing files to the directory returned by 'location'. */
|
||||
def cache(id: String): Unit = IvyCache.cacheJar(sbtModuleID(id), file(id), log)
|
||||
def clearCache(id: String): Unit = IvyCache.clearCachedJar(sbtModuleID(id), log)
|
||||
def cache(id: String): Unit = IvyCache.cacheJar(sbtModuleID(id), file(id)(IfMissing.Fail), log)
|
||||
def clearCache(id: String): Unit = lockGlobalCache { IvyCache.clearCachedJar(sbtModuleID(id), log) }
|
||||
}
|
||||
class InvalidComponent(msg: String, cause: Throwable) extends RuntimeException(msg, cause)
|
||||
{
|
||||
def this(msg: String) = this(msg, null)
|
||||
}
|
||||
sealed trait IfMissing extends NotNull
|
||||
object IfMissing
|
||||
{
|
||||
object Fail extends IfMissing
|
||||
final class Define(val cache: Boolean, define: => Unit) extends IfMissing { def apply() = define }
|
||||
}
|
||||
object ComponentManager
|
||||
{
|
||||
def lock[T](label: String, file: File, log: IvyLogger)(action: => T): T =
|
||||
{
|
||||
synchronized {
|
||||
val channel = new FileOutputStream(file).getChannel
|
||||
try {
|
||||
val freeLock = channel.tryLock
|
||||
val lock = if(freeLock eq null) { log.info("Waiting for " + label + " to be available..."); channel.lock } else freeLock
|
||||
try { action }
|
||||
finally { lock.release() }
|
||||
}
|
||||
finally { channel.close() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ private object NotInCache
|
|||
/** Provides methods for working at the level of a single jar file with the default Ivy cache.*/
|
||||
object IvyCache
|
||||
{
|
||||
def lockFile = new File(System.getProperty("temp.dir"), "sbt.cache.lock")
|
||||
/** Caches the given 'file' with the given ID. It may be retrieved or cleared using this ID.*/
|
||||
def cacheJar(moduleID: ModuleID, file: File, log: IvyLogger)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,47 +4,48 @@ import java.io.File
|
|||
import org.specs._
|
||||
import FileUtilities.{createDirectory, delete, touch, withTemporaryDirectory}
|
||||
import org.apache.ivy.util.ChecksumHelper
|
||||
import IfMissing.Fail
|
||||
|
||||
object ComponentManagerTest extends Specification
|
||||
{
|
||||
val TestID = "manager-test"
|
||||
"Component manager" should {
|
||||
"throw an exception if 'file' is called for a non-existing component" in {
|
||||
withManager { _.file(TestID) must throwA[InvalidComponent] }
|
||||
withManager { _.file(TestID)(Fail) must throwA[InvalidComponent] }
|
||||
}
|
||||
"throw an exception if 'file' is called for an empty component" in {
|
||||
withManager { manager =>
|
||||
manager.define(TestID, Nil)
|
||||
( manager.file(TestID) ) must throwA[InvalidComponent]
|
||||
( manager.file(TestID)(Fail) ) must throwA[InvalidComponent]
|
||||
}
|
||||
}
|
||||
"return the file for a single-file component" in {
|
||||
withManager { manager =>
|
||||
val hash = defineFile(manager, TestID, "a")
|
||||
checksum(manager.file(TestID)) must beEqualTo(hash)
|
||||
checksum(manager.file(TestID)(Fail)) must beEqualTo(hash)
|
||||
}
|
||||
}
|
||||
|
||||
"throw an exception if 'file' is called for multi-file component" in {
|
||||
withManager { manager =>
|
||||
defineFiles(manager, TestID, "a", "b")
|
||||
( manager.file(TestID) ) must throwA[InvalidComponent]
|
||||
( manager.file(TestID)(Fail) ) must throwA[InvalidComponent]
|
||||
}
|
||||
}
|
||||
"return the files for a multi-file component" in {
|
||||
withManager { manager =>
|
||||
val hashes = defineFiles(manager, TestID, "a", "b")
|
||||
checksum(manager.files(TestID)) must haveTheSameElementsAs(hashes)
|
||||
checksum(manager.files(TestID)(Fail)) must haveTheSameElementsAs(hashes)
|
||||
}
|
||||
}
|
||||
"return the files for a single-file component" in {
|
||||
withManager { manager =>
|
||||
val hashes = defineFiles(manager, TestID, "a")
|
||||
checksum(manager.files(TestID)) must haveTheSameElementsAs(hashes)
|
||||
checksum(manager.files(TestID)(Fail)) must haveTheSameElementsAs(hashes)
|
||||
}
|
||||
}
|
||||
"throw an exception if 'files' is called for a non-existing component" in {
|
||||
withManager { _.files(TestID) must throwA[InvalidComponent] }
|
||||
withManager { _.files(TestID)(Fail) must throwA[InvalidComponent] }
|
||||
}
|
||||
|
||||
"properly cache a file and then retrieve it to an unresolved component" in {
|
||||
|
|
@ -54,7 +55,7 @@ object ComponentManagerTest extends Specification
|
|||
{
|
||||
definingManager.cache(TestID)
|
||||
withManager { usingManager =>
|
||||
checksum(usingManager.file(TestID)) must beEqualTo(hash)
|
||||
checksum(usingManager.file(TestID)(Fail)) must beEqualTo(hash)
|
||||
}
|
||||
}
|
||||
finally { definingManager.clearCache(TestID) }
|
||||
|
|
|
|||
|
|
@ -106,4 +106,9 @@ class ComponentProvider(baseDirectory: File) extends xsbti.ComponentProvider
|
|||
else
|
||||
Copy(files, location)
|
||||
}
|
||||
def lockFile =
|
||||
{
|
||||
baseDirectory.mkdirs()
|
||||
new File(baseDirectory, "sbt.components.lock")
|
||||
}
|
||||
}
|
||||
|
|
@ -6,4 +6,5 @@ public interface ComponentProvider
|
|||
{
|
||||
public File[] component(String componentID);
|
||||
public void defineComponent(String componentID, File[] components);
|
||||
public File lockFile();
|
||||
}
|
||||
Loading…
Reference in New Issue