diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGen.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGen.kt index 688fde52..96cc8573 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGen.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGen.kt @@ -99,7 +99,7 @@ private class InMemorySchemaSource( private data class DependencySchemaSource( val file: File, - val entryName: String, + val entryIndex: Int, val schema: String, ) @@ -282,20 +282,18 @@ class CodeGen private constructor( } } - jarFile.entries().asSequence().forEach { entry -> + jarFile.entries().asSequence().forEachIndexed { index, entry -> if (!entry.isDirectory && entry.name.hasGraphQLSchemaExtension()) { logger.info("Generating schema from {}: {}", file.name, entry.name) val schema = jarFile.getInputStream(entry).bufferedReader().use { it.readText() } - dependencySchemaSources += DependencySchemaSource(file, entry.name, schema) + dependencySchemaSources += DependencySchemaSource(file, index, schema) } } } } - // Jars are ordered by path, as in 8.7.0. Entries within a jar are ordered by name, not by position in the - // archive: Gradle's classpath fingerprint ignores entry order, so the zip order must not affect the output. dependencySchemaSources - .sortedWith(compareBy { it.file }.thenBy { it.entryName }) + .sortedWith(compareBy { it.file }.thenBy { it.entryIndex }) .mapTo(schemaSources) { InMemorySchemaSource(it.schema, "codegen") } return schemaSources } diff --git a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/SchemaMergingTest.kt b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/SchemaMergingTest.kt index bd2de0f0..c1debd7a 100644 --- a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/SchemaMergingTest.kt +++ b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/SchemaMergingTest.kt @@ -76,7 +76,7 @@ class SchemaMergingTest { } @Test - fun `dependency jar schemas are ordered by entry name instead of zip order`( + fun `dependency jar schemas retain zip entry order`( @TempDir tempDir: Path, ) { val schemaJar = @@ -98,10 +98,30 @@ class SchemaMergingTest { ), ) - listOf(schemaJar, reorderedJar).forEach { + listOf( + schemaJar to listOf("first", "third", "second"), + reorderedJar to listOf("first", "second", "third"), + ).forEach { (jar, expectedFields) -> + val fooType = + CodeGen(CodeGenConfig(schemaJarFilesFromDependencies = listOf(jar), packageName = "com.example")) + .generate() + .javaDataTypes + .single { type -> type.typeSpec().name() == "Foo" } + .typeSpec() + Assertions + .assertThat(fooType.fieldSpecs().map { field -> field.name() }) + .containsExactlyElementsOf(expectedFields) Assertions - .assertThat(fooFields(CodeGenConfig(schemaJarFilesFromDependencies = listOf(it), packageName = "com.example"))) - .containsExactly("first", "second", "third") + .assertThat( + fooType + .methodSpecs() + .single { constructor -> + constructor.isConstructor && constructor.parameters().isNotEmpty() + }.parameters() + .map { parameter -> + parameter.name() + }, + ).containsExactlyElementsOf(expectedFields) } } diff --git a/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/GenerateJavaTask.kt b/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/GenerateJavaTask.kt index a87615fe..ebadf325 100644 --- a/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/GenerateJavaTask.kt +++ b/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/GenerateJavaTask.kt @@ -36,6 +36,7 @@ import org.gradle.api.tasks.* import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper import java.io.File import java.util.* +import java.util.jar.JarFile import javax.inject.Inject @CacheableTask @@ -264,6 +265,26 @@ open class GenerateJavaTask val sortedDgsCodegenJars: ConfigurableFileCollection = objectFactory.fileCollection().from(providerFactory.provider { dgsCodegenClasspath.files.sorted() }) + /** + * `CodeGen` reads schema entries in ZIP order, which Gradle's classpath fingerprint ignores. + * Record that order separately so jars with the same entries in a different order cannot share cached output. + */ + @get:Input + val dependencySchemaEntryOrder: List + get() = + dgsCodegenClasspath.files.sorted().flatMapIndexed { jarIndex, file -> + JarFile(file).use { jar -> + jar + .entries() + .asSequence() + .filter { !it.isDirectory && it.name.isDependencySchemaEntryName() } + .map { "$jarIndex:${it.name}" } + .toList() + } + } + + private fun String.isDependencySchemaEntryName(): Boolean = endsWith(".graphqls") || endsWith(".graphql") || endsWith(".gqls") + @Input val jacksonVersionOverride: ListProperty = objectFactory.listProperty(String::class.java).convention(emptyList()) diff --git a/graphql-dgs-codegen-gradle/src/test/kotlin/com/netflix/graphql/dgs/GenerateJavaTaskBuildCacheTest.kt b/graphql-dgs-codegen-gradle/src/test/kotlin/com/netflix/graphql/dgs/GenerateJavaTaskBuildCacheTest.kt index eb44688b..e5300930 100644 --- a/graphql-dgs-codegen-gradle/src/test/kotlin/com/netflix/graphql/dgs/GenerateJavaTaskBuildCacheTest.kt +++ b/graphql-dgs-codegen-gradle/src/test/kotlin/com/netflix/graphql/dgs/GenerateJavaTaskBuildCacheTest.kt @@ -96,7 +96,7 @@ class GenerateJavaTaskBuildCacheTest { } @Test - fun dependencyJarEntryOrderDoesNotChangeTheOutput( + fun dependencyJarEntryOrderChangesOutputWithoutReusingStaleCache( @TempDir tempDir: File, ) { val cacheDir = File(tempDir, "shared-build-cache") @@ -104,16 +104,20 @@ class GenerateJavaTaskBuildCacheTest { writeSchemaJar(File(projectDir, "schemas.jar"), listOf("c-extension.graphqls", "a-base.graphqls", "b-extension.graphqls")) assertThat(run(projectDir, "--build-cache", "generateJava").task(":generateJava")?.outcome).isEqualTo(SUCCESS) - assertThat(fooFields(projectDir)).containsExactly("base", "fb", "fc") + assertThat(fooFields(projectDir)).containsExactly("base", "fc", "fb") writeSchemaJar(File(projectDir, "schemas.jar"), listOf("a-base.graphqls", "b-extension.graphqls", "c-extension.graphqls")) - run(projectDir, "--build-cache", "generateJava") + assertThat(run(projectDir, "--build-cache", "generateJava").task(":generateJava")?.outcome).isEqualTo(SUCCESS) assertThat(fooFields(projectDir)).containsExactly("base", "fb", "fc") val freshProject = createJarProject(tempDir, "jar-entry-order-fresh", File(tempDir, "unused-cache")) writeSchemaJar(File(freshProject, "schemas.jar"), listOf("a-base.graphqls", "b-extension.graphqls", "c-extension.graphqls")) assertThat(run(freshProject, "--no-build-cache", "generateJava").task(":generateJava")?.outcome).isEqualTo(SUCCESS) assertThat(outputFingerprints(generatedSources(projectDir))).isEqualTo(outputFingerprints(generatedSources(freshProject))) + + writeSchemaJar(File(projectDir, "schemas.jar"), listOf("c-extension.graphqls", "a-base.graphqls", "b-extension.graphqls")) + assertThat(run(projectDir, "--build-cache", "generateJava").task(":generateJava")?.outcome).isEqualTo(FROM_CACHE) + assertThat(fooFields(projectDir)).containsExactly("base", "fc", "fb") } @Test