diff --git a/README.md b/README.md index e85edbd6..66c8d268 100644 --- a/README.md +++ b/README.md @@ -88,13 +88,14 @@ buildscript { } ``` -Your kotlin android library +Your kotlin android library (role-specific — prefer `androidUtil` / `uiLibrary` / +`impl` over deprecated `androidLibrary`) ``` gradle // Single method, type-safe creation of your target // Plugins applied automatically // Project configuration shared between targets -androidLibrary( +androidUtil( // Mandatory, visible from build configuration packageName = "tools.forma.sample.example", // External dependencies declaration, one universal syntax @@ -103,15 +104,11 @@ androidLibrary( androidx.appcompat, ) + deps( // Internal project dependencies, declared separately from externals - project(":demo-library") + project(":demo-android-util") ), // Test dependencies declaration testDependencies = deps( test.junit - ), - // Android test dependencies declaration - androidTestDependencies = deps( - test.espresso ) ) ``` @@ -150,7 +147,7 @@ project-dep type checks + content layout rules from live validators |:----------------------:|:-----------:|:-------:|:----------:| | `androidBinary` | ✅ | Generate single APK | name + no `res/`; composition-root project-dep list | | `androidApp` | ✅ | Application / root Android library | name + no `res/`; composition-root project-dep list | -| `androidLibrary` | ✅ | Android library | name + project-dep list (**no** `impl`) | +| `androidLibrary` | ⚠️ deprecated | Generic Android lib escape hatch — **do not use** | see [`docs/ANDROID-LIBRARY-DEPRECATION.md`](docs/ANDROID-LIBRARY-DEPRECATION.md) | | `uiLibrary` | ✅ | Shared UI library for impl/widget | project-dep list | | `widget` | ✅ | Custom View / UI component | project-dep list | | `composeWidget` | ✅ | Compose UI component | always Compose + project-dep list | @@ -177,16 +174,16 @@ That document is generated from each target’s | `api` | `api`, `library` | | `impl` | `api`, `android-util`, `test-util`, `util`, `library`, `ui-library`, `res`, `viewbinding`, `widget`, `compose-widget` | | `library` (JVM) | `util`, `test-util` | -| `androidLibrary` | `library`, `util`, `android-util`, `test-util`, `res`, `api` | +| `androidLibrary` (deprecated) | `library`, `util`, `android-util`, `test-util`, `res`, `api` | | `uiLibrary` | `widget`, `compose-widget`, `util`, `android-util`, `res` | | `util` | `util`, `library` | -| `androidUtil` | `android-util`, `test-util`, `res` | +| `androidUtil` | `android-util`, `test-util`, `res`, `library` | | `testUtil` | `test-util`, `util` | | `androidTestUtil` | `android-test-util`, `test-util` | | `androidRes` | `res`, `widget`, `compose-widget` | | `widget` | `ui-library`, `widget`, `compose-widget`, `util`, `android-util`, `res` | | `composeWidget` | `ui-library`, `compose-widget`, `widget`, `util`, `android-util`, `res` | -| `viewBinding` | `api`, `widget`, `compose-widget`, `res`, `library`, `android-util` | +| `viewBinding` | `api`, `widget`, `compose-widget`, `res`, `library`, `android-util`, `ui-library` | | `androidApp` | `api`, `impl`, `library`, `util`, `android-util`, `test-util`, `res`, `viewbinding`, `widget`, `compose-widget`, `ui-library` | | `androidBinary` | `app`, `api`, `impl`, `library`, `util`, `android-util`, `test-util`, `res`, `viewbinding`, `widget`, `compose-widget`, `ui-library` | | `androidNative` | *(no project-dep check)* | diff --git a/TICKETS.md b/TICKETS.md index 5652f8fb..1f15d90e 100644 --- a/TICKETS.md +++ b/TICKETS.md @@ -58,6 +58,19 @@ Update this file when picking or finishing work. Cron workers must pick the **hi |----|--------|-------|-------| | F-050 | done | Progressive examples + agent skills for all supported features | `examples/{jvm,android}/*` + `examples/agent-skills/` + `docs/PROGRESSIVE-EXAMPLES.md`; JVM build+run and Android assembleDebug verified | +## P6 — Flat structure (deprecate generic androidLibrary) + +Forma’s job is to **keep the graph flat and role-typed**. `androidLibrary` was a +temporary generic AGP-library escape hatch (shared suffix with JVM `library`) that +encourages dumping mixed concerns into one bucket. Prefer specific targets. + +| ID | Status | Title | Notes | +|----|--------|-------|-------| +| F-060 | done | Deprecate `androidLibrary` + document flat-structure policy | `@Deprecated` on DSL; VISION + `docs/ANDROID-LIBRARY-DEPRECATION.md`; matrix/README | +| F-061 | done | Migrate `application/` sample off `androidLibrary` | di→`androidUtil`, mvvm→`uiLibrary`, navigation→`androidRes`; path renames | +| F-062 | done | Reimplement progressive examples / skills without `androidLibrary` | `examples/android/04` + agent skills + README curriculum | +| F-063 | todo | Hard-remove `androidLibrary` target (after consumers migrated) | Drop registry type / docs row once no in-repo callers | + ## Backlog (lower priority / historical GitHub) Keep for reference; do not start unless higher tickets done or user prioritizes: diff --git a/application/binary/build.gradle.kts b/application/binary/build.gradle.kts index 5998a49c..e7f46609 100644 --- a/application/binary/build.gradle.kts +++ b/application/binary/build.gradle.kts @@ -22,8 +22,8 @@ androidBinary( target(":common:extensions:android-util"), // target(":common:util-native"), target(":common:greeting:compose-widget"), - target(":core:mvvm:library"), - target(":core:di:library") + target(":core:mvvm:ui-library"), + target(":core:di:android-util") ) ) // TODO: enable when create crashlytics project diff --git a/application/core/di/library/.gitignore b/application/core/di/android-util/.gitignore similarity index 100% rename from application/core/di/library/.gitignore rename to application/core/di/android-util/.gitignore diff --git a/application/core/di/library/build.gradle.kts b/application/core/di/android-util/build.gradle.kts similarity index 86% rename from application/core/di/library/build.gradle.kts rename to application/core/di/android-util/build.gradle.kts index b225716f..c62be57e 100644 --- a/application/core/di/library/build.gradle.kts +++ b/application/core/di/android-util/build.gradle.kts @@ -1,6 +1,6 @@ -androidLibrary( +androidUtil( packageName = "tools.forma.sample.core.di.library", dependencies = deps( google.dagger ) -) \ No newline at end of file +) diff --git a/application/core/di/library/di-structure.puml b/application/core/di/android-util/di-structure.puml similarity index 96% rename from application/core/di/library/di-structure.puml rename to application/core/di/android-util/di-structure.puml index b58f3d39..46e86dde 100644 --- a/application/core/di/library/di-structure.puml +++ b/application/core/di/android-util/di-structure.puml @@ -1,7 +1,7 @@ @startuml together { - package ":core:di:library" { + package ":core:di:android-util" { class BaseModule { + providesContext(app: Application): Context } @@ -71,7 +71,7 @@ package ":app" { ":app" ..> ":root" : deps ":app" ..> ":feature:home:api" : deps ":app" ..> ":feature:home:impl" : deps -":app" ..> ":core:di:library" : deps +":app" ..> ":core:di:android-util" : deps @enduml \ No newline at end of file diff --git a/application/core/di/library/src/main/java/tools/forma/sample/core/di/library/BaseComponent.kt b/application/core/di/android-util/src/main/java/tools/forma/sample/core/di/library/BaseComponent.kt similarity index 100% rename from application/core/di/library/src/main/java/tools/forma/sample/core/di/library/BaseComponent.kt rename to application/core/di/android-util/src/main/java/tools/forma/sample/core/di/library/BaseComponent.kt diff --git a/application/core/di/library/src/main/java/tools/forma/sample/core/di/library/BaseModule.kt b/application/core/di/android-util/src/main/java/tools/forma/sample/core/di/library/BaseModule.kt similarity index 100% rename from application/core/di/library/src/main/java/tools/forma/sample/core/di/library/BaseModule.kt rename to application/core/di/android-util/src/main/java/tools/forma/sample/core/di/library/BaseModule.kt diff --git a/application/core/di/library/src/main/java/tools/forma/sample/core/di/library/scopes/AppScope.kt b/application/core/di/android-util/src/main/java/tools/forma/sample/core/di/library/scopes/AppScope.kt similarity index 100% rename from application/core/di/library/src/main/java/tools/forma/sample/core/di/library/scopes/AppScope.kt rename to application/core/di/android-util/src/main/java/tools/forma/sample/core/di/library/scopes/AppScope.kt diff --git a/application/core/di/library/src/main/java/tools/forma/sample/core/di/library/scopes/FeatureScope.kt b/application/core/di/android-util/src/main/java/tools/forma/sample/core/di/library/scopes/FeatureScope.kt similarity index 100% rename from application/core/di/library/src/main/java/tools/forma/sample/core/di/library/scopes/FeatureScope.kt rename to application/core/di/android-util/src/main/java/tools/forma/sample/core/di/library/scopes/FeatureScope.kt diff --git a/application/core/mvvm/library/.gitignore b/application/core/mvvm/ui-library/.gitignore similarity index 100% rename from application/core/mvvm/library/.gitignore rename to application/core/mvvm/ui-library/.gitignore diff --git a/application/core/mvvm/library/build.gradle.kts b/application/core/mvvm/ui-library/build.gradle.kts similarity index 78% rename from application/core/mvvm/library/build.gradle.kts rename to application/core/mvvm/ui-library/build.gradle.kts index 22937a98..ce27b95a 100644 --- a/application/core/mvvm/library/build.gradle.kts +++ b/application/core/mvvm/ui-library/build.gradle.kts @@ -1,5 +1,5 @@ -// TODO Change module type from `androidLibary` to `databinding` -androidLibrary( +// Shared MVVM / ViewBinding UI bases — typed as uiLibrary (not generic androidLibrary). +uiLibrary( packageName = "tools.forma.sample.core.mvvm.library", dependencies = deps( @@ -12,7 +12,7 @@ androidLibrary( androidx.paging, viewbinding.viewBinding, google.dagger, - )+ deps( + ) + deps( libs.jakewhartonTimber, ), diff --git a/application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/Extensions.kt b/application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/Extensions.kt similarity index 100% rename from application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/Extensions.kt rename to application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/Extensions.kt diff --git a/application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/di/ViewModelKey.kt b/application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/di/ViewModelKey.kt similarity index 100% rename from application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/di/ViewModelKey.kt rename to application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/di/ViewModelKey.kt diff --git a/application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/lifecycle/SingleLiveData.kt b/application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/lifecycle/SingleLiveData.kt similarity index 100% rename from application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/lifecycle/SingleLiveData.kt rename to application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/lifecycle/SingleLiveData.kt diff --git a/application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/lifecycle/ViewModelFactory.kt b/application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/lifecycle/ViewModelFactory.kt similarity index 100% rename from application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/lifecycle/ViewModelFactory.kt rename to application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/lifecycle/ViewModelFactory.kt diff --git a/application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseListAdapter.kt b/application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseListAdapter.kt similarity index 100% rename from application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseListAdapter.kt rename to application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseListAdapter.kt diff --git a/application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BasePagedListAdapter.kt b/application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BasePagedListAdapter.kt similarity index 100% rename from application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BasePagedListAdapter.kt rename to application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BasePagedListAdapter.kt diff --git a/application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseViewBindingFragment.kt b/application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseViewBindingFragment.kt similarity index 100% rename from application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseViewBindingFragment.kt rename to application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseViewBindingFragment.kt diff --git a/application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseViewHolder.kt b/application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseViewHolder.kt similarity index 100% rename from application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseViewHolder.kt rename to application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseViewHolder.kt diff --git a/application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseViewState.kt b/application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseViewState.kt similarity index 100% rename from application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseViewState.kt rename to application/core/mvvm/ui-library/src/main/java/tools/forma/sample/core/mvvm/library/ui/BaseViewState.kt diff --git a/application/core/navigation/library/build.gradle.kts b/application/core/navigation/library/build.gradle.kts deleted file mode 100644 index 4d55e07c..00000000 --- a/application/core/navigation/library/build.gradle.kts +++ /dev/null @@ -1,6 +0,0 @@ -androidLibrary( - packageName = "tools.forma.sample.core.navigation.library", - dependencies = deps( - androidx.navigation - ) -).withPlugin(Plugins.navigationSafeArgs) \ No newline at end of file diff --git a/application/core/navigation/library/.gitignore b/application/core/navigation/res/.gitignore similarity index 100% rename from application/core/navigation/library/.gitignore rename to application/core/navigation/res/.gitignore diff --git a/application/core/navigation/res/build.gradle.kts b/application/core/navigation/res/build.gradle.kts new file mode 100644 index 00000000..5884f5fd --- /dev/null +++ b/application/core/navigation/res/build.gradle.kts @@ -0,0 +1,7 @@ +// Navigation graphs only — resources target (flat structure, not androidLibrary). +androidRes( + packageName = "tools.forma.sample.core.navigation.library", + dependencies = deps( + androidx.navigation + ) +).withPlugin(Plugins.navigationSafeArgs) diff --git a/application/core/navigation/library/consumer-rules.pro b/application/core/navigation/res/consumer-rules.pro similarity index 100% rename from application/core/navigation/library/consumer-rules.pro rename to application/core/navigation/res/consumer-rules.pro diff --git a/application/core/navigation/library/navigation.puml b/application/core/navigation/res/navigation.puml similarity index 100% rename from application/core/navigation/library/navigation.puml rename to application/core/navigation/res/navigation.puml diff --git a/application/core/navigation/library/proguard-rules.pro b/application/core/navigation/res/proguard-rules.pro similarity index 100% rename from application/core/navigation/library/proguard-rules.pro rename to application/core/navigation/res/proguard-rules.pro diff --git a/application/core/navigation/library/src/main/res/navigation/navigation_character_favorite_graph.xml b/application/core/navigation/res/src/main/res/navigation/navigation_character_favorite_graph.xml similarity index 100% rename from application/core/navigation/library/src/main/res/navigation/navigation_character_favorite_graph.xml rename to application/core/navigation/res/src/main/res/navigation/navigation_character_favorite_graph.xml diff --git a/application/core/navigation/library/src/main/res/navigation/navigation_characters_list_graph.xml b/application/core/navigation/res/src/main/res/navigation/navigation_characters_list_graph.xml similarity index 100% rename from application/core/navigation/library/src/main/res/navigation/navigation_characters_list_graph.xml rename to application/core/navigation/res/src/main/res/navigation/navigation_characters_list_graph.xml diff --git a/application/feature/characters/core/impl/build.gradle.kts b/application/feature/characters/core/impl/build.gradle.kts index 672e69f0..9bb84acc 100644 --- a/application/feature/characters/core/impl/build.gradle.kts +++ b/application/feature/characters/core/impl/build.gradle.kts @@ -7,9 +7,9 @@ impl( ) + deps( target(":feature:characters:core:api"), - target(":core:di:library"), + target(":core:di:android-util"), target(":core:theme:android-util"), - target(":core:mvvm:library"), + target(":core:mvvm:ui-library"), target(":core:network:library"), target(":common:extensions:util"), diff --git a/application/feature/characters/detail/impl/build.gradle.kts b/application/feature/characters/detail/impl/build.gradle.kts index 85a72f53..7380c86e 100644 --- a/application/feature/characters/detail/impl/build.gradle.kts +++ b/application/feature/characters/detail/impl/build.gradle.kts @@ -23,11 +23,11 @@ impl( target(":feature:characters:detail:res"), target(":feature:characters:detail:viewbinding"), - target(":core:di:library"), + target(":core:di:android-util"), target(":core:theme:android-util"), - target(":core:mvvm:library"), + target(":core:mvvm:ui-library"), target(":core:network:library"), - target(":core:navigation:library"), + target(":core:navigation:res"), target(":common:util"), target(":common:extensions:android-util"), diff --git a/application/feature/characters/favorite/impl/build.gradle.kts b/application/feature/characters/favorite/impl/build.gradle.kts index 22af9060..ac5d167f 100644 --- a/application/feature/characters/favorite/impl/build.gradle.kts +++ b/application/feature/characters/favorite/impl/build.gradle.kts @@ -23,11 +23,11 @@ impl( target(":feature:characters:favorite:res"), target(":feature:characters:favorite:viewbinding"), - target(":core:di:library"), + target(":core:di:android-util"), target(":core:theme:android-util"), - target(":core:mvvm:library"), + target(":core:mvvm:ui-library"), target(":core:network:library"), - target(":core:navigation:library"), + target(":core:navigation:res"), target(":common:util"), target(":common:extensions:android-util"), diff --git a/application/feature/characters/favorite/viewbinding/build.gradle.kts b/application/feature/characters/favorite/viewbinding/build.gradle.kts index d8657f6a..329c9377 100644 --- a/application/feature/characters/favorite/viewbinding/build.gradle.kts +++ b/application/feature/characters/favorite/viewbinding/build.gradle.kts @@ -10,8 +10,8 @@ viewBinding( target(":feature:characters:favorite:api"), target(":feature:characters:favorite:res"), - target(":core:navigation:library"), - target(":core:mvvm:library"), + target(":core:navigation:res"), + target(":core:mvvm:ui-library"), target(":core:theme:res") ) ) \ No newline at end of file diff --git a/application/feature/characters/list/impl/build.gradle.kts b/application/feature/characters/list/impl/build.gradle.kts index 5fa18173..1ed95a3a 100644 --- a/application/feature/characters/list/impl/build.gradle.kts +++ b/application/feature/characters/list/impl/build.gradle.kts @@ -21,11 +21,11 @@ impl( target(":feature:characters:list:api"), target(":feature:characters:list:viewbinding"), target(":feature:characters:list:res"), - target(":core:di:library"), + target(":core:di:android-util"), target(":core:theme:android-util"), - target(":core:mvvm:library"), + target(":core:mvvm:ui-library"), target(":core:network:library"), - target(":core:navigation:library"), + target(":core:navigation:res"), target(":common:util"), target(":common:extensions:android-util"), target(":common:recyclerview:widget") diff --git a/application/feature/characters/list/viewbinding/build.gradle.kts b/application/feature/characters/list/viewbinding/build.gradle.kts index df5be85f..a6cc1ce7 100644 --- a/application/feature/characters/list/viewbinding/build.gradle.kts +++ b/application/feature/characters/list/viewbinding/build.gradle.kts @@ -12,6 +12,6 @@ viewBinding( target(":core:theme:res"), target(":core:network:library"), - target(":core:mvvm:library"), + target(":core:mvvm:ui-library"), ) ) \ No newline at end of file diff --git a/application/feature/home/impl/build.gradle.kts b/application/feature/home/impl/build.gradle.kts index 9c6fe30a..b803b357 100644 --- a/application/feature/home/impl/build.gradle.kts +++ b/application/feature/home/impl/build.gradle.kts @@ -16,10 +16,10 @@ impl( target(":feature:home:api"), target(":feature:home:res"), target(":feature:home:viewbinding"), - target(":core:di:library"), + target(":core:di:android-util"), target(":core:theme:android-util"), - target(":core:mvvm:library"), - target(":core:navigation:library"), + target(":core:mvvm:ui-library"), + target(":core:navigation:res"), target(":common:extensions:android-util"), ) ) diff --git a/application/feature/home/viewbinding/build.gradle.kts b/application/feature/home/viewbinding/build.gradle.kts index 8be7b872..cfeaf3f0 100644 --- a/application/feature/home/viewbinding/build.gradle.kts +++ b/application/feature/home/viewbinding/build.gradle.kts @@ -7,8 +7,8 @@ viewBinding( androidx.appcompat, androidx.constraintlayout ) + deps( - target(":core:navigation:library"), - target(":core:mvvm:library"), + target(":core:navigation:res"), + target(":core:mvvm:ui-library"), target(":core:theme:res"), target(":feature:home:res"), ) diff --git a/application/root-app/build.gradle.kts b/application/root-app/build.gradle.kts index 6962759c..94478d39 100644 --- a/application/root-app/build.gradle.kts +++ b/application/root-app/build.gradle.kts @@ -20,8 +20,8 @@ androidApp( target(":feature:characters:core:impl"), target(":feature:characters:favorite:api"), target(":feature:characters:favorite:impl"), - target(":core:di:library"), - target(":core:navigation:library"), + target(":core:di:android-util"), + target(":core:navigation:res"), target(":core:theme:android-util"), target(":core:network:library"), target(":common:util") diff --git a/docs/ANDROID-LIBRARY-DEPRECATION.md b/docs/ANDROID-LIBRARY-DEPRECATION.md new file mode 100644 index 00000000..27259832 --- /dev/null +++ b/docs/ANDROID-LIBRARY-DEPRECATION.md @@ -0,0 +1,59 @@ +# Deprecating `androidLibrary` (F-060+) + +## Why + +`androidLibrary` is a **generic AGP library** target. It was a temporary way to +host “shared Android code that isn’t a feature `impl`.” That works against +Forma’s goal: **keep structure flat and role-typed**. + +Problems: + +1. **Suffix collision** with JVM `library` (`library` name suffix / historical + `LibraryTargetTemplate`) — validators resolve deps by suffix and cannot tell + JVM vs Android library modules apart reliably. +2. **Mixed concerns** — sample historically stuffed DI scopes, MVVM/UI bases, and + navigation XML into the same target kind. +3. **Weak teaching signal** — newcomers reach for `androidLibrary` instead of the + target that matches the module’s job. + +## Replacement map + +| If the module is… | Use instead | Suffix | +|-------------------|-------------|--------| +| Pure JVM shared code | `library` | `library` | +| Small Android helpers, **no** `res/` | `androidUtil` | `android-util` | +| Shared UI bases for `impl` / `widget` | `uiLibrary` | `ui-library` | +| Resources only (`res/` tree) | `androidRes` | `res` | +| Layout XML only | `viewBinding` | `viewbinding` | +| Custom View | `widget` | `widget` | +| Compose UI component | `composeWidget` | `compose-widget` | +| Feature implementation | `impl` | `impl` | +| Feature contracts | `api` | `api` | + +## Sample migration (F-061) + +| Old path | Old DSL | New path | New DSL | +|----------|---------|----------|---------| +| `core/di/library` | `androidLibrary` | `core/di/android-util` | `androidUtil` | +| `core/mvvm/library` | `androidLibrary` | `core/mvvm/ui-library` | `uiLibrary` | +| `core/navigation/library` | `androidLibrary` | `core/navigation/res` | `androidRes` | + +Kotlin **packages** may keep historical `.library` segments to limit churn; +**project path / suffix** is what Forma validates. + +## Matrix tweaks paired with migration + +- `viewBinding` may depend on `ui-library` (shared UI bases without generic library). +- `androidUtil` may depend on JVM `library` (Android helpers wrapping pure JVM code). + +## Lifecycle + +| Ticket | Intent | +|--------|--------| +| **F-060** | `@Deprecated` on DSL + docs/vision | +| **F-061** | `application/` sample migrated | +| **F-062** | Progressive examples + agent skills teach replacements | +| **F-063** | Hard-remove target type / registry entry when nothing in-repo calls it | + +Until F-063, `androidLibrary { }` still configures modules but should not appear +in new code or tutorials. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 133c4832..ba7c59f8 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -131,7 +131,7 @@ Defined in `plugins/android/.../AndroidTargets.kt`: |--------|--------|---------------------| | `BinaryTargetTemplate` | `binary` | `androidBinary` | | `ApplicationTargetTemplate` | `app` | `androidApp` | -| `LibraryTargetTemplate` | `library` | `androidLibrary` **and** `library` (JVM) | +| `LibraryTargetTemplate` | `library` | `library` (JVM); deprecated `androidLibrary` shares suffix | | `UiLibraryTargetTemplate` | `ui-library` | `uiLibrary` | | `NativeTarget` | `native` | `androidNative` | | `UtilTargetTemplate` | `util` | `util` | @@ -158,16 +158,16 @@ when validators change. | `api` | `api`, `library` | no `res/` under `src/main` | | `impl` | `api`, `android-util`, `test-util`, `util`, `library`, `ui-library`, `res`, `viewbinding`, `widget`, `compose-widget` | — | | `library` (JVM) | `util`, `test-util` | — | -| `androidLibrary` | `library`, `util`, `android-util`, `test-util`, `res`, `api` | — | +| `androidLibrary` (deprecated) | `library`, `util`, `android-util`, `test-util`, `res`, `api` | — | | `uiLibrary` | `widget`, `compose-widget`, `util`, `android-util`, `res` | — | | `util` | `util`, `library` | no `res/` | -| `androidUtil` | `android-util`, `test-util`, `res` | no `res/` | +| `androidUtil` | `android-util`, `test-util`, `res`, `library` | no `res/` | | `testUtil` | `test-util`, `util` | no `res/` | | `androidTestUtil` | `android-test-util`, `test-util` | — | | `androidRes` | `res`, `widget`, `compose-widget` | **only** `res/` under `src/main` | | `widget` | `ui-library`, `widget`, `compose-widget`, `util`, `android-util`, `res` | — | | `composeWidget` | `ui-library`, `compose-widget`, `widget`, `util`, `android-util`, `res` | always Compose | -| `viewBinding` | `api`, `widget`, `compose-widget`, `res`, `library`, `android-util` | only `layout*` under `src/main/res` | +| `viewBinding` | `api`, `widget`, `compose-widget`, `res`, `library`, `android-util`, `ui-library` | only `layout*` under `src/main/res` | | `androidApp` | `api`, `impl`, `library`, `util`, `android-util`, `test-util`, `res`, `viewbinding`, `widget`, `compose-widget`, `ui-library` | no `res/` | | `androidBinary` | `app`, `api`, `impl`, `library`, `util`, `android-util`, `test-util`, `res`, `viewbinding`, `widget`, `compose-widget`, `ui-library` | no `res/` | | `androidNative` | (no `applyDependencies` in current code) | no `res/` | @@ -175,8 +175,8 @@ when validators change. Notes for later tickets: - `impl` cannot depend on other `impl` (Dagger-friendly) — enforced; F-011. -- `androidLibrary` / `androidApp` / `androidBinary` use restricted project-dep lists — F-011 done. -- JVM `library` and Android `androidLibrary` share `LibraryTargetTemplate` suffix `library` — naming collision risk for forma-core registry design (F-020). +- `androidApp` / `androidBinary` use restricted composition-root lists — F-011 done. +- `androidLibrary` **deprecated** (F-060+); JVM `library` keeps the `library` suffix — see [`ANDROID-LIBRARY-DEPRECATION.md`](ANDROID-LIBRARY-DEPRECATION.md). ### 2.3 Feature stack (Android module) @@ -225,9 +225,9 @@ application/ ├── root-res/ androidRes ├── toggle-widget/ widget ├── core/ -│ ├── di/library androidLibrary -│ ├── mvvm/library androidLibrary -│ ├── navigation/library androidLibrary +│ ├── di/android-util androidUtil +│ ├── mvvm/ui-library uiLibrary +│ ├── navigation/res androidRes │ ├── network/library library (JVM) │ └── theme/{android-util,res} ├── common/ diff --git a/docs/COMPOSE.md b/docs/COMPOSE.md index 9d1af288..faa8f4b3 100644 --- a/docs/COMPOSE.md +++ b/docs/COMPOSE.md @@ -22,7 +22,7 @@ androidProjectConfiguration( | Setting | Role | |---------|------| -| `compose` | Default value for per-target `compose = …` parameters on `impl`, `androidLibrary`, `androidUtil`, `androidApp`, `uiLibrary`, `androidBinary`. | +| `compose` | Default value for per-target `compose = …` parameters on `impl`, `androidUtil`, `androidApp`, `uiLibrary`, `androidBinary` (deprecated `androidLibrary` still accepts it). | | `composeCompilerVersion` | Written to AGP `composeOptions.kotlinCompilerExtensionVersion` when Compose is enabled. | `composeWidget` modules **always** enable Compose; they ignore the project default @@ -37,7 +37,7 @@ impl( dependencies = deps(/* … */), ) -androidLibrary( +uiLibrary( packageName = "com.example.shared.ui.library", compose = true, ) diff --git a/docs/DEPENDENCY-MATRIX.md b/docs/DEPENDENCY-MATRIX.md index 3ed3cec5..8d025c23 100644 --- a/docs/DEPENDENCY-MATRIX.md +++ b/docs/DEPENDENCY-MATRIX.md @@ -8,7 +8,7 @@ Source of truth for **F-010**. Every rule below is taken from the live 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 + F-013 Compose support. +Last verified: **2026-07-16** against `v2` + F-060 androidLibrary deprecation migration. --- @@ -41,7 +41,7 @@ From `plugins/android/.../AndroidTargets.kt`: |----------------|-----------------|-------------| | `androidBinary` | `BinaryTargetTemplate` | `binary` | | `androidApp` | `ApplicationTargetTemplate` | `app` | -| `androidLibrary` | `LibraryTargetTemplate` | `library` | +| `androidLibrary` (**deprecated**) | `LibraryTargetTemplate` | `library` | | `library` (JVM) | `LibraryTargetTemplate` | `library` | | `uiLibrary` | `UiLibraryTargetTemplate` | `ui-library` | | `androidNative` | `NativeTarget` | `native` | @@ -78,16 +78,16 @@ these suffixes (or are unrestricted if `EmptyValidator`). | `api` | `api.kt` | `validator(api, library)` | `api`, `library` | | `impl` | `impl.kt` | `validator(api, android-util, test-util, util, library, ui-library, res, viewbinding, widget, compose-widget)` | `api`, `android-util`, `test-util`, `util`, `library`, `ui-library`, `res`, `viewbinding`, `widget`, `compose-widget` | | `library` (JVM) | `library.kt` | `validator(util, test-util)` | `util`, `test-util` | -| `androidLibrary` | `androidLibrary.kt` | `validator(library, util, android-util, test-util, res, api)` | `library`, `util`, `android-util`, `test-util`, `res`, `api` | +| `androidLibrary` (**deprecated**) | `androidLibrary.kt` | `validator(library, util, android-util, test-util, res, api)` | `library`, `util`, `android-util`, `test-util`, `res`, `api` | | `uiLibrary` | `uiLibrary.kt` | `validator(widget, compose-widget, util, android-util, res)` | `widget`, `compose-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` | +| `androidUtil` | `androidUtil.kt` | `validator(android-util, test-util, res, library)` | `android-util`, `test-util`, `res`, `library` | | `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, compose-widget)` | `res`, `widget`, `compose-widget` | | `widget` | `widget.kt` | `validator(ui-library, widget, compose-widget, util, android-util, res)` | `ui-library`, `widget`, `compose-widget`, `util`, `android-util`, `res` | | `composeWidget` | `composeWidget.kt` | `validator(ui-library, compose-widget, widget, util, android-util, res)` | `ui-library`, `compose-widget`, `widget`, `util`, `android-util`, `res` | -| `viewBinding` | `viewBinding.kt` | `validator(api, widget, compose-widget, res, library, android-util)` | `api`, `widget`, `compose-widget`, `res`, `library`, `android-util` | +| `viewBinding` | `viewBinding.kt` | `validator(api, widget, compose-widget, res, library, android-util, ui-library)` | `api`, `widget`, `compose-widget`, `res`, `library`, `android-util`, `ui-library` | | `androidApp` | `androidApp.kt` | `validator(api, impl, library, util, android-util, test-util, res, viewbinding, widget, compose-widget, ui-library)` | `api`, `impl`, `library`, `util`, `android-util`, `test-util`, `res`, `viewbinding`, `widget`, `compose-widget`, `ui-library` | | `androidBinary` | `androidBinary.kt` | `validator(app, api, impl, library, util, android-util, test-util, res, viewbinding, widget, compose-widget, ui-library)` | `app`, `api`, `impl`, `library`, `util`, `android-util`, `test-util`, `res`, `viewbinding`, `widget`, `compose-widget`, `ui-library` | | `androidNative` | `androidNative.kt` | *(no `applyDependencies`)* | *no project-dep validation* | @@ -105,6 +105,9 @@ These follow from the table (not from separate deny-lists): - **`util` cannot depend on `api` / `impl`** — same intent as library. - **`androidLibrary` cannot depend on `impl` / widgets / viewbinding** — shared libs may use `api` contracts only (F-011). +- **`androidLibrary` is deprecated (F-060)** — temporary generic escape hatch; prefer `androidUtil` / `uiLibrary` / `androidRes` / `viewBinding` / `impl` / JVM `library`. See [`ANDROID-LIBRARY-DEPRECATION.md`](ANDROID-LIBRARY-DEPRECATION.md). +- **`viewBinding` may depend on `ui-library`** — shared UI bases without generic library (F-061). +- **`androidUtil` may depend on JVM `library`** — Android helpers wrapping pure JVM code (F-061). - **`androidApp` / `androidBinary` cannot depend on other `binary`** — single APK composition root (F-011; `app` may not depend on `app`/`binary`). - **Circular-ish UI graph is intentional while experimental:** `uiLibrary` @@ -160,13 +163,13 @@ Rows = **consumer**. Columns = **dependency type (suffix)**. | **androidLibrary** | Y | — | Y | — | Y | Y | Y | — | Y | — | — | — | — | — | — | | **uiLibrary** | — | — | — | — | Y | — | Y | — | Y | — | Y | Y | — | — | — | | **util** | — | — | Y | — | Y | — | — | — | — | — | — | — | — | — | — | -| **androidUtil** | — | — | — | — | — | Y | Y | — | Y | — | — | — | — | — | — | +| **androidUtil** | — | — | Y | — | — | Y | Y | — | Y | — | — | — | — | — | — | | **testUtil** | — | — | — | — | Y | Y | — | — | — | — | — | — | — | — | — | | **androidTestUtil** | — | — | — | — | — | Y | — | Y | — | — | — | — | — | — | — | | **androidRes** | — | — | — | — | — | — | — | — | Y | — | Y | Y | — | — | — | | **widget** | — | — | — | Y | Y | — | Y | — | Y | — | Y | Y | — | — | — | | **composeWidget** | — | — | — | Y | Y | — | Y | — | Y | — | Y | Y | — | — | — | -| **viewBinding** | Y | — | Y | — | — | — | Y | — | Y | — | Y | Y | — | — | — | +| **viewBinding** | Y | — | Y | Y | — | — | Y | — | Y | — | Y | Y | — | — | — | | **androidApp** | Y | Y | Y | Y | Y | Y | Y | — | Y | Y | Y | Y | — | — | — | | **androidBinary** | Y | Y | Y | Y | Y | Y | Y | — | Y | Y | Y | Y | Y | — | — | | **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 | n/a | diff --git a/docs/DEPS-CATALOG.md b/docs/DEPS-CATALOG.md index d137d799..7cf694d5 100644 --- a/docs/DEPS-CATALOG.md +++ b/docs/DEPS-CATALOG.md @@ -73,7 +73,7 @@ you need to inspect catalog contents. ### Using catalog deps in targets ```kotlin -androidLibrary( +impl( packageName = "…", dependencies = deps( libs.jakewhartonTimber, diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md index d9ba50e0..cf0c4d92 100644 --- a/docs/GETTING-STARTED.md +++ b/docs/GETTING-STARTED.md @@ -1,7 +1,7 @@ # Getting started — Android project with Forma (F-015 / GH #53) Forma is a **meta build system** for Android (Gradle plugin). You declare -**targets** with typed, single-method builders (`api`, `impl`, `androidLibrary`, +**targets** with typed, single-method builders (`api`, `impl`, `androidUtil`, `uiLibrary`, …) instead of hand-wiring AGP plugins, source sets, and ad-hoc dependency rules. Forma applies plugins, shared Android settings, and **dependency visibility validation** for you. @@ -292,7 +292,7 @@ dependency**, and the allowed set — fix the edge, don’t weaken the graph. | `viewBinding` | `viewbinding` | Layout XML (+ light binding-facing types) | | `widget` | `widget` | Custom Views | | `composeWidget` | `compose-widget` | Compose UI components (always Compose) | -| `androidLibrary` | `library` | Shared Android library (not a feature impl) | +| ~~`androidLibrary`~~ | `library` | **Deprecated** — use role-specific targets ([deprecation guide](ANDROID-LIBRARY-DEPRECATION.md)) | | `library` | `library` | Shared pure JVM library | | `uiLibrary` | `ui-library` | Shared UI building blocks for widgets | | `util` / `androidUtil` / `testUtil` | `util` / `android-util` / `test-util` | Small helpers | @@ -339,13 +339,14 @@ validator intent (F-011). 1. **`impl` → `impl` is forbidden.** Cross-feature collaboration goes through `api` (or shared `core` libraries). -2. **`androidLibrary` cannot depend on `impl`.** Shared libs depend on contracts, - not feature implementations. +2. **Do not use deprecated `androidLibrary`.** Prefer `androidUtil` / `uiLibrary` / + `androidRes` / `viewBinding` so the graph stays flat and role-typed + ([ANDROID-LIBRARY-DEPRECATION.md](ANDROID-LIBRARY-DEPRECATION.md)). 3. **`api` cannot contain `res/`.** Put resources in `androidRes` / `viewBinding`. 4. **`androidRes` only allows `res/` content** under `src/main`. 5. **`viewBinding` only allows layout resource types.** 6. **Name your folder / project so the suffix matches the DSL** (`…/impl` → - `impl { }`, not `androidLibrary`). + `impl { }`, not a generic library target). 7. **Align `packageName` with source roots** (`tools.forma.sample…` in the sample). If you fight the matrix, usually the **type** is wrong — not the tool. diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index ae624d9b..49790a6d 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -2,6 +2,24 @@ Newest entries first. +## 2026-07-16 — F-060/061/062: deprecate androidLibrary, flatten sample + examples + +- **Tickets:** F-060, F-061, F-062 → `done`; F-063 hard-remove remains `todo` +- **Branch:** `forma/F-060-deprecate-androidLibrary` +- **Product direction (user):** `androidLibrary` is a temporary hack; Forma should protect a **flat, role-typed** structure — reimplement examples without it. +- **Code:** + - `@Deprecated` on `Project.androidLibrary` with replacement guidance + - `androidRes` now returns `TargetBuilder` (safe-args / plugins parity) + - Matrix: `viewBinding` → `ui-library`; `androidUtil` → JVM `library` + - Sample renames: `core/di/android-util`, `core/mvvm/ui-library`, `core/navigation/res` + - Example 04: `core/platform/android-util` (no `androidLibrary`) +- **Docs:** `docs/ANDROID-LIBRARY-DEPRECATION.md`, VISION flat-structure principle, DEPENDENCY-MATRIX, README, GETTING-STARTED, SAMPLE-APP, ARCHITECTURE, COMPOSE, DEPS-CATALOG, progressive examples + agent skills +- **Verify (real host):** + - `plugins/ ./gradlew build` → **BUILD SUCCESSFUL** + - `application/ ./gradlew build` → **BUILD SUCCESSFUL** (2157 tasks) + - `examples/android/04-shared-libs ./gradlew :binary:assembleDebug` → **BUILD SUCCESSFUL** +- **Next:** F-063 hard-remove when ready; optional package renames off historical `.library` segments + ## 2026-07-15 — 4h worker: empty queue + CI re-run - **Ticket:** none — `TICKETS.md` all `done` through F-050 (P0–P5 complete) diff --git a/docs/PROGRESSIVE-EXAMPLES.md b/docs/PROGRESSIVE-EXAMPLES.md index a6c02fc2..d3365541 100644 --- a/docs/PROGRESSIVE-EXAMPLES.md +++ b/docs/PROGRESSIVE-EXAMPLES.md @@ -23,7 +23,7 @@ narrative tutorials when you want a **minimal buildable project per concept**. | `api` / `impl` | 02 | 02 | platform skills + matrix | | `viewBinding` | 03 | — | forma-android-targets | | `library` / `util` | 04 | 03 | platform skills | -| `androidUtil` / `androidLibrary` | 04 | — | forma-android-targets | +| `androidUtil` (not deprecated `androidLibrary`) | 04 | — | forma-android-targets | | `widget` / `uiLibrary` | 05 | — | forma-android-targets | | Compose + `composeWidget` | 06 | — | forma-compose | | Multi-feature composition | 07 | 04 | forma-project-layout | diff --git a/docs/SAMPLE-APP.md b/docs/SAMPLE-APP.md index 57813f27..aa65e22b 100644 --- a/docs/SAMPLE-APP.md +++ b/docs/SAMPLE-APP.md @@ -16,9 +16,9 @@ application/ ├── root-res/ androidRes (launcher, app nav host layout) ├── toggle-widget/ widget (theme toggle action view) ├── core/ -│ ├── di/library androidLibrary (Dagger scopes / base component) -│ ├── mvvm/library androidLibrary (ViewModel helpers, adapters) -│ ├── navigation/library androidLibrary (feature nav graphs + safe-args) +│ ├── di/android-util androidUtil (Dagger scopes / base component) +│ ├── mvvm/ui-library uiLibrary (ViewModel helpers, adapters) +│ ├── navigation/res androidRes (feature nav graphs + safe-args) │ ├── network/library library (JVM) (Retrofit / Config / NetworkState) │ └── theme/{android-util,res} ├── common/ @@ -96,7 +96,7 @@ root-app hosts SampleApp DI: home/impl hosts bottom navigation: navigation_characters_list_graph + navigation_character_favorite_graph - (from core/navigation/library) + (from core/navigation/res) ``` ## Build diff --git a/docs/VISION.md b/docs/VISION.md index 6e5706b8..213c9078 100644 --- a/docs/VISION.md +++ b/docs/VISION.md @@ -4,6 +4,21 @@ Make **Forma** a working product for **Android** first, then adapt the same approach for **JVM applications**, then for **Bazel**. +## Core product principle — keep structure flat + +Forma exists to **protect a flat, role-typed module graph**. Each target type is a +**role** (`api`, `impl`, `androidRes`, `viewBinding`, `widget`, `androidUtil`, +`uiLibrary`, composition roots, …), not a vague “Android library” bucket. + +- **Prefer the most specific target** that matches the module’s job. +- **Composition of features** happens only at `androidApp` / `androidBinary` + (and JVM `binary`) — never by stacking feature `impl`s on each other. +- **Generic escape hatches fight the product.** `androidLibrary` is **deprecated** + (F-060+): it collides with JVM `library` on the `library` suffix and invites + mixed DI/UI/res dumps that the matrix cannot reason about. Migrate to + role-specific targets (see [`GETTING-STARTED.md`](GETTING-STARTED.md) cheat + sheet and F-060 notes in `PROGRESS.md`). + ## Architectural split ### `forma-core` (framework) @@ -21,7 +36,10 @@ The portable core for: Concrete stacks built **on** forma-core: -1. **Android** — full target set (`api`, `impl`, `androidLibrary`, `androidBinary`, …) with strict dependency matrix based on the current Forma implementation; Dagger2-friendly `api`/`impl` conventions; external deps catalog patterns. +1. **Android** — role-typed target set (`api`, `impl`, `androidUtil`, `uiLibrary`, + `androidRes`, `viewBinding`, `androidBinary`, …) with a strict dependency + matrix; Dagger2-friendly `api`/`impl` conventions; external deps catalog + patterns. (`androidLibrary` deprecated — do not teach it as the default.) 2. **JVM** — pure JVM targets and samples reusing the same restriction model. 3. **Bazel** — later adapter that reuses forma-core concepts (types + restrictions) rather than forking the rules engine. @@ -31,6 +49,7 @@ Concrete stacks built **on** forma-core: 2. **Extract forma-core** — peel framework concerns out of the Android plugin while Android keeps working. 3. **JVM implementation** — prove core is portable. 4. **Bazel** — design + implement adapter once core is stable (see [`docs/BAZEL-ADAPTER.md`](BAZEL-ADAPTER.md) for the F-040 mapping design). +5. **Flat-structure hardening** — deprecate/remove generic targets that undermine role typing (P6 / F-060+). ## Working principles diff --git a/examples/README.md b/examples/README.md index 4bdec768..1833c368 100644 --- a/examples/README.md +++ b/examples/README.md @@ -30,7 +30,7 @@ Gold-standard product apps remain [`application/`](../application/) (Android) an | 01 | `android/01-hello-apk` | `androidProjectConfiguration`, `androidBinary`, `androidApp`, `androidRes` | | 02 | `android/02-feature-api-impl` | `api`, `impl` | | 03 | `android/03-res-viewbinding` | feature `androidRes`, `viewBinding` | -| 04 | `android/04-shared-libs` | `library`, `util`, `androidUtil`, `androidLibrary` | +| 04 | `android/04-shared-libs` | `library`, `util`, `androidUtil` (no deprecated `androidLibrary`) | | 05 | `android/05-widget-ui` | `widget`, `uiLibrary` | | 06 | `android/06-compose` | `compose` flags, `composeWidget` | | 07 | `android/07-multi-feature` | two features at composition root | diff --git a/examples/agent-skills/forma-android-targets.md b/examples/agent-skills/forma-android-targets.md index 22c8fc5e..a7bf6bcb 100644 --- a/examples/agent-skills/forma-android-targets.md +++ b/examples/agent-skills/forma-android-targets.md @@ -18,7 +18,7 @@ Plugin: `tools.forma.android`. Register happens on first DSL use via `AndroidTar | `library` | `library` | pure JVM library | 04 | | `util` | `util` | no res/ | 04 | | `androidUtil` | `android-util` | no res/ | 04 | -| `androidLibrary` | `library` | Android lib; **no** `impl` deps | 04 | +| ~~`androidLibrary`~~ | `library` | **Deprecated** — do not use | — | | `widget` | `widget` | Custom View | 05 | | `uiLibrary` | `ui-library` | shared UI blocks | 05 | | `composeWidget` | `compose-widget` | always Compose | 06 | @@ -37,7 +37,7 @@ impl(packageName = "…", dependencies = deps(target(":feature:x:api"))) viewBinding(packageName = "…", dependencies = deps(target(":feature:x:res"))) widget(packageName = "…") composeWidget(packageName = "…", dependencies = deps(/* compose GAVs */)) -androidLibrary(packageName = "…") +// androidLibrary(…) // DEPRECATED — use androidUtil / uiLibrary / androidRes / … androidUtil(packageName = "…") util(packageName = "…") library(packageName = "…") @@ -50,6 +50,6 @@ androidTestUtil(packageName = "…") - `impl` → `impl` - `api` shipping `res/` -- `androidLibrary` → `impl` +- deprecated `androidLibrary` (use role-specific targets) - Wrong suffix for DSL - Relying on transitive feature wiring instead of listing api+impl on roots diff --git a/examples/agent-skills/forma-dependency-matrix.md b/examples/agent-skills/forma-dependency-matrix.md index 5a043ffb..de7474a1 100644 --- a/examples/agent-skills/forma-dependency-matrix.md +++ b/examples/agent-skills/forma-dependency-matrix.md @@ -14,7 +14,9 @@ description: Project-dep allowlists and how to fix validator failures. | `api` | `api`, `library` | | `impl` | api, utils, library, ui-library, res, viewbinding, widget, compose-widget — **not** other `impl` | | `androidApp` / `androidBinary` | api, impl, shared UI/libs (composition roots) | -| `androidLibrary` | library, utils, res, api — **not** impl | +| ~~`androidLibrary`~~ | **deprecated** — prefer androidUtil/uiLibrary/androidRes | +| `androidUtil` | android-util, test-util, res, **library** (JVM) | +| `uiLibrary` | widget, compose-widget, util, android-util, res | ## Fixing failures diff --git a/examples/android/03-res-viewbinding/README.md b/examples/android/03-res-viewbinding/README.md index 54c9fc44..702be3fa 100644 --- a/examples/android/03-res-viewbinding/README.md +++ b/examples/android/03-res-viewbinding/README.md @@ -15,4 +15,4 @@ printf 'sdk.dir=%s\n' "$ANDROID_HOME" > local.properties ``` ## Next -04 adds shared `library` / `util` / `androidUtil` / `androidLibrary`. +04 adds shared `library` / `util` / `androidUtil` (no deprecated `androidLibrary`). diff --git a/examples/android/04-shared-libs/README.md b/examples/android/04-shared-libs/README.md index 40147a38..7e3890cb 100644 --- a/examples/android/04-shared-libs/README.md +++ b/examples/android/04-shared-libs/README.md @@ -1,12 +1,18 @@ # 04 — shared-libs (Android) -**Goal:** Shared pure-JVM and Android libraries outside features. +**Goal:** Shared pure-JVM and Android libraries outside features — **role-typed**, no generic `androidLibrary`. ## Features introduced - `library` (JVM) - `util` (JVM helpers) - `androidUtil` (Android helpers, no res/) -- `androidLibrary` (shared Android library — **cannot** depend on `impl`) +- (intentionally **no** `androidLibrary` — deprecated; see `docs/ANDROID-LIBRARY-DEPRECATION.md`) + +## Layout +- `common/library` — pure JVM +- `common/util` — JVM helpers +- `common/android-util` — Android helpers +- `core/platform/android-util` — platform Android util depending on JVM `library` + `android-util` ## Build ```bash diff --git a/examples/android/04-shared-libs/core/platform/library/build.gradle.kts b/examples/android/04-shared-libs/core/platform/android-util/build.gradle.kts similarity index 92% rename from examples/android/04-shared-libs/core/platform/library/build.gradle.kts rename to examples/android/04-shared-libs/core/platform/android-util/build.gradle.kts index 9e7a7fe5..9440a4aa 100644 --- a/examples/android/04-shared-libs/core/platform/library/build.gradle.kts +++ b/examples/android/04-shared-libs/core/platform/android-util/build.gradle.kts @@ -1,4 +1,4 @@ -androidLibrary( +androidUtil( packageName = "tools.forma.examples.android.shared.core.platform.library", dependencies = deps( target(":common:library"), diff --git a/examples/android/04-shared-libs/core/platform/library/src/main/AndroidManifest.xml b/examples/android/04-shared-libs/core/platform/android-util/src/main/AndroidManifest.xml similarity index 100% rename from examples/android/04-shared-libs/core/platform/library/src/main/AndroidManifest.xml rename to examples/android/04-shared-libs/core/platform/android-util/src/main/AndroidManifest.xml diff --git a/examples/android/04-shared-libs/core/platform/library/src/main/kotlin/tools/forma/examples/android/shared/core/platform/library/PlatformHello.kt b/examples/android/04-shared-libs/core/platform/android-util/src/main/kotlin/tools/forma/examples/android/shared/core/platform/library/PlatformHello.kt similarity index 100% rename from examples/android/04-shared-libs/core/platform/library/src/main/kotlin/tools/forma/examples/android/shared/core/platform/library/PlatformHello.kt rename to examples/android/04-shared-libs/core/platform/android-util/src/main/kotlin/tools/forma/examples/android/shared/core/platform/library/PlatformHello.kt diff --git a/examples/android/04-shared-libs/feature/hello/impl/build.gradle.kts b/examples/android/04-shared-libs/feature/hello/impl/build.gradle.kts index 81e5a45c..de7acb6b 100644 --- a/examples/android/04-shared-libs/feature/hello/impl/build.gradle.kts +++ b/examples/android/04-shared-libs/feature/hello/impl/build.gradle.kts @@ -5,6 +5,6 @@ impl( target(":common:library"), target(":common:util"), target(":common:android-util"), - target(":core:platform:library") + target(":core:platform:android-util") ) ) diff --git a/examples/android/04-shared-libs/root-app/build.gradle.kts b/examples/android/04-shared-libs/root-app/build.gradle.kts index a499d3e5..125d2b73 100644 --- a/examples/android/04-shared-libs/root-app/build.gradle.kts +++ b/examples/android/04-shared-libs/root-app/build.gradle.kts @@ -5,6 +5,6 @@ androidApp( target(":feature:hello:api"), target(":feature:hello:impl"), target(":common:library"), - target(":core:platform:library") + target(":core:platform:android-util") ) ) diff --git a/plugins/android/README.md b/plugins/android/README.md index b8d3de9e..243ff8d5 100644 --- a/plugins/android/README.md +++ b/plugins/android/README.md @@ -10,10 +10,11 @@ The androidApp target is used to configure Android applications. It contains cod An androidApp cannot depend on androidBinary. The app target should only provide high-level application configurations, components, and resources. Depending on androidBinary could tightly couple the app code to distribution details like signing, ProGuard, versioning etc. Isolating androidApp allows changing distribution methods without impacting Application code. -### androidLibrary -The androidLibrary target defines reusable components and code modules for Android. It contains business logic, data layers, UI widgets, custom views, etc that can be consumed by multiple androidApps or other libraries. - -An androidLibrary is allowed to depend on other androidLibraries, androidWidgets, and androidUtils. This allows building up a graph of modular, decoupled libraries with clean APIs. However, androidLibrary cannot depend directly on androidApp or androidBinary targets. This prevents libraries from making assumptions about higher level application code, or leaking implementation details through the public API. Keeping the library layer decoupled maximizes reusability across different apps. +### androidLibrary (deprecated) +**Do not use for new modules.** Generic AGP-library escape hatch that undermines +Forma's flat, role-typed graph. Prefer `androidUtil`, `uiLibrary`, `androidRes`, +`viewBinding`, `widget`, or `impl`. See `docs/ANDROID-LIBRARY-DEPRECATION.md`. +Still present until hard-removal (F-063). ### androidWidget The androidWidget target provides reusable UI components that can be shared between apps. These contain layout XML, drawables, custom views, composables, etc. that abstract UI elements away from app code. @@ -43,7 +44,7 @@ An impl can only depend on its own api target. This enforces a 1:1 relationship ### androidUtil The androidUtil target provides Android-specific utilities and platform extensions. These contain code to wrap Android SDK APIs, system services, device integrations, aar libraries etc. -androidUtils can be used by androidLibrary, androidWidget, androidApp, impl. +androidUtils can be used by uiLibrary, widget, androidApp, impl. ### util The util target provides general-purpose utility code not specific to Android. This includes data types, helpers, extensions, algorithms, etc. Usable across multiple platforms. diff --git a/plugins/android/src/main/java/androidLibrary.kt b/plugins/android/src/main/java/androidLibrary.kt index 1c0c1107..3275dee9 100644 --- a/plugins/android/src/main/java/androidLibrary.kt +++ b/plugins/android/src/main/java/androidLibrary.kt @@ -18,13 +18,25 @@ import tools.forma.validation.asValidator import tools.forma.validation.validate /** - * Shared Android library (not a feature [impl]). + * **Deprecated.** Generic Android library target — a temporary escape hatch that + * flattens Forma's typed structure back into "just an Android library". * - * Dagger-friendly: may depend on other libraries/utils/res and feature [api] - * contracts, but **not** on [impl] (implementations are composed only at - * [androidApp] / [androidBinary]). Also disallows widgets/viewbinding as - * project deps (use dedicated targets). + * Prefer a **role-specific** target so the dependency matrix can protect a flat + * graph: + * - shared Android helpers (no `res/`) → [androidUtil] + * - shared UI bases for impl/widget → [uiLibrary] + * - resources only → [androidRes] + * - layouts only → [viewBinding] + * - feature UI/DI → [impl] + * - pure JVM → [library] + * + * Remains functional until hard-removal (F-063) so existing consumers can migrate. */ +@Deprecated( + message = "androidLibrary is a temporary generic escape hatch. Use androidUtil, " + + "uiLibrary, androidRes, viewBinding, impl, or library so Forma can keep structure flat.", + replaceWith = ReplaceWith("androidUtil(packageName, owner, visibility, dependencies, testDependencies, compose)") +) fun Project.androidLibrary( packageName: String, owner: Owner = NoOwner, diff --git a/plugins/android/src/main/java/androidRes.kt b/plugins/android/src/main/java/androidRes.kt index b3a428f6..cfaa5921 100644 --- a/plugins/android/src/main/java/androidRes.kt +++ b/plugins/android/src/main/java/androidRes.kt @@ -22,7 +22,7 @@ fun Project.androidRes( visibility: Visibility = Public, dependencies: FormaDependency = emptyDependency(), manifestPlaceholders: Map = emptyMap() -) { +): TargetBuilder { onlyAllowResources() @@ -40,4 +40,6 @@ fun Project.androidRes( validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.res).asValidator(), dependencies = dependencies ) + + return TargetBuilder(this) } diff --git a/plugins/android/src/main/java/tools/forma/android/restriction/AndroidRestrictions.kt b/plugins/android/src/main/java/tools/forma/android/restriction/AndroidRestrictions.kt index e7779961..7c4214c2 100644 --- a/plugins/android/src/main/java/tools/forma/android/restriction/AndroidRestrictions.kt +++ b/plugins/android/src/main/java/tools/forma/android/restriction/AndroidRestrictions.kt @@ -66,8 +66,8 @@ object AndroidRestrictionKit { // util (JVM): util + library graph.allow(t.util, t.util, t.library) - // androidUtil - graph.allow(t.androidUtil, t.androidUtil, t.testUtil, t.res) + // androidUtil — may depend on pure JVM library (helpers wrapping shared code) + graph.allow(t.androidUtil, t.androidUtil, t.testUtil, t.res, t.library) // testUtil graph.allow(t.testUtil, t.testUtil, t.util) @@ -84,8 +84,11 @@ object AndroidRestrictionKit { // composeWidget (symmetric with widget for coexistence) graph.allow(t.composeWidget, t.uiLibrary, t.composeWidget, t.widget, t.util, t.androidUtil, t.res) - // viewBinding - graph.allow(t.viewBinding, t.api, t.widget, t.composeWidget, t.res, t.library, t.androidUtil) + // viewBinding — ui-library allowed so shared UI bases are not stuffed into androidLibrary + graph.allow( + t.viewBinding, + t.api, t.widget, t.composeWidget, t.res, t.library, t.androidUtil, t.uiLibrary + ) // androidApp (composition root) graph.allow( diff --git a/plugins/android/src/main/java/tools/forma/android/target/AndroidTargetRegistry.kt b/plugins/android/src/main/java/tools/forma/android/target/AndroidTargetRegistry.kt index 420291ac..ad5ef0f0 100644 --- a/plugins/android/src/main/java/tools/forma/android/target/AndroidTargetRegistry.kt +++ b/plugins/android/src/main/java/tools/forma/android/target/AndroidTargetRegistry.kt @@ -82,11 +82,11 @@ fun registerAndroidDefaults(registry: TargetRegistry = AndroidTargetRegistry) { ) ) - // androidUtil + // androidUtil — may wrap pure JVM libraries; still no res content registry.register( TargetRegistration( type = t.androidUtil, - allowedDependencies = setOf(t.androidUtil, t.testUtil, t.res), + allowedDependencies = setOf(t.androidUtil, t.testUtil, t.res, t.library), contentRules = noRes ) ) @@ -133,11 +133,13 @@ fun registerAndroidDefaults(registry: TargetRegistry = AndroidTargetRegistry) { ) ) - // viewBinding + // viewBinding — may use shared UI bases (ui-library) after androidLibrary deprecation registry.register( TargetRegistration( type = t.viewBinding, - allowedDependencies = setOf(t.api, t.widget, t.composeWidget, t.res, t.library, t.androidUtil), + allowedDependencies = setOf( + t.api, t.widget, t.composeWidget, t.res, t.library, t.androidUtil, t.uiLibrary + ), contentRules = onlyLayouts ) )