From b68498d013fd5de33758921318f23e9351d58b59 Mon Sep 17 00:00:00 2001 From: jvican Date: Thu, 27 Apr 2017 21:22:33 +0200 Subject: [PATCH 1/7] Remove unused arguments in clean and use site `cleanCachedResolutionCache` and `clean` were doing extra work because `clean` does only need to clean the cache in the configuration (that is a class parameter). --- .../scala/sbt/internal/librarymanagement/Ivy.scala | 13 ++++--------- .../ivyint/CachedResolutionResolveEngine.scala | 5 ++--- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/Ivy.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/Ivy.scala index b5084ffb6..d0218ad9e 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/Ivy.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/Ivy.scala @@ -159,15 +159,10 @@ final class IvySbt(val configuration: IvyConfiguration) { self => * * @param md - module descriptor of the original Ivy graph. */ - private[sbt] def cleanCachedResolutionCache(md: ModuleDescriptor, log: Logger): Unit = - withIvy(log) { i => - val prOpt = Option(i.getSettings.getResolver(ProjectResolver.InterProject)) map { - case pr: ProjectResolver => pr - } - if (configuration.updateOptions.cachedResolution) { - IvySbt.cachedResolutionResolveCache.clean(md, prOpt) - } - } + private[sbt] def cleanCachedResolutionCache(md: ModuleDescriptor, log: Logger): Unit = { + if (!configuration.updateOptions.cachedResolution) () + else IvySbt.cachedResolutionResolveCache.clean() + } final class Module(rawModuleSettings: ModuleSettings) { val moduleSettings: ModuleSettings = IvySbt.substituteCross(rawModuleSettings) diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ivyint/CachedResolutionResolveEngine.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ivyint/CachedResolutionResolveEngine.scala index 0b4e2b92a..cfb2cc755 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ivyint/CachedResolutionResolveEngine.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ivyint/CachedResolutionResolveEngine.scala @@ -64,9 +64,8 @@ private[sbt] class CachedResolutionResolveCache { val maxConflictCacheSize: Int = 1024 val maxUpdateReportCacheSize: Int = 1024 - def clean(md0: ModuleDescriptor, prOpt: Option[ProjectResolver]): Unit = { - updateReportCache.clear - } + def clean(): Unit = updateReportCache.clear + def directDependencies(md0: ModuleDescriptor): Vector[DependencyDescriptor] = md0.getDependencies.toVector // Returns a vector of (module descriptor, changing, dd) From eea500d64f7d5ac5c38da2096cbabdc436c36643 Mon Sep 17 00:00:00 2001 From: jvican Date: Fri, 28 Apr 2017 01:12:34 +0200 Subject: [PATCH 2/7] Remove coded uplication in `updateEither` This commit reduces the code duplication in `updateEither` which was duplicating a good deal of the resolution logic to deal with the different resolution mechanisms: the simple one and the cached one. It also unifies the signatures of the helpers that are invoked by `updateEither`, removing the weirdness of the different return type signatures and ad-hoc logic handling. --- .../librarymanagement/IvyActions.scala | 182 +++++++++++------- 1 file changed, 112 insertions(+), 70 deletions(-) diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala index aad711ff5..c5bce8839 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala @@ -236,56 +236,32 @@ object IvyActions { logicalClock: LogicalClock, depDir: Option[File], log: Logger - ): Either[UnresolvedWarning, UpdateReport] = + ): Either[UnresolvedWarning, UpdateReport] = { module.withModule(log) { - case (ivy, md, default) - if module.owner.configuration.updateOptions.cachedResolution && depDir.isDefined => - ivy.getResolveEngine match { - case x: CachedResolutionResolveEngine => - val iw = IvySbt.inconsistentDuplicateWarning(md) - iw foreach { log.warn(_) } - val resolveOptions = new ResolveOptions - val resolveId = ResolveOptions.getDefaultResolveId(md) - resolveOptions.setResolveId(resolveId) - resolveOptions.setArtifactFilter(configuration.artifactFilter) - resolveOptions.setLog(ivyLogLevel(configuration.logging)) - x.customResolve( - md, - configuration.missingOk, - logicalClock, - resolveOptions, - depDir getOrElse { - sys.error("dependency base directory is not specified") - }, - log - ) match { - case Left(x) => - Left(UnresolvedWarning(x, uwconfig)) - case Right(uReport) => - configuration.retrieve match { - case Some(rConf) => Right(retrieve(log, ivy, uReport, rConf)) - case None => Right(uReport) - } - } - } - case (ivy, md, default) => - val iw = IvySbt.inconsistentDuplicateWarning(md) - iw foreach { log.warn(_) } - val (report, err) = - resolve(configuration.logging)(ivy, md, default, configuration.artifactFilter) - err match { - case Some(x) if !configuration.missingOk => - Left(UnresolvedWarning(x, uwconfig)) - case _ => - val cachedDescriptor = ivy.getSettings.getResolutionCacheManager - .getResolvedIvyFileInCache(md.getModuleRevisionId) - val uReport = IvyRetrieve.updateReport(report, cachedDescriptor) - configuration.retrieve match { - case Some(rConf) => Right(retrieve(log, ivy, uReport, rConf)) - case None => Right(uReport) - } + case (ivy, moduleDescriptor, defaultConf) => + // Warn about duplicated and inconsistent dependencies + val iw = IvySbt.inconsistentDuplicateWarning(moduleDescriptor) + iw.foreach(log.warn(_)) + + // Create inputs, resolve and retrieve the module descriptor + val inputs = ResolutionInputs(ivy, moduleDescriptor, configuration, log) + val resolutionResult: Either[ResolveException, UpdateReport] = { + if (module.owner.configuration.updateOptions.cachedResolution && depDir.isDefined) { + val cache = depDir.getOrElse(sys.error("Missing directory for cached resolution.")) + cachedResolveAndRetrieve(inputs, logicalClock, cache) + } else resolveAndRetrieve(inputs, defaultConf) } + + // Convert to unresolved warning or retrieve update report + resolutionResult.fold( + exception => Left(UnresolvedWarning(exception, uwconfig)), + updateReport => { + val retrieveConf = configuration.retrieve + Right(retrieveConf.map(retrieve(log, ivy, updateReport, _)).getOrElse(updateReport)) + } + ) } + } @deprecated("No longer used.", "0.13.6") def processUnresolved(err: ResolveException, log: Logger): Unit = () def groupedConflicts[T](moduleFilter: ModuleFilter, grouping: ModuleID => T)( @@ -496,38 +472,104 @@ object IvyActions { .withConfigurations(if (confs) m.configurations else None) .branch(m.branchName) - private[this] def resolve(logging: UpdateLogging)( + /** + * Represents the inputs to pass in to [[resolveAndRetrieve]] and [[cachedResolveAndRetrieve]]. + * + * @param ivy The ivy instance to resolve and retrieve dependencies. + * @param module The module descriptor to be resolved. + * @param updateConfiguration The update configuration for [[ResolveOptions]]. + * @param log The logger. + */ + private case class ResolutionInputs( ivy: Ivy, module: DefaultModuleDescriptor, - defaultConf: String, - filter: ArtifactTypeFilter - ): (ResolveReport, Option[ResolveException]) = { + updateConfiguration: UpdateConfiguration, + log: Logger + ) + + /** + * Defines the internal entrypoint of module resolution and retrieval. + * + * This method is the responsible of populating [[ResolveOptions]] and pass + * it in to the ivy instance to perform the module resolution. + * + * It returns an already resolved [[UpdateReport]] instead of a [[ResolveReport]] + * like its counterpart [[CachedResolutionResolveEngine.customResolve]]. + * + * @param inputs The resolution inputs. + * @param defaultModuleConfiguration The default ivy configuration. + * @return The result of the resolution. + */ + private[this] def resolveAndRetrieve( + inputs: ResolutionInputs, + defaultModuleConfiguration: String + ): Either[ResolveException, UpdateReport] = { + // Populate resolve options from the passed arguments + val ivyInstance = inputs.ivy + val moduleDescriptor = inputs.module + val updateConfiguration = inputs.updateConfiguration + val logging = updateConfiguration.logging val resolveOptions = new ResolveOptions - val resolveId = ResolveOptions.getDefaultResolveId(module) + val resolveId = ResolveOptions.getDefaultResolveId(moduleDescriptor) resolveOptions.setResolveId(resolveId) - resolveOptions.setArtifactFilter(filter) + resolveOptions.setArtifactFilter(updateConfiguration.artifactFilter) resolveOptions.setLog(ivyLogLevel(logging)) ResolutionCache.cleanModule( - module.getModuleRevisionId, + moduleDescriptor.getModuleRevisionId, resolveId, - ivy.getSettings.getResolutionCacheManager + ivyInstance.getSettings.getResolutionCacheManager ) - val resolveReport = ivy.resolve(module, resolveOptions) - val err = - if (resolveReport.hasError) { - val messages = resolveReport.getAllProblemMessages.toArray.map(_.toString).distinct - val failedPaths = Map(resolveReport.getUnresolvedDependencies map { node => - val m = IvyRetrieve.toModuleID(node.getId) - val path = IvyRetrieve.findPath(node, module.getModuleRevisionId) map { x => - IvyRetrieve.toModuleID(x.getId) - } - m -> path - }: _*) - val failed = failedPaths.keys.toSeq - Some(new ResolveException(messages, failed, failedPaths)) - } else None - (resolveReport, err) + + val resolveReport = ivyInstance.resolve(moduleDescriptor, resolveOptions) + if (resolveReport.hasError && !inputs.updateConfiguration.missingOk) { + // If strict error, collect report information and generated UnresolvedWarning + val messages = resolveReport.getAllProblemMessages.toArray.map(_.toString).distinct + val failedPaths = resolveReport.getUnresolvedDependencies.map { node => + val moduleID = IvyRetrieve.toModuleID(node.getId) + val path = IvyRetrieve + .findPath(node, moduleDescriptor.getModuleRevisionId) + .map(x => IvyRetrieve.toModuleID(x.getId)) + moduleID -> path + }.toMap + val failedModules = failedPaths.keys.toSeq + Left(new ResolveException(messages, failedModules, failedPaths)) + } else { + // If no strict error, we convert the resolve report into an update report + val cachedDescriptor = ivyInstance.getSettings.getResolutionCacheManager + .getResolvedIvyFileInCache(moduleDescriptor.getModuleRevisionId) + Right(IvyRetrieve.updateReport(resolveReport, cachedDescriptor)) + } } + + /** + * Resolves and retrieves a module with a cache mechanism defined + * here. + * + * It's the cached version of [[resolveAndRetrieve]]. + * + * @param inputs The resolution inputs. + * @param logicalClock The clock to check if a file is outdated or not. + * @param cache The optional cache dependency. + * @return The result of the cached resolution. + */ + private[this] def cachedResolveAndRetrieve( + inputs: ResolutionInputs, + logicalClock: LogicalClock, + cache: File + ): Either[ResolveException, UpdateReport] = { + val log = inputs.log + val descriptor = inputs.module + val updateConfiguration = inputs.updateConfiguration + val resolver = inputs.ivy.getResolveEngine.asInstanceOf[CachedResolutionResolveEngine] + val resolveOptions = new ResolveOptions + val resolveId = ResolveOptions.getDefaultResolveId(descriptor) + resolveOptions.setResolveId(resolveId) + resolveOptions.setArtifactFilter(updateConfiguration.artifactFilter) + resolveOptions.setLog(ivyLogLevel(updateConfiguration.logging)) + val acceptError = updateConfiguration.missingOk + resolver.customResolve(descriptor, acceptError, logicalClock, resolveOptions, cache, log) + } + private def retrieve( log: Logger, ivy: Ivy, From 258cb7c5ac863fd5e9376811b7a3d207a16b4595 Mon Sep 17 00:00:00 2001 From: jvican Date: Fri, 28 Apr 2017 01:16:39 +0200 Subject: [PATCH 3/7] Remove unused instance of `retrieve` This commit removes the second instance of `retrieve` that was private and unused, therefore reducing the API surface of `IvyActions`. --- .../librarymanagement/IvyActions.scala | 26 ++++--------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala index c5bce8839..2e5d7bef6 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala @@ -575,27 +575,11 @@ object IvyActions { ivy: Ivy, report: UpdateReport, config: RetrieveConfiguration - ): UpdateReport = - retrieve( - log, - ivy, - report, - config.retrieveDirectory, - config.outputPattern, - config.sync, - config.configurationsToRetrieve - ) - - private def retrieve( - log: Logger, - ivy: Ivy, - report: UpdateReport, - base: File, - pattern: String, - sync: Boolean, - configurationsToRetrieve: Option[Set[Configuration]] ): UpdateReport = { - val configurationNames = configurationsToRetrieve match { + val toRetrieve = config.configurationsToRetrieve + val base = config.retrieveDirectory + val pattern = config.outputPattern + val configurationNames = toRetrieve match { case None => None case Some(configs) => Some(configs.map(_.name)) } @@ -611,7 +595,7 @@ object IvyActions { } IO.copy(toCopy) val resolvedFiles = toCopy.map(_._2) - if (sync) { + if (config.sync) { val filesToDelete = existingFiles.filterNot(resolvedFiles.contains) filesToDelete foreach { f => log.info(s"Deleting old dependency: ${f.getAbsolutePath}") From c84d2979122745ea4bc3942fccc61903435fcaf4 Mon Sep 17 00:00:00 2001 From: jvican Date: Fri, 28 Apr 2017 01:23:03 +0200 Subject: [PATCH 4/7] Turn `ResolveException` private[sbt] Plus, remove deprecated method using `ResolveException`. --- .../scala/sbt/internal/librarymanagement/IvyActions.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala index 2e5d7bef6..f13ebb7dd 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala @@ -262,8 +262,7 @@ object IvyActions { ) } } - @deprecated("No longer used.", "0.13.6") - def processUnresolved(err: ResolveException, log: Logger): Unit = () + def groupedConflicts[T](moduleFilter: ModuleFilter, grouping: ModuleID => T)( report: UpdateReport ): Map[T, Set[String]] = @@ -685,7 +684,8 @@ object IvyActions { ) } } -final class ResolveException( + +private[sbt] final class ResolveException( val messages: Seq[String], val failed: Seq[ModuleID], val failedPaths: Map[ModuleID, Seq[ModuleID]] From 1f4030ff17b4bca9d6957f9332e59945db3fead7 Mon Sep 17 00:00:00 2001 From: jvican Date: Fri, 28 Apr 2017 01:24:35 +0200 Subject: [PATCH 5/7] Remove unused apply in `UnresolvedWarning` --- .../scala/sbt/internal/librarymanagement/IvyActions.scala | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala index f13ebb7dd..a6e21056a 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala @@ -723,13 +723,9 @@ object UnresolvedWarning { (id, modulePosition(id)) } } - apply(err, failedPaths) - } - private[sbt] def apply( - err: ResolveException, - failedPaths: Seq[Seq[(ModuleID, Option[SourcePosition])]] - ): UnresolvedWarning = new UnresolvedWarning(err, failedPaths) + } + private[sbt] def sourcePosStr(posOpt: Option[SourcePosition]): String = posOpt match { case Some(LinePosition(path, start)) => s" ($path#L$start)" From 429dd843935a309e3df4a444fc40dd29cb0ae121 Mon Sep 17 00:00:00 2001 From: jvican Date: Fri, 28 Apr 2017 01:27:45 +0200 Subject: [PATCH 6/7] Remove deprecated methods in `IvyActions` --- .../librarymanagement/IvyActions.scala | 55 ------------------- 1 file changed, 55 deletions(-) diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala index a6e21056a..49b0e1091 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala @@ -202,29 +202,6 @@ object IvyActions { zipped map { case (a, ivyA) => (ivyA, artifacts(a)) } } - /** - * Resolves and retrieves dependencies. 'ivyConfig' is used to produce an Ivy file and configuration. - * 'updateConfig' configures the actual resolution and retrieval process. - */ - @deprecated("This is no longer public.", "0.13.6") - def update( - module: IvySbt#Module, - configuration: UpdateConfiguration, - log: Logger - ): UpdateReport = - updateEither( - module, - configuration, - UnresolvedWarningConfiguration(), - LogicalClock.unknown, - None, - log - ) match { - case Right(r) => r - case Left(w) => - throw w.resolveException - } - /** * Resolves and retrieves dependencies. 'ivyConfig' is used to produce an Ivy file and configuration. * 'updateConfig' configures the actual resolution and retrieval process. @@ -277,23 +254,6 @@ object IvyActions { def grouped[T](grouping: ModuleID => T)(mods: Seq[ModuleID]): Map[T, Set[String]] = mods groupBy (grouping) mapValues (_.map(_.revision).toSet) - @deprecated("This is no longer public.", "0.13.6") - def transitiveScratch( - ivySbt: IvySbt, - label: String, - config: GetClassifiersConfiguration, - log: Logger - ): UpdateReport = - transitiveScratch( - ivySbt, - label, - config, - UnresolvedWarningConfiguration(), - LogicalClock.unknown, - None, - log - ) - private[sbt] def transitiveScratch( ivySbt: IvySbt, label: String, @@ -316,21 +276,6 @@ object IvyActions { val newConfig = config.copy(module = mod.copy(modules = report.allModules)) updateClassifiers(ivySbt, newConfig, uwconfig, logicalClock, depDir, Vector(), log) } - @deprecated("This is no longer public.", "0.13.6") - def updateClassifiers( - ivySbt: IvySbt, - config: GetClassifiersConfiguration, - log: Logger - ): UpdateReport = - updateClassifiers( - ivySbt, - config, - UnresolvedWarningConfiguration(), - LogicalClock.unknown, - None, - Vector(), - log - ) /** * Creates explicit artifacts for each classifier in `config.module`, and then attempts to resolve them directly. This From 0885ae1a966df34c68c9df532f18bf583e7c1380 Mon Sep 17 00:00:00 2001 From: jvican Date: Fri, 28 Apr 2017 01:32:07 +0200 Subject: [PATCH 7/7] Add documentation to `updateEither` --- .../internal/librarymanagement/IvyActions.scala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala index 49b0e1091..0c7cef4a6 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala @@ -203,8 +203,18 @@ object IvyActions { } /** - * Resolves and retrieves dependencies. 'ivyConfig' is used to produce an Ivy file and configuration. - * 'updateConfig' configures the actual resolution and retrieval process. + * Updates one module's dependencies performing a dependency resolution and retrieval. + * + * The following mechanism uses ivy under the hood. + * + * @param module The module to be resolved. + * @param configuration The update configuration. + * @param uwconfig The configuration to handle unresolved warnings. + * @param logicalClock The clock necessary to cache ivy. + * @param depDir The base directory used for caching resolution. + * @param log The logger. + * @return The result, either an unresolved warning or an update report. Note that this + * update report will or will not be successful depending on the `missingOk` option. */ private[sbt] def updateEither( module: IvySbt#Module,