Jerkar - Reference Guide

Author : Jérôme Angibaud
Version : 0.7.0.RC4

Introduction
Library Part
Files
System
Dependency Management
Concepts
What is a dependency ?
What is a scope ?
What is a scoped dependency ?
Define a set of dependencies
Defining different type of dependencies
Dependencies on Module
Dependencies on local files
Dependencies on files produced by computation
Resolve Dependencies
Publication
Publish to a Maven repository
Publish to a Ivy repository
Java Project Making
Compilation
Javadoc
Classpath
Java jar and manifest
Classloaders
Junit tests
Projects
Tool Part
Jerkar Runtime
Jerkar from Command line
Parse the Command Line
Populate System Properties from Configuration Files and Command line
Pre-process
Compile Def Classes (def classes)
Select Run Class
Instantiate Run Class
Invoke Command Line Methods
Error Handling
Jerkar from IDE
IDE Classpath Setting
Launch from IDE
Embedded Mode
Default path settings
Specify Jerkar User Home
Specify the local Repository Cache
See Effective Paths
Run Parameters
Environment Variables
System Properties
Jerkar Options
Inject Options
Retrieve Options as String Values
Retrieve Option in Run Class Fields
Composite options
Document Options
Built-in Options
Plugins
Load Plugins
Modify Owing JkRun Instance
Configure Plugins in JkRun Class
Document Plugins
Import External Runs
Principle
Declare Run Import
Option Propagation
Method propagation
Access Imported Runs Programmatically
Self Documentation

Introduction

This document stands for reference guide and provides details about Jerkar behaviour. 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. Jerkar philosophy is to be as transparent and easy to master as possible. We hope that no user will ever feel the need to buy some trainings or books to master it.

The source code has been written with intelligibility in mind in order to navigate easily from the user code to the Jerkar engine room. For Java developers, reading source code and placing break points troubleshoots faster than documentation/support most of the time.

What is Jerkar ?

Jerkar contains both a library and a tool.

The library is for dealing with file sets, compilation, dependency management, testing, external processes, crypto signatures, ... in a glance, all regular things you need to build/publish projects and especially Java projects. The library can be used in any Java program and does not have any dependency.

The tool is intended to execute Java source code from the console in a parameterizable way. Its architecture eases the reuse of build elements (logic, settings, doc, ...) across projects.

Although library and tool are bundled in the same jar, the library does not depend on the tool at all. It can be understood on its own without any knowledge of the tool part.

Library Part

Jerkar contains a library for all regular things you need to build/publish projects and especially Java projects. It can be embedded in your own tool and used in a simple main method.

3rd party libraries

The Jerkar core jar embeds third party jars as Ivy or BouncyCastle but these libraries are embedded in the Jerkar jar and loaded in a specific class loader. These 3rd party APIs are not visible/accessible to client code so one can use another version of these APIs without conflict : from user point of view, Jerkar is a zero-dependency library.

Example

This is an example for building and publishing a multi-module project :

    JkLog.registerHierarchicalConsoleHandler();  // activate console logging

    // A project with ala Maven layout (src/main/java, src/test/java, ...)
    JkJavaProject coreProject = JkJavaProject.ofMavenLayout("../org.myorg.mycore");
    coreProject.addDependencies(
            JkDependencySet.of().and("junit:junit:4.11", JkJavaDepScopes.TEST));

    // Another project depending on the first project + Guava
    JkJavaProject dependerProject = JkJavaProject.ofMavenLayout(".");
    dependerProject.addDependencies(JkDependencySet.of()
            .and("com.google.guava:guava:22.0")
            .and(coreProject));

    dependerProject.getMaker().clean().makeAllArtifacts();  // generate source and binary jars
    dependerProject.getMaker().getTasksForPublishing().publish(); // Publish artifacts on the default binary repository 

Above code defines two projects, one depending on the other.

API Style

Jerkar tries to stick with a consistent API design style.

Class and Interface Naming Convenytion

All Jerkar public classes/interfaces starts with Jk. Its for easing distinction in IDE between classes supposed be used in production or test and the ones used for building.

Mutable Vs Immutable

As a rule of thumb Jerkar favors immutable objects. Nevertheless when object structure is pretty deep, immutability makes object cumbersome to configure, that's why objects of the API with deep structure are mutable while simpler are immutable.

Instantiation

All objects are instantiated using static factory methods. Every factory method names start with of.

Read Accessors

All accessor method names (methods returning a result without requiring IO, just computation) starts with get.

Withers/Anders for Immutable Objects

To create a subtly different object from an other immutable one, Jerkar provides :

Setters/Adders for Mutable Objects

To modify a mutable object, Jerkar provides :

Translators

To translate an object to another representation (for example a JkDependencySet to a list of JkScopedDependency) Jerkar provides methods starting with to.

Domains Covered by the API

The previous example demonstrates how the Java/project API can be used to build and publish Java projects. This API relies on other lower level ones provided by Jerkar. In a glance these are the domains covered by the Jerkar APIs :

Files

File manipulation is a central part of building software. Jerkar embraces JDK7 java.nio.file API by adding some concept around and provides a powerful fluent style API to perform recurrent tasks with minimal effort.

The following classes lie in org.jerkar.api.file package :

The following snippet creates a file on file system and copy the content of the specified url into it

JkPathFile.of("config/my-config.xml").createIfNotExist().replaceContentBy("http://myserver/conf/central.xml");

Instances of this class are returned by dependency manager to turn a set of dependency into a resolved classpath.

Used in path trees to filter in/out files according name patterns.

The following snippet copy all non java source files to another directory preserving structure.

JkPathTree.of("src").andMatching(false, "**/*.java").copyTo("build/classes");

Instances of this class are used by Java project api to defines source and resource files.

Map<String, String> varReplacement = new HashMap<>();
varReplacement.put("${server.ip}", "123.211.11.0");
varReplacement.put("${server.port}", "8881");
JkResourceProcessor.of(JkPathTreeSet.of(Paths.get("src"))).andInterpolate("**/*.properties", varReplacement)
    .generateTo(Paths.get("build/classes"), Charset.forName("UTF-8"));

System

The org.jerkar.api.system package provides classes providing low level functions :

Dependency Management

Concepts

What is a dependency ?

In Jerkar context, a dependency is something that can be resolved to a set of files by a JkDependencyResolver. Generally a dependency is resolved to 1 file (or forlder) but it can be 0 or many.

A dependency is always an instance of JkDependency.

Jerkar distinguishes 3 types of dependency :

For the last, Jerkar is using Ivy 2.4.0 under the hood. This library is embedded inside 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 these 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 too. To label dependencies according their usage, Jerkar uses the notion of scope (represented 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 module dependencies. 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.

JkJavaDepScope class pre-defines scopes used in Java projects.

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

What is a scoped dependency ?

A scoped dependency (represented by JkScopedDependency class) is simply a dependency associated with zero, one or many scopes.

Define a set of dependencies

To define a set of dependencies (typically the dependencies of the project to build), you basically define a set of scoped dependencies.

The set of scoped dependencies concept is represented by JkDependencySet class. This class provides fluent API for easier instantiation.

import static org.jerkar.api.depmanagement.JkJavaDepScopes.*;
...
JkDependencySet deps = JkDependencySet.of()
    .and("com.google.guava") 
    .and("org.slf4j:slf4j-simple")
    .and("com.orientechnologies:orientdb-client:2.0.8")
    .and("junit:junit:4.11", TEST)
    .and("org.mockito:mockito-all:1.9.5", TEST)
    .andFile("../libs.myjar")
    .withVersionProvider(myVersionProvider)
    .withDefaultScopes(COMPILE);

Note that :

- COMPILE RUNTIME
org.springframework.boot:spring-boot-starter-thymeleaf
org.springframework.boot:spring-boot-starter-data-jpa

- RUNTIME
com.h2database:h2
org.liquibase:liquibase-core
com.oracle:ojdbc6:12.1.0

- TEST
org.springframework.boot:spring-boot-starter-test
org.seleniumhq.selenium:selenium-chrome-driver:3.4.0
org.fluentlenium:fluentlenium-assertj:3.2.0
org.fluentlenium:fluentlenium-junit:3.2.0

- PROVIDED
org.projectlombok:lombok:1.16.16

Defining different type of dependencies

This section describes how to declare different types of dependencies.

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.

    JkDependencySet.of()
        .and(JkPopularModule.GUAVA, "18.0")
        .and("com.orientechnologies:orientdb-client:[2.0.8, 2.1.0[")
        .and("mygroup:mymodule:myclassifier:0.2-SNAPSHOT");

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

Note that :

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.

    JkDependencySet of()
        .andFile("libs/my.jar")
        .andFile("libs/my.testingtool.jar", TEST);
    }
		

Dependencies on files produced by computation

It is typically used for multi-modules or multi-techno 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 :

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

The following snippet constructs a set of dependencies on two external project : one is built with maven, the other with Jerkar.

