mirror of https://github.com/sbt/sbt.git
Add offline mode to `UpdateConfiguration`
The following commit tries to address the well-known issue that sbt cannot be used in offline mode. In order to enable that use case, this commit adds support for a flag in update configuration called `offline` that users can change as they wish (and that sbt will expose via settings). It adds tests to check that the resolution uses the caches instead of trying to resolve from the Internet. Unfortunately, ivy does not expose a way to know whether a resolution was made from the cache or the Internet, so the test case invents a metric to check that resolution indeed happens from cache. In order to benefit from this 100%, we need to update to ivy 2.4.0 or cherry-pick a commit because a major issue in `useCacheOnly` has been fixed: https://issues.apache.org/jira/browse/IVY-1515. In short, this is good for the dependency lock file too. Since we can make sure that once we have downloaded and resolved all the dependencies locally, we do resolve from the cache.
This commit is contained in:
parent
1a11fd86a3
commit
67d9012a17
|
|
@ -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" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
|
|
@ -69,18 +69,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(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
package sbt.librarymanagement
|
||||
|
||||
import org.scalatest.Assertion
|
||||
import sbt.internal.librarymanagement._
|
||||
import sbt.internal.librarymanagement.impl.DependencyBuilders
|
||||
|
||||
class OfflineModeSpec extends BaseIvySpecification with DependencyBuilders {
|
||||
private final val targetDir = Some(currentDependency)
|
||||
private final val onlineConf = makeUpdateConfiguration(false)
|
||||
private final val offlineConf = makeUpdateConfiguration(true)
|
||||
private final val noClock = LogicalClock.unknown
|
||||
private final val warningConf = UnresolvedWarningConfiguration()
|
||||
private final val normalOptions = UpdateOptions()
|
||||
private final val cachedOptions = UpdateOptions().withCachedResolution(true)
|
||||
|
||||
final val scalaCompiler = Vector("org.scala-lang" % "scala-compiler" % "2.12.2" % "compile")
|
||||
|
||||
def checkOnlineAndOfflineResolution(updateOptions: UpdateOptions): Assertion = {
|
||||
cleanIvyCache()
|
||||
|
||||
val toResolve = module(defaultModuleId, scalaCompiler, None, updateOptions)
|
||||
val isCachedResolution = updateOptions.cachedResolution
|
||||
if (isCachedResolution) 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 resolutionTime: Long = onlineResolution.right.map(_.stats.resolveTime).getOrElse(0L)
|
||||
val estimatedCachedTime = resolutionTime * 0.15
|
||||
|
||||
val offlineResolution =
|
||||
IvyActions.updateEither(toResolve, offlineConf, warningConf, noClock, targetDir, log)
|
||||
assert(offlineResolution.isRight)
|
||||
|
||||
if (!isCachedResolution) {
|
||||
// Only check the estimate for the non cached resolution, otherwise resolution is cached
|
||||
assert(offlineResolution.right.exists(_.stats.resolveTime <= estimatedCachedTime),
|
||||
"Offline resolution took more than 15% of normal resolution's running time.")
|
||||
} else assert(true) // We cannot check offline resolution if it's cached.
|
||||
}
|
||||
|
||||
"Offline update configuration" should "reuse the caches when it's enabled" in {
|
||||
checkOnlineAndOfflineResolution(normalOptions)
|
||||
}
|
||||
|
||||
it should "work with cached resolution" in {
|
||||
checkOnlineAndOfflineResolution(cachedOptions)
|
||||
}
|
||||
|
||||
def checkFailingResolution(updateOptions: UpdateOptions): Assertion = {
|
||||
cleanIvyCache()
|
||||
val toResolve = module(defaultModuleId, scalaCompiler, 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Reference in New Issue