diff --git a/Jenkinsfile b/Jenkinsfile index 9964626..4bf92b0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,6 +1,6 @@ pipeline { agent { - label "light-java" + label "light-java && java17" } stages { stage('Build') { diff --git a/build.gradle b/build.gradle.kts similarity index 52% rename from build.gradle rename to build.gradle.kts index ec935a9..f344325 100644 --- a/build.gradle +++ b/build.gradle.kts @@ -2,15 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 // For generating IntelliJ project files -apply plugin: 'idea' +plugins { + idea +} -wrapper { - gradleVersion = '8.2.1' +tasks.wrapper { + gradleVersion = "9.6.1" + distributionSha256Sum = "9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14" } // Using this instead of allprojects allows this project to be embedded yet not affect parent projects -group = 'org.terasology' +group = "org.terasology" subprojects { - group = 'org.terasology.crashreporter' + group = "org.terasology.crashreporter" } - diff --git a/cr-core/build.gradle b/cr-core/build.gradle deleted file mode 100644 index 60fc774..0000000 --- a/cr-core/build.gradle +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 - -plugins { - id "org.standardout.versioneye" version "1.3.0" - id "com.github.spotbugs" version "4.6.0" - id "java-library" - id "eclipse" - id "idea" - id "checkstyle" - id "pmd" - id "maven-publish" -} - -apply from: "$rootDir/gradle/common.gradle" - -def env = System.getenv() -def versionInfoFile = new File(sourceSets.main.output.resourcesDir, 'versionInfo.properties') - -sourceCompatibility = 1.8 -targetCompatibility = 1.8 - -// We use both Maven Central and our own Artifactory instance, which contains module builds, extra libs, and so on -repositories { - mavenCentral { - content { - // This is first choice for most java dependencies, but assume we'll need to check our - // own repository for things from our own organization. - // (This is an optimization so gradle doesn't try to find our hundreds of modules in 3rd party repos) - excludeGroupByRegex("org.terasology(..+)?") - } - } - // JBoss Maven Repository requried to fetch `org.jpastebin` dependency for CrashReporter - // https://developer.jboss.org/docs/DOC-11377 - maven { - name "JBoss Public Maven Repository Group" - url "https://repository.jboss.org/nexus/content/repositories/public/" - content { - includeModule("org", "jpastebin") - } - } - maven { - name "Terasology Artifactory" - url "http://artifactory.terasology.org:8081/artifactory/virtual-repo-live" - allowInsecureProtocol true // 😱 - } -} - -configurations { - codeMetrics -} - -dependencies { - - codeMetrics(group: 'org.terasology.config', name: 'codemetrics', version: '1.1.0', ext: 'zip') - - checkstyle ('com.puppycrawl.tools:checkstyle:6.17') - pmd ('net.sourceforge.pmd:pmd-core:5.4.1') - pmd ('net.sourceforge.pmd:pmd-java:5.4.1') - - implementation group: 'org', name: 'jpastebin', version: '1.0.1' - implementation group: 'org.apache.httpcomponents', name: 'httpcomponents-client', version: '4.5.2' - implementation group: 'org.apache.httpcomponents', name: 'httpmime', version: '4.5.2' - - testImplementation group: 'junit', name: 'junit', version: '4.12' - testImplementation group: 'org.mockito', name: 'mockito-core', version: '2.7.22' - testImplementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.21' - - testRuntimeOnly group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.7' - - implementation group: 'com.google.guava', name: 'guava', version: '19.0' - - // But on the other hand to be able to run the unit tests successfully while embedded we still do need this - if (rootProject.name == "Terasology") { - testImplementation group: 'com.google.http-client', name: 'google-http-client-jackson2', version: '1.20.0' - } -} - -def convertGitBranch = { gitBranch -> - // Remove "origin/" from "origin/develop" - return gitBranch ? gitBranch.substring(gitBranch.lastIndexOf("/") + 1) : null -} - -task createVersionInfoFile { - - doLast { - logger.lifecycle("Creating $versionInfoFile") - - ant.propertyfile(file: versionInfoFile) { - ant.entry(key: 'buildNumber', value: env.BUILD_NUMBER) - ant.entry(key: 'buildId', value: env.BUILD_ID) - ant.entry(key: 'buildTag', value: env.BUILD_TAG) - ant.entry(key: 'buildUrl', value: env.BUILD_URL) - ant.entry(key: 'jobName', value: env.JOB_NAME) - ant.entry(key: 'gitBranch', value: convertGitBranch(env.GIT_BRANCH)) - ant.entry(key: 'gitCommit', value: env.GIT_COMMIT) - ant.entry(key: 'displayVersion', value: version) - } - } -} - -jar.dependsOn createVersionInfoFile - - -task sourceJar(type: Jar) { - description = "Create a JAR with all sources" - from sourceSets.main.allSource - archiveClassifier = "sources" -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - description = "Create a JAR with the JavaDoc for the java sources" - from javadoc.destinationDir - archiveClassifier = "javadoc" -} - -task runInteractiveTest(type: JavaExec, dependsOn: testClasses) { - main = "org.terasology.crashreporter.InteractiveTestCases" - classpath = files(sourceSets.test.runtimeClasspath) - args = ['setupForExtraLongMessageException', 'src/test/resources/lengthy_logfile.log', 'en-US'] -} - -// Define the artifacts we want to publish (the .pom will also be included since the Maven plugin is active) -artifacts { - archives sourceJar - archives javadocJar -} - -configure([checkstyleMain, checkstyleTest]) { - doFirst { - def suppressions = resources.text.fromArchiveEntry(configurations.codeMetrics, "checkstyle/suppressions.xml") - // the asFile() method extracts the file from the zip archive and puts it next to checkstyle.xml - // this procedure should not be done in the config phase, since the clean task would erase the file before it can be used - suppressions.asFile() - } -} - -checkstyle { - ignoreFailures = true - config = resources.text.fromArchiveEntry(configurations.codeMetrics, "checkstyle/checkstyle.xml") - // this assigns the property variable ${samedir} in checkstyle.xml - configProperties.samedir = config.asFile().parent -} - -pmd { - incrementalAnalysis = false - ignoreFailures = true - ruleSetConfig = resources.text.fromArchiveEntry(configurations.codeMetrics, "pmd/pmd.xml") - ruleSets = [] -} - -spotbugs { - ignoreFailures = true - effort = 'max' - reportLevel = 'medium' - // includeFilter = resources.text.fromArchiveEntry(configurations.codeMetrics, "findbugs/findbugs-exclude.xml") -} - -javadoc { - failOnError = false -} diff --git a/cr-core/build.gradle.kts b/cr-core/build.gradle.kts new file mode 100644 index 0000000..7fcf709 --- /dev/null +++ b/cr-core/build.gradle.kts @@ -0,0 +1,158 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +import org.gradle.api.plugins.quality.Checkstyle +import org.gradle.api.plugins.quality.Pmd + +plugins { + `java-library` + eclipse + idea + checkstyle + pmd + `maven-publish` +} + +apply(from = "$rootDir/gradle/common.gradle.kts") + +val env: Map = System.getenv() +val versionInfoFile = File(sourceSets.main.get().output.resourcesDir, "versionInfo.properties") + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + withSourcesJar() + withJavadocJar() +} + +// We use both Maven Central and our own Artifactory instance, which contains module builds, extra libs, and so on +repositories { + mavenCentral { + content { + // This is first choice for most java dependencies, but assume we'll need to check our + // own repository for things from our own organization. + // (This is an optimization so gradle doesn't try to find our hundreds of modules in 3rd party repos) + excludeGroupByRegex("org.terasology(..+)?") + } + } + // JBoss Maven Repository requried to fetch `org.jpastebin` dependency for CrashReporter + // https://developer.jboss.org/docs/DOC-11377 + maven { + name = "JBoss Public Maven Repository Group" + url = uri("https://repository.jboss.org/nexus/content/repositories/public/") + content { + includeModule("org", "jpastebin") + } + } + maven { + name = "Terasology Artifactory" + url = uri("https://artifactory.terasology.io/artifactory/virtual-repo-live") + } +} + +val codeMetrics = configurations.create("codeMetrics") + +dependencies { + + codeMetrics("org.terasology.config:codemetrics:2.2.0@zip") + + checkstyle("com.puppycrawl.tools:checkstyle:10.2") + pmd("net.sourceforge.pmd:pmd-ant:7.0.0-rc4") + pmd("net.sourceforge.pmd:pmd-core:7.0.0-rc4") + pmd("net.sourceforge.pmd:pmd-java:7.0.0-rc4") + + implementation("org:jpastebin:1.0.1") + implementation("org.apache.httpcomponents:httpclient:4.5.13") + implementation("org.apache.httpcomponents:httpmime:4.5.13") + + testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1") + testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.1") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testImplementation("org.mockito:mockito-core:5.6.0") + testImplementation("org.slf4j:slf4j-api:2.0.18") + + testRuntimeOnly("ch.qos.logback:logback-classic:1.6.0") + + implementation("com.google.guava:guava:31.1-jre") + + // But on the other hand to be able to run the unit tests successfully while embedded we still do need this + if (rootProject.name == "Terasology") { + testImplementation("com.google.http-client:google-http-client-jackson2:1.20.0") + } +} + +fun convertGitBranch(gitBranch: String?): String? { + // Remove "origin/" from "origin/develop" + return if (gitBranch.isNullOrEmpty()) null else gitBranch.substringAfterLast("/") +} + +val createVersionInfoFile = tasks.register("createVersionInfoFile") { + doLast { + logger.lifecycle("Creating $versionInfoFile") + ant.withGroovyBuilder { + "propertyfile"("file" to versionInfoFile) { + "entry"("key" to "buildNumber", "value" to env["BUILD_NUMBER"]) + "entry"("key" to "buildId", "value" to env["BUILD_ID"]) + "entry"("key" to "buildTag", "value" to env["BUILD_TAG"]) + "entry"("key" to "buildUrl", "value" to env["BUILD_URL"]) + "entry"("key" to "jobName", "value" to env["JOB_NAME"]) + "entry"("key" to "gitBranch", "value" to convertGitBranch(env["GIT_BRANCH"])) + "entry"("key" to "gitCommit", "value" to env["GIT_COMMIT"]) + "entry"("key" to "displayVersion", "value" to version) + } + } + } +} + +tasks.jar { + dependsOn(createVersionInfoFile) +} + +val runInteractiveTest = tasks.register("runInteractiveTest") { + dependsOn(tasks.named("testClasses")) + mainClass.set("org.terasology.crashreporter.InteractiveTestCases") + classpath = files(sourceSets.test.get().runtimeClasspath) + args = listOf("setupForExtraLongMessageException", "src/test/resources/lengthy_logfile.log", "en-US") +} + +// checkstyle.xml's own SuppressionFilter references ${config_loc}/suppressions.xml, Checkstyle's +// built-in "directory containing the config file" property - that's only populated correctly +// when the config is loaded from a real directory on disk, not from an in-place archive-entry +// read (which extracts each entry to its own isolated temp location, so checkstyle.xml and +// suppressions.xml never end up as real siblings). Extract the already-downloaded codeMetrics +// zip once instead, and point checkstyle/pmd at the extracted directory. +val extractCodeMetrics = tasks.register("extractCodeMetrics") { + from(codeMetrics.map { zipTree(it) }) + into(layout.buildDirectory.dir("codeMetrics")) +} + +tasks.withType().configureEach { + dependsOn(extractCodeMetrics) +} + +tasks.withType().configureEach { + dependsOn(extractCodeMetrics) +} + +checkstyle { + isIgnoreFailures = true + configDirectory.set(layout.buildDirectory.dir("codeMetrics/checkstyle")) +} + +pmd { + isIgnoreFailures = true + ruleSetFiles = files(layout.buildDirectory.file("codeMetrics/pmd/pmd.xml")) + ruleSets = listOf() +} + +tasks.javadoc { + isFailOnError = false +} + +tasks.test { + useJUnitPlatform() + // InteractiveTestCases is a manual runner (see runInteractiveTest), not an automated test - + // there are no @Test methods in this project. + failOnNoDiscoveredTests.set(false) +} diff --git a/cr-core/src/main/java/org/terasology/crashreporter/CrashReporter.java b/cr-core/src/main/java/org/terasology/crashreporter/CrashReporter.java index 787f077..850db0e 100644 --- a/cr-core/src/main/java/org/terasology/crashreporter/CrashReporter.java +++ b/cr-core/src/main/java/org/terasology/crashreporter/CrashReporter.java @@ -13,8 +13,12 @@ import java.awt.Dialog; import java.awt.Dimension; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; /** * Displays a detailed error message and provides some options to communicate with devs. @@ -48,6 +52,14 @@ public static void report(final Throwable throwable, final Path logFileFolder) { * @param mode crash reporter, issue reporter or feedback window */ public static void report(final Throwable throwable, final Path logFileFolder, final MODE mode) { + if (requiresProcessIsolation()) { + reportInSubprocess(throwable, logFileFolder, mode); + } else { + reportInProcess(throwable, logFileFolder, mode); + } + } + + private static void reportInProcess(final Throwable throwable, final Path logFileFolder, final MODE mode) { // Swing element methods must be called in the swing thread try { SwingUtilities.invokeAndWait(new Runnable() { @@ -74,6 +86,111 @@ public void run() { } } + /** + * Name of the system property that, when set to {@code true}, forces + * {@link #requiresProcessIsolation()} to return {@code true} regardless of platform - lets + * the subprocess path be exercised on non-macOS platforms too, e.g. for testing. + *

+ * Only the property name is a constant here - {@code static final} on this String + * has no bearing on how live the check is. {@link #requiresProcessIsolation()} calls + * {@link Boolean#getBoolean(String)} (which reads {@link System#getProperty(String)}) fresh + * on every invocation; nothing caches the resolved value at class-load time. So this can be + * set any time before {@link #report(Throwable, java.nio.file.Path)} is actually called - + * via a {@code -D} JVM launch flag, or programmatically via + * {@link System#setProperty(String, String)} at runtime. + */ + private static final String FORCE_PROCESS_ISOLATION_PROPERTY = "org.terasology.crashreporter.forceProcessIsolation"; + + /** + * On macOS, a process launched with {@code -XstartOnFirstThread} (required by GLFW-based + * games such as Terasology and Destination Sol, so they can create their window) permanently + * claims the OS's single native UI thread for its own run loop. AWT's native AppKit toolkit + * needs that very same thread to initialize, and blocks forever if it's already claimed - so + * a Swing dialog can never appear in such a process. + *

+ * {@code -XstartOnFirstThread} itself isn't visible via + * {@link ManagementFactory#getRuntimeMXBean()}'s input arguments - it's consumed by the + * native launcher before the JVM's own argument list is populated - so it can't be detected + * directly, and not every caller pairs it with a reliably-visible signal like + * {@code -Djava.awt.headless=true} (Terasology does; Destination Sol doesn't). Rather than + * depend on a convention only some callers follow, isolate unconditionally on macOS - the + * cost of an unnecessary subprocess is small, and a caller that isn't actually + * {@code -XstartOnFirstThread}-constrained just gets the dialog shown from a fresh process + * instead of in-process, with no functional difference either way. + */ + private static boolean requiresProcessIsolation() { + if (Boolean.getBoolean(FORCE_PROCESS_ISOLATION_PROPERTY)) { + return true; + } + String osName = System.getProperty("os.name", ""); + return osName.toLowerCase().contains("mac"); + } + + private static void reportInSubprocess(Throwable throwable, Path logFileFolder, MODE mode) { + String javaBin = Paths.get(System.getProperty("java.home"), "bin", "java").toString(); + String message = throwable.getLocalizedMessage(); + + List command = new ArrayList<>(); + command.add(javaBin); + command.add("-cp"); + command.add(System.getProperty("java.class.path")); + command.add(CrashReporter.class.getName()); + command.add(throwable.getClass().getName()); + command.add(message == null ? "" : message); + command.add(logFileFolder == null ? "" : logFileFolder.toAbsolutePath().toString()); + command.add(mode.name()); + + try { + ProcessBuilder processBuilder = new ProcessBuilder(command) + .redirectOutput(ProcessBuilder.Redirect.DISCARD) + .redirectError(ProcessBuilder.Redirect.INHERIT); + // -XstartOnFirstThread is exactly the constraint requiresProcessIsolation() isolates + // against - every JVM reads these two env vars at startup automatically, so if either + // carries that flag, ProcessBuilder's default environment inheritance would hand the + // exact same constraint straight back to the subprocess we're spawning to escape it. + processBuilder.environment().remove("JAVA_TOOL_OPTIONS"); + processBuilder.environment().remove("_JAVA_OPTIONS"); + processBuilder.start(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Entry point used internally to relaunch the reporter in an isolated process, see + * {@link #reportInSubprocess}. Not intended to be invoked directly by callers of this + * library. + */ + public static void main(String[] args) { + String throwableClassName = args[0]; + String message = args[1].isEmpty() ? null : args[1]; + Path logFileFolder = args[2].isEmpty() ? null : Paths.get(args[2]); + MODE mode = MODE.valueOf(args[3]); + + reportInProcess(reconstructThrowable(throwableClassName, message), logFileFolder, mode); + } + + /** + * Best-effort reconstruction of the original throwable's identity in the new process: a real + * exception can't be passed across a process boundary, and + * {@link org.terasology.crashreporter.pages.ErrorMessagePanel} only ever reads + * {@code getClass().getSimpleName()} and {@code getLocalizedMessage()} for display, so + * that's all that needs to survive the trip. + */ + private static Throwable reconstructThrowable(String className, String message) { + try { + // Don't initialize (run static initializers of) a class from an arbitrary name + // before confirming it's actually a Throwable subtype we intend to instantiate. + Class throwableClass = Class.forName(className, false, CrashReporter.class.getClassLoader()); + if (Throwable.class.isAssignableFrom(throwableClass)) { + return (Throwable) throwableClass.getConstructor(String.class).newInstance(message); + } + } catch (ReflectiveOperationException e) { + // fall through to the generic Throwable below + } + return new Throwable(message == null ? className : className + ": " + message); + } + protected static void showModalDialog(Throwable throwable, GlobalProperties properties, Path logFolder, MODE mode) { String dialogTitle; switch (mode) { diff --git a/cr-destsol/build.gradle b/cr-destsol/build.gradle deleted file mode 100644 index b9be56f..0000000 --- a/cr-destsol/build.gradle +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 - -plugins { - id "java-library" - id "maven-publish" - id "eclipse" - id "idea" -} - -dependencies { - api project(":cr-core") -} - -apply from: "$rootDir/gradle/common.gradle" diff --git a/cr-destsol/build.gradle.kts b/cr-destsol/build.gradle.kts new file mode 100644 index 0000000..0b4ceb0 --- /dev/null +++ b/cr-destsol/build.gradle.kts @@ -0,0 +1,15 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +plugins { + `java-library` + `maven-publish` + eclipse + idea +} + +dependencies { + api(project(":cr-core")) +} + +apply(from = "$rootDir/gradle/common.gradle.kts") diff --git a/cr-terasology/build.gradle b/cr-terasology/build.gradle deleted file mode 100644 index 86e0aaa..0000000 --- a/cr-terasology/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 - -plugins { - id "java-library" - id "maven-publish" - id "eclipse" - id "idea" -} - -repositories { - maven { - name "Terasology Artifactory" - url "http://artifactory.terasology.org:8081/artifactory/virtual-repo-live" - } - mavenCentral() - maven { - name "JBoss Public Maven Repository Group" - url "https://repository.jboss.org/nexus/content/repositories/public/" - } -} - -dependencies { - api project(":cr-core") -} - -java { - targetCompatibility = JavaVersion.VERSION_1_8 -} - -apply from: "$rootDir/gradle/common.gradle" diff --git a/cr-terasology/build.gradle.kts b/cr-terasology/build.gradle.kts new file mode 100644 index 0000000..dce1c43 --- /dev/null +++ b/cr-terasology/build.gradle.kts @@ -0,0 +1,31 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +plugins { + `java-library` + `maven-publish` + eclipse + idea +} + +repositories { + maven { + name = "Terasology Artifactory" + url = uri("https://artifactory.terasology.io/artifactory/virtual-repo-live") + } + mavenCentral() + maven { + name = "JBoss Public Maven Repository Group" + url = uri("https://repository.jboss.org/nexus/content/repositories/public/") + } +} + +dependencies { + api(project(":cr-core")) +} + +java { + targetCompatibility = JavaVersion.VERSION_1_8 +} + +apply(from = "$rootDir/gradle/common.gradle.kts") diff --git a/gradle/common.gradle b/gradle/common.gradle deleted file mode 100644 index bfa3859..0000000 --- a/gradle/common.gradle +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 - -apply plugin: 'java' -apply plugin: 'maven-publish' - -publishing { - publications { - "CrashReporter"(MavenPublication) { - // Without this we get a .pom with no dependencies - from components.java - - repositories { - maven { - name = 'TerasologyOrg' - allowInsecureProtocol true // 😱 - no https on our Artifactory yet - - if (rootProject.hasProperty("publishRepo")) { - // This first option is good for local testing, you can set a full explicit target repo in gradle.properties - url = "http://artifactory.terasology.org/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 = "http://artifactory.terasology.org/artifactory/$deducedPublishRepo" - } - - if (rootProject.hasProperty("mavenUser") && rootProject.hasProperty("mavenPass")) { - credentials { - username = "$mavenUser" - password = "$mavenPass" - } - authentication { - basic(BasicAuthentication) - } - } - } - } - } - } -} diff --git a/gradle/common.gradle.kts b/gradle/common.gradle.kts new file mode 100644 index 0000000..780c8ef --- /dev/null +++ b/gradle/common.gradle.kts @@ -0,0 +1,56 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.authentication.http.BasicAuthentication + +// Applied via apply(from = ...) into each subproject's build script, so it must use the legacy +// apply(plugin = ...) form (the plugins {} block isn't available in script plugins) and the +// explicit configure form (script plugins don't get the generated type-safe accessors a +// project's own primary build script does). +apply(plugin = "java") +apply(plugin = "maven-publish") + +configure { + publications { + create("CrashReporter") { + // Without this we get a .pom with no dependencies + from(components["java"]) + } + } + repositories { + maven { + name = "TerasologyOrg" + + val publishRepo = if (rootProject.hasProperty("publishRepo")) { + // This first option is good for local testing, you can set a full explicit target repo in gradle.properties + (rootProject.property("publishRepo") as String).also { + logger.info("Changing PUBLISH repoKey set via Gradle property to {}", it) + } + } else { + // Support override from the environment to use a different target publish org + var deducedPublishRepo = System.getenv("PUBLISH_ORG")?.takeIf { it.isNotEmpty() } ?: "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) + deducedPublishRepo + } + url = uri("https://artifactory.terasology.io/artifactory/$publishRepo") + + 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/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 033e24c..b1b8ef5 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9f4197d..dbe66e1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,10 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index fcb6fca..249efbb 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,10 +15,12 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -27,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,7 +85,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -111,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -144,7 +146,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +154,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -169,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -201,16 +202,15 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 6689b85..8508ef6 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,16 +13,18 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -43,13 +45,13 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -57,36 +59,24 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% -:mainEnd -if "%OS%"=="Windows_NT" endlocal +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index f193093..0000000 --- a/settings.gradle +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 - -// Check to see if CR is embedded within Terasology, if so use nested paths -println "rootProject for CrashReporter is: " + rootProject.name - -if (rootProject.name == 'Terasology') { - println "CrashReporter is embedded within Terasology, using nested paths" - include ':libs:CrashReporter:cr-core' - include ':libs:CrashReporter:cr-destsol' - include ':libs:CrashReporter:cr-terasology' -} else { - println "CrashReporter is running standalone so using simple paths for includes" - include 'cr-core' - include 'cr-destsol' - include 'cr-terasology' -} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..ac1fcd9 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,17 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +// Check to see if CR is embedded within Terasology, if so use nested paths +println("rootProject for CrashReporter is: " + rootProject.name) + +if (rootProject.name == "Terasology") { + println("CrashReporter is embedded within Terasology, using nested paths") + include(":libs:CrashReporter:cr-core") + include(":libs:CrashReporter:cr-destsol") + include(":libs:CrashReporter:cr-terasology") +} else { + println("CrashReporter is running standalone so using simple paths for includes") + include("cr-core") + include("cr-destsol") + include("cr-terasology") +}