diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/BreakingChangesGenerator.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/BreakingChangesGenerator.java index 4f4b4ce33a8f8..2ca67134b8922 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/BreakingChangesGenerator.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/BreakingChangesGenerator.java @@ -74,6 +74,7 @@ static String generateMigrationFile(QualifiedVersion version, String template, L bindings.put("deprecationsByNotabilityByArea", deprecationsByNotabilityByArea); bindings.put("isElasticsearchSnapshot", version.isSnapshot()); bindings.put("majorDotMinor", version.major() + "." + version.minor()); + bindings.put("majorDotMinorDotRevision", version.major() + "." + version.minor() + "." + version.revision()); bindings.put("majorMinor", String.valueOf(version.major()) + version.minor()); bindings.put("nextMajor", (version.major() + 1) + ".0"); bindings.put("version", version); diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ChangelogEntry.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ChangelogEntry.java index 7827b34f57e43..093a7a19a42f9 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ChangelogEntry.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ChangelogEntry.java @@ -99,6 +99,7 @@ public Highlight getHighlight() { public void setHighlight(Highlight highlight) { this.highlight = highlight; + if (this.highlight != null) this.highlight.pr = this.pr; } public Breaking getBreaking() { @@ -160,6 +161,7 @@ public static class Highlight { private boolean notable; private String title; private String body; + private Integer pr; public boolean isNotable() { return notable; @@ -189,6 +191,10 @@ public String getAnchor() { return generatedAnchor(this.title); } + public Integer getPr() { + return pr; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/GenerateReleaseNotesTask.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/GenerateReleaseNotesTask.java index d110fa392ae4b..25034eb36b1a7 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/GenerateReleaseNotesTask.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/GenerateReleaseNotesTask.java @@ -37,6 +37,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.stream.Stream; import javax.inject.Inject; @@ -55,11 +56,13 @@ public class GenerateReleaseNotesTask extends DefaultTask { private final RegularFileProperty releaseNotesTemplate; private final RegularFileProperty releaseHighlightsTemplate; private final RegularFileProperty breakingChangesTemplate; + private final RegularFileProperty migrationIndexTemplate; private final RegularFileProperty releaseNotesIndexFile; private final RegularFileProperty releaseNotesFile; private final RegularFileProperty releaseHighlightsFile; private final RegularFileProperty breakingChangesMigrationFile; + private final RegularFileProperty migrationIndexFile; private final GitWrapper gitWrapper; @@ -71,11 +74,13 @@ public GenerateReleaseNotesTask(ObjectFactory objectFactory, ExecOperations exec releaseNotesTemplate = objectFactory.fileProperty(); releaseHighlightsTemplate = objectFactory.fileProperty(); breakingChangesTemplate = objectFactory.fileProperty(); + migrationIndexTemplate = objectFactory.fileProperty(); releaseNotesIndexFile = objectFactory.fileProperty(); releaseNotesFile = objectFactory.fileProperty(); releaseHighlightsFile = objectFactory.fileProperty(); breakingChangesMigrationFile = objectFactory.fileProperty(); + migrationIndexFile = objectFactory.fileProperty(); gitWrapper = new GitWrapper(execOperations); } @@ -136,6 +141,13 @@ public void executeTask() throws IOException { this.breakingChangesMigrationFile.get().getAsFile(), entries ); + + LOGGER.info("Updating migration/index..."); + MigrationIndexGenerator.update( + getMinorVersions(versions), + this.migrationIndexTemplate.get().getAsFile(), + this.migrationIndexFile.get().getAsFile() + ); } /** @@ -146,11 +158,19 @@ public void executeTask() throws IOException { */ @VisibleForTesting static Set getVersions(GitWrapper gitWrapper, String currentVersion) { - QualifiedVersion v = QualifiedVersion.of(currentVersion); - final String pattern = "v" + v.major() + ".*"; - Set versions = gitWrapper.listVersions(pattern).collect(toSet()); - versions.add(v); - return versions; + QualifiedVersion qualifiedVersion = QualifiedVersion.of(currentVersion); + final String pattern = "v" + qualifiedVersion.major() + ".*"; + // We may be generating notes for a minor version prior to the latest minor, so we need to filter out versions that are too new. + return Stream.concat(gitWrapper.listVersions(pattern).filter(v -> v.isBefore(qualifiedVersion)), Stream.of(qualifiedVersion)) + .collect(toSet()); + } + + /** + * Convert set of QualifiedVersion to MinorVersion by deleting all but the major and minor components. + */ + @VisibleForTesting + static Set getMinorVersions(Set versions) { + return versions.stream().map(MinorVersion::of).collect(toSet()); } /** @@ -319,6 +339,15 @@ public void setBreakingChangesTemplate(RegularFile file) { this.breakingChangesTemplate.set(file); } + @InputFile + public RegularFileProperty getMigrationIndexTemplate() { + return migrationIndexTemplate; + } + + public void setMigrationIndexTemplate(RegularFile file) { + this.migrationIndexTemplate.set(file); + } + @OutputFile public RegularFileProperty getReleaseNotesIndexFile() { return releaseNotesIndexFile; @@ -354,4 +383,13 @@ public RegularFileProperty getBreakingChangesMigrationFile() { public void setBreakingChangesMigrationFile(RegularFile file) { this.breakingChangesMigrationFile.set(file); } + + @OutputFile + public RegularFileProperty getMigrationIndexFile() { + return migrationIndexFile; + } + + public void setMigrationIndexFile(RegularFile file) { + this.migrationIndexFile.set(file); + } } diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/MigrationIndexGenerator.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/MigrationIndexGenerator.java new file mode 100644 index 0000000000000..ca420294fede0 --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/MigrationIndexGenerator.java @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.gradle.internal.release; + +import com.google.common.annotations.VisibleForTesting; + +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.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import static java.util.Comparator.reverseOrder; + +/** + * This class ensures that the migrate/index page has the appropriate anchors and include directives + * for the current repository version. + */ +public class MigrationIndexGenerator { + + static void update(Set versions, File indexTemplate, File indexFile) throws IOException { + try (FileWriter indexFileWriter = new FileWriter(indexFile)) { + indexFileWriter.write(generateFile(versions, Files.readString(indexTemplate.toPath()))); + } + } + + @VisibleForTesting + static String generateFile(Set versionsSet, String template) throws IOException { + final Set versions = new TreeSet<>(reverseOrder()); + versions.addAll(versionsSet); + final List includeVersions = versions.stream().map(MinorVersion::underscore).collect(Collectors.toList()); + + final Map bindings = new HashMap<>(); + bindings.put("versions", versions); + bindings.put("includeVersions", includeVersions); + + return TemplateUtils.render(template, bindings); + } +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/MinorVersion.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/MinorVersion.java new file mode 100644 index 0000000000000..8c78c6b38b79d --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/MinorVersion.java @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.gradle.internal.release; + +import java.util.Comparator; +import java.util.Objects; + +/** + * Encapsulates comparison and printing logic for an x.y. + */ +public record MinorVersion(int major, int minor) implements Comparable { + /** + * Converts a QualifiedVersion into a MinorVersion by deleting all but the major and minor components. + */ + public static MinorVersion of(final QualifiedVersion v) { + Objects.requireNonNull(v); + return new MinorVersion(v.major(), v.minor()); + } + + @Override + public String toString() { + return major + "." + minor; + } + + /** Generate version string with underscore instead of dot */ + public String underscore() { + return major + "_" + minor; + } + + private static final Comparator COMPARATOR = Comparator.comparing((MinorVersion v) -> v.major) + .thenComparing(v -> v.minor); + + @Override + public int compareTo(MinorVersion other) { + return COMPARATOR.compare(this, other); + } + + public boolean isBefore(MinorVersion other) { + return this.compareTo(other) < 0; + } +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGenerator.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGenerator.java index b614a0936b201..a562f0f3583f1 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGenerator.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGenerator.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,21 +44,14 @@ static String generateFile(QualifiedVersion version, String template, List= 0; minor--) { String majorMinor = major + "." + minor; - priorVersions.add( - "{ref-bare}/" - + majorMinor - + "/release-highlights" - + (minor <= 6 ? "-" + majorMinor + ".0" : "") - + ".html[" - + majorMinor - + "]" - ); + priorVersions.add("{ref-bare}/" + majorMinor + "/release-highlights.html[" + majorMinor + "]"); } } final Map> groupedHighlights = entries.stream() .map(ChangelogEntry::getHighlight) .filter(Objects::nonNull) + .sorted(Comparator.comparingInt(ChangelogEntry.Highlight::getPr)) .collect(Collectors.groupingBy(ChangelogEntry.Highlight::isNotable, Collectors.toList())); final List notableHighlights = groupedHighlights.getOrDefault(true, List.of()); diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseNotesGenerator.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseNotesGenerator.java index 48d4f49925812..58dc1f2c37299 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseNotesGenerator.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseNotesGenerator.java @@ -68,9 +68,6 @@ static String generateFile(String template, QualifiedVersion version, Set>> buildChangelogBreakdown(Set changelogs) { Map>> changelogsByTypeByArea = changelogs.stream() - // Special case - we have a changelog file that isn't in the 'known-issue' or 'security' areas, but - // doesn't have an ES PR for it. - .filter(each -> each.getPr() == null || each.getPr() != -1) .collect( groupingBy( // Entries with breaking info are always put in the breaking section diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseNotesIndexGenerator.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseNotesIndexGenerator.java index 6de2cc5544a09..81375c44acd3a 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseNotesIndexGenerator.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseNotesIndexGenerator.java @@ -29,13 +29,6 @@ */ public class ReleaseNotesIndexGenerator { - // Some versions where never released or were pulled. They shouldn't be listed. - private static Set EXCLUDED_VERSIONS = Set.of( - QualifiedVersion.of("7.0.1"), - QualifiedVersion.of("7.13.3"), - QualifiedVersion.of("7.13.4") - ); - static void update(Set versions, File indexTemplate, File indexFile) throws IOException { try (FileWriter indexFileWriter = new FileWriter(indexFile)) { indexFileWriter.write(generateFile(versions, Files.readString(indexTemplate.toPath()))); @@ -47,19 +40,9 @@ static String generateFile(Set versionsSet, String template) t final Set versions = new TreeSet<>(reverseOrder()); // For the purpose of generating the index, snapshot versions are the same as released versions. Prerelease versions are not. - versionsSet.stream() - .filter(v -> EXCLUDED_VERSIONS.contains(v) == false) - .map(v -> v.isSnapshot() ? v.withoutQualifier() : v) - .forEach(versions::add); + versionsSet.stream().map(v -> v.isSnapshot() ? v.withoutQualifier() : v).forEach(versions::add); - final List includeVersions = versions.stream() - .map( - version -> version.isBefore(QualifiedVersion.of("7.17.0")) && version.hasQualifier() == false - ? version.major() + "." + version.minor() - : version.toString() - ) - .distinct() - .collect(Collectors.toList()); + final List includeVersions = versions.stream().map(QualifiedVersion::toString).collect(Collectors.toList()); final Map bindings = new HashMap<>(); bindings.put("versions", versions); diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseToolsPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseToolsPlugin.java index 7d9d4dceba265..c93320dc2b498 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseToolsPlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ReleaseToolsPlugin.java @@ -12,6 +12,7 @@ import org.elasticsearch.gradle.VersionProperties; import org.elasticsearch.gradle.internal.conventions.precommit.PrecommitTaskPlugin; import org.elasticsearch.gradle.internal.precommit.ValidateYamlAgainstSchemaTask; +import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.file.Directory; @@ -22,6 +23,7 @@ import org.gradle.api.tasks.util.PatternSet; import java.io.File; +import java.util.function.Function; import javax.inject.Inject; @@ -67,10 +69,14 @@ public void apply(Project project) { task.dependsOn(validateChangelogsAgainstYamlTask); }); - project.getTasks().register("generateReleaseNotes", GenerateReleaseNotesTask.class).configure(task -> { + final Function> configureGenerateTask = shouldConfigureYamlFiles -> task -> { task.setGroup("Documentation"); - task.setDescription("Generates release notes from changelog files held in this checkout"); - task.setChangelogs(yamlFiles); + if (shouldConfigureYamlFiles) { + task.setChangelogs(yamlFiles); + task.setDescription("Generates release notes from changelog files held in this checkout"); + } else { + task.setDescription("Generates stub release notes e.g. after feature freeze"); + } task.setReleaseNotesIndexTemplate(projectDirectory.file(RESOURCES + "templates/release-notes-index.asciidoc")); task.setReleaseNotesIndexFile(projectDirectory.file("docs/reference/release-notes.asciidoc")); @@ -96,9 +102,16 @@ public void apply(Project project) { String.format("docs/reference/migration/migrate_%d_%d.asciidoc", version.getMajor(), version.getMinor()) ) ); + task.setMigrationIndexTemplate(projectDirectory.file(RESOURCES + "templates/migration-index.asciidoc")); + task.setMigrationIndexFile(projectDirectory.file("docs/reference/migration/index.asciidoc")); task.dependsOn(validateChangelogsTask); - }); + }; + + project.getTasks().register("generateReleaseNotes", GenerateReleaseNotesTask.class).configure(configureGenerateTask.apply(true)); + project.getTasks() + .register("generateStubReleaseNotes", GenerateReleaseNotesTask.class) + .configure(configureGenerateTask.apply(false)); project.getTasks().register("pruneChangelogs", PruneChangelogsTask.class).configure(task -> { task.setGroup("Documentation"); diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ValidateChangelogEntryTask.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ValidateChangelogEntryTask.java index 14114314ad4de..acbd79fe28194 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ValidateChangelogEntryTask.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/release/ValidateChangelogEntryTask.java @@ -8,6 +8,8 @@ package org.elasticsearch.gradle.internal.release; +import com.google.common.annotations.VisibleForTesting; + import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.file.ConfigurableFileCollection; @@ -30,6 +32,21 @@ public class ValidateChangelogEntryTask extends DefaultTask { private final ConfigurableFileCollection changelogs; private final ProjectLayout projectLayout; + public static final String TRIPLE_BACKTICK = "```"; + private static final String CODE_BLOCK_ERROR = """ + [%s] uses a triple-backtick in the [%s] section, but it must be + formatted as a Asciidoc code block. For example: + + [source,yaml] + ---- + { + "metrics.time" : 10, + "metrics.time.min" : 1, + "metrics.time.max" : 500 + } + ---- + """; + @Inject public ValidateChangelogEntryTask(ObjectFactory objectFactory, ProjectLayout projectLayout) { this.changelogs = objectFactory.fileCollection(); @@ -43,37 +60,60 @@ public void executeTask() { .stream() .collect(Collectors.toMap(file -> rootDir.relativize(file.toURI()).toString(), ChangelogEntry::parse)); + changelogs.forEach(ValidateChangelogEntryTask::validate); + } + + @VisibleForTesting + static void validate(String path, ChangelogEntry entry) { // We don't try to find all such errors, because we expect them to be rare e.g. only // when a new file is added. - changelogs.forEach((path, entry) -> { - final String type = entry.getType(); - - if (type.equals("known-issue") == false && type.equals("security") == false) { - if (entry.getPr() == null) { - throw new GradleException( - "[" + path + "] must provide a [pr] number (only 'known-issue' and " + "'security' entries can omit this" - ); - } - - if (entry.getArea() == null) { - throw new GradleException( - "[" + path + "] must provide an [area] (only 'known-issue' and " + "'security' entries can omit this" - ); - } + final String type = entry.getType(); + + if (type.equals("known-issue") == false && type.equals("security") == false) { + if (entry.getPr() == null) { + throw new GradleException( + "[" + path + "] must provide a [pr] number (only 'known-issue' and 'security' entries can omit this" + ); } - if ((type.equals("breaking") || type.equals("breaking-java")) && entry.getBreaking() == null) { + if (entry.getArea() == null) { + throw new GradleException("[" + path + "] must provide an [area] (only 'known-issue' and 'security' entries can omit this"); + } + } + + if (type.equals("breaking") || type.equals("breaking-java")) { + if (entry.getBreaking() == null) { throw new GradleException( "[" + path + "] has type [" + type + "] and must supply a [breaking] section with further information" ); } - if (type.equals("deprecation") && entry.getDeprecation() == null) { + if (entry.getBreaking().getDetails().contains(TRIPLE_BACKTICK)) { + throw new GradleException(CODE_BLOCK_ERROR.formatted(path, "breaking.details")); + } + if (entry.getBreaking().getImpact().contains(TRIPLE_BACKTICK)) { + throw new GradleException(CODE_BLOCK_ERROR.formatted(path, "breaking.impact")); + } + } + + if (type.equals("deprecation")) { + if (entry.getDeprecation() == null) { throw new GradleException( "[" + path + "] has type [deprecation] and must supply a [deprecation] section with further information" ); } - }); + + if (entry.getDeprecation().getDetails().contains(TRIPLE_BACKTICK)) { + throw new GradleException(CODE_BLOCK_ERROR.formatted(path, "deprecation.details")); + } + if (entry.getDeprecation().getImpact().contains(TRIPLE_BACKTICK)) { + throw new GradleException(CODE_BLOCK_ERROR.formatted(path, "deprecation.impact")); + } + } + + if (entry.getHighlight() != null && entry.getHighlight().getBody().contains(TRIPLE_BACKTICK)) { + throw new GradleException(CODE_BLOCK_ERROR.formatted(path, "highlight.body")); + } } @InputFiles diff --git a/build-tools-internal/src/main/resources/templates/breaking-changes.asciidoc b/build-tools-internal/src/main/resources/templates/breaking-changes.asciidoc index cbbf19034aa12..75abce3b2b6ef 100644 --- a/build-tools-internal/src/main/resources/templates/breaking-changes.asciidoc +++ b/build-tools-internal/src/main/resources/templates/breaking-changes.asciidoc @@ -9,13 +9,17 @@ your application to {es} ${majorDotMinor}. See also <> and <>. <% if (isElasticsearchSnapshot) { %> -coming::[${version}] +coming::[${majorDotMinorDotRevision}] <% } %> -<% if (breakingByNotabilityByArea.isEmpty() == false) { %> + [discrete] [[breaking-changes-${majorDotMinor}]] === Breaking changes - +<% if (breakingByNotabilityByArea.isEmpty()) { %> +// tag::notable-breaking-changes[] +There are no breaking changes in {es} ${majorDotMinor}. +// end::notable-breaking-changes[] +<% } else { %> The following changes in {es} ${majorDotMinor} might affect your applications and prevent them from operating normally. Before upgrading to ${majorDotMinor}, review these changes and take the described steps @@ -66,13 +70,11 @@ if (deprecationsByNotabilityByArea.isEmpty() == false) { %> === Deprecations The following functionality has been deprecated in {es} ${majorDotMinor} -and will be removed in ${nextMajor}. +and will be removed in a future version. While this won't have an immediate impact on your applications, we strongly encourage you take the described steps to update your code after upgrading to ${majorDotMinor}. -NOTE: Significant changes in behavior are deprecated in a minor release and -the old behavior is supported until the next major release. To find out if you are using any deprecated functionality, enable <>. diff --git a/build-tools-internal/src/main/resources/templates/migration-index.asciidoc b/build-tools-internal/src/main/resources/templates/migration-index.asciidoc new file mode 100644 index 0000000000000..a83792900e527 --- /dev/null +++ b/build-tools-internal/src/main/resources/templates/migration-index.asciidoc @@ -0,0 +1,4 @@ +include::migration_intro.asciidoc[] + +<% versions.each { print "* <>\n" } %> +<% includeVersions.each { print "include::migrate_${ it }.asciidoc[]\n" } %> diff --git a/build-tools-internal/src/main/resources/templates/release-highlights.asciidoc b/build-tools-internal/src/main/resources/templates/release-highlights.asciidoc index 6641fcbc9abd7..bd8ef8602530b 100644 --- a/build-tools-internal/src/main/resources/templates/release-highlights.asciidoc +++ b/build-tools-internal/src/main/resources/templates/release-highlights.asciidoc @@ -1,14 +1,13 @@ [[release-highlights]] == What's new in {minor-version} -// tag::notable-highlights[] -{es} 7.17 is a compatibility release for the 7.17 Elastic Stack and contains -no major enhancements. -// end::notable-highlights[] +coming::[{minor-version}] +Here are the highlights of what's new and improved in {es} {minor-version}! +ifeval::[\\{release-state}\\"!=\\"unreleased\\"] For detailed information about this release, see the <> and <>. - +endif::[] <% if (priorVersions.size() > 0) { %> // Add previous release to the list Other versions: @@ -35,6 +34,8 @@ if (notableHighlights.isEmpty()) { %> [[${ highlight.anchor }]] === ${highlight.title} ${highlight.body.trim()} + +{es-pull}${highlight.pr}[#${highlight.pr}] <% } %> // end::notable-highlights[] <% } %> @@ -43,4 +44,6 @@ ${highlight.body.trim()} [[${ highlight.anchor }]] === ${highlight.title} ${highlight.body.trim()} + +{es-pull}${highlight.pr}[#${highlight.pr}] <% } %> diff --git a/build-tools-internal/src/main/resources/templates/release-notes.asciidoc b/build-tools-internal/src/main/resources/templates/release-notes.asciidoc index 88a4ab21d9fa3..096608435333a 100644 --- a/build-tools-internal/src/main/resources/templates/release-notes.asciidoc +++ b/build-tools-internal/src/main/resources/templates/release-notes.asciidoc @@ -32,10 +32,7 @@ for (changeType in changelogsByTypeByArea.keySet()) { %> print "\n${team}::\n"; for (change in changelogsByTypeByArea[changeType][team]) { - print "* ${change.summary}" - if (change.pr != -1) { - print " {es-pull}${change.pr}[#${change.pr}]" - } + print "* ${change.summary} {es-pull}${change.pr}[#${change.pr}]" if (change.issues != null && change.issues.empty == false) { print change.issues.size() == 1 ? " (issue: " : " (issues: " print change.issues.collect { "{es-issue}${it}[#${it}]" }.join(", ") diff --git a/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/GenerateReleaseNotesTaskTest.java b/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/GenerateReleaseNotesTaskTest.java index d1cab6c905829..f96ff6179101a 100644 --- a/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/GenerateReleaseNotesTaskTest.java +++ b/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/GenerateReleaseNotesTaskTest.java @@ -180,6 +180,29 @@ public void getVersions_includesCurrentVersion() { ); } + /** + * Check that when deriving a list of major.minor versions from git tags, the current unreleased version is included, + * but any higher version numbers are not. + */ + @Test + public void getMinorVersions_includesCurrentButNotFutureVersions() { + // given: + when(gitWrapper.listVersions(anyString())).thenReturn( + Stream.of("8.0.0-alpha1", "8.0.0-alpha2", "8.0.0", "8.0.1", "8.1.0", "8.2.0", "8.2.1", "8.3.0", "8.3.1", "8.4.0") + .map(QualifiedVersion::of) + ); + + // when: + Set versions = GenerateReleaseNotesTask.getVersions(gitWrapper, "8.3.0-SNAPSHOT"); + Set minorVersions = GenerateReleaseNotesTask.getMinorVersions(versions); + + // then: + assertThat( + minorVersions, + containsInAnyOrder(new MinorVersion(8, 0), new MinorVersion(8, 1), new MinorVersion(8, 2), new MinorVersion(8, 3)) + ); + } + /** * Check that the task partitions the list of files correctly by version for a prerelease. */ diff --git a/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGeneratorTest.java b/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGeneratorTest.java index a96e3f52223a9..db39c6eea7e86 100644 --- a/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGeneratorTest.java +++ b/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGeneratorTest.java @@ -16,8 +16,8 @@ import java.util.List; import java.util.Objects; -import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; public class ReleaseHighlightsGeneratorTest { @@ -33,7 +33,7 @@ public void generateFile_withNoHighlights_rendersCorrectMarkup() throws Exceptio ); // when: - final String actualOutput = ReleaseHighlightsGenerator.generateFile(QualifiedVersion.of("7.17.3-SNAPSHOT"), template, List.of()); + final String actualOutput = ReleaseHighlightsGenerator.generateFile(QualifiedVersion.of("8.4.0-SNAPSHOT"), template, List.of()); // then: assertThat(actualOutput, equalTo(expectedOutput)); @@ -53,38 +53,31 @@ public void generateFile_rendersCorrectMarkup() throws Exception { final List entries = getEntries(); // when: - final String actualOutput = ReleaseHighlightsGenerator.generateFile(QualifiedVersion.of("7.18.0-SNAPSHOT"), template, entries); + final String actualOutput = ReleaseHighlightsGenerator.generateFile(QualifiedVersion.of("8.4.0-SNAPSHOT"), template, entries); // then: assertThat(actualOutput, equalTo(expectedOutput)); } private List getEntries() { - ChangelogEntry entry1 = new ChangelogEntry(); - ChangelogEntry.Highlight highlight1 = new ChangelogEntry.Highlight(); - entry1.setHighlight(highlight1); - - highlight1.setNotable(true); - highlight1.setTitle("Notable release highlight number 1"); - highlight1.setBody("Notable release body number 1"); - - ChangelogEntry entry2 = new ChangelogEntry(); - ChangelogEntry.Highlight highlight2 = new ChangelogEntry.Highlight(); - entry2.setHighlight(highlight2); - - highlight2.setNotable(true); - highlight2.setTitle("Notable release highlight number 2"); - highlight2.setBody("Notable release body number 2"); + ChangelogEntry entry123 = makeChangelogEntry(123, true); + ChangelogEntry entry456 = makeChangelogEntry(456, true); + ChangelogEntry entry789 = makeChangelogEntry(789, false); + // Return unordered list, to test correct re-ordering + return List.of(entry456, entry123, entry789); + } - ChangelogEntry entry3 = new ChangelogEntry(); - ChangelogEntry.Highlight highlight3 = new ChangelogEntry.Highlight(); - entry3.setHighlight(highlight3); + private ChangelogEntry makeChangelogEntry(int pr, boolean notable) { + ChangelogEntry entry = new ChangelogEntry(); + entry.setPr(pr); + ChangelogEntry.Highlight highlight = new ChangelogEntry.Highlight(); + entry.setHighlight(highlight); - highlight3.setNotable(false); - highlight3.setTitle("Notable release highlight number 3"); - highlight3.setBody("Notable release body number 3"); + highlight.setNotable(notable); + highlight.setTitle("Notable release highlight number " + pr); + highlight.setBody("Notable release body number " + pr); - return List.of(entry1, entry2, entry3); + return entry; } private String getResource(String name) throws Exception { diff --git a/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ReleaseNotesGeneratorTest.java b/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ReleaseNotesGeneratorTest.java index d5107162816a9..f8a27c41770dc 100644 --- a/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ReleaseNotesGeneratorTest.java +++ b/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ReleaseNotesGeneratorTest.java @@ -19,8 +19,8 @@ import java.util.Objects; import java.util.Set; -import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; public class ReleaseNotesGeneratorTest { diff --git a/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ValidateChangelogEntryTaskTest.java b/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ValidateChangelogEntryTaskTest.java new file mode 100644 index 0000000000000..ec7b47b057a97 --- /dev/null +++ b/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ValidateChangelogEntryTaskTest.java @@ -0,0 +1,179 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.gradle.internal.release; + +import org.gradle.api.GradleException; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.endsWith; + +class ValidateChangelogEntryTaskTest { + + @Test + void test_prNumber_isRequired() { + ChangelogEntry changelog = new ChangelogEntry(); + changelog.setType("enhancement"); + + final String message = doValidate(changelog); + + assertThat(message, endsWith("must provide a [pr] number (only 'known-issue' and 'security' entries can omit this")); + } + + @Test + void test_prNumber_notRequired() { + Stream.of("known-issue", "security").forEach(type -> { + ChangelogEntry changelog = new ChangelogEntry(); + changelog.setType(type); + + // Should not throw an exception! + ValidateChangelogEntryTask.validate("", changelog); + }); + } + + @Test + void test_area_isRequired() { + final ChangelogEntry changelog = new ChangelogEntry(); + changelog.setType("enhancement"); + changelog.setPr(123); + + final String message = doValidate(changelog); + + assertThat(message, endsWith("must provide an [area] (only 'known-issue' and 'security' entries can omit this")); + } + + @Test + void test_breaking_requiresBreakingSection() { + Stream.of("breaking", "breaking-java").forEach(type -> { + final ChangelogEntry changelog = buildChangelog(type); + + final String message = doValidate(changelog); + + assertThat(message, endsWith("has type [" + type + "] and must supply a [breaking] section with further information")); + }); + } + + @Test + void test_breaking_rejectsTripleBackticksInDetails() { + Stream.of("breaking", "breaking-java").forEach(type -> { + final ChangelogEntry.Breaking breaking = new ChangelogEntry.Breaking(); + breaking.setDetails(""" + Some waffle. + ``` + I AM CODE! + ``` + """); + + final ChangelogEntry changelog = buildChangelog(type); + changelog.setBreaking(breaking); + + final String message = doValidate(changelog); + + assertThat(message, containsString("uses a triple-backtick in the [breaking.details] section")); + }); + } + + @Test + void test_breaking_rejectsTripleBackticksInImpact() { + Stream.of("breaking", "breaking-java").forEach(type -> { + final ChangelogEntry.Breaking breaking = new ChangelogEntry.Breaking(); + breaking.setDetails("Waffle waffle"); + breaking.setImpact(""" + More waffle. + ``` + THERE ARE WEASEL RAKING THROUGH MY GARBAGE! + ``` + """); + + final ChangelogEntry changelog = buildChangelog(type); + changelog.setBreaking(breaking); + + final String message = doValidate(changelog); + + assertThat(message, containsString("uses a triple-backtick in the [breaking.impact] section")); + }); + } + + @Test + void test_deprecation_rejectsTripleBackticksInImpact() { + final ChangelogEntry.Deprecation deprecation = new ChangelogEntry.Deprecation(); + deprecation.setDetails("Waffle waffle"); + deprecation.setImpact(""" + More waffle. + ``` + THERE ARE WEASEL RAKING THROUGH MY GARBAGE! + ``` + """); + + final ChangelogEntry changelog = buildChangelog("deprecation"); + changelog.setDeprecation(deprecation); + + final String message = doValidate(changelog); + + assertThat(message, containsString("uses a triple-backtick in the [deprecation.impact] section")); + } + + @Test + void test_deprecation_rejectsTripleBackticksInDetails() { + final ChangelogEntry.Deprecation deprecation = new ChangelogEntry.Deprecation(); + deprecation.setDetails(""" + Some waffle. + ``` + I AM CODE! + ``` + """); + + final ChangelogEntry changelog = buildChangelog("deprecation"); + changelog.setDeprecation(deprecation); + + final String message = doValidate(changelog); + + assertThat(message, containsString("uses a triple-backtick in the [deprecation.details] section")); + } + + @Test + void test_highlight_rejectsTripleBackticksInBody() { + final ChangelogEntry.Highlight highlight = new ChangelogEntry.Highlight(); + highlight.setBody(""" + Some waffle. + ``` + I AM CODE! + ``` + """); + + final ChangelogEntry changelog = buildChangelog("enhancement"); + changelog.setHighlight(highlight); + + final String message = doValidate(changelog); + + assertThat(message, containsString("uses a triple-backtick in the [highlight.body] section")); + } + + private static ChangelogEntry buildChangelog(String type) { + final ChangelogEntry changelog = new ChangelogEntry(); + changelog.setType(type); + changelog.setPr(123); + changelog.setArea("Infra/Core"); + return changelog; + } + + private String doValidate(ChangelogEntry entry) { + try { + ValidateChangelogEntryTask.validate("docs/123.yaml", entry); + throw new AssertionError("No exception thrown!"); + } catch (Exception e) { + assertThat(e, Matchers.instanceOf(GradleException.class)); + return e.getMessage(); + } + } +} diff --git a/build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/BreakingChangesGeneratorTest.generateMigrationFile.asciidoc b/build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/BreakingChangesGeneratorTest.generateMigrationFile.asciidoc index bfa24d5201375..0de9941327a66 100644 --- a/build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/BreakingChangesGeneratorTest.generateMigrationFile.asciidoc +++ b/build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/BreakingChangesGeneratorTest.generateMigrationFile.asciidoc @@ -9,7 +9,7 @@ your application to {es} 8.4. See also <> and <>. -coming::[8.4.0-SNAPSHOT] +coming::[8.4.0] [discrete] @@ -87,13 +87,11 @@ Breaking change impact description 3 === Deprecations The following functionality has been deprecated in {es} 8.4 -and will be removed in 9.0. +and will be removed in a future version. While this won't have an immediate impact on your applications, we strongly encourage you take the described steps to update your code after upgrading to 8.4. -NOTE: Significant changes in behavior are deprecated in a minor release and -the old behavior is supported until the next major release. To find out if you are using any deprecated functionality, enable <>. diff --git a/build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGeneratorTest.generateFile.asciidoc b/build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGeneratorTest.generateFile.asciidoc index 39d23dd3d339e..19c713042a42b 100644 --- a/build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGeneratorTest.generateFile.asciidoc +++ b/build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGeneratorTest.generateFile.asciidoc @@ -1,54 +1,45 @@ [[release-highlights]] == What's new in {minor-version} -// tag::notable-highlights[] -{es} 7.17 is a compatibility release for the 7.17 Elastic Stack and contains -no major enhancements. -// end::notable-highlights[] +coming::[{minor-version}] +Here are the highlights of what's new and improved in {es} {minor-version}! +ifeval::[\{release-state}\"!=\"unreleased\"] For detailed information about this release, see the <> and <>. - +endif::[] // Add previous release to the list Other versions: -{ref-bare}/7.17/release-highlights.html[7.17] -| {ref-bare}/7.16/release-highlights.html[7.16] -| {ref-bare}/7.15/release-highlights.html[7.15] -| {ref-bare}/7.14/release-highlights.html[7.14] -| {ref-bare}/7.13/release-highlights.html[7.13] -| {ref-bare}/7.12/release-highlights.html[7.12] -| {ref-bare}/7.11/release-highlights.html[7.11] -| {ref-bare}/7.10/release-highlights.html[7.10] -| {ref-bare}/7.9/release-highlights.html[7.9] -| {ref-bare}/7.8/release-highlights.html[7.8] -| {ref-bare}/7.7/release-highlights.html[7.7] -| {ref-bare}/7.6/release-highlights-7.6.0.html[7.6] -| {ref-bare}/7.5/release-highlights-7.5.0.html[7.5] -| {ref-bare}/7.4/release-highlights-7.4.0.html[7.4] -| {ref-bare}/7.3/release-highlights-7.3.0.html[7.3] -| {ref-bare}/7.2/release-highlights-7.2.0.html[7.2] -| {ref-bare}/7.1/release-highlights-7.1.0.html[7.1] -| {ref-bare}/7.0/release-highlights-7.0.0.html[7.0] +{ref-bare}/8.3/release-highlights.html[8.3] +| {ref-bare}/8.2/release-highlights.html[8.2] +| {ref-bare}/8.1/release-highlights.html[8.1] +| {ref-bare}/8.0/release-highlights.html[8.0] // tag::notable-highlights[] [discrete] -[[notable_release_highlight_number_1]] -=== Notable release highlight number 1 -Notable release body number 1 +[[notable_release_highlight_number_123]] +=== Notable release highlight number 123 +Notable release body number 123 + +{es-pull}123[#123] [discrete] -[[notable_release_highlight_number_2]] -=== Notable release highlight number 2 -Notable release body number 2 +[[notable_release_highlight_number_456]] +=== Notable release highlight number 456 +Notable release body number 456 + +{es-pull}456[#456] // end::notable-highlights[] [discrete] -[[notable_release_highlight_number_3]] -=== Notable release highlight number 3 -Notable release body number 3 +[[notable_release_highlight_number_789]] +=== Notable release highlight number 789 +Notable release body number 789 + +{es-pull}789[#789] diff --git a/build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGeneratorTest.noHighlights.generateFile.asciidoc b/build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGeneratorTest.noHighlights.generateFile.asciidoc index 43a706735295e..87a5f6420252e 100644 --- a/build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGeneratorTest.noHighlights.generateFile.asciidoc +++ b/build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/ReleaseHighlightsGeneratorTest.noHighlights.generateFile.asciidoc @@ -1,35 +1,21 @@ [[release-highlights]] == What's new in {minor-version} -// tag::notable-highlights[] -{es} 7.17 is a compatibility release for the 7.17 Elastic Stack and contains -no major enhancements. -// end::notable-highlights[] +coming::[{minor-version}] +Here are the highlights of what's new and improved in {es} {minor-version}! +ifeval::[\{release-state}\"!=\"unreleased\"] For detailed information about this release, see the <> and <>. - +endif::[] // Add previous release to the list Other versions: -{ref-bare}/7.16/release-highlights.html[7.16] -| {ref-bare}/7.15/release-highlights.html[7.15] -| {ref-bare}/7.14/release-highlights.html[7.14] -| {ref-bare}/7.13/release-highlights.html[7.13] -| {ref-bare}/7.12/release-highlights.html[7.12] -| {ref-bare}/7.11/release-highlights.html[7.11] -| {ref-bare}/7.10/release-highlights.html[7.10] -| {ref-bare}/7.9/release-highlights.html[7.9] -| {ref-bare}/7.8/release-highlights.html[7.8] -| {ref-bare}/7.7/release-highlights.html[7.7] -| {ref-bare}/7.6/release-highlights-7.6.0.html[7.6] -| {ref-bare}/7.5/release-highlights-7.5.0.html[7.5] -| {ref-bare}/7.4/release-highlights-7.4.0.html[7.4] -| {ref-bare}/7.3/release-highlights-7.3.0.html[7.3] -| {ref-bare}/7.2/release-highlights-7.2.0.html[7.2] -| {ref-bare}/7.1/release-highlights-7.1.0.html[7.1] -| {ref-bare}/7.0/release-highlights-7.0.0.html[7.0] +{ref-bare}/8.3/release-highlights.html[8.3] +| {ref-bare}/8.2/release-highlights.html[8.2] +| {ref-bare}/8.1/release-highlights.html[8.1] +| {ref-bare}/8.0/release-highlights.html[8.0] // The notable-highlights tag marks entries that // should be featured in the Stack Installation and Upgrade Guide: