From f951a876afde843b2ed1012cc70187503be7ed07 Mon Sep 17 00:00:00 2001 From: Claw Date: Thu, 30 Jul 2026 02:50:59 -0700 Subject: [PATCH] F-115: androidUtilTarget Path B helper + NiA Room dogfood docs Add androidUtilTarget (resourcesTarget twin) so derived util kinds can own plugins. Document NiA spike Room Path B (roomAndroidUtil) green. --- TICKETS.md | 4 +- docs/CALL-SITE-SURFACE.md | 4 +- docs/DOGFOOD-NIA.md | 22 ++++++- docs/PROGRESS.md | 23 +++++++ docs/TARGET-PLUGINS.md | 2 + plugins/android/src/main/java/androidUtil.kt | 39 ++---------- .../src/main/java/androidUtilTarget.kt | 62 +++++++++++++++++++ 7 files changed, 117 insertions(+), 39 deletions(-) create mode 100644 plugins/android/src/main/java/androidUtilTarget.kt diff --git a/TICKETS.md b/TICKETS.md index abc454f7..575860d7 100644 --- a/TICKETS.md +++ b/TICKETS.md @@ -172,11 +172,11 @@ External multimodule OSS validation of meta-build axioms (structure over configu type-owned plugins, attrs-only call sites, closed matrix). **Not** another in-repo sample. Open coding: **F-115** (`in_progress`). **F-094** Portal remains `blocked` (human). -4h workers: pick F-115 phase C remainder (Room / Firebase / more features) unless blocked; do not `[SILENT]` while F-115 is open. +4h workers: pick F-115 phase C remainder (Firebase / more features) unless blocked; do not `[SILENT]` while F-115 is open. | ID | Status | Title | Notes | |----|--------|-------|-------| -| F-115 | in_progress | Dogfood Now in Android under Forma | Primary OSS target [`android/nowinandroid`](https://github.com/android/nowinandroid). Design: [`docs/DOGFOOD-NIA.md`](docs/DOGFOOD-NIA.md). **Phase A+B+C Hilt done:** spike green with Hilt Path A (`hiltImpl`/`hiltApp`/`hiltAndroidUtil`/`hiltBinary`), data/domain, designsystem, topic+interests (no impl→impl). Engine: `applyTargetPlugins` + KSP companion deps (F12). **Next phase C:** Room/Firebase, more features. No `androidLibrary`. | +| F-115 | in_progress | Dogfood Now in Android under Forma | Primary OSS target [`android/nowinandroid`](https://github.com/android/nowinandroid). Design: [`docs/DOGFOOD-NIA.md`](docs/DOGFOOD-NIA.md). **Phase A+B+C Hilt+Room done:** spike green with Hilt Path A + Room Path B (`roomAndroidUtil` / `androidUtilTarget`), data OfflineFirst+DAO, topic+interests (no impl→impl). Engine: KSP companion (F12) + `androidUtilTarget` (F15). **Next phase C:** Firebase binary, more features. No `androidLibrary`. | ## Backlog (lower priority / historical GitHub) diff --git a/docs/CALL-SITE-SURFACE.md b/docs/CALL-SITE-SURFACE.md index 6872fc4b..0c90d86a 100644 --- a/docs/CALL-SITE-SURFACE.md +++ b/docs/CALL-SITE-SURFACE.md @@ -45,7 +45,9 @@ All return **`Unit`**. | `androidNative` | `native` | `buildSystem`, `abi`, … | NDK leaf; consumers `androidUtil`/`app`/`binary` | | Path B derived (e.g. `navigationRes`) | often shared suffix | same as base + type-owned plugins | Consumer-defined; see TARGET-PLUGINS | -Internal helper `resourcesTarget(type, …)` backs `androidRes` and derived res DSLs — +Internal helpers: `resourcesTarget(type, …)` backs `androidRes` and derived res DSLs; +`androidUtilTarget(type, …)` backs `androidUtil` and derived util DSLs (e.g. dogfood +`roomAndroidUtil`) — not a public “generic library” escape hatch. ## JVM target DSLs (`tools.forma.jvm`) diff --git a/docs/DOGFOOD-NIA.md b/docs/DOGFOOD-NIA.md index 5072f0d4..fdc460c9 100644 --- a/docs/DOGFOOD-NIA.md +++ b/docs/DOGFOOD-NIA.md @@ -1,11 +1,11 @@ # Dogfood: Now in Android → Forma (F-115) -**Status:** `in_progress` — phase C Hilt Path A **green** (Room/more features next) +**Status:** `in_progress` — phase C Hilt Path A + **Room Path B green** (Firebase / more features next) **Upstream:** [android/nowinandroid](https://github.com/android/nowinandroid) (Apache-2.0) **Pinned checkout (local):** `/Users/claw/work/nowinandroid` @ `7d45eae` (main tip when cloned 2026-07-29) **Dogfood fork:** `/Users/claw/work/nowinandroid-forma` **Spike tree:** `/Users/claw/work/nowinandroid-forma/forma-spike` -**Forma plugins:** `mavenLocal` version **`0.1.3-NIA`** (republish after F-115 engine fix) +**Forma plugins:** `mavenLocal` version **`0.1.3-NIA`** (republish after `androidUtilTarget` + prior KSP fix) **Goal:** Validate Forma’s meta-build approach on a **real multi-module OSS** graph — not another in-repo sample. ## Product bar (what “success” means) @@ -209,7 +209,21 @@ Workspace: same **`forma-spike`** external tree. | F11+ | `compose = false` on data/domain **and** binary Application host (Compose compiler without runtime ICE) | | Verify | `forma-spike` `./gradlew :binary:assembleDebug` → **BUILD SUCCESSFUL** (242 tasks); Hilt tasks `hiltAggregateDepsDebug` / `hiltJavaCompileDebug` green | -**Still open in phase C:** Room derived type, Firebase binary, foryou/bookmarks/search/settings, full designsystem port. +**Still open in phase C (pre-Room):** Firebase binary, foryou/bookmarks/search/settings, full designsystem port. + +### Phase C — Room Path B ✅ (2026-07-30) + +| Step | Result | +|------|--------| +| Engine | `androidUtilTarget(type, …)` — Path B twin of `resourcesTarget`; `androidUtil` delegates | +| `forma-defs` | `RoomAndroidUtilType` = `deriveTargetType(base=androidUtil, suffix=android-util)` + thin `roomAndroidUtil` | +| Plugins on type | `androidx.room` (runtime/ktx + `room-compiler`.ksp) **and** Hilt re-bound (F14: derived types do not inherit base Path A plugins) | +| Module | `core-database-android-util` — slim `NiaDatabase` / `TopicEntity` / `TopicDao` / Hilt `DatabaseModule`; `exportSchema=true` → `schemas/…/1.json` | +| Data | `OfflineFirstTopicsRepository` seeds DAO when empty; UserData still in-memory | +| Classpath | `extraPlugins` += Room Gradle plugin (`libs.plugins.room`) | +| Verify | `forma-spike` `./gradlew :binary:assembleDebug` → **BUILD SUCCESSFUL** (266 tasks); KSP `TopicDao_Impl` / `NiaDatabase_Impl`; APK ~10 MB | + +**Still open in phase C:** Firebase binary, remaining features, full designsystem, DataStore UserData. ### Phase D — Case study write-up @@ -231,6 +245,8 @@ Workspace: same **`forma-spike`** external tree. | F11 | 2026-07-29 | project-global `compose=true` + `androidUtil` | Compose compiler runs on data/domain without Compose runtime → ICE. **Fix:** `androidUtil(..., compose = false)` on non-UI cores; same on binary when only Application sources | | F12 | 2026-07-29 | Path A companion `.ksp` deps | `applyTargetPlugins` called `applyDependencies` **without** `configurationFeatures` → no `ksp` configuration. **Engine fix:** optional `configurationFeatures` param forwarded from DSLs that already know processors (`impl`/`app`/`androidUtil`/`binary`/…) | | F13 | 2026-07-29 | Hilt Application placement | `@HiltAndroidApp` must live on `com.android.application` (`androidBinary` / `hiltBinary`), not library-shell `androidApp` | +| F14 | 2026-07-30 | Path B does not inherit base type plugins | `deriveTargetType` clones matrix/content rules only; plugin registry is per-type. Room util must **re-register Hilt** (or stack plugins on the derived type) — not rely on Path A `androidUtil` Hilt alone | +| F15 | 2026-07-30 | No public `androidUtil(type=)` before helper | Engine needed `androidUtilTarget` (like `resourcesTarget`) so Path B Room DSL can pass derived type + processor features | ## Local reference commands diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 90c6b376..24ed93c3 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -2,6 +2,29 @@ Newest entries first. +## 2026-07-30 — F-115: NiA dogfood Room Path B + androidUtilTarget + +- **Ticket:** F-115 still `in_progress` (Firebase / more features next) +- **Skills/modes:** Hermes direct dogfood + mechanical Path B engine helper (F-050/F-115 class) +- **Engine (`plugins/android`):** + - New `androidUtilTarget(type, …)` — shared AGP/util wiring + `applyTargetPlugins` with processor features + - `androidUtil` delegates to it with `AndroidTargetTypes.androidUtil` + - Republished mavenLocal **`0.1.3-NIA`** +- **External tree:** `/Users/claw/work/nowinandroid-forma/forma-spike` + - `forma-defs/RoomAndroidUtil.kt` — Path B `deriveTargetType` + `roomAndroidUtil` thin DSL + - `core-database-android-util` — Room DB/DAO/entity + Hilt provides; schema `1.json` exported + - data: `OfflineFirstTopicsRepository` seeds DAO; still no impl→impl + - Findings **F14** (derived types need Hilt re-bind), **F15** (`androidUtilTarget` helper) +- **Verify (real host):** + - `scripts/publish-local.sh 0.1.3-NIA` → **ok** + - `forma-spike` `./gradlew :binary:assembleDebug` → **BUILD SUCCESSFUL** (266 tasks) + - KSP generated `TopicDao_Impl` / `NiaDatabase_Impl`; Room schema JSON present + - APK `binary-debug.apk` ~10.2 MB +- **Docs:** `docs/DOGFOOD-NIA.md` phase C Room; TICKETS F-115 notes; TARGET-PLUGINS pointer; spike README +- **Commits/PRs:** this branch → PR base `v2` +- **Blockers:** none product; F-094 Portal still human-blocked +- **Next:** Firebase Path A on binary and/or more NiA features + ## 2026-07-29 — F-115: NiA dogfood Hilt Path A + applyTargetPlugins KSP fix - **Ticket:** F-115 still `in_progress` (Room / more features next) diff --git a/docs/TARGET-PLUGINS.md b/docs/TARGET-PLUGINS.md index 40891f96..c2f011d8 100644 --- a/docs/TARGET-PLUGINS.md +++ b/docs/TARGET-PLUGINS.md @@ -32,6 +32,8 @@ engine fix when dogfooding from `mavenLocal`. Hands-on: [`examples/android/10-target-plugins`](../examples/android/10-target-plugins) (safe-args) · [`examples/android/11-metro-di`](../examples/android/11-metro-di) (Metro DI). +**Dogfood Path B util:** NiA spike `roomAndroidUtil` via engine helper `androidUtilTarget` +(same role as `resourcesTarget` for res) — see [`DOGFOOD-NIA.md`](DOGFOOD-NIA.md). Agent skill: [`forma-target-plugins`](../examples/agent-skills/forma-target-plugins.md). Global configuration lives at root `buildscript { androidProjectConfiguration(...) }` (F-082). diff --git a/plugins/android/src/main/java/androidUtil.kt b/plugins/android/src/main/java/androidUtil.kt index 89889b45..2b414be1 100644 --- a/plugins/android/src/main/java/androidUtil.kt +++ b/plugins/android/src/main/java/androidUtil.kt @@ -1,22 +1,10 @@ -import tools.forma.android.feature.AndroidLibraryFeatureConfiguration -import tools.forma.android.feature.androidLibraryFeatureDefinition -import tools.forma.android.feature.applyFeatures -import tools.forma.android.feature.kotlinAndroidFeatureDefinition -import tools.forma.android.target.AndroidTargetRegistry import tools.forma.android.target.AndroidTargetTypes import tools.forma.owners.NoOwner import tools.forma.owners.Owner -import tools.forma.android.validation.disallowResources import tools.forma.android.visibility.Public import tools.forma.android.visibility.Visibility import org.gradle.api.Project -import tools.forma.android.feature.processorConfigurationFeatures -import tools.forma.deps.core.applyDependencies -import tools.forma.deps.core.applyTargetPlugins import tools.forma.deps.core.FormaDependency -import tools.forma.deps.fleet.registerFormaLayout -import tools.forma.validation.asValidator -import tools.forma.validation.validate /** * TODO @@ -33,6 +21,7 @@ import tools.forma.validation.validate * ) * ``` * + * Path B derived util kinds (Room, etc.) use [androidUtilTarget] with a derived type. */ fun Project.androidUtil( packageName: String, @@ -43,29 +32,13 @@ fun Project.androidUtil( /** Enable Jetpack Compose; defaults to project-wide `compose` setting. */ compose: Boolean = Forma.settings.compose, ) { - - disallowResources() - - //TODO unify with util, use androidJar dependency - AndroidTargetRegistry.selfValidator(AndroidTargetTypes.androidUtil).asValidator().validate(target) - registerFormaLayout(packageName) - - val androidFeatureConfig = AndroidLibraryFeatureConfiguration( + androidUtilTarget( + type = AndroidTargetTypes.androidUtil, packageName = packageName, - compose = compose, - ) - - applyFeatures( - androidLibraryFeatureDefinition(androidFeatureConfig), - kotlinAndroidFeatureDefinition() - ) - val processors = processorConfigurationFeatures() - applyTargetPlugins(AndroidTargetTypes.androidUtil, configurationFeatures = processors) - - applyDependencies( - validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.androidUtil).asValidator(), + owner = owner, + visibility = visibility, dependencies = dependencies, testDependencies = testDependencies, - configurationFeatures = processors + compose = compose, ) } diff --git a/plugins/android/src/main/java/androidUtilTarget.kt b/plugins/android/src/main/java/androidUtilTarget.kt new file mode 100644 index 00000000..4d35c3b5 --- /dev/null +++ b/plugins/android/src/main/java/androidUtilTarget.kt @@ -0,0 +1,62 @@ +import tools.forma.android.feature.AndroidLibraryFeatureConfiguration +import tools.forma.android.feature.androidLibraryFeatureDefinition +import tools.forma.android.feature.applyFeatures +import tools.forma.android.feature.kotlinAndroidFeatureDefinition +import tools.forma.android.feature.processorConfigurationFeatures +import tools.forma.android.target.AndroidTargetRegistry +import tools.forma.android.validation.disallowResources +import tools.forma.android.visibility.Public +import tools.forma.android.visibility.Visibility +import org.gradle.api.Project +import tools.forma.core.target.TargetType +import tools.forma.deps.core.applyDependencies +import tools.forma.deps.core.applyTargetPlugins +import tools.forma.deps.core.FormaDependency +import tools.forma.deps.fleet.registerFormaLayout +import tools.forma.owners.NoOwner +import tools.forma.owners.Owner +import tools.forma.validation.asValidator +import tools.forma.validation.validate + +/** + * Non-UI Android library target bound to an arbitrary [TargetType]. + * + * Used by [androidUtil] (pre-defined `android-util`) and by Path B derived DSLs + * (e.g. dogfood `roomAndroidUtil`) so plugin identity stays on the type while the + * AGP/util wiring is shared. Call sites of derived DSLs remain attributes-only — + * never pass plugin ids here. + */ +fun Project.androidUtilTarget( + type: TargetType, + packageName: String, + owner: Owner = NoOwner, + visibility: Visibility = Public, + dependencies: FormaDependency = emptyDependency(), + testDependencies: FormaDependency = emptyDependency(), + /** Enable Jetpack Compose; defaults to project-wide `compose` setting. */ + compose: Boolean = Forma.settings.compose, +) { + disallowResources() + + AndroidTargetRegistry.selfValidator(type).asValidator().validate(target) + registerFormaLayout(packageName) + + val androidFeatureConfig = AndroidLibraryFeatureConfiguration( + packageName = packageName, + compose = compose, + ) + + applyFeatures( + androidLibraryFeatureDefinition(androidFeatureConfig), + kotlinAndroidFeatureDefinition() + ) + val processors = processorConfigurationFeatures() + applyTargetPlugins(type, configurationFeatures = processors) + + applyDependencies( + validator = AndroidTargetRegistry.validatorFor(type).asValidator(), + dependencies = dependencies, + testDependencies = testDependencies, + configurationFeatures = processors + ) +}