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") + } + } +}