diff --git a/build.gradle.kts b/build.gradle.kts index 7fafccc96c9d1f633b4bb0ae9ac07b84eadcc094..8051e2f1bc9038c79da7efee2c12b087a1f94ded 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,7 @@ allprojects { apply(plugin = "org.jetbrains.dokka") repositories { mavenCentral() + google() exclusiveContent { forRepository { maven { diff --git a/libquassel-generator/build.gradle.kts b/libquassel-generator/build.gradle.kts new file mode 100644 index 0000000000000000000000000000000000000000..cc08d9d541c544b247c042b07c10199763b517dc --- /dev/null +++ b/libquassel-generator/build.gradle.kts @@ -0,0 +1,19 @@ +/* + * libquassel + * Copyright (c) 2021 Janne Mareike Koschinski + * Copyright (c) 2021 The Quassel Project + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +repositories { + google() +} + +dependencies { + implementation("com.google.devtools.ksp:symbol-processing-api:1.4.30-1.0.0-alpha02") + implementation(project(":libquassel-annotations")) + implementation("com.squareup", "kotlinpoet", "1.7.2") +} diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerProcessor.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerProcessor.kt new file mode 100644 index 0000000000000000000000000000000000000000..9e3c1a2fa456c9f18cc529b96f02301b749cb325 --- /dev/null +++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerProcessor.kt @@ -0,0 +1,158 @@ +/* + * libquassel + * Copyright (c) 2021 Janne Mareike Koschinski + * Copyright (c) 2021 The Quassel Project + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.generator + +import com.google.devtools.ksp.getDeclaredFunctions +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSType +import com.google.devtools.ksp.symbol.KSValueParameter +import com.google.devtools.ksp.symbol.KSVisitorVoid +import de.justjanne.libquassel.annotations.ProtocolSide +import de.justjanne.libquassel.annotations.SyncedCall +import de.justjanne.libquassel.annotations.SyncedObject +import de.justjanne.libquassel.generator.annotation.SyncedCallAnnotationModel +import de.justjanne.libquassel.generator.annotation.SyncedObjectAnnotationModel +import de.justjanne.libquassel.generator.model.SyncedCallModel +import de.justjanne.libquassel.generator.model.SyncedObjectModel +import de.justjanne.libquassel.generator.model.SyncedParameterModel +import de.justjanne.libquassel.generator.util.findAnnotationWithType +import de.justjanne.libquassel.generator.util.getMember +import de.justjanne.libquassel.generator.util.toEnum + +class InvokerProcessor : SymbolProcessor { + lateinit var codeGenerator: CodeGenerator + lateinit var logger: KSPLogger + + override fun init( + options: Map<String, String>, + kotlinVersion: KotlinVersion, + codeGenerator: CodeGenerator, + logger: KSPLogger + ) { + this.logger = logger + this.codeGenerator = codeGenerator + } + + override fun process(resolver: Resolver): List<KSAnnotated> { + val classes = resolver.getSymbolsWithAnnotation(SyncedObject::class.java.canonicalName) + .flatMap { + val visitor = QuasselRpcVisitor(resolver) + it.accept(visitor, Unit) + visitor.classes + } + + val message = classes.flatMap { + listOf( + "@SyncedObject(name=${it.rpcName})", + it.name, + ) + it.methods.flatMap { + listOf( + "@SyncedCall(name=${it.rpcName}, target=${it.side})", + it.name, + ) + it.parameters.flatMap { + listOf( + "${it.name}: ${it.type}" + ) + }.map { " $it" } + }.map { " $it" } + }.joinToString("\n") + logger.logging("Data: \n\n$message\n\n") + + return emptyList() + } + + override fun finish() { + // TODO + } + + inner class QuasselRpcVisitor( + private val resolver: Resolver + ) : KSVisitorVoid() { + val classes = mutableListOf<SyncedObjectModel>() + var functions = mutableListOf<SyncedCallModel>() + var parameters = mutableListOf<SyncedParameterModel>() + + private fun collectFunctions(): List<SyncedCallModel> { + val result = functions + functions = mutableListOf() + return result + } + + private fun collectParameters(): List<SyncedParameterModel> { + val result = parameters + parameters = mutableListOf() + return result + } + + override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) { + try { + val annotation = classDeclaration.findAnnotationWithType<SyncedObject>(resolver) ?: return + val syncedObjectModel = SyncedObjectAnnotationModel( + name = annotation.getMember("name"), + ) + classDeclaration.getDeclaredFunctions().map { it.accept(this, Unit) } + classes.add( + SyncedObjectModel( + classDeclaration.qualifiedName?.asString() ?: return, + syncedObjectModel.name, + collectFunctions() + ) + ) + } catch (t: Throwable) { + logger.error("Error processing class ${classDeclaration.qualifiedName?.asString()}", classDeclaration) + logger.exception(t) + } + } + + override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) { + val annotation = function.findAnnotationWithType<SyncedCall>(resolver) ?: return + try { + val syncedCallModel = SyncedCallAnnotationModel( + name = annotation.getMember("name"), + target = annotation.getMember<KSType>("target")?.toEnum<ProtocolSide>(), + ) + function.parameters.map { it.accept(this, Unit) } + functions.add( + SyncedCallModel( + function.simpleName.asString(), + syncedCallModel.name, + syncedCallModel.target, + collectParameters() + ) + ) + } catch (t: Throwable) { + logger.error("Error processing function ${function.qualifiedName?.asString()}", function) + logger.exception(t) + } + } + + override fun visitValueParameter(valueParameter: KSValueParameter, data: Unit) { + try { + super.visitValueParameter(valueParameter, data) + parameters.add( + SyncedParameterModel( + valueParameter.name?.asString(), + valueParameter.type.resolve() + ) + ) + } catch (t: Throwable) { + logger.error("Error processing parameter ${valueParameter.name?.asString()}", valueParameter) + logger.exception(t) + } + } + } +} diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedCallAnnotationModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedCallAnnotationModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..536fb79c944fd9382e8ec620fd83cc3f69569de0 --- /dev/null +++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedCallAnnotationModel.kt @@ -0,0 +1,18 @@ +/* + * libquassel + * Copyright (c) 2021 Janne Mareike Koschinski + * Copyright (c) 2021 The Quassel Project + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.generator.annotation + +import de.justjanne.libquassel.annotations.ProtocolSide + +data class SyncedCallAnnotationModel( + val name: String?, + val target: ProtocolSide? +) diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedObjectAnnotationModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedObjectAnnotationModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..d2ce55a4212257f27b51efed76ed27e303fc4992 --- /dev/null +++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedObjectAnnotationModel.kt @@ -0,0 +1,15 @@ +/* + * libquassel + * Copyright (c) 2021 Janne Mareike Koschinski + * Copyright (c) 2021 The Quassel Project + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.generator.annotation + +data class SyncedObjectAnnotationModel( + val name: String? +) diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedCallModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedCallModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..e55a86ebf2a9d1706ee1e47d4f48e2dd58582204 --- /dev/null +++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedCallModel.kt @@ -0,0 +1,20 @@ +/* + * libquassel + * Copyright (c) 2021 Janne Mareike Koschinski + * Copyright (c) 2021 The Quassel Project + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.generator.model + +import de.justjanne.libquassel.annotations.ProtocolSide + +data class SyncedCallModel( + val name: String, + val rpcName: String?, + val side: ProtocolSide?, + val parameters: List<SyncedParameterModel> +) diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedObjectModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedObjectModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..04f52dc2caa9d4d736545f25172cfd7f94412886 --- /dev/null +++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedObjectModel.kt @@ -0,0 +1,17 @@ +/* + * libquassel + * Copyright (c) 2021 Janne Mareike Koschinski + * Copyright (c) 2021 The Quassel Project + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.generator.model + +data class SyncedObjectModel( + val name: String, + val rpcName: String?, + val methods: List<SyncedCallModel> +) diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedParameterModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedParameterModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..e7242ab971f6df3dd3377e5bc0e277dcdb7a3c6a --- /dev/null +++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedParameterModel.kt @@ -0,0 +1,18 @@ +/* + * libquassel + * Copyright (c) 2021 Janne Mareike Koschinski + * Copyright (c) 2021 The Quassel Project + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.generator.model + +import com.google.devtools.ksp.symbol.KSType + +data class SyncedParameterModel( + val name: String?, + val type: KSType +) diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/asType.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/asType.kt new file mode 100644 index 0000000000000000000000000000000000000000..f562c99a6fd84d21cb451ff066089613b5c84bb5 --- /dev/null +++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/asType.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2021 Zac Sweers + * + * Licensed 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 + * + * https://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. + */ + +package de.justjanne.libquassel.generator.util + +import com.google.devtools.ksp.symbol.KSClassDeclaration + +internal fun KSClassDeclaration.asType() = asType(emptyList()) diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/findAnnotationWithType.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/findAnnotationWithType.kt new file mode 100644 index 0000000000000000000000000000000000000000..2a37e0b670583c22cddf244c677f9a832b7eb325 --- /dev/null +++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/findAnnotationWithType.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 Zac Sweers + * + * Licensed 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 + * + * https://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. + */ + +package de.justjanne.libquassel.generator.util + +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSAnnotation +import com.google.devtools.ksp.symbol.KSType + +internal inline fun <reified T : Annotation> KSAnnotated.findAnnotationWithType( + resolver: Resolver, +): KSAnnotation? { + return findAnnotationWithType(resolver.getClassDeclarationByName<T>().asType()) +} + +internal fun KSAnnotated.findAnnotationWithType(target: KSType): KSAnnotation? { + return annotations.find { it.annotationType.resolve() == target } +} diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getClassDeclarationByName.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getClassDeclarationByName.kt new file mode 100644 index 0000000000000000000000000000000000000000..2a5694b057fbed0ee36e867c265fa26f9334677c --- /dev/null +++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getClassDeclarationByName.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 Zac Sweers + * + * Licensed 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 + * + * https://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. + */ + +package de.justjanne.libquassel.generator.util + +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.symbol.KSClassDeclaration + +internal inline fun <reified T> Resolver.getClassDeclarationByName(): KSClassDeclaration { + return getClassDeclarationByName(T::class.qualifiedName!!) +} + +internal fun Resolver.getClassDeclarationByName(fqcn: String): KSClassDeclaration { + return getClassDeclarationByName(getKSNameFromString(fqcn)) ?: error("Class '$fqcn' not found.") +} diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getMember.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getMember.kt new file mode 100644 index 0000000000000000000000000000000000000000..520994a195ac2eef3403004fff34aad139e46553 --- /dev/null +++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getMember.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 Zac Sweers + * + * Licensed 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 + * + * https://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. + */ + +package de.justjanne.libquassel.generator.util + +import com.google.devtools.ksp.symbol.KSAnnotation +import com.google.devtools.ksp.symbol.KSType +import com.squareup.kotlinpoet.ClassName + +internal inline fun <reified T> KSAnnotation.getMember(name: String): T? { + val matchingArg = arguments.find { it.name?.asString() == name } + ?: error( + "No member name found for '$name'. All arguments: ${arguments.map { it.name?.asString() }}" + ) + return when (val argValue = matchingArg.value) { + is T -> argValue + is KSType -> + when { + T::class.java != ClassName::class.java -> null + else -> argValue.toClassName() as T + } + else -> null + } +} diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/hasAnnotation.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/hasAnnotation.kt new file mode 100644 index 0000000000000000000000000000000000000000..16f3cff2364c0ad33fdeb82d7713ff531f67c421 --- /dev/null +++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/hasAnnotation.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 Zac Sweers + * + * Licensed 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 + * + * https://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. + */ + +package de.justjanne.libquassel.generator.util + +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSType + +internal fun KSAnnotated.hasAnnotation(target: KSType): Boolean { + return findAnnotationWithType(target) != null +} diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toClassName.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toClassName.kt new file mode 100644 index 0000000000000000000000000000000000000000..4542f82560360654ecb63e2b92b23ee4f0cd3b2d --- /dev/null +++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toClassName.kt @@ -0,0 +1,42 @@ +/* + * libquassel + * Copyright (c) 2021 Janne Mareike Koschinski + * Copyright (c) 2021 The Quassel Project + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.generator.util + +import com.google.devtools.ksp.isLocal +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSType +import com.squareup.kotlinpoet.ClassName + +internal fun KSType.toClassName(): ClassName { + val decl = declaration + check(decl is KSClassDeclaration) + return decl.toClassName() +} + +internal fun KSClassDeclaration.toClassName(): ClassName { + require(!isLocal()) { + "Local/anonymous classes are not supported!" + } + val pkgName = packageName.asString() + val typesString = qualifiedName!!.asString().removePrefix("$pkgName.") + + val simpleNames = typesString + .split(".") + return ClassName(pkgName, simpleNames) +} + +fun Class<*>.toClassName(): ClassName { + val packageName = `package`.name + val names = canonicalName.substring(packageName.length) + .trimStart('.') + .split('.') + return ClassName(packageName, names) +} diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toEnum.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toEnum.kt new file mode 100644 index 0000000000000000000000000000000000000000..115a1f7a01d042a63130e5d5a9637d2418088e39 --- /dev/null +++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toEnum.kt @@ -0,0 +1,29 @@ +/* + * libquassel + * Copyright (c) 2021 Janne Mareike Koschinski + * Copyright (c) 2021 The Quassel Project + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.generator.util + +import com.google.devtools.ksp.symbol.KSType +import com.squareup.kotlinpoet.ClassName + +internal inline fun <reified T : Enum<T>> KSType.toEnum(): T? { + return toClassName().toEnum(T::class.java) +} + +internal inline fun <reified T : Enum<T>> ClassName.toEnum(): T? { + return toEnum(T::class.java) +} + +internal fun <T : Enum<T>> ClassName.toEnum(clazz: Class<T>): T? { + val enumClassName = clazz.toClassName() + return clazz.enumConstants.find { + this.canonicalName == enumClassName.nestedClass(it.name).canonicalName + } +} diff --git a/libquassel-generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessor b/libquassel-generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessor new file mode 100644 index 0000000000000000000000000000000000000000..3720f23ceb0706e898b05f19e202cd213cc002c7 --- /dev/null +++ b/libquassel-generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessor @@ -0,0 +1 @@ +de.justjanne.libquassel.generator.InvokerProcessor diff --git a/libquassel-state/build.gradle.kts b/libquassel-state/build.gradle.kts index ac709bd82221efc352e403bd581612ce221cb43b..9d0afbdb400b7348a58fc0d72ec9c36203183598 100644 --- a/libquassel-state/build.gradle.kts +++ b/libquassel-state/build.gradle.kts @@ -10,8 +10,10 @@ plugins { id("com.vanniktech.maven.publish") + id("com.google.devtools.ksp") version "1.4.30-1.0.0-alpha02" } dependencies { api(project(":libquassel-protocol")) + ksp(project(":libquassel-generator")) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 0f0d93227259cc79712cbf2ebf31204db827538a..e0c6a2fa7fe66a50f89cb2a69e5772f6de11e517 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,5 +14,13 @@ include( ":libquassel-annotations", ":libquassel-protocol", ":libquassel-state", + ":libquassel-generator", ":libquassel-client" ) + +pluginManagement { + repositories { + gradlePluginPortal() + google() + } +}