From 87467f5d93ebbd0687bd156059d5bdf0499cb7a6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 16:55:49 +0000 Subject: [PATCH 01/11] feat: add Minecraft 26.1.2 support for Spigot/Paper - Create bukkit-helper-26-1-2 module modeled on 1.21.11 - BukkitVersionHelperSpigot26_1_2, MapChunkCache26_1_2, NBT - Reflective CraftBukkit lookup tries Paper unversioned, plus candidate Spigot prefixes v1_21_R8 and v26_1_R1 - Module compiles with Java 25 (required by MC 26.1+) - Update Helper.java to dispatch (MC: 26.*) to new helper - Wire bukkit-helper-26-1-2 into settings.gradle and spigot/build.gradle - ci(release): bump to JDK 25 and add BuildTools 26.1.2 - ci(auto-beta-release): on push to upgrade branch, compute next 26.1.2-beta. tag, build with BuildTools+Gradle, then create GitHub Release with the spigot JAR attached --- .github/workflows/auto-beta-release.yml | 91 +++ .github/workflows/release.yml | 7 +- bukkit-helper-26-1-2/build.gradle | 20 + .../BukkitVersionHelperSpigot26_1_2.java | 557 ++++++++++++++++++ .../helper/v26_1_2/MapChunkCache26_1_2.java | 190 ++++++ .../org/dynmap/bukkit/helper/v26_1_2/NBT.java | 143 +++++ settings.gradle | 2 + spigot/build.gradle | 4 + .../main/java/org/dynmap/bukkit/Helper.java | 3 + 9 files changed, 1014 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/auto-beta-release.yml create mode 100644 bukkit-helper-26-1-2/build.gradle create mode 100644 bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/BukkitVersionHelperSpigot26_1_2.java create mode 100644 bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/MapChunkCache26_1_2.java create mode 100644 bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/NBT.java diff --git a/.github/workflows/auto-beta-release.yml b/.github/workflows/auto-beta-release.yml new file mode 100644 index 000000000..08bc1c345 --- /dev/null +++ b/.github/workflows/auto-beta-release.yml @@ -0,0 +1,91 @@ +name: Auto Beta Release + +# Each push to the upgrade branch produces a new release tagged +# 26.1.2-beta., where N auto-increments from the highest existing tag. + +on: + push: + branches: + - claude/minecraft-26.1.2-upgrade-6YV9K + +concurrency: + group: auto-beta-release + cancel-in-progress: false + +permissions: + contents: write + +jobs: + build-and-release: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Compute next beta version + id: version + run: | + set -euo pipefail + BASE="26.1.2-beta" + git fetch --tags --force + LAST=$(git tag --list "${BASE}.*" \ + | sed -E "s/^${BASE}\.([0-9]+)$/\1/" \ + | grep -E '^[0-9]+$' \ + | sort -n \ + | tail -n1 || true) + if [ -z "${LAST}" ]; then + NEXT=1 + else + NEXT=$((LAST + 1)) + fi + TAG="${BASE}.${NEXT}" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "Next tag: ${TAG}" + + - name: Set up JDK 25 + uses: actions/setup-java@v5 + with: + java-version: '25' + distribution: 'temurin' + cache: gradle + + - name: Build Spigot with BuildTools + run: | + mkdir -p buildtools + cd buildtools + wget -q https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar + java -jar BuildTools.jar --rev 1.21.10 --remapped + java -jar BuildTools.jar --rev 1.21.11 --remapped + java -jar BuildTools.jar --rev 26.1.2 --remapped + cd .. + timeout-minutes: 60 + + - name: Build with Gradle + env: + USERNAME: ${{ secrets.USERNAME }} + TOKEN: ${{ secrets.TOKEN }} + run: ./gradlew :spigot:build + + - name: Find JAR file + id: find_jar + run: | + set -euo pipefail + JAR_FILE=$(ls target/*.jar | grep 'spigot' | tail -n 1) + echo "jar_file=${JAR_FILE}" >> "$GITHUB_OUTPUT" + echo "jar_name=$(basename "${JAR_FILE}")" >> "$GITHUB_OUTPUT" + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.version.outputs.tag }} + JAR_FILE: ${{ steps.find_jar.outputs.jar_file }} + run: | + set -euo pipefail + gh release create "${TAG}" "${JAR_FILE}" \ + --target "${GITHUB_SHA}" \ + --title "${TAG}" \ + --notes "Automated beta release for Minecraft 26.1.2 (commit ${GITHUB_SHA})." \ + --prerelease diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6bbbabf30..4ad70e3d5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,10 +12,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@v5 with: - java-version: '21' + java-version: '25' distribution: 'temurin' cache: gradle @@ -26,8 +26,9 @@ jobs: wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar java -jar BuildTools.jar --rev 1.21.10 --remapped java -jar BuildTools.jar --rev 1.21.11 --remapped + java -jar BuildTools.jar --rev 26.1.2 --remapped cd .. - timeout-minutes: 45 + timeout-minutes: 60 - name: Build with Gradle env: diff --git a/bukkit-helper-26-1-2/build.gradle b/bukkit-helper-26-1-2/build.gradle new file mode 100644 index 000000000..207d6a796 --- /dev/null +++ b/bukkit-helper-26-1-2/build.gradle @@ -0,0 +1,20 @@ +eclipse { + project { + name = "Dynmap(Spigot-26.1.2)" + } +} + +description = 'bukkit-helper-26.1.2' + +// MC 26.1+ requires Java 25. +sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(25) // Need this here so eclipse task generates correctly. + +dependencies { + implementation project(':bukkit-helper') + implementation project(':dynmap-api') + implementation project(path: ':DynmapCore', configuration: 'shadow') + compileOnly 'org.spigotmc:spigot-api:26.1.2-R0.1-SNAPSHOT' + compileOnly ('org.spigotmc:spigot:26.1.2-R0.1-SNAPSHOT:remapped-mojang') { + exclude group: "com.mojang", module: "jtracy" + } +} diff --git a/bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/BukkitVersionHelperSpigot26_1_2.java b/bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/BukkitVersionHelperSpigot26_1_2.java new file mode 100644 index 000000000..8188ef197 --- /dev/null +++ b/bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/BukkitVersionHelperSpigot26_1_2.java @@ -0,0 +1,557 @@ +package org.dynmap.bukkit.helper.v26_1_2; + +import org.bukkit.*; +import org.bukkit.entity.Player; +import org.dynmap.DynmapChunk; +import org.dynmap.Log; +import org.dynmap.bukkit.helper.BukkitMaterial; +import org.dynmap.bukkit.helper.BukkitVersionHelper; +import org.dynmap.bukkit.helper.BukkitWorld; +import org.dynmap.bukkit.helper.BukkitVersionHelperGeneric.TexturesPayload; +import org.dynmap.renderer.DynmapBlockState; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.Polygon; + +import com.google.common.collect.Iterables; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; + +import net.minecraft.core.HolderLookup; +import net.minecraft.core.IdMapper; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.ByteArrayTag; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.FloatTag; +import net.minecraft.nbt.IntArrayTag; +import net.minecraft.nbt.IntTag; +import net.minecraft.nbt.LongTag; +import net.minecraft.nbt.ShortTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.resources.Identifier; +import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.status.ChunkStatus; + +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + + +/** + * Helper for isolation of bukkit version specific issues + */ +public class BukkitVersionHelperSpigot26_1_2 extends BukkitVersionHelper { + + // CraftBukkit class references (loaded via reflection for Paper/Spigot compatibility) + private static Class craftWorldClass; + private static Class craftChunkClass; + private static Class craftPlayerClass; + private static Class craftServerClass; + private static Method craftWorldGetHandle; + private static Method craftWorldGetMinHeight; + private static Method craftChunkGetHandle; + private static Method craftPlayerGetProfile; + private static Method craftServerGetServer; + private static boolean initialized = false; + + private static void initCraftBukkitClasses() { + if (initialized) return; + initialized = true; + + // Try Paper's unversioned packages first, then fall back to Spigot's versioned packages. + // MC 26.1.x is fully unobfuscated: Paper drops versioned packages and Spigot's R-series + // continues from 1.21.11 (R7). The exact suffix is unconfirmed, so we try several. + String[] packagePrefixes = { + "org.bukkit.craftbukkit", // Paper 1.20.5+ / Spigot 26.1+ (unversioned) + "org.bukkit.craftbukkit.v1_21_R8", // Spigot 26.1.x (next R after 1.21.11) + "org.bukkit.craftbukkit.v26_1_R1" // Possible new year-based naming + }; + + for (String prefix : packagePrefixes) { + try { + craftWorldClass = Class.forName(prefix + ".CraftWorld"); + craftChunkClass = Class.forName(prefix + ".CraftChunk"); + craftPlayerClass = Class.forName(prefix + ".entity.CraftPlayer"); + craftServerClass = Class.forName(prefix + ".CraftServer"); + + // Get methods + craftWorldGetHandle = craftWorldClass.getMethod("getHandle"); + craftWorldGetMinHeight = craftWorldClass.getMethod("getMinHeight"); + craftChunkGetHandle = craftChunkClass.getMethod("getHandle", ChunkStatus.class); + craftPlayerGetProfile = craftPlayerClass.getMethod("getProfile"); + craftServerGetServer = craftServerClass.getMethod("getServer"); + + Log.info("[Dynmap] Using CraftBukkit package: " + prefix); + return; + } catch (ClassNotFoundException | NoSuchMethodException e) { + // Try next prefix + } + } + Log.severe("[Dynmap] Failed to find CraftBukkit classes!"); + } + + @Override + public boolean isUnsafeAsync() { + return false; + } + + /** + * Get block short name list + */ + @Override + public String[] getBlockNames() { + IdMapper bsids = Block.BLOCK_STATE_REGISTRY; + Block baseb = null; + Iterator iter = bsids.iterator(); + ArrayList names = new ArrayList(); + while (iter.hasNext()) { + BlockState bs = iter.next(); + Block b = bs.getBlock(); + // If this is new block vs last, it's the base block state + if (b != baseb) { + baseb = b; + continue; + } + Identifier id = BuiltInRegistries.BLOCK.getKey(b); + String bn = id.toString(); + if (bn != null) { + names.add(bn); + Log.info("block=" + bn); + } + } + return names.toArray(new String[0]); + } + + private static Object biomeRegistry = null; + private static java.lang.reflect.Method getKeyMethod = null; + private static java.lang.reflect.Method getIdMethod = null; + + private static Object getBiomeReg() { + if (biomeRegistry == null) { + biomeRegistry = MinecraftServer.getServer().registryAccess().lookup(Registries.BIOME).orElseThrow(); + // Cache reflection methods + try { + getKeyMethod = biomeRegistry.getClass().getMethod("getKey", Object.class); + getIdMethod = biomeRegistry.getClass().getMethod("getId", Object.class); + } catch (Exception e) { + Log.severe("Failed to get biome registry methods: " + e.getMessage()); + } + } + return biomeRegistry; + } + + @SuppressWarnings("unchecked") + private static Iterator getBiomeIterator() { + return ((Iterable) getBiomeReg()).iterator(); + } + + private static int getBiomeId(Biome biome) { + try { + getBiomeReg(); // ensure methods are cached + return (Integer) getIdMethod.invoke(biomeRegistry, biome); + } catch (Exception e) { + return -1; + } + } + + private static Identifier getBiomeKey(Biome biome) { + try { + getBiomeReg(); // ensure methods are cached + return (Identifier) getKeyMethod.invoke(biomeRegistry, biome); + } catch (Exception e) { + Log.warning("Failed to get biome key: " + e.getMessage()); + return null; + } + } + + private Object[] biomelist; + /** + * Get list of defined biomebase objects + */ + @Override + public Object[] getBiomeBaseList() { + if (biomelist == null) { + biomelist = new Biome[256]; + Iterator iter = getBiomeIterator(); + while (iter.hasNext()) { + Biome b = iter.next(); + int bidx = getBiomeId(b); + if (bidx >= biomelist.length) { + biomelist = Arrays.copyOf(biomelist, bidx + biomelist.length); + } + biomelist[bidx] = b; + } + } + return biomelist; + } + + /** Get ID from biomebase */ + @Override + public int getBiomeBaseID(Object bb) { + return getBiomeId((Biome)bb); + } + + public static IdentityHashMap dataToState; + + /** + * Initialize block states (org.dynmap.blockstate.DynmapBlockState) + */ + @Override + public void initializeBlockStates() { + dataToState = new IdentityHashMap(); + HashMap lastBlockState = new HashMap(); + IdMapper bsids = Block.BLOCK_STATE_REGISTRY; + Block baseb = null; + Iterator iter = bsids.iterator(); + ArrayList names = new ArrayList(); + + // Loop through block data states + DynmapBlockState.Builder bld = new DynmapBlockState.Builder(); + while (iter.hasNext()) { + BlockState bd = iter.next(); + Block b = bd.getBlock(); + Identifier id = BuiltInRegistries.BLOCK.getKey(b); + String bname = id.toString(); + DynmapBlockState lastbs = lastBlockState.get(bname); // See if we have seen this one + int idx = 0; + if (lastbs != null) { // Yes + idx = lastbs.getStateCount(); // Get number of states so far, since this is next + } + // Build state name + String sb = ""; + String fname = bd.toString(); + int off1 = fname.indexOf('['); + if (off1 >= 0) { + int off2 = fname.indexOf(']'); + sb = fname.substring(off1+1, off2); + } + int lightAtten = bd.getLightBlock(); + // Fill in base attributes + bld.setBaseState(lastbs).setStateIndex(idx).setBlockName(bname).setStateName(sb).setAttenuatesLight(lightAtten); + if (bd.isSolid()) { bld.setSolid(); } + if (bd.isAir()) { bld.setAir(); } + if (bd.is(BlockTags.OVERWORLD_NATURAL_LOGS)) { bld.setLog(); } + if (bd.is(BlockTags.LEAVES)) { bld.setLeaves(); } + if (!bd.getFluidState().isEmpty() && !(bd.getBlock() instanceof LiquidBlock)) { + bld.setWaterlogged(); + } + DynmapBlockState dbs = bld.build(); // Build state + + dataToState.put(bd, dbs); + lastBlockState.put(bname, (lastbs == null) ? dbs : lastbs); + Log.verboseinfo("blk=" + bname + ", idx=" + idx + ", state=" + sb + ", waterlogged=" + dbs.isWaterlogged()); + } + } + /** + * Create chunk cache for given chunks of given world + * @param dw - world + * @param chunks - chunk list + * @return cache + */ + @Override + public MapChunkCache getChunkCache(BukkitWorld dw, List chunks) { + MapChunkCache26_1_2 c = new MapChunkCache26_1_2(gencache); + c.setChunks(dw, chunks); + return c; + } + + /** + * Get biome base water multiplier + */ + @Override + public int getBiomeBaseWaterMult(Object bb) { + Biome biome = (Biome) bb; + return biome.getWaterColor(); + } + + /** Get temperature from biomebase */ + @Override + public float getBiomeBaseTemperature(Object bb) { + return ((Biome)bb).getBaseTemperature(); + } + + /** Get humidity from biomebase */ + @Override + public float getBiomeBaseHumidity(Object bb) { + String vals = ((Biome)bb).climateSettings.toString(); + float humidity = 0.5F; + int idx = vals.indexOf("downfall="); + if (idx >= 0) { + humidity = Float.parseFloat(vals.substring(idx+9, vals.indexOf(']', idx))); + } + return humidity; + } + + @Override + public Polygon getWorldBorder(World world) { + Polygon p = null; + WorldBorder wb = world.getWorldBorder(); + if (wb != null) { + Location c = wb.getCenter(); + double size = wb.getSize(); + if ((size > 1) && (size < 1E7)) { + size = size / 2; + p = new Polygon(); + p.addVertex(c.getX()-size, c.getZ()-size); + p.addVertex(c.getX()+size, c.getZ()-size); + p.addVertex(c.getX()+size, c.getZ()+size); + p.addVertex(c.getX()-size, c.getZ()+size); + } + } + return p; + } + // Send title/subtitle to user + public void sendTitleText(Player p, String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTIcks) { + if (p != null) { + p.sendTitle(title, subtitle, fadeInTicks, stayTicks, fadeOutTIcks); + } + } + + /** + * Get material map by block ID + */ + @Override + public BukkitMaterial[] getMaterialList() { + return new BukkitMaterial[4096]; // Not used + } + + @Override + public void unloadChunkNoSave(World w, Chunk c, int cx, int cz) { + Log.severe("unloadChunkNoSave not implemented"); + } + + private String[] biomenames; + @Override + public String[] getBiomeNames() { + if (biomenames == null) { + biomenames = new String[256]; + Iterator iter = getBiomeIterator(); + while (iter.hasNext()) { + Biome b = iter.next(); + int bidx = getBiomeId(b); + if (bidx >= biomenames.length) { + biomenames = Arrays.copyOf(biomenames, bidx + biomenames.length); + } + biomenames[bidx] = b.toString(); + } + } + return biomenames; + } + + @Override + public String getStateStringByCombinedId(int blkid, int meta) { + Log.severe("getStateStringByCombinedId not implemented"); + return null; + } + @Override + /** Get ID string from biomebase */ + public String getBiomeBaseIDString(Object bb) { + Identifier key = getBiomeKey((Biome)bb); + return key != null ? key.getPath() : ""; + } + @Override + public String getBiomeBaseResourceLocsation(Object bb) { + Identifier key = getBiomeKey((Biome)bb); + return key != null ? key.toString() : ""; + } + + @Override + public Object getUnloadQueue(World world) { + Log.warning("getUnloadQueue not implemented yet"); + return null; + } + + @Override + public boolean isInUnloadQueue(Object unloadqueue, int x, int z) { + Log.warning("isInUnloadQueue not implemented yet"); + return false; + } + + @Override + public Object[] getBiomeBaseFromSnapshot(ChunkSnapshot css) { + Log.warning("getBiomeBaseFromSnapshot not implemented yet"); + return new Object[256]; + } + + @Override + public long getInhabitedTicks(Chunk c) { + initCraftBukkitClasses(); + try { + Object handle = craftChunkGetHandle.invoke(c, ChunkStatus.FULL); + return ((LevelChunk)handle).getInhabitedTime(); + } catch (Exception e) { + Log.warning("getInhabitedTicks failed: " + e.getMessage()); + return 0; + } + } + + @Override + public Map getTileEntitiesForChunk(Chunk c) { + initCraftBukkitClasses(); + try { + Object handle = craftChunkGetHandle.invoke(c, ChunkStatus.FULL); + return ((LevelChunk)handle).getBlockEntities(); + } catch (Exception e) { + Log.warning("getTileEntitiesForChunk failed: " + e.getMessage()); + return new HashMap<>(); + } + } + + @Override + public int getTileEntityX(Object te) { + BlockEntity blockent = (BlockEntity) te; + return blockent.getBlockPos().getX(); + } + + @Override + public int getTileEntityY(Object te) { + BlockEntity blockent = (BlockEntity) te; + return blockent.getBlockPos().getY(); + } + + @Override + public int getTileEntityZ(Object te) { + BlockEntity blockent = (BlockEntity) te; + return blockent.getBlockPos().getZ(); + } + + @Override + public Object readTileEntityNBT(Object te, World w) { + initCraftBukkitClasses(); + try { + BlockEntity blockent = (BlockEntity) te; + Object handle = craftWorldGetHandle.invoke(w); + Method registryAccess = handle.getClass().getMethod("registryAccess"); + HolderLookup.Provider registry = (HolderLookup.Provider) registryAccess.invoke(handle); + return blockent.saveCustomOnly(registry); + } catch (Exception e) { + Log.warning("readTileEntityNBT failed: " + e.getMessage()); + return null; + } + } + + @Override + public Object getFieldValue(Object nbt, String field) { + CompoundTag rec = (CompoundTag) nbt; + Tag val = rec.get(field); + if(val == null) return null; + if(val instanceof ByteTag) { + return ((ByteTag)val).byteValue(); + } + else if(val instanceof ShortTag) { + return ((ShortTag)val).shortValue(); + } + else if(val instanceof IntTag) { + return ((IntTag)val).intValue(); + } + else if(val instanceof LongTag) { + return ((LongTag)val).longValue(); + } + else if(val instanceof FloatTag) { + return ((FloatTag)val).floatValue(); + } + else if(val instanceof DoubleTag) { + return ((DoubleTag)val).doubleValue(); + } + else if(val instanceof ByteArrayTag) { + return ((ByteArrayTag)val).getAsByteArray(); + } + else if(val instanceof StringTag) { + return val.asString().orElse(""); + } + else if(val instanceof IntArrayTag) { + return ((IntArrayTag)val).getAsIntArray(); + } + return null; + } + + @Override + public Player[] getOnlinePlayers() { + Collection p = Bukkit.getServer().getOnlinePlayers(); + return p.toArray(new Player[0]); + } + + @Override + public double getHealth(Player p) { + return p.getHealth(); + } + + private static final Gson gson = new GsonBuilder().create(); + + /** + * Get skin URL for player + * @param player + */ + @Override + public String getSkinURL(Player player) { + initCraftBukkitClasses(); + String url = null; + try { + GameProfile profile = (GameProfile) craftPlayerGetProfile.invoke(player); + if (profile != null) { + PropertyMap pm = profile.properties(); + if (pm != null) { + Collection txt = pm.get("textures"); + Property textureProperty = Iterables.getFirst(pm.get("textures"), null); + if (textureProperty != null) { + String val = textureProperty.value(); + if (val != null) { + TexturesPayload result = null; + try { + String json = new String(Base64.getDecoder().decode(val), StandardCharsets.UTF_8); + result = gson.fromJson(json, TexturesPayload.class); + } catch (JsonParseException e) { + } catch (IllegalArgumentException x) { + Log.warning("Malformed response from skin URL check: " + val); + } + if ((result != null) && (result.textures != null) && (result.textures.containsKey("SKIN"))) { + url = result.textures.get("SKIN").url; + } + } + } + } + } + } catch (Exception e) { + Log.warning("getSkinURL failed: " + e.getMessage()); + } + return url; + } + // Get minY for world + @Override + public int getWorldMinY(World w) { + initCraftBukkitClasses(); + try { + return (Integer) craftWorldGetMinHeight.invoke(w); + } catch (Exception e) { + Log.warning("getWorldMinY failed: " + e.getMessage()); + return 0; + } + } + @Override + public boolean useGenericCache() { + return true; + } + +} diff --git a/bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/MapChunkCache26_1_2.java b/bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/MapChunkCache26_1_2.java new file mode 100644 index 000000000..b63d5b6f8 --- /dev/null +++ b/bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/MapChunkCache26_1_2.java @@ -0,0 +1,190 @@ +package org.dynmap.bukkit.helper.v26_1_2; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSpecialEffects; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.storage.SerializableChunkData; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.dynmap.DynmapChunk; +import org.dynmap.Log; +import org.dynmap.bukkit.helper.BukkitWorld; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.chunk.GenericChunk; +import org.dynmap.common.chunk.GenericChunkCache; +import org.dynmap.common.chunk.GenericMapChunkCache; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +/** + * Container for managing chunks - dependent upon using chunk snapshots, since rendering is off server thread + */ +public class MapChunkCache26_1_2 extends GenericMapChunkCache { + private World w; + + // CraftBukkit reflection support for Paper/Spigot compatibility + private static Class craftWorldClass; + private static Class craftServerClass; + private static Method craftWorldGetHandle; + private static Method craftWorldIsChunkLoaded; + private static Method craftServerGetServer; + private static boolean initialized = false; + + private static void initReflection() { + if (initialized) return; + initialized = true; + + // Try Paper's unversioned packages first, then fall back to Spigot's versioned packages. + // MC 26.1.x is fully unobfuscated; we try several prefix patterns. + String[] packagePrefixes = { + "org.bukkit.craftbukkit", // Paper 1.20.5+ / Spigot 26.1+ (unversioned) + "org.bukkit.craftbukkit.v1_21_R8", // Spigot 26.1.x (next R after 1.21.11) + "org.bukkit.craftbukkit.v26_1_R1" // Possible new year-based naming + }; + + for (String prefix : packagePrefixes) { + try { + craftWorldClass = Class.forName(prefix + ".CraftWorld"); + craftServerClass = Class.forName(prefix + ".CraftServer"); + + // Get methods + craftWorldGetHandle = craftWorldClass.getMethod("getHandle"); + craftWorldIsChunkLoaded = craftWorldClass.getMethod("isChunkLoaded", int.class, int.class); + craftServerGetServer = craftServerClass.getMethod("getServer"); + + Log.info("[Dynmap] MapChunkCache using CraftBukkit package: " + prefix); + return; + } catch (ClassNotFoundException | NoSuchMethodException e) { + // Try next prefix + } + } + Log.severe("[Dynmap] MapChunkCache failed to find CraftBukkit classes!"); + } + + private ServerLevel getServerLevel(World world) { + initReflection(); + try { + return (ServerLevel) craftWorldGetHandle.invoke(world); + } catch (Exception e) { + Log.severe("Failed to get ServerLevel: " + e.getMessage()); + return null; + } + } + + private boolean isChunkLoaded(World world, int x, int z) { + initReflection(); + try { + return (Boolean) craftWorldIsChunkLoaded.invoke(world, x, z); + } catch (Exception e) { + Log.warning("Failed to check chunk loaded status: " + e.getMessage()); + return false; + } + } + + private MinecraftServer getMinecraftServer() { + initReflection(); + try { + return (MinecraftServer) craftServerGetServer.invoke(Bukkit.getServer()); + } catch (Exception e) { + Log.severe("Failed to get MinecraftServer: " + e.getMessage()); + return null; + } + } + + /** + * Construct empty cache + */ + public MapChunkCache26_1_2(GenericChunkCache cc) { + super(cc); + } + + @Override + protected Supplier getLoadedChunkAsync(DynmapChunk chunk) { + ServerLevel serverLevel = getServerLevel(w); + MinecraftServer server = getMinecraftServer(); + if (serverLevel == null || server == null) { + return () -> null; + } + + CompletableFuture> chunkData = CompletableFuture.supplyAsync(() -> { + LevelChunk c = serverLevel.getChunkIfLoaded(chunk.x, chunk.z); + if (c == null || !c.loaded) { + return Optional.empty(); + } + return Optional.of(SerializableChunkData.copyOf(serverLevel, c)); + }, server); + return () -> chunkData.join().map(SerializableChunkData::write).map(NBT.NBTCompound::new).map(this::parseChunkFromNBT).orElse(null); + } + + protected GenericChunk getLoadedChunk(DynmapChunk chunk) { + ServerLevel serverLevel = getServerLevel(w); + if (serverLevel == null) return null; + + if (!isChunkLoaded(w, chunk.x, chunk.z)) return null; + LevelChunk c = serverLevel.getChunkIfLoaded(chunk.x, chunk.z); + if (c == null || !c.loaded) return null; + SerializableChunkData chunkData = SerializableChunkData.copyOf(serverLevel, c); + CompoundTag nbt = chunkData.write(); + return nbt != null ? parseChunkFromNBT(new NBT.NBTCompound(nbt)) : null; + } + + @Override + protected Supplier loadChunkAsync(DynmapChunk chunk) { + ServerLevel serverLevel = getServerLevel(w); + if (serverLevel == null) { + return () -> null; + } + + CompletableFuture> genericChunk = serverLevel.getChunkSource().chunkMap.read(new ChunkPos(chunk.x, chunk.z)); + return () -> genericChunk.join().map(NBT.NBTCompound::new).map(this::parseChunkFromNBT).orElse(null); + } + + protected GenericChunk loadChunk(DynmapChunk chunk) { + ServerLevel serverLevel = getServerLevel(w); + if (serverLevel == null) return null; + + CompoundTag nbt = null; + ChunkPos cc = new ChunkPos(chunk.x, chunk.z); + GenericChunk gc = null; + try { + nbt = serverLevel + .getChunkSource() + .chunkMap + .read(cc) + .join().get(); + } catch (CancellationException cx) { + } catch (NoSuchElementException snex) { + } + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + return gc; + } + + public void setChunks(BukkitWorld dw, List chunks) { + this.w = dw.getWorld(); + super.setChunks(dw, chunks); + } + + @Override + public int getFoliageColor(BiomeMap bm, int[] colormap, int x, int z) { + return bm.getBiomeObject().map(Biome::getSpecialEffects).flatMap(BiomeSpecialEffects::foliageColorOverride).orElse(colormap[bm.biomeLookup()]); + } + + @Override + public int getGrassColor(BiomeMap bm, int[] colormap, int x, int z) { + BiomeSpecialEffects effects = bm.getBiomeObject().map(Biome::getSpecialEffects).orElse(null); + if (effects == null) return colormap[bm.biomeLookup()]; + return effects.grassColorModifier().modifyColor(x, z, effects.grassColorOverride().orElse(colormap[bm.biomeLookup()])); + } +} diff --git a/bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/NBT.java b/bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/NBT.java new file mode 100644 index 000000000..fe2b7ea22 --- /dev/null +++ b/bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/NBT.java @@ -0,0 +1,143 @@ +package org.dynmap.bukkit.helper.v26_1_2; + +import org.dynmap.common.chunk.GenericBitStorage; +import org.dynmap.common.chunk.GenericNBTCompound; +import org.dynmap.common.chunk.GenericNBTList; + +import java.util.Optional; +import java.util.Set; +import net.minecraft.nbt.Tag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.util.SimpleBitStorage; + +public class NBT { + + public static class NBTCompound implements GenericNBTCompound { + private final CompoundTag obj; + public NBTCompound(CompoundTag t) { + this.obj = t; + } + @Override + public Set getAllKeys() { + return obj.keySet(); + } + @Override + public boolean contains(String s) { + return obj.contains(s); + } + @Override + public boolean contains(String s, int i) { + Tag base = obj.get(s); + if (base == null) + return false; + byte type = base.getId(); + if (type == i) + return true; + else if (i != TAG_ANY_NUMERIC) + return false; + return type == TAG_BYTE || type == TAG_SHORT || type == TAG_INT || type == TAG_LONG || type == TAG_FLOAT + || type == TAG_DOUBLE; + } + @Override + public byte getByte(String s) { + return obj.getByteOr(s, (byte)0); + } + @Override + public short getShort(String s) { + return obj.getShortOr(s, (short)0); + } + @Override + public int getInt(String s) { + return obj.getIntOr(s, 0); + } + @Override + public long getLong(String s) { + return obj.getLongOr(s, 0L); + } + @Override + public float getFloat(String s) { + return obj.getFloatOr(s, 0.0f); + } + @Override + public double getDouble(String s) { + return obj.getDoubleOr(s, 0.0); + } + @Override + public String getString(String s) { + return obj.getStringOr(s, ""); + } + @Override + public byte[] getByteArray(String s) { + Optional byteArr = obj.getByteArray(s); + return byteArr.orElseGet(() -> new byte[0]); + } + @Override + public int[] getIntArray(String s) { + Optional intArr = obj.getIntArray(s); + return intArr.orElseGet(() -> new int[0]); + } + @Override + public long[] getLongArray(String s) { + Optional longArr = obj.getLongArray(s); + return longArr.orElseGet(() -> new long[0]); + } + @Override + public GenericNBTCompound getCompound(String s) { + return new NBTCompound(obj.getCompoundOrEmpty(s)); + } + @Override + public GenericNBTList getList(String s, int i) { + return new NBTList(obj.getListOrEmpty(s)); + } + @Override + public boolean getBoolean(String s) { + return getByte(s) != 0; + } + @Override + public String getAsString(String s) { + Tag tag = obj.get(s); + return tag != null ? tag.asString().orElse("") : ""; + } + @Override + public GenericBitStorage makeBitStorage(int bits, int count, long[] data) { + return new OurBitStorage(bits, count, data); + } + public String toString() { + return obj.toString(); + } + } + + public static class NBTList implements GenericNBTList { + private final ListTag obj; + public NBTList(ListTag t) { + obj = t; + } + @Override + public int size() { + return obj.size(); + } + @Override + public String getString(int idx) { + return obj.getStringOr(idx, ""); + } + @Override + public GenericNBTCompound getCompound(int idx) { + return new NBTCompound(obj.getCompoundOrEmpty(idx)); + } + public String toString() { + return obj.toString(); + } + } + + public static class OurBitStorage implements GenericBitStorage { + private final SimpleBitStorage bs; + public OurBitStorage(int bits, int count, long[] data) { + bs = new SimpleBitStorage(bits, count, data); + } + @Override + public int get(int idx) { + return bs.get(idx); + } + } +} diff --git a/settings.gradle b/settings.gradle index 4b7ae603c..abfcc349e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,6 +33,7 @@ include ':bukkit-helper-121-5' include ':bukkit-helper-121-6' include ':bukkit-helper-121-10' include ':bukkit-helper-121-11' +include ':bukkit-helper-26-1-2' include ':bukkit-helper' include ':dynmap-api' include ':DynmapCore' @@ -88,6 +89,7 @@ project(':bukkit-helper-121-5').projectDir = "$rootDir/bukkit-helper-121-5" as F project(':bukkit-helper-121-6').projectDir = "$rootDir/bukkit-helper-121-6" as File project(':bukkit-helper-121-10').projectDir = "$rootDir/bukkit-helper-121-10" as File project(':bukkit-helper-121-11').projectDir = "$rootDir/bukkit-helper-121-11" as File +project(':bukkit-helper-26-1-2').projectDir = "$rootDir/bukkit-helper-26-1-2" as File project(':bukkit-helper').projectDir = "$rootDir/bukkit-helper" as File project(':dynmap-api').projectDir = "$rootDir/dynmap-api" as File project(':DynmapCore').projectDir = "$rootDir/DynmapCore" as File diff --git a/spigot/build.gradle b/spigot/build.gradle index 9fa548e1d..36cd97904 100644 --- a/spigot/build.gradle +++ b/spigot/build.gradle @@ -109,6 +109,9 @@ dependencies { implementation(project(':bukkit-helper-121-11')) { transitive = false } + implementation(project(':bukkit-helper-26-1-2')) { + transitive = false + } } processResources { @@ -156,6 +159,7 @@ shadowJar { include(dependency(':bukkit-helper-121-6')) include(dependency(':bukkit-helper-121-10')) include(dependency(':bukkit-helper-121-11')) + include(dependency(':bukkit-helper-26-1-2')) } relocate('org.bstats', 'org.dynmap.bstats') destinationDirectory = file '../target' diff --git a/spigot/src/main/java/org/dynmap/bukkit/Helper.java b/spigot/src/main/java/org/dynmap/bukkit/Helper.java index 89441f999..dac61a80c 100644 --- a/spigot/src/main/java/org/dynmap/bukkit/Helper.java +++ b/spigot/src/main/java/org/dynmap/bukkit/Helper.java @@ -61,6 +61,9 @@ else if (v.contains("(MC: 1.21.10)")) { else if (v.contains("(MC: 1.21.")) { BukkitVersionHelper.helper = loadVersionHelper("org.dynmap.bukkit.helper.v121_11.BukkitVersionHelperSpigot121_11"); } + else if (v.contains("(MC: 26.")) { + BukkitVersionHelper.helper = loadVersionHelper("org.dynmap.bukkit.helper.v26_1_2.BukkitVersionHelperSpigot26_1_2"); + } else if (v.contains("(MC: 1.20)") || v.contains("(MC: 1.20.1)")) { BukkitVersionHelper.helper = loadVersionHelper("org.dynmap.bukkit.helper.v120.BukkitVersionHelperSpigot120"); } From 27ed865f46ae6541d211c9732d63c690f0450e12 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 16:59:10 +0000 Subject: [PATCH 02/11] ci(auto-beta-release): also open a draft PR on first push --- .github/workflows/auto-beta-release.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/auto-beta-release.yml b/.github/workflows/auto-beta-release.yml index 08bc1c345..ac2c9867c 100644 --- a/.github/workflows/auto-beta-release.yml +++ b/.github/workflows/auto-beta-release.yml @@ -2,6 +2,8 @@ name: Auto Beta Release # Each push to the upgrade branch produces a new release tagged # 26.1.2-beta., where N auto-increments from the highest existing tag. +# Also opens a PR back to the default branch on first push if one +# doesn't exist yet. on: push: @@ -14,6 +16,7 @@ concurrency: permissions: contents: write + pull-requests: write jobs: build-and-release: @@ -25,6 +28,25 @@ jobs: with: fetch-depth: 0 + - name: Ensure pull request exists + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEAD_BRANCH: ${{ github.ref_name }} + BASE_BRANCH: ${{ github.event.repository.default_branch }} + run: | + set -euo pipefail + existing=$(gh pr list --head "${HEAD_BRANCH}" --base "${BASE_BRANCH}" --state open --json number --jq 'length') + if [ "${existing}" = "0" ]; then + gh pr create \ + --base "${BASE_BRANCH}" \ + --head "${HEAD_BRANCH}" \ + --title "feat: add Minecraft 26.1.2 support for Spigot/Paper" \ + --body "$(printf '## Summary\n- Adds bukkit-helper-26-1-2 module modeled on 1.21.11 (compiled with Java 25, requires Spigot 26.1.2-R0.1-SNAPSHOT).\n- Routes (MC: 26.*) servers to the new helper in spigot/Helper.java.\n- Wires the new module into settings.gradle and spigot/build.gradle (impl + shadowJar include).\n- ci(release): bumps to JDK 25 and adds BuildTools 26.1.2.\n- ci(auto-beta-release): on each push, computes next 26.1.2-beta. tag, builds, and publishes a GitHub Release with the spigot JAR.\n\n## Test plan\n- [ ] BuildTools resolves Spigot 26.1.2-R0.1-SNAPSHOT into the local Maven repository.\n- [ ] Gradle :spigot:build succeeds on JDK 25.\n- [ ] Auto-beta-release workflow tags 26.1.2-beta.1 and uploads the spigot jar.\n- [ ] Manual smoke test on a Paper/Spigot 26.1.2 server.')" \ + --draft || true + else + echo "PR already exists for ${HEAD_BRANCH} -> ${BASE_BRANCH}; skipping." + fi + - name: Compute next beta version id: version run: | From ea1eecb37c5eb38462add981784e2fb605475ea6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 17:01:38 +0000 Subject: [PATCH 03/11] fix: align MC 26.1.2 build with Java 25 / unobfuscated mappings - ASM 9.8 in root build.gradle so Shadow can read Java 25 bytecode. - bukkit-helper-26-1-2: - declare Java 25 toolchain explicitly - drop :remapped-mojang classifier (MC 26.1+ ships mojang-mapped) - use getLightDampening() (was getLightBlock() pre-26.1) - ci(release): install both JDK 21 (Spigot 1.21.x BuildTools, Gradle/ Shadow runtime) and JDK 25 (BuildTools for 26.1.2 + toolchain), pass both to Gradle via -Porg.gradle.java.installations.paths. --- .github/workflows/release.yml | 23 +++++++++++++++---- build.gradle | 4 ++-- bukkit-helper-26-1-2/build.gradle | 9 +++++++- .../BukkitVersionHelperSpigot26_1_2.java | 2 +- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4ad70e3d5..b10f84e4a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' + - name: Set up JDK 25 uses: actions/setup-java@v5 with: @@ -20,13 +26,18 @@ jobs: cache: gradle - name: Build Spigot with BuildTools + env: + JAVA21_HOME: ${{ env.JAVA_HOME_21_X64 }} + JAVA25_HOME: ${{ env.JAVA_HOME_25_X64 }} run: | mkdir -p buildtools cd buildtools wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar - java -jar BuildTools.jar --rev 1.21.10 --remapped - java -jar BuildTools.jar --rev 1.21.11 --remapped - java -jar BuildTools.jar --rev 26.1.2 --remapped + # MC 1.21.10 / 1.21.11 build with Java 21 + "$JAVA21_HOME/bin/java" -jar BuildTools.jar --rev 1.21.10 --remapped + "$JAVA21_HOME/bin/java" -jar BuildTools.jar --rev 1.21.11 --remapped + # MC 26.1.2 requires Java 25 + "$JAVA25_HOME/bin/java" -jar BuildTools.jar --rev 26.1.2 --remapped cd .. timeout-minutes: 60 @@ -34,7 +45,11 @@ jobs: env: USERNAME: ${{ secrets.USERNAME }} TOKEN: ${{ secrets.TOKEN }} - run: ./gradlew :spigot:build + # Run Gradle on JDK 21 (compatible with shadow plugin), use JDK 25 via toolchain + JAVA_HOME: ${{ env.JAVA_HOME_21_X64 }} + run: | + ./gradlew :spigot:build \ + -Porg.gradle.java.installations.paths="${JAVA_HOME_25_X64},${JAVA_HOME_21_X64}" - name: Find JAR file id: find_jar diff --git a/build.gradle b/build.gradle index 80dda0a97..c90117a3b 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ buildscript { configurations.all { resolutionStrategy { - force("org.ow2.asm:asm:9.5") - force("org.ow2.asm:asm-commons:9.5") + force("org.ow2.asm:asm:9.8") + force("org.ow2.asm:asm-commons:9.8") } } } diff --git a/bukkit-helper-26-1-2/build.gradle b/bukkit-helper-26-1-2/build.gradle index 207d6a796..686682288 100644 --- a/bukkit-helper-26-1-2/build.gradle +++ b/bukkit-helper-26-1-2/build.gradle @@ -9,12 +9,19 @@ description = 'bukkit-helper-26.1.2' // MC 26.1+ requires Java 25. sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(25) // Need this here so eclipse task generates correctly. +java { + toolchain { + languageVersion = JavaLanguageVersion.of(25) + } +} + dependencies { implementation project(':bukkit-helper') implementation project(':dynmap-api') implementation project(path: ':DynmapCore', configuration: 'shadow') compileOnly 'org.spigotmc:spigot-api:26.1.2-R0.1-SNAPSHOT' - compileOnly ('org.spigotmc:spigot:26.1.2-R0.1-SNAPSHOT:remapped-mojang') { + // MC 26.1+ ships with mojang-mapped names by default (no `remapped-mojang` classifier needed). + compileOnly ('org.spigotmc:spigot:26.1.2-R0.1-SNAPSHOT') { exclude group: "com.mojang", module: "jtracy" } } diff --git a/bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/BukkitVersionHelperSpigot26_1_2.java b/bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/BukkitVersionHelperSpigot26_1_2.java index 8188ef197..b953af59a 100644 --- a/bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/BukkitVersionHelperSpigot26_1_2.java +++ b/bukkit-helper-26-1-2/src/main/java/org/dynmap/bukkit/helper/v26_1_2/BukkitVersionHelperSpigot26_1_2.java @@ -247,7 +247,7 @@ public void initializeBlockStates() { int off2 = fname.indexOf(']'); sb = fname.substring(off1+1, off2); } - int lightAtten = bd.getLightBlock(); + int lightAtten = bd.getLightDampening(); // Fill in base attributes bld.setBaseState(lastbs).setStateIndex(idx).setBlockName(bname).setStateName(sb).setAttenuatesLight(lightAtten); if (bd.isSolid()) { bld.setSolid(); } From b28232a0da866fa3a95ee08c4c55aafc98a636b1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 17:03:33 +0000 Subject: [PATCH 04/11] ci(auto-beta-release): run Gradle on JDK 21 with JDK 25 toolchain Gradle 8.12 doesn't officially support JDK 25 as the Gradle runtime. Install both JDK 21 and JDK 25, run Gradle on JDK 21, and expose JDK 25 to the new bukkit-helper-26-1-2 module via the toolchain auto- detection paths. --- .github/workflows/auto-beta-release.yml | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-beta-release.yml b/.github/workflows/auto-beta-release.yml index ac2c9867c..4c24f6ae0 100644 --- a/.github/workflows/auto-beta-release.yml +++ b/.github/workflows/auto-beta-release.yml @@ -67,6 +67,12 @@ jobs: echo "tag=${TAG}" >> "$GITHUB_OUTPUT" echo "Next tag: ${TAG}" + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' + - name: Set up JDK 25 uses: actions/setup-java@v5 with: @@ -75,13 +81,18 @@ jobs: cache: gradle - name: Build Spigot with BuildTools + env: + JAVA21_HOME: ${{ env.JAVA_HOME_21_X64 }} + JAVA25_HOME: ${{ env.JAVA_HOME_25_X64 }} run: | mkdir -p buildtools cd buildtools wget -q https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar - java -jar BuildTools.jar --rev 1.21.10 --remapped - java -jar BuildTools.jar --rev 1.21.11 --remapped - java -jar BuildTools.jar --rev 26.1.2 --remapped + # MC 1.21.10 / 1.21.11 build with Java 21 + "$JAVA21_HOME/bin/java" -jar BuildTools.jar --rev 1.21.10 --remapped + "$JAVA21_HOME/bin/java" -jar BuildTools.jar --rev 1.21.11 --remapped + # MC 26.1.2 requires Java 25 + "$JAVA25_HOME/bin/java" -jar BuildTools.jar --rev 26.1.2 --remapped cd .. timeout-minutes: 60 @@ -89,7 +100,11 @@ jobs: env: USERNAME: ${{ secrets.USERNAME }} TOKEN: ${{ secrets.TOKEN }} - run: ./gradlew :spigot:build + # Run Gradle on JDK 21 (compatible with Shadow plugin); use JDK 25 via toolchain. + JAVA_HOME: ${{ env.JAVA_HOME_21_X64 }} + run: | + ./gradlew :spigot:build \ + -Porg.gradle.java.installations.paths="${JAVA_HOME_25_X64},${JAVA_HOME_21_X64}" - name: Find JAR file id: find_jar From 2adbbf3f8647f17f3a70d5f88173eba980ef4bcb Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 17:17:26 +0000 Subject: [PATCH 05/11] ci(auto-beta-release): split BuildTools into per-version steps Surface which Spigot version (1.21.10, 1.21.11, or 26.1.2) is failing in BuildTools by running each in its own step with set -x and a maven repo listing for diagnostic. --- .github/workflows/auto-beta-release.yml | 40 +++++++++++++++++++------ 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/.github/workflows/auto-beta-release.yml b/.github/workflows/auto-beta-release.yml index 4c24f6ae0..4d97b35aa 100644 --- a/.github/workflows/auto-beta-release.yml +++ b/.github/workflows/auto-beta-release.yml @@ -80,21 +80,43 @@ jobs: distribution: 'temurin' cache: gradle - - name: Build Spigot with BuildTools - env: - JAVA21_HOME: ${{ env.JAVA_HOME_21_X64 }} - JAVA25_HOME: ${{ env.JAVA_HOME_25_X64 }} + - name: Download BuildTools.jar run: | + set -euxo pipefail mkdir -p buildtools cd buildtools - wget -q https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar - # MC 1.21.10 / 1.21.11 build with Java 21 + wget -nv https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar + ls -la + + - name: BuildTools Spigot 1.21.10 (Java 21) + working-directory: buildtools + env: + JAVA21_HOME: ${{ env.JAVA_HOME_21_X64 }} + run: | + set -euxo pipefail "$JAVA21_HOME/bin/java" -jar BuildTools.jar --rev 1.21.10 --remapped + ls -la "$HOME/.m2/repository/org/spigotmc/spigot/" || true + timeout-minutes: 30 + + - name: BuildTools Spigot 1.21.11 (Java 21) + working-directory: buildtools + env: + JAVA21_HOME: ${{ env.JAVA_HOME_21_X64 }} + run: | + set -euxo pipefail "$JAVA21_HOME/bin/java" -jar BuildTools.jar --rev 1.21.11 --remapped - # MC 26.1.2 requires Java 25 + ls -la "$HOME/.m2/repository/org/spigotmc/spigot/" || true + timeout-minutes: 30 + + - name: BuildTools Spigot 26.1.2 (Java 25) + working-directory: buildtools + env: + JAVA25_HOME: ${{ env.JAVA_HOME_25_X64 }} + run: | + set -euxo pipefail "$JAVA25_HOME/bin/java" -jar BuildTools.jar --rev 26.1.2 --remapped - cd .. - timeout-minutes: 60 + ls -la "$HOME/.m2/repository/org/spigotmc/spigot/" || true + timeout-minutes: 45 - name: Build with Gradle env: From e4e90dd1bccf67e41d3950922ca5e30fd5ea90e9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 17:30:35 +0000 Subject: [PATCH 06/11] ci: surface BuildTools/Gradle errors via step-summary and artifact --- .github/workflows/auto-beta-release.yml | 46 ++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/.github/workflows/auto-beta-release.yml b/.github/workflows/auto-beta-release.yml index 4d97b35aa..ec32d20cc 100644 --- a/.github/workflows/auto-beta-release.yml +++ b/.github/workflows/auto-beta-release.yml @@ -109,15 +109,41 @@ jobs: timeout-minutes: 30 - name: BuildTools Spigot 26.1.2 (Java 25) + id: bt2612 + continue-on-error: true working-directory: buildtools env: JAVA25_HOME: ${{ env.JAVA_HOME_25_X64 }} run: | - set -euxo pipefail - "$JAVA25_HOME/bin/java" -jar BuildTools.jar --rev 26.1.2 --remapped - ls -la "$HOME/.m2/repository/org/spigotmc/spigot/" || true + set -uxo pipefail + mkdir -p logs + "$JAVA25_HOME/bin/java" -jar BuildTools.jar --rev 26.1.2 --remapped 2>&1 | tee logs/buildtools-26.1.2.log + rc=${PIPESTATUS[0]} + { + echo "## BuildTools 26.1.2 exit=$rc" + echo + echo '```' + tail -n 80 logs/buildtools-26.1.2.log + echo '```' + echo + echo "## Maven repo for org.spigotmc" + echo '```' + ls -la "$HOME/.m2/repository/org/spigotmc/" 2>&1 || true + ls -la "$HOME/.m2/repository/org/spigotmc/spigot/" 2>&1 || true + ls -la "$HOME/.m2/repository/org/spigotmc/spigot-api/" 2>&1 || true + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + exit $rc timeout-minutes: 45 + - name: Upload BuildTools 26.1.2 log + if: always() + uses: actions/upload-artifact@v4 + with: + name: buildtools-26.1.2-log + path: buildtools/logs/buildtools-26.1.2.log + if-no-files-found: ignore + - name: Build with Gradle env: USERNAME: ${{ secrets.USERNAME }} @@ -125,8 +151,18 @@ jobs: # Run Gradle on JDK 21 (compatible with Shadow plugin); use JDK 25 via toolchain. JAVA_HOME: ${{ env.JAVA_HOME_21_X64 }} run: | - ./gradlew :spigot:build \ - -Porg.gradle.java.installations.paths="${JAVA_HOME_25_X64},${JAVA_HOME_21_X64}" + set -uxo pipefail + ./gradlew :spigot:build --info \ + -Porg.gradle.java.installations.paths="${JAVA_HOME_25_X64},${JAVA_HOME_21_X64}" 2>&1 | tee gradle-build.log + rc=${PIPESTATUS[0]} + { + echo "## Gradle exit=$rc" + echo + echo '```' + tail -n 200 gradle-build.log + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + exit $rc - name: Find JAR file id: find_jar From fee3d15f4203baa66968ca37fa1a956d7f8001db Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 17:42:32 +0000 Subject: [PATCH 07/11] ci: post diagnostic logs as PR comment so we can read them via MCP --- .github/workflows/auto-beta-release.yml | 48 ++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/.github/workflows/auto-beta-release.yml b/.github/workflows/auto-beta-release.yml index ec32d20cc..0abdcf1e6 100644 --- a/.github/workflows/auto-beta-release.yml +++ b/.github/workflows/auto-beta-release.yml @@ -145,6 +145,8 @@ jobs: if-no-files-found: ignore - name: Build with Gradle + id: gradle_build + continue-on-error: true env: USERNAME: ${{ secrets.USERNAME }} TOKEN: ${{ secrets.TOKEN }} @@ -155,6 +157,7 @@ jobs: ./gradlew :spigot:build --info \ -Porg.gradle.java.installations.paths="${JAVA_HOME_25_X64},${JAVA_HOME_21_X64}" 2>&1 | tee gradle-build.log rc=${PIPESTATUS[0]} + echo "rc=$rc" >> "$GITHUB_OUTPUT" { echo "## Gradle exit=$rc" echo @@ -162,7 +165,50 @@ jobs: tail -n 200 gradle-build.log echo '```' } >> "$GITHUB_STEP_SUMMARY" - exit $rc + # Don't fail the job here so diagnostic comment can be posted. + exit 0 + + - name: Upload Gradle build log + if: always() + uses: actions/upload-artifact@v4 + with: + name: gradle-build-log + path: gradle-build.log + if-no-files-found: ignore + + - name: Post diagnostic PR comment + if: always() + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.repository.default_branch && '20' || '20' }} + run: | + set -uxo pipefail + { + echo "### Diagnostic for commit ${GITHUB_SHA::7}" + echo + echo "BuildTools 26.1.2 outcome: ${{ steps.bt2612.outcome }}" + echo "Gradle outcome: ${{ steps.gradle_build.outcome }} (rc=${{ steps.gradle_build.outputs.rc }})" + echo + echo "**BuildTools 26.1.2 log (last 80 lines):**" + echo '```' + tail -n 80 buildtools/logs/buildtools-26.1.2.log 2>/dev/null || echo "(log not produced)" + echo '```' + echo + echo "**Gradle log (last 200 lines):**" + echo '```' + tail -n 200 gradle-build.log 2>/dev/null || echo "(log not produced)" + echo '```' + } > pr_comment.md + # Comment on PR if one exists for this branch. + PR=$(gh pr list --head "${GITHUB_REF_NAME}" --state open --json number --jq '.[0].number' || echo "") + if [ -n "$PR" ]; then + gh pr comment "$PR" --body-file pr_comment.md || true + fi + # Now fail the job if Gradle failed. + if [ "${{ steps.gradle_build.outputs.rc }}" != "0" ]; then + echo "Gradle failed; failing job." + exit 1 + fi - name: Find JAR file id: find_jar From 246b77e0a45afa0c6f8165dfe3ee66fc3c2340aa Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 17:55:09 +0000 Subject: [PATCH 08/11] fix: drop :remapped-mojang classifier for 1.21.10/1.21.11 BuildTools no longer publishes the :remapped-mojang classifier; from 1.20.5 the regular Spigot server jar is already mojang-mapped. Pin the dependency without the classifier so Gradle resolves the artifact that BuildTools actually installs to ~/.m2. Resolves: bukkit-helper-121-11:compileJava 'Could not find spigot-1.21.11-R0.1-SNAPSHOT-remapped-mojang.jar'. --- bukkit-helper-121-10/build.gradle | 4 +++- bukkit-helper-121-11/build.gradle | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/bukkit-helper-121-10/build.gradle b/bukkit-helper-121-10/build.gradle index bbd80a1de..a328b0fb4 100644 --- a/bukkit-helper-121-10/build.gradle +++ b/bukkit-helper-121-10/build.gradle @@ -13,7 +13,9 @@ dependencies { implementation project(':dynmap-api') implementation project(path: ':DynmapCore', configuration: 'shadow') compileOnly group: 'org.spigotmc', name: 'spigot-api', version:'1.21.10-R0.1-SNAPSHOT' - compileOnly ('org.spigotmc:spigot:1.21.10-R0.1-SNAPSHOT:remapped-mojang') { + // Spigot 1.20.5+ ships mojang-mapped by default; the `:remapped-mojang` + // classifier is no longer published by BuildTools. + compileOnly ('org.spigotmc:spigot:1.21.10-R0.1-SNAPSHOT') { exclude group: "com.mojang", module: "jtracy" } } diff --git a/bukkit-helper-121-11/build.gradle b/bukkit-helper-121-11/build.gradle index 7cff42e2e..fd59d0b3a 100644 --- a/bukkit-helper-121-11/build.gradle +++ b/bukkit-helper-121-11/build.gradle @@ -13,7 +13,9 @@ dependencies { implementation project(':dynmap-api') implementation project(path: ':DynmapCore', configuration: 'shadow') compileOnly 'org.spigotmc:spigot-api:1.21.11-R0.1-SNAPSHOT' - compileOnly ('org.spigotmc:spigot:1.21.11-R0.1-SNAPSHOT:remapped-mojang') { + // Spigot 1.20.5+ ships mojang-mapped by default; the `:remapped-mojang` + // classifier is no longer published by BuildTools. + compileOnly ('org.spigotmc:spigot:1.21.11-R0.1-SNAPSHOT') { exclude group: "com.mojang", module: "jtracy" } } From f11a814826fac452d70584ffa4f82fba23c2bd5d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 18:09:17 +0000 Subject: [PATCH 09/11] ci: pin BuildTools #189 for 1.21.x to keep :remapped-mojang classifier Latest BuildTools dropped the 'remapped' Maven profile, so it no longer publishes the :remapped-mojang classifier that bukkit-helper-121-10/121-11 depend on. Use BuildTools #189 (late-2025 era, the version that produced the working 1.21.11-beta.X releases) for those two versions, and keep the latest BuildTools for 26.1.2. --- .github/workflows/auto-beta-release.yml | 26 +++++++++++++++---------- bukkit-helper-121-10/build.gradle | 4 +--- bukkit-helper-121-11/build.gradle | 4 +--- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/workflows/auto-beta-release.yml b/.github/workflows/auto-beta-release.yml index 0abdcf1e6..7602564a2 100644 --- a/.github/workflows/auto-beta-release.yml +++ b/.github/workflows/auto-beta-release.yml @@ -80,35 +80,41 @@ jobs: distribution: 'temurin' cache: gradle - - name: Download BuildTools.jar + - name: Download BuildTools.jar (latest, for 26.1.2) run: | set -euxo pipefail mkdir -p buildtools cd buildtools - wget -nv https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar + wget -nv -O BuildTools-latest.jar https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar + # Older BuildTools build that still emits the :remapped-mojang classifier + # used by bukkit-helper-1.21.10/1.21.11. Build #189 dates from late 2025 + # (Spigot 1.21.10/1.21.11 era). + wget -nv -O BuildTools-189.jar https://hub.spigotmc.org/jenkins/job/BuildTools/189/artifact/target/BuildTools.jar ls -la - - name: BuildTools Spigot 1.21.10 (Java 21) + - name: BuildTools Spigot 1.21.10 (Java 21, BuildTools #189) working-directory: buildtools env: JAVA21_HOME: ${{ env.JAVA_HOME_21_X64 }} run: | set -euxo pipefail - "$JAVA21_HOME/bin/java" -jar BuildTools.jar --rev 1.21.10 --remapped - ls -la "$HOME/.m2/repository/org/spigotmc/spigot/" || true + mkdir -p logs + "$JAVA21_HOME/bin/java" -jar BuildTools-189.jar --rev 1.21.10 --remapped 2>&1 | tee logs/buildtools-1.21.10.log + ls -la "$HOME/.m2/repository/org/spigotmc/spigot/1.21.10-R0.1-SNAPSHOT/" || true timeout-minutes: 30 - - name: BuildTools Spigot 1.21.11 (Java 21) + - name: BuildTools Spigot 1.21.11 (Java 21, BuildTools #189) working-directory: buildtools env: JAVA21_HOME: ${{ env.JAVA_HOME_21_X64 }} run: | set -euxo pipefail - "$JAVA21_HOME/bin/java" -jar BuildTools.jar --rev 1.21.11 --remapped - ls -la "$HOME/.m2/repository/org/spigotmc/spigot/" || true + mkdir -p logs + "$JAVA21_HOME/bin/java" -jar BuildTools-189.jar --rev 1.21.11 --remapped 2>&1 | tee logs/buildtools-1.21.11.log + ls -la "$HOME/.m2/repository/org/spigotmc/spigot/1.21.11-R0.1-SNAPSHOT/" || true timeout-minutes: 30 - - name: BuildTools Spigot 26.1.2 (Java 25) + - name: BuildTools Spigot 26.1.2 (Java 25, BuildTools latest) id: bt2612 continue-on-error: true working-directory: buildtools @@ -117,7 +123,7 @@ jobs: run: | set -uxo pipefail mkdir -p logs - "$JAVA25_HOME/bin/java" -jar BuildTools.jar --rev 26.1.2 --remapped 2>&1 | tee logs/buildtools-26.1.2.log + "$JAVA25_HOME/bin/java" -jar BuildTools-latest.jar --rev 26.1.2 --remapped 2>&1 | tee logs/buildtools-26.1.2.log rc=${PIPESTATUS[0]} { echo "## BuildTools 26.1.2 exit=$rc" diff --git a/bukkit-helper-121-10/build.gradle b/bukkit-helper-121-10/build.gradle index a328b0fb4..bbd80a1de 100644 --- a/bukkit-helper-121-10/build.gradle +++ b/bukkit-helper-121-10/build.gradle @@ -13,9 +13,7 @@ dependencies { implementation project(':dynmap-api') implementation project(path: ':DynmapCore', configuration: 'shadow') compileOnly group: 'org.spigotmc', name: 'spigot-api', version:'1.21.10-R0.1-SNAPSHOT' - // Spigot 1.20.5+ ships mojang-mapped by default; the `:remapped-mojang` - // classifier is no longer published by BuildTools. - compileOnly ('org.spigotmc:spigot:1.21.10-R0.1-SNAPSHOT') { + compileOnly ('org.spigotmc:spigot:1.21.10-R0.1-SNAPSHOT:remapped-mojang') { exclude group: "com.mojang", module: "jtracy" } } diff --git a/bukkit-helper-121-11/build.gradle b/bukkit-helper-121-11/build.gradle index fd59d0b3a..7cff42e2e 100644 --- a/bukkit-helper-121-11/build.gradle +++ b/bukkit-helper-121-11/build.gradle @@ -13,9 +13,7 @@ dependencies { implementation project(':dynmap-api') implementation project(path: ':DynmapCore', configuration: 'shadow') compileOnly 'org.spigotmc:spigot-api:1.21.11-R0.1-SNAPSHOT' - // Spigot 1.20.5+ ships mojang-mapped by default; the `:remapped-mojang` - // classifier is no longer published by BuildTools. - compileOnly ('org.spigotmc:spigot:1.21.11-R0.1-SNAPSHOT') { + compileOnly ('org.spigotmc:spigot:1.21.11-R0.1-SNAPSHOT:remapped-mojang') { exclude group: "com.mojang", module: "jtracy" } } From 1612b5e72e4aa497161e04f08a19c1e73b0e74b8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 2 May 2026 18:10:50 +0000 Subject: [PATCH 10/11] ci: try multiple older BuildTools builds; soft-fail per BuildTools step - Download multiple candidate older BuildTools.jar (196..186) until one downloads. - Each BuildTools step is now continue-on-error so one failure doesn't skip the rest. Each emits its own log file. - Diagnostic PR comment now lists outcomes/rcs for every step plus tails of each BuildTools log and the spigot maven repo. --- .github/workflows/auto-beta-release.yml | 90 +++++++++++++++++++------ 1 file changed, 69 insertions(+), 21 deletions(-) diff --git a/.github/workflows/auto-beta-release.yml b/.github/workflows/auto-beta-release.yml index 7602564a2..9b264e8a2 100644 --- a/.github/workflows/auto-beta-release.yml +++ b/.github/workflows/auto-beta-release.yml @@ -80,38 +80,62 @@ jobs: distribution: 'temurin' cache: gradle - - name: Download BuildTools.jar (latest, for 26.1.2) + - name: Download BuildTools.jar + id: download_bt + continue-on-error: true run: | - set -euxo pipefail - mkdir -p buildtools + set -uxo pipefail + mkdir -p buildtools/logs cd buildtools - wget -nv -O BuildTools-latest.jar https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar - # Older BuildTools build that still emits the :remapped-mojang classifier - # used by bukkit-helper-1.21.10/1.21.11. Build #189 dates from late 2025 - # (Spigot 1.21.10/1.21.11 era). - wget -nv -O BuildTools-189.jar https://hub.spigotmc.org/jenkins/job/BuildTools/189/artifact/target/BuildTools.jar + # Latest BuildTools is needed for 26.1.2. + wget -nv -O BuildTools-latest.jar \ + https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar 2>&1 \ + | tee logs/dl-latest.log + # Try older BuildTools build numbers that still emit the :remapped-mojang + # classifier for 1.21.x. We try a few until one downloads successfully. + for n in 196 195 194 193 192 191 190 189 188 187 186; do + url="https://hub.spigotmc.org/jenkins/job/BuildTools/$n/artifact/target/BuildTools.jar" + if wget -nv -O "BuildTools-$n.jar" "$url" 2>&1 | tee -a logs/dl-old.log; then + if [ -s "BuildTools-$n.jar" ]; then + ln -sf "BuildTools-$n.jar" BuildTools-old.jar + echo "old=$n" >> "$GITHUB_OUTPUT" + break + fi + fi + rm -f "BuildTools-$n.jar" + done ls -la - - name: BuildTools Spigot 1.21.10 (Java 21, BuildTools #189) + - name: BuildTools Spigot 1.21.10 (Java 21, older BuildTools) + id: bt121_10 + continue-on-error: true working-directory: buildtools env: JAVA21_HOME: ${{ env.JAVA_HOME_21_X64 }} run: | - set -euxo pipefail + set -uxo pipefail mkdir -p logs - "$JAVA21_HOME/bin/java" -jar BuildTools-189.jar --rev 1.21.10 --remapped 2>&1 | tee logs/buildtools-1.21.10.log - ls -la "$HOME/.m2/repository/org/spigotmc/spigot/1.21.10-R0.1-SNAPSHOT/" || true + "$JAVA21_HOME/bin/java" -jar BuildTools-old.jar --rev 1.21.10 --remapped 2>&1 | tee logs/buildtools-1.21.10.log + rc=${PIPESTATUS[0]} + echo "rc=$rc" >> "$GITHUB_OUTPUT" + ls -la "$HOME/.m2/repository/org/spigotmc/spigot/1.21.10-R0.1-SNAPSHOT/" 2>&1 | tee -a logs/buildtools-1.21.10.log || true + exit 0 timeout-minutes: 30 - - name: BuildTools Spigot 1.21.11 (Java 21, BuildTools #189) + - name: BuildTools Spigot 1.21.11 (Java 21, older BuildTools) + id: bt121_11 + continue-on-error: true working-directory: buildtools env: JAVA21_HOME: ${{ env.JAVA_HOME_21_X64 }} run: | - set -euxo pipefail + set -uxo pipefail mkdir -p logs - "$JAVA21_HOME/bin/java" -jar BuildTools-189.jar --rev 1.21.11 --remapped 2>&1 | tee logs/buildtools-1.21.11.log - ls -la "$HOME/.m2/repository/org/spigotmc/spigot/1.21.11-R0.1-SNAPSHOT/" || true + "$JAVA21_HOME/bin/java" -jar BuildTools-old.jar --rev 1.21.11 --remapped 2>&1 | tee logs/buildtools-1.21.11.log + rc=${PIPESTATUS[0]} + echo "rc=$rc" >> "$GITHUB_OUTPUT" + ls -la "$HOME/.m2/repository/org/spigotmc/spigot/1.21.11-R0.1-SNAPSHOT/" 2>&1 | tee -a logs/buildtools-1.21.11.log || true + exit 0 timeout-minutes: 30 - name: BuildTools Spigot 26.1.2 (Java 25, BuildTools latest) @@ -186,18 +210,44 @@ jobs: if: always() env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.repository.default_branch && '20' || '20' }} run: | set -uxo pipefail { echo "### Diagnostic for commit ${GITHUB_SHA::7}" echo + echo "Download BuildTools outcome: ${{ steps.download_bt.outcome }} (older build picked: ${{ steps.download_bt.outputs.old }})" + echo "BuildTools 1.21.10 outcome: ${{ steps.bt121_10.outcome }} (rc=${{ steps.bt121_10.outputs.rc }})" + echo "BuildTools 1.21.11 outcome: ${{ steps.bt121_11.outcome }} (rc=${{ steps.bt121_11.outputs.rc }})" echo "BuildTools 26.1.2 outcome: ${{ steps.bt2612.outcome }}" echo "Gradle outcome: ${{ steps.gradle_build.outcome }} (rc=${{ steps.gradle_build.outputs.rc }})" echo - echo "**BuildTools 26.1.2 log (last 80 lines):**" + echo "**Maven repo (org.spigotmc):**" + echo '```' + ls -la "$HOME/.m2/repository/org/spigotmc/spigot/" 2>&1 || true + for v in 1.21.10-R0.1-SNAPSHOT 1.21.11-R0.1-SNAPSHOT 26.1.2-R0.1-SNAPSHOT; do + echo "--- spigot/$v ---" + ls -la "$HOME/.m2/repository/org/spigotmc/spigot/$v/" 2>&1 || true + done + echo '```' + echo + echo "**Download log (tail 30):**" + echo '```' + tail -n 30 buildtools/logs/dl-old.log 2>/dev/null || true + echo '```' + echo + echo "**BuildTools 1.21.10 log (tail 60):**" + echo '```' + tail -n 60 buildtools/logs/buildtools-1.21.10.log 2>/dev/null || echo "(no log)" + echo '```' + echo + echo "**BuildTools 1.21.11 log (tail 60):**" + echo '```' + tail -n 60 buildtools/logs/buildtools-1.21.11.log 2>/dev/null || echo "(no log)" + echo '```' + echo + echo "**BuildTools 26.1.2 log (tail 30):**" echo '```' - tail -n 80 buildtools/logs/buildtools-26.1.2.log 2>/dev/null || echo "(log not produced)" + tail -n 30 buildtools/logs/buildtools-26.1.2.log 2>/dev/null || echo "(no log)" echo '```' echo echo "**Gradle log (last 200 lines):**" @@ -205,12 +255,10 @@ jobs: tail -n 200 gradle-build.log 2>/dev/null || echo "(log not produced)" echo '```' } > pr_comment.md - # Comment on PR if one exists for this branch. PR=$(gh pr list --head "${GITHUB_REF_NAME}" --state open --json number --jq '.[0].number' || echo "") if [ -n "$PR" ]; then gh pr comment "$PR" --body-file pr_comment.md || true fi - # Now fail the job if Gradle failed. if [ "${{ steps.gradle_build.outputs.rc }}" != "0" ]; then echo "Gradle failed; failing job." exit 1 From eb5bfeee6ad385a803212d62acf91a69ead16735 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 3 May 2026 15:40:28 +0000 Subject: [PATCH 11/11] ci: BuildTools 196 only for 1.21.10; latest for 1.21.11 + 26.1.2 BuildTools 196 (the latest with the 'remapped' Maven profile) refuses to build 1.21.11 because that version needs toolsVersion 197+. Use the latest BuildTools (197+) for 1.21.11, which produces a mojang-mapped regular jar (no :remapped-mojang classifier needed). Also drop the :remapped-mojang classifier in bukkit-helper-121-11/build.gradle. --- .github/workflows/auto-beta-release.yml | 5 +++-- bukkit-helper-121-11/build.gradle | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/auto-beta-release.yml b/.github/workflows/auto-beta-release.yml index 9b264e8a2..c4540794b 100644 --- a/.github/workflows/auto-beta-release.yml +++ b/.github/workflows/auto-beta-release.yml @@ -122,7 +122,7 @@ jobs: exit 0 timeout-minutes: 30 - - name: BuildTools Spigot 1.21.11 (Java 21, older BuildTools) + - name: BuildTools Spigot 1.21.11 (Java 21, BuildTools latest) id: bt121_11 continue-on-error: true working-directory: buildtools @@ -131,7 +131,8 @@ jobs: run: | set -uxo pipefail mkdir -p logs - "$JAVA21_HOME/bin/java" -jar BuildTools-old.jar --rev 1.21.11 --remapped 2>&1 | tee logs/buildtools-1.21.11.log + # 1.21.11 needs toolsVersion 197+; older BuildTools refuses to build it. + "$JAVA21_HOME/bin/java" -jar BuildTools-latest.jar --rev 1.21.11 --remapped 2>&1 | tee logs/buildtools-1.21.11.log rc=${PIPESTATUS[0]} echo "rc=$rc" >> "$GITHUB_OUTPUT" ls -la "$HOME/.m2/repository/org/spigotmc/spigot/1.21.11-R0.1-SNAPSHOT/" 2>&1 | tee -a logs/buildtools-1.21.11.log || true diff --git a/bukkit-helper-121-11/build.gradle b/bukkit-helper-121-11/build.gradle index 7cff42e2e..495fcfc13 100644 --- a/bukkit-helper-121-11/build.gradle +++ b/bukkit-helper-121-11/build.gradle @@ -13,7 +13,9 @@ dependencies { implementation project(':dynmap-api') implementation project(path: ':DynmapCore', configuration: 'shadow') compileOnly 'org.spigotmc:spigot-api:1.21.11-R0.1-SNAPSHOT' - compileOnly ('org.spigotmc:spigot:1.21.11-R0.1-SNAPSHOT:remapped-mojang') { + // BuildTools 197+ (required for 1.21.11) no longer publishes the + // :remapped-mojang classifier; the regular jar is now mojang-mapped. + compileOnly ('org.spigotmc:spigot:1.21.11-R0.1-SNAPSHOT') { exclude group: "com.mojang", module: "jtracy" } }