diff --git a/documentation/index.html b/documentation/index.html index 398300e..d1bcb43 100644 --- a/documentation/index.html +++ b/documentation/index.html @@ -57,9 +57,7 @@
A universal build system for the exigent developer.
-A universal build system for the exigent developer.
+
+Download the zip file then unzip it in a location we'll call KOBALT_HOME
:
+
+cd $KOBALT_HOME +unzip kobalt-xxx.zip ++ +
+Change to your project directory and call the kobaltw
command with --init
:
+
+cd ~/java/project +$KOBALT_HOME/kobaltw --init ++ +
+This command will do two things: +
+ +kobalt/src/Build.kt
file based on what was found there.
+kobalt/
directory. From now on, you can just use ./kobaltw
to build and you can ignore $KOBALT_HOME
.
++You can now attempt to build your project with Kobalt: +
+ ++./kobaltw assemble ++ +If your project follows a regular build structure (e.g. Maven's hierarchy), this should compile your file and create a .jar file. If not, you will have to make a few edits to your
Build.kt
.
+
+As of this writing, Kobalt supports Java and Kotlin projects.
+
++import com.beust.kobalt.* +import com.beust.kobalt.plugin.kotlin.kotlinProject + +val kobalt = kotlinProject { + name = "kobalt" + group = "com.beust" + artifactId = name + version = "0.62" + directory = homeDir("kotlin/kobalt") +} ++ +
@@ -123,10 +129,6 @@ Kobalt is currently in Beta but I'm already using it to build most of my project
- Where to next? Are you interested in writing some cool Kotlin code and contribute to Kobalt? Or maybe just download and run Kobalt on your own projects just to get a feel for it? Then proceed to Kobalt's main documentation! -
-@@ -142,7 +144,7 @@ Kobalt is currently in Beta but I'm already using it to build most of my project - + + + + + + + + + + +
How to write a Kobalt plug-in.
++ 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-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.
+
How to write a Kobalt plug-in.
-- Kobalt plug-ins are usually made of several parts: - -
kotlinProject
or dependencies
. These functions typically configure some data that your plug-in will later use to perform its functions.- 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. -
- -
- 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:
-
-<kobalt-plugin> - <name>kobalt</name> - <plugin-actors> - <class-name>com.beust.kobalt.plugin.android.AndroidPlugin</class-name> - <class-name>com.beust.kobalt.plugin.java.JavaBuildGenerator</class-name> - </plugin-actors> -</kobalt-plugin> -- -
- IPluginActors
can be split in several categories:
-
@Task
annotations.
- All plug-in actors are interfaces that extend IPluginActor
. Plug-ins extend IPlugin
,
- interceptors extend IInterceptor
and contributors extend IContributor
. When Kobalt parses your
- kobalt-plugin.xml
, it instantiates all the classes found in the <plugin-actors>
tag
- and then introspects them to find out which IPluginActor
interfaces that class implements.
-
-
-
- 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.
-
-
- Here is a list of actors (contributors and interceptors) that you can define in your plug-in. -
-Interface name | -Type | -Description | - -
- IBuildConfigFieldContributor |
- IInterceptor |
-
- Plug-ins that want to add custom fields to the generated BuildConfig class.
- |
-
IBuildDirectoryInterceptor |
- IInterceptor |
- - Plug-ins that need to generate class files in a different directory than the default one should - implement this interface. - | -
IClasspathContributor
- |
- IContributor |
- Classpath contributors let you specify additional jar files or directories that will be used by
- the "compile" task.
- |
-
-
IClasspathInterceptor
- |
- IInterceptor |
- - Plug-ins that want to modify the classpath before Kobalt uses it should implement this interface. - | -
ICompilerContributor |
- IContributor |
- - Plug-ins that know how to turn files into bytecodes should implement this interface. - | -
ICompilerFlagContributor |
- IContributor
- |
- - Plug-ins that need to add flags to the compiler. - | -
ICompilerInterceptor |
- IInterceptor |
-
- Plug-ins that implement this interface get a chance to alter the dependencies of a project (dependencies{} , dependenciesTest{} , ...) before Kobalt sees them.
- |
-
IDocContributor |
- IContributor |
- - Plug-ins that know how to generate documentation out of source files should implement this interface. - | -
IInitContributor |
- IContributor |
- 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 implement this interface. 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...
- |
-
IProjectContributor |
- IContributor |
- Some plug-ins 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. | -
IRepoContributor |
- IContributor |
-
- 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.
- |
-
IRunnerContributor |
- IContributor |
-
- Plug-ins that can operate when the "run" task gets invoked should implement that interface.
- |
-
- ISourceDirectoryContributor |
- IContributor
- |
- - Plug-ins that add source directories. - | -
- ISourceDirectoryInterceptor |
- IInterceptor |
- - Plug-ins that want to add, remove or alter the source directories should implement this interface. - | -
- ITestRunnerContributor |
- IContributor |
-
- Plug-ins that can operate when the "test" task gets invoked should implement that interface.
- |
-
- ITestSourceDirectoryContributor |
- IContributor
- |
- - Plug-ins that add test source directories. - | -
- Kobalt itself uses a kobalt-plugin.xml
to define contributors and interceptors, here is
- an excerpt of it:
-
+
kotlinProject
or dependencies
. These functions typically configure some data that your plug-in will later use to perform its functions.
+ 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> <class-name>com.beust.kobalt.plugin.android.AndroidPlugin</class-name> - <class-name>com.beust.kobalt.plugin.java.JavaBuildGenerator</class-name> <class-name>com.beust.kobalt.plugin.kotlin.KotlinBuildGenerator</class-name> </plugin-actors> --
- In order to find out what these actually do, we just need to take a look at their definition and - see which interfaces they implement. For example: -
+ +
+ IPluginActors
can be split in several categories:
+
@Task
annotations.
+ All plug-in actors are interfaces that extend IPluginActor
. Plug-ins extend IPlugin
,
+ interceptors extend IInterceptor
and contributors extend IContributor
. When Kobalt parses your
+ kobalt-plugin.xml
, it instantiates all the classes found in the <plugin-actors>
tag
+ and then introspects them to find out which IPluginActor
interfaces that class implements.
+
+ If we look a the declarations of these classes, we can get an idea what they do +
class JavaPlugin : ICompilerContributor, IDocContributor {- -
- With this declaration, we know that the JavaPlugin
contributes a compiler and a doc generator.
-
+ With this declaration, we know that the JavaPlugin
contributes a compiler and a doc generator.
+
class JavaBuildGenerator: IInitContributor {-
- This class is declaring that it wants to take part in the --init
selection process, which is
- discussed in the next section.
-
- 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: -
+
+ This class is declaring that it wants to take part in the --init
selection process, discussed below.
+
+ Here is a list of actors (contributors and interceptors) that you can define in your plug-in. +
+Interface name | +Type | +Description | + +
+ IBuildConfigFieldContributor |
+ IInterceptor |
+
+ Plug-ins that want to add custom fields to the generated BuildConfig class.
+ |
+
IBuildDirectoryInterceptor |
+ IInterceptor |
+ + Plug-ins that need to generate class files in a different directory than the default one should + implement this interface. + | +
IClasspathContributor
+ |
+ IContributor |
+ Classpath contributors let you specify additional jar files or directories that will be used by
+ the "compile" task.
+ |
+
IClasspathInterceptor
+ |
+ IInterceptor |
+ + Plug-ins that want to modify the classpath before Kobalt uses it should implement this interface. + | +
ICompilerContributor |
+ IContributor |
+ + Plug-ins that know how to turn files into bytecodes should implement this interface. + | +
ICompilerFlagContributor |
+ IContributor
+ |
+ + Plug-ins that need to add flags to the compiler. + | +
ICompilerInterceptor |
+ IInterceptor |
+
+ Plug-ins that implement this interface get a chance to alter the dependencies of a project (dependencies{} , dependenciesTest{} , ...) before Kobalt sees them.
+ |
+
IDocContributor |
+ IContributor |
+ + Plug-ins that know how to generate documentation out of source files should implement this interface. + | +
IInitContributor |
+ IContributor |
+ 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 implement this interface. 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...
+ |
+
IProjectContributor |
+ IContributor |
+ Some plug-ins 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. | +
IRepoContributor |
+ IContributor |
+
+ 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.
+ |
+
IRunnerContributor |
+ IContributor |
+
+ Plug-ins that can operate when the "run" task gets invoked should implement that interface.
+ |
+
+ ISourceDirectoryContributor |
+ IContributor
+ |
+ + Plug-ins that add source directories. + | +
+ ISourceDirectoryInterceptor |
+ IInterceptor |
+ + Plug-ins that want to add, remove or alter the source directories should implement this interface. + | +
+ ITestRunnerContributor |
+ IContributor |
+
+ Plug-ins that can operate when the "test" task gets invoked should implement that interface.
+ |
+
+ ITestSourceDirectoryContributor |
+ IContributor
+ |
+ + Plug-ins that add test source directories. + | +
+ 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 { /** @@ -319,49 +277,45 @@ interface IProjectAffinity { */ fun affinity(project: Project, context: KobaltContext) : Int }-
- For example, the JavaPlugin implements the ICompilerContributor
interface and then overrides
- the affinity()
method to make sure it gets run for Java projects but ignored for others:
-
+ For example, the JavaPlugin implements the ICompilerContributor
interface and then overrides
+ the affinity()
method to make sure it gets run for Java projects but ignored for others:
+
override fun affinity(project: Project, context: KobaltContext) = - if (project.sourceSuffix == ".java") 1 else 0- - -
- 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. -
-
- Imagine that you want to offer a boolean parameter publish
to users of your plug-in, you start by creating a class to hold that parameter:
-
+ 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. +
+
+ Imagine that you want to offer a boolean parameter publish
to users of your plug-in, you start by creating a class to hold that parameter:
+
class Info(val publish: Boolean)-
- Next, you create a directive that returns such a class and which also allows to configure it via the type safe builder pattern: -
++ Next, you create a directive that returns such a class and which also allows to configure it via the type safe builder pattern: +
@Directive public fun myConfig(init: Info.() -> Unit) = Info().apply { init() }-
- The @Directive
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. The code above defines a myConfig
function that accepts a closure as an argument. It creates an Info
- object, calls the init()
function on it (which runs all the code inside that closure) and then return that Info
object.
-
- Users can now specify the following in their build file: -
+
+ The @Directive
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. The code above defines a myConfig
function that accepts a closure as an argument. It creates an Info
+ object, calls the init()
function on it (which runs all the code inside that closure) and then return that Info
object.
+
+ Users can now specify the following in their build file: +
// Build.kt-ort.com.example.plugin.myConfig - myConfig { publish = true }
- If you need access to the project being built, just declare an additional parameter of type Project
to your directive and have the user pass that project:
-
+ If you need access to the project being built, just declare an additional parameter of type Project
to your directive and have the user pass that project:
+
@Directive public fun myConfig(project: Project, init: Info.() -> Unit) : Info { @@ -372,10 +326,9 @@ myConfig(project) { publish = true }-
- 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 Plugins
registry and invoke whatever function you need to run:
-
+ 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 Plugins
registry and invoke whatever function you need to run:
+
@Directive public fun myConfig(init: Info.() -> Unit) = Info().apply { @@ -383,19 +336,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 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 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 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 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 { @@ -403,100 +356,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:
-
kobaltw
command.
- 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 exampleTask |
- Result | - - -||||||||||||
runBefore = "compile" | -
+
+ A Kobalt task needs to accept a
+ The
+ The difference between
+ For example,
+ 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
+ Here are a few different scenarios to illustrate how the three attributes work for the task
+ Result of the command
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 |
+
+ 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> }-
- For example: -
++ For example: +
override fun tasksFor(context: KobaltContext) = listOf( DynamicTask( @@ -506,16 +455,16 @@ override fun tasksFor(context: KobaltContext) = listOf( closure = { project: Project -> 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 ===== @@ -524,76 +473,67 @@ $ ./kobaltw --tasks $ ./kobaltw dynamicTask Running dynamictask- -
- 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
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 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
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) --
- 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:
-
+ 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 context.pluginProperties.put(PLUGIN_NAME, "somePluginProperty", "someValue") - // Read a property from another plug-in val sourceDir = context.pluginProperties.get("pluginName", "somePluginProperty") }- -
- Plug-ins that define properties should annotate them with the @ExportedPluginProperty
or
- @ExportedProjectProperty
annotation:
-
+ Plug-ins that define properties should annotate them with the @ExportedPluginProperty
or
+ @ExportedProjectProperty
annotation:
+
companion object { @ExportedProjectProperty const val BUILD_DIR = "buildDir"- -