Skip to content

Runtime Dependency Staging

AzureDoom edited this page Jul 26, 2026 · 1 revision

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.

Dependency Flow

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 Overview

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

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.

vineImplementation

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.

Normal Library

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

Hytale Plugin

When a root-level manifest.json is present, the JAR is treated as a Hytale plugin.

The plugin JAR is:

  • copied intact into run/mods for 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.

Why Plugin JARs Are Both Copied and Expanded

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.

vineMod

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.

Choosing Between 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.

vineCompileOnly

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

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

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.

vineDecompileTargets

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 prepareDecompiledSourcesForIde

This 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

Manifest Dependencies

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.

Single-Project Staging

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 vineImplementation runtime 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.

Workspace Staging

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:

  • hytaleVersion
  • patchline

Workspace plugin identifiers must also be unique by:

manifestGroup:modId

Development Directory Linking

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 stageAllModAssets

The staging process tracks plugin-managed entries and removes stale outputs created by previous staging runs.

Unrelated manually added files are intentionally preserved.

Shared Modules

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.

Duplicate Runtime Dependencies

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.

Source Attachment

Dependencies declared through the Hytale-aware configurations participate in IDE source generation.

Run:

./gradlew prepareDecompiledSourcesForIde

The task processes dependencies from:

  • vineImplementation
  • vineCompileOnly
  • requiredDependency
  • optionalDependency
  • vineDecompileTargets

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.

Complete Dependency Example

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=*'
}

Troubleshooting

The project compiles, but Hytale cannot find the plugin

A Java dependency alone does not establish Hytale plugin discovery or load ordering.

Check that:

  1. The dependency uses vineImplementation or vineMod when it must run locally.
  2. The dependency JAR contains manifest.json at the root.
  3. Your own manifest declares it in manifestDependencies when required.
  4. No stale manual copy is overriding the staged plugin.

Inspect staging with:

./gradlew prepareRunServer --info

For a workspace:

./gradlew stageAllModAssets --info

The logs indicate whether the resolved dependency was recognized as a plugin or a library.

The API reports that the dependency is not loaded

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.

A vineImplementation dependency is treated as a library

Inspect the dependency JAR:

unzip -l dependency.jar | grep manifest.json

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

A vineMod API cannot be referenced by the project

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'
}

An optional integration fails at runtime

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.

A required or optional alias does not update manifest.json

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=*'
}

runServer works but runAllMods fails

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 --info

Resource or class changes do not appear

The staging process may have used a file-copy fallback instead of a link.

Rerun:

./gradlew stageAllModAssets

Then restart the development server when the changed class cannot be hot swapped.

Stale files remain in run/mods

Plugin-managed entries are tracked and cleaned automatically. Unrelated manual files are preserved.

Remove obsolete manually installed files yourself, then rerun:

./gradlew prepareRunServer

Or for a workspace:

./gradlew stageAllModAssets

Related Pages

Clone this wiki locally