aboutsummaryrefslogtreecommitdiff
path: root/doc/plugin-author-guide.md
blob: db0eede604aaefb7453cd39fef422ea6f5e55b76 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
## How to write an idiomatic CBT plugin?

Write a small library that could be fully used outside of CBT's
build classes and handles all the use cases you need. For example

```
object MyLibrary{
 def doSomething( ... ) = // do something here
}
```

Publish it as a library and you might be done right here.

If your library requires configuration information commonly found
in your build, like the sourceFiles, groupId, scalaVersion or else,
consider offering a mixin trait, that pre-configures your library
for user convenience. (Consider publishing them separately if that
allows people to use your library outside of CBT with fewer
dependencies.) Here is an example of a library with an
accompanying mixin trait configuring the library for CBT.

```
package my.library
object MyLibrary{
  case class DoSomething( scalaVersion: String, ... ){
    case class config( targetFile: File, affectBehavior: Boolean = true, ... ){
      def apply = // really do something here
    }
  }
}

package my.plugin
trait MyLibrary extends BaseBuild{
  def doSomething = MyLibrary.DoSomething(scalaVersion, ...).config( scalaTarget / "my.file" )
}
```

* Note: Do not override any common method like `compile` or `test` in a public plugin. *
* Instead document recommendations where users should hook in your custom methods. *
* This will help users understand their own builds. *

See how we only define a single `def doSomething` in the MyLibrary
trait? We did not define things like `def doSomethingTargetFile`.
Instead we have a case class defined in the library which a user
can .copy as needed to customize configuration. A user build could
look like this:

```
class Build(val context: Context) extends MyLibrary{
  override def doSomething = super.doSomething.copy(
    affectBehavior = false
  )
}
```

As you can see a user can use .copy to override default behavior
of your library.

This nesting allows us to keep the global namespace small, which
helps us lower the risk of global name clashes between different
libraries. It also makes it clearer that `affectBehavior` is
something specific to `MyLibary` which should help making builds
easier to understand. Further this nesting means we don't need to
encode namespaces in the names themselves, but use Scala's language
features for that, which allows us to keep names nice and concise.

You might wonder why there is a case class `DoSomething` rather
than `scalaVersion` just being another parameter in case class
`config`. Nesting case classes like this is a pattern that given
the way we use them allows us to make it slightly harder to
modify some parameters (the ones on the outer case class) than
others (the ones on the inner case class). Why? Some are likely
to need user customization, others are likely to break stuff if
they are touched. Example: The scalaVersion is probably something
you want to configure once consistently across your entire build.
Otherwise you might end up accidentally packaging scala-2.11
compiled class files as a jar with a `_2.10` artifact id.
Changing which targetFile `doSomething` writes to however is
something you should be able to safely change. Since we defined
`doSomething` as
`def doSomething = MyLibrary.DoSomething(...).config( ... )`
overriding behavior in user code with .copy only affects
the inner case class because super.doSomething is an instance
of that:
```
  override def doSomething = super.doSomething.copy(
    affectBehavior = false
  )
```
Overriding things in class `DoSomething` is possible by creating
an entire new instance of the outer one, but slightly harder
preventing users from accidentally doing the wrong thing.

Obviously this decisions what's dangerous to override and what
is not can be a judgment call and not 100% clear.

A few more conventions for more uniform plugin designs:
If you only have one outer case class in yur plugin `object`,
call the case class `apply` instead of `DoSomething`. If you
need multiple (because you basically have several commands,
each with their own private configuration), give it a name
representing the operation, e.g. `compile` or `doc`. If there
is only one inner case class inside of anothe case class,
call it `config`, give it a name representing the operation,
e.g. `compile` or `doc`.