diff --git a/README.md b/README.md
index a729aa8..5ee60a2 100644
--- a/README.md
+++ b/README.md
@@ -198,6 +198,7 @@ Configuration-time performance: **F-017**
forma-core API design: **F-020** ([`docs/forma-core-api.md`](docs/forma-core-api.md)).
forma-core extraction (restriction engine + TargetType + Android matrix kit): **F-021** done (`plugins/core`).
forma-core validation SPI + content rules (Gradle facade in `:validation`): **F-022** done.
+forma-core TargetRegistry + Android DSL consumers: **F-023** done.
Icons made by Freepik
from www.flaticon.com
diff --git a/TICKETS.md b/TICKETS.md
index b310511..166434b 100644
--- a/TICKETS.md
+++ b/TICKETS.md
@@ -33,7 +33,7 @@ Update this file when picking or finishing work. Cron workers must pick the **hi
| F-020 | done | Design forma-core public API (types, restrictions, validation, target registry) | `docs/forma-core-api.md` — types, restriction graph, validator SPI, registry, coords, library-suffix decision |
| F-021 | done | Extract dependency-type / restriction engine into `forma-core` | `plugins/core` + TargetType/NameMatcher/RestrictionGraph + AndroidTargetTypes + AndroidRestrictionKit (distinct jvm.library vs android.library); facades preserved. Related GH #39 |
| F-022 | done | Extract validation framework into `forma-core` | `plugins/core` TargetValidator + ContentRule; `:validation` facade; Android helpers call pure rules |
-| F-023 | todo | Wire Android implementation as first consumer of forma-core | Sample still builds |
+| F-023 | done | Wire Android implementation as first consumer of forma-core | TargetRegistry + AndroidTargetRegistry; DSL uses registry validators; sample green |
| F-024 | todo | Publish/coordinate coordinates: `tools.forma:core` vs android plugins | |
## P3 — JVM applications
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 704a359..cb55d96 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -308,14 +308,14 @@ Aligned with `docs/VISION.md`: core must not assume Android/AGP/Dagger.
| Concern | Current home | forma-core? | Notes |
|---------|--------------|-------------|-------|
-| Target type identity (`TargetTemplate` / suffix) | `:target` + `:core` (F-021) | **Yes** | Parallel `TargetType` in core; templates kept for compat. Registry in F-023. |
+| Target type identity (`TargetTemplate` / suffix) | `:target` + `:core` (F-021) | **Yes** | Parallel `TargetType` in core; templates kept for compat. **Registry F-023 done.** |
| Name + dep-type `Validator` | `:core` + `:validation` facade (F-022) | **Yes** | Core `TargetValidator` + factories; legacy API re-exports |
| Content validators (`onlyAllowResources`, …) | core predicates + `:android` helpers (F-022) | **Split** | Pure `ContentRule` in core; Gradle listing + Android helpers in platform |
| Dependency model + apply | `:deps` | **Mostly yes** | Strip AGP-ish config features; catalog generators may stay tooling |
| Settings store | `:config` | **Split** | Generic `SettingsStore` / plugin registry → core; `AndroidProjectSettings` → android |
| Owners | `:owners` | **Optional / yes** | Platform-agnostic metadata |
| Feature definitions (AGP library/binary/native) | `:android` | **No** | Stay platform |
-| DSL entrypoints (`api`/`impl`/…) | `:android` | **No** (register *onto* core) | First consumer of core (F-023) |
+| DSL entrypoints (`api`/`impl`/…) | `:android` | **No** (register *onto* core) | First consumer of core — **F-023 done** (`AndroidTargetRegistry`) |
| Includer / depgen | separate builds | **No** | Adjacent tooling |
| build-dependencies catalogs | sample support | **No** | Demo-only; pattern informs F-012 |
@@ -326,7 +326,8 @@ Suggested extraction order (tickets F-020…F-024):
`library` suffix decision).
2. Create `plugins/core` + TargetType + RestrictionGraph + wire Android matrix from DEPENDENCY-MATRIX — **done (F-021)**.
3. Validation SPI + content predicates in core; `:validation` thin facade — **done (F-022)**.
-4. Wire Android DSL / registry as first consumer of core (F-023); sample stays green.
+4. Wire Android DSL / registry as first consumer of core — **done (F-023)**
+ (`TargetRegistry` + `AndroidTargetRegistry`; sample green).
5. Coordinates: e.g. `tools.forma:core` vs `tools.forma.android` (F-024).
---
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index ea7302e..6ea570b 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -2,6 +2,31 @@
Newest entries first.
+## 2026-07-13 — F-023 Wire Android as first consumer of forma-core
+
+- **Ticket:** F-023 → `done`
+- **Branch:** `forma/F-023-android-registry` (from `origin/v2`)
+- **Skills/modes:** Grok Build `--mode full` (design plan) then `--mode implement` (hit max-turns twice; tree completed by Hermes verify/docs)
+- **Core (`tools.forma.core.target`):**
+ - `TargetRegistration`, `TargetRegistry`, `DefaultTargetRegistry`
+ - `validatorFor` / `selfValidator` via core factories + registry-level caches
+ - `restrictionGraph()` rebuilt from registrations (replace semantics)
+ - Unit tests: `TargetRegistryTest` (9 tests, all green)
+- **Android consumer:**
+ - `AndroidTargetRegistry` + `registerAndroidDefaults()` (matrix + content-rule metadata)
+ - `androidProjectConfiguration` initializes registry after `Forma.store`
+ - All DSL entrypoints (`api`/`impl`/`library`/`androidLibrary`/…/app/binary/native) use registry validators
+ - **jvm.library** vs **android.library** distinguished for `library()` vs `androidLibrary()`
+- **Facade:** `TargetValidator.asValidator()` → legacy `Validator` / `ProjectValidationError`
+- **Docs:** forma-core-api checklist + status; ARCHITECTURE §6; README Progress; TICKETS
+- **Verify (OpenJDK 17 + env-mac.sh):**
+ - `plugins/`: `./gradlew :core:test` → **BUILD SUCCESSFUL** (`TargetRegistryTest` 9/9)
+ - `plugins/`: `./gradlew build` → **BUILD SUCCESSFUL** (68 tasks)
+ - `application/`: `./gradlew :binary:assembleDebug` → **BUILD SUCCESSFUL** (578 tasks)
+- **Commits/PRs:** this run — push + PR base `v2`
+- **Blockers:** none (Grok implement max-turns; code/docs finished under Hermes)
+- **Next step:** F-024 Publish/coordinate coordinates (`tools.forma:core` vs android plugins)
+
## 2026-07-13 — F-022 Extract validation framework into forma-core
- **Ticket:** F-022 → `done`
diff --git a/docs/forma-core-api.md b/docs/forma-core-api.md
index 61ca416..f5b03bb 100644
--- a/docs/forma-core-api.md
+++ b/docs/forma-core-api.md
@@ -6,8 +6,9 @@ plugins under `plugins/` and in [`ARCHITECTURE.md`](ARCHITECTURE.md) §6 +
**Status:** accepted design. **Implemented so far:** F-021 (types +
restriction graph + Android kit), **F-022** (validation SPI + content
-predicates in `plugins/core`; `:validation` is a Gradle facade). Registry /
-DSL consumption remains **F-023**.
+predicates in `plugins/core`; `:validation` is a Gradle facade), **F-023**
+(`TargetRegistry` + Android DSL consumers via `AndroidTargetRegistry`).
+Publishing coordinates remain **F-024**.
---
@@ -403,7 +404,7 @@ Bazel adapter (later): same registry + restriction graph; replace
| **F-020** (this doc) | Public API design only |
| **F-021** | Create `plugins/core` (or move packages); target types + **restriction graph** + wire Android matrix data; keep binary/API facades so sample builds |
| **F-022** | **Done:** validation SPI, identity-cached factories, content predicates in `plugins/core`; Android helpers call core; `:validation` facade |
-| **F-023** | Android DSL uses registry; delete duplicated allow-lists from individual `*.kt` entrypoints where safe; sample green |
+| **F-023** | **Done:** Android DSL uses registry; duplicated allow-lists removed from entrypoints; sample green |
| **F-024** | Publishing coordinates, README/Portal metadata, deprecate old plugin jars if merged |
Do not skip to JVM targets (F-030) until F-023 is done.
@@ -415,7 +416,7 @@ Do not skip to JVM targets (F-030) until F-023 is done.
**Must ship in forma-core v1**
- [x] `TargetType`, `TargetRef`, `NameMatcher` / `SuffixNameMatcher` (F-021)
-- [ ] `TargetRegistry` + `TargetRegistration` + `DefaultTargetRegistry` (F-023)
+- [x] `TargetRegistry` + `TargetRegistration` + `DefaultTargetRegistry` (F-023)
- [x] `RestrictionGraph` / `RestrictionRule` / `EdgeKind` (F-021)
- [x] `TargetValidator`, `AcceptAny`, `dependencyTypeValidator`, `selfTypeValidator` (F-022)
- [x] Content rule interfaces + `NoResourcesUnderMain` / `OnlyResourcesUnderMain` / `OnlyLayoutResources` (F-022)
diff --git a/plugins/android/src/main/java/androidApp.kt b/plugins/android/src/main/java/androidApp.kt
index 32d5bb7..cc9ecf7 100644
--- a/plugins/android/src/main/java/androidApp.kt
+++ b/plugins/android/src/main/java/androidApp.kt
@@ -1,32 +1,22 @@
+import org.gradle.api.Project
import tools.forma.android.feature.AndroidLibraryFeatureConfiguration
import tools.forma.android.feature.androidLibraryFeatureDefinition
import tools.forma.android.feature.applyFeatures
+import tools.forma.android.feature.kaptConfigurationFeature
import tools.forma.android.feature.kotlinAndroidFeatureDefinition
-import tools.forma.android.target.AndroidUtilTargetTemplate
-import tools.forma.android.target.ApiTargetTemplate
-import tools.forma.android.target.ApplicationTargetTemplate
-import tools.forma.android.target.ImplTargetTemplate
-import tools.forma.android.target.LibraryTargetTemplate
-import tools.forma.android.target.ResourcesTargetTemplate
-import tools.forma.android.target.TestUtilTargetTemplate
-import tools.forma.android.target.UiLibraryTargetTemplate
-import tools.forma.android.target.UtilTargetTemplate
-import tools.forma.android.target.ViewBindingTargetTemplate
-import tools.forma.android.target.WidgetTargetTemplate
-import tools.forma.android.target.ComposeWidgetTargetTemplate
-import tools.forma.owners.NoOwner
-import tools.forma.owners.Owner
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
import tools.forma.android.utils.BuildConfiguration
import tools.forma.android.validation.disallowResources
-import tools.forma.validation.validate
-import tools.forma.validation.validator
import tools.forma.android.visibility.Public
import tools.forma.android.visibility.Visibility
import tools.forma.deps.core.FormaDependency
import tools.forma.deps.core.NamedDependency
import tools.forma.deps.core.applyDependencies
-import org.gradle.api.Project
-import tools.forma.android.feature.kaptConfigurationFeature
+import tools.forma.owners.NoOwner
+import tools.forma.owners.Owner
+import tools.forma.validation.asValidator
+import tools.forma.validation.validate
/**
* Root Android application library (feature composition, not the APK entry).
@@ -52,13 +42,15 @@ fun Project.androidApp(
disallowResources()
- target.validate(ApplicationTargetTemplate)
+ val selfV = AndroidTargetRegistry.selfValidator(AndroidTargetTypes.app).asValidator()
+ selfV.validate(target)
val libraryFeatureConfiguration = AndroidLibraryFeatureConfiguration(
packageName,
buildConfiguration,
testInstrumentationRunner,
consumerMinificationFiles,
manifestPlaceholders,
+ selfValidator = selfV,
compose = compose,
)
applyFeatures(
@@ -67,19 +59,7 @@ fun Project.androidApp(
)
applyDependencies(
- validator = validator(
- ApiTargetTemplate,
- ImplTargetTemplate,
- LibraryTargetTemplate,
- UtilTargetTemplate,
- AndroidUtilTargetTemplate,
- TestUtilTargetTemplate,
- ResourcesTargetTemplate,
- ViewBindingTargetTemplate,
- WidgetTargetTemplate,
- ComposeWidgetTargetTemplate,
- UiLibraryTargetTemplate,
- ),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.app).asValidator(),
dependencies = dependencies,
testDependencies = testDependencies,
androidTestDependencies = androidTestDependencies,
diff --git a/plugins/android/src/main/java/androidBinary.kt b/plugins/android/src/main/java/androidBinary.kt
index f2b9413..3fa33b7 100644
--- a/plugins/android/src/main/java/androidBinary.kt
+++ b/plugins/android/src/main/java/androidBinary.kt
@@ -2,27 +2,16 @@ import org.gradle.api.Project
import tools.forma.android.feature.AndroidBinaryFeatureConfiguration
import tools.forma.android.feature.androidBinaryFeatureDefinition
import tools.forma.android.feature.applyFeatures
-import tools.forma.owners.NoOwner
-import tools.forma.owners.Owner
-import tools.forma.android.target.AndroidUtilTargetTemplate
-import tools.forma.android.target.ApiTargetTemplate
-import tools.forma.android.target.ApplicationTargetTemplate
-import tools.forma.android.target.BinaryTargetTemplate
-import tools.forma.android.target.ImplTargetTemplate
-import tools.forma.android.target.LibraryTargetTemplate
-import tools.forma.android.target.ResourcesTargetTemplate
-import tools.forma.android.target.TestUtilTargetTemplate
-import tools.forma.android.target.UiLibraryTargetTemplate
-import tools.forma.android.target.UtilTargetTemplate
-import tools.forma.android.target.ViewBindingTargetTemplate
-import tools.forma.android.target.WidgetTargetTemplate
-import tools.forma.android.target.ComposeWidgetTargetTemplate
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
import tools.forma.android.utils.BuildConfiguration
import tools.forma.android.validation.disallowResources
import tools.forma.deps.core.FormaDependency
import tools.forma.deps.core.applyDependencies
+import tools.forma.owners.NoOwner
+import tools.forma.owners.Owner
+import tools.forma.validation.asValidator
import tools.forma.validation.validate
-import tools.forma.validation.validator
/**
* Android Binary target — application entry point (single APK).
@@ -55,8 +44,8 @@ fun Project.androidBinary(
disallowResources()
- target.validate(BinaryTargetTemplate)
-
+ val selfV = AndroidTargetRegistry.selfValidator(AndroidTargetTypes.binary).asValidator()
+ selfV.validate(target)
val binaryFeatureConfiguration = AndroidBinaryFeatureConfiguration(
packageName,
versionCode,
@@ -65,6 +54,7 @@ fun Project.androidBinary(
testInstrumentationRunner,
consumerMinificationFiles,
manifestPlaceholders,
+ selfValidator = selfV,
compose = compose,
)
applyFeatures(
@@ -72,20 +62,7 @@ fun Project.androidBinary(
)
applyDependencies(
- validator = validator(
- ApplicationTargetTemplate,
- ApiTargetTemplate,
- ImplTargetTemplate,
- LibraryTargetTemplate,
- UtilTargetTemplate,
- AndroidUtilTargetTemplate,
- TestUtilTargetTemplate,
- ResourcesTargetTemplate,
- ViewBindingTargetTemplate,
- WidgetTargetTemplate,
- ComposeWidgetTargetTemplate,
- UiLibraryTargetTemplate,
- ),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.binary).asValidator(),
dependencies = dependencies
)
diff --git a/plugins/android/src/main/java/androidLibrary.kt b/plugins/android/src/main/java/androidLibrary.kt
index 240983e..1c0c110 100644
--- a/plugins/android/src/main/java/androidLibrary.kt
+++ b/plugins/android/src/main/java/androidLibrary.kt
@@ -4,22 +4,18 @@ import tools.forma.android.feature.androidLibraryFeatureDefinition
import tools.forma.android.feature.applyFeatures
import tools.forma.android.feature.kaptConfigurationFeature
import tools.forma.android.feature.kotlinAndroidFeatureDefinition
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
import tools.forma.owners.NoOwner
import tools.forma.owners.Owner
-import tools.forma.android.target.AndroidUtilTargetTemplate
-import tools.forma.android.target.ApiTargetTemplate
-import tools.forma.android.target.LibraryTargetTemplate
-import tools.forma.android.target.ResourcesTargetTemplate
-import tools.forma.android.target.TestUtilTargetTemplate
-import tools.forma.android.target.UtilTargetTemplate
import tools.forma.android.utils.BuildConfiguration
import tools.forma.android.visibility.Public
import tools.forma.android.visibility.Visibility
import tools.forma.deps.core.FormaDependency
import tools.forma.deps.core.NamedDependency
import tools.forma.deps.core.applyDependencies
+import tools.forma.validation.asValidator
import tools.forma.validation.validate
-import tools.forma.validation.validator
/**
* Shared Android library (not a feature [impl]).
@@ -43,7 +39,7 @@ fun Project.androidLibrary(
/** Enable Jetpack Compose; defaults to project-wide `compose` setting. */
compose: Boolean = Forma.settings.compose,
): TargetBuilder {
- target.validate(LibraryTargetTemplate)
+ AndroidTargetRegistry.selfValidator(AndroidTargetTypes.library).asValidator().validate(target)
val libraryFeatureConfiguration = AndroidLibraryFeatureConfiguration(
packageName,
buildConfiguration,
@@ -58,14 +54,7 @@ fun Project.androidLibrary(
)
applyDependencies(
- validator = validator(
- LibraryTargetTemplate,
- UtilTargetTemplate,
- AndroidUtilTargetTemplate,
- TestUtilTargetTemplate,
- ResourcesTargetTemplate,
- ApiTargetTemplate,
- ),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.library).asValidator(),
dependencies = dependencies,
testDependencies = testDependencies,
androidTestDependencies = androidTestDependencies,
diff --git a/plugins/android/src/main/java/androidNative.kt b/plugins/android/src/main/java/androidNative.kt
index f6d8cfb..9877f76 100644
--- a/plugins/android/src/main/java/androidNative.kt
+++ b/plugins/android/src/main/java/androidNative.kt
@@ -1,16 +1,18 @@
import org.gradle.api.Project
import tools.forma.android.config.NdkAbi
import tools.forma.android.config.NdkBuildSystem
-import tools.forma.owners.NoOwner
-import tools.forma.owners.Owner
-import tools.forma.android.target.NativeTarget
+import tools.forma.android.feature.AndroidNativeConfiguration
+import tools.forma.android.feature.androidNativeDefinition
+import tools.forma.android.feature.applyFeatures
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
+import tools.forma.android.validation.disallowResources
import tools.forma.android.visibility.Public
import tools.forma.android.visibility.Visibility
+import tools.forma.owners.NoOwner
+import tools.forma.owners.Owner
+import tools.forma.validation.asValidator
import tools.forma.validation.validate
-import tools.forma.android.validation.disallowResources
-import tools.forma.android.feature.applyFeatures
-import tools.forma.android.feature.androidNativeDefinition
-import tools.forma.android.feature.AndroidNativeConfiguration
fun Project.androidNative(
packageName: String,
@@ -20,7 +22,7 @@ fun Project.androidNative(
visibility: Visibility = Public
) {
disallowResources()
- target.validate(NativeTarget)
+ AndroidTargetRegistry.selfValidator(AndroidTargetTypes.native).asValidator().validate(target)
val configuration = AndroidNativeConfiguration(
packageName = packageName,
diff --git a/plugins/android/src/main/java/androidProjectConfiguration.kt b/plugins/android/src/main/java/androidProjectConfiguration.kt
index 7eff29c..34f691d 100644
--- a/plugins/android/src/main/java/androidProjectConfiguration.kt
+++ b/plugins/android/src/main/java/androidProjectConfiguration.kt
@@ -8,6 +8,7 @@ import org.gradle.kotlin.dsl.embeddedKotlinVersion
import org.gradle.kotlin.dsl.repositories
import org.gradle.plugin.use.PluginDependency
import tools.forma.android.utils.register
+import tools.forma.android.target.registerAndroidDefaults
import tools.forma.config.AndroidProjectSettings
import tools.forma.config.FormaSettingsStore
import tools.forma.config.PluginInfoStore
@@ -75,6 +76,7 @@ fun ScriptHandlerScope.androidProjectConfiguration(
)
Forma.store(configuration)
+ registerAndroidDefaults()
}
@Deprecated("Old approach to configuration, use ScriptHandlerScope Extension")
@@ -116,6 +118,7 @@ fun Project.androidProjectConfiguration(
)
Forma.store(configuration)
+ registerAndroidDefaults()
}
/** Compose Compiler matching Kotlin 1.9.10 (application Gradle 8.4 embedded Kotlin). */
diff --git a/plugins/android/src/main/java/androidRes.kt b/plugins/android/src/main/java/androidRes.kt
index f2ff63e..b3a428f 100644
--- a/plugins/android/src/main/java/androidRes.kt
+++ b/plugins/android/src/main/java/androidRes.kt
@@ -1,20 +1,19 @@
+import org.gradle.api.Project
import tools.forma.android.feature.AndroidLibraryFeatureConfiguration
import tools.forma.android.feature.androidLibraryFeatureDefinition
import tools.forma.android.feature.applyFeatures
import tools.forma.android.feature.kotlinAndroidFeatureDefinition
-import tools.forma.android.target.ResourcesTargetTemplate
-import tools.forma.android.target.WidgetTargetTemplate
-import tools.forma.android.target.ComposeWidgetTargetTemplate
-import tools.forma.owners.NoOwner
-import tools.forma.owners.Owner
-import tools.forma.validation.validate
-import tools.forma.validation.validator
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
import tools.forma.android.validation.onlyAllowResources
import tools.forma.android.visibility.Public
import tools.forma.android.visibility.Visibility
-import org.gradle.api.Project
-import tools.forma.deps.core.applyDependencies
import tools.forma.deps.core.FormaDependency
+import tools.forma.deps.core.applyDependencies
+import tools.forma.owners.NoOwner
+import tools.forma.owners.Owner
+import tools.forma.validation.asValidator
+import tools.forma.validation.validate
// Only resources allowed
fun Project.androidRes(
@@ -27,7 +26,7 @@ fun Project.androidRes(
onlyAllowResources()
- target.validate(ResourcesTargetTemplate)
+ AndroidTargetRegistry.selfValidator(AndroidTargetTypes.res).asValidator().validate(target)
val libraryFeatureConfiguration = AndroidLibraryFeatureConfiguration(
packageName = packageName,
manifestPlaceholders = manifestPlaceholders
@@ -38,11 +37,7 @@ fun Project.androidRes(
)
applyDependencies(
- validator = validator(
- ResourcesTargetTemplate,
- WidgetTargetTemplate,
- ComposeWidgetTargetTemplate,
- ),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.res).asValidator(),
dependencies = dependencies
)
}
diff --git a/plugins/android/src/main/java/androidTestUtil.kt b/plugins/android/src/main/java/androidTestUtil.kt
index 902a8cf..892faad 100644
--- a/plugins/android/src/main/java/androidTestUtil.kt
+++ b/plugins/android/src/main/java/androidTestUtil.kt
@@ -2,17 +2,17 @@ import tools.forma.android.feature.AndroidLibraryFeatureConfiguration
import tools.forma.android.feature.androidLibraryFeatureDefinition
import tools.forma.android.feature.applyFeatures
import tools.forma.android.feature.kotlinAndroidFeatureDefinition
-import tools.forma.android.target.AndroidTestUtilTargetTemplate
-import tools.forma.android.target.TestUtilTargetTemplate
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
import tools.forma.owners.NoOwner
import tools.forma.owners.Owner
-import tools.forma.validation.validate
-import tools.forma.validation.validator
import tools.forma.android.visibility.Public
import tools.forma.android.visibility.Visibility
import org.gradle.api.Project
import tools.forma.deps.core.applyDependencies
import tools.forma.deps.core.FormaDependency
+import tools.forma.validation.asValidator
+import tools.forma.validation.validate
fun Project.androidTestUtil(
packageName: String,
@@ -20,7 +20,7 @@ fun Project.androidTestUtil(
visibility: Visibility = Public,
dependencies: FormaDependency = emptyDependency()
) {
- target.validate(AndroidTestUtilTargetTemplate)
+ AndroidTargetRegistry.selfValidator(AndroidTargetTypes.androidTestUtil).asValidator().validate(target)
val androidFeatureConfig = AndroidLibraryFeatureConfiguration(
packageName
@@ -31,7 +31,7 @@ fun Project.androidTestUtil(
)
applyDependencies(
- validator = validator(AndroidTestUtilTargetTemplate, TestUtilTargetTemplate),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.androidTestUtil).asValidator(),
dependencies = dependencies
)
}
diff --git a/plugins/android/src/main/java/androidUtil.kt b/plugins/android/src/main/java/androidUtil.kt
index aee97ad..2b60d61 100644
--- a/plugins/android/src/main/java/androidUtil.kt
+++ b/plugins/android/src/main/java/androidUtil.kt
@@ -2,13 +2,10 @@ import tools.forma.android.feature.AndroidLibraryFeatureConfiguration
import tools.forma.android.feature.androidLibraryFeatureDefinition
import tools.forma.android.feature.applyFeatures
import tools.forma.android.feature.kotlinAndroidFeatureDefinition
-import tools.forma.android.target.AndroidUtilTargetTemplate
-import tools.forma.android.target.ResourcesTargetTemplate
-import tools.forma.android.target.TestUtilTargetTemplate
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
import tools.forma.owners.NoOwner
import tools.forma.owners.Owner
-import tools.forma.validation.validate
-import tools.forma.validation.validator
import tools.forma.android.validation.disallowResources
import tools.forma.android.visibility.Public
import tools.forma.android.visibility.Visibility
@@ -16,6 +13,8 @@ import org.gradle.api.Project
import tools.forma.android.feature.kaptConfigurationFeature
import tools.forma.deps.core.applyDependencies
import tools.forma.deps.core.FormaDependency
+import tools.forma.validation.asValidator
+import tools.forma.validation.validate
/**
* TODO
@@ -46,7 +45,7 @@ fun Project.androidUtil(
disallowResources()
//TODO unify with util, use androidJar dependency
- target.validate(AndroidUtilTargetTemplate)
+ AndroidTargetRegistry.selfValidator(AndroidTargetTypes.androidUtil).asValidator().validate(target)
val androidFeatureConfig = AndroidLibraryFeatureConfiguration(
packageName = packageName,
@@ -59,11 +58,7 @@ fun Project.androidUtil(
)
applyDependencies(
- validator = validator(
- AndroidUtilTargetTemplate,
- TestUtilTargetTemplate,
- ResourcesTargetTemplate
- ),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.androidUtil).asValidator(),
dependencies = dependencies,
testDependencies = testDependencies,
configurationFeatures = kaptConfigurationFeature()
diff --git a/plugins/android/src/main/java/api.kt b/plugins/android/src/main/java/api.kt
index 0a93e7d..de21bf0 100644
--- a/plugins/android/src/main/java/api.kt
+++ b/plugins/android/src/main/java/api.kt
@@ -1,14 +1,14 @@
import tools.forma.android.feature.applyFeatures
import tools.forma.android.feature.kotlinFeatureDefinition
-import tools.forma.android.target.ApiTargetTemplate
-import tools.forma.android.target.LibraryTargetTemplate
-import tools.forma.validation.validator
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
+import tools.forma.android.validation.disallowResources
import tools.forma.owners.Owner
import tools.forma.owners.NoOwner
-import tools.forma.android.validation.disallowResources
import org.gradle.api.Project
import tools.forma.deps.core.applyDependencies
import tools.forma.deps.core.FormaDependency
+import tools.forma.validation.asValidator
import tools.forma.validation.validate
/**
@@ -26,12 +26,12 @@ fun Project.api(
disallowResources()
- target.validate(ApiTargetTemplate)
+ AndroidTargetRegistry.selfValidator(AndroidTargetTypes.api).asValidator().validate(target)
applyFeatures(
kotlinFeatureDefinition()
)
applyDependencies(
- validator = validator(ApiTargetTemplate, LibraryTargetTemplate),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.api).asValidator(),
dependencies = dependencies
)
}
diff --git a/plugins/android/src/main/java/composeWidget.kt b/plugins/android/src/main/java/composeWidget.kt
index d9d976b..9770208 100644
--- a/plugins/android/src/main/java/composeWidget.kt
+++ b/plugins/android/src/main/java/composeWidget.kt
@@ -3,21 +3,17 @@ import tools.forma.android.feature.AndroidLibraryFeatureConfiguration
import tools.forma.android.feature.androidLibraryFeatureDefinition
import tools.forma.android.feature.applyFeatures
import tools.forma.android.feature.kotlinAndroidFeatureDefinition
-import tools.forma.android.target.AndroidUtilTargetTemplate
-import tools.forma.android.target.ComposeWidgetTargetTemplate
-import tools.forma.android.target.ResourcesTargetTemplate
-import tools.forma.android.target.UiLibraryTargetTemplate
-import tools.forma.android.target.UtilTargetTemplate
-import tools.forma.android.target.WidgetTargetTemplate
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
+import tools.forma.android.visibility.Public
+import tools.forma.android.visibility.Visibility
import tools.forma.deps.core.FormaDependency
import tools.forma.deps.core.NamedDependency
import tools.forma.deps.core.applyDependencies
import tools.forma.owners.NoOwner
import tools.forma.owners.Owner
-import tools.forma.android.visibility.Public
-import tools.forma.android.visibility.Visibility
+import tools.forma.validation.asValidator
import tools.forma.validation.validate
-import tools.forma.validation.validator
/**
* Compose UI component target (Jetpack Compose counterpart of [widget]).
@@ -37,7 +33,8 @@ fun Project.composeWidget(
consumerMinificationFiles: Set = emptySet(),
manifestPlaceholders: Map = emptyMap()
) {
- target.validate(ComposeWidgetTargetTemplate)
+ val selfV = AndroidTargetRegistry.selfValidator(AndroidTargetTypes.composeWidget).asValidator()
+ selfV.validate(target)
val featureConfiguration = AndroidLibraryFeatureConfiguration(
packageName = packageName,
@@ -45,7 +42,7 @@ fun Project.composeWidget(
consumerMinificationFiles = consumerMinificationFiles,
manifestPlaceholders = manifestPlaceholders,
compose = true,
- selfValidator = validator(ComposeWidgetTargetTemplate)
+ selfValidator = selfV
)
applyFeatures(
@@ -54,14 +51,7 @@ fun Project.composeWidget(
)
applyDependencies(
- validator = validator(
- UiLibraryTargetTemplate,
- ComposeWidgetTargetTemplate,
- WidgetTargetTemplate,
- UtilTargetTemplate,
- AndroidUtilTargetTemplate,
- ResourcesTargetTemplate
- ),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.composeWidget).asValidator(),
dependencies = dependencies,
testDependencies = testDependencies,
androidTestDependencies = androidTestDependencies
diff --git a/plugins/android/src/main/java/impl.kt b/plugins/android/src/main/java/impl.kt
index 89940ce..58ebd38 100644
--- a/plugins/android/src/main/java/impl.kt
+++ b/plugins/android/src/main/java/impl.kt
@@ -4,23 +4,15 @@ import tools.forma.android.feature.androidLibraryFeatureDefinition
import tools.forma.android.feature.applyFeatures
import tools.forma.android.feature.kaptConfigurationFeature
import tools.forma.android.feature.kotlinAndroidFeatureDefinition
-import tools.forma.android.target.AndroidUtilTargetTemplate
-import tools.forma.android.target.ApiTargetTemplate
-import tools.forma.android.target.ImplTargetTemplate
-import tools.forma.android.target.LibraryTargetTemplate
-import tools.forma.android.target.ResourcesTargetTemplate
-import tools.forma.android.target.TestUtilTargetTemplate
-import tools.forma.android.target.UiLibraryTargetTemplate
-import tools.forma.android.target.UtilTargetTemplate
-import tools.forma.android.target.ViewBindingTargetTemplate
-import tools.forma.android.target.WidgetTargetTemplate
-import tools.forma.android.target.ComposeWidgetTargetTemplate
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
import tools.forma.android.utils.BuildConfiguration
import tools.forma.deps.core.FormaDependency
import tools.forma.deps.core.NamedDependency
import tools.forma.deps.core.applyDependencies
+import tools.forma.validation.asValidator
import tools.forma.validation.validate
-import tools.forma.validation.validator
+
/**
* Feature **implementation** (Android library + optional view binding / kapt / Compose).
@@ -47,14 +39,15 @@ fun Project.impl(
manifestPlaceholders: Map = emptyMap()
) {
- target.validate(ImplTargetTemplate)
+ val selfV = AndroidTargetRegistry.selfValidator(AndroidTargetTypes.impl).asValidator()
+ selfV.validate(target)
val libraryFeatureConfiguration = AndroidLibraryFeatureConfiguration(
packageName,
buildConfiguration,
testInstrumentationRunner,
consumerMinificationFiles,
manifestPlaceholders,
- selfValidator = validator(ImplTargetTemplate),
+ selfValidator = selfV,
viewBinding = viewBinding,
compose = compose
)
@@ -64,18 +57,7 @@ fun Project.impl(
)
applyDependencies(
- validator = validator(
- ApiTargetTemplate,
- AndroidUtilTargetTemplate,
- TestUtilTargetTemplate,
- UtilTargetTemplate,
- LibraryTargetTemplate,
- UiLibraryTargetTemplate,
- ResourcesTargetTemplate,
- ViewBindingTargetTemplate,
- WidgetTargetTemplate,
- ComposeWidgetTargetTemplate,
- ),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.impl).asValidator(),
dependencies = dependencies,
testDependencies = testDependencies,
androidTestDependencies = androidTestDependencies,
diff --git a/plugins/android/src/main/java/library.kt b/plugins/android/src/main/java/library.kt
index 5d8a288..b08c755 100644
--- a/plugins/android/src/main/java/library.kt
+++ b/plugins/android/src/main/java/library.kt
@@ -1,11 +1,9 @@
import tools.forma.android.feature.applyFeatures
import tools.forma.android.feature.kotlinFeatureDefinition
-import tools.forma.android.target.LibraryTargetTemplate
-import tools.forma.android.target.TestUtilTargetTemplate
-import tools.forma.android.target.UtilTargetTemplate
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
import tools.forma.owners.NoOwner
import tools.forma.owners.Owner
-import tools.forma.validation.validator
import tools.forma.android.visibility.Public
import tools.forma.android.visibility.Visibility
import org.gradle.api.Project
@@ -13,6 +11,7 @@ import tools.forma.android.feature.kaptConfigurationFeature
import tools.forma.deps.core.applyDependencies
import tools.forma.deps.core.FormaDependency
import tools.forma.deps.core.NamedDependency
+import tools.forma.validation.asValidator
import tools.forma.validation.validate
/**
@@ -25,14 +24,14 @@ fun Project.library(
visibility: Visibility = Public,
testDependencies: NamedDependency = emptyDependency()
) {
- target.validate(LibraryTargetTemplate)
+ AndroidTargetRegistry.selfValidator(AndroidTargetTypes.jvmLibrary).asValidator().validate(target)
applyFeatures(
kotlinFeatureDefinition()
)
applyDependencies(
- validator = validator(UtilTargetTemplate, TestUtilTargetTemplate),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.jvmLibrary).asValidator(),
dependencies = dependencies,
testDependencies = testDependencies,
configurationFeatures = kaptConfigurationFeature()
diff --git a/plugins/android/src/main/java/testUtil.kt b/plugins/android/src/main/java/testUtil.kt
index 7a51ee2..f13706f 100644
--- a/plugins/android/src/main/java/testUtil.kt
+++ b/plugins/android/src/main/java/testUtil.kt
@@ -1,16 +1,16 @@
import tools.forma.android.feature.applyFeatures
import tools.forma.android.feature.kotlinFeatureDefinition
-import tools.forma.android.target.TestUtilTargetTemplate
-import tools.forma.android.target.UtilTargetTemplate
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
import tools.forma.owners.NoOwner
import tools.forma.owners.Owner
import tools.forma.android.validation.disallowResources
-import tools.forma.validation.validator
import tools.forma.android.visibility.Public
import tools.forma.android.visibility.Visibility
import org.gradle.api.Project
import tools.forma.deps.core.applyDependencies
import tools.forma.deps.core.FormaDependency
+import tools.forma.validation.asValidator
import tools.forma.validation.validate
fun Project.testUtil(
@@ -22,14 +22,14 @@ fun Project.testUtil(
disallowResources()
- target.validate(TestUtilTargetTemplate)
+ AndroidTargetRegistry.selfValidator(AndroidTargetTypes.testUtil).asValidator().validate(target)
applyFeatures(
kotlinFeatureDefinition()
)
applyDependencies(
- validator = validator(TestUtilTargetTemplate, UtilTargetTemplate),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.testUtil).asValidator(),
dependencies = dependencies
)
}
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
new file mode 100644
index 0000000..420291a
--- /dev/null
+++ b/plugins/android/src/main/java/tools/forma/android/target/AndroidTargetRegistry.kt
@@ -0,0 +1,177 @@
+package tools.forma.android.target
+
+import tools.forma.core.target.DefaultTargetRegistry
+import tools.forma.core.target.TargetRegistration
+import tools.forma.core.target.TargetRegistry
+import tools.forma.core.validation.NoResourcesUnderMain
+import tools.forma.core.validation.OnlyLayoutResources
+import tools.forma.core.validation.OnlyResourcesUnderMain
+
+/**
+ * Singleton registry for Android platform target types.
+ * Initialized explicitly via [registerAndroidDefaults] from [androidProjectConfiguration].
+ *
+ * DSL entrypoints obtain self + dependency validators from here (no more hand-written allow-lists).
+ */
+object AndroidTargetRegistry : TargetRegistry by DefaultTargetRegistry()
+
+/**
+ * Register Android target types + restriction matrix once.
+ * Replicates the allow lists from AndroidRestrictionKit + DEPENDENCY-MATRIX for fidelity.
+ * Attaches ContentRules for targets that historically called disallowResources / only* helpers
+ * (enforcement of content rules remains via the Gradle helpers in DSL bodies for this phase).
+ *
+ * Safe to call multiple times (re-register replaces).
+ */
+fun registerAndroidDefaults(registry: TargetRegistry = AndroidTargetRegistry) {
+ val t = AndroidTargetTypes
+ val noRes = listOf(NoResourcesUnderMain)
+ val onlyRes = listOf(OnlyResourcesUnderMain)
+ val onlyLayouts = listOf(OnlyLayoutResources)
+
+ // api: api + jvm library (JVM contract layer)
+ registry.register(
+ TargetRegistration(
+ type = t.api,
+ allowedDependencies = setOf(t.api, t.library),
+ contentRules = noRes
+ )
+ )
+
+ // impl: api + utils + libraries + ui blocks; **no impl**
+ registry.register(
+ TargetRegistration(
+ type = t.impl,
+ allowedDependencies = setOf(
+ t.api, t.androidUtil, t.testUtil, t.util, t.library,
+ t.uiLibrary, t.res, t.viewBinding, t.widget, t.composeWidget
+ )
+ )
+ )
+
+ // JVM library() consumer (distinct id)
+ registry.register(
+ TargetRegistration(
+ type = t.jvmLibrary,
+ allowedDependencies = setOf(t.util, t.testUtil)
+ )
+ )
+
+ // androidLibrary consumer (distinct id from jvmLibrary)
+ registry.register(
+ TargetRegistration(
+ type = t.library,
+ allowedDependencies = setOf(t.library, t.util, t.androidUtil, t.testUtil, t.res, t.api)
+ )
+ )
+
+ // uiLibrary
+ registry.register(
+ TargetRegistration(
+ type = t.uiLibrary,
+ allowedDependencies = setOf(t.widget, t.composeWidget, t.util, t.androidUtil, t.res)
+ )
+ )
+
+ // util (JVM)
+ registry.register(
+ TargetRegistration(
+ type = t.util,
+ allowedDependencies = setOf(t.util, t.library),
+ contentRules = noRes
+ )
+ )
+
+ // androidUtil
+ registry.register(
+ TargetRegistration(
+ type = t.androidUtil,
+ allowedDependencies = setOf(t.androidUtil, t.testUtil, t.res),
+ contentRules = noRes
+ )
+ )
+
+ // testUtil
+ registry.register(
+ TargetRegistration(
+ type = t.testUtil,
+ allowedDependencies = setOf(t.testUtil, t.util),
+ contentRules = noRes
+ )
+ )
+
+ // androidTestUtil
+ registry.register(
+ TargetRegistration(
+ type = t.androidTestUtil,
+ allowedDependencies = setOf(t.androidTestUtil, t.testUtil)
+ )
+ )
+
+ // androidRes
+ registry.register(
+ TargetRegistration(
+ type = t.res,
+ allowedDependencies = setOf(t.res, t.widget, t.composeWidget),
+ contentRules = onlyRes
+ )
+ )
+
+ // widget
+ registry.register(
+ TargetRegistration(
+ type = t.widget,
+ allowedDependencies = setOf(t.uiLibrary, t.widget, t.composeWidget, t.util, t.androidUtil, t.res)
+ )
+ )
+
+ // composeWidget (coexist with widget)
+ registry.register(
+ TargetRegistration(
+ type = t.composeWidget,
+ allowedDependencies = setOf(t.uiLibrary, t.composeWidget, t.widget, t.util, t.androidUtil, t.res)
+ )
+ )
+
+ // viewBinding
+ registry.register(
+ TargetRegistration(
+ type = t.viewBinding,
+ allowedDependencies = setOf(t.api, t.widget, t.composeWidget, t.res, t.library, t.androidUtil),
+ contentRules = onlyLayouts
+ )
+ )
+
+ // androidApp (composition root)
+ registry.register(
+ TargetRegistration(
+ type = t.app,
+ allowedDependencies = setOf(
+ t.api, t.impl, t.library, t.util, t.androidUtil, t.testUtil, t.res,
+ t.viewBinding, t.widget, t.composeWidget, t.uiLibrary
+ ),
+ contentRules = noRes
+ )
+ )
+
+ // androidBinary (composition root)
+ registry.register(
+ TargetRegistration(
+ type = t.binary,
+ allowedDependencies = setOf(
+ t.app, t.api, t.impl, t.library, t.util, t.androidUtil, t.testUtil, t.res,
+ t.viewBinding, t.widget, t.composeWidget, t.uiLibrary
+ ),
+ contentRules = noRes
+ )
+ )
+
+ // native: no project-dep validation today
+ registry.register(
+ TargetRegistration(
+ type = t.native,
+ allowedDependencies = emptySet(),
+ contentRules = noRes
+ )
+ )
+}
diff --git a/plugins/android/src/main/java/uiLibrary.kt b/plugins/android/src/main/java/uiLibrary.kt
index 23000af..c3fb842 100644
--- a/plugins/android/src/main/java/uiLibrary.kt
+++ b/plugins/android/src/main/java/uiLibrary.kt
@@ -4,22 +4,18 @@ import tools.forma.android.feature.androidLibraryFeatureDefinition
import tools.forma.android.feature.applyFeatures
import tools.forma.android.feature.kaptConfigurationFeature
import tools.forma.android.feature.kotlinAndroidFeatureDefinition
-import tools.forma.owners.NoOwner
-import tools.forma.owners.Owner
-import tools.forma.android.target.AndroidUtilTargetTemplate
-import tools.forma.android.target.ResourcesTargetTemplate
-import tools.forma.android.target.UiLibraryTargetTemplate
-import tools.forma.android.target.UtilTargetTemplate
-import tools.forma.android.target.WidgetTargetTemplate
-import tools.forma.android.target.ComposeWidgetTargetTemplate
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
import tools.forma.android.utils.BuildConfiguration
import tools.forma.android.visibility.Public
import tools.forma.android.visibility.Visibility
import tools.forma.deps.core.FormaDependency
import tools.forma.deps.core.NamedDependency
import tools.forma.deps.core.applyDependencies
+import tools.forma.owners.NoOwner
+import tools.forma.owners.Owner
+import tools.forma.validation.asValidator
import tools.forma.validation.validate
-import tools.forma.validation.validator
/**
* Android UI Library target - this can be used to share common ui code for impl and widget target.
@@ -40,7 +36,7 @@ fun Project.uiLibrary(
/** Enable Jetpack Compose; defaults to project-wide `compose` setting. */
compose: Boolean = Forma.settings.compose,
): TargetBuilder {
- target.validate(UiLibraryTargetTemplate)
+ AndroidTargetRegistry.selfValidator(AndroidTargetTypes.uiLibrary).asValidator().validate(target)
val libraryFeatureConfiguration = AndroidLibraryFeatureConfiguration(
packageName,
buildConfiguration,
@@ -55,14 +51,7 @@ fun Project.uiLibrary(
)
applyDependencies(
- validator = validator(
- // Better to have ability to use widget while we experiment with dependency rules
- WidgetTargetTemplate,
- ComposeWidgetTargetTemplate,
- UtilTargetTemplate,
- AndroidUtilTargetTemplate,
- ResourcesTargetTemplate
- ),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.uiLibrary).asValidator(),
dependencies = dependencies,
testDependencies = testDependencies,
androidTestDependencies = androidTestDependencies,
diff --git a/plugins/android/src/main/java/util.kt b/plugins/android/src/main/java/util.kt
index 771f215..36603ca 100644
--- a/plugins/android/src/main/java/util.kt
+++ b/plugins/android/src/main/java/util.kt
@@ -1,16 +1,16 @@
import tools.forma.android.feature.applyFeatures
import tools.forma.android.feature.kotlinFeatureDefinition
-import tools.forma.android.target.UtilTargetTemplate
-import tools.forma.android.target.LibraryTargetTemplate
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
import tools.forma.owners.NoOwner
import tools.forma.owners.Owner
import tools.forma.android.validation.disallowResources
-import tools.forma.validation.validator
import tools.forma.android.visibility.Public
import tools.forma.android.visibility.Visibility
import org.gradle.api.Project
import tools.forma.deps.core.applyDependencies
import tools.forma.deps.core.FormaDependency
+import tools.forma.validation.asValidator
import tools.forma.validation.validate
/**
@@ -39,14 +39,14 @@ fun Project.util(
disallowResources()
- target.validate(UtilTargetTemplate)
+ AndroidTargetRegistry.selfValidator(AndroidTargetTypes.util).asValidator().validate(target)
applyFeatures(
kotlinFeatureDefinition()
)
applyDependencies(
- validator = validator(UtilTargetTemplate, LibraryTargetTemplate),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.util).asValidator(),
dependencies = dependencies,
testDependencies = testDependencies
)
diff --git a/plugins/android/src/main/java/viewBinding.kt b/plugins/android/src/main/java/viewBinding.kt
index f34d948..2dbe1aa 100644
--- a/plugins/android/src/main/java/viewBinding.kt
+++ b/plugins/android/src/main/java/viewBinding.kt
@@ -3,22 +3,17 @@ import tools.forma.android.feature.AndroidLibraryFeatureConfiguration
import tools.forma.android.feature.androidLibraryFeatureDefinition
import tools.forma.android.feature.applyFeatures
import tools.forma.android.feature.kotlinAndroidFeatureDefinition
-import tools.forma.owners.NoOwner
-import tools.forma.owners.Owner
-import tools.forma.android.target.AndroidUtilTargetTemplate
-import tools.forma.android.target.ApiTargetTemplate
-import tools.forma.android.target.LibraryTargetTemplate
-import tools.forma.android.target.ResourcesTargetTemplate
-import tools.forma.android.target.ViewBindingTargetTemplate
-import tools.forma.android.target.WidgetTargetTemplate
-import tools.forma.android.target.ComposeWidgetTargetTemplate
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
import tools.forma.android.validation.onlyAllowLayouts
import tools.forma.android.visibility.Public
import tools.forma.android.visibility.Visibility
import tools.forma.deps.core.FormaDependency
import tools.forma.deps.core.applyDependencies
+import tools.forma.owners.NoOwner
+import tools.forma.owners.Owner
+import tools.forma.validation.asValidator
import tools.forma.validation.validate
-import tools.forma.validation.validator
/**
* Android View Binding Target - View Binding layouts collection
@@ -40,7 +35,7 @@ fun Project.viewBinding(
) {
onlyAllowLayouts()
- target.validate(ViewBindingTargetTemplate)
+ AndroidTargetRegistry.selfValidator(AndroidTargetTypes.viewBinding).asValidator().validate(target)
val libraryFeatureConfiguration = AndroidLibraryFeatureConfiguration(
packageName = packageName,
consumerMinificationFiles = consumerMinificationFiles,
@@ -51,14 +46,7 @@ fun Project.viewBinding(
kotlinAndroidFeatureDefinition(),
)
applyDependencies(
- validator = validator(
- ApiTargetTemplate,
- WidgetTargetTemplate,
- ComposeWidgetTargetTemplate,
- ResourcesTargetTemplate,
- LibraryTargetTemplate,
- AndroidUtilTargetTemplate,
- ),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.viewBinding).asValidator(),
dependencies = dependencies
)
}
diff --git a/plugins/android/src/main/java/widget.kt b/plugins/android/src/main/java/widget.kt
index afc93f7..0fefa8d 100644
--- a/plugins/android/src/main/java/widget.kt
+++ b/plugins/android/src/main/java/widget.kt
@@ -1,22 +1,18 @@
+import org.gradle.api.Project
import tools.forma.android.feature.AndroidLibraryFeatureConfiguration
import tools.forma.android.feature.androidLibraryFeatureDefinition
import tools.forma.android.feature.applyFeatures
import tools.forma.android.feature.kotlinAndroidFeatureDefinition
-import tools.forma.android.target.AndroidUtilTargetTemplate
-import tools.forma.android.target.UtilTargetTemplate
-import tools.forma.android.target.WidgetTargetTemplate
-import tools.forma.android.target.ComposeWidgetTargetTemplate
-import tools.forma.android.target.ResourcesTargetTemplate
-import tools.forma.owners.NoOwner
-import tools.forma.owners.Owner
-import tools.forma.validation.validator
+import tools.forma.android.target.AndroidTargetRegistry
+import tools.forma.android.target.AndroidTargetTypes
import tools.forma.android.visibility.Public
import tools.forma.android.visibility.Visibility
-import org.gradle.api.Project
-import tools.forma.android.target.UiLibraryTargetTemplate
-import tools.forma.deps.core.applyDependencies
import tools.forma.deps.core.FormaDependency
import tools.forma.deps.core.NamedDependency
+import tools.forma.deps.core.applyDependencies
+import tools.forma.owners.NoOwner
+import tools.forma.owners.Owner
+import tools.forma.validation.asValidator
import tools.forma.validation.validate
// TODO only allow layouts and view classes
@@ -37,7 +33,7 @@ fun Project.widget(
consumerMinificationFiles: Set = emptySet(),
manifestPlaceholders: Map = emptyMap()
) {
- target.validate(WidgetTargetTemplate)
+ AndroidTargetRegistry.selfValidator(AndroidTargetTypes.widget).asValidator().validate(target)
val featureConfiguration = AndroidLibraryFeatureConfiguration(
packageName = packageName,
@@ -52,14 +48,7 @@ fun Project.widget(
)
applyDependencies(
- validator = validator(
- UiLibraryTargetTemplate,
- WidgetTargetTemplate,
- ComposeWidgetTargetTemplate,
- UtilTargetTemplate,
- AndroidUtilTargetTemplate,
- ResourcesTargetTemplate
- ),
+ validator = AndroidTargetRegistry.validatorFor(AndroidTargetTypes.widget).asValidator(),
dependencies = dependencies,
testDependencies = testDependencies,
androidTestDependencies = androidTestDependencies
diff --git a/plugins/core/src/main/java/tools/forma/core/target/TargetRegistry.kt b/plugins/core/src/main/java/tools/forma/core/target/TargetRegistry.kt
new file mode 100644
index 0000000..001d956
--- /dev/null
+++ b/plugins/core/src/main/java/tools/forma/core/target/TargetRegistry.kt
@@ -0,0 +1,108 @@
+package tools.forma.core.target
+
+import tools.forma.core.restriction.MutableRestrictionGraph
+import tools.forma.core.restriction.RestrictionGraph
+import tools.forma.core.validation.ContentRule
+import tools.forma.core.validation.TargetValidator
+import tools.forma.core.validation.dependencyTypeValidator
+import tools.forma.core.validation.selfTypeValidator
+
+/**
+ * Registration for a target type with its dependency allow-list, optional content rules,
+ * and name matching strategy.
+ */
+data class TargetRegistration(
+ val type: TargetType,
+ val allowedDependencies: Set,
+ val contentRules: List = emptyList(),
+ val nameMatcher: NameMatcher = SuffixNameMatcher,
+ val metadata: Map = emptyMap(),
+)
+
+/**
+ * Registry of target types and their restriction rules.
+ * Platform plugins (Android first) register their types + matrix once at configuration time.
+ * Consumers (DSL entrypoints) obtain validators from here instead of hardcoding allow-lists.
+ */
+interface TargetRegistry {
+ /** Register or replace a type's full definition (last write wins for a given id). */
+ fun register(registration: TargetRegistration)
+
+ /** Lookup by stable id (e.g. "android.impl", "jvm.library"). */
+ fun get(id: String): TargetType?
+
+ /** All registered types whose nameSuffix matches (may return >1 on suffix collision per §7). */
+ fun getBySuffix(suffix: String): Collection
+
+ /** Snapshot of all registrations. */
+ fun all(): Collection
+
+ /** Current restriction graph view built from registrations. */
+ fun restrictionGraph(): RestrictionGraph
+
+ /** Validator for project dependencies of the given consumer type. Identity-cached. */
+ fun validatorFor(consumer: TargetType): TargetValidator
+
+ /** Validator for the consumer's own project name (self-type check). Identity-cached. */
+ fun selfValidator(type: TargetType): TargetValidator
+}
+
+/**
+ * Default in-memory implementation.
+ * - Stores by type.id
+ * - Builds restriction rules from allowedDependencies on register (replace semantics)
+ * - validatorFor / selfValidator delegate to core factories (which provide identity caching)
+ * - getBySuffix supports shared suffixes (library vs jvm.library)
+ */
+class DefaultTargetRegistry(
+ private val defaultNameMatcher: NameMatcher = SuffixNameMatcher,
+) : TargetRegistry {
+
+ private val byId = mutableMapOf()
+ // We compute graph on demand from authoritative registrations to guarantee exact sets (no merge surprises on re-register).
+ private val validatorCache = mutableMapOf()
+ private val selfValidatorCache = mutableMapOf()
+
+ override fun register(registration: TargetRegistration) {
+ byId[registration.type.id] = registration
+ // Invalidate caches that could be affected (consumer or any that listed it); simplest: clear all.
+ validatorCache.clear()
+ selfValidatorCache.clear()
+ }
+
+ override fun get(id: String): TargetType? = byId[id]?.type
+
+ override fun getBySuffix(suffix: String): Collection =
+ byId.values
+ .filter { it.type.nameSuffix == suffix }
+ .map { it.type }
+
+ override fun all(): Collection = byId.values.toList()
+
+ override fun restrictionGraph(): RestrictionGraph {
+ val g = MutableRestrictionGraph()
+ byId.values.forEach { reg ->
+ if (reg.allowedDependencies.isNotEmpty()) {
+ g.allow(reg.type, reg.allowedDependencies)
+ }
+ }
+ return g
+ }
+
+ override fun validatorFor(consumer: TargetType): TargetValidator {
+ return validatorCache.getOrPut(consumer) {
+ val reg = byId[consumer.id]
+ val allowed = reg?.allowedDependencies ?: emptySet()
+ val matcher = reg?.nameMatcher ?: defaultNameMatcher
+ dependencyTypeValidator(allowed, matcher)
+ }
+ }
+
+ override fun selfValidator(type: TargetType): TargetValidator {
+ return selfValidatorCache.getOrPut(type) {
+ val reg = byId[type.id]
+ val matcher = reg?.nameMatcher ?: defaultNameMatcher
+ selfTypeValidator(type, matcher)
+ }
+ }
+}
diff --git a/plugins/core/src/test/java/tools/forma/core/target/TargetRegistryTest.kt b/plugins/core/src/test/java/tools/forma/core/target/TargetRegistryTest.kt
new file mode 100644
index 0000000..1742d3b
--- /dev/null
+++ b/plugins/core/src/test/java/tools/forma/core/target/TargetRegistryTest.kt
@@ -0,0 +1,137 @@
+package tools.forma.core.target
+
+import tools.forma.core.restriction.EdgeKind
+import tools.forma.core.validation.FormaValidationException
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+class TargetRegistryTest {
+
+ private data class TestRef(override val name: String) : TargetRef
+
+ @Test
+ fun `register + get by id roundtrips`() {
+ val reg = DefaultTargetRegistry()
+ val api = targetType("android.api", "api")
+ reg.register(TargetRegistration(api, allowedDependencies = setOf(api)))
+
+ assertEquals(api, reg.get("android.api"))
+ assertEquals(null, reg.get("nope"))
+ }
+
+ @Test
+ fun `getBySuffix returns all with same suffix (library collision)`() {
+ val reg = DefaultTargetRegistry()
+ val jvmLib = targetType("jvm.library", "library")
+ val androidLib = targetType("android.library", "library")
+ reg.register(TargetRegistration(jvmLib, allowedDependencies = emptySet()))
+ reg.register(TargetRegistration(androidLib, allowedDependencies = emptySet()))
+
+ val hits = reg.getBySuffix("library")
+ assertEquals(2, hits.size)
+ assertTrue(hits.any { it.id == "jvm.library" })
+ assertTrue(hits.any { it.id == "android.library" })
+ }
+
+ @Test
+ fun `restrictionGraph isAllowed matches registered edges`() {
+ val reg = DefaultTargetRegistry()
+ val api = targetType("android.api", "api")
+ val impl = targetType("android.impl", "impl")
+ val lib = targetType("android.library", "library")
+ reg.register(TargetRegistration(api, allowedDependencies = setOf(api, lib)))
+ reg.register(TargetRegistration(impl, allowedDependencies = setOf(api, lib))) // no impl
+
+ val g = reg.restrictionGraph()
+ assertTrue(g.isAllowed(api, api))
+ assertTrue(g.isAllowed(api, lib))
+ assertFalse(g.isAllowed(api, impl))
+ assertTrue(g.isAllowed(impl, api))
+ assertFalse(g.isAllowed(impl, impl)) // critical
+ }
+
+ @Test
+ fun `validatorFor accepts names matching allowed suffixes and rejects others`() {
+ val reg = DefaultTargetRegistry()
+ val api = targetType("android.api", "api")
+ val lib = targetType("jvm.library", "library")
+ reg.register(TargetRegistration(api, allowedDependencies = setOf(api, lib)))
+
+ val v = reg.validatorFor(api)
+ v.validate(TestRef("api"))
+ v.validate(TestRef("core-library"))
+ val ex = assertFailsWith {
+ v.validate(TestRef("impl"))
+ }
+ assertTrue("api, library" in ex.message.orEmpty() || "api,library" in ex.message.orEmpty())
+ }
+
+ @Test
+ fun `selfValidator accepts matching suffix and rejects wrong`() {
+ val reg = DefaultTargetRegistry()
+ val impl = targetType("android.impl", "impl")
+ reg.register(TargetRegistration(impl, allowedDependencies = emptySet()))
+
+ val v = reg.selfValidator(impl)
+ v.validate(TestRef("impl"))
+ v.validate(TestRef("feature-foo-impl"))
+ assertFailsWith {
+ v.validate(TestRef("api"))
+ }
+ }
+
+ @Test
+ fun `validatorFor returns same instance on repeated calls (identity cache)`() {
+ val reg = DefaultTargetRegistry()
+ val api = targetType("x.api", "api")
+ reg.register(TargetRegistration(api, allowedDependencies = setOf(api)))
+
+ val v1 = reg.validatorFor(api)
+ val v2 = reg.validatorFor(api)
+ assertSame(v1, v2)
+ }
+
+ @Test
+ fun `selfValidator returns cached instance`() {
+ val reg = DefaultTargetRegistry()
+ val t = targetType("x.t", "t")
+ reg.register(TargetRegistration(t, allowedDependencies = emptySet()))
+
+ val s1 = reg.selfValidator(t)
+ val s2 = reg.selfValidator(t)
+ assertSame(s1, s2)
+ }
+
+ @Test
+ fun `re-register replaces and updates allowed set (no stale edges)`() {
+ val reg = DefaultTargetRegistry()
+ val c = targetType("c.c", "c")
+ val d1 = targetType("d.one", "d1")
+ val d2 = targetType("d.two", "d2")
+ reg.register(TargetRegistration(c, allowedDependencies = setOf(d1)))
+ reg.register(TargetRegistration(c, allowedDependencies = setOf(d2)))
+
+ val g = reg.restrictionGraph()
+ assertTrue(g.isAllowed(c, d2))
+ assertFalse(g.isAllowed(c, d1))
+ }
+
+ @Test
+ fun `restrictionGraph exposes ruleFor and edge defaults`() {
+ val reg = DefaultTargetRegistry()
+ val a = targetType("a.a", "a")
+ val b = targetType("b.b", "b")
+ reg.register(TargetRegistration(a, allowedDependencies = setOf(a, b)))
+
+ val g = reg.restrictionGraph()
+ val rule = g.ruleFor(a)
+ assertNotNull(rule)
+ assertEquals(setOf(a, b), rule!!.allowedDependencies)
+ assertEquals(setOf(EdgeKind.IMPLEMENTATION), rule.edgeKinds)
+ }
+}
diff --git a/plugins/validation/src/main/java/tools/forma/validation/Validator.kt b/plugins/validation/src/main/java/tools/forma/validation/Validator.kt
index 708e5e2..85e209a 100644
--- a/plugins/validation/src/main/java/tools/forma/validation/Validator.kt
+++ b/plugins/validation/src/main/java/tools/forma/validation/Validator.kt
@@ -117,3 +117,20 @@ fun throwProjectDepsValidationError(
""".trimIndent()
)
}
+
+/**
+ * Bridge from core [TargetValidator] (obtained via registry) to the legacy [Validator]
+ * interface consumed by [applyDependencies] and feature selfValidator slots.
+ *
+ * Wraps [FormaValidationException] as [ProjectValidationError] so existing callers,
+ * tests, and error shape expectations are unchanged.
+ */
+fun CoreValidator.asValidator(): Validator = object : Validator {
+ override fun validate(target: FormaTarget) {
+ try {
+ this@asValidator.validate(target)
+ } catch (ex: FormaValidationException) {
+ throw ProjectValidationError(ex.message ?: "Project ${target.name}: validation failed")
+ }
+ }
+}