13 Commits

Author SHA1 Message Date
a65a57f9e2 ci(github-action): 减少矩阵量, 以免大量占用 Action 资源.
把 windows 系统移除, 因此只需要检查是否兼容 jvm 版本即可.
2022-08-18 12:50:49 +08:00
19cc1b9358 ci(github-action): 试着加入兼容性测试.
试一下 Github Action 的矩阵作业, 不过本项目可能还用不到兼容性测试, 所以本改动可能不会并入主分支.
2022-08-18 12:43:55 +08:00
ff396425a7 refactor: 补充缺失代码.
由于先前的修改导致代码缺失, 我现在同意暂存功能很好用了.
2022-08-18 01:06:54 +08:00
580d9122e5 refactor(utils): 把日志记录器的获取方式改的优雅一点(对于测试来说).
由于 Kover 一直不把无代码高阶函数忽略掉, 所以稍微改一下这个日志记录器的获取方法, 让测试覆盖数据好看点(doge).
2022-08-17 23:01:06 +08:00
2a607f1129 fix(extension): 修复 ExtensionClassLoader 无法过滤非扩展包内 BotExtensionFactory 对象的问题.
按照 ServiceLoader 的规范, 文件应该是在 META-INF/services/{class} 这里的, 但当时忘记这个设计了, 导致直接判断 name == class, 然后失效.
修复好了.
2022-08-15 02:00:34 +08:00
2d6da7c1ae build(test): 添加 Jacoco 插件.
给 Extension 模块添加 Jacoco 插件, 以后估计会开 Codecov 来统计测试覆盖率.
2022-08-15 01:45:22 +08:00
6235c5f51a build(dependencies): 更新 Kotlin 版本(1.6.10 -> 1.7.10). 2022-08-15 01:39:08 +08:00
255a02c93c refactor(config): 重构 AppPaths 的构造方法, 应对将来 Kotlin 更新中的特性.
先前的方法是利用了初始化与调用的顺序, 来实现的 Supplier 互补(虽然在代码中, 确实存在未初始化调用的情况, 但实际运行的时候, 会先初始化, 再调用 Supplier),
但是未来 Kotlin 的更新中,编译器会把这个操作视为未初始化错误, 所以在这次改动中修复掉这个 bug 操作.
2022-08-15 01:38:08 +08:00
dce28be9c7 fix(logging): 修复日志滚动文件路径错误.
由于在滚动格式中没有使用 DATA_LOGS 占位符, 导致在日志滚动时会把日志归档文件保存在运行目录下, 而不是指定的数据目录,
该改动已修复该问题.
2022-08-13 13:19:09 +08:00
673c6d8392 build: 为项目支持可重现构建.
为确保项目的使用者(无论是开发者, 还是最终用户)可以完全重现构建, 确保安全, 故调整相关配置, 以实现"可重现构建".
有关可重现构建, 可以看这个: https://reproducible-builds.org/
2022-08-06 03:23:35 +08:00
d586ca378e fix(launch): 修复缺省的 Maven 中央库不遵循代理规则的问题.
这个属于漏网之鱼, 已修复.
2022-08-05 00:23:13 +08:00
3ba4364a07 test(config): 补全对 ProxyConfig 的单元测试.
先前加了个 ProxyConfig.toString 方法, 所以补充了一下测试项.
2022-08-04 19:13:45 +08:00
eda0e522cd docs(config): 补充关于 Gson 类型适配器的使用指导.
为 AppConfig 和 BotConfig 补充关于 Gson 类型适配器使用的信息, 以便于开发者正确使用类型适配器解析和编码 Json.
2022-07-22 21:59:14 +08:00
13 changed files with 137 additions and 30 deletions

View File

