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.
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.
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.
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.
UNSPECIFIED-SNAPSHOT
.
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 :
with
when a property is to be replaced by another.and
when a collection property is to be replaced by the same one plus an extra element.minus
when a collection property is to be replaced by the same one minus a specified element.Setters/Adders for Mutable Objects
To modify a mutable object, Jerkar provides :
set
to replace a single property value by an other.add
to add a value to a collection property.
Those methods returns the object itself for chaining.Translators
To translate an object to another representation (for example a JkDependencySet
to a list of JkScopedDependency
)
Jerkar provides methods starting with to
.
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 :
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 :
JkPathFile
: A simple wrapper around for file (not folder) with copy, content interpolation,
checksum, deletion, creation features.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");
JkPathSequence
: A list of java.nio.file.Path
.Instances of this class are returned by dependency manager to turn a set of dependency into a resolved classpath.
JkPathMatcher
: A java.nio.file.PathMatcher
based on java.nio.file
glob pattern or regerxp.Used in path trees to filter in/out files according name patterns.
JkPathTree
: A root folder (or a zip file) along a PathMatcher
providing operations as copy, navigate, zip, iterate.
This central class is spread all over Jerkar APIs.The following snippet copy all non java source files to another directory preserving structure.
JkPathTree.of("src").andMatching(false, "**/*.java").copyTo("build/classes");
JkPathTreeSet
: A set of JkPathTree
.Instances of this class are used by Java project api to defines source and resource files.
JkResourceProcessor
: Provides a means to copy a set of files, preserving the structure and
replacing some text by other text. Typically used for replacing token as ${server.ip}
by concrete value.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"));
The org.jerkar.api.system
package provides classes providing low level functions :
JkException
: Marker exception generally to mention user misuse.
JkInfo
: Provides information as current version of Jerkar.
JkLocator
: Provides information about where is located folder as repository cache or Jerkar user home.
JkLog
: Provides API to log Jerkar event. It supports hierarchical logs through #startTask
and #endtask
methods.
JkProcess
: Launcher for external process.
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 :
JkFileSystemDependency
class). These files are assumed to be present on the file system when the build is running.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.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. 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.
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.
A scoped dependency (represented by JkScopedDependency
class) is simply a dependency associated with zero, one or many scopes.
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 :
Module version and scopes can be omitted when declaring dependencies. Versions can be provided by a JkVersionProvider
and scopes can be defaulted.
Instances of JkDependencySet
can be combined together in order to construct large dependencySet from smaller ones.
JkDependencySet#ofTextDescription
provides a mean to instantiate a dependency set from a simple text as :
- 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
This section describes how to declare different types of dependencies.
JkModuleDependency
: Dependency on Maven modulesJkFileDependency
(Abstract): Dependency on files to be found on file system
JkComputedDependency
: Dependency on files produced by the execution of a Runnable
.JkFileSystemDependency
: Dependency on files supposed to already exist on file system.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 :
-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.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);
}
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 :
JkArtifactProducer
). A Jerkar Java project is an artifact producer.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);
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();
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.
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 :
JkRepoSet
instead of a JkRepo
.To sign with PGP, no need to have PGP installed on Jerkar machine. Jerkar uses Bouncy Castle internally to sign artifacts.
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());
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");
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.
Simple Javadoc tasks can be performed using JkJavadocMaker
class.
JkJavadocMaker.of(JkPathTreeSet.of(src), buildDir.resolve("javadoc")).process();
Jerkar provides JkClasspath
to construct and reason about classpath.
JkClasspath classpath = JkUrlClassLoader.ofCurrent().getFullClasspath();
Path guavaJar = classpath.getEntryContainingClass("com.google.common.base.Strings");
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);
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.
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 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.
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.
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 :
MyRun
instance,myParam1
field,myMethod1()
,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.
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.
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 :
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.JERKAR_OPTS
exists then its value is passed to the java
command line parameters.org.jerkar.tool.Main
class passing the command line argument as is. This class main method does the following :
The following sub-sections detail about these steps.
Jerkar parses the command line and processes each arguments according the following pattern :
Argument starts with @
: This is a library import clause : the text just next to, is added to the run classpath.
For example jerkar myMethod @org.jerkar:an-extra-plugin:3.3
augments the run classpath with the an-extra-Plugin jar.
This is similar to annotate a def class with @JkImport("org.jerkar:an-extra-plugin:3.3")
.
This is intended to modifiate behavior of run class by plugins dynamically.
Argument starts with -
: This is an option declaration. The content following is is expected to be formatted as optionName=optionValue.
For example, `-repo.run.url=http://my.repo.milestone/' will inject 'http://my.repo.milestone/' in the 'repo.run.url' Jerkar option.
In other cases : argument is considered as a run method name to be invoked on the run class instance.
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.
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 :
repo.runName
is present and option repo.${repo.runName}.url
is present as well, it takes the value of this property.repo.run.url
option.repo.download.url
option.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
.
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.
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 :
-RunClass
option (shorthand -RC
) is specified, then Jerkar selects a class having the same name or same
short name among run classes present in run classpath.MyRun
will be selected prior apackage.ARun
, and aa.bb.MyClass
will be selected prior ab.OtherClass
.org.jerkar.tool.JkRun
class.The run instantiation process is defined in ork.jerkar.tool.JkRun#of
factory method. It consists in :
JkRun#setup
method on run class. This method might be overridden by users to configure run and plugins before they have been activated.JkPlugin#activate
method on each loaded plugins. This method is defined by plugin authors.JkRun#postPluginSetup
on run class. This method might be overridden by users to configure run class instance once plugins have been activated.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.
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.
In order your IDE compiles and launches your def classes, you must ensure that project/module classpath contains :
org.jerkar.core.jar
(found in Jerkar distrib)@JkImport
annotations of your def classes.@JkImportProject
annotations of your def run classes.Plugin methods eclipse#generateFiles
and intellij#generateIml
achieve this for you.
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.
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 :
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 :
jerkar myFunction ...
as you would do in regular mode. This works only if you have copied jerkar/jerkar.bat shell scripts into [PROJECT DIR]java -cp jerkar/boot/* org.jerkar.tool.Main myFunction ...
from [PROJECT_DIR] .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.
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.
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
...
Jerkar runs are parameterizable. One can retrieve values defined at runtime by reading :
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.
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 :
Jerkar doDefault -DmyProperty=myValue
.In every case, defined system properties are injected after the creation of the java process (via System#setProperty
method).
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.
Jerkar proposes 3 ways to inject options. They are considered in following order :
Jerkar doDefault -myOption=myValue
.Note for boolean options, when no value is specified, true
will be used as default.
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.
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 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
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;
Jerkar defines some built-in options that are used by the engine itself. Unlike regular options, they respect an UpperCamelCase naming convention :
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 .
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
}
}
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.
There is three places where you can configure plugins :
JkRun
subclass constructor : at this point options has yet been injected so it's the place to configure default option values.JkRun#setup
subclass method : at this point, options has been injected but plugins has not been activated yet.
It is the place to configure plugins and other instance member to take options in account.JkRun#postPluginSetup
subclass method : at this point plugins has been activated. If you wan't to override
some values plugins may have set, override this method.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.
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
There is many way to perform multi-project build. One of is to import runs from external projects.
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.
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.
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.
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.
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());
}
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 :
@JkDoc
annotation at class level.@JkDoc
.@JkDoc
annotation on public fields.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.