KBeans
KBean is the central concept of the execution engine. KBeans are classes with declared executable methods.
There is only one KBean instance per KBean class in any given Jeka base directory.
KBean classes share the following characteristics:
- They extend the
KBean
class. - They may declare
public void
methods without arguments. All these methods can be invoked from the command line. - They may declare
public
fields (also known as KBean properties). These field values can be injected from the command line.
Additionally, they can have non-public fields annotated with@JkDoc
. - They must provide a no-argument constructor.
- They may override the
init()
method. - They must be instantiated by the execution engine and not by user code.
Simple Example¶
The following KBeans expose the hello
and bye
methods. The rendering can be configured
through nema
and uppercase
attributes.
@JkDoc("Displays greeting messages")
public class Greeting extends KBean {
public String name = "Bob";
@JkDoc("If true, the message is shown in upper case.")
public boolean uppercase;
@JkDoc("Prints a hello message.")
public void hello() {
System.out.println(formatMessage("Hello " + name + "!"));
}
public void bye() {
System.out.println(formatMessage("Goodbye " + name + "!"));
}
private String formatMessage(String message) {
return uppercase ? message.toUpperCase() : message;
}
}
jeka hello name=Alice uppercase=true
jeka greeting: --doc
Displays greeting messages.
Fields
name=<String> No description.
Default: Bob
uppercase If true, the message is shown in upper case.
Default: false
Methods
bye No Description.
hello Prints a hello message.
Location¶
KBeans can exists as source code in the local project jeka-src folder, at root or any package, or as class in the Jeka classpath.
Multiple KBeans in jeka-src
Many KBeans may coexist in a single jeka-src dir. In this case, use KBean names to precise on which bean to invoke, as:
jeka greeting: hello bye other: foo
Classpath KBeans
Jeka bundles a collection of KBeans for building projects, creating Docker images, performing Git operations, and more.
For example, running:
jeka project: compile
To display the documentation for the project
KBean, run:
jeka project: --doc
To list all available KBeans in the classpath, execute:
jeka --doc
KBeans can be added to the classpath like any third-party dependency.
This can be done by setting the jeka.inject.classpath
property in the jeka.properties file as follows:
jeka.inject.classpath=dev.jeka:springboot-plugin dev.jeka:openapi-plugin:0.11.8-1
KBeans can also be included directly in the source code using the @JkDep
annotation:
import dev.jeka.core.tool.JkDep;
@JkDep("dev.jeka:springboot-plugin")
@JkDep("dev.jeka:openapi-plugin:0.11.8-1")
class Build extends KBean {
...
Additionally, KBeans can be dynamically added from the command line like this:
jeka --classpath=dev.jeka:openapi-plugin:0.11.8-1 openapi:--doc
Jeka discovers KBeans automatically by scanning the classpath.
KBean Methods¶
A KBean method is a specific method defined in a KBean class, designed to be executable from the command line interface. For successful recognition as a command, the method must adhere to the following criteria:
- It must be designated as
public
. - It must be an instance method, not static or abstract.
- It must not require any arguments upon invocation.
- It must not return any value, as indicated by a
void
return type.
KBean Attributes¶
A KBean attribute is a public
instance field of a KBean class. Its value can be injected from the command line or from a property file.
Additionally, it can be a non-public field annotated with @JkDoc
.
Attributes can be annotated with @JkInjectProperty("my.prop.name")
to inject the value of a property into the field.
We can also inject value using *jeka.properties
For more details on field accepted types, see the dev.jeka.core.tool.FieldInjector#parse
method.
KBean attributes can also represent nested composite objects. See the example in the ProjectKBean#pack
field.
Naming KBeans¶
To be referenced conveniently, KBeans can be identified by specific names. For any given KBean class, the accepted names are:
- Fully qualified class name.
- Uncapitalized simple class name (e.g.,
myBuild
matchesorg.example.MyBuild
). - Uncapitalized simple class name without the
KBean
suffix (e.g.,project
matchesdev.jeka.core.tool.builtin.project.ProjectKBean
).
Tip
Execute jeka
at the root of a project to display the KBeans available on the Jeka classpath.
Document KBeans¶
KBean classes, methods, and attributes can include the @JkDoc
annotation to provide self-documentation.
The text from these annotations is displayed when running the command:
jeka <kbeanName>: --doc
jeka --doc
Use the @JkDocUrl
annotation to indicate that a KBean has online documentation.
This URL will be displayed in the output of the --doc
command.
To document your KBean, you can use the following command:
jeka <kbeanName>: --doc-md
Invoke KBeans¶
From the Command Line¶
KBean methods can be executed directly from the command line using the syntax:
jeka <kbeanName>: [methodName...] [attributeName=xxx...]
Example:
jeka project: info pack tests.fork=false pack.jarType=FAT sonarqube: run
You can call multiple methods and set multiple attributes in a single command.
From IntelliJ Jeka Plugin¶
The IntelliJ Jeka Plugin enables invoking KBean methods directly from the IDE, either from the code editor or the project explorer tool window.
From a Plain IDE Setup¶
KBean methods can also be launched or debugged in an IDE by invoking the dev.jeka.core.tool.Main
method and passing the corresponding command-line arguments.
Example:
Invoking the dev.jeka.core.tool.Main
method with arguments project:
and compile
will instantiate the ProjectKBean
class and invoke its compile
method.
Warning
Ensure that the main method is launched with the module directory set as the working directory.
In IntelliJ, the default working directory is the project directory, which may cause issues.
To update IntelliJ defaults:
- Navigate to Run | Edit Configurations... | Application | Working Directory
- Set the value to $MODULE_DIR$
.
Default KBean¶
The [kbeanName] prefix is optional and defaults to:
- The KBean specified by the
jeka.kbean.default
property (if this property is set). - If the property is not set, it defaults to the first KBean found in the jeka-src directory, sorted alphabetically by fully qualified class name.
Example¶
The following command:
jeka doSomething aProperty=xxxx
doSomething
method of the default KBean.
To explicitly reference the default KBean and avoid ambiguity, use :
as the prefix.
Examples¶
jeka : --doc
displays the documentation of the default KBean.jeka --doc
displays the overall documentation.
KBean Collaboration¶
There's 2 goals for making KBean collaboration:
- Invoke a KBean from another one
- Configure a KBean from another one
Invoke KBean from another one¶
Using @JkInject¶
import dev.jeka.core.tool.builtins.project.ProjectKBean;
import dev.jeka.core.tool.builtins.tooling.maven.MavenKBean;
import dev.jeka.core.tool.JkInject;
@JkDoc("A simple example to illustrate KBean concept.")
class Build extends KBean {
@JkInject
private ProjectKBean projectKBean;
@JkInject
private MavenKBean mavenKBean;
@JkDoc("Clean, compile, test, create jar files, and publish them.")
public void packPublish() {
projectKBean.clean();
projectKBean.pack();
mavenKBean.publishLocal();
}
}
ProjectKBean
and MavenKBean
are created and injected into the Build
KBean during initialization.
Using #load and #find methods¶
As we saw earlier, you can dynamically retrieve a KBean
using the KBean#load(Class)
method.
This method forces the initialization of the KBean
if it is not already present.
It is useful when you need a specific KBean
only within certain methods.
@JkDoc("Compiles application to native executable")
public void createNativeExec() {
load(NativeKBean.class).compile();
}
On the other hand, the KBean#find(Class)
method returns an Optional<? extends KBean>
,
which is empty if the specified KBean
is not initialized.
This is helpful for performing conditional tasks based on the presence of a KBean
.
import dev.jeka.core.tool.builtins.tooling.docker.DockerKBean;
public void cleanup() {
find(DockerKBean.class).ifPresent(dockerKBean -> {
// Do something
});
}
Configure a Kbean from another KBean.¶
Wether you want to create a JeKa extension or just configure a build, the technic is the same: create a KBean and configure an existing one.
For example, to configure a build, you can create a Build class as:
class Build extends KBean {
public boolean skipIT;
@JkPreInit
private static void preInit(ProjectKBean projectKBean) {
projectKBean.tests.progress = JkTestProcessor.JkProgressStyle.PLAIN;
}
@JkPostInit(required = true)
private void postInit(ProjectKBean projectKBean) {
JkProject project = projectKBean.project;
project.flatFacade.dependencies.compile
.add("com.google.guava:guava:33.3.1-jre")
.add("org.openjfx:javafx-base:21");
project.flatFacade.dependencies.test
.add("org.junit.jupiter:junit-jupiter:5.8.1");
project.flatFacade
.addTestExcludeFilterSuffixedBy("IT", skipIT);
}
@JkPostInit
private void postInit(MavenKBean mavenKBean) {
// Customize the published pom dependencies
mavenKBean.getMavenPublication().customizeDependencies(deps -> deps
.withTransitivity("com.google.guava:guava", JkTransitivity.RUNTIME)
.minus("org.openjfx:javafx")
);
}
}
Build
class that customizes the project
and maven
KBeans.
Pre-initialize KBeans¶
The preInit
methods are invoked before the KBean is instantiated; therefore, they must be declared as static
.
These methods are applied to the target KBean immediately after it is instantiated but before it is initialized.
This means they are executed prior to the injection of properties or command-line values.
The sole purpose of preInit
methods is to provide default values, which can later be overridden by properties or command-line arguments.
They should not perform further configuration, as the target KBean has not yet been fully initialized when these methods are invoked.
Post-initialize KBeans¶
The postInit
methods are invoked only if their respective KBean
is fully initialized.
This occurs after its properties and command-line values have been injected, and its init()
method has been executed.
The required = true
attribute, means that the KBean project must be instantiated by JeKa, if not already setup.
For example, when executing jeka project: pack
, the ProjectKBean
will be initialized with the settings provided by
command-line arguments and @project...=
properties defined in the jeka.properties file.
The instance will then be passed to the postInit
method before invoking the pack
method.
When executing jeka maven: publish
, the project
KBean will be implicitly loaded and configured,
followed by the same process for the maven
KBean, before invoking the publish
method.
Lifecycle¶
Before Kbean methods are executed, Kbeans are configured as described in the detailed sequence:
sequenceDiagram
participant RB as Run Base
participant IN as Init class resolver
participant IT as Instantiater
participant KB as KBean
participant PR as Pre-initializer
participant PO as Post-initializer
participant RK as Registered KBeans
RB->>IN: Discover classes to init
RB->>PR: Register pre-init methods
RB->>IT: Instantiate classes to init
IT->>KB: New
IT->>RK: Register Kbean instance
IT->>PR: Pre-initialise KBean
IT->>KB: Inject fields annotated with @JkInject
IT->>KB: Inject values from properties
IT->>KB: Inject values from Command-line
IT->>KB: Init()
IT->>PO: Register KBean post-init methods
RB->>PO: Post-initialize all initialized KBeans
Post initialisation.¶
One all the KBean has been initialized, bean already initialized or extra one can be invoked from the KBean methods.
sequenceDiagram
participant AK as A KBean
participant RB as Run Base
participant IT as Instantiater
participant KB as KBean
participant PR as Pre-initializer
participant PO as Post-initializer
participant RK as Registered KBeans
AK->>RB: Load a KBean
RB->>RK: Find if KBean is already instantiated
RB-->>AK: Returns Kbean instance if found
RB->>IT: Initialise KBean
IT->>RK: Register KBean
RB->>PO: Register KBean post-init methods
RB->>PO: Post-initialize all initialized KBeans
RB-->>AK:
classDiagram
class JkRunBase {
+Path baseDir
+KBean initKBean
+KBean defaultKBean
+JkProperties properties
+List dependencies
+KBean load()
+KBean find()
+List getKBeans()
}
class KBean {
+JkRunbase runbase
}
JkRunBase "1" <--> "0..*" KBean
JkRunBase --> "0..*" BaseDir: Imported Base Dirs (multi-modules)
note for JkRunBase "There is only one JkRunBase per base folder.<br/>The base folder is the project root.<br/>In multi-module projects, usually one JkRunBase exists per module."
note for BaseDir "This class doesn’t exist. It represents the base directory <br/>of another runbase in a multi-module project."
Multi-Project setup¶
In multi-project scenarios, it is common for a KBean in one project to access a KBean instance from another project. This can be achieved in a statically typed manner:
- In the master KBean, declare a field of type
KBean
(e.g.,KBean importedBuild;
). This field does not need to be public. - Annotate the field, by specifying the relative path of the imported project (e.g.,
@JkInjectRunbase("../anotherModule")
). - Run the command
jeka intellij: iml
orjeka eclipse: files
to refresh project metadata. - Change the declared field type from
KBean
to the concrete type of the imported KBean. - The master KBean can now access the imported KBean in a type-safe manner.
- For an example, see this implementation.
Tip
Ensure that the imported KBean uses KBean#getBaseDir
for handling file paths. This practice ensures safe execution from any working directory.
For multi-module projects, use JkInject
to access sub-module KBeans.
import dev.jeka.core.tool.builtins.project.ProjectKBean;
import dev.jeka.core.tool.builtins.tooling.maven.MavenKBean;
import dev.jeka.core.tool.JkInject;
import java.util.List;
@JkDoc("A simple example to illustrate KBean concept.")
public class MasterBuild extends KBean {
@JkInject("./foo")
ProjectKBean fooProject;
@JkInject("./bar")
ProjectKBean barProject;
@JkDoc("For all sub-modules: clean, compile, test, create jar files, and publish them.")
public void buildAll() {
List.of(fooProject, barProject).forEach(projectKbean -> {
projectKbean.clean();
projectKbean.pack();
MavenKBean mavenKBean = projectKbean.load(MavenKBean.class);
mavenKBean.publishLocal();
});
}
}
MasterBuild
KBean.
We can create or load a KBean on the fly using the KBean#load
method.
This means we only need to declare one KBean per sub-module.
Another option is to inject JkRunbase
and make all calls through it:
import dev.jeka.core.tool.JkRunbase;
import dev.jeka.core.tool.builtins.project.ProjectKBean;
import dev.jeka.core.tool.builtins.tooling.maven.MavenKBean;
import dev.jeka.core.tool.JkInject;
import java.util.List;
@JkDoc("A simple example to illustrate KBean concept.")
public class MasterBuild extends KBean {
@JkInject("./foo")
JkRunbase foo;
@JkInject("./bar")
JkRunbase bar;
@JkDoc("For all sun-modules: clean, compile, test, create jar files, and publish them.")
public void buildAll() {
List.of(foo, bar).forEach(runbase -> {
ProjectKBean projectKBean = runbase.load(ProjectKBean.class);
projectKbean.clean();
projectKbean.pack();
MavenKBean mavenKBean = runbase.load(MavenKBean.class);
mavenKBean.publishLocal();
});
}
}
For larger sub-project structures, use KBean#getImportedKBeans()
to list all sub-modules, either recursively or not.
@JkDoc("For all sub-modules: compile, test, create jar files, and publish them.")
public void buildAll() {
this.getImportedKBeans().get(ProjectKBean.class, true).forEach(ProjectKBean::pack);
this.getImportedKBeans().get(MavenKBean.class, true).forEach(MavenKBean::publish);
}