Skip to content

Commit 99d4c41

Browse files
authored
Implement configuration migration system and update ABI checks (#270)
2 parents e33908a + f2f0513 commit 99d4c41

14 files changed

Lines changed: 605 additions & 66 deletions

File tree

.github/workflows/api-dump-version.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
id: check_api
3434
run: |
3535
set +e
36-
./gradlew checkLegacyAbi
36+
./gradlew checkKotlinAbi
3737
CHECK_EXIT=$?
3838
echo "check_exit=$CHECK_EXIT" >> $GITHUB_OUTPUT
3939
set -e
@@ -46,7 +46,7 @@ jobs:
4646
echo "✅ No API changes detected."
4747
else
4848
echo "api_changed=true" >> $GITHUB_OUTPUT
49-
echo "❌ API changes detected! To update the reference, run './gradlew updateLegacyAbi' locally and commit the changes." >&2
49+
echo "❌ API changes detected! To update the reference, run './gradlew updateKotlinAbi' locally and commit the changes." >&2
5050
fi
5151
5252
- name: Comment on PR if API changed
@@ -61,7 +61,7 @@ jobs:
6161
This PR contains changes that modified the public API. To update the reference ABI dumps:
6262
6363
```bash
64-
./gradlew updateLegacyAbi
64+
./gradlew updateKotlinAbi
6565
git add **/api/**
6666
git commit -m "Update ABI reference"
6767
git push

.github/workflows/publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
run: ./gradlew build shadowJar --parallel --no-scan
4141

4242
- name: Check all modules with Gradle
43-
run: ./gradlew check checkLegacyAbi --parallel --no-scan
43+
run: ./gradlew check checkKotlinAbi --parallel --no-scan
4444

4545
- name: Publish all modules to Maven
4646
run: ./gradlew publish --parallel --no-scan

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
77
javaVersion=25
88
mcVersion=1.21.11
99
group=dev.slne.surf
10-
version=1.21.11-2.71.2
10+
version=1.21.11-2.72.0
1111
relocationPrefix=dev.slne.surf.surfapi.libs
1212
snapshot=false

surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/BukkitPluginMain.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import dev.slne.surf.surfapi.bukkit.test.command.subcommands.inventory.TestInven
1111
import dev.slne.surf.surfapi.bukkit.test.command.subcommands.inventory.testInventoryViewDsl
1212
import dev.slne.surf.surfapi.bukkit.test.command.subcommands.reflection.Reflection
1313
import dev.slne.surf.surfapi.bukkit.test.config.ModernTestConfig
14+
import dev.slne.surf.surfapi.bukkit.test.config.MyPluginConfig
1415
import dev.slne.surf.surfapi.bukkit.test.listener.ChatListener
1516
import dev.slne.surf.surfapi.core.api.component.surfComponentApi
1617

@@ -31,6 +32,8 @@ class BukkitPluginMain : SuspendingJavaPlugin() {
3132
dialogTestCommand()
3233
Reflection::class.java.getClassLoader() // initialize Reflection
3334

35+
MyPluginConfig.init()
36+
3437
surfComponentApi.enable(this)
3538
}
3639

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package dev.slne.surf.surfapi.bukkit.test.config
2+
3+
import dev.slne.surf.surfapi.bukkit.test.plugin
4+
import dev.slne.surf.surfapi.core.api.config.SpongeYmlConfigClass
5+
import dev.slne.surf.surfapi.core.api.config.migration.ConfigMigration
6+
import org.spongepowered.configurate.ConfigurationNode
7+
import org.spongepowered.configurate.objectmapping.ConfigSerializable
8+
9+
/**
10+
* Example config class with migrations.
11+
* Before migration:
12+
* ```yaml
13+
* server:
14+
* version: "1-20-4"
15+
* deprecated-field: "please-remove-me"
16+
* max-players: 0
17+
* ```
18+
*/
19+
@ConfigSerializable
20+
data class MyPluginConfig(
21+
var release: String = "1.0.0",
22+
var maxPlayers: Int = 100
23+
) {
24+
companion object : SpongeYmlConfigClass<MyPluginConfig>(
25+
MyPluginConfig::class.java,
26+
plugin.dataPath,
27+
"migration-example-config.yml"
28+
) {
29+
init {
30+
migration(1, RenameServerVersionMigration)
31+
migration(2, RemoveDeprecatedFieldMigration)
32+
migration(3) { node ->
33+
// inline migration: rename maxPlayers default
34+
val mp = node.node("max-players")
35+
if (!mp.virtual() && mp.getInt(0) == 0) {
36+
mp.set(100)
37+
}
38+
}
39+
}
40+
}
41+
}
42+
43+
object RenameServerVersionMigration : ConfigMigration {
44+
override fun migrate(node: ConfigurationNode) {
45+
val old = node.node("server", "version")
46+
if (!old.virtual()) {
47+
node.node("server", "release").set(old.raw())
48+
old.raw(null)
49+
}
50+
}
51+
}
52+
53+
object RemoveDeprecatedFieldMigration : ConfigMigration {
54+
override fun migrate(node: ConfigurationNode) {
55+
node.node("deprecated-field").raw(null)
56+
}
57+
}

surf-api-core/surf-api-core-api/api/surf-api-core-api.api

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,13 @@ public abstract class dev/slne/surf/surfapi/core/api/config/SpongeConfigClass {
152152
protected final fun getConfigFolder ()Ljava/nio/file/Path;
153153
protected final fun getFileName ()Ljava/lang/String;
154154
public abstract fun getManager ()Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
155+
protected final fun getMigrationBuilder ()Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder;
155156
public final fun init ()V
157+
protected final fun migration (ILdev/slne/surf/surfapi/core/api/config/migration/ConfigMigration;)V
158+
protected final fun migration (ILkotlin/jvm/functions/Function1;)V
156159
public final fun reloadFromFile ()Ljava/lang/Object;
157160
public final fun save ()V
161+
protected final fun versionKey ([Ljava/lang/Object;)V
158162
}
159163

160164
public abstract class dev/slne/surf/surfapi/core/api/config/SpongeJsonConfigClass : dev/slne/surf/surfapi/core/api/config/SpongeConfigClass {
@@ -171,9 +175,11 @@ public abstract interface class dev/slne/surf/surfapi/core/api/config/SurfConfig
171175
public static final field Companion Ldev/slne/surf/surfapi/core/api/config/SurfConfigApi$Companion;
172176
public abstract fun createDazzlConfig (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ljava/lang/Object;
173177
public abstract fun createSpongeJsonConfig (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ljava/lang/Object;
174-
public abstract fun createSpongeJsonConfigManager (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
178+
public fun createSpongeJsonConfigManager (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
179+
public abstract fun createSpongeJsonConfigManager (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
175180
public abstract fun createSpongeYmlConfig (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ljava/lang/Object;
176-
public abstract fun createSpongeYmlConfigManager (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
181+
public fun createSpongeYmlConfigManager (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
182+
public abstract fun createSpongeYmlConfigManager (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
177183
public abstract fun getDazzlConfig (Ljava/lang/Class;)Ljava/lang/Object;
178184
public abstract fun getSpongeConfig (Ljava/lang/Class;)Ljava/lang/Object;
179185
public abstract fun getSpongeConfigManagerForConfig (Ljava/lang/Class;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
@@ -185,8 +191,10 @@ public final class dev/slne/surf/surfapi/core/api/config/SurfConfigApi$Companion
185191
public fun createDazzlConfig (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ljava/lang/Object;
186192
public fun createSpongeJsonConfig (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ljava/lang/Object;
187193
public fun createSpongeJsonConfigManager (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
194+
public fun createSpongeJsonConfigManager (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
188195
public fun createSpongeYmlConfig (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ljava/lang/Object;
189196
public fun createSpongeYmlConfigManager (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
197+
public fun createSpongeYmlConfigManager (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
190198
public fun getDazzlConfig (Ljava/lang/Class;)Ljava/lang/Object;
191199
public final fun getInstance ()Ldev/slne/surf/surfapi/core/api/config/SurfConfigApi;
192200
public fun getSpongeConfig (Ljava/lang/Class;)Ljava/lang/Object;
@@ -195,7 +203,14 @@ public final class dev/slne/surf/surfapi/core/api/config/SurfConfigApi$Companion
195203
public fun reloadSpongeConfig (Ljava/lang/Class;)Ljava/lang/Object;
196204
}
197205

206+
public final class dev/slne/surf/surfapi/core/api/config/SurfConfigApi$DefaultImpls {
207+
public static fun createSpongeJsonConfigManager (Ldev/slne/surf/surfapi/core/api/config/SurfConfigApi;Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
208+
public static fun createSpongeYmlConfigManager (Ldev/slne/surf/surfapi/core/api/config/SurfConfigApi;Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
209+
}
210+
198211
public final class dev/slne/surf/surfapi/core/api/config/SurfConfigApiKt {
212+
public static final synthetic fun createSpongeJsonConfigManager (Ldev/slne/surf/surfapi/core/api/config/SurfConfigApi;Ljava/nio/file/Path;Ljava/lang/String;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
213+
public static final synthetic fun createSpongeYmlConfigManager (Ldev/slne/surf/surfapi/core/api/config/SurfConfigApi;Ljava/nio/file/Path;Ljava/lang/String;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
199214
public static final fun getSurfConfigApi ()Ldev/slne/surf/surfapi/core/api/config/SurfConfigApi;
200215
}
201216

@@ -243,16 +258,58 @@ public final class dev/slne/surf/surfapi/core/api/config/manager/SerializationCo
243258
public final class dev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager {
244259
public static final field Companion Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager$Companion;
245260
public field config Ljava/lang/Object;
246-
public synthetic fun <init> (Ljava/lang/Class;Ljava/lang/Object;Lorg/spongepowered/configurate/loader/ConfigurationLoader;Lorg/spongepowered/configurate/ConfigurationNode;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
261+
public synthetic fun <init> (Ljava/lang/Class;Ljava/lang/Object;Lorg/spongepowered/configurate/loader/ConfigurationLoader;Lorg/spongepowered/configurate/ConfigurationNode;Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
262+
public final fun addMigration (ILdev/slne/surf/surfapi/core/api/config/migration/ConfigMigration;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
263+
public final fun addMigration (ILkotlin/jvm/functions/Function1;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
247264
public final fun edit (ZLkotlin/jvm/functions/Function1;)V
248265
public static synthetic fun edit$default (Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
266+
public final fun migrations ()Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder;
249267
public final fun reloadFromFile ()Ljava/lang/Object;
250268
public final fun save ()V
251269
}
252270

253271
public final class dev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager$Companion {
254272
public final fun json (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
273+
public final fun json (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
274+
public static synthetic fun json$default (Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager$Companion;Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder;ILjava/lang/Object;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
255275
public final fun yaml (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
276+
public final fun yaml (Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
277+
public static synthetic fun yaml$default (Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager$Companion;Ljava/lang/Class;Ljava/nio/file/Path;Ljava/lang/String;Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder;ILjava/lang/Object;)Ldev/slne/surf/surfapi/core/api/config/manager/SpongeConfigManager;
278+
}
279+
280+
public abstract interface class dev/slne/surf/surfapi/core/api/config/migration/ConfigMigration {
281+
public abstract fun migrate (Lorg/spongepowered/configurate/ConfigurationNode;)V
282+
}
283+
284+
public final class dev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder {
285+
public static final field Companion Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder$Companion;
286+
public static final field DEFAULT_VERSION_KEY Ljava/lang/String;
287+
public fun <init> ()V
288+
public final fun buildTransformation ()Lorg/spongepowered/configurate/transformation/ConfigurationTransformation$Versioned;
289+
public final fun hasMigrations ()Z
290+
public final fun latestVersion ()I
291+
public final fun migrate (Lorg/spongepowered/configurate/ConfigurationNode;)Ldev/slne/surf/surfapi/core/api/config/migration/MigrationResult;
292+
public final fun migration (ILdev/slne/surf/surfapi/core/api/config/migration/ConfigMigration;)Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder;
293+
public final fun migration (ILkotlin/jvm/functions/Function1;)Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder;
294+
public final fun versionKey ([Ljava/lang/Object;)Ldev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder;
295+
}
296+
297+
public final class dev/slne/surf/surfapi/core/api/config/migration/ConfigMigrationBuilder$Companion {
298+
}
299+
300+
public final class dev/slne/surf/surfapi/core/api/config/migration/MigrationResult {
301+
public fun <init> (IIZ)V
302+
public final fun component1 ()I
303+
public final fun component2 ()I
304+
public final fun component3 ()Z
305+
public final fun copy (IIZ)Ldev/slne/surf/surfapi/core/api/config/migration/MigrationResult;
306+
public static synthetic fun copy$default (Ldev/slne/surf/surfapi/core/api/config/migration/MigrationResult;IIZILjava/lang/Object;)Ldev/slne/surf/surfapi/core/api/config/migration/MigrationResult;
307+
public fun equals (Ljava/lang/Object;)Z
308+
public final fun getFromVersion ()I
309+
public final fun getMigrated ()Z
310+
public final fun getToVersion ()I
311+
public fun hashCode ()I
312+
public fun toString ()Ljava/lang/String;
256313
}
257314

258315
public final class dev/slne/surf/surfapi/core/api/config/serializer/DefaultDazzlConfSerializers {

surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/config/SpongeConfigClass.kt

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package dev.slne.surf.surfapi.core.api.config
22

33
import dev.slne.surf.surfapi.core.api.config.manager.SpongeConfigManager
4+
import dev.slne.surf.surfapi.core.api.config.migration.ConfigMigration
5+
import dev.slne.surf.surfapi.core.api.config.migration.ConfigMigrationBuilder
6+
import org.spongepowered.configurate.ConfigurationNode
47
import java.nio.file.Path
58

69
/**
@@ -12,10 +15,36 @@ import java.nio.file.Path
1215
* - persist changes via [save]
1316
* - reload the config from disk via [reloadFromFile]
1417
* - perform in-place mutations via [edit]
18+
* - register schema migrations via [migration]
1519
*
1620
* The actual manager instance is provided by subclasses and typically created by
1721
* a central configuration API (e.g. [surfConfigApi]).
1822
*
23+
* ## Versioned Migrations
24+
*
25+
* Migrations can be registered in the `init` block of companion objects:
26+
*
27+
* ```kotlin
28+
* @ConfigSerializable
29+
* data class MyConfig(
30+
* var release: String = "1.0.0"
31+
* ) {
32+
* companion object : SpongeYmlConfigClass<MyConfig>(
33+
* MyConfig::class.java,
34+
* Path("config/my-plugin"),
35+
* "my-config.yml"
36+
* ) {
37+
* init {
38+
* migration(1, RenameServerVersionMigration)
39+
* migration(2) { node ->
40+
* // inline migration
41+
* node.node("old-field").raw(null)
42+
* }
43+
* }
44+
* }
45+
* }
46+
* ```
47+
*
1948
* @param C the type of the configuration data object.
2049
* @param configClass the Java class of [C], used by underlying config frameworks
2150
* to create and map configuration instances.
@@ -35,12 +64,63 @@ sealed class SpongeConfigClass<C>(
3564
protected val fileName: String
3665
) {
3766

67+
/**
68+
* The migration builder that collects all registered migrations.
69+
*
70+
* Subclasses should use [migration] to register migrations rather than
71+
* accessing this directly.
72+
*/
73+
protected val migrationBuilder = ConfigMigrationBuilder()
74+
3875
/**
3976
* The underlying configuration manager responsible for loading, saving,
4077
* and tracking the config instance of type [C].
4178
*/
4279
abstract val manager: SpongeConfigManager<C>
4380

81+
/**
82+
* Registers a migration for the given target [version].
83+
*
84+
* Migrations are applied in version order. For existing configs without a
85+
* version field, **all** registered migrations will be applied the first time.
86+
*
87+
* @param version the target version this migration upgrades to (must be >= 0)
88+
* @param migration the migration to apply
89+
*/
90+
protected fun migration(version: Int, migration: ConfigMigration) {
91+
migrationBuilder.migration(version, migration)
92+
}
93+
94+
/**
95+
* Registers an inline migration for the given target [version].
96+
*
97+
* ```kotlin
98+
* init {
99+
* migration(1) { node ->
100+
* node.node("old-key").raw(null)
101+
* }
102+
* }
103+
* ```
104+
*
105+
* @param version the target version this migration upgrades to (must be >= 0)
106+
* @param migration the migration lambda
107+
*/
108+
protected inline fun migration(version: Int, crossinline migration: (ConfigurationNode) -> Unit) {
109+
migrationBuilder.migration(version, ConfigMigration { node -> migration(node) })
110+
}
111+
112+
/**
113+
* Sets the path in the config file where the version number is stored.
114+
*
115+
* Defaults to `"config-version"`. Call this in the `init` block before
116+
* any migration registrations if you need a custom key.
117+
*
118+
* @param path the path components to the version key
119+
*/
120+
protected fun versionKey(vararg path: Any) {
121+
migrationBuilder.versionKey(*path)
122+
}
123+
44124
/**
45125
* Persists the current configuration to disk.
46126
*
@@ -92,5 +172,7 @@ sealed class SpongeConfigClass<C>(
92172
*
93173
* This method is a no-op and can be safely called multiple times.
94174
*/
95-
fun init() = Unit
175+
fun init() {
176+
manager
177+
}
96178
}

0 commit comments

Comments
 (0)