Browse Source

Generate a release notes page per patch version (#85279)

Closes #85250. The approach of generating a single asciidoc page for all
releases in a minor series makes it harder to preserve any manual edits
that we have to make. Instead, generate a page per-version. It should
make little difference to the final documentation that users see.
Rory Hunter 3 years ago
parent
commit
4ec75537cd

+ 8 - 4
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/GenerateReleaseNotesTask.java

@@ -82,7 +82,9 @@ public class GenerateReleaseNotesTask extends DefaultTask {
 
     @TaskAction
     public void executeTask() throws IOException {
-        if (needsGitTags(VersionProperties.getElasticsearch())) {
+        final String currentVersion = VersionProperties.getElasticsearch();
+
+        if (needsGitTags(currentVersion)) {
             findAndUpdateUpstreamRemote(gitWrapper);
         }
 
@@ -90,7 +92,7 @@ public class GenerateReleaseNotesTask extends DefaultTask {
 
         final Map<QualifiedVersion, Set<File>> filesByVersion = partitionFilesByVersion(
             gitWrapper,
-            VersionProperties.getElasticsearch(),
+            currentVersion,
             this.changelogs.getFiles()
         );
 
@@ -103,7 +105,7 @@ public class GenerateReleaseNotesTask extends DefaultTask {
             changelogsByVersion.put(version, entriesForVersion);
         });
 
-        final Set<QualifiedVersion> versions = getVersions(gitWrapper, VersionProperties.getElasticsearch());
+        final Set<QualifiedVersion> versions = getVersions(gitWrapper, currentVersion);
 
         LOGGER.info("Updating release notes index...");
         ReleaseNotesIndexGenerator.update(
@@ -113,10 +115,12 @@ public class GenerateReleaseNotesTask extends DefaultTask {
         );
 
         LOGGER.info("Generating release notes...");
+        final QualifiedVersion qualifiedVersion = QualifiedVersion.of(currentVersion);
         ReleaseNotesGenerator.update(
             this.releaseNotesTemplate.get().getAsFile(),
             this.releaseNotesFile.get().getAsFile(),
-            changelogsByVersion
+            qualifiedVersion,
+            changelogsByVersion.getOrDefault(qualifiedVersion, Set.of())
         );
 
         LOGGER.info("Generating release highlights...");

+ 3 - 7
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/QualifiedVersion.java

@@ -11,6 +11,7 @@ package org.elasticsearch.gradle.internal.release;
 import org.elasticsearch.gradle.Version;
 
 import java.util.Comparator;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -21,12 +22,7 @@ import java.util.regex.Pattern;
  * with how {@link Version} is used in the build. It also retains any qualifier (prerelease) information, and uses
  * that information when comparing instances.
  */
-public record QualifiedVersion(
-    int major,
-    int minor,
-    int revision,
-    org.elasticsearch.gradle.internal.release.QualifiedVersion.Qualifier qualifier
-) implements Comparable<QualifiedVersion> {
+public record QualifiedVersion(int major, int minor, int revision, Qualifier qualifier) implements Comparable<QualifiedVersion> {
 
     private static final Pattern pattern = Pattern.compile(
         "^v? (\\d+) \\. (\\d+) \\. (\\d+) (?: - (alpha\\d+ | beta\\d+ | rc\\d+ | SNAPSHOT ) )? $",
@@ -56,7 +52,7 @@ public record QualifiedVersion(
 
     @Override
     public String toString() {
-        return "%d.%d.%d%s".formatted(major, minor, revision, qualifier == null ? "" : "-" + qualifier);
+        return String.format(Locale.ROOT, "%d.%d.%d%s", major, minor, revision, qualifier == null ? "" : "-" + qualifier);
     }
 
     public boolean hasQualifier() {

+ 23 - 39
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseNotesGenerator.java

@@ -14,7 +14,6 @@ import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.nio.file.Files;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -31,7 +30,7 @@ import static java.util.stream.Collectors.toList;
  */
 public class ReleaseNotesGenerator {
     /**
-     * These mappings translate change types into the headings as they should appears in the release notes.
+     * These mappings translate change types into the headings as they should appear in the release notes.
      */
     private static final Map<String, String> TYPE_LABELS = new HashMap<>();
 
@@ -47,63 +46,48 @@ public class ReleaseNotesGenerator {
         TYPE_LABELS.put("upgrade", "Upgrades");
     }
 
-    static void update(File templateFile, File outputFile, Map<QualifiedVersion, Set<ChangelogEntry>> changelogs) throws IOException {
+    static void update(File templateFile, File outputFile, QualifiedVersion version, Set<ChangelogEntry> changelogs) throws IOException {
         final String templateString = Files.readString(templateFile.toPath());
 
         try (FileWriter output = new FileWriter(outputFile)) {
-            output.write(generateFile(templateString, changelogs));
+            output.write(generateFile(templateString, version, changelogs));
         }
     }
 
     @VisibleForTesting
-    static String generateFile(String template, Map<QualifiedVersion, Set<ChangelogEntry>> changelogs) throws IOException {
-        final var changelogsByVersionByTypeByArea = buildChangelogBreakdown(changelogs);
+    static String generateFile(String template, QualifiedVersion version, Set<ChangelogEntry> changelogs) throws IOException {
+        final var changelogsByTypeByArea = buildChangelogBreakdown(changelogs);
 
         final Map<String, Object> bindings = new HashMap<>();
-        bindings.put("changelogsByVersionByTypeByArea", changelogsByVersionByTypeByArea);
+        bindings.put("version", version);
+        bindings.put("changelogsByTypeByArea", changelogsByTypeByArea);
         bindings.put("TYPE_LABELS", TYPE_LABELS);
 
         return TemplateUtils.render(template, bindings);
     }
 
-    private static Map<QualifiedVersion, Map<String, Map<String, List<ChangelogEntry>>>> buildChangelogBreakdown(
-        Map<QualifiedVersion, Set<ChangelogEntry>> changelogsByVersion
-    ) {
-        Map<QualifiedVersion, Map<String, Map<String, List<ChangelogEntry>>>> changelogsByVersionByTypeByArea = new TreeMap<>(
-            Comparator.reverseOrder()
-        );
-
-        changelogsByVersion.forEach((version, changelogs) -> {
-            Map<String, Map<String, List<ChangelogEntry>>> changelogsByTypeByArea = changelogs.stream()
-                .collect(
+    private static Map<String, Map<String, List<ChangelogEntry>>> buildChangelogBreakdown(Set<ChangelogEntry> changelogs) {
+        Map<String, Map<String, List<ChangelogEntry>>> changelogsByTypeByArea = changelogs.stream()
+            .collect(
+                groupingBy(
+                    // Entries with breaking info are always put in the breaking section
+                    entry -> entry.getBreaking() == null ? entry.getType() : "breaking",
+                    TreeMap::new,
+                    // Group changelogs for each type by their team area
                     groupingBy(
-                        // Entries with breaking info are always put in the breaking section
-                        entry -> entry.getBreaking() == null ? entry.getType() : "breaking",
+                        // `security` and `known-issue` areas don't need to supply an area
+                        entry -> entry.getType().equals("known-issue") || entry.getType().equals("security") ? "_all_" : entry.getArea(),
                         TreeMap::new,
-                        // Group changelogs for each type by their team area
-                        groupingBy(
-                            // `security` and `known-issue` areas don't need to supply an area
-                            entry -> entry.getType().equals("known-issue") || entry.getType().equals("security")
-                                ? "_all_"
-                                : entry.getArea(),
-                            TreeMap::new,
-                            toList()
-                        )
+                        toList()
                     )
-                );
-
-            changelogsByVersionByTypeByArea.put(version, changelogsByTypeByArea);
-        });
+                )
+            );
 
         // Sort per-area changelogs by their summary text. Assumes that the underlying list is sortable
-        changelogsByVersionByTypeByArea.forEach(
-            (_version, byVersion) -> byVersion.forEach(
-                (_type, byTeam) -> byTeam.forEach(
-                    (_team, changelogsForTeam) -> changelogsForTeam.sort(comparing(ChangelogEntry::getSummary))
-                )
-            )
+        changelogsByTypeByArea.forEach(
+            (_type, byTeam) -> byTeam.forEach((_team, changelogsForTeam) -> changelogsForTeam.sort(comparing(ChangelogEntry::getSummary)))
         );
 
-        return changelogsByVersionByTypeByArea;
+        return changelogsByTypeByArea;
     }
 }

+ 6 - 1
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseNotesIndexGenerator.java

@@ -43,7 +43,12 @@ public class ReleaseNotesIndexGenerator {
         versionsSet.stream().map(v -> v.isSnapshot() ? v.withoutQualifier() : v).forEach(versions::add);
 
         final List<String> includeVersions = versions.stream()
-            .map(v -> v.hasQualifier() ? v.toString() : v.major() + "." + v.minor())
+            .map(
+                // We didn't split up the notes for 8.0
+                version -> version.isBefore(QualifiedVersion.of("8.1.0")) && version.hasQualifier() == false
+                    ? version.major() + "." + version.minor()
+                    : version.toString()
+            )
             .distinct()
             .collect(Collectors.toList());
 

+ 8 - 1
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseToolsPlugin.java

@@ -77,7 +77,14 @@ public class ReleaseToolsPlugin implements Plugin<Project> {
 
             task.setReleaseNotesTemplate(projectDirectory.file(RESOURCES + "templates/release-notes.asciidoc"));
             task.setReleaseNotesFile(
-                projectDirectory.file(String.format("docs/reference/release-notes/%d.%d.asciidoc", version.getMajor(), version.getMinor()))
+                projectDirectory.file(
+                    String.format(
+                        "docs/reference/release-notes/%d.%d.%d.asciidoc",
+                        version.getMajor(),
+                        version.getMinor(),
+                        version.getRevision()
+                    )
+                )
             );
 
             task.setReleaseHighlightsTemplate(projectDirectory.file(RESOURCES + "templates/release-highlights.asciidoc"));

+ 8 - 10
build-tools-internal/src/main/resources/templates/release-notes.asciidoc

@@ -1,4 +1,4 @@
-<% for (version in changelogsByVersionByTypeByArea.keySet()) {
+<%
 def unqualifiedVersion = version.withoutQualifier()
 %>[[release-notes-$unqualifiedVersion]]
 == {es} version ${unqualifiedVersion}
@@ -6,32 +6,32 @@ def unqualifiedVersion = version.withoutQualifier()
 coming[$unqualifiedVersion]
 <% } %>
 Also see <<breaking-changes-${ version.major }.${ version.minor },Breaking changes in ${ version.major }.${ version.minor }>>.
-<% if (changelogsByVersionByTypeByArea[version]["security"] != null) { %>
+<% if (changelogsByTypeByArea["security"] != null) { %>
 [discrete]
 [[security-updates-${unqualifiedVersion}]]
 === Security updates
 
-<% for (change in changelogsByVersionByTypeByArea[version].remove("security").remove("_all_")) {
+<% for (change in changelogsByTypeByArea.remove("security").remove("_all_")) {
     print "* ${change.summary}\n"
 }
 }
-if (changelogsByVersionByTypeByArea[version]["known-issue"] != null) { %>
+if (changelogsByTypeByArea["known-issue"] != null) { %>
 [discrete]
 [[known-issues-${unqualifiedVersion}]]
 === Known issues
 
-<% for (change in changelogsByVersionByTypeByArea[version].remove("known-issue").remove("_all_")) {
+<% for (change in changelogsByTypeByArea.remove("known-issue").remove("_all_")) {
     print "* ${change.summary}\n"
 }
 }
-for (changeType in changelogsByVersionByTypeByArea[version].keySet()) { %>
+for (changeType in changelogsByTypeByArea.keySet()) { %>
 [[${ changeType }-${ unqualifiedVersion }]]
 [float]
 === ${ TYPE_LABELS.getOrDefault(changeType, 'No mapping for TYPE_LABELS[' + changeType + ']') }
-<% for (team in changelogsByVersionByTypeByArea[version][changeType].keySet()) {
+<% for (team in changelogsByTypeByArea[changeType].keySet()) {
     print "\n${team}::\n";
 
-    for (change in changelogsByVersionByTypeByArea[version][changeType][team]) {
+    for (change in changelogsByTypeByArea[changeType][team]) {
         print "* ${change.summary} {es-pull}${change.pr}[#${change.pr}]"
         if (change.issues != null && change.issues.empty == false) {
             print change.issues.size() == 1 ? " (issue: " : " (issues: "
@@ -43,5 +43,3 @@ for (changeType in changelogsByVersionByTypeByArea[version].keySet()) { %>
 }
 }
 print "\n\n"
-}
-%>

+ 10 - 28
build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ReleaseNotesGeneratorTest.java

@@ -14,10 +14,8 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -37,52 +35,36 @@ public class ReleaseNotesGeneratorTest {
             "/org/elasticsearch/gradle/internal/release/ReleaseNotesGeneratorTest.generateFile.asciidoc"
         );
 
-        final Map<QualifiedVersion, Set<ChangelogEntry>> entries = getEntries();
+        final Set<ChangelogEntry> entries = getEntries();
 
         // when:
-        final String actualOutput = ReleaseNotesGenerator.generateFile(template, entries);
+        final String actualOutput = ReleaseNotesGenerator.generateFile(template, QualifiedVersion.of("8.2.0-SNAPSHOT"), entries);
 
         // then:
         assertThat(actualOutput, equalTo(expectedOutput));
     }
 
-    private Map<QualifiedVersion, Set<ChangelogEntry>> getEntries() {
-        final Set<ChangelogEntry> entries_8_2_0 = new HashSet<>();
-        entries_8_2_0.addAll(buildEntries(1, 2));
-        entries_8_2_0.addAll(buildEntries(2, 2));
-        entries_8_2_0.addAll(buildEntries(3, 2));
-
-        final Set<ChangelogEntry> entries_8_1_0 = new HashSet<>();
-        entries_8_1_0.addAll(buildEntries(4, 2));
-        entries_8_1_0.addAll(buildEntries(5, 2));
-        entries_8_1_0.addAll(buildEntries(6, 2));
-
-        final Set<ChangelogEntry> entries_8_0_0 = new HashSet<>();
-        entries_8_0_0.addAll(buildEntries(7, 2));
-        entries_8_0_0.addAll(buildEntries(8, 2));
-        entries_8_0_0.addAll(buildEntries(9, 2));
+    private Set<ChangelogEntry> getEntries() {
+        final Set<ChangelogEntry> entries = new HashSet<>();
+        entries.addAll(buildEntries(1, 2));
+        entries.addAll(buildEntries(2, 2));
+        entries.addAll(buildEntries(3, 2));
 
         // Security issues are presented first in the notes
         final ChangelogEntry securityEntry = new ChangelogEntry();
         securityEntry.setArea("Security");
         securityEntry.setType("security");
         securityEntry.setSummary("Test security issue");
-        entries_8_2_0.add(securityEntry);
+        entries.add(securityEntry);
 
         // known issues are presented after security issues
         final ChangelogEntry knownIssue = new ChangelogEntry();
         knownIssue.setArea("Search");
         knownIssue.setType("known-issue");
         knownIssue.setSummary("Test known issue");
-        entries_8_1_0.add(knownIssue);
-
-        final Map<QualifiedVersion, Set<ChangelogEntry>> result = new HashMap<>();
+        entries.add(knownIssue);
 
-        result.put(QualifiedVersion.of("8.2.0-SNAPSHOT"), entries_8_2_0);
-        result.put(QualifiedVersion.of("8.1.0"), entries_8_1_0);
-        result.put(QualifiedVersion.of("8.0.0"), entries_8_0_0);
-
-        return result;
+        return entries;
     }
 
     private List<ChangelogEntry> buildEntries(int seed, int count) {

+ 6 - 67
build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/ReleaseNotesGeneratorTest.generateFile.asciidoc

@@ -11,6 +11,12 @@ Also see <<breaking-changes-8.2,Breaking changes in 8.2>>.
 
 * Test security issue
 
+[discrete]
+[[known-issues-8.2.0]]
+=== Known issues
+
+* Test known issue
+
 [[deprecation-8.2.0]]
 [float]
 === Deprecations
@@ -36,70 +42,3 @@ Mappings::
 * Test changelog entry 3_1 {es-pull}3002[#3002] (issues: {es-issue}3003[#3003], {es-issue}3004[#3004])
 
 
-[[release-notes-8.1.0]]
-== {es} version 8.1.0
-
-Also see <<breaking-changes-8.1,Breaking changes in 8.1>>.
-
-[discrete]
-[[known-issues-8.1.0]]
-=== Known issues
-
-* Test known issue
-
-[[new-aggregation-8.1.0]]
-[float]
-=== New aggregation
-
-Search::
-* Test changelog entry 4_0 {es-pull}4000[#4000] (issue: {es-issue}4001[#4001])
-* Test changelog entry 4_1 {es-pull}4002[#4002] (issues: {es-issue}4003[#4003], {es-issue}4004[#4004])
-
-[[regression-8.1.0]]
-[float]
-=== Regressions
-
-Security::
-* Test changelog entry 5_0 {es-pull}5000[#5000] (issue: {es-issue}5001[#5001])
-* Test changelog entry 5_1 {es-pull}5002[#5002] (issues: {es-issue}5003[#5003], {es-issue}5004[#5004])
-
-[[upgrade-8.1.0]]
-[float]
-=== Upgrades
-
-Aggregation::
-* Test changelog entry 6_0 {es-pull}6000[#6000] (issue: {es-issue}6001[#6001])
-* Test changelog entry 6_1 {es-pull}6002[#6002] (issues: {es-issue}6003[#6003], {es-issue}6004[#6004])
-
-
-[[release-notes-8.0.0]]
-== {es} version 8.0.0
-
-Also see <<breaking-changes-8.0,Breaking changes in 8.0>>.
-
-[[bug-8.0.0]]
-[float]
-=== Bug fixes
-
-Cluster::
-* Test changelog entry 7_0 {es-pull}7000[#7000] (issue: {es-issue}7001[#7001])
-* Test changelog entry 7_1 {es-pull}7002[#7002] (issues: {es-issue}7003[#7003], {es-issue}7004[#7004])
-
-[[deprecation-8.0.0]]
-[float]
-=== Deprecations
-
-Indices::
-* Test changelog entry 8_0 {es-pull}8000[#8000] (issue: {es-issue}8001[#8001])
-* Test changelog entry 8_1 {es-pull}8002[#8002] (issues: {es-issue}8003[#8003], {es-issue}8004[#8004])
-
-[[enhancement-8.0.0]]
-[float]
-=== Enhancements
-
-Mappings::
-* Test changelog entry 9_0 {es-pull}9000[#9000] (issue: {es-issue}9001[#9001])
-* Test changelog entry 9_1 {es-pull}9002[#9002] (issues: {es-issue}9003[#9003], {es-issue}9004[#9004])
-
-
-

+ 3 - 2
build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/ReleaseNotesIndexGeneratorTest.generateFile.asciidoc

@@ -18,8 +18,9 @@ This section summarizes the changes in each release.
 
 --
 
-include::release-notes/8.2.asciidoc[]
-include::release-notes/8.1.asciidoc[]
+include::release-notes/8.2.0.asciidoc[]
+include::release-notes/8.1.1.asciidoc[]
+include::release-notes/8.1.0.asciidoc[]
 include::release-notes/8.0.asciidoc[]
 include::release-notes/8.0.0-rc3.asciidoc[]
 include::release-notes/8.0.0-beta2.asciidoc[]