Migrate Process
This commit is contained in:
parent
528e6ca388
commit
e8535cd25a
7 changed files with 345 additions and 115 deletions
|
@ -22,7 +22,7 @@ package de.staropensource.engine.base.implementable.stream
|
|||
import de.staropensource.engine.base.exception.io.IOAccessException
|
||||
import de.staropensource.engine.base.implementable.PlatformData
|
||||
import de.staropensource.engine.base.platform.StreamPlatform
|
||||
import de.staropensource.engine.base.platform.platformStreamPlatform
|
||||
import de.staropensource.engine.base.platform.platformStreamCreate
|
||||
import de.staropensource.engine.base.platform.platformStreamStandardError
|
||||
import de.staropensource.engine.base.platform.platformStreamStandardInput
|
||||
import de.staropensource.engine.base.platform.platformStreamStandardOutput
|
||||
|
@ -74,10 +74,19 @@ abstract class Stream(
|
|||
}
|
||||
|
||||
|
||||
// -----> Properties
|
||||
// -----> Platform-specific
|
||||
override val platformData: MutableMap<String, Any?> = mutableMapOf()
|
||||
private val platform: StreamPlatform = platformStreamPlatform(this)
|
||||
|
||||
/**
|
||||
* Platform-specific [StreamPlatform]
|
||||
* instance for this [Stream].
|
||||
*
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
private val platform: StreamPlatform = platformStreamCreate(this)
|
||||
|
||||
|
||||
// -----> Properties
|
||||
/**
|
||||
* Indicates whether this stream is closed.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Authors
|
||||
* Licensed under the GNU General Public License v3.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.engine.base.platform
|
||||
|
||||
import de.staropensource.engine.base.implementable.stream.ReadStream
|
||||
import de.staropensource.engine.base.implementable.stream.WriteStream
|
||||
import de.staropensource.engine.base.utility.Process
|
||||
|
||||
/**
|
||||
* Platform-specific implementation of [Process].
|
||||
*
|
||||
* @constructor Initializes this platform implementation
|
||||
* @param process [Process] instance
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
abstract class ProcessPlatform (
|
||||
val process: Process
|
||||
) {
|
||||
// -----> Lifecycle
|
||||
/**
|
||||
* Initializes the process.
|
||||
*
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
abstract fun start()
|
||||
|
||||
/**
|
||||
* Kills the process.
|
||||
*
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
abstract fun kill()
|
||||
|
||||
|
||||
// -----> Getters
|
||||
abstract fun isAlive(): Boolean
|
||||
abstract fun getPid(): Long
|
||||
abstract fun getExitCode(): UByte?
|
||||
|
||||
|
||||
// -----> Stream getters
|
||||
/**
|
||||
* Returns the standard input stream.
|
||||
*
|
||||
* @return standard input stream
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
abstract fun getStandardInput(): WriteStream
|
||||
|
||||
/**
|
||||
* Returns the standard output stream.
|
||||
*
|
||||
* @return standard output stream
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
abstract fun getStandardOutput(): ReadStream
|
||||
|
||||
/**
|
||||
* Returns the standard error stream.
|
||||
*
|
||||
* @return standard error stream
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
abstract fun getStandardError(): ReadStream
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [ProcessPlatform] implementation
|
||||
* for the supplied [Process] instance.
|
||||
*
|
||||
* @param process [Process] instance
|
||||
* @return matching [ProcessPlatform] instance
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
internal expect fun platformProcessCreate(process: Process): ProcessPlatform
|
|
@ -50,7 +50,7 @@ abstract class StreamPlatform(
|
|||
* @return matching [StreamPlatform] instance
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
internal expect fun platformStreamPlatform(stream: Stream): StreamPlatform
|
||||
internal expect fun platformStreamCreate(stream: Stream): StreamPlatform
|
||||
|
||||
/**
|
||||
* Returns a [ReadStream] representing
|
||||
|
|
|
@ -22,14 +22,14 @@ package de.staropensource.engine.base.utility
|
|||
import de.staropensource.engine.base.Engine.Companion.logger
|
||||
import de.staropensource.engine.base.exception.VerificationFailedException
|
||||
import de.staropensource.engine.base.exception.io.IOAccessException
|
||||
import de.staropensource.engine.base.implementable.PlatformData
|
||||
import de.staropensource.engine.base.implementable.stream.ReadStream
|
||||
import de.staropensource.engine.base.implementable.stream.Stream
|
||||
import de.staropensource.engine.base.implementable.stream.WriteStream
|
||||
import de.staropensource.engine.base.implementation.stream.JavaReadStream
|
||||
import de.staropensource.engine.base.implementation.stream.JavaWriteStream
|
||||
import de.staropensource.engine.base.platform.ProcessPlatform
|
||||
import de.staropensource.engine.base.platform.platformProcessCreate
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import kotlin.Throws
|
||||
|
||||
/**
|
||||
* Provides a simplified way of
|
||||
|
@ -38,16 +38,20 @@ import java.io.OutputStream
|
|||
* @since v1-alpha10
|
||||
*/
|
||||
@Suppress("Unused")
|
||||
class Process {
|
||||
// -----> Metadata
|
||||
class Process : PlatformData {
|
||||
// -----> Platform-specific
|
||||
override val platformData: MutableMap<String, Any?> = mutableMapOf()
|
||||
|
||||
/**
|
||||
* Contains an instance of Java's
|
||||
* [java.lang.Process] class.
|
||||
* Platform-specific [ProcessPlatform]
|
||||
* instance for this [Stream].
|
||||
*
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
private val process: java.lang.Process
|
||||
private val platform: ProcessPlatform = platformProcessCreate(this)
|
||||
|
||||
|
||||
// -----> Metadata
|
||||
/**
|
||||
* Contains the executable this
|
||||
* [Process] is running.
|
||||
|
@ -70,7 +74,7 @@ class Process {
|
|||
*
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
val initialWorkingDirectory: FileAccess
|
||||
val workingDirectory: FileAccess
|
||||
|
||||
/**
|
||||
* Contains if this [Process] was launched
|
||||
|
@ -81,6 +85,13 @@ class Process {
|
|||
*/
|
||||
val launchedWithFreshEnvironment: Boolean
|
||||
|
||||
/**
|
||||
* Contains all initial environment variables.
|
||||
*
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
val environmentVariables: Map<String, String>
|
||||
|
||||
/**
|
||||
* Contains all registered exit events.
|
||||
*
|
||||
|
@ -147,47 +158,21 @@ class Process {
|
|||
autoWatchStreams: Boolean = true,
|
||||
) {
|
||||
try {
|
||||
val builder: ProcessBuilder = ProcessBuilder()
|
||||
|
||||
// Set properties
|
||||
this.executable = executable
|
||||
this.arguments = arguments
|
||||
this.initialWorkingDirectory = workingDirectory ?: FileAccess(System.getProperty("user.dir"))
|
||||
this.workingDirectory = workingDirectory ?: FileAccess(System.getProperty("user.dir"))
|
||||
this.launchedWithFreshEnvironment = freshEnvironment
|
||||
this.environmentVariables = environmentVariables.toMap()
|
||||
this.exitEvents = exitEvents.toMutableList()
|
||||
|
||||
// Reset environment
|
||||
if (freshEnvironment)
|
||||
builder.environment().clear()
|
||||
|
||||
// Pass arguments
|
||||
builder
|
||||
.command(listOf(executable.toStringRaw()).plus(arguments))
|
||||
.directory(workingDirectory?.file)
|
||||
.environment().putAll(environmentVariables)
|
||||
|
||||
// Start process
|
||||
process = builder.start()
|
||||
platform.start()
|
||||
logger.diag("Started process ${getPid()}")
|
||||
|
||||
// Set streams
|
||||
this.standardError = StandardOutputErrorStream(this, process.errorStream)
|
||||
this.standardOutput = StandardOutputErrorStream(this, process.inputStream)
|
||||
this.standardInput = StandardInputStream(this, process.outputStream)
|
||||
|
||||
// Attach process exit handling
|
||||
process.onExit().thenAccept {
|
||||
logger.diag("Process ${getPid()} died")
|
||||
|
||||
// Invoke all exit events
|
||||
for (exitEvent: UByte.() -> Unit in exitEvents)
|
||||
exitEvent.invoke(process.exitValue().toUByte())
|
||||
|
||||
// Close streams
|
||||
standardInput.close()
|
||||
standardOutput.close()
|
||||
standardError.close()
|
||||
}
|
||||
standardInput = platform.getStandardInput()
|
||||
standardOutput = platform.getStandardOutput()
|
||||
standardError = platform.getStandardError()
|
||||
|
||||
// Connect streams
|
||||
inputStreams.forEach { it.pipes.add(standardInput) }
|
||||
|
@ -216,10 +201,32 @@ class Process {
|
|||
*/
|
||||
fun kill(): Process {
|
||||
logger.diag("Killing process ${getPid()}")
|
||||
process.destroyForcibly()
|
||||
platform.kill()
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the death of this [Process].
|
||||
*
|
||||
* Must only be called by the
|
||||
* matching [ProcessPlatform]
|
||||
* implementation.
|
||||
*
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
fun platformDeath() {
|
||||
logger.diag("Process ${getPid()} died")
|
||||
|
||||
// Invoke all exit events
|
||||
for (exitEvent: UByte.() -> Unit in exitEvents)
|
||||
exitEvent.invoke(getExitCode()!!)
|
||||
|
||||
// Close streams
|
||||
standardInput.close()
|
||||
standardOutput.close()
|
||||
standardError.close()
|
||||
}
|
||||
|
||||
|
||||
// -----> Getters & setters
|
||||
/**
|
||||
|
@ -229,7 +236,7 @@ class Process {
|
|||
* @return still running?
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
fun isAlive(): Boolean = process.isAlive
|
||||
fun isAlive(): Boolean = platform.isAlive()
|
||||
|
||||
/**
|
||||
* Returns the PID of this [Process]
|
||||
|
@ -241,7 +248,7 @@ class Process {
|
|||
* @return process identifier
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
fun getPid(): Long = process.pid()
|
||||
fun getPid(): Long = platform.getPid()
|
||||
|
||||
/**
|
||||
* Returns the code with which
|
||||
|
@ -250,13 +257,7 @@ class Process {
|
|||
* @return exit code or `null` if alive
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
fun getExitCode(): UByte? {
|
||||
return try {
|
||||
process.exitValue().toUByte()
|
||||
} catch (exception: IllegalThreadStateException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
fun getExitCode(): UByte? = platform.getExitCode()
|
||||
|
||||
|
||||
// -----> Miscellaneous
|
||||
|
@ -351,61 +352,4 @@ class Process {
|
|||
throw VerificationFailedException("Expected that process ${getPid()} was not launched with a fresh environment")
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
// -----> Inner classes
|
||||
/**
|
||||
* Used for the [standardInput]
|
||||
* stream of a [Process].
|
||||
*
|
||||
* @constructor Initializes this stream
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
private class StandardInputStream(
|
||||
val process: Process,
|
||||
stream: OutputStream,
|
||||
) : JavaWriteStream(
|
||||
stream,
|
||||
autoFlushAfter = 100UL
|
||||
) {
|
||||
// -----> Closure
|
||||
override fun close() {
|
||||
if (!process.isAlive())
|
||||
super.close()
|
||||
}
|
||||
|
||||
// -----> Writing
|
||||
// Assist in flushing
|
||||
override fun writeByte(byte: Byte): Stream {
|
||||
super.writeByte(byte)
|
||||
if (byte.toInt() == 0x0A)
|
||||
flush()
|
||||
return this
|
||||
}
|
||||
|
||||
override fun writeBytes(bytes: ByteArray): Stream {
|
||||
super.writeBytes(bytes)
|
||||
if (bytes.contains((0x0A).toByte()))
|
||||
flush()
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for the [standardOutput]
|
||||
* and [standardError] streams
|
||||
* of a [Process].
|
||||
*
|
||||
* @constructor Initializes this stream
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
private class StandardOutputErrorStream(
|
||||
val process: Process,
|
||||
stream: InputStream,
|
||||
) : JavaReadStream(stream) {
|
||||
override fun close() {
|
||||
if (!process.isAlive())
|
||||
super.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Authors
|
||||
* Licensed under the GNU General Public License v3.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.engine.base.platform
|
||||
|
||||
import de.staropensource.engine.base.implementable.stream.ReadStream
|
||||
import de.staropensource.engine.base.implementable.stream.Stream
|
||||
import de.staropensource.engine.base.implementable.stream.WriteStream
|
||||
import de.staropensource.engine.base.implementation.stream.JavaReadStream
|
||||
import de.staropensource.engine.base.implementation.stream.JavaWriteStream
|
||||
import de.staropensource.engine.base.utility.Process
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
private typealias JProcess = java.lang.Process
|
||||
|
||||
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
|
||||
/**
|
||||
* Java implementation of [ProcessPlatform].
|
||||
*
|
||||
* @constructor Initializes this platform implementation
|
||||
* @param process [Process] instance
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
private class ProcessPlatformImpl (process: Process) : ProcessPlatform(process) {
|
||||
/**
|
||||
* Contains an instance of Java's
|
||||
* [java.lang.Process] class.
|
||||
*
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
private var jProcess: JProcess? = null
|
||||
|
||||
/**
|
||||
* Contains the standard input
|
||||
* [Stream] of this [Process].
|
||||
*
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
var standardInput: WriteStream? = null
|
||||
|
||||
/**
|
||||
* Contains the standard output
|
||||
* [Stream] of this [Process].
|
||||
*
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
var standardOutput: ReadStream? = null
|
||||
|
||||
/**
|
||||
* Contains the standard error
|
||||
* [Stream] of this [Process].
|
||||
*
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
var standardError: ReadStream? = null
|
||||
|
||||
|
||||
// -----> Lifecycle
|
||||
override fun start() {
|
||||
val builder: ProcessBuilder = ProcessBuilder()
|
||||
|
||||
// Reset environment
|
||||
if (process.launchedWithFreshEnvironment)
|
||||
builder.environment().clear()
|
||||
|
||||
// Pass arguments
|
||||
builder
|
||||
.command(listOf(process.executable.toStringRaw()).plus(process.arguments))
|
||||
.directory(process.workingDirectory.file)
|
||||
.environment().putAll(process.environmentVariables)
|
||||
|
||||
// Start process
|
||||
jProcess = builder.start()
|
||||
|
||||
// Set streams
|
||||
standardError = StandardOutputErrorStream(process, jProcess!!.errorStream)
|
||||
standardOutput = StandardOutputErrorStream(process, jProcess!!.inputStream)
|
||||
standardInput = StandardInputStream(process, jProcess!!.outputStream)
|
||||
|
||||
// Attach process exit handling
|
||||
jProcess!!.onExit().thenAccept {
|
||||
process.platformDeath()
|
||||
}
|
||||
}
|
||||
|
||||
override fun kill() {
|
||||
jProcess!!.destroyForcibly()
|
||||
}
|
||||
|
||||
|
||||
// -----> Getters
|
||||
override fun isAlive(): Boolean = jProcess!!.isAlive
|
||||
|
||||
override fun getPid(): Long = jProcess!!.pid()
|
||||
|
||||
override fun getExitCode(): UByte? = try {
|
||||
jProcess!!.exitValue().toUByte()
|
||||
} catch (_: IllegalThreadStateException) {
|
||||
null
|
||||
}
|
||||
|
||||
|
||||
// -----> Stream getters
|
||||
override fun getStandardInput(): WriteStream = standardInput!!
|
||||
override fun getStandardOutput(): ReadStream = standardOutput!!
|
||||
override fun getStandardError(): ReadStream = standardError!!
|
||||
}
|
||||
|
||||
|
||||
// -----> Actual methods
|
||||
actual fun platformProcessCreate(process: Process): ProcessPlatform = ProcessPlatformImpl(process)
|
||||
|
||||
|
||||
// -----> Inner classes
|
||||
/**
|
||||
* Used for the standard input
|
||||
* stream of a [Process].
|
||||
*
|
||||
* @constructor Initializes this stream
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
private class StandardInputStream(
|
||||
val process: Process,
|
||||
stream: OutputStream,
|
||||
) : JavaWriteStream(
|
||||
stream,
|
||||
autoFlushAfter = 100UL
|
||||
) {
|
||||
// -----> Closure
|
||||
override fun close() {
|
||||
if (!process.isAlive())
|
||||
super.close()
|
||||
}
|
||||
|
||||
// -----> Writing
|
||||
// Assist in flushing
|
||||
override fun writeByte(byte: Byte): Stream {
|
||||
super.writeByte(byte)
|
||||
if (byte.toInt() == 0x0A)
|
||||
flush()
|
||||
return this
|
||||
}
|
||||
|
||||
override fun writeBytes(bytes: ByteArray): Stream {
|
||||
super.writeBytes(bytes)
|
||||
if (bytes.contains((0x0A).toByte()))
|
||||
flush()
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for the standard output
|
||||
* and standard error streams
|
||||
* of a [Process].
|
||||
*
|
||||
* @constructor Initializes this stream
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
private class StandardOutputErrorStream(
|
||||
val process: Process,
|
||||
stream: InputStream,
|
||||
) : JavaReadStream(stream) {
|
||||
override fun close() {
|
||||
if (!process.isAlive())
|
||||
super.close()
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ import java.io.OutputStream
|
|||
* @param stream [Stream] instance
|
||||
* @since v1-alpha10
|
||||
*/
|
||||
class StreamPlatformImpl(stream: Stream) : StreamPlatform(stream) {
|
||||
private class StreamPlatformImpl(stream: Stream) : StreamPlatform(stream) {
|
||||
override fun startWatchingThread() {
|
||||
Thread
|
||||
.ofVirtual()
|
||||
|
@ -53,7 +53,7 @@ class StreamPlatformImpl(stream: Stream) : StreamPlatform(stream) {
|
|||
|
||||
|
||||
// -----> Actual methods
|
||||
actual fun platformStreamPlatform(stream: Stream): StreamPlatform = stream.platformData.getOrPut("platform") { StreamPlatformImpl(stream) } as StreamPlatform
|
||||
actual fun platformStreamCreate(stream: Stream): StreamPlatform = StreamPlatformImpl(stream)
|
||||
actual fun platformStreamStandardInput(): ReadStream = object : JavaReadStream(System.`in`) {
|
||||
override fun close() = Unit
|
||||
override fun closeStream() = Unit
|
||||
|
|
Loading…
Reference in a new issue