1
0
Fork 0
mirror of https://github.com/ethauvin/kobalt-doc.git synced 2025-04-25 12:07:10 -07:00
kobalt-doc/plug-in-development/index.html
2015-10-14 21:24:23 -07:00

536 lines
18 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 set up your environment 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 two parts.
<ul>
<li>Directives. These are 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>Tasks. These tasks are invoked from the command line and ask your plug-ins to perform certain actions.</li>
</ul>
</p>
<p>
We'll cover these two items shortly but first of all, let's go over a quick example that will show you the whole process of writing a plug-in from scratch and publishing it on JCenter in ten minutes.
</p>
<h2 class="section" id="ten-minutes">Writing and publishing a plug-in in ten minutes</h2>
<p>
In this example, we'll write a Kobalt plug-in that simply counts the number of source files and lines in your project.
Starting from scratch, we'll have this plug-in published to JCenter and ready to use in ten minutes.
</p>
<p>
Let's start by creating our project:
</p>
<pre>
$ mkdir linecount
$ mkdir -p src/main/kotlin/com/beust/plugin/linecount
$ touch src/main/kotlin/com/beust/plugin/linecount/Main.kt
$ $KOBALT_HOME/kobaltw --init
</pre>
<p>
I create an empty <code>Main.kt</code> in the example above so that calling <code>./kobaltw --init</code> will detect the project as a Kotlin one. This way, the <code>Build.kt</code> file generated is already configured for Kotlin. Since we will be publishing this project to a Maven repository, we need to make sure that its <code>group</code>, <code>artifactId</code> and <code>version</code> are correct. The only thing that the generator can't guess is the <code>group</code>, so let's go ahead and fix it:
</p>
<pre>
val project = kotlinProject {
name = "kobalt-line-count"
group = "com.beust.kobalt"
artifactId = name
version = "0.1"
...
</pre>
<p>
Next, we want the manifest of our jar file to point to our main Kobalt plug-in class:
</p>
<pre>
val packProject = packaging(project) {
jar {
manifest {
attributes("Kobalt-Plugin-Class", "com.beust.kobalt.plugin.linecount.Main")
}
}
}
</pre>
<p>
Now we're ready to code.
</p>
<p>
Let's start by writing the simplest plug-in we can:
</p>
<pre>
package com.beust.kobalt.plugin.linecount
import com.beust.kobalt.api.*
public class Main : BasePlugin() {
override val name = "kobalt-line-count"
override fun apply(project: Project, context: KobaltContext) {
println("*** Applying plugin ${name} with project ${project}")
}
}
</pre>
<p>
Before we can upload it, we need to create the package in bintray, <a href="https://bintray.com/docs/usermanual/uploads/uploads_creatinganewpackage.html">as explained here</a>. Once this is done, we are ready to do our first upload:
</p>
<pre>
$ ./kobaltw uploadJcenter
...
========== kobalt-line-count:uploadJcenter
kobalt-line-count: Found 2 artifacts to upload
All artifacts successfully uploaded
############# Time to Build: 3590 ms
</pre>
<p>
If you go to the maven section of your bintray account, you will now see that the new package has two unpublished files. Your new plug-in won't be visible by clients until you publish those files, so let's update our build file to automatically publish files from now on:
</p>
<pre>
val jc = jcenter(project) {
publish = true
}
</pre>
<p>
And now, let's implement our logic, which is pretty simple:
</p>
<pre>
// Main.kt
@Task(name = "lineCount", description = "Count the lines", runBefore = arrayOf("compile"))
fun lineCount(project: Project): TaskResult {
var fileCount = 0
var lineCount : Long = 0
val matcher = FileSystems.getDefault().getPathMatcher("glob:**.kt")
project.sourceDirectories.forEach {
Files.walkFileTree(Paths.get(it), object: SimpleFileVisitor<Path>() {
override public fun visitFile(path: Path, attrs: BasicFileAttributes): FileVisitResult {
if (matcher.matches(path)) {
fileCount++
lineCount += Files.lines(path).count()
}
return FileVisitResult.CONTINUE
}
})
}
log(1, "Found ${lineCount} lines in ${fileCount} files")
return TaskResult()
}
</pre>
<p>
We create a task called <code>"lineCount"</code> in which we look for all files ending in ".kt" in all the source directories of the project. Finally, we display a count of files and lines at the end by using <code>log()</code>, which is automatically supplied by the Kobalt API:
</p>
<pre>
public class Main : BasePlugin() {
</pre>
<p>
Let's bump our version to 0.2 (since version 0.1 is already uploaded and JCenter won't allow us to overwrite it) and upload our new plug-in:
</p>
<pre>
$ ./kobaltw uploadJcenter
...
kobalt-line-count: Compilation succeeded
========== kobalt-line-count:assemble
Created /Users/beust/kotlin/kobalt-line-count/kobaltBuild/libs/kobalt-line-count-0.2.jar
========== kobalt-line-count:generatePom
Wrote /Users/beust/kotlin/kobalt-line-count/kobaltBuild/libs/kobalt-line-count-0.2.pom
========== kobalt-line-count:uploadJcenter
kobalt-line-count: Found 2 artifacts to upload
All artifacts successfully uploaded
Time to Build: 5907 ms
</pre>
<p>
Finally, let's use our plug-in from another project. Since we didn't link this project to JCenter, it's uploaded in the user's maven repository, so we will have to add this maven repository to the build file where we want to use the plug-in. Adjust this line to point to your own maven repo:
</p>
<pre>
val repos = repos("https://dl.bintray.com/cbeust/maven/")
val plugins = plugins("com.beust.kobalt:kobalt-line-count:0.2")
</pre>
<p>
Now let's launch a build:
</p>
<pre>
$ ./kobaltw assemble
...
========== kobalt:lineCount
Found 4972 lines in 65 files
========== kobalt:compile
...
</pre>
<p>
Note that our plugin ran before the <code>compile</code> task, as we requested in the <code>@Task</code> annotation. We can also verify that it's activated and we can invoke the task directly instead of having it run as part of the build:
</p>
<pre>
$ ./kobaltw --tasks
===== kobalt-line-count =====
lineCount Count the lines
$ ./kobaltw lineCount
Found 4972 lines in 65 files
</pre>
<p>
And that's it! You can now iterate on your plug-in and upload it with additional <code>./kobaltw uploadJcenter</code>. This plug-in is <a href="https://github.com/cbeust/kobalt-linecount">available on github</a>.
</p>
<h2 class="section" id="debugging">Debugging</h2>
<p>
The simplest way to run your plug-in in your IDE is to create a main function in the main class of your
plug-in as follows:
</p>
<pre>
fun main(argv: Array&lt;String&gt;) {
com.beust.kobalt.main(argv)
}
public class Main : BasePlugin() {
// ...
</pre>
<p>
Now you can simply create a launch configuration for your main class, which will invoke Kobalt.
</p>
<p>
The next step is to have Kobalt invoke your plug-in, so you will have to modify your build file
to call it. As long as you haven't deployed your plug-in to JCenter, you might want to use the
<code>file()</code> directive to declare your dependency, so that Kobalt will use the jar file
on your file system:
</p>
<pre>
val p = plugins(
file(homeDir("kotlin/kobalt-line-count/kobaltBuild/libs/kobalt-line-count-0.8.jar"))
)
</pre>
<p>
You can now set a breakpoint in your plug-in and launch the configuration you created above.
</p>
<h2 class="section" id="initialization">Initialization</h2>
<p>
When your plug-in is activated, Kobalt will invoke its <code>apply()</code> function:
</p>
<pre>
override fun apply(project: Project, context: KobaltContext) {
}
</pre>
<p>
<code>project</code> is the project that your plug-in is currently being initialized for (keep in mind there can be multiple projects in a build) and the <code>context</code> gives you some information about other external data you might find useful, such as the command line that was passed to Kobalt.
</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>