Jeka - Reference Guide

Author : Jérôme Angibaud
Version : HEAD-SNAPSHOT

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

Introduction

This document stands for reference guide to provide details about Jeka behaviour. 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.

However, a document can not replace a source code or API for exhaustion. Jeka 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 Jeka engine room. For Java developers, reading source code and placing break points troubleshoots faster than documentation/support most of the time.

What is Jeka ?

Jeka contains both a library and a tool.

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

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

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

Library

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 but these libraries are embedded in the Jeka 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, Jeka 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 : building the depender project implies building the core project if not already done.

API Style

Jeka tries to stick with a consistent API design style.

Class and Interface Naming Convention

All Jeka public classes/interfaces starts with Jk. The reason is 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 Jeka favors immutable objects. Nevertheless when object structure is getting 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, only computation) starts with get.

Withers/Anders for Immutable Objects

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

Setters/Adders for Mutable Objects

To modify a mutable object, Jeka provides :

Translators

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

Domains Covered by the API

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

Files

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

The following classes lie in dev.jeka.core.api.file package :

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

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

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

Used by JkPathTree to filter in/out files according name patterns.

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

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

Instances of this class are used by Java project api to defines source and resource files. It also helps to create fat jars.

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

System

The dev.jeka.core.api.system package provides systeml level functions :

Dependency Management

Concepts

What is a dependency ?

In Jeka 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.

Jeka distinguishes 3 types of dependency :

For the last, Jeka is using Ivy 2.4.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 on Ivy.

What is a scope ?

Projects may need dependencies to accomplish certain tasks and these dependencies may vary according the executed tasks. For example, to compile you may need guava library only but to test you'll need junit library too. To 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.

What is a scoped dependency ?

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

Define a set of dependencies

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

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

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

Note that :

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

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

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

- PROVIDED
org.projectlombok:lombok:1.16.16

HHierarchy of Dependency Types.

Dependencies on Module

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

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

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

Note that :

Dependencies on local files

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

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

Dependencies on files produced by computation

It is typically used for multi-modules or multi-techno projects.

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

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

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

The following snippet constructs a set of dependencies on two external 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);

Resolve Dependencies

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

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

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

From the result you can :

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

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

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

Publication

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.

Publish to a Maven 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 :

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

Publish to a Ivy repository

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

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

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

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

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

Java Project Making

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

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

Compilation

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

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

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

Javadoc

Simple Javadoc tasks can be performed using JkJavadocMaker class.

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

Classpath

Jeka provides JkClasspath to construct and reason about classpath.

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

Java jar and manifest

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

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

JkManifest.ofEmpty().addMainClass("RunClass").writeToStandardLocation(classDir);

Classloaders

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

JkUrlClassloader provides classpath scanning functions.

Junit tests

The following snippet shows how to launch Junit tests programmatically.

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

Projects

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

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

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

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

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

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

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

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

Tool Part

Lexical

The following concepts are used all over the tool section :

[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 depends 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 another project. Once def classes sources have been compiled, def Classpath is augmented with their .class counterpart.

Command Classes : Classes extending JkCommands. 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 command class though there can be many or none. Command class can be a def class but can also be imported from a library or external project.

Commands : Java methods member of command classes and invokable from Jeka command line. They must be instance method (not static), public, zero-args and returning void. Every method verifying these constraints 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.

In a Glance

The Jeka 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]
   |
   + jeka
      + boot             <-------- Put extra jars here to augment def Classpath.
      + def
         + MyCommands.java   <----- Class extending JkCommands
         + MyUtility.java   <---- Utility class consumed by MyRun
      + output              <---- Build artifacts are generated here
   + src
      + main
          + java
          + resources
   + ...