Path mavenProject = Paths.get("../a-maven-project");
JkProcess mavenBuild = JkProcess.of("mvn", "clean", "install").withWorkingDir(mavenProject);
Path mavenProjectJar = mavenProject.resolve("target/maven-project.jar");
JkJavaProject externalProject = JkJavaProject.ofSimple(Paths.get("../a-jerkar-project")); 
JkDependencySet deps = JkDependencySet.of()
    .and(JkComputedDependency.of(mavenBuild, mavenProjectJar))
    .and(externalProject);

Resolve Dependencies

The JkDependencyResolver class is responsible JkDependencyResolver.of(JkRepo.ofMavenCentral());to resolve dependencies by returning JkResolveResult from a JkdependencySet.

JkDependencySet deps =  JkDependencySet
                            .of("org.apache.httpcomponents:httpclient:4.5.3")
                            .andFile("libs/my.jar");

// Module dependencies are fetched from Maven central repo
JkDependencyResolver resolver = JkDependencyResolver.of(JkRepo.ofMavenCentral());  
JkResolveResult result = resolver().resolve(deps);

From the result you can :

JkDependencyNode slfjApiNodeDep = result.getDependencyTree().getFirst(JkModuleId.of("org.slf4j:slf4j-api"));
System.out.println(slfjApiNode.getModuleInfo().getResolvedVersion());
JkPathSequence sequence = result.getFiles();  
sequence.forEach(System.out::println); // print each files part of the dependency resolution

The following snippets captures the resolved dependency files for COMPILE scope. Junit is excluded from this result.

JkDependencySet deps = JkDependencySet.of()
    .and("org.slf4j:slf4j-simple", COMPILE_AND_RUNTIME)
    .and("junit:junit:4.11", TEST);
    
Iterable<Path> files = JkDependencyResolver.of(JkRepo.ofMavenCentral()).resolve(COMPILE).getFiles();

Publication

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

The following snippet demonstrate a pretty sophisticated publishing on Maven :

    JkVersionedModule versionedModule = JkVersionedModule.of("org.myorg:mylib:1.2.6");
    JkDependencySet deps = JkDependencySet.of()
            .and("org.slf4j:slf4j-simple", COMPILE_AND_RUNTIME)
            .and("junit:junit:4.11", TEST);
    JkMavenPublication mavenPublication = JkMavenPublication.of(Paths.get("org.myorg.mylib.jar"))

            // the following are optional but required to publish on public repositories.
            .and(Paths.get("org.myorg.mylib-sources.jar"), "sources")
            .and(Paths.get("org.myorg.mylib-javadoc.jar"), "javadoc")
            .withChecksums("sha-2", "md5")
            .withSigner(JkPgp.of(Paths.get("myPubring"), Paths.get("mySecretRing"), "mypassword"))
            .with(JkMavenPublicationInfo.of("My sample project",
                    "A project to demonstrate publishing on Jerkar",
                    "http://project.jerkar.org")
                    .andApache2License()
                    .andDeveloper("djeang", "myemail@gmail.com", "jerkar.org", "http://project.jerkar.org/"));

    // A complex case for repo (credential + signature + filtering) 
    JkRepo repo = JkRepo.of("http://myserver/myrepo")
            .withOptionalCredentials("myUserName", "myPassword")
            .with(JkRepo.JkPublishConfig.of()
                        .withUniqueSnapshot(false)
                        .withNeedSignature(true)
                        .withFilter(mod -> // only accept SNAPSHOT and MILESTONE
                            mod.getVersion().isSnapshot() || mod.getVersion().getValue().endsWith("MILESTONE")
                        ));
    
    // Actually publish the artifacts
    JkPublisher publisher = JkPublisher.of(repo);
    publisher.publishMaven(versionedModule, mavenPublication, deps);

Notice that Jerkar allows to :

To sign with PGP, no need to have PGP installed on Jerkar machine. Jerkar uses Bouncy Castle internally to sign artifacts.

Publish to a Ivy repository

Publishing on Ivy repo is pretty similar than on Maven though there is specific options to Ivy.

    JkVersionedModule versionedModule = JkVersionedModule.of("org.myorg:mylib:1.2.6-SNAPSHOT");
    JkDependencySet deps = JkDependencySet.of()
            .and("org.slf4j:slf4j-simple", COMPILE_AND_RUNTIME)
            .and("junit:junit:4.11", TEST);

    JkIvyPublication publication = JkIvyPublication.of(Paths.get("org.myorg.mylib.jar"), "master")
            .and(Paths.get("org.myorg.mylib-sources.jar"));

    JkRepo repo = JkRepo.ofIvy(Paths.get("ivyrepo"));

    JkPublisher publisher = JkPublisher.of(repo);
    publisher.publishIvy(versionedModule, publication, deps, JkJavaDepScopes.DEFAULT_SCOPE_MAPPING,
            Instant.now(), JkVersionProvider.of());