@ -13,22 +13,46 @@ permissions:
contents: read
jobs:
build:
build-and-test:
timeout-minutes: 8
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'adopt-hotspot'
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build and test
uses: gradle/gradle-build-action@v2.2.1
with:
gradle-version: 'wrapper'
arguments: test
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'adopt-hotspot'
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build and test
uses: gradle/gradle-build-action@v2.2.1
with:
gradle-version: 'wrapper'
arguments: test
compatibility-check:
timeout-minutes: 30
continue-on-error: true
strategy:
matrix:
os: [ ubuntu-latest ]
java-version: [ 11, 12, 13, 14, 15, 16, 17, 18 ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: ${{ matrix.java-version }}
distribution: 'adopt-hotspot'
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build and test
uses: gradle/gradle-build-action@v2.2.1
with:
gradle-version: 'wrapper'
arguments: test

View File

@ -1,5 +1,5 @@
plugins {
kotlin("jvm") version "1.6.10" apply false
kotlin("jvm") version "1.7.10" apply false
id("org.jetbrains.kotlinx.kover") version "0.5.1" apply false
}

View File

@ -56,3 +56,8 @@ application {
tasks.jar.configure {
exclude("**/logback-test.xml")
}
tasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}

View File

@ -18,6 +18,8 @@ import java.net.URL
import java.nio.charset.StandardCharsets
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.util.function.Supplier
import kotlin.reflect.KProperty
private val log = KotlinLogging.logger { }
@ -102,9 +104,9 @@ private fun createDefaultRepositoryId(): String {
* 必须提供 `pathSupplier` 或 `fileSupplier` 其中一个, 才能正常提供路径.
*/
internal enum class AppPaths(
private val pathSupplier: () -> String = { fileSupplier.invoke().canonicalPath },
private val pathSupplier: PathSupplier,
private val initializer: AppPaths.() -> Unit = AppPaths::defaultInitializer,
private val fileSupplier: () -> File = { File(pathSupplier()) }
private val fileSupplier: FileSupplier,
) {
/**
* 数据根目录.
@ -113,7 +115,7 @@ internal enum class AppPaths(
*
* 提示: 结尾不带 `/`.
*/
DATA_ROOT(fileSupplier = {
DATA_ROOT(fileSupplier = FileSupplier {
File(
System.getProperty(PathConst.PROP_DATA_PATH) ?: System.getenv(PathConst.ENV_DATA_PATH)
?: System.getProperty("user.dir") ?: "."
@ -125,7 +127,7 @@ internal enum class AppPaths(
}
}),
CONFIG_APPLICATION({ "$DATA_ROOT/config.json" }, {
CONFIG_APPLICATION(PathSupplier { "$DATA_ROOT/config.json" }, {
if (!file.exists()) {
file.bufferedWriter(StandardCharsets.UTF_8).use {
GsonConst.appConfigGson.toJson(
@ -141,7 +143,7 @@ internal enum class AppPaths(
}
}
}),
CONFIG_BOT({ "$DATA_ROOT/bot.json" }, {
CONFIG_BOT(PathSupplier { "$DATA_ROOT/bot.json" }, {
if (!file.exists()) {
file.bufferedWriter(StandardCharsets.UTF_8).use {
GsonConst.botConfigGson.toJson(
@ -167,10 +169,25 @@ internal enum class AppPaths(
TEMP({ "$DATA_ROOT/tmp/" })
;
val file: File
get() = fileSupplier.invoke()
val path: String
get() = pathSupplier.invoke()
constructor(pathSupplier: PathSupplier, initializer: AppPaths.() -> Unit = AppPaths::defaultInitializer) : this(
fileSupplier = FileSupplier { File(pathSupplier.path).canonicalFile },
pathSupplier = pathSupplier,
initializer = initializer
)
constructor(fileSupplier: FileSupplier, initializer: AppPaths.() -> Unit = AppPaths::defaultInitializer) : this(
fileSupplier = fileSupplier,
pathSupplier = PathSupplier { fileSupplier.file.canonicalPath },
initializer = initializer
)
constructor(pathSupplier: () -> String) : this(
fileSupplier = FileSupplier { File(pathSupplier.invoke()).canonicalFile },
pathSupplier = PathSupplier { pathSupplier.invoke() }
)
val file: File by fileSupplier
val path: String by pathSupplier
private val initialized = AtomicBoolean(false)
@ -206,6 +223,20 @@ internal enum class AppPaths(
const val ENV_DATA_PATH = "BOT_DATA_PATH"
}
private class FileSupplier(private val supplier: Supplier<File>) {
operator fun getValue(appPaths: AppPaths, property: KProperty<*>): File = supplier.get()
val file: File
get() = supplier.get()
}
private class PathSupplier(private val supplier: Supplier<String>) {
operator fun getValue(appPaths: AppPaths, property: KProperty<*>): String = supplier.get()
val path: String
get() = supplier.get()
}
}
/**

View File

@ -182,7 +182,7 @@ internal class Launcher(private val config: AppConfig = Const.config) : AutoClos
it.url == MavenRepositoryExtensionFinder.MAVEN_CENTRAL_URL
|| it.url == MavenRepositoryExtensionFinder.MAVEN_CENTRAL_URL.trimEnd('/')
}) {
add(MavenRepositoryExtensionFinder.getMavenCentralRepository(proxy = config.proxy.toAetherProxy()))
add(MavenRepositoryExtensionFinder.getMavenCentralRepository(proxy = proxyConfig.toAetherProxy()))
}
}.toList()
val extensionPackageFinders = setOf(

View File

@ -413,14 +413,14 @@ internal class ExtensionClassLoader(urls: Array<URL>, dependencyLoader: ClassLoa
// 以免使用了不来自扩展包的机器人扩展.
override fun getResources(name: String?): Enumeration<URL> {
if (BotExtensionFactory::class.java.equals(name)) {
if ("META-INF/services/${BotExtensionFactory::class.java.name}" == name) {
return findResources(name)
}
return super.getResources(name)
}
override fun getResource(name: String?): URL? {
if (BotExtensionFactory::class.java.equals(name)) {
if ("META-INF/services/${BotExtensionFactory::class.java}" == name) {
return findResource(name)
}
return super.getResource(name)

View File

@ -74,7 +74,7 @@ fun <T : AutoCloseable> T.registerShutdownHook(): T {
private object UtilsInternal {
val autoCloseableSet = mutableSetOf<AutoCloseable>()
private val log = KotlinLogging.logger { }
private val log = KotlinLogging.logger(UtilsInternal::class.java.name)
init {
Runtime.getRuntime().addShutdownHook(Thread(this::doCloseResources, "Shutdown-AutoCloseable"))

View File

@ -24,7 +24,7 @@
<appender name="FILE_OUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${DATA_LOGS}/latest.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>data/logs/%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<fileNamePattern>${DATA_LOGS}/%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>

View File

@ -21,6 +21,17 @@ import kotlin.test.*
internal class AppPathsTest {
@Test
fun `Consistency check`() {
for (path in AppPaths.values()) {
assertEquals(
File(path.path).canonicalPath,
path.file.canonicalPath,
"路径 File 与 Path 不一致: ${path.name}"
)
}
}
@Test
fun `Data root path priority`() {
System.setProperty(AppPaths.PathConst.PROP_DATA_PATH, "fromSystemProperties")

View File

@ -1,6 +1,7 @@
plugins {
kotlin("jvm")
java
jacoco
`maven-publish`
signing
}
@ -29,6 +30,16 @@ java {
tasks.test {
useJUnitPlatform()
finalizedBy(tasks.jacocoTestReport)
}
tasks.jacocoTestReport {
dependsOn(tasks.test)
}
tasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}
publishing {

View File

@ -36,6 +36,11 @@ java {
targetCompatibility = JavaVersion.VERSION_11
}
tasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}
tasks.getByName<Test>("test") {
useJUnitPlatform()
}

View File

@ -28,6 +28,13 @@ data class BotAccount(
/**
* 机器人配置.
*
* 使用 Gson 解析时, 请添加以下类型适配器:
* - [net.lamgc.scalabot.config.serializer.ProxyTypeSerializer]
* - [net.lamgc.scalabot.config.serializer.BotConfigSerializer]
* - [net.lamgc.scalabot.config.serializer.BotAccountSerializer]
* - [net.lamgc.scalabot.config.serializer.ArtifactSerializer]
*
* @property enabled 是否启用机器人.
* @property account 机器人帐号信息, 用于访问 API.
* @property disableBuiltInAbility 是否禁用 AbilityBot 自带命令.
@ -126,6 +133,13 @@ data class MavenRepositoryConfig(
* ScalaBot App 配置.
*
* App 配置信息与 BotConfig 分开, 分别存储在各自单独的文件中.
*
* 使用 Gson 解析时, 请添加以下类型适配器:
* - [net.lamgc.scalabot.config.serializer.ProxyTypeSerializer]
* - [net.lamgc.scalabot.config.serializer.MavenRepositoryConfigSerializer]
* - [net.lamgc.scalabot.config.serializer.AuthenticationSerializer]
* - [net.lamgc.scalabot.config.serializer.UsernameAuthenticatorSerializer]
*
* @property proxy Telegram API 代理配置.
* @property metrics 运行指标数据配置. 可通过时序数据库记录运行数据.
* @property mavenRepositories Maven 远端仓库配置.

View File

@ -219,6 +219,12 @@ internal class ProxyConfigTest {
assertEquals(8080, actualConfig.port)
assertEquals(ProxyType.HTTP, actualConfig.type)
}
@Test
fun `toString test`() {
assertEquals("NO_PROXY", ProxyConfig(ProxyType.NO_PROXY).toString())
assertEquals("HTTP://example.org:1008", ProxyConfig(ProxyType.HTTP, "example.org", 1008).toString())
}
}
internal class MetricsConfigTest {