diff --git a/build-logic/base/build.gradle.kts b/build-logic/base/build.gradle.kts index a90f6c8d5..b801364cf 100644 --- a/build-logic/base/build.gradle.kts +++ b/build-logic/base/build.gradle.kts @@ -21,11 +21,6 @@ dependencies { gradlePlugin { plugins { - register("cc-tweaked") { - id = "cc-tweaked" - implementationClass = "cc.tweaked.gradle.CCTweakedPlugin" - } - register("cc-tweaked.illuaminate") { id = "cc-tweaked.illuaminate" implementationClass = "cc.tweaked.gradle.IlluaminatePlugin" diff --git a/build-logic/base/src/main/kotlin/cc-tweaked.base-convention.gradle.kts b/build-logic/base/src/main/kotlin/cc-tweaked.base-convention.gradle.kts new file mode 100644 index 000000000..ad9db8d47 --- /dev/null +++ b/build-logic/base/src/main/kotlin/cc-tweaked.base-convention.gradle.kts @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +import cc.tweaked.gradle.CCTweakedExtension +import cc.tweaked.gradle.JUnitExt +import com.diffplug.gradle.spotless.FormatExtension +import com.diffplug.spotless.LineEnding +import org.gradle.plugins.ide.idea.model.IdeaModel +import org.jetbrains.gradle.ext.IdeaExtPlugin +import org.jetbrains.gradle.ext.runConfigurations +import org.jetbrains.gradle.ext.settings +import java.nio.charset.StandardCharsets + +plugins { + idea + checkstyle + id("com.diffplug.spotless") +} + +val cct = project.extensions.create("cct", CCTweakedExtension::class.java) + +// Extend the [IdeaExtPlugin] plugin's `runConfiguration` container to also support [JUnitExt]. +project.plugins.withType(IdeaExtPlugin::class.java) { + val ideaModel = project.extensions.findByName("idea") as IdeaModel? ?: return@withType + val ideaProject = ideaModel.project ?: return@withType + + ideaProject.settings.runConfigurations { + registerFactory(JUnitExt::class.java) { name -> project.objects.newInstance(JUnitExt::class.java, name) } + } +} + +val libs = project.extensions.getByType().named("libs") + +group = "cc.tweaked" +base.archivesName.set("cc-tweaked-${libs.findVersion("minecraft").get()}-${project.name}") + +repositories { + mavenCentral() + + exclusiveContent { + forRepositories(maven("https://maven.squiddev.cc/mirror")) + filter { + includeGroup("cc.tweaked") + // Things we mirror + includeGroup("com.simibubi.create") + includeGroup("commoble.morered") + includeGroup("dev.architectury") + includeGroup("dev.emi") + includeGroup("maven.modrinth") + includeGroup("me.shedaniel.cloth") + includeGroup("me.shedaniel") + includeGroup("mezz.jei") + includeModule("com.terraformersmc", "modmenu") + includeModule("me.lucko", "fabric-permissions-api") + } + } +} + +dependencies { + checkstyle(libs.findLibrary("checkstyle").get()) +} + +tasks.register("checkstyle") { + description = "Run Checkstyle on all sources" + group = LifecycleBasePlugin.VERIFICATION_GROUP + dependsOn(tasks.withType(Checkstyle::class.java)) +} + +spotless { + encoding = StandardCharsets.UTF_8 + lineEndings = LineEnding.UNIX + + fun FormatExtension.defaults() { + endWithNewline() + trimTrailingWhitespace() + leadingTabsToSpaces(4) + } + + kotlinGradle { + defaults() + ktlint() + } + + // Only configure Java/Kotlin if we have our source sets available. + project.plugins.withType(JvmEcosystemPlugin::class.java) { + java { + defaults() + importOrder("", "javax|java", "\\#") + removeUnusedImports() + } + kotlin { + defaults() + ktlint() + } + } +} + +idea.module { + excludeDirs.addAll(project.files("run", "out", "logs").files) + + // Force Gradle to write to inherit the output directory from the parent, instead of writing to out/xxx/classes. + // This is required for Loom. + inheritOutputDirs = true +} diff --git a/build-logic/base/src/main/kotlin/cc-tweaked.fabric.gradle.kts b/build-logic/base/src/main/kotlin/cc-tweaked.fabric.gradle.kts index 8d93d904a..61579b7a1 100644 --- a/build-logic/base/src/main/kotlin/cc-tweaked.fabric.gradle.kts +++ b/build-logic/base/src/main/kotlin/cc-tweaked.fabric.gradle.kts @@ -5,7 +5,6 @@ /** Default configuration for Fabric projects. */ import cc.tweaked.gradle.CCTweakedExtension -import cc.tweaked.gradle.CCTweakedPlugin import cc.tweaked.gradle.DependencyCheck import cc.tweaked.gradle.MinecraftConfigurations @@ -15,8 +14,6 @@ plugins { id("cc-tweaked.java-convention") } -plugins.apply(CCTweakedPlugin::class.java) - repositories { maven("https://maven.parchmentmc.org/") { name = "Parchment" @@ -33,9 +30,8 @@ loom { MinecraftConfigurations.setup(project) -extensions.configure(CCTweakedExtension::class.java) { - linters(minecraft = true, loader = "fabric") -} +val cct = extensions.getByType(CCTweakedExtension::class.java) +cct.linters(minecraft = true) dependencies { val libs = project.extensions.getByType().named("libs") @@ -78,3 +74,25 @@ tasks.named("checkDependencyConsistency", DependencyCheck::class.java) { override("org.lwjgl", lwjgl, "3.3.2") } } + +/** + * Given our list of source/class directories, build a list of files in the "client" source set. + */ +fun getClientFiles(files: FileCollection): List { + val out = mutableListOf() + files + // This is a massive hack. *Ideally* we'd like to filter our embedded projects list to those with a client + // capability, but I cannot find a sensible way to do that. + // Instead, we look for directories which are probably "build/classes/XXX/client" or "src/client/XXX" + .filter { dir -> dir.name == "client" || dir.parentFile.name == "client" } + .asFileTree.visit { if (!isDirectory) out.add(path) } + return out +} + +tasks.remapJar { + additionalClientOnlyEntries.addAll(cct.embeddedProjectClassesAndResources.map(::getClientFiles)) +} + +tasks.remapSourcesJar { + additionalClientOnlyEntries.addAll(cct.embeddedProjectSources.map(::getClientFiles)) +} diff --git a/build-logic/base/src/main/kotlin/cc-tweaked.forge.gradle.kts b/build-logic/base/src/main/kotlin/cc-tweaked.forge.gradle.kts index 44de854e0..73433cb02 100644 --- a/build-logic/base/src/main/kotlin/cc-tweaked.forge.gradle.kts +++ b/build-logic/base/src/main/kotlin/cc-tweaked.forge.gradle.kts @@ -5,7 +5,6 @@ /** Default configuration for Forge projects. */ import cc.tweaked.gradle.CCTweakedExtension -import cc.tweaked.gradle.CCTweakedPlugin import cc.tweaked.gradle.MinecraftConfigurations plugins { @@ -13,8 +12,6 @@ plugins { id("net.neoforged.moddev.legacyforge") } -plugins.apply(CCTweakedPlugin::class.java) - legacyForge { val libs = project.extensions.getByType().named("libs") enable { @@ -33,5 +30,5 @@ legacyForge { MinecraftConfigurations.setup(project) extensions.configure(CCTweakedExtension::class.java) { - linters(minecraft = true, loader = "forge") + linters(minecraft = true) } diff --git a/build-logic/base/src/main/kotlin/cc-tweaked.java-convention.gradle.kts b/build-logic/base/src/main/kotlin/cc-tweaked.java-convention.gradle.kts index d1530ac06..bdbc74884 100644 --- a/build-logic/base/src/main/kotlin/cc-tweaked.java-convention.gradle.kts +++ b/build-logic/base/src/main/kotlin/cc-tweaked.java-convention.gradle.kts @@ -3,120 +3,103 @@ // SPDX-License-Identifier: MPL-2.0 import cc.tweaked.gradle.CCTweakedExtension -import cc.tweaked.gradle.CCTweakedPlugin -import com.diffplug.gradle.spotless.FormatExtension -import com.diffplug.spotless.LineEnding +import cc.tweaked.gradle.CCTweakedJavaVersions +import cc.tweaked.gradle.JUnitExt import net.ltgt.gradle.errorprone.CheckSeverity import net.ltgt.gradle.errorprone.errorprone -import java.nio.charset.StandardCharsets +import org.gradle.plugins.ide.idea.model.IdeaModel +import org.jetbrains.gradle.ext.IdeaExtPlugin +import org.jetbrains.gradle.ext.runConfigurations +import org.jetbrains.gradle.ext.settings +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin plugins { `java-library` - idea jacoco - checkstyle - id("com.diffplug.spotless") + id("cc-tweaked.base-convention") id("net.ltgt.errorprone") // Required for cross-project dependencies in Fabric id("net.fabricmc.fabric-loom-companion") } -val modVersion = extra["modVersion"] as String +val cct = project.extensions.getByType() + +// Extend the [IdeaExtPlugin] plugin's `runConfiguration` container to also support [JUnitExt]. +project.plugins.withType(IdeaExtPlugin::class.java) { + val ideaModel = project.extensions.findByName("idea") as IdeaModel? ?: return@withType + val ideaProject = ideaModel.project ?: return@withType + + ideaProject.settings.runConfigurations { + registerFactory(JUnitExt::class.java) { name -> project.objects.newInstance(JUnitExt::class.java, name) } + } +} val libs = project.extensions.getByType().named("libs") -group = "cc.tweaked" -version = modVersion -base.archivesName.set("cc-tweaked-${libs.findVersion("minecraft").get()}-${project.name}") - java { - toolchain { languageVersion = CCTweakedPlugin.JDK_VERSION } - sourceCompatibility = CCTweakedPlugin.JAVA_VERSION - targetCompatibility = CCTweakedPlugin.JAVA_VERSION + toolchain { languageVersion = CCTweakedJavaVersions.JDK_VERSION } + sourceCompatibility = CCTweakedJavaVersions.JAVA_VERSION + targetCompatibility = CCTweakedJavaVersions.JAVA_VERSION withSourcesJar() } -repositories { - mavenCentral() - - val mainMaven = maven("https://maven.squiddev.cc/mirror") { - name = "SquidDev" - } - - exclusiveContent { - forRepositories(mainMaven) - filter { - includeGroup("cc.tweaked") - // Things we mirror - includeGroup("com.simibubi.create") - includeGroup("commoble.morered") - includeGroup("dev.architectury") - includeGroup("dev.emi") - includeGroup("maven.modrinth") - includeGroup("me.shedaniel.cloth") - includeGroup("me.shedaniel") - includeGroup("mezz.jei") - includeModule("com.terraformersmc", "modmenu") - includeModule("me.lucko", "fabric-permissions-api") - } - } -} - dependencies { - checkstyle(libs.findLibrary("checkstyle").get()) - - constraints { - checkstyle("org.codehaus.plexus:plexus-container-default:2.1.1") { - because("2.1.0 depends on deprecated Google collections module") - } - } - errorprone(libs.findLibrary("errorProne-core").get()) errorprone(libs.findLibrary("nullAway").get()) } -// Configure default JavaCompile tasks with our arguments. -sourceSets.all { - tasks.named(compileJavaTaskName, JavaCompile::class.java) { - // Explicitly set release, as that limits the APIs we can use to the right version of Java. - options.release = CCTweakedPlugin.JAVA_TARGET.asInt() +// Explicitly set the Kotlin target version to match our release/target version, not the JVM toolchain version. +project.plugins.withType(KotlinBasePlugin::class.java) { + val kotlin = project.extensions.getByType() + kotlin.compilerOptions.jvmTarget = CCTweakedJavaVersions.KOTLIN_TARGET +} - options.compilerArgs.addAll( - listOf( - "-Xlint", - // Processing just gives us "No processor claimed any of these annotations", so skip that! - "-Xlint:-processing", - // We violate this pattern too often for it to be a helpful warning. Something to improve one day! - "-Xlint:-this-escape", - ), +// Configure JavaCompile tasks with our default settings. +tasks.withType(JavaCompile::class.java).configureEach { + options.encoding = "UTF-8" + + // Explicitly set release, as that limits the APIs we can use to the right version of Java. + options.release.set(CCTweakedJavaVersions.JAVA_TARGET.asInt()) + + options.compilerArgs.addAll( + listOf( + "-Xlint", + // Processing just gives us "No processor claimed any of these annotations", so skip that! + "-Xlint:-processing", + // We violate this pattern too often for it to be a helpful warning. Something to improve one day! + "-Xlint:-this-escape", + ), + ) + + options.errorprone { + check("MissingOverride", CheckSeverity.ERROR) + check("InvalidBlockTag", CheckSeverity.OFF) // Broken by @cc.xyz + check("InlineMeSuggester", CheckSeverity.OFF) // Minecraft uses @Deprecated liberally + // Too many false positives right now. Maybe we need an indirection for it later on. + check("AssignmentExpression", CheckSeverity.OFF) // I'm a bad person. + check("ReferenceEquality", CheckSeverity.OFF) + check("EnumOrdinal", CheckSeverity.OFF) // For now. We could replace most of these with EnumMap. + check("OperatorPrecedence", CheckSeverity.OFF) // For now. + check("NonOverridingEquals", CheckSeverity.OFF) // Peripheral.equals makes this hard to avoid + // The lint isn't wrong, but we don't control the number of threads used by MC, so thread priority is a reasonable + // option. + check("ThreadPriorityCheck", CheckSeverity.OFF) + check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty + option("UnusedMethod:ExemptingMethodAnnotations", "dan200.computercraft.api.lua.LuaFunction") + + check("NullAway", CheckSeverity.ERROR) + option( + "NullAway:AnnotatedPackages", + listOf("dan200.computercraft", "cc.tweaked", "net.fabricmc.fabric.api").joinToString(","), ) + option("NullAway:ExcludedFieldAnnotations", listOf("org.spongepowered.asm.mixin.Shadow").joinToString(",")) + option("NullAway:CastToNonNullMethod", "dan200.computercraft.core.util.Nullability.assertNonNull") + option("NullAway:CheckOptionalEmptiness") + option("NullAway:AcknowledgeRestrictiveAnnotations") - options.errorprone { - check("InvalidBlockTag", CheckSeverity.OFF) // Broken by @cc.xyz - check("InlineMeSuggester", CheckSeverity.OFF) // Minecraft uses @Deprecated liberally - // Too many false positives right now. Maybe we need an indirection for it later on. - check("AssignmentExpression", CheckSeverity.OFF) // I'm a bad person. - check("ReferenceEquality", CheckSeverity.OFF) - check("EnumOrdinal", CheckSeverity.OFF) // For now. We could replace most of these with EnumMap. - check("OperatorPrecedence", CheckSeverity.OFF) // For now. - check("NonOverridingEquals", CheckSeverity.OFF) // Peripheral.equals makes this hard to avoid - check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty - check("InvalidInlineTag", CheckSeverity.OFF) // Triggered by @snippet. Can be removed on Java 21. - option("UnusedMethod:ExemptingMethodAnnotations", "dan200.computercraft.api.lua.LuaFunction") - - check("NullAway", CheckSeverity.ERROR) - option( - "NullAway:AnnotatedPackages", - listOf("dan200.computercraft", "cc.tweaked", "net.fabricmc.fabric.api").joinToString(","), - ) - option("NullAway:ExcludedFieldAnnotations", listOf("org.spongepowered.asm.mixin.Shadow").joinToString(",")) - option("NullAway:CastToNonNullMethod", "dan200.computercraft.core.util.Nullability.assertNonNull") - option("NullAway:CheckOptionalEmptiness") - option("NullAway:AcknowledgeRestrictiveAnnotations") - - excludedPaths = ".*/jmh_generated/.*" - } + excludedPaths.set(".*/jmh_generated/.*") } } @@ -126,22 +109,11 @@ tasks.compileTestJava { } } -tasks.withType(JavaCompile::class.java).configureEach { - options.encoding = "UTF-8" -} - tasks.processResources { exclude("**/*.license") exclude(".cache") } -tasks.withType(AbstractArchiveTask::class.java).configureEach { - isPreserveFileTimestamps = false - isReproducibleFileOrder = true - filePermissions {} - dirPermissions {} -} - tasks.jar { manifest { attributes( @@ -149,17 +121,23 @@ tasks.jar { "Specification-Vendor" to "SquidDev", "Specification-Version" to "1", "Implementation-Title" to "cctweaked-${project.name}", - "Implementation-Version" to modVersion, + "Implementation-Version" to project.version as String, "Implementation-Vendor" to "SquidDev", ) } + + from(cct.embeddedProjectClassesAndResources) +} + +tasks.named("sourcesJar") { + from(cct.embeddedProjectSources) } tasks.javadoc { options { val stdOptions = this as StandardJavadocDocletOptions stdOptions.addBooleanOption("Xdoclint:all,-missing", true) - stdOptions.links("https://docs.oracle.com/en/java/javase/${CCTweakedPlugin.JAVA_TARGET.asInt()}/docs/api/") + stdOptions.links("https://docs.oracle.com/en/java/javase/${CCTweakedJavaVersions.JAVA_TARGET.asInt()}/docs/api/") } } @@ -175,53 +153,7 @@ tasks.test { tasks.withType(JacocoReport::class.java).configureEach { reports.xml.required = true reports.html.required = true -} -project.plugins.withType(CCTweakedPlugin::class.java) { - // Set up jacoco to read from /all/ our source directories. - val cct = project.extensions.getByType() - project.tasks.named("jacocoTestReport", JacocoReport::class.java) { - for (ref in cct.sourceSets.get()) sourceDirectories.from(ref.allSource.sourceDirectories) - } -} - -tasks.register("checkstyle") { - description = "Run Checkstyle on all sources" - group = LifecycleBasePlugin.VERIFICATION_GROUP - dependsOn(tasks.withType(Checkstyle::class.java)) -} - -spotless { - encoding = StandardCharsets.UTF_8 - lineEndings = LineEnding.UNIX - - fun FormatExtension.defaults() { - endWithNewline() - trimTrailingWhitespace() - leadingTabsToSpaces(4) - } - - java { - defaults() - importOrder("", "javax|java", "\\#") - removeUnusedImports() - } - - kotlinGradle { - defaults() - ktlint() - } - - kotlin { - defaults() - ktlint() - } -} - -idea.module { - excludeDirs.addAll(project.files("run", "out", "logs").files) - - // Force Gradle to write to inherit the output directory from the parent, instead of writing to out/xxx/classes. - // This is required for Loom, and we patch Forge's run configurations to work there. - inheritOutputDirs = true + sourceDirectories.from(cct.embeddedProjectSources) + classDirectories.from(cct.embeddedProjectClasses) } diff --git a/build-logic/base/src/main/kotlin/cc-tweaked.mod-publishing.gradle.kts b/build-logic/base/src/main/kotlin/cc-tweaked.mod-publishing.gradle.kts deleted file mode 100644 index ed1b57b65..000000000 --- a/build-logic/base/src/main/kotlin/cc-tweaked.mod-publishing.gradle.kts +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -import cc.tweaked.gradle.setProvider - -plugins { - id("com.modrinth.minotaur") - id("cc-tweaked.publishing") -} - -abstract class ModPublishingExtension { - abstract val output: Property - - init { - output.finalizeValueOnRead() - } -} - -val modPublishing = project.extensions.create("modPublishing", ModPublishingExtension::class.java) - -val isUnstable = extra["isUnstable"] == "true" -val modVersion = extra["modVersion"] as String -val mcVersion = project.extensions.getByType().named("libs") - .findVersion("minecraft").get().toString() - -modrinth { - token = findProperty("modrinthApiKey") as String? ?: "" - projectId = "gu7yAYhd" - versionNumber = modVersion - versionName = modVersion - versionType = if (isUnstable) "alpha" else "release" - uploadFile.setProvider(modPublishing.output) - gameVersions.add(mcVersion) - changelog = "Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion)." - - syncBodyFrom = provider { rootProject.file("doc/mod-page.md").readText() } -} - -tasks.publish { dependsOn(tasks.modrinth) } diff --git a/build-logic/base/src/main/kotlin/cc-tweaked.mod.gradle.kts b/build-logic/base/src/main/kotlin/cc-tweaked.mod.gradle.kts index d119451df..2dc9ed9a2 100644 --- a/build-logic/base/src/main/kotlin/cc-tweaked.mod.gradle.kts +++ b/build-logic/base/src/main/kotlin/cc-tweaked.mod.gradle.kts @@ -8,10 +8,7 @@ * See notes in [cc.tweaked.gradle.MinecraftConfigurations] for the general design behind these cursed ideas. */ -import cc.tweaked.gradle.CCTweakedPlugin import cc.tweaked.gradle.MinecraftConfigurations -import cc.tweaked.gradle.clientClasses -import cc.tweaked.gradle.commonClasses plugins { kotlin("jvm") @@ -35,30 +32,3 @@ configurations.consumable("samplesElement") { type = ArtifactTypeDefinition.DIRECTORY_TYPE } } - -// Make sure our examples compile. -tasks.check { dependsOn(tasks.named("compileExamplesJava")) } - -// Similar to java-test-fixtures, but tries to avoid putting the obfuscated jar on the classpath. - -val testFixtures = sourceSets.create("testFixtures") { - compileClasspath += main.compileClasspath + client.compileClasspath -} - -java.registerFeature("testFixtures") { - usingSourceSet(testFixtures) - disablePublication() -} - -dependencies { - val libs = project.extensions.getByType().named("libs") - add(testFixtures.apiConfigurationName, libs.findBundle("test").get()) - // Consumers of this project already have the common and client classes on the classpath, so it's fine for these - // to be compile-only. - add(testFixtures.compileOnlyApiConfigurationName, commonClasses(project())) - add(testFixtures.compileOnlyApiConfigurationName, clientClasses(project())) - - testImplementation(testFixtures(project())) -} - -kotlin.compilerOptions.jvmTarget = CCTweakedPlugin.KOTLIN_TARGET diff --git a/build-logic/base/src/main/kotlin/cc-tweaked.published-mod.gradle.kts b/build-logic/base/src/main/kotlin/cc-tweaked.published-mod.gradle.kts new file mode 100644 index 000000000..a11c391e3 --- /dev/null +++ b/build-logic/base/src/main/kotlin/cc-tweaked.published-mod.gradle.kts @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +import cc.tweaked.gradle.ExternalProjectArtifacts +import cc.tweaked.gradle.VerifyJavaCompile +import cc.tweaked.gradle.setProvider +import net.ltgt.gradle.errorprone.CheckSeverity +import net.ltgt.gradle.errorprone.errorprone + +plugins { + id("com.modrinth.minotaur") + id("cc-tweaked.java-convention") + id("cc-tweaked.publishing") +} + +// Create a new "minecraftEmbeddedProject" configuration that the main embeddedProject extends from, which just contains +// Minecraft specific classes. We use this to recompile all Minecraft-facing code. +val minecraftEmbeddedProject = configurations.dependencyScope("minecraftEmbeddedProject") +configurations.named("embeddedProjectResolved") { extendsFrom(minecraftEmbeddedProject) } +val minecraftEmbeddedProjectResolved = configurations.resolvable("minecraftEmbeddedProjectResolved") { + extendsFrom(minecraftEmbeddedProject) + isTransitive = false + ExternalProjectArtifacts.configure(this) +} + +abstract class ModPublishingExtension { + abstract val output: Property + + init { + output.finalizeValueOnRead() + } +} + +val modPublishing = project.extensions.create("modPublishing", ModPublishingExtension::class.java) + +val isUnstable = extra["isUnstable"] == "true" +val mcVersion = project.extensions.getByType().named("libs") + .findVersion("minecraft").get().toString() + +val verifyClassesMatch = tasks.register("verifyClassesMatch") { + group = LifecycleBasePlugin.VERIFICATION_GROUP + description = "Verify compiling common sources against the loader produces the same classes" + + val clientSources = sourceSets["client"] + + destinationDirectory = layout.buildDirectory.dir(name) + classpath = clientSources.compileClasspath + options.annotationProcessorPath = clientSources.annotationProcessorPath + + options.errorprone { + isEnabled = true + // Check loader override in the common code. + check("MissingOverride", CheckSeverity.OFF) + check("LoaderOverride", CheckSeverity.DEFAULT) + check("MissingLoaderOverride", CheckSeverity.DEFAULT) + } + + source = minecraftEmbeddedProjectResolved.map { + (ExternalProjectArtifacts.sources(it) + sourceSets.main.get().allJava + clientSources.allJava).asFileTree + }.get() + referenceDirectories.from(minecraftEmbeddedProjectResolved.map(ExternalProjectArtifacts::classes)) + referenceDirectories.from(sourceSets.main.get().output.classesDirs) + referenceDirectories.from(clientSources.output.classesDirs) +} + +tasks.check { dependsOn(verifyClassesMatch) } + +modrinth { + val modVersion = project.version as String + token = findProperty("modrinthApiKey") as String? ?: "" + projectId = "gu7yAYhd" + versionNumber = modVersion + versionName = modVersion + versionType = if (isUnstable) "alpha" else "release" + uploadFile.setProvider(modPublishing.output) + gameVersions.add(mcVersion) + changelog = + "Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion)." + + syncBodyFrom = provider { rootProject.file("doc/mod-page.md").readText() } +} + +tasks.publish { dependsOn(tasks.modrinth) } diff --git a/build-logic/base/src/main/kotlin/cc-tweaked.vanilla.gradle.kts b/build-logic/base/src/main/kotlin/cc-tweaked.vanilla.gradle.kts index 785ef5e99..41e515ab3 100644 --- a/build-logic/base/src/main/kotlin/cc-tweaked.vanilla.gradle.kts +++ b/build-logic/base/src/main/kotlin/cc-tweaked.vanilla.gradle.kts @@ -5,7 +5,6 @@ /** Default configuration for non-modloader-specific Minecraft projects. */ import cc.tweaked.gradle.CCTweakedExtension -import cc.tweaked.gradle.CCTweakedPlugin import cc.tweaked.gradle.MinecraftConfigurations plugins { @@ -13,8 +12,6 @@ plugins { id("cc.tweaked.vanilla-extract") } -plugins.apply(CCTweakedPlugin::class.java) - val libs = project.extensions.getByType().named("libs") minecraft { @@ -27,13 +24,13 @@ minecraft { unpick(libs.findLibrary("yarn").get()) } +MinecraftConfigurations.setupBasic(project) + +extensions.configure(CCTweakedExtension::class.java) { + linters(minecraft = true) +} + dependencies { // Depend on error prone annotations to silence a lot of compile warnings. compileOnly(libs.findLibrary("errorProne.annotations").get()) } - -MinecraftConfigurations.setupBasic(project) - -extensions.configure(CCTweakedExtension::class.java) { - linters(minecraft = true, loader = null) -} diff --git a/build-logic/base/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt b/build-logic/base/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt index de630b28d..3a5438117 100644 --- a/build-logic/base/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt +++ b/build-logic/base/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt @@ -10,21 +10,15 @@ import org.gradle.api.GradleException import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.file.FileCollection import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.provider.Provider -import org.gradle.api.provider.SetProperty -import org.gradle.api.tasks.SourceSet -import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.compile.JavaCompile -import org.gradle.api.tasks.javadoc.Javadoc import org.gradle.language.base.plugins.LifecycleBasePlugin -import org.gradle.language.jvm.tasks.ProcessResources import org.gradle.process.JavaForkOptions import org.gradle.testing.jacoco.plugins.JacocoPluginExtension import org.gradle.testing.jacoco.plugins.JacocoTaskExtension import org.gradle.testing.jacoco.tasks.JacocoReport -import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.io.File import java.io.IOException import java.net.URI @@ -54,105 +48,34 @@ abstract class CCTweakedExtension(private val project: Project) { .sortedWith(String.CASE_INSENSITIVE_ORDER) } - /** - * References to other sources - */ - val sourceDirectories: SetProperty = project.objects.setProperty(SourceSetReference::class.java) - - /** All source sets referenced by this project. */ - val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } } - - init { - sourceDirectories.finalizeValueOnRead() - project.afterEvaluate { sourceDirectories.disallowChanges() } + private val embeddedProjects = project.configurations.dependencyScope("embeddedProject") + private val embeddedProjectsResolved = project.configurations.resolvable("embeddedProjectResolved") { + extendsFrom(embeddedProjects) + isTransitive = false + ExternalProjectArtifacts.configure(this) } - /** - * Mark this project as consuming another project. Its [sourceDirectories] are added, allowing easier configuration - * of run configurations and other tasks which consume sources/classes. - */ - fun externalSources(project: Project) { - val otherCct = project.extensions.getByType(CCTweakedExtension::class.java) - for (sourceSet in otherCct.sourceDirectories.get()) { - sourceDirectories.add(SourceSetReference(sourceSet.sourceSet, classes = sourceSet.classes, external = true)) - } - } + val embeddedProjectClasses: Provider = embeddedProjectsResolved.map(ExternalProjectArtifacts::classes) + val embeddedProjectClassesAndResources: Provider = + embeddedProjectsResolved.map(ExternalProjectArtifacts::classesAndResources) + val embeddedProjectSources: Provider = embeddedProjectsResolved.map(ExternalProjectArtifacts::sources) /** - * Add a dependency on another project such that its sources and compiles are processed with this one. - * - * This is used when importing a common library into a loader-specific one, as we want to compile sources using - * the loader-specific sources. + * Enable our custom linters on this project. */ - fun inlineProject(path: String) { - val otherProject = project.evaluationDependsOn(path) - val otherJava = otherProject.extensions.getByType(JavaPluginExtension::class.java) - val main = otherJava.sourceSets.getByName("main") - val client = otherJava.sourceSets.getByName("client") - - // Pull in sources from the other project. - extendSourceSet(otherProject, main) - extendSourceSet(otherProject, client) - for (sourceSet in listOf(MinecraftConfigurations.DATAGEN, MinecraftConfigurations.EXAMPLES, MinecraftConfigurations.TEST_MOD, "testFixtures")) { - otherJava.sourceSets.findByName(sourceSet)?.let { extendSourceSet(otherProject, it) } - } - - // The extra source-processing tasks should include these files too. - project.tasks.named(main.javadocTaskName, Javadoc::class.java) { source(main.allJava, client.allJava) } - project.tasks.named(main.sourcesJarTaskName, Jar::class.java) { from(main.allSource, client.allSource) } - sourceDirectories.addAll(SourceSetReference.inline(main), SourceSetReference.inline(client)) - } - - /** - * Extend a source set with files from another project. - * - * This actually extends the original compile tasks, as extending the source sets does not play well with IDEs. - */ - private fun extendSourceSet(otherProject: Project, sourceSet: SourceSet) { - project.tasks.named(sourceSet.compileJavaTaskName, JavaCompile::class.java) { - dependsOn(otherProject.tasks.named(sourceSet.compileJavaTaskName)) // Avoid duplicate compile errors - source(sourceSet.allJava) - } - - project.tasks.named(sourceSet.processResourcesTaskName, ProcessResources::class.java) { - from(sourceSet.resources) - } - - // Also try to depend on Kotlin if it exists - val kotlin = otherProject.extensions.findByType(KotlinProjectExtension::class.java) - if (kotlin != null) { - val compileKotlin = sourceSet.getCompileTaskName("kotlin") - project.tasks.named(compileKotlin, KotlinCompile::class.java) { - dependsOn(otherProject.tasks.named(compileKotlin)) - source(kotlin.sourceSets.getByName(sourceSet.name).kotlin) - } - } - - // If we're doing an IDE sync, add a fake dependency to ensure it's on the classpath. - if (isIdeSync) project.dependencies.add(sourceSet.apiConfigurationName, sourceSet.output) - } - - fun linters(@Suppress("UNUSED_PARAMETER") vararg unused: UseNamedArgs, minecraft: Boolean, loader: String?) { + fun linters(@Suppress("UNUSED_PARAMETER") vararg unused: UseNamedArgs, minecraft: Boolean) { val java = project.extensions.getByType(JavaPluginExtension::class.java) - val sourceSets = java.sourceSets - project.dependencies.run { add("errorprone", project(mapOf("path" to ":lints"))) } - sourceSets.all { - val name = name - project.tasks.named(compileJavaTaskName, JavaCompile::class.java) { - options.errorprone { - // Only the main source set should run the side checker - check("SideChecker", if (minecraft && name == "main") CheckSeverity.DEFAULT else CheckSeverity.OFF) + project.dependencies.run { add("errorprone", project(":lints")) } - // The MissingLoaderOverride check superseds the MissingOverride one, so disable that. - if (loader != null) { - check("MissingOverride", CheckSeverity.OFF) - option("ModLoader", loader) - } else { - check("LoaderOverride", CheckSeverity.OFF) - check("MissingLoaderOverride", CheckSeverity.OFF) - } - } + project.tasks.withType(JavaCompile::class.java).configureEach { + options.errorprone { + // Only the main source set should run the side checker + check("SideChecker", if (minecraft && name == "compileJava") CheckSeverity.DEFAULT else CheckSeverity.OFF) + + // If we have a custom loader, then we disable MissingOverride and enable the other two override checks. + check("LoaderOverride", CheckSeverity.OFF) + check("MissingLoaderOverride", CheckSeverity.OFF) } } } @@ -179,12 +102,6 @@ abstract class CCTweakedExtension(private val project: Project) { description = "Generates code coverage report for the ${task.name} task." executionData(task.get()) - - // Don't want to use sourceSets(...) here as we don't use all class directories. - for (ref in this@CCTweakedExtension.sourceDirectories.get()) { - sourceDirectories.from(ref.sourceSet.allSource.sourceDirectories) - if (ref.classes) classDirectories.from(ref.sourceSet.output) - } } } @@ -223,7 +140,7 @@ abstract class CCTweakedExtension(private val project: Project) { ).resolve().single() } - private fun gitProvider(default: T, command: List, process: (String) -> T): Provider { + private fun gitProvider(default: T, command: List, process: (String) -> T): Provider { val baseResult = project.providers.exec { commandLine = listOf("git", "-C", project.rootDir.absolutePath) + command } @@ -247,8 +164,5 @@ abstract class CCTweakedExtension(private val project: Project) { private val IGNORED_USERS = setOf( "GitHub", "Daniel Ratcliffe", "NotSquidDev", "Weblate", ) - - private val isIdeSync: Boolean - get() = java.lang.Boolean.parseBoolean(System.getProperty("idea.sync.active", "false")) } } diff --git a/build-logic/base/src/main/kotlin/cc/tweaked/gradle/CCTweakedJavaVersions.kt b/build-logic/base/src/main/kotlin/cc/tweaked/gradle/CCTweakedJavaVersions.kt new file mode 100644 index 000000000..c76839d43 --- /dev/null +++ b/build-logic/base/src/main/kotlin/cc/tweaked/gradle/CCTweakedJavaVersions.kt @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package cc.tweaked.gradle + +import org.gradle.api.JavaVersion +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +object CCTweakedJavaVersions { + /** + * The version we run with. We use Java 25 here, as our Gradle build requires that. + */ + val JDK_VERSION = JavaLanguageVersion.of(25) + + /** + * The Java version we target. Should be the same as what Minecraft uses. + */ + val JAVA_TARGET = JavaLanguageVersion.of(17) + + val JAVA_VERSION = JavaVersion.toVersion(JAVA_TARGET.asInt()) + val KOTLIN_TARGET = JvmTarget.fromTarget(JAVA_TARGET.toString()) +} diff --git a/build-logic/base/src/main/kotlin/cc/tweaked/gradle/CCTweakedPlugin.kt b/build-logic/base/src/main/kotlin/cc/tweaked/gradle/CCTweakedPlugin.kt deleted file mode 100644 index fd5ff0424..000000000 --- a/build-logic/base/src/main/kotlin/cc/tweaked/gradle/CCTweakedPlugin.kt +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package cc.tweaked.gradle - -import org.gradle.api.JavaVersion -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.plugins.JavaPlugin -import org.gradle.api.plugins.JavaPluginExtension -import org.gradle.jvm.toolchain.JavaLanguageVersion -import org.gradle.plugins.ide.idea.model.IdeaModel -import org.jetbrains.gradle.ext.IdeaExtPlugin -import org.jetbrains.gradle.ext.runConfigurations -import org.jetbrains.gradle.ext.settings -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - -/** - * Configures projects to match a shared configuration. - */ -class CCTweakedPlugin : Plugin { - override fun apply(project: Project) { - val cct = project.extensions.create("cct", CCTweakedExtension::class.java) - - project.plugins.withType(JavaPlugin::class.java) { - val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets - cct.sourceDirectories.add(SourceSetReference.internal(sourceSets.getByName("main"))) - } - - project.plugins.withType(IdeaExtPlugin::class.java) { extendIdea(project) } - } - - /** - * Extend the [IdeaExtPlugin] plugin's `runConfiguration` container to also support [JUnitExt]. - */ - private fun extendIdea(project: Project) { - val ideaModel = project.extensions.findByName("idea") as IdeaModel? ?: return - val ideaProject = ideaModel.project ?: return - - ideaProject.settings.runConfigurations { - registerFactory(JUnitExt::class.java) { name -> project.objects.newInstance(JUnitExt::class.java, name) } - } - } - - companion object { - /** - * The version we run with. We use Java 25 here, as our Gradle build requires that. - */ - val JDK_VERSION = JavaLanguageVersion.of(25) - - /** - * The Java version we target. Should be the same as what Minecraft uses. - */ - val JAVA_TARGET = JavaLanguageVersion.of(17) - - val JAVA_VERSION = JavaVersion.toVersion(JAVA_TARGET.asInt()) - val KOTLIN_TARGET = JvmTarget.fromTarget(JAVA_TARGET.toString()) - } -} diff --git a/build-logic/base/src/main/kotlin/cc/tweaked/gradle/Extensions.kt b/build-logic/base/src/main/kotlin/cc/tweaked/gradle/Extensions.kt index 01a212824..d614a82a6 100644 --- a/build-logic/base/src/main/kotlin/cc/tweaked/gradle/Extensions.kt +++ b/build-logic/base/src/main/kotlin/cc/tweaked/gradle/Extensions.kt @@ -10,7 +10,6 @@ import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.JavaExec import org.gradle.process.BaseExecSpec -import org.gradle.process.JavaExecSpec import org.gradle.process.ProcessForkOptions /** @@ -124,38 +123,11 @@ class CloseScope : AutoCloseable { } /** Proxy method to avoid overload ambiguity. */ -fun Property.setProvider(provider: Provider) = set(provider) +fun Property.setProvider(provider: Provider) = set(provider) /** Short-cut method to get the absolute path of a [FileSystemLocation] provider. */ fun Provider.getAbsolutePath(): String = get().asFile.absolutePath -/** - * Get the version immediately after the provided version. - * - * For example, given "1.2.3", this will return "1.2.4". - */ -fun getNextVersion(version: String): String { - // Split a version like x.y.z-SNAPSHOT into x.y.z and -SNAPSHOT - val dashIndex = version.indexOf('-') - val mainVersion = if (dashIndex < 0) version else version.substring(0, dashIndex) - - // Find the last component in x.y.z and increment it. - val lastIndex = mainVersion.lastIndexOf('.') - if (lastIndex < 0) throw IllegalArgumentException("Cannot parse version format \"$version\"") - val lastVersion = try { - mainVersion.substring(lastIndex + 1).toInt() - } catch (e: NumberFormatException) { - throw IllegalArgumentException("Cannot parse version format \"$version\"", e) - } - - // Then append all components together. - val out = StringBuilder() - out.append(version, 0, lastIndex + 1) - out.append(lastVersion + 1) - if (dashIndex >= 0) out.append(version, dashIndex, version.length) - return out.toString() -} - /** * Capitalise the first letter of the string. * diff --git a/build-logic/base/src/main/kotlin/cc/tweaked/gradle/ExternalProjectArtifacts.kt b/build-logic/base/src/main/kotlin/cc/tweaked/gradle/ExternalProjectArtifacts.kt new file mode 100644 index 000000000..53e9ab4af --- /dev/null +++ b/build-logic/base/src/main/kotlin/cc/tweaked/gradle/ExternalProjectArtifacts.kt @@ -0,0 +1,53 @@ +package cc.tweaked.gradle + +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.component.ProjectComponentIdentifier +import org.gradle.api.attributes.* +import org.gradle.api.attributes.java.TargetJvmEnvironment +import org.gradle.api.file.FileCollection +import org.gradle.kotlin.dsl.named + +/** + * Support for extracting sources and build outputs from dependencies on external projects. + * + * See [org.gradle.testing.jacoco.plugins.JacocoReportAggregationPlugin] for the variant resolution logic this is based + * on. + */ +object ExternalProjectArtifacts { + /** Configure the attributes on a [Configuration] to use the standard dependency resolution rules. */ + fun configure(configuration: Configuration) { + configuration.attributes { + attribute(Category.CATEGORY_ATTRIBUTE, named(Category.LIBRARY)) + attribute(Usage.USAGE_ATTRIBUTE, named(Usage.JAVA_RUNTIME)) + attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, named(LibraryElements.JAR)) + attribute(Bundling.BUNDLING_ATTRIBUTE, named(Bundling.EXTERNAL)) + attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, named(TargetJvmEnvironment.STANDARD_JVM)) + } + } + + /** Get the classes directories from projects within a [Configuration]. */ + fun classes(configuration: Configuration): FileCollection = configuration.incoming.artifactView { + componentFilter { c -> c is ProjectComponentIdentifier } + attributes { attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, named(LibraryElements.CLASSES)) } + }.files + + /** Get the resources directories from projects within a [Configuration]. */ + fun resources(configuration: Configuration): FileCollection = configuration.incoming.artifactView { + componentFilter { c -> c is ProjectComponentIdentifier } + attributes { attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, named(LibraryElements.RESOURCES)) } + }.files + + /** Get the classes and resources directories from projects within a [Configuration]. */ + fun classesAndResources(configuration: Configuration): FileCollection = + classes(configuration) + resources(configuration) + + /** Get the sources directory from projects within a [Configuration]. */ + fun sources(configuration: Configuration): FileCollection = configuration.incoming.artifactView { + withVariantReselection() + componentFilter { c -> c is ProjectComponentIdentifier } + attributes { + attribute(Category.CATEGORY_ATTRIBUTE, named(Category.VERIFICATION)) + attribute(VerificationType.VERIFICATION_TYPE_ATTRIBUTE, named(VerificationType.MAIN_SOURCES)) + } + }.files +} diff --git a/build-logic/base/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt b/build-logic/base/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt index 86befec72..ba003cad1 100644 --- a/build-logic/base/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt +++ b/build-logic/base/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt @@ -4,11 +4,9 @@ package cc.tweaked.gradle -import cc.tweaked.vanillaextract.configurations.Capabilities import cc.tweaked.vanillaextract.configurations.MinecraftSetup import org.gradle.api.Project -import org.gradle.api.artifacts.ModuleDependency -import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.api.artifacts.type.ArtifactTypeDefinition import org.gradle.api.plugins.BasePlugin import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.tasks.SourceSet @@ -90,8 +88,24 @@ class MinecraftConfigurations private constructor(private val project: Project) private fun setupBasic() { val client = sourceSets["client"] - project.extensions.configure(CCTweakedExtension::class.java) { - sourceDirectories.add(SourceSetReference.internal(client)) + project.configurations.named("mainSourceElements") { + outgoing.artifacts(client.allSource.sourceDirectories.elements) { + type = ArtifactTypeDefinition.DIRECTORY_TYPE + } + } + + project.configurations.named(main.runtimeElementsConfigurationName) { + outgoing.variants.named("classes") { + for (classDir in client.output.classesDirs.files) { + artifact(classDir) { type = ArtifactTypeDefinition.JVM_CLASS_DIRECTORY } + } + } + + outgoing.variants.named("resources") { + artifact(project.tasks.named(client.processResourcesTaskName)) { + type = ArtifactTypeDefinition.JVM_RESOURCES_DIRECTORY + } + } } // Register a task to check there are no conflicts with the core project. @@ -108,8 +122,13 @@ class MinecraftConfigurations private constructor(private val project: Project) * Create a new configuration that pulls in the main and client classes from the mod. */ private fun createDerivedConfiguration(name: String) { - val client = sourceSets["client"] val sourceSet = sourceSets.create(name) + java.registerFeature(name) { + usingSourceSet(sourceSet) + disablePublication() + } + + val client = sourceSets["client"] sourceSet.compileClasspath += main.compileClasspath + client.compileClasspath sourceSet.runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath consistentWithMain(sourceSet) @@ -135,9 +154,3 @@ class MinecraftConfigurations private constructor(private val project: Project) } } } - -fun DependencyHandler.clientClasses(notation: Any): ModuleDependency = - Capabilities.clientClasses(create(notation) as ModuleDependency) - -fun DependencyHandler.commonClasses(notation: Any): ModuleDependency = - Capabilities.commonClasses(create(notation) as ModuleDependency) diff --git a/build-logic/base/src/main/kotlin/cc/tweaked/gradle/SourceSetReference.kt b/build-logic/base/src/main/kotlin/cc/tweaked/gradle/SourceSetReference.kt deleted file mode 100644 index b550a26b6..000000000 --- a/build-logic/base/src/main/kotlin/cc/tweaked/gradle/SourceSetReference.kt +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package cc.tweaked.gradle - -import org.gradle.api.tasks.SourceSet - -data class SourceSetReference( - val sourceSet: SourceSet, - val classes: Boolean, - val external: Boolean, -) { - companion object { - /** A source set in the current project. */ - fun internal(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = true, external = false) - - /** A source set from another project. */ - fun external(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = true, external = true) - - /** A source set which is inlined into the current project. */ - fun inline(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = false, external = false) - } -} diff --git a/build-logic/base/src/main/kotlin/cc/tweaked/gradle/VerifyJavaCompile.kt b/build-logic/base/src/main/kotlin/cc/tweaked/gradle/VerifyJavaCompile.kt new file mode 100644 index 000000000..599d7c4ca --- /dev/null +++ b/build-logic/base/src/main/kotlin/cc/tweaked/gradle/VerifyJavaCompile.kt @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package cc.tweaked.gradle + +import cc.tweaked.vanillaextract.core.util.MoreFiles +import org.gradle.api.GradleException +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.FileTree +import org.gradle.api.problems.* +import org.gradle.api.provider.SetProperty +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.work.InputChanges +import java.io.File +import javax.inject.Inject + +/** + * Compile Java sources, then verify the resulting classes are identical to another set of files. + * + * This is used to ensure our common sources compile cleanly against Forge/Fabric directly. + */ +abstract class VerifyJavaCompile : JavaCompile() { + /** + * The directories containing the classes to compare against. + */ + @get:InputFiles + abstract val referenceDirectories: ConfigurableFileCollection + + /** + * Files to exclude from verification. + */ + @get:Input + abstract val ignoredFiles: SetProperty + + @get:Inject + abstract val problems: Problems + + override fun compile(inputs: InputChanges) { + super.compile(inputs) + + val reporter = problems.reporter + + val referenceFiles = readFiles(reporter, referenceDirectories.asFileTree) + val outputFiles = readFiles(reporter, destinationDirectory.asFileTree) + val ignored = ignoredFiles.get().toMutableSet() + + val problems = mutableListOf() + + // Check all output files match + for ((path, outputFile) in outputFiles) { + val referenceFile = referenceFiles[path] + if (referenceFile == null) { + val problem = reporter.create(EXTRA_FILE) { + contextualLabel("'$path' is generated by this task, but is not in the reference classes") + fileLocation(outputFile.file.toString()) + } + if (ignored.remove(path)) { + reporter.report(problem) + } else { + problems.add(problem) + } + } else if (outputFile.hash != referenceFile.hash) { + val problem = reporter.create(MISMATCH) { + contextualLabel("'$path' has different file contents'") + fileLocation(outputFile.file.toString()) + fileLocation(referenceFile.file.toString()) + } + if (ignored.remove(path)) { + reporter.report(problem) + } else { + problems.add(problem) + } + } + } + + // Check we've got no additional reference files. + for ((path, referenceFile) in referenceFiles) { + if (outputFiles.containsKey(path)) continue + + val problem = reporter.create(MISSING_FILE) { + contextualLabel("'$path' is in the reference classes but not generated by this task") + fileLocation(referenceFile.file.toString()) + } + if (ignored.remove(path)) { + reporter.report(problem) + } else { + problems.add(problem) + } + } + + // Ensure there's no redundant ignored files. + if (ignored.isNotEmpty()) { + val problem = reporter.create(REDUNDANT_IGNORE) { + val files = ignored.sorted().joinToString(", ") + details("$files are marked as excluded but had no problems") + } + problems.add(problem) + } + + if (problems.isNotEmpty()) throw reporter.throwing(GradleException("Classes do not match"), problems) + } + + private fun readFiles(reporter: ProblemReporter, dir: FileTree): Map { + val output = mutableMapOf() + dir.visit { + if (isDirectory) return@visit + + val relativePath = path + val hash = MoreFiles.computeSha1(file.toPath()) + + if (output.putIfAbsent(relativePath, ClassFile(hash, file.absoluteFile)) != null) { + throw reporter.throwing(GradleException("Multiple directories contain $relativePath"), DUPLICATE_FILE) { + fileLocation(file.absolutePath) + } + } + } + + return output + } + + class ClassFile(val hash: String, val file: File) + + companion object { + private val GROUP: ProblemGroup = ProblemGroup.create("cc.tweaked.verify-java", "Verify Java") + private val DUPLICATE_FILE: ProblemId = ProblemId.create("duplicate-file", "Duplicate file", GROUP) + private val EXTRA_FILE: ProblemId = ProblemId.create("extra-file", "Additional file in output", GROUP) + private val MISSING_FILE: ProblemId = ProblemId.create("missing-file", "Additional file in reference", GROUP) + private val MISMATCH: ProblemId = ProblemId.create("mismatch", "Classes do not match", GROUP) + private val REDUNDANT_IGNORE: ProblemId = + ProblemId.create("redundant-ignore", "Ignored files entries without problems", GROUP) + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 6fb519c04..d35d1e661 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,11 +13,10 @@ plugins { alias(libs.plugins.gradleVersions) alias(libs.plugins.ideaExt) alias(libs.plugins.versionCatalogUpdate) - id("cc-tweaked") + id("cc-tweaked.base-convention") } val isUnstable = extra["isUnstable"] == "true" -val modVersion = extra["modVersion"] as String val mcVersion = project.extensions.getByType().named("libs") .findVersion("minecraft").get().toString() @@ -27,6 +26,7 @@ githubRelease { repo = "CC-Tweaked" targetCommitish = cct.gitBranch + val modVersion = project.version as String tagName = "v$mcVersion-$modVersion" releaseName = "[$mcVersion] $modVersion" body = provider { @@ -114,7 +114,7 @@ idea.project.settings.compiler.javac { .toMap() } -repositories() { +repositories { mavenCentral() } diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index f3c40377e..23e627359 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -36,7 +36,9 @@ SPDX-License-Identifier: MPL-2.0 - + + + diff --git a/gradle.properties b/gradle.properties index c43db076f..8848c4573 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,6 @@ kotlin.stdlib.default.dependency=false kotlin.jvm.target.validation.mode=error # Mod properties -modVersion=1.120.2 +version=1.120.2 isUnstable=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 564bb6834..bcb1f1a68 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -67,7 +67,7 @@ gradleVersions = "0.61.0" ideaExt = "1.3" illuaminate = "0.1.0-83-g1131f68" lwjgl = "3.4.3" -minotaur = "2.8.7" +minotaur = "2.9.0" modDevGradle = "2.0.141" nullAway = "0.14.0" shadow = "9.6.1" diff --git a/projects/common/build.gradle.kts b/projects/common/build.gradle.kts index 88989ad7c..845397910 100644 --- a/projects/common/build.gradle.kts +++ b/projects/common/build.gradle.kts @@ -2,9 +2,15 @@ // // SPDX-License-Identifier: MPL-2.0 -import cc.tweaked.gradle.* +import cc.tweaked.gradle.CCTweakedJavaVersions +import cc.tweaked.gradle.IlluaminateExec +import cc.tweaked.gradle.MergeTrees +import cc.tweaked.gradle.annotationProcessorEverywhere +import cc.tweaked.vanillaextract.configurations.Capabilities.clientClasses +import cc.tweaked.vanillaextract.configurations.Capabilities.commonClasses plugins { + `java-test-fixtures` id("cc-tweaked.vanilla") id("cc-tweaked.illuaminate") id("cc-tweaked.mod") @@ -22,6 +28,10 @@ configurations { register("cctJavadoc") } +sourceSets.testFixtures { + compileClasspath += sourceSets.main.get().compileClasspath + sourceSets.client.get().compileClasspath +} + repositories { maven("https://maven.minecraftforge.net/") { content { @@ -42,7 +52,10 @@ dependencies { annotationProcessorEverywhere(libs.autoService) testFixturesAnnotationProcessor(libs.autoService) + testFixturesApi(testFixtures(project(":core"))) + testImplementation(testFixtures(project(":core"))) + testImplementation(testFixtures(project())) testImplementation(libs.bundles.test) testRuntimeOnly(libs.bundles.testRuntime) @@ -54,8 +67,6 @@ dependencies { testModImplementation(testFixtures(project(":common"))) testModImplementation(libs.bundles.kotlin) - testFixturesImplementation(testFixtures(project(":core"))) - "cctJavadoc"(libs.cctJavadoc) } @@ -79,7 +90,7 @@ val luaJavadoc = tasks.register("luaJavadoc") { options.addStringOption("project-root", rootProject.file(".").absolutePath) options.noTimestamp(false) - javadocTool = javaToolchains.javadocToolFor { languageVersion = CCTweakedPlugin.JDK_VERSION } + javadocTool = javaToolchains.javadocToolFor { languageVersion = CCTweakedJavaVersions.JDK_VERSION } } val lintLua = tasks.register("lintLua") { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java index 3d9895f74..cb753e747 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java @@ -35,6 +35,7 @@ import java.util.*; * @cc.module commands * @cc.since 1.7 */ +@SuppressWarnings("MissingSummary") public class CommandAPI implements ILuaAPI { private static final Logger LOG = LoggerFactory.getLogger(CommandAPI.class); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java index 2720abbc2..f8691772a 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java @@ -258,14 +258,11 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba private boolean hasPeripheralUpgradeOnSide(ComputerSide side) { ITurtleUpgrade upgrade; switch (side) { - case RIGHT: - upgrade = getUpgrade(TurtleSide.RIGHT); - break; - case LEFT: - upgrade = getUpgrade(TurtleSide.LEFT); - break; - default: + case RIGHT -> upgrade = getUpgrade(TurtleSide.RIGHT); + case LEFT -> upgrade = getUpgrade(TurtleSide.LEFT); + default -> { return false; + } } return upgrade != null && upgrade.getType().isPeripheral(); } diff --git a/projects/core-api/build.gradle.kts b/projects/core-api/build.gradle.kts index 8f58e2d4a..6778380a1 100644 --- a/projects/core-api/build.gradle.kts +++ b/projects/core-api/build.gradle.kts @@ -5,7 +5,6 @@ plugins { id("cc-tweaked.java-convention") id("cc-tweaked.publishing") - id("cc-tweaked") } dependencies { @@ -15,4 +14,4 @@ dependencies { // Don't build Javadoc here. We build combined docs in the common-api project. tasks.javadoc { isEnabled = false } -cct.linters(minecraft = false, loader = null) +cct.linters(minecraft = false) diff --git a/projects/core/build.gradle.kts b/projects/core/build.gradle.kts index 4afebd1fd..c0ae5087b 100644 --- a/projects/core/build.gradle.kts +++ b/projects/core/build.gradle.kts @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: MPL-2.0 -import cc.tweaked.gradle.CCTweakedPlugin import cc.tweaked.gradle.getAbsolutePath plugins { @@ -13,11 +12,8 @@ plugins { id("cc-tweaked.java-convention") id("cc-tweaked.publishing") - id("cc-tweaked") } -val modVersion = extra["modVersion"] as String - dependencies { api(project(":core-api")) implementation(libs.cobalt) @@ -40,8 +36,6 @@ dependencies { testRuntimeOnly(libs.slf4j.simple) } -kotlin.compilerOptions.jvmTarget = CCTweakedPlugin.KOTLIN_TARGET - tasks.processResources { inputs.property("gitHash", cct.gitHash) @@ -54,14 +48,14 @@ tasks.test { } val checkChangelog = tasks.register("checkChangelog") { - version = modVersion + version = project.version as String whatsNew = file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md") changelog = file("src/main/resources/data/computercraft/lua/rom/help/changelog.md") } tasks.check { dependsOn(checkChangelog) } -cct.linters(minecraft = false, loader = null) +cct.linters(minecraft = false) // We configure the shadow jar to ship netty-codec and all its dependencies, relocating them under the // dan200.computercraft.core package. diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/OSAPI.java b/projects/core/src/main/java/dan200/computercraft/core/apis/OSAPI.java index c36f069bc..6640be901 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/OSAPI.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/OSAPI.java @@ -335,6 +335,7 @@ public class OSAPI implements ILuaAPI { * @cc.changed 1.82.0 Arguments are now case insensitive. */ @LuaFunction + @SuppressWarnings("InvalidParam") // Local vs Locale confusion public final int day(Optional locale) throws LuaException { return switch (locale.orElse("ingame").toLowerCase(Locale.ROOT)) { case "utc" -> getDayForCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC"))); @@ -374,6 +375,7 @@ public class OSAPI implements ILuaAPI { * } */ @LuaFunction + @SuppressWarnings("InvalidParam") // Local vs Locale confusion public final long epoch(Optional locale) throws LuaException { return switch (locale.orElse("ingame").toLowerCase(Locale.ROOT)) { case "utc" -> getEpochForCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC"))); // Get utc epoch diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/TermMethods.java b/projects/core/src/main/java/dan200/computercraft/core/apis/TermMethods.java index 209911ba3..e8b8e3e00 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/TermMethods.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/TermMethods.java @@ -32,16 +32,16 @@ public abstract class TermMethods { * Unlike functions like [`_G.write`] and [`print`], this does not wrap the text - it simply copies the * text to the current terminal line. * - * @param textA The text to write. + * @param text The text to write. * @throws LuaException (hidden) If the terminal cannot be found. */ @LuaFunction - public final void write(Coerced textA) throws LuaException { - var text = textA.value(); + public final void write(Coerced text) throws LuaException { + var textStr = text.value(); var terminal = getTerminal(); synchronized (terminal) { - terminal.write(text); - terminal.setCursorPos(terminal.getCursorX() + text.length(), terminal.getCursorY()); + terminal.write(textStr); + terminal.setCursorPos(terminal.getCursorX() + textStr.length(), terminal.getCursorY()); } } diff --git a/projects/core/src/main/java/dan200/computercraft/core/asm/Generator.java b/projects/core/src/main/java/dan200/computercraft/core/asm/Generator.java index 5a94800fd..8b5b92cef 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/asm/Generator.java +++ b/projects/core/src/main/java/dan200/computercraft/core/asm/Generator.java @@ -55,7 +55,7 @@ final class Generator { } } - static void addArgType(Map, ArgMethods> types, Class type, String name) throws ReflectiveOperationException { + private static void addArgType(Map, ArgMethods> types, Class type, String name) throws ReflectiveOperationException { types.put(type, ArgMethods.of(type, name)); } diff --git a/projects/core/src/main/java/dan200/computercraft/core/filesystem/FileSystem.java b/projects/core/src/main/java/dan200/computercraft/core/filesystem/FileSystem.java index 6fb31162f..c0a3adad8 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/filesystem/FileSystem.java +++ b/projects/core/src/main/java/dan200/computercraft/core/filesystem/FileSystem.java @@ -388,7 +388,6 @@ public class FileSystem { } } else if (part.isEmpty() || (part.startsWith(".") && manyDotsPattern.matcher(part).matches())) { // Skip empty paths, ".", or any other sequence of "[. ]+" (as this is also treated as "." on Windows). - continue; } else { // Anything else we add to the stack outputParts.addLast(part); diff --git a/projects/core/src/test/java/dan200/computercraft/core/filesystem/WritableFileMountTest.java b/projects/core/src/test/java/dan200/computercraft/core/filesystem/WritableFileMountTest.java index 712eac2fb..5129f8e2b 100644 --- a/projects/core/src/test/java/dan200/computercraft/core/filesystem/WritableFileMountTest.java +++ b/projects/core/src/test/java/dan200/computercraft/core/filesystem/WritableFileMountTest.java @@ -38,6 +38,7 @@ public class WritableFileMountTest implements WritableMountContract { @Override @Test @DisabledOnOs(OS.WINDOWS) // This fails on Windows, and I don't have a debugger to find out why. + @SuppressWarnings("JUnitMethodInvoked") public void Writing_uses_latest_file_size() throws IOException { WritableMountContract.super.Writing_uses_latest_file_size(); } diff --git a/projects/fabric-api/build.gradle.kts b/projects/fabric-api/build.gradle.kts index 4e60bae75..418abf57d 100644 --- a/projects/fabric-api/build.gradle.kts +++ b/projects/fabric-api/build.gradle.kts @@ -2,15 +2,20 @@ // // SPDX-License-Identifier: MPL-2.0 +import cc.tweaked.vanillaextract.configurations.Capabilities.clientClasses +import cc.tweaked.vanillaextract.configurations.Capabilities.commonClasses + plugins { id("cc-tweaked.fabric") id("cc-tweaked.publishing") } -cct.inlineProject(":common-api") - dependencies { api(project(":core-api")) + + compileOnly(commonClasses(project(":common-api"))) + clientApi(clientClasses(project(":common-api"))) + embeddedProject(project(":common-api")) } tasks.jar { diff --git a/projects/fabric/build.gradle.kts b/projects/fabric/build.gradle.kts index 8009e020b..8f5ebb432 100644 --- a/projects/fabric/build.gradle.kts +++ b/projects/fabric/build.gradle.kts @@ -3,21 +3,17 @@ // SPDX-License-Identifier: MPL-2.0 import cc.tweaked.gradle.* +import cc.tweaked.vanillaextract.configurations.Capabilities.clientClasses +import cc.tweaked.vanillaextract.configurations.Capabilities.commonClasses import net.fabricmc.loom.configuration.ide.RunConfigSettings +import net.ltgt.gradle.errorprone.CheckSeverity +import net.ltgt.gradle.errorprone.errorprone import java.util.* plugins { id("cc-tweaked.fabric") id("cc-tweaked.mod") - id("cc-tweaked.mod-publishing") -} - -val modVersion = extra["modVersion"] as String - -val allProjects = listOf(":core-api", ":core", ":fabric-api").map { evaluationDependsOn(it) } -cct { - inlineProject(":common") - allProjects.forEach { externalSources(it) } + id("cc-tweaked.published-mod") } fun addRemappedConfiguration(name: String) { @@ -99,30 +95,43 @@ dependencies { "includeImplementation"(libs.nightConfig.toml) // Pull in our other projects. See comments in MinecraftConfigurations on this nastiness. - "localImplementation"(project(":core")) + "localImplementation"(commonClasses(project(":common"))) + clientImplementation(clientClasses(project(":common"))) "localImplementation"(commonClasses(project(":fabric-api"))) clientImplementation(clientClasses(project(":fabric-api"))) annotationProcessorEverywhere(libs.autoService) - testModImplementation(testFixtures(project(":core"))) - testModImplementation(testFixtures(project(":fabric"))) + datagenImplementation(project(":common")) { capabilities { requireFeature("datagen") } } + examplesImplementation(project(":common")) { capabilities { requireFeature("examples") } } - testImplementation(libs.bundles.test) + testModImplementation(project(":common")) { capabilities { requireFeature("test-mod") } } + testModImplementation(testFixtures(project(":common"))) + + testImplementation(testFixtures(project(":common"))) testRuntimeOnly(libs.bundles.testRuntime) testRuntimeOnly(libs.fabric.junit) - testFixturesImplementation(testFixtures(project(":core"))) + embeddedProject(project(":core")) + embeddedProject(project(":core-api")) + minecraftEmbeddedProject(project(":common")) + minecraftEmbeddedProject(project(":common-api")) + minecraftEmbeddedProject(project(":fabric-api")) } loom { - accessWidenerPath = project(":common").file("src/main/resources/computercraft.accesswidener") + accessWidenerPath = file("../common/src/main/resources/computercraft.accesswidener") mods { register("computercraft") { // Configure sources when running via the IDE. Note these don't add build dependencies (hence why it's safe // to use common), only change how the launch.cfg file is generated. - cct.sourceDirectories.get().forEach { sourceSet(it.sourceSet) } + sourceSet("main", ":core-api") + sourceSet("main", ":core") + for (proj in listOf(":common-api", ":common", ":fabric-api", ":fabric")) { + sourceSet("main", proj) + sourceSet("client", proj) + } // Running via Gradle dependency(dependencies.project(":core").apply { isTransitive = false }) @@ -130,7 +139,13 @@ loom { register("cctest") { sourceSet(sourceSets.testMod.get()) - sourceSet(project(":common").sourceSets.testMod.get()) + sourceSet("testMod", ":common") + + // Loom's sourceSet doesn't add jars to the list, so also include the :common test mod jar. + val dep = dependencies.project(":common") + dep.capabilities { requireFeature("test-mod") } + dep.isTransitive = false + modFiles.from(configurations.detachedConfiguration(dep)) } register("examplemod") { @@ -173,7 +188,7 @@ loom { fun RunConfigSettings.configureForGameTest() { sourceSet = sourceSets.testMod.name - val testSources = project(":common").file("src/testMod/resources/data/cctest").absolutePath + val testSources = file("../common/src/testMod/resources/data/cctest").absolutePath systemProperties.put("cctest.sources", testSources) // Load cctest last, so it can override resources. This bypasses Fabric's shuffling of mods @@ -219,6 +234,8 @@ loom { } tasks.processResources { + val modVersion = project.version as String + inputs.property("modVersion", modVersion) var props = mapOf("version" to modVersion) @@ -226,16 +243,6 @@ tasks.processResources { filesMatching("fabric.mod.json") { expand(props) } } -tasks.jar { - for (source in cct.sourceDirectories.get()) { - if (source.classes && source.external) from(source.sourceSet.output) - } -} - -tasks.sourcesJar { - for (source in cct.sourceDirectories.get()) from(source.sourceSet.allSource) -} - val validateMixinNames = tasks.register("validateMixinNames") { source(sourceSets.main.get().output) source(sourceSets.client.get().output) @@ -245,6 +252,12 @@ tasks.check { dependsOn(validateMixinNames) } tasks.test { dependsOn(tasks.generateDLIConfig) } +tasks.verifyClassesMatch { + options.errorprone { + option("ModLoader", "fabric") + } +} + val runGametest = tasks.named("runGametest") { usesService(MinecraftRunnerService.get(gradle)) } diff --git a/projects/forge-api/build.gradle.kts b/projects/forge-api/build.gradle.kts index 9582164f8..45da48532 100644 --- a/projects/forge-api/build.gradle.kts +++ b/projects/forge-api/build.gradle.kts @@ -2,15 +2,20 @@ // // SPDX-License-Identifier: MPL-2.0 +import cc.tweaked.vanillaextract.configurations.Capabilities.clientClasses +import cc.tweaked.vanillaextract.configurations.Capabilities.commonClasses + plugins { id("cc-tweaked.forge") id("cc-tweaked.publishing") } -cct.inlineProject(":common-api") - dependencies { api(project(":core-api")) + + compileOnly(commonClasses(project(":common-api"))) + clientApi(clientClasses(project(":common-api"))) + embeddedProject(project(":common-api")) } tasks.javadoc { diff --git a/projects/forge/build.gradle.kts b/projects/forge/build.gradle.kts index fb583bff3..dbd7e29f1 100644 --- a/projects/forge/build.gradle.kts +++ b/projects/forge/build.gradle.kts @@ -3,46 +3,60 @@ // SPDX-License-Identifier: MPL-2.0 import cc.tweaked.gradle.* +import cc.tweaked.vanillaextract.configurations.Capabilities.clientClasses +import cc.tweaked.vanillaextract.configurations.Capabilities.commonClasses +import net.ltgt.gradle.errorprone.errorprone +import net.neoforged.moddevgradle.dsl.ModModel import net.neoforged.moddevgradle.dsl.RunModel plugins { id("cc-tweaked.forge") id("cc-tweaked.mod") - id("cc-tweaked.mod-publishing") + id("cc-tweaked.published-mod") } -val modVersion = extra["modVersion"] as String - -val allProjects = listOf(":core-api", ":core", ":forge-api").map { evaluationDependsOn(it) } -cct { - inlineProject(":common") - allProjects.forEach { externalSources(it) } +// This is needed for configuring source sets in our Forge runs. +for (project in listOf(":core-api", ":core", ":forge-api", ":common")) { + evaluationDependsOn(project) } legacyForge { - val computercraft = mods.register("computercraft") { - cct.sourceDirectories.get().forEach { - if (it.classes) sourceSet(it.sourceSet) + fun ModModel.sourceSet(name: String, project: String) { + modSourceSets.add(provider { project(project) }.flatMap { it.sourceSets.named(name) }) + } + + fun ModModel.addComputerCraft() { + sourceSet("main", ":core-api") + sourceSet("main", ":core") + for (proj in listOf(":common-api", ":common", ":forge-api", ":forge")) { + sourceSet("main", proj) + sourceSet("client", proj) } } + val computercraft = mods.register("computercraft") { + addComputerCraft() + } + val computercraftDatagen = mods.register("computercraftDatagen") { - cct.sourceDirectories.get().forEach { - if (it.classes) sourceSet(it.sourceSet) - } + addComputerCraft() sourceSet(sourceSets.datagen.get()) + sourceSet("datagen", ":common") } val testMod = mods.register("testMod") { sourceSet(sourceSets.testMod.get()) - sourceSet(sourceSets.testFixtures.get()) - sourceSet(project(":core").sourceSets["testFixtures"]) + sourceSet("testFixtures", ":common") + sourceSet("testFixtures", ":core") + sourceSet("testMod", ":common") } val exampleMod = mods.register("exampleMod") { sourceSet(sourceSets.examples.get()) + sourceSet("examples", ":common") } + val project = project // Silly shadowing to prevent "runs.project" referencing the deprecated member. runs { configureEach { ideName = "${name.capitalise()} (Forge)" @@ -71,8 +85,8 @@ legacyForge { "--output", layout.buildDirectory.dir(sourceSet.getTaskName("generateResources", null)) .getAbsolutePath(), - "--existing", project.project(":common").file("src/${sourceSet.name}/resources/").absolutePath, - "--existing", project.file("src/${sourceSet.name}/resources/").absolutePath, + "--existing", file("../common/src/${sourceSet.name}/resources/").absolutePath, + "--existing", file("src/${sourceSet.name}/resources/").absolutePath, ) } @@ -84,7 +98,7 @@ legacyForge { fun RunModel.configureForGameTest() { systemProperty( "cctest.sources", - project.project(":common").file("src/testMod/resources/data/cctest").absolutePath, + file("../common/src/testMod/resources/data/cctest").absolutePath, ) programArgument("--mixin.config=computercraft-gametest.mixins.json") @@ -162,7 +176,8 @@ dependencies { modCompileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false } // Depend on our other projects. - "localImplementation"(project(":core")) + "localImplementation"(commonClasses(project(":common"))) + clientImplementation(clientClasses(project(":common"))) "localImplementation"(commonClasses(project(":forge-api"))) clientImplementation(clientClasses(project(":forge-api"))) @@ -174,26 +189,31 @@ dependencies { additionalRuntimeClasspath(libs.netty.socks) { isTransitive = false } additionalRuntimeClasspath(libs.netty.proxy) { isTransitive = false } - testFixturesApi(libs.bundles.test) - testFixturesApi(libs.bundles.kotlin) + datagenImplementation(project(":common")) { capabilities { requireFeature("datagen") } } + examplesImplementation(project(":common")) { capabilities { requireFeature("examples") } } - testImplementation(testFixtures(project(":core"))) - testImplementation(libs.bundles.test) - testRuntimeOnly(libs.bundles.testRuntime) - - testModImplementation(testFixtures(project(":core"))) - testModImplementation(testFixtures(project(":forge"))) + testModImplementation(testFixtures(project(":common"))) + testModImplementation(project(":common")) { capabilities { requireFeature("test-mod") } } // Ensure our test fixture dependencies are on the classpath "testAdditionalRuntimeClasspath"(libs.bundles.kotlin) "testAdditionalRuntimeClasspath"(libs.bundles.test) - testFixturesImplementation(testFixtures(project(":core"))) + testImplementation(testFixtures(project(":common"))) + testRuntimeOnly(libs.bundles.testRuntime) + + embeddedProject(project(":core-api")) + // We explicitly *don't* include core, as that's bundled separately. + minecraftEmbeddedProject(project(":common")) + minecraftEmbeddedProject(project(":common-api")) + minecraftEmbeddedProject(project(":forge-api")) } // Compile tasks tasks.processResources { + val modVersion = project.version as String + inputs.property("modVersion", modVersion) inputs.property("forgeVersion", libs.versions.forge) @@ -206,20 +226,17 @@ tasks.processResources { } tasks.jar { - // Include all classes from other projects except core. - val coreSources = project(":core").sourceSets["main"] - for (source in cct.sourceDirectories.get()) { - if (source.classes && source.sourceSet != coreSources) from(source.sourceSet.output) - } - // Include core separately, along with the relocated netty classes. - from(zipTree(project(":core").tasks.named("shadowJar", AbstractArchiveTask::class).map { it.archiveFile })) { + val dep = dependencies.project(mapOf("path" to ":core", "configuration" to "shadowRuntimeElements")) + val conf = configurations.detachedConfiguration(dep) + from(zipTree(conf.elements.map { it.single() })) { exclude("META-INF/**") } } tasks.sourcesJar { - for (source in cct.sourceDirectories.get()) from(source.sourceSet.allSource) + val conf = configurations.detachedConfiguration(dependencies.project(":core")) + from(ExternalProjectArtifacts.sources(conf)) } // Check tasks @@ -228,6 +245,19 @@ tasks.test { systemProperty("cct.test-files", layout.buildDirectory.dir("tmp/testFiles").getAbsolutePath()) } +tasks.verifyClassesMatch { + options.errorprone { + option("ModLoader", "forge") + } + + // We have an extra adapater between SimpleJsonResourceReloadListener.prepare and SimplePreparableReloadListener.prepare. + // Not quite sure why, but doesn't cause issues. + ignoredFiles.add("dan200/computercraft/impl/UpgradeManager.class") + // Forge widens the inner classes of RenderType, so we end up with different InnerClasses attributes. Doesn't cause + // issues. + ignoredFiles.add("dan200/computercraft/client/render/RenderTypes\$Types.class") +} + val runGametest = tasks.named("runGametest") { usesService(MinecraftRunnerService.get(gradle)) } @@ -250,6 +280,3 @@ tasks.register("checkClient") { modPublishing { output = tasks.reobfJar } - -// TODO: Remove once https://github.com/modrinth/minotaur/pull/72 is merged. -modrinth { loaders = listOf("forge") } diff --git a/projects/lints/build.gradle.kts b/projects/lints/build.gradle.kts index 82b495841..2bac91d54 100644 --- a/projects/lints/build.gradle.kts +++ b/projects/lints/build.gradle.kts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MPL-2.0 -import cc.tweaked.gradle.CCTweakedPlugin +import cc.tweaked.gradle.CCTweakedJavaVersions plugins { kotlin("jvm") @@ -30,8 +30,6 @@ dependencies { testRuntimeOnly(libs.bundles.testRuntime) } -kotlin.compilerOptions.jvmTarget = CCTweakedPlugin.KOTLIN_TARGET - tasks.test { jvmArgs( "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", diff --git a/projects/lints/src/main/kotlin/cc/tweaked/linter/LoaderOverride.kt b/projects/lints/src/main/kotlin/cc/tweaked/linter/LoaderOverride.kt index d8edf0a62..5b619cec1 100644 --- a/projects/lints/src/main/kotlin/cc/tweaked/linter/LoaderOverride.kt +++ b/projects/lints/src/main/kotlin/cc/tweaked/linter/LoaderOverride.kt @@ -39,8 +39,7 @@ internal object LoaderOverrides { All methods which override a method or implement an interface should use @Override. When overriding loader-specific methods, you should use @ForgeOverride or @FabricOverride instead. """, - severity = BugPattern.SeverityLevel.WARNING, - tags = [BugPattern.StandardTags.STYLE], + severity = BugPattern.SeverityLevel.ERROR, ) class MissingLoaderOverride(flags: ErrorProneFlags? = null) : BugChecker(), BugChecker.MethodTreeMatcher { private val annotation = if (flags == null) null else LoaderOverrides.getAnnotation(flags) @@ -73,9 +72,8 @@ class MissingLoaderOverride(flags: ErrorProneFlags? = null) : BugChecker(), BugC } @BugPattern( - summary = "@ForgeOverride does not match a super method.", + summary = "@ForgeOverride/@FabricOverride does not match a super method.", severity = BugPattern.SeverityLevel.ERROR, - tags = [BugPattern.StandardTags.LIKELY_ERROR], ) class LoaderOverride(flags: ErrorProneFlags? = null) : BugChecker(), BugChecker.MethodTreeMatcher { private val annotation = if (flags == null) null else LoaderOverrides.getAnnotation(flags) diff --git a/projects/lints/src/main/kotlin/cc/tweaked/linter/SideChecker.kt b/projects/lints/src/main/kotlin/cc/tweaked/linter/SideChecker.kt index 36d145a24..6ce41af05 100644 --- a/projects/lints/src/main/kotlin/cc/tweaked/linter/SideChecker.kt +++ b/projects/lints/src/main/kotlin/cc/tweaked/linter/SideChecker.kt @@ -28,7 +28,6 @@ enum class Side { CLIENT, SERVER, BOTH } Errors on any reference to client-only classes, fields or identifiers. """, severity = BugPattern.SeverityLevel.ERROR, - tags = [BugPattern.StandardTags.LIKELY_ERROR], ) class SideChecker : BugChecker(), BugChecker.IdentifierTreeMatcher, BugChecker.MemberSelectTreeMatcher { override fun matchIdentifier(tree: IdentifierTree, state: VisitorState): Description { diff --git a/projects/lints/src/test/java/cc/tweaked/linter/TestForbiddenImport.java b/projects/lints/src/test/java/cc/tweaked/linter/TestForbiddenImport.java index 9e90dbcf9..4d794f470 100644 --- a/projects/lints/src/test/java/cc/tweaked/linter/TestForbiddenImport.java +++ b/projects/lints/src/test/java/cc/tweaked/linter/TestForbiddenImport.java @@ -14,12 +14,15 @@ public class TestForbiddenImport { @Test public void testForbiddenImport() { compilationHelper - .addSourceLines("Import.java", """ + .addSourceLines( + "Import.java", + """ // BUG: Diagnostic matches: X import org.jspecify.annotations.NonNull; - class X { - } - """) + + class X {} + """ + ) .expectErrorMessage("X", Predicates.containsPattern("Cannot import this symbol")) .doTest(); } @@ -27,12 +30,15 @@ public class TestForbiddenImport { @Test public void testForbiddenImportSuggestion() { compilationHelper - .addSourceLines("Import.java", """ + .addSourceLines( + "Import.java", + """ // BUG: Diagnostic matches: X import javax.annotation.concurrent.GuardedBy; - class X { - } - """) + + class X {} + """ + ) .expectErrorMessage("X", Predicates.containsPattern("Did you mean 'import com.google.errorprone.annotations.concurrent.GuardedBy;'")) .doTest(); } diff --git a/projects/lints/src/test/java/cc/tweaked/linter/TestSideChecker.java b/projects/lints/src/test/java/cc/tweaked/linter/TestSideChecker.java index 8ce5cb7cb..87cc2b893 100644 --- a/projects/lints/src/test/java/cc/tweaked/linter/TestSideChecker.java +++ b/projects/lints/src/test/java/cc/tweaked/linter/TestSideChecker.java @@ -14,11 +14,13 @@ public class TestSideChecker { @Test public void textExtendsAnnotated() { compilationHelper - .addSourceLines("UsesClientOnly.java", """ + .addSourceLines( + "UsesClientOnly.java", + """ // BUG: Diagnostic matches: X - class UsesClientOnly extends cc.tweaked.linter.AnnotatedClientClass { - } - """) + class UsesClientOnly extends cc.tweaked.linter.AnnotatedClientClass {} + """ + ) .expectErrorMessage("X", Predicates.containsPattern("Using client-only symbol in common source set")) .doTest(); } @@ -26,12 +28,15 @@ public class TestSideChecker { @Test public void testImportsAnnotated() { compilationHelper - .addSourceLines("UsesClientOnly.java", """ + .addSourceLines( + "UsesClientOnly.java", + """ import cc.tweaked.linter.AnnotatedClientClass; + // BUG: Diagnostic matches: X - class UsesClientOnly extends AnnotatedClientClass { - } - """) + class UsesClientOnly extends AnnotatedClientClass {} + """ + ) .expectErrorMessage("X", Predicates.containsPattern("Using client-only symbol in common source set")) .doTest(); } @@ -39,19 +44,23 @@ public class TestSideChecker { @Test public void textUsesAnnotated() { compilationHelper - .addSourceLines("UsesClientOnly.java", """ + .addSourceLines( + "UsesClientOnly.java", + """ import cc.tweaked.linter.AnnotatedClientClass; + class UsesClientOnly { - public void f() { - // BUG: Diagnostic matches: X - AnnotatedClientClass.doSomething(); - // BUG: Diagnostic matches: Y - System.out.println(AnnotatedClientClass.field); - // BUG: Diagnostic matches: Z - AnnotatedClientClass.field = 0; - } + public void f() { + // BUG: Diagnostic matches: X + AnnotatedClientClass.doSomething(); + // BUG: Diagnostic matches: Y + System.out.println(AnnotatedClientClass.field); + // BUG: Diagnostic matches: Z + AnnotatedClientClass.field = 0; + } } - """) + """ + ) .expectErrorMessage("X", Predicates.containsPattern("Using client-only symbol in common source set")) .expectErrorMessage("Y", Predicates.containsPattern("Using client-only symbol in common source set")) .expectErrorMessage("Z", Predicates.containsPattern("Using client-only symbol in common source set")) @@ -61,11 +70,13 @@ public class TestSideChecker { @Test public void testExtendsPackage() { compilationHelper - .addSourceLines("UsesClientOnly.java", """ + .addSourceLines( + "UsesClientOnly.java", + """ // BUG: Diagnostic matches: X - class UsesClientOnly extends cc.tweaked.linter.client.PackageClientClass { - } - """) + class UsesClientOnly extends cc.tweaked.linter.client.PackageClientClass {} + """ + ) .expectErrorMessage("X", Predicates.containsPattern("Using client-only symbol in common source set")) .doTest(); } diff --git a/projects/standalone/build.gradle.kts b/projects/standalone/build.gradle.kts index d7b717d76..1f32184f5 100644 --- a/projects/standalone/build.gradle.kts +++ b/projects/standalone/build.gradle.kts @@ -51,3 +51,5 @@ tasks.named("run", JavaExec::class.java) { workingDir = rootProject.projectDir args = listOf("-r", project(":core").layout.projectDirectory.dir("src/main/resources").asFile.absolutePath) } + +cct.linters(minecraft = false) diff --git a/projects/web/build.gradle.kts b/projects/web/build.gradle.kts index 0840a9801..52d0cd751 100644 --- a/projects/web/build.gradle.kts +++ b/projects/web/build.gradle.kts @@ -10,8 +10,6 @@ plugins { id("cc-tweaked.illuaminate") } -val modVersion = extra["modVersion"] as String - node { projectRoot = rootProject.projectDir } @@ -44,6 +42,7 @@ val compileTeaVM = tasks.register("compileTeaVM") { val output = layout.buildDirectory.dir("teaVM") val minify = !project.hasProperty("noMinify") + val modVersion = project.version as String inputs.property("version", modVersion) inputs.property("minify", minify) inputs.files(sourceSets.main.get().runtimeClasspath).withPropertyName("inputClasspath") @@ -150,3 +149,5 @@ val docWebsite = tasks.register("docWebsite") { } tasks.assemble { dependsOn(docWebsite) } + +cct.linters(minecraft = false) diff --git a/projects/web/src/main/java/dan200/computercraft/core/apis/http/request/THttpRequest.java b/projects/web/src/main/java/dan200/computercraft/core/apis/http/request/THttpRequest.java index 7ee0fecc5..a4389feaf 100644 --- a/projects/web/src/main/java/dan200/computercraft/core/apis/http/request/THttpRequest.java +++ b/projects/web/src/main/java/dan200/computercraft/core/apis/http/request/THttpRequest.java @@ -5,6 +5,7 @@ package dan200.computercraft.core.apis.http.request; import cc.tweaked.web.Main; +import com.google.common.base.Splitter; import dan200.computercraft.core.Logging; import dan200.computercraft.core.apis.IAPIEnvironment; import dan200.computercraft.core.apis.handles.ArrayByteChannel; @@ -122,7 +123,7 @@ public class THttpRequest extends Resource { var reader = new ReadHandle(contents, binary); Map responseHeaders = new HashMap<>(); - for (var header : request.getAllResponseHeaders().split("\r\n")) { + for (var header : Splitter.on("\r\n").split(request.getAllResponseHeaders())) { var index = header.indexOf(':'); if (index < 0) continue;