diff --git a/launch.specification b/launch.specification index 4b3299933..e88f0f1e6 100644 --- a/launch.specification +++ b/launch.specification @@ -35,7 +35,7 @@ The launcher may be configured in one of the following ways in increasing order The configuration file is line-based, read as UTF-8 encoded, and defined by the following grammer. `'nl'` is a newline or end of file and `'text'` is plain text without newlines or the surrounding delimiters (such as parentheses or square brackets): {{{ configuration ::= scala app repositories boot log app-properties - scala ::= '[' 'scala' ']' nl version nl + scala ::= '[' 'scala' ']' nl version nl classifiers nl app ::= '[' 'app' ']' nl org nl name nl version nl components nl class nl cross-versioned nl resources nl repositories ::= '[' 'repositories' ']' nl (repository nl)* boot ::= '[' 'boot' ']' nl directory nl bootProperties nl search nl promptCreate nl promptFill nl quickOption nl @@ -55,6 +55,8 @@ configuration ::= scala app repositories boot log app-properties readProperty ::= 'read' '(' propertyName ')' '[' default ']' fixedVersion ::= text + classifiers ::= 'classifiers' ':' text (',' text)* + org ::= 'org' ':' text name ::= 'name' ':' text class ::= 'class' ':' text @@ -117,7 +119,7 @@ The default configuration file for sbt looks like: project.initialize: quick=set(true), new=set(true) }}} -The `scala.version` property specifies the version of Scala used to run the application. The `app.org`, `app.name`, and `app.version` properties specify the organization, module ID, and version of the application, respectively. These are used to resolve and retrieve the application from the repositories listed in `[repositories]`. If `app.cross-versioned` is true, the resolved module ID is `{app.name+'_'+scala.version}`. The paths given in `app.resources` are added to the application's classpath. If the path is relative, it is resolved against the application's working directory. +The `scala.version` property specifies the version of Scala used to run the application. If specified, the `scala.classifiers`property defines classifers such as 'sources' of extra Scala artifacts to retrieve. The `app.org`, `app.name`, and `app.version` properties specify the organization, module ID, and version of the application, respectively. These are used to resolve and retrieve the application from the repositories listed in `[repositories]`. If `app.cross-versioned` is true, the resolved module ID is `{app.name+'_'+scala.version}`. The paths given in `app.resources` are added to the application's classpath. If the path is relative, it is resolved against the application's working directory. Jars are retrieved to the directory given by `boot.directory`. You can make this an absolute path to be shared by all sbt instances on the machine. You might see messages like: {{{ diff --git a/launch/BootConfiguration.scala b/launch/BootConfiguration.scala index a0a8d7fe7..a55516a70 100644 --- a/launch/BootConfiguration.scala +++ b/launch/BootConfiguration.scala @@ -23,7 +23,7 @@ private object BootConfiguration /** The name of the local Ivy repository, which is used when compiling sbt from source.*/ val LocalIvyName = "local" /** The pattern used for the local Ivy repository, which is used when compiling sbt from source.*/ - val LocalPattern = "[organisation]/[module]/[revision]/[type]s/[artifact].[ext]" + val LocalPattern = "[organisation]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]" /** The artifact pattern used for the local Ivy repository.*/ def LocalArtifactPattern = LocalPattern /** The Ivy pattern used for the local Ivy repository.*/ @@ -51,7 +51,7 @@ private object BootConfiguration val ScalaDirectoryName = "lib" /** The Ivy pattern to use for retrieving the scala compiler and library. It is relative to the directory * containing all jars for the requested version of scala. */ - val scalaRetrievePattern = ScalaDirectoryName + "/[artifact].[ext]" + val scalaRetrievePattern = ScalaDirectoryName + "/[artifact](-[classifier]).[ext]" /** The Ivy pattern to use for retrieving the application and its dependencies. It is relative to the directory * containing all jars for the requested version of scala. */ diff --git a/launch/ConfigurationParser.scala b/launch/ConfigurationParser.scala index c521f6a1e..438a77b5c 100644 --- a/launch/ConfigurationParser.scala +++ b/launch/ConfigurationParser.scala @@ -23,16 +23,22 @@ class ConfigurationParser extends NotNull // section -> configuration instance processing def processSections(sections: SectionMap): LaunchConfiguration = { - val (scalaVersion, m1) = processSection(sections, "scala", getScalaVersion) + val ((scalaVersion, scalaClassifiers), m1) = processSection(sections, "scala", getScala) val (app, m2) = processSection(m1, "app", getApplication) val (repositories, m3) = processSection(m2, "repositories", getRepositories) val (boot, m4) = processSection(m3, "boot", getBoot) val (logging, m5) = processSection(m4, "log", getLogging) val (properties, m6) = processSection(m5, "app-properties", getAppProperties) check(m6, "section") - new LaunchConfiguration(scalaVersion, app, repositories, boot, logging, properties) + new LaunchConfiguration(scalaVersion, scalaClassifiers, app, repositories, boot, logging, properties) + } + def getScala(m: LabelMap) = + { + val (scalaVersion, m1) = getVersion(m, "Scala version", "scala.version") + val (scalaClassifiers, m2) = ids(m1, "classifiers", Nil) + check(m2, "label") + (scalaVersion, "" :: scalaClassifiers) // the added "" ensures that the main jars are retrieved } - def getScalaVersion(m: LabelMap) = check("label", getVersion(m, "Scala version", "scala.version")) def getVersion(m: LabelMap, label: String, defaultName: String): (Version, LabelMap) = process(m, "version", processVersion(label, defaultName)) def processVersion(label: String, defaultName: String)(value: Option[String]): Version = value.map(version(label)).getOrElse(new Version.Implicit(defaultName, None)) diff --git a/launch/Launch.scala b/launch/Launch.scala index 2b1c60141..008d3a607 100644 --- a/launch/Launch.scala +++ b/launch/Launch.scala @@ -40,7 +40,7 @@ object Launch } def explicit(currentDirectory: File, explicit: LaunchConfiguration, arguments: List[String]): Unit = - launch( run(new Launch(explicit.boot.directory, explicit.repositories)) ) ( + launch( run(new Launch(explicit.boot.directory, explicit.repositories, explicit.scalaClassifiers)) ) ( new RunConfiguration(explicit.getScalaVersion, explicit.app.toID, currentDirectory, arguments) ) def run(launcher: xsbti.Launcher)(config: RunConfiguration): xsbti.MainResult = @@ -70,7 +70,7 @@ object Launch final class RunConfiguration(val scalaVersion: String, val app: xsbti.ApplicationID, val workingDirectory: File, val arguments: List[String]) extends NotNull import BootConfiguration.{appDirectoryName, baseDirectoryName, ScalaDirectoryName, TestLoadScalaClasses} -class Launch(val bootDirectory: File, repositories: List[Repository]) extends xsbti.Launcher +class Launch(val bootDirectory: File, repositories: List[Repository], scalaClassifiers: List[String]) extends xsbti.Launcher { bootDirectory.mkdirs private val scalaProviders = new Cache[String, ScalaProvider](new ScalaProvider(_)) @@ -91,9 +91,10 @@ class Launch(val bootDirectory: File, repositories: List[Repository]) extends xs lazy val scalaHome = new File(libDirectory, ScalaDirectoryName) def compilerJar = new File(scalaHome, "scala-compiler.jar") def libraryJar = new File(scalaHome, "scala-library.jar") + override def classpath = Array(compilerJar, libraryJar) def baseDirectories = List(scalaHome) def testLoadClasses = TestLoadScalaClasses - def target = UpdateScala + def target = new UpdateScala(scalaClassifiers) def failLabel = "Scala " + version def lockFile = updateLockFile def extraClasspath = Array[File]() diff --git a/launch/LaunchConfiguration.scala b/launch/LaunchConfiguration.scala index 110d2cd01..3ebb4bac1 100644 --- a/launch/LaunchConfiguration.scala +++ b/launch/LaunchConfiguration.scala @@ -4,14 +4,14 @@ import Pre._ import java.io.File import java.net.URL -final case class LaunchConfiguration(scalaVersion: Version, app: Application, repositories: List[Repository], boot: BootSetup, logging: Logging, appProperties: List[AppProperty]) extends NotNull +final case class LaunchConfiguration(scalaVersion: Version, scalaClassifiers: List[String], app: Application, repositories: List[Repository], boot: BootSetup, logging: Logging, appProperties: List[AppProperty]) extends NotNull { def getScalaVersion = Version.get(scalaVersion) - def withScalaVersion(newScalaVersion: String) = LaunchConfiguration(new Version.Explicit(newScalaVersion), app, repositories, boot, logging, appProperties) - def withApp(app: Application) = LaunchConfiguration(scalaVersion, app, repositories, boot, logging, appProperties) - def withAppVersion(newAppVersion: String) = LaunchConfiguration(scalaVersion, app.withVersion(new Version.Explicit(newAppVersion)), repositories, boot, logging, appProperties) - def withVersions(newScalaVersion: String, newAppVersion: String) = LaunchConfiguration(new Version.Explicit(newScalaVersion), app.withVersion(new Version.Explicit(newAppVersion)), repositories, boot, logging, appProperties) - def map(f: File => File) = LaunchConfiguration(scalaVersion, app.map(f), repositories, boot.map(f), logging, appProperties) + def withScalaVersion(newScalaVersion: String) = LaunchConfiguration(new Version.Explicit(newScalaVersion), scalaClassifiers, app, repositories, boot, logging, appProperties) + def withApp(app: Application) = LaunchConfiguration(scalaVersion, scalaClassifiers, app, repositories, boot, logging, appProperties) + def withAppVersion(newAppVersion: String) = LaunchConfiguration(scalaVersion, scalaClassifiers, app.withVersion(new Version.Explicit(newAppVersion)), repositories, boot, logging, appProperties) + def withVersions(newScalaVersion: String, newAppVersion: String) = LaunchConfiguration(new Version.Explicit(newScalaVersion), scalaClassifiers, app.withVersion(new Version.Explicit(newAppVersion)), repositories, boot, logging, appProperties) + def map(f: File => File) = LaunchConfiguration(scalaVersion, scalaClassifiers, app.map(f), repositories, boot.map(f), logging, appProperties) } sealed trait Version extends NotNull diff --git a/launch/Provider.scala b/launch/Provider.scala index fd3edc850..e579da147 100644 --- a/launch/Provider.scala +++ b/launch/Provider.scala @@ -22,7 +22,7 @@ trait Provider extends NotNull def retrieveCorrupt(missing: Iterable[String]): Nothing = fail(": missing " + missing.mkString(", ")) private def fail(extra: String) = throw new xsbti.RetrieveException(versionString, "Could not retrieve " + failLabel + extra) - private def versionString: String = target match { case UpdateScala => configuration.scalaVersion; case a: UpdateApp => Version.get(a.id.version) } + private def versionString: String = target match { case _: UpdateScala => configuration.scalaVersion; case a: UpdateApp => Version.get(a.id.version) } val (jars, loader) = Locks(lockFile, new initialize) private final class initialize extends Callable[(Array[File], ClassLoader)] diff --git a/launch/Update.scala b/launch/Update.scala index 30a70ecd2..bfb04fcc0 100644 --- a/launch/Update.scala +++ b/launch/Update.scala @@ -12,7 +12,7 @@ import core.LogOptions import core.cache.DefaultRepositoryCacheManager import core.event.EventManager import core.module.id.ModuleRevisionId -import core.module.descriptor.{Configuration => IvyConfiguration, DefaultDependencyDescriptor, DefaultModuleDescriptor, ModuleDescriptor} +import core.module.descriptor.{Configuration => IvyConfiguration, DefaultDependencyArtifactDescriptor, DefaultDependencyDescriptor, DefaultModuleDescriptor, ModuleDescriptor} import core.report.ResolveReport import core.resolve.{ResolveEngine, ResolveOptions} import core.retrieve.{RetrieveEngine, RetrieveOptions} @@ -25,7 +25,7 @@ import util.{DefaultMessageLogger, Message} import BootConfiguration._ sealed trait UpdateTarget extends NotNull { def tpe: String } -final object UpdateScala extends UpdateTarget { def tpe = "scala" } +final class UpdateScala(val classifiers: List[String]) extends UpdateTarget { def tpe = "scala" } final class UpdateApp(val id: Application) extends UpdateTarget { def tpe = "app" } final class UpdateConfiguration(val bootDirectory: File, val scalaVersion: String, val repositories: List[Repository]) extends NotNull @@ -81,14 +81,14 @@ final class Update(config: UpdateConfiguration) // add dependencies based on which target needs updating target match { - case UpdateScala => - addDependency(moduleID, ScalaOrg, CompilerModuleName, scalaVersion, "default") - addDependency(moduleID, ScalaOrg, LibraryModuleName, scalaVersion, "default") + case u: UpdateScala => + addDependency(moduleID, ScalaOrg, CompilerModuleName, scalaVersion, "default", u.classifiers) + addDependency(moduleID, ScalaOrg, LibraryModuleName, scalaVersion, "default", u.classifiers) System.out.println("Getting Scala " + scalaVersion + " ...") case u: UpdateApp => val app = u.id val resolvedName = if(app.crossVersioned) app.name + "_" + scalaVersion else app.name - addDependency(moduleID, app.groupID, resolvedName, app.getVersion, "default(compile)") + addDependency(moduleID, app.groupID, resolvedName, app.getVersion, "default(compile)", Nil) System.out.println("Getting " + app.groupID + " " + resolvedName + " " + app.getVersion + " ...") } update(moduleID, target) @@ -103,12 +103,23 @@ final class Update(config: UpdateConfiguration) private def createID(organization: String, name: String, revision: String) = ModuleRevisionId.newInstance(organization, name, revision) /** Adds the given dependency to the default configuration of 'moduleID'. */ - private def addDependency(moduleID: DefaultModuleDescriptor, organization: String, name: String, revision: String, conf: String) + private def addDependency(moduleID: DefaultModuleDescriptor, organization: String, name: String, revision: String, conf: String, classifiers: List[String]) { val dep = new DefaultDependencyDescriptor(moduleID, createID(organization, name, revision), false, false, true) dep.addDependencyConfiguration(DefaultIvyConfiguration, conf) + for(classifier <- classifiers) + addClassifier(dep, name, classifier) moduleID.addDependency(dep) } + private def addClassifier(dep: DefaultDependencyDescriptor, name: String, classifier: String) + { + val extraMap = new java.util.HashMap[String,String] + if(!classifier.isEmpty) + extraMap.put("e:classifier", classifier) + val ivyArtifact = new DefaultDependencyArtifactDescriptor(dep, name, "jar", "jar", null, extraMap) + for(conf <- dep.getModuleConfigurations) + dep.addDependencyArtifact(conf, ivyArtifact) + } private def resolve(eventManager: EventManager, module: ModuleDescriptor) { val resolveOptions = new ResolveOptions @@ -143,7 +154,7 @@ final class Update(config: UpdateConfiguration) val pattern = target match { - case UpdateScala => scalaRetrievePattern + case _: UpdateScala => scalaRetrievePattern case u: UpdateApp => appRetrievePattern(u.id.toID) } retrieveEngine.retrieve(module.getModuleRevisionId, baseDirectoryName(scalaVersion) + "/" + pattern, retrieveOptions) diff --git a/launch/src/main/resources/sbt/sbt.boot.properties b/launch/src/main/resources/sbt/sbt.boot.properties index 1d5735dab..374f3d6b9 100644 --- a/launch/src/main/resources/sbt/sbt.boot.properties +++ b/launch/src/main/resources/sbt/sbt.boot.properties @@ -1,6 +1,6 @@ [scala] -# version: read(def.scala.version) - version: 2.7.7 + version: read(def.scala.version) +# classifiers: sources [app] org: org.scala-tools.sbt @@ -32,7 +32,7 @@ project.name: quick=set(test), new=prompt(Name), fill=prompt(Name) project.organization: new=prompt(Organization) project.version: quick=set(1.0), new=prompt(Version)[1.0], fill=prompt(Version)[1.0] -# def.scala.version: quick=set(2.7.7), new=set(2.7.7), fill=set(2.7.7) + def.scala.version: quick=set(2.7.7), new=set(2.7.7), fill=set(2.7.7) build.scala.versions: quick=set(2.7.7), new=prompt(Scala version)[2.7.7], fill=prompt(Scala version)[2.7.7] sbt.version: quick=set(0.6.9), new=prompt(sbt version)[0.6.9], fill=prompt(sbt version)[0.6.9] project.scratch: quick=set(true)