From 8fd40e56dffba2878efdd90ca73f3ec28defcb40 Mon Sep 17 00:00:00 2001 From: jvican Date: Sun, 5 Mar 2017 19:05:34 +0100 Subject: [PATCH 1/4] Ease `updateTask` and `cachedUpdate` maintenance This commit does some changes to the implementation with the purpose of making this code more readable. I find that this rewrite was necessary as I was implementing the dependency lock file. --- main/src/main/scala/sbt/Defaults.scala | 290 ++++++++++++++----------- 1 file changed, 159 insertions(+), 131 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 962771d84..d906b244b 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2051,75 +2051,89 @@ object Classpaths { ) } - def updateTask: Initialize[Task[UpdateReport]] = Def.task { - val depsUpdated = transitiveUpdate.value.exists(!_.stats.cached) - val isRoot = executionRoots.value contains resolvedScoped.value - val forceUpdate = forceUpdatePeriod.value - val s = streams.value - val fullUpdateOutput = s.cacheDirectory / "out" - val forceUpdateByTime = forceUpdate match { - case None => false - case Some(period) => - val elapsedDuration = - new FiniteDuration(System.currentTimeMillis() - fullUpdateOutput.lastModified(), - TimeUnit.MILLISECONDS) - fullUpdateOutput.exists() && elapsedDuration > period - } - val scalaProvider = appConfiguration.value.provider.scalaProvider - - // Only substitute unmanaged jars for managed jars when the major.minor parts of the versions the same for: - // the resolved Scala version and the scalaHome version: compatible (weakly- no qualifier checked) - // the resolved Scala version and the declared scalaVersion: assume the user intended scalaHome to override anything with scalaVersion - def subUnmanaged(subVersion: String, jars: Seq[File]) = - (sv: String) => - (partialVersion(sv), partialVersion(subVersion), partialVersion(scalaVersion.value)) match { - case (Some(res), Some(sh), _) if res == sh => jars - case (Some(res), _, Some(decl)) if res == decl => jars - case _ => Nil + /** + * Substitute unmanaged jars for managed jars when the major.minor parts of + * the version are the same for: + * 1. The Scala version and the `scalaHome` (unmanaged) version are equal. + * 2. The Scala version and the `declared` (managed) version are equal. + * + * Equality is weak, that is, no version qualifier is checked. + */ + private def unmanagedJarsTask(scalaVersion: String, unmanagedVersion: String, jars: Seq[File]) = { + (subVersion0: String) => + val scalaV = partialVersion(scalaVersion) + val managedV = partialVersion(subVersion0) + val unmanagedV = partialVersion(unmanagedVersion) + (managedV, unmanagedV, scalaV) match { + case (Some(mv), Some(uv), _) if mv == uv => jars + case (Some(mv), _, Some(sv)) if mv == sv => jars + case _ => Nil + } + } + + def updateTask: Initialize[Task[UpdateReport]] = Def.task { + val s = streams.value + val cacheDirectory = streams.value.cacheDirectory + + val isRoot = executionRoots.value contains resolvedScoped.value + val shouldForce = isRoot || { + forceUpdatePeriod.value match { + case None => false + case Some(period) => + val fullUpdateOutput = cacheDirectory / "out" + val now = System.currentTimeMillis + val diff = now - fullUpdateOutput.lastModified() + val elapsedDuration = new FiniteDuration(diff, TimeUnit.MILLISECONDS) + fullUpdateOutput.exists() && elapsedDuration > period } - val subScalaJars: String => Seq[File] = Defaults.unmanagedScalaInstanceOnly.value match { - case Some(si) => subUnmanaged(si.version, si.allJars) - case None => - sv => - if (scalaProvider.version == sv) scalaProvider.jars else Nil } - val transform: UpdateReport => UpdateReport = - r => substituteScalaFiles(scalaOrganization.value, r)(subScalaJars) - val uwConfig = (unresolvedWarningConfiguration in update).value - val show = Reference.display(thisProjectRef.value) - val st = state.value - val logicalClock = LogicalClock(st.hashCode) - val depDir = dependencyCacheDirectory.value - val uc0 = updateConfiguration.value - val ms = publishMavenStyle.value - val cw = compatibilityWarningOptions.value - // Normally, log would capture log messages at all levels. - // Ivy logs are treated specially using sbt.UpdateConfiguration.logging. - // This code bumps up the sbt.UpdateConfiguration.logging to Full when logLevel is Debug. - import UpdateLogging.{ Full, DownloadOnly, Default } - val uc = (logLevel in update).?.value orElse st.get(logLevel.key) match { - case Some(Level.Debug) if uc0.logging == Default => uc0.withLogging(Full) - case Some(x) if uc0.logging == Default => uc0.withLogging(DownloadOnly) - case _ => uc0 + + val providedScalaJars: String => Seq[File] = { + val scalaProvider = appConfiguration.value.provider.scalaProvider + Defaults.unmanagedScalaInstanceOnly.value match { + case Some(instance) => + unmanagedJarsTask(scalaVersion.value, instance.version, instance.allJars) + case None => + (subVersion: String) => + if (scalaProvider.version == subVersion) scalaProvider.jars else Nil + } } - val ewo = - if (executionRoots.value exists { _.key == evicted.key }) EvictionWarningOptions.empty + + val state0 = state.value + val updateConf = { + // Log captures log messages at all levels, except ivy logs. + // Use full level when debug is enabled so that ivy logs are shown. + import UpdateLogging.{ Full, DownloadOnly, Default } + val conf = updateConfiguration.value + val maybeUpdateLevel = (logLevel in update).?.value + maybeUpdateLevel.orElse(state0.get(logLevel.key)) match { + case Some(Level.Debug) if conf.logging == Default => conf.withLogging(logging = Full) + case Some(_) if conf.logging == Default => conf.withLogging(logging = DownloadOnly) + case _ => conf + } + } + + val evictionOptions = { + if (executionRoots.value.exists(_.key == evicted.key)) + EvictionWarningOptions.empty else (evictionWarningOptions in update).value + } + cachedUpdate( - s.cacheStoreFactory sub updateCacheName.value, - show, + s.cacheStoreFactory.sub(updateCacheName.value), + Reference.display(thisProjectRef.value), ivyModule.value, - uc, - transform, + updateConf, + substituteScalaFiles(scalaOrganization.value, _)(providedScalaJars), skip = (skip in update).value, - force = isRoot || forceUpdateByTime, - depsUpdated = depsUpdated, - uwConfig = uwConfig, - logicalClock = logicalClock, - depDir = Some(depDir), - ewo = ewo, - mavenStyle = ms, - compatWarning = cw, + force = shouldForce, + depsUpdated = transitiveUpdate.value.exists(!_.stats.cached), + uwConfig = (unresolvedWarningConfiguration in update).value, + logicalClock = LogicalClock(state0.hashCode), + depDir = Some(dependencyCacheDirectory.value), + ewo = evictionOptions, + mavenStyle = publishMavenStyle.value, + compatWarning = compatibilityWarningOptions.value, log = s.log ) } @@ -2230,87 +2244,101 @@ object Classpaths { } } - private[sbt] def cachedUpdate(cacheStoreFactory: CacheStoreFactory, - label: String, - module: IvySbt#Module, - config: UpdateConfiguration, - transform: UpdateReport => UpdateReport, - skip: Boolean, - force: Boolean, - depsUpdated: Boolean, - uwConfig: UnresolvedWarningConfiguration, - logicalClock: LogicalClock, - depDir: Option[File], - ewo: EvictionWarningOptions, - mavenStyle: Boolean, - compatWarning: CompatibilityWarningOptions, - log: Logger): UpdateReport = { - type In = IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil + private type UpdateInputs = IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil - def work = (_: In) match { - case conf :+: settings :+: config :+: HNil => - import ShowLines._ - log.info("Updating " + label + "...") - val r = - IvyActions.updateEither(module, config, uwConfig, logicalClock, depDir, log) match { - case Right(ur) => ur - case Left(uw) => - uw.lines foreach { log.warn(_) } - throw uw.resolveException - } - log.info("Done updating.") - val result = transform(r) - val ew = EvictionWarning(module, ewo, result, log) - ew.lines foreach { log.warn(_) } - ew.infoAllTheThings foreach { log.info(_) } - CompatibilityWarning.run(compatWarning, module, mavenStyle, log) - result + private[sbt] def cachedUpdate( + cacheStoreFactory: CacheStoreFactory, + label: String, + module: IvySbt#Module, + updateConfig: UpdateConfiguration, + transform: UpdateReport => UpdateReport, + skip: Boolean, + force: Boolean, + depsUpdated: Boolean, + uwConfig: UnresolvedWarningConfiguration, + logicalClock: LogicalClock, + depDir: Option[File], + ewo: EvictionWarningOptions, + mavenStyle: Boolean, + compatWarning: CompatibilityWarningOptions, + log: Logger + ): UpdateReport = { + + /* Resolve the module settings from the inputs. */ + def resolve(inputs: UpdateInputs): UpdateReport = { + import ShowLines._ + + log.info(s"Updating $label...") + val reportOrUnresolved: Either[UnresolvedWarning, UpdateReport] = + IvyActions.updateEither(module, updateConfig, uwConfig, logicalClock, depDir, log) + + val report = reportOrUnresolved match { + case Right(report0) => report0 + case Left(unresolvedWarning) => + unresolvedWarning.lines.foreach(log.warn(_)) + throw unresolvedWarning.resolveException + } + log.info("Done updating.") + val finalReport = transform(report) + + // Warn of any eviction and compatibility warnings + val ew = EvictionWarning(module, ewo, finalReport, log) + ew.lines foreach { log.warn(_) } + ew.infoAllTheThings foreach { log.info(_) } + CompatibilityWarning.run(compatWarning, module, mavenStyle, log) + + finalReport } - def uptodate(inChanged: Boolean, out: UpdateReport): Boolean = - !force && - !depsUpdated && - !inChanged && - out.allFiles.forall(f => fileUptodate(f, out.stamps)) && - fileUptodate(out.cachedDescriptor, out.stamps) - val outStore = cacheStoreFactory make "output" - def skipWork: In => UpdateReport = { + /* Check if a update report is still up to date or we must resolve again. */ + def upToDate(inChanged: Boolean, out: UpdateReport): Boolean = { + !force && + !depsUpdated && + !inChanged && + out.allFiles.forall(f => fileUptodate(f, out.stamps)) && + fileUptodate(out.cachedDescriptor, out.stamps) + } + + /* Skip resolve if last output exists, otherwise error. */ + def skipResolve(cache: CacheStore): UpdateInputs => UpdateReport = { import LibraryManagementCodec._ - Tracked.lastOutput[In, UpdateReport](outStore) { + Tracked.lastOutput[UpdateInputs, UpdateReport](cache) { case (_, Some(out)) => out case _ => sys.error("Skipping update requested, but update has not previously run successfully.") } } - def doWorkInternal = { (inChanged: Boolean, in: In) => - import LibraryManagementCodec._ - val outCache = Tracked.lastOutput[In, UpdateReport](outStore) { - case (_, Some(out)) if uptodate(inChanged, out) => out - case _ => work(in) + + def doResolve(cache: CacheStore): UpdateInputs => UpdateReport = { + val doCachedResolve = { (inChanged: Boolean, updateInputs: UpdateInputs) => + import LibraryManagementCodec._ + val cachedResolve = Tracked.lastOutput[UpdateInputs, UpdateReport](cache) { + case (_, Some(out)) if upToDate(inChanged, out) => out + case _ => resolve(updateInputs) + } + import scala.util.control.Exception.catching + catching(classOf[NullPointerException], classOf[OutOfMemoryError]) + .withApply { t => + val resolvedAgain = resolve(updateInputs) + val culprit = t.getClass.getSimpleName + log.warn(s"Update task caching failed due to $culprit.") + log.warn("Report the following output to sbt:") + resolvedAgain.toString.lines foreach { log.warn(_) } + log.trace(t) + resolvedAgain + } + .apply(cachedResolve(updateInputs)) } - try { - outCache(in) - } catch { - case e: NullPointerException => - val r = work(in) - log.warn("Update task has failed to cache the report due to null.") - log.warn("Report the following output to sbt:") - r.toString.lines foreach { log.warn(_) } - log.trace(e) - r - case e: OutOfMemoryError => - val r = work(in) - log.warn("Update task has failed to cache the report due to OutOfMemoryError.") - log.trace(e) - r - } - } - def doWork: In => UpdateReport = { import AltLibraryManagementCodec._ - Tracked.inputChanged(cacheStoreFactory make "inputs")(doWorkInternal) + Tracked.inputChanged(cacheStoreFactory.make("inputs"))(doCachedResolve) } - val f = if (skip && !force) skipWork else doWork - f(module.owner.configuration :+: module.moduleSettings :+: config :+: HNil) + + // Get the handler to use and feed it in the inputs + val ivyConfig = module.owner.configuration + val settings = module.moduleSettings + val outStore = cacheStoreFactory.make("output") + val handler = if (skip && !force) skipResolve(outStore) else doResolve(outStore) + handler(ivyConfig :+: settings :+: updateConfig :+: HNil) } private[this] def fileUptodate(file: File, stamps: Map[File, Long]): Boolean = From 1c961e8814e05d8389b21cee7cd9d1442a300bb1 Mon Sep 17 00:00:00 2001 From: jvican Date: Thu, 27 Apr 2017 15:32:03 +0200 Subject: [PATCH 2/4] Move `AltLibraryManagementCodec` to internal This is an implementation detail that should not be exposed in `Defaults`. It is therefore moved to `librarymanagement.internal`. --- main/src/main/scala/sbt/Defaults.scala | 109 +---------------- .../AltLibraryManagementCodec.scala | 113 ++++++++++++++++++ .../cache-update/build.sbt | 2 +- 3 files changed, 116 insertions(+), 108 deletions(-) create mode 100644 main/src/main/scala/sbt/internal/librarymanagement/AltLibraryManagementCodec.scala diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index d906b244b..cf894a32c 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2138,112 +2138,6 @@ object Classpaths { ) } - object AltLibraryManagementCodec extends LibraryManagementCodec { - type In0 = ModuleSettings :+: UpdateConfiguration :+: HNil - type In = IvyConfiguration :+: In0 - - object NullLogger extends sbt.internal.util.BasicLogger { - override def control(event: sbt.util.ControlEvent.Value, message: ⇒ String): Unit = () - override def log(level: Level.Value, message: ⇒ String): Unit = () - override def logAll(events: Seq[sbt.util.LogEvent]): Unit = () - override def success(message: ⇒ String): Unit = () - override def trace(t: ⇒ Throwable): Unit = () - } - - implicit val altRawRepositoryJsonFormat: JsonFormat[RawRepository] = - project(_.name, FakeRawRepository.create) - - // Redefine to add RawRepository, and switch to unionFormat - override lazy implicit val ResolverFormat: JsonFormat[Resolver] = - unionFormat8[Resolver, - ChainedResolver, - MavenRepo, - MavenCache, - FileRepository, - URLRepository, - SshRepository, - SftpRepository, - RawRepository] - - type InlineIvyHL = (IvyPaths :+: Vector[Resolver] :+: Vector[Resolver] :+: Vector[ - ModuleConfiguration] :+: Boolean :+: Vector[String] :+: HNil) - def inlineIvyToHL(i: InlineIvyConfiguration): InlineIvyHL = ( - i.paths :+: i.resolvers :+: i.otherResolvers :+: i.moduleConfigurations :+: i.localOnly - :+: i.checksums :+: HNil - ) - - type ExternalIvyHL = PlainFileInfo :+: Array[Byte] :+: HNil - def externalIvyToHL(e: ExternalIvyConfiguration): ExternalIvyHL = - FileInfo.exists(e.baseDirectory) :+: Hash.contentsIfLocal(e.uri) :+: HNil - - // Redefine to use a subset of properties, that are serialisable - override lazy implicit val InlineIvyConfigurationFormat: JsonFormat[InlineIvyConfiguration] = { - def hlToInlineIvy(i: InlineIvyHL): InlineIvyConfiguration = { - val (paths :+: resolvers :+: otherResolvers :+: moduleConfigurations :+: localOnly - :+: checksums :+: HNil) = i - InlineIvyConfiguration(None, - IO.createTemporaryDirectory, - NullLogger, - UpdateOptions(), - paths, - resolvers, - otherResolvers, - moduleConfigurations, - localOnly, - checksums, - None) - } - - project[InlineIvyConfiguration, InlineIvyHL](inlineIvyToHL, hlToInlineIvy) - } - - // Redefine to use a subset of properties, that are serialisable - override lazy implicit val ExternalIvyConfigurationFormat - : JsonFormat[ExternalIvyConfiguration] = { - def hlToExternalIvy(e: ExternalIvyHL): ExternalIvyConfiguration = { - val baseDirectory :+: _ /* uri */ :+: HNil = e - ExternalIvyConfiguration( - None, - baseDirectory.file, - NullLogger, - UpdateOptions(), - IO.createTemporaryDirectory.toURI /* the original uri is destroyed.. */, - Vector.empty) - } - - project[ExternalIvyConfiguration, ExternalIvyHL](externalIvyToHL, hlToExternalIvy) - } - - // Redefine to switch to unionFormat - override implicit lazy val IvyConfigurationFormat: JsonFormat[IvyConfiguration] = - unionFormat2[IvyConfiguration, InlineIvyConfiguration, ExternalIvyConfiguration] - - def forHNil[A <: HNil]: Equiv[A] = new Equiv[A] { def equiv(x: A, y: A) = true } - implicit val lnilEquiv1: Equiv[HNil] = forHNil[HNil] - implicit val lnilEquiv2: Equiv[HNil.type] = forHNil[HNil.type] - - implicit def hconsEquiv[H, T <: HList](implicit he: Equiv[H], te: Equiv[T]): Equiv[H :+: T] = - new Equiv[H :+: T] { - def equiv(x: H :+: T, y: H :+: T) = he.equiv(x.head, y.head) && te.equiv(x.tail, y.tail) - } - - implicit object altIvyConfigurationEquiv extends Equiv[IvyConfiguration] { - def equiv(x: IvyConfiguration, y: IvyConfiguration): Boolean = (x, y) match { - case (x: InlineIvyConfiguration, y: InlineIvyConfiguration) => - implicitly[Equiv[InlineIvyHL]].equiv(inlineIvyToHL(x), inlineIvyToHL(y)) - case (x: ExternalIvyConfiguration, y: ExternalIvyConfiguration) => - implicitly[Equiv[ExternalIvyHL]].equiv(externalIvyToHL(x), externalIvyToHL(y)) - case (x: Any, y: Any) => sys error s"Trying to compare ${x.getClass} with ${y.getClass}" - } - } - - implicit object altInSingletonCache extends SingletonCache[In] { - def write(to: Output, value: In) = to.write(value) - def read(from: Input) = from.read[In]() - def equiv = hconsEquiv(altIvyConfigurationEquiv, implicitly[Equiv[In0]]) - } - } - private type UpdateInputs = IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil private[sbt] def cachedUpdate( @@ -2329,7 +2223,8 @@ object Classpaths { } .apply(cachedResolve(updateInputs)) } - import AltLibraryManagementCodec._ + + import sbt.internal.librarymanagement.AltLibraryManagementCodec._ Tracked.inputChanged(cacheStoreFactory.make("inputs"))(doCachedResolve) } diff --git a/main/src/main/scala/sbt/internal/librarymanagement/AltLibraryManagementCodec.scala b/main/src/main/scala/sbt/internal/librarymanagement/AltLibraryManagementCodec.scala new file mode 100644 index 000000000..c6ceeff73 --- /dev/null +++ b/main/src/main/scala/sbt/internal/librarymanagement/AltLibraryManagementCodec.scala @@ -0,0 +1,113 @@ +package sbt.internal.librarymanagement + +import sbt.internal.util.{ HList, HNil } +import sbt.internal.util.Types._ +import sbt.io.{ Hash, IO } +import sbt.librarymanagement._ +import sbt.util._ +import sbt.util.CacheImplicits._ +import sjsonnew.JsonFormat + +object AltLibraryManagementCodec extends LibraryManagementCodec { + type In0 = ModuleSettings :+: UpdateConfiguration :+: HNil + type In = IvyConfiguration :+: In0 + + object NullLogger extends sbt.internal.util.BasicLogger { + override def control(event: sbt.util.ControlEvent.Value, message: ⇒ String): Unit = () + override def log(level: Level.Value, message: ⇒ String): Unit = () + override def logAll(events: Seq[sbt.util.LogEvent]): Unit = () + override def success(message: ⇒ String): Unit = () + override def trace(t: ⇒ Throwable): Unit = () + } + + implicit val altRawRepositoryJsonFormat: JsonFormat[RawRepository] = + project(_.name, FakeRawRepository.create) + + // Redefine to add RawRepository, and switch to unionFormat + override lazy implicit val ResolverFormat: JsonFormat[Resolver] = + unionFormat8[Resolver, + ChainedResolver, + MavenRepo, + MavenCache, + FileRepository, + URLRepository, + SshRepository, + SftpRepository, + RawRepository] + + type InlineIvyHL = (IvyPaths :+: Vector[Resolver] :+: Vector[Resolver] :+: Vector[ + ModuleConfiguration] :+: Boolean :+: Vector[String] :+: HNil) + def inlineIvyToHL(i: InlineIvyConfiguration): InlineIvyHL = ( + i.paths :+: i.resolvers :+: i.otherResolvers :+: i.moduleConfigurations :+: i.localOnly + :+: i.checksums :+: HNil + ) + + type ExternalIvyHL = PlainFileInfo :+: Array[Byte] :+: HNil + def externalIvyToHL(e: ExternalIvyConfiguration): ExternalIvyHL = + FileInfo.exists(e.baseDirectory) :+: Hash.contentsIfLocal(e.uri) :+: HNil + + // Redefine to use a subset of properties, that are serialisable + override lazy implicit val InlineIvyConfigurationFormat: JsonFormat[InlineIvyConfiguration] = { + def hlToInlineIvy(i: InlineIvyHL): InlineIvyConfiguration = { + val (paths :+: resolvers :+: otherResolvers :+: moduleConfigurations :+: localOnly + :+: checksums :+: HNil) = i + InlineIvyConfiguration(None, + IO.createTemporaryDirectory, + NullLogger, + UpdateOptions(), + paths, + resolvers, + otherResolvers, + moduleConfigurations, + localOnly, + checksums, + None) + } + + project[InlineIvyConfiguration, InlineIvyHL](inlineIvyToHL, hlToInlineIvy) + } + + // Redefine to use a subset of properties, that are serialisable + override lazy implicit val ExternalIvyConfigurationFormat + : JsonFormat[ExternalIvyConfiguration] = { + def hlToExternalIvy(e: ExternalIvyHL): ExternalIvyConfiguration = { + val baseDirectory :+: _ /* uri */ :+: HNil = e + ExternalIvyConfiguration( + None, + baseDirectory.file, + NullLogger, + UpdateOptions(), + IO.createTemporaryDirectory.toURI /* the original uri is destroyed.. */, + Vector.empty) + } + + project[ExternalIvyConfiguration, ExternalIvyHL](externalIvyToHL, hlToExternalIvy) + } + + // Redefine to switch to unionFormat + override implicit lazy val IvyConfigurationFormat: JsonFormat[IvyConfiguration] = + unionFormat2[IvyConfiguration, InlineIvyConfiguration, ExternalIvyConfiguration] + + def forHNil[A <: HNil]: Equiv[A] = (_: A, _: A) => true + implicit val lnilEquiv1: Equiv[HNil] = forHNil[HNil] + implicit val lnilEquiv2: Equiv[HNil.type] = forHNil[HNil.type] + + implicit def hconsEquiv[H, T <: HList](implicit he: Equiv[H], te: Equiv[T]): Equiv[H :+: T] = + (x: H :+: T, y: H :+: T) => he.equiv(x.head, y.head) && te.equiv(x.tail, y.tail) + + implicit object altIvyConfigurationEquiv extends Equiv[IvyConfiguration] { + def equiv(x: IvyConfiguration, y: IvyConfiguration): Boolean = (x, y) match { + case (x: InlineIvyConfiguration, y: InlineIvyConfiguration) => + implicitly[Equiv[InlineIvyHL]].equiv(inlineIvyToHL(x), inlineIvyToHL(y)) + case (x: ExternalIvyConfiguration, y: ExternalIvyConfiguration) => + implicitly[Equiv[ExternalIvyHL]].equiv(externalIvyToHL(x), externalIvyToHL(y)) + case (x: Any, y: Any) => sys error s"Trying to compare ${x.getClass} with ${y.getClass}" + } + } + + implicit object altInSingletonCache extends SingletonCache[In] { + def write(to: Output, value: In) = to.write(value) + def read(from: Input) = from.read[In]() + def equiv = hconsEquiv(altIvyConfigurationEquiv, implicitly[Equiv[In0]]) + } +} diff --git a/sbt/src/sbt-test/dependency-management/cache-update/build.sbt b/sbt/src/sbt-test/dependency-management/cache-update/build.sbt index 1b8656650..28e616e9c 100644 --- a/sbt/src/sbt-test/dependency-management/cache-update/build.sbt +++ b/sbt/src/sbt-test/dependency-management/cache-update/build.sbt @@ -22,7 +22,7 @@ TaskKey[Unit]("check") := { type In = IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil import sbt.util.CacheImplicits._ - import sbt.Classpaths.AltLibraryManagementCodec._ + import sbt.internal.librarymanagement.AltLibraryManagementCodec._ val f: In => Unit = Tracked.inputChanged(cacheStoreFactory make "inputs") { (inChanged: Boolean, in: In) => From e7b8cbfe010ee447546005e1fdcbfe1de599142a Mon Sep 17 00:00:00 2001 From: jvican Date: Thu, 27 Apr 2017 16:00:48 +0200 Subject: [PATCH 3/4] Move `cachedUpdate` to new `DependencyResolver` The `cachedUpdate` implementation does not need to be in `Defaults` since it's not using any of the tasks/settings defined there, that's `updateTask`'s job. This commit moves the utilities required by `updateTask` to the `sbt.internal.librarymanagement` namespace. --- main/src/main/scala/sbt/Defaults.scala | 103 +-------------- .../DependencyResolver.scala | 117 ++++++++++++++++++ 2 files changed, 118 insertions(+), 102 deletions(-) create mode 100644 main/src/main/scala/sbt/internal/librarymanagement/DependencyResolver.scala diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index cf894a32c..4965653a4 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2119,7 +2119,7 @@ object Classpaths { else (evictionWarningOptions in update).value } - cachedUpdate( + DependencyResolver.cachedUpdate( s.cacheStoreFactory.sub(updateCacheName.value), Reference.display(thisProjectRef.value), ivyModule.value, @@ -2138,107 +2138,6 @@ object Classpaths { ) } - private type UpdateInputs = IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil - - private[sbt] def cachedUpdate( - cacheStoreFactory: CacheStoreFactory, - label: String, - module: IvySbt#Module, - updateConfig: UpdateConfiguration, - transform: UpdateReport => UpdateReport, - skip: Boolean, - force: Boolean, - depsUpdated: Boolean, - uwConfig: UnresolvedWarningConfiguration, - logicalClock: LogicalClock, - depDir: Option[File], - ewo: EvictionWarningOptions, - mavenStyle: Boolean, - compatWarning: CompatibilityWarningOptions, - log: Logger - ): UpdateReport = { - - /* Resolve the module settings from the inputs. */ - def resolve(inputs: UpdateInputs): UpdateReport = { - import ShowLines._ - - log.info(s"Updating $label...") - val reportOrUnresolved: Either[UnresolvedWarning, UpdateReport] = - IvyActions.updateEither(module, updateConfig, uwConfig, logicalClock, depDir, log) - - val report = reportOrUnresolved match { - case Right(report0) => report0 - case Left(unresolvedWarning) => - unresolvedWarning.lines.foreach(log.warn(_)) - throw unresolvedWarning.resolveException - } - log.info("Done updating.") - val finalReport = transform(report) - - // Warn of any eviction and compatibility warnings - val ew = EvictionWarning(module, ewo, finalReport, log) - ew.lines foreach { log.warn(_) } - ew.infoAllTheThings foreach { log.info(_) } - CompatibilityWarning.run(compatWarning, module, mavenStyle, log) - - finalReport - } - - /* Check if a update report is still up to date or we must resolve again. */ - def upToDate(inChanged: Boolean, out: UpdateReport): Boolean = { - !force && - !depsUpdated && - !inChanged && - out.allFiles.forall(f => fileUptodate(f, out.stamps)) && - fileUptodate(out.cachedDescriptor, out.stamps) - } - - /* Skip resolve if last output exists, otherwise error. */ - def skipResolve(cache: CacheStore): UpdateInputs => UpdateReport = { - import LibraryManagementCodec._ - Tracked.lastOutput[UpdateInputs, UpdateReport](cache) { - case (_, Some(out)) => out - case _ => - sys.error("Skipping update requested, but update has not previously run successfully.") - } - } - - def doResolve(cache: CacheStore): UpdateInputs => UpdateReport = { - val doCachedResolve = { (inChanged: Boolean, updateInputs: UpdateInputs) => - import LibraryManagementCodec._ - val cachedResolve = Tracked.lastOutput[UpdateInputs, UpdateReport](cache) { - case (_, Some(out)) if upToDate(inChanged, out) => out - case _ => resolve(updateInputs) - } - import scala.util.control.Exception.catching - catching(classOf[NullPointerException], classOf[OutOfMemoryError]) - .withApply { t => - val resolvedAgain = resolve(updateInputs) - val culprit = t.getClass.getSimpleName - log.warn(s"Update task caching failed due to $culprit.") - log.warn("Report the following output to sbt:") - resolvedAgain.toString.lines foreach { log.warn(_) } - log.trace(t) - resolvedAgain - } - .apply(cachedResolve(updateInputs)) - } - - import sbt.internal.librarymanagement.AltLibraryManagementCodec._ - Tracked.inputChanged(cacheStoreFactory.make("inputs"))(doCachedResolve) - } - - // Get the handler to use and feed it in the inputs - val ivyConfig = module.owner.configuration - val settings = module.moduleSettings - val outStore = cacheStoreFactory.make("output") - val handler = if (skip && !force) skipResolve(outStore) else doResolve(outStore) - handler(ivyConfig :+: settings :+: updateConfig :+: HNil) - } - - private[this] def fileUptodate(file: File, stamps: Map[File, Long]): Boolean = - stamps.get(file).forall(_ == file.lastModified) - private[sbt] def dependencyPositionsTask: Initialize[Task[Map[ModuleID, SourcePosition]]] = Def.task { val projRef = thisProjectRef.value diff --git a/main/src/main/scala/sbt/internal/librarymanagement/DependencyResolver.scala b/main/src/main/scala/sbt/internal/librarymanagement/DependencyResolver.scala new file mode 100644 index 000000000..a63a1e19f --- /dev/null +++ b/main/src/main/scala/sbt/internal/librarymanagement/DependencyResolver.scala @@ -0,0 +1,117 @@ +package sbt.internal.librarymanagement + +import java.io.File + +import sbt.internal.util.Types._ +import sbt.internal.util.HNil +import sbt.librarymanagement._ +import sbt.util.{ CacheStore, CacheStoreFactory, Logger, Tracked } +import sbt.librarymanagement.syntax._ +import sbt.util.CacheImplicits._ + +object DependencyResolver { + + private type UpdateInputs = IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil + + private[sbt] def cachedUpdate( + cacheStoreFactory: CacheStoreFactory, + label: String, + module: IvySbt#Module, + updateConfig: UpdateConfiguration, + transform: UpdateReport => UpdateReport, + skip: Boolean, + force: Boolean, + depsUpdated: Boolean, + uwConfig: UnresolvedWarningConfiguration, + logicalClock: LogicalClock, + depDir: Option[File], + ewo: EvictionWarningOptions, + mavenStyle: Boolean, + compatWarning: CompatibilityWarningOptions, + log: Logger + ): UpdateReport = { + + /* Resolve the module settings from the inputs. */ + def resolve(inputs: UpdateInputs): UpdateReport = { + import sbt.util.ShowLines._ + + log.info(s"Updating $label...") + val reportOrUnresolved: Either[UnresolvedWarning, UpdateReport] = + IvyActions.updateEither(module, updateConfig, uwConfig, logicalClock, depDir, log) + + val report = reportOrUnresolved match { + case Right(report0) => report0 + case Left(unresolvedWarning) => + unresolvedWarning.lines.foreach(log.warn(_)) + throw unresolvedWarning.resolveException + } + log.info("Done updating.") + val finalReport = transform(report) + + // Warn of any eviction and compatibility warnings + val ew = EvictionWarning(module, ewo, finalReport, log) + ew.lines.foreach(log.warn(_)) + ew.infoAllTheThings.foreach(log.info(_)) + CompatibilityWarning.run(compatWarning, module, mavenStyle, log) + + finalReport + } + + /* Check if a update report is still up to date or we must resolve again. */ + def upToDate(inChanged: Boolean, out: UpdateReport): Boolean = { + !force && + !depsUpdated && + !inChanged && + out.allFiles.forall(f => fileUptodate(f, out.stamps)) && + fileUptodate(out.cachedDescriptor, out.stamps) + } + + /* Skip resolve if last output exists, otherwise error. */ + def skipResolve(cache: CacheStore): UpdateInputs => UpdateReport = { + import sbt.librarymanagement.LibraryManagementCodec._ + Tracked.lastOutput[UpdateInputs, UpdateReport](cache) { + case (_, Some(out)) => out + case _ => + sys.error("Skipping update requested, but update has not previously run successfully.") + } + } + + def doResolve(cache: CacheStore): UpdateInputs => UpdateReport = { + val doCachedResolve = { (inChanged: Boolean, updateInputs: UpdateInputs) => + import sbt.librarymanagement.LibraryManagementCodec._ + val cachedResolve = Tracked.lastOutput[UpdateInputs, UpdateReport](cache) { + case (_, Some(out)) if upToDate(inChanged, out) => out + case _ => resolve(updateInputs) + } + import scala.util.control.Exception.catching + catching(classOf[NullPointerException], classOf[OutOfMemoryError]) + .withApply { t => + val resolvedAgain = resolve(updateInputs) + val culprit = t.getClass.getSimpleName + log.warn(s"Update task caching failed due to $culprit.") + log.warn("Report the following output to sbt:") + resolvedAgain.toString.lines foreach { + log.warn(_) + } + log.trace(t) + resolvedAgain + } + .apply(cachedResolve(updateInputs)) + } + + import sbt.internal.librarymanagement.AltLibraryManagementCodec._ + Tracked.inputChanged(cacheStoreFactory.make("inputs"))(doCachedResolve) + } + + // Get the handler to use and feed it in the inputs + val ivyConfig = module.owner.configuration + val settings = module.moduleSettings + val outStore = cacheStoreFactory.make("output") + val handler = if (skip && !force) skipResolve(outStore) else doResolve(outStore) + handler(ivyConfig :+: settings :+: updateConfig :+: HNil) + } + + private[this] def fileUptodate(file: File, stamps: Map[File, Long]): Boolean = + stamps.get(file).forall(_ == file.lastModified) + +} From 1252db6b4ac9dc4bbf10b76cf12987c9edb58714 Mon Sep 17 00:00:00 2001 From: jvican Date: Wed, 3 May 2017 16:14:02 +0200 Subject: [PATCH 4/4] Address Eugene's feedback --- main/src/main/scala/sbt/Defaults.scala | 2 +- .../AltLibraryManagementCodec.scala | 7 ++++--- ...ncyResolver.scala => LibraryManagement.scala} | 16 +++++++--------- .../dependency-management/cache-update/build.sbt | 2 +- 4 files changed, 13 insertions(+), 14 deletions(-) rename main/src/main/scala/sbt/internal/{librarymanagement => }/AltLibraryManagementCodec.scala (98%) rename main/src/main/scala/sbt/internal/{librarymanagement/DependencyResolver.scala => LibraryManagement.scala} (94%) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 4965653a4..4c958c62d 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2119,7 +2119,7 @@ object Classpaths { else (evictionWarningOptions in update).value } - DependencyResolver.cachedUpdate( + LibraryManagement.cachedUpdate( s.cacheStoreFactory.sub(updateCacheName.value), Reference.display(thisProjectRef.value), ivyModule.value, diff --git a/main/src/main/scala/sbt/internal/librarymanagement/AltLibraryManagementCodec.scala b/main/src/main/scala/sbt/internal/AltLibraryManagementCodec.scala similarity index 98% rename from main/src/main/scala/sbt/internal/librarymanagement/AltLibraryManagementCodec.scala rename to main/src/main/scala/sbt/internal/AltLibraryManagementCodec.scala index c6ceeff73..12caa6c47 100644 --- a/main/src/main/scala/sbt/internal/librarymanagement/AltLibraryManagementCodec.scala +++ b/main/src/main/scala/sbt/internal/AltLibraryManagementCodec.scala @@ -1,11 +1,12 @@ -package sbt.internal.librarymanagement +package sbt.internal -import sbt.internal.util.{ HList, HNil } +import sbt.internal.librarymanagement._ import sbt.internal.util.Types._ +import sbt.internal.util.{ HList, HNil } import sbt.io.{ Hash, IO } import sbt.librarymanagement._ -import sbt.util._ import sbt.util.CacheImplicits._ +import sbt.util._ import sjsonnew.JsonFormat object AltLibraryManagementCodec extends LibraryManagementCodec { diff --git a/main/src/main/scala/sbt/internal/librarymanagement/DependencyResolver.scala b/main/src/main/scala/sbt/internal/LibraryManagement.scala similarity index 94% rename from main/src/main/scala/sbt/internal/librarymanagement/DependencyResolver.scala rename to main/src/main/scala/sbt/internal/LibraryManagement.scala index a63a1e19f..fa78eff39 100644 --- a/main/src/main/scala/sbt/internal/librarymanagement/DependencyResolver.scala +++ b/main/src/main/scala/sbt/internal/LibraryManagement.scala @@ -1,15 +1,16 @@ -package sbt.internal.librarymanagement +package sbt.internal import java.io.File -import sbt.internal.util.Types._ +import sbt.internal.librarymanagement._ import sbt.internal.util.HNil +import sbt.internal.util.Types._ import sbt.librarymanagement._ -import sbt.util.{ CacheStore, CacheStoreFactory, Logger, Tracked } import sbt.librarymanagement.syntax._ import sbt.util.CacheImplicits._ +import sbt.util.{ CacheStore, CacheStoreFactory, Logger, Tracked } -object DependencyResolver { +object LibraryManagement { private type UpdateInputs = IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil @@ -90,16 +91,13 @@ object DependencyResolver { val culprit = t.getClass.getSimpleName log.warn(s"Update task caching failed due to $culprit.") log.warn("Report the following output to sbt:") - resolvedAgain.toString.lines foreach { - log.warn(_) - } + resolvedAgain.toString.lines.foreach(log.warn(_)) log.trace(t) resolvedAgain } .apply(cachedResolve(updateInputs)) } - - import sbt.internal.librarymanagement.AltLibraryManagementCodec._ + import AltLibraryManagementCodec._ Tracked.inputChanged(cacheStoreFactory.make("inputs"))(doCachedResolve) } diff --git a/sbt/src/sbt-test/dependency-management/cache-update/build.sbt b/sbt/src/sbt-test/dependency-management/cache-update/build.sbt index 28e616e9c..2043ff2f4 100644 --- a/sbt/src/sbt-test/dependency-management/cache-update/build.sbt +++ b/sbt/src/sbt-test/dependency-management/cache-update/build.sbt @@ -22,7 +22,7 @@ TaskKey[Unit]("check") := { type In = IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil import sbt.util.CacheImplicits._ - import sbt.internal.librarymanagement.AltLibraryManagementCodec._ + import sbt.internal.AltLibraryManagementCodec._ val f: In => Unit = Tracked.inputChanged(cacheStoreFactory make "inputs") { (inChanged: Boolean, in: In) =>