Building
What you need installed, the Gradle commands that compile, test, run and package the mod, the flag that stops the version number moving, and how to build for the other Minecraft version.
On this page
This page is for anyone who has the source and wants a running game or a jar out of it: a contributor checking a change, or someone starting a fork. It covers what has to be installed, the three commands you will use every day, where the development client keeps its files, how a jar is named, the version-bump trap, the second Minecraft target, and the build failures people actually hit. If you do not have the source yet, Forking says how to get it.
What you need
| Tool | Version | Why |
|---|---|---|
| A JDK | 25 | Gradle itself has to run on it. The build compiles with options.release = 25. |
| Git | any | build.gradle asks Git for the commit and whether the tree is dirty, and stamps both into the jar. |
| Python 3 | Any 3.9 or later; CI's Python jobs use 3.12, the release job the JDK image's own python3 | Only for the tools in tools/, such as the feature tracker. They use the standard library and nothing else. |
| Gradle | none to install | The repository carries the wrapper, gradlew and gradlew.bat, which downloads the Gradle it was pinned to (9.5.1, as of 2.0.1). |
Everything else comes through Gradle: Minecraft, Fabric Loader 0.19.3, Fabric API, Fabric Loom,
the Hypixel Mod API and JUnit. As of 2.0.1, gradle.properties pins Loom 1.17.19 and JUnit
5.14.2, and each Minecraft version's Fabric API is pinned in its own file under gradle/versions/.
gradle.properties also gives the Gradle daemon a 2 GB heap (org.gradle.jvmargs=-Xmx2G) and turns on parallel
execution and the build cache. The configuration cache is off.
The three commands
These are the commands the project's README gives, and they cover almost everything you will do.
./gradlew compileJava test # the gate, and what CI runs
./gradlew runClient # a development client
./gradlew build -PnoVersionBump # a jar, into build/libsOn Windows the repository also has gradlew.bat: run gradlew build -PnoVersionBump in Command Prompt, or
.\gradlew build -PnoVersionBump in PowerShell.
Compile and test
./gradlew compileJava test is the check that matters. It is what the compile job in CI runs on every merge
request, with --console=plain --no-daemon added. If it passes on your machine, your change compiles and the
JUnit suite under src/test agrees with it.
The suite deliberately avoids booting the game, so it runs in seconds. A green run ends with one line that says how many tests ran:
Vantage tests: <n> run, <n> failed, <n> skippedA failing test prints its full stack trace to the console, because CI shows the job log and nothing else. Testing covers what the suite holds and the in-game harnesses that sit beside it.
Compiling also builds the feature index
The mod's build includes a second Gradle project, :processor, an annotation processor that reads every
@FeatureInfo and generates features.GeneratedFeatureIndex. You never run it yourself: compileJava builds
it first and runs it. It reports Vantage: indexed <n> features as a compiler note. A mistake in a
feature declaration is a compile error from this processor; the messages are listed under
When the build fails, and Writing a feature explains the rules
behind them.
Play from source
./gradlew runClient starts a Minecraft client with your build of Vantage loaded. The run is configured in the
loom block of build.gradle:
| Setting | Value |
|---|---|
| Run name | Minecraft Client |
| Player name | Dev, passed as --username Dev |
| JVM flag | -Dmixin.debug.export=true |
| Working directory | run/ in the repository root |
The game directory is run/, which the repository ignores. Everything the development client writes lands under
it, so Vantage's own files are in run/config/vantage/: the config profiles, hud.json, the caches and the
downloaded item repository. Delete that folder to see a first launch again.
This page does not cover signing the development client into a real Minecraft account. Two more run
configurations exist for automated checks, runUiSmokeTest and runPathSmokeTest; Testing explains
both.
Build a jar
./gradlew build -PnoVersionBump compiles, tests and packages the mod. The files land in build/libs/:
| File | What it is |
|---|---|
vantage-<mod version>+<minecraft version>.jar | the mod; for 26.2 its name ends in +26.2.jar |
vantage-<mod version>+<minecraft version>-sources.jar | the sources, from withSourcesJar() |
vantage-<mod version>+<minecraft version>.jar.sha256 | the jar's SHA-256, written by the signRelease task |
The name comes from two settings: archives_base_name in gradle.properties (vantage) and
version = "${mod_version}+${minecraft_version}" in build.gradle. Change archives_base_name in a fork and
every file above is renamed with it.
Each jar carries the LICENSE file, renamed LICENSE_<archives_base_name>, and a manifest that records where it
came from:
| Manifest attribute | Value |
|---|---|
Implementation-Title | Vantage |
Implementation-Version | the full version, <mod version>+<minecraft version> |
Implementation-Vendor | maven_group, dev.cherishev |
Minecraft-Version | the target the jar was built for |
Build-Timestamp | the UTC time of the build |
Build-Commit | the short commit hash, with -dirty when the tree had uncommitted changes |
The checksum is written for every jar that assemble produces, sources jars excepted, whether or not you sign
anything. Signing is covered at the end of this page.
The version-bump trap
Read this before your first build. Any Gradle invocation that names a jar-producing task rewrites
gradle.properties at configuration time, before a single class compiles, and adds one to the last number of
mod_version. The tasks that trigger it are jar, remapJar, build, assemble, publish and
publishToMavenLocal.
Vantage version <old> -> <new> (<commit>)That line in the output means it happened. The intent is that every jar that leaves a release machine has a
number of its own; the cost is that an ordinary local build churns a tracked file. Pass -PnoVersionBump and the
version stays where it is. If you forgot, put it back:
git checkout -- gradle.propertiesThe bump splits mod_version on dots and adds one to the last part. A version whose last part is not a number is
left alone with a warning. A pre-release such as x.y.z-beta.1 does end in a number, so it becomes -beta.2.
The other Minecraft version
One source tree builds a jar for each Minecraft version Vantage supports: 26.1.2 and 26.2. The default
is the version in gradle.properties (minecraft_version=26.2). Name the other one on the command line:
./gradlew build -Pminecraft_version=26.1.2 -PnoVersionBumpThe property picks three things: gradle/versions/<version>.properties (that version's Fabric API and the
Minecraft range the jar declares), the source overlay src/main/java-mc<version>/, and the resource overlay
src/main/resources-mc<version>/, which holds that version's access widener. It applies to every task, so
./gradlew runClient -Pminecraft_version=26.1.2 starts a 26.1.2 client.
Both jars go to the same build/libs/ and their names differ only by the suffix. The release job builds them one
after the other in the same checkout:
./gradlew build -Pminecraft_version=26.2 -PnoVersionBump
./gradlew build -Pminecraft_version=26.1.2 -PnoVersionBumpA version with no properties file or no overlay fails straight away instead of as a wall of missing-symbol errors. Multi-version explains the overlay rule and what it takes to add a version.
When the build fails
These are the failures the build itself was written to report, and the ones the CI file records as having happened.
| What you see | What it means | What to do |
|---|---|---|
Vantage does not support Minecraft <v>. It needs gradle/versions/<v>.properties and src/main/java-mc<v>. Supported: [...] | -Pminecraft_version names a version the tree does not have | use one of the listed versions, or add the version (Multi-version) |
gradle.properties shows as modified, mod_version one higher | a jar task ran without -PnoVersionBump | git checkout -- gradle.properties |
| the build stops before Vantage's own code compiles, on an error that names a Java version | Gradle is running on a JDK other than 25 | set JAVA_HOME to a JDK 25; check with ./gradlew --version |
./gradlew: Permission denied | the wrapper lost its executable bit, as it can on a checkout made on Windows | chmod +x ./gradlew |
duplicate feature id '…', already declared by … | two classes declare the same @FeatureInfo id | give one of them a new id |
feature id '…' must be lower_snake_case ([a-z0-9] words joined by _) | the id has capitals, spaces or hyphens | rename it, before anyone has settings stored under it |
feature '…' has a blank description; it is shown in the menu | description is empty | write one |
feature '…' is abstract; @FeatureInfo belongs on the concrete class | the annotation is on a base class | move it to the class that is constructed |
feature '…' does not extend dev.cherishev.vantage.core.feature.Feature | the annotated class is not a feature | extend Feature, or remove the annotation |
feature '…' needs an accessible no-argument constructor for the index to build it | the only constructor takes arguments, or is private | add a no-argument constructor that calls super() |
Build-Commit: unknown in the jar's manifest | Git was not on the PATH during the build | install Git; the build does not fail without it, it only loses the commit |
Some mistakes compile and fail when the game starts instead. An @Option field that is static or final, of a
type the config cannot store, or a Runnable option with no action, throws an IllegalArgumentException naming
the field while the features initialise, and nothing catches it there, so the client does not start. A feature
whose constructor throws is different: it is logged as Feature '<id>' failed to register and the rest of the mod
carries on without it.
Options and config lists the types an option may have.
Signing, if you want it
The signRelease task runs after every assemble. It always writes the .sha256 files. It signs the jars only
when you give it a keystore, through project properties that can live in ~/.gradle/gradle.properties rather
than in the repository:
./gradlew build -PnoVersionBump -PsignKeystore=/path/to/ks.jks -PsignAlias=vantage -PsignStorePass=…-PsignKeyPass (defaulting to the store password) and -PsignTsa=<timestamp authority URL> are optional. The
task shells out to the JDK's jarsigner. Without a keystore it logs No signing keystore configured and carries
on; a signing attempt that fails is a warning, not a failed build.
The other build-time secret is the Vet card key. build.gradle bakes it into assets/vantage/vet.key from
-PvetKey or the VANTAGE_VET_KEY environment variable. A build with neither is still a working build: the
client logs that no Vet card key was baked in, signs cards with the development key, and a real bot rejects them.
Forking says what that means for a fork, and Releasing why a release refuses
a jar without a real key.