A command class may look like :

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import dev.jeka.core.tool.JkDoc;
import dev.jeka.core.tool.JkImport;
import dev.jeka.core.tool.JkCommands;
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 MyCommands extends JkCommands {    // 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 :

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

If no command class are present in def classes, Jeka picks JkCommands. 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.

Jeka Runtime

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 8. JDK is required and JRE is not sufficient as Jeka uses the JDK tools to compile def classes.

Jeka commands can be launched from both command line and your IDE.

Jeka from Command line

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

This script does the following :

  1. Find the Java executable path : Look first at JEKA_JDK environment variable then JAVA_HOME. If no such variables are defined it takes the one lying in this JDK, otherwise it takes the one accessible in the PATH of your OS. JEKA_JDK/bin/java or JAVA_HOME/bin/java must resolve to the Java executable.
  2. Get java execution option : If an environment variable JEKA_OPTS exists then its value is passed to the java command line parameters.
  3. Get the classpath in the following order :
  4. Run the java process for launching Main class passing the command line argument as is. This class main method does the following :
    1. Parse the command line.
    2. Populate system properties from configuration files and command line.
    3. Pre-process def classes . In this step, def class code is parsed to detect 3rd party and external project imports. Imports are added to the def classpath.
    4. Compile def classes using the classpath computed in previous step.
    5. Select the command class to be run.
    6. Instantiate selected command class, inject options and bind plugins on it.
    7. Invoke methods specified in command line arguments : methods are executed in the order they appear on the command line.

The following sub-sections detail about these steps.

Parse the Command Line

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

Populate System Properties from Configuration Files and Command line

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.

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

In order to compile def classes, Jeka has to compute def Classpath first. With Jeka 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 JkCommands {

    @JkImportProject("../another/project/using/jeka")
    private OtherRun otherRun;  // Command 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 using repository url defined by in order :

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.run.url=http://my.repo1, http://my.repo2.snapshot).

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

Compile Def Classes

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/output/def-classes directory.

Jeka uses the compiler provided by the running JDK.

Select Command Class

Once compiled, Jeka augments the def Classpath with classes compiled in previous step. Then it selects one command class from def classpath and instantiate it.

The selection logic is :

Instantiate Command Class

The commands instantiation process is defined in ork.jeka.tool.JkCommands#of factory method. It consists in :

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

Invoke Command Line Methods

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

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

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

Error Handling

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, only the message is displayed.

Jeka from IDE

IDE Classpath Setting

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

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

Launch from IDE

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

To launch 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).doDefault();
} 

The JkInit#instanceOf method loads options from args and instantiates command 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.

Embedded Mode

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 :

  1. Copy [JEKA_HOME]/dev.jeka.jeka-core.jar into [PROJECT_DIR]/jeka/boot/* directory.
  2. Copy [JEKA_HOME]/jeka.bat and [JEKA_HOME]/jeka at the root of [PROJECT_DIR] (optional).

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 :

Default Path Settings

Specify Jeka User Home

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.

Specify the Local Repository Cache

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.

See Effective Paths

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

...

Commands Parameters

Jeka commands are parameterizable. One can retrieve values defined at runtime by reading :

Environment Variables

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

System Properties

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

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

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

Jeka Options

Jeka options are similar to system properties as it stands for a set of key/value.

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

Inject Options

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

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

Retrieve Options as String Values

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

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

Retrieve Option in Command Class Fields

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

For example, if you declare a field like :

class MyRun extends JkCommands {
   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

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 command class :

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

Then you can inject the server object using following options :

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

Document Options

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

For example :

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

Built-in Options

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

Plugins

Jeka provides a pluggble 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 instance is owned by a JkCommands object, and can access to it through JkPlugin#owner protected field.

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 the jar or directory containing the plugin class to your 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 .

Load Plugins

Plugins need not to be mentioned in command class code in order to be bound to the JkCommands 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 JkCommands instance. 'java' plugin instance will modify 'scaffold' plugin instance in such it produces a command class code extending JkJavaProjectBuild instead of 'JkCommands' 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 command class code as below. That way, you don't need to mention java# in command line.

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

Modify Owing JkCommands Instance

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

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

For example, Jacoco Plugin does not provide command 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 JkCommands instance, for example Scaffold Plugin does not override activate method, therefore it has no side effect on its owning JkRun instance. It only features commands along options.

Configure Plugins in JkCommands Class

There is three places where you can configure plugins :

Example of configuring a plugin in command class.

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

Jeka own build class makes a good example.

Document Plugins

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

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

A good example is Java Plugin

Import External Runs

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

Principle

A command class instance can import command 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.

Declare Run Import

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

public class MyCommands extends JkCommands {
    
    @JkImportProject("../otherProject")   
    private BarCommands importedCommands;  

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

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

Option Propagation

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

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

Method propagation

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

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

Access Imported Runs Programmatically

You can access to the list of imported command classes within using JkCommands#getImportedCommands methods as show below :

public class MyRun extends JkRun{

    ...

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

Self Documentation

Command classes and plugins can provide self documentation.

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

The displayed documentation consist in :

If command 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 command 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 command class :

Usage: jeka [methodA...] [pluginName#methodB...] [-optionName=value...] [-pluginName#optionName=value...] [-DsystemPropName=value...]
Execute the specified methods defined in command 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 command 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 JkCommands :
  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.