Skip to content

Commit 476b6b8

Browse files
kirich1409claude
andcommitted
Address finalize review findings (#249)
- Remove the dead optional `producer` parameter from the KMP/JVM wiring helpers and rewrite the stale KDoc that described the obsolete pure-layout workaround removed by the @OutputDirectory refactor. - Narrow the helper parameter from Provider<out Any> to Provider<out Directory>. - Warn (instead of silently no-op) when an AGP module has no Kotlin `main` source set, pointing the developer at the missing Kotlin Android plugin. - Pass --no-build-cache in the wiring TestKit runs so a cached compile cannot mask broken wiring. - Fix the GeneratedSourceWiringTest KDoc and CLAUDE.md to reflect the dedicated generated/featured/registry directory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent ce52ac7 commit 476b6b8

4 files changed

Lines changed: 44 additions & 40 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ Two plugins, two roles:
8181

8282
**Enum-flag classpath gotcha.** `featuredAggregation(project(":foo"))` only pulls the manifest variant — not `:foo`'s compile classpath. If `:foo` declares an `enum` flag whose enum type lives in `:foo`, the aggregator module must also declare `implementation(project(":foo"))` so the enum class is visible at compile time. Primitive-only modules need no extra dependency.
8383

84-
**Auto-wiring policy.** Both `generateConfigParam` (from `dev.androidbroadcast.featured`) and `generateFeaturedRegistry` (from the aggregator plugin) auto-wire their generated output directory into the consumer module's compilation — consumers need **zero** manual `srcDir` / `dependsOn`. The plugin detects the applied Kotlin/Android plugin and wires the right source set: KMP `commonMain` and Kotlin/JVM `main` via a `srcDir(Provider)` (Gradle auto-infers the task dependency); plain AGP via `sourceSets["main"].kotlin.directories.add(<resolved path>)` plus an explicit `dependsOn` on every `compile*Kotlin` / `ksp*` task (the AGP source set rejects `Provider`s at configuration time). The three branches are mutually exclusive in AGP 9, so exactly one fires per module.
84+
**Auto-wiring policy.** Both `generateConfigParam` (from `dev.androidbroadcast.featured`) and `generateFeaturedRegistry` (from the aggregator plugin) auto-wire their generated output directory into the consumer module's compilation — consumers need **zero** manual `srcDir` / `dependsOn`. They write to **distinct** directories so the two outputs never overlap when a module applies both plugins: `generateConfigParam``build/generated/featured/commonMain`, `generateFeaturedRegistry``build/generated/featured/registry`. The plugin detects the applied Kotlin/Android plugin and wires the right source set: KMP `commonMain` and Kotlin/JVM `main` via a `srcDir(Provider)` (Gradle auto-infers the task dependency); plain AGP via `sourceSets["main"].kotlin.directories.add(<resolved path>)` plus an explicit `dependsOn` on every `compile*Kotlin` / `ksp*` task (the AGP source set rejects `Provider`s at configuration time). The three branches are mutually exclusive in AGP 9, so exactly one fires per module.
8585

8686
## Multi-Module Pattern (canonical, demonstrated in `:sample`)
8787

featured-gradle-plugin/CLAUDE.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ featured {
5454
## Auto-wiring generated sources
5555

5656
`generateConfigParam` (this plugin) and `generateFeaturedRegistry` (the
57-
`dev.androidbroadcast.featured.application` aggregator) auto-wire their `build/generated/featured/commonMain`
58-
output into the consumer module's compilation — consumers write **zero** manual `srcDir` / `dependsOn`.
57+
`dev.androidbroadcast.featured.application` aggregator) auto-wire their generated output into the
58+
consumer module's compilation — consumers write **zero** manual `srcDir` / `dependsOn`. They write to
59+
**distinct** directories so the two outputs never overlap when a module applies both plugins:
60+
`generateConfigParam``build/generated/featured/commonMain`, `generateFeaturedRegistry`
61+
`build/generated/featured/registry`.
5962
The plugin reacts to the applied Kotlin/Android plugin and picks the right source set
6063
(`GeneratedSourceWiring.kt`):
6164

featured-gradle-plugin/src/main/kotlin/dev/androidbroadcast/featured/gradle/GeneratedSourceWiring.kt

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dev.androidbroadcast.featured.gradle
33
import com.android.build.api.dsl.CommonExtension
44
import org.gradle.api.Project
55
import org.gradle.api.Task
6+
import org.gradle.api.file.Directory
67
import org.gradle.api.provider.Provider
78
import org.gradle.api.tasks.TaskProvider
89
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
@@ -16,11 +17,11 @@ import java.io.File
1617
* `com.android.application` / `com.android.library` + `org.jetbrains.kotlin.multiplatform` a hard
1718
* error, and Android Kotlin uses `org.jetbrains.kotlin.android`, never `.jvm`).
1819
*
19-
* KMP / Kotlin-JVM receive the producer task's native @OutputDirectory provider
20-
* (`generateConfigParam.outputDir`, a `Provider<Directory>` obtained via `flatMap`). Passing it to
21-
* `SourceDirectorySet.srcDir(Provider)` records the producer as the directory's builder, so Gradle
22-
* auto-infers the task dependency for EVERY consumer (compile*, sourcesJar, metadata, lint, …) with
23-
* no name-matched `dependsOn`. AGP cannot take a provider (its source set rejects one at
20+
* KMP / Kotlin-JVM receive the producer task's native @OutputDirectory provider — a
21+
* `Provider<Directory>` obtained via `task.flatMap { it.outputDir }`. Passing it to
22+
* `SourceDirectorySet.srcDir(Provider)` records the producer task as the directory's builder, so
23+
* Gradle auto-infers the task dependency for EVERY consumer (compile*, sourcesJar, metadata, lint, …)
24+
* with no name-matched `dependsOn`. AGP cannot take a provider (its source set rejects one at
2425
* configuration time), so it gets a resolved [File] plus the explicit [dependOnProducer] ordering.
2526
*/
2627

@@ -32,57 +33,45 @@ import java.io.File
3233
* co-requires `org.jetbrains.kotlin.multiplatform` (`commonMain` exists), so this branch covers it
3334
* too.
3435
*
35-
* Two wiring shapes share this helper:
36-
* - **Task-carrying provider (per-module `FeaturedPlugin`, [producer] omitted).** [generatedDir] is
37-
* the producer task's @OutputDirectory provider (`generateConfigParam.outputDir`, obtained via
38-
* `flatMap`). `srcDir(Provider)` records the producer as the directory's builder, so Gradle
39-
* auto-infers the task dependency for every consumer — no explicit `dependsOn` needed.
40-
* - **Pure layout provider (aggregator, [producer] passed).** The registry task is @OutputFile-based;
41-
* a derived `.map { it.parentFile }` provider eager-errors inside
42-
* `com.android.kotlin.multiplatform.library`. The aggregator therefore passes a plain layout
43-
* provider (no task attached) for [generatedDir] and supplies [producer] so ordering is carried by
44-
* an explicit `dependsOn` via [dependOnProducer].
36+
* [generatedDir] is the producer task's @OutputDirectory provider (`task.flatMap { it.outputDir }`).
37+
* `srcDir(Provider)` records the producer task as the directory's builder, so Gradle auto-infers the
38+
* task dependency for every consumer — no explicit `dependsOn` needed.
4539
*/
4640
internal fun wireGeneratedSourcesToKmp(
4741
project: Project,
48-
generatedDir: Provider<out Any>,
49-
producer: TaskProvider<*>? = null,
42+
generatedDir: Provider<out Directory>,
5043
) {
5144
val kotlin = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
5245
kotlin.sourceSets
5346
.getByName("commonMain")
5447
.kotlin
5548
.srcDir(generatedDir)
56-
if (producer != null) dependOnProducer(project, producer)
5749
}
5850

5951
/**
6052
* Wires [generatedDir] into the Kotlin/JVM `main` source set.
6153
*
6254
* As with [wireGeneratedSourcesToKmp], `KotlinSourceSet.kotlin` is Gradle's
63-
* [org.gradle.api.file.SourceDirectorySet]. When [generatedDir] is the producer task's
64-
* @OutputDirectory provider (per-module `FeaturedPlugin`, [producer] omitted) the task dependency is
65-
* auto-inferred for every consumer. When [generatedDir] is a pure layout provider (aggregator,
66-
* because the registry task is @OutputFile-based and a derived provider eager-errors), [producer] is
67-
* passed so ordering is carried by an explicit `dependsOn` via [dependOnProducer].
55+
* [org.gradle.api.file.SourceDirectorySet]. [generatedDir] is the producer task's @OutputDirectory
56+
* provider (`task.flatMap { it.outputDir }`); `srcDir(Provider)` records the producer task as the
57+
* directory's builder, so Gradle auto-infers the task dependency for every consumer — no explicit
58+
* `dependsOn` needed.
6859
*/
6960
internal fun wireGeneratedSourcesToKotlinJvm(
7061
project: Project,
71-
generatedDir: Provider<out Any>,
72-
producer: TaskProvider<*>? = null,
62+
generatedDir: Provider<out Directory>,
7363
) {
7464
val kotlin = project.extensions.getByType(KotlinJvmProjectExtension::class.java)
7565
kotlin.sourceSets
7666
.getByName("main")
7767
.kotlin
7868
.srcDir(generatedDir)
79-
if (producer != null) dependOnProducer(project, producer)
8069
}
8170

8271
/**
8372
* Adds an explicit `dependsOn([producer])` to every Kotlin-compile, `ksp*`, and AGP
8473
* annotation-extraction (`extract*Annotations`) task so the generated sources are produced before
85-
* any task that consumes the generated source dir. Used by the AGP branch only — its
74+
* any task that consumes the generated source dir. This is AGP-only — its
8675
* `AndroidSourceDirectorySet` takes a resolved [File] that carries no task dependency. The KMP and
8776
* Kotlin/JVM branches do not call this: their `srcDir(Provider)` auto-infers the dependency.
8877
* AGP's `extractDebugAnnotations` / `extractReleaseAnnotations` read the generated source directory
@@ -123,16 +112,25 @@ private fun dependOnProducer(
123112
* reusing one global [producer] task across `debug` + `release` would share a single dir and bypass
124113
* AGP path generation.
125114
*
126-
* No-ops on an Android module with no Kotlin source set (a pure-Java AGP module never references the
127-
* generated objects).
115+
* Warns and no-ops on an Android module with no Kotlin `main` source set — typically
116+
* `com.android.library` applied without `org.jetbrains.kotlin.android`, so the generated objects
117+
* would never compile anyway.
128118
*/
129119
internal fun wireGeneratedSourcesToAndroid(
130120
project: Project,
131121
generatedDirFile: File,
132122
producer: TaskProvider<*>,
133123
) {
134124
val android = project.extensions.getByType(CommonExtension::class.java)
135-
val mainSourceSet = android.sourceSets.findByName("main") ?: return
125+
val mainSourceSet =
126+
android.sourceSets.findByName("main") ?: run {
127+
project.logger.warn(
128+
"Featured: ${project.path} has no Android 'main' Kotlin source set — generated " +
129+
"sources will NOT be wired into compilation. Apply the Kotlin Android plugin " +
130+
"(org.jetbrains.kotlin.android) to this module.",
131+
)
132+
return
133+
}
136134
// Resolved absolute path, not a Provider — the Android source set rejects providers at
137135
// configuration time. directories is a MutableSet<String> (srcDir(Any) is @Deprecated).
138136
mainSourceSet.kotlin.directories.add(generatedDirFile.absolutePath)

featured-gradle-plugin/src/test/kotlin/dev/androidbroadcast/featured/gradle/GeneratedSourceWiringTest.kt

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@ import kotlin.test.assertTrue
2828
* strictly stronger than reading a source-set directory list, which could list a directory that is
2929
* never actually compiled.
3030
*
31-
* The generated directory is `build/generated/featured/commonMain` for every plugin shape
32-
* (see [GenerateConfigParamTask] / GenerateFeaturedRegistryTask output convention).
31+
* The generated directory depends on the plugin: `build/generated/featured/commonMain` for
32+
* `generateConfigParam` (per-module, see [GenerateConfigParamTask]) and
33+
* `build/generated/featured/registry` for `generateFeaturedRegistry` (the aggregator, see
34+
* GenerateFeaturedRegistryTask) — distinct directories so the two outputs never overlap when a
35+
* module applies both plugins.
3336
*
3437
* AGP-based cases skip when `ANDROID_HOME` / `ANDROID_SDK_ROOT` is unset, matching the rest of the
3538
* integration suite.
@@ -46,7 +49,7 @@ class GeneratedSourceWiringTest {
4649

4750
val result =
4851
gradleRunner(projectDir)
49-
.withArguments("compileKotlin", "--stacktrace")
52+
.withArguments("compileKotlin", "--stacktrace", "--no-build-cache")
5053
.build()
5154

5255
assertGeneratedSourceTaskRan(result, ":$GENERATE_CONFIG_PARAM_TASK_NAME")
@@ -61,7 +64,7 @@ class GeneratedSourceWiringTest {
6164

6265
val result =
6366
gradleRunner(projectDir)
64-
.withArguments("compileKotlinJvm", "--stacktrace")
67+
.withArguments("compileKotlinJvm", "--stacktrace", "--no-build-cache")
6568
.build()
6669

6770
assertGeneratedSourceTaskRan(result, ":$GENERATE_CONFIG_PARAM_TASK_NAME")
@@ -77,7 +80,7 @@ class GeneratedSourceWiringTest {
7780

7881
val result =
7982
gradleRunner(projectDir)
80-
.withArguments("compileDebugKotlin", "--stacktrace")
83+
.withArguments("compileDebugKotlin", "--stacktrace", "--no-build-cache")
8184
.build()
8285

8386
assertGeneratedSourceTaskRan(result, ":$GENERATE_CONFIG_PARAM_TASK_NAME")
@@ -93,7 +96,7 @@ class GeneratedSourceWiringTest {
9396

9497
val result =
9598
gradleRunner(projectDir)
96-
.withArguments("compileDebugKotlin", "--stacktrace")
99+
.withArguments("compileDebugKotlin", "--stacktrace", "--no-build-cache")
97100
.build()
98101

99102
assertGeneratedSourceTaskRan(result, ":$GENERATE_CONFIG_PARAM_TASK_NAME")
@@ -109,7 +112,7 @@ class GeneratedSourceWiringTest {
109112

110113
val result =
111114
gradleRunner(projectDir)
112-
.withArguments(":app:compileDebugKotlin", "--stacktrace")
115+
.withArguments(":app:compileDebugKotlin", "--stacktrace", "--no-build-cache")
113116
.build()
114117

115118
// The aggregator plugin must auto-wire generateFeaturedRegistry; compiling the app must

0 commit comments

Comments
 (0)