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 @@ -48,6 +48,7 @@ systems like Buck and Bazel.
5. [Plugin publish path](docs/PLUGIN-PUBLISH.md) — Portal metadata DSL + release notes (F-016)
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)

Configuration made easy:

Expand Down Expand Up @@ -200,6 +201,7 @@ forma-core extraction (restriction engine + TargetType + Android matrix kit): **
forma-core validation SPI + content rules (Gradle facade in `:validation`): **F-022** done.
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)).

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 @@ -40,7 +40,7 @@ Update this file when picking or finishing work. Cron workers must pick the **hi

| ID | Status | Title | Notes |
|----|--------|-------|-------|
| F-030 | todo | JVM target set on forma-core (`library`, `api`, `impl`, `utils`, tests) | |
| 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-032 | todo | Docs: JVM getting started | |

Expand Down
22 changes: 15 additions & 7 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ that compose via `includeBuild`:

- `pluginManagement { includeBuild("../build-settings") }`
- `tools.forma.includer` **0.2.0** from Portal (not local includer composite)
- Includer discovers `:android`, `:config`, `:core`, `:deps`, `:owners`, `:target`, `:validation` (F-021 added `:core`)
- Includer discovers `:android`, `:config`, `:core`, `:deps`, `:jvm`, `:owners`, `:target`, `:validation` (F-021 `:core`; F-030 `:jvm`)

---

