diff --git a/gradle/plugins/library/build.gradle.kts b/gradle/plugins/library/build.gradle.kts new file mode 100644 index 000000000..d2707eef4 --- /dev/null +++ b/gradle/plugins/library/build.gradle.kts @@ -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) +} \ No newline at end of file diff --git a/gradle/plugins/library/src/main/kotlin/BundleLibraryFilesTask.kt b/gradle/plugins/library/src/main/kotlin/BundleLibraryFilesTask.kt new file mode 100644 index 000000000..1d5ab57ee --- /dev/null +++ b/gradle/plugins/library/src/main/kotlin/BundleLibraryFilesTask.kt @@ -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) + } +} \ No newline at end of file diff --git a/gradle/plugins/library/src/main/kotlin/ProcessingLibraryExtension.kt b/gradle/plugins/library/src/main/kotlin/ProcessingLibraryExtension.kt new file mode 100644 index 000000000..f03b4cb1d --- /dev/null +++ b/gradle/plugins/library/src/main/kotlin/ProcessingLibraryExtension.kt @@ -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) { + 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 = 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 = 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 +} \ No newline at end of file diff --git a/gradle/plugins/library/src/main/kotlin/ProcessingLibraryPlugin.kt b/gradle/plugins/library/src/main/kotlin/ProcessingLibraryPlugin.kt new file mode 100644 index 000000000..4514f581f --- /dev/null +++ b/gradle/plugins/library/src/main/kotlin/ProcessingLibraryPlugin.kt @@ -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 { + + 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 { + 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() + } + } + } + + } + } +} \ No newline at end of file diff --git a/gradle/plugins/settings.gradle.kts b/gradle/plugins/settings.gradle.kts new file mode 100644 index 000000000..ab39f6aca --- /dev/null +++ b/gradle/plugins/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + +include("library") \ No newline at end of file diff --git a/java/libraries/dxf/build.gradle.kts b/java/libraries/dxf/build.gradle.kts index 93c114f41..8947a3684 100644 --- a/java/libraries/dxf/build.gradle.kts +++ b/java/libraries/dxf/build.gradle.kts @@ -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("createLibrary"){ +/** + * @deprecated Legacy task, use 'bundleLibrary' task provided by 'org.processing.library' plugin + */ +tasks.register("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") } diff --git a/settings.gradle.kts b/settings.gradle.kts index 7eacb0687..6f21e8983 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,9 @@ rootProject.name = "processing" + +pluginManagement { + includeBuild("gradle/plugins") +} + include( "core", "core:examples",