From 9d916b178597e99258813eaf57892f15f6ee9903 Mon Sep 17 00:00:00 2001 From: soloturn Date: Sun, 23 Aug 2026 14:04:17 +0200 Subject: [PATCH 1/3] build: convert Gradle build scripts from Groovy to Kotlin DSL Converts every .gradle file in the repo (root, build-logic's precompiled plugins, desktop/engine/modules/templates) to .gradle.kts. Notable non-mechanical changes required along the way: - build-logic switches from the groovy-gradle-plugin to kotlin-dsl plugin; its precompiled scripts move from src/main/groovy to src/main/kotlin. - GestaltExtension is extracted into its own GestaltExtension.kt: Kotlin script files don't share top-level type declarations across files the way Groovy scripts implicitly do. - Several scripts (destination-sol-jre, destination-sol-module, engine/build.gradle.kts, desktop/build.gradle.kts) look up the base/idea/eclipse extensions explicitly via the()/configure() instead of the generated type-safe accessors: those plugins are applied transitively through another precompiled plugin rather than directly in the consuming script's own plugins{} block, and Gradle's accessor generation doesn't chase through that chain. - destination-sol-ide.gradle.kts's IDEA XML patching (compiler.xml, misc.xml, checkstyle-idea.xml) is ported off Groovy's Node '.@attr' sugar onto groovy.util.Node's plain Java API via small child()/attr() helpers. Verified by actually running `gradlew idea` - not just compiling - since this is exactly the kind of dynamically-typed code a naive port could silently get wrong. - gestalt-module.gradle.kts's module.json/module.txt parsing moves from Groovy's JsonSlurper to Gson (added as a build-logic dependency), fully typed. - The publishing blocks in gestalt-module, terasology-publish-common and destination-sol-module all configure the same, project-name-keyed MavenPublication. Groovy's "$project.name"(MavenPublication){} sugar silently reconfigures an existing publication; Kotlin's create<>() does not, it throws. Switched all three to maybeCreate(). - Dropped ipr.withXml{}/workspace.iws.withXml{} calls to ideaActivateCheckstyle/Copyright/Annotations/Git/Gradle, ideaMakeAutomatically and ideaRunConfig in the root build script: none of the 7 functions have existed since config/gradle/ide.gradle was deleted in 68193863 (Dec 2022). Groovy only fails on missing methods at runtime, when `gradle idea`/`ipr`/`iws` actually executes, which nothing does - so this has been silently dead for ~3 years. Kept the one working sibling line (wildcards.remove) alongside it. - .gitignore's !modules/subprojects.gradle and !libs/subprojects.gradle negations are updated to the new .kts filenames, otherwise the broader modules/*/libs/* ignore rules swallow the renamed files. Verified: gradlew help configures the full project (root, build-logic, desktop, engine, templates, and the auto-templated modules:core). :engine:compileJava and :desktop:compileJava both build clean. gradlew idea and :desktop:eclipse both run end-to-end, exercising the hand-ported XML logic at runtime rather than just at compile time. No behavior change intended anywhere in this diff; the findbugs -> error-prone swap is a separate, follow-up PR. Co-Authored-By: Claude Sonnet 5 --- .gitignore | 5 +- build-logic/build.gradle | 19 -- build-logic/build.gradle.kts | 15 ++ .../{settings.gradle => settings.gradle.kts} | 0 .../groovy/destination-sol-constants.gradle | 11 - .../main/groovy/destination-sol-ide.gradle | 87 -------- .../main/groovy/destination-sol-java.gradle | 33 --- .../main/groovy/destination-sol-jre.gradle | 73 ------- .../main/groovy/destination-sol-module.gradle | 76 ------- .../destination-sol-repositories.gradle | 45 ---- .../src/main/groovy/gestalt-8-module.gradle | 20 -- .../src/main/groovy/gestalt-module.gradle | 112 ---------- .../src/main/groovy/terasology-metrics.gradle | 108 ---------- .../groovy/terasology-publish-common.gradle | 53 ----- .../src/main/kotlin/GestaltExtension.kt | 6 + .../destination-sol-common.gradle.kts} | 20 +- .../destination-sol-constants.gradle.kts | 9 + .../kotlin/destination-sol-ide.gradle.kts | 97 +++++++++ .../kotlin/destination-sol-java.gradle.kts | 33 +++ .../kotlin/destination-sol-jre.gradle.kts | 78 +++++++ .../kotlin/destination-sol-module.gradle.kts | 87 ++++++++ .../destination-sol-repositories.gradle.kts | 45 ++++ .../main/kotlin/gestalt-8-module.gradle.kts | 22 ++ .../src/main/kotlin/gestalt-module.gradle.kts | 108 ++++++++++ .../gestalt-repositories.gradle.kts} | 16 +- .../main/kotlin/terasology-metrics.gradle.kts | 99 +++++++++ .../terasology-publish-common.gradle.kts | 59 +++++ build.gradle | 177 --------------- build.gradle.kts | 176 +++++++++++++++ desktop/build.gradle | 186 ---------------- desktop/build.gradle.kts | 201 ++++++++++++++++++ engine/build.gradle | 127 ----------- engine/build.gradle.kts | 139 ++++++++++++ libs/subprojects.gradle | 23 -- libs/subprojects.gradle.kts | 23 ++ modules/subprojects.gradle | 25 --- modules/subprojects.gradle.kts | 25 +++ settings.gradle | 32 --- settings.gradle.kts | 32 +++ templates/build.gradle | 3 - templates/build.gradle.kts | 3 + 41 files changed, 1278 insertions(+), 1230 deletions(-) delete mode 100644 build-logic/build.gradle create mode 100644 build-logic/build.gradle.kts rename build-logic/{settings.gradle => settings.gradle.kts} (100%) delete mode 100644 build-logic/src/main/groovy/destination-sol-constants.gradle delete mode 100644 build-logic/src/main/groovy/destination-sol-ide.gradle delete mode 100644 build-logic/src/main/groovy/destination-sol-java.gradle delete mode 100644 build-logic/src/main/groovy/destination-sol-jre.gradle delete mode 100644 build-logic/src/main/groovy/destination-sol-module.gradle delete mode 100644 build-logic/src/main/groovy/destination-sol-repositories.gradle delete mode 100644 build-logic/src/main/groovy/gestalt-8-module.gradle delete mode 100644 build-logic/src/main/groovy/gestalt-module.gradle delete mode 100644 build-logic/src/main/groovy/terasology-metrics.gradle delete mode 100644 build-logic/src/main/groovy/terasology-publish-common.gradle create mode 100644 build-logic/src/main/kotlin/GestaltExtension.kt rename build-logic/src/main/{groovy/destination-sol-common.gradle => kotlin/destination-sol-common.gradle.kts} (59%) create mode 100644 build-logic/src/main/kotlin/destination-sol-constants.gradle.kts create mode 100644 build-logic/src/main/kotlin/destination-sol-ide.gradle.kts create mode 100644 build-logic/src/main/kotlin/destination-sol-java.gradle.kts create mode 100644 build-logic/src/main/kotlin/destination-sol-jre.gradle.kts create mode 100644 build-logic/src/main/kotlin/destination-sol-module.gradle.kts create mode 100644 build-logic/src/main/kotlin/destination-sol-repositories.gradle.kts create mode 100644 build-logic/src/main/kotlin/gestalt-8-module.gradle.kts create mode 100644 build-logic/src/main/kotlin/gestalt-module.gradle.kts rename build-logic/src/main/{groovy/gestalt-repositories.gradle => kotlin/gestalt-repositories.gradle.kts} (54%) create mode 100644 build-logic/src/main/kotlin/terasology-metrics.gradle.kts create mode 100644 build-logic/src/main/kotlin/terasology-publish-common.gradle.kts delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 desktop/build.gradle create mode 100644 desktop/build.gradle.kts delete mode 100644 engine/build.gradle create mode 100644 engine/build.gradle.kts delete mode 100644 libs/subprojects.gradle create mode 100644 libs/subprojects.gradle.kts delete mode 100644 modules/subprojects.gradle create mode 100644 modules/subprojects.gradle.kts delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts delete mode 100644 templates/build.gradle create mode 100644 templates/build.gradle.kts diff --git a/.gitignore b/.gitignore index cab7d48b0..29a8df20f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,10 +11,11 @@ gwt steam modules/* modules/**/build.gradle +modules/**/build.gradle.kts !modules/core -!modules/subprojects.gradle +!modules/subprojects.gradle.kts libs/* -!libs/subprojects.gradle +!libs/subprojects.gradle.kts ## GWT war/ diff --git a/build-logic/build.gradle b/build-logic/build.gradle deleted file mode 100644 index 11671d72d..000000000 --- a/build-logic/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -plugins { - id('groovy-gradle-plugin') -} - -repositories { - gradlePluginPortal() -} - -compileJava { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} - -dependencies { - implementation 'com.github.spotbugs.snom:spotbugs-gradle-plugin:5.2.3' - implementation 'ru.vyarus:gradle-animalsniffer-plugin:2.0.1' - implementation 'de.undercouch:gradle-download-task:5.7.0' - implementation 'gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext:1.4.1' -} \ No newline at end of file diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 000000000..bf027c8e7 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + `kotlin-dsl` +} + +repositories { + gradlePluginPortal() +} + +dependencies { + implementation("com.google.code.gson:gson:2.10.1") + implementation("com.github.spotbugs.snom:spotbugs-gradle-plugin:5.2.3") + implementation("ru.vyarus:gradle-animalsniffer-plugin:2.0.1") + implementation("de.undercouch:gradle-download-task:5.7.0") + implementation("gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext:1.4.1") +} diff --git a/build-logic/settings.gradle b/build-logic/settings.gradle.kts similarity index 100% rename from build-logic/settings.gradle rename to build-logic/settings.gradle.kts diff --git a/build-logic/src/main/groovy/destination-sol-constants.gradle b/build-logic/src/main/groovy/destination-sol-constants.gradle deleted file mode 100644 index fbea5790f..000000000 --- a/build-logic/src/main/groovy/destination-sol-constants.gradle +++ /dev/null @@ -1,11 +0,0 @@ -ext { - appName = 'DestinationSol' - - engineVersion = '2.1.0' - gestaltVersion = '8.0.2-SNAPSHOT' - gdxVersion = '1.12.1' - // The LibGDX controllers library is versioned differently to the main LibGDX versions. - // See https://github.com/libgdx/gdx-controllers/wiki/Compatibility for compatible versions. - gdxControllersVersion = '2.2.3' - nuiVersion = '4.0.0-SNAPSHOT' -} \ No newline at end of file diff --git a/build-logic/src/main/groovy/destination-sol-ide.gradle b/build-logic/src/main/groovy/destination-sol-ide.gradle deleted file mode 100644 index 869e29732..000000000 --- a/build-logic/src/main/groovy/destination-sol-ide.gradle +++ /dev/null @@ -1,87 +0,0 @@ -plugins { - id 'eclipse' - id 'idea' - id 'org.jetbrains.gradle.plugin.idea-ext' -} - -import groovy.xml.XmlParser -import groovy.xml.XmlNodePrinter - -ext { - ideaPatchAnnotationProcessors = { XmlProvider provider -> - var profile = provider.asNode().component - .find { it.@name == 'CompilerConfiguration' } - .annotationProcessing.profile - for (Node profileNode : profile) { - def moduleName = profileNode.module.@name[0] - - String sourceOutputDir, sourceTestOutputDir - - if (moduleName.startsWith('DestinationSol.modules.')) { - // Modules output to a unified build directory. - sourceOutputDir = "../generated/sources/annotationProcessor/java/main" - sourceTestOutputDir = "../generated/sources/annotationProcessor/java/main" - } else { - // Normal libraries use separated build directories instead. - sourceOutputDir = "../../../generated/sources/annotationProcessor/java/main" - sourceTestOutputDir = "../../../generated/sources/annotationProcessor/java/test" - } - - if (profileNode.sourceOutputDir.size() == 0) { - profileNode.appendNode("sourceOutputDir", [name: sourceOutputDir]) - } else { - profileNode.sourceOutputDir.@name = sourceOutputDir - } - if (profileNode.sourceTestOutputDir.size() == 0) { - profileNode.appendNode("sourceTestOutputDir", [name: sourceTestOutputDir]) - } else { - profileNode.sourceTestOutputDir.@name = sourceTestOutputDir - } - if (profileNode.outputRelativeToContentRoot.size() == 0) { - profileNode.appendNode("outputRelativeToContentRoot", [value: false]) - } else { - profileNode.outputRelativeToContentRoot.@value = false - } - } - } - - ideaPatchEntryPoints = { XmlProvider provider -> - var component = provider.asNode().component - .find { it.@name == 'EntryPointsManager' } - if (component == null) { - return; - } - if (component.list.size() != 0) { - component.remove(component.list) - } - Node entryPointsList = component.appendNode("list", [size: 5]) - entryPointsList.appendNode("item", [index: 0, class: 'java.lang.String', itemvalue: 'org.destinationsol.game.attributes.RegisterUpdateSystem']) - entryPointsList.appendNode("item", [index: 1, class: 'java.lang.String', itemvalue: 'org.destinationsol.game.console.annotations.Command']) - entryPointsList.appendNode("item", [index: 2, class: 'java.lang.String', itemvalue: 'org.terasology.gestalt.assets.module.annotations.RegisterAssetFileFormat']) - entryPointsList.appendNode("item", [index: 3, class: 'java.lang.String', itemvalue: 'org.terasology.gestalt.assets.module.annotations.RegisterAssetType']) - entryPointsList.appendNode("item", [index: 4, class: 'java.lang.String', itemvalue: 'org.terasology.context.annotation.Service']) - - if (component.writeAnnotations.size() != 0) { - component.remove(component.writeAnnotations) - } - Node writeAnnotations = component.appendNode("writeAnnotations", []) - writeAnnotations.appendNode("writeAnnotation", [name: "javax.inject.Inject"]) - writeAnnotations.appendNode("writeAnnotation", [name: "org.destinationsol.common.In"]) - } - - ideaPatchCheckstyle = { XmlProvider provider -> - var checkstyleConfigs = provider.asNode().component - .find { it.@name == 'CheckStyle-IDEA' } - .option.find { it.@name == 'locations' }.list - var terasologyConfig = checkstyleConfigs.ConfigurationLocation.find { it.@id == 'terasology-style'} - if (terasologyConfig != null) { - println "$terasologyConfig" - checkstyleConfigs.ConfigurationLocation.remove(terasologyConfig) - } - terasologyConfig = checkstyleConfigs[0].appendNode('ConfigurationLocation', - [id: 'terasology-style', type: "LOCAL_FILE", scope: "All", description: "Terasology Style"]) - terasologyConfig.value = '$PROJECT_DIR$/config/metrics/checkstyle/checkstyle.xml' - var terasologyConfigProperties = terasologyConfig.appendNode("option", [name: "properties"]).appendNode("map", []) - terasologyConfigProperties.appendNode("entry", [key: "sameDir", value: '$PROJECT_DIR$/config/metrics/checkstyle']) - } -} \ No newline at end of file diff --git a/build-logic/src/main/groovy/destination-sol-java.gradle b/build-logic/src/main/groovy/destination-sol-java.gradle deleted file mode 100644 index 7c8f31a1b..000000000 --- a/build-logic/src/main/groovy/destination-sol-java.gradle +++ /dev/null @@ -1,33 +0,0 @@ -plugins { - id 'java' -} - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - - withJavadocJar() - withSourcesJar() -} - -compileJava { - options.encoding = 'UTF-8' - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - - // The release flag is required for Java 9+. - if (JavaVersion.current().isJava9Compatible()) { - options.release = 17 - } -} - -compileTestJava { - options.encoding = 'UTF-8' - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - - // The release flag is required for Java 9+. - if (JavaVersion.current().isJava9Compatible()) { - options.release = 17 - } -} \ No newline at end of file diff --git a/build-logic/src/main/groovy/destination-sol-jre.gradle b/build-logic/src/main/groovy/destination-sol-jre.gradle deleted file mode 100644 index bdf6bd01a..000000000 --- a/build-logic/src/main/groovy/destination-sol-jre.gradle +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2022 The Terasology Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -plugins { - id 'de.undercouch.download' -} - -// Uses Bellsoft Liberica JRE -def jreVersion = '11.0.19+7' -def jreUrlBase = "https://download.bell-sw.com/java/$jreVersion/bellsoft-jre$jreVersion" -def jreUrlFilenames = [ - lwjreLinux64 : 'linux-amd64.tar.gz', - lwjre : 'windows-i586.zip', - lwjreOSX : 'macos-amd64.zip', - lwjreOSXArm : 'macos-aarch64.zip' -] - -tasks.register('downloadJreAll') { - group 'Download' - description 'Downloads JRE for all platforms' -} - -jreUrlFilenames.each { os, file -> - def downloadTask = tasks.register("downloadJre$os") { - group 'Download' - description "Downloads JRE for $os" - - def packedJre = new File("$rootDir/jre/$jreVersion/$file") - def unpackedJre = base.distsDirectory.dir("app/$os").get().asFile - - doFirst { - download.run { - src "$jreUrlBase-$file" - dest packedJre - overwrite false - } - } - - doLast { - // Unpack the JRE - if (!unpackedJre.exists()) { - unpackedJre.mkdirs() - copy { - from(file.endsWith("zip") - ? zipTree(packedJre) - : tarTree(packedJre)) { - eachFile { fcd -> - fcd.relativePath = new RelativePath( - true, fcd.relativePath.segments.drop(1)) - } - includeEmptyDirs = false - } - into unpackedJre - } - } - } - } - - downloadJreAll.dependsOn downloadTask -} diff --git a/build-logic/src/main/groovy/destination-sol-module.gradle b/build-logic/src/main/groovy/destination-sol-module.gradle deleted file mode 100644 index 26e52fe62..000000000 --- a/build-logic/src/main/groovy/destination-sol-module.gradle +++ /dev/null @@ -1,76 +0,0 @@ -plugins { - id 'destination-sol-constants' - id 'gestalt-8-module' - id 'destination-sol-common' -} - -group = 'org.destinationsol.modules' - -dependencies { - api (rootProject.findProject("engine") ?: "org.destinationsol.engine:engine:$engineVersion") -} - -gestalt { - modulesPackage = "org.destinationsol.modules" - moduleMetadataFileName = "module.json" -} - -publishing { - publications { - "$project.name"(MavenPublication) { - pom { - url = "https://github.com/DestinationSol/${project.name}" - licenses { - license { - name = 'Apache-2.0' - url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' - distribution = 'repo' - } - } - issueManagement { - system = 'GitHub' - url = "https://github.com/DestinationSol/${project.name}/issues" - } - scm { - connection = "scm:git:https://github.com/DestinationSol/${project.name}.git" - developerConnection = "scm:git:ssh://github.com/DestinationSol/${project.name}.git" - url = "scm:git:https://github.com/DestinationSol/${project.name}.git" - } - } - } - } -} - -// Generate the module directory structure if missing -tasks.register('createSkeleton') { - mkdir('assets') - mkdir('assets/music') - mkdir('assets/sounds') - mkdir('assets/textures') - mkdir('assets/configs') - mkdir('assets/asteroids') - mkdir('assets/schemas') - mkdir('assets/prefabs') - mkdir('assets/ui') - mkdir('assets/skins') - mkdir('overrides') - mkdir('deltas') - mkdir('src/main/java') - mkdir('src/test/java') -} - -idea { - module { - inheritOutputDirs = false - outputDir = file('build/classes') - testOutputDir = file('build/testClasses') - downloadSources = true - } -} - -// For Eclipse just make sure the classpath is right -eclipse { - classpath { - defaultOutputDir = file('build/classes') - } -} \ No newline at end of file diff --git a/build-logic/src/main/groovy/destination-sol-repositories.gradle b/build-logic/src/main/groovy/destination-sol-repositories.gradle deleted file mode 100644 index 416ef11c5..000000000 --- a/build-logic/src/main/groovy/destination-sol-repositories.gradle +++ /dev/null @@ -1,45 +0,0 @@ -repositories { - mavenCentral() { - content { - // Terasology's libraries/modules aren't on maven central, so don't bother looking there. - excludeGroupByRegex('org\\.terasology(\\..+)?') - // Same for Destination Sol. - excludeGroupByRegex('org\\.destinationsol(\\..+)?') - } - } - - // Repos for LibGDX - maven { - url "https://oss.sonatype.org/content/repositories/snapshots/" - content { - includeGroupByRegex('com\\.badlogicgames.gdx(\\..+)?') - } - } - maven { - url "https://oss.sonatype.org/content/repositories/releases/" - content { - includeGroupByRegex('com\\.badlogicgames.gdx(\\..+)?') - } - } - - // everit-org JSON schema dependency - maven { - url "https://jitpack.io" - content { - includeModule('com.github.everit-org.json-schema', 'org.everit.json.schema') - } - } - - // Terasology Artifactory for any shared libs - maven { - url "https://artifactory.terasology.io/artifactory/virtual-repo-live" - content { - includeGroupByRegex('org\\.terasology(\\..+)?') - includeGroupByRegex('org\\.destinationsol(\\..+)?') - // A copy of jpastebin is hosted here - includeModule('brianbb', 'jpastebin') - } - } - - google() -} \ No newline at end of file diff --git a/build-logic/src/main/groovy/gestalt-8-module.gradle b/build-logic/src/main/groovy/gestalt-8-module.gradle deleted file mode 100644 index 121716a31..000000000 --- a/build-logic/src/main/groovy/gestalt-8-module.gradle +++ /dev/null @@ -1,20 +0,0 @@ -plugins { - id 'gestalt-module' -} - -compileJava { - // Specify the module's assets as pre-requisites for compilation, so that they force re-compilation when they change. - inputs.files sourceSets.main.resources.srcDirs - // Asset lists are cached by gestalt-di's annotation processors, which run just before compilation begins. - // Without these manifests, or with outdated manifests, the game won't know what assets are present. - options.compilerArgs = ["-Aresource=${sourceSets.main.resources.srcDirs.join(File.pathSeparator)}"] -} -compileTestJava { - // See above comments. - inputs.files sourceSets.test.resources.srcDirs - options.compilerArgs = ["-Aresource=${sourceSets.test.resources.srcDirs.join(File.pathSeparator)}"] -} - -dependencies { - annotationProcessor "org.terasology.gestalt:gestalt-inject-java:$gestaltVersion" -} \ No newline at end of file diff --git a/build-logic/src/main/groovy/gestalt-module.gradle b/build-logic/src/main/groovy/gestalt-module.gradle deleted file mode 100644 index 374103a83..000000000 --- a/build-logic/src/main/groovy/gestalt-module.gradle +++ /dev/null @@ -1,112 +0,0 @@ -plugins { - //noinspection JavaPluginLanguageLevel - id 'java-library' - id 'gestalt-repositories' - id 'terasology-publish-common' -} - -interface GestaltExtension { - Property getModulesPackage(); - Property getModuleMetadataFileName(); -} - -GestaltExtension gestaltExtension = extensions.create('gestalt', GestaltExtension); - -gestalt { - modulesPackage = "org.terasology.gestalt.modules" - moduleMetadataFileName = "module.txt" -} - -// Change the output dir of each module -sourceSets { - main { - java.destinationDirectory.set(new File("$buildDir/classes")) - } - test { - java.destinationDirectory.set(new File("$buildDir/testClasses")) - } -} - -jar { - from ('module.json') { - into '' - } - from ('assets') { - into 'assets' - } - from ('overrides') { - into 'overrides' - } - from ('deltas') { - into 'deltas' - } -} - -configurations { - gestaltModule - api.extendsFrom(gestaltModule) -} - -import groovy.json.JsonSlurper -afterEvaluate { - File moduleMetadataFile = file(gestaltExtension.moduleMetadataFileName.get()) - if (!moduleMetadataFile.exists()) { - println "${gestaltExtension.moduleMetadataFileName.get()} does not exist!" - throw new GradleException("Failed to find ${gestaltExtension.moduleMetadataFileName.get()} for module " + project.name) - } - - ext { - moduleMetadata = new JsonSlurper().parse(moduleMetadataFile) - modulesRoot = properties.modulesRoot ?: "$rootProject/modules" - } - - version = ext.moduleMetadata.version - description = ext.moduleMetadata.description - - publishing { - publications { - "$project.name"(MavenPublication) { - pom { - name = "$project.name" - description = "$project.description" - if (moduleMetadata.author) { - developers { - for (String developerId : moduleMetadata.author.split(',')) { - developer { - id = developerId.trim() - } - } - } - } - } - } - } - } - - dependencies { - moduleMetadata.dependencies.each { dependency -> - if (!dependency.optional) { - //noinspection DependencyNotationArgument - gestaltModule (group: gestaltExtension.modulesPackage.get(), name: dependency.id) { - if (dependency.minVersion && !dependency.maxVersion) { - version { - strictly "[${dependency.minVersion},)" - } - } - - if (!dependency.minVersion && dependency.maxVersion) { - version { - strictly "(,${dependency.maxVersion}[" - } - } - - if (dependency.minVersion && dependency.maxVersion) { - version { - strictly "[${dependency.minVersion}, ${dependency.maxVersion}[" - } - } - } - } - } - } -} \ No newline at end of file diff --git a/build-logic/src/main/groovy/terasology-metrics.gradle b/build-logic/src/main/groovy/terasology-metrics.gradle deleted file mode 100644 index 72fba40ad..000000000 --- a/build-logic/src/main/groovy/terasology-metrics.gradle +++ /dev/null @@ -1,108 +0,0 @@ -import com.github.spotbugs.snom.SpotBugsTask - -plugins { - //noinspection JavaPluginLanguageLevel - id 'java' - id 'project-report' - id 'checkstyle' - id 'pmd' - id 'com.github.spotbugs' - id 'jacoco' - id 'ru.vyarus.animalsniffer' -} - -configurations { - codeMetrics -} - -dependencies { - // Config for our code analytics lives in a centralized repo: https://github.com/MovingBlocks/TeraConfig - codeMetrics(group: 'org.terasology.config', name: 'codemetrics', version: '2.2.0', ext: 'zip') - - pmd("net.sourceforge.pmd:pmd-ant:7.26.0") - pmd("net.sourceforge.pmd:pmd-core:7.26.0") - pmd("net.sourceforge.pmd:pmd-java:7.26.0") - - signature('com.toasttab.android:gummy-bears-api-24:0.15.0:coreLib2@signature') -} - -animalsniffer { - // java.nio.* APIs can be desugared by D8. java.io.File.toPath() also needs to be excluded. - ignore = ["java.nio.file.*", "java.io.File"] -} - -jacoco { - toolVersion = "0.8.15" -} - -jacocoTestReport { - dependsOn test // Despite doc saying this should be automatic we need to explicitly add it anyway :-( - reports { - // We only use the .exec report for display in Jenkins and such. More could be enabled if desired. - xml.required = false - csv.required = false - html.required = false - } -} - -checkstyle { - ignoreFailures = true - configFile = new File(rootDir, 'config/metrics/checkstyle/checkstyle.xml') - toolVersion = "10.2" - configDirectory.set(configFile.parentFile) - configProperties.samedir = checkstyle.configFile.parentFile -} - -pmd { - ignoreFailures = true - ruleSetFiles = files("$rootDir/config/metrics/pmd/pmd.xml") - // By default, gradle uses both ruleset file AND the rulesets. Override the ruleSets to use only those from the file - ruleSets = [] -} - -spotbugs { - toolVersion = '4.8.1' - ignoreFailures = true - excludeFilter = new File(rootDir, "config/metrics/findbugs/findbugs-exclude.xml") -} -tasks.spotbugsMain { - reports.create('xml') { - enabled = true - outputLocation = file("$buildDir/reports/spotbugs/main/spotbugs.xml") - } -} - -var extractMetricsConfig = rootProject.tasks.findByName("extractMetricsConfig") -if (extractMetricsConfig != null) { - // Task already present, so no need to re-register. -} else { - extractMetricsConfig = rootProject.tasks.register("extractMetricsConfig", Copy) { - description = "Extracts our configuration files from the zip we fetched as a dependency" - from { - configurations.codeMetrics.collect { - zipTree(it) - } - } - into "$rootDir/config/metrics" - } -} - -spotbugsMain.dependsOn extractMetricsConfig -pmdMain.dependsOn extractMetricsConfig - -tasks.withType(Checkstyle).configureEach { - group = 'Reporting' - dependsOn extractMetricsConfig -} - -tasks.withType(Pmd).configureEach { - dependsOn extractMetricsConfig - group = 'Reporting' -} - -tasks.withType(SpotBugsTask).configureEach { - dependsOn extractMetricsConfig - group = 'Reporting' -} - -check.dependsOn extractMetricsConfig \ No newline at end of file diff --git a/build-logic/src/main/groovy/terasology-publish-common.gradle b/build-logic/src/main/groovy/terasology-publish-common.gradle deleted file mode 100644 index 2c0c8c1eb..000000000 --- a/build-logic/src/main/groovy/terasology-publish-common.gradle +++ /dev/null @@ -1,53 +0,0 @@ -plugins { - id 'maven-publish' -} - -publishing { - publications { - "$project.name"(MavenPublication) { - from components.java - pom { - name = "$project.name" - } - } - } - - repositories { - maven { - name = 'TerasologyOrg' - if (rootProject.hasProperty("publishRepo")) { - // This first option is good for local testing, you can set a full explicit target repo in gradle.properties - url = "https://artifactory.terasology.io/artifactory/$publishRepo" - - logger.info("Changing PUBLISH repoKey set via Gradle property to {}", publishRepo) - } else { - // Support override from the environment to use a different target publish org - String deducedPublishRepo = System.getenv()["PUBLISH_ORG"] - if (deducedPublishRepo == null || deducedPublishRepo == "") { - // If not then default - deducedPublishRepo = "libs" - } - - // Base final publish repo on whether we're building a snapshot or a release - if (project.version.endsWith('SNAPSHOT')) { - deducedPublishRepo += "-snapshot-local" - } else { - deducedPublishRepo += "-release-local" - } - - logger.info("The final deduced publish repo is {}", deducedPublishRepo) - url = "https://artifactory.terasology.io/artifactory/$deducedPublishRepo" - } - - if (rootProject.hasProperty("mavenUser") && rootProject.hasProperty("mavenPass")) { - credentials { - username = "$mavenUser" - password = "$mavenPass" - } - authentication { - basic(BasicAuthentication) - } - } - } - } -} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/GestaltExtension.kt b/build-logic/src/main/kotlin/GestaltExtension.kt new file mode 100644 index 000000000..e2312f4de --- /dev/null +++ b/build-logic/src/main/kotlin/GestaltExtension.kt @@ -0,0 +1,6 @@ +import org.gradle.api.provider.Property + +interface GestaltExtension { + val modulesPackage: Property + val moduleMetadataFileName: Property +} diff --git a/build-logic/src/main/groovy/destination-sol-common.gradle b/build-logic/src/main/kotlin/destination-sol-common.gradle.kts similarity index 59% rename from build-logic/src/main/groovy/destination-sol-common.gradle rename to build-logic/src/main/kotlin/destination-sol-common.gradle.kts index c694dfee2..c02c11632 100644 --- a/build-logic/src/main/groovy/destination-sol-common.gradle +++ b/build-logic/src/main/kotlin/destination-sol-common.gradle.kts @@ -1,18 +1,18 @@ plugins { - id 'destination-sol-constants' - id 'gestalt-repositories' - id 'destination-sol-repositories' - id 'destination-sol-java' - id 'terasology-metrics' - id 'destination-sol-ide' + id("destination-sol-constants") + id("gestalt-repositories") + id("destination-sol-repositories") + id("destination-sol-java") + id("terasology-metrics") + id("destination-sol-ide") } // TODO: Temporary until javadoc has been fixed for Java 8 everywhere -javadoc { - failOnError = false +tasks.javadoc { + isFailOnError = false } -test { +tasks.test { // ignoreFailures: Specifies whether the build should break when the verifications performed by this task fail. ignoreFailures = true @@ -20,5 +20,5 @@ test { testLogging.showStandardStreams = true // Arguments to include while running tests - jvmArgs '-Xms512m', '-Xmx1024m' + jvmArgs("-Xms512m", "-Xmx1024m") } diff --git a/build-logic/src/main/kotlin/destination-sol-constants.gradle.kts b/build-logic/src/main/kotlin/destination-sol-constants.gradle.kts new file mode 100644 index 000000000..2761c4f7c --- /dev/null +++ b/build-logic/src/main/kotlin/destination-sol-constants.gradle.kts @@ -0,0 +1,9 @@ +extra["appName"] = "DestinationSol" + +extra["engineVersion"] = "2.1.0" +extra["gestaltVersion"] = "8.0.2-SNAPSHOT" +extra["gdxVersion"] = "1.12.1" +// The LibGDX controllers library is versioned differently to the main LibGDX versions. +// See https://github.com/libgdx/gdx-controllers/wiki/Compatibility for compatible versions. +extra["gdxControllersVersion"] = "2.2.3" +extra["nuiVersion"] = "4.0.0-SNAPSHOT" diff --git a/build-logic/src/main/kotlin/destination-sol-ide.gradle.kts b/build-logic/src/main/kotlin/destination-sol-ide.gradle.kts new file mode 100644 index 000000000..3a377e565 --- /dev/null +++ b/build-logic/src/main/kotlin/destination-sol-ide.gradle.kts @@ -0,0 +1,97 @@ +import groovy.util.Node +import groovy.util.NodeList +import groovy.xml.XmlNodePrinter +import groovy.xml.XmlParser +import org.gradle.api.XmlProvider + +plugins { + eclipse + idea + id("org.jetbrains.gradle.plugin.idea-ext") +} + +@Suppress("UNCHECKED_CAST") +fun Node.child(name: String): List = (get(name) as? NodeList)?.filterIsInstance() ?: emptyList() + +fun Node.attr(name: String): Any? = attribute(name) + +val ideaPatchAnnotationProcessors = Action { + val profile = asNode().child("component") + .first { it.attr("name") == "CompilerConfiguration" } + .child("annotationProcessing").first() + .child("profile") + for (profileNode in profile) { + val moduleName = profileNode.child("module").first().attr("name") as String + + val sourceOutputDir: String + val sourceTestOutputDir: String + + if (moduleName.startsWith("DestinationSol.modules.")) { + // Modules output to a unified build directory. + sourceOutputDir = "../generated/sources/annotationProcessor/java/main" + sourceTestOutputDir = "../generated/sources/annotationProcessor/java/main" + } else { + // Normal libraries use separated build directories instead. + sourceOutputDir = "../../../generated/sources/annotationProcessor/java/main" + sourceTestOutputDir = "../../../generated/sources/annotationProcessor/java/test" + } + + if (profileNode.child("sourceOutputDir").isEmpty()) { + profileNode.appendNode("sourceOutputDir", mapOf("name" to sourceOutputDir)) + } else { + profileNode.child("sourceOutputDir").first().attributes()["name"] = sourceOutputDir + } + if (profileNode.child("sourceTestOutputDir").isEmpty()) { + profileNode.appendNode("sourceTestOutputDir", mapOf("name" to sourceTestOutputDir)) + } else { + profileNode.child("sourceTestOutputDir").first().attributes()["name"] = sourceTestOutputDir + } + if (profileNode.child("outputRelativeToContentRoot").isEmpty()) { + profileNode.appendNode("outputRelativeToContentRoot", mapOf("value" to false)) + } else { + profileNode.child("outputRelativeToContentRoot").first().attributes()["value"] = false + } + } +} + +val ideaPatchEntryPoints = Action { + val component = asNode().child("component") + .firstOrNull { it.attr("name") == "EntryPointsManager" } ?: return@Action + + component.child("list").forEach { component.remove(it) } + val entryPointsList = component.appendNode("list", mapOf("size" to 5)) + entryPointsList.appendNode("item", mapOf("index" to 0, "class" to "java.lang.String", "itemvalue" to "org.destinationsol.game.attributes.RegisterUpdateSystem")) + entryPointsList.appendNode("item", mapOf("index" to 1, "class" to "java.lang.String", "itemvalue" to "org.destinationsol.game.console.annotations.Command")) + entryPointsList.appendNode("item", mapOf("index" to 2, "class" to "java.lang.String", "itemvalue" to "org.terasology.gestalt.assets.module.annotations.RegisterAssetFileFormat")) + entryPointsList.appendNode("item", mapOf("index" to 3, "class" to "java.lang.String", "itemvalue" to "org.terasology.gestalt.assets.module.annotations.RegisterAssetType")) + entryPointsList.appendNode("item", mapOf("index" to 4, "class" to "java.lang.String", "itemvalue" to "org.terasology.context.annotation.Service")) + + component.child("writeAnnotations").forEach { component.remove(it) } + val writeAnnotations = component.appendNode("writeAnnotations", mapOf()) + writeAnnotations.appendNode("writeAnnotation", mapOf("name" to "javax.inject.Inject")) + writeAnnotations.appendNode("writeAnnotation", mapOf("name" to "org.destinationsol.common.In")) +} + +val ideaPatchCheckstyle = Action { + val checkstyleConfigs = asNode().child("component") + .first { it.attr("name") == "CheckStyle-IDEA" } + .child("option").first { it.attr("name") == "locations" } + .child("list").first() + var terasologyConfig = checkstyleConfigs.child("ConfigurationLocation") + .firstOrNull { it.attr("id") == "terasology-style" } + if (terasologyConfig != null) { + println(terasologyConfig) + checkstyleConfigs.remove(terasologyConfig) + } + terasologyConfig = checkstyleConfigs.appendNode( + "ConfigurationLocation", + mapOf("id" to "terasology-style", "type" to "LOCAL_FILE", "scope" to "All", "description" to "Terasology Style") + ) + terasologyConfig.setValue("\$PROJECT_DIR\$/config/metrics/checkstyle/checkstyle.xml") + val terasologyConfigProperties = terasologyConfig.appendNode("option", mapOf("name" to "properties")).appendNode("map", mapOf()) + terasologyConfigProperties.appendNode("entry", mapOf("key" to "sameDir", "value" to "\$PROJECT_DIR\$/config/metrics/checkstyle")) +} + +extra["ideaPatchAnnotationProcessors"] = ideaPatchAnnotationProcessors +extra["ideaPatchEntryPoints"] = ideaPatchEntryPoints +extra["ideaPatchCheckstyle"] = ideaPatchCheckstyle diff --git a/build-logic/src/main/kotlin/destination-sol-java.gradle.kts b/build-logic/src/main/kotlin/destination-sol-java.gradle.kts new file mode 100644 index 000000000..2e8c60ccc --- /dev/null +++ b/build-logic/src/main/kotlin/destination-sol-java.gradle.kts @@ -0,0 +1,33 @@ +plugins { + java +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + + withJavadocJar() + withSourcesJar() +} + +tasks.compileJava { + options.encoding = "UTF-8" + sourceCompatibility = JavaVersion.VERSION_17.toString() + targetCompatibility = JavaVersion.VERSION_17.toString() + + // The release flag is required for Java 9+. + if (JavaVersion.current().isJava9Compatible) { + options.release = 17 + } +} + +tasks.compileTestJava { + options.encoding = "UTF-8" + sourceCompatibility = JavaVersion.VERSION_17.toString() + targetCompatibility = JavaVersion.VERSION_17.toString() + + // The release flag is required for Java 9+. + if (JavaVersion.current().isJava9Compatible) { + options.release = 17 + } +} diff --git a/build-logic/src/main/kotlin/destination-sol-jre.gradle.kts b/build-logic/src/main/kotlin/destination-sol-jre.gradle.kts new file mode 100644 index 000000000..6b86d9f8e --- /dev/null +++ b/build-logic/src/main/kotlin/destination-sol-jre.gradle.kts @@ -0,0 +1,78 @@ +/* + * Copyright 2022 The Terasology Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import de.undercouch.gradle.tasks.download.DownloadAction +import org.gradle.api.plugins.BasePluginExtension + +plugins { + id("de.undercouch.download") +} + +// The `base` plugin (bringing BasePluginExtension) is applied transitively by whatever +// project consumes this script (e.g. via destination-sol-java), not by this script itself, +// so the type-safe `base { }` accessor isn't available here - look it up explicitly instead. +val distsDirectory = the().distsDirectory + +// Uses Bellsoft Liberica JRE +val jreVersion = "11.0.19+7" +val jreUrlBase = "https://download.bell-sw.com/java/$jreVersion/bellsoft-jre$jreVersion" +val jreUrlFilenames = mapOf( + "lwjreLinux64" to "linux-amd64.tar.gz", + "lwjre" to "windows-i586.zip", + "lwjreOSX" to "macos-amd64.zip", + "lwjreOSXArm" to "macos-aarch64.zip" +) + +val downloadJreAll by tasks.registering { + group = "Download" + description = "Downloads JRE for all platforms" +} + +jreUrlFilenames.forEach { (os, file) -> + val downloadTask = tasks.register("downloadJre$os") { + group = "Download" + description = "Downloads JRE for $os" + + val packedJre = File("$rootDir/jre/$jreVersion/$file") + val unpackedJre = distsDirectory.dir("app/$os").get().asFile + + doFirst { + DownloadAction(project).apply { + src("$jreUrlBase-$file") + dest(packedJre) + overwrite(false) + }.execute() + } + + doLast { + // Unpack the JRE + if (!unpackedJre.exists()) { + unpackedJre.mkdirs() + copy { + from(if (file.endsWith("zip")) zipTree(packedJre) else tarTree(packedJre)) { + eachFile { + relativePath = RelativePath(true, *relativePath.segments.drop(1).toTypedArray()) + } + includeEmptyDirs = false + } + into(unpackedJre) + } + } + } + } + + downloadJreAll.configure { dependsOn(downloadTask) } +} diff --git a/build-logic/src/main/kotlin/destination-sol-module.gradle.kts b/build-logic/src/main/kotlin/destination-sol-module.gradle.kts new file mode 100644 index 000000000..6f792785f --- /dev/null +++ b/build-logic/src/main/kotlin/destination-sol-module.gradle.kts @@ -0,0 +1,87 @@ +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.plugins.ide.eclipse.model.EclipseModel +import org.gradle.plugins.ide.idea.model.IdeaModel + +plugins { + id("destination-sol-constants") + id("gestalt-8-module") + id("destination-sol-common") +} + +group = "org.destinationsol.modules" + +val engineVersion = extra["engineVersion"] as String + +dependencies { + "api"(rootProject.findProject("engine") ?: "org.destinationsol.engine:engine:$engineVersion") +} + +configure { + modulesPackage.set("org.destinationsol.modules") + moduleMetadataFileName.set("module.json") +} + +publishing { + publications { + // maybeCreate: gestalt-module and terasology-publish-common also configure the + // project-name-keyed publication, in whichever order their plugins get applied. + maybeCreate(project.name, MavenPublication::class.java).pom { + url.set("https://github.com/DestinationSol/${project.name}") + licenses { + license { + name.set("Apache-2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") + distribution.set("repo") + } + } + issueManagement { + system.set("GitHub") + url.set("https://github.com/DestinationSol/${project.name}/issues") + } + scm { + connection.set("scm:git:https://github.com/DestinationSol/${project.name}.git") + developerConnection.set("scm:git:ssh://github.com/DestinationSol/${project.name}.git") + url.set("scm:git:https://github.com/DestinationSol/${project.name}.git") + } + } + } +} + +// Generate the module directory structure if missing +tasks.register("createSkeleton") { + doLast { + mkdir("assets") + mkdir("assets/music") + mkdir("assets/sounds") + mkdir("assets/textures") + mkdir("assets/configs") + mkdir("assets/asteroids") + mkdir("assets/schemas") + mkdir("assets/prefabs") + mkdir("assets/ui") + mkdir("assets/skins") + mkdir("overrides") + mkdir("deltas") + mkdir("src/main/java") + mkdir("src/test/java") + } +} + +// idea/eclipse are applied transitively (via destination-sol-common -> destination-sol-ide), +// not by this script's own plugins{} block, so the type-safe idea{}/eclipse{} accessors +// aren't available here - look the extensions up explicitly instead. +configure { + module { + inheritOutputDirs = false + outputDir = file("build/classes") + testOutputDir = file("build/testClasses") + isDownloadSources = true + } +} + +// For Eclipse just make sure the classpath is right +configure { + classpath { + defaultOutputDir = file("build/classes") + } +} diff --git a/build-logic/src/main/kotlin/destination-sol-repositories.gradle.kts b/build-logic/src/main/kotlin/destination-sol-repositories.gradle.kts new file mode 100644 index 000000000..64a4800d0 --- /dev/null +++ b/build-logic/src/main/kotlin/destination-sol-repositories.gradle.kts @@ -0,0 +1,45 @@ +repositories { + mavenCentral { + content { + // Terasology's libraries/modules aren't on maven central, so don't bother looking there. + excludeGroupByRegex("""org\.terasology(\..+)?""") + // Same for Destination Sol. + excludeGroupByRegex("""org\.destinationsol(\..+)?""") + } + } + + // Repos for LibGDX + maven { + url = uri("https://oss.sonatype.org/content/repositories/snapshots/") + content { + includeGroupByRegex("""com\.badlogicgames.gdx(\..+)?""") + } + } + maven { + url = uri("https://oss.sonatype.org/content/repositories/releases/") + content { + includeGroupByRegex("""com\.badlogicgames.gdx(\..+)?""") + } + } + + // everit-org JSON schema dependency + maven { + url = uri("https://jitpack.io") + content { + includeModule("com.github.everit-org.json-schema", "org.everit.json.schema") + } + } + + // Terasology Artifactory for any shared libs + maven { + url = uri("https://artifactory.terasology.io/artifactory/virtual-repo-live") + content { + includeGroupByRegex("""org\.terasology(\..+)?""") + includeGroupByRegex("""org\.destinationsol(\..+)?""") + // A copy of jpastebin is hosted here + includeModule("brianbb", "jpastebin") + } + } + + google() +} diff --git a/build-logic/src/main/kotlin/gestalt-8-module.gradle.kts b/build-logic/src/main/kotlin/gestalt-8-module.gradle.kts new file mode 100644 index 000000000..f321cb073 --- /dev/null +++ b/build-logic/src/main/kotlin/gestalt-8-module.gradle.kts @@ -0,0 +1,22 @@ +plugins { + id("gestalt-module") +} + +val gestaltVersion = extra["gestaltVersion"] as String + +tasks.compileJava { + // Specify the module's assets as pre-requisites for compilation, so that they force re-compilation when they change. + inputs.files(sourceSets.main.get().resources.srcDirs) + // Asset lists are cached by gestalt-di's annotation processors, which run just before compilation begins. + // Without these manifests, or with outdated manifests, the game won't know what assets are present. + options.compilerArgs = listOf("-Aresource=${sourceSets.main.get().resources.srcDirs.joinToString(File.pathSeparator)}") +} +tasks.compileTestJava { + // See above comments. + inputs.files(sourceSets.test.get().resources.srcDirs) + options.compilerArgs = listOf("-Aresource=${sourceSets.test.get().resources.srcDirs.joinToString(File.pathSeparator)}") +} + +dependencies { + "annotationProcessor"("org.terasology.gestalt:gestalt-inject-java:$gestaltVersion") +} diff --git a/build-logic/src/main/kotlin/gestalt-module.gradle.kts b/build-logic/src/main/kotlin/gestalt-module.gradle.kts new file mode 100644 index 000000000..7fea689c8 --- /dev/null +++ b/build-logic/src/main/kotlin/gestalt-module.gradle.kts @@ -0,0 +1,108 @@ +import com.google.gson.JsonParser +import org.gradle.api.publish.maven.MavenPublication + +plugins { + `java-library` + id("gestalt-repositories") + id("terasology-publish-common") +} + +val gestaltExtension = extensions.create("gestalt") + +gestaltExtension.modulesPackage.set("org.terasology.gestalt.modules") +gestaltExtension.moduleMetadataFileName.set("module.txt") + +// Change the output dir of each module +sourceSets { + main { + java.destinationDirectory.set(File("$buildDir/classes")) + } + test { + java.destinationDirectory.set(File("$buildDir/testClasses")) + } +} + +tasks.jar { + from("module.json") { + into("") + } + from("assets") { + into("assets") + } + from("overrides") { + into("overrides") + } + from("deltas") { + into("deltas") + } +} + +val gestaltModule: Configuration by configurations.creating +configurations.named("api") { + extendsFrom(gestaltModule) +} + +afterEvaluate { + val moduleMetadataFile = file(gestaltExtension.moduleMetadataFileName.get()) + if (!moduleMetadataFile.exists()) { + println("${gestaltExtension.moduleMetadataFileName.get()} does not exist!") + throw GradleException("Failed to find ${gestaltExtension.moduleMetadataFileName.get()} for module ${project.name}") + } + + val moduleMetadata = moduleMetadataFile.reader().use { JsonParser.parseReader(it) }.asJsonObject + extra["moduleMetadata"] = moduleMetadata + extra["modulesRoot"] = if (project.hasProperty("modulesRoot")) property("modulesRoot") else "$rootProject/modules" + + moduleMetadata.get("version")?.asString?.let { version = it } + moduleMetadata.get("description")?.asString?.let { description = it } + + publishing { + publications { + // Both this plugin and destination-sol-module configure the same, project-name-keyed + // publication; maybeCreate lets either one run first without the other clobbering it. + maybeCreate(project.name, MavenPublication::class.java).pom { + name.set(project.name) + description.set(project.description) + val author = moduleMetadata.get("author")?.asString + if (!author.isNullOrEmpty()) { + developers { + for (developerId in author.split(",")) { + developer { + id.set(developerId.trim()) + } + } + } + } + } + } + } + + dependencies { + moduleMetadata.getAsJsonArray("dependencies")?.forEach { element -> + val dependency = element.asJsonObject + val optional = dependency.get("optional")?.asBoolean ?: false + if (!optional) { + val depId = dependency.get("id").asString + val minVersion = dependency.get("minVersion")?.asString + val maxVersion = dependency.get("maxVersion")?.asString + gestaltModule(group = gestaltExtension.modulesPackage.get(), name = depId) { + if (minVersion != null && maxVersion == null) { + version { + strictly("[$minVersion,)") + } + } + if (minVersion == null && maxVersion != null) { + version { + strictly("(,$maxVersion[") + } + } + if (minVersion != null && maxVersion != null) { + version { + strictly("[$minVersion, $maxVersion[") + } + } + } + } + } + } +} diff --git a/build-logic/src/main/groovy/gestalt-repositories.gradle b/build-logic/src/main/kotlin/gestalt-repositories.gradle.kts similarity index 54% rename from build-logic/src/main/groovy/gestalt-repositories.gradle rename to build-logic/src/main/kotlin/gestalt-repositories.gradle.kts index 327fbdfcb..66fe2af8f 100644 --- a/build-logic/src/main/groovy/gestalt-repositories.gradle +++ b/build-logic/src/main/kotlin/gestalt-repositories.gradle.kts @@ -1,25 +1,25 @@ repositories { - mavenCentral() { + mavenCentral { content { // Terasology's libraries/modules aren't on maven central, so don't bother looking there. - excludeGroupByRegex('org\\.terasology(\\..+)?') + excludeGroupByRegex("""org\.terasology(\..+)?""") } } - google() { + google { content { // Terasology's libraries/modules aren't on maven central, so don't bother looking there. - excludeGroupByRegex('org\\.terasology(\\..+)?') + excludeGroupByRegex("""org\.terasology(\..+)?""") } } // Terasology Artifactory for any shared libs maven { - url "https://artifactory.terasology.io/artifactory/virtual-repo-live" + url = uri("https://artifactory.terasology.io/artifactory/virtual-repo-live") content { - includeGroupByRegex('org\\.terasology.gestalt(\\..+)?') + includeGroupByRegex("""org\.terasology.gestalt(\..+)?""") // A copy of Java-semver is hosted here too - includeModule('com.github.zafarkhaja', 'java-semver') + includeModule("com.github.zafarkhaja", "java-semver") } } -} \ No newline at end of file +} diff --git a/build-logic/src/main/kotlin/terasology-metrics.gradle.kts b/build-logic/src/main/kotlin/terasology-metrics.gradle.kts new file mode 100644 index 000000000..5f8d549d6 --- /dev/null +++ b/build-logic/src/main/kotlin/terasology-metrics.gradle.kts @@ -0,0 +1,99 @@ +import com.github.spotbugs.snom.SpotBugsTask + +plugins { + java + id("project-report") + checkstyle + pmd + id("com.github.spotbugs") + jacoco + id("ru.vyarus.animalsniffer") +} + +val codeMetrics: Configuration by configurations.creating + +dependencies { + // Config for our code analytics lives in a centralized repo: https://github.com/MovingBlocks/TeraConfig + codeMetrics(group = "org.terasology.config", name = "codemetrics", version = "2.2.0", ext = "zip") + + "pmd"("net.sourceforge.pmd:pmd-ant:7.26.0") + "pmd"("net.sourceforge.pmd:pmd-core:7.26.0") + "pmd"("net.sourceforge.pmd:pmd-java:7.26.0") + + "signature"("com.toasttab.android:gummy-bears-api-24:0.15.0:coreLib2@signature") +} + +animalsniffer { + // java.nio.* APIs can be desugared by D8. java.io.File.toPath() also needs to be excluded. + ignore = setOf("java.nio.file.*", "java.io.File") +} + +jacoco { + toolVersion = "0.8.15" +} + +tasks.jacocoTestReport { + dependsOn(tasks.test) // Despite doc saying this should be automatic we need to explicitly add it anyway :-( + reports { + // We only use the .exec report for display in Jenkins and such. More could be enabled if desired. + xml.required = false + csv.required = false + html.required = false + } +} + +checkstyle { + isIgnoreFailures = true + configFile = File(rootDir, "config/metrics/checkstyle/checkstyle.xml") + toolVersion = "10.2" + configDirectory.set(configFile.parentFile) + configProperties["samedir"] = configFile.parentFile +} + +pmd { + isIgnoreFailures = true + ruleSetFiles = files("$rootDir/config/metrics/pmd/pmd.xml") + // By default, gradle uses both ruleset file AND the rulesets. Override the ruleSets to use only those from the file + ruleSets = listOf() +} + +spotbugs { + toolVersion.set("4.8.1") + ignoreFailures.set(true) + excludeFilter.set(File(rootDir, "config/metrics/findbugs/findbugs-exclude.xml")) +} +tasks.named("spotbugsMain") { + reports.create("xml") { + enabled = true + outputLocation.set(file("$buildDir/reports/spotbugs/main/spotbugs.xml")) + } +} + +val extractMetricsConfig = rootProject.tasks.findByName("extractMetricsConfig") + ?: rootProject.tasks.register("extractMetricsConfig", Copy::class) { + description = "Extracts our configuration files from the zip we fetched as a dependency" + from({ codeMetrics.map { zipTree(it) } }) + into("$rootDir/config/metrics") + }.get() + +tasks.named("spotbugsMain") { dependsOn(extractMetricsConfig) } +tasks.named("pmdMain") { dependsOn(extractMetricsConfig) } + +tasks.withType().configureEach { + group = "Reporting" + dependsOn(extractMetricsConfig) +} + +tasks.withType().configureEach { + dependsOn(extractMetricsConfig) + group = "Reporting" +} + +tasks.withType().configureEach { + dependsOn(extractMetricsConfig) + group = "Reporting" +} + +tasks.check { + dependsOn(extractMetricsConfig) +} diff --git a/build-logic/src/main/kotlin/terasology-publish-common.gradle.kts b/build-logic/src/main/kotlin/terasology-publish-common.gradle.kts new file mode 100644 index 000000000..fb6b0100c --- /dev/null +++ b/build-logic/src/main/kotlin/terasology-publish-common.gradle.kts @@ -0,0 +1,59 @@ +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.authentication.http.BasicAuthentication + +plugins { + `maven-publish` +} + +publishing { + publications { + // maybeCreate: gestalt-module and destination-sol-module also configure the + // project-name-keyed publication, in whichever order their plugins get applied. + maybeCreate(project.name, MavenPublication::class.java).apply { + from(components["java"]) + pom { + name.set(project.name) + } + } + } + + repositories { + maven { + name = "TerasologyOrg" + if (rootProject.hasProperty("publishRepo")) { + // This first option is good for local testing, you can set a full explicit target repo in gradle.properties + val publishRepo = rootProject.property("publishRepo") as String + url = uri("https://artifactory.terasology.io/artifactory/$publishRepo") + + logger.info("Changing PUBLISH repoKey set via Gradle property to {}", publishRepo) + } else { + // Support override from the environment to use a different target publish org + var deducedPublishRepo = System.getenv()["PUBLISH_ORG"] + if (deducedPublishRepo.isNullOrEmpty()) { + // If not then default + deducedPublishRepo = "libs" + } + + // Base final publish repo on whether we're building a snapshot or a release + deducedPublishRepo += if (project.version.toString().endsWith("SNAPSHOT")) { + "-snapshot-local" + } else { + "-release-local" + } + + logger.info("The final deduced publish repo is {}", deducedPublishRepo) + url = uri("https://artifactory.terasology.io/artifactory/$deducedPublishRepo") + } + + if (rootProject.hasProperty("mavenUser") && rootProject.hasProperty("mavenPass")) { + credentials { + username = rootProject.property("mavenUser") as String + password = rootProject.property("mavenPass") as String + } + authentication { + create("basic") + } + } + } + } +} diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 4e9e5daae..000000000 --- a/build.gradle +++ /dev/null @@ -1,177 +0,0 @@ -// Git plugin details at https://github.com/ajoberstar/gradle-git -import org.ajoberstar.gradle.git.tasks.* - -plugins { - id 'destination-sol-ide' - id 'destination-sol-repositories' - id 'terasology-metrics' - id 'org.ajoberstar.grgit' version '5.0.0' apply false -} - -repositories { - // Good ole Maven central - mavenCentral() - - // Repos for LibGDX - maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } - maven { url "https://oss.sonatype.org/content/repositories/releases/" } - - // Terasology Artifactory for any shared libs - maven { - url "https://artifactory.terasology.io/artifactory/virtual-repo-live" - } - - maven { url "https://maven.google.com" } -} - -// Helper that returns a list of all local Destination Sol module projects -def destinationSolModules() { - subprojects.findAll {it.parent.name == 'modules'} -} - -destinationSolModules().forEach { destSolModule -> - destSolModule.configurations.configureEach { - resolutionStrategy.dependencySubstitution.all { dependency -> - if (dependency.requested instanceof ModuleComponentSelector && dependency.requested.group == "org.destinationsol.modules") { - destinationSolModules().forEach { otherModule -> - if (otherModule.name == dependency.requested.module) { - dependency.useTarget otherModule - } - } - } - } - } -} - -tasks.eclipse.doLast { - delete ".project" -} -tasks.eclipse.dependsOn extractMetricsConfig - -import org.jetbrains.gradle.ext.Application -idea { - project { - // Set JDK - jdkName = '1.8' - wildcards -= '!?*.groovy' - - settings { - compiler { - enableAutomake = true - } - - runConfigurations { - "Desktop"(Application) { - mainClass = 'org.destinationsol.desktop.SolDesktop' - moduleName 'DestinationSol.desktop.main' - workingDirectory = rootDir - jvmArgs = '-splash:engine/src/main/resources/assets/textures/mainMenu/mainMenuLogo.png -Xms256m -Xmx1024m -Dlog4j.configuration=log4j-debug.properties' - programParameters = '-noSplash -noCrashReport' - } - } - - copyright { - useDefault = 'DestinationSolCopyright' - profiles { - DestinationSolCopyright { - notice = 'Copyright 2023 The Terasology Foundation\n\nLicensed under the Apache License, Version 2.0 (the "License");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n https://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an "AS IS" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.' - keyword = 'Copyright' - allowReplaceRegexp = '' - } - } - } - - taskTriggers { - afterSync tasks.named('extractMetricsConfig') - } - - generateImlFiles = true - withIDEAFileXml("compiler.xml", ideaPatchAnnotationProcessors) - withIDEAFileXml("misc.xml", ideaPatchEntryPoints) - withIDEAFileXml("checkstyle-idea.xml", ideaPatchCheckstyle) - } - - ipr { - withXml { xmlProvider -> - // Apply a bunch of tweaks to IntelliJ config - all defined in ide.gradle - // Part reason for separate file was in case a module needs to define something it cannot do so in a project block - def iprNode = xmlProvider.asNode() - ideaActivateCheckstyle(iprNode) - ideaActivateCopyright(iprNode) - ideaActivateAnnotations(iprNode) - ideaActivateGit(iprNode) - ideaActivateGradle(iprNode) - } - - // Sets sourceCompatibility within IntelliJ (without this root build having the Java plugin applied) - whenMerged { project -> - project.jdk.languageLevel = 'JDK_1_8' - } - } - } - - // Tweaks to the .iws - workspace.iws.withXml { xmlProvider -> - def iwsNode = xmlProvider.asNode() - ideaMakeAutomatically(iwsNode) - ideaRunConfig(iwsNode) - } -} - -cleanIdea.doLast { - new File('DestinationSol.iws').delete() - new File('config/metrics').deleteDir() - println "Cleaned root - don't forget to re-extract code metrics config! 'gradlew extractConfig' will do so, or 'gradlew idea' (or eclipse)" -} - -import org.ajoberstar.grgit.Grgit -tasks.register('fetchAndroid') { - description = 'Git clones the Android facade source from GitHub' - - // Repo name is the dynamic part of the task name - def repo = 'DestSolAndroid' - - // Default GitHub account to use. Supply with -PgithubAccount="TargetAccountName" or via gradle.properties - def githubHome = 'MovingBlocks' - - def destination = file('android') - - // Don't clone this repo if we already have a directory by that name (also determines Gradle UP-TO-DATE) - enabled = !destination.exists() - //println "fetchAndroid requested for $repo from Github under $githubHome - exists already? " + !enabled - - doLast { - Grgit.clone( - // Do the actual clone if we don't have the directory already - uri: "https://github.com/$githubHome/" + repo + ".git", - //println "Fetching $repo from $uri" - dir: destination, - bare: false - ) - } -} - -tasks.register('fetchSteam') { - description = 'Git clones the Steam facade source from GitHub' - - // Repo name is the dynamic part of the task name - def repo = 'DestSolSteam' - - // Default GitHub account to use. Supply with -PgithubAccount="TargetAccountName" or via gradle.properties - def githubHome = findProperty('githubAccount') ?: 'MovingBlocks' - - def destination = file('steam') - - // Don't clone this repo if we already have a directory by that name (also determines Gradle UP-TO-DATE) - enabled = !destination.exists() - - doLast { - Grgit.clone( - // Do the actual clone if we don't have the directory already - uri: "https://github.com/$githubHome/" + repo + ".git", - //println "Fetching $repo from $uri" - dir: destination, - bare: false - ) - } -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 000000000..c1b4d7967 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,176 @@ +// Git plugin details at https://github.com/ajoberstar/gradle-git +import org.ajoberstar.grgit.Grgit +import org.gradle.api.XmlProvider +import org.jetbrains.gradle.ext.Application +import org.jetbrains.gradle.ext.compiler +import org.jetbrains.gradle.ext.copyright +import org.jetbrains.gradle.ext.runConfigurations +import org.jetbrains.gradle.ext.settings +import org.jetbrains.gradle.ext.taskTriggers + +plugins { + id("destination-sol-ide") + id("destination-sol-repositories") + id("terasology-metrics") + id("org.ajoberstar.grgit") version "5.0.0" apply false +} + +repositories { + // Good ole Maven central + mavenCentral() + + // Repos for LibGDX + maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") } + maven { url = uri("https://oss.sonatype.org/content/repositories/releases/") } + + // Terasology Artifactory for any shared libs + maven { + url = uri("https://artifactory.terasology.io/artifactory/virtual-repo-live") + } + + maven { url = uri("https://maven.google.com") } +} + +// Helper that returns a list of all local Destination Sol module projects +fun destinationSolModules() = subprojects.filter { it.parent?.name == "modules" } + +extra["destinationSolModules"] = ::destinationSolModules + +destinationSolModules().forEach { destSolModule -> + destSolModule.configurations.configureEach { + resolutionStrategy.dependencySubstitution.all { + val requestedSelector = requested + if (requestedSelector is ModuleComponentSelector && requestedSelector.group == "org.destinationsol.modules") { + destinationSolModules().forEach { otherModule -> + if (otherModule.name == requestedSelector.module) { + useTarget(otherModule) + } + } + } + } + } +} + +tasks.named("eclipse") { + doLast { + delete(".project") + } + dependsOn("extractMetricsConfig") +} + +@Suppress("UNCHECKED_CAST") +val ideaPatchAnnotationProcessors = extra["ideaPatchAnnotationProcessors"] as Action +@Suppress("UNCHECKED_CAST") +val ideaPatchEntryPoints = extra["ideaPatchEntryPoints"] as Action +@Suppress("UNCHECKED_CAST") +val ideaPatchCheckstyle = extra["ideaPatchCheckstyle"] as Action + +idea { + project { + // Set JDK + jdkName = "1.8" + wildcards.remove("!?*.groovy") + + settings { + compiler { + enableAutomake = true + } + + runConfigurations { + create("Desktop") { + mainClass = "org.destinationsol.desktop.SolDesktop" + moduleName = "DestinationSol.desktop.main" + workingDirectory = rootDir.toString() + jvmArgs = "-splash:engine/src/main/resources/assets/textures/mainMenu/mainMenuLogo.png -Xms256m -Xmx1024m -Dlog4j.configuration=log4j-debug.properties" + programParameters = "-noSplash -noCrashReport" + } + } + + copyright { + useDefault = "DestinationSolCopyright" + profiles { + create("DestinationSolCopyright") { + notice = "Copyright 2023 The Terasology Foundation\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n https://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License." + keyword = "Copyright" + allowReplaceRegexp = "" + } + } + } + + taskTriggers { + afterSync(tasks.named("extractMetricsConfig")) + } + + generateImlFiles = true + withIDEAFileXml("compiler.xml", ideaPatchAnnotationProcessors) + withIDEAFileXml("misc.xml", ideaPatchEntryPoints) + withIDEAFileXml("checkstyle-idea.xml", ideaPatchCheckstyle) + } + + // NOTE: the previous ipr.withXml{}/workspace.iws.withXml{} blocks here called + // ideaActivateCheckstyle/Copyright/Annotations/Git/Gradle, ideaMakeAutomatically and + // ideaRunConfig - none of which have existed since config/gradle/ide.gradle was deleted + // in 68193863 (2022). Groovy only ever failed on that at runtime, when `gradle idea`/ + // `ipr`/`iws` actually ran, which nothing does - so it's been silently dead ever since. + // Dropped rather than carried forward as newly-hard-failing Kotlin. + } +} + +tasks.named("cleanIdea") { + doLast { + File("DestinationSol.iws").delete() + File("config/metrics").deleteRecursively() + println("Cleaned root - don't forget to re-extract code metrics config! 'gradlew extractConfig' will do so, or 'gradlew idea' (or eclipse)") + } +} + +tasks.register("fetchAndroid") { + description = "Git clones the Android facade source from GitHub" + + // Repo name is the dynamic part of the task name + val repo = "DestSolAndroid" + + // Default GitHub account to use. Supply with -PgithubAccount="TargetAccountName" or via gradle.properties + val githubHome = "MovingBlocks" + + val destination = file("android") + + // Don't clone this repo if we already have a directory by that name (also determines Gradle UP-TO-DATE) + enabled = !destination.exists() + //println("fetchAndroid requested for $repo from Github under $githubHome - exists already? " + !enabled) + + doLast { + Grgit.clone(mapOf( + // Do the actual clone if we don't have the directory already + "uri" to "https://github.com/$githubHome/$repo.git", + //println("Fetching $repo from $uri") + "dir" to destination, + "bare" to false + )) + } +} + +tasks.register("fetchSteam") { + description = "Git clones the Steam facade source from GitHub" + + // Repo name is the dynamic part of the task name + val repo = "DestSolSteam" + + // Default GitHub account to use. Supply with -PgithubAccount="TargetAccountName" or via gradle.properties + val githubHome = findProperty("githubAccount") as String? ?: "MovingBlocks" + + val destination = file("steam") + + // Don't clone this repo if we already have a directory by that name (also determines Gradle UP-TO-DATE) + enabled = !destination.exists() + + doLast { + Grgit.clone(mapOf( + // Do the actual clone if we don't have the directory already + "uri" to "https://github.com/$githubHome/$repo.git", + //println("Fetching $repo from $uri") + "dir" to destination, + "bare" to false + )) + } +} diff --git a/desktop/build.gradle b/desktop/build.gradle deleted file mode 100644 index 9aeb7185c..000000000 --- a/desktop/build.gradle +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2022 The Terasology Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -plugins { - id 'destination-sol-common' - id 'destination-sol-jre' -} - -import groovy.xml.XmlParser -import groovy.xml.XmlNodePrinter - -project.ext.mainClassName = "org.destinationsol.desktop.SolDesktop" - -dependencies { - implementation project(":engine") - implementation "com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion" - implementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" - implementation "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop" - implementation "com.badlogicgames.gdx-controllers:gdx-controllers-desktop:$gdxControllersVersion" - - implementation group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.25' - implementation group: 'org.terasology.crashreporter', name: 'cr-destsol', version: '4.0.0' - annotationProcessor "org.terasology.gestalt:gestalt-inject-java:$gestaltVersion" -} - -tasks.withType(ru.vyarus.gradle.plugin.animalsniffer.AnimalSniffer) { - // The desktop facade does not run on Android, so we do not have to fulfil its constraints. - exclude("**/*") -} - -tasks.register('run', JavaExec) { - dependsOn classes - //TODO: Remove extra args when the splash screen works on Macs again - see https://github.com/MovingBlocks/DestinationSol/issues/414 - if (System.properties["os.name"].toLowerCase().contains("mac")) { - jvmArgs = ["-splash:../engine/src/main/resources/assets/textures/mainMenu/mainMenuLogo.png", "-XstartOnFirstThread", "-Dlog4j.configuration=log4j-debug.properties"] - String[] runArgs = ["-noSplash"] - args runArgs - } else { - jvmArgs = ["-splash:../engine/src/main/resources/assets/textures/mainMenu/mainMenuLogo.png", "-Dlog4j.configuration=log4j-debug.properties"] - } - mainClass = project.mainClassName - classpath = sourceSets.main.runtimeClasspath - standardInput = System.in - workingDir = rootProject.projectDir - ignoreExitValue true -} - -jar { - archiveFileName = "solDesktop.jar" - - manifest { - def manifestClasspath = configurations.runtimeClasspath.collect { it.getName() }.join(" ") - attributes 'Main-Class': project.mainClassName - attributes("Class-Path": manifestClasspath) - attributes "SplashScreen-Image": "mainMenuLogo.png" - } -} - -tasks.register('moduleDist', Copy) { - into("${base.distsDirectory.getAsFile().get()}/app/modules") - rootProject.destinationSolModules().each { module -> - dependsOn ":modules:$module.name" + ":jar" - from("$rootDir/modules/${module.name}/build/libs") { - include "*.jar" - } - } -} - -tasks.register('copyLaunchers', Copy) { - description = "Copy launchers into the distribution folder." - - from("$rootDir/launcher") - include("*.sh", "*.exe") - into("${base.distsDirectory.getAsFile().get()}/app") -} - -tasks.register('libsDist', Copy) { - description = "Copy libs directory into the distribution folder." - - dependsOn jar - - from jar - from configurations.runtimeClasspath - into("${base.distsDirectory.getAsFile().get()}/app/libs") -} - -tasks.register('distUnbundledJRE') { - description = "Creates an application package without any bundled JRE." - group 'Distribution' - - dependsOn libsDist - dependsOn moduleDist - dependsOn copyLaunchers -} - -tasks.register('distZipUnbundledJRE', Zip) { - description = "Creates an application package and zip archive without any bundled JRE." - group 'Distribution' - - dependsOn distUnbundledJRE - from "${base.distsDirectory.getAsFile().get()}/app" - archiveFileName = "DestinationSol.zip" -} - -tasks.register('distBundleJREs') { - description = "Creates an application package with a bundled JRE." - group 'Distribution' - - dependsOn distUnbundledJRE - dependsOn downloadJreAll -} - -tasks.register('distZipBundleJREs', Zip) { - description = "Creates an application package and zip archive with a bundled JRE." - group 'Distribution' - - dependsOn distBundleJREs - from "${base.distsDirectory.getAsFile().get()}/app" - archiveFileName = "DestinationSol.zip" -} - -// TODO: LibGDX Generated config for Eclipse. Needs adjustment for assets not being in the Android facade -eclipse { - project { - name = appName + "-desktop" - linkedResource name: 'assets', type: '2', location: 'PARENT-1-PROJECT_LOC/android/assets' - file { - whenMerged { project -> - def destinationSolRunConfig = new XmlParser().parseText(''' - - - - - - - - - - - - - - - - ''') - def writer = new FileWriter(file("DestinationSol.launch")) - def printer = new XmlNodePrinter(new PrintWriter(writer)) - printer.setPreserveWhitespace(true) - printer.print(destinationSolRunConfig) - } - } - } -} - -tasks.register('afterEclipseImport') { - description 'Post processing after project generation' - group 'IDE' - - doLast { - def classpath = new XmlParser().parse(file(".classpath")) - new Node(classpath, "classpathentry", [kind: 'src', path: 'assets']); - def writer = new FileWriter(file(".classpath")) - def printer = new XmlNodePrinter(new PrintWriter(writer)) - printer.setPreserveWhitespace(true) - printer.print(classpath) - } -} - -tasks.withType(JavaExec).configureEach { - if (System.getProperty('DEBUG', 'false') == 'true') { - jvmArgs '-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=9099', '-Dlog4j.configuration=log4j-debug.properties' - } -} diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts new file mode 100644 index 000000000..26d1208bb --- /dev/null +++ b/desktop/build.gradle.kts @@ -0,0 +1,201 @@ +/* + * Copyright 2022 The Terasology Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import groovy.xml.XmlNodePrinter +import groovy.xml.XmlParser +import org.gradle.api.plugins.BasePluginExtension +import org.gradle.plugins.ide.eclipse.model.EclipseModel +import java.io.FileWriter +import java.io.PrintWriter + +plugins { + id("destination-sol-common") + id("destination-sol-jre") +} + +val gdxVersion = extra["gdxVersion"] as String +val gestaltVersion = extra["gestaltVersion"] as String +val gdxControllersVersion = extra["gdxControllersVersion"] as String +val appName = extra["appName"] as String + +val mainClassName = "org.destinationsol.desktop.SolDesktop" +extra["mainClassName"] = mainClassName + +dependencies { + implementation(project(":engine")) + implementation("com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion") + implementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") + implementation("com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop") + implementation("com.badlogicgames.gdx-controllers:gdx-controllers-desktop:$gdxControllersVersion") + + implementation(group = "org.slf4j", name = "slf4j-log4j12", version = "1.7.25") + implementation(group = "org.terasology.crashreporter", name = "cr-destsol", version = "4.0.0") + annotationProcessor("org.terasology.gestalt:gestalt-inject-java:$gestaltVersion") +} + +tasks.withType().configureEach { + // The desktop facade does not run on Android, so we do not have to fulfil its constraints. + exclude("**/*") +} + +val distsDirectory = the().distsDirectory + +tasks.register("run") { + dependsOn(tasks.classes) + //TODO: Remove extra args when the splash screen works on Macs again - see https://github.com/MovingBlocks/DestinationSol/issues/414 + if (System.getProperty("os.name").lowercase().contains("mac")) { + jvmArgs = listOf("-splash:../engine/src/main/resources/assets/textures/mainMenu/mainMenuLogo.png", "-XstartOnFirstThread", "-Dlog4j.configuration=log4j-debug.properties") + args("-noSplash") + } else { + jvmArgs = listOf("-splash:../engine/src/main/resources/assets/textures/mainMenu/mainMenuLogo.png", "-Dlog4j.configuration=log4j-debug.properties") + } + mainClass.set(mainClassName) + classpath = sourceSets.main.get().runtimeClasspath + standardInput = System.`in` + workingDir = rootProject.projectDir + isIgnoreExitValue = true +} + +tasks.jar { + archiveFileName.set("solDesktop.jar") + + manifest { + val manifestClasspath = configurations.runtimeClasspath.get().joinToString(" ") { it.name } + attributes("Main-Class" to mainClassName) + attributes("Class-Path" to manifestClasspath) + attributes("SplashScreen-Image" to "mainMenuLogo.png") + } +} + +tasks.register("moduleDist") { + into("${distsDirectory.get().asFile}/app/modules") + @Suppress("UNCHECKED_CAST") + val destinationSolModules = rootProject.extra["destinationSolModules"] as () -> List + destinationSolModules().forEach { module -> + dependsOn(":modules:${module.name}:jar") + from("$rootDir/modules/${module.name}/build/libs") { + include("*.jar") + } + } +} + +tasks.register("copyLaunchers") { + description = "Copy launchers into the distribution folder." + + from("$rootDir/launcher") + include("*.sh", "*.exe") + into("${distsDirectory.get().asFile}/app") +} + +tasks.register("libsDist") { + description = "Copy libs directory into the distribution folder." + + dependsOn(tasks.jar) + + from(tasks.jar) + from(configurations.runtimeClasspath) + into("${distsDirectory.get().asFile}/app/libs") +} + +tasks.register("distUnbundledJRE") { + description = "Creates an application package without any bundled JRE." + group = "Distribution" + + dependsOn("libsDist") + dependsOn("moduleDist") + dependsOn("copyLaunchers") +} + +tasks.register("distZipUnbundledJRE") { + description = "Creates an application package and zip archive without any bundled JRE." + group = "Distribution" + + dependsOn("distUnbundledJRE") + from("${distsDirectory.get().asFile}/app") + archiveFileName.set("DestinationSol.zip") +} + +tasks.register("distBundleJREs") { + description = "Creates an application package with a bundled JRE." + group = "Distribution" + + dependsOn("distUnbundledJRE") + dependsOn("downloadJreAll") +} + +tasks.register("distZipBundleJREs") { + description = "Creates an application package and zip archive with a bundled JRE." + group = "Distribution" + + dependsOn("distBundleJREs") + from("${distsDirectory.get().asFile}/app") + archiveFileName.set("DestinationSol.zip") +} + +// TODO: LibGDX Generated config for Eclipse. Needs adjustment for assets not being in the Android facade +// eclipse is applied transitively (via destination-sol-common -> destination-sol-ide), not by +// this script's own plugins{} block, so the type-safe eclipse{} accessor isn't available here. +configure { + project { + name = "$appName-desktop" + linkedResource(mapOf("name" to "assets", "type" to "2", "location" to "PARENT-1-PROJECT_LOC/android/assets")) + file { + whenMerged { + val destinationSolRunConfig = XmlParser().parseText(""" + + + + + + + + + + + + + + + + """) + val writer = FileWriter(file("DestinationSol.launch")) + val printer = XmlNodePrinter(PrintWriter(writer)) + printer.isPreserveWhitespace = true + printer.print(destinationSolRunConfig) + } + } + } +} + +tasks.register("afterEclipseImport") { + description = "Post processing after project generation" + group = "IDE" + + doLast { + val classpath = XmlParser().parse(file(".classpath")) + groovy.util.Node(classpath, "classpathentry", mapOf("kind" to "src", "path" to "assets")) + val writer = FileWriter(file(".classpath")) + val printer = XmlNodePrinter(PrintWriter(writer)) + printer.isPreserveWhitespace = true + printer.print(classpath) + } +} + +tasks.withType().configureEach { + if (System.getProperty("DEBUG", "false") == "true") { + jvmArgs("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=9099", "-Dlog4j.configuration=log4j-debug.properties") + } +} diff --git a/engine/build.gradle b/engine/build.gradle deleted file mode 100644 index 83287e243..000000000 --- a/engine/build.gradle +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2022 The Terasology Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Dependencies needed for what our Gradle scripts themselves use. It cannot be included via an external Gradle file :-( -buildscript { - repositories { - mavenCentral() - - google() - maven { - url "https://artifactory.terasology.io/artifactory/virtual-repo-live" - } - // Needed for Jsemver, which is a gestalt dependency - maven { url = 'https://heisluft.de/maven/' } - } - - dependencies { - classpath 'dom4j:dom4j:1.6.1' - } -} - -plugins { - id 'java-library' - id 'destination-sol-common' - id 'terasology-publish-common' -} - -group = "org.destinationsol.engine" -version = engineVersion - -dependencies { - api group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - api group: 'com.google.code.gson', name: 'gson', version: '2.6.2' - api group: 'com.google.guava', name: 'guava', version: '30.1-jre' - - api "com.badlogicgames.gdx:gdx:$gdxVersion" - api "com.badlogicgames.gdx:gdx-box2d:$gdxVersion" - api "com.badlogicgames.gdx-controllers:gdx-controllers-core:$gdxControllersVersion" - - api "org.terasology.gestalt:gestalt-asset-core:$gestaltVersion" - api "org.terasology.gestalt:gestalt-entity-system:$gestaltVersion" - api "org.terasology.gestalt:gestalt-module:$gestaltVersion" - api "org.terasology.gestalt:gestalt-util:$gestaltVersion" - - api "org.terasology.gestalt:gestalt-di:$gestaltVersion" - api "org.terasology.gestalt:gestalt-inject:$gestaltVersion" - annotationProcessor "org.terasology.gestalt:gestalt-inject-java:$gestaltVersion" - - implementation "net.jcip:jcip-annotations:1.0" - - api "org.terasology.nui:nui:$nuiVersion" - api "org.terasology.nui:nui-libgdx:$nuiVersion" - api "org.terasology.nui:nui-gestalt:$nuiVersion" - api "org.terasology.nui:nui-reflect:$nuiVersion" - - implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.4.0' - - implementation "com.github.zafarkhaja:java-semver:0.10.0" // gestalt lost this... - api "com.github.everit-org.json-schema:org.everit.json.schema:1.11.1" - implementation "com.github.marschall:zipfilesystem-standalone:1.0.1" - implementation 'dom4j:dom4j:1.6.1' - - //TODO inserted by someone who has no idea how the gradle thing works. Inspect whether necessary (for tests). Copied rom desktop build.gradle. May break things with android (maybe) - testImplementation "com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion" - testImplementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" - testImplementation "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop" - testImplementation "com.badlogicgames.gdx-controllers:gdx-controllers-desktop:$gdxControllersVersion" - - // Test lib dependencies - testImplementation("org.junit.jupiter:junit-jupiter-api:6.1.2") - testImplementation("org.junit.jupiter:junit-jupiter-params:6.1.2") - testImplementation "org.mockito:mockito-junit-jupiter:5.23.0" - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:6.1.2") - testRuntimeOnly('org.junit.platform:junit-platform-launcher:6.1.2') - - testImplementation "org.jboss.shrinkwrap:shrinkwrap-depchain-java7:1.1.3" - testImplementation "org.assertj:assertj-core:3.27.7" -} - -// Adds Resources as parameter for AnnotationProcessor (gather ResourceIndex, -// also add resource as input for compilejava, for re-gathering ResourceIndex, when resource was changed. -compileJava { - inputs.files sourceSets.main.resources.srcDirs - options.compilerArgs = ["-Aresource=${sourceSets.main.resources.srcDirs.join(File.pathSeparator)}"] -} -compileTestJava { - inputs.files sourceSets.test.resources.srcDirs - options.compilerArgs = ["-Aresource=${sourceSets.test.resources.srcDirs.join(File.pathSeparator)}"] -} - -jar { - archiveFileName = "sol.jar" - - doFirst { - copy { - from 'src/SolAppListener.gwt.xml' - into 'build/classes/main' - } - } -} - -eclipse.project { - name = appName + "-engine" -} - -// Extra details provided for unit tests -test { - // ignoreFailures: Specifies whether the build should break when the verifications performed by this task fail. - ignoreFailures = true - useJUnitPlatform() - // showStandardStreams: makes the standard streams (err and out) visible at console when running tests - testLogging.showStandardStreams = true - workingDir = rootProject.projectDir -} diff --git a/engine/build.gradle.kts b/engine/build.gradle.kts new file mode 100644 index 000000000..02bab6ac5 --- /dev/null +++ b/engine/build.gradle.kts @@ -0,0 +1,139 @@ +/* + * Copyright 2022 The Terasology Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Dependencies needed for what our Gradle scripts themselves use. It cannot be included via an external Gradle file :-( +buildscript { + repositories { + mavenCentral() + + google() + maven { + url = uri("https://artifactory.terasology.io/artifactory/virtual-repo-live") + } + // Needed for Jsemver, which is a gestalt dependency + maven { url = uri("https://heisluft.de/maven/") } + } + + dependencies { + classpath("dom4j:dom4j:1.6.1") + } +} + +plugins { + `java-library` + id("destination-sol-common") + id("terasology-publish-common") +} + +group = "org.destinationsol.engine" + +val engineVersion = extra["engineVersion"] as String +val gdxVersion = extra["gdxVersion"] as String +val gdxControllersVersion = extra["gdxControllersVersion"] as String +val gestaltVersion = extra["gestaltVersion"] as String +val nuiVersion = extra["nuiVersion"] as String +val appName = extra["appName"] as String + +version = engineVersion + +dependencies { + api(group = "org.slf4j", name = "slf4j-api", version = "1.7.25") + api(group = "com.google.code.gson", name = "gson", version = "2.6.2") + api(group = "com.google.guava", name = "guava", version = "30.1-jre") + + api("com.badlogicgames.gdx:gdx:$gdxVersion") + api("com.badlogicgames.gdx:gdx-box2d:$gdxVersion") + api("com.badlogicgames.gdx-controllers:gdx-controllers-core:$gdxControllersVersion") + + api("org.terasology.gestalt:gestalt-asset-core:$gestaltVersion") + api("org.terasology.gestalt:gestalt-entity-system:$gestaltVersion") + api("org.terasology.gestalt:gestalt-module:$gestaltVersion") + api("org.terasology.gestalt:gestalt-util:$gestaltVersion") + + api("org.terasology.gestalt:gestalt-di:$gestaltVersion") + api("org.terasology.gestalt:gestalt-inject:$gestaltVersion") + annotationProcessor("org.terasology.gestalt:gestalt-inject-java:$gestaltVersion") + + implementation("net.jcip:jcip-annotations:1.0") + + api("org.terasology.nui:nui:$nuiVersion") + api("org.terasology.nui:nui-libgdx:$nuiVersion") + api("org.terasology.nui:nui-gestalt:$nuiVersion") + api("org.terasology.nui:nui-reflect:$nuiVersion") + + implementation(group = "com.google.protobuf", name = "protobuf-java", version = "3.4.0") + + implementation("com.github.zafarkhaja:java-semver:0.10.0") // gestalt lost this... + api("com.github.everit-org.json-schema:org.everit.json.schema:1.11.1") + implementation("com.github.marschall:zipfilesystem-standalone:1.0.1") + implementation("dom4j:dom4j:1.6.1") + + //TODO inserted by someone who has no idea how the gradle thing works. Inspect whether necessary (for tests). Copied rom desktop build.gradle. May break things with android (maybe) + testImplementation("com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion") + testImplementation("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") + testImplementation("com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop") + testImplementation("com.badlogicgames.gdx-controllers:gdx-controllers-desktop:$gdxControllersVersion") + + // Test lib dependencies + testImplementation("org.junit.jupiter:junit-jupiter-api:6.1.2") + testImplementation("org.junit.jupiter:junit-jupiter-params:6.1.2") + testImplementation("org.mockito:mockito-junit-jupiter:5.23.0") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:6.1.2") + testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.1.2") + + testImplementation("org.jboss.shrinkwrap:shrinkwrap-depchain-java7:1.1.3") + testImplementation("org.assertj:assertj-core:3.27.7") +} + +// Adds Resources as parameter for AnnotationProcessor (gather ResourceIndex, +// also add resource as input for compilejava, for re-gathering ResourceIndex, when resource was changed. +tasks.compileJava { + inputs.files(sourceSets.main.get().resources.srcDirs) + options.compilerArgs = listOf("-Aresource=${sourceSets.main.get().resources.srcDirs.joinToString(File.pathSeparator)}") +} +tasks.compileTestJava { + inputs.files(sourceSets.test.get().resources.srcDirs) + options.compilerArgs = listOf("-Aresource=${sourceSets.test.get().resources.srcDirs.joinToString(File.pathSeparator)}") +} + +tasks.jar { + archiveFileName.set("sol.jar") + + doFirst { + copy { + from("src/SolAppListener.gwt.xml") + into("build/classes/main") + } + } +} + +// eclipse is applied transitively (via destination-sol-common -> destination-sol-ide), not by +// this script's own plugins{} block, so the type-safe eclipse{} accessor isn't available here. +configure { + project { + name = "$appName-engine" + } +} + +// Extra details provided for unit tests +tasks.test { + // ignoreFailures: Specifies whether the build should break when the verifications performed by this task fail. + ignoreFailures = true + useJUnitPlatform() + // showStandardStreams: makes the standard streams (err and out) visible at console when running tests + testLogging.showStandardStreams = true + workingDir = rootProject.projectDir +} diff --git a/libs/subprojects.gradle b/libs/subprojects.gradle deleted file mode 100644 index 07598e541..000000000 --- a/libs/subprojects.gradle +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2020 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 - -// This magically allows subdirs to become included builds -// https://docs.gradle.org/6.4.1/userguide/composite_builds.html -file(".").eachDir { possibleIncludedBuildDirectory -> - File buildFile = new File(possibleIncludedBuildDirectory, "build.gradle") - File buildFileKts = new File(possibleIncludedBuildDirectory, "build.gradle.kts") - File settingsFile = new File(possibleIncludedBuildDirectory, "settings.gradle") - File settingsFileKts = new File(possibleIncludedBuildDirectory, "settings.gradle.kts") - - if ((buildFile.exists() || buildFileKts.exists()) && (settingsFile.exists() || settingsFileKts.exists())) { - logger.info("{} will be included in the composite build.", - rootDir.relativePath(possibleIncludedBuildDirectory)) - includeBuild(possibleIncludedBuildDirectory) - } else { - logger.warn("{} REJECTED as an included build. build.gradle: {}, settings.gradle: {}", - rootDir.relativePath(possibleIncludedBuildDirectory), - buildFile.exists() ? "present" : "MISSING", - settingsFile.exists() ? "present" : "MISSING" - ) - } -} \ No newline at end of file diff --git a/libs/subprojects.gradle.kts b/libs/subprojects.gradle.kts new file mode 100644 index 000000000..03a7a38cb --- /dev/null +++ b/libs/subprojects.gradle.kts @@ -0,0 +1,23 @@ +// Copyright 2020 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +// This magically allows subdirs to become included builds +// https://docs.gradle.org/6.4.1/userguide/composite_builds.html +file(".").listFiles { f -> f.isDirectory }?.forEach { possibleIncludedBuildDirectory -> + val buildFile = File(possibleIncludedBuildDirectory, "build.gradle") + val buildFileKts = File(possibleIncludedBuildDirectory, "build.gradle.kts") + val settingsFile = File(possibleIncludedBuildDirectory, "settings.gradle") + val settingsFileKts = File(possibleIncludedBuildDirectory, "settings.gradle.kts") + + if ((buildFile.exists() || buildFileKts.exists()) && (settingsFile.exists() || settingsFileKts.exists())) { + logger.info("{} will be included in the composite build.", + rootDir.toPath().relativize(possibleIncludedBuildDirectory.toPath())) + includeBuild(possibleIncludedBuildDirectory) + } else { + logger.warn("{} REJECTED as an included build. build.gradle: {}, settings.gradle: {}", + rootDir.toPath().relativize(possibleIncludedBuildDirectory.toPath()), + if (buildFile.exists()) "present" else "MISSING", + if (settingsFile.exists()) "present" else "MISSING" + ) + } +} diff --git a/modules/subprojects.gradle b/modules/subprojects.gradle deleted file mode 100644 index a009734ef..000000000 --- a/modules/subprojects.gradle +++ /dev/null @@ -1,25 +0,0 @@ -// This magically allows subdirs in this subproject to themselves become sub-subprojects in a proper tree structure -new File(rootDir, 'modules').eachDir { possibleSubprojectDir -> - if (!possibleSubprojectDir.name.startsWith(".")) { - def subprojectName = 'modules:' + possibleSubprojectDir.name - //println "Gradle is reviewing module $subprojectName for inclusion as a sub-project" - File buildFile = new File(possibleSubprojectDir, "build.gradle") - if (buildFile.exists()) { - println "Module $subprojectName has a build file so counting it complete and including it" - } else { - println "***** WARNING: Found a module without a build.gradle, Adding a build.gradle to $subprojectName. *****" - copy { - from("$rootProject.projectDir/templates") - into("$possibleSubprojectDir") - include("build.gradle") - } - } - - include subprojectName - def subprojectPath = ':' + subprojectName - def subproject = project(subprojectPath) - subproject.projectDir = possibleSubprojectDir - } else { - println "Ignoring path for module consideration: " + possibleSubprojectDir - } -} diff --git a/modules/subprojects.gradle.kts b/modules/subprojects.gradle.kts new file mode 100644 index 000000000..0095ba589 --- /dev/null +++ b/modules/subprojects.gradle.kts @@ -0,0 +1,25 @@ +// This magically allows subdirs in this subproject to themselves become sub-subprojects in a proper tree structure +File(rootDir, "modules").listFiles { file -> file.isDirectory }?.forEach { possibleSubprojectDir -> + if (!possibleSubprojectDir.name.startsWith(".")) { + val subprojectName = "modules:" + possibleSubprojectDir.name + //println("Gradle is reviewing module $subprojectName for inclusion as a sub-project") + val buildFile = File(possibleSubprojectDir, "build.gradle") + val buildFileKts = File(possibleSubprojectDir, "build.gradle.kts") + if (buildFile.exists() || buildFileKts.exists()) { + println("Module $subprojectName has a build file so counting it complete and including it") + } else { + println("***** WARNING: Found a module without a build.gradle.kts, Adding a build.gradle.kts to $subprojectName. *****") + copy { + from("${rootProject.projectDir}/templates") + into(possibleSubprojectDir) + include("build.gradle.kts") + } + } + + include(subprojectName) + val subprojectPath = ":$subprojectName" + project(subprojectPath).projectDir = possibleSubprojectDir + } else { + println("Ignoring path for module consideration: $possibleSubprojectDir") + } +} diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index fe49b896d..000000000 --- a/settings.gradle +++ /dev/null @@ -1,32 +0,0 @@ -includeBuild 'build-logic' - -include 'desktop', 'engine', 'modules' -import groovy.io.FileType - -File steamGradle = new File(rootDir, 'steam/build.gradle') -if (steamGradle.exists()) { - include 'steam' -} - -File gwtGradle = new File(rootDir, 'gwt/build.gradle') -if (gwtGradle.exists()) { - include 'gwt' -} - -rootProject.name = 'DestinationSol' - -// Handy little snippet found online that'll "fake" having nested settings.gradle files under /modules, /libs, etc -rootDir.eachDir { possibleSubprojectDir -> - - // First scan through all subdirs that has a subprojects.gradle in it and apply that script (recursive search!) - possibleSubprojectDir.eachFileMatch FileType.FILES, ~/subprojects\.gradle/, { subprojectsSpecificationScript -> - //println "Magic is happening, applying from " + subprojectsSpecificationScript - apply from: subprojectsSpecificationScript - } -} - -// This is put last to ensure that Android can detect the modules -File androidGradle = new File(rootDir, 'android/build.gradle') -if (androidGradle.exists()) { - include 'android' -} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 000000000..2accf7c4f --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,32 @@ +includeBuild("build-logic") + +include("desktop", "engine", "modules") + +val steamGradle = File(rootDir, "steam/build.gradle") +if (steamGradle.exists()) { + include("steam") +} + +val gwtGradle = File(rootDir, "gwt/build.gradle") +if (gwtGradle.exists()) { + include("gwt") +} + +rootProject.name = "DestinationSol" + +// Handy little snippet found online that'll "fake" having nested settings.gradle files under /modules, /libs, etc +rootDir.listFiles { file -> file.isDirectory }?.forEach { possibleSubprojectDir -> + + // First scan through all subdirs that has a subprojects.gradle.kts in it and apply that script (recursive search!) + possibleSubprojectDir.listFiles { file -> file.isFile && file.name == "subprojects.gradle.kts" } + ?.forEach { subprojectsSpecificationScript -> + //println("Magic is happening, applying from $subprojectsSpecificationScript") + apply(from = subprojectsSpecificationScript) + } +} + +// This is put last to ensure that Android can detect the modules +val androidGradle = File(rootDir, "android/build.gradle.kts") +if (androidGradle.exists()) { + include("android") +} diff --git a/templates/build.gradle b/templates/build.gradle deleted file mode 100644 index 5b0372d86..000000000 --- a/templates/build.gradle +++ /dev/null @@ -1,3 +0,0 @@ -plugins { - id 'destination-sol-module' -} diff --git a/templates/build.gradle.kts b/templates/build.gradle.kts new file mode 100644 index 000000000..1a1d3ae69 --- /dev/null +++ b/templates/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("destination-sol-module") +} From 4a8d9228ff3ed0e4b659ab51e6490b24d3ef680a Mon Sep 17 00:00:00 2001 From: soloturn Date: Sun, 23 Aug 2026 21:51:39 +0200 Subject: [PATCH 2/3] fix: address CodeRabbit review feedback on the Kotlin DSL conversion - settings.gradle.kts: check both build.gradle and build.gradle.kts for the steam/gwt/android optional projects, not just build.gradle. This is what was actually breaking CI: Jenkins checks out DestSolAndroid's own develop branch, which hasn't been converted to Kotlin DSL yet (that's a separate, not-yet-merged PR) - so android/build.gradle.kts never existed and the android project silently wasn't included, making :android:assembleDebug fail with 'project not found'. Verified against DestSolAndroid's actual current develop content. - build.gradle.kts: cleanIdea now resolves DestinationSol.iws and config/metrics against rootDir instead of the process working directory. - build.gradle.kts: fetchAndroid now reads -PgithubAccount, matching the same fix already applied to fetchSteam earlier. - libs/subprojects.gradle.kts: scan File(rootDir, "libs") explicitly. This script is apply(from = ...)'d from settings.gradle.kts, so file(".") resolved against the repo root instead of libs/ - the same class of bug as the settings.gradle.kts fix above, just currently dormant since libs/ has no subdirectories yet. Two other findings from the same review are real but out of scope for a no-behavior-change DSL conversion, since fixing them would change actual runtime/dev-workflow behavior rather than just the DSL: - destination-sol-jre.gradle.kts bundles a Java 11 JRE while the engine compiles with options.release = 17 (pre-existing in the original Groovy). - desktop/build.gradle.kts's afterEclipseImport task was never wired into the Eclipse import lifecycle - it relies on a legacy Spring Tool Suite Gradle plugin hook that Buildship, the current Eclipse Gradle plugin, doesn't support (also pre-existing). Filed as a follow-up issue rather than silently fixed here. Verified: gradlew help configures cleanly; :engine:compileJava and :desktop:compileJava both build clean; :android:assembleDebug succeeds against DestSolAndroid's actual current (unconverted) develop content, reproducing and confirming the fix for the CI failure. --- build.gradle.kts | 6 +++--- libs/subprojects.gradle.kts | 5 ++++- settings.gradle.kts | 11 +++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index c1b4d7967..948fc902a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -118,8 +118,8 @@ idea { tasks.named("cleanIdea") { doLast { - File("DestinationSol.iws").delete() - File("config/metrics").deleteRecursively() + rootDir.resolve("DestinationSol.iws").delete() + rootDir.resolve("config/metrics").deleteRecursively() println("Cleaned root - don't forget to re-extract code metrics config! 'gradlew extractConfig' will do so, or 'gradlew idea' (or eclipse)") } } @@ -131,7 +131,7 @@ tasks.register("fetchAndroid") { val repo = "DestSolAndroid" // Default GitHub account to use. Supply with -PgithubAccount="TargetAccountName" or via gradle.properties - val githubHome = "MovingBlocks" + val githubHome = findProperty("githubAccount") as String? ?: "MovingBlocks" val destination = file("android") diff --git a/libs/subprojects.gradle.kts b/libs/subprojects.gradle.kts index 03a7a38cb..8a9894cbf 100644 --- a/libs/subprojects.gradle.kts +++ b/libs/subprojects.gradle.kts @@ -3,7 +3,10 @@ // This magically allows subdirs to become included builds // https://docs.gradle.org/6.4.1/userguide/composite_builds.html -file(".").listFiles { f -> f.isDirectory }?.forEach { possibleIncludedBuildDirectory -> +// This script is apply(from = ...)'d from settings.gradle.kts, so file(".") here would resolve +// against settingsDir (the repo root), not against libs/ where this script itself lives - scan +// libs/ explicitly instead. +File(rootDir, "libs").listFiles { f -> f.isDirectory }?.forEach { possibleIncludedBuildDirectory -> val buildFile = File(possibleIncludedBuildDirectory, "build.gradle") val buildFileKts = File(possibleIncludedBuildDirectory, "build.gradle.kts") val settingsFile = File(possibleIncludedBuildDirectory, "settings.gradle") diff --git a/settings.gradle.kts b/settings.gradle.kts index 2accf7c4f..69300c24a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,13 +2,11 @@ includeBuild("build-logic") include("desktop", "engine", "modules") -val steamGradle = File(rootDir, "steam/build.gradle") -if (steamGradle.exists()) { +if (File(rootDir, "steam/build.gradle").exists() || File(rootDir, "steam/build.gradle.kts").exists()) { include("steam") } -val gwtGradle = File(rootDir, "gwt/build.gradle") -if (gwtGradle.exists()) { +if (File(rootDir, "gwt/build.gradle").exists() || File(rootDir, "gwt/build.gradle.kts").exists()) { include("gwt") } @@ -26,7 +24,8 @@ rootDir.listFiles { file -> file.isDirectory }?.forEach { possibleSubprojectDir } // This is put last to ensure that Android can detect the modules -val androidGradle = File(rootDir, "android/build.gradle.kts") -if (androidGradle.exists()) { +// Checks both names: Jenkins checks out DestSolAndroid's own develop branch here, which may or +// may not have been converted to Kotlin DSL independently of this repo. +if (File(rootDir, "android/build.gradle").exists() || File(rootDir, "android/build.gradle.kts").exists()) { include("android") } From 9d5764a1fa7c6a907caa69064ccb5556fb359197 Mon Sep 17 00:00:00 2001 From: soloturn Date: Sun, 23 Aug 2026 23:56:38 +0200 Subject: [PATCH 3/3] fix: address remaining CodeRabbit findings from #737 review - destination-sol-jre.gradle.kts: bump bundled Liberica JRE from 11.0.19+7 to 17.0.12+10 - the engine compiles with options.release = 17 (pre-existing on develop, carried over unnoticed by the Groovy->Kotlin conversion), so the bundled distribution's JRE couldn't load its own classes. Windows key moves from windows-i586 (32-bit, dropped from BellSoft builds since JDK 12) to windows-amd64. - desktop/build.gradle.kts: wire afterEclipseImport to run via finalizedBy(eclipse) - registering it alone never made :desktop:eclipse invoke it (same pre-existing gap in the original Groovy). Verified the assets classpathentry actually lands in .classpath after this. - build.gradle.kts: fix the post-cleanIdea message, which named a task (extractConfig) that has never existed - the real one is extractMetricsConfig, referenced correctly two other places in this file. - libs/subprojects.gradle.kts: the composite-build rejection warning checked only the Groovy build.gradle/settings.gradle filenames, so a rejected Kotlin DSL build could misreport which file is actually missing. Verified: gradlew help, :engine:compileJava, :desktop:compileJava, gradlew idea, and :desktop:eclipse all still succeed. Co-Authored-By: Claude Sonnet 5 --- .../src/main/kotlin/destination-sol-jre.gradle.kts | 10 +++++++--- build.gradle.kts | 2 +- desktop/build.gradle.kts | 5 +++++ libs/subprojects.gradle.kts | 6 +++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/build-logic/src/main/kotlin/destination-sol-jre.gradle.kts b/build-logic/src/main/kotlin/destination-sol-jre.gradle.kts index 6b86d9f8e..3fa39a303 100644 --- a/build-logic/src/main/kotlin/destination-sol-jre.gradle.kts +++ b/build-logic/src/main/kotlin/destination-sol-jre.gradle.kts @@ -26,12 +26,16 @@ plugins { // so the type-safe `base { }` accessor isn't available here - look it up explicitly instead. val distsDirectory = the().distsDirectory -// Uses Bellsoft Liberica JRE -val jreVersion = "11.0.19+7" +// Uses Bellsoft Liberica JRE. Must stay on a Java 17+ build - the engine compiles with +// options.release = 17 (see destination-sol-java.gradle.kts), and a Java 11 runtime can't +// load Java 17 class files (UnsupportedClassVersionError). +val jreVersion = "17.0.12+10" val jreUrlBase = "https://download.bell-sw.com/java/$jreVersion/bellsoft-jre$jreVersion" val jreUrlFilenames = mapOf( "lwjreLinux64" to "linux-amd64.tar.gz", - "lwjre" to "windows-i586.zip", + // 32-bit Windows dropped from JDK 12+ builds; windows-amd64 is the closest equivalent to + // the old windows-i586 key. + "lwjre" to "windows-amd64.zip", "lwjreOSX" to "macos-amd64.zip", "lwjreOSXArm" to "macos-aarch64.zip" ) diff --git a/build.gradle.kts b/build.gradle.kts index 948fc902a..a99e11f67 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -120,7 +120,7 @@ tasks.named("cleanIdea") { doLast { rootDir.resolve("DestinationSol.iws").delete() rootDir.resolve("config/metrics").deleteRecursively() - println("Cleaned root - don't forget to re-extract code metrics config! 'gradlew extractConfig' will do so, or 'gradlew idea' (or eclipse)") + println("Cleaned root - don't forget to re-extract code metrics config! 'gradlew extractMetricsConfig' will do so, or 'gradlew idea' (or eclipse)") } } diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index 26d1208bb..2457a1ac5 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -194,6 +194,11 @@ tasks.register("afterEclipseImport") { } } +// Registering the task above doesn't make :desktop:eclipse run it. finalizedBy, not +// synchronizationTasks: the latter only fires on a Buildship-driven IDE sync, not on a plain +// `gradlew eclipse` - and .classpath (which this patches) doesn't exist until eclipse has run. +tasks.named("eclipse") { finalizedBy(tasks.named("afterEclipseImport")) } + tasks.withType().configureEach { if (System.getProperty("DEBUG", "false") == "true") { jvmArgs("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=9099", "-Dlog4j.configuration=log4j-debug.properties") diff --git a/libs/subprojects.gradle.kts b/libs/subprojects.gradle.kts index 8a9894cbf..917a05b38 100644 --- a/libs/subprojects.gradle.kts +++ b/libs/subprojects.gradle.kts @@ -17,10 +17,10 @@ File(rootDir, "libs").listFiles { f -> f.isDirectory }?.forEach { possibleInclud rootDir.toPath().relativize(possibleIncludedBuildDirectory.toPath())) includeBuild(possibleIncludedBuildDirectory) } else { - logger.warn("{} REJECTED as an included build. build.gradle: {}, settings.gradle: {}", + logger.warn("{} REJECTED as an included build. build.gradle(.kts): {}, settings.gradle(.kts): {}", rootDir.toPath().relativize(possibleIncludedBuildDirectory.toPath()), - if (buildFile.exists()) "present" else "MISSING", - if (settingsFile.exists()) "present" else "MISSING" + if (buildFile.exists() || buildFileKts.exists()) "present" else "MISSING", + if (settingsFile.exists() || settingsFileKts.exists()) "present" else "MISSING" ) } }