diff --git a/plug-in-development/index.html b/plug-in-development/index.html index a54a451..1d502bb 100644 --- a/plug-in-development/index.html +++ b/plug-in-development/index.html @@ -30,54 +30,54 @@ --> - -
- - - -

How to write a Kobalt plug-in

- -
-

Tutorial

-

- If you are curious to get a quick feel for what a Kobalt plug-in looks like, I suggest you go read how to - write and publish a plug-in in ten minutes and then you can come back here - and keep reading. -

+ +
+ + + +

How to write a Kobalt plug-in

+ +
+

Tutorial

+

+If you are curious to get a quick feel for what a Kobalt plug-in looks like, I suggest you go read how to +write and publish a plug-in in ten minutes and then you can come back here +and keep reading. +

-

Plug-in architecture

-

-

- Plug-ins often produce files and data that other plug-ins need to use in order for a build to succeed. For example, - the Android plug-in needs to generate a file called R.java and then make this file available at - compile time by the Java or Kotlin (or any other language) plug-in. Since plug-ins have no idea about what other - plug-ins are currently enabled and running, they can't directly talk to each other so instead of calling into - Kobalt, Kobalt calls into them. This is done by declaring various "actors" that Kobalt will invoke whenever - it needs the information that your plug-in produced. This is a design pattern often referred to as the - "Hollywood Principle": "Don't call us, we'll call you". -

-

- These "actors" are exactly what the kobalt-plugin.xml file describes. This file informs Kobalt about - the various ways in which your plug-in participates in the build system by specifying 1) plug-ins, 2) contributors - or 3) interceptors. -

-

+

Plug-in architecture

+

+

+Plug-ins often produce files and data that other plug-ins need to use in order for a build to succeed. For example, +the Android plug-in needs to generate a file called R.java and then make this file available at +compile time by the Java or Kotlin (or any other language) plug-in. Since plug-ins have no idea about what other +plug-ins are currently enabled and running, they can't directly talk to each other so instead of calling into +Kobalt, Kobalt calls into them. This is done by declaring various "actors" that Kobalt will invoke whenever +it needs the information that your plug-in produced. This is a design pattern often referred to as the +"Hollywood Principle": "Don't call us, we'll call you". +

+

+These "actors" are exactly what the kobalt-plugin.xml file describes. This file informs Kobalt about +the various ways in which your plug-in participates in the build system by specifying 1) plug-ins, 2) contributors +or 3) interceptors. +

+

-

Parts

-

-

    -
  • kobalt-plugin.xml. A file that describes all the components (called "plug-in actors") of your plug-in, such as contributors.
  • -
  • Directives. Kotlin functions that users of your plug-in can invoke in their build file, such as project or dependencies. These functions typically configure some data that your plug-in will later use to perform its functions.
  • -
  • Tasks. These tasks are invoked from the command line and ask your plug-ins to perform certain actions.
  • -
  • Properties. Plug-ins can export properties and read properties from other plug-ins.
  • -
-

- -

kobalt-plugin.xml

-

- The kobalt-plugin.xml file (stored in META-INF in the jar file of your plug-in) is mandatory and describes all the actors of your plug-in. This file contains a list of class names, each of which is expected to implement at least one of IPluginActor's interfaces: -

+

Parts

+

+

    +
  • kobalt-plugin.xml. A file that describes all the components (called "plug-in actors") of your plug-in, such as contributors.
  • +
  • Directives. Kotlin functions that users of your plug-in can invoke in their build file, such as project or dependencies. These functions typically configure some data that your plug-in will later use to perform its functions.
  • +
  • Tasks. These tasks are invoked from the command line and ask your plug-ins to perform certain actions.
  • +
  • Properties. Plug-ins can export properties and read properties from other plug-ins.
  • +
+

+ +

kobalt-plugin.xml

+

