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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,11 @@ Worker / contributor host setup (JDK **21** build JVM, Android SDK platform **34

## External dependency catalogs

Declare third-party libraries and plugins once in `settings.gradle.kts` with
`projectDependencies` (version catalog), or use typed Kotlin catalogs
(`build-dependencies/` pattern). Full guide: [`docs/DEPS-CATALOG.md`](docs/DEPS-CATALOG.md).
**House style:** declare third-party libraries and plugins once in
`settings.gradle.kts` with `projectDependencies` (version catalog) and consume
`libs.*` via `deps(...)`. Typed Kotlin catalogs (`build-dependencies/` pattern)
are an **advanced** sample-scale option, not a second default. Full guide:
[`docs/DEPS-CATALOG.md`](docs/DEPS-CATALOG.md).

```gradle
// settings.gradle.kts
Expand Down
6 changes: 3 additions & 3 deletions TICKETS.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,15 @@ Close the biggest call-site / multi-way gap: chain `withPlugin`. Design:
## P8 — Principle alignment (implementation matches goals)

Close remaining gaps where code/docs still allow **multiple ways**, **fat call
sites**, or **missing fleet tooling**. P7 (F-070–F-073) and F-081–F-082 are **done**; **F-019 Phase 1 done**.
**Next coding:** **F-083**
sites**, or **missing fleet tooling**. P7 (F-070–F-073) and F-081–F-083 are **done**; **F-019 Phase 1 done**.
**Next coding:** **F-084**

| ID | Status | Title | Notes |
|----|--------|-------|-------|
| F-080 | done | Codify root principles in product docs | `VISION.md` § Root principles + README + `AGENTS.md` + GETTING-STARTED; board P8 added |
| F-081 | done | Call-site surface audit: `Unit` returns, no builder chains, minimal attrs | Hard-removed `TargetBuilder` / `PluginWrapper` / sample `Plugins` + chain-only `PluginConfiguration`; all Android/JVM DSLs already `Unit`; `docs/CALL-SITE-SURFACE.md` flag inventory (`compose` → project-global default, `viewBinding` on impl + dedicated type) |
| F-082 | done | One global configuration path | Hard-removed `Project.androidProjectConfiguration`; `ScriptHandlerScope` form is the single API; `extraPlugins` documented classpath-only; new `PROJECT-CONFIGURATION.md` + cross-links; builds green |
| F-083 | todo | One project-global external-deps convention | House style: version catalog **or** typed catalogs as the sample+docs standard; other path = advanced/legacy note, not dual happy path (`DEPS-CATALOG.md`) |
| F-083 | done | One project-global external-deps convention | **House style = `projectDependencies` → `libs.*`**. Typed `build-dependencies/` catalogs = advanced/sample-scale only (not dual happy path). `DEPS-CATALOG.md` + README/GETTING-STARTED/agent skill/08 README |
| F-084 | todo | Fleet tooling for explicit graphs (check / generate / migrate) | Principle 3 at scale: tighten includer/depgen/bazel-check; design+spike migrate helpers for renames/type changes; related GH **#54**. Reliable large refactors without hand-editing thousands of modules |
| F-085 | todo | Full-tree principle audit: sample + examples + agent skills | After F-072/F-081: no chain APIs, no dual styles taught, call sites minimal; fix stragglers |

Expand Down
3 changes: 2 additions & 1 deletion application/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ val roomVersion = "2.7.2"

val ksp = CustomConfiguration("ksp")

// Version catalog UX: bare GAV, library(name=…), bundle, plugin — see docs/DEPS-CATALOG.md
// House style external deps (F-083): bare GAV, library(name=…), bundle, plugin — docs/DEPS-CATALOG.md
// Sample still uses advanced typed catalogs (build-dependencies/) for androidx/google graphs.
projectDependencies(
"libs",
"com.jakewharton.timber:timber:5.0.1",
Expand Down
7 changes: 4 additions & 3 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,10 @@ compatibility). See [`COMPOSE.md`](COMPOSE.md) for F-013 usage.
side-effects from catalog registrations, sets transitive flags
- Catalog: `projectDependencies` / `library` / `bundle` / `plugin` in settings
(`tools.forma.deps.catalog`) + pure name generators + GAV validation
(user guide: [`DEPS-CATALOG.md`](DEPS-CATALOG.md))
- Sample also uses hand-written catalogs in `build-dependencies/dependencies`
(`Androidx`, `Google`, `Test`, …)
(**house style** — user guide: [`DEPS-CATALOG.md`](DEPS-CATALOG.md))
- Sample also uses **advanced** hand-written typed catalogs in
`build-dependencies/dependencies` (`Androidx`, `Google`, `Test`, …) for large
nested graphs — not a second default happy path (F-083)

### 2.5 Includer

Expand Down
2 changes: 1 addition & 1 deletion docs/CALL-SITE-SURFACE.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,5 @@ parameters beside them.
- Matrix: [DEPENDENCY-MATRIX.md](DEPENDENCY-MATRIX.md)
- Plugins: [TARGET-PLUGINS.md](TARGET-PLUGINS.md)
- Global config (one path + store story): [`PROJECT-CONFIGURATION.md`](PROJECT-CONFIGURATION.md) (F-082)
- House style for external deps catalogs: **F-083**
- External deps house style: [`DEPS-CATALOG.md`](DEPS-CATALOG.md) (**F-083** — `projectDependencies` happy path; typed catalogs advanced)
- Full-tree teaching audit: **F-085**
88 changes: 69 additions & 19 deletions docs/DEPS-CATALOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
# Forma external dependency catalogs

Forma has **two** complementary ways to declare third-party libraries. This doc is the
user-facing guide for **F-012** catalog UX (settings-level version catalogs) and how it
relates to the typed demo catalogs under `build-dependencies/`.
**House style (one global way):** declare third-party libraries and plugins once with
settings-level [`projectDependencies`](#1-house-style--projectdependencies-version-catalog)
and consume them via `libs.*` + `deps(...)`.

## 1. Settings version catalog — `projectDependencies`
This is the **only** supported happy path for new apps, progressive examples, and
agent skills. It matches root principle 2 — *one global way* per concern
([VISION.md](VISION.md)).

**Advanced (not a second happy path):** hand-written typed Kotlin catalog objects
(the sample’s `build-dependencies/` pattern). Keep them only when you need large
shared non-transitive graphs that version-catalog accessors do not express well.
Do not teach typed catalogs and `projectDependencies` as equal peer defaults.

| Path | Status | Use when |
|------|--------|----------|
| `projectDependencies` → `libs.*` | **House style** | New projects, Portal apps, plugins, bundles, normal third-party pins |
| Typed objects (`androidx.*`, `google.*`, …) | **Advanced / sample-scale** | Nested explicit transitive trees at monorepo scale; not the default teaching path |

---

## 1. House style — `projectDependencies` version catalog

Declare libraries, bundles, and plugins once in `settings.gradle.kts`. Forma fills a
Gradle [version catalog](https://docs.gradle.org/current/userguide/platforms.html#sub:version-catalog)
Expand Down Expand Up @@ -42,6 +58,9 @@ projectDependencies(
)
```

Teaching ladder: [`examples/android/08-deps-catalog`](../examples/android/08-deps-catalog).
Agent skill: [`forma-deps-catalog`](../examples/agent-skills/forma-deps-catalog.md).

### Entry kinds

| Entry | Factory | Catalog result | Notes |
Expand Down Expand Up @@ -101,26 +120,46 @@ plugin(
At dependency application time, consuming the room-compiler coordinate applies the KSP
plugin once and adds the processor with `isTransitive = true`.

## 2. Typed catalogs — `build-dependencies/` (sample pattern)
---

## 2. Advanced — typed catalogs (`build-dependencies/` pattern)

The sample app also ships **hand-written** Kotlin objects
The gold-standard sample still ships **hand-written** Kotlin objects
(`androidx`, `google`, `test`, …) under `build-dependencies/dependencies`. Those use
`String.dep` / `deps(...)` and nest transitive graphs explicitly (e.g. `androidx.appcompat`
pulls fragment, core, …).

| Approach | Best for | Trade-offs |
|----------|----------|------------|
| `projectDependencies` version catalog | App-level third-party pins, plugins, short `libs.*` accessors | Names generated or explicit; less control over nested transitive graphs |
| Typed `build-dependencies` objects | Large shared graphs, team-owned versions, non-transitive-by-default trees | More boilerplate; included via `includeBuild` + demo plugin |
This is **not** the default product path. Prefer expanding `projectDependencies` first.
Use typed catalogs only when:

Both can coexist: the sample uses catalogs for timber/coil/room/plugins and typed objects
for the androidx/google surface used across features.
- You maintain a large shared AndroidX/Google surface with intentional non-transitive trees
- Version-catalog bundles become unwieldy for that surface
- You already own an `includeBuild` deps module (as the sample does via
`tools.forma.demo:dependencies`)

| Concern | House style (`projectDependencies`) | Advanced typed objects |
|---------|-------------------------------------|-------------------------|
| New app / tutorial | **Yes** | No |
| Plugin ids on classpath | **Yes** (`plugin(...)`) | Via separate wiring |
| Short `libs.*` accessors | **Yes** | N/A (`androidx.foo` style) |
| Nested non-transitive graphs | Limited (bundles + `deps` defaults) | **Yes** — main reason to stay advanced |
| includeBuild required | No | Yes (sample pattern) |

The sample **mixes** both on purpose at monorepo scale: timber/coil/room/plugins go through
the house-style catalog; the broad androidx/google surface stays on typed objects. New
projects should start with **catalog only** and adopt typed objects later only if needed.

Do **not** invent a third path (scattering raw GAVs across every `build.gradle.kts`) for
production modules. Tiny progressive examples may still use inline `deps("g:a:v")` before
step 08 introduces catalogs.

---

## 3. API surface (plugins `:deps`)

| Symbol | Package | Role |
|--------|---------|------|
| `projectDependencies` | `tools.forma.deps.catalog` | Settings DSL entry |
| `projectDependencies` | `tools.forma.deps.catalog` | Settings DSL entry (**house style**) |
| `library` / `LibraryDep` | same | Explicit library registration |
| `bundle` / `BundleDep` | same | Named library groups |
| `plugin` / `PluginDep` | same | Plugin + optional config/libs |
Expand All @@ -129,22 +168,33 @@ for the androidx/google surface used across features.
| `deps` / `String.dep` / `Provider.dep` | root (`dependencies.kt`) | Bridge into `FormaDependency` |
| `applyDependencies` | `tools.forma.deps.core` | Wire deps + plugin side effects |

---

## 4. Practical tips

1. **Pin versions in one place** — either catalog constants in `settings.gradle.kts` or
`versions` object in a typed catalog module; avoid scattering GAVs across targets.
1. **Pin versions in one place** — catalog constants in `settings.gradle.kts` (house style)
or a single `versions` object inside an advanced typed module; never scatter GAVs across
feature targets.
2. **Prefer `library(…, name = …)`** for public-facing short names you will type often.
3. **Use `bundle`** when several artifacts always travel together (Room, Coil).
4. **Keep transitive control intentional** — bare catalog `deps(libs.foo)` follows Forma's
default non-transitive named-deps path unless a plugin registration forces transitive.
Use `transitiveDeps(...)` when Maven transitively is required.
5. **Invalid GAV fails fast** — `group:artifact:version` only; two or four segments throw
a clear `IllegalArgumentException` at configuration time.
6. **Plugins vs apply** — catalog `plugin(...)` + `extraPlugins` put jars on the
**buildscript classpath only**. Type-owned apply is separate
([TARGET-PLUGINS.md](TARGET-PLUGINS.md)).

---

## Related

- Live project-dep matrix: [`DEPENDENCY-MATRIX.md`](DEPENDENCY-MATRIX.md)
- Architecture map: [`ARCHITECTURE.md`](ARCHITECTURE.md) §2.4
- Sample settings: `application/settings.gradle.kts`
- Sample typed catalogs: `build-dependencies/dependencies/src/main/kotlin/`
- **Target external plugins** — type owns plugin, call sites auto-apply (Bazel-like):
[`TARGET-PLUGINS.md`](TARGET-PLUGINS.md) (F-070+)
- Sample settings (house style + advanced mix): `application/settings.gradle.kts`
- Sample typed catalogs (advanced): `build-dependencies/dependencies/src/main/kotlin/`
- Progressive example (house style): `examples/android/08-deps-catalog`
- **Target external plugins** — type owns plugin, call sites auto-apply:
[`TARGET-PLUGINS.md`](TARGET-PLUGINS.md)
- Call-site surface: [`CALL-SITE-SURFACE.md`](CALL-SITE-SURFACE.md)
27 changes: 16 additions & 11 deletions docs/GETTING-STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ full multi-feature reference layout, see [SAMPLE-APP.md](SAMPLE-APP.md).
| **Suffix** | Folder / project name ending that encodes type (`…-impl`, `…-api`, `…-res`, `binary`, …). Validators use suffixes. |
| **Composition root** | `androidApp` and/or `androidBinary` — where feature graphs are wired (Dagger-friendly). |
| **Feature slice** | Typical feature folder: `api` + `impl` + `res` + `viewbinding`. |
| **Catalogs** | External deps declared once (`projectDependencies` / typed catalogs), consumed via `deps(...)`. |
| **Catalogs** | External deps declared once via house-style `projectDependencies` (`libs.*`), consumed with `deps(...)`. Typed objects = advanced only — [DEPS-CATALOG.md](DEPS-CATALOG.md). |

**What Forma does for each target**

Expand Down Expand Up @@ -248,19 +248,21 @@ androidBinary(
androidApp(
packageName = "com.example.myapp.root",
dependencies = deps(
androidx.core_ktx, // when using typed demo catalogs; or libs.* / bare deps
androidx.appcompat,
google.material,
libs.androidxCoreKtx, // house style: projectDependencies → libs.*
libs.androidxAppcompat,
libs.material,
) + deps(
target(":root-res"),
),
)
```

> **Note on external catalogs:** the sample uses both typed catalogs under
> `build-dependencies/` (`androidx.*`, `google.*`) and settings
> `projectDependencies` (`libs.*`). A Portal-only app can start with
> `projectDependencies` only — see [DEPS-CATALOG.md](DEPS-CATALOG.md).
> **External deps house style (F-083):** use settings
> `projectDependencies` → `libs.*` for all new modules
> ([DEPS-CATALOG.md](DEPS-CATALOG.md)). The gold-standard sample still uses
> **advanced** typed catalogs under `build-dependencies/` (`androidx.*`,
> `google.*`) for its large shared graph — that is optional scale, not a second
> happy path. Portal / greenfield apps should stay on `projectDependencies` only.

**`feature/hello/api/build.gradle.kts`** — JVM contracts only (no `res/`):

Expand Down Expand Up @@ -335,20 +337,23 @@ Compose flags: [COMPOSE.md](COMPOSE.md).

```kotlin
dependencies = deps(
libs.jakewhartonTimber, // from projectDependencies catalog
libs.jakewhartonTimber, // house style: projectDependencies catalog
libs.bundles.room,
// or typed catalog objects in the sample: androidx.appcompat, google.dagger
libs.androidxAppcompat,
)
```

Combine groups with `+`:

```kotlin
dependencies = deps(androidx.core_ktx, google.material) +
dependencies = deps(libs.androidxCoreKtx, libs.material) +
deps(libs.jakewhartonTimber) +
deps(target(":feature:hello:api"))
```

> Advanced only: the sample’s typed objects (`androidx.core_ktx`, `google.dagger`)
> remain valid where that pattern is already in use — see DEPS-CATALOG §2.

### Project / internal

```kotlin
Expand Down
4 changes: 2 additions & 2 deletions docs/JVM-GETTING-STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ the full multi-feature reference layout, see [JVM-SAMPLE.md](JVM-SAMPLE.md).
| **Suffix** | Folder / project name ending that encodes type (`…-impl`, `…-api`, `binary`, …). Validators use suffixes. |
| **Composition root** | `binary` — where feature graphs are wired (entry point for `main`). |
| **Feature slice** | Typical feature folder: `api` + `impl`. |
| **Catalogs** | External deps declared once (`projectDependencies` / typed catalogs), consumed via `deps(...)`. |
| **Catalogs** | External deps declared once via house-style `projectDependencies` (`libs.*`), consumed with `deps(...)`. Typed catalogs = advanced — [DEPS-CATALOG.md](DEPS-CATALOG.md). |

**What Forma does for each target**

Expand Down Expand Up @@ -127,7 +127,7 @@ includer {

rootProject.name = "my-jvm-app"

// Optional: settings-level version catalog — see docs/DEPS-CATALOG.md
// House style external deps — see docs/DEPS-CATALOG.md
// projectDependencies("libs", "com.squareup.okhttp3:okhttp:4.12.0", …)
```

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

Newest entries first.

## 2026-07-20 — F-083: one external-deps house style

- **Ticket:** F-083 → `done`
- **Branch:** `forma/F-083-external-deps-convention` (from origin/v2)
- **Skills/modes:** Grok Build `--mode full` design 503 + implement timeout (no tree changes); Hermes finish path for docs/convention slice (F-083 is house-style docs class)
- **Decision (locked):**
- **House style:** `projectDependencies` → `libs.*` + `deps(...)`
- **Advanced only:** typed `build-dependencies/` objects (`androidx.*`, `google.*`) for large nested non-transitive graphs
- Not dual happy paths; sample may still mix at monorepo scale
- **Docs / teaching:**
- Rewrote `docs/DEPS-CATALOG.md` (house style §1, advanced §2)
- README, GETTING-STARTED, JVM-GETTING-STARTED, CALL-SITE-SURFACE, ARCHITECTURE §2.4, PROGRESSIVE-EXAMPLES
- Agent skill `forma-deps-catalog` + `examples/android/08-deps-catalog/README.md`
- Light KDoc on `Settings.projectDependencies`; sample settings comment
- **Out of scope (intentional):** mass-migrate sample modules off typed catalogs
- **Verify (real host, `source scripts/env-mac.sh`):**
- `plugins/ ./gradlew build` → **BUILD SUCCESSFUL** (78 tasks)
- `application/ ./gradlew build` → **BUILD SUCCESSFUL** (2322 tasks)
- `examples/android/08-deps-catalog ./gradlew :binary:assembleDebug` → **BUILD SUCCESSFUL** (128 tasks)
- **Blockers:** none
- **Next:** F-084 fleet tooling (check/generate/migrate)

## 2026-07-20 — F-086: kapt → KSP + AGP built-in Kotlin

- **Ticket:** F-086 → `done`
Expand Down
2 changes: 1 addition & 1 deletion docs/PROGRESSIVE-EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ narrative tutorials when you want a **minimal buildable project per concept**.
| `widget` / `uiLibrary` | 05 | — | forma-android-targets |
| Compose + `composeWidget` | 06 | — | forma-compose |
| Multi-feature composition | 07 | 04 | forma-project-layout |
| `projectDependencies` catalogs | 08 | — | forma-deps-catalog |
| `projectDependencies` catalogs (**house style**) | 08 | — | forma-deps-catalog |
| `testUtil` | 09 | 05 | platform skills |
| `androidTestUtil` | 09 | — | forma-android-targets |
| Type-owned target plugins (`navigationRes` Path B) | 10 | — | forma-target-plugins |
Expand Down
20 changes: 16 additions & 4 deletions examples/agent-skills/forma-deps-catalog.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
---
name: forma-deps-catalog
description: projectDependencies catalogs — library, bundle, plugin, bare GAV.
description: House-style projectDependencies catalogs — library, bundle, plugin, bare GAV.
---

# Deps catalog (agent skill)

**House style (one global way):** `projectDependencies` in `settings.gradle.kts` →
`libs.*` + `deps(...)`. Do not teach typed `build-dependencies/` objects as an
equal default.

Doc: `docs/DEPS-CATALOG.md`. Example: `examples/android/08-deps-catalog`.

```kotlin
Expand All @@ -21,9 +25,17 @@ projectDependencies(
dependencies = deps(libs.appcompat, libs.jakewhartonTimber, libs.bundles.uiBasics)
```

External GAV strings also work in `deps("group:artifact:version")` for tiny examples.
Prefer catalogs in real apps.
External GAV strings also work in `deps("group:artifact:version")` for tiny
examples **before** step 08. Prefer catalogs in real apps.

## Advanced (not default)

Hand-written typed catalogs (`androidx.*`, `google.*` under sample
`build-dependencies/`) are for large nested non-transitive graphs. See
DEPS-CATALOG §2. New projects and agent scaffolds: **catalog only**.

## Transitivity

`deps("g:a:v")` is **non-transitive**. Use `transitiveDeps(...)` when Maven transitively is required (Compose UI, JUnit/hamcrest, AppCompat clusters).
`deps("g:a:v")` / bare catalog entries are **non-transitive**. Use
`transitiveDeps(...)` when Maven transitively is required (Compose UI,
JUnit/hamcrest, AppCompat clusters).
Loading
Loading