From 6abac450ef3c3685708a493d88f41acb620c08cd Mon Sep 17 00:00:00 2001 From: Dan Sanduleac Date: Thu, 6 Feb 2014 15:38:37 +0000 Subject: [PATCH] Retrieve dynamic app versions correctly --- launch/src/main/scala/xsbt/boot/Launch.scala | 25 +++++--- .../scala/xsbt/boot/ModuleDefinition.scala | 6 +- launch/src/main/scala/xsbt/boot/Update.scala | 62 ++++++++++++------- 3 files changed, 59 insertions(+), 34 deletions(-) diff --git a/launch/src/main/scala/xsbt/boot/Launch.scala b/launch/src/main/scala/xsbt/boot/Launch.scala index f27441918..688a769ee 100644 --- a/launch/src/main/scala/xsbt/boot/Launch.scala +++ b/launch/src/main/scala/xsbt/boot/Launch.scala @@ -200,28 +200,34 @@ class Launch private[xsbt](val bootDirectory: File, val lockBoot: Boolean, val i @tailrec private[this] final def getAppProvider0(id: xsbti.ApplicationID, explicitScalaVersion: Option[String], forceAppUpdate: Boolean): xsbti.AppProvider = { val app = appModule(id, explicitScalaVersion, true, "app") - val baseDirs = (base: File) => appBaseDirs(base, id) + /** Replace the version of an ApplicationID with the given one, if set. */ + def resolveId(appVersion: Option[String], id: xsbti.ApplicationID) = appVersion map { v => + import id._ + AppID(groupID(), name(), v, mainClass(), mainComponents(), crossVersionedValue(), classpathExtra()) + } getOrElse id + val baseDirs = (resolvedVersion: Option[String]) => (base: File) => appBaseDirs(base, resolveId(resolvedVersion, id)) def retrieve() = { - val sv = update(app, "") + val (appv, sv) = update(app, "") val scalaVersion = strictOr(explicitScalaVersion, sv) - new RetrievedModule(true, app, sv, baseDirs(scalaHome(ScalaOrg, scalaVersion))) + new RetrievedModule(true, app, sv, appv, baseDirs(appv)(scalaHome(ScalaOrg, scalaVersion))) } val retrievedApp = if(forceAppUpdate) retrieve() else - existing(app, ScalaOrg, explicitScalaVersion, baseDirs) getOrElse retrieve() + existing(app, ScalaOrg, explicitScalaVersion, baseDirs(None)) getOrElse retrieve() val scalaVersion = getOrError(strictOr(explicitScalaVersion, retrievedApp.detectedScalaVersion), "No Scala version specified or detected") val scalaProvider = getScala(scalaVersion, "(for " + id.name + ")") + val resolvedId = resolveId(retrievedApp.resolvedAppVersion, id) - val (missing, appProvider) = checkedAppProvider(id, retrievedApp, scalaProvider) + val (missing, appProvider) = checkedAppProvider(resolvedId, retrievedApp, scalaProvider) if(missing.isEmpty) appProvider else if(retrievedApp.fresh) app.retrieveCorrupt(missing) else - getAppProvider0(id, explicitScalaVersion, true) + getAppProvider0(resolvedId, explicitScalaVersion, true) } def scalaHome(scalaOrg: String, scalaVersion: Option[String]): File = new File(bootDirectory, baseDirectoryName(scalaOrg, scalaVersion)) def appHome(id: xsbti.ApplicationID, scalaVersion: Option[String]): File = appDirectory(scalaHome(ScalaOrg, scalaVersion), id) @@ -248,7 +254,7 @@ class Launch private[xsbt](val bootDirectory: File, val lockBoot: Boolean, val i try Some(provider(mod)) catch { case e: Exception => None } } getOrElse { - val scalaVersion = update(scalaM, reason) + val (_, scalaVersion) = update(scalaM, reason) provider( new RetrievedModule(true, scalaM, scalaVersion, baseDirs) ) } } @@ -343,10 +349,11 @@ class Launch private[xsbt](val bootDirectory: File, val lockBoot: Boolean, val i failLabel = "Scala " + version, extraClasspath = array() ) - def update(mm: ModuleDefinition, reason: String): Option[String] = + /** Returns the resolved appVersion (if this was an App), as well as the scalaVersion. */ + def update(mm: ModuleDefinition, reason: String): (Option[String], Option[String]) = { val result = ( new Update(mm.configuration) )(mm.target, reason) - if(result.success) result.scalaVersion else mm.retrieveFailed + if(result.success) result.appVersion -> result.scalaVersion else mm.retrieveFailed } } object Launcher diff --git a/launch/src/main/scala/xsbt/boot/ModuleDefinition.scala b/launch/src/main/scala/xsbt/boot/ModuleDefinition.scala index c5903d415..800247743 100644 --- a/launch/src/main/scala/xsbt/boot/ModuleDefinition.scala +++ b/launch/src/main/scala/xsbt/boot/ModuleDefinition.scala @@ -13,8 +13,12 @@ final class ModuleDefinition(val configuration: UpdateConfiguration, val extraCl private def versionString: String = target match { case _: UpdateScala => configuration.getScalaVersion; case a: UpdateApp => Value.get(a.id.version) } } -final class RetrievedModule(val fresh: Boolean, val definition: ModuleDefinition, val detectedScalaVersion: Option[String], val baseDirectories: List[File]) +final class RetrievedModule(val fresh: Boolean, val definition: ModuleDefinition, val detectedScalaVersion: Option[String], val resolvedAppVersion: Option[String], val baseDirectories: List[File]) { + /** Use this constructor only when the module exists already, or when its version is not dynamic (so its resolved version would be the same) */ + def this(fresh: Boolean, definition: ModuleDefinition, detectedScalaVersion: Option[String], baseDirectories: List[File]) = + this(fresh, definition, detectedScalaVersion, None, baseDirectories) + lazy val classpath: Array[File] = getJars(baseDirectories) lazy val fullClasspath: Array[File] = concat(classpath, definition.extraClasspath) diff --git a/launch/src/main/scala/xsbt/boot/Update.scala b/launch/src/main/scala/xsbt/boot/Update.scala index 8d1c2e206..cbf0fb020 100644 --- a/launch/src/main/scala/xsbt/boot/Update.scala +++ b/launch/src/main/scala/xsbt/boot/Update.scala @@ -39,7 +39,10 @@ final class UpdateConfiguration(val bootDirectory: File, val ivyHome: Option[Fil def getScalaVersion = scalaVersion match { case Some(sv) => sv; case None => "" } } -final class UpdateResult(val success: Boolean, val scalaVersion: Option[String]) +final class UpdateResult(val success: Boolean, val scalaVersion: Option[String], val appVersion: Option[String]) { + @deprecated("0.13.2", "Please use the other constructor providing appVersion.") + def this(success: Boolean, scalaVersion: Option[String]) = this(success, scalaVersion, None) +} /** Ensures that the Scala and application jars exist for the given versions or else downloads them.*/ final class Update(config: UpdateConfiguration) @@ -109,7 +112,7 @@ final class Update(config: UpdateConfiguration) e.printStackTrace(logWriter) log(e.toString) System.out.println(" (see " + logFile + " for complete log)") - new UpdateResult(false, None) + new UpdateResult(false, None, None) } finally { @@ -127,15 +130,16 @@ final class Update(config: UpdateConfiguration) moduleID.setLastModified(System.currentTimeMillis) moduleID.addConfiguration(new IvyConfiguration(DefaultIvyConfiguration, PUBLIC, "", new Array(0), true, null)) // add dependencies based on which target needs updating - target match + val dep = 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) + val ddesc = addDependency(moduleID, scalaOrg, LibraryModuleName, scalaVersion, "default", u.classifiers) excludeJUnit(moduleID) val scalaOrgString = if (scalaOrg != ScalaOrg) " " + scalaOrg else "" System.out.println("Getting" + scalaOrgString + " Scala " + scalaVersion + " " + reason + "...") + ddesc.getDependencyId case u: UpdateApp => val app = u.id val resolvedName = (app.crossVersioned, scalaVersion) match { @@ -143,24 +147,31 @@ final class Update(config: UpdateConfiguration) case (xsbti.CrossValue.Binary, Some(sv)) => app.name + "_" + CrossVersionUtil.binaryScalaVersion(sv) case _ => app.name } - addDependency(moduleID, app.groupID, resolvedName, app.getVersion, "default(compile)", u.classifiers) + val ddesc = addDependency(moduleID, app.groupID, resolvedName, app.getVersion, "default(compile)", u.classifiers) System.out.println("Getting " + app.groupID + " " + resolvedName + " " + app.getVersion + " " + reason + "...") + ddesc.getDependencyId } - update(moduleID, target) + update(moduleID, target, dep) } /** Runs the resolve and retrieve for the given moduleID, which has had its dependencies added already. */ - private def update(moduleID: DefaultModuleDescriptor, target: UpdateTarget): UpdateResult = + private def update(moduleID: DefaultModuleDescriptor, target: UpdateTarget, dep: ModuleId): UpdateResult = { val eventManager = new EventManager - val autoScalaVersion = resolve(eventManager, moduleID) + val (autoScalaVersion, depVersion) = resolve(eventManager, moduleID, dep) + // Fix up target.id with the depVersion that we know for sure is resolved (not dynamic) -- this way, `retrieve` + // will put them in the right version directory. + val target1 = (depVersion, target) match { + case (Some(dv), u: UpdateApp) => import u._; new UpdateApp(id.copy(version = new Explicit(dv)), classifiers, tpe) + case _ => target + } setScalaVariable(settings, autoScalaVersion) - retrieve(eventManager, moduleID, target, autoScalaVersion) - new UpdateResult(true, autoScalaVersion) + retrieve(eventManager, moduleID, target1, autoScalaVersion) + new UpdateResult(true, autoScalaVersion, depVersion) } 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, classifiers: List[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) for(c <- conf.split(";")) @@ -168,6 +179,7 @@ final class Update(config: UpdateConfiguration) for(classifier <- classifiers) addClassifier(dep, name, classifier) moduleID.addDependency(dep) + dep } private def addClassifier(dep: DefaultDependencyDescriptor, name: String, classifier: String) { @@ -186,8 +198,9 @@ final class Update(config: UpdateConfiguration) rule.addConfiguration(DefaultIvyConfiguration) rule } - // returns the version of any Scala dependency - private def resolve(eventManager: EventManager, module: ModuleDescriptor): Option[String] = + val scalaLibraryId = ModuleId.newInstance(ScalaOrg, LibraryModuleName) + // Returns the version of the scala library, as well as `dep` (a dependency of `module`) after it's been resolved + private def resolve(eventManager: EventManager, module: ModuleDescriptor, dep: ModuleId): (Option[String], Option[String]) = { val resolveOptions = new ResolveOptions // this reduces the substantial logging done by Ivy, including the progress dots when downloading artifacts @@ -203,18 +216,18 @@ final class Update(config: UpdateConfiguration) System.out.println(seen.toArray.mkString(System.getProperty("line.separator"))) error("Error retrieving required libraries") } - scalaDependencyVersion(resolveReport).headOption + val modules = moduleRevisionIDs(resolveReport) + extractVersion(modules, scalaLibraryId) -> extractVersion(modules, dep) } - private[this] def scalaDependencyVersion(report: ResolveReport): List[String] = + private[this] def extractVersion(modules: Seq[ModuleRevisionId], dep: ModuleId): Option[String] = { - val modules = report.getConfigurations.toList flatMap { config => - report.getConfigurationReport(config).getModuleRevisionIds.toArray - } - modules flatMap { - case module: ModuleRevisionId if module.getOrganisation == ScalaOrg && module.getName == LibraryModuleName => - module.getRevision :: Nil - case _ => Nil - } + modules collectFirst { case m if m.getModuleId.equals(dep) => m.getRevision } + } + private[this] def moduleRevisionIDs(report: ResolveReport): Seq[ModuleRevisionId] = + { + import collection.JavaConverters._ + import org.apache.ivy.core.resolve.IvyNode + report.getDependencies.asInstanceOf[java.util.List[IvyNode]].asScala map (_.getResolvedId) } /** Exceptions are logged to the update log file. */ @@ -244,7 +257,8 @@ final class Update(config: UpdateConfiguration) val filter = (a: IArtifact) => retrieveType(a.getType) && a.getExtraAttribute("classifier") == null && extraFilter(a) retrieveOptions.setArtifactFilter(new ArtifactFilter(filter)) val scalaV = strictOr(scalaVersion, autoScalaVersion) - retrieveEngine.retrieve(module.getModuleRevisionId, baseDirectoryName(scalaOrg, scalaV) + "/" + pattern, retrieveOptions) + retrieveOptions.setDestArtifactPattern(baseDirectoryName(scalaOrg, scalaV) + "/" + pattern) + retrieveEngine.retrieve(module.getModuleRevisionId, retrieveOptions) } private[this] def notCoreScala(a: IArtifact) = a.getName match { case LibraryModuleName | CompilerModuleName => false