aboutsummaryrefslogtreecommitdiff
path: root/libraries
diff options
context:
space:
mode:
authorChristopher Vogt <oss.nsp@cvogt.org>2017-06-15 22:34:42 -0400
committerChristopher Vogt <oss.nsp@cvogt.org>2017-06-15 22:43:18 -0400
commitb5194aab6f1f57aff6e4538acaf91245fdf15039 (patch)
tree4516f4c4686ceaf35619a68c0d9d1a6c359d1897 /libraries
parentc65d21ae38bdfb646af991a5f3b1dfe8e41a5318 (diff)
downloadcbt-b5194aab6f1f57aff6e4538acaf91245fdf15039.tar.gz
cbt-b5194aab6f1f57aff6e4538acaf91245fdf15039.tar.bz2
cbt-b5194aab6f1f57aff6e4538acaf91245fdf15039.zip
add process library with extracted and new functions
Diffstat (limited to 'libraries')
-rw-r--r--libraries/process/build/build.scala10
-rw-r--r--libraries/process/build/build/build.scala5
-rw-r--r--libraries/process/process.scala175
-rw-r--r--libraries/process/test/test.scala7
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
+ }
+}