diff --git a/README.md b/README.md index 0f5e649..4a99a31 100644 --- a/README.md +++ b/README.md @@ -103,38 +103,54 @@ Worker / contributor host setup (JDK 17+, Android SDK platform 33): see [`docs/E ## Progress -| Supported target types | implemented | purpose | validation | -|:----------------------:|:-----------:|:-----------------------------:|:----------:| -| androidBinary | ✅ | Generate single APK | ✅ | -| androidApp | ✅ | Application class | partial | -| androidLibrary | ✅ | Android library | partial | -| androidWidget | ✅ | Custom View | partial | -| androidRes | ✅ | Resources Only | ✅ | -| androidTestUtils | ✅ | Shared code for Android tests | ✅ | -| androidUtils | ✅ | Library extensions | partial | -| testUtils | ✅ | Shared code for unit tests | ✅ | -| utils | ✅ | JVM Library extensions | partial | -| library | ✅ | JVM Library | partial | -| api | ✅ | Feature external API's | partial | -| impl | ✅ | Feature implementation | partial | +DSL names match code entrypoints (`widget`, `util`, …). **Validation** = +project-dep type checks + content layout rules from live validators +([full matrix](docs/DEPENDENCY-MATRIX.md)). + +| Supported target types | implemented | purpose | validation | +|:----------------------:|:-----------:|:-------:|:----------:| +| `androidBinary` | ✅ | Generate single APK | name + no `res/`; project deps **unrestricted** (`EmptyValidator`) | +| `androidApp` | ✅ | Application / root Android library | name + no `res/`; project deps **unrestricted** | +| `androidLibrary` | ✅ | Android library | name only; project deps **unrestricted** | +| `uiLibrary` | ✅ | Shared UI library for impl/widget | project-dep list | +| `widget` | ✅ | Custom View / UI component | project-dep list | +| `androidRes` | ✅ | Resources only | only `res/` under `src/main` + project-dep list | +| `viewBinding` | ✅ | Layout-only view binding module | only `layout*` under `res` + project-dep list | +| `androidTestUtil` | ✅ | Shared code for Android tests | project-dep list | +| `androidUtil` | ✅ | Android library extensions | no `res/` + project-dep list | +| `testUtil` | ✅ | Shared code for unit tests | no `res/` + project-dep list | +| `util` | ✅ | JVM library extensions | no `res/` + project-dep list | +| `library` | ✅ | JVM library | project-dep list (shares `library` suffix with `androidLibrary`) | +| `api` | ✅ | Feature external APIs | no `res/` + project-dep list | +| `impl` | ✅ | Feature implementation | project-dep list (**no** other `impl`) | +| `androidNative` | ✅ | NDK / native | no `res/`; no project-dep validation yet | ## Dependency matrix -Each column represents the list of allowed/disallowed dependencies - -| | androidApp | androidBinary | androidLibrary | androidWidget | androidRes | androidTestUtils | api | impl | androidUtils | utils | testUtils | -|------------------|-------------|---------------|----------------|---------------|------------|------------------|-----|------|-------------|------|----------| -| androidApp | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| androidBinary | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| androidLibrary | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | -| androidWidget | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | -| androidRes | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | -| androidTestUtils | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | -| api | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | -| impl | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | -| androidUtils | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | -| utils | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | -| testUtils | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | +**Canonical reference (code truth):** [`docs/DEPENDENCY-MATRIX.md`](docs/DEPENDENCY-MATRIX.md). + +That document is generated from each target’s +`applyDependencies(validator = …)` call and content helpers. Summary: + +| Consumer | May depend on project suffixes… | +|----------|----------------------------------| +| `api` | `api`, `library` | +| `impl` | `api`, `android-util`, `test-util`, `util`, `library`, `ui-library`, `res`, `viewbinding`, `widget` | +| `library` (JVM) | `util`, `test-util` | +| `androidLibrary` | *any* (`EmptyValidator`) | +| `uiLibrary` | `widget`, `util`, `android-util`, `res` | +| `util` | `util`, `library` | +| `androidUtil` | `android-util`, `test-util`, `res` | +| `testUtil` | `test-util`, `util` | +| `androidTestUtil` | `android-test-util`, `test-util` | +| `androidRes` | `res`, `widget` | +| `widget` | `ui-library`, `widget`, `util`, `android-util`, `res` | +| `viewBinding` | `api`, `widget`, `res`, `library`, `android-util` | +| `androidApp` / `androidBinary` | *any* (`EmptyValidator`) | +| `androidNative` | *(no project-dep check)* | + +Tightening unrestricted entry targets and Dagger-friendly `api`/`impl` +boundaries is tracked as **F-011**. Icons made by Freepik from www.flaticon.com diff --git a/TICKETS.md b/TICKETS.md index adc8ceb..9e7fe77 100644 --- a/TICKETS.md +++ b/TICKETS.md @@ -17,7 +17,7 @@ Update this file when picking or finishing work. Cron workers must pick the **hi | ID | Status | Title | Notes | |----|--------|-------|-------| -| F-010 | todo | Document strict dependency matrix from live code (not only README) | Source of truth from validators/targets | +| F-010 | done | Document strict dependency matrix from live code (not only README) | `docs/DEPENDENCY-MATRIX.md` + README summary aligned to validators | | F-011 | todo | Tighten validation for `api` / `impl` (Dagger2-friendly boundaries) | Align with current types; related GH #56, #18 | | F-012 | todo | External deps catalog UX + tooling polish | `plugins/deps` catalog generators | | F-013 | todo | Compose support for Android library/ui targets | GH #96 | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 149d1be..5d6fcdf 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -130,9 +130,9 @@ Name rule (`validation`): project name equals `suffix` or ends with `-$suffix`. ### 2.2 Allowed project dependencies (from live validators) -This is **code truth** (each target’s `applyDependencies(validator = …)`). -README matrix (F-010) should be reconciled against this table — they diverge -(e.g. several Android entry targets use `EmptyValidator`). +**Canonical tables + full matrix:** [`docs/DEPENDENCY-MATRIX.md`](DEPENDENCY-MATRIX.md) +(F-010). That file is the user-facing code truth; keep this section in sync +when validators change. | Consumer DSL | Allowed *project* dependency suffixes | Content rules | |--------------|----------------------------------------|---------------| @@ -312,7 +312,7 @@ Suggested extraction order (tickets F-020…F-024): | Item | Ticket | |------|--------| -| README dependency matrix ≠ live validators | F-010 | +| ~~README dependency matrix ≠ live validators~~ → [`docs/DEPENDENCY-MATRIX.md`](DEPENDENCY-MATRIX.md) | F-010 done | | `EmptyValidator` on app/binary/androidLibrary | F-011 | | ~~AGP 7.4.2 compile vs 8.1.2 runtime~~ (aligned 8.1.2) | F-003 done | | ~~CI missing SDK + Java on some jobs~~ (GHA green on PR #153) | F-004 done | diff --git a/docs/DEPENDENCY-MATRIX.md b/docs/DEPENDENCY-MATRIX.md new file mode 100644 index 0000000..26aac42 --- /dev/null +++ b/docs/DEPENDENCY-MATRIX.md @@ -0,0 +1,210 @@ +# Forma dependency matrix (code truth) + +Source of truth for **F-010**. Every rule below is taken from the live +`applyDependencies(validator = …)` / content validators in +`plugins/android/src/main/java/*.kt` (and +`tools/forma/android/validation/commonValidators.kt`). + +When validators change, update **this file first**, then the README summary +matrix. Do not invent rules from aspirational docs. + +Last verified: **2026-07-11** against `v2` tip (post F-003 / F-004). + +--- + +## How validation works + +1. **Name / type of the consumer** — each DSL entry calls + `target.validate(SomeTargetTemplate)`. The project name must equal the + template `suffix` or end with `-$suffix` (see + `plugins/validation/.../Validator.kt`). +2. **Allowed project dependencies** — `applyDependencies` runs a `Validator` + on every **project** dependency. `validator(A, B, …)` accepts only + dependency projects whose names match those templates’ suffixes. + `EmptyValidator` accepts **any** project dependency (no type check). +3. **Content layout** — some targets call `disallowResources()`, + `onlyAllowResources()`, or `onlyAllowLayouts()` before configuration. +4. **External / catalog deps** — named library deps are not filtered by the + project-suffix validators; only `project(...)` edges are. + +Validation is **suffix-based**, not Gradle configuration-based. A project +named `feature-home-api` is treated as type `api` because of the `-api` +suffix (`ApiTargetTemplate("api")`). + +--- + +## Target templates (suffixes) + +From `plugins/android/.../AndroidTargets.kt`: + +| DSL entrypoint | Template object | Name suffix | +|----------------|-----------------|-------------| +| `androidBinary` | `BinaryTargetTemplate` | `binary` | +| `androidApp` | `ApplicationTargetTemplate` | `app` | +| `androidLibrary` | `LibraryTargetTemplate` | `library` | +| `library` (JVM) | `LibraryTargetTemplate` | `library` | +| `uiLibrary` | `UiLibraryTargetTemplate` | `ui-library` | +| `androidNative` | `NativeTarget` | `native` | +| `util` | `UtilTargetTemplate` | `util` | +| `testUtil` | `TestUtilTargetTemplate` | `test-util` | +| `androidTestUtil` | `AndroidTestUtilTargetTemplate` | `android-test-util` | +| `androidUtil` | `AndroidUtilTargetTemplate` | `android-util` | +| `viewBinding` | `ViewBindingTargetTemplate` | `viewbinding` | +| `androidRes` | `ResourcesTargetTemplate` | `res` | +| `api` | `ApiTargetTemplate` | `api` | +| `impl` | `ImplTargetTemplate` | `impl` | +| `widget` | `WidgetTargetTemplate` | `widget` | + +**Collision note:** JVM `library` and Android `androidLibrary` share the same +suffix `library` / `LibraryTargetTemplate`. The DSL functions differ (Kotlin +JVM features vs AGP library features), but project-name validation cannot +tell them apart. Follow-up: F-020 (forma-core registry design). + +README historically said `androidWidget` / `androidUtils` / `testUtils` / +`utils` / `androidTestUtils` — the **code** entrypoints are `widget`, +`androidUtil`, `testUtil`, `util`, `androidTestUtil`. + +--- + +## Allowed project dependencies (consumer → allowed dependency suffixes) + +Read as: **consumer DSL** may depend on project targets whose names match +these suffixes (or are unrestricted if `EmptyValidator`). + +| Consumer DSL | Source file | Project-dep validator | Allowed dependency suffixes | +|--------------|-------------|------------------------|-----------------------------| +| `api` | `api.kt` | `validator(api, library)` | `api`, `library` | +| `impl` | `impl.kt` | `validator(api, android-util, test-util, util, library, ui-library, res, viewbinding, widget)` | `api`, `android-util`, `test-util`, `util`, `library`, `ui-library`, `res`, `viewbinding`, `widget` | +| `library` (JVM) | `library.kt` | `validator(util, test-util)` | `util`, `test-util` | +| `androidLibrary` | `androidLibrary.kt` | **`EmptyValidator`** | *any project* | +| `uiLibrary` | `uiLibrary.kt` | `validator(widget, util, android-util, res)` | `widget`, `util`, `android-util`, `res` | +| `util` | `util.kt` | `validator(util, library)` | `util`, `library` | +| `androidUtil` | `androidUtil.kt` | `validator(android-util, test-util, res)` | `android-util`, `test-util`, `res` | +| `testUtil` | `testUtil.kt` | `validator(test-util, util)` | `test-util`, `util` | +| `androidTestUtil` | `androidTestUtil.kt` | `validator(android-test-util, test-util)` | `android-test-util`, `test-util` | +| `androidRes` | `androidRes.kt` | `validator(res, widget)` | `res`, `widget` | +| `widget` | `widget.kt` | `validator(ui-library, widget, util, android-util, res)` | `ui-library`, `widget`, `util`, `android-util`, `res` | +| `viewBinding` | `viewBinding.kt` | `validator(api, widget, res, library, android-util)` | `api`, `widget`, `res`, `library`, `android-util` | +| `androidApp` | `androidApp.kt` | **`EmptyValidator`** | *any project* | +| `androidBinary` | `androidBinary.kt` | **`EmptyValidator`** | *any project* | +| `androidNative` | `androidNative.kt` | *(no `applyDependencies`)* | *no project-dep validation* | + +### Explicit non-edges (important product rules) + +These follow from the table (not from separate deny-lists): + +- **`impl` cannot depend on `impl`** — only listed suffixes; good for + Dagger-style feature boundaries (tighten further in F-011). +- **`api` cannot depend on `impl` / widgets / res / viewbinding** — JVM API + surface only (`api` + `library`). +- **`library` (JVM) cannot depend on `api` / `impl`** — comment in code: + “Can't depend on api\impl”. +- **`util` cannot depend on `api` / `impl`** — same intent as library. +- **Circular-ish UI graph is intentional while experimental:** `uiLibrary` + may depend on `widget`, and `widget` may depend on `ui-library` + (`uiLibrary.kt` comment). + +--- + +## Content rules (src layout) + +| Consumer DSL | Helper | Rule | +|--------------|--------|------| +| `api` | `disallowResources()` | no `res/` under `src/main` | +| `util` | `disallowResources()` | no `res/` under `src/main` | +| `androidUtil` | `disallowResources()` | no `res/` under `src/main` | +| `testUtil` | `disallowResources()` | no `res/` under `src/main` | +| `androidApp` | `disallowResources()` | no `res/` under `src/main` | +| `androidBinary` | `disallowResources()` | no `res/` under `src/main` | +| `androidNative` | `disallowResources()` | no `res/` under `src/main` | +| `androidRes` | `onlyAllowResources()` | **only** `res/` directory under `src/main` | +| `viewBinding` | `onlyAllowLayouts()` | under `src/main/res`, only `layout*` folders | +| others | — | no extra content validator in current code | + +Helpers live in +`plugins/android/src/main/java/tools/forma/android/validation/commonValidators.kt`. + +--- + +## Full matrix (consumer rows × dependency columns) + +Legend: + +- **Y** — allowed by non-empty `validator(...)` list +- **\*** — `EmptyValidator` / no project-dep check (anything goes) +- **—** — not allowed by the live validator list +- **n/a** — `androidNative` has no `applyDependencies` + +Rows = **consumer**. Columns = **dependency type (suffix)**. + +| Consumer ↓ \ Dep → | api | impl | library | ui-library | util | test-util | android-util | android-test-util | res | viewbinding | widget | app | binary | native | +|--------------------|-----|------|---------|------------|------|-----------|--------------|-------------------|-----|-------------|--------|-----|--------|--------| +| **api** | Y | — | Y | — | — | — | — | — | — | — | — | — | — | — | +| **impl** | Y | — | Y | Y | Y | Y | Y | — | Y | Y | Y | — | — | — | +| **library** (JVM) | — | — | — | — | Y | Y | — | — | — | — | — | — | — | — | +| **androidLibrary** | * | * | * | * | * | * | * | * | * | * | * | * | * | * | +| **uiLibrary** | — | — | — | — | Y | — | Y | — | Y | — | Y | — | — | — | +| **util** | — | — | Y | — | Y | — | — | — | — | — | — | — | — | — | +| **androidUtil** | — | — | — | — | — | Y | Y | — | Y | — | — | — | — | — | +| **testUtil** | — | — | — | — | Y | Y | — | — | — | — | — | — | — | — | +| **androidTestUtil** | — | — | — | — | — | Y | — | Y | — | — | — | — | — | — | +| **androidRes** | — | — | — | — | — | — | — | — | Y | — | Y | — | — | — | +| **widget** | — | — | — | Y | Y | — | Y | — | Y | — | Y | — | — | — | +| **viewBinding** | Y | — | Y | — | — | — | Y | — | Y | — | Y | — | — | — | +| **androidApp** | * | * | * | * | * | * | * | * | * | * | * | * | * | * | +| **androidBinary** | * | * | * | * | * | * | * | * | * | * | * | * | * | * | +| **androidNative** | n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a | + +Self-deps: a type may depend on itself only if its own suffix is in its +allowed list (e.g. `api`→`api` **Y**, `impl`→`impl` **—**, `widget`→`widget` +**Y**). + +--- + +## README matrix vs code (divergences) + +The historical README table used columns as *consumers* and rows as +*dependencies*, mixed aspirational edges, and omitted several types +(`uiLibrary`, `viewBinding`, `library` JVM vs `androidLibrary`, `native`). + +Notable mismatches fixed by treating **this document** as truth: + +| Topic | Old README implication | Live code | +|-------|------------------------|-----------| +| `androidLibrary` project deps | selective ✅/❌ grid | **`EmptyValidator`** (unrestricted) | +| `androidApp` / `androidBinary` project deps | selective grid | **`EmptyValidator`** | +| `impl` → `impl` | ❌ (agrees) | not in allowed list | +| `api` → `library` | not clearly shown | **allowed** | +| `impl` → `ui-library` / `viewbinding` / `widget` / `res` | partially missing | **allowed** | +| `widget` → `ui-library` | not shown | **allowed** | +| `viewBinding` | missing from matrix | full row above | +| `uiLibrary` | missing / confused with library | full row above | +| DSL names | `androidWidget`, `utils`, … | `widget`, `util`, … | + +Tightening `EmptyValidator` targets and Dagger-friendly `api`/`impl` edges is +**F-011**, not documentation fiction. + +--- + +## Sample app check (sanity) + +`application/` multi-feature layout uses the pattern: + +- `feature/*/api` → JVM `api` +- `feature/*/impl` → Android `impl` + viewbinding/res/widget/core libraries +- `binary` → `androidBinary` with explicit deps on root-app + feature api/impl + (legal today because binary uses `EmptyValidator`) + +See `docs/ARCHITECTURE.md` §2.2 and §3 for the module map. + +--- + +## Maintenance checklist + +1. Change a `validator(...)` list or content helper in `plugins/android`. +2. Update the tables in **this file**. +3. Refresh the compact README matrix (link here as canonical). +4. Note the change in `docs/PROGRESS.md` / ticket notes when user-facing. + +Related tickets: **F-011** (tighten validation), **F-020** (forma-core type +registry / shared `library` suffix). diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index b8e617e..9d5b7a4 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -2,6 +2,28 @@ Newest entries first. +## 2026-07-11 — F-010 Dependency matrix from live validators + +- **Ticket:** F-010 → `done` +- **Branch:** `forma/F-010-dependency-matrix` (from `origin/v2` after F-003/F-004 merge) +- **Actions:** + - Added `docs/DEPENDENCY-MATRIX.md` as canonical code-truth matrix: + - How name / project-dep / content validation works + - Per-DSL allowed suffixes from each `applyDependencies(validator=…)` + - Content rules (`disallowResources` / `onlyAllowResources` / `onlyAllowLayouts`) + - Full consumer×dependency table (Y / * / — / n/a) + - README vs code divergence notes + - Replaced aspirational README ✅/❌ grid with code-aligned summary + link + - Expanded Progress target table (`uiLibrary`, `viewBinding`, `androidNative`, real DSL names) + - Pointed `docs/ARCHITECTURE.md` §2.2 + §7 at the new doc; marked F-010 done +- **Also this run (P0 land on `v2`):** + - Squash-merge path unavailable (repo allows squash only); merged #152 then #153 into `v2` locally and pushed (`123ec44`) + - PR #152 F-003 and #153 F-004 both **MERGED** into `v2` +- **Build:** docs-only slice; no Gradle re-run required for matrix extraction (rules read from Kotlin sources) +- **Commits/PRs:** this branch → PR base `v2` +- **Blockers:** none +- **Next step:** F-011 tighten `api`/`impl` (+ optional replace `EmptyValidator` on app/binary/androidLibrary) + ## 2026-07-11 — F-004 CI green (workflow + badge) - **Ticket:** F-004 → `done`