Source layout in cross-builds and sbt imports
The everyday source layouts (kotlin, normal, java) are documented in
Project layout. This page covers the parts
of the model that only matter once you're cross-building Scala, or once
you're maintaining a build that came over from sbt.
If you're not doing either, you can stop here.
Versioned source folders in normal
The Scala default, normal, derives more paths than the table on the
concepts page admits. The full set, when the project's scala.version is
set:
| path | when it appears |
|---|---|
<folder>/src/scala | always |
<folder>/src/java | always |
<folder>/src/scala-<bin-version> | e.g. src/scala-2.13 or src/scala-3 |
<folder>/src/scala-<epoch> | e.g. src/scala-2 or src/scala-3 |
The version-specific folders only resolve if scala.version is set on
the project (which is to say, on each cross axis when cross-building).
If the binary version and the epoch produce the same string — like
Scala 3, where binVersion and epoch are both 3 — the path
appears once.
This is how a single source tree can keep version-specific shims under
src/scala-3/ while the bulk of the code stays under src/scala/. The
Scala-2.13 cross axis picks up src/scala-2.13/; the Scala-3 cross axis
picks up src/scala-3/.
Cross-build layouts
When a project has a cross: block, every cross-id (e.g. jvm213,
jvm3, js3) becomes its own expanded project, with the source layout
re-evaluated using that cross's Scala version and platform. So a
normal-layout project with cross-ids jvm213 and jvm3 ends up looking
in:
<folder>/src/scala
<folder>/src/java
<folder>/src/scala-2.13 (only on the jvm213 cross)
<folder>/src/scala-3 (only on the jvm3 cross)
<folder>/src/resources
For Scala.js / Scala Native cross-builds you have two more layouts to choose from, depending on how you like to organise platform-specific code.
cross-pure
The full set of normal paths, plus the same paths suffixed with
.<platform>:
<folder>/src/scala + <folder>/src/scala.jvm
<folder>/src/java + <folder>/src/java.jvm
<folder>/src/scala-3 + <folder>/src/scala-3.jvm
…and so on for <folder>/src/resources(.jvm)
For the js cross axis, replace .jvm with .js. The "pure" half lives
side-by-side with platform-specific overrides under the same src/ tree.
cross-full
The full set of normal paths, prefixed with both shared/ and
<platform>/:
<folder>/shared/src/scala + <folder>/jvm/src/scala
<folder>/shared/src/java + <folder>/jvm/src/java
<folder>/shared/src/scala-3 + <folder>/jvm/src/scala-3
…
Closer to how some Scala.js / Scala Native projects are laid out, with a clean separation between shared and platform-specific source trees.
sbt-matrix
The full set of normal paths, plus folders whose names mash the platform
and the Scala version together — the layout sbt uses for the
projectMatrix style cross builds:
<folder>/src/scala
<folder>/src/scalajvm
<folder>/src/scalajvm-2.13
<folder>/src/scalajvm-3
Generally only seen on builds imported from sbt. New bleep builds should
prefer cross-pure or cross-full.
sbt-scope: the legacy field
In sbt, sources for the main code go under src/main/, tests under
src/test/, integration tests under src/it/, and so on. That <scope>
segment is what sbt-scope controls.
In bleep, scope distinctions are encoded by separate projects: the
test code is its own project, with isTestProject: true. There is no
src/test/ directory inside the same project — the tests live in
myapp-test/src/kotlin/, not in myapp/src/test/kotlin/. So in a
bleep-native build the <scope> segment is empty, and the layout
produces short folder names (src/scala, not src/main/scala).
When you run bleep import on an sbt build, however, bleep preserves the
original src/main/... / src/test/... shape. Each imported project
gets an sbt-scope of main, test, or it, and the layout fills the
segment in:
projects:
myapp-main:
sbt-scope: main # → src/main/scala, src/main/java, ...
myapp-test:
sbt-scope: test # → src/test/scala, src/test/java, ...
isTestProject: true
This is preserved purely so imports work without moving any files. The
bleep build move-files-into-bleep-layout command exists to migrate an
imported build into the bleep-native form — it relocates source
files from src/main/scala to src/scala (and so on) and clears the
sbt-scope field on every project.
Wiring placeholders into custom paths
Sometimes you want a custom sources: entry that participates in the
cross-build's version axis — for example, sharing a single
shared/scala-3/ directory between several cross-builds. The
path placeholders make that possible:
projects:
mylib:
sources:
- ../shared/src/scala-${SCALA_EPOCH}
${SCALA_EPOCH} expands to 2 or 3 depending on the cross axis; the
result joins each cross's source set on top of whatever the layout
produces. ${SCALA_BIN_VERSION}, ${PLATFORM}, and the rest of the
vocabulary are documented in Path replacements.
The recommendation from that page still holds: prefer plain relative paths when a relative path is enough. Reach for placeholders only when the value you need genuinely depends on the cross axis.
Where to go next
- Project layout — the everyday case.
- Cross-building — cross axes themselves, beyond just the source layout.
- Path replacements — the full placeholder vocabulary.