Permalink
Browse files

auto detect Scala version for non-cross-versioned launcher app

  • Loading branch information...
harrah committed Feb 5, 2012
1 parent 7b31db4 commit dd4efec03ccf3d9e211cb1dc9354db924279f20a
@@ -3,6 +3,8 @@
*/
package xsbt.boot
+ import java.io.File
+
// <boot.directory>
// scala-<scala.version>/ [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
{
View
@@ -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)
}
}
View
@@ -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)
}
View
@@ -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
- {
- 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
- }
-
- class ScalaProvider(val version: String, override val reason: String) extends xsbti.ScalaProvider with Provider
- {
- 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 jnaLoader(parent: ClassLoader): ClassLoader =
+ {
+ 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)
+ }
+
+ 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 =
+ {
+ 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 } =
{
- 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 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)
@@ -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)
View
@@ -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
Oops, something went wrong.

0 comments on commit dd4efec

Please sign in to comment.