-
Notifications
You must be signed in to change notification settings - Fork 40.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add config metadata changelog generator to main build
Closes gh-21486
- Loading branch information
1 parent
b655523
commit 32b7b31
Showing
11 changed files
with
686 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
...ect/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
plugins { | ||
id "java" | ||
id "org.springframework.boot.conventions" | ||
} | ||
|
||
description = "Spring Boot Configuration Metadata Changelog Generator" | ||
|
||
configurations { | ||
oldMetadata | ||
newMetadata | ||
} | ||
|
||
dependencies { | ||
implementation(enforcedPlatform(project(":spring-boot-project:spring-boot-dependencies"))) | ||
implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-configuration-metadata")) | ||
|
||
testImplementation(enforcedPlatform(project(":spring-boot-project:spring-boot-dependencies"))) | ||
testImplementation("org.assertj:assertj-core") | ||
testImplementation("org.junit.jupiter:junit-jupiter") | ||
} | ||
|
||
if (project.hasProperty("oldVersion") && project.hasProperty("newVersion")) { | ||
dependencies { | ||
["spring-boot", | ||
"spring-boot-actuator", | ||
"spring-boot-actuator-autoconfigure", | ||
"spring-boot-autoconfigure", | ||
"spring-boot-devtools", | ||
"spring-boot-test-autoconfigure"].each { | ||
oldMetadata("org.springframework.boot:$it:$oldVersion") | ||
newMetadata("org.springframework.boot:$it:$newVersion") | ||
} | ||
} | ||
|
||
def prepareOldMetadata = tasks.register("prepareOldMetadata", Sync) { | ||
from(configurations.oldMetadata) | ||
if (project.hasProperty("oldVersion")) { | ||
destinationDir = project.file("build/configuration-metadata-diff/$oldVersion") | ||
} | ||
} | ||
|
||
def prepareNewMetadata = tasks.register("prepareNewMetadata", Sync) { | ||
from(configurations.newMetadata) | ||
if (project.hasProperty("newVersion")) { | ||
destinationDir = project.file("build/configuration-metadata-diff/$newVersion") | ||
} | ||
} | ||
|
||
tasks.register("generate", JavaExec) { | ||
inputs.files(prepareOldMetadata, prepareNewMetadata) | ||
outputs.file(project.file("build/configuration-metadata-changelog.adoc")) | ||
classpath = sourceSets.main.runtimeClasspath | ||
mainClass = 'org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataChangelogGenerator' | ||
if (project.hasProperty("oldVersion") && project.hasProperty("newVersion")) { | ||
args = [project.file("build/configuration-metadata-diff/$oldVersion"), project.file("build/configuration-metadata-diff/$newVersion"), project.file("build/configuration-metadata-changelog.adoc")] | ||
} | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
...amework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/* | ||
* Copyright 2012-2023 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.boot.configurationmetadata.changelog; | ||
|
||
import java.io.File; | ||
import java.io.FileWriter; | ||
import java.io.IOException; | ||
|
||
/** | ||
* Generates a configuration metadata changelog. Requires three arguments: | ||
* | ||
* <ol> | ||
* <li>The path of a directory containing jar files from which the old metadata will be | ||
* extracted | ||
* <li>The path of a directory containing jar files from which the new metadata will be | ||
* extracted | ||
* <li>The path of a file to which the changelog will be written | ||
* </ol> | ||
* | ||
* The name of each directory will be used to name the old and new metadata in the | ||
* generated changelog | ||
* | ||
* @author Andy Wilkinson | ||
*/ | ||
final class ConfigurationMetadataChangelogGenerator { | ||
|
||
private ConfigurationMetadataChangelogGenerator() { | ||
|
||
} | ||
|
||
public static void main(String[] args) throws IOException { | ||
ConfigurationMetadataDiff diff = ConfigurationMetadataDiff.of( | ||
NamedConfigurationMetadataRepository.from(new File(args[0])), | ||
NamedConfigurationMetadataRepository.from(new File(args[1]))); | ||
try (ConfigurationMetadataChangelogWriter writer = new ConfigurationMetadataChangelogWriter( | ||
new FileWriter(new File(args[2])))) { | ||
writer.write(diff); | ||
} | ||
System.out.println("\nConfiguration metadata changelog written to '" + args[2] + "'"); | ||
} | ||
|
||
} |
204 changes: 204 additions & 0 deletions
204
...gframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogWriter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
/* | ||
* Copyright 2012-2023 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.boot.configurationmetadata.changelog; | ||
|
||
import java.io.PrintWriter; | ||
import java.io.Writer; | ||
import java.text.BreakIterator; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Locale; | ||
import java.util.Map; | ||
import java.util.function.Function; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; | ||
import org.springframework.boot.configurationmetadata.Deprecation; | ||
import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference; | ||
import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference.Type; | ||
|
||
/** | ||
* Writes a configuration metadata changelog from a {@link ConfigurationMetadataDiff}. | ||
* | ||
* @author Stephane Nicoll | ||
* @author Andy Wilkinson | ||
*/ | ||
class ConfigurationMetadataChangelogWriter implements AutoCloseable { | ||
|
||
private final PrintWriter out; | ||
|
||
ConfigurationMetadataChangelogWriter(Writer out) { | ||
this.out = new PrintWriter(out); | ||
} | ||
|
||
void write(ConfigurationMetadataDiff diff) { | ||
this.out.append(String.format("Configuration property changes between `%s` and " + "`%s`%n", diff.leftName(), | ||
diff.rightName())); | ||
this.out.append(System.lineSeparator()); | ||
this.out.append(String.format("== Deprecated in `%s`%n", diff.rightName())); | ||
Map<Type, List<Difference>> differencesByType = differencesByType(diff); | ||
writeDeprecatedProperties(differencesByType.get(Type.DEPRECATED)); | ||
this.out.append(System.lineSeparator()); | ||
this.out.append(String.format("== New in `%s`%n", diff.rightName())); | ||
writeAddedProperties(differencesByType.get(Type.ADDED)); | ||
this.out.append(System.lineSeparator()); | ||
this.out.append(String.format("== Removed in `%s`%n", diff.rightName())); | ||
writeRemovedProperties(differencesByType.get(Type.DELETED), differencesByType.get(Type.DEPRECATED)); | ||
} | ||
|
||
private Map<Type, List<Difference>> differencesByType(ConfigurationMetadataDiff diff) { | ||
Map<Type, List<Difference>> differencesByType = new HashMap<>(); | ||
for (Type type : Type.values()) { | ||
differencesByType.put(type, new ArrayList<>()); | ||
} | ||
for (Difference difference : diff.differences()) { | ||
differencesByType.get(difference.type()).add(difference); | ||
} | ||
return differencesByType; | ||
} | ||
|
||
private void writeDeprecatedProperties(List<Difference> differences) { | ||
if (differences.isEmpty()) { | ||
this.out.append(String.format("None.%n")); | ||
} | ||
else { | ||
List<Difference> properties = sortProperties(differences, Difference::right).stream() | ||
.filter(this::isDeprecatedInRelease) | ||
.collect(Collectors.toList()); | ||
this.out.append(String.format("|======================%n")); | ||
this.out.append(String.format("|Key |Replacement |Reason%n")); | ||
properties.forEach((diff) -> { | ||
ConfigurationMetadataProperty property = diff.right(); | ||
writeDeprecatedProperty(property); | ||
}); | ||
this.out.append(String.format("|======================%n")); | ||
} | ||
this.out.append(String.format("%n%n")); | ||
} | ||
|
||
private boolean isDeprecatedInRelease(Difference difference) { | ||
return difference.right().getDeprecation() != null | ||
&& Deprecation.Level.ERROR != difference.right().getDeprecation().getLevel(); | ||
} | ||
|
||
private void writeAddedProperties(List<Difference> differences) { | ||
if (differences.isEmpty()) { | ||
this.out.append(String.format("None.%n")); | ||
} | ||
else { | ||
List<Difference> properties = sortProperties(differences, Difference::right); | ||
this.out.append(String.format("|======================%n")); | ||
this.out.append(String.format("|Key |Default value |Description%n")); | ||
properties.forEach((diff) -> writeRegularProperty(diff.right())); | ||
this.out.append(String.format("|======================%n")); | ||
} | ||
this.out.append(String.format("%n%n")); | ||
} | ||
|
||
private void writeRemovedProperties(List<Difference> deleted, List<Difference> deprecated) { | ||
List<Difference> removed = getRemovedProperties(deleted, deprecated); | ||
if (removed.isEmpty()) { | ||
this.out.append(String.format("None.%n")); | ||
} | ||
else { | ||
this.out.append(String.format("|======================%n")); | ||
this.out.append(String.format("|Key |Replacement |Reason%n")); | ||
removed.forEach((property) -> writeDeprecatedProperty( | ||
(property.right() != null) ? property.right() : property.left())); | ||
this.out.append(String.format("|======================%n")); | ||
} | ||
} | ||
|
||
private List<Difference> getRemovedProperties(List<Difference> deleted, List<Difference> deprecated) { | ||
List<Difference> properties = new ArrayList<>(deleted); | ||
properties.addAll(deprecated.stream().filter((p) -> !isDeprecatedInRelease(p)).collect(Collectors.toList())); | ||
return sortProperties(properties, | ||
(difference) -> (difference.left() != null) ? difference.left() : difference.right()); | ||
} | ||
|
||
private void writeRegularProperty(ConfigurationMetadataProperty property) { | ||
this.out.append("|`").append(property.getId()).append("` |"); | ||
if (property.getDefaultValue() != null) { | ||
this.out.append("`").append(defaultValueToString(property.getDefaultValue())).append("`"); | ||
} | ||
this.out.append(" |"); | ||
if (property.getDescription() != null) { | ||
this.out.append(property.getShortDescription()); | ||
} | ||
this.out.append(System.lineSeparator()); | ||
} | ||
|
||
private void writeDeprecatedProperty(ConfigurationMetadataProperty property) { | ||
Deprecation deprecation = (property.getDeprecation() != null) ? property.getDeprecation() : new Deprecation(); | ||
this.out.append("|`").append(property.getId()).append("` |"); | ||
if (deprecation.getReplacement() != null) { | ||
this.out.append("`").append(deprecation.getReplacement()).append("`"); | ||
} | ||
this.out.append(" |"); | ||
if (deprecation.getReason() != null) { | ||
this.out.append(getFirstSentence(deprecation.getReason())); | ||
} | ||
this.out.append(System.lineSeparator()); | ||
} | ||
|
||
private String getFirstSentence(String text) { | ||
int dot = text.indexOf('.'); | ||
if (dot != -1) { | ||
BreakIterator breakIterator = BreakIterator.getSentenceInstance(Locale.US); | ||
breakIterator.setText(text); | ||
String sentence = text.substring(breakIterator.first(), breakIterator.next()).trim(); | ||
return removeSpaceBetweenLine(sentence); | ||
} | ||
else { | ||
String[] lines = text.split(System.lineSeparator()); | ||
return lines[0].trim(); | ||
} | ||
} | ||
|
||
private static String removeSpaceBetweenLine(String text) { | ||
String[] lines = text.split(System.lineSeparator()); | ||
StringBuilder sb = new StringBuilder(); | ||
for (String line : lines) { | ||
sb.append(line.trim()).append(" "); | ||
} | ||
return sb.toString().trim(); | ||
} | ||
|
||
private List<Difference> sortProperties(List<Difference> properties, | ||
Function<Difference, ConfigurationMetadataProperty> property) { | ||
List<Difference> sorted = new ArrayList<>(properties); | ||
sorted.sort((o1, o2) -> property.apply(o1).getId().compareTo(property.apply(o2).getId())); | ||
return sorted; | ||
} | ||
|
||
private static String defaultValueToString(Object defaultValue) { | ||
if (defaultValue instanceof Object[]) { | ||
return Stream.of((Object[]) defaultValue).map(Object::toString).collect(Collectors.joining(", ")); | ||
} | ||
else { | ||
return defaultValue.toString(); | ||
} | ||
} | ||
|
||
@Override | ||
public void close() { | ||
this.out.close(); | ||
} | ||
|
||
} |
Oops, something went wrong.