Merge pull request #1347 from Stefterv/library-plugin

Gradle Plugin for creating Processing Libraries
This commit is contained in:
Stef Tervelde
2026-02-02 13:42:05 +01:00
committed by GitHub
7 changed files with 330 additions and 12 deletions

View File

@@ -0,0 +1,28 @@
plugins {
`java-gradle-plugin`
kotlin("jvm") version "2.2.20"
}
gradlePlugin {
plugins {
create("processing.library") {
id = "org.processing.library"
implementationClass = "ProcessingLibraryPlugin"
}
}
}
repositories {
mavenCentral()
}
dependencies {
testImplementation(kotlin("test"))
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(17)
}

View File

@@ -0,0 +1,77 @@
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.javadoc.Javadoc
abstract class BundleLibraryFilesTask : DefaultTask() {
@Input
var configuration: ProcessingLibraryConfiguration? = null
@OutputDirectory
val outputDir = project.objects.directoryProperty()
init {
outputDir.convention(project.layout.buildDirectory.dir("library"))
}
@TaskAction
fun bundle() {
val configuration = configuration
?: throw GradleException("Processing library configuration must be provided.")
val libraryName = configuration.name ?: project.name
val buildDir = project.layout.buildDirectory.dir("library/$libraryName").get().asFile
buildDir.mkdirs()
val libDir = buildDir.resolve("library")
libDir.mkdirs()
// Copy the jar file
val jarFile = project.tasks.named("jar", Jar::class.java).get().archiveFile.get().asFile
jarFile.copyTo(libDir.resolve("$libraryName.jar"), overwrite = true)
// Copy all runtime dependencies
val runtimeClasspath = project.configurations.getByName("runtimeClasspath")
runtimeClasspath.resolvedConfiguration.resolvedArtifacts.forEach { artifact ->
val depFile = artifact.file
depFile.copyTo(libDir.resolve(depFile.name), overwrite = true)
}
// Copy Examples folder
val examplesDir = project.projectDir.resolve("examples")
if (!examplesDir.exists() || !examplesDir.isDirectory) {
throw GradleException("Examples folder not found in project directory.")
}
examplesDir.copyRecursively(buildDir.resolve("examples"), overwrite = true)
// Copy javadoc to reference folder
val docsDir = project.tasks.named("javadoc", Javadoc::class.java).get().destinationDir
docsDir?.copyRecursively(buildDir.resolve("reference"), overwrite = true)
// Create library.properties file
val propertiesFile = buildDir.resolve("library.properties")
propertiesFile.bufferedWriter().use { writer ->
val properties = mapOf(
"name" to libraryName,
"version" to (configuration.version ?: "1.0.0"),
"prettyVersion" to (configuration.prettyVersion ?: configuration.version ?: "1.0.0"),
"authors" to (configuration.authors.entries.joinToString(", ") { "[${it.key}](${it.value})" }),
"url" to configuration.url,
"category" to configuration.categories.joinToString(", "),
"sentence" to configuration.sentence,
"paragraph" to configuration.paragraph,
"minRevision" to configuration.minRevision,
"maxRevision" to configuration.maxRevision
)
properties
.filter { it.value != null && it.value.toString().isNotEmpty() }
.forEach { (key, value) ->
writer.write("$key=$value\n")
}
}
propertiesFile.copyTo(buildDir.resolve("../$libraryName.txt"), overwrite = true)
}
}

View File

@@ -0,0 +1,64 @@
import org.gradle.api.Action
import org.gradle.api.model.ObjectFactory
import java.io.Serializable
import javax.inject.Inject
open class ProcessingLibraryExtension @Inject constructor(objects: ObjectFactory) {
var version: String? = null
val library = objects.newInstance(ProcessingLibraryConfiguration::class.java)
fun library(action: Action<ProcessingLibraryConfiguration>) {
action.execute(library)
}
}
open class ProcessingLibraryConfiguration @Inject constructor() : Serializable {
/**
* Name of the library. If not set, the project name will be used.
*/
var name: String? = null
/**
* Version number of the library.
*/
var version: Int? = null
/**
* Pretty version string of the library.
*/
var prettyVersion: String? = null
/**
* Map of author URLs to author names.
*/
var authors: Map<String, String> = emptyMap()
/**
* URL of the library where more information can be found.
*/
var url: String? = null
/**
* List of categories the library belongs to.
*/
var categories: List<String> = emptyList()
/**
* A one-line sentence describing the library.
*/
var sentence: String? = null
/**
* A longer paragraph describing the library.
*/
var paragraph: String? = null
/**
* Minimum Processing revision required.
*/
var minRevision: Int? = null
/**
* Maximum Processing revision supported.
*/
var maxRevision: Int? = null
}

View File

@@ -0,0 +1,125 @@
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.jvm.toolchain.JavaLanguageVersion
import java.util.prefs.Preferences
class ProcessingLibraryPlugin : Plugin<Project> {
override fun apply(target: Project) {
val extension = target.extensions.create("processing", ProcessingLibraryExtension::class.java)
target.plugins.apply(JavaPlugin::class.java)
target.repositories.mavenCentral()
target.repositories.maven { it.setUrl("https://jogamp.org/deployment/maven/") }
// Grab processing core if available, otherwise use the published version
val hasCore = try {
val core = target.project(":core")
target.dependencies.add("compileOnly", core)
true
} catch (_: Exception) {
false
}
target.afterEvaluate {
if (!hasCore) {
if (extension.version == null) {
throw GradleException("Processing library version must be specified, please set processing.version in your build.gradle.kts")
}
val processingVersion = extension.version
target.dependencies.add("compileOnly", "org.processing:core:$processingVersion")
}
}
target.extensions.configure(JavaPluginExtension::class.java) { extension ->
extension.toolchain.languageVersion.set(JavaLanguageVersion.of(17))
}
target.plugins.withType(JavaPlugin::class.java) {
val jarTask = target.tasks.named("jar", Jar::class.java)
val javaDocTask = target.tasks.named("javadoc", Javadoc::class.java)
val bundleTask = target.tasks.register("bundleLibrary", BundleLibraryFilesTask::class.java) { task ->
task.configuration = extension.library
task.group = "processing"
task.description = "Creates the Processing library folder with jar, library.properties, and examples."
task.dependsOn(jarTask, javaDocTask)
}
val zipTask = target.tasks.register("zipLibrary", Zip::class.java) { task ->
task.apply {
val libraryName = extension.library.name ?: target.name
val sourceDir = bundleTask.get().outputDir.get().asFile
group = "processing"
description = "Creates a zip & pdex archive of the Processing library folder."
dependsOn(bundleTask)
include("${libraryName}/**")
archiveFileName.set("$libraryName.zip")
from(sourceDir)
destinationDirectory.set(sourceDir)
doLast {
val zip = task.outputs.files.files.first()
zip.copyTo(sourceDir.resolve("$libraryName.pdex"), overwrite = true)
}
}
}
target.tasks.register("installLibrary") { task ->
task.apply {
group = "processing"
dependsOn(zipTask)
doLast {
val preferences = Preferences.userRoot().node("org/processing/app")
val semverRe = Regex("""^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?""")
fun semverKey(v: String): Triple<Long, Boolean, String> {
val m = semverRe.find(v)
val maj = m?.groupValues?.getOrNull(1)?.toLongOrNull() ?: 0L
val min = m?.groupValues?.getOrNull(2)?.toLongOrNull() ?: 0L
val pat = m?.groupValues?.getOrNull(3)?.toLongOrNull() ?: 0L
val pre = m?.groupValues?.getOrNull(4)
val packed = (maj shl 40) or (min shl 20) or pat
return Triple(packed, pre == null, pre ?: "")
}
val installLocations = preferences.get("installLocations", "")
.split(",")
.filter { it.isNotEmpty() }
.mapNotNull {
val parts = it.split("^")
if (parts.size < 2) null else parts[1] to parts[0] // version to path
}
.sortedWith(Comparator { a, b ->
val ka = semverKey(a.first)
val kb = semverKey(b.first)
when {
ka.first != kb.first -> kb.first.compareTo(ka.first)
ka.second != kb.second -> kb.second.compareTo(ka.second)
else -> kb.third.compareTo(ka.third)
}
})
val installPath = installLocations.firstOrNull()?.second
?: throw GradleException("Could not find Processing install location in preferences.")
val libraryName = extension.library.name ?: target.name
val sourceDir = bundleTask.get().outputDir.get().asFile.resolve("$libraryName.pdex")
ProcessBuilder()
.command(installPath, sourceDir.absolutePath)
.inheritIO()
.start()
}
}
}
}
}
}

View File

@@ -0,0 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}
include("library")

View File

@@ -1,5 +1,23 @@
plugins{
java
id("org.processing.library")
}
processing {
library {
version = 1
prettyVersion = "1.0.0"
authors = mapOf(
"The Processing Foundation" to "https://processing.org"
)
url = "https://processing.org/"
categories = listOf("file", "exporter", "dxf")
sentence = "DXF export library for Processing"
paragraph =
"This library allows you to export your Processing drawings as DXF files, which can be opened in CAD applications."
}
}
sourceSets {
@@ -9,27 +27,23 @@ sourceSets {
}
}
}
repositories{
mavenCentral()
maven("https://jogamp.org/deployment/maven/")
}
dependencies{
compileOnly(project(":core"))
implementation("com.lowagie:itext:2.1.7")
}
tasks.register<Copy>("createLibrary"){
/**
* @deprecated Legacy task, use 'bundleLibrary' task provided by 'org.processing.library' plugin
*/
tasks.register<Copy>("createLibrary") {
dependsOn("jar")
into(layout.buildDirectory.dir("library"))
from(layout.projectDirectory){
include ("library.properties")
from(layout.projectDirectory) {
include("library.properties")
include("examples/**")
}
from(configurations.runtimeClasspath){
from(configurations.runtimeClasspath) {
into("library")
}

View File

@@ -1,4 +1,9 @@
rootProject.name = "processing"
pluginManagement {
includeBuild("gradle/plugins")
}
include(
"core",
"core:examples",