Introduction


This document stands for reference guide and provides details about Jerkar behaviour. If you are new to Jerkar you may find other places to learn about Jerkar :

  • To have an overall vision of Jerkar, please have a tour.

  • To get a step-by-step learning guide, please visit Getting Started.

If you are looking for how exactly Jerkar behaves or you want to get a pretty exhaustive list of Jerkar features, you are in the right place.

However, a document can not replace a source code or API for exhaustion, so please consult javadoc or browse source code to get deeper knowledge.

Also, you're welcome to raise an issue in Git-hub for requesting an improvement on documentation.

Lexical

These terms are used all over the document, this lexical disambiguates their meanings.

[PROJECT DIR] : refers to the root folder of the project to build (the one where you would put pom.xml or build.xml file using ANT or Maven).

[JERKAR HOME] : refers to the folder where is intalled Jerkar. You should find jerkar.bat and jerkar shell files directly under this folder.

Build Class : These are files that define build for a given project. In Jerkar, those files are Java sources (.java files) located under [PROJECT DIR]/build/def directory and extending org.jerkar.tool.JkBuild.
This term can also be use to designate the compiled build class (.class files) as this class is generated transparently by Jerkar.

In general, there is a single build class by project, but it can have none (if your project embrace conventions strictly) or many if project developers estimates that make sense.
[PROJECT DIR]/build/def directory may contain other utilities classes and files consumed by build classes.

Jerkar Options : This is a set of key-value used to inject parameters in builds. The options can be mentioned as command line arguments or stored in specific files.
The section Build Configuration details about Jerkar options.

Jerkar Runtime


This section details what happens behind the cover when Jerkar is run.

Launching Java Process

Jerkar is a pure Java application requiring JDK 6 or above. JDK is required and JRE is not sufficient.
Indeed Jerkar uses the JDK tools to compile java source files located under [PROJECT DIR]/build/def.

To ease launching Java process in command line, Jerkar provides native scripts ( jerkar.bat for Windows and jerkar for Unix ).
These scripts do the following :

  1. Find the java executable path : If a JAVA_HOME environment variable is defined then it takes its value as java path. Otherwise it takes the java executable defined in the PATH of your OS.
  2. Get java execution option : If an environment variable JERKAR_OPTS exists then its value is passed to the java command line parameters, otherwise default -Xmx512m -XX:MaxPermSize=512m is passed.
  3. Set Jerkar classpath in the following order :
    • all jar and zip files found under [WORKING DIR]/build/boot
    • all jar and zip files found under [JERKAR HOME]/libs/ext
    • the [JERKAR_HOME]/org.jerkar.core.jar file
  4. Run the org.jerkar.tool.Main class passing the command line argument as is. So if you have typed jerkar myArg1 myArg2 the myArg1 myArg2 will be passed as Java command-line arguments.

Embedded Mode

