Cross-building
Cross-building means producing the same artifact for multiple targets —
multiple Scala versions (e.g., 2.13 and 3.x), multiple platforms (JVM,
Scala.js, Scala Native), or Kotlin Multiplatform (JVM + JS + Native).
Bleep models all of these declaratively with a cross: block per
project, so a single project description fans out into one compile unit
per target.
This guide walks through two minimal working examples:
- A Scala library cross-built against Scala 2.13 and Scala 3.
- A Kotlin app cross-built against the JVM and JS — same source file, colored output on both.
Scala: cross-version on the JVM
The build
$schema: https://raw.githubusercontent.com/oyvindberg/bleep/master/schema.json
$version: 1.0.0-M9
jvm:
name: graalvm-community:25.0.1
projects:
mylib:
extends: template-cross
mylib-test:
dependencies:
- org.scalameta::munit:1.0.0
dependsOn: mylib
extends: template-cross
isTestProject: true
templates:
template-cross:
cross:
jvm213:
scala:
version: 2.13.16
jvm3:
scala:
version: 3.8.3
platform:
name: jvm
What's going on:
templates.template-crossdeclares the cross axis once. Bothmylibandmylib-testextend it, so both fan out intojvm213andjvm3variants. A template avoids repeating the cross block per project.cross:has one entry per cross-id. The id (jvm213,jvm3) is yours to pick — it ends up in directory paths and in the command-line selector. Convention:<platform><scalaMajor>or just<scalaMajor>.- Each cross entry overrides what's specific to that variant — here,
the Scala version. Anything outside the
cross:block (likeplatform.name: jvm) applies to all variants.
The library
The shared source lives at mylib/src/scala/Greeting.scala and compiles
under both Scala 2.13 and Scala 3:
package mylib
object Greeting {
def hello(name: String): String = s"Hello, $name!"
}
For code that needs to diverge between versions, bleep supports per-cross
source roots — see Cross-building (concepts)
for the source-layout options (cross-pure, cross-full, etc.).
The tests
package mylib
class GreetingTest extends munit.FunSuite {
test("hello") {
assertEquals(Greeting.hello("world"), "Hello, world!")
}
}
mylib-test inherits the cross axis from template-cross, so MUnit
runs the same test class once under each Scala version.
Building and testing
Build everything (all variants of every project):
bleep compile
Pick a single variant with the @<crossId> suffix:
bleep compile mylib@jvm3
bleep test mylib-test@jvm213
Drop the suffix to operate on every variant of a project:
bleep test mylib-test # runs against jvm213 and jvm3
Where output lands
Each variant gets its own target/ subdirectory and its own JAR:
.bleep/builds/normal/.bloop/mylib/jvm213/classes/ # Scala 2.13 .class files
.bleep/builds/normal/.bloop/mylib/jvm3/classes/ # Scala 3 .class files
When you publish, each variant becomes a separate Maven artifact with
the appropriate Scala suffix (mylib_2.13, mylib_3).
Kotlin: cross-platform JVM + JS
Same cross: mechanic, applied to a Kotlin app: one source file gets
compiled for the JVM by kotlinc-jvm and for JavaScript (Node) by
kotlinc-js.
The build
$schema: https://raw.githubusercontent.com/oyvindberg/bleep/master/schema.json
$version: 1.0.0-M9
jvm:
name: graalvm-community:25.0.1
projects:
app:
kotlin:
version: 2.3.0
cross:
jvm:
kotlin:
jvmTarget: "25"
platform:
mainClass: com.example.MainKt
name: jvm
js:
kotlin:
js:
outputMode: js
target: nodejs
platform:
name: js
Notes:
kotlin.versionlives at the project level, outside thecross:block, so both variants share the Kotlin compiler version. The cross variants only set what differs:platform.name,kotlin.jvmTarget(jvm only), and thekotlin.jsblock (js only).platform.name: jspaired withkotlin.versiontriggers Kotlin/JS compilation; bleep doesn't require Scala.js metadata in that case.kotlin.js.target: nodejsproduces a Node-runnable JS file.
The shared source
package com.example
fun main() {
val green = "\u001B[32m"
val cyan = "\u001B[36m"
val reset = "\u001B[0m"
println("${green}Hello${reset} from ${cyan}bleep${reset}!")
}
The same Main.kt compiles under both targets. ANSI escape codes for
color render correctly in any terminal — including Node when run via
node app.js — so the JVM and JS outputs both produce a green "Hello"
- cyan "bleep".
Building
bleep compile app@jvm
bleep compile app@js
Or both:
bleep compile app
Going further
- Cross-building (concepts) — source layouts and source-layout options for diverging code, JS / Native cross-platform builds, and naming conventions.
- Publishing to Maven Central — publish all cross variants from one command.