From 6f55b5785594dabb3fbd7c394dd85ae3f6ad0dc3 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 30 Oct 2017 22:04:39 +0000 Subject: [PATCH] Make discovery of additional config metdata more robust with Gradle Previously, the configuration metadata annotation processor relied upon an additional metadata file have been copied to an output location. When building with Gradle, it's the processResources task that performs this copy and there is no guarantee that it will have run before the compileJava task unless an explicit dependency betwee the two tasks has been configured. If a project is built using Gradle's parallel build support, the likelihood of this required ordering not occurring increases. This commit updates the configuration metadata annotation processor to consider a new annotation processor option when looking for the additional config metadata file. The Gradle plugin has been updated to provide this option as a compiler argument. The option is only provided when the annotation processor is found on the compilation classpath to avoid a warning from javac's annotation processing about the use of an option that is not supported by any of the available annotation processors. Closes gh-9755 --- ...figurationMetadataAnnotationProcessor.java | 20 ++++++-- .../configurationprocessor/MetadataStore.java | 10 ++++ .../MetadataStoreTests.java | 23 ++++++++- .../boot/gradle/plugin/JavaPluginAction.java | 49 +++++++++++++++++++ .../JavaPluginActionIntegrationTests.java | 38 ++++++++++++++ ...AnnotationProcessorIsOnTheClasspath.gradle | 22 +++++++++ ...otationProcessorIsNotOnTheClasspath.gradle | 14 ++++++ 7 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-additionalMetadataLocationsCompilerArgumentIsAddedWhenAnnotationProcessorIsOnTheClasspath.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-additionalMetadataLocationsCompilerArgumentIsNotAddedWhenAnnotationProcessorIsNotOnTheClasspath.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java index 7c798bde0d8..04e18760b75 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java @@ -21,6 +21,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -65,6 +66,9 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; @SupportedAnnotationTypes({ "*" }) public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { + static final String ADDITIONAL_METADATA_LOCATIONS_OPTION = "org.springframework.boot." + + "configurationprocessor.additionalMetadataLocations"; + static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot." + "context.properties.ConfigurationProperties"; @@ -83,6 +87,9 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor static final String LOMBOK_SETTER_ANNOTATION = "lombok.Setter"; + private static final Set SUPPORTED_OPTIONS = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList(ADDITIONAL_METADATA_LOCATIONS_OPTION))); + private MetadataStore metadataStore; private MetadataCollector metadataCollector; @@ -114,6 +121,11 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor return SourceVersion.latestSupported(); } + @Override + public Set getSupportedOptions() { + return SUPPORTED_OPTIONS; + } + @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); @@ -394,10 +406,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor type, null, String.format("Expose the %s endpoint as a Web endpoint.", endpointId), enabledByDefault, null)); - this.metadataCollector.add(ItemMetadata.newProperty( - endpointKey(endpointId), "web.path", String.class.getName(), type, - null, String.format("Path of the %s endpoint.", endpointId), - endpointId, null)); + this.metadataCollector.add(ItemMetadata.newProperty(endpointKey(endpointId), + "web.path", String.class.getName(), type, null, + String.format("Path of the %s endpoint.", endpointId), endpointId, + null)); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataStore.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataStore.java index aa9a4d2d6c8..f12400d8b05 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataStore.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataStore.java @@ -119,6 +119,16 @@ public class MetadataStore { if (standardLocation.exists()) { return standardLocation; } + String locations = this.environment.getOptions().get( + ConfigurationMetadataAnnotationProcessor.ADDITIONAL_METADATA_LOCATIONS_OPTION); + if (locations != null) { + for (String location : locations.split(",")) { + File candidate = new File(location, ADDITIONAL_METADATA_PATH); + if (candidate.isFile()) { + return candidate; + } + } + } return new File(locateGradleResourcesFolder(standardLocation), ADDITIONAL_METADATA_PATH); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MetadataStoreTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MetadataStoreTests.java index c54e0579725..2994ead7298 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MetadataStoreTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MetadataStoreTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.configurationprocessor; import java.io.File; import java.io.IOException; +import java.util.Collections; import javax.annotation.processing.ProcessingEnvironment; @@ -26,6 +27,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** @@ -38,8 +40,9 @@ public class MetadataStoreTests { @Rule public final TemporaryFolder temp = new TemporaryFolder(); - private final MetadataStore metadataStore = new MetadataStore( - mock(ProcessingEnvironment.class)); + private final ProcessingEnvironment environment = mock(ProcessingEnvironment.class); + + private final MetadataStore metadataStore = new MetadataStore(this.environment); @Test public void additionalMetadataIsLocatedInMavenBuild() throws IOException { @@ -88,4 +91,20 @@ public class MetadataStoreTests { .isEqualTo(additionalMetadata); } + @Test + public void additionalMetadataIsLocatedUsingLocationsOption() throws IOException { + File app = this.temp.newFolder("app"); + File location = new File(app, "src/main/resources"); + File metaInf = new File(location, "META-INF"); + metaInf.mkdirs(); + File additionalMetadata = new File(metaInf, + "additional-spring-configuration-metadata.json"); + additionalMetadata.createNewFile(); + given(this.environment.getOptions()).willReturn(Collections.singletonMap( + ConfigurationMetadataAnnotationProcessor.ADDITIONAL_METADATA_LOCATIONS_OPTION, + location.getAbsolutePath())); + assertThat(this.metadataStore.locateAdditionalMetadataFile(new File(app, "foo"))) + .isEqualTo(additionalMetadata); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java index 4c1a40ac3d3..b2a516d06c6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java @@ -16,8 +16,11 @@ package org.springframework.boot.gradle.plugin; +import java.io.File; import java.util.Collections; import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.Callable; import org.gradle.api.Action; @@ -34,6 +37,7 @@ import org.gradle.api.tasks.compile.JavaCompile; import org.springframework.boot.gradle.tasks.bundling.BootJar; import org.springframework.boot.gradle.tasks.run.BootRun; +import org.springframework.util.StringUtils; /** * {@link Action} that is executed in response to the {@link JavaPlugin} being applied. @@ -63,6 +67,7 @@ final class JavaPluginAction implements PluginApplicationAction { configureBootRunTask(project); configureUtf8Encoding(project); configureParametersCompilerArg(project); + configureAdditionalMetadataLocations(project); } private void disableJarTask(Project project) { @@ -134,4 +139,48 @@ final class JavaPluginAction implements PluginApplicationAction { }); } + private void configureAdditionalMetadataLocations(Project project) { + project.afterEvaluate((evaluated) -> { + evaluated.getTasks().withType(JavaCompile.class, (compile) -> { + configureAdditionalMetadataLocations(project, compile); + }); + }); + } + + private void configureAdditionalMetadataLocations(Project project, + JavaCompile compile) { + compile.doFirst((task) -> { + if (hasConfigurationProcessorOnClasspath(compile)) { + findMatchingSourceSet(compile).ifPresent((sourceSet) -> { + configureAdditionalMetadataLocations(compile, sourceSet); + }); + } + }); + } + + private Optional findMatchingSourceSet(JavaCompile compile) { + return compile.getProject().getConvention().getPlugin(JavaPluginConvention.class) + .getSourceSets().stream().filter((sourceSet) -> sourceSet + .getCompileJavaTaskName().equals(compile.getName())) + .findFirst(); + } + + private boolean hasConfigurationProcessorOnClasspath(JavaCompile compile) { + Set files = compile.getOptions().getAnnotationProcessorPath() != null + ? compile.getOptions().getAnnotationProcessorPath().getFiles() + : compile.getClasspath().getFiles(); + return files.stream().map(File::getName) + .filter((name) -> name.startsWith("spring-boot-configuration-processor")) + .findFirst().isPresent(); + } + + private void configureAdditionalMetadataLocations(JavaCompile compile, + SourceSet sourceSet) { + String locations = StringUtils + .collectionToCommaDelimitedString(sourceSet.getResources().getSrcDirs()); + compile.getOptions().getCompilerArgs() + .add("-Aorg.springframework.boot.configurationprocessor.additionalMetadataLocations=" + + locations); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java index 53ab13366fa..3f565b3e53a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java @@ -17,6 +17,9 @@ package org.springframework.boot.gradle.plugin; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.jar.JarOutputStream; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.TaskOutcome; @@ -113,4 +116,39 @@ public class JavaPluginActionIntegrationTests { this.gradleBuild.getProjectDir().getName() + "-boot.jar")); } + @Test + public void additionalMetadataLocationsCompilerArgumentIsAddedWhenAnnotationProcessorIsOnTheClasspath() + throws IOException { + createMinimalMainSource(); + File libs = new File(this.gradleBuild.getProjectDir(), "libs"); + libs.mkdirs(); + new JarOutputStream(new FileOutputStream( + new File(libs, "spring-boot-configuration-processor-1.2.3.jar"))).close(); + BuildResult result = this.gradleBuild.build("compileJava"); + assertThat(result.task(":compileJava").getOutcome()) + .isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains( + "compileJava compiler args: [-parameters, -Aorg.springframework.boot.configurationprocessor.additionalMetadataLocations=" + + this.gradleBuild.getProjectDir().getCanonicalPath() + + "/src/main/resources]"); + } + + @Test + public void additionalMetadataLocationsCompilerArgumentIsNotAddedWhenAnnotationProcessorIsNotOnTheClasspath() + throws IOException { + createMinimalMainSource(); + BuildResult result = this.gradleBuild.build("compileJava"); + assertThat(result.task(":compileJava").getOutcome()) + .isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()) + .contains("compileJava compiler args: [-parameters]"); + } + + private void createMinimalMainSource() throws IOException { + File examplePackage = new File(this.gradleBuild.getProjectDir(), + "src/main/java/com/example"); + examplePackage.mkdirs(); + new File(examplePackage, "Application.java").createNewFile(); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-additionalMetadataLocationsCompilerArgumentIsAddedWhenAnnotationProcessorIsOnTheClasspath.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-additionalMetadataLocationsCompilerArgumentIsAddedWhenAnnotationProcessorIsOnTheClasspath.gradle new file mode 100644 index 00000000000..2c6e0992803 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-additionalMetadataLocationsCompilerArgumentIsAddedWhenAnnotationProcessorIsOnTheClasspath.gradle @@ -0,0 +1,22 @@ +buildscript { + dependencies { + classpath files(pluginClasspath.split(',')) + } +} + +apply plugin: 'org.springframework.boot' +apply plugin: 'java' + +repositories { + flatDir { dirs 'libs' } +} + +dependencies { + compile name: 'spring-boot-configuration-processor-1.2.3' +} + +compileJava { + doLast { + println "$name compiler args: ${options.compilerArgs}" + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-additionalMetadataLocationsCompilerArgumentIsNotAddedWhenAnnotationProcessorIsNotOnTheClasspath.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-additionalMetadataLocationsCompilerArgumentIsNotAddedWhenAnnotationProcessorIsNotOnTheClasspath.gradle new file mode 100644 index 00000000000..1e502a8b848 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-additionalMetadataLocationsCompilerArgumentIsNotAddedWhenAnnotationProcessorIsNotOnTheClasspath.gradle @@ -0,0 +1,14 @@ +buildscript { + dependencies { + classpath files(pluginClasspath.split(',')) + } +} + +apply plugin: 'org.springframework.boot' +apply plugin: 'java' + +compileJava { + doLast { + println "$name compiler args: ${options.compilerArgs}" + } +}