diff --git a/CHANGELOG.md b/CHANGELOG.md index e2df779..7bcd4d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,18 @@ Refactor development server implementation to use [jpenilla/run-task](https://github.com/jpenilla/run-task/) plugin and integrate run-paper for server execution, improving maintainability and compatibility with various server versions. - **Breaking change!** - Rename `bukkit.meta { ... }` to `bukkit.plugin { ... }`. - - Task `:parsePluginMetaFile` and `:mergePluginMeta` renamed to `:parsePluginYaml` and `:mergePluginYaml` respectively. - - `bukkit.disableMetaGeneration()` replaced by `bukkit.plugin.disablePluginYamlGeneration()` - - Package `.meta` renamed to `.plugin` to reflect this change + Use [jpenilla/resource-factory](https://github.com/jpenilla/resource-factory) to generate `plugin.yml`. + This change comes with some renames: + - Configuration block `bukkit.meta { ... }` -> `bukkit.plugin { ... }` + - Property `bukkit.plugin.url` -> `bukkit.plugin.website` + - Task `:parsePluginMetaFile` -> `:parsePluginYaml` + - Task `:mergePluginMeta` has been dropped. Use `:mainResourceFactory` instead + - Package `ru.endlesscode.bukkitgradle.meta` -> `ru.endlesscode.bukkitgradle.plugin` +- Change API for disabling `plugin.yml` generation: + ```diff + -bukkit.disableMetaGeneration() + +bukkit.generatePluginYaml.set(false) + ``` - Set the default [JVM toolchain](https://docs.gradle.org/current/userguide/toolchains.html) version instead of setting JVM target and source compatibility to 1.8. By default, the minimal supported JVM version compatible with the specified `bukkit.server.version` is used. diff --git a/README.md b/README.md index 3bf736f..023cb33 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Gradle utilities to simplify Bukkit/Spigot plugins writing and debugging. - [Installation](#installation) - - [First steps](#first-steps) + - [Quick Start](#quick-start) - [Configuration](#configuration) - [Repositories and Dependencies](#repositories-and-dependencies) - [Running Dev server](#running-dev-server) @@ -21,7 +21,6 @@ Gradle utilities to simplify Bukkit/Spigot plugins writing and debugging. #### Features: -- Automatically applies plugin: java - Sets up compiler encoding to UTF-8 - Sets archivesBaseName to plugin name - Supports APIs: Bukkit, CraftBukkit, Spigot, Paper @@ -35,33 +34,22 @@ Gradle utilities to simplify Bukkit/Spigot plugins writing and debugging. ## Installation -[BukkitGradle on plugins.gradle.org](https://plugins.gradle.org/plugin/ru.endlesscode.bukkitgradle) -> **Note:** Gradle 8.0+ is required +> [!NOTE] +> BukkitGradle requires Gradle 8.0+ to run -#### With new plugins mechanism ```kotlin plugins { id("ru.endlesscode.bukkitgradle") version "0.10.1" } ``` -#### With buildscript and apply -```groovy -buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath("gradle.plugin.ru.endlesscode:bukkit-gradle:0.10.1") - } -} +[BukkitGradle on plugins.gradle.org](https://plugins.gradle.org/plugin/ru.endlesscode.bukkitgradle) -apply(plugin: "ru.endlesscode.bukkitgradle") -``` +
-#### Snapshots +Using snapshots -If you want to use snapshots, you can add jitpack repository to `settings.gradle` and use version `develop-SNAPSHOT`: +To use snapshots, add jitpack repository to the `settings.gradle.kts` and specify version `develop-SNAPSHOT`: ```kotlin // settings.gradle @@ -82,88 +70,86 @@ plugins { } ``` -### First steps -Simple `build.gradle` file that use BukkitGradle: +
+ +### Quick Start + +Apply the plugin and configure project's `group`, `description` and `version`. +These values will be used to generate the `plugin.yml` file: + ```kotlin plugins { id("ru.endlesscode.bukkitgradle") version "0.10.1" } - -// Project information + group = "com.example.myplugin" -description = "My first Bukkit plugin with Gradle" +description = "My first Bukkit plugin built by Gradle" version = "0.1" -// Let's add needed API to project +bukkit { + apiVersion = "1.16.5" +} + +// Add the necessary API to the project dependencies { compileOnly(bukkitApi()) - // see section 'Dependencies' for more info + // See the 'Dependencies' section for more info } ``` -> **Note:** `compileOnly` - it's like `provided` scope in Maven. -It means that this dependency will not be included to your final jar. -It's enough! -Will be hooked the latest version of Bukkit and automatically generated `plugin.yml` with next content: +That's it! +During the plugin compilation `plugin.yml` will be generated with the following content: + ```yaml +api-version: '1.16' name: MyPlugin -description: My first Bukkit plugin with Gradle +version: '0.1' main: com.example.myplugin.MyPlugin -version: 0.1 -api-version: 1.16 +description: My first Bukkit plugin built by Gradle ``` -> **Note:** Main class built by following pattern: `.` + +> [!NOTE] +> By default, main class is built by the following pattern: `.` + +Next, you might need to [configure](#configuration) `plugin.yml` content or [run dev server](#running-dev-server). ## Configuration -You can configure attributes that will be placed to `plugin.yml`: + +The `plugin.yml` content can be configured using `bukkit.plugin { ... }` block. + ```kotlin -// Override default configurations bukkit { - // Version of API (if you will not set this property, will be used latest version at moment of BukkitGradle release) + // Version of API. By default, 1.16.5 is used apiVersion = "1.15.2" - // Attributes for plugin.yml + // Configure plugin.yml content plugin { - name.set("MyPlugin") - description.set("My amazing plugin, that doing nothing") - main.set("com.example.plugin.MyPlugin") - version.set("1.0") - url.set("http://www.example.com") // Attribute website - authors.set(["OsipXD", "Contributors"]) + name = "MyPlugin" + description = "My amazing plugin" + main = "com.example.plugin.MyPlugin" + version = "1.0" + authors = listOf("osipxd", "contributors") + depend = listOf("Vault", "Mimic") } } ``` -Will be generated `plugin.yml` file: -```yaml -name: MyPlugin -description: My amazing plugin, that doing nothing -main: com.example.plugin.MyPlugin -version: 1.0 -api-version: 1.15 -website: http://www.example.com -authors: [OsipXD, Contributors] -``` - -If you want to add unsupported by BukkitGradle attributes, like a `depend`, `commands` etc. -Create `plugin.yml` file and put custom attributes there. - ## Repositories and Dependencies -BukkitGradle provides short extension-functions to add common repositories and dependencies. -There are list of its. -Usage example: +BukkitGradle provides shortcuts to add common repositories and dependencies: + ```kotlin repositories { - spigot() // Adds spigot repo + spigot() } dependencies { - compileOnly(paperApi()) // Adds paper-api dependency + compileOnly(paperApi()) } ``` -#### Repositories: +#### Repositories + Name | Url ----------------|------------------------------------------------------------------- spigot | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ @@ -176,7 +162,8 @@ dependencies { aikar | https://repo.aikar.co/content/groups/aikar/ codemc | https://repo.codemc.org/repository/maven-public/ -#### Dependencies: +#### Dependencies + Some dependencies also add a repository needed for them. Name | Signature | Adds repository @@ -188,7 +175,7 @@ Some dependencies also add a repository needed for them. **Note:** `$apiVersion` - is `${version}-R0.1-SNAPSHOT` (where `$version` is `bukkit.version`) -If you need more extension-functions, [create issue][issue]. +If you need more extension-functions, [file an issue][issue]. ## Running Dev server @@ -263,21 +250,29 @@ bukkit { ## Migration Guide -### Upgrade from 0.8.x +### Upgrade from 0.10.x -1. Update gradle to 6.6 or newer: - ```shell - $ ./gradlew wrapper --gradle-version 6.7.1 - ``` -1. Use syntax `.set` in `bukkit.meta` instead of `=`: +1. Update Gradle to 8.0 or newer (the latest version is recommended): +```shell +./gradlew wrapper --gradle-version 8.13 +``` + +2. Replace deprecated and removed APIs: ```diff bukkit { - meta { - - desctiption = "My plugin's description" - + description.set("My plugin's description") + - meta { + + plugin { + name = "MyPlugin" + - url = "https://example.com/" + + website = "https://example.com/" } } - ``` + ``` + +3. If you have `plugin.yml`, move it's content to `bukkit.plugin { ... }` block + +### Upgrade from 0.8.x + 1. Use `bukkit.apiVersion` instead of `bukkit.version`: ```diff bukkit { @@ -285,7 +280,7 @@ bukkit { + apiVersion = "1.16.4" } ``` -1. Use `build.server` block instead of `build.run`: +2. Use `build.server` block instead of `build.run`: ```diff bukkit { - run { @@ -294,7 +289,7 @@ bukkit { } } ``` -1. Update arguments assignment syntax: +3. Update arguments assignment syntax: ```diff bukkit { server { @@ -304,7 +299,7 @@ bukkit { } } ``` -1. Replace removed APIs: +4. Replace removed APIs: ```diff repositories { - destroystokyo() @@ -319,8 +314,8 @@ bukkit { + compileOnly(spigot()) } ``` -1. Remove `q` and `qq` functions calls in `meta { ... }` -1. Check generated plugin.yml contents after build. +5. Remove `q` and `qq` functions calls in `meta { ... }` +6. Check generated plugin.yml contents after build. If there are any problems, [create an issue][issue]. diff --git a/build.gradle.kts b/build.gradle.kts index af984b1..c775ad3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,7 +29,7 @@ repositories { dependencies { implementation("xyz.jpenilla.run-paper:xyz.jpenilla.run-paper.gradle.plugin:2.3.1") - implementation("de.undercouch:gradle-download-task:5.6.0") + implementation("xyz.jpenilla.resource-factory:xyz.jpenilla.resource-factory.gradle.plugin:1.2.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1") implementation("com.charleskorn.kaml:kaml:0.74.0") testImplementation("junit:junit:4.13.1") diff --git a/src/main/kotlin/Bukkit.kt b/src/main/kotlin/Bukkit.kt index 71ef34e..d93a4a7 100644 --- a/src/main/kotlin/Bukkit.kt +++ b/src/main/kotlin/Bukkit.kt @@ -1,16 +1,14 @@ package ru.endlesscode.bukkitgradle import org.gradle.api.provider.Provider -import ru.endlesscode.bukkitgradle.plugin.extension.PluginConfiguration +import ru.endlesscode.bukkitgradle.plugin.plugin import ru.endlesscode.bukkitgradle.server.extension.ServerConfiguration +import xyz.jpenilla.resourcefactory.bukkit.BukkitPluginYaml public interface Bukkit { - /** Plugin plugin. */ - public val plugin: PluginConfiguration - @Deprecated("Use 'plugin' field instead", ReplaceWith("plugin")) - public val meta: PluginConfiguration + public val meta: BukkitPluginYaml get() = plugin /** Dev server configuration. */ @@ -20,7 +18,10 @@ public interface Bukkit { public val apiVersion: Provider /** Plugin Meta generation enabled. */ - @Deprecated("Use 'plugin.generatePluginYaml' instead", ReplaceWith("plugin.generatePluginYaml")) + @Deprecated("Use 'generatePluginYaml' instead", ReplaceWith("generatePluginYaml")) public val generateMeta: Provider - get() = plugin.generatePluginYaml + get() = generatePluginYaml + + /** Whether plugin.yml generation enabled or not. */ + public val generatePluginYaml: Provider } diff --git a/src/main/kotlin/BukkitExtension.kt b/src/main/kotlin/BukkitExtension.kt index fb33d3f..8cf2adc 100644 --- a/src/main/kotlin/BukkitExtension.kt +++ b/src/main/kotlin/BukkitExtension.kt @@ -7,16 +7,19 @@ import org.gradle.api.provider.Property import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.property import ru.endlesscode.bukkitgradle.extensions.finalizedOnRead -import ru.endlesscode.bukkitgradle.plugin.extension.PluginConfigurationImpl +import ru.endlesscode.bukkitgradle.plugin.plugin import ru.endlesscode.bukkitgradle.server.ServerConstants import ru.endlesscode.bukkitgradle.server.extension.ServerConfigurationImpl +import xyz.jpenilla.resourcefactory.bukkit.BukkitPluginYaml public open class BukkitExtension internal constructor( - public final override val plugin: PluginConfigurationImpl, public final override val server: ServerConfigurationImpl, objects: ObjectFactory, ) : Bukkit { + override val generatePluginYaml: Property = objects.property() + .convention(true) + public final override val apiVersion: Property = objects.property() .convention(ServerConstants.DEFAULT_VERSION) .finalizedOnRead() @@ -25,22 +28,22 @@ public open class BukkitExtension internal constructor( body.execute(server) } - public fun plugin(body: Action) { - body.execute(plugin) - } - @Deprecated("Use 'plugin' instead", ReplaceWith("plugin(body)")) - public fun meta(body: Action) { - plugin(body) + public fun meta(body: Action) { + body.execute(plugin) } /** Disables plugin.yml generation. */ @Deprecated( - "Use 'plugin.disablePluginYamlGeneration()' instead", - ReplaceWith("plugin.disablePluginYamlGeneration()") + "Use 'generatePluginYaml.set(false)' instead", + ReplaceWith("generatePluginYaml.set(false)") ) public fun disableMetaGeneration() { - plugin.disablePluginYamlGeneration() + generatePluginYaml.set(false) + } + + public companion object { + public const val NAME: String = "bukkit" } } diff --git a/src/main/kotlin/BukkitGradlePlugin.kt b/src/main/kotlin/BukkitGradlePlugin.kt index f4b771f..00edf50 100644 --- a/src/main/kotlin/BukkitGradlePlugin.kt +++ b/src/main/kotlin/BukkitGradlePlugin.kt @@ -2,14 +2,13 @@ package ru.endlesscode.bukkitgradle import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.plugins.JavaBasePlugin +import org.gradle.api.plugins.JavaPlugin import org.gradle.api.tasks.compile.JavaCompile import org.gradle.kotlin.dsl.* import ru.endlesscode.bukkitgradle.dependencies.Dependencies -import ru.endlesscode.bukkitgradle.plugin.PluginConfigurationPlugin -import ru.endlesscode.bukkitgradle.plugin.extension.PluginConfigurationImpl -import ru.endlesscode.bukkitgradle.plugin.util.MinecraftVersion -import ru.endlesscode.bukkitgradle.plugin.util.StringUtils +import ru.endlesscode.bukkitgradle.extensions.java +import ru.endlesscode.bukkitgradle.plugin.configurePluginYamlFeature import ru.endlesscode.bukkitgradle.plugin.util.parsedApiVersion import ru.endlesscode.bukkitgradle.plugin.util.resolveMinimalJavaVersion import ru.endlesscode.bukkitgradle.server.DevServerPlugin @@ -28,53 +27,34 @@ public class BukkitGradlePlugin : Plugin { Dependencies.configureProject(project) } - /** Adds needed plugins. */ private fun Project.addPlugins() { - val bukkit = extensions.create("bukkit", createPluginConfiguration(), ServerConfigurationImpl()) - - with(plugins) { - apply("java") - apply() - apply() + // Apply Java plugin, but only if another JVM-language plugin wasn't applied before + if (!plugins.hasPlugin(JavaBasePlugin::class)) { + plugins.apply(JavaPlugin::class) } - extensions.configure { - toolchain { - languageVersion.convention(bukkit.parsedApiVersion.map(::resolveMinimalJavaVersion)) - } - } - } + val bukkit = extensions.create( + BukkitExtension.NAME, + ServerConfigurationImpl(), + ) - private fun Project.createPluginConfiguration(): PluginConfigurationImpl { - return PluginConfigurationImpl(objects).apply { - name.convention(project.name) - description.convention(provider { project.description }) - main.convention(name.map { "${project.group}.${StringUtils.toPascalCase(it)}" }) - version.convention(provider { project.version.toString() }) - apiVersion.convention(provider { bukkit.parsedApiVersion.get() }.map(::resolveDefaultApiVersion)) - url.convention(provider { providers.gradleProperty("url").orNull }) + configurePluginYamlFeature(bukkit) + apply() + + java.toolchain { + languageVersion.convention(bukkit.parsedApiVersion.map(::resolveMinimalJavaVersion)) } } - /** Sets encoding on compile to UTF-8. */ private fun Project.configureEncoding() { tasks.withType().configureEach { options.encoding = "UTF-8" } } - /** Adds needed repositories. */ private fun Project.addRepositories() { repositories { mavenCentral() } } - - private fun resolveDefaultApiVersion(version: MinecraftVersion): String = when { - // "API version" has been introduced in Spigot 1.13 - version < MinecraftVersion.V1_13_0 -> "" - // From 1.20.5 and onward, a patch version is supported. - version < MinecraftVersion.V1_20_5 -> version.withoutPatch().toString() - else -> version.toString() - } } diff --git a/src/main/kotlin/extensions/Accessors.kt b/src/main/kotlin/extensions/Accessors.kt index e84c692..9bb9950 100644 --- a/src/main/kotlin/extensions/Accessors.kt +++ b/src/main/kotlin/extensions/Accessors.kt @@ -2,7 +2,16 @@ package ru.endlesscode.bukkitgradle.extensions import org.gradle.api.Project import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.SourceSet +import org.gradle.api.tasks.SourceSetContainer import org.gradle.kotlin.dsl.the +import xyz.jpenilla.resourcefactory.ResourceFactoryExtension internal val Project.java: JavaPluginExtension get() = the() + +internal val Project.sourceSets: SourceSetContainer + get() = the() + +internal val SourceSet.resourceFactory: ResourceFactoryExtension + get() = the() diff --git a/src/main/kotlin/extensions/Provider.kt b/src/main/kotlin/extensions/Provider.kt index 1ff73d5..5b6051b 100644 --- a/src/main/kotlin/extensions/Provider.kt +++ b/src/main/kotlin/extensions/Provider.kt @@ -1,11 +1,23 @@ package ru.endlesscode.bukkitgradle.extensions -import org.gradle.api.Project -import org.gradle.api.file.Directory import org.gradle.api.provider.Property import org.gradle.api.provider.Provider -import java.io.File +import java.util.* internal fun Property.finalizedOnRead(): Property = apply { finalizeValueOnRead() } -internal fun Project.directoryProvider(file: () -> File?): Provider = layout.dir(provider(file)) +internal fun Property.finalizeAndGet(): T { + finalizeValue() + return get() +} + +// See: https://github.com/gradle/gradle/issues/12388 +@Suppress("UnstableApiUsage") +internal fun Provider.mapNotNull(transform: (T) -> R?): Provider { + return map { value: T -> + val transformedValue = transform(value) + Optional.ofNullable(transformedValue) + } + .filter(Optional::isPresent) + .map(Optional::get) +} diff --git a/src/main/kotlin/plugin/BukkitPluginYamlDefaults.kt b/src/main/kotlin/plugin/BukkitPluginYamlDefaults.kt new file mode 100644 index 0000000..4b5a6f1 --- /dev/null +++ b/src/main/kotlin/plugin/BukkitPluginYamlDefaults.kt @@ -0,0 +1,79 @@ +@file:UseSerializers(PermissionDefaultSerializer::class) + +package ru.endlesscode.bukkitgradle.plugin + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import xyz.jpenilla.resourcefactory.bukkit.BukkitPluginYaml +import xyz.jpenilla.resourcefactory.bukkit.Permission.Default as PermissionDefault + +/** + * Should be synced with [BukkitPluginYaml.Serializable]. + * We can't use the existing class as we want all fields to be nullable. + */ +@Serializable +internal data class BukkitPluginYamlDefaults( + val apiVersion: String? = null, + val name: String? = null, + val version: String? = null, + val main: String? = null, + val description: String? = null, + val load: BukkitPluginYaml.PluginLoadOrder? = null, + val author: String? = null, + val authors: List? = null, + val website: String? = null, + val depend: List? = null, + val softdepend: List? = null, + val loadbefore: List? = null, + val prefix: String? = null, + val defaultPermission: PermissionDefault? = null, + val provides: List? = null, + val libraries: List? = null, + val commands: Map = emptyMap(), + val permissions: Map = emptyMap(), + val foliaSupported: Boolean? = null, + val paperPluginLoader: String? = null, + val paperSkipLibraries: Boolean? = null, +) { + + @Serializable + internal data class Command( + val description: String? = null, + val aliases: List? = null, + val permission: String? = null, + val permissionMessage: String? = null, + val usage: String? = null, + ) + + @Serializable + internal data class Permission( + val description: String? = null, + val default: PermissionDefault? = null, + val children: Map? = null, + ) +} + +internal object PermissionDefaultSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor( + "xyz.jpenilla.resourcefactory.bukkit.Permission.Default", + PrimitiveKind.STRING, + ) + + override fun deserialize(decoder: Decoder): PermissionDefault { + val string = decoder.decodeString() + return requireNotNull(PermissionDefault.values().find { it.serialized.equals(string, ignoreCase = true) }) { + val knownValues = PermissionDefault.values().joinToString { it.serialized } + "Unknown permission default '$string'. Allowed: $knownValues" + } + } + + override fun serialize(encoder: Encoder, value: PermissionDefault) { + encoder.encodeString(value.serialized) + } +} diff --git a/src/main/kotlin/plugin/PluginConfigurationPlugin.kt b/src/main/kotlin/plugin/PluginConfigurationPlugin.kt deleted file mode 100644 index 2f0893d..0000000 --- a/src/main/kotlin/plugin/PluginConfigurationPlugin.kt +++ /dev/null @@ -1,69 +0,0 @@ -package ru.endlesscode.bukkitgradle.plugin - -import com.charleskorn.kaml.SequenceStyle -import com.charleskorn.kaml.Yaml -import com.charleskorn.kaml.YamlConfiguration -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.file.CopySpec -import org.gradle.api.file.DuplicatesStrategy -import org.gradle.kotlin.dsl.get -import org.gradle.kotlin.dsl.named -import org.gradle.kotlin.dsl.register -import ru.endlesscode.bukkitgradle.bukkit -import ru.endlesscode.bukkitgradle.extensions.java -import ru.endlesscode.bukkitgradle.plugin.extension.PluginConfigurationImpl -import ru.endlesscode.bukkitgradle.plugin.task.MergePluginYaml -import ru.endlesscode.bukkitgradle.plugin.task.ParsePluginYaml -import java.io.File - -public class PluginConfigurationPlugin : Plugin { - - override fun apply(project: Project) { - val yaml = Yaml( - configuration = YamlConfiguration( - encodeDefaults = false, - sequenceStyle = SequenceStyle.Flow - ) - ) - - val parsePluginYaml = project.tasks.register("parsePluginYaml") { - val bukkit = project.bukkit - - this.yaml = yaml - this.plugin = bukkit.plugin as PluginConfigurationImpl - this.pluginYamlFile.set(project.findPluginYaml()) - - val predicate = bukkit.plugin.generatePluginYaml - onlyIf { predicate.get() } - } - - val mergePluginYaml = project.tasks.register("mergePluginYaml") { - val bukkit = project.bukkit - - this.yaml = yaml - this.plugin = bukkit.plugin - pluginYaml.set(parsePluginYaml.map { it.pluginYaml.get() }) - - val predicate = bukkit.plugin.generatePluginYaml - onlyIf { predicate.get() } - } - - project.tasks.named("processResources") { - from(mergePluginYaml) - duplicatesStrategy = DuplicatesStrategy.INCLUDE - } - } - - /** Finds and returns project's plugin.yaml if it exists. */ - private fun Project.findPluginYaml(): File? { - val mainSourceSet = java.sourceSets["main"] - val resourceDir = mainSourceSet.resources.srcDirs.first() - - return File(resourceDir, FILE_NAME).takeIf { it.isFile } - } - - internal companion object { - const val FILE_NAME: String = "plugin.yml" - } -} diff --git a/src/main/kotlin/plugin/PluginYaml.kt b/src/main/kotlin/plugin/PluginYaml.kt deleted file mode 100644 index 78a0666..0000000 --- a/src/main/kotlin/plugin/PluginYaml.kt +++ /dev/null @@ -1,111 +0,0 @@ -package ru.endlesscode.bukkitgradle.plugin - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -internal data class PluginYaml( - - /** The class of the plugin that extends JavaPlugin. */ - val main: String? = null, - - /** Name of the plugin. */ - val name: String? = null, - - /** A human friendly description of the functionality the plugin provides. */ - val description: String? = null, - - /** The name to use when logging to console instead of the plugin's name. */ - val prefix: String? = null, - - /** The version of the plugin. */ - val version: String? = null, - - /** The version of the API that plugin use. */ - @SerialName("api-version") - val apiVersion: String? = null, - - /** Explicitly state when a plugin should be loaded. */ - val load: String? = null, - - /** Uniquely identifies who developed this plugin. */ - val author: String? = null, - - /** - * Allows listing multiple authors if it is a collaborative project. - * @see author - */ - val authors: List? = null, - - /** The contributors to the plugin that aren't the managing author(s). */ - val contributors: List? = null, - - /** The plugin's or author's website. */ - val website: String? = null, - - /** A list of plugins that your plugin requires to load. */ - val depend: List? = null, - - /** A list of plugins that are required for your plugin to have full functionality. */ - val softdepend: List? = null, - - /** A list of plugins that should be loaded after your plugin. */ - val loadbefore: List? = null, - - /** - * This can be used to tell the server that this plugin will provide the functionality - * of some library or other plugin (like an alias system). - * Plugins that (soft)depend on the other plugin will treat your plugin - * as if the other plugin exists when resolving dependencies or using `PluginManager#getPlugin(String)`. - */ - val provides: List? = null, - - /** A list of libraries your plugin needs which can be loaded from Maven Central. */ - val libraries: List? = null, - - /** The name of a command the plugin wishes to register, as well as an optional list of command attributes. */ - val commands: Map? = null, - - /** Permission that the plugin wishes to register. */ - val permissions: Map? = null, - - /** The default value that permissions that don't have a `default` specified will have. */ - @SerialName("default-permission") - val defaultPermission: String? = null, -) : java.io.Serializable - -@Serializable -internal data class PluginCommand( - - /** A short description of what the command does. */ - val description: String? = null, - - /** Alternate command names a user may use instead. */ - val aliases: List? = null, - - /** The most basic permission node required to use the command. */ - val permission: String? = null, - - /** - * A no-permission message which is displayed to a user if they do not have the required permission - * to use this command. - */ - @SerialName("permission-message") - val permissionMessage: String? = null, - - /** A short description of how to use this command. */ - val usage: String? = null -) : java.io.Serializable - -@Serializable -internal data class PluginPermission( - - /** A short description of what this permission allows. */ - val description: String? = null, - - /** The default value of the permission. */ - val default: String? = null, - - /** Children for the permission. */ - val children: Map? = null -) : java.io.Serializable diff --git a/src/main/kotlin/plugin/PluginYamlFeature.kt b/src/main/kotlin/plugin/PluginYamlFeature.kt new file mode 100644 index 0000000..6a2dc56 --- /dev/null +++ b/src/main/kotlin/plugin/PluginYamlFeature.kt @@ -0,0 +1,112 @@ +package ru.endlesscode.bukkitgradle.plugin + +import org.gradle.api.Project +import org.gradle.api.file.RegularFile +import org.gradle.api.plugins.ExtensionAware +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.SourceSet +import org.gradle.kotlin.dsl.* +import ru.endlesscode.bukkitgradle.Bukkit +import ru.endlesscode.bukkitgradle.BukkitExtension +import ru.endlesscode.bukkitgradle.extensions.finalizeAndGet +import ru.endlesscode.bukkitgradle.extensions.mapNotNull +import ru.endlesscode.bukkitgradle.extensions.resourceFactory +import ru.endlesscode.bukkitgradle.extensions.sourceSets +import ru.endlesscode.bukkitgradle.plugin.task.ParsePluginYaml +import ru.endlesscode.bukkitgradle.plugin.util.MinecraftVersion +import ru.endlesscode.bukkitgradle.plugin.util.StringUtils +import ru.endlesscode.bukkitgradle.plugin.util.parsedApiVersion +import xyz.jpenilla.resourcefactory.ExecuteResourceFactories +import xyz.jpenilla.resourcefactory.ResourceFactoryPlugin +import xyz.jpenilla.resourcefactory.bukkit.BukkitPluginYaml +import xyz.jpenilla.resourcefactory.bukkit.bukkitPluginYaml + +private const val PLUGIN_EXTENSION_NAME: String = "plugin" +private const val SOURCE_SET_NAME: String = "main" // Make it configurable, maybe? +internal const val PLUGIN_YML: String = "plugin.yml" + +internal fun Project.configurePluginYamlFeature(bukkit: BukkitExtension) { + project.apply() + + val bukkitPluginYaml = bukkitPluginYaml { + setConventionsFromProjectMeta(project, bukkit) + } + (bukkit as ExtensionAware).extensions.add(PLUGIN_EXTENSION_NAME, bukkitPluginYaml) + + val mainSourceSet = sourceSets.named(SOURCE_SET_NAME) { + resourceFactory.factory(bukkitPluginYaml.resourceFactory()) + } + + val parsePluginYamlProvider = tasks.register("parsePluginYaml") { + pluginYaml.set(bukkitPluginYaml) + pluginYamlFile.set(findPluginYaml(mainSourceSet)) + + onlyIf { bukkit.generatePluginYaml.finalizeAndGet() } + } + + tasks.named("${SOURCE_SET_NAME}ResourceFactory") { + val generatePluginYaml = bukkit.generatePluginYaml.finalizeAndGet() + onlyIf("Flag generatePluginYaml is enabled") { generatePluginYaml } + if (!generatePluginYaml) return@named + + val parsePluginYaml = parsePluginYamlProvider.get() + + // Switch to in-place generation mode if the plugin.yml exists + if (parsePluginYaml.pluginYamlFile.isPresent) { + dependsOn(parsePluginYaml) + + val pluginYamlFile = parsePluginYaml.pluginYamlFile.get().asFile + outputDir.set(pluginYamlFile.parentFile) + // Deduplicate resource dirs after changing outputDir + mainSourceSet.configure { + resources.setSrcDirs(resources.srcDirs.distinct()) + } + } + } +} + +private fun Project.findPluginYaml(sourceSetProvider: Provider): Provider { + val fileProvider = sourceSetProvider.mapNotNull { sourceSet -> + sourceSet.resources.sourceDirectories + .map { it.resolve(PLUGIN_YML) } + .find { it.isFile } + } + + return layout.file(fileProvider) +} + +/** + * We use custom implementation of `setConventionsFromProjectMeta` that uses providers to defer project fields reading. + */ +private fun BukkitPluginYaml.setConventionsFromProjectMeta(project: Project, bukkit: BukkitExtension) { + name.convention(project.name) // Project name is immutable, no need to wrap it with provider + description.convention(project.provider { project.description }) + version.convention(project.provider { project.version.toString() }) + apiVersion.convention(bukkit.parsedApiVersion.map(::resolveDefaultApiVersion)) + + // Kept for backward compatibility + // TODO: This is a bit unobvious behavior, so probably we should remove defaults for these properties + main.convention(name.mapNotNull { + if (project.group.toString().isEmpty()) return@mapNotNull null + "${project.group}.${StringUtils.toPascalCase(it)}" } + ) + website.convention( + project.providers.gradleProperty("url") + .orElse(project.providers.gradleProperty("website")) + ) +} + +private fun resolveDefaultApiVersion(version: MinecraftVersion): String = when { + // "API version" has been introduced in Spigot 1.13 + version < MinecraftVersion.V1_13_0 -> "" + // From 1.20.5 and onward, a patch version is supported. + version < MinecraftVersion.V1_20_5 -> version.withoutPatch().toString() + else -> version.toString() +} + +public val Bukkit.plugin: BukkitPluginYaml + get() = (this as ExtensionAware).the() + +public fun Bukkit.plugin(configure: BukkitPluginYaml.() -> Unit) { + (this as ExtensionAware).configure(configure) +} diff --git a/src/main/kotlin/plugin/extension/PluginConfiguration.kt b/src/main/kotlin/plugin/extension/PluginConfiguration.kt deleted file mode 100644 index 9a796ff..0000000 --- a/src/main/kotlin/plugin/extension/PluginConfiguration.kt +++ /dev/null @@ -1,37 +0,0 @@ -package ru.endlesscode.bukkitgradle.plugin.extension - -import org.gradle.api.provider.Provider -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.Optional - -public interface PluginConfiguration { - - @get:Internal - public val generatePluginYaml: Provider - - @get:Input - public val name: Provider - - @get:Optional - @get:Input - public val description: Provider - - @get:Input - public val main: Provider - - @get:Input - public val version: Provider - - @get:Optional - @get:Input - public val apiVersion: Provider - - @get:Optional - @get:Input - public val url: Provider - - @get:Optional - @get:Input - public val authors: Provider> -} diff --git a/src/main/kotlin/plugin/extension/PluginConfigurationImpl.kt b/src/main/kotlin/plugin/extension/PluginConfigurationImpl.kt deleted file mode 100644 index 3b400d6..0000000 --- a/src/main/kotlin/plugin/extension/PluginConfigurationImpl.kt +++ /dev/null @@ -1,29 +0,0 @@ -package ru.endlesscode.bukkitgradle.plugin.extension - -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.Property -import org.gradle.kotlin.dsl.listProperty -import org.gradle.kotlin.dsl.property -import ru.endlesscode.bukkitgradle.extensions.finalizedOnRead - -public class PluginConfigurationImpl(objects: ObjectFactory) : PluginConfiguration { - - private val _generatePluginYaml: Property = objects.property() - .convention(true) - .finalizedOnRead() - override val generatePluginYaml: Property = _generatePluginYaml - - override val name: Property = objects.property() - override val description: Property = objects.property() - override val main: Property = objects.property() - override val version: Property = objects.property() - override val apiVersion: Property = objects.property() - override val url: Property = objects.property() - override val authors: ListProperty = objects.listProperty() - - /** Disables plugin.yaml parsing and generation. */ - public fun disablePluginYamlGeneration() { - _generatePluginYaml.set(false) - } -} diff --git a/src/main/kotlin/plugin/task/MergePluginYaml.kt b/src/main/kotlin/plugin/task/MergePluginYaml.kt deleted file mode 100644 index 9a4434f..0000000 --- a/src/main/kotlin/plugin/task/MergePluginYaml.kt +++ /dev/null @@ -1,65 +0,0 @@ -package ru.endlesscode.bukkitgradle.plugin.task - -import com.charleskorn.kaml.Yaml -import kotlinx.serialization.encodeToString -import org.gradle.api.DefaultTask -import org.gradle.api.file.ProjectLayout -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.Property -import org.gradle.api.provider.ProviderFactory -import org.gradle.api.tasks.* -import ru.endlesscode.bukkitgradle.TASKS_GROUP_BUKKIT -import ru.endlesscode.bukkitgradle.plugin.PluginConfigurationPlugin -import ru.endlesscode.bukkitgradle.plugin.PluginYaml -import ru.endlesscode.bukkitgradle.plugin.extension.PluginConfiguration -import java.io.File -import javax.inject.Inject - -/** - * Task that generates plugin.yml for bukkit plugin. - * - * @see ru.endlesscode.bukkitgradle.plugin.PluginConfigurationPlugin - */ -public abstract class MergePluginYaml @Inject internal constructor( - projectLayout: ProjectLayout, - providers: ProviderFactory -): DefaultTask() { - - @get:Internal - internal lateinit var yaml: Yaml - - @get:Nested - internal lateinit var plugin: PluginConfiguration - - @get:Input - internal abstract val pluginYaml: Property - - @get:OutputFile - public abstract val target: RegularFileProperty - - init { - group = TASKS_GROUP_BUKKIT - description = "Generate plugin.yml file" - - val defaultTargetProvider = providers.provider { File(temporaryDir, PluginConfigurationPlugin.FILE_NAME) } - target.convention(projectLayout.file(defaultTargetProvider)) - } - - /** Writes plugin to a target file */ - @TaskAction - public fun mergePluginYaml() { - val merged = pluginYaml.get().copy( - main = plugin.main.get(), - name = plugin.name.get(), - description = plugin.description.orNull, - version = plugin.version.get(), - apiVersion = plugin.apiVersion.orNull?.takeIf { it.isNotEmpty() }, - website = plugin.url.orNull, - authors = plugin.authors.get().takeIf { it.isNotEmpty() } - ) - - target.get() - .asFile - .writeText(yaml.encodeToString(merged)) - } -} diff --git a/src/main/kotlin/plugin/task/ParsePluginYaml.kt b/src/main/kotlin/plugin/task/ParsePluginYaml.kt index 81f761b..a61aaa9 100644 --- a/src/main/kotlin/plugin/task/ParsePluginYaml.kt +++ b/src/main/kotlin/plugin/task/ParsePluginYaml.kt @@ -1,69 +1,102 @@ package ru.endlesscode.bukkitgradle.plugin.task -import com.charleskorn.kaml.EmptyYamlDocumentException -import com.charleskorn.kaml.Yaml -import com.charleskorn.kaml.decodeFromStream +import com.charleskorn.kaml.* import org.gradle.api.DefaultTask +import org.gradle.api.GradleException import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.Provider -import org.gradle.api.provider.ProviderFactory -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.Optional -import org.gradle.api.tasks.TaskAction +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* import ru.endlesscode.bukkitgradle.TASKS_GROUP_BUKKIT -import ru.endlesscode.bukkitgradle.plugin.PluginYaml -import ru.endlesscode.bukkitgradle.plugin.extension.PluginConfigurationImpl -import javax.inject.Inject +import ru.endlesscode.bukkitgradle.plugin.BukkitPluginYamlDefaults +import xyz.jpenilla.resourcefactory.bukkit.BukkitPluginYaml +import xyz.jpenilla.resourcefactory.bukkit.Permission -internal abstract class ParsePluginYaml @Inject constructor( - providers: ProviderFactory -) : DefaultTask() { +internal abstract class ParsePluginYaml : DefaultTask() { - @get:Internal - lateinit var yaml: Yaml + private val yaml by lazy { + Yaml( + configuration = YamlConfiguration( + strictMode = false, + decodeEnumCaseInsensitive = true, + yamlNamingStrategy = YamlNamingStrategy.KebabCase, + ) + ) + } @get:Internal - lateinit var plugin: PluginConfigurationImpl + abstract val pluginYaml: Property - @get:Optional + @get:SkipWhenEmpty @get:InputFile + @get:PathSensitive(PathSensitivity.NONE) abstract val pluginYamlFile: RegularFileProperty - private var _pluginYaml: PluginYaml? = null - - @get:Internal - val pluginYaml: Provider = providers.provider { checkNotNull(_pluginYaml) } - init { group = TASKS_GROUP_BUKKIT - description = "Parse plugin.yml file if it exists" + description = "Parse plugin.yml file if it exists and add its content as defaults to generation" } @TaskAction - fun parse() { - _pluginYaml = readFromFile() + fun parseAndSetDefaults() { + val defaults = readFromFile() + if (defaults != null) pluginYaml.get().applyConventions(defaults) } - /** Reads plugin.yaml from [pluginYamlFile] and adds conventions for specified fields. */ - private fun readFromFile(): PluginYaml { - if (!pluginYamlFile.isPresent) return PluginYaml() - - val file = pluginYamlFile.get().asFile - val pluginYaml = try { - file.inputStream().use { yaml.decodeFromStream(it) } + private fun readFromFile(): BukkitPluginYamlDefaults? { + return try { + pluginYamlFile.get().asFile + .inputStream() + .use { yaml.decodeFromStream(it) } } catch (cause: EmptyYamlDocumentException) { - return PluginYaml() + logger.debug("plugin.yml is empty – skipping defaults setting", cause) + null + } catch (cause: Exception) { + throw GradleException("Failed to parse plugin.yml: ${cause.message}.\n" + + "Ensure the file is valid YAML that matches plugin.yml schema.", cause) } + } +} - return pluginYaml.apply { - main?.let(plugin.main::convention) - name?.let(plugin.name::convention) - description?.let(plugin.description::convention) - version?.let(plugin.version::convention) - apiVersion?.let(plugin.apiVersion::convention) - website?.let(plugin.url::convention) - authors?.let(plugin.authors::convention) - } +private fun BukkitPluginYaml.applyConventions(defaults: BukkitPluginYamlDefaults) { + defaults.main?.let(main::convention) + defaults.name?.let(name::convention) + defaults.description?.let(description::convention) + defaults.prefix?.let(prefix::convention) + defaults.version?.let(version::convention) + defaults.apiVersion?.let(apiVersion::convention) + defaults.load?.let(load::convention) + defaults.author?.let(author::convention) + defaults.authors?.let(authors::convention) + defaults.website?.let(website::convention) + defaults.depend?.let(depend::convention) + defaults.softdepend?.let(softDepend::convention) + defaults.loadbefore?.let(loadBefore::convention) + defaults.provides?.let(provides::convention) + defaults.libraries?.let(libraries::convention) + defaults.defaultPermission?.let(defaultPermission::convention) + defaults.foliaSupported?.let(foliaSupported::convention) + defaults.paperPluginLoader?.let(paperPluginLoader::convention) + defaults.paperSkipLibraries?.let(paperSkipLibraries::convention) + + for ((name, command) in defaults.commands) { + commands.maybeCreate(name).applyConventions(command) + } + + for ((name, permission) in defaults.permissions) { + permissions.maybeCreate(name).applyConventions(permission) } } + +private fun BukkitPluginYaml.Command.applyConventions(defaults: BukkitPluginYamlDefaults.Command) { + defaults.description?.let(description::convention) + defaults.aliases?.let(aliases::convention) + defaults.permission?.let(permission::convention) + defaults.permissionMessage?.let(permissionMessage::convention) + defaults.usage?.let(usage::convention) +} + +private fun Permission.applyConventions(defaults: BukkitPluginYamlDefaults.Permission) { + defaults.description?.let(description::convention) + defaults.default?.let(default::convention) + defaults.children?.let(children::convention) +} diff --git a/src/main/kotlin/server/DevServerPlugin.kt b/src/main/kotlin/server/DevServerPlugin.kt index 5968d15..04d9187 100644 --- a/src/main/kotlin/server/DevServerPlugin.kt +++ b/src/main/kotlin/server/DevServerPlugin.kt @@ -3,7 +3,6 @@ package ru.endlesscode.bukkitgradle.server import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.file.Directory -import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.provider.Provider import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.TaskProvider @@ -11,6 +10,7 @@ import org.gradle.jvm.toolchain.JavaToolchainService import org.gradle.kotlin.dsl.* import ru.endlesscode.bukkitgradle.Bukkit import ru.endlesscode.bukkitgradle.bukkit +import ru.endlesscode.bukkitgradle.extensions.java import ru.endlesscode.bukkitgradle.plugin.util.resolveMinimalJavaVersion import ru.endlesscode.bukkitgradle.server.extension.ServerConfiguration import ru.endlesscode.bukkitgradle.server.task.CreateIdeaGradleRunConfiguration @@ -54,7 +54,7 @@ public class DevServerPlugin : Plugin { } // RunPaperPlugin uses afterEvaluate under the hood, so we have to use afterEvaluate - // to set our conventions after theirs + // to set our conventions after their ones project.afterEvaluate { configureDefaultJvmForServer() } val prepareServer = registerPrepareServerTask(runServer) @@ -65,7 +65,7 @@ public class DevServerPlugin : Plugin { private fun Project.configureDefaultJvmForServer() { val toolchains = project.extensions.findByType() ?: return - val spec = the().toolchain + val spec = java.toolchain tasks.withType().configureEach { javaLauncher.convention( diff --git a/src/test/groovy/ru/endlesscode/bukkitgradle/BukkitGradlePluginSpec.groovy b/src/test/groovy/ru/endlesscode/bukkitgradle/BukkitGradlePluginSpec.groovy index 96a4336..7d46aa3 100644 --- a/src/test/groovy/ru/endlesscode/bukkitgradle/BukkitGradlePluginSpec.groovy +++ b/src/test/groovy/ru/endlesscode/bukkitgradle/BukkitGradlePluginSpec.groovy @@ -7,11 +7,6 @@ class BukkitGradlePluginSpec extends PluginSpecification { project.apply(plugin: BukkitGradlePlugin) } - def "when initialized - should add required plugins"() { - expect: "java plugin added" - project.pluginManager.hasPlugin("java") - } - def "when initialized - should set default JVM toolchain"(String apiVersion, int jvmVersion) { when: "apiVersion is set" project.bukkit.apiVersion = apiVersion diff --git a/src/test/groovy/ru/endlesscode/bukkitgradle/PluginSpecification.groovy b/src/test/groovy/ru/endlesscode/bukkitgradle/PluginSpecification.groovy index 17a5a15..963e36e 100644 --- a/src/test/groovy/ru/endlesscode/bukkitgradle/PluginSpecification.groovy +++ b/src/test/groovy/ru/endlesscode/bukkitgradle/PluginSpecification.groovy @@ -82,6 +82,6 @@ class PluginSpecification extends Specification { } protected TaskOutcome taskOutcome(String task) { - return result.task(task).outcome + return result.task(task)?.outcome } } diff --git a/src/test/groovy/ru/endlesscode/bukkitgradle/plugin/task/GeneratePluginYamlSpec.groovy b/src/test/groovy/ru/endlesscode/bukkitgradle/plugin/task/GeneratePluginYamlSpec.groovy new file mode 100644 index 0000000..1975050 --- /dev/null +++ b/src/test/groovy/ru/endlesscode/bukkitgradle/plugin/task/GeneratePluginYamlSpec.groovy @@ -0,0 +1,415 @@ +package ru.endlesscode.bukkitgradle.plugin.task + + +import org.gradle.testkit.runner.TaskOutcome +import ru.endlesscode.bukkitgradle.PluginSpecification +import ru.endlesscode.bukkitgradle.plugin.PluginYamlFeatureKt +import ru.endlesscode.bukkitgradle.util.CharsetUtils + +class GeneratePluginYamlSpec extends PluginSpecification { + + private final static RESOURCE_FACTORY = ':mainResourceFactory' + private final static PARSE_PLUGIN_YAML = ':parsePluginYaml' + + private File pluginYamlFile + + def setup() { + pluginYamlFile = file("src/main/resources/$PluginYamlFeatureKt.PLUGIN_YML") + + buildFile << """ + bukkit.apiVersion = "1.16.2" + """.stripIndent() + } + + def 'when run processResources - should also run resource factory'() { + when: + run(':processResources') + + then: + taskOutcome(RESOURCE_FACTORY) == TaskOutcome.SUCCESS + + and: + taskOutcome(PARSE_PLUGIN_YAML) == TaskOutcome.SUCCESS + } + + def 'when run processResources - and plugin.yml generation disabled - should not run resource factory'() { + given: + buildFile << "bukkit.generatePluginYaml = false" + + when: + run(':processResources') + + then: + taskOutcome(RESOURCE_FACTORY) == TaskOutcome.SKIPPED + + and: + taskOutcome(PARSE_PLUGIN_YAML) == null + } + + def 'when run processResources - and plugin.yml doesnt exist - should skip parsing'() { + given: + pluginYamlFile.delete() + + when: + run(':processResources') + + then: + taskOutcome(RESOURCE_FACTORY) == TaskOutcome.SUCCESS + + and: + taskOutcome(PARSE_PLUGIN_YAML) == null + } + + def 'when run parsing - and plugin.yml doesnt exist - should show NO_SOURCE'() { + given: + pluginYamlFile.delete() + + when: + run(PARSE_PLUGIN_YAML) + + then: + taskOutcome(PARSE_PLUGIN_YAML) == TaskOutcome.NO_SOURCE + } + + def 'when generate plugin.yml - should generate default plugin.yml successfully'() { + when: + run(RESOURCE_FACTORY) + + then: "plugin file content corresponds to default config" + pluginYamlFile.text == """\ + api-version: '1.16' + name: test-plugin + version: '1.0' + main: com.example.testplugin.TestPlugin + """.stripIndent() + } + + def 'when generate plugin.yml - should set api-version'(String apiVersion, String expectedResult) { + when: "api version is $apiVersion" + buildFile << """ + bukkit.apiVersion = "$apiVersion" + """.stripIndent() + + and: + run(RESOURCE_FACTORY) + + then: "plugin file content corresponds to default config" + pluginYamlFile.text == """\ + api-version: $expectedResult + name: test-plugin + version: '1.0' + main: com.example.testplugin.TestPlugin + """.stripIndent() + + where: + apiVersion | expectedResult + "1.16.5" | "'1.16'" + "1.20.4" | "'1.20'" + "1.20.5" | "1.20.5" + "1.21.1" | "1.21.1" + } + + def 'when generate plugin.yml - and generate it again - should skip second task run'() { + when: + run(RESOURCE_FACTORY) + def firstRunOutcome = taskOutcome(RESOURCE_FACTORY) + + and: "run generation again" + run(RESOURCE_FACTORY) + def secondRunOutcome = taskOutcome(RESOURCE_FACTORY) + + then: + firstRunOutcome == TaskOutcome.SUCCESS + secondRunOutcome == TaskOutcome.UP_TO_DATE + } + + def 'when generate plugin.yml - and changed plugin configuration - should update plugin.yml'() { + when: + run(RESOURCE_FACTORY) + + and: "changed description" + buildFile << 'description = "Plugin description goes here"' + + and: + run(RESOURCE_FACTORY) + + then: + taskOutcome(RESOURCE_FACTORY) == TaskOutcome.SUCCESS + + and: "plugin generated with new description" + pluginYamlFile.text == """\ + api-version: '1.16' + name: test-plugin + version: '1.0' + main: com.example.testplugin.TestPlugin + description: Plugin description goes here + """.stripIndent() + } + + void 'when generate plugin.yml - and properties configured using set'() { + given: + //language=kotlin + buildFile << """ + bukkit { + plugin { + name.set("TestPlugin") + description.set("Test plugin description") + main.set("com.example.plugin.Plugin") + version.set("0.1") + apiVersion.set("1.13") + website.set("https://www.example.com/") + authors.set(listOf("OsipXD", "Contributors")) + } + } + """.stripIndent() + + when: + run(RESOURCE_FACTORY) + + then: + pluginYamlFile.text == """\ + api-version: '1.13' + name: TestPlugin + version: '0.1' + main: com.example.plugin.Plugin + description: Test plugin description + authors: + - OsipXD + - Contributors + website: https://www.example.com/ + """.stripIndent() + } + + void 'when generate plugin.yml - and properties configured using assignment'() { + given: + //language=kotlin + buildFile << """ + bukkit { + plugin { + name = "TestPlugin" + description = "Test plugin description" + main = "com.example.plugin.Plugin" + version = "0.1" + apiVersion = "1.13" + website = "https://www.example.com/" + authors = listOf("OsipXD", "Contributors") + } + } + """.stripIndent() + + when: + run(RESOURCE_FACTORY) + + then: + pluginYamlFile.text == """\ + api-version: '1.13' + name: TestPlugin + version: '0.1' + main: com.example.plugin.Plugin + description: Test plugin description + authors: + - OsipXD + - Contributors + website: https://www.example.com/ + """.stripIndent() + } + + void 'when generate plugin.yml - should parse commands and permissions'() { + given: + pluginYamlFile.text = """\ + depend: [Vault, ProtocolLib] + default-permission: not op + commands: + example: + description: Just a command + permissions: + example.foo: + description: My foo permission + """.stripIndent() + + when: + run(RESOURCE_FACTORY) + + then: + pluginYamlFile.text == """\ + api-version: '1.16' + name: test-plugin + version: '1.0' + main: com.example.testplugin.TestPlugin + depend: + - Vault + - ProtocolLib + default-permission: not op + commands: + example: + description: Just a command + permissions: + example.foo: + description: My foo permission + """.stripIndent() + } + + // BukkitGradle-26 + void 'when generate plugin.yml - and there are exotic chars in source - should read it correctly'() { + given: "source plugin file with exotic chars" + pluginYamlFile.text = """\ + commands: + 퀘스트: + description: 퀘스트 명령어 입니다. + """.stripIndent() + + and: "default charset differs from UTF-8" + CharsetUtils.setDefaultCharset('CP866') + + when: "run processResources" + try { + run(RESOURCE_FACTORY) + } finally { + CharsetUtils.setDefaultCharset('UTF-8') + } + + then: + pluginYamlFile.text == """\ + api-version: '1.16' + name: test-plugin + version: '1.0' + main: com.example.testplugin.TestPlugin + commands: + 퀘스트: + description: 퀘스트 명령어 입니다. + """.stripIndent() + } + + void 'when generate plugin.yml - and there are fields in source - should prefer values from source'() { + given: "plugin.yml file with some fields" + pluginYamlFile.text = """\ + name: SourceValue + version: 1.2 + """.stripIndent() + + when: "run processResources" + run(RESOURCE_FACTORY) + + then: "should write plugin and keep values of the source file" + pluginYamlFile.text == """\ + api-version: '1.16' + name: SourceValue + version: '1.2' + main: com.example.testplugin.SourceValue + """.stripIndent() + } + + void 'when generate plugin.yml - and there are conflicting values'() { + given: + pluginYamlFile.text = """\ + name: SourceValue + version: 1.2 + commands: + source-command: + description: A command from source + permissions: + source.permission: + description: A permission from source + """.stripIndent() + + and: "conflicting fields in build script" + buildFile << """ + bukkit { + plugin { + name = "BuildscriptValue" + version = "1.3" + commands { + register("buildscript-command") { + description = "A command from build script" + } + } + permissions { + register("buildscript.permission") { + description = "A permission from build script" + } + } + } + } + """.stripIndent() + + when: + run(RESOURCE_FACTORY) + + then: "should overwrite source fields" + pluginYamlFile.text == """\ + api-version: '1.16' + name: BuildscriptValue + version: '1.3' + main: com.example.testplugin.BuildscriptValue + commands: + buildscript-command: + description: A command from build script + source-command: + description: A command from source + permissions: + buildscript.permission: + description: A permission from build script + source.permission: + description: A permission from source + """.stripIndent() + } + + void 'when generate plugin.yml - and it is already full of values - should keep existing file'() { + given: + def content = """\ + api-version: '1.13' + name: TestPlugin + version: '0.1' + main: com.example.plugin.Plugin + description: Test plugin description + load: STARTUP + author: MainAuthor + authors: + - OsipXD + - Contributors + website: https://www.example.com/ + depend: + - Dependency1 + - Dependency2 + softdepend: + - SoftDependency1 + - SoftDependency2 + loadbefore: + - LoadBefore1 + - LoadBefore2 + prefix: TestPrefix + default-permission: op + provides: + - FeatureA + - FeatureB + libraries: + - org.example:library:1.0 + - com.test:util:2.0 + commands: + test: + description: A test command + aliases: + - t + - tst + permission: test.command + permission-message: You need permission + usage: /test + permissions: + test.command: + description: Existing command permission + default: op + children: + test.command.child: true + folia-supported: true + paper-plugin-loader: custom.plugin.Loader + paper-skip-libraries: false + """.stripIndent() + pluginYamlFile.text = content + + when: + run(RESOURCE_FACTORY) + + then: "the file content should remain the same" + pluginYamlFile.text == content + } +} diff --git a/src/test/groovy/ru/endlesscode/bukkitgradle/plugin/task/MergePluginYamlSpec.groovy b/src/test/groovy/ru/endlesscode/bukkitgradle/plugin/task/MergePluginYamlSpec.groovy deleted file mode 100644 index c674467..0000000 --- a/src/test/groovy/ru/endlesscode/bukkitgradle/plugin/task/MergePluginYamlSpec.groovy +++ /dev/null @@ -1,291 +0,0 @@ -package ru.endlesscode.bukkitgradle.plugin.task - - -import org.gradle.testkit.runner.TaskOutcome -import ru.endlesscode.bukkitgradle.PluginSpecification -import ru.endlesscode.bukkitgradle.plugin.PluginConfigurationPlugin -import ru.endlesscode.bukkitgradle.util.CharsetUtils - -class MergePluginYamlSpec extends PluginSpecification { - - private final static MERGE_PLUGIN_YAML = ':mergePluginYaml' - - private File sourcePluginYamlFile - private File pluginYamlFile - - def setup() { - sourcePluginYamlFile = file("src/main/resources/$PluginConfigurationPlugin.FILE_NAME") - pluginYamlFile = file("build/tmp/mergePluginYaml/$PluginConfigurationPlugin.FILE_NAME") - - buildFile << """ - bukkit.apiVersion = "1.16.2" - """.stripIndent() - } - - def 'when run processResources - should also run mergePluginYaml'() { - when: - run(':processResources') - - then: - taskOutcome(MERGE_PLUGIN_YAML) == TaskOutcome.SUCCESS - - and: - taskOutcome(":parsePluginYaml") == TaskOutcome.SUCCESS - } - - def 'when run processResources - and plugin.yaml generation disabled - should not run mergePluginYaml'() { - given: "plugin generation disabled" - buildFile << "bukkit.plugin.disablePluginYamlGeneration()" - - when: "run processResources" - run(':processResources') - - then: "task mergePluginYaml completed successfully" - taskOutcome(MERGE_PLUGIN_YAML) == TaskOutcome.SKIPPED - - and: "task mergePluginYaml completed successfully" - taskOutcome(":parsePluginYaml") == TaskOutcome.SKIPPED - } - - def 'when merge plugin.yaml - should generate default plugin.yaml successfully'() { - when: - run(MERGE_PLUGIN_YAML) - - then: "plugin file content corresponds to default config" - pluginYamlFile.text == """ - main: "com.example.testplugin.TestPlugin" - name: "test-plugin" - version: "1.0" - api-version: "1.16" - """.stripIndent().trim() - } - - def 'when merge plugin.yaml - should set api-version'(String apiVersion, String expectedResult) { - when: "api version is $apiVersion" - buildFile << """ - bukkit.apiVersion = "$apiVersion" - """.stripIndent() - - and: - run(MERGE_PLUGIN_YAML) - - then: "plugin file content corresponds to default config" - pluginYamlFile.text == """ - main: "com.example.testplugin.TestPlugin" - name: "test-plugin" - version: "1.0" - api-version: "$expectedResult" - """.stripIndent().trim() - - where: - apiVersion | expectedResult - "1.16.5" | "1.16" - "1.20.4" | "1.20" - "1.20.5" | "1.20.5" - "1.21.1" | "1.21.1" - } - - def 'when merge plugin.yaml - and generate it again - should skip second task run'() { - when: - run(MERGE_PLUGIN_YAML) - - and: "run generate again" - run(MERGE_PLUGIN_YAML) - - then: "the task is skipped due to up-to-date" - taskOutcome(MERGE_PLUGIN_YAML) == TaskOutcome.UP_TO_DATE - } - - def 'when merge plugin.yaml - and changed plugin configuration - should update plugin.yaml'() { - when: - run(MERGE_PLUGIN_YAML) - - and: "changed description" - buildFile << 'description = "Plugin can has description"' - - and: "run the task again" - run(MERGE_PLUGIN_YAML) - - then: "the task is successful" - taskOutcome(MERGE_PLUGIN_YAML) == TaskOutcome.SUCCESS - - and: "plugin generated with new description" - pluginYamlFile.text == """ - main: "com.example.testplugin.TestPlugin" - name: "test-plugin" - description: "Plugin can has description" - version: "1.0" - api-version: "1.16" - """.stripIndent().trim() - } - - void 'when merge plugin.yaml - and all properties configured - should write all lines'() { - given: "configured all plugin properties" - //language=kotlin - buildFile << """ - bukkit { - plugin { - name.set("TestPlugin") - description.set("Test plugin description") - main.set("com.example.plugin.Plugin") - version.set("0.1") - apiVersion.set("1.13") - url.set("https://www.example.com/") - authors.set(listOf("OsipXD", "Contributors")) - } - } - """.stripIndent() - - when: "run processResources" - run(MERGE_PLUGIN_YAML) - - then: "should write all lines" - pluginYamlFile.text == """ - main: "com.example.plugin.Plugin" - name: "TestPlugin" - description: "Test plugin description" - version: "0.1" - api-version: "1.13" - authors: ["OsipXD", "Contributors"] - website: "https://www.example.com/" - """.stripIndent().trim() - } - - void 'when merge plugin.yaml - and all properties configured with assignment - should write all lines'() { - given: "configured all plugin properties in old way" - //language=kotlin - buildFile << """ - bukkit { - plugin { - name = "TestPlugin" - description = "Test plugin description" - main = "com.example.plugin.Plugin" - version = "0.1" - url = "https://www.example.com/" - authors = listOf("OsipXD", "Contributors") - } - } - """.stripIndent() - - when: "run processResources" - run(MERGE_PLUGIN_YAML) - - then: "should write all lines" - pluginYamlFile.text == """ - main: "com.example.plugin.Plugin" - name: "TestPlugin" - description: "Test plugin description" - version: "0.1" - api-version: "1.16" - authors: ["OsipXD", "Contributors"] - website: "https://www.example.com/" - """.stripIndent().trim() - } - - void 'when merge plugin.yaml - and there are extra fields in source - should write all lines'() { - given: "source plugin file with extra fields" - sourcePluginYamlFile << """ - depend: [Vault, ProtocolLib] - commands: - example: - description: Just a command - permissions: - example.foo: - description: My foo permission - """.stripIndent() - - when: "run processResources" - run(MERGE_PLUGIN_YAML) - - then: "should write plugin with the extra fields" - pluginYamlFile.text == """ - main: "com.example.testplugin.TestPlugin" - name: "test-plugin" - version: "1.0" - api-version: "1.16" - depend: ["Vault", "ProtocolLib"] - commands: - "example": - description: "Just a command" - permissions: - "example.foo": - description: "My foo permission" - """.stripIndent().trim() - } - - // BukkitGradle-26 - void 'when merge plugin.yaml - and there are exotic chars in source - should read it correctly'() { - given: "source plugin file with exotic chars" - sourcePluginYamlFile << """ - commands: - 퀘스트: - description: 퀘스트 명령어 입니다. - """.stripIndent() - - and: "default charset differs from UTF-8" - CharsetUtils.setDefaultCharset('CP866') - - when: "run processResources" - run(MERGE_PLUGIN_YAML) - CharsetUtils.setDefaultCharset('UTF-8') - - then: - pluginYamlFile.text == """ - main: "com.example.testplugin.TestPlugin" - name: "test-plugin" - version: "1.0" - api-version: "1.16" - commands: - "퀘스트": - description: "퀘스트 명령어 입니다." - """.stripIndent().trim() - } - - void 'when merge plugin.yaml - and there are fields in source - should prefer values from source'() { - given: "source plugin file with extra fields" - sourcePluginYamlFile << """ - name: SourceValue - version: 1.2 - """.stripIndent() - - when: "run processResources" - run(MERGE_PLUGIN_YAML) - - then: "should write plugin and prefer source fields" - pluginYamlFile.text == """ - main: "com.example.testplugin.SourceValue" - name: "SourceValue" - version: "1.2" - api-version: "1.16" - """.stripIndent().trim() - } - - void 'when merge plugin.yaml - and there are conflicting fields in source and in build script - should prefer values from build script'() { - given: "source plugin.yaml file with extra fields" - sourcePluginYamlFile << """ - name: SourceValue - version: 1.2 - """.stripIndent() - - and: "conflicting fields in build script" - buildFile << """ - bukkit { - plugin { - name.set("BuildscriptValue") - version.set("1.3") - } - } - """.stripIndent() - - when: - run(MERGE_PLUGIN_YAML) - - then: "should overwrite source fields" - pluginYamlFile.text == """ - main: "com.example.testplugin.BuildscriptValue" - name: "BuildscriptValue" - version: "1.3" - api-version: "1.16" - """.stripIndent().trim() - } -}