├── library └── process │ ├── .gitignore │ ├── README.md │ ├── gradle.properties │ └── src │ ├── nativeInterop │ └── cinterop │ │ ├── spawn.def │ │ ├── kmp_process_sys.def │ │ ├── syscall.def │ │ ├── kmp_process_sys.c │ │ └── kmp_process_sys.h │ ├── jvmMain │ ├── java9 │ │ └── module-info.java │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ ├── -JvmLock.kt │ │ └── -JvmPlatform.kt │ ├── commonTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ ├── CommonTestPlatform.kt │ │ ├── ReadBufferUnitTest.kt │ │ ├── internal │ │ ├── InstanceUnitTest.kt │ │ └── OutputFeedBufferUnitTest.kt │ │ ├── OutputOptionsUnitTest.kt │ │ └── StdioConfigUnitTest.kt │ ├── jvmTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ ├── JvmTestPlatform.kt │ │ └── internal │ │ └── PIDUnitTest.kt │ ├── unixForkMain │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ ├── internal │ │ └── UnixForkPlatform.kt │ │ └── UnixForkProcessBuilder.kt │ ├── iosTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ ├── IosTestPlatform.kt │ │ └── spawn │ │ └── PosixSpawnIosUnitTest.kt │ ├── unixForkTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ └── UnixForkTestPlatform.kt │ ├── darwinTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ └── DarwinTestPlatform.kt │ ├── linuxTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ ├── LinuxTestPlatform.kt │ │ └── spawn │ │ └── PosixFileActionsUnitTest.kt │ ├── jsWasmJsMain │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ ├── internal │ │ ├── node │ │ │ ├── ModuleOs.kt │ │ │ ├── ModuleProcess.kt │ │ │ ├── ModuleBuffer.kt │ │ │ ├── ModuleEvents.kt │ │ │ ├── ModuleFs.kt │ │ │ ├── ModuleChildProcess.kt │ │ │ └── ModuleStream.kt │ │ ├── js │ │ │ └── JsError.kt │ │ ├── JsWasmJsLock.kt │ │ ├── PID.kt │ │ └── JsWasmJsPlatform.kt │ │ └── Blocking.kt │ ├── jsTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── JsTestPlatform.kt │ ├── unixTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ └── UnixTestPlatform.kt │ ├── commonMain │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ ├── internal │ │ ├── PID.kt │ │ ├── -CommonLock.kt │ │ ├── -Instance.kt │ │ ├── -DoNotReferenceDirectly.kt │ │ ├── -SynchronizedSet.kt │ │ ├── PlatformBuilder.kt │ │ ├── -CommonCondition.kt │ │ ├── -OutputFeedBuffer.kt │ │ └── -RealLineOutputFeed.kt │ │ ├── Signal.kt │ │ ├── InternalProcessApi.kt │ │ └── Blocking.kt │ ├── jsMain │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ ├── js │ │ ├── _JsError.kt │ │ ├── _JsArrays.kt │ │ └── _JsObject.kt │ │ ├── node │ │ ├── _ModuleBuffer.kt │ │ ├── _Modules.kt │ │ ├── _ModuleEvents.kt │ │ └── _ModuleStream.kt │ │ ├── JsPlatform.kt │ │ └── JsEvents.kt │ ├── wasmJsTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── WasmJsTestPlatform.kt │ ├── jsWasmJsTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ ├── JsWasmJsTestPlatform.kt │ │ └── internal │ │ └── js │ │ └── JsArrayUnitTest.kt │ ├── nativeMain │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ ├── PID.kt │ │ ├── NativeLock.kt │ │ ├── NativeCloseable.kt │ │ ├── PATHIterator.kt │ │ ├── ReadStream.kt │ │ └── WriteStream.kt │ ├── androidNativeTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ ├── AndroidNativeTestPlatform.kt │ │ ├── stdio │ │ └── StdioDescriptorAndroidNativeUnitTest.kt │ │ └── spawn │ │ └── PosixSpawnAndroidNativeUnitTest.kt │ ├── nativeTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ ├── NativeTestPlatform.kt │ │ └── internal │ │ ├── PATHIteratorUnitTest.kt │ │ └── stdio │ │ └── StdioDescriptorUnitTest.kt │ ├── wasmJsMain │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ ├── node │ │ ├── _ModuleBuffer.kt │ │ ├── _Modules.kt │ │ ├── _ModuleEvents.kt │ │ └── _ModuleStream.kt │ │ └── js │ │ ├── _JsError.kt │ │ ├── _JsArrays.kt │ │ └── _JsObject.kt │ ├── nonAppleMobileMain │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── NonAppleMobileProcessBuilder.kt │ ├── linuxMain │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ ├── stdio │ │ └── LinuxStdioDescriptor.kt │ │ └── LinuxPlatform.kt │ ├── blockingMain │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ ├── internal │ │ ├── WriteStream.kt │ │ └── ReadStream.kt │ │ └── BufferedWriteStream.kt │ ├── androidNativeArm64Main │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ ├── spawn │ │ └── AndroidNativeArm64PosixSpawn.kt │ │ └── AndroidNativeArm64Platform.kt │ ├── androidNativeMain │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ ├── stdio │ │ └── AndroidStdioDescriptor.kt │ │ └── AndroidNativePlatform.kt │ ├── darwinMain │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ ├── DarwinPlatform.kt │ │ ├── stdio │ │ └── DarwinStdioDescriptor.kt │ │ └── spawn │ │ └── DarwinPosixSpawn.kt │ ├── androidNativeArm32Main │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ ├── spawn │ │ └── AndroidNativeArm32PosixSpawn.kt │ │ └── AndroidNativeArm32Platform.kt │ ├── androidNativeX64Main │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ ├── spawn │ │ └── AndroidNativeX64PosixSpawn.kt │ │ └── AndroidNativeX64Platform.kt │ ├── androidNativeX86Main │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ ├── spawn │ │ └── AndroidNativeX86PosixSpawn.kt │ │ └── AndroidNativeX86Platform.kt │ ├── macosMain │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── internal │ │ └── MacosPlatform.kt │ ├── blockingTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ ├── ReadBufferLineLineOutputFeedUnitTest.kt │ │ ├── internal │ │ └── ProcessBlockingUnitTest.kt │ │ └── BlockingUnitTest.kt │ └── unixMain │ └── kotlin │ └── io │ └── matthewnelson │ └── kmp │ └── process │ └── internal │ └── stdio │ └── UnixStdioDescriptor.kt ├── tools └── check-publication │ ├── .gitignore │ ├── src │ ├── androidMain │ │ └── AndroidManifest.xml │ └── commonMain │ │ └── kotlin │ │ └── tools │ │ └── check │ │ └── publication │ │ └── Stub.kt │ └── build.gradle.kts ├── test-api ├── .gitignore ├── README.md └── src │ ├── commonMain │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── test │ │ └── api │ │ └── -Stub.kt │ ├── jsTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── test │ │ └── api │ │ ├── ProcessJsUnitTest.kt │ │ └── JsTestPlatform.kt │ ├── wasmJsTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── test │ │ └── api │ │ ├── ProcessWasmJsUnitTest.kt │ │ └── WasmJsTestPlatform.kt │ ├── androidUnitTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── test │ │ └── api │ │ └── ProcessAndroidUnitTest.kt │ ├── nativeTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── test │ │ └── api │ │ ├── ProcessNativeUnitTest.kt │ │ └── NativeTestPlatform.kt │ ├── jvmTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── test │ │ └── api │ │ └── ProcessJvmUnitTest.kt │ ├── linuxTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── test │ │ └── api │ │ └── LinuxTestPlatform.kt │ ├── macosTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── test │ │ └── api │ │ └── MacosTestPlatform.kt │ ├── androidMain │ └── AndroidManifest.xml │ ├── androidNativeTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── test │ │ └── api │ │ └── AndroidNativeTestPlatform.kt │ ├── jvmAndroidTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── test │ │ └── api │ │ └── JvmAndroidTestPlatform.kt │ ├── commonTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── test │ │ └── api │ │ └── CommonTestPlatform.kt │ ├── iosTest │ └── kotlin │ │ └── io │ │ └── matthewnelson │ │ └── kmp │ │ └── process │ │ └── test │ │ └── api │ │ └── IosTestPlatform.kt │ └── androidInstrumentedTest │ └── kotlin │ └── io │ └── matthewnelson │ └── kmp │ └── process │ └── test │ └── api │ └── ProcessAndroidNativeTest.kt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── RELEASING.md ├── gradle.properties ├── gh-pages └── publish.sh ├── settings.gradle.kts ├── .kotlin-js-store └── wasm │ └── yarn.lock └── gradlew.bat /library/process/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /tools/check-publication/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /test-api/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | src/androidInstrumentedTest/jniLibs/ 3 | -------------------------------------------------------------------------------- /library/process/README.md: -------------------------------------------------------------------------------- 1 | # Module process 2 | 3 | `Process` implementation for Kotlin Multiplatform. 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/05nelsonm/kmp-process/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /test-api/README.md: -------------------------------------------------------------------------------- 1 | # test-api 2 | 3 | Test module (which includes Android) for testing `:library:process` using its public API 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | build/ 3 | *.iml 4 | .idea/ 5 | local.properties 6 | .kotlin/ 7 | 8 | gh-pages/* 9 | !gh-pages/publish.sh 10 | -------------------------------------------------------------------------------- /library/process/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=process 2 | POM_NAME=Kotlin Multiplatform Process 3 | POM_DESCRIPTION=Process for Kotlin Multiplatform 4 | -------------------------------------------------------------------------------- /library/process/src/nativeInterop/cinterop/spawn.def: -------------------------------------------------------------------------------- 1 | package = io.matthewnelson.kmp.process.internal.spawn 2 | headers = spawn.h 3 | headerFilter = spawn.h sys/spawn.h 4 | -------------------------------------------------------------------------------- /library/process/src/nativeInterop/cinterop/kmp_process_sys.def: -------------------------------------------------------------------------------- 1 | package = io.matthewnelson.kmp.process.internal 2 | headers = kmp_process_sys.h 3 | headerFilter = kmp_process_sys.h 4 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # RELEASING 2 | 3 | See [KotlinCrypto/documentation/RELEASING.md][url-kotlincrypto-releasing] 4 | 5 | [url-kotlincrypto-releasing]: https://github.com/KotlinCrypto/documentation/blob/master/RELEASING.md 6 | -------------------------------------------------------------------------------- /library/process/src/nativeInterop/cinterop/syscall.def: -------------------------------------------------------------------------------- 1 | # SYS_pipe2 for AndroidNative testing 2 | package = io.matthewnelson.kmp.process.internal.testing 3 | --- 4 | #ifndef KMP_PROCESS_SYSCALL_TESTING_H 5 | #define KMP_PROCESS_SYSCALL_TESTING_H 6 | 7 | #ifdef __ANDROID__ 8 | #include 9 | #endif /* !defined(__ANDROID__) */ 10 | 11 | #endif /* !defined(KMP_PROCESS_SYSCALL_TESTING_H) */ 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Nov 06 12:59:01 EDT 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStorePath=wrapper/dists 5 | zipStoreBase=GRADLE_USER_HOME 6 | 7 | # https://gradle.org/release-checksums/ 8 | distributionSha256Sum=ed1a8d686605fd7c23bdf62c7fc7add1c5b23b2bbc3721e661934ef4a4911d7c 9 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip 10 | -------------------------------------------------------------------------------- /library/process/src/jvmMain/java9/module-info.java: -------------------------------------------------------------------------------- 1 | module io.matthewnelson.kmp.process { 2 | requires io.matthewnelson.encoding.utf8; 3 | requires io.matthewnelson.immutable.collections; 4 | requires transitive io.matthewnelson.kmp.file; 5 | requires io.matthewnelson.kmp.file.async; 6 | requires java.management; 7 | requires kotlinx.coroutines.core; 8 | 9 | exports io.matthewnelson.kmp.process; 10 | } 11 | -------------------------------------------------------------------------------- /tools/check-publication/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | -------------------------------------------------------------------------------- /tools/check-publication/src/commonMain/kotlin/tools/check/publication/Stub.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package tools.check.publication 17 | 18 | internal fun stub() { /* no-op */ } 19 | -------------------------------------------------------------------------------- /library/process/src/commonTest/kotlin/io/matthewnelson/kmp/process/CommonTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process 17 | 18 | internal expect val IsAppleSimulator: Boolean 19 | -------------------------------------------------------------------------------- /library/process/src/jvmTest/kotlin/io/matthewnelson/kmp/process/JvmTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process 17 | 18 | internal actual val IsAppleSimulator: Boolean = false 19 | -------------------------------------------------------------------------------- /library/process/src/unixForkMain/kotlin/io/matthewnelson/kmp/process/internal/UnixForkPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | internal expect val FD_DIR: String 19 | -------------------------------------------------------------------------------- /test-api/src/commonMain/kotlin/io/matthewnelson/kmp/process/test/api/-Stub.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | @Suppress("UNUSED") 19 | internal fun stub() { /* no-op */ } 20 | -------------------------------------------------------------------------------- /library/process/src/iosTest/kotlin/io/matthewnelson/kmp/process/internal/IosTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | internal actual val PLATFORM_FD_DIR: String? = null 19 | -------------------------------------------------------------------------------- /library/process/src/unixForkTest/kotlin/io/matthewnelson/kmp/process/internal/UnixForkTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | internal actual val PLATFORM_FD_DIR: String? = FD_DIR 19 | -------------------------------------------------------------------------------- /test-api/src/jsTest/kotlin/io/matthewnelson/kmp/process/test/api/ProcessJsUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | @Suppress("UNUSED") 19 | class ProcessJsUnitTest: ProcessBaseTest() 20 | -------------------------------------------------------------------------------- /test-api/src/wasmJsTest/kotlin/io/matthewnelson/kmp/process/test/api/ProcessWasmJsUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | @Suppress("UNUSED") 19 | class ProcessWasmJsUnitTest: ProcessBaseTest() 20 | -------------------------------------------------------------------------------- /test-api/src/androidUnitTest/kotlin/io/matthewnelson/kmp/process/test/api/ProcessAndroidUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | @Suppress("UNUSED") 19 | class ProcessAndroidUnitTest: ProcessBaseTest() 20 | -------------------------------------------------------------------------------- /library/process/src/nativeInterop/cinterop/kmp_process_sys.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | #include "kmp_process_sys.h" 18 | 19 | #ifdef __ANDROID__ 20 | #include 21 | 22 | int 23 | __SYS_pipe2() 24 | { 25 | return SYS_pipe2; 26 | } 27 | #endif /* !defined(__ANDROID__) */ 28 | -------------------------------------------------------------------------------- /library/process/src/darwinTest/kotlin/io/matthewnelson/kmp/process/internal/DarwinTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | internal actual val IS_POSIX_SPAWN_AVAILABLE: Boolean = true 19 | internal actual val IS_COMMAND_WHICH_AVAILABLE: Boolean = true 20 | -------------------------------------------------------------------------------- /library/process/src/linuxTest/kotlin/io/matthewnelson/kmp/process/internal/LinuxTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | internal actual val IS_POSIX_SPAWN_AVAILABLE: Boolean = true 19 | internal actual val IS_COMMAND_WHICH_AVAILABLE: Boolean = true 20 | -------------------------------------------------------------------------------- /library/process/src/jsWasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/node/ModuleOs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.node 17 | 18 | /** [docs](https://nodejs.org/api/os.html) */ 19 | internal external interface ModuleOs { 20 | fun platform(): String? 21 | } 22 | -------------------------------------------------------------------------------- /library/process/src/jsTest/kotlin/io/matthewnelson/kmp/process/JsTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process 17 | 18 | import io.matthewnelson.kmp.process.internal.js.JsArrayBufferView 19 | 20 | internal actual fun jsArrayGet(a: T, index: Int): Byte = js("a[index]") 21 | -------------------------------------------------------------------------------- /library/process/src/unixTest/kotlin/io/matthewnelson/kmp/process/internal/UnixTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | internal expect val IS_POSIX_SPAWN_AVAILABLE: Boolean 19 | internal expect val IS_COMMAND_WHICH_AVAILABLE: Boolean 20 | internal expect val PLATFORM_FD_DIR: String? 21 | -------------------------------------------------------------------------------- /library/process/src/nativeInterop/cinterop/kmp_process_sys.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 KotlinCrypto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | /* https://youtrack.jetbrains.com/issue/KT-75722 */ 18 | #ifndef KMP_PROCESS_SYS_H 19 | #define KMP_PROCESS_SYS_H 20 | 21 | #ifdef __ANDROID__ 22 | int __SYS_pipe2(); 23 | #endif /* !defined(__ANDROID__) */ 24 | 25 | #endif /* !defined(KMP_PROCESS_SYS_H) */ 26 | -------------------------------------------------------------------------------- /library/process/src/commonMain/kotlin/io/matthewnelson/kmp/process/internal/PID.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | internal expect object PID { 21 | @Throws(UnsupportedOperationException::class) 22 | internal fun get(): Int 23 | } 24 | -------------------------------------------------------------------------------- /library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/js/_JsError.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package io.matthewnelson.kmp.process.internal.js 19 | 20 | internal actual typealias JsError = Throwable 21 | 22 | internal actual fun JsError.toThrowable(): Throwable = this 23 | -------------------------------------------------------------------------------- /library/process/src/wasmJsTest/kotlin/io/matthewnelson/kmp/process/WasmJsTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("OPT_IN_USAGE") 17 | 18 | package io.matthewnelson.kmp.process 19 | 20 | import io.matthewnelson.kmp.process.internal.js.JsArrayBufferView 21 | 22 | internal actual fun jsArrayGet(a: T, index: Int): Byte = js("a[index]") 23 | -------------------------------------------------------------------------------- /library/process/src/jsWasmJsTest/kotlin/io/matthewnelson/kmp/process/JsWasmJsTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process 17 | 18 | import io.matthewnelson.kmp.process.internal.js.JsArrayBufferView 19 | 20 | internal actual val IsAppleSimulator: Boolean = false 21 | 22 | internal expect fun jsArrayGet(a: T, index: Int): Byte 23 | -------------------------------------------------------------------------------- /library/process/src/nativeMain/kotlin/io/matthewnelson/kmp/process/internal/PID.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import platform.posix.getpid 21 | 22 | internal actual object PID { 23 | @Throws(UnsupportedOperationException::class) 24 | internal actual fun get(): Int = getpid() 25 | } 26 | -------------------------------------------------------------------------------- /library/process/src/jvmMain/kotlin/io/matthewnelson/kmp/process/internal/-JvmLock.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | internal actual class Lock 21 | 22 | internal actual fun newLock(): Lock = Lock() 23 | 24 | internal actual inline fun Lock.withLockImpl(block: () -> T): T = synchronized(this, block) 25 | -------------------------------------------------------------------------------- /library/process/src/jsWasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/node/ModuleProcess.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.node 17 | 18 | import io.matthewnelson.kmp.process.internal.js.JsObject 19 | 20 | /** [docs](https://nodejs.org/api/process.html) */ 21 | internal external interface ModuleProcess { 22 | val env: JsObject 23 | val pid: Int 24 | val versions: JsObject 25 | } 26 | -------------------------------------------------------------------------------- /library/process/src/androidNativeTest/kotlin/io/matthewnelson/kmp/process/internal/AndroidNativeTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | import platform.posix.android_get_device_api_level 19 | 20 | internal actual val IS_POSIX_SPAWN_AVAILABLE: Boolean = android_get_device_api_level() >= 28 21 | internal actual val IS_COMMAND_WHICH_AVAILABLE: Boolean = android_get_device_api_level() >= 23 22 | -------------------------------------------------------------------------------- /library/process/src/jsWasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/js/JsError.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package io.matthewnelson.kmp.process.internal.js 19 | 20 | import kotlin.js.JsName 21 | 22 | @JsName("Error") 23 | internal expect class JsError { 24 | internal val message: String? 25 | } 26 | 27 | internal expect fun JsError.toThrowable(): Throwable 28 | -------------------------------------------------------------------------------- /library/process/src/nativeTest/kotlin/io/matthewnelson/kmp/process/NativeTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process 17 | 18 | import kotlin.experimental.ExperimentalNativeApi 19 | 20 | @OptIn(ExperimentalNativeApi::class) 21 | internal actual val IsAppleSimulator: Boolean = when (Platform.osFamily) { 22 | OsFamily.IOS, 23 | OsFamily.TVOS, 24 | OsFamily.WATCHOS -> true 25 | else -> false 26 | } 27 | -------------------------------------------------------------------------------- /library/process/src/jsWasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/JsWasmJsLock.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | private val LOCK by lazy { Lock() } 21 | 22 | internal actual class Lock 23 | 24 | internal actual fun newLock(): Lock = LOCK 25 | 26 | internal actual inline fun Lock.withLockImpl(block: () -> T): T = block() 27 | -------------------------------------------------------------------------------- /test-api/src/nativeTest/kotlin/io/matthewnelson/kmp/process/test/api/ProcessNativeUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | import kotlin.experimental.ExperimentalNativeApi 19 | 20 | @Suppress("UNUSED") 21 | class ProcessNativeUnitTest: ProcessBaseTest() { 22 | 23 | @OptIn(ExperimentalNativeApi::class) 24 | override val IsAndroidInstrumentTest: Boolean by lazy { Platform.osFamily == OsFamily.ANDROID } 25 | } 26 | -------------------------------------------------------------------------------- /library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/node/_ModuleBuffer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal.node 19 | 20 | import io.matthewnelson.kmp.file.Buffer 21 | 22 | @JsName("Buffer") 23 | internal actual external interface JsBuffer 24 | 25 | internal actual inline fun JsBuffer.asBuffer(): Buffer = Buffer.wrap(this) 26 | -------------------------------------------------------------------------------- /library/process/src/commonTest/kotlin/io/matthewnelson/kmp/process/ReadBufferUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process 17 | 18 | import kotlin.test.Test 19 | import kotlin.test.assertEquals 20 | 21 | @OptIn(InternalProcessApi::class) 22 | class ReadBufferUnitTest { 23 | 24 | @Test 25 | fun givenAllocate_whenCapacity_thenIs8mb() { 26 | val buf = ReadBuffer.allocate() 27 | assertEquals(8 * 1024, buf.capacity()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /library/process/src/jsWasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/PID.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import io.matthewnelson.kmp.process.internal.node.node_process 21 | 22 | internal actual object PID { 23 | @Throws(UnsupportedOperationException::class) 24 | internal actual fun get(): Int = node_process.pid 25 | } 26 | -------------------------------------------------------------------------------- /library/process/src/wasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/node/_ModuleBuffer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "NOTHING_TO_INLINE", "OPT_IN_USAGE") 17 | 18 | package io.matthewnelson.kmp.process.internal.node 19 | 20 | import io.matthewnelson.kmp.file.Buffer 21 | 22 | @JsName("Buffer") 23 | internal actual external interface JsBuffer: JsAny 24 | 25 | internal actual inline fun JsBuffer.asBuffer(): Buffer = Buffer.wrap(this) 26 | -------------------------------------------------------------------------------- /test-api/src/nativeTest/kotlin/io/matthewnelson/kmp/process/test/api/NativeTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | import kotlin.experimental.ExperimentalNativeApi 19 | 20 | @OptIn(ExperimentalNativeApi::class) 21 | internal actual val IsAppleSimulator: Boolean = when (Platform.osFamily) { 22 | OsFamily.IOS, 23 | OsFamily.TVOS, 24 | OsFamily.WATCHOS -> true 25 | else -> false 26 | } 27 | 28 | internal actual val IsNodeJs: Boolean = false 29 | -------------------------------------------------------------------------------- /test-api/src/jvmTest/kotlin/io/matthewnelson/kmp/process/test/api/ProcessJvmUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | import kotlin.test.Test 19 | 20 | class ProcessJvmUnitTest: ProcessBaseTest() { 21 | 22 | @Test 23 | override fun givenExecutable_whenRelativePathWithChDir_thenExecutes() { 24 | if (IsWindows) { 25 | println("Skipping...") 26 | return 27 | } 28 | super.givenExecutable_whenRelativePathWithChDir_thenExecutes() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /library/process/src/jsWasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/node/ModuleBuffer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal.node 19 | 20 | import io.matthewnelson.kmp.file.Buffer 21 | import kotlin.js.JsName 22 | 23 | internal external interface ModuleBuffer { 24 | // ... 25 | } 26 | 27 | @JsName("Buffer") 28 | internal expect interface JsBuffer 29 | 30 | internal expect inline fun JsBuffer.asBuffer(): Buffer 31 | -------------------------------------------------------------------------------- /test-api/src/linuxTest/kotlin/io/matthewnelson/kmp/process/test/api/LinuxTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | import io.matthewnelson.kmp.tor.common.api.ResourceLoader 19 | import io.matthewnelson.kmp.tor.resource.exec.tor.ResourceLoaderTorExec 20 | 21 | internal actual val AndroidNativeDeviceAPILevel: Int? = null 22 | 23 | internal actual val LOADER: ResourceLoader.Tor.Exec by lazy { 24 | ResourceLoaderTorExec.getOrCreate(TorResourceBinder.RESOURCE_DIR) as ResourceLoader.Tor.Exec 25 | } 26 | -------------------------------------------------------------------------------- /test-api/src/macosTest/kotlin/io/matthewnelson/kmp/process/test/api/MacosTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | import io.matthewnelson.kmp.tor.common.api.ResourceLoader 19 | import io.matthewnelson.kmp.tor.resource.exec.tor.ResourceLoaderTorExec 20 | 21 | internal actual val AndroidNativeDeviceAPILevel: Int? = null 22 | 23 | internal actual val LOADER: ResourceLoader.Tor.Exec by lazy { 24 | ResourceLoaderTorExec.getOrCreate(TorResourceBinder.RESOURCE_DIR) as ResourceLoader.Tor.Exec 25 | } 26 | -------------------------------------------------------------------------------- /library/process/src/iosTest/kotlin/io/matthewnelson/kmp/process/internal/spawn/PosixSpawnIosUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.spawn 17 | 18 | import kotlin.test.Test 19 | import kotlin.test.assertFailsWith 20 | 21 | class PosixSpawnIosUnitTest { 22 | 23 | @Test 24 | fun givenPosixSpawnScopeOrNull_whenChangeDirRequired_thenThrowsException() { 25 | assertFailsWith { 26 | posixSpawnScopeOrNull(requireChangeDir = true) {} 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /library/process/src/nativeMain/kotlin/io/matthewnelson/kmp/process/internal/NativeLock.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import kotlinx.atomicfu.locks.SynchronizedObject 21 | import kotlinx.atomicfu.locks.synchronized 22 | 23 | internal actual class Lock: SynchronizedObject() 24 | 25 | internal actual fun newLock(): Lock = Lock() 26 | 27 | internal actual inline fun Lock.withLockImpl(block: () -> T): T = synchronized(this, block) 28 | -------------------------------------------------------------------------------- /library/process/src/nonAppleMobileMain/kotlin/io/matthewnelson/kmp/process/NonAppleMobileProcessBuilder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process 17 | 18 | import io.matthewnelson.kmp.file.File 19 | 20 | /** 21 | * Changes the working directory of the spawned process. 22 | * 23 | * [directory] must exist, otherwise the process will fail 24 | * to be spawned. 25 | * */ 26 | public fun Process.Builder.changeDir( 27 | directory: File?, 28 | ): Process.Builder = apply { 29 | @Suppress("DEPRECATION_ERROR") 30 | chdir(directory) 31 | } 32 | -------------------------------------------------------------------------------- /test-api/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test-api/src/androidNativeTest/kotlin/io/matthewnelson/kmp/process/test/api/AndroidNativeTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | import io.matthewnelson.kmp.tor.common.api.ResourceLoader 19 | import io.matthewnelson.kmp.tor.resource.exec.tor.ResourceLoaderTorExec 20 | import platform.posix.android_get_device_api_level 21 | 22 | internal actual val AndroidNativeDeviceAPILevel: Int? = android_get_device_api_level() 23 | 24 | internal actual val LOADER: ResourceLoader.Tor.Exec by lazy { 25 | ResourceLoaderTorExec.getOrCreate(TorResourceBinder.RESOURCE_DIR) as ResourceLoader.Tor.Exec 26 | } 27 | -------------------------------------------------------------------------------- /library/process/src/linuxMain/kotlin/io/matthewnelson/kmp/process/internal/stdio/LinuxStdioDescriptor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal.stdio 19 | 20 | import kotlinx.cinterop.CPointer 21 | import kotlinx.cinterop.ExperimentalForeignApi 22 | import kotlinx.cinterop.IntVar 23 | import kotlinx.cinterop.convert 24 | import platform.linux.SYS_pipe2 25 | import platform.posix.syscall 26 | 27 | @OptIn(ExperimentalForeignApi::class) 28 | internal actual inline fun CPointer.pipe2( 29 | flags: Int, 30 | ): Int = syscall(SYS_pipe2.convert(), this, flags).convert() 31 | -------------------------------------------------------------------------------- /library/process/src/nativeMain/kotlin/io/matthewnelson/kmp/process/internal/NativeCloseable.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import io.matthewnelson.kmp.file.Closeable 21 | import io.matthewnelson.kmp.file.IOException 22 | 23 | internal interface NativeCloseable: Closeable { 24 | val isClosed: Boolean 25 | } 26 | 27 | internal inline fun Closeable.tryCloseSuppressed(throwable: T): T { 28 | try { 29 | close() 30 | } catch (e: IOException) { 31 | throwable.addSuppressed(e) 32 | } 33 | return throwable 34 | } 35 | -------------------------------------------------------------------------------- /test-api/src/jsTest/kotlin/io/matthewnelson/kmp/process/test/api/JsTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | import io.matthewnelson.kmp.tor.common.api.ResourceLoader 19 | import io.matthewnelson.kmp.tor.resource.exec.tor.ResourceLoaderTorExec 20 | 21 | internal actual val IsAppleSimulator: Boolean = false 22 | 23 | internal actual val IsNodeJs: Boolean = true 24 | 25 | internal actual val AndroidNativeDeviceAPILevel: Int? = null 26 | 27 | internal actual val LOADER: ResourceLoader.Tor.Exec by lazy { 28 | ResourceLoaderTorExec.getOrCreate(TorResourceBinder.RESOURCE_DIR) as ResourceLoader.Tor.Exec 29 | } 30 | -------------------------------------------------------------------------------- /test-api/src/wasmJsTest/kotlin/io/matthewnelson/kmp/process/test/api/WasmJsTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | import io.matthewnelson.kmp.tor.common.api.ResourceLoader 19 | import io.matthewnelson.kmp.tor.resource.exec.tor.ResourceLoaderTorExec 20 | 21 | internal actual val IsAppleSimulator: Boolean = false 22 | 23 | internal actual val IsNodeJs: Boolean = true 24 | 25 | internal actual val AndroidNativeDeviceAPILevel: Int? = null 26 | 27 | internal actual val LOADER: ResourceLoader.Tor.Exec by lazy { 28 | ResourceLoaderTorExec.getOrCreate(TorResourceBinder.RESOURCE_DIR) as ResourceLoader.Tor.Exec 29 | } 30 | -------------------------------------------------------------------------------- /test-api/src/jvmAndroidTest/kotlin/io/matthewnelson/kmp/process/test/api/JvmAndroidTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | import io.matthewnelson.kmp.tor.common.api.ResourceLoader 19 | import io.matthewnelson.kmp.tor.resource.exec.tor.ResourceLoaderTorExec 20 | 21 | internal actual val IsAppleSimulator: Boolean = false 22 | 23 | internal actual val IsNodeJs: Boolean = false 24 | 25 | internal actual val AndroidNativeDeviceAPILevel: Int? = null 26 | 27 | internal actual val LOADER: ResourceLoader.Tor.Exec by lazy { 28 | ResourceLoaderTorExec.getOrCreate(TorResourceBinder.RESOURCE_DIR) as ResourceLoader.Tor.Exec 29 | } 30 | -------------------------------------------------------------------------------- /library/process/src/blockingMain/kotlin/io/matthewnelson/kmp/process/internal/WriteStream.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "UNUSED") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import io.matthewnelson.kmp.file.Closeable 21 | import io.matthewnelson.kmp.file.IOException 22 | 23 | internal expect abstract class WriteStream: Closeable { 24 | 25 | @Throws(IOException::class) 26 | open fun write(buf: ByteArray, offset: Int, len: Int) 27 | 28 | @Throws(IOException::class) 29 | fun write(buf: ByteArray) 30 | 31 | @Throws(IOException::class) 32 | override fun close() 33 | 34 | @Throws(IOException::class) 35 | open fun flush() 36 | } 37 | -------------------------------------------------------------------------------- /library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/node/_Modules.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.node 17 | 18 | internal actual fun nodeModuleBuffer(): ModuleBuffer = js(CODE_MODULE_BUFFER) 19 | internal actual fun nodeModuleChildProcess(): ModuleChildProcess = js(CODE_MODULE_CHILD_PROCESS) 20 | internal actual fun nodeModuleEvents(): ModuleEvents = js(CODE_MODULE_EVENTS) 21 | internal actual fun nodeModuleFs(): ModuleFs = js(CODE_MODULE_FS) 22 | internal actual fun nodeModuleOs(): ModuleOs = js(CODE_MODULE_OS) 23 | internal actual fun nodeModuleProcess(): ModuleProcess = js(CODE_MODULE_PROCESS) 24 | internal actual fun nodeModuleStream(): ModuleStream = js(CODE_MODULE_STREAM) 25 | -------------------------------------------------------------------------------- /library/process/src/androidNativeArm64Main/kotlin/io/matthewnelson/kmp/process/internal/spawn/AndroidNativeArm64PosixSpawn.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.spawn 17 | 18 | import kotlinx.cinterop.CValuesRef 19 | import kotlinx.cinterop.ExperimentalForeignApi 20 | import kotlinx.cinterop.MemScope 21 | import kotlinx.cinterop.alloc 22 | import kotlinx.cinterop.ptr 23 | import platform.posix.sigemptyset 24 | import platform.posix.sigset_t 25 | 26 | @OptIn(ExperimentalForeignApi::class) 27 | internal actual inline fun MemScope.sigsetInitEmpty(action: (CValuesRef<*>) -> Int): Int { 28 | val sigset = alloc() 29 | if (sigemptyset(sigset.ptr) == -1) return -1 30 | return action(sigset.ptr) 31 | } 32 | -------------------------------------------------------------------------------- /library/process/src/androidNativeMain/kotlin/io/matthewnelson/kmp/process/internal/stdio/AndroidStdioDescriptor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal.stdio 19 | 20 | import io.matthewnelson.kmp.process.internal.__SYS_pipe2 21 | import kotlinx.cinterop.CPointer 22 | import kotlinx.cinterop.ExperimentalForeignApi 23 | import kotlinx.cinterop.IntVar 24 | import kotlinx.cinterop.UnsafeNumber 25 | import kotlinx.cinterop.convert 26 | import platform.posix.syscall 27 | 28 | @OptIn(ExperimentalForeignApi::class, UnsafeNumber::class) 29 | internal actual inline fun CPointer.pipe2( 30 | flags: Int, 31 | ): Int = syscall(__SYS_pipe2().convert(), this, flags).convert() 32 | -------------------------------------------------------------------------------- /library/process/src/darwinMain/kotlin/io/matthewnelson/kmp/process/internal/DarwinPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import platform.Foundation.NSProcessInfo 21 | 22 | internal actual inline fun PlatformBuilder.parentEnvironment(): MutableMap { 23 | // NSDictionary 24 | val env = NSProcessInfo.processInfo.environment 25 | val map = LinkedHashMap(env.size, 1.0F) 26 | for (entry in env.entries) { 27 | val key = entry.key ?: continue 28 | val value = entry.value ?: continue 29 | map[key.toString()] = value.toString() 30 | } 31 | return map 32 | } 33 | -------------------------------------------------------------------------------- /library/process/src/androidNativeArm32Main/kotlin/io/matthewnelson/kmp/process/internal/spawn/AndroidNativeArm32PosixSpawn.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.spawn 17 | 18 | import kotlinx.cinterop.CValuesRef 19 | import kotlinx.cinterop.ExperimentalForeignApi 20 | import kotlinx.cinterop.MemScope 21 | import kotlinx.cinterop.alloc 22 | import kotlinx.cinterop.ptr 23 | import platform.posix.sigemptyset 24 | import platform.posix.sigset_tVar 25 | 26 | @OptIn(ExperimentalForeignApi::class) 27 | internal actual inline fun MemScope.sigsetInitEmpty(action: (CValuesRef<*>) -> Int): Int { 28 | val sigset = alloc() 29 | if (sigemptyset(sigset.ptr) == -1) return -1 30 | return action(sigset.ptr) 31 | } 32 | -------------------------------------------------------------------------------- /library/process/src/androidNativeX64Main/kotlin/io/matthewnelson/kmp/process/internal/spawn/AndroidNativeX64PosixSpawn.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.spawn 17 | 18 | import kotlinx.cinterop.CValuesRef 19 | import kotlinx.cinterop.ExperimentalForeignApi 20 | import kotlinx.cinterop.MemScope 21 | import kotlinx.cinterop.alloc 22 | import kotlinx.cinterop.ptr 23 | import platform.posix.sigemptyset 24 | import platform.posix.sigset_tVar 25 | 26 | @OptIn(ExperimentalForeignApi::class) 27 | internal actual inline fun MemScope.sigsetInitEmpty(action: (CValuesRef<*>) -> Int): Int { 28 | val sigset = alloc() 29 | if (sigemptyset(sigset.ptr) == -1) return -1 30 | return action(sigset.ptr) 31 | } 32 | -------------------------------------------------------------------------------- /library/process/src/androidNativeX86Main/kotlin/io/matthewnelson/kmp/process/internal/spawn/AndroidNativeX86PosixSpawn.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.spawn 17 | 18 | import kotlinx.cinterop.CValuesRef 19 | import kotlinx.cinterop.ExperimentalForeignApi 20 | import kotlinx.cinterop.MemScope 21 | import kotlinx.cinterop.alloc 22 | import kotlinx.cinterop.ptr 23 | import platform.posix.sigemptyset 24 | import platform.posix.sigset_tVar 25 | 26 | @OptIn(ExperimentalForeignApi::class) 27 | internal actual inline fun MemScope.sigsetInitEmpty(action: (CValuesRef<*>) -> Int): Int { 28 | val sigset = alloc() 29 | if (sigemptyset(sigset.ptr) == -1) return -1 30 | return action(sigset.ptr) 31 | } 32 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 2 | org.gradle.caching=true 3 | org.gradle.configuration-cache=false 4 | org.gradle.parallel=true 5 | 6 | android.useAndroidX=true 7 | android.enableJetifier=true 8 | 9 | kotlin.code.style=official 10 | kotlin.mpp.applyDefaultHierarchyTemplate=false 11 | kotlin.mpp.enableCInteropCommonization=true 12 | kotlin.mpp.stability.nowarn=true 13 | kotlin.native.ignoreDisabledTargets=true 14 | 15 | SONATYPE_HOST=CENTRAL_PORTAL 16 | RELEASE_SIGNING_ENABLED=true 17 | 18 | GROUP=io.matthewnelson.kmp-process 19 | 20 | POM_INCEPTION_YEAR=2024 21 | 22 | POM_URL=https://github.com/05nelsonm/kmp-process/ 23 | POM_SCM_URL=https://github.com/05nelsonm/kmp-process/ 24 | POM_SCM_CONNECTION=scm:git:git://github.com/05nelsonm/kmp-process.git 25 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/05nelsonm/kmp-process.git 26 | 27 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 28 | POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt 29 | POM_LICENCE_DIST=repo 30 | 31 | POM_DEVELOPER_ID=05nelsonm 32 | POM_DEVELOPER_NAME=Matthew Nelson 33 | POM_DEVELOPER_URL=https://github.com/05nelsonm/ 34 | 35 | VERSION_NAME=0.5.1-SNAPSHOT 36 | # 0.1.0-alpha01 = 00 01 00 11 37 | # 0.1.0-beta01 = 00 01 00 41 38 | # 0.1.0-rc01 = 00 01 00 71 39 | # 0.1.0 = 00 01 00 99 40 | # 1.1.0 = 01 01 00 99 41 | VERSION_CODE=00050199 42 | -------------------------------------------------------------------------------- /library/process/src/wasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/node/_Modules.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("OPT_IN_USAGE") 17 | 18 | package io.matthewnelson.kmp.process.internal.node 19 | 20 | internal actual fun nodeModuleBuffer(): ModuleBuffer = js(CODE_MODULE_BUFFER) 21 | internal actual fun nodeModuleChildProcess(): ModuleChildProcess = js(CODE_MODULE_CHILD_PROCESS) 22 | internal actual fun nodeModuleEvents(): ModuleEvents = js(CODE_MODULE_EVENTS) 23 | internal actual fun nodeModuleFs(): ModuleFs = js(CODE_MODULE_FS) 24 | internal actual fun nodeModuleOs(): ModuleOs = js(CODE_MODULE_OS) 25 | internal actual fun nodeModuleProcess(): ModuleProcess = js(CODE_MODULE_PROCESS) 26 | internal actual fun nodeModuleStream(): ModuleStream = js(CODE_MODULE_STREAM) 27 | -------------------------------------------------------------------------------- /library/process/src/blockingMain/kotlin/io/matthewnelson/kmp/process/internal/ReadStream.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "UNUSED") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import io.matthewnelson.kmp.file.Closeable 21 | import io.matthewnelson.kmp.file.IOException 22 | 23 | internal expect abstract class ReadStream: Closeable { 24 | 25 | // Returns -1 on EOF 26 | @Throws(IOException::class/*, IndexOutOfBoundsException::class, IllegalArgumentException::class*/) 27 | open fun read(buf: ByteArray, offset: Int, len: Int): Int 28 | 29 | // Returns -1 on EOF 30 | @Throws(IOException::class) 31 | fun read(buf: ByteArray): Int 32 | 33 | @Throws(IOException::class) 34 | override fun close() 35 | } 36 | -------------------------------------------------------------------------------- /library/process/src/commonMain/kotlin/io/matthewnelson/kmp/process/Signal.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process 17 | 18 | import kotlin.jvm.JvmField 19 | 20 | /** 21 | * The signal to send when [Process.destroy] is called. 22 | * */ 23 | public enum class Signal( 24 | @JvmField 25 | public val code: Int, 26 | ) { 27 | 28 | /** 29 | * The default 30 | * 31 | * On Jvm, this is the same as calling `java.lang.Process.destroy` 32 | * */ 33 | SIGTERM(128 + 15), 34 | 35 | /** 36 | * On Jvm, this is the same as calling `java.lang.Process.destroyForcibly`. 37 | * 38 | * Note that on Android API 25 and below, SIGTERM is always utilized 39 | * as `java.lang.Process.destroyForcibly` is unavailable. 40 | * */ 41 | SIGKILL(128 + 9), 42 | } 43 | -------------------------------------------------------------------------------- /library/process/src/commonMain/kotlin/io/matthewnelson/kmp/process/internal/-CommonLock.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import kotlin.contracts.ExperimentalContracts 21 | import kotlin.contracts.InvocationKind 22 | import kotlin.contracts.contract 23 | 24 | internal expect class Lock 25 | 26 | internal expect fun newLock(): Lock 27 | 28 | @OptIn(ExperimentalContracts::class) 29 | @Suppress("WRONG_INVOCATION_KIND", "LEAKED_IN_PLACE_LAMBDA") 30 | internal inline fun Lock.withLock(block: () -> T): T { 31 | contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } 32 | return withLockImpl(block) 33 | } 34 | 35 | internal expect inline fun Lock.withLockImpl(block: () -> T): T 36 | -------------------------------------------------------------------------------- /library/process/src/androidNativeTest/kotlin/io/matthewnelson/kmp/process/internal/stdio/StdioDescriptorAndroidNativeUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.stdio 17 | 18 | import io.matthewnelson.kmp.process.internal.__SYS_pipe2 19 | import io.matthewnelson.kmp.process.internal.testing.SYS_pipe2 20 | import kotlinx.cinterop.ExperimentalForeignApi 21 | import kotlin.test.Test 22 | import kotlin.test.assertEquals 23 | 24 | @OptIn(ExperimentalForeignApi::class) 25 | class StdioDescriptorAndroidNativeUnitTest { 26 | 27 | @Test 28 | fun givenSYSpipe2_whenCheckedAgainstHeaderDefinition_thenMatches() { 29 | assertEquals( 30 | SYS_pipe2, 31 | __SYS_pipe2(), 32 | "expected[${SYS_pipe2}] vs actual[${__SYS_pipe2()}]", 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /library/process/src/jsWasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/node/ModuleEvents.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal.node 19 | 20 | import kotlin.js.JsName 21 | 22 | /** [docs](https://nodejs.org/api/events.html) */ 23 | internal external interface ModuleEvents { 24 | // 25 | } 26 | 27 | /** [docs](https://nodejs.org/api/events.html#class-eventemitter) */ 28 | @JsName("EventEmitter") 29 | internal expect interface JsEventEmitter { 30 | // fun on( 31 | // event: String, 32 | // listener: (JsAny?/dynamic?) -> Unit, 33 | // ): JsEventEmitter 34 | } 35 | 36 | internal expect inline fun T.onError( 37 | noinline block: (Throwable) -> Unit, 38 | ): T 39 | -------------------------------------------------------------------------------- /gh-pages/publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) 2024 Matthew Nelson 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | set -e 16 | 17 | readonly DIR_SCRIPT="$( cd "$( dirname "$0" )" >/dev/null && pwd )" 18 | readonly REPO_NAME="kmp-process" 19 | 20 | trap 'rm -rf "$DIR_SCRIPT/$REPO_NAME"' EXIT 21 | 22 | cd "$DIR_SCRIPT" 23 | git clone -b gh-pages --single-branch https://github.com/05nelsonm/$REPO_NAME.git 24 | rm -rf "$DIR_SCRIPT/$REPO_NAME/"* 25 | echo "$REPO_NAME.matthewnelson.io" > "$DIR_SCRIPT/$REPO_NAME/CNAME" 26 | 27 | cd .. 28 | ./gradlew clean -DKMP_TARGETS_ALL 29 | ./gradlew dokkaGenerate --no-build-cache -DKMP_TARGETS_ALL 30 | cp -aR build/dokka/html/* gh-pages/$REPO_NAME 31 | 32 | cd "$DIR_SCRIPT/$REPO_NAME" 33 | PACKAGE_LIST="$(sed "s|module:|module:library/|g" "package-list")" 34 | echo "$PACKAGE_LIST" > "package-list" 35 | 36 | git add --all 37 | git commit -S --message "Update dokka docs" 38 | git push 39 | -------------------------------------------------------------------------------- /library/process/src/androidNativeArm64Main/kotlin/io/matthewnelson/kmp/process/internal/AndroidNativeArm64Platform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import kotlinx.cinterop.AutofreeScope 21 | import kotlinx.cinterop.ExperimentalForeignApi 22 | import kotlinx.cinterop.alloc 23 | import kotlinx.cinterop.ptr 24 | import platform.posix.SIG_SETMASK 25 | import platform.posix.sigemptyset 26 | import platform.posix.sigprocmask 27 | import platform.posix.sigset_t 28 | 29 | @OptIn(ExperimentalForeignApi::class) 30 | internal actual inline fun ChildProcess.resetSignalMasks(scope: AutofreeScope): Int { 31 | val sigset = scope.alloc() 32 | if (sigemptyset(sigset.ptr) == -1) return -1 33 | if (sigprocmask(SIG_SETMASK, sigset.ptr, null) == -1) return -1 34 | return 0 35 | } 36 | -------------------------------------------------------------------------------- /library/process/src/androidNativeX64Main/kotlin/io/matthewnelson/kmp/process/internal/AndroidNativeX64Platform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import kotlinx.cinterop.AutofreeScope 21 | import kotlinx.cinterop.ExperimentalForeignApi 22 | import kotlinx.cinterop.alloc 23 | import kotlinx.cinterop.ptr 24 | import platform.posix.SIG_SETMASK 25 | import platform.posix.sigemptyset 26 | import platform.posix.sigprocmask 27 | import platform.posix.sigset_tVar 28 | 29 | @OptIn(ExperimentalForeignApi::class) 30 | internal actual inline fun ChildProcess.resetSignalMasks(scope: AutofreeScope): Int { 31 | val sigset = scope.alloc() 32 | if (sigemptyset(sigset.ptr) == -1) return -1 33 | if (sigprocmask(SIG_SETMASK, sigset.ptr, null) == -1) return -1 34 | return 0 35 | } 36 | -------------------------------------------------------------------------------- /library/process/src/commonMain/kotlin/io/matthewnelson/kmp/process/internal/-Instance.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | import kotlin.concurrent.Volatile 19 | 20 | internal class Instance internal constructor(create: () -> T) { 21 | 22 | @Volatile 23 | private var create: (() -> T)? = create 24 | private val instance = SynchronizedSet() 25 | 26 | internal fun getOrCreate(): T = instance.withLock { 27 | if (isEmpty()) { 28 | val create = create ?: throw IllegalStateException() 29 | 30 | create().also { element -> 31 | this@Instance.create = null 32 | add(element) 33 | } 34 | } else { 35 | first() 36 | } 37 | } 38 | 39 | internal fun getOrNull(): T? = instance.withLock { firstOrNull() } 40 | } 41 | -------------------------------------------------------------------------------- /library/process/src/androidNativeArm32Main/kotlin/io/matthewnelson/kmp/process/internal/AndroidNativeArm32Platform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import kotlinx.cinterop.AutofreeScope 21 | import kotlinx.cinterop.ExperimentalForeignApi 22 | import kotlinx.cinterop.alloc 23 | import kotlinx.cinterop.ptr 24 | import platform.posix.SIG_SETMASK 25 | import platform.posix.sigemptyset 26 | import platform.posix.sigprocmask 27 | import platform.posix.sigset_tVar 28 | 29 | @OptIn(ExperimentalForeignApi::class) 30 | internal actual inline fun ChildProcess.resetSignalMasks(scope: AutofreeScope): Int { 31 | val sigset = scope.alloc() 32 | if (sigemptyset(sigset.ptr) == -1) return -1 33 | if (sigprocmask(SIG_SETMASK, sigset.ptr, null) == -1) return -1 34 | return 0 35 | } 36 | -------------------------------------------------------------------------------- /library/process/src/androidNativeX86Main/kotlin/io/matthewnelson/kmp/process/internal/AndroidNativeX86Platform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import kotlinx.cinterop.AutofreeScope 21 | import kotlinx.cinterop.ExperimentalForeignApi 22 | import kotlinx.cinterop.alloc 23 | import kotlinx.cinterop.cValue 24 | import kotlinx.cinterop.ptr 25 | import platform.posix.SIG_SETMASK 26 | import platform.posix.sigemptyset 27 | import platform.posix.sigprocmask 28 | import platform.posix.sigset_tVar 29 | 30 | @OptIn(ExperimentalForeignApi::class) 31 | internal actual inline fun ChildProcess.resetSignalMasks(scope: AutofreeScope): Int { 32 | val sigset = scope.alloc() 33 | if (sigemptyset(sigset.ptr) == -1) return -1 34 | if (sigprocmask(SIG_SETMASK, sigset.ptr, null) == -1) return -1 35 | return 0 36 | } 37 | -------------------------------------------------------------------------------- /library/process/src/jvmTest/kotlin/io/matthewnelson/kmp/process/internal/PIDUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | import kotlin.test.Test 19 | import kotlin.test.assertEquals 20 | import kotlin.test.assertNull 21 | 22 | // Project requires Java11, so all tests should run properly 23 | class PIDUnitTest { 24 | 25 | @Test 26 | fun givenPID_whenAndroid_thenReturnsNull() { 27 | assertNull(PID.androidOrNull()) 28 | } 29 | 30 | @Test 31 | fun givenPID_whenJava9_thenReturnsPIDJava8() { 32 | assertEquals(PID.java8(), PID.java9OrNull()) 33 | } 34 | 35 | @Test 36 | fun givenPID_whenJava10_thenReturnsPIDJava8() { 37 | assertEquals(PID.java8(), PID.java10OrNull()) 38 | } 39 | 40 | @Test 41 | fun givenPID_whenGet_thenReturnsPIDJava8() { 42 | assertEquals(PID.java8(), PID.get()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /library/process/src/commonMain/kotlin/io/matthewnelson/kmp/process/internal/-DoNotReferenceDirectly.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | /** 19 | * Helper for marking internal things which have extension 20 | * functions that should be used, instead of referencing the 21 | * thing itself. 22 | * */ 23 | @RequiresOptIn 24 | @Target( 25 | AnnotationTarget.CLASS, 26 | AnnotationTarget.ANNOTATION_CLASS, 27 | AnnotationTarget.PROPERTY, 28 | AnnotationTarget.FIELD, 29 | AnnotationTarget.LOCAL_VARIABLE, 30 | AnnotationTarget.VALUE_PARAMETER, 31 | AnnotationTarget.CONSTRUCTOR, 32 | AnnotationTarget.FUNCTION, 33 | AnnotationTarget.PROPERTY_GETTER, 34 | AnnotationTarget.PROPERTY_SETTER, 35 | AnnotationTarget.TYPEALIAS, 36 | ) 37 | @Retention(AnnotationRetention.BINARY) 38 | internal annotation class DoNotReferenceDirectly(val useInstead: String) 39 | -------------------------------------------------------------------------------- /library/process/src/jsWasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/node/ModuleFs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.node 17 | 18 | import io.matthewnelson.kmp.process.internal.js.JsError 19 | import kotlin.js.JsName 20 | 21 | /** [docs](https://nodejs.org/api/fs.html) */ 22 | internal external interface ModuleFs { 23 | fun close(fd: Double, callback: (err: JsError?) -> Unit) 24 | fun fstat(fd: Double, callback: (err: JsError?, stats: JsStats?) -> Unit) 25 | fun open(path: String, flags: String, callback: (err: JsError?, fd: Double?) -> Unit) 26 | 27 | fun closeSync(fd: Double) 28 | fun fstatSync(fd: Double): JsStats 29 | fun openSync(path: String, flags: String): Double 30 | } 31 | 32 | /** [docs](https://nodejs.org/api/fs.html#class-fsstats) */ 33 | @JsName("Stats") 34 | internal external interface JsStats { 35 | fun isDirectory(): Boolean 36 | } 37 | -------------------------------------------------------------------------------- /library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/js/_JsArrays.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.js 17 | 18 | import io.matthewnelson.kmp.process.internal.DoNotReferenceDirectly 19 | 20 | @DoNotReferenceDirectly("JsArray.getString(index)") 21 | internal actual fun jsArrayGetString(array: JsArray, index: Int): String = js("array[index]") 22 | @DoNotReferenceDirectly("JsArray.set[index] = value") 23 | internal actual fun jsArraySetString(array: JsArray, index: Int, value: String) { js("array[index] = value") } 24 | @DoNotReferenceDirectly("JsArray.set[index] = value") 25 | internal actual fun jsArraySetDouble(array: JsArray, index: Int, value: Double) { js("array[index] = value") } 26 | @DoNotReferenceDirectly("JsArrayBufferView.set[index] = value") 27 | internal actual fun jsArraySet(array: T, index: Int, value: Byte) { js("array[index] = value") } 28 | -------------------------------------------------------------------------------- /library/process/src/wasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/js/_JsError.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "OPT_IN_USAGE") 17 | 18 | package io.matthewnelson.kmp.process.internal.js 19 | 20 | import io.matthewnelson.kmp.file.jsExternTryCatch 21 | import kotlin.js.JsName 22 | 23 | @JsName("Error") 24 | internal actual external class JsError: JsAny { 25 | internal actual val message: String? 26 | } 27 | 28 | internal actual fun JsError.toThrowable(): Throwable { 29 | try { 30 | jsExternTryCatch { jsThrow(this) } 31 | } catch (t: Throwable) { 32 | return t 33 | } 34 | 35 | // Try Kotlin's implementation for WasmJs 36 | toThrowableOrNull()?.let { return it } 37 | 38 | // Total failure... 39 | return Throwable(message) 40 | } 41 | 42 | @Suppress("UNUSED") 43 | private fun jsThrow(e: JsError) { js("throw e;") } 44 | -------------------------------------------------------------------------------- /library/process/src/darwinMain/kotlin/io/matthewnelson/kmp/process/internal/stdio/DarwinStdioDescriptor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal.stdio 19 | 20 | import kotlinx.cinterop.CPointer 21 | import kotlinx.cinterop.ExperimentalForeignApi 22 | import kotlinx.cinterop.IntVar 23 | 24 | @OptIn(ExperimentalForeignApi::class) 25 | internal actual inline fun CPointer.pipe2( 26 | flags: Int, 27 | ): Int { 28 | // SYS_pipe2 is currently undefined for darwin SDKs 29 | // This can be checked for periodically by looking in 30 | // the respective SDK's usr/include/sys/syscall.h 31 | // e.g. 32 | // 33 | // $ cat "$(xcrun --sdk iphoneos --show-sdk-path)/usr/include/sys/syscall.h" | grep "pipe2" 34 | // 35 | // Until then, -1 (failure) is returned such that flags can 36 | // then be set non-atomically via pipe1 & fcntl 37 | return -1 38 | } 39 | -------------------------------------------------------------------------------- /library/process/src/wasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/js/_JsArrays.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("OPT_IN_USAGE") 17 | 18 | package io.matthewnelson.kmp.process.internal.js 19 | 20 | import io.matthewnelson.kmp.process.internal.DoNotReferenceDirectly 21 | 22 | @DoNotReferenceDirectly("JsArray.getString(index)") 23 | internal actual fun jsArrayGetString(array: JsArray, index: Int): String = js("array[index]") 24 | @DoNotReferenceDirectly("JsArray.set[index] = value") 25 | internal actual fun jsArraySetString(array: JsArray, index: Int, value: String) { js("array[index] = value") } 26 | @DoNotReferenceDirectly("JsArray.set[index] = value") 27 | internal actual fun jsArraySetDouble(array: JsArray, index: Int, value: Double) { js("array[index] = value") } 28 | @DoNotReferenceDirectly("JsArrayBufferView.set[index] = value") 29 | internal actual fun jsArraySet(array: T, index: Int, value: Byte) { js("array[index] = value") } 30 | -------------------------------------------------------------------------------- /library/process/src/commonMain/kotlin/io/matthewnelson/kmp/process/internal/-SynchronizedSet.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | import kotlin.contracts.ExperimentalContracts 19 | import kotlin.contracts.InvocationKind 20 | import kotlin.contracts.contract 21 | 22 | internal class SynchronizedSet internal constructor(initialCapacity: Int = 1) { 23 | @DoNotReferenceDirectly("SynchronizedSet.withLock") 24 | internal val lock = newLock() 25 | @DoNotReferenceDirectly("SynchronizedSet.withLock") 26 | internal val set = LinkedHashSet(initialCapacity.coerceAtLeast(1), 1.0F) 27 | } 28 | 29 | @OptIn(ExperimentalContracts::class) 30 | internal inline fun SynchronizedSet.withLock( 31 | block: LinkedHashSet.() -> T, 32 | ): T { 33 | contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } 34 | @OptIn(DoNotReferenceDirectly::class) 35 | return lock.withLock { block(set) } 36 | } 37 | -------------------------------------------------------------------------------- /library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/node/_ModuleEvents.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal.node 19 | 20 | import io.matthewnelson.kmp.process.internal.DoNotReferenceDirectly 21 | import io.matthewnelson.kmp.process.internal.js.JsError 22 | import kotlin.js.JsName 23 | 24 | /** [docs](https://nodejs.org/api/events.html#class-eventemitter) */ 25 | @JsName("EventEmitter") 26 | internal actual external interface JsEventEmitter { 27 | @DoNotReferenceDirectly("JsEventEmitter.onError") 28 | fun on( 29 | event: String, 30 | listener: Function, 31 | ): JsEventEmitter 32 | } 33 | 34 | internal actual inline fun T.onError( 35 | noinline block: (Throwable) -> Unit, 36 | ): T { 37 | @OptIn(DoNotReferenceDirectly::class) 38 | on("error", block) 39 | return this 40 | } 41 | -------------------------------------------------------------------------------- /library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/JsPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE", "DEPRECATION_ERROR") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import io.matthewnelson.kmp.process.InternalProcessApi 21 | 22 | /** @suppress */ 23 | @InternalProcessApi 24 | @Deprecated("Scheduled for removal. Do not use.", level = DeprecationLevel.ERROR) // ERROR OK, imo, b/c is InternalProcessApi 25 | public inline fun T.onError( 26 | noinline block: (err: dynamic) -> Unit, 27 | ): T { 28 | on("error", block) 29 | return this 30 | } 31 | 32 | /** @suppress */ 33 | @InternalProcessApi 34 | @Deprecated("Scheduled for removal. Do not use.", level = DeprecationLevel.ERROR) // ERROR OK, imo, b/c is InternalProcessApi 35 | public inline fun T.onceError( 36 | noinline block: (err: dynamic) -> Unit, 37 | ): T { 38 | once("error", block) 39 | return this 40 | } 41 | -------------------------------------------------------------------------------- /library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/node/_ModuleStream.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal.node 19 | 20 | import io.matthewnelson.kmp.process.ReadBuffer 21 | import io.matthewnelson.kmp.process.internal.DoNotReferenceDirectly 22 | import kotlin.js.JsName 23 | 24 | /** [docs](https://nodejs.org/api/stream.html#class-streamreadable) */ 25 | @JsName("Readable") 26 | internal actual external interface JsReadable { 27 | fun on( 28 | event: String, 29 | listener: Function, 30 | ): JsReadable 31 | actual fun destroy() 32 | } 33 | 34 | internal actual inline fun JsReadable.onClose( 35 | noinline block: () -> Unit, 36 | ): JsReadable = on("close", block) 37 | 38 | internal actual inline fun JsReadable.onData( 39 | noinline block: (data: ReadBuffer) -> Unit, 40 | ): JsReadable { 41 | @OptIn(DoNotReferenceDirectly::class) 42 | return on("data", onDataListener(block)) 43 | } 44 | -------------------------------------------------------------------------------- /library/process/src/jsWasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/node/ModuleChildProcess.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.node 17 | 18 | import io.matthewnelson.kmp.process.internal.js.JsArray 19 | import io.matthewnelson.kmp.process.internal.js.JsObject 20 | import kotlin.js.JsName 21 | 22 | /** [docs](https://nodejs.org/api/child_process.html) */ 23 | internal external interface ModuleChildProcess { 24 | fun spawn(command: String, args: JsArray, options: JsObject): JsChildProcess 25 | fun spawnSync(command: String, args: JsArray, options: JsObject): JsObject 26 | } 27 | 28 | /** [docs](https://nodejs.org/api/child_process.html#class-childprocess) */ 29 | @JsName("ChildProcess") 30 | internal external interface JsChildProcess: JsEventEmitter { 31 | val exitCode: Int? 32 | fun kill(signal: String): Boolean 33 | val killed: Boolean 34 | val pid: Int? 35 | val signalCode: String? 36 | val stdin: JsWritable? 37 | val stdout: JsReadable? 38 | val stderr: JsReadable? 39 | fun unref() 40 | } 41 | -------------------------------------------------------------------------------- /library/process/src/commonTest/kotlin/io/matthewnelson/kmp/process/internal/InstanceUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | import kotlin.test.Test 19 | import kotlin.test.assertEquals 20 | import kotlin.test.assertNotNull 21 | import kotlin.test.assertNull 22 | 23 | class InstanceUnitTest { 24 | 25 | @Test 26 | fun givenNullableType_whenGetOrCreate_thenReturnsAsExpected() { 27 | val i = Instance { Any() } 28 | 29 | assertNull(i.getOrNull()) 30 | assertNotNull(i.getOrCreate()) 31 | assertNotNull(i.getOrNull()) 32 | } 33 | 34 | @Test 35 | fun givenNullInstance_whenGetOrCreate_thenReturnsNull() { 36 | var invocations = 0 37 | val i = Instance { invocations++; null } 38 | i.getOrNull() 39 | assertEquals(0, invocations) 40 | 41 | i.getOrCreate() 42 | assertEquals(1, invocations) 43 | i.getOrCreate() 44 | assertEquals(1, invocations) 45 | i.getOrNull() 46 | assertEquals(1, invocations) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /library/process/src/jsWasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/JsWasmJsPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import io.matthewnelson.kmp.file.File 21 | import io.matthewnelson.kmp.file.SysDirSep 22 | import io.matthewnelson.kmp.file.toFile 23 | import io.matthewnelson.kmp.process.Process 24 | import io.matthewnelson.kmp.process.internal.node.node_os 25 | 26 | internal actual val STDIO_NULL: File by lazy { 27 | val isWindows = try { 28 | node_os.platform() == "win32" 29 | } catch (_: Throwable) { 30 | SysDirSep == '\\' 31 | } 32 | 33 | (if (isWindows) "NUL" else "/dev/null").toFile() 34 | } 35 | 36 | internal actual val IsDesktop: Boolean get() = try { 37 | node_os.platform() != "android" 38 | } catch (_: Throwable) { 39 | false 40 | } 41 | 42 | internal actual inline fun Process.hasStdoutStarted(): Boolean = (this as NodeJsProcess)._hasStdoutStarted 43 | internal actual inline fun Process.hasStderrStarted(): Boolean = (this as NodeJsProcess)._hasStderrStarted 44 | -------------------------------------------------------------------------------- /library/process/src/commonMain/kotlin/io/matthewnelson/kmp/process/InternalProcessApi.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process 17 | 18 | /** 19 | * Denotes an api as internal and subject to change at any time. 20 | * 21 | * Any usage of a declaration annotated with [InternalProcessApi] 22 | * must be accepted by annotating that usage with the [OptIn] 23 | * annotation, e.g. @OptIn(InternalProcessApi::class), or by using 24 | * the following compiler argument: 25 | * 26 | * -Xopt-in=io.matthewnelson.kmp.process.InternalProcessApi 27 | * */ 28 | @RequiresOptIn 29 | @MustBeDocumented 30 | @Target( 31 | AnnotationTarget.CLASS, 32 | AnnotationTarget.ANNOTATION_CLASS, 33 | AnnotationTarget.PROPERTY, 34 | AnnotationTarget.FIELD, 35 | AnnotationTarget.LOCAL_VARIABLE, 36 | AnnotationTarget.VALUE_PARAMETER, 37 | AnnotationTarget.CONSTRUCTOR, 38 | AnnotationTarget.FUNCTION, 39 | AnnotationTarget.PROPERTY_GETTER, 40 | AnnotationTarget.PROPERTY_SETTER, 41 | AnnotationTarget.TYPEALIAS, 42 | ) 43 | @Retention(AnnotationRetention.BINARY) 44 | public annotation class InternalProcessApi 45 | -------------------------------------------------------------------------------- /test-api/src/commonTest/kotlin/io/matthewnelson/kmp/process/test/api/CommonTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | import io.matthewnelson.kmp.file.File 19 | import io.matthewnelson.kmp.file.SysTempDir 20 | import io.matthewnelson.kmp.file.path 21 | import io.matthewnelson.kmp.file.resolve 22 | import io.matthewnelson.kmp.process.Stdio 23 | import io.matthewnelson.kmp.tor.common.api.ResourceLoader 24 | 25 | internal val IsWindows = Stdio.Null.file.path == "NUL" 26 | 27 | internal expect val IsAppleSimulator: Boolean 28 | 29 | internal expect val IsNodeJs: Boolean 30 | 31 | internal expect val AndroidNativeDeviceAPILevel: Int? 32 | 33 | internal expect val LOADER: ResourceLoader.Tor.Exec 34 | 35 | internal object TorResourceBinder: ResourceLoader.RuntimeBinder { 36 | 37 | internal val RESOURCE_DIR: File by lazy { 38 | 39 | // This is OK for Android Runtime, as only geoip files will be installed 40 | // to the Context.cacheDir/kmp_process. libtor.so is extracted to the 41 | // Context.applicationInfo.nativeLibraryDir automatically, so. 42 | SysTempDir.resolve("kmp_process") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /library/process/src/darwinMain/kotlin/io/matthewnelson/kmp/process/internal/spawn/DarwinPosixSpawn.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "NOTHING_TO_INLINE", "FunctionName") 17 | 18 | package io.matthewnelson.kmp.process.internal.spawn 19 | 20 | import kotlinx.cinterop.CValuesRef 21 | import kotlinx.cinterop.ExperimentalForeignApi 22 | import kotlinx.cinterop.MemScope 23 | import kotlinx.cinterop.alloc 24 | import kotlinx.cinterop.ptr 25 | 26 | @OptIn(ExperimentalForeignApi::class) 27 | internal inline fun MemScope.posix_spawnattr_init(): CValuesRef? { 28 | val attrs = alloc() 29 | if (posix_spawnattr_init(attrs.ptr) != 0) { 30 | return null 31 | } 32 | defer { posix_spawnattr_destroy(attrs.ptr) } 33 | return attrs.ptr 34 | } 35 | 36 | @OptIn(ExperimentalForeignApi::class) 37 | internal inline fun MemScope.posix_spawn_file_actions_init(): CValuesRef? { 38 | val fileActions = alloc() 39 | if (posix_spawn_file_actions_init(fileActions.ptr) != 0) { 40 | return null 41 | } 42 | defer { posix_spawn_file_actions_destroy(fileActions.ptr) } 43 | return fileActions.ptr 44 | } 45 | -------------------------------------------------------------------------------- /library/process/src/commonMain/kotlin/io/matthewnelson/kmp/process/Blocking.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "UNUSED") 17 | 18 | package io.matthewnelson.kmp.process 19 | 20 | import io.matthewnelson.kmp.file.IOException 21 | 22 | /** 23 | * Extended by [OutputFeed.Handler] (which is extended by [Process]) in order to provide 24 | * blocking APIs for Jvm & Native. 25 | * */ 26 | public expect sealed class Blocking protected constructor() { 27 | 28 | /** 29 | * Extended by [OutputFeed.Waiter] in order to provide blocking APIs for Jvm & Native. 30 | * */ 31 | public sealed class Waiter protected constructor(process: Process) { 32 | 33 | /** @suppress */ 34 | protected val process: Process 35 | /** @suppress */ 36 | protected abstract fun isStarted(): Boolean 37 | /** @suppress */ 38 | protected abstract fun isStopped(): Boolean 39 | } 40 | 41 | /** 42 | * Extended by [Process.Builder] in order to provide blocking APIs for Jvm/Native 43 | * */ 44 | public sealed class Builder protected constructor() { 45 | 46 | /** @suppress */ 47 | @Throws(IOException::class) 48 | protected abstract fun createProcessProtected(): Process 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /library/process/src/nativeMain/kotlin/io/matthewnelson/kmp/process/internal/PATHIterator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("PropertyName") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import io.matthewnelson.kmp.file.SysPathSep 21 | import kotlinx.cinterop.ExperimentalForeignApi 22 | import kotlinx.cinterop.toKString 23 | import platform.posix._PATH_DEFPATH 24 | import platform.posix.getenv 25 | 26 | @OptIn(ExperimentalForeignApi::class) 27 | internal class PATHIterator(internal val PATH: String): Iterator { 28 | internal constructor(): this(getenv("PATH")?.toKString()?.ifBlank { null } ?: _PATH_DEFPATH) 29 | 30 | private var i: Int = 0 31 | private var iNext: Int = PATH.indexOf(SysPathSep, startIndex = i) 32 | init { if (iNext == -1) iNext = PATH.length } 33 | 34 | override fun hasNext(): Boolean = iNext != -1 35 | 36 | override fun next(): String { 37 | if (!hasNext()) throw NoSuchElementException() 38 | val n = PATH.substring(i, iNext) 39 | i = iNext + 1 40 | if (i > PATH.length) { 41 | iNext = -1 42 | } else { 43 | iNext = PATH.indexOf(SysPathSep, startIndex = i) 44 | if (iNext == -1) { 45 | iNext = PATH.length 46 | } 47 | } 48 | return n 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /library/process/src/jsWasmJsMain/kotlin/io/matthewnelson/kmp/process/Blocking.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package io.matthewnelson.kmp.process 19 | 20 | import io.matthewnelson.kmp.file.IOException 21 | 22 | /** 23 | * Extended by [OutputFeed.Handler] (which is extended by [Process]) in order to provide 24 | * blocking APIs for Jvm & Native. 25 | * */ 26 | public actual sealed class Blocking protected actual constructor() { 27 | 28 | /** 29 | * Extended by [OutputFeed.Waiter] in order to provide blocking APIs for Jvm & Native. 30 | * */ 31 | public actual sealed class Waiter actual constructor( 32 | /** @suppress */ 33 | protected actual val process: Process, 34 | ) { 35 | 36 | /** @suppress */ 37 | protected actual abstract fun isStarted(): Boolean 38 | /** @suppress */ 39 | protected actual abstract fun isStopped(): Boolean 40 | } 41 | 42 | /** 43 | * Extended by [Process.Builder] in order to provide blocking APIs for Jvm/Native 44 | * */ 45 | public actual sealed class Builder protected actual constructor() { 46 | 47 | /** @suppress */ 48 | @Throws(IOException::class) 49 | protected actual abstract fun createProcessProtected(): Process 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /library/process/src/jvmMain/kotlin/io/matthewnelson/kmp/process/internal/-JvmPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT", "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import io.matthewnelson.kmp.file.* 21 | import io.matthewnelson.kmp.process.Process 22 | import kotlin.time.Duration 23 | 24 | internal actual val STDIO_NULL: File = (System.getProperty("os.name") 25 | ?.ifBlank { null } 26 | ?.contains("windows", ignoreCase = true) 27 | ?: (SysDirSep == '\\')) 28 | .let { isWindows -> if (isWindows) "NUL" else "/dev/null" } 29 | .toFile() 30 | 31 | internal actual val IsDesktop: Boolean get() = ANDROID.SDK_INT == null 32 | 33 | @Throws(InterruptedException::class) 34 | internal actual inline fun Duration.threadSleep() { 35 | Thread.sleep(inWholeMilliseconds) 36 | } 37 | 38 | internal actual inline fun Process.hasStdoutStarted(): Boolean = (this as JvmProcess)._hasStdoutStarted 39 | internal actual inline fun Process.hasStderrStarted(): Boolean = (this as JvmProcess)._hasStderrStarted 40 | 41 | @Suppress("ACTUAL_WITHOUT_EXPECT") 42 | internal actual typealias ReadStream = java.io.InputStream 43 | @Suppress("ACTUAL_WITHOUT_EXPECT") 44 | internal actual typealias WriteStream = java.io.OutputStream 45 | -------------------------------------------------------------------------------- /library/process/src/wasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/node/_ModuleEvents.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:OptIn(DelicateFileApi::class) 17 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "NOTHING_TO_INLINE", "OPT_IN_USAGE") 18 | 19 | package io.matthewnelson.kmp.process.internal.node 20 | 21 | import io.matthewnelson.kmp.file.DelicateFileApi 22 | import io.matthewnelson.kmp.file.jsExternTryCatch 23 | import io.matthewnelson.kmp.process.internal.DoNotReferenceDirectly 24 | import io.matthewnelson.kmp.process.internal.js.JsError 25 | import io.matthewnelson.kmp.process.internal.js.toThrowable 26 | import kotlin.js.JsName 27 | 28 | /** [docs](https://nodejs.org/api/events.html#class-eventemitter) */ 29 | @JsName("EventEmitter") 30 | internal actual external interface JsEventEmitter { 31 | @DoNotReferenceDirectly("JsEventEmitter.onError") 32 | fun on( 33 | event: String, 34 | listener: (T) -> Unit, 35 | ): JsEventEmitter 36 | } 37 | 38 | internal actual inline fun T.onError( 39 | noinline block: (Throwable) -> Unit, 40 | ): T { 41 | val listener: (JsError) -> Unit = { jsError -> 42 | val t = jsError.toThrowable() 43 | block(t) 44 | } 45 | @OptIn(DoNotReferenceDirectly::class) 46 | jsExternTryCatch { on("error", listener) } 47 | return this 48 | } 49 | -------------------------------------------------------------------------------- /library/process/src/jsWasmJsTest/kotlin/io/matthewnelson/kmp/process/internal/js/JsArrayUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.js 17 | 18 | import io.matthewnelson.kmp.process.jsArrayGet 19 | import kotlin.test.Test 20 | import kotlin.test.assertEquals 21 | 22 | class JsArrayUnitTest { 23 | 24 | @Test 25 | fun givenBytes_whenToJsArray_thenBytesAreCopied() { 26 | val b = ByteArray(50) { i -> i.toByte() } 27 | val a = b.toJsArray(factory = ::JsInt8Array) 28 | for (i in b.indices) { 29 | assertEquals(b[i], jsArrayGet(a, i)) 30 | } 31 | } 32 | 33 | @Test 34 | fun givenBytes_whenToJsArrayWithOffset_thenBytesAreCopied() { 35 | val b = ByteArray(250) { i -> i.toByte() } 36 | val offset = 50 37 | val a = b.toJsArray(offset = offset, factory = ::JsInt8Array) 38 | 39 | assertEquals(b.size - offset, a.byteLength) 40 | 41 | for (i in 0 until a.byteLength) { 42 | assertEquals(b[i + offset], jsArrayGet(a, i)) 43 | } 44 | } 45 | 46 | @Test 47 | fun givenJsArray_whenFill_thenArrayIsZeroedOut() { 48 | val a = ByteArray(250) { i -> i.toByte() }.toJsArray(factory = ::JsUint8Array) 49 | a.fill() 50 | for (i in 0 until a.byteLength) { 51 | assertEquals(0, jsArrayGet(a, i)) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/JsEvents.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("ClassName", "UNUSED", "DEPRECATION_ERROR") 17 | @file:JsModule("events") 18 | @file:JsNonModule 19 | 20 | package io.matthewnelson.kmp.process.internal 21 | 22 | import io.matthewnelson.kmp.process.InternalProcessApi 23 | 24 | /** 25 | * [docs](https://nodejs.org/api/events.html#class-eventemitter) 26 | * @suppress 27 | * */ 28 | @InternalProcessApi 29 | @JsName("EventEmitter") 30 | @Deprecated("Scheduled for removal. Do not use.", level = DeprecationLevel.ERROR) // ERROR OK, imo, b/c is InternalProcessApi 31 | public open external class events_EventEmitter { 32 | 33 | public fun getMaxListeners(): Number 34 | 35 | public fun on( 36 | eventName: String, 37 | listener: Function, 38 | ): events_EventEmitter 39 | 40 | public fun once( 41 | eventName: String, 42 | listener: Function, 43 | ): events_EventEmitter 44 | 45 | public fun listeners( 46 | eventName: String, 47 | ): Array> 48 | 49 | public fun removeListener( 50 | eventName: String, 51 | listener: Function, 52 | ): events_EventEmitter 53 | 54 | public fun removeAllListeners(): events_EventEmitter 55 | public fun removeAllListeners( 56 | eventName: String, 57 | ): events_EventEmitter 58 | } 59 | -------------------------------------------------------------------------------- /tools/check-publication/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | import kotlin.io.encoding.Base64 17 | import kotlin.io.encoding.ExperimentalEncodingApi 18 | 19 | plugins { 20 | id("configuration") 21 | } 22 | 23 | repositories { 24 | if (version.toString().endsWith("-SNAPSHOT")) { 25 | maven("https://central.sonatype.com/repository/maven-snapshots/") 26 | } else { 27 | maven("https://central.sonatype.com/api/v1/publisher/deployments/download/") { 28 | val p = rootProject.properties 29 | authentication.create("Authorization", HttpHeaderAuthentication::class.java) 30 | credentials(HttpHeaderCredentials::class.java) { 31 | val username = p["mavenCentralUsername"]?.toString() ?: throw NullPointerException() 32 | val password = p["mavenCentralPassword"]?.toString() ?: throw NullPointerException() 33 | name = "Authorization" 34 | @OptIn(ExperimentalEncodingApi::class) 35 | value = "Bearer " + Base64.Mime.encode("$username:$password".encodeToByteArray()) 36 | } 37 | } 38 | } 39 | } 40 | 41 | kmpConfiguration { 42 | configureShared { 43 | common { 44 | sourceSetMain { 45 | dependencies { 46 | implementation("$group:process:$version") 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /library/process/src/linuxTest/kotlin/io/matthewnelson/kmp/process/internal/spawn/PosixFileActionsUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.spawn 17 | 18 | import io.matthewnelson.kmp.file.resolve 19 | import io.matthewnelson.kmp.file.toFile 20 | import io.matthewnelson.kmp.process.PROJECT_DIR_PATH 21 | import io.matthewnelson.kmp.process.internal.DoNotReferenceDirectly 22 | import io.matthewnelson.kmp.process.internal.check 23 | import kotlinx.cinterop.ExperimentalForeignApi 24 | import kotlin.test.Test 25 | import kotlin.test.assertNotNull 26 | import kotlin.test.fail 27 | 28 | @OptIn(DoNotReferenceDirectly::class, ExperimentalForeignApi::class) 29 | class PosixFileActionsUnitTest { 30 | 31 | @Test 32 | fun givenAddChDirNp_ifAvailable_thenIsSuccessful() { 33 | val d = PROJECT_DIR_PATH.toFile() 34 | .resolve("src") 35 | .resolve("linuxTest") 36 | 37 | val unit = try { 38 | posixSpawnScopeOrNull(requireChangeDir = false) { 39 | file_actions_addchdir(d).check { it == 0 } 40 | Unit 41 | } 42 | } catch (e: UnsupportedOperationException) { 43 | if (PosixSpawnScope.FILE_ACTIONS_ADDCHDIR_NP == null) return // pass 44 | fail("change dir should be available, but function call threw exception", e) 45 | } 46 | 47 | // Only way this would be non-null is if: 48 | // - posix_spawn_file_actions_init failed 49 | // - posix_spawnattr_init failed 50 | assertNotNull(unit) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /library/process/src/commonTest/kotlin/io/matthewnelson/kmp/process/OutputOptionsUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process 17 | 18 | import kotlin.test.* 19 | import kotlin.time.Duration.Companion.milliseconds 20 | 21 | class OutputOptionsUnitTest { 22 | 23 | @Test 24 | fun givenBuilder_whenOptionsLessThanMin_thenOptionsUseMinValues() { 25 | val o = Output.Options.Builder.get().apply { 26 | timeoutMillis = 2 27 | maxBuffer = 5 28 | }.build() 29 | 30 | assertEquals(250.milliseconds, o.timeout) 31 | assertEquals(1024 * 16, o.maxBuffer) 32 | assertFalse(o.hasInput) 33 | assertNull(o.consumeInputBytes()) 34 | assertNull(o.consumeInputUtf8()) 35 | } 36 | 37 | @Test 38 | fun givenOptions_whenInputBytesConsumed_thenDereferencesCallback() { 39 | val o = Output.Options.Builder.get().apply { 40 | inputUtf8 { "Hello" } 41 | input { "Hello".encodeToByteArray() } 42 | }.build() 43 | 44 | assertTrue(o.hasInput) 45 | assertNull(o.consumeInputUtf8()) 46 | assertNotNull(o.consumeInputBytes()) 47 | assertNull(o.consumeInputBytes()) 48 | } 49 | 50 | @Test 51 | fun givenOptions_whenInputUtf8Consumed_thenDereferencesCallback() { 52 | val o = Output.Options.Builder.get().apply { 53 | input { "Hello".encodeToByteArray() } 54 | inputUtf8 { "Hello" } 55 | }.build() 56 | 57 | assertTrue(o.hasInput) 58 | assertNull(o.consumeInputBytes()) 59 | assertNotNull(o.consumeInputUtf8()) 60 | assertNull(o.consumeInputUtf8()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /library/process/src/commonMain/kotlin/io/matthewnelson/kmp/process/internal/PlatformBuilder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import io.matthewnelson.kmp.file.File 21 | import io.matthewnelson.kmp.file.IOException 22 | import io.matthewnelson.kmp.file.async.AsyncFs 23 | import io.matthewnelson.kmp.process.* 24 | import kotlin.coroutines.cancellation.CancellationException 25 | 26 | internal expect class PlatformBuilder private constructor() { 27 | 28 | internal val env: MutableMap 29 | 30 | @Throws(IOException::class) 31 | internal fun output( 32 | command: String, 33 | args: List, 34 | chdir: File?, 35 | env: Map, 36 | stdio: Stdio.Config, 37 | options: Output.Options, 38 | destroy: Signal, 39 | ): Output 40 | 41 | @Throws(CancellationException::class, IOException::class) 42 | internal suspend fun spawnAsync( 43 | fs: AsyncFs, 44 | command: String, 45 | args: List, 46 | chdir: File?, 47 | env: Map, 48 | stdio: Stdio.Config, 49 | destroy: Signal, 50 | handler: ProcessException.Handler, 51 | isOutput: Boolean, 52 | ): Process 53 | 54 | @Throws(IOException::class) 55 | internal fun spawn( 56 | command: String, 57 | args: List, 58 | chdir: File?, 59 | env: Map, 60 | stdio: Stdio.Config, 61 | destroy: Signal, 62 | handler: ProcessException.Handler, 63 | ): Process 64 | 65 | internal companion object { 66 | internal fun get(): PlatformBuilder 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /library/process/src/macosMain/kotlin/io/matthewnelson/kmp/process/internal/MacosPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import kotlinx.cinterop.AutofreeScope 21 | import kotlinx.cinterop.CPointer 22 | import kotlinx.cinterop.ExperimentalForeignApi 23 | import kotlinx.cinterop.alloc 24 | import kotlinx.cinterop.ptr 25 | import platform.posix.SIG_SETMASK 26 | import platform.posix.closedir 27 | import platform.posix.dirent 28 | import platform.posix.errno 29 | import platform.posix.fdopendir 30 | import platform.posix.readdir 31 | import platform.posix.sigemptyset 32 | import platform.posix.sigprocmask 33 | import platform.posix.sigset_tVar 34 | 35 | internal actual val FD_DIR: String get() = "/dev/fd" 36 | 37 | /** 38 | * [action] return [Unit] to break from loop, or `null` to continue. 39 | * 40 | * @return [errno] if `fdopendir` fails, otherwise `null`. 41 | * @suppress 42 | * */ 43 | @OptIn(ExperimentalForeignApi::class) 44 | internal actual inline fun ChildProcess.parseDir(fdDir: Int, action: (CPointer) -> Unit?): Int? { 45 | val dir = fdopendir(fdDir) ?: return errno 46 | 47 | try { 48 | var entry: CPointer? = readdir(dir) 49 | while (entry != null) { 50 | if (action(entry) != null) break 51 | entry = readdir(dir) 52 | } 53 | } finally { 54 | closedir(dir) 55 | } 56 | 57 | return null 58 | } 59 | 60 | @OptIn(ExperimentalForeignApi::class) 61 | internal actual inline fun ChildProcess.resetSignalMasks(scope: AutofreeScope): Int { 62 | val sigset = scope.alloc() 63 | if (sigemptyset(sigset.ptr) == -1) return -1 64 | if (sigprocmask(SIG_SETMASK, sigset.ptr, null) == -1) return -1 65 | return 0 66 | } 67 | -------------------------------------------------------------------------------- /library/process/src/wasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/node/_ModuleStream.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "NOTHING_TO_INLINE", "UNUSED", "OPT_IN_USAGE") 17 | @file:OptIn(DelicateFileApi::class) 18 | 19 | package io.matthewnelson.kmp.process.internal.node 20 | 21 | import io.matthewnelson.kmp.file.DelicateFileApi 22 | import io.matthewnelson.kmp.file.jsExternTryCatch 23 | import io.matthewnelson.kmp.process.ReadBuffer 24 | import io.matthewnelson.kmp.process.internal.DoNotReferenceDirectly 25 | import kotlin.js.JsName 26 | 27 | /** [docs](https://nodejs.org/api/stream.html#class-streamreadable) */ 28 | @JsName("Readable") 29 | internal actual external interface JsReadable { 30 | actual fun destroy() 31 | } 32 | 33 | internal actual inline fun JsReadable.onClose( 34 | noinline block: () -> Unit, 35 | ): JsReadable { 36 | @OptIn(DoNotReferenceDirectly::class) 37 | return jsExternTryCatch { jsReadableOn(this, "close", block) } 38 | } 39 | 40 | internal actual inline fun JsReadable.onData( 41 | noinline block: (data: ReadBuffer) -> Unit, 42 | ): JsReadable { 43 | @OptIn(DoNotReferenceDirectly::class) 44 | val listener = onDataListener(block) 45 | @OptIn(DoNotReferenceDirectly::class) 46 | return jsExternTryCatch { jsReadableOn(this, "data", listener) } 47 | } 48 | 49 | @DoNotReferenceDirectly("JsReadable.onClose") 50 | internal fun jsReadableOn( 51 | readable: JsReadable, 52 | event: String, 53 | listener: () -> Unit, 54 | ): JsReadable = js("readable.on(event, listener)") 55 | 56 | @DoNotReferenceDirectly("JsReadable.onData") 57 | internal fun jsReadableOn( 58 | readable: JsReadable, 59 | event: String, 60 | listener: (T) -> Unit, 61 | ): JsReadable = js("readable.on(event, listener)") 62 | -------------------------------------------------------------------------------- /library/process/src/androidNativeMain/kotlin/io/matthewnelson/kmp/process/internal/AndroidNativePlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import kotlinx.cinterop.CPointer 21 | import kotlinx.cinterop.ExperimentalForeignApi 22 | import kotlinx.cinterop.get 23 | import kotlinx.cinterop.toKString 24 | import platform.posix.closedir 25 | import platform.posix.dirent 26 | import platform.posix.environ 27 | import platform.posix.errno 28 | import platform.posix.fdopendir 29 | import platform.posix.readdir 30 | 31 | @OptIn(ExperimentalForeignApi::class) 32 | internal actual inline fun PlatformBuilder.parentEnvironment(): MutableMap { 33 | val map = LinkedHashMap(10, 1.0F) 34 | val env = environ ?: return map 35 | var i = 0 36 | while (true) { 37 | val arg = env[i++]?.toKString() ?: break 38 | val key = arg.substringBefore('=') 39 | val value = arg.substringAfter('=') 40 | map[key] = value 41 | } 42 | return map 43 | } 44 | 45 | internal actual val FD_DIR: String get() = "/proc/self/fd" 46 | 47 | /** 48 | * [action] return [Unit] to break from loop, or `null` to continue. 49 | * 50 | * @return [errno] if `fdopendir` fails, otherwise `null`. 51 | * @suppress 52 | * */ 53 | @OptIn(ExperimentalForeignApi::class) 54 | internal actual inline fun ChildProcess.parseDir(fdDir: Int, action: (CPointer) -> Unit?): Int? { 55 | val dir = fdopendir(fdDir) ?: return errno 56 | 57 | try { 58 | var entry: CPointer? = readdir(dir) 59 | while (entry != null) { 60 | if (action(entry) != null) break 61 | entry = readdir(dir) 62 | } 63 | } finally { 64 | closedir(dir) 65 | } 66 | 67 | return null 68 | } 69 | -------------------------------------------------------------------------------- /test-api/src/iosTest/kotlin/io/matthewnelson/kmp/process/test/api/IosTestPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | import io.matthewnelson.kmp.file.exists2 19 | import io.matthewnelson.kmp.file.toFile 20 | import io.matthewnelson.kmp.tor.common.api.GeoipFiles 21 | import io.matthewnelson.kmp.tor.common.api.ResourceLoader 22 | 23 | internal actual val AndroidNativeDeviceAPILevel: Int? = null 24 | 25 | internal actual val LOADER: ResourceLoader.Tor.Exec by lazy { 26 | IosTestLoader.getOrCreate() as ResourceLoader.Tor.Exec 27 | } 28 | 29 | // Need to mock executable files for ios simulator tests to use macOS 30 | // compilations of tor. 31 | private class IosTestLoader private constructor(): ResourceLoader.Tor.Exec() { 32 | companion object { 33 | fun getOrCreate(): Tor = getOrCreate( 34 | resourceDir = TorResourceBinder.RESOURCE_DIR, 35 | extract = { 36 | check(IOS_TOR_GEOIP.isNotBlank()) { "geoip file does not exist" } 37 | check(IOS_TOR_GEOIP6.isNotBlank()) { "geoip6 file does not exist" } 38 | 39 | val files = GeoipFiles(IOS_TOR_GEOIP.toFile(), IOS_TOR_GEOIP6.toFile()) 40 | check(files.geoip.exists2()) { "geoip file does not exist" } 41 | check(files.geoip6.exists2()) { "geoip6 file does not exist" } 42 | 43 | files 44 | }, 45 | extractTor = { 46 | check(IOS_TOR_EXECUTABLE.isNotBlank()) { "tor executable file does not exist" } 47 | 48 | val file = IOS_TOR_EXECUTABLE.toFile() 49 | check(file.exists2()) { "tor executable file does not exist" } 50 | 51 | file 52 | }, 53 | configureEnv = { /* no-op */ }, 54 | toString = { "IosTestLoader" }, 55 | ) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("UnstableApiUsage") 17 | 18 | rootProject.name = "kmp-process" 19 | 20 | pluginManagement { 21 | repositories { 22 | mavenCentral() 23 | google() 24 | gradlePluginPortal() 25 | maven("https://central.sonatype.com/repository/maven-snapshots/") 26 | } 27 | } 28 | 29 | @Suppress("PrivatePropertyName") 30 | private val VERSION_NAME: String? by settings 31 | 32 | dependencyResolutionManagement { 33 | repositories { 34 | mavenCentral() 35 | 36 | if (VERSION_NAME?.endsWith("-SNAPSHOT") == true) { 37 | // Only allow snapshot dependencies for non-release versions. 38 | // This would cause a build failure if attempting to make a release 39 | // while depending on a -SNAPSHOT version (such as core). 40 | maven("https://central.sonatype.com/repository/maven-snapshots/") 41 | } 42 | } 43 | 44 | val vCatalogKC = rootDir 45 | .resolve("gradle") 46 | .resolve("libs.versions.toml") 47 | .readLines() 48 | .first { it.startsWith("kotlincrypto-catalog ") } 49 | .substringAfter('"') 50 | .substringBeforeLast('"') 51 | 52 | versionCatalogs { 53 | create("kotlincrypto") { 54 | // https://github.com/KotlinCrypto/version-catalog/blob/master/gradle/kotlincrypto.versions.toml 55 | from("org.kotlincrypto:version-catalog:$vCatalogKC") 56 | } 57 | } 58 | } 59 | 60 | includeBuild("build-logic") 61 | 62 | @Suppress("PrivatePropertyName") 63 | private val CHECK_PUBLICATION: String? by settings 64 | 65 | if (CHECK_PUBLICATION != null) { 66 | include(":tools:check-publication") 67 | } else { 68 | listOf( 69 | "process", 70 | ).forEach { module -> 71 | include(":library:$module") 72 | } 73 | 74 | include("test-api") 75 | } 76 | -------------------------------------------------------------------------------- /library/process/src/unixForkMain/kotlin/io/matthewnelson/kmp/process/UnixForkProcessBuilder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process 17 | 18 | /** 19 | * DEFAULT: `true` 20 | * 21 | * By default, [posix_spawn](https://man7.org/linux/man-pages/man3/posix_spawn.3.html) and 22 | * [posix_spawnp](https://man7.org/linux/man-pages/man3/posix_spawn.3.html) are always preferred 23 | * when spawning processes, given that the necessary API availability is present (e.g. Android 24 | * Native requires a device API of 28+, or 34+ if changing the process' working directory via 25 | * [changeDir]). If API availability is **not** present, then an alternative implementation using 26 | * [fork](https://man7.org/linux/man-pages/man2/fork.2.html) and [execve](https://man7.org/linux/man-pages/man2/execve.2.html) 27 | * is fallen back to. This option allows you to skip over [posix_spawn](https://man7.org/linux/man-pages/man3/posix_spawn.3.html), 28 | * regardless of its API availability, and go directly to the alternative implementation using 29 | * [fork](https://man7.org/linux/man-pages/man2/fork.2.html) and [execve](https://man7.org/linux/man-pages/man2/execve.2.html). 30 | * 31 | * @param [use] If `true`, [posix_spawn](https://man7.org/linux/man-pages/man3/posix_spawn.3.html) 32 | * or [posix_spawnp](https://man7.org/linux/man-pages/man3/posix_spawn.3.html) will be used, if 33 | * available, with a fallback to using [fork](https://man7.org/linux/man-pages/man2/fork.2.html) 34 | * and [execve](https://man7.org/linux/man-pages/man2/execve.2.html). If `false`, then the 35 | * [fork](https://man7.org/linux/man-pages/man2/fork.2.html) and [execve](https://man7.org/linux/man-pages/man2/execve.2.html) 36 | * implementation will **always** be used. 37 | * */ 38 | public fun Process.Builder.usePosixSpawn( 39 | use: Boolean, 40 | ): Process.Builder = apply { 41 | _platform.usePosixSpawn = use 42 | } 43 | -------------------------------------------------------------------------------- /library/process/src/jsWasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/node/ModuleStream.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal.node 19 | 20 | import io.matthewnelson.kmp.process.InternalProcessApi 21 | import io.matthewnelson.kmp.process.ReadBuffer 22 | import io.matthewnelson.kmp.process.internal.DoNotReferenceDirectly 23 | import io.matthewnelson.kmp.process.internal.js.JsUint8Array 24 | import kotlin.js.JsName 25 | 26 | /** [docs](https://nodejs.org/api/stream.html) */ 27 | internal external interface ModuleStream { 28 | // 29 | } 30 | 31 | /** [docs](https://nodejs.org/api/stream.html#class-streamwritable) */ 32 | @JsName("Writable") 33 | internal external interface JsWritable { 34 | val writable: Boolean 35 | fun end() 36 | fun once( 37 | event: String, 38 | listener: () -> Unit, 39 | ): JsWritable 40 | fun write( 41 | chunk: JsUint8Array, 42 | callback: () -> Unit, 43 | ): Boolean 44 | } 45 | 46 | /** [docs](https://nodejs.org/api/stream.html#class-streamreadable) */ 47 | @JsName("Readable") 48 | internal expect interface JsReadable { 49 | // fun on( 50 | // event: String, 51 | // listener: (JsAny?/dynamic) -> Unit, 52 | // ): JsReadable 53 | fun destroy() 54 | } 55 | 56 | internal expect inline fun JsReadable.onClose( 57 | noinline block: () -> Unit, 58 | ): JsReadable 59 | 60 | internal expect inline fun JsReadable.onData( 61 | noinline block: (data: ReadBuffer) -> Unit, 62 | ): JsReadable 63 | 64 | @DoNotReferenceDirectly("JsReadable.onData") 65 | internal inline fun onDataListener( 66 | noinline block: (data: ReadBuffer) -> Unit 67 | ): (JsBuffer) -> Unit = { chunk -> 68 | @OptIn(InternalProcessApi::class) 69 | val buf = ReadBuffer.of(chunk.asBuffer()) 70 | block(buf) 71 | buf.buf.fill() 72 | } 73 | -------------------------------------------------------------------------------- /library/process/src/commonTest/kotlin/io/matthewnelson/kmp/process/internal/OutputFeedBufferUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | import kotlin.test.Test 19 | import kotlin.test.assertEquals 20 | import kotlin.test.assertFalse 21 | import kotlin.test.assertTrue 22 | 23 | class OutputFeedBufferUnitTest { 24 | 25 | @Test 26 | fun givenFeedBuffer_whenMaxSizeSet_thenReturnsExpected() { 27 | val expectedSize = 20 28 | val buf = OutputFeedBuffer.of(expectedSize) 29 | buf.onOutput(" ") 30 | buf.onOutput(" ") 31 | assertFalse(buf.maxSizeExceeded) 32 | 33 | buf.onOutput("123") 34 | assertTrue(buf.maxSizeExceeded) 35 | 36 | buf.onOutput("") 37 | assertTrue(buf.maxSizeExceeded) 38 | 39 | buf.doFinal().let { final -> 40 | val lines = final.lines() 41 | assertEquals(expectedSize, final.length) 42 | assertEquals(3, lines.size) 43 | // last line was truncated 44 | assertEquals("1", lines.last()) 45 | } 46 | 47 | // lines were blanked after calling doFinal once 48 | assertTrue(buf.doFinal().isEmpty()) 49 | 50 | buf.onOutput(" abc") 51 | assertTrue(buf.maxSizeExceeded) 52 | buf.doFinal().let { final -> 53 | assertEquals(expectedSize, final.length) 54 | assertEquals('b', final.last()) 55 | assertEquals(1, final.lines().size) 56 | } 57 | 58 | buf.onOutput("123") 59 | assertEquals("123", buf.doFinal()) 60 | } 61 | 62 | @Test 63 | fun givenBuffer_whenObserveNullLine_thenHasEndedIsTrue() { 64 | val buf = OutputFeedBuffer.of(20) 65 | assertFalse(buf.hasEnded) 66 | buf.onOutput(null) 67 | assertTrue(buf.hasEnded) 68 | buf.doFinal() 69 | assertFalse(buf.hasEnded) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /library/process/src/blockingTest/kotlin/io/matthewnelson/kmp/process/ReadBufferLineLineOutputFeedUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process 17 | 18 | import kotlin.test.* 19 | 20 | @OptIn(InternalProcessApi::class) 21 | class ReadBufferLineLineOutputFeedUnitTest { 22 | 23 | @Test 24 | fun givenData_whenCRorCRLF_thenAreIgnored() { 25 | val buf = ReadBuffer.allocate() 26 | 27 | val b = buildString { 28 | append("Hello\r\n") 29 | append("World\n") 30 | append("Hello\n") 31 | append("World\r\n") 32 | }.encodeToByteArray() 33 | 34 | b.forEachIndexed { index, byte -> buf.buf[index] = byte } 35 | 36 | var i = 0 37 | val expected = listOf("Hello", "World", "Hello", "World", null) 38 | val scanner = ReadBuffer.lineOutputFeed { line -> 39 | assertEquals(expected[i++], line) 40 | } 41 | 42 | scanner.onData(buf, b.size) 43 | scanner.close() 44 | assertEquals(expected.size, i) 45 | } 46 | 47 | @Test 48 | fun givenUnterminated_whenDoFinal_thenAssumesTermination() { 49 | val expected = "Not terminated" 50 | val buf = ReadBuffer.allocate() 51 | val b = expected.encodeToByteArray() 52 | b.forEachIndexed { index, byte -> buf.buf[index] = byte } 53 | 54 | var actual: String? = null 55 | var nullTerminated = false 56 | val scanner = ReadBuffer.lineOutputFeed { line -> 57 | if (line == null) { 58 | nullTerminated = true 59 | } else { 60 | actual = line 61 | } 62 | } 63 | 64 | scanner.onData(buf, b.size) 65 | assertNull(actual) 66 | assertFalse(nullTerminated) 67 | 68 | scanner.close() 69 | assertNotNull(actual) 70 | assertTrue(nullTerminated) 71 | assertEquals(expected, actual) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test-api/src/androidInstrumentedTest/kotlin/io/matthewnelson/kmp/process/test/api/ProcessAndroidNativeTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.test.api 17 | 18 | import android.app.Application 19 | import android.os.Build 20 | import androidx.test.core.app.ApplicationProvider 21 | import io.matthewnelson.kmp.file.toFile 22 | import io.matthewnelson.kmp.process.Process 23 | import io.matthewnelson.kmp.process.Stdio 24 | import kotlin.test.Test 25 | import kotlin.time.Duration 26 | import kotlin.time.Duration.Companion.minutes 27 | 28 | class ProcessAndroidNativeTest { 29 | 30 | private val ctx = ApplicationProvider.getApplicationContext().applicationContext 31 | private val nativeLibraryDir = ctx.applicationInfo.nativeLibraryDir.toFile().absoluteFile 32 | 33 | @Test 34 | fun givenAndroidNative_whenExecuteApiTestBinary_thenIsSuccessful() { 35 | run("libTestApiExec.so", 3.minutes) 36 | } 37 | 38 | @Test 39 | fun givenAndroidNative_whenExecuteProcessTestBinary_thenIsSuccessful() { 40 | run("libTestProcessExec.so", 2.minutes) 41 | } 42 | 43 | private fun run(libName: String, timeout: Duration) { 44 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 45 | println("Skipping...") 46 | return 47 | } 48 | 49 | val out = Process.Builder(executable = nativeLibraryDir.resolve(libName)) 50 | .stdin(Stdio.Null) 51 | .createOutput { 52 | maxBuffer = Int.MAX_VALUE / 2 53 | timeoutMillis = timeout.inWholeMilliseconds.toInt() 54 | } 55 | 56 | if (out.processInfo.exitCode == 0) return 57 | 58 | System.err.println(out.toString()) 59 | System.err.println(out.stdout) 60 | System.err.println(out.stderr) 61 | System.err.println("--- ENVIRONMENT ---") 62 | out.processInfo.environment.forEach { (key, value) -> System.err.println("$key=$value") } 63 | throw AssertionError("Process.exitCode[${out.processInfo.exitCode}] != 0") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /library/process/src/commonMain/kotlin/io/matthewnelson/kmp/process/internal/-CommonCondition.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import io.matthewnelson.kmp.process.Process 21 | import kotlin.contracts.ExperimentalContracts 22 | import kotlin.contracts.InvocationKind 23 | import kotlin.contracts.contract 24 | import kotlin.math.min 25 | import kotlin.time.Duration 26 | import kotlin.time.Duration.Companion.milliseconds 27 | import kotlin.time.Duration.Companion.nanoseconds 28 | import kotlin.time.TimeSource 29 | 30 | internal typealias Condition = () -> T? 31 | 32 | internal inline fun condition(noinline c: Condition): Condition = c 33 | 34 | // @Throws(CancellationException::class, InterruptedException::class) 35 | @OptIn(ExperimentalContracts::class) 36 | internal inline fun Process.commonWaitFor( 37 | timeout: Duration, 38 | sleep: (millis: Duration) -> Unit, 39 | ): Int? { 40 | contract { callsInPlace(sleep, InvocationKind.UNKNOWN) } 41 | return ::exitCodeOrNull.commonWaitFor(timeout, sleep) 42 | } 43 | 44 | // @Throws(CancellationException::class, InterruptedException::class) 45 | @OptIn(ExperimentalContracts::class) 46 | internal inline fun Condition.commonWaitFor( 47 | timeout: Duration, 48 | sleep: (millis: Duration) -> Unit, 49 | ): T? { 50 | contract { callsInPlace(sleep, InvocationKind.UNKNOWN) } 51 | 52 | val startMark = TimeSource.Monotonic.markNow() 53 | var remainingNanos = timeout.inWholeNanoseconds 54 | 55 | do { 56 | val condition = this.invoke() 57 | if (condition != null) return condition 58 | 59 | if (remainingNanos > 0) { 60 | val millis = min( 61 | (remainingNanos.nanoseconds.inWholeMilliseconds + 1).toDouble(), 62 | 100.0 63 | ).toLong().milliseconds 64 | 65 | sleep(millis) 66 | } 67 | 68 | remainingNanos = (timeout - startMark.elapsedNow()).inWholeNanoseconds 69 | } while (remainingNanos > 0) 70 | 71 | return null 72 | } 73 | -------------------------------------------------------------------------------- /library/process/src/blockingTest/kotlin/io/matthewnelson/kmp/process/internal/ProcessBlockingUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | import io.matthewnelson.kmp.file.IOException 19 | import io.matthewnelson.kmp.file.use 20 | import io.matthewnelson.kmp.process.IsAppleSimulator 21 | import io.matthewnelson.kmp.process.Process 22 | import io.matthewnelson.kmp.process.Signal 23 | import kotlin.test.* 24 | import kotlin.time.Duration.Companion.milliseconds 25 | import kotlin.time.Duration.Companion.seconds 26 | import kotlin.time.measureTime 27 | 28 | class ProcessBlockingUnitTest { 29 | 30 | @Test 31 | fun givenWaitFor_whenProcessCompletes_thenReturnsEarly() { 32 | if (IsWindows) { 33 | println("Skipping...") 34 | return 35 | } 36 | 37 | val runTime = Process.Builder(command = if (IsAppleSimulator) "/bin/sleep" else "sleep") 38 | .args("0.5") 39 | .destroySignal(Signal.SIGKILL) 40 | .createProcess().use { p -> 41 | measureTime { 42 | assertNull(p.waitFor(200.milliseconds), "200ms") 43 | assertTrue(p.isAlive) 44 | assertEquals(0, p.waitFor(5.seconds), "exitCode") 45 | assertFalse(p.isAlive) 46 | } 47 | } 48 | 49 | // Should be less than the 2 seconds (waitFor popped out early) 50 | assertTrue(runTime < 2.seconds, "runTime[$runTime] > 2s") 51 | } 52 | 53 | @Test 54 | fun givenWaitFor_whenCompletion_thenReturnsExitCode() { 55 | val exitCode = try { 56 | Process.Builder(command = if (IsAppleSimulator) "/bin/sleep" else "sleep") 57 | .args("0.25") 58 | .destroySignal(Signal.SIGKILL) 59 | .createProcess().use { p -> p.waitFor() } 60 | } catch (e: IOException) { 61 | // Host (Window) did not have sleep available 62 | if (IsWindows) { 63 | println("Skipping...") 64 | return 65 | } 66 | throw e 67 | } 68 | 69 | assertEquals(0, exitCode) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /library/process/src/nativeTest/kotlin/io/matthewnelson/kmp/process/internal/PATHIteratorUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | import io.matthewnelson.kmp.file.SysPathSep 19 | import kotlin.test.Test 20 | import kotlin.test.assertEquals 21 | import kotlin.test.assertFailsWith 22 | import kotlin.test.assertFalse 23 | import kotlin.test.assertTrue 24 | 25 | class PATHIteratorUnitTest { 26 | 27 | @Test 28 | fun givenPath_whenSeparatorBetween_thenIteratesFor2AsExpected() { 29 | val i = PATHIterator(PATH = "/usr/bin${SysPathSep}/something/else") 30 | assertTrue(i.hasNext()) 31 | assertEquals("/usr/bin", i.next()) 32 | assertTrue(i.hasNext()) 33 | assertEquals("/something/else", i.next()) 34 | assertFalse(i.hasNext()) 35 | assertFailsWith { i.next() } 36 | } 37 | 38 | @Test 39 | fun givenPath_whenSeparatorStart_thenIteratesFor2AsExpected() { 40 | val i = PATHIterator(PATH = "${SysPathSep}/usr/bin") 41 | assertTrue(i.hasNext()) 42 | assertEquals("", i.next()) 43 | assertTrue(i.hasNext()) 44 | assertEquals("/usr/bin", i.next()) 45 | assertFalse(i.hasNext()) 46 | } 47 | 48 | @Test 49 | fun givenPath_whenSeparatorEnd_thenIteratesFor2AsExpected() { 50 | val i = PATHIterator(PATH = "/usr/bin${SysPathSep}") 51 | assertTrue(i.hasNext()) 52 | assertEquals("/usr/bin", i.next()) 53 | assertTrue(i.hasNext()) 54 | assertEquals("", i.next()) 55 | assertFalse(i.hasNext()) 56 | } 57 | 58 | @Test 59 | fun givenPath_whenEmpty_thenHasSingleEmptyValue() { 60 | val i = PATHIterator(PATH = "") 61 | assertTrue(i.hasNext()) 62 | assertEquals("", i.next()) 63 | assertFalse(i.hasNext()) 64 | } 65 | 66 | @Test 67 | fun givenPath_whenSEP_thenHas2EmptyValues() { 68 | val i = PATHIterator(PATH = "$SysPathSep") 69 | assertTrue(i.hasNext()) 70 | assertEquals("", i.next()) 71 | assertTrue(i.hasNext()) 72 | assertEquals("", i.next()) 73 | assertFalse(i.hasNext()) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.kotlin-js-store/wasm/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | kmp-tor.resource-exec-tor.all@408.21.0: 6 | version "408.21.0" 7 | resolved "https://registry.yarnpkg.com/kmp-tor.resource-exec-tor.all/-/kmp-tor.resource-exec-tor.all-408.21.0.tgz#28f5bb903453df7bd0ea2fd3745483028a7e9b9f" 8 | integrity sha512-9VOWUOW6uOfnnKm9F4Blv5W4C8mgyZdenDWelRplnuFJ8hwyaI7HIIkKtX3aw7ZKWUWdJHN0ToKdux1saiATMw== 9 | dependencies: 10 | kmp-tor.resource-exec-tor.linux-android "408.21.0" 11 | kmp-tor.resource-exec-tor.linux-libc "408.21.0" 12 | kmp-tor.resource-exec-tor.linux-musl "408.21.0" 13 | kmp-tor.resource-exec-tor.macos "408.21.0" 14 | kmp-tor.resource-exec-tor.mingw "408.21.0" 15 | 16 | kmp-tor.resource-exec-tor.linux-android@408.21.0: 17 | version "408.21.0" 18 | resolved "https://registry.yarnpkg.com/kmp-tor.resource-exec-tor.linux-android/-/kmp-tor.resource-exec-tor.linux-android-408.21.0.tgz#45d63c28b0e28d1f5afbc2a56006591dd593849a" 19 | integrity sha512-w9mLKboy0wRbwctysloOhNT46rYbDfi+wJp91kFpn6PpD0IuOW6u5kxj/z5kZ/GCpfpy9tsKdDyQSFWy58zweQ== 20 | 21 | kmp-tor.resource-exec-tor.linux-libc@408.21.0: 22 | version "408.21.0" 23 | resolved "https://registry.yarnpkg.com/kmp-tor.resource-exec-tor.linux-libc/-/kmp-tor.resource-exec-tor.linux-libc-408.21.0.tgz#cabdbe707af4085e0230fbd901fe8ab831badb25" 24 | integrity sha512-nXehYtF+kDTYNUfegWr+l7UWNeABCvJ3M0KX1Uovi+KuPpZBZGPWzZW5WJcnTIyodClfK8xzdZIN2J2dzXI3kw== 25 | 26 | kmp-tor.resource-exec-tor.linux-musl@408.21.0: 27 | version "408.21.0" 28 | resolved "https://registry.yarnpkg.com/kmp-tor.resource-exec-tor.linux-musl/-/kmp-tor.resource-exec-tor.linux-musl-408.21.0.tgz#a4510b3714d49a07c0c9f4cd9cb049635bdacf14" 29 | integrity sha512-+moYO7epwYJwL0B/M4KShOGWHUsUp39+2MrMTrOBUSSwqtA7SMV1zMbhIqsQ5L5zGOdL1pXOvQ2X91u622FmdA== 30 | 31 | kmp-tor.resource-exec-tor.macos@408.21.0: 32 | version "408.21.0" 33 | resolved "https://registry.yarnpkg.com/kmp-tor.resource-exec-tor.macos/-/kmp-tor.resource-exec-tor.macos-408.21.0.tgz#4065db8840633636a1074ea9c0523f18796cd0c6" 34 | integrity sha512-8OuZ5Z5sV7IMGfKl/2Yp1YG3Rk0eRdJncYCGkCjlSBi8cUUPBODfKJnTk/kynR1PwWf0vXvOVmVbzwjwEUbBlw== 35 | 36 | kmp-tor.resource-exec-tor.mingw@408.21.0: 37 | version "408.21.0" 38 | resolved "https://registry.yarnpkg.com/kmp-tor.resource-exec-tor.mingw/-/kmp-tor.resource-exec-tor.mingw-408.21.0.tgz#e022620a8dec71d4a27161b0faf2212202331d4f" 39 | integrity sha512-u+53wDde0FHsXGVhfT9Y3vjZ9ofMLGs7bc/xQEU9TYPcOqK7UPP1HhCt/W/QsdzcXm0IU7QY/UeqKSYugvUPVg== 40 | 41 | kmp-tor.resource-geoip@408.21.0: 42 | version "408.21.0" 43 | resolved "https://registry.yarnpkg.com/kmp-tor.resource-geoip/-/kmp-tor.resource-geoip-408.21.0.tgz#a5fac05497c0bb0022e25f0520c219b4f6d151aa" 44 | integrity sha512-5nbBbLYlF4N9qM1IeGc5Tc/lurW1RTwADUS5c83Xu6agdZ5BOlIJSoMGVL9vn9zLVC9okvX1h/VCthilu7HyzA== 45 | -------------------------------------------------------------------------------- /library/process/src/blockingTest/kotlin/io/matthewnelson/kmp/process/BlockingUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process 17 | 18 | import kotlin.test.Test 19 | import kotlin.test.assertFailsWith 20 | import kotlin.test.assertTrue 21 | import kotlin.time.Duration.Companion.milliseconds 22 | import kotlin.time.measureTime 23 | 24 | class BlockingUnitTest { 25 | 26 | @Test 27 | fun givenThreadSleep_whenGreaterThan1second_thenSleepsApproximatelyThatDuration() { 28 | val duration = 2_400.milliseconds 29 | 30 | // warm up 31 | repeat(10) { 32 | measureTime { Blocking.threadSleep(1.milliseconds) } 33 | } 34 | 35 | val time = measureTime { Blocking.threadSleep(duration) } 36 | 37 | // Cannot be exact b/c we're talking about threads in a test here, 38 | // but can be close just to catch any critically wrong function logic. 39 | val min = duration - 5.milliseconds 40 | val max = duration + 250.milliseconds // macOS is ridiculous 41 | println("EXPECTED[${duration.inWholeMilliseconds}ms] vs ACTUAL[${time.inWholeMilliseconds}ms]") 42 | assertTrue(time in min..max, "${time.inWholeMilliseconds}ms") 43 | } 44 | 45 | @Test 46 | fun givenThreadSleep_whenLessThan1Second_thenSleepsApproximatelyThatDuration() { 47 | val duration = 50.milliseconds 48 | 49 | // warm up 50 | repeat(10) { 51 | measureTime { Blocking.threadSleep(1.milliseconds) } 52 | } 53 | 54 | val time = measureTime { Blocking.threadSleep(duration) } 55 | 56 | // Cannot be exact b/c we're talking about threads in a test here, 57 | // but can be close just to catch any critically wrong function logic. 58 | val min = duration - 5.milliseconds 59 | val max = duration + 250.milliseconds // macOS is ridiculous 60 | println("EXPECTED[${duration.inWholeMilliseconds}ms] vs ACTUAL[${time.inWholeMilliseconds}ms]") 61 | assertTrue(time in min..max, "${time.inWholeMilliseconds}ms") 62 | } 63 | 64 | @Test 65 | fun givenThreadSleep_whenNegative_thenThrowsException() { 66 | assertFailsWith { Blocking.threadSleep((-1).milliseconds) } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /library/process/src/blockingMain/kotlin/io/matthewnelson/kmp/process/BufferedWriteStream.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package io.matthewnelson.kmp.process 19 | 20 | import io.matthewnelson.kmp.file.Closeable 21 | import io.matthewnelson.kmp.file.IOException 22 | import io.matthewnelson.kmp.file.use 23 | import io.matthewnelson.kmp.process.internal.WriteStream 24 | 25 | /** 26 | * A stream for writing data synchronously, buffering any writes until 8192 bytes 27 | * are accumulated. 28 | * 29 | * @see [AsyncWriteStream] 30 | * */ 31 | public expect sealed class BufferedWriteStream(stream: WriteStream): Closeable { 32 | 33 | /** 34 | * Writes [len] number of bytes from [buf], starting at index [offset]. 35 | * 36 | * @param [buf] The array of data to write. 37 | * @param [offset] The index in [buf] to start at when writing data. 38 | * @param [len] The number of bytes from [buf], starting at index [offset], to write. 39 | * 40 | * @throws [IOException] If an I/O error occurs, or the stream is closed. 41 | * @throws [IndexOutOfBoundsException] If [offset] or [len] are inappropriate. 42 | * */ 43 | @Throws(IOException::class) 44 | public fun write(buf: ByteArray, offset: Int, len: Int) 45 | 46 | /** 47 | * Writes the entire contents of [buf]. 48 | * 49 | * @param [buf] the array of data to write. 50 | * 51 | * @throws [IOException] If an I/O error occurs, or the stream is closed. 52 | * */ 53 | @Throws(IOException::class) 54 | public fun write(buf: ByteArray) 55 | 56 | /** 57 | * Flushes any buffered data. 58 | * 59 | * @throws [IOException] If an I/O error occurs, or the stream is closed. 60 | * */ 61 | @Throws(IOException::class) 62 | public open fun flush() 63 | 64 | /** 65 | * Closes the resource releasing any system resources that may 66 | * be allocated to this [BufferedWriteStream]. Subsequent invocations 67 | * do nothing. 68 | * 69 | * Any buffered data is written to the underlying stream via [flush] 70 | * prior to closing. 71 | * 72 | * @see [use] 73 | * 74 | * @throws [IOException] If an I/O error occurs. 75 | * */ 76 | @Throws(IOException::class) 77 | public override fun close() 78 | } 79 | -------------------------------------------------------------------------------- /library/process/src/jsMain/kotlin/io/matthewnelson/kmp/process/internal/js/_JsObject.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.js 17 | 18 | import io.matthewnelson.kmp.process.internal.DoNotReferenceDirectly 19 | import io.matthewnelson.kmp.process.internal.node.JsBuffer 20 | 21 | @DoNotReferenceDirectly("JsObject.Companion.new()") 22 | internal actual fun jsObjectNew(): JsObject = js("({})") 23 | 24 | @DoNotReferenceDirectly("JsObject.getJsBufferOrNull(key)") 25 | internal actual fun jsObjectGetJsBufferOrNull(obj: JsObject, key: String): JsBuffer? = js("obj[key]") 26 | @DoNotReferenceDirectly("JsObject.getJsErrorOrNull(key)") 27 | internal actual fun jsObjectGetJsErrorOrNull(obj: JsObject, key: String): JsError? = js("obj[key]") 28 | @DoNotReferenceDirectly("JsObject.getInt(key)") 29 | internal actual fun jsObjectGetInt(obj: JsObject, key: String): Int = js("obj[key]") 30 | @DoNotReferenceDirectly("JsObject.getIntOrNull(key)") 31 | internal actual fun jsObjectGetIntOrNull(obj: JsObject, key: String): Int? = js("obj[key]") 32 | @DoNotReferenceDirectly("JsObject.getString(key)") 33 | internal actual fun jsObjectGetString(obj: JsObject, key: String): String = js("obj[key]") 34 | @DoNotReferenceDirectly("JsObject.getStringOrNull(key)") 35 | internal actual fun jsObjectGetStringOrNull(obj: JsObject, key: String): String? = js("obj[key]") 36 | 37 | @DoNotReferenceDirectly("JsObject.set[key] = value") 38 | internal actual fun jsObjectSetInt(obj: JsObject, key: String, value: Int) { js("obj[key] = value") } 39 | @DoNotReferenceDirectly("JsObject.set[key] = value") 40 | internal actual fun jsObjectSetBoolean(obj: JsObject, key: String, value: Boolean) { js("obj[key] = value") } 41 | @DoNotReferenceDirectly("JsObject.set[key] = value") 42 | internal actual fun jsObjectSetString(obj: JsObject, key: String, value: String) { js("obj[key] = value") } 43 | @DoNotReferenceDirectly("JsObject.set[key] = value") 44 | internal actual fun jsObjectSetJsArray(obj: JsObject, key: String, value: JsArray) { js("obj[key] = value") } 45 | @DoNotReferenceDirectly("JsObject.set[key] = value") 46 | internal actual fun jsObjectSetJsObject(obj: JsObject, key: String, value: JsObject) { js("obj[key] = value") } 47 | @DoNotReferenceDirectly("JsObject.set[key] = value") 48 | internal actual fun jsObjectSetJsInt8Array(obj: JsObject, key: String, value: JsInt8Array) { js("obj[key] = value") } 49 | -------------------------------------------------------------------------------- /library/process/src/linuxMain/kotlin/io/matthewnelson/kmp/process/internal/LinuxPlatform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import kotlinx.cinterop.AutofreeScope 21 | import kotlinx.cinterop.CPointer 22 | import kotlinx.cinterop.ExperimentalForeignApi 23 | import kotlinx.cinterop.alloc 24 | import kotlinx.cinterop.get 25 | import kotlinx.cinterop.ptr 26 | import kotlinx.cinterop.toKString 27 | import platform.posix.SIG_SETMASK 28 | import platform.posix.__environ 29 | import platform.posix.closedir 30 | import platform.posix.dirent 31 | import platform.posix.errno 32 | import platform.posix.fdopendir 33 | import platform.posix.readdir 34 | import platform.posix.sigemptyset 35 | import platform.posix.sigprocmask 36 | import platform.posix.sigset_t 37 | 38 | @OptIn(ExperimentalForeignApi::class) 39 | internal actual inline fun PlatformBuilder.parentEnvironment(): MutableMap { 40 | val map = LinkedHashMap(10, 1.0F) 41 | val env = __environ ?: return map 42 | var i = 0 43 | while (true) { 44 | val arg = env[i++]?.toKString() ?: break 45 | val key = arg.substringBefore('=') 46 | val value = arg.substringAfter('=') 47 | map[key] = value 48 | } 49 | return map 50 | } 51 | 52 | internal actual val FD_DIR: String get() = "/proc/self/fd" 53 | 54 | /** 55 | * [action] return [Unit] to break from loop, or `null` to continue. 56 | * 57 | * @return [errno] if `fdopendir` fails, otherwise `null`. 58 | * @suppress 59 | * */ 60 | @OptIn(ExperimentalForeignApi::class) 61 | internal actual inline fun ChildProcess.parseDir(fdDir: Int, action: (CPointer) -> Unit?): Int? { 62 | val dir = fdopendir(fdDir) ?: return errno 63 | 64 | try { 65 | var entry: CPointer? = readdir(dir) 66 | while (entry != null) { 67 | if (action(entry) != null) break 68 | entry = readdir(dir) 69 | } 70 | } finally { 71 | closedir(dir) 72 | } 73 | 74 | return null 75 | } 76 | 77 | @OptIn(ExperimentalForeignApi::class) 78 | internal actual inline fun ChildProcess.resetSignalMasks(scope: AutofreeScope): Int { 79 | val sigset = scope.alloc() 80 | if (sigemptyset(sigset.ptr) == -1) return -1 81 | if (sigprocmask(SIG_SETMASK, sigset.ptr, null) == -1) return -1 82 | return 0 83 | } 84 | -------------------------------------------------------------------------------- /library/process/src/wasmJsMain/kotlin/io/matthewnelson/kmp/process/internal/js/_JsObject.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("OPT_IN_USAGE") 17 | 18 | package io.matthewnelson.kmp.process.internal.js 19 | 20 | import io.matthewnelson.kmp.process.internal.DoNotReferenceDirectly 21 | import io.matthewnelson.kmp.process.internal.node.JsBuffer 22 | 23 | @DoNotReferenceDirectly("JsObject.Companion.new()") 24 | internal actual fun jsObjectNew(): JsObject = js("({})") 25 | 26 | @DoNotReferenceDirectly("JsObject.getJsBufferOrNull(key)") 27 | internal actual fun jsObjectGetJsBufferOrNull(obj: JsObject, key: String): JsBuffer? = js("obj[key]") 28 | @DoNotReferenceDirectly("JsObject.getJsErrorOrNull(key)") 29 | internal actual fun jsObjectGetJsErrorOrNull(obj: JsObject, key: String): JsError? = js("obj[key]") 30 | @DoNotReferenceDirectly("JsObject.getInt(key)") 31 | internal actual fun jsObjectGetInt(obj: JsObject, key: String): Int = js("obj[key]") 32 | @DoNotReferenceDirectly("JsObject.getIntOrNull(key)") 33 | internal actual fun jsObjectGetIntOrNull(obj: JsObject, key: String): Int? = js("obj[key]") 34 | @DoNotReferenceDirectly("JsObject.getString(key)") 35 | internal actual fun jsObjectGetString(obj: JsObject, key: String): String = js("obj[key]") 36 | @DoNotReferenceDirectly("JsObject.getStringOrNull(key)") 37 | internal actual fun jsObjectGetStringOrNull(obj: JsObject, key: String): String? = js("obj[key]") 38 | 39 | @DoNotReferenceDirectly("JsObject.set[key] = value") 40 | internal actual fun jsObjectSetInt(obj: JsObject, key: String, value: Int) { js("obj[key] = value") } 41 | @DoNotReferenceDirectly("JsObject.set[key] = value") 42 | internal actual fun jsObjectSetBoolean(obj: JsObject, key: String, value: Boolean) { js("obj[key] = value") } 43 | @DoNotReferenceDirectly("JsObject.set[key] = value") 44 | internal actual fun jsObjectSetString(obj: JsObject, key: String, value: String) { js("obj[key] = value") } 45 | @DoNotReferenceDirectly("JsObject.set[key] = value") 46 | internal actual fun jsObjectSetJsArray(obj: JsObject, key: String, value: JsArray) { js("obj[key] = value") } 47 | @DoNotReferenceDirectly("JsObject.set[key] = value") 48 | internal actual fun jsObjectSetJsObject(obj: JsObject, key: String, value: JsObject) { js("obj[key] = value") } 49 | @DoNotReferenceDirectly("JsObject.set[key] = value") 50 | internal actual fun jsObjectSetJsInt8Array(obj: JsObject, key: String, value: JsInt8Array) { js("obj[key] = value") } 51 | -------------------------------------------------------------------------------- /library/process/src/nativeMain/kotlin/io/matthewnelson/kmp/process/internal/ReadStream.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import io.matthewnelson.kmp.file.Closeable 21 | import io.matthewnelson.kmp.file.IOException 22 | import io.matthewnelson.kmp.file.errnoToIOException 23 | import io.matthewnelson.kmp.process.internal.stdio.StdioDescriptor 24 | import io.matthewnelson.kmp.process.internal.stdio.withFd 25 | import kotlinx.cinterop.ExperimentalForeignApi 26 | import kotlinx.cinterop.UnsafeNumber 27 | import kotlinx.cinterop.addressOf 28 | import kotlinx.cinterop.convert 29 | import kotlinx.cinterop.usePinned 30 | import platform.posix.errno 31 | 32 | @OptIn(ExperimentalForeignApi::class, UnsafeNumber::class) 33 | internal actual abstract class ReadStream private constructor( 34 | private val descriptor: StdioDescriptor, 35 | ): Closeable, NativeCloseable by descriptor { 36 | 37 | @Throws(IOException::class/*, IndexOutOfBoundsException::class*/) 38 | actual open fun read(buf: ByteArray, offset: Int, len: Int): Int { 39 | if (isClosed) throw IOException("ReadStream is closed") 40 | buf.checkBounds(offset, len) 41 | if (len == 0) return 0 42 | 43 | @Suppress("RemoveRedundantCallsOfConversionMethods") 44 | val ret = buf.usePinned { pinned -> 45 | descriptor.withFd(retries = 10, action = { fd -> 46 | platform.posix.read( 47 | fd, 48 | pinned.addressOf(offset), 49 | len.convert(), 50 | ).toInt() 51 | }) 52 | } 53 | 54 | if (ret < 0) throw errnoToIOException(errno) 55 | if (ret == 0) return -1 56 | return ret 57 | } 58 | 59 | @Throws(IOException::class) 60 | actual fun read(buf: ByteArray): Int = read(buf, 0, buf.size) 61 | 62 | internal companion object { 63 | 64 | internal fun of( 65 | pipe: StdioDescriptor.Pipe, 66 | ): ReadStream = of(pipe.read) 67 | 68 | @Throws(IllegalArgumentException::class) 69 | internal fun of( 70 | descriptor: StdioDescriptor, 71 | ): ReadStream { 72 | require(descriptor.canRead) { "StdioDescriptor must be readable" } 73 | return object : ReadStream(descriptor) {} 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /library/process/src/unixMain/kotlin/io/matthewnelson/kmp/process/internal/stdio/UnixStdioDescriptor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("NOTHING_TO_INLINE") 17 | 18 | package io.matthewnelson.kmp.process.internal.stdio 19 | 20 | import io.matthewnelson.kmp.file.IOException 21 | import io.matthewnelson.kmp.file.errnoToIOException 22 | import io.matthewnelson.kmp.process.internal.check 23 | import kotlinx.cinterop.* 24 | import platform.posix.* 25 | 26 | internal actual inline fun Int.orOCloExec(): Int = this or O_CLOEXEC 27 | 28 | @Throws(IOException::class) 29 | @OptIn(ExperimentalForeignApi::class) 30 | internal actual fun ((isPipe1: Boolean, fd0: Int, fd1: Int) -> StdioDescriptor.Pipe).fdOpen( 31 | readEndNonBlock: Boolean, 32 | ): StdioDescriptor.Pipe { 33 | val fds = IntArray(2) { -1 } 34 | 35 | val isPipe1 = fds.usePinned { pinned -> 36 | val p0: CPointer = pinned.addressOf(0) 37 | if (p0.pipe2(O_CLOEXEC) == 0) return@usePinned false 38 | 39 | pipe(p0).check() 40 | true 41 | } 42 | 43 | if (isPipe1) { 44 | fds.forEach { fd -> 45 | if (fcntl(fd, F_SETFD, FD_CLOEXEC) == 0) return@forEach 46 | 47 | val e = errnoToIOException(errno) 48 | if (close(fds[0]) == -1) { 49 | e.addSuppressed(errnoToIOException(errno)) 50 | } 51 | if (close(fds[1]) == -1) { 52 | e.addSuppressed(errnoToIOException(errno)) 53 | } 54 | throw e 55 | } 56 | } 57 | 58 | // This is for when the pipe is being utilized as a monitor for a newly 59 | // spawned process, such that we can await execve/execvpe success, or 60 | // child process exit due to failure. 61 | if (readEndNonBlock) { 62 | if (fcntl(fds[0], F_SETFL, O_NONBLOCK) != 0) { 63 | val e = errnoToIOException(errno) 64 | if (close(fds[0]) == -1) { 65 | e.addSuppressed(errnoToIOException(errno)) 66 | } 67 | if (close(fds[1]) == -1) { 68 | e.addSuppressed(errnoToIOException(errno)) 69 | } 70 | throw e 71 | } 72 | } 73 | 74 | return this(/* isPipe1 = */ isPipe1, /* fd0 = */ fds[0], /* fd1 = */ fds[1]) 75 | } 76 | 77 | // returns 0 for success, -1 for failure 78 | @OptIn(ExperimentalForeignApi::class) 79 | internal expect inline fun CPointer.pipe2( 80 | flags: Int, 81 | ): Int 82 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /library/process/src/nativeMain/kotlin/io/matthewnelson/kmp/process/internal/WriteStream.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import io.matthewnelson.kmp.file.Closeable 21 | import io.matthewnelson.kmp.file.IOException 22 | import io.matthewnelson.kmp.process.internal.stdio.StdioDescriptor 23 | import io.matthewnelson.kmp.process.internal.stdio.withFd 24 | import kotlinx.cinterop.ExperimentalForeignApi 25 | import kotlinx.cinterop.UnsafeNumber 26 | import kotlinx.cinterop.addressOf 27 | import kotlinx.cinterop.convert 28 | import kotlinx.cinterop.usePinned 29 | 30 | @OptIn(ExperimentalForeignApi::class, UnsafeNumber::class) 31 | internal actual abstract class WriteStream private constructor( 32 | private val descriptor: StdioDescriptor, 33 | ): Closeable, NativeCloseable by descriptor { 34 | 35 | @Throws(IOException::class/*, IndexOutOfBoundsException::class*/) 36 | actual open fun write(buf: ByteArray, offset: Int, len: Int) { 37 | if (isClosed) throw IOException("WriteStream is closed") 38 | buf.checkBounds(offset, len) 39 | if (len == 0) return 40 | 41 | @Suppress("RemoveRedundantCallsOfConversionMethods") 42 | buf.usePinned { pinned -> 43 | var total = 0 44 | while (total < len) { 45 | 46 | val write = descriptor.withFd(retries = 10, action = { fd -> 47 | platform.posix.write( 48 | fd, 49 | pinned.addressOf(offset + total), 50 | (len - total).convert(), 51 | ).toInt() 52 | }).check() 53 | 54 | if (write == 0) throw IOException("write == 0") 55 | 56 | total += write 57 | } 58 | } 59 | } 60 | 61 | @Throws(IOException::class) 62 | actual fun write(buf: ByteArray) { write(buf, 0, buf.size) } 63 | 64 | @Throws(IOException::class) 65 | actual open fun flush() {} 66 | 67 | internal companion object { 68 | 69 | internal fun of( 70 | pipe: StdioDescriptor.Pipe, 71 | ): WriteStream = of(pipe.write) 72 | 73 | @Throws(IllegalArgumentException::class) 74 | internal fun of( 75 | descriptor: StdioDescriptor, 76 | ): WriteStream { 77 | require(descriptor.canWrite) { "StdioDescriptor must be writable" } 78 | return object : WriteStream(descriptor) {} 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /library/process/src/commonMain/kotlin/io/matthewnelson/kmp/process/internal/-OutputFeedBuffer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal 17 | 18 | import io.matthewnelson.encoding.core.util.wipe 19 | import io.matthewnelson.kmp.process.Output 20 | import io.matthewnelson.kmp.process.OutputFeed 21 | import kotlin.concurrent.Volatile 22 | import kotlin.jvm.JvmName 23 | import kotlin.jvm.JvmSynthetic 24 | 25 | internal class OutputFeedBuffer private constructor(maxSize: Int): OutputFeed { 26 | 27 | private val maxSize = maxSize.coerceAtLeast(1) 28 | private val lines = ArrayList(20) 29 | 30 | @Volatile 31 | @get:JvmName("size") 32 | internal var size = 0 33 | private set 34 | 35 | @Volatile 36 | @get:JvmName("hasEnded") 37 | internal var hasEnded: Boolean = false 38 | private set 39 | 40 | @Volatile 41 | @get:JvmName("maxSizeExceeded") 42 | internal var maxSizeExceeded: Boolean = false 43 | private set 44 | 45 | override fun onOutput(line: String?) { 46 | if (line == null) { 47 | hasEnded = true 48 | return 49 | } 50 | if (maxSizeExceeded) return 51 | 52 | // do we need to add a new line character 53 | // between the previous line and this one 54 | val newLineChar = if (lines.isNotEmpty()) 1 else 0 55 | 56 | val remaining = maxSize - size - newLineChar 57 | if ((newLineChar + line.length) > remaining) { 58 | maxSizeExceeded = true 59 | 60 | if (line.isEmpty()) { 61 | lines.add(line) 62 | size += newLineChar 63 | return 64 | } 65 | 66 | val truncate = line.take(remaining) 67 | lines.add(truncate) 68 | size += (newLineChar + truncate.length) 69 | } else { 70 | lines.add(line) 71 | size += (newLineChar + line.length) 72 | } 73 | } 74 | 75 | internal fun doFinal(): String { 76 | val sb = StringBuilder(size) 77 | lines.joinTo(sb, separator = "\n") 78 | lines.clear() 79 | val result = sb.toString() 80 | sb.wipe() 81 | size = 0 82 | hasEnded = false 83 | maxSizeExceeded = false 84 | return result 85 | } 86 | 87 | internal companion object { 88 | 89 | @JvmSynthetic 90 | internal fun of(maxSize: Int) = OutputFeedBuffer(maxSize) 91 | 92 | @JvmSynthetic 93 | internal fun of(options: Output.Options) = OutputFeedBuffer(options.maxBuffer) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /library/process/src/nativeTest/kotlin/io/matthewnelson/kmp/process/internal/stdio/StdioDescriptorUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process.internal.stdio 17 | 18 | import io.matthewnelson.kmp.file.IOException 19 | import io.matthewnelson.kmp.file.SysTempDir 20 | import io.matthewnelson.kmp.file.delete2 21 | import io.matthewnelson.kmp.file.mkdirs2 22 | import io.matthewnelson.kmp.file.resolve 23 | import io.matthewnelson.kmp.file.writeUtf8 24 | import io.matthewnelson.kmp.process.Stdio 25 | import io.matthewnelson.kmp.process.internal.stdio.StdioDescriptor.Companion.fdOpen 26 | import platform.posix.* 27 | import kotlin.random.Random 28 | import kotlin.test.Test 29 | import kotlin.test.assertEquals 30 | import kotlin.test.assertFailsWith 31 | import kotlin.test.fail 32 | 33 | class StdioDescriptorUnitTest { 34 | 35 | @Test 36 | fun givenIO_whenCheckFD_thenIsExpected() { 37 | assertEquals(StdioDescriptor.STDIN.withFd { it }, STDIN_FILENO) 38 | assertEquals(StdioDescriptor.STDOUT.withFd { it }, STDOUT_FILENO) 39 | assertEquals(StdioDescriptor.STDERR.withFd { it }, STDERR_FILENO) 40 | } 41 | 42 | @Test 43 | fun givenClosed_whenWithFd_thenIsError() { 44 | @OptIn(ExperimentalStdlibApi::class) 45 | val d = Random.Default.nextBytes(8).toHexString().let { name -> 46 | SysTempDir.resolve(name) 47 | } 48 | val f = d.resolve("test.txt") 49 | 50 | try { 51 | d.mkdirs2(mode = null) 52 | f.writeUtf8(excl = null, "Hello World!") 53 | 54 | val descriptor = Stdio.File.of(f).fdOpen(isStdin = true) 55 | descriptor.close() 56 | assertFailsWith { descriptor.withFd { it } } 57 | } finally { 58 | f.delete2() 59 | d.delete2() 60 | } 61 | } 62 | 63 | @Test 64 | fun givenStdinFile_whenIsExistingDirectory_thenOpenFails() { 65 | @OptIn(ExperimentalStdlibApi::class) 66 | val d = Random.Default.nextBytes(8).toHexString().let { name -> 67 | SysTempDir.resolve(name) 68 | }.mkdirs2(mode = null, mustCreate = true) 69 | 70 | var fd: StdioDescriptor? = null 71 | try { 72 | fd = Stdio.File.of(d).fdOpen(isStdin = true) 73 | fail("fdOpen should have failed to open O_RDONLY file b/c it's a directory...") 74 | } catch (e: IOException) { 75 | // pass 76 | assertEquals(true, e.message?.contains("EISDIR")) 77 | } finally { 78 | fd?.close() 79 | d.delete2() 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /library/process/src/commonMain/kotlin/io/matthewnelson/kmp/process/internal/-RealLineOutputFeed.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("RedundantVisibilityModifier") 17 | 18 | package io.matthewnelson.kmp.process.internal 19 | 20 | import io.matthewnelson.encoding.core.util.wipe 21 | import io.matthewnelson.encoding.utf8.UTF8 22 | import io.matthewnelson.kmp.process.ReadBuffer 23 | 24 | internal class RealLineOutputFeed internal constructor( 25 | private var dispatch: (line: String?) -> Unit 26 | ): ReadBuffer.LineOutputFeed() { 27 | 28 | private var skipLF: Boolean = false 29 | private val sb = StringBuilder(2 * 1024) 30 | private val feed = UTF8.newEncoderFeed(sb::append) 31 | private var sbMaxLen = 0 32 | 33 | @Throws(IllegalStateException::class) 34 | public override fun onData(buf: ReadBuffer, len: Int) { 35 | if (feed.isClosed()) throw IllegalStateException("LineOutputFeed.isClosed[true]") 36 | if (buf.capacity() <= 0) return 37 | buf.capacity().checkBounds(0, len) 38 | 39 | for (i in 0 until len) { 40 | val b = buf[i] 41 | 42 | if (skipLF) { 43 | skipLF = false 44 | if (b == LF) continue 45 | } 46 | 47 | when(b) { 48 | CR -> skipLF = true 49 | LF -> {} 50 | else -> { 51 | feed.consume(b) 52 | continue 53 | } 54 | } 55 | 56 | feed.flush() 57 | if (sb.length > sbMaxLen) sbMaxLen = sb.length 58 | try { 59 | dispatch(sb.toString()) 60 | } catch (t: Throwable) { 61 | feed.close() 62 | dispatch = NoOp 63 | sb.wipe(len = sbMaxLen) 64 | throw t 65 | } 66 | sb.setLength(0) 67 | } 68 | } 69 | 70 | public override fun close() { 71 | val d = dispatch 72 | dispatch = NoOp 73 | if (feed.isClosed()) return 74 | try { 75 | feed.doFinal() 76 | if (sb.isNotEmpty()) { 77 | if (sb.length > sbMaxLen) sbMaxLen = sb.length 78 | d(sb.toString()) 79 | } 80 | } finally { 81 | sb.wipe(len = sbMaxLen) 82 | sbMaxLen = 0 83 | skipLF = false 84 | d(null) 85 | } 86 | } 87 | 88 | internal companion object { 89 | internal const val CR: Byte = '\r'.code.toByte() 90 | internal const val LF: Byte = '\n'.code.toByte() 91 | 92 | private val NoOp: (line: String?) -> Unit = {} 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /library/process/src/commonTest/kotlin/io/matthewnelson/kmp/process/StdioConfigUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | package io.matthewnelson.kmp.process 17 | 18 | import io.matthewnelson.kmp.file.IOException 19 | import io.matthewnelson.kmp.file.resolve 20 | import io.matthewnelson.kmp.file.toFile 21 | import io.matthewnelson.kmp.process.internal.STDIO_NULL 22 | import kotlin.test.Test 23 | import kotlin.test.assertEquals 24 | import kotlin.test.assertFailsWith 25 | 26 | class StdioConfigUnitTest { 27 | 28 | @Test 29 | fun givenBuilder_whenStdinFileAppendTrue_thenBuildChangesToAppendFalse() { 30 | val b = Stdio.Config.Builder.get() 31 | 32 | val buildGradle = PROJECT_DIR_PATH.toFile().resolve("build.gradle.kts") 33 | b.stdin = Stdio.File.of(buildGradle, append = true) 34 | 35 | val config = b.build(null) 36 | assertEquals(false, (config.stdin as Stdio.File).append) 37 | } 38 | 39 | @Test 40 | fun givenBuilder_whenStdinFileIsSameAsOutputFile_thenThrowsException() { 41 | val b = Stdio.Config.Builder.get() 42 | 43 | b.stdin = Stdio.Null 44 | b.stdout = Stdio.Null 45 | b.stderr = Stdio.Null 46 | 47 | // Null file should not throw exception 48 | b.build(null) 49 | b.stderr = Stdio.Pipe 50 | 51 | val f = Stdio.File.of(PROJECT_DIR_PATH.toFile().resolve("build.gradle.kts")) 52 | b.stdin = f 53 | b.stdout = f 54 | 55 | assertFailsWith { b.build(null) } 56 | b.stdout = Stdio.Pipe 57 | 58 | b.stderr = f 59 | assertFailsWith { b.build(null) } 60 | } 61 | 62 | @Test 63 | fun givenBuilder_whenOutputOptions_thenStdoutStderrArePipe() { 64 | val b = Stdio.Config.Builder.get() 65 | 66 | b.stdout = Stdio.File.of("stdout") 67 | b.stderr = Stdio.File.of("stderr") 68 | 69 | val config = b.build(Output.Options.Builder.get().build()) 70 | assertEquals(Stdio.Pipe, config.stdout) 71 | assertEquals(Stdio.Pipe, config.stderr) 72 | 73 | // stdin was Pipe (the default), but there is no input 74 | // expressed via Output.Options so should be switched to 75 | // null file. 76 | assertEquals(Stdio.Null, config.stdin) 77 | } 78 | 79 | @Test 80 | fun givenBuilder_whenOutputOptionsWithInput_thenStdinIsAsExpected() { 81 | val b = Stdio.Config.Builder.get() 82 | val o = Output.Options.Builder.get().apply { inputUtf8 { "text" } }.build() 83 | 84 | b.stdin = Stdio.Inherit 85 | assertEquals(Stdio.Pipe, b.build(o).stdin) 86 | 87 | } 88 | 89 | @Test 90 | fun givenStdioNull_whenAppendTrue_thenReturnsAppendFalse() { 91 | assertEquals(false, Stdio.File.of(STDIO_NULL, append = true).append) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /library/process/src/androidNativeTest/kotlin/io/matthewnelson/kmp/process/internal/spawn/PosixSpawnAndroidNativeUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Matthew Nelson 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | @file:Suppress("RedundantUnitExpression") 17 | 18 | package io.matthewnelson.kmp.process.internal.spawn 19 | 20 | import io.matthewnelson.kmp.file.resolve 21 | import io.matthewnelson.kmp.file.toFile 22 | import io.matthewnelson.kmp.process.PROJECT_DIR_PATH 23 | import io.matthewnelson.kmp.process.internal.DoNotReferenceDirectly 24 | import io.matthewnelson.kmp.process.internal.IS_POSIX_SPAWN_AVAILABLE 25 | import io.matthewnelson.kmp.process.internal.check 26 | import kotlinx.cinterop.ExperimentalForeignApi 27 | import platform.posix.android_get_device_api_level 28 | import kotlin.test.Test 29 | import kotlin.test.assertFalse 30 | import kotlin.test.assertNotNull 31 | import kotlin.test.assertNull 32 | import kotlin.test.assertTrue 33 | import kotlin.test.fail 34 | 35 | @OptIn(DoNotReferenceDirectly::class, ExperimentalForeignApi::class) 36 | class PosixSpawnAndroidNativeUnitTest { 37 | 38 | @Test 39 | fun givenDeviceApi_when28OrGreater_thenPosixSpawnIsAvailable() { 40 | val result = posixSpawnScopeOrNull(requireChangeDir = false) { Unit } 41 | if (android_get_device_api_level() >= 28) { 42 | assertNotNull(result) 43 | assertTrue(IS_POSIX_SPAWN_AVAILABLE) 44 | } else { 45 | assertNull(result) 46 | assertFalse(IS_POSIX_SPAWN_AVAILABLE) 47 | } 48 | } 49 | 50 | @Test 51 | fun givenDeviceApi_when34OrGreater_thenChdirIsAvailable() { 52 | val result = posixSpawnScopeOrNull(requireChangeDir = true) { Unit } 53 | if (android_get_device_api_level() >= 34) { 54 | assertNotNull(result) 55 | } else { 56 | assertNull(result) 57 | } 58 | } 59 | 60 | @Test 61 | fun givenAddChDirNp_ifAvailable_thenIsSuccessful() { 62 | if (!IS_POSIX_SPAWN_AVAILABLE) { 63 | println("Skipping...") 64 | return 65 | } 66 | 67 | val d = PROJECT_DIR_PATH.toFile() 68 | .resolve("src") 69 | .resolve("androidNativeTest") 70 | 71 | val unit = try { 72 | posixSpawnScopeOrNull(requireChangeDir = false) { 73 | file_actions_addchdir(d).check { it == 0 } 74 | Unit 75 | } 76 | } catch (e: UnsupportedOperationException) { 77 | if (PosixSpawnScope.FILE_ACTIONS_ADDCHDIR_NP == null) return // pass 78 | fail("change dir should be available, but function call threw exception", e) 79 | } 80 | 81 | // Only way this would be non-null is if: 82 | // - posix_spawn_file_actions_init failed 83 | // - posix_spawnattr_init failed 84 | assertNotNull(unit) 85 | } 86 | } 87 | --------------------------------------------------------------------------------