Merge branch 'mc-1.20.x' into mc-1.21.x

This commit is contained in:
Jonathan Coates
2026-09-11 20:58:46 +01:00
108 changed files with 1410 additions and 1116 deletions
+6 -12
View File
@@ -3,23 +3,19 @@
# SPDX-License-Identifier: CC0-1.0
# Build directories
/classes
/logs
/build
/projects/*/logs
/projects/fabric/fabricloader.log
/build-logic/*/build
/build-logic/build
/projects/*/build
/projects/*/src/generated/resources/.cache
/projects/*/src/test/generated_tests/
/buildSrc/build
/out
/buildSrc/out
/jars
/doc/out/
/projects/*/logs
/node_modules
.jqwik-database
# Runtime directories
/run
/projects/*/run
*.ipr
@@ -35,5 +31,3 @@
/.settings
/.vscode
*.launch
/projects/*/src/generated/resources/.cache
@@ -5,29 +5,6 @@
plugins {
`java-gradle-plugin`
`kotlin-dsl`
alias(libs.plugins.gradleVersions)
alias(libs.plugins.versionCatalogUpdate)
}
// Duplicated in settings.gradle.kts
repositories {
mavenCentral()
gradlePluginPortal()
maven("https://maven.fabricmc.net/") {
name = "Fabric"
content {
includeGroup("net.fabricmc")
includeGroup("net.fabricmc.unpick")
}
}
maven("https://maven.squiddev.cc") {
name = "SquidDev"
content {
includeGroup("cc.tweaked.vanilla-extract")
}
}
}
dependencies {
@@ -44,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"
@@ -60,9 +32,3 @@ gradlePlugin {
}
}
}
versionCatalogUpdate {
sortByKey = false
keep { keepUnusedVersions = true }
catalogFile = file("../gradle/libs.versions.toml")
}
@@ -0,0 +1,104 @@
// 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<VersionCatalogsExtension>().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("net.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")
}
}
}
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
}
@@ -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<VersionCatalogsExtension>().named("libs")
@@ -62,3 +58,25 @@ dependencies {
// Depend on error prone annotations to silence a lot of compile warnings.
compileOnlyApi(libs.findLibrary("errorProne.annotations").get())
}
/**
* Given our list of source/class directories, build a list of files in the "client" source set.
*/
fun getClientFiles(files: FileCollection): List<String> {
val out = mutableListOf<String>()
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))
}
@@ -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")
}
plugins.apply(CCTweakedPlugin::class.java)
neoForge {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
version = libs.findVersion("neoForge").get().toString()
@@ -28,5 +25,5 @@ neoForge {
MinecraftConfigurations.setup(project)
extensions.configure(CCTweakedExtension::class.java) {
linters(minecraft = true, loader = "forge")
linters(minecraft = true)
}
@@ -0,0 +1,159 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedJavaVersions
import cc.tweaked.gradle.JUnitExt
import net.ltgt.gradle.errorprone.CheckSeverity
import net.ltgt.gradle.errorprone.errorprone
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`
jacoco
id("cc-tweaked.base-convention")
id("net.ltgt.errorprone")
// Required for cross-project dependencies in Fabric
id("net.fabricmc.fabric-loom-companion")
}
val cct = project.extensions.getByType<CCTweakedExtension>()
// 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<VersionCatalogsExtension>().named("libs")
java {
toolchain { languageVersion = CCTweakedJavaVersions.JDK_VERSION }
sourceCompatibility = CCTweakedJavaVersions.JAVA_VERSION
targetCompatibility = CCTweakedJavaVersions.JAVA_VERSION
withSourcesJar()
}
dependencies {
errorprone(libs.findLibrary("errorProne-core").get())
errorprone(libs.findLibrary("nullAway").get())
}
// 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<KotlinJvmProjectExtension>()
kotlin.compilerOptions.jvmTarget = CCTweakedJavaVersions.KOTLIN_TARGET
}
// 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")
excludedPaths.set(".*/jmh_generated/.*")
}
}
tasks.compileTestJava {
options.errorprone {
check("NullAway", CheckSeverity.OFF)
}
}
tasks.processResources {
exclude("**/*.license")
exclude(".cache")
}
tasks.jar {
manifest {
attributes(
"Specification-Title" to "computercraft",
"Specification-Vendor" to "SquidDev",
"Specification-Version" to "1",
"Implementation-Title" to "cctweaked-${project.name}",
"Implementation-Version" to project.version as String,
"Implementation-Vendor" to "SquidDev",
)
}
from(cct.embeddedProjectClassesAndResources)
}
tasks.named<Jar>("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/${CCTweakedJavaVersions.JAVA_TARGET.asInt()}/docs/api/")
}
}
tasks.test {
finalizedBy("jacocoTestReport")
useJUnitPlatform()
testLogging {
events("skipped", "failed")
}
}
tasks.withType(JacocoReport::class.java).configureEach {
reports.xml.required = true
reports.html.required = true
sourceDirectories.from(cct.embeddedProjectSources)
classDirectories.from(cct.embeddedProjectClasses)
}
@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
/**
* Sets up the configurations for writing game tests.
*
* See notes in [cc.tweaked.gradle.MinecraftConfigurations] for the general design behind these cursed ideas.
*/
import cc.tweaked.gradle.MinecraftConfigurations
plugins {
kotlin("jvm")
id("cc-tweaked.java-convention")
}
val main = sourceSets["main"]
val client = sourceSets["client"]
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.DATAGEN)
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.EXAMPLES)
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.TEST_MOD)
// Set up generated resources
sourceSets.main { resources.srcDir("src/generated/resources") }
sourceSets.named("examples") { resources.srcDir("src/examples/generatedResources") }
configurations.consumable("samplesElement") {
attributes { attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.SAMPLES)) }
outgoing.artifacts(sourceSets.named("examples").map { it.allSource.sourceDirectories.files }) {
type = ArtifactTypeDefinition.DIRECTORY_TYPE
}
}
@@ -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<AbstractArchiveTask>
init {
output.finalizeValueOnRead()
}
}
val modPublishing = project.extensions.create("modPublishing", ModPublishingExtension::class.java)
val isUnstable = extra["isUnstable"] == "true"
val mcVersion = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
.findVersion("minecraft").get().toString()
val verifyClassesMatch = tasks.register<VerifyJavaCompile>("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) }
@@ -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<VersionCatalogsExtension>().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)
}
@@ -0,0 +1,168 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import net.ltgt.gradle.errorprone.CheckSeverity
import net.ltgt.gradle.errorprone.errorprone
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.tasks.compile.JavaCompile
import org.gradle.language.base.plugins.LifecycleBasePlugin
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 java.io.File
import java.io.IOException
import java.net.URI
import java.util.regex.Pattern
abstract class CCTweakedExtension(private val project: Project) {
/** Get the hash of the latest git commit. */
val gitHash: Provider<String> =
gitProvider("<no git commit>", listOf("rev-parse", "HEAD")) { it.trim() }
/** Get the current git branch. */
val gitBranch: Provider<String> =
gitProvider("<no git branch>", listOf("rev-parse", "--abbrev-ref", "HEAD")) { it.trim() }
/** Get a list of all contributors to the project. */
val gitContributors: Provider<List<String>> =
gitProvider(listOf(), listOf("shortlog", "-ns", "--group=author", "--group=trailer:co-authored-by", "HEAD")) { input ->
input.lineSequence()
.filter { it.isNotEmpty() }
.map {
val matcher = COMMIT_COUNTS.matcher(it)
matcher.find()
matcher.group(1)
}
.filter { !IGNORED_USERS.contains(it) }
.toList()
.sortedWith(String.CASE_INSENSITIVE_ORDER)
}
private val embeddedProjects = project.configurations.dependencyScope("embeddedProject")
private val embeddedProjectsResolved = project.configurations.resolvable("embeddedProjectResolved") {
extendsFrom(embeddedProjects)
isTransitive = false
ExternalProjectArtifacts.configure(this)
}
val embeddedProjectClasses: Provider<FileCollection> = embeddedProjectsResolved.map(ExternalProjectArtifacts::classes)
val embeddedProjectClassesAndResources: Provider<FileCollection> =
embeddedProjectsResolved.map(ExternalProjectArtifacts::classesAndResources)
val embeddedProjectSources: Provider<FileCollection> = embeddedProjectsResolved.map(ExternalProjectArtifacts::sources)
/**
* Enable our custom linters on this project.
*/
fun linters(@Suppress("UNUSED_PARAMETER") vararg unused: UseNamedArgs, minecraft: Boolean) {
val java = project.extensions.getByType(JavaPluginExtension::class.java)
project.dependencies.run { add("errorprone", project(":lints")) }
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)
}
}
}
fun <T> jacoco(task: NamedDomainObjectProvider<T>) where T : Task, T : JavaForkOptions {
val reportTaskName = "jacoco${task.name.capitalise()}Report"
val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java)
task.configure {
finalizedBy(reportTaskName)
jacoco.applyTo(this)
extensions.configure(JacocoTaskExtension::class.java) {
includes = listOf("dan200.computercraft.*")
excludes = listOf(
"dan200.computercraft.mixin.*", // Exclude mixins, as they're not executed at runtime.
"dan200.computercraft.shared.Capabilities$*", // Exclude capability tokens, as Forge rewrites them.
)
}
}
project.tasks.register(reportTaskName, JacocoReport::class.java) {
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = "Generates code coverage report for the ${task.name} task."
executionData(task.get())
}
}
/**
* Download a file by creating a dummy Ivy repository.
*
* This should only be used for one-off downloads. Using a more conventional Ivy or Maven repository is preferred
* where possible.
*/
fun downloadFile(label: String, url: String): File {
val uri = URI(url)
val path = File(uri.path)
project.repositories.ivy {
name = label
setUrl(URI(uri.scheme, uri.userInfo, uri.host, uri.port, path.parent, null, null))
patternLayout {
artifact("[artifact].[ext]")
}
metadataSources {
artifact()
}
content {
includeModule("cc.tweaked.internal", path.nameWithoutExtension)
}
}
return project.configurations.detachedConfiguration(
project.dependencies.create(
mapOf(
"group" to "cc.tweaked.internal",
"name" to path.nameWithoutExtension,
"ext" to path.extension,
),
),
).resolve().single()
}
private fun <T : Any> gitProvider(default: T, command: List<String>, process: (String) -> T): Provider<T> {
val baseResult = project.providers.exec {
commandLine = listOf("git", "-C", project.rootDir.absolutePath) + command
}
return project.provider {
val res = try {
baseResult.standardOutput.asText.get()
} catch (e: IOException) {
project.logger.error("Cannot read Git repository: ${e.message}", e)
return@provider default
} catch (e: GradleException) {
project.logger.error("Cannot read Git repository: ${e.message}", e)
return@provider default
}
process(res)
}
}
companion object {
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
private val IGNORED_USERS = setOf(
"GitHub", "Daniel Ratcliffe", "NotSquidDev", "Weblate",
)
}
}
@@ -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(21)
val JAVA_VERSION = JavaVersion.toVersion(JAVA_TARGET.asInt())
val KOTLIN_TARGET = JvmTarget.fromTarget(JAVA_TARGET.toString())
}
@@ -11,7 +11,6 @@ import org.gradle.api.provider.Provider
import org.gradle.api.tasks.JavaExec
import org.gradle.kotlin.dsl.getByName
import org.gradle.process.BaseExecSpec
import org.gradle.process.JavaExecSpec
import org.gradle.process.ProcessForkOptions
/**
@@ -140,38 +139,11 @@ class CloseScope : AutoCloseable {
}
/** Proxy method to avoid overload ambiguity. */
fun <T: Any> Property<T >.setProvider(provider: Provider<out T>) = set(provider)
fun <T : Any> Property<T>.setProvider(provider: Provider<out T>) = set(provider)
/** Short-cut method to get the absolute path of a [FileSystemLocation] provider. */
fun Provider<out FileSystemLocation>.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.
*
@@ -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
}
@@ -27,7 +27,7 @@ abstract class MergeTrees : DefaultTask() {
*/
interface Source {
/**
* The folder contianing all input files.
* The folder containing all input files.
*/
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
@@ -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)
@@ -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<String>
@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<Problem>()
// 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<String, ClassFile> {
val output = mutableMapOf<String, ClassFile>()
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)
}
}
+14
View File
@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
plugins {
alias(libs.plugins.gradleVersions)
alias(libs.plugins.versionCatalogUpdate)
}
versionCatalogUpdate {
sortByKey = false
keep { keepUnusedVersions = true }
catalogFile = file("../gradle/libs.versions.toml")
}
+39
View File
@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
// Duplicated in root settings.gradle.kts
repositories {
mavenCentral()
gradlePluginPortal()
exclusiveContent {
forRepositories(maven("https://maven.fabricmc.net/"))
filter {
includeGroup("fabric-loom")
includeGroup("net.fabricmc")
includeGroup("net.fabricmc.unpick")
}
}
exclusiveContent {
forRepositories(maven("https://maven.squiddev.cc"))
filter {
includeGroup("cc.tweaked.vanilla-extract")
}
}
}
}
rootProject.name = "build-logic"
include("base")
+5 -5
View File
@@ -11,13 +11,12 @@ plugins {
publishing
alias(libs.plugins.githubRelease)
alias(libs.plugins.gradleVersions)
alias(libs.plugins.ideaExt)
alias(libs.plugins.versionCatalogUpdate)
id("org.jetbrains.gradle.plugin.idea-ext")
id("cc-tweaked")
id("cc-tweaked.base-convention")
}
val isUnstable = project.properties["isUnstable"] == "true"
val modVersion: String by extra
val isUnstable = extra["isUnstable"] == "true"
val mcVersion = project.extensions.getByType<VersionCatalogsExtension>().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()
}
-11
View File
@@ -1,11 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
@@ -1,226 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// 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 net.ltgt.gradle.errorprone.CheckSeverity
import net.ltgt.gradle.errorprone.errorprone
import java.nio.charset.StandardCharsets
plugins {
`java-library`
idea
jacoco
checkstyle
id("com.diffplug.spotless")
id("net.ltgt.errorprone")
// Required for cross-project dependencies in Fabric
id("net.fabricmc.fabric-loom-companion")
}
val modVersion: String by extra
val libs = project.extensions.getByType<VersionCatalogsExtension>().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
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("net.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")
}
}
}
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()
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("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/.*"
}
}
}
tasks.compileTestJava {
options.errorprone {
check("NullAway", CheckSeverity.OFF)
}
}
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(
"Specification-Title" to "computercraft",
"Specification-Vendor" to "SquidDev",
"Specification-Version" to "1",
"Implementation-Title" to "cctweaked-${project.name}",
"Implementation-Version" to modVersion,
"Implementation-Vendor" to "SquidDev",
)
}
}
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/")
}
}
tasks.test {
finalizedBy("jacocoTestReport")
useJUnitPlatform()
testLogging {
events("skipped", "failed")
}
}
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<CCTweakedExtension>()
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
}
@@ -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<AbstractArchiveTask>
init {
output.finalizeValueOnRead()
}
}
val modPublishing = project.extensions.create("modPublishing", ModPublishingExtension::class.java)
val isUnstable = project.properties["isUnstable"] == "true"
val modVersion: String by extra
val mcVersion = project.extensions.getByType<VersionCatalogsExtension>().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) }
@@ -1,57 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
/**
* Sets up the configurations for writing game tests.
*
* 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")
id("cc-tweaked.java-convention")
}
val main = sourceSets["main"]
val client = sourceSets["client"]
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.DATAGEN)
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.EXAMPLES)
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.TEST_MOD)
// Set up generated resources
sourceSets.main { resources.srcDir("src/generated/resources") }
sourceSets.named("examples") { resources.srcDir("src/examples/generatedResources") }
// 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 by sourceSets.creating {
compileClasspath += main.compileClasspath + client.compileClasspath
}
java.registerFeature("testFixtures") {
usingSourceSet(testFixtures)
disablePublication()
}
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().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
@@ -1,254 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import net.ltgt.gradle.errorprone.CheckSeverity
import net.ltgt.gradle.errorprone.errorprone
import org.gradle.api.GradleException
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.Task
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
import java.util.regex.Pattern
abstract class CCTweakedExtension(private val project: Project) {
/** Get the hash of the latest git commit. */
val gitHash: Provider<String> =
gitProvider("<no git commit>", listOf("rev-parse", "HEAD")) { it.trim() }
/** Get the current git branch. */
val gitBranch: Provider<String> =
gitProvider("<no git branch>", listOf("rev-parse", "--abbrev-ref", "HEAD")) { it.trim() }
/** Get a list of all contributors to the project. */
val gitContributors: Provider<List<String>> =
gitProvider(listOf(), listOf("shortlog", "-ns", "--group=author", "--group=trailer:co-authored-by", "HEAD")) { input ->
input.lineSequence()
.filter { it.isNotEmpty() }
.map {
val matcher = COMMIT_COUNTS.matcher(it)
matcher.find()
matcher.group(1)
}
.filter { !IGNORED_USERS.contains(it) }
.toList()
.sortedWith(String.CASE_INSENSITIVE_ORDER)
}
/**
* References to other sources
*/
val sourceDirectories: SetProperty<SourceSetReference> = 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() }
}
/**
* 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))
}
}
/**
* 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.
*/
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?) {
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)
// The MissingLoaderOverride check superseeds 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)
}
}
}
}
}
fun <T> jacoco(task: NamedDomainObjectProvider<T>) where T : Task, T : JavaForkOptions {
val reportTaskName = "jacoco${task.name.capitalise()}Report"
val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java)
task.configure {
finalizedBy(reportTaskName)
jacoco.applyTo(this)
extensions.configure(JacocoTaskExtension::class.java) {
includes = listOf("dan200.computercraft.*")
excludes = listOf(
"dan200.computercraft.mixin.*", // Exclude mixins, as they're not executed at runtime.
"dan200.computercraft.shared.Capabilities$*", // Exclude capability tokens, as Forge rewrites them.
)
}
}
project.tasks.register(reportTaskName, JacocoReport::class.java) {
group = LifecycleBasePlugin.VERIFICATION_GROUP
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)
}
}
}
/**
* Download a file by creating a dummy Ivy repository.
*
* This should only be used for one-off downloads. Using a more conventional Ivy or Maven repository is preferred
* where possible.
*/
fun downloadFile(label: String, url: String): File {
val uri = URI(url)
val path = File(uri.path)
project.repositories.ivy {
name = label
setUrl(URI(uri.scheme, uri.userInfo, uri.host, uri.port, path.parent, null, null))
patternLayout {
artifact("[artifact].[ext]")
}
metadataSources {
artifact()
}
content {
includeModule("cc.tweaked.internal", path.nameWithoutExtension)
}
}
return project.configurations.detachedConfiguration(
project.dependencies.create(
mapOf(
"group" to "cc.tweaked.internal",
"name" to path.nameWithoutExtension,
"ext" to path.extension,
),
),
).resolve().single()
}
private fun <T: Any> gitProvider(default: T, command: List<String>, process: (String) -> T): Provider<T> {
val baseResult = project.providers.exec {
commandLine = listOf("git", "-C", project.rootDir.absolutePath) + command
}
return project.provider {
val res = try {
baseResult.standardOutput.asText.get()
} catch (e: IOException) {
project.logger.error("Cannot read Git repository: ${e.message}", e)
return@provider default
} catch (e: GradleException) {
project.logger.error("Cannot read Git repository: ${e.message}", e)
return@provider default
}
process(res)
}
}
companion object {
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
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"))
}
}
@@ -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<Project> {
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(21)
val JAVA_VERSION = JavaVersion.toVersion(JAVA_TARGET.asInt())
val KOTLIN_TARGET = JvmTarget.fromTarget(JAVA_TARGET.toString())
}
}
@@ -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)
}
}
+4 -4
View File
@@ -45,7 +45,9 @@ SPDX-License-Identifier: MPL-2.0
<module name="MissingOverride" />
<!-- Blocks -->
<module name="EmptyBlock" />
<module name="EmptyBlock">
<property name="option" value="text" />
</module>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="ignored" />
</module>
@@ -105,9 +107,7 @@ SPDX-License-Identifier: MPL-2.0
<!-- Seems to complain about @hidden!? -->
<property name="allowUnknownTags" value="true" />
</module>
<module name="JavadocStyle">
<property name="checkHtml" value="false" />
</module>
<module name="SummaryJavadoc" />
<module name="NonEmptyAtclauseDescription" />
<module name="SingleLineJavadoc" />
<module name="SummaryJavadocCheck"/>
+1 -1
View File
@@ -21,7 +21,7 @@ files:
pl: pl_pl # Polish
pt-BR: pt_br # Portuguese, Brazilian
ru: ru_ru # Russian
sv-SE: sv_se # Sweedish
sv-SE: sv_se # Swedish
tok: tok # Toki Pona
tr: tr_tr # Turkish
uk: uk_ua # Ukraine
+1 -1
View File
@@ -22,7 +22,7 @@ determine a computer's position.
You will need:
- Four computers.
- Four Ender Modems. Normal Wireless Modems may be be used, but the range of the GPS constellation will be severely
- Four Ender Modems. Normal Wireless Modems may be used, but the range of the GPS constellation will be severely
limited.
Additionally, you will need another computer and a wireless modem, in order to test that GPS works!
+1 -2
View File
@@ -63,7 +63,7 @@ as documentation for breaking changes and "gotchas" one should look out for betw
- Due to how data packs are read by CC: Tweaked, you may need to use the `/reload` command to see changes to your
pack show up on the computer.
See [the example datapack][datapack-example] for how to get started.
See [`data_pack`] for more information.
- Turtles can now be waterlogged and move "through" water sources rather than breaking them.
@@ -79,4 +79,3 @@ as documentation for breaking changes and "gotchas" one should look out for betw
[flattening]: https://minecraft.wiki/w/Java_Edition_1.13/Flattening
[legal_data_pack]: https://minecraft.wiki/w/Tutorials/Creating_a_data_pack#Legal_characters
[datapack-example]: https://github.com/cc-tweaked/datapack-example "An example datapack for CC: Tweaked"
+67
View File
@@ -0,0 +1,67 @@
---
module: [kind=reference] data_pack
---
<!--
SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
# Data packs
CC: Tweaked allows customising and extending many of its features via [data packs][data pack wiki], such as adding new
programs to the ROM, or providing new turtle tools. This page serves as a reference for CC:T's data pack functionality.
> [Data pack compatibility][!NOTE]
>
> Unlike other parts of this documentation, the format of data packs change between Minecraft versions. Make sure you're
> using the right documentation for your Minecraft version.
>
> - [Minecraft 1.20.1](/mc-1.20.x/reference/data_pack.html)
> - [Minecraft 1.21.1](/mc-1.21.x/reference/data_pack.html)
> - [Minecraft 26.2](/mc-26.2/reference/data_pack.html)
In addition to this documentation, we also provide an [online data pack builder][data pack builder] and an
[example/template data pack][data pack example].
## CraftOS ROM
Datapacks can be used to add or overwrite files in CraftOS's ROM, allowing you to add new programs or modules, or adjust
the behaviour of existing ones. Files should be placed in `data/computercraft/lua/rom`. For example, a datapack
containing `data/computercraft/lua/rom/programs/example.lua` will add a new program `example`.
> [File naming][!WARNING]
>
> Data packs [impose some restrictions on file names][legal data pack]. As a result, all file and directory names must
> be lower case.
- Files under `rom/autorun` will be run on computer startup.
- Files under `rom/modules/main` can be loaded via `require`.
- Files located in `rom/apis/` will be loaded via `os.loadAPI` on startup. Similarly:
- Files under `rom/apis/http` will be loaded if the HTTP API is enabled.
- Files under `rom/apis/turtle` will be loaded on all turtles.
- Files under `rom/apis/pocket` will be loaded on all pocket computers.
- Files under `rom/apis/command` will be loaded on all command computers.
## Tags
CC:T provides several [block][block tags] and [item][item tags] tags. While most tags are just used to group related
blocks/items (e.g. `computercraft:turtles` is the list of all turtles), several can be extended by data packs to tweak
functionality:
- `computercraft:turtle_can_place` item tag: This tag lists items turtles can use with [`turtle.place`]. By default
this contains glass bottles and boats,
- `computercraft:turtle_can_use` block tag: This tag lists blocks turtles can interact with [`turtle.place`]. By
default this contains beehives, cauldrons and the composter. The [data pack builder] has options for adding
levers/buttons and modems to this list, allowing turtles to toggle them.
- `computercraft:always_breakable` block tag: All turtle tools are capable of breaking instant-break blocks, such as
saplings. This tag specifies additional blocks that can be broken by all tools. By default, this includes leaves,
bamboo and bamboo saplings.
- `computercraft:turtle_shovel_harvestable`, `computercraft:turtle_sword_harvestable`,
`computercraft:turtle_hoe_harvestable` block tags: These tags specify which blocks can be broken by the shovel, sword
and hoe respectively.
[block tags]: ../../javadoc/dan200/computercraft/api/ComputerCraftTags.Blocks.html "ComputerCraftTags.Blocks javadoc"
[data pack builder]: https://datapacks.madefor.cc/
[data pack example]: https://github.com/cc-tweaked/datapack-example "An example data pack for CC: Tweaked"
[data pack wiki]: https://minecraft.wiki/w/Data_pack "Data pack on the Minecraft wiki"
[item tags]: ../../javadoc/dan200/computercraft/api/ComputerCraftTags.Items.html "ComputerCraftTags.Items javadoc"
[legal data pack]: https://minecraft.wiki/w/Tutorials/Creating_a_data_pack#Legal_characters
+1 -2
View File
@@ -15,8 +15,7 @@ When a computer turns on, it searches for files to run as part of the startup pr
For information about creating a basic startup file, see [`guide!startup`].
1. `/rom/autorun`: Computers first look in the `/rom/autorun` folder, and run every file in that folder. This folder is
empty by default, but may be extended by datapacks or other mods. See the [example
datapack](https://github.com/cc-tweaked/datapack-example) for an example.
empty by default, but may be extended by datapacks or other mods. See [`data_pack`] for more information.
2. If the `shell.allow_disk_startup` [setting][`settings`] is `true`, then connected disk drives are searched for a
`startup` file, `startup.lua` file, or `startup/` directory. The first disk containing these files will be used for
+2 -1
View File
@@ -4,11 +4,12 @@
org.gradle.jvmargs=-Xmx3G -Dfile.encoding=UTF-8
org.gradle.parallel=true
org.gradle.configuration-cache=true
kotlin.stdlib.default.dependency=false
kotlin.jvm.target.validation.mode=error
# Mod properties
modVersion=1.120.2
version=1.120.2
isUnstable=true
+22 -21
View File
@@ -23,17 +23,17 @@ netty = "4.1.97.Final"
slf4j = "2.0.9"
# Core dependencies (independent of Minecraft)
asm = "9.9.1"
asm = "9.10.1"
autoService = "1.1.1"
checkerFramework = "4.0.0"
checkerFramework = "4.2.2"
cobalt = { strictly = "0.9.9" }
commonsCli = "1.11.0"
jetbrainsAnnotations = "26.1.0"
jspecify = "1.0.0"
jspecify = "1.0.1"
jzlib = "1.1.3"
kotlin = "2.3.21"
kotlin-coroutines = "1.10.2"
nightConfig = "3.8.4"
kotlin = "2.4.10"
kotlin-coroutines = "1.11.0"
nightConfig = "3.9.0"
# Minecraft mods
emi = "1.1.7+1.21"
@@ -52,30 +52,30 @@ create-fabric = "6.0.7.0+mc1.20.1-build.1716"
# Testing
hamcrest = "3.0"
jqwik = "1.9.3"
junit = "6.0.3"
junitPlatform = "6.0.3"
jqwik = "1.10.1"
junit = "6.1.3"
junitPlatform = "6.1.3"
jmh = "1.37"
# Build tools
cctJavadoc = "1.9.0"
checkstyle = "13.4.1"
errorProne-core = "2.49.0"
checkstyle = "14.0.0"
errorProne-core = "2.50.0"
errorProne-plugin = "4.3.0"
fabric-loom = "1.17.11"
fabric-loom = "1.17.20"
githubRelease = "2.5.2"
gradleVersions = "0.54.0"
gradleVersions = "0.61.0"
ideaExt = "1.3"
illuaminate = "0.1.0-83-g1131f68"
lwjgl = "3.4.1"
minotaur = "2.8.7"
lwjgl = "3.4.3"
minotaur = "2.9.0"
modDevGradle = "2.0.141"
nullAway = "0.13.4"
shadow = "9.4.1"
spotless = "8.4.0"
nullAway = "0.14.0"
shadow = "9.6.1"
spotless = "8.10.0"
teavm = "0.15.0"
vanillaExtract = "0.3.1"
versionCatalogUpdate = "1.1.0"
vanillaExtract = "0.3.2"
versionCatalogUpdate = "1.1.1"
[libraries]
# Normal dependencies
@@ -171,7 +171,8 @@ yarn = { module = "net.fabricmc:yarn", version.ref = "yarn" }
[plugins]
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" }
gradleVersions = { id = "io.github.ben-manes.versions", version.ref = "gradleVersions" }
ideaExt = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "ideaExt" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
Vendored
+2 -2
View File
@@ -20,7 +20,7 @@
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
# gradlew start up script for POSIX generated by Gradle.
#
# Important for running:
#
@@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
# ksh gradlew
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
Vendored
+2 -2
View File
@@ -19,7 +19,7 @@
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem gradlew startup script for Windows
@rem
@rem ##########################################################################
@@ -72,7 +72,7 @@ echo location of your Java installation. 1>&2
@rem Execute Gradle
@rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
+1 -1
View File
@@ -68,7 +68,7 @@ mentioning:
- `web`: This contains the additional tooling for building [the documentation website][tweaked.cc], such as support for
rendering recipes
- `buildSrc` (in the base directory, not in `projects/`): This contains any build logic shared between modules. For
- `build-logic` (in the base directory, not in `projects/`): This contains any build logic shared between modules. For
instance, `cc-tweaked.java-convention.gradle.kts` sets up the defaults for Java that we use across the whole project.
> **Note**
+14 -6
View File
@@ -15,11 +15,21 @@ java {
withJavadocJar()
}
dependencies {
api(project(":core-api"))
val snippetSourcesDependencies = configurations.dependencyScope("snippetSourcesDependencies")
val snippetSources = configurations.resolvable("snippetSources") {
extendsFrom(snippetSourcesDependencies)
attributes { attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.SAMPLES)) }
}
val javadocOverview by tasks.registering(Copy::class) {
dependencies {
api(project(":core-api"))
"snippetSourcesDependencies"(project(":common"))
"snippetSourcesDependencies"(project(":fabric"))
"snippetSourcesDependencies"(project(":forge"))
}
val javadocOverview = tasks.register<Copy>("javadocOverview") {
from("src/overview.html")
into(layout.buildDirectory.dir(name))
@@ -64,9 +74,7 @@ tasks.javadoc {
""".trimIndent(),
)
val snippetSources = listOf(":common", ":fabric", ":forge").flatMap {
project(it).sourceSets["examples"].allSource.sourceDirectories
}
val snippetSources = snippetSources.get().files.toList()
inputs.files(snippetSources)
addPathOption("-snippet-path").value = snippetSources
}
@@ -22,10 +22,29 @@ import net.minecraft.world.phys.BlockHitResult;
* Tags provided by ComputerCraft.
*/
public class ComputerCraftTags {
/**
* Item tags provided by ComputerCraft.
*/
public static class Items {
/**
* All computers. This includes normal, advanced, and command computers. It does not include turtles or pocket
* computers.
*/
public static final TagKey<Item> COMPUTER = make("computer");
/**
* All turtles. This includes normal and advanced turtles.
*/
public static final TagKey<Item> TURTLE = make("turtle");
/**
* All modems. This includes the multi-part and full-block modem.
*/
public static final TagKey<Item> WIRED_MODEM = make("wired_modem");
/**
* All monitors. This includes normal and advanced monitors.
*/
public static final TagKey<Item> MONITOR = make("monitor");
/**
@@ -64,10 +83,29 @@ public class ComputerCraftTags {
}
}
/**
* Block tags provided by ComputerCraft.
*/
public static class Blocks {
/**
* All computers. This includes normal, advanced, and command computers. It does not include turtles or pocket
* computers.
*/
public static final TagKey<Block> COMPUTER = make("computer");
/**
* All turtles. This includes normal and advanced turtles.
*/
public static final TagKey<Block> TURTLE = make("turtle");
/**
* All modems. This includes the multi-part and full-block modem.
*/
public static final TagKey<Block> WIRED_MODEM = make("wired_modem");
/**
* All monitors. This includes normal and advanced monitors.
*/
public static final TagKey<Block> MONITOR = make("monitor");
/**
+22 -13
View File
@@ -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 {
exclusiveContent {
forRepositories(maven("https://maven.neoforged.net/releases"))
@@ -45,7 +55,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)
@@ -57,8 +70,6 @@ dependencies {
testModImplementation(testFixtures(project(":common")))
testModImplementation(libs.bundles.kotlin)
testFixturesImplementation(testFixtures(project(":core")))
"cctJavadoc"(libs.cctJavadoc)
}
@@ -66,15 +77,13 @@ illuaminate {
version = libs.versions.illuaminate
}
val luaJavadoc by tasks.registering(Javadoc::class) {
val luaJavadoc = tasks.register<Javadoc>("luaJavadoc") {
description = "Generates documentation for Java-side Lua functions."
group = JavaBasePlugin.DOCUMENTATION_GROUP
val sourceSets = listOf(sourceSets.main.get(), project(":core").sourceSets.main.get())
for (sourceSet in sourceSets) {
source(sourceSet.java)
classpath += sourceSet.compileClasspath
}
source(sourceSets.main.get().java.sourceDirectories)
source(project.layout.projectDirectory.dir("../core/src/main/java"))
classpath = sourceSets.main.get().compileClasspath + sourceSets.main.get().runtimeClasspath
destinationDir = layout.buildDirectory.dir("docs/luaJavadoc").get().asFile
@@ -84,10 +93,10 @@ val luaJavadoc by tasks.registering(Javadoc::class) {
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 by tasks.registering(IlluaminateExec::class) {
val lintLua = tasks.register<IlluaminateExec>("lintLua") {
group = JavaBasePlugin.VERIFICATION_GROUP
description = "Lint Lua (and Lua docs) with illuaminate"
@@ -121,11 +130,11 @@ fun MergeTrees.configureForDatagen(source: SourceSet, outputFolder: String) {
}
}
val runData by tasks.registering(MergeTrees::class) {
val runData = tasks.register<MergeTrees>("runData") {
configureForDatagen(sourceSets.main.get(), "src/generated/resources")
}
val runExampleData by tasks.registering(MergeTrees::class) {
val runExampleData = tasks.register<MergeTrees>("runExampleData") {
configureForDatagen(sourceSets.examples.get(), "src/examples/generatedResources")
}
@@ -14,7 +14,7 @@ import java.util.Objects;
import java.util.stream.Stream;
/**
* Sprite sheet for all GUI texutres in the mod.
* Sprite sheet for all GUI textures in the mod.
*/
public final class GuiSprites {
public static final ButtonTextures TURNED_OFF = button("turned_off");
@@ -48,9 +48,9 @@ public class MonitorRenderState implements ClientMonitor.RenderState {
* or this mode does not require one.
*/
public boolean createBuffer(MonitorRenderer renderer) {
switch (renderer) {
return switch (renderer) {
case TBO -> {
if (tboBuffer != 0) return false;
if (tboBuffer != 0) yield false;
deleteBuffers();
@@ -65,21 +65,19 @@ public class MonitorRenderState implements ClientMonitor.RenderState {
DirectBuffers.setEmptyBufferData(GL31.GL_UNIFORM_BUFFER, tboUniform, GL15.GL_STATIC_DRAW);
addMonitor();
return true;
yield true;
}
case VBO -> {
if (backgroundBuffer != null) return false;
if (backgroundBuffer != null) yield false;
deleteBuffers();
backgroundBuffer = new DirectVertexBuffer();
foregroundBuffer = new DirectVertexBuffer();
addMonitor();
return true;
yield true;
}
default -> {
return false;
}
}
default -> false;
};
}
private void addMonitor() {
@@ -50,7 +50,7 @@ public class ExampleAPI implements ILuaAPI {
}
/**
* A Lua-facing function function that returns the direction the turtle is facing.
* A Lua-facing function that returns the direction the turtle is facing.
*
* @return The turtle's direction.
*/
@@ -43,7 +43,7 @@ final class InvariantChecker {
for (var neighbour : node.neighbours) {
if (!neighbour.neighbours.contains(node)) {
okay = false;
LOG.error("{}: Neighbour {}'s neighbour set does not contain origianl node.", node, neighbour);
LOG.error("{}: Neighbour {}'s neighbour set does not contain original node.", node, neighbour);
}
}
@@ -484,7 +484,7 @@ public final class ModRegistry {
static {
register("tracking_field", TrackingFieldArgumentType.class, TrackingFieldArgumentType.metric());
register("computer", ComputerArgumentType.class, ComputerArgumentType.get());
registerUnsafe("repeat", RepeatArgumentType.class, new RepeatArgumentType.Info());
registerUnsafe("repeat", RepeatArgumentType.class, new RepeatArgumentType.TypeInfo());
}
}
@@ -315,7 +315,7 @@ public record ComputerSelector(
private final Message tooltip;
Option(String name, Parser parser, SuggestionProvider suggest) {
private Option(String name, Parser parser, SuggestionProvider suggest) {
this.name = name;
this.parser = parser;
this.suggest = suggest;
@@ -111,7 +111,7 @@ public final class RepeatArgumentType<T, U> implements ArgumentType<List<T>> {
return child.getExamples();
}
public static class Info implements ArgumentTypeInfo<RepeatArgumentType<?, ?>, Template> {
public static class TypeInfo implements ArgumentTypeInfo<RepeatArgumentType<?, ?>, Template> {
@Override
public void serializeToNetwork(RepeatArgumentType.Template arg, FriendlyByteBuf buf) {
buf.writeBoolean(arg.flatten);
@@ -141,7 +141,7 @@ public final class RepeatArgumentType<T, U> implements ArgumentType<List<T>> {
}
public record Template(
Info info, ArgumentTypeInfo.Template<?> child, boolean flatten, SimpleCommandExceptionType some
TypeInfo info, ArgumentTypeInfo.Template<?> child, boolean flatten, SimpleCommandExceptionType some
) implements ArgumentTypeInfo.Template<RepeatArgumentType<?, ?>> {
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
@@ -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);
@@ -270,7 +271,7 @@ public class CommandAPI implements ILuaAPI {
/**
* Get some basic information about a block.
* <p>
* The returned table contains the the same information as listed in [`block_details`]. If there is a block entity
* The returned table contains the same information as listed in [`block_details`]. If there is a block entity
* for that block, its NBT will also be returned.
*
* @param x The x position of the block to query.
@@ -181,7 +181,7 @@ public final class ServerContext {
* Get the directory used for all ComputerCraft related information. This includes the computer/peripheral id store,
* and all computer data.
*
* @return The storge directory for ComputerCraft.
* @return The storage directory for ComputerCraft.
*/
public Path storageDir() {
return storageDir;
@@ -10,6 +10,7 @@ import dan200.computercraft.api.filesystem.WritableMount;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.common.AbstractContainerBlockEntity;
import dan200.computercraft.shared.container.BasicContainer;
import dan200.computercraft.shared.network.client.PlayRecordClientMessage;
@@ -278,7 +279,7 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity imp
void detach(IComputerAccess computer) {
synchronized (this) {
unmountDisk(computer, computers.remove(computer));
unmountDisk(computer, Nullability.assertNonNull(computers.remove(computer)));
}
}
@@ -304,7 +304,8 @@ public interface PlatformHelper {
/**
* This result was not handled, and should be handled by the caller.
*
* @param block Whether the block may be used ({@link BlockState#use(Level, Player, InteractionHand, BlockHitResult)}).
* @param block Whether the block may be used ({@link BlockState#useItemOn(ItemStack, Level, Player, InteractionHand, BlockHitResult)}
* and {@link BlockState#useWithoutItem(Level, Player, BlockHitResult)}).
* @param item Whether the item may be used on the block ({@link ItemStack#useOn(UseOnContext)}).
* @see ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult)
*/
@@ -306,14 +306,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.getUpgradeType().isPeripheral();
}
@@ -518,7 +518,7 @@ public class TurtleBrain implements TurtleAccessInternal {
}
public Vec3 getRenderOffset(float f) {
switch (animation) {
return switch (animation) {
case MOVE_FORWARD, MOVE_BACK, MOVE_UP, MOVE_DOWN -> {
// Get direction
var dir = switch (animation) {
@@ -530,16 +530,14 @@ public class TurtleBrain implements TurtleAccessInternal {
};
var distance = -1.0 + getAnimationFraction(f);
return new Vec3(
yield new Vec3(
distance * dir.getStepX(),
distance * dir.getStepY(),
distance * dir.getStepZ()
);
}
default -> {
return Vec3.ZERO;
}
}
default -> Vec3.ZERO;
};
}
public float getToolRenderAngle(TurtleSide side, float f) {
@@ -755,7 +753,7 @@ public class TurtleBrain implements TurtleAccessInternal {
private @Nullable UpgradeData<ITurtleUpgrade> cachedUpgradeData;
public void setUpgrade(@Nullable UpgradeData<ITurtleUpgrade> upgrade) {
private void setUpgrade(@Nullable UpgradeData<ITurtleUpgrade> upgrade) {
if (upgrade == null) {
this.upgrade = null;
data = DataComponentPatch.EMPTY;
@@ -767,7 +765,7 @@ public class TurtleBrain implements TurtleAccessInternal {
}
}
public @Nullable UpgradeData<ITurtleUpgrade> getUpgrade() {
private @Nullable UpgradeData<ITurtleUpgrade> getUpgrade() {
if (upgrade == null) return null;
var cached = cachedUpgradeData;
@@ -18,20 +18,17 @@ public class TurtleTurnCommand implements TurtleCommand {
@Override
public TurtleCommandResult execute(ITurtleAccess turtle) {
switch (direction) {
return switch (direction) {
case LEFT -> {
turtle.setDirection(turtle.getDirection().getCounterClockWise());
turtle.playAnimation(TurtleAnimation.TURN_LEFT);
return TurtleCommandResult.success();
yield TurtleCommandResult.success();
}
case RIGHT -> {
turtle.setDirection(turtle.getDirection().getClockWise());
turtle.playAnimation(TurtleAnimation.TURN_RIGHT);
return TurtleCommandResult.success();
yield TurtleCommandResult.success();
}
default -> {
return TurtleCommandResult.failure("Unknown direction");
}
}
};
}
}
@@ -131,7 +131,7 @@ public final class TickScheduler {
* As such, it should be unique per {@link BlockEntity} instance to avoid it being queued multiple times.
*/
public static class Token {
static final AtomicReferenceFieldUpdater<Token, State> STATE = AtomicReferenceFieldUpdater.newUpdater(Token.class, State.class, "$state");
private static final AtomicReferenceFieldUpdater<Token, State> STATE = AtomicReferenceFieldUpdater.newUpdater(Token.class, State.class, "$state");
final BlockEntity owner;
@@ -333,7 +333,7 @@ fun GameTestHelper.setContainerItem(pos: BlockPos, slot: Int, item: ItemStack) {
}
/**
* An alternative version ot [GameTestHelper.placeAt], which sets the player's held item first.
* An alternative version to [GameTestHelper.placeAt], which sets the player's held item first.
*
* This is required for compatibility with Forge, which uses the in-hand stack, rather than the stack requested.
*/
+3 -13
View File
@@ -5,23 +5,13 @@
plugins {
id("cc-tweaked.java-convention")
id("cc-tweaked.publishing")
id("cc-tweaked")
}
// Due to the slightly circular nature of our API, add the main API jars to the javadoc classpath.
val docApi by configurations.registering {
isTransitive = false
}
dependencies {
compileOnlyApi(libs.bundles.annotations)
"docApi"(project(":common-api"))
}
tasks.javadoc {
// Depend on the common API when publishing javadoc
classpath += docApi.get()
}
// 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)
+3 -9
View File
@@ -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: String by extra
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)
@@ -53,15 +47,15 @@ tasks.test {
systemProperty("cct.test-files", layout.buildDirectory.dir("tmp/testFiles").getAbsolutePath())
}
val checkChangelog by tasks.registering(cc.tweaked.gradle.CheckChangelog::class) {
version = modVersion
val checkChangelog = tasks.register<cc.tweaked.gradle.CheckChangelog>("checkChangelog") {
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.
@@ -103,10 +103,7 @@ public class HTTPAPI implements ILuaAPI {
if (requestMethod == null) {
httpMethod = postBody == null ? HttpMethod.GET : HttpMethod.POST;
} else {
httpMethod = HttpMethod.valueOf(requestMethod.toUpperCase(Locale.ROOT));
if (httpMethod == null || requestMethod.equalsIgnoreCase("CONNECT")) {
throw new LuaException("Unsupported HTTP method");
}
httpMethod = getMethod(requestMethod.toUpperCase(Locale.ROOT));
}
try {
@@ -174,9 +171,27 @@ public class HTTPAPI implements ILuaAPI {
}
}
private static HttpMethod getMethod(String method) throws LuaException {
// Reject CONNECT outright, as that requires the ability to read indefinitely.
if (method.equals("CONNECT")) throw new LuaException("Unsupported HTTP method");
// The HTTP specification allows the method to be any token[^1], which is all non-control/separator
// characters[^2]. However, we limit ourselves to a more reasonable subset. The allowed characters match
// what NGINX does[^3].
// [^1] https://www.rfc-editor.org/info/rfc2616/#section-5.1.1
// [^2] https://www.rfc-editor.org/info/rfc2616/#section-2.2
// [^3]: https://github.com/nginx/nginx/blob/5f54125dde0d144476155d8964f6ec3d449f578d/src/http/ngx_http_parse.c#L283-L285
if (method.isEmpty() || method.length() > 32 ||
!method.chars().allMatch(c -> (c >= 'A' && c <= 'Z') || c == '_' || c == '-')) {
throw new LuaException("Invalid HTTP method");
}
return HttpMethod.valueOf(method);
}
private HttpHeaders getHeaders(Map<?, ?> headerTable) throws LuaException {
HttpHeaders headers = new DefaultHttpHeaders();
for (Map.Entry<?, ?> entry : headerTable.entrySet()) {
for (var entry : headerTable.entrySet()) {
var value = entry.getValue();
if (entry.getKey() instanceof String && value instanceof String) {
try {
@@ -38,11 +38,13 @@ public class OSAPI implements ILuaAPI {
private int nextAlarmToken = 0;
private record Alarm(double time, int day) implements Comparable<Alarm> {
double totalTime() {
return day() * 24 + time();
}
@Override
public int compareTo(Alarm o) {
var t = day * 24.0 + time;
var ot = day * 24.0 + time;
return Double.compare(t, ot);
return Double.compare(totalTime(), o.totalTime());
}
}
@@ -83,7 +85,7 @@ public class OSAPI implements ILuaAPI {
while (it.hasNext()) {
var entry = it.next();
var alarm = entry.getValue();
var t = alarm.day * 24.0 + alarm.time;
var t = alarm.totalTime();
if (now >= t) {
apiEnvironment.queueEvent("alarm", entry.getIntKey());
it.remove();
@@ -333,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<String> locale) throws LuaException {
return switch (locale.orElse("ingame").toLowerCase(Locale.ROOT)) {
case "utc" -> getDayForCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC")));
@@ -372,6 +375,7 @@ public class OSAPI implements ILuaAPI {
* }</pre>
*/
@LuaFunction
@SuppressWarnings("InvalidParam") // Local vs Locale confusion
public final long epoch(Optional<String> locale) throws LuaException {
return switch (locale.orElse("ingame").toLowerCase(Locale.ROOT)) {
case "utc" -> getEpochForCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC"))); // Get utc epoch
@@ -397,7 +401,7 @@ public class OSAPI implements ILuaAPI {
*
* @param formatA The format of the string to return. This defaults to {@code %c}, which expands to a string similar to "Sat Dec 24 16:58:00 2011".
* @param timeA The timestamp to convert to a string. This defaults to the current time.
* @return The resulting formated string, or table.
* @return The resulting formatted string, or table.
* @throws LuaException If an invalid format is passed.
* @cc.since 1.83.0
* @cc.usage Print the current date in a user-friendly string.
@@ -18,7 +18,7 @@ import java.util.List;
* - Binary input/output ({@link #setOutput}/{@link #getInput}): These simply check if a redstone wire has any input or
* output. A signal strength of 1 and 15 are treated the same.
* - Analogue input/output ({@link #setAnalogOutput}/{@link #getAnalogInput}): These work with the actual signal
* strength of the redstone wired, from 0 to 15.
* strength of the redstone wires, from 0 to 15.
* - Bundled cables ({@link #setBundledOutput}/{@link #getBundledInput}): These interact with "bundled" cables, such
* as those from Project:Red. These allow you to send 16 separate on/off signals. Each channel corresponds to a
* colour, with the first being [`colors.white`] and the last [`colors.black`].
@@ -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<String> textA) throws LuaException {
var text = textA.value();
public final void write(Coerced<String> 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());
}
}
@@ -55,7 +55,7 @@ final class Generator<T> {
}
}
static void addArgType(Map<Class<?>, ArgMethods> types, Class<?> type, String name) throws ReflectiveOperationException {
private static void addArgType(Map<Class<?>, ArgMethods> types, Class<?> type, String name) throws ReflectiveOperationException {
types.put(type, ArgMethods.of(type, name));
}
@@ -291,7 +291,7 @@ public final class ComputerThread implements ComputerScheduler {
*
* @param executor The computer to execute work on.
*/
void queue(ExecutorImpl executor) {
private void queue(ExecutorImpl executor) {
computerLock.lock();
try {
if (state.get() != RUNNING) throw new IllegalStateException("ComputerThread is no longer running");
@@ -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);
@@ -145,7 +145,7 @@ public final class MemoryMount extends AbstractInMemoryMount<MemoryMount.FileEnt
if (destParent == null) throw new FileOperationException(dest, "Parent directory does not exist");
if (destParent.exists()) throw new FileOperationException(dest, FILE_EXISTS);
destParent.put(sourceParent.parent().remove(sourceParent.name()));
destParent.put(Nullability.assertNonNull(sourceParent.parent().remove(sourceParent.name())));
}
@Override
@@ -11,7 +11,7 @@ import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
/**
* Manages the state of redstone inputs and ouputs on a computer (or other redstone emitting block).
* Manages the state of redstone inputs and outputs on a computer (or other redstone emitting block).
* <p>
* As computers execute on a separate thread to the main Minecraft world, computers cannot immediately read or write
* redstone values. Instead, we maintain a copy of the block's redstone inputs and outputs, and sync that with the
@@ -15,7 +15,7 @@ public final class AtomicHelpers {
*
* @param atomic The atomic to increment.
* @param limit The maximum value of {@code value}.
* @return Whether the value was sucessfully incremented.
* @return Whether the value was successfully incremented.
*/
public static boolean incrementToLimit(AtomicInteger atomic, int limit) {
int value;
@@ -14,12 +14,6 @@ local expect = dofile("rom/modules/main/cc/expect.lua").expect
local native = http
local nativeHTTPRequest = http.request
local methods = {
GET = true, POST = true, HEAD = true,
OPTIONS = true, PUT = true, DELETE = true,
PATCH = true, TRACE = true,
}
local function check_key(options, key, ty, opt)
local value = options[key]
local valueTy = type(value)
@@ -41,8 +35,13 @@ local function check_request_options(options, body)
check_key(options, "redirect", "boolean", true)
check_key(options, "timeout", "number", true)
if options.method and not methods[options.method] then
error("Unsupported HTTP method", 3)
check_key(options, "method", "string", true)
if options.method then
local method = options.method:upper()
if method == "CONNECT" then error("Unsupported HTTP method", 3) end
if method == "" or #method > 32 or method:find("[^A-Z_-]") then
error("Invalid HTTP method", 3)
end
end
end
@@ -90,6 +89,7 @@ error or connection timeout.
@changed 1.105.0 Added support for custom timeouts.
@changed 1.109.0 The returned response now reads the body as raw bytes, rather
than decoding from UTF-8.
@changed 1.121.0 Relax HTTP verb validation to allow anything matching `[A-Z_-]{1,32}`.
@usage Make a request to [example.tweaked.cc](https://example.tweaked.cc),
and print the returned page.
@@ -144,6 +144,7 @@ error or connection timeout.
@changed 1.105.0 Added support for custom timeouts.
@changed 1.109.0 The returned response now reads the body as raw bytes, rather
than decoding from UTF-8.
@changed 1.121.0 Relax HTTP verb validation to allow anything matching `[A-Z_-]{1,32}`.
]]
function post(_url, _post, _headers, _binary)
if type(_url) == "table" then
@@ -197,6 +198,7 @@ from above are passed in as fields instead (for instance,
@changed 1.105.0 Added support for custom timeouts.
@changed 1.109.0 The returned response now reads the body as raw bytes, rather
than decoding from UTF-8.
@changed 1.121.0 Relax HTTP verb validation to allow anything matching `[A-Z_-]{1,32}`.
]]
function request(_url, _post, _headers, _binary)
local url
@@ -119,7 +119,7 @@ handleMetatable = {
- `a`: Returns the entire rest of the file.
- ~~`n`: Returns a number~~ (not implemented in CC).
These formats can be preceded by a `*` to make it compatible with Lua 5.1.
These formats can be preceded by a `*` to make it compatibility with Lua 5.1.
If no format is provided, `l` is assumed.
@@ -201,7 +201,7 @@ handleMetatable = {
--[[- Sets the buffering mode for an output file.
This has no effect under ComputerCraft, and exists with compatility
This has no effect under ComputerCraft, and exists for compatibility
with base Lua.
@tparam string mode The buffering mode.
@tparam[opt] number size The size of the buffer.
@@ -90,7 +90,7 @@ Several bug fixes:
* Ignore shader compilation errors when running with Pojav.
* Fix several issues with character input.
* Fix pocket computer dyes being lost when equipping/unequipping upgrades.
* Fix superflous warnings from allocation tracking.
* Fix superfluous warnings from allocation tracking.
* Fix `__lt`/`__le` not working on heterogeneous types.
* Many documentation fixes (Lemmmy, matematikaadit, McJack123).
* Fix `0` being treated as a valid colour in `window` and `colour.toBlit`.
@@ -441,7 +441,7 @@ Several bug fixes:
* Support custom proxies for HTTP requests (Lemmmy).
* The `speaker` program now errors when playing HTML files.
* `edit` now shows an error message when editing read-only files.
* Update Ukranian translation (SirEdvin).
* Update Ukrainian translation (SirEdvin).
Several bug fixes:
* Allow GPS hosts to only be 1 block apart.
@@ -5,5 +5,5 @@ http.request( url, [postData], [headers] )
http.get( url, [headers] )
http.post( url, postData, [headers] )
The HTTP API may be disabled in ComputerCraft.cfg
The HTTP API may be disabled in the server config.
A period of time after a http.request() call is made, a "http_success" or "http_failure" event will be raised. Arguments are the url and a file handle if successful. Arguments are nil, an error message, and (optionally) a file handle if the request failed. http.get() and http.post() block until this event fires instead.
@@ -1,4 +1,4 @@
paint is a program for creating images on Advanced Computers. Select colors from the color pallette on the right, and click on the canvas to draw. Press Ctrl to access the menu and save your pictures.
paint is a program for creating images on Advanced Computers. Select colors from the color palette on the right, and click on the canvas to draw. Press Ctrl to access the menu and save your pictures.
ex:
"edit mario" opens an image called "mario" for editing.
@@ -1,5 +1,5 @@
pastebin is a program for uploading files to and downloading files from pastebin.com. This is useful for sharing programs with other players.
The HTTP API must be enabled in ComputerCraft.cfg to use this program.
The HTTP API must be enabled in the server config to use this program.
ex:
"pastebin put foo" will upload the file "foo" to pastebin.com, and print the URL.
@@ -1,6 +1,6 @@
wget is a program for downloading files from the internet. This is useful for downloading programs created by other players.
If no filename is specified wget will try to determine the filename from the URL by stripping any anchors, parameters and trailing slashes and then taking everything remaining after the last slash.
The HTTP API must be enabled in ComputerCraft.cfg to use this program.
The HTTP API must be enabled in the server config to use this program.
ex:
"wget http://pastebin.com/raw/CxaWmPrX test" will download the file from the URL http://pastebin.com/raw/CxaWmPrX, and save it as "test".
"wget http://example.org/test.lua/?foo=bar#qzu" will download the file from the URL http://example.org/test.lua/?foo=bar#qzu and save it as "test.lua"
@@ -116,7 +116,7 @@ For instance, splitting the string `"a b c"` with the deliminator `" "`, would
return a table with three strings: `"a"`, `"b"`, and `"c"`.
By default, the deliminator is given as a [Lua pattern][pattern]. Passing `true`
to the `plain` argument will cause the deliminator to be treated as a litteral
to the `plain` argument will cause the deliminator to be treated as a literal
string.
[pattern]: https://www.lua.org/manual/5.3/manual.html#6.4.1
@@ -22,7 +22,7 @@ local fSpeedS
local bPaused
local Tick
local Blocks
local XOrgin, YOrgin
local XOrigin, YOrigin
local fLevel
local function reset()
@@ -39,7 +39,7 @@ local function reset()
bPaused = false
Tick = os.startTimer(Speed)
Blocks = 0
XOrgin, YOrgin = 1, 1
XOrigin, YOrigin = 1, 1
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
@@ -69,9 +69,9 @@ local function printCentred(yc, stg)
term.write(stg)
end
local function centerOrgin()
XOrgin = math.floor(TermW / 2 - SizeW / 2)
YOrgin = math.floor(TermH / 2 - SizeH / 2)
local function centerOrigin()
XOrigin = math.floor(TermW / 2 - SizeW / 2)
YOrigin = math.floor(TermH / 2 - SizeH / 2)
end
local function reMap()
@@ -272,10 +272,10 @@ local function drawMap()
local obj = tScreen[x][y]
if obj.ground == true then
paintutils.drawPixel(XOrgin + x, YOrgin + y + 1, cG)
paintutils.drawPixel(XOrigin + x, YOrigin + y + 1, cG)
end
if obj.wall == true then
paintutils.drawPixel(XOrgin + x, YOrgin + y + 1, cW)
paintutils.drawPixel(XOrigin + x, YOrigin + y + 1, cW)
end
local ex = tostring(tScreen[x][y].exit)
@@ -293,7 +293,7 @@ local function drawMap()
end
term.setBackgroundColor(cG)
term.setTextColor(ex)
term.setCursorPos(XOrgin + x, YOrgin + y + 1)
term.setCursorPos(XOrigin + x, YOrigin + y + 1)
print("X")
end
@@ -314,7 +314,7 @@ local function drawMap()
term.setTextColor(Cr)
term.setBackgroundColor(cG)
term.setCursorPos(XOrgin + x, YOrgin + y + 1)
term.setCursorPos(XOrigin + x, YOrigin + y + 1)
local sSide = string.sub(st, 1, 1)
if sSide == "a" then
@@ -331,7 +331,7 @@ local function drawMap()
end
if obj.space == true then
paintutils.drawPixel(XOrgin + x, YOrgin + y + 1, cS)
paintutils.drawPixel(XOrigin + x, YOrigin + y + 1, cS)
end
local rb = tostring(tScreen[x][y].robot)
@@ -350,7 +350,7 @@ local function drawMap()
end
term.setBackgroundColor(Cr)
term.setTextColor(colors.white)
term.setCursorPos(XOrgin + x, YOrgin + y + 1)
term.setCursorPos(XOrigin + x, YOrigin + y + 1)
local sSide = string.sub(rb, 1, 1)
if sSide == "a" then
print("^")
@@ -573,12 +573,12 @@ function InterFace.render()
Speed = fSpeedS and fSpeed or nSpeed
Tick = os.startTimer(Speed)
InterFace.drawBar()
elseif p3 - 1 < YOrgin + SizeH + 1 and p3 - 1 > YOrgin and
p2 < XOrgin + SizeW + 1 and p2 > XOrgin then
local eobj = tScreen[p2 - XOrgin][p3 - YOrgin - 1]
local erobj = tostring(tScreen[p2 - XOrgin][p3 - YOrgin - 1].robot)
elseif p3 - 1 < YOrigin + SizeH + 1 and p3 - 1 > YOrigin and
p2 < XOrigin + SizeW + 1 and p2 > XOrigin then
local eobj = tScreen[p2 - XOrigin][p3 - YOrigin - 1]
local erobj = tostring(tScreen[p2 - XOrigin][p3 - YOrigin - 1].robot)
if (erobj == "zz" or erobj == "nil") and not eobj.wall == true and not eobj.space == true and Blocks > 0 then
addWall(p2 - XOrgin, p3 - YOrgin - 1)
addWall(p2 - XOrigin, p3 - YOrigin - 1)
Blocks = Blocks - 1
InterFace.drawBar()
drawMap()
@@ -598,7 +598,7 @@ end
local function startG(LevelN)
drawStars()
loadLevel(LevelN)
centerOrgin()
centerOrigin()
drawMap()
InterFace.drawBar()
gRender("start")
@@ -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();
}
@@ -236,7 +236,7 @@ expect_mt.ne = expect_mt.not_equals
--- Assert that this expectation has something of the provided type
--
-- @tparam string exp_type The type to require this expectation to have
-- @throws If it does not have that thpe
-- @throws If it does not have that type
function expect_mt:type(exp_type)
local actual_type = type(self.value)
if exp_type ~= actual_type then
@@ -20,7 +20,7 @@ describe("The gps library", function()
describe("on fake computers", function()
local fake_computer = require "support.fake_computer"
local function gps_reciever(x, y, z, fn)
local function gps_receiver(x, y, z, fn)
local computer = fake_computer.make_computer(1, fn)
computer.position = vector.new(x, y, z)
fake_computer.add_api(computer, "rom/apis/gps.lua")
@@ -52,7 +52,7 @@ describe("The gps library", function()
end
it("locates a computer", function()
local computer, modem = gps_reciever(12, 23, 52, function(env)
local computer, modem = gps_receiver(12, 23, 52, function(env)
local x, y, z = env.gps.locate()
expect({ x, y, z }):same { 12, 23, 52 }
end)
@@ -69,7 +69,7 @@ describe("The gps library", function()
end)
it("fails to locate a computer with insufficient hosts", function()
local computer, modem = gps_reciever(12, 23, 52, function(env)
local computer, modem = gps_receiver(12, 23, 52, function(env)
local x, y, z = env.gps.locate()
expect({ x, y, z }):same { nil, nil, nil }
end)
@@ -85,7 +85,7 @@ describe("The gps library", function()
end)
it("doesn't fail on duplicate hosts", function()
local computer, modem = gps_reciever(12, 23, 52, function(env)
local computer, modem = gps_receiver(12, 23, 52, function(env)
local x, y, z = env.gps.locate()
expect({ x, y, z }):same { 12, 23, 52 }
end)
@@ -62,7 +62,7 @@ describe("cc.expect", function()
end)
describe("range", function()
it("works fith full args", function()
it("works with full args", function()
expect(e.range(1, 1, 1)):eq(1)
expect(e.range(2, 1, 3)):eq(2)
+7 -2
View File
@@ -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 {
+53 -36
View File
@@ -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: String by extra
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) {
@@ -49,11 +45,11 @@ addRemappedConfiguration("testWithIris")
configurations {
// Declare some configurations which are both included (jar-in-jar-ed) and a normal dependency (so they appear in
// our POM).
val includeRuntimeOnly by registering {
val includeRuntimeOnly = register("includeRuntimeOnly") {
isCanBeConsumed = false
isCanBeResolved = false
}
val includeImplementation by registering {
val includeImplementation = register("includeImplementation") {
isCanBeConsumed = false
isCanBeResolved = false
}
@@ -64,7 +60,7 @@ configurations {
// Declare a configuration for projects which are on the compile and runtime classpath, but not treated as
// dependencies. This is used for our local projects.
val localImplementation by registering {
val localImplementation = register("localImplementation") {
isCanBeResolved = false
isCanBeConsumed = false
}
@@ -98,30 +94,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 })
@@ -129,7 +138,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") {
@@ -174,7 +189,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
@@ -183,7 +198,7 @@ loom {
jvmArguments.add("-ea")
}
val testClient by registering {
val testClient = register("testClient") {
displayName = "Test Client"
client()
configureForGameTest()
@@ -220,6 +235,8 @@ loom {
}
tasks.processResources {
val modVersion = project.version as String
inputs.property("modVersion", modVersion)
var props = mapOf("version" to modVersion)
@@ -227,17 +244,7 @@ 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 by tasks.registering(net.fabricmc.loom.task.ValidateMixinNameTask::class) {
val validateMixinNames = tasks.register<net.fabricmc.loom.task.ValidateMixinNameTask>("validateMixinNames") {
source(sourceSets.main.get().output)
source(sourceSets.client.get().output)
source(sourceSets.testMod.get().output)
@@ -246,13 +253,23 @@ tasks.check { dependsOn(validateMixinNames) }
tasks.test { dependsOn(tasks.generateDLIConfig) }
tasks.verifyClassesMatch {
options.errorprone {
option("ModLoader", "fabric")
}
// Fabric 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<JavaExec>("runGametest") {
usesService(MinecraftRunnerService.get(gradle))
}
cct.jacoco(runGametest)
tasks.check { dependsOn(runGametest) }
val runGametestClient by tasks.registering(ClientJavaExec::class) {
val runGametestClient = tasks.register<ClientJavaExec>("runGametestClient") {
description = "Runs client-side gametests with no mods"
copyFrom("runTestClient")
@@ -260,7 +277,7 @@ val runGametestClient by tasks.registering(ClientJavaExec::class) {
}
cct.jacoco(runGametestClient)
val runGametestClientWithSodium by tasks.registering(ClientJavaExec::class) {
val runGametestClientWithSodium = tasks.register<ClientJavaExec>("runGametestClientWithSodium") {
description = "Runs client-side gametests with Sodium"
copyFrom("runTestClient")
@@ -269,7 +286,7 @@ val runGametestClientWithSodium by tasks.registering(ClientJavaExec::class) {
}
cct.jacoco(runGametestClientWithSodium)
val runGametestClientWithIris by tasks.registering(ClientJavaExec::class) {
val runGametestClientWithIris = tasks.register<ClientJavaExec>("runGametestClientWithIris") {
description = "Runs client-side gametests with Iris"
copyFrom("runTestClient")
@@ -17,7 +17,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PlayerChunkSender.class)
class PlayerChunkSenderMixin {
@Inject(method = "sendChunk", at = @At("TAIL"))
@SuppressWarnings("UnusedMethod")
@SuppressWarnings("unused")
private static void onPlayerLoadedChunk(ServerGamePacketListenerImpl connection, ServerLevel server, LevelChunk chunk, CallbackInfo ci) {
CommonHooks.onChunkWatch(chunk, connection.player);
}
+6 -6
View File
@@ -2,20 +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"))
// FIXME: This should be implementation (and in the common Forge config)
// but NeoGradle does weird things and we end up with two Forge deps on the
// classpath - https://github.com/neoforged/NeoGradle/issues/162.
compileOnly("net.neoforged:neoforge:${libs.versions.neoForge.get()}")
compileOnly(commonClasses(project(":common-api")))
clientApi(clientClasses(project(":common-api")))
embeddedProject(project(":common-api"))
}
tasks.javadoc {
+69 -47
View File
@@ -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: String by extra
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)
}
neoForge {
val computercraft by mods.registering {
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 computercraftDatagen by mods.registering {
cct.sourceDirectories.get().forEach {
if (it.classes) sourceSet(it.sourceSet)
}
val computercraft = mods.register("computercraft") {
addComputerCraft()
}
val computercraftDatagen = mods.register("computercraftDatagen") {
addComputerCraft()
sourceSet(sourceSets.datagen.get())
sourceSet("datagen", ":common")
}
val testMod by mods.registering {
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 by mods.registering {
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 @@ neoForge {
"--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 @@ neoForge {
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")
@@ -131,7 +145,7 @@ configurations {
additionalRuntimeClasspath { extendsFrom(jarJar.get()) }
val testAdditionalRuntimeClasspath by registering {
val testAdditionalRuntimeClasspath = register("testAdditionalRuntimeClasspath") {
isCanBeResolved = true
isCanBeConsumed = false
// Prevent ending up with multiple versions of libraries on the classpath.
@@ -149,7 +163,7 @@ configurations {
// Declare a configuration for projects which are on the compile and runtime classpath, but not treated as
// dependencies. This is used for our local projects.
val localImplementation by registering {
val localImplementation = register("localImplementation") {
isCanBeResolved = false
isCanBeConsumed = false
}
@@ -167,7 +181,8 @@ dependencies {
compileOnly(libs.create.forge) { 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")))
@@ -179,29 +194,32 @@ 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")))
"testWithIris"(libs.iris.forge)
"testWithIris"(libs.sodium.forge)
testImplementation(testFixtures(project(":common")))
testRuntimeOnly(libs.bundles.testRuntime)
embeddedProject(project(":core-api"))
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("neoVersion", libs.versions.neoForge)
@@ -214,20 +232,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
@@ -236,20 +251,30 @@ tasks.test {
systemProperty("cct.test-files", layout.buildDirectory.dir("tmp/testFiles").getAbsolutePath())
}
tasks.verifyClassesMatch {
options.errorprone {
option("ModLoader", "forge")
}
// 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<JavaExec>("runGametest") {
usesService(MinecraftRunnerService.get(gradle))
}
cct.jacoco(runGametest)
tasks.check { dependsOn(runGametest) }
val runGametestClient by tasks.registering(ClientJavaExec::class) {
val runGametestClient = tasks.register<ClientJavaExec>("runGametestClient") {
description = "Runs client-side gametests with no mods"
copyFromForge("runTestClient")
tags("client")
}
cct.jacoco(runGametestClient)
val runGametestClientWithIris by tasks.registering(ClientJavaExec::class) {
val runGametestClientWithIris = tasks.register<ClientJavaExec>("runGametestClientWithIris") {
description = "Runs client-side gametests with Iris"
copyFromForge("runGameTestClient")
@@ -269,6 +294,3 @@ tasks.register("checkClient") {
modPublishing {
output = tasks.jar
}
// TODO: Remove once https://github.com/modrinth/minotaur/pull/72 is merged.
modrinth { loaders = listOf("neoforge") }
+1 -3
View File
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.CCTweakedJavaVersions
plugins {
kotlin("jvm")
@@ -31,8 +31,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",
@@ -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)
@@ -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 {

Some files were not shown because too many files have changed in this diff Show More