From a947c8a21ad7550713384762a6628385e3e39329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ampflower=20=F0=9F=8C=BA?= Date: Wed, 12 Mar 2025 19:38:25 -0700 Subject: [PATCH] feat: Wire Gradle for releasing --- build.gradle.kts | 8 +- buildSrc/build.gradle.kts | 12 ++ .../main/java/gay/ampflower/BuildPlugin.java | 51 +++++++ .../src/main/java/gay/ampflower/CiStatus.java | 12 ++ .../main/java/gay/ampflower/Environment.java | 20 +++ .../src/main/java/gay/ampflower/Meta.java | 16 ++ .../main/java/gay/ampflower/ModExtension.java | 73 +++++++++ .../main/java/gay/ampflower/internal/Env.java | 77 ++++++++++ .../gay/ampflower/internal/Properties.java | 40 +++++ .../java/gay/ampflower/internal/Util.java | 140 ++++++++++++++++++ .../java/gay/ampflower/internal/VcsHost.java | 33 +++++ fabric/build.gradle.kts | 8 +- fabric/src/main/resources/fabric.mod.json | 2 +- gradle.properties | 4 + 14 files changed, 488 insertions(+), 8 deletions(-) create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/java/gay/ampflower/BuildPlugin.java create mode 100644 buildSrc/src/main/java/gay/ampflower/CiStatus.java create mode 100644 buildSrc/src/main/java/gay/ampflower/Environment.java create mode 100644 buildSrc/src/main/java/gay/ampflower/Meta.java create mode 100644 buildSrc/src/main/java/gay/ampflower/ModExtension.java create mode 100644 buildSrc/src/main/java/gay/ampflower/internal/Env.java create mode 100644 buildSrc/src/main/java/gay/ampflower/internal/Properties.java create mode 100644 buildSrc/src/main/java/gay/ampflower/internal/Util.java create mode 100644 buildSrc/src/main/java/gay/ampflower/internal/VcsHost.java diff --git a/build.gradle.kts b/build.gradle.kts index b108431..8b96a24 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,20 +5,21 @@ import javax.imageio.ImageIO plugins { java + id("gay.ampflower.BuildPlugin") alias(libs.plugins.loom) alias(libs.plugins.minotaur) } -val id: String by project - val excluded = setOf(rootProject, project(":xplat")) allprojects { apply(plugin = "java") + apply(plugin = "gay.ampflower.BuildPlugin") apply(plugin = rootProject.libs.plugins.loom.get().pluginId) apply(plugin = rootProject.libs.plugins.minotaur.get().pluginId) val libs = rootProject.libs + version = meta.globalVersion base { if (project != rootProject) { @@ -90,10 +91,11 @@ allprojects { } val map = mapOf( - "id" to id, + "id" to mod.id.get(), "version" to version, "java" to java.targetCompatibility.majorVersion, "loader" to libs.versions.fabric.loader.get(), + "description" to project.description, "minecraftRequired" to libs.versions.minecraft.required.get(), ) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000..038aa1f --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + `java-gradle-plugin` +} + +gradlePlugin { + plugins { + create("buildPlugin") { + id = "gay.ampflower.BuildPlugin" + implementationClass = "gay.ampflower.BuildPlugin" + } + } +} diff --git a/buildSrc/src/main/java/gay/ampflower/BuildPlugin.java b/buildSrc/src/main/java/gay/ampflower/BuildPlugin.java new file mode 100644 index 0000000..aa524c2 --- /dev/null +++ b/buildSrc/src/main/java/gay/ampflower/BuildPlugin.java @@ -0,0 +1,51 @@ +package gay.ampflower; + +import gay.ampflower.internal.Env; +import gay.ampflower.internal.Properties; +import gay.ampflower.internal.Util; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.VersionCatalogsExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Ampflower + * @since 0.0.0 + **/ +public class BuildPlugin implements Plugin { + private static final Logger logger = LoggerFactory.getLogger(BuildPlugin.class); + + @Override + public void apply(final Project target) { + final var extensions = target.getExtensions(); + + if (extensions.findByType(Meta.class) != null) { + logger.warn("Meta already registered in {}, bailing", target); + return; + } + + final var libs = target.getRootProject().getExtensions().getByType(VersionCatalogsExtension.class).named("libs"); + + final var version = Properties.str(target, "version"); + final var minecraftVersion = libs.findLibrary("minecraft").get().get().getVersionConstraint().getRequiredVersion(); + + extensions.add("ci", new CiStatus( + Env.Publish, + Env.Release, + Env.Actions + )); + + extensions.add("meta", new Meta( + version, + Util.mkVersion(version + "+mc." + minecraftVersion), + Util.getVersionType(version), + Util.mkChangelog(Properties.str(target, "github")), + Util.getCompatibleVersions(libs, target) + )); + + extensions.add("mod", ModExtension.class); + + logger.info("Got {}", extensions.findByName("meta")); + } +} diff --git a/buildSrc/src/main/java/gay/ampflower/CiStatus.java b/buildSrc/src/main/java/gay/ampflower/CiStatus.java new file mode 100644 index 0000000..3b44385 --- /dev/null +++ b/buildSrc/src/main/java/gay/ampflower/CiStatus.java @@ -0,0 +1,12 @@ +package gay.ampflower; + +/** + * @author Ampflower + * @since 0.0.0 + **/ +public record CiStatus( + boolean publish, + boolean release, + boolean actions +) { +} diff --git a/buildSrc/src/main/java/gay/ampflower/Environment.java b/buildSrc/src/main/java/gay/ampflower/Environment.java new file mode 100644 index 0000000..a93e9f9 --- /dev/null +++ b/buildSrc/src/main/java/gay/ampflower/Environment.java @@ -0,0 +1,20 @@ +package gay.ampflower; + +/** + * @author Ampflower + * @since 0.0.0 + **/ +public enum Environment { + ANY("*", "*", "BOTH"), + CLIENT("client", "client", "CLIENT"), + SERVER("server", "dedicated_server", "SERVER"), + ; + + public final String fabric, quilt, forge; + + Environment(String fabric, String quilt, String forge) { + this.fabric = fabric; + this.quilt = quilt; + this.forge = forge; + } +} diff --git a/buildSrc/src/main/java/gay/ampflower/Meta.java b/buildSrc/src/main/java/gay/ampflower/Meta.java new file mode 100644 index 0000000..23531da --- /dev/null +++ b/buildSrc/src/main/java/gay/ampflower/Meta.java @@ -0,0 +1,16 @@ +package gay.ampflower; + +import java.util.List; + +/** + * @author Ampflower + * @since 0.0.0 + **/ +public record Meta( + String projectVersion, + String globalVersion, + String releaseType, + String changelog, + List minecraftCompatible +) { +} diff --git a/buildSrc/src/main/java/gay/ampflower/ModExtension.java b/buildSrc/src/main/java/gay/ampflower/ModExtension.java new file mode 100644 index 0000000..eceea59 --- /dev/null +++ b/buildSrc/src/main/java/gay/ampflower/ModExtension.java @@ -0,0 +1,73 @@ +package gay.ampflower; + +import org.gradle.api.Project; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; + +/** + * @author Ampflower + * @since 0.0.0 + **/ +public class ModExtension { + private final Property id, version, name, description, icon; + private final Property environment; + private final ListProperty authors, licenses; + private final MapProperty contact; + //private final Map> entrypoints; + + public ModExtension(Project project) { + final var rootProject = project.getRootProject(); + final var objectFactory = project.getObjects(); + + this.id = objectFactory.property(String.class).convention(rootProject.getName()); + this.version = objectFactory.property(String.class).convention((String) project.getVersion()); + this.name = objectFactory.property(String.class).convention(rootProject.getDisplayName()); + this.description = objectFactory.property(String.class).convention(rootProject.getDescription()); + + this.authors = objectFactory.listProperty(String.class); + this.contact = objectFactory.mapProperty(String.class, String.class); + this.licenses = objectFactory.listProperty(String.class); + + this.icon = objectFactory.property(String.class); + this.environment = objectFactory.property(Environment.class); + } + + public Property getId() { + return this.id; + } + + public Property getVersion() { + return this.version; + } + + public Property getName() { + return this.name; + } + + public Property getDescription() { + return this.description; + } + + public ListProperty getAuthors() { + return this.authors; + } + + public MapProperty getContact() { + return this.contact; + } + + public ListProperty getLicenses() { + return this.licenses; + } + + public Property getIcon() { + return this.icon; + } + + public Property getEnvironment() { + return this.environment; + } + + //public void depend(String dependency, Action<>) +} diff --git a/buildSrc/src/main/java/gay/ampflower/internal/Env.java b/buildSrc/src/main/java/gay/ampflower/internal/Env.java new file mode 100644 index 0000000..48fdf5c --- /dev/null +++ b/buildSrc/src/main/java/gay/ampflower/internal/Env.java @@ -0,0 +1,77 @@ +package gay.ampflower.internal; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Ampflower + * @since 0.0.0 + **/ +public final class Env { + public static final boolean Publish = "release".equals(env("GITHUB_EVENT_NAME")); + public static final boolean Release = bool("BUILD_RELEASE"); + public static final boolean Actions = bool("GITHUB_ACTIONS"); + + public static final @Nullable String Commit = env("GITHUB_SHA"); + public static final @Nullable String RunNumber = env("GITHUB_RUN_NUMBER"); + public static final @Nullable String Reference = env("GITHUB_REF"); + public static final @Nullable String Changelog = env("CHANGELOG"); + public static final @Nullable String ReleaseOverride = env("RELEASE_OVERRIDE"); + + private Env() { + } + + public static boolean isReferenceTag() { + return Util.startsWith(Reference, "refs/tags/"); + } + + public static boolean isReferenceBranch() { + return Util.startsWith(Reference, "refs/heads/"); + } + + public static @Nullable String getTag() { + if (isReferenceTag()) { + return Reference.substring(10); + } + return null; + } + + public static @Nullable String getBranch() { + if (isReferenceBranch()) { + return Reference.substring(11); + } + return null; + } + + public static String getCommit(int limit) { + if (Commit == null) { + return "unknown"; + } + if (limit > 0) { + return Commit.substring(0, Math.min(Commit.length(), limit)); + } + return Commit; + } + + public static String getRunNumber() { + if (RunNumber == null) { + return "unknown"; + } + return RunNumber; + } + + public static String getReference() { + if (Reference == null) { + return "unknown"; + } + return Reference; + } + + public static @Nullable String env(@NotNull String env) { + return System.getenv(env); + } + + public static boolean bool(@NotNull String env) { + return Boolean.parseBoolean(env(env)); + } +} diff --git a/buildSrc/src/main/java/gay/ampflower/internal/Properties.java b/buildSrc/src/main/java/gay/ampflower/internal/Properties.java new file mode 100644 index 0000000..82e73ac --- /dev/null +++ b/buildSrc/src/main/java/gay/ampflower/internal/Properties.java @@ -0,0 +1,40 @@ +package gay.ampflower.internal; + +import org.gradle.api.Project; +import org.jetbrains.annotations.Nullable; + +/** + * @author Ampflower + * @since 0.0.0 + **/ +public final class Properties { + private static final String pluginStr = "gay.ampflower."; + + public static boolean bool(Project project, String property) { + final var p = project.property(property); + if (p instanceof Boolean bool) { + return bool; + } + if (p instanceof String str) { + return Boolean.parseBoolean(str); + } + return false; + } + + public static @Nullable String str(Project project, String property) { + final var p = project.property(property); + if (p == null) { + return null; + } + return p.toString(); + } + + public static boolean pluginBool(Project project, String property) { + return bool(project, pluginStr + property); + } + + + public static @Nullable String pluginStr(Project project, String property) { + return str(project, pluginStr + property); + } +} diff --git a/buildSrc/src/main/java/gay/ampflower/internal/Util.java b/buildSrc/src/main/java/gay/ampflower/internal/Util.java new file mode 100644 index 0000000..61481ca --- /dev/null +++ b/buildSrc/src/main/java/gay/ampflower/internal/Util.java @@ -0,0 +1,140 @@ +package gay.ampflower.internal; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.VersionCatalog; +import org.gradle.api.artifacts.VersionConstraint; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Optional; + +/** + * @author Ampflower + * @since 0.0.0 + **/ +public class Util { + private static final Logger logger = LoggerFactory.getLogger(Util.class); + + public static String urlEncode(String str) { + return URLEncoder.encode(str, StandardCharsets.UTF_8); + } + + public static String mkChangelog(String git) { + if (!isBlank(Env.Changelog)) { + logger.debug("Changelog found, returning {}", Env.Changelog); + return Env.Changelog; + } + + if (git == null) { + logger.warn("Git repository not defined. Returning null."); + return null; + } + + if (startsWith(Env.Reference, "refs/tags/")) { + final var host = VcsHost.find(git); + + logger.warn("Changelog not found but tag reference was, returning link to {}{}{}", + git, host.release, urlEncode(Env.getTag())); + + return "You may view the changelog at " + git + host.release + urlEncode(Env.getTag()); + } + + logger.warn("Changelog and reference not found, returning link to {}", git); + return "No changelog is available. Perhaps poke at " + git + " for a changelog?"; + } + + public static String mkVersion(final String baseVersion) { + if (Env.Publish) { + return baseVersion; + } + + if (Env.Actions) { + return baseVersion + "-build." + Env.getRunNumber() + "-commit." + Env.getCommit(7) + "-branch." + getBranchForVersion(); + } + + return baseVersion + "-build.local"; + } + + private static String getBranchForVersion() { + final var ref = Env.getBranch(); + if (ref == null) { + return "unknown"; + } + return ref.replace('/', '.'); + } + + public static List getCompatibleVersions(VersionCatalog catalog, Project module) { + Optional version; + switch (module.getName().toLowerCase()) { + case "neoforge": + version = catalog.findVersion("minecraft-neoforge-compatible"); + logger.info("Testing Neoforge: {}", version); + if (version.isPresent()) break; + case "forge": + version = catalog.findVersion("minecraft-forge-compatible"); + logger.info("Testing Forge: {}", version); + if (version.isPresent()) break; + case "quilt": + version = catalog.findVersion("minecraft-quilt-compatible"); + logger.info("Testing Quilt: {}", version); + if (version.isPresent()) break; + case "fabric": + version = catalog.findVersion("minecraft-fabric-compatible"); + logger.info("Testing Fabric: {}", version); + if (version.isPresent()) break; + default: + version = catalog.findVersion("minecraft-compatible"); + logger.info("Testing default: {}", version); + } + + if (version.isEmpty()) { + final var minecraft = catalog.findLibrary("minecraft").map(provider -> provider.get().getVersion()); + + if (minecraft.isPresent()) { + logger.warn("No marked compatible versions available for {}; compatible version will be marked as {}.", + module, minecraft.get()); + return List.of(minecraft.get()); + } + + logger.warn("No marked compatible versions available for {}; compatible versions will not be marked.", module); + return List.of(); + } + + return List.of(version.get().getRequiredVersion().split(",")); + } + + public static String getVersionType(String version) { + if (!Util.isBlank(Env.ReleaseOverride)) { + return Env.ReleaseOverride; + } + + if (version.contains("alpha")) { + return "alpha"; + } + + if (!Env.Release || version.indexOf('-') >= 0) { + return "beta"; + } + + return "release"; + } + + public static boolean isBlank(@Nullable String str) { + if (str == null) { + return true; + } + return str.isBlank(); + } + + public static boolean startsWith(@Nullable String str, @NotNull String prefix) { + if (str == null) { + return false; + } + return str.startsWith(prefix); + } +} diff --git a/buildSrc/src/main/java/gay/ampflower/internal/VcsHost.java b/buildSrc/src/main/java/gay/ampflower/internal/VcsHost.java new file mode 100644 index 0000000..6511d66 --- /dev/null +++ b/buildSrc/src/main/java/gay/ampflower/internal/VcsHost.java @@ -0,0 +1,33 @@ +package gay.ampflower.internal; + +/** + * @author Ampflower + * @since 0.0.0 + **/ +public enum VcsHost { + GitHub("github.com", "/issues", "/releases/tag/"), + GitLab("gitlab.com", "/-/issues", "/-/releases/"), + Codeberg("codeberg.org", "/issues", "/releases/tag/"); + + public final String host, issues, release; + + VcsHost(String host, String issues, String release) { + this.host = host; + this.issues = issues; + this.release = release; + } + + public static VcsHost find(String url) { + // "https://".length = 8 + final int lastSlash = url.indexOf('/', 8); + for (final var host : values()) { + final var str = host.host; + final var len = str.length(); + if (len > lastSlash) continue; + if (url.regionMatches(lastSlash - len, str, 0, len)) { + return host; + } + } + return null; + } +} diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index d9170d1..54960ff 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -40,13 +40,13 @@ tasks { modrinth { token.set(System.getenv("MODRINTH_TOKEN")) projectId.set(modrinthId) - //versionType.set(meta.releaseType) - //versionName.set("${meta.projectVersion} - Fabric ${libs.versions.minecraft.version.get()}") + versionType.set(meta.releaseType) + versionName.set("${meta.projectVersion} - Fabric ${libs.versions.minecraft.version.get()}") versionNumber.set("${project.version}-fabric") - //changelog.set(meta.changelog) + changelog.set(meta.changelog) uploadFile.set(tasks.remapJar) dependencies { } - //gameVersions.set(meta.minecraftCompatible) + gameVersions.set(meta.minecraftCompatible) loaders.addAll("fabric", "quilt") } diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 507ea7f..2512ee4 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -3,7 +3,7 @@ "id": "joy", "version": "${version}", "name": "Joy", - "description": "Joy is a Fabric mod built with the core goal of tastefully adding pride to Minecraft, while also porting Pridepack to its own set of decoration.", + "description": "${description}", "authors": [ "Pridecraft Studios", "Wolren", diff --git a/gradle.properties b/gradle.properties index c6a75ed..8564745 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,6 +5,10 @@ version=1.0.0-alpha.1 group=gay.pridecraft id=joy +github=https://git.pridecraft.gay/joy + +description=Joy is a Fabric mod built with the core goal of tastefully adding pride to Minecraft, while also porting Pridepack to its own set of decoration. + deps.midnightlib=1.5.7-fabric modrinthId=joy