Turned sbt launcher into a general Scala application launcher as described in launch.specification

This commit is contained in:
Mark Harrah 2009-09-27 14:39:26 -04:00
parent 56e96c3f49
commit b2fdc07505
28 changed files with 308 additions and 290 deletions

View File

@ -35,9 +35,9 @@ class AnalyzingCompiler(scalaInstance: ScalaInstance, manager: ComponentManager)
new DualLoader(scalaLoader, notXsbtiFilter, x => true, sbtLoader, xsbtiFilter, x => false)
}
override def toString = "Analyzing compiler (Scala " + scalaInstance.actualVersion + ")"
}
}/*
object AnalyzingCompiler
{
def apply(scalaVersion: String, provider: xsbti.ScalaProvider, manager: ComponentManager): AnalyzingCompiler =
new AnalyzingCompiler(ScalaInstance(scalaVersion, provider), manager)
}
}*/

View File

@ -27,12 +27,13 @@ class ComponentCompiler(compiler: RawCompiler, manager: ComponentManager)
protected def compileAndInstall(id: String, binID: String): File =
{
val srcID = id + srcExtension
val binaryDirectory = manager.location(binID)
createDirectory(binaryDirectory)
val targetJar = new File(binaryDirectory, id + ".jar")
compileSources(manager.files(srcID), targetJar, id)
manager.cache(binID)
targetJar
withTemporaryDirectory { binaryDirectory =>
val targetJar = new File(binaryDirectory, id + ".jar")
compileSources(manager.files(srcID), targetJar, id)
manager.define(binID, Seq(targetJar))
manager.cache(binID)
}
manager.file(binID)
}
/** Extract sources from source jars, compile them with the xsbti interfaces on the classpath, and package the compiled classes and
* any resources from the source jars into a final jar.*/

View File

@ -21,12 +21,8 @@ object ScalaInstance
{
val VersionPrefix = "version "
/** Creates a ScalaInstance using the given provider to obtain the jars and loader.*/
def apply(version: String, provider: xsbti.ScalaProvider) =
{
val scalaLibDirectory = provider.getScalaHome(version)
// these get the locations of the scala jars given the location of the lib/ directory
val libraryJar = new File(scalaLibDirectory, "scala-library.jar")
val compilerJar = new File(scalaLibDirectory, "scala-compiler.jar")
new ScalaInstance(version, provider.getScalaLoader(version), libraryJar, compilerJar)
}
def apply(version: String, launcher: xsbti.Launcher): ScalaInstance =
apply(version, launcher.getScala(version))
def apply(version: String, provider: xsbti.ScalaProvider): ScalaInstance =
new ScalaInstance(version, provider.loader, provider.libraryJar, provider.compilerJar)
}

View File

@ -34,34 +34,23 @@ object WithCompiler
val log = new TestIvyLogger with CompileLogger
log.setLevel(Level.Debug)
log.bufferQuietly {
FileUtilities.withTemporaryDirectory { temp =>
val launch = new xsbt.boot.Launch(temp)
val sbtVersion = xsbti.Versions.Sbt
val manager = new ComponentManager(launch.getSbtHome(sbtVersion, scalaVersion), log)
prepare(manager, ComponentCompiler.compilerInterfaceSrcID, "CompilerInterface.scala")
prepare(manager, ComponentCompiler.xsbtiID, classOf[xsbti.AnalysisCallback])
val result = f(AnalyzingCompiler(scalaVersion, launch, manager), log)
launch.clearScalaLoaderCache
System.gc()
System.gc()
System.gc()
result
boot.LaunchTest.withLauncher { launch =>
FileUtilities.withTemporaryDirectory { componentDirectory =>
val manager = new ComponentManager(new boot.ComponentProvider(componentDirectory), log)
prepare(manager, ComponentCompiler.compilerInterfaceSrcID, "CompilerInterface.scala")
prepare(manager, ComponentCompiler.xsbtiID, classOf[xsbti.AnalysisCallback])
f(new AnalyzingCompiler(ScalaInstance(scalaVersion, launch), manager), log)
}
}
}
}
private def prepare(manager: ComponentManager, id: String, resource: Class[_]): Unit =
{
val src = FileUtilities.classLocationFile(resource)
prepare(manager, id, src)
}
manager.define(id, FileUtilities.classLocationFile(resource) :: Nil)
private def prepare(manager: ComponentManager, id: String, resource: String): Unit =
{
val src = getClass.getClassLoader.getResource(resource)
if(src eq null)
error("Resource not found: " + resource)
prepare(manager, id, FileUtilities.asFile(src))
manager.define(id, FileUtilities.asFile(src) :: Nil)
}
import Paths._
private def prepare(manager: ComponentManager, id: String, file: File): Unit =
FileUtilities.copy(file x FileMapper.flat(manager.location(id)))
}

View File

@ -5,7 +5,7 @@ package xsbti;
public interface Versions
{
public static final String Sbt = "0.7";
public static final String Sbt = "0.7.0_13"; // keep in sync with LauncherProject in the XSbt project definition;
public static final int Interface = 1;
public static final int BootInterface = 1;
}

View File

