diff --git a/build.sbt b/build.sbt index aed7152ab..2e07ce2ce 100644 --- a/build.sbt +++ b/build.sbt @@ -19,6 +19,7 @@ def commonSettings: Seq[Setting[_]] = Seq( mimaPreviousArtifacts := Set(), // Some(organization.value %% moduleName.value % "1.0.0"), publishArtifact in Compile := true, publishArtifact in Test := false, + parallelExecution in Test := false, commands += Command.command("scalafmtCheck") { state => sys.process.Process("git diff --name-only --exit-code").! match { case 0 => // ok diff --git a/librarymanagement/src/main/contraband/librarymanagement.json b/librarymanagement/src/main/contraband/librarymanagement.json index c93091568..810ecc1ac 100644 --- a/librarymanagement/src/main/contraband/librarymanagement.json +++ b/librarymanagement/src/main/contraband/librarymanagement.json @@ -634,7 +634,8 @@ { "name": "retrieve", "type": "sbt.internal.librarymanagement.RetrieveConfiguration?" }, { "name": "missingOk", "type": "boolean" }, { "name": "logging", "type": "sbt.librarymanagement.UpdateLogging" }, - { "name": "artifactFilter", "type": "sbt.librarymanagement.ArtifactTypeFilter" } + { "name": "artifactFilter", "type": "sbt.librarymanagement.ArtifactTypeFilter" }, + { "name": "offline", "type": "boolean" } ] }, { @@ -728,7 +729,6 @@ { "name": "resolvers", "type": "sbt.librarymanagement.Resolver*" }, { "name": "otherResolvers", "type": "sbt.librarymanagement.Resolver*" }, { "name": "moduleConfigurations", "type": "sbt.librarymanagement.ModuleConfiguration*" }, - { "name": "localOnly", "type": "boolean" }, { "name": "checksums", "type": "String*" }, { "name": "resolutionCacheDir", "type": "java.io.File?" } ], @@ -738,7 +738,6 @@ " resolvers: Vector[sbt.librarymanagement.Resolver],", " otherResolvers: Vector[sbt.librarymanagement.Resolver],", " moduleConfigurations: Vector[sbt.librarymanagement.ModuleConfiguration],", - " localOnly: Boolean,", " lock: Option[xsbti.GlobalLock],", " checksums: Vector[String],", " resolutionCacheDir: Option[java.io.File],", @@ -746,7 +745,7 @@ " log: xsbti.Logger", ") =", " this(lock, paths.baseDirectory, log, updateOptions, paths, resolvers, otherResolvers,", - " moduleConfigurations, localOnly, checksums, resolutionCacheDir)" + " moduleConfigurations, checksums, resolutionCacheDir)" ] }, { diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ConvertResolver.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ConvertResolver.scala index c90ece754..82760272c 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ConvertResolver.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ConvertResolver.scala @@ -202,7 +202,7 @@ private[sbt] object ConvertResolver { resolver } case repo: ChainedResolver => - IvySbt.resolverChain(repo.name, repo.resolvers, false, settings, log) + IvySbt.resolverChain(repo.name, repo.resolvers, settings, log) case repo: RawRepository => repo.resolver } } diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/Ivy.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/Ivy.scala index 725f98e92..a3b250b78 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/Ivy.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/Ivy.scala @@ -88,16 +88,10 @@ final class IvySbt(val configuration: IvyConfiguration) { self => case i: InlineIvyConfiguration => is.setVariable("ivy.checksums", i.checksums mkString ",") i.paths.ivyHome foreach is.setDefaultIvyUserDir - IvySbt.configureCache(is, i.localOnly, i.resolutionCacheDir) - IvySbt.setResolvers( - is, - i.resolvers, - i.otherResolvers, - i.localOnly, - configuration.updateOptions, - configuration.log - ) - IvySbt.setModuleConfigurations(is, i.moduleConfigurations, configuration.log) + val log = configuration.log + IvySbt.configureCache(is, i.resolutionCacheDir) + IvySbt.setResolvers(is, i.resolvers, i.otherResolvers, configuration.updateOptions, log) + IvySbt.setModuleConfigurations(is, i.moduleConfigurations, log) } is } @@ -342,13 +336,12 @@ private[sbt] object IvySbt { settings: IvySettings, resolvers: Seq[Resolver], other: Seq[Resolver], - localOnly: Boolean, updateOptions: UpdateOptions, log: Logger ): Unit = { def makeChain(label: String, name: String, rs: Seq[Resolver]) = { log.debug(label + " repositories:") - val chain = resolverChain(name, rs, localOnly, settings, updateOptions, log) + val chain = resolverChain(name, rs, settings, updateOptions, log) settings.addResolver(chain) chain } @@ -362,18 +355,17 @@ private[sbt] object IvySbt { module.revision endsWith "-SNAPSHOT" private[sbt] def isChanging(mrid: ModuleRevisionId): Boolean = mrid.getRevision endsWith "-SNAPSHOT" + def resolverChain( name: String, resolvers: Seq[Resolver], - localOnly: Boolean, settings: IvySettings, log: Logger - ): DependencyResolver = - resolverChain(name, resolvers, localOnly, settings, UpdateOptions(), log) + ): DependencyResolver = resolverChain(name, resolvers, settings, UpdateOptions(), log) + def resolverChain( name: String, resolvers: Seq[Resolver], - localOnly: Boolean, settings: IvySettings, updateOptions: UpdateOptions, log: Logger @@ -446,19 +438,12 @@ private[sbt] object IvySbt { ) } } - private def configureCache( - settings: IvySettings, - localOnly: Boolean, - resCacheDir: Option[File] - ): Unit = { - configureResolutionCache(settings, localOnly, resCacheDir) - configureRepositoryCache(settings, localOnly) + + private def configureCache(settings: IvySettings, resCacheDir: Option[File]): Unit = { + configureResolutionCache(settings, resCacheDir) + configureRepositoryCache(settings) } - private[this] def configureResolutionCache( - settings: IvySettings, - localOnly: Boolean, - resCacheDir: Option[File] - ): Unit = { + private[this] def configureResolutionCache(settings: IvySettings, resCacheDir: Option[File]) = { val base = resCacheDir getOrElse settings.getDefaultResolutionCacheBasedir settings.setResolutionCacheManager(new ResolutionCache(base, settings)) } @@ -485,7 +470,7 @@ private[sbt] object IvySbt { ) } - private[this] def configureRepositoryCache(settings: IvySettings, localOnly: Boolean): Unit = { + private[this] def configureRepositoryCache(settings: IvySettings): Unit = { val cacheDir = settings.getDefaultRepositoryCacheBasedir() val manager = new DefaultRepositoryCacheManager("default-cache", settings, cacheDir) { override def findModuleInCache( @@ -529,12 +514,8 @@ private[sbt] object IvySbt { manager.setDataFilePattern(PluginPattern + manager.getDataFilePattern) manager.setIvyPattern(PluginPattern + manager.getIvyPattern) manager.setUseOrigin(true) - if (localOnly) - manager.setDefaultTTL(java.lang.Long.MAX_VALUE) - else { - manager.setChangingMatcher(PatternMatcher.REGEXP) - manager.setChangingPattern(".*-SNAPSHOT") - } + manager.setChangingMatcher(PatternMatcher.REGEXP) + manager.setChangingPattern(".*-SNAPSHOT") settings.addRepositoryCacheManager(manager) settings.setDefaultRepositoryCacheManager(manager) } diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala index 0c7cef4a6..4d8465a10 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala @@ -467,6 +467,7 @@ object IvyActions { val resolveId = ResolveOptions.getDefaultResolveId(moduleDescriptor) resolveOptions.setResolveId(resolveId) resolveOptions.setArtifactFilter(updateConfiguration.artifactFilter) + resolveOptions.setUseCacheOnly(updateConfiguration.offline) resolveOptions.setLog(ivyLogLevel(logging)) ResolutionCache.cleanModule( moduleDescriptor.getModuleRevisionId, @@ -519,6 +520,7 @@ object IvyActions { val resolveId = ResolveOptions.getDefaultResolveId(descriptor) resolveOptions.setResolveId(resolveId) resolveOptions.setArtifactFilter(updateConfiguration.artifactFilter) + resolveOptions.setUseCacheOnly(updateConfiguration.offline) resolveOptions.setLog(ivyLogLevel(updateConfiguration.logging)) val acceptError = updateConfiguration.missingOk resolver.customResolve(descriptor, acceptError, logicalClock, resolveOptions, cache, log) diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyCache.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyCache.scala index c697b639f..bef341acc 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyCache.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/IvyCache.scala @@ -108,7 +108,6 @@ class IvyCache(val ivyHome: Option[File]) { Vector(local), Vector.empty, Vector.empty, - false, lock, IvySbt.DefaultChecksums, None, diff --git a/librarymanagement/src/main/scala/sbt/librarymanagement/DefaultLibraryManagement.scala b/librarymanagement/src/main/scala/sbt/librarymanagement/DefaultLibraryManagement.scala index 43bf8b231..3c5d38167 100644 --- a/librarymanagement/src/main/scala/sbt/librarymanagement/DefaultLibraryManagement.scala +++ b/librarymanagement/src/main/scala/sbt/librarymanagement/DefaultLibraryManagement.scala @@ -68,7 +68,8 @@ class DefaultLibraryManagement(ivyConfiguration: IvyConfiguration, log: Logger) Some(retrieveConfiguration), true, UpdateLogging.DownloadOnly, - artifactFilter + artifactFilter, + false ) log.debug(s"Attempting to fetch ${dependenciesNames(module)}. This operation may fail.") diff --git a/librarymanagement/src/test/scala/BaseIvySpecification.scala b/librarymanagement/src/test/scala/BaseIvySpecification.scala index 50a7f1f95..20041ab04 100644 --- a/librarymanagement/src/test/scala/BaseIvySpecification.scala +++ b/librarymanagement/src/test/scala/BaseIvySpecification.scala @@ -54,14 +54,12 @@ trait BaseIvySpecification extends UnitSpec { val paths = IvyPaths(currentBase, Some(currentTarget)) val other = Vector.empty val moduleConfs = Vector(ModuleConfiguration("*", chainResolver)) - val off = false val check = Vector.empty val resCacheDir = currentTarget / "resolution-cache" new InlineIvyConfiguration(paths, resolvers, other, moduleConfs, - off, None, check, Some(resCacheDir), @@ -69,18 +67,19 @@ trait BaseIvySpecification extends UnitSpec { log) } - def makeUpdateConfiguration: UpdateConfiguration = { + def makeUpdateConfiguration(offline: Boolean): UpdateConfiguration = { val retrieveConfig = RetrieveConfiguration(currentManaged, Resolver.defaultRetrievePattern).withSync(false) UpdateConfiguration(Some(retrieveConfig), false, UpdateLogging.Full, - ArtifactTypeFilter.forbid(Set("src", "doc"))) + ArtifactTypeFilter.forbid(Set("src", "doc")), + offline) } def ivyUpdateEither(module: IvySbt#Module): Either[UnresolvedWarning, UpdateReport] = { // IO.delete(currentTarget) - val config = makeUpdateConfiguration + val config = makeUpdateConfiguration(false) IvyActions.updateEither(module, config, UnresolvedWarningConfiguration(), diff --git a/librarymanagement/src/test/scala/CustomPomParserTest.scala b/librarymanagement/src/test/scala/CustomPomParserTest.scala index ea0113596..950562638 100644 --- a/librarymanagement/src/test/scala/CustomPomParserTest.scala +++ b/librarymanagement/src/test/scala/CustomPomParserTest.scala @@ -19,7 +19,6 @@ class CustomPomParserTest extends UnitSpec { Vector(local), Vector.empty, Vector.empty, - false, None, Vector("sha1", "md5"), None, diff --git a/librarymanagement/src/test/scala/OfflineModeSpec.scala b/librarymanagement/src/test/scala/OfflineModeSpec.scala new file mode 100644 index 000000000..0b941f405 --- /dev/null +++ b/librarymanagement/src/test/scala/OfflineModeSpec.scala @@ -0,0 +1,79 @@ +package sbt.librarymanagement + +import org.scalatest.Assertion +import sbt.internal.librarymanagement._ +import sbt.internal.librarymanagement.impl.DependencyBuilders +import sbt.io.IO + +class OfflineModeSpec extends BaseIvySpecification with DependencyBuilders { + private final def targetDir = Some(currentDependency) + private final def onlineConf = makeUpdateConfiguration(false) + private final def offlineConf = makeUpdateConfiguration(true) + private final def warningConf = UnresolvedWarningConfiguration() + private final def normalOptions = UpdateOptions() + private final def cachedOptions = UpdateOptions().withCachedResolution(true) + private final def noClock = LogicalClock.unknown + + def avro177 = ModuleID("org.apache.avro", "avro", "1.7.7") + def dataAvro1940 = ModuleID("com.linkedin.pegasus", "data-avro", "1.9.40") + def netty320 = ModuleID("org.jboss.netty", "netty", "3.2.0.Final") + final def dependencies: Vector[ModuleID] = + Vector(avro177, dataAvro1940, netty320).map(_.withConfigurations(Some("compile"))) + + def cleanAll(): Unit = { + cleanIvyCache() + IO.delete(currentTarget) + IO.delete(currentManaged) + IO.delete(currentDependency) + } + + def checkOnlineAndOfflineResolution(updateOptions: UpdateOptions): Assertion = { + cleanAll() + val toResolve = module(defaultModuleId, dependencies, None, updateOptions) + if (updateOptions.cachedResolution) + cleanCachedResolutionCache(toResolve) + + val onlineResolution = + IvyActions.updateEither(toResolve, onlineConf, warningConf, noClock, targetDir, log) + assert(onlineResolution.isRight) + assert(onlineResolution.right.exists(report => report.stats.resolveTime > 0)) + + // Compute an estimate to ensure that the second resolution does indeed use the cache + val originalResolveTime = onlineResolution.right.get.stats.resolveTime + val estimatedCachedTime = originalResolveTime * 0.15 + + val offlineResolution = + IvyActions.updateEither(toResolve, offlineConf, warningConf, noClock, targetDir, log) + assert(offlineResolution.isRight) + + val resolveTime = offlineResolution.right.get.stats.resolveTime + // Only check the estimate for the non cached resolution, otherwise resolution is cached + assert(resolveTime <= estimatedCachedTime, + "Offline resolution took more than 15% of normal resolution's running time.") + } + + "Offline update configuration" should "reuse the caches when offline is enabled" in { + checkOnlineAndOfflineResolution(normalOptions) + } + + it should "reuse the caches when offline and cached resolution are enabled" in { + checkOnlineAndOfflineResolution(cachedOptions) + } + + def checkFailingResolution(updateOptions: UpdateOptions): Assertion = { + cleanAll() + val toResolve = module(defaultModuleId, dependencies, None, updateOptions) + if (updateOptions.cachedResolution) cleanCachedResolutionCache(toResolve) + val failedOfflineResolution = + IvyActions.updateEither(toResolve, offlineConf, warningConf, noClock, targetDir, log) + assert(failedOfflineResolution.isLeft) + } + + it should "fail when artifacts are missing in the cache" in { + checkFailingResolution(normalOptions) + } + + it should "fail when artifacts are missing in the cache for cached resolution" in { + checkFailingResolution(cachedOptions) + } +} diff --git a/librarymanagement/src/test/scala/sbt/internal/librarymanagement/IvyRepoSpec.scala b/librarymanagement/src/test/scala/sbt/internal/librarymanagement/IvyRepoSpec.scala index 61c9aa27c..51be80d06 100644 --- a/librarymanagement/src/test/scala/sbt/internal/librarymanagement/IvyRepoSpec.scala +++ b/librarymanagement/src/test/scala/sbt/internal/librarymanagement/IvyRepoSpec.scala @@ -44,7 +44,7 @@ class IvyRepoSpec extends BaseIvySpecification with DependencyBuilders { val m = makeModuleForDepWithSources // the "default" configuration used in updateEither. - val c = makeUpdateConfiguration + val c = makeUpdateConfiguration(false) val ivyScala = m.moduleSettings.ivyScala val srcTypes = Set("src") diff --git a/project/Dependencies.scala b/project/Dependencies.scala index b8a07cd13..ef67b2344 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -51,7 +51,7 @@ object Dependencies { def addSbtUtilCache(p: Project): Project = addSbtModule(p, sbtUtilPath, "utilCache", utilCache) val launcherInterface = "org.scala-sbt" % "launcher-interface" % "1.0.0" - val ivy = "org.scala-sbt.ivy" % "ivy" % "2.3.0-sbt-48dd0744422128446aee9ac31aa356ee203cc9f4" + val ivy = "org.scala-sbt.ivy" % "ivy" % "2.3.0-sbt-a3314352b638afbf0dca19f127e8263ed6f898bd" val jsch = "com.jcraft" % "jsch" % "0.1.46" intransitive () val scalaReflect = Def.setting { "org.scala-lang" % "scala-reflect" % scalaVersion.value } val scalaXml = scala211Module("scala-xml", "1.0.5")