From db2b94902d4afbb270a39f319dc6f72bfd775679 Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Sun, 15 Jun 2025 11:04:31 +0200 Subject: [PATCH 01/19] Start rewriting to Gradle --- .gitignore | 7 +- build.gradle.kts | 135 ++++++++++ buildSrc/build.gradle.kts | 32 +++ .../kotlin/BotCommands-conventions.gradle.kts | 47 ++++ .../src/main/kotlin/GenerateBCInfoTask.kt | 59 ++++ buildSrc/src/main/kotlin/GitUtils.kt | 58 ++++ buildSrc/src/main/kotlin/Version.kt | 39 +++ gradle.properties | 7 + gradle/libs.versions.toml | 75 ++++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 251 ++++++++++++++++++ gradlew.bat | 94 +++++++ settings.gradle.kts | 3 + .../freya022/botcommands/api/$BCInfo.java | 28 +- .../core/service/ClassAnnotationsMap.kt | 2 +- 16 files changed, 820 insertions(+), 24 deletions(-) create mode 100644 build.gradle.kts create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/kotlin/BotCommands-conventions.gradle.kts create mode 100644 buildSrc/src/main/kotlin/GenerateBCInfoTask.kt create mode 100644 buildSrc/src/main/kotlin/GitUtils.kt create mode 100644 buildSrc/src/main/kotlin/Version.kt create mode 100644 gradle.properties create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts diff --git a/.gitignore b/.gitignore index 74f4b87dd5..7bbac96957 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,9 @@ *.iml .idea target -release.properties -pom.xml.releaseBackup +build +.gradle +.kotlin -schemas/Test.json -/schemas dev-config dev-data \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000000..31020cd817 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,135 @@ +plugins { + id("BotCommands-conventions") + `java-library` + `maven-publish` +} + +dependencies { + // -------------------- CORE DEPENDENCIES -------------------- + + // Kotlin + api(libs.kotlin.reflect) + api(libs.kotlinx.coroutines.core) + compileOnly(libs.kotlinx.coroutines.debug) // Optional + + // Logging + api(libs.slf4j.api) + implementation(libs.kotlin.logging.jvm) + + // JDA + api(libs.jda) + api(libs.jda.ktx) + + // Classpath scanning + api(libs.classgraph) + + // -------------------- GLOBAL DEPENDENCIES -------------------- + + api(libs.kotlinx.datetime.jvm) + + // Deserialization + api(libs.jackson.databind) + api(libs.jackson.module.kotlin) + + // Efficient data structures + api(libs.trove4j.core) + + // Rate limiting + api(libs.bucket4j.jdk17.core) + + // -------------------- DATABASE DEPENDENCIES -------------------- + + // SQL connection pooling + compileOnly(libs.hikaricp) // Optional + + // -------------------- EMOJI DEPENDENCIES -------------------- + + // All Unicode emojis + api(libs.jemoji) + // JDA-specific emojis + api(libs.jda.emojis) + + // -------------------- AUTOCOMPLETE DEPENDENCIES -------------------- + + // Fuzzy matching + api(libs.java.string.similarity) + + // Caching + implementation(libs.caffeine) + + // -------------------- SPRING DEPENDENCIES -------------------- + + // Spring Boot + compileOnly(libs.spring.boot.starter) // Optional + + // -------------------- ANNOTATION DEPENDENCIES -------------------- + + api(libs.jsr305) + compileOnly(libs.jetbrains.annotations) + + // -------------------- TEST DEPENDENCIES -------------------- + + // Mocking + testImplementation(libs.mockk.jvm) + + // Logging + testImplementation(libs.logback.classic) + + // Coroutines + testImplementation(libs.stacktrace.decoroutinator.jvm) + + // Database + testImplementation(libs.postgresql) + testImplementation(libs.h2) + testImplementation(libs.flyway.core) + testImplementation(libs.flyway.database.postgresql) + testImplementation(libs.hikaricp) + + // Persistent rate limiting + testImplementation(libs.bucket4j.jdk17.postgresql) + + // YAML (de)serialization + testImplementation(libs.jackson.dataformat.yaml) + + // Upgrade because kotlinx-coroutines-debug somehow has an ANCIENT version + testImplementation(libs.byte.buddy) + testImplementation(libs.byte.buddy.agent) + + // Test stuff + testImplementation(libs.kotlin.metadata.jvm) + + // Spring Boot + testImplementation(libs.spring.boot.starter) + testImplementation(libs.spring.boot.devtools) +} + +val generateInfo by tasks.registering(GenerateBCInfoTask::class) { + doNotTrackState("Can't know when Git hash/branch changes") + outputs.upToDateWhen { false } +} + +sourceSets { + main { + java { + srcDir(generateInfo) + exclude("**/\$BCInfo.java") + } + } +} + +kotlin { + compilerOptions { + freeCompilerArgs.addAll( + "-Xjvm-default=all", + "-Xcontext-receivers", + "-Xsuppress-warning=CONTEXT_RECEIVERS_DEPRECATED", + "-Xconsistent-data-class-copy-visibility", + ) + } +} + +publishing { + publications.create("maven") { + from(components["java"]) + } +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000000..843a6745a2 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,32 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() + gradlePluginPortal() +} + +dependencies { + // Change in version catalog too + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.0") +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +// Kotlin configuration for the precompiled classes +kotlin { + compilerOptions { + // Version of the buildscript bytecode, so Gradle shuts up + jvmTarget = JvmTarget.JVM_17 + + freeCompilerArgs.addAll( + "-Xjsr305=strict", + ) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/BotCommands-conventions.gradle.kts b/buildSrc/src/main/kotlin/BotCommands-conventions.gradle.kts new file mode 100644 index 0000000000..9717c691fa --- /dev/null +++ b/buildSrc/src/main/kotlin/BotCommands-conventions.gradle.kts @@ -0,0 +1,47 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + kotlin("jvm") +} + +group = "io.github.freya022" +version = "3.0.0-beta.2_DEV" + +version = Version( + major = "3", + minor = "0", + revision = "0", + classifier = "beta.2", + isDev = true +) + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(24) + } + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + + withSourcesJar() +} + +repositories { + mavenCentral() + mavenLocal() +} + +dependencies { + testImplementation(kotlin("test")) + testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.0") +} + +kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_17 + + freeCompilerArgs.addAll( + "-Xjsr305=strict", + ) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/GenerateBCInfoTask.kt b/buildSrc/src/main/kotlin/GenerateBCInfoTask.kt new file mode 100644 index 0000000000..d1eec806c5 --- /dev/null +++ b/buildSrc/src/main/kotlin/GenerateBCInfoTask.kt @@ -0,0 +1,59 @@ +import org.gradle.api.DefaultTask +import org.gradle.api.file.Directory +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import org.gradle.work.DisableCachingByDefault +import java.time.Instant +import javax.inject.Inject + +@DisableCachingByDefault +abstract class GenerateBCInfoTask : DefaultTask() { + + @get:InputFile + val inputFile = project.layout.projectDirectory.file("src/main/java/io/github/freya022/botcommands/api/\$BCInfo.java") + + @get:OutputDirectory + val outputDir = project.layout.buildDirectory.dir("generated/sources/BotCommands/main/java") + + @get:Input + val projectDir = project.projectDir.absolutePath + + @get:Inject + abstract val providers: ProviderFactory + + @get:Input + val version: Version = project.version as Version + + @get:Input + val jdaVersion = project.configurations.getByName("compileClasspath") + .incoming.dependencies.find { it.name.endsWith("JDA") }!!.version!! + + @TaskAction + fun run() { + val attributes = mapOf( + "version-major" to version.major, + "version-minor" to version.minor, + "version-revision" to version.revision, + "version-classifier" to (version.classifier ?: "null"), + "branch-name" to (GitUtils.getCommitBranch(logger, providers, projectDir) ?: "null"), + "commit-hash" to (GitUtils.getCommitHash(logger, providers, projectDir) ?: "null"), + "build-jda-version" to jdaVersion, + "build-time" to Instant.now().toEpochMilli().toString(), + ) + + val initialContent = inputFile.asFile.readText() + val filteredContent = initialContent.replaceTokens(attributes).replace("\$BCInfo", "BCInfo") + + val bcInfoFile = outputDir.get().file("io/github/freya022/botcommands/api/BCInfo.java").asFile + bcInfoFile.parentFile.mkdirs() + bcInfoFile.writeText(filteredContent) + } + + private fun String.replaceTokens(map: Map): String { + return map.entries.fold(this) { current, (key, value) -> current.replace("@$key@", value) } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/GitUtils.kt b/buildSrc/src/main/kotlin/GitUtils.kt new file mode 100644 index 0000000000..b77443f11c --- /dev/null +++ b/buildSrc/src/main/kotlin/GitUtils.kt @@ -0,0 +1,58 @@ +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.file.Directory +import org.gradle.api.logging.Logger +import org.gradle.api.provider.ProviderFactory +import java.io.IOException + +object GitUtils { + + fun isCI(providers: ProviderFactory): Boolean { + return (providers.systemProperty("BUILD_NUMBER").isPresent // Jenkins + || providers.environmentVariable("BUILD_NUMBER").isPresent + || providers.systemProperty("GIT_COMMIT").isPresent // Jitpack + || providers.environmentVariable("GIT_COMMIT").isPresent + || providers.systemProperty("GITHUB_ACTIONS").isPresent // GitHub Actions + || providers.environmentVariable("GITHUB_ACTIONS").isPresent) + } + + fun getCommitBranch(logger: Logger, providers: ProviderFactory, directory: String): String? { + try { + //Jitpack builds are detached from a branch, this will return the HEAD hash, + // which can still be used on GitHub to get the state of the repository at that point + val jitpackBranch = providers.environmentVariable("GIT_BRANCH").getOrNull() + if (jitpackBranch != null) return jitpackBranch + + val output = providers.exec { + commandLine("git", "rev-parse", "--abbrev-ref", "HEAD") + workingDir(directory) + } + + output.result.get().assertNormalExitValue() + + return output.standardOutput.asText.get().lineSequence().first() + } catch (e: Exception) { + logger.error("Unable to get commit branch", e) + return null + } + } + + fun getCommitHash(logger: Logger, providers: ProviderFactory, directory: String): String? { + try { + val jitpackCommit = providers.environmentVariable("GIT_COMMIT").getOrNull() + if (jitpackCommit != null) return jitpackCommit + + val output = providers.exec { + commandLine("git", "rev-parse", "--verify", "HEAD") + workingDir(directory) + } + + output.result.get().assertNormalExitValue() + + return output.standardOutput.asText.get().lineSequence().first() + } catch (e: Exception) { + logger.error("Unable to get commit hash", e) + return null + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Version.kt b/buildSrc/src/main/kotlin/Version.kt new file mode 100644 index 0000000000..0d8aaebd31 --- /dev/null +++ b/buildSrc/src/main/kotlin/Version.kt @@ -0,0 +1,39 @@ +import java.io.ObjectInputStream +import java.io.ObjectOutputStream +import java.io.Serializable + +data class Version( + val major: String, + val minor: String, + val revision: String, + val classifier: String?, + val isDev: Boolean, +) : Serializable { + + override fun toString(): String { + return "$major.$minor.$revision-$classifier" + let { if (isDev) "_DEV" else "" } + } + + private fun writeObject(out: ObjectOutputStream) { + out.defaultWriteObject() + } + + private fun readObject(out: ObjectInputStream) { + out.defaultReadObject() + } + + companion object { + private val versionPattern = Regex("""(\d+)\.(\d+)\.(\d+)(?:-(\w+\.\d+))?(?:_DEV)?""") + + fun parseOrNull(version: String): Version? { + val groups = versionPattern.matchEntire(version)?.groups ?: return null + + val major = groups[1]?.value ?: return null + val minor = groups[2]?.value ?: return null + val revision = groups[3]?.value ?: return null + val classifier = groups[4]?.value + + return Version(major, minor, revision, classifier, version.endsWith("_DEV")) + } + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000000..10bfa7072f --- /dev/null +++ b/gradle.properties @@ -0,0 +1,7 @@ +# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties + +org.gradle.configuration-cache=true +org.gradle.parallel=true +org.gradle.caching=true +kotlin.daemon.jvmargs=-Xmx2G + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000000..91663ee21d --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,75 @@ +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format + +[versions] +logback-classic = "1.5.16" +jda-ktx = "0.12.0" +bucket4j-jdk17-core = "8.14.0" +bucket4j-jdk17-postgresql = "8.14.0" +jackson = "2.18.3" +caffeine = "3.2.0" +jsr305 = "3.0.2" +h2 = "2.3.232" +hikaricp = "6.2.1" +jda-emojis = "3.0.0" +stacktrace-decoroutinator-jvm = "2.4.8" +java-string-similarity = "2.0.0" +classgraph = "4.8.179" +kotlin-logging-jvm = "7.0.3" +mockk-jvm = "1.13.16" +byte-buddy = "1.16.1" +byte-buddy-agent = "1.16.1" +jda = "5.5.0" +jemoji = "1.6.0" +trove4j-core = "3.1.0" +flyway-core = "11.2.0" +flyway-database-postgresql = "11.2.0" +jetbrains-annotations = "26.0.2" +kotlin-metadata-jvm = "2.1.0" +kotlin-reflect = "2.1.0" +kotlin-stdlib = "2.1.0" +kotlinx-coroutines-core = "1.10.1" +kotlinx-coroutines-debug = "1.10.1" +kotlinx-datetime-jvm = "0.6.1" +junit-jupiter = "5.11.4" +postgresql = "42.7.5" +slf4j-api = "2.0.16" +spring-boot-devtools = "3.4.2" +spring-boot-starter = "3.4.2" + +[libraries] +logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-classic" } +jda-ktx = { module = "club.minnced:jda-ktx", version.ref = "jda-ktx" } +bucket4j-jdk17-core = { module = "com.bucket4j:bucket4j_jdk17-core", version.ref = "bucket4j-jdk17-core" } +bucket4j-jdk17-postgresql = { module = "com.bucket4j:bucket4j_jdk17-postgresql", version.ref = "bucket4j-jdk17-postgresql" } +jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } +jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } +jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } +caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine" } +jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" } +h2 = { module = "com.h2database:h2", version.ref = "h2" } +hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" } +jda-emojis = { module = "dev.freya02:jda-emojis", version.ref = "jda-emojis" } +stacktrace-decoroutinator-jvm = { module = "dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-jvm", version.ref = "stacktrace-decoroutinator-jvm" } +java-string-similarity = { module = "info.debatty:java-string-similarity", version.ref = "java-string-similarity" } +classgraph = { module = "io.github.classgraph:classgraph", version.ref = "classgraph" } +kotlin-logging-jvm = { module = "io.github.oshai:kotlin-logging-jvm", version.ref = "kotlin-logging-jvm" } +mockk-jvm = { module = "io.mockk:mockk-jvm", version.ref = "mockk-jvm" } +byte-buddy = { module = "net.bytebuddy:byte-buddy", version.ref = "byte-buddy" } +byte-buddy-agent = { module = "net.bytebuddy:byte-buddy-agent", version.ref = "byte-buddy-agent" } +jda = { module = "net.dv8tion:JDA", version.ref = "jda" } +jemoji = { module = "net.fellbaum:jemoji", version.ref = "jemoji" } +trove4j-core = { module = "net.sf.trove4j:core", version.ref = "trove4j-core" } +flyway-core = { module = "org.flywaydb:flyway-core", version.ref = "flyway-core" } +flyway-database-postgresql = { module = "org.flywaydb:flyway-database-postgresql", version.ref = "flyway-database-postgresql" } +jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" } +kotlin-metadata-jvm = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin-metadata-jvm" } +kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" } +kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin-stdlib" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" } +kotlinx-coroutines-debug = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-debug", version.ref = "kotlinx-coroutines-debug" } +kotlinx-datetime-jvm = { module = "org.jetbrains.kotlinx:kotlinx-datetime-jvm", version.ref = "kotlinx-datetime-jvm" } +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } +postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" } +slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-api" } +spring-boot-devtools = { module = "org.springframework.boot:spring-boot-devtools", version.ref = "spring-boot-devtools" } +spring-boot-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "spring-boot-starter" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..1b33c55baabb587c669f562ae36f953de2481846 GIT binary patch literal 43764 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# 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 + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +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=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# 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" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +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 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" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..db3a6ac207 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@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 +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +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 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +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 + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +: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 + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000000..bb1792cf2b --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,3 @@ +rootProject.name = "BotCommands" + +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") diff --git a/src/main/java/io/github/freya022/botcommands/api/$BCInfo.java b/src/main/java/io/github/freya022/botcommands/api/$BCInfo.java index b2e19cf2ec..a67dd8c990 100644 --- a/src/main/java/io/github/freya022/botcommands/api/$BCInfo.java +++ b/src/main/java/io/github/freya022/botcommands/api/$BCInfo.java @@ -3,30 +3,20 @@ import java.time.Instant; public class $BCInfo { - public static final Instant BUILD_TIME; - public static final String VERSION_MAJOR = "%%version-major%%"; - public static final String VERSION_MINOR = "%%version-minor%%"; - public static final String VERSION_REVISION = "%%version-revision%%"; - public static final String VERSION_CLASSIFIER = "%%version-classifier%%"; + public static final Instant BUILD_TIME = Instant.ofEpochMilli(Long.parseLong("@build-time@")); + public static final String VERSION_MAJOR = "@version-major@"; + public static final String VERSION_MINOR = "@version-minor@"; + public static final String VERSION_REVISION = "@version-revision@"; + public static final String VERSION_CLASSIFIER = "@version-classifier@"; public static final String GITHUB = "https://github.com/freya022/BotCommands"; /** May be "null", may also be a full commit hash in Jitpack builds */ - public static final String BRANCH_NAME = "%%branch-name%%"; + public static final String BRANCH_NAME = "@branch-name@"; /** May be "null" */ - public static final String COMMIT_HASH = "%%commit-hash%%"; - public static final String BUILD_JDA_VERSION = "%%build-jda-version%%"; + public static final String COMMIT_HASH = "@commit-hash@"; + public static final String BUILD_JDA_VERSION = "@build-jda-version@"; @SuppressWarnings("ConstantConditions") public static final String VERSION = "%s.%s.%s%s%s".formatted(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, - VERSION_CLASSIFIER == null ? "" : "-" + VERSION_CLASSIFIER, + VERSION_CLASSIFIER.equals("null") ? "" : "-" + VERSION_CLASSIFIER, COMMIT_HASH.equals("null") ? "" : "_" + COMMIT_HASH); - - static { - Instant tmpBuildTime; - try { - tmpBuildTime = Instant.ofEpochMilli(Long.parseLong("%%build-time%%")); - } catch (NumberFormatException e) { //Can happen on IJ builds, ig - tmpBuildTime = Instant.now(); - } - BUILD_TIME = tmpBuildTime; - } } diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/ClassAnnotationsMap.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/ClassAnnotationsMap.kt index cc2c516324..514030bea5 100644 --- a/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/ClassAnnotationsMap.kt +++ b/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/ClassAnnotationsMap.kt @@ -52,6 +52,6 @@ internal class SpringClassAnnotationsMap( val beansWithAnnotation = context.getBeansWithAnnotation(clazz.java) if (beansWithAnnotation.isEmpty()) return null - return beansWithAnnotation.keys.mapTo(hashSetOf()) { context.getType(it).kotlin } + return beansWithAnnotation.keys.mapTo(hashSetOf()) { context.getType(it)!!.kotlin } } } \ No newline at end of file From 62e60e016784a61ff61d6b80b357486088ec32f8 Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Sun, 15 Jun 2025 11:14:02 +0200 Subject: [PATCH 02/19] Rearrange libs.versions.toml --- gradle/libs.versions.toml | 120 +++++++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 48 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 91663ee21d..680c89e211 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,75 +1,99 @@ # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format [versions] +kotlin = "2.1.0" +kotlinx-coroutines = "1.10.1" + +stacktrace-decoroutinator-jvm = "2.4.8" + +slf4j-api = "2.0.16" logback-classic = "1.5.16" +kotlin-logging-jvm = "7.0.3" + +jda = "5.5.0" jda-ktx = "0.12.0" -bucket4j-jdk17-core = "8.14.0" -bucket4j-jdk17-postgresql = "8.14.0" + +classgraph = "4.8.179" + +kotlinx-datetime-jvm = "0.6.1" + jackson = "2.18.3" + +trove4j-core = "3.1.0" + +jda-emojis = "3.0.0" +jemoji = "1.6.0" + caffeine = "3.2.0" + +java-string-similarity = "2.0.0" + jsr305 = "3.0.2" +jetbrains-annotations = "26.0.2" + +spring-boot = "3.4.2" + h2 = "2.3.232" +postgresql = "42.7.5" # Tied to [[ComponentRepository#checkPostgresVersion]] hikaricp = "6.2.1" -jda-emojis = "3.0.0" -stacktrace-decoroutinator-jvm = "2.4.8" -java-string-similarity = "2.0.0" -classgraph = "4.8.179" -kotlin-logging-jvm = "7.0.3" +flyway = "11.2.0" + +bucket4j-jdk17-core = "8.14.0" +bucket4j-jdk17-postgresql = "8.14.0" + mockk-jvm = "1.13.16" + byte-buddy = "1.16.1" byte-buddy-agent = "1.16.1" -jda = "5.5.0" -jemoji = "1.6.0" -trove4j-core = "3.1.0" -flyway-core = "11.2.0" -flyway-database-postgresql = "11.2.0" -jetbrains-annotations = "26.0.2" -kotlin-metadata-jvm = "2.1.0" -kotlin-reflect = "2.1.0" -kotlin-stdlib = "2.1.0" -kotlinx-coroutines-core = "1.10.1" -kotlinx-coroutines-debug = "1.10.1" -kotlinx-datetime-jvm = "0.6.1" -junit-jupiter = "5.11.4" -postgresql = "42.7.5" -slf4j-api = "2.0.16" -spring-boot-devtools = "3.4.2" -spring-boot-starter = "3.4.2" [libraries] +kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } +kotlin-metadata-jvm = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-debug = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-debug", version.ref = "kotlinx-coroutines" } + +stacktrace-decoroutinator-jvm = { module = "dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-jvm", version.ref = "stacktrace-decoroutinator-jvm" } + +slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-api" } logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-classic" } +kotlin-logging-jvm = { module = "io.github.oshai:kotlin-logging-jvm", version.ref = "kotlin-logging-jvm" } + +jda = { module = "net.dv8tion:JDA", version.ref = "jda" } jda-ktx = { module = "club.minnced:jda-ktx", version.ref = "jda-ktx" } -bucket4j-jdk17-core = { module = "com.bucket4j:bucket4j_jdk17-core", version.ref = "bucket4j-jdk17-core" } -bucket4j-jdk17-postgresql = { module = "com.bucket4j:bucket4j_jdk17-postgresql", version.ref = "bucket4j-jdk17-postgresql" } + +classgraph = { module = "io.github.classgraph:classgraph", version.ref = "classgraph" } + +kotlinx-datetime-jvm = { module = "org.jetbrains.kotlinx:kotlinx-datetime-jvm", version.ref = "kotlinx-datetime-jvm" } + jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } + +trove4j-core = { module = "net.sf.trove4j:core", version.ref = "trove4j-core" } + +jda-emojis = { module = "dev.freya02:jda-emojis", version.ref = "jda-emojis" } +jemoji = { module = "net.fellbaum:jemoji", version.ref = "jemoji" } + caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine" } + +java-string-similarity = { module = "info.debatty:java-string-similarity", version.ref = "java-string-similarity" } + jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" } +jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" } + +spring-boot-devtools = { module = "org.springframework.boot:spring-boot-devtools", version.ref = "spring-boot" } +spring-boot-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "spring-boot" } + h2 = { module = "com.h2database:h2", version.ref = "h2" } +postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" } hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" } -jda-emojis = { module = "dev.freya02:jda-emojis", version.ref = "jda-emojis" } -stacktrace-decoroutinator-jvm = { module = "dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-jvm", version.ref = "stacktrace-decoroutinator-jvm" } -java-string-similarity = { module = "info.debatty:java-string-similarity", version.ref = "java-string-similarity" } -classgraph = { module = "io.github.classgraph:classgraph", version.ref = "classgraph" } -kotlin-logging-jvm = { module = "io.github.oshai:kotlin-logging-jvm", version.ref = "kotlin-logging-jvm" } +flyway-core = { module = "org.flywaydb:flyway-core", version.ref = "flyway" } +flyway-database-postgresql = { module = "org.flywaydb:flyway-database-postgresql", version.ref = "flyway" } + +bucket4j-jdk17-core = { module = "com.bucket4j:bucket4j_jdk17-core", version.ref = "bucket4j-jdk17-core" } +bucket4j-jdk17-postgresql = { module = "com.bucket4j:bucket4j_jdk17-postgresql", version.ref = "bucket4j-jdk17-postgresql" } + mockk-jvm = { module = "io.mockk:mockk-jvm", version.ref = "mockk-jvm" } + byte-buddy = { module = "net.bytebuddy:byte-buddy", version.ref = "byte-buddy" } byte-buddy-agent = { module = "net.bytebuddy:byte-buddy-agent", version.ref = "byte-buddy-agent" } -jda = { module = "net.dv8tion:JDA", version.ref = "jda" } -jemoji = { module = "net.fellbaum:jemoji", version.ref = "jemoji" } -trove4j-core = { module = "net.sf.trove4j:core", version.ref = "trove4j-core" } -flyway-core = { module = "org.flywaydb:flyway-core", version.ref = "flyway-core" } -flyway-database-postgresql = { module = "org.flywaydb:flyway-database-postgresql", version.ref = "flyway-database-postgresql" } -jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" } -kotlin-metadata-jvm = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin-metadata-jvm" } -kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" } -kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin-stdlib" } -kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" } -kotlinx-coroutines-debug = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-debug", version.ref = "kotlinx-coroutines-debug" } -kotlinx-datetime-jvm = { module = "org.jetbrains.kotlinx:kotlinx-datetime-jvm", version.ref = "kotlinx-datetime-jvm" } -junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } -postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" } -slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-api" } -spring-boot-devtools = { module = "org.springframework.boot:spring-boot-devtools", version.ref = "spring-boot-devtools" } -spring-boot-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "spring-boot-starter" } From b3c493e7d824a2d20113a67ba3e39645b3881787 Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Sun, 15 Jun 2025 16:06:01 +0200 Subject: [PATCH 03/19] Download more RAM for Gradle Because Gradle --- gradle.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle.properties b/gradle.properties index 10bfa7072f..c62c31a53d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,5 +3,6 @@ org.gradle.configuration-cache=true org.gradle.parallel=true org.gradle.caching=true +org.gradle.jvmargs=-Xmx2G kotlin.daemon.jvmargs=-Xmx2G From bf23b26b5a64a0d76445d14bd3731178ac11e1e0 Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Sun, 15 Jun 2025 16:07:26 +0200 Subject: [PATCH 04/19] Add Dokka + publishing logic --- build.gradle.kts | 15 +-- buildSrc/build.gradle.kts | 2 + ...BotCommands-publish-conventions.gradle.kts | 116 ++++++++++++++++++ buildSrc/src/main/kotlin/GitUtils.kt | 17 +++ gradle.properties | 3 +- 5 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts diff --git a/build.gradle.kts b/build.gradle.kts index 31020cd817..5d3b7bea65 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("BotCommands-conventions") + id("BotCommands-publish-conventions") `java-library` - `maven-publish` } dependencies { @@ -117,6 +117,13 @@ sourceSets { } } +dokka { + dokkaSourceSets.configureEach { + suppressedFiles.from("src/main/java/io/github/freya022/botcommands/api/\$BCInfo.java") + suppressGeneratedFiles = false + } +} + kotlin { compilerOptions { freeCompilerArgs.addAll( @@ -127,9 +134,3 @@ kotlin { ) } } - -publishing { - publications.create("maven") { - from(components["java"]) - } -} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 843a6745a2..acd4340906 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -12,6 +12,8 @@ repositories { dependencies { // Change in version catalog too implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.0") + implementation("com.vanniktech.maven.publish:com.vanniktech.maven.publish.gradle.plugin:0.32.0") + implementation("org.jetbrains.dokka:org.jetbrains.dokka.gradle.plugin:2.0.0") } java { diff --git a/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts b/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts new file mode 100644 index 0000000000..c4a93698e5 --- /dev/null +++ b/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts @@ -0,0 +1,116 @@ +import com.vanniktech.maven.publish.SonatypeHost + +plugins { + signing + id("com.vanniktech.maven.publish") + id("org.jetbrains.dokka") +} + +val mavenCentralUsername: String? by project +val mavenCentralPassword: String? by project +/** + * 1. Generate a key pair with `gpg --gen-key`, it will ask for a key name and an email address + * 2. Start editing the key with `gpg --edit-key `, you should see a `gpg>` prompt + * 3. (Optional) Modify the expiry of your primary key with `expire` + * 4. If a subkey (`ssb`) was generated automatically, you can delete it by selecting it with `key 1` and running `delkey` + * 5. Add a subkey with `addkey`, select `RSA (sign only)` + * 6. Save everything with `save`, it will exit the gpg prompt + * 7. Show the subkey IDs with `gpg -K --keyid-format short`, you should see two keys: + * - `sec ed25519/ [SC]`, with the line below being the public key + * - `ssb rsa/ [S]` + * 8. Set `mavenGpgKeyId` with the subkey id + * 9. Set `mavenGpgSecretKey` with the secret key using `gpg --export-secret-key --armor ` + */ +val mavenGpgKeyId: String? by project +val mavenGpgSecretKey: String? by project + +val canSign = mavenGpgKeyId != null && mavenGpgSecretKey != null +val canPublish = mavenCentralUsername != null && mavenCentralPassword != null && canSign + +version = (version as Version).copy(isDev = !canPublish) + +val effectiveTag = if (canPublish) { + GitUtils.getHeadTag(logger, providers, projectDir.absolutePath) ?: error("Attempted to publish on a non-release commit") +} else { + "3.X" +} + +dokka { + dokkaSourceSets.configureEach { + jdkVersion = 17 + + perPackageOption { + matchingRegex = ".*internal.*" + suppress = true + } + + sourceLink { + localDirectory = file("src") + remoteUrl("https://github.com/freya022/BotCommands/tree/${effectiveTag}/src") + remoteLineSuffix = "#L" + } + + externalDocumentationLinks.register("JDA") { + url("https://docs.jda.wiki") + packageListUrl("https://docs.jda.wiki/element-list") + } + + externalDocumentationLinks.register("JetBrainsAnnotations") { + url("https://javadoc.io/doc/org.jetbrains/annotations/26.0.2") + packageListUrl("https://javadoc.io/doc/org.jetbrains/annotations/26.0.2/package-list") + } + + externalDocumentationLinks.register("Spring") { + url("https://docs.spring.io/spring-framework/docs/current/javadoc-api") + packageListUrl("https://docs.spring.io/spring-framework/docs/current/javadoc-api/element-list") + } + + externalDocumentationLinks.register("Bucket4J") { + url("https://javadoc.io/doc/com.bucket4j/bucket4j_jdk17-core/8.14.0") + packageListUrl("https://javadoc.io/doc/com.bucket4j/bucket4j_jdk17-core/8.14.0/element-list") + } + } +} + +signing { + isRequired = canPublish + + useInMemoryPgpKeys(mavenGpgKeyId, mavenGpgSecretKey, "") +} + +mavenPublishing { + if (canPublish) { + publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, automaticRelease = false) // TODO set to automatic when done testing + + signAllPublications() + } + + pom { + description = "A Kotlin-first (and Java) framework that makes creating Discord bots a piece of cake, using the JDA library." + url = "https://github.com/freya022/BotCommands" + inceptionYear = "2020" + + licenses { + license { + name = "Mozilla Public License 2.0" + url = "https://opensource.org/licenses/MPL-2.0" + distribution = "repo" + } + } + + developers { + developer { + name = "freya022" + email = "41875020+freya022@users.noreply.github.com" + url = "https://github.com/freya022" + } + } + + scm { + connection = "scm:git:https://github.com/freya022/BotCommands.git" + developerConnection = connection + url = "https://github.com/freya022/BotCommands" + tag = effectiveTag + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/GitUtils.kt b/buildSrc/src/main/kotlin/GitUtils.kt index b77443f11c..744f8b6d5f 100644 --- a/buildSrc/src/main/kotlin/GitUtils.kt +++ b/buildSrc/src/main/kotlin/GitUtils.kt @@ -55,4 +55,21 @@ object GitUtils { return null } } + + fun getHeadTag(logger: Logger, providers: ProviderFactory, directory: String): String? { + try { + val output = providers.exec { + commandLine("git", "describe", "--tags", "--abbrev=0", "--exact-match") + workingDir(directory) + isIgnoreExitValue = true + } + + if (output.result.get().exitValue == 128) { return null } + + return output.standardOutput.asText.get().lineSequence().first() + } catch (e: Exception) { + logger.error("Unable to get head tag", e) + return null + } + } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index c62c31a53d..a2c29543e4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,4 +5,5 @@ org.gradle.parallel=true org.gradle.caching=true org.gradle.jvmargs=-Xmx2G kotlin.daemon.jvmargs=-Xmx2G - +org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled +org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true From bdf43640bac5245f496022a554d7eb2f909aa43b Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Sun, 15 Jun 2025 16:42:12 +0200 Subject: [PATCH 05/19] Move some publishing-related configuration from base convention plugin --- .../main/kotlin/BotCommands-conventions.gradle.kts | 11 ----------- .../BotCommands-publish-conventions.gradle.kts | 13 ++++++++++++- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/buildSrc/src/main/kotlin/BotCommands-conventions.gradle.kts b/buildSrc/src/main/kotlin/BotCommands-conventions.gradle.kts index 9717c691fa..2944da76df 100644 --- a/buildSrc/src/main/kotlin/BotCommands-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/BotCommands-conventions.gradle.kts @@ -5,15 +5,6 @@ plugins { } group = "io.github.freya022" -version = "3.0.0-beta.2_DEV" - -version = Version( - major = "3", - minor = "0", - revision = "0", - classifier = "beta.2", - isDev = true -) java { toolchain { @@ -22,8 +13,6 @@ java { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 - - withSourcesJar() } repositories { diff --git a/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts b/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts index c4a93698e5..d0d225f3d4 100644 --- a/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts @@ -1,6 +1,7 @@ import com.vanniktech.maven.publish.SonatypeHost plugins { + java signing id("com.vanniktech.maven.publish") id("org.jetbrains.dokka") @@ -27,7 +28,13 @@ val mavenGpgSecretKey: String? by project val canSign = mavenGpgKeyId != null && mavenGpgSecretKey != null val canPublish = mavenCentralUsername != null && mavenCentralPassword != null && canSign -version = (version as Version).copy(isDev = !canPublish) +version = Version( + major = "3", + minor = "0", + revision = "0", + classifier = "beta.2", + isDev = !canPublish +) val effectiveTag = if (canPublish) { GitUtils.getHeadTag(logger, providers, projectDir.absolutePath) ?: error("Attempted to publish on a non-release commit") @@ -35,6 +42,10 @@ val effectiveTag = if (canPublish) { "3.X" } +java { + withSourcesJar() +} + dokka { dokkaSourceSets.configureEach { jdkVersion = 17 From 6d37dee8e021f246eba3a404ff6a6c7438e90be6 Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:52:22 +0200 Subject: [PATCH 06/19] Add KSP processor for Spring properties --- build.gradle.kts | 9 + gradle/libs.versions.toml | 16 ++ settings.gradle.kts | 2 + spring-properties-processor/build.gradle.kts | 19 ++ .../properties/processor/SpringMetadata.kt | 50 ++++ .../processor/SpringPropertiesProcessor.kt | 243 ++++++++++++++++++ .../SpringPropertiesProcessorProvider.kt | 12 + .../processor/utils/AnnotationName.kt | 15 ++ .../properties/processor/utils/KSP.kt | 43 ++++ .../properties/processor/utils/Strings.kt | 6 + ...ols.ksp.processing.SymbolProcessorProvider | 1 + 11 files changed, 416 insertions(+) create mode 100644 spring-properties-processor/build.gradle.kts create mode 100644 spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringMetadata.kt create mode 100644 spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringPropertiesProcessor.kt create mode 100644 spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringPropertiesProcessorProvider.kt create mode 100644 spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/utils/AnnotationName.kt create mode 100644 spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/utils/KSP.kt create mode 100644 spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/utils/Strings.kt create mode 100644 spring-properties-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider diff --git a/build.gradle.kts b/build.gradle.kts index 5d3b7bea65..3026d19b9e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("BotCommands-conventions") id("BotCommands-publish-conventions") + alias(libs.plugins.ksp) `java-library` } @@ -67,6 +68,10 @@ dependencies { api(libs.jsr305) compileOnly(libs.jetbrains.annotations) + // -------------------- ANNOTATION PROCESSORS -------------------- + + ksp(projects.springPropertiesProcessor) + // -------------------- TEST DEPENDENCIES -------------------- // Mocking @@ -108,6 +113,10 @@ val generateInfo by tasks.registering(GenerateBCInfoTask::class) { outputs.upToDateWhen { false } } +ksp { + excludedSources.from(generateInfo) +} + sourceSets { main { java { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 680c89e211..b24812df8c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,8 @@ kotlinx-datetime-jvm = "0.6.1" jackson = "2.18.3" +kotlinx-serialization = "1.8.1" + trove4j-core = "3.1.0" jda-emojis = "3.0.0" @@ -46,6 +48,14 @@ mockk-jvm = "1.13.16" byte-buddy = "1.16.1" byte-buddy-agent = "1.16.1" +ksp = "2.1.21-2.0.2" + +jetbrains-markdown = "0.7.3" + +[plugins] +kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } + [libraries] kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } kotlin-metadata-jvm = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin" } @@ -69,6 +79,8 @@ jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", ver jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } +kotlinx-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } + trove4j-core = { module = "net.sf.trove4j:core", version.ref = "trove4j-core" } jda-emojis = { module = "dev.freya02:jda-emojis", version.ref = "jda-emojis" } @@ -97,3 +109,7 @@ mockk-jvm = { module = "io.mockk:mockk-jvm", version.ref = "mockk-jvm" } byte-buddy = { module = "net.bytebuddy:byte-buddy", version.ref = "byte-buddy" } byte-buddy-agent = { module = "net.bytebuddy:byte-buddy-agent", version.ref = "byte-buddy-agent" } + +ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } + +jetbrains-markdown = { module = "org.jetbrains:markdown", version.ref = "jetbrains-markdown" } diff --git a/settings.gradle.kts b/settings.gradle.kts index bb1792cf2b..20f35e3925 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,5 @@ rootProject.name = "BotCommands" enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + +include(":spring-properties-processor") diff --git a/spring-properties-processor/build.gradle.kts b/spring-properties-processor/build.gradle.kts new file mode 100644 index 0000000000..793619728f --- /dev/null +++ b/spring-properties-processor/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("BotCommands-conventions") + alias(libs.plugins.kotlinx.serialization) +} + +dependencies { + implementation(libs.ksp) + implementation(libs.kotlinx.serialization) + implementation(libs.jetbrains.markdown) +} + +kotlin { + compilerOptions { + freeCompilerArgs.addAll( + "-Xcontext-receivers", + "-Xsuppress-warning=CONTEXT_RECEIVERS_DEPRECATED", + ) + } +} \ No newline at end of file diff --git a/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringMetadata.kt b/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringMetadata.kt new file mode 100644 index 0000000000..d79a4d15cc --- /dev/null +++ b/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringMetadata.kt @@ -0,0 +1,50 @@ +package io.github.freya022.botcommands.properties.processor + +import kotlinx.serialization.Serializable + +@Serializable +class SpringMetadata( + val groups: MutableList, + val properties: MutableList, + val hints: MutableList, +) { + constructor() : this(arrayListOf(), arrayListOf(), arrayListOf()) +} + +@Serializable +class GroupMetadata( + val name: String, + val type: String, + val sourceType: String, +) + +@Serializable +class PropertyMetadata( + val name: String, + val defaultValue: String?, + val type: String, + val sourceType: String, + val description: String?, + val deprecation: Deprecation? +) { + + @Serializable + class Deprecation( + val reason: String, + val level: String, + val replacement: String?, + ) +} + +@Serializable +class ClassReferenceHint(val name: String, val providers: List) { + + @Serializable + class Provider(val name: String, val parameters: Parameters) { + + @Serializable + class Parameters(val target: String) + } + + constructor(name: String, targetClass: String) : this(name, listOf(Provider("class-reference", Provider.Parameters(targetClass)))) +} \ No newline at end of file diff --git a/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringPropertiesProcessor.kt b/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringPropertiesProcessor.kt new file mode 100644 index 0000000000..b6dd24a374 --- /dev/null +++ b/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringPropertiesProcessor.kt @@ -0,0 +1,243 @@ +package io.github.freya022.botcommands.properties.processor + +import com.google.devtools.ksp.containingFile +import com.google.devtools.ksp.getDeclaredFunctions +import com.google.devtools.ksp.isConstructor +import com.google.devtools.ksp.processing.* +import com.google.devtools.ksp.symbol.* +import io.github.freya022.botcommands.properties.processor.utils.* +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToStream +import org.intellij.markdown.ast.getTextInNode +import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor +import org.intellij.markdown.parser.MarkdownParser + +private val nameName = AnnotationName("org.springframework.boot.context.properties.bind", "Name") +private val configurationPropertiesName = AnnotationName("org.springframework.boot.context.properties", "ConfigurationProperties") +private val configurationValueName = AnnotationName("io.github.freya022.botcommands.internal.core.config", "ConfigurationValue") +private val deprecatedValueName = AnnotationName("io.github.freya022.botcommands.internal.core.config", "DeprecatedValue") +private val ignoreDefaultValueName = AnnotationName("io.github.freya022.botcommands.internal.core.config", "IgnoreDefaultValue") + +val json = Json { + prettyPrint = true + explicitNulls = false + encodeDefaults = true +} + +private val classPattern = Regex("java.lang.Class<(.+)>") +private val collectionPattern = Regex("java\\.util\\.(?:Set|List)<(.+)>$") +private val mapPattern = Regex("java\\.util\\.Map<(.+), (.+)>$") + +class SpringPropertiesProcessor( + private val log: KSPLogger, + private val codeGenerator: CodeGenerator, +) : SymbolProcessor { + + private val configurableProperties: MutableSet = hashSetOf() + private val configuredProperties: MutableSet = hashSetOf() + private val metadata = SpringMetadata() + + private val processedNodes: MutableList = arrayListOf() + + override fun process(resolver: Resolver): List { + processedNodes += resolver.getSymbolsWithAnnotation(configurationPropertiesName.name, inDepth = false) + .filterIsInstance() + .onEach(::processClassDeclaration) + + processedNodes += resolver.getSymbolsWithAnnotation(configurationValueName.name, inDepth = false) + .filterIsInstance() + .onEach(::processPropertyDeclaration) + + return emptyList() + } + + @OptIn(ExperimentalSerializationApi::class) + override fun finish() { + log.warn("Processed ${processedNodes.size} nodes") + + val propertiesWithoutConfigValue = configurableProperties - configuredProperties + val propertiesWithoutConstructorParam = configuredProperties - configurableProperties + if (propertiesWithoutConfigValue.isNotEmpty()) { + log.warn("Could not find a @ConfigurationValue for $propertiesWithoutConfigValue") + } + if (propertiesWithoutConstructorParam.isNotEmpty()) { + log.warn("Could not find a constructor parameter for $propertiesWithoutConstructorParam") + } + + codeGenerator.createNewFile( + dependencies = Dependencies( + aggregating = true, + sources = processedNodes.mapNotNull { it.containingFile }.toTypedArray() + ), + packageName = "META-INF", + fileName = "spring-configuration-metadata", + extensionName = "json" + ).use { + json.encodeToStream(metadata, it) + } + } + + /** + * Find all properties based on the @ConfigurationProperties prefix + constructor parameter name. + * + * This is used to check that all properties have a @ConfigurationValue assigned and vice versa + */ + private fun processClassDeclaration(classDeclaration: KSClassDeclaration) { + val prefix: String = classDeclaration.findAnnotation(configurationPropertiesName).getOrDefault("prefix") + addPropertyBinds(classDeclaration, prefix) + } + + /** + * Add all constructor parameters as configuration properties, + * handle inner classes recursively + */ + private fun addPropertyBinds(classDeclaration: KSClassDeclaration, prefix: String) { + val constructor = classDeclaration.getDeclaredFunctions().single { it.isConstructor() } + + constructor.parameters.forEach { param -> + /** + * Get the class declaration of [param], + * or `null` if the parameter does not represent an inner class of [classDeclaration]. + */ + fun getInnerClassOrNull(): KSClassDeclaration? { + val paramClassDeclaration = param.type.resolve().declaration as? KSClassDeclaration ?: return null + + val parentDeclaration = paramClassDeclaration.parentDeclaration ?: return null + if (parentDeclaration.qualifiedName!!.asString() != classDeclaration.qualifiedName!!.asString()) return null + + return paramClassDeclaration + } + + val innerClass = getInnerClassOrNull() + val fullBindName = "$prefix.${param.getBindName()}" + if (innerClass != null && innerClass.classKind == ClassKind.CLASS) { + addPropertyBinds(innerClass, fullBindName) + } else { + configurableProperties.add(fullBindName) + } + } + } + + private fun KSValueParameter.getBindName(): String { + val nameAnnotation = findAnnotationOrNull(nameName) + return nameAnnotation?.getOrDefault("value") + ?: this.name?.asString() + ?: throw IllegalArgumentException("No name for $this") + } + + private fun processPropertyDeclaration(propertyDeclaration: KSPropertyDeclaration) { + val configurationPropertiesAnnotation = propertyDeclaration.findAnnotation(configurationValueName) + val path: String = configurationPropertiesAnnotation.getOrDefault("path") + val defaultValue: String? = configurationPropertiesAnnotation.getIfSet("defaultValue") + + if (path !in configurableProperties) { + log.warn("Metadata was added for '$path' but there is no such parameter in a constructor annotated with @ConfigurationProperties") + } else { + configuredProperties += path + } + + fun KSTypeReference.resolveTypedQualifiedName(from: KSDeclaration): String { + val type = resolve() + val qualifiedName = type.declaration.qualifiedName?.asString()?.toJavaType() + ?: throw IllegalArgumentException("Unknown type for $this in ${from.qualifiedName?.asString()}") + + return when { + type.arguments.isNotEmpty() -> { + val argumentsStr = type.arguments.joinToString { + when (it.variance) { + Variance.STAR -> "?" + else -> it.type!!.resolveTypedQualifiedName(from) + } + } + "$qualifiedName<$argumentsStr>" + } + else -> qualifiedName + } + } + // Get the type string if explicitly set, otherwise resolve + val typeStr = run { + val typeStr = configurationPropertiesAnnotation.getIfSet("type") + ?: propertyDeclaration.type.resolveTypedQualifiedName(propertyDeclaration) + // Wildcard is invalid for spring metadata, we ignore wildcards for the reference hint too + typeStr.replace("", "") + } + + val description = propertyDeclaration.docString?.let { docString -> + if (docString.contains("Default: ") && defaultValue == null && !propertyDeclaration.isAnnotationPresent(ignoreDefaultValueName)) { + log.warn("Missing default value for ${propertyDeclaration.qualifiedName?.asString()}") + } + + val parsedTree = MarkdownParser(CommonMarkFlavourDescriptor()).buildMarkdownTreeFromString(docString) + parsedTree.children + .asSequence() + .map { it.getTextInNode(docString) } + .filter { it.isNotBlank() } + .map { it.trim() } + .filterNot { it.startsWith("Spring property:") } + .filterNot { it.startsWith("@") } + .joinToString(" ") + .replace("\n", " ") // New lines in paragraph = structural wrapping + .tryAppendDot() + } + + tryPutClassReferenceHint(path, typeStr) + + val deprecation = propertyDeclaration.findAnnotationOrNull(deprecatedValueName)?.let { deprecatedValueAnnotation -> + val reason = deprecatedValueAnnotation.getOrDefault("reason").tryAppendDot() + val level = deprecatedValueAnnotation.getOrDefault("level").simpleName.asString().lowercase() + val replacement = deprecatedValueAnnotation.getIfSet("replacement") + PropertyMetadata.Deprecation(reason, level, replacement) + } + + metadata.properties += PropertyMetadata( + name = path, + defaultValue = defaultValue, + type = typeStr.toJavaType(), + sourceType = propertyDeclaration.canonicalName, + description = description, + deprecation = deprecation + ) + } + + private fun tryPutClassReferenceHint(name: String, typeStr: String) { + classPattern.matchEntire(typeStr)?.let { matchResult -> + val classTypeStr = matchResult.groupValues[1] + metadata.hints += ClassReferenceHint(name, classTypeStr) + return + } + + collectionPattern.matchEntire(typeStr)?.let { matchResult -> + val elementTypeStr = matchResult.groupValues[1] + // May or may not be a class + tryPutClassReferenceHint(name, elementTypeStr) + return + } + + mapPattern.matchEntire(typeStr)?.let { matchResult -> + val keyTypeStr = matchResult.groupValues[1] + val valueTypeStr = matchResult.groupValues[2] + // May or may not be a class + tryPutClassReferenceHint("$name.keys", keyTypeStr) + tryPutClassReferenceHint("$name.values", valueTypeStr) + return + } + } + + private fun String.toJavaType() = when (this) { + "kotlin.collections.List" -> "java.util.List" + "kotlin.collections.Set" -> "java.util.Set" + "kotlin.collections.Collection" -> "java.util.Collection" + "kotlin.collections.Map" -> "java.util.Map" + "kotlin.Boolean" -> "java.lang.Boolean" + "kotlin.Int" -> "java.lang.Integer" + "kotlin.Long" -> "java.lang.Long" + "kotlin.Double" -> "java.lang.Double" + "kotlin.String" -> "java.lang.String" + else -> { + if (this.startsWith("kotlin.")) + log.warn("Unmapped type: $this") + this + } + } +} \ No newline at end of file diff --git a/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringPropertiesProcessorProvider.kt b/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringPropertiesProcessorProvider.kt new file mode 100644 index 0000000000..404a18b237 --- /dev/null +++ b/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringPropertiesProcessorProvider.kt @@ -0,0 +1,12 @@ +package io.github.freya022.botcommands.properties.processor + +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider + +class SpringPropertiesProcessorProvider : SymbolProcessorProvider { + + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + return SpringPropertiesProcessor(environment.logger, environment.codeGenerator) + } +} \ No newline at end of file diff --git a/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/utils/AnnotationName.kt b/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/utils/AnnotationName.kt new file mode 100644 index 0000000000..93f908e7e7 --- /dev/null +++ b/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/utils/AnnotationName.kt @@ -0,0 +1,15 @@ +package io.github.freya022.botcommands.properties.processor.utils + +import com.google.devtools.ksp.symbol.KSAnnotation + +class AnnotationName( + val packageName: String, + val simpleName: String +) { + val name: String + get() = "$packageName.$simpleName" +} + +fun KSAnnotation.isA(annotationName: AnnotationName): Boolean = + shortName.asString() == annotationName.simpleName && + annotationType.resolve().declaration.qualifiedName!!.asString() == annotationName.name \ No newline at end of file diff --git a/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/utils/KSP.kt b/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/utils/KSP.kt new file mode 100644 index 0000000000..d2ad8ba399 --- /dev/null +++ b/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/utils/KSP.kt @@ -0,0 +1,43 @@ +package io.github.freya022.botcommands.properties.processor.utils + +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSAnnotation +import com.google.devtools.ksp.symbol.KSDeclaration +import com.google.devtools.ksp.symbol.KSValueArgument + +val KSDeclaration.canonicalName: String + get() { + tailrec fun String.prependDeclaringClass(declaration: KSDeclaration): String { + val parent = declaration.parentDeclaration ?: return this + return (parent.simpleName.asString() + "$" + this).prependDeclaringClass(parent) + } + + val parent = parentDeclaration ?: return "" + return parent.packageName.asString() + "." + parent.simpleName.asString().prependDeclaringClass(parent) + } + +fun KSAnnotation.getIfSet(name: String): R? { + return arguments.get(name).takeIf { defaultArguments.get(name) != it } +} + +fun KSAnnotation.getOrDefault(name: String): R { + return arguments[name] +} + +context(KSAnnotation) +@Suppress("UNCHECKED_CAST") +private operator fun Iterable.get(name: String): R = + singleOrNull { it.name?.asString() == name }?.value as R? + ?: throw IllegalArgumentException("Could not find an argument named '$name' on ${shortName.asString()}") + +fun KSAnnotated.findAnnotation(annotationName: AnnotationName): KSAnnotation { + return annotations.single { it.isA(annotationName) } +} + +fun KSAnnotated.findAnnotationOrNull(annotationName: AnnotationName): KSAnnotation? { + return annotations.singleOrNull { it.isA(annotationName) } +} + +fun KSAnnotated.isAnnotationPresent(annotationName: AnnotationName): Boolean { + return annotations.any { it.isA(annotationName) } +} \ No newline at end of file diff --git a/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/utils/Strings.kt b/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/utils/Strings.kt new file mode 100644 index 0000000000..d9903ee7ca --- /dev/null +++ b/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/utils/Strings.kt @@ -0,0 +1,6 @@ +package io.github.freya022.botcommands.properties.processor.utils + +fun String.tryAppendDot(): String = when { + this.endsWith('.') -> this + else -> "$this." +} \ No newline at end of file diff --git a/spring-properties-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/spring-properties-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 0000000000..c2832f9641 --- /dev/null +++ b/spring-properties-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +io.github.freya022.botcommands.properties.processor.SpringPropertiesProcessorProvider \ No newline at end of file From bf5f042ab6ab92091d48fb4e9da26bd396366101 Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Tue, 17 Jun 2025 11:29:11 +0200 Subject: [PATCH 07/19] Reduce optional Spring compile dependencies --- build.gradle.kts | 3 ++- gradle/libs.versions.toml | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3026d19b9e..e2b52a9388 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -61,7 +61,8 @@ dependencies { // -------------------- SPRING DEPENDENCIES -------------------- // Spring Boot - compileOnly(libs.spring.boot.starter) // Optional + compileOnly(libs.spring.boot) // Optional + compileOnly(libs.spring.boot.autoconfigure) // Optional // -------------------- ANNOTATION DEPENDENCIES -------------------- diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b24812df8c..c7bb8fbf24 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,7 @@ # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format [versions] +# Change in buildSrc too kotlin = "2.1.0" kotlinx-coroutines = "1.10.1" @@ -93,6 +94,8 @@ java-string-similarity = { module = "info.debatty:java-string-similarity", versi jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" } jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" } +spring-boot = { module = "org.springframework.boot:spring-boot", version.ref = "spring-boot" } +spring-boot-autoconfigure = { module = "org.springframework.boot:spring-boot-autoconfigure", version.ref = "spring-boot" } spring-boot-devtools = { module = "org.springframework.boot:spring-boot-devtools", version.ref = "spring-boot" } spring-boot-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "spring-boot" } From 7f9f5cb1dfe75ea8c8177417810de6f791b956f4 Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Tue, 17 Jun 2025 11:42:37 +0200 Subject: [PATCH 08/19] Move `java-library` plugin to publish convention --- build.gradle.kts | 1 - .../src/main/kotlin/BotCommands-publish-conventions.gradle.kts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e2b52a9388..cc7a024209 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,6 @@ plugins { id("BotCommands-conventions") id("BotCommands-publish-conventions") alias(libs.plugins.ksp) - `java-library` } dependencies { diff --git a/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts b/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts index d0d225f3d4..124dd4ad04 100644 --- a/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts @@ -1,7 +1,7 @@ import com.vanniktech.maven.publish.SonatypeHost plugins { - java + `java-library` signing id("com.vanniktech.maven.publish") id("org.jetbrains.dokka") From dc8c7a60daa5f42222457d9089afbd1a4b6f961a Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Tue, 17 Jun 2025 19:33:31 +0200 Subject: [PATCH 09/19] Add `-parameters` to javac --- buildSrc/src/main/kotlin/BotCommands-conventions.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/buildSrc/src/main/kotlin/BotCommands-conventions.gradle.kts b/buildSrc/src/main/kotlin/BotCommands-conventions.gradle.kts index 2944da76df..ee12aa6d78 100644 --- a/buildSrc/src/main/kotlin/BotCommands-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/BotCommands-conventions.gradle.kts @@ -15,6 +15,10 @@ java { targetCompatibility = JavaVersion.VERSION_17 } +tasks.withType { + options.compilerArgs.add("-parameters") +} + repositories { mavenCentral() mavenLocal() From a8b3d6d48a8f3eb270e9eb7c0392da1681947a72 Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Tue, 17 Jun 2025 23:23:13 +0200 Subject: [PATCH 10/19] Move Spring support to the BotCommands-spring module --- BotCommands-spring/build.gradle.kts | 41 ++++++++++ .../api/core/config/ConfigConfigurers.kt | 2 +- .../api/core/config/JDAConfiguration.kt | 4 +- .../BotCommandsAutoConfiguration.kt | 0 .../core/SpringBotCommandsConfiguration.kt | 0 .../core/SpringBotCommandsInitializer.kt | 0 .../internal/core/SpringJDARestartListener.kt | 0 .../core/SpringJDAServiceMismatchChecker.kt | 45 +++++++++++ .../core/annotations/InternalComponentScan.kt | 0 .../core/config/BotCommandsConfigurations.kt | 5 +- .../internal/core/config/ConfigProvider.kt | 0 .../config/DefaultSearchPathConfigurer.kt | 4 +- .../service/SpringBotCommandsBootstrap.kt | 0 .../service/SpringInstantiableServices.kt | 39 ++++++++++ .../core/service/SpringServiceContainer.kt | 0 ...itional-spring-configuration-metadata.json | 0 .../main/resources/META-INF/spring.factories | 0 ...ot.autoconfigure.AutoConfiguration.imports | 0 .../botcommands/spring/test/SpringMain.kt | 0 .../test/resources/application.properties | 0 build.gradle.kts | 9 ++- settings.gradle.kts | 1 + .../processor/SpringPropertiesProcessor.kt | 75 +------------------ .../botcommands/api/core/JDAService.kt | 5 +- .../botcommands/api/core/config/BConfig.kt | 5 +- .../api/core/config/BDatabaseConfig.kt | 2 +- .../core/JDAServiceMismatchChecker.kt | 37 --------- .../botcommands/internal/core/Version.kt | 4 +- .../core/config/ConfigurationAnnotations.kt | 6 +- .../service/AbstractBotCommandsBootstrap.kt | 6 +- .../core/service/InstantiableServices.kt | 35 +-------- .../botcommands/internal/utils/References.kt | 3 +- 32 files changed, 153 insertions(+), 175 deletions(-) create mode 100644 BotCommands-spring/build.gradle.kts rename {src => BotCommands-spring/src}/main/kotlin/io/github/freya022/botcommands/api/core/config/ConfigConfigurers.kt (99%) rename {src => BotCommands-spring/src}/main/kotlin/io/github/freya022/botcommands/api/core/config/JDAConfiguration.kt (98%) rename {src => BotCommands-spring/src}/main/kotlin/io/github/freya022/botcommands/autoconfigure/BotCommandsAutoConfiguration.kt (100%) rename {src => BotCommands-spring/src}/main/kotlin/io/github/freya022/botcommands/internal/core/SpringBotCommandsConfiguration.kt (100%) rename {src => BotCommands-spring/src}/main/kotlin/io/github/freya022/botcommands/internal/core/SpringBotCommandsInitializer.kt (100%) rename {src => BotCommands-spring/src}/main/kotlin/io/github/freya022/botcommands/internal/core/SpringJDARestartListener.kt (100%) create mode 100644 BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringJDAServiceMismatchChecker.kt rename {src => BotCommands-spring/src}/main/kotlin/io/github/freya022/botcommands/internal/core/annotations/InternalComponentScan.kt (100%) rename {src => BotCommands-spring/src}/main/kotlin/io/github/freya022/botcommands/internal/core/config/BotCommandsConfigurations.kt (98%) rename {src => BotCommands-spring/src}/main/kotlin/io/github/freya022/botcommands/internal/core/config/ConfigProvider.kt (100%) rename {src => BotCommands-spring/src}/main/kotlin/io/github/freya022/botcommands/internal/core/config/DefaultSearchPathConfigurer.kt (85%) rename {src => BotCommands-spring/src}/main/kotlin/io/github/freya022/botcommands/internal/core/service/SpringBotCommandsBootstrap.kt (100%) create mode 100644 BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/SpringInstantiableServices.kt rename {src => BotCommands-spring/src}/main/kotlin/io/github/freya022/botcommands/internal/core/service/SpringServiceContainer.kt (100%) rename {src => BotCommands-spring/src}/main/resources/META-INF/additional-spring-configuration-metadata.json (100%) rename {src => BotCommands-spring/src}/main/resources/META-INF/spring.factories (100%) rename {src => BotCommands-spring/src}/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (100%) rename {src => BotCommands-spring/src}/test/kotlin/io/github/freya022/botcommands/spring/test/SpringMain.kt (100%) rename {src => BotCommands-spring/src}/test/resources/application.properties (100%) diff --git a/BotCommands-spring/build.gradle.kts b/BotCommands-spring/build.gradle.kts new file mode 100644 index 0000000000..9b9c8e582f --- /dev/null +++ b/BotCommands-spring/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + id("BotCommands-conventions") + id("BotCommands-publish-conventions") + alias(libs.plugins.ksp) +} + +dependencies { + api(projects.botCommands) + + // Logging + implementation(libs.kotlin.logging.jvm) + + // -------------------- SPRING DEPENDENCIES -------------------- + + // Spring Boot + api(libs.spring.boot) + api(libs.spring.boot.autoconfigure) + + // -------------------- ANNOTATION PROCESSORS -------------------- + + ksp(projects.springPropertiesProcessor) + + // -------------------- TEST DEPENDENCIES -------------------- + + // Take the same test dependencies as the main library + testImplementation(rootProject.sourceSets.test.get().compileClasspath) + // Take the same test sources as the main library + testImplementation(rootProject.sourceSets.test.get().output) + + // Spring Boot + testImplementation(libs.spring.boot.starter) + testRuntimeOnly(libs.spring.boot.devtools) +} + +kotlin { + compilerOptions { + freeCompilerArgs.addAll( + "-Xjvm-default=all", + ) + } +} diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/core/config/ConfigConfigurers.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/api/core/config/ConfigConfigurers.kt similarity index 99% rename from src/main/kotlin/io/github/freya022/botcommands/api/core/config/ConfigConfigurers.kt rename to BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/api/core/config/ConfigConfigurers.kt index 77805d70b5..557b868185 100644 --- a/src/main/kotlin/io/github/freya022/botcommands/api/core/config/ConfigConfigurers.kt +++ b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/api/core/config/ConfigConfigurers.kt @@ -9,7 +9,7 @@ sealed interface BConfigurer { /** * Configurer for [BConfig]. - * + * * Only usable with Spring. */ interface BConfigConfigurer : BConfigurer diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/core/config/JDAConfiguration.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/api/core/config/JDAConfiguration.kt similarity index 98% rename from src/main/kotlin/io/github/freya022/botcommands/api/core/config/JDAConfiguration.kt rename to BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/api/core/config/JDAConfiguration.kt index 03e4d82b07..8e89206ca0 100644 --- a/src/main/kotlin/io/github/freya022/botcommands/api/core/config/JDAConfiguration.kt +++ b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/api/core/config/JDAConfiguration.kt @@ -1,5 +1,3 @@ -@file:Suppress("ConfigurationProperties") - package io.github.freya022.botcommands.api.core.config import io.github.freya022.botcommands.api.core.JDAService @@ -10,9 +8,9 @@ import net.dv8tion.jda.api.utils.cache.CacheFlag import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.bind.Name import org.springframework.context.event.ContextClosedEvent +import java.time.Duration as JavaDuration import kotlin.time.Duration import kotlin.time.toKotlinDuration -import java.time.Duration as JavaDuration /** * Configuration properties for [JDAService]. diff --git a/src/main/kotlin/io/github/freya022/botcommands/autoconfigure/BotCommandsAutoConfiguration.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/autoconfigure/BotCommandsAutoConfiguration.kt similarity index 100% rename from src/main/kotlin/io/github/freya022/botcommands/autoconfigure/BotCommandsAutoConfiguration.kt rename to BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/autoconfigure/BotCommandsAutoConfiguration.kt diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringBotCommandsConfiguration.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringBotCommandsConfiguration.kt similarity index 100% rename from src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringBotCommandsConfiguration.kt rename to BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringBotCommandsConfiguration.kt diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringBotCommandsInitializer.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringBotCommandsInitializer.kt similarity index 100% rename from src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringBotCommandsInitializer.kt rename to BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringBotCommandsInitializer.kt diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringJDARestartListener.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringJDARestartListener.kt similarity index 100% rename from src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringJDARestartListener.kt rename to BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringJDARestartListener.kt diff --git a/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringJDAServiceMismatchChecker.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringJDAServiceMismatchChecker.kt new file mode 100644 index 0000000000..cd809f56a0 --- /dev/null +++ b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/SpringJDAServiceMismatchChecker.kt @@ -0,0 +1,45 @@ +package io.github.freya022.botcommands.internal.core + +import io.github.freya022.botcommands.api.core.JDAService +import io.github.freya022.botcommands.api.core.annotations.BEventListener +import io.github.freya022.botcommands.api.core.config.JDAConfiguration +import io.github.freya022.botcommands.api.core.events.InjectedJDAEvent +import io.github.freya022.botcommands.internal.utils.reference +import io.github.oshai.kotlinlogging.KotlinLogging +import org.springframework.stereotype.Component + +private val logger = KotlinLogging.logger { } + +// Spring checks are slightly different, we want to tell the user to move them to their application environment, +// so the checks are consistent with condition annotations, as they can only check the environment +@Component +internal class SpringJDAServiceMismatchChecker { + @BEventListener + internal fun onJDA(event: InjectedJDAEvent, jdaConfiguration: JDAConfiguration, jdaService: JDAService) { + val environmentIntents = jdaConfiguration.intents + val jdaServiceIntents = jdaService.intents + if (environmentIntents != jdaServiceIntents) { + logger.warn { + """ + The intents given in JDAService and the environment should be the same! + Environment intents: ${environmentIntents.sorted()} + JDAService intents: ${jdaServiceIntents.sorted()} + Hint: you should get your intents from ${JDAConfiguration::intents.reference} + """.trimIndent() + } + } + + val environmentCacheFlags = jdaConfiguration.cacheFlags + val jdaServiceCacheFlags = jdaService.cacheFlags + if (environmentCacheFlags != jdaServiceCacheFlags) { + logger.warn { + """ + The cache flags given in JDAService and the environment should be the same! + Environment cache flags: ${environmentCacheFlags.sorted()} + JDAService cache flags: ${jdaServiceCacheFlags.sorted()} + Hint: you should get your caches flags from ${JDAConfiguration::cacheFlags.reference} + """.trimIndent() + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/annotations/InternalComponentScan.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/annotations/InternalComponentScan.kt similarity index 100% rename from src/main/kotlin/io/github/freya022/botcommands/internal/core/annotations/InternalComponentScan.kt rename to BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/annotations/InternalComponentScan.kt diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/BotCommandsConfigurations.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/BotCommandsConfigurations.kt similarity index 98% rename from src/main/kotlin/io/github/freya022/botcommands/internal/core/config/BotCommandsConfigurations.kt rename to BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/BotCommandsConfigurations.kt index 10c1ed607e..a85e81ec73 100644 --- a/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/BotCommandsConfigurations.kt +++ b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/BotCommandsConfigurations.kt @@ -5,16 +5,15 @@ import io.github.freya022.botcommands.api.core.config.* import io.github.freya022.botcommands.api.core.config.application.cache.ApplicationCommandsCacheConfig import io.github.freya022.botcommands.api.core.config.application.cache.ApplicationCommandsCacheConfigBuilder import io.github.freya022.botcommands.api.utils.EmojiUtils -import io.github.freya022.botcommands.internal.utils.throwArgument import net.dv8tion.jda.api.events.Event import net.dv8tion.jda.api.interactions.DiscordLocale import net.dv8tion.jda.api.requests.GatewayIntent import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.bind.Name +import java.time.Duration as JavaDuration import kotlin.io.path.Path import kotlin.time.Duration import kotlin.time.toKotlinDuration -import java.time.Duration as JavaDuration @ConfigurationProperties(prefix = "botcommands.core", ignoreUnknownFields = false) internal class BotCommandsCoreConfiguration( @@ -221,4 +220,4 @@ internal fun BComponentsConfigBuilder.applyConfig(configuration: BotCommandsComp enable = configuration.enable } -private fun unusable(): Nothing = throwArgument("Cannot be used") +private fun unusable(): Nothing = throw UnsupportedOperationException("Cannot be used") diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/ConfigProvider.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/ConfigProvider.kt similarity index 100% rename from src/main/kotlin/io/github/freya022/botcommands/internal/core/config/ConfigProvider.kt rename to BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/ConfigProvider.kt diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/DefaultSearchPathConfigurer.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/DefaultSearchPathConfigurer.kt similarity index 85% rename from src/main/kotlin/io/github/freya022/botcommands/internal/core/config/DefaultSearchPathConfigurer.kt rename to BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/DefaultSearchPathConfigurer.kt index 00293ced4f..805ff67710 100644 --- a/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/DefaultSearchPathConfigurer.kt +++ b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/DefaultSearchPathConfigurer.kt @@ -3,9 +3,7 @@ package io.github.freya022.botcommands.internal.core.config import io.github.freya022.botcommands.api.core.config.BConfigBuilder import io.github.freya022.botcommands.api.core.config.BConfigConfigurer import io.github.freya022.botcommands.internal.core.annotations.InternalComponentScan -import io.github.freya022.botcommands.internal.utils.annotationRef import org.springframework.beans.factory.getBeansWithAnnotation -import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.context.ApplicationContext import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.ComponentScans @@ -19,7 +17,7 @@ internal open class DefaultSearchPathConfigurer(private val applicationContext: val allUserPackages = (scans + groupScans).flatMap { it.packages } check(allUserPackages.isNotEmpty()) { - "You must configure at least one package on your ${annotationRef()} (recommended) or in a ${annotationRef()}" + "You must configure at least one package on your @SpringBootApplication (recommended) or in a @ComponentScan" } builder.packages += allUserPackages } diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/SpringBotCommandsBootstrap.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/SpringBotCommandsBootstrap.kt similarity index 100% rename from src/main/kotlin/io/github/freya022/botcommands/internal/core/service/SpringBotCommandsBootstrap.kt rename to BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/SpringBotCommandsBootstrap.kt diff --git a/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/SpringInstantiableServices.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/SpringInstantiableServices.kt new file mode 100644 index 0000000000..792c2a4e1d --- /dev/null +++ b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/SpringInstantiableServices.kt @@ -0,0 +1,39 @@ +package io.github.freya022.botcommands.internal.core.service + +import io.github.freya022.botcommands.api.core.config.BConfig +import io.github.oshai.kotlinlogging.KotlinLogging +import org.springframework.context.ApplicationContext +import org.springframework.stereotype.Service +import kotlin.reflect.KClass + +private val logger = KotlinLogging.logger { } + +@Service +internal class SpringInstantiableServices internal constructor( + private val config: BConfig, + private val applicationContext: ApplicationContext +) : InstantiableServices { + override fun getAllPrimaryTypes(): Set> { + return applicationContext.beanDefinitionNames + .asSequence() + .map { beanName -> + val type = applicationContext.getType(beanName) + if (type != null) { + type + } else { + logger.debug { "Creating bean '$beanName' as no type is available" } + applicationContext.getBean(beanName).javaClass + } + } + .filter { type -> + if (config.packages.any { type.packageName.startsWith(it) }) + return@filter true + if (type.packageName.startsWith("io.github.freya022.botcommands")) + return@filter true + if (type in config.classes) + return@filter true + return@filter false + } + .mapTo(hashSetOf()) { it.kotlin } + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/SpringServiceContainer.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/SpringServiceContainer.kt similarity index 100% rename from src/main/kotlin/io/github/freya022/botcommands/internal/core/service/SpringServiceContainer.kt rename to BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/SpringServiceContainer.kt diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/BotCommands-spring/src/main/resources/META-INF/additional-spring-configuration-metadata.json similarity index 100% rename from src/main/resources/META-INF/additional-spring-configuration-metadata.json rename to BotCommands-spring/src/main/resources/META-INF/additional-spring-configuration-metadata.json diff --git a/src/main/resources/META-INF/spring.factories b/BotCommands-spring/src/main/resources/META-INF/spring.factories similarity index 100% rename from src/main/resources/META-INF/spring.factories rename to BotCommands-spring/src/main/resources/META-INF/spring.factories diff --git a/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/BotCommands-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports similarity index 100% rename from src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports rename to BotCommands-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/src/test/kotlin/io/github/freya022/botcommands/spring/test/SpringMain.kt b/BotCommands-spring/src/test/kotlin/io/github/freya022/botcommands/spring/test/SpringMain.kt similarity index 100% rename from src/test/kotlin/io/github/freya022/botcommands/spring/test/SpringMain.kt rename to BotCommands-spring/src/test/kotlin/io/github/freya022/botcommands/spring/test/SpringMain.kt diff --git a/src/test/resources/application.properties b/BotCommands-spring/src/test/resources/application.properties similarity index 100% rename from src/test/resources/application.properties rename to BotCommands-spring/src/test/resources/application.properties diff --git a/build.gradle.kts b/build.gradle.kts index cc7a024209..a653994f92 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,7 +59,7 @@ dependencies { // -------------------- SPRING DEPENDENCIES -------------------- - // Spring Boot + // Spring Boot (only for compatibility that cannot be put in a different module) compileOnly(libs.spring.boot) // Optional compileOnly(libs.spring.boot.autoconfigure) // Optional @@ -103,9 +103,10 @@ dependencies { // Test stuff testImplementation(libs.kotlin.metadata.jvm) - // Spring Boot - testImplementation(libs.spring.boot.starter) - testImplementation(libs.spring.boot.devtools) + // The Spring Boot module will include them at runtime, + // but we need to make sure the main module works without it + testCompileOnly(libs.spring.boot) + testCompileOnly(libs.spring.boot.autoconfigure) } val generateInfo by tasks.registering(GenerateBCInfoTask::class) { diff --git a/settings.gradle.kts b/settings.gradle.kts index 20f35e3925..b13e33fb72 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,3 +3,4 @@ rootProject.name = "BotCommands" enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") include(":spring-properties-processor") +include(":BotCommands-spring") diff --git a/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringPropertiesProcessor.kt b/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringPropertiesProcessor.kt index b6dd24a374..cf92ad3cf7 100644 --- a/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringPropertiesProcessor.kt +++ b/spring-properties-processor/src/main/kotlin/io/github/freya022/botcommands/properties/processor/SpringPropertiesProcessor.kt @@ -1,8 +1,6 @@ package io.github.freya022.botcommands.properties.processor import com.google.devtools.ksp.containingFile -import com.google.devtools.ksp.getDeclaredFunctions -import com.google.devtools.ksp.isConstructor import com.google.devtools.ksp.processing.* import com.google.devtools.ksp.symbol.* import io.github.freya022.botcommands.properties.processor.utils.* @@ -13,8 +11,6 @@ import org.intellij.markdown.ast.getTextInNode import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor import org.intellij.markdown.parser.MarkdownParser -private val nameName = AnnotationName("org.springframework.boot.context.properties.bind", "Name") -private val configurationPropertiesName = AnnotationName("org.springframework.boot.context.properties", "ConfigurationProperties") private val configurationValueName = AnnotationName("io.github.freya022.botcommands.internal.core.config", "ConfigurationValue") private val deprecatedValueName = AnnotationName("io.github.freya022.botcommands.internal.core.config", "DeprecatedValue") private val ignoreDefaultValueName = AnnotationName("io.github.freya022.botcommands.internal.core.config", "IgnoreDefaultValue") @@ -34,17 +30,11 @@ class SpringPropertiesProcessor( private val codeGenerator: CodeGenerator, ) : SymbolProcessor { - private val configurableProperties: MutableSet = hashSetOf() - private val configuredProperties: MutableSet = hashSetOf() private val metadata = SpringMetadata() private val processedNodes: MutableList = arrayListOf() override fun process(resolver: Resolver): List { - processedNodes += resolver.getSymbolsWithAnnotation(configurationPropertiesName.name, inDepth = false) - .filterIsInstance() - .onEach(::processClassDeclaration) - processedNodes += resolver.getSymbolsWithAnnotation(configurationValueName.name, inDepth = false) .filterIsInstance() .onEach(::processPropertyDeclaration) @@ -54,16 +44,7 @@ class SpringPropertiesProcessor( @OptIn(ExperimentalSerializationApi::class) override fun finish() { - log.warn("Processed ${processedNodes.size} nodes") - - val propertiesWithoutConfigValue = configurableProperties - configuredProperties - val propertiesWithoutConstructorParam = configuredProperties - configurableProperties - if (propertiesWithoutConfigValue.isNotEmpty()) { - log.warn("Could not find a @ConfigurationValue for $propertiesWithoutConfigValue") - } - if (propertiesWithoutConstructorParam.isNotEmpty()) { - log.warn("Could not find a constructor parameter for $propertiesWithoutConstructorParam") - } + log.info("Processed ${processedNodes.size} nodes") codeGenerator.createNewFile( dependencies = Dependencies( @@ -78,65 +59,11 @@ class SpringPropertiesProcessor( } } - /** - * Find all properties based on the @ConfigurationProperties prefix + constructor parameter name. - * - * This is used to check that all properties have a @ConfigurationValue assigned and vice versa - */ - private fun processClassDeclaration(classDeclaration: KSClassDeclaration) { - val prefix: String = classDeclaration.findAnnotation(configurationPropertiesName).getOrDefault("prefix") - addPropertyBinds(classDeclaration, prefix) - } - - /** - * Add all constructor parameters as configuration properties, - * handle inner classes recursively - */ - private fun addPropertyBinds(classDeclaration: KSClassDeclaration, prefix: String) { - val constructor = classDeclaration.getDeclaredFunctions().single { it.isConstructor() } - - constructor.parameters.forEach { param -> - /** - * Get the class declaration of [param], - * or `null` if the parameter does not represent an inner class of [classDeclaration]. - */ - fun getInnerClassOrNull(): KSClassDeclaration? { - val paramClassDeclaration = param.type.resolve().declaration as? KSClassDeclaration ?: return null - - val parentDeclaration = paramClassDeclaration.parentDeclaration ?: return null - if (parentDeclaration.qualifiedName!!.asString() != classDeclaration.qualifiedName!!.asString()) return null - - return paramClassDeclaration - } - - val innerClass = getInnerClassOrNull() - val fullBindName = "$prefix.${param.getBindName()}" - if (innerClass != null && innerClass.classKind == ClassKind.CLASS) { - addPropertyBinds(innerClass, fullBindName) - } else { - configurableProperties.add(fullBindName) - } - } - } - - private fun KSValueParameter.getBindName(): String { - val nameAnnotation = findAnnotationOrNull(nameName) - return nameAnnotation?.getOrDefault("value") - ?: this.name?.asString() - ?: throw IllegalArgumentException("No name for $this") - } - private fun processPropertyDeclaration(propertyDeclaration: KSPropertyDeclaration) { val configurationPropertiesAnnotation = propertyDeclaration.findAnnotation(configurationValueName) val path: String = configurationPropertiesAnnotation.getOrDefault("path") val defaultValue: String? = configurationPropertiesAnnotation.getIfSet("defaultValue") - if (path !in configurableProperties) { - log.warn("Metadata was added for '$path' but there is no such parameter in a constructor annotated with @ConfigurationProperties") - } else { - configuredProperties += path - } - fun KSTypeReference.resolveTypedQualifiedName(from: KSDeclaration): String { val type = resolve() val qualifiedName = type.declaration.qualifiedName?.asString()?.toJavaType() diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/core/JDAService.kt b/src/main/kotlin/io/github/freya022/botcommands/api/core/JDAService.kt index 548a918dc1..ab27957a8a 100644 --- a/src/main/kotlin/io/github/freya022/botcommands/api/core/JDAService.kt +++ b/src/main/kotlin/io/github/freya022/botcommands/api/core/JDAService.kt @@ -1,10 +1,11 @@ package io.github.freya022.botcommands.api.core import dev.minn.jda.ktx.events.CoroutineEventManager +import io.github.freya022.botcommands.api.core.JDAService.Companion.defaultIntents import io.github.freya022.botcommands.api.core.JDAService.Companion.getDefaultRestConfig +import io.github.freya022.botcommands.api.core.JDAService.Companion.getDefaultRestRateLimiter import io.github.freya022.botcommands.api.core.annotations.BEventListener import io.github.freya022.botcommands.api.core.conditions.RequiredIntents -import io.github.freya022.botcommands.api.core.config.JDAConfiguration import io.github.freya022.botcommands.api.core.events.BReadyEvent import io.github.freya022.botcommands.api.core.events.InjectedJDAEvent import io.github.freya022.botcommands.api.core.requests.PriorityGlobalRestRateLimiter @@ -60,7 +61,7 @@ import javax.annotation.CheckReturnValue * * #### Spring support * Spring users must set their gateway intents and cache flags using properties, - * named `jda.intents` and `jda.cacheFlags` respectively, also available in [JDAConfiguration]. + * named `jda.intents` and `jda.cacheFlags` respectively, also available in `JDAConfiguration`. * * @see createJDA * @see InterfacedService @InterfacedService diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/core/config/BConfig.kt b/src/main/kotlin/io/github/freya022/botcommands/api/core/config/BConfig.kt index a40df0e09f..747c696659 100644 --- a/src/main/kotlin/io/github/freya022/botcommands/api/core/config/BConfig.kt +++ b/src/main/kotlin/io/github/freya022/botcommands/api/core/config/BConfig.kt @@ -119,7 +119,7 @@ interface BConfig { } @ConfigDSL -class BConfigBuilder internal constructor() : BConfig { +class BConfigBuilder : BConfig { override val packages: MutableSet = HashSet() override val classes: MutableSet> = HashSet() @@ -272,8 +272,7 @@ class BConfigBuilder internal constructor() : BConfig { componentsConfig.apply(block) } - @JvmSynthetic - internal fun build(): BConfig { + fun build(): BConfig { val logger = KotlinLogging.loggerOf() if (disableExceptionsInDMs) logger.info { "Disabled sending exception in bot owners DMs" } diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/core/config/BDatabaseConfig.kt b/src/main/kotlin/io/github/freya022/botcommands/api/core/config/BDatabaseConfig.kt index 8553a22822..7d38283e02 100644 --- a/src/main/kotlin/io/github/freya022/botcommands/api/core/config/BDatabaseConfig.kt +++ b/src/main/kotlin/io/github/freya022/botcommands/api/core/config/BDatabaseConfig.kt @@ -5,10 +5,10 @@ import io.github.freya022.botcommands.api.core.service.annotations.InjectedServi import io.github.freya022.botcommands.internal.core.config.ConfigDSL import io.github.freya022.botcommands.internal.core.config.ConfigurationValue import kotlinx.coroutines.debug.DebugProbes +import java.time.Duration as JavaDuration import kotlin.time.Duration import kotlin.time.toJavaDuration import kotlin.time.toKotlinDuration -import java.time.Duration as JavaDuration @InjectedService interface BDatabaseConfig { diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/JDAServiceMismatchChecker.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/core/JDAServiceMismatchChecker.kt index 94c24e3ba4..cc2fa62e43 100644 --- a/src/main/kotlin/io/github/freya022/botcommands/internal/core/JDAServiceMismatchChecker.kt +++ b/src/main/kotlin/io/github/freya022/botcommands/internal/core/JDAServiceMismatchChecker.kt @@ -2,13 +2,10 @@ package io.github.freya022.botcommands.internal.core import io.github.freya022.botcommands.api.core.JDAService import io.github.freya022.botcommands.api.core.annotations.BEventListener -import io.github.freya022.botcommands.api.core.config.JDAConfiguration import io.github.freya022.botcommands.api.core.events.InjectedJDAEvent import io.github.freya022.botcommands.api.core.service.annotations.BService -import io.github.freya022.botcommands.internal.utils.reference import io.github.oshai.kotlinlogging.KotlinLogging import net.dv8tion.jda.api.requests.GatewayIntent -import org.springframework.stereotype.Component private val logger = KotlinLogging.logger { } @@ -52,37 +49,3 @@ internal object JDAServiceMismatchChecker { private fun Collection.withOverlappingIntents(): Set = GatewayIntent.getIntents(GatewayIntent.getRaw(this)) - -// Spring checks are slightly different, we want to tell the user to move them to their application environment, -// so the checks are consistent with condition annotations, as they can only check the environment -@Component -internal class SpringJDAServiceMismatchChecker { - @BEventListener - internal fun onJDA(event: InjectedJDAEvent, jdaConfiguration: JDAConfiguration, jdaService: JDAService) { - val environmentIntents = jdaConfiguration.intents - val jdaServiceIntents = jdaService.intents - if (environmentIntents != jdaServiceIntents) { - logger.warn { - """ - The intents given in JDAService and the environment should be the same! - Environment intents: ${environmentIntents.sorted()} - JDAService intents: ${jdaServiceIntents.sorted()} - Hint: you should get your intents from ${JDAConfiguration::intents.reference} - """.trimIndent() - } - } - - val environmentCacheFlags = jdaConfiguration.cacheFlags - val jdaServiceCacheFlags = jdaService.cacheFlags - if (environmentCacheFlags != jdaServiceCacheFlags) { - logger.warn { - """ - The cache flags given in JDAService and the environment should be the same! - Environment cache flags: ${environmentCacheFlags.sorted()} - JDAService cache flags: ${jdaServiceCacheFlags.sorted()} - Hint: you should get your caches flags from ${JDAConfiguration::cacheFlags.reference} - """.trimIndent() - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/Version.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/core/Version.kt index c56d237e45..99378ede1a 100644 --- a/src/main/kotlin/io/github/freya022/botcommands/internal/core/Version.kt +++ b/src/main/kotlin/io/github/freya022/botcommands/internal/core/Version.kt @@ -6,13 +6,13 @@ import io.github.oshai.kotlinlogging.KotlinLogging import net.dv8tion.jda.api.JDAInfo // This really needs to not be critical -internal class Version private constructor( +class Version private constructor( val minor: Int, val major: Int, val revision: Int, val classifier: Classifier? ) : Comparable { - internal data class Classifier(val name: String, val version: Int) : Comparable { + data class Classifier internal constructor(val name: String, val version: Int) : Comparable { override fun compareTo(other: Classifier): Int { if (name != other.name) return classifierIndex().compareTo(other.classifierIndex()) return version.compareTo(other.version) diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/ConfigurationAnnotations.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/ConfigurationAnnotations.kt index 6ab2403319..d47ca1211e 100644 --- a/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/ConfigurationAnnotations.kt +++ b/src/main/kotlin/io/github/freya022/botcommands/internal/core/config/ConfigurationAnnotations.kt @@ -5,7 +5,7 @@ import org.intellij.lang.annotations.Language @Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.SOURCE) @MustBeDocumented -internal annotation class ConfigurationValue( +annotation class ConfigurationValue( val path: String, val defaultValue: String = "", @Language("Java", prefix = "", suffix = " x = null;") val type: String = "java.lang.Byte", @@ -14,7 +14,7 @@ internal annotation class ConfigurationValue( @Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.SOURCE) @MustBeDocumented -internal annotation class DeprecatedValue( +annotation class DeprecatedValue( val reason: String, val level: DeprecationLevel = DeprecationLevel.WARNING, val replacement: String = "", @@ -27,4 +27,4 @@ internal annotation class DeprecatedValue( @Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.SOURCE) -internal annotation class IgnoreDefaultValue \ No newline at end of file +annotation class IgnoreDefaultValue \ No newline at end of file diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/AbstractBotCommandsBootstrap.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/AbstractBotCommandsBootstrap.kt index 4be42ca36b..e3573821f4 100644 --- a/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/AbstractBotCommandsBootstrap.kt +++ b/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/AbstractBotCommandsBootstrap.kt @@ -14,16 +14,16 @@ import kotlinx.coroutines.runBlocking import kotlin.time.DurationUnit import kotlin.time.measureTime -internal abstract class AbstractBotCommandsBootstrap(protected val config: BConfig) : BotCommandsBootstrap { +abstract class AbstractBotCommandsBootstrap(protected val config: BConfig) : BotCommandsBootstrap { protected val logger = objectLogger() - internal fun init() { + protected fun init() { measure("Scanned reflection metadata") { ReflectionMetadata.runScan(config, this) } } - internal fun loadContext() = runBlocking { + fun loadContext() = runBlocking { measure("Completed BotCommands loading events") { serviceContainer.getService().apply { setStatus(BContext.Status.PRE_LOAD) diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/InstantiableServices.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/InstantiableServices.kt index 41bb1df518..e1d51d44c0 100644 --- a/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/InstantiableServices.kt +++ b/src/main/kotlin/io/github/freya022/botcommands/internal/core/service/InstantiableServices.kt @@ -1,6 +1,5 @@ package io.github.freya022.botcommands.internal.core.service -import io.github.freya022.botcommands.api.core.config.BConfig import io.github.freya022.botcommands.api.core.service.ServiceError.ErrorType.* import io.github.freya022.botcommands.api.core.service.annotations.BService import io.github.freya022.botcommands.api.core.service.annotations.InterfacedService @@ -15,48 +14,16 @@ import io.github.freya022.botcommands.internal.utils.throwArgument import io.github.freya022.botcommands.internal.utils.throwInternal import io.github.freya022.botcommands.internal.utils.throwState import io.github.oshai.kotlinlogging.KotlinLogging -import org.springframework.context.ApplicationContext -import org.springframework.stereotype.Service import kotlin.reflect.KClass import kotlin.reflect.full.findAnnotation private val logger = KotlinLogging.logger { } // Don't use InterfacedService, having it sealed + ServiceType is enough, saves unnecessary checks and logs -internal sealed interface InstantiableServices { +interface InstantiableServices { fun getAllPrimaryTypes(): Set> } -@Service -internal class SpringInstantiableServices internal constructor( - private val config: BConfig, - private val applicationContext: ApplicationContext -) : InstantiableServices { - override fun getAllPrimaryTypes(): Set> { - return applicationContext.beanDefinitionNames - .asSequence() - .map { beanName -> - val type = applicationContext.getType(beanName) - if (type != null) { - type - } else { - logger.debug { "Creating bean '$beanName' as no type is available" } - applicationContext.getBean(beanName).javaClass - } - } - .filter { type -> - if (config.packages.any { type.packageName.startsWith(it) }) - return@filter true - if (type.packageName.startsWith("io.github.freya022.botcommands")) - return@filter true - if (type in config.classes) - return@filter true - return@filter false - } - .mapTo(hashSetOf()) { it.kotlin } - } -} - @BService(priority = Int.MAX_VALUE) @ServiceType(InstantiableServices::class) @RequiresDefaultInjection diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/utils/References.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/utils/References.kt index 50fc34f31e..386f337783 100644 --- a/src/main/kotlin/io/github/freya022/botcommands/internal/utils/References.kt +++ b/src/main/kotlin/io/github/freya022/botcommands/internal/utils/References.kt @@ -5,8 +5,7 @@ import kotlin.jvm.internal.CallableReference import kotlin.reflect.KClass import kotlin.reflect.KProperty -@PublishedApi -internal val KProperty<*>.reference: String +val KProperty<*>.reference: String get() { val callableReference = (this as? CallableReference) ?: throwInternal("Referenced field doesn't seem to be compiler generated, exact type: ${this::class}") From 3c6bfecc2fc08e7cc910e246fe83a8163e7d375a Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Tue, 17 Jun 2025 23:42:34 +0200 Subject: [PATCH 11/19] Generate aggregated docs with all modules --- build.gradle.kts | 3 +++ .../main/kotlin/BotCommands-publish-conventions.gradle.kts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a653994f92..ebf0e72b21 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -107,6 +107,9 @@ dependencies { // but we need to make sure the main module works without it testCompileOnly(libs.spring.boot) testCompileOnly(libs.spring.boot.autoconfigure) + + dokka(rootProject) + dokka(projects.botCommandsSpring) } val generateInfo by tasks.registering(GenerateBCInfoTask::class) { diff --git a/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts b/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts index 124dd4ad04..167e405c81 100644 --- a/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts @@ -56,8 +56,8 @@ dokka { } sourceLink { - localDirectory = file("src") - remoteUrl("https://github.com/freya022/BotCommands/tree/${effectiveTag}/src") + localDirectory = rootDir + remoteUrl("https://github.com/freya022/BotCommands/tree/${effectiveTag}") remoteLineSuffix = "#L" } From 447484e349f131722397003ce9bbb21dabb26ac9 Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Wed, 18 Jun 2025 11:09:44 +0200 Subject: [PATCH 12/19] Remove "Only usable with Spring" from Spring module --- .../api/core/config/ConfigConfigurers.kt | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/api/core/config/ConfigConfigurers.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/api/core/config/ConfigConfigurers.kt index 557b868185..25bcf1f5bc 100644 --- a/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/api/core/config/ConfigConfigurers.kt +++ b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/api/core/config/ConfigConfigurers.kt @@ -1,7 +1,7 @@ package io.github.freya022.botcommands.api.core.config /** - * Base interface for runtime configuration, only usable with Spring, see sub-interfaces. + * Base interface for runtime configuration, see sub-interfaces. */ sealed interface BConfigurer { fun configure(builder: T) @@ -9,63 +9,45 @@ sealed interface BConfigurer { /** * Configurer for [BConfig]. - * - * Only usable with Spring. */ interface BConfigConfigurer : BConfigurer /** * Configurer for [BDatabaseConfig]. - * - * Only usable with Spring. */ interface BDatabaseConfigConfigurer : BConfigurer /** * Configurer for [BLocalizationConfig]. - * - * Only usable with Spring. */ interface BLocalizationConfigConfigurer : BConfigurer /** * Configurer for [BAppEmojisConfig]. - * - * Only usable with Spring. */ interface BAppEmojisConfigConfigurer : BConfigurer /** * Configurer for [BTextConfig]. - * - * Only usable with Spring. */ interface BTextConfigConfigurer : BConfigurer /** * Configurer for [BApplicationConfig]. - * - * Only usable with Spring. */ interface BApplicationConfigConfigurer : BConfigurer /** * Configurer for [BModalsConfig]. - * - * Only usable with Spring. */ interface BModalsConfigConfigurer : BConfigurer /** * Configurer for [BComponentsConfig]. - * - * Only usable with Spring. */ interface BComponentsConfigConfigurer : BConfigurer /** * Configurer for [BCoroutineScopesConfig]. - * - * Only usable with Spring. */ interface BCoroutineScopesConfigConfigurer : BConfigurer \ No newline at end of file From fa4ad3358c6111ddbff7ef27d55f5d88e3f73939 Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Wed, 18 Jun 2025 11:10:29 +0200 Subject: [PATCH 13/19] Move BotCommandsAutoConfiguration to internal package --- .../core}/autoconfigure/BotCommandsAutoConfiguration.kt | 2 +- ...springframework.boot.autoconfigure.AutoConfiguration.imports | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/{ => internal/core}/autoconfigure/BotCommandsAutoConfiguration.kt (82%) diff --git a/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/autoconfigure/BotCommandsAutoConfiguration.kt b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/autoconfigure/BotCommandsAutoConfiguration.kt similarity index 82% rename from BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/autoconfigure/BotCommandsAutoConfiguration.kt rename to BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/autoconfigure/BotCommandsAutoConfiguration.kt index d22c2ea994..9f390a9075 100644 --- a/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/autoconfigure/BotCommandsAutoConfiguration.kt +++ b/BotCommands-spring/src/main/kotlin/io/github/freya022/botcommands/internal/core/autoconfigure/BotCommandsAutoConfiguration.kt @@ -1,4 +1,4 @@ -package io.github.freya022.botcommands.autoconfigure +package io.github.freya022.botcommands.internal.core.autoconfigure import io.github.freya022.botcommands.internal.core.SpringBotCommandsConfiguration import org.springframework.boot.autoconfigure.AutoConfiguration diff --git a/BotCommands-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/BotCommands-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index cfa787ee1b..55e322c794 100644 --- a/BotCommands-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/BotCommands-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -io.github.freya022.botcommands.autoconfigure.BotCommandsAutoConfiguration \ No newline at end of file +io.github.freya022.botcommands.internal.core.autoconfigure.BotCommandsAutoConfiguration \ No newline at end of file From 98467c3cedd361e467411bebc5b1da8523faa01b Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Wed, 18 Jun 2025 12:10:52 +0200 Subject: [PATCH 14/19] Set version as dev only outside CIs and when publishing isn't set up --- .../src/main/kotlin/BotCommands-publish-conventions.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts b/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts index 167e405c81..425fe4d9bd 100644 --- a/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/BotCommands-publish-conventions.gradle.kts @@ -33,7 +33,8 @@ version = Version( minor = "0", revision = "0", classifier = "beta.2", - isDev = !canPublish + // isRelease = isCi || canPublish + isDev = !GitUtils.isCI(providers) && !canPublish ) val effectiveTag = if (canPublish) { From b91de649e1157e2ea0e9c631e532afbedb0d3eb6 Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Wed, 18 Jun 2025 12:12:26 +0200 Subject: [PATCH 15/19] Update workflows --- .github/workflows/check-kdocs.yml | 47 ++++++++++++++------------- .github/workflows/docs.yml | 25 +++++++------- .github/workflows/jitpack-preview.yml | 6 ---- .github/workflows/maven-publish.yml | 36 +++++++++----------- 4 files changed, 55 insertions(+), 59 deletions(-) diff --git a/.github/workflows/check-kdocs.yml b/.github/workflows/check-kdocs.yml index 549bead0e6..b6f052eacb 100644 --- a/.github/workflows/check-kdocs.yml +++ b/.github/workflows/check-kdocs.yml @@ -4,43 +4,46 @@ on: pull_request: branches: - 3.X - paths: - - 'pom.xml' - - 'src/main/**.java' - - 'src/test/**.java' - - 'src/main/**.kt' - - 'src/test/**.kt' push: branches: - 3.X - paths: - - 'pom.xml' - - 'src/main/**.java' - - 'src/test/**.java' - - 'src/main/**.kt' - - 'src/test/**.kt' + jobs: check-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + + - name: Set up JDK 24 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '17' - cache: 'maven' - - name: Compile and run tests with Maven - run: mvn -B --file pom.xml -Dkotlin.compiler.incremental=false test + # Toolchain is 24 due to certain modules, but we compile down to 17 for most modules + java-version: '24' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + + - name: Run tests + run: ./gradlew test check-kdocs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + + - name: Set up JDK 24 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '17' - cache: 'maven' - - name: Build KDocs with Maven - run: mvn -P docs -B generate-sources dokka:dokka + # Toolchain is 24 due to certain modules, but we compile down to 17 for most modules + java-version: '24' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + + - name: Build KDocs + run: ./gradlew dokkaGenerate diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 81a5aa49bd..3c330c5f12 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,10 +3,6 @@ name: Deploy KDocs to Pages on: push: branches: ["3.X"] - paths: - - 'pom.xml' - - 'src/main/**.java' - - 'src/main/**.kt' workflow_dispatch: # Allow one concurrent deployment @@ -21,20 +17,27 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + + - name: Set up JDK 24 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '17' - cache: 'maven' - - name: Set release version - run: mvn -B BotCommandsBuild:set-ci-version + # Toolchain is 24 due to certain modules, but we compile down to 17 for most modules + java-version: '24' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + - name: Generate KDocs - run: mvn -B -P docs generate-sources dokka:dokka + run: ./gradlew dokkaGenerate + - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: - path: ./target/dokka + path: ./build/dokka + publish-docs: name: Publish docs runs-on: ubuntu-latest diff --git a/.github/workflows/jitpack-preview.yml b/.github/workflows/jitpack-preview.yml index 115c0bce01..011e2e0f0c 100644 --- a/.github/workflows/jitpack-preview.yml +++ b/.github/workflows/jitpack-preview.yml @@ -6,12 +6,6 @@ on: - reopened - synchronize - closed - paths: - - 'pom.xml' - - 'src/main/**.java' - - 'src/test/**.java' - - 'src/main/**.kt' - - 'src/test/**.kt' permissions: pull-requests: write diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index bed20aaa5e..93ccbc7f82 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -1,5 +1,3 @@ -# https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-Apache-Maven - name: Publish to Maven Central on: release: @@ -11,30 +9,28 @@ jobs: if: github.event.release.draft == false steps: - uses: actions/checkout@v4 + - name: Configure Git User run: | git config user.email "41875020+freya022@users.noreply.github.com" git config user.name "freya022" - - name: Set up JDK 17 + + - name: Set up JDK 24 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '17' - cache: 'maven' - server-id: central - server-username: MAVEN_USERNAME - server-password: MAVEN_PASSWORD - gpg-private-key: ${{ secrets.OSSRH_GPG_SECRET_KEY }} - gpg-passphrase: MAVEN_GPG_PASSPHRASE - - name: Set release version - run: mvn -B --file pom.xml BotCommandsBuild:set-ci-version + # Toolchain is 24 due to certain modules, but we compile down to 17 for most modules + java-version: '24' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + - name: Compile and release - run: > - mvn -B --file pom.xml - -P docs,release - -Dkotlin.compiler.incremental=false - deploy + run: ./gradlew publishToMavenCentral env: - MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_TOKEN }} + ORG_GRADLE_PROJECT_mavenGpgSecretKey: ${{ secrets.GPG_SECRET_KEY }} + ORG_GRADLE_PROJECT_mavenGpgKeyId: ${{ secrets.GPG_KEY_ID }} From adb32915c9bf2b93db97e980f2fee8bb32464917 Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Wed, 18 Jun 2025 16:13:40 +0200 Subject: [PATCH 16/19] Exclude opus-java and tink --- build.gradle.kts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index ebf0e72b21..3c24ac0801 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,11 @@ plugins { alias(libs.plugins.ksp) } +configurations.all { + exclude(module = "opus-java") + exclude(module = "tink") +} + dependencies { // -------------------- CORE DEPENDENCIES -------------------- From 3b248807cd39abe3d8c981963ac59bd33773dd66 Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Wed, 18 Jun 2025 17:45:16 +0200 Subject: [PATCH 17/19] Add `examples` source set --- build.gradle.kts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 3c24ac0801..2cc870ab25 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -135,6 +135,18 @@ sourceSets { } } +// Register examples +sourceSets { + register("examples") { + compileClasspath += sourceSets.main.get().output + runtimeClasspath += sourceSets.main.get().output + } +} + +configurations["examplesApi"].extendsFrom(configurations["api"], configurations["testApi"]) +configurations["examplesImplementation"].extendsFrom(configurations["implementation"], configurations["testImplementation"]) +configurations["examplesCompileOnly"].extendsFrom(configurations["compileOnly"], configurations["testCompileOnly"]) + dokka { dokkaSourceSets.configureEach { suppressedFiles.from("src/main/java/io/github/freya022/botcommands/api/\$BCInfo.java") From 4e05afbf8fee6b651fe8ab53910c4c1e39c2ca85 Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Wed, 18 Jun 2025 17:46:17 +0200 Subject: [PATCH 18/19] Remove Maven files --- .gitignore | 1 - .mvn/wrapper/maven-wrapper.jar | Bin 62547 -> 0 bytes .mvn/wrapper/maven-wrapper.properties | 18 - mvnw | 308 ------------- mvnw.cmd | 205 --------- pom.xml | 629 -------------------------- rules.xml | 36 -- 7 files changed, 1197 deletions(-) delete mode 100644 .mvn/wrapper/maven-wrapper.jar delete mode 100644 .mvn/wrapper/maven-wrapper.properties delete mode 100644 mvnw delete mode 100644 mvnw.cmd delete mode 100644 pom.xml delete mode 100644 rules.xml diff --git a/.gitignore b/.gitignore index 7bbac96957..23c4ccac95 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ *.iml .idea -target build .gradle .kotlin diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index cb28b0e37c7d206feb564310fdeec0927af4123a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62547 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 A?*IS* diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index eacdc9ed17..0000000000 --- a/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/mvnw b/mvnw deleted file mode 100644 index 8d937f4c14..0000000000 --- a/mvnw +++ /dev/null @@ -1,308 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.2.0 -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /usr/local/etc/mavenrc ] ; then - . /usr/local/etc/mavenrc - fi - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "$(uname)" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME - else - JAVA_HOME="/Library/Java/Home"; export JAVA_HOME - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=$(java-config --jre-home) - fi -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --unix "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --unix "$CLASSPATH") -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && - JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="$(which javac)" - if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=$(which readlink) - if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then - if $darwin ; then - javaHome="$(dirname "\"$javaExecutable\"")" - javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" - else - javaExecutable="$(readlink -f "\"$javaExecutable\"")" - fi - javaHome="$(dirname "\"$javaExecutable\"")" - javaHome=$(expr "$javaHome" : '\(.*\)/bin') - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=$(cd "$wdir/.." || exit 1; pwd) - fi - # end of workaround - done - printf '%s' "$(cd "$basedir" || exit 1; pwd)" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - # Remove \r in case we run on Windows within Git Bash - # and check out the repository with auto CRLF management - # enabled. Otherwise, we may read lines that are delimited with - # \r\n and produce $'-Xarg\r' rather than -Xarg due to word - # splitting rules. - tr -s '\r\n' ' ' < "$1" - fi -} - -log() { - if [ "$MVNW_VERBOSE" = true ]; then - printf '%s\n' "$1" - fi -} - -BASE_DIR=$(find_maven_basedir "$(dirname "$0")") -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR -log "$MAVEN_PROJECTBASEDIR" - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" -if [ -r "$wrapperJarPath" ]; then - log "Found $wrapperJarPath" -else - log "Couldn't find $wrapperJarPath, downloading it ..." - - if [ -n "$MVNW_REPOURL" ]; then - wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - else - wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - fi - while IFS="=" read -r key value; do - # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) - safeValue=$(echo "$value" | tr -d '\r') - case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; - esac - done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" - log "Downloading from: $wrapperUrl" - - if $cygwin; then - wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") - fi - - if command -v wget > /dev/null; then - log "Found wget ... using wget" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - else - wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - log "Found curl ... using curl" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - else - curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - fi - else - log "Falling back to using Java to download" - javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" - javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaSource=$(cygpath --path --windows "$javaSource") - javaClass=$(cygpath --path --windows "$javaClass") - fi - if [ -e "$javaSource" ]; then - if [ ! -e "$javaClass" ]; then - log " - Compiling MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/javac" "$javaSource") - fi - if [ -e "$javaClass" ]; then - log " - Running MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -# If specified, validate the SHA-256 sum of the Maven wrapper jar file -wrapperSha256Sum="" -while IFS="=" read -r key value; do - case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; - esac -done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" -if [ -n "$wrapperSha256Sum" ]; then - wrapperSha256Result=false - if command -v sha256sum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then - wrapperSha256Result=true - fi - elif command -v shasum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then - wrapperSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." - echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." - exit 1 - fi - if [ $wrapperSha256Result = false ]; then - echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 - echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 - echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 - exit 1 - fi -fi - -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --windows "$CLASSPATH") - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -# shellcheck disable=SC2086 # safe args -exec "$JAVACMD" \ - $MAVEN_OPTS \ - $MAVEN_DEBUG_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd deleted file mode 100644 index c4586b564e..0000000000 --- a/mvnw.cmd +++ /dev/null @@ -1,205 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.2.0 -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* -if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %WRAPPER_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file -SET WRAPPER_SHA_256_SUM="" -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B -) -IF NOT %WRAPPER_SHA_256_SUM%=="" ( - powershell -Command "&{"^ - "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ - "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ - " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ - " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ - " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ - " exit 1;"^ - "}"^ - "}" - if ERRORLEVEL 1 goto error -) - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% ^ - %JVM_CONFIG_MAVEN_PROPS% ^ - %MAVEN_OPTS% ^ - %MAVEN_DEBUG_OPTS% ^ - -classpath %WRAPPER_JAR% ^ - "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ - %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" -if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%"=="on" pause - -if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% - -cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml deleted file mode 100644 index f96d6f52f0..0000000000 --- a/pom.xml +++ /dev/null @@ -1,629 +0,0 @@ - - - 4.0.0 - - io.github.freya022 - BotCommands - - - - - 3.0.0-beta.2_DEV - BotCommands - - A Kotlin-first (and Java) framework that makes creating Discord bots a piece of cake, using the JDA library. - - https://github.com/freya022/BotCommands - - - - freya022 - 41875020+freya022@users.noreply.github.com - - - - - UTF-8 - - 2.1.0 - 1.10.1 - 3.4.2 - 11.2.0 - 8.14.0 - 2.18.3 - - 2.0 - 2.0 - 17 - true - - - - - Mozilla Public License 2.0 - https://opensource.org/licenses/MPL-2.0 - repo - - - - - scm:git:https://github.com/freya022/BotCommands.git - scm:git:https://github.com/freya022/BotCommands.git - https://github.com/freya022/BotCommands - 3.X - - - - - docs - - - - org.jetbrains.dokka - dokka-maven-plugin - 1.9.20 - - - package - - dokka - - - - - 17 - true - - - src - ${project.scm.url}/tree/${project.scm.tag}/src - #L - - - ${project.basedir}/src/main/java/io/github/freya022/botcommands/api/$BCInfo.java - - ${project.basedir}/src/main/java - ${project.build.directory}/generated-sources - ${project.basedir}/src/main/kotlin - - - - https://docs.jda.wiki/ - https://docs.jda.wiki/element-list - - - https://javadoc.io/doc/org.jetbrains/annotations/23.0.0 - https://javadoc.io/doc/org.jetbrains/annotations/23.0.0/element-list - - - https://docs.spring.io/spring-framework/docs/current/javadoc-api - https://docs.spring.io/spring-framework/docs/current/javadoc-api/element-list - - - https://javadoc.io/doc/com.bucket4j/bucket4j_jdk17-core/${bucket4j.version} - https://javadoc.io/doc/com.bucket4j/bucket4j_jdk17-core/${bucket4j.version}/element-list - - - - - .*internal.* - true - - - - - - - - - release - - - - org.apache.maven.plugins - maven-enforcer-plugin - 3.4.1 - - - enforce-all-profiles-are-activated - - enforce - - - - - docs - - - true - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.3.0 - - - - freya02 - - - - - - dokka-jar - package - - jar - - - javadoc - ${project.build.directory}/dokka - false - - - - - - org.sonatype.central - central-publishing-maven-plugin - 0.7.0 - true - - central - true - - - - org.apache.maven.plugins - maven-gpg-plugin - 3.2.4 - - - sign-artifacts - verify - - sign - - - - - - - org.apache.maven.plugins - maven-install-plugin - 3.1.1 - - - - - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - 3.4.1 - - - enforce-maven - - enforce - - - - - 3.6.3 - - - true - - - - - - io.github.freya022 - BotCommandsBuild-maven-plugin - 01a97655cd - - - - generate-version-info - generate-configuration-metadata - check-ci-version - - - - - - ${project.basedir}/src/main/kotlin - - - - - org.jetbrains.kotlin - kotlin-maven-plugin - ${kotlin.version} - - - -Xjvm-default=all - -Xcontext-receivers - -Xsuppress-warning=CONTEXT_RECEIVERS_DEPRECATED - -Xconsistent-data-class-copy-visibility - - - - - compile - - compile - - - - ${project.basedir}/src/main/kotlin - ${project.basedir}/src/main/java - - ${project.build.directory}/generated-sources - - - - - test-compile - - test-compile - - - - ${project.basedir}/src/test/kotlin - ${project.basedir}/src/test/java - - ${project.basedir}/src/examples/kotlin - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.12.1 - - 17 - - -parameters - - - io/github/freya022/botcommands/api/$BCInfo.java - - - - - - default-compile - none - - - - default-testCompile - none - - - java-compile - compile - - compile - - - - java-test-compile - test-compile - - testCompile - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.5.0 - - - generate-sources - - add-source - - - - src/main/kotlin - - ${project.build.directory}/generated-sources - - - - - examples-test-sources - generate-test-sources - - add-test-source - - - - src/examples/kotlin - - - - - examples-test-resources - generate-test-resources - - add-test-resource - - - - - src/examples/resources - - - - - - - - org.apache.maven.plugins - maven-source-plugin - 3.3.0 - - - attach-sources - - jar-no-fork - - - io/github/freya022/botcommands/api/$BCInfo.java - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.2.5 - - - org.codehaus.mojo - versions-maven-plugin - 2.16.2 - - file:///${project.basedir}/rules.xml - - - - - - - - jitpack - https://jitpack.io - - - - - - org.springframework.boot - spring-boot-starter - ${spring-boot.version} - true - - - - org.jetbrains.kotlin - kotlin-stdlib - ${kotlin.version} - - - org.jetbrains.kotlin - kotlin-reflect - ${kotlin.version} - - - org.jetbrains.kotlinx - kotlinx-coroutines-core - ${kotlin.coroutine.version} - - - org.jetbrains.kotlinx - kotlinx-coroutines-debug - ${kotlin.coroutine.version} - true - - - org.jetbrains.kotlinx - kotlinx-datetime-jvm - 0.6.1 - - - net.dv8tion - JDA - 5.5.0 - - - club.minnced - opus-java - - - com.google.crypto.tink - tink - - - provided - - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - com.fasterxml.jackson.module - jackson-module-kotlin - ${jackson.version} - - - club.minnced - jda-ktx - 0.12.0 - - - com.google.code.findbugs - jsr305 - 3.0.2 - - - net.fellbaum - jemoji - 1.6.0 - - - dev.freya02 - jda-emojis - 3.0.0 - - - org.jetbrains - annotations - 26.0.2 - provided - - - io.github.classgraph - classgraph - 4.8.179 - - - net.sf.trove4j - core - 3.1.0 - - - com.github.ben-manes.caffeine - caffeine - 3.2.0 - - - info.debatty - java-string-similarity - 2.0.0 - - - org.slf4j - slf4j-api - 2.0.16 - - - io.github.oshai - kotlin-logging-jvm - 7.0.3 - - - com.bucket4j - bucket4j_jdk17-core - ${bucket4j.version} - - - com.bucket4j - bucket4j_jdk17-postgresql - ${bucket4j.version} - test - - - dev.reformator.stacktracedecoroutinator - stacktrace-decoroutinator-jvm - 2.4.8 - test - - - org.springframework.boot - spring-boot-devtools - ${spring-boot.version} - test - - - ch.qos.logback - logback-classic - 1.5.16 - test - - - org.postgresql - postgresql - 42.7.5 - test - - - com.h2database - h2 - 2.3.232 - test - - - org.flywaydb - flyway-core - ${flyway.version} - test - - - org.flywaydb - flyway-database-postgresql - ${flyway.version} - test - - - com.zaxxer - HikariCP - 6.2.1 - true - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - ${jackson.version} - test - - - org.junit.jupiter - junit-jupiter - 5.11.4 - test - - - io.mockk - mockk-jvm - 1.13.16 - test - - - net.bytebuddy - byte-buddy - 1.16.1 - test - - - net.bytebuddy - byte-buddy-agent - 1.16.1 - test - - - org.jetbrains.kotlin - kotlin-metadata-jvm - ${kotlin.version} - test - - - diff --git a/rules.xml b/rules.xml deleted file mode 100644 index b2622f428b..0000000000 --- a/rules.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - .+-(?:Beta|RC).* - - - - - .+-(?:RC|Beta).* - - - - - .+-alpha.+ - - - - - .+-rc.+ - - - - - .+_DEV - - - - - .+-M\d+ - - - - \ No newline at end of file From 086891602be0ed6def3aa67514e8feefac859a8d Mon Sep 17 00:00:00 2001 From: freya02 <41875020+freya022@users.noreply.github.com> Date: Sun, 22 Jun 2025 14:46:40 +0200 Subject: [PATCH 19/19] Use version catalog in `buildSrc` --- buildSrc/build.gradle.kts | 7 +++---- buildSrc/settings.gradle.kts | 7 +++++++ gradle/libs.versions.toml | 10 +++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 buildSrc/settings.gradle.kts diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index acd4340906..a8cc5d6687 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -10,10 +10,9 @@ repositories { } dependencies { - // Change in version catalog too - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.0") - implementation("com.vanniktech.maven.publish:com.vanniktech.maven.publish.gradle.plugin:0.32.0") - implementation("org.jetbrains.dokka:org.jetbrains.dokka.gradle.plugin:2.0.0") + implementation(libs.kotlin.plugin) + implementation(libs.maven.publish.plugin) + implementation(libs.dokka.plugin) } java { diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts new file mode 100644 index 0000000000..fa8bc74926 --- /dev/null +++ b/buildSrc/settings.gradle.kts @@ -0,0 +1,7 @@ +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c7bb8fbf24..04ea8a2c17 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,11 @@ # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format [versions] -# Change in buildSrc too +# Plugins +maven-publish-plugin = "0.32.0" +dokka-plugin = "2.0.0" + +# Libraries kotlin = "2.1.0" kotlinx-coroutines = "1.10.1" @@ -58,6 +62,10 @@ kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", vers ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } [libraries] +kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } +maven-publish-plugin = { module = "com.vanniktech.maven.publish:com.vanniktech.maven.publish.gradle.plugin", version.ref = "maven-publish-plugin" } +dokka-plugin = { module = "org.jetbrains.dokka:org.jetbrains.dokka.gradle.plugin", version.ref = "dokka-plugin" } + kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } kotlin-metadata-jvm = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }