diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ConvertResolver.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ConvertResolver.scala index 82760272c..2a75124c5 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ConvertResolver.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ConvertResolver.scala @@ -5,10 +5,15 @@ package sbt.internal.librarymanagement import java.net.URL import java.util.Collections + import org.apache.ivy.core.module.descriptor.DependencyDescriptor import org.apache.ivy.core.resolve.ResolveData import org.apache.ivy.core.settings.IvySettings -import org.apache.ivy.plugins.repository.{ RepositoryCopyProgressListener, TransferEvent } +import org.apache.ivy.plugins.repository.{ + RepositoryCopyProgressListener, + Resource, + TransferEvent +} import org.apache.ivy.plugins.resolver.{ BasicResolver, DependencyResolver, @@ -24,9 +29,10 @@ import org.apache.ivy.plugins.resolver.{ URLResolver } import org.apache.ivy.plugins.repository.url.{ URLRepository => URLRepo } -import org.apache.ivy.plugins.repository.file.{ FileRepository => FileRepo, FileResource } -import java.io.{ IOException, File } -import org.apache.ivy.util.{ FileUtil, ChecksumHelper } +import org.apache.ivy.plugins.repository.file.{ FileResource, FileRepository => FileRepo } +import java.io.{ File, IOException } + +import org.apache.ivy.util.{ ChecksumHelper, FileUtil, Message } import org.apache.ivy.core.module.descriptor.{ Artifact => IArtifact } import sbt.io.IO import sbt.util.Logger @@ -156,6 +162,7 @@ private[sbt] object ConvertResolver { extends IBiblioResolver with ChecksumFriendlyURLResolver with DescriptorRequired { + override def getResource(resource: Resource, dest: File): Long = get(resource, dest) def setPatterns(): Unit = { // done this way for access to protected methods. setArtifactPatterns(pattern) @@ -170,7 +177,9 @@ private[sbt] object ConvertResolver { resolver } case repo: SshRepository => { - val resolver = new SshResolver with DescriptorRequired + val resolver = new SshResolver with DescriptorRequired { + override def getResource(resource: Resource, dest: File): Long = get(resource, dest) + } initializeSSHResolver(resolver, repo, settings) repo.publishPermissions.foreach(perm => resolver.setPublishPermissions(perm)) resolver @@ -187,6 +196,7 @@ private[sbt] object ConvertResolver { // in local files for non-changing revisions. // This will be fully enforced in sbt 1.0. setRepository(new WarnOnOverwriteFileRepo()) + override def getResource(resource: Resource, dest: File): Long = get(resource, dest) } resolver.setName(repo.name) initializePatterns(resolver, repo.patterns, settings) @@ -196,7 +206,9 @@ private[sbt] object ConvertResolver { resolver } case repo: URLRepository => { - val resolver = new URLResolver with ChecksumFriendlyURLResolver with DescriptorRequired + val resolver = new URLResolver with ChecksumFriendlyURLResolver with DescriptorRequired { + override def getResource(resource: Resource, dest: File): Long = get(resource, dest) + } resolver.setName(repo.name) initializePatterns(resolver, repo.patterns, settings) resolver @@ -208,11 +220,59 @@ private[sbt] object ConvertResolver { } private sealed trait DescriptorRequired extends BasicResolver { + // Works around implementation restriction to access protected method `get` + def getResource(resource: Resource, dest: File): Long + + override def getAndCheck(resource: Resource, dest: File): Long = { + // Follows the same semantics that private method `check` as defined in ivy `BasicResolver` + def check(resource: Resource, destination: File, algorithm: String) = { + if (!ChecksumHelper.isKnownAlgorithm(algorithm)) { + throw new IllegalArgumentException(s"Unknown checksum algorithm: $algorithm") + } + val checksumResource = resource.clone(s"${resource.getName}.$algorithm") + if (checksumResource.exists) { + Message.debug(s"$algorithm file found for $resource: checking...") + val checksumFile = File.createTempFile("ivytmp", algorithm) + try { + getResource(checksumResource, checksumFile) + try { + ChecksumHelper.check(dest, checksumFile, algorithm) + Message.verbose(s"$algorithm OK for $resource") + true + } catch { + case e: IOException => + dest.delete() + throw e + } + } finally { + checksumFile.delete() + } + } else false + } + + val size = getResource(resource, dest) + val checksums = getChecksumAlgorithms + checksums.foldLeft(false) { (failed, checksum) => + // Continue checking until we hit a failure + if (failed) failed + else check(resource, dest, checksum) + } + size + } + var i = 0 override def getDependency(dd: DependencyDescriptor, data: ResolveData) = { + val moduleID = IvyRetrieve.toModuleID(dd.getDependencyRevisionId) + print(" " * i) + println(s"Downloading and checking for $moduleID") + i += 2 val prev = descriptorString(isAllownomd) setDescriptor(descriptorString(hasExplicitURL(dd))) - try super.getDependency(dd, data) + val t = try super.getDependency(dd, data) finally setDescriptor(prev) + i -= 2 + print(" " * i) + println(s"End $moduleID") + t } def descriptorString(optional: Boolean) = if (optional) BasicResolver.DESCRIPTOR_OPTIONAL else BasicResolver.DESCRIPTOR_REQUIRED diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ivyint/SbtChainResolver.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ivyint/SbtChainResolver.scala index a364b9d77..ad6245055 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ivyint/SbtChainResolver.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ivyint/SbtChainResolver.scala @@ -31,6 +31,7 @@ private[sbt] case class SbtChainResolver( updateOptions: UpdateOptions, log: Logger ) extends ChainResolver { + override def setCheckmodified(check: Boolean): Unit = super.setCheckmodified(check) override def equals(o: Any): Boolean = o match { case o: SbtChainResolver => diff --git a/librarymanagement/src/test/scala/BaseIvySpecification.scala b/librarymanagement/src/test/scala/BaseIvySpecification.scala index d87baa862..6068496d6 100644 --- a/librarymanagement/src/test/scala/BaseIvySpecification.scala +++ b/librarymanagement/src/test/scala/BaseIvySpecification.scala @@ -53,8 +53,8 @@ trait BaseIvySpecification extends UnitSpec { def mkIvyConfiguration(uo: UpdateOptions): IvyConfiguration = { val paths = IvyPaths(currentBase, Some(currentTarget)) val other = Vector.empty - val moduleConfs = Vector(ModuleConfiguration("*", chainResolver)) val check = Vector.empty + val moduleConfs = Vector(ModuleConfiguration("*", chainResolver)) val resCacheDir = currentTarget / "resolution-cache" new InlineIvyConfiguration(paths, resolvers, diff --git a/librarymanagement/src/test/scala/OfflineModeSpec.scala b/librarymanagement/src/test/scala/OfflineModeSpec.scala index 0b941f405..04d8cf836 100644 --- a/librarymanagement/src/test/scala/OfflineModeSpec.scala +++ b/librarymanagement/src/test/scala/OfflineModeSpec.scala @@ -44,7 +44,7 @@ class OfflineModeSpec extends BaseIvySpecification with DependencyBuilders { val offlineResolution = IvyActions.updateEither(toResolve, offlineConf, warningConf, noClock, targetDir, log) - assert(offlineResolution.isRight) + assert(offlineResolution.isRight, s"Offline resolution has failed with $offlineResolution.") val resolveTime = offlineResolution.right.get.stats.resolveTime // Only check the estimate for the non cached resolution, otherwise resolution is cached