lyngio: added processes and docs for it, JVM and linux

This commit is contained in:
Sergey Chernov 2026-01-06 02:32:18 +01:00
parent d91acd593a
commit 660a80a26b
22 changed files with 1563 additions and 3 deletions

View File

@ -44,6 +44,7 @@ and it is multithreaded on platforms supporting it (automatically, no code chang
- [Language home](https://lynglang.com) - [Language home](https://lynglang.com)
- [introduction and tutorial](docs/tutorial.md) - start here please - [introduction and tutorial](docs/tutorial.md) - start here please
- [Testing and Assertions](docs/Testing.md) - [Testing and Assertions](docs/Testing.md)
- [Filesystem and Processes (lyngio)](docs/lyngio.md)
- [Return Statement](docs/return_statement.md) - [Return Statement](docs/return_statement.md)
- [Efficient Iterables in Kotlin Interop](docs/EfficientIterables.md) - [Efficient Iterables in Kotlin Interop](docs/EfficientIterables.md)
- [Samples directory](docs/samples) - [Samples directory](docs/samples)

View File

@ -8,7 +8,7 @@ This module provides a uniform, suspend-first filesystem API to Lyng scripts, ba
It exposes a Lyng class `Path` with methods for file and directory operations, including streaming readers for large files. It exposes a Lyng class `Path` with methods for file and directory operations, including streaming readers for large files.
It is a separate library because access to teh filesystem is a security risk we compensate with a separate API that user must explicitly include to the dependency and allow. Together with `FsAceessPolicy` that is required to `createFs()` which actually adds the filesystem to the scope, the security risk is isolated. It is a separate library because access to the filesystem is a security risk we compensate with a separate API that user must explicitly include to the dependency and allow. Together with `FsAccessPolicy` that is required to `createFs()` which actually adds the filesystem to the scope, the security risk is isolated.
Also, it helps keep Lyng core small and focused. Also, it helps keep Lyng core small and focused.
@ -23,7 +23,7 @@ dependencies {
implementation("net.sergeych:lyngio:0.0.1-SNAPSHOT") implementation("net.sergeych:lyngio:0.0.1-SNAPSHOT")
} }
``` ```
Note on maven repository. Lyngio uses ths same maven as Lyng code (`lynglib`) so it is most likely already in your project. If ont, add it to the proper section of your `build.gradle.kts` or settings.gradle.kts: Note on maven repository. Lyngio uses the same maven as Lyng code (`lynglib`) so it is most likely already in your project. If not, add it to the proper section of your `build.gradle.kts` or settings.gradle.kts:
```kotlin ```kotlin
repositories { repositories {
@ -43,9 +43,13 @@ This brings in:
The filesystem module is not installed automatically. You must explicitly register it in the scope’s `ImportManager` using the installer. You can customize access control via `FsAccessPolicy`. The filesystem module is not installed automatically. You must explicitly register it in the scope’s `ImportManager` using the installer. You can customize access control via `FsAccessPolicy`.
Kotlin (host) bootstrap example (imports omitted for brevity): Kotlin (host) bootstrap example:
```kotlin ```kotlin
import net.sergeych.lyng.Scope
import net.sergeych.lyng.io.fs.createFs
import net.sergeych.lyngio.fs.security.PermitAllAccessPolicy
val scope: Scope = Scope.new() val scope: Scope = Scope.new()
val installed: Boolean = createFs(PermitAllAccessPolicy, scope) val installed: Boolean = createFs(PermitAllAccessPolicy, scope)
// installed == true on first registration in this ImportManager, false on repeats // installed == true on first registration in this ImportManager, false on repeats

136
docs/lyng.io.process.md Normal file
View File

@ -0,0 +1,136 @@
### lyng.io.process — Process execution and control for Lyng scripts
This module provides a way to run external processes and shell commands from Lyng scripts. It is designed to be multiplatform and uses coroutines for non-blocking execution.
> **Note:** `lyngio` is a separate library module. It must be explicitly added as a dependency to your host application and initialized in your Lyng scopes.
---
#### Add the library to your project (Gradle)
If you use this repository as a multi-module project, add a dependency on `:lyngio`:
```kotlin
dependencies {
implementation("net.sergeych:lyngio:0.0.1-SNAPSHOT")
}
```
For external projects, ensure you have the appropriate Maven repository configured (see `lyng.io.fs` documentation).
---
#### Install the module into a Lyng Scope
The process module is not installed automatically. You must explicitly register it in the scope’s `ImportManager` using `createProcessModule`. You can customize access control via `ProcessAccessPolicy`.
Kotlin (host) bootstrap example:
```kotlin
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Script
import net.sergeych.lyng.io.process.createProcessModule
import net.sergeych.lyngio.process.security.PermitAllProcessAccessPolicy
// ... inside a suspend function or runBlocking
val scope: Scope = Script.newScope()
createProcessModule(PermitAllProcessAccessPolicy, scope)
// In scripts (or via scope.eval), import the module:
scope.eval("import lyng.io.process")
```
---
#### Using from Lyng scripts
```lyng
import lyng.io.process
// Execute a process with arguments
val p = Process.execute("ls", ["-l", "/tmp"])
for (line in p.stdout) {
println("OUT: " + line)
}
val exitCode = p.waitFor()
println("Process exited with: " + exitCode)
// Run a shell command
val sh = Process.shell("echo 'Hello from shell' | wc -w")
for (line in sh.stdout) {
println("Word count: " + line.trim())
}
// Platform information
val details = Platform.details()
println("OS: " + details.name + " " + details.version + " (" + details.arch + ")")
if (details.kernelVersion != null) {
println("Kernel: " + details.kernelVersion)
}
if (Platform.isSupported()) {
println("Processes are supported!")
}
```
---
#### API Reference
##### `Process` (static methods)
- `execute(executable: String, args: List<String>): RunningProcess` — Start an external process.
- `shell(command: String): RunningProcess` — Run a command through the system shell (e.g., `/bin/sh` or `cmd.exe`).
##### `RunningProcess` (instance methods)
- `stdout: Flow` — Standard output stream as a Lyng Flow of lines.
- `stderr: Flow` — Standard error stream as a Lyng Flow of lines.
- `waitFor(): Int` — Wait for the process to exit and return the exit code.
- `signal(name: String)` — Send a signal to the process (e.g., `"SIGINT"`, `"SIGTERM"`, `"SIGKILL"`).
- `destroy()` — Forcefully terminate the process.
##### `Platform` (static methods)
- `details(): Map` — Get platform details. Returned map keys: `name`, `version`, `arch`, `kernelVersion`.
- `isSupported(): Bool` — True if process execution is supported on the current platform.
---
#### Security Policy
Process execution is a sensitive operation. `lyngio` uses `ProcessAccessPolicy` to control access to `execute` and `shell` operations.
- `ProcessAccessPolicy` — Interface for custom policies.
- `PermitAllProcessAccessPolicy` — Allows all operations.
- `ProcessAccessOp` (sealed) — Operations to check:
- `Execute(executable, args)`
- `Shell(command)`
Example of a restricted policy in Kotlin:
```kotlin
import net.sergeych.lyngio.fs.security.AccessDecision
import net.sergeych.lyngio.fs.security.Decision
import net.sergeych.lyngio.process.security.ProcessAccessOp
import net.sergeych.lyngio.process.security.ProcessAccessPolicy
val restrictedPolicy = object : ProcessAccessPolicy {
override suspend fun check(op: ProcessAccessOp, ctx: AccessContext): AccessDecision {
return when (op) {
is ProcessAccessOp.Execute -> {
if (op.executable == "ls") AccessDecision(Decision.Allow)
else AccessDecision(Decision.Deny, "Only 'ls' is allowed")
}
is ProcessAccessOp.Shell -> AccessDecision(Decision.Deny, "Shell is forbidden")
}
}
}
createProcessModule(restrictedPolicy, scope)
```
---
#### Platform Support
- **JVM:** Full support using `ProcessBuilder`.
- **Native (Linux/macOS):** Support via POSIX.
- **Windows:** Support planned.
- **Android/JS/iOS/Wasm:** Currently not supported; `isSupported()` returns `false` and attempts to run processes will throw `UnsupportedOperationException`.

87
docs/lyngio.md Normal file
View File

@ -0,0 +1,87 @@
### lyngio — Extended I/O and System Library for Lyng
`lyngio` is a separate library that extends the Lyng core (`lynglib`) with powerful, multiplatform, and secure I/O capabilities.
#### Why a separate module?
1. **Security:** I/O and process execution are sensitive operations. By keeping them in a separate module, we ensure that the Lyng core remains 100% safe by default. You only enable what you explicitly need.
2. **Footprint:** Not every script needs filesystem or process access. Keeping these as a separate module helps minimize the dependency footprint for small embedded projects.
3. **Control:** `lyngio` provides fine-grained security policies (`FsAccessPolicy`, `ProcessAccessPolicy`) that allow you to control exactly what a script can do.
#### Included Modules
- **[lyng.io.fs](lyng.io.fs.md):** Async filesystem access. Provides the `Path` class for file/directory operations, streaming, and globbing.
- **[lyng.io.process](lyng.io.process.md):** External process execution and shell commands. Provides `Process`, `RunningProcess`, and `Platform` information.
---
#### Quick Start: Embedding lyngio
##### 1. Add Dependencies (Gradle)
```kotlin
repositories {
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
}
dependencies {
// Both are required for full I/O support
implementation("net.sergeych:lynglib:0.0.1-SNAPSHOT")
implementation("net.sergeych:lyngio:0.0.1-SNAPSHOT")
}
```
##### 2. Initialize in Kotlin (JVM or Native)
To use `lyngio` modules in your scripts, you must install them into your Lyng scope and provide a security policy.
```kotlin
import net.sergeych.lyng.Script
import net.sergeych.lyng.io.fs.createFs
import net.sergeych.lyng.io.process.createProcessModule
import net.sergeych.lyngio.fs.security.PermitAllAccessPolicy
import net.sergeych.lyngio.process.security.PermitAllProcessAccessPolicy
suspend fun runMyScript() {
val scope = Script.newScope()
// Install modules with policies
createFs(PermitAllAccessPolicy, scope)
createProcessModule(PermitAllProcessAccessPolicy, scope)
// Now scripts can import them
scope.eval("""
import lyng.io.fs
import lyng.io.process
println("Working dir: " + Path(".").readUtf8())
println("OS: " + Platform.details().name)
""")
}
```
---
#### Security Tools
`lyngio` is built with a "Secure by Default" philosophy. Every I/O or process operation is checked against a policy.
- **Filesystem Security:** Implement `FsAccessPolicy` to restrict access to specific paths or operations (e.g., read-only access to a sandbox directory).
- **Process Security:** Implement `ProcessAccessPolicy` to restrict which executables can be run or to disable shell execution entirely.
For more details, see the specific module documentation:
- [Filesystem Security Details](lyng.io.fs.md#access-policy-security)
- [Process Security Details](lyng.io.process.md#security-policy)
---
#### Platform Support Overview
| Platform | lyng.io.fs | lyng.io.process |
| :--- | :---: | :---: |
| **JVM** | ✅ | ✅ |
| **Native (Linux/macOS)** | ✅ | ✅ |
| **Native (Windows)** | ✅ | 🚧 (Planned) |
| **Android** | ✅ | ❌ |
| **NodeJS** | ✅ | ❌ |
| **Browser / Wasm** | ✅ (In-memory) | ❌ |

View File

@ -0,0 +1,33 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process
actual fun getPlatformDetails(): PlatformDetails {
return PlatformDetails(
name = "Android",
version = android.os.Build.VERSION.RELEASE,
arch = android.os.Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown",
kernelVersion = System.getProperty("os.version")
)
}
actual fun isProcessSupported(): Boolean = false
actual fun getSystemProcessRunner(): LyngProcessRunner {
throw UnsupportedOperationException("Processes are not supported on Android yet")
}

View File

@ -0,0 +1,234 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng.io.process
import kotlinx.coroutines.flow.Flow
import net.sergeych.lyng.ModuleScope
import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager
import net.sergeych.lyng.statement
import net.sergeych.lyngio.process.*
import net.sergeych.lyngio.process.security.ProcessAccessDeniedException
import net.sergeych.lyngio.process.security.ProcessAccessPolicy
/**
* Install Lyng module `lyng.io.process` into the given scope's ImportManager.
*/
fun createProcessModule(policy: ProcessAccessPolicy, scope: Scope): Boolean =
createProcessModule(policy, scope.importManager)
/** Same as [createProcessModule] but with explicit [ImportManager]. */
fun createProcessModule(policy: ProcessAccessPolicy, manager: ImportManager): Boolean {
val name = "lyng.io.process"
if (manager.packageNames.contains(name)) return false
manager.addPackage(name) { module ->
buildProcessModule(module, policy)
}
return true
}
private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAccessPolicy) {
val runner = try {
SecuredLyngProcessRunner(getSystemProcessRunner(), policy)
} catch (e: Exception) {
null
}
val runningProcessType = object : ObjClass("RunningProcess") {}
runningProcessType.apply {
addFnDoc(
name = "stdout",
doc = "Get standard output stream as a Flow of lines.",
returns = type("lyng.Flow"),
moduleName = module.packageName
) {
val self = thisAs<ObjRunningProcess>()
self.process.stdout.toLyngFlow(this)
}
addFnDoc(
name = "stderr",
doc = "Get standard error stream as a Flow of lines.",
returns = type("lyng.Flow"),
moduleName = module.packageName
) {
val self = thisAs<ObjRunningProcess>()
self.process.stderr.toLyngFlow(this)
}
addFnDoc(
name = "signal",
doc = "Send a signal to the process (e.g. 'SIGINT', 'SIGTERM', 'SIGKILL').",
params = listOf(ParamDoc("signal", type("lyng.String"))),
moduleName = module.packageName
) {
processGuard {
val sigStr = requireOnlyArg<ObjString>().value.uppercase()
val sig = try {
ProcessSignal.valueOf(sigStr)
} catch (e: Exception) {
try {
ProcessSignal.valueOf("SIG$sigStr")
} catch (e2: Exception) {
raiseIllegalArgument("Unknown signal: $sigStr")
}
}
thisAs<ObjRunningProcess>().process.sendSignal(sig)
ObjVoid
}
}
addFnDoc(
name = "waitFor",
doc = "Wait for the process to exit and return its exit code.",
returns = type("lyng.Int"),
moduleName = module.packageName
) {
processGuard {
thisAs<ObjRunningProcess>().process.waitFor().toObj()
}
}
addFnDoc(
name = "destroy",
doc = "Forcefully terminate the process.",
moduleName = module.packageName
) {
thisAs<ObjRunningProcess>().process.destroy()
ObjVoid
}
}
val processType = object : ObjClass("Process") {}
processType.apply {
addClassFnDoc(
name = "execute",
doc = "Execute a process with arguments.",
params = listOf(ParamDoc("executable", type("lyng.String")), ParamDoc("args", type("lyng.List"))),
returns = type("RunningProcess"),
moduleName = module.packageName
) {
if (runner == null) raiseError("Processes are not supported on this platform")
processGuard {
val executable = requiredArg<ObjString>(0).value
val args = requiredArg<ObjList>(1).list.map { it.toString() }
val lp = runner.execute(executable, args)
ObjRunningProcess(runningProcessType, lp)
}
}
addClassFnDoc(
name = "shell",
doc = "Execute a command via system shell.",
params = listOf(ParamDoc("command", type("lyng.String"))),
returns = type("RunningProcess"),
moduleName = module.packageName
) {
if (runner == null) raiseError("Processes are not supported on this platform")
processGuard {
val command = requireOnlyArg<ObjString>().value
val lp = runner.shell(command)
ObjRunningProcess(runningProcessType, lp)
}
}
}
val platformType = object : ObjClass("Platform") {}
platformType.apply {
addClassFnDoc(
name = "details",
doc = "Get platform core details.",
returns = type("lyng.Map"),
moduleName = module.packageName
) {
val d = getPlatformDetails()
ObjMap(mutableMapOf(
ObjString("name") to ObjString(d.name),
ObjString("version") to ObjString(d.version),
ObjString("arch") to ObjString(d.arch),
ObjString("kernelVersion") to (d.kernelVersion?.toObj() ?: ObjNull)
))
}
addClassFnDoc(
name = "isSupported",
doc = "Check if processes are supported on this platform.",
returns = type("lyng.Bool"),
moduleName = module.packageName
) {
isProcessSupported().toObj()
}
}
module.addConstDoc(
name = "Process",
value = processType,
doc = "Process execution and control.",
type = type("Process"),
moduleName = module.packageName
)
module.addConstDoc(
name = "Platform",
value = platformType,
doc = "Platform information.",
type = type("Platform"),
moduleName = module.packageName
)
module.addConstDoc(
name = "RunningProcess",
value = runningProcessType,
doc = "Handle to a running process.",
type = type("RunningProcess"),
moduleName = module.packageName
)
}
class ObjRunningProcess(
override val objClass: ObjClass,
val process: LyngProcess
) : Obj() {
override fun toString(): String = "RunningProcess($process)"
}
private suspend inline fun Scope.processGuard(crossinline block: suspend () -> Obj): Obj {
return try {
block()
} catch (e: ProcessAccessDeniedException) {
raiseError(ObjIllegalOperationException(this, e.reasonDetail ?: "process access denied"))
} catch (e: Exception) {
raiseError(ObjIllegalOperationException(this, e.message ?: "process error"))
}
}
private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow {
val producer = statement {
val builder = (this as? net.sergeych.lyng.ClosureScope)?.callScope?.thisObj as? ObjFlowBuilder
?: this.thisObj as? ObjFlowBuilder
this@toLyngFlow.collect {
try {
builder?.output?.send(ObjString(it))
} catch (e: Exception) {
// Channel closed or other error, stop collecting
return@collect
}
}
ObjVoid
}
return ObjFlow(producer, flowScope)
}

View File

@ -1,3 +1,20 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/* /*
* Filesystem module builtin docs registration, located in lyngio so core library * Filesystem module builtin docs registration, located in lyngio so core library
* does not depend on external packages. The IDEA plugin (and any other tooling) * does not depend on external packages. The IDEA plugin (and any other tooling)

View File

@ -0,0 +1,93 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process
import kotlinx.coroutines.flow.Flow
import net.sergeych.lyngio.process.security.ProcessAccessOp
import net.sergeych.lyngio.process.security.ProcessAccessPolicy
/**
* Common signals for process control.
*/
enum class ProcessSignal {
SIGINT, SIGTERM, SIGKILL
}
/**
* Multiplatform process representation.
*/
interface LyngProcess {
/**
* Standard output stream as a flow of strings (lines).
*/
val stdout: Flow<String>
/**
* Standard error stream as a flow of strings (lines).
*/
val stderr: Flow<String>
/**
* Send a signal to the process.
* Throws exception if signals are not supported on the platform or for this process.
*/
suspend fun sendSignal(signal: ProcessSignal)
/**
* Wait for the process to exit and return the exit code.
*/
suspend fun waitFor(): Int
/**
* Forcefully terminate the process.
*/
fun destroy()
}
/**
* Interface for running processes.
*/
interface LyngProcessRunner {
/**
* Execute a process with the given executable and arguments.
*/
suspend fun execute(executable: String, args: List<String>): LyngProcess
/**
* Execute a command via the platform's default shell.
*/
suspend fun shell(command: String): LyngProcess
}
/**
* Secured implementation of [LyngProcessRunner] that checks against a [ProcessAccessPolicy].
*/
class SecuredLyngProcessRunner(
private val runner: LyngProcessRunner,
private val policy: ProcessAccessPolicy
) : LyngProcessRunner {
override suspend fun execute(executable: String, args: List<String>): LyngProcess {
policy.require(ProcessAccessOp.Execute(executable, args))
return runner.execute(executable, args)
}
override suspend fun shell(command: String): LyngProcess {
policy.require(ProcessAccessOp.Shell(command))
return runner.shell(command)
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process
/**
* Platform core details.
*/
data class PlatformDetails(
val name: String,
val version: String,
val arch: String,
val kernelVersion: String? = null
)
/**
* Get the current platform core details.
*/
expect fun getPlatformDetails(): PlatformDetails
/**
* Check whether the current platform supports processes and shell execution.
*/
expect fun isProcessSupported(): Boolean
/**
* Get the system default [LyngProcessRunner].
* Throws [UnsupportedOperationException] if processes are not supported on this platform.
*/
expect fun getSystemProcessRunner(): LyngProcessRunner

View File

@ -0,0 +1,59 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process.security
import net.sergeych.lyngio.fs.security.AccessContext
import net.sergeych.lyngio.fs.security.AccessDecision
import net.sergeych.lyngio.fs.security.Decision
/**
* Primitive process operations for access control decisions.
*/
sealed interface ProcessAccessOp {
data class Execute(val executable: String, val args: List<String>) : ProcessAccessOp
data class Shell(val command: String) : ProcessAccessOp
}
class ProcessAccessDeniedException(
val op: ProcessAccessOp,
val reasonDetail: String? = null,
) : IllegalStateException("Process access denied for $op" + (reasonDetail?.let { ": $it" } ?: ""))
/**
* Policy interface that decides whether a specific process operation is allowed.
*/
interface ProcessAccessPolicy {
suspend fun check(op: ProcessAccessOp, ctx: AccessContext = AccessContext()): AccessDecision
// Convenience helpers
suspend fun require(op: ProcessAccessOp, ctx: AccessContext = AccessContext()) {
val res = check(op, ctx)
if (!res.isAllowed()) throw ProcessAccessDeniedException(op, res.reason)
}
suspend fun canExecute(executable: String, args: List<String>, ctx: AccessContext = AccessContext()) =
check(ProcessAccessOp.Execute(executable, args), ctx).isAllowed()
suspend fun canShell(command: String, ctx: AccessContext = AccessContext()) =
check(ProcessAccessOp.Shell(command), ctx).isAllowed()
}
object PermitAllProcessAccessPolicy : ProcessAccessPolicy {
override suspend fun check(op: ProcessAccessOp, ctx: AccessContext): AccessDecision =
AccessDecision(Decision.Allow)
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process
internal actual fun getNativeKernelVersion(): String? = null
internal actual fun isNativeProcessSupported(): Boolean = false
internal actual fun getNativeProcessRunner(): LyngProcessRunner {
throw UnsupportedOperationException("Processes are not supported on iOS")
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process
actual fun getPlatformDetails(): PlatformDetails {
return PlatformDetails(
name = "JavaScript",
version = "unknown",
arch = "unknown"
)
}
actual fun isProcessSupported(): Boolean = false
actual fun getSystemProcessRunner(): LyngProcessRunner {
throw UnsupportedOperationException("Processes are not supported on JS")
}

View File

@ -0,0 +1,107 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
actual fun getPlatformDetails(): PlatformDetails {
val osName = System.getProperty("os.name")
return PlatformDetails(
name = osName,
version = System.getProperty("os.version"),
arch = System.getProperty("os.arch"),
kernelVersion = if (osName.lowercase().contains("linux")) {
System.getProperty("os.version")
} else null
)
}
actual fun isProcessSupported(): Boolean = true
actual fun getSystemProcessRunner(): LyngProcessRunner = JvmProcessRunner
object JvmProcessRunner : LyngProcessRunner {
override suspend fun execute(executable: String, args: List<String>): LyngProcess {
val process = ProcessBuilder(listOf(executable) + args)
.start()
return JvmLyngProcess(process)
}
override suspend fun shell(command: String): LyngProcess {
val os = System.getProperty("os.name").lowercase()
val shellCmd = if (os.contains("win")) {
listOf("cmd.exe", "/c", command)
} else {
listOf("sh", "-c", command)
}
val process = ProcessBuilder(shellCmd)
.start()
return JvmLyngProcess(process)
}
}
class JvmLyngProcess(private val process: Process) : LyngProcess {
override val stdout: Flow<String> = flow {
val reader = process.inputStream.bufferedReader()
while (true) {
val line = reader.readLine() ?: break
emit(line)
}
}
override val stderr: Flow<String> = flow {
val reader = process.errorStream.bufferedReader()
while (true) {
val line = reader.readLine() ?: break
emit(line)
}
}
override suspend fun sendSignal(signal: ProcessSignal) {
when (signal) {
ProcessSignal.SIGINT -> {
// SIGINT is hard on JVM without native calls or external 'kill'
val os = System.getProperty("os.name").lowercase()
if (os.contains("win")) {
throw UnsupportedOperationException("SIGINT not supported on Windows JVM")
} else {
// Try to use kill -2 <pid>
try {
val pid = process.pid()
Runtime.getRuntime().exec(arrayOf("kill", "-2", pid.toString())).waitFor()
} catch (e: Exception) {
throw UnsupportedOperationException("Failed to send SIGINT: ${e.message}")
}
}
}
ProcessSignal.SIGTERM -> process.destroy()
ProcessSignal.SIGKILL -> process.destroyForcibly()
}
}
override suspend fun waitFor(): Int = withContext(Dispatchers.IO) {
process.waitFor()
}
override fun destroy() {
process.destroy()
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng.io.process
import kotlinx.coroutines.runBlocking
import net.sergeych.lyng.Compiler
import net.sergeych.lyng.Script
import net.sergeych.lyngio.process.security.PermitAllProcessAccessPolicy
import kotlin.test.Test
import kotlin.test.assertTrue
class LyngProcessModuleTest {
@Test
fun testLyngProcess() = runBlocking {
val scope = Script.newScope()
createProcessModule(PermitAllProcessAccessPolicy, scope)
val code = """
import lyng.io.process
var p = Process.execute("echo", ["hello", "lyng"])
var output = []
for (line in p.stdout()) {
output.add(line)
}
p.waitFor()
output
""".trimIndent()
val script = Compiler.compile(code)
val result = script.execute(scope)
assertTrue(result.inspect(scope).contains("hello lyng"))
}
@Test
fun testLyngShell() = runBlocking {
val scope = Script.newScope()
createProcessModule(PermitAllProcessAccessPolicy, scope)
val code = """
import lyng.io.process
var p = Process.shell("echo 'shell lyng'")
var output = ""
for (line in p.stdout()) {
output = output + line
}
p.waitFor()
output
""".trimIndent()
val script = Compiler.compile(code)
val result = script.execute(scope)
assertTrue(result.inspect(scope).contains("shell lyng"))
}
@Test
fun testPlatformDetails() = runBlocking {
val scope = Script.newScope()
createProcessModule(PermitAllProcessAccessPolicy, scope)
val code = """
import lyng.io.process
Platform.details()
""".trimIndent()
val script = Compiler.compile(code)
val result = script.execute(scope)
assertTrue(result.inspect(scope).contains("name"), "Result should contain 'name', but was: ${result.inspect(scope)}")
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import net.sergeych.lyngio.process.security.PermitAllProcessAccessPolicy
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class JvmProcessTest {
@Test
fun testExecute() = runBlocking {
val runner = getSystemProcessRunner()
val secured = SecuredLyngProcessRunner(runner, PermitAllProcessAccessPolicy)
val process = secured.execute("echo", listOf("hello", "world"))
val output = process.stdout.toList()
assertEquals(listOf("hello world"), output)
assertEquals(0, process.waitFor())
}
@Test
fun testShell() = runBlocking {
val runner = getSystemProcessRunner()
val secured = SecuredLyngProcessRunner(runner, PermitAllProcessAccessPolicy)
val process = secured.shell("echo 'hello shell'")
val output = process.stdout.toList()
assertEquals(listOf("hello shell"), output)
assertEquals(0, process.waitFor())
}
@Test
fun testPlatformDetails() {
val details = getPlatformDetails()
assertTrue(details.name.isNotEmpty())
assertTrue(isProcessSupported())
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process
import kotlinx.cinterop.*
import platform.posix.*
@OptIn(ExperimentalForeignApi::class)
internal actual fun getNativeKernelVersion(): String? {
return memScoped {
val u = alloc<utsname>()
if (uname(u.ptr) == 0) {
u.release.toKString()
} else null
}
}
internal actual fun isNativeProcessSupported(): Boolean = true
internal actual fun getNativeProcessRunner(): LyngProcessRunner = PosixProcessRunner

View File

@ -0,0 +1,152 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process
import kotlinx.cinterop.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import platform.posix.*
@OptIn(ExperimentalForeignApi::class)
internal class NativeLyngProcess(
private val pid: pid_t,
private val stdoutFd: Int,
private val stderrFd: Int
) : LyngProcess {
override val stdout: Flow<String> = createPipeFlow(stdoutFd)
override val stderr: Flow<String> = createPipeFlow(stderrFd)
override suspend fun sendSignal(signal: ProcessSignal) {
val sig = when (signal) {
ProcessSignal.SIGINT -> SIGINT
ProcessSignal.SIGTERM -> SIGTERM
ProcessSignal.SIGKILL -> SIGKILL
}
if (kill(pid, sig) != 0) {
throw RuntimeException("Failed to send signal $signal to process $pid: ${strerror(errno)?.toKString()}")
}
}
override suspend fun waitFor(): Int = withContext(Dispatchers.Default) {
memScoped {
val status = alloc<IntVar>()
if (waitpid(pid, status.ptr, 0) == -1) {
throw RuntimeException("Failed to wait for process $pid: ${strerror(errno)?.toKString()}")
}
val s = status.value
if ((s and 0x7f) == 0) (s shr 8) and 0xff else -1
}
}
override fun destroy() {
kill(pid, SIGKILL)
}
}
@OptIn(ExperimentalForeignApi::class)
private fun createPipeFlow(fd: Int): Flow<String> = flow {
val buffer = ByteArray(4096)
val lineBuffer = StringBuilder()
try {
while (true) {
val bytesRead = buffer.usePinned { pinned ->
read(fd, pinned.addressOf(0), buffer.size.toULong())
}
if (bytesRead <= 0L) break
val text = buffer.decodeToString(endIndex = bytesRead.toInt())
lineBuffer.append(text)
var newlineIdx = lineBuffer.indexOf('\n')
while (newlineIdx != -1) {
val line = lineBuffer.substring(0, newlineIdx)
emit(line)
lineBuffer.deleteRange(0, newlineIdx + 1)
newlineIdx = lineBuffer.indexOf('\n')
}
}
if (lineBuffer.isNotEmpty()) {
emit(lineBuffer.toString())
}
} finally {
close(fd)
}
}.flowOn(Dispatchers.Default)
@OptIn(ExperimentalForeignApi::class)
object PosixProcessRunner : LyngProcessRunner {
override suspend fun execute(executable: String, args: List<String>): LyngProcess = withContext(Dispatchers.Default) {
memScoped {
val pipeStdout = allocArray<IntVar>(2)
val pipeStderr = allocArray<IntVar>(2)
if (pipe(pipeStdout) != 0) throw RuntimeException("Failed to create stdout pipe")
if (pipe(pipeStderr) != 0) {
close(pipeStdout[0])
close(pipeStdout[1])
throw RuntimeException("Failed to create stderr pipe")
}
val pid = fork()
if (pid == -1) {
close(pipeStdout[0])
close(pipeStdout[1])
close(pipeStderr[0])
close(pipeStderr[1])
throw RuntimeException("Failed to fork: ${strerror(errno)?.toKString()}")
}
if (pid == 0) {
// Child process
dup2(pipeStdout[1], 1)
dup2(pipeStderr[1], 2)
close(pipeStdout[0])
close(pipeStdout[1])
close(pipeStderr[0])
close(pipeStderr[1])
val argv = allocArray<CPointerVar<ByteVar>>(args.size + 2)
argv[0] = executable.cstr.ptr
for (i in args.indices) {
argv[i + 1] = args[i].cstr.ptr
}
argv[args.size + 1] = null
execvp(executable, argv)
// If we are here, exec failed
_exit(1)
}
// Parent process
close(pipeStdout[1])
close(pipeStderr[1])
NativeLyngProcess(pid, pipeStdout[0], pipeStderr[0])
}
}
override suspend fun shell(command: String): LyngProcess {
return execute("/bin/sh", listOf("-c", command))
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import net.sergeych.lyng.Compiler
import net.sergeych.lyng.Script
import net.sergeych.lyng.io.process.createProcessModule
import net.sergeych.lyngio.process.security.PermitAllProcessAccessPolicy
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class LinuxProcessTest {
@Test
fun testExecuteEcho() = runBlocking {
val process = PosixProcessRunner.execute("echo", listOf("hello", "native"))
val stdout = process.stdout.toList()
val exitCode = process.waitFor()
assertEquals(0, exitCode)
assertEquals(listOf("hello native"), stdout)
}
@Test
fun testShellCommand() = runBlocking {
val process = PosixProcessRunner.shell("echo 'shell native' && printf 'line2'")
val stdout = process.stdout.toList()
val exitCode = process.waitFor()
assertEquals(0, exitCode)
assertEquals(listOf("shell native", "line2"), stdout)
}
@Test
fun testStderrCapture() = runBlocking {
val process = PosixProcessRunner.shell("echo 'to stdout'; echo 'to stderr' >&2")
val stdout = process.stdout.toList()
val stderr = process.stderr.toList()
process.waitFor()
assertEquals(listOf("to stdout"), stdout)
assertEquals(listOf("to stderr"), stderr)
}
@Test
fun testPlatformDetails() {
val details = getPlatformDetails()
assertEquals("LINUX", details.name)
assertTrue(details.kernelVersion != null)
assertTrue(details.kernelVersion!!.isNotEmpty())
println("Linux Native Details: $details")
}
@Test
fun testLyngModuleNative() = runBlocking {
val scope = Script.newScope()
createProcessModule(PermitAllProcessAccessPolicy, scope)
val code = """
import lyng.io.process
var p = Process.execute("echo", ["hello", "lyng", "native"])
var output = []
for (line in p.stdout()) {
output.add(line)
}
p.waitFor()
println(output)
assertEquals("hello lyng native", output.joinToString(" "))
output
""".trimIndent()
val script = Compiler.compile(code)
val result = script.execute(scope)
assertTrue(result.inspect(scope).contains("hello lyng native"))
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process
import kotlinx.cinterop.*
import platform.posix.*
@OptIn(ExperimentalForeignApi::class)
internal actual fun getNativeKernelVersion(): String? {
return memScoped {
val u = alloc<utsname>()
if (uname(u.ptr) == 0) {
u.release.toKString()
} else null
}
}
internal actual fun isNativeProcessSupported(): Boolean = true
internal actual fun getNativeProcessRunner(): LyngProcessRunner = PosixProcessRunner

View File

@ -0,0 +1,152 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process
import kotlinx.cinterop.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import platform.posix.*
@OptIn(ExperimentalForeignApi::class)
internal class NativeLyngProcess(
private val pid: pid_t,
private val stdoutFd: Int,
private val stderrFd: Int
) : LyngProcess {
override val stdout: Flow<String> = createPipeFlow(stdoutFd)
override val stderr: Flow<String> = createPipeFlow(stderrFd)
override suspend fun sendSignal(signal: ProcessSignal) {
val sig = when (signal) {
ProcessSignal.SIGINT -> SIGINT
ProcessSignal.SIGTERM -> SIGTERM
ProcessSignal.SIGKILL -> SIGKILL
}
if (kill(pid, sig) != 0) {
throw RuntimeException("Failed to send signal $signal to process $pid: ${strerror(errno)?.toKString()}")
}
}
override suspend fun waitFor(): Int = withContext(Dispatchers.Default) {
memScoped {
val status = alloc<IntVar>()
if (waitpid(pid, status.ptr, 0) == -1) {
throw RuntimeException("Failed to wait for process $pid: ${strerror(errno)?.toKString()}")
}
val s = status.value
if ((s and 0x7f) == 0) (s shr 8) and 0xff else -1
}
}
override fun destroy() {
kill(pid, SIGKILL)
}
}
@OptIn(ExperimentalForeignApi::class)
private fun createPipeFlow(fd: Int): Flow<String> = flow {
val buffer = ByteArray(4096)
val lineBuffer = StringBuilder()
try {
while (true) {
val bytesRead = buffer.usePinned { pinned ->
read(fd, pinned.addressOf(0), buffer.size.toULong())
}
if (bytesRead <= 0L) break
val text = buffer.decodeToString(endIndex = bytesRead.toInt())
lineBuffer.append(text)
var newlineIdx = lineBuffer.indexOf('\n')
while (newlineIdx != -1) {
val line = lineBuffer.substring(0, newlineIdx)
emit(line)
lineBuffer.deleteRange(0, newlineIdx + 1)
newlineIdx = lineBuffer.indexOf('\n')
}
}
if (lineBuffer.isNotEmpty()) {
emit(lineBuffer.toString())
}
} finally {
close(fd)
}
}.flowOn(Dispatchers.Default)
@OptIn(ExperimentalForeignApi::class)
object PosixProcessRunner : LyngProcessRunner {
override suspend fun execute(executable: String, args: List<String>): LyngProcess = withContext(Dispatchers.Default) {
memScoped {
val pipeStdout = allocArray<IntVar>(2)
val pipeStderr = allocArray<IntVar>(2)
if (pipe(pipeStdout) != 0) throw RuntimeException("Failed to create stdout pipe")
if (pipe(pipeStderr) != 0) {
close(pipeStdout[0])
close(pipeStdout[1])
throw RuntimeException("Failed to create stderr pipe")
}
val pid = fork()
if (pid == -1) {
close(pipeStdout[0])
close(pipeStdout[1])
close(pipeStderr[0])
close(pipeStderr[1])
throw RuntimeException("Failed to fork: ${strerror(errno)?.toKString()}")
}
if (pid == 0) {
// Child process
dup2(pipeStdout[1], 1)
dup2(pipeStderr[1], 2)
close(pipeStdout[0])
close(pipeStdout[1])
close(pipeStderr[0])
close(pipeStderr[1])
val argv = allocArray<CPointerVar<ByteVar>>(args.size + 2)
argv[0] = executable.cstr.ptr
for (i in args.indices) {
argv[i + 1] = args[i].cstr.ptr
}
argv[args.size + 1] = null
execvp(executable, argv)
// If we are here, exec failed
_exit(1)
}
// Parent process
close(pipeStdout[1])
close(pipeStderr[1])
NativeLyngProcess(pid, pipeStdout[0], pipeStderr[0])
}
}
override suspend fun shell(command: String): LyngProcess {
return execute("/bin/sh", listOf("-c", command))
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process
internal actual fun getNativeKernelVersion(): String? = null
internal actual fun isNativeProcessSupported(): Boolean = true
internal actual fun getNativeProcessRunner(): LyngProcessRunner = WindowsProcessRunner
object WindowsProcessRunner : LyngProcessRunner {
override suspend fun execute(executable: String, args: List<String>): LyngProcess {
throw UnsupportedOperationException("Windows native process execution not implemented yet")
}
override suspend fun shell(command: String): LyngProcess {
return execute("cmd.exe", listOf("/c", command))
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.process
import kotlin.experimental.ExperimentalNativeApi
@OptIn(ExperimentalNativeApi::class)
actual fun getPlatformDetails(): PlatformDetails {
return PlatformDetails(
name = Platform.osFamily.name,
version = "unknown",
arch = Platform.cpuArchitecture.name,
kernelVersion = getNativeKernelVersion()
)
}
internal expect fun getNativeKernelVersion(): String?
actual fun isProcessSupported(): Boolean = isNativeProcessSupported()
internal expect fun isNativeProcessSupported(): Boolean
actual fun getSystemProcessRunner(): LyngProcessRunner = getNativeProcessRunner()
internal expect fun getNativeProcessRunner(): LyngProcessRunner