-
Notifications
You must be signed in to change notification settings - Fork 1
Runtime Dependency Staging
The Hytale Gradle Plugin separates several concepts that are often handled by a single Gradle classpath:
- compile-time visibility
- normal JVM runtime visibility
- Hytale plugin discovery
- Hytale plugin load ordering
- IDE source generation
Choosing the correct dependency configuration determines where a dependency is available and how it is staged for a local development server.
vineServerJar ────────────────> compileOnly / compileClasspath
vineCompileOnly ──────────────> compileOnly / compileClasspath
vineImplementation ───────────> implementation / runtimeClasspath
requiredDependency ───────────> implementation / runtimeClasspath
optionalDependency ───────────> compileOnly / compileClasspath
vineDecompileTargets ─────────> IDE decompilation targets
vineMod ──────────────────────> run/mods as a complete plugin jar
vineImplementation plugin jar
(contains manifest.json) ───> run/mods as a complete plugin jar
└─> expanded shared runtime classpath
(manifest.json omitted)
vineImplementation library jar
(no manifest.json) ─────────> expanded shared runtime classpath only
| Configuration | Compile visibility | Shared runtime classpath | Copied to run/mods
|
Source generation |
|---|---|---|---|---|
vineServerJar |
Yes | Server runtime | No | Server sources |
vineImplementation library |
Yes | Yes | No | Yes |
vineImplementation plugin |
Yes | Yes | Yes | Yes |
vineCompileOnly |
Yes | No | No | Yes |
requiredDependency |
Yes | Yes | Depends on resolved artifact staging | Yes |
optionalDependency |
Yes | No | No | Yes |
vineMod |
No shared API visibility | No | Yes | No |
vineDecompileTargets |
No additional classpath behavior | No | No | Yes |
The exact staging behavior of an alias-backed dependency follows the runtime configurations into which it participates. The aliases do not automatically create Hytale manifest declarations.
vineServerJar contains the Hytale server dependency used for compilation, development runs, and source generation.
The plugin normally adds it automatically from hytaleVersion:
hytaleTools {
hytaleVersion = '0.+'
}This is equivalent to resolving:
dependencies {
vineServerJar "com.hypixel.hytale:Server:${hytaleVersion}"
}The server API is exposed through compileOnly, so it is not bundled into your final mod JAR.
You normally should not declare vineServerJar manually.
Use vineImplementation when:
- your code directly references classes from the dependency
- the dependency is required during local development
- the dependency is a normal runtime library
- the dependency is a Hytale plugin whose API must be visible to your code
Example:
dependencies {
vineImplementation 'com.example:some-runtime-plugin:1.0.0'
}The plugin inspects the resolved JAR for a root-level:
manifest.json
The result determines how the dependency is staged.
When no root-level manifest.json is present, the JAR is treated as a normal library.
The library is:
- expanded into an isolated runtime-classpath directory
- added to the shared server classpath
- not copied to
run/mods
When a root-level manifest.json is present, the JAR is treated as a Hytale plugin.
The plugin JAR is:
- copied intact into
run/modsfor Hytale plugin discovery - expanded into an isolated shared-runtime directory
- made available to the shared JVM classloader
The expanded copy excludes manifest.json. This prevents Hytale from discovering the expanded runtime directory as a second copy of the plugin.
Signature metadata under META-INF is also excluded from expanded copies:
*.SF
*.RSA
*.DSA
This avoids invalid signature metadata after the contents have been expanded or filtered.
Hytale plugin discovery and Java class loading are separate concerns.
The complete JAR in run/mods provides:
- the plugin manifest
- the plugin identity
- Hytale plugin discovery
- plugin lifecycle loading
The expanded runtime-classpath directory provides:
- API classes to the shared JVM classloader
- direct class visibility for dependent code
- consistent local runtime behavior
Placing only the original JAR on the ordinary Java launch classpath would not correctly represent all of the plugin's runtime roles. Copying only the JAR to run/mods may also be insufficient when dependent code requires shared classloader visibility.
The staging process therefore handles both concerns.
Use vineMod when a plugin needs to be installed for the development server but your code does not directly reference its classes through the shared JVM classpath.
dependencies {
vineMod 'com.example:some-isolated-plugin:1.0.0'
}A vineMod dependency is:
- copied intact to
run/mods - discoverable by Hytale
- not expanded onto the shared runtime classpath
This is useful for standalone server plugins that only need to run alongside your mod.
Do not declare the same plugin through both vineImplementation and vineMod.
Use vineImplementation when your source code imports or otherwise references classes from the dependency:
dependencies {
vineImplementation 'com.example:economy-plugin:1.0.0'
}Use vineMod when the plugin only needs to be installed and discovered:
dependencies {
vineMod 'com.example:administration-plugin:1.0.0'
}A useful rule is:
Need the dependency's classes in your code?
Yes -> vineImplementation
No -> vineMod
This does not replace the need to declare Hytale manifest dependencies when load ordering is required.
Use vineCompileOnly for a dependency needed to compile the project but not staged into the local development runtime:
dependencies {
vineCompileOnly 'com.example:compile-api:1.0.0'
}The dependency is:
- visible to the compiler
- excluded from local runtime staging
- included in the source-generation pipeline
Use this when the dependency is supplied by another runtime mechanism or when only its API types are required during compilation.
requiredDependency expresses a required integration at the Gradle classpath level:
dependencies {
requiredDependency 'com.example:economy-api:1.2.0'
}It:
- extends
implementation - is available during compilation
- is available during local runtime
- participates in decompilation
- participates in IDE source attachment
It does not automatically add the corresponding Hytale plugin identifier to manifest.json.
Declare the manifest side separately:
hytaleTools {
manifestDependencies = 'Example:Economy=*'
}A complete declaration therefore looks like:
dependencies {
requiredDependency 'com.example:economy-api:1.2.0'
}
hytaleTools {
manifestDependencies = 'Example:Economy=*'
}The Maven coordinate and Hytale plugin identifier are separate values and cannot be reliably inferred from each other.
optionalDependency expresses an optional integration:
dependencies {
optionalDependency 'com.example:placeholder-api:2.0.0'
}It:
- extends
compileOnly - is available while compiling optional integration code
- is not placed on the local runtime classpath by this alias
- participates in decompilation
- participates in IDE source attachment
Declare the corresponding manifest entry separately:
hytaleTools {
manifestOptionalDependencies =
'Example:PlaceholderAPI=*'
}A complete optional integration looks like:
dependencies {
optionalDependency 'com.example:placeholder-api:2.0.0'
}
hytaleTools {
manifestOptionalDependencies =
'Example:PlaceholderAPI=*'
}Your plugin must handle the dependency being absent at runtime.
Use vineDecompileTargets when you want generated IDE sources without changing the dependency's compile or runtime behavior:
dependencies {
vineDecompileTargets 'com.example:some-api:1.0.0'
}Then run:
./gradlew prepareDecompiledSourcesForIdeThis configuration is useful when:
- the dependency enters the project through another configuration
- only readable IDE sources are required
- you do not want to change runtime staging behavior
Gradle dependency configurations determine Java classpath and staging behavior.
The following manifest fields determine Hytale plugin requirements and load ordering:
hytaleTools {
manifestDependencies =
'Example:Economy=*,Example:Core=>=2.0.0'
manifestOptionalDependencies =
'Example:PlaceholderAPI=*'
}Manifest entries use:
Group:Name=Version
Multiple values are comma-separated without spaces:
manifestDependencies =
'GroupOne:DependencyOne=*,GroupTwo:DependencyTwo=1.0.0'Do not use:
manifestDependencies =
'GroupOne:DependencyOne=*, GroupTwo:DependencyTwo=1.0.0'Use manifestDependencies when your plugin cannot load or operate without the dependency.
Use manifestOptionalDependencies when your plugin can operate without it but enables an integration when it is present.
For runServer, expanded dependency runtime entries are generated under:
build/hytale-vine-runtime/classpath/
The current project is launched using:
- compiled class directories
- resource source directories
- the normal project runtime classpath
- expanded
vineImplementationruntime entries - complete staged plugin JARs
- the resolved Hytale server JAR
The current project's output and original raw vineImplementation JARs are excluded where the plugin supplies their prepared equivalents.
The staging directories are regenerated automatically. Do not store manual files inside them.
For runAllMods, shared dependency runtime entries are generated under:
build/hytale-vine-runtime/workspace-classpath/
Workspace mods are staged under:
run/mods/<manifest-group>_<mod-id>/
Each development plugin directory contains linked project output rather than a packaged copy of the project's final JAR.
The workspace staging process includes:
- compiled class output
- project resources
- external plugin dependencies
- shared runtime libraries
- workspace-wide validation
All configured mod projects must use the same effective:
hytaleVersionpatchline
Workspace plugin identifiers must also be unique by:
manifestGroup:modId
Workspace projects are staged as development directories.
The plugin prefers symbolic links for files. On Windows, it may try hard links before falling back to copying.
This allows class and resource changes to become available without rebuilding a complete project JAR.
When the plugin must use a copy fallback, live changes to that copied file are not reflected until the staging task runs again:
./gradlew stageAllModAssetsThe staging process tracks plugin-managed entries and removes stale outputs created by previous staging runs.
Unrelated manually added files are intentionally preserved.
A normal shared-code module should remain a standard Gradle module:
plugins {
id 'java-library'
}For example:
// common/build.gradle
plugins {
id 'java-library'
}A mod project can depend on it normally:
dependencies {
implementation project(':common')
}Do not apply com.azuredoom.hytale-tools to the shared module unless that module is itself a loadable Hytale plugin with its own manifest and entry point.
Do not declare the same plugin through multiple staging configurations:
dependencies {
// Do not do this:
vineImplementation 'com.example:plugin:1.0.0'
vineMod 'com.example:plugin:1.0.0'
}The workspace also rejects different dependencies that resolve to the same output filename because only one file can occupy that location under run/mods.
Remove duplicate or conflicting declarations rather than relying on dependency order.
Dependencies declared through the Hytale-aware configurations participate in IDE source generation.
Run:
./gradlew prepareDecompiledSourcesForIdeThe task processes dependencies from:
vineImplementationvineCompileOnlyrequiredDependencyoptionalDependencyvineDecompileTargets
Generated repositories are written under:
build/generated-sources-m2/
build/generated-sources-ivy/
This is one reason to prefer the plugin's Hytale-aware configurations over plain Gradle implementation or compileOnly for Hytale integrations.
dependencies {
// Plugin API referenced by this project's code and required locally.
vineImplementation \
'com.example:economy-plugin:1.0.0'
// Plugin that only needs to be installed in run/mods.
vineMod \
'com.example:administration-plugin:1.0.0'
// Compile-time API that should not be staged locally.
vineCompileOnly \
'com.example:external-api:1.0.0'
// Required integration alias.
requiredDependency \
'com.example:permissions-api:2.0.0'
// Optional integration alias.
optionalDependency \
'com.example:placeholder-api:3.0.0'
// Extra dependency for readable IDE sources.
vineDecompileTargets \
'com.example:utility-api:1.5.0'
}
hytaleTools {
manifestDependencies =
'Example:Economy=*,Example:Permissions=*'
manifestOptionalDependencies =
'Example:PlaceholderAPI=*'
}A Java dependency alone does not establish Hytale plugin discovery or load ordering.
Check that:
- The dependency uses
vineImplementationorvineModwhen it must run locally. - The dependency JAR contains
manifest.jsonat the root. - Your own manifest declares it in
manifestDependencieswhen required. - No stale manual copy is overriding the staged plugin.
Inspect staging with:
./gradlew prepareRunServer --infoFor a workspace:
./gradlew stageAllModAssets --infoThe logs indicate whether the resolved dependency was recognized as a plugin or a library.
Make sure both sides are configured:
dependencies {
vineImplementation 'com.example:plugin:1.0.0'
}
hytaleTools {
manifestDependencies = 'Example:Plugin=*'
}vineImplementation provides Java classpath visibility and development staging.
manifestDependencies tells Hytale that the dependency must be loaded for your plugin.
Inspect the dependency JAR:
unzip -l dependency.jar | grep manifest.jsonThe required path is:
manifest.json
It must be at the root of the JAR. A manifest stored under another directory will not identify the artifact as a Hytale plugin.
This is expected. vineMod does not add the plugin to the shared application classpath.
Change it to vineImplementation when your code must reference the plugin's classes:
dependencies {
vineImplementation 'com.example:plugin:1.0.0'
}optionalDependency supplies compile-time visibility but does not install the dependency.
Install the optional plugin separately for testing or declare it through an appropriate runtime configuration when it should be present in the local environment.
Your code must still check whether the optional plugin is available before calling its API.
This is intentional.
Declare both the Maven dependency and Hytale plugin identifier:
dependencies {
requiredDependency 'com.example:economy-api:1.0.0'
}
hytaleTools {
manifestDependencies = 'Example:Economy=*'
}Check that all workspace projects use the same effective:
- Hytale version
- patchline
Also verify:
- every mod has a unique
manifestGroup:modId - every configured project produces compiled class output
- every configured resource directory exists
- the correct host project is selected
- no dependencies collide on the same staged filename
Run:
./gradlew stageAllModAssets --infoThe staging process may have used a file-copy fallback instead of a link.
Rerun:
./gradlew stageAllModAssetsThen restart the development server when the changed class cannot be hot swapped.
Plugin-managed entries are tracked and cleaned automatically. Unrelated manual files are preserved.
Remove obsolete manually installed files yourself, then rerun:
./gradlew prepareRunServerOr for a workspace:
./gradlew stageAllModAssets- Why use this plugin?
- Quickstart
- Hot Swap Quickstart
- Usage
- Running and Customizing the Development Server
- VS Code Setup
- Configuration
- Extension Reference
- Configurations
- Dependencies
- Manifest dependency fields
- SubPlugins
- AssetBridge