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
74 changes: 45 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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 @@ -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 |
Expand Down
8 changes: 4 additions & 4 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|--------------|----------------------------------------|---------------|
Expand Down Expand Up @@ -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 |
Expand Down
210 changes: 210 additions & 0 deletions docs/DEPENDENCY-MATRIX.md
Original file line number Diff line number Diff line change
@@ -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).
Loading
Loading