From 8daaf9ea178f42cdd92a7a3caed8721b95e10741 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Thu, 4 Feb 2010 18:56:07 -0500 Subject: [PATCH] Cut down launcher jar size a bit --- launch/Configuration.scala | 1 + launch/ConfigurationParser.scala | 3 +- launch/Launch.scala | 6 ++-- launch/Pre.scala | 10 ++++++ launch/Provider.scala | 9 +++--- launch/Update.scala | 2 +- launch/src/test/scala/LocksTest.scala | 46 +++++++++++++++++++++++++-- launch/src/test/scala/PreTest.scala | 6 ++++ 8 files changed, 72 insertions(+), 11 deletions(-) diff --git a/launch/Configuration.scala b/launch/Configuration.scala index 0435ba482..06ae72a5e 100644 --- a/launch/Configuration.scala +++ b/launch/Configuration.scala @@ -35,6 +35,7 @@ object Configuration if(exists) Some(resolved.toURL) else None } val against = resolveAgainst(baseDirectory) + // use Iterators so that resolution occurs lazily, for performance val resolving = against.elements.flatMap(e => resolve(e).toList.elements) if(!resolving.hasNext) multiPartError("Could not find configuration file '" + path + "'. Searched:", against) resolving.next() diff --git a/launch/ConfigurationParser.scala b/launch/ConfigurationParser.scala index 438a77b5c..20eb6895c 100644 --- a/launch/ConfigurationParser.scala +++ b/launch/ConfigurationParser.scala @@ -1,5 +1,6 @@ package xsbt.boot + import Pre._ import java.lang.Character.isWhitespace import java.io.{BufferedReader, File, FileInputStream, InputStreamReader, Reader, StringReader} @@ -102,7 +103,7 @@ class ConfigurationParser extends NotNull val (crossVersioned, m6) = id(m5, "cross-versioned", "true") val (resources, m7) = ids(m6, "resources", Nil) check(m7, "label") - val classpathExtra = toFiles(resources).toArray[File] + val classpathExtra = toArray(toFiles(resources)) new Application(org, name, rev, main, components, toBoolean(crossVersioned), classpathExtra) } def getRepositories(m: LabelMap): List[Repository] = diff --git a/launch/Launch.scala b/launch/Launch.scala index e08d5bd1d..ca49d8326 100644 --- a/launch/Launch.scala +++ b/launch/Launch.scala @@ -92,13 +92,13 @@ class Launch(val bootDirectory: File, repositories: List[Repository], scalaClass lazy val scalaHome = new File(libDirectory, ScalaDirectoryName) def compilerJar = new File(scalaHome,CompilerModuleName + ".jar") def libraryJar = new File(scalaHome, LibraryModuleName + ".jar") - override def classpath = Array(compilerJar, libraryJar) + override def classpath = array(compilerJar, libraryJar) def baseDirectories = List(scalaHome) def testLoadClasses = TestLoadScalaClasses def target = new UpdateScala(scalaClassifiers) def failLabel = "Scala " + version def lockFile = updateLockFile - def extraClasspath = Array[File]() + def extraClasspath = array() def app(id: xsbti.ApplicationID): xsbti.AppProvider = new AppProvider(id) @@ -113,7 +113,7 @@ class Launch(val bootDirectory: File, repositories: List[Repository], scalaClass def target = new UpdateApp(Application(id)) def failLabel = id.name + " " + id.version def lockFile = updateLockFile - def mainClasspath = classpath ++ extraClasspath + def mainClasspath = fullClasspath def extraClasspath = id.classpathExtra lazy val mainClass: Class[T] forSome { type T <: xsbti.AppMain } = diff --git a/launch/Pre.scala b/launch/Pre.scala index 11ca3d856..12c51edc1 100644 --- a/launch/Pre.scala +++ b/launch/Pre.scala @@ -28,4 +28,14 @@ object Pre copy(0, list) arr } + /* These exist in order to avoid bringing in dependencies on RichInt and ArrayBuffer, among others. */ + import java.io.File + def concat(a: Array[File], b: Array[File]): Array[File] = + { + val n = new Array[File](a.length + b.length) + java.lang.System.arraycopy(a, 0, n, 0, a.length) + java.lang.System.arraycopy(b, 0, n, a.length, b.length) + n + } + def array(files: File*): Array[File] = toArray(files.toList) } diff --git a/launch/Provider.scala b/launch/Provider.scala index e579da147..8a5055e8e 100644 --- a/launch/Provider.scala +++ b/launch/Provider.scala @@ -16,7 +16,8 @@ trait Provider extends NotNull def parentLoader: ClassLoader def lockFile: File - def classpath = Provider.getJars(baseDirectories) + def classpath: Array[File] = Provider.getJars(baseDirectories) + def fullClasspath:Array[File] = concat(classpath, extraClasspath) def retrieveFailed: Nothing = fail("") def retrieveCorrupt(missing: Iterable[String]): Nothing = fail(": missing " + missing.mkString(", ")) @@ -47,8 +48,8 @@ trait Provider extends NotNull } def createLoader = { - val fullClasspath = classpath ++ extraClasspath - (fullClasspath, new URLClassLoader(Provider.toURLs(fullClasspath), parentLoader) ) + val full = fullClasspath + (full, new URLClassLoader(Provider.toURLs(full), parentLoader) ) } } } @@ -56,7 +57,7 @@ trait Provider extends NotNull object Provider { def getJars(directories: List[File]): Array[File] = toArray(directories.flatMap(directory => wrapNull(directory.listFiles(JarFilter)))) - def wrapNull(a: Array[File]): Array[File] = if(a == null) Array() else a + def wrapNull(a: Array[File]): Array[File] = if(a == null) new Array[File](0) else a object JarFilter extends FileFilter { diff --git a/launch/Update.scala b/launch/Update.scala index 398da6f35..1f0aeff0a 100644 --- a/launch/Update.scala +++ b/launch/Update.scala @@ -92,7 +92,7 @@ final class Update(config: UpdateConfiguration) // the actual module id here is not that important val moduleID = new DefaultModuleDescriptor(createID(SbtOrg, "boot-" + target.tpe, "1.0"), "release", null, false) moduleID.setLastModified(System.currentTimeMillis) - moduleID.addConfiguration(new IvyConfiguration(DefaultIvyConfiguration, PUBLIC, "", Array(), true, null)) + moduleID.addConfiguration(new IvyConfiguration(DefaultIvyConfiguration, PUBLIC, "", new Array(0), true, null)) // add dependencies based on which target needs updating target match { diff --git a/launch/src/test/scala/LocksTest.scala b/launch/src/test/scala/LocksTest.scala index 626df0bbc..16fc93bcf 100644 --- a/launch/src/test/scala/LocksTest.scala +++ b/launch/src/test/scala/LocksTest.scala @@ -4,11 +4,53 @@ import org.scalacheck._ import Prop._ import java.io.File +/** These mainly test that things work in the uncontested case and that no OverlappingFileLockExceptions occur. +* There is no real locking testing, just the coordination of locking.*/ object LocksTest extends Properties("Locks") { - property("Lock in nonexisting directory") = + property("Lock in nonexisting directory") = spec { FileUtilities.withTemporaryDirectory { dir => val lockFile = new File(dir, "doesntexist/lock") - Locks(lockFile, new java.util.concurrent.Callable[Boolean] { def call = true }) + Locks(lockFile, callTrue) } + } + + property("Uncontested re-entrant lock") = spec { + FileUtilities.withTemporaryDirectory { dir => + val lockFile = new File(dir, "lock") + Locks(lockFile, callLocked(lockFile)) && + Locks(lockFile, callLocked(lockFile)) + } + } + + property("Uncontested double lock") = spec { + FileUtilities.withTemporaryDirectory { dir => + val lockFileA = new File(dir, "lockA") + val lockFileB = new File(dir, "lockB") + Locks(lockFileA, callLocked(lockFileB)) && + Locks(lockFileB, callLocked(lockFileA)) + } + } + + property("Contested single lock") = spec { + FileUtilities.withTemporaryDirectory { dir => + val lockFile = new File(dir, "lock") + forkFold(2000){i => Locks(lockFile, callTrue) } + } + } + + private def spec(f: => Boolean): Prop = Prop { _ => Result(if(f) True else False) } + + private def call[T](impl: => T) = new java.util.concurrent.Callable[T] { def call = impl } + private def callLocked(lockFile: File) = call { Locks(lockFile, callTrue) } + private lazy val callTrue = call { true } + + private def forkFold(n: Int)(impl: Int => Boolean): Boolean = + (true /: forkWait(n)(impl))(_ && _) + private def forkWait(n: Int)(impl: Int => Boolean): Iterable[Boolean] = + { + import scala.concurrent.ops.future + val futures = (0 until n).map { i => future { impl(i) } } + futures.toList.map(_()) + } } \ No newline at end of file diff --git a/launch/src/test/scala/PreTest.scala b/launch/src/test/scala/PreTest.scala index 6cdbc9e9a..76d9beec8 100644 --- a/launch/src/test/scala/PreTest.scala +++ b/launch/src/test/scala/PreTest.scala @@ -1,5 +1,6 @@ package xsbt.boot +import java.io.File import org.scalacheck._ object PreTest extends Properties("Pre") @@ -17,6 +18,11 @@ object PreTest extends Properties("Pre") property("toBoolean") = Prop.forAll( (s: String) => trap(toBoolean(s)) == trap(java.lang.Boolean.parseBoolean(s)) ) property("toArray") = Prop.forAll( (list: List[Int]) => list.toArray deepEquals toArray(list) ) property("toArray") = Prop.forAll( (list: List[String]) => list.toArray deepEquals toArray(list) ) + property("concat") = Prop.forAll(genFiles, genFiles) { (a: Array[File], b: Array[File]) => (a ++ b) sameElements concat(a, b) } + property("array") = Prop.forAll(genFiles) { (a: Array[File]) => array(a.toList : _*) sameElements Array(a: _*) } + + implicit lazy val arbFile: Arbitrary[File] = Arbitrary { for(i <- Arbitrary.arbitrary[Int] ) yield new File(i.toString) } + implicit lazy val genFiles: Gen[Array[File]] = Arbitrary.arbitrary[Array[File]] def trap[T](t: => T): Option[T] = try { Some(t) } catch { case e: Exception => None } } \ No newline at end of file