Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions crates/chunk-cli/src/creation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,17 @@ fn create(options: &Options) -> io::Result<()> {
.file_name()
.and_then(|name| name.to_str())
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "project directory must have a UTF-8 name"))?;
let (plugin, settings, app_build, gameplay, gameplay_path) = match options.language {
let (settings, root_build, app_build, gameplay, gameplay_path) = match options.language {
Language::Java => (
"com.chunkzero.chunk",
include_str!("../templates/java/settings.gradle.kts"),
include_str!("../templates/java/build.gradle.kts"),
include_str!("../templates/java/app.gradle.kts"),
include_str!("../templates/java/Lobby.java"),
"apps/lobby/src/main/java/example/Lobby.java",
),
Language::Kotlin => (
"com.chunkzero.chunk.kotlin",
include_str!("../templates/kotlin/settings.gradle.kts"),
include_str!("../templates/kotlin/build.gradle.kts"),
include_str!("../templates/kotlin/app.gradle.kts"),
include_str!("../templates/kotlin/Lobby.kt"),
"apps/lobby/src/main/kotlin/example/Lobby.kt",
Expand All @@ -90,18 +90,16 @@ fn create(options: &Options) -> io::Result<()> {
.replace("\"@KOTLIN_VERSION@\"", &kotlin(&toolchain.versions.kotlin))
.replace("\"@FOOJAY_VERSION@\"", &kotlin(&toolchain.versions.foojay))
.replace("\"@PROJECT_NAME@\"", &kotlin(name));
let app_build = app_build.replace("@CHUNK_VERSION@", &toolchain.versions.chunk);
let readme = include_str!("../templates/common/README.md").replace("@PROJECT_NAME@", name);
let root_build = format!(
"plugins {{\n id(\"{plugin}\")\n}}\n\njava {{ toolchain.languageVersion = JavaLanguageVersion.of(25) }}\n"
);

let staging = tempfile::tempdir()?;
for (name, content) in COMMON.iter().copied().chain([
("settings.gradle.kts", settings.as_str()),
("README.md", &readme),
("build.gradle.kts", &root_build),
("build.gradle.kts", root_build),
("gradle.properties", &properties),
("apps/lobby/build.gradle.kts", app_build),
("apps/lobby/build.gradle.kts", &app_build),
(gameplay_path, gameplay),
]) {
let target = staging.path().join(name);
Expand Down
8 changes: 8 additions & 0 deletions crates/chunk-cli/src/creation/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ fn cli_creates_both_languages_without_adjacent_sdk_files() {
matches!(language, Language::Kotlin),
);
assert!(settings.contains(&format!("rootProject.name = {name:?}")));
let engine = match language {
Language::Java => "multistom",
Language::Kotlin => "multistom-kotlin",
};
let app_build = fs::read_to_string(options.directory.join("apps/lobby/build.gradle.kts")).unwrap();
assert!(app_build.contains(&format!("\"com.chunkzero.chunk:{engine}:{}\"", crate::RELEASE_VERSION)));
let root_build = fs::read_to_string(options.directory.join("build.gradle.kts")).unwrap();
assert_eq!(root_build.contains("org.jetbrains.kotlin.jvm"), matches!(language, Language::Kotlin));
let properties = fs::read_to_string(options.directory.join("gradle.properties")).unwrap();
assert!(!properties.contains("chunk.source="));
assert!(!properties.contains("sdk/maven"));
Expand Down
4 changes: 2 additions & 2 deletions crates/chunk-cli/templates/common/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ downloads it if needed. Node is not needed: the CLI includes its TypeScript comp
`gradle/wrapper/` in version control.

Gradle downloads the chunk libraries and plugins from `maven.chunkzero.com`, at the versions pinned in
`settings.gradle.kts` when the CLI created the project. A newer CLI does not change those pins, so keep the CLI and the
pins on the same version.
`settings.gradle.kts` and `apps/lobby/build.gradle.kts` when the CLI created the project. A newer CLI does not change
those pins, so keep the CLI and the pins on the same version.

`./gradlew` and IDE builds run the `chunk` on your `PATH`; pass `-Pchunk.executable=...` to use another. `chunk build`
and `chunk dev` always use the CLI you run them with. To build against unpublished chunk libraries, set the
Expand Down
2 changes: 2 additions & 0 deletions crates/chunk-cli/templates/java/app.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ plugins {
java { toolchain.languageVersion = JavaLanguageVersion.of(25) }
tasks.withType<JavaCompile>().configureEach { options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror")) }

dependencies { implementation("com.chunkzero.chunk:multistom:@CHUNK_VERSION@") }

application { mainClass = "example.Lobby" }
5 changes: 5 additions & 0 deletions crates/chunk-cli/templates/java/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
plugins {
id("com.chunkzero.chunk")
}

java { toolchain.languageVersion = JavaLanguageVersion.of(25) }
11 changes: 8 additions & 3 deletions crates/chunk-cli/templates/kotlin/app.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
plugins {
id("com.chunkzero.chunk.kotlin")
id("org.jetbrains.kotlin.jvm")
id("com.chunkzero.chunk")
}

java { toolchain.languageVersion = JavaLanguageVersion.of(25) }
kotlin { compilerOptions { allWarningsAsErrors = true } }
kotlin {
jvmToolchain(25)
compilerOptions { allWarningsAsErrors = true }
}

dependencies { implementation("com.chunkzero.chunk:multistom-kotlin:@CHUNK_VERSION@") }

application { mainClass = "example.LobbyKt" }
6 changes: 6 additions & 0 deletions crates/chunk-cli/templates/kotlin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
plugins {
id("org.jetbrains.kotlin.jvm")
id("com.chunkzero.chunk")
}

kotlin { jvmToolchain(25) }
6 changes: 3 additions & 3 deletions docs/distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ How the chunk SDK is packaged, installed and published. A release of the SDK has
TypeScript SDK, the project templates, the Gradle wrapper and the version pins that `chunk create` writes.
- **The JVM artifacts**, published to `https://maven.chunkzero.com` (nightlies to
`https://maven.chunkzero.com/nightlies`) under `com.chunkzero.chunk`: `proto`, `backend-api`, `backend-client`,
`backend-client-kotlin`, `runtime`, `multistom`, `multistom-kotlin`, `minestom`, the Gradle plugin and its three
plugin markers. Each library has `-sources.jar` and `-javadoc.jar` (Dokka HTML) artifacts. Projects resolve these
through Gradle; the CLI archives contain no JARs.
`backend-client-kotlin`, `runtime`, `multistom`, `multistom-kotlin`, `minestom`, `world-converter`, the Gradle plugin
and its two plugin markers. Each library has `-sources.jar` and `-javadoc.jar` (Dokka HTML) artifacts. Projects
resolve these through Gradle; the CLI archives contain no JARs.

Linux archives are built on Ubuntu 24.04 and need its glibc and OpenSSL 3; macOS and Windows archives link OpenSSL
statically. Users need Java to run Gradle; the Minestom runtime needs Java 25, which the generated projects download
Expand Down
1 change: 1 addition & 0 deletions examples/arena/apps/arena/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ tasks.withType<JavaCompile>().configureEach {
}

dependencies {
implementation("com.chunkzero.chunk:multistom:${libs.versions.chunk.get()}")
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter)
testRuntimeOnly(libs.junit.platform.launcher)
Expand Down
2 changes: 2 additions & 0 deletions examples/arena/apps/lobby/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror"))
}

dependencies { implementation("com.chunkzero.chunk:multistom:${libs.versions.chunk.get()}") }

application { mainClass = "example.lobby.Lobby" }
11 changes: 8 additions & 3 deletions examples/load/apps/lobby/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
plugins {
id("com.chunkzero.chunk.kotlin")
id("org.jetbrains.kotlin.jvm")
id("com.chunkzero.chunk")
}

java { toolchain.languageVersion = JavaLanguageVersion.of(25) }
kotlin { compilerOptions { allWarningsAsErrors = true } }
kotlin {
jvmToolchain(25)
compilerOptions { allWarningsAsErrors = true }
}

dependencies { implementation("com.chunkzero.chunk:multistom-kotlin:${libs.versions.chunk.get()}") }

application { mainClass = "com.chunkzero.chunk.example.load.LobbyKt" }
5 changes: 3 additions & 2 deletions examples/load/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id("com.chunkzero.chunk.kotlin")
id("org.jetbrains.kotlin.jvm")
id("com.chunkzero.chunk")
}

java { toolchain.languageVersion = JavaLanguageVersion.of(25) }
kotlin { jvmToolchain(25) }
1 change: 1 addition & 0 deletions examples/load/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ dependencyResolutionManagement {
}
mavenCentral()
}
versionCatalogs { create("libs") { from(files("../../gradle/libs.versions.toml")) } }
}

includeBuild("../..") { name = "chunk-platform" }
Expand Down
14 changes: 10 additions & 4 deletions examples/local/apps/games/arena/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
plugins {
id("com.chunkzero.chunk.kotlin")
id("org.jetbrains.kotlin.jvm")
id("com.chunkzero.chunk")
}

java { toolchain.languageVersion = JavaLanguageVersion.of(25) }
kotlin { compilerOptions { allWarningsAsErrors = true } }
kotlin {
jvmToolchain(25)
compilerOptions { allWarningsAsErrors = true }
}

dependencies { implementation(project(":shared")) }
dependencies {
implementation(project(":shared"))
implementation("com.chunkzero.chunk:multistom-kotlin:${libs.versions.chunk.get()}")
}

application { mainClass = "com.chunkzero.chunk.example.arena.ArenaSessionsKt" }
14 changes: 10 additions & 4 deletions examples/local/apps/lobby/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
plugins {
id("com.chunkzero.chunk.kotlin")
id("org.jetbrains.kotlin.jvm")
id("com.chunkzero.chunk")
}

java { toolchain.languageVersion = JavaLanguageVersion.of(25) }
kotlin { compilerOptions { allWarningsAsErrors = true } }
kotlin {
jvmToolchain(25)
compilerOptions { allWarningsAsErrors = true }
}

dependencies { implementation(project(":shared")) }
dependencies {
implementation(project(":shared"))
implementation("com.chunkzero.chunk:multistom-kotlin:${libs.versions.chunk.get()}")
}

application { mainClass = "com.chunkzero.chunk.example.lobby.LobbySessionsKt" }
5 changes: 3 additions & 2 deletions examples/local/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id("com.chunkzero.chunk.kotlin")
id("org.jetbrains.kotlin.jvm")
id("com.chunkzero.chunk")
}

java { toolchain.languageVersion = JavaLanguageVersion.of(25) }
kotlin { jvmToolchain(25) }
10 changes: 7 additions & 3 deletions examples/local/shared/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
plugins {
id("com.chunkzero.chunk.kotlin")
id("org.jetbrains.kotlin.jvm")
id("com.chunkzero.chunk")
}

java { toolchain.languageVersion = JavaLanguageVersion.of(25) }
kotlin { compilerOptions { allWarningsAsErrors = true } }
kotlin {
jvmToolchain(25)
compilerOptions { allWarningsAsErrors = true }
}

dependencies {
api("com.chunkzero.chunk:multistom-kotlin:${libs.versions.chunk.get()}")
testImplementation("com.chunkzero.chunk:proto:${libs.versions.chunk.get()}")
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter)
Expand Down
6 changes: 3 additions & 3 deletions jvm/backend-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ wait.
## Kotlin

`jvm/backend-client-kotlin` wraps a `BackendSession` as `CoroutineBackend`, with suspending calls, actions included, and
`Flow<WatchState<R>>` watches, bound to an owned `CoroutineScope` that closes its calls and watches when it ends. With
the `com.chunkzero.chunk.kotlin` plugin, projects also get a generated `CoroutineBackendClient` with property-style
namespaces:
`Flow<WatchState<R>>` watches, bound to an owned `CoroutineScope` that closes its calls and watches when it ends. When
the root build applies Kotlin, Kotlin projects also get a generated `CoroutineBackendClient` with property-style
namespaces (see the [Gradle plugin](../gradle-plugin/README.md#kotlin)):

```kotlin
val backend = CoroutineBackendClient(scope.coroutines.backend(requireNotNull(scope.backend), player))
Expand Down
88 changes: 67 additions & 21 deletions jvm/gradle-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ backend bindings before JVM compilation, index each app's session types, session
the finished app JARs for `chunk build` to package into a release. This is a separate Kotlin build, included by the
repository's root build.

| Plugin | Apply in | Does |
| ------------------------------ | ------------------------------- | --------------------------------------------------------------- |
| `com.chunkzero.chunk.settings` | `settings.gradle.kts` | Runs `chunk inspect` and includes every app as a Gradle project |
| `com.chunkzero.chunk` | The root build and Java modules | Shared Java bindings, app packaging and indexing |
| `com.chunkzero.chunk.kotlin` | The root build and Kotlin apps | Everything above, plus Kotlin and the coroutine backend facade |
| Plugin | Apply in | Does |
| ------------------------------ | -------------------------------------- | --------------------------------------------------------------- |
| `com.chunkzero.chunk.settings` | `settings.gradle.kts` | Runs `chunk inspect` and includes every app as a Gradle project |
| `com.chunkzero.chunk` | The root build, apps and their modules | Shared bindings, app packaging and indexing |

The plugins don't choose a game engine or configure Kotlin: each app adds its engine library and, for Kotlin, applies
and configures the Kotlin plugin itself.

## Setup

Expand Down Expand Up @@ -43,29 +45,72 @@ dependencyResolutionManagement {
rootProject.name = "my-server"
```

The root `build.gradle.kts` and each app's `build.gradle.kts` apply a project plugin and choose a Java toolchain; apps
also name their main class:
The root `build.gradle.kts` and each app's `build.gradle.kts` apply `com.chunkzero.chunk` and choose a Java toolchain;
apps also add their engine and name their main class:

```kotlin
// build.gradle.kts
plugins { id("com.chunkzero.chunk") }

java { toolchain.languageVersion = JavaLanguageVersion.of(25) }
```

```kotlin
// apps/lobby/build.gradle.kts
plugins { id("com.chunkzero.chunk") }

java { toolchain.languageVersion = JavaLanguageVersion.of(25) }

dependencies { implementation("com.chunkzero.chunk:multistom:0.1.0") }

application { mainClass = "example.Lobby" }
```

- Every module that applies a Chunk plugin must set its Java toolchain explicitly, to Java 25 or newer (the Minestom
runtime's requirement), and compile for that same version. `validateChunkJvm` fails the build otherwise; Chunk never
falls back to the Gradle daemon's JVM.
- The root applies the same plugin as the apps before they do. For Kotlin apps that is `com.chunkzero.chunk.kotlin`, and
the Kotlin Gradle plugin's version must be declared in the settings `plugins` block, as above, so that Chunk's
settings plugin can see it. Java apps in a Kotlin project keep `com.chunkzero.chunk`.
- A library module (such as a `:shared` project with common gameplay code) applies a Chunk plugin too if it declares
- Every module that applies the Chunk plugin must set its Java toolchain explicitly, to Java 25 or newer (the Minestom
runtime's requirement), and compile Java and Kotlin for that same version. `validateChunkJvm` fails the build
otherwise; Chunk never falls back to the Gradle daemon's JVM.
- The root applies the plugin before the apps do.
- A library module (such as a `:shared` project with common gameplay code) applies the plugin too if it declares
`@Component` factories, so its factories are indexed.

The project plugins add the Chunk libraries at the plugin's own version: the root exports
`com.chunkzero.chunk:backend-client`, Java apps get `multistom` and Kotlin apps `multistom-kotlin`.
The root exports `com.chunkzero.chunk:backend-client` at the plugin's own version. The engine is the app's choice:
`com.chunkzero.chunk:multistom` (or `multistom-kotlin` for coroutine sessions) runs sessions on the multistom fork,
while [`com.chunkzero.chunk:minestom`](../minestom/README.md) with upstream `net.minestom:minestom` runs your own
sessions on upstream Minestom.

### Kotlin

When the root project applies `org.jetbrains.kotlin.jvm`, the plugin also generates the coroutine backend facade,
`CoroutineBackendClient`, and every module that applies Kotlin depends on it. Modules that don't apply Kotlin, such as
Java apps in the same build, get no Kotlin dependency: the root's own classes are Java and it doesn't export the Kotlin
standard library. Declare the Kotlin plugin's version either in the settings `plugins` block with `apply false` or in
the root build's `plugins` block:

```kotlin
// build.gradle.kts
plugins {
id("org.jetbrains.kotlin.jvm") // version in settings.gradle.kts: id("org.jetbrains.kotlin.jvm") version "2.4.10" apply false
id("com.chunkzero.chunk")
}

kotlin { jvmToolchain(25) }
```

```kotlin
// apps/lobby/build.gradle.kts
plugins {
id("org.jetbrains.kotlin.jvm")
id("com.chunkzero.chunk")
}

kotlin { jvmToolchain(25) }

dependencies { implementation("com.chunkzero.chunk:multistom-kotlin:0.1.0") }

application { mainClass = "example.LobbyKt" }
```

A Kotlin app in a build whose root doesn't apply Kotlin uses the Java `BackendClient`.

### Settings

Expand All @@ -89,22 +134,23 @@ run as. The plugin runs that CLI; it never builds Rust tools or installs Node pa
1. Settings evaluation runs `chunk inspect` and includes each app directory as a Gradle project: `apps/games/arena`
becomes `:apps:games:arena`. Adding an `app.ts` and a `build.gradle.kts` under `apps/` adds an app on the next build,
including with the configuration cache. It also includes a reserved `:chunk:backend-kotlin` project under
`.chunk/gradle/`.
`.chunk/gradle/`, which stays empty unless the root applies Kotlin.
2. `generateChunkBackend`, on the root, runs `chunk gen` before any JVM compilation. The compiled backend goes to
`.chunk/build/backend` and JVM sources to `.chunk/generated/jvm`: `java/` (models, references, `SessionMethods`,
`SessionConfigs`, `Destinations`, `Vars`), `java-client/` (`BackendClient`), `java-session/<app>/` (configured
provider interfaces) and, for Kotlin, `kotlin/` (`CoroutineBackendClient`). The task always runs, since the compiler
resolves TypeScript dependencies itself; unchanged output keeps JVM compilation incremental.
3. The root compiles `java/` and `java-client/` once into the shared `chunk-backend` JAR. With the Kotlin plugin,
3. The root compiles `java/` and `java-client/` once into the shared `chunk-backend` JAR. When the root applies Kotlin,
`:chunk:backend-kotlin` compiles the coroutine facade into `chunk-backend-kotlin`. Apps depend on these JARs, so a
Java app has no Kotlin dependency.
4. Each app applies `application` and Shadow, compiles, and is indexed from its bytecode (below). Its `shadowJar` is an
executable JAR with every dependency.
5. Each app's Anvil worlds (`format: "anvil"` in `chunk inspect`) are converted to
`.chunk/build/worlds/<app>/<name>.polar` by `convertChunkWorld_<name>`, cropped to the world's `chunks` if given. The
converter runs in the app's Java toolchain with the Minestom and Polar of its runtime classpath, leaving out the
build's own projects so code changes don't reconvert. It rejects a save whose data version isn't the runtime's
Minecraft version. The conversion is cacheable and its output is the same bytes for the same save.
converter, `com.chunkzero.chunk:world-converter` at the plugin's version, runs in the app's Java toolchain on the
app's runtime classpath, leaving out the build's own projects so code changes don't reconvert. It uses the app's
Minestom, upstream or multistom, and the app's Polar if it has one. It rejects a save whose data version isn't that
Minestom's Minecraft version. The conversion is cacheable and its output is the same bytes for the same save.
6. `chunkArtifacts`, on the root, builds every app and writes `.chunk/build/jvm/artifacts.json`: each app's JAR, session
types and Java version, and the Java executable of the newest toolchain. `chunk build` runs this task and packages
the result into a release. `chunk dev` passes `-Pchunk.dev=true`, which skips the shadow JAR and lists each app's
Expand Down
Loading
Loading