Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Release Script #405

Merged
merged 7 commits into from
Jan 4, 2021
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
69 changes: 69 additions & 0 deletions .build/Helper.java
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);
}
}
}
164 changes: 164 additions & 0 deletions .build/PostRelease.java
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());
}

}
116 changes: 116 additions & 0 deletions .build/PreRelease.java
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;
}

}
Loading