From 8a619cc1818bee32fec0e9a128d4ee7465297056 Mon Sep 17 00:00:00 2001 From: Cedric Beust Date: Sat, 7 Nov 2015 19:05:42 -0800 Subject: [PATCH] Document the new plug-in architecture. --- js/kobalt.js | 5 + plug-in-development/index.html | 303 ++++++++++---------------------- ten-minutes/index.html | 308 +++++++++++++++++++++++++++++++++ 3 files changed, 400 insertions(+), 216 deletions(-) create mode 100644 ten-minutes/index.html diff --git a/js/kobalt.js b/js/kobalt.js index 0ffc48e..e6b6c95 100644 --- a/js/kobalt.js +++ b/js/kobalt.js @@ -24,7 +24,12 @@ var content = [ { url: "../idea-plug-in/index.html", title: "IDEA plug-in" + }, + { + url: "../ten-minutes/index.html", + title: "Ten minutes" } + ]; var before = '
' diff --git a/plug-in-development/index.html b/plug-in-development/index.html index 02a67c5..86c12f8 100644 --- a/plug-in-development/index.html +++ b/plug-in-development/index.html @@ -46,7 +46,7 @@

Plug-in development

-

How to set up your environment to write a Kobalt plug-in.

+

How to write a Kobalt plug-in.

@@ -55,245 +55,116 @@

Introduction

- Kobalt plug-ins are usually made of two parts. + Kobalt plug-ins are usually made of several parts:

-

- 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. -

-

Writing and publishing a plug-in in ten minutes

+

plugin.xml

-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. + The plugin.xml file (stored in META-INF 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:

- -

-Let's start by creating our project: -

-
-$ 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
+<kobalt-plugin>
+    <name>kobalt</name>
+    <plugins>
+        <class-name>com.beust.kobalt.plugin.android.AndroidPlugin</class-name>
+    </plugins>
+</kobalt-plugin>
 

-I create an empty Main.kt in the example above so that calling ./kobaltw --init will detect the project as a Kotlin one. This way, the Build.kt 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 group, artifactId and version are correct. The only thing that the generator can't guess is the group, so let's go ahead and fix it: + This file can also contain Contributors, which are the main mechanism that Kobalt plug-ins use to interact with each other.

+

Contributors

+

+ 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 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 + "Hollywood Principle": "Don't call us, we'll call you". +

+ +

In order to make things more concrete, let's take a look at + Kobalt's own plugin.xml + and go over it line by line. +

+

plugins (IPlugin)

-val project = kotlinProject {
-    name = "kobalt-line-count"
-    group = "com.beust.kobalt"
-    artifactId = name
-    version = "0.1"
-    ...
+    <plugins>
+        <class-name>com.beust.kobalt.plugin.android.AndroidPlugin</class-name>
+        <class-name>com.beust.kobalt.plugin.application.ApplicationPlugin<class-name>
+
+

+ Kobalt defines a few plug-ins in its core so you never need to download them. +

+ +

Classpath contributors (IClasspathContributor)

+
+    <classpath-contributors>
+        <class-name>com.beust.kobalt.plugin.android.AndroidPlugin</class-name>
+        <class-name>com.beust.kobalt.plugin.kotlin.KotlinPlugin</class-name>
 

-Next, we want the manifest of our jar file to point to our main Kobalt plug-in class: + Classpath contributors let you specify additional jar files or directories that will be used by + the compile task. In the above example, the KotlinPlugin adds the Kotlin runtime + to the classpath and Android adds various Android resources (e.g. aar files) to it + as well.

+

Project contributors (IProjectContributor)

-packaging {
-    jar {
-        manifest {
-            attributes("Kobalt-Plugin-Class", "com.beust.kobalt.plugin.linecount.Main")
-        }
-    }
-}
-
- -

-Now we're ready to code. -

- -

-Let's start by writing the simplest plug-in we can: -

- -
-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}")
-    }
-}
-
- -

-Before we can upload it, we need to create the package in bintray, as explained here. Once this is done, we are ready to do our first upload: -

- -
-$ ./kobaltw uploadJcenter
-...
-========== kobalt-line-count:uploadJcenter
-  kobalt-line-count: Found 2 artifacts to upload
-  All artifacts successfully uploaded
-  ############# Time to Build: 3590 ms
-
- -

-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: -

- -
-jcenter {
-    publish = true
-}
-
- -

-And now, let's implement our logic, which is pretty simple: -

- -
-// 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() {
-            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()
-}
-
- -

-We create a task called "lineCount" 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 log(), which is automatically supplied by the Kobalt API: -

