diff --git a/.github/workflows/check-update.yml b/.github/workflows/check-update.yml index 9bf77cf74..a1c5ae403 100644 --- a/.github/workflows/check-update.yml +++ b/.github/workflows/check-update.yml @@ -13,26 +13,16 @@ jobs: if: ${{ github.repository_owner == 'HMCL-dev' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 + - name: Set up JDK + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' - name: Fetch tags run: git fetch --all --tags - - name: Install tools - run: sudo apt-get install -y jq - name: Fetch last version - run: | - wget -O ci.json https://ci.huangyuhui.net/job/HMCL/lastSuccessfulBuild/api/json - - export HMCL_EXE_FILE_NAME=`cat ci.json | jq -M -r '.artifacts[] | select(.fileName | endswith(".exe")) | .fileName'` - if [ -z `echo $HMCL_EXE_FILE_NAME | grep -E "^HMCL-[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?\.exe\$"` ]; then exit 1; fi - - export HMCL_VERSION=`echo "${HMCL_EXE_FILE_NAME%.exe}" | tail -c +6` - export HMCL_COMMIT_SHA=`cat ci.json | jq -M -r '.actions[] | select(._class == "hudson.plugins.git.util.BuildData") | .lastBuiltRevision.SHA1'` - - if [ "${#HMCL_COMMIT_SHA}" != 40 ]; then exit 1; fi - - echo "HMCL_VERSION=$HMCL_VERSION" >> $GITHUB_ENV - echo "HMCL_COMMIT_SHA=$HMCL_COMMIT_SHA" >> $GITHUB_ENV - echo "HMCL_TAG_NAME=v$HMCL_VERSION" >> $GITHUB_ENV + run: ./gradlew checkUpdateDev --no-daemon --info --stacktrace - name: Check for existing tags run: if [ -z "$(git tag -l "$HMCL_TAG_NAME")" ]; then echo "continue=true" >> $GITHUB_ENV; fi - name: Download artifacts @@ -78,26 +68,17 @@ jobs: needs: dev-check-update runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 + - name: Set up JDK + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' - name: Fetch tags run: git fetch --all --tags - - name: Install tools - run: sudo apt-get install -y jq + - name: Fetch last version - run: | - wget -O ci.json https://ci.huangyuhui.net/job/HMCL-stable/lastSuccessfulBuild/api/json - - export HMCL_EXE_FILE_NAME=`cat ci.json | jq -M -r '.artifacts[] | select(.fileName | endswith(".exe")) | .fileName'` - if [ -z `echo $HMCL_EXE_FILE_NAME | grep -E "^HMCL-[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?\.exe\$"` ]; then exit 1; fi - - export HMCL_VERSION=`echo "${HMCL_EXE_FILE_NAME%.exe}" | tail -c +6` - export HMCL_COMMIT_SHA=`cat ci.json | jq -M -r '.actions[] | select(._class == "hudson.plugins.git.util.BuildData") | .lastBuiltRevision.SHA1'` - - if [ "${#HMCL_COMMIT_SHA}" != 40 ]; then exit 1; fi - - echo "HMCL_VERSION=$HMCL_VERSION" >> $GITHUB_ENV - echo "HMCL_COMMIT_SHA=$HMCL_COMMIT_SHA" >> $GITHUB_ENV - echo "HMCL_TAG_NAME=release-$HMCL_VERSION" >> $GITHUB_ENV + run: ./gradlew checkUpdateStable --no-daemon --info --stacktrace - name: Check for existing tags run: if ! git tag -l | grep -q "$HMCL_TAG_NAME"; then echo "continue=true" >> $GITHUB_ENV; fi - name: Download artifacts diff --git a/build.gradle.kts b/build.gradle.kts index d68432856..173537d00 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ +import org.jackhuang.hmcl.gradle.ci.CheckUpdate import org.jackhuang.hmcl.gradle.docs.UpdateDocuments plugins { @@ -66,4 +67,14 @@ defaultTasks("clean", "build") tasks.register("updateDocuments") { documentsDir.set(layout.projectDirectory.dir("docs")) -} \ No newline at end of file +} + +tasks.register("checkUpdateDev") { + tagPrefix.set("v") + api.set("https://ci.huangyuhui.net/job/HMCL/lastSuccessfulBuild/api/json") +} + +tasks.register("checkUpdateStable") { + tagPrefix.set("release-") + api.set("https://ci.huangyuhui.net/job/HMCL-stable/lastSuccessfulBuild/api/json") +} diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/ci/CheckUpdate.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/ci/CheckUpdate.java new file mode 100644 index 000000000..4b52b6ba5 --- /dev/null +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/ci/CheckUpdate.java @@ -0,0 +1,135 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.gradle.ci; + +import com.google.gson.Gson; +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.TaskAction; + +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/// @author Glavo +public abstract class CheckUpdate extends DefaultTask { + private static final Logger LOGGER = Logging.getLogger(CheckUpdate.class); + + @Input + public abstract Property getApi(); + + @Input + public abstract Property getTagPrefix(); + + public CheckUpdate() { + getOutputs().upToDateWhen(task -> false); + } + + private static T fetch(URI uri, Class type) throws IOException, InterruptedException { + // // HttpClient implements Closeable since Java 21 + //noinspection resource + var client = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.ALWAYS) + .build(); + + HttpResponse 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 + public void run() throws IOException, InterruptedException { + 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 addEnv = (name, value) -> { + String item = name + "=" + value; + LOGGER.info(item); + if (writer != null) + writer.println(item); + }; + + String revision = Objects.requireNonNullElse(buildInfo.actions(), List.of()) + .stream() + .filter(action -> "hudson.plugins.git.util.BuildData".equals(action._class)) + .map(BuildInfo.ActionInfo::lastBuiltRevision) + .map(BuildInfo.ActionInfo.BuiltRevision::SHA1) + .findFirst() + .orElseThrow(() -> new GradleException("Could not find revision")); + if (revision.matches("[0-9a-z]{40}")) + addEnv.accept("HMCL_COMMIT_SHA", revision); + else + throw new GradleException("Invalid revision: " + revision); + + Pattern fileNamePattern = Pattern.compile("HMCL-(?\\d+(?:\\.\\d+)+)\\.jar"); + String version = Objects.requireNonNullElse(buildInfo.artifacts(), List.of()) + .stream() + .map(BuildInfo.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); + } + } + + private record BuildInfo(long number, + List artifacts, + List actions + ) { + + record ArtifactInfo(String fileName) { + } + + record ActionInfo(String _class, BuiltRevision lastBuiltRevision) { + record BuiltRevision(String SHA1) { + } + } + } + +}