Skip to content

Commit

Permalink
Add Extension Dependency Check
Browse files Browse the repository at this point in the history
  • Loading branch information
gabber235 committed Dec 19, 2024
1 parent 606f4fe commit 9aa6ebd
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,37 +55,60 @@ class ExtensionLoader : KoinComponent {
// TODO: Remove this when the database update is implemented
extensionJson = JsonArray()

extensions = extensionJsonTexts.map { extensionJson ->
gson.fromJson(extensionJson, Extension::class.java) to extensionJson
}.filter { (extension, _) ->
if (extension.extension.engineVersion != version) {
logger.warning("Extension '${extension.extension.name}Extension' was made for Typewriter $version but you are using Typewriter ${extension.extension.engineVersion}. Ignoring extension.")
return@filter false
}
val possibleExtensions =

extensionJsonTexts.map { extensionJson ->
gson.fromJson(extensionJson, Extension::class.java) to extensionJson
}.filter { (extension, _) ->
if (extension.extension.engineVersion != version) {
logger.warning("Extension '${extension.extension.name}Extension' was made for Typewriter $version but you are using Typewriter ${extension.extension.engineVersion}. Ignoring extension.")
return@filter false
}

val paper = extension.extension.paper
if (paper == null) {
logger.warning("Extension '${extension.extension.name}Extension' does not seem to be a paper extension. Ignoring extension.")
return@filter false
}
val paper = extension.extension.paper
if (paper == null) {
logger.warning("Extension '${extension.extension.name}Extension' does not seem to be a paper extension. Ignoring extension.")
return@filter false
}

val dependencies = paper.dependencies
val missingDependencies = dependencies.filter { !dependencyChecker.hasDependency(it) }
if (missingDependencies.isNotEmpty()) {
val missing = missingDependencies.joinToString(", ")
logger.warning(
"Extension '${extension.extension.name}Extension' is missing dependencies: '${missing}'. Ignoring extension."
)
return@filter false
val dependencies = paper.dependencies
val missingDependencies = dependencies.filter { !dependencyChecker.hasDependency(it) }
if (missingDependencies.isNotEmpty()) {
val missing = missingDependencies.joinToString(", ")
logger.warning(
"Extension '${extension.extension.name}Extension' is missing external dependencies: '${missing}'. Ignoring extension."
)
return@filter false
}

true
}

true
}.map { (extension, extensionJson) ->
// TODO: Remove this when the database update is implemented
this.extensionJson.add(JsonParser.parseString(extensionJson))
extensions = possibleExtensions
.filter { (extension, _) ->
// Check if the extension dependencies are met
val missingDependencies =
extension.extension.dependencies.filter { (namespace, name) ->
possibleExtensions.none { (extension, _) ->
extension.extension.namespace == namespace && extension.extension.name == name
}
}

if (missingDependencies.isNotEmpty()) {
val missing = missingDependencies.joinToString(", ") { "${it.namespace}:${it.name}Extension" }
logger.warning(
"Extension '${extension.extension.name}Extension' is missing extension dependencies: '${missing}'. Ignoring extension."
)
return@filter false
}
true
}
.map { (extension, extensionJson) ->
// TODO: Remove this when the database update is implemented
this.extensionJson.add(JsonParser.parseString(extensionJson))

extension
}
extension
}

if (hasShownLoadedMessage) return
hasShownLoadedMessage = true
Expand Down Expand Up @@ -170,10 +193,17 @@ data class ExtensionInfo(
val description: String = "",
val version: String = "",
val engineVersion: String = "",
val namespace: String = "",
val flags: List<ExtensionFlag> = emptyList(),
val dependencies: List<ExtensionDependencyInfo> = emptyList(),
val paper: PaperExtensionInfo? = null,
)

data class ExtensionDependencyInfo(
val namespace: String = "",
val name: String = "",
)

data class PaperExtensionInfo(
val dependencies: List<String> = emptyList(),
)
Expand Down
4 changes: 2 additions & 2 deletions engine/engine-paper/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ dependencies {
compileOnlyApi("net.kyori:adventure-text-serializer-legacy:$adventureVersion")
compileOnlyApi("net.kyori:adventure-text-serializer-gson:$adventureVersion")

compileOnlyApi("com.github.retrooper:packetevents-api:2.7.0-SNAPSHOT")
compileOnlyApi("com.github.retrooper:packetevents-spigot:2.7.0-SNAPSHOT")
compileOnlyApi("com.github.retrooper:packetevents-api:2.7.0")
compileOnlyApi("com.github.retrooper:packetevents-spigot:2.7.0")
compileOnly("me.clip:placeholderapi:2.11.6")
compileOnlyApi("org.geysermc.floodgate:api:2.2.3-SNAPSHOT")

Expand Down
5 changes: 5 additions & 0 deletions extensions/EntityExtension/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ typewriter {
engineVersion = file("../../version.txt").readText().trim().substringBefore("-beta")
channel = com.typewritermc.moduleplugin.ReleaseChannel.NONE

dependencies {
dependency("typewritermc", "RoadNetwork")
dependency("typewritermc", "Quest")
}

paper()
}
}
3 changes: 3 additions & 0 deletions extensions/QuestExtension/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ typewriter {
engineVersion = file("../../version.txt").readText().trim().substringBefore("-beta")
channel = com.typewritermc.moduleplugin.ReleaseChannel.NONE

dependencies {
dependency("typewritermc", "RoadNetwork")
}

paper()
}
Expand Down
11 changes: 10 additions & 1 deletion extensions/VaultExtension/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
repositories {
maven("https://jitpack.io")
exclusiveContent {
forRepository {
maven {
url = uri("https://jitpack.io")
}
}
filter {
includeGroup("com.github.MilkBowl")
}
}
}
dependencies {
compileOnly("com.github.MilkBowl:VaultAPI:1.7.1")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.typewritermc.moduleplugin

import com.typewritermc.loader.ExtensionFlag
import kotlinx.serialization.MissingFieldException
import kotlinx.serialization.Serializable

@Serializable
Expand Down Expand Up @@ -34,16 +35,21 @@ open class TypewriterModuleConfiguration {

const val MIN_NAMESPACE_LENGTH = 5
const val MAX_NAMESPACE_LENGTH = 20
fun TypewriterModuleConfiguration.validate() {
if (namespace.isBlank()) {
throw MissingFieldException("namespace")
val NAMESPACE_REGEX = Regex("^([a-z0-9])+\$")
private fun String.validateAsNamespace(path: String) {
if (isBlank()) {
throw MissingFieldException(path)
}
if (!Regex("^([a-z0-9])+\$").matches(namespace)) {
if (!NAMESPACE_REGEX.matches(this)) {
throw IllegalArgumentException("Namespace must only contain lowercase letters and numbers.")
}
if (namespace.length !in MIN_NAMESPACE_LENGTH..MAX_NAMESPACE_LENGTH) {
throw FieldOutsideRangeException("namespace", MIN_NAMESPACE_LENGTH, MAX_NAMESPACE_LENGTH)
if (length !in MIN_NAMESPACE_LENGTH..MAX_NAMESPACE_LENGTH) {
throw FieldOutsideRangeException(path, MIN_NAMESPACE_LENGTH, MAX_NAMESPACE_LENGTH)
}
}

fun TypewriterModuleConfiguration.validate() {
namespace.validateAsNamespace("namespace")

if (engine != null && extension != null) {
throw IllegalArgumentException("Cannot have both engine and extension set for the same module.")
Expand Down Expand Up @@ -89,11 +95,18 @@ data class TypewriterExtensionConfiguration(
* The release channel to use for the engine and other dependencies.
*/
var channel: ReleaseChannel = ReleaseChannel.RELEASE,
/**
* Dependencies on other extensions.
*/
var dependencies: ExtensionDependencyConfiguration? = null,
/**
* The paper extension configuration.
*/
var paper: PaperExtensionConfiguration? = null
) {
fun dependencies(block: ExtensionDependencyConfiguration.() -> Unit) {
dependencies = ExtensionDependencyConfiguration().apply(block)
}
fun paper(block: PaperExtensionConfiguration.() -> Unit) {
paper = PaperExtensionConfiguration().apply(block)
}
Expand All @@ -109,23 +122,28 @@ private const val MIN_SHORT_DESCRIPTION_LENGTH = 10
private const val MAX_SHORT_DESCRIPTION_LENGTH = 80
private const val MIN_DESCRIPTION_LENGTH = 100
private const val MAX_DESCRIPTION_LENGTH = 2000
private val NAME_REGEX = Regex("^([a-zA-Z0-9])+\$")
private val FORBIDDEN_WORDS = listOf("Adapter", "Extension")

internal fun TypewriterExtensionConfiguration.validate() {
if (name.isBlank()) {
throw MissingFieldException("extension.name")
private fun String.validateAsExtensionName(path: String) {
if (isBlank()) {
throw MissingFieldException(path)
}
if (name.length !in MIN_NAME_LENGTH..MAX_NAME_LENGTH) {
throw FieldOutsideRangeException("extension.name", MIN_NAME_LENGTH, MAX_NAME_LENGTH)
if (length !in MIN_NAME_LENGTH..MAX_NAME_LENGTH) {
throw FieldOutsideRangeException(path, MIN_NAME_LENGTH, MAX_NAME_LENGTH)
}
if (!Regex("^[a-zA-Z0-9]+$").matches(name)) {
throw IllegalArgumentException("Extension name '$name' must be alphanumeric.")
if (!NAME_REGEX.matches(this)) {
throw IllegalArgumentException("Extension name '$this' must be alphanumeric.")
}
for (word in FORBIDDEN_WORDS) {
if (name.contains(word, ignoreCase = true)) {
throw IllegalArgumentException("Extension name '$name' cannot contain '$word'")
if (this.contains(word, ignoreCase = true)) {
throw IllegalArgumentException("Extension name '$this' cannot contain '$word'")
}
}
}

internal fun TypewriterExtensionConfiguration.validate() {
name.validateAsExtensionName("extension.name")

if (shortDescription.isBlank()) {
throw MissingFieldException("extension.shortDescription")
Expand All @@ -152,9 +170,34 @@ internal fun TypewriterExtensionConfiguration.validate() {
throw IllegalArgumentException("Version must follow the Semantic Versioning format.")
}

dependencies?.validate()
paper?.validate()
}

@Serializable
data class ExtensionDependencyConfiguration(
val dependencies: MutableList<ExtensionDependency> = mutableListOf(),
) {
fun dependency(namespace: String, name: String) {
dependencies.add(ExtensionDependency(namespace, name))
}
}

@Serializable
data class ExtensionDependency(
val namespace: String,
val name: String,
)

private fun ExtensionDependencyConfiguration.validate() {
dependencies.forEach { it.validate() }
}

private fun ExtensionDependency.validate() {
namespace.validateAsNamespace("extension.dependencies.namespace")
name.validateAsExtensionName("extension.dependencies.name")
}

@Serializable
data class PaperExtensionConfiguration(
val dependencies: MutableList<String> = mutableListOf(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ class ExtensionProcessor(
"engineVersion" to JsonPrimitive(extension.engineVersion),
"pluginVersion" to JsonPrimitive(pluginVersion),
"version" to JsonPrimitive(version),
"namespace" to JsonPrimitive(configuration.namespace),
"dependencies" to JsonArray(
extension.dependencies?.dependencies?.map { Json.encodeToJsonElement(it) } ?: emptyList()
)
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class TypewriterModulePlugin : Plugin<Project> {

// Add PacketEvents repository
repositories.maven {
it.setUrl("https://repo.codemc.io/repository/maven-snapshots/")
it.setUrl("https://repo.codemc.io/repository/maven-releases/")
}
// Add EntityLib repository
repositories.maven {
Expand Down

0 comments on commit 9aa6ebd

Please sign in to comment.