From b5194aab6f1f57aff6e4538acaf91245fdf15039 Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Thu, 15 Jun 2017 22:34:42 -0400 Subject: add process library with extracted and new functions --- build/build.scala | 2 +- libraries/process/build/build.scala | 10 ++ libraries/process/build/build/build.scala | 5 + libraries/process/process.scala | 175 ++++++++++++++++++++++++++++++ libraries/process/test/test.scala | 7 ++ stage1/Stage1.scala | 1 + stage1/Stage1Lib.scala | 93 ---------------- stage1/resolver.scala | 26 ++--- stage2/BasicBuild.scala | 28 +++++ stage2/Lib.scala | 5 +- stage2/libraries.scala | 1 + 11 files changed, 241 insertions(+), 112 deletions(-) create mode 100644 libraries/process/build/build.scala create mode 100644 libraries/process/build/build/build.scala create mode 100644 libraries/process/process.scala create mode 100644 libraries/process/test/test.scala diff --git a/build/build.scala b/build/build.scala index 74bf6da..729e6ed 100644 --- a/build/build.scala +++ b/build/build.scala @@ -13,7 +13,7 @@ class Build(val context: Context) extends Shared with Scalariform with PublishLo super.dependencies ++ Resolver(mavenCentral).bind( MavenDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r"), ScalaDependency("org.scala-lang.modules","scala-xml",constants.scalaXmlVersion) - ) :+ libraries.cbt.reflect :+ libraries.cbt.eval + ) :+ libraries.cbt.reflect :+ libraries.cbt.eval :+ libraries.cbt.process } override def sources = Seq( 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 + } +} diff --git a/stage1/Stage1.scala b/stage1/Stage1.scala index d9bde7c..99c7b1e 100644 --- a/stage1/Stage1.scala +++ b/stage1/Stage1.scala @@ -94,6 +94,7 @@ object Stage1{ stage2.listFiles ++ (stage2 / "plugins").listOrFail ++ (cbtHome / "libraries" / "eval").listOrFail + ++ (cbtHome / "libraries" / "process").listOrFail ).filter(_.isFile).filter(_.toString.endsWith(".scala")) val cls = this.getClass.getClassLoader.loadClass("cbt.NailgunLauncher") diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala index 40b3fed..ab95a41 100644 --- a/stage1/Stage1Lib.scala +++ b/stage1/Stage1Lib.scala @@ -432,99 +432,6 @@ ${sourceFiles.sorted.mkString(" \\\n")} outputLastModified ) } - - 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 - } - } - ) - } - - def runWithIO( commandLine: Seq[String], directory: Option[File] = None ): ExitCode = { - val (out,err,in) = lib.getOutErrIn match { case (l,r, in) => (l.get,r.get, in) } - val pb = new ProcessBuilder( commandLine: _* ) - val exitCode = - if( !NailgunLauncher.runningViaNailgun ){ - pb.inheritIO.start.waitFor - } else { - val process = directory.map( pb.directory( _ ) ).getOrElse( pb ) - .redirectInput(ProcessBuilder.Redirect.PIPE) - .redirectOutput(ProcessBuilder.Redirect.PIPE) - .redirectError(ProcessBuilder.Redirect.PIPE) - .start - - val lock = new AnyRef - - val t1 = lib.asyncPipeCharacterStreamSyncLines( process.getErrorStream, err, lock ) - val t2 = lib.asyncPipeCharacterStreamSyncLines( process.getInputStream, out, lock ) - val t3 = lib.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 - e - } - - ExitCode( exitCode ) - } } import scala.reflect._ diff --git a/stage1/resolver.scala b/stage1/resolver.scala index f4a9b13..13d4070 100644 --- a/stage1/resolver.scala +++ b/stage1/resolver.scala @@ -77,25 +77,16 @@ trait DependencyImplementation extends Dependency{ ) } */ - def fork = false - def runMain( className: String, args: Seq[String] ): ExitCode = { - if(fork){ - val java_exe = new File(System.getProperty("java.home")) / "bin" / "java" - lib.runWithIO( - java_exe.string +: "-cp" +: classpath.string +: className +: args - ) - } else { - lib.getMain( classLoader.loadClass( className ) )( args ) - } - } + def runMain( className: String, args: Seq[String] ): ExitCode = + lib.getMain( classLoader.loadClass( className ) )( args ) - def runMain( args: Seq[String] ): ExitCode = { - val c = mainClass.getOrElse( - throw new RuntimeException( "No main class found in " + this ) - ) - runMain( c.getName, args ) - } + def runMain( args: Seq[String] ): ExitCode = + runMain( mainClassOrFail.getName, args ) + + def mainClassOrFail = mainClass.getOrElse( + throw new RuntimeException( "No main class found in " + this ) + ) def mainClass = lib.pickOne( "Which one do you want to run?", @@ -209,6 +200,7 @@ case class CbtDependencies(cbtLastModified: Long, mavenCache: File, nailgunTarge stage1Dependency +: MavenResolver(cbtLastModified, mavenCache,mavenCentral).bind( MavenDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r"), + MavenDependency("net.java.dev.jna", "jna-platform", "4.4.0"), MavenDependency("org.scala-lang","scala-compiler",constants.scalaVersion) ) ) diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala index 0bdbad7..e41545b 100644 --- a/stage2/BasicBuild.scala +++ b/stage2/BasicBuild.scala @@ -319,4 +319,32 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with SbtDep final def crossScalaVersionsArray = Array(scalaVersion) def publish: Seq[URL] = Seq() + + def fork = false + + def runForked: ExitCode = { + val ( pid, waitFor, destroy ) = runForkedHandles + waitFor() + } + + protected def runForkedHandles = runForked( mainClassOrFail.getName, context.args ) + + def runForked( className: String, args: Seq[String] ): ( Int, () => ExitCode, () => ExitCode ) = + lib.runMainForked( + className, + args, + classpath.string, + Some( context.workingDirectory ), + NailgunLauncher.runningViaNailgun.option( + lib.getOutErrIn match { case (l,r, in) => (l.get,r.get, in) } + ) + ) + + override def runMain( className: String, args: Seq[String] ): ExitCode = { + if(fork){ + runForked(className, args)._2() + } else { + super.runMain( className, args ) + } + } } diff --git a/stage2/Lib.scala b/stage2/Lib.scala index 56f24c6..8801b33 100644 --- a/stage2/Lib.scala +++ b/stage2/Lib.scala @@ -16,7 +16,10 @@ import scala.reflect.NameTransformer case class Developer(id: String, name: String, timezone: String, url: URL) /** Don't extend. Create your own libs :). */ -final class Lib(val logger: Logger) extends Stage1Lib(logger){ +final class Lib(val logger: Logger) extends + Stage1Lib(logger) with + _root_.cbt.process.Module +{ lib => val buildFileName = "build.scala" diff --git a/stage2/libraries.scala b/stage2/libraries.scala index 08a7a74..b4515e1 100644 --- a/stage2/libraries.scala +++ b/stage2/libraries.scala @@ -14,6 +14,7 @@ class libraries( context: Context, scalaVersion: String, scalaMajorVersion: Stri def eval = dep( "eval" ) def file = dep( "file" ) def interfaces = dep( "interfaces" ) + def process = dep( "process" ) def proguard = dep( "proguard" ) def reflect = dep( "reflect" ) def scalatestRunner = dep( "scalatest-runner" ) -- cgit v1.2.3