Skip to content

Commit 276e2d4

Browse files
committed
✨ 基于.metadata的项目自动配置
1 parent 80558ef commit 276e2d4

17 files changed

Lines changed: 264 additions & 146 deletions

File tree

build.gradle.kts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,14 @@ allprojects {
7474
}
7575

7676
dependencies.constraints {
77-
val mindustryVersion = "v2025.10.X21" //v153
77+
val mindustryVersion = "3e787fe01c" //v155.4
7878
api("com.github.TinyLake.MindustryX:core:$mindustryVersion")
7979
val bukkitVersion = "1.21.4-R0.1-SNAPSHOT"
8080
api("dev.folia:folia-api:$bukkitVersion")
8181
}
8282
dependencies {
83-
val libraryVersion = "2.2.0.4"
83+
val libraryVersion = "2.3.2.4"
8484
api("cf.wayzer:ScriptAgent:${libraryVersion}")
85-
implementation("cf.wayzer:LibraryManager:1.6")
8685

8786
"mindustryCompileOnly"("com.github.TinyLake.MindustryX:core")
8887
"bukkitCompileOnly"("dev.folia:folia-api")
@@ -139,7 +138,7 @@ tasks {
139138
)
140139
dependencies {
141140
include(dependency("cf.wayzer:ScriptAgent"))
142-
include(dependency("cf.wayzer:LibraryManager"))
141+
include(dependency("com.github.way-zer:LibraryManager"))
143142
}
144143
doLast {
145144
println(archiveFile.get())

buildSrc/src/Module.kt

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,70 @@
11
@file:Suppress("unused")
22

3+
package buildsrc
4+
35
import org.gradle.api.Project
4-
import org.gradle.api.artifacts.Dependency
5-
import org.gradle.api.artifacts.dsl.DependencyHandler
66
import org.gradle.api.tasks.SourceSet
77
import org.gradle.api.tasks.SourceSetContainer
88
import org.gradle.kotlin.dsl.dependencies
99
import org.gradle.kotlin.dsl.kotlin
1010
import org.gradle.kotlin.dsl.project
1111

1212
@DslMarker
13-
annotation class ModuleBuilderMarker
13+
internal annotation class ModuleBuilderMarker
1414

1515
@ModuleBuilderMarker
16-
class ModuleScope(val moduleId: String, val project: Project, val sourceSet: SourceSet) {
16+
internal class ModuleScope(val moduleId: String, val project: Project, val sourceSet: SourceSet) {
1717
val exposedConfigurationName get() = sourceSet.apiConfigurationName.replace("Api", "Exposed")
1818

19-
fun DependencyHandler.dependsOnModule(module: String, project: Project = this@ModuleScope.project): Dependency? {
19+
fun dependsOnModule(module: String, project: Project = this.project) {
2020
val sourceSet = project.sourceSets.getByName(mapIdToSourceSetName(module))
21-
return dependsOn(ModuleScope(module, project, sourceSet))
21+
dependsOn(ModuleScope(module, project, sourceSet))
2222
}
2323

24-
fun DependencyHandler.dependsOn(module: ModuleScope): Dependency? {
25-
return add(sourceSet.apiConfigurationName, project(module.project.path, module.exposedConfigurationName))
24+
fun dependsOn(module: ModuleScope) {
25+
project.dependencies {
26+
add(
27+
sourceSet.apiConfigurationName,
28+
project(module.project.path, module.exposedConfigurationName)
29+
)
30+
}
2631
}
2732

28-
fun DependencyHandler.api(dep: Any) = add(sourceSet.apiConfigurationName, dep)
29-
fun DependencyHandler.implementation(dep: Any) = add(sourceSet.implementationConfigurationName, dep)
33+
fun api(dep: Any) = project.dependencies.add(sourceSet.apiConfigurationName, dep)
34+
fun implementation(dep: Any) = project.dependencies.add(sourceSet.implementationConfigurationName, dep)
3035
}
3136

3237
private fun mapIdToSourceSetName(id: String) = id.replace('/', '.')
3338

34-
@ModuleBuilderMarker
35-
fun Project.defineModule(
39+
// Child directories with their own `.metadata` become standalone modules,
40+
// so the parent source set should exclude them from compilation.
41+
private fun Project.excludeNestedMetadataModules(moduleId: String, sourceSet: SourceSet) {
42+
val moduleDir = projectDir.resolve(moduleId)
43+
if (!moduleDir.isDirectory) return
44+
45+
moduleDir.walkTopDown()
46+
.filter { it.isDirectory && it != moduleDir && it.resolve(".metadata").isFile }
47+
.forEach { childDir ->
48+
val relative = childDir.relativeTo(moduleDir).invariantSeparatorsPath
49+
sourceSet.java.exclude("$relative/**")
50+
}
51+
}
52+
53+
// Create the backing SourceSet and base configurations for a script module.
54+
internal fun Project.createModule(
3655
name: String,
3756
onlyLibrary: Boolean = false,
38-
body: ModuleScope.() -> Unit,
39-
) {
57+
): ModuleScope {
4058
val sourceSet = sourceSets.create(mapIdToSourceSetName(name)) {
41-
if(!onlyLibrary){
59+
if (!onlyLibrary) {
4260
java.srcDir(name)
4361
java.exclude("lib/**")
4462
}
4563
//独立设置成srcDir,这样可以自定义package,不需要包含lib前缀。
4664
java.srcDir("$name/lib")
4765
}
48-
ModuleScope(name, project, sourceSet).apply {
66+
excludeNestedMetadataModules(name, sourceSet)
67+
return ModuleScope(name, project, sourceSet).apply {
4968
configurations.create(exposedConfigurationName) {
5069
extendsFrom(configurations.getByName(sourceSet.apiConfigurationName))
5170
isCanBeConsumed = true
@@ -56,30 +75,15 @@ fun Project.defineModule(
5675
implementation(rootProject)
5776
add(exposedConfigurationName, sourceSet.output)
5877
}
59-
body()
6078
}
6179
}
6280

63-
@ModuleBuilderMarker
64-
fun ModuleScope.subModule(
65-
name: String,
66-
onlyLibrary: Boolean = false,
67-
body: ModuleScope.() -> Unit,
68-
) {
69-
val parent = this@subModule
70-
val project = parent.project
71-
72-
val id = "${parent.moduleId}/$name"
73-
parent.sourceSet.java.exclude("$name/**")
74-
project.dependencies {
75-
project.defineModule(id, onlyLibrary = onlyLibrary) {
76-
dependsOn(parent)
77-
body()
78-
}
79-
}
81+
// Apply additional module wiring after all dependent SourceSets are available.
82+
internal fun configureModule(scope: ModuleScope, body: ModuleScope.() -> Unit) {
83+
scope.body()
8084
}
8185

8286
private val Project.sourceSets
8387
get() = project.extensions.getByType(
8488
SourceSetContainer::class.java
85-
)
89+
)

buildSrc/src/ModuleParser.kt

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
@file:Suppress("unused")
2+
3+
package buildsrc
4+
5+
import java.io.File
6+
7+
/**
8+
* ScriptAgent Gradle Plugin - 模块信息
9+
*
10+
* 从 metadata 文件中提取的顶层模块配置
11+
*/
12+
internal data class ModuleInfo(
13+
val id: String,
14+
val depends: List<String> = emptyList(),
15+
val apiDeps: List<String> = emptyList(),
16+
val implementationDeps: List<String> = emptyList(),
17+
)
18+
19+
/**
20+
* 解析 ScriptAgent metadata 文件,聚合成顶层模块配置
21+
*
22+
* metadata 格式:
23+
* ```
24+
* ID coreLibrary
25+
* +IMPORT MavenDependsSingle com.example:lib:1.0
26+
* +IMPORT MavenDepends com.example:impl:1.0
27+
* +DEPENDS otherModule
28+
*
29+
* ID coreLibrary/subModule <- 子脚本,依赖与导入会聚合到 coreLibrary
30+
* ...
31+
* ```
32+
*/
33+
private fun parseMetadataFile(file: File, moduleId: String): ModuleInfo? {
34+
if (!file.exists()) return null
35+
36+
val lines = file.readLines()
37+
val depends = linkedSetOf<String>()
38+
val apiDeps = linkedSetOf<String>()
39+
val implementationDeps = linkedSetOf<String>()
40+
41+
for (line in lines) {
42+
val trimmed = line.trim()
43+
when {
44+
trimmed.startsWith("+DEPENDS ") -> {
45+
val dep = trimmed.removePrefix("+DEPENDS ").trim().split(" ").firstOrNull()
46+
if (dep != null) depends.add(dep)
47+
}
48+
trimmed.startsWith("+IMPORT MavenDependsSingle ") -> {
49+
apiDeps.add(trimmed.removePrefix("+IMPORT MavenDependsSingle ").trim())
50+
}
51+
trimmed.startsWith("+IMPORT MavenDepends ") -> {
52+
// metadata 只描述脚本编译依赖,不可靠地区分 Gradle api/implementation。
53+
// 这里统一提升为 api,优先保证 IDE 与脚本编译的可见性。
54+
apiDeps.add(trimmed.removePrefix("+IMPORT MavenDepends ").trim())
55+
}
56+
}
57+
}
58+
59+
return ModuleInfo(moduleId, depends.toList(), apiDeps.toList(), implementationDeps.toList())
60+
}
61+
62+
/**
63+
* 扫描脚本目录下的所有 .metadata 文件。
64+
* `.metadata` 的模块 id 由其所在目录相对路径决定,例如:
65+
* - `scripts/coreLibrary/.metadata` -> `coreLibrary`
66+
* - `scripts/coreLibrary/db/.metadata` -> `coreLibrary/db`
67+
*/
68+
internal fun scanMetadataDir(scriptsDir: File): Map<String, ModuleInfo> {
69+
if (!scriptsDir.exists()) return emptyMap()
70+
71+
return scriptsDir.walkTopDown()
72+
.filter { it.isFile && it.name == ".metadata" }
73+
.mapNotNull { file ->
74+
val moduleId = file.parentFile.relativeTo(scriptsDir).invariantSeparatorsPath
75+
if (moduleId.isBlank()) null else parseMetadataFile(file, moduleId)
76+
}
77+
.associateBy { it.id }
78+
}

buildSrc/src/SAMetadataModules.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package buildsrc
2+
3+
import org.gradle.api.Project
4+
import java.io.File
5+
6+
// Resolve script-level dependencies like `coreLibrary/extApi/KVStore` to the
7+
// nearest folder that actually declares a `.metadata` module.
8+
private fun resolveModuleDependency(dependencyId: String, moduleIds: Set<String>): String? {
9+
var current = dependencyId
10+
while (current.isNotEmpty()) {
11+
if (current in moduleIds) return current
12+
current = current.substringBeforeLast('/', "")
13+
}
14+
return null
15+
}
16+
17+
// Register modules in two phases: create all source sets first, then wire dependencies.
18+
fun Project.defineMetadataModules(scriptsDir: File = projectDir) {
19+
val modules = scanMetadataDir(scriptsDir)
20+
val moduleIds = modules.keys
21+
val scopes = linkedMapOf<String, ModuleScope>()
22+
23+
for (info in modules.values.sortedBy { it.id }) {
24+
scopes[info.id] = createModule(info.id)
25+
}
26+
27+
for (info in modules.values.sortedBy { it.id }) {
28+
val scope = scopes.getValue(info.id)
29+
configureModule(scope) {
30+
for (dep in info.depends.mapNotNull { resolveModuleDependency(it, moduleIds) }.distinct()) {
31+
if (dep == info.id) continue
32+
dependsOnModule(dep)
33+
}
34+
for (dep in info.apiDeps) {
35+
api(dep)
36+
}
37+
for (dep in info.implementationDeps) {
38+
implementation(dep)
39+
}
40+
}
41+
}
42+
}

loader/bukkit/src/Main.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class Main : JavaPlugin(), CommonMain {
2626
Config.pluginMain = this
2727
Config.delayEnable = mutableListOf()
2828

29-
DependencyManager {
29+
DependencyManager.blocking {
3030
require(Dependency.parse("org.jetbrains.kotlin:kotlin-stdlib:${Config.kotlinVersion}"))
3131
require(Dependency.parse("org.jetbrains.kotlin:kotlin-reflect:${Config.kotlinVersion}"))
3232
require(Dependency.parse("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Config.kotlinCoroutineVersion}"))

scripts/bootStrap/.metadata

Whitespace-only changes.

scripts/build.gradle.kts

Lines changed: 4 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,13 @@
1+
import buildsrc.defineMetadataModules
12
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
23

34
plugins {
45
kotlin("jvm")
56
}
6-
dependencies {
7-
defineModule("kcp") {
8-
//kcp/serialization
9-
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
10-
}
11-
defineModule("bootStrap") {}
12-
//不能设置为onlyLibrary,因为有子模块,IDEA会无法正确关联脚本所在模块。
13-
defineModule("coreLibrary"/*, onlyLibrary = true*/) {
14-
api("com.github.way-zer:PlaceHoldLib:v7.3")
15-
api("io.github.config4k:config4k:0.7.0")
16-
api("org.slf4j:slf4j-api:2.0.16")
17-
18-
subModule("db", onlyLibrary = true) {
19-
val exposedVersion = "0.59.0"
20-
api("org.jetbrains.exposed:exposed-core:$exposedVersion")
21-
api("org.jetbrains.exposed:exposed-dao:$exposedVersion")
22-
api("org.jetbrains.exposed:exposed-java-time:$exposedVersion")
23-
}
24-
25-
subModule("extApi", onlyLibrary = true) {
26-
//coreLib/extApi/redisApi
27-
api("redis.clients:jedis:4.3.1")
28-
//coreLib/extApi/mongoApi
29-
api("org.litote.kmongo:kmongo-coroutine:4.8.0")
30-
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
31-
//coreLib/extApi/KVStore
32-
api("com.h2database:h2-mvstore:2.3.232")
33-
}
34-
}
35-
36-
defineModule("javalin") {
37-
dependsOnModule("coreLibrary")
38-
api("io.javalin:javalin:6.7.0")
39-
}
407

41-
defineModule("coreMindustry") {
42-
dependsOnModule("coreLibrary")
43-
api("com.github.TinyLake.MindustryX:core")
44-
//coreMindustry/console
45-
implementation("org.jline:jline-terminal:3.21.0")
46-
implementation("org.jline:jline-reader:3.21.0")
47-
}
48-
defineModule("scratch") {
49-
dependsOnModule("coreLibrary")
50-
}
51-
52-
defineModule("wayzer") {
53-
dependsOnModule("coreMindustry")
54-
dependsOnModule("coreLibrary/extApi")
55-
api("com.google.guava:guava:30.1-jre")
56-
//wayzer/ext/profiler
57-
implementation("tools.profiler:async-profiler:4.1")
58-
subModule("store") {
59-
dependsOnModule("coreLibrary/db")
60-
}
61-
}
62-
defineModule("mapScript") {
63-
dependsOnModule("wayzer")
64-
}
65-
}
8+
defineMetadataModules()
669

10+
// 全局编译器配置
6711
allprojects {
6812
tasks.withType<KotlinCompile>().configureEach {
6913
compilerOptions {
@@ -75,4 +19,4 @@ allprojects {
7519
)
7620
}
7721
}
78-
}
22+
}

0 commit comments

Comments
 (0)