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-M10
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-M10
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.