Compare commits

...
22 Commits
Author SHA1 Message Date
LexManos 8ec8685098 Fix test detection in gradle 2026-11-12 13:25:41 -08:00
LexManos f375683bcd Fix Mod Sorting reporting incorrect cycles when sorting fails. Closes #10942
Co-authored-by: ZZZank <[email protected]>
2026-11-12 13:16:53 -08:00
LexManos 9cd61b99f1 Add junit tests for fmlloader Topological Sort
Co-authored-by: ZZZank <[email protected]>
2026-11-12 13:12:19 -08:00
Paint_Ninja ad5ca0b67d Deprecate more rarely used APIs for removal (#10907) 2026-11-02 14:21:52 -08:00
berkay8987 b76746e62f Fix missing forge config UI translation keys (#10909) 2026-11-02 14:10:58 -08:00
Paint_Ninja 4cfa1f83c4 Deprecate EventBusMigrationHelper for removal (#10908)
Migrating away from this should be trivial for mod devs. Refer to the PR for details.
2026-11-02 12:59:01 +00:00
LexManos da7e5f8bfa Fix EntityTeleportEvent.ChorusFruit is not fired Closes #10906 2026-10-29 12:17:44 -07:00
Paint_Ninja cd8eccc29c Deprecate some rarely used events for removal (#10904)
See PR description for details
2026-10-29 17:49:21 +00:00
LexManos 7d0bd1898f 26.3 RB 1 2026-10-21 11:02:26 -07:00
LexManos 55f03a2d86 Fix incorrect filter when placing items on bundles. Closes #10902 2026-10-16 13:40:27 -07:00
LexManos c328912074 Add protection against large network channel lists 2026-10-12 19:18:15 -07:00
BonsUnleashed aa4e31beab Store object holder handlers per registry name (#10899) 2026-10-12 13:18:32 -07:00
LexManos 4249a25ec7 Fix Spectator Mode having block overlay when inside blocks. Fixes #10900 2026-10-09 13:18:39 -07:00
LexManos 939fb13d3b Fix MDK including wrong resources. Fixes #10892 2026-09-30 14:11:21 -07:00
LexManos 932c6e9649 Fix checkLicense tasks with projectInfo task 2026-09-28 11:55:03 -07:00
LexManos ed6d9ff0e4 Move tests and mdk to sub-project (#10887)
This introduces two new artifacts
forge-examiner: A new mod locator that allows for a single directory to be split into multiple modules
forge-tests: Our test mods, showing examples of data gen, game tests, and various Forge features
Adds a new system useful for projects with multiple sub-projects that use datagen
2026-09-28 10:56:58 -07:00
Matt 851a57e808 Match ForgeRenderTypes OIT pipelines to vanilla, fixes #10890 (#10891) 2026-09-27 09:17:33 -07:00
LexManos 49aac2c496 Fix entities not being pushed by fluids 2026-09-26 18:32:39 -07:00
LexManos 26eeb1cc70 Fix typo in BlockScanUtils patch, fixes placing cushions. Closes #10889 2026-09-25 19:13:42 -07:00
LexManos 5afe3c050c Migrate to fully declarative ForgeGradlePlugin and local build-logic 2026-09-22 16:16:56 -07:00
LexManos 839fd66e52 Fix players dropping items. Closes #10885 2026-09-19 14:02:52 -07:00
LexManos fea644f6d2 Fix players not drowning propely 2026-09-19 14:02:21 -07:00
340 changed files with 2537 additions and 1431 deletions

No files matched your search

+5 -6
View File
@@ -11,7 +11,7 @@ forgedev
/eclipse/
#idea
/.idea
**/.idea
/*/out
/classes
*.iml
@@ -33,13 +33,12 @@ forgedev
src/minecraft
#occupational hazards
/projects/**/build/
/projects/**/out/
/projects/**/run/
/projects/**/*.launch
/*/runs/
/repo/
/rejects-*/
src/*/generated/**/.cache/
**/src/*/generated/**/.cache/
**/*.launch
**/.project_info.properties
# Generated by gradle every import
**/src/main/resources/META-INF/MANIFEST.MF
+42
View File
@@ -0,0 +1,42 @@
plugins {
id("groovy-gradle-plugin")
alias(libs.plugins.licenser)
}
repositories {
mavenCentral()
gradlePluginPortal()
maven { url = 'https://maven.minecraftforge.net/' }
//mavenLocal()
}
dependencies {
compileOnly gradleApi()
// Manually import plugins so that we can apply them by id.
implementation plugin('licenser')
implementation plugin('gradleutils')
implementation plugin('gitversion')
implementation plugin('changelog')
implementation plugin('forgedev')
}
gradlePlugin {
plugins {
register('convention') {
id = 'net.minecraftforge.forge.build.convention'
implementationClass = 'net.minecraftforge.forge.build.ConventionPlugin'
}
}
}
final Provider<String> plugin(String alias) {
return (Provider<String>)project.extensions.getByType(VersionCatalogsExtension)
.named("libs")
.findPlugin(alias).get()
.map { it.pluginId + ":" + it.pluginId + ".gradle.plugin:" + it.version }
}
license {
header = file('../LICENSE-header.txt')
}
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+251
View File
@@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+94
View File
@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+30
View File
@@ -0,0 +1,30 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven { url = 'https://maven.minecraftforge.net/' }
mavenLocal()
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
//enableFeaturePreview 'TYPESAFE_PROJECT_ACCESSORS'
dependencyResolutionManagement.versionCatalogs {
register('bootLibs') {
description = 'Important version numbers that are externally declared for easy modification.'
from(files('../minecraft.versions.toml'))
}
create('libs') {
description = 'Libraries used by Forge and its subprojects.'
from(files("../gradle/libs.versions.toml"))
}
}
if (new File('forgedev/settings.gradle').exists())
includeBuild '../forgedev'
@@ -0,0 +1,166 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.build
import groovy.transform.CompileStatic
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.ExternalModuleDependencyBundle
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
import javax.inject.Inject
@CompileStatic
abstract class ConventionExtension implements Meta {
private final Project project;
private final Runs runs
protected abstract @Inject ObjectFactory getObjects()
protected abstract @Inject ProviderFactory getProviders()
@Inject
ConventionExtension(Project project) {
this.project = project
this.runs = objects.newInstance(Runs, this)
}
@Override
Project getProject() {
this.project
}
void manifest(String pkg) {
this.project.tasks.named('jar', Jar).configure {
this.gradleutils.manifestDefaults(it.manifest, pkg)
}
}
void manifest(String pkg, Map<String, String> info) {
manifest(pkg)
this.project.tasks.named('jar', Jar).configure {
it.manifest.getAttributes().with {
for (var entry : info.entrySet())
put(entry.key, entry.value)
}
}
}
// region Maven publishing ==============================================================
void publish() {
publish(pub -> pub.from(project.components.getByName('java')))
}
void publish(Action<MavenPublication> config) {
project.with {
publishing.with {
repositories.with {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
// The actual artifacts to be published should be configured, so run the config
config.execute(it)
it.pom.with {
description = project.description
gradleutils.pom.addRemoteDetails(it)
license.with {
gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
}
}
// endregion
// region Runs ================================================================
private TaskProvider<Task> genAllData = null
TaskProvider<Task> getGenAllData() {
if (genAllData == null)
genAllData = this.project.tasks.register('genAllData')
return genAllData
}
TaskProvider<Task> getGenAllData(Action<Task> config) {
getGenAllData().configure(config)
return getGenAllData()
}
Runs getRuns() {
return this.runs
}
Runs runs(Action<Runs> config) {
config.execute(this.runs)
return this.runs
}
Runs basicRuns() {
return basicRuns({})
}
Runs basicRuns(Action<Runs> config) {
this.runs.main(null, null)
config.execute(this.runs)
return getRuns()
}
Runs basicRuns(String mods, File output) {
return basicRuns(mods, output, {})
}
Runs basicRuns(String mods, File output, Action<Runs> config) {
this.runs.main(mods, output)
config.execute(this.runs)
return getRuns()
}
Runs testRuns(String mods, File output) {
return testRuns(mods, output, {})
}
Runs testRuns(String mods, File output, Action<Runs> config) {
this.runs.test(mods, output)
config.execute(this.runs)
return getRuns()
}
// endregion
// region Forge Version Json ===================================================
private TaskProvider<WriteForgeVersionJson> writeForgeVersionJson = null
TaskProvider<WriteForgeVersionJson> getForgeVersionJson() {
if (writeForgeVersionJson == null) {
writeForgeVersionJson = this.project.tasks.register('writeForgeVersionJson', WriteForgeVersionJson)
final versions = this.versions
writeForgeVersionJson.configure {
it.forgeVersion.set(versions.forge)
it.minecraftVersion.set(versions.minecraft)
it.mcpVersion.set(versions.mcp)
}
}
return writeForgeVersionJson
}
TaskProvider<WriteForgeVersionJson> forgeVersionJson(Action<WriteForgeVersionJson> config) {
forgeVersionJson.configure(config)
return forgeVersionJson
}
// endregion
// region Natives helper =========================================================
// See gradle/gradle#35070 (https://github.com/gradle/gradle/issues/35070)
// We are making the list of providers first BEFORE mapping them to a provider of the resolved objects so
// that they can be finalized after configuration.
List<MinimalExternalModuleDependency> nativeVariants(Provider<ExternalModuleDependencyBundle> bundle) {
final natives = "natives-" + forgedev.os
final deps = this.project.dependencies
bundle.get().collect { dep ->
(deps.variantOf(providers.provider { (MinimalExternalModuleDependency)dep }) {
it.classifier(natives)
}).get()
}
}
// endregion
}
@@ -0,0 +1,96 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.build
import groovy.transform.CompileStatic
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.jvm.toolchain.JavaLanguageVersion
@CompileStatic
class ConventionPlugin implements Plugin<Project>, Meta {
Project project
@Override
Project getProject() {
this.project
}
@Override
void apply(Project project) {
this.project = project
project.with {
pluginManager.with {
apply 'java-library'
apply 'maven-publish'
apply pluginId('licenser')
apply pluginId('gradleutils')
apply pluginId('gitversion')
apply pluginId('changelog')
apply pluginId('forgedev')
}
final versions = extensions.create("versions", VersionsExtension, project)
final convention = extensions.create('convention', ConventionExtension, project)
logger.lifecycle(name + " " + version)
gradleutils.with {
vendor.set('Forge Development LLC')
version.set(gitversion.version)
}
project.setGroup('net.minecraftforge')
java.with {
toolchain.languageVersion.set(JavaLanguageVersion.of(versions.java))
// This is needed for IDE runtime classpath to be correct/not have multiple of the same artifact
consistentResolution {
it.useCompileClasspathVersions()
}
// Forge needs to build its own sources jar due to patching, everything else should use the default
if (project != rootProject)
it.withSourcesJar()
}
tasks.withType(JavaCompile).configureEach {
it.options.with {
encoding = 'UTF-8'
compilerArgs << '-Xlint:-unchecked' // This is mainly for vanilla code, but just makes it quiet down outside the IDE
}
}
// NOTE: We are adding dependencies to projects instead of through settings, so we don't have to apply forgedev in settings.
// Applying forgedev in settings will break IDE linting support, which makes it very hard to work with.
// We can reconsider this if/when JetBrains gets their shit together.
// - I doubt they will ever fix their shit 8/28/2006 - Lex
repositories.with {
// Libraries has to be before maven central because Mojang hosts a classifer that central doesn't (org.lwjgl:lwjgl-freetype:3.3.3:natives-macos-patch)
maven { url = 'https://libraries.minecraft.net/' }
mavenCentral()
maven gradleutils.forgeMaven
project.pluginManager.withPlugin('net.minecraftforge.forgedev') {
maven forgedev.mavenizer
}
mavenLocal()
}
changelog.with {
from versions.changelogBase
}
license.with {
header = rootProject.file('LICENSE-header.txt')
exclude('.project_info.properties')
}
// Common dependencies,
project.dependencies.with {
add('compileOnly', libs.findLibrary('jetbrains.annotations').get())
}
}
}
}
@@ -0,0 +1,72 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.build
import groovy.transform.CompileStatic
import net.minecraftforge.forgedev.ForgeDevExtension
import net.minecraftforge.gitversion.gradle.GitVersionExtension
import net.minecraftforge.gitversion.gradle.changelog.ChangelogExtension
import net.minecraftforge.gradleutils.GradleUtilsExtensionForProject
import net.minecraftforge.licenser.LicenseExtension
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.publish.PublishingExtension
@CompileStatic
interface Meta {
Project getProject();
default VersionCatalog getLibs() {
project.extensions.getByType(VersionCatalogsExtension).named("libs")
}
default ExtraPropertiesExtension getExt() {
project.extensions.getByType(ExtraPropertiesExtension)
}
default JavaPluginExtension getJava() {
project.extensions.getByType(JavaPluginExtension)
}
default GradleUtilsExtensionForProject getGradleutils() {
project.extensions.getByType(GradleUtilsExtensionForProject)
}
default GitVersionExtension getGitversion() {
project.extensions.getByType(GitVersionExtension)
}
default ForgeDevExtension getForgedev() {
project.extensions.getByType(ForgeDevExtension)
}
default ChangelogExtension getChangelog() {
project.extensions.getByType(ChangelogExtension)
}
default LicenseExtension getLicense() {
project.extensions.getByType(LicenseExtension)
}
default PublishingExtension getPublishing() {
project.extensions.getByType(PublishingExtension)
}
default VersionsExtension getVersions() {
project.extensions.getByType(VersionsExtension)
}
default ConventionExtension getConvention() {
project.extensions.getByType(ConventionExtension)
}
default String pluginId(String alias) {
getLibs().findPlugin(alias).orElseThrow { new IllegalStateException("Could not find plugin " + alias) }.get().pluginId
}
}
@@ -0,0 +1,220 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.build
import groovy.transform.CompileStatic
import net.minecraftforge.licenser.LicenseExtension
import net.minecraftforge.licenser.tasks.LicenseCheck
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.WriteProperties
import org.jspecify.annotations.Nullable
import javax.inject.Inject
import javax.naming.spi.ObjectFactory
@CompileStatic
class Runs implements Meta {
private final ConventionExtension ext
private boolean hasDependency = false
@Inject
Runs(ConventionExtension ext) {
this.ext = ext
}
@Override
Project getProject() {
this.ext.project
}
void main(@Nullable String mods, @Nullable File output) {
final project = this.project
final genAllData = this.ext.genAllData
project.with {
final SourceSet sourceSet = getJava().getSourceSets().named("main").get()
if (!hasDependency && project != project.rootProject) {
dependencies.with {
add('implementation', it.project(':')) // Root project is forge itself
}
hasDependency = true
}
if (output != null)
sourceSet.resources.with { srcDir output }
forgedev.runs.with {
final File existing = sourceSet.getResources().getSrcDirs()[0]
configureEach {
it.with {
cache.set(project.layout.projectDirectory.dir('runs/cache'))
eclipsePrefix = project.name
// If we're not forge, we need to get the metadata from the forge project
if (project != project.rootProject)
metadata = project.files(getForgedev().consumeFile('metadata', project.rootProject))
var runName = it.name.toLowerCase(Locale.ENGLISH)
if (runName.contains('client') || runName.contains('data')) {
options.with {
// LWJGL needs native access, Mojang gets away with just saying 'ALL-UNNAMED' because it doesnt boot into module land
// We need a system to translate that to module names
//jvmArgs '--enable-native-access=ALL-UNNAMED', '--add-exports', 'java.base/jdk.internal.misc=ALL-UNNAMED'
args '--assetsDir', '{assets_root}', '--assetIndex', '{asset_index}'
systemProperty 'org.lwjgl.system.SharedLibraryExtractDirectory', 'lwjgl_dll'
}
}
if (runName.contains('data')) {
options.with(sourceSet).with {
systemProperties.put("forge.project.list", getProjectList().flatMap { it.destinationFile.map{ it.asFile.absolutePath } })
if (mods != null) args('--mod', mods)
if (output != null) args('--output', output)
}
}
options.with {
getWorkingDir().set(project.layout.projectDirectory.dir("runs/$name"))
getMainClass().set('net.minecraftforge.bootstrap.ForgeBootstrap')
args '--gameDir', '.'
jvmArgs '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders', '-XX:StackShadowPages=32'
//systemProperty 'forge.logging.marker.registries', 'ACCEPT'
//systemProperty 'forge.logging.console.level', 'debug'
systemProperty 'bsl.debug', 'true'
systemProperty 'terminal.jline', 'true'
systemProperty 'forge.enableGameTest', 'true'
systemProperty 'eventbus.api.strictRuntimeChecks', 'true'
}
}
}
maybeCreate('client').with {
options.with {
args '--launchTarget', 'forge_dev_client',
'--username', 'Dev',
'--version', project.name,
'--accessToken', '0',
'--userType', 'mojang',
'--versionType', 'release'
}
}
maybeCreate('server').with {
options.with {
args '--launchTarget', 'forge_dev_server'
}
}
maybeCreate('gameTestServer').with {
options.with {
args '--launchTarget', 'forge_dev_server_gametest',
'--uniqueWorld' // Unique world is used so that the world regenerates, as well as the world config isn't influenced by other runs
}
}
maybeCreate('data').with {
options.with {
args '--launchTarget', 'forge_dev_data',
'--all',
'--existing', existing
}
}
maybeCreate('clientData').with {
options.with {
args '--launchTarget', 'forge_dev_client_data',
'--all',
'--existing', existing
}
}
}
genAllData.configure {
it.dependsOn forgedev.runs.getByName('data').with(sourceSet).run, forgedev.runs.getByName('clientData').with(sourceSet).run
}
}
}
void test(@Nullable String mods, @Nullable File output) {
final project = this.project
final genAllData = this.ext.genAllData
project.with {
final SourceSet sourceSet = getJava().getSourceSets().named("test").get()
forgedev.runs.with {
final File existing = sourceSet.getResources().getSrcDirs()[0]
configureEach {
it.with {
var runName = it.name.toLowerCase(Locale.ENGLISH)
options.with(sourceSet).with {
systemProperty 'forgedev.enableTestMods', 'true'
}
if (runName.contains('data')) {
options.with(sourceSet).with {
if (mods != null) args('--mod', mods)
if (output != null) args('--output', output)
args('--existing', existing)
}
}
}
}
}
genAllData.configure {
it.dependsOn forgedev.runs.getByName('data').with(sourceSet).run, forgedev.runs.getByName('clientData').with(sourceSet).run
}
}
}
private TaskProvider<WriteProperties> projectInfo = null
TaskProvider<WriteProperties> getProjectInfo() {
if (projectInfo == null) {
projectInfo = this.project.tasks.register("writeProjectInfo", WriteProperties)
projectInfo.configure {
it.destinationFile.set(this.project.file('src/main/resources/.project_info.properties'))
it.property('name', this.project.rootProject.name + this.project.isolated.path.replaceAll(':', '.'))
it.property('output', 'src/main/generated')
}
this.project.tasks.named("generateResources").configure { it.dependsOn(projectInfo) }
this.project.tasks.withType(LicenseCheck).configureEach {
it.dependsOn(projectInfo)
}
}
return projectInfo
}
TaskProvider<WriteProperties> projectInfo(Action<? extends WriteProperties> action) {
getProjectInfo().configure(action)
return getProjectInfo()
}
void setFlat(boolean value) {
projectInfo(task -> task.property('flat', value))
}
private TaskProvider<WriteProperties> projectList = null
TaskProvider<WriteProperties> getProjectList() {
if (projectList == null) {
projectList = this.project.tasks.register("writeProjectList", WriteProperties)
projectList.configure { task ->
task.destinationFile.set(this.project.layout.buildDirectory.file('project_list.properties'))
task.encoding = 'UTF-8'
final root = this.project.rootProject.name
this.project.rootProject.allprojects.forEach { p ->
task.property(root + p.isolated.path.replaceAll(':', '.'), p.isolated.projectDirectory.asFile.absolutePath)
}
}
}
return projectList
}
TaskProvider<WriteProperties> projectList(Action<? extends WriteProperties> action) {
getProjectList().configure(action)
return getProjectList()
}
}
@@ -0,0 +1,41 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.build;
import net.minecraftforge.gitversion.gradle.GitVersionExtension;
import org.gradle.api.Project;
import org.gradle.api.artifacts.VersionCatalog;
import org.gradle.api.artifacts.VersionCatalogsExtension;
import javax.inject.Inject;
public abstract class VersionsExtension {
private final VersionCatalog libs;
public final String java, minecraft, forge, forgeSpec, minecraftNext, mcp, changelogBase;
@Inject
public VersionsExtension(Project project) {
final var extensions = project.getExtensions();
final var gitversion = extensions.getByType(GitVersionExtension.class);
libs = extensions.getByType(VersionCatalogsExtension.class).named("bootLibs");
java = get("java");
minecraft = get("minecraft");
minecraftNext = get("minecraft.next");
mcp = get("mcp");
changelogBase = get("changelog.base");
forge = gitversion.of(project.getRootProject()).getVersion();
forgeSpec = forge.split("\\.")[0];
project.setVersion(minecraft + '-' + gitversion.getVersion());
}
private String get(String name) {
var ver = this.libs.findVersion(name);
if (ver.isEmpty())
throw new IllegalStateException("No version found for " + name);
return ver.get().toString();
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.build
import groovy.json.JsonBuilder
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import javax.inject.Inject
@CompileStatic
abstract class WriteForgeVersionJson extends DefaultTask {
abstract @Input Property<String> getForgeVersion()
abstract @Input Property<String> getMinecraftVersion()
abstract @Input Property<String> getMcpVersion()
abstract @OutputFile RegularFileProperty getOutputFile()
@Inject
WriteForgeVersionJson() {}
@TaskAction
@CompileDynamic
void exec() {
var json = new JsonBuilder()
json {
forge this.forgeVersion.get()
mc this.minecraftVersion.get()
mcp this.mcpVersion.get()
}
this.outputFile.asFile.get().text = json.toPrettyString()
}
}
+42 -254
View File
@@ -1,37 +1,20 @@
import org.apache.tools.ant.filters.ReplaceTokens
plugins {
id 'java-library'
id 'idea'
id 'eclipse'
id 'maven-publish'
alias libs.plugins.licenser
alias libs.plugins.versions
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
alias libs.plugins.jarsigner
id 'net.minecraftforge.forgedev'
alias libs.plugins.forgedev
id 'net.minecraftforge.forge.build.convention'
}
gradleutils.displayName = 'Forge'
group = 'net.minecraftforge'
description = 'Modifications to Minecraft to enable mod developers.'
// NOTE: See settings.gradle for all the referenceable version variables.
// All subprojects (that apply 'net.minecraftforge.forge.build.convention') will have these available.
println "Version: $version"
java.toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
jarSigner.autoDetect('forge')
// We have to tell ForgeDev to use the exact versions of tools we ship in the installer
fdtools {
configure('mavenizer') {
version = '0.5.22'
}
configure('binpatcher') {
version = buildLibs.binarypatcher.get().version
}
@@ -47,18 +30,19 @@ fdtools {
forgedev.validatePublish {
task {
baseBranch = 'upstream/26.1.2'
versionPrefix = minecraftVersion
versionPrefix = versions.minecraft
}
}
*/
final minecraftFiles = forgedev.minecraftFiles(minecraftVersion)
final minecraftFiles = forgedev.minecraftFiles(versions.minecraft)
final mcpBase = forgedev.mcpBase {
mcpVersion = "${minecraftVersion}-${project.ext.mcpVersion}"
mcpVersion = "${versions.minecraft}-${versions.mcp}"
accessTransformers.from file('src/main/resources/META-INF/accesstransformer.cfg')
sideAnnotationStrippers.from file('src/main/resources/forge.sas')
}
forgedev.provideFile('metadata', mcpBase.metadata)
forgedev.patches {
base = mcpBase
@@ -71,34 +55,12 @@ tasks.register('setup') {
dependsOn forgedev.patches.apply
}
sourceSets {
named('main') {
resources.srcDir 'src/main/generated'
}
named('test') {
resources.srcDir 'src/test/generated'
}
}
license {
header = file('LICENSE-header.txt')
include 'net/minecraftforge/'
exclude 'net/minecraftforge/common/LenientUnboundedMapCodec.java'
tasks.tap {
register('main') {
files.from files('src/main/java')
}
register('test') {
files.from files('src/test/java')
}
}
}
changelog {
from changelogBase
publishAll = false
}
@@ -120,10 +82,7 @@ configurations {
named('api') {
extendsFrom(installer)
}
named('implementation') {
// Inherit vanilla Minecraft's dependencies
// Inherit vanilla Minecraft's dependencies, and let downstream access them
extendsFrom(mcpBase.dependencyConfiguration)
}
}
@@ -174,110 +133,12 @@ dependencies {
annotationProcessor libs.eventbus.validator
}
convention.basicRuns('forge', file('src/main/generated/'))
final EXTRA_TXTS = [
file('LICENSE.txt'),
changelog.task.flatMap(task -> task.outputFile)
file('LICENSE.txt'),
changelog.task.flatMap(task -> task.outputFile)
]
forgedev.runs {
configureEach {
options {
workingDir = layout.projectDirectory.dir("runs/$name")
mainClass = 'net.minecraftforge.bootstrap.ForgeBootstrap'
args '--gameDir', '.'
jvmArgs '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders', '-XX:StackShadowPages=32'
systemProperty 'bsl.debug', 'true'
systemProperty 'terminal.jline', 'true'
}
}
create('client') {
options {
systemProperty 'eventbus.api.strictRuntimeChecks', 'true'
systemProperty 'org.lwjgl.system.SharedLibraryExtractDirectory', 'lwjgl_dll'
args '--launchTarget', 'forge_dev_client',
'--username', 'Dev',
'--version', project.name,
'--accessToken', '0',
'--userType', 'mojang',
'--versionType', 'release',
'--assetsDir', '{assets_root}',
'--assetIndex', '{asset_index}'
with(sourceSets.test) {
systemProperty 'forge.enableGameTest', 'true'
systemProperty 'forgedev.enableTestMods', 'true'
}
}
}
create('server') {
options {
args '--launchTarget', 'forge_dev_server'
}
}
create('gameTestServer') {
options {
systemProperty 'forge.enableGameTest', 'true'
args '--launchTarget', 'forge_dev_server_gametest'
args '--uniqueWorld' // Unique world is used so that the world regenerates, as well as the world config isn't influenced by other runs
with(sourceSets.test) {
systemProperty 'forgedev.enableTestMods', 'true'
}
}
}
create('data') {
options {
args '--launchTarget', 'forge_dev_data',
'--all',
'--existing', sourceSets.main.resources.srcDirs[0],
'--assetsDir', '{assets_root}',
'--assetIndex', '{asset_index}'
with(sourceSets.main) {
args '--mod', 'forge',
'--output', rootProject.file('src/main/generated/')
}
with(sourceSets.test) {
systemProperty 'forge.enableGameTest', 'true'
systemProperty 'forgedev.enableTestMods', 'true'
args '--mod', '.+',
'--existing', sourceSets.test.resources.srcDirs[0],
'--output', rootProject.file('src/test/generated/')
}
}
}
create('clientData') {
options {
args '--launchTarget', 'forge_dev_client_data',
'--all',
'--existing', sourceSets.main.resources.srcDirs[0],
'--assetsDir', '{assets_root}',
'--assetIndex', '{asset_index}'
with(sourceSets.main) {
args '--mod', 'forge',
'--output', rootProject.file('src/main/generated/')
}
with(sourceSets.test) {
systemProperty 'forgedev.enableTestMods', 'true'
args '--mod', '.+',
'--existing', sourceSets.test.resources.srcDirs[0],
'--output', rootProject.file('src/test/generated/')
}
}
}
}
tasks.register('genAllData') {
dependsOn forgedev.runs.data.run, forgedev.runs.data.runTest, forgedev.runs.clientData.run, forgedev.runs.clientData.runTest
}
final serverShim = forgedev.shim {
config {
@@ -304,7 +165,7 @@ final finalizeSpawn = forgedev.methodCallFinder('findFinalizeSpawnTargets') {
}
forgedev.validateDeprecations(tasks.named('jar', Jar)) {
mcVersion = minecraftVersion
mcVersion = versions.minecraft
}
forgedev.checks() {
@@ -343,24 +204,15 @@ tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.forge'
'Automatic-Module-Name': 'net.minecraftforge.forge'
])
gradleutils.manifestDefaults(it, 'net/minecraftforge/versions/forge/')
gradleutils.manifestDefaults(it, 'net/minecraftforge/versions/mcp/', [
'Specification-Title' : 'Minecraft',
'Specification-Version' : versions.minecraft,
'Implementation-Title' : 'MCP',
'Implementation-Version': versions.mcp
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : gradleutils.vendor.get(),
'Specification-Version' : gitversion.info.tag,
'Implementation-Title' : project.group,
'Implementation-Vendor' : gradleutils.vendor.get(),
'Implementation-Version': forgeVersion
], 'net/minecraftforge/versions/forge/')
attributes([
'Specification-Title' : 'Minecraft',
'Specification-Vendor' : gradleutils.vendor.get(),
'Specification-Version' : minecraftVersion,
'Implementation-Title' : 'MCP',
'Implementation-Vendor' : gradleutils.vendor.get(),
'Implementation-Version': mcpVersion
], 'net/minecraftforge/versions/mcp/')
}
jarSigner.sign(it)
}
@@ -452,7 +304,7 @@ forgedev.userDev {
}
}
forgedev.userdevCompatibility(minecraftVersion) {
forgedev.userdevCompatibility(versions.minecraft) {
clean = mcpBase.classes
dirty = tasks.named('jar', Jar)
}
@@ -490,7 +342,7 @@ forgedev.installer {
launcherLibraries configurations.installer
final launcherId = "${minecraftVersion}-${project.name}-${forgeVersion}"
final launcherId = "${versions.minecraft}-${project.name}-${versions.forge}"
final installertools = tool(buildLibs.installertools)
final binarypatcher = tool(buildLibs.binarypatcher)
@@ -533,12 +385,12 @@ forgedev.installer {
json {
icon = rootProject.file('icon.ico')
executable serverShim.jar
minecraft = minecraftVersion
minecraft = versions.minecraft
profileVersion = launcherId
final projectArtifact = "${project.group}:${project.name}:${project.version}"
data 'MC_UNPACKED', "[net.minecraft:client:${minecraftVersion}]", "[net.minecraft:server:${minecraftVersion}:unpacked]"
data 'MC_UNPACKED', "[net.minecraft:client:${versions.minecraft}]", "[net.minecraft:server:${versions.minecraft}:unpacked]"
data 'MC_UNPACKED_SHA', minecraftFiles.client, minecraftFiles.serverExtracted
data 'BINPATCH', '/data/client.lzma', '/data/server.lzma'
@@ -599,60 +451,13 @@ forgedev.installer {
librariesLast = false // Makes libraries be added before args
id = launcherId
inheritsFrom = minecraftVersion
inheritsFrom = versions.minecraft
mainClass = 'net.minecraftforge.bootstrap.ForgeBootstrap'
gameArgs = [ '--launchTarget', 'forge_client' ]
jvmArgs = [ '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders', '-XX:StackShadowPages=32' ]
}
}
final mdkGradleWrapper = tasks.register('mdkGradleWrapper', Wrapper) {
gradleVersion = '9.7.1'
scriptFile = layout.buildDirectory.dir("wrapper/" + gradleVersion + "/gradlew")
jarFile = layout.buildDirectory.dir("wrapper/" + gradleVersion + "/gradle/wrapper/gradle-wrapper.jar")
}
final mdkZip = tasks.register('mdkZip', Zip) {
dependsOn mdkGradleWrapper
archiveBaseName = project.name
archiveClassifier = 'mdk'
archiveVersion = project.version
destinationDirectory = file('build/libs')
from(EXTRA_TXTS)
from mdkGradleWrapper.map(Wrapper.&getScriptFile)
from mdkGradleWrapper.map(Wrapper.&getBatchScript)
into('gradle/wrapper/') {
from mdkGradleWrapper.map(Wrapper.&getJarFile)
from mdkGradleWrapper.map(Wrapper.&getPropertiesFile)
}
from(rootProject.file('mdk/')){
rootProject.file('mdk/gitignore.txt').eachLine {
if (!it.trim().isEmpty() && !it.trim().startsWith('#'))
exclude it
}
filter(ReplaceTokens, tokens: [
FORGE_VERSION: forgeVersion,
FORGE_GROUP: project.group,
FORGE_NAME: project.name,
MC_VERSION: minecraftVersion,
FORGE_SPEC_VERSION: gitversion.info.tag.split("\\.")[0],
MC_NEXT_VERSION: minecraftNextVersion,
EVENTBUS_VERSION: libs.versions.eventbus.get()
])
rename 'gitignore\\.txt', '.gitignore'
rename 'gitattributes\\.txt', '.gitattributes'
}
from(rootProject.file('src/test/java/com/example/examplemod/')) {
into('src/main/java/com/example/examplemod/')
}
from(rootProject.file('src/test/generated/mdk_datagen/')) {
into('src/main/resources/')
exclude '**/.cache/'
}
}
/*
tasks.named('javadoc', Javadoc) {
description = 'Generates the combined javadocs for the FML projects and the main Forge project'
@@ -717,8 +522,8 @@ tasks.named('javadoc', Javadoc) {
bottom = "Minecraft Forge is an open source modding API for Minecraft: Java Edition, licensed under the Lesser GNU General Public License, version 2.1."
windowTitle = "Minecraft Forge API ${version}"
docTitle = "Minecraft Forge API - ${forgeVersion} for Minecraft ${minecraftVersion}"
header = "<div style=\"margin-top: 9px;padding: 5px 6px;\"><strong>${forgeVersion} for Minecraft ${minecraftVersion}</strong></div>"
docTitle = "Minecraft Forge API - ${versions.forge} for Minecraft ${versions.minecraft}"
header = "<div style=\"margin-top: 9px;padding: 5px 6px;\"><strong>${versions.forge} for Minecraft ${versions.minecraft}</strong></div>"
}
doLast {
@@ -730,52 +535,36 @@ tasks.named('javadoc', Javadoc) {
}
}
*/
publishing {
repositories {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
artifact universalJar
artifact forgedev.installer.jar
artifact mdkZip
artifact forgedev.userDev.jar
artifact sourcesJar
artifact serverShim.jar
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
final mdkZip = forgedev.consumeFile("mdkZip", project(':forge-mdk'))
convention.publish {
artifact universalJar
artifact forgedev.installer.jar
artifact(mdkZip.map { it.singleFile }) {
builtBy mdkZip
classifier = "mdk"
extension = "zip"
}
artifact forgedev.userDev.jar
artifact sourcesJar
artifact serverShim.jar
}
/*
// These are debugging tasks that are only used when developing ForgeDevPlugin, to bulk test task's config/caching
// region ForgeDev Tests
def all = tasks.register('all') {}
afterEvaluate {
if (gradle.startParameter.taskNames.contains('all')) {
tasks.forEach { task -> // This forces everything to be realized, which is why we have the startParameter check
if (!gradle.startParameter.isTaskGraph() && task.name.startsWith('run')) return
tasks.forEach { task -> // This forces everything to be realized, which is why we have the startParameter check
if (!gradle.startParameter.isTaskGraph() && task.name.startsWith('run')) return
if (task.name in [all.name, 'dependencyInsight', 'dependencies', 'dependencyUpdates', 'init', 'buildEnvironment', 'idea',
'eclipse', 'javaToolchains', 'outgoingVariants', 'projects', 'properties', 'resolvableConfigurations', 'tasks', 'updateDaemonJvm',
'generateActionsWorkflow'
]) return
if (task.name.startsWith('clean')) return
if (task.name == 'checkJarCompatibility') return // This doesn't work in FG6 dev, I think it's cuz we check vs srg names. Will need to look at later
all.configure { dependsOn(task) }
}
all.configure { dependsOn(task) }
}
}
}
*/
@@ -789,8 +578,7 @@ tasks.withType(AbstractTestTask).configureEach {
[ 'makePatches', 'compileJava', 'checkLicenseMain', 'updateLicenseMain', 'sourcesJar', 'sourcesJarAll' ]
.each{ tasks.named(it).configure{ mustRunAfter forgedev.patches.apply }}
forgedev.runs.configureEach {
run.configure { dependsOn finalizeSpawn}
runTest.configure { dependsOn finalizeSpawn}
run.configure { dependsOn finalizeSpawn }
}
// run gradlew dependencyUpdates --no-parallel
//noinspection UnnecessaryQualifiedReference
+2 -2
View File
@@ -2,7 +2,7 @@
MinecraftForge
=============
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=26.2-&query=$.promos["26.2-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=26.3-&query=$.promos["26.3-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=26.3-&query=$.promos["26.3-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
)][Download] [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)][Discord] [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)][Patreon]
@@ -74,7 +74,7 @@ Please ensure you have a valid email address associated with your GitHub account
### Thanks
[![YourKit](https://www.yourkit.com/images/yklogo.png)](https://www.yourkit.com/)
YourKit for providing us accesss to their [YourKit Java Profiler] which helps identify bottlenecks and places for improvment.
YourKit for providing us accesss to their [YourKit Java Profiler] which helps identify bottlenecks and places for improvement.
[Contributing]: ./CONTRIBUTING.md
[CLA]: https://cla-assistant.io/MinecraftForge/MinecraftForge
+6 -64
View File
@@ -1,78 +1,20 @@
plugins {
id 'java-library'
id 'maven-publish'
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
id 'net.minecraftforge.forge.build.convention'
}
gradleutils.displayName = 'FML'
final vendor = 'Forge Development LLC'
description = 'Modifications to Minecraft to enable mod developers.'
description = 'Core library used for Forge ModLoader.'
dependencies {
compileOnly libs.jetbrains.annotations
api libs.eventbus
implementation projects.fmlloader
implementation libs.commons.io
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
convention.manifest('net/minecraftforge/fml/', [
'Automatic-Module-Name': 'net.minecraftforge.fmlcore',
'FMLModType' : 'LIBRARY'
])
license {
header = rootProject.file('LICENSE-header.txt')
}
changelog {
from changelogBase
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:-unchecked'
}
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.fmlcore',
'FMLModType' : 'LIBRARY'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Version': '1.0',
'Implementation-Vendor' : vendor
], 'net/minecraftforge/fml/')
}
}
publishing {
repositories {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
convention.publish()
@@ -366,6 +366,10 @@ public final class ModLoader {
mod.acceptEvent(e);
}
public static <T extends IModBusEvent> void postEvent(ModContainer mod, T e) {
mod.acceptEvent(e);
}
public static <T extends IModBusEvent> T postEventWithReturn(T e) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
+6 -89
View File
@@ -1,49 +1,14 @@
import org.gradle.api.plugins.jvm.JvmTestSuite
import org.gradle.internal.os.OperatingSystem
plugins {
id 'java-library'
id 'jvm-test-suite'
id 'maven-publish'
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
id 'net.minecraftforge.forge.build.convention'
}
switch (OperatingSystem.current()) {
case OperatingSystem.LINUX:
project.ext.lwjglNatives = "natives-linux"
break
case OperatingSystem.MAC_OS:
project.ext.lwjglNatives = "natives-macos"
break
case OperatingSystem.WINDOWS:
project.ext.lwjglNatives = "natives-windows"
break
}
gradleutils.displayName = 'FML Early Display'
final vendor = 'Forge Development LLC'
description = 'A pretty looking, but prone to erroring screen displayed during Forge loading.'
java {
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
license {
header = rootProject.file('LICENSE-header.txt')
}
changelog {
from changelogBase
}
dependencies {
compileOnly libs.jetbrains.annotations
implementation projects.fmlloader
implementation projects.fmlcore
implementation libs.bundles.lwjgl
@@ -51,67 +16,19 @@ dependencies {
implementation libs.jopt.simple
}
// See gradle/gradle#35070 (https://github.com/gradle/gradle/issues/35070)
// We are making the list of providers first BEFORE mapping them to a provider of the resolved objects so that they can be finalized after configuration.
var lwjglTestLibs = [
dependencies.variantOf(earlyDisplayTestLibs.lwjgl) { classifier lwjglNatives },
dependencies.variantOf(earlyDisplayTestLibs.lwjgl.opengl) { classifier lwjglNatives },
dependencies.variantOf(earlyDisplayTestLibs.lwjgl.stb) { classifier lwjglNatives },
dependencies.variantOf(earlyDisplayTestLibs.lwjgl.sdl) { classifier lwjglNatives }
].with { list ->
provider { list.collect { it.get() } }
}
testing.suites.named('test', JvmTestSuite) {
useJUnitJupiter('5.8.2')
dependencies {
implementation earlyDisplayTestLibs.powermock.core
runtimeOnly earlyDisplayLibs.slf4j.jdk14
runtimeOnly.bundle lwjglTestLibs
runtimeOnly.bundle convention.nativeVariants(earlyDisplayTestLibs.bundles.lwjgl)
}
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
convention.manifest('net/minecraftforge/fml/earlydisplay/', [
'Automatic-Module-Name': 'net.minecraftforge.earlydisplay',
'Forge-Module-Layer' : 'boot'
])
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.earlydisplay',
'Forge-Module-Layer' : 'boot'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': '1.0'
], 'net/minecraftforge/fml/earlydisplay/')
}
}
publishing {
repositories {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
convention.publish()
+18
View File
@@ -0,0 +1,18 @@
plugins {
id 'net.minecraftforge.forge.build.convention'
id 'jvm-test-suite'
}
gradleutils.displayName = 'FMLLoader-Tests'
description = 'JUnit tests for FMLLoader, not meant for distribution'
dependencies {
testImplementation(project(':fmlloader'))
testImplementation(libs.guava)
testImplementation(libs.junit.api)
testRuntimeOnly(libs.bundles.junit.runtime)
}
tasks.named('test') {
useJUnitPlatform()
}
@@ -0,0 +1,172 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.test;
import com.google.common.graph.Graph;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.MutableGraph;
import net.minecraftforge.fml.loading.toposort.CyclePresentException;
import net.minecraftforge.fml.loading.toposort.StronglyConnectedComponentDetector;
import net.minecraftforge.fml.loading.toposort.TopologicalSort;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests for {@link TopologicalSort} and {@link StronglyConnectedComponentDetector}
*/
class TopologicalSortTest {
@Test
void resultProperlySorted() {
MutableGraph<String> graph = GraphBuilder.directed().build();
graph.putEdge("a", "b");
graph.putEdge("a", "c");
graph.putEdge("b", "c");
var sorted = TopologicalSort.topologicalSort(graph, Comparator.naturalOrder());
assertEquals(List.of("a", "b", "c"), sorted);
}
@Test
void throwsOnCycle() {
MutableGraph<String> graph = GraphBuilder.directed().build();
graph.putEdge("a", "b");
graph.putEdge("b", "a");
var exception = Assertions.assertThrows(CyclePresentException.class, () -> TopologicalSort.topologicalSort(graph, Comparator.naturalOrder()));
assertEquals(Set.of(
Set.of("a", "b")
), exception.getCycles());
}
@Test
void simpleCycle() {
MutableGraph<String> graph = GraphBuilder.directed().build();
// a -> b -> c -> a
graph.putEdge("a", "b");
graph.putEdge("b", "c");
graph.putEdge("c", "a");
var cycles = cyclesOf(graph);
assertEquals(Set.of(
Set.of("a", "b", "c")
), cycles);
}
@Test
void noCycle() {
MutableGraph<String> graph = GraphBuilder.directed().build();
graph.putEdge("a", "b");
graph.putEdge("a", "c");
graph.putEdge("b", "d");
graph.putEdge("c", "d");
graph.putEdge("d", "e");
var cycles = cyclesOf(graph);
assertTrue(cycles.isEmpty());
}
/**
* Regression test: the previous implementation lowered the lowlink of a node from every already-visited
* successor, including successors whose component was already completed. That merged nodes into the wrong
* component, so a member of the real cycle ({@code c}) was missing from the reported cycle.
*/
@Test
void reportsAllMembersOfCycle() {
MutableGraph<String> graph = GraphBuilder.directed().build();
graph.addNode("a");
graph.addNode("b");
graph.addNode("c");
graph.putEdge("a", "b");
graph.putEdge("a", "c");
graph.putEdge("b", "a");
graph.putEdge("c", "b");
var cycles = cyclesOf(graph);
assertEquals(Set.of(
Set.of("a", "b", "c")
), cycles);
}
/**
* Regression test: the previous implementation invented a cycle out of {@code c -> d -> a}, which is a plain
* path, because it compared against the DFS depth instead of a discovery counter and ignored whether a
* successor was still on the stack.
*/
@Test
void doesNotInventCycles() {
MutableGraph<String> graph = GraphBuilder.directed().build();
graph.addNode("a");
graph.addNode("b");
graph.addNode("c");
graph.addNode("d");
graph.putEdge("a", "b");
graph.putEdge("b", "a");
graph.putEdge("c", "d");
graph.putEdge("d", "a");
var cycles = cyclesOf(graph);
assertEquals(Set.of(
Set.of("a", "b")
), cycles);
}
/**
* Reproduces the shape of the mod graphs that reported bogus cycles in the wild: one real three-node cycle,
* with nodes that merely depend on members of the cycle and a node on a plain path into it.
*
* <p>Every reported component must be the real cycle itself; nodes such as {@code dependent_a} only depend on
* the cycle and must not be reported.
*
* @see <a href="https://github.com/MinecraftForge/MinecraftForge/pull/10924">related GitHub issue</a>
*/
@Test
void reportsOnlyRealCycles() {
MutableGraph<String> graph = GraphBuilder.directed().build();
// Mirrors the insertion order of ModSorter: every mod is added as a node first, edges are added afterwards.
for (String mod : List.of("upstream", "cycle_a", "cycle_b", "dependent_a", "cycle_c", "dependent_b")) {
graph.addNode(mod);
}
// a -> b -> a
graph.putEdge("cycle_a", "cycle_b");
graph.putEdge("cycle_b", "cycle_a");
// a -> c -> b -> a
graph.putEdge("cycle_a", "cycle_c");
graph.putEdge("cycle_c", "cycle_b");
// dA -> b -> a - Valid
graph.putEdge("dependent_a", "cycle_b");
// dB -> b -> a - Valid
graph.putEdge("dependent_b", "cycle_b");
// u -> a -> b -> a - Not valid, but u is not part of the cycle so shouldn't be reported
graph.putEdge("upstream", "cycle_a");
var cycles = cyclesOf(graph);
assertEquals(Set.of(
Set.of("cycle_a", "cycle_b", "cycle_c")
), cycles);
var exception = Assertions.assertThrows(CyclePresentException.class, () -> TopologicalSort.topologicalSort(graph, Comparator.naturalOrder()));
assertEquals(Set.of(
Set.of("cycle_a", "cycle_b", "cycle_c")
), exception.getCycles());
}
private static Set<Set<String>> cyclesOf(Graph<String> graph) {
var detector = new StronglyConnectedComponentDetector<>(graph);
var connected = detector.getComponents();
var cycles = new HashSet<>(connected);
cycles.removeIf(component -> component.size() < 2);
return cycles;
}
}
+16 -120
View File
@@ -1,44 +1,15 @@
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import net.minecraftforge.forgedev.legacy.tasks.JarJarMetadataOptions
import org.gradle.api.plugins.jvm.JvmTestSuite
import javax.inject.Inject
plugins {
id 'java-library'
id 'jvm-test-suite'
id 'maven-publish'
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
alias libs.plugins.apt
id 'net.minecraftforge.forge.build.convention'
alias libs.plugins.apt
}
gradleutils.displayName = 'FMLLoader'
final vendor = 'Forge Development LLC'
description = 'Modifications to Minecraft to enable mod developers.'
java {
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
license {
header = rootProject.file('LICENSE-header.txt')
}
changelog {
from changelogBase
}
description = 'Mod Locator and manager for Minecraft Forge.'
dependencies {
compileOnly libs.jetbrains.annotations
// TODO [FMLLoader] Figure out a better way to lock transitive dependencies to the explicit versions shipped by the isntaller
// TODO [FMLLoader] Figure out a better way to lock transitive dependencies to the explicit versions shipped by the installer
api(libs.bundles.asm ){ transitive = false } // Needed by all the black magic
api(libs.forgespi ){ transitive = false }
api(libs.mergetool.api ){ transitive = false }
@@ -58,7 +29,6 @@ dependencies {
implementation(libs.securemodules ){ transitive = false }
implementation(libs.accesstransformers ){ transitive = false }
implementation(libs.terminalconsoleappender){ transitive = false }
implementation(libs.jimfs ){ transitive = false }
implementation(libs.roimfs ){ transitive = false }
// Needed because we have a custom log4j plugin, and they removed package scanning and require a data file to be generated
@@ -66,93 +36,19 @@ dependencies {
annotationProcessor(libs.bundles.log4j){ transitive = false }
}
testing.suites.named('test', JvmTestSuite) {
dependencies {
compileOnly libs.jetbrains.annotations
runtimeOnly libs.bootstrap
}
}
convention.manifest('net/minecraftforge/fml/loading/', [
'Automatic-Module-Name': 'net.minecraftforge.fmlloader',
'Forge-Module-Layer' : 'boot'
])
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.fmlloader',
'Forge-Module-Layer' : 'boot'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': forgeVersion
], 'net/minecraftforge/fml/loading/')
}
}
publishing {
repositories {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
// A simple task to write JSON to a file. Using Task#doLast to output files is no longer supported.
// This code may seem verbose, but it's better this way. If more projects need this, we can move it to buildSrc.
@CompileStatic
@PackageScope abstract class WriteForgeVersionJson extends DefaultTask {
abstract @Input Property<String> getForgeVersion()
abstract @Input Property<String> getMinecraftVersion()
abstract @Input Property<String> getMcpVersion()
abstract @OutputFile RegularFileProperty getOutputFile()
@Inject
WriteForgeVersionJson() {}
@TaskAction
@CompileDynamic
void exec() {
var json = new groovy.json.JsonBuilder()
json {
forge this.forgeVersion.get()
mc this.minecraftVersion.get()
mcp this.mcpVersion.get()
}
this.outputFile.asFile.get().text = json.toPrettyString()
}
}
tasks.register('writeForgeVersionJson', WriteForgeVersionJson) {
forgeVersion = project.forgeVersion
minecraftVersion = project.minecraftVersion
mcpVersion = project.mcpVersion
convention.publish()
final forgeJson = convention.forgeVersionJson {
outputFile = file('src/main/resources/forge_version.json')
}
tasks.register('jarJarOptionsJson', JarJarMetadataOptions) {
metadataFile = project.file('src/main/resources/jarjar_options.json')
final jarJarJson = tasks.register('jarJarOptionsJson', JarJarMetadataOptions) {
metadataFile = file('src/main/resources/jarjar_options.json')
// This is resolved too early by cpw.mods.modlauncher.TransformationServicesHandler.discoverServices(DiscoveryData) But eventually...
//add(libs.mixin, 'org/spongepowered/asm/mixin/Mixin.class')
add(libs.mixinextras.forge) {
@@ -168,11 +64,11 @@ tasks.register('jarJarOptionsJson', JarJarMetadataOptions) {
tasks.named('generateResources') {
dependsOn(
tasks.named('eclipseJdt'),
tasks.named('eclipseJdtApt'),
tasks.named('eclipseFactorypath'),
tasks.named('writeForgeVersionJson', WriteForgeVersionJson),
tasks.named('jarJarOptionsJson')
tasks.named('eclipseJdt'),
tasks.named('eclipseJdtApt'),
tasks.named('eclipseFactorypath'),
forgeJson,
jarJarJson
)
}
@@ -5,39 +5,16 @@
package net.minecraftforge.fml.loading.targets;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.ApiStatus;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.core.file.FileNotFoundAction;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.electronwill.nightconfig.toml.TomlParser;
import com.electronwill.nightconfig.toml.TomlWriter;
import com.google.common.jimfs.Jimfs;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.moddiscovery.AbstractModProvider;
import net.minecraftforge.forgespi.locating.IModLocator;
@ApiStatus.Internal
public final class ForgeDevLocator extends AbstractModProvider implements IModLocator {
private static final String PACK_META = "pack.mcmeta";
@Override
public String name() {
return "forge_dev_locator";
@@ -71,228 +48,6 @@ public final class ForgeDevLocator extends AbstractModProvider implements IModLo
}
var ret = new ArrayList<Path>();
ret.add(forge);
var isTest = Boolean.getBoolean("forgedev.enableTestMods");
if (!isTest)
return ret;
var test = ForgeDevLaunchHandler.getPathFromResource("net/minecraftforge/test/BaseTestMod.class", ClassLoader.getSystemClassLoader());
// Explode our test mods into a framework that treats them all as separate jars.
var tests = explodeTestMods(test);
ret.addAll(tests);
return ret;
}
private static List<Path> explodeTestMods(Path path) {
var mod = new ArrayList<Path>();
var memory = Jimfs.newFileSystem();
// First lets find all class files, that have the @Mod annotation and map packages to modids.
var packages = findTestModPackages(path);
for (var entry : packages.entrySet()) {
var pkg = entry.getKey() + '/';
var modids = entry.getValue();
// Find resource directories for every mod in this group
var paths = new LinkedHashSet<Path>();
for (var modid : modids.stream().sorted().toList()) {
var rsc = path.resolve(modid);
if (Files.exists(rsc))
paths.add(rsc);
}
var root = memory.getPath(modids.iterator().next()); // use the first modid as our root
buildModsToml(paths, modids, root, pkg.startsWith("net/minecraftforge/debug/client/"));
buildPackMeta(paths, root);
moveModuleInfo(paths, root);
if (Files.exists(root))
paths.add(root);
// We want just the class files from the root of the input paths. So make a new union with a filter.
var classes = UnionHelper.newFileSystem(
(name, _) -> {
if (name.endsWith("/")) {
if (name.startsWith("/"))
name = name.substring(1);
return pkg.startsWith(name) || name.startsWith(pkg);
}
return name.endsWith(".class") && name.startsWith(pkg);
}, new Path[] { path }
);
paths.addFirst(classes.getRootDirectories().iterator().next());
// Union of unions, Yay!
var union = UnionHelper.newFileSystem(null, paths.stream().toArray(Path[]::new));
mod.add(union.getRootDirectories().iterator().next());
}
return mod;
}
// Find all the @Mods so we can generate tomls, and so we can pick packages. Right now it doesn't allow mods in parent directories,
// I could make it merge all the way up, but I think this would be fine.
private static Map<String, Set<String>> findTestModPackages(Path path) {
var mods = new HashMap<String, Set<String>>();
try (var files = Files.walk(path)) {
var classes = files
.filter(Files::isRegularFile)
.filter(p -> p.getFileName().toString().endsWith(".class"))
.toList();
for (var cls : classes) {
try (var is = Files.newInputStream(cls)) {
var reader = new ClassReader(is);
reader.accept(new ClassVisitor(Opcodes.ASM9) {
private String clsName;
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.clsName = name;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public AnnotationVisitor visitAnnotation(String name, boolean runtime) {
if (!"Lnet/minecraftforge/fml/common/Mod;".equals(name))
return super.visitAnnotation(name, runtime);
return new AnnotationVisitor(Opcodes.ASM9) {
@Override
public void visit(String key, Object value) {
if ("value".equals(key)) {
int idx = clsName.lastIndexOf('/');
var pkg = clsName.substring(0, idx);
mods.computeIfAbsent(pkg, _ -> new HashSet<>()).add((String)value);
idx = pkg.lastIndexOf('/');
while (idx != -1) {
pkg = pkg.substring(0, idx);
idx = pkg.lastIndexOf('/');
if (mods.containsKey(pkg))
throw new IllegalStateException("Invalid ForgeDev test mod layout, conflicting packages: " + pkg + " for " + clsName);
}
}
}
};
}
}, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
}
}
} catch (IOException e) {
sneak(e);
}
return mods;
}
// Builds or update the mods.toml file for all @Mods in this package
private static void buildModsToml(Set<Path> resources, Set<String> modids, Path root, boolean clientOnly) {
var toml = resources.stream()
.map(p -> p.resolve(MODS_TOML))
.filter(Files::exists)
.findFirst()
.orElse(null);
Config cfg = null;
if (toml != null)
cfg = new TomlParser().parse(toml, FileNotFoundAction.READ_NOTHING);
else
cfg = Config.inMemory();
boolean modified = false;
var defaults = Map.of(
"modLoader", "javafml",
"loaderVersion", "[0,)",
"license", "Doesnt fucking matter",
"clientSideOnly", clientOnly
);
for (var key : defaults.keySet()) {
if (!cfg.contains(key)) {
cfg.set(key, defaults.get(key));
modified = true;
}
}
var modlist = new ArrayList<Config>();
if (cfg.contains("mods")) {
@SuppressWarnings("unchecked")
var existing = ((List<Config>)cfg.get("mods"));
modlist.addAll(existing);
}
for (var modid : modids) {
if (modlist.stream().noneMatch(c -> modid.equals(c.get("modId")))) {
modified = true;
var tmp = Config.inMemory();
tmp.set("modId", modid);
modlist.add(tmp);
}
}
cfg.set("mods", modlist);
if (modified) {
var target = root.resolve(MODS_TOML);
try {
Files.createDirectories(target.getParent());
new TomlWriter().write(cfg, target, WritingMode.REPLACE);
} catch (IOException e) {
throw new IllegalStateException("Failed to create in memory toml: " + target, e);
}
}
}
// This is optional, it just hides a warning screen when starting up. I should probably remove this once I restructure how data gen for test mods work and make it generate there.
private static void buildPackMeta(Set<Path> paths, Path root) {
var existing = paths.stream()
.map(p -> p.resolve(PACK_META))
.filter(Files::exists)
.findFirst()
.orElse(null);
if (existing != null)
return;
var target = root.resolve(PACK_META);
try {
Files.createDirectories(target.getParent());
Files.writeString(target,
"""
{
"pack": {
"description": "doesn't matter",
"pack_format": 18
}
}
""", StandardCharsets.UTF_8, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
} catch (IOException e) {
throw new IllegalStateException("Failed to make in memory pack.mcmeta: " + target, e);
}
}
private static void moveModuleInfo(Set<Path> paths, Path root) {
var existing = paths.stream()
.map(p -> p.resolve("module-info.dat"))
.filter(Files::exists)
.findFirst()
.orElse(null);
if (existing == null)
return;
var target = root.resolve("module-info.class");
try {
Files.createDirectories(target.getParent());
Files.copy(existing, target);
} catch (IOException e) {
throw new IllegalStateException("Failed to copy module-info.dat to memory: " + target, e);
}
}
@SuppressWarnings("unchecked")
private static <E extends Throwable, R> R sneak(Throwable e) throws E {
throw (E)e;
}
}
@@ -22,15 +22,18 @@ import java.util.Set;
*
* <p>This detector evaluates the graph lazily and won't reflect the modifications in the
* graph after initial evaluation.
*
* <p> Implementation based on <a href="https://en.wikipedia.org/w/index.php?title=Tarjan%27s_strongly_connected_components_algorithm&oldid=1384955226">Wikipedia Psudocode</a>
*/
public class StronglyConnectedComponentDetector<T> {
private final Graph<T> graph;
private final Map<T, Integer> ids = new HashMap<>();
private T[] elements;
private int[] dfn;
private int[] low;
private int[] indexes;
private int[] lowlinks;
private int[] stack;
private int top;
private int index;
private BitSet onStack;
private Set<Set<T>> components;
@@ -58,48 +61,43 @@ public class StronglyConnectedComponentDetector<T> {
}
final int n = nodes.size();
dfn = new int[n];
low = new int[n];
indexes = new int[n];
lowlinks = new int[n];
stack = new int[n];
onStack = new BitSet(n);
top = -1;
index = 1;
for (int i = 0; i < n; i++) {
if (dfn[i] == 0) {
dfs(i, 1);
}
if (indexes[i] == 0)
dfs(i);
}
}
private void dfs(int now, int depth) {
dfn[now] = depth;
low[now] = depth;
top++;
stack[top] = now;
private void dfs(int now) {
indexes[now] = lowlinks[now] = index++;
stack[++top] = now;
onStack.set(now);
for (T each : graph.successors(elements[now])) {
int to = ids.get(each);
if (dfn[to] != 0) {
if (low[now] > dfn[to]) {
low[now] = dfn[to];
}
} else {
dfs(to, depth + 1);
if (low[now] > low[to]) {
low[now] = low[to];
}
if (indexes[to] == 0) { // 0 is unvisited
dfs(to);
if (lowlinks[now] > lowlinks[to])
lowlinks[now] = lowlinks[to];
} else if (onStack.get(to)) {
if (lowlinks[now] > indexes[to])
lowlinks[now] = indexes[to];
}
}
if (dfn[now] == low[now]) {
if (indexes[now] == lowlinks[now]) {
Set<T> component = new HashSet<>();
while (top >= 0) {
final int t = stack[top];
component.add(elements[t]);
onStack.clear(t);
top--;
if (t == now) {
final int w = stack[top--];
onStack.clear(w);
component.add(elements[w]);
if (w == now)
break;
}
}
components.add(component);
}
+19
View File
@@ -0,0 +1,19 @@
plugins {
id 'net.minecraftforge.forge.build.convention'
}
gradleutils.displayName = 'Forge-Examiner'
description = 'Forge Mod Locator for multiple mods merged together into one code source'
dependencies {
implementation project(':fmlloader')
implementation(libs.jimfs){ transitive = false }
implementation(libs.bootstrap.api)
}
convention.manifest('net/minecraftforge/examiner/', [
'Automatic-Module-Name': 'net.minecraftforge.examiner',
'Forge-Module-Layer' : 'boot'
])
convention.publish()
@@ -0,0 +1,38 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.examiner;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import net.minecraftforge.bootstrap.api.BootstrapClasspathModifier;
public class BootstrapFilter implements BootstrapClasspathModifier {
@Override
public String name() {
return "examiner";
}
@Override
public boolean process(List<Path[]> classpath) {
var modified = false;
for (var itr = classpath.iterator(); itr.hasNext(); ) {
for (var path : itr.next()) {
final var target = path.resolve(Locator.MARKER);
if (Files.exists(target)) {
itr.remove();
modified = true;
break;
}
}
}
return modified;
}
}
@@ -0,0 +1,338 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.examiner;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.core.file.FileNotFoundAction;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.electronwill.nightconfig.toml.TomlParser;
import com.electronwill.nightconfig.toml.TomlWriter;
import com.google.common.jimfs.Jimfs;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.moddiscovery.AbstractModProvider;
import net.minecraftforge.forgespi.locating.IModLocator;
public class Locator extends AbstractModProvider implements IModLocator {
private static final Logger LOGGER = LogUtils.getLogger();
static final String MARKER = ".examiner";
private static final String PACK_META = "pack.mcmeta";
private static final String PROJECT_INFO = ".project_info.properties";
@Override
public String name() {
return "examiner";
}
@Override
public List<ModFileOrException> scanMods() {
var mods = getMods();
var ret = new ArrayList<ModFileOrException>();
for (var path : mods) {
var mod = createMod(path);
if (mod != null)
ret.add(mod);
}
return ret;
}
private Collection<Path> getMods() {
var ret = new HashSet<Path>();
var cl = ClassLoader.getSystemClassLoader();
var markers = getUrls(cl, MARKER);
markers.addAll(getUrls(getClass().getClassLoader(), MARKER));
for (var url : markers) {
var path = getPath(url, MARKER);
var mods = explodeTestMods(path);
for (var mod : mods)
ret.add(mod);
}
return ret;
}
private static Set<URL> getUrls(ClassLoader cl, String resource) {
try {
var lst = Collections.list(cl.getResources(resource));
if (LOGGER.isDebugEnabled(LogMarkers.SCAN)) {
lst.sort(Comparator.comparing(URL::toString));
LOGGER.debug(LogMarkers.SCAN, "Scanning Classloader: {} for {}", cl, resource);
for (var url : lst)
LOGGER.debug(LogMarkers.SCAN, "\t{}", url);
}
return new LinkedHashSet<URL>(lst);
} catch (IOException e) {
LOGGER.warn(LogMarkers.SCAN, "Error finding resources for: " + resource, e);
return Set.of();
}
}
private static Path getPath(URL url, String resource) {
var str = url.toString();
int len = resource.length();
if ("jar".equalsIgnoreCase(url.getProtocol())) {
str = url.getFile();
len += 2;
}
str = str.substring(0, str.length() - len);
return Path.of(URI.create(str));
}
private static List<Path> explodeTestMods(Path path) {
var mod = new ArrayList<Path>();
var memory = Jimfs.newFileSystem();
var info = path.resolve(PROJECT_INFO);
if (!Files.exists(info))
info = null;
// First lets find all class files, that have the @Mod annotation and map packages to modids.
var packages = findTestModPackages(path);
if (LOGGER.isDebugEnabled(LogMarkers.SCAN)) {
var ordered = packages.entrySet().stream().sorted((e1, e2) -> e1.getKey().compareTo(e2.toString())).toList();
for (var entry : ordered)
LOGGER.debug(LogMarkers.SCAN, "Examiner located {} with mods {}", entry.getKey(), entry.getValue().stream().sorted().toList());
}
for (var entry : packages.entrySet()) {
var pkg = entry.getKey() + '/';
var modids = entry.getValue();
// Find resource directories for every mod in this group
var paths = new LinkedHashSet<Path>();
for (var modid : modids.stream().sorted().toList()) {
var rsc = path.resolve(modid);
if (Files.exists(rsc))
paths.add(rsc);
}
var root = memory.getPath(modids.iterator().next()); // use the first modid as our root
buildModsToml(paths, modids, root, pkg.startsWith("net/minecraftforge/debug/client/"));
buildPackMeta(paths, root);
moveModuleInfo(paths, root);
copyFile(info, root.resolve(PROJECT_INFO));
if (Files.exists(root))
paths.add(root);
// We want just the class files from the root of the input paths. So make a new union with a filter.
var classes = UnionHelper.newFileSystem(
(name, _) -> {
if (name.endsWith("/")) {
if (name.startsWith("/"))
name = name.substring(1);
return pkg.startsWith(name) || name.startsWith(pkg);
}
return name.endsWith(".class") && name.startsWith(pkg);
}, new Path[] { path }
);
paths.addFirst(classes.getRootDirectories().iterator().next());
// Union of unions, Yay!
var union = UnionHelper.newFileSystem(null, paths.stream().toArray(Path[]::new));
mod.add(union.getRootDirectories().iterator().next());
}
return mod;
}
// Find all the @Mods so we can generate tomls, and so we can pick packages. Right now it doesn't allow mods in parent directories,
// I could make it merge all the way up, but I think this would be fine.
private static Map<String, Set<String>> findTestModPackages(Path path) {
var mods = new HashMap<String, Set<String>>();
try (var files = Files.walk(path)) {
var classes = files
.filter(Files::isRegularFile)
.filter(p -> p.getFileName().toString().endsWith(".class"))
.toList();
for (var cls : classes) {
try (var is = Files.newInputStream(cls)) {
var reader = new ClassReader(is);
reader.accept(new ClassVisitor(Opcodes.ASM9) {
private String clsName;
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.clsName = name;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public AnnotationVisitor visitAnnotation(String name, boolean runtime) {
if (!"Lnet/minecraftforge/fml/common/Mod;".equals(name))
return super.visitAnnotation(name, runtime);
return new AnnotationVisitor(Opcodes.ASM9) {
@Override
public void visit(String key, Object value) {
if ("value".equals(key)) {
int idx = clsName.lastIndexOf('/');
var pkg = clsName.substring(0, idx);
mods.computeIfAbsent(pkg, _ -> new HashSet<>()).add((String)value);
idx = pkg.lastIndexOf('/');
while (idx != -1) {
pkg = pkg.substring(0, idx);
idx = pkg.lastIndexOf('/');
if (mods.containsKey(pkg))
throw new IllegalStateException("Invalid ForgeDev test mod layout, conflicting packages: " + pkg + " for " + clsName);
}
}
}
};
}
}, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
}
}
} catch (IOException e) {
sneak(e);
}
return mods;
}
// Builds or update the mods.toml file for all @Mods in this package
private static void buildModsToml(Set<Path> resources, Set<String> modids, Path root, boolean clientOnly) {
var toml = resources.stream()
.map(p -> p.resolve(MODS_TOML))
.filter(Files::exists)
.findFirst()
.orElse(null);
Config cfg = null;
if (toml != null)
cfg = new TomlParser().parse(toml, FileNotFoundAction.READ_NOTHING);
else
cfg = Config.inMemory();
boolean modified = false;
var defaults = Map.of(
"modLoader", "javafml",
"loaderVersion", "[0,)",
"license", "Doesnt fucking matter",
"clientSideOnly", clientOnly
);
for (var key : defaults.keySet()) {
if (!cfg.contains(key)) {
cfg.set(key, defaults.get(key));
modified = true;
}
}
var modlist = new ArrayList<Config>();
if (cfg.contains("mods")) {
@SuppressWarnings("unchecked")
var existing = ((List<Config>)cfg.get("mods"));
modlist.addAll(existing);
}
for (var modid : modids) {
if (modlist.stream().noneMatch(c -> modid.equals(c.get("modId")))) {
modified = true;
var tmp = Config.inMemory();
tmp.set("modId", modid);
modlist.add(tmp);
}
}
cfg.set("mods", modlist);
if (modified) {
var target = root.resolve(MODS_TOML);
try {
Files.createDirectories(target.getParent());
new TomlWriter().write(cfg, target, WritingMode.REPLACE);
} catch (IOException e) {
throw new IllegalStateException("Failed to create in memory toml: " + target, e);
}
}
}
// This is optional, it just hides a warning screen when starting up. I should probably remove this once I restructure how data gen for test mods work and make it generate there.
private static void buildPackMeta(Set<Path> paths, Path root) {
var existing = paths.stream()
.map(p -> p.resolve(PACK_META))
.filter(Files::exists)
.findFirst()
.orElse(null);
if (existing != null)
return;
var target = root.resolve(PACK_META);
try {
Files.createDirectories(target.getParent());
Files.writeString(target,
"""
{
"pack": {
"description": "doesn't matter",
"pack_format": 18
}
}
""", StandardCharsets.UTF_8, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
} catch (IOException e) {
throw new IllegalStateException("Failed to make in memory pack.mcmeta: " + target, e);
}
}
private static void moveModuleInfo(Set<Path> paths, Path root) {
var existing = paths.stream()
.map(p -> p.resolve("module-info.dat"))
.filter(Files::exists)
.findFirst()
.orElse(null);
var target = root.resolve("module-info.class");
copyFile(existing, target);
}
private static void copyFile(@Nullable Path existing, Path target) {
if (existing == null)
return;
try {
Files.createDirectories(target.getParent());
Files.copy(existing, target);
} catch (IOException e) {
throw new IllegalStateException("Failed to copy " + existing.getFileName() + " to memory: " + target, e);
}
}
@SuppressWarnings("unchecked")
private static <E extends Throwable, R> R sneak(Throwable e) throws E {
throw (E)e;
}
}
@@ -0,0 +1,56 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.examiner;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.spi.FileSystemProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.function.BiPredicate;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
final class UnionHelper {
private static final FileSystemProvider UFSP = getUnionFileSystemProvider();
private static FileSystemProvider getUnionFileSystemProvider() {
for (var provider : FileSystemProvider.installedProviders()) {
if ("union".equals(provider.getScheme()))
return provider;
}
throw new IllegalStateException("Missing UnionFileSystemProvider");
};
static FileSystem newFileSystem(BiPredicate<String, String> filter, Path[] paths) {
try {
if (paths == null || paths.length == 0)
throw new IllegalArgumentException("Must contain atleast one path");
if (filter == null && paths.length == 1)
return FileSystems.newFileSystem(paths[0]);
var map = new HashMap<String, Object>();
if (filter != null)
map.put("filter", filter);
var lst = new ArrayList<>(Arrays.asList(paths));
var base = lst.remove(0);
map.put("additional", lst);
return UFSP.newFileSystem(base, map);
} catch (IOException e) {
return sneak(e);
}
}
@SuppressWarnings("unchecked")
private static <E extends Throwable, R> R sneak(Throwable e) throws E {
throw (E)e;
}
}
@@ -0,0 +1 @@
net.minecraftforge.examiner.BootstrapFilter
@@ -0,0 +1 @@
net.minecraftforge.examiner.Locator
+68
View File
@@ -0,0 +1,68 @@
plugins {
id 'net.minecraftforge.forge.build.convention'
}
gradleutils.displayName = 'Forge-Tests'
description = 'Mods used for testing Forge features'
license {
exclude 'com/example/'
}
convention.basicRuns('mdk_datagen', file('src/main/generated')) {
flat = true
}
final mdkGradleWrapper = tasks.register('mdkGradleWrapper', Wrapper) {
gradleVersion = libs.versions.gradle.get()
scriptFile = layout.buildDirectory.dir("wrapper/" + gradleVersion + "/gradlew")
jarFile = layout.buildDirectory.dir("wrapper/" + gradleVersion + "/gradle/wrapper/gradle-wrapper.jar")
}
final mdkZip = tasks.register('mdkZip', Zip) {
dependsOn mdkGradleWrapper
archiveBaseName = project.name
archiveClassifier = 'mdk'
archiveVersion = project.version
destinationDirectory = file('build/libs')
from(rootProject.file('LICENSE.txt'))
from(changelog.task.flatMap(task -> task.outputFile))
from mdkGradleWrapper.map(Wrapper.&getScriptFile)
from mdkGradleWrapper.map(Wrapper.&getBatchScript)
into('gradle/wrapper/') {
from mdkGradleWrapper.map(Wrapper.&getJarFile)
from mdkGradleWrapper.map(Wrapper.&getPropertiesFile)
}
from(file('template/')){
file('template/gitignore.txt').eachLine {
if (!it.trim().isEmpty() && !it.trim().startsWith('#'))
exclude it
}
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: [
FORGE_VERSION: versions.forge,
FORGE_GROUP: project.group,
FORGE_NAME: project.name,
MC_VERSION: versions.minecraft,
FORGE_SPEC_VERSION: versions.forgeSpec,
MC_NEXT_VERSION: versions.minecraftNext,
EVENTBUS_VERSION: libs.versions.eventbus.get()
])
rename 'gitignore\\.txt', '.gitignore'
rename 'gitattributes\\.txt', '.gitattributes'
}
from(file('src/main/java/com/example/examplemod/')) {
into('src/main/java/com/example/examplemod/')
}
from(file('src/main/generated/')) {
into('src/main/resources/')
exclude '**/.cache/'
}
}
forgedev.provideFile('mdkZip', mdkZip.flatMap { it.archiveFile } )
@@ -0,0 +1,13 @@
modLoader="javafml"
loaderVersion="[0,)"
license="LGPL_v2"
[[mods]]
modId="examplemod"
version="1.0.0"
displayName="Example Mod DataGen"
description="Example Mod DataGen"
[[mods]]
modId="mdk_datagen"
version="1.0.0"
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+25
View File
@@ -0,0 +1,25 @@
plugins {
id 'net.minecraftforge.forge.build.convention'
}
gradleutils.displayName = 'Forge-Tests'
description = 'Mods used for testing Forge features'
license {
exclude 'com/example/'
}
convention.manifest('net/minecraftforge/forge/test/', [
'Automatic-Module-Name': 'net.minecraftforge.forge.test'
])
convention.manifest('net/minecraftforge/forge/debug/')
convention.publish()
convention.basicRuns('.+', file('src/main/generated')) {
flat = false
}
dependencies {
api project(':forge-examiner')
}
Loaded 100 of 340 files, more files were not shown because too many files have changed in this diff. Show more