Java Project Making

Jerkar provides API for processing usual Java build tasks. To illustrate this, let's start from the following layout :

    Path src = getBaseDir().resolve("src/main/java");
    Path buildDir = getBaseDir().resolve("build/output");
    Path classDir = getOutputDir().resolve("classes");
    Path jarFile = getOutputDir().resolve("jar/" + getBaseTree().getRoot().getFileName() + ".jar");
    JkClasspath classpath = JkClasspath.of(getBaseTree().andAccept("libs/**/*.jar").getFiles());
    Path reportDir = buildDir.resolve("junitRreport");

Compilation

JkJavaCompiler stands for the compiler binary or tool while JkJavaCompileSpec stands for what to compile and how.

JkJavaCompiler.ofJdk().compile(JkJavaCompileSpec.of()
                .setOutputDir(classDir)
                .setClasspath(classpath)
                .setSourceAndTargetVersion(JkJavaVersion.V8)
                .addSources(src));

JkJavaCompiler.ofJdk() provides the compiler embedded with the JDK without forking the process. It is possible to fork it or choose an external compiler for cross-compile purpose.

Javadoc

Simple Javadoc tasks can be performed using JkJavadocMaker class.

JkJavadocMaker.of(JkPathTreeSet.of(src), buildDir.resolve("javadoc")).process();

Classpath

Jerkar provides JkClasspath to construct and reason about classpath.

JkClasspath classpath = JkUrlClassLoader.ofCurrent().getFullClasspath();
Path guavaJar = classpath.getEntryContainingClass("com.google.common.base.Strings");

Java jar and manifest

JkpathTree class help to produce simply jar files using zipTo method : JkPathTree.of(classDir).zipTo(jarFile)

Nevertheless JkJarPacker along JkManifest provides powerful methods to read/write/edit manifests and create fat jars.

JkManifest.ofEmpty().addMainClass("org.jerkar.samples.RunClass").writeToStandardLocation(classDir);

Classloaders

JkClassloader provides utility methods to reason about classloaders and to invoke methods coming from class loaded in other classloader than the current one.

JkUrlClassloader provides classpath scanning functions.

Junit tests

The following snippet shows how to launch Junit tests programmatically.

   JkUnit.of().withForking()
        .withReportDir(reportDir)
        .withReport(JunitReportDetail.FULL)
        .run(classpath, JkPathTree.of(testClassDir).andAccept("**/*Test.class", "*Test.class") ));

Projects

Projects are file structures for hosting Java projects meaning source code, test codes, dependencies, build instructions ...

From a project definition, one can easily build it and produce artifacts and test executions.

   JkJavaProject coreProject = JkJavaProject.ofMavenLayout("./projects/core");
    coreProject.addDependencies(
            JkDependencySet.of().and("junit:junit:4.11", JkJavaDepScopes.TEST));

    // A project depending on the first project + Guava
    JkJavaProject dependerProject = JkJavaProject.ofMavenLayout(".project/depender");
    dependerProject.setVersionedModule("mygroup:depender", "1.0-SNAPSHOT");
    dependerProject.addDependencies(JkDependencySet.of()
            .and("com.google.guava:guava:22.0")
            .and(coreProject));

    coreProject.getMaker().clean();
    dependerProject.getMaker().clean().makeAllArtifacts();  // create depender.jar project along core.jar
    dependerProject.getMaker().getTasksForPublishing().publish();  // publish depender.jar on default binary repository

The principle is that each JkJavaProject holds a JkJavaProjectMaker responsible to achieved build tasks. The maker object defines atifacts to build. By default it defines jar and sources jar but it's a one liner to add javadoc artifact as well.

You can define your onw specific artifact (distrib, binary specific,...). When defined, this artifact will be built and deployed along the other ones.

JkJavaProject instances are highly configurable. You can tune your project structure/build without limits.

Tool Part

Lexical

The following terms are used all over this section :

[PROJECT DIR] : Refers to the root folder of the project to build (or to run tasks on). This is where you would put pom.xml or build.xml files.

[JERKAR HOME] : Refers to the folder where is installed Jerkar. You should find jerkar.bat and jerkar shell scripts at the root of this folder.

[JERKAR USER HOME] : Refers to the folder where Jerkar stores caches, binary repository and global user configuration. By default it is located at [USER DIR]/.jerkar.

Def classes : Java source files located under [PROJECT DIR]/jerkar/def compiled on the flight by Jerkar.

