Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run process in background #747

Merged
merged 2 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ final case class SimpleScalaCompiler(
args,
logger,
cwd = Some(project.workspace)
)
).waitFor()

res == 0
}
Expand Down
25 changes: 14 additions & 11 deletions modules/build/src/main/scala/scala/build/internal/Runner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object Runner {
logger: Logger,
allowExecve: Boolean = false,
cwd: Option[os.Path] = None
): Int = {
): Process = {

import logger.{log, debug}

Expand Down Expand Up @@ -54,7 +54,8 @@ object Runner {
.inheritIO()
for (dir <- cwd)
b.directory(dir.toIO)
b.start().waitFor()
val process = b.start()
process
}
}

Expand All @@ -67,7 +68,7 @@ object Runner {
logger: Logger,
allowExecve: Boolean = false,
cwd: Option[os.Path] = None
): Int = {
): Process = {

val command =
Seq(javaCommand) ++
Expand Down Expand Up @@ -116,7 +117,7 @@ object Runner {
args: Seq[String],
logger: Logger,
allowExecve: Boolean = false
): Int = {
): Process = {

import logger.{log, debug}

Expand All @@ -138,19 +139,20 @@ object Runner {
)
sys.error("should not happen")
}
else
new ProcessBuilder(command: _*)
else {
val process = new ProcessBuilder(command: _*)
.inheritIO()
.start()
.waitFor()
process
}
}

def runNative(
launcher: File,
args: Seq[String],
logger: Logger,
allowExecve: Boolean = false
): Int = {
): Process = {

import logger.{log, debug}

Expand All @@ -171,11 +173,12 @@ object Runner {
)
sys.error("should not happen")
}
else
new ProcessBuilder(command: _*)
else {
val process = new ProcessBuilder(command: _*)
.inheritIO()
.start()
.waitFor()
process
}
}

private def runTests(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ final case class SharedWatchOptions(

@HelpMessage("Watch source files for changes")
@Name("w")
watch: Boolean = false
watch: Boolean = false,
@HelpMessage("Run your application in background and automatically restart if sources have been changed")
revolver: Boolean = false
) { // format: on

)
// format: on
lazy val watchMode = watch || revolver
}

