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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ systems like Buck and Bazel.
6. [Configuration performance](docs/CONFIGURATION-PERFORMANCE.md) — measure + hot-path guidance (F-017 / GH #106)
7. [forma-core public API design](docs/forma-core-api.md) — extraction contract for types / restrictions / registry (F-020)
8. [JVM targets](docs/JVM-TARGETS.md) — pure JVM platform plugin `tools.forma.jvm` (F-030)
9. [JVM sample application](docs/JVM-SAMPLE.md) — multi-module pure-JVM gold standard (`jvm-application/`, F-031)

Configuration made easy:

Expand Down Expand Up @@ -202,6 +203,7 @@ forma-core validation SPI + content rules (Gradle facade in `:validation`): **F-
forma-core TargetRegistry + Android DSL consumers: **F-023** done.
Publish coordinates for `tools.forma:core` (library) + facade deprecation policy: **F-024** done (see [`docs/PLUGIN-PUBLISH.md`](docs/PLUGIN-PUBLISH.md)).
Pure JVM target set (`tools.forma.jvm`, `JvmTargetRegistry`): **F-030** done (see [`docs/JVM-TARGETS.md`](docs/JVM-TARGETS.md)).
JVM sample application + `binary` composition root: **F-031** done ([`docs/JVM-SAMPLE.md`](docs/JVM-SAMPLE.md), `jvm-application/`).

Icons made by <a href="https://www.flaticon.com/authors/freepik" title="Freepik">Freepik</a>
from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
2 changes: 1 addition & 1 deletion TICKETS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Update this file when picking or finishing work. Cron workers must pick the **hi
| ID | Status | Title | Notes |
|----|--------|-------|-------|
| F-030 | done | JVM target set on forma-core (`library`, `api`, `impl`, `utils`, tests) | `plugins/jvm` + `tools.forma.jvm`; `docs/JVM-TARGETS.md` |
| F-031 | todo | JVM sample application | |
| F-031 | done | JVM sample application | `jvm-application/` + `binary` DSL; docs/JVM-SAMPLE.md |
| F-032 | todo | Docs: JVM getting started | |

## P4 — Bazel
Expand Down
5 changes: 3 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ that compose via `includeBuild`:
|------|------|----------------|-------------------------|
| `plugins/` | Forma Gradle plugins (main product) | 8.3 | Composite + Plugin Portal (`tools.forma.*`) |
| `application/` | Sample Android product (gold standard) | 8.4 | Consumer only |
| `jvm-application/` | Sample pure-JVM product (`tools.forma.jvm`) | 8.4 | Consumer only |
| `includer/` | Settings plugin: auto-`include` subprojects | (own wrapper) | `tools.forma.includer` |
| `depgen/` | Transitive-deps generation plugin | (own wrapper) | `tools.forma.depgen` (standalone) |
| `build-settings/` | Shared settings conventions (repos) | — | `includeBuild` / convention plugins |
Expand Down Expand Up @@ -335,7 +336,7 @@ Suggested extraction order (tickets F-020…F-024):
5. Coordinates + publish finalization: `tools.forma:core` (library) vs `tools.forma.android` + facades (F-024 done — see `docs/PLUGIN-PUBLISH.md`).
6. JVM platform consumer of core — **done (F-030):** `plugins/jvm`, plugin id
`tools.forma.jvm`, `JvmTargetRegistry` + matrix ([`JVM-TARGETS.md`](JVM-TARGETS.md)).
Sample JVM app is **F-031**.
Sample JVM app is **F-031** done (`jvm-application/`, `binary` composition root).

---

Expand Down Expand Up @@ -368,7 +369,7 @@ Suggested extraction order (tickets F-020…F-024):
| External deps UX | `plugins/deps` catalog + `build-dependencies` |
| Auto module discovery | `includer/` |
| CI | `.github/workflows/main.yml` |
| Sample structure | `application/feature/**`, `binary/` · [SAMPLE-APP.md](SAMPLE-APP.md) |
| Sample structure | `application/feature/**`, `binary/` · [SAMPLE-APP.md](SAMPLE-APP.md); JVM: `jvm-application/` · [JVM-SAMPLE.md](JVM-SAMPLE.md) |

---

Expand Down
111 changes: 111 additions & 0 deletions docs/JVM-SAMPLE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# JVM sample application (F-031)

Pure-JVM product reference for **`tools.forma.jvm`**, parallel to the Android
[`application/`](../application/) gold standard.

Location: **`jvm-application/`** at the repo root.

## Layout

```
jvm-application/
├── binary/ binary (composition root + run)
├── common/
│ ├── library/ library
│ ├── util/ util
│ └── test-util/ testUtil
└── feature/
├── greeter/{api,impl}
└── calculator/{api,impl}
```

Includer discovers every `build.gradle.kts` (`arbitraryBuildScriptNames = true`).
Project path uses `-` instead of `/` for Gradle names (e.g.
`feature/greeter/impl` → `:feature-greeter-impl`). Forma `target(":feature:greeter:impl")`
uses colon path form.

## What it exercises

| Target | Role in sample |
|--------|----------------|
| `api` / `impl` | Two features (greeter, calculator); **impl ↛ impl** |
| `library` | Shared math helpers |
| `util` | String helpers used by greeter impl |
| `testUtil` | Shared test helpers (wired as `testDependencies` on greeter impl) |
| `binary` | Composition root: wires both feature **api + impl** + shared modules; Gradle `application` plugin provides `:binary:run` |

Cross-feature code only through `api`. Multiple `impl`s meet only at `binary`.

## Package naming

| Module path | `packageName` |
|-------------|----------------|
| `feature/greeter/api` | `tools.forma.jvm.sample.feature.greeter.api` |
| `feature/greeter/impl` | `tools.forma.jvm.sample.feature.greeter.impl` |
| `feature/calculator/api` | `tools.forma.jvm.sample.feature.calculator.api` |
| `feature/calculator/impl` | `tools.forma.jvm.sample.feature.calculator.impl` |
| `common/library` | `tools.forma.jvm.sample.common.library` |
| `common/util` | `tools.forma.jvm.sample.common.util` |
| `common/test-util` | `tools.forma.jvm.sample.common.testutil` |
| `binary` | `tools.forma.jvm.sample.binary` |

Sources live under `src/main/kotlin/<package-as-dirs>/…`.

## Build / run

Requires JDK **17+** (same host bootstrap as Android: `source scripts/env-mac.sh`).
**No Android SDK.**

```bash
source scripts/env-mac.sh
cd jvm-application
./gradlew build
./gradlew :binary:run
```

Expected run output:

```
Hello, World! (2 + 3 = 5)
JVM sample (tools.forma.jvm) build + run successful.
```

Composite includes: `../plugins` (`tools.forma.jvm`), `../includer`,
`../build-settings` (`convention-dependencies` for `mavenCentral` /
`google` / Plugin Portal on project resolution).

## Consumer DSL notes

JVM entrypoints live in package **`tools.forma.jvm`** (unlike Android DSL files
in the default package). Sample scripts import them explicitly:

```kotlin
import tools.forma.jvm.api
import tools.forma.jvm.impl
import tools.forma.jvm.binary
// …

binary(
packageName = "tools.forma.jvm.sample.binary",
mainClass = "tools.forma.jvm.sample.binary.MainKt",
dependencies = deps(
target(":feature:greeter:api"),
target(":feature:greeter:impl"),
// …
)
)
```

For a top-level `fun main()` in `Main.kt`, `mainClass` is `…MainKt`.

## Matrix

Full pure-JVM dependency matrix (including `binary`):
[`JVM-TARGETS.md`](JVM-TARGETS.md).

## See also

- [`JVM-TARGETS.md`](JVM-TARGETS.md) — types + matrix
- [`SAMPLE-APP.md`](SAMPLE-APP.md) — Android multi-feature analog
- [`VISION.md`](VISION.md) — product sequencing (JVM after Android + core)
- F-032 — fuller JVM getting-started tutorial (next)
45 changes: 24 additions & 21 deletions docs/JVM-TARGETS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# JVM targets (F-030)
# JVM targets (F-030 / F-031)

Pure **JVM** platform for Forma, built on **forma-core** (`tools.forma:core`)
without AGP. Parallel to the Android product (`tools.forma.android`), not a
Expand All @@ -15,37 +15,38 @@ Plugin id: **`tools.forma.jvm`** (module `plugins/:jvm`).
| `library` | `jvm.library` | `library` | Shared pure-JVM library |
| `util` | `jvm.util` | `util` | Utilities / extensions |
| `testUtil` | `jvm.test-util` | `test-util` | Shared test helpers |
| `binary` | `jvm.binary` | `binary` | Composition root + runnable app |

Name matching is the same as Android / core: project name equals the suffix or
ends with `-$suffix` (see `SuffixNameMatcher`).

Composition roots (`binary` / app) are **out of scope for F-030** (sample app
is F-031). Until then, feature graphs should avoid needing multi-`impl`
composition at a root.
`binary(...)` applies `org.jetbrains.kotlin.jvm` + Gradle `application`, sets
`mainClass`, and may depend on multiple `impl` modules (composition root).

## Dependency matrix (project edges only)

External / catalog GAV deps are not filtered by type (same as Android).

| Consumer ↓ \ Dep → | api | impl | library | util | test-util |
|--------------------|-----|------|---------|------|-----------|
| **api** | Y | — | Y | — | — |
| **impl** | Y | — | Y | Y | Y |
| **library** | — | — | — | Y | Y |
| **util** | — | — | Y | Y | — |
| **test-util** | — | — | Y | Y | Y |
| Consumer ↓ \ Dep → | api | impl | library | util | test-util | binary |
|--------------------|-----|------|---------|------|-----------|--------|
| **api** | Y | — | Y | — | — | — |
| **impl** | Y | — | Y | Y | Y | — |
| **library** | — | — | — | Y | Y | — |
| **util** | — | — | Y | Y | — | — |
| **test-util** | — | — | Y | Y | Y | — |
| **binary** | Y | Y | Y | Y | Y | — |

### Rules of thumb

- **`impl` ↛ `impl`** — implementations stay independent; compose later at a
binary/app root (F-031).
- **`impl` ↛ `impl`** — implementations stay independent; compose at `binary`.
- **`api` only sees contracts** — `api` + `library` (no util/impl leakage into
the public surface).
- **`library` stays leaf-ish** — only `util` / `test-util` (parity with the
historical JVM `library()` row inside the Android matrix).
- **`util` cannot depend on `api` / `impl`**.
- **`binary` is the composition root** — may pull api + multiple impls + shared.
- **Content rules** — pure JVM has no Android `res/`; no content rules are
attached for F-030.
attached.

Source of truth in code: `plugins/jvm/.../JvmTargetRegistry.kt`
(`registerJvmDefaults`).
Expand All @@ -56,8 +57,9 @@ Source of truth in code: `plugins/jvm/.../JvmTargetRegistry.kt`
`testUtil`) **inside** `tools.forma.android`, registered on
`AndroidTargetRegistry` with some shared **id strings** (`jvm.library`,
`jvm.util`).
- F-030 introduces an **authoritative JVM platform registry**
- F-030 introduced an **authoritative JVM platform registry**
(`JvmTargetRegistry`) and plugin so pure-JVM products do not need AGP.
- F-031 added **`binary`** + the multi-module sample `jvm-application/`.
- Registries are **separate singletons**; shared id strings are intentional.
- Full Android matrix remains [`DEPENDENCY-MATRIX.md`](DEPENDENCY-MATRIX.md).

