All pages

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.

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

ToolVersionWhy
A JDK25Gradle itself has to run on it. The build compiles with options.release = 25.
Gitanybuild.gradle asks Git for the commit and whether the tree is dirty, and stamps both into the jar.
Python 3Any 3.9 or later; CI's Python jobs use 3.12, the release job the JDK image's own python3Only for the tools in tools/, such as the feature tracker. They use the standard library and nothing else.
Gradlenone to installThe 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/libs

On 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> skipped

A 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:

SettingValue
Run nameMinecraft Client
Player nameDev, passed as --username Dev
JVM flag-Dmixin.debug.export=true
Working directoryrun/ 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/:

FileWhat it is
vantage-<mod version>+<minecraft version>.jarthe mod; for 26.2 its name ends in +26.2.jar
vantage-<mod version>+<minecraft version>-sources.jarthe sources, from withSourcesJar()
vantage-<mod version>+<minecraft version>.jar.sha256the 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 attributeValue
Implementation-TitleVantage
Implementation-Versionthe full version, <mod version>+<minecraft version>
Implementation-Vendormaven_group, dev.cherishev
Minecraft-Versionthe target the jar was built for
Build-Timestampthe UTC time of the build
Build-Committhe 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.properties

The 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 -PnoVersionBump

The 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 -PnoVersionBump

A 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 seeWhat it meansWhat 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 haveuse one of the listed versions, or add the version (Multi-version)
gradle.properties shows as modified, mod_version one highera jar task ran without -PnoVersionBumpgit checkout -- gradle.properties
the build stops before Vantage's own code compiles, on an error that names a Java versionGradle is running on a JDK other than 25set JAVA_HOME to a JDK 25; check with ./gradlew --version
./gradlew: Permission deniedthe wrapper lost its executable bit, as it can on a checkout made on Windowschmod +x ./gradlew
duplicate feature id '…', already declared by …two classes declare the same @FeatureInfo idgive 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 hyphensrename it, before anyone has settings stored under it
feature '…' has a blank description; it is shown in the menudescription is emptywrite one
feature '…' is abstract; @FeatureInfo belongs on the concrete classthe annotation is on a base classmove it to the class that is constructed
feature '…' does not extend dev.cherishev.vantage.core.feature.Featurethe annotated class is not a featureextend Feature, or remove the annotation
feature '…' needs an accessible no-argument constructor for the index to build itthe only constructor takes arguments, or is privateadd a no-argument constructor that calls super()
Build-Commit: unknown in the jar's manifestGit was not on the PATH during the buildinstall 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.