Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions TICKETS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Canonical write-up: `docs/VISION.md` § Root principles. Plugins design: `docs/T
| F-004 | done | CI green on GitHub Actions for plugins + application | Temurin 17 all jobs + Android SDK 33 for app; PR #153 GHA green |
| F-018 | done | JDK 21 + Gradle/AGP staged modernization | **Shipped on `v2`:** Gradle **8.14.5**, AGP **8.13.2**, Kotlin **2.0.21**, KSP **2.0.21-1.0.28**, Compose **1.9.4**/compiler **2.0.21**, JDK **21** host/CI, sample SDK min23/target35/compile35 + deps at AGP-8.13 ceiling. PRs **#179–#182**. Superseded by **F-019** for 9.x. |
| F-019 | done | Gradle 9 + Kotlin 2.3 + AGP 9 toolchain | Phase 1 done (9.6.1 / 2.3.21 / AGP 9.3.0). **F-086** = kapt→KSP + built-in Kotlin. AndroidX ceiling → **F-087**. |
| F-086 | done | Migrate kapt → KSP + AGP built-in Kotlin | First-class `Ksp` + `String.ksp`; `processorConfigurationFeatures()`; sample Dagger on **ksp**; drop F-019 kapt bridge flags; legacy `.kapt` kept until **F-093**. |
| F-086 | done | Migrate kapt → KSP + AGP built-in Kotlin | First-class `Ksp` + `String.ksp`; `processorConfigurationFeatures()`; sample Dagger on **ksp**; drop F-019 kapt bridge flags; legacy `.kapt` removed in **F-093**. |

## P1 — Android working product

Expand Down Expand Up @@ -124,7 +124,7 @@ Promoted from daily next-actions / historical GH. Workers pick top `todo` in ord
| F-090 | done | Exclude modules from dependency validation | GH **#97** — `dependencyValidationExclusions` on root `androidProjectConfiguration` / `AndroidProjectSettings`; `applyDependencies` skips project-dep suffix validation for exact path/name matches only. Self-type validation unchanged. Unit tests in `:config` + `:deps`; matrix + PROJECT-CONFIGURATION docs. |
| F-091 | done | BuildFeatures under Forma | GH **#88** — `FormaBuildFeatures` project-global (defaults **off**); central apply on library+binary; type-owned viewBinding/composeWidget + minimal `impl`/`compose` attrs; docs CALL-SITE + PROJECT-CONFIGURATION. |
| F-092 | done | versionCode / versionName on binary (and app) | GH **#82** — **done:** required `versionCode`/`versionName` on `androidBinary` only (wired to AGP `defaultConfig`); **not** on `AndroidProjectSettings` / `androidProjectConfiguration`; `androidApp` stays library shell (no version attrs — Gradle app-module limit). Sample + examples already use call-site API; docs/KDoc locked. |
| F-093 | todo | Remove legacy `.kapt` once unused | After F-086 sample is KSP-only: hard-remove or hard-deprecate `String.kapt` / `kapt()` / `Kapt` configuration path if no in-repo consumers; keep only if a documented external-compat shim is still required. Grep tree + tests + DEPS docs. |
| F-093 | done | Remove legacy `.kapt` once unused | **KSP-only:** hard-removed `String.kapt` / `kapt()` / `object Kapt` / `kotlin-kapt` auto-apply / `kaptConfigurationFeature`. Zero in-repo consumers after F-086; no external-compat shim. |
| F-094 | blocked | Gradle Plugin Portal Forma org/user | GH **#133** — human/admin: create shared Portal user/org and credentials for team publish. Worker cannot finish without Stepan Portal access. Track only; see `docs/PLUGIN-PUBLISH.md`. |

## Backlog (lower priority / historical GitHub)
Expand Down
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ Under `tools.forma.android.feature`:
- `FeatureDefinition` + `applyFeatures`
- `androidLibraryFeatureDefinition` / `androidBinaryFeatureDefinition` / `androidNativeDefinition`
- Kotlin JVM vs Kotlin Android feature definitions
- `kaptConfigurationFeature` (auto when kapt-ish deps present)
- `processorConfigurationFeatures` (auto-applies KSP when `.ksp` deps present)

Configuration singleton: `Forma` object delegates to `FormaSettingsStore`
(`AndroidProjectSettings`: min/target/compile SDK, AGP/Kotlin versions, repos,
Expand Down
2 changes: 1 addition & 1 deletion docs/CONFIGURATION-PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ definitions, skip empty repo blocks), not as a claim of a fixed wall-clock %.
| Area | Change |
|------|--------|
| `validator(...)` | Identity-cache single- and multi-suffix validators; hot path uses precomputed `endsWith` strings, no intermediate `map`/`contains` lists |
| `kotlinFeatureDefinition` / `kotlinAndroidFeatureDefinition` / kapt | Singleton `FeatureDefinition` instances; read live `Forma.settings` at apply time |
| `kotlinFeatureDefinition` / `kotlinAndroidFeatureDefinition` / KSP processor | Singleton `FeatureDefinition` instances; read live `Forma.settings` at apply time |
| `applyDependencies` | Skip `repositories {}` when config is the empty sentinel; skip entire `dependencies {}` block when all three dep args are `EmptyDependency`; skip plugin lookup when no plugin deps registered |
| `deps` / `FormaDependency.plus` | Prefer typed fields over `filterIsInstance`; pre-size / single-pass merges; indexed `forEach` over specs |
| Catalog generators | `filteredTokens` is a `Set` for O(1) membership |
Expand Down
11 changes: 9 additions & 2 deletions docs/DEPS-CATALOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,24 @@ custom configuration automatically.

### Plugin + processor wiring

**Annotation processing happy path is KSP only** (`String.ksp` / `ksp()` / `Ksp`).
Legacy `.kapt` / `kotlin-kapt` was hard-removed in F-093 — there is no dual path.

```kotlin
plugin(
id = "com.google.devtools.ksp:symbol-processing-gradle-plugin",
version = "…",
configuration = CustomConfiguration("ksp"),
"androidx.room:room-compiler:2.5.1" // registered on the `ksp` configuration
)