Run Classes : Classes extending org.jerkar.tool.JkRun. Their run methods can be invoked and their pubic fields set from the command line. Generally def classes contains one run class though there can be many or none.

Run Classpath : Classpath on which depends def classes to get compiled and run classes to be executed. By default, it consists in Jerkar core classes. it can be augmented with any third party lib or run classpath coming from another project. Once def classes sources have been compiled, run classpath is augmented with their .class counterpart.

Run Methods : Java methods member of run classes and invokable from Jerkar command line. They must be instance method (not static), public, zero-args and returning void. Every method verigfying these constraints is considered as a run method.

Options : This is a set of key-value used to inject parameters. Options can be mentioned as command line arguments, stored in specific files or hard coded in run classes.

In a Glance

The Jerkar tool consists in an engine able to run Java source code or Java compiled code from the command line.

Generally this code is intended to build Java projects but it can be used for any purpose.

In practice, your project has a structure respecting the following layout :

[Project Dir]
   |
   + jerkar
      + boot             <-------- Put extra jars here to augment run classpath.
      + def
         + MyRun.java   <----- Class extending JkRun 
         + MyUtility.java   <---- Utility class consumed by MyRun
      + output              <---- Build artifacts are generated here
   + src
      + main
          + java
          + resources
   + ...

A run class may look like :

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.jerkar.tool.JkDoc;
import org.jerkar.tool.JkImport;
import org.jerkar.tool.JkRun;
import com.google.common.base.MoreObjects;

@JkImport("commons-httpclient:commons-httpclient:3.1")  // Imports 3rd party library to be used by def classes
@JkImport("com.google.guava:guava:21.0")
public class MyRun extends JkRun {    // The run class
    
    public String myParam1 = "myDefault";    // Overridable by injecting options in command line

    @JkDoc("Performs some tasks using http client")    // Only for self documentation purpose
    public void myMethod1() {                   // Run method (callable from command line)
        HttpClient client = new HttpClient();
        GetMethod getMethod = new GetMethod("http://my.url/" + myParam1);
        ....
    }
    
    public void myMethod2() {   // An other run method 
        MyUtility.soSomething();
        ...
    }

}

From [Project Dir], you can invoke any run method defined on MyRun class from the command line.

For example, executing jerkar myMethod1 myMethod2 -myParam1=foo does the following :

  1. compile sources located in jerkar/def directory,
  2. instantiate a MyRun instance,
  3. inject "foo" in the myParam1 field,
  4. invoke myMethod1(),
  5. invoke myMethod2().

If no run class are present in def classes, Jerkar picks org.jerkar.tool.JkRun. In despite this class does not provide any particular methods, you can still perform full Java builds by invoking built-in 'java' plugin executing jerkar clean java#pack (See Plugins).

Executing jerkar or jerkar help on command line displays all run methods and options for the current run class.

The following chapters detail about how the mechanism works and what you can do with.

Jerkar Runtime

This chapter describes how to use Jerkar with command line and mostly what happens behind the cover when Jerkar is run.

Jerkar is a pure Java application requiring JDK 8. JDK is required and JRE is not sufficient as Jerkar uses the JDK tools to compile def classes.

Jerkar can be launched from both command line and your IDE.

Jerkar from Command line

To ease launching Java processes from command line, Jerkar provides shell scripts ( jerkar.bat for Windows and jerkar for Unix ). They are located at root of [JERKAR HOME]. [JERKAR HOME] is supposed to be in your PATH environment variable.

This script does the following :

  1. Find the java executable path : If a JAVA_HOME environment variable is defined then it takes the one lying in this JDK, otherwise it takes the one accessible 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.
  3. Get the classpath in the following order :
  4. Run the java process for launching org.jerkar.tool.Main class passing the command line argument as is. This class main method does the following :
    1. Parse the command line.
    2. Populate system properties from configuration files and command line.
    3. Pre-process def classes . In this step, def class code is parsed to detect 3rd party and external project imports. Imports are added to the run classpath.
    4. Compile def classes using the classpath computed in previous step.
    5. Select the run class to be run.
    6. Instantiate selected run class, inject options and bind plugins on it.
    7. Invoke methods specified in command line arguments : methods are executed in the order they appear on the command line.

The following sub-sections detail about these steps.

Parse the Command Line

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

Populate System Properties from Configuration Files and Command line

Jerkar loads system properties in order from :

The last loaded properties override the previous ones if there is some conflicts.

Jerkar follows a similar process to load options. It loads in order :

The last loaded options override the previous ones if there is some conflicts.

Pre-process Def Class Code (Import 3rd party library into Run Classpath)

