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
pull/10833/merge
Andy Wilkinson 7 years ago
parent de080165ec
commit 6f55b57855

@ -21,6 +21,7 @@ import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -65,6 +66,9 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
@SupportedAnnotationTypes({ "*" }) @SupportedAnnotationTypes({ "*" })
public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor { 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." static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot."
+ "context.properties.ConfigurationProperties"; + "context.properties.ConfigurationProperties";
@ -83,6 +87,9 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
static final String LOMBOK_SETTER_ANNOTATION = "lombok.Setter"; static final String LOMBOK_SETTER_ANNOTATION = "lombok.Setter";
private static final Set<String> SUPPORTED_OPTIONS = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList(ADDITIONAL_METADATA_LOCATIONS_OPTION)));
private MetadataStore metadataStore; private MetadataStore metadataStore;
private MetadataCollector metadataCollector; private MetadataCollector metadataCollector;
@ -114,6 +121,11 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
return SourceVersion.latestSupported(); return SourceVersion.latestSupported();
} }
@Override
public Set<String> getSupportedOptions() {
return SUPPORTED_OPTIONS;
}
@Override @Override
public synchronized void init(ProcessingEnvironment env) { public synchronized void init(ProcessingEnvironment env) {
super.init(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.", type, null, String.format("Expose the %s endpoint as a Web endpoint.",
endpointId), endpointId),
enabledByDefault, null)); enabledByDefault, null));
this.metadataCollector.add(ItemMetadata.newProperty( this.metadataCollector.add(ItemMetadata.newProperty(endpointKey(endpointId),
endpointKey(endpointId), "web.path", String.class.getName(), type, "web.path", String.class.getName(), type, null,
null, String.format("Path of the %s endpoint.", endpointId), String.format("Path of the %s endpoint.", endpointId), endpointId,
endpointId, null)); null));
} }
} }

@ -119,6 +119,16 @@ public class MetadataStore {
if (standardLocation.exists()) { if (standardLocation.exists()) {
return standardLocation; 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), return new File(locateGradleResourcesFolder(standardLocation),
ADDITIONAL_METADATA_PATH); ADDITIONAL_METADATA_PATH);
} }

@ -18,6 +18,7 @@ package org.springframework.boot.configurationprocessor;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.ProcessingEnvironment;
@ -26,6 +27,7 @@ import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
@ -38,8 +40,9 @@ public class MetadataStoreTests {
@Rule @Rule
public final TemporaryFolder temp = new TemporaryFolder(); public final TemporaryFolder temp = new TemporaryFolder();
private final MetadataStore metadataStore = new MetadataStore( private final ProcessingEnvironment environment = mock(ProcessingEnvironment.class);
mock(ProcessingEnvironment.class));
private final MetadataStore metadataStore = new MetadataStore(this.environment);
@Test @Test
public void additionalMetadataIsLocatedInMavenBuild() throws IOException { public void additionalMetadataIsLocatedInMavenBuild() throws IOException {
@ -88,4 +91,20 @@ public class MetadataStoreTests {
.isEqualTo(additionalMetadata); .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);
}
} }

@ -16,8 +16,11 @@
package org.springframework.boot.gradle.plugin; package org.springframework.boot.gradle.plugin;
import java.io.File;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import org.gradle.api.Action; 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.bundling.BootJar;
import org.springframework.boot.gradle.tasks.run.BootRun; 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. * {@link Action} that is executed in response to the {@link JavaPlugin} being applied.
@ -63,6 +67,7 @@ final class JavaPluginAction implements PluginApplicationAction {
configureBootRunTask(project); configureBootRunTask(project);
configureUtf8Encoding(project); configureUtf8Encoding(project);
configureParametersCompilerArg(project); configureParametersCompilerArg(project);
configureAdditionalMetadataLocations(project);
} }
private void disableJarTask(Project 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<SourceSet> 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<File> 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);
}
} }

@ -17,6 +17,9 @@
package org.springframework.boot.gradle.plugin; package org.springframework.boot.gradle.plugin;
import java.io.File; 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.BuildResult;
import org.gradle.testkit.runner.TaskOutcome; import org.gradle.testkit.runner.TaskOutcome;
@ -113,4 +116,39 @@ public class JavaPluginActionIntegrationTests {
this.gradleBuild.getProjectDir().getName() + "-boot.jar")); 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();
}
} }

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

@ -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}"
}
}
Loading…
Cancel
Save