// Or at the call site with the typed DSL:
dependencies = deps("com.google.dagger:dagger-compiler:…".ksp)
```

At dependency application time, consuming the room-compiler coordinate applies the KSP
plugin once and adds the processor with `isTransitive = true`.
At dependency application time, consuming a `.ksp` coordinate applies
`com.google.devtools.ksp` once via `processorConfigurationFeatures()` and adds the
processor with `isTransitive = true`.

---

Expand Down
2 changes: 1 addition & 1 deletion docs/PRINCIPLE-AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Not opened as new product tickets here (board empty after F-085; promote only on
- Optional Path B `firebaseBinary` for Crashlytics/GMS apply (sample currently classpath + comment)
- F-084 follow-ups (Gradle tasks, AST migrate) — listed in FLEET-TOOLING.md
- F-019 AndroidX absolute ceiling / compileSdk chase
- Legacy `.kapt` API removal once no consumers
- ~~Legacy `.kapt` API removal once no consumers~~ → **F-093** done (KSP-only)
- Backlog GH issues (#110, #97, #88, #82, …)

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

Newest entries first.

## 2026-07-22 — F-093: hard-remove legacy kapt (KSP-only)

- **Ticket:** F-093 → `done`
- **Branch:** `forma/F-093-remove-kapt` (from `origin/v2`)
- **Actions:**
- Removed public DSL: `fun kapt` / `String.kapt` (`dependencies.kt`), `DependencyHandler.kapt`
- Removed `object Kapt` from `ConfigurationType.kt`
- Removed `kotlinKaptFeatureDefinition` / `Kapt` branch / deprecated `kaptConfigurationFeature` from `Kotlin.kt`; `processorConfigurationFeatures()` is KSP-only
- Tests: drop Kapt assertion; add `ksp` helper coverage
- JaCoCo: drop dead `Kapt*` include
- Docs: DEPS-CATALOG, CONFIGURATION-PERFORMANCE, ARCHITECTURE, forma-core-api, PRINCIPLE-AUDIT, impl KDoc; TICKETS F-093 done
- **Verify:** (this run) plugins `test jacocoHappyPathCoverageVerification build`; application `:binary:assembleDebug`
- **Commits/PRs:** commit on branch; Hermes may open PR
- **Blockers:** none
- **Next step:** F-094 (blocked Portal) or next open P8/backlog ticket

## 2026-07-22 — F-092: versionCode / versionName on binary (GH #82)

- **Ticket:** F-092 → `done`
Expand Down
4 changes: 2 additions & 2 deletions docs/forma-core-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ free of Bazel dependencies. JVM kit is the initial target set.

| Concern | Stays in |
|---------|----------|
| AGP / Kotlin Android / Compose / kapt feature appliers | `:android` (or future platform plugins) |
| AGP / Kotlin Android / Compose / KSP feature appliers | `:android` (or future platform plugins) |
| User-facing Gradle DSL entrypoints (`fun Project.impl(…)`) | platform plugins |
| Plugin Portal publish DSL (`formaPluginConfiguration`) | `plugins/buildSrc` |
| Includer / depgen | separate builds |
Expand Down Expand Up @@ -328,7 +328,7 @@ fun Project.impl(/* platform params */) {
| Type | Notes |
|------|--------|
| `FormaDependency` sealed hierarchy | `Empty`, `Named`, `Target`, `File`, `Platform`, `Mixed` |
| `DepSpec` / `ConfigurationType` | `Implementation`, `Kapt`, `CustomConfiguration`, … |
| `DepSpec` / `ConfigurationType` | `Implementation`, `Ksp`, `CustomConfiguration`, … |
| `deps(…)`, `plus`, `forEach` | pure merge helpers (F-017 hot path) |
| Project-edge validation hook | call `TargetValidator` before adding project dep |

Expand Down
2 changes: 1 addition & 1 deletion plugins/android/src/main/java/impl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import tools.forma.validation.validate


/**
* Feature **implementation** (Android library + optional view binding / kapt / Compose).
* Feature **implementation** (Android library + optional view binding / KSP / Compose).
*
* Dagger2-friendly boundaries:
* - **May** depend on feature `api` contracts, shared libraries/utils, and UI
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
package tools.forma.android.feature

import deps
import kapt
import tools.forma.config.AndroidProjectSettings
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.plugin.KaptExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.gradle.api.tasks.compile.JavaCompile
import tools.forma.deps.core.ConfigurationType
import tools.forma.deps.core.Kapt
import tools.forma.deps.core.Ksp

private fun defaultConfiguration(project: Project, androidProjectSettings: AndroidProjectSettings) {
Expand Down Expand Up @@ -51,27 +47,14 @@ private val kotlinFeatureDefinitionInstance =
configuration = sharedFeatureConfiguration
)

/** Legacy kapt path — still supported if a target declares `.kapt` deps. */
private val kotlinKaptFeatureDefinitionInstance =
FeatureDefinition(
pluginName = "kotlin-kapt",
pluginExtension = KaptExtension::class,
featureConfiguration = Unit,
defaultDependencies = deps("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.2.0".kapt),
configuration = sharedFeatureConfiguration
)

fun kotlinFeatureDefinition() = kotlinFeatureDefinitionInstance

/** Config-only under AGP 9 built-in Kotlin (no `kotlin-android` plugin). */
fun kotlinAndroidFeatureDefinition() = kotlinAndroidFeatureDefinitionInstance

fun kotlinKaptFeatureDefinition() = kotlinKaptFeatureDefinitionInstance

/**
* Lazy processor plugins when a target declares kapt/ksp deps.
* - [Ksp] → apply `com.google.devtools.ksp` (preferred; F-086)
* - [Kapt] → apply `kotlin-kapt` (legacy; incompatible with built-in Kotlin)
* Lazy processor plugins when a target declares KSP deps.
* - [Ksp] → apply `com.google.devtools.ksp` (sole annotation-processing path; F-093)
*/
fun Project.processorConfigurationFeatures(): Map<ConfigurationType, () -> Unit> =
mapOf(
Expand All @@ -80,12 +63,4 @@ fun Project.processorConfigurationFeatures(): Map<ConfigurationType, () -> Unit>
pluginManager.apply("com.google.devtools.ksp")
}
},
Kapt to {
applyFeatures(kotlinKaptFeatureDefinition())
}
)

/** @deprecated Use [processorConfigurationFeatures] (includes KSP). */
@Deprecated("Use processorConfigurationFeatures()", ReplaceWith("processorConfigurationFeatures()"))
fun Project.kaptConfigurationFeature(): Map<ConfigurationType, () -> Unit> =
processorConfigurationFeatures()
1 change: 0 additions & 1 deletion plugins/buildSrc/src/main/kotlin/formaCoverage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ internal val happyPathClassIncludes =
"**/tools/forma/deps/core/CompileOnly*",
"**/tools/forma/deps/core/RuntimeOnly*",
"**/tools/forma/deps/core/AnnotationProcessor*",
"**/tools/forma/deps/core/Kapt*",
"**/tools/forma/deps/core/Ksp*",
"**/tools/forma/deps/core/DepSpec*",
"**/tools/forma/deps/core/TargetSpec*",
Expand Down
7 changes: 0 additions & 7 deletions plugins/deps/src/main/java/dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import tools.forma.deps.core.FileDependency
import tools.forma.deps.core.FileSpec
import tools.forma.deps.core.FormaDependency
import tools.forma.deps.core.Implementation
import tools.forma.deps.core.Kapt
import tools.forma.deps.core.Ksp
import tools.forma.deps.core.MixedDependency
import tools.forma.deps.core.NameSpec
Expand Down Expand Up @@ -224,9 +223,6 @@ fun deps(vararg dependencies: TargetDependency): TargetDependency {
return TargetDependency(out)
}

fun kapt(vararg names: String): NamedDependency =
NamedDependency(names.map { NameSpec(it, Kapt, true) })

fun ksp(vararg names: String): NamedDependency =
NamedDependency(names.map { NameSpec(it, Ksp, true) })

Expand All @@ -236,9 +232,6 @@ fun String.dep(configuration: CustomConfiguration, transitive: Boolean = true) =
val String.dep: NamedDependency
get() = deps(this)

val String.kapt: NamedDependency
get() = kapt(this)

val String.ksp: NamedDependency
get() = ksp(this)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,7 @@ object AnnotationProcessor : ConfigurationType {
override val name: String = "annotationProcessor"
}

object Kapt : ConfigurationType {
override val name: String = "kapt"
}

/** Kotlin Symbol Processing configuration (`ksp`). Prefer over [Kapt] for new code (F-086). */
/** Kotlin Symbol Processing configuration (`ksp`). Sole annotation-processing path (F-093). */
object Ksp : ConfigurationType {
override val name: String = "ksp"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,6 @@ import org.gradle.kotlin.dsl.accessors.runtime.addExternalModuleDependencyTo
import org.gradle.kotlin.dsl.add
import org.gradle.kotlin.dsl.create

/**
* Adds a dependency to the `kapt` configuration.
*
* @param dependencyNotation name of dependency to add at specific configuration
*
* @return the dependency
*/
fun DependencyHandler.kapt(dependencyNotation: String): Dependency? =
add("kapt", dependencyNotation)

/** Adds a dependency to the `ksp` configuration. */
fun DependencyHandler.ksp(dependencyNotation: String): Dependency? =
add("ksp", dependencyNotation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertSame
import kotlin.test.assertTrue
import ksp
import tools.forma.core.target.targetType

/**
Expand All @@ -19,11 +20,21 @@ class DepsModelAndPluginHappyPathTest {
assertEquals("compileOnly", CompileOnly.name)
assertEquals("runtimeOnly", RuntimeOnly.name)
assertEquals("annotationProcessor", AnnotationProcessor.name)
assertEquals("kapt", Kapt.name)
assertEquals("ksp", Ksp.name)
assertEquals("ksp", CustomConfiguration("ksp").name)
}

@Test
fun `ksp helpers place specs on Ksp configuration`() {
val viaFun = ksp("g:processor:1")
val viaExt = "g:processor:1".ksp
assertEquals(1, viaFun.names.size)
assertEquals(Ksp, viaFun.names.single().config)
assertTrue(viaFun.names.single().transitive)
assertEquals("g:processor:1", viaFun.names.single().name)
assertEquals(Ksp, viaExt.names.single().config)
}

@Test
fun `dependency model constructors preserve specs`() {
val name = NameSpec("g:a:1", Implementation, transitive = true)
Expand Down
Loading