Skip to content

Commit fcf45d5

Browse files
committed
Add support for invoking AOT to the Gradle plugin
Closes gh-30527
1 parent fd7bb53 commit fcf45d5

File tree

16 files changed

+413
-9
lines changed

16 files changed

+413
-9
lines changed

spring-boot-project/spring-boot-parent/build.gradle

+7
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,13 @@ bom {
126126
]
127127
}
128128
}
129+
library("Native Gradle Plugin", "0.9.11") {
130+
group("org.graalvm.buildtools") {
131+
modules = [
132+
"native-gradle-plugin"
133+
]
134+
}
135+
}
129136
library("Plexus Build API", "0.0.7") {
130137
group("org.sonatype.plexus") {
131138
modules = [

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/build.gradle

+8-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ plugins {
77
id "org.springframework.boot.optional-dependencies"
88
}
99

10-
description = "Spring Boot Gradle Plugin"
10+
description = "Spring Boot Gradle Plugins"
1111

1212
configurations {
1313
documentation
@@ -22,6 +22,7 @@ dependencies {
2222
implementation("org.apache.commons:commons-compress")
2323
implementation("org.springframework:spring-core")
2424

25+
optional("org.graalvm.buildtools:native-gradle-plugin")
2526
optional("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") {
2627
exclude(group: "commons-logging", module: "commons-logging")
2728
}
@@ -44,6 +45,12 @@ gradlePlugin {
4445
description = "Spring Boot Gradle Plugin"
4546
implementationClass = "org.springframework.boot.gradle.plugin.SpringBootPlugin"
4647
}
48+
springBootAotPlugin {
49+
id = "org.springframework.boot.aot"
50+
displayName = "Spring Boot AOT Gradle Plugin"
51+
description = "Spring Boot AOT Gradle Plugin"
52+
implementationClass = "org.springframework.boot.gradle.plugin.SpringBootAotPlugin"
53+
}
4754
}
4855
}
4956

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/index.adoc

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ v{gradle-project-version}
3939
:buildpacks-reference: https://buildpacks.io/docs
4040
:paketo-reference: https://paketo.io/docs
4141
:paketo-java-reference: {paketo-reference}/buildpacks/language-family-buildpacks/java
42+
:nbt-gradle-plugin: https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html
4243

4344

4445

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/reacting.adoc

+13
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,16 @@ When Gradle's {application-plugin}[`application` plugin] is applied to a project
6363
5. Configures the `bootJar` task to use the `mainClassName` property as a convention for the `Start-Class` entry in its manifest.
6464
6. Configures the `bootWar` task to use the `mainClassName` property as a convention for the `Start-Class` entry in its manifest.
6565

66+
67+
68+
[[reacting-to-other-plugins.nbt]]
69+
== Reacting to the GraalVM Native Image Plugin
70+
When the {nbt-gradle-plugin}[GraalVM Native Image plugin] is applied to a project, the Spring Boot plugin:
71+
72+
. Applies the `org.springframework.boot.aot` plugin that:
73+
.. Registers a `GenerateAotSources` task named `generateAotSources` that will generate AOT-optimized source code for the application.
74+
.. Configures the Java compilation and process resources tasks for the `aot` source set to depend upon `generateAotSources`.
75+
. Adds the output of the `aot` source set to the classpath of the `nativeCompile` task.
76+
. Configures the GraalVM extension to disable Toolchain detection.
77+
78+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
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 org.springframework.boot.gradle.plugin;
18+
19+
import org.graalvm.buildtools.gradle.NativeImagePlugin;
20+
import org.graalvm.buildtools.gradle.dsl.GraalVMExtension;
21+
import org.graalvm.buildtools.gradle.tasks.BuildNativeImageTask;
22+
import org.gradle.api.Action;
23+
import org.gradle.api.Plugin;
24+
import org.gradle.api.Project;
25+
import org.gradle.api.plugins.JavaPlugin;
26+
import org.gradle.api.plugins.JavaPluginExtension;
27+
import org.gradle.api.tasks.SourceSet;
28+
import org.gradle.api.tasks.SourceSetContainer;
29+
30+
/**
31+
* {@link Action} that is executed in response to the {@link NativeImagePlugin} being
32+
* applied.
33+
*
34+
* @author Andy Wilkinson
35+
*/
36+
class NativeImagePluginAction implements PluginApplicationAction {
37+
38+
@Override
39+
public Class<? extends Plugin<? extends Project>> getPluginClass()
40+
throws ClassNotFoundException, NoClassDefFoundError {
41+
return NativeImagePlugin.class;
42+
}
43+
44+
@Override
45+
public void execute(Project project) {
46+
project.getPlugins().apply(SpringBootAotPlugin.class);
47+
project.getPlugins().withType(JavaPlugin.class).all((plugin) -> {
48+
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
49+
SourceSetContainer sourceSets = javaPluginExtension.getSourceSets();
50+
SourceSet aotSourceSet = sourceSets.getByName(SpringBootAotPlugin.AOT_SOURCE_SET_NAME);
51+
project.getTasks().named(NativeImagePlugin.NATIVE_COMPILE_TASK_NAME, BuildNativeImageTask.class,
52+
(nativeCompile) -> nativeCompile.getOptions().get().classpath(aotSourceSet.getOutput()));
53+
});
54+
GraalVMExtension graalVmExtension = project.getExtensions().getByType(GraalVMExtension.class);
55+
graalVmExtension.getToolchainDetection().set(false);
56+
}
57+
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
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 org.springframework.boot.gradle.plugin;
18+
19+
import java.util.List;
20+
21+
import org.gradle.api.Plugin;
22+
import org.gradle.api.Project;
23+
import org.gradle.api.artifacts.Configuration;
24+
import org.gradle.api.artifacts.ConfigurationContainer;
25+
import org.gradle.api.plugins.JavaPlugin;
26+
import org.gradle.api.plugins.JavaPluginExtension;
27+
import org.gradle.api.plugins.PluginContainer;
28+
import org.gradle.api.tasks.SourceSet;
29+
import org.gradle.api.tasks.SourceSetContainer;
30+
import org.gradle.api.tasks.TaskProvider;
31+
32+
import org.springframework.boot.gradle.tasks.aot.GenerateAotSources;
33+
34+
/**
35+
* Gradle plugin for Spring Boot AOT.
36+
*
37+
* @author Andy Wilkinson
38+
* @since 3.0.0
39+
*/
40+
public class SpringBootAotPlugin implements Plugin<Project> {
41+
42+
/**
43+
* Name of the {@code aot} {@link SourceSet source set}.
44+
*/
45+
public static final String AOT_SOURCE_SET_NAME = "aot";
46+
47+
/**
48+
* Name of the default {@link GenerateAotSources} task.
49+
*/
50+
public static final String GENERATE_AOT_SOURCES_TASK_NAME = "generateAotSources";
51+
52+
@Override
53+
public void apply(Project project) {
54+
PluginContainer plugins = project.getPlugins();
55+
plugins.withType(JavaPlugin.class).all((javaPlugin) -> {
56+
plugins.withType(SpringBootPlugin.class).all((bootPlugin) -> {
57+
SourceSet aotSourceSet = configureAotSourceSet(project);
58+
registerGenerateAotSourcesTask(project, aotSourceSet);
59+
});
60+
});
61+
}
62+
63+
private SourceSet configureAotSourceSet(Project project) {
64+
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
65+
SourceSetContainer sourceSets = javaPluginExtension.getSourceSets();
66+
SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
67+
SourceSet aotSourceSet = sourceSets.create(AOT_SOURCE_SET_NAME, (aot) -> {
68+
aot.getJava().setSrcDirs(List.of("build/generated/aotSources"));
69+
aot.getResources().setSrcDirs(List.of("build/generated/aotResources"));
70+
aot.setCompileClasspath(aot.getCompileClasspath().plus(main.getOutput()));
71+
main.setRuntimeClasspath(main.getRuntimeClasspath().plus(aot.getOutput()));
72+
ConfigurationContainer configurations = project.getConfigurations();
73+
Configuration aotImplementation = configurations.getByName(aot.getImplementationConfigurationName());
74+
aotImplementation.extendsFrom(configurations.getByName(main.getImplementationConfigurationName()));
75+
aotImplementation.extendsFrom(configurations.getByName(main.getRuntimeOnlyConfigurationName()));
76+
});
77+
return aotSourceSet;
78+
}
79+
80+
private void registerGenerateAotSourcesTask(Project project, SourceSet aotSourceSet) {
81+
TaskProvider<ResolveMainClassName> resolveMainClassName = project.getTasks()
82+
.named(SpringBootPlugin.RESOLVE_MAIN_CLASS_NAME_TASK_NAME, ResolveMainClassName.class);
83+
TaskProvider<GenerateAotSources> generateAotSources = project.getTasks()
84+
.register(GENERATE_AOT_SOURCES_TASK_NAME, GenerateAotSources.class, (task) -> {
85+
task.getApplicationClass().set(resolveMainClassName.flatMap((thing) -> thing.readMainClassName()));
86+
task.setClasspath(aotSourceSet.getCompileClasspath());
87+
task.getSourcesDir().set(aotSourceSet.getJava().getSrcDirs().iterator().next());
88+
task.getResourcesDir().set(aotSourceSet.getResources().getSrcDirs().iterator().next());
89+
task.getGroupId().set(project.provider(() -> String.valueOf(project.getGroup())));
90+
task.getArtifactId().set(project.provider(() -> project.getName()));
91+
});
92+
project.getTasks().getByName(aotSourceSet.getCompileJavaTaskName(),
93+
(compile) -> compile.dependsOn(generateAotSources));
94+
project.getTasks().getByName(aotSourceSet.getProcessResourcesTaskName(),
95+
(processResources) -> processResources.dependsOn(generateAotSources));
96+
}
97+
98+
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ private void registerPluginActions(Project project, Configuration bootArchives)
126126
project.getArtifacts());
127127
List<PluginApplicationAction> actions = Arrays.asList(new JavaPluginAction(singlePublishedArtifact),
128128
new WarPluginAction(singlePublishedArtifact), new DependencyManagementPluginAction(),
129-
new ApplicationPluginAction(), new KotlinPluginAction());
129+
new ApplicationPluginAction(), new KotlinPluginAction(), new NativeImagePluginAction());
130130
for (PluginApplicationAction action : actions) {
131131
withPluginClassOfAction(action,
132132
(pluginClass) -> project.getPlugins().withType(pluginClass, (plugin) -> action.execute(project)));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
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 org.springframework.boot.gradle.tasks.aot;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import org.gradle.api.file.DirectoryProperty;
23+
import org.gradle.api.provider.Property;
24+
import org.gradle.api.tasks.CacheableTask;
25+
import org.gradle.api.tasks.Input;
26+
import org.gradle.api.tasks.JavaExec;
27+
import org.gradle.api.tasks.OutputDirectory;
28+
import org.gradle.api.tasks.TaskAction;
29+
30+
/**
31+
* Custom {@link JavaExec} task for generating sources ahead of time.
32+
*
33+
* @author Andy Wilkinson
34+
* @since 3.0
35+
*/
36+
@CacheableTask
37+
public class GenerateAotSources extends JavaExec {
38+
39+
private final Property<String> applicationClass;
40+
41+
private final DirectoryProperty sourcesDir;
42+
43+
private final DirectoryProperty resourcesDir;
44+
45+
private final Property<String> groupId;
46+
47+
private final Property<String> artifactId;
48+
49+
public GenerateAotSources() {
50+
this.applicationClass = getProject().getObjects().property(String.class);
51+
this.sourcesDir = getProject().getObjects().directoryProperty();
52+
this.resourcesDir = getProject().getObjects().directoryProperty();
53+
this.groupId = getProject().getObjects().property(String.class);
54+
this.artifactId = getProject().getObjects().property(String.class);
55+
getMainClass().set("org.springframework.boot.AotProcessor");
56+
}
57+
58+
@Input
59+
public Property<String> getApplicationClass() {
60+
return this.applicationClass;
61+
}
62+
63+
@Input
64+
public Property<String> getGroupId() {
65+
return this.groupId;
66+
}
67+
68+
@Input
69+
public Property<String> getArtifactId() {
70+
return this.artifactId;
71+
}
72+
73+
@OutputDirectory
74+
public DirectoryProperty getSourcesDir() {
75+
return this.sourcesDir;
76+
}
77+
78+
@OutputDirectory
79+
public DirectoryProperty getResourcesDir() {
80+
return this.resourcesDir;
81+
}
82+
83+
@Override
84+
@TaskAction
85+
public void exec() {
86+
List<String> args = new ArrayList<>();
87+
args.add(this.applicationClass.get());
88+
args.add(this.sourcesDir.getAsFile().get().getAbsolutePath());
89+
args.add(this.resourcesDir.getAsFile().get().getAbsolutePath());
90+
args.addAll(super.getArgs());
91+
this.setArgs(args);
92+
super.exec();
93+
}
94+
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
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 org.springframework.boot.gradle.plugin;
18+
19+
import org.junit.jupiter.api.TestTemplate;
20+
21+
import org.springframework.boot.gradle.junit.GradleCompatibility;
22+
import org.springframework.boot.testsupport.gradle.testkit.GradleBuild;
23+
24+
import static org.assertj.core.api.Assertions.assertThat;
25+
26+
/**
27+
* Integration tests for {@link NativeImagePluginAction}.
28+
*
29+
* @author Andy Wilkinson
30+
*/
31+
@GradleCompatibility(configurationCache = true)
32+
class NativeImagePluginActionIntegrationTests {
33+
34+
GradleBuild gradleBuild;
35+
36+
@TestTemplate
37+
void applyingNativeImagePluginAppliesAotPlugin() {
38+
assertThat(this.gradleBuild.build("aotPluginApplied").getOutput())
39+
.contains("org.springframework.boot.aot applied = true");
40+
}
41+
42+
}

0 commit comments

Comments
 (0)