Note that [JERKAR_HOME]/org.jerkar.core-all.jar comes after [WORKING_DIR]/build/boot/* in the classpath.
This means that if a version of Jerkar (org.jerkar.core.jar) is in this directory, the build will be processed with this instance of Jerkar and not with the one located in in [JERKAR HOME].

This is called the Embedded mode. It guarantees that your project will build regardless of Jerkar version installed on the host machine.
This mode allows to build your project even if Jerkar is not installed on the host machine.

Enable embedded mode

To enable embedded mode :
* Copy [JERKAR_HOME]/org.jerkar.core-all.jar into [PROJECT_DIR]/build/boot/* directory.
* Copy [JERKAR_HOME]/jerkar.bat and [JERKAR_HOME]/jerkar at the root of [PROJECT_DIR].

You can also achieve this by invoking jerkar scaffold -scaffoldEmbed.
This will generate jerkar.bat and jerkar file at the root of [PROJECT_DIR] and copy [JERKAR_HOME]/org.jerkar.core-all.jar to [PROJECT_DIR]/build/boot.

Run in embedded mode

Once a project enables embedded mode, all Jerkar command will run in that mode, there is nothing special to do.
If you don't want to make in enables by default, remove jerkar.bat and jerkar from [PROJECT_DIR]. In this case, to run in embedded mode you need to usejava -cp build/boot/* org.jerkar.tool.Main instead of jerkar in the command line.

Jerkar Execution

The org.jerkar.tool.Main#main is the entry point of Jerkar. This is the method you invoke to launch or debug a Jerkar build within your IDE.

It processes as follow :

  1. Parse the command line.
  2. Populate system properties and Jerkar options from configuration files and command line (see build configuration).
  3. Pre-process and compile java source files located under under [PROJECT DIR]/build/def (see Build Definition Compilation).
  4. Instantiate the build class (see Build Class Instantiation)
  5. Inject options in build instance fields (see Build Configuration).
  6. Instantiate and configure plugins specified in command line arguments (see Mention Plugins in the Command Line).
  7. Invoke methods specified in command line arguments : methods are executed in the order they appear on the command line.

Command Line

Jerkar parses the command line and processes each arguments according this pattern :

  • Argument starts with @ : This is a module import clause, the following will be used for adding a jar to the build classpath. For example if the command line contains @org.jerkar:addin-spring-boot:1.3.1, the build class will be run with the spring-boot-addin on its classpath.

  • Argument starts with - : This is an option declaration. The following is expected to be formatted as optionName=optionValue. For example, `-repo.build.url=http://my.repo.milestone/' will inject 'http://my.repo.milestone/' in the 'repo.build.url' Jerkar option.

  • in the other cases, argument is considered as a method name to invoke on the build class instance.

Build class Compilation

Jerkar compiles build class files prior to execute it. Build class files are expected to be in [PROJECT DIR]/build/def. If this directory does not exist or does not contains java sources, the compilation is skipped.
Compilation outputs class files in [PROJECT DIR]/build/output/def-bin directory and uses classpath containing :

  • Java libraries located in [PROJECT DIR]/build/libs/build.
  • Java libraries located in [JERKAR HOME]/libs/ext.

You can augment classpath with :

  • Java libraries hosted on a Maven or Ivy repositories
  • Java libraries located on file system.
  • Build definition (java sources) of other projects

Information about extra lib to add to classpath are located in the build classes, inside @JkImport and @JkProject annotation.
This information is read by parsing java source files, prior they are compiled.

Libraries Located on Maven/Ivy Repository

To add libraries from Maven/Ivy repository you need to annotate the build definition with @JkImport. This annotation takes an array of String as its default parameter so you can specify several dependencies.
The mentioned dependencies are resolved transitively.

@JkImport(`{"commons-httpclient:commons-httpclient:3.1", "com.google.guava:guava:18.0"})
public class HttpClientTaskBuild extends JkJavaBuild {`
...

Url of the maven/ivy repositories is given by repo.build.url Jerkar option. If this option is not set, then it takes the url given by repo.download.url option. If the last is nor present as well, it falls back in Maven Central.
If this repository needs credentials, you need to supply it through Jerkar options repo.build.username and repo.build.password.

Note that you can define several repo.build.url by separating then with coma (as repo.build.url=http://my.repo1, http://my.repo2.snapshot).

As for other repo, If the download repository is an Ivy repo, you have to prefix url with ivy: so for example you'll get repo.build.url=ivy:file://my.ivy/repo.

Libraries on File System

To add library from file system you need to annotate the build definition with @JkImport. This annotation takes an array of String as argument so you can specify several dependencies.
The mentioned dependencies are not resolved transitively.
The expected value is a Ant include pattern applied to the project root directory.

@JkImport({"commons-httpclient:commons-httpclient:3.1", "build/libs/compile/*.jar"})
public class HttpClientTaskBuild extends JkJavaBuild {`
...

This will include commons-httpclient and its dependencies in the classpath along all jar file located in [PROJECT DIR]/build/libs/compile.

Build Definitions of other project

Your build definitions can depends on build definitions of other projects. It is typically the case for multi-project builds.
This capability allows to share build elements in a static typed way as the build definitions files can consume classes coming from build definitions of other projects.

@JkProject is an annotation that applies on fields instance of org.jerkar.tool.JkBuild or its subclasses. This annotation contains the relative path of the consumed project.
If the project build definition sources contain some @JkProject annotations, build class of the consumed project are pre-processed and compiled recursively.
Classes and classpath of the consumed project are added to the build definition classpath of the consumer project.

public class DistribAllBuild extends JkBuildDependencySupport {
	
	@JkProject("../org.jerkar.plugins-sonar")
	PluginsSonarBuild pluginsSonar;
	
	@JkProject("../org.jerkar.plugins-jacoco")
	PluginsJacocoBuild pluginsJacoco;
	
	@JkDoc("Construct a distrib assuming all dependent sub projects are already built.")
	public void distrib() {
		
		JkLog.startln("Creating distribution file");
		
		JkLog.info("Copy core distribution localy.");
		CoreBuild core = pluginsJacoco.core;  // The core project is got by transitivity
		File distDir = this.ouputDir("dist");
		JkFileTree dist = JkFileTree.of(distDir).importDirContent(core.distribFolder);
		...

Build Class Instantiation

Once build class compiled. Jerkar instantiate the build it.
Build class is specified by the buildClass option if present. If not, it is the first class implementing org.jerkar.tool.JkBuild.
If no class implementing org.jerkar.tool.JkBuild is found then the org.jerkar.tool.builtins.javabuild.JkJavaBuild is instantiated.

The class scanning processes classes in alphabetic order then sub-package in deep first. This mean that class MyBuid will be scanned prior apackage.ABuild, and aa.bb.MyClass will be scanned prior ab.OtherClass.

The buildClass option can mention a simple name class (class name omitting its package). If no class matches the specified buildClass then an exception is thrown.

The org.jerkar.tool.JkBuild constructor instantiate fields annotated with @JkProject. If a project build appears many time in the annotated project tree, a single instance is created then shared.

Setting paths

Specify Jerkar user home

Jerkar uses user directory to store user-specific configuration and cache files, in this document we refer to this directory using [Jerkar User Home].
By default the this directory is located at [User Home]/.jerkar (_[User Home]_ being the path given by System.getProperty("user.home");.
You can override this setting by defining the JERKAR_USER_HOME environment variable.
You can programatically get this location in your build definition using JkLocator.jerkarUserHome().

Specify the local repository cache

Jerkar uses Apache Ivy under the hood to handle module dependencies. Ivy downloads and stores locally artifacts consumed by projects.
By default the location is [JERKAR USER HOME]/repo-cache but you can redefine it by defining the JERKAR_REPO environment variable.
You can programatically get this location in your build definition using JkLocator.jerkarRepositoryCache().

See the effective paths

The Jerkar logs displays the effective path at the very start of the process :

 _______           _                 
(_______)         | |                
     _ _____  ____| |  _ _____  ____ 
 _  | | ___ |/ ___) |_/ |____ |/ ___)
| |_| | ____| |   |  _ (/ ___ | |    
 \___/|_____)_|   |_| \_)_____|_|
                                     The 100% Java build tool.

Java Home : C:\UserTemp\I19451\software\jdk1.6.0_24\jre
Java Version : 1.6.0_24, Sun Microsystems Inc.
Jerkar Home : C:\software\jerkar
Jerkar User Home : C:\users\djeang\.jerkar
Jerkar Repository Cache : C:\users\djeang\.jerkar\cache\repo
...

Build Configuration


Jerkar builds are configurable. Build definition classes can retrieve values defined at runtime by reading :

  • environment variables
  • system properties
  • Jerkar options

Environment variables

There is nothing specific to Jerkar. Just set the environment variable as you usually do on your OS and get the value from build using the standard System#getenv method.

System properties

Naturally, your build definitions can read system properties by using the standard System#getProperty method.

Jerkar proposes 3 ways to inject system properties :

  • By editing [Jerkar Home]/system.properties file.
    Note that if you are running Jerkar in embedded mode, the [Jerkar Home]/system.properties file will not be taken in account but [project dir]/build/def/build/system.properties.
  • By editing [Jerkar User Home]/system.properties file.
  • By mentioning the property/value in Jerkar command line as Jerkar doDefault -DmyProperty=myValue.

The command line takes precedence on [Jerkar User Home]/system.properties that in turn, takes precedence on [Jerkar Home]/system.properties.

In every case, defined system properties are injected after the creation of the java process (via System#setProperty method).

Jerkar options

Jerkar options are similar to system properties as it stands for a set of key/value. You can read it by using a dedicated API or let it be injected in Java field as explained below.

Injecting options

Jerkar proposes 3 ways to inject options :

  • By editing [Jerkar Home]/options.properties file.
    Note that if you are running Jerkar in embedded mode, the [Jerkar Home]/options.properties file will not be taken in account but [project dir]/build/def/build/options.properties.
  • By editing [Jerkar User Home]/options.properties file.
  • By mentioning the property/value in the Jerkar command line as Jerkar doDefault -myOption=myValue.

As for system properties, The command line takes precedence on [Jerkar User Home]/options.properties that takes in turn, precedence on [Jerkar Home]/options.properties.

Note for boolean, when no value is specified, true will be used as default.

Retrieve Jerkar options

You can retrieve string values using the JkOptions API providing convenient static methods as JkOptions#get, JkOptions#getAll or JkOptions#getAllStartingWith(String prefix).

You can also retrieve options just by declaring fields in build definition class.
All non private instance fields of the build definition class, are likely to be injected as an option.

For example, if you declare a field like protected int size = 3; then you can override the default value by injecting the option value with any of the 3 ways described above.

Any fields except static fields or private fields can be used to inject options.
If you want inject option in a private field, you must annotate it with @JkDoc as @JkDoc private boolean myField;

Note that the injected string value will be automatically converted to the target type.
Handled types are String, all primitive types (and their wrappers), enum, File and composite object.
To get a precise idea on how types are converted see this code.

Composite options

Composite options are a way to structure your options. Say that you want to configure some server access with url, userName and passwsord,
you can gather all these information in a object as

class Server {
    private String url;
    private String userName;
    private String password;
    // ...
}

declare a Server field in your build :

class MyBuild extends JkBuild {
   Server deployServer;
   ...
}

Then you can inject the server object using following options :

deployServer.url=http:/myServer:8090/to
deployServer.username=myUsername
deployServer.password=myPassword

Standard options

Jerkar predefines some standard options that you can set for any build :

  • buildClass : This forces the build class to use. If this option is not null then Jerkar will used the specified class as the build class.
    Note that this can be a simple class as MyBuildClass is enough for running org.my.project.MyBuildClass.
  • verbose : when true Jerkar will be more verbose at logging at the price of being slower and bloating logs. Default value is false.
  • silent : when truenothing will be logged. Default is false

How to document options ?

If you want your option been displayed when invoking jerkar help you need to annotate it with @JkDoc.

For example :

@JkDoc("Make the test run in a forked process")
private boolean forkTests = false;

Master/Slave Projects


Jerkar proposes 2 ways to deal with multi-project builds :

  • By using computed dependencies (see Dependency Managemment).
  • By defining slave builds. This sections focus on this way.

Principle

A build class (master build) declares its slave builds. The slave builds can be triggered individually or all-in-one from the master build.
The slave builds are not aware they are slave. In fact any build can be used as slave. The relation is uni-directional

JkBuild defines a method #slaves() returning the slaves of its instances (embodied as org.jerkar.tool.JkSlaveBuilds). Naturally this result is recursive as it contains slaves of the slaves and so on ...

From this result you can invoke a method for all slaves as slaves().invokeOnAll("clean"). The iteration order ensure that an invokation on a build can not be done until all its slaves has been invoked first.

Also from the command line you can invoke a method or set an option either for the master build only or for the master builds along all its slaves.

Declare Slave Builds

By default the #slaves() method returns all the JkBuild instance declared as field and annotated with @JkProject. You can modify this behavior by overriding this method.

This is an example of how to declare external build with @JkProject annotation.

public class DistribAllBuild extends JkBuildDependencySupport {
	
	@JkProject("../org.jerkar.plugins-sonar")
	PluginsSonarBuild pluginsSonar;
	
	@JkProject("../org.jerkar.plugins-jacoco")
	PluginsJacocoBuild pluginsJacoco;
	

Invoke Slave Methods from Master Build Code

To invoke methods on all slaves you can use the JkSlaveBuilds#invokeOnAll() method from the instance returned by JkBuild#slaves().

To invoke methods on a single slave, you can just invoke the method on the build instance as pluginsJacoco.clean().

Invoke Slave Builds from Command Line.

When mentioning a method on the command line, it only applies to the master build.

If you want this method to be executed on the slave build as well, you must append a * at the end of the method name as jerkar doSomething*.

If a slave build do not have such a method, the build does not fail but warns it.

Configure Slave Builds from command line.

When mentioning an option on the command line, only the master build try to inject its corresponding field with the option value.

If you want to inject option field on the slave build as well, just append a * at the end of the option declaration as jerkar aField=myValue*.

If a build don't have such field, the injection simply does not happen and the build does not fail.

Note that JkOptions class is shared among master and slave builds so slave builds can have access to master options by using its static methods.

Dependency Management


What is a dependency ?

In build context, a dependency is an indication that can be resolved to a file (or a set of file) needed to accomplish certain part of the build.
So for example if a project Foo has a dependency bar, this means that Foo may need the files indicated by bar for building.
In Jerkar code, the dependency concept is embodied by the abstract JkDependency class.

Jerkar distinguishes 3 types of dependency :

  • Arbitrary files located on the file system (Embodied by JkFileSystemDependency class). These files are assumed to be present on the file system when the build is running.
  • Files produced by a computation (Embodied by JkComputedDependency class). These files may be present on file system or not. If they are not present, the computation is run in order to produce the missing files. Generally the computation stands for the build of an external project.
  • Reference to module (Embodied by JkModuleDependency) hosted in a binary repository (Ivy or Maven for instance) : Jerkar can consume and resolve transitively any artifact located in a repository as you would do with Maven or Ivy.

For the last, Jerkar is using Ivy 2.4.0 under the hood. The library is embedded in the Jerkar jar and is executed in a dedicated classloader. So all happens as if there where no dependency on Ivy.

What is a scope ?

Projects may need dependencies to accomplish certain tasks and needed dependencies may vary according the executed tasks.
For example, to compile you may need guava library only but to test you'll need junit library as well.
To label dependencies according their usage, Jerkar uses the notion of scope (embodied by JkScope class). This notion is similar to the Maven scope.

A scope can inherit from one or several scopes. This means that if a scope Foo inherits from scope Bar then a dependencies declared with scope Bar will be also considered as declared with scope Foo.
For instance, in JkJavaBuild, scope TEST inherits from RUNTIME that inherits from COMPILE so every dependencies declared with scope COMPILE are considered to be declared with scope RUNTIME and TEST as well.

By default, scopes are transitive. This has only a meaning for reference to module.
If we have 3 modules having the following dependency scheme : A -> B -> C and the A-> B dependency is declared with a non transitive scope, then A won't depend from C.

Projects consuming artifacts coming from Ivy repository can also use JkScopeMapping which is more powerful. This notion maps strictly to the Ivy configuration concept.

Define a set of dependencies

To define a set dependencies (typically the dependencies of the project to build), you basically define a list of scoped dependency (embodied by JkScopedDependency). A scoped dependency is a dependency associated with zero, one or several scopes.

Practically, you define some scopes then you bind dependencies to these scopes.

The set of dependency concept is embodied by JkDependencies class. This class provides builder for easier instantiation.

return JkDependencies.builder()
    .on(GUAVA, "18.0").scope(COMPILE)  
    .on(JERSEY_SERVER, "1.19").scope(COMPILE)
    .on("com.orientechnologies:orientdb-client:2.0.8").scope(COMPILE)
    .on(JUNIT, "4.11").scope(TEST)
    .on(MOCKITO_ALL, "1.9.5").scope(TEST, ANOTHER_SCOPE)
.build();

You can also omit the scope and set it later...

JkDependencies deps = JkDependencies.builder()
    .on(GUAVA, "18.0")
    .on(JERSEY_SERVER, "1.19")
    .on("com.orientechnologies:orientdb-client:2.0.8")
    .on(JUNIT, "4.11").scope(TEST)
    .on(MOCKITO_ALL, "1.9.5").scope(TEST, ANOTHER_SCOPE)
.build();
...
deps = deps.withDefaultScope(COMPILE);

Here, both GUAVA and JERSEY_SERVER will be declared with COMPILE scope.

If you don't specify scope on a module and you don't set default scope, then at resolution time the dependency will be considerer as binded to every scope.

Note : Instances of JkDependencies can be added to each other. Look at the JkDepencies class API for further details.

Define scopes

In the examples above, we use the predefined scopes COMPILE or TEST. These scopes are standard scopes defined on the JkJavaBuild class.
So if your build definition class inherit from JkJavaBuild template you won't need to create it.

If you need to create your own scope, a good practice is to declare it as java constant (static final) as it will be reusable anywhere all over your build definition.

As an example, these are the scopes declared in JkJavaBuild :

public static final JkScope PROVIDED = JkScope.of("provided").transitive(false)
    .descr("Dependencies to compile the project but that should not be embedded in produced artifacts.");

public static final JkScope COMPILE = JkScope.of("compile")
    .descr("Dependencies to compile the project.");

public static final JkScope RUNTIME = JkScope.of("runtime").extending(COMPILE)
	.descr("Dependencies to embed in produced artifacts (as war or fat jar files).");

public static final JkScope TEST = JkScope.of("test").extending(RUNTIME, PROVIDED)
	.descr("Dependencies necessary to compile and run tests."); 

Defining different type of dependencies

This section describes how to declare different types of dependencies.

Dependencies on local files

You just have to mention the path of one or several files. If one of the files does not exist at resolution time (when the dependency is actually retrieved), build fails.

    @Override
    protected JkDependencies dependencies() {
        final File depFile1 = new File("/my/file1.jar");  // file with absolute path
    	final File depFile2 = file("zips/file2.zip");  // file related to project root dir
        return JkDependencies.builder()
            .on(depFile1, depFile2, file("libs/my.jar")).build();
    }
		
Dependencies on files produced by computation

It is typically used for multi projects builds projects.

The principle is that if the specified files are not found, then the computation is run in order to generate the missing files.
If some files still missing after the computation has run, the build fails.

This mechanism is quite simple yet powerful as it addresses following use cases :

  • Dependencies on files produced by other Jerkar project.
  • Dependencies on files produced by external project built with any type of technology (Ant, Grunt, Maven, Gradle, SBT, Android SDK, Make, ...).
  • Dependencies on files produced by a method of the main build.

The generic way is to construct this kind of dependency using a java.lang.Runnable.

private Runnable computation = new Runnable() {...}; 
	
File fooFile = new File("../otherproject/target/output/foo.jar");  // dependency file  
	
@Override
protected JkDependencies dependencies() {
return JkDependencies.builder()
    .on(JkComputedDependency.of(computation, fooFile)).build();
}

Here, if the fooFile is absent then the computation will be run prior to retry to find FooFile.

Jerkar provides some shortcuts to deal with other Jerkar projects : For this, you can create the dependency directly from the slave build instance.

@JkProject("../foo")          // The external project path relative to the current project root
public JkJavaBuild fooBuild;  // This build comes from 'foo' project 
	
@Override
protected JkDependencies dependencies() {
    return JkDependencies.builder()
	    .on(fooBuild.asComputedDependency("doPack", fooBuild.packer().jarFile() ))
    .build();
}

Here the method doPack of fooBuild will be invoked if the specified file does not exist.
See Multi Module Project to get details how parameters are propagated to slave builds.

You can also use another kind of project mentioning the command line to run in order to build the project.

File fooDir = new File("../../foo");  // base dir of a Ant project 
File fooJar = new File(fooDir, "build/foo.jar");
JkProcess antBuild = JkProcess.of("ant", "makeJar").withWorkingDir(fooDir));
...
@Override
protected JkDependencies dependencies() {
    return JkDependencies.builder()
        .on(JkProjectDependency.of(antBuild, fooJar)).scope(PROVIDED)  
    .build();
}

Here, if fooJar file does not exist, ant makeJar command line is invoked prior to retry to find the file.
If the file still does not exist then the build fails.

Dependencies on Module

This is for declaring a dependency on module hosted in Maven or Ivy repository. Basically you instantiate a JkModuleDepency from it's group, name and version.

...	
@Override  
protected JkDependencies dependencies() {
    return JkDependencies.builder()
        .on(GUAVA, "18.0")
        .on("com.orientechnologies:orientdb-client:2.0.8")
        .on("my.group:mymodule:0.2-SNAPSHOT")
	.build();
}
...   

There is many way to indicate a module dependency, see Javadoc for browsing possibilities.

Note that a version ending by -SNAPSHOT has a special meaning : Jerkar will consider it "changing". This means that it won't cache it locally and will download the latest version from repository.

Dependencies on Dynamic Versions

Jerkar allows to specify a version range, for example, the following is legal :

...	
@Override  
protected JkDependencies dependencies() {
    return JkDependencies.builder()
        .on(GUAVA, "16.+")
        .on("com.orientechnologies:orientdb-client:[2.0.8, 2.1.0[")
	.build();
}
...   

As Jerkar relies on Ivy under the hood, you can use any expression mentioned (here) [http://ant.apache.org/ivy/history/latest-milestone/ivyfile/dependency.html].

Specifying Maven Classifier and extension of the artifact

Maven or Ivy module dependencies need to be downloaded from a binary repository. This could be a managed repository (as Nexus or Artifactory), simple file system repo or a combination of any.

...	
@Override 
protected JkDependencies dependencies() {
    return JkDependencies.builder()
        .on("my.group:mymodule:1.0.1:jdk15")
	.build();
}
...   

You can also precise the extension of the artifact :

...	
@Override 
protected JkDependencies dependencies() {
    return JkDependencies.builder()
        .on("my.group:mymodule:1.0.1:jdk15@zip")
        .on("my.group:otherModule:1.0.15@exe")
	.build();
}
...   
Choose the binary repository where to download your dependencies

If use JkBuildDependencySupport template, or one of its subclass as JkBuildJava, the default is to use the repository mentioned in your JkOptions :
- repo.download.url : the url of the download repository, default is Maven central :http://repo1.maven.org/maven2`.
- repo.download.username : the username credential to access to the repository (optional). Default is null cause Maven central does not require authentication.
- repo.download.password : the password credential to access to the repository (optional). Default is null cause Maven central does not require authentication.

If the repository is an Ivy one, you should prefix the url with ivy: as ivy:/my/shared/drive/repo

repo.download.url=ivy:http://my/ivy/repo
repo.download.username=myIvyUsername
repo.download.password=myIvyPassword

You can also define it programmatically for richer and more flexible options:

protected JkPublishRepos publishRepositories() {
    return JkPublishRepos.of(
        JkRepo.maven("http://my.snapshot.repo").asPublishSnapshotRepo())
            .and( 
        JkRepo.maven("http://my.release.repo").asPublishReleaseRepo());
}
What happen behind the hood ?

Jerkar uses Apache Ivy under the hood to resolve/fetch module dependencies.
Ivy is invisible to the user except in some log output.
The dependencies are downloaded in local cache located at [JERKAR USER DIR]/cache/repo.
You can override this setting by defining the JERKAR_REPO environment variable.

Bind Dependencies to Scopes

The whole project dependency description lie in a single instance of JkDependencies. This class offers convenient factory methods and builder to define the dependencies.

Simple scopes

You can bind any kind of dependency to on one or several scopes as :

private static final JkScope FOO = JkScope.of("foo"); 

private static final JkScope BAR = JkScope.of("bar"); 

protected JkDependencies dependencies() {
		return JkDependencies.builder()
		    .on(file("libs/foo3.jar")).scope(BAR)  
		    .on(file("libs/foo1.jar")).scope(BAR, FOO)  
			.on("com.foo:barcomp", "1.19").scope(BAR, FOO)  
			.on("com.google.guava:guava, "18.0")
		.build();
}

When the dependency is a module dependency, transitive resolution comes in play and more subtle concepts appear.
For resolving module dependency Jerkar uses Ivy under the cover and scopes are translated to Ivy configurations.

So the above module dependencies are translated to Ivy equivalent :

...
<dependency org="org.foo" name="barcomp" rev="1.19" conf="bar;foo"/>
<dependency org="com.google.guava" name="guava" rev="18.0"/>

Scope Mapping

You can also specify a scope mapping (aka Ivy configuration mapping) for module dependencies :

protected JkDependencies dependencies() {
		return JkDependencies.builder()
			.on("com.foo:barcomp", "1.19")
				.mapScope(COMPILE).to(RUNTIME, BAR)
				.and(FOO, PROVIDED).to("fish", "master(*)")
		.build();
}

So the above module dependencies are translated to Ivy equivalent :

...
<dependency org="org.foo" name="barcomp" rev="1.19" conf="compile->runtime,bar; foo->fish,master(*); provided->fish,master(*)"/>

Default Scope Mapping

The way transitive dependencies are actually resolved depends on the JkDependencyResolver used for resolution.
Indeed you can set default scope mapping on the resolver, through JkResolutionParameter. This setting ends at being translated to respectively Ivy configuration mapping.
This page explains how Ivy configurations works.

Excluding Module from the Dependency Tree

When resolving dependency transitively you may grab unwanted dependencies. To filter them out you can exclude them from the tree using appropriate methods.

final JkDependencies deps = JkDependencies.builder()
    .on("org.springframework:spring-context:4.2.1.RELEASE")
    .on("org.hibernate:hibernate-core:4.3.7.Final").excludeLocally("dom4j","dom4j")
    .excludeGlobally("antlr", "antlr")
    .excludeGlobally("org.jboss.logging", "*").build();

#excludeLocally apply only to the module previously declared. So here, dom4j excludes will apply only for hibernate-core dependency. This means that if dom4j is a transitive dependency of hibernate-core then transitive resolution will stop at dom4j. If spring-context as a dependency on dom4j (direct or transitive) then the result will include dom4j along its dependencies as it has not been excluded from spring-context dependency.

#excludeGlobally acts on the global result. If any of the declared dependencies have a dependency on antlr then this lib (and its dependencies) won't be part of the result.

Note : You can use wild-card or regular expressions for both group and artifact name. In this case all matching dependencies will be excluded.

Displaying dependency tree

You can display the resolved dependency tree by running jerkar showDependencies. If you want to retrieve it programmatically, the tree structure is obtained using JkDependencyResolver#resolve() which returns a JkResolveResult. In turn, this object contains a JkDependencyNode standing for the dependency tree root.

Publication on binary repositories


Jerkar is able to publish on both Maven and Ivy repository. This includes repositories as Sonatype Nexus or Jfrog Artifactory.

Maven and Ivy have different publication model, so Jerkar proposes specific APIs according you want to publish on a Maven or Ivy repository.

Publish to a Maven repository

Jerkar proposes a complete API to pubish on Maven repository. POM files will be generated by Jerkar according
elements provided by users.

Using raw API

You can set minimal information in order to deploy a file on a Maven repository as below :

File fileToPublish = new File("build/output/myFile.jar");
JkPublishRepo repo = JkRepo.maven("http://my.mvn.repo/publisher").asPublishRepo()
JkVersionedModule versionedModule = JkVersionedModule.of("myGroup:myName", "0.2.1");
JkPublisher.of(repo).publishMaven(versionedModule, JkMavenPublication.of(fileToPublish), JkDependencies.on());

You can also set additional information in order to :

  • Publish more than one artifact.
  • Produce & publish checksum files for each published artifact.
  • Mention if you wish or not to use unique snapshot (What is it ?).
  • Feed generated pom with data necessary to publish on central repository.
  • Sign published artifact with PGP

Not that for signing with PGP, you don't need to have PGP installed on Jerkar machine. Jerkar uses Bouncy Castle internally to sign artifacts.

// You can sign your artifatcs with PGP
JkPgp pgp = JkPgp.ofSecretRing(new File("/usr/myName/pgp/privateRing"), "myPgpPhrase");

JkPublishRepo repo = JkRepo.maven(publishRepo)
JkPublishRepo repo = JkRepo.maven(publishRepo)
    .withCredential("myRepoUserName", "myRepoPassword").asPublishRepo()
    .withUniqueSnapshot(false)
    .withSigner(pgp)
    .andSha1Md5Checksums(); // You can checksum each of published artifacts
			
JkVersionedModule versionedModule = JkVersionedModule.of("myGroup:myName", "0.2.1");
		
// Optinal : if you need to add metadata in the generated pom
JkMavenPublicationInfo info = JkMavenPublicationInfo
    .of("my project", "my description", "http://myproject.github")
    .withScm("http://scm/url/connection")
    .andApache2License()
    .andGitHubDeveloper("myName", "myName@provider.com");				
		
// Optional : if you want publish sources
File srcZip = ouputDir("src.zip");
JkZipper.of(this.src).to(srcZip);
		
JkMavenPublication publication = JkMavenPublication.of(jarFile).with(info).and(srcZip, "sources");
JkPublisher.of(repo).publishMaven(versionedModule, publication, JkDependencies.on());

Using JkJavaBuild template

If your build class inherit from JkBuildJava template, the effort you must produce to publish artifacts is minimal or zero if you don't need special feature.
The prerequisite is to setup options.properties according to your infrastructure.

Using defaults

If you don't specify anything the publication will occurs locally at : [JERKAR USER HOME]/maven-publish-dir

If you specify

repo.publish.url=http://my.repository/location
repo.publish.username=myUsername (optional)
repo.publish.password=myPassword (optional)

Jerkar will use this repository to publish your all your artifacts (snapshots and releases).

If you specify

repo.publish.url=http://my.repository/location
repo.publish.username=myUsername (optional)
repo.publish.password=myPassword (optional)

repo.publishRelease.url=http://my.repository/release/location
repo.publishRelease.username=myUsername (optional)
repo.publishRelease.password=myPassword (optional)

Jerkar will publish snapshots on _http://my.repository/location_ and releases on _http://my.repository/release/location_.

Using a pool of repositories

You can define a pool of repositories in your options ([JERKAR USER DIR]/options.properties) so that
you can refer only to the repository name to point on a repository.

repo.myRepo1.url=https://my.nexus/repo1

repo.myRepo2.url=https://my.nexus/repo2
repo.myRepo2.username=usernameRepo2
repo.myRepo2.password=repo2

repo.myRepo3.url=https://my.nexus/repo3
repo.myRepo3.username=usernameRepo3
repo.myRepo3.password=passwordRepo3

Then you can mention your publish repositories in option as

repo.publishName=myRepo2   (for snapshots + releases)

or

repo.publishName=myRepo2   (for snapshots)
repo.publishReleaseName=myRepo3  (for releases)

or you can override the JKbuildDependencySupport#publishRepositories in your build class as

@Override
protected JkPublishRepos publishRepositories() {
    return repoFromOptions("myRepo2").asPublishSnapshotRepo()
        .andRelease(repoFromOptions("myRepo3"));
}

In this case the snapshot artifacts (those with version's ending with '-SNAPSHOT') will be published on https://my.nexus/repo2 while the other will be published in https://my.nexus/repo3.

You can also override `publishRepositories``in your build script so you can write your own specific logic to setup and select publish repositories.

Publish to a Ivy repository

Publishing to an Ivy repository is similar to publishing to a Maven one, except :

  • The publishing creates a ivy.xml file instead of a pom.xml file.
  • The publisher needs to declare at least one Ivy repository. If not the case, nothing will be published.
  • You must feed the publish method with JkIvyPublication instead of JkMavenPublication.

This give an example of build file publishing on ivy with specific scope mapping.

public class IvyPublishBuild extends JkJavaBuild {
	
    {
        this.pack.tests = true;
        this.pack.javadoc = true;
    }
	
    @Override
    public JkModuleId moduleId() {
        return JkModuleId.of("org.jerkar", "script-samples-ivy");
    }

    @Override
    public JkDependencies dependencies() {
        return JkDependencies.builder()
            .on(GUAVA, "18.0")	
            .on(JERSEY_SERVER, "1.19").mapScope(RUNTIME, TEST).to(COMPILE)
            .on("com.orientechnologies:orientdb-client:2.0.8")
            .on(JUNIT, "4.11").scope(TEST)
            .on(MOCKITO_ALL, "1.9.5").scope(TEST)
            .build();
    }
	
    @Override
    protected JkRepos downloadRepositories() {
        return JkRepo.ivy(this.repo.publish.url).and(JkRepo.mavenCentral());
    }
	
    @Override 
    protected JkPublishRepos publishRepositories() {
        return JkRepo.ivy(this.repo.publish.url).asPublishRepos();
    }
	
    @Override
    protected JkIvyPublication ivyPublication() {
        return JkIvyPublication.of(packer().jarFile(), COMPILE)
            .and(packer().jarTestFile(), TEST)
            .and(packer().javadocFile(), JAVADOC)
            .and(packer().jarSourceFile(), "src", SOURCES)
            .and(packer().jarTestSourceFile(), "src", SOURCES, TEST);	
    }

}

Publish to a public central repositoty

Publishing to a central repository (as Maven central through OSSRH) generally requires extra information to be passed along indications to sign your artifacts.

    // Extra information mandatory to publish to OSSRH 
    @Override
    protected JkMavenPublication mavenPublication() {
        return super.mavenPublication().with(
                JkMavenPublicationInfo
                .of("Jerkar", "Build simpler, stronger, faster", "http://jerkar.github.io")
                .withScm("https://github.com/jerkar/jerkar.git").andApache2License()
                .andGitHubDeveloper("djeang", "djeangdev@yahoo.fr"));
    }

   @Override
   protected JkPublishRepos publishRepositories() {
        return JkPublishRepos.ossrh("myOssrhUserName", "myOssrhPassword", pgp());
   }

OSSRH requires to sign artifact using PGP. For such Jerkar needs a JkPgp object containing everything necessary to sign artifact.
By default Jerkar assumes that :

  • public ring is located at [USER HOME]\AppData\Roaming\gnupg\pubring.gpg on Windows and at [USER HOME]/gnupg/pubring.gpg on Unix systems
  • secret ring is located at [USER HOME]\AppData\Roaming\gnupg\secring.gpg on Windows and at [USER HOME]/gnupg/secring.gpg on Unix systems
  • secret ring password is provided by option pgp.secretKeyPassword

Pass your secret key password as an option pgp.secretKeyPassword=mySecretRingPassword when you launch your build or store it in your [JERKAR USER HOME]/options.properties if you consider it is safe enough.

Plugins


Jerkar provides a plugable architecture. To be precise, build templates provided with Jerkar (org.jerkar.tool.JkBuild, org.jerkar.tool.builtins.javabuild.JkJavaBuild) are plugable.
They provide methods designed for extension point. Methods designed for extension point alter their behavior according template plugins activated in the enclosing template.

Example for JkJavaBuild template :

/**
 * Returns location of production source code (containing edited + generated sources).
 */
