Skip to content

Commit

Permalink
Rewrite the GeneratePluginList task, now uses properties/providers
Browse files Browse the repository at this point in the history
  • Loading branch information
floscher committed Jan 29, 2022
1 parent 53fec10 commit ac94d5a
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 163 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,10 @@ open class JosmPluginExtension(val project: Project) {
try {
val parser = JosmPluginListParser(this.project, false)
parser.plugins
.filter { !it.downloadUrl.toExternalForm().startsWith(Urls.MainJosmWebsite.PLUGIN_DIST_DIR.toExternalForm()) }
.filter { !it.downloadUri.toString().startsWith(Urls.MainJosmWebsite.PLUGIN_DIST_DIR.toURI().toString()) }
.forEach { pluginInfo ->
rh.ivy { repo ->
repo.url = URI(pluginInfo.downloadUrl.toExternalForm().replaceAfterLast('/', ""))
repo.url = URI(pluginInfo.downloadUri.toString().replaceAfterLast('/', ""))
repo.content {
// This constrains the repo to this specific plugin.
it.includeModule(GROUP_JOSM_PLUGIN, pluginInfo.pluginName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import org.gradle.api.Project
import org.openstreetmap.josm.gradle.plugin.task.GeneratePluginList
import org.openstreetmap.josm.gradle.plugin.util.josmPluginList
import java.net.MalformedURLException
import java.net.URL
import java.net.URI

/**
* Reads a plugin info file like it is found at [https://josm.openstreetmap.de/plugin]
Expand Down Expand Up @@ -40,7 +40,7 @@ class JosmPluginListParser(val project: Project, val withIcons: Boolean = false)

// Temporary variables to collect properties of the next PluginInfo object
var pluginName: String? = null
var downloadUrl: URL? = null
var downloadUrl: URI? = null
val manifestAtts: MutableMap<String, String> = mutableMapOf()

resolvedFiles.first().readLines().forEachIndexed { i, line ->
Expand All @@ -63,7 +63,7 @@ class JosmPluginListParser(val project: Project, val withIcons: Boolean = false)
manifestAtts.clear()
pluginName = titleLineMatch.groupValues[1].removeSuffix(".jar")
try {
downloadUrl = URL(titleLineMatch.groupValues[2])
downloadUrl = URI(titleLineMatch.groupValues[2])
} catch (e: MalformedURLException) {
errors.add("Line $i: Plugin $pluginName has a malformed download URL set!")
pluginName = null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package org.openstreetmap.josm.gradle.plugin.io

import java.net.URL
import java.io.Serializable
import java.net.URI

/**
* Encapsulates the plugin metadata that can be found for each plugin in the plugin list.
* @property pluginName the name of the JOSM plugin
* @property downloadUrl the URL from where the plugin can be downloaded
* @property downloadUri the URI from where the plugin can be downloaded
* @property manifestAtts the attributes of the `MANIFEST.MF` file of the plugin
*/
data class PluginInfo(val pluginName: String, val downloadUrl: URL, val manifestAtts: Map<String, String>) {
companion object {
fun build(pluginName: String?, downloadUrl: URL?, manifestAtts: Map<String, String>) = pluginName?.let { name ->
downloadUrl?.let { url ->
PluginInfo(name, url, manifestAtts.toMap())
public data class PluginInfo(val pluginName: String, val downloadUri: URI, val manifestAtts: Map<String, String>): Serializable {
public companion object {
public fun build(pluginName: String?, downloadUri: URI?, manifestAtts: Map<String, String>): PluginInfo? = pluginName?.let { name ->
downloadUri?.let { uri ->
PluginInfo(name, uri, manifestAtts.toMap())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,100 +1,130 @@
package org.openstreetmap.josm.gradle.plugin.task

import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskExecutionException
import org.openstreetmap.josm.gradle.plugin.config.JosmManifest
import org.openstreetmap.josm.gradle.plugin.io.PluginInfo
import org.openstreetmap.josm.gradle.plugin.util.toBase64DataUrl
import java.io.File
import java.io.IOException
import java.io.Serializable
import java.net.URL
import java.util.GregorianCalendar
import javax.inject.Inject

open class GeneratePluginList : DefaultTask() {
/**
* A task that can write a plugin update site. JOSM can be pointed to a URL of such an update site,
* so the plugins in that list can be installed via the regular JOSM update mechanism in the JOSM settings.
*
* @property iconPathToFile a function that returns a [File] object for a (relative) icon path.
* This is used to convert a manifest entry for [JosmManifest.Attribute.PLUGIN_ICON] from a relative path
* to a Base64-Data-URL. If you don't need this functionality, just pass `{ it -> null }`, which will always return `null`.
*/
public open class GeneratePluginList @Inject constructor(
private val iconPathToFile: (String) -> File?
): DefaultTask() {

/**
* Maps the plugin name to the manifest attributes and the download URL of the plugin
* All plugins that should appear in the list (as [PluginInfo]s).
*/
private val plugins: MutableList<PluginInfo> = mutableListOf()

@Input
val immutablePlugins = plugins.toList()
@get:Input
public val plugins: SetProperty<PluginInfo> = project.objects.setProperty(PluginInfo::class.java)

/**
* The file to which this task writes the plugin list, will be overwritten if it exists.
* This parameter is required.
*/
@OutputFile
lateinit var outputFile: File
@get:OutputFile
public val outputFile: RegularFileProperty = project.objects.fileProperty()

/**
* Optional parameter, converts a relative icon path (you decide relative to what,
* this class does not make assumptions about that) to a Base64 representation.
* This parameter is optional, by default or if it returns `null`, the icon path is added as-is to the list.
* Maps plugins to icon files, helper to connect [plugins] and [iconFiles].
*/
@Internal
var iconBase64Provider: (String) -> String? = { _ -> null }
private val iconFileMap: Provider<Map<PluginInfo, File>> = plugins.map { plugins ->
plugins.mapNotNull { pi ->
pi.manifestAtts.entries
.firstOrNull { (key, _) -> key == JosmManifest.Attribute.PLUGIN_ICON.manifestKey }
?.value
?.let { iconPathToFile(it) }
?.let { pi to it }
}.toMap()
}

/**
* This field is only here, so Gradle can serialize [iconBase64Provider] for up-to-date-checking/caching
* A list of icon files derived from [plugins] and [iconPathToFile].
* This is marked as input files, so that changes in the icon files are detected and trigger a regeneration.
*/
@Input
val serializableIconProvider = iconBase64Provider as Serializable
@get:InputFiles
public val iconFiles: Provider<Set<File>> = iconFileMap.map { prop -> prop.values.map { it.canonicalFile }.toSet() }

/**
* A function that gives you a suffix that's appended to the plugin version. It takes the plugin name as an argument.
* A version suffix that's appended to the plugin version(s).
*/
@Internal
var versionSuffix: (String) -> String? = { _ -> '#' + String.format("%1\$tY-%1\$tm-%1\$tdT%1\$tH:%1\$tM:%1\$tS%1\$tz", GregorianCalendar()) }
@get:Input
public val versionSuffix: Property<String> = project.objects.property(String::class.java).convention("")

/**
* This field is only here, so Gradle can serialize [versionSuffix] for up-to-date-checking/caching
*/
@Input
val serializableVersionSuffix = versionSuffix as Serializable
@Internal
final override fun getGroup(): String = "JOSM"
final override fun setGroup(group: String?): Nothing = throw UnsupportedOperationException(
"Can't change group of ${javaClass.name}!"
)

@TaskAction
fun action() {
logger.lifecycle("Writing list of ${plugins.size} plugins to ${outputFile.absolutePath}")
val fileBuilder = StringBuilder()
public open fun action() {
val plugins: Set<PluginInfo> = plugins.also{ it.finalizeValue() }.get()
val outputFile: File = outputFile.also { it.finalizeValue() }.asFile.get()

plugins.sortedBy { it.pluginName }.forEach { (name, url, manifestAtts) ->
fileBuilder
.append(name)
.append(';')
.append(url)
.append('\n')
manifestAtts.forEach { key, value ->
fileBuilder
.append('\t')
.append(key)
.append(": ")
.append(when (key) {
JosmManifest.Attribute.PLUGIN_ICON.manifestKey -> iconBase64Provider.invoke(value) ?: value
JosmManifest.Attribute.PLUGIN_VERSION.manifestKey -> value + versionSuffix.invoke(name)
else -> value
})
.append('\n')
}
}
logger.lifecycle("Writing list of ${plugins.size} plugin${if (plugins.size > 1) "s" else ""} to ${outputFile.absolutePath}")

if (!outputFile.parentFile.exists() && !outputFile.parentFile.mkdirs()) {
throw TaskExecutionException(this, IOException("Can't create directory ${outputFile.parentFile.absolutePath}!"))
}
outputFile.writeText(fileBuilder.toString(), Charsets.UTF_8)
}

/**
* Add a plugin that should appear in the list
* @param name the name of the plugin *.jar file (including file extension), e.g. `MyAwesomePlugin.jar`
* @param atts the main attributes of the plugin manifest, e.g. supplied by [JosmManifest.createJosmPluginJarManifest]
* @param downloadUrl the URL from which the plugin can be downloaded
*/
fun addPlugin(name: String, atts: Map<String, String>, downloadUrl: URL) {
plugins.add(PluginInfo(name, downloadUrl, atts))
outputFile.writeText(
plugins.sortedBy { it.pluginName }.flatMap {
listOf("${it.pluginName}.jar;${it.downloadUri}") +
it.manifestAtts.map { (key, value) ->
"\t$key: " +
when (key) {
JosmManifest.Attribute.PLUGIN_ICON.manifestKey ->
try {
iconFileMap.get()[it]?.toBase64DataUrl()
} catch (e: IOException) {
logger.lifecycle("Error reading")
} ?: value
JosmManifest.Attribute.PLUGIN_VERSION.manifestKey ->
value + versionSuffix.get()
else -> value
}
}
}.joinToString("\n", "", "\n")
)

logger.lifecycle("""
|
|The list contains:
|${
plugins.joinToString("\n", " * ") { pluginInfo ->
pluginInfo.pluginName +
(
pluginInfo.manifestAtts.entries
.firstOrNull { (key, _) -> key == JosmManifest.Attribute.PLUGIN_VERSION.manifestKey }
?.value
?.let { " ($it)" }
?: ""
)
}
}
|
|${"By adding the following URL as a JOSM plugin update site (see tab 'Plugins' in expert mode), " +
"you can load the current development state into a JOSM instance via the regular plugin update mechanism:"}
| ${outputFile.toURI()}
""".trimMargin())
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.openstreetmap.josm.gradle.plugin.task

import org.gradle.api.DefaultTask
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFile
Expand All @@ -25,7 +26,7 @@ import java.io.File
*/
open class InitJosmPreferences: DefaultTask() {
@get:InputFiles
val pluginDistTask: TaskProvider<out Sync> = project.tasks.named("dist", Sync::class.java)
val pluginDistTask: TaskProvider<Task> = project.tasks.named("dist")

@get:Input
val pluginName: Provider<String> = project.provider { project.extensions.josm.pluginName }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package org.openstreetmap.josm.gradle.plugin.task

import org.gradle.api.Transformer
import org.gradle.api.DefaultTask
import org.gradle.api.file.Directory
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.AbstractCopyTask
import org.gradle.api.tasks.Sync
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.AbstractArchiveTask
Expand All @@ -15,34 +18,35 @@ import javax.inject.Inject
* Task that simply copies the archive file produced by the given [archiverTask]
*/
public open class RenameArchiveFile @Inject constructor(
private val archiverTask: TaskProvider<out AbstractArchiveTask>,
private val targetDir: Provider<out Directory>,
fileBaseName: Provider<out String>
): Sync() {
@get:InputFiles
public val archiverTask: TaskProvider<AbstractArchiveTask>,
@get:Internal
public val targetDir: Provider<out Directory>,
@get:Input
public val fileBaseName: Provider<out String>
): DefaultTask() {

private val fileName by lazy(LazyThreadSafetyMode.PUBLICATION) {
"${fileBaseName.get()}.${archiverTask.get().archiveExtension.get()}"
}

init {
from(archiverTask.map { it.archiveFile })
into(targetDir)
rename { fileName }
duplicatesStrategy = DuplicatesStrategy.FAIL
private val fileName: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
"${fileBaseName.get()}.${archiverTask.get().archiveExtension.get()}"
}

final override fun from(vararg sourcePaths: Any?): AbstractCopyTask = super.from(*sourcePaths)
final override fun into(destDir: Any): AbstractCopyTask = super.into(destDir)
final override fun rename(renamer: Transformer<String, String>): AbstractCopyTask = super.rename(renamer)
@get:OutputFile
public val targetFile: Provider<RegularFile> = targetDir.map { it.file(fileName) }

@TaskAction
override fun copy() {
super.copy()
public fun copy() {
project.sync {
it.from(archiverTask.map { it.archiveFile })
it.into(targetDir)
it.rename { fileName }
it.duplicatesStrategy = DuplicatesStrategy.FAIL
}
logger.lifecycle(
"Distribution {} (version {}) has been written into {}",
fileName,
project.version,
targetDir.get().asFile.canonicalPath
"""
Copied file
from ${archiverTask.get().archiveFile.get()}
into ${targetDir.get().asFile.canonicalPath}/$fileName
""".trimIndent()
)
}
}
Loading

0 comments on commit ac94d5a

Please sign in to comment.