From 72be0c9ccdb6e299c781eba7b4022ea4df2770bb Mon Sep 17 00:00:00 2001 From: LexManos Date: Tue, 30 Jun 2026 12:42:29 -0700 Subject: [PATCH] ForgeDev 7 Co-authored-by: Jonathing Co-authored-by: Paint_Ninja --- .gitignore | 16 +- LICENSE.txt | 2 +- build.gradle | 815 ++++++++++++- buildSrc/.gitignore | 3 - buildSrc/build.gradle | 14 - .../forge/tasks/BundleList.groovy | 67 -- .../forge/tasks/BytecodeFinder.groovy | 52 - .../tasks/BytecodePredicateFinder.groovy | 48 - .../forge/tasks/CleanProperties.groovy | 97 -- .../forge/tasks/ClosureHelper.groovy | 33 - .../forge/tasks/DownloadLibraries.groovy | 54 - .../forge/tasks/ExtractFile.groovy | 28 - .../forge/tasks/FieldCompareFinder.groovy | 101 -- .../forge/tasks/InheritanceData.groovy | 49 - .../forge/tasks/InstallerJar.groovy | 158 --- .../forge/tasks/InstallerJson.groovy | 73 -- .../forge/tasks/JarJarMetadataOptions.java | 415 ------- .../forge/tasks/LauncherJson.groovy | 94 -- .../forge/tasks/MergeJars.groovy | 46 - .../forge/tasks/ObjectTarget.groovy | 32 - .../tasks/SetupCheckJarCompatibility.groovy | 47 - .../forge/tasks/TeamcityRequests.groovy | 74 -- .../minecraftforge/forge/tasks/Util.groovy | 230 ---- .../forge/tasks/ValidateDeprecations.groovy | 94 -- .../forge/tasks/checks/CheckATs.groovy | 282 ----- .../forge/tasks/checks/CheckExcs.groovy | 95 -- .../forge/tasks/checks/CheckMode.groovy | 11 - .../forge/tasks/checks/CheckPatches.groovy | 182 --- .../forge/tasks/checks/CheckSAS.groovy | 110 -- .../forge/tasks/checks/CheckTask.groovy | 114 -- build_clean.gradle | 73 -- build_forge.gradle | 1021 ----------------- build_shared.gradle | 89 -- fmlcore/build.gradle | 90 +- fmlearlydisplay/build.gradle | 147 ++- fmlloader/build.gradle | 244 ++-- forge-transformers/build.gradle | 113 +- .../coremods/finalize_spawn_targets.json | 24 - gradle.properties | 34 +- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 46175 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 15 +- gradlew.bat | 5 +- javafmllanguage/build.gradle | 109 +- lowcodelanguage/build.gradle | 104 +- mclanguage/build.gradle | 103 +- minecraft.versions.toml | 6 + settings.gradle | 307 +++-- 48 files changed, 1540 insertions(+), 4382 deletions(-) delete mode 100644 buildSrc/.gitignore delete mode 100644 buildSrc/build.gradle delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BundleList.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BytecodeFinder.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BytecodePredicateFinder.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/CleanProperties.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ClosureHelper.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/DownloadLibraries.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ExtractFile.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/FieldCompareFinder.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InheritanceData.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InstallerJar.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InstallerJson.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/JarJarMetadataOptions.java delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/LauncherJson.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/MergeJars.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ObjectTarget.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/SetupCheckJarCompatibility.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/TeamcityRequests.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/Util.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ValidateDeprecations.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckATs.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckExcs.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckMode.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckPatches.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckSAS.groovy delete mode 100644 buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckTask.groovy delete mode 100644 build_clean.gradle delete mode 100644 build_forge.gradle delete mode 100644 build_shared.gradle create mode 100644 minecraft.versions.toml diff --git a/.gitignore b/.gitignore index d3754c4c8f..c134175e62 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +#forgedev dev if the folder exists in here +forgedev + #eclipse **/bin **/.settings @@ -22,10 +25,12 @@ /*/build /*/.gradle +# Minecraft +/run +/*.launch + # Projects repo, either ignore it, or ignore patches. -/projects/mcp/ -/projects/clean/ -/projects/forge/ +src/minecraft #occupational hazards /projects/**/build/ @@ -48,3 +53,8 @@ src/*/generated/**/.cache/ /*/.factorypath /*/.apt_generated/ /fmlcore/logs/ +/lib/ +/_actual/ +/runs/ +/forge.ipr +/forge.iws diff --git a/LICENSE.txt b/LICENSE.txt index 249debe26b..51c22dd7db 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -3,7 +3,7 @@ parts herein are licensed under the terms of the LGPL 2.1 found here http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt and copied below. -Homepage: http://minecraftforge.net/ +Homepage: https://minecraftforge.net/ https://github.com/MinecraftForge/MinecraftForge diff --git a/build.gradle b/build.gradle index d99a97e636..46ebfa85f0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,57 +1,798 @@ -import net.minecraftforge.forge.tasks.* -import net.minecraftforge.gradleutils.PomUtils +import org.apache.tools.ant.filters.ReplaceTokens plugins { - id 'net.minecraftforge.licenser' version '1.0.1' - id 'com.github.ben-manes.versions' version '0.46.0' - id 'net.minecraftforge.gradleutils' version '[2.6.4,2.7)' + id 'java-library' + id 'idea' id 'eclipse' - id 'de.undercouch.download' version '5.4.0' - id 'net.minecraftforge.gradle.patcher' version '[6.0.51,6.2)' apply false - id 'net.minecraftforge.gradle.mcp' version '[6.0.51,6.2)' apply false - id 'net.minecraftforge.gradlejarsigner' version '1.0.4' - //id 'org.barfuin.gradle.taskinfo' version '3.0.1' + 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.renamer + alias libs.plugins.jarsigner + id 'net.minecraftforge.forgedev' + id 'net.minecraftforge.forge.build.convention' } -Util.init() //Init all our extension methods! +gradleutils.displayName = 'Forge' +group = 'net.minecraftforge' +description = 'Modifications to Minecraft to enable mod developers.' -ext { - VERSION = gitversion.getMCTagOffsetBranch(MC_VERSION) - FORGE_VERSION = VERSION.substring(MC_VERSION.length() + 1) +// 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('binpatcher') { + version = buildLibs.binarypatcher.get().version + } + configure('installertools') { + version = buildLibs.installertools.get().version + } + configure('jarcompatibilitychecker') { + javaLauncher = javaToolchains.launcherFor(java.toolchain) + } +} +renamerTools { + configure('classes') { + artifact = buildLibs.renamer.get().toString() + ':all' + } + configure('srg2source') { + version = buildLibs.srg2source.get().version + javaLauncher = javaToolchains.launcherFor(java.toolchain) + } } -changelog { - from '65.0' +/* This is used for debugging, comparing all published artifacts against actual production +forgedev.validatePublish { + task { + baseBranch = 'upstream/26.1.2' + versionPrefix = minecraftVersion + } +} +*/ + +final minecraftFiles = forgedev.minecraftFiles(minecraftVersion) + +final mcpBase = forgedev.mcpBase { + mcpVersion = "${minecraftVersion}-${project.ext.mcpVersion}" + accessTransformers.from file('src/main/resources/META-INF/accesstransformer.cfg') + sideAnnotationStrippers.from file('src/main/resources/forge.sas') +} + +forgedev.patches { + base = mcpBase + patches = file('patches/minecraft') + patched = file('src/minecraft/java') + sourceSets.main.java.srcDir patched } tasks.register('setup') { - dependsOn ':forge:extractMapped' - if (findProject(':clean')) - dependsOn ':clean:extractMapped' + dependsOn forgedev.patches.apply } -tasks.register('doChecks') { - dependsOn ':forge:checkJarCompatibility' - dependsOn ':forge:publish' -} - -project(':mcp') { - apply plugin: 'net.minecraftforge.gradle.mcp' - mcp { - config MC_VERSION + '-' + MCP_VERSION - pipeline = 'joined' +sourceSets { + named('main') { + resources.srcDir 'src/main/generated' } - repositories { - mavenLocal() + named('test') { + resources.srcDir 'src/test/generated' } } -if (System.env.TEAMCITY_VERSION) { - //Only setup the CI environment if and only if the environment variables are set. - tasks.named('configureTeamCity').configure { - doLast { - println "##teamcity[buildNumber '${project(':forge').version}']" - println "##teamcity[setParameter name='env.PUBLISHED_JAVA_ARTIFACT_VERSION' value='${project(':forge').version}']" +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 +} + +// TODO [ForgeDev] Support compileOnlyApi, runtimeOnly, and a consumable annotation processor configuration +configurations { + // Don't pull all libraries, if we're missing something, add it to the installer list so the installer knows to download it. + register('bootstrap') { + transitive = false + canBeDeclared = true + canBeResolved = true + } + + register('installer') { + transitive = false + canBeDeclared = true + canBeResolved = true + extendsFrom(bootstrap) + } + + named('api') { + extendsFrom(installer) + } + + named('implementation') { + // Inherit vanilla Minecraft's dependencies + extendsFrom(mcpBase.dependencyConfiguration) + } +} + +dependencies { + // These need to actually be on the classpath at the start. This is only used for the server shim jar. + // And this is only needed because custom file systems are REQUIRED to be on the boot classloader. + // This has ASM/BootStrap/Unsafe all because I haven't gotten around to moving UnionFileSystem out to its own project. + bootstrap libs.jarjar.fs // JarInJar file system + bootstrap libs.roimfs // JarInJar File System - FileSystems need to be on boot loader. + bootstrap libs.bundles.jimfs // In memory file system used for ForgeDev launches + bootstrap libs.securemodules // Has Union file system in it + bootstrap libs.unsafe // Needed by securemodules + bootstrap libs.bundles.asm // Needed by securemodules + + implementation libs.jopt.simple + + installer libs.bootstrap + installer libs.bootstrap.api // Needed by securemodules + installer libs.accesstransformers + installer libs.eventbus + installer libs.jspecify // Dep of EventBus + installer libs.forgespi + installer libs.coremods.api + installer libs.modlauncher + installer libs.mergetool.api + installer libs.bundles.night.config + installer libs.maven.artifact + installer libs.bundles.terminalconsoleappender + installer libs.mixin + installer libs.mixinextras.forge + installer libs.bundles.jarjar + installer libs.roimfs + + installer projects.fmlcore + installer projects.fmlloader + installer projects.fmlearlydisplay + installer projects.javafmllanguage + installer projects.lowcodelanguage + installer projects.mclanguage + installer projects.forgeTransformers + + runtimeOnly files(mcpBase.extra) + + runtimeOnly libs.bootstrap + runtimeOnly libs.bootstrap.dev + + annotationProcessor libs.eventbus.validator +} + +final EXTRA_TXTS = [ + 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' + + systemProperty 'bsl.debug', 'true' + systemProperty 'terminal.jline', 'true' + with(sourceSets.test) { + systemProperty 'forge.enableGameTest', 'true' + systemProperty 'forgedev.enableTestMods', '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}' + } + } + + create('server') { + options { + args '--launchTarget', 'forge_dev_server' + } + } + + create('gameTestServer') { + options { + 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 + } + } + + 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) { + args '--mod', '.+', + '--existing', sourceSets.test.resources.srcDirs[0], + '--output', rootProject.file('src/test/generated/') + } + } + } + + create('clientData') { + options { + args '--all', + '--existing', sourceSets.main.resources.srcDirs[0], + '--assetsDir', '{assets_root}', + '--assetIndex', '{asset_index}' + + with(sourceSets.main) { + args '--launchTarget', 'forge_dev_client_data', + '--mod', 'forge', + '--output', rootProject.file('src/main/generated/'), + '--mod', 'forge' + } + + with(sourceSets.test) { + args '--launchTarget', 'forge_dev', + '--launchEntry', 'minecraft/net.minecraft.client.data.Main', + '--launchData', + '--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 { + mainClass = 'net.minecraftforge.bootstrap.ForgeBootstrap' + args = ['--launchTarget', 'forge_server'] + } + classpath { + libraries configurations.installer + library tasks.named('universalJar', Jar) + library forgedev.binaryPatches('server').apply, 'server' + serverBundle.fileProvider(minecraftFiles.server) + } + bootstrapClasspath(configurations.bootstrap) + jar { + jarSigner.sign(it) + } +} + +final finalizeSpawn = forgedev.methodCallFinder('findFinalizeSpawnTargets') { + jar.fileProvider(mcpBase.classes) + output = rootProject.file('forge-transformers/src/main/resources/coremods/finalize_spawn_targets.json') + blacklist 'net/minecraft/world/level/BaseSpawner' // Ignore this class as we special case it. + invokeVirtual('finalizeSpawn', '(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;') +} + +forgedev.validateDeprecations(tasks.named('jar', Jar)) { + mcVersion = minecraftVersion +} + +forgedev.checks() { + base = mcpBase + ats {} + sas {} + execs {} + + patches { + dependsOn forgedev.patches.make + patchDir = forgedev.patches.patches + patchesWithS2SArtifact = [ + 'minecraft/net/minecraft/client/renderer/ViewArea.java.patch', + 'minecraft/net/minecraft/data/models/blockstates/Variant.java.patch', + ] + } + + fix { + dependsOn finalizeSpawn + } + + forgedev.patches.make.configure { + finalizedBy forgedev.checks.patches.fix + } +} + +final crowdin = forgedev.crowdin // Create the crowdin tasks +tasks.named('jar', Jar) { + from(EXTRA_TXTS) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + exclude '.cache' + + from(zipTree(crowdin.flatMap { it.output } )) { + include 'assets/forge/lang/*.json' + } + + manifest { + attributes([ + 'Automatic-Module-Name': 'net.minecraftforge.forge' + ]) + 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) +} + +final universalJar = tasks.register('universalJar', Jar) { + from (zipTree(tasks.named('jar', Jar).flatMap { it.archiveFile } )) { + exclude(forgedev.filterVanilla(mcpBase.classes)) + } + manifest = tasks.named('jar', Jar).get().manifest + archiveClassifier = 'universal' + jarSigner.sign(it) +} + +final sourcesJarAll = tasks.register('sourcesJarAll', Jar) { + archiveClassifier = 'sources-all' + from(sourceSets.main.allJava.srcDirs) +} + +// Our sources, plus the patches we do to Vanilla Minecraft +final sourcesJar = tasks.register('sourcesJar', Jar) { + archiveClassifier = 'sources' + from(sourceSets.main.allJava.srcDirs - forgedev.patches.patched.asFile.get()) + mustRunAfter(forgedev.patches.make) + from(forgedev.patches.patches) { + into 'patches/' + } +} + +forgedev.userDev { + base(mcpBase) + + config { + universal universalJar + sources sourcesJar + addLibraries(configurations.installer) + // TODO [ForgeDev][UserDev] Split API/Runtime elements in userdev config + // so we don't gotta keep making exceptions for MixinExtras + addCompileDependency(libs.mixinextras.common) + addAnnotationProcessorDependency(libs.mixinextras.common) + + // TODO [ForgeDev] Consolidate Patcher + UserDev Config runs in a sane way + runs { + configureEach { + parents = [] + main = 'net.minecraftforge.bootstrap.ForgeBootstrap' + args '--gameDir', '.' + jvmArgs '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders' + client = it.name.containsIgnoreCase('client') + environment 'MCP_MAPPINGS', '{mcp_mappings}' + } + + register('clientData') { + args '--launchTarget', "forge_userdev_client_data" + args '--assetIndex', '{asset_index}' + args '--assetsDir', '{assets_root}' + } + + register('client') { + property 'forge.enableGameTest', 'true' + args '--launchTarget', "forge_userdev_client" + args '--version', 'MOD_DEV' + args '--assetIndex', '{asset_index}' + args '--assetsDir', '{assets_root}' + } + + register('server') { + property 'forge.enableGameTest', 'true' + args '--launchTarget', "forge_userdev_server" + } + + register('gameTestServer') { + args '--launchTarget', "forge_userdev_server_gametest" + } + + register('data') { + args '--launchTarget', "forge_userdev_data" + args '--assetIndex', '{asset_index}' + args '--assetsDir', '{assets_root}' + } + } + } + patches { + modified.setFrom(sourcesJarAll.flatMap { it.archiveFile }) + } + binaryPatches { + mustRunAfter(forgedev.patches.make) + patches.setFrom(forgedev.patches.patches) + dirty.setFrom(tasks.named('jar', Jar).flatMap { it.archiveFile }) + } +} + +forgedev.userdevCompatibility(minecraftVersion) { + clean = mcpBase.classes + dirty = tasks.named('jar', Jar) +} + +// region Installer tasks, Not needed when we simplify the installer/remove obf +final clientBinPatches = forgedev.binaryPatches('client') { + clean = minecraftFiles.client + // Workaround for 3rd party launcher issues, marker needed to locate MC, store needed to fix zlib-ng issues + apply { + store = true + marker = '.forge_patched_minecraft' + } +} +final serverBinPatches = forgedev.binaryPatches('server') { + clean = minecraftFiles.serverExtracted + apply { store = true } +} +// endregion + +forgedev.installer { + dev = !forgedev.ci + offline = false + baseVersion = buildLibs.installer.get().version.toString() + + // Add extra libraries + pack serverShim.jar // The Server executable jar IS needed to be packed, as the installer would need a spec bump in order to download it + pack universalJar // I don't think this is strictly needed to be packed anymore, will need to double check later + + launcherLibrary universalJar + // We configure the launcher here to keep the order for easier diff, after publishing, move this to the LauncherJson configuration below + launcherJson { + // We create this during install, so it's not downloadable + generated(clientBinPatches.apply, 'client') + } + + launcherLibraries configurations.installer + + final launcherId = "${minecraftVersion}-${project.name}-${forgeVersion}" + final installertools = tool(buildLibs.installertools) + final binarypatcher = tool(buildLibs.binarypatcher) + final renamer = tool(buildLibs.renamer) + + jar { + from(EXTRA_TXTS) + from(rootProject.file('/forge_installer_logo.png')) { + rename { 'big_logo.png' } + } + from(clientBinPatches.create.flatMap { t -> t.output }) { + rename { 'data/client.lzma' } + } + from(serverBinPatches.create.flatMap { t -> t.output }) { + rename { 'data/server.lzma' } + } + + final argsFile = rootProject.file('server_files/args.txt') + final tokens = [tokens: [ + SHIM_JAR_FILE: serverShim.jar.get().archiveFileName.get(), + MAVEN_PATH: forgedev.mavenArtifact.directory + ]] + from(argsFile) { + filter(tokens, ReplaceTokens) + into 'data' + rename { 'unix_args.txt' } + } + from(argsFile) { + filter(tokens, ReplaceTokens) + into 'data' + rename { 'win_args.txt' } + } + + from(rootProject.file('server_files')) { + filter(tokens, ReplaceTokens) + into 'data' + exclude 'args.txt' + } + + jarSigner.sign(it) + } + json { + icon = rootProject.file('icon.ico') + executable serverShim.jar + minecraft = minecraftVersion + 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_SHA', minecraftFiles.client, minecraftFiles.serverExtracted + + data 'BINPATCH', '/data/client.lzma', '/data/server.lzma' + data 'PATCHED', "[$projectArtifact:client]", "[$projectArtifact:server]" + data 'PATCHED_SHA', clientBinPatches.apply.flatMap { it.output }, serverBinPatches.apply.flatMap { it.output } + + steps = [ + extract(installertools) { + side = 'server' + archive '{INSTALLER}' + extract 'data/README.txt', '{ROOT}/README.txt' + extract 'data/run.sh', '{ROOT}/run.sh' + executable '{ROOT}/run.sh' + extract 'data/run.bat', '{ROOT}/run.bat' + optional 'data/user_jvm_args.txt', '{ROOT}/user_jvm_args.txt' + extract 'data/unix_args.txt', "{ROOT}/libraries/${forgedev.mavenArtifact.directory}/unix_args.txt" + extract 'data/win_args.txt', "{ROOT}/libraries/${forgedev.mavenArtifact.directory}/win_args.txt" + }, + extractBundle(installertools) { + side = 'server' + libraries() + }, + extractBundle(installertools) { + side = 'server' + jar '{MINECRAFT_JAR}', '{MC_UNPACKED}' + cache '{MC_UNPACKED}', '{MC_UNPACKED_SHA}' + }, + step(binarypatcher) { + side = 'server' + args = [ + '--clean', '{MC_UNPACKED}', + '--output', '{PATCHED}', + '--apply', '{BINPATCH}', + '--data', '--unpatched', + '--store' + ] + cache '{PATCHED}', '{PATCHED_SHA}' + }, + step(binarypatcher) { + side = 'client' + args = [ + '--clean', '{MINECRAFT_JAR}', + '--output', '{PATCHED}', + '--apply', '{BINPATCH}', + '--data', '--unpatched', + '--store', + '--marker', '.forge_patched_minecraft' + ] + cache '{PATCHED}', '{PATCHED_SHA}' + } + ] + } + + launcherJson { + // These are flags to preserve glitchy old behavior to make diffing easier, removing when migrating to production is recommended + sortLibraries = false // Don't sort, makes things non-deterministic, but easier to diff old versions + duplicateLibraries = true // Our 'bootstrap' layer go added twice + librariesLast = false // Makes libraries be added before args + + id = launcherId + inheritsFrom = minecraftVersion + mainClass = 'net.minecraftforge.bootstrap.ForgeBootstrap' + gameArgs = [ '--launchTarget', 'forge_client' ] + jvmArgs = [ '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders' ] + } +} + +final mdkGradleWrapper = tasks.register('mdkGradleWrapper', Wrapper) { + gradleVersion = '9.5.0' + 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' + var includedProjects = [ ':fmlcore', ':fmlloader', ':javafmllanguage', ':mclanguage', ':forge-transformers' ] + source includedProjects.collect { project(it).sourceSets.main.allJava } + classpath = classpath + files(includedProjects.collect { project(it).sourceSets.main.compileClasspath }) + + var docsDir = rootProject.file('src/docs/') + inputs.dir(docsDir) + .withPropertyName('docs resources directory') + .withPathSensitivity(PathSensitivity.RELATIVE) + .optional() + + failOnError = false + + // Exclude the Minecraft classes if not enabled + if (!project.hasProperty('generateAllDocumentation')) { + exclude 'net/minecraft/**' + exclude 'com/mojang/**' + } + exclude 'mcp/**' + + options { + stylesheetFile = new File(docsDir, 'stylesheet.css') + + tags = [ + 'apiNote:a:API Note:', + 'implSpec:a:Implementation Requirements:', + 'implNote:a:Implementation Note:' + ] + + groups = [ + 'Forge Mod Loader': [ + 'net.minecraftforge.fml.common.asm*', + 'net.minecraftforge.fml.loading*', + 'net.minecraftforge.fml.server*' + ], + 'FML Core': [ + 'net.minecraftforge.fml', + 'net.minecraftforge.fml.config*', + 'net.minecraftforge.fml.event*', + 'net.minecraftforge.fml.util*' + ], + 'FML Common': [ + 'net.minecraftforge.fml.core', + 'net.minecraftforge.fml.event.config', + 'net.minecraftforge.fml.event.lifecycle' + ], + 'FML Java/MC Language Providers': [ + 'net.minecraftforge.fml.common', + 'net.minecraftforge.fml.javafmlmod', + 'net.minecraftforge.fml.mclanguageprovider' + ], + 'Minecraft Forge API': [ + 'net.minecraftforge*' + ] + ] + + author = false + noSince = true + noHelp = true + + 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 = "
${forgeVersion} for Minecraft ${minecraftVersion}
" + } + + doLast { + project.copy { + from docsDir + exclude '/stylesheet.css' + into destinationDir + } + } +} +*/ + +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 + } + } + } +} + + +/* +// 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 + 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) } + } + } +} +*/ +// endregion +// We dont have any unit tests, so disable this error +tasks.withType(AbstractTestTask).configureEach { + failOnNoDiscoveredTests = false +} +// This shouldn't be needed according to the Gradle devs, as we never run these commands together. +// But in another case of Gradle being gradle, here we are +[ '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} +} diff --git a/buildSrc/.gitignore b/buildSrc/.gitignore deleted file mode 100644 index 2979d33310..0000000000 --- a/buildSrc/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/.gradle/ -/build/ -/out/ diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle deleted file mode 100644 index c3e2c4e2b5..0000000000 --- a/buildSrc/build.gradle +++ /dev/null @@ -1,14 +0,0 @@ -repositories { - maven { url = 'https://maven.minecraftforge.net/' } - mavenCentral() -} - -dependencies { - implementation 'org.ow2.asm:asm:9.9.1' - implementation 'org.ow2.asm:asm-tree:9.9.1' - implementation 'net.minecraftforge:srgutils:0.6.2' - implementation 'net.minecraftforge:JarJarMetadata:0.3.27' - implementation 'commons-io:commons-io:2.13.0' - implementation 'com.google.code.gson:gson:2.10.1' - implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r' -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BundleList.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BundleList.groovy deleted file mode 100644 index ad9bed5f55..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BundleList.groovy +++ /dev/null @@ -1,67 +0,0 @@ -package net.minecraftforge.forge.tasks - -import org.gradle.api.tasks.* -import org.gradle.api.DefaultTask -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.bundling.AbstractArchiveTask - -import java.util.zip.ZipFile - -abstract class BundleList extends DefaultTask { - @InputFiles abstract ConfigurableFileCollection getConfig() - @InputFile abstract RegularFileProperty getServerBundle() - @OutputFile abstract RegularFileProperty getOutput() - - BundleList() { - config.setFrom(project.configurations.installer) - output.convention(project.layout.buildDirectory.file("$name/output.list")) - configure { - dependsOn(project.tasks.universalJar) - inputs.file(project.tasks.universalJar.archiveFile) - dependsOn(project.tasks.applyServerBinPatches) - inputs.file(project.tasks.applyServerBinPatches.output) - } - } - - @TaskAction - void run() { - def entries = [:] as TreeMap - def resolved = project.configurations.installer.resolvedConfiguration.resolvedArtifacts - for (def dep : resolved) { - def info = Util.getMavenInfoFromDep(dep) - //println("$dep.file.sha1\t$info.name\t$info.path") - entries.put("$info.art.group:$info.art.name", "$dep.file.sha256\t$info.name\t$info.path") - } - - var packed = (AbstractArchiveTask) project.tasks.universalJar - var info = Util.getMavenInfoFromTask(packed) - def file = packed.archiveFile.get().asFile - entries.put("$info.art.group:$info.art.name:$info.art.classifier", "$file.sha256\t$info.name\t$info.path") - - var classifier = 'server' - var genned = project.tasks.applyServerBinPatches - info = Util.getMavenInfoFromTask(genned, classifier) - file = genned.output.get().asFile - entries.put("$info.art.group:$info.art.name:$info.art.classifier", "$file.sha256\t$info.name\t$info.path") - - try (def zip = new ZipFile(serverBundle.get().asFile)) { - def entry = zip.getEntry('META-INF/libraries.list') - def data = zip.getInputStream(entry).text.split('\n') - for (def line : data) { - def (sha, artifact, path) = line.split('\t') - def (group, name, other) = artifact.split(':', 3) - //println("Group: $group Name: $name") - def key = "$group:$name" - if (other.indexOf(':') != -1){ - key += ':' + other.split(':', 2)[1] - //println("\tKey: $key") - } - if (!entries.containsKey(key)) - entries.put(key, line) - } - } - - output.get().asFile.text = entries.values().join('\n') - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BytecodeFinder.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BytecodeFinder.groovy deleted file mode 100644 index 8f8271f490..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BytecodeFinder.groovy +++ /dev/null @@ -1,52 +0,0 @@ -package net.minecraftforge.forge.tasks - -import groovy.json.JsonBuilder -import groovy.transform.CompileStatic -import org.gradle.api.DefaultTask -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.TaskAction -import org.objectweb.asm.tree.ClassNode -import org.objectweb.asm.tree.FieldNode -import org.objectweb.asm.tree.MethodNode - -@CompileStatic -abstract class BytecodeFinder extends DefaultTask { - @InputFile abstract RegularFileProperty getJar() - // It should be fine to mark the output as internal as we want to control when we run it anyways. - // This also shuts Gradle 8 up about implicit task dependencies. - @Internal abstract RegularFileProperty getOutput() - - BytecodeFinder() { - output.convention(project.layout.buildDirectory.dir(name).map { it.file("output.json") }) - } - - @TaskAction - protected void exec() { - Util.init() - - var outputFile = output.get().asFile - if (outputFile.exists()) - outputFile.delete() - - pre() - - Util.processClassNodes(jar.get().asFile, this.&process) - - post() - outputFile.text = new JsonBuilder(getData()).toPrettyString() - } - - - protected process(ClassNode node) { - if (node.fields !== null) node.fields.each { process(node, it) } - if (node.methods !== null) node.methods.each { process(node, it) } - } - - protected pre() {} - protected process(ClassNode parent, FieldNode node) {} - protected process(ClassNode parent, MethodNode node) {} - protected post() {} - protected abstract Object getData() -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BytecodePredicateFinder.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BytecodePredicateFinder.groovy deleted file mode 100644 index b0efa482cc..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BytecodePredicateFinder.groovy +++ /dev/null @@ -1,48 +0,0 @@ -package net.minecraftforge.forge.tasks - -import groovy.transform.CompileStatic -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Internal -import org.objectweb.asm.Opcodes -import org.objectweb.asm.tree.ClassNode -import org.objectweb.asm.tree.MethodNode - -@CompileStatic -abstract class BytecodePredicateFinder extends BytecodeFinder { - - @Internal - abstract Property> getPredicate() - - private final Map> matches = new TreeMap<>(Comparator.naturalOrder()) - - @Override - protected process(ClassNode parent, MethodNode node) { - for (final current : node.instructions) { - if (predicate.get().call(parent, node, current)) { - var methods = matches.compute(parent.name, { k, v -> v ?: new ArrayList<>() }) - - // only add non-synthetic methods - if ((node.access & Opcodes.ACC_SYNTHETIC) == 0) { - methods.add(node.name + node.desc) - } - - return - } - } - } - - @Internal - @Override - protected Object getData() { - var array = new ArrayList() - matches.forEach { c, m -> - { - array.add( - 'class': c, - 'methods': m - ) - } - } - return array ?: { throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.') }() - } -} \ No newline at end of file diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/CleanProperties.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/CleanProperties.groovy deleted file mode 100644 index 676ac30300..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/CleanProperties.groovy +++ /dev/null @@ -1,97 +0,0 @@ -package net.minecraftforge.forge.tasks; - -import groovy.transform.CompileDynamic; -import groovy.transform.CompileStatic; - -import java.util.Properties; -import java.util.Enumeration; -import java.util.Map; -import java.util.TreeSet; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.Reader; -import java.io.InputStreamReader; -import java.io.IOException; -import java.io.OutputStream; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.Set; - -/** - * Eclipse config files are literally just java properties, with the header cleaned up. - * https://github.com/eclipse/buildship/blob/5b2c7fca7fa86cd74d71b3c099c7b1559eba038e/org.eclipse.buildship.core/src/main/java/org/eclipse/buildship/core/internal/configuration/PreferenceStore.java#L238 - * - * This does the same thing, as well as sorting alphabetically. - * It also ignores all comments. We can add them latter if someone cares. - */ -@CompileStatic -public class CleanProperties extends Properties { - private static final long serialVersionUID = 1L; - private static final String LINE_SEP = System.getProperty("line.separator"); - private static final String UNIX_LINE_SEP = "\n"; - private static final Charset ENCODING = StandardCharsets.UTF_8; - - public CleanProperties load(File input) throws IOException { - if (input.exists()) { - try (Reader is = new InputStreamReader(new FileInputStream(input), ENCODING)) { - super.load(is); - } - } - return this; - } - - public void store(File out) throws IOException { - if (!out.getParentFile().exists()) - out.getParentFile().mkdirs(); - - try (OutputStream os = new FileOutputStream(out)) { - store(os, null); - } - } - - @Override - public synchronized Enumeration keys() { - Set ret = new TreeSet<>(); - for (Enumeration e = super.keys(); e.hasMoreElements();) - ret.add(e.nextElement()); - return Collections.enumeration(ret); - } - - @CompileDynamic - @Override - public Set> entrySet() { - Set> ret = new TreeSet<>((l, r) -> ((String)l.getKey()).compareTo((String)r.getKey())); - ret.addAll(super.entrySet()); - return ret; - } - - @Override - public void store(OutputStream out, String comments) throws IOException { - out.write(clean().getBytes(ENCODING)); - out.flush(); - } - - @Override - public void store(Writer out, String comments) throws IOException { - out.write(clean()); - out.flush(); - } - - private String clean() throws IOException { - ByteArrayOutputStream tmp = new ByteArrayOutputStream(); - try { - super.store(tmp, null); - } finally { - tmp.close(); - } - - String ret = tmp.toString(ENCODING).replace(LINE_SEP, UNIX_LINE_SEP); - ret = ret.substring(ret.indexOf(UNIX_LINE_SEP) + 1); - return ret; - } -} \ No newline at end of file diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ClosureHelper.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ClosureHelper.groovy deleted file mode 100644 index 348ca2cfe2..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ClosureHelper.groovy +++ /dev/null @@ -1,33 +0,0 @@ -package net.minecraftforge.forge.tasks - -import groovy.transform.stc.FirstParam - -import java.util.function.BiConsumer - -public class ClosureHelper { - BiConsumer callback - - public ClosureHelper(Closure cl, BiConsumer callback) { - this.callback = callback - apply(this, cl) - } - - - def methodMissing(String name, Object args) { - if (!args.class.isArray()) return - Object[] aargs = (Object[])args - - if (aargs.length == 1 && aargs[0] instanceof Closure) { - this.callback.accept(name, (Closure)aargs[0]) - } else { - throw new IllegalArgumentException('Unknown method: "' + name + '" with arguments ' + args + ' for ' + this) - } - } - - static T apply(T obj, @DelegatesTo(value = FirstParam, strategy = Closure.DELEGATE_FIRST) Closure cl) { - cl.delegate = obj - cl.resolveStrategy = Closure.DELEGATE_FIRST - cl() - return obj - } -} \ No newline at end of file diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/DownloadLibraries.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/DownloadLibraries.groovy deleted file mode 100644 index c3f98258ff..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/DownloadLibraries.groovy +++ /dev/null @@ -1,54 +0,0 @@ -package net.minecraftforge.forge.tasks - -import org.gradle.api.DefaultTask -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.OutputFiles -import org.gradle.api.tasks.TaskAction -import org.gradle.api.tasks.OutputDirectory - -import java.nio.file.Files - -abstract class DownloadLibraries extends DefaultTask { - @InputFile abstract RegularFileProperty getInput() - @OutputDirectory abstract DirectoryProperty getOutput() - @OutputFile abstract RegularFileProperty getLibrariesOutput() - - DownloadLibraries() { - output.convention(project.layout.buildDirectory.dir(name)) - librariesOutput.convention(project.layout.buildDirectory.dir(name).map(d -> d.file('libraries.txt'))) - } - - @TaskAction - def run() { - Util.init() - File outputDir = output.get().asFile - var libraries = new ArrayList() - - def json = input.get().asFile.json().libraries.each { lib -> - //TODO: Thread? - def artifacts = [lib.downloads.artifact] + lib.downloads.get('classifiers', [:]).values() - artifacts.each{ art -> - def target = new File(outputDir, art.path) - libraries.add(target.absolutePath) - if (!target.exists() || art.sha1 != target.sha1()) { - project.logger.lifecycle("Downloading ${art.url}") - if (!target.parentFile.exists()) { - target.parentFile.mkdirs() - } - new URL(art.url).withInputStream { i -> - target.withOutputStream { it << i } - } - if (art.sha1 != target.sha1()) { - throw new IllegalStateException("Failed to download ${art.url} to ${target.canonicalPath} SHA Mismatch") - } - } - } - } - Files.write(librariesOutput.get().asFile.toPath(), libraries) - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ExtractFile.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ExtractFile.groovy deleted file mode 100644 index 727c3258e0..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ExtractFile.groovy +++ /dev/null @@ -1,28 +0,0 @@ -package net.minecraftforge.forge.tasks - -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.* - -import java.util.zip.ZipFile - -@CompileStatic -abstract class ExtractFile extends DefaultTask { - @InputFile abstract RegularFileProperty getInput() - @Input abstract Property getTarget() - @OutputFile abstract RegularFileProperty getOutput() - - @TaskAction - protected void exec() { - var zip = new ZipFile(input.get().asFile); - var entry = zip.getEntry(target.get()) - if (entry == null) - throw new IllegalStateException(input.get().asFile.absolutePath + " does not contain " + target.get()) - var stream = zip.getInputStream(entry) - output.get().asFile.withOutputStream { out -> - stream.transferTo(out) - } - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/FieldCompareFinder.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/FieldCompareFinder.groovy deleted file mode 100644 index ec0deac865..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/FieldCompareFinder.groovy +++ /dev/null @@ -1,101 +0,0 @@ -package net.minecraftforge.forge.tasks - -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode - -import org.gradle.api.tasks.* -import org.objectweb.asm.tree.AbstractInsnNode -import org.objectweb.asm.tree.ClassNode -import org.objectweb.asm.tree.MethodNode - -import static org.objectweb.asm.Opcodes.* - -abstract class FieldCompareFinder extends BytecodeFinder { - @Nested - Map fields = [:] as HashMap - @Internal - Map fieldsReverse = [:] as HashMap - @Internal - Map> targets = [:] as TreeMap - - @Override - protected pre() { - //fields.each{ k,v -> logger.lifecycle("Fields: " + k + ' ' + v) } - } - - @Override - protected process(ClassNode parent, MethodNode node) { - AbstractInsnNode last = null - def parentInstance = new ObjectTarget(owner: parent.name, name: '', desc: '') - for (int x = 0; x < node.instructions.size(); x++) { - def current = node.instructions.get(x) - if (current.opcode === IF_ACMPEQ || current.opcode === IF_ACMPNE) { - if (last !== null && (last.opcode === GETSTATIC || last.opcode === GETFIELD)) { - def target = new Search(cls: last.owner, name: last.name) - def wanted = fieldsReverse.get(target) - def original = fields.get(wanted) - def instance = new ObjectTarget(owner: parent.name, name: node.name, desc: node.desc) - if (wanted !== null && (original.blacklist === null || (!original.blacklist.contains(instance) && !original.blacklist.contains(parentInstance)))) { - targets.computeIfAbsent(wanted, { k -> new TreeSet() }).add(instance) - } - } - } - last = current - } - } - - @Internal - @Override - protected Object getData() { - def ret = [:] as HashMap - targets.forEach{ k, v -> - def e = fields.get(k) - ret[k] = [ - cls: e.cls, - name: e.name, - replacement: e.replacement, - targets: v - ] - } - return ret - } - - @CompileStatic - @EqualsAndHashCode(excludes = ['replacement', 'blacklist']) - static class Search { - @Input - String cls - - @Input - String name - - @Input - String replacement - - @Nested - @Optional - Set blacklist - - @Override - String toString() { - return cls + '.' + name - } - - def blacklist(String owner, String name, String desc) { - if (blacklist === null) - blacklist = new HashSet<>() - blacklist.add(new ObjectTarget(owner: owner, name: name, desc: desc)) - } - def blacklist(String owner) { - blacklist(owner, '', '') - } - } - - void fields(Closure cl) { - new ClosureHelper(cl, {name, ccl -> - def search = ClosureHelper.apply(new Search(), ccl) - this.fields.put(name, search) - this.fieldsReverse.put(search, name) - }) - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InheritanceData.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InheritanceData.groovy deleted file mode 100644 index 546aec2018..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InheritanceData.groovy +++ /dev/null @@ -1,49 +0,0 @@ -package net.minecraftforge.forge.tasks - -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import groovy.transform.CompileStatic -import groovy.transform.TupleConstructor - -@CompileStatic -@TupleConstructor -final class InheritanceData implements Annotatable { - String name - int access - String superName - List interfaces = [] - Map methods = [:] - Map fields = [:] - List annotations = [] - - @TupleConstructor - static final class Method implements Annotatable { - int access - String override - List annotations = [] - } - - @TupleConstructor - static final class Field implements Annotatable { - int access - String desc - List annotations = [] - } - - @TupleConstructor - static final class Annotation { - String desc - } - - private static final Gson GSON = new Gson() - static Map parse(File file) { - try (final reader = file.newReader()) { - return GSON.fromJson(reader, new TypeToken>() {}) - } - } -} - -@CompileStatic -interface Annotatable { - List getAnnotations() -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InstallerJar.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InstallerJar.groovy deleted file mode 100644 index f0918baace..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InstallerJar.groovy +++ /dev/null @@ -1,158 +0,0 @@ -package net.minecraftforge.forge.tasks - -import org.gradle.api.file.DuplicatesStrategy -import org.gradle.api.tasks.bundling.AbstractArchiveTask -import org.gradle.api.tasks.bundling.Zip -import org.gradle.api.tasks.* -import org.gradle.api.DefaultTask -import org.gradle.api.provider.Property -import groovy.json.JsonSlurper - -abstract class InstallerJar extends Zip { - @Input @Optional abstract Property getFat() - @Input @Optional abstract Property getOffline() - - InstallerJar() { - archiveClassifier.set('installer') - archiveExtension.set('jar') // Needs to be Zip task to not override Manifest, so set extension - destinationDirectory.set(project.layout.buildDirectory.dir('libs')) - fat.convention(false) - offline.convention(false) - - def installerJson = project.tasks.installerJson - def launcherJson = project.tasks.launcherJson - def downloadInstaller = project.tasks.downloadInstaller - - dependsOn(installerJson, launcherJson, downloadInstaller, project.configurations.installer) - from(installerJson, launcherJson) - - from(project.rootProject.file('/src/main/resources/url.png')) - project.afterEvaluate { - from(project.zipTree(downloadInstaller.output)) { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - } - - if (fat.get() || offline.get()) { - def cfg = project.tasks.register(name + "Config", Configure) - cfg.get().configure { - parent = this - dependsOn(project.tasks.installerJson, project.tasks.launcherJson) - } - dependsOn(cfg) - } else { - // Things we ALWAYS bundle, this just the server shim jar, because the installer spec only says to extract the file. - // I should make it allow downloads but thats a spec break, and this is just a ~14KB jar - [ - project.tasks.serverShimJar // Server bootstrap executable jar - ].forEach { AbstractArchiveTask packed -> - def path = Util.getMavenInfoFromTask(packed).path - from(packed) { - rename { "maven/$path" } - } - } - } - } - } - - static abstract class Configure extends DefaultTask { - public Zip parent - private int count = 0; - - @TaskAction - protected void exec() { - def deps = [:] as java.util.TreeMap - - // Gather all things that need downloading - [ - project.tasks.installerJson, - project.tasks.launcherJson - ].each { task -> - def json = task.output.get().asFile.json - json.libraries.each { lib -> - if (lib.downloads?.artifact?.url !== null && !lib.downloads.artifact.url.isEmpty()) - deps.put(lib.name, lib.downloads.artifact) - } - } - - // First find things we build in this project. - [ - project.tasks.universalJar, // Forge runtime code - project.tasks.serverShimJar // Shim jar for dedicated server - ].forEach { AbstractArchiveTask packed -> - def name = Util.getMavenInfoFromTask(packed).name - def info = deps.remove(name) - if (info !== null) { - println("Adding: $packed.path $name") - parent.from(packed) { - rename { "maven/$info.path" } - } - } - } - - // Find any artifacts from the 'installer' config - // This config specifies the runtime files we intend for the interaller to have. - // And are typically what we would be developing and testing alongside Forge. - // So we may have local modified versions - def cfg = project.configurations.installer - while (cfg != null) { - //println('') - def resolved = cfg.resolvedConfiguration.resolvedArtifacts - int found = 0 - for (def dep : resolved) { - def name = Util.getMavenInfoFromDep(dep).name - def info = deps.remove(name) - if (info == null) { - //println("Skipping: $name") - continue - } - //println("-$name") - found++ - addFile(dep.file, info) - } - - if (deps.isEmpty()) { - cfg = null - continue - } - - // Prevent infinite loops if something fucky happens - if (found == 0) - throw new IllegalStateException("Failed to find any installer dependencies") - - def seen = [] as Set - cfg = project.configurations.detachedConfiguration() - cfg.transitive = false - for (def key : deps.keySet()) { - def (group, artifact, other) = key.split(':', 3) - // Only resolve unique group:artifact so we don't get version overrides - if (seen.add(group + ':' + artifact)) { - //println("+$key") - cfg.dependencies.add(project.dependencies.create(key)) - } - } - } - } - - void addFile(file, info) { - boolean pack = parent.offline.get() || info.url.isEmpty() - - // If it's a offline jar just always pack - if (!pack) { - try { - var remote = new URL("${info.url}.sha1").getText('UTF-8') - pack = info.sha1 != remote - } catch (FileNotFoundException e) { - pack = !info.url.startsWith('https://libraries.minecraft.net/') - // Oh noes its not there! - } - } - - if (pack) { - println("Adding: $file.absolutePath") - parent.from(file) { - rename { "maven/$info.path" } - } - } - } - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InstallerJson.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InstallerJson.groovy deleted file mode 100644 index 1076b7552b..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InstallerJson.groovy +++ /dev/null @@ -1,73 +0,0 @@ -package net.minecraftforge.forge.tasks - -import groovy.json.JsonBuilder -import org.gradle.api.DefaultTask -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.Property -import org.gradle.api.provider.SetProperty -import org.gradle.api.tasks.* -import org.gradle.api.tasks.bundling.AbstractArchiveTask - -import java.nio.file.Files - -abstract class InstallerJson extends DefaultTask { - @OutputFile abstract RegularFileProperty getOutput() - @InputFiles abstract ConfigurableFileCollection getInput() - @Input @Optional final Map libraries = new LinkedHashMap<>() - @Input Map json = new LinkedHashMap<>() - @InputFile abstract RegularFileProperty getIcon() - @Input abstract Property getLauncherJsonName() - @Input abstract Property getLogo() - @Input abstract Property getMirrors() - @Input abstract Property getWelcome() - - InstallerJson() { - launcherJsonName.convention('/version.json') - logo.convention('/big_logo.png') - mirrors.convention('https://files.minecraftforge.net/mirrors-2.0.json') - welcome.convention("Welcome to the ${project.name.capitalize()} installer.") - output.convention(project.layout.buildDirectory.file('libs/install_profile.json')) - - project.afterEvaluate { - [ - project.tasks.universalJar, - project.tasks.serverShimJar - ].forEach { packed -> - dependsOn(packed) - input.from packed.archiveFile - } - } - } - - @TaskAction - protected void exec() { - def libs = libraries - [ - project.tasks.universalJar, - project.tasks.serverShimJar - ].forEach { AbstractArchiveTask packed -> - def info = Util.getMavenInfoFromTask(packed) - libs.put(info.name, [ - name: info.name, - downloads: [ - artifact: [ - path: info.path, - url: "https://maven.minecraftforge.net/$info.path", - sha1: packed.archiveFile.get().asFile.sha1(), - size: packed.archiveFile.get().asFile.length() - ] - ] - ]) - } - json.libraries = libs.values().sort{a,b -> a.name.compareTo(b.name)} - json.icon = "data:image/png;base64," + new String(Base64.getEncoder().encode(Files.readAllBytes(icon.get().asFile.toPath()))) - json.json = launcherJsonName.get() - json.logo = logo.get() - if (!mirrors.get().isEmpty()) - json.mirrorList = mirrors.get() - json.welcome = welcome.get() - - Files.writeString(output.get().getAsFile().toPath(), new JsonBuilder(json).toPrettyString()) - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/JarJarMetadataOptions.java b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/JarJarMetadataOptions.java deleted file mode 100644 index 8837a29032..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/JarJarMetadataOptions.java +++ /dev/null @@ -1,415 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.minecraftforge.forge.tasks; - -import com.google.common.reflect.TypeToken; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import net.minecraftforge.jarjar.metadata.ContainedJarIdentifier; -import net.minecraftforge.jarjar.metadata.ContainedJarMetadata; -import net.minecraftforge.jarjar.metadata.ContainedVersion; -import net.minecraftforge.jarjar.metadata.Metadata; -import net.minecraftforge.jarjar.metadata.MetadataIOHandler; -import net.minecraftforge.jarjar.metadata.json.ArtifactVersionSerializer; -import net.minecraftforge.jarjar.metadata.json.ContainedJarIdentifierSerializer; -import net.minecraftforge.jarjar.metadata.json.ContainedJarMetadataSerializer; -import net.minecraftforge.jarjar.metadata.json.ContainedVersionSerializer; -import net.minecraftforge.jarjar.metadata.json.MetadataSerializer; -import net.minecraftforge.jarjar.metadata.json.VersionRangeSerializer; -import org.apache.maven.artifact.versioning.ArtifactVersion; -import org.apache.maven.artifact.versioning.DefaultArtifactVersion; -import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; -import org.apache.maven.artifact.versioning.VersionRange; -import org.codehaus.groovy.runtime.InvokerHelper; -import org.gradle.api.Action; -import org.gradle.api.DefaultTask; -import org.gradle.api.artifacts.ConfigurationContainer; -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.artifacts.ExternalModuleDependency; -import org.gradle.api.artifacts.FileCollectionDependency; -import org.gradle.api.artifacts.MinimalExternalModuleDependency; -import org.gradle.api.artifacts.ModuleDependency; -import org.gradle.api.artifacts.ModuleIdentifier; -import org.gradle.api.artifacts.ModuleVersionIdentifier; -import org.gradle.api.artifacts.ResolvedArtifact; -import org.gradle.api.file.ProjectLayout; -import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.provider.Provider; -import org.gradle.api.provider.SetProperty; -import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.OutputFile; -import org.gradle.api.tasks.TaskAction; - -import javax.inject.Inject; -import java.io.File; -import java.io.IOException; -import java.io.Serial; -import java.io.Serializable; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.zip.ZipFile; - -// TODO SUPER SUPER SUPER UGLY, CLEAN UP IN FORGEDEV 7 -@Deprecated(forRemoval = true) // Will be moved to JarJar plugin in ForgeDev 7 -public abstract class JarJarMetadataOptions extends DefaultTask { - private static final Gson GSON = new GsonBuilder() - .registerTypeAdapter(VersionRange.class, new VersionRangeSerializer()) - .registerTypeAdapter(ArtifactVersion.class, new ArtifactVersionSerializer()) - .registerTypeAdapter(DefaultArtifactVersion.class, new ArtifactVersionSerializer()) - .registerTypeAdapter(ContainedJarIdentifier.class, new ContainedJarIdentifierSerializer()) - .registerTypeAdapter(ContainedJarMetadata.class, new ContainedJarMetadataSerializer()) - .registerTypeAdapter(ContainedVersion.class, new ContainedVersionSerializer()) - .registerTypeAdapter(Metadata.class, new MetadataSerializer()) - .setPrettyPrinting() - .create(); - - protected abstract @Input SetProperty getResolvedDependencies(); - - protected abstract @OutputFile RegularFileProperty getMetadataFile(); - - protected abstract @Inject ProjectLayout getLayout(); - - // NOTE: I'm not adding a non-provider version. please just use the version catalog entries for now. - public void add(Provider dependency, Action action) { - this.getResolvedDependencies().add(dependency.map(d -> { - var ret = ResolvedDependencyInfoImpl.from(this.getProject().getConfigurations(), d); - action.execute(ret); - return ret; - })); - } - - @Inject - public JarJarMetadataOptions() { - this.getMetadataFile().convention(this.getLayout().getBuildDirectory().file(this.getName() + "/options.json")); - } - - @TaskAction - protected void exec() { - record ForgeLocaterOptions(String resource, String layer, String id, List deps, ContainedJarMetadata meta, boolean nested) { } - - var resolved = this.getResolvedDependencies().get(); - var jars = new ArrayList(resolved.size()); - for (var dependency : resolved) { - var deps = new ArrayList(); - try (var zip = new ZipFile(dependency.artifact)) { - var entry = zip.getEntry("META-INF/jarjar/metadata.json"); - if (entry != null) { - try (var stream = zip.getInputStream(entry)) { - var meta = MetadataIOHandler.fromStream(stream).orElse(null); - if (meta == null) - throw new IllegalStateException("Corrupt metadata.json in " + dependency.artifact.getAbsolutePath()); - for (var dep : meta.jars()) - deps.add(new ContainedJarMetadata(dep.identifier(), dep.version(), "", dep.isObfuscated())); - } - } - } catch (IOException e) { - throw new RuntimeException(e); - } - jars.add(new ForgeLocaterOptions( - dependency.resource, - dependency.layer, - dependency.identifier, - deps, - new ContainedJarMetadata( - new ContainedJarIdentifier(validateGroup(dependency), dependency.module.getName()), - new ContainedVersion(null, parseVersion(dependency)), - Objects.requireNonNull(dependency.path, "Dependency path is unspecified: " + dependency.asString), - false - ), - dependency.nested - )); - } - - try { - record Meta(List options){} - - Files.writeString( - this.getMetadataFile().getAsFile().get().toPath(), - GSON.toJson(new Meta(jars), Meta.class) - ); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private String validateGroup(ResolvedDependencyInfoImpl dependency) { - try { - return Objects.requireNonNull(dependency.module.getGroup()); - } catch (NullPointerException e) { - throw new IllegalArgumentException("Module dependency has no group: " + dependency.asString, e); - } - } - - private ArtifactVersion parseVersion(ResolvedDependencyInfoImpl resolved) { - try { - return VersionRange.createFromVersionSpec(Objects.requireNonNull(resolved.version)).getRecommendedVersion(); - } catch (InvalidVersionSpecificationException e) { - throw new IllegalArgumentException("Version is invalid for: " + resolved.asString, e); - } catch (NullPointerException e) { - throw new IllegalArgumentException("Version is unspecified for: " + resolved.asString, e); - } - } - - private VersionRange parseVersionRange(ResolvedDependencyInfoImpl dependency) { - if (dependency.hasManuallySpecifiedRange) { - try { - return VersionRange.createFromVersionSpec(dependency.versionRange); - } catch (InvalidVersionSpecificationException e) { - throw new IllegalArgumentException("Version is invalid for: " + dependency.asString, e); - } - } else { - try { - return VersionRange.createFromVersionSpec("[%s,)".formatted(Objects.requireNonNull(dependency.versionRange))); - } catch (InvalidVersionSpecificationException e) { - throw new IllegalArgumentException("Version range is invalid for: " + dependency.asString, e); - } catch (NullPointerException e) { - throw new IllegalArgumentException("Version is unspecified for: " + dependency.asString, e); - } - } - } - - public interface ResolvedDependencyInfo { - void containedJarMetadata(Action action); - - void setResource(String resource); - - void setLayer(String layer); - - void setId(String identifier); - - void setNested(boolean nested); - - interface ContainedJarMetadataInfo { - void setGroup(String group); - - void setName(String name); - - void setPath(String version); - } - } - - static final class ResolvedDependencyInfoImpl implements ResolvedDependencyInfo, ResolvedDependencyInfo.ContainedJarMetadataInfo, Serializable { - private static final @Serial long serialVersionUID = -7577318115877822993L; - - final MinimalModuleVersionIdentifier module; - final String version; - String versionRange; - boolean hasManuallySpecifiedRange; - boolean nested; - final File artifact; - String path; - String resource; - String layer; - String identifier; - final String asString; - - public ResolvedDependencyInfoImpl(MinimalModuleVersionIdentifier module, String version, File artifact, String asString) { - this.module = module; - this.version = version; - this.artifact = artifact; - this.asString = asString; - } - - @Override - public void containedJarMetadata(Action action) { - action.execute(this); - } - - @Override - public void setGroup(String group) { - this.module.group = group; - } - - @Override - public void setName(String name) { - this.module.name = name; - } - - @Override - public void setPath(String path) { - this.path = path; - } - - @Override - public void setResource(String resource) { - this.resource = resource; - } - - @Override - public void setLayer(String layer) { - this.layer = layer; - } - - @Override - public void setId(String identifier) { - this.identifier = identifier; - } - - @Override - public void setNested(boolean nested) { - this.nested = nested; - } - - static Set getFiles(Set resolvedDependencies) { - var ret = new HashSet(resolvedDependencies.size()); - for (var dependency : resolvedDependencies) { - ret.add(dependency.artifact); - } - return ret; - } - - static ResolvedDependencyInfoImpl from(ConfigurationContainer configurations, Dependency dependency) { - var group = dependency.getGroup(); - var name = dependency.getName(); - var version = dependency.getVersion(); - - if (dependency instanceof FileCollectionDependency filesDependency) { - File artifact; - try { - artifact = filesDependency.getFiles().getSingleFile(); - } catch (IllegalStateException e) { - // TODO fileCollectionDependencyIsNotSingleFile - throw e; - } - - return new ResolvedDependencyInfoImpl( - new MinimalModuleVersionIdentifier(group, name, version), - version, - artifact, - filesDependency.toString() - ); - } else if (dependency instanceof ModuleDependency moduleDependency) { - moduleDependency = moduleDependency.copy(); - if (moduleDependency instanceof ExternalModuleDependency externalModuleDependency) { - externalModuleDependency.version(v -> v.strictly(version.toString())); - } - - var detachedConfiguration = configurations.detachedConfiguration(moduleDependency); - detachedConfiguration.setTransitive(false); - - ResolvedDependencyInfoImpl ret = null; - for (var artifact : detachedConfiguration.getResolvedConfiguration().getFirstLevelModuleDependencies().iterator().next().getModuleArtifacts()) { - var fileName = getFileName(artifact); - if (!fileName.endsWith(".jar")) - continue; - - if (ret != null) - throw new IllegalArgumentException("Module dependency has too many Jar artifacts: " + moduleDependency); - - ret = new ResolvedDependencyInfoImpl( - new MinimalModuleVersionIdentifier(group, name, artifact.getModuleVersion().getId().getVersion()), - version, - artifact.getFile(), - moduleDependency.toString() - ); - } - if (ret == null) - throw new IllegalArgumentException("Module dependency has no Jar artifacts: " + moduleDependency); - - return ret; - } else { - throw new IllegalArgumentException("Unsupported dependency type: " + dependency.getClass().getName() + " -- " + dependency); - } - } - - private static String getFileName(ResolvedArtifact artifact) { - try { - return InvokerHelper.getProperty(artifact.getId(), "fileName").toString(); - } catch (Throwable e) { - // NOTE: Why not just use this to begin with? - // ComponentArtifactIdentifier can have a getFileName() method, which doesn't necessarily resolve the file itself. - // This allows us to get the name of the file to be used without asking Gradle to download the file. - // So, if a file is not a JAR file, we can check the name without actually downloading it. - return artifact.getFile().getName(); - } - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - var that = (ResolvedDependencyInfoImpl) obj; - return Objects.equals(this.module, that.module) && - Objects.equals(this.version, that.version) && - Objects.equals(this.versionRange, that.versionRange) && - Objects.equals(this.artifact, that.artifact); - } - - @Override - public int hashCode() { - return Objects.hash(module, version, versionRange, artifact); - } - - @Override - public String toString() { - return "ResolvedDependencyInfo[" + - "module=" + module + ", " + - "fixedVersion=" + version + ", " + - "versionRange=" + versionRange + ", " + - "artifact=" + artifact + ']'; - } - - static final class MinimalModuleVersionIdentifier implements ModuleIdentifier, ModuleVersionIdentifier { - private static final @Serial long serialVersionUID = -955346236759069739L; - - private String group; - private String name; - private final String version; - - @Inject - public MinimalModuleVersionIdentifier(String group, String name, String version) { - this.group = group; - this.name = name; - this.version = version; - } - - @Override - public ModuleIdentifier getModule() { - return this; - } - - @Override - public String getGroup() { - return this.group; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public String getVersion() { - return this.version; - } - - @Override - public boolean equals(Object obj) { - return this == obj || obj instanceof MinimalModuleVersionIdentifier o - && Objects.equals(this.group, o.group) - && Objects.equals(this.name, o.name) - && Objects.equals(this.version, o.version); - } - - @Override - public int hashCode() { - return Objects.hash(group, name, version); - } - - @Override - public String toString() { - return "MinimalModuleVersionIdentifier[" + - "group=" + group + ", " + - "name=" + name + ", " + - "version=" + version + ']'; - } - } - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/LauncherJson.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/LauncherJson.groovy deleted file mode 100644 index e9cf3a18d2..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/LauncherJson.groovy +++ /dev/null @@ -1,94 +0,0 @@ -package net.minecraftforge.forge.tasks - -import groovy.json.JsonBuilder -import org.gradle.api.DefaultTask -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.SetProperty -import org.gradle.api.tasks.* -import org.gradle.api.tasks.bundling.AbstractArchiveTask - -import java.nio.file.Files - -import static net.minecraftforge.forge.tasks.Util.getArtifacts -import static net.minecraftforge.forge.tasks.Util.iso8601Now - -abstract class LauncherJson extends DefaultTask { - @OutputFile abstract RegularFileProperty getOutput() - @InputFiles abstract ConfigurableFileCollection getInput() - @Input Map json = new LinkedHashMap<>() - - LauncherJson() { - output.convention(project.layout.buildDirectory.file('libs/version.json')) - - dependsOn(project.tasks.universalJar) - input.from(project.tasks.universalJar.archiveFile) - input.from(project.configurations.installer) - configure { - def mc = project.rootProject.ext.MC_VERSION - def forge = project.rootProject.ext.FORGE_VERSION - def timestamp = iso8601Now() - json.putAll([ - _comment: [ - "Please do not automate the download and installation of Forge.", - "Our efforts are supported by ads from the download page.", - "If you MUST automate this, please consider supporting the project through https://www.patreon.com/LexManos/" - ], - id: "$mc-$project.name-$forge", - time: timestamp, - releaseTime: timestamp, - inheritsFrom: mc, - type: 'release', - logging: [:], - mainClass: '', - libraries: [] - ] as LinkedHashMap) - - [ - project.tasks.universalJar - ].forEach { packed -> - dependsOn(packed) - input.from packed.archiveFile - } - - def patched = project.tasks.applyClientBinPatches - dependsOn(patched) - input.from patched.output - } - } - - @TaskAction - protected void exec() { - var packed = (AbstractArchiveTask) project.tasks.universalJar - def info = Util.getMavenInfoFromTask(packed) - json.libraries.add([ - name: info.name, - downloads: [ - artifact: [ - path: info.path, - url: "https://maven.minecraftforge.net/$info.path", - sha1: packed.archiveFile.get().asFile.sha1(), - size: packed.archiveFile.get().asFile.length() - ] - ] - ]) - - var classifier = 'client' - var genned = project.tasks.applyClientBinPatches - info = Util.getMavenInfoFromTask(genned, classifier) - json.libraries.add([ - name: info.name, - downloads: [ - artifact: [ - path: info.path, - url: "", - sha1: genned.output.get().asFile.sha1(), - size: genned.output.get().asFile.length() - ] - ] - ]) - - json.libraries.addAll(getArtifacts(project.configurations.installer).values()) - Files.writeString(output.get().asFile.toPath(), new JsonBuilder(json).toPrettyString()) - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/MergeJars.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/MergeJars.groovy deleted file mode 100644 index 0f95d42f36..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/MergeJars.groovy +++ /dev/null @@ -1,46 +0,0 @@ -package net.minecraftforge.forge.tasks - -import groovy.transform.CompileStatic -import org.apache.commons.io.IOUtils -import org.gradle.api.DefaultTask -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction - -import java.util.zip.ZipEntry -import java.util.zip.ZipInputStream -import java.util.zip.ZipOutputStream - -@CompileStatic -abstract class MergeJars extends DefaultTask { - MergeJars() { - output.convention(project.layout.buildDirectory.dir(name).map { it.file('output.jar') }) - } - - @TaskAction - void run() { - def jars = inputJars.files - - try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(output.get().asFile))) { - for (def jar : jars) { - try (ZipInputStream zin = new ZipInputStream(new FileInputStream(jar))) { - ZipEntry entry - while ((entry = zin.getNextEntry()) !== null) { - ZipEntry _new = new ZipEntry(entry.getName()) - _new.setTime(0) //SHOULD be the same time as the main entry, but NOOOO _new.setTime(entry.getTime()) throws DateTimeException, so you get 0, screw you! - zout.putNextEntry(_new) - IOUtils.copy(zin, zout) - } - } - } - } - } - - @InputFiles - abstract ConfigurableFileCollection getInputJars() - - @OutputFile - abstract RegularFileProperty getOutput() -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ObjectTarget.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ObjectTarget.groovy deleted file mode 100644 index 00f64e18fa..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ObjectTarget.groovy +++ /dev/null @@ -1,32 +0,0 @@ -package net.minecraftforge.forge.tasks - -import groovy.transform.EqualsAndHashCode - -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Nested -import org.gradle.api.tasks.Optional - -@EqualsAndHashCode -public class ObjectTarget implements Comparable { - @Input - String owner - - @Input - String name - - @Input - @Optional - String desc - - @Override - String toString() { - if (desc == null) - return owner + '.' + name - return owner + '.' + name + desc - } - - @Override - int compareTo(ObjectTarget o) { - return toString() <=> o.toString() - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/SetupCheckJarCompatibility.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/SetupCheckJarCompatibility.groovy deleted file mode 100644 index a583d6b5e6..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/SetupCheckJarCompatibility.groovy +++ /dev/null @@ -1,47 +0,0 @@ -package net.minecraftforge.forge.tasks - -import org.gradle.api.DefaultTask -import org.gradle.api.artifacts.Dependency -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Optional -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction - -import java.nio.file.Files -import java.nio.file.StandardCopyOption - -abstract class SetupCheckJarCompatibility extends DefaultTask { - SetupCheckJarCompatibility() { - group = 'jar compatibility' - onlyIf { - inputVersion.getOrNull() !== null - } - outputs.upToDateWhen { false } // Never up to date, because this setup task should always run - - baseBinPatchesOutput.convention(project.layout.buildDirectory.dir(name).map { it.file('joined.lzma') }) - } - - @TaskAction - void run() { - def inputVersion = inputVersion.get() - - def baseForgeUserdev = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-userdev.jar") }.get().asFile - project.rootProject.extensions.download.run { - src "https://maven.minecraftforge.net/net/minecraftforge/forge/${inputVersion}/forge-${inputVersion}-userdev.jar" - dest baseForgeUserdev - } - - def joinedLzma = project.zipTree(baseForgeUserdev).matching { it.include('joined.lzma') }.singleFile - - Files.copy(joinedLzma.toPath(), baseBinPatchesOutput.get().asFile.toPath(), StandardCopyOption.REPLACE_EXISTING) - } - - @Input - @Optional - abstract Property getInputVersion() - - @OutputFile - abstract RegularFileProperty getBaseBinPatchesOutput() -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/TeamcityRequests.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/TeamcityRequests.groovy deleted file mode 100644 index bccebbaffa..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/TeamcityRequests.groovy +++ /dev/null @@ -1,74 +0,0 @@ -package net.minecraftforge.forge.tasks - -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import groovy.transform.CompileStatic -import org.eclipse.jgit.api.Git - -import javax.annotation.Nullable -import java.util.stream.Collectors - -@CompileStatic -class TeamcityRequests { - public static final Gson GSON = new Gson() - - @Nullable - static T jsonRequest(TypeToken clazz, String url) throws Exception { - final HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection() - conn.setRequestProperty('Accept', 'application/json') - conn.setReadTimeout(5 * 1000) - conn.setConnectTimeout(5 * 1000) - conn.connect() - - if (conn.responseCode !== 200) { - return null - } - - try (final InputStream is = conn.getInputStream()) { - GSON.fromJson(new InputStreamReader(is), clazz.getType()) - } - } - - @Nullable - static Map buildsByCommit() throws IOException { - final Map builds = [:] - jsonRequest(new TypeToken() {}, 'https://teamcity.minecraftforge.net/guestAuth/app/rest/builds?fields=build:(revisions,number)&locator=buildType:(id:MinecraftForge_MinecraftForge_MinecraftForge_MinecraftForge__Build),defaultFilter:false,count:300,status:SUCCESS') - ?.build?.forEach { - it.revisions.revision.each { rev -> builds[rev.version] = it } - } - return builds - } - - @Nullable - static String attemptFindBase(File gitPath) { - try (final git = Git.open(gitPath)) { - final Map buildByCommit = buildsByCommit() - for (final commit in git.log().setMaxCount(100).call()) { - // Find the first commit which was built on CI - final build = buildByCommit[commit.id.name] - if (build) { - return build.number - } - } - } catch (Exception ignored) {} - return null - } - - static final class Builds { - public List build - } - - static final class Build { - public String number - public Revisions revisions - } - - static final class Revisions { - public int count - public List revision - } - - static final class Revision { - public String version - } -} \ No newline at end of file diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/Util.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/Util.groovy deleted file mode 100644 index c15e595952..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/Util.groovy +++ /dev/null @@ -1,230 +0,0 @@ -package net.minecraftforge.forge.tasks - -import groovy.json.JsonBuilder -import groovy.json.JsonSlurper -import groovy.transform.CompileStatic -import groovy.transform.stc.ClosureParams -import groovy.transform.stc.SimpleType -import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.ResolvedArtifact -import org.gradle.api.tasks.bundling.AbstractArchiveTask -import org.objectweb.asm.ClassReader -import org.objectweb.asm.Opcodes -import org.objectweb.asm.tree.ClassNode - -import java.net.http.HttpClient -import java.net.http.HttpRequest -import java.net.http.HttpResponse -import java.security.MessageDigest -import java.text.SimpleDateFormat -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.Semaphore -import java.util.zip.ZipEntry -import java.util.zip.ZipInputStream - -final class Util { - public static final int ASM_LEVEL = Opcodes.ASM9 - private static final HttpClient HTTP = HttpClient.newBuilder().build() - - static void init() { - File.metaClass.sha1 = { -> - MessageDigest md = MessageDigest.getInstance('SHA-1') - delegate.eachByte(4096) { byte[] bytes, int size -> - md.update(bytes, 0, size) - } - return md.digest().collect(this.&toHex).join('') - } - File.metaClass.getSha1 = { !delegate.exists() ? null : delegate.sha1() } - File.metaClass.sha256 = { -> - MessageDigest md = MessageDigest.getInstance('SHA-256') - delegate.eachByte(4096) { byte[] bytes, int size -> - md.update(bytes, 0, size) - } - return md.digest().collect(this.&toHex).join('') - } - File.metaClass.getSha256 = { !delegate.exists() ? null : delegate.sha256() } - - File.metaClass.json = { -> new JsonSlurper().parseText(delegate.text) } - File.metaClass.getJson = { return delegate.exists() ? new JsonSlurper().parse(delegate) : [:] } - File.metaClass.setJson = { json -> delegate.text = new JsonBuilder(json).toPrettyString() } - - Date.metaClass.iso8601 = { -> - var format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") - var result = format.format(delegate) - return result[0..21] + ':' + result[22..-1] - } - - String.metaClass.rsplit = { String del, int limit = -1 -> - var lst = new ArrayList() - int x = 0 - int idx - String tmp = delegate - while ((idx = tmp.lastIndexOf(del)) != -1 && (limit === -1 || x++ < limit)) { - lst.add(0, tmp.substring(idx + del.length(), tmp.length())) - tmp = tmp.substring(0, idx) - } - lst.add(0, tmp) - return lst - } - } - - static String[] getClasspath(Project project, Map libs, String artifact) { - def ret = [] - artifactTree(project, artifact).each { key, lib -> - libs[lib.name] = lib - if (lib.name != artifact) - ret.add(lib.name) - } - return ret - } - - @CompileStatic - static Map getArtifacts(Configuration config) { - var ret = [:] - var semaphore = new Semaphore(1, true) - config.resolvedConfiguration.resolvedArtifacts.parallelStream().forEachOrdered(dep -> { - var info = getMavenInfoFromDep(dep) - var domain = 'libraries.minecraft.net' - var url = "https://$domain/$info.path" - if (!checkExists(url)) - url.values[0] = 'maven.minecraftforge.net' - - var sha1 = sha1(dep.file) - - semaphore.acquire() - ret[info.key] = [ - name: info.name, - downloads: [ - artifact: [ - path: info.path, - url: url.toString(), - sha1: sha1, - size: dep.file.length() - ] - ] - ] - semaphore.release() - }) - return ret - } - - @CompileStatic - static Map getMavenInfoFromDep(ResolvedArtifact dep) { - return getMavenInfoFromMap([ - group: dep.moduleVersion.id.group, - name: dep.moduleVersion.id.name, - version: dep.moduleVersion.id.version, - classifier: dep.classifier, - extension: dep.extension - ]) - } - - @CompileStatic - static Map getMavenInfoFromTask(AbstractArchiveTask task) { - return getMavenInfoFromMap([ - group: task.project.group.toString(), - name: task.project.name, - version: task.project.version.toString(), - classifier: task.archiveClassifier.get(), - extension: task.archiveExtension.get() - ]) - } - - @CompileStatic - static Map getMavenInfoFromTask(Task task, String classifier) { - return getMavenInfoFromMap([ - group: task.project.group.toString(), - name: task.project.name, - version: task.project.version.toString(), - classifier: classifier, - extension: 'jar' - ]) - } - - @CompileStatic - private static Map getMavenInfoFromMap(Map art) { - var key = "$art.group:$art.name" - var name = "$art.group:$art.name:$art.version" - var path = "${art.group.replace('.', '/')}/$art.name/$art.version/$art.name-$art.version" - if (art.classifier !== null) { - name += ":$art.classifier" - path += "-$art.classifier" - } - if ('jar' != art.extension) { - name += "@$art.extension" - path += ".$art.extension" - } else { - path += ".jar" - } - return [ - key: key.toString(), - name: name.toString(), - path: path.toString(), - art: art - ] - } - - static String iso8601Now() { new Date().iso8601() } - - @CompileStatic - static String sha1(File file) { - MessageDigest md = MessageDigest.getInstance('SHA-1') - file.eachByte(4096) { byte[] bytes, int size -> - md.update(bytes, 0, size) - } - return md.digest().collect(this.&toHex).join('') - } - - @CompileStatic - private static String toHex(byte bite) { - return String.format('%02x', bite) - } - - private static Map artifactTree(Project project, String artifact, boolean transitive = true) { - if (!project.ext.has('tree_resolver')) - project.ext.tree_resolver = 1 - def cfg = project.configurations.create('tree_resolver_' + project.ext.tree_resolver++) - cfg.transitive = transitive - def dep = project.dependencies.create(artifact) - cfg.dependencies.add(dep) - def files = cfg.resolve() - return getArtifacts(cfg) - } - - @CompileStatic - static boolean checkExists(String url) { - try { - return HTTP.send(HttpRequest.newBuilder(new URI(url)) - .method('HEAD', HttpRequest.BodyPublishers.noBody()).build(), HttpResponse.BodyHandlers.discarding() - ).statusCode() === 200 - } catch (Exception e) { - if (e.toString().contains('unable to find valid certification path to requested target')) - throw new RuntimeException("Failed to connect to $url: Missing certificate root authority, try updating Java") - throw e - } - } - - static String getLatestForgeVersion(String mcVersion) { - final json = new JsonSlurper().parseText(new URL('https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json').getText('UTF-8')) - final ver = json.promos["$mcVersion-latest"] - ver === null ? null : (mcVersion + '-' + ver) - } - - @CompileStatic - static void processClassNodes(File file, @ClosureParams(value = SimpleType, options = 'org.objectweb.asm.tree.ClassNode') Closure process) { - file.withInputStream { i -> - new ZipInputStream(i).withCloseable { zin -> - ZipEntry zein - while ((zein = zin.nextEntry) !== null) { - if (zein.name.endsWith('.class')) { - var node = new ClassNode(ASM_LEVEL) - new ClassReader(zin).accept(node, 0) - process(node) - } - } - } - } - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ValidateDeprecations.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ValidateDeprecations.groovy deleted file mode 100644 index 14018ddd8a..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ValidateDeprecations.groovy +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.minecraftforge.forge.tasks - -import groovy.transform.CompileStatic -import net.minecraftforge.srgutils.MinecraftVersion -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.InputFile -import org.gradle.api.tasks.TaskAction -import org.objectweb.asm.tree.AnnotationNode -import org.objectweb.asm.tree.ClassNode - -abstract class ValidateDeprecations extends DefaultTask { - @InputFile - abstract RegularFileProperty getInput() - - @Input - abstract Property getMcVersion() - - ValidateDeprecations() { - this.onlyIf { !System.env.TEAMCITY_VERSION } - } - - @TaskAction - protected void exec() { - var mcVer = MinecraftVersion.from(mcVersion.get()) - List errors = [] - - Util.processClassNodes(input.get().asFile) { - processNode(mcVer, errors, it) - } - - if (!errors.isEmpty()) { - errors.forEach { - this.logger.error("Deprecated ${it[0]} is marked for removal in ${it[1]} but is not yet removed") - } - throw new IllegalStateException("Found deprecated members marked for removal but not yet removed in ${mcVer}; see log for details") - } - } - - protected processNode(MinecraftVersion mcVer, List errors, ClassNode node) { - node.visibleAnnotations?.each { annotation -> - ValidateDeprecations.processAnnotations(annotation, mcVer, errors) { - "class ${node.name}" - } - } - node.fields?.each { field -> - field.visibleAnnotations?.each { annotation -> - ValidateDeprecations.processAnnotations(annotation, mcVer, errors) { - "field ${node.name}#${field.name}" - } - } - } - node.methods?.each { method -> - method.visibleAnnotations?.each { annotation -> - ValidateDeprecations.processAnnotations(annotation, mcVer, errors) { - "method ${node.name}#${method.name}${method.desc}" - } - } - } - } - - private static void processAnnotations(AnnotationNode annotation, MinecraftVersion mcVer, List errors, Closure context) { - def values = annotation.values - if (values === null) - return - int forRemoval = values.indexOf('forRemoval') - int since = values.indexOf('since') - if (annotation.desc == 'Ljava/lang/Deprecated;' && forRemoval !== -1 && since !== -1 && values.size() >= 4 && values[forRemoval + 1] === true) { - def oldVersion = MinecraftVersion.from(values[since + 1]) - int[] split = ValidateDeprecations.splitDots(oldVersion.toString()) - if (split.length < 2) - return - def removeVersion = MinecraftVersion.from("${split[0]}.${split[1] + 1}") - if (removeVersion <= mcVer) - errors.add([context(), removeVersion]) - } - } - - @CompileStatic - private static int[] splitDots(String version) { - String[] pts = version.split('\\.') - int[] values = new int[pts.length] - for (int x = 0; x < pts.length; x++) - values[x] = Integer.parseInt(pts[x]) - return values - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckATs.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckATs.groovy deleted file mode 100644 index 7e7bf20da2..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckATs.groovy +++ /dev/null @@ -1,282 +0,0 @@ -package net.minecraftforge.forge.tasks.checks - -import groovy.transform.CompileStatic -import groovy.transform.TupleConstructor -import net.minecraftforge.forge.tasks.InheritanceData -import net.minecraftforge.srgutils.IMappingFile -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.RegularFile -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Optional -import org.objectweb.asm.Opcodes - -@CompileStatic -abstract class CheckATs extends CheckTask { - - @InputFile abstract RegularFileProperty getInheritance() - @InputFiles abstract ConfigurableFileCollection getAts() - - @Override - void check(Reporter reporter, boolean fix) { - final inheritance = InheritanceData.parse(this.inheritance.get().asFile) - - ats.each { - final lines = process(it, reporter, inheritance) - if (fix) { - it.text = joinBack(lines, inheritance).join('\n') - } - } - } - - private static TreeMap process(File file, Reporter reporter, Map inheritance) { - final TreeMap lines = ATParser.parse(file.readLines(), reporter) - final Map constructorGroups = [:] - - final itr = lines.entrySet().iterator() - final toRemove = [] - while (itr.hasNext()) { - final next = itr.next() - String key = next.key - final entry = next.value - if (entry === null) continue - - final binaryName = entry.cls.replaceAll('\\.', '/') - - // Process Groups, this will remove any entries outside the group that is covered by the group - if (entry.group) { - final jcls = inheritance[binaryName] - if (jcls === null) { - itr.remove() - reporter.report("Invalid group: $key") - } else if ('*' == entry.desc) { - if (!jcls.fields) { - itr.remove() - reporter.report("Invalid group, class has no fields: $key") - } else { - jcls.fields.each { field, value -> - final fkey = entry.cls + ' ' + field - if (accessLevel(value.access) < accessStr(entry.modifier)) { - if (lines.containsKey(fkey)) { - toRemove.add(fkey) - } else if (!entry.existing.contains(fkey)) { - reporter.report("Missing group entry: $fkey") - } - entry.children.add(fkey) - } else if (lines.containsKey(fkey)) { - toRemove.add(fkey) - reporter.report("Found invalid group entry: $fkey") - } - } - entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) } - } - } else if ('*()' == entry.desc) { - if (!jcls.methods) { - itr.remove() - reporter.report("Invalid group, class has no methods: $key") - } else { - jcls.methods.each { mtd, value -> - if (mtd.startsWith('') || mtd.startsWith('lambda$')) return - key = entry.cls + ' ' + mtd.replace(' ', '') - if (accessLevel(value.access) < accessStr(entry.modifier)) { - if (lines.containsKey(key)) { - toRemove.add(key) - } else if (!entry.existing.contains(key)) { - reporter.report("Missing group entry: $key") - } - entry.children.add(key) - } else if (lines.containsKey(key)) { - toRemove.add(key) - reporter.report("Found invalid group entry: $key") - } - } - entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) } - } - } else if ('' == entry.desc) { //Make all public non-abstract subclasses - constructorGroups.put(binaryName, entry) - } - } - - // Process normal lines, remove invalid and remove narrowing - else { - def jcls = inheritance.get(binaryName) - if (jcls === null) { - itr.remove() - reporter.report("Invalid: $key") - } else if (entry.desc == '') { - if (accessLevel(jcls.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) { - itr.remove() - reporter.report("Invalid Narrowing: $key") - } - } else if (!entry.desc.contains('(')) { - if (!jcls.fields || !jcls.fields.containsKey(entry.desc)) { - itr.remove() - reporter.report("Invalid: $key") - } else { - final value = jcls.fields[entry.desc] - if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) { - itr.remove() - reporter.report("Invalid Narrowing: $key - ${entry.comment}") - } - } - } else { - final jdesc = entry.desc.replace('(', ' (') - if (!jcls.methods || !jcls.methods.containsKey(jdesc)) { - itr.remove() - reporter.report("Invalid: $key") - } else { - final value = jcls.methods[jdesc] - if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) { - itr.remove() - reporter.report("Invalid Narrowing: $key") - } - } - } - } - } - - inheritance.each { tcls, value -> - if (!value.methods || ((value.access & Opcodes.ACC_ABSTRACT) !== 0)) return - String parent = tcls - while (parent !== null) { - constructorGroups[parent]?.tap { entry -> - value.methods.each { mtd, v -> - if (mtd.startsWith('')) { - final child = tcls.replaceAll('/', '\\.') + ' ' + mtd.replace(' ', '') - if (accessLevel(v.access) < 3) { - if (lines.containsKey(child)) { - toRemove.add(child) - } else if (child !in entry.existing) { - reporter.report("Missing group entry: $child") - } - entry.children.add(child) - } else if (lines.containsKey(child)) { - toRemove.add(child) - reporter.report("Found invalid group entry: $child") - } - } - } - } - parent = inheritance[parent]?.superName - } - } - constructorGroups.values().each { entry -> entry.existing.findAll { it !in entry.children }.each{ reporter.report("Found invalid group entry: $it") } } - - toRemove.each(lines.&remove) - - return lines - } - - private static List joinBack(TreeMap lines, Map inheritance) { - final data = [] as List - lines.each { key, value -> - if (!value.group) { - def comment = null //value.comment - data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : '')) - } else { - data.add(('#group ' + value.modifier + ' ' + key + ' ' + (value.comment ?: '')).trim()) - value.children.each { - final line = value.modifier + ' ' + it - final entry = ATParser.parseEntry(line) - final comment = null //entry.comment - data.add(line + (comment ? ' ' + comment : '')) - } - data.add('#endgroup') - } - } - return data - } - - static int accessStr(String access) { - if (access.endsWith('-f') || access.endsWith('+f')) return 4 - switch (access.toLowerCase()) { - case 'public': return 3 - case 'protected': return 2 - case 'default': return 1 - case 'private': return 0 - default: return -1 - } - } - - static int accessLevel(int access) { - if ((access & Opcodes.ACC_PUBLIC) !== 0) return 3 - if ((access & Opcodes.ACC_PROTECTED) !== 0) return 2 - if ((access & Opcodes.ACC_PRIVATE) !== 0) return 0 - return 1 - } -} - -@CompileStatic -class ATParser { - static TreeMap parse(List lines, CheckTask.Reporter reporter) { - TreeMap outLines = new TreeMap<>() - Entry group = null - for (final line : lines) { - if (line.isEmpty()) continue - if (line.startsWith('#group ')) { - final entry = parseEntry(line.substring(7)) - - if (entry.desc != '*' && entry.desc != '*()' && entry.desc != '') { - reporter.report("Invalid group: $line", false) - } - - entry.group = true - entry.children = [] - entry.existing = [] - - group = entry - - if (outLines.containsKey(entry.key)) { - reporter.report("Duplicate group: $line", false) - } - - outLines[entry.key] = group - } else if (group !== null) { - if (line.startsWith('#endgroup')) { - group = null - } else { - final key = parseEntry(line).key - group.existing.add(key) - } - } else if (line.startsWith('#endgroup')) { - reporter.report("Invalid group ending: $line", false) - } else if (line.startsWith('#')) { - //Nom - } else { - final entry = parseEntry(line) - if (outLines.containsKey(entry.key)) { - reporter.report("Found duplicate: $line") - continue - } - outLines[entry.key] = entry - } - } - return outLines - } - - static Entry parseEntry(String line) { - final idx = line.indexOf('#') - final String comment = idx === -1 ? null : line.substring(idx) - if (idx !== -1) line = line.substring(0, idx - 1) - final data = (line.trim() + ' ').split(' ', -1) - data[1] = data[1].replaceAll('/', '.') // Convert to Source names, internal names are fine by spec, but not supported by old AST based AT implementations. - new Entry(data[0], data[1], data[2], comment) - } - - @TupleConstructor - static final class Entry { - String modifier, cls, desc, comment - - Set existing - TreeSet children - boolean group = false - - @Lazy - String key = {cls + (desc.isEmpty() ? '' : ' ' + desc)}() - - Object getAt(String key) { - return getProperty(key) - } - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckExcs.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckExcs.groovy deleted file mode 100644 index 8925657539..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckExcs.groovy +++ /dev/null @@ -1,95 +0,0 @@ -package net.minecraftforge.forge.tasks.checks - -import net.minecraftforge.forge.tasks.Util -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.InputFiles -import org.objectweb.asm.ClassReader -import org.objectweb.asm.ClassVisitor -import org.objectweb.asm.MethodVisitor -import org.objectweb.asm.Type - -import java.util.zip.ZipEntry -import java.util.zip.ZipInputStream - -abstract class CheckExcs extends CheckTask { - - @InputFile abstract RegularFileProperty getBinary() - @InputFiles abstract ConfigurableFileCollection getExcs() - - @Override - void check(Reporter reporter, boolean fix) { - final Set known = [] - collectKnown(known) - - excs.each { f -> - final lines = [] - f.eachLine { line -> - int idx = line.indexOf('#') - if (idx === 0 || line.isEmpty()) { - return - } - - if (idx !== -1) line = line.substring(0, idx - 1) - - if (!line.contains('=')) { - reporter.report("Invalid: $line") - return - } - - def (String key, String value) = line.split('=', 2) - if (!known.contains(key)) { - reporter.report("Unknown: $line") - return - } - - String desc = key.split('\\.', 2)[1] - if (!desc.contains('(')) { - reporter.report("Invalid: $line") - return - } - desc = '(' + desc.split('\\(', 2)[1] - - def (exceptions, String args) = value.contains('|') ? value.split('|', 2) : [value, ''] - - if (args.split(',').length !== Type.getArgumentTypes(desc).length) { - reporter.report("Invalid: $line") - return - } - lines.add(line) - - return - } - - if (fix) f.text = lines.sort().join('\n') - } - } - - private void collectKnown(Collection known) { - binary.get().asFile.withInputStream { i -> - new ZipInputStream(i).withCloseable { zin -> - final visitor = new ClassVisitor(Util.ASM_LEVEL) { - private String cls - @Override - void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - this.cls = name - } - - @Override - MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { - known.add(this.cls + '.' + name + descriptor) - super.visitMethod(access, name, descriptor, signature, exceptions) - } - } - ZipEntry zein - while ((zein = zin.nextEntry) !== null) { - if (zein.name.endsWith('.class')) { - ClassReader reader = new ClassReader(zin) - reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) - } - } - } - } - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckMode.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckMode.groovy deleted file mode 100644 index 8580aa64e4..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckMode.groovy +++ /dev/null @@ -1,11 +0,0 @@ -package net.minecraftforge.forge.tasks.checks - -import groovy.transform.CompileStatic - -@CompileStatic -enum CheckMode { - CHECK, - FIX - - CheckMode() {} -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckPatches.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckPatches.groovy deleted file mode 100644 index 89192dff55..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckPatches.groovy +++ /dev/null @@ -1,182 +0,0 @@ -package net.minecraftforge.forge.tasks.checks - -import groovy.transform.CompileStatic -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.provider.ListProperty -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputDirectory -import org.gradle.api.tasks.Optional - -import java.nio.file.Files -import java.nio.file.Path -import java.util.regex.Pattern - -@CompileStatic -abstract class CheckPatches extends CheckTask { - - private static final Pattern HUNK_START_PATTERN = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$') - private static final Pattern WHITESPACE_PATTERN = Pattern.compile('^[+\\-]\\s*$') - private static final Pattern IMPORT_PATTERN = Pattern.compile('^[+\\-]\\s*import.*') - private static final Pattern FIELD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$') - private static final Pattern METHOD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^(]*)[(]([^)]*)?[)]\\s*[{]\\s*$') - private static final Pattern CLASS_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final[\\s]*)?(class|interface)([^{]*)[{]\\s*$') - private static final Map ACCESS_MAP = [private: 0, protected: 2, public: 3].tap { it.put(null, 1) } - - @InputDirectory abstract DirectoryProperty getPatchDir() - @Input @Optional abstract ListProperty getPatchesWithS2SArtifact() - - @Override - void check(Reporter reporter, boolean fix) { - final patchDir = getPatchDir().get().asFile.toPath() - Files.walk(patchDir).withCloseable { - it.filter(Files.&isRegularFile).forEach { path -> - final String relativeName = patchDir.relativize(path).toString() - verifyPatch(path, reporter, fix, relativeName, patchesWithS2SArtifact.get().contains(relativeName.replace('\\', '/'))) - } - } - } - - static boolean accessChange(String previous, String current) { - //return ACCESS_MAP[previous] < ACCESS_MAP[current] - return previous != current - } - - void verifyPatch(Path patch, Reporter reporter, boolean fix, String patchPath, boolean hasS2SArtifact) { - int oldFixedErrors = reporter.fixed.size() - - final lines = Files.readAllLines(patch) - - int hunksStart = 0 - boolean onlyWhiteSpace = false - - final List newLines = [] - - // First two lines are file name ++/-- and we do not care - newLines.add(lines[0] + '\n') - newLines.add(lines[1] + '\n') - - int i - for (i = 2; i < lines.size(); ++i) { - def line = lines[i] - newLines.add(line + '\n') - - if (HUNK_START_PATTERN.matcher(line).find()) { - if (onlyWhiteSpace) { - if (!hasS2SArtifact) - reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath") - int toRemove = i - hunksStart - while (toRemove-- > 0) - newLines.remove(newLines.size() - 1) - } - hunksStart = i - onlyWhiteSpace = true - continue - } - - if (line.startsWithAny('+','-')) { - def prefixChange = false - def prevLine = lines[i - 1] - - if (line.charAt(0) == (char)'+' && prevLine.charAt(0) == (char)'-') { - def prevTrim = prevLine.substring(1).replaceAll("\\s", "") - def currTrim = line.substring(1).replaceAll("\\s", "") - - if (prevTrim == currTrim) { - prefixChange = true - } - - def pMatcher = FIELD_PATTERN.matcher(prevLine) - def cMatcher = FIELD_PATTERN.matcher(line) - - if (pMatcher.find() && cMatcher.find() && - pMatcher.group(6) == cMatcher.group(6) && // = ... - pMatcher.group(5) == cMatcher.group(5) && // field name - pMatcher.group(3) == cMatcher.group(3) && // static - (accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) { - reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false) - } - - pMatcher = METHOD_PATTERN.matcher(prevLine) - cMatcher = METHOD_PATTERN.matcher(line) - - if (pMatcher.find() && cMatcher.find() && - pMatcher.group(6) == cMatcher.group(6) && // params - pMatcher.group(5) == cMatcher.group(5) && // void name - pMatcher.group(3) == cMatcher.group(3) && // static - (accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) { - reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false) - } - - pMatcher = CLASS_PATTERN.matcher(prevLine) - cMatcher = CLASS_PATTERN.matcher(line) - - if (pMatcher.find() && cMatcher.find() && - pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ... - pMatcher.group(5) == cMatcher.group(5) && // class | interface - pMatcher.group(3) == cMatcher.group(3) && // static - (accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) { - reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false) - } - } - - if (line.charAt(0) == (char)'-' && i + 1 < lines.size()) { - final nextLine = lines[i + 1] - if (nextLine.charAt(0) == (char)'+') { - final nextTrim = nextLine.substring(1).replaceAll("\\s", "") - final currTrim = line.substring(1).replaceAll("\\s", "") - - if (nextTrim == currTrim) { - prefixChange = true - } - } - } - - final isWhiteSpaceChange = WHITESPACE_PATTERN.matcher(line).find() - - if (!prefixChange && !isWhiteSpaceChange) { - onlyWhiteSpace = hasS2SArtifact && IMPORT_PATTERN.matcher(line).find() - } else if (isWhiteSpaceChange) { - final prevLineChange = prevLine.startsWithAny('+','-') - final nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-') - - if (!prevLineChange && !nextLineChange) { - reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath \n$prevLine\n$line\n${lines[i+1]}", false) - } - } - - if (line.contains('\t')) { - reporter.report("Patch contains tabs on line ${i + 1}, file: $patchPath") - line = line.replaceAll('\t', ' ') - newLines.remove(newLines.size() - 1) - newLines.add(line + '\n') - } - - if (IMPORT_PATTERN.matcher(line).find() && !hasS2SArtifact) { - reporter.report("Patch contains import change on line ${i + 1}, file: $patchPath", false) - } - } - } - - if (onlyWhiteSpace) { - if (!hasS2SArtifact) - reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath") - def toRemove = i - hunksStart; - while (toRemove-- > 0) - newLines.remove(newLines.size() - 1) - } - - if ((reporter.fixed.size() > oldFixedErrors && fix) || hasS2SArtifact) { - if (newLines.size() <= 2) { - logger.lifecycle("Patch is now empty removing, file: {}", patchPath) - Files.delete(patch) - } - else { - if (!hasS2SArtifact) - logger.lifecycle("*** Updating patch file. Please run setup then genPatches again. ***") - Files.newBufferedWriter(patch).withCloseable { - newLines.each { l -> it.write(l) } - } - } - } - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckSAS.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckSAS.groovy deleted file mode 100644 index bc93cf05ba..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckSAS.groovy +++ /dev/null @@ -1,110 +0,0 @@ -package net.minecraftforge.forge.tasks.checks - -import groovy.transform.CompileStatic -import net.minecraftforge.forge.tasks.Annotatable -import net.minecraftforge.forge.tasks.InheritanceData -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.InputFiles - -@CompileStatic -abstract class CheckSAS extends CheckTask { - - @InputFile abstract RegularFileProperty getInheritance() - @InputFiles abstract ConfigurableFileCollection getSass() - - @Override - void check(Reporter reporter, boolean fix) { - final inheritance = InheritanceData.parse(this.inheritance.get().asFile) - - sass.each { f -> - final lines = [] - f.eachLine { line -> - if (line[0] == '\t') return // Skip any tabbed lines, those are ones we add - final idx = line.indexOf('#') - if (idx == 0 || line.isEmpty()) { - lines.add(line) - return - } - def comment = idx == -1 ? null : line.substring(idx) - if (idx != -1) line = line.substring(0, idx - 1) - final spl = (line.trim().replace('(', ' (') + ' ').split(' ', -1) - def (String cls, String name, String desc) = [spl[0], spl[1], spl[2]] - cls = cls.replaceAll('\\.', '/') - - if (inheritance[cls] === null) { - reporter.report("Invalid: $line") - } else if (name.isEmpty()) { //Class SAS - final toAdd = [] - final clsSided = isSided(inheritance[cls]) - boolean sided = clsSided - - /* TODO: MergeTool doesn't do fields - if (json[cls]['fields'] != null) { - for (entry in json[cls]['fields']) { - if (isSided(entry.value)) { - sided = true - toAdd.add('\t' + cls + ' ' + entry.key) - } - } - } */ - - final clsInh = inheritance[cls] - if (clsInh.methods) { - for (entry in clsInh.methods) { - if (isSided(entry.value)) { - sided = true - toAdd.add('\t' + cls + ' ' + entry.key.replaceAll(' ', '')) - findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) } - findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) } - } else if (clsSided) { - findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) } - findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) } - } - } - } - - if (sided) { - lines.add(cls + (comment == null ? '' : ' ' + comment)) - lines.addAll(toAdd.sort()) - } else { - reporter.report("Invalid: $line") - } - - } else if (desc.isEmpty()) { // Fields - /* TODO: MergeTool doesn't do fields - if (json[cls]['fields'] != null && isSided(json[cls]['fields'][name])) - lines.add(cls + ' ' + name + (comment == null ? '' : ' ' + comment)) - else */ - reporter.report("Invalid: $line") - } else { // Methods - final clsInh = inheritance[cls] - if (clsInh.methods === null || !isSided(clsInh.methods[name + ' ' + desc])) - reporter.report("Invalid: $line") - else { - lines.add(cls + ' ' + name + desc + (comment == null ? '' : ' ' + comment)) - findChildMethods(inheritance, cls, name + ' ' + desc).each { println(line + ' -- ' + it) } - findChildMethods(inheritance, cls, name + ' ' + desc).each { lines.add('\t' + it) } - } - } - } - - if (fix) f.text = lines.join('\n') - } - } - - protected static boolean isSided(Annotatable annotatable) { - if (annotatable === null) return false - for (ann in annotatable.annotations) { - if ('Lnet/minecraftforge/api/distmarker/OnlyIn;' == ann.desc) - return true - } - return false - } - - protected static findChildMethods(Map json, String cls, String desc) { - return json.values().findAll{ it.methods !== null && it.methods[desc] !== null && it.methods[desc].override == cls && isSided(it.methods[desc]) } - .collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet - } -} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckTask.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckTask.groovy deleted file mode 100644 index 62b50abdbf..0000000000 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckTask.groovy +++ /dev/null @@ -1,114 +0,0 @@ -package net.minecraftforge.forge.tasks.checks - -import groovy.transform.CompileStatic -import groovy.transform.stc.ClosureParams -import groovy.transform.stc.ThirdParam -import net.minecraftforge.forge.tasks.Util -import org.gradle.api.Action -import org.gradle.api.DefaultTask -import org.gradle.api.logging.LogLevel -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.TaskAction -import org.gradle.api.tasks.TaskContainer -import org.gradle.api.tasks.VerificationTask - -@CompileStatic -abstract class CheckTask extends DefaultTask implements VerificationTask { - @Input - abstract Property getMode() - - private boolean ignoreFailures = false - - @Input - @Override - boolean getIgnoreFailures() { - return ignoreFailures - } - - @Override - void setIgnoreFailures(boolean ignoreFailures) { - this.ignoreFailures = ignoreFailures - } - - @TaskAction - void run() { - Util.init() - - boolean doFix = getMode().get() === CheckMode.FIX - final Reporter reporter = new Reporter(doFix) - check(reporter, doFix) - - if (reporter.messages) { - if (getMode().get() === CheckMode.CHECK) { - logger.error("Check task '{}' found errors:\n{}", name, reporter.messages.join('\n')) - if (!ignoreFailures) { - throw new IllegalArgumentException("${reporter.messages.size()} errors were found!") - } - } else { - if (logger.isEnabled(LogLevel.DEBUG)) { - logger.warn("Check task '{}' found {} errors and fixed {}:\n{}", name, reporter.messages.size(), reporter.fixed.size(), reporter.fixed.join('\n')) - } else { - logger.warn("Check task '{}' found {} errors and fixed {}.", name, reporter.messages.size(), reporter.fixed.size()) - } - - if (reporter.notFixed) { - logger.error('{} errors could not be fixed:\n{}', reporter.notFixed.size(), reporter.notFixed.join('\n')) - if (!ignoreFailures) { - throw new IllegalArgumentException("${reporter.notFixed.size()} errors which cannot be fixed were found!") - } - } - } - } - } - - abstract void check(Reporter reporter, boolean fix) - - @CompileStatic - static final class Reporter { - final boolean trackFixed - Reporter(boolean trackFixed) { - this.trackFixed = trackFixed - } - - public final List messages = [] - - public final List fixed = [] - public final List notFixed = [] - - void report(String message, boolean canBeFixed = true) { - messages.add(message) - - if (trackFixed) { - if (canBeFixed) { - fixed.add(message) - } else { - notFixed.add(message) - } - } - } - } - - static void registerTask(TaskContainer tasks, String taskName, @DelegatesTo.Target('type') Class clazz, - @DelegatesTo(genericTypeIndex = 0, target = 'type') - @ClosureParams(ThirdParam.FirstGenericType) Closure configuration) { - taskName = taskName.capitalize() - tasks.register("check$taskName", clazz) { CheckTask task -> - def castedTask = task as T - configuration.setDelegate(castedTask) - configuration.call(castedTask) - castedTask.mode.set(CheckMode.CHECK) - castedTask.group = 'checks' - } - tasks.named('checkAll').configure { it.dependsOn("check$taskName") } - - tasks.register("checkAndFix$taskName", clazz) { CheckTask task -> - def castedTask = task as T - configuration.setDelegate(castedTask) - configuration.call(castedTask) - castedTask.mode.set(CheckMode.FIX) - castedTask.group = 'checks' - } - tasks.named('checkAllAndFix').configure { it.dependsOn("checkAndFix$taskName") } - } -} diff --git a/build_clean.gradle b/build_clean.gradle deleted file mode 100644 index 21471fcc6c..0000000000 --- a/build_clean.gradle +++ /dev/null @@ -1,73 +0,0 @@ -plugins { - id 'java-library' - id 'net.minecraftforge.gradle.patcher' -} - -evaluationDependsOn(':mcp') - -repositories { - mavenCentral() - maven gradleutils.forgeMaven - maven gradleutils.minecraftLibsMaven -} - -java.toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION) - -dependencies { - implementation(libs.forgespi) -} - -patcher { - parent = project(':mcp') - mcVersion = MC_VERSION - patchedSrc = file('src/main/java') - - mappings channel: MAPPING_CHANNEL, version: MAPPING_VERSION - - runs { - clean_client { - client true - taskName 'clean_client' - ideaModule "${rootProject.name}.${project.name}.main" - - main 'net.minecraft.client.main.Main' - workingDirectory project.file('run/client') - - args '--gameDir', '.' - args '--version', MC_VERSION - args '--assetsDir', downloadAssets.output - args '--assetIndex', '{asset_index}' - args '--accessToken', '0' - } - - clean_server { - client false - taskName 'clean_server' - ideaModule "${rootProject.name}.${project.name}.main" - - main 'net.minecraft.server.Main' - workingDirectory project.file('run/server') - } - } -} - -tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation - options.warnings = false // Shutup deprecated for removal warnings -} - -eclipse.classpath.file.whenMerged { - // Disable optional warnings on the minecraft decompiled source code. It just muddies the warning window and hides warnings in our own codebase - def src = entries.find { it.path == 'src/main/java' } - if (src == null) throw new IllegalStateException("You must run `setup` task before importing") - src.entryAttributes['ignore_optional_problems'] = 'true' -} - -tasks.named('genPatches').configure { - onlyIf { false } -} - -tasks.named('extractRangeMap').configure { - onlyIf { false } - tool = 'net.minecraftforge:Srg2Source:8.1.0:fatjar' -} \ No newline at end of file diff --git a/build_forge.gradle b/build_forge.gradle deleted file mode 100644 index 27c950ac8b..0000000000 --- a/build_forge.gradle +++ /dev/null @@ -1,1021 +0,0 @@ -import de.undercouch.gradle.tasks.download.Download -import net.minecraftforge.forge.tasks.checks.CheckATs -import net.minecraftforge.forge.tasks.checks.CheckExcs -import net.minecraftforge.forge.tasks.checks.CheckPatches -import net.minecraftforge.forge.tasks.checks.CheckSAS -import net.minecraftforge.forge.tasks.checks.CheckTask - -import java.nio.file.Files -import net.minecraftforge.forge.tasks.* -import static net.minecraftforge.forge.tasks.Util.* -import net.minecraftforge.gradle.common.tasks.ApplyBinPatches -import net.minecraftforge.gradle.common.tasks.CheckJarCompatibility -import net.minecraftforge.gradle.common.tasks.DownloadMavenArtifact -import net.minecraftforge.gradle.common.tasks.ExtractInheritance -import net.minecraftforge.gradle.patcher.tasks.FilterNewJar -import net.minecraftforge.gradle.patcher.tasks.GeneratePatches -import net.minecraftforge.gradle.userdev.tasks.RenameJar -import org.apache.tools.ant.filters.ReplaceTokens -import org.objectweb.asm.Opcodes - -plugins { - id 'idea' - id 'eclipse' - id 'java-library' - id 'maven-publish' - id 'net.minecraftforge.licenser' - id 'de.undercouch.download' - id 'net.minecraftforge.gradleutils' - id 'net.minecraftforge.gradle.patcher' - id 'net.minecraftforge.gradlejarsigner' -} - -Util.init() //Init all our extension methods! - -// We depend on all other projects so that we can know their versions for userdev config -rootProject.subprojects.each { sib -> if (sib != project) evaluationDependsOn(sib.path) } - -apply from: rootProject.file('build_shared.gradle') - -java { - toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION) - withSourcesJar() -} - -jarSigner.autoDetect('forge') - -applyPatches { - level 'WARNING' - failOnError = UPDATING != 'true' - rejects = rootProject.layout.projectDirectory.dir('rejects').asFile -} - -sourceSets { - main { - java { - srcDir "$rootDir/src/main/java" - } - resources { - srcDir "$rootDir/src/main/resources" - srcDir "$rootDir/src/main/generated" - } - } - test { - java { - srcDir "$rootDir/src/test/java" - } - resources { - srcDir "$rootDir/src/test/resources" - srcDir "$rootDir/src/test/generated" - } - } -} - -final String SPEC_VERSION = gitversion.info.tag - -// The new versioning sceme is -.. -// ForgeMC is a unique identifier for every MC version we have supported. -// Essentially, the same as the old, except dropping the first number, and the builds are no longer unique. -final def MCP_ARTIFACT = project(':mcp').mcp.config.get() - -final List EXTRA_TXTS = [ - rootProject.file('LICENSE.txt'), - rootProject.tasks.createChangelog.outputFile -] - -ext { - MAVEN_PATH = "${group.toString().replace('.', '/')}/${project.name}/${VERSION}".toString() -} -final String MAVEN_PATH = ext.MAVEN_PATH - -final String BINPATCH_TOOL = 'net.minecraftforge:binarypatcher:1.3.0:fatjar' -final String INSTALLER_TOOLS = 'net.minecraftforge:installertools:1.4.5' -final String FART = 'net.minecraftforge:ForgeAutoRenamingTool:1.0.6' -final String S2S_TOOL = 'net.minecraftforge:Srg2Source:8.2.0:fatjar' - -configurations { - // Don't pull all libraries, if we're missing something, add it to the installer list so the installer knows to download it. - bootstrap { transitive = false } - installer { - extendsFrom(bootstrap) - transitive = false - } - api.extendsFrom(installer) -} - -dependencies { - // These need to actually be on the classpath at the start. This is only used for the server shim jar. - // And this is only needed because custom file systems are REQUIRED to be on the boot classloader. - // This has ASM/BootStrap/Unsafe all because I haven't gotten around to moving UnionFileSystem out to its own project. - bootstrap(libs.jarjar.fs) // JarInJar file system - bootstrap(libs.roimfs) // JarInJar File System - FileSystems need to be on boot loader. - bootstrap(libs.bundles.jimfs) // In memory file system used for ForgeDev launches - bootstrap(libs.securemodules) // Has Union file system in it - bootstrap(libs.unsafe) // Needed by securemodules - bootstrap(libs.bundles.asm) // Needed by securemodules - - implementation(libs.jopt.simple) - - installer(libs.bootstrap) - installer(libs.bootstrap.api) // Needed by securemodules - installer(libs.accesstransformers) - installer(libs.eventbus) - installer(libs.jspecify) // Dep of EventBus - installer(libs.forgespi) - installer(libs.coremods.api) - installer(libs.modlauncher) - installer(libs.mergetool.api) - installer(libs.bundles.night.config) - installer(libs.maven.artifact) - installer(libs.bundles.terminalconsoleappender) - installer(libs.mixin) - installer(libs.mixinextras.forge) - installer(libs.bundles.jarjar) - installer(libs.roimfs) - - installer(project(':fmlcore')) - installer(project(':fmlloader')) - installer(project(':fmlearlydisplay')) - installer(project(':javafmllanguage')) - installer(project(':lowcodelanguage')) - installer(project(':mclanguage')) - installer(project(':forge-transformers')) - - runtimeOnly(libs.bootstrap) - runtimeOnly(libs.bootstrap.dev) - - annotationProcessor(libs.eventbus.validator) -} - -tasks.named('extractRangeMap').configure { - tool = S2S_TOOL -} -tasks.named('applyRangeMap').configure { - tool = S2S_TOOL -} -tasks.named('applyRangeMapBase').configure { - tool = S2S_TOOL -} - -// Disable all tests, we have GameTests not JUnit tests -test { - exclude '**/*' -} - -patcher { - excs.from file("$rootDir/src/main/resources/forge.exc") - parent = project(':mcp') - mcVersion = MC_VERSION - mappings channel: MAPPING_CHANNEL, version: MAPPING_VERSION - patches = file("$rootDir/patches/minecraft") - patchedSrc = file('src/main/java') - srgPatches = false - accessTransformers.from file("$rootDir/src/main/resources/META-INF/accesstransformer.cfg") - sideAnnotationStrippers.from file("$rootDir/src/main/resources/forge.sas") - - runs { - forge_client { - property 'eventbus.api.strictRuntimeChecks', 'true' - property 'org.lwjgl.system.SharedLibraryExtractDirectory', 'lwjgl_dll' - - args '--launchTarget', 'forge_dev_client', - '--username', 'Dev', - '--version', project.name, - '--accessToken', '0', - '--userType', 'mojang', - '--versionType', 'release', - '--assetsDir', downloadAssets.output, - '--assetIndex', "{asset_index}" - } - - forge_client_test { - parent runs.forge_client - source sourceSets.test - } - - forge_server { - args '--launchTarget', 'forge_dev_server' - } - - forge_server_test { - parent runs.forge_server - source sourceSets.test - } - - forge_server_gametest { - 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 - } - - forge_server_gametest_test { - parent runs.forge_server_gametest - source sourceSets.test - } - - forge_data { - args '--launchTarget', 'forge_dev_data', - '--mod', 'forge', - '--all', - '--output', rootProject.file('src/main/generated/'), - '--existing', sourceSets.main.resources.srcDirs[0], - '--assetsDir', downloadAssets.output, - '--assetIndex', "{asset_index}" - } - - forge_data_test { - source sourceSets.test - args '--launchTarget', 'forge_dev_data', - '--mod', '.+', - '--all', - '--output', rootProject.file('src/test/generated/'), - '--existing', sourceSets.main.resources.srcDirs[0], - '--existing', sourceSets.test.resources.srcDirs[0], - '--assetsDir', downloadAssets.output, - '--assetIndex', "{asset_index}" - } - - forge_client_data { - args '--launchTarget', 'forge_dev_client_data', - '--mod', 'forge', - '--all', - '--output', rootProject.file('src/main/generated/'), - '--existing', sourceSets.main.resources.srcDirs[0], - '--assetsDir', downloadAssets.output, - '--assetIndex', "{asset_index}" - } - - forge_client_data_test { - source sourceSets.test - args '--launchTarget', 'forge_dev', - '--launchEntry', 'minecraft/net.minecraft.client.data.Main', - '--launchData', - '--mod', '.+', - '--all', - '--output', rootProject.file('src/test/generated/'), - '--existing', sourceSets.main.resources.srcDirs[0], - '--existing', sourceSets.test.resources.srcDirs[0], - '--assetsDir', downloadAssets.output, - '--assetIndex', "{asset_index}" - } - } -} - -afterEvaluate { - if (!patcher.srgPatches) { - srg2mcpClean { - dependsOn = [] - input = project(':mcp').setupMCP.output - } - userdevJar { - onlyIf = { t -> true } - } - } -} - -tasks.register('downloadCrowdin', Download) { - src 'https://files.minecraftforge.net/crowdin.zip' - dest file('build/crowdin.zip') - useETag 'all' - onlyIfModified true - quiet true -} - -tasks.userdevConfig.configure { - configurations.installer.allDependencies.forEach { - def dep = it.toString() - if (it instanceof ProjectDependency) - dep = "net.minecraftforge:$it.dependencyProject.name:$it.dependencyProject.version" - libraries.add(dep) - } - - // TODO [ForgeDev][UserDev] Split API/Runtime elements in userdev config - // so we don't gotta keep making exceptions for MixinExtras - addCompileDependency(libs.mixinextras.common) - addAnnotationProcessorDependency(libs.mixinextras.common) - - inject = '' // We don't have a userdev sourceset anymore. Empty as a gradle workaround... - runs { - client { - environment 'MCP_MAPPINGS', '{mcp_mappings}' - property 'forge.enableGameTest', 'true' - args '--launchTarget', "forge_userdev_client" - args '--version', 'MOD_DEV' - args '--assetIndex', '{asset_index}' - args '--assetsDir', '{assets_root}' - } - - clientData { - environment 'MCP_MAPPINGS', '{mcp_mappings}' - args '--launchTarget', "forge_userdev_client_data" - args '--assetIndex', '{asset_index}' - args '--assetsDir', '{assets_root}' - } - - server { - environment 'MCP_MAPPINGS', '{mcp_mappings}' - property 'forge.enableGameTest', 'true' - args '--launchTarget', "forge_userdev_server" - } - - gameTestServer { - environment 'MCP_MAPPINGS', '{mcp_mappings}' - args '--launchTarget', "forge_userdev_server_gametest" - } - - data { - environment 'MCP_MAPPINGS', '{mcp_mappings}' - args '--launchTarget', "forge_userdev_data" - args '--assetIndex', '{asset_index}' - args '--assetsDir', '{assets_root}' - } - } -} - -for (def run in patcher.runs + tasks.userdevConfig.runs) { - if (run.parents) continue // We already added this to the parent run config - //run.property 'bsl.debug', 'true' - run.args '--gameDir', '.' - run.jvmArgs '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders' - run.client run.name.contains('client') - run.main 'net.minecraftforge.bootstrap.ForgeBootstrap' -} - -for (def run : patcher.runs) { - def isTest = run.name.endsWith('_test') - run.taskName = run.name - run.workingDirectory file('run/' + run.name) - run.ideaModule rootProject.name + '.' + project.name + '.' + (isTest ? 'test' : 'main') - run.property 'bsl.debug', 'true' - run.property 'terminal.jline', 'true' - if (isTest) { - run.property 'forge.enableGameTest', 'true' - run.property 'forgedev.enableTestMods', 'true' - } -} - -tasks.register('downloadVersionManifest', Download) { - src 'https://piston-meta.mojang.com/mc/game/version_manifest_v2.json' - dest file('build/versions/version_manifest.json') - useETag 'all' - onlyIfModified true - quiet true -} -tasks.register('downloadJson', Download) { - dependsOn downloadVersionManifest - inputs.file downloadVersionManifest.dest - src { downloadVersionManifest.dest.json.versions.find { it.id == MC_VERSION }.url } - dest file("build/versions/$MC_VERSION/version.json") - useETag 'all' - onlyIfModified true - quiet true -} -tasks.register('downloadClientRaw', Download) { - dependsOn downloadJson - inputs.file downloadJson.dest - src { downloadJson.dest.json.downloads.client.url } - dest file("build/versions/$MC_VERSION/client.jar") - useETag 'all' - onlyIfModified true - quiet true -} -tasks.register('downloadServerRaw', Download) { - dependsOn downloadJson - inputs.file downloadJson.dest - src { downloadJson.dest.json.downloads.server.url } - dest file("build/versions/$MC_VERSION/server-bundled.jar") - useETag 'all' - onlyIfModified true - quiet true -} -tasks.register('extractServer', ExtractFile) { - dependsOn downloadServerRaw - input = downloadServerRaw.dest - target = "META-INF/versions/$MC_VERSION/server-${MC_VERSION}.jar" - output = file("build/versions/$MC_VERSION/server.jar") -} -tasks.register('downloadLibraries', DownloadLibraries) { - dependsOn downloadJson - input = downloadJson.dest - output = rootProject.file('build/libraries/') -} -tasks.register('extractInheritance', ExtractInheritance) { - dependsOn downloadLibraries - tool = INSTALLER_TOOLS + ':fatjar' - args.add '--annotations' - input = genJoinedBinPatches.cleanJar - libraries.addAll downloadLibraries.librariesOutput.map { rf -> - Files.readAllLines(rf.asFile.toPath()).stream().map(File::new).toList() - } -} -tasks.register("findFinalizeSpawnTargets", BytecodePredicateFinder) { - dependsOn downloadClientRaw - jar = downloadClientRaw.dest - output = rootProject.file('forge-transformers/src/main/resources/coremods/finalize_spawn_targets.json') - predicate = { - parent, node, insn -> - return 'net/minecraft/world/level/BaseSpawner' != parent.name // Ignore this class as we special case it. - && insn.getOpcode().equals(Opcodes.INVOKEVIRTUAL) - && insn.name == 'finalizeSpawn' - && insn.desc == '(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;'; - } -} -tasks.register('validateDeprecations', ValidateDeprecations) { - input = tasks.jar.archiveFile - mcVersion = MC_VERSION -} -tasks.named('jar', Jar).configure { - finalizedBy 'validateDeprecations' -} -tasks.register("downloadInstaller", DownloadMavenArtifact) { - artifact = "net.minecraftforge:installer:2.2.+:fatjar" - changing = true -} -tasks.register("downloadServerShim", DownloadMavenArtifact) { - artifact = libs.bootstrap.shim.get().toString() - changing = true -} -tasks.register("createJoinedSRG", DownloadMavenArtifact) { - artifact = "net.minecraft:joined:${MC_VERSION}-${MCP_VERSION}" -} -tasks.named('genClientBinPatches').configure { - dependsOn(downloadClientRaw) - tool = BINPATCH_TOOL - cleanJar = downloadClientRaw.dest - dirtyJar = jar.archiveFile -} -tasks.named('genServerBinPatches').configure { - dependsOn(extractServer) - tool = BINPATCH_TOOL - cleanJar = extractServer.output - dirtyJar = jar.archiveFile -} -tasks.named('genJoinedBinPatches').configure { - tool = BINPATCH_TOOL - cleanJar = createJoinedSRG.output - dirtyJar = jar.archiveFile -} -tasks.register('applyClientBinPatches', ApplyBinPatches) { - dependsOn downloadClientRaw - tool = BINPATCH_TOOL - clean = downloadClientRaw.dest - patch = genClientBinPatches.output - args.addAll(['--data', '--unpatched', '--store', '--marker', '.forge_patched_minecraft']) -} -tasks.register('applyServerBinPatches', ApplyBinPatches) { - dependsOn extractServer - tool = BINPATCH_TOOL - clean = extractServer.output - patch = genServerBinPatches.output - args.addAll(['--data', '--unpatched', '--store']) -} -tasks.register('applyJoinedBinPatches', ApplyBinPatches) { - tool = BINPATCH_TOOL - clean = genJoinedBinPatches.cleanJar - patch = genJoinedBinPatches.output -} -tasks.register('createServerShimClasspath', BundleList) { - dependsOn(downloadServerRaw) - serverBundle = downloadServerRaw.dest -} -tasks.register('createServerShimConfig') { - ext.output = file('build/libs/bootstrap-shim.properties') - doLast { - var cfg = new CleanProperties() - cfg['Main-Class'] = 'net.minecraftforge.bootstrap.ForgeBootstrap' - cfg['Java-Version'] = '25' - cfg['Arguments'] = '--launchTarget forge_server' - cfg.store(output) - } -} -tasks.register('serverShimJar', Jar) { - dependsOn(createServerShimConfig) - from (createServerShimConfig.output) - from (createServerShimClasspath.output) { - rename { 'bootstrap-shim.list' } - } - from (zipTree(downloadServerShim.output)) - manifest { - from { - zipTree(downloadServerShim.output).find { it.name == 'MANIFEST.MF' } - } - - attributes('Class-Path': configurations.bootstrap.resolvedConfiguration.resolvedArtifacts.collect { "libraries/${Util.getMavenInfoFromDep(it).path}" }.join(' ')) - } - archiveClassifier = 'shim' - jarSigner.sign(it) -} -tasks.register('checkAll') { - dependsOn 'checkLicenses' - group = 'checks' -} -tasks.register('checkAllAndFix') { - dependsOn 'findFinalizeSpawnTargets', 'checkLicenses' - group = 'checks' -} - -CheckTask.registerTask(tasks, 'ATs', CheckATs) { - dependsOn extractInheritance - ats.from patcher.accessTransformers - inheritance = extractInheritance.output -} - -CheckTask.registerTask(tasks, 'SAS', CheckSAS) { - dependsOn extractInheritance - sass.from patcher.sideAnnotationStrippers - inheritance = extractInheritance.output -} - -CheckTask.registerTask(tasks, 'Excs', CheckExcs) { - dependsOn jar - binary = jar.archiveFile.get().asFile - excs.from patcher.excs -} - -CheckTask.registerTask(tasks, 'Patches', CheckPatches) { - dependsOn 'genPatches' - patchDir = file("$rootDir/patches") - patchesWithS2SArtifact = [ - 'minecraft/net/minecraft/client/renderer/ViewArea.java.patch', - 'minecraft/net/minecraft/data/models/blockstates/Variant.java.patch', - ] -} - -tasks.named('genPatches', GeneratePatches).configure { - finalizedBy checkAndFixPatches - autoHeader true - lineEnding = '\n' -} - -def baseForgeVersionProperty = project.objects.property(String) -baseForgeVersionProperty.set(project.provider { TeamcityRequests.attemptFindBase(rootDir) ?: getLatestForgeVersion(MC_VERSION) }) -baseForgeVersionProperty.finalizeValueOnRead() -final jarCompatibilityTaskSetup = { Task task -> - task.group = 'jar compatibility' - task.onlyIf { - baseForgeVersionProperty.getOrNull() !== null - } -} - -tasks.register('setupCheckJarCompatibility', SetupCheckJarCompatibility) { - inputVersion = baseForgeVersionProperty -} - -tasks.register('applyBaseCompatibilityJarBinPatches', ApplyBinPatches) { - jarCompatibilityTaskSetup(it) - - clean = project.tasks.createJoinedSRG.output - patch = project.tasks.named('setupCheckJarCompatibility').flatMap { it.baseBinPatchesOutput } - output = project.layout.buildDirectory.dir(name).map { it.file('output.jar') } -} - -tasks.register('mergeBaseForgeJar', MergeJars) { - jarCompatibilityTaskSetup(it) - - inputJars.from(project.tasks.named('applyBaseCompatibilityJarBinPatches').flatMap { it.output }) - inputJars.from(baseForgeVersionProperty.map { inputVersion -> - def output = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-universal.jar") }.get().asFile - project.rootProject.extensions.download.run { - src "https://maven.minecraftforge.net/net/minecraftforge/forge/${inputVersion}/forge-${inputVersion}-universal.jar" - dest output - } - return output - }) -} - -tasks.register('checkJarCompatibility', CheckJarCompatibility) { - jarCompatibilityTaskSetup(it) - dependsOn 'setupCheckJarCompatibility' - - baseJar = project.tasks.named('mergeBaseForgeJar').flatMap { it.output } - baseLibraries.from(project.tasks.named('createJoinedSRG').flatMap { it.output }) - - inputJar = project.tasks.named('reobfJar').flatMap { it.output } - - commonLibraries.from(project.configurations.minecraftImplementation) - commonLibraries.from(project.configurations.installer) -} - -tasks.register('launcherJson', LauncherJson).configure { - json.putAll([ - mainClass: 'net.minecraftforge.bootstrap.ForgeBootstrap', - arguments: [ - game: [ - '--launchTarget', 'forge_client' - ], - jvm: [ - '-Djava.net.preferIPv6Addresses=system', - '-XX:+UseCompactObjectHeaders' - ] - ] - ] as LinkedHashMap) -} - -tasks.register('installerJson', InstallerJson) { - icon = rootProject.file('icon.ico') - - // Json to install into the client's launcher - dependsOn(launcherJson) - input.from(launcherJson.output) - - // Get 'base' MC jar, Client is straight download, server is extracted from the bundle - dependsOn(downloadClientRaw, extractServer) - input.from(downloadClientRaw.dest, extractServer.output) - // Apply Binary patches to vanilla jar - dependsOn(applyClientBinPatches, applyServerBinPatches) - input.from(applyClientBinPatches.output, applyServerBinPatches.output, genClientBinPatches.toolJar) - - doFirst { - var libs = libraries - String[] INSTALLER_TOOLS_CLASSPATH = getClasspath(project, libs, INSTALLER_TOOLS) - json.putAll([ - _comment: launcherJson.json._comment, - hideExtract: true, - spec: 1, - profile: project.name, - version: launcherJson.json.id, - path: Util.getMavenInfoFromTask(tasks.serverShimJar).name, - minecraft: MC_VERSION, - serverJarPath: '{LIBRARY_DIR}/net/minecraft/server/{MINECRAFT_VERSION}/server-{MINECRAFT_VERSION}-bundled.jar', - data: [ - MC_UNPACKED: [ - client: "[net.minecraft:client:${MC_VERSION}]", - server: "[net.minecraft:server:${MC_VERSION}:unpacked]" - ], - MC_UNPACKED_SHA: [ - client: "'${downloadClientRaw.dest.sha1}'", - server: "'${extractServer.output.get().asFile.sha1}'" - ], - BINPATCH: [ - client: '/data/client.lzma', - server: '/data/server.lzma' - ], - PATCHED: [ - client: "[${project.group}:${project.name}:${project.version}:client]", - server: "[${project.group}:${project.name}:${project.version}:server]" - ], - PATCHED_SHA: [ - client: "'${applyClientBinPatches.output.get().asFile.sha1}'", - server: "'${applyServerBinPatches.output.get().asFile.sha1}'" - ] - ], - processors: [ - [ - sides: ['server'], - jar: INSTALLER_TOOLS, - classpath: INSTALLER_TOOLS_CLASSPATH, - args: [ - '--task', 'EXTRACT_FILES', - '--archive', '{INSTALLER}', - - '--from', 'data/README.txt', - '--to', '{ROOT}/README.txt', - - '--from', 'data/run.sh', - '--to', '{ROOT}/run.sh', - '--exec', '{ROOT}/run.sh', - - '--from', 'data/run.bat', - '--to', '{ROOT}/run.bat', - - '--from', 'data/user_jvm_args.txt', - '--to', '{ROOT}/user_jvm_args.txt', - '--optional', '{ROOT}/user_jvm_args.txt', - - '--from', 'data/unix_args.txt', - '--to', "{ROOT}/libraries/${MAVEN_PATH}/unix_args.txt", - - '--from', 'data/win_args.txt', - '--to', "{ROOT}/libraries/${MAVEN_PATH}/win_args.txt" - ] - ], [ - sides: ['server'], - jar: INSTALLER_TOOLS, - classpath: INSTALLER_TOOLS_CLASSPATH, - args: [ - '--task', 'BUNDLER_EXTRACT', - '--input', '{MINECRAFT_JAR}', - '--output', '{ROOT}/libraries/', - '--libraries' - ] - ], [ - sides: ['server'], - jar: INSTALLER_TOOLS, - classpath: INSTALLER_TOOLS_CLASSPATH, - args: [ - '--task', 'BUNDLER_EXTRACT', - '--input', '{MINECRAFT_JAR}', - '--output', '{MC_UNPACKED}', - '--jar-only' - ], - outputs: [ - '{MC_UNPACKED}': '{MC_UNPACKED_SHA}' - ] - ], [ - sides: ['server'], - jar: BINPATCH_TOOL.rsplit(':', 1)[0], // remove :fatjar - classpath: getClasspath(project, libs, BINPATCH_TOOL.rsplit(':', 1)[0]), - args: [ - '--clean', '{MC_UNPACKED}', - '--output', '{PATCHED}', - '--apply', '{BINPATCH}', - '--data', '--unpatched', '--store' - ], - outputs: [ - '{PATCHED}': '{PATCHED_SHA}' - ] - ], [ - sides: ['client'], - jar: BINPATCH_TOOL.rsplit(':', 1)[0], // remove :fatjar - classpath: getClasspath(project, libs, BINPATCH_TOOL.rsplit(':', 1)[0]), - args: [ - '--clean', '{MINECRAFT_JAR}', - '--output', '{PATCHED}', - '--apply', '{BINPATCH}', - '--data', '--unpatched', '--store', '--marker', '.forge_patched_minecraft' - ], - outputs: [ - '{PATCHED}': '{PATCHED_SHA}' - ] - ] - ] - ] as LinkedHashMap) - getClasspath(project, libs, MCP_ARTIFACT.descriptor) //Tell it to download mcp_config - } -} - -tasks.register('officialClassesOnly', Zip).configure { - dependsOn(jar) - destinationDirectory = file('build/libs') - archiveClassifier = 'official-classes' - from zipTree(jar.archiveFile).matching { - include '**/*.class' - exclude 'mcp/**' - } -} - -tasks.named('filterJarNew').configure { - dependsOn('officialClassesOnly') - input = officialClassesOnly.archiveFile - filterInners = true -} - -tasks.named('universalJar').configure { - dependsOn downloadCrowdin - from zipTree(downloadCrowdin.dest).matching { - include 'assets/forge/lang/*.json' - } - - from(EXTRA_TXTS) - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - exclude '.cache' - - manifest { - attributes([ - 'Automatic-Module-Name': 'net.minecraftforge.forge' - ] as LinkedHashMap) - attributes([ - 'Specification-Title': 'Forge', - 'Specification-Vendor': 'Forge Development LLC', - 'Specification-Version': SPEC_VERSION, - 'Implementation-Title': project.group, - 'Implementation-Vendor': 'Forge Development LLC', - 'Implementation-Version': FORGE_VERSION - ] as LinkedHashMap, 'net/minecraftforge/versions/forge/') - attributes([ - 'Specification-Title': 'Minecraft', - 'Specification-Vendor': 'Forge Development LLC', - 'Specification-Version': MC_VERSION, - 'Implementation-Title': 'MCP', - 'Implementation-Vendor': 'Forge Development LLC', - 'Implementation-Version': MCP_VERSION - ] as LinkedHashMap, 'net/minecraftforge/versions/mcp/') - } - jarSigner.sign(it) -} - -tasks.named('userdevConfig').configure { - universal = "$project.group:$project.name:$project.version:universal@jar" -} - -tasks.register('installerJar', InstallerJar) { - fat = !System.env.TEAMCITY_VERSION - //offline = true - from(EXTRA_TXTS) - from(rootProject.file('/forge_installer_logo.png')) { - rename { 'big_logo.png' } - } - from(genClientBinPatches.output) { - rename { 'data/client.lzma' } - } - from(genServerBinPatches.output) { - rename { 'data/server.lzma' } - } - - final var argsFile = rootProject.file('server_files/args.txt') - final Map> tokens = [tokens: [ - SHIM_JAR_FILE: serverShimJar.archiveFileName.get(), - MAVEN_PATH: MAVEN_PATH - ]] - from(argsFile) { - filter(tokens, ReplaceTokens) - into 'data' - rename { 'unix_args.txt' } - } - from(argsFile) { - filter(tokens, ReplaceTokens) - into 'data' - rename { 'win_args.txt' } - } - - from(rootProject.file('server_files')) { - filter(tokens, ReplaceTokens) - into 'data' - exclude 'args.txt' - } - - jarSigner.sign(it) -} - -final mdkGradleWrapper = tasks.register('mdkGradleWrapper', Wrapper) { - gradleVersion = '9.3.1' -} - -tasks.register('mdkZip', Zip) { - dependsOn mdkGradleWrapper - - archiveBaseName = project.name - archiveClassifier = 'mdk' - archiveVersion = project.version - destinationDirectory = file('build/libs') - - from mdkGradleWrapper.map(Wrapper.&getScriptFile) - from mdkGradleWrapper.map(Wrapper.&getBatchScript) - from(EXTRA_TXTS) - 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: FORGE_VERSION, - FORGE_GROUP: project.group, - FORGE_NAME: project.name, - MC_VERSION: MC_VERSION, - MAPPING_CHANNEL: MAPPING_CHANNEL, - MAPPING_VERSION: MAPPING_VERSION, - FORGE_SPEC_VERSION: SPEC_VERSION.split("\\.")[0], - MC_NEXT_VERSION: MC_NEXT_VERSION, - 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/' - } -} - -license { - header = file("$rootDir/LICENSE-header.txt") - - include 'net/minecraftforge/' - exclude 'net/minecraftforge/common/LenientUnboundedMapCodec.java' - - tasks { - main { - files.from files("$rootDir/src/main/java") - } - test { - files.from files("$rootDir/src/test/java") - } - } -} - -tasks.register('genAllData') { - dependsOn 'forge_data', 'forge_data_test', 'forge_client_data_test' -} - -if (project.hasProperty('UPDATE_MAPPINGS')) { - extractRangeMap { - sources.from sourceSets.test.java.srcDirs - addDependencies compileTestJava.classpath - } - applyRangeMap { - sources.from sourceSets.test.java.srcDirs - } - sourceSets.test.java.srcDirs.each { extractMappedNew.addTarget it } -} - -tasks.named('javadoc', Javadoc).configure { - description 'Generates the combined javadocs for the FML projects and the main Forge project' - var includedProjects = [ ':fmlcore', ':fmlloader', ':javafmllanguage', ':mclanguage', ':forge-transformers' ] - source includedProjects.collect { project(it).sourceSets.main.allJava } - classpath = classpath + files(includedProjects.collect { project(it).sourceSets.main.compileClasspath }) - - var docsDir = rootProject.file('src/docs/') - inputs.dir(docsDir) - .withPropertyName('docs resources directory') - .withPathSensitivity(PathSensitivity.RELATIVE) - .optional() - - failOnError = false - - // Exclude the Minecraft classes if not enabled - if (!project.hasProperty('generateAllDocumentation')) { - exclude 'net/minecraft/**' - exclude 'com/mojang/**' - } - exclude 'mcp/**' - - options.addStringOption('Xdoclint:all,-missing', '-public') - options { - stylesheetFile = new File(docsDir, 'stylesheet.css') - - tags = [ - 'apiNote:a:API Note:', - 'implSpec:a:Implementation Requirements:', - 'implNote:a:Implementation Note:' - ] - - groups = [ - 'Forge Mod Loader': [ - 'net.minecraftforge.fml.common.asm*', - 'net.minecraftforge.fml.loading*', - 'net.minecraftforge.fml.server*' - ], - 'FML Core': [ - 'net.minecraftforge.fml', - 'net.minecraftforge.fml.config*', - 'net.minecraftforge.fml.event*', - 'net.minecraftforge.fml.util*' - ], - 'FML Common': [ - 'net.minecraftforge.fml.core', - 'net.minecraftforge.fml.event.config', - 'net.minecraftforge.fml.event.lifecycle' - ], - 'FML Java/MC Language Providers': [ - 'net.minecraftforge.fml.common', - 'net.minecraftforge.fml.javafmlmod', - 'net.minecraftforge.fml.mclanguageprovider' - ], - 'Minecraft Forge API': [ - 'net.minecraftforge*' - ] - ] - - author = false - noSince = true - noHelp = true - - 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 - ${FORGE_VERSION} for Minecraft ${MC_VERSION}" - header = "
${FORGE_VERSION} for Minecraft ${MC_VERSION}
" - } - - doLast { - project.copy { - from docsDir - exclude '/stylesheet.css' - into destinationDir - } - } -} - -publishing { - publications.register('mavenJava', MavenPublication).configure { - artifact universalJar - artifact installerJar - artifact mdkZip - artifact userdevJar - artifact sourcesJar - artifact serverShimJar - - artifactId = project.name - pom { - name = project.name - description = 'Modifactions to Minecraft to enable mod developers.' - url = 'https://github.com/MinecraftForge/MinecraftForge' - gradleutils.pom.setGitHubDetails(pom, 'MinecraftForge') - license gradleutils.pom.Licenses.LGPLv2_1 - } - } - - repositories { - maven gradleutils.publishingForgeMaven - } -} - -// Make sure we run bin compat checking during local testing. -if ((!System.env.MAVEN_USER || !System.env.MAVEN_PASSWORD) && CHECK_COMPATIBILITY == "true") - tasks.named('publish').configure { dependsOn(':forge:checkJarCompatibility') } diff --git a/build_shared.gradle b/build_shared.gradle deleted file mode 100644 index b988510649..0000000000 --- a/build_shared.gradle +++ /dev/null @@ -1,89 +0,0 @@ -import net.minecraftforge.forge.tasks.CleanProperties - -apply plugin: 'eclipse' -apply plugin: 'idea' -apply plugin: 'net.minecraftforge.gradleutils' - -group = 'net.minecraftforge' -version = VERSION -println("Version: $version") - -repositories { - mavenCentral() - maven gradleutils.forgeMaven - maven gradleutils.minecraftLibsMaven - //mavenLocal() -} - -tasks.withType(Javadoc).configureEach { - options.tags = [ - 'apiNote:a:API Note:', - 'implSpec:a:Implementation Requirements:', - 'implNote:a:Implementation Note:' - ] - options.addStringOption('Xdoclint:all,-missing', '-public') -} - -// We need to write the manifest to the binary file so we have properly versioned packaged at dev time. -tasks.register('writeManifest') { - doLast { - if (plugins.findPlugin('net.minecraftforge.gradle.patcher')) // Forge project - universalJar.manifest.writeTo(rootProject.file('src/main/resources/META-INF/MANIFEST.MF')) - else - jar.manifest.writeTo(project.file('src/main/resources/META-INF/MANIFEST.MF')) - } -} - -tasks.register('generateResources') { - dependsOn('writeManifest') -} - -tasks.named('processResources') { - dependsOn(generateResources) -} - -// Make sure out manifests get written before compiling the code, IDEA calls this task if you tell it to use the gradle build. -tasks.withType(JavaCompile).configureEach { - dependsOn 'generateResources' - dependsOn 'processResources' // Needed because we merge the output of this with the output of the compile task. And gradle detects downstream tasks using the output without a hard dep - options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation - options.warnings = false // Shutup deprecated for removal warnings - options.forkOptions.jvmArgs += '-Xmx6G' // Needed to make compiling faster, and not run out of heap space in some cases. -} - -// Merge the resources and classes into the same directory. We'll need to split them at runtime because -// Minecraft and Forge are in the same sourceSet as they are inter dependent.. for now.. -sourceSets.each { - def dir = layout.buildDirectory.dir("classes/java/$it.name") - it.output.resourcesDir = dir - it.java.destinationDirectory = dir -} - -tasks.register('copyEclipseSettings') { - doLast { - rootProject.fileTree('ide/eclipse/template/.settings/').matching { include '**/*.prefs' }.each { file -> - def target = project.file('.settings/' + file.name) - def temp = new CleanProperties().load(file) - def exst = new CleanProperties().load(target) - exst.put('eclipse.preferences.version', '1') - exst.putAll(temp) - exst.store(target) - } - } -} - -// TODO: [Gradle][IntelliJ] Auto trigger these tasks on import. -eclipse { - // Run everytime eclipse builds the code - //autoBuildTasks writeManifest - // Run when importing the project - synchronizationTasks generateResources, copyEclipseSettings, eclipseClasspath, eclipseProject -} - -idea { - module { - // IntelliJ IDEA does not do this by itself anymore... - downloadJavadoc = true - downloadSources = true - } -} diff --git a/fmlcore/build.gradle b/fmlcore/build.gradle index 2c1153c9c9..d6adcbbea8 100644 --- a/fmlcore/build.gradle +++ b/fmlcore/build.gradle @@ -1,68 +1,78 @@ -import net.minecraftforge.gradleutils.PomUtils - plugins { id 'java-library' id 'maven-publish' - id 'net.minecraftforge.licenser' - id 'net.minecraftforge.gradleutils' + alias libs.plugins.licenser + alias libs.plugins.gradleutils + alias libs.plugins.gitversion + alias libs.plugins.changelog + id 'net.minecraftforge.forge.build.convention' } -apply from: rootProject.file('build_shared.gradle') +gradleutils.displayName = 'FML' +final vendor = 'Forge Development LLC' +description = 'Modifications to Minecraft to enable mod developers.' dependencies { - compileOnly(libs.jetbrains.annotations) + compileOnly libs.jetbrains.annotations - api(libs.eventbus) - annotationProcessor(libs.eventbus.validator) + api libs.eventbus - implementation(project(':fmlloader')) - implementation(libs.commons.io) + implementation projects.fmlloader + implementation libs.commons.io } java { - toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION) + toolchain.languageVersion = JavaLanguageVersion.of(javaVersion) withSourcesJar() } -tasks.named('jar', Jar).configure { - manifest { - attributes([ - 'Automatic-Module-Name': 'net.minecraftforge.fmlcore', - 'FMLModType': 'LIBRARY' - ] as LinkedHashMap) - attributes([ - 'Specification-Title': 'FML', - 'Specification-Vendor': 'Forge Development LLC', - 'Specification-Version': '1', - 'Implementation-Title': 'FML', - 'Implementation-Version': '1.0', - 'Implementation-Vendor': 'Forge Development LLC' - ] as LinkedHashMap, 'net/minecraftforge/fml/') - } +license { + header = rootProject.file('LICENSE-header.txt') +} +changelog { + from changelogBase } tasks.withType(JavaCompile).configureEach { options.compilerArgs << '-Xlint:-unchecked' } -license { - header = rootProject.file('LICENSE-header.txt') +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 { - publications.register('mavenJava', MavenPublication).configure { - from components.java - artifactId = 'fmlcore' - pom { - name = project.name - description = 'Modifactions to Minecraft to enable mod developers.' - url = 'https://github.com/MinecraftForge/MinecraftForge' - PomUtils.setGitHubDetails(pom, 'MinecraftForge') - license PomUtils.Licenses.LGPLv2_1 - } - } - 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 + } + } + } } diff --git a/fmlearlydisplay/build.gradle b/fmlearlydisplay/build.gradle index 78e8905e8f..b3316d0262 100644 --- a/fmlearlydisplay/build.gradle +++ b/fmlearlydisplay/build.gradle @@ -1,67 +1,74 @@ -import net.minecraftforge.gradleutils.PomUtils +import org.gradle.api.plugins.jvm.JvmTestSuite +import org.gradle.internal.os.OperatingSystem plugins { id 'java-library' + id 'jvm-test-suite' id 'maven-publish' - id 'net.minecraftforge.licenser' - id 'net.minecraftforge.gradleutils' + alias libs.plugins.licenser + alias libs.plugins.gradleutils + alias libs.plugins.gitversion + alias libs.plugins.changelog + id 'net.minecraftforge.forge.build.convention' } -apply from: rootProject.file('build_shared.gradle') - -import org.gradle.internal.os.OperatingSystem 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 + 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(JAVA_VERSION) + toolchain.languageVersion = JavaLanguageVersion.of(javaVersion) withSourcesJar() } +license { + header = rootProject.file('LICENSE-header.txt') +} + +changelog { + from changelogBase +} + dependencies { - compileOnly(libs.jetbrains.annotations) - implementation(project(':fmlloader')) - implementation(project(':fmlcore')) - implementation(libs.bundles.lwjgl) - implementation('org.slf4j:slf4j-api:1.8.0-beta4') - implementation(libs.jopt.simple) - testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.2') - testImplementation('org.powermock:powermock-core:2.0.9') - testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.2') - testRuntimeOnly('org.slf4j:slf4j-jdk14:1.8.0-beta4') - testRuntimeOnly("org.lwjgl:lwjgl::$lwjglNatives") - testRuntimeOnly("org.lwjgl:lwjgl-glfw::$lwjglNatives") - testRuntimeOnly("org.lwjgl:lwjgl-opengl::$lwjglNatives") - testRuntimeOnly("org.lwjgl:lwjgl-stb::$lwjglNatives") + compileOnly libs.jetbrains.annotations + + implementation projects.fmlloader + implementation projects.fmlcore + implementation libs.bundles.lwjgl + implementation earlyDisplayLibs.slf4j.api + implementation libs.jopt.simple } -tasks.named('test', Test).configure { - useJUnitPlatform() +// 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.glfw) { classifier lwjglNatives }, + dependencies.variantOf(earlyDisplayTestLibs.lwjgl.opengl) { classifier lwjglNatives }, + dependencies.variantOf(earlyDisplayTestLibs.lwjgl.stb) { classifier lwjglNatives } +].with { list -> + provider { list.collect { it.get() } } } -tasks.named('jar', Jar).configure { - manifest { - attributes([ - 'Automatic-Module-Name': 'net.minecraftforge.earlydisplay', - 'Forge-Module-Layer': 'boot' - ] as LinkedHashMap) - attributes([ - 'Specification-Title': 'FML Early Display', - 'Specification-Vendor': 'Forge Development LLC', - 'Specification-Version': '1', - 'Implementation-Title': 'FML Early Display', - 'Implementation-Vendor': 'Forge Development LLC', - 'Implementation-Version': '1.0' - ] as LinkedHashMap, 'net/minecraftforge/fml/earlydisplay/') +testing.suites.named('test', JvmTestSuite) { + useJUnitJupiter('5.8.2') + + dependencies { + implementation earlyDisplayTestLibs.powermock.core + runtimeOnly earlyDisplayLibs.slf4j.jdk14 + runtimeOnly.bundle lwjglTestLibs } } @@ -69,24 +76,42 @@ tasks.withType(JavaCompile).configureEach { options.compilerArgs << '-Xlint:unchecked' } -license { - header = rootProject.file('LICENSE-header.txt') +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 { - publications.register('mavenJava', MavenPublication).configure { - from components.java - artifactId = 'fmlearlydisplay' - pom { - name = project.name - description = 'A pretty looking, but prone to erroring screen displayed during Forge loading.' - url = 'https://github.com/MinecraftForge/MinecraftForge' - PomUtils.setGitHubDetails(pom, 'MinecraftForge') - license PomUtils.Licenses.LGPLv2_1 - } - } - 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 + } + } + } +} \ No newline at end of file diff --git a/fmlloader/build.gradle b/fmlloader/build.gradle index 1ee709ae2a..21a9112b80 100644 --- a/fmlloader/build.gradle +++ b/fmlloader/build.gradle @@ -1,69 +1,75 @@ -import net.minecraftforge.gradleutils.PomUtils +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' - id 'net.minecraftforge.licenser' - id 'net.minecraftforge.gradleutils' - alias(libs.plugins.apt) + 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' } -apply from: rootProject.file('build_shared.gradle') - -configurations.forEach{ it.transitive = false } - -dependencies { - compileOnly(libs.jetbrains.annotations) - - api(libs.bundles.asm) // Needed by all the black magic - api(libs.forgespi) - api(libs.mergetool.api) - api(libs.log4j.api) - api(libs.slf4j.api) - api(libs.guava) - api(libs.gson) - api(libs.maven.artifact) - api(libs.apache.commons) - api(libs.bundles.night.config) - api(libs.modlauncher) - api(libs.mojang.logging) - api(libs.jarjar.selector) - api(libs.jarjar.meta) - - implementation(libs.jopt.simple) - implementation(libs.securemodules) - implementation(libs.accesstransformers) - implementation(libs.terminalconsoleappender) - implementation(libs.jimfs) - implementation(libs.roimfs) - - // Needed because we have a custom log4j plugin, and they removed package scanning and require a data file to be generated - implementation(libs.log4j.core) - annotationProcessor(libs.bundles.log4j) - - testCompileOnly(libs.jetbrains.annotations) - testRuntimeOnly(libs.bootstrap) -} +gradleutils.displayName = 'FMLLoader' +final vendor = 'Forge Development LLC' +description = 'Modifications to Minecraft to enable mod developers.' java { - toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION) + toolchain.languageVersion = JavaLanguageVersion.of(javaVersion) withSourcesJar() } -tasks.named('jar', Jar).configure { - manifest { - attributes([ - 'Automatic-Module-Name': 'net.minecraftforge.fmlloader', - 'Forge-Module-Layer': 'boot' - ] as LinkedHashMap) - attributes([ - 'Specification-Title': 'FMLLoader', - 'Specification-Vendor': 'Forge Development LLC', - 'Specification-Version': '1', - 'Implementation-Title': 'FMLLoader', - 'Implementation-Vendor': 'Forge Development LLC', - 'Implementation-Version': FORGE_VERSION - ] as LinkedHashMap, 'net/minecraftforge/fml/loading/') +license { + header = rootProject.file('LICENSE-header.txt') +} + +changelog { + from changelogBase +} + +dependencies { + compileOnly libs.jetbrains.annotations + + // TODO [FMLLoader] Figure out a better way to lock transitive dependencies to the explicit versions shipped by the isntaller + api(libs.bundles.asm ){ transitive = false } // Needed by all the black magic + api(libs.forgespi ){ transitive = false } + api(libs.mergetool.api ){ transitive = false } + api(libs.log4j.api ){ transitive = false } + api(libs.slf4j.api ){ transitive = false } + api(libs.guava ){ transitive = false } + api(libs.gson ){ transitive = false } + api(libs.maven.artifact ){ transitive = false } + api(libs.apache.commons ){ transitive = false } + api(libs.bundles.night.config){ transitive = false } + api(libs.modlauncher ){ transitive = false } + api(libs.mojang.logging ){ transitive = false } + api(libs.jarjar.selector ){ transitive = false } + api(libs.jarjar.meta ){ transitive = false } + + implementation(libs.jopt.simple ){ transitive = false } + 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 + implementation(libs.log4j.core ){ transitive = false } + annotationProcessor(libs.bundles.log4j){ transitive = false } +} + +testing.suites.named('test', JvmTestSuite) { + dependencies { + compileOnly libs.jetbrains.annotations + runtimeOnly libs.bootstrap } } @@ -71,39 +77,81 @@ tasks.withType(JavaCompile).configureEach { options.compilerArgs << '-Xlint:unchecked' } -license { - header = rootProject.file('LICENSE-header.txt') +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 { - publications.register('mavenJava', MavenPublication).configure { - from components.java - artifactId = 'fmlloader' - pom { - name = project.name - description = 'Modifactions to Minecraft to enable mod developers.' - url = 'https://github.com/MinecraftForge/MinecraftForge' - PomUtils.setGitHubDetails(pom, 'MinecraftForge') - license PomUtils.Licenses.LGPLv2_1 - } - } - repositories { maven gradleutils.publishingForgeMaven } -} -tasks.register('writeForgeVersionJson') { - doLast { - file('src/main/resources/forge_version.json').json = [ - forge: FORGE_VERSION, - mc: MC_VERSION, - mcp: MCP_VERSION - ] + 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 + } + } } } -tasks.register('jarJarOptionsJson', net.minecraftforge.forge.tasks.JarJarMetadataOptions) { +// 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 getForgeVersion() + abstract @Input Property getMinecraftVersion() + abstract @Input Property 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 + + outputFile = file('src/main/resources/forge_version.json') +} + +tasks.register('jarJarOptionsJson', JarJarMetadataOptions) { metadataFile = project.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') @@ -118,29 +166,25 @@ tasks.register('jarJarOptionsJson', net.minecraftforge.forge.tasks.JarJarMetadat } } -tasks.named('generateResources').configure { - dependsOn('eclipseJdt') - dependsOn('eclipseJdtApt') - dependsOn('eclipseFactorypath') - dependsOn('writeForgeVersionJson') - dependsOn('jarJarOptionsJson') +tasks.named('generateResources') { + dependsOn( + tasks.named('eclipseJdt'), + tasks.named('eclipseJdtApt'), + tasks.named('eclipseFactorypath'), + tasks.named('writeForgeVersionJson', WriteForgeVersionJson), + tasks.named('jarJarOptionsJson') + ) } -tasks.named('sourcesJar') { - dependsOn('jarJarOptionsJson') -} - -eclipse { - classpath { - // We need to set the default output directory for the log4j annotation processor - // Ideally we'd just set the value, but the eclipse plugin deduplicates the output directories - // So we have to do a hacky whenMerged - //defaultOutputDir = file('bin/main') - file.whenMerged { - entries.each { entry -> - if (entry.kind == 'output' && entry.hasProperty('path')) - entry.path = 'bin/main' - } - } +eclipse.classpath { + // We need to set the default output directory for the log4j annotation processor + // Ideally we'd just set the value, but the eclipse plugin deduplicates the output directories + // So we have to do a hacky whenMerged + //defaultOutputDir = file('bin/main') + file.whenMerged { + entries.each { entry -> + if (entry.kind == 'output' && entry.hasProperty('path')) + entry.path = 'bin/main' + } } } diff --git a/forge-transformers/build.gradle b/forge-transformers/build.gradle index 105a289c88..145d9ed3e9 100644 --- a/forge-transformers/build.gradle +++ b/forge-transformers/build.gradle @@ -1,73 +1,78 @@ -import net.minecraftforge.gradleutils.PomUtils - plugins { id 'java-library' id 'maven-publish' - id 'net.minecraftforge.licenser' - id 'net.minecraftforge.gradleutils' - alias(libs.plugins.apt) + alias libs.plugins.licenser + alias libs.plugins.gradleutils + alias libs.plugins.gitversion + alias libs.plugins.changelog + id 'net.minecraftforge.forge.build.convention' } -apply from: rootProject.file('build_shared.gradle') - -configurations.forEach{ it.transitive = false } - -dependencies { - compileOnly(libs.jetbrains.annotations) - - implementation(libs.bundles.asm) // Needed by all the black magic - implementation(libs.log4j.api) - implementation(libs.gson) - implementation(libs.modlauncher) - implementation(libs.coremods.api) - - testCompileOnly(libs.jetbrains.annotations) -} +gradleutils.displayName = 'Forge Transformers' +final vendor = 'Forge Development LLC' +description = 'Forge-specific transformers unrelated to the FML project.' java { - toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION) + toolchain.languageVersion = JavaLanguageVersion.of(javaVersion) withSourcesJar() } -tasks.named('jar', Jar) { - manifest { - attributes([ - 'Automatic-Module-Name': 'net.minecraftforge.forge.transformers', - 'Forge-Module-Layer': 'boot' - ] as LinkedHashMap) - attributes([ - 'Specification-Title': 'Forge Transformers', - 'Specification-Vendor': 'Forge Development LLC', - 'Specification-Version': '1', - 'Implementation-Title': 'Forge Transformers', - 'Implementation-Vendor': 'Forge Development LLC', - 'Implementation-Version': FORGE_VERSION - ] as LinkedHashMap, 'net/minecraftforge/forge/transformers/') - } -} - -tasks.withType(JavaCompile).configureEach { - options.compilerArgs << '-Xlint:unchecked' -} - license { header = rootProject.file('LICENSE-header.txt') } -publishing { - publications.register('mavenJava', MavenPublication) { - from components.java - artifactId = 'forge-transformers' - pom { - name = project.name - description = 'Forge-specific transformers unrelated to the FML project.' - url = 'https://github.com/MinecraftForge/MinecraftForge' - PomUtils.setGitHubDetails(pom, 'MinecraftForge') - license PomUtils.Licenses.LGPLv2_1 - } - } +changelog { + from changelogBase +} +dependencies { + compileOnly(libs.jetbrains.annotations) + + implementation(libs.bundles.asm ){ transitive = false } // Needed by all the black magic + implementation(libs.log4j.api ){ transitive = false } + implementation(libs.gson ){ transitive = false } + implementation(libs.modlauncher ){ transitive = false } + implementation(libs.coremods.api){ transitive = false } + + testCompileOnly(libs.jetbrains.annotations) +} + +tasks.named('jar', Jar) { + manifest { + attributes([ + 'Automatic-Module-Name': 'net.minecraftforge.forge.transformers', + '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/forge/transformers/') + } +} + +publishing { repositories { maven gradleutils.publishingForgeMaven } + + publications.register('mavenJava', MavenPublication) { + changelog.publish(it) + gradleutils.promote(it) + + from components.java + + pom { pom -> + description = project.description + + gradleutils.pom.addRemoteDetails(pom) + + licenses { + license gradleutils.pom.licenses.LGPLv2_1 + } + } + } } diff --git a/forge-transformers/src/main/resources/coremods/finalize_spawn_targets.json b/forge-transformers/src/main/resources/coremods/finalize_spawn_targets.json index c2ed16258c..555238cf0c 100644 --- a/forge-transformers/src/main/resources/coremods/finalize_spawn_targets.json +++ b/forge-transformers/src/main/resources/coremods/finalize_spawn_targets.json @@ -42,12 +42,6 @@ "finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;" ] }, - { - "class": "net/minecraft/world/entity/animal/frog/Tadpole", - "methods": [ - - ] - }, { "class": "net/minecraft/world/entity/monster/Strider", "methods": [ @@ -85,24 +79,12 @@ "finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;" ] }, - { - "class": "net/minecraft/world/entity/monster/zombie/ZombieVillager", - "methods": [ - - ] - }, { "class": "net/minecraft/world/entity/npc/CatSpawner", "methods": [ "spawnCat(Lnet/minecraft/core/BlockPos;Lnet/minecraft/server/level/ServerLevel;Z)V" ] }, - { - "class": "net/minecraft/world/entity/npc/villager/Villager", - "methods": [ - - ] - }, { "class": "net/minecraft/world/entity/raid/Raid", "methods": [ @@ -158,11 +140,5 @@ "methods": [ "handleDataMarker(Ljava/lang/String;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/util/RandomSource;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;)V" ] - }, - { - "class": "net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate", - "methods": [ - - ] } ] \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index fb411878ff..636e21bfab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,17 +1,29 @@ +# TODO Re-evaluate # Sets default memory used for gradle commands. Can be overridden by user or command line properties. # This is required to provide enough memory for the Minecraft decompilation process. org.gradle.jvmargs=-Xmx6G -org.gradle.daemon=false + +org.gradle.warning.mode=all +org.gradle.caching=true org.gradle.parallel=true +org.gradle.configureondemand=true -JAVA_VERSION=25 -MC_VERSION=26.2 -MC_NEXT_VERSION=26.3 -MCP_VERSION=20260616.103818 -MAPPING_CHANNEL=official -MAPPING_VERSION=26.2 +# TODO [ForgeDev] Enable this once ForgeDev 7 tasks are fully migrated +org.gradle.configuration-cache=false +org.gradle.configuration-cache.parallel=false +#org.gradle.configuration-cache.problems=warn +#org.gradle.configuration-cache.integrity-check=true -# Set to true before the first build of a new MC version, so we don't do compatibility checks -CHECK_COMPATIBILITY=false -# Set to true to allow for fuzzy patching. Useful during updateing -UPDATING=false +net.minecraftforge.gradleutils.ide.automatic.sources=true +net.minecraftforge.gradleutils.compilation.defaults=true + +net.minecraftforge.gradle.merge-source-sets=true + +# Controls if the compatibility checks are auto-added to the 'check' task. +# The check tasks are still created and can be run, they just are not grouped with the normal 'check' task. +# Set to false for the first builds of a new Minecraft version. +net.minecraftforge.forge.build.check.compatibility=false +# Set to true to allow for fuzzy patching. Useful during updating. +net.minecraftforge.forge.build.updating=false +# Set to true to enable the 'validatePublish' task which is currently a work in progress +net.minecraftforge.forgedev.validate.publish=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..61285a659d17295f1de7c53e24fdf13ad755c379 100644 GIT binary patch delta 38026 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e18bc253b8..1a704683a0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4269..adff685a03 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# 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. @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# 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/. @@ -84,7 +86,7 @@ done # 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 "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +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 @@ -112,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -170,7 +171,6 @@ fi # 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" ) @@ -203,15 +203,14 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 25da30dbde..c4bdd3ab8e 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @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 ########################################################################## @@ -68,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/javafmllanguage/build.gradle b/javafmllanguage/build.gradle index e81fd2b59f..8f0b9b76c9 100644 --- a/javafmllanguage/build.gradle +++ b/javafmllanguage/build.gradle @@ -1,66 +1,79 @@ -import net.minecraftforge.gradleutils.PomUtils - plugins { id 'java-library' id 'maven-publish' - id 'net.minecraftforge.licenser' - id 'net.minecraftforge.gradleutils' + alias libs.plugins.licenser + alias libs.plugins.gradleutils + alias libs.plugins.gitversion + alias libs.plugins.changelog + id 'net.minecraftforge.forge.build.convention' } -apply from: rootProject.file('build_shared.gradle') - -dependencies { - compileOnly(libs.jetbrains.annotations) - implementation(project(':fmlloader')) - implementation(project(':fmlcore')) - implementation(libs.unsafe) - implementation(libs.securemodules) -} +gradleutils.displayName = 'JavaFMLMod' +final vendor = 'Forge Development LLC' +description = 'Language provider for Minecraft Forge that provides basic Java mod functionality.' java { - toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION) + toolchain.languageVersion = JavaLanguageVersion.of(javaVersion) withSourcesJar() } -tasks.named('jar', Jar).configure { - manifest { - attributes([ - 'Automatic-Module-Name': 'net.minecraftforge.javafmlmod', - 'FMLModType': 'LANGPROVIDER' - ] as LinkedHashMap) - attributes([ - 'Specification-Title': 'JavaFMLMod', - 'Specification-Vendor': 'Forge Development LLC', - 'Specification-Version': '1', - 'Implementation-Title': 'JavaFMLMod', - 'Implementation-Vendor': 'Forge Development LLC', - 'Implementation-Version': FORGE_VERSION - ] as LinkedHashMap, 'net/minecraftforge/fml/javafmlmod/') - } -} - -tasks.withType(JavaCompile).configureEach { - options.compilerArgs << '-Xlint:unchecked' -} - license { header = rootProject.file('LICENSE-header.txt') } -publishing { - publications.register('mavenJava', MavenPublication).configure { - from components.java - artifactId = 'javafmllanguage' - pom { - name = project.name - description = 'Language provider for Minecraft Forge that provides basic java mod functionality' - url = 'https://github.com/MinecraftForge/MinecraftForge' - PomUtils.setGitHubDetails(pom, 'MinecraftForge') - license PomUtils.Licenses.LGPLv2_1 - } - } +changelog { + from changelogBase +} +dependencies { + compileOnly libs.jetbrains.annotations + + implementation projects.fmlloader + implementation projects.fmlcore + implementation libs.unsafe + implementation libs.securemodules +} + +tasks.withType(JavaCompile).configureEach { + options.compilerArgs << '-Xlint:unchecked' +} + +tasks.named('jar', Jar) { + manifest { + attributes([ + 'Automatic-Module-Name': 'net.minecraftforge.javafmlmod', + 'FMLModType' : 'LANGPROVIDER' + ]) + 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/javafmlmod/') + } +} + +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 + } + } + } +} \ No newline at end of file diff --git a/lowcodelanguage/build.gradle b/lowcodelanguage/build.gradle index f4e92d6233..5681135f79 100644 --- a/lowcodelanguage/build.gradle +++ b/lowcodelanguage/build.gradle @@ -1,65 +1,77 @@ -import net.minecraftforge.gradleutils.PomUtils - plugins { id 'java-library' id 'maven-publish' - id 'net.minecraftforge.licenser' - id 'net.minecraftforge.gradleutils' + alias libs.plugins.licenser + alias libs.plugins.gradleutils + alias libs.plugins.gitversion + alias libs.plugins.changelog + id 'net.minecraftforge.forge.build.convention' } -apply from: rootProject.file('build_shared.gradle') +gradleutils.displayName = 'LowCodeMod' +final vendor = 'Forge Development LLC' +description = 'Language provider for Minecraft Forge that loads mods without a Java entrypoint.' java { - toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION) + toolchain.languageVersion = JavaLanguageVersion.of(javaVersion) withSourcesJar() } -dependencies { - compileOnly(libs.jetbrains.annotations) - - implementation(project(':fmlloader')) - implementation(project(':fmlcore')) -} - -tasks.named('jar', Jar).configure { - manifest { - attributes([ - 'Automatic-Module-Name': 'net.minecraftforge.lowcodemod', - 'FMLModType': 'LANGPROVIDER' - ] as LinkedHashMap) - attributes([ - 'Specification-Title': 'LowCodeMod', - 'Specification-Vendor': 'Forge Development LLC', - 'Specification-Version': '1.0', - 'Implementation-Title': project.name, - 'Implementation-Vendor': 'Forge Development LLC', - 'Implementation-Version': FORGE_VERSION.split('\\.')[0] - ] as java.util.LinkedHashMap, 'net/minecraftforge/fml/lowcodemod/') - } -} - -tasks.withType(JavaCompile).configureEach { - options.compilerArgs << '-Xlint:unchecked' -} - license { header = rootProject.file('LICENSE-header.txt') } -publishing { - publications.register('mavenJava', MavenPublication).configure { - from components.java - artifactId = 'lowcodelanguage' - pom { - name = project.name - description = 'Language provider for Minecraft Forge that loads resource packs as mods' - url = 'https://github.com/MinecraftForge/MinecraftForge' - PomUtils.setGitHubDetails(pom, 'MinecraftForge') - license PomUtils.Licenses.LGPLv2_1 - } +changelog { + from changelogBase +} + +dependencies { + compileOnly libs.jetbrains.annotations + + implementation projects.fmlloader + implementation projects.fmlcore +} + +tasks.withType(JavaCompile).configureEach { + options.compilerArgs << '-Xlint:unchecked' +} + +tasks.named('jar', Jar) { + manifest { + attributes([ + 'Automatic-Module-Name': 'net.minecraftforge.lowcodemod', + 'FMLModType' : 'LANGPROVIDER' + ]) + attributes([ + 'Specification-Title' : gradleutils.displayName.get(), + 'Specification-Vendor' : vendor, + 'Specification-Version' : '1.0', + 'Implementation-Title' : project.name, + 'Implementation-Vendor' : vendor, + 'Implementation-Version': forgeVersion.split('\\.')[0] + ], 'net/minecraftforge/fml/lowcodemod/') } - +} + +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 + } + } + } } diff --git a/mclanguage/build.gradle b/mclanguage/build.gradle index abf4e79ed2..7e4ed70a54 100644 --- a/mclanguage/build.gradle +++ b/mclanguage/build.gradle @@ -1,64 +1,77 @@ -import net.minecraftforge.gradleutils.PomUtils - plugins { id 'java-library' id 'maven-publish' - id 'net.minecraftforge.licenser' - id 'net.minecraftforge.gradleutils' + alias libs.plugins.licenser + alias libs.plugins.gradleutils + alias libs.plugins.gitversion + alias libs.plugins.changelog + id 'net.minecraftforge.forge.build.convention' } -apply from: rootProject.file('build_shared.gradle') - -dependencies { - compileOnly(libs.jetbrains.annotations) - implementation(project(':fmlloader')) - implementation(project(':fmlcore')) -} +gradleutils.displayName = 'MCLanguage' +final vendor = 'Forge Development LLC' +description = 'Language provider for Minecraft Forge that provides Minecraft Itself.' java { - toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION) + toolchain.languageVersion = JavaLanguageVersion.of(javaVersion) withSourcesJar() } -tasks.named('jar', Jar).configure { - manifest { - attributes([ - 'Automatic-Module-Name': 'net.minecraftforge.mclanguageprovider', - 'FMLModType': 'LANGPROVIDER' - ] as LinkedHashMap) - attributes([ - 'Specification-Title': 'MCLanguage', - 'Specification-Vendor': 'Forge Development LLC', - 'Specification-Version': '1', - 'Implementation-Title': 'MCLanguage', - 'Implementation-Vendor': 'Forge Development LLC', - 'Implementation-Version': '1.0' - ] as LinkedHashMap, 'net/minecraftforge/fml/mclanguageprovider/') - } -} - -tasks.withType(JavaCompile).configureEach { - options.compilerArgs << '-Xlint:unchecked' -} - license { header = rootProject.file('LICENSE-header.txt') } -publishing { - publications.register('mavenJava', MavenPublication).configure { - from components.java - artifactId = 'mclanguage' - pom { - name = project.name - description = 'Language provider for Minecraft Forge that provides Minecraft Itself' - url = 'https://github.com/MinecraftForge/MinecraftForge' - PomUtils.setGitHubDetails(pom, 'MinecraftForge') - license PomUtils.Licenses.LGPLv2_1 - } +changelog { + from changelogBase +} + +dependencies { + compileOnly libs.jetbrains.annotations + + implementation projects.fmlloader + implementation projects.fmlcore +} + +tasks.withType(JavaCompile).configureEach { + options.compilerArgs << '-Xlint:unchecked' +} + +tasks.named('jar', Jar) { + manifest { + attributes([ + 'Automatic-Module-Name': 'net.minecraftforge.mclanguageprovider', + 'FMLModType' : 'LANGPROVIDER' + ]) + 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/mclanguageprovider/') } - +} + +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 + } + } + } } diff --git a/minecraft.versions.toml b/minecraft.versions.toml new file mode 100644 index 0000000000..a2a4d47b43 --- /dev/null +++ b/minecraft.versions.toml @@ -0,0 +1,6 @@ +[versions] +java = "25" +minecraft = "26.2" +minecraft-next = "26.3" +mcp = "20260616.103818" +changelog-base = "65.0" diff --git a/settings.gradle b/settings.gradle index 631180f095..58eb3b195d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,119 +1,34 @@ -pluginManagement { - repositories { - gradlePluginPortal() - //mavenLocal() - maven { url = 'https://maven.minecraftforge.net/' } - } -} +import groovy.transform.Field -buildscript { - dependencies { - classpath('com.google.code.gson:gson') { - version { - strictly '2.11.0' +pluginManagement { + private static final @Field String FORGEDEV_VERSION = '7.0.0-beta.51' + + if (new File('forgedev/settings.gradle').exists()) { + includeBuild 'forgedev' + } else { + resolutionStrategy.eachPlugin { + // TODO [ForgeDev] Consolidate these entry-points + switch (requested.id.id) { + case 'net.minecraftforge.forgedev': + case 'net.minecraftforge.forge.build.convention': + useVersion(FORGEDEV_VERSION) + break } } } + + repositories { + gradlePluginPortal() + maven { url = 'https://maven.minecraftforge.net/' } + //mavenLocal() + } } plugins { id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' } -dependencyResolutionManagement { - versionCatalogs { - libs { - plugin('apt', 'com.diffplug.eclipse.apt').version('4.2.0') - - library('forgespi', 'net.minecraftforge:forgespi:8.0.0') // Needs modlauncher - library('modlauncher', 'net.minecraftforge:modlauncher:10.2.4') // Needs securemodules - library('securemodules', 'net.minecraftforge:securemodules:2.2.24') // Needs unsafe - library('unsafe', 'net.minecraftforge:unsafe:0.9.2') - library('accesstransformers', 'net.minecraftforge:accesstransformers:8.2.2') - library('coremods-api', 'net.minecraftforge:coremods-api:5.3.0') - version('eventbus', '7.0.1') - library('eventbus', 'net.minecraftforge', 'eventbus').versionRef('eventbus') - library('eventbus-validator', 'net.minecraftforge', 'eventbus-validator').versionRef('eventbus') - library('mergetool-api', 'net.minecraftforge:mergetool-api:1.0') - - library('roimfs', 'net.minecraftforge:roimfs:1.0.0') // ReadOnlyInMemoryFileSystem - Used for JarInJar extraction at runtime - library('mixin', 'org.spongepowered:mixin:0.8.7') - version('mixinextras', '0.5.3') - library('mixinextras-forge', 'io.github.llamalad7', 'mixinextras-forge').versionRef('mixinextras') - library('mixinextras-common', 'io.github.llamalad7', 'mixinextras-common').versionRef('mixinextras') - library('jetbrains-annotations', 'org.jetbrains:annotations:24.1.0') // for ApiStatus annotations - library('jspecify', 'org.jspecify:jspecify:1.0.0') // for nullability annotations - library('slf4j-api', 'org.slf4j:slf4j-api:2.0.7') - library('maven-artifact', 'org.apache.maven:maven-artifact:3.8.8') - - // Google's InMemory File System. Used by ForgeDev Tests for now, but could be useful for a lot of things. - library('jimfs', 'com.google.jimfs:jimfs:1.3.0') - bundle('jimfs', ['guava', 'failureaccess']) - - version('bootstrap', '2.1.8') - library('bootstrap', 'net.minecraftforge', 'bootstrap' ).versionRef('bootstrap') // Needs modlauncher - library('bootstrap-api', 'net.minecraftforge', 'bootstrap-api' ).versionRef('bootstrap') - library('bootstrap-dev', 'net.minecraftforge', 'bootstrap-dev' ).versionRef('bootstrap') - library('bootstrap-shim', 'net.minecraftforge', 'bootstrap-shim').versionRef('bootstrap') - - // ASM it's used for so many of our hacks - version('asm', '9.9.1') - library('asm', 'org.ow2.asm', 'asm' ).versionRef('asm') - library('asm-tree', 'org.ow2.asm', 'asm-tree' ).versionRef('asm') - library('asm-util', 'org.ow2.asm', 'asm-util' ).versionRef('asm') - library('asm-commons', 'org.ow2.asm', 'asm-commons' ).versionRef('asm') - library('asm-analysis', 'org.ow2.asm', 'asm-analysis').versionRef('asm') - bundle('asm', ['asm', 'asm-tree', 'asm-util', 'asm-commons', 'asm-analysis']) - - // Terminal Console Appender.. essentually make pretty colors in the console, but it's been a PITA - library('terminalconsoleappender', 'net.minecrell:terminalconsoleappender:1.2.0') - version('jline', '3.25.1') - library('jline-reader', 'org.jline', 'jline-reader' ).versionRef('jline') - library('jline-terminal', 'org.jline', 'jline-terminal' ).versionRef('jline') - library('jline-terminal-jna', 'org.jline', 'jline-terminal-jna').versionRef('jline') // Colors and tab completeion - bundle('terminalconsoleappender', ['terminalconsoleappender', 'jline-reader', 'jline-terminal', 'jline-terminal-jna']) - - // The core of our configuration system, it has many flaws, but it works for the most part - version('night-config', '3.7.4') - library('night-config-toml', 'com.electronwill.night-config', 'toml').versionRef('night-config') - library('night-config-core', 'com.electronwill.night-config', 'core').versionRef('night-config') - bundle('night-config', ['night-config-toml', 'night-config-core']) - - // Jar in Jar FileSystem - version('jarjar', '0.4.2') - library('jarjar-fs', 'net.minecraftforge', 'JarJarFileSystems').versionRef('jarjar') - library('jarjar-meta', 'net.minecraftforge', 'JarJarMetadata' ).versionRef('jarjar') - library('jarjar-selector', 'net.minecraftforge', 'JarJarSelector' ).versionRef('jarjar') - bundle('jarjar', ['jarjar-fs', 'jarjar-selector', 'jarjar-meta']) - - // These are libraries shipped by the MC launcher, try and keep them in sync with the manifest - // but honestly if we don't it just means we ship them as normal libraries by adding them to the installer config in the forge project - library('gson', 'com.google.code.gson:gson:2.11.0') - library('guava', 'com.google.guava:guava:33.5.0-jre') - library('failureaccess', 'com.google.guava:failureaccess:1.0.3') - - version('log4j', '2.24.3') - library('log4j-api', 'org.apache.logging.log4j', 'log4j-api' ).versionRef('log4j') - library('log4j-core', 'org.apache.logging.log4j', 'log4j-core').versionRef('log4j') - bundle('log4j', ['log4j-api', 'log4j-core']) - - library('apache-commons', 'org.apache.commons:commons-lang3:3.17.0') - library('mojang-logging', 'com.mojang:logging:1.5.10') - library('jopt-simple', 'net.sf.jopt-simple:jopt-simple:5.0.4') - library('commons-io', 'commons-io:commons-io:2.17.0') - - version('lwjgl', '3.4.1') - library('lwjgl', 'org.lwjgl', 'lwjgl' ).versionRef('lwjgl') - library('lwjgl-glfw', 'org.lwjgl', 'lwjgl-glfw' ).versionRef('lwjgl') - library('lwjgl-opengl', 'org.lwjgl', 'lwjgl-opengl').versionRef('lwjgl') - library('lwjgl-stb', 'org.lwjgl', 'lwjgl-stb' ).versionRef('lwjgl') - library('lwjgl-tinyfd', 'org.lwjgl', 'lwjgl-tinyfd').versionRef('lwjgl') - bundle('lwjgl', ['lwjgl', 'lwjgl-glfw', 'lwjgl-opengl', 'lwjgl-stb', 'lwjgl-tinyfd']) - } - } -} - -rootProject.name = 'ForgeRoot' +rootProject.name = 'forge' include 'fmlloader' include 'fmlcore' @@ -123,15 +38,177 @@ include 'lowcodelanguage' include 'fmlearlydisplay' include 'forge-transformers' -include ':mcp' -project(":mcp").projectDir = file("projects/mcp") +enableFeaturePreview 'TYPESAFE_PROJECT_ACCESSORS' -include ':forge' -project(":forge").projectDir = file("projects/forge") -project(':forge').buildFileName = '../../build_forge.gradle' +gradle.beforeProject { Project project -> + project.pluginManager.withPlugin('net.minecraftforge.forge.build.convention') { + // NOTE: We are defining these variables in here so that the projects can stay isolated, increasing build time. + // See minecraft.versions.toml for the versions where you can change these values. + //@formatter:off + project.ext.javaVersion = project.gradleutils.unpack(project.bootLibs.versions.java) + project.ext.minecraftVersion = project.gradleutils.unpack(project.bootLibs.versions.minecraft) + project.version = project.gitversion.getMCTagOffsetBranch(project.ext.minecraftVersion) + project.ext.forgeVersion = project.version.substring(project.ext.minecraftVersion.length() + 1) + project.ext.minecraftNextVersion = project.gradleutils.unpack(project.bootLibs.versions.minecraft.next) + project.ext.mcpVersion = project.gradleutils.unpack(project.bootLibs.versions.mcp) + project.ext.changelogBase = project.gradleutils.unpack(project.bootLibs.versions.changelog.base) + //@formatter:on + } -if (false && !System.env.TEAMCITY_VERSION) { - include ':clean' - project(':clean').projectDir = file('projects/clean') - project(':clean').buildFileName = '../../build_clean.gradle' + // 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 switching to Kotlin DSL, or if JetBrains gets their shit together. + project.repositories { + // 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() + + project.pluginManager.withPlugin('net.minecraftforge.gradleutils') { + maven project.gradleutils.forgeMaven + } + + project.pluginManager.withPlugin('net.minecraftforge.forgedev') { + maven project.forgedev.mavenizer + } + + mavenLocal() + } +} + +dependencyResolutionManagement.versionCatalogs { + register('bootLibs') { + description = 'Important version numbers that are externally declared for easy modification.' + + // Contains Java, Minecraft, and MCP versions + from(files('minecraft.versions.toml')) + } + + register('buildLibs') { + description = 'Libraries used solely during the build and publish process.' + + library 'binarypatcher', 'net.minecraftforge', 'binarypatcher' version '1.3.4' + library 'installer', 'net.minecraftforge', 'installer' version '2.2.+' + library 'installertools', 'net.minecraftforge', 'installertools' version '1.4.5' + library 'renamer', 'net.minecraftforge', 'ForgeAutoRenamingTool' version '1.0.6' + library 'srg2source', 'net.minecraftforge', 'Srg2Source' version '8.2.0' + } + + //@formatter:off + register('libs') { + description = 'Libraries used by Forge and its subprojects.' + + plugin 'licenser', 'net.minecraftforge.licenser' version '1.2.0' + plugin 'versions', 'com.github.ben-manes.versions' version '0.53.0' + plugin 'gradleutils', 'net.minecraftforge.gradleutils' version '3.3.35' + plugin 'gitversion', 'net.minecraftforge.gitversion' version '3.1.7' + plugin 'changelog', 'net.minecraftforge.changelog' version '3.2.2' + plugin 'renamer', 'net.minecraftforge.renamer' version '1.0.16' + plugin 'download', 'de.undercouch.download' version '5.6.0' + plugin 'jarsigner', 'net.minecraftforge.gradlejarsigner' version '1.2.1' + plugin 'apt', 'com.diffplug.eclipse.apt' version '4.4.1' + + // TODO [Forge][Buildscript We should not be needing to manually add transitive dependencies + // If we need to make strict versions for dependencies, we should do so using dependency constraints + library 'forgespi', 'net.minecraftforge', 'forgespi' version '8.0.0' // Needs modlauncher + library 'modlauncher', 'net.minecraftforge', 'modlauncher' version '10.2.4' // Needs securemodules + library 'securemodules', 'net.minecraftforge', 'securemodules' version '2.2.24' // Needs unsafe + library 'unsafe', 'net.minecraftforge', 'unsafe' version '0.9.2' + library 'accesstransformers', 'net.minecraftforge', 'accesstransformers' version '8.2.2' + library 'coremods-api', 'net.minecraftforge', 'coremods-api' version '5.3.0' + version 'eventbus', '7.0.1' + library 'eventbus', 'net.minecraftforge', 'eventbus' versionRef 'eventbus' + library 'eventbus-validator', 'net.minecraftforge', 'eventbus-validator' versionRef 'eventbus' + library 'mergetool-api', 'net.minecraftforge', 'mergetool-api' version '1.0' + + library 'roimfs', 'net.minecraftforge', 'roimfs' version '1.0.0' // ReadOnlyInMemoryFileSystem - Used for JarInJar extraction at runtime + library 'mixin', 'org.spongepowered', 'mixin' version '0.8.7' + version 'mixinextras', '0.5.3' + library 'mixinextras-forge', 'io.github.llamalad7', 'mixinextras-forge' versionRef 'mixinextras' + library 'mixinextras-common', 'io.github.llamalad7', 'mixinextras-common' versionRef 'mixinextras' + library 'jetbrains-annotations', 'org.jetbrains', 'annotations' version '24.1.0' // for ApiStatus annotations + library 'jspecify', 'org.jspecify', 'jspecify' version '1.0.0' // for nullability annotations + library 'slf4j-api', 'org.slf4j', 'slf4j-api' version '2.0.7' + library 'maven-artifact', 'org.apache.maven', 'maven-artifact' version '3.8.8' + + // Google's InMemory File System. Used by ForgeDev Tests for now, but could be useful for a lot of things. + library 'jimfs', 'com.google.jimfs:jimfs:1.3.0' + bundle 'jimfs', ['guava', 'failureaccess'] + + version 'bootstrap', '2.1.8' + library 'bootstrap', 'net.minecraftforge', 'bootstrap' versionRef 'bootstrap' // Needs modlauncher + library 'bootstrap-api', 'net.minecraftforge', 'bootstrap-api' versionRef 'bootstrap' + library 'bootstrap-dev', 'net.minecraftforge', 'bootstrap-dev' versionRef 'bootstrap' + library 'bootstrap-shim', 'net.minecraftforge', 'bootstrap-shim' versionRef 'bootstrap' + + // ASM it's used for so many of our hacks + version 'asm', '9.9.1' + library 'asm', 'org.ow2.asm', 'asm' versionRef 'asm' + library 'asm-tree', 'org.ow2.asm', 'asm-tree' versionRef 'asm' + library 'asm-util', 'org.ow2.asm', 'asm-util' versionRef 'asm' + library 'asm-commons', 'org.ow2.asm', 'asm-commons' versionRef 'asm' + library 'asm-analysis', 'org.ow2.asm', 'asm-analysis' versionRef 'asm' + bundle 'asm', ['asm', 'asm-tree', 'asm-util', 'asm-commons', 'asm-analysis'] + + // Terminal Console Appender. essentially make pretty colors in the console, but it's been a PITA + library 'terminalconsoleappender', 'net.minecrell', 'terminalconsoleappender' version '1.2.0' + version 'jline', '3.25.1' + library 'jline-reader', 'org.jline', 'jline-reader' versionRef 'jline' + library 'jline-terminal', 'org.jline', 'jline-terminal' versionRef 'jline' + library 'jline-terminal-jna', 'org.jline', 'jline-terminal-jna' versionRef 'jline' // Colors and tab completeion + bundle 'terminalconsoleappender', ['terminalconsoleappender', 'jline-reader', 'jline-terminal', 'jline-terminal-jna'] + + // The core of our configuration system, it has many flaws, but it works for the most part + version 'night-config', '3.7.4' + library 'night-config-toml', 'com.electronwill.night-config', 'toml' versionRef 'night-config' + library 'night-config-core', 'com.electronwill.night-config', 'core' versionRef 'night-config' + bundle 'night-config', ['night-config-toml', 'night-config-core'] + + // Jar in Jar FileSystem + version 'jarjar', '0.4.2' + library 'jarjar-fs', 'net.minecraftforge', 'JarJarFileSystems' versionRef 'jarjar' + library 'jarjar-meta', 'net.minecraftforge', 'JarJarMetadata' versionRef 'jarjar' + library 'jarjar-selector', 'net.minecraftforge', 'JarJarSelector' versionRef 'jarjar' + bundle 'jarjar', ['jarjar-fs', 'jarjar-selector', 'jarjar-meta'] + + // These are libraries shipped by the MC launcher, try and keep them in sync with the manifest + // but honestly if we don't it just means we ship them as normal libraries by adding them to the installer config in the forge project + library 'gson', 'com.google.code.gson', 'gson' version '2.11.0' + library 'guava', 'com.google.guava', 'guava' version '33.5.0-jre' + library 'failureaccess', 'com.google.guava', 'failureaccess' version '1.0.3' + + version 'log4j', '2.24.3' + library 'log4j-api', 'org.apache.logging.log4j', 'log4j-api' versionRef 'log4j' + library 'log4j-core', 'org.apache.logging.log4j', 'log4j-core' versionRef 'log4j' + bundle 'log4j', ['log4j-api', 'log4j-core'] + + library 'apache-commons', 'org.apache.commons', 'commons-lang3' version '3.17.0' + library 'mojang-logging', 'com.mojang', 'logging' version '1.5.10' + library 'jopt-simple', 'net.sf.jopt-simple', 'jopt-simple' version '5.0.4' + library 'commons-io', 'commons-io', 'commons-io' version '2.17.0' + + version 'lwjgl', '3.4.1' + library 'lwjgl', 'org.lwjgl', 'lwjgl' versionRef 'lwjgl' + library 'lwjgl-glfw', 'org.lwjgl', 'lwjgl-glfw' versionRef 'lwjgl' + library 'lwjgl-opengl', 'org.lwjgl', 'lwjgl-opengl' versionRef 'lwjgl' + library 'lwjgl-stb', 'org.lwjgl', 'lwjgl-stb' versionRef 'lwjgl' + library 'lwjgl-tinyfd', 'org.lwjgl', 'lwjgl-tinyfd' versionRef 'lwjgl' + bundle 'lwjgl', ['lwjgl', 'lwjgl-glfw', 'lwjgl-opengl', 'lwjgl-stb', 'lwjgl-tinyfd'] + } + + register('earlyDisplayLibs') { + version 'slf4j', '1.8.0-beta4' + library 'slf4j-api', 'org.slf4j', 'slf4j-api' versionRef 'slf4j' + library 'slf4j-jdk14', 'org.slf4j', 'slf4j-jdk14' versionRef 'slf4j' + } + + register('earlyDisplayTestLibs') { + library 'powermock-core', 'org.powermock', 'powermock-core' version '2.0.9' + + library 'lwjgl', 'org.lwjgl', 'lwjgl' withoutVersion() + library 'lwjgl-glfw', 'org.lwjgl', 'lwjgl-glfw' withoutVersion() + library 'lwjgl-opengl', 'org.lwjgl', 'lwjgl-opengl' withoutVersion() + library 'lwjgl-stb', 'org.lwjgl', 'lwjgl-stb' withoutVersion() + bundle 'lwjgl', ['lwjgl', 'lwjgl-glfw', 'lwjgl-opengl', 'lwjgl-stb'] + } + //@formatter:on }