An Aussom library that builds a Java project - compile, jar, javadoc, resolve
dependencies, and native image - from a short build.auss script. For a
standard project, that script is three lines:
include javabuild;
// (command-line args, sources path, make the jar executable)
runner = new BuildRunner(args, "src/main/java", true);
runner.run();
Then run abld in that directory to build it. See
Getting Started: The Three-Line Build
for the walk-through.
The Java Build Tools let you build a Java project from Aussom. They turn the
everyday Java build steps - compile the sources, package a jar, generate
Javadoc, resolve Maven dependencies, and produce a native app image - into a
small set of Aussom objects you create and call. A standard project builds
from a build.auss script only a few lines long; a project with special
needs drops down to the same objects and wires the steps by hand.
This guide starts with the everyday case: a short build.auss and the
commands to run it. It then builds up to dependencies, native images, custom
build steps, and finally the low-level JavaTools calls the whole system
sits on. The first half is what most projects need; the second half is
reference material for when the easy path is not enough.
The tools are a library, not a framework. Nothing takes over your script.
You create a runner, hand it the facts about your project, and call run().
Everything else is a normal Aussom object you can inspect and extend.
module-info.java) with no extra setupbuild.auss -c -p worksjpackagejavac, jar, javadoc, jlink, and jpackage directly when
you want full controlThe system has a few parts. You will use the top two most of the time and rarely need to think about the rest.
| Piece | What it is |
|---|---|
BuildRunner |
The build entry point. It reads command-line flags, holds the build steps, and runs them. This is what a build.auss creates. |
build.auss |
Your project's build script. It creates a BuildRunner and calls run(). It runs in script mode (top-level statements, no class). |
abld |
A command that finds and runs the build.auss in the current directory, forwarding your arguments. A shorter way to run a build. |
| Actions | The build steps a run performs: clean, compile, package, docs, install, image, all. Flags select them. |
BuildTask |
The base class for one build step. The standard steps extend it, and so can you for a custom step. |
| Standard tasks | The built-in steps: resolve, compile, jar, javadoc, jpackage, install. The runner builds and wires these for you. |
BuildLog |
The progress display. It shows a colored terminal UI when the output is a terminal and plain lines when it is piped. |
JavaTools |
The low-level Java API underneath: one call each for compile, jar, javadoc, resolve, jlink, jpackage, and install. |
The javabuild module gives you BuildRunner, BuildTask, the standard
tasks, and BuildLog. It pulls in the javatools module, which gives you
JavaTools and the option-builder classes. Including javabuild gets you
everything:
include javabuild;
The easy path assumes a conventional project:
myapp/
build.auss # your build script
src/main/java/ # your Java sources
build/ # created by the build; safe to delete
classes/ # compiled .class files
dist/myapp.jar # the packaged jar
docs/ # generated Javadoc
image/ # the native app image
The output name (myapp above) comes from the project directory name.
Everything under build/ is generated, so clean can remove it and you can
.gitignore it.
For a standard project, the whole build.auss is three lines:
include javabuild;
// (command-line args, sources path, make the jar executable)
runner = new BuildRunner(args, "src/main/java", true);
runner.run();
That is a complete build script. args is the command-line argument list,
which Aussom binds for you in script mode. The runner reads it to pick which
action to run, builds the standard steps for a project rooted at
src/main/java, and runs them.
The third argument, true, makes the jar executable: it gets a
Main-Class header and the resolved dependencies bundled in, so the jar runs
on its own. Pass false for a plain library jar.
There are three ways to run it, all equivalent:
abld # finds build.auss in this directory and runs it
./build.auss # run the script directly
aussom build.auss # run it through the aussom CLI
With no flags, the build runs the default package action: resolve
dependencies, compile, and build the jar. When there is no build.auss in
the current directory, abld prints build.auss not found in the current directory. and stops.
Flags on the command line pick which action runs. Each flag has a short and a long form:
| Flag | Action | What it does |
|---|---|---|
-c / --clean |
clean | Remove the build outputs. |
-cp / --compile |
compile | Resolve and compile only. |
-p / --package |
package | Resolve, compile, and build the jar. This is the default. |
-d / --docs |
docs | Resolve and generate Javadoc. |
-i / --install |
install | Build and install the jar into the local Maven repository. |
-im / --image |
image | Resolve, compile, jar, and build a native app image. |
-a / --all |
all | Package, docs, and image together. |
-h / --help |
- | Print the flag list and stop. |
All short flags are lower case on purpose, so there is no -c versus -C
confusion. Some short flags are two letters (-cp, -im) to stay distinct:
-i is install, the common operation, and -im is image.
abld # default: package
abld -cp # compile only
abld -d # docs only
abld -i # build and install into the local Maven repo
abld -im # build the native app image
abld -a # package, docs, and image
You can pass flags in any order and get the same build. This is different
from mvn clean package, where order matters. The flags select a set of
steps, and the runner always runs them in one correct order:
abld -c -p # clean, then package
abld -p -c # exactly the same: clean, then package
clean always runs first when it is selected. If clean is the only flag,
the build cleans and stops:
abld -c # clean only, no build
When you pass any action flag, exactly those actions run. The default
package applies only when you pass no action flag at all.
Note that -cp (compile) is one flag, not -c -p clustered together.
-c -p is clean plus package; -cp is compile.
Most real projects need Maven dependencies and an entry point. Dependencies
are a list of group:artifact:version coordinates. You can pass them as the
fourth constructor argument or set them afterward; both do the same thing.
include javabuild;
runner = new BuildRunner(args, "src/main/java", true,
["com.google.guava:guava:33.0.0-jre"]);
runner.setMainClass("com.example.Main");
runner.run();
The same build, written with setters:
include javabuild;
runner = new BuildRunner(args, "src/main/java", true);
runner.setDeps(["com.google.guava:guava:33.0.0-jre",
"org.apache.commons:commons-lang3:3.14.0"]);
runner.setMainClass("com.example.Main");
runner.run();
setDeps feeds both the dependency resolver and the compile classpath, so
your code compiles against the libraries and (for an executable jar) ships
with them. setMainClass sets the jar's entry point and the app image's
launcher class.
This is the boundary of the easy path: a standard Java build expressed as one constructor call and two setters. The library builds and orders the steps.
The runner reports progress as it goes. It picks the display automatically:
[2/5] compile ... line while it runs, replaced by a colored result
when it finishes (green done, dim skipped, red failed), and a closing
Build succeeded or Build failed.[1/3] compile ... done
[2/3] jar ... done
[3/3] jpackage ... done
Build succeeded (3 tasks, 0 skipped)
You can force either style. Add --plain to force plain logging (handy in a
script even when you have a terminal) or --tui to force the terminal UI:
abld -p --plain
abld -p --tui
Every standard step is incremental. Before a step runs, the runner checks
whether the step's outputs are newer than its inputs. If nothing changed, the
step is skipped and reported as skipped. Run a build twice in a row and the
second run does almost no work:
[1/3] resolve ... skipped
[2/3] compile ... skipped
[3/3] jar ... skipped
Build succeeded (3 tasks, 3 skipped)
Change one source file and only the steps that depend on it run again. You do
not configure this; it is on by default. A clean (or abld -c) removes the
outputs so the next build runs every step from scratch.
If your project is a Java module - it has a module-info.java under the
source path - the build detects that and puts the dependencies on the compile
module path instead of the class path, so javac can resolve your
module's requires. You do not configure anything; it is automatic:
include javabuild;
// A modular JavaFX library. Modular mode is auto-detected from
// src/main/java/module-info.java.
runner = new BuildRunner(args, "src/main/java", false,
["org.openjfx:javafx-controls:23.0.1"]);
runner.run();
To override the detection in the rare case it guesses wrong, call
setModular(true) or setModular(false). Note this is about your code: a
non-modular project that merely depends on modular libraries needs no modular
mode - those dependencies compile fine on the class path.
The install action (-i) builds the jar and installs it into your local
Maven repository (~/.m2), so other projects on the same machine can resolve
it as a dependency. It installs the jar, a generated POM, a sources jar, and a
javadoc jar under the coordinates you set.
Install needs the Maven coordinates - the groupId, artifactId, and version -
which you set with setCoordinates:
include javabuild;
runner = new BuildRunner(args, "src/main/java", false,
["eu.hansolo.fx:countries:21.0.19"]);
runner.setCoordinates("com.example", "widgets", "1.4.2");
runner.run();
abld -i # build and install into ~/.m2
The generated POM lists your dependencies, so a project that depends on
com.example:widgets:1.4.2 also pulls its transitive dependencies. Install
runs every time you ask for it, even when the version is unchanged - it does
not skip, so you never have to bump a version or delete from ~/.m2 to
re-install after a change.
The image action (-im) produces a native app image with jpackage: your
application plus a bundled Java runtime, launched by a native executable.
include javabuild;
runner = new BuildRunner(args, "src/main/java", true,
["com.google.guava:guava:33.0.0-jre"]);
runner.setMainClass("com.example.Main");
runner.run();
abld -im # build the app image under build/image
Set the main class so the launcher knows where to start, and make the jar
executable (true) so the image bundles a runnable jar. The image action
runs resolve, compile, jar, and jpackage in order.
The app image lands under build/image. To produce an installer instead of a
plain app image (a .deb, .dmg, .msi, and so on), use the low-level
JavaTools.jpackage call described later, which takes an explicit package
type.
When the standard pipeline is not enough, you build the runner by hand. The no-argument constructor registers the flags but creates no steps. You add the steps you want - standard ones, your own, or a mix - and run:
include javabuild;
runner = new BuildRunner();
runner.add(new MyCustomTask()); // your own step
runner.setAction(buildAction.package); // or pass args and let the CLI pick
runner.run();
add appends a step to the end of the list. setAction picks the action
directly; if you pass args to the constructor instead, the command line
picks it. The runner runs whatever steps it holds, in order.
A custom step extends BuildTask and overrides run(). That is the only
required method. run() returns a result map with a success key.
/**
* `StampTask` writes a build-stamp file each time the build runs.
*/
class StampTask : BuildTask {
public pathValue = null;
/**
* Creates the stamp task.
* @p Path is the file to write the stamp to.
*/
public StampTask(string Path) {
this.nameValue = "stamp";
this.pathValue = Path;
this.incrementalValue = false; // always run
}
/**
* Writes the stamp file.
* @r A result map with success set to true.
*/
public run() {
file.write(this.pathValue, "built at " + sys.getMills() + "\n");
return { "success": true };
}
}
To make a custom step incremental, also override inputs() and outputs()
to return the files it reads and writes. The runner then skips the step when
its outputs are newer than its inputs, exactly like the standard steps. Set
incrementalValue = false (as above) for a step that should always run.
The runner calls execute() on each step, never run() directly.
execute() is where the incremental check lives, so it runs first and calls
your run() only when the step is not up to date.
BuildRunner and the tasks all sit on JavaTools, which you can call
directly for full control or a one-off task. Each operation is a single call
that returns a result map with a success key.
include javatools;
jt = new JavaTools();
// Compile: sources in, .class files out.
res = jt.compile(["src/main/java"], "build/classes");
if (res.get("success") == true) {
// Package a jar from the compiled classes.
jt.jar("build/classes", "build/dist/app.jar", "com.example.Main");
}
The main operations:
| Call | What it does |
|---|---|
jt.compile(sources, outputDir, classpath, release) |
Run javac. Returns diagnostics on failure. |
jt.jar(inputDir, output, mainClass) |
Package a jar from compiled classes. |
jt.javadoc(sources, outputDir, classpath) |
Generate Javadoc. |
jt.resolve(coordinates, scope, transitive) |
Resolve Maven dependencies to a classpath. |
jt.jlink(modules, output, modulePath) |
Build a custom Java runtime image. |
jt.jpackage(type, name, inputDir, mainJar, mainClass) |
Build a native app image or installer. |
jt.install(group, artifact, version, jarPath, pomPath, sourcesPath, javadocPath) |
Install the jar and its companions into the local Maven repository. |
Only the first argument of each is required; the rest have sensible defaults
(for example, compile writes to build/classes).
The short calls above cover the common options. For everything else, each
operation has a matching option-builder class with a method per option.
You build it up and pass it to the operation. For example, JavaCompile
covers the full javac surface:
include javatools;
comp = new JavaCompile();
comp.sources(["src/main/java"]);
comp.outputDir("build/classes");
comp.classpath(["build/lib/guava.jar"]);
comp.release(21);
comp.encoding("UTF-8");
jt = new JavaTools();
jt.compile(comp);
The builders are JavaCompile, JavaJar, JavaDoc, JavaLink (for
jlink), and JavaPackage (for jpackage). Each has a method for every
option its tool supports - classpath, module path, jar manifest entries,
Javadoc titles, jpackage icons and installer types, and so on. Reach for a
builder when the short call does not expose the option you need.
jlink builds a stripped-down custom runtime image. It is a best-effort,
low-level feature: it is available through jt.jlink and the JavaLink
builder but is not part of the image action, which uses jpackage.
jlink needs a full JDK with a jmods directory present; the runtime bundled
with the Aussom CLI does not include one, so jt.jlink reports a clear error
when it cannot find the modules it needs. Point it at a full JDK install when
you use it.
| Action | Steps run, in order |
|---|---|
| clean | Removes every step's outputs. Always runs first. |
| compile | resolve, compile |
| package | resolve, compile, jar |
| docs | resolve, javadoc |
| install | resolve, compile, jar, javadoc, install |
| image | resolve, compile, jar, jpackage |
| all | resolve, compile, jar, javadoc, jpackage |
Selecting more than one action runs the union of their steps, and each shared step (like resolve) runs only once.
| Method | What it does |
|---|---|
new BuildRunner(args, sourcesPath, executableJar, deps) |
Easy path. All arguments optional; a source path builds the standard steps. |
new BuildRunner() |
Programmatic path. Registers flags, creates no steps. |
setDeps(list) |
Set the Maven coordinates to resolve. |
setMainClass(string) |
Set the jar entry point and image launcher. |
setCoordinates(group, artifact, version) |
Set the Maven coordinates the jar is installed under. Required for install. |
setModular(bool) |
Force modular mode on or off, overriding the module-info.java auto-detection. |
setAction(buildAction) |
Set a single action, overriding the default and the CLI. |
setLogMode(string) |
Force the progress display: "auto", "tui", or "plain". |
add(task) |
Append a build step. |
run() |
Run the selected actions. Returns a result map. |
run() returns a map with success (a bool) and results (a list, one
entry per step). Each entry has a name and a status. The status is one of
pending, skipped, done, or failed.
res = runner.run();
if (res.get("success") != true) {
// a step failed; the runner already printed the reason
}
When a step fails, the runner stops (by default), prints the failure reason,
and run() returns with success set to false.
The tools install to your local Maven repository (the install action),
but they do not publish to a remote one:
So the tools cover everything through a local install; pushing to a remote repository is a separate step you would run outside these tools for now (for example, with Maven itself).
design/java-build-tools.md - the Phase 1 design (the JavaTools API).design/java-build-tools-phase-2.md - the Phase 2 design (tasks, the
runner, incremental builds, and native images).