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#-367120931
// 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#-367120931
// 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@55b4fd57
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))
}
}