/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
* This source file is part of the Cangjie project, licensed under Apache-2.0
* with Runtime Library Exception.
*
* See https://cangjie-lang.cn/pages/LICENSE for license information.
*/
// The Cangjie API is in Beta. For details on its capabilities and limitations, please refer to the README file.
package std.process
import std.collection.*
import std.fs.*
import std.io.*
/**
* SubProcess
*
*/
@When[os != "iOS"]
public class SubProcess <: Process {
var _stdIn: OutputStream
var _stdOut: InputStream
var _stdErr: InputStream
init(
pid: Int64,
name: ?String,
command: ?String,
arguments: ?Array<String>,
commandLine: ?Array<String>,
workingDirectory: ?Path,
environment: ?Map<String, String>,
stdIn: OutputStream,
stdOut: InputStream,
stdErr: InputStream
) {
super(pid, name, command, arguments, commandLine, workingDirectory, environment)
this._stdIn = stdIn
this._stdOut = stdOut
this._stdErr = stdErr
}
@When[os == "Windows"]
~init() {
if (this._handle != INVALID_HANDLE) {
unsafe { CJ_OS_CloseProcessHandle(this._handle) }
}
}
public prop stdInPipe: OutputStream {
get() {
this._stdIn
}
}
public prop stdOutPipe: InputStream {
get() {
this._stdOut
}
}
public prop stdErrPipe: InputStream {
get() {
this._stdErr
}
}
@Deprecated[message: "Use `public prop stdErrPipe: InputStream` instead."]
public prop stdErr: InputStream {
get() {
this._stdErr
}
}
@Deprecated[message: "Use `public prop stdOutPipe: InputStream` instead."]
public prop stdOut: InputStream {
get() {
this._stdOut
}
}
@Deprecated[message: "Use `public prop stdInPipe: OutputStream` instead."]
public prop stdIn: OutputStream {
get() {
this._stdIn
}
}
/**
* wait func developer should read the outputStream or errStream first and then invoke this func.
* if developer invoke wait func first and then read stream, it can be occur unexpected concurrency results.
*
* @param timeout
* @return Int64
*/
public func wait(timeout!: ?Duration = None): Int64 {
// implement func for wait subprocess exec result and return statuscode
let exitCode: Int64 = getWaitExitCode(timeout ?? Duration.Zero, Int32(this.pid), this._handle)
closeStdStream()
return exitCode
}
/**
* waitOutput func is applicable to scenarios where number of bytes in output or error stream is small.
* If you need to deal with big size within output or error stream, please use stdOut or stdErr prop and wait func.
*
* @return (exitCode: Int64, out: Array<Byte>, err: Array<Byte>)
*/
public func waitOutput(): (Int64, Array<Byte>, Array<Byte>) {
let outStreamFuture: Future<Array<Byte>> = spawn {
=> try {
let outByteArrStream: ByteBuffer = readToByteBuffer(this._stdOut)
return readToEnd(outByteArrStream)
} catch (_: ProcessException) {
// processException while read outputStream means it is invalid.
throw ProcessException("OutputStream is invalid, not allow to read.")
}
}
let errStreamFuture: Future<Array<Byte>> = spawn {
=> try {
let errByteArrStream: ByteBuffer = readToByteBuffer(this._stdErr)
return readToEnd(errByteArrStream)
} catch (_: ProcessException) {
// processException while read errStream means it is invalid.
throw ProcessException("ErrorStream is invalid, not allow to read.")
}
}
let outArr: Array<Byte> = outStreamFuture.get()
let errArr: Array<Byte> = errStreamFuture.get()
// implement func for wait subprocess exec result and return statuscode, stdOut and stdErr
let exitCode: Int64 = getWaitExitCode(Duration.Zero, Int32(this.pid), this._handle)
closeStdStream()
return (exitCode, outArr, errArr)
}
func closeStdStream(): Unit {
if (let Some(stdInStream) <- (this._stdIn as ProcessOutputStream)) {
if (let Some(stdInPipeOutputStream) <- (stdInStream.outputStream as PipeOutputStream)) {
try {
stdInPipeOutputStream.close()
} finally {
stdInStream.outputStream = NullProcessStream()
}
}
}
if (let Some(stdOutStream) <- (this._stdOut as ProcessInputStream)) {
if (let Some(stdOutPipeInputStream) <- (stdOutStream.inputStream as PipeInputStream)) {
try {
stdOutPipeInputStream.close()
} finally {
stdOutStream.inputStream = NullProcessStream()
}
}
}
if (let Some(stdErrStream) <- (this._stdErr as ProcessInputStream)) {
if (let Some(stdErrPipeInputStream) <- (stdErrStream.inputStream as PipeInputStream)) {
try {
stdErrPipeInputStream.close()
} finally {
stdErrStream.inputStream = NullProcessStream()
}
}
}
}
}
@When[os == "iOS"]
@Deprecated
public class SubProcess <: Process {
var _stdIn: OutputStream
var _stdOut: InputStream
var _stdErr: InputStream
init(
pid: Int64,
name: ?String,
command: ?String,
arguments: ?Array<String>,
commandLine: ?Array<String>,
workingDirectory: ?Path,
environment: ?Map<String, String>,
stdIn: OutputStream,
stdOut: InputStream,
stdErr: InputStream
) {
super(pid, name, command, arguments, commandLine, workingDirectory, environment)
this._stdIn = stdIn
this._stdOut = stdOut
this._stdErr = stdErr
}
@When[os == "Windows"]
~init() {
if (this._handle != INVALID_HANDLE) {
unsafe { CJ_OS_CloseProcessHandle(this._handle) }
}
}
public prop stdInPipe: OutputStream {
get() {
this._stdIn
}
}
public prop stdOutPipe: InputStream {
get() {
this._stdOut
}
}
public prop stdErrPipe: InputStream {
get() {
this._stdErr
}
}
@Deprecated[message: "Use `public prop stdErrPipe: InputStream` instead."]
public prop stdErr: InputStream {
get() {
this._stdErr
}
}
@Deprecated[message: "Use `public prop stdOutPipe: InputStream` instead."]
public prop stdOut: InputStream {
get() {
this._stdOut
}
}
@Deprecated[message: "Use `public prop stdInPipe: OutputStream` instead."]
public prop stdIn: OutputStream {
get() {
this._stdIn
}
}
/**
* wait func developer should read the outputStream or errStream first and then invoke this func.
* if developer invoke wait func first and then read stream, it can be occur unexpected concurrency results.
*
* @param timeout
* @return Int64
*/
public func wait(timeout!: ?Duration = None): Int64 {
// implement func for wait subprocess exec result and return statuscode
let exitCode: Int64 = getWaitExitCode(timeout ?? Duration.Zero, Int32(this.pid), this._handle)
closeStdStream()
return exitCode
}
/**
* waitOutput func is applicable to scenarios where number of bytes in output or error stream is small.
* If you need to deal with big size within output or error stream, please use stdOut or stdErr prop and wait func.
*
* @return (exitCode: Int64, out: Array<Byte>, err: Array<Byte>)
*/
public func waitOutput(): (Int64, Array<Byte>, Array<Byte>) {
let outStreamFuture: Future<Array<Byte>> = spawn {
=> try {
let outByteArrStream: ByteBuffer = readToByteBuffer(this._stdOut)
return readToEnd(outByteArrStream)
} catch (_: ProcessException) {
// processException while read outputStream means it is invalid.
throw ProcessException("OutputStream is invalid, not allow to read.")
}
}
let errStreamFuture: Future<Array<Byte>> = spawn {
=> try {
let errByteArrStream: ByteBuffer = readToByteBuffer(this._stdErr)
return readToEnd(errByteArrStream)
} catch (_: ProcessException) {
// processException while read errStream means it is invalid.
throw ProcessException("ErrorStream is invalid, not allow to read.")
}
}
let outArr: Array<Byte> = outStreamFuture.get()
let errArr: Array<Byte> = errStreamFuture.get()
// implement func for wait subprocess exec result and return statuscode, stdOut and stdErr
let exitCode: Int64 = getWaitExitCode(Duration.Zero, Int32(this.pid), this._handle)
closeStdStream()
return (exitCode, outArr, errArr)
}
func closeStdStream(): Unit {
if (let Some(stdInStream) <- (this._stdIn as ProcessOutputStream)) {
if (let Some(stdInPipeOutputStream) <- (stdInStream.outputStream as PipeOutputStream)) {
try {
stdInPipeOutputStream.close()
} finally {
stdInStream.outputStream = NullProcessStream()
}
}
}
if (let Some(stdOutStream) <- (this._stdOut as ProcessInputStream)) {
if (let Some(stdOutPipeInputStream) <- (stdOutStream.inputStream as PipeInputStream)) {
try {
stdOutPipeInputStream.close()
} finally {
stdOutStream.inputStream = NullProcessStream()
}
}
}
if (let Some(stdErrStream) <- (this._stdErr as ProcessInputStream)) {
if (let Some(stdErrPipeInputStream) <- (stdErrStream.inputStream as PipeInputStream)) {
try {
stdErrPipeInputStream.close()
} finally {
stdErrStream.inputStream = NullProcessStream()
}
}
}
}
}
@When[os == "Windows"]
func getWaitExitCode(timeout: Duration, pid: Int32, handle: IntNative): Int64 {
let exitCode: Int64
if (timeout > Duration.Zero) {
let future: Future<CPointer<ExitInfo>> = spawn {
=> return unsafe { CJ_OS_WaitSubProcessExit(handle) }
}
var waitExitInfo_cp: CPointer<ExitInfo> = CPointer<ExitInfo>()
try {
waitExitInfo_cp = if (timeout >= MAX_TIMEOUT_DURATION) {
future.get(MAX_TIMEOUT_DURATION)
} else {
future.get(timeout)
}
return parseExitInfoPointer(waitExitInfo_cp, pid)
} finally {
if (!waitExitInfo_cp.isNull()) {
unsafe { LibC.free(waitExitInfo_cp) }
}
}
}
var waitExitInfo_cp: CPointer<ExitInfo> = CPointer<ExitInfo>()
try {
waitExitInfo_cp = unsafe { CJ_OS_WaitSubProcessExit(handle) }
return parseExitInfoPointer(waitExitInfo_cp, pid)
} finally {
if (!waitExitInfo_cp.isNull()) {
unsafe { LibC.free(waitExitInfo_cp) }
}
}
}
@When[os == "Windows"]
func parseExitInfoPointer(waitExitInfo_cp: CPointer<ExitInfo>, pid: Int32): Int64 {
if (waitExitInfo_cp.isNull()) {
throw ProcessException("Wait subProcess exception, pid: ${pid} not exist.")
}
let waitExitInfo: ExitInfo = unsafe { waitExitInfo_cp.read() }
if (waitExitInfo.error) {
throw ProcessException("Wait subProcess exception, pid: ${pid} not exist.")
}
return waitExitInfo.exitCode
}
@When[os != "Windows"]
func getWaitExitCode(timeout: Duration, pid: Int32, _: IntNative): Int64 {
return if (timeout > Duration.Zero) {
let future: Future<Int64> = spawn {
=> return unsafe { CJ_OS_WaitSubProcessExit(pid) }
}
if (timeout >= MAX_TIMEOUT_DURATION) {
future.get(MAX_TIMEOUT_DURATION)
} else {
future.get(timeout)
}
} else {
unsafe { CJ_OS_WaitSubProcessExit(pid) }
}
}