Welcome to Jeka
      Execution engine
            Jeka directory content
            What is Jeka wrapper
            What is inside
            Setup
                  Install
                  Or use a Template Wrapper Project
                  Setup IDE
            Basic Project
                  Create Project Structure
            KBeans
                  Create a Basic KBean
                  3rd party dependencies
                  Import
                  Launch and Debug from the IDE
                        Create a
                        Configure an IDE Launcher
            More about KBeans
            Properties
            Useful commands
            Useful standard options
            How to change the JDK that will run
            How to change the repository
      The Build Library
            API Style
            Domains Covered by the API
            Files
            System
            Dependency Management
                  Concepts
                        Dependency
                              JkModuleDependencies (dependency on module through coordinates)
                              JkFileSystemSependency (dependency on local files)
                              JkComputedDependenciy (dependency on files produced by computation)
                        DependencySet
                        Transiitivity
                  Resolve Dependencies
                  Publication
                        Publish to a Maven repository
                        Publish to a Ivy repository
      Project Building
            Java Tool Base API
            Testing API
            Project API
            Third Party Tool Integration
                  Eclipse
                  Intellij
                  Git
                  Maven

Welcome to Jeka

Jeka is a general-purpose build tool designed as a library. In other words, users describe programmatically the actions needed to achieve automation tasks, as they would do for regular code.

The library is designed to make complex common tasks as compiling, testing or resolving dependencies as concise as possible, so building projects may require less typing compare to other tools you may know, with simplicity and transparency as a bonus.

Jeka automation code can be run indifferently from IDE (via classic main method) or command line, thanks to a bundled lean and fast execution engine. This engine comes with a small set of concepts promoting simplicity, flexibility and re-usability.

Execution engine

Jeka directory content

By convention, every project automated or built by Jeka contains a jeka directory at its root ([Project Root]/jeka). This directory contains everything Jeka needs to automate or build the project.

In this directory, you may find :

Depending on your needs, feel free to store any build related elements in this directory (keys, document templates,...).

Besides, project root may also contains jekaw and jekaw.bat shell scripts to invoke Jeka wrapper conveniently.

For the following, when we refer to the command jeka, you can use ./jekaw indifferently. All command lines are supposed to be launched from the root of the project (and not from [Project Root]/jeka).

What is Jeka wrapper

Jeka wrapper consists in shell scripts, a thin booting jar and a configuration file in order Jeka can be executed on a specified version without being installed on the host machine. This is the recommended way of using Jeka as it makes builds portable from one machine to another.

What is inside [User Home]/.jeka

Jeka automatically creates a directory [User Home]/.jeka when running for the first time. This directory may contain

In the contrary of Maven, Jeka does not publish locally on the same repository where are downloaded dependency artifacts.

Setup Jeka

Note : Jeka organization provides a plugin to make the following tasks smoother or transparent. Here, we'll focus only on how to do it using command line.

Prerequisite : You need a JDK 8 or higher installed on your machine. By default, Jeka will use the Java executable found in your PATH environment variable. See later sections for changing this default.

There is two ways of using jeka : by installing Jeka itself of by using a template project containing the Jeka wrapper.

Install Jeka

Or use a Template Wrapper Project

Setup IDE

Basic Project

In first instance, we'll focus on how execution engine works. For simplicity's sake, we'll use trivial examples. Concrete real-life cases, as building projects, will be documented in specific sections.

Create Project Structure

Execute jeka -h (or simply jeka) to display a contextual help on the console.

KBeans

KBean is the central concept of execution engine. It consists in classes sharing characteristics :

KBeans can be declared in def directory as source file or just be present as classes in classpath (see later).

In a given project, there can only be one KBean instance per KBean class, but if you work with a multi-project build there can be several in classpath (one per project).

Generally KBeans interact with each other inside their init method. They access each other using getRuntime().getRegistry().get(MyBean.class).

When a KBean depends on another one, it's good to declare it as an instance property of the first bean as this dependency will be mentioned in the quto-generated documentation.

Create a Basic KBean

Extras

3rd party dependencies

Jeka embeds a bunch of utilities to perform build related tasks (file/zip manipulation, git, launching processes, compilation, testing, dependency management, crypto, ...) nevertheless, you may want rightfully to use some 3rd-party dependencies.

Import KBean from other Projects

In multi-project build, it's quite common that a KBean accesses to a KBean instance coming from another project. You can achieve it in a statically typed way.

Launch and Debug from the IDE

There's 2 ways of launching or debugging Jeka builds from IDE. We don't mention here, usage of Intellij plugin.

Create a main Method inside your def Classes

Create one or many main methods as :

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

    public static class Release {
        public static void main(String[] args) {
            JkInit.instanceOf(MasterBuild.class, args, "-runIT").release();
        }
    }

Build classes (inheriting JkClass) must be instantiated using JkInit#instanceOf in order it be setup in proper state.

The arguments passed in main method are interpreted as command line arguments.

Launching or debugging this way is performant as all build classes and their dependencies are already on classpath. Therefore, no compilation or dependency resolution is needed.

Be careful to launch the main method using module dir as working dir. On IntelliJ, this is not the default (it uses project dir).

To change intelliJ defaults, follow : Edit Configurations | Edit configuration templates... | Application | Working Directory : $MODULE_DIR$.

Configure an IDE Launcher

Sometimes, you may need to mimic closer the command line behavior, for debugging purpose or to pass '@' arguments.

More about KBeans

When executing, Jeka will first determine the default KBean to instantiate it. The default KBean is determined as follows :

  1. The KBean mentioned in command line -kb= option.
  2. The first KBean found in def dir according the fully qualified class name alphabetical order.

The KBean instantiation consists in :

  1. Call the constructor
  2. Inject properties in KBean fields
  3. Call its initmethod.

The init method of the default KBean can, in turn, inkove oth

Properties

Properties are pairs of String key-value that are used across Jeka system. It typically carries urls, local paths, tool versions or credentials. They can be globally accessed using JkProperties#get* static method.

Properties can be defined at different level, in order of precedence :

Standard properties :

Useful commands

Jeka comes with predefined methods coming either from JkClass or built-in plugins.

Useful standard options

You can add these options to you command line.

How to change the JDK that will run Jeka

To determine the JDK to run upon, jeka looks in priority order at :

If none of these variables are present, jeka will run upon the java executable accessible from your PATH environment.

How to change the repository Jeka uses to fetch dependencies

