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}" + } +}