Expand Down Expand Up @@ -90,9 +90,12 @@ Group/version (root `plugins/build.gradle.kts`): **`tools.forma` / `0.1.3`**.
│ │ │
└─────┬─────┴────────────┘
┌─────┴─────┐
│ android │ DSL + AGP + AndroidTargetTypes + matrix + content helpers
└───────────┘
┌───────────┴───────────┐
│ │
┌─────┴─────┐ ┌─────┴─────┐
│ android │ │ jvm │ F-030 pure JVM DSL + JvmTargetRegistry
│ DSL+AGP │ │ no AGP │ (parallel platform consumer of core)
└───────────┘ └───────────┘
```

| Module | Plugin id | Depends on | Responsibility |
Expand All @@ -104,6 +107,7 @@ Group/version (root `plugins/build.gradle.kts`): **`tools.forma` / `0.1.3`**.
| `:config` | `tools.forma.config` | `gradleApi` | `AndroidProjectSettings`, `FormaSettingsStore`, plugin/dep registration maps |
| `:deps` | `tools.forma.deps` | `:core`, `:validation`, `:target`, `:config`, kotlin-dsl | `FormaDependency` model, `applyDependencies`, version-catalog generators |
| `:android` | `tools.forma.android` | `:core` + all of the above + **AGP** + Kotlin GP | Target DSL; `AndroidTargetTypes` + `AndroidRestrictionKit` (F-021); content helpers call core `ContentRule` (F-022) |
| `:jvm` | `tools.forma.jvm` | `:core`, `:deps`, `:validation`, `:target`, `:owners` + Kotlin GP (**no AGP**) | Pure JVM DSL (`api`/`impl`/`library`/`util`/`testUtil` in package `tools.forma.jvm`); `JvmTargetTypes` + `JvmTargetRegistry` (F-030) |

`:android` compiles against **AGP 8.1.2** (aligned with sample runtime force in
`application/settings.gradle.kts` as of F-003). Keep `plugins/android` AGP
Expand Down Expand Up @@ -315,7 +319,7 @@ Aligned with `docs/VISION.md`: core must not assume Android/AGP/Dagger.
| Settings store | `:config` | **Split** | Generic `SettingsStore` / plugin registry → core; `AndroidProjectSettings` → android |
| Owners | `:owners` | **Optional / yes** | Platform-agnostic metadata |
| Feature definitions (AGP library/binary/native) | `:android` | **No** | Stay platform |
| DSL entrypoints (`api`/`impl`/…) | `:android` | **No** (register *onto* core) | First consumer of core — **F-023 done** (`AndroidTargetRegistry`) |
| DSL entrypoints (`api`/`impl`/…) | `:android` + `:jvm` | **No** (register *onto* core) | Android first consumer — **F-023**; pure JVM plugin — **F-030** (`JvmTargetRegistry`) |
| Includer / depgen | separate builds | **No** | Adjacent tooling |
| build-dependencies catalogs | sample support | **No** | Demo-only; pattern informs F-012 |

Expand All @@ -329,6 +333,9 @@ Suggested extraction order (tickets F-020…F-024):
4. Wire Android DSL / registry as first consumer of core — **done (F-023)**
(`TargetRegistry` + `AndroidTargetRegistry`; sample green).
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**.

---

Expand All @@ -354,8 +361,9 @@ Suggested extraction order (tickets F-020…F-024):
|------|------------|
| New user / first project | [`docs/GETTING-STARTED.md`](GETTING-STARTED.md) |
| Configuration-time performance | [`docs/CONFIGURATION-PERFORMANCE.md`](CONFIGURATION-PERFORMANCE.md) |
| New target type | `AndroidTargets.kt` + new DSL file under `plugins/android/src/main/java/` + validator list |
| Tighten dep rules | `validator(...)` in that DSL file; update this doc §2.2 |
| New Android target type | `AndroidTargetTypes` + DSL under `plugins/android/` + `registerAndroidDefaults` |
| New JVM target type | `JvmTargetTypes` + DSL under `plugins/jvm/` + `registerJvmDefaults` ([`JVM-TARGETS.md`](JVM-TARGETS.md)) |
| Tighten dep rules | platform registry allow-lists; update DEPENDENCY-MATRIX / JVM-TARGETS |
| Global SDK/AGP defaults | `androidProjectConfiguration` + sample `application/build.gradle.kts` |
| External deps UX | `plugins/deps` catalog + `build-dependencies` |
| Auto module discovery | `includer/` |
Expand Down
115 changes: 115 additions & 0 deletions docs/JVM-TARGETS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# JVM targets (F-030)

Pure **JVM** platform for Forma, built on **forma-core** (`tools.forma:core`)
without AGP. Parallel to the Android product (`tools.forma.android`), not a
fork of the restriction engine.

Plugin id: **`tools.forma.jvm`** (module `plugins/:jvm`).

## Types

| DSL (package `tools.forma.jvm`) | Type id | Name suffix | Role |
|---------------------------------|---------|-------------|------|
| `api` | `jvm.api` | `api` | Public contracts (Dagger-friendly) |
| `impl` | `jvm.impl` | `impl` | Feature implementation |
| `library` | `jvm.library` | `library` | Shared pure-JVM library |
| `util` | `jvm.util` | `util` | Utilities / extensions |
| `testUtil` | `jvm.test-util` | `test-util` | Shared test helpers |

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.

## 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 |

### Rules of thumb

- **`impl` ↛ `impl`** — implementations stay independent; compose later at a
binary/app root (F-031).
- **`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`**.
- **Content rules** — pure JVM has no Android `res/`; no content rules are
attached for F-030.

Source of truth in code: `plugins/jvm/.../JvmTargetRegistry.kt`
(`registerJvmDefaults`).

## Relation to Android

- Android already exposes pure-Kotlin entrypoints (`api`, `library`, `util`,
`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**
(`JvmTargetRegistry`) and plugin so pure-JVM products do not need AGP.
- Registries are **separate singletons**; shared id strings are intentional.
- Full Android matrix remains [`DEPENDENCY-MATRIX.md`](DEPENDENCY-MATRIX.md).

## Consumer sketch

```kotlin
// settings.gradle.kts
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}
plugins {
id("tools.forma.jvm") version "0.1.3"
}

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

api(
packageName = "com.example.feature.api",
dependencies = deps(
project(":shared-library")
)
)
```

`registerJvmDefaults()` runs on first DSL entry so the matrix is available
without a separate configuration call (a dedicated `jvmProjectConfiguration`
can land later if settings need grow).

Kotlin JVM is applied by the DSL (`org.jetbrains.kotlin.jvm`); default
compatibility is Java **11** (`JvmDefaults`).

## Tests

- `plugins/jvm` unit tests: `JvmTargetRegistryTest` (matrix allow/deny,
self-suffix, validator identity cache).
- Engine coverage remains in `plugins/core` tests.

