summaryrefslogtreecommitdiff
path: root/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/ExternalJSEnv.scala
diff options
context:
space:
mode:
Diffstat (limited to 'examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/ExternalJSEnv.scala')
-rw-r--r--examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/ExternalJSEnv.scala200
1 files changed, 200 insertions, 0 deletions
diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/ExternalJSEnv.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/ExternalJSEnv.scala
new file mode 100644
index 0000000..e0aa557
--- /dev/null
+++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/ExternalJSEnv.scala
@@ -0,0 +1,200 @@
+package scala.scalajs.sbtplugin.env
+
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.classpath._
+import scala.scalajs.tools.env._
+import scala.scalajs.tools.logging._
+
+import scala.scalajs.sbtplugin.JSUtils._
+
+import java.io.{ Console => _, _ }
+import scala.io.Source
+
+import scala.concurrent.{Future, Promise}
+import scala.util.Try
+
+abstract class ExternalJSEnv(
+ final protected val additionalArgs: Seq[String],
+ final protected val additionalEnv: Map[String, String]) extends AsyncJSEnv {
+
+ /** Printable name of this VM */
+ protected def vmName: String
+
+ /** Command to execute (on shell) for this VM */
+ protected def executable: String
+
+ protected class AbstractExtRunner(protected val classpath: CompleteClasspath,
+ protected val code: VirtualJSFile, protected val logger: Logger,
+ protected val console: JSConsole) {
+
+ /** JS files used to setup VM */
+ protected def initFiles(): Seq[VirtualJSFile] = Nil
+
+ /** Sends required data to VM Stdin (can throw) */
+ protected def sendVMStdin(out: OutputStream): Unit = {}
+
+ /** VM arguments excluding executable. Override to adapt.
+ * Overrider is responsible to add additionalArgs.
+ */
+ protected def getVMArgs(): Seq[String] = additionalArgs
+
+ /** VM environment. Override to adapt.
+ *
+ * Default is `sys.env` and [[additionalEnv]]
+ */
+ protected def getVMEnv(): Map[String, String] =
+ sys.env ++ additionalEnv
+
+ /** Get files that are a library (i.e. that do not run anything) */
+ protected def getLibJSFiles(): Seq[VirtualJSFile] =
+ initFiles() ++ classpath.allCode
+
+ /** Get all files that are passed to VM (libraries and code) */
+ protected def getJSFiles(): Seq[VirtualJSFile] =
+ getLibJSFiles() :+ code
+
+ /** write a single JS file to a writer using an include fct if appropriate */
+ protected def writeJSFile(file: VirtualJSFile, writer: Writer): Unit = {
+ // The only platform-independent way to do this in JS is to dump the file.
+ writer.write(file.content)
+ writer.write('\n')
+ }
+
+ /** Pipe stdin and stdout from/to VM */
+ final protected def pipeVMData(vmInst: Process): Unit = {
+ // Send stdin to VM.
+ val out = vmInst.getOutputStream()
+ try { sendVMStdin(out) }
+ finally { out.close() }
+
+ // Pipe stdout to console
+ pipeToConsole(vmInst.getInputStream(), console)
+
+ // We are probably done (stdin is closed). Report any errors
+ val errSrc = Source.fromInputStream(vmInst.getErrorStream(), "UTF-8")
+ try { errSrc.getLines.foreach(err => logger.error(err)) }
+ finally { errSrc.close }
+ }
+
+ /** Wait for the VM to terminate, verify exit code */
+ final protected def waitForVM(vmInst: Process): Unit = {
+ // Make sure we are done.
+ vmInst.waitFor()
+
+ // Get return value and return
+ val retVal = vmInst.exitValue
+ if (retVal != 0)
+ sys.error(s"$vmName exited with code $retVal")
+ }
+
+ protected def startVM(): Process = {
+ val vmArgs = getVMArgs()
+ val vmEnv = getVMEnv()
+
+ val allArgs = executable +: vmArgs
+ val pBuilder = new ProcessBuilder(allArgs: _*)
+
+ pBuilder.environment().clear()
+ for ((name, value) <- vmEnv)
+ pBuilder.environment().put(name, value)
+
+ pBuilder.start()
+ }
+
+ /** send a bunch of JS files to an output stream */
+ final protected def sendJS(files: Seq[VirtualJSFile],
+ out: OutputStream): Unit = {
+ val writer = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"))
+ try sendJS(files, writer)
+ finally writer.close()
+ }
+
+ /** send a bunch of JS files to a writer */
+ final protected def sendJS(files: Seq[VirtualJSFile], out: Writer): Unit =
+ files.foreach { writeJSFile(_, out) }
+
+ /** pipe lines from input stream to JSConsole */
+ final protected def pipeToConsole(in: InputStream, console: JSConsole) = {
+ val source = Source.fromInputStream(in, "UTF-8")
+ try { source.getLines.foreach(console.log _) }
+ finally { source.close() }
+ }
+
+ }
+
+ protected class ExtRunner(classpath: CompleteClasspath, code: VirtualJSFile,
+ logger: Logger, console: JSConsole
+ ) extends AbstractExtRunner(classpath, code, logger, console)
+ with JSRunner {
+
+ def run(): Unit = {
+ val vmInst = startVM()
+
+ pipeVMData(vmInst)
+ waitForVM(vmInst)
+ }
+ }
+
+ protected class AsyncExtRunner(classpath: CompleteClasspath,
+ code: VirtualJSFile, logger: Logger, console: JSConsole
+ ) extends AbstractExtRunner(classpath, code, logger, console)
+ with AsyncJSRunner {
+
+ private[this] var vmInst: Process = null
+ private[this] var ioThreadEx: Throwable = null
+ private[this] val promise = Promise[Unit]
+
+ private[this] val thread = new Thread {
+ override def run(): Unit = {
+ // This thread should not be interrupted, so it is safe to use Trys
+ val pipeResult = Try(pipeVMData(vmInst))
+ val vmComplete = Try(waitForVM(vmInst))
+
+ // Store IO exception
+ pipeResult recover {
+ case e => ioThreadEx = e
+ }
+
+ // Chain Try's the other way: We want VM failure first, then IO failure
+ promise.complete(pipeResult orElse vmComplete)
+ }
+ }
+
+ def start(): Future[Unit] = {
+ require(vmInst == null, "start() may only be called once")
+ vmInst = startVM()
+ thread.start()
+ promise.future
+ }
+
+ def stop(): Unit = {
+ require(vmInst != null, "start() must have been called")
+ vmInst.destroy()
+ }
+
+ def isRunning(): Boolean = {
+ require(vmInst != null, "start() must have been called")
+ // Emulate JDK 8 Process.isAlive
+ try {
+ vmInst.exitValue()
+ false
+ } catch {
+ case e: IllegalThreadStateException =>
+ true
+ }
+ }
+
+ def await(): Unit = {
+ require(vmInst != null, "start() must have been called")
+ thread.join()
+ waitForVM(vmInst)
+
+ // At this point, the VM itself didn't fail. We need to check if
+ // anything bad happened while piping the data from the VM
+
+ if (ioThreadEx != null)
+ throw ioThreadEx
+ }
+ }
+
+}