+The kobalt-plugin.xml file (stored in META-INF in the jar file of your plug-in) is mandatory and describes all the actors of your plug-in. This file contains a list of class names, each of which is expected to implement at least one of IPluginActor's interfaces: +

 <plugin-actors>
   <class-name>com.beust.kobalt.plugin.java.JavaPlugin</class-name>
@@ -276,19 +276,19 @@ class JavaBuildGenerator: ITemplateContributor {
-

Selection process

-

- Several plug-ins might want to contribute to a specific task where only one participant should be allowed, - such as running tests or generating documentation. Even the simple task of compiling should probably only - ever be performed by no more than one plug-in for a given project. Therefore, when comes the time to - compile a project, - Kobalt needs to find which plug-in is the most suitable for that task and pick it. In order to do that, - plug-ins that contribute to tasks that can only be performed by one candidate need to declare their - affinity to that task for a given project. -

-

- Contributors that want to participate in a selection process need to implement the following interface: -

+

Selection process

+

+Several plug-ins might want to contribute to a specific task where only one participant should be allowed, +such as running tests or generating documentation. Even the simple task of compiling should probably only +ever be performed by no more than one plug-in for a given project. Therefore, when comes the time to +compile a project, +Kobalt needs to find which plug-in is the most suitable for that task and pick it. In order to do that, +plug-ins that contribute to tasks that can only be performed by one candidate need to declare their +affinity to that task for a given project. +

+

+Contributors that want to participate in a selection process need to implement the following interface: +

 interface IProjectAffinity {
     /**
@@ -356,19 +356,19 @@ public fun myConfig(init: Info.() -> Unit) = Info().apply {
     (Kobalt.findPlugin("my-plug-in") as MyPlugin).info = info
     this
 }
-

- Obviously, you can choose any kind of API to communicate between the directive and its plug-in. In the code - above, I chose to directly override the entire Info field, but you could instead choose to call - a function, just set one boolean instead of the whole object, etc... -

-

Tasks

-

- Tasks are provided by plug-ins and can be invoked from the command line, e.g. ./kobaltw assemble. There are two kinds of tasks: static and dynamic. -

-

Static tasks

-

- Static tasks are functions declared directly in your plug-in class and annotated with the @Task annotation. Here is an example: -

+

+Obviously, you can choose any kind of API to communicate between the directive and its plug-in. In the code +above, I chose to directly override the entire Info field, but you could instead choose to call +a function, just set one boolean instead of the whole object, etc... +

+

Tasks

+

+Tasks are provided by plug-ins and can be invoked from the command line, e.g. ./kobaltw assemble. There are two kinds of tasks: static and dynamic. +

+

Static tasks

+

+Static tasks are functions declared directly in your plug-in class and annotated with the @Task annotation. Here is an example: +

 @Task(name = "lineCount", description = "Count the lines", runBefore = arrayOf("compile"))
 fun lineCount(project: Project): TaskResult {
@@ -376,96 +376,96 @@ fun lineCount(project: Project): TaskResult {
     return TaskResult()
 }
 
-

- A Kobalt task needs to accept a Project in parameter and return a TaskResult, which indicates whether this task completed successfully. -

-

- The @Task annotation accepts the following attributes: -

-
name
-
The name of the task, which will be used to invoke it from the command line.
-
description
-
The description of this command, which will be displayed if the user invokes the usage for the kobaltw command.
-
runBefore
-
A list of all the tasks that this task should run prior to.
-
runAfter
-
A list of all the tasks that should run before this task does.
-
alwaysRunAfter
-
A list of all the tasks that will always be run after this task if it's invoked.
-
-

-

- The difference between runAfter and alwaysRunAfter is subtle but important. runAfter - is just a declaration of dependency. It's basically the reverse of runBefore but it's useful in case - you are not the author of the task you want to run before (if you were, you would just use the runBefore - annotation on it). Since you can't say "a runBefore b" because you don't own task "a", - you say "b runAfter a". -

-

- For example, compileTest is declared as a runAfter for the task compile. - This means that it doesn't make sense to run compileTest unless compile has run first. - However, if a user invokes the task compile, they probably don't want to invoke compileTest, - so a dependency is exactly what we need here: invoking compileTest will trigger compile - but not the other way around. -

-

- However, there are times where you want to define a task that will always run after a given task. - For example, you could have a signJarFile task that should always be invoked if someone builds a jar - file. You don't expect users to invoke that target explicitly, but whenever they invoke the assemble - target, you want your signJarFile target to be invoked. When you want such a task to always be invoked - even if the user didn't explicitly request it, you should use alwaysRunAfter. - Note that there is no alwaysRunBefore annotation since runBefore - achieves the same functionality. -

-

- Here are a few different scenarios to illustrate how the three attributes work for the task exampleTask: -

-

- Result of the command ./kobaltw --dryRun compile -

- - - - - - - - + +
Configuration for exampleTaskResult
runBefore = "compile" +

+A Kobalt task needs to accept a Project in parameter and return a TaskResult, which indicates whether this task completed successfully. +

+

+The @Task annotation accepts the following attributes: +

+
name
+
The name of the task, which will be used to invoke it from the command line.
+
description
+
The description of this command, which will be displayed if the user invokes the usage for the kobaltw command.
+
runBefore
+
A list of all the tasks that this task should run prior to.
+
runAfter
+
A list of all the tasks that should run before this task does.
+
alwaysRunAfter
+
A list of all the tasks that will always be run after this task if it's invoked.
+
+

+

+The difference between runAfter and alwaysRunAfter is subtle but important. runAfter +is just a declaration of dependency. It's basically the reverse of runBefore but it's useful in case +you are not the author of the task you want to run before (if you were, you would just use the runBefore +annotation on it). Since you can't say "a runBefore b" because you don't own task "a", +you say "b runAfter a". +

+

+For example, compileTest is declared as a runAfter for the task compile. +This means that it doesn't make sense to run compileTest unless compile has run first. +However, if a user invokes the task compile, they probably don't want to invoke compileTest, +so a dependency is exactly what we need here: invoking compileTest will trigger compile +but not the other way around. +

+

+However, there are times where you want to define a task that will always run after a given task. +For example, you could have a signJarFile task that should always be invoked if someone builds a jar +file. You don't expect users to invoke that target explicitly, but whenever they invoke the assemble +target, you want your signJarFile target to be invoked. When you want such a task to always be invoked +even if the user didn't explicitly request it, you should use alwaysRunAfter. +Note that there is no alwaysRunBefore annotation since runBefore +achieves the same functionality. +

+

+Here are a few different scenarios to illustrate how the three attributes work for the task exampleTask: +

+

+Result of the command ./kobaltw --dryRun compile +

+ + + + + + + + - - - - + + + + - - - - + + + + - -
Configuration for exampleTaskResult
runBefore = "compile"
kobalt-line-count:clean
 kobalt-line-count:exampleTask
 kobalt-line-count:compile
-
runAfter = "compile" -
kobalt-line-count:clean
+        
runAfter = "compile" +
kobalt-line-count:clean
 kobalt-line-count:compile
-
alwaysRunAfter = "compile" -
kobalt-line-count:clean
+        
alwaysRunAfter = "compile" +
kobalt-line-count:clean
 kobalt-line-count:compile
 kobalt-line-count:exampleTask
-
-

Dynamic tasks

-

- Dynamic tasks are useful when you want your plug-in to generate one or several tasks that depend on - some other runtime information (therefore, you can't declare a method and put a @Task - annotation on it). Plug-ins declare dynamic tasks by implementing the ITaskContributor - intrface: -

+
+

Dynamic tasks

+

+Dynamic tasks are useful when you want your plug-in to generate one or several tasks that depend on +some other runtime information (therefore, you can't declare a method and put a @Task +annotation on it). Plug-ins declare dynamic tasks by implementing the ITaskContributor +intrface: +

 interface ITaskContributor {
-    fun tasksFor(context: KobaltContext) : List<DynamicTask>
+fun tasksFor(context: KobaltContext) : List<DynamicTask>
 }
-

- For example: -

+

+For example: +

 override fun tasksFor(context: KobaltContext) = listOf(
     DynamicTask(
@@ -476,15 +476,15 @@ override fun tasksFor(context: KobaltContext) = listOf(
             println("Running dynamicTask")
             TaskResult()
 }))
-

- DynamicTask mirrors the @Task attributes: name, description and - dependencies. The only addition is the closure parameter, which specifics the code that will - run if your task gets invoked. That closure needs to follow the same constraints that a @Task method - obeys: it takes a Project parameter and returns a TaskResult. -

-

- Once you have implemented ITaskContributor, you can see your dynamic task in the list of tasks and run it directly: -

+

+DynamicTask mirrors the @Task attributes: name, description and +dependencies. The only addition is the closure parameter, which specifics the code that will +run if your task gets invoked. That closure needs to follow the same constraints that a @Task method +obeys: it takes a Project parameter and returns a TaskResult. +

+

+Once you have implemented ITaskContributor, you can see your dynamic task in the list of tasks and run it directly: +

 $ ./kobaltw --tasks
   ===== kobalt-line-count =====
@@ -493,32 +493,32 @@ $ ./kobaltw --tasks
 $ ./kobaltw dynamicTask
 Running dynamictask
 
-

Properties

-

- Properties are the mechanism that plug-ins can use to export values and also read values that other - plug-ins have exported. There are two kinds of properties that plug-ins can manipulate: -

-
    -
  • Project properties: project-specific properties.
  • -
  • Plug-in properties: general properties that are applicable to no project - in particular.
  • -
-

Project properties

-

- Project instances have a property called projectProperties that is an - instance of the ProjectProperties class. Plugins can put and get values on this - object in order to store project specific properties. -

-
+

Properties

+

+Properties are the mechanism that plug-ins can use to export values and also read values that other +plug-ins have exported. There are two kinds of properties that plug-ins can manipulate: +

+
    +
  • Project properties: project-specific properties.
  • +
  • Plug-in properties: general properties that are applicable to no project + in particular.
  • +
+

Project properties

+

+Project instances have a property called projectProperties that is an +instance of the ProjectProperties class. Plugins can put and get values on this +object in order to store project specific properties. +

+
 fun taskAssemble(project: Project) : TaskResult {
-    project.projectProperties.put(PACKAGES, packages)
+project.projectProperties.put(PACKAGES, packages)
 
-

Plug-in properties

-

- The PluginProperties instance can be found on the KobaltContext - object that your plug-in receives in its apply() method. Once you have an instance of this - class, you can read or write variables into it: -

+

Plug-in properties

+

+The PluginProperties instance can be found on the KobaltContext +object that your plug-in receives in its apply() method. Once you have an instance of this +class, you can read or write variables into it: +

 override fun apply(project: Project, context: KobaltContext) {
     // Export a property for other plug-ins to use
@@ -527,33 +527,33 @@ override fun apply(project: Project, context: KobaltContext) {
     val sourceDir = context.pluginProperties.get("pluginName", "somePluginProperty")
 }
 
-

Documenting properties

-

- Plug-ins that define properties should annotate them with the @ExportedPluginProperty or - @ExportedProjectPropertyannotation: -

+

Documenting properties

+

+ Plug-ins that define properties should annotate them with the @ExportedPluginProperty or + @ExportedProjectPropertyannotation: +

-    companion object {
-        @ExportedProjectProperty
-        const val BUILD_DIR = "buildDir"
+companion object {
+@ExportedProjectProperty
+const val BUILD_DIR = "buildDir"
 
-
- -
-
- - - - - - - - - -
- \ No newline at end of file +
+ +
+
+ + + + + + + + + +
+ \ No newline at end of file