Skip to content

Bootstrap unification, standalone bundles + messaging, local release redesign, bare-common coordinates#38

Merged
THEROER merged 39 commits into
mainfrom
feature/bootstrap-unification-and-docs
Jul 10, 2026
Merged

Bootstrap unification, standalone bundles + messaging, local release redesign, bare-common coordinates#38
THEROER merged 39 commits into
mainfrom
feature/bootstrap-unification-and-docs

Conversation

@THEROER

@THEROER THEROER commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Brings main up to the current state of the project. main had drifted well behind the working branch; this PR is the accumulated work since, in a few themes.

Bootstrap unification

Shared language-wiring extracted into a core LanguageBootstrap; Fabric / NeoForge / Velocity / Bungee migrated onto it. Typed Logger facades added to Velocity and Bungee for parity with the other platforms. Metadata-derived forPlugin overloads for proxies.

Standalone bundles + messaging

Standalone Fabric / NeoForge / JVM bundles as drop-in mods/plugins. Cross-server messaging module (plugin-messaging transport by default, optional Redis/Jedis shaded into JVM bundles).

Release mechanic redesign (build-logic as a reusable tool)

The release is now fully local and configurable via release { } DSL + -Prelease.<step> overrides, no longer requiring GitHub Actions:

  • per-target Exec fan-out helper, local Maven release, git tag/push, local Modrinth release, javadoc upload, configurable orchestrator;
  • resumable/idempotent publish (-Pskip_existing HEADs each POM), fan-out credential forwarding, release-consistency verification.

Coordinate scheme: common modules bare, +java<N> only for bundles

Plain library modules are byte-identical across Java levels, so they now publish once under the bare <base> coordinate instead of three near-duplicate +java17/21/25 copies. Bundles keep +java<N> (their shaded dependency set really differs per level). Publish side and every consumer plugin agree by construction via one discriminator.

fabric-bundle messaging fix

The 26.x (java25) fabric bundle was shipping without the messaging package (it only reached the <26 bundle transitively). :messaging is now inlined on every branch.

Release branch gate

releasePreflight now refuses to release off the configured release branch (default main); overridable with -Prelease.branch, bypassable with -Prelease.allowAnyBranch=true.


./gradlew -p build-logic test is green; full builds pass on obfuscated (mc1201) and deobfuscated (mc262) targets; the coordinate scheme was verified end-to-end against a file:// sandbox repo across two Java-level representatives.

THEROER added 30 commits July 9, 2026 20:55
Bump the library version to 1.24.0 and the reusable build-logic plugins
to 0.1.6. 1.24.0 adds mountSubCommands for flat sub-command grafting and
the standalone bungee/velocity bundles; 0.1.6 carries the publish fixes
(skip_shadow_publish ordering, java-component binding) and per-target
fabric java that landed after the 0.1.5 publish.

Published to Reposilite across all targets (+1.20.1, +1.21.10, +1.21.11,
+26.1.1, +26.2).
…anup

Switch the published Maven coordinate from +<minecraft> to +java<N>.
MagicUtils' compiled bytecode depends only on the Java level, not the
Minecraft version: the whole library is byte-identical between targets
that share a Java level (measured: +1.21.10 vs +1.21.11 differed in 0
classes) and differs wholesale only across Java levels (bytecode major
65 vs 69). The old per-Minecraft scheme published five near-duplicate
copies where three real variants exist. Now one representative target
per Java level (mc1201/17, mc12110/21, mc2611/25) publishes +java17,
+java21, +java25; the other targets remain for the build/smoke matrix.
Consumers keep declaring the bare version and resolve +java<N> from
their target's Java level, so downstreams need no change.

build-logic cleanup (0.1.6 -> 0.1.7):
- Merge bukkit/bungee/velocity bundle plugins into one parameterised
  MagicUtilsJvmBundlePlugin (JvmBundlePlatform table).
- Extract the duplicated publication pom-strip into stripPomDependencies
  and the Bukkit/Velocity consumer embed logic into
  configureJvmConsumerEmbed.
- processor is build-only (annotationProcessor, baked into consumers,
  never a runtime dependency) -> category NONE, no longer published.

Published to Reposilite across java17/java21/java25; processor excluded.
Add the messaging module: a typed MessageBus over a MessageTransport SPI
with envelope codec and Target-based addressing (broadcast / all-backends
/ proxy / server / player). Two interchangeable transports behind one SPI:
the default plugin-messaging transport (no extra dependency) and an
optional Redis pub/sub transport (Jedis).

Platform wiring mirrors the diagnostics pattern: BukkitMessagingSupport /
VelocityMessagingSupport / BungeeMessagingSupport plus
enableMessaging() / messagingRedis() / configureMessaging() on each
Bootstrap.Builder. platform-bukkit/bungee/velocity depend on :messaging.

Jedis is compileOnly in :messaging (the Redis transport is class-loaded
lazily) and shaded+relocated into the standalone bundles via the shared
magicUtilsBundleRedis helper, so operators get the Redis transport out of
the box once they enable it in messaging.yml. Verified: bundle carries
647 relocated jedis classes, 0 unrelocated, 29 messaging classes.

Merged from the MagicUtils-messaging branch onto the 1.25.0 +java publish
scheme. refreshReflectionAllowlist run (messaging imports shifted the
getMethod marker line in BukkitMagicUtilsConsumerRegistry).
…eBootstrap

The five platform bootstrap builders (Bukkit, Bungee, Velocity, Fabric,
NeoForge) each duplicated the same eight language/messages flags, their
setters, and the apply/close logic. Extract that byte-identical wiring into
core/bootstrap/LanguageBootstrap (single source of truth) and delegate from
BukkitBootstrap. Also adds withRecommendedDefaults()/minimal() presets so the
default flag set is explicit instead of a wall of booleans.

Remaining platforms are migrated in follow-up commits.
…ageBootstrap

Delegate the language/messages flags, setters, apply and close-hook logic to
core LanguageBootstrap in the four remaining platform bootstraps, matching the
Bukkit migration. Each builder now exposes withRecommendedDefaults()/minimal()
presets. Proxy builders pass their LoggerCore directly; MC builders pass
logger.getCore(). Removes the duplicated boolean-flag blocks entirely.
Verifies withRecommendedDefaults()/minimal() flag state and that apply()
plus installMessagesCloseHooks() register/clear the scoped and global Messages
manager and logger binding, driven against real LanguageManager/Messages.
Velocity gains forPlugin(proxy, plugin) and Bungee gains forPlugin(plugin),
deriving the plugin name (and Velocity's data directory as plugins/<id>) from
the plugin's @Plugin/description metadata. The verbose overloads that take an
explicit name/dataDirectory are kept and marked @deprecated for source
compatibility. This makes the entry points consistent across platforms:
Bukkit/Bungee forPlugin(plugin), Velocity forPlugin(proxy, plugin),
Fabric/NeoForge forMod(id, supplier).
… duplication

Each platform LogBuilder re-declared ~10 covariant overrides of the shared
fluent methods (to/target/toAll/toConsole/noPrefix/prefixMode/args/
placeholders/withResolvers/toAudiences) purely to keep the chain typed as the
platform builder rather than LogBuilderCore. Make LogBuilderCore<SELF> return
SELF from those methods so subclasses inherit them already correctly typed.
Platform builders now hold only their player/command-source overloads
(Bukkit -66 lines, Fabric/NeoForge -~70 each).
Velocity previously exposed only the raw LoggerCore. Add a Velocity Logger
(plus PrefixedLogger and LogBuilder) mirroring Bukkit/Fabric/NeoForge: generated
info/warn/error/success/debug/trace methods, Player-typed overloads, prefixed
sub-loggers, and a fluent LogBuilder. VelocityBootstrap now builds and returns
this Logger (RuntimeResult/Result.logger type LoggerCore -> Logger; safe, no
external consumer uses it). VelocityAudience is made public so the facade can
wrap players.
Mirror the Velocity change on BungeeCord: add a Bungee Logger (plus
PrefixedLogger and LogBuilder) with generated log methods, ProxiedPlayer-typed
overloads, prefixed sub-loggers, and a fluent LogBuilder. BungeeBootstrap now
builds and returns this Logger (RuntimeResult/Result.logger type LoggerCore ->
Logger; safe, no external consumer uses it). BungeeAudience is made public so
the facade can wrap players. All five platforms now expose the same typed
Logger facade.
VelocityBootstrap/BungeeBootstrap now return the typed Logger facade instead of
LoggerCore. The standalone velocity-bundle and bungee-bundle plugins read
bootstrap.logger() as a LoggerCore, so call .getCore() to unwrap it.
Show the consistent metadata-derived entry points (forPlugin(this) on Bukkit/
Bungee, forPlugin(proxy, this) on Velocity, forMod on Fabric/NeoForge), document
the withRecommendedDefaults()/minimal() presets, note NeoForge has a bootstrap
(not manual wiring), and state that bootstrap.logger() returns a typed Logger
facade on every platform.
setEscapePlaceholders existed on LoggerCore but was the one LoggerCore method
missing from LoggerAdapter, forcing callers to drop to getCore(). Delegate it
from the adapter so it is available on the typed Logger facade on every platform.
Give every platform Logger facade a class-level JavaDoc that frames it as the
single output surface (console log line + player chat via the same level
methods), with a short usage example and a note that the fluent log() builder is
only for composite cases. Remove the @SuppressWarnings("doclint:missing")
suppressions on the logger facades, LoggerCore, LogBuilder, PrefixedLogger, and
FabricExternalPlaceholderEngine, documenting the few members that needed it so
javadoc stays green without hiding gaps.
… colour

Players saw the internal log level spelled out in chat as [Plugin SUCCESS] /
[Plugin WARN]. Two changes to LogMessageFormatter:

- Drop the level suffix from the chat prefix. The prefix is rendered into the
  chat message only (the console line already carries the level via the platform
  logger), so chat now shows just the plugin brand [Plugin].
- Decouple the level colour from gradients. The success/warn/error signal is now
  conveyed by colour and applied to every chat message; useGradientChat only
  chooses between a two-stop gradient and a single solid colour, so the signal
  survives even with gradients disabled.

Adds tests: chat keeps the brand but not the level word, and SUCCESS vs ERROR
carry distinct colours with gradients off.
Fabric mods can now enable cross-server messaging, matching the Bukkit/Bungee/
Velocity backends. Adds FabricMessagingSupport.install (Redis transport via the
shared messaging module; hostsPlayer via Platform.playerById) and the messaging
surface on FabricBootstrap (enableMessaging/messagingRedis/configureMessaging +
buildRuntime wiring). No default plugin-messaging transport: riding the proxy's
vanilla channel from a mod needs the platform networking API on modern MC, so
without Redis the bus falls back to the in-process loopback transport (logged).
Compiles on mc12110 and mc1201.
Mirrors the Fabric change on NeoForge: NeoForgeMessagingSupport.install (Redis
transport via the shared messaging module) plus the messaging surface on
NeoForgeBootstrap (enableMessaging/messagingRedis/configureMessaging +
buildRuntime wiring). hostsPlayer resolves via the MinecraftServer supplier
(server.getPlayerList().getPlayer(id)) since the NeoForge platform provider does
not override Platform.playerById. No default plugin-messaging transport; without
Redis the bus falls back to loopback (logged). Compiles on mc12110 and mc262
(NeoForge is disabled on the 1.20.x targets).
…lone jar

The NeoForge bundle adds project deps non-transitively, so the newly-added
:commands-neoforge messaging code pulled neither the :messaging module nor
Jackson into the jar — MessageCodec would NoClassDefFoundError at runtime.
Add :messaging to bundleContents and the jackson-databind/core/annotations
artifacts explicitly by coordinate (mirroring the Adventure handling). This also
closes a pre-existing gap where Jackson was absent entirely, so the config
module now has its runtime dependency on the NeoForge bundle too. Verified the
jar now contains the messaging classes, RedisMessageTransport, and Jackson.
…e X.Y.Z

The library ships only per-Java coordinates (X.Y.Z+java<N>); a bare X.Y.Z
POM is never published, so smokeTest polled a URL that 404s forever. Build
the URL from the default target's Java level (java21 on mc12110) and
percent-encode the '+'. Factor the '+java<N>' format into one pure
javaSuffixedCoordinate() reused by publishedVersion, the Modrinth bundle
file name, and the smoke URL.
Cross-checks -Pversion across gradle.properties, the git tag, Reposilite
Maven (HEAD on the java-suffixed POM, same coordinate as smokeTest) and
Modrinth. Strict fail (non-zero) when the required three disagree; Modrinth
is advisory (published manually, may lag) and is skipped without a token.
-Preport prints the report without failing.

Comparison logic (evaluateReleaseConsistency) and the Modrinth version-list
parse (parseModrinthVersionIds, factored out of the publish task) live in
the model and are unit-tested; the task is a thin git/HTTP wrapper.
Ships the smokeTest java-suffix fix and the new verifyReleaseConsistency
task to consumers. Published to Reposilite by publish-maven.yml alongside
the library.
The 'one Gradle invocation per Minecraft target' mechanism (wrapper name,
isolated child Gradle home, -Ptarget) was inline in registerAllTargetsTask.
Extract it into registerMagicUtilsFanout so the upcoming local Maven and
Modrinth release fan-outs reuse it instead of copying the machinery. Adds a
dryRun mode that prints the command per target instead of spawning a child
build. buildAllTargets --dry-run is unchanged (same buildTarget<Name> tasks).
MagicUtilsReleaseSpec makes every release step (validate/bump/tag/push/
maven/modrinth/javadoc/verify) individually optional, so a consumer builds
exactly the pipeline they want. releasePlan() gives the fixed run order;
applyReleaseOverrides() merges -Prelease.<step>=true|false over the DSL.
Wired as magicMatrix { release { ... } } into magicutilsReleaseSpec. No task
consumes the spec yet.
releaseMavenAll publishes the full module matrix to the Maven repo locally,
one Gradle invocation per publish unit (one representative target per Java
level, from the shared publishUnits), plus publishBuildLogic for the plugins.
Reuses registerMagicUtilsFanout; the dry-run command set matches
printPublishMatrix unit-for-unit. Factors the gradlew wrapper name into
magicUtilsGradleWrapperName so it is spelled once. Actions-free equivalent of
publish-maven.yml.
releaseTag creates the vX.Y.Z tag locally and only pushes to origin when
push is enabled (-Prelease.push=true), matching the release spec's push=false
default. Idempotent: an existing local tag is left in place. Replaces the
tagging that lived in release.yml so the tag step runs without actions.
releaseModrinth builds the platform bundle jars for every Java level (one
representative target per level, reusing the shared publishUnits + fan-out)
and then runs the existing matrix-driven publishToModrinth. The +java<N>
bundle jars accumulate side by side per platform, so one publishToModrinth
finds all of them. Factors the unit resolution into resolvePublishUnits so
the Maven and Modrinth fan-outs share it. Actions-free Modrinth publish.
releaseJavadoc runs aggregatedJavadocZip and PUTs the zip to the publish repo
at the stable latest path plus a versioned copy, replacing the curl upload in
publish-javadoc.yml. URL builders (javadocLatestUrl/javadocVersionUrl) are pure
and unit-tested. Credentials reuse the shared findMagicUtilsPublishSecret
(now internal). The versioned URL uses the raw -Pversion, not the +java<N>
project.version. -Prelease.dryRun prints the target URLs.
release now runs exactly the steps enabled in release { } (validate/bump/
tag/maven/modrinth/javadoc/verify), in the fixed plan order with a
mustRunAfter chain, and prints the plan. -Prelease.<step>=true|false
overrides the DSL per invocation. Adds releaseValidateBuild (non-Fabric
build gate). dispatchRelease is dropped from the default release (release is
now fully local) but kept as a standalone task for the CI-trigger path.
The orchestrator is registered after every step task so the chain resolves.
The Modrinth token was env-only (MODRINTH_TOKEN) while the Maven secrets
accepted a property or env. Make it symmetric: findMagicUtilsModrinthToken
reads modrinth_token property or MODRINTH_TOKEN env, so a consumer configures
every secret one way (e.g. all in ~/.gradle/gradle.properties). The token is
carried as an @internal task input (never in the up-to-date hash), resolved in
the configuration phase, in both publishToModrinth and verifyReleaseConsistency.
THEROER added 9 commits July 10, 2026 20:49
The Maven release fan-out runs 'clean' in each child invocation to republish
from scratch. With the child Gradle home under build/ (release-maven-gradle-home),
that clean deleted build/ mid-run — including the child's own daemon working
directory — failing with 'could not setcwd (errno 2)'. Move the child home to
.gradle/<subdir>, which survives clean. Applies to every fan-out (buildAllTargets
too), so none can self-destruct.
A release can fail mid-way (Maven publish dies on one target) and must be
re-runnable. But preflight rejected the requested version once its tag existed
or gradle.properties was already bumped to it, making a resume impossible.
Treat requested == current as a resume: skip the tag/monotonicity checks (a
prior run already cut them). A fresh release (version not yet bumped to) is
still strictly validated.
…release)

