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,