Skip to main content

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.

Other languages: Java → · Kotlin →

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