## Next

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

## See also

- [`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
- [`VISION.md`](VISION.md) — product sequencing
18 changes: 13 additions & 5 deletions docs/PLUGIN-PUBLISH.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ expected ownership model and credential flow so workers do not invent secrets.

| Project | Plugin id | Notes |
|---------|-----------|--------|
| `plugins/:android` | `tools.forma.android` | Main product entrypoint |
| `plugins/:android` | `tools.forma.android` | Main Android product entrypoint |
| `plugins/:jvm` | `tools.forma.jvm` | Pure JVM platform (F-030); no AGP |
| `plugins/:target` | `tools.forma.target` | Target identity |
| `plugins/:validation` | `tools.forma.validation` | Validators |
| `plugins/:owners` | `tools.forma.owners` | Ownership metadata |
Expand Down Expand Up @@ -37,7 +38,8 @@ Android consumers continue to use the unchanged plugin id `tools.forma.android`.
| Artifact | Coordinates | Type | Notes |
|----------|-------------|------|-------|
| forma-core | `tools.forma:core:0.1.3` (+ `-sources`) | Maven library | Single jar. Contains `tools.forma.core.target`, `.restriction`, `.validation`. |
| Android | `tools.forma.android` (Plugin Portal) | Gradle plugin | Primary entrypoint. Depends on core (transitive). |
| Android | `tools.forma.android` (Plugin Portal) | Gradle plugin | Primary Android entrypoint. Depends on core (transitive). |
| JVM | `tools.forma.jvm` (Plugin Portal) | Gradle plugin | Pure JVM targets (F-030). Depends on core (transitive); **no AGP**. See [`JVM-TARGETS.md`](JVM-TARGETS.md). |
| Facades (compat) | `tools.forma.target`, `.validation`, `.deps`, `.config`, `.owners` | Gradle plugins (thin) | Remain published; delegate/re-export core. |

Group `tools.forma`, version `0.1.3` (shared source of truth in root `formaPluginConfiguration`).
Expand All @@ -50,10 +52,16 @@ Group `tools.forma`, version `0.1.3` (shared source of truth in root `formaPlugi
id("tools.forma.android") version "0.1.3"
}
```
- **Pure engine / future JVM or Bazel** (F-030+): depend directly on the library
when writing adapters that do not need the Android DSL:
- **Pure JVM apps** (F-030): use the JVM plugin (no AGP):
```kotlin
plugins {
id("tools.forma.jvm") version "0.1.3"
}
```
DSL lives in package `tools.forma.jvm` — see [`JVM-TARGETS.md`](JVM-TARGETS.md).
- **Pure engine / Bazel adapters**: depend directly on the library when writing
adapters that do not need a platform DSL:
`implementation("tools.forma:core:0.1.3")`.
(A future `tools.forma.jvm` plugin would also pull core.)

### How plugin POMs declare the core dependency

Expand Down
23 changes: 23 additions & 0 deletions docs/PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

Newest entries first.

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

- **Ticket:** F-030 → `done`
- **Branch:** `forma/F-030-jvm-targets` (from `origin/v2`)
- **Skills/modes:** Grok Build `--mode full` (design plan completed; implement hit max-turns with partial tree); Hermes finished tests, docs, verify, commit/PR
- **Platform (`plugins/jvm`):**
- Plugin id **`tools.forma.jvm`** via `formaPublishedPlugin(name = "jvm")` + empty Settings `FormaPlugin`
- **No AGP**; depends on `:core`, `:deps`, `:validation`, `:target`, `:owners` + embedded Kotlin GP
- `JvmTargetTypes`: `jvm.api`, `jvm.impl`, `jvm.library`, `jvm.util`, `jvm.test-util`
- `JvmTargetRegistry` + `registerJvmDefaults()` matrix (impl↛impl; api→api/library; library→util/test-util; util→util/library; test-util→test-util/util/library)
- DSL under package `tools.forma.jvm`: `api` / `impl` / `library` / `util` / `testUtil`
- Minimal `applyKotlinJvm()` (Java 11) without `AndroidProjectSettings`
- **Tests:** `JvmTargetRegistryTest` (matrix, self-suffix, identity cache)
- **Docs:** `docs/JVM-TARGETS.md`; ARCHITECTURE module graph; PLUGIN-PUBLISH table; README Progress; forma-core-api status
- **Verify (OpenJDK 17 + env-mac.sh):**
- `plugins/`: `./gradlew :core:test :jvm:test build` → **BUILD SUCCESSFUL** (78 tasks; `JvmTargetRegistryTest` 5/5)
- Plugin descriptor `tools.forma.jvm` → `tools.forma.jvm.plugin.FormaPlugin`
- `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-031 JVM sample application

