diff --git a/launch/BootConfiguration.scala b/launch/BootConfiguration.scala index f2dda2d5d..3409d55d9 100644 --- a/launch/BootConfiguration.scala +++ b/launch/BootConfiguration.scala @@ -3,6 +3,8 @@ */ package xsbt.boot + import java.io.File + // // scala-/ [baseDirectoryName] // lib/ [ScalaDirectoryName] @@ -69,10 +71,20 @@ private object BootConfiguration * containing all jars for the requested version of scala. */ def appRetrievePattern(appID: xsbti.ApplicationID) = appDirectoryName(appID, "/") + "(/[component])/[artifact]-[revision](-[classifier]).[ext]" + val ScalaDirPrefix = "scala-" + /** The name of the directory to retrieve the application and its dependencies to.*/ def appDirectoryName(appID: xsbti.ApplicationID, sep: String) = appID.groupID + sep + appID.name + sep + appID.version /** The name of the directory in the boot directory to put all jars for the given version of scala in.*/ - def baseDirectoryName(scalaVersion: String) = if(scalaVersion.isEmpty) "other" else "scala-" + scalaVersion + def baseDirectoryName(scalaVersion: Option[String]) = scalaVersion match { + case None => "other" + case Some(sv) => ScalaDirPrefix + sv + } + def extractScalaVersion(dir: File): Option[String] = + { + val name = dir.getName + if(name.startsWith(ScalaDirPrefix)) Some(name.substring(ScalaDirPrefix.length)) else None + } } private object ProxyProperties { diff --git a/launch/Create.scala b/launch/Create.scala index 3434938ca..36a0f3dc9 100644 --- a/launch/Create.scala +++ b/launch/Create.scala @@ -56,7 +56,7 @@ object Initialize { case None => noValue case Some(line) => - val value = if(isEmpty(line)) prompt.default.getOrElse(noValue) else line + val value = if(isEmpty(line)) orElse(prompt.default, noValue) else line properties.setProperty(name, value) } } diff --git a/launch/Find.scala b/launch/Find.scala index 8539b58ec..7ee823431 100644 --- a/launch/Find.scala +++ b/launch/Find.scala @@ -38,7 +38,7 @@ class Find(config: LaunchConfiguration) } case _ => Some(current) } - val baseDirectory = found.getOrElse(current) + val baseDirectory = orElse(found, current) System.setProperty("user.dir", baseDirectory.getAbsolutePath) (ResolvePaths(config, baseDirectory), baseDirectory) } diff --git a/launch/Launch.scala b/launch/Launch.scala index 18154cf36..c7e6d64ba 100644 --- a/launch/Launch.scala +++ b/launch/Launch.scala @@ -6,8 +6,10 @@ package xsbt.boot import Pre._ import BootConfiguration.{CompilerModuleName, LibraryModuleName} import java.io.File -import java.net.URL +import java.net.{URL, URLClassLoader} +import java.util.concurrent.Callable import scala.collection.immutable.List +import scala.annotation.tailrec object Launch { @@ -46,8 +48,7 @@ object Launch def run(launcher: xsbti.Launcher)(config: RunConfiguration): xsbti.MainResult = { import config._ - val scalaProvider: xsbti.ScalaProvider = launcher.getScala(scalaVersion, "(for " + app.name + ")") - val appProvider: xsbti.AppProvider = scalaProvider.app(app) + val appProvider: xsbti.AppProvider = launcher.app(app, orNull(scalaVersion)) // takes ~40 ms when no update is required val appConfig: xsbti.AppConfiguration = new AppConfiguration(toArray(arguments), workingDirectory, appProvider) val main = appProvider.newMain() @@ -69,87 +70,208 @@ object Launch { case e: xsbti.Exit => Some(e.code) case c: xsbti.Continue => None - case r: xsbti.Reboot => launch(run)(new RunConfiguration(r.scalaVersion, r.app, r.baseDirectory, r.arguments.toList)) + case r: xsbti.Reboot => launch(run)(new RunConfiguration(Option(r.scalaVersion), r.app, r.baseDirectory, r.arguments.toList)) case x => throw new BootException("Invalid main result: " + x + (if(x eq null) "" else " (class: " + x.getClass + ")")) } } } -final class RunConfiguration(val scalaVersion: String, val app: xsbti.ApplicationID, val workingDirectory: File, val arguments: List[String]) +final class RunConfiguration(val scalaVersion: Option[String], val app: xsbti.ApplicationID, val workingDirectory: File, val arguments: List[String]) -import BootConfiguration.{appDirectoryName, baseDirectoryName, ScalaDirectoryName, TestLoadScalaClasses} +import BootConfiguration.{appDirectoryName, baseDirectoryName, extractScalaVersion, ScalaDirectoryName, TestLoadScalaClasses} class Launch private[xsbt](val bootDirectory: File, val lockBoot: Boolean, val ivyOptions: IvyOptions) extends xsbti.Launcher { import ivyOptions.{checksums => checksumsList, classifiers, repositories} bootDirectory.mkdirs - private val scalaProviders = new Cache[String, String, ScalaProvider](new ScalaProvider(_, _)) + private val scalaProviders = new Cache[String, String, xsbti.ScalaProvider](getScalaProvider(_,_)) def getScala(version: String): xsbti.ScalaProvider = getScala(version, "") def getScala(version: String, reason: String): xsbti.ScalaProvider = scalaProviders(version, reason) + def app(id: xsbti.ApplicationID, version: String): xsbti.AppProvider = app(id, Option(version)) + def app(id: xsbti.ApplicationID, scalaVersion: Option[String]): xsbti.AppProvider = + getAppProvider(id, scalaVersion, false) + + val bootLoader = new BootFilteredLoader(getClass.getClassLoader) + val topLoader = jnaLoader(bootLoader) - lazy val topLoader = (new JNAProvider).loader val updateLockFile = if(lockBoot) Some(new File(bootDirectory, "sbt.boot.lock")) else None def globalLock: xsbti.GlobalLock = Locks - def ivyHome = ivyOptions.ivyHome.orNull + def ivyHome = orNull(ivyOptions.ivyHome) def ivyRepositories = repositories.toArray def checksums = checksumsList.toArray[String] - class JNAProvider extends Provider + def jnaLoader(parent: ClassLoader): ClassLoader = { - lazy val id = new Application("net.java.dev.jna", "jna", new Explicit("3.2.3"), "", Nil, false, array()) - lazy val configuration = new UpdateConfiguration(bootDirectory, ivyOptions.ivyHome, None, repositories, checksumsList) - lazy val libDirectory = new File(bootDirectory, baseDirectoryName("")) - def baseDirectories: List[File] = new File(libDirectory, appDirectoryName(id.toID, File.separator)) :: Nil - def testLoadClasses: List[String] = "com.sun.jna.Function" :: Nil - def extraClasspath = array() - def target = new UpdateApp(id, Nil) - lazy val parentLoader = new BootFilteredLoader(getClass.getClassLoader) - def failLabel = "JNA" - def lockFile = updateLockFile + val id = AppID("net.java.dev.jna", "jna", "3.2.3", "", toArray(Nil), false, array()) + val configuration = makeConfiguration(None) + val jnaHome = appDirectory(new File(bootDirectory, baseDirectoryName(None)), id) + val module = appModule(id, None, false, "jna") + def makeLoader(): ClassLoader = { + val urls = toURLs(wrapNull(jnaHome.listFiles(JarFilter))) + val loader = new URLClassLoader(urls, bootLoader) + checkLoader(loader, module, "com.sun.jna.Function" :: Nil, loader) + } + val existingLoader = + if(jnaHome.exists) + try Some(makeLoader()) catch { case e: Exception => None } + else + None + existingLoader getOrElse { + update(module, "") + makeLoader() + } + } + def checkLoader[T](loader: ClassLoader, module: ModuleDefinition, testClasses: Seq[String], ifValid: T): T = + { + val missing = getMissing(loader, testClasses) + if(missing.isEmpty) + ifValid + else + module.retrieveCorrupt(missing) } - class ScalaProvider(val version: String, override val reason: String) extends xsbti.ScalaProvider with Provider + private[this] def makeConfiguration(version: Option[String]): UpdateConfiguration = + new UpdateConfiguration(bootDirectory, ivyOptions.ivyHome, version, repositories, checksumsList) + + final def getAppProvider(id: xsbti.ApplicationID, explicitScalaVersion: Option[String], forceAppUpdate: Boolean): xsbti.AppProvider = + locked(new Callable[xsbti.AppProvider] { def call = getAppProvider0(id, explicitScalaVersion, forceAppUpdate) }) + + @tailrec private[this] final def getAppProvider0(id: xsbti.ApplicationID, explicitScalaVersion: Option[String], forceAppUpdate: Boolean): xsbti.AppProvider = { - def launcher: xsbti.Launcher = Launch.this - def parentLoader = topLoader - - lazy val configuration = new UpdateConfiguration(bootDirectory, ivyOptions.ivyHome, Some(version), repositories, checksumsList) - lazy val libDirectory = new File(configuration.bootDirectory, baseDirectoryName(version)) - lazy val scalaHome = new File(libDirectory, ScalaDirectoryName) - def compilerJar = new File(scalaHome, CompilerModuleName + ".jar") - def libraryJar = new File(scalaHome, LibraryModuleName + ".jar") - override def classpath = Provider.getJars(scalaHome :: Nil) - def baseDirectories = List(scalaHome) - def testLoadClasses = TestLoadScalaClasses - def target = new UpdateScala(Value.get(classifiers.forScala)) - def failLabel = "Scala " + version - def lockFile = updateLockFile - def extraClasspath = array() - - def app(id: xsbti.ApplicationID): xsbti.AppProvider = new AppProvider(id) - - class AppProvider(val id: xsbti.ApplicationID) extends xsbti.AppProvider with Provider - { - def scalaProvider: xsbti.ScalaProvider = ScalaProvider.this - def configuration = ScalaProvider.this.configuration - lazy val appHome = new File(libDirectory, appDirectoryName(id, File.separator)) - def parentLoader = ScalaProvider.this.loader - def baseDirectories = appHome :: id.mainComponents.map(components.componentLocation).toList - def testLoadClasses = List(id.mainClass) - def target = new UpdateApp(Application(id), Value.get(classifiers.app)) - def failLabel = id.name + " " + id.version - def lockFile = updateLockFile - def mainClasspath = fullClasspath - def extraClasspath = id.classpathExtra - - lazy val mainClass: Class[T] forSome { type T <: xsbti.AppMain } = - { - val c = Class.forName(id.mainClass, true, loader) - c.asSubclass(classOf[xsbti.AppMain]) - } - def newMain(): xsbti.AppMain = mainClass.newInstance - - lazy val components = new ComponentProvider(appHome, lockBoot) + val app = appModule(id, explicitScalaVersion, true, "app") + val baseDirs = (base: File) => appBaseDirs(base, id) + def retrieve() = { + val sv = update(app, "") + val scalaVersion = strictOr(explicitScalaVersion, sv) + new RetrievedModule(true, app, sv, baseDirs(scalaHome(scalaVersion))) } + val retrievedApp = + if(forceAppUpdate) + retrieve() + else + existing(app, explicitScalaVersion, baseDirs) getOrElse retrieve() + + val scalaVersion = getOrError(strictOr(explicitScalaVersion, retrievedApp.detectedScalaVersion), "No Scala version specified or detected") + val scalaProvider = getScala(scalaVersion, "(for " + id.name + ")") + + val (missing, appProvider) = checkedAppProvider(id, retrievedApp, scalaProvider) + if(missing.isEmpty) + appProvider + else if(retrievedApp.fresh) + app.retrieveCorrupt(missing) + else + getAppProvider0(id, explicitScalaVersion, true) + } + def scalaHome(scalaVersion: Option[String]): File = new File(bootDirectory, baseDirectoryName(scalaVersion)) + def appHome(id: xsbti.ApplicationID, scalaVersion: Option[String]): File = appDirectory(scalaHome(scalaVersion), id) + def checkedAppProvider(id: xsbti.ApplicationID, module: RetrievedModule, scalaProvider: xsbti.ScalaProvider): (Iterable[String], xsbti.AppProvider) = + { + val p = appProvider(id, module, scalaProvider, appHome(id, Some(scalaProvider.version))) + val missing = getMissing(p.loader, id.mainClass :: Nil) + (missing, p) + } + private[this] def locked[T](c: Callable[T]): T = Locks(orNull(updateLockFile), c) + def getScalaProvider(scalaVersion: String, reason: String): xsbti.ScalaProvider = + locked(new Callable[xsbti.ScalaProvider] { def call = getScalaProvider0(scalaVersion, reason) }) + + private[this] final def getScalaProvider0(scalaVersion: String, reason: String) = + { + val scalaM = scalaModule(scalaVersion) + val (scalaHome, lib) = scalaDirs(scalaM, scalaVersion) + val baseDirs = lib :: Nil + def provider(retrieved: RetrievedModule): xsbti.ScalaProvider = { + val p = scalaProvider(scalaVersion, retrieved, topLoader, lib) + checkLoader(p.loader, retrieved.definition, TestLoadScalaClasses, p) + } + existing(scalaM, Some(scalaVersion), _ => baseDirs) flatMap { mod => + try Some(provider(mod)) + catch { case e: Exception => None } + } getOrElse { + val scalaVersion = update(scalaM, reason) + provider( new RetrievedModule(true, scalaM, scalaVersion, baseDirs) ) + } + } + + def existing(module: ModuleDefinition, explicitScalaVersion: Option[String], baseDirs: File => List[File]): Option[RetrievedModule] = + { + val filter = new java.io.FileFilter { + val explicitName = explicitScalaVersion.map(sv => baseDirectoryName(Some(sv))) + def accept(file: File) = file.isDirectory && explicitName.forall(_ == file.getName) + } + val retrieved = wrapNull(bootDirectory.listFiles(filter)) flatMap { scalaDir => + val appDir = directory(scalaDir, module.target) + if(appDir.exists) + new RetrievedModule(false, module, extractScalaVersion(scalaDir), baseDirs(scalaDir)) :: Nil + else + Nil + } + retrieved.headOption + } + def directory(scalaDir: File, target: UpdateTarget): File = target match { + case _: UpdateScala => scalaDir + case ua: UpdateApp => appDirectory(scalaDir, ua.id.toID) + } + def appBaseDirs(scalaHome: File, id: xsbti.ApplicationID): List[File] = + { + val appHome = appDirectory(scalaHome, id) + val components = componentProvider(appHome) + appHome :: id.mainComponents.map(components.componentLocation).toList + } + def appDirectory(base: File, id: xsbti.ApplicationID): File = + new File(base, appDirectoryName(id, File.separator)) + + def scalaDirs(module: ModuleDefinition, scalaVersion: String): (File, File) = + { + val scalaHome = new File(bootDirectory, baseDirectoryName(Some(scalaVersion))) + val libDirectory = new File(scalaHome, ScalaDirectoryName) + (scalaHome, libDirectory) + } + + def appProvider(appID: xsbti.ApplicationID, app: RetrievedModule, scalaProvider0: xsbti.ScalaProvider, appHome: File): xsbti.AppProvider = new xsbti.AppProvider + { + val scalaProvider = scalaProvider0 + val id = appID + def mainClasspath = app.fullClasspath + lazy val loader = app.createLoader(scalaProvider.loader) + lazy val mainClass: Class[T] forSome { type T <: xsbti.AppMain } = + { + val c = Class.forName(id.mainClass, true, loader) + c.asSubclass(classOf[xsbti.AppMain]) + } + def newMain(): xsbti.AppMain = mainClass.newInstance + + lazy val components = componentProvider(appHome) + } + def componentProvider(appHome: File) = new ComponentProvider(appHome, lockBoot) + + def scalaProvider(scalaVersion: String, module: RetrievedModule, parentLoader: ClassLoader, scalaLibDir: File): xsbti.ScalaProvider = new xsbti.ScalaProvider + { + def launcher = Launch.this + def version = scalaVersion + lazy val loader = module.createLoader(parentLoader) + + def compilerJar = new File(scalaLibDir, CompilerModuleName + ".jar") + def libraryJar = new File(scalaLibDir, LibraryModuleName + ".jar") + def jars = module.fullClasspath + + def app(id: xsbti.ApplicationID) = Launch.this.app(id, Some(scalaVersion)) + } + + def appModule(id: xsbti.ApplicationID, scalaVersion: Option[String], getClassifiers: Boolean, tpe: String): ModuleDefinition = new ModuleDefinition( + configuration = makeConfiguration(scalaVersion), + target = new UpdateApp(Application(id), if(getClassifiers) Value.get(classifiers.app) else Nil, tpe), + failLabel = id.name + " " + id.version, + extraClasspath = id.classpathExtra + ) + def scalaModule(version: String): ModuleDefinition = new ModuleDefinition( + configuration = makeConfiguration(Some(version)), + target = new UpdateScala(Value.get(classifiers.forScala)), + failLabel = "Scala " + version, + extraClasspath = array() + ) + def update(mm: ModuleDefinition, reason: String): Option[String] = + { + val result = ( new Update(mm.configuration) )(mm.target, reason) + if(result.success) result.scalaVersion else mm.retrieveFailed } } object Launcher @@ -171,14 +293,13 @@ object Launcher Initialize.process(parsed.boot.properties, parsed.appProperties, Initialize.selectQuick) val config = ResolveValues(parsed) val launcher = apply(config) - val scalaProvider = launcher.getScala(config.getScalaVersion) - scalaProvider.app(config.app.toID) + launcher.app(config.app.toID, orNull(config.getScalaVersion)) } } class ComponentProvider(baseDirectory: File, lockBoot: Boolean) extends xsbti.ComponentProvider { def componentLocation(id: String): File = new File(baseDirectory, id) - def component(id: String) = Provider.wrapNull(componentLocation(id).listFiles).filter(_.isFile) + def component(id: String) = wrapNull(componentLocation(id).listFiles).filter(_.isFile) def defineComponent(id: String, files: Array[File]) = { val location = componentLocation(id) diff --git a/launch/LaunchConfiguration.scala b/launch/LaunchConfiguration.scala index 9fd186e58..d1e150d1a 100644 --- a/launch/LaunchConfiguration.scala +++ b/launch/LaunchConfiguration.scala @@ -11,7 +11,10 @@ import scala.collection.immutable.List //TODO: use copy constructor, check size change final case class LaunchConfiguration(scalaVersion: Value[String], ivyConfiguration: IvyOptions, app: Application, boot: BootSetup, logging: Logging, appProperties: List[AppProperty]) { - def getScalaVersion = Value.get(scalaVersion) + def getScalaVersion = { + val sv = Value.get(scalaVersion) + if(sv == "auto") None else Some(sv) + } def withScalaVersion(newScalaVersion: String) = LaunchConfiguration(new Explicit(newScalaVersion), ivyConfiguration, app, boot, logging, appProperties) def withApp(app: Application) = LaunchConfiguration(scalaVersion, ivyConfiguration, app, boot, logging, appProperties) def withAppVersion(newAppVersion: String) = LaunchConfiguration(scalaVersion, ivyConfiguration, app.withVersion(new Explicit(newAppVersion)), boot, logging, appProperties) diff --git a/launch/ListMap.scala b/launch/ListMap.scala index 298645fc6..588f9bfaf 100644 --- a/launch/ListMap.scala +++ b/launch/ListMap.scala @@ -16,7 +16,7 @@ sealed class ListMap[K,V] private(backing: List[(K,V)]) extends Iterable[(K,V)] def -(k: K) = copy(remove(backing,k)) def get(k: K): Option[V] = backing.find(_._1 == k).map(_._2) def keys: List[K] = backing.reverse.map(_._1) - def apply(k: K): V = get(k).getOrElse(error("Key " + k + " not found")) + def apply(k: K): V = getOrError(get(k), "Key " + k + " not found") def contains(k: K): Boolean = get(k).isDefined def iterator = backing.reverse.iterator override def isEmpty: Boolean = backing.isEmpty diff --git a/launch/ModuleDefinition.scala b/launch/ModuleDefinition.scala new file mode 100644 index 000000000..c5903d415 --- /dev/null +++ b/launch/ModuleDefinition.scala @@ -0,0 +1,23 @@ +package xsbt.boot + +import Pre._ +import java.io.File +import java.net.URLClassLoader + +final class ModuleDefinition(val configuration: UpdateConfiguration, val extraClasspath: Array[File], val target: UpdateTarget, val failLabel: String) +{ + def retrieveFailed: Nothing = fail("") + def retrieveCorrupt(missing: Iterable[String]): Nothing = fail(": missing " + missing.mkString(", ")) + private def fail(extra: String) = + throw new xsbti.RetrieveException(versionString, "Could not retrieve " + failLabel + extra) + private def versionString: String = target match { case _: UpdateScala => configuration.getScalaVersion; case a: UpdateApp => Value.get(a.id.version) } +} + +final class RetrievedModule(val fresh: Boolean, val definition: ModuleDefinition, val detectedScalaVersion: Option[String], val baseDirectories: List[File]) +{ + lazy val classpath: Array[File] = getJars(baseDirectories) + lazy val fullClasspath: Array[File] = concat(classpath, definition.extraClasspath) + + def createLoader(parentLoader: ClassLoader): ClassLoader = + new URLClassLoader(toURLs(fullClasspath), parentLoader) +} \ No newline at end of file diff --git a/launch/Pre.scala b/launch/Pre.scala index b6c43ac90..da4bc5a71 100644 --- a/launch/Pre.scala +++ b/launch/Pre.scala @@ -2,7 +2,10 @@ * Copyright 2008, 2009, 2010 Mark Harrah */ package xsbt.boot -import scala.collection.immutable.List + + import scala.collection.immutable.List + import java.io.{File, FileFilter} + import java.net.{URL, URLClassLoader} object Pre { @@ -33,7 +36,6 @@ object Pre 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) @@ -44,4 +46,23 @@ object Pre def array(files: File*): Array[File] = toArray(files.toList) /* Saves creating a closure for default if it has already been evaluated*/ def orElse[T](opt: Option[T], default: T) = if(opt.isDefined) opt.get else default + + def wrapNull(a: Array[File]): Array[File] = if(a == null) new Array[File](0) else a + def const[B](b: B): Any => B = _ => b + def strictOr[T](a: Option[T], b: Option[T]): Option[T] = a match { case None => b; case _ => a } + def getOrError[T](a: Option[T], msg: String): T = a match { case None => error(msg); case Some(x) => x } + def orNull[T >: Null](t: Option[T]): T = t match { case None => null; case Some(x) => x } + + def getJars(directories: List[File]): Array[File] = toArray(directories.flatMap(directory => wrapNull(directory.listFiles(JarFilter)))) + + object JarFilter extends FileFilter + { + def accept(file: File) = !file.isDirectory && file.getName.endsWith(".jar") + } + def getMissing(loader: ClassLoader, classes: Iterable[String]): Iterable[String] = + { + def classMissing(c: String) = try { Class.forName(c, false, loader); false } catch { case e: ClassNotFoundException => true } + classes.toList.filter(classMissing) + } + def toURLs(files: Array[File]): Array[URL] = files.map(_.toURI.toURL) } diff --git a/launch/Provider.scala b/launch/Provider.scala deleted file mode 100644 index a889b2e60..000000000 --- a/launch/Provider.scala +++ /dev/null @@ -1,77 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2009, 2010, 2011 Mark Harrah - */ -package xsbt.boot - -import Pre._ -import java.io.{File, FileFilter} -import java.net.{URL, URLClassLoader} -import java.util.concurrent.Callable - -trait Provider -{ - def configuration: UpdateConfiguration - def baseDirectories: List[File] - def testLoadClasses: List[String] - def extraClasspath: Array[File] - def target: UpdateTarget - def failLabel: String - def parentLoader: ClassLoader - def lockFile: Option[File] - - def classpath: Array[File] = Provider.getJars(baseDirectories) - def fullClasspath:Array[File] = concat(classpath, extraClasspath) - - def reason: String = "" - def retrieveFailed: Nothing = fail("") - def retrieveCorrupt(missing: Iterable[String]): Nothing = fail(": missing " + missing.mkString(", ")) - private def fail(extra: String) = - throw new xsbti.RetrieveException(versionString, "Could not retrieve " + failLabel + extra) - private def versionString: String = target match { case _: UpdateScala => configuration.getScalaVersion; case a: UpdateApp => Value.get(a.id.version) } - - val (jars, loader) = Locks(orNull(lockFile), new initialize) - private[this] def orNull[T >: Null](opt: Option[T]): T = opt match { case None => null; case Some(x) => x } - private final class initialize extends Callable[(Array[File], ClassLoader)] - { - def call = - { - val (existingJars, existingLoader) = createLoader - if(Provider.getMissing(existingLoader, testLoadClasses).isEmpty) - (existingJars, existingLoader) - else - { - val result = ( new Update(configuration) )(target, reason) - if(result.success) - { - val (newJars, newLoader) = createLoader - val missing = Provider.getMissing(newLoader, testLoadClasses) - if(missing.isEmpty) (newJars, newLoader) else retrieveCorrupt(missing) - } - else - retrieveFailed - } - } - def createLoader = - { - val full = fullClasspath - (full, new URLClassLoader(Provider.toURLs(full), parentLoader) ) - } - } -} - -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) new Array[File](0) else a - - object JarFilter extends FileFilter - { - def accept(file: File) = !file.isDirectory && file.getName.endsWith(".jar") - } - def getMissing(loader: ClassLoader, classes: Iterable[String]): Iterable[String] = - { - def classMissing(c: String) = try { Class.forName(c, false, loader); false } catch { case e: ClassNotFoundException => true } - classes.toList.filter(classMissing) - } - def toURLs(files: Array[File]): Array[URL] = files.map(_.toURI.toURL) -} \ No newline at end of file diff --git a/launch/ResolveValues.scala b/launch/ResolveValues.scala index 97d54cc1d..b04cdb949 100644 --- a/launch/ResolveValues.scala +++ b/launch/ResolveValues.scala @@ -48,7 +48,6 @@ final class ResolveValues(conf: LaunchConfiguration) case e: Explicit[t] => e.value case i: Implicit[t] => trim(properties.getProperty(i.name)) map read orElse - i.default getOrElse - error("No " + i.name + " specified in " + propertiesFile) + i.default getOrElse ("No " + i.name + " specified in " + propertiesFile) } } \ No newline at end of file diff --git a/launch/Update.scala b/launch/Update.scala index 11df9a493..df20f6d36 100644 --- a/launch/Update.scala +++ b/launch/Update.scala @@ -31,7 +31,7 @@ import BootConfiguration._ sealed trait UpdateTarget { def tpe: String; def classifiers: List[String] } final class UpdateScala(val classifiers: List[String]) extends UpdateTarget { def tpe = "scala" } -final class UpdateApp(val id: Application, val classifiers: List[String]) extends UpdateTarget { def tpe = "app" } +final class UpdateApp(val id: Application, val classifiers: List[String], val tpe: String) extends UpdateTarget final class UpdateConfiguration(val bootDirectory: File, val ivyHome: Option[File], val scalaVersion: Option[String], val repositories: List[xsbti.Repository], val checksums: List[String]) { def getScalaVersion = scalaVersion match { case Some(sv) => sv; case None => "" } @@ -55,7 +55,10 @@ final class Update(config: UpdateConfiguration) Option(System.getenv("SBT_CREDENTIALS")) map ( path => ResolveValues.readProperties(new File(path)) ) - optionProps foreach extractCredentials("realm","host","user","password") + optionProps match { + case Some(props) => extractCredentials("realm","host","user","password")(props) + case None => () + } extractCredentials("sbt.boot.realm","sbt.boot.host","sbt.boot.user","sbt.boot.password")(System.getProperties) } private def extractCredentials(keys: (String,String,String,String))(props: Properties) { @@ -67,7 +70,7 @@ final class Update(config: UpdateConfiguration) { addCredentials() val settings = new IvySettings - ivyHome foreach settings.setDefaultIvyUserDir + ivyHome match { case Some(dir) => settings.setDefaultIvyUserDir(dir); case None => } addResolvers(settings) settings.setVariable("ivy.checksums", checksums mkString ",") settings.setDefaultConflictManager(settings.getConflictManager(ConflictManagerName)) @@ -76,7 +79,7 @@ final class Update(config: UpdateConfiguration) settings } private[this] def setScalaVariable(settings: IvySettings, scalaVersion: Option[String]): Unit = - scalaVersion foreach { sv => settings.setVariable("scala", sv) } + scalaVersion match { case Some(sv) => settings.setVariable("scala", sv); case None => } private lazy val ivy = { val ivy = new Ivy() { private val loggerEngine = new SbtMessageLoggerEngine; override def getLoggerEngine = loggerEngine } @@ -136,7 +139,6 @@ final class Update(config: UpdateConfiguration) case _ => app.name } addDependency(moduleID, app.groupID, resolvedName, app.getVersion, "default(compile)", u.classifiers) - excludeScala(moduleID) System.out.println("Getting " + app.groupID + " " + resolvedName + " " + app.getVersion + " " + reason + "...") } update(moduleID, target) @@ -165,19 +167,13 @@ final class Update(config: UpdateConfiguration) private def addClassifier(dep: DefaultDependencyDescriptor, name: String, classifier: String) { val extraMap = new java.util.HashMap[String,String] - if(!classifier.isEmpty) + if(!isEmpty(classifier)) extraMap.put("e:classifier", classifier) val ivyArtifact = new DefaultDependencyArtifactDescriptor(dep, name, artifactType(classifier), "jar", null, extraMap) for(conf <- dep.getModuleConfigurations) dep.addDependencyArtifact(conf, ivyArtifact) } private def excludeJUnit(module: DefaultModuleDescriptor): Unit = module.addExcludeRule(excludeRule(JUnitName, JUnitName)) - private def excludeScala(module: DefaultModuleDescriptor) - { - def excludeScalaJar(name: String): Unit = module.addExcludeRule(excludeRule(ScalaOrg, name)) - excludeScalaJar(LibraryModuleName) - excludeScalaJar(CompilerModuleName) - } private def excludeRule(organization: String, name: String): ExcludeRule = { val artifact = new ArtifactId(ModuleId.newInstance(organization, name), "*", "*", "*") @@ -205,12 +201,16 @@ final class Update(config: UpdateConfiguration) scalaDependencyVersion(resolveReport).headOption } private[this] def scalaDependencyVersion(report: ResolveReport): List[String] = - for { - config <- report.getConfigurations.toList - module <- report.getConfigurationReport(config).getModuleRevisionIds.toArray collect { case revId: ModuleRevisionId => revId } - if module.getOrganisation == ScalaOrg && module.getName == LibraryModuleName - } yield - module.getRevision + { + val modules = report.getConfigurations.toList flatMap { config => + report.getConfigurationReport(config).getModuleRevisionIds.toArray + } + modules flatMap { + case module: ModuleRevisionId if module.getOrganisation == ScalaOrg && module.getName == LibraryModuleName => + module.getRevision :: Nil + case _ => Nil + } + } /** Exceptions are logged to the update log file. */ private def logExceptions(report: ResolveReport) @@ -229,17 +229,22 @@ final class Update(config: UpdateConfiguration) private def retrieve(eventManager: EventManager, module: ModuleDescriptor, target: UpdateTarget, autoScalaVersion: Option[String]) { val retrieveOptions = new RetrieveOptions - retrieveOptions.setArtifactFilter(new ArtifactFilter(a => retrieveType(a.getType) && a.getExtraAttribute("classifier") == null)) val retrieveEngine = new RetrieveEngine(settings, eventManager) - val pattern = + val (pattern, extraFilter) = target match { - case _: UpdateScala => scalaRetrievePattern - case u: UpdateApp => appRetrievePattern(u.id.toID) + case _: UpdateScala => (scalaRetrievePattern, const(true)) + case u: UpdateApp => (appRetrievePattern(u.id.toID), notCoreScala _) } - val scalaV = scalaVersion orElse autoScalaVersion getOrElse "" + val filter = (a: IArtifact) => retrieveType(a.getType) && a.getExtraAttribute("classifier") == null && extraFilter(a) + retrieveOptions.setArtifactFilter(new ArtifactFilter(filter)) + val scalaV = strictOr(scalaVersion, autoScalaVersion) retrieveEngine.retrieve(module.getModuleRevisionId, baseDirectoryName(scalaV) + "/" + pattern, retrieveOptions) } + private[this] def notCoreScala(a: IArtifact) = a.getName match { + case LibraryModuleName | CompilerModuleName => false + case _ => true + } private def retrieveType(tpe: String): Boolean = tpe == "jar" || tpe == "bundle" /** Add the scala tools repositories and a URL resolver to download sbt from the Google code project.*/ private def addResolvers(settings: IvySettings) @@ -311,7 +316,7 @@ final class Update(config: UpdateConfiguration) resolver } private def adjustPattern(base: String, pattern: String): String = - (if(base.endsWith("/") || base.isEmpty) base else (base + "/") ) + pattern + (if(base.endsWith("/") || isEmpty(base)) base else (base + "/") ) + pattern private def mavenLocal = mavenResolver("Maven2 Local", "file://" + System.getProperty("user.home") + "/.m2/repository/") /** Creates a maven-style resolver.*/ private def mavenResolver(name: String, root: String) = diff --git a/launch/interface/src/main/java/xsbti/AppProvider.java b/launch/interface/src/main/java/xsbti/AppProvider.java index 8fdafe32f..c6eb3d33f 100644 --- a/launch/interface/src/main/java/xsbti/AppProvider.java +++ b/launch/interface/src/main/java/xsbti/AppProvider.java @@ -9,6 +9,7 @@ public interface AppProvider /** The ID of the application that will be created by 'newMain' or 'mainClass'.*/ public ApplicationID id(); + public ClassLoader loader(); /** Loads the class for the entry point for the application given by 'id'. This method will return the same class * every invocation. That is, the ClassLoader is not recreated each call.*/ public Class mainClass(); diff --git a/launch/interface/src/main/java/xsbti/Launcher.java b/launch/interface/src/main/java/xsbti/Launcher.java index 92e54ba15..0593db35d 100644 --- a/launch/interface/src/main/java/xsbti/Launcher.java +++ b/launch/interface/src/main/java/xsbti/Launcher.java @@ -7,6 +7,7 @@ public interface Launcher public static final int InterfaceVersion = 1; public ScalaProvider getScala(String version); public ScalaProvider getScala(String version, String reason); + public AppProvider app(ApplicationID id, String version); public ClassLoader topLoader(); public GlobalLock globalLock(); public File bootDirectory(); diff --git a/launch/src/main/input_resources/sbt/sbt.boot.properties b/launch/src/main/input_resources/sbt/sbt.boot.properties index 195ddba80..2f0c5d198 100644 --- a/launch/src/main/input_resources/sbt/sbt.boot.properties +++ b/launch/src/main/input_resources/sbt/sbt.boot.properties @@ -1,5 +1,5 @@ [scala] - version: ${{scala.version}} + version: ${sbt.scala.version-auto} [app] org: ${{org}} diff --git a/launch/src/test/scala/ScalaProviderTest.scala b/launch/src/test/scala/ScalaProviderTest.scala index fd6823c27..ab85bef16 100644 --- a/launch/src/test/scala/ScalaProviderTest.scala +++ b/launch/src/test/scala/ScalaProviderTest.scala @@ -36,7 +36,7 @@ object ScalaProviderTest extends Specification withTemporaryDirectory { currentDirectory => withLauncher { launcher => Launch.run(launcher)( - new RunConfiguration(mapScalaVersion(LaunchTest.getScalaVersion), LaunchTest.testApp(mainClassName, extra(currentDirectory)).toID, currentDirectory, arguments) + new RunConfiguration(Some(mapScalaVersion(LaunchTest.getScalaVersion)), LaunchTest.testApp(mainClassName, extra(currentDirectory)).toID, currentDirectory, arguments) ) } }