What is Jeka ?
Jeka is both a build library, and a thin automation tool.
The build library helps for dealing with file sets, compilation, dependency management, testing, publishing, launching 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 aims at executing Java source code from the command line. Its design eases reuse of build elements (logic, settings, doc, ...) across projects.
Combined, it provides a full feature build tool with endless possibilities of extension.
Although library and tool are bundled in the same jar, the library does not depend on the tool at all.
What is this document for ?
This document stands for reference guide. It provides:
If you are looking for how exactly Jeka behaves, or you want to get a pretty exhaustive list of Jeka features, you are in the right place.
If you are looking for further details about API, please consult Javadoc or source code. Jeka source code has been written with intelligibility in mind in order to navigate easily from the user code to the Jeka engine room. For Java developers, reading source code and putting break points troubleshoots faster than documentation/support most of the time.
Jeka philosophy is to be transparent and easy to master. We hope that no user will ever feel the need to buy some trainings or books to master it.
The following concepts are used all over the tool section :
[PROJECT DIR] : Refers to the root folder of the project to build (or to run Jeka methods on). This is where you would put pom.xml or build.xml files.
[JEKA HOME] : Refers to the folder where Jeka is installed. You should find jeka.bat and jeka shell scripts at the root of this folder.
[JEKA USER HOME] : Refers to the folder where Jeka stores caches, binary repository and global user configuration. By default it is located at [USER DIR]/.jeka.
Def Classes : Java source files located under [PROJECT DIR]/jeka/def. They are compiled on the flight by Jeka when invoked from the command line.
Def Classpath : Classpath on which depends def classes to get compiled and Jeka classes to be executed. By default, it consists in Jeka core classes. it can be augmented with any third party lib or def Classpath coming from another project. Once def classes sources have been compiled, def Classpath is augmented with their .class counterpart.
Jeka Classes : Classes extending JkClass
. Their commands can be invoked from the command line and
their pubic fields set from the command line as well. Generally def classes contains one Jeka class though there can be many or
none. CommandSet class can be a def class but can also be imported from a library or external project.
Jeka Methods : Java methods from Jeka classes which are invokable from Jeka command line. They must be instance method (not static), public, zero-args and returning void. Every method verifying these constraints is a command.
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 Jeka classes.
The Jeka tool consists in an engine able to run Java/Kotlin source code or 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]
|
+ jeka
+ boot <-------- Put extra jars here to augment def Classpath.
+ def
+ MyCommands.java <----- Class extending JkClass
+ MyUtility.java <---- Utility class consumed by MyCommands
+ OtherUtility.kt <---- Kotlin code is also accepted
+ output <---- Build artifacts are generated here
+ src
+ main
+ java
+ resources
+ ...
A command class may look like :
import dev.jeka.core.tool.JkClass;import dev.jeka.core.tool.JkDefClasspath;import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import dev.jeka.core.tool.JkDoc;
import com.google.common.base.MoreObjects;
@JkDefClasspath("commons-httpclient:commons-httpclient:3.1") // Imports 3rd party library to be used by def classes
@JkDefClasspath("com.google.guava:guava:21.0")
public class MyCommands extends JkClass { // The command 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 command method defined on MyCommands
class from the command line.
For example, executing jeka myMethod1 myMethod2 -myParam1=foo
does the following :
MyCommands
instance,myParam1
field,myMethod1()
,myMethod2()
.If no command class are present in def classes, Jeka picks JkClass
. In despite this class
does not provide any particular methods, you can still perform full Java builds by invoking built-in 'java' plugin.
For such, execute jeka clean java#pack
(See Plugins).
Executing jeka
or jeka help
on command line displays all run methods and options for the current command class.
The following chapters detail about how the mechanism works and what you can do with.
This chapter describes how to use Jeka with command line and mostly what happens behind the cover when Jeka is run.
Jeka is a pure Java application requiring JDK version 8 or higher (tested until 12). JDK is required and JRE is not sufficient as Jeka uses the JDK tools to compile def classes.
Jeka classes can be launched both from command line and from your IDE as regular Java main method.
To ease launching Java processes from command line, Jeka provides shell script jeka.bat
and jeka
.
They are located at root of JEKA HOME which is supposed to be in your PATH environment variable.
This script does the following :
Find the java executable by looking at, in order :
JEKA_JDK
environment variableJAVA_HOME
environment variablePATH
environment variable.The java
executable must be one from a JDK and not a JRE, as Jeka needs javac
to compile DEF CLASSES.
Get java execution option : If an environment variable JEKA_OPTS
exists then its value is passed to the java
command line parameters.
Get the classpath in the following order :
Run java class dev.jeka.core.tool.Main
passing the command line argument as is.
Jeka entry point is dev.jeka.core.tool.Main#main
method.
1. Parse the command line.
2. Populate system properties from configuration files and command line.
3. Construct DEF CLASSPATH by parsing DEF CLASSES source code.
4. Compile DEF CLASSES using DEF CLASSPATH.
5. Select the Jeka class to be run.
6. Instantiate selected Jeka 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.
Jeka 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 def Classpath.
For example jeka myMethod @dev.jeka:an-extra-plugin:3.3
augments the def Classpath with the an-extra-Plugin jar.
This is similar to annotate a def class with @JkDefClasspath("dev.jeka:an-extra-plugin:3.3")
.
This is intended to modifiate behavior of Jeka 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.def.url=http://my.repo.milestone/' will inject 'http://my.repo.milestone/' in the 'repo.def.url' Jeka option.
In other cases : argument is considered as a Jeka (method) to be invoked on the Jeka class instance.
Jeka loads system properties in order from :
The last loaded properties override the previous ones if there is some conflicts.
Jeka 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, Jeka has to compute DEF CLASSPATH first. With Jeka you can specify dependencies
directly inside the source code using @JkDefClasspath
or @JkDefImport
annotations as shown below.
@JkDefClasspath("commons-httpclient:commons-httpclient:3.1")
@JkDefClasspath("com.google.guava:guava:18.0")
@JkDefClasspath("../local/library/bin")
public class HttpClientTaskRun extends JkCommandSet {
@JkDefImport("../another/project/using/jeka")
private OtherCommandSet otherCommandSet; // CommandSet class from another project
...
To achieve this, Jeka parses source code of all classes under jeka/def and add the detected imports to the def Classpath. Note that classes having a name starting by a '_' are skipped.
When a dependency is expressed as a maven/ivy module, Jeka tries to resolve it against a binary repository.
Jeka defines this url by looking at following options, in order :
repo.defName
and repo.${repo.defName}.url
(example : repo.defName=build_repo, repo.build_repo.url=https://my.buid.repo/url )repo.def.url
repo.download.url
If a repository needs credentials, you need to supply it through Jeka 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.def.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.def.url=ivy:file://my.ivy/repo
.
Jeka compiles def class source files prior to execute it. Def class source files are expected to be in [PROJECT DIR]/jeka/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]/jeka/.work/def-classes directory.
Jeka uses the compiler provided by the running JDK.
Once compiled, Jeka augments the def Classpath with classes compiled in previous step. Then it selects one Jeka class from def classpath and instantiate it.
The selection logic is :
-CommandClass
option (shorthand -CC
) is specified, then Jeka selects a class having the same name or same
short name among Jeka classes present in def classpath.SomeCommands
will be selected prior apackage.SomeCommands
, and aa.bb.SomeCommands
will be selected prior ab.OtherCommands
.JkCommandSet
class.The Jeka class instantiation process is defined in ork.jeka.tool.JkCommandSet#of
factory method. It consists in :
JkPlugin#beforeSetup
method)JkCommandSet#setup
method on COMMAND CLASS. This method is supposed to be override to set specific settings.JkPlugin#afterSetup
method on each bound plugins.JkCommandSet#postSetup
on Jeka class. This method might be override.Once Jeka class instance is ready, Jeka invokes instance methods mentioned in command line as jeka myFistMethod mySecondMethod ...
.
Jeka invokes methods, in same order they appear in command line regardless if method is defined on the Jeka class itself or in a plugin.
In order a method to be considered as a Jeka method (invokable from Jeka command line), it must :
If Jeka command line specifies no method, then Jeka invokes help
.
If an exception is thrown during the execution, Jeka displays full stack trace on the console except if
this is a JkException
. In this case, Jeka displays only the message.
In order your IDE compiles and launches your def classes, you must ensure that project/module classpath contains :
dev.jeka.jeka-core.jar
(found in Jeka distrib)@JkDefClasspath
annotations of your def classes.@JkDefImport
annotations of your def Jeka classes.Plugin methods eclipse#files
and intellij#iml
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 Jeka 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(MyCommands.class, args).doSomething();
}
The JkInit#instanceOf
method loads options from args and instantiates Jeka classes. Then user can
configure it using hard coding prior launching any method programmatically.
The other way is to launch Main
method from your IDE with same arguments as you would do with command line.
Jeka offers a wrapper mechanism similar to let execution independent of the Jeka version installed in host machine. This is the recommended way to use Jeka.
Wrapper consists in :
When executed in place of jeka, jekaw invoke the wrapper jar. This jar downloads the specified version of Jeka and pass the arguments to Jeka main class.
To start a project with a Jeka wrapper, just execute jeka scaffold#wrap at the root of the project. It will add the mentioned files above to your project. Then just invoke jekaw or ./jekaw in place of jeka.
If you are using multi-project structure, you don't have to scaffold wrapper on each. Just scaffold Jeka at a single place in your multi-project structure (for example in the root dir or in the 'master' project) and invoke it always from the sub project you want to build.
For example execute ../jekaw clean java#pack
if the sub-project you want to build is
located in a sub-directory of the root dir.
Embedded mode is the most aggressive strategy to not depend on the host machine. It consists in embedding Jeka tool itself within the project.
When launched from command line, [JEKA_HOME]/dev.jeka.jeka-core.jar comes after [WORKING_DIR]/jeka/boot/* in def classpath. This means that if a version of Jeka (dev.jeka.jeka-core.jar) is in this directory, the run will be processed with this instance of Jeka instead of the one located in in [JEKA HOME].
This is called the Embedded mode. The Jeka tool is embded within your project so the run does not depend of the presence and version of Jeka installed in the host machine.
__Enable embedded mode : __
To enable embedded mode :
Jeka is provided with a scaffold plugin that do it for you : just execute jeka scaffold#run -scaffold#embed
.
Run in embedded mode :
You can go two ways :
jeka myFunction ...
as you would do in regular mode. This works only if you have copied jeka/jeka.bat shell scripts into [PROJECT DIR]java -cp jeka/boot/* Main myFunction ...
from [PROJECT_DIR] .Jeka uses user directory to store user-specific configuration and cache files, in this document we refer to this directory using [Jeka User Home].
By default this directory is located at [User Home]/.jeka ([User Home] being the path given by System.getProperty("user.home");
.
You can override this setting by defining the JEKA_USER_HOME
environment variable.
Jeka uses Apache Ivy under the hood to handle module dependencies. Ivy downloads and stores locally artifacts consumed by projects.
By default the location is [JEKA USER HOME]/cache/repo but you can redefine it by defining the JEKA_REPO
environment variable.
You can get this location programmatically using JkLocator.getJekaRepositoryCache()
method.
The Jeka displays the effective path at the very start of the process if launched with -LogHeaders=true
option :
For example, jeka help -LogHeaders
will output :
_______ _
(_______) | |
_ _____| | _ _____
_ | | ___ | |_/ |____ |
| |_| | ____| _ (/ ___ |
\___/|_____)_| \_)_____|
The 100% Java build tool.
Working Directory : C:\Users\me\IdeaProjects\playground\jeka-sample
Java Home : C:\Program Files (x86)\Java\jdk1.8.0_121\jre
Java Version : 1.8.0_121, Oracle Corporation
Jeka Version : Xxxxx
Jeka Home : C:\Users\me\IdeaProjects\jeka\dev.jeka.core\jeka\output\distrib
Jeka User Home : C:\Users\me\.jeka
Jeka Repository Cache : C:\Users\me\.jeka\cache\repo
...
Jeka classes 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.
Jeka proposes 3 ways of injecting system properties. They are considered in following order :
Jeka doDefault -DmyProperty=myValue
.In every case, defined system properties are injected after the creation of the java process (via System#setProperty
method).
Jeka options are similar to system properties as it stands for a set of key/value.
Options are globally available in all Jeka classes but can be retrieve in a static typed way (injected in Jeka class fields) or as set of key/string value.
Jeka proposes 3 ways to inject options. They are considered in following order :
Jeka 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 Jeka classes. All public non-final instance fields of the invoked Jeka class, are likely to be injected as an option. Note that it can be private, but a public setter must be present.
For example, if you declare a field like :
class MyRun extends JkCommandSet {
public int size = 10;
...
}
Then you can override the value by mentioning in command line jeka 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, commands 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 new field of type Server
in your Jeka class :
class MyRun extends JkCommandSet {
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 jeka help
you need to annotate it with @JkDoc
.
For example :
@JkDoc("Make the test run in a forked process")
public boolean forkTests = false;
Jeka defines some built-in options that are used by the engine itself. Unlike regular options, they respect an UpperCamelCase naming convention :
Jeka provides a pluggable architecture. In Jeka, a plugin is a class extending JkPlugin
and named as JkPlugin[PluginName].
The plugin name is inferred from Plugin class name.
Each plugin instantiated plugin is bound to a JkCommandSet object.
Plugins has 3 capabilities :
Jeka is bundled with a bunch of plugins (java, scaffold, eclipse, intellij, ...), but one can add extra plugins just by adding a jar or directory containing the plugin class to the def classpath.
To see all available plugins in the def classpath, just execute jeka help
.
See Command Line Parsing and Run Class Pre-processing
to augment def classpath .
Plugins need not to be bound in Jeka class code in order to be bound to the JkCommandSet instance. Just the fact to mention a plugin in the command line loads it.
For example jeka scaffold#run java#
will load 'java' and 'scaffold' plugins into a JkCommandSet instance.
'java' plugin instance will modify 'scaffold' plugin instance in such it produces a Jeka class declaring 'java' plugin
when 'scaffold#run' 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 be loaded in your Jeka class code as below. That way, you don't need to mention java#
in command line.
public class MyBuild extends JkCommandSet {
MyBuild() {
getPlugin(JkPluginJava.class); // Loads 'java' plugins in MyBuild instances, a second call on 'plugins().get(JkPluginJava.class)' would return the same JkPluginJava instance.
getPlugin("intellij"); // You can also load plugins by mentioning their name but it's slower cause it involves classpath scanning
}
}
JkCommandSet instances are created using JkCommandSet#of
factory method.
This method invokes JkPlugin#beforeSetup
and JkPlugin#afterSetup
methods on all plugins loaded in the JkCommandSet instance.
JkPlugin#afterSetup
is supposed to be used to modify other plugins that might be bound to the owning Jeka class.
For example, Jacoco Plugin
does not provide Jeka 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 Jeka class instance. For instance, Scaffold Plugin just exposes methods to scaffold new Jeka projects.
There is three places where you can configure plugins :
JkCommandSet
constructor : at this point options has not yet been injected, so it's the place to configure default option values.JkCommandSet#setup
: at this point, options has been injected.
This is the place to configure plugins and other instance members.JkCommandSet#postSetup
: at this point, plugins has done their configuration job. So it is the place
to override plugin settings, if needed.Example of configuring a plugin in Jeka class.
JkPluginSonar sonarPlugin = getPlugin(JkPluginSonar.class); // Load sonar plugin
...
public MyBuild() {
sonarPlugin.prop(JkSonar.BRANCH, "myBranch"); // define a default for sonar.branch property
...
}
Jeka own build class makes a good example.
Plugin authors can embed self-documentation using @JkDoc
annotation on classes, Jeka methods and option 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 impact on plugins loading mechanism.
A good example is Java Plugin
As Jeka API is likely to evolve (less and less), plugins author can mention the Jeka lower version
on which the plugin is compatible with using JkPlugin#getLowestJekaCompatibleVersion
method.
It is also possible to mention the highest version on Jeka, a plugin version is compatible with. As this information is unknown at the time of publishing the plugin, a mechanism based on a central registry keeps track this information.
It consists of publishing a specific format flat file on a url. The file contains the versions that start breaking compatibility. Project repository might host this registry.
A working example is available for spring-boot plugin.
The breaking_versions.txt
is accessible at https://raw.githubusercontent.com/jerkar/springboot-plugin/master/breaking_versions.txt
declared in JkPluginSpringboot
as :
@Override
protected String getBreakingVersionRegistryUrl() {
return "https://raw.githubusercontent.com/jerkar/springboot-plugin/master/breaking_versions.txt";
}
There is many way to perform multi-project build. One of is to import runs from external projects.
A Jeka class instance can import Jeka class instances from other projects.
The current def classpath is augmented with the def 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 Jeka class from an external project, use the @JkDefImport
annotation as shown below :
public class MyCommands extends JkCommandSet {
@JkDefImport("../otherProject")
private BarCommands importedCommands;
public void doSomesthing() {
importedCommands.doBar(); // use the command class defined in ../otherProject
...
CommandSet classes are imported transitively, this means that, in above example, if BarCommands
imports an other project, this
last will be also imported.
Options mentioned in command line are propagated to the imported Jekas.
So for example you execute jeka 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 jeka clean
will
only clean the current run project.
To propagate method call to every imported Jeka classes, method name should be prefixed with a '*'. Executing jeka clean*
will
invoke 'clean' method on the current Jeka class along along all imported Jeka classes.
You can access to the list of imported Jeka classes within using JkCommandSet#getImportedCommandSets
methods as show below :
public class MyRun extends JkCommandSet {
...
public void doForAll() {
this.clean();
this.getImportedCommandSets().getAll().forEach(JkRun::clean);
this.getImportedCommandSets().getAllOf(JkJavaProjectBuild.class).forEach(build -> build.java().pack());
}
CommandSet classes and plugins can provide self documentation.
When properly auto-documented, users can display documentation by executing jeka help
.
The displayed documentation consist in :
@JkDoc
annotation at class level.@JkDoc
.@JkDoc
annotation on public fields.If Jeka 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 Jeka class or plugin declares a command 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 Jeka project Jeka class :
Usage: jeka [methodA...] [pluginName#methodB...] [-optionName=value...] [-pluginName#optionName=value...] [-DsystemPropName=value...]
Execute the specified methods defined in Jeka class or plugins using the specified options and system properties.
When no method specified, 'doDefault' method is invoked.
Ex: jeka 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).
-CommandClass (shorthand -CC) : Force to use the specified class as the Jeka class to be invoked. It can be the short name of the class (without package prefix).
Available methods and options :
From class 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 JkCommandSet :
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 'jeka [pluginName]#help' to get help on a perticular plugin (ex : 'jeka java#help').
Type 'jeka help -Plugins' to get help on all available plugins in the classpath.
Jeka 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 Jeka-core jar embeds third party jars as Ivy or BouncyCastle. Those are not visible/accessible to client code due to a specific classloading mechanism, preventing version clashing with client dependencies.
Jeka tries to stick with a consistent API design style.
Class and Interface Naming Convention
All Jeka public classes/interfaces start with Jk
. The reason is for easing distinction, in IDE, between classes supposed be used
in production or test and the ones used for building. It also helps to explore Jeka API.
Mutable Vs Immutable
As a rule of thumb Jeka favors immutable objects for shallow structures and parent-chaining trees for deeper ones. Both provide a fluent interface when possible.
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, only computation) starts with get
.
Withers/Anders for Immutable Objects
To create a subtly different object from another immutable one, Jeka 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, Jeka provides :
set
to replace a single property value by another.add
to add a value to a collection property.
Those methods return the object itself for chaining.Translators
To translate an object to another representation (for example a JkDependencySet
to a list of JkScopedDependency
)
Jeka 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 Jeka. In a glance these are the domains covered by the Jeka APIs :
File manipulation is a central part for building software. Jeka embraces JDK7 java.nio.file API by adding some concepts around, to provide a powerful fluent style API performing recurrent tasks with minimal effort.
The following classes lie in dev.jeka.core.api.file
package:
JkPathFile
A simple wrapper for files (not folders). It provides copying, interpolation, checksum, deletion and creation methods.
JkPathSequence
An Immutable sequence of java.nio.file.Path
providing methods for filtering or appending.
JkPathMatcher
An immutable java.nio.file.PathMatcher
based on java.nio.file
glob pattern or regerxp.
Used by JkPathTree
to filter in/out files according name patterns.
JkPathTree
An Immutable root folder (or a zip file) along a PathMatcher
providing operations to copy, navigate, zip or iterate.
This is a central class in Jeka API.
JkPathTreeSet
An Immutable set of JkPathTree
. Helpful to define set of sources/resources and create jar/zip files.
JkResourceProcessor
A mutable processor for copying a set of files, preserving the structure and
replacing some text by other text. Typically, used for replacing token as ${server.ip}
by an actual value.
// creates a file and writes the content of the specified url.
JkPathFile.of("config/my-config.xml").createIfNotExist().replaceContentBy("http://myserver/conf/central.xml");
// copies all non java source files to another directory preserving structure
JkPathTree.of("src").andMatching(false, "**/*.java").copyTo("build/classes");
// One liner to zip an entire directory
JkPathTree.of("build/classes").zipTo(Paths.get("mylib.jar"));
The dev.jeka.core.api.system
package provides system level functions :
JkInfo
Provides meta information as the running version of Jeka.
JkLocator
Provides information about where is located repository cache or Jeka user home.
JkLog
Provides API to log Jeka event. It supports hierarchical logs through #startTask
and #endtask
methods.
JkProcess
Launcher for external process.
JkPrompt
One-liner to ask user input.
For Jeka, 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
.
Jeka 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) : Jeka can consume and resolve transitively any artifact located in a repository as you would do with Maven, Ivy or Gradle.For the last, Jeka is using Ivy 2.5.0 under the hood. This library is embedded inside the Jeka jar and is executed in a dedicated classloader. So all happens as if there where no dependency at all.
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 tag dependencies according their usage, Jeka 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 a fluent API for easier instantiation.
import static dev.jeka.core.api.depmanagement.JkScopes.*;
...
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
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 : Jeka 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 Jeka 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 projects : one is built with Maven, the other with Jeka.
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-jeka-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();
Jeka 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 Jeka proposes specific APIs according you want to publish on a Maven or Ivy repository.
Jeka proposes a complete API to pubish on Maven repository. POM files will be generated by Jeka 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 Jeka",
"http://project.jeka.org")
.andApache2License()
.andDeveloper("djeang", "myemail@gmail.com", "jeka.org", "http://project.jeka.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 Jeka allows to :
JkRepoSet
instead of a JkRepo
.To sign with PGP, no need to have PGP installed on Jeka machine. Jeka 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());
dev.jeka.core.api.java.testing
package hosts classes to handle Java builds.
JkClassLoader
and JkUrlClassloader
Wrap a java.lang.ClassLoader
adding convenient methods and classpath scanning capability.
JkClassPath
A sequence of file to be used as a class path
.
JkJarPacker
A simple utility tyo create Jar or fat Jar file from compiled classes.
JkJavaCompiler
Wraps either a Java Compiler tool, nor a javac process.
JkJavadocProcessor
A Java source processor producing standard Javadoc
JkJavaProcess
A utility to launch Java process (from class dirs or jars)
JkManifest
Stands for the manifest file to include in jar files.
dev.jeka.core.api.java.testing
package hosts three classes that let user launch test based
on the Junit5 platform. This means that any test framework supported by Junit5 platform,
is also supported by Jeka. Jeka also supports Junit4 out-of-the-box through the embedded
vintage engine.
Jeka testing API mostly hides Junit Platform. For most of the cases, you won't need to code against Junit-Platform API to launch tests with Jeka. Nevertheless, Jeka allows users to code against Junit-Platform for fine-tuning.
JkTestProcessor
This is the entry point to launch tests. Tests are executed using the
current classloader classpath + extra class path mentioned in #launch
method arguments.
JkTestResult
The result of a test launch : count for found, failure, skip, success ...
JkTestSelection
A mean to determine which test to launch. It can be set using file or tag filter. It is
also possible to code against JUnit Platform
Projects are file structures for hosting Java projects meaning source code, test codes, dependencies, build instructions.
The principle is that each JkJavaProject
holds everything needed to compile, test, pack and publish
artifacts. The API embrace the parent-chaining pattern.
This is a pretty complete example taken from the Jeka build itself.
project
.getJarProduction()
.getDependencyManagement()
.addDependencies(JkDependencySet.of()
.and("com.google.guava:guava:21.0")
.and("com.sun.jersey:jersey-server:1.19.4")
.and("org.junit.jupiter:junit-jupiter-engine:5.1.0", TEST)
.and("org.junit.vintage:junit-vintage-engine:jar:5.6.0", TEST)).__
.getManifest()
.addMainClass("dev.jeka.core.tool.Main").__
.getCompilation()
.getLayout()
.includeSourceDirsInResources().__
.addOptions("-Xlint:none","-g")
.setJavaVersion(JkJavaVersion.V8)
.getCompiler()
.setForkingWithJavac().__.__.__
.getTesting()
.getCompilation()
.getLayout()
.includeSourceDirsInResources().__
.getCompiler()
.setDefault().__.__
.getTestProcessor()
.getEngineBehavior()
.setProgressDisplayer(JkTestProcessor.JkProgressOutputStyle.ONE_LINE).__.__
.getTestSelection()
.addIncludePatterns(JkTestSelection.STANDARD_INCLUDE_PATTERN)
.addIncludePatternsIf(runIT, JkTestSelection.IT_INCLUDE_PATTERN).__.__
.getDocumentation()
.getJavadocProcessor()
.setDisplayOutput(false)
.addOptions("-notimestamp").__.__
.getPublication()
.setModuleId("dev.jeka:jeka-core")
.setVersionSupplier(git::getJkVersionFromTags)
.setRepos(JkRepoSet.ofOssrhSnapshotAndRelease(ossrhUser, ossrhPwd))
.getArtifactProducer()
.putMainArtifact(this::doPackWithEmbedded)
.putArtifact(DISTRIB_FILE_ID, this::doDistrib)
.putArtifact(WRAPPER_ARTIFACT_ID, this::doWrapper).__
.getMavenPublication()
.getPomMetadata()
.getProjectInfo()
.setName("jeka")
.setUrl("https://jeka.dev")
.setDescription("Automate with plain Java code and nothing else.").__
.getScm()
.setUrl("https://github.com/jerkar/jeka.git").__
.addApache2License()
.addGithubDeveloper("djeang", "djeangdev@yahoo.fr").__.__
.getPostActions()
.append(() -> createGithubRelease());
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 dev.jeka.core.api.tooling
package provides integration with tools developers generally deal with.
JkEclipseClasspathGenerator
and JkEclipseProjectGenerator
provides method to generate a proper .classpath and .project file respectively.
JkEclipseClasspathApplier
reads information from a .classpath file.
JkIntellijImlGenerator
generates proper .iml files.
JkGitWrapper
wraps common Git commands in a lean API.
JkMvn
wraps Maven command line in a lean API
JkPom
reads POM/BOM to extract information like : declared dependencies, dependency management, repos,
properties, version and artifactId.