支持从 WorkflowMultiBranchProject 中检查最新版本 (#4576)

This commit is contained in:
Glavo
2025-10-02 15:22:15 +08:00
committed by GitHub
parent 484b65b513
commit 361e928e73
2 changed files with 129 additions and 53 deletions

View File

@@ -78,10 +78,10 @@ tasks.register<UpdateDocuments>("updateDocuments") {
tasks.register<CheckUpdate>("checkUpdateDev") {
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") {
tagPrefix.set("release-")
api.set("https://ci.huangyuhui.net/job/HMCL-stable/lastSuccessfulBuild/api/json")
uri.set("https://ci.huangyuhui.net/job/HMCL-stable")
}

View File

@@ -18,6 +18,8 @@
package org.jackhuang.hmcl.gradle.ci;
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.GradleException;
import org.gradle.api.logging.Logger;
@@ -35,6 +37,7 @@ import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
@@ -45,8 +48,12 @@ import java.util.regex.Pattern;
public abstract class CheckUpdate extends DefaultTask {
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
public abstract Property<String> getApi();
public abstract Property<String> getUri();
@Input
public abstract Property<String> getTagPrefix();
@@ -55,81 +62,150 @@ public abstract class CheckUpdate extends DefaultTask {
getOutputs().upToDateWhen(task -> false);
}
private static <T> T fetch(URI uri, Class<T> type) throws IOException, InterruptedException {
// // HttpClient implements Closeable since Java 21
//noinspection resource
var client = HttpClient.newBuilder()
.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);
private static URI toURI(String baseUri, String suffix) {
return URI.create(baseUri.endsWith("/")
? baseUri + suffix
: baseUri + "/" + suffix
);
}
@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"), "");
if (githubEnv.isBlank())
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()
? null
: new PrintWriter(Files.newBufferedWriter(Path.of(githubEnv), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND))) {
BiConsumer<String, String> addEnv = (name, value) -> {
String item = name + "=" + value;
LOGGER.info(item);
LOGGER.quiet(item);
if (writer != null)
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()
.filter(action -> "hudson.plugins.git.util.BuildData".equals(action._class))
.map(BuildInfo.ActionInfo::lastBuiltRevision)
.map(BuildInfo.ActionInfo.BuiltRevision::SHA1)
.filter(action -> "hudson.plugins.git.util.BuildData".equals(action._class()))
.map(ActionInfo::lastBuiltRevision)
.map(BuiltRevision::SHA1)
.findFirst()
.orElseThrow(() -> new GradleException("Could not find revision"));
if (revision.matches("[0-9a-z]{40}"))
addEnv.accept("HMCL_COMMIT_SHA", revision);
else
if (!revision.matches("[0-9a-z]{40}"))
throw new GradleException("Invalid revision: " + revision);
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()
.map(BuildInfo.ArtifactInfo::fileName)
.map(ArtifactInfo::fileName)
.map(fileNamePattern::matcher)
.filter(Matcher::matches)
.map(matcher -> matcher.group("version"))
.findFirst()
.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,
long timestamp,
List<ArtifactInfo> artifacts,
List<ActionInfo> actions
) {
}
record ArtifactInfo(String fileName) {
}
record ActionInfo(String _class, BuiltRevision lastBuiltRevision) {
record BuiltRevision(String SHA1) {
}
}
}
record BuiltRevision(String SHA1) {
}
}