diff --git a/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/CodegenPlugin.kt b/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/CodegenPlugin.kt index 9bcfdd879..b97d66730 100644 --- a/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/CodegenPlugin.kt +++ b/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/CodegenPlugin.kt @@ -44,7 +44,8 @@ class CodegenPlugin : Plugin { val javaConvention = project.convention.getPlugin(JavaPluginConvention::class.java) val sourceSets = javaConvention.sourceSets val mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME) - val outputDir = generateJavaTaskProvider.map(GenerateJavaTask::getOutputDir) + val outputDir = generateJavaTaskProvider.flatMap(GenerateJavaTask::outputDir) + mainSourceSet.java.srcDirs(project.files(outputDir).builtBy(generateJavaTaskProvider)) project.afterEvaluate { p -> 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 e6e050063..e0dc2d2ac 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 @@ -22,136 +22,177 @@ import com.netflix.graphql.dgs.codegen.CodeGen import com.netflix.graphql.dgs.codegen.CodeGenConfig import com.netflix.graphql.dgs.codegen.Language import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.ProjectLayout +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper -import java.io.File import java.nio.file.Paths -import java.util.* - -open class GenerateJavaTask : DefaultTask() { - @Input - var generatedSourcesDir: String = project.buildDir.absolutePath - - @InputFiles - var schemaPaths = mutableListOf("${project.projectDir}/src/main/resources/schema") - - @Input - var packageName = "com.netflix.dgs.codegen.generated" - - @Input - var subPackageNameClient = "client" - - @Input - var subPackageNameDatafetchers = "datafetchers" - - @Input - var subPackageNameTypes = "types" - - private val hasKotlinPluginWrapperClass = try { - this.javaClass.classLoader.loadClass("org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper") - true - } catch (ex: Exception) { - false - } - - @Input - var language = if (hasKotlinPluginWrapperClass && project.plugins.hasPlugin(KotlinPluginWrapper::class.java)) "KOTLIN" else "JAVA" +import java.util.Locale +import javax.inject.Inject + +abstract class GenerateJavaTask @Inject constructor( + projectLayout: ProjectLayout, + providerFactory: ProviderFactory, + objectFactory: ObjectFactory +) : DefaultTask() { + @get:Input + val generatedSourcesDir: Property = objectFactory.property(String::class.java) + .convention(projectLayout.buildDirectory.map { it.asFile.absolutePath }) + + @get:InputFiles + val schemaPaths: ListProperty = objectFactory.listProperty(Any::class.java) + .convention(listOf(projectLayout.projectDirectory.dir("src/main/resources/schema").toString())) + + @get:Input + val packageName: Property = objectFactory.property(String::class.java) + .convention("com.netflix.dgs.codegen.generated") + + @get:Input + val subPackageNameClient: Property = objectFactory.property(String::class.java) + .convention("client") + + @get:Input + val subPackageNameDatafetchers: Property = objectFactory.property(String::class.java) + .convention("datafetchers") + + @get:Input + val subPackageNameTypes: Property = objectFactory.property(String::class.java) + .convention("types") + + @get:Input + val language: Property = objectFactory.property(String::class.java) + .convention( + providerFactory.provider { + val hasKotlinPluginWrapperClass = try { + this.javaClass.classLoader.loadClass("org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper") + true + } catch (ex: Exception) { + false + } + if (hasKotlinPluginWrapperClass && project.plugins.hasPlugin(KotlinPluginWrapper::class.java)) "KOTLIN" else "JAVA" + } + ) - @Input - var typeMapping = mutableMapOf() + @get:Input + val typeMapping: MapProperty = objectFactory.mapProperty(String::class.java, String::class.java) - @Input - var generateBoxedTypes = false + @get:Input + val generateBoxedTypes: Property = objectFactory.property(Boolean::class.java) + .convention(false) - @Input - var generateClient = false + @get:Input + val generateClient: Property = objectFactory.property(Boolean::class.java) + .convention(false) - @Input - var generateDataTypes = true + @get:Input + val generateDataTypes: Property = objectFactory.property(Boolean::class.java) + .convention(true) - @Input - var generateInterfaces = false + @get:Input + val generateInterfaces: Property = objectFactory.property(Boolean::class.java) + .convention(false) - @Input - var generateInterfaceSetters = true + @get:Input + val generateInterfaceSetters: Property = objectFactory.property(Boolean::class.java) + .convention(true) - @OutputDirectory - fun getOutputDir(): File { - return Paths.get("$generatedSourcesDir/generated/sources/dgs-codegen").toFile() - } + @get:OutputDirectory + val outputDir: DirectoryProperty = objectFactory.directoryProperty() + .convention( + generatedSourcesDir.flatMap { baseDir -> + projectLayout.dir(providerFactory.provider { project.file("$baseDir/generated/sources/dgs-codegen") }) + } + ) - @OutputDirectory - fun getExampleOutputDir(): File { - return Paths.get("$generatedSourcesDir/generated/sources/dgs-codegen-generated-examples").toFile() - } + @get:OutputDirectory + val exampleOutputDir: DirectoryProperty = objectFactory.directoryProperty() + .convention( + generatedSourcesDir.flatMap { baseDir -> + projectLayout.dir(providerFactory.provider { project.file("$baseDir/generated/sources/dgs-codegen-generated-examples") }) + } + ) - @Input - var includeQueries = mutableListOf() + @get:Input + val includeQueries: SetProperty = objectFactory.setProperty(String::class.java) - @Input - var includeMutations = mutableListOf() + @get:Input + val includeMutations: SetProperty = objectFactory.setProperty(String::class.java) - @Input - var includeSubscriptions = mutableListOf() + @get:Input + val includeSubscriptions: SetProperty = objectFactory.setProperty(String::class.java) - @Input - var skipEntityQueries = false + @get:Input + val skipEntityQueries: Property = objectFactory.property(Boolean::class.java) + .convention(false) - @Input - var shortProjectionNames = false + @get:Input + val shortProjectionNames: Property = objectFactory.property(Boolean::class.java) + .convention(false) - @Input - var omitNullInputFields = false + @get:Input + val omitNullInputFields: Property = objectFactory.property(Boolean::class.java) + .convention(false) - @Input - var maxProjectionDepth = 10 + @get:Input + val maxProjectionDepth: Property = objectFactory.property(Int::class.javaObjectType) + .convention(10) - @Input - var kotlinAllFieldsOptional = false + @get:Input + val kotlinAllFieldsOptional: Property = objectFactory.property(Boolean::class.java) + .convention(false) - @Input - var snakeCaseConstantNames = false + @get:Input + val snakeCaseConstantNames: Property = objectFactory.property(Boolean::class.java) + .convention(false) @TaskAction fun generate() { - val schemaPaths = schemaPaths.map { Paths.get(it.toString()).toFile() }.sorted().toSet() - schemaPaths.filter { !it.exists() }.forEach { - logger.warn("Schema location ${it.absolutePath} does not exist") + val schemaPaths = schemaPaths.get().asSequence() + .map { Paths.get(it.toString()).toFile() } + .toSortedSet() + schemaPaths.asSequence().filter { !it.exists() }.forEach { + logger.warn("Schema location {} does not exist", it.absolutePath) } logger.info("Processing schema files:") schemaPaths.forEach { - logger.info("Processing $it") + logger.info("Processing {}", it) } val config = CodeGenConfig( schemas = emptySet(), schemaFiles = schemaPaths, - outputDir = getOutputDir().toPath(), - examplesOutputDir = getExampleOutputDir().toPath(), + outputDir = outputDir.get().asFile.toPath(), + examplesOutputDir = exampleOutputDir.get().asFile.toPath(), writeToFiles = true, - packageName = packageName, - subPackageNameClient = subPackageNameClient, - subPackageNameDatafetchers = subPackageNameDatafetchers, - subPackageNameTypes = subPackageNameTypes, - language = Language.valueOf(language.uppercase(Locale.getDefault())), - generateBoxedTypes = generateBoxedTypes, - generateClientApi = generateClient, - generateInterfaces = generateInterfaces, - generateInterfaceSetters = generateInterfaceSetters, - typeMapping = typeMapping, - includeQueries = includeQueries.toSet(), - includeMutations = includeMutations.toSet(), - includeSubscriptions = includeSubscriptions.toSet(), - skipEntityQueries = skipEntityQueries, - shortProjectionNames = shortProjectionNames, - generateDataTypes = generateDataTypes, - omitNullInputFields = omitNullInputFields, - maxProjectionDepth = maxProjectionDepth, - kotlinAllFieldsOptional = kotlinAllFieldsOptional, - snakeCaseConstantNames = snakeCaseConstantNames + packageName = packageName.get(), + subPackageNameClient = subPackageNameClient.get(), + subPackageNameDatafetchers = subPackageNameDatafetchers.get(), + subPackageNameTypes = subPackageNameTypes.get(), + language = Language.valueOf(language.get().uppercase(Locale.getDefault())), + generateBoxedTypes = generateBoxedTypes.get(), + generateClientApi = generateClient.get(), + generateInterfaces = generateInterfaces.get(), + generateInterfaceSetters = generateInterfaceSetters.get(), + typeMapping = typeMapping.get(), + includeQueries = includeQueries.get(), + includeMutations = includeMutations.get(), + includeSubscriptions = includeSubscriptions.get(), + skipEntityQueries = skipEntityQueries.get(), + shortProjectionNames = shortProjectionNames.get(), + generateDataTypes = generateDataTypes.get(), + omitNullInputFields = omitNullInputFields.get(), + maxProjectionDepth = maxProjectionDepth.get(), + kotlinAllFieldsOptional = kotlinAllFieldsOptional.get(), + snakeCaseConstantNames = snakeCaseConstantNames.get() ) logger.info("Codegen config: {}", config) diff --git a/graphql-dgs-codegen-gradle/src/test/kotlin/com/netflix/graphql/dgs/CodegenGradlePluginTest.kt b/graphql-dgs-codegen-gradle/src/test/kotlin/com/netflix/graphql/dgs/CodegenGradlePluginTest.kt index bd6763255..6b6d94830 100644 --- a/graphql-dgs-codegen-gradle/src/test/kotlin/com/netflix/graphql/dgs/CodegenGradlePluginTest.kt +++ b/graphql-dgs-codegen-gradle/src/test/kotlin/com/netflix/graphql/dgs/CodegenGradlePluginTest.kt @@ -23,19 +23,32 @@ package com.netflix.graphql.dgs import org.assertj.core.api.Assertions.assertThat import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import java.io.File +import org.junit.jupiter.api.io.TempDir +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.io.path.div /** * A simple unit test for the 'com.netflix.graphql.dgs.greeting' plugin. */ class CodegenGradlePluginTest { + @TempDir + lateinit var projectDir: Path + + @BeforeEach + fun setUp() { + val projectResourcePath = Paths.get("src", "test", "resources", "test-project") + projectResourcePath.toFile().copyRecursively(target = projectDir.toFile()) + } + @Test fun taskRegisteredSuccessfully() { // get a list of Gradle tasks val result = GradleRunner.create() - .withProjectDir(File("src/test/resources/test-project/")) + .withProjectDir(projectDir.toFile()) .withPluginClasspath() .withArguments( "-c", "smoke_test_settings.gradle", @@ -51,7 +64,7 @@ class CodegenGradlePluginTest { fun taskDependenciesRegisteredSuccessfully() { // get a list of Gradle tasks val result = GradleRunner.create() - .withProjectDir(File("src/test/resources/test-project/")) + .withProjectDir(projectDir.toFile()) .withPluginClasspath() .withArguments( "-c", "smoke_test_settings.gradle", @@ -69,7 +82,7 @@ class CodegenGradlePluginTest { fun sourcesGenerated() { // build a project val result = GradleRunner.create() - .withProjectDir(File("src/test/resources/test-project/")) + .withProjectDir(projectDir.toFile()) .withPluginClasspath() .withArguments( "--stacktrace", @@ -83,14 +96,15 @@ class CodegenGradlePluginTest { // Verify the result assertThat(result.task(":build")).extracting { it?.outcome }.isEqualTo(SUCCESS) // Verify that POJOs are generated in the configured directory - assertThat(File(EXPECTED_PATH + "Result.java").exists()).isTrue + + assertThat(projectDir / EXPECTED_PATH / "Result.java").exists() } @Test fun sourcesGenerated_UsingDefaultPath() { // build a project val result = GradleRunner.create() - .withProjectDir(File("src/test/resources/test-project/")) + .withProjectDir(projectDir.toFile()) .withPluginClasspath() .withArguments( "--stacktrace", @@ -105,14 +119,36 @@ class CodegenGradlePluginTest { // Verify the result assertThat(result.task(":build")).extracting { it?.outcome }.isEqualTo(SUCCESS) // Verify that POJOs are generated in the configured directory - assertThat(File(EXPECTED_DEFAULT_PATH + "Result.java").exists()).isTrue + assertThat(projectDir / EXPECTED_DEFAULT_PATH / "Result.java").exists() + assertThat(projectDir / EXPECTED_DEFAULT_PATH / "Result.java").exists() + } + + @Test + fun sourcesGenerated_UsingOverridenOutputDir() { + // build a project + val result = GradleRunner.create() + .withProjectDir(projectDir.toFile()) + .withPluginClasspath() + .withArguments( + "--stacktrace", + "-b", "build_with_output_dir.gradle", + "clean", + "build" + ).forwardOutput() + .withDebug(true) + .build() + + // Verify the result + assertThat(result.task(":build")).extracting { it?.outcome }.isEqualTo(SUCCESS) + // Verify that POJOs are generated in the configured directory + assertThat(projectDir / BUILD_DIR / "custom-generated" / SOURCES_PATH / "Result.java").exists() } @Test fun sourcesGenerated_OmitNullInputFields() { // build a project val result = GradleRunner.create() - .withProjectDir(File("src/test/resources/test-project/")) + .withProjectDir(projectDir.toFile()) .withPluginClasspath() .withArguments( "--stacktrace", @@ -128,12 +164,14 @@ class CodegenGradlePluginTest { // Verify the result assertThat(result.task(":build")).extracting { it?.outcome }.isEqualTo(SUCCESS) // Verify that POJOs are generated in the configured directory - assertThat(File(EXPECTED_DEFAULT_PATH + "Result.java").exists()).isTrue - assertThat(File(EXPECTED_DEFAULT_PATH + "Filter.java").exists()).isTrue + assertThat(projectDir / EXPECTED_DEFAULT_PATH / "Result.java").exists() + assertThat(projectDir / EXPECTED_DEFAULT_PATH / "Filter.java").exists() } companion object { - const val EXPECTED_PATH = "src/test/resources/test-project/build/graphql/generated/sources/dgs-codegen/com/netflix/testproject/graphql/types/" - const val EXPECTED_DEFAULT_PATH = "src/test/resources/test-project/build/generated/sources/dgs-codegen/com/netflix/testproject/graphql/types/" + private val BUILD_DIR = Paths.get("build") + private val SOURCES_PATH = Paths.get("com", "netflix", "testproject", "graphql", "types") + private val EXPECTED_PATH = BUILD_DIR / "graphql" / "generated" / "sources" / "dgs-codegen" / SOURCES_PATH + private val EXPECTED_DEFAULT_PATH = BUILD_DIR / "generated" / "sources" / "dgs-codegen" / SOURCES_PATH } } diff --git a/graphql-dgs-codegen-gradle/src/test/resources/test-project/build_with_output_dir.gradle b/graphql-dgs-codegen-gradle/src/test/resources/test-project/build_with_output_dir.gradle new file mode 100644 index 000000000..c3d4414d4 --- /dev/null +++ b/graphql-dgs-codegen-gradle/src/test/resources/test-project/build_with_output_dir.gradle @@ -0,0 +1,34 @@ +/* + * + * Copyright 2020 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +plugins { + id 'java' + id 'com.netflix.dgs.codegen' +} + +configurations { + // injected by Gradle Runner through test configuration, see CodegenGradlePluginTest + CodeGenConfiguration.exclude group: "com.netflix.graphql.dgs.codegen", module: "graphql-dgs-codegen-core" +} + +generateJava { + packageName = 'com.netflix.testproject.graphql' + outputDir = file("${buildDir}/custom-generated/") +} + +codegen.clientCoreConventionsEnabled = false