public JkFileTreeSet sources() {
    return JkJavaBuildPlugin.applySourceDirs(this.plugins.getActives(),
        editedSources().and(generatedSourceDir()));
}

By default this method simply returns the files mentioned by the #editedSources() and #generatedSourceDir(). If plugins
are activated, the result may be altered as the JkJavaBuildPlugin class specifies :

static JkFileTreeSet applySourceDirs(Iterable<? extends JkBuildPlugin> plugins, JkFileTreeSet original) {
    JkFileTreeSet result = original;
        for (final JkBuildPlugin plugin : plugins) {
            result = ((JkJavaBuildPlugin) plugin).alterSourceDirs(result);
        }
    return result;
}
	
/**
 * Override this method if the plugin need to alter the source directory to use for compiling.
 * 
 * @see JkJavaBuild#sources()
 */
protected JkFileTreeSet alterSourceDirs(JkFileTreeSet original) {
    return original;
}

For instance, the Eclipse plugin for JkJavaBuild redefines this method to enforce the source directories defined in the .classpath file.

To bind plugin to a template : you can either declare it inside the build class or mention it in the command line...

Declare Plugin in Build Class

The best place to declare plugin is within #init() method. Indeed, when this method is invoked, fields and project base directory have been already set to proper value.
At this point you can choose either to activate it (mean that the the plugin is always taken in account) or just configure it (in this case the plugin is taken in account only if specified in command line).

To activate a plugin, just invoke JkBuildPlugins#activate() method passing the instantiated plugin as :

@Override
protected void init() {
    JkBuildPluginSonar sonarPlugin = new JkBuildPluginSonar()
        .prop(JkSonar.HOST_URL, sonarEnv.url)
        .prop(JkSonar.BRANCH, "myBranch");
    JkBuildPluginJacoco pluginJacoco = new JkBuildPluginJacoco();
    this.plugins.activate(sonarPlugin);
}

Here, the SonarQube plugin is active at each build. A SonarQube analysis is run when the #verify() method is invoked on the JkBuild instance.

But, if you need to configure the plugin without activating it, you must use JkBuildPlugins#configure() method instead :

@Override
protected void init() {
    JkBuildPluginSonar sonarPlugin = new JkBuildPluginSonar()
        .prop(JkSonar.HOST_URL, sonarEnv.url)
        .prop(JkSonar.BRANCH, "myBranch");
    JkBuildPluginJacoco pluginJacoco = new JkBuildPluginJacoco();
    this.plugins.configure(sonarPlugin);
}

SonarQube plugin is not activated unless you specify #sonar in the command line (see below).

Mention Plugins in the Command Line

You can both configure plugin, activate plugin and invoke plugin methods from the command line without declaring anything in the build definition files.

