Target Matrix
The matrix is what lets one source tree build and publish against many Minecraft versions. A target is a named coordinate (Minecraft version + Java level + platform dependency versions); adding one is a single edit to a properties file, never a change to build scripts or CI.
targets.properties
Each target is a prefix in gradle/targets.properties. The target= line at the
top is the default target used when no -Ptarget= is passed:
Every module compiles against the target's java level (via options.release),
while a single modern toolchain (JDK 25) compiles all targets. So you never
provision a separate JDK per Minecraft version.
Per-target Fabric Java requirement
A module that ships a fabric.mod.json declares its Java requirement as the
literal token "java": "@MC_JAVA@" rather than a hardcoded >=21. During the
build the shared java-library plugin substitutes it with >=<target.java>, so
a mod built for a 1.20.1 (Java 17) branch requires Java 17 and a 1.21.10 (Java
21) branch requires Java 21 — from one manifest, resolved per target. This
prevents a lower-Java branch from being wrongly rejected by the Fabric loader.
The token is a plain string, not a ${...} placeholder, because Loom expands
${version} and would fail on an unknown ${} key. All modules that carry a
fabric.mod.json — including the common ones (commands, logger, config, lang,
placeholders) — get the substitution.
Library vs runtime Minecraft
The published coordinate branch normally follows minecraft. When the version
you publish against differs from the runtime one (for example a Paper build that
targets Java 21 on 1.20.5+), set <target>.library_minecraft and consumers
resolve against that branch:
The magicMatrix DSL
magicMatrix { } in settings.gradle.kts declares the module layout and how
targets map to platforms. This is the single source of truth for the whole
build:
moduleNamePrefix(...) / moduleName(project, artifact) remap a subproject name
to a published artifact id (for example platform-bukkit -> magicutils-bukkit).
MagicUtils itself uses them; most consumers keep their own module names and can
skip both.
Key methods:
| Method | Purpose |
|---|---|
targetsFile / publishingFile | Paths to the properties files. |
defaultTarget | Target used when -Ptarget is absent. |
moduleNamePrefix(...) / moduleName(project, artifact) | Artifact id naming. |
commonProjects(...) | Platform-neutral modules (built once). |
platform(name, projects [, disabledTargetPrefixes]) | A platform's module group, optionally disabled on some targets. |
scenario(name, platforms [, description]) | A named subset of platforms. |
smoke { } / modrinth { } | Compatibility smoke and Modrinth release config (see the other pages). |
Selecting A Target Or Scenario
At invocation time you pick a target and a scenario with Gradle properties:
Scenario aggregate tasks (build<Scenario>, check<Scenario>,
publishToMavenLocal<Scenario>) are generated from your scenario(...)
declarations.
Inspecting The Matrix
These tasks print what the matrix resolves to, and feed CI:
| Task | Output |
|---|---|
magicutilsHelp | Human-readable summary of targets, scenarios, and usage. |
listBuildMatrix | The resolved target, platforms, scenario, and included projects. |
printBuildMatrix | JSON array of every target (for a CI build fan-out). |
printPublishMatrix | JSON of every target with its publish tasks (publish fan-out). |
printReleaseMatrix | JSON rows of (platform × smoke entry) for a release fan-out. |
Because the target list comes from targets.properties, adding a Minecraft
version never touches a workflow file: CI reads the matrix from these tasks.