# Design This chapter explains some of CBT's less obvious design decisions and the reasons behind them. Hopefully this answers some of the questions often unanswered when trying to understand a new tool. ## Why does CBT use inheritance? First of all CBT uses classes, because needs some place to put tasks and name them. Methods in classes are one way to do it. Methods calling each other allows to effectively build a graph of dependent tasks. Inheritance on top of that allows patching this graph to insert additional steps, e.g. formatting the code before compiling it. Patching of the task graph is what you do with sbt andso with CBT via inheritance/overrides. This was also discussed in gitter here: https://gitter.im/cvogt/cbt?at=58a95663de50490822e869e5 Taking a graph an contrinuously patching it can be confusing, which is why inheritance is confusing. You can even build non-terminating call cycles. CBT's logic is not coupled to the inheritance layer. You can write CBT builds without inheritance. They require a bit more code, but may end up easier to maintain and understand. ## Task composition and aborting failed tasks and dependents In CBT build tasks are methods. Task can depend on each other by invoking each other. E.g. `package` is a method that invokes `compile`. Build tasks can fail. By convention, if a task fails it is expected to throw an Exception in order to also abort the execution of dependent tasks. When `compile` finds compile errors it throws an exception and thereby also drops out of the `package` execution pth. CBT catches and handles the exception and returns control back to the user. This design was chosen for simplicity and because build code lives in a world with exceptions anyways as a lot of it is directly or indirectly using java libraries. We might reconsider this design at some point. Or not. The hope is that Exceptions are without major drawbacks and more approachable to newcomers than monadic error handling, which would require wrapper types and value composition via .map and for-expressions. Not using a Monad however also means that CBT cannot reason about task composition automatically, which means parallel task execution is not automatic, but manual and opt-in where wanted. ## Why do CBT plugins use case classes where methods seem simpler? In CBT plugins you may see ``` trait SomePlugin{ def doSomething = SomePluginLib(...context).doSomething( a, b, ... ) } case class SomePluginLib(...context...)(implicit ...more context...){ case class doSomething(a: A, b: B, ...){ def apply = { // logic } } } ``` Why this? This seems much simpler: ``` trait SomePlugin{ def doSomething = SomePluginLib.doSomething( a, b, ... )(...context) } object SomePluginLib def doSomething(a: A, b: B, ...)(...context...)(implicit ...more context...) = { // logic } } ``` The reason is that the former allows easy patching while the second does not. Let's pretend your build is this: ``` class Build(val context: Context) extends SomePlugin{ override def doSomething = super.doSomething.copy( b = someOtherB ) } ``` Such a simple replacement of `b` while keeping all other arguments would not be easily possible if doSomething was a def not a case class. ## What is newBuild and why do we need it? Methods in a class can call each other and thereby effectively form a graph. Subclasses can patch this graph. Sometimes you want to create several different variants of a graph. Let's say one building artifacts with a -SNAPSHOT version and another one building a stable release. Or one with scalaVersion 2.10 and one with scalaVersion 2.11. Or one optmizing your code and one not optimizing it. You can subclass your build once for each alternative. That's fine if you know which class to subclass, but plugins for your build do not know what your concrete build class is and cannot produce subclasses for it. Example ``` class Build(val context: Context){ // this works fine def with210 = new Build(context){ override def scalaVersion = "2.10" } def with211 = new Build(context){ override def scalaVersion = "2.11" } } ``` Imagine however ``` // defined by you: class Build(val context: Context) extends CrossVersionPlugin // defined by the plugin author: trait CrossVersionPlugin{ // this does not compile, Build does not exists when the plugin is compiled def with210 = new Build(context){ override def scalaVersion = "2.10" } def with211 = new Build(context){ override def scalaVersion = "2.11" } } ``` Luckily CBT can cheat because it has a compiler available. ``` // defined by the plugin author: trait CrossVersionPlugin{ // method `newBuild` generates a new subclass of Build at runtime and creates an instance. def with210 = newBuild{""" override def scalaVersion = "2.10" """} def with211 = newBuild{""" override def scalaVersion = "2.11" """} } ``` Problem solved. In fact this allows for a very, very flexible way of creating differents variants of your build.