Debugging
Testing Plans
Use Injector#assert
method to test whether the plan will execute correctly when passed to Injector#produce
.
import distage.{DIKey, Roots, ModuleDef, Injector}
class A(b: B)
class B
def badModule = new ModuleDef {
make[A]
make[B].fromEffect(zio.ZIO.attempt { ??? })
}
// the effect types are mismatched - `badModule` uses `zio.Task`, but we expect `cats.effect.IO`
Injector[cats.effect.IO]().assert(badModule, Roots.target[A])
// izumi.distage.model.exceptions.PlanVerificationException: Plan verification failed, issues were:
//
//
// - IncompatibleEffectType({type.repl.MdocSession.MdocApp0.B},{type.repl.MdocSession.MdocApp0.B} BindingOrigin((debugging.md:22)) := effect[λ %0 → zio.ZIO[-scala.Any,+java.lang.Throwable,+0]]{effect.{type.repl.MdocSession.MdocApp0.B}/zio.ZIO[-scala.Any,+java.lang.Throwable,+scala.Nothing]},λ %0 → cats.effect.IO[+0],λ %0 → zio.ZIO[-scala.Any,+java.lang.Throwable,+0])
//
// Visited keys:
//
//
// - {type.repl.MdocSession.MdocApp0.A}
//
// at izumi.distage.planning.solver.PlanVerifier$PlanVerifierResult.throwOnError(PlanVerifier.scala:308)
// at izumi.distage.model.Injector.assert(Injector.scala:270)
// at izumi.distage.model.Injector.assert$(Injector.scala:264)
// at izumi.distage.InjectorDefaultImpl.assert(InjectorDefaultImpl.scala:23)
// at repl.MdocSession$MdocApp0$$anonfun$3.apply$mcV$sp(debugging.md:30)
// at repl.MdocSession$MdocApp0$$anonfun$3.apply(debugging.md:30)
// at repl.MdocSession$MdocApp0$$anonfun$3.apply(debugging.md:30)
def goodModule = new ModuleDef {
make[A]
make[B].fromEffect(cats.effect.IO(new B))
}
// the effect types in `goodModule` and here match now
Injector[cats.effect.IO]().assert(goodModule, Roots.target[A])
Pretty-printing plans
You can print the output of plan.render()
to get detailed info on what will happen during instantiation. The printout includes source and line numbers so your IDE can show you where the binding was defined!
val plan = Injector().plan(goodModule, Roots.target[A]).getOrThrow()
// plan: izumi.distage.model.plan.Plan = 1: {effect.{type.MdocSession::MdocApp0::B}} BindingOrigin((debugging.md:41)) := value cats.effect.IO$Delay#-96351630
// 2: {type.MdocSession::MdocApp0::B} BindingOrigin((debugging.md:41)) := effect[λ %0 → cats.effect.IO[+0]]{effect.{type.MdocSession::MdocApp0::B}}
// 3: {type.MdocSession::MdocApp0::A} BindingOrigin((debugging.md:40)) :=
// 4: call(π:Constructor(MdocSession::MdocApp0::B): MdocSession::MdocApp0::A[32m) {
// 5: arg b: MdocSession::MdocApp0::B <- {type.MdocSession::MdocApp0::B}
// 6: }
println(plan.render())
// 1: {effect.{type.MdocSession::MdocApp0::B}} BindingOrigin((debugging.md:41)) := value cats.effect.IO$Delay#-96351630
// 2: {type.MdocSession::MdocApp0::B} BindingOrigin((debugging.md:41)) := effect[λ %0 → cats.effect.IO[+0]]{effect.{type.MdocSession::MdocApp0::B}}
// 3: {type.MdocSession::MdocApp0::A} BindingOrigin((debugging.md:40)) :=
// 4: call(π:Constructor(MdocSession::MdocApp0::B): MdocSession::MdocApp0::A[32m) {
// 5: arg b: MdocSession::MdocApp0::B <- {type.MdocSession::MdocApp0::B}
// 6: }
You can also query a plan to see the dependencies and reverse dependencies of a specific class and their order of instantiation:
// Print dependencies
println(plan.renderDeps(DIKey[A]))
// ➤ {type.MdocSession::MdocApp0::A} BindingOrigin((debugging.md:40))
// ⮑ 1: {type.MdocSession::MdocApp0::B} BindingOrigin((debugging.md:41))
// ⮑ 2: {effect.{type.MdocSession::MdocApp0::B}} BindingOrigin((debugging.md:41))
//
// Print reverse dependencies
println(plan.renderDependees(DIKey[B]))
// ➤ {type.MdocSession::MdocApp0::B} BindingOrigin((debugging.md:41))
// ↖ 1: {type.MdocSession::MdocApp0::A} BindingOrigin((debugging.md:40))
//
The printer highlights circular dependencies:
To debug macros used by distage
you may use the following Java Properties:
# izumi-reflect macros
-Dizumi.debug.macro.rtti=true
# izumi.distage.constructors.* macros
-Dizumi.debug.macro.distage.constructors=true
# Functoid macro
-Dizumi.debug.macro.distage.functoid=true
Graphviz rendering
Add GraphDumpBootstrapModule
to your Injector
’s configuration to enable writing GraphViz files with a graphical representation of distage.Plan
. Data will be saved to ./target/plan-last-full.gv
and ./target/plan-last-nogc.gv
in the current working directory.
import distage.{GraphDumpBootstrapModule, Injector}
Injector(GraphDumpBootstrapModule)
// res6: Injector[[A]izumi.fundamentals.platform.functional.package.Identity[A]] = izumi.distage.InjectorDefaultImpl@6b4186b2
You’ll need a GraphViz
installation to render these files into a viewable PNG images:
dot -Tpng target/plan-last-nogc.gv -o out.png
Command-line activation
You may activate GraphViz dump for a distage-framework
Role-based application by passing a --debug-dump-graph
option:
./launcher --debug-dump-graph :myrole
Testkit activation
You may activate GraphViz dump in distage-testkit
tests by setting PlanningOptions(addGraphVizDump = true)
in config
:
import izumi.distage.testkit.scalatest.Spec2
import izumi.distage.testkit.TestConfig
import izumi.distage.framework.config.PlanningOptions
final class MyTest extends Spec2[zio.IO] {
override def config: TestConfig = super.config.copy(
planningOptions = PlanningOptions(
addGraphVizDump = true,
)
)
}
Launcher activation
PlanningOptions are also modifiable in distage-framework
applications:
import distage.{Module, ModuleDef}
import izumi.distage.framework.config.PlanningOptions
import izumi.distage.roles.RoleAppMain
import zio.IO
abstract class MyRoleLauncher extends RoleAppMain.LauncherBIO[IO] {
override protected def roleAppBootOverrides(argv: RoleAppMain.ArgV): Module = new ModuleDef {
make[PlanningOptions].from(PlanningOptions(addGraphVizDump = true))
}
}