object SharedWatchOptions {
lazy val parser: Parser[SharedWatchOptions] = Parser.derive
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ object Compile extends ScalaCommand[CompileOptions] {

val compilerMaker = options.shared.compilerMaker(threads)

if (options.watch.watch) {
if (options.watch.watchMode) {
val watcher = Build.watch(
inputs,
buildOptions,
Expand Down
6 changes: 3 additions & 3 deletions modules/cli/src/main/scala/scala/cli/commands/Package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ object Package extends ScalaCommand[PackageOptions] {

val cross = options.compileCross.cross.getOrElse(false)

if (options.watch.watch) {
if (options.watch.watchMode) {
var expectedModifyEpochSecondOpt = Option.empty[Long]
val watcher = Build.watch(
inputs,
Expand Down Expand Up @@ -401,7 +401,7 @@ object Package extends ScalaCommand[PackageOptions] {
args,
logger,
cwd = Some(build.inputs.workspace)
)
).waitFor()
if (retCode == 0)
Library.libraryJar(build, hasActualManifest = false, contentDirOverride = Some(destDir))
else
Expand Down Expand Up @@ -759,7 +759,7 @@ object Package extends ScalaCommand[PackageOptions] {
"scala.scalanative.cli.ScalaNativeLd",
args,
logger
)
).waitFor()
if (exitCode == 0)
NativeBuilderHelper.updateProjectAndOutputSha(dest, nativeWorkDir, cacheData.projectSha)
else
Expand Down
6 changes: 3 additions & 3 deletions modules/cli/src/main/scala/scala/cli/commands/Repl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,14 @@ object Repl extends ScalaCommand[ReplOptions] {

if (inputs.isEmpty) {
val artifacts = initialBuildOptions.artifacts(logger).orExit(logger)
doRunRepl(initialBuildOptions, artifacts, None, allowExit = !options.watch.watch)
if (options.watch.watch) {
doRunRepl(initialBuildOptions, artifacts, None, allowExit = !options.watch.watchMode)
if (options.watch.watchMode) {
// nothing to watch, just wait for Ctrl+C
WatchUtil.printWatchMessage()
WatchUtil.waitForCtrlC()
}
}
else if (options.watch.watch) {
else if (options.watch.watchMode) {
val watcher = Build.watch(
inputs,
initialBuildOptions,
Expand Down
96 changes: 61 additions & 35 deletions modules/cli/src/main/scala/scala/cli/commands/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package scala.cli.commands

import caseapp._

import java.util.concurrent.CompletableFuture

import scala.build.EitherCps.{either, value}
import scala.build.errors.BuildException
import scala.build.internal.{Constants, Runner, ScalaJsLinkerConfig}
import scala.build.options.{BuildOptions, JavaOpt, Platform}
import scala.build.{Build, BuildThreads, Inputs, Logger, Positioned}
import scala.cli.CurrentParams
import scala.cli.commands.util.SharedOptionsUtil._
import scala.cli.internal.ProcUtil
import scala.util.Properties

object Run extends ScalaCommand[RunOptions] {
Expand Down Expand Up @@ -58,17 +61,37 @@ object Run extends ScalaCommand[RunOptions] {

val compilerMaker = options.shared.compilerMaker(threads)

def maybeRun(build: Build.Successful, allowTerminate: Boolean): Either[BuildException, Unit] =
maybeRunOnce(
def maybeRun(
build: Build.Successful,
allowTerminate: Boolean
): Either[BuildException, (Process, CompletableFuture[_])] = either {
val process = value(maybeRunOnce(
inputs.workspace,
inputs.projectName,
build,
programArgs,
logger,
allowExecve = allowTerminate,
exitOnError = allowTerminate,
jvmRunner = build.options.addRunnerDependency.getOrElse(true)
)
))

val onExitProcess = process.onExit().thenApply { p1 =>
val retCode = p1.exitValue()
if (retCode != 0)
if (allowTerminate)
sys.exit(retCode)
else {
val red = Console.RED
val lightRed = "\u001b[91m"
val reset = Console.RESET
System.err.println(
s"${red}Program exited with return code $lightRed$retCode$red.$reset"
)
}
}

(process, onExitProcess)
}

val cross = options.compileCross.cross.getOrElse(false)
SetupIde.runSafe(
Expand All @@ -80,7 +103,8 @@ object Run extends ScalaCommand[RunOptions] {
if (CommandUtils.shouldCheckUpdate)
Update.checkUpdateSafe(logger)

if (options.watch.watch) {
if (options.watch.watchMode) {
var processOpt = Option.empty[(Process, CompletableFuture[_])]
val watcher = Build.watch(
inputs,
initialBuildOptions,
Expand All @@ -91,10 +115,21 @@ object Run extends ScalaCommand[RunOptions] {
partial = None,
postAction = () => WatchUtil.printWatchMessage()
) { res =>
for ((process, onExitProcess) <- processOpt) {
onExitProcess.cancel(true)
ProcUtil.interruptProcess(process, logger)
lwronski marked this conversation as resolved.
Show resolved Hide resolved
}
res.orReport(logger).map(_.main).foreach {
case s: Build.Successful =>
maybeRun(s, allowTerminate = false)
for ((proc, _) <- processOpt) // If the process doesn't exit, send SIGKILL
if (proc.isAlive) ProcUtil.forceKillProcess(proc, logger)
val maybeProcess = maybeRun(s, allowTerminate = false)
.orReport(logger)
if (options.watch.revolver)
processOpt = maybeProcess
else
for ((proc, onExit) <- maybeProcess)
ProcUtil.waitForProcess(proc, onExit)
case _: Build.Failed =>
System.err.println("Compilation failed")
}
Expand All @@ -116,8 +151,9 @@ object Run extends ScalaCommand[RunOptions] {
.orExit(logger)
builds.main match {
case s: Build.Successful =>
maybeRun(s, allowTerminate = true)
val (process, onExit) = maybeRun(s, allowTerminate = true)
.orExit(logger)
ProcUtil.waitForProcess(process, onExit)
case _: Build.Failed =>
System.err.println("Compilation failed")
sys.exit(1)
Expand All @@ -132,9 +168,8 @@ object Run extends ScalaCommand[RunOptions] {
args: Seq[String],
logger: Logger,
allowExecve: Boolean,
exitOnError: Boolean,
jvmRunner: Boolean
): Either[BuildException, Unit] = either {
): Either[BuildException, Process] = either {

val mainClassOpt = build.options.mainClass.filter(_.nonEmpty) // trim it too?
.orElse {
Expand All @@ -157,8 +192,7 @@ object Run extends ScalaCommand[RunOptions] {
finalMainClass,
finalArgs,
logger,
allowExecve,
exitOnError
allowExecve
)
value(res)
}
Expand All @@ -170,30 +204,32 @@ object Run extends ScalaCommand[RunOptions] {
mainClass: String,
args: Seq[String],
logger: Logger,
allowExecve: Boolean,
exitOnError: Boolean
): Either[BuildException, Boolean] = either {
allowExecve: Boolean
): Either[BuildException, Process] = either {

val retCode = build.options.platform.value match {
val process = build.options.platform.value match {
case Platform.JS =>
val linkerConfig = build.options.scalaJsOptions.linkerConfig(logger)
val jsDest = os.temp(prefix = "main", suffix = ".js")
val res =
withLinkedJs(
Package.linkJs(
build,
jsDest,
Some(mainClass),
addTestInitializer = false,
linkerConfig,
build.options.scalaJsOptions.fullOpt.getOrElse(false),
build.options.scalaJsOptions.noOpt.getOrElse(false),
logger
) {
js =>
Runner.runJs(
js.toIO,
args,
logger,
allowExecve = allowExecve
)
).map { _ =>
val process = Runner.runJs(
jsDest.toIO,
args,
logger,
allowExecve = allowExecve
)
process.onExit().thenApply(_ => if (os.exists(jsDest)) os.remove(jsDest))
process
}
value(res)
case Platform.Native =>
Expand Down Expand Up @@ -222,17 +258,7 @@ object Run extends ScalaCommand[RunOptions] {
)
}

if (retCode != 0)
if (exitOnError)
sys.exit(retCode)
else {
val red = Console.RED
val lightRed = "\u001b[91m"
val reset = Console.RESET
System.err.println(s"${red}Program exited with return code $lightRed$retCode$red.$reset")
}

retCode == 0
process
}

def withLinkedJs[T](
Expand Down
4 changes: 2 additions & 2 deletions modules/cli/src/main/scala/scala/cli/commands/Test.scala
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ object Test extends ScalaCommand[TestOptions] {
}
}

if (options.watch.watch) {
if (options.watch.watchMode) {
val watcher = Build.watch(
inputs,
initialBuildOptions,
Expand Down Expand Up @@ -221,7 +221,7 @@ object Test extends ScalaCommand[TestOptions] {
extraArgs,
logger,
allowExecve = allowExecve
)
).waitFor()
}
}

Expand Down
Loading