5

I'm working on a proof of concept for a command line tool written in Kotlin/Native, as it seems to be an ideal language and runtime for cross-platform binaries.

This command line tool needs to regularly interact with operating system executables, commands and/or shell functions in the os user $PATH or shell environment. However, I don't see any examples in kotlin-native samples or documentation anywhere that might indicate how to:

  • execute an operating system executable or $PATH command
  • obtain the execution's return code (integer)
  • ensure the executed process's stdin, stdout and stderr file descriptor streams can be represented as OutputStream and InputStreams respectively

In JVM-land, we'd use java.lang.ProcessBuilder for all of this, but that's apparently not available in Kotlin/Native.

I found the cinterop/posix platform.posix.system function, but that doesn't give you access to the process's streams.

In my web research, I found a really nice C tutorial that indicates the only clean way to do this is with fork and dup2 and such, but it's not clear to me if or how that would translate to Kotlin/Native code.

4
  • Hello! AFAIK, both fork() and dup2 got to be in the platform.posix.*, distributed with the compiler. Are you sure, that this is not enough? Commented Jul 25, 2019 at 7:08
  • @ArtyomDegtyarev I know they're there, but I've never used them. As a result, it's not obvious to me how to 1) actually use them effectively with proper error handling in a C+Kotlin best-practices manner and 2) wrap them to enable something similar to ProcessBuilder. Commented Jul 25, 2019 at 17:07
  • any news on this? is it meanwhile possible to exec Process with kotlin native?? Commented Apr 25, 2021 at 21:47
  • @DirkHoffmann unfortunately no. I didn't have time to implement this myself and there aren't native solutions yet that I'm aware of. Commented May 1, 2021 at 1:53

1 Answer 1

2

I did some play around with kotlin native and succeded to do a posix exec command for jvm, mac, unix and (untested) windows...

https://github.com/hoffipublic/minimal_kotlin_multiplatform

target common

fun String.executeCommand(
    redirectStderr: Boolean = true
): String? = MppProcess.executeCommand(this, redirectStderr)

interface IMppProcess {
    fun executeCommand(
        command: String,
        redirectStderr: Boolean = true
    ): String?
}

expect object MppProcess : IMppProcess {
    override fun executeCommand(
        command: String,
        redirectStderr: Boolean
    ): String?
}

target jvm

actual object MppProcess : IMppProcess {
    actual override fun executeCommand(
        command: String,
        redirectStderr: Boolean
    ): String? {
        return runCatching {
            ProcessBuilder(command.split(Regex("(?<!(\"|').{0,255}) | (?!.*\\1.*)")))
                //.directory(workingDir)
                .redirectOutput(ProcessBuilder.Redirect.PIPE)
                .apply { if (redirectStderr) this.redirectError(ProcessBuilder.Redirect.PIPE) }
                .start().apply { waitFor(60L, TimeUnit.SECONDS) }
                .inputStream.bufferedReader().readText()
        }.onFailure { it.printStackTrace() }.getOrNull()
    }
}

target mac/unix/windows

import kotlinx.cinterop.refTo
import kotlinx.cinterop.toKString
import platform.posix.fgets
import platform.posix.pclose
import platform.posix.popen

actual object MppProcess : IMppProcess {
    actual override fun executeCommand(
        command: String,
        redirectStderr: Boolean
    ): String? {
        val commandToExecute = if (redirectStderr) "$command 2>&1" else command
        val fp = popen(commandToExecute, "r") ?: error("Failed to run command: $command")

        val stdout = buildString {
            val buffer = ByteArray(4096)
            while (true) {
                val input = fgets(buffer.refTo(0), buffer.size, fp) ?: break
                append(input.toKString())
            }
        }

        val status = pclose(fp)
        if (status != 0) {
            error("Command `$command` failed with status $status${if (redirectStderr) ": $stdout" else ""}")
        }

        return stdout
    }
}

only did execute ls by now, but it works.

Sign up to request clarification or add additional context in comments.

1 Comment

Dirk - did you have a chance to get it to work with fork and dup2 per the "best practices" cited in that article? If not, why did you choose the above approach?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.