Skip to content

Commit

Permalink
Make improvements to the release notes generator (#83525)
Browse files Browse the repository at this point in the history
Forward-port of the `build-tools-internal` parts of #83341.
  • Loading branch information
pugnascotia committed Feb 7, 2022
1 parent 3ae08c8 commit bafdcbd
Show file tree
Hide file tree
Showing 21 changed files with 412 additions and 509 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
import java.util.stream.StreamSupport;

/**
* Incremental task to validate a set of JSON files against against a schema.
* Incremental task to validate a set of JSON files against a schema.
*/
public class ValidateJsonAgainstSchemaTask extends DefaultTask {
private File jsonSchema;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

/**
* Incremental task to validate a set of YAML files against against a schema.
* Incremental task to validate a set of YAML files against a schema.
*/
public class ValidateYamlAgainstSchemaTask extends ValidateJsonAgainstSchemaTask {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,148 +11,72 @@
import com.google.common.annotations.VisibleForTesting;

import org.elasticsearch.gradle.VersionProperties;
import org.gradle.api.GradleException;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;

/**
* Generates the page that contains an index into the breaking changes and lists deprecations for a minor version release,
* and the individual pages for each breaking area.
* Generates the page that contains breaking changes deprecations for a minor release series.
*/
public class BreakingChangesGenerator {

// Needs to match `changelog-schema.json`
private static final List<String> BREAKING_AREAS = List.of(
"Cluster and node setting",
"Command line tool",
"Index setting",
"JVM option",
"Java API",
"Logging",
"Mapping",
"Packaging",
"Painless",
"REST API",
"System requirement",
"Transform"
);

static void update(
File indexTemplateFile,
File indexOutputFile,
File outputDirectory,
File areaTemplateFile,
List<ChangelogEntry> entries
) throws IOException {
if (outputDirectory.exists()) {
if (outputDirectory.isDirectory() == false) {
throw new GradleException("Path [" + outputDirectory + "] exists but isn't a directory!");
}
} else {
Files.createDirectory(outputDirectory.toPath());
}

try (FileWriter output = new FileWriter(indexOutputFile)) {
static void update(File migrationTemplateFile, File migrationOutputFile, List<ChangelogEntry> entries) throws IOException {
try (FileWriter output = new FileWriter(migrationOutputFile)) {
output.write(
generateIndexFile(
generateMigrationFile(
QualifiedVersion.of(VersionProperties.getElasticsearch()),
Files.readString(indexTemplateFile.toPath()),
Files.readString(migrationTemplateFile.toPath()),
entries
)
);
}

String areaTemplate = Files.readString(areaTemplateFile.toPath());

for (String breakingArea : BREAKING_AREAS) {
final List<ChangelogEntry.Breaking> entriesForArea = entries.stream()
.map(ChangelogEntry::getBreaking)
.filter(entry -> entry != null && breakingArea.equals(entry.getArea()))
.collect(Collectors.toList());

if (entriesForArea.isEmpty()) {
continue;
}

final String outputFilename = breakingArea.toLowerCase(Locale.ROOT).replaceFirst(" and", "").replaceAll(" ", "-")
+ "-changes.asciidoc";

try (FileWriter output = new FileWriter(outputDirectory.toPath().resolve(outputFilename).toFile())) {
output.write(
generateBreakingAreaFile(
QualifiedVersion.of(VersionProperties.getElasticsearch()),
areaTemplate,
breakingArea,
entriesForArea
)
);
}
}
}

@VisibleForTesting
static String generateIndexFile(QualifiedVersion version, String template, List<ChangelogEntry> entries) throws IOException {
final Map<String, List<ChangelogEntry.Deprecation>> deprecationsByArea = entries.stream()
static String generateMigrationFile(QualifiedVersion version, String template, List<ChangelogEntry> entries) throws IOException {
final Map<Boolean, Map<String, List<ChangelogEntry.Deprecation>>> deprecationsByNotabilityByArea = entries.stream()
.map(ChangelogEntry::getDeprecation)
.filter(Objects::nonNull)
.sorted(comparing(ChangelogEntry.Deprecation::getTitle))
.collect(groupingBy(ChangelogEntry.Deprecation::getArea, TreeMap::new, Collectors.toList()));

final List<String> breakingIncludeList = entries.stream()
.filter(each -> each.getBreaking() != null)
.map(each -> each.getBreaking().getArea().toLowerCase(Locale.ROOT).replaceFirst(" and", "").replaceAll(" ", "-"))
.distinct()
.sorted()
.toList();

final Map<String, Object> bindings = new HashMap<>();
bindings.put("breakingIncludeList", breakingIncludeList);
bindings.put("deprecationsByArea", deprecationsByArea);
bindings.put("isElasticsearchSnapshot", version.isSnapshot());
bindings.put("majorDotMinor", version.major() + "." + version.minor());
bindings.put("majorMinor", String.valueOf(version.major()) + version.minor());
bindings.put("nextMajor", (version.major() + 1) + ".0");
bindings.put("version", version);

return TemplateUtils.render(template, bindings);
}
.collect(
groupingBy(
ChangelogEntry.Deprecation::isNotable,
TreeMap::new,
groupingBy(ChangelogEntry.Deprecation::getArea, TreeMap::new, toList())
)
);

@VisibleForTesting
static String generateBreakingAreaFile(
QualifiedVersion version,
String template,
String breakingArea,
List<ChangelogEntry.Breaking> entriesForArea
) throws IOException {
final Map<Boolean, Set<ChangelogEntry.Breaking>> breakingEntriesByNotability = entriesForArea.stream()
final Map<Boolean, Map<String, List<ChangelogEntry.Breaking>>> breakingByNotabilityByArea = entries.stream()
.map(ChangelogEntry::getBreaking)
.filter(Objects::nonNull)
.sorted(comparing(ChangelogEntry.Breaking::getTitle))
.collect(
groupingBy(
ChangelogEntry.Breaking::isNotable,
toCollection(() -> new TreeSet<>(comparing(ChangelogEntry.Breaking::getTitle)))
TreeMap::new,
groupingBy(ChangelogEntry.Breaking::getArea, TreeMap::new, toList())
)
);

final Map<String, Object> bindings = new HashMap<>();
bindings.put("breakingArea", breakingArea);
bindings.put("breakingEntriesByNotability", breakingEntriesByNotability);
bindings.put("breakingAreaAnchor", breakingArea.toLowerCase(Locale.ROOT).replaceFirst(" and", "").replaceAll(" ", "_"));
bindings.put("breakingByNotabilityByArea", breakingByNotabilityByArea);
bindings.put("deprecationsByNotabilityByArea", deprecationsByNotabilityByArea);
bindings.put("isElasticsearchSnapshot", version.isSnapshot());
bindings.put("majorDotMinor", version.major() + "." + version.minor());
bindings.put("majorMinor", String.valueOf(version.major()) + version.minor());
bindings.put("nextMajor", (version.major() + 1) + ".0");
bindings.put("version", version);

return TemplateUtils.render(template, bindings);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public class ChangelogEntry {

private static final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());

/**
* Create a new instance by parsing the supplied file
* @param file the YAML file to parse
* @return a new instance
*/
public static ChangelogEntry parse(File file) {
try {
return yamlMapper.readValue(file, ChangelogEntry.class);
Expand Down Expand Up @@ -209,7 +214,11 @@ public String toString() {
}
}

public static class Breaking {
public static class Breaking extends CompatibilityChange {}

public static class Deprecation extends CompatibilityChange {}

abstract static class CompatibilityChange {
private String area;
private String title;
private String details;
Expand Down Expand Up @@ -277,13 +286,13 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
Breaking breaking = (Breaking) o;
return notable == breaking.notable
&& Objects.equals(area, breaking.area)
&& Objects.equals(title, breaking.title)
&& Objects.equals(details, breaking.details)
&& Objects.equals(impact, breaking.impact)
&& Objects.equals(essSettingChange, breaking.essSettingChange);
CompatibilityChange breaking = (CompatibilityChange) o;
return notable == breaking.isNotable()
&& Objects.equals(area, breaking.getArea())
&& Objects.equals(title, breaking.getTitle())
&& Objects.equals(details, breaking.getDetails())
&& Objects.equals(impact, breaking.getImpact())
&& Objects.equals(essSettingChange, breaking.isEssSettingChange());
}

@Override
Expand All @@ -294,7 +303,8 @@ public int hashCode() {
@Override
public String toString() {
return String.format(
"Breaking{area='%s', title='%s', details='%s', impact='%s', notable=%s, essSettingChange=%s}",
"%s{area='%s', title='%s', details='%s', impact='%s', notable=%s, essSettingChange=%s}",
this.getClass().getSimpleName(),
area,
title,
details,
Expand All @@ -305,66 +315,11 @@ public String toString() {
}
}

public static class Deprecation {
private String area;
private String title;
private String body;

public String getArea() {
return area;
}

public void setArea(String area) {
this.area = area;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getBody() {
return body;
}

public void setBody(String body) {
this.body = body;
}

public String getAnchor() {
return generatedAnchor(this.title);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Deprecation that = (Deprecation) o;
return Objects.equals(area, that.area) && Objects.equals(title, that.title) && Objects.equals(body, that.body);
}

@Override
public int hashCode() {
return Objects.hash(area, title, body);
}

@Override
public String toString() {
return String.format("Deprecation{area='%s', title='%s', body='%s'}", area, title, body);
}
}

private static String generatedAnchor(String input) {
final List<String> excludes = List.of("the", "is", "a", "and");
final List<String> excludes = List.of("the", "is", "a", "and", "now", "that");

final String[] words = input.toLowerCase(Locale.ROOT)
.replaceAll("'", "")
.replaceAll("[^\\w]+", "_")
.replaceFirst("^_+", "")
.replaceFirst("_+$", "")
Expand Down

0 comments on commit bafdcbd

Please sign in to comment.