The following concepts are used all over this document :
[PROJECT DIR] : Refers to the root folder of the project to build (or to run commands 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 depend def classes to get compiled and command 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 imported project.
Command Classes : Classes extending JkCommands
. Their public no-arg methods can be invoked from the command line and
their pubic fields set from the command line. Generally def classes contains one command class though there can be many or
none. Command classes can come from def classes but can also be imported from a library or external project.
Commands : Java methods member of command classes and invokable from command line. They must be instance method (not static), public, zero-args and returning void. Every method verifying these constraints within a command class or a plugin is considered as 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 command classes.
Plugins : Classes extending JkPlugin
and named as JkPluginXxxxx where_Xxxxx_ is the name of the plugin. In short, a plugin
add dynamically commands and options to the running command class.
Thank to the wrapper feature, you don't need to install Jeka on your machine to run it... except if you are creating projects from scratch. As it's what we are going to do in this tutorial, you need to get it properly installed.
jeka help
in the command line. You should get an output starting by :Usage:
jeka (method | pluginName#method) [-optionName=<value>] [-pluginName#optionName=<value>] [-DsystemPropertyName=value]
Executes the specified methods defined in command class or plugins using the specified options and system properties.
Ex: jeka clean java#pack -java#pack.sources=true -LogVerbose -other=xxx -DmyProp=Xxxx
...
_______ _
(_______) | |
_ _____| | _ _____
_ | | ___ | |_/ |____ |
| |_| | ____| _ (/ ___ |
\___/|_____)_| \_)_____|
The pure Java build tool.
Working Directory : C:\Users\djeang\IdeaProjects\jeka
Java Home : C:\Program Files (x86)\Java\jdk1.8.0_121\jre
Java Version : 1.8.0_121, Oracle Corporation
Jeka Version : null
Jeka Home : C:\Users\djeang\IdeaProjects\jeka\dev.jeka.core\jeka\output\distrib
Jeka User Home : C:\Users\djeang\.jeka
Jeka Run Repositories : [https://repo.maven.apache.org/maven2/, file:/C:/Users/djeang/.jeka/maven-publish-dir/]
Jeka Repository Cache : C:\Users\djeang\.jeka\cache\repo
Jeka Classpath : C:\Users\djeang\IdeaProjects\jeka\dev.jeka.core\jeka\output\distrib\dev.jeka.jeka-core.jar
Command Line : -LH help
Specified System Properties : none.
Standard Options : RunClass=null, LogVerbose=false, LogHeaders=true, LogMaxLength=230
Options : LH=null LML=230 jdk.9=C:/Program Files (x86)/Java/jdk9.0.1 jdk.10=C:/Program Files (x86)/Java/jdk10.0.2 repo.download.url=https://repo.maven.apache.org/maven2/
Compile and initialise command classes ...
│ Initializing class JkCommands at C:\Users\djeang\IdeaProjects\jeka ...
│ │ Run instance initialized with options []
│ └ Done in 57 milliseconds.
└ Done in 336 milliseconds.
Jeka commands are ready to be executed.
Method : help on JkCommands
Usage:
jeka (method | pluginName#method) [-optionName=<value>] [-pluginName#optionName=<value>] [-DsystemPropName=value]
Execute the specified methods defined in command class or plugins using the specified options and system properties.
Ex: jeka clean java#pack -java#pack.sources=true -LogVerbose -other=xxx -DmyProp=Xxxx
...
Method help succeeded in 660 milliseconds.
______ _
/ _____) | |
( (____ _ _ ____ ____ _____ ___ ___ | |
\____ \| | | |/ ___) ___) ___ |/___)/___) |_|
_____) ) |_| ( (__( (___| ____|___ |___ | _
(______/|____/ \____)____)_____|___/(___/ |_|
Total run duration : 1.159 seconds.
For now, there is no valuable Jeka Visual Plugin for your IDE but you can work pretty well without thanks to Intellij and Eclipse Jeka plugin whose generating IDE metadata files (.iml and .classpath).
But first you have to instruct your IDE where is located Jeka distribution and repositories.
Declare the 2 path variables (go settings -> Apparence & behavior -> Path Variables)
JEKA_HOME
which point to [Jeka Home],JEKA_USER_HOME
which point to [Jeka User Home]Declare the 2 classpath variables in Eclipse.
JEKA_HOME
which point to [Jeka Home],JEKA_USER_HOME
which point to [Jeka User Home].By default [Jeka User Home] point to [User Home]/.jeka but can be overridden by defining the environment
variable JEKA_USER_HOME
.
First, let's create a simple automation project that read content from url and display it on the console.
jeka scaffold#run intellij#
under this directory (replace intellij#
by eclipse#
if you're using Eclipse).sample1
+ jeka
+ def <----- Java code that build your project goes here
+ Build.java
+ output <---- Genererated files are supposed to lie here
+ sample1.iml <----- Intellij metadata containing project dependencies (At least dev.jeka.core)
import dev.jeka.core.tool.JkCommands;
import dev.jeka.core.tool.JkInit;
class Commands extends JkCommands {
public static void main(String[] args) {
Commands commands = JkInit.instanceOf(Commands.class, args);
commands.clean();
}
}
Add the following method to the Commands
class.
import dev.jeka.core.api.utils.JkUtilsIO;
import dev.jeka.core.tool.JkCommands;
import dev.jeka.core.tool.JkInit;
import java.net.MalformedURLException;
import java.net.URL;
class Commands extends JkCommands {
public void displayGoogle() throws MalformedURLException {
String content = JkUtilsIO.read(new URL("https://www.google.com/"));
System.out.println(content);
}
public static void main(String[] args) throws Exception {
JkInit.instanceOf(Commands.class, args).displayGoogle();
}
}
Open a console/:terminal in sample1 directory and execute jeka displayGoogle
. You should see the Google source displayed.
Execute jeka help
and the output should mention your new method.
...
From class Commands :
Methods :
displayGoogle : No description available.
From class JkCommands :
Methods :
clean : Cleans the output directory except the compiled command classes.
help : Displays all available methods and options defined for this command class.
...
Any public instance method with no-args and returning void
fits to be a command. You can call several commands in a single row.
You can also launch/debug command directly from your IDE, using the main method. Note that for so, you must instantiate
your command class using JkInit.instanceOf
.
Add the following annotation to the command.
@JkDoc("Fetch Google page and display its source on the console.")
public void displayGoogle() throws MalformedURLException {
String content = JkUtilsIO.read(new URL("https://www.google.com/"));
System.out.println(content);
}
Execute jeka help
and the output should mention documentation.
From class Build :
Methods :
displayGoogle : Fetch Google page and display its source on the console.
May you like to see Google page source but you probably want to apply this method to any other url.
To make it configurable, just declare the url in a public field so its value can be injected from command line.
class Commands extends JkCommands {
@JkDoc("The url to display content.") // Optional self documentation
public String url = "https://www.google.com/";
@JkDoc("Fetch Google page and display its source on the console.")
public void displayContent() throws MalformedURLException {
String content = JkUtilsIO.read(new URL(url));
System.out.println(content);
}
public static void main(String[] args) throws Exception {
JkInit.instanceOf(Commands.class, args).displayContent();
}
}
Execute jeka displayContent -url=https://github.com/github
and you should see the Github page source displayed.
If you execute jeka help
you should see the url option mentioned.
...
From class Commands :
Methods :
displayContent : Fetch Google page and display its source on the console.
Options :
-url (String, default : https://www.google.com/) : The url to display content.
...
You can mention inline the external libraries you need to compile and execute your command class. For exemple, you main need Apache HttpClient library to perform some non basic HTTP tasks.
@JkImport("org.apache.httpcomponents:httpclient:jar:4.5.8") // Can import files from Maven repos
@JkImport("../local_libs/my-utility.jar") // or simply located locally
class Commands extends JkCommands {
...
}
Execute jeka intellij#iml
or jeka eclipse#files
to add properly the dependencies to your IDE (You may need to refresh it).
You can add code depending on the imported libs
import org.apache.http.client.methods.HttpPost;
...
public void post() {
HttpPost httpPost = new HttpPost();
httpPost.setHeader("content-type", "application/json");
...
}
Execute post method as usual : jeka post
.
Imagine that you want to want to reuse displayContent method from project sample1 in another project. Let's create a new sample2 project located in a sibling folder than sample1.
mkdir sample2
then cd sample2
followed by jeka scaffold#run intellij#
(or jeka scaffold#run eclipse#
)JkImportProject
and the relative path of sample1 as value.class Sample2Commands extends JkCommands {
@JkImportProject("../sample1")
private JkCommands sample1Commands;
public void hello() throws MalformedURLException {
System.out.println("Hello World");
}
}
Execute jeka intellij#iml
(or jeka eclipse#files
) to add sample1 dependencies to your IDE. Now Sampl2Commands can refer to the command class of sample1.
Replace JkCommands Type by the Commands type from sample1 and use it in method implementation.
class Sample2Commands extends JkCommands {
@JkImportProject("../sample1")
private Commands sample1Commands; // This class comes from sample1
public void printUrlContent() throws MalformedURLException {
System.out.println("Content of " + sample1Commands.url);
sample1Commands.displayContent();
}
}
Executing jeka printUrlContent
displays :
Content of https://www.google.com/
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="nl-BE"><head><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"><meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.pn
g" itemprop="image"><title>Google</title><script nonce="JkJFrHNh1i7pdGGBGDk/tw==">(function(){window.google={kEI:'AyndXKnDGrLgkgW-kp7gAw',kEXPI:'0,1353747,57,1958,1640,782,698,527,731,223,1575,1257,1894,58,320,207,1017,167,438,
...
You can set directly the value of the url on the command line as option values are injected on all imported commands recursively.
jeka printUrlContent -url=https://github.com/github
displays :
Content of https://fr.wikipedia.org
<!DOCTYPE html>
<html class="client-nojs" lang="en" dir="ltr">
<head>
<meta charset="UTF-8"/>
<title>Wikipedia, the free encyclopedia</title>
...
Except that command classes must have the same name than their filename, there is not known restriction about what you can do with command classes or def classes. You can define as many classes as you want into def directory. Organise them within Java packages or not.
Each command class instance acts as a registry for plugins. In turn, plugins can interact each other through this registry.
Let's implements similar commands than previously but using plugins :
@JkDoc("Provide command and option to display url content on the console")
public class JkPluginWebReader extends JkPlugin {
@JkDoc("The url to display content.")
public String url = "https://www.google.com/";
protected JkPluginWebReader(JkCommands commands) {
super(commands);
}
@JkDoc("Display source cotent of the url option on the console.")
public void displayContent() throws MalformedURLException {
String content = JkUtilsIO.read(new URL(url));
System.out.println(content);
}
}
jeka help
in the console at the root of the project. At the end of the output your plugin should be
mentioned :Available plugins in classpath : eclipse, eclipsePath, git, intellij, jacoco, java, pgp, pom, repo, scaffold, sonar, war, webReader.
Jeka has discovered automatically your plugin called 'webReader'. For this your plugin class must follow 3 requirements :
public
JkPlugin
Now you can execute jeka webReader#help
to display which options and commands are available on this plugin.
To execute displayContent
command : jeka webReader#displayContent -webReader#url=https://twitter.com
.
You don't need to have a command class defined in jeka/def. By default Jeka uses dev.jeka.core.tool.JkCommands
.
Mentioning webReader#
on the command line has instantiated JkPluginWebReader class, attaching it to the current command class instance.
Mentioning webReader#displayContent -webReader#url=https://twitter.com
has injected the url value and invoke
JkPluginWebReader#displayContent
method on the instance plugin.
This mechanism is similar to options/commands existing on command class.
You may need to configure default options on plugins and invoke them from the command classes without specifying everything from the command line.
For such create a command class within the same project and declare the plugin as below. In the command class constructor you can redefine plugin state as its default field values.
public class MyCommands extends JkCommands {
private final JkPluginWebReader webReaderPlugin = getPlugin(JkPluginWebReader.class);
MyCommands() {
webReaderPlugin.url = "https://twitter.com"; // define a new default url
}
public void show() throws MalformedURLException {
webReaderPlugin.displayContent();
}
}
Now, if you execute jeka webReader#help
you'll notice that the default url
value is now the one defined above.
You can also use jeka show
as a shorthand for jeka webReader#displayContent
.
Note : You can also configure plugin by overriding JkCommands@setup
methods. In this case
the values set here will override the ones provided by command line.
It's quite common to have a plugin that acts as an enhancer of another one. For example Jacoco plugin enhance Java plugin by adding test coverage when tests are run.
Of course this kind of mechanism is possible because Java plugin test feature has been designed to be extendable but the idea is that a plugin can access or load any other plugins for its command class owner.
Let's modify the webReader plugin in order it can allow the url be modified from the outside.
public class JkPluginWebReader extends JkPlugin {
@JkDoc("The url to display content.")
public String url = "https://www.google.com/";
private UnaryOperator<URL> urlTransformer = url -> url;
protected JkPluginWebReader(JkCommands commands) {
super(commands);
}
@JkDoc("Fetch Google page and display its source on the console.")
public void displayContent() throws MalformedURLException {
URL effectiveUrl = urlTransformer.apply(new URL(url));
System.out.println("Reading content from " + effectiveUrl);
String content = JkUtilsIO.read(effectiveUrl);
System.out.println(content);
}
public void setUrlTransformer(UnaryOperator<URL> urlTransformer) {
this.urlTransformer = urlTransformer;
}
}
Now let's create a plugin HttpsIzer in jeka/def that forces the webReader plugin to use https protocol.
public class JkPluginHttpsIzer extends JkPlugin {
protected JkPluginHttpsIzer(JkCommands commands) {
super(commands);
}
@Override
protected void activate() {
boolean webReaderPresent = this.getCommands().getPlugins().hasLoaded(JkPluginWebReader.class);
if (!webReaderPresent) {
return;
}
UnaryOperator<URL> urlTransformer = url -> {
try {
return new URL("https", url.getHost(), url.getPort(), url.getFile());
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
};
JkPluginWebReader webReader = this.getCommands().getPlugins().get(JkPluginWebReader.class);
webReader.setUrlTransformer(urlTransformer);
}
}
Now, if you execute jeka webReader#displayContent -webReader#url=http://jeka.dev httpsIzer#
console will display :
Reading content from https://jeka.dev
...
Explanation: Adding httpIzer#
instantiates and binds httpIzer plugin to the running command class instance.
During this process, JkPlugin#activate
method is invoked. By default this method does nothing but as we have
overrode it in JkPluginHttpsIzer
, it modifies the JkPluginWebReader
plugin bound to the running command class instance.
You can pack your plugin in a jar, publish it on a repository and reuse it any build. The prerequisite is to know about building a Java project which is covered in the next section.
For those just create a regular Java project and add Jeka as PROVIDED dependency.
// Use same Jeka version both for building and compiling
project.addDependencies(JkDependencySet.of()
.andFile(JkLocator.getJekaJarPath(), PROVIDED));
Code your plugin in src/main/java as you will do for any regular Java project. Of course you an use any third party lib as you want but keep in mind that the more deps you have the more dependency issues you may face with other plugins.
Execute Java java#pack java#publish
to build your plugin project and deploy it to your default repository.
You have to import the jar containing the plugin you want to use.
As for any other jar, you can declare it in a @JkImport
annotation.
@JkImport("org.myorg:myjekaplugin:0.1")
class MyBuid extends JkCommands {
}
If you don't have declared it in a command class @JkImport
annotation you can still invoke it from
the command line : jeka @org.myorg:myjekaplugin:0.1 myPlugin#doSomething
.
Now let's start more complicated tasks as building a Java poject. It involves compilation, testing, packaging, dependency resolution, releasing, ... There's many option to handle it in Jeka :
The one you choose is a matter of taste, flexibility, verbosity, reusability and integration with existing tools.
TODO
TODO
jeka scaffold#run java#
under this directory.
This will generate a project skeleton with the following build class at [PROJECT DIR]/build/def/Build.javamygroup.myproject
+ jeka
+ def <----- Java code that build your project goes here
+ Build.java
+ output <---- Build artifacts are generated here
+ src
+ main
+ java <----- Your project java sources and resources for production go here
+ test
+ java <----- Your project java sources and resources for testing go here
Explanation : scaffold#run
invokes 'run' method on the 'scaffold' plugin. java#
forces the java
plugin to be loaded. When loaded,
'java' plugin has the effect to instruct scaffold plugin extra actions for generating a Java project.
By default the project mimics Maven layout convention so sources are supposed to lie in src/main/java.
Below is the content of the generated build class. Guava and Junit are pesent only fo demo purpose. You can remove it safely and add any dependency you need.
import dev.jeka.core.api.depmanagement.JkDependencySet;
import dev.jeka.core.api.java.project.JkJavaProject;
import dev.jeka.core.tool.JkInit;
import dev.jeka.core.tool.JkCommands;
import dev.jeka.core.tool.JkPluginJava;
import static dev.jeka.core.api.depmanagement.JkJavaDepScopes.*;
class Build extends JkCommands {
final JkPluginJava javaPlugin = getPlugin(JkPluginJava.class);
/*
* Configures plugins to be bound to this command class. When this method is called, option
* fields have already been injected from command line.
*/
@Override
protected void setup() {
JkJavaProject project = javaPlugin.getProject();
project.addDependencies(dependencies());
}
private JkDependencySet dependencies() { // Example of dependencies.
return JkDependencySet.of()
.and("com.google.guava:guava:21.0")
.and("junit:junit:4.11", TEST);
}
public static void main(String[] args) {
JkInit.instanceOf(Build.class, args).javaPlugin.clean().pack();
}
}
Execute jeka java#info
to see an abstract of the project setup.
jeka clean java#pack
under the project base directory. This will compile, run test and package your project in a jar file. You can also lauch the main
method from your IDE.If you want to create javadoc, jar sources and jar tests or checksums :
just execute jeka clean java#pack -java#pack.tests -java#pack.sources -java#pack.checksums=sha-256
.
Explanation '-' prefix means that you want to set an option value. For example -java#pack.sources
means that
JkPluginJava.pack.sources
will be injected the 'true' value.
You can also set it by default in the build class constructor :
protected Build() {
javaPlugin.pack.javadoc = true;
javaPlugin.pack.sources = true;
javaPlugin.pack.tests = true;
javaPlugin.pack.checksums = "sha-256";
}
Execute jeka help
to display all what you can do from the command line for the current project. As told on the help screen,
you can execute jeka aGivenPluginName#help
to display help on a specific plugin.
The list of available plugins on the Jeka classpath is displayed in help screen.