-
Notifications
You must be signed in to change notification settings - Fork 128
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #405 from smallrye/new-release-script
New Release Script
- Loading branch information
Showing
23 changed files
with
891 additions
and
430 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package helpers; | ||
|
||
import org.kohsuke.github.GHRelease; | ||
import org.kohsuke.github.GHRepository; | ||
import org.kohsuke.github.GHTag; | ||
|
||
import java.io.IOException; | ||
import java.util.Collection; | ||
import java.util.function.Supplier; | ||
|
||
|
||
public class Helper { | ||
|
||
public static void info(String msg, Object... params) { | ||
System.out.println("[INFO] " + String.format(msg, params)); | ||
} | ||
|
||
public static void completed(String msg, Object... params) { | ||
System.out.println("[DONE] " + String.format(msg, params)); | ||
} | ||
|
||
public static void success(String msg, Object... params) { | ||
System.out.println("[WARN] " + String.format(msg, params)); | ||
} | ||
|
||
public static void warn(String msg, Object... params) { | ||
System.out.println("[WARN] " + String.format(msg, params)); | ||
} | ||
|
||
public static void fail(String msg, Object... params) { | ||
fail(String.format(msg, params), 2); | ||
} | ||
|
||
public static void fail(String msg, int code) { | ||
System.out.println("[FAIL] " + msg); | ||
System.exit(code); | ||
} | ||
|
||
public static void failIfTrue(Supplier<Boolean> predicate, String message, Object... params) { | ||
if (predicate.get()) { | ||
fail(message, params); | ||
} | ||
} | ||
|
||
public static <T> T first(Collection<T> collection) { | ||
if (collection.isEmpty()) { | ||
fail("Cannot retrieve the first item from an empty collection"); | ||
} | ||
return collection.iterator().next(); | ||
} | ||
|
||
public static String computeNextVersion(GHRepository repository, GHTag tag, boolean micro) throws IOException { | ||
info("Retrieving release associated with tag %s", tag.getName()); | ||
GHRelease name = repository.getReleaseByTagName(tag.getName()); | ||
if (name == null) { | ||
warn("No release associated with tag %s", tag.getName()); | ||
} | ||
String[] segments = tag.getName().split("\\."); | ||
if (segments.length < 3) { | ||
fail("Invalid version %s, number of segments must be at least 3, found %d", tag.getName(), segments.length); | ||
} | ||
|
||
if (micro) { | ||
return String.format("%s.%s.%d", segments[0], segments[1], Integer.parseInt(segments[2]) + 1); | ||
} else { | ||
return String.format("%s.%d.0", segments[0], Integer.parseInt(segments[1]) + 1); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
///usr/bin/env jbang "$0" "$@" ; exit $? | ||
//DEPS org.kohsuke:github-api:1.117 | ||
//DEPS info.picocli:picocli:4.5.0 | ||
//SOURCES Helper.java | ||
|
||
import org.kohsuke.github.*; | ||
import picocli.CommandLine; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.Files; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.concurrent.Callable; | ||
import java.util.stream.Collectors; | ||
|
||
import static helpers.Helper.*; | ||
|
||
/** | ||
* Script run after the release. | ||
* The script does the following action in this order: | ||
* - checks that the previous milestone is closed, close it if not | ||
* - checks that the next milestone exists, or create it if not | ||
* - creates the Github release and compute the release notes | ||
* <p> | ||
* Run with `./PostRelease.java --token=GITHUB_TOKEN --release-version=version | ||
* <p> | ||
* 1. The github token is mandatory. | ||
* <p> | ||
* The version is taken from the last tag if not set. | ||
*/ | ||
@CommandLine.Command(name = "post-release", mixinStandardHelpOptions = true, version = "0.1", | ||
description = "Post-Release Check") | ||
public class PostRelease implements Callable<Integer> { | ||
|
||
@CommandLine.Option(names = "--token", description = "The Github Token", required = true) | ||
private String token; | ||
|
||
@CommandLine.Option(names = "--release-version", description = "Set the released version", required = true) | ||
private String releaseVersion; | ||
|
||
private static final String REPO = "smallrye/smallrye-mutiny"; | ||
|
||
public static void main(String... args) { | ||
int exitCode = new CommandLine(new PostRelease()).execute(args); | ||
System.exit(exitCode); | ||
} | ||
|
||
@Override | ||
public Integer call() throws Exception { | ||
GitHub gitHub = new GitHubBuilder().withOAuthToken(token).build(); | ||
GHRepository repository = gitHub.getRepository(REPO); | ||
|
||
List<GHTag> tags = repository.listTags().toList(); | ||
List<GHMilestone> milestones = repository.listMilestones(GHIssueState.ALL).toList(); | ||
|
||
info("Running post-release checks for release %s", releaseVersion); | ||
|
||
// Check that the tag associated with the release version exists | ||
GHTag tag = getTag(releaseVersion, tags); | ||
|
||
assert tag != null; | ||
|
||
// Check that the milestone exists (this check has already been done during the pre-release checks, just to be double sure) | ||
GHMilestone milestone = milestones.stream().filter(m -> m.getTitle().equals(releaseVersion)).findFirst() | ||
.orElse(null); | ||
failIfTrue(() -> milestone == null, "Unable to find the milestone %s", releaseVersion); | ||
assert milestone != null; | ||
success("Milestone %s found (%s)", milestone.getTitle(), milestone.getHtmlUrl()); | ||
|
||
success("Post-release check successful"); | ||
info("Starting post-release tasks"); | ||
|
||
// Close milestone | ||
if (milestone.getState() != GHMilestoneState.CLOSED) { | ||
milestone.close(); | ||
success("Milestone %s closed (%s)", milestone.getTitle(), milestone.getHtmlUrl()); | ||
} else { | ||
success("Milestone %s already closed (%s)", milestone.getTitle(), milestone.getHtmlUrl()); | ||
} | ||
|
||
// Compute next version | ||
String nextVersion = getNextVersion(releaseVersion); | ||
success("Next version will be %s", nextVersion); | ||
|
||
// Create new milestone if it does not already exist | ||
GHMilestone nextMilestone = milestones.stream().filter(m -> m.getTitle().equals(nextVersion)).findFirst() | ||
.orElse(null); | ||
if (nextMilestone != null) { | ||
success("Next milestone (%s) already exists: %s", nextMilestone.getTitle(), nextMilestone.getHtmlUrl()); | ||
} else { | ||
nextMilestone = repository.createMilestone(nextVersion, null); | ||
success("Next milestone (%s) created: %s", nextMilestone.getTitle(), nextMilestone.getHtmlUrl()); | ||
} | ||
|
||
// Compute the release notes and create releases | ||
List<GHIssue> issues = repository.getIssues(GHIssueState.CLOSED, milestone); | ||
String description = createReleaseDescription(issues); | ||
// Check if release already exists | ||
GHRelease existingRelease = repository.getReleaseByTagName(tag.getName()); | ||
if (existingRelease != null) { | ||
info("Release %s already exists (%s) - skip release note generation", existingRelease.getName(), existingRelease.getHtmlUrl()); | ||
info("Generated release notes:\n%s", description); | ||
|
||
if (existingRelease.isDraft()) { | ||
existingRelease.update().draft(false); | ||
success("Marked release %s as non-draft", existingRelease.getName()); | ||
} | ||
} else { | ||
GHRelease release = repository.createRelease(releaseVersion) | ||
.name(releaseVersion) | ||
.body(description) | ||
.create(); | ||
success("Release %s created: %s", releaseVersion, release.getHtmlUrl()); | ||
} | ||
|
||
completed("Post-Release done!"); | ||
|
||
return 0; | ||
} | ||
|
||
private GHTag getTag(String releaseVersion, List<GHTag> tags) { | ||
failIfTrue(tags::isEmpty, "No tags found in repository"); | ||
Optional<GHTag> first = tags.stream().filter(tag -> tag.getName().equals(releaseVersion)).findFirst(); | ||
if (first.isPresent()) { | ||
success("Tag %s found", releaseVersion); | ||
return first.get(); | ||
} | ||
fail("Unable to find the tag %s in the repository", releaseVersion); | ||
return null; | ||
} | ||
|
||
private String getNextVersion(String v) { | ||
String[] segments = v.split("\\."); | ||
if (segments.length < 3) { | ||
fail("Invalid version %s, number of segments must be at least 3, found %d", v, segments.length); | ||
} | ||
|
||
return String.format("%s.%d.0", segments[0], Integer.parseInt(segments[1]) + 1); | ||
} | ||
|
||
private String createReleaseDescription(List<GHIssue> issues) throws IOException { | ||
File file = new File("target/differences.md"); | ||
String compatibility = ""; | ||
if (file.isFile()) { | ||
compatibility += new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); | ||
} | ||
|
||
StringBuilder desc = new StringBuilder(); | ||
desc.append("### Changelog\n\n"); | ||
List<String> list = issues.stream().map(this::line).collect(Collectors.toList()); | ||
desc.append(String.join("\n", list)); | ||
desc.append("\n"); | ||
|
||
desc.append(compatibility).append("\n"); | ||
return desc.toString(); | ||
} | ||
|
||
private String line(GHIssue issue) { | ||
return String.format(" * [#%d](%s) - %s", issue.getNumber(), issue.getHtmlUrl(), issue.getTitle()); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
///usr/bin/env jbang "$0" "$@" ; exit $? | ||
//DEPS org.kohsuke:github-api:1.117 | ||
//DEPS info.picocli:picocli:4.5.0 | ||
//SOURCES Helper.java | ||
|
||
import org.kohsuke.github.*; | ||
import picocli.CommandLine; | ||
|
||
import java.io.File; | ||
import java.nio.file.Files; | ||
import java.util.List; | ||
import java.util.concurrent.Callable; | ||
|
||
import static helpers.Helper.*; | ||
|
||
/** | ||
* Script checking that the repository can be released. | ||
* It checks that: | ||
* - the new version target an existing milestone | ||
* - the milestone has no opened issues | ||
* - the tag does not exist | ||
* | ||
* Run with `./PreRelease.java --token=GITHUB_TOKEN [--micro] [--release-version=version] | ||
*/ | ||
@CommandLine.Command(name = "pre-release", mixinStandardHelpOptions = true, version = "0.1", | ||
description = "Pre-Release Check") | ||
public class PreRelease implements Callable<Integer> { | ||
|
||
@CommandLine.Option(names = "--token", description = "The Github Token", required = true) | ||
private String token; | ||
|
||
@CommandLine.Option(names = "--micro", description = "To set the release to be a micro release", defaultValue = "false") | ||
private boolean micro; | ||
|
||
@CommandLine.Option(names = "--release-version", description = "Set the released version - if not set, the version is computed") | ||
private String target; | ||
|
||
private static final String REPO = "smallrye/smallrye-mutiny"; | ||
|
||
public static void main(String... args) { | ||
int exitCode = new CommandLine(new PreRelease()).execute(args); | ||
System.exit(exitCode); | ||
} | ||
|
||
@Override | ||
public Integer call() throws Exception { | ||
if (micro) { | ||
info("Preparing micro release"); | ||
} else { | ||
info("Preparing release"); | ||
} | ||
|
||
GitHub gitHub = new GitHubBuilder().withOAuthToken(token).build(); | ||
GHRepository repository = gitHub.getRepository(REPO); | ||
|
||
List<GHTag> tags = repository.listTags().toList(); | ||
List<GHMilestone> milestones = repository.listMilestones(GHIssueState.ALL).toList(); | ||
|
||
String newVersion; | ||
if (target != null && ! target.trim().isEmpty()) { | ||
newVersion = target; | ||
} else { | ||
info("No version passed, computing new version from the last tag"); | ||
GHTag tag = getLastTag(tags); | ||
newVersion = computeNextVersion(repository, tag, micro); | ||
} | ||
info("Released version would be %s", newVersion); | ||
|
||
info("Running pre-checks..."); | ||
failIfTrue(milestones::isEmpty, "No milestones in repository %s", repository.getName()); | ||
|
||
// Check that the is no tag with the name newVersion | ||
if (tags.stream().anyMatch(t -> t.getName().equals(newVersion))) { | ||
fail("Cannot cut release %s - there is already a tag with this version", newVersion); | ||
} | ||
success("No existing tag named %s", newVersion); | ||
|
||
// Check that there is a milestone named newVersion | ||
GHMilestone milestone = milestones.stream().filter(m -> m.getTitle().equals(newVersion)).findFirst().orElse(null); | ||
if (milestone == null) { | ||
fail("No milestone named %s in repository %s - you need to create one", newVersion, repository.getName()); | ||
} else { | ||
success("Milestone %s found with %d closed issues", | ||
milestone.getTitle(), | ||
milestone.getClosedIssues()); | ||
} | ||
|
||
assert milestone != null; | ||
// Check that all the issues associated with the milestone are closed | ||
failIfTrue(() -> milestone.getOpenIssues() != 0, "The milestone %s has %d opened issues, all the issues " | ||
+ "must be closed or moved to another milestone before being able to cut the release. Visit %s for details", | ||
milestone.getTitle(), milestone.getOpenIssues(), milestone.getHtmlUrl().toExternalForm()); | ||
success("No remaining (open) issue associated with the milestone %s", milestone.getTitle()); | ||
|
||
success("Release pre-checks successful!"); | ||
info("Writing release version in the /tmp/release-version file"); | ||
Files.write(new File("/tmp/release-version").toPath(), newVersion.getBytes()); | ||
if (micro) { | ||
//noinspection ResultOfMethodCallIgnored | ||
new File("/tmp/micro").createNewFile(); | ||
} | ||
|
||
completed("Pre-release checks completed!"); | ||
|
||
return 0; | ||
} | ||
|
||
private GHTag getLastTag(List<GHTag> tags) { | ||
failIfTrue(tags::isEmpty, "No tags found in repository"); | ||
GHTag tag = first(tags); | ||
assert tag != null; | ||
success("Last tag retrieved: %s", tag.getName()); | ||
return tag; | ||
} | ||
|
||
} |
Oops, something went wrong.