支持从 WorkflowMultiBranchProject 中检查最新版本 (#4576)
This commit is contained in:
@@ -78,10 +78,10 @@ tasks.register<UpdateDocuments>("updateDocuments") {
|
|||||||
|
|
||||||
tasks.register<CheckUpdate>("checkUpdateDev") {
|
tasks.register<CheckUpdate>("checkUpdateDev") {
|
||||||
tagPrefix.set("v")
|
tagPrefix.set("v")
|
||||||
api.set("https://ci.huangyuhui.net/job/HMCL/lastSuccessfulBuild/api/json")
|
uri.set("https://ci.huangyuhui.net/job/HMCL")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<CheckUpdate>("checkUpdateStable") {
|
tasks.register<CheckUpdate>("checkUpdateStable") {
|
||||||
tagPrefix.set("release-")
|
tagPrefix.set("release-")
|
||||||
api.set("https://ci.huangyuhui.net/job/HMCL-stable/lastSuccessfulBuild/api/json")
|
uri.set("https://ci.huangyuhui.net/job/HMCL-stable")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
package org.jackhuang.hmcl.gradle.ci;
|
package org.jackhuang.hmcl.gradle.ci;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
import org.gradle.api.DefaultTask;
|
import org.gradle.api.DefaultTask;
|
||||||
import org.gradle.api.GradleException;
|
import org.gradle.api.GradleException;
|
||||||
import org.gradle.api.logging.Logger;
|
import org.gradle.api.logging.Logger;
|
||||||
@@ -35,6 +37,7 @@ import java.net.http.HttpResponse;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
@@ -45,8 +48,12 @@ import java.util.regex.Pattern;
|
|||||||
public abstract class CheckUpdate extends DefaultTask {
|
public abstract class CheckUpdate extends DefaultTask {
|
||||||
private static final Logger LOGGER = Logging.getLogger(CheckUpdate.class);
|
private static final Logger LOGGER = Logging.getLogger(CheckUpdate.class);
|
||||||
|
|
||||||
|
private static final String WORKFLOW_JOB = "org.jenkinsci.plugins.workflow.job.WorkflowJob";
|
||||||
|
private static final String WORKFLOW_MULTI_BRANCH_PROJECT = "org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject";
|
||||||
|
private static final String FREE_STYLE_PROJECT = "hudson.model.FreeStyleProject";
|
||||||
|
|
||||||
@Input
|
@Input
|
||||||
public abstract Property<String> getApi();
|
public abstract Property<String> getUri();
|
||||||
|
|
||||||
@Input
|
@Input
|
||||||
public abstract Property<String> getTagPrefix();
|
public abstract Property<String> getTagPrefix();
|
||||||
@@ -55,81 +62,150 @@ public abstract class CheckUpdate extends DefaultTask {
|
|||||||
getOutputs().upToDateWhen(task -> false);
|
getOutputs().upToDateWhen(task -> false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T fetch(URI uri, Class<T> type) throws IOException, InterruptedException {
|
private static URI toURI(String baseUri, String suffix) {
|
||||||
// // HttpClient implements Closeable since Java 21
|
return URI.create(baseUri.endsWith("/")
|
||||||
//noinspection resource
|
? baseUri + suffix
|
||||||
var client = HttpClient.newBuilder()
|
: baseUri + "/" + suffix
|
||||||
.followRedirects(HttpClient.Redirect.ALWAYS)
|
);
|
||||||
.build();
|
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(HttpRequest.newBuilder(uri).build(), HttpResponse.BodyHandlers.ofString());
|
|
||||||
if (response.statusCode() / 100 != 2) {
|
|
||||||
throw new IOException("Bad status code: " + response.statusCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Gson().fromJson(response.body(), type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
public void run() throws IOException, InterruptedException {
|
public void run() throws Exception {
|
||||||
|
String uri = getUri().get();
|
||||||
|
URI apiUri = toURI(uri, "api/json");
|
||||||
|
LOGGER.quiet("Fetching metadata from {}", apiUri);
|
||||||
|
|
||||||
|
BuildMetadata buildMetadata;
|
||||||
|
|
||||||
|
try (var helper = new Helper()) {
|
||||||
|
JsonObject body = Objects.requireNonNull(helper.fetch(apiUri, JsonObject.class));
|
||||||
|
String jobType = Objects.requireNonNull(body.getAsJsonPrimitive("_class"), "Missing _class property")
|
||||||
|
.getAsString();
|
||||||
|
|
||||||
|
if (WORKFLOW_MULTI_BRANCH_PROJECT.equals(jobType)) {
|
||||||
|
Pattern namePattern = Pattern.compile("release%2F3\\.\\d+");
|
||||||
|
|
||||||
|
List<BuildMetadata> metadatas = Objects.requireNonNull(helper.gson.fromJson(body.get("jobs"), new TypeToken<List<SubJobInfo>>() {
|
||||||
|
}), "jobs")
|
||||||
|
.stream()
|
||||||
|
.filter(it -> WORKFLOW_JOB.equals(it._class()))
|
||||||
|
.filter(it -> namePattern.matcher(it.name()).matches())
|
||||||
|
.filter(it -> !it.color().equals("disabled"))
|
||||||
|
.map(it -> {
|
||||||
|
try {
|
||||||
|
return fetchBuildInfo(helper, toURI(it.url, "lastSuccessfulBuild/api/json"));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new GradleException("Failed to retrieve build info from " + it.url(), e);
|
||||||
|
}
|
||||||
|
}).sorted(Comparator.comparing(BuildMetadata::timestamp))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (metadatas.isEmpty())
|
||||||
|
throw new GradleException("Failed to retrieve build metadata from " + apiUri);
|
||||||
|
|
||||||
|
buildMetadata = metadatas.get(metadatas.size() - 1);
|
||||||
|
} else if (WORKFLOW_JOB.equals(jobType) || FREE_STYLE_PROJECT.equals(jobType)) {
|
||||||
|
buildMetadata = fetchBuildInfo(helper, toURI(uri, "lastSuccessfulBuild/api/json"));
|
||||||
|
} else {
|
||||||
|
throw new GradleException("Unsupported job type: " + jobType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.quiet("Build metadata found: {}", buildMetadata);
|
||||||
|
|
||||||
String githubEnv = Objects.requireNonNullElse(System.getenv("GITHUB_ENV"), "");
|
String githubEnv = Objects.requireNonNullElse(System.getenv("GITHUB_ENV"), "");
|
||||||
if (githubEnv.isBlank())
|
if (githubEnv.isBlank())
|
||||||
LOGGER.warn("GITHUB_ENV is not set");
|
LOGGER.warn("GITHUB_ENV is not set");
|
||||||
|
|
||||||
String uri = getApi().get();
|
|
||||||
LOGGER.info("Fetching metadata from {}", uri);
|
|
||||||
BuildInfo buildInfo = Objects.requireNonNull(fetch(URI.create(uri), BuildInfo.class),
|
|
||||||
"Could not fetch build info");
|
|
||||||
|
|
||||||
try (PrintWriter writer = githubEnv.isBlank()
|
try (PrintWriter writer = githubEnv.isBlank()
|
||||||
? null
|
? null
|
||||||
: new PrintWriter(Files.newBufferedWriter(Path.of(githubEnv), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND))) {
|
: new PrintWriter(Files.newBufferedWriter(Path.of(githubEnv), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND))) {
|
||||||
|
|
||||||
BiConsumer<String, String> addEnv = (name, value) -> {
|
BiConsumer<String, String> addEnv = (name, value) -> {
|
||||||
String item = name + "=" + value;
|
String item = name + "=" + value;
|
||||||
LOGGER.info(item);
|
LOGGER.quiet(item);
|
||||||
if (writer != null)
|
if (writer != null)
|
||||||
writer.println(item);
|
writer.println(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
String revision = Objects.requireNonNullElse(buildInfo.actions(), List.<BuildInfo.ActionInfo>of())
|
addEnv.accept("HMCL_COMMIT_SHA", buildMetadata.revision());
|
||||||
|
addEnv.accept("HMCL_VERSION", buildMetadata.version());
|
||||||
|
addEnv.accept("HMCL_TAG_NAME", getTagPrefix().get() + buildMetadata.version());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record BuildMetadata(String version, String revision, long timestamp) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private BuildMetadata fetchBuildInfo(Helper helper, URI uri) throws IOException, InterruptedException {
|
||||||
|
LOGGER.quiet("Fetching build info from {}", uri);
|
||||||
|
|
||||||
|
BuildInfo buildInfo = Objects.requireNonNull(helper.fetch(uri, BuildInfo.class), "build info");
|
||||||
|
|
||||||
|
String revision = Objects.requireNonNullElse(buildInfo.actions(), List.<ActionInfo>of())
|
||||||
.stream()
|
.stream()
|
||||||
.filter(action -> "hudson.plugins.git.util.BuildData".equals(action._class))
|
.filter(action -> "hudson.plugins.git.util.BuildData".equals(action._class()))
|
||||||
.map(BuildInfo.ActionInfo::lastBuiltRevision)
|
.map(ActionInfo::lastBuiltRevision)
|
||||||
.map(BuildInfo.ActionInfo.BuiltRevision::SHA1)
|
.map(BuiltRevision::SHA1)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow(() -> new GradleException("Could not find revision"));
|
.orElseThrow(() -> new GradleException("Could not find revision"));
|
||||||
if (revision.matches("[0-9a-z]{40}"))
|
if (!revision.matches("[0-9a-z]{40}"))
|
||||||
addEnv.accept("HMCL_COMMIT_SHA", revision);
|
|
||||||
else
|
|
||||||
throw new GradleException("Invalid revision: " + revision);
|
throw new GradleException("Invalid revision: " + revision);
|
||||||
|
|
||||||
Pattern fileNamePattern = Pattern.compile("HMCL-(?<version>\\d+(?:\\.\\d+)+)\\.jar");
|
Pattern fileNamePattern = Pattern.compile("HMCL-(?<version>\\d+(?:\\.\\d+)+)\\.jar");
|
||||||
String version = Objects.requireNonNullElse(buildInfo.artifacts(), List.<BuildInfo.ArtifactInfo>of())
|
String version = Objects.requireNonNullElse(buildInfo.artifacts(), List.<ArtifactInfo>of())
|
||||||
.stream()
|
.stream()
|
||||||
.map(BuildInfo.ArtifactInfo::fileName)
|
.map(ArtifactInfo::fileName)
|
||||||
.map(fileNamePattern::matcher)
|
.map(fileNamePattern::matcher)
|
||||||
.filter(Matcher::matches)
|
.filter(Matcher::matches)
|
||||||
.map(matcher -> matcher.group("version"))
|
.map(matcher -> matcher.group("version"))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow(() -> new GradleException("Could not find .jar artifact"));
|
.orElseThrow(() -> new GradleException("Could not find .jar artifact"));
|
||||||
addEnv.accept("HMCL_VERSION", version);
|
|
||||||
addEnv.accept("HMCL_TAG_NAME", getTagPrefix().get() + version);
|
return new BuildMetadata(version, revision, buildInfo.timestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Helper implements AutoCloseable {
|
||||||
|
private final HttpClient client = HttpClient.newBuilder()
|
||||||
|
.followRedirects(HttpClient.Redirect.ALWAYS)
|
||||||
|
.build();
|
||||||
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
|
private <T> T fetch(URI uri, Class<T> type) throws IOException, InterruptedException {
|
||||||
|
HttpResponse<String> response = client.send(HttpRequest.newBuilder(uri).build(), HttpResponse.BodyHandlers.ofString());
|
||||||
|
if (response.statusCode() / 100 != 2) {
|
||||||
|
throw new IOException("Bad status code " + response.statusCode() + " for " + uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gson.fromJson(response.body(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
// HttpClient implements AutoCloseable since Java 21
|
||||||
|
if (((Object) client) instanceof AutoCloseable closeable) {
|
||||||
|
closeable.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record SubJobInfo(String _class, String name, String url, String color) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private record BuildInfo(long number,
|
private record BuildInfo(long number,
|
||||||
|
long timestamp,
|
||||||
List<ArtifactInfo> artifacts,
|
List<ArtifactInfo> artifacts,
|
||||||
List<ActionInfo> actions
|
List<ActionInfo> actions
|
||||||
) {
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
record ArtifactInfo(String fileName) {
|
record ArtifactInfo(String fileName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
record ActionInfo(String _class, BuiltRevision lastBuiltRevision) {
|
record ActionInfo(String _class, BuiltRevision lastBuiltRevision) {
|
||||||
|
}
|
||||||
|
|
||||||
record BuiltRevision(String SHA1) {
|
record BuiltRevision(String SHA1) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user