mirror of
https://github.com/processing/processing4.git
synced 2026-02-02 21:29:17 +01:00
Processing Plugin tests & Refactor
This commit is contained in:
@@ -7,6 +7,7 @@ plugins{
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://jogamp.org/deployment/maven")
|
||||
}
|
||||
|
||||
dependencies{
|
||||
@@ -16,9 +17,11 @@ dependencies{
|
||||
implementation(libs.kotlinGradlePlugin)
|
||||
implementation(libs.kotlinComposePlugin)
|
||||
|
||||
testImplementation(project(":core"))
|
||||
testImplementation(libs.junit)
|
||||
}
|
||||
|
||||
// TODO: CI/CD for publishing the plugin to the Gradle Plugin Portal
|
||||
gradlePlugin{
|
||||
plugins{
|
||||
create("processing"){
|
||||
|
||||
@@ -17,6 +17,7 @@ This task stores the resulting information in a file that can be used later to r
|
||||
*/
|
||||
abstract class LibrariesTask : DefaultTask() {
|
||||
|
||||
// TODO: Allow multiple directories
|
||||
@InputDirectory
|
||||
@Optional
|
||||
val librariesDirectory: DirectoryProperty = project.objects.directoryProperty()
|
||||
@@ -41,6 +42,9 @@ abstract class LibrariesTask : DefaultTask() {
|
||||
fun execute() {
|
||||
if (!librariesDirectory.isPresent) {
|
||||
logger.error("Libraries directory is not set. Libraries will not be imported.")
|
||||
val meta = ObjectOutputStream(librariesMetaData.get().asFile.outputStream())
|
||||
meta.writeObject(arrayListOf<Library>())
|
||||
meta.close()
|
||||
return
|
||||
}
|
||||
val libraries = librariesDirectory.get().asFile
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.gradle.work.InputChanges
|
||||
import processing.mode.java.preproc.PdePreprocessor
|
||||
import java.io.File
|
||||
import java.io.ObjectOutputStream
|
||||
import java.io.Serializable
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.jar.JarFile
|
||||
import javax.inject.Inject
|
||||
@@ -14,7 +15,7 @@ import javax.inject.Inject
|
||||
|
||||
// TODO: Generate sourcemaps
|
||||
/*
|
||||
* The PDETask is the main task that processes the .pde files and generates the Java source code
|
||||
* The PDETask is the main task that processes the .pde files and generates the Java source code through the PdePreprocessor.
|
||||
*/
|
||||
abstract class PDETask : SourceTask() {
|
||||
@get:InputFiles
|
||||
@@ -24,21 +25,13 @@ abstract class PDETask : SourceTask() {
|
||||
open val stableSources: FileCollection = project.files(Callable<Any> { this.source })
|
||||
|
||||
@OutputDirectory
|
||||
val outputDirectory = project.objects.directoryProperty()
|
||||
|
||||
@get:Input
|
||||
@get:Optional
|
||||
var workingDir: String? = null
|
||||
val outputDirectory: DirectoryProperty = project.objects.directoryProperty()
|
||||
|
||||
@get:Input
|
||||
var sketchName: String = "processing"
|
||||
|
||||
@get:Input
|
||||
@get:Optional
|
||||
var sketchBook: String? = null
|
||||
|
||||
@OutputFile
|
||||
val sketchMetaData = project.objects.fileProperty()
|
||||
val sketchMetaData: RegularFileProperty = project.objects.fileProperty()
|
||||
|
||||
init{
|
||||
outputDirectory.convention(project.layout.buildDirectory.dir("generated/pde"))
|
||||
@@ -49,18 +42,16 @@ abstract class PDETask : SourceTask() {
|
||||
val sketchName: String,
|
||||
val sketchRenderer: String?,
|
||||
val importStatements: List<String>
|
||||
) : java.io.Serializable
|
||||
) : Serializable
|
||||
|
||||
@TaskAction
|
||||
fun execute() {
|
||||
// TODO: Allow pre-processor to run on individual files (future)
|
||||
// TODO: Only compare file names from both defined roots (e.g. sketch.pde and folder/sketch.pde should both be included)
|
||||
|
||||
// Using stableSources since we can only run the pre-processor on the full set of sources
|
||||
val combined = stableSources
|
||||
.files
|
||||
.groupBy { it.name }
|
||||
.map { entry ->
|
||||
// TODO: Select by which one is in the unsaved folder
|
||||
entry.value.maxByOrNull { it.lastModified() }!!
|
||||
}
|
||||
.joinToString("\n"){
|
||||
@@ -74,6 +65,8 @@ abstract class PDETask : SourceTask() {
|
||||
.build()
|
||||
.write(javaFile, combined)
|
||||
|
||||
// TODO: Save the edits to meta files
|
||||
|
||||
javaFile.flush()
|
||||
javaFile.close()
|
||||
|
||||
@@ -87,10 +80,4 @@ abstract class PDETask : SourceTask() {
|
||||
metaFile.writeObject(sketchMeta)
|
||||
metaFile.close()
|
||||
}
|
||||
|
||||
@get:Inject
|
||||
open val deleter: Deleter
|
||||
get() {
|
||||
throw UnsupportedOperationException("Decorator takes care of injection")
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import java.net.Socket
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO: CI/CD for publishing the plugin
|
||||
class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFactory) : Plugin<Project> {
|
||||
override fun apply(project: Project) {
|
||||
val sketchName = project.layout.projectDirectory.asFile.name.replace(Regex("[^a-zA-Z0-9_]"), "_")
|
||||
@@ -32,6 +31,7 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact
|
||||
|
||||
// TODO: Setup sketchbook when using as a standalone plugin, use the Java Preferences
|
||||
val sketchbook = project.findProperty("processing.sketchbook") as String?
|
||||
val settings = project.findProperty("processing.settings") as String?
|
||||
|
||||
// Apply the Java plugin to the Project
|
||||
project.plugins.apply(JavaPlugin::class.java)
|
||||
@@ -46,13 +46,12 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact
|
||||
project.tasks.findByName("wrapper")?.enabled = false
|
||||
}
|
||||
|
||||
// Add the compose plugin to wrap the sketch in an executable
|
||||
project.plugins.apply("org.jetbrains.compose")
|
||||
|
||||
// Add kotlin support
|
||||
project.plugins.apply("org.jetbrains.kotlin.jvm")
|
||||
// Add jetpack compose support
|
||||
project.plugins.apply("org.jetbrains.kotlin.plugin.compose")
|
||||
// Add the compose plugin to wrap the sketch in an executable
|
||||
project.plugins.apply("org.jetbrains.compose")
|
||||
|
||||
// Add the Processing core library (within Processing from the internal maven repo and outside from the internet)
|
||||
project.dependencies.add("implementation", "$processingGroup:core:${processingVersion}")
|
||||
@@ -129,8 +128,7 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact
|
||||
|
||||
}
|
||||
|
||||
project.extensions.getByType(JavaPluginExtension::class.java).sourceSets.all { sourceSet ->
|
||||
// For each java source set (mostly main) add a new source set for the PDE files
|
||||
project.extensions.getByType(JavaPluginExtension::class.java).sourceSets.first().let{ sourceSet ->
|
||||
val pdeSourceSet = objectFactory.newInstance(
|
||||
DefaultPDESourceDirectorySet::class.java,
|
||||
objectFactory.sourceDirectorySet("${sourceSet.name}.pde", "${sourceSet.name} Processing Source")
|
||||
@@ -142,11 +140,16 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact
|
||||
srcDir("$workingDir/unsaved")
|
||||
}
|
||||
sourceSet.allSource.source(pdeSourceSet)
|
||||
sourceSet.java.srcDir(project.layout.projectDirectory).apply {
|
||||
include("**/*.java")
|
||||
exclude("${project.layout.buildDirectory.asFile.get()}/**/*")
|
||||
}
|
||||
|
||||
val librariesTaskName = sourceSet.getTaskName("scanLibraries", "PDE")
|
||||
val librariesScan = project.tasks.register(librariesTaskName, LibrariesTask::class.java) { task ->
|
||||
task.description = "Scans the libraries in the sketchbook"
|
||||
task.librariesDirectory.set(sketchbook?.let { File(it, "libraries") })
|
||||
// TODO: Save the libraries metadata to settings folder to share between sketches
|
||||
}
|
||||
|
||||
val pdeTaskName = sourceSet.getTaskName("preprocess", "PDE")
|
||||
@@ -154,31 +157,20 @@ class ProcessingPlugin @Inject constructor(private val objectFactory: ObjectFact
|
||||
task.description = "Processes the ${sourceSet.name} PDE"
|
||||
task.source = pdeSourceSet
|
||||
task.sketchName = sketchName
|
||||
task.workingDir = workingDir
|
||||
task.sketchBook = sketchbook
|
||||
|
||||
// Set the output of the pre-processor as the input for the java compiler
|
||||
sourceSet.java.srcDir(task.outputDirectory)
|
||||
|
||||
task.doLast {
|
||||
// Copy java files from the root to the generated directory
|
||||
project.copy { copyTask ->
|
||||
copyTask.from(project.layout.projectDirectory){ from ->
|
||||
from.include("*.java")
|
||||
}
|
||||
copyTask.into(task.outputDirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val depsTaskName = sourceSet.getTaskName("addLegacyDependencies", "PDE")
|
||||
project.tasks.register(depsTaskName, DependenciesTask::class.java){ task ->
|
||||
task.librariesMetaData
|
||||
task.dependsOn(pdeTask, librariesScan)
|
||||
// TODO: Save the libraries metadata to settings folder to share between sketches
|
||||
}
|
||||
|
||||
project.tasks.named(
|
||||
sourceSet.compileJavaTaskName
|
||||
) { task ->
|
||||
// Make sure that the PDE task runs before the java compilation task
|
||||
project.tasks.named(sourceSet.compileJavaTaskName) { task ->
|
||||
task.dependsOn(pdeTaskName, depsTaskName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,198 @@
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.testfixtures.ProjectBuilder
|
||||
import org.gradle.testkit.runner.BuildResult
|
||||
import org.gradle.testkit.runner.GradleRunner
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.File
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.net.URLClassLoader
|
||||
|
||||
class ProcessingPluginTest{
|
||||
// TODO: Write tests
|
||||
// TODO: Test on multiple platforms since there is meaningful differences between the platforms
|
||||
@Test
|
||||
fun testPluginAddsSketchTask(){
|
||||
val project = ProjectBuilder.builder().build()
|
||||
project.pluginManager.apply("org.processing.java")
|
||||
// TODO: Test on multiple platforms since there are meaningful differences between the platforms
|
||||
data class TemporaryProcessingSketchResult(
|
||||
val buildResult: BuildResult,
|
||||
val sketchFolder: File,
|
||||
val classLoader: ClassLoader
|
||||
)
|
||||
|
||||
assert(project.tasks.getByName("sketch") is Task)
|
||||
fun createTemporaryProcessingSketch(vararg arguments: String, configure: (sketchFolder: File) -> Unit): TemporaryProcessingSketchResult{
|
||||
val directory = TemporaryFolder()
|
||||
directory.create()
|
||||
val sketchFolder = directory.newFolder("sketch")
|
||||
directory.newFile("sketch/build.gradle.kts").writeText("""
|
||||
plugins {
|
||||
id("org.processing.java")
|
||||
}
|
||||
""".trimIndent())
|
||||
directory.newFile("sketch/settings.gradle.kts")
|
||||
configure(sketchFolder)
|
||||
|
||||
val buildResult = GradleRunner.create()
|
||||
.withProjectDir(sketchFolder)
|
||||
.withArguments(*arguments)
|
||||
.withPluginClasspath()
|
||||
.withDebug(true)
|
||||
.build()
|
||||
|
||||
val classDir = sketchFolder.resolve("build/classes/java/main")
|
||||
val classLoader = URLClassLoader(arrayOf(classDir.toURI().toURL()), this::class.java.classLoader)
|
||||
|
||||
return TemporaryProcessingSketchResult(
|
||||
buildResult,
|
||||
sketchFolder,
|
||||
classLoader
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSinglePDE(){
|
||||
val (buildResult, sketchFolder, classLoader) = createTemporaryProcessingSketch("build"){ sketchFolder ->
|
||||
sketchFolder.resolve("sketch.pde").writeText("""
|
||||
void setup(){
|
||||
size(100, 100);
|
||||
}
|
||||
|
||||
void draw(){
|
||||
println("Hello World");
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
val sketchClass = classLoader.loadClass("sketch")
|
||||
|
||||
assert(sketchClass != null) {
|
||||
"Class sketch not found"
|
||||
}
|
||||
|
||||
assert(sketchClass?.methods?.find { method -> method.name == "setup" } != null) {
|
||||
"Method setup not found in class sketch"
|
||||
}
|
||||
|
||||
assert(sketchClass?.methods?.find { method -> method.name == "draw" } != null) {
|
||||
"Method draw not found in class sketch"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMultiplePDE(){
|
||||
val (buildResult, sketchFolder, classLoader) = createTemporaryProcessingSketch("build"){ sketchFolder ->
|
||||
sketchFolder.resolve("sketch.pde").writeText(""")
|
||||
void setup(){
|
||||
size(100, 100);
|
||||
}
|
||||
|
||||
void draw(){
|
||||
otherFunction();
|
||||
}
|
||||
""".trimIndent())
|
||||
sketchFolder.resolve("sketch2.pde").writeText("""
|
||||
void otherFunction(){
|
||||
println("Hi");
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
val sketchClass = classLoader.loadClass("sketch")
|
||||
|
||||
assert(sketchClass != null) {
|
||||
"Class sketch not found"
|
||||
}
|
||||
|
||||
assert(sketchClass?.methods?.find { method -> method.name == "otherFunction" } != null) {
|
||||
"Method otherFunction not found in class sketch"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJavaSourceFile(){
|
||||
val (buildResult, sketchFolder, classLoader) = createTemporaryProcessingSketch("build"){ sketchFolder ->
|
||||
sketchFolder.resolve("sketch.pde").writeText("""
|
||||
void setup(){
|
||||
size(100, 100);
|
||||
}
|
||||
|
||||
void draw(){
|
||||
println("Hello World");
|
||||
}
|
||||
""".trimIndent())
|
||||
sketchFolder.resolve("extra.java").writeText("""
|
||||
class SketchJava {
|
||||
public void javaMethod() {
|
||||
System.out.println("Hello from Java");
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
val sketchJavaClass = classLoader.loadClass("SketchJava")
|
||||
|
||||
assert(sketchJavaClass != null) {
|
||||
"Class SketchJava not found"
|
||||
}
|
||||
|
||||
assert(sketchJavaClass?.methods?.find { method -> method.name == "javaMethod" } != null) {
|
||||
"Method javaMethod not found in class SketchJava"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWithUnsavedSource(){
|
||||
val (buildResult, sketchFolder, classLoader) = createTemporaryProcessingSketch("build"){ sketchFolder ->
|
||||
sketchFolder.resolve("sketch.pde").writeText("""
|
||||
void setup(){
|
||||
size(100, 100);
|
||||
}
|
||||
|
||||
void draw(){
|
||||
println("Hello World");
|
||||
}
|
||||
""".trimIndent())
|
||||
sketchFolder.resolve("../unsaved").mkdirs()
|
||||
sketchFolder.resolve("../unsaved/sketch.pde").writeText("""
|
||||
void setup(){
|
||||
size(100, 100);
|
||||
}
|
||||
|
||||
void draw(){
|
||||
println("Hello World");
|
||||
}
|
||||
|
||||
void newMethod(){
|
||||
println("This is an unsaved method");
|
||||
}
|
||||
""".trimIndent())
|
||||
sketchFolder.resolve("gradle.properties").writeText(""")
|
||||
processing.workingDir = ${sketchFolder.parentFile.absolutePath}
|
||||
""".trimIndent())
|
||||
}
|
||||
val sketchClass = classLoader.loadClass("sketch")
|
||||
|
||||
assert(sketchClass != null) {
|
||||
"Class sketch not found"
|
||||
}
|
||||
|
||||
assert(sketchClass?.methods?.find { method -> method.name == "newMethod" } != null) {
|
||||
"Method otherFunction not found in class sketch"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun isDebuggerAttached(): Boolean {
|
||||
val runtimeMxBean = ManagementFactory.getRuntimeMXBean()
|
||||
val inputArguments = runtimeMxBean.inputArguments
|
||||
return inputArguments.any {
|
||||
it.contains("-agentlib:jdwp")
|
||||
}
|
||||
}
|
||||
fun openFolderInFinder(folder: File) {
|
||||
if (!folder.exists() || !folder.isDirectory) {
|
||||
println("Invalid directory: ${folder.absolutePath}")
|
||||
return
|
||||
}
|
||||
|
||||
val process = ProcessBuilder("open", folder.absolutePath)
|
||||
.inheritIO()
|
||||
.start()
|
||||
process.waitFor()
|
||||
}
|
||||
Reference in New Issue
Block a user