A release can fail mid-publish; re-running then 409'd against the immutable
releases repo on every coordinate a prior run already uploaded (e.g. the
build-logic plugins). -Pskip_existing HEADs each POM and skips the
PublishToMavenRepository task when it is already present, so a resumed release
publishes only what is missing. releaseMavenAll and publishBuildLogic pass it
by default, making the whole Maven release resumable (Modrinth already is,
by version_number).
The Maven release fan-out runs each target in a child Gradle with an isolated
--gradle-user-home, so the child cannot read ~/.gradle/gradle.properties and
its publish 401'd. Forward the parent-resolved publish_user/publish_password
to the child as PUBLISH_USER/PUBLISH_TOKEN env (never as -P, so they stay out
of ps). MagicUtilsFanoutInvocation gains an env map for this.
releaseModrinth built each representative with -Pscenario=workspace, but the
java17 representative (mc1201, a 1.20.x target) has no neoforge, so the child
failed with 'target does not support platforms: neoforge'. Use the target's
availablePlatformsFor set as -PincludePlatforms instead, so each Java level
builds exactly the bundles it supports (neoforge only on java21/java25).
The javadoc zip uploads to a stable latest/ path plus a versioned path. The
immutable releases repo returns 409 when latest/ already holds a prior
release's zip, which failed the whole release on a step that is effectively
done. Treat 409 as a skip (warn, continue) on every javadoc target, so a
resumed release does not die on javadoc and the versioned path still uploads.
The messaging package was absent from the 26.x (java25) fabric bundle: it
was never listed in the bundle plugin and reached the <26 bundle only
transitively via commands-fabric's api(:messaging) through Loom's
namedElements fat jar. The 26.x bundle is built from bundleShadow alone
(the jiJRemap path drops transitive api project deps), so messaging
silently vanished from the shipped java25 jar. Add :messaging to
bundleLibProjects so it is inlined on every branch, matching how the
neoforge/jvm bundles list it explicitly.
…dles

Plain library modules are byte-identical across Java levels (the per-level
diffs are javac codegen artifacts, not behaviour), so publishing three
near-duplicate +java17/21/25 copies was redundant. Only bundles genuinely
differ per level (their shaded dependency set tracks the Minecraft branch).

Introduce magicUtilsModuleIsBundle (artifact id ends in -bundle) and
magicUtilsPublishedModuleVersion: bundles get <base>+java<N>, plain modules
get the bare <base>. The root build stamps project.version per module via
this rule, and every consumer plugin resolves the same coordinate shape, so
publish and consume agree by construction. A resumed release with
-Pskip_existing skips the already-uploaded bare module on the second and
third Java-level representatives. The release smoke/verify poll the smoke
artifact's real coordinate (magicutils-core, now bare) instead of a
java-suffixed one that would 404.
Publishing off a feature branch pins the git tag and the immutable Maven
coordinate to a commit that may never land on the release branch as written,
so the release history diverges from it. releasePreflight now checks the
current branch against a configurable releaseBranch (default main; null
disables the gate) and fails fast before any tag or upload.

Configurable via release { releaseBranch = ... }, overridable per invocation
with -Prelease.branch=<name>, and bypassable once for a genuine off-branch
hotfix with -Prelease.allowAnyBranch=true. releaseBranchViolation is a pure
function so the gate is unit-tested without a git checkout.
@THEROER THEROER merged commit 1895969 into main Jul 10, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant