Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/build-mods.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Build Mods
on:
push:
branches:
- main
pull_request:
jobs:
build:
name: Build ${{ matrix.platform }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
platform: [ fabric, forge, neoforge ]
steps:
- name: Checkout sources
uses: actions/checkout@v7.0.1
- name: Setup Java
uses: actions/setup-java@v6
with:
distribution: 'temurin'
java-version: 25
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
with:
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
- name: Build with Gradle
run: ./gradlew build -PmodPlatforms=${{ matrix.platform }} -p ${{ matrix.platform }}
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ jobs:
with:
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
- name: Build with Gradle
run: ./gradlew build
run: ./gradlew build -PmodPlatforms=none
29 changes: 29 additions & 0 deletions .github/workflows/maven-publish-mods.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Maven Publish Mods
on:
release:
types: [ prereleased, released ]
jobs:
build:
name: Publish ${{ matrix.platform }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
platform: [ fabric, forge, neoforge ]
steps:
- name: Checkout sources
uses: actions/checkout@v7.0.1
- name: Setup Java
uses: actions/setup-java@v6
with:
distribution: 'temurin'
java-version: 25
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
with:
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
- name: Publish with Gradle to Repository
env:
REPOSITORY_USER: ${{ secrets.REPOSITORY_USER }}
REPOSITORY_TOKEN: ${{ secrets.REPOSITORY_TOKEN }}
run: ./gradlew publish -PmodPlatforms=${{ matrix.platform }} -p ${{ matrix.platform }}
2 changes: 1 addition & 1 deletion .github/workflows/maven-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ jobs:
env:
REPOSITORY_USER: ${{ secrets.REPOSITORY_USER }}
REPOSITORY_TOKEN: ${{ secrets.REPOSITORY_TOKEN }}
run: ./gradlew publish
run: ./gradlew publish -PmodPlatforms=none
43 changes: 27 additions & 16 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,39 @@ Run Gradle from the repository root:
./gradlew build
```

This compiles, tests, and packages every module, including the Fabric and NeoForge artifacts for every supported
This compiles, tests, and packages every module, including the Fabric, Forge and NeoForge artifacts for every supported
Minecraft version. To work on a single version, build only its project:

```sh
./gradlew :fabric:versions:1.21.10:build
./gradlew :forge:versions:1.21.10:build
./gradlew :neoforge:versions:1.21.10:build
```

The `modPlatforms` property limits which mod platforms are part of the build at all, which skips configuring them.
CI uses it to build the plugin modules and each mod platform in separate jobs:

```sh
./gradlew build -PmodPlatforms=none # everything except Fabric, Forge and NeoForge
./gradlew build -PmodPlatforms=fabric -p fabric # only Fabric and its Minecraft versions
```

## Minecraft versions

Fabric and NeoForge are built and published once per Minecraft version, as
`dev.faststats.metrics:<fabric|neoforge>:<sdk-version>+mc<minecraft-version>`. Each artifact only declares the
Fabric, Forge and NeoForge are built and published once per Minecraft version, as
`dev.faststats.metrics:<fabric|forge|neoforge>:<sdk-version>+mc<minecraft-version>`. Each artifact only declares the
Minecraft version it was built for.

### Where the code lives

| Directory | Contents |
|---------------------------------|--------------------------------------------------------------|
| `fabric/`, `neoforge/` | Platform code that is the same for every Minecraft version |
| `onboarding/` | Onboarding code without any Minecraft dependency |
| `fabric/versions/<version>` | Fabric code that depends on the Minecraft version |
| `neoforge/versions/<version>` | NeoForge code that depends on the Minecraft version |
| `onboarding/versions/<version>` | The consent screen and its mixins, compiled into both of them |
| Directory | Contents |
|----------------------------------|--------------------------------------------------------------|
| `fabric/`, `forge/`, `neoforge/` | Platform code that is the same for every Minecraft version |
| `onboarding/` | Onboarding code without any Minecraft dependency |
| `fabric/versions/<version>` | Fabric code that depends on the Minecraft version |
| `forge/versions/<version>` | Forge code that depends on the Minecraft version |
| `neoforge/versions/<version>` | NeoForge code that depends on the Minecraft version |
| `onboarding/versions/<version>` | The consent screen and its mixins, compiled into all of them |

The directories in `<platform>/versions` are called variants. A variant is named after the first Minecraft version
that needs its code, and every later version is built from it until the next variant. For example, Fabric 1.21.10 is
Expand All @@ -40,27 +50,28 @@ Each Minecraft version still gets its own Gradle project, `:<platform>:versions:
against exactly that version. Their directories in `<platform>/versions` are created by Gradle and only contain build
output.

The onboarding sources are compiled into the Fabric and NeoForge projects rather than built on their own. Files that
The onboarding sources are compiled into the Fabric, Forge and NeoForge projects rather than built on their own. Files that
are identical between onboarding variants only exist in the oldest variant using them. The other variants reference
them through the `sharedSources` table in `build-logic/src/main/kotlin/faststats.onboarding-sources.gradle.kts`.

### Naming

- **Variant directories:** the Minecraft version, e.g. `1.21.9` or `26.1`.
- **Fabric and NeoForge packages:** the variant with underscores, e.g. `dev.faststats.fabric.compat.v1_21_9`. Each
- **Fabric, Forge and NeoForge packages:** the variant with underscores, e.g. `dev.faststats.fabric.compat.v1_21_9`. Each
variant registers its `CompatibilityLayerImpl` in
`src/main/resources/META-INF/services/dev.faststats.<platform>.compat.CompatibilityLayer`.
- **Onboarding packages:** the same in every variant, `dev.faststats.screen.minecraft` and
`dev.faststats.screen.minecraft.mixin`, so files can be shared between variants.
- **Version catalog entries:** the Minecraft version with `x` instead of dots, e.g. `minecraft-v1x21x10`,
`fabric-api-v1x21x10` and `neoforge-v1x21x10`.
`fabric-api-v1x21x10`, `forge-v1x21x10` and `neoforge-v1x21x10`.

## Adding a Minecraft version

1. Add the version to `gradle/libs.versions.toml` (the release numbers below are placeholders):

```toml
[versions]
forge-v26x4 = "26.4-67.0.0"
neoforge-v26x4 = "26.4.0.1"

[libraries]
Expand All @@ -82,7 +93,7 @@ them through the `sharedSources` table in `build-logic/src/main/kotlin/faststats
3. Build the new version:

```sh
./gradlew :fabric:versions:26.4:build :neoforge:versions:26.4:build
./gradlew :fabric:versions:26.4:build :forge:versions:26.4:build :neoforge:versions:26.4:build
```

If it builds, the version is done. It is published with the next release.
Expand All @@ -92,11 +103,11 @@ If it builds, the version is done. It is published with the next release.
If a version doesn't compile, or needs different behavior, give it its own variant on the platform that changed:

1. Copy the closest older variant, e.g. `fabric/versions/26.1` to `fabric/versions/26.4`.
2. For Fabric and NeoForge, rename the package to the new variant, e.g. `v26_1` to `v26_4`, and update the class name
2. For Fabric, Forge and NeoForge, rename the package to the new variant, e.g. `v26_1` to `v26_4`, and update the class name
in its `META-INF/services` file.
3. For onboarding, the new variant only needs the files that change. For every file it takes unchanged from an older
variant, add it to that file's `sharedSources` entry, or add an entry if the file doesn't have one yet.
4. Fix the code and build every version from the new one onwards, as they now use the new variant too.

A variant only replaces the code of its own platform. A new Fabric variant doesn't affect NeoForge or onboarding, and
A variant only replaces the code of its own platform. A new Fabric variant doesn't affect Forge, NeoForge or onboarding, and
the other way around.
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@ Use `build` for the reusable FastStats libraries:
./gradlew :velocity:build
```

Library jars are written to each module's `build/libs` directory. Fabric and NeoForge compatibility artifacts are
Library jars are written to each module's `build/libs` directory. Fabric, Forge and NeoForge compatibility artifacts are
published under stable Maven artifact IDs for every supported Minecraft version:

```text
dev.faststats.metrics:fabric:<sdk-version>+mc26.1.2
dev.faststats.metrics:neoforge:<sdk-version>+mc26.1.2
dev.faststats.metrics:fabric:<sdk-version>+mc26.3
dev.faststats.metrics:forge:<sdk-version>+mc26.3
dev.faststats.metrics:neoforge:<sdk-version>+mc26.3
```

Each artifact only declares the Minecraft version it was built for in its `fabric.mod.json` or `neoforge.mods.toml`.
Each artifact only declares the Minecraft version it was built for in its `fabric.mod.json`, `mods.toml` or
`neoforge.mods.toml`.

See [CONTRIBUTING.md](CONTRIBUTING.md) for how the version-specific code is organized and how to add a Minecraft
version.
Expand All @@ -43,3 +45,9 @@ To compile and test all modules with the standard lifecycle, run:
./gradlew build
```

To build everything except the Fabric, Forge and NeoForge mod platforms, which is much faster, run:

```sh
./gradlew build -PmodPlatforms=none
```

1 change: 1 addition & 0 deletions build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ fun plugin(plugin: Provider<PluginDependency>) = plugin.map {

dependencies {
implementation(plugin(libs.plugins.fabric.loom))
implementation(plugin(libs.plugins.forgegradle))
implementation(plugin(libs.plugins.moddev))
implementation(plugin(libs.plugins.shadow))
}
3 changes: 2 additions & 1 deletion build-logic/src/main/kotlin/MinecraftVersions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,12 @@ val minecraftVersions = listOf(
private val platformVersions = mapOf(
"fabric" to minecraftVersions,
"neoforge" to minecraftVersions.dropWhile { it.name != "1.20.6" },
"forge" to minecraftVersions.dropWhile { it.name != "1.20.6" }.filter { it.name != "1.21.2" },
"onboarding" to minecraftVersions,
)

// the platforms with a project per Minecraft version, the onboarding sources are compiled into them
val minecraftPlatforms = setOf("fabric", "neoforge")
val minecraftPlatforms = setOf("fabric", "forge", "neoforge")

fun minecraftVersions(platform: String): List<MinecraftVersion> = platformVersions.getValue(platform)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ plugins {
id("maven-publish")
}

// version projects are configured by their parent before they have a catalog of their own
val libs = rootProject.the<VersionCatalogsExtension>().named("libs")
val minecraft = minecraftVersion

Expand Down
67 changes: 67 additions & 0 deletions build-logic/src/main/kotlin/faststats.forge-compat.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import net.minecraftforge.gradle.ForgeGradleExtension
import net.minecraftforge.gradle.MinecraftExtensionForProject

plugins {
id("net.minecraftforge.gradle")
id("faststats.minecraft-version")
id("faststats.onboarding-sources")
id("faststats.shadow-conventions")
id("maven-publish")
}

val libs = rootProject.the<VersionCatalogsExtension>().named("libs")
val minecraft = minecraftVersion

extra.set("publishArtifactId", "forge")
extra.set("publishDocsUrl", "https://docs.faststats.dev/java/platform/forge")
extra.set("publishVersionSuffix", "mc$minecraft")

val forgeGradle = the<ForgeGradleExtension>()
val minecraftExtension = the<MinecraftExtensionForProject>()

if (minecraft.obfuscated) minecraftExtension.mappings("official", minecraft.name)

repositories {
minecraftExtension.mavenizer(this)
maven(forgeGradle.forgeMaven)
maven(forgeGradle.minecraftLibsMaven)
}

sourceSets.main {
java.srcDir(project(":forge").file("src/mod/java"))
}

tasks.processResources {
from(project(":forge").file("src/main/resources"))
val version = provider { project.version.toString() }
val range = "[${minecraft.name}]"
inputs.property("version", version)
inputs.property("minecraft", range)
filesMatching("META-INF/mods.toml") {
expand("version" to version.get(), "minecraft" to range)
}
// Forge ships Mixin 0.8.5 (1.20.6) or 0.8.7, which don't know compatibility levels above JAVA_18 or JAVA_21
val maxMixinLevel = if (minecraft.name == "1.20.6") 18 else 21
inputs.property("maxMixinLevel", maxMixinLevel)
filesMatching("faststats-onboarding.mixins.json") {
filter { line ->
line.replace(Regex("\"JAVA_(\\d+)\"")) { level ->
if (level.groupValues[1].toInt() > maxMixinLevel) "\"JAVA_$maxMixinLevel\"" else level.value
}
}
}
}

tasks.withType<Jar>().configureEach {
manifest.attributes("MixinConfigs" to "faststats-onboarding.mixins.json")
}

val forge = libs.findVersion("forge-${minecraft.catalogName}").get().requiredVersion

dependencies {
compileOnly(minecraftExtension.dependency("net.minecraftforge:forge:$forge"))
implementation(project(":core"))
implementation(project(":config"))
implementation(project(":onboarding"))
implementation(project(":forge"))
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
include(
"fabric",
"neoforge",
"onboarding"
)
// -PmodPlatforms=fabric,forge limits which mod platforms are included, -PmodPlatforms=none excludes all of them
val modPlatforms = providers.gradleProperty("modPlatforms").orNull?.let { property ->
property.split(',').map(String::trim).filter { it.isNotEmpty() && it != "none" }.toSet().onEach {
require(it in minecraftPlatforms) { "Unknown mod platform \"$it\", expected one of $minecraftPlatforms" }
}
} ?: minecraftPlatforms

include("onboarding")
modPlatforms.forEach { include(it) }

(minecraftPlatforms + "onboarding").forEach { platform ->
file("$platform/versions").listFiles { file -> file.resolve("src").isDirectory }?.forEach { variant ->
Expand All @@ -13,7 +17,7 @@ include(
}

// one project per platform and Minecraft version, see MinecraftVersions.kt
minecraftPlatforms.forEach { platform ->
modPlatforms.forEach { platform ->
minecraftVersions(platform).forEach { version ->
val path = ":$platform:versions:${version.name}"
include(path)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ plugins {
id("maven-publish")
}

// version projects are configured by their parent before they have a catalog of their own
val libs = rootProject.the<VersionCatalogsExtension>().named("libs")
val minecraft = minecraftVersion

Expand Down
26 changes: 26 additions & 0 deletions forge/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
extra.set("moduleName", "dev.faststats.forge")
extra.set("publishArtifactId", "forge")
extra.set("publishDocsUrl", "https://docs.faststats.dev/java/platform/forge")

plugins {
id("faststats.shadow-conventions")
id("maven-publish")
}

java.toolchain.languageVersion = JavaLanguageVersion.of(25)

tasks.compileJava {
options.release.set(21)
}

dependencies {
implementation(project(":core"))
implementation(project(":config"))
compileOnlyApi(project(":onboarding"))
compileOnly(libs.gson)
compileOnly(libs.slf4j.api)
}

subprojects {
if (parent?.path == ":forge:versions") pluginManager.apply("faststats.forge-compat")
}
Loading
Loading