mirror of https://github.com/sbt/sbt.git
Initial code for the loader.
This commit is contained in:
parent
108807a773
commit
b716d33ba3
|
|
@ -0,0 +1,30 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package xsbt.boot
|
||||
|
||||
import BootConfiguration.SbtMainClass
|
||||
import java.io.File
|
||||
|
||||
// The entry point to the launcher
|
||||
object Boot
|
||||
{
|
||||
def main(args: Array[String])
|
||||
{
|
||||
CheckProxy()
|
||||
try { (new Launch(new File("."), SbtMainClass)).boot(args) }
|
||||
catch
|
||||
{
|
||||
case b: BootException => errorAndExit(b)
|
||||
case e =>
|
||||
e.printStackTrace
|
||||
errorAndExit(e)
|
||||
}
|
||||
System.exit(0)
|
||||
}
|
||||
private def errorAndExit(e: Throwable)
|
||||
{
|
||||
System.out.println("Error during sbt execution: " + e.toString)
|
||||
System.exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package xsbt.boot
|
||||
|
||||
// project/boot/ [BootDirectoryName]
|
||||
// scala-<version>/ [baseDirectoryName]
|
||||
// lib/ [ScalaDirectoryName]
|
||||
// sbt-<version>/ [sbtDirectoryName]
|
||||
//
|
||||
// see also ProjectProperties for the set of constants that apply to the build.properties file in a project
|
||||
private object BootConfiguration
|
||||
{
|
||||
val SbtMainClass = "xsbt.Main"
|
||||
|
||||
// these are the module identifiers to resolve/retrieve
|
||||
val ScalaOrg = "org.scala-lang"
|
||||
val SbtOrg = "sbt"
|
||||
val CompilerModuleName = "scala-compiler"
|
||||
val LibraryModuleName = "scala-library"
|
||||
val SbtModuleName = "simple-build-tool"
|
||||
|
||||
/** The Ivy conflict manager to use for updating.*/
|
||||
val ConflictManagerName = "strict"
|
||||
/** The name of the local Ivy repository, which is used when compiling sbt from source.*/
|
||||
val LocalIvyName = "local"
|
||||
/** The pattern used for the local Ivy repository, which is used when compiling sbt from source.*/
|
||||
val LocalPattern = "[organisation]/[module]/[revision]/[type]s/[artifact].[ext]"
|
||||
/** The artifact pattern used for the local Ivy repository.*/
|
||||
def LocalArtifactPattern = LocalPattern
|
||||
/** The Ivy pattern used for the local Ivy repository.*/
|
||||
def LocalIvyPattern = LocalPattern
|
||||
|
||||
/** The class name prefix used to hide the Scala classes used by this loader from sbt
|
||||
* and the project definition*/
|
||||
val ScalaPackage = "scala."
|
||||
/** The class name prefix used to hide the Ivy classes used by this loader from sbt
|
||||
* and the project definition*/
|
||||
val IvyPackage = "org.apache.ivy."
|
||||
/** The class name prefix used to hide the sbt launcher classes from sbt and the project definition.
|
||||
* Note that access to xsbti.boot classes are allowed.*/
|
||||
val SbtBootPackage = "xsbt.boot."
|
||||
/** The loader will check that these classes can be loaded and will assume that their presence indicates
|
||||
* sbt and its dependencies have been downloaded.*/
|
||||
val TestLoadSbtClasses = "xsbt.Main" :: "org.apache.ivy.Ivy" :: Nil
|
||||
/** The loader will check that these classes can be loaded and will assume that their presence indicates
|
||||
* the Scala compiler and library have been downloaded.*/
|
||||
val TestLoadScalaClasses = "scala.ScalaObject" :: "scala.tools.nsc.GenericRunnerCommand" :: Nil
|
||||
|
||||
val ProjectDirectoryName = "project"
|
||||
val BootDirectoryName = "boot"
|
||||
val BuildPropertiesName ="build.properties"
|
||||
val ScalaHomeProperty = "scala.home"
|
||||
val UpdateLogName = "update.log"
|
||||
|
||||
val DefaultIvyConfiguration = "default"
|
||||
|
||||
/** The base URL to use to resolve sbt for download. */
|
||||
val sbtRootBase = "http://simple-build-tool.googlecode.com/svn/artifacts/"
|
||||
/** The name of the directory within the boot directory to retrieve scala to. */
|
||||
val ScalaDirectoryName = "lib"
|
||||
/** The Ivy pattern to use for retrieving the scala compiler and library. It is relative to the directory
|
||||
* containing all jars for the requested version of scala. */
|
||||
val scalaRetrievePattern = ScalaDirectoryName + "/[artifact].[ext]"
|
||||
|
||||
/** The Ivy pattern to use for retrieving sbt and its dependencies. It is relative to the directory
|
||||
* containing all jars for the requested version of scala. */
|
||||
def sbtRetrievePattern(sbtVersion: String) = sbtDirectoryName(sbtVersion) + "/[conf]/[artifact]-[revision].[ext]"
|
||||
/** The Ivy pattern to use for resolving sbt and its dependencies from the Google code project.*/
|
||||
def sbtResolverPattern(scalaVersion: String) = sbtRootBase + "[revision]/[type]s/[artifact].[ext]"
|
||||
/** The name of the directory to retrieve sbt and its dependencies to.*/
|
||||
def sbtDirectoryName(sbtVersion: String) = SbtOrg + "-" + sbtVersion
|
||||
/** The name of the directory in the boot directory to put all jars for the given version of scala in.*/
|
||||
def baseDirectoryName(scalaVersion: String) = "scala-" + scalaVersion
|
||||
}
|
||||
private object ProxyProperties
|
||||
{
|
||||
val HttpProxyEnv = "http_proxy"
|
||||
val HttpProxyUser = "http_proxy_user"
|
||||
val HttpProxyPassword = "http_proxy_pass"
|
||||
|
||||
val ProxyHost = "http.proxyHost"
|
||||
val ProxyPort = "http.proxyPort"
|
||||
val ProxyUser = "http.proxyUser"
|
||||
val ProxyPassword = "http.proxyPassword"
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package xsbt.boot
|
||||
|
||||
import java.net.{MalformedURLException, URL}
|
||||
|
||||
object CheckProxy
|
||||
{
|
||||
def apply()
|
||||
{
|
||||
import ProxyProperties._
|
||||
val httpProxy = System.getenv(HttpProxyEnv)
|
||||
if(isDefined(httpProxy) && !isPropertyDefined(ProxyHost) && !isPropertyDefined(ProxyPort))
|
||||
{
|
||||
try
|
||||
{
|
||||
val proxy = new URL(httpProxy)
|
||||
setProperty(ProxyHost, proxy.getHost)
|
||||
val port = proxy.getPort
|
||||
if(port >= 0)
|
||||
System.setProperty(ProxyPort, port.toString)
|
||||
copyEnv(HttpProxyUser, ProxyUser)
|
||||
copyEnv(HttpProxyPassword, ProxyPassword)
|
||||
}
|
||||
catch
|
||||
{
|
||||
case e: MalformedURLException =>
|
||||
System.out.println("Warning: could not parse http_proxy setting: " + e.toString)
|
||||
}
|
||||
}
|
||||
}
|
||||
private def copyEnv(envKey: String, sysKey: String) { setProperty(sysKey, System.getenv(envKey)) }
|
||||
private def setProperty(key: String, value: String) { if(value != null) System.setProperty(key, value) }
|
||||
private def isPropertyDefined(k: String) = isDefined(System.getProperty(k))
|
||||
private def isDefined(s: String) = s != null && !s.isEmpty
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package xsbt.boot
|
||||
|
||||
import BootConfiguration.{IvyPackage, SbtBootPackage, ScalaPackage}
|
||||
|
||||
/** A custom class loader to ensure the main part of sbt doesn't load any Scala or
|
||||
* Ivy classes from the jar containing the loader. */
|
||||
private[boot] final class BootFilteredLoader extends ClassLoader with NotNull
|
||||
{
|
||||
@throws(classOf[ClassNotFoundException])
|
||||
override final def loadClass(className: String, resolve: Boolean): Class[_] =
|
||||
{
|
||||
if(className.startsWith(ScalaPackage) || className.startsWith(IvyPackage) || className.startsWith(SbtBootPackage))
|
||||
throw new ClassNotFoundException(className)
|
||||
else
|
||||
super.loadClass(className, resolve)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
/* 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
|
||||
|
||||
// contains constants and paths
|
||||
import BootConfiguration._
|
||||
import UpdateTarget.{UpdateScala, UpdateSbt}
|
||||
|
||||
import xsbti.boot.{Exit => IExit, Launcher, MainResult, Reboot, SbtConfiguration, SbtMain}
|
||||
|
||||
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, scalaVersion, sbtVersion))(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
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package xsbt.boot
|
||||
|
||||
/*
|
||||
Project does not exist, create new project? [y/N/s] y
|
||||
Name:
|
||||
Organization []:
|
||||
Version [1.0]:
|
||||
Scala version [2.7.5]:
|
||||
sbt version [0.7]:
|
||||
*/
|
||||
import java.io.File
|
||||
/** Constants related to reading/writing the build.properties file in a project.
|
||||
* See BootConfiguration for general constants used by the loader. */
|
||||
private object ProjectProperties
|
||||
{
|
||||
/** The properties key for storing the name of the project.*/
|
||||
val NameKey = "project.name"
|
||||
/** The properties key for storing the organization of the project.*/
|
||||
val OrganizationKey = "project.organization"
|
||||
/** The properties key for storing the version of the project.*/
|
||||
val VersionKey = "project.version"
|
||||
/** The properties key for storing the version of Scala used with the project.*/
|
||||
val ScalaVersionKey = "scala.version"
|
||||
/** The properties key for storing the version of sbt used to build the project.*/
|
||||
val SbtVersionKey = "sbt.version"
|
||||
/** The properties key to communicate to the main component of sbt that the project
|
||||
* should be initialized after being loaded, typically by creating a default directory structure.*/
|
||||
val InitializeProjectKey = "project.initialize"
|
||||
/** The properties key that configures the project to be flattened a bit for use by quick throwaway projects.*/
|
||||
val ScratchKey = "project.scratch"
|
||||
|
||||
/** The label used when prompting for the name of the user's project.*/
|
||||
val NameLabel = "Name"
|
||||
/** The label used when prompting for the organization of the user's project.*/
|
||||
val OrganizationLabel = "Organization"
|
||||
/** The label used when prompting for the version of the user's project.*/
|
||||
val VersionLabel = "Version"
|
||||
/** The label used when prompting for the version of Scala to use for the user's project.*/
|
||||
val ScalaVersionLabel = "Scala version"
|
||||
/** The label used when prompting for the version of sbt to use for the user's project.*/
|
||||
val SbtVersionLabel = "sbt version"
|
||||
|
||||
/** The default organization of the new user project when the user doesn't explicitly specify one when prompted.*/
|
||||
val DefaultOrganization = ""
|
||||
/** The default version of the new user project when the user doesn't explicitly specify a version when prompted.*/
|
||||
val DefaultVersion = "1.0"
|
||||
/** The default version of sbt when the user doesn't explicitly specify a version when prompted.*/
|
||||
val DefaultSbtVersion = "0.7"
|
||||
/** The default version of Scala when the user doesn't explicitly specify a version when prompted.*/
|
||||
val DefaultScalaVersion = "2.7.5"
|
||||
|
||||
// sets up the project properties for a throwaway project (flattens src and lib to the root project directory)
|
||||
def scratch(file: File)
|
||||
{
|
||||
withProperties(file) { properties =>
|
||||
for( (key, _, default, _) <- propertyDefinitions(false))
|
||||
properties(key) = default.getOrElse("scratch")
|
||||
properties(ScratchKey) = true.toString
|
||||
}
|
||||
}
|
||||
// returns (scala version, sbt version)
|
||||
def apply(file: File, setInitializeProject: Boolean): (String, String) = applyImpl(file, setInitializeProject, Nil)
|
||||
def forcePrompt(file: File, propertyKeys: String*) = applyImpl(file, false, propertyKeys)
|
||||
private def applyImpl(file: File, setInitializeProject: Boolean, propertyKeys: Iterable[String]): (String, String) =
|
||||
{
|
||||
val organizationOptional = file.exists
|
||||
withProperties(file) { properties =>
|
||||
properties -= propertyKeys
|
||||
|
||||
prompt(properties, organizationOptional)
|
||||
if(setInitializeProject)
|
||||
properties(InitializeProjectKey) = true.toString
|
||||
}
|
||||
}
|
||||
// (key, label, defaultValue, promptRequired)
|
||||
private def propertyDefinitions(organizationOptional: Boolean) =
|
||||
(NameKey, NameLabel, None, true) ::
|
||||
(OrganizationKey, OrganizationLabel, Some(DefaultOrganization), !organizationOptional) ::
|
||||
(VersionKey, VersionLabel, Some(DefaultVersion), true) ::
|
||||
(ScalaVersionKey, ScalaVersionLabel, Some(DefaultScalaVersion), true) ::
|
||||
(SbtVersionKey, SbtVersionLabel, Some(DefaultSbtVersion), true) ::
|
||||
Nil
|
||||
private def prompt(fill: ProjectProperties, organizationOptional: Boolean)
|
||||
{
|
||||
for( (key, label, default, promptRequired) <- propertyDefinitions(organizationOptional))
|
||||
{
|
||||
val value = fill(key)
|
||||
if(value == null && promptRequired)
|
||||
fill(key) = readLine(label, default)
|
||||
}
|
||||
}
|
||||
private def withProperties(file: File)(f: ProjectProperties => Unit) =
|
||||
{
|
||||
val properties = new ProjectProperties(file)
|
||||
f(properties)
|
||||
properties.save
|
||||
(properties(ScalaVersionKey), properties(SbtVersionKey))
|
||||
}
|
||||
private def readLine(label: String, default: Option[String]): String =
|
||||
{
|
||||
val prompt =
|
||||
default match
|
||||
{
|
||||
case Some(d) => "%s [%s]: ".format(label, d)
|
||||
case None => "%s: ".format(label)
|
||||
}
|
||||
SimpleReader.readLine(prompt) orElse default match
|
||||
{
|
||||
case Some(line) => line
|
||||
case None => throw new BootException("Project not loaded: " + label + " not specified.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import java.io.{FileInputStream, FileOutputStream}
|
||||
import java.util.Properties
|
||||
private class ProjectProperties(file: File) extends NotNull
|
||||
{
|
||||
private[this] var modified = false
|
||||
private[this] val properties = new Properties
|
||||
if(file.exists)
|
||||
{
|
||||
val in = new FileInputStream(file)
|
||||
try { properties.load(in) } finally { in.close() }
|
||||
}
|
||||
|
||||
def update(key: String, value: String)
|
||||
{
|
||||
modified = true
|
||||
properties.setProperty(key, value)
|
||||
}
|
||||
def apply(key: String) = properties.getProperty(key)
|
||||
def save()
|
||||
{
|
||||
if(modified)
|
||||
{
|
||||
file.getParentFile.mkdirs()
|
||||
val out = new FileOutputStream(file)
|
||||
try { properties.store(out, "Project Properties") } finally { out.close() }
|
||||
modified = false
|
||||
}
|
||||
}
|
||||
def -= (keys: Iterable[String]) { for(key <- keys) properties.remove(key) }
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package xsbt.boot
|
||||
|
||||
import jline.ConsoleReader
|
||||
object SimpleReader extends NotNull
|
||||
{
|
||||
protected[this] val reader =
|
||||
{
|
||||
val cr = new ConsoleReader
|
||||
cr.setBellEnabled(false)
|
||||
cr
|
||||
}
|
||||
def readLine(prompt: String) =
|
||||
reader.readLine(prompt) match
|
||||
{
|
||||
case null => None
|
||||
case x =>
|
||||
val trimmed = x.trim
|
||||
if(trimmed.isEmpty)
|
||||
None
|
||||
else
|
||||
Some(trimmed)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package xsbt.boot
|
||||
|
||||
import java.io.{File, FileWriter, PrintWriter, Writer}
|
||||
|
||||
import org.apache.ivy.{core, plugins, util, Ivy}
|
||||
import core.LogOptions
|
||||
import core.cache.DefaultRepositoryCacheManager
|
||||
import core.event.EventManager
|
||||
import core.module.id.ModuleRevisionId
|
||||
import core.module.descriptor.{Configuration, DefaultDependencyDescriptor, DefaultModuleDescriptor, ModuleDescriptor}
|
||||
import core.report.ResolveReport
|
||||
import core.resolve.{ResolveEngine, ResolveOptions}
|
||||
import core.retrieve.{RetrieveEngine, RetrieveOptions}
|
||||
import core.sort.SortEngine
|
||||
import core.settings.IvySettings
|
||||
import plugins.resolver.{ChainResolver, FileSystemResolver, IBiblioResolver, URLResolver}
|
||||
import util.{DefaultMessageLogger, Message}
|
||||
|
||||
import BootConfiguration._
|
||||
|
||||
private object UpdateTarget extends Enumeration
|
||||
{
|
||||
val UpdateScala, UpdateSbt = Value
|
||||
}
|
||||
import UpdateTarget.{UpdateSbt, UpdateScala}
|
||||
|
||||
/** Ensures that the Scala and sbt jars exist for the given versions or else downloads them.*/
|
||||
final class Update(bootDirectory: File, sbtVersion: String, scalaVersion: String)
|
||||
{
|
||||
private def logFile = new File(bootDirectory, UpdateLogName)
|
||||
/** A Writer to use to write the full logging information to a file for debugging. **/
|
||||
private lazy val logWriter =
|
||||
{
|
||||
bootDirectory.mkdirs
|
||||
new PrintWriter(new FileWriter(logFile))
|
||||
}
|
||||
|
||||
/** The main entry point of this class for use by the Update module. It runs Ivy */
|
||||
def apply(target: UpdateTarget.Value)
|
||||
{
|
||||
Message.setDefaultLogger(new SbtIvyLogger(logWriter))
|
||||
try { update(target) }
|
||||
catch
|
||||
{
|
||||
case e: Exception =>
|
||||
e.printStackTrace(logWriter)
|
||||
log(e.toString)
|
||||
println(" (see " + logFile + " for complete log)")
|
||||
}
|
||||
finally { logWriter.close() }
|
||||
}
|
||||
/** Runs update for the specified target (updates either the scala or sbt jars for building the project) */
|
||||
private def update(target: UpdateTarget.Value)
|
||||
{
|
||||
val settings = new IvySettings
|
||||
val ivy = Ivy.newInstance(settings)
|
||||
ivy.pushContext()
|
||||
try { update(target, settings) }
|
||||
finally { ivy.popContext() }
|
||||
}
|
||||
private def update(target: UpdateTarget.Value, settings: IvySettings)
|
||||
{
|
||||
import Configuration.Visibility.PUBLIC
|
||||
// the actual module id here is not that important
|
||||
val moduleID = new DefaultModuleDescriptor(createID(SbtOrg, "boot", "1.0"), "release", null, false)
|
||||
moduleID.setLastModified(System.currentTimeMillis)
|
||||
moduleID.addConfiguration(new Configuration(DefaultIvyConfiguration, PUBLIC, "", Array(), true, null))
|
||||
// add dependencies based on which target needs updating
|
||||
target match
|
||||
{
|
||||
case UpdateScala =>
|
||||
addDependency(moduleID, ScalaOrg, CompilerModuleName, scalaVersion)
|
||||
addDependency(moduleID, ScalaOrg, LibraryModuleName, scalaVersion)
|
||||
update(settings, moduleID, target)
|
||||
case UpdateSbt =>
|
||||
addDependency(moduleID, SbtOrg, SbtModuleName + "_" + scalaVersion, sbtVersion)
|
||||
update(settings, moduleID, target)
|
||||
}
|
||||
}
|
||||
/** Runs the resolve and retrieve for the given moduleID, which has had its dependencies added already. */
|
||||
private def update(settings: IvySettings, moduleID: DefaultModuleDescriptor, target: UpdateTarget.Value)
|
||||
{
|
||||
val eventManager = new EventManager
|
||||
addResolvers(settings, scalaVersion, target)
|
||||
settings.setDefaultConflictManager(settings.getConflictManager(ConflictManagerName))
|
||||
settings.setBaseDir(bootDirectory)
|
||||
resolve(settings, eventManager, moduleID)
|
||||
retrieve(settings, eventManager, moduleID, target)
|
||||
}
|
||||
private def createID(organization: String, name: String, revision: String) =
|
||||
ModuleRevisionId.newInstance(organization, name, revision)
|
||||
/** Adds the given dependency to the default configuration of 'moduleID'. */
|
||||
private def addDependency(moduleID: DefaultModuleDescriptor, organization: String, name: String, revision: String)
|
||||
{
|
||||
val dep = new DefaultDependencyDescriptor(moduleID, createID(organization, name, revision), false, false, true)
|
||||
dep.addDependencyConfiguration(DefaultIvyConfiguration, "default")
|
||||
moduleID.addDependency(dep)
|
||||
}
|
||||
private def resolve(settings: IvySettings, eventManager: EventManager, module: ModuleDescriptor)
|
||||
{
|
||||
val resolveOptions = new ResolveOptions
|
||||
// this reduces the substantial logging done by Ivy, including the progress dots when downloading artifacts
|
||||
resolveOptions.setLog(LogOptions.LOG_DOWNLOAD_ONLY)
|
||||
val resolveEngine = new ResolveEngine(settings, eventManager, new SortEngine(settings))
|
||||
val resolveReport = resolveEngine.resolve(module, resolveOptions)
|
||||
if(resolveReport.hasError)
|
||||
{
|
||||
logExceptions(resolveReport)
|
||||
println(Set(resolveReport.getAllProblemMessages.toArray: _*).mkString(System.getProperty("line.separator")))
|
||||
throw new BootException("Error retrieving required libraries")
|
||||
}
|
||||
}
|
||||
/** Exceptions are logged to the update log file. */
|
||||
private def logExceptions(report: ResolveReport)
|
||||
{
|
||||
for(unresolved <- report.getUnresolvedDependencies)
|
||||
{
|
||||
val problem = unresolved.getProblem
|
||||
if(problem != null)
|
||||
problem.printStackTrace(logWriter)
|
||||
}
|
||||
}
|
||||
/** Retrieves resolved dependencies using the given target to determine the location to retrieve to. */
|
||||
private def retrieve(settings: IvySettings, eventManager: EventManager, module: ModuleDescriptor, target: UpdateTarget.Value)
|
||||
{
|
||||
val retrieveOptions = new RetrieveOptions
|
||||
val retrieveEngine = new RetrieveEngine(settings, eventManager)
|
||||
val pattern =
|
||||
target match
|
||||
{
|
||||
// see BuildConfiguration
|
||||
case UpdateSbt => sbtRetrievePattern(sbtVersion)
|
||||
case UpdateScala => scalaRetrievePattern
|
||||
}
|
||||
retrieveEngine.retrieve(module.getModuleRevisionId, pattern, retrieveOptions);
|
||||
}
|
||||
/** Add the scala tools repositories and a URL resolver to download sbt from the Google code project.*/
|
||||
private def addResolvers(settings: IvySettings, scalaVersion: String, target: UpdateTarget.Value)
|
||||
{
|
||||
val newDefault = new ChainResolver
|
||||
newDefault.setName("redefined-public")
|
||||
newDefault.add(localResolver(settings.getDefaultIvyUserDir.getAbsolutePath))
|
||||
newDefault.add(mavenLocal)
|
||||
newDefault.add(mavenMainResolver)
|
||||
target match
|
||||
{
|
||||
case UpdateSbt =>
|
||||
newDefault.add(sbtResolver(scalaVersion))
|
||||
case UpdateScala =>
|
||||
newDefault.add(mavenResolver("Scala-Tools Maven2 Repository", "http://scala-tools.org/repo-releases"))
|
||||
newDefault.add(mavenResolver("Scala-Tools Maven2 Snapshots Repository", "http://scala-tools.org/repo-snapshots"))
|
||||
}
|
||||
onDefaultRepositoryCacheManager(settings)(_.setUseOrigin(true))
|
||||
settings.addResolver(newDefault)
|
||||
settings.setDefaultResolver(newDefault.getName)
|
||||
}
|
||||
private def onDefaultRepositoryCacheManager(settings: IvySettings)(f: DefaultRepositoryCacheManager => Unit)
|
||||
{
|
||||
settings.getDefaultRepositoryCacheManager match
|
||||
{
|
||||
case manager: DefaultRepositoryCacheManager => f(manager)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
/** Uses the pattern defined in BuildConfiguration to download sbt from Google code.*/
|
||||
private def sbtResolver(scalaVersion: String) =
|
||||
{
|
||||
val pattern = sbtResolverPattern(scalaVersion)
|
||||
val resolver = new URLResolver
|
||||
resolver.setName("Sbt Repository")
|
||||
resolver.addIvyPattern(pattern)
|
||||
resolver.addArtifactPattern(pattern)
|
||||
resolver
|
||||
}
|
||||
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) =
|
||||
{
|
||||
val resolver = defaultMavenResolver(name)
|
||||
resolver.setRoot(root)
|
||||
resolver
|
||||
}
|
||||
/** Creates a resolver for Maven Central.*/
|
||||
private def mavenMainResolver = defaultMavenResolver("Maven Central")
|
||||
/** Creates a maven-style resolver with the default root.*/
|
||||
private def defaultMavenResolver(name: String) =
|
||||
{
|
||||
val resolver = new IBiblioResolver
|
||||
resolver.setName(name)
|
||||
resolver.setM2compatible(true)
|
||||
resolver
|
||||
}
|
||||
private def localResolver(ivyUserDirectory: String) =
|
||||
{
|
||||
val localIvyRoot = ivyUserDirectory + "/local"
|
||||
val artifactPattern = localIvyRoot + "/" + LocalArtifactPattern
|
||||
val ivyPattern = localIvyRoot + "/" + LocalIvyPattern
|
||||
val resolver = new FileSystemResolver
|
||||
resolver.setName(LocalIvyName)
|
||||
resolver.addIvyPattern(ivyPattern)
|
||||
resolver.addArtifactPattern(artifactPattern)
|
||||
resolver
|
||||
}
|
||||
/** Logs the given message to a file and to the console. */
|
||||
private def log(msg: String) =
|
||||
{
|
||||
try { logWriter.println(msg) }
|
||||
catch { case e: Exception => System.err.println("Error writing to update log file: " + e.toString) }
|
||||
println(msg)
|
||||
}
|
||||
}
|
||||
/** A custom logger for Ivy to ignore the messages about not finding classes
|
||||
* intentionally filtered using proguard. */
|
||||
private final class SbtIvyLogger(logWriter: PrintWriter) extends DefaultMessageLogger(Message.MSG_INFO) with NotNull
|
||||
{
|
||||
private val ignorePrefix = "impossible to define"
|
||||
override def log(msg: String, level: Int)
|
||||
{
|
||||
logWriter.println(msg)
|
||||
if(level <= getLevel && msg != null && !msg.startsWith(ignorePrefix))
|
||||
System.out.println(msg)
|
||||
}
|
||||
override def rawlog(msg: String, level: Int) { log(msg, level) }
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package xsbti.boot;
|
||||
public interface Exit extends MainResult
|
||||
{
|
||||
public int code();
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package xsbti.boot;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface Launcher extends ScalaProvider
|
||||
{
|
||||
public static final int InterfaceVersion = 1;
|
||||
|
||||
public void boot(String[] args);
|
||||
public MainResult checkAndLoad(String[] args);
|
||||
public MainResult load(String[] args);
|
||||
public MainResult run(ClassLoader sbtLoader, SbtConfiguration configuration);
|
||||
|
||||
public File ProjectDirectory();
|
||||
public File BootDirectory();
|
||||
public File PropertiesFile();
|
||||
|
||||
public Launcher launcher(File base, String mainClassName);
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package xsbti.boot;
|
||||
|
||||
public interface MainResult {}
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package xsbti.boot;
|
||||
public interface Reboot extends MainResult
|
||||
{
|
||||
public String[] arguments();
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package xsbti.boot;
|
||||
|
||||
public interface SbtConfiguration
|
||||
{
|
||||
public String[] arguments();
|
||||
public String scalaVersion();
|
||||
public String sbtVersion();
|
||||
public Launcher launcher();
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package xsbti.boot;
|
||||
|
||||
public interface SbtMain
|
||||
{
|
||||
public MainResult run(SbtConfiguration configuration);
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package xsbti.boot;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface ScalaProvider
|
||||
{
|
||||
public ClassLoader getScalaLoader(String scalaVersion);
|
||||
public File getScalaHome(String scalaVersion);
|
||||
}
|
||||
|
|
@ -2,6 +2,9 @@ import sbt._
|
|||
|
||||
class XSbt(info: ProjectInfo) extends ParentProject(info)
|
||||
{
|
||||
val launchInterfaceSub = project(launchPath / "interface", "Launcher Interface", new InterfaceProject(_))
|
||||
val launchSub = project(launchPath, "Launcher", new LaunchProject(_), launchInterfaceSub)
|
||||
|
||||
val commonDeps = project("common", "Dependencies", new CommonDependencies(_))
|
||||
val interfaceSub = project("interface", "Interface", new InterfaceProject(_))
|
||||
|
||||
|
|
@ -19,9 +22,14 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
|
|||
val cacheSub = project("cache", "Cache", new CacheProject(_), taskSub, ioSub)
|
||||
val compilerSub = project(compilePath, "Compile", new Base(_), interfaceSub, ivySub, ioSub, compilerInterfaceSub)
|
||||
|
||||
def launchPath = path("launch")
|
||||
def utilPath = path("util")
|
||||
def compilePath = path("compile")
|
||||
|
||||
class LaunchProject(info: ProjectInfo) extends Base(info)
|
||||
{
|
||||
val ivy = "org.apache.ivy" % "ivy" % "2.0.0"
|
||||
}
|
||||
class CommonDependencies(info: ProjectInfo) extends DefaultProject(info)
|
||||
{
|
||||
val sc = "org.scala-tools.testing" % "scalacheck" % "1.5" % "test->default"
|
||||
|
|
|
|||
Loading…
Reference in New Issue