Expand All @@ -73,12 +75,12 @@ pluginManagement {
}
plugins {
id("tools.forma.jvm") version "0.1.3"
// or includeBuild("../plugins") in a composite
}

// some-module/build.gradle.kts
// module build.gradle.kts
import tools.forma.jvm.api
import tools.forma.jvm.library
// …

api(
packageName = "com.example.feature.api",
Expand All @@ -98,17 +100,18 @@ compatibility is Java **11** (`JvmDefaults`).
## Tests

- `plugins/jvm` unit tests: `JvmTargetRegistryTest` (matrix allow/deny,
self-suffix, validator identity cache).
self-suffix, validator identity cache, binary composition-root row).
- Engine coverage remains in `plugins/core` tests.
- Integration: [`jvm-application/`](../jvm-application/) (`./gradlew build` /
`:binary:run`).

## Next

- **F-031** — multi-module JVM sample application (gold standard like
`application/`).
- **F-032** — JVM getting-started tutorial.
- **F-032** — JVM getting-started tutorial (point at this doc + sample).

## See also

- [`JVM-SAMPLE.md`](JVM-SAMPLE.md) — multi-module sample layout + run
- [`forma-core-api.md`](forma-core-api.md) — core types / registry SPI
- [`ARCHITECTURE.md`](ARCHITECTURE.md) — module graph
- [`PLUGIN-PUBLISH.md`](PLUGIN-PUBLISH.md) — coordinates
Expand Down
25 changes: 25 additions & 0 deletions docs/PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,31 @@

