Add BuildInformation & a matching CrashCategory

This commit is contained in:
JeremyStar™ 2024-12-19 02:50:00 +01:00
parent 8b15e8a84e
commit 47bb0f6cab
Signed by: JeremyStarTM
GPG key ID: E366BAEF67E4704D
5 changed files with 630 additions and 1 deletions

View file

@ -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
}
}

View file

@ -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<CrashCategory> = linkedSetOf(
InfoCrashCategory.instance
InfoCrashCategory.instance,
EngineCrashCategory.instance
)
/**

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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"
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, String>
// -----> Dependencies
/**
* All dependencies and their versions
* this build was compiled against.
*
* @since v1-alpha10
*/
var dependencies: Map<String, String>
/**
* All test dependencies and their versions
* this build was tested against.
*
* @since v1-alpha10
*/
var testDependencies: Map<String, String>
// -----> 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<String>
// 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<String>).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<String, String> = 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<String, String> {
val map: MutableMap<String, String> = 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<String>) {
// 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<String, Any?> {
// Check if Engine.info is 'null'
if (info == null)
return linkedMapOf(
Pair("Not available", null)
)
val map: LinkedHashMap<String, Any?> = linkedMapOf()
// Add metadata
// -> Versioning
map.put("Version", "${info!!.versionString(semver = false)} \"${info!!.versionCodename}\"")
// -> Languages
if (!info!!.languages.isEmpty()) {
val languagesMap: LinkedHashMap<String, String> = 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<String, String> = 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<String, String> = 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<String, Any> = linkedMapOf()
val gitCommitMap: LinkedHashMap<String, Any> = linkedMapOf()
val gitCommitAuthorMap: LinkedHashMap<String, String> = linkedMapOf()
val gitBuilderMap: LinkedHashMap<String, String> = 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
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
/**
* 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