Write Your First Sourcegen (Scala)
This page walks through the Scala 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.
Prerequisites
- A working bleep project (see Your First Project)
- Scala 3 and a matching JVM
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:
platform:
name: jvm
mainClass: com.example.Main
scala:
version: 3.8.3
sourcegen:
project: scripts
main: scripts.GenConstants
scripts:
dependencies:
- build.bleep:bleepscript:${BLEEP_VERSION}
platform:
name: jvm
scala:
version: 3.8.3
Key points:
sourcegen:onmyappnames the project that holds the generator and the fully qualified main class. Bleep will run the generator beforemyappcompiles, and rerun it whenever the script project or its dependencies change.${BLEEP_VERSION}keeps the script'sbleepscriptdep 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 java.util.List[CodegenTarget] — one
entry per project (or cross-id) that consumes this generator. Write
source files under target.sources:
package scripts
import bleepscript.{BleepCodegenScript, CodegenTarget, Commands, Started}
import java.nio.file.Files
import scala.jdk.CollectionConverters.*
class GenConstants extends BleepCodegenScript("GenConstants") {
override def run(started: Started, commands: Commands, targets: java.util.List[CodegenTarget], args: java.util.List[String]): Unit =
targets.asScala.foreach { target =>
val file = target.sources.resolve("generated/Constants.scala")
Files.createDirectories(file.getParent)
Files.writeString(file,
"package generated\n" +
"\n" +
"object Constants:\n" +
" val ANSWER: Int = 42\n"
)
}
}
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 Scala object — the
generated package is on the consumer's source path automatically:
package com.example
import generated.Constants
@main def main(): Unit =
println(s"answer: ${Constants.ANSWER}")
Step 4: Run it
bleep run myapp
answer: 42
bleep compile triggers the generator, then compiles myapp against
the generated Constants object, then runs it. Subsequent
invocations skip the generator (its inputs haven't changed) until you
edit the script project.
Where to go from here
- Source generation (concept) —
the
BleepCodegenScriptAPI, cross-build targets, the temp-directory cleanup model, and how invalidation works. - Bleep scripts (concept) — the after-compile half: scripts that act on artifacts.
- Run sourcegen explicitly —
bleep sourcegen --watch myappre-runs generators when their inputs change.