Newest entries first.

## 2026-07-13 — F-031 JVM sample application

- **Ticket:** F-031 → `done`
- **Branch:** `forma/F-031-jvm-sample` (from `origin/v2`)
- **Skills/modes:** Grok Build `--mode full` (design plan completed; implement hit max-turns with partial tree on disk); Hermes finished wiring, verify, docs, commit/PR
- **Platform (`plugins/jvm`):**
- New type **`jvm.binary`** + `binary(...)` DSL (Kotlin JVM + Gradle `application` + `mainClass`)
- `registerJvmDefaults` composition-root row: binary → api/impl/library/util/test-util (no binary→binary)
- `impl` / `library` `testDependencies` widened to `FormaDependency` (project test-util)
- `JvmTargetRegistryTest` updated for six types + binary matrix/self-suffix
- **Sample (`jvm-application/`):**
- Multi-module pure-JVM product: greeter + calculator features (api/impl), common library/util/test-util, binary composition root
- Composite: `includeBuild` plugins + includer + build-settings; `convention-dependencies` for project repos
- Explicit `import tools.forma.jvm.*` in module scripts (JVM DSL is packaged, not default package)
- Runnable: `./gradlew :binary:run` → `Hello, World! (2 + 3 = 5)`
- **Docs:** `docs/JVM-SAMPLE.md`; JVM-TARGETS matrix + binary row; ARCHITECTURE layout; README Progress + getting-started link; TICKETS
- **Verify (OpenJDK 17 + env-mac.sh):**
- `plugins/`: `./gradlew :core:test :jvm:test build` → **BUILD SUCCESSFUL** (earlier full build; re-run `:jvm:test` green)
- `jvm-application/`: `./gradlew build :binary:run` → **BUILD SUCCESSFUL** (run output as above)
- `application/`: `./gradlew :binary:assembleDebug` → **BUILD SUCCESSFUL** (578 tasks)
- **Grounded:** all commands executed; no invented green builds.
- **Commits/PRs:** this run — push + PR base `v2`
- **Blockers:** none (Grok implement max-turns; completed under Hermes)
- **Next step:** F-032 Docs: JVM getting started