In order to compile def classes, Jerkar has to compute run classpath first. With Jerkar you can specify run dependencies directly inside the source code using @JkImport or @JkImportProject annotations as shown below.

@JkImport("commons-httpclient:commons-httpclient:3.1")
@JkImport("com.google.guava:guava:18.0")
@JkImport("../local/library/bin")
public class HttpClientTaskRun extends JkRun {

    @JkImportProject("../another/project/using/jerkar")
    private OtherRun otherRun;  // Run class from another project
    
    ...

To achieve this, Jerkar parses source code of all classes under jerkar/def and add the detected imports to the run classpath. Note that classes having a name starting by a '_' are skipped.

When a dependency is expressed as a maven/ivy module, Jerkar tries to resolve it using repository url defined by in order :

If a repository needs credentials, you need to supply it through Jerkar options repo.[repo name].username and repo.[repo name].password.

Note that you can define several urls for a repo.[repo name].url by separating then with coma (as repo.run.url=http://my.repo1, http://my.repo2.snapshot).

As with other repo, if the download repository is an Ivy repo, you must prefix url with ivy: so for example you'll get repo.run.url=ivy:file://my.ivy/repo.

Compile Def Classes (def classes)

Jerkar compiles def class files prior to execute it. Def class files are expected to be in [PROJECT DIR]/jerkar/def. Classes having a name starting by a '_' are skipped. If this directory does not exist or does not contains java sources, the compilation is skipped. Compilation occurs upon the following classpath :

It outputs class files in [PROJECT DIR]/jerkar/output/def-classes directory.

Jerkar uses the compiler provided by the running JDK.

Select Run Class

Once compiled, Jerkar augments the run classpath with classes compiled in previous step. Then it selects one run class from run classpath and instantiate it.

The selection logic is :

Instantiate Run Class

The run instantiation process is defined in ork.jerkar.tool.JkRun#of factory method. It consists in :

  1. Creating a new run class instance (Invoking default constructor).
  2. Injecting defined options in public instance fields.
  3. Invoking JkRun#setup method on run class. This method might be overridden by users to configure run and plugins before they have been activated.
  4. Loading plugins defined in command line into the run class instance.
  5. Invoking JkPlugin#activate method on each loaded plugins. This method is defined by plugin authors.
  6. Invoking JkRun#postPluginSetup on run class. This method might be overridden by users to configure run class instance once plugins have been activated.

Invoke Command Line Methods

Once run class instantiated, Jerkar invokes instance methods mentioned in command line as jerkar myFistMethod mySecondMethod .... Methods are invoked in order they appear in command line regardless if method is defined on the run class itself or in a plugin.

In order a method to be considered as a run method (invokable from Jerkar command line), it must :

If Jerkar command line specifies no method, then help method is invoked.

Error Handling

If an exception is thrown during the execution, Jerkar displays full stack trace on the console except if this is a org.jerkar.api.system.JkException. In this case, only the message is displayed.

Jerkar from IDE

IDE Classpath Setting

In order your IDE compiles and launches your def classes, you must ensure that project/module classpath contains :

Plugin methods eclipse#generateFiles and intellij#generateIml achieve this for you.

Launch from IDE

If launched from the IDE, def classes are already compiled and the classpath already set by the IDE. This leads in a simpler and faster process.

To launch Jerkar from your IDE, you can go two ways :

One is to create a main method in one of your def classes as below and invoke it.

public static void main(String[] args) {
    JkInit.instanceOf(MyRun.class, args).doDefault();
} 

The JkInit#instanceOf method loads options from args and instantiates run classes. Then user can configure it using hard coding prior launching any method programmatically.

The other way is to launch org.jerkar.tool.Main method from your IDE with same arguments as you would do with command line.

Embedded Mode

When launched from command line, [JERKAR_HOME]/org.jerkar.core.jar comes after [WORKING_DIR]/jerkar/boot/* in Jerkar classpath. This means that if a version of Jerkar (org.jerkar.core.jar) is in this directory, the run will be processed with this instance of Jerkar instead of the one located in in [JERKAR HOME].

This is called the Embedded mode. The Jerkar tool is embded within your project so the run does not depends of the presence and version of Jerkar installed in the host machine.

__Enable embedded mode : __

To enable embedded mode :

  1. Copy [JERKAR_HOME]/org.jerkar.core.jar into [PROJECT_DIR]/jerkar/boot/* directory.
  2. Copy [JERKAR_HOME]/jerkar.bat and [JERKAR_HOME]/jerkar at the root of [PROJECT_DIR] (optional).

Jerkar is provided with a scaffold plugin that do it for you : just execute jerkar scaffold#run -scaffold#embed.

Run in embedded mode :

You can go two ways :

Default path settings

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 get this location programmatically using JkLocator.jerkarUserHome() method.

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]/cache/repo but you can redefine it by defining the JERKAR_REPO environment variable. You can get this location programmatically using JkLocator.jerkarRepositoryCache() method.

See Effective Paths

The Jerkar displays the effective path at the very start of the process if launched with -LogHeaders=true option :

For example, jerkar help -LogHeaders will output :

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

Working Directory : C:\Users\me\IdeaProjects\playground\jerkar-sample
Java Home : C:\Program Files (x86)\Java\jdk1.8.0_121\jre
Java Version : 1.8.0_121, Oracle Corporation
Jerkar Version : Xxxxx
Jerkar Home : C:\Users\me\IdeaProjects\jerkar\org.jerkar.core\jerkar\output\distrib
Jerkar User Home : C:\Users\angibaudj\.jerkar
Jerkar Repository Cache : C:\Users\me\.jerkar\cache\repo

...

Run Parameters

Jerkar runs are parameterizable. One can retrieve values defined at runtime by reading :

Environment Variables

You can fetch environment variables using the standard System#getenv method or by annotating a public instance field with @JkEnv. JkOption mechanism takes precedence on environment variable injection.

System Properties

As for environment variables, one can read system properties using the standard System#getProperty method.

Jerkar proposes 3 ways of injecting system properties. They are considered in following order :

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.

Options are globally available in all run classes but can be retrieve in a static typed way (injected in run class fields) or as set of key/string value.

Inject Options

Jerkar proposes 3 ways to inject options. They are considered in following order :

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

Retrieve Options as String Values

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

This way you only get the string literal value for the option and you have to parse it if the intended type was a boolean or a number.

Retrieve Option in Run Class Fields

You can retrieve options just by declaring fields in run classes. All public non-final instance fields of the invoked run class, are likely to be injected as an option.

For example, if you declare a field like :

class MyRun extends JkRun {
   public int size = 10;
   ...
}

Then you can override the value by mentioning in command line jerkar doSomething -size=5.

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. If the value is not parsable to the target type, run fails.

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 group all these information into a single object as :

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

Declare a Server field in your run class :

class MyRun extends JkRun {
   public Server deployServer = new Server();
   ...
}

Then you can inject the server object using following options :

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

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")
public boolean forkTests = false;

Built-in Options

Jerkar defines some built-in options that are used by the engine itself. Unlike regular options, they respect an UpperCamelCase naming convention :

Plugins

Jerkar provides a plugable architecture. In Jerkar, a plugin is a class extending org.jerkar.tool.JkPlugin and named as JkPlugin[PluginName]. The plugin name is inferred from Plugin class name.

Each plugin instance is owned by a JkRun object, and can access to it through JkPlugin#run protected field.

Plugins has 3 capabilities :

Jerkar is bundled with a bunch plugins (java, scaffold, eclipse, intellij, ...) but one can add extra plugins just by adding the jar or directory containing the plugin class to your run classpath.

To see all available plugins in the run classpath, just execute jerkar help. See Command Line Parsing and Run Class Pre-processing to augment run_classpath .

Load Plugins

Plugins need not to be mentioned in run class code in order to be bound to the JkRun instance. Just the fact to mention a plugin run method, options or [pluginName]# in the command line will load the plugin.

For example jerkar scaffold#run java# will load 'java' and 'scaffold' plugins into a JkRun instance. 'java' plugin instance will modify 'scaffold' plugin instance in such it produces a run class code extending JkJavaProjectBuild instead of 'JkRun' when 'scaffold#run' command is executed. It also creates Java project layout folders. See activate method in JkPluginJava Code to have a concrete view.

You can also force a plugin to load in your run class code as below. That way, you don't need to mention java# in command line.

public class MyBuild extends JkRun {
    
    MyBuild() {
        getPlugin(JkPluginJava.class);  // Loads 'java' plugins in this instance, a second call on 'plugins().get(JkPluginJava.class)' will return same instance.
        getPlugin("intellij");   // You can also load plugin by mentioning its name but it's slower cause it involves classpath scanning
    }
    
}

Modify Owing JkRun Instance

JkRun instances are created using JkRun#of factory method. This method invoke JkPlugin#active method on all plugin loaded in the JkRun instance. By default, this method does nothing but plugin implementations can override it in order to let the plugin modify its owning JkRun or owe of its plugins.

In fact, many plugins act just as modifier/enhancer of other plugins.

For example, Jacoco Plugin does not provide run method but configures 'java' plugin in such unit tests are forked on a JVM with Jacoco agent on. It also provides a utility class JKocoJunitEnhancer that supplies lower level features to launch Jacoco programmatically.

Some other plugins does not modify their owning JkRun instance, for example Scaffold Plugin does not override activate method, therefore it has no side effect on its owning JkRun instance. It only features run methods along options.

Configure Plugins in JkRun Class

There is three places where you can configure plugins :

Example of configuring a plugin in run class.

    ...
    public MyBuild() {
        JkPluginSonar sonarPlugin = getPlugin(JkPluginSonar.class);  // Load sonar plugin 
		sonarPlugin.prop(JkSonar.BRANCH, "myBranch");  // define a default for sonar.branch property
        ...
    }

Jerkar own build class makes a good example.

Document Plugins

Plugin writers can embed self-documentation using @JkDoc annotation on classes, run methods and public fields.

Writers can also mention that the plugin has dependencies on other plugins using @JkDocPluginDeps annotation. This annotation has only a documentation purpose and does not has influence on plugin loading mechanism.

A good example is Java Plugin

Import External Runs

There is many way to perform multi-project build. One of is to import runs from external projects.

Principle

A run class instance can import run class instances from other projects.

The current run classpath is augmented with the run classpath of imported projects.

Imported runs are not aware they are imported. In fact any run can be imported. The relation is uni-directional.

Declare Run Import

To import a run class from an external project, use the @JkImportProject annotation as shown below :

public class MRun extends JkRun {
    
    @JkImportProject("../otherProject")   getSibling
    private BarRun anImportedRun;   getSibling

    public void doSomesthing() {
       anImportedRun.doBar();   // use the run class defined in ../otherProject
       ...

Run classes are imported transitively, this means that, in above example, if BarRun imports an other project, this last will be also imported.

Option Propagation

Options mentioned in command line are propagated to the imported runs.

So for example you execute jerkar java#pack -java#tests.fork, test will be forked for the main run and all imported ones.

Method propagation

Methods mentioned in the command line are not automatically propagated to imported runs. Executing jerkar clean will only clean the current run project.

To propagate method call to every imported runs, method name should be prefixed with a '*'. Executing jerkar clean* will invoke 'clean' method on the current run class along along all imported run classes.

Access Imported Runs Programmatically

You can access to the list of imported run classes within using JkRun#ImportedRuns methods as show below :

public class MyRun extends JkRun{

    ...

    public void doForAll() {
        this.clean();
        this.importedRuns().all().forEach(JkRun::clean);
        this.importedRuns().allOf(JkJavaProjectBuild.class).forEach(build -> build.java().pack());
    }

Self Documentation

Run classes and plugins can provide self documentation.

When properly auto-documented, users can display documentation by executing jerkar help.

The displayed documentation consist in :

If run class or plugin declares a public instance field without @JkDoc annotation, then it will be displayed in help screen but mentioning that there is no description available.

If run class or plugin declares a run method without @JkDoc, it will be also displayed in help screen but mentioning that there is no description available.

This is the display screen for the build class of Jerkar itsef :

Usage: jerkar [methodA...] [pluginName#methodB...] [-optionName=value...] [-pluginName#optionName=value...] [-DsystemPropName=value...]
Execute the specified methods defined in run class or plugins using the specified options and system properties.
When no method specified, 'doDefault' method is invoked.
Ex: jerkar clean java#pack -java#pack.sources=true -LogVerbose -other=xxx -DmyProp=Xxxx

Built-in options (these options are not specific to a plugin or a build class) :
  -LogVerbose (shorthand -LV) : if true, logs will display 'trace' level logs.
  -LogHeaders (shorthand -LH) : if true, meta-information about the build creation itself and method execution will be logged.
  -LogMaxLength (shorthand -LML) : Console will do a carriage return automatically after N characters are outputted in a single line (ex : -LML=120).
  -RunClass (shorthand -RC) : Force to use the specified class as the run class to be invoked. It can be the short name of the class (without package prefix).

Available methods and options :

From class org.jerkar.CoreBuild :
  Methods :
    doDefault : Conventional method standing for the default operations to perform.
  Options :
    -testSamples (boolean, default : false) : If true, executes black-box tests on sample projects prior ending the distrib.

From class org.jerkar.tool.JkRun :
  Methods :
    clean : Cleans the output directory.
    help : Displays all available methods defined in this build.

Available plugins in classpath : eclipse, eclipsePath, intellij, jacoco, java, pgp, pom, repo, scaffold, sonar.

Type 'jerkar [pluginName]#help' to get help on a perticular plugin (ex : 'jerkar java#help').
Type 'jerkar help -Plugins' to get help on all available plugins in the classpath.