Skip to content

Commit f732ee1

Browse files
committed
fix(codegen): complete follow-up fixes for issue #418
1 parent 7f05e86 commit f732ee1

18 files changed

Lines changed: 1077 additions & 56 deletions

File tree

.agents/skills/retrofit-graphql-build-dependencies/references/build-map.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Use this map to choose the right build file before editing.
1111
| Shared dependency strategy | `buildSrc/.../strategy/DependencyStrategy.kt` | Default Kotlin, Retrofit, OkHttp, coroutines, and test libraries per module type |
1212
| Shared Dokka behavior | `buildSrc/.../components/AndroidOptions.kt` | `dokkaHtml` task, `reportUndocumented = true`, internal packages suppressed, Android docs linked |
1313
| Shared formatting | `buildSrc/.../components/AndroidConfiguration.kt` (Spotless config), `spotless/copyright.kt` | Ktlint and license header configuration |
14-
| Publishing options | `buildSrc/.../components/AndroidOptions.kt` (Android), per-module `build.gradle.kts` (JVM) | AGP component-backed publications via `singleVariant("release")` + `withSourcesJar()`. JVM modules (`:annotations`, `:codegen-core`) inline publishing. Publication name `"maven"` for all modules. |
14+
| Publishing options | `buildSrc/.../components/AndroidOptions.kt` (Android), per-module `build.gradle.kts` (JVM), `gradle-plugin/build.gradle.kts`, `gradle-plugin/buildSrc/.../JvmConfiguration.kt`, `.jitpack.yml` | Root Android publications use AGP components. The standalone `gradle-plugin` composite build publishes both the plugin implementation + marker artifacts and republishes included `:codegen-core` with explicit coordinates from `gradle/version.properties`. `.jitpack.yml` must run both root and standalone plugin `publishToMavenLocal` commands. |
1515
| Dependency versions and aliases | `gradle/libs.versions.toml` | Add or update aliases here first |
1616
| Library build | `library/build.gradle.kts` | Applies the shared plugin; no module-specific overrides needed for standard changes |
1717
| Sample app build | `app/build.gradle.kts` | Applies the shared plugin; Room compiler options, build config fields from `.config/` |
@@ -28,4 +28,5 @@ Use this map to choose the right build file before editing.
2828
- Cross-module convention: `buildSrc`.
2929
- One module only: that module's `build.gradle.kts`.
3030
- Documentation generation or publish behavior: Dokka config in `buildSrc/...AndroidOptions.kt` plus the workflow file.
31+
- Standalone plugin publishing or plugin marker issues: `gradle-plugin/build.gradle.kts`, `gradle-plugin/settings.gradle.kts`, `gradle-plugin/buildSrc/.../JvmConfiguration.kt`, and `.jitpack.yml`.
3132
- Memory or JVM tuning: `gradle.properties` (repo-wide) or `~/.gradle/gradle.properties` (user-local, preferred for personal overrides).

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ captures/
6161
# External native build folder generated in Android Studio 2.2 and later
6262
.externalNativeBuild
6363

64+
# Deepwork session state
65+
.slim/deepwork/
66+
6467
# Google Services (e.g. APIs or Firebase)
6568
# google-services.json
6669

.jitpack.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
jdk:
2+
- openjdk21
3+
install:
4+
- ./gradlew build publishToMavenLocal
5+
- ./gradlew -p gradle-plugin publishToMavenLocal

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ The project is organized into composable modules under the `co.anitrend.retrofit
6161
| `:android-assets` | Android | Runtime asset-based query discovery: `GraphProcessor`, `AssetManagerDiscoveryPlugin`, APQ, logging |
6262
| `:runtime` | Android | Retrofit `Converter.Factory`: `GraphConverter`, `GraphRequestConverter`, `GraphResponseConverter`. Depends on `:api`, `:android-assets`, `:annotations` |
6363
| `:codegen-core` | Kotlin JVM | Code generation engine: parses `.graphql` files with graphql-java, generates Kotlin with KotlinPoet. Uses `SchemaIndex` for typed schema metadata, `GraphQLTypeMapper` for kind-aware mapping, `GraphQLDefaultValueRenderer` for default literals, and `GraphQLTypeUsageValidator` for recursive scalar validation. |
64-
| `:gradle-plugin` | Gradle Plugin | Plugin `id("co.anitrend.retrofit.graphql.codegen")`. Registers `GenerateGraphQLSourcesTask`, wires output into source sets. Lives in composite build under `pluginManagement` |
64+
| `:gradle-plugin` | Gradle Plugin | Plugin `id("co.anitrend.retrofit.graphql.codegen")`. Registers `GenerateGraphQLSourcesTask`, wires output into source sets, owns standalone functional tests, and publishes its own marker/implementation artifacts from the composite build. |
6565
| `:serialization-gson` | Android | Gson-backed `GraphQLJson` serialization |
6666
| `:serialization-kotlinx` | Android | kotlinx.serialization-backed `GraphQLJson` |
6767
| `:library` | Android | **Deprecated** aggregator. Re-exports all modules via `api()`. Contains type aliases from old `io.github.wax911.library` package |
@@ -83,7 +83,7 @@ The project is organized into composable modules under the `co.anitrend.retrofit
8383
- **Toolchain**: Java/Kotlin 21 (pinned in `.java-version`). Local dev uses `jenv`; CI uses `actions/setup-java`.
8484
- **SDK**: `compileSdk=37`, `minSdk=23`, `targetSdk=37`.
8585
- **Formatting**: Ktlint via Spotless. License header at `spotless/copyright.kt`.
86-
- **Publishing**: JitPack. JitPack badge/coordinates at top of README.
86+
- **Publishing**: JitPack. Root modules publish from the main build; the standalone `:gradle-plugin` composite build must also publish separately. `.jitpack.yml` runs both `./gradlew build publishToMavenLocal` and `./gradlew -p gradle-plugin publishToMavenLocal` so plugin marker + implementation artifacts and the included `:codegen-core` dependency are materialized for JitPack.
8787
- **CI**: Only `:library` is built/tested in CI environments. It serves as the aggregate facade and transitively builds all sub-modules via `api()` deps. `:app` is excluded in CI (see `settings.gradle.kts`).
8888
- **Dokka**: Generated via `buildSrc` `AndroidOptions.kt`. Published at `https://anitrend.github.io/retrofit-graphql/`. Currently generates from `:library` only; multi-module Dokka is a planned follow-up.
8989

