ResolutionProcess API

This commit is contained in:
Alexandre Archambault 2015-06-26 00:59:07 +01:00
parent 95f51fdb8f
commit 6a91ddfaca
9 changed files with 224 additions and 107 deletions

View File

@ -12,8 +12,11 @@ import scalaz.concurrent.Task
case class Coursier(scope: List[String],
keepOptional: Boolean,
fetch: Boolean,
verbose: List[Unit],
@ExtraName("N") maxIterations: Int = 100) extends App {
val verbose0 = verbose.length
val scopes0 =
if (scope.isEmpty) List(Scope.Compile, Scope.Runtime)
else scope.map(Parse.scope)
@ -24,44 +27,48 @@ case class Coursier(scope: List[String],
def fileRepr(f: File) = f.toString
val logger: MetadataFetchLogger with FilesLogger = new MetadataFetchLogger with FilesLogger {
def println(s: String) = Console.err.println(s)
val logger: Option[MetadataFetchLogger with FilesLogger] =
if (verbose0 <= 1) None
else Some(
new MetadataFetchLogger with FilesLogger {
def println(s: String) = Console.err.println(s)
def downloading(url: String) =
println(s"Downloading $url")
def downloaded(url: String, success: Boolean) =
println(
if (success) s"Downloaded $url"
else s"Failed to download $url"
)
def readingFromCache(f: File) = {
println(s"Reading ${fileRepr(f)} from cache")
}
def puttingInCache(f: File) =
println(s"Writing ${fileRepr(f)} in cache")
def downloading(url: String) =
println(s"Downloading $url")
def downloaded(url: String, success: Boolean) =
println(
if (success) s"Downloaded $url"
else s"Failed to download $url"
)
def readingFromCache(f: File) = {
println(s"Reading ${fileRepr(f)} from cache")
}
def puttingInCache(f: File) =
println(s"Writing ${fileRepr(f)} in cache")
def foundLocally(f: File) =
println(s"Found locally ${fileRepr(f)}")
def downloadingArtifact(url: String) =
println(s"Downloading $url")
def downloadedArtifact(url: String, success: Boolean) =
println(
if (success) s"Downloaded $url"
else s"Failed to download $url"
)
}
def foundLocally(f: File) =
println(s"Found locally ${fileRepr(f)}")
def downloadingArtifact(url: String) =
println(s"Downloading $url")
def downloadedArtifact(url: String, success: Boolean) =
println(
if (success) s"Downloaded $url"
else s"Failed to download $url"
)
}
)
val cachedMavenCentral = repository.mavenCentral.copy(
fetchMetadata = repository.mavenCentral.fetchMetadata.copy(
cache = Some(centralCacheDir),
logger = Some(logger)
logger = logger
)
)
val repositories = Seq[Repository](
cachedMavenCentral,
repository.ivy2Local.copy(
fetchMetadata = repository.ivy2Local.fetchMetadata.copy(
logger = Some(logger)
logger = logger
)
)
)
@ -71,7 +78,7 @@ case class Coursier(scope: List[String],
.partition(_.length == 3)
if (splitDependencies.isEmpty) {
Console.err.println("Usage: coursier dependencies...")
CaseApp.printUsage[Coursier]()
sys exit 1
}
@ -94,7 +101,21 @@ case class Coursier(scope: List[String],
filter = Some(dep => (keepOptional || !dep.optional) && scopes(dep.scope))
)
val res = startRes.last(fetchFrom(repositories), maxIterations).run
val fetchQuiet = fetchSeveralFrom(repositories)
val fetch0 =
if (verbose == 0) fetchQuiet
else {
modVers: Seq[(Module, String)] =>
val print = Task{
println(s"Getting ${modVers.length} project definition(s)")
}
print.flatMap(_ => fetchQuiet(modVers))
}
val res = ResolutionProcess(startRes)
.run(fetch0, maxIterations)
.run
if (!res.isDone) {
Console.err.println(s"Maximum number of iteration reached!")
@ -140,7 +161,7 @@ case class Coursier(scope: List[String],
cachedMavenCentral.fetchMetadata.root -> centralFilesCacheDir
),
() => ???,
Some(logger)
logger
)
val tasks = artifacts.map(files.file(_, cachePolicy).run)

View File

@ -6,7 +6,7 @@ import scalaz.concurrent.Task
package object core {
def resolution(dependencies: Set[Dependency],
fetch: ModuleVersion => EitherT[Task, List[String], (Repository, Project)],
fetch: ModuleVersion => EitherT[Task, Seq[String], (Repository, Project)],
filter: Option[Dependency => Boolean],
profileActivation: Option[(String, Activation, Map[String, String]) => Boolean]): Stream[Resolution] = {

View File

@ -24,10 +24,10 @@ object Resolution {
*/
def find(repositories: Seq[Repository],
module: Module,
version: String): EitherT[Task, List[String], (Repository, Project)] = {
version: String): EitherT[Task, Seq[String], (Repository, Project)] = {
val lookups = repositories.map(repo => repo -> repo.find(module, version).run)
val task = lookups.foldLeft(Task.now(-\/(Nil)): Task[List[String] \/ (Repository, Project)]) {
val task = lookups.foldLeft(Task.now(-\/(Nil)): Task[Seq[String] \/ (Repository, Project)]) {
case (acc, (repo, t)) =>
acc.flatMap {
case -\/(errors) =>
@ -36,7 +36,7 @@ object Resolution {
if (project.module == module) \/-((repo, project))
else -\/(s"Wrong module returned (expected: $module, got: ${project.module})")
)
.leftMap(error => error :: errors)
.leftMap(error => error +: errors)
)
case res @ \/-(_) =>
@ -456,16 +456,20 @@ case class Resolution(rootDependencies: Set[Dependency],
* If no module info is missing, the next state of the resolution, which can be immediately calculated.
* Else, the current resolution itself.
*/
def nextIfNoMissing: Resolution = {
@tailrec
final def nextIfNoMissing: Resolution = {
val missing = missingFromCache
if (missing.isEmpty) nextNoMissingUnsafe
else this
if (missing.isEmpty) {
val next0 = nextNoMissingUnsafe
if (next0 == this) this
else next0.nextIfNoMissing
} else this
}
/**
* Do a new iteration, fetching the missing modules along the way.
*/
def next(fetchModule: ModuleVersion => EitherT[Task, List[String], (Repository, Project)]): Task[Resolution] = {
def next(fetchModule: ModuleVersion => EitherT[Task, Seq[String], (Repository, Project)]): Task[Resolution] = {
val missing = missingFromCache
if (missing.isEmpty) Task.now(nextNoMissingUnsafe)
else fetch(missing.toList, fetchModule).map(_.nextIfNoMissing)
@ -575,7 +579,7 @@ case class Resolution(rootDependencies: Set[Dependency],
* Fetch `modules` with `fetchModules`, and add the resulting errors and projects to the cache.
*/
def fetch(modules: Seq[ModuleVersion],
fetchModule: ModuleVersion => EitherT[Task, List[String], (Repository, Project)]): Task[Resolution] = {
fetchModule: ModuleVersion => EitherT[Task, Seq[String], (Repository, Project)]): Task[Resolution] = {
val lookups = modules.map(dep => fetchModule(dep).run.map(dep -> _))
val gatheredLookups = Task.gatherUnordered(lookups, exceptionCancels = true)
@ -598,7 +602,7 @@ case class Resolution(rootDependencies: Set[Dependency],
}
}
def last(fetchModule: ModuleVersion => EitherT[Task, List[String], (Repository, Project)], maxIterations: Int = -1): Task[Resolution] = {
def last(fetchModule: ModuleVersion => EitherT[Task, Seq[String], (Repository, Project)], maxIterations: Int = -1): Task[Resolution] = {
if (maxIterations == 0 || isDone) Task.now(this)
else {
next(fetchModule)
@ -606,7 +610,7 @@ case class Resolution(rootDependencies: Set[Dependency],
}
}
def stream(fetchModule: ModuleVersion => EitherT[Task, List[String], (Repository, Project)], run: Task[Resolution] => Resolution): Stream[Resolution] = {
def stream(fetchModule: ModuleVersion => EitherT[Task, Seq[String], (Repository, Project)], run: Task[Resolution] => Resolution): Stream[Resolution] = {
this #:: {
if (isDone) Stream.empty
else run(next(fetchModule)).stream(fetchModule, run)

View File

@ -0,0 +1,88 @@
package coursier
package core
import scalaz._
import scala.annotation.tailrec
sealed trait ResolutionProcess {
def run[F[_]](fetch: ResolutionProcess.Fetch[F],
maxIterations: Int = -1)
(implicit F: Monad[F]): F[Resolution] = {
if (maxIterations == 0) F.point(current)
else
this match {
case Done(res) => F.point(res)
case missing0 @ Missing(missing, _, _) =>
F.bind(fetch(missing))(result => missing0.next(result).run(fetch, if (maxIterations > 0) maxIterations - 1 else maxIterations))
case cont @ Continue(_, _) => cont.nextNoCont.run(fetch)
}
}
def current: Resolution
}
case class Missing(missing: Seq[(Module, String)],
current: Resolution,
cont: Resolution => ResolutionProcess) extends ResolutionProcess {
def next(results: ResolutionProcess.FetchResult): ResolutionProcess = {
val errors = results.collect{case (modVer, -\/(errs)) => modVer -> errs }
val successes = results.collect{case (modVer, \/-(repoProj)) => modVer -> repoProj }
val depMgmtMissing0 = successes
.map{case (_, (_, proj)) => current.dependencyManagementMissing(proj) }
.fold(Set.empty)(_ ++ _)
val depMgmtMissing = depMgmtMissing0 -- results.map(_._1)
def cont0(res: Resolution) = {
val res0 =
successes.foldLeft(res){case (acc, (modVer, (repo, proj))) =>
acc.copy(projectCache = acc.projectCache + (
modVer -> (repo, acc.withDependencyManagement(proj))
))
}
Continue(res0, cont)
}
val current0 = current
.copy(errorCache = current.errorCache ++ errors)
if (depMgmtMissing.isEmpty) cont0(current0)
else Missing(depMgmtMissing.toSeq, current0, cont0)
}
}
case class Continue(current: Resolution,
cont: Resolution => ResolutionProcess) extends ResolutionProcess {
def next: ResolutionProcess = cont(current)
@tailrec final def nextNoCont: ResolutionProcess =
next match {
case nextCont: Continue => nextCont.nextNoCont
case other => other
}
}
case class Done(resolution: Resolution) extends ResolutionProcess {
def current: Resolution = resolution
}
object ResolutionProcess {
def apply(resolution: Resolution): ResolutionProcess = {
val resolution0 = resolution.nextIfNoMissing
if (resolution0.isDone) Done(resolution0)
else Missing(resolution0.missingFromCache.toSeq, resolution0, apply)
}
type FetchResult = Seq[((Module, String), Seq[String] \/ (Repository, Project))]
type Fetch[F[_]] = Seq[(Module, String)] => F[FetchResult]
}

View File

@ -1,4 +1,4 @@
import scalaz.EitherT
import scalaz.{ EitherT, \/ }
import scalaz.concurrent.Task
/**
@ -67,9 +67,15 @@ package object coursier {
type Repository = core.Repository
def fetchFrom(repositories: Seq[Repository]): ModuleVersion => EitherT[Task, List[String], (Repository, Project)] =
def fetchFrom(repositories: Seq[Repository]): ModuleVersion => EitherT[Task, Seq[String], (Repository, Project)] =
modVersion => core.Resolution.find(repositories, modVersion._1, modVersion._2)
def fetchSeveralFrom(repositories: Seq[Repository]): Seq[ModuleVersion] => Task[Seq[(ModuleVersion, Seq[String] \/ (Repository, Project))]] = {
val fetchOne = fetchFrom(repositories)
modVers =>
Task.gatherUnordered(modVers.map(modVer => fetchOne(modVer).run.map(modVer -> _)))
}
type Resolution = core.Resolution
object Resolution {
val empty = apply()
@ -84,7 +90,7 @@ package object coursier {
}
def resolve(dependencies: Set[Dependency],
fetch: ModuleVersion => EitherT[Task, List[String], (Repository, Project)],
fetch: ModuleVersion => EitherT[Task, Seq[String], (Repository, Project)],
maxIterations: Option[Int] = Some(200),
filter: Option[Dependency => Boolean] = None,
profileActivation: Option[(String, Profile.Activation, Map[String, String]) => Boolean] = None): Task[Resolution] = {
@ -110,4 +116,6 @@ package object coursier {
type MavenRepository[G <: core.FetchMetadata] = core.MavenRepository[G]
val MavenRepository: core.MavenRepository.type = core.MavenRepository
type ResolutionProcess = core.ResolutionProcess
val ResolutionProcess: core.ResolutionProcess.type = core.ResolutionProcess
}

View File

@ -12,6 +12,12 @@ object CentralTests extends TestSuite {
repository.mavenCentral
)
def resolve(deps: Set[Dependency], filter: Option[Dependency => Boolean] = None) = {
ResolutionProcess(Resolution(deps, filter = filter))
.run(fetchSeveralFrom(repositories))
.runF
}
def repr(dep: Dependency) =
s"${dep.module.organization}:${dep.module.name}:${dep.attributes.`type`}:${Some(dep.attributes.classifier).filter(_.nonEmpty).map(_+":").mkString}${dep.version}"
@ -20,7 +26,7 @@ object CentralTests extends TestSuite {
val expected = await(textResource(s"resolutions/${module.organization}:${module.name}:$version")).split('\n').toSeq
val dep = Dependency(module, version)
val res = await(resolve(Set(dep), fetchFrom(repositories)).runF)
val res = await(resolve(Set(dep)))
val result = res.dependencies.toVector.map(repr).sorted.distinct
@ -34,7 +40,7 @@ object CentralTests extends TestSuite {
'logback{
async {
val dep = Dependency(Module("ch.qos.logback", "logback-classic"), "1.1.3")
val res = await(resolve(Set(dep), fetchFrom(repositories)).runF)
val res = await(resolve(Set(dep)))
.copy(projectCache = Map.empty, errorCache = Map.empty) // No validating these here
val expected = Resolution(
@ -50,7 +56,7 @@ object CentralTests extends TestSuite {
'asm{
async {
val dep = Dependency(Module("org.ow2.asm", "asm-commons"), "5.0.2")
val res = await(resolve(Set(dep), fetchFrom(repositories)).runF)
val res = await(resolve(Set(dep)))
.copy(projectCache = Map.empty, errorCache = Map.empty) // No validating these here
val expected = Resolution(
@ -66,7 +72,7 @@ object CentralTests extends TestSuite {
'jodaVersionInterval{
async {
val dep = Dependency(Module("joda-time", "joda-time"), "[2.2,2.8]")
val res0 = await(resolve(Set(dep), fetchFrom(repositories)).runF)
val res0 = await(resolve(Set(dep)))
val res = res0.copy(projectCache = Map.empty, errorCache = Map.empty)
val expected = Resolution(

View File

@ -8,6 +8,12 @@ import coursier.test.compatibility._
object ResolutionTests extends TestSuite {
def resolve0(deps: Set[Dependency], filter: Option[Dependency => Boolean] = None) = {
ResolutionProcess(Resolution(deps, filter = filter))
.run(fetchSeveralFrom(repositories))
.runF
}
implicit class ProjectOps(val p: Project) extends AnyVal {
def kv: (ModuleVersion, (Repository, Project)) = p.moduleVersion -> (testRepository, p)
}
@ -149,10 +155,9 @@ object ResolutionTests extends TestSuite {
val tests = TestSuite {
'empty{
async{
val res = await(resolve(
Set.empty,
fetchFrom(repositories)
).runF)
val res = await(resolve0(
Set.empty
))
assert(res == Resolution.empty)
}
@ -160,10 +165,9 @@ object ResolutionTests extends TestSuite {
'notFound{
async {
val dep = Dependency(Module("acme", "playy"), "2.4.0")
val res = await(resolve(
Set(dep),
fetchFrom(repositories)
).runF)
val res = await(resolve0(
Set(dep)
))
val expected = Resolution(
rootDependencies = Set(dep),
@ -177,10 +181,9 @@ object ResolutionTests extends TestSuite {
'single{
async {
val dep = Dependency(Module("acme", "config"), "1.3.0")
val res = await(resolve(
Set(dep),
fetchFrom(repositories)
).runF)
val res = await(resolve0(
Set(dep)
))
val expected = Resolution(
rootDependencies = Set(dep),
@ -195,10 +198,9 @@ object ResolutionTests extends TestSuite {
async {
val dep = Dependency(Module("acme", "play"), "2.4.0")
val trDep = Dependency(Module("acme", "play-json"), "2.4.0")
val res = await(resolve(
Set(dep),
fetchFrom(repositories)
).runF)
val res = await(resolve0(
Set(dep)
))
val expected = Resolution(
rootDependencies = Set(dep),
@ -219,10 +221,9 @@ object ResolutionTests extends TestSuite {
Dependency(Module("acme", "play-json"), "2.4.0"),
Dependency(Module("acme", "config"), "1.3.0")
)
val res = await(resolve(
Set(dep),
fetchFrom(repositories)
).runF)
val res = await(resolve0(
Set(dep)
))
val expected = Resolution(
rootDependencies = Set(dep),
@ -244,10 +245,9 @@ object ResolutionTests extends TestSuite {
Dependency(Module("acme", "play-json"), "2.4.0",
exclusions = Set(("acme", "config")))
)
val res = await(resolve(
Set(dep),
fetchFrom(repositories)
).runF)
val res = await(resolve0(
Set(dep)
))
val expected = Resolution(
rootDependencies = Set(dep),
@ -269,10 +269,9 @@ object ResolutionTests extends TestSuite {
Dependency(Module("acme", "play-json"), "2.4.0",
exclusions = Set(("*", "config")))
)
val res = await(resolve(
Set(dep),
fetchFrom(repositories)
).runF)
val res = await(resolve0(
Set(dep)
))
val expected = Resolution(
rootDependencies = Set(dep),
@ -288,11 +287,10 @@ object ResolutionTests extends TestSuite {
'filter{
async {
val dep = Dependency(Module("hudsucker", "mail"), "10.0")
val res = await(resolve(
val res = await(resolve0(
Set(dep),
fetchFrom(repositories),
filter = Some(_.scope == Scope.Compile)
).runF).copy(filter = None)
)).copy(filter = None)
val expected = Resolution(
rootDependencies = Set(dep),
@ -312,11 +310,10 @@ object ResolutionTests extends TestSuite {
Dependency(Module("acme", "play"), "2.4.0",
exclusions = Set(("acme", "play-json")))
)
val res = await(resolve(
val res = await(resolve0(
Set(dep),
fetchFrom(repositories),
filter = Some(_.scope == Scope.Compile)
).runF).copy(filter = None, projectCache = Map.empty)
)).copy(filter = None, projectCache = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep),
@ -332,11 +329,10 @@ object ResolutionTests extends TestSuite {
val trDeps = Seq(
Dependency(Module("org.gnu", "glib"), "13.4"),
Dependency(Module("org.gnome", "desktop"), "7.0"))
val res = await(resolve(
val res = await(resolve0(
Set(dep),
fetchFrom(repositories),
filter = Some(_.scope == Scope.Compile)
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
)).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep),
@ -351,11 +347,10 @@ object ResolutionTests extends TestSuite {
val dep = Dependency(Module("com.mailapp", "mail-client"), "2.1")
val trDeps = Seq(
Dependency(Module("gov.nsa", "secure-pgp"), "10.0", exclusions = Set(("*", "crypto"))))
val res = await(resolve(
val res = await(resolve0(
Set(dep),
fetchFrom(repositories),
filter = Some(_.scope == Scope.Compile)
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
)).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep),
@ -368,11 +363,10 @@ object ResolutionTests extends TestSuite {
'depMgmtInParentDeps{
async {
val dep = Dependency(Module("com.thoughtworks.paranamer", "paranamer"), "2.6")
val res = await(resolve(
val res = await(resolve0(
Set(dep),
fetchFrom(repositories),
filter = Some(_.scope == Scope.Compile)
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
)).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep),
@ -387,11 +381,10 @@ object ResolutionTests extends TestSuite {
val dep = Dependency(Module("com.github.dummy", "libb"), "0.3.3")
val trDeps = Seq(
Dependency(Module("org.escalier", "librairie-standard"), "2.11.6"))
val res = await(resolve(
val res = await(resolve0(
Set(dep),
fetchFrom(repositories),
filter = Some(_.scope == Scope.Compile)
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
)).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep),
@ -408,11 +401,10 @@ object ResolutionTests extends TestSuite {
val dep = Dependency(Module("com.github.dummy", "libb"), version)
val trDeps = Seq(
Dependency(Module("org.escalier", "librairie-standard"), "2.11.6"))
val res = await(resolve(
val res = await(resolve0(
Set(dep),
fetchFrom(repositories),
filter = Some(_.scope == Scope.Compile)
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
)).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep),
@ -431,11 +423,10 @@ object ResolutionTests extends TestSuite {
val dep = Dependency(Module("com.github.dummy", "libb"), "0.4.2")
val trDeps = Seq(
Dependency(Module("org.escalier", "librairie-standard"), "2.11.6"))
val res = await(resolve(
val res = await(resolve0(
Set(dep),
fetchFrom(repositories),
filter = Some(_.scope == Scope.Compile)
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
)).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep),
@ -453,11 +444,10 @@ object ResolutionTests extends TestSuite {
Dependency(Module("an-org", "a-lib"), "1.0", exclusions = Set(("an-org", "a-name"))),
Dependency(Module("an-org", "another-lib"), "1.0", optional = true),
Dependency(Module("an-org", "a-name"), "1.0", optional = true))
val res = await(resolve(
val res = await(resolve0(
Set(dep),
fetchFrom(repositories),
filter = Some(_.scope == Scope.Compile)
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
)).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
val expected = Resolution(
rootDependencies = Set(dep),
@ -477,11 +467,10 @@ object ResolutionTests extends TestSuite {
Dependency(Module("an-org", "a-lib"), "1.0", exclusions = Set(("an-org", "a-name"))),
Dependency(Module("an-org", "another-lib"), "1.0", optional = true),
Dependency(Module("an-org", "a-name"), "1.0", optional = true))
val res = await(resolve(
val res = await(resolve0(
deps,
fetchFrom(repositories),
filter = Some(_.scope == Scope.Compile)
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
)).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
val expected = Resolution(
rootDependencies = deps,

View File

@ -10,7 +10,7 @@ package object test {
}
def resolve(dependencies: Set[Dependency],
fetch: ModuleVersion => EitherT[Task, List[String], (Repository, Project)],
fetch: ModuleVersion => EitherT[Task, Seq[String], (Repository, Project)],
maxIterations: Option[Int] = Some(200),
filter: Option[Dependency => Boolean] = None,
profileActivation: Option[(String, Profile.Activation, Map[String, String]) => Boolean] = None): Task[Resolution] = {

View File

@ -127,7 +127,8 @@ class Backend($: BackendScope[Unit, State]) {
filter = Some(dep => (s.options.followOptional || !dep.optional) && (s.options.keepTest || dep.scope != Scope.Test))
)
res.last(fetchFrom(s.repositories.map(r => r.copy(fetchMetadata = r.fetchMetadata.copy(logger = Some(logger))))), 100)
ResolutionProcess(res)
.run(fetchSeveralFrom(s.repositories.map(r => r.copy(fetchMetadata = r.fetchMetadata.copy(logger = Some(logger))))), 100)
}
// For reasons that are unclear to me, not delaying this when using the runNow execution context