diff --git a/launch/Launch.scala b/launch/Launch.scala index f356483b4..18154cf36 100644 --- a/launch/Launch.scala +++ b/launch/Launch.scala @@ -96,7 +96,7 @@ class Launch private[xsbt](val bootDirectory: File, val lockBoot: Boolean, val i class JNAProvider extends Provider { lazy val id = new Application("net.java.dev.jna", "jna", new Explicit("3.2.3"), "", Nil, false, array()) - lazy val configuration = new UpdateConfiguration(bootDirectory, ivyOptions.ivyHome, "", repositories, checksumsList) + lazy val configuration = new UpdateConfiguration(bootDirectory, ivyOptions.ivyHome, None, repositories, checksumsList) lazy val libDirectory = new File(bootDirectory, baseDirectoryName("")) def baseDirectories: List[File] = new File(libDirectory, appDirectoryName(id.toID, File.separator)) :: Nil def testLoadClasses: List[String] = "com.sun.jna.Function" :: Nil @@ -112,7 +112,7 @@ class Launch private[xsbt](val bootDirectory: File, val lockBoot: Boolean, val i def launcher: xsbti.Launcher = Launch.this def parentLoader = topLoader - lazy val configuration = new UpdateConfiguration(bootDirectory, ivyOptions.ivyHome, version, repositories, checksumsList) + lazy val configuration = new UpdateConfiguration(bootDirectory, ivyOptions.ivyHome, Some(version), repositories, checksumsList) lazy val libDirectory = new File(configuration.bootDirectory, baseDirectoryName(version)) lazy val scalaHome = new File(libDirectory, ScalaDirectoryName) def compilerJar = new File(scalaHome, CompilerModuleName + ".jar") diff --git a/launch/Provider.scala b/launch/Provider.scala index 27ca3c3f5..a889b2e60 100644 --- a/launch/Provider.scala +++ b/launch/Provider.scala @@ -27,7 +27,7 @@ trait Provider 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 => Value.get(a.id.version) } + private def versionString: String = target match { case _: UpdateScala => configuration.getScalaVersion; case a: UpdateApp => Value.get(a.id.version) } val (jars, loader) = Locks(orNull(lockFile), new initialize) private[this] def orNull[T >: Null](opt: Option[T]): T = opt match { case None => null; case Some(x) => x } @@ -40,8 +40,8 @@ trait Provider (existingJars, existingLoader) else { - val retrieveSuccess = ( new Update(configuration) )(target, reason) - if(retrieveSuccess) + val result = ( new Update(configuration) )(target, reason) + if(result.success) { val (newJars, newLoader) = createLoader val missing = Provider.getMissing(newLoader, testLoadClasses) diff --git a/launch/Update.scala b/launch/Update.scala index f18d7226d..11df9a493 100644 --- a/launch/Update.scala +++ b/launch/Update.scala @@ -33,12 +33,16 @@ sealed trait UpdateTarget { def tpe: String; def classifiers: List[String] } final class UpdateScala(val classifiers: List[String]) extends UpdateTarget { def tpe = "scala" } final class UpdateApp(val id: Application, val classifiers: List[String]) extends UpdateTarget { def tpe = "app" } -final class UpdateConfiguration(val bootDirectory: File, val ivyHome: Option[File], val scalaVersion: String, val repositories: List[xsbti.Repository], val checksums: List[String]) +final class UpdateConfiguration(val bootDirectory: File, val ivyHome: Option[File], val scalaVersion: Option[String], val repositories: List[xsbti.Repository], val checksums: List[String]) { + def getScalaVersion = scalaVersion match { case Some(sv) => sv; case None => "" } +} + +final class UpdateResult(val success: Boolean, val scalaVersion: Option[String]) /** Ensures that the Scala and application jars exist for the given versions or else downloads them.*/ final class Update(config: UpdateConfiguration) { - import config.{bootDirectory, checksums, ivyHome, repositories, scalaVersion} + import config.{bootDirectory, checksums, getScalaVersion, ivyHome, repositories, scalaVersion} bootDirectory.mkdirs private def logFile = new File(bootDirectory, UpdateLogName) @@ -58,7 +62,7 @@ final class Update(config: UpdateConfiguration) val List(realm, host, user, password) = keys.productIterator.map(key => props.getProperty(key.toString)).toList if (realm != null && host != null && user != null && password != null) CredentialsStore.INSTANCE.addCredentials(realm, host, user, password) - } + } private lazy val settings = { addCredentials() @@ -68,9 +72,11 @@ final class Update(config: UpdateConfiguration) settings.setVariable("ivy.checksums", checksums mkString ",") settings.setDefaultConflictManager(settings.getConflictManager(ConflictManagerName)) settings.setBaseDir(bootDirectory) - settings.setVariable("scala", scalaVersion) + setScalaVariable(settings, scalaVersion) settings } + private[this] def setScalaVariable(settings: IvySettings, scalaVersion: Option[String]): Unit = + scalaVersion foreach { sv => settings.setVariable("scala", sv) } private lazy val ivy = { val ivy = new Ivy() { private val loggerEngine = new SbtMessageLoggerEngine; override def getLoggerEngine = loggerEngine } @@ -82,23 +88,23 @@ final class Update(config: UpdateConfiguration) private lazy val ivyLockFile = new File(settings.getDefaultIvyUserDir, ".sbt.ivy.lock") /** The main entry point of this class for use by the Update module. It runs Ivy */ - def apply(target: UpdateTarget, reason: String): Boolean = + def apply(target: UpdateTarget, reason: String): UpdateResult = { Message.setDefaultLogger(new SbtIvyLogger(logWriter)) - val action = new Callable[Boolean] { def call = lockedApply(target, reason) } + val action = new Callable[UpdateResult] { def call = lockedApply(target, reason) } Locks(ivyLockFile, action) } - private def lockedApply(target: UpdateTarget, reason: String) = + private def lockedApply(target: UpdateTarget, reason: String): UpdateResult = { ivy.pushContext() - try { update(target, reason); true } + try { update(target, reason) } catch { case e: Exception => e.printStackTrace(logWriter) log(e.toString) System.out.println(" (see " + logFile + " for complete log)") - false + new UpdateResult(false, None) } finally { @@ -107,7 +113,7 @@ final class Update(config: UpdateConfiguration) } } /** Runs update for the specified target (updates either the scala or appliciation jars for building the project) */ - private def update(target: UpdateTarget, reason: String) + private def update(target: UpdateTarget, reason: String): UpdateResult = { import IvyConfiguration.Visibility.PUBLIC // the actual module id here is not that important @@ -118,13 +124,17 @@ final class Update(config: UpdateConfiguration) target match { case u: UpdateScala => + val scalaVersion = getScalaVersion addDependency(moduleID, ScalaOrg, CompilerModuleName, scalaVersion, "default;optional(default)", u.classifiers) addDependency(moduleID, ScalaOrg, LibraryModuleName, scalaVersion, "default", u.classifiers) excludeJUnit(moduleID) System.out.println("Getting Scala " + scalaVersion + " " + reason + "...") case u: UpdateApp => val app = u.id - val resolvedName = if(app.crossVersioned) app.name + "_" + scalaVersion else app.name + val resolvedName = (app.crossVersioned, scalaVersion) match { + case (true, Some(sv)) => app.name + "_" + sv + case _ => app.name + } addDependency(moduleID, app.groupID, resolvedName, app.getVersion, "default(compile)", u.classifiers) excludeScala(moduleID) System.out.println("Getting " + app.groupID + " " + resolvedName + " " + app.getVersion + " " + reason + "...") @@ -132,11 +142,13 @@ final class Update(config: UpdateConfiguration) 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) + private def update(moduleID: DefaultModuleDescriptor, target: UpdateTarget): UpdateResult = { val eventManager = new EventManager - resolve(eventManager, moduleID) - retrieve(eventManager, moduleID, target) + val autoScalaVersion = resolve(eventManager, moduleID) + setScalaVariable(settings, autoScalaVersion) + retrieve(eventManager, moduleID, target, autoScalaVersion) + new UpdateResult(true, autoScalaVersion) } private def createID(organization: String, name: String, revision: String) = ModuleRevisionId.newInstance(organization, name, revision) @@ -173,7 +185,8 @@ final class Update(config: UpdateConfiguration) rule.addConfiguration(DefaultIvyConfiguration) rule } - private def resolve(eventManager: EventManager, module: ModuleDescriptor) + // returns the version of any Scala dependency + private def resolve(eventManager: EventManager, module: ModuleDescriptor): Option[String] = { val resolveOptions = new ResolveOptions // this reduces the substantial logging done by Ivy, including the progress dots when downloading artifacts @@ -189,7 +202,16 @@ final class Update(config: UpdateConfiguration) System.out.println(seen.toArray.mkString(System.getProperty("line.separator"))) error("Error retrieving required libraries") } + scalaDependencyVersion(resolveReport).headOption } + private[this] def scalaDependencyVersion(report: ResolveReport): List[String] = + for { + config <- report.getConfigurations.toList + module <- report.getConfigurationReport(config).getModuleRevisionIds.toArray collect { case revId: ModuleRevisionId => revId } + if module.getOrganisation == ScalaOrg && module.getName == LibraryModuleName + } yield + module.getRevision + /** Exceptions are logged to the update log file. */ private def logExceptions(report: ResolveReport) { @@ -204,7 +226,7 @@ final class Update(config: UpdateConfiguration) def accept(o: Any) = o match { case a: IArtifact => f(a); case _ => false } } /** Retrieves resolved dependencies using the given target to determine the location to retrieve to. */ - private def retrieve(eventManager: EventManager, module: ModuleDescriptor, target: UpdateTarget) + private def retrieve(eventManager: EventManager, module: ModuleDescriptor, target: UpdateTarget, autoScalaVersion: Option[String]) { val retrieveOptions = new RetrieveOptions retrieveOptions.setArtifactFilter(new ArtifactFilter(a => retrieveType(a.getType) && a.getExtraAttribute("classifier") == null)) @@ -215,7 +237,8 @@ final class Update(config: UpdateConfiguration) case _: UpdateScala => scalaRetrievePattern case u: UpdateApp => appRetrievePattern(u.id.toID) } - retrieveEngine.retrieve(module.getModuleRevisionId, baseDirectoryName(scalaVersion) + "/" + pattern, retrieveOptions) + val scalaV = scalaVersion orElse autoScalaVersion getOrElse "" + retrieveEngine.retrieve(module.getModuleRevisionId, baseDirectoryName(scalaV) + "/" + pattern, retrieveOptions) } private def retrieveType(tpe: String): Boolean = tpe == "jar" || tpe == "bundle" /** Add the scala tools repositories and a URL resolver to download sbt from the Google code project.*/ @@ -240,7 +263,7 @@ final class Update(config: UpdateConfiguration) artifact.getQualifiedExtraAttributes.keys.exists(_.asInstanceOf[String] startsWith "m:") } // exclude the local Maven repository for Scala -SNAPSHOTs - private def includeRepo(repo: xsbti.Repository) = !(Repository.isMavenLocal(repo) && isSnapshot(scalaVersion) ) + private def includeRepo(repo: xsbti.Repository) = !(Repository.isMavenLocal(repo) && isSnapshot(getScalaVersion) ) private def isSnapshot(scalaVersion: String) = scalaVersion.endsWith(Snapshot) private[this] val Snapshot = "-SNAPSHOT" private[this] val ChangingPattern = ".*" + Snapshot @@ -266,7 +289,7 @@ final class Update(config: UpdateConfiguration) case MavenLocal => mavenLocal case MavenCentral => mavenMainResolver case ScalaToolsReleases => mavenResolver("Scala-Tools Maven2 Repository", "http://scala-tools.org/repo-releases") - case ScalaToolsSnapshots => scalaSnapshots(scalaVersion) + case ScalaToolsSnapshots => scalaSnapshots(getScalaVersion) } } }