diff --git a/compile/AnalyzingCompiler.scala b/compile/AnalyzingCompiler.scala index 33765e024..897b70c1a 100644 --- a/compile/AnalyzingCompiler.scala +++ b/compile/AnalyzingCompiler.scala @@ -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) -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/compile/ComponentCompiler.scala b/compile/ComponentCompiler.scala index f491763a0..36fea4dbc 100644 --- a/compile/ComponentCompiler.scala +++ b/compile/ComponentCompiler.scala @@ -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.*/ diff --git a/compile/ScalaInstance.scala b/compile/ScalaInstance.scala index a9127a12b..f868a48d2 100644 --- a/compile/ScalaInstance.scala +++ b/compile/ScalaInstance.scala @@ -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) } \ No newline at end of file diff --git a/compile/src/test/scala/CompileTest.scala b/compile/src/test/scala/CompileTest.scala index 13ed67c6e..196b92bdc 100644 --- a/compile/src/test/scala/CompileTest.scala +++ b/compile/src/test/scala/CompileTest.scala @@ -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))) } \ No newline at end of file diff --git a/interface/src/main/java/xsbti/Versions.java b/interface/src/main/java/xsbti/Versions.java index 8576aeaf5..122499c04 100644 --- a/interface/src/main/java/xsbti/Versions.java +++ b/interface/src/main/java/xsbti/Versions.java @@ -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; } diff --git a/ivy/ComponentManager.scala b/ivy/ComponentManager.scala index fc1209758..d13459a67 100644 --- a/ivy/ComponentManager.scala +++ b/ivy/ComponentManager.scala @@ -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) diff --git a/ivy/ConvertResolver.scala b/ivy/ConvertResolver.scala index 51c3277eb..6625e604e 100644 --- a/ivy/ConvertResolver.scala +++ b/ivy/ConvertResolver.scala @@ -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) diff --git a/ivy/CustomXmlParser.scala b/ivy/CustomXmlParser.scala index 91ca60a79..218b324ff 100644 --- a/ivy/CustomXmlParser.scala +++ b/ivy/CustomXmlParser.scala @@ -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) } } \ No newline at end of file diff --git a/ivy/Ivy.scala b/ivy/Ivy.scala index 3e45f790b..fee07b00b 100644 --- a/ivy/Ivy.scala +++ b/ivy/Ivy.scala @@ -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() diff --git a/ivy/IvyActions.scala b/ivy/IvyActions.scala index 3b95062a9..f738a3912 100644 --- a/ivy/IvyActions.scala +++ b/ivy/IvyActions.scala @@ -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]]) = diff --git a/ivy/IvyCache.scala b/ivy/IvyCache.scala index cd6fdb2b2..9e9c4d9e4 100644 --- a/ivy/IvyCache.scala +++ b/ivy/IvyCache.scala @@ -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.*/ diff --git a/ivy/IvyConfigurations.scala b/ivy/IvyConfigurations.scala index 6bf0115e5..29cbd15ab 100644 --- a/ivy/IvyConfigurations.scala +++ b/ivy/IvyConfigurations.scala @@ -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) = { diff --git a/ivy/IvyInterface.scala b/ivy/IvyInterface.scala index f62266cb1..f659321f2 100644 --- a/ivy/IvyInterface.scala +++ b/ivy/IvyInterface.scala @@ -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 { diff --git a/ivy/src/test/scala/ComponentManagerTest.scala b/ivy/src/test/scala/ComponentManagerTest.scala index 5d88297f4..6928ab98a 100644 --- a/ivy/src/test/scala/ComponentManagerTest.scala +++ b/ivy/src/test/scala/ComponentManagerTest.scala @@ -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)) } ) } \ No newline at end of file diff --git a/launch.specification b/launch.specification index 21914dbd0..d3e83dac5 100644 --- a/launch.specification +++ b/launch.specification @@ -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): diff --git a/launch/BootConfiguration.scala b/launch/BootConfiguration.scala index 94920091c..a0a8d7fe7 100644 --- a/launch/BootConfiguration.scala +++ b/launch/BootConfiguration.scala @@ -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" diff --git a/launch/Configuration.scala b/launch/Configuration.scala index 09e06b318..d4ff8771b 100644 --- a/launch/Configuration.scala +++ b/launch/Configuration.scala @@ -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) diff --git a/launch/Launch.scala b/launch/Launch.scala index b89b05d9e..cfd2699aa 100644 --- a/launch/Launch.scala +++ b/launch/Launch.scala @@ -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) + } } \ No newline at end of file diff --git a/launch/Provider.scala b/launch/Provider.scala index ca1f2c99c..0b8e3c3f1 100644 --- a/launch/Provider.scala +++ b/launch/Provider.scala @@ -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 diff --git a/launch/Update.scala b/launch/Update.scala index 5712a2f8f..38d461f15 100644 --- a/launch/Update.scala +++ b/launch/Update.scala @@ -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) diff --git a/launch/Using.scala b/launch/Using.scala index fca82e1f8..dc66db93f 100644 --- a/launch/Using.scala +++ b/launch/Using.scala @@ -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) } } diff --git a/launch/interface/src/main/java/xsbti/AppProvider.java b/launch/interface/src/main/java/xsbti/AppProvider.java index c149b1700..fed5445c7 100644 --- a/launch/interface/src/main/java/xsbti/AppProvider.java +++ b/launch/interface/src/main/java/xsbti/AppProvider.java @@ -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 mainClass(); public AppMain newMain(); - public File[] component(String componentID); - public void defineComponent(String componentID, File[] components); -} \ No newline at end of file + public ComponentProvider components(); +} diff --git a/launch/interface/src/main/java/xsbti/ComponentProvider.java b/launch/interface/src/main/java/xsbti/ComponentProvider.java new file mode 100644 index 000000000..25d26b803 --- /dev/null +++ b/launch/interface/src/main/java/xsbti/ComponentProvider.java @@ -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); +} \ No newline at end of file diff --git a/launch/interface/src/main/java/xsbti/ScalaProvider.java b/launch/interface/src/main/java/xsbti/ScalaProvider.java index e47c9b3cd..738947dda 100644 --- a/launch/interface/src/main/java/xsbti/ScalaProvider.java +++ b/launch/interface/src/main/java/xsbti/ScalaProvider.java @@ -9,5 +9,7 @@ public interface ScalaProvider public ClassLoader loader(); public File[] jars(); + public File libraryJar(); + public File compilerJar(); public AppProvider app(ApplicationID id); } \ No newline at end of file diff --git a/launch/src/test/scala/ScalaProviderTest.scala b/launch/src/test/scala/ScalaProviderTest.scala index a66aaf2ca..1cbb4ce7e 100644 --- a/launch/src/test/scala/ScalaProviderTest.scala +++ b/launch/src/test/scala/ScalaProviderTest.scala @@ -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) } } diff --git a/main/Main.scala b/main/Main.scala index 313a35ba4..bc7a5be3e 100644 --- a/main/Main.scala +++ b/main/Main.scala @@ -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 \ No newline at end of file +final case class Exit(code: Int) extends xsbti.Exit \ No newline at end of file diff --git a/notes b/notes index 48feda28d..cb0454be3 100644 --- a/notes +++ b/notes @@ -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 \ No newline at end of file +- 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) \ No newline at end of file diff --git a/project/build/XSbt.scala b/project/build/XSbt.scala index 0f6964778..49a06d4d6 100644 --- a/project/build/XSbt.scala +++ b/project/build/XSbt.scala @@ -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