Skip to content

Commit fa73b73

Browse files
committed
Fail build on missing configuration property descriptions
Closes gh-31916
1 parent d540eef commit fa73b73

File tree

11 files changed

+386
-17
lines changed

11 files changed

+386
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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.build.context.properties;
18+
19+
import java.io.File;
20+
import java.io.IOException;
21+
import java.nio.file.Files;
22+
import java.nio.file.Path;
23+
import java.nio.file.StandardOpenOption;
24+
import java.util.ArrayList;
25+
import java.util.Iterator;
26+
import java.util.List;
27+
import java.util.Map;
28+
import java.util.stream.Collectors;
29+
30+
import com.fasterxml.jackson.core.JsonParseException;
31+
import com.fasterxml.jackson.databind.JsonMappingException;
32+
import com.fasterxml.jackson.databind.ObjectMapper;
33+
import org.gradle.api.DefaultTask;
34+
import org.gradle.api.GradleException;
35+
import org.gradle.api.file.RegularFileProperty;
36+
import org.gradle.api.tasks.Input;
37+
import org.gradle.api.tasks.InputFile;
38+
import org.gradle.api.tasks.OutputFile;
39+
import org.gradle.api.tasks.PathSensitive;
40+
import org.gradle.api.tasks.PathSensitivity;
41+
import org.gradle.api.tasks.SourceTask;
42+
import org.gradle.api.tasks.TaskAction;
43+
44+
/**
45+
* {@link SourceTask} that checks {@code spring-configuration-metadata.json} files.
46+
*
47+
* @author Andy Wilkinson
48+
*/
49+
public class CheckSpringConfigurationMetadata extends DefaultTask {
50+
51+
private List<String> exclusions = new ArrayList<>();
52+
53+
private final RegularFileProperty reportLocation;
54+
55+
private final RegularFileProperty metadataLocation;
56+
57+
public CheckSpringConfigurationMetadata() {
58+
this.metadataLocation = getProject().getObjects().fileProperty();
59+
this.reportLocation = getProject().getObjects().fileProperty();
60+
}
61+
62+
@OutputFile
63+
public RegularFileProperty getReportLocation() {
64+
return this.reportLocation;
65+
}
66+
67+
@InputFile
68+
@PathSensitive(PathSensitivity.RELATIVE)
69+
public RegularFileProperty getMetadataLocation() {
70+
return this.metadataLocation;
71+
}
72+
73+
public void setExclusions(List<String> exclusions) {
74+
this.exclusions = exclusions;
75+
}
76+
77+
@Input
78+
public List<String> getExclusions() {
79+
return this.exclusions;
80+
}
81+
82+
@TaskAction
83+
void check() throws JsonParseException, IOException {
84+
Report report = createReport();
85+
File reportFile = getReportLocation().get().getAsFile();
86+
Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
87+
if (report.hasProblems()) {
88+
throw new GradleException(
89+
"Problems found in Spring configuration metadata. See " + reportFile + " for details.");
90+
}
91+
}
92+
93+
@SuppressWarnings("unchecked")
94+
private Report createReport() throws IOException, JsonParseException, JsonMappingException {
95+
ObjectMapper objectMapper = new ObjectMapper();
96+
File file = this.metadataLocation.get().getAsFile();
97+
Report report = new Report(getProject().getProjectDir().toPath().relativize(file.toPath()));
98+
Map<String, Object> json = objectMapper.readValue(file, Map.class);
99+
List<Map<String, Object>> properties = (List<Map<String, Object>>) json.get("properties");
100+
for (Map<String, Object> property : properties) {
101+
String name = (String) property.get("name");
102+
if (!isDeprecated(property) && !isDescribed(property) && !isExcluded(name)) {
103+
report.propertiesWithNoDescription.add(name);
104+
}
105+
}
106+
return report;
107+
}
108+
109+
private boolean isExcluded(String propertyName) {
110+
for (String exclusion : this.exclusions) {
111+
if (propertyName.equals(exclusion)) {
112+
return true;
113+
}
114+
if (exclusion.endsWith(".*")) {
115+
if (propertyName.startsWith(exclusion.substring(0, exclusion.length() - 2))) {
116+
return true;
117+
}
118+
}
119+
}
120+
return false;
121+
}
122+
123+
@SuppressWarnings("unchecked")
124+
private boolean isDeprecated(Map<String, Object> property) {
125+
return (Map<String, Object>) property.get("deprecation") != null;
126+
}
127+
128+
private boolean isDescribed(Map<String, Object> property) {
129+
return property.get("description") != null;
130+
}
131+
132+
private static final class Report implements Iterable<String> {
133+
134+
private final List<String> propertiesWithNoDescription = new ArrayList<>();
135+
136+
private final Path source;
137+
138+
private Report(Path source) {
139+
this.source = source;
140+
}
141+
142+
private boolean hasProblems() {
143+
return !this.propertiesWithNoDescription.isEmpty();
144+
}
145+
146+
@Override
147+
public Iterator<String> iterator() {
148+
List<String> lines = new ArrayList<>();
149+
lines.add(this.source.toString());
150+
lines.add("");
151+
if (this.propertiesWithNoDescription.isEmpty()) {
152+
lines.add("No problems found.");
153+
}
154+
else {
155+
lines.add("The following properties have no description:");
156+
lines.add("");
157+
lines.addAll(this.propertiesWithNoDescription.stream().map((line) -> "\t" + line)
158+
.collect(Collectors.toList()));
159+
}
160+
lines.add("");
161+
return lines.iterator();
162+
163+
}
164+
165+
}
166+
167+
}

buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationPropertiesPlugin.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
import org.gradle.api.Project;
2626
import org.gradle.api.Task;
2727
import org.gradle.api.artifacts.Configuration;
28+
import org.gradle.api.file.RegularFile;
2829
import org.gradle.api.plugins.JavaPlugin;
2930
import org.gradle.api.plugins.JavaPluginConvention;
31+
import org.gradle.api.provider.Provider;
3032
import org.gradle.api.tasks.PathSensitivity;
3133
import org.gradle.api.tasks.SourceSet;
3234
import org.gradle.api.tasks.TaskProvider;
@@ -41,6 +43,7 @@
4143
*
4244
* <ul>
4345
* <li>Adding a dependency on the configuration properties annotation processor.
46+
* <li>Disables incremental compilation to avoid property descriptions being lost.
4447
* <li>Configuring the additional metadata locations annotation processor compiler
4548
* argument.
4649
* <li>Adding the outputs of the processResources task as inputs of the compileJava task
@@ -66,12 +69,19 @@ public class ConfigurationPropertiesPlugin implements Plugin<Project> {
6669
*/
6770
public static final String CHECK_ADDITIONAL_SPRING_CONFIGURATION_METADATA_TASK_NAME = "checkAdditionalSpringConfigurationMetadata";
6871

72+
/**
73+
* Name of the {@link CheckAdditionalSpringConfigurationMetadata} task.
74+
*/
75+
public static final String CHECK_SPRING_CONFIGURATION_METADATA_TASK_NAME = "checkSpringConfigurationMetadata";
76+
6977
@Override
7078
public void apply(Project project) {
7179
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
7280
addConfigurationProcessorDependency(project);
81+
disableIncrementalCompilation(project);
7382
configureAdditionalMetadataLocationsCompilerArgument(project);
7483
registerCheckAdditionalMetadataTask(project);
84+
registerCheckMetadataTask(project);
7585
addMetadataArtifact(project);
7686
});
7787
}
@@ -83,6 +93,13 @@ private void addConfigurationProcessorDependency(Project project) {
8393
":spring-boot-project:spring-boot-tools:spring-boot-configuration-processor")));
8494
}
8595

