mirror of
https://github.com/neoforged/NeoForge
synced 2026-08-20 08:23:13 -04:00
192 lines
8 KiB
Groovy
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)
|
|
}
|