## 2026-07-13 — F-024 Publish/coordinate coordinates: `tools.forma:core` vs android plugins

- **Ticket:** F-024 → `done`
Expand Down
6 changes: 4 additions & 2 deletions docs/forma-core-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ plugins under `plugins/` and in [`ARCHITECTURE.md`](ARCHITECTURE.md) §6 +
**Status:** accepted design. **Implemented so far:** F-021 (types +
restriction graph + Android kit), **F-022** (validation SPI + content
predicates in `plugins/core`; `:validation` is a Gradle facade), **F-023**
(`TargetRegistry` + Android DSL consumers via `AndroidTargetRegistry`).
Publishing coordinates remain **F-024**.
(`TargetRegistry` + Android DSL consumers via `AndroidTargetRegistry`),
**F-024** (publish coordinates for `tools.forma:core`), **F-030** (pure JVM
platform plugin `tools.forma.jvm` + `JvmTargetRegistry` — see
[`JVM-TARGETS.md`](JVM-TARGETS.md)).

---

Expand Down
39 changes: 39 additions & 0 deletions plugins/jvm/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@file:Suppress("UnstableApiUsage")

import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask

plugins {
id("org.gradle.kotlin.kotlin-dsl")
id("com.gradle.plugin-publish")
}

formaPublishedPlugin(name = "jvm")

tasks.named("compileKotlin", KotlinCompilationTask::class.java) {
compilerOptions {
// Keep parity options with sibling plugins if useful; none strictly required for pure JVM.
}
}

dependencies {
// Pure JVM platform plugin — NO AGP. Kotlin JVM only.
implementation(embeddedKotlin("gradle-plugin"))

// forma-core + supporting facades (deps/validation own the apply + validator facades)
implementation(project(":core"))
implementation(project(":deps"))
implementation(project(":validation"))
implementation(project(":target"))
implementation(project(":owners"))

testImplementation(kotlin("test"))
}

tasks.test {
useJUnitPlatform()
}

tasks.named<Task>("publishPlugins") {
// If/when wiring a root publish that chains jvm too, list deps here.
// For F-030 slice, standalone :jvm:publishPlugins works for validation.
}
39 changes: 39 additions & 0 deletions plugins/jvm/src/main/java/tools/forma/jvm/api.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package tools.forma.jvm

import org.gradle.api.Project
import tools.forma.deps.core.EmptyDependency
import tools.forma.deps.core.FormaDependency
import tools.forma.deps.core.applyDependencies
import tools.forma.jvm.feature.applyKotlinJvm
import tools.forma.jvm.target.JvmTargetRegistry
import tools.forma.jvm.target.JvmTargetTypes
import tools.forma.jvm.target.registerJvmDefaults
import tools.forma.owners.NoOwner
import tools.forma.owners.Owner
import tools.forma.target.FormaTarget
import tools.forma.validation.asValidator

/**
* Feature **API** surface (pure JVM).
*
* Dagger2-friendly public contracts only.
* May depend on other `api` and pure `jvm.library` modules.
* Must not depend on `impl`, `util` (except through library contracts if modeled that way).
*/
fun Project.api(
packageName: String,
owner: Owner = NoOwner,
dependencies: FormaDependency = EmptyDependency
) {
registerJvmDefaults()

JvmTargetRegistry.selfValidator(JvmTargetTypes.api).asValidator()
.validate(FormaTarget(this))

applyKotlinJvm()

applyDependencies(
validator = JvmTargetRegistry.validatorFor(JvmTargetTypes.api).asValidator(),
dependencies = dependencies
)
}
Loading
Loading