sbt/launch/Launch.scala

165 lines
6.1 KiB
Scala

/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package xsbt.boot
// This is the main class for the sbt launcher. It reads the project/build.properties file to determine the
// version of Scala and sbt requested for the project definition. It downloads the requested version of
// Scala, sbt, and dependencies to the project's 'project/boot' directory. It loads the main sbt and then
// satisfies requests from the main sbt for different versions of Scala for use in the build.
import java.io.{File, FileFilter}
import java.net.URLClassLoader
import xsbti.boot.{Exit => IExit, Launcher, MainResult, Reboot, SbtConfiguration, SbtMain}
// contains constants and paths
import BootConfiguration._
import UpdateTarget.{UpdateScala, UpdateSbt}
class Launch(projectRootDirectory: File, mainClassName: String) extends Launcher with NotNull
{
import Launch._
final def boot(args: Array[String])
{
checkAndLoad(args) match
{
case e: Exit => System.exit(e.code)
case r: Reboot => boot(r.arguments())
}
}
def checkAndLoad(args: Array[String]): MainResult =
{
// prompt to create project if it doesn't exist.
checkProject() match
{
case Some(result) => result
case None => load(args)
}
}
/** Loads the project in the current working directory using the version of scala and sbt
* declared in the build. The class loader used prevents the Scala and Ivy classes used by
* this loader from being seen by the loaded sbt/project.*/
def load(args: Array[String]): MainResult =
{
val (definitionScalaVersion, useSbtVersion) = ProjectProperties.forcePrompt(PropertiesFile)//, forcePrompt : _*)
val scalaLoader = getScalaLoader(definitionScalaVersion)
val sbtLoader = createSbtLoader(useSbtVersion, definitionScalaVersion, scalaLoader)
val configuration = new SbtConfiguration
{
def arguments = args
def scalaVersion = definitionScalaVersion
def sbtVersion = useSbtVersion
def launcher: Launcher = Launch.this
}
run(sbtLoader, configuration)
}
def run(sbtLoader: ClassLoader, configuration: SbtConfiguration): MainResult =
{
val sbtMain = Class.forName(mainClassName, true, sbtLoader)
val main = sbtMain.newInstance.asInstanceOf[SbtMain]
main.run(configuration)
}
final val ProjectDirectory = new File(projectRootDirectory, ProjectDirectoryName)
final val BootDirectory = new File(ProjectDirectory, BootDirectoryName)
final val PropertiesFile = new File(ProjectDirectory, BuildPropertiesName)
final def checkProject() =
{
if(ProjectDirectory.exists)
None
else
{
val line = SimpleReader.readLine("Project does not exist, create new project? (y/N/s) : ")
if(isYes(line))
{
ProjectProperties(PropertiesFile, true)
None
}
else if(isScratch(line))
{
ProjectProperties.scratch(PropertiesFile)
None
}
else
Some(new Exit(1))
}
}
private val scalaLoaderCache = new scala.collection.jcl.WeakHashMap[String, ClassLoader]
def launcher(directory: File, mainClassName: String): Launcher = new Launch(directory, mainClassName)
def getScalaLoader(scalaVersion: String) = scalaLoaderCache.getOrElseUpdate(scalaVersion, createScalaLoader(scalaVersion))
def getScalaHome(scalaVersion: String) = new File(new File(BootDirectory, baseDirectoryName(scalaVersion)), ScalaDirectoryName)
def createScalaLoader(scalaVersion: String): ClassLoader =
{
val baseDirectory = new File(BootDirectory, baseDirectoryName(scalaVersion))
val scalaDirectory = new File(baseDirectory, ScalaDirectoryName)
val scalaLoader = newScalaLoader(scalaDirectory)
if(needsUpdate(scalaLoader, TestLoadScalaClasses))
{
(new Update(baseDirectory, "", scalaVersion))(UpdateScala)
val scalaLoader = newScalaLoader(scalaDirectory)
failIfMissing(scalaLoader, TestLoadScalaClasses, "Scala " + scalaVersion)
scalaLoader
}
else
scalaLoader
}
def createSbtLoader(sbtVersion: String, scalaVersion: String, scalaLoader: ClassLoader): ClassLoader =
{
val baseDirectory = new File(BootDirectory, baseDirectoryName(scalaVersion))
val sbtDirectory = new File(baseDirectory, sbtDirectoryName(sbtVersion))
val sbtLoader = newSbtLoader(sbtDirectory, scalaLoader)
if(needsUpdate(sbtLoader, TestLoadSbtClasses))
{
(new Update(baseDirectory, sbtVersion, scalaVersion))(UpdateSbt)
val sbtLoader = newSbtLoader(sbtDirectory, scalaLoader)
failIfMissing(sbtLoader, TestLoadSbtClasses, "sbt " + sbtVersion)
sbtLoader
}
else
sbtLoader
}
private def newScalaLoader(dir: File) = newLoader(dir, new BootFilteredLoader)
private def newSbtLoader(dir: File, scalaLoader: ClassLoader) = newLoader(dir, scalaLoader)
}
private object Launch
{
def isYes(so: Option[String]) = isValue("y", "yes")(so)
def isScratch(so: Option[String]) = isValue("s", "scratch")(so)
def isValue(values: String*)(so: Option[String]) =
so match
{
case Some(s) => values.contains(s.toLowerCase)
case None => false
}
private def failIfMissing(loader: ClassLoader, classes: Iterable[String], label: String) =
checkTarget(loader, classes, (), throw new BootException("Could not retrieve " + label + "."))
private def needsUpdate(loader: ClassLoader, classes: Iterable[String]) = checkTarget(loader, classes, false, true)
private def checkTarget[T](loader: ClassLoader, classes: Iterable[String], ifSuccess: => T, ifFailure: => T): T =
{
try
{
classes.foreach { c => Class.forName(c, false, loader) }
ifSuccess
}
catch { case e: ClassNotFoundException => ifFailure }
}
private def newLoader(directory: File, parent: ClassLoader) = new URLClassLoader(getJars(directory), parent)
private def getJars(directory: File) = wrapNull(directory.listFiles(JarFilter)).map(_.toURI.toURL)
private def wrapNull(a: Array[File]): Array[File] = if(a == null) Array() else a
}
private object JarFilter extends FileFilter
{
def accept(file: File) = !file.isDirectory && file.getName.endsWith(".jar")
}
final class Exit(val code: Int) extends IExit
// The exception to use when an error occurs at the launcher level (and not a nested exception).
// This indicates overrides toString because the exception class name is not needed to understand
// the error message.
private class BootException(override val toString: String) extends RuntimeException