For such, you need to get the plugin name. By construction the plugin name is plugin class short name minus JkBuildPlugin with lower case at the first letter.
Indeed, plugin classes are required to be called JkBuildPluginXxxx and so xxxx is the plugin name for JkBuildPluginXxxx plugin class.
If 2 plugins has the same name in your classpath then you have to name it with the fully qualified class name of the plugin class (xx.xxxx.xx.JkBuildPluginXxxx for instance).

Activate a Plugin

To activate a plugin, you have to mention its name followed by a # in the command line. For example if you want to launch unit tests with Jacoco plugin activated, you may execute the following command line : jerkar doUnitTest jacoco#.

If a project has slave projects, then you can activate the plugin for both main and slave projects by mentioning a * after the plugin declaration as jerkar doUnitTest jacoco#*.

Configure a Plugin

Configuring a plugin consists in setting its instance fields via the option mechanism. So for setting a plugin field, just mention -pluginName#fieldName=value in the command line.

This setting will apply to both master and slave build plugin instances.

Execute a Plugin Method

Plugin provide extension point methods to alter template methods but can also provide their own methods.
To invoke a plugin method, just mention pluginName#methodName in the command line. Any public zero argument method is valid.

If a project has slave projects, then you can invoke the plugin method for both main and slave projects by mentioning a * after the method invokation as jerkar myPlugin#doSomething*.

Plugins Location

To be activated or configured a plugin has to be in the Jerkar classpath. Plugins are assumed to be packaged as jar file. There is 3 ways to add a plugin in the classpath :

  • Add the jar file in [JERKAR HOME]/libs/ext. The plugin will be available for all builds within this Jerkar installation but some builds may be not portable to other Jerkar installation.
  • Add the jar file in the [PROJECT DIR]/build/libs/build directory. The build is portable as the plugin jar will be provided with the build definition. If the build has dependencies, they should be provided as well.
  • Publish the plugin in a Maven/Ivy repository and mention it in the @JkImport annotation. The build is portable as long as the plugin is available in the download repository.
@JkImport(`{"my.comp:jerkar-plugin-myPlugin:1.1"})
public class MyBuild extends JkJavaBuild {`
...

For now, Jerkar ships with several plugins out-of-the-box :

  • Eclipse : Leverage and generate Eclipse metadata files (included in org.jerkar.core.jar).
  • Sonar : Excecutor for SonarQube code analyser (included in org.jerkar.core-fat.jar).
  • Jacoco : Test coverage tool (included in org.jerkar.core-fat.jar).

You can have a description of plugins available in your classpath by executing jerkar helpPlugins.