diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e8de46389b..e21fce7a40 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -74,6 +74,43 @@ jobs:
ORG_GRADLE_PROJECT_version: ${{ needs.version.outputs.version }}
ORG_GRADLE_PROJECT_group: ${{ vars.GRADLE_GROUP }}
+
+ publish-gradle:
+ name: Publish Processing Plugins to Gradle Plugin Portal
+ runs-on: ubuntu-latest
+ needs: version
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+
+ - name: Setup Processing
+ uses: ./.github/actions/setup
+
+ - name: Publish plugins to Gradle Plugin Portal
+ run: ./gradlew publishPlugins
+ env:
+ GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }}
+ GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }}
+
+ ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }}
+ ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }}
+
+ ORG_GRADLE_PROJECT_version: ${{ needs.version.outputs.version }}
+ ORG_GRADLE_PROJECT_group: ${{ vars.GRADLE_GROUP }}
+
+ - name: Publish internal plugins to Gradle Plugin Portal
+ run: ./gradlew -c gradle/plugins/settings.gradle.kts publishPlugins
+ env:
+ GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }}
+ GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }}
+
+ ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }}
+ ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }}
+
+ ORG_GRADLE_PROJECT_version: ${{ needs.version.outputs.version }}
+ ORG_GRADLE_PROJECT_group: ${{ vars.GRADLE_GROUP }}
+ ORG_GRADLE_PROJECT_publishingGroup: ${{ vars.GRADLE_GROUP }}
+
release-windows:
name: (windows/${{ matrix.arch }}) Create Processing Release
runs-on: ${{ matrix.os }}
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000000..2db2e88c86
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/ant/processing/app/gradle/GradleService.java b/app/ant/processing/app/gradle/GradleService.java
new file mode 100644
index 0000000000..98f7395133
--- /dev/null
+++ b/app/ant/processing/app/gradle/GradleService.java
@@ -0,0 +1,22 @@
+package processing.app.gradle;
+
+import processing.app.Mode;
+import processing.app.Sketch;
+import processing.app.ui.Editor;
+
+import java.io.PrintStream;
+
+public class GradleService {
+ public GradleService(Mode mode, Editor editor) { }
+
+ public void setEnabled(boolean enabled) {}
+ public boolean getEnabled() { return false; }
+ public void prepare(){}
+ public void run() {}
+ public void export(){}
+ public void stop() {}
+ public void startService() {}
+ public void setSketch(Sketch sketch) {}
+ public void setErr(PrintStream err) {}
+ public void setOut(PrintStream out) {}
+}
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 4f91e6d98c..6967af1d43 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -51,27 +51,66 @@ compose.desktop {
application {
mainClass = "processing.app.ProcessingKt"
- jvmArgs(*listOf(
+ jvmArgs(
+ *listOf(
+ Pair("processing.group", group),
Pair("processing.version", rootProject.version),
Pair("processing.revision", findProperty("revision") ?: Int.MAX_VALUE),
Pair("processing.contributions.source", "https://contributions.processing.org/contribs"),
Pair("processing.download.page", "https://processing.org/download/"),
Pair("processing.download.latest", "https://processing.org/download/latest.txt"),
Pair("processing.tutorials", "https://processing.org/tutorials/"),
- ).map { "-D${it.first}=${it.second}" }.toTypedArray())
+ ).map { "-D${it.first}=${it.second}" }.toTypedArray()
+ )
nativeDistributions{
- modules("jdk.jdi", "java.compiler", "jdk.accessibility", "jdk.zipfs", "java.management.rmi", "java.scripting", "jdk.httpserver")
+ modules(
+ "jdk.jdi",
+ "java.compiler",
+ "jdk.accessibility",
+ "jdk.zipfs",
+ "java.management.rmi",
+ "java.scripting",
+ "jdk.httpserver"
+ )
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "Processing"
- fileAssociation("application/x-processing","pde", "Processing Source Code",rootProject.file("build/shared/lib/icons/pde-512.png"), rootProject.file("build/windows/pde.ico"), rootProject.file("build/macos/pde.icns"))
- fileAssociation("application/x-processing","pyde", "Processing Python Source Code",rootProject.file("build/shared/lib/icons/pde-512.png"), rootProject.file("build/windows/pde.ico"), rootProject.file("build/macos/pde.icns"))
- fileAssociation("application/x-processing","pdez", "Processing Sketch Bundle",rootProject.file("build/shared/lib/icons/pde-512.png"), rootProject.file("build/windows/pdze.ico"), rootProject.file("build/macos/pdez.icns"))
- fileAssociation("application/x-processing","pdex", "Processing Contribution Bundle", rootProject.file("build/shared/lib/icons/pde-512.png"), rootProject.file("build/windows/pdex.ico"), rootProject.file("build/macos/pdex.icns"))
+ fileAssociation(
+ "application/x-processing",
+ "pde",
+ "Processing Source Code",
+ rootProject.file("build/shared/lib/icons/pde-512.png"),
+ rootProject.file("build/windows/pde.ico"),
+ rootProject.file("build/macos/pde.icns")
+ )
+ fileAssociation(
+ "application/x-processing",
+ "pyde",
+ "Processing Python Source Code",
+ rootProject.file("build/shared/lib/icons/pde-512.png"),
+ rootProject.file("build/windows/pde.ico"),
+ rootProject.file("build/macos/pde.icns")
+ )
+ fileAssociation(
+ "application/x-processing",
+ "pdez",
+ "Processing Sketch Bundle",
+ rootProject.file("build/shared/lib/icons/pde-512.png"),
+ rootProject.file("build/windows/pdze.ico"),
+ rootProject.file("build/macos/pdez.icns")
+ )
+ fileAssociation(
+ "application/x-processing",
+ "pdex",
+ "Processing Contribution Bundle",
+ rootProject.file("build/shared/lib/icons/pde-512.png"),
+ rootProject.file("build/windows/pdex.ico"),
+ rootProject.file("build/macos/pdex.icns")
+ )
macOS{
bundleID = "${rootProject.group}.app"
@@ -126,6 +165,9 @@ dependencies {
implementation(libs.kaml)
implementation(libs.markdown)
implementation(libs.markdownJVM)
+ implementation(gradleApi())
+ implementation(libs.clikt)
+ implementation(libs.kotlinxSerializationJson)
implementation(libs.clikt)
implementation(libs.kotlinxSerializationJson)
@@ -136,7 +178,6 @@ dependencies {
testImplementation(libs.mockitoKotlin)
testImplementation(libs.junitJupiter)
testImplementation(libs.junitJupiterParams)
-
}
tasks.test {
@@ -265,7 +306,7 @@ tasks.register("generateSnapConfiguration"){
)
}
}
-tasks.register("generateFlatpakConfiguration"){
+tasks.register("generateFlatpakConfiguration") {
val identifier = findProperty("flathubidentifier") as String? ?: "org.processing.pde"
val dir = distributable().destinationDir.get()
@@ -289,7 +330,7 @@ fun replaceVariablesInFile(
target: RegularFile,
variables: Map,
sections: List
-){
+) {
var content = source.asFile.readText()
for ((key, value) in variables) {
content = content.replace("\$$key", value)
@@ -318,7 +359,7 @@ tasks.register("packageSnap"){
commandLine("snapcraft")
}
-tasks.register("buildFlatpak"){
+tasks.register("buildFlatpak") {
onlyIf { OperatingSystem.current().isLinux }
dependsOn("generateFlatpakConfiguration")
group = "compose desktop"
@@ -338,7 +379,7 @@ tasks.register("buildFlatpak"){
)
}
-tasks.register("packageFlatpak"){
+tasks.register("packageFlatpak") {
onlyIf { OperatingSystem.current().isLinux }
dependsOn("buildFlatpak")
group = "compose desktop"
@@ -468,7 +509,7 @@ tasks.register("includeJavaModeResources") {
}
// TODO: Move to java mode
tasks.register("renameWindres") {
- dependsOn("includeSharedAssets","includeJavaModeResources")
+ dependsOn("includeSharedAssets", "includeJavaModeResources")
val dir = composeResources("modes/java/application/launch4j/bin/")
val os = DefaultNativePlatform.getCurrentOperatingSystem()
val platform = when {
@@ -571,9 +612,9 @@ tasks.register("signResources"){
}
file(composeResources("Info.plist")).delete()
}
+}
-}
tasks.register("setExecutablePermissions") {
description = "Sets executable permissions on binaries in Processing.app resources"
group = "compose desktop"
@@ -598,6 +639,15 @@ tasks.register("setExecutablePermissions") {
afterEvaluate {
tasks.named("prepareAppResources").configure {
dependsOn("includeProcessingResources")
+ // Make sure all libraries are bundled in the maven repository distributed with the app
+ dependsOn(
+ listOf(
+ "core",
+ "java:preprocessor",
+ "java:gradle",
+ "java:gradle:hotreload",
+ "app:utils"
+ ).map { project(":$it").tasks.named("publishAllPublicationsToAppRepository") })
}
tasks.named("createDistributable").configure {
dependsOn("includeJdk")
diff --git a/app/src/main/resources/defaults.txt b/app/src/main/resources/defaults.txt
index c979747bd7..ba0c1726cf 100644
--- a/app/src/main/resources/defaults.txt
+++ b/app/src/main/resources/defaults.txt
@@ -186,6 +186,9 @@ console.temp.days = 7
console.scrollback.lines = 500
console.scrollback.chars = 40000
+# run java sketches with Gradle aka the Modern Build System
+run.use_gradle = false
+
# Any additional Java options when running.
# If you change this and can't run things, it's your own durn fault.
run.options =
diff --git a/app/src/main/resources/languages/PDE.properties b/app/src/main/resources/languages/PDE.properties
index c9189efa3a..95f772d91b 100644
--- a/app/src/main/resources/languages/PDE.properties
+++ b/app/src/main/resources/languages/PDE.properties
@@ -290,6 +290,7 @@ preferences.editor.window.height.min=Minimum editor window height
preferences.editor.smooth=Enable antialiasing in the code editor
preferences.editor.caret.blink=Blink the caret
preferences.editor.caret.block=Use block caret
+preferences.use_modern_build_system = Use modern build system (see Processing GitHub Wiki more details)
# Sketchbook Location (Frame)
sketchbook_location = Select new sketchbook folder
@@ -377,6 +378,13 @@ debugger.name = Name
debugger.value = Value
debugger.type = Type
+# Gradle
+gradle.instructions = About this file: \nProcessing creates this file when you run your sketch. \nIt configures the tools needed to build and export your code. \nLearn more: [Gradle Primer link]\n \nTo customize this file: \n1. Delete the line above that begins with '@processing-auto-generated'. \nThis will prevent Processing from overwriting this file in the future. \n2. Make your desired changes.
+gradle.using_gradle = Building sketch using the new build system. (See settings to switch to the legacy build system.)
+gradle.using_eclipse = Building sketch using the legacy build system. (See settings to switch to the new build system.)
+gradle.settings = Settings
+gradle.settings.plugins = Plugins (experimental)
+
# ---------------------------------------
# Toolbars
diff --git a/app/src/processing/app/Language.java b/app/src/processing/app/Language.java
index 447d839831..8cccb7c766 100644
--- a/app/src/processing/app/Language.java
+++ b/app/src/processing/app/Language.java
@@ -173,7 +173,6 @@ static public Language init() {
return instance;
}
-
static private String get(String key) {
LanguageBundle bundle = init().bundle;
diff --git a/app/src/processing/app/Preferences.java b/app/src/processing/app/Preferences.java
index b88e1416da..2b8a0156fc 100644
--- a/app/src/processing/app/Preferences.java
+++ b/app/src/processing/app/Preferences.java
@@ -416,6 +416,8 @@ static public String getSketchbookPath() {
static protected void setSketchbookPath(String path) {
+ // Unify path seperator for all platforms
+ path = path.replace(File.separatorChar, '/');
set("sketchbook.path.four", path); //$NON-NLS-1$
}
}
diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt
index 08ad763775..f047fd1c97 100644
--- a/app/src/processing/app/Processing.kt
+++ b/app/src/processing/app/Processing.kt
@@ -13,6 +13,7 @@ import com.github.ajalt.clikt.parameters.options.option
import processing.app.api.Contributions
import processing.app.api.SketchCommand
import processing.app.api.Sketchbook
+import processing.app.gradle.api.Sketch
import processing.app.ui.Start
import java.io.File
import java.util.prefs.Preferences
@@ -34,7 +35,8 @@ suspend fun main(args: Array){
LegacyCLI(args),
Contributions(),
Sketchbook(),
- SketchCommand()
+ SketchCommand(),
+ Sketch()
)
.main(args)
}
diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java
index 8bb50352b0..52c0de5b20 100644
--- a/app/src/processing/app/Sketch.java
+++ b/app/src/processing/app/Sketch.java
@@ -50,6 +50,8 @@
* Stores information about files in the current sketch.
*/
public class Sketch {
+ public static final String PROPERTIES_NAME = "sketch.properties";
+
private final Editor editor;
private final Mode mode;
@@ -1305,7 +1307,7 @@ static protected Settings loadProperties(File folder) throws IOException {
}
return null;
*/
- return new Settings(new File(folder, "sketch.properties"));
+ return new Settings(new File(folder, PROPERTIES_NAME));
}
diff --git a/app/src/processing/app/gradle/Debugger.kt b/app/src/processing/app/gradle/Debugger.kt
new file mode 100644
index 0000000000..9c93bd824a
--- /dev/null
+++ b/app/src/processing/app/gradle/Debugger.kt
@@ -0,0 +1,44 @@
+package processing.app.gradle
+
+import com.sun.jdi.Bootstrap
+import com.sun.jdi.VirtualMachine
+import com.sun.jdi.connect.AttachingConnector
+import kotlinx.coroutines.delay
+import processing.app.Messages
+import kotlin.time.Duration.Companion.seconds
+import kotlin.time.TimeSource
+
+class Debugger {
+ companion object {
+ suspend fun connect(port: Int?): VirtualMachine? {
+ try {
+ Messages.log("Attaching to VM $port")
+ val connector = Bootstrap.virtualMachineManager().allConnectors()
+ .firstOrNull { it.name() == "com.sun.jdi.SocketAttach" }
+ as AttachingConnector?
+ ?: throw IllegalStateException("No socket attach connector found")
+ val args = connector.defaultArguments()
+ args["port"]?.setValue(port?.toString() ?: "5005")
+
+ // Try to attach the debugger, retrying if it fails
+ // TODO: Stop retrying after the job has been cancelled / failed
+ val start = TimeSource.Monotonic.markNow()
+ while (start.elapsedNow() < 10.seconds) {
+ try {
+ val sketch = connector.attach(args)
+ sketch.resume()
+ Messages.log("Attached to VM: ${sketch.name()}")
+ return sketch
+ } catch (e: Exception) {
+ Messages.log("Error while attaching to VM: ${e.message}... Retrying")
+ }
+ delay(250)
+ }
+ } catch (e: Exception) {
+ Messages.log("Error while attaching to VM: ${e.message}")
+ return null
+ }
+ return null
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/gradle/Exceptions.kt b/app/src/processing/app/gradle/Exceptions.kt
new file mode 100644
index 0000000000..c913ef4f8b
--- /dev/null
+++ b/app/src/processing/app/gradle/Exceptions.kt
@@ -0,0 +1,99 @@
+package processing.app.gradle
+
+import com.sun.jdi.Location
+import com.sun.jdi.StackFrame
+import com.sun.jdi.VirtualMachine
+import com.sun.jdi.event.ExceptionEvent
+import com.sun.jdi.request.EventRequest
+import kotlinx.coroutines.delay
+import processing.app.Messages
+import processing.app.SketchException
+import processing.app.ui.Editor
+
+// TODO: Consider adding a panel to the footer
+class Exceptions (val vm: VirtualMachine, val editor: Editor?) {
+ suspend fun listen() {
+ try {
+ val manager = vm.eventRequestManager()
+
+ val request = manager.createExceptionRequest(null, false, true)
+ request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD)
+ request.enable()
+
+ val queue = vm.eventQueue()
+ while (true) {
+ val eventSet = queue.remove()
+ for (event in eventSet) {
+ if (event is ExceptionEvent) {
+ printExceptionDetails(event)
+ event.thread().resume()
+ }
+ }
+ eventSet.resume()
+ delay(10)
+ }
+ } catch (e: Exception) {
+ Messages.log("Error while listening for exceptions: ${e.message}")
+ }
+ }
+
+ fun printExceptionDetails(event: ExceptionEvent) {
+ val exception = event.exception()
+ val thread = event.thread()
+ val location = event.location().mapToPdeFile()
+ val stackFrames = thread.frames()
+
+ val (processingFrames, userFrames) = stackFrames
+ .map{
+ val location = it.location().mapToPdeFile()
+ val method = location.method()
+ it to "${method.declaringType().name()}.${method.name()}() @ ${location.sourcePath()}:${location.lineNumber()}"
+ }
+ .partition {
+ it.first.location().declaringType().name().startsWith("processing.")
+ }
+
+ /*
+ We have 6 lines by default within the editor to display more information about the exception.
+ */
+ // TODO: Improve the display and clarity of the exception details
+
+ val message = """
+ In Processing code:
+ #processingFrames
+
+ In your code:
+ #userFrames
+
+ """
+ .trimIndent()
+ .replace("#processingFrames", processingFrames.joinToString("\n ") { it.second })
+ .replace("#userFrames", userFrames.joinToString("\n ") { it.second })
+
+ val error = """
+ Exception: ${exception.referenceType().name()} @ ${location.sourcePath()}:${location.lineNumber()}
+ """.trimIndent()
+
+ println(message)
+ System.err.println(error)
+
+ editor?.statusError(exception.referenceType().name())
+ }
+
+ fun Location.mapToPdeFile(): Location {
+ if(editor == null) return this
+
+ // Check if the source is a .java file
+ val sketch = editor.sketch
+ sketch.code.forEach { code ->
+ if(code.extension != "java") return@forEach
+ if(sourceName() != code.fileName) return@forEach
+ return@mapToPdeFile this
+ }
+
+ // TODO: Map to .pde file again, @see JavaBuild.placeException
+ // TODO: This functionality should be provided by the mode
+
+ return this
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/gradle/GradleJob.kt b/app/src/processing/app/gradle/GradleJob.kt
new file mode 100644
index 0000000000..16787404ff
--- /dev/null
+++ b/app/src/processing/app/gradle/GradleJob.kt
@@ -0,0 +1,441 @@
+package processing.app.gradle
+
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import com.sun.jdi.VirtualMachine
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import org.gradle.tooling.BuildCancelledException
+import org.gradle.tooling.BuildLauncher
+import org.gradle.tooling.GradleConnector
+import org.gradle.tooling.events.ProgressListener
+import org.gradle.tooling.events.problems.ProblemEvent
+import org.gradle.tooling.events.problems.Severity
+import org.gradle.tooling.events.problems.internal.DefaultSingleProblemEvent
+import org.gradle.tooling.events.task.TaskFinishEvent
+import org.gradle.tooling.events.task.TaskStartEvent
+import org.gradle.tooling.events.task.TaskSuccessResult
+import processing.app.Base.*
+import processing.app.Language.text
+import processing.app.Messages
+import processing.app.Platform
+import processing.app.Platform.getContentFile
+import processing.app.Settings
+import processing.app.Sketch
+import processing.app.gradle.Log.Companion.startLogServer
+import processing.app.ui.Editor
+import processing.app.ui.EditorStatus
+import java.nio.file.Path
+import kotlin.io.path.deleteIfExists
+import kotlin.io.path.writeText
+
+/*
+* The gradle job runs the gradle tasks and manages the gradle connection
+ */
+class GradleJob(
+ vararg val tasks: String,
+ val workingDir: Path,
+ val sketch: Sketch,
+ val editor: Editor? = null,
+){
+ enum class State{
+ NONE,
+ BUILDING,
+ RUNNING,
+ ERROR,
+ DONE
+ }
+
+ val debugPort = (30_000..60_000).random()
+ val logPort = debugPort + 1
+ val errPort = logPort + 1
+
+ val state = mutableStateOf(State.NONE)
+ val vm = mutableStateOf(null)
+ val problems = mutableStateListOf()
+ val jobs = mutableStateListOf()
+
+ private val scope = CoroutineScope(Dispatchers.IO)
+ private val cancel = GradleConnector.newCancellationTokenSource()
+
+
+ /*
+ Set up the gradle build launcher with the necessary configuration
+ This includes setting the working directory, the tasks to run,
+ and the arguments to pass to gradle.
+ Create the necessary build files if they do not exist.
+ */
+ private fun BuildLauncher.setupGradle(extraArguments: List = listOf()) {
+
+ val copy = sketch.isReadOnly || sketch.isUntitled
+
+ val sketchFolder = if(copy) workingDir.resolve("sketch").toFile() else sketch.folder
+
+ if(copy){
+ // If the sketch is read-only, we copy it to the working directory
+ // This allows us to run the sketch without modifying the original files
+ sketch.folder.copyRecursively(sketchFolder, overwrite = true)
+ }
+ // Save the unsaved code into the working directory for gradle to compile
+ val unsaved = sketch.code
+ .map { code ->
+ val file = workingDir.resolve("unsaved/${code.fileName}")
+ file.parent.toFile().mkdirs()
+ // If tab is marked modified save it to the working directory
+ // Otherwise delete the file so we don't compile with old code
+ if(code.isModified){
+ file.writeText(code.documentText)
+ }else{
+ file.deleteIfExists()
+ }
+ return@map code.fileName
+ }
+ val group = System.getProperty("processing.group", "org.processing")
+ // Collect the variables to pass to gradle
+ val variables = mapOf(
+ "group" to group,
+ "version" to getVersionName(),
+ "sketchFolder" to sketchFolder,
+ "sketchbook" to getSketchbookFolder(),
+ "workingDir" to workingDir.toAbsolutePath().toString(),
+ "settings" to getSettingsFolder().absolutePath.toString(),
+ "unsaved" to unsaved.joinToString(","),
+ "debugPort" to debugPort.toString(),
+ "logPort" to logPort.toString(),
+ "errPort" to errPort.toString(),
+ "fullscreen" to System.getProperty("processing.fullscreen", "false").equals("true"),
+ "display" to 1, // TODO: Implement
+ "external" to true,
+ "location" to null, // TODO: Implement
+ "editor.location" to editor?.location?.let { "${it.x},${it.y}" },
+ //"awt.disable" to false,
+ //"window.color" to "0xFF000000", // TODO: Implement
+ //"stop.color" to "0xFF000000", // TODO: Implement
+ "stop.hide" to false, // TODO: Implement
+ )
+ val repository = getContentFile("repository").absolutePath.replace("""\""", """\\""")
+ // Create the init.gradle.kts file in the working directory
+ // This allows us to run the gradle plugin that has been bundled with the editor
+ // TODO: Add the plugin repositories if they are defined
+ // TODO: Feedback when the JDK is being downloaded
+ val initGradle = workingDir.resolve("init.gradle.kts").apply {
+ val content = """
+ beforeSettings{
+ pluginManagement {
+ plugins {
+ id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
+ }
+ repositories {
+ maven("$repository")
+ gradlePluginPortal()
+ }
+ }
+ }
+ allprojects{
+ repositories {
+ maven("$repository")
+ mavenCentral()
+ }
+ }
+ """.trimIndent()
+
+ writeText(content)
+ }
+ // Create the build.gradle.kts file in the sketch folder
+ val buildGradle = sketchFolder.resolve("build.gradle.kts")
+ val generate = buildGradle.let {
+ if(!it.exists()) return@let true
+
+ val contents = it.readText()
+ if(!contents.contains("@processing-auto-generated")) return@let false
+
+ val version = contents.substringAfter("version=").substringBefore("\n")
+ if(version != getVersionName()) return@let true
+
+ val modeTitle = contents.substringAfter("mode=").substringBefore(" ")
+ if(sketch.mode.title != modeTitle) return@let true
+
+ return@let DEBUG
+ }
+ if (generate) {
+ Messages.log("build.gradle.kts outdated or not found in ${sketch.folder}, creating one")
+ val header = """
+ // @processing-auto-generated mode=${sketch.mode.title} version=${getVersionName()}
+ //
+ """.trimIndent()
+
+ val instructions = text("gradle.instructions")
+ .split("\n")
+ .joinToString("\n") { "// $it" }
+
+ val enabledPlugins = mutableListOf(GradlePlugin(
+ "Processing Java",
+ "The Processing Java mode for Gradle",
+ null,
+ "$group.java",
+ getVersionName()
+ ))
+ val propertiesFile = sketchFolder.resolve(Sketch.PROPERTIES_NAME)
+ if(propertiesFile.exists()){
+ val sketchSettings = Settings(propertiesFile)
+
+ // Grab the installed plugins
+ val plugins = GradlePlugin.plugins
+
+ // Grab the enabled plugins
+ val pluginSetting = (sketchSettings.get(GradlePlugin.PROPERTIES_KEY) ?: "")
+ .split(",")
+ .map { it.trim() }
+ .filter{ it.isNotEmpty() }
+
+ // Link plugins in the settings to their installed counterparts
+ enabledPlugins.addAll(
+ pluginSetting
+ .mapNotNull { id ->
+ plugins.find { plugin -> plugin.id == id
+ }
+ }
+ )
+ }
+
+ val pluginList = enabledPlugins
+ .joinToString("\n ") { "id(\"${it.id}\") version \"${it.version}\"" }
+
+ val configuration = """
+ plugins{
+ #plugins
+ }
+ """.trimIndent().replace("#plugins", pluginList)
+ val content = "${header}\n${instructions}\n\n${configuration}"
+ buildGradle.writeText(content)
+ }
+ // Create and empty settings.gradle.kts file in the sketch folder
+ val settingsGradle = sketchFolder.resolve("settings.gradle.kts")
+ if (!settingsGradle.exists()) {
+ settingsGradle.createNewFile()
+ }
+ // Collect the arguments to pass to gradle
+ val arguments = mutableListOf("--init-script", initGradle.toAbsolutePath().toString())
+ // Hide Gradle output from the console if not in debug mode
+ if(!DEBUG) arguments += "--quiet"
+ if(copy) arguments += listOf("--project-dir", sketchFolder.absolutePath)
+
+ arguments += variables.entries
+ .filter { it.value != null }
+ .map { "-Pprocessing.${it.key}=${it.value}" }
+
+ arguments += extraArguments
+
+ withArguments(*arguments.toTypedArray())
+
+ forTasks(*tasks)
+
+ // TODO: Instead of shipping Processing with a build-in JDK we should download the JDK through Gradle
+ setJavaHome(Platform.getJavaHome())
+ withCancellationToken(cancel.token())
+ }
+
+ /*
+ Start the gradle job and run the tasks
+ */
+ fun start() {
+ launchJob {
+ handleExceptions {
+ state.value = State.BUILDING
+
+ // Connect Gradle, configure the build and run it
+ GradleConnector.newConnector()
+ .forProjectDirectory(sketch.folder)
+ .apply {
+ editor?.statusMessage("Connecting to Gradle", EditorStatus.NOTICE)
+ // TODO: Remove when switched to classic confinement within Snap
+ if (System.getenv("SNAP_USER_COMMON") != null) {
+ useGradleUserHomeDir(getSettingsFolder().resolve("gradle"))
+ }
+ }
+ .connect()
+ .apply {
+ editor?.statusMessage("Building sketch", EditorStatus.NOTICE)
+ }
+ .newBuild()
+ .apply {
+ if (DEBUG) {
+ setStandardOutput(System.out)
+ setStandardError(System.err)
+ }
+
+ setupGradle()
+
+ addStateListener()
+ addLogserver()
+ addDebugging()
+
+ }
+ .run()
+ }
+ }
+ }
+
+
+ /*
+ Cancel the gradle job and all the jobs that were launched in this scope
+ */
+ fun cancel(){
+ cancel.cancel()
+ jobs.forEach(Job::cancel)
+ }
+
+ /*
+ Add a job to the scope and add it to the list of jobs so we can cancel it later
+ */
+ private fun launchJob(block: suspend CoroutineScope.() -> Unit){
+ val job = scope.launch { block() }
+ jobs.add(job)
+ }
+
+
+ /*
+ Handle exceptions that occur during the build process and inform the user about them
+ */
+ private fun handleExceptions(action: () -> Unit){
+ try{
+ action()
+ }catch (e: Exception){
+ val causesList = mutableListOf()
+ var cause: Throwable? = e
+
+ while (cause != null && cause.cause != cause) {
+ causesList.add(cause)
+ cause = cause.cause
+ }
+
+ val errors = causesList.joinToString("\n") { it.message ?: "Unknown error" }
+
+ val skip = listOf(BuildCancelledException::class)
+
+ if (skip.any { it.isInstance(e) }) {
+ Messages.log("Gradle job error: $errors")
+ return
+ }
+
+ if(state.value == State.RUNNING){
+ Messages.log("Gradle job error: $errors")
+ return
+ }
+
+ // An error occurred during the build process
+
+ System.err.println(errors)
+ editor?.statusError(causesList.last().message)
+ }finally {
+ state.value = State.DONE
+ vm.value = null
+ }
+ }
+
+ // TODO: Move to separate file?
+ /*
+ Add a progress listener to the build launcher
+ to track the progress of the build and update the editor status accordingly
+ */
+ private fun BuildLauncher.addStateListener(){
+ addProgressListener(ProgressListener { event ->
+ if(event is TaskStartEvent) {
+ editor?.statusMessage("Running task: ${event.descriptor.name}", EditorStatus.NOTICE)
+ when(event.descriptor.name) {
+ ":run" -> {
+ state.value = State.RUNNING
+ Messages.log("Start run")
+ editor?.toolbar?.activateRun()
+ }
+ }
+
+ }
+ if(event is TaskFinishEvent) {
+ if(event.result is TaskSuccessResult){
+ editor?.statusMessage("Finished task ${event.descriptor.name}", EditorStatus.NOTICE)
+ }
+
+ when(event.descriptor.name){
+ ":run"->{
+ state.value = State.DONE
+ editor?.toolbar?.deactivateRun()
+ editor?.toolbar?.deactivateStop()
+ }
+ }
+ }
+ if(event is DefaultSingleProblemEvent) {
+
+
+ problems.add(event)
+
+ val skip = listOf(
+ "mutating-the-dependencies-of-configuration-implementation-after-it-has-been-resolved-or-consumed-this-behavior-has-been-deprecated",
+ "mutating-the-dependencies-of-configuration-runtimeonly-after-it-has-been-resolved-or-consumed-this-behavior-has-been-deprecated"
+ )
+ if(skip.any { event.definition.id.name.contains(it) }) {
+ Messages.log(event.toString())
+ return@ProgressListener
+ }
+
+ if(event.definition.severity == Severity.ADVICE) {
+ Messages.log(event.toString())
+ return@ProgressListener
+ }
+ // TODO: Show the error on the location if it is available
+ // TODO: This functionality should be provided by the mode
+ /*
+ We have 6 lines to display the error in the editor.
+ */
+
+ val error = event.definition.id.displayName
+ editor?.statusError(error)
+ System.err.println("Problem: $error")
+ state.value = State.ERROR
+
+ val message = """
+ Context: ${event.contextualLabel.contextualLabel}
+ Solutions: ${event.solutions.joinToString("\n\t") { it.solution }}
+ """
+ .trimIndent()
+
+ println(message)
+ }
+ })
+ }
+
+ /*
+ Start log servers for the standard output and error streams
+ This allows us to capture the output of Processing and display it in the editor
+ Whilst keeping the gradle output separate
+ */
+ fun BuildLauncher.addLogserver(){
+ launchJob {
+ startLogServer(logPort, System.out)
+ }
+ launchJob{
+ startLogServer(errPort, System.err)
+ }
+ }
+
+ /*
+ Connected a debugger to the gradle run task
+ This allows us to debug the sketch while it is running
+ */
+ fun BuildLauncher.addDebugging() {
+ addProgressListener(ProgressListener { event ->
+ if (event !is TaskStartEvent) return@ProgressListener
+ if (event.descriptor.name != ":run") return@ProgressListener
+
+ launchJob {
+ val debugger = Debugger.connect(debugPort) ?: return@launchJob
+ vm.value = debugger
+ val exceptions = Exceptions(debugger, editor)
+ exceptions.listen()
+ }
+
+ })
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/gradle/GradlePlugin.kt b/app/src/processing/app/gradle/GradlePlugin.kt
new file mode 100644
index 0000000000..e82dbb3836
--- /dev/null
+++ b/app/src/processing/app/gradle/GradlePlugin.kt
@@ -0,0 +1,33 @@
+package processing.app.gradle
+
+import androidx.compose.runtime.mutableStateListOf
+import processing.app.Base
+import java.nio.file.Path
+
+data class GradlePlugin(
+ val name: String,
+ val description: String,
+ val repository: Path?,
+ val id: String,
+ val version: String){
+ companion object{
+ const val PROPERTIES_KEY = "sketch.plugins"
+ val group = System.getProperty("processing.group", "org.processing")
+ val plugins = mutableStateListOf(
+ GradlePlugin(
+ "Hot Reload",
+ "Automatically apply changes in your sketch upon saving",
+ null,
+ "$group.java.hotreload",
+ Base.getVersionName()
+ ),
+ GradlePlugin(
+ "Android",
+ "Run your sketch on an Android device",
+ null,
+ "$group.android",
+ Base.getVersionName()
+ ),
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/gradle/GradleService.kt b/app/src/processing/app/gradle/GradleService.kt
new file mode 100644
index 0000000000..34d0498a27
--- /dev/null
+++ b/app/src/processing/app/gradle/GradleService.kt
@@ -0,0 +1,121 @@
+package processing.app.gradle
+
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.neverEqualPolicy
+import androidx.compose.ui.awt.ComposePanel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import processing.app.Language.text
+import processing.app.Mode
+import processing.app.Preferences
+import processing.app.Sketch
+import processing.app.ui.Editor
+import processing.app.ui.Theme
+import kotlin.io.path.createTempDirectory
+
+// TODO: Highlight errors in the editor in the right place
+
+// TODO: ---- FUTURE ----
+// TODO: Improve progress tracking and show it in the UI
+// TODO: PoC new debugger/tweak mode
+// TODO: Track build speed (for analytics?)
+// TODO: Bundle Gradle with the app
+
+/*
+* The gradle service runs the gradle tasks and manages the gradle connection
+* It will create the necessary build files for gradle to run
+* Then it will kick off a new GradleJob to run the tasks
+* GradleJob manages the gradle build and connects the debugger
+*/
+class GradleService(
+ val mode: Mode,
+ val editor: Editor?,
+) {
+ val active = mutableStateOf(Preferences.getBoolean("run.use_gradle"))
+ var sketch = mutableStateOf(null, neverEqualPolicy())
+ val jobs = mutableStateListOf()
+ val workingDir = createTempDirectory()
+
+ fun run(){
+ startJob("run")
+ }
+
+ fun export(){
+ startJob("runDistributable")
+ }
+
+ fun stop(){
+ stopJobs()
+ }
+
+ private fun startJob(vararg tasks: String) {
+ if(!active.value) return
+ editor?.let { println(text("gradle.using_gradle")) }
+
+ val job = GradleJob(
+ tasks = tasks,
+ workingDir = workingDir,
+ sketch = sketch.value ?: throw IllegalStateException("Sketch is not set"),
+ editor = editor
+ )
+ jobs.add(job)
+ job.start()
+ }
+
+ private fun stopJobs(){
+ jobs.forEach(GradleJob::cancel)
+ }
+
+ private val scope = CoroutineScope(Dispatchers.IO)
+
+ /*
+ Watch the sketch folder for changes and start a build job when the sketch is modified
+ This need to be done properly to use hooks in the future but right now this is the simplest way to do it
+ */
+ init{
+ scope.launch {
+ var path = ""
+ var modified = false
+ var sketched: Sketch? = null
+ while(true){
+ sketch.value?.let { sketch ->
+ if(sketch.folder.absolutePath != path){
+ path = sketch.folder.absolutePath
+ if(sketched == sketch){
+ // The same sketch has its folder changed, trigger updates downstream from the service
+ this@GradleService.sketch.value = sketch
+ }else {
+ sketched = sketch
+ }
+ startJob("build")
+ }
+ if(sketch.isModified != modified){
+ modified = sketch.isModified
+ if(!modified){
+ // If the sketch is no longer modified, start the build job, aka build on save
+ startJob("build")
+ }
+ }
+ }
+
+
+ delay(100)
+ }
+ }
+ }
+
+ // Hooks for java to interact with the Gradle service since mutableStateOf is not accessible in java
+ fun setSketch(sketch: Sketch){
+ this.sketch.value = sketch
+ }
+ fun getEnabled(): Boolean {
+ return active.value
+ }
+ fun setEnabled(active: Boolean) {
+ if(!active) stopJobs()
+ this.active.value = active
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/gradle/GradleSettings.kt b/app/src/processing/app/gradle/GradleSettings.kt
new file mode 100644
index 0000000000..f77ac3a609
--- /dev/null
+++ b/app/src/processing/app/gradle/GradleSettings.kt
@@ -0,0 +1,170 @@
+package processing.app.gradle
+
+import androidx.compose.foundation.*
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.Checkbox
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.awt.ComposePanel
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import processing.app.Language.text
+import processing.app.Settings
+import processing.app.Sketch
+import processing.app.ui.Editor
+import processing.app.ui.EditorFooter
+import processing.app.ui.Theme
+import processing.app.ui.theme.PDETheme
+import processing.app.watchFile
+import java.util.*
+import javax.swing.SwingUtilities
+
+class GradleSettings{
+ companion object{
+ private val scope = CoroutineScope(Dispatchers.IO)
+
+ @JvmStatic
+ fun addGradleSettings(footer: EditorFooter, service: GradleService){
+ val panel = ComposePanel()
+ panel.setContent {
+ Panel(service)
+ }
+ scope.launch {
+ // Only add the panel to the footer when Gradle is active
+ // Can be removed later when Gradle becomes the default build system
+ snapshotFlow { service.active.value }
+ .collect { active ->
+ SwingUtilities.invokeLater {
+ if(active){
+ footer.addPanel(panel, text("gradle.settings"), "/lib/footer/settings")
+ }else{
+ footer.removePanel(panel)
+ }
+ }
+ }
+ }
+ }
+
+ @Composable
+ fun Panel(service: GradleService){
+ val properties = service.sketch.value?.folder?.resolve(Sketch.PROPERTIES_NAME) ?: return
+ // TODO: Rewatch again is the sketch is saved in a different location
+
+ val changed = watchFile(properties)
+
+ val settings = remember(changed) {Settings(properties) }
+
+ LaunchedEffect(changed){
+ /*
+ If the sketch.id is not set, generate a new UUID and save it.
+ We will use this key to save preferences that do not influence the sketch itself,
+ so they are not code, but do influence how the sketch shows up in the editor.
+ This is useful for things like favoring a sketch
+ These are items that should not be shared between users/computers
+ // TODO: Reset id on save-as?
+ */
+ if(settings.get("sketch.id") == null){
+ // TODO: Should this watch the file or should it update a bunch on running the sketch?
+ settings.set("sketch.id", UUID.randomUUID().toString())
+ settings.save()
+ }
+ }
+ val stateVertical = rememberScrollState(0)
+
+ PDETheme {
+ Box {
+ Row(
+ modifier = Modifier
+ .background(Color(Theme.getColor("editor.line.highlight.color").rgb))
+ .padding(start = Editor.LEFT_GUTTER.dp)
+ .fillMaxSize()
+ .verticalScroll(stateVertical)
+ .padding(vertical = 4.dp)
+ ) {
+ PluginsPanel(settings)
+ }
+ VerticalScrollbar(
+ modifier = Modifier
+ .align(Alignment.CenterEnd)
+ .padding(8.dp)
+ .fillMaxHeight(),
+ adapter = rememberScrollbarAdapter(stateVertical)
+ )
+ }
+ }
+ }
+
+ @Composable
+ private fun PluginsPanel(settings: Settings) {
+ // Grab the installed plugins
+ val plugins = GradlePlugin.plugins
+
+ // Grab the enabled plugins
+ val pluginSetting = (settings.get(GradlePlugin.PROPERTIES_KEY) ?: "")
+ .split(",")
+ .map { it.trim() }
+ .filter{ it.isNotEmpty() }
+
+ // Link plugins in the settings to their installed counterparts
+ val enabledPlugins = pluginSetting
+ .map { id -> plugins.find { plugin -> plugin.id == id } }
+ Column {
+ Text(
+ text = text("gradle.settings.plugins"),
+ textAlign = TextAlign.Start,
+ fontSize = 10.sp,
+ fontWeight = FontWeight.Bold
+ )
+ Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
+ GradlePlugin.plugins.map { plugin ->
+ Row() {
+ Checkbox(
+ checked = enabledPlugins.contains(plugin),
+ modifier = Modifier
+ .padding(start = 0.dp, end = 8.dp)
+ .size(24.dp),
+ onCheckedChange = { checked ->
+ scope.launch {
+ // Work from the setting as we do not want to remove missing plugins
+ val current = pluginSetting.toMutableSet()
+ if (checked) {
+ current.add(plugin.id)
+ } else {
+ current.remove(plugin.id)
+ }
+ settings.set(GradlePlugin.PROPERTIES_KEY, current.joinToString(","))
+ settings.save()
+ }
+ },
+ )
+ Column {
+ Text(
+ text = plugin.name,
+ textAlign = TextAlign.Start,
+ fontSize = 12.sp
+ )
+ Text(
+ text = plugin.description,
+ textAlign = TextAlign.Start,
+ fontSize = 10.sp,
+ )
+ }
+ }
+
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/gradle/Log.kt b/app/src/processing/app/gradle/Log.kt
new file mode 100644
index 0000000000..30ca070cf6
--- /dev/null
+++ b/app/src/processing/app/gradle/Log.kt
@@ -0,0 +1,30 @@
+package processing.app.gradle
+
+import processing.app.Messages
+import java.io.PrintStream
+import java.net.ServerSocket
+
+class Log{
+ companion object{
+ fun startLogServer(port: Int, target: PrintStream){
+ val server = ServerSocket(port)
+ Messages.Companion.log("Log server started on port $port")
+ val client = server.accept()
+ Messages.Companion.log("Log server client connected")
+
+ val reader = client.getInputStream().bufferedReader()
+ try {
+ reader.forEachLine { line ->
+ if (line.isNotBlank()) {
+ target.println(line)
+ }
+ }
+ } catch (e: Exception) {
+ Messages.Companion.log("Error while reading from log server: ${e.message}")
+ } finally {
+ client.close()
+ server.close()
+ }
+ }
+ }
+}
diff --git a/app/src/processing/app/gradle/api/Sketch.kt b/app/src/processing/app/gradle/api/Sketch.kt
new file mode 100644
index 0000000000..635094ec83
--- /dev/null
+++ b/app/src/processing/app/gradle/api/Sketch.kt
@@ -0,0 +1,66 @@
+package processing.app.gradle.api
+
+import com.github.ajalt.clikt.command.SuspendingCliktCommand
+import com.github.ajalt.clikt.core.Context
+import com.github.ajalt.clikt.core.subcommands
+import com.github.ajalt.clikt.parameters.options.default
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.options.required
+import processing.app.Base
+import processing.app.Platform
+import processing.app.Preferences
+import processing.app.contrib.ModeContribution
+import processing.app.gradle.GradleJob
+import processing.app.gradle.GradleService
+
+class Sketch : SuspendingCliktCommand("sketch") {
+ init {
+ subcommands(
+ Run()
+ )
+ }
+
+ override fun help(context: Context): String {
+ return """Manage sketches in the Processing environment."""
+ }
+
+ override suspend fun run() {
+ System.setProperty("java.awt.headless", "true")
+ }
+
+ class Run : SuspendingCliktCommand(name = "run") {
+ val sketch by option("--sketch", help = "The sketch to run")
+ .required()
+
+ val mode by option("--mode", help = "The mode to use for running the sketch (only java is supported for now)")
+
+ override fun help(context: Context): String {
+ return "Run the Processing sketch."
+ }
+
+ override suspend fun run() {
+ Base.setCommandLine()
+ Platform.init()
+ Preferences.init()
+ Base.locateSketchbookFolder()
+
+ // TODO: Support modes other than Java
+ val mode = ModeContribution.load(
+ null, Platform.getContentFile("modes/java"),
+ "processing.mode.java.JavaMode"
+ ).mode ?: throw IllegalStateException("Java mode not found")
+
+ System.setProperty("java.awt.headless", "false")
+
+ val service = GradleService(mode,null)
+ service.sketch.value = processing.app.Sketch(sketch, mode)
+ service.run()
+
+ // TODO: Use an async way to wait for the job to finish
+ //Wait for the service to finish
+ while (service.jobs.any { it.state.value != GradleJob.State.DONE }) {
+ Thread.sleep(100)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java
index d5d964b164..ca82abf0c8 100644
--- a/app/src/processing/app/ui/Editor.java
+++ b/app/src/processing/app/ui/Editor.java
@@ -27,6 +27,7 @@
import processing.app.*;
import processing.app.Formatter;
import processing.app.contrib.ContributionManager;
+import processing.app.gradle.GradleService;
import processing.app.laf.PdeMenuItemUI;
import processing.app.syntax.*;
import processing.core.PApplet;
@@ -68,6 +69,7 @@ public abstract class Editor extends JFrame implements RunnerListener {
protected Base base;
protected EditorState state;
protected Mode mode;
+ protected GradleService service;
// There may be certain gutter sizes that cause text bounds
// inside the console to be calculated incorrectly.
@@ -151,6 +153,7 @@ protected Editor(final Base base, String path, final EditorState state,
this.base = base;
this.state = state;
this.mode = mode;
+ this.service = new GradleService(this.mode,this);
// Make sure Base.getActiveEditor() never returns null
base.checkFirstEditor(this);
@@ -211,10 +214,10 @@ public void windowDeactivated(WindowEvent e) {
spacer.setAlignmentX(Component.LEFT_ALIGNMENT);
box.add(spacer);
}
- if (Platform.isLinux()) {
- setUndecorated(true);
- getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
- }
+ if (Platform.isLinux()) {
+ setUndecorated(true);
+ getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
+ }
rebuildModePopup();
toolbar = createToolbar();
@@ -371,7 +374,7 @@ public void actionPerformed(ActionEvent e) {
});
}
- PreferencesEvents.onUpdated(this::updateTheme);
+ PreferencesEvents.onUpdated(this::updateTheme);
}
@@ -388,6 +391,9 @@ public EditorFooter createFooter() {
return ef;
}
+ public EditorFooter getFooter() {
+ return footer;
+ }
public void addErrorTable(EditorFooter ef) {
JScrollPane scrollPane = new JScrollPane();
@@ -477,6 +483,9 @@ public Mode getMode() {
return mode;
}
+ public GradleService getService() {
+ return service;
+ }
public void repaintHeader() {
header.repaint();
@@ -587,6 +596,7 @@ protected int getDividerLocation() {
* with things in the Preferences window.
*/
public void applyPreferences() {
+ service.setEnabled(Preferences.getBoolean("run.use_gradle"));
// Even though this is only updating the theme (colors, icons),
// subclasses use this to apply other preferences.
// For instance, Java Mode applies changes to error checking.
@@ -1066,7 +1076,7 @@ public void buildDevelopMenu(){
var updateTrigger = new JMenuItem(Language.text("menu.develop.check_for_updates"));
updateTrigger.addActionListener(e -> {
Preferences.unset("update.last");
- Preferences.setInteger("update.beta_welcome", 0);
+ Preferences.setInteger("update.beta_welcome", 0);
new UpdateCheck(base);
});
developMenu.add(updateTrigger);
@@ -2297,6 +2307,7 @@ protected void handleOpenInternal(String path) throws EditorException {
} catch (IOException e) {
throw new EditorException("Could not create the sketch.", e);
}
+ service.setSketch(sketch);
header.rebuild();
updateTitle();
diff --git a/app/src/processing/app/ui/EditorConsole.java b/app/src/processing/app/ui/EditorConsole.java
index c8c40ee487..f49283823f 100644
--- a/app/src/processing/app/ui/EditorConsole.java
+++ b/app/src/processing/app/ui/EditorConsole.java
@@ -254,7 +254,14 @@ private boolean suppressMessage(String what, boolean err) {
// "java.lang.NoSuchMethodError: accessibilityHitTest"
// https://github.com/processing/processing4/issues/368
return true;
+ } else if (what.contains("__MOVE__")) {
+ // Don't display the "Move" message that is used to position the sketch window
+ return true;
+ }else if (what.startsWith("SLF4J: ")) {
+ // Don't display the SLF4J messages
+ return true;
}
+
} else { // !err
if (what.contains("Listening for transport dt_socket at address")) {
// Message from the JVM about the socket launch for debug
diff --git a/app/src/processing/app/ui/EditorFooter.java b/app/src/processing/app/ui/EditorFooter.java
index 7efef4132e..7ab57a0fd7 100644
--- a/app/src/processing/app/ui/EditorFooter.java
+++ b/app/src/processing/app/ui/EditorFooter.java
@@ -160,13 +160,19 @@ public void addPanel(Component comp, String name) {
public void addPanel(Component comp, String name, String icon) {
tabs.add(new Tab(comp, name, icon));
cardPanel.add(name, comp);
+ repaint();
}
-
-// public void setPanel(int index) {
-// cardLayout.show(cardPanel, tabs.get(index).name);
-// }
-
+ /**
+ * Remove a panel from the footer.
+ * @param comp Component that links to this tab.
+ * */
+ public void removePanel(Component comp){
+ cardLayout.show(cardPanel, tabs.get(0).title);
+ tabs.removeIf(tab -> tab.comp == comp);
+ cardPanel.remove(comp);
+ repaint();
+ }
public void setPanel(Component comp) {
for (Tab tab : tabs) {
diff --git a/app/src/processing/app/ui/PreferencesFrame.java b/app/src/processing/app/ui/PreferencesFrame.java
index a8cf68c27d..28424a2ea3 100644
--- a/app/src/processing/app/ui/PreferencesFrame.java
+++ b/app/src/processing/app/ui/PreferencesFrame.java
@@ -85,6 +85,7 @@ public class PreferencesFrame {
JCheckBox hidpiDisableBox;
// JLabel hidpiRestartLabel;
JCheckBox syncSketchNameBox;
+ JCheckBox useModernBuildSystem;
JComboBox displaySelectionBox;
JComboBox languageSelectionBox;
@@ -554,6 +555,9 @@ public void mouseExited(MouseEvent e) {
runningPanel.setBorder(new TitledBorder("Running"));
runningPanel.setLayout(new BoxLayout(runningPanel, BoxLayout.Y_AXIS));
+ useModernBuildSystem = new JCheckBox(Language.text("preferences.use_modern_build_system"));
+ addRow(runningPanel, useModernBuildSystem);
+
addRow(runningPanel, displayLabel, displaySelectionBox);
addRow(runningPanel, backgroundColorLabel, presentColor);
addRow(runningPanel, memoryOverrideBox, memoryField, mbLabel);
@@ -827,6 +831,8 @@ protected void applyFrame() {
Preferences.setBoolean("pdex.completion", codeCompletionBox.isSelected());
Preferences.setBoolean("pdex.suggest.imports", importSuggestionsBox.isSelected());
+ Preferences.setBoolean("run.use_gradle", useModernBuildSystem.isSelected());
+
for (Editor editor : base.getEditors()) {
editor.applyPreferences();
}
@@ -902,6 +908,11 @@ public void showFrame() {
if (autoAssociateBox != null) {
autoAssociateBox.setSelected(Preferences.getBoolean("platform.auto_file_type_associations")); //$NON-NLS-1$
}
+
+ if(useModernBuildSystem != null) {
+ useModernBuildSystem.setSelected(Preferences.getBoolean("run.use_gradle"));
+ }
+
// The OK Button has to be set as the default button every time the
// PrefWindow is to be displayed
frame.getRootPane().setDefaultButton(okButton);
diff --git a/app/src/processing/app/ui/preferences/Sketches.kt b/app/src/processing/app/ui/preferences/Sketches.kt
index f6754705d5..048676b50f 100644
--- a/app/src/processing/app/ui/preferences/Sketches.kt
+++ b/app/src/processing/app/ui/preferences/Sketches.kt
@@ -32,6 +32,19 @@ class Sketches {
fun register() {
PDEPreferences.register(
+ PDEPreference(
+ key = "run.use_gradle",
+ descriptionKey = "preferences.use_modern_build_system",
+ pane = sketches,
+ control = { preference, setPreference ->
+ Switch(
+ checked = preference?.toBoolean() ?: false,
+ onCheckedChange = {
+ setPreference(it.toString())
+ }
+ )
+ }
+ ),
PDEPreference(
key = "run.display",
descriptionKey = "preferences.run_sketches_on_display",
@@ -157,8 +170,9 @@ class Sketches {
}
)
}
+ ),
+
)
- )
}
val Select_window: ImageVector
get() {
diff --git a/app/test/processing/app/gradle/GradleServiceTest.kt b/app/test/processing/app/gradle/GradleServiceTest.kt
new file mode 100644
index 0000000000..64a04d447c
--- /dev/null
+++ b/app/test/processing/app/gradle/GradleServiceTest.kt
@@ -0,0 +1,13 @@
+package processing.app.gradle
+
+import org.junit.jupiter.api.Assertions.*
+import processing.app.ui.Editor
+import kotlin.test.Test
+import org.mockito.kotlin.mock
+
+class GradleServiceTest{
+
+ @Test
+ fun testRunningSketch(){
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 371e34bc29..22fad069bc 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -3,15 +3,23 @@ plugins {
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.jetbrainsCompose) apply false
- alias(libs.plugins.mavenPublish) apply false
alias(libs.plugins.versions)
+ alias(libs.plugins.mavenPublish) apply false
}
// Set the build directory to not /build to prevent accidental deletion through the clean action
// Can be deleted after the migration to Gradle is complete
layout.buildDirectory = file(".build")
+allprojects{
+ tasks.withType {
+ options.encoding = "UTF-8"
+ }
+ tasks.withType {
+ options.encoding = "UTF-8"
+ }
+}
// Configure the dependencyUpdates task
tasks {
dependencyUpdates {
diff --git a/build/shared/lib/footer/settings.svg b/build/shared/lib/footer/settings.svg
new file mode 100644
index 0000000000..25a9736fff
--- /dev/null
+++ b/build/shared/lib/footer/settings.svg
@@ -0,0 +1,8 @@
+
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index 16593450ec..e6b28f9791 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -35,10 +35,21 @@ dependencies {
testImplementation(libs.junit)
}
+publishing{
+ repositories{
+ maven {
+ name = "App"
+ url = uri(project(":app").layout.buildDirectory.dir("resources-bundled/common/repository").get().asFile.absolutePath)
+ }
+ }
+}
mavenPublishing{
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, automaticRelease = true)
- signAllPublications()
+ if (project.hasProperty("signingInMemoryKey")) {
+ signAllPublications()
+ }
+
pom{
name.set("Processing Core")
diff --git a/gradle/plugins/library/build.gradle.kts b/gradle/plugins/library/build.gradle.kts
index d2707eef4a..f70338b4ec 100644
--- a/gradle/plugins/library/build.gradle.kts
+++ b/gradle/plugins/library/build.gradle.kts
@@ -1,12 +1,17 @@
plugins {
- `java-gradle-plugin`
+ id("com.gradle.plugin-publish") version "2.0.0"
kotlin("jvm") version "2.2.20"
}
gradlePlugin {
+ website = "https://processing.org/"
+ vcsUrl = "https://github.com/processing/processing4"
plugins {
create("processing.library") {
- id = "org.processing.library"
+ id = project.properties.getOrElse("publishingGroup", { "org.processing" }).toString() + ".library"
+ displayName = "Processing Library Plugin"
+ description = "Gradle plugin for building Processing libraries"
+ tags = listOf("processing", "library", "dsl")
implementationClass = "ProcessingLibraryPlugin"
}
}
diff --git a/java/README.md b/java/README.md
index a4be6e9a87..0a905208d0 100644
--- a/java/README.md
+++ b/java/README.md
@@ -3,8 +3,9 @@
This the Java Mode in Processing. It compiles your sketches and runs them. It is the primary mode of Processing.
## Folders
-- `application` assets for exporting applications within the mode
-- `generated` generated antlr code for the mode, should be moved to a proper `antlr` plugin within gradle
+- `application` assets for exporting applications within the mode (Deprecated)
+- `generated` generated antlr code for the mode, should be moved to a proper `antlr` plugin within gradle (Deprecated)
+- `gradle` the Processing java gradle plugin
- `libraries` libraries that are available within the mode
- `lsp` gradle build system for the language server protocol, in the future we should decouple the lsp from the java mode and pde and move all relevant code here. For now it can be found in `src/.../lsp`
- `mode` legacy files for `Ant`
@@ -13,8 +14,25 @@ This the Java Mode in Processing. It compiles your sketches and runs them. It is
- `test` tests for the mode
- `theme` assets for the mode, related to autocomplete and syntax highlighting
-## Future plans
-- Decouple the `lsp` and `preprocessor` from the mode and move them to their own repositories
-- Move the `antlr` code to a proper plugin within gradle
-- Create a gradle plugin to convert `.pde` file to `.java` files
-- Create a gradle based version of Java mode.
\ No newline at end of file
+## The Modern Build system
+
+Since 2025 work has started on creating a new internal build system for the Java Mode based on Gradle.
+The goal is to simplify by leaning more on Gradle, which provides a lot of the functionality that was build before out of the box and a lot more.
+
+### How it used to work
+
+The build system used to be based on some parts Ant, some parts eclipse (org.eclipse.jdt.core) and a lot of custom work build up over the years.
+
+### How it will work going forward
+
+The modern build system is based around Gradle, the main service (GradleService) for building a sketch with Gradle is included in `app` instead of into the Java mode as future modes are most likely also based on Gradle if they use `core` in some way. Most _Modes_ should/could probably be a Gradle plugin going forward.
+Breaking the build system away from the java mode will mean that we create an island of isolation when it comes to the build system, allowing contributors to work on the build system without running the editor.
+Another upside is that when we publish the Gradle plugin to the Gradle Plugin repository, it will become trivial to run Processing sketches outside the PDE and improvements made to the build system will be usable for everyone.
+There is now also an opportunity for creating contributions that modify the build system in more subtle ways rather than having to make a complete new mode, e.g. a compilation step for shaders or some setup tweaks to make JavaFX work out of the box.
+Furthermore, this change will embed Processing more into the wider Java ecosystem, if users want to upgrade from using Processing to Java whilst still using `core` that will become possible and won't need a rewrite of what they already created.
+
+### How to work on the modern build system
+
+If you want to work on the build system without the PDE, open `/java/gradle/example` into a new intellij IDEA window, this is set up to compile the Processing Java plugin and run sketches standalone.
+
+Within the editor, the gradle plugin is embedded in Processing's embedded maven repository so that Gradle can find it.
\ No newline at end of file
diff --git a/java/build.gradle.kts b/java/build.gradle.kts
index d5d306a2e0..b4ae951624 100644
--- a/java/build.gradle.kts
+++ b/java/build.gradle.kts
@@ -5,7 +5,6 @@ plugins {
repositories{
mavenCentral()
google()
- maven("https://jogamp.org/deployment/maven")
}
sourceSets{
@@ -61,14 +60,13 @@ val bundle = tasks.register("extraResources"){
tasks.register("copyCore"){
val coreProject = project(":core")
dependsOn(coreProject.tasks.jar)
- from(coreProject.tasks.jar) {
- include("core*.jar")
- }
+ from(coreProject.tasks.jar)
+ include("core*.jar")
rename("core.+\\.jar", "core.jar")
into(coreProject.layout.projectDirectory.dir("library"))
}
-val legacyLibraries = arrayOf("io","net")
+val legacyLibraries = arrayOf("io", "net")
legacyLibraries.forEach { library ->
tasks.register("library-$library-extraResources"){
val build = project(":java:libraries:$library").tasks.named("build")
diff --git a/java/gradle/build.gradle.kts b/java/gradle/build.gradle.kts
index b5718a8905..b8a42bac62 100644
--- a/java/gradle/build.gradle.kts
+++ b/java/gradle/build.gradle.kts
@@ -21,15 +21,20 @@ dependencies{
testImplementation(libs.junit)
}
-// TODO: CI/CD for publishing the plugin to the Gradle Plugin Portal
gradlePlugin{
+ website = "https://processing.org/"
+ vcsUrl = "https://github.com/processing/processing4"
plugins{
create("processing.java"){
- id = "org.processing.java"
+ id = "${project(":java").group}.java"
+ displayName = "Processing Plugin"
+ description = "Gradle plugin for building Processing sketches"
+ tags = listOf("processing", "sketch", "dsl")
implementationClass = "org.processing.java.gradle.ProcessingPlugin"
}
}
}
+
publishing{
repositories{
mavenLocal()
@@ -39,6 +44,9 @@ publishing{
}
}
}
+tasks.withType().configureEach {
+ systemProperty("project.group", project(":java").group)
+}
tasks.register("writeVersion") {
// make the version available to the plugin at runtime by writing it to a properties file in the resources directory
diff --git a/java/gradle/example/.idea/.gitignore b/java/gradle/example/.idea/.gitignore
new file mode 100644
index 0000000000..a0ccf77bc5
--- /dev/null
+++ b/java/gradle/example/.idea/.gitignore
@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Environment-dependent path to Maven home directory
+/mavenHomeManager.xml
diff --git a/java/gradle/example/.idea/.name b/java/gradle/example/.idea/.name
new file mode 100644
index 0000000000..fb61c9d808
--- /dev/null
+++ b/java/gradle/example/.idea/.name
@@ -0,0 +1 @@
+processing-gradle-plugin-demo
\ No newline at end of file
diff --git a/java/gradle/example/.idea/compiler.xml b/java/gradle/example/.idea/compiler.xml
new file mode 100644
index 0000000000..b589d56e9f
--- /dev/null
+++ b/java/gradle/example/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/gradle/example/.idea/gradle.xml b/java/gradle/example/.idea/gradle.xml
new file mode 100644
index 0000000000..ae55d4d4a2
--- /dev/null
+++ b/java/gradle/example/.idea/gradle.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/gradle/example/.idea/kotlinc.xml b/java/gradle/example/.idea/kotlinc.xml
new file mode 100644
index 0000000000..d4b7accbaa
--- /dev/null
+++ b/java/gradle/example/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/gradle/example/.idea/misc.xml b/java/gradle/example/.idea/misc.xml
new file mode 100644
index 0000000000..5a50b6cd23
--- /dev/null
+++ b/java/gradle/example/.idea/misc.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/java/gradle/example/.idea/vcs.xml b/java/gradle/example/.idea/vcs.xml
new file mode 100644
index 0000000000..c2365ab11f
--- /dev/null
+++ b/java/gradle/example/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java/gradle/hotreload/build.gradle.kts b/java/gradle/hotreload/build.gradle.kts
new file mode 100644
index 0000000000..2b75b1d0d1
--- /dev/null
+++ b/java/gradle/hotreload/build.gradle.kts
@@ -0,0 +1,33 @@
+plugins {
+ `java-gradle-plugin`
+ kotlin("jvm") version libs.versions.kotlin
+ alias(libs.plugins.gradlePublish)
+
+}
+
+
+repositories {
+ mavenCentral()
+}
+
+dependencies{
+ implementation("org.jetbrains.compose.hot-reload:hot-reload-gradle-plugin:1.0.0-beta03")
+}
+
+gradlePlugin{
+ plugins{
+ create("processing.java.hotreload"){
+ id = "${project(":java").group}.java.hotreload"
+ implementationClass = "org.processing.java.gradle.ProcessingHotReloadPlugin"
+ }
+ }
+}
+publishing{
+ repositories{
+ mavenLocal()
+ maven {
+ name = "App"
+ url = uri(project(":app").layout.buildDirectory.dir("resources-bundled/common/repository").get().asFile.absolutePath)
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/gradle/hotreload/src/main/kotlin/org/processing/java/gradle/ProcessingHotReloadPlugin.kt b/java/gradle/hotreload/src/main/kotlin/org/processing/java/gradle/ProcessingHotReloadPlugin.kt
new file mode 100644
index 0000000000..4776d4ebce
--- /dev/null
+++ b/java/gradle/hotreload/src/main/kotlin/org/processing/java/gradle/ProcessingHotReloadPlugin.kt
@@ -0,0 +1,29 @@
+package org.processing.java.gradle
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPluginExtension
+import org.gradle.jvm.toolchain.JavaLanguageVersion
+import org.gradle.jvm.toolchain.JvmVendorSpec
+import org.jetbrains.compose.reload.gradle.ComposeHotReloadPlugin
+
+class ProcessingHotReloadPlugin: Plugin {
+ override fun apply(project: Project) {
+ project.plugins.apply(ComposeHotReloadPlugin::class.java)
+
+ project.repositories.google()
+ project.extensions.getByType(JavaPluginExtension::class.java).toolchain {
+ it.languageVersion.set(JavaLanguageVersion.of(21))
+ it.vendor.set(JvmVendorSpec.JETBRAINS)
+ }
+
+ project.afterEvaluate {
+ project.tasks.named("build").configure { task ->
+ task.finalizedBy("reload")
+ }
+ project.tasks.named("run").configure { task ->
+ task.dependsOn("hotRun")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/gradle/src/test/kotlin/ProcessingPluginTest.kt b/java/gradle/src/test/kotlin/ProcessingPluginTest.kt
index 7ffeeecb58..c67725e993 100644
--- a/java/gradle/src/test/kotlin/ProcessingPluginTest.kt
+++ b/java/gradle/src/test/kotlin/ProcessingPluginTest.kt
@@ -20,7 +20,7 @@ class ProcessingPluginTest{
val sketchFolder = directory.newFolder("sketch")
directory.newFile("sketch/build.gradle.kts").writeText("""
plugins {
- id("org.processing.java")
+ id("${System.getProperty("project.group")}.java")
}
""".trimIndent())
directory.newFile("sketch/settings.gradle.kts")
diff --git a/java/libraries/dxf/build.gradle.kts b/java/libraries/dxf/build.gradle.kts
index 0fb74dfd3c..9df50b137d 100644
--- a/java/libraries/dxf/build.gradle.kts
+++ b/java/libraries/dxf/build.gradle.kts
@@ -32,7 +32,10 @@ sourceSets {
mavenPublishing {
publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.CENTRAL_PORTAL, automaticRelease = true)
- signAllPublications()
+ if (project.hasProperty("signingInMemoryKey")) {
+ signAllPublications()
+ }
+
coordinates("$group.core", name, version.toString())
pom {
diff --git a/java/libraries/net/build.gradle.kts b/java/libraries/net/build.gradle.kts
index 23289e4855..721ff81ff4 100644
--- a/java/libraries/net/build.gradle.kts
+++ b/java/libraries/net/build.gradle.kts
@@ -53,7 +53,10 @@ mavenPublishing {
coordinates("$group.core", name, version.toString())
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, automaticRelease = true)
- signAllPublications()
+ if (project.hasProperty("signingInMemoryKey")) {
+ signAllPublications()
+ }
+
pom {
name.set("Processing Net")
diff --git a/java/libraries/pdf/build.gradle.kts b/java/libraries/pdf/build.gradle.kts
index f0e0485a17..25e4be6623 100644
--- a/java/libraries/pdf/build.gradle.kts
+++ b/java/libraries/pdf/build.gradle.kts
@@ -55,7 +55,10 @@ mavenPublishing{
coordinates("$group.core", name, version.toString())
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, automaticRelease = true)
- signAllPublications()
+ if (project.hasProperty("signingInMemoryKey")) {
+ signAllPublications()
+ }
+
pom{
name.set("Processing PDF")
diff --git a/java/libraries/svg/build.gradle.kts b/java/libraries/svg/build.gradle.kts
index 6189265ba1..770a81738f 100644
--- a/java/libraries/svg/build.gradle.kts
+++ b/java/libraries/svg/build.gradle.kts
@@ -55,7 +55,10 @@ mavenPublishing {
coordinates("$group.core", name, version.toString())
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, automaticRelease = true)
- signAllPublications()
+ if (project.hasProperty("signingInMemoryKey")) {
+ signAllPublications()
+ }
+
pom {
name.set("Processing SVG")
diff --git a/java/preprocessor/build.gradle.kts b/java/preprocessor/build.gradle.kts
index 6eb71a1242..9c7e213b43 100644
--- a/java/preprocessor/build.gradle.kts
+++ b/java/preprocessor/build.gradle.kts
@@ -49,9 +49,9 @@ publishing{
mavenPublishing{
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, automaticRelease = true)
- // Only sign if signing is set up
- if(project.hasProperty("signing.keyId") || project.hasProperty("signingInMemoryKey"))
+ if (project.hasProperty("signingInMemoryKey")) {
signAllPublications()
+ }
pom{
name.set("Processing Pre-processor")
diff --git a/java/src/processing/mode/java/Compiler.java b/java/src/processing/mode/java/Compiler.java
index c7ec613b45..a2ac110455 100644
--- a/java/src/processing/mode/java/Compiler.java
+++ b/java/src/processing/mode/java/Compiler.java
@@ -52,6 +52,7 @@ public class Compiler {
* @throws SketchException Only if there's a problem. Only then.
*/
static public boolean compile(JavaBuild build) throws SketchException {
+ System.out.println(Language.text("gradle.using_eclipse"));
// This will be filled in if anyone gets angry
SketchException exception = null;
diff --git a/java/src/processing/mode/java/JavaEditor.java b/java/src/processing/mode/java/JavaEditor.java
index 561e38ba54..dce1e389bc 100644
--- a/java/src/processing/mode/java/JavaEditor.java
+++ b/java/src/processing/mode/java/JavaEditor.java
@@ -29,7 +29,6 @@
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -63,6 +62,8 @@
import processing.mode.java.tweak.SketchParser;
import processing.mode.java.tweak.TweakClient;
+import static processing.app.gradle.GradleSettings.addGradleSettings;
+
public class JavaEditor extends Editor {
JavaMode jmode;
@@ -196,6 +197,7 @@ public void rebuild() {
public EditorFooter createFooter() {
EditorFooter footer = super.createFooter();
addErrorTable(footer);
+ addGradleSettings(footer, service);
return footer;
}
@@ -484,6 +486,10 @@ public String getCommentPrefix() {
* Handler for Sketch → Export Application
*/
public void handleExportApplication() {
+ if(service.getEnabled()){
+ service.export();
+ return;
+ }
if (handleExportCheckModified()) {
statusNotice(Language.text("export.notice.exporting"));
ExportPrompt ep = new ExportPrompt(this, () -> {
@@ -634,6 +640,14 @@ public void handleTweak() {
protected void handleLaunch(boolean present, boolean tweak) {
prepareRun();
toolbar.activateRun();
+
+ if(this.service.getEnabled()){
+ System.setProperty("processing.fullscreen", present ? "true" : "false");
+ System.setProperty("processing.tweak", tweak ? "true" : "false");
+ this.service.run();
+ return;
+ }
+
synchronized (runtimeLock) {
runtimeLaunchRequested = true;
}
@@ -662,6 +676,11 @@ protected void handleLaunch(boolean present, boolean tweak) {
* session or performs standard stop action if not currently debugging.
*/
public void handleStop() {
+ if(this.service.getEnabled()){
+ this.service.stop();
+ return;
+ }
+
if (debugger.isStarted()) {
debugger.stopDebug();
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 813b0a296c..9a3b322908 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -12,6 +12,7 @@ include(
"java",
"java:preprocessor",
"java:gradle",
+ "java:gradle:hotreload",
"java:libraries:dxf",
"java:libraries:io",
"java:libraries:net",