|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Build, Test, Run |
| 6 | + |
| 7 | +- `mvn clean package` — full build; produces the shaded kernel jar and the kernelspec zip under `jjava-distro/target/`. |
| 8 | +- `mvn clean verify` — what CI runs on Linux/macOS. Adds Failsafe integration tests (the `*IT.java` cases in `jjava-distro`, which spin up `eclipse-temurin` containers via Testcontainers). Testcontainers does not work on Windows; CI uses `mvn clean test` there. |
| 9 | +- `mvn test -pl jjava-kernel -am` — single-module unit tests (with required upstream modules built). |
| 10 | +- `mvn -pl jjava-kernel test -Dtest=JavaKernelExtensionsLifecycleTest` — run a single test class. |
| 11 | +- Surefire in `jjava-kernel` and `jjava-distro` is configured with `--add-opens jdk.jshell/jdk.jshell=ALL-UNNAMED` — required for JShell reflection. Anything that exercises `JavaKernel`/`CodeEvaluator` directly needs the same flag. |
| 12 | +- Local install of the freshly built kernel into Jupyter: see `DEV-GUIDE.md` (`unzip` the kernelspec zip, then `jupyter kernelspec install ... --name=java --user`). |
| 13 | + |
| 14 | +Minimum toolchain: JDK 11 (`maven.compiler.release=11`). CI matrix builds against 11, 17, 21, 25. |
| 15 | + |
| 16 | +## Module Layout and Dependency Graph |
| 17 | + |
| 18 | +This is a multi-module Maven project (`groupId: org.dflib.jjava`). Each module has a distinct role — do not collapse them. |
| 19 | + |
| 20 | +``` |
| 21 | +jjava-jupyter ← generic, language-agnostic Jupyter kernel framework |
| 22 | + ▲ |
| 23 | +jjava-kernel ← Java-specific kernel (uses JShell), built on jjava-jupyter |
| 24 | + ▲ |
| 25 | +jjava-maven ← Maven dependency resolution magics, built on jjava-kernel |
| 26 | + ▲ |
| 27 | +jjava-distro ← Fat/shaded jar combining kernel + maven + magics. THE deliverable. |
| 28 | +
|
| 29 | +jjava-launcher ← Tiny no-dependency jar; spawns the child java process for jjava-distro. |
| 30 | +``` |
| 31 | + |
| 32 | +- **`jjava-jupyter`** — Reusable JVM Jupyter library: the ZMQ wire protocol (jeromq), message types and Gson adapters, channel loops (`shell`, `iopub`, `stdin`, `heartbeat`, `control`), display/rendering, magics infrastructure (`MagicsRegistry`, `MagicsResolver`, `MagicTranspiler`), comm, history, and `BaseKernel` — the abstract superclass that any language-specific kernel extends. Published to Maven Central as a library for building other JVM kernels. |
| 33 | +- **`jjava-kernel`** — `JavaKernel extends BaseKernel`. Owns the `JShell` instance and `CodeEvaluator` (with custom `JJavaExecutionControl` and `JJavaLoaderDelegate`). Ships built-in magics under `org.dflib.jjava.kernel.magics`: `%load`, `%classpath`, `%time`, `%jars` (deprecated). |
| 34 | +- **`jjava-maven`** — Maven dependency resolution, isolated so the core kernel stays slim. Uses Maveniverse Mima for resolution. Ships `%maven`, `%mavenRepo`, `%loadFromPOM` (line + cell), `%addMavenDependency` (deprecated). |
| 35 | +- **`jjava-distro`** — The actual runnable kernel jar. Main class: `org.dflib.jjava.distro.JJava`. Wires all magics into `JavaKernel.builder()`, reads `JJAVA_*` env vars (see `Env.java`), parses the Jupyter connection file, and runs the message loop. Uses `maven-shade-plugin` to relocate every 3rd-party package (gson, jeromq, slf4j, maven, mima, etc.) under `org.dflib.jjava.shaded.*` — keep this in mind when reading stack traces from a built kernel. SLF4J is bound to `slf4j-jdk14` (JUL) inside the shaded jar. |
| 36 | +- **`jjava-launcher`** — Standalone jar with zero dependencies. Spawns a child `java` process running the distro jar, applying `--add-opens jdk.jshell/jdk.jshell=ALL-UNNAMED` and any `JJAVA_JVM_OPTS`. Its `package` phase also copies the launcher jar into `kernelspec/java/jjava-launcher.jar`. Uses `java.util.logging` directly rather than SLF4J because it must remain dependency-free. |
| 37 | + |
| 38 | +`kernelspec/java/kernel.json` is the Jupyter kernel descriptor — its `argv` invokes `jjava-launcher.jar`, which in turn invokes `jjava.jar`. The shade output of `jjava-distro` and the `jjava-launcher` jar are both copied into the assembled `jjava-<version>-kernelspec.zip` (see `jjava-distro/assembly/zipped-kernel.xml`). |
| 39 | + |
| 40 | +`pip/jjava/` and `pyproject.toml` package the kernelspec for PyPI distribution; version is injected from the git tag by the release workflow. |
| 41 | + |
| 42 | +## Runtime Behavior to Keep in Mind |
| 43 | + |
| 44 | +- **JShell-based execution.** All user code runs in a `JShell` instance owned by `JavaKernel`. `CodeEvaluator` orchestrates snippet evaluation, and `JJavaLoaderDelegate` participates in JShell's class loading. Adding to the classpath at runtime means calling `kernel.addToClasspath(...)`, which both extends JShell's classpath *and* triggers extension discovery from the new entries. |
| 45 | +- **Magic resolution happens before evaluation.** Cells are passed through `MagicTranspiler` / `MagicsResolver`. Line magics start with `%`, cell magics with `%%`. New magics are registered in `JJava.main()` via `.lineMagic(...)` / `.cellMagic(...)` on the `JavaKernel.Builder`. |
| 46 | +- **Extensions** are discovered via `ServiceLoader` for `org.dflib.jjava.jupyter.Extension`, scanning both the application classpath and any classpath added at runtime through magics. `JJAVA_LOAD_EXTENSIONS=false` disables this entirely. Extensions get an `install(BaseKernel)` callback on load and `uninstall` on shutdown. |
| 47 | +- **Environment variables** consumed by the kernel are centralized in `org.dflib.jjava.distro.Env`: `JJAVA_COMPILER_OPTS`, `JJAVA_TIMEOUT`, `JJAVA_CLASSPATH`, `JJAVA_STARTUP_SCRIPT`, `JJAVA_STARTUP_SCRIPTS_PATH`, `JJAVA_LOAD_EXTENSIONS`. `JJAVA_JVM_OPTS` is read by the launcher, not the kernel, and is *not* forwarded to the child process beyond being unpacked into the JVM command line. |
| 48 | +- **Logging.** Code in `jjava-jupyter`, `jjava-kernel`, `jjava-maven` uses SLF4J. The launcher and `JJava` main class use JUL directly (the launcher because it has no dependencies; `JJava.main` because it sets the JUL `SimpleFormatter.format` system property to mimic Jupyter's log style before the SLF4J→JUL bridge takes over). In the shaded distro jar, SLF4J is itself relocated to `org.dflib.jjava.shaded.org.slf4j` and routed to JUL via `slf4j-jdk14`. |
| 49 | +- **Integration tests** (`jjava-distro/src/test/java/.../*IT.java`) extend `ContainerizedKernelCase`, which mounts the locally-built `kernelspec/java` and `src/test/resources` into an `eclipse-temurin:<jdkVersion>` container and runs `jupyter` against the real kernel. These tests need a working Docker daemon and the kernel jars must already be packaged (Failsafe runs in the `verify` phase, after `package`). |
0 commit comments