From 3fd80a7f1e2d7fb1143846570a008ad0e9c07be4 Mon Sep 17 00:00:00 2001 From: Jin Seop Kim Date: Thu, 16 Jul 2026 13:58:25 -0400 Subject: [PATCH 1/3] fix(release): handle missing release tags gracefully in release-note-generation --- .../google/cloud/ReleaseNoteGeneration.java | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/java-cloud-bom/release-note-generation/src/main/java/com/google/cloud/ReleaseNoteGeneration.java b/java-cloud-bom/release-note-generation/src/main/java/com/google/cloud/ReleaseNoteGeneration.java index 483df61ba1ed..43ac7f044ee0 100644 --- a/java-cloud-bom/release-note-generation/src/main/java/com/google/cloud/ReleaseNoteGeneration.java +++ b/java-cloud-bom/release-note-generation/src/main/java/com/google/cloud/ReleaseNoteGeneration.java @@ -113,16 +113,30 @@ public static void main(String[] arguments) } @VisibleForTesting final StringBuilder report = new StringBuilder(); + private boolean monorepoReleaseExists = true; @VisibleForTesting ReleaseNoteGeneration() {} + private static boolean releaseExists(String repository, String tag) { + try { + ProcessBuilder builder = + new ProcessBuilder( + "gh", "release", "--repo", GOOGLEAPIS_ORG + "/" + repository, "view", tag); + Process process = builder.start(); + return process.waitFor() == 0; + } catch (IOException | InterruptedException e) { + return false; + } + } + @VisibleForTesting String generateReport(Bom bom, String googleCloudJavaVersion) throws MavenRepositoryException, ArtifactDescriptorException, IOException, InterruptedException { + monorepoReleaseExists = releaseExists("google-cloud-java", "v" + googleCloudJavaVersion); Bom previousBom = previousBom(bom); DefaultArtifact bomArtifact = new DefaultArtifact(bom.getCoordinates()); @@ -422,10 +436,15 @@ private static String releaseUrlForSplitRepo(String libraryName, String version) "https://github.com/googleapis/java-%s/releases/tag/v%s", libraryName, version); } - private static String releaseUrlForMonorepo(String libraryName, String version) { - // libraryName is unused for the monorepo release note as of Dec 2022 - return String.format( - "https://github.com/googleapis/google-cloud-java/releases/tag/v%s", version); + private String releaseUrlForMonorepo(String libraryName, String version) { + if (monorepoReleaseExists) { + return String.format( + "https://github.com/googleapis/google-cloud-java/releases/tag/v%s", version); + } else { + return String.format( + "https://github.com/googleapis/google-cloud-java/releases/tag/v%s-%s", + version, libraryName); + } } /** @@ -621,7 +640,11 @@ static String fetchReleaseNote(String owner, String repository, String tag) process.getInputStream().readAllBytes(), java.nio.charset.StandardCharsets.UTF_8); boolean finished = process.waitFor(1, TimeUnit.MINUTES); Verify.verify(finished, "The process timed out"); - Verify.verify(0 == process.exitValue(), "The command failed: %s", output); + if (process.exitValue() != 0) { + System.err.println( + "Warning: The command failed (release likely not found): " + output.trim()); + return ""; + } return output; } } From 0bae71caefdcffa1e740a467720684c97af4e666 Mon Sep 17 00:00:00 2001 From: Jin Seop Kim Date: Thu, 16 Jul 2026 21:19:25 -0400 Subject: [PATCH 2/3] fix(release): consume process streams safely and handle interrupts in ReleaseNoteGeneration --- .../google/cloud/ReleaseNoteGeneration.java | 71 ++++++++++++++----- 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/java-cloud-bom/release-note-generation/src/main/java/com/google/cloud/ReleaseNoteGeneration.java b/java-cloud-bom/release-note-generation/src/main/java/com/google/cloud/ReleaseNoteGeneration.java index 43ac7f044ee0..773501f0ce8f 100644 --- a/java-cloud-bom/release-note-generation/src/main/java/com/google/cloud/ReleaseNoteGeneration.java +++ b/java-cloud-bom/release-note-generation/src/main/java/com/google/cloud/ReleaseNoteGeneration.java @@ -119,14 +119,28 @@ public static void main(String[] arguments) ReleaseNoteGeneration() {} private static boolean releaseExists(String repository, String tag) { + Process process = null; try { ProcessBuilder builder = new ProcessBuilder( "gh", "release", "--repo", GOOGLEAPIS_ORG + "/" + repository, "view", tag); - Process process = builder.start(); - return process.waitFor() == 0; - } catch (IOException | InterruptedException e) { + builder.redirectOutput(ProcessBuilder.Redirect.DISCARD); + builder.redirectError(ProcessBuilder.Redirect.DISCARD); + process = builder.start(); + boolean finished = process.waitFor(1, TimeUnit.MINUTES); + if (!finished) { + return false; + } + return process.exitValue() == 0; + } catch (IOException e) { + return false; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); return false; + } finally { + if (process != null && process.isAlive()) { + process.destroyForcibly(); + } } } @@ -631,20 +645,43 @@ static String fetchReleaseNote(String owner, String repository, String tag) throws IOException, InterruptedException { // gh release --repo googleapis/java-storage view v2.16.0 - ProcessBuilder builder = - new ProcessBuilder("gh", "release", "--repo", owner + "/" + repository, "view", tag); - builder.redirectErrorStream(true); - Process process = builder.start(); - String output = - new String( - process.getInputStream().readAllBytes(), java.nio.charset.StandardCharsets.UTF_8); - boolean finished = process.waitFor(1, TimeUnit.MINUTES); - Verify.verify(finished, "The process timed out"); - if (process.exitValue() != 0) { - System.err.println( - "Warning: The command failed (release likely not found): " + output.trim()); - return ""; + File tempFile = java.nio.file.Files.createTempFile("gh-release-notes", ".txt").toFile(); + tempFile.deleteOnExit(); + + Process process = null; + try { + ProcessBuilder builder = + new ProcessBuilder("gh", "release", "--repo", owner + "/" + repository, "view", tag); + builder.redirectErrorStream(true); + builder.redirectOutput(ProcessBuilder.Redirect.to(tempFile)); + + process = builder.start(); + boolean finished = process.waitFor(1, TimeUnit.MINUTES); + if (!finished) { + throw new IOException("The process timed out"); + } + + String output = + new String( + java.nio.file.Files.readAllBytes(tempFile.toPath()), + java.nio.charset.StandardCharsets.UTF_8); + + if (process.exitValue() != 0) { + String trimmedOutput = output.trim(); + String lowerOutput = trimmedOutput.toLowerCase(); + if (lowerOutput.contains("not found") || lowerOutput.contains("404")) { + System.err.println( + "Warning: The command failed (release likely not found): " + trimmedOutput); + return ""; + } + throw new IOException("The command failed: " + trimmedOutput); + } + return output; + } finally { + if (process != null && process.isAlive()) { + process.destroyForcibly(); + } + tempFile.delete(); } - return output; } } From 7f2cfc020dc920644e2ff7af9fc08f1008ee8805 Mon Sep 17 00:00:00 2001 From: Jin Seop Kim Date: Mon, 20 Jul 2026 16:09:20 -0400 Subject: [PATCH 3/3] chore(release): address PR review comments on ReleaseNoteGeneration --- .../com/google/cloud/ReleaseNoteGeneration.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/java-cloud-bom/release-note-generation/src/main/java/com/google/cloud/ReleaseNoteGeneration.java b/java-cloud-bom/release-note-generation/src/main/java/com/google/cloud/ReleaseNoteGeneration.java index 773501f0ce8f..4420e6dc33b4 100644 --- a/java-cloud-bom/release-note-generation/src/main/java/com/google/cloud/ReleaseNoteGeneration.java +++ b/java-cloud-bom/release-note-generation/src/main/java/com/google/cloud/ReleaseNoteGeneration.java @@ -34,9 +34,11 @@ import com.google.common.collect.Sets; import com.google.common.collect.Sets.SetView; import com.google.common.collect.Streams; -import com.google.common.io.Files; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -46,7 +48,6 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.commons.codec.Charsets; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; @@ -108,7 +109,7 @@ public static void main(String[] arguments) ReleaseNoteGeneration generation = new ReleaseNoteGeneration(); String report = generation.generateReport(bom, googleCloudJavaVersion); - Files.asCharSink(new File(RELEASE_NOTE_FILE_NAME), Charsets.UTF_8).write(report); + Files.writeString(Path.of(RELEASE_NOTE_FILE_NAME), report); System.out.println("Wrote " + RELEASE_NOTE_FILE_NAME); } @@ -645,7 +646,7 @@ static String fetchReleaseNote(String owner, String repository, String tag) throws IOException, InterruptedException { // gh release --repo googleapis/java-storage view v2.16.0 - File tempFile = java.nio.file.Files.createTempFile("gh-release-notes", ".txt").toFile(); + File tempFile = Files.createTempFile("gh-release-notes", ".txt").toFile(); tempFile.deleteOnExit(); Process process = null; @@ -661,10 +662,7 @@ static String fetchReleaseNote(String owner, String repository, String tag) throw new IOException("The process timed out"); } - String output = - new String( - java.nio.file.Files.readAllBytes(tempFile.toPath()), - java.nio.charset.StandardCharsets.UTF_8); + String output = new String(Files.readAllBytes(tempFile.toPath()), StandardCharsets.UTF_8); if (process.exitValue() != 0) { String trimmedOutput = output.trim();