Skip to content

Commit 43005fb

Browse files
authored
fix(release): handle missing release tags gracefully in release-note-generation (#13795)
This PR updates the release note generation tool to: 1. Handle missing release tags gracefully by dynamically appending library suffixes (e.g. `v1.88.1-dataproc`) for partial releases to avoid broken links. 2. Fix process stdout buffer blocking and deadlock/timeout hang risks during command execution. Tested manually with `-Dlibraries-bom.version=26.85.1 -Dgoogle-cloud-java.version=1.88.1` (verified correct tag fallback output in release_note.md). Fixes b/535634855
1 parent 5e54547 commit 43005fb

1 file changed

Lines changed: 75 additions & 20 deletions

File tree

java-cloud-bom/release-note-generation/src/main/java/com/google/cloud/ReleaseNoteGeneration.java

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,8 @@
3434
import com.google.common.collect.Sets;
3535
import com.google.common.collect.Sets.SetView;
3636
import com.google.common.collect.Streams;
37-
import com.google.common.io.CharStreams;
38-
import com.google.common.io.Files;
3937
import java.io.File;
4038
import java.io.IOException;
41-
import java.io.InputStreamReader;
4239
import java.nio.charset.StandardCharsets;
4340
import java.util.ArrayList;
4441
import java.util.HashMap;
@@ -49,7 +46,6 @@
4946
import java.util.concurrent.TimeUnit;
5047
import java.util.regex.Matcher;
5148
import java.util.regex.Pattern;
52-
import org.apache.commons.codec.Charsets;
5349
import org.eclipse.aether.RepositorySystem;
5450
import org.eclipse.aether.artifact.Artifact;
5551
import org.eclipse.aether.artifact.DefaultArtifact;
@@ -111,21 +107,50 @@ public static void main(String[] arguments)
111107
ReleaseNoteGeneration generation = new ReleaseNoteGeneration();
112108
String report = generation.generateReport(bom, googleCloudJavaVersion);
113109

114-
Files.asCharSink(new File(RELEASE_NOTE_FILE_NAME), Charsets.UTF_8).write(report);
110+
com.google.common.io.Files.asCharSink(new File(RELEASE_NOTE_FILE_NAME), StandardCharsets.UTF_8)
111+
.write(report);
115112
System.out.println("Wrote " + RELEASE_NOTE_FILE_NAME);
116113
}
117114

118115
@VisibleForTesting final StringBuilder report = new StringBuilder();
116+
private boolean monorepoReleaseExists = true;
119117

120118
@VisibleForTesting
121119
ReleaseNoteGeneration() {}
122120

121+
private static boolean releaseExists(String repository, String tag) {
122+
Process process = null;
123+
try {
124+
ProcessBuilder builder =
125+
new ProcessBuilder(
126+
"gh", "release", "--repo", GOOGLEAPIS_ORG + "/" + repository, "view", tag);
127+
builder.redirectOutput(ProcessBuilder.Redirect.DISCARD);
128+
builder.redirectError(ProcessBuilder.Redirect.DISCARD);
129+
process = builder.start();
130+
boolean finished = process.waitFor(1, TimeUnit.MINUTES);
131+
if (!finished) {
132+
return false;
133+
}
134+
return process.exitValue() == 0;
135+
} catch (IOException e) {
136+
return false;
137+
} catch (InterruptedException e) {
138+
Thread.currentThread().interrupt();
139+
return false;
140+
} finally {
141+
if (process != null && process.isAlive()) {
142+
process.destroyForcibly();
143+
}
144+
}
145+
}
146+
123147
@VisibleForTesting
124148
String generateReport(Bom bom, String googleCloudJavaVersion)
125149
throws MavenRepositoryException,
126150
ArtifactDescriptorException,
127151
IOException,
128152
InterruptedException {
153+
monorepoReleaseExists = releaseExists("google-cloud-java", "v" + googleCloudJavaVersion);
129154
Bom previousBom = previousBom(bom);
130155

131156
DefaultArtifact bomArtifact = new DefaultArtifact(bom.getCoordinates());
@@ -425,10 +450,15 @@ private static String releaseUrlForSplitRepo(String libraryName, String version)
425450
"https://github.com/googleapis/java-%s/releases/tag/v%s", libraryName, version);
426451
}
427452

428-
private static String releaseUrlForMonorepo(String libraryName, String version) {
429-
// libraryName is unused for the monorepo release note as of Dec 2022
430-
return String.format(
431-
"https://github.com/googleapis/google-cloud-java/releases/tag/v%s", version);
453+
private String releaseUrlForMonorepo(String libraryName, String version) {
454+
if (monorepoReleaseExists) {
455+
return String.format(
456+
"https://github.com/googleapis/google-cloud-java/releases/tag/v%s", version);
457+
} else {
458+
return String.format(
459+
"https://github.com/googleapis/google-cloud-java/releases/tag/v%s-%s",
460+
version, libraryName);
461+
}
432462
}
433463

434464
/**
@@ -615,16 +645,41 @@ static String fetchReleaseNote(String owner, String repository, String tag)
615645
throws IOException, InterruptedException {
616646
// gh release --repo googleapis/java-storage view v2.16.0
617647

618-
ProcessBuilder builder =
619-
new ProcessBuilder("gh", "release", "--repo", owner + "/" + repository, "view", tag);
620-
builder.redirectErrorStream(true);
621-
Process process = builder.start();
622-
String output =
623-
CharStreams.toString(
624-
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));
625-
boolean finished = process.waitFor(1, TimeUnit.MINUTES);
626-
Verify.verify(finished, "The process timed out");
627-
Verify.verify(0 == process.exitValue(), "The command failed: %s", output);
628-
return output;
648+
File tempFile = java.nio.file.Files.createTempFile("gh-release-notes", ".txt").toFile();
649+
tempFile.deleteOnExit();
650+
651+
Process process = null;
652+
try {
653+
ProcessBuilder builder =
654+
new ProcessBuilder("gh", "release", "--repo", owner + "/" + repository, "view", tag);
655+
builder.redirectErrorStream(true);
656+
builder.redirectOutput(ProcessBuilder.Redirect.to(tempFile));
657+
658+
process = builder.start();
659+
boolean finished = process.waitFor(1, TimeUnit.MINUTES);
660+
if (!finished) {
661+
throw new IOException("The process timed out");
662+
}
663+
664+
String output =
665+
new String(java.nio.file.Files.readAllBytes(tempFile.toPath()), StandardCharsets.UTF_8);
666+
667+
if (process.exitValue() != 0) {
668+
String trimmedOutput = output.trim();
669+
String lowerOutput = trimmedOutput.toLowerCase();
670+
if (lowerOutput.contains("not found") || lowerOutput.contains("404")) {
671+
System.err.println(
672+
"Warning: The command failed (release likely not found): " + trimmedOutput);
673+
return "";
674+
}
675+
throw new IOException("The command failed: " + trimmedOutput);
676+
}
677+
return output;
678+
} finally {
679+
if (process != null && process.isAlive()) {
680+
process.destroyForcibly();
681+
}
682+
tempFile.delete();
683+
}
629684
}
630685
}

0 commit comments

Comments
 (0)