- -
-public class Main : BasePlugin() {
-
- -

-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: -

- - -
-$ ./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
-
- -

-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: -

- -
-val repos = repos("https://dl.bintray.com/cbeust/maven/")
-val plugins = plugins("com.beust.kobalt:kobalt-line-count:0.2")
-
- -

-Now let's launch a build: -

- -
-$ ./kobaltw assemble
-...
-========== kobalt:lineCount
-Found 4972 lines in 65 files
-========== kobalt:compile
-...
-
- -

-Note that our plugin ran before the compile task, as we requested in the @Task 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: -

- -
-$ ./kobaltw --tasks
-  ===== kobalt-line-count =====
-    lineCount		Count the lines
-$ ./kobaltw lineCount
-Found 4972 lines in 65 files
-
- -

-And that's it! You can now iterate on your plug-in and upload it with additional ./kobaltw uploadJcenter. This plug-in is available on github. -

- -

Debugging

-

- 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: -

-
-fun main(argv: Array<String>) {
-    com.beust.kobalt.main(argv)
-}
-
-public class Main : BasePlugin() {
-// ...
-
- -

- Now you can simply create a launch configuration for your main class, which will invoke Kobalt. -

- -

- 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 - file() directive to declare your dependency, so that Kobalt will use the jar file - on your file system: -

- -
-val p = plugins(
-    file(homeDir("kotlin/kobalt-line-count/kobaltBuild/libs/kobalt-line-count-0.8.jar"))
-)
-
- -

- You can now set a breakpoint in your plug-in and launch the configuration you created above. -

-

Initialization

-

- When your plug-in is activated, Kobalt will invoke its apply() function: -

-
-override fun apply(project: Project, context: KobaltContext) {
-}
+    <project-contributors>
+        <class-name>com.beust.kobalt.plugin.java.JavaPlugin</class-name>
+        <class-name>com.beust.kobalt.plugin.kotlin.KotlinPlugin</class-name>
 

- project 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 context gives you some information about other external data you might find useful, such as the command line that was passed to Kobalt. +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.

+

Init contributors (IInitContributor)

+
+    <init-contributors>
+        <class-name>com.beust.kobalt.plugin.java.JavaBuildGenerator</class-name>
+        <class-name>com.beust.kobalt.plugin.kotlin.KotlinBuildGenerator</class-name>
+
+

+ Kobalt supports the --init 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... +

+

+ You can take a look at the IInitContributor 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. +

+ +

Repo contributors (IRepoContributor)

+
+    <repo-contributors>
+        <class-name>com.beust.kobalt.plugin.android.AndroidPlugin</class-name>
+
+

+ 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 ANDROID_HOME environment variable has been + defined, will automatically add the repository inside the Android distribution so that support libraries and other + artifacts can be found. +

+ +

Directives

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, as described here. diff --git a/ten-minutes/index.html b/ten-minutes/index.html new file mode 100644 index 0000000..8e3e30d --- /dev/null +++ b/ten-minutes/index.html @@ -0,0 +1,308 @@ + + + + + Kobalt, by Cedric Beust + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ + + + + +
+

Ten minutes

+

Write and publish a Kobalt plug-in in ten minutes

+ +
+ + +
+ +

Introduction

+

+ 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. +

+ +

+ Let's start by creating our project: +

+ +
+$ 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
+
+ +

+ I create an empty Main.kt in the example above so that calling ./kobaltw --init will detect the project as a Kotlin one. This way, the Build.kt 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 group, artifactId and version are correct. The only thing that the generator can't guess is the group, so let's go ahead and fix it: +

+ +
+val project = kotlinProject {
+    name = "kobalt-line-count"
+    group = "com.beust.kobalt"
+    artifactId = name
+    version = "0.1"
+    ...
+
+ +

+ Next, we want the manifest of our jar file to point to our main Kobalt plug-in class: +

+ +
+packaging {
+    jar {
+        manifest {
+            attributes("Kobalt-Plugin-Class", "com.beust.kobalt.plugin.linecount.Main")
+        }
+    }
+}
+
+ +

+ Now we're ready to code. +

+ +

+ Let's start by writing the simplest plug-in we can: +

+ +
+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")
+    }
+}
+
+ +

+ Before we can upload it, we need to create the package in bintray, as explained here. Once this is done, we are ready to do our first upload: +

+ +
+$ ./kobaltw uploadJcenter
+...
+========== kobalt-line-count:uploadJcenter
+  kobalt-line-count: Found 2 artifacts to upload
+  All artifacts successfully uploaded
+  ############# Time to Build: 3590 ms
+
+ +

+ 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: +

+ +
+jcenter {
+    publish = true
+}
+
+ +

+ And now, let's implement our logic, which is pretty simple: +

+ +
+// 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()
+}
+
+ +

+ We create a task called "lineCount" 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 log(), which is automatically supplied by the Kobalt API: +

+ +
+public class Main : BasePlugin() {
+
+ +

+ 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: +

+ + +
+$ ./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
+
+ +

+ 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: +

+ +
+val repos = repos("https://dl.bintray.com/cbeust/maven/")
+val plugins = plugins("com.beust.kobalt:kobalt-line-count:0.2")
+
+ +

+ Now let's launch a build: +

+ +
+$ ./kobaltw assemble
+...
+========== kobalt:lineCount
+Found 4972 lines in 65 files
+========== kobalt:compile
+...
+
+ +

+ Note that our plugin ran before the compile task, as we requested in the @Task 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: +

+ +
+$ ./kobaltw --tasks
+  ===== kobalt-line-count =====
+    lineCount		Count the lines
+$ ./kobaltw lineCount
+Found 4972 lines in 65 files
+
+ +

+ And that's it! You can now iterate on your plug-in and upload it with additional ./kobaltw uploadJcenter. This plug-in is available on github. +

+ +

Initialization

+

+ When your plug-in is activated, Kobalt will invoke its apply() function: +

+
+override fun apply(project: Project, context: KobaltContext) {
+}
+
+

+ project 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 context gives you some information about other external data you might find useful, such as the command line that was passed to Kobalt. +

+

+

+ +

Debugging

+

+ 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: +

+
+fun main(argv: Array<String>) {
+    com.beust.kobalt.main(argv)
+}
+
+public class Main : BasePlugin() {
+// ...
+
+ +

+ Now you can simply create a launch configuration for your main class, which will invoke Kobalt. +

+ +

+ 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 + file() directive to declare your dependency, so that Kobalt will use the jar file + on your file system: +

+ +
+val p = plugins(
+    file(homeDir("kotlin/kobalt-line-count/kobaltBuild/libs/kobalt-line-count-0.8.jar"))
+)
+
+ +

+ You can now set a breakpoint in your plug-in and launch the configuration you created above. +

+
+ + +
+
+ + + + + + + + + + + + +