Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
295 changes: 295 additions & 0 deletions .ci/ReleaseChangelog.java
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] almost copied as-is from apm-agent-java with minor tweaks to handle the lack of previous version.


public static void main(String[] args) throws IOException {
if (args.length != 3) {
System.out.println(
"Expected exactly three arguments: <ChangelogFile> <ReleaseNotesPath> <VersionToRelease>");
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("<!--FIXES-START-->", "<!--FIXES-END-->");
Lines enhancements = nextChangelogLines.cutLinesBetween("<!--ENHANCEMENTS-START-->",
"<!--ENHANCEMENTS-END-->");
Lines deprecations = nextChangelogLines.cutLinesBetween("<!--DEPRECATIONS-START-->",
"<!--DEPRECATIONS-END-->");
Lines breakingChanges = nextChangelogLines.cutLinesBetween("<!--BREAKING-CHANGES-START-->",
"<!--BREAKING-CHANGES-END-->");

Lines dependenciesNotes = nextChangelogLines.cutLinesBetween("<!--DEPENDENCIES-NOTES-START-->",
"<!--DEPENDENCIES-NOTES-END-->");
Comment on lines +61 to +62
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] this section does not exists in the next release changelog template, we have to append it manually at runtime. This is due to the fact that we can't easily invoke the gradle task from here, doing everything from gradle in the future would simplify this.


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<VersionNumber> 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<VersionNumber> 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<String> lines;

public Lines() {
this.lines = new ArrayList<>();
}

public Lines(List<String> 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<String> 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<String> 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);
}
}

}
24 changes: 6 additions & 18 deletions .github/workflows/pre-post-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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 '<!--DEPENDENCIES-NOTES-START-->' >> 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 '<!--DEPENDENCIES-NOTES-END-->' >> CHANGELOG.next-release.md
Comment on lines +101 to +104
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the reason those headers/bottom are not also generated by printUpstreamDependenciesMarkdown?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

printUpstreamDependenciesMarkdown is also used to generate the github release markdown, which should not include those, again this is a shortcut and I'll create an issue with all the things we could improve here.

java .ci/ReleaseChangelog.java CHANGELOG.next-release.md docs/release-notes ${{ env.RELEASE_VERSION }}

- name: Push the ${{ inputs.phase }} release branch
run: |
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release-step-3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Comment on lines +221 to +222
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] here the github release changelog needs to be in pure markdown, so we can't append our custom markdown from breaking changes.

run: |
echo 'notes<<RELNOTESEOF' >> $GITHUB_OUTPUT
cat CHANGELOG.next-release.md >> $GITHUB_OUTPUT
Expand Down
26 changes: 26 additions & 0 deletions CHANGELOG.next-release.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
This file contains all changes which are not released yet.
<!--
Note that the content between the marker comment lines (e.g. FIXES-START/END) will be automatically
moved into the docs/release-notes markdown files on release (via the .ci/ReleaseChangelog.java script).
Simply add the changes as bullet points into those sections, empty lines will be ignored. Example:

* Description of the change - [#1234](https://github.com/elastic/apm-agent-java/pull/1234)
-->

# Fixes
<!--FIXES-START-->

<!--FIXES-END-->
# Features and enhancements
<!--ENHANCEMENTS-START-->
* Add support for dynamic configuration options for 9.2 #818
* Switch upstream Opamp client #789

<!--ENHANCEMENTS-END-->
# Deprecations
<!--DEPRECATIONS-START-->

<!--DEPRECATIONS-END-->

# Breaking Changes
<!--BREAKING-CHANGES-START-->
* Switch to upstream instrumentation of openai by default #763

<!--BREAKING-CHANGES-END-->
2 changes: 1 addition & 1 deletion docs/release-notes/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] this made the changelog generation not properly detect the previous version.


### Features and enhancements [edot-java-1.5.0-features-enhancements]

Expand Down
Loading