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

This commit is contained in:
Mark Harrah 2012-02-04 21:10:30 -05:00
parent 7b31db4171
commit dd4efec03c
15 changed files with 285 additions and 176 deletions

View File

@ -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 File

@ -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 File

@ -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 File

@ -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)

View File

@ -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 File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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) =

View File

@ -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<? extends AppMain> mainClass();

View File

@ -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();

View File

@ -1,5 +1,5 @@
[scala]
version: ${{scala.version}}
version: ${sbt.scala.version-auto}
[app]
org: ${{org}}

View File

@ -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)
)
}
}