Migrate Process
Some checks failed
PRs & Pushes / test (push) Failing after 1m38s
PRs & Pushes / build-jars (push) Failing after 1m47s
PRs & Pushes / build-apidoc (push) Failing after 3m12s

This commit is contained in:
JeremyStar™ 2024-12-29 05:00:35 +01:00
parent 528e6ca388
commit e8535cd25a
Signed by: JeremyStarTM
GPG key ID: E366BAEF67E4704D
7 changed files with 345 additions and 115 deletions

View file

@ -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.
*

View file

@ -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

View file

@ -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

View file

@ -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()
}
}
}

View file

@ -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()
}
}

View file

@ -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