@ -1,7 +1,6 @@
package xsbt
import java.io.File
import xsbti.Versions
/** A component manager provides access to the pieces of xsbt that are distributed as components.
* There are two types of components. The first type is compiled subproject jars with their dependencies.
@ -12,30 +11,13 @@ import xsbti.Versions
* This is used for compiled source jars so that the compilation need not be repeated for other projects on the same
* machine.
*/
class ComponentManager(baseDirectory: File, log: IvyLogger) extends NotNull
class ComponentManager(provider: xsbti.ComponentProvider, log: IvyLogger) extends NotNull
{
/** Get the location where files for component 'id' are stored. This method does not ensure that the component is retrieved from the
* local repository. By default, the location returned is is baseDirectory / id.*/
def location(id: String): File = new File(baseDirectory, id)
/** Get the location where files for component 'id' are stored. If the component has not yet been retrieved from the local repository,
* it is retrieved first. */
def directory(id: String): File =
{
val dir = location(id)
if(!dir.exists)
update(id)
dir
}
// get the contents of the given directory, wrapping a null result in an empty list.
private def contents(dir: File): Seq[File] =
{
val fs = dir.listFiles
if(fs == null) Nil else fs
}
/** Get all of the files for component 'id', throwing an exception if no files exist for the component. */
def files(id: String): Iterable[File] =
{
val fs = contents(directory(id))
val existing = provider.component(id)
val fs = if(existing.isEmpty) { update(id); provider.component(id) } else existing
if(!fs.isEmpty) fs else invalid("Could not find required component '" + id + "'")
}
/** Get the file for component 'id', throwing an exception if no files or multiple files exist for the component. */
@ -47,12 +29,13 @@ class ComponentManager(baseDirectory: File, log: IvyLogger) extends NotNull
private def invalid(msg: String) = throw new InvalidComponent(msg)
private def invalid(e: NotInCache) = throw new InvalidComponent(e.getMessage, e)
def define(id: String, files: Iterable[File]) = provider.defineComponent(id, files.toSeq.toArray)
/** Retrieve the file for component 'id' from the local repository. */
def update(id: String): Unit =
try { IvyCache.retrieveCachedJar(sbtModuleID(id), location(id), log) }
try { IvyCache.withCachedJar(sbtModuleID(id), log)(jar => define(id, Seq(jar)) ) }
catch { case e: NotInCache => invalid(e) }
def sbtModuleID(id: String) = ModuleID("org.scala-tools.sbt", id, Versions.Sbt)
def sbtModuleID(id: String) = ModuleID("org.scala-tools.sbt", id, xsbti.Versions.Sbt)
/** Install the files for component 'id' to the local repository. This is usually used after writing files to the directory returned by 'location'. */
def cache(id: String): Unit = IvyCache.cacheJar(sbtModuleID(id), file(id), log)
def clearCache(id: String): Unit = IvyCache.clearCachedJar(sbtModuleID(id), log)

View File

@ -91,7 +91,7 @@ private object ConvertResolver
setKeyFilePassword(password)
}
}
private def initializePatterns(resolver: AbstractPatternsBasedResolver, patterns: RepositoryHelpers.Patterns)
private def initializePatterns(resolver: AbstractPatternsBasedResolver, patterns: Patterns)
{
resolver.setM2compatible(patterns.isMavenCompatible)
patterns.ivyPatterns.foreach(resolver.addIvyPattern)

View File

@ -17,8 +17,10 @@ import plugins.repository.url.URLResource
private[xsbt] object CustomXmlParser extends XmlModuleDescriptorParser with NotNull
{
import XmlModuleDescriptorParser.Parser
class CustomParser(settings: IvySettings) extends Parser(CustomXmlParser, settings) with NotNull
class CustomParser(settings: IvySettings, defaultConfig: Option[String]) extends Parser(CustomXmlParser, settings) with NotNull
{
defaultConfig.foreach(x => setDefaultConfMapping("*->default(compile)"))
def setSource(url: URL) =
{
super.setResource(new URLResource(url))
@ -29,7 +31,6 @@ private[xsbt] object CustomXmlParser extends XmlModuleDescriptorParser with NotN
override def setResource(res: Resource) {}
override def setMd(md: DefaultModuleDescriptor) = super.setMd(md)
override def parseDepsConfs(confs: String, dd: DefaultDependencyDescriptor) = super.parseDepsConfs(confs, dd)
override def getDefaultConf = super.getDefaultConf
override def setDefaultConf(conf: String) = super.setDefaultConf(conf)
override def getDefaultConf = defaultConfig.getOrElse(super.getDefaultConf)
}
}

View File

@ -8,6 +8,7 @@ import Artifact.{defaultExtension, defaultType}
import java.io.File
import org.apache.ivy.{core, plugins, util, Ivy}
import core.IvyPatternHelper
import core.cache.DefaultRepositoryCacheManager
import core.module.descriptor.{DefaultArtifact, DefaultDependencyArtifactDescriptor, MDArtifact}
import core.module.descriptor.{DefaultDependencyDescriptor, DefaultModuleDescriptor, ModuleDescriptor}
@ -74,7 +75,7 @@ final class IvySbt(configuration: IvyConfiguration)
finally { ivy.popContext() }
}
final class Module(val moduleConfiguration: ModuleConfiguration) extends NotNull
final class Module(val moduleSettings: ModuleSettings) extends NotNull
{
def logger = configuration.log
def withModule[T](f: (Ivy,DefaultModuleDescriptor,String) => T): T =
@ -83,14 +84,14 @@ final class IvySbt(configuration: IvyConfiguration)
private lazy val (moduleDescriptor: DefaultModuleDescriptor, defaultConfig: String) =
{
val (baseModule, baseConfiguration) =
moduleConfiguration match
moduleSettings match
{
case ic: InlineConfiguration => configureInline(ic)
case ec: EmptyConfiguration => configureEmpty(ec.module)
case pc: PomConfiguration => readPom(pc.file, pc.validate)
case ifc: IvyFileConfiguration => readIvyFile(ifc.file, ifc.validate)
}
moduleConfiguration.ivyScala.foreach(IvyScala.checkModule(baseModule, baseConfiguration))
moduleSettings.ivyScala.foreach(IvyScala.checkModule(baseModule, baseConfiguration))
baseModule.getExtraAttributesNamespaces.asInstanceOf[java.util.Map[String,String]].put("e", "http://ant.apache.org/ivy/extra")
(baseModule, baseConfiguration)
}
@ -105,6 +106,7 @@ final class IvySbt(configuration: IvyConfiguration)
IvySbt.addArtifacts(moduleID, artifacts)
IvySbt.addDependencies(moduleID, dependencies, parser)
IvySbt.setModuleConfigurations(settings, moduleConfigurations)
IvySbt.addMainArtifact(moduleID)
(moduleID, parser.getDefaultConf)
}
@ -126,7 +128,7 @@ final class IvySbt(configuration: IvyConfiguration)
private def readIvyFile(ivyFile: File, validate: Boolean) =
{
val url = toURL(ivyFile)
val parser = new CustomXmlParser.CustomParser(settings)
val parser = new CustomXmlParser.CustomParser(settings, None)
parser.setValidate(validate)
parser.setSource(url)
parser.parse()
@ -168,6 +170,20 @@ private object IvySbt
settings.setDefaultResolver(newDefault.getName)
log.debug("Using repositories:\n" + resolvers.mkString("\n\t"))
}
private def setModuleConfigurations(settings: IvySettings, moduleConfigurations: Seq[ModuleConfiguration])
{
val existing = settings.getResolverNames
for(moduleConf <- moduleConfigurations)
{
import moduleConf._
import IvyPatternHelper._
import PatternMatcher._
if(!existing.contains(resolver.name))
settings.addResolver(ConvertResolver(resolver))
val attributes = javaMap(Map(MODULE_KEY -> name, ORGANISATION_KEY -> organization, REVISION_KEY -> revision))
settings.addModuleConfiguration(attributes, settings.getMatcher(EXACT_OR_REGEXP), resolver.name, null, null, null)
}
}
private def configureCache(settings: IvySettings, dir: Option[File])
{
val cacheDir = dir.getOrElse(settings.getDefaultRepositoryCacheBasedir())
@ -244,9 +260,8 @@ private object IvySbt
/** Parses the given in-memory Ivy file 'xml', using the existing 'moduleID' and specifying the given 'defaultConfiguration'. */
private def parseIvyXML(settings: IvySettings, xml: String, moduleID: DefaultModuleDescriptor, defaultConfiguration: String, validate: Boolean): CustomXmlParser.CustomParser =
{
val parser = new CustomXmlParser.CustomParser(settings)
val parser = new CustomXmlParser.CustomParser(settings, Some(defaultConfiguration))
parser.setMd(moduleID)
parser.setDefaultConf(defaultConfiguration)
parser.setValidate(validate)
parser.setInput(xml.getBytes)
parser.parse()

View File

@ -27,7 +27,7 @@ object IvyActions
{
module.logger.info("Installing " + dependency)
val options = new InstallOptions
options.setValidate(module.moduleConfiguration.validate)
options.setValidate(module.moduleSettings.validate)
options.setTransitive(dependency.isTransitive)
ivy.install(dependency.getDependencyRevisionId, from, to, options)
}
@ -50,10 +50,8 @@ object IvyActions
// todo: correct default configuration for extra dependencies
private def addLateDependencies(ivy: Ivy, module: DefaultModuleDescriptor, defaultConfiguration: String, extraDependencies: Iterable[ModuleID])
{
val parser = new CustomXmlParser.CustomParser(ivy.getSettings)
val parser = new CustomXmlParser.CustomParser(ivy.getSettings, Some(defaultConfiguration))
parser.setMd(module)
val defaultConf = if(defaultConfiguration.contains("->")) defaultConfiguration else (defaultConfiguration + "->default(compile)")
parser.setDefaultConf(defaultConf)
IvySbt.addDependencies(module, extraDependencies, parser)
}
private def getConfigurations(module: ModuleDescriptor, configurations: Option[Iterable[Configuration]]) =

View File

@ -40,19 +40,20 @@ object IvyCache
/** Clears the cache of the jar for the given ID.*/
def clearCachedJar(id: ModuleID, log: IvyLogger)
{
try { getCachedFile(id, log).delete }
try { withCachedJar(id, log)(_.delete) }
catch { case e: Exception => log.debug("Error cleaning cached jar: " + e.toString) }
}
/** Copies the cached jar for the given ID to the directory 'toDirectory'. If the jar is not in the cache, NotInCache is thrown.*/
def retrieveCachedJar(id: ModuleID, toDirectory: File, log: IvyLogger) =
{
val cachedFile = getCachedFile(id, log)
val copyTo = new File(toDirectory, cachedFile.getName)
FileUtil.copy(cachedFile, copyTo, null)
copyTo
}
/** Get the location of the cached jar for the given ID in the Ivy cache. If the jar is not in the cache, NotInCache is thrown.*/
def getCachedFile(id: ModuleID, log: IvyLogger): File =
withCachedJar(id, log) { cachedFile =>
val copyTo = new File(toDirectory, cachedFile.getName)
FileUtil.copy(cachedFile, copyTo, null)
copyTo
}
/** Get the location of the cached jar for the given ID in the Ivy cache. If the jar is not in the cache, NotInCache is thrown
* TODO: locking.*/
def withCachedJar[T](id: ModuleID, log: IvyLogger)(f: File => T): T =
{
val cachedFile =
try
@ -64,7 +65,7 @@ object IvyCache
}
catch { case e: Exception => throw new NotInCache(id, e) }
if(cachedFile.exists) cachedFile else throw new NotInCache(id)
if(cachedFile.exists) f(cachedFile) else throw new NotInCache(id)
}
/** Calls the given function with the default Ivy cache.*/
def withDefaultCache[T](log: IvyLogger)(f: DefaultRepositoryCacheManager => T): T =
@ -82,7 +83,7 @@ object IvyCache
{
val local = Resolver.defaultLocal
val paths = new IvyPaths(new File("."), None)
val conf = new IvyConfiguration(paths, Seq(local), log)
val conf = new IvyConfiguration(paths, Seq(local), Nil, log)
(new IvySbt(conf), local)
}
/** Creates a default jar artifact based on the given ID.*/

View File

@ -7,19 +7,20 @@ import java.io.File
import scala.xml.NodeSeq
final class IvyPaths(val baseDirectory: File, val cacheDirectory: Option[File]) extends NotNull
final class IvyConfiguration(val paths: IvyPaths, val resolvers: Seq[Resolver], val log: IvyLogger) extends NotNull
final class IvyConfiguration(val paths: IvyPaths, val resolvers: Seq[Resolver],
val moduleConfigurations: Seq[ModuleConfiguration], val log: IvyLogger) extends NotNull
sealed trait ModuleConfiguration extends NotNull
sealed trait ModuleSettings extends NotNull
{
def validate: Boolean
def ivyScala: Option[IvyScala]
}
final class IvyFileConfiguration(val file: File, val ivyScala: Option[IvyScala], val validate: Boolean) extends ModuleConfiguration
final class PomConfiguration(val file: File, val ivyScala: Option[IvyScala], val validate: Boolean) extends ModuleConfiguration
final class IvyFileConfiguration(val file: File, val ivyScala: Option[IvyScala], val validate: Boolean) extends ModuleSettings
final class PomConfiguration(val file: File, val ivyScala: Option[IvyScala], val validate: Boolean) extends ModuleSettings
final class InlineConfiguration(val module: ModuleID, val dependencies: Iterable[ModuleID], val ivyXML: NodeSeq,
val configurations: Iterable[Configuration], val defaultConfiguration: Option[Configuration], val ivyScala: Option[IvyScala],
val artifacts: Iterable[Artifact], val validate: Boolean) extends ModuleConfiguration
final class EmptyConfiguration(val module: ModuleID, val ivyScala: Option[IvyScala], val validate: Boolean) extends ModuleConfiguration
val artifacts: Iterable[Artifact], val validate: Boolean) extends ModuleSettings
final class EmptyConfiguration(val module: ModuleID, val ivyScala: Option[IvyScala], val validate: Boolean) extends ModuleSettings
object InlineConfiguration
{
def apply(module: ModuleID, dependencies: Iterable[ModuleID], artifacts: Iterable[Artifact]) =
@ -37,7 +38,7 @@ object InlineConfiguration
else
explicitConfigurations
}
object ModuleConfiguration
object ModuleSettings
{
def apply(ivyScala: Option[IvyScala], validate: Boolean, module: => ModuleID)(baseDirectory: File, log: IvyLogger) =
{

View File

@ -19,7 +19,7 @@ final case class ModuleID(organization: String, name: String, revision: String,
def from(url: String) = artifacts(Artifact(name, new URL(url)))
def classifier(c: String) = artifacts(Artifact(name, c))
def artifacts(newArtifacts: Artifact*) = ModuleID(organization, name, revision, configurations, isChanging, isTransitive, newArtifacts ++ explicitArtifacts, extraAttributes)
def extra(attributes: (String,String)*) = ModuleID(organization, name, revision, configurations, isChanging, isTransitive, explicitArtifacts, extraAttributes ++ attributes)
def extra(attributes: (String,String)*) = ModuleID(organization, name, revision, configurations, isChanging, isTransitive, explicitArtifacts, extraAttributes ++ ModuleID.checkE(attributes))
}
object ModuleID
{
@ -30,6 +30,10 @@ object ModuleID
ModuleID(organization, name, revision, configurations, isChanging, isTransitive, Nil)
def apply(organization: String, name: String, revision: String, configurations: Option[String], isChanging: Boolean, isTransitive: Boolean, explicitArtifacts: Seq[Artifact]): ModuleID =
ModuleID(organization, name, revision, configurations, isChanging, isTransitive, explicitArtifacts, Map.empty)
def checkE(attributes: Seq[(String, String)]) =
for ( (key, value) <- attributes) yield
if(key.startsWith("e:")) (key, value) else ("e:" + key, value)
}
sealed trait Resolver extends NotNull
{
@ -40,14 +44,20 @@ sealed case class MavenRepository(name: String, root: String) extends Resolver
override def toString = name + ": " + root
}
final class Patterns(val ivyPatterns: Seq[String], val artifactPatterns: Seq[String], val isMavenCompatible: Boolean) extends NotNull
{
private[xsbt] def mavenStyle(): Patterns = Patterns(ivyPatterns, artifactPatterns, true)
private[xsbt] def withIvys(patterns: Seq[String]): Patterns = Patterns(patterns ++ ivyPatterns, artifactPatterns, isMavenCompatible)
private[xsbt] def withArtifacts(patterns: Seq[String]): Patterns = Patterns(ivyPatterns, patterns ++ artifactPatterns, isMavenCompatible)
}
object Patterns
{
def apply(artifactPatterns: String*): Patterns = Patterns(true, artifactPatterns : _*)
def apply(isMavenCompatible: Boolean, artifactPatterns: String*): Patterns = Patterns(Nil, artifactPatterns, isMavenCompatible)
def apply(ivyPatterns: Seq[String], artifactPatterns: Seq[String], isMavenCompatible: Boolean): Patterns = new Patterns(ivyPatterns, artifactPatterns, isMavenCompatible)
}
object RepositoryHelpers
{
final case class Patterns(ivyPatterns: Seq[String], artifactPatterns: Seq[String], isMavenCompatible: Boolean) extends NotNull
{
private[xsbt] def mavenStyle(): Patterns = Patterns(ivyPatterns, artifactPatterns, true)
private[xsbt] def withIvys(patterns: Seq[String]): Patterns = Patterns(patterns ++ ivyPatterns, artifactPatterns, isMavenCompatible)
private[xsbt] def withArtifacts(patterns: Seq[String]): Patterns = Patterns(ivyPatterns, patterns ++ artifactPatterns, isMavenCompatible)
}
final case class SshConnection(authentication: Option[SshAuthentication], hostname: Option[String], port: Option[Int]) extends NotNull
{
def copy(authentication: Option[SshAuthentication]) = SshConnection(authentication, hostname, port)
@ -63,7 +73,7 @@ object RepositoryHelpers
final case class PasswordAuthentication(user: String, password: String) extends SshAuthentication
final case class KeyFileAuthentication(keyfile: File, password: String) extends SshAuthentication
}
import RepositoryHelpers.{Patterns, SshConnection, FileConfiguration}
import RepositoryHelpers.{SshConnection, FileConfiguration}
import RepositoryHelpers.{KeyFileAuthentication, PasswordAuthentication, SshAuthentication}
/** sbt interface to an Ivy repository based on patterns, which is most Ivy repositories.*/
@ -283,12 +293,7 @@ object Configurations
private[xsbt] val DefaultMavenConfiguration = defaultConfiguration(true)
private[xsbt] val DefaultIvyConfiguration = defaultConfiguration(false)
private[xsbt] def DefaultConfiguration(mavenStyle: Boolean) = if(mavenStyle) DefaultMavenConfiguration else DefaultIvyConfiguration
private[xsbt] def defaultConfiguration(mavenStyle: Boolean) =
{
val base = if(mavenStyle) Configurations.Compile else Configurations.Default
config(base.name + "->default(compile)")
}
private[xsbt] def defaultConfiguration(mavenStyle: Boolean) = if(mavenStyle) Configurations.Compile else Configurations.Default
private[xsbt] def removeDuplicates(configs: Iterable[Configuration]) = Set(scala.collection.mutable.Map(configs.map(config => (config.name, config)).toSeq: _*).values.toList: _*)
}
/** Represents an Ivy configuration. */
@ -337,6 +342,12 @@ object Artifact
Artifact(name, extract(name, defaultType), extract(name, defaultExtension), None, Nil, Some(file.toURI.toURL))
}
}
final case class ModuleConfiguration(organization: String, name: String, revision: String, resolver: Resolver) extends NotNull
object ModuleConfiguration
{
def apply(org: String, resolver: Resolver): ModuleConfiguration = apply(org, "*", "*", resolver)
def apply(org: String, name: String, resolver: Resolver): ModuleConfiguration = ModuleConfiguration(org, name, "*", resolver)
}
/*
object Credentials
{

View File

@ -14,34 +14,33 @@ object ComponentManagerTest extends Specification
}
"throw an exception if 'file' is called for an empty component" in {
withManager { manager =>
createDirectory(manager.location(TestID))
manager.define(TestID, Nil)
( manager.file(TestID) ) must throwA[InvalidComponent]
}
}
"return the file for a single-file component" in {
withManager { manager =>
createFiles(manager, TestID, "a") match { case Seq(x) =>
manager.file(TestID).getAbsoluteFile must beEqualTo(x.getAbsoluteFile)
}
val hash = defineFile(manager, TestID, "a")
checksum(manager.file(TestID)) must beEqualTo(hash)
}
}
"throw an exception if 'file' is called for multi-file component" in {
withManager { manager =>
createFiles(manager, TestID, "a", "b")
defineFiles(manager, TestID, "a", "b")
( manager.file(TestID) ) must throwA[InvalidComponent]
}
}
"return the files for a multi-file component" in {
withManager { manager =>
val files = createFiles(manager, TestID, "a", "b")
manager.files(TestID) must haveTheSameElementsAs(files)
val hashes = defineFiles(manager, TestID, "a", "b")
checksum(manager.files(TestID)) must haveTheSameElementsAs(hashes)
}
}
"return the files for a single-file component" in {
withManager { manager =>
val files = createFiles(manager, TestID, "a")
manager.files(TestID) must haveTheSameElementsAs(files)
val hashes = defineFiles(manager, TestID, "a")
checksum(manager.files(TestID)) must haveTheSameElementsAs(hashes)
}
}
"throw an exception if 'files' is called for a non-existing component" in {
@ -49,47 +48,33 @@ object ComponentManagerTest extends Specification
}
"properly cache a file and then retrieve it to an unresolved component" in {
withManager { manager =>
val file = createFile(manager, TestID, "a")
val hash = checksum(file)
withManager { definingManager =>
val hash = defineFile(definingManager, TestID, "a")
try
{
manager.cache(TestID)
delete(manager.location(TestID))
FileUtilities.listFiles(manager.location(TestID)).toList must haveSize(0)
checksum(manager.file(TestID)) must beEqualTo(hash)
definingManager.cache(TestID)
withManager { usingManager =>
checksum(usingManager.file(TestID)) must beEqualTo(hash)
}
}
finally { manager.clearCache(TestID) }
}
}
"not retrieve to a component already resolved" in {
withManager { manager =>
val file = createFile(manager, TestID, "a")
try
{
manager.cache(TestID)
val idDirectory = manager.location(TestID)
delete(idDirectory)
createDirectory(idDirectory)
manager.file(TestID) must throwA[InvalidComponent]
}
finally { manager.clearCache(TestID) }
finally { definingManager.clearCache(TestID) }
}
}
}
private def checksum(file: File) = ChecksumHelper.computeAsString(file, "sha1")
private def createFile(manager: ComponentManager, id: String, name: String): File = createFiles(manager, id, name).toList.head
private def createFiles(manager: ComponentManager, id: String, names: String*): Seq[File] =
{
val dir = manager.location(id)
createDirectory(dir)
names.map { name =>
val testFile = new File(dir, name)
touch(testFile)
testFile
private def checksum(files: Iterable[File]): Seq[String] = files.map(checksum).toSeq
private def checksum(file: File): String = if(file.exists) ChecksumHelper.computeAsString(file, "sha1") else ""
private def defineFile(manager: ComponentManager, id: String, name: String): String = createFile(manager, id, name)(checksum)
private def defineFiles(manager: ComponentManager, id: String, names: String*): Seq[String] = createFiles(manager, id, names : _*)(checksum)
private def createFile[T](manager: ComponentManager, id: String, name: String)(f: File => T): T = createFiles(manager, id, name)(files => f(files.toList.head))
private def createFiles[T](manager: ComponentManager, id: String, names: String*)(f: Seq[File] => T): T =
withTemporaryDirectory { dir =>
val files = names.map(name => new File(dir, name) )
files.foreach(writeRandomContent)
manager.define(id, files)
f(files)
}
}
private def writeRandomContent(file: File) = FileUtilities.write(file, randomString)
private def randomString = "asdf"
private def withManager[T](f: ComponentManager => T): T =
TestIvyLogger( logger => withTemporaryDirectory { temp => f(new ComponentManager(temp, logger)) } )
TestIvyLogger( logger => withTemporaryDirectory { temp => f(new ComponentManager(new xsbt.boot.ComponentProvider(temp), logger)) } )
}

View File

@ -7,7 +7,7 @@ The sbt launcher component is a self-contained jar that boots a Scala applicatio
The launcher may be configured in the following ways in increasing order of precedence:
* Replace the /sbt/sbt.launch.config file in the jar
* Put a configuration file named sbt.launch.config file on the classpath. Put it in the classpath root without the /sbt prefix.
* Specify the location of an alternate configuration on the command line. The alternate configuration option is specified as the first argument to the launcher. This option is the path to the configuration file preceeded by '@'. Resolution of a relative path is first attempted against the current working directory, then against the user's home directory, and then against the directory containing the launcher jar. An error is generated if none of these attempts succeed.
* Specify the location of an alternate configuration on the command line. This can be done by either specifying the location as the system property 'sbt.boot.properties' or as the first argument to the launcher prefixed by '@'. The system property has lower precedence. Resolution of a relative path is first attempted against the current working directory, then against the user's home directory, and then against the directory containing the launcher jar. An error is generated if none of these attempts succeed.
The configuration file is read as UTF-8 encoded and is defined by the following grammer (nl is a newline or end of file):

View File

@ -40,7 +40,7 @@ private object BootConfiguration
val JLinePackagePath = "jline/"
/** 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.List" :: "scala.tools.nsc.GenericRunnerCommand" :: Nil
val TestLoadScalaClasses = "scala.Option" :: "scala.tools.nsc.Global" :: Nil
val ScalaHomeProperty = "scala.home"
val UpdateLogName = "update.log"

View File

@ -10,12 +10,15 @@ object Configuration
args match
{
case Seq(head, tail @ _*) if head.startsWith("@")=> (configurationFromFile(head.substring(1), baseDirectory), tail)
case _ => (configurationOnClasspath, args)
case _ =>
val propertyConfigured = System.getProperty("sbt.boot.properties")
val url = if(propertyConfigured == null) configurationOnClasspath else configurationFromFile(propertyConfigured, baseDirectory)
(url , args)
}
def configurationOnClasspath: URL =
{
resourcePaths.toStream.map(getClass.getResource).find(_ ne null) getOrElse
( throw new BootException(resourcePaths.mkString("Could not finder sbt launch configuration. (Searched classpath for ", ",", ")")) )
( multiPartError("Could not finder sbt launch configuration. Searched classpath for:", resourcePaths))
}
def configurationFromFile(path: String, baseDirectory: File): URL =
{
@ -25,13 +28,15 @@ object Configuration
val exists = try { (new File(resolved)).exists } catch { case _: IllegalArgumentException => false }
if(exists) Some(resolved.toURL) else None
}
resolveAgainst(baseDirectory).toStream.flatMap(resolve).firstOption.getOrElse(throw new BootException("Could not find configuration file '" + path + "'."))
val against = resolveAgainst(baseDirectory)
against.toStream.flatMap(resolve).firstOption.getOrElse(multiPartError("Could not find configuration file '" + path + "'. Searched:", against))
}
def multiPartError[T](firstLine: String, lines: Seq[T]) = throw new BootException( (Seq(firstLine) ++ lines).mkString("\n\t") )
val ConfigurationFilename = "sbt.boot.properties"
val ConfigurationName = "sbt.boot.properties"
val JarBasePath = "/sbt/"
def userConfigurationPath = "/" + ConfigurationFilename
def defaultConfigurationPath = JarBasePath + ConfigurationFilename
def userConfigurationPath = "/" + ConfigurationName
def defaultConfigurationPath = JarBasePath + ConfigurationName
def resourcePaths = Seq(userConfigurationPath, defaultConfigurationPath)
def resolveAgainst(baseDirectory: File) = Seq(baseDirectory toURI, new File(System.getProperty("user.home")) toURI, classLocation(getClass).toURI)

View File

@ -1,37 +1,50 @@
package xsbt.boot
import java.io.File
import java.net.URL
object Launch
{
def apply(arguments: Seq[String]): Unit = apply( (new File("")).getAbsoluteFile, arguments )
def apply(currentDirectory: File, arguments: Seq[String])
{
val (configFile, newArguments) = Configuration.find(arguments, currentDirectory)
val parsed = Configuration.parse(configFile, currentDirectory)
val (explicit, finish) = ResolveVersions(parsed)
val launcher = new Launch(explicit)
launch(launcher, explicit.getScalaVersion, explicit.app.toID, currentDirectory, newArguments, finish)
}
final def launch(launcher: xsbti.Launcher, scalaVersion: String, app: xsbti.ApplicationID, workingDirectory: File, arguments: Seq[String], finish: () => Unit)
def apply(arguments: Seq[String]): Unit = apply( (new File("")).getAbsoluteFile , arguments )
def apply(currentDirectory: File, arguments: Seq[String]): Unit =
Configuration.find(arguments, currentDirectory) match { case (configLocation, newArguments) => configured(currentDirectory, configLocation, newArguments) }
def configured(currentDirectory: File, configLocation: URL, arguments: Seq[String]): Unit =
parsed(currentDirectory, Configuration.parse(configLocation, currentDirectory) , arguments)
def parsed(currentDirectory: File, parsed: LaunchConfiguration, arguments: Seq[String]): Unit =
ResolveVersions(parsed) match { case (resolved, finish) => explicit(currentDirectory, resolved, arguments, finish) }
def explicit(currentDirectory: File, explicit: LaunchConfiguration, arguments: Seq[String], setupComplete: () => Unit): Unit =
launch( run(new Launch(explicit.boot.directory, explicit.repositories)) ) (
RunConfiguration(explicit.getScalaVersion, explicit.app.toID, currentDirectory, arguments, setupComplete) )
final def run(launcher: xsbti.Launcher)(config: RunConfiguration): xsbti.MainResult =
{
import config._
val scalaProvider: xsbti.ScalaProvider = launcher.getScala(scalaVersion)
val appProvider: xsbti.AppProvider = scalaProvider.app(app)
val appConfig: xsbti.AppConfiguration = new AppConfiguration(arguments.toArray, workingDirectory, appProvider)
val main = appProvider.newMain()
finish()
main.run(appConfig) match
setupComplete()
main.run(appConfig)
}
final def launch(run: RunConfiguration => xsbti.MainResult)(config: RunConfiguration)
{
run(config) match
{
case e: xsbti.Exit => System.exit(e.code)
case r: xsbti.Reboot => launch(launcher, r.scalaVersion, r.app, r.baseDirectory, r.arguments, () => ())
case r: xsbti.Reboot => launch(run)(RunConfiguration(r.scalaVersion, r.app, r.baseDirectory, r.arguments, () => ()))
case x => throw new BootException("Invalid main result: " + x + (if(x eq null) "" else " (class: " + x.getClass + ")"))
}
}
}
case class RunConfiguration(scalaVersion: String, app: xsbti.ApplicationID, workingDirectory: File, arguments: Seq[String], setupComplete: () => Unit) extends NotNull
import BootConfiguration.{appDirectoryName, baseDirectoryName, ScalaDirectoryName, TestLoadScalaClasses}
class Launch(val configuration: LaunchConfiguration) extends xsbti.Launcher
class Launch(val bootDirectory: File, repositories: Seq[Repository]) extends xsbti.Launcher
{
import scala.collection.mutable.HashMap
private val scalaProviders = new HashMap[String, ScalaProvider]
@ -42,12 +55,15 @@ class Launch(val configuration: LaunchConfiguration) extends xsbti.Launcher
def launcher: xsbti.Launcher = Launch.this
lazy val parentLoader = new BootFilteredLoader(getClass.getClassLoader)
lazy val configuration = Launch.this.configuration.withScalaVersion(version)
lazy val libDirectory = new File(configuration.boot.directory, baseDirectoryName(version))
lazy val configuration = new UpdateConfiguration(bootDirectory, version, repositories)
lazy val libDirectory = new File(configuration.bootDirectory, baseDirectoryName(version))
lazy val scalaHome = new File(libDirectory, ScalaDirectoryName)
def compilerJar = new File(scalaHome, "scala-compiler.jar")
def libraryJar = new File(scalaHome, "scala-library.jar")
def baseDirectories = Seq(scalaHome)
def testLoadClasses = TestLoadScalaClasses
def target = UpdateTarget.UpdateScala
def target = UpdateScala
def failLabel = "Scala " + version
def app(id: xsbti.ApplicationID): xsbti.AppProvider = new AppProvider(id)
@ -55,12 +71,12 @@ class Launch(val configuration: LaunchConfiguration) extends xsbti.Launcher
class AppProvider(val id: xsbti.ApplicationID) extends xsbti.AppProvider with Provider
{
def scalaProvider: xsbti.ScalaProvider = ScalaProvider.this
lazy val configuration = ScalaProvider.this.configuration.withApp(Application(id))
def configuration = ScalaProvider.this.configuration
lazy val appHome = new File(libDirectory, appDirectoryName(id, File.separator))
def parentLoader = ScalaProvider.this.loader
def baseDirectories = id.mainComponents.map(componentLocation) ++ Seq(appHome)
def baseDirectories = id.mainComponents.map(components.componentLocation) ++ Seq(appHome)
def testLoadClasses = Seq(id.mainClass)
def target = UpdateTarget.UpdateApp
def target = new UpdateApp(Application(id))
def failLabel = id.name + " " + id.version
lazy val mainClass: Class[T] forSome { type T <: xsbti.AppMain } =
@ -70,16 +86,20 @@ class Launch(val configuration: LaunchConfiguration) extends xsbti.Launcher
}
def newMain(): xsbti.AppMain = mainClass.newInstance
def componentLocation(id: String): File = new File(appHome, id)
def component(id: String) = GetJars(componentLocation(id) :: Nil)
def defineComponent(id: String, files: Array[File]) =
{
val location = componentLocation(id)
if(location.exists)
throw new BootException("Cannot redefine component. (id: " + id + ", files: " + files.mkString(","))
else
files.foreach(file => Copy(file, location))
}
lazy val components = new ComponentProvider(appHome)
}
}
}
class ComponentProvider(baseDirectory: File) extends xsbti.ComponentProvider
{
def componentLocation(id: String): File = new File(baseDirectory, id)
def component(id: String) = GetJars.wrapNull(componentLocation(id).listFiles).filter(_.isFile)
def defineComponent(id: String, files: Array[File]) =
{
val location = componentLocation(id)
if(location.exists)
throw new BootException("Cannot redefine component. ID: " + id + ", files: " + files.mkString(","))
else
Copy(files, location)
}
}

View File

@ -5,10 +5,10 @@ import java.net.{URL, URLClassLoader}
trait Provider extends NotNull
{
def configuration: LaunchConfiguration
def configuration: UpdateConfiguration
def baseDirectories: Seq[File]
def testLoadClasses: Seq[String]
def target: UpdateTarget.Value
def target: UpdateTarget
def failLabel: String
def parentLoader: ClassLoader

View File

@ -21,19 +21,17 @@ import util.{DefaultMessageLogger, Message}
import BootConfiguration._
object UpdateTarget extends Enumeration
{
val UpdateScala = Value("scala")
val UpdateApp = Value("app")
}
import UpdateTarget.{UpdateApp, UpdateScala}
sealed trait UpdateTarget extends NotNull { def tpe: String }
final object UpdateScala extends UpdateTarget { def tpe = "scala" }
final case class UpdateApp(id: Application) extends UpdateTarget { def tpe = "app" }
final class UpdateConfiguration(val bootDirectory: File, val scalaVersion: String, val repositories: Seq[Repository]) extends NotNull
/** Ensures that the Scala and application jars exist for the given versions or else downloads them.*/
final class Update(config: LaunchConfiguration)
final class Update(config: UpdateConfiguration)
{
private val bootDirectory = config.boot.directory
import config.{bootDirectory, repositories, scalaVersion}
bootDirectory.mkdirs
private val scalaVersion = config.getScalaVersion
private def logFile = new File(bootDirectory, UpdateLogName)
private val logWriter = new PrintWriter(new FileWriter(logFile))
@ -49,7 +47,7 @@ final class Update(config: LaunchConfiguration)
private lazy val ivy = Ivy.newInstance(settings)
/** The main entry point of this class for use by the Update module. It runs Ivy */
def apply(target: UpdateTarget.Value)
def apply(target: UpdateTarget)
{
Message.setDefaultLogger(new SbtIvyLogger(logWriter))
ivy.pushContext()
@ -68,11 +66,11 @@ final class Update(config: LaunchConfiguration)
}
}
/** Runs update for the specified target (updates either the scala or appliciation jars for building the project) */
private def update(target: UpdateTarget.Value)
private def update(target: UpdateTarget)
{
import IvyConfiguration.Visibility.PUBLIC
// the actual module id here is not that important
val moduleID = new DefaultModuleDescriptor(createID(SbtOrg, "boot-" + target, "1.0"), "release", null, false)
val moduleID = new DefaultModuleDescriptor(createID(SbtOrg, "boot-" + target.tpe, "1.0"), "release", null, false)
moduleID.setLastModified(System.currentTimeMillis)
moduleID.addConfiguration(new IvyConfiguration(DefaultIvyConfiguration, PUBLIC, "", Array(), true, null))
// add dependencies based on which target needs updating
@ -81,14 +79,14 @@ final class Update(config: LaunchConfiguration)
case UpdateScala =>
addDependency(moduleID, ScalaOrg, CompilerModuleName, scalaVersion, "default")
addDependency(moduleID, ScalaOrg, LibraryModuleName, scalaVersion, "default")
case UpdateApp =>
val resolvedName = if(config.app.crossVersioned) config.app.name + "_" + scalaVersion else config.app.name
addDependency(moduleID, config.app.groupID, resolvedName, config.app.getVersion, "runtime(default)")
case UpdateApp(app) =>
val resolvedName = if(app.crossVersioned) app.name + "_" + scalaVersion else app.name
addDependency(moduleID, app.groupID, resolvedName, app.getVersion, "runtime(default)")
}
update(moduleID, target)
}
/** Runs the resolve and retrieve for the given moduleID, which has had its dependencies added already. */
private def update(moduleID: DefaultModuleDescriptor, target: UpdateTarget.Value)
private def update(moduleID: DefaultModuleDescriptor, target: UpdateTarget)
{
val eventManager = new EventManager
resolve(eventManager, moduleID)
@ -128,16 +126,15 @@ final class Update(config: LaunchConfiguration)
}
}
/** Retrieves resolved dependencies using the given target to determine the location to retrieve to. */
private def retrieve(eventManager: EventManager, module: ModuleDescriptor, target: UpdateTarget.Value)
private def retrieve(eventManager: EventManager, module: ModuleDescriptor, target: UpdateTarget)
{
val retrieveOptions = new RetrieveOptions
val retrieveEngine = new RetrieveEngine(settings, eventManager)
val pattern =
target match
{
// see BootConfiguration
case UpdateApp => appRetrievePattern(config.app.toID)
case UpdateScala => scalaRetrievePattern
case UpdateApp(app) => appRetrievePattern(app.toID)
}
retrieveEngine.retrieve(module.getModuleRevisionId, baseDirectoryName(scalaVersion) + "/" + pattern, retrieveOptions)
}
@ -146,8 +143,8 @@ final class Update(config: LaunchConfiguration)
{
val newDefault = new ChainResolver
newDefault.setName("redefined-public")
if(config.repositories.isEmpty) throw new BootException("No repositories defined.")
config.repositories.foreach(repo => newDefault.add(toIvyRepository(settings, repo)))
if(repositories.isEmpty) throw new BootException("No repositories defined.")
repositories.foreach(repo => newDefault.add(toIvyRepository(settings, repo)))
onDefaultRepositoryCacheManager(settings)(_.setUseOrigin(true))
settings.addResolver(newDefault)
settings.setDefaultResolver(newDefault.getName)

View File

@ -10,15 +10,12 @@ object Using extends NotNull
object Copy
{
def apply(files: Array[File], to: File)
{
to.mkdirs()
files.foreach(file => apply(file, to))
}
def apply(file: File, to: File)
def apply(files: Seq[File], toDirectory: File): Unit = files.foreach(file => apply(file, toDirectory))
def apply(file: File, toDirectory: File)
{
toDirectory.mkdirs()
Using(new FileInputStream(file)) { in =>
Using(new FileOutputStream(new File(to, file.getName))) { out =>
Using(new FileOutputStream(new File(toDirectory, file.getName))) { out =>
transfer(in, out)
}
}

View File

@ -1,7 +1,5 @@
package xsbti;
import java.io.File;
public interface AppProvider
{
public ScalaProvider scalaProvider();
@ -10,6 +8,5 @@ public interface AppProvider
public Class<? extends AppMain> mainClass();
public AppMain newMain();
public File[] component(String componentID);
public void defineComponent(String componentID, File[] components);
}
public ComponentProvider components();
}

View File

@ -0,0 +1,9 @@
package xsbti;
import java.io.File;
public interface ComponentProvider
{
public File[] component(String componentID);
public void defineComponent(String componentID, File[] components);
}

View File

@ -9,5 +9,7 @@ public interface ScalaProvider
public ClassLoader loader();
public File[] jars();
public File libraryJar();
public File compilerJar();
public AppProvider app(ApplicationID id);
}

View File

@ -1,9 +1,10 @@
package xsbt
package xsbt.boot
import java.io.File
import java.util.Properties
import xsbti._
import org.specs._
import LoadHelpers._
import LaunchTest._
final class Main // needed so that when we test Launch, it doesn't think sbt was improperly downloaded (it looks for xsbt.Main to verify the right jar was downloaded)
@ -18,32 +19,44 @@ object ScalaProviderTest extends Specification
}
"Launch" should {
"Successfully load (stub) main sbt from local repository and run it with correct arguments" in {
checkLoad(Array("test"), "xsbt.test.ArgumentTest").asInstanceOf[Exit].code must be(0)
checkLoad(Array(), "xsbt.test.ArgumentTest") must throwA[RuntimeException]
"Successfully load an application from local repository and run it with correct arguments" in {
checkLoad(Array("test"), "xsbt.boot.test.ArgumentTest").asInstanceOf[Exit].code must be(0)
checkLoad(Array(), "xsbt.boot.test.ArgumentTest") must throwA[RuntimeException]
}
"Successfully load (stub) main sbt from local repository and run it with correct sbt version" in {
checkLoad(Array(), "xsbt.test.SbtVersionTest").asInstanceOf[Exit].code must be(0)
"Successfully load an application from local repository and run it with correct sbt version" in {
checkLoad(Array(), "xsbt.boot.test.AppVersionTest").asInstanceOf[Exit].code must be(0)
}
}
private def checkLoad(args: Array[String], mainClassName: String): MainResult =
withLaunch { _.load(args, test.MainTest.SbtTestVersion, mainClassName, mapScalaVersion(getScalaVersion)) }
private def checkScalaLoader(version: String): Unit = withLaunch(checkLauncher(version, scalaVersionMap(version)))
private def checkLauncher(version: String, versionValue: String)(launcher: ScalaProvider): Unit =
private def checkLoad(arguments: Array[String], mainClassName: String): MainResult =
FileUtilities.withTemporaryDirectory { currentDirectory =>
withLauncher { launcher =>
Launch.run(launcher)(
RunConfiguration(mapScalaVersion(LaunchTest.getScalaVersion), LaunchTest.testApp(mainClassName).toID, currentDirectory, arguments, () => ())
)
}
}
private def checkScalaLoader(version: String): Unit = withLauncher( checkLauncher(version, scalaVersionMap(version)) )
private def checkLauncher(version: String, versionValue: String)(launcher: Launcher): Unit =
{
val loader = launcher.getScalaLoader(version)
val provider = launcher.getScala(version)
val loader = provider.loader
// ensure that this loader can load Scala classes by trying scala.ScalaObject.
tryScala(loader)
getScalaVersion(loader) must beEqualTo(versionValue)
}
private def tryScala(loader: ClassLoader): Unit = Class.forName("scala.ScalaObject", false, loader).getClassLoader must be(loader)
}
object LoadHelpers
object LaunchTest
{
def withLaunch[T](f: Launcher => T): T =
FileUtilities.withTemporaryDirectory { temp => f(new xsbt.boot.Launch(temp)) }
def testApp(main: String) = Application("org.scala-tools.sbt", "launch-test", Version.Explicit(test.MainTest.Version), main, Nil, false)
import Repository.Predefined._
def testRepositories = Seq(Local, ScalaToolsReleases, ScalaToolsSnapshots).map(Repository.Predefined.apply)
def withLauncher[T](f: xsbti.Launcher => T): T =
FileUtilities.withTemporaryDirectory { bootDirectory =>
f(new Launch(bootDirectory, testRepositories))
}
def mapScalaVersion(versionNumber: String) = scalaVersionMap.find(_._2 == versionNumber).getOrElse {
error("Scala version number " + versionNumber + " from library.properties has no mapping")}._1
val scalaVersionMap = Map("2.7.2" -> "2.7.2") ++ Seq("2.7.3", "2.7.4", "2.7.5").map(v => (v, v + ".final"))
@ -56,29 +69,28 @@ object LoadHelpers
properties.getProperty("version.number")
}
}
package test
{
object MainTest
{
val SbtTestVersion = "test-0.7" // keep in sync with LauncherProject in the XSbt project definition
val Version = "test-" + xsbti.Versions.Sbt
}
import MainTest.SbtTestVersion
class Exit(val code: Int) extends xsbti.Exit
final class MainException(message: String) extends RuntimeException(message)
final class ArgumentTest extends SbtMain
final class ArgumentTest extends AppMain
{
def run(configuration: SbtConfiguration) =
def run(configuration: xsbti.AppConfiguration) =
if(configuration.arguments.length == 0)
throw new MainException("Arguments were empty")
else
new xsbt.boot.Exit(0)
new Exit(0)
}
class SbtVersionTest extends SbtMain
class AppVersionTest extends AppMain
{
def run(configuration: SbtConfiguration) =
if(configuration.sbtVersion == SbtTestVersion)
new xsbt.boot.Exit(0)
def run(configuration: xsbti.AppConfiguration) =
if(configuration.provider.id.version == MainTest.Version)
new Exit(0)
else
throw new MainException("sbt version was " + configuration.sbtVersion + ", expected: " + SbtTestVersion)
throw new MainException("app version was " + configuration.provider.id.version + ", expected: " + MainTest.Version)
}
}

View File

@ -1,14 +1,12 @@
package xsbt
import xsbti.{Exit => IExit, MainResult, SbtConfiguration, SbtMain}
class Main extends xsbti.SbtMain
class Main extends xsbti.AppMain
{
def run(configuration: SbtConfiguration): MainResult =
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult =
{
println(configuration.arguments.mkString("\n"))
Exit(0)
}
}
final case class Exit(code: Int) extends IExit
final case class Exit(code: Int) extends xsbti.Exit

16
notes
View File

@ -11,8 +11,6 @@ Task engine
- unnamed tasks log to parent task
- in parallel, optionally one task always logging
- boot interface contains static final int version = N that main xsbt can use to check if it can be loaded by that version (a lower bound check)
- main xsbt has static final int version = N that boot can use to check if it can load that version (a lower bound check)
- Have Interfaces subproject that depends on no other project and defines interfaces in package xsbti. They are written in Java and cannot refer to Scala classes (compileOrder = JavaThenScala). These interfaces are loaded by the root loader and can be used to pass objects across ClassLoader boundaries.
- launcher/main interface is not static (no system properties!)
- simple, well-defined ClassLoaders
@ -26,18 +24,10 @@ Task engine
- minimal dependence on main xsbt logger from subcomponents: use thin interface for subcomponents and implement interface in separate files in main xsbt
Dependency Management
- drop explicit managers
- resolvers are completely defined in project definition (use Resolver.withDefaultResolvers)
- configurations completely defined within project (use ModuleConfiguration.configurations)
Launcher
- generalize to be able to launch any conforming application
- default configuration file in jar- can be replaced in jar or overridden on command line
- format:
scala.version=versionSpecification
main.org=ID
main.name=ID
main.version=versionSpecification
main.components=ID (',' ID)*
repo=id ',' url [',' pattern]
versionSpecification := ( ('read'|'prompt'|'readOrPrompt') [default] ) |version
- see launch.specification
- boot interface contains static final int version = N that main xsbt can use to check if it can be loaded by that version (a lower bound check)
- main xsbt has static final int version = N that boot can use to check if it can load that version (a lower bound check)

View File

@ -14,7 +14,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
val ioSub = project(utilPath / "io", "IO", new IOProject(_), controlSub)
val classpathSub = project(utilPath / "classpath", "Classpath", new Base(_))
val ivySub = project("ivy", "Ivy", new IvyProject(_), interfaceSub)
val ivySub = project("ivy", "Ivy", new IvyProject(_), interfaceSub, launchInterfaceSub)
val logSub = project(utilPath / "log", "Logging", new Base(_), interfaceSub)
val compileInterfaceSub = project(compilePath / "interface", "Compiler Interface", new CompilerInterfaceProject(_), interfaceSub)
@ -64,21 +64,22 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
val jline = "jline" % "jline" % "0.9.94"
val ivy = "org.apache.ivy" % "ivy" % "2.0.0"
def rawJarPath = jarPath
override final def crossScalaVersions = Set.empty // don't need to cross-build, since the distributed jar is standalone (proguard)
lazy val rawPackage = packageTask(packagePaths +++ launchInterfaceSub.packagePaths, rawJarPath, packageOptions).dependsOn(compile)
// to test the retrieving and loading of the main sbt, we package and publish the test classes to the local repository
override def defaultMainArtifact = Artifact(idWithVersion)
override def projectID = ModuleID(organization, idWithVersion, "test-" + version)
override def packageAction = packageTask(packageTestPaths, outputPath / (idWithVersion + "-" + projectID.revision +".jar"), packageOptions).dependsOn(rawTestCompile)
override def defaultMainArtifact = Artifact(testID)
override def projectID = ModuleID(organization, testID, "test-" + version)
override def packageAction = packageTask(packageTestPaths, outputPath / (testID + "-" + projectID.revision +".jar"), packageOptions).dependsOn(rawTestCompile)
override def deliverProjectDependencies = Nil
def idWithVersion = "xsbt_" + ScalaVersion.currentString
lazy val rawTestCompile = super.testCompileAction
def testID = "launch-test"
override def testClasspath = super.testClasspath +++ interfaceSub.compileClasspath
lazy val rawTestCompile = super.testCompileAction dependsOn(interfaceSub.compile)
override def testCompileAction = publishLocal dependsOn(rawTestCompile)
}
trait TestDependencies extends Project
{
val sc = "org.scala-tools.testing" % "scalacheck" % "1.5" % "test->default"
val sp = "org.scala-tools.testing" % "specs" % "1.5.0" % "test->default"
val ju = "junit" % "junit" % "4.5" % "test->default" // required by specs to compile properly
val sp = "org.scala-tools.testing" % "specs" % "1.6.0" % "test->default"
}
class StandardTaskProject(info: ProjectInfo) extends Base(info)
{
@ -97,15 +98,15 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
override def scratch = true
override def consoleClasspath = testClasspath
}
class CompileProject(info: ProjectInfo) extends Base(info) with TestWithLog
class CompileProject(info: ProjectInfo) extends Base(info) with TestWithLog with TestWithLaunch
{
override def testCompileAction = super.testCompileAction dependsOn(launchSub.testCompile, compileInterfaceSub.`package`, interfaceSub.`package`)
override def testCompileAction = super.testCompileAction dependsOn(compileInterfaceSub.`package`, interfaceSub.`package`)
// don't include launch interface in published dependencies because it will be provided by launcher
override def deliverProjectDependencies = Set(super.deliverProjectDependencies.toSeq : _*) - launchInterfaceSub.projectID
override def testClasspath = super.testClasspath +++ launchSub.testClasspath +++ compileInterfaceSub.jarPath +++ interfaceSub.jarPath --- compilerInterfaceClasspath
override def testClasspath = super.testClasspath +++ compileInterfaceSub.jarPath +++ interfaceSub.jarPath --- compilerInterfaceClasspath
override def compileOptions = super.compileOptions ++ Seq(CompileOption("-Xno-varargs-conversion")) //needed for invoking nsc.scala.tools.Main.process(Array[String])
}
class IvyProject(info: ProjectInfo) extends Base(info) with TestWithIO with TestWithLog
class IvyProject(info: ProjectInfo) extends Base(info) with TestWithIO with TestWithLog with TestWithLaunch
{
val ivy = "org.apache.ivy" % "ivy" % "2.0.0"
}
@ -122,16 +123,25 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
def xTestClasspath = projectClasspath(Configurations.Test)
override def componentID = Some("compiler-interface-src")
}
trait TestWithIO extends BasicScalaProject
{
// use IO from tests
override def testCompileAction = super.testCompileAction dependsOn(ioSub.testCompile)
override def testClasspath = super.testClasspath +++ ioSub.testClasspath
trait TestWithIO extends TestWith {
override def testWithTestClasspath = super.testWithTestClasspath ++ Seq(ioSub)
}
trait TestWithLog extends BasicScalaProject
trait TestWithLaunch extends TestWith {
override def testWithTestClasspath = super.testWithTestClasspath ++ Seq(launchSub)
}
trait TestWithLog extends TestWith {
override def testWithCompileClasspath = super.testWithCompileClasspath ++ Seq(logSub)
}
trait TestWith extends BasicScalaProject
{
override def testCompileAction = super.testCompileAction dependsOn(logSub.compile)
override def testClasspath = super.testClasspath +++ logSub.compileClasspath
def testWithCompileClasspath: Seq[BasicScalaProject] = Nil
def testWithTestClasspath: Seq[BasicScalaProject] = Nil
override def testCompileAction = super.testCompileAction dependsOn((testWithTestClasspath.map(_.testCompile) ++ testWithCompileClasspath.map(_.compile)) : _*)
override def testClasspath = (super.testClasspath /: (testWithTestClasspath.map(_.testClasspath) ++ testWithCompileClasspath.map(_.compileClasspath) ))(_ +++ _)
}
trait WithLauncherInterface extends BasicScalaProject
{
override def deliverProjectDependencies = super.deliverProjectDependencies.toList - launchInterfaceSub.projectID
}
}
trait JavaProject extends BasicScalaProject
@ -148,7 +158,7 @@ trait SourceProject extends BasicScalaProject
}
trait ManagedBase extends BasicScalaProject
{
override def deliverScalaDependencies = Nil //
override def deliverScalaDependencies = Nil
override def crossScalaVersions = Set("2.7.5")
override def managedStyle = ManagedStyle.Ivy
override def useDefaultConfigurations = false