mirror of
https://github.com/ethauvin/kobalt-doc.git
synced 2025-04-25 12:07:10 -07:00
407 lines
16 KiB
HTML
407 lines
16 KiB
HTML
<html>
|
|
<head>
|
|
<title>
|
|
|
|
Kobalt, by Cedric Beust
|
|
|
|
</title>
|
|
|
|
<!-- Bootstrap core CSS -->
|
|
|
|
<link href="../bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link href="../css/kobalt.css" rel="stylesheet" />
|
|
|
|
<!-- Optional Bootstrap Theme -->
|
|
|
|
<link href="data:text/css;charset=utf-8," data-href="../bootstrap/dist/css/bootstrap-theme.min.css" rel="stylesheet" id="bs-theme-stylesheet">
|
|
|
|
|
|
<!--[if lt IE 9]><script src="../assets/js/ie8-responsive-file-warning.js"></script><![endif]-->
|
|
<!--
|
|
<script src="../bootstrap/assets/js/ie-emulation-modes-warning.js"></script>
|
|
-->
|
|
|
|
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
|
<!--[if lt IE 9]>
|
|
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
|
|
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
|
<![endif]-->
|
|
|
|
<!-- Favicons -->
|
|
<!--
|
|
<link rel="icon" href="/favicon.ico">
|
|
-->
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
|
|
<div class="container">
|
|
|
|
<!-- Static navbar -->
|
|
<nav id="kobalt-navbar" class="navbar navbar-default">
|
|
</nav>
|
|
|
|
<!-- Main component for a primary marketing message or call to action -->
|
|
<div class="jumbotron">
|
|
<h1>Plug-in development</h1>
|
|
<p>How to write a Kobalt plug-in.</p>
|
|
</div>
|
|
|
|
<!-- Main content -->
|
|
<div class="col-md-9">
|
|
|
|
<h2 class="section" id="introduction">Introduction</h2>
|
|
|
|
<p>
|
|
Kobalt plug-ins are usually made of several parts:
|
|
|
|
<ul>
|
|
<li><b>plugin.xml</b>. A file that describes all the components of your plug-in, such as contributors.</li>
|
|
<li><b>Directives</b>. Kotlin functions that users of your plug-in can invoke in their build file, such as <code>kotlinProject</code> or <code>dependencies</code>. These functions typically configure some data that your plug-in will later use to perform its functions.</li>
|
|
<li><b>Tasks</b>. These tasks are invoked from the command line and ask your plug-ins to perform certain actions.</li>
|
|
<li><b>Properties</b>. Plug-ins can export properties and read properties from other plug-ins.</li>
|
|
</ul>
|
|
</p>
|
|
|
|
<h2 class="section" id="plugin-xml">plugin.xml</h2>
|
|
<p>
|
|
The <code>plugin.xml</code> file (stored in <code>META-INF</code> in the jar file of your plug-in) is mandatory and describes all the components of your plug-in. At a minimum,
|
|
this file will contain the name of your plug-in and the main plug-in class:
|
|
</p>
|
|
<pre>
|
|
<kobalt-plugin>
|
|
<name>kobalt</name>
|
|
<plugins>
|
|
<class-name>com.beust.kobalt.plugin.android.AndroidPlugin</class-name>
|
|
</plugins>
|
|
</kobalt-plugin>
|
|
</pre>
|
|
|
|
<p>
|
|
This file can also contain Contributors, which are the main mechanism that Kobalt plug-ins use to interact with each other.
|
|
</p>
|
|
|
|
<h3>Contributors</h3>
|
|
<p>
|
|
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 <code>R.java</code> 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 contributors that Kobalt will invoke whenever
|
|
it needs the information that your plug-in produced. This is a design pattern often referred to as the
|
|
<a href="https://en.wikipedia.org/wiki/Hollywood_principle">"Hollywood Principle"</a>: "Don't call us, we'll call you".
|
|
</p>
|
|
|
|
<p>In order to make things more concrete, let's take a look at
|
|
<a href=https://github.com/cbeust/kobalt/blob/master/src/main/resources/META-INF/plugin.xml">Kobalt's own <code>plugin.xml</code></a>
|
|
and go over it line by line.
|
|
</p>
|
|
<h4>plugins (<code>IPlugin</code>)</h4>
|
|
<pre>
|
|
<plugins>
|
|
<class-name>com.beust.kobalt.plugin.android.AndroidPlugin</class-name>
|
|
<class-name>com.beust.kobalt.plugin.application.ApplicationPlugin<class-name>
|
|
</pre>
|
|
<p>
|
|
Kobalt defines a few plug-ins in its core so you never need to download them.
|
|
</p>
|
|
|
|
<h4>Classpath contributors (<code>IClasspathContributor</code>)</h4>
|
|
<pre>
|
|
<classpath-contributors>
|
|
<class-name>com.beust.kobalt.plugin.android.AndroidPlugin</class-name>
|
|
<class-name>com.beust.kobalt.plugin.kotlin.KotlinPlugin</class-name>
|
|
</pre>
|
|
|
|
<p>
|
|
Classpath contributors let you specify additional jar files or directories that will be used by
|
|
the compile task. In the above example, the <code>KotlinPlugin</code> adds the Kotlin runtime
|
|
to the classpath and Android adds various Android resources (e.g. <code>aar</code> files) to it
|
|
as well.
|
|
</p>
|
|
|
|
<h4>Project contributors (<code>IProjectContributor</code>)</h4>
|
|
<pre>
|
|
<project-contributors>
|
|
<class-name>com.beust.kobalt.plugin.java.JavaPlugin</class-name>
|
|
<class-name>com.beust.kobalt.plugin.kotlin.KotlinPlugin</class-name>
|
|
</pre>
|
|
<p>
|
|
Some plug-ings produce projects (Java, Kotlin) while others don't (Packaging, Application, etc...). The ones that
|
|
do need to register themselves as project contributors. This is how Kobalt collects all the projects defined
|
|
after a build file was parsed.
|
|
</p>
|
|
|
|
<h4>Init contributors (<code>IInitContributor</code>)</h4>
|
|
<pre>
|
|
<init-contributors>
|
|
<class-name>com.beust.kobalt.plugin.java.JavaBuildGenerator</class-name>
|
|
<class-name>com.beust.kobalt.plugin.kotlin.KotlinBuildGenerator</class-name>
|
|
</pre>
|
|
<p>
|
|
Kobalt supports the <code>--init</code> command line parameter, which generates a default build file
|
|
based on the files found in the current directory. Any plug-in that wants to be part of this process need
|
|
to specify Init Contributors. In this case, both the Java and Kotlin plug-ins define such a contributor
|
|
but future plug-ins might use this contributor to generate their own build file: Android, Ceylon, Spring, etc...
|
|
</p>
|
|
<p>
|
|
You can take a look at the <code>IInitContributor</code> interface to find out more details but in a nutshell,
|
|
each Init Contributor is asked how many files in the current directory their plug-in handles and the contributor
|
|
with the highest number of files is then asked to generate the build file.
|
|
</p>
|
|
|
|
<h4>Repo contributors (<code>IRepoContributor</code>)</h4>
|
|
<pre>
|
|
<repo-contributors>
|
|
<class-name>com.beust.kobalt.plugin.android.AndroidPlugin</class-name>
|
|
</pre>
|
|
<p>
|
|
Some plug-ins might want to add their own repository to the list of repositories that Kobalt already supports.
|
|
This is the case of the Android plug-in which, once the <code>ANDROID_HOME</code> environment variable has been
|
|
defined, will automatically add the repository inside the Android distribution so that support libraries and other
|
|
artifacts can be found.
|
|
</p>
|
|
|
|
|
|
<h2 class="section" id="directives">Directives</h2>
|
|
<p>
|
|
Directives are functions that users of your plug-in can use in their build file in order to configure your plug-in. These can be any kind of Kotlin function but in the interest of preserving a clean syntax in the build file, it's recommended to use the type safe builder pattern, <a href="https://kotlinlang.org/docs/reference/type-safe-builders.html">as described here</a>.
|
|
</p>
|
|
<p>
|
|
Imagine that you want to offer a boolean parameter <code>publish</code> to users of your plug-in, you start by creating a class to hold that parameter:
|
|
</p>
|
|
<pre>
|
|
class Info(val publish: Boolean)
|
|
</pre>
|
|
<p>
|
|
Next, you create a directive that returns such a class and which also allows to configure it via the type safe builder pattern:
|
|
</p>
|
|
<pre>
|
|
@Directive
|
|
public fun myConfig(init: Info.() -> Unit) : Info {
|
|
val info = Info()
|
|
info.init()
|
|
return info
|
|
}
|
|
</pre>
|
|
<p>
|
|
The <code>@Directive</code> annotation is not enforced but you should always use it in order to help future tools (e.g. an IDEA plug-in) identify Kobalt directives so they can be treated differently from regular Kotlin functions.
|
|
</p>
|
|
<p>
|
|
Users can now specify the following in their build file:
|
|
</p>
|
|
<pre>
|
|
// Build.kt
|
|
import.com.example.plugin.myConfig
|
|
|
|
myConfig {
|
|
publish = true
|
|
}
|
|
</pre>
|
|
<p>
|
|
If you need access to the project being built, just declare an additional parameter of type <code>Project</code> to your directive and have the user pass that project:
|
|
</p>
|
|
<pre>
|
|
@Directive
|
|
public fun myConfig(project: Project, init: Info.() -> Unit) : Info {
|
|
// ...
|
|
</pre>
|
|
<pre>
|
|
myConfig(project) {
|
|
publish = true
|
|
}
|
|
</pre>
|
|
<p>
|
|
The last piece of this puzzle is how you give this data back to your plug-in so it can act on it. In order to do this, you simply look up the name of your plug-in in the <code>Plugins</code> registry and invoke whatever function you need to run:
|
|
</p>
|
|
|
|
<pre>
|
|
@Directive
|
|
public fun myConfig(project: Project, init: Info.() -> Unit) : Info {
|
|
val info = Info()
|
|
info.init()
|
|
(Kobalt.findPlugin("my-plug-in") as MyPlugin).info = info
|
|
return info
|
|
}
|
|
</pre>
|
|
<p>
|
|
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 overrid the entire <code>Info</code> field, but you could instead choose to call
|
|
a function, just set one boolean instead of the whole object, etc...
|
|
</p>
|
|
<h2 class="section" id="tasks">Tasks</h2>
|
|
<p>
|
|
Tasks are provided by plug-ins and can be invoked from the command line, e.g. <code>./gradlew assemble</code>. There are two kinds of tasks: static and dynamic.
|
|
</p>
|
|
<h3 class="section" indent="1">Static tasks</h3>
|
|
<p>
|
|
Static tasks are functions declared directly in your plug-in class and annotated with the <code>@Task</code> annotation. Here is an example:
|
|
</p>
|
|
<pre>
|
|
@Task(name = "lineCount", description = "Count the lines", runBefore = arrayOf("compile"))
|
|
fun lineCount(project: Project): TaskResult {
|
|
// ...
|
|
return TaskResult()
|
|
}
|
|
</pre>
|
|
<p>
|
|
A Kobalt task needs to accept a <code>Project</code> in parameter and return a <code>TaskResult</code>, which indicates whether this task completed successfully.
|
|
</p>
|
|
|
|
<div class="bs-callout bs-callout-warning">
|
|
<h4>Request for feedback</h4>
|
|
Having the <code>Project</code> passed in both the <code>apply()</code> function and in each task feels redundant, although it avoids the trouble from having to store that project in a field of the plug-in, making it essentially stateless.
|
|
</div>
|
|
|
|
<p>
|
|
The <code>@Task</code> annotation accepts the following attributes:
|
|
<dl class="dl-horizontal">
|
|
<dt>name</dt>
|
|
<dd>The name of the task, which will be used to invoke it from the command line.</dd>
|
|
<dt>description</dt>
|
|
<dd>The description of this command, which will be displayed if the user invokes the usage for the <code>gradlew</code> command.</dd>
|
|
<dt>runBefore</dt>
|
|
<dd>A list of all the tasks that this task should run prior to.</dd>
|
|
<dt>runAfter</dt>
|
|
<dd>A list of all the tasks that should run before this task does.</dd>
|
|
<dt>alwaysRunAfter</dt>
|
|
<dd>A list of all the tasks that will always be run after this task if it's invoked.</dd>
|
|
</dl>
|
|
</p>
|
|
<p>
|
|
The difference between <code>runAfter</code> and <code>alwaysRunAfter</code> is subtle but important. <code>runAfter</code>
|
|
is just a declaration of dependency. It's basically the reverse of <code>runBefore</code> 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 <code>runBefore</code>
|
|
annotation on it). Since you can't say <code>"a runBefore b"</code> because you don't own task "a",
|
|
you say <code>"b runAfter a"</code>.
|
|
</p>
|
|
<p>
|
|
For example, <code>compileTest</code> is declared as a <code>runAfter</code> for the task <code>compile</code>.
|
|
This means that it doesn't make sense to run <code>compileTest</code> unless <code>compile</code> has run first.
|
|
However, if a user invokes the task <code>compile</code>, they probably don't want to invoke <code>compileTest</code>,
|
|
so a dependency is exactly what we need here: invoking <code>compileTest</code> will trigger <code>compile</code>
|
|
but not the other way around.
|
|
</p>
|
|
<p>
|
|
However, there are times where you want to define a task that will <strong>always</strong> run after a given task.
|
|
For example, you could have a <code>signJarFile</code> 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 <code>assemble</code>
|
|
target, you want your <code>signJarFile</code> 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 <code>alwaysRunAfter</code>.
|
|
Note that there is no <code>alwaysRunBefore</code> annotation since <code>runBefore</code>
|
|
achieves the same functionality.
|
|
</p>
|
|
<p>
|
|
Here are a few different scenarios to illustrate how the three attributes work for the task <code>exampleTask</code>:
|
|
</p>
|
|
<p align="center">
|
|
<strong>Result of the command <code>./kobaltw --dryRun compile</code></strong>
|
|
</p>
|
|
<table width="100%" class="table table-bordered table-condensed">
|
|
<thead>
|
|
<td align="center">Configuration for <code>exampleTask</code></td>
|
|
<td align="center">Result</td>
|
|
</thead>
|
|
|
|
<tr>
|
|
<td align="center">runBefore = "compile"</td>
|
|
<td>
|
|
<pre>kobalt-line-count:clean
|
|
kobalt-line-count:exampleTask
|
|
kobalt-line-count:compile</pre>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td align="center">runAfter = "compile"</td>
|
|
<td>
|
|
<pre>kobalt-line-count:clean
|
|
kobalt-line-count:compile</pre>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td align="center">alwaysRunAfter = "compile"</td>
|
|
<td>
|
|
<pre>kobalt-line-count:clean
|
|
kobalt-line-count:compile
|
|
kobalt-line-count:exampleTask</pre>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h3 class="section" indent="1">Dynamic tasks</h3>
|
|
|
|
<p>
|
|
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 <code>@Task</code>
|
|
annotation on it). Here is the simplest dynamic task you can create in your plug-in class:
|
|
</p>
|
|
|
|
<pre>
|
|
override fun apply(project: Project, context: KobaltContext) {
|
|
println("*** Adding dynamic task")
|
|
addTask(project, "dynamicTask") {
|
|
println("Dynamic task")
|
|
TaskResult()
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
<p>
|
|
Like a regular task method, the closure you pass to <code>addTask()</code> has to return a <code>TaskResult</code>
|
|
object to indicate whether it succeeded or failed. You can
|
|
then see your dynamic task in the list of tasks and run it directly:
|
|
</p>
|
|
<pre>
|
|
$ ./kobaltw --tasks
|
|
===== kobalt-line-count =====
|
|
dynamicTask
|
|
lineCount Count the lines
|
|
$ ./kobaltw dynamicTask
|
|
Dynamic task
|
|
</pre>
|
|
<p>
|
|
The <code>addTask()</code> method lets you specify any attribute you can specify on the <code>@Task</code>
|
|
annotation: <code>description</code>, <code>runBefore</code>, etc... For example, here is how we would
|
|
specify that this task should always run after <code>compile:</code>
|
|
</p>
|
|
<pre>
|
|
addTask(project, "dynamicTask", alwaysRunAfter = listOf("compile")) {
|
|
println("Dynamic task")
|
|
TaskResult()
|
|
}
|
|
</pre>
|
|
<p>
|
|
Let's test it:
|
|
</p>
|
|
|
|
<pre>
|
|
$ ./kobaltw --dryRun compile
|
|
kobalt-line-count:clean
|
|
kobalt-line-count:compile
|
|
kobalt-line-count:exampleTask
|
|
</pre>
|
|
</div>
|
|
<!-- Table of contents -->
|
|
<div class="col-md-3" id="table-of-contents">
|
|
</div>
|
|
|
|
<!-- Bootstrap core JavaScript
|
|
================================================== -->
|
|
<!-- Placed at the end of the document so the pages load faster -->
|
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
|
|
<script src="../bootstrap/dist/js/bootstrap.min.js"></script>
|
|
<script src="../js/kobalt.js"></script>
|
|
<script>generateKobalt(3);</script>
|
|
|
|
<!--
|
|
<script src="../bootstrap/dist/js/docs.min.js"></script>
|
|
-->
|
|
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
|
|
<!--
|
|
<script src="../../assets/js/ie10-viewport-bug-workaround.js"></script>
|
|
-->
|
|
|
|
</div>
|
|
</body>
|