diff --git a/.ci/ReleaseChangelog.java b/.ci/ReleaseChangelog.java new file mode 100644 index 00000000..bf3826a4 --- /dev/null +++ b/.ci/ReleaseChangelog.java @@ -0,0 +1,295 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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 + * + * http://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. + */ + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.OptionalInt; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ReleaseChangelog { + + public static void main(String[] args) throws IOException { + if (args.length != 3) { + System.out.println( + "Expected exactly three arguments: "); + System.exit(-1); + } + Path nextChangelogFile = Paths.get(args[0]); + Path releaseNotesDir = Paths.get(args[1]); + Path releaseNotesFile = releaseNotesDir.resolve("index.md"); + Path deprecationsFile = releaseNotesDir.resolve("deprecations.md"); + Path breakingChangesFile = releaseNotesDir.resolve("breaking-changes.md"); + VersionNumber version = VersionNumber.parse(args[2].trim()); + + Lines nextChangelogLines = new Lines( + Files.readAllLines(nextChangelogFile, StandardCharsets.UTF_8)); + Lines fixes = nextChangelogLines.cutLinesBetween("", ""); + Lines enhancements = nextChangelogLines.cutLinesBetween("", + ""); + Lines deprecations = nextChangelogLines.cutLinesBetween("", + ""); + Lines breakingChanges = nextChangelogLines.cutLinesBetween("", + ""); + + Lines dependenciesNotes = nextChangelogLines.cutLinesBetween("", + ""); + + var formatter = DateTimeFormatter.ofPattern("LLLL d, yyyy", Locale.ENGLISH); + String releaseDateLine = "**Release date:** " + formatter.format(LocalDate.now()); + + Lines allReleaseNotes = new Lines(Files.readAllLines(releaseNotesFile, StandardCharsets.UTF_8)); + int insertBeforeLine = findHeadingOfPreviousVersion(allReleaseNotes, version); + if (insertBeforeLine < 0) { + insertBeforeLine = allReleaseNotes.lineCount(); + } + allReleaseNotes.insert( + generateReleaseNotes(version, releaseDateLine, enhancements, fixes, breakingChanges, + dependenciesNotes), + insertBeforeLine); + + if (!deprecations.isEmpty()) { + Lines allDeprecations = new Lines( + Files.readAllLines(deprecationsFile, StandardCharsets.UTF_8)); + int insertDepsBeforeLine = findHeadingOfPreviousVersion(allDeprecations, version); + if (insertDepsBeforeLine < 0) { + // in case no previous version was listed + insertDepsBeforeLine = allDeprecations.lineCount(); + } + allDeprecations.insert(generateDeprecations(version, releaseDateLine, deprecations), + insertDepsBeforeLine); + Files.writeString(deprecationsFile, allDeprecations + "\n", StandardCharsets.UTF_8); + } + if (!breakingChanges.isEmpty()) { + Lines allBreakingChanges = new Lines( + Files.readAllLines(breakingChangesFile, StandardCharsets.UTF_8)); + int insertBcBeforeLine = findHeadingOfPreviousVersion(allBreakingChanges, version); + if (insertBcBeforeLine < 0) { + // in case no previous version was listed + insertBcBeforeLine = allBreakingChanges.lineCount(); + } + allBreakingChanges.insert(generateBreakingChanges(version, releaseDateLine, breakingChanges), + insertBcBeforeLine); + Files.writeString(breakingChangesFile, allBreakingChanges + "\n", StandardCharsets.UTF_8); + } + Files.writeString(releaseNotesFile, allReleaseNotes + "\n", StandardCharsets.UTF_8); + Files.writeString(nextChangelogFile, nextChangelogLines + "\n", StandardCharsets.UTF_8); + } + + private static Lines generateReleaseNotes(VersionNumber version, String releaseDateLine, + Lines enhancements, Lines fixes, Lines breaking, Lines dependenciesNotes) { + Lines result = new Lines() + .append("## " + version.dotStr() + " [edot-java-" + version.dashStr() + "-release-notes]") + .append(releaseDateLine); + if (!enhancements.isEmpty()) { + result + .append("") + .append("### Features and enhancements [edot-java-" + version.dashStr() + + "-features-enhancements]") + .append(enhancements); + } + if (!fixes.isEmpty()) { + result + .append("") + .append("### Fixes [edot-java-" + version.dashStr() + "-fixes]") + .append(fixes); + } + if (!breaking.isEmpty()) { + result + .append("") + .append("### Breaking changes [edot-java-" + version.dashStr() + "-fixes]") + .append(breaking); + } + if (!dependenciesNotes.isEmpty()) { + result + .append("") + .append(dependenciesNotes); + } + result.append(""); + return result; + } + + private static Lines generateDeprecations(VersionNumber version, String releaseDateLine, + Lines deprecations) { + return new Lines() + .append("## " + version.dotStr() + " [edot-java-" + version.dashStr() + "-deprecations]") + .append(releaseDateLine) + .append("") + .append(deprecations) + .append(""); + } + + private static Lines generateBreakingChanges(VersionNumber version, String releaseDateLine, + Lines breakingChanges) { + return new Lines() + .append("## " + version.dotStr() + " [" + version.dotStr() + "]") + .append("") + .append(releaseDateLine) + .append("") + .append(breakingChanges) + .append(""); + } + + static int findHeadingOfPreviousVersion(Lines lines, VersionNumber version) { + Pattern headingPattern = Pattern.compile("## (\\d+\\.\\d+\\.\\d+) .*"); + Comparator comp = VersionNumber.comparator(); + int currentBestLineNo = -1; + VersionNumber currentBestVersion = null; + for (int i = 0; i < lines.lineCount(); i++) { + Matcher matcher = headingPattern.matcher(lines.getLine(i)); + if (matcher.matches()) { + VersionNumber headingForVersion = VersionNumber.parse(matcher.group(1)); + if (comp.compare(headingForVersion, version) < 0 + && (currentBestVersion == null + || comp.compare(headingForVersion, currentBestVersion) > 0)) { + currentBestLineNo = i; + currentBestVersion = headingForVersion; + } + } + } + return currentBestLineNo; + } + + record VersionNumber(int major, int minor, int patch) { + public static VersionNumber parse (String versionString){ + if (!versionString.matches("\\d+\\.\\d+\\.\\d+")) { + throw new IllegalArgumentException( + "Version must be in the format x.x.x but was not: " + versionString); + } + String[] parts = versionString.split("\\."); + return new VersionNumber(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), + Integer.parseInt(parts[2])); + } + + static Comparator comparator () { + return Comparator + .comparing(VersionNumber::major) + .thenComparing(VersionNumber::minor) + .thenComparing(VersionNumber::patch); + } + + String dashStr () { + return major + "-" + minor + "-" + patch; + } + + String dotStr () { + return major + "." + minor + "." + patch; + } + + } + + static class Lines { + + private final List lines; + + public Lines() { + this.lines = new ArrayList<>(); + } + + public Lines(List lines) { + this.lines = new ArrayList<>(lines); + } + + int lineCount() { + return lines.size(); + } + + boolean isEmpty() { + return lines.isEmpty(); + } + + Lines cutLinesBetween(String startLine, String endLine) { + int start = findLine(l -> l.trim().equals(startLine), 0) + .orElseThrow( + () -> new IllegalStateException("Expected line '" + startLine + "' to exist")); + int end = findLine(l -> l.trim().equals(endLine), start + 1) + .orElseThrow(() -> new IllegalStateException( + "Expected line '" + endLine + "' to exist after '" + startLine + "'")); + Lines result = cut(start + 1, end).trim(); + + lines.add(start + 1, ""); + + return result; + } + + OptionalInt findLine(Predicate condition, int startAt) { + for (int i = startAt; i < lines.size(); i++) { + if (condition.test(lines.get(i))) { + return OptionalInt.of(i); + } + } + return OptionalInt.empty(); + } + + Lines cut(int startInclusive, int endExclusive) { + List cutLines = new ArrayList<>(); + for (int i = startInclusive; i < endExclusive; i++) { + cutLines.add(lines.remove(startInclusive)); + } + return new Lines(cutLines); + } + + void insert(Lines other, int insertAt) { + this.lines.addAll(insertAt, other.lines); + } + + Lines append(String line) { + lines.add(line); + return this; + } + + Lines append(Lines toAppend) { + lines.addAll(toAppend.lines); + return this; + } + + /** + * Trims lines consisting of only blanks at the top and bottom + */ + Lines trim() { + while (!lines.isEmpty() && lines.get(0).matches("\\s*")) { + lines.remove(0); + } + while (!lines.isEmpty() && lines.get(lines.size() - 1).matches("\\s*")) { + lines.remove(lines.size() - 1); + } + return this; + } + + @Override + public String toString() { + return String.join("\n", lines); + } + + public String getLine(int number) { + return lines.get(number); + } + } + +} diff --git a/.github/workflows/pre-post-release.yml b/.github/workflows/pre-post-release.yml index 88f87ac2..f9044a1a 100644 --- a/.github/workflows/pre-post-release.yml +++ b/.github/workflows/pre-post-release.yml @@ -26,11 +26,6 @@ on: description: 'pull-request body' type: string required: true - changelog: - description: 'The changelog to prepend to CHANGELOG.md without heading' - type: string - required: false - default: '' env: RELEASE_VERSION: ${{ inputs.version }} @@ -100,21 +95,14 @@ jobs: with: command: ./gradlew -q setNextVersion - - name: Insert notes into cumulative changelog (post release) - if: inputs.phase == 'post' - run: | - echo "# ${VERSION} - $(date +'%d/%m/%Y')" > tmpchangelog - echo "${CHANGELOG}" >> tmpchangelog - cat CHANGELOG.md >> tmpchangelog - mv tmpchangelog CHANGELOG.md - env: - VERSION: ${{ inputs.version }} - CHANGELOG: ${{ inputs.changelog }} - - - name: Clear next release changelog (post release) + - name: Generate documentation changelog (post release) if: inputs.phase == 'post' run: | - echo '' > CHANGELOG.next-release.md + echo '' >> CHANGELOG.next-release.md + echo -e "This release is based on the following upstream versions:\n\n" >> CHANGELOG.next-release.md + ./gradlew -q printUpstreamDependenciesMarkdown >> CHANGELOG.next-release.md + echo '' >> CHANGELOG.next-release.md + java .ci/ReleaseChangelog.java CHANGELOG.next-release.md docs/release-notes ${{ env.RELEASE_VERSION }} - name: Push the ${{ inputs.phase }} release branch run: | diff --git a/.github/workflows/release-step-3.yml b/.github/workflows/release-step-3.yml index 5075a9c3..a6a7414b 100644 --- a/.github/workflows/release-step-3.yml +++ b/.github/workflows/release-step-3.yml @@ -218,6 +218,8 @@ jobs: command: "" - name: Print Release Notes id: print_release_notes + # note: release notes here will be copied as-is from 'CHANGELOG.next-release.md' + # the 'pre-post-release' workflow executed after this will reset contents of 'CHANGELOG.next-release.md' run: | echo 'notes<> $GITHUB_OUTPUT cat CHANGELOG.next-release.md >> $GITHUB_OUTPUT diff --git a/CHANGELOG.next-release.md b/CHANGELOG.next-release.md index 434b3325..890f91f5 100644 --- a/CHANGELOG.next-release.md +++ b/CHANGELOG.next-release.md @@ -1,3 +1,29 @@ +This file contains all changes which are not released yet. + + +# Fixes + + + +# Features and enhancements + * Add support for dynamic configuration options for 9.2 #818 * Switch upstream Opamp client #789 + + +# Deprecations + + + + +# Breaking Changes + * Switch to upstream instrumentation of openai by default #763 + + diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md index 4d70e0ad..983bdb06 100644 --- a/docs/release-notes/index.md +++ b/docs/release-notes/index.md @@ -27,7 +27,7 @@ To check for security updates, go to [Security announcements for the Elastic sta % ### Fixes [edot-java-X.X.X-fixes] % * -# 1.5.0 [edot-java-1.5.0-release-notes] +## 1.5.0 [edot-java-1.5.0-release-notes] ### Features and enhancements [edot-java-1.5.0-features-enhancements]