Skip to content

Commit 095ce20

Browse files
committed
feat: add surf-api-processor module with AutoService support
- Introduced `surf-api-processor` module for symbol processing. - Implemented `AutoServiceSymbolProcessor` to handle dependencies and service generation. - Updated `build.gradle.kts` and `settings.gradle.kts` to include the new module. - Removed unused library from `surf-api-gradle-plugin`. - Bumped version to 1.4.1.
1 parent 57888b2 commit 095ce20

File tree

10 files changed

+250
-47
lines changed

10 files changed

+250
-47
lines changed

build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ apiValidation {
3434
"surf-api-bukkit-server",
3535
"surf-api-velocity-server",
3636
"surf-api-standalone",
37-
"surf-api-gradle-plugin"
37+
"surf-api-gradle-plugin",
38+
"surf-api-processor"
3839
)
3940
)
4041

buildSrc/src/main/kotlin/core-convention.gradle.kts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,16 @@ repositories {
2727

2828
dependencies {
2929
compileOnly(libs.auto.service.annotations)
30-
ksp(libs.auto.service)
30+
ksp(project(":surf-api-gradle-plugin:surf-api-processor"))
3131

3232
compileOnlyApi("org.jetbrains:annotations:24.1.0")
3333
}
3434

35+
ksp {
36+
arg("autoserviceKsp.verbose", "true")
37+
arg("autoserviceKsp.verify", "true")
38+
}
39+
3540
extensions.configure<KotlinJvmProjectExtension> {
3641
val javaVersion: String by project
3742

gradle/libs.versions.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ auto-service = "1.1.1"
5858
auto-service-ksp = "1.2.0"
5959
ktor = "3.1.1"
6060
glm = "0.9.9.1-12"
61+
ksp-version = "2.2.0-2.0.2"
6162

6263
# Plugin versions
6364
maven-repo-auth = "3.0.4"
6465
shadow-gradle-plugin = "9.0.0"
65-
ksp-gradle-plugin = "2.2.0-2.0.2"
6666
run-paper-gradle-plugin = "2.3.1"
6767
dokka = "2.0.0"
6868

@@ -159,7 +159,8 @@ kotlin-no-arg = { module = "org.jetbrains.kotlin:kotlin-noarg", version.ref = "k
159159
kotlin-serialization = { module = "org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin", version.ref = "kotlinVersion" }
160160
maven-repo-auth = { module = "org.hibernate.build.maven-repo-auth:org.hibernate.build.maven-repo-auth.gradle.plugin", version.ref = "maven-repo-auth" }
161161
shadow-gradle-plugin = { module = "com.gradleup.shadow:shadow-gradle-plugin", version.ref = "shadow-gradle-plugin" }
162-
ksp-gradle-plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp-gradle-plugin" }
162+
ksp-gradle-plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp-version" }
163+
ksp-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp-version" }
163164
plugin-yml-paper-gradle-plugin = { module = "de.eldoria.plugin-yml.paper:de.eldoria.plugin-yml.paper.gradle.plugin", version.ref = "plugin-yml-paper" }
164165
run-paper-gradle-plugin = { module = "xyz.jpenilla.run-paper:xyz.jpenilla.run-paper.gradle.plugin", version.ref = "run-paper-gradle-plugin" }
165166
dokka-gradle-plugin = { module = "org.jetbrains.dokka:org.jetbrains.dokka.gradle.plugin", version.ref = "dokka" }
@@ -170,7 +171,7 @@ run-paper = { id = "xyz.jpenilla.run-paper", version.ref = "run-paper-gradle-plu
170171
maven-repo-auth = { id = "org.hibernate.build.maven-repo-auth", version.ref = "maven-repo-auth" }
171172
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
172173
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinVersion" }
173-
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp-gradle-plugin" }
174+
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp-version" }
174175

175176
[bundles]
176177
ktor-client = ["ktor-client-core", "ktor-client-okhttp", "ktor-client-content-negotiation", "ktor-serialization-kotlinx-json"]

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ include(":surf-api-velocity:surf-api-velocity-server")
2222

2323
include("surf-api-standalone")
2424
include("surf-api-gradle-plugin")
25+
include("surf-api-gradle-plugin:surf-api-processor")
2526

2627
if (!ci) {
2728
include(":surf-api-bukkit:surf-api-bukkit-plugin-test")

surf-api-gradle-plugin/build.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ plugins {
2020
group = groupId
2121
version = buildString {
2222
append(mcVersion)
23-
append("-1.4.0")
23+
append("-1.4.1")
2424
if (snapshot) append("-SNAPSHOT")
2525
}
2626

@@ -107,7 +107,6 @@ val generateConstants by tasks.registering {
107107
inputs.property("libs.paper.api", libs.paper.api.get().toString())
108108
inputs.property("libs.velocity.api", libs.velocity.api.get().toString())
109109
inputs.property("libs.auto.service.annotations", libs.auto.service.annotations.get().toString())
110-
inputs.property("libs.auto.service", libs.auto.service.asProvider().get().toString())
111110
inputs.property("libs.versions.commandapi", libs.versions.commandapi.get().toString())
112111
inputs.property("libs.versions.placeholder.api", libs.versions.placeholder.api.get().toString())
113112
inputs.property("libs.versions.luckperms", libs.versions.luckperms.get().toString())
@@ -122,6 +121,7 @@ val generateConstants by tasks.registering {
122121
outputs.dir(constantsOutputDir)
123122

124123
doLast {
124+
val generator = project(":surf-api-gradle-plugin:surf-api-processor")
125125
val content = """
126126
|package dev.slne.surf.surfapi.gradle.generated
127127
|
@@ -132,7 +132,7 @@ val generateConstants by tasks.registering {
132132
| const val PAPER_API = "${libs.paper.api.get()}"
133133
| const val VELOCITY_API = "${libs.velocity.api.get()}"
134134
| const val AUTO_SERVICE_ANNOTATIONS = "${libs.auto.service.annotations.get()}"
135-
| const val AUTO_SERVICE = "${libs.auto.service.asProvider().get()}"
135+
| const val AUTO_SERVICE = "${generator.group}:${generator.name}:${generator.version}"
136136
|
137137
| const val JAVA_VERSION = $javaVersion
138138
| const val MINECRAFT_VERSION = "$mcVersion"

surf-api-gradle-plugin/src/main/kotlin/dev/slne/surf/surfapi/gradle/platform/common/CommonSurfPlugin.kt

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -123,51 +123,32 @@ abstract class CommonSurfPlugin<E : CommonSurfExtension>(
123123
}
124124
}
125125

126-
project.configurations.configureEach {
127-
if (isCanBeResolved) {
128-
incoming.afterResolve {
129-
val deps = resolutionResult.allDependencies
130-
.map { it.requested.displayName }
131-
.toSet()
132-
133-
dependencyDependentRelocations.forEach { (needle, relocations) ->
134-
if (deps.any { it.contains(needle) }) {
135-
logger.lifecycle("Dependency $needle found. Applying relocations.")
136-
project.tasks.withType<ShadowJar>().configureEach {
137-
relocations.forEach { (from, to) ->
138-
logger.lifecycle("Relocating $from to $to")
139-
relocate(from, to)
140-
}
141-
}
126+
tasks.withType<ShadowJar>().configureEach {
127+
val depsProvider = project.provider {
128+
project.configurations
129+
.asSequence()
130+
.filter { it.isCanBeResolved }
131+
.flatMap { cfg ->
132+
cfg.incoming.resolutionResult.allDependencies.asSequence()
133+
}
134+
.map { it.requested.displayName }
135+
.toSet()
136+
}
137+
138+
doFirst {
139+
val deps = depsProvider.get()
140+
dependencyDependentRelocations.forEach { (needle, relos) ->
141+
if (deps.any { it.contains(needle) }) {
142+
logger.lifecycle("Dependency $needle found — applying relocations.")
143+
relos.forEach { (from, to) ->
144+
logger.lifecycle("Relocating $from to $to")
145+
relocate(from, to)
142146
}
143147
}
144148
}
145149
}
146150
}
147151

148-
149-
// gradle.projectsEvaluated {
150-
// tasks.withType<ShadowJar> {
151-
// val deps = project.configurations
152-
// .filter { it.isCanBeResolved }
153-
// .map { it.incoming.resolutionResult.allDependencies }
154-
// .flatten()
155-
// .map { it.requested.displayName }
156-
// .distinct()
157-
// .toList()
158-
//
159-
// dependencyDependentRelocations.forEach { (dependency, relocations) ->
160-
// if (deps.any { it.contains(dependency) }) {
161-
// logger.warn("Dependency $dependency found. Applying relocations.")
162-
// relocations.forEach { (from, to) ->
163-
// logger.warn("Relocating $from to $to")
164-
// relocate(from, to)
165-
// }
166-
// }
167-
// }
168-
// }
169-
// }
170-
171152
configure<JavaPluginExtension> {
172153
withSourcesJar()
173154
withJavadocJar()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// region properties
2+
val mcVersion: String by project
3+
val groupId = findProperty("group") as String
4+
val snapshot = (findProperty("snapshot") as String).toBooleanStrict()
5+
// endregion
6+
7+
plugins {
8+
kotlin("jvm")
9+
}
10+
11+
group = groupId
12+
version = buildString {
13+
append(mcVersion)
14+
append("-1.0.0")
15+
if (snapshot) append("-SNAPSHOT")
16+
}
17+
18+
dependencies {
19+
implementation(libs.ksp.api)
20+
implementation(libs.auto.service.annotations)
21+
22+
// https://mvnrepository.com/artifact/com.squareup/kotlinpoet
23+
implementation("com.squareup:kotlinpoet:2.2.0")
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package dev.slne.surf.surfapi.processor.autoservice
2+
3+
import com.google.auto.service.AutoService
4+
import com.google.devtools.ksp.closestClassDeclaration
5+
import com.google.devtools.ksp.getAllSuperTypes
6+
import com.google.devtools.ksp.isLocal
7+
import com.google.devtools.ksp.processing.Dependencies
8+
import com.google.devtools.ksp.processing.Resolver
9+
import com.google.devtools.ksp.processing.SymbolProcessor
10+
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
11+
import com.google.devtools.ksp.symbol.KSAnnotated
12+
import com.google.devtools.ksp.symbol.KSClassDeclaration
13+
import com.google.devtools.ksp.symbol.KSFile
14+
import com.google.devtools.ksp.symbol.KSType
15+
import com.squareup.kotlinpoet.ClassName
16+
import java.io.IOException
17+
18+
class AutoServiceSymbolProcessor(environment: SymbolProcessorEnvironment) : SymbolProcessor {
19+
20+
companion object {
21+
private val SERVICE_ANNOTATION = AutoService::class.java.name
22+
}
23+
24+
private val logger = environment.logger
25+
private val codeGenerator = environment.codeGenerator
26+
27+
// Map: serviceFqName -> set of (impl binary name, source file)
28+
private val providers = mutableMapOf<String, MutableSet<Pair<String, KSFile?>>>()
29+
30+
private val verify = environment.options["autoserviceKsp.verify"]?.toBoolean() == true
31+
private val verbose = environment.options["autoserviceKsp.verbose"]?.toBoolean() == true
32+
33+
34+
override fun process(resolver: Resolver): List<KSAnnotated> {
35+
val deferred = mutableListOf<KSAnnotated>()
36+
37+
resolver.getSymbolsWithAnnotation(SERVICE_ANNOTATION)
38+
.filterIsInstance<KSClassDeclaration>()
39+
.forEach { implementer ->
40+
val annotation = implementer.annotations.firstOrNull {
41+
it.annotationType.resolve().declaration.qualifiedName?.asString() == SERVICE_ANNOTATION
42+
} ?: run {
43+
logger.error("@AutoService annotation not found on element", implementer)
44+
return@forEach
45+
}
46+
47+
val argValue =
48+
annotation.arguments.firstOrNull { it.name?.asString() == "value" }?.value
49+
val providerTypes: List<KSType> = when (argValue) {
50+
is List<*> -> argValue.filterIsInstance<KSType>()
51+
is KSType -> listOf(argValue)
52+
null -> emptyList()
53+
else -> emptyList()
54+
}
55+
56+
if (providerTypes.isEmpty()) {
57+
logger.error(
58+
"No service interfaces specified in @AutoService. " +
59+
"Use @AutoService(YourService::class).",
60+
annotation
61+
)
62+
}
63+
64+
for (providerType in providerTypes) {
65+
if (providerType.isError) {
66+
deferred += implementer
67+
continue
68+
}
69+
70+
val providerDecl = providerType.declaration.closestClassDeclaration()
71+
if (providerDecl == null) {
72+
deferred += implementer
73+
continue
74+
}
75+
76+
when (checkImplementer(implementer, providerType)) {
77+
ValidationResult.VALID -> {
78+
val key = providerDecl.toBinaryName()
79+
val impl = implementer.toBinaryName()
80+
providers.getOrPut(key) { mutableSetOf() }
81+
.add(impl to implementer.containingFile)
82+
}
83+
84+
ValidationResult.INVALID -> {
85+
logger.error(
86+
"Service providers must implement their service interface. " +
87+
"${implementer.qualifiedName?.asString()} does not implement " +
88+
providerDecl.qualifiedName?.asString(),
89+
implementer
90+
)
91+
}
92+
93+
ValidationResult.DEFERRED -> {
94+
deferred += implementer
95+
}
96+
}
97+
}
98+
}
99+
100+
return deferred
101+
}
102+
103+
override fun finish() {
104+
generateAndClearConfigFiles()
105+
}
106+
107+
private fun generateAndClearConfigFiles() {
108+
for ((serviceFqName, impls) in providers) {
109+
val resourcePath = "META-INF/services/$serviceFqName"
110+
log("Working on resource file: $resourcePath")
111+
112+
try {
113+
val allServices = impls.asSequence().map { it.first }.toSortedSet()
114+
log("New service file contents: $allServices")
115+
116+
val ksFiles = impls.mapNotNull { it.second }
117+
log("Originating files: ${ksFiles.map(KSFile::fileName)}")
118+
119+
val deps = if (ksFiles.isEmpty()) {
120+
Dependencies(aggregating = true)
121+
} else {
122+
Dependencies(aggregating = true, sources = ksFiles.toTypedArray())
123+
}
124+
125+
codeGenerator.createNewFileByPath(deps, resourcePath, "").bufferedWriter()
126+
.use { writer ->
127+
for (service in allServices) {
128+
writer.write(service)
129+
writer.newLine()
130+
}
131+
}
132+
133+
log("Wrote to: $resourcePath")
134+
} catch (e: IOException) {
135+
logger.error("Unable to create $resourcePath, $e")
136+
}
137+
}
138+
}
139+
140+
private fun log(message: String) {
141+
if (verbose) {
142+
logger.info(message)
143+
}
144+
}
145+
146+
147+
private fun KSClassDeclaration.toClassName(): ClassName {
148+
require(!isLocal()) { "Local/anonymous classes are not supported!" }
149+
val pkg = packageName.asString()
150+
val typesString = qualifiedName!!.asString().removePrefix("$pkg.")
151+
val simpleNames = typesString.split(".")
152+
return ClassName(pkg, simpleNames)
153+
}
154+
155+
private fun KSClassDeclaration.toBinaryName(): String = toClassName().reflectionName()
156+
157+
private fun checkImplementer(
158+
implementer: KSClassDeclaration,
159+
providerType: KSType,
160+
): ValidationResult {
161+
if (!verify) return ValidationResult.VALID
162+
163+
for (superType in implementer.getAllSuperTypes()) {
164+
if (superType.isError) return ValidationResult.DEFERRED
165+
// Accept equal types or assignable in either direction
166+
if (superType == providerType ||
167+
superType.isAssignableFrom(providerType) ||
168+
providerType.isAssignableFrom(superType)
169+
) {
170+
return ValidationResult.VALID
171+
}
172+
}
173+
174+
return ValidationResult.INVALID
175+
}
176+
177+
private enum class ValidationResult { VALID, INVALID, DEFERRED }
178+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dev.slne.surf.surfapi.processor.autoservice
2+
3+
import com.google.devtools.ksp.processing.SymbolProcessor
4+
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
5+
import com.google.devtools.ksp.processing.SymbolProcessorProvider
6+
7+
class AutoServiceSymbolProcessorProvider: SymbolProcessorProvider {
8+
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
9+
return AutoServiceSymbolProcessor(environment)
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dev.slne.surf.surfapi.processor.autoservice.AutoServiceSymbolProcessorProvider

0 commit comments

Comments
 (0)