96+
private void disableIncrementalCompilation(Project project) {
97+
SourceSet mainSourceSet = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets()
98+
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
99+
project.getTasks().named(mainSourceSet.getCompileJavaTaskName(), JavaCompile.class)
100+
.configure((compileJava) -> compileJava.getOptions().setIncremental(false));
101+
}
102+
86103
private void addMetadataArtifact(Project project) {
87104
SourceSet mainSourceSet = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets()
88105
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
@@ -124,4 +141,22 @@ private void registerCheckAdditionalMetadataTask(Project project) {
124141
.configure((check) -> check.dependsOn(checkConfigurationMetadata));
125142
}
126143

144+
private void registerCheckMetadataTask(Project project) {
145+
TaskProvider<CheckSpringConfigurationMetadata> checkConfigurationMetadata = project.getTasks()
146+
.register(CHECK_SPRING_CONFIGURATION_METADATA_TASK_NAME, CheckSpringConfigurationMetadata.class);
147+
checkConfigurationMetadata.configure((check) -> {
148+
SourceSet mainSourceSet = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets()
149+
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
150+
Provider<RegularFile> metadataLocation = project.getTasks()
151+
.named(mainSourceSet.getCompileJavaTaskName(), JavaCompile.class)
152+
.flatMap((javaCompile) -> javaCompile.getDestinationDirectory()
153+
.file("META-INF/spring-configuration-metadata.json"));
154+
check.getMetadataLocation().set(metadataLocation);
155+
check.getReportLocation().set(
156+
project.getLayout().getBuildDirectory().file("reports/spring-configuration-metadata/check.txt"));
157+
});
158+
project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME)
159+
.configure((check) -> check.dependsOn(checkConfigurationMetadata));
160+
}
161+
127162
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/AutoTimeProperties.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
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.
@@ -30,10 +30,19 @@
3030
*/
3131
public final class AutoTimeProperties implements AutoTimer {
3232

33+
/**
34+
* Whether to enable auto-timing.
35+
*/
3336
private boolean enabled = true;
3437

38+
/**
39+
* Whether to publish percentile histrograms.
40+
*/
3541
private boolean percentilesHistogram;
3642

43+
/**
44+
* Percentiles for which additional time series should be published.
45+
*/
3746
private double[] percentiles;
3847

3948
/**

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontProperties.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,20 @@ public Sender getSender() {
9797

9898
public static class Sender {
9999

100+
/**
101+
* Maximum queue size of the in-memory buffer.
102+
*/
100103
private int maxQueueSize = 50000;
101104

105+
/**
106+
* Interval at which points are flushed to the Wavefront server.
107+
*/
102108
private Duration flushInterval = Duration.ofSeconds(1);
103109

110+
/**
111+
* Maximum message size, such that each batch is reported as one or more messages
112+
* where no message exceeds the specified size.
113+
*/
104114
private DataSize messageSize = DataSize.ofBytes(Integer.MAX_VALUE);
105115

106116
public int getMaxQueueSize() {

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,6 @@
243243
"description": "Whether to enable Solr health check.",
244244
"defaultValue": true
245245
},
246-
{
247-
"name": "management.health.status.order",
248-
"defaultValue": [
249-
"DOWN",
250-
"OUT_OF_SERVICE",
251-
"UP",
252-
"UNKNOWN"
253-
]
254-
},
255246
{
256247
"name": "management.info.build.enabled",
257248
"type": "java.lang.Boolean",

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ void runCreatesHttpCodeStatusMapperFromProperties() {
123123
@Test
124124
void runWhenHasHttpCodeStatusMapperBeanIgnoresProperties() {
125125
this.contextRunner.withUserConfiguration(HttpCodeStatusMapperConfiguration.class)
126-
.withPropertyValues("management.health.status.http-mapping.up=123").run((context) -> {
126+
.withPropertyValues("management.endpoint.health.status.http-mapping.up=123").run((context) -> {
127127
HttpCodeStatusMapper mapper = context.getBean(HttpCodeStatusMapper.class);
128128
assertThat(mapper.getStatusCode(Status.UP)).isEqualTo(456);
129129
});

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,13 @@ dependencies {
257257
testRuntimeOnly("jakarta.management.j2ee:jakarta.management.j2ee-api")
258258
testRuntimeOnly("org.jetbrains.kotlin:kotlin-reflect")
259259
}
260+
261+
tasks.named("checkSpringConfigurationMetadata").configure {
262+
exclusions = [
263+
"spring.datasource.dbcp2.*",
264+
"spring.datasource.hikari.*",
265+
"spring.datasource.oracleucp.*",
266+
"spring.datasource.tomcat.*",
267+
"spring.groovy.template.configuration.*"
268+
]
269+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1738,8 +1738,14 @@ public void setWorker(Integer worker) {
17381738

17391739
public static class Options {
17401740

1741+
/**
1742+
* Socket options as defined in org.xnio.Options.
1743+
*/
17411744
private Map<String, String> socket = new LinkedHashMap<>();
17421745

1746+
/**
1747+
* Server options as defined in io.undertow.UndertowOptions.
1748+
*/
17431749
private Map<String, String> server = new LinkedHashMap<>();
17441750

17451751
public Map<String, String> getServer() {

0 commit comments

Comments
 (0)