From 47bb0f6cab35c26fbd6dea4e37e0f64e7e6762a7 Mon Sep 17 00:00:00 2001 From: JeremyStarTM Date: Thu, 19 Dec 2024 02:50:00 +0100 Subject: [PATCH] Add BuildInformation & a matching CrashCategory --- .../de/staropensource/engine/base/Engine.kt | 13 + .../engine/base/EngineConfiguration.kt | 4 +- .../crashcategory/EngineCrashCategory.kt | 55 ++ .../base/utility/dnihbd/BuildInformation.kt | 528 ++++++++++++++++++ .../base/utility/dnihbd/package-info.kt | 31 + 5 files changed, 630 insertions(+), 1 deletion(-) create mode 100644 base/src/main/kotlin/de/staropensource/engine/base/implementation/logging/crashcategory/EngineCrashCategory.kt create mode 100644 base/src/main/kotlin/de/staropensource/engine/base/utility/dnihbd/BuildInformation.kt create mode 100644 base/src/main/kotlin/de/staropensource/engine/base/utility/dnihbd/package-info.kt diff --git a/base/src/main/kotlin/de/staropensource/engine/base/Engine.kt b/base/src/main/kotlin/de/staropensource/engine/base/Engine.kt index e03aa2a97..6f1cb7126 100644 --- a/base/src/main/kotlin/de/staropensource/engine/base/Engine.kt +++ b/base/src/main/kotlin/de/staropensource/engine/base/Engine.kt @@ -24,6 +24,7 @@ import de.staropensource.engine.base.exception.EngineInitializationFailureExcept import de.staropensource.engine.base.utility.Environment import de.staropensource.engine.base.utility.FileAccess import de.staropensource.engine.base.logging.Logger +import de.staropensource.engine.base.utility.dnihbd.BuildInformation /** * Primary class of the engine. @@ -70,6 +71,16 @@ class Engine private constructor() { */ var bootstrapping: Boolean? = null + /** + * Contains a [BuildInformation] instance + * providing information about the running + * engine build. + * + * @see BuildInformation + * @since v1-alpha10 + */ + var info: BuildInformation? = null + // -----> Initialization /** @@ -136,6 +147,7 @@ class Engine private constructor() { // Run initialization code Environment.detect() FileAccess.updateDefaultPaths() + info = BuildInformation(loadPrefix = "sosengine-base") state = State.INITIALIZED } catch (exception: Exception) { @@ -225,6 +237,7 @@ class Engine private constructor() { Environment.unset() FileAccess.deleteScheduled() FileAccess.unsetDefaultPaths() + info = null } } diff --git a/base/src/main/kotlin/de/staropensource/engine/base/EngineConfiguration.kt b/base/src/main/kotlin/de/staropensource/engine/base/EngineConfiguration.kt index 4754bcfba..1efbc9396 100644 --- a/base/src/main/kotlin/de/staropensource/engine/base/EngineConfiguration.kt +++ b/base/src/main/kotlin/de/staropensource/engine/base/EngineConfiguration.kt @@ -28,6 +28,7 @@ import de.staropensource.engine.base.implementation.logging.crashcategory.InfoCr import de.staropensource.engine.base.implementation.logging.formatbuilder.SOSLSv2FormatBuilder import de.staropensource.engine.base.logging.Logger import de.staropensource.engine.base.implementable.logging.LoggerThreadingHandler +import de.staropensource.engine.base.implementation.logging.crashcategory.EngineCrashCategory import de.staropensource.engine.base.type.logging.ChannelSettings import de.staropensource.engine.base.type.logging.Feature import de.staropensource.engine.base.type.logging.Level @@ -150,7 +151,8 @@ class EngineConfiguration private constructor() { */ @JvmStatic var logCrashCategories: LinkedHashSet = linkedSetOf( - InfoCrashCategory.instance + InfoCrashCategory.instance, + EngineCrashCategory.instance ) /** diff --git a/base/src/main/kotlin/de/staropensource/engine/base/implementation/logging/crashcategory/EngineCrashCategory.kt b/base/src/main/kotlin/de/staropensource/engine/base/implementation/logging/crashcategory/EngineCrashCategory.kt new file mode 100644 index 000000000..751a9d267 --- /dev/null +++ b/base/src/main/kotlin/de/staropensource/engine/base/implementation/logging/crashcategory/EngineCrashCategory.kt @@ -0,0 +1,55 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU Affero General Public License v3 + * with an exception allowing classpath linking. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.base.implementation.logging.crashcategory + +import de.staropensource.engine.base.implementable.logging.CrashCategory +import de.staropensource.engine.base.utility.dnihbd.BuildInformation + +/** + * [CrashCategory] implementation + * providing information about the engine. + * + * @since v1-alpha10 + */ +class EngineCrashCategory private constructor() : BuildInformation.BuildInformationCrashCategory() { + /** + * Companion object of [EngineCrashCategory]. + * + * @since v1-alpha10 + */ + companion object { + /** + * Global instance of [EngineCrashCategory]. + * + * @since v1-alpha10 + */ + @JvmStatic + val instance: EngineCrashCategory = EngineCrashCategory() + } + + override fun check(): Boolean { + return true + } + + override fun getName(): String { + return "sos!engine" + } +} diff --git a/base/src/main/kotlin/de/staropensource/engine/base/utility/dnihbd/BuildInformation.kt b/base/src/main/kotlin/de/staropensource/engine/base/utility/dnihbd/BuildInformation.kt new file mode 100644 index 000000000..23da71daa --- /dev/null +++ b/base/src/main/kotlin/de/staropensource/engine/base/utility/dnihbd/BuildInformation.kt @@ -0,0 +1,528 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU Affero General Public License v3 + * with an exception allowing classpath linking. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.base.utility.dnihbd + +import de.staropensource.engine.base.Engine.Companion.info +import de.staropensource.engine.base.implementable.logging.CrashCategory +import de.staropensource.engine.base.type.Origin +import de.staropensource.engine.base.type.logging.Call +import de.staropensource.engine.base.type.logging.ChannelSettings +import de.staropensource.engine.base.type.versioning.VersionType +import de.staropensource.engine.base.utility.FileAccess +import de.staropensource.engine.base.utility.misc.StackTraceUtils +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import java.io.InputStreamReader +import java.io.Reader +import java.io.StringReader +import java.util.* + +/** + * Loads the specified `-git.properties` and + * `-gradle.properties` files into memory, + * parses them and makes them available + * using properties. + * + * See the sos!engine documentation on how + * to use this class or you will get the + * `help what does this do i don't + * understand :neofox_woozy:` disease. + * + * @param loadPrefix prefix used for accessing the files + * @param loadLocation location used for accessing the files. Set to `null` to use the JAR's bundled resources. + * @since v1-alpha10 + */ +// This method is not sorted like the usual order +// (companion objects, constants, properties, +// constructors and then methods) but instead like +// this: properties & getter methods, constructors, +// methods. This change was made for a better, +// less chaotic class source code layout. +@Suppress("unused") +open class BuildInformation + @Throws(RuntimeException::class) + constructor(val loadPrefix: String, val loadLocation: FileAccess? = null) { + // -----> Versioning + /** + * Returns the current version string. + * + * @param semver if to return the SemVer-compatible format instead + * @return current version string + * @since v1-alpha10 + */ + fun versionString(semver: Boolean = false): String? = + try { + if (semver) + "${versionRelease}.${versionType.toUByte()}.${versionTyperelease}${if (versionFork.isEmpty()) "" else "-${versionFork}"}${if (versionCompanion.isEmpty()) "" else "+${versionCompanion}"}" + else + "v${versionRelease}-${versionType}${versionTyperelease}${if (versionFork.isEmpty()) "" else "-${versionFork}"}${if (versionCompanion.isEmpty()) "" else "+${versionCompanion}"}" + } catch (_: NullPointerException) { + null + } + + /** + * The version codename of this build. + * + * @since v1-alpha10 + */ + var versionCodename: String + + /** + * The release number of this build. + * + * @since v1-alpha10 + */ + var versionRelease: UInt + + /** + * The version type of this build. + * + * @since v1-alpha10 + */ + var versionType: VersionType.V2 + + /** + * The version typerelease of this build. + * + * @since v1-alpha10 + */ + var versionTyperelease: UInt + + /** + * The fork version of this build. + * + * @since v1-alpha10 + */ + var versionFork: String + + /** + * The companion version of this build. + * + * @since v1-alpha10 + */ + var versionCompanion: String + + + // -----> Languages + /** + * All languages and their versions + * this build was compiled against. + * + * @since v1-alpha10 + */ + var languages: Map + + + // -----> Dependencies + /** + * All dependencies and their versions + * this build was compiled against. + * + * @since v1-alpha10 + */ + var dependencies: Map + + /** + * All test dependencies and their versions + * this build was tested against. + * + * @since v1-alpha10 + */ + var testDependencies: Map + + + // -----> Git + /** + * Whether the `-git.properties` is + * missing for this build and the + * values provided by this instance + * have been faked to avoid issues. + * + * @since v1-alpha10 + */ + var gitFaked: Boolean + + /** + * Whether uncommitted changes have + * been compiled into this build. + * + * @since v1-alpha10 + */ + var gitDirty: Boolean + + /** + * The branch name from which this + * build was compiled from. + * + * @since v1-alpha10 + */ + var gitBranch: String + + /** + * The hostname of the build computer + * which compiled this build. + * + * @since v1-alpha10 + */ + var gitBuildHostname: String + + /** + * The username of the build user + * which compiled this build. + * + * @since v1-alpha10 + */ + var gitBuildUsername: String + + /** + * The email address of the build user + * which compiled this build. + * + * @since v1-alpha10 + */ + var gitBuildEmail: String + + /** + * The total commit count at + * the time of compilation. + * + * @since v1-alpha10 + */ + var gitCommits: UInt + + // -----> Git (commit information) + /** + * The long commit identifier + * of this build. + * + * @since v1-alpha10 + */ + var gitCommitIdentifierLong: String + + /** + * The short commit identifier + * of this build. + * + * @since v1-alpha10 + */ + var gitCommitIdentifierShort: String + + /** + * The short commit message + * of this build. + * + * @since v1-alpha10 + */ + var gitCommitMessageShort: String + + /** + * The full commit message + * * of this build. + * + * @since v1-alpha10 + */ + var gitCommitMessageFull: String + + /** + * The time of the commit + * of this build. + * + * @since v1-alpha10 + */ + var gitCommitTime: Instant + + /** + * The username of the commit + * author of this build. + * + * @since v1-alpha10 + */ + var gitCommitAuthorUsername: String + + /** + * The email address of the + * commit author of this build. + * + * @since v1-alpha10 + */ + var gitCommitAuthorEmail: String + + + // -----> Constructors + init { + try { + var gitProperties: Properties? = Properties() + val gradleProperties: Properties = Properties() + val gradlePropertiesKeys: Array + + // Load properties + try { + StringReader(getFileContent("git")!!).use { gitProperties?.load(it) } + } catch (exception: NullPointerException) { + gitProperties = null + } + StringReader(getFileContent("gradle")!!).use { gradleProperties.load(it) } + + @Suppress("UNCHECKED_CAST") + gradlePropertiesKeys = (gradleProperties.keys as Set).toTypedArray() + + // Set properties + // -> Versioning + versionCodename = gradleProperties.getProperty("versionCodename")!! + versionRelease = gradleProperties.getProperty("versionRelease")!!.toUInt() + versionType = VersionType.V2.of(gradleProperties.getProperty("versionType")!!)!! + versionTyperelease = gradleProperties.getProperty("versionTyperelease")!!.toUInt() + versionFork = gradleProperties.getProperty("versionFork")!! + versionCompanion = gradleProperties.getProperty("versionCompanion")!! + + // -> Languages + val languagesMutable: MutableMap = mutableMapOf() + + for (language: String in gradlePropertiesKeys.filter { key -> key.startsWith("language") }) + languagesMutable.put(language.removePrefix("language").replaceFirstChar { it.uppercaseChar() }, gradleProperties.getProperty(language)) + + languages = languagesMutable.toMap() + + // -> Dependencies + dependencies = resolveDependencies(gradleProperties, "dependency") + testDependencies = resolveDependencies(gradleProperties, "testDependency") + + gitFaked = gitProperties == null + gitDirty = gitProperties?.getProperty("git.dirty").toBoolean() + gitBranch = gitProperties?.getProperty("git.branch") ?: "unknown" + gitBuildHostname = gitProperties?.getProperty("git.build.host") ?: "unknown" + gitBuildUsername = gitProperties?.getProperty("git.build.user.name") ?: "Unknown" + gitBuildEmail = gitProperties?.getProperty("git.build.user.email") ?: "unknown@example.org" + gitCommits = gitProperties?.getProperty("git.total.commit.count")?.toUInt() ?: 0u + gitCommitIdentifierLong = gitProperties?.getProperty("git.commit.id") ?: "0000000000000000000000000000000000000000" + gitCommitIdentifierShort = gitProperties?.getProperty("git.commit.id.abbrev") ?: "0000000" + gitCommitMessageFull = gitProperties?.getProperty("git.commit.message.full") ?: "-> The -git.properties file could not be loaded. Did you download a tarball?" + gitCommitMessageShort = gitProperties?.getProperty("git.commit.message.short") ?: "-> The -git.properties file could not be loaded. Did you download a tarball?" + gitCommitTime = Instant.parse(gitProperties?.getProperty("git.commit.time") ?: "") + gitCommitAuthorUsername = gitProperties?.getProperty("git.commit.user.name") ?: "unknown@example.org" + gitCommitAuthorEmail = gitProperties?.getProperty("git.commit.user.email") ?: "Unknown" + } catch (throwable: Throwable) { + throw RuntimeException("Unable to load build information", throwable) + } + } + + + // -----> Utility methods + /** + * Returns the specified file content. + * + * @param file file to access + * @return file content or `null` if it doesn't exist + * @throws Throwable on error + * @since v1-alpha10 + */ + @Throws(Throwable::class) + protected fun getFileContent(file: String): String? { + if (loadLocation == null) { + // Read from resources + val origin: Origin = StackTraceUtils.getMethodCaller(2u) + val reader: Reader + try { + reader = InputStreamReader( + Class + .forName("${origin.packageName}.${origin.className}") + .classLoader + .getResourceAsStream("${loadPrefix}-${file}.properties")!! + ) + } catch (exception: NullPointerException) { + return null + } + val output = reader.readText() + reader.close() + + return output + } else + // Read from filesystem + return FileAccess("${loadLocation}/${loadPrefix}-${file}.properties") + .readString() + } + + /** + * Resolves and returns a map of + * dependencies and their versions. + * + * @param properties properties containing all dependencies + * @param prefix prefix used for accessing the dependencies + * @return map of dependencies + * @since v1-alpha10 + */ + private fun resolveDependencies(properties: Properties, prefix: String): Map { + val map: MutableMap = mutableMapOf() + var temp: String + var name: String + var identifier: String + var version: String + + // Loop through all properties + @Suppress("UNCHECKED_CAST") + for (property: String in properties.keys as Set) { + // Only process properties which start with + // the specified prefix and end in "Identifier" + if (property.startsWith(prefix) && property.endsWith("Identifier")) { + temp = property.removeSuffix("Identifier") + name = temp.removePrefix(prefix).replace("_", " ") + identifier = properties.getProperty(property) + + // Determine version + @Suppress("LoopWithTooManyJumpStatements") + while (true) { + // Stop if 'temp' is blank + if (temp.isBlank()) { + version = "?" + break + } else if (properties.getProperty("${temp}Version") != null) { + version = properties.getProperty("${temp}Version") + break + } else + try { + // Search for last capitalized letter + // and only keep everything before it + temp = temp.substring(0, temp.findLastAnyOf( + setOf( + "A", "B", "C", "D", "E", "F", "G", + "H", "I", "J", "K", "L", "M", "N", + "O", "P", "Q", "R", "S", "T", "U", + "V", "W", "X", "Y", "Z" + ) + )!!.first - 1) + } catch (exception: NullPointerException) { + // Cancel if substring last capitalized + // character search fails + temp = "" + } + } + + // Add to output map + map.put(name, "${identifier} v${version}") + } + } + + return map.toMap() + } + + + // -----> Inner classes + /** + * A [CrashCategory] implementation providing + * details about your running build. + * + * @since v1-alpha10 + */ + abstract class BuildInformationCrashCategory : CrashCategory { + override fun execute( + call: Call, + channelSettings: ChannelSettings?, + throwable: Throwable?, + fatal: Boolean, + ): LinkedHashMap { + // Check if Engine.info is 'null' + if (info == null) + return linkedMapOf( + Pair("Not available", null) + ) + + val map: LinkedHashMap = linkedMapOf() + + // Add metadata + // -> Versioning + map.put("Version", "${info!!.versionString(semver = false)} \"${info!!.versionCodename}\"") + + // -> Languages + if (!info!!.languages.isEmpty()) { + val languagesMap: LinkedHashMap = linkedMapOf() + + // Add all languages + for (language: String in info!!.languages.keys) + languagesMap.put(language, info!!.languages[language]!!) + + // Add to output map + map.put("Languages", languagesMap) + } + + // -> Dependencies + if (!info!!.dependencies.isEmpty()) { + val dependenciesMap: LinkedHashMap = linkedMapOf() + + // Add all dependencies + for (dependency: String in info!!.dependencies.keys) + dependenciesMap.put(dependency, info!!.dependencies[dependency]!!) + + // Add to output map + map.put("Dependencies", dependenciesMap) + } + + if (!info!!.testDependencies.isEmpty()) { + val testDependenciesMap: LinkedHashMap = linkedMapOf() + + // Add all test dependencies + for (testDependency: String in info!!.testDependencies.keys) + testDependenciesMap.put(testDependency, info!!.testDependencies[testDependency]!!) + + // Add to output map + map.put("Test dependencies", testDependenciesMap) + } + + // -> Git + if (info!!.gitFaked) + map.put("Git", "Unavailable") + else { + val gitMap: LinkedHashMap = linkedMapOf() + val gitCommitMap: LinkedHashMap = linkedMapOf() + val gitCommitAuthorMap: LinkedHashMap = linkedMapOf() + val gitBuilderMap: LinkedHashMap = linkedMapOf() + val gitCommitTime: LocalDateTime = info!!.gitCommitTime.toLocalDateTime(TimeZone.UTC) + + // Add to 'gitCommitAuthorMap' + gitCommitAuthorMap.put("Username", info!!.gitCommitAuthorUsername) + gitCommitAuthorMap.put("Email address", info!!.gitCommitAuthorEmail) + + // Add to 'gitCommitMap' + gitCommitMap.put("Identifier", "'${info!!.gitCommitIdentifierLong}'") + gitCommitMap.put("Message (short)", "'${info!!.gitCommitMessageShort}'") + gitCommitMap.put("Time", "${gitCommitTime.dayOfMonth}.${gitCommitTime.monthNumber}.${gitCommitTime.year} ${gitCommitTime.hour}:${gitCommitTime.minute}:${gitCommitTime.second} UTC") + gitCommitMap.put("Author", gitCommitAuthorMap) + + // Add to 'gitCommitMap' + gitBuilderMap.put("Hostname", info!!.gitBuildHostname) + gitBuilderMap.put("Username", info!!.gitBuildUsername) + gitBuilderMap.put("Email address", info!!.gitBuildEmail) + + // Add to 'gitMap' + gitMap.put("Dirty", if (info!!.gitDirty) "yes" else "no") + gitMap.put("Branch", info!!.gitBranch) + gitMap.put("Commit", gitCommitMap) + gitMap.put("Builder", gitBuilderMap) + + // Add to output map + map.put("Git", gitMap) + } + + return map + } + } +} diff --git a/base/src/main/kotlin/de/staropensource/engine/base/utility/dnihbd/package-info.kt b/base/src/main/kotlin/de/staropensource/engine/base/utility/dnihbd/package-info.kt new file mode 100644 index 000000000..679f0636d --- /dev/null +++ b/base/src/main/kotlin/de/staropensource/engine/base/utility/dnihbd/package-info.kt @@ -0,0 +1,31 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU Affero General Public License v3 + * with an exception allowing classpath linking. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * DNI, HBD. Short for Do Not + * Interact, Here Be Dragons. + * + * Only use the classes contained + * in this package if you really + * know what you are doing. + * + * @since v1-alpha10 + */ +package de.staropensource.engine.base.utility.dnihbd