MIGRATION.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,17 @@ dependencies {
7171
If you want to replace asset-based discovery with build-time code generation:
7272

7373
```kotlin
74+
// settings.gradle.kts
75+
pluginManagement {
76+
repositories {
77+
maven(url = uri("https://jitpack.io"))
78+
gradlePluginPortal()
79+
mavenCentral()
80+
google()
81+
}
82+
}
83+
84+
// build.gradle.kts
7485
plugins {
7586
id("co.anitrend.retrofit.graphql.codegen") version "{tag}"
7687
}
@@ -117,18 +128,33 @@ Generated output (under `build/generated/source/graphql/`) includes:
117128

118129
Wire the registry into your converter via Koin or manual construction:
119130
```kotlin
120-
// Koin
131+
// Koin, registry-only (no Context required)
121132
single<GraphQLDocumentRegistry> { GeneratedGraphQLRegistry }
133+
factory { GraphConverter.create(registry = get()) }
134+
135+
// Koin, mixed asset + codegen fallback
122136
factory { GraphConverter.create(context = get(), registry = get()) }
123137

124-
// Manual
125-
val factory = GraphConverter.create(
138+
// Manual, registry-only
139+
val registryOnlyFactory = GraphConverter.create(
140+
registry = GeneratedGraphQLRegistry,
141+
)
142+
143+
// Manual, custom Gson + registry-only
144+
val customGsonFactory = GraphConverter.create(
145+
gson = GsonBuilder().serializeNulls().create(),
146+
registry = GeneratedGraphQLRegistry,
147+
)
148+
149+
// Manual, mixed asset + codegen fallback
150+
val mixedFactory = GraphConverter.create(
126151
context = context,
127152
registry = GeneratedGraphQLRegistry,
128153
)
129154
```
130155

131-
> **Note:** `GraphConverter.create(context, registry)` is the recommended factory for codegen consumers. It wires the generated registry while keeping asset-based discovery as a fallback.
156+
> **Note:** `GraphConverter.create(registry)` and `GraphConverter.create(gson, registry)` are the new codegen-first factories. Use `GraphConverter.create(context, registry)` when you still want asset-based fallback during a mixed migration.
157+
> **Note:** In registry-only mode, unresolved operations do not throw during conversion. The request is still built and the serialized GraphQL `query` remains `null`.
132158
133159
### Multipart Uploads
134160

@@ -263,6 +289,8 @@ New (with optional registry for codegen):
263289
GraphConverter(processor, gson, registry = GeneratedGraphQLRegistry)
264290
// or via static factory:
265291
GraphConverter.create(androidContext(), registry = GeneratedGraphQLRegistry)
292+
GraphConverter.create(registry = GeneratedGraphQLRegistry)
293+
GraphConverter.create(gson, registry = GeneratedGraphQLRegistry)
266294
```
267295

268296
### Query Resolution

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,52 @@ dependencies {
103103
104104
For code generation support, apply the Gradle plugin and add a `retrofitGraphQL { }` config block. The plugin generates operation constants, a document registry, enum classes, variable classes, and typed request helpers. See [MIGRATION.md](MIGRATION.md) for the full migration guide and the [wiki Code Generation page](https://github.com/AniTrend/retrofit-graphql/wiki/Codegen) for the DSL reference.
105105

106+
### Gradle Plugin Consumption
107+
108+
The codegen plugin can now be consumed through the Gradle `plugins {}` DSL.
109+
110+
```kotlin
111+
pluginManagement {
112+
repositories {
113+
mavenLocal() // optional for local verification / development
114+
maven(url = uri("https://jitpack.io"))
115+
gradlePluginPortal()
116+
mavenCentral()
117+
google()
118+
}
119+
}
120+
121+
plugins {
122+
id("co.anitrend.retrofit.graphql.codegen") version "{latest_version}"
123+
}
124+
```
125+
126+
> **JitPack note:** this repository now ships a root `.jitpack.yml` that runs both the main build publication and the standalone `gradle-plugin` publication path. Local verification in this repository resolves the plugin from `mavenLocal()` using the published plugin marker artifact; remote JitPack resolution should follow the same published metadata flow.
127+
128+
### Converter Factories for Codegen Registries
129+
130+
Codegen-only consumers no longer need an Android `Context` just to use the generated registry:
131+
132+
```kotlin
133+
val converter = GraphConverter.create(registry = GeneratedGraphQLRegistry)
134+
135+
val customGsonConverter = GraphConverter.create(
136+
gson = GsonBuilder().serializeNulls().create(),
137+
registry = GeneratedGraphQLRegistry,
138+
)
139+
```
140+
141+
If you want generated documents first **and** asset-based fallback for mixed migrations, keep using the context-backed overload:
142+
143+
```kotlin
144+
val converter = GraphConverter.create(
145+
context = context,
146+
registry = GeneratedGraphQLRegistry,
147+
)
148+
```
149+
150+
In registry-only mode, if an operation is missing from the registry, the request body is still built and the serialized GraphQL `query` remains `null`.
151+
106152
- __Optional R8 / ProGuard Rules__
107153

108154
If you are using R8 the shrinking and obfuscation rules are included automatically.

android-assets/src/main/kotlin/co/anitrend/retrofit/graphql/annotation/processor/contract/AbstractGraphProcessor.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ abstract class AbstractGraphProcessor {
4848
* @param annotations A collection of method annotation from an ongoing request
4949
*
5050
* @return GraphQL query in the form of [String] or null if the request method was not
51-
* annotated with [GraphQuery] or if the no such file could be found
51+
* annotated with [GraphQuery], if no such file could be found, or when a no-op processor is
52+
* intentionally used for registry-only runtime lookup.
5253
*/
5354
abstract fun getQuery(annotations: Array<out Annotation>): String?
5455

api/src/main/kotlin/co/anitrend/retrofit/graphql/model/GraphQLDocumentRegistry.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ package co.anitrend.retrofit.graphql.model
2020
* A registry that provides GraphQL document text and APQ hash for a given operation name.
2121
*
2222
* Implementations can be generated at build time (e.g., [GeneratedGraphQLRegistry]) or
23-
* provided manually by the consumer.
23+
* provided manually by the consumer. Runtime converter integrations check this registry before
24+
* attempting legacy asset-based discovery.
2425
*/
2526
public interface GraphQLDocumentRegistry {
2627
/**

api/src/main/kotlin/co/anitrend/retrofit/graphql/model/request/QueryContainerBuilder.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,10 @@ open class QueryContainerBuilder(
8383
}
8484

8585
/**
86-
* Should only be called by the GraphQLConverter or any other subclasses of it
87-
* after the query has been added to the request
86+
* Should only be called by the GraphQL converter after query resolution has been attempted.
87+
*
88+
* The built request may still contain a `null` query when neither a generated registry nor an
89+
* asset-backed processor can resolve the referenced operation.
8890
*
8991
* @see co.anitrend.retrofit.graphql.converter.GraphConverter
9092
*/

gradle-plugin/build.gradle.kts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
1+
import co.anitrend.retrofit.graphql.buildSrc.plugin.components.applyStandaloneBuildCoordinates
12
import co.anitrend.retrofit.graphql.buildSrc.plugin.components.configureSpotlessForJvm
3+
import org.gradle.api.tasks.SourceSetContainer
4+
import org.gradle.api.tasks.testing.Test
5+
import org.gradle.api.publish.maven.MavenPublication
26

37
plugins {
48
kotlin("jvm")
59
`java-gradle-plugin`
10+
`maven-publish`
611
}
712

13+
applyStandaloneBuildCoordinates()
14+
815
configureSpotlessForJvm()
916

17+
val sourceSets = the<SourceSetContainer>()
18+
val functionalTestSourceSet = sourceSets.create("functionalTest")
19+
20+
configurations[functionalTestSourceSet.implementationConfigurationName].extendsFrom(
21+
configurations.testImplementation.get(),
22+
)
23+
configurations[functionalTestSourceSet.runtimeOnlyConfigurationName].extendsFrom(
24+
configurations.testRuntimeOnly.get(),
25+
)
26+
1027
gradlePlugin {
28+
testSourceSets(functionalTestSourceSet)
1129
plugins {
1230
create("retrofitGraphQL") {
1331
id = "co.anitrend.retrofit.graphql.codegen"
@@ -16,10 +34,65 @@ gradlePlugin {
1634
}
1735
}
1836

37+
publishing {
38+
publications.withType(MavenPublication::class.java).configureEach {
39+
pom {
40+
name.set("Retrofit GraphQL Gradle Plugin")
41+
description.set("Standalone Gradle plugin for retrofit-graphql GraphQL source generation")
42+
url.set("https://github.com/anitrend/retrofit-graphql")
43+
licenses {
44+
license {
45+
name.set("Apache License, Version 2.0")
46+
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
47+
}
48+
}
49+
developers {
50+
developer {
51+
id.set("wax911")
52+
name.set("Maxwell Mapako")
53+
organizationUrl.set("https://github.com/anitrend")
54+
}
55+
}
56+
}
57+
}
58+
}
59+
1960
dependencies {
2061
api(project(":codegen-core"))
2162
implementation(libs.kotlinpoet)
2263
implementation(gradleApi())
2364
compileOnly(libs.android.gradle.plugin)
65+
66+
add(functionalTestSourceSet.implementationConfigurationName, gradleTestKit())
67+
add(functionalTestSourceSet.implementationConfigurationName, kotlin("test"))
68+
add(functionalTestSourceSet.implementationConfigurationName, libs.junit)
2469
}
2570

71+
val functionalTest = tasks.register<Test>("functionalTest") {
72+
description = "Runs Gradle TestKit functional tests"
73+
group = "verification"
74+
testClassesDirs = functionalTestSourceSet.output.classesDirs
75+
classpath = functionalTestSourceSet.runtimeClasspath
76+
filter {
77+
excludeTestsMatching("co.anitrend.retrofit.graphql.codegen.PublishedConsumerPluginResolutionTest")
78+
}
79+
shouldRunAfter(tasks.test)
80+
}
81+
82+
val publishedConsumerTest = tasks.register<Test>("publishedConsumerTest") {
83+
description = "Verifies plugin resolution from mavenLocal"
84+
group = "verification"
85+
testClassesDirs = functionalTestSourceSet.output.classesDirs
86+
classpath = functionalTestSourceSet.runtimeClasspath
87+
// Runs separately from functionalTest because it validates the published-marker flow
88+
// and needs fresh local publications before executing.
89+
dependsOn("publishToMavenLocal")
90+
filter {
91+
includeTestsMatching("co.anitrend.retrofit.graphql.codegen.PublishedConsumerPluginResolutionTest")
92+
}
93+
shouldRunAfter(functionalTest)
94+
}
95+
96+
tasks.check {
97+
dependsOn(functionalTest)
98+
}

0 commit comments

Comments
 (0)