diff options
author | Jan Christopher Vogt <oss.nsp@cvogt.org> | 2017-06-15 23:15:37 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-06-15 23:15:37 -0400 |
commit | 494c302e738529ff2113d25910398d7b2b7a196c (patch) | |
tree | d67c88d10906647c7fd56229e6e56121a9107744 /libraries | |
parent | 618711302b7ea29de651c1f771eb3160e236b339 (diff) | |
parent | 09051773461b98d374d1b46dd0a2caa57768ab30 (diff) | |
download | cbt-494c302e738529ff2113d25910398d7b2b7a196c.tar.gz cbt-494c302e738529ff2113d25910398d7b2b7a196c.tar.bz2 cbt-494c302e738529ff2113d25910398d7b2b7a196c.zip |
Merge pull request #523 from cvogt/restart
sbt-revolver like restart feature
Diffstat (limited to 'libraries')
-rw-r--r-- | libraries/process/build/build.scala | 10 | ||||
-rw-r--r-- | libraries/process/build/build/build.scala | 5 | ||||
-rw-r--r-- | libraries/process/process.scala | 175 | ||||
-rw-r--r-- | libraries/process/test/test.scala | 7 |
4 files changed, 197 insertions, 0 deletions
diff --git a/libraries/process/build/build.scala b/libraries/process/build/build.scala new file mode 100644 index 0000000..da859b5 --- /dev/null +++ b/libraries/process/build/build.scala @@ -0,0 +1,10 @@ +package cbt_build.process +import cbt._ +import cbt_internal._ +class Build(val context: Context) extends Library{ + override def inceptionYear = 2017 + override def description = "helpers for process calls" + override def dependencies = super.dependencies ++ Resolver(mavenCentral).bind( + MavenDependency( "net.java.dev.jna", "jna-platform", "4.4.0" ) + ) :+ libraries.cbt.common_1 +} diff --git a/libraries/process/build/build/build.scala b/libraries/process/build/build/build.scala new file mode 100644 index 0000000..d3f98ce --- /dev/null +++ b/libraries/process/build/build/build.scala @@ -0,0 +1,5 @@ +package cbt_build.reflect.build +import cbt._ +class Build(val context: Context) extends BuildBuild with CbtInternal{ + override def dependencies = super.dependencies :+ cbtInternal.library +} diff --git a/libraries/process/process.scala b/libraries/process/process.scala new file mode 100644 index 0000000..982c9d0 --- /dev/null +++ b/libraries/process/process.scala @@ -0,0 +1,175 @@ +package cbt.process +import cbt.ExitCode +import java.io._ + +object `package` extends Module + +trait Module { + def runMainForked( + className: String, + args: Seq[String], + classpath: String, + directory: Option[File], + outErrIn: Option[( OutputStream, OutputStream, InputStream )] + ): ( Int, () => ExitCode, () => ExitCode ) = { + // FIXME: Windows support + val java_exe = new File( System.getProperty( "java.home" ) + "/bin/java" ) + runWithIO( + java_exe.toString +: "-cp" +: classpath +: className +: args, + directory, + outErrIn + ) + } + + def runWithIO( + commandLine: Seq[String], + directory: Option[File], + outErrIn: Option[( OutputStream, OutputStream, InputStream )] + ): ( Int, () => ExitCode, () => ExitCode ) = { + val pb = new ProcessBuilder( commandLine: _* ) + outErrIn.map { + case ( out, err, in ) => + val process = directory.map( pb.directory( _ ) ).getOrElse( pb ) + .redirectInput( ProcessBuilder.Redirect.PIPE ) + .redirectOutput( ProcessBuilder.Redirect.PIPE ) + .redirectError( ProcessBuilder.Redirect.PIPE ) + .start + + ( + processId( process ), + () => { + val lock = new AnyRef + + val t1 = asyncPipeCharacterStreamSyncLines( process.getErrorStream, err, lock ) + val t2 = asyncPipeCharacterStreamSyncLines( process.getInputStream, out, lock ) + val t3 = asyncPipeCharacterStream( System.in, process.getOutputStream, process.isAlive ) + + t1.start + t2.start + t3.start + + t1.join + t2.join + + val e = process.waitFor + System.err.println( scala.Console.RESET + "Please press ENTER to continue..." ) + t3.join + ExitCode( e ) + }, + () => { + process.destroy + Thread.sleep( 20 ) + ExitCode( process.destroyForcibly.waitFor ) + } + ) + }.getOrElse { + val process = pb.inheritIO.start + ( + processId( process ), + () => ExitCode( process.waitFor ), + () => { + process.destroy + Thread.sleep( 20 ) + ExitCode( process.destroyForcibly.waitFor ) + } + ) + } + } + + private def accessField( cls: Class[_], field: String ): java.lang.reflect.Field = { + val f = cls.getDeclaredField( field ) + f.setAccessible( true ) + f + } + + import com.sun.jna.{ Library, Native } + private trait CLibrary extends Library { + def getpid: Int + } + private val CLibraryInstance: CLibrary = Native.loadLibrary( "c", classOf[CLibrary] ).asInstanceOf[CLibrary] + + def currentProcessId: Int = { + if ( Option( System.getProperty( "os.name" ) ).exists( _.startsWith( "Windows" ) ) ) { + com.sun.jna.platform.win32.Kernel32.INSTANCE.GetCurrentProcessId + } else { + CLibraryInstance.getpid + } + } + + /** process id of given Process */ + def processId( process: Process ): Int = { + val clsName = process.getClass.getName + if ( clsName == "java.lang.UNIXProcess" ) { + accessField( process.getClass, "pid" ).getInt( process ) + } else if ( clsName == "java.lang.Win32Process" || clsName == "java.lang.ProcessImpl" ) { + import com.sun.jna.platform.win32.{ WinNT, Kernel32 } + val handle = new WinNT.HANDLE + handle.setPointer( + com.sun.jna.Pointer.createConstant( + accessField( process.getClass, "handle" ).getLong( process ) + ) + ) + Kernel32.INSTANCE.GetProcessId( handle ) + } else { + throw new Exception( "Unexpected Process sub-class: " + clsName ) + } + } + + def asyncPipeCharacterStreamSyncLines( inputStream: InputStream, outputStream: OutputStream, lock: AnyRef ): Thread = { + new Thread( + new Runnable { + def run = { + val b = new BufferedInputStream( inputStream ) + Iterator.continually { + b.read // block until and read next character + }.takeWhile( _ != -1 ).map { c => + lock.synchronized { // synchronize with other invocations + outputStream.write( c ) + Iterator + .continually( b.read ) + .takeWhile( _ != -1 ) + .map { c => + try { + outputStream.write( c ) + outputStream.flush + ( + c != '\n' // release lock when new line was encountered, allowing other writers to slip in + && b.available > 0 // also release when nothing is available to not block other outputs + ) + } catch { + case e: IOException if e.getMessage == "Stream closed" => false + } + } + .takeWhile( identity ) + .length // force entire iterator + } + }.length // force entire iterator + } + } + ) + } + + def asyncPipeCharacterStream( inputStream: InputStream, outputStream: OutputStream, continue: => Boolean ) = { + new Thread( + new Runnable { + def run = { + Iterator + .continually { inputStream.read } + .takeWhile( _ != -1 ) + .map { c => + try { + outputStream.write( c ) + outputStream.flush + true + } catch { + case e: IOException if e.getMessage == "Stream closed" => false + } + } + .takeWhile( identity ) + .takeWhile( _ => continue ) + .length // force entire iterator + } + } + ) + } +} diff --git a/libraries/process/test/test.scala b/libraries/process/test/test.scala new file mode 100644 index 0000000..4e1c1a1 --- /dev/null +++ b/libraries/process/test/test.scala @@ -0,0 +1,7 @@ +object Tests{ + def main(args: Array[String]): Unit = { + val pb = new ProcessBuilder("cat") + val p = pb.start + cbt.process.getProcessId( p ) // checks that it actually gets a process id + } +} |