From 59121cddf78f5235d5133da613eb2420bae6da35 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 8 Dec 2014 09:27:56 -0500 Subject: [PATCH] Fixes #1620. Fixes Set[ModuleID] serialization that broke update cache. --- .../main/scala/sbt/IvyConfigurations.scala | 81 +++++++++++++++++-- ivy/src/main/scala/sbt/Resolver.scala | 13 +++ ivy/src/main/scala/sbt/UpdateOptions.scala | 17 ++++ .../scala/sbt/ivyint/SbtChainResolver.scala | 27 ++++++- .../actions/src/main/scala/sbt/CacheIvy.scala | 7 +- .../cache-update/build.sbt | 31 +++++++ .../dependency-management/cache-update/test | 2 + 7 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 sbt/src/sbt-test/dependency-management/cache-update/build.sbt create mode 100644 sbt/src/sbt-test/dependency-management/cache-update/test diff --git a/ivy/src/main/scala/sbt/IvyConfigurations.scala b/ivy/src/main/scala/sbt/IvyConfigurations.scala index d064006a0..3c695f71a 100644 --- a/ivy/src/main/scala/sbt/IvyConfigurations.scala +++ b/ivy/src/main/scala/sbt/IvyConfigurations.scala @@ -34,12 +34,42 @@ final class InlineIvyConfiguration(val paths: IvyPaths, val resolvers: Seq[Resol checksums: Seq[String], resolutionCacheDir: Option[File], log: Logger) = this(paths, resolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums, resolutionCacheDir, UpdateOptions(), log) + override def toString: String = s"InlineIvyConfiguration($paths, $resolvers, $otherResolvers, " + + s"$moduleConfigurations, $localOnly, $checksums, $resolutionCacheDir, $updateOptions)" + type This = InlineIvyConfiguration def baseDirectory = paths.baseDirectory def withBase(newBase: File) = new InlineIvyConfiguration(paths.withBase(newBase), resolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums, resolutionCacheDir, updateOptions, log) def changeResolvers(newResolvers: Seq[Resolver]) = new InlineIvyConfiguration(paths, newResolvers, otherResolvers, moduleConfigurations, localOnly, lock, checksums, resolutionCacheDir, updateOptions, log) + + override def equals(o: Any): Boolean = o match { + case o: InlineIvyConfiguration => + this.paths == o.paths && + this.resolvers == o.resolvers && + this.otherResolvers == o.otherResolvers && + this.moduleConfigurations == o.moduleConfigurations && + this.localOnly == o.localOnly && + this.checksums == o.checksums && + this.resolutionCacheDir == o.resolutionCacheDir && + this.updateOptions == o.updateOptions + case _ => false + } + + override def hashCode: Int = + { + var hash = 1 + hash = hash * 31 + this.paths.## + hash = hash * 31 + this.resolvers.## + hash = hash * 31 + this.otherResolvers.## + hash = hash * 31 + this.moduleConfigurations.## + hash = hash * 31 + this.localOnly.## + hash = hash * 31 + this.checksums.## + hash = hash * 31 + this.resolutionCacheDir.## + hash = hash * 31 + this.updateOptions.## + hash + } } final class ExternalIvyConfiguration(val baseDirectory: File, val uri: URI, val lock: Option[xsbti.GlobalLock], val extraResolvers: Seq[Resolver], val updateOptions: UpdateOptions, val log: Logger) extends IvyConfiguration { @@ -108,18 +138,18 @@ object InlineConfiguration { final class InlineConfigurationWithExcludes private[sbt] (val module: ModuleID, val moduleInfo: ModuleInfo, val dependencies: Seq[ModuleID], - val overrides: Set[ModuleID] = Set.empty, + val overrides: Set[ModuleID], val excludes: Seq[SbtExclusionRule], - val ivyXML: NodeSeq = NodeSeq.Empty, - val configurations: Seq[Configuration] = Nil, - val defaultConfiguration: Option[Configuration] = None, - val ivyScala: Option[IvyScala] = None, - val validate: Boolean = false, - val conflictManager: ConflictManager = ConflictManager.default) extends ModuleSettings { + val ivyXML: NodeSeq, + val configurations: Seq[Configuration], + val defaultConfiguration: Option[Configuration], + val ivyScala: Option[IvyScala], + val validate: Boolean, + val conflictManager: ConflictManager) extends ModuleSettings { def withConfigurations(configurations: Seq[Configuration]) = copy(configurations = configurations) def noScala = copy(ivyScala = None) - def copy(module: ModuleID = this.module, + private[sbt] def copy(module: ModuleID = this.module, moduleInfo: ModuleInfo = this.moduleInfo, dependencies: Seq[ModuleID] = this.dependencies, overrides: Set[ModuleID] = this.overrides, @@ -133,6 +163,41 @@ final class InlineConfigurationWithExcludes private[sbt] (val module: ModuleID, InlineConfigurationWithExcludes(module, moduleInfo, dependencies, overrides, excludes, ivyXML, configurations, defaultConfiguration, ivyScala, validate, conflictManager) + override def toString: String = + s"InlineConfigurationWithExcludes($module, $moduleInfo, $dependencies, $overrides, $excludes, " + + s"$ivyXML, $configurations, $defaultConfiguration, $ivyScala, $validate, $conflictManager)" + + override def equals(o: Any): Boolean = o match { + case o: InlineConfigurationWithExcludes => + this.module == o.module && + this.moduleInfo == o.moduleInfo && + this.dependencies == o.dependencies && + this.overrides == o.overrides && + this.excludes == o.excludes && + this.ivyXML == o.ivyXML && + this.configurations == o.configurations && + this.defaultConfiguration == o.defaultConfiguration && + this.ivyScala == o.ivyScala && + this.validate == o.validate && + this.conflictManager == o.conflictManager + case _ => false + } + + override def hashCode: Int = + { + var hash = 1 + hash = hash * 31 + this.module.## + hash = hash * 31 + this.dependencies.## + hash = hash * 31 + this.overrides.## + hash = hash * 31 + this.excludes.## + hash = hash * 31 + this.ivyXML.## + hash = hash * 31 + this.configurations.## + hash = hash * 31 + this.defaultConfiguration.## + hash = hash * 31 + this.ivyScala.## + hash = hash * 31 + this.validate.## + hash = hash * 31 + this.conflictManager.## + hash + } } object InlineConfigurationWithExcludes { def apply(module: ModuleID, diff --git a/ivy/src/main/scala/sbt/Resolver.scala b/ivy/src/main/scala/sbt/Resolver.scala index c7e67f876..406c27e78 100644 --- a/ivy/src/main/scala/sbt/Resolver.scala +++ b/ivy/src/main/scala/sbt/Resolver.scala @@ -15,6 +15,19 @@ sealed trait Resolver { final class RawRepository(val resolver: DependencyResolver) extends Resolver { def name = resolver.getName override def toString = "Raw(" + resolver.toString + ")" + + override def equals(o: Any): Boolean = o match { + case o: RawRepository => + this.name == o.name + case _ => false + } + + override def hashCode: Int = + { + var hash = 1 + hash = hash * 31 + this.name.## + hash + } } sealed case class ChainedResolver(name: String, resolvers: Seq[Resolver]) extends Resolver sealed case class MavenRepository(name: String, root: String) extends Resolver { diff --git a/ivy/src/main/scala/sbt/UpdateOptions.scala b/ivy/src/main/scala/sbt/UpdateOptions.scala index 2f7ad26ec..1d4688481 100644 --- a/ivy/src/main/scala/sbt/UpdateOptions.scala +++ b/ivy/src/main/scala/sbt/UpdateOptions.scala @@ -40,6 +40,23 @@ final class UpdateOptions private[sbt] ( latestSnapshots, consolidatedResolution, cachedResolution) + + override def equals(o: Any): Boolean = o match { + case o: UpdateOptions => + this.circularDependencyLevel == o.circularDependencyLevel && + this.latestSnapshots == o.latestSnapshots && + this.cachedResolution == o.cachedResolution + case _ => false + } + + override def hashCode: Int = + { + var hash = 1 + hash = hash * 31 + this.circularDependencyLevel.## + hash = hash * 31 + this.latestSnapshots.## + hash = hash * 31 + this.cachedResolution.## + hash + } } object UpdateOptions { diff --git a/ivy/src/main/scala/sbt/ivyint/SbtChainResolver.scala b/ivy/src/main/scala/sbt/ivyint/SbtChainResolver.scala index 46bb35f60..a4d60c997 100644 --- a/ivy/src/main/scala/sbt/ivyint/SbtChainResolver.scala +++ b/ivy/src/main/scala/sbt/ivyint/SbtChainResolver.scala @@ -16,7 +16,32 @@ import org.apache.ivy.plugins.resolver.{ ChainResolver, BasicResolver, Dependenc import org.apache.ivy.plugins.resolver.util.{ HasLatestStrategy, ResolvedResource } import org.apache.ivy.util.{ Message, MessageLogger, StringUtils => IvyStringUtils } -class SbtChainResolver(name: String, resolvers: Seq[DependencyResolver], settings: IvySettings, updateOptions: UpdateOptions, log: Logger) extends ChainResolver { +private[sbt] case class SbtChainResolver( + name: String, + resolvers: Seq[DependencyResolver], + settings: IvySettings, + updateOptions: UpdateOptions, + log: Logger) extends ChainResolver { + + override def equals(o: Any): Boolean = o match { + case o: SbtChainResolver => + this.name == o.name && + this.resolvers == o.resolvers && + this.settings == o.settings && + this.updateOptions == o.updateOptions + case _ => false + } + + override def hashCode: Int = + { + var hash = 1 + hash = hash * 31 + this.name.## + hash = hash * 31 + this.resolvers.## + hash = hash * 31 + this.settings.## + hash = hash * 31 + this.updateOptions.## + hash + } + // TODO - We need to special case the project resolver so it always "wins" when resolving with inter-project dependencies. // Initialize ourselves. diff --git a/main/actions/src/main/scala/sbt/CacheIvy.scala b/main/actions/src/main/scala/sbt/CacheIvy.scala index b038da256..b3fb41adb 100644 --- a/main/actions/src/main/scala/sbt/CacheIvy.scala +++ b/main/actions/src/main/scala/sbt/CacheIvy.scala @@ -112,7 +112,12 @@ object CacheIvy { m => ((m.organization, m.name, m.revision, m.configurations), (m.isChanging, m.isTransitive, m.isForce, m.explicitArtifacts, m.exclusions, m.extraAttributes, m.crossVersion)), { case ((o, n, r, cs), (ch, t, f, as, excl, x, cv)) => ModuleID(o, n, r, cs, ch, t, f, as, excl, x, cv) } ) - implicit def moduleSetIC: InputCache[Set[ModuleID]] = basicInput(defaultEquiv, immutableSetFormat) + // For some reason sbinary seems to detect unserialized instance Set[ModuleID] to be not equal. #1620 + implicit def moduleSetIC: InputCache[Set[ModuleID]] = + { + implicit def toSeq(ms: Set[ModuleID]): Seq[ModuleID] = ms.toSeq.sortBy { _.toString } + wrapIn + } implicit def configurationFormat(implicit sf: Format[String]): Format[Configuration] = wrap[Configuration, String](_.name, s => new Configuration(s)) diff --git a/sbt/src/sbt-test/dependency-management/cache-update/build.sbt b/sbt/src/sbt-test/dependency-management/cache-update/build.sbt new file mode 100644 index 000000000..92e439733 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/cache-update/build.sbt @@ -0,0 +1,31 @@ +lazy val check = taskKey[Unit]("Runs the check") + +def commonSettings: Seq[Def.Setting[_]] = + Seq( + ivyPaths := new IvyPaths( (baseDirectory in ThisBuild).value, Some((baseDirectory in LocalRootProject).value / "ivy-cache")), + scalaVersion := "2.10.4", + resolvers += Resolver.sonatypeRepo("snapshots") + ) + +// #1620 +lazy val root = (project in file(".")). + settings( + dependencyOverrides in ThisBuild += "com.github.nscala-time" %% "nscala-time" % "1.0.0", + libraryDependencies += "com.github.nscala-time" %% "nscala-time" % "1.0.0", + check := { + import sbt.Cache._, sbt.CacheIvy.updateIC + implicit val updateCache = updateIC + type In = IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil + val s = (streams in update).value + val cacheFile = s.cacheDirectory / updateCacheName.value + val module = ivyModule.value + val config = updateConfiguration.value + val f: In => Unit = + Tracked.inputChanged(cacheFile / "inputs") { (inChanged: Boolean, in: In) => + if (inChanged) { + sys.error(s"Update cache is invalidated: ${module.owner.configuration}, ${module.moduleSettings}, $config") + } + } + f(module.owner.configuration :+: module.moduleSettings :+: config :+: HNil) + } + ) diff --git a/sbt/src/sbt-test/dependency-management/cache-update/test b/sbt/src/sbt-test/dependency-management/cache-update/test new file mode 100644 index 000000000..e3bd83da1 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/cache-update/test @@ -0,0 +1,2 @@ +> compile +> check