## 2026-07-13 — F-030 JVM target set on forma-core

- **Ticket:** F-030 → `done`
Expand Down
14 changes: 14 additions & 0 deletions jvm-application/binary/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import tools.forma.jvm.binary

binary(
packageName = "tools.forma.jvm.sample.binary",
mainClass = "tools.forma.jvm.sample.binary.MainKt",
dependencies = deps(
target(":feature:greeter:api"),
target(":feature:greeter:impl"),
target(":feature:calculator:api"),
target(":feature:calculator:impl"),
target(":common:util"),
target(":common:library")
)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package tools.forma.jvm.sample.binary

import tools.forma.jvm.sample.feature.greeter.api.Greeter
import tools.forma.jvm.sample.feature.greeter.impl.GreeterImpl
import tools.forma.jvm.sample.feature.calculator.api.Calculator
import tools.forma.jvm.sample.feature.calculator.impl.CalculatorImpl

/**
* Composition root demo for F-031.
* Wires impls (greeter + calculator) + shared library/util only at the binary.
* Cross-feature communication happens exclusively through api modules.
*/
fun main() {
val greeter: Greeter = GreeterImpl()
val calculator: Calculator = CalculatorImpl()

val greeting = greeter.greet("world")
val sum = calculator.add(2, 3)

println("$greeting (2 + 3 = $sum)")
println("JVM sample (tools.forma.jvm) build + run successful.")
}
4 changes: 4 additions & 0 deletions jvm-application/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Root build for the pure-JVM Forma sample (F-031).
// No androidProjectConfiguration or jvmProjectConfiguration hook is required yet.
// The tools.forma.jvm settings plugin + DSL entrypoints (api/impl/library/util/testUtil/binary)
// configure each module when their build.gradle.kts are evaluated.
3 changes: 3 additions & 0 deletions jvm-application/common/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import tools.forma.jvm.library

library(packageName = "tools.forma.jvm.sample.common.library")
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package tools.forma.jvm.sample.common.library

object Math {
fun add(a: Int, b: Int): Int = a + b
fun multiply(a: Int, b: Int): Int = a * b
}
3 changes: 3 additions & 0 deletions jvm-application/common/test-util/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import tools.forma.jvm.testUtil

testUtil(packageName = "tools.forma.jvm.sample.common.testutil")
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package tools.forma.jvm.sample.common.testutil

// Shared test helpers (exercises jvm.test-util target + matrix).
// No src/test sources in this minimal sample; the module is declared via testDependencies
// on impls if needed in future. Presence proves registration and build.
object TestHelpers {
fun echo(s: String): String = s
}
3 changes: 3 additions & 0 deletions jvm-application/common/util/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import tools.forma.jvm.util

util(packageName = "tools.forma.jvm.sample.common.util")
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package tools.forma.jvm.sample.common.util

fun greet(name: String): String = "Hello, ${name.lowercase().replaceFirstChar { it.uppercase() }}!"
3 changes: 3 additions & 0 deletions jvm-application/feature/calculator/api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import tools.forma.jvm.api

api(packageName = "tools.forma.jvm.sample.feature.calculator.api")
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package tools.forma.jvm.sample.feature.calculator.api

interface Calculator {
fun add(a: Int, b: Int): Int
}
10 changes: 10 additions & 0 deletions jvm-application/feature/calculator/impl/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import tools.forma.jvm.impl

impl(
packageName = "tools.forma.jvm.sample.feature.calculator.impl",
dependencies = deps(
target(":feature:calculator:api"),
target(":common:library"),
target(":common:util")
)
)
Loading
Loading