Skip to main content

Write Your First Sourcegen (Java)

This page walks through the Java flow for adding a source generator to your build — a small program that writes source files compile then sees. For what a sourcegen script is, why bleep makes it a first-class concept, and how it differs from a bleep script, see Source generation.

Other languages: Kotlin → · Scala →

Prerequisites

Step 1: The build

Add a scripts project to host the generator and point myapp at it via sourcegen:. The scripts project depends on bleepscript, the same dep you'd add for a regular bleep script:

$schema: https://raw.githubusercontent.com/oyvindberg/bleep/master/schema.json
$version: 1.0.0-M9
jvm:
name: graalvm-community:25.0.1
projects:
myapp:
java:
version: "25"
platform:
name: jvm
mainClass: com.example.Main
sourcegen:
project: scripts
main: scripts.GenConstants
scripts:
dependencies:
- build.bleep:bleepscript:${BLEEP_VERSION}
java:
version: "25"
platform:
name: jvm

Key points:

  • sourcegen: on myapp names the project that holds the generator and the fully qualified main class. Bleep will run the generator before myapp compiles, and rerun it whenever the script project or its dependencies change.
  • ${BLEEP_VERSION} keeps the script's bleepscript dep in lockstep with whatever bleep is running you.
  • A project can list more than one sourcegen script — each gets its own isolated output directory.

Step 2: Write the generator

Extend bleepscript.BleepCodegenScript and override run. The framework hands you a list of CodegenTargets — one per project (or cross-id) that consumes this generator. Write source files under target.sources():

package scripts;

import bleepscript.BleepCodegenScript;
import bleepscript.CodegenTarget;
import bleepscript.Commands;
import bleepscript.Started;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

public final class GenConstants extends BleepCodegenScript {
public GenConstants() {
super("GenConstants");
}

@Override
public void run(Started started, Commands commands, List<CodegenTarget> targets, List<String> args) {
for (CodegenTarget target : targets) {
Path file = target.sources().resolve("generated/Constants.java");
try {
Files.createDirectories(file.getParent());
Files.writeString(file,
"package generated;\n" +
"\n" +
"public final class Constants {\n" +
" public static final int ANSWER = 42;\n" +
"}\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}

BleepCodegenScript provides main for you. The output goes to a temp directory the framework supplies; only on a successful return does bleep sync the temp tree to the real generated-sources directory. A failed run never leaves stale code behind.

Step 3: The consumer

myapp imports Constants like any other class — the generated package is on the consumer's source path automatically:

package com.example;

import generated.Constants;

public final class Main {
public static void main(String[] args) {
System.out.println("answer: " + Constants.ANSWER);
}
}

Step 4: Run it

bleep run myapp
answer: 42

bleep compile triggers the generator, then compiles myapp against the generated Constants class, then runs it. Subsequent invocations skip the generator (its inputs haven't changed) until you edit the script project.

Where to go from here