bleep
bleep copied to clipboard
Watch mode in `run` doesn't kill and re-compile/re-run project
Trying to use bleep run httpserver -w
in my ZIO-HTTP sample project doesn't re-compile/re-run the project.
Doing bleep compile httpserver -w
works though.
To reproduce, clone https://github.com/carlosedp/bleepziohttp and run / compile with -w
.
I've also tested with a Cask minimal HTTP project and same happens, watch mode in run
doesn't reload.
Used files from https://github.com/com-lihaoyi/cask/tree/master/example/minimalApplication and the build below:
$schema: https://raw.githubusercontent.com/oyvindberg/bleep/master/schema.json
$version: 0.0.2
jvm:
name: graalvm-java17:22.3.1
projects:
caskhttp:
dependencies:
- com.lihaoyi::cask:0.9.1
extends: template-common
tests:
dependencies:
- com.lihaoyi::utest:0.8.1
- com.lihaoyi::requests:0.8.0
dependsOn: caskhttp
extends: template-common
isTestProject: true
templates:
template-common:
platform:
name: jvm
scala:
options: -encoding utf8 -feature -unchecked
strict: true
version: 3.3.0
I think it does work, just not in the way you expect it to. if the program you're watch/running completes, it will be restarted on source changes. we need to extend the run/watch functionality to kill a running program and restart it on source changes for this to work better
Ah yes, that was the expected way... to make it kill and re-run the application. Can we keep this open to track when the feature will be available? Thanks
Absolutely
Here is what I'm using in a project lately, as a local script
.
Note that it includes a little Cask server to support live reload.
import java.io.File
import java.nio.file.Files
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Promise}
import scala.jdk.CollectionConverters.*
import scala.jdk.OptionConverters.RichOptional
import bleep.commands.Compile
import bleep.internal.{TransitiveProjects, jvmRunCommand}
import bleep.model.{CrossProjectName, ProjectName}
import bleep.{BleepFileWatching, BleepScript, Commands, FileWatching, PathOps, Started}
import io.undertow.Undertow
object RunBg extends BleepScript("runBackground") {
private var promise: Promise[Unit] = Promise()
private object MyServer extends cask.MainRoutes {
@cask.get("/wait-for-event")
def waitForEvent() = {
Await.result(promise.future, Duration.Inf)
promise = Promise() // Reset for the next event
cask.Response("Done", headers = Seq("Access-Control-Allow-Origin" -> "*"))
}
initialize()
}
override def run(started: Started, commands: Commands, args: List[String]): Unit = {
Undertow.builder
.addHttpListener(4000, "localhost")
.setHandler(MyServer.defaultHandler)
.build
.start()
val projectName = "main"
val pidFile = started.buildPaths.buildsDir / s"$projectName.pid"
val project = CrossProjectName(ProjectName(projectName), None)
var process: Option[Process] = None
def terminateProcessHandle(p: ProcessHandle): Unit = {
val desc = s"[${p.pid()}] ${p.info().commandLine().orElse("")}"
val children = p.children().toList.asScala
p.destroy()
if (p.isAlive) {
println(s"Waiting for $desc to terminate")
val start = System.currentTimeMillis()
while (p.isAlive && System.currentTimeMillis() - start < 20000) {
Thread.sleep(100)
}
if (p.isAlive) {
println(s"Killing $desc")
p.destroyForcibly()
}
}
children.foreach(terminateProcessHandle)
}
def terminate(): Unit = {
val pid =
try
Option.when(Files.exists(pidFile)) {
Files.readString(pidFile).toInt
}
catch {
case e: Throwable =>
println(s"Error reading pid file $pidFile")
e.printStackTrace()
None
}
pid.flatMap(ProcessHandle.of(_).toScala).foreach(terminateProcessHandle)
process.foreach(p => terminateProcessHandle(p.toHandle))
}
def runApp(): Unit =
Compile(watch = false, Array(project)).run(started) match {
case Left(value) =>
value.printStackTrace()
case Right(()) =>
terminate()
val value = jvmRunCommand(started.bloopProject(project), started.resolvedJvm, project, None, args)
value.left.foreach(_.printStackTrace())
val command = value.orThrow
val p =
new ProcessBuilder(command*)
.directory(new File(sys.env("PWD")))
.inheritIO()
.start()
Files.writeString(pidFile, p.pid().toString)
process = Some(p)
promise.success(())
promise = Promise()
}
val watcher = BleepFileWatching.projects(started, TransitiveProjects(started.build, Array(project))) { projects =>
println("changed: " + projects)
runApp()
}
try {
runApp()
watcher.run(FileWatching.StopWhen.OnStdInput)
} finally terminate()
}
}
The live reload works with the following code in the web page, rendered only in dev:
function waitUntilAvailable() {
fetch(location.href)
.then(
res => {
console.dir(res);
location.reload()
},
() => setTimeout(waitUntilAvailable, 1000)
)
}
async function longPoll() {
try {
const response = await fetch('http://localhost:4000/wait-for-event');
await response.text();
waitUntilAvailable();
} catch (error) {
console.error('Error during long polling:', error);
setTimeout(longPoll, 5000); // Retry after delay in case of error
}
}
longPoll()