By default, jeka fetch dependencies from maven central (https://repo.maven.apache.org/maven2).

You can select another default repository by setting the jeka.repos.download.url options. We recommend storing this value in your [USER DIR]/.jeka/options.properties file to be reused across projects.

For more details, see JkRepoFromOptions javadoc.

The Build Library

Jeka contains a library for all regular things you need to build/test/publish projects.. The library does not depend on the execution engine and has zero dependency.

API Style

Jeka tries to stick with a consistent API design style.

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

Examples

// 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"));

System

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

Dependency Management

Dependency management API let define, fetch and publish dependencies. Api classes belong to dev.jeka.core.api.depmanagement package

Concepts

Dependency

For Jeka, a dependency is something that can be resolved to a set of files by a JkDependencyResolver. Generally a dependency resolves to 1 file (or folder) but it can be 0 or many.

A dependency is always an instance of JkDependency.

Jeka distinguishes mainly 3 types of dependency :

For the last, Jeka is using Ivy 2.5.0 under the hood. Jeka jar embeds Ivy and executes it in a dedicated classloader to be hidden for client code.

image

JkModuleDependencies (dependency on module through coordinates)

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 :

JkFileSystemSependency (dependency on local files)

Just 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().andFiles("libs/my.jar", "libs/my.testingtool.jar");
JkComputedDependenciy (dependency 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 present, 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);

DependencySet

A dependencySet (JkDependencySet) is an ordered bunch of dependencies used for a given purpose (compilation, war packaging, testing, ...). It can contain any kind of JkDependency. See here

dependencySet also defines :

It is designed as an immutable object where we can apply set theory operations for adding, removing or merging with other dependencies and dependencySet.

Example of dependency set
JkDependencySet deps = JkDependencySet.of()
    .and("com.google.guava") 
    .and("org.slf4j:slf4j-simple")
    .and("com.orientechnologies:orientdb-client:2.0.8")
    .andFile("../libs.myjar")
    .withVersionProvider(myVersionProvider);

Note that :

Example of text describing dependencies
- 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

- COMPILE
org.projectlombok:lombok:1.16.16

Transiitivity

Mainstream build tools use a single concept ('scope' or 'configuration') to determine both :

  1. Which part of the build needs the dependency
  2. Which transitive dependencies to fetch along the dependency.
  3. If the dependency must be part of the transitive dependencies according a configuration.

This confusion leads in dependency management systems that are bloated, difficult to reason about and not quite flexible. Gradle comes with a proliferation of 'configurations' to cover most use case combinations, while Maven narrows 'scopes' to a fewer but with limitations and not-so-clear transitivity/publish rules.

In the opposite, Jeka distinguishes clearly the three purposes :

  1. Jeka uses distinct dependencySet instances for each part of the build (compile, runtime, test,...). Each can be defined relatively to another using set theory operations.
  2. For each dependency, we can decide its transitivity, that is, the transitive dependencies fetched along the dependency.
  3. For publishing, we can optionally re-define a specific dependencySet, exposing exactly what we want.

Jeka defines by default, 3 levels of transitivity :

Reminder : on Maven repositories, published poms can declare only two scopes for transitive dependencies : 'compile' and 'runtime'.

For Ivy repositories, it is possible to declare a specific transitivity that maps to a slave 'configuration'.

The below example shows a JkJavaProject declaration using explicit transitivity.

JkJavaProject.of().simpleFacade()
    .setCompileDependencies(deps -> deps
            .and("com.google.guava:guava:23.0", JkTransitivity.NONE)
            .and("javax.servlet:javax.servlet-api:4.0.1"))
    .setRuntimeDependencies(deps -> deps
            .and("org.postgresql:postgresql:42.2.19")
            .withTransitivity("com.google.guava:guava", JkTransitivity.RUNTIME)
            .minus("javax.servlet:javax.servlet-api"))
    .setTestDependencies(deps -> deps
            .and(Hint.first(), "org.mockito:mockito-core:2.10.0")
    )

It results in :

Declared Compile Dependencies : 2 elements.
  com.google.guava:guava:23.0 transitivity:NONE
  javax.servlet:javax.servlet-api:4.0.1
  
Declared Runtime Dependencies : 2 elements.
  com.google.guava:guava:23.0 transitivity:RUNTIME
  org.postgresql:postgresql:42.2.19
  
Declared Test Dependencies : 4 elements.
  org.mockito:mockito-core:2.10.0
  com.google.guava:guava:23.0 transitivity:RUNTIME
  org.postgresql:postgresql:42.2.19
  javax.servlet:javax.servlet-api:4.0.1

Dependencies without any transitivity specified on, will take default transitivity for their purpose, namely COMPILE for compile dependencies, and RUNTIME for runtime and test dependencies.

The API allows to redefine the transitivity declared in a upper dependency set.

Note that transitivity can only apply to JkModuleDependency (like com.google.guava:guava:23.0) and JkLocalProjectDependency.

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");

// Here, 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

Publication

Jeka is able to publish on both Maven and Ivy repository. This includes repositories as Sonatype Nexus.

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());

Project Building

Jeka features high-level and low-level classes to deal with Java builds and JVM concepts.

Java Tool Base API

Base classes are used as foundation for implementing Jeka high-level build API but they can be used directly in a low level build description. These classes belong to dev.jeka.core.api.java package.

Testing API

Jeka features a simple yet powerful API to launch tests. It relies entirely on JUnit5. This means that any test framework supported by Junit5 platform.

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.

The API classes all belongs to dev.jeka.core.api.java.testing package.

Project API

This is the Jeka high-level API to build Java/JVM projects. API classes belong to dev.jeka.core.api.project package.

It introduces the concept of JkProject from where is performed compilation, testing, resources processing, packaging, publication and more. JkProject is the root of a deep structure embracing the parent-chaining pattern for readability.

The API contains a lot of extension points to add specific behaviors.

Project API structure
project
+- baseDir
+- outputDir
+- artifactProducer (define artifacts to be produce by the build as map of artifactName -> Consumer<Path> producing the artifact)
+- duplicateDependencyConflictStrategy
+- construction  (Produce packaged binaries from sources. This includes test checking)
|  +- jvmTargetVersion
|  +- sourceEncoding
|  +- javaCompiler
|  +- dependencyResolver
|  +- runtimeDependencies
|  +- manifest
|  +- fatJar (customize produced fat/uber jar if any)
|  +- compilation  (produce individual binary files from production sources. This includes resource processing, code generation, transpiling, post binary processing, ...)
|  |  +- layout (where are located source and resource files)
|  |  +- dependencies   (stands for compile dependencies)
|  |  +- preCompileActions (including resources processing)
|  |  +- compileActions (including java sources compilation. Compilation for other languages can be added here)
|  |  +- postCompileActions
|  |  +- methods : resolveDependencies(), run()
|  +- testing
|  |  +- compilation (same as above 'compilation' but for test sources/resources)
|  |  |  +- layout
|  |  |  +- dependencies (stands for test dependencies)
|  |  |  + ...
|  |  +- breakOnFailure (true/false)
|  |  +- skipped (true/false)
|  |  +- testProcessor
|  |  |  +- forkedProcess (configured the forked process who will run tests)
|  |  |  +- preActions
|  |  |  +- postActions
|  |  |  +- engineBehavior
|  |  |  |  +- progressDisplayer
|  |  |  |  +- launcherConfiguration (based on junit5 platform API)
|  |  |  +- testSelection
|  |  |  |  +- includePatterns
|  |  |  |  +- includeTags
|  |  +- method : run()
|  +- methods : createBinJar(), createFatJar(), resolveRuntimeDependencies(), getDependenciesAsXml()
|  +            includeLocalDependencies(), includeTextDependencies()            
+- documentation (mainly procude javadoc and source jar)
|  +- javadocConfiguration
|  +- methods : createJavadocJar(), createSourceJar(), run()
+- publication (define information about module and artifacts to be published)
|  +- moduleId (group:name)
|  +- version
|  +- maven (maven specific information to be published in a Maven Repositoty)
|  |  +- dependencyCustomizer (customize the dependencies to be published)
|  |  +- mavenSpecificInfo
|  |  +- methods : publish
|  +- ivy (Ivy specific information to be published in a Ivy Repositoty)
|  |  +- dependencyCustomizer (customize the dependencies to be published)
|  |  +- ivySpecifictInfo
|  |  +- method : publish()
|  +- methods : publish(), getVersion(), getModuleId()
+ methods : getArtifacctPath(artifactName), toDependency(transitivity), getIdeSupport(), pack()

For simplicity’s sake, JkProject provides a facade in order to setup common settings friendly, without navigating deep into the structure. From facade, you can setup dependencies, java version, project layout, test behavior, test selection and publication.

JkProject.of().simpleFacade()
   .configureCompileDeps(deps -> deps
           .and("com.google.guava:guava:21.0")
           .and("com.sun.jersey:jersey-server:1.19.4")
           .and("org.junit.jupiter:junit-jupiter-engine:5.6.0"))
   .configureRuntimeDeps(deps -> deps
           .minus("org.junit.jupiter:junit-jupiter-engine")
           .and("com.github.djeang:vincer-dom:1.2.0"))
   .configureTestDeps(deps -> deps
           .and("org.junit.vintage:junit-vintage-engine:5.6.0"))
   .addTestExcludeFilterSuffixedBy("IT", false)
   .setJavaVersion(JkJavaVersion.V8)
   .setPublishedModuleId("dev.jeka:sample-javaplugin")
   .setPublishedVersion("1.0-SNAPSHOT");

If facade is not sufficient for setting up project build, it's still possible to complete through the main API. JkProject instances are highly configurable.

Here is a pretty complete example inspired from the Jeka Build Class .

Third Party Tool Integration

The dev.jeka.core.api.tooling package provides integration with tools developers generally deal with.

Eclipse

JkEclipseClasspathGenerator and JkEclipseProjectGenerator provides method to generate a proper .classpath and .project file respectively.

JkEclipseClasspathApplier reads information from a .classpath file.

Intellij

JkIntellijImlGenerator generates proper .iml files.

Git

JkGitWrapper wraps common Git commands in a lean API.

Maven

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.