Skip to content

Commit b0520ad

Browse files
committed
feat(plugin): new GraalVm metadata Gradle task
Create new Gradle task that automatically generates GraalVm reachability metadata for GraphQL Kotlin servers. Following metadata files are generated: * `native-image.properties` * `reflect-config.json` * `resource-config.json` Usage: ``` plugins { kotlin("jvm") version "1.7.21" application id("org.graalvm.buildtools.native") version "0.9.20" id("com.expediagroup.graphql") version $graphQLKotlinVersion } // other build configuration goes here graphql { graalVm { packages = listOf("com.example") } } ```
1 parent ea6d503 commit b0520ad

File tree

18 files changed

+615
-107
lines changed

18 files changed

+615
-107
lines changed

gradle/libs.versions.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ classgraph = "4.8.149"
44
dataloader = "3.2.0"
55
federation = "2.2.0"
66
graphql-java = "19.2"
7+
graalvm = "0.9.20"
78
jackson = "2.14.1"
89
kotlin = "1.7.21"
910
kotlinx-benchmark = "0.4.4"
@@ -108,6 +109,7 @@ wiremock-standalone = { group = "com.github.tomakehurst", name = "wiremock-jre8-
108109
# build src plugin libraries
109110
detekt-plugin = { group = "io.gitlab.arturbosch.detekt", name = "detekt-gradle-plugin", version.ref = "detekt" }
110111
dokka-plugin = { group = "org.jetbrains.dokka", name = "dokka-gradle-plugin", version.ref = "dokka" }
112+
graalvm-plugin = { group = "org.graalvm.buildtools.native", name = "org.graalvm.buildtools.native.gradle.plugin", version.ref = "graalvm" }
111113
kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
112114
ktlint-plugin = { group = "org.jlleitschuh.gradle", name = "ktlint-gradle", version.ref = "ktlint-plugin" }
113115

@@ -135,5 +137,6 @@ plugin-publish = { id = "com.gradle.plugin-publish", version.ref = "plugin-publi
135137
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
136138

137139
# test projects
138-
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
139140
android-application = { id = "com.android.application", version.ref = "android-plugin" }
141+
graalvm-native = { id = "org.graalvm.buildtools.native", version.ref = "graalvm" }
142+
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

plugins/graphql-kotlin-gradle-plugin/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ plugins {
99

1010
dependencies {
1111
implementation(libs.kotlin.gradle.api)
12-
compileOnly(libs.android.plugin)
1312

13+
compileOnly(libs.android.plugin)
14+
compileOnly(libs.graalvm.plugin)
1415
compileOnly(projects.graphqlKotlinClientGenerator)
1516
compileOnly(projects.graphqlKotlinSdlGenerator)
17+
compileOnly(projects.graphqlKotlinGraalvmMetadataGenerator)
1618

1719
testImplementation(libs.wiremock.jre8)
1820
testImplementation(libs.junit.params)

plugins/graphql-kotlin-gradle-plugin/src/main/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLGradlePlugin.kt

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 Expedia, Inc
2+
* Copyright 2023 Expedia, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,26 +20,33 @@ import com.expediagroup.graphql.plugin.gradle.tasks.DOWNLOAD_SDL_TASK_NAME
2020
import com.expediagroup.graphql.plugin.gradle.tasks.GENERATE_CLIENT_TASK_NAME
2121
import com.expediagroup.graphql.plugin.gradle.tasks.GENERATE_SDL_TASK_NAME
2222
import com.expediagroup.graphql.plugin.gradle.tasks.GENERATE_TEST_CLIENT_TASK_NAME
23+
import com.expediagroup.graphql.plugin.gradle.tasks.GRAALVM_METADATA_TASK_NAME
2324
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLDownloadSDLTask
2425
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateClientTask
2526
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateSDLTask
2627
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateTestClientTask
28+
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGraalVmMetadataTask
2729
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLIntrospectSchemaTask
2830
import com.expediagroup.graphql.plugin.gradle.tasks.INTROSPECT_SCHEMA_TASK_NAME
31+
import org.graalvm.buildtools.gradle.NativeImagePlugin.NATIVE_COMPILE_TASK_NAME
2932
import org.gradle.api.Plugin
3033
import org.gradle.api.Project
3134
import org.gradle.api.file.DirectoryProperty
35+
import org.gradle.api.plugins.ApplicationPlugin
36+
import org.gradle.api.plugins.JavaApplication
3237
import org.gradle.api.tasks.SourceSetContainer
38+
import org.gradle.jvm.tasks.Jar
3339
import java.io.File
3440

3541
private const val PLUGIN_EXTENSION_NAME = "graphql"
3642
private const val GENERATE_CLIENT_CONFIGURATION = "graphqlClient"
3743
private const val GENERATE_SDL_CONFIGURATION = "graphqlSDL"
44+
private const val GRAALVM_METADATA_CONFIGURATION = "graphqlGraalVM"
45+
private const val GRAALVM_PLUGIN_NAME = "org.graalvm.buildtools.native"
3846

3947
/**
4048
* GraphQL Kotlin Gradle Plugin
4149
*/
42-
@Suppress("UnstableApiUsage")
4350
class GraphQLGradlePlugin : Plugin<Project> {
4451

4552
override fun apply(project: Project) {
@@ -49,7 +56,7 @@ class GraphQLGradlePlugin : Plugin<Project> {
4956
val extension = project.extensions.create(PLUGIN_EXTENSION_NAME, GraphQLPluginExtension::class.java)
5057
project.afterEvaluate {
5158
processExtensionConfiguration(project, extension)
52-
configureTaskClasspaths(project)
59+
configureTasks(project)
5360
}
5461
}
5562

@@ -69,6 +76,14 @@ class GraphQLGradlePlugin : Plugin<Project> {
6976

7077
configuration.dependencies.add(project.dependencies.create("com.expediagroup:graphql-kotlin-sdl-generator:$DEFAULT_PLUGIN_VERSION"))
7178
}
79+
80+
project.configurations.create(GRAALVM_METADATA_CONFIGURATION) { configuration ->
81+
configuration.isVisible = true
82+
configuration.isTransitive = true
83+
configuration.description = "Configuration for generating GraalVM reflect metadata"
84+
85+
configuration.dependencies.add(project.dependencies.create("com.expediagroup:graphql-kotlin-graalvm-metadata-generator:$DEFAULT_PLUGIN_VERSION"))
86+
}
7287
}
7388

7489
private fun registerTasks(project: Project) {
@@ -77,6 +92,15 @@ class GraphQLGradlePlugin : Plugin<Project> {
7792
project.tasks.register(GENERATE_TEST_CLIENT_TASK_NAME, GraphQLGenerateTestClientTask::class.java)
7893
project.tasks.register(GENERATE_SDL_TASK_NAME, GraphQLGenerateSDLTask::class.java)
7994
project.tasks.register(INTROSPECT_SCHEMA_TASK_NAME, GraphQLIntrospectSchemaTask::class.java)
95+
project.tasks.register(GRAALVM_METADATA_TASK_NAME, GraphQLGraalVmMetadataTask::class.java)
96+
97+
// create new source for GraalVM metadata task
98+
if (project.plugins.hasPlugin(GRAALVM_PLUGIN_NAME)) {
99+
val graphQLGraalVmSource = project.extensions.getByType(SourceSetContainer::class.java).create("graphqlGraalVm")
100+
project.tasks.withType(Jar::class.java).configureEach { jarTask ->
101+
jarTask.from(graphQLGraalVmSource.runtimeClasspath)
102+
}
103+
}
80104
}
81105

82106
private fun processExtensionConfiguration(project: Project, extension: GraphQLPluginExtension) {
@@ -131,9 +155,22 @@ class GraphQLGradlePlugin : Plugin<Project> {
131155
val generateSchemaTask = project.tasks.named(GENERATE_SDL_TASK_NAME, GraphQLGenerateSDLTask::class.java).get()
132156
generateSchemaTask.packages.set(supportedPackages)
133157
}
158+
159+
if (extension.isGraalVmConfigurationAvailable()) {
160+
val supportedPackages = extension.graalVmExtension.packages
161+
if (supportedPackages.isEmpty()) {
162+
throw RuntimeException("Invalid GraphQL graalVm extension configuration - missing required supportedPackages property")
163+
}
164+
165+
val graalVmMetadataTask = project.tasks.named(GRAALVM_METADATA_TASK_NAME, GraphQLGraalVmMetadataTask::class.java).get()
166+
graalVmMetadataTask.packages.set(supportedPackages)
167+
extension.graalVmExtension.mainClassName?.let {
168+
graalVmMetadataTask.mainClassName.set(it)
169+
}
170+
}
134171
}
135172

136-
private fun configureTaskClasspaths(project: Project) {
173+
private fun configureTasks(project: Project) {
137174
val isAndroidProject = project.plugins.hasPlugin("com.android.application") || project.plugins.hasPlugin("com.android.library")
138175
val clientGeneratingTaskNames = mutableListOf<GraphQLGenerateClientTask>()
139176
val testClientGeneratingTaskNames = mutableListOf<GraphQLGenerateTestClientTask>()
@@ -183,6 +220,43 @@ class GraphQLGradlePlugin : Plugin<Project> {
183220
// we also need to explicitly configure compile dependencies
184221
configureAndroidCompileTasks(project, clientGeneratingTaskNames, testClientGeneratingTaskNames)
185222
}
223+
224+
// auto-configure graphQLGraalVmMetadata task if GraalVM native plugin is applied
225+
if (project.plugins.hasPlugin(GRAALVM_PLUGIN_NAME)) {
226+
project.tasks.withType(GraphQLGraalVmMetadataTask::class.java).configureEach { graalVmMetadataTask ->
227+
// set GraalVM application info
228+
if (project.plugins.hasPlugin(ApplicationPlugin.APPLICATION_PLUGIN_NAME)) {
229+
project.extensions.findByType(JavaApplication::class.java)?.let { javaApplication ->
230+
javaApplication.mainClass.orNull?.let { mainClass ->
231+
graalVmMetadataTask.mainClassName.convention(mainClass)
232+
}
233+
}
234+
}
235+
236+
// create task dependencies
237+
val compileKotlinTask = project.tasks.findByName("compileKotlin") ?: project.tasks.findByName("compileKotlinJvm")
238+
if (compileKotlinTask != null) {
239+
graalVmMetadataTask.dependsOn(compileKotlinTask)
240+
} else {
241+
project.logger.warn("compileKotlin/compileKotlinJvm tasks not found. Unable to auto-configure the generateSDLTask dependency on compile task.")
242+
}
243+
project.tasks.findByName(NATIVE_COMPILE_TASK_NAME)?.dependsOn(graalVmMetadataTask)
244+
245+
// configure source sets
246+
val sourceSetContainer = project.extensions.getByType(SourceSetContainer::class.java)
247+
val graalVmSource = sourceSetContainer.getByName("graphqlGraalVm")
248+
graalVmSource.resources {
249+
it.setSrcDirs(listOf(graalVmMetadataTask.outputDirectory))
250+
}
251+
252+
val mainSourceSet = sourceSetContainer.getByName("main")
253+
graalVmMetadataTask.source(mainSourceSet.output)
254+
graalVmMetadataTask.projectClasspath.setFrom(mainSourceSet.runtimeClasspath)
255+
256+
val configuration = project.configurations.getAt(GRAALVM_METADATA_CONFIGURATION)
257+
graalVmMetadataTask.pluginClasspath.setFrom(configuration)
258+
}
259+
}
186260
}
187261

188262
private fun configureDefaultProjectSourceSet(project: Project, outputDirectory: DirectoryProperty, targetSourceSet: String = "main") {

plugins/graphql-kotlin-gradle-plugin/src/main/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLPluginExtension.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ open class GraphQLPluginExtension {
4040
GraphQLPluginSchemaExtension()
4141
}
4242

43+
private var graalVmExtensionConfigured: Boolean = false
44+
internal val graalVmExtension: GraphQLPluginGraalVmExtension by lazy {
45+
graalVmExtensionConfigured = true
46+
GraphQLPluginGraalVmExtension()
47+
}
48+
4349
/** Plugin configuration for generating GraphQL client. */
4450
fun client(action: Action<GraphQLPluginClientExtension>) {
4551
action.execute(clientExtension)
@@ -49,10 +55,16 @@ open class GraphQLPluginExtension {
4955

5056
internal fun isSchemaConfigurationAvailable(): Boolean = schemaExtensionConfigured
5157

58+
internal fun isGraalVmConfigurationAvailable(): Boolean = graalVmExtensionConfigured
59+
5260
/** Plugin configuration for generating GraphQL schema artifact. */
5361
fun schema(action: Action<GraphQLPluginSchemaExtension>) {
5462
action.execute(schemaExtension)
5563
}
64+
65+
fun graalVm(action: Action<GraphQLPluginGraalVmExtension>) {
66+
action.execute(graalVmExtension)
67+
}
5668
}
5769

5870
open class GraphQLPluginClientExtension {
@@ -102,3 +114,10 @@ open class GraphQLPluginSchemaExtension {
102114
/** List of supported packages that can contain GraphQL schema type definitions. */
103115
var packages: List<String> = emptyList()
104116
}
117+
118+
open class GraphQLPluginGraalVmExtension {
119+
/** List of supported packages that can contain GraphQL schema type definitions. */
120+
var packages: List<String> = emptyList()
121+
/** Application main class name. */
122+
var mainClassName: String? = null
123+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2023 Expedia, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.expediagroup.graphql.plugin.gradle.actions
18+
19+
import com.expediagroup.graphql.plugin.graalvm.generateGraalVmMetadata
20+
import com.expediagroup.graphql.plugin.gradle.parameters.GenerateGraalVmMetadataParameters
21+
import org.gradle.workers.WorkAction
22+
import java.io.File
23+
24+
abstract class GenerateGraalVmMetadataAction : WorkAction<GenerateGraalVmMetadataParameters> {
25+
26+
/**
27+
* Generate GraphQL GraalVM reachability metadata.
28+
*/
29+
override fun execute() {
30+
val supportedPackages: List<String> = parameters.supportedPackages.get()
31+
val mainClassName: String? = parameters.mainClassName.orNull
32+
val targetDirectory: File = parameters.outputDirectory.get()
33+
34+
generateGraalVmMetadata(targetDirectory = targetDirectory, supportedPackages = supportedPackages, mainClassName = mainClassName)
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2023 Expedia, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.expediagroup.graphql.plugin.gradle.parameters
18+
19+
import org.gradle.api.provider.ListProperty
20+
import org.gradle.api.provider.Property
21+
import org.gradle.workers.WorkParameters
22+
import java.io.File
23+
24+
/**
25+
* WorkParameters used for generating GraalVM reachability metadata for GraphQL schema.
26+
*/
27+
interface GenerateGraalVmMetadataParameters : WorkParameters {
28+
/** List of supported packages that can contain GraphQL schema type definitions. */
29+
val supportedPackages: ListProperty<String>
30+
/** Main application class name. */
31+
val mainClassName: Property<String>
32+
/** Directory where to store generated reachability metadata. */
33+
val outputDirectory: Property<File>
34+
}

0 commit comments

Comments
 (0)