From 578c2876066d07a0df415f8581a45d60cb092984 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Fri, 18 Jul 2025 10:43:33 +0200 Subject: [PATCH] Build on save --- .gitignore | 2 + app/src/processing/app/gradle/GradleJob.kt | 3 ++ .../processing/app/gradle/GradleService.kt | 48 +++++++++++++++++-- .../java/gradle/ProcessingHotReloadPlugin.kt | 13 +++-- 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index b9a075332..0a951eebf 100644 --- a/.gitignore +++ b/.gitignore @@ -122,3 +122,5 @@ processing-examples /java/gradle/example/gradlew /java/gradle/example/gradlew.bat /java/gradle/example/.kotlin/errors +/java/gradle/hotreload/build +*.iml diff --git a/app/src/processing/app/gradle/GradleJob.kt b/app/src/processing/app/gradle/GradleJob.kt index 080b58f8c..90f77060d 100644 --- a/app/src/processing/app/gradle/GradleJob.kt +++ b/app/src/processing/app/gradle/GradleJob.kt @@ -72,6 +72,7 @@ class GradleJob( 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 @@ -218,6 +219,8 @@ class GradleJob( val arguments = mutableListOf("--init-script", initGradle.toAbsolutePath().toString()) // Hide Gradle output from the console if not in debug mode if(!DEBUG) arguments += "--quiet" + // TODO: Fix continuous mode for the hot reload + arguments += "-t" if(copy) arguments += listOf("--project-dir", sketchFolder.absolutePath) diff --git a/app/src/processing/app/gradle/GradleService.kt b/app/src/processing/app/gradle/GradleService.kt index b6a4769c3..34d0498a2 100644 --- a/app/src/processing/app/gradle/GradleService.kt +++ b/app/src/processing/app/gradle/GradleService.kt @@ -2,7 +2,12 @@ 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 @@ -30,7 +35,7 @@ class GradleService( val editor: Editor?, ) { val active = mutableStateOf(Preferences.getBoolean("run.use_gradle")) - var sketch = mutableStateOf(null) + var sketch = mutableStateOf(null, neverEqualPolicy()) val jobs = mutableStateListOf() val workingDir = createTempDirectory() @@ -52,7 +57,7 @@ class GradleService( val job = GradleJob( tasks = tasks, - workingDir = workingDir, + workingDir = workingDir, sketch = sketch.value ?: throw IllegalStateException("Sketch is not set"), editor = editor ) @@ -64,10 +69,47 @@ class GradleService( 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 - startJob("build") } fun getEnabled(): Boolean { return active.value 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 index 8eaecc35f..9ee60ab49 100644 --- 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 @@ -2,7 +2,10 @@ package org.processing.java.gradle import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.tasks.GradleBuild +import org.gradle.api.plugins.JavaPlugin +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 import org.jetbrains.compose.reload.gradle.ComposeHotRun @@ -11,10 +14,14 @@ class ProcessingHotReloadPlugin: Plugin { 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("hotRun", ComposeHotRun::class.java){ task -> - task.isAutoReloadEnabled.set(true) + project.tasks.named("build").configure { task -> + task.finalizedBy("reload") } project.tasks.named("run").configure { task -> task.dependsOn("hotRun")