mirror of https://github.com/sbt/sbt.git
auto detect Scala version for non-cross-versioned launcher app
This commit is contained in:
parent
7b31db4171
commit
dd4efec03c
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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) =
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
[scala]
|
||||
version: ${{scala.version}}
|
||||
version: ${sbt.scala.version-auto}
|
||||
|
||||
[app]
|
||||
org: ${{org}}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue