mc-neoforge/build.gradle
sciwhiz12 0b43c802ab
Update Gradle and other dependencies (#3334)
Co-authored-by: Luke Bemish <53243629+lukebemish@users.noreply.github.com>
2026-08-08 04:10:07 +08:00

192 lines
8 KiB
Groovy

import java.util.regex.Matcher
import java.util.regex.Pattern
import net.neoforged.neodev.CheckSplitSources
plugins {
id 'net.neoforged.gradleutils' version '6.0.0'
id 'dev.lukebemish.immaculate' version '0.2.5' apply false
id 'net.neoforged.licenser' version '0.7.5'
id 'neoforge.formatting-conventions'
id 'neoforge.versioning'
}
// If an external version is defined, always use that
ext.isPreReleaseVersion = false
final fixedProjectVersion = project.providers.gradleProperty("version").getOrElse(null)
if (fixedProjectVersion == null) {
if (project.minecraft_version.contains('-')) {
ext.isPreReleaseVersion = true
// Match the manual snapshot release suffix (e.g. -alpha.M+snapshot-N) with M=0 and a suffixed .<timestamp>
// Separate out the -snapshot-N or -rc-N suffix
final preReleaseSuffix = project.minecraft_version.split("-", 2)[1]
project.version = "${project.neoforge_snapshot_next_stable}.0-alpha.0+${preReleaseSuffix}.${(new Date()).format('yyyyMMdd.HHmmss', TimeZone.getTimeZone('UTC'))}"
} else {
project.version = gradleutils.version.toString()
}
} else {
project.version = fixedProjectVersion
}
// Print version, generally useful to know - also appears on CI
System.out.println("NeoForge version ${project.version}")
allprojects {
version = rootProject.version
group = 'net.neoforged'
apply plugin: 'java'
java.toolchain.languageVersion.set(JavaLanguageVersion.of(project.java_version))
}
// Remove src/ sources from the root project. They are used in the neoforge subproject.
sourceSets {
main {
java {
srcDirs = []
}
resources {
srcDirs = []
}
}
}
// Put licenser here otherwise it tries to license all source sets including decompiled MC sources
license {
header = file('codeformat/HEADER.txt')
skipExistingHeaders = true
tasks {
neoforge {
// Add all NeoForge sources
files.from rootProject.fileTree("src", {
include "**/*.java"
})
}
}
}
// Put spotless here because it wants the files to live inside the project root
immaculate {
workflows.named("java") {
files.from rootProject.fileTree("buildSrc/src", {
include "**/*.java"
})
files.from rootProject.fileTree("src", {
include "**/*.java"
})
}
workflows.register("patches") {
files.from(rootProject.fileTree("patches"))
custom 'noImportChanges', { String fileContents ->
if (fileContents.contains('+import') || fileContents.contains('-import')) {
throw new GradleException("Import changes are not allowed in patches!")
}
return fileContents
}
def interfaceChange = Pattern.compile('[-+].*(implements|(interface.*extends))(.*)\\{')
custom 'noInterfaceModifications', { String fileContents ->
// FIXME: remove when handling of Holder is resolved
// Special case: removal of sealed and permits incorrectly triggers interface change detection
if (fileContents.startsWith("--- a/net/minecraft/core/Holder.java")) {
return fileContents
}
def interfaceChanges = fileContents.lines().filter { it.matches(interfaceChange) }.toList()
if (interfaceChanges.isEmpty()) return fileContents
String oldInterfaces = ""
// we expect interface additions/removals in pairs of - and then +
interfaceChanges.each { String change ->
final match = change =~ interfaceChange
match.find()
final values = match.group(3).trim()
if (change.startsWith('-')) {
oldInterfaces = values
} else if (oldInterfaces != values) {
throw new GradleException("Modification of interfaces via patches is not allowed!")
}
}
return fileContents
}
//Note: This doesn't detect changing access level to or from package private
//TODO: Eventually try and make this support checking package private access level changes?
def accessLevelChange = Pattern.compile('^[-+]\\s*(public|private|protected)\\s.*\$', Pattern.UNIX_LINES | Pattern.MULTILINE)
custom 'noAccessChanges', { String fileContents ->
// Special case: we need to make the field private for our coremod
if (fileContents.startsWith("--- a/net/minecraft/world/level/levelgen/structure/Structure.java")) {
return fileContents
}
// The anvil menu patches a method to be final, which cannot be done with an AT, but trips this check otherwise.
if (fileContents.startsWith("--- a/net/minecraft/world/inventory/AnvilMenu.java")) {
return fileContents
}
def accessChanges = fileContents.findAll(accessLevelChange)
if (accessChanges.isEmpty()) return fileContents
Map<String, Set<String>> removals = Map.of("private", new HashSet<>(), "protected", new HashSet<>(), "public", new HashSet<>())
accessChanges.each { String change ->
//Get the type of match
String[] data = change.substring(1).trim().split(" ", 2)
String lineStart = data[1].substring(0, Math.min(30, data[1].length()))
if (change.startsWith('-')) {//Removal
removals.get(data[0]).add(lineStart)
} else {//Addition
for (var entry : removals.entrySet()) {
if (entry.key == data[0]) {
continue
}
if (entry.value.remove(lineStart)) {
throw new GradleException("Changing access level via patches is not allowed, use an AT! Changed line: " + change)
}
}
}
}
//Note: We allow mismatched counts in case we are removing a method entirely for some reason
return fileContents
}
//Trim any trailing whitespace from patch additions
def trailingWhitespace = Pattern.compile('^\\+.*[ \t]+\$', Pattern.UNIX_LINES | Pattern.MULTILINE)
custom 'trimTrailingWhitespacePatches', { String fileContents ->
Matcher matcher = trailingWhitespace.matcher(fileContents)
StringBuilder sb = new StringBuilder()
while (matcher.find()) {
matcher.appendReplacement(sb, matcher.group().trim())
}
matcher.appendTail(sb)
return sb.toString()
}
// Disallow explicit not null in patches, it's always implied.
custom 'noNotNull', { String fileContents ->
fileContents.eachLine {
if (!it.startsWith("+")) return
if (it.contains('@NotNull') || it.contains('@Nonnull')
|| it.contains('@org.jetbrains.annotations.NotNull')
|| it.contains('@javax.annotation.Nonnull')) {
throw new GradleException('@NotNull and @Nonnull are disallowed.')
}
}
}
//Replace any FQN versions of javax/jetbrains @Nullable with the jspecify variant
custom 'jetbrainsNullablePatches', { String fileContents ->
fileContents.replace('@javax.annotation.Nullable', '@org.jspecify.annotations.Nullable')
fileContents.replace('@org.jetbrains.annotations.Nullable', '@org.jspecify.annotations.Nullable')
}
}
}
final task = tasks.register('checkSourcesAreSplit', CheckSplitSources) {
serverClientFolder = project.provider { project.layout.projectDirectory.dir('src/main/java/net/neoforged/neoforge/client') }
.map { it.asFile.exists() ? it : null }
clientClientFolder = project.layout.projectDirectory.dir('src/client/java/net/neoforged/neoforge/client')
}
tasks.named('checkFormatting') {
dependsOn(task)
}