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/2] 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/2] 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; } }