From 71039e68febcef6352dfd9b872b7f93a6d83911d Mon Sep 17 00:00:00 2001 From: bppleman Date: Wed, 20 Dec 2023 22:25:56 +0800 Subject: [PATCH 01/22] Update eko --- eko/src/main.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/eko/src/main.rs b/eko/src/main.rs index 223d6c1..807f932 100644 --- a/eko/src/main.rs +++ b/eko/src/main.rs @@ -10,6 +10,7 @@ fn main() { } let cmd = &args[1]; match cmd { + cmd if cmd == "echo" => echo(), cmd if cmd == "stdout" => stdout(), cmd if cmd == "stderr" => stderr(), cmd if cmd == "interval" => interval( @@ -23,6 +24,13 @@ fn main() { }; } +fn echo() { + let mut line = String::new(); + if stdin().read_line(&mut line).is_ok() { + print!("{}", line); + } +} + fn stdout() { for line in stdin().lines() { match line { From aef6d54df47bb55679940c5721197638aa8d424c Mon Sep 17 00:00:00 2001 From: BppleMan Date: Fri, 22 Dec 2023 12:17:28 +0800 Subject: [PATCH 02/22] Initialize v2.0 scaffold --- build.gradle.kts | 170 +++-- gradle.properties | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- justfile | 18 +- kommand_core/.gitignore | 2 + .../.idea/vcs.xml | 2 +- kommand_core/.idea/workspace.xml | 164 +++++ kommand_core/Cargo.lock | 476 +++++++++++++ kommand_core/Cargo.toml | 17 + kommand_core/build.rs | 17 + kommand_core/justfile | 42 ++ kommand_core/kommand_core.h | 203 ++++++ kommand_core/rust-toolchain.toml | 3 + kommand_core/src/bin/leaks_test.rs | 61 ++ kommand_core/src/bin/normal_test.rs | 24 + kommand_core/src/child.rs | 80 +++ kommand_core/src/ffi_util.rs | 85 +++ kommand_core/src/io.rs | 55 ++ kommand_core/src/io/stderr.rs | 16 + kommand_core/src/io/stdin.rs | 16 + kommand_core/src/io/stdout.rs | 16 + kommand_core/src/kommand.rs | 208 ++++++ kommand_core/src/lib.rs | 11 + kommand_core/src/output.rs | 42 ++ kommand_core/src/result.rs | 126 ++++ kommand_core/src/stdio.rs | 6 + kotlin-js-store/yarn.lock | 555 ---------------- settings.gradle.kts | 8 +- src/commonMain/kotlin/com/kgit2/Child.kt | 5 + src/commonMain/kotlin/com/kgit2/Command.kt | 45 ++ src/commonMain/kotlin/com/kgit2/Exception.kt | 15 + src/commonMain/kotlin/com/kgit2/Output.kt | 9 + src/commonMain/kotlin/com/kgit2/io/Reader.kt | 37 -- src/commonMain/kotlin/com/kgit2/io/Writer.kt | 21 - .../kotlin/com/kgit2/process/Child.kt | 138 ---- .../kotlin/com/kgit2/process/Command.kt | 103 --- .../kotlin/com/kgit2/CommandTest.kt | 10 + src/commonTest/kotlin/process/CommandTest.kt | 148 ----- src/jsMain/kotlin/Main.kt | 13 - .../kotlin/com/kgit2/io/PlatformReader.kt | 30 - .../kotlin/com/kgit2/io/PlatformWriter.kt | 19 - src/jsMain/kotlin/com/kgit2/process/Child.kt | 71 -- src/jsMain/resources/main.js | 17 - src/jsTest/kotlin/process/subCommand.kt | 3 - src/jvmMain/kotlin/com/kgit2/Child.jvm.kt | 7 + src/jvmMain/kotlin/com/kgit2/Command.jvm.kt | 97 +++ .../kotlin/com/kgit2/io/PlatformReader.kt | 44 -- .../kotlin/com/kgit2/io/PlatformWriter.kt | 19 - src/jvmMain/kotlin/com/kgit2/process/Child.kt | 188 ------ src/jvmTest/kotlin/process/CommandTest.jvm.kt | 64 -- .../kotlin/com/kgit2/process/Child.kt | 337 ---------- .../kotlin/process/CommandTest.mingwX64.kt | 42 -- .../resources/sub_command/.idea/.gitignore | 8 - src/nativeInterop/cinterop/linuxarm64.def | 4 + src/nativeInterop/cinterop/linuxx64.def | 4 + src/nativeInterop/cinterop/macos.def | 4 + src/nativeInterop/cinterop/mingw64.def | 5 + .../kotlin/com/kgit2/Child.native.kt | 27 + .../kotlin/com/kgit2/Command.native.kt | 110 +++ src/nativeMain/kotlin/com/kgit2/Exception.kt | 10 + src/nativeMain/kotlin/com/kgit2/FFiUtil.kt | 12 + src/nativeMain/kotlin/com/kgit2/Output.kt | 24 + .../kotlin/com/kgit2/io/PlatformReader.kt | 49 -- .../kotlin/com/kgit2/io/PlatformWriter.kt | 28 - .../kotlin/process/CommandTest.posix.kt | 32 - .../kotlin/com/kgit2/process/Child.kt | 230 ------- .../kotlin/com/kgit2/process/Posix.kt | 189 ------ .../kotlin/process/ProcessTest.kt | 46 -- src/unixLikeTest/kotlin/server/FileService.kt | 19 - sub_command/.gitignore | 629 ------------------ sub_command/build.gradle.kts | 12 - sub_command/src/main/kotlin/Main.kt | 37 -- 72 files changed, 2199 insertions(+), 3191 deletions(-) create mode 100644 kommand_core/.gitignore rename {src/mingwX64Test/resources/sub_command => kommand_core}/.idea/vcs.xml (61%) create mode 100644 kommand_core/.idea/workspace.xml create mode 100644 kommand_core/Cargo.lock create mode 100644 kommand_core/Cargo.toml create mode 100644 kommand_core/build.rs create mode 100755 kommand_core/justfile create mode 100644 kommand_core/kommand_core.h create mode 100644 kommand_core/rust-toolchain.toml create mode 100644 kommand_core/src/bin/leaks_test.rs create mode 100644 kommand_core/src/bin/normal_test.rs create mode 100644 kommand_core/src/child.rs create mode 100644 kommand_core/src/ffi_util.rs create mode 100644 kommand_core/src/io.rs create mode 100644 kommand_core/src/io/stderr.rs create mode 100644 kommand_core/src/io/stdin.rs create mode 100644 kommand_core/src/io/stdout.rs create mode 100644 kommand_core/src/kommand.rs create mode 100644 kommand_core/src/lib.rs create mode 100644 kommand_core/src/output.rs create mode 100644 kommand_core/src/result.rs create mode 100644 kommand_core/src/stdio.rs delete mode 100644 kotlin-js-store/yarn.lock create mode 100644 src/commonMain/kotlin/com/kgit2/Child.kt create mode 100644 src/commonMain/kotlin/com/kgit2/Command.kt create mode 100644 src/commonMain/kotlin/com/kgit2/Exception.kt create mode 100644 src/commonMain/kotlin/com/kgit2/Output.kt delete mode 100644 src/commonMain/kotlin/com/kgit2/io/Reader.kt delete mode 100644 src/commonMain/kotlin/com/kgit2/io/Writer.kt delete mode 100644 src/commonMain/kotlin/com/kgit2/process/Child.kt delete mode 100644 src/commonMain/kotlin/com/kgit2/process/Command.kt create mode 100644 src/commonTest/kotlin/com/kgit2/CommandTest.kt delete mode 100644 src/commonTest/kotlin/process/CommandTest.kt delete mode 100644 src/jsMain/kotlin/Main.kt delete mode 100644 src/jsMain/kotlin/com/kgit2/io/PlatformReader.kt delete mode 100644 src/jsMain/kotlin/com/kgit2/io/PlatformWriter.kt delete mode 100644 src/jsMain/kotlin/com/kgit2/process/Child.kt delete mode 100644 src/jsMain/resources/main.js delete mode 100644 src/jsTest/kotlin/process/subCommand.kt create mode 100644 src/jvmMain/kotlin/com/kgit2/Child.jvm.kt create mode 100644 src/jvmMain/kotlin/com/kgit2/Command.jvm.kt delete mode 100644 src/jvmMain/kotlin/com/kgit2/io/PlatformReader.kt delete mode 100644 src/jvmMain/kotlin/com/kgit2/io/PlatformWriter.kt delete mode 100644 src/jvmMain/kotlin/com/kgit2/process/Child.kt delete mode 100644 src/jvmTest/kotlin/process/CommandTest.jvm.kt delete mode 100644 src/mingwX64Main/kotlin/com/kgit2/process/Child.kt delete mode 100644 src/mingwX64Test/kotlin/process/CommandTest.mingwX64.kt delete mode 100644 src/mingwX64Test/resources/sub_command/.idea/.gitignore create mode 100644 src/nativeInterop/cinterop/linuxarm64.def create mode 100644 src/nativeInterop/cinterop/linuxx64.def create mode 100644 src/nativeInterop/cinterop/macos.def create mode 100644 src/nativeInterop/cinterop/mingw64.def create mode 100644 src/nativeMain/kotlin/com/kgit2/Child.native.kt create mode 100644 src/nativeMain/kotlin/com/kgit2/Command.native.kt create mode 100644 src/nativeMain/kotlin/com/kgit2/Exception.kt create mode 100644 src/nativeMain/kotlin/com/kgit2/FFiUtil.kt create mode 100644 src/nativeMain/kotlin/com/kgit2/Output.kt delete mode 100644 src/posixMain/kotlin/com/kgit2/io/PlatformReader.kt delete mode 100644 src/posixMain/kotlin/com/kgit2/io/PlatformWriter.kt delete mode 100644 src/posixTest/kotlin/process/CommandTest.posix.kt delete mode 100644 src/unixLikeMain/kotlin/com/kgit2/process/Child.kt delete mode 100644 src/unixLikeMain/kotlin/com/kgit2/process/Posix.kt delete mode 100644 src/unixLikeTest/kotlin/process/ProcessTest.kt delete mode 100644 src/unixLikeTest/kotlin/server/FileService.kt delete mode 100644 sub_command/.gitignore delete mode 100644 sub_command/build.gradle.kts delete mode 100644 sub_command/src/main/kotlin/Main.kt diff --git a/build.gradle.kts b/build.gradle.kts index 3d02587..b71f318 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform import org.jetbrains.dokka.gradle.DokkaTask plugins { @@ -12,6 +13,14 @@ plugins { group = "com.kgit2" version = "1.2.0" +val mainHost = Platform.MACOS_X64 +// for debug +// val mainHost = Platform.MACOS_ARM64 +// val mainHost = Platform.LINUX_X64 +// val mainHost = Platform.LINUX_ARM64 +// val mainHost = Platform.MINGW_X64 +val targetPlatform = Platform.valueOf(project.findProperty("targetPlatform")?.toString() ?: "MACOS_X64") + val ktorIO = "2.3.4" repositories { @@ -30,20 +39,37 @@ kotlin { } } - val nativeTargets = listOf( - macosArm64(), - macosX64(), - linuxX64(), - linuxArm64(), - mingwX64(), - ) + val nativeTarget = when (targetPlatform) { + Platform.MACOS_X64 -> macosX64("native") + Platform.MACOS_ARM64 -> macosArm64("native") + Platform.LINUX_X64 -> linuxX64("native") + Platform.LINUX_ARM64 -> linuxArm64("native") + Platform.MINGW_X64 -> mingwX64("native") + } + + nativeTarget.apply { + compilations.getByName("main") { + cinterops { + val kommandCore by creating { + if (targetPlatform.toString().contains("macos")) { + defFile(project.file("src/nativeInterop/cinterop/macos.def")) + } else { + defFile(project.file("src/nativeInterop/cinterop/${targetPlatform}.def")) + } + packageName("kommand_core") + } + } + } + } sourceSets { // add opt-in all { languageSettings.optIn("kotlinx.cinterop.UnsafeNumber") languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi") - // languageSettings.optIn("kotlin.ExperimentalStdlibApi") + languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi") + languageSettings.optIn("kotlin.native.runtime.NativeRuntimeApi") + languageSettings.optIn("kotlin.ExperimentalStdlibApi") } val commonMain by getting { @@ -60,52 +86,57 @@ kotlin { val jvmMain by getting val jvmTest by getting - val posixMain by creating { + val targetSourceSetName = when (targetPlatform) { + Platform.MACOS_X64 -> "macosX64" + Platform.MACOS_ARM64 -> "macosArm64" + Platform.LINUX_X64 -> "linuxX64" + Platform.LINUX_ARM64 -> "linuxArm64" + Platform.MINGW_X64 -> "mingwX64" + } + + val targetMain = create("${targetSourceSetName}Main") { dependsOn(commonMain) } - val posixTest by creating { + val targetTest = create("${targetSourceSetName}Test") { dependsOn(commonTest) - dependencies { - implementation("io.ktor:ktor-server-core:2.3.4") - implementation("io.ktor:ktor-server-cio:2.3.4") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") - } } + // val macosX64Main by creating { + // dependsOn(commonMain) + // } + // val macosX64Test by creating { + // dependsOn(commonTest) + // } + // val macosArm64Main by creating { + // dependsOn(commonMain) + // } + // val macosArm64Test by creating { + // dependsOn(commonTest) + // } + // val linuxX64Main by creating { + // dependsOn(commonMain) + // } + // val linuxX64Test by creating { + // dependsOn(commonTest) + // } + // val linuxArm64Main by creating { + // dependsOn(commonMain) + // } + // val linuxArm64Test by creating { + // dependsOn(commonTest) + // } + // val mingwX64Main by creating { + // dependsOn(commonMain) + // } + // val mingwX64Test by creating { + // dependsOn(commonTest) + // } - val unixLikeMain by creating { - dependsOn(posixMain) - } - val unixLikeTest by creating { - dependsOn(posixTest) - } - val macosArm64Main by getting { - dependsOn(unixLikeMain) - } - val macosArm64Test by getting { - dependsOn(unixLikeTest) - } - val macosX64Main by getting { - dependsOn(unixLikeMain) + val nativeMain by getting { + dependsOn(targetMain) } - val macosX64Test by getting { - dependsOn(unixLikeTest) + val nativeTest by getting { + dependsOn(targetTest) } - val linuxX64Main by getting { - dependsOn(unixLikeMain) - } - val linuxX64Test by getting { - dependsOn(unixLikeTest) - } - val linuxArm64Main by getting { - dependsOn(unixLikeMain) - } - val linuxArm64Test by getting { - dependsOn(unixLikeTest) - } - val mingwX64Main by getting { - dependsOn(posixMain) - } - val mingwX64Test by getting } } @@ -128,6 +159,13 @@ tasks.forEach { } } +tasks { + val wrapper by getting(Wrapper::class) { + distributionType = Wrapper.DistributionType.ALL + gradleVersion = "8.5" + } +} + val ossrhUrl: String = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" val releasesRepoUrl = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") val snapshotsRepoUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") @@ -217,3 +255,41 @@ if (ossrhUsername != null && ossrhPassword != null) { sign(publishing.publications) } } + +enum class Platform( + val archName: String +) { + MACOS_X64("x86_64-apple-darwin"), + MACOS_ARM64("aarch64-apple-darwin"), + LINUX_X64("x86_64-unknown-linux-gnu"), + LINUX_ARM64("aarch64-unknown-linux-gnu"), + MINGW_X64("x86_64-pc-windows-gnu"), + ; + + override fun toString(): String { + return when (this) { + MACOS_X64 -> "macosx64" + MACOS_ARM64 -> "macosarm64" + LINUX_X64 -> "linuxx64" + LINUX_ARM64 -> "linuxarm64" + MINGW_X64 -> "mingw64" + } + } +} + +val currentPlatform: Platform = when { + DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.MACOS_X64 + DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX && DefaultNativePlatform.getCurrentArchitecture().isArm64 -> Platform.MACOS_ARM64 + DefaultNativePlatform.getCurrentOperatingSystem().isLinux && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.LINUX_X64 + DefaultNativePlatform.getCurrentOperatingSystem().isLinux && DefaultNativePlatform.getCurrentArchitecture().isArm64 -> Platform.LINUX_ARM64 + DefaultNativePlatform.getCurrentOperatingSystem().isWindows && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.MINGW_X64 + else -> throw GradleException("Host OS is not supported in Kotlin/Native.") +} + +val platforms: List = listOf( + Platform.MACOS_X64, + Platform.MACOS_ARM64, + Platform.LINUX_X64, + Platform.LINUX_ARM64, + Platform.MINGW_X64, +) diff --git a/gradle.properties b/gradle.properties index 1da62bf..4b55a42 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,4 @@ kotlin.code.style=official -kotlin.mpp.enableGranularSourceSetsMetadata=true -kotlin.native.enableDependencyPropagation=false -kotlin.js.generate.executable.default=false org.gradle.jvmargs=-Xmx4096m +kotlin.mpp.applyDefaultHierarchyTemplate=false diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fc..d0d403e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/justfile b/justfile index 23a510a..320c16f 100755 --- a/justfile +++ b/justfile @@ -1,21 +1,27 @@ #!/usr/bin/env just --justfile macosX64: - ./gradlew macosX64TestBinaries + ./gradlew linkNative -PtargetPlatform=MACOS_X64 macosArm64: - ./gradlew macosArm64TestBinaries + ./gradlew linkNative -PtargetPlatform=MACOS_ARM64 + +macos: macosX64 macosArm64 linuxX64: - ./gradlew linuxX64TestBinaries + ./gradlew linkNative -PtargetPlatform=LINUX_X64 linuxArm64: - ./gradlew linuxArm64TestBinaries + ./gradlew linkNative -PtargetPlatform=LINUX_ARM64 + +linux: linuxX64 linuxArm64 windowsX64: - ./gradlew mingwX64TestBinaries + ./gradlew linkNative -PtargetPlatform=MINGW_X64 + +windows: windowsX64 -all: macosX64 macosArm64 linuxX64 linuxArm64 windowsX64 +all: macos linux windows clean: ./gradlew clean diff --git a/kommand_core/.gitignore b/kommand_core/.gitignore new file mode 100644 index 0000000..54ba423 --- /dev/null +++ b/kommand_core/.gitignore @@ -0,0 +1,2 @@ +/target +!src/bin diff --git a/src/mingwX64Test/resources/sub_command/.idea/vcs.xml b/kommand_core/.idea/vcs.xml similarity index 61% rename from src/mingwX64Test/resources/sub_command/.idea/vcs.xml rename to kommand_core/.idea/vcs.xml index 4fce1d8..6c0b863 100644 --- a/src/mingwX64Test/resources/sub_command/.idea/vcs.xml +++ b/kommand_core/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/kommand_core/.idea/workspace.xml b/kommand_core/.idea/workspace.xml new file mode 100644 index 0000000..de80daf --- /dev/null +++ b/kommand_core/.idea/workspace.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + "associatedIndex": 4 +} + + + + { + "keyToString": { + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.cidr.known.project.marker": "true", + "WebServerToolWindowFactoryState": "false", + "cf.first.check.clang-format": "false", + "cidr.known.project.marker": "true", + "dart.analysis.tool.window.visible": "false", + "git-widget-placeholder": "main", + "last_opened_file_path": "/Users/bppleman/kgit2/kommand2/kommand_core", + "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", + "settings.editor.selected.configurable": "terminal" + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1703038493898 + + + + \ No newline at end of file diff --git a/kommand_core/Cargo.lock b/kommand_core/Cargo.lock new file mode 100644 index 0000000..f9537a0 --- /dev/null +++ b/kommand_core/Cargo.lock @@ -0,0 +1,476 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "cbindgen" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faeaa693e5a727975a79211b8f35c0cb09b031fdb6eaa4a788bc6713d01488ca" +dependencies = [ + "clap", + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "tempfile", + "toml", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex", + "indexmap", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "kommand_core" +version = "0.1.0" +dependencies = [ + "cbindgen", +] + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.42", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "termcolor" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/kommand_core/Cargo.toml b/kommand_core/Cargo.toml new file mode 100644 index 0000000..63f3c44 --- /dev/null +++ b/kommand_core/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "kommand_core" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["rlib", "staticlib"] + +[dependencies] + +[build-dependencies] +cbindgen = "0.25.0" + +[package.metadata.cargo-xbuild] +memcpy = true +sysroot_path = "/Users/bppleman/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-pc-windows-gnu:/Users/bppleman/.konan/dependencies/msys2-mingw-w64-x86_64-2/x86_64-w64-mingw32" +panic_immediate_abort = false diff --git a/kommand_core/build.rs b/kommand_core/build.rs new file mode 100644 index 0000000..8d9f93f --- /dev/null +++ b/kommand_core/build.rs @@ -0,0 +1,17 @@ +fn main() { + use std::env; + println!("PROFILE {:?}", env::var("PROFILE")); + println!( + "CARGO_CFG_TARGET_ARCH {:?}", + env::var("CARGO_CFG_TARGET_ARCH") + ); + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_no_includes() + .with_language(cbindgen::Language::C) + .generate() + .expect("Unable to generate bindings") + .write_to_file("kommand_core.h"); +} diff --git a/kommand_core/justfile b/kommand_core/justfile new file mode 100755 index 0000000..ee30509 --- /dev/null +++ b/kommand_core/justfile @@ -0,0 +1,42 @@ +#!/usr/bin/env just --justfile + +leaks: + cargo build + leaks -atExit -- target/debug/leaks_test + +bench: + cargo build + time target/debug/leaks_test + +macosx64: + cargo build --release --target x86_64-apple-darwin + +macosarm64: + cargo build --release --target aarch64-apple-darwin + +macos: macosx64 macosarm64 + [ -d target/universal-apple-darwin/release ] || mkdir -p target/universal-apple-darwin/release + lipo -create -output target/universal-apple-darwin/release/libkommand_core.a \ + target/x86_64-apple-darwin/release/libkommand_core.a \ + target/aarch64-apple-darwin/release/libkommand_core.a + +linuxx64: + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-unknown-linux-gnu-gcc \ + cargo build --release --target x86_64-unknown-linux-gnu + +linuxarm64: + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-unknown-linux-gnu-gcc \ + cargo build --release --target aarch64-unknown-linux-gnu + +linux: linuxx64 linuxarm64 + +winx64: + cargo build --release --target x86_64-pc-windows-gnu + +xwinx64: + CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=/Users/bppleman/.konan/dependencies/apple-llvm-20200714-macos-x64-essentials/bin/clang++ \ + rustup run nightly cargo xbuild -Zbuild-std --release --target x86_64-pc-windows-gnu + +win: winx64 + +all: macos linux win diff --git a/kommand_core/kommand_core.h b/kommand_core/kommand_core.h new file mode 100644 index 0000000..2a5c638 --- /dev/null +++ b/kommand_core/kommand_core.h @@ -0,0 +1,203 @@ +typedef enum ErrorType { + None, + Io, + Utf8, + Unknown, +} ErrorType; + +typedef enum Stdio { + Inherit, + Null, + Pipe, +} Stdio; + +typedef struct UnitResult { + int ok; + char *err; + enum ErrorType error_type; +} UnitResult; + +typedef struct IntResult { + int ok; + char *err; + enum ErrorType error_type; +} IntResult; + +typedef struct VoidResult { + void *ok; + char *err; + enum ErrorType error_type; +} VoidResult; + +typedef struct Output { + int exit_code; + char *stdout_content; + char *stderr_content; +} Output; + +void *stdin_child(const void *child); + +void *stdout_child(const void *child); + +void *stderr_child(const void *child); + +struct UnitResult kill_child(const void *child); + +unsigned int id_child(const void *child); + +struct IntResult wait_child(const void *child); + +struct VoidResult wait_with_output_child(void *child); + +void drop_child(void *child); + +/** + * # Safety + * Will drop the string + */ +void drop_string(char *string); + +struct VoidResult read_line_stdout(const void *reader); + +struct VoidResult read_all_stdout(const void *reader); + +struct VoidResult read_line_stderr(const void *reader); + +struct VoidResult read_all_stderr(const void *reader); + +/** + * # Safety + */ +struct UnitResult write_line_stdin(const void *writer, const char *line); + +void drop_stderr(void *reader); + +void drop_stdin(void *reader); + +void drop_stdout(void *reader); + +/** + * # Safety + * Will not move the [name]'s ownership + * You must drop the command with [drop_command] + * + * ```rust + * use kommand_core::ffi_util::as_cstring; + * use kommand_core::kommand::{drop_command, new_command}; + * unsafe { + * let command = new_command(as_cstring("pwd").as_ptr()); + * drop_command(command); + * } + * ``` + */ +void *new_command(const char *name); + +/** + * ```rust + * use kommand_core::ffi_util::{as_cstring, drop_string}; + * use kommand_core::kommand::{display_command, drop_command, new_command}; + * unsafe { + * let command = new_command(as_cstring("pwd").as_ptr()); + * let display = display_command(command); + * drop_string(display); + * drop_command(command); + * } + * ``` + */ +char *display_command(const void *command); + +/** + * ```rust + * use kommand_core::ffi_util::{as_cstring, drop_string}; + * use kommand_core::kommand::{debug_command, drop_command, new_command}; + * unsafe { + * let command = new_command(as_cstring("pwd").as_ptr()); + * let debug = debug_command(command); + * drop_string(debug); + * drop_command(command); + * } + * ``` + */ +char *debug_command(const void *command); + +/** + * ```rust + * use kommand_core::ffi_util::as_cstring; + * use kommand_core::kommand::{drop_command, new_command}; + * unsafe { + * let command = new_command(as_cstring("pwd").as_ptr()); + * drop_command(command); + * } + * ``` + */ +void drop_command(void *command); + +/** + * # Safety + * Will not take over ownership of arg + * + * ```rust + * use kommand_core::ffi_util::as_cstring; + * use kommand_core::kommand::{arg_command, drop_command, new_command}; + * unsafe { + * let command = new_command(as_cstring("ls").as_ptr()); + * arg_command(command, as_cstring("-l").as_ptr()); + * drop_command(command); + * } + * ``` + */ +void arg_command(const void *command, const char *arg); + +/** + * # Safety + * Will not take over ownership of key & value + * + * ```rust + * use kommand_core::ffi_util::as_cstring; + * use kommand_core::kommand::{arg_command, drop_command, env_command, new_command}; + * unsafe { + * let command = new_command(as_cstring("echo").as_ptr()); + * arg_command(command, as_cstring("$KOMMAND").as_ptr()); + * env_command(command, as_cstring("KOMMAND").as_ptr(), as_cstring("kommand").as_ptr()); + * drop_command(command); + * } + * ``` + */ +void env_command(const void *command, const char *key, const char *value); + +/** + * # Safety + * Will not drop the key + */ +void remove_env_command(const void *command, const char *key); + +void env_clear_command(const void *command); + +/** + * # Safety + * Will not drop the path + */ +void current_dir_command(const void *command, const char *path); + +void stdin_command(const void *command, enum Stdio stdio); + +void stdout_command(const void *command, enum Stdio stdio); + +void stderr_command(const void *command, enum Stdio stdio); + +struct VoidResult spawn_command(const void *command); + +struct VoidResult output_command(const void *command); + +struct IntResult status_command(const void *command); + +/** + * [Command::get_program] returns a [OsString] which is not compatible with C. + * unix-like: OsString is vec + * windows: OsString is vec + */ +char *get_program_command(const void *command); + +struct Output into_output(void *ptr); + +void drop_output(struct Output output); diff --git a/kommand_core/rust-toolchain.toml b/kommand_core/rust-toolchain.toml new file mode 100644 index 0000000..0ff3080 --- /dev/null +++ b/kommand_core/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.69.0" +targets = ["x86_64-apple-darwin", "aarch64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu", "x86_64-pc-windows-gnu"] diff --git a/kommand_core/src/bin/leaks_test.rs b/kommand_core/src/bin/leaks_test.rs new file mode 100644 index 0000000..adfab20 --- /dev/null +++ b/kommand_core/src/bin/leaks_test.rs @@ -0,0 +1,61 @@ +use std::ffi::c_char; + +use kommand_core::child::{drop_child, stdin_child, stdout_child, wait_child}; +use kommand_core::ffi_util::{as_cstring, into_string}; +use kommand_core::io::{drop_stdin, drop_stdout, read_line_stdout, write_line_stdin}; +use kommand_core::kommand::{ + arg_command, drop_command, new_command, spawn_command, stdin_command, stdout_command, +}; +use kommand_core::stdio::Stdio; + +fn main() { + unsafe { + (0..10000).for_each(|i| { + let command = new_command( + as_cstring("/Users/bppleman/kgit2/kommand/eko/target/release/eko").as_ptr(), + ); + arg_command(command, as_cstring("echo").as_ptr()); + stdin_command(command, Stdio::Pipe); + stdout_command(command, Stdio::Pipe); + let result = spawn_command(command); + let child = if !result.ok.is_null() { + result.ok + } else { + drop_command(command); + panic!("{}", into_string(result.err)) + }; + + let stdin = stdin_child(child); + if stdin.is_null() { + drop_child(child); + drop_command(command); + panic!("stdin is null"); + } + let result = write_line_stdin(stdin, as_cstring("Hello, Kommand!").as_ptr()); + if result.ok != 0 { + drop_child(child); + drop_command(command); + panic!("{}", into_string(result.err)) + } + drop_stdin(stdin); + + let stdout = stdout_child(child); + if stdout.is_null() { + panic!("stdout is null"); + } + let result = read_line_stdout(stdout); + let line = if !result.ok.is_null() { + into_string(result.ok as *mut c_char) + } else { + panic!("{}", into_string(result.err)) + }; + println!("[{i}] line {}", line); + + drop_stdout(stdout); + wait_child(child); + drop_child(child); + drop_command(command); + // std::thread::sleep(std::time::Duration::from_secs(1)); + }); + } +} diff --git a/kommand_core/src/bin/normal_test.rs b/kommand_core/src/bin/normal_test.rs new file mode 100644 index 0000000..ea76a9d --- /dev/null +++ b/kommand_core/src/bin/normal_test.rs @@ -0,0 +1,24 @@ +use std::io::{Read, Write}; +use std::process::{Command, Stdio}; + +fn main() { + (0..1).for_each(|i| { + let mut command = Command::new("/Users/bppleman/kgit2/kommand/eko/target/release/eko"); + command + .arg("echo") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()); + let mut child = command.spawn().unwrap(); + + { + let mut stdin = child.stdin.take().unwrap(); + stdin.write_all(b"Hello, Kommand!").unwrap(); + } + + let mut stdout = child.stdout.take().unwrap(); + let mut line = String::new(); + stdout.read_to_string(&mut line).unwrap(); + + println!("[{i}] line {}", line); + }); +} diff --git a/kommand_core/src/child.rs b/kommand_core/src/child.rs new file mode 100644 index 0000000..9a40182 --- /dev/null +++ b/kommand_core/src/child.rs @@ -0,0 +1,80 @@ +use crate::ffi_util::into_void; +use crate::output::Output; +use crate::result::{IntResult, UnitResult, VoidResult}; +use std::ffi::{c_uint, c_void}; +use std::io::{BufReader, BufWriter}; +use std::process::Child; + +pub fn as_child(command_ptr: &*const c_void) -> &Child { + unsafe { &*(*command_ptr as *const Child) } +} + +pub fn as_child_mut(command_ptr: &mut *const c_void) -> &mut Child { + unsafe { &mut *(*command_ptr as *mut Child) } +} + +pub fn into_child(command_ptr: *mut c_void) -> Child { + unsafe { *Box::from_raw(command_ptr as *mut Child) } +} + +#[no_mangle] +pub extern "C" fn stdin_child(mut child: *const c_void) -> *mut c_void { + let child = as_child_mut(&mut child); + child + .stdin + .take() + .map(BufWriter::new) + .map(into_void) + .unwrap_or(std::ptr::null_mut()) +} + +#[no_mangle] +pub extern "C" fn stdout_child(mut child: *const c_void) -> *mut c_void { + let child = as_child_mut(&mut child); + child + .stdout + .take() + .map(BufReader::new) + .map(into_void) + .unwrap_or(std::ptr::null_mut()) +} + +#[no_mangle] +pub extern "C" fn stderr_child(mut child: *const c_void) -> *mut c_void { + let child = as_child_mut(&mut child); + child + .stderr + .take() + .map(BufReader::new) + .map(into_void) + .unwrap_or(std::ptr::null_mut()) +} + +#[no_mangle] +pub extern "C" fn kill_child(mut child: *const c_void) -> UnitResult { + let child = as_child_mut(&mut child); + child.kill().into() +} + +#[no_mangle] +pub extern "C" fn id_child(child: *const c_void) -> c_uint { + let child = as_child(&child); + child.id() +} + +#[no_mangle] +pub extern "C" fn wait_child(mut child: *const c_void) -> IntResult { + let child = as_child_mut(&mut child); + child.wait().into() +} + +#[no_mangle] +pub extern "C" fn wait_with_output_child(child: *mut c_void) -> VoidResult { + let child = into_child(child); + child.wait_with_output().map(Output::from).into() +} + +#[no_mangle] +pub extern "C" fn drop_child(child: *mut c_void) { + into_child(child); +} diff --git a/kommand_core/src/ffi_util.rs b/kommand_core/src/ffi_util.rs new file mode 100644 index 0000000..6585e45 --- /dev/null +++ b/kommand_core/src/ffi_util.rs @@ -0,0 +1,85 @@ +use std::ffi::{c_char, c_void, CStr, CString}; + +/// # Safety +pub unsafe fn as_string(ptr: *const c_char) -> String { + assert!(!ptr.is_null(), "char ptr is null"); + CStr::from_ptr(ptr) + .to_str() + .expect("convert char ptr to CStr failed") + .to_string() +} + +/// # Safety +pub unsafe fn into_string(ptr: *mut c_char) -> String { + assert!(!ptr.is_null(), "char ptr is null"); + CString::from_raw(ptr) + .into_string() + .expect("convert char ptr to CString failed") +} + +pub fn as_cstring(str: impl AsRef) -> CString { + CString::new(str.as_ref()).expect("convert to CString failed") +} + +pub fn into_cstring(str: impl AsRef) -> *mut c_char { + CString::new(str.as_ref()) + .expect("convert to CString failed") + .into_raw() +} + +// /// # Safety +// #[no_mangle] +// pub unsafe extern "C" fn into_i32(int: *mut i32) -> i32 { +// *Box::from_raw(int) +// } +// +// /// # Safety +// #[no_mangle] +// #[cfg(unix)] +// pub unsafe extern "C" fn u8_ptr_from_os_string(ptr: *mut c_void) -> *mut c_uchar { +// let os_string = *Box::from_raw(ptr as *mut OsString); +// let mut u8_vec = std::os::unix::prelude::OsStringExt::into_vec(os_string); +// let ptr = u8_vec.as_mut_ptr(); +// std::mem::forget(u8_vec); +// ptr +// } +// +// /// # Safety +// #[no_mangle] +// #[cfg(not(unix))] +// pub unsafe extern "C" fn u8_ptr_from_os_string(ptr: *mut c_void) -> *mut c_uchar { +// let _ = *Box::from_raw(ptr as *mut OsString); +// std::ptr::null_mut() +// } +// +// /// # Safety +// #[no_mangle] +// #[cfg(windows)] +// pub unsafe extern "C" fn u16_ptr_from_os_string(ptr: *mut c_void) -> *mut u16 { +// let os_string = *Box::from_raw(ptr as *mut OsString); +// let mut u16_vec: Vec = std::os::windows::ffi::OsStringExt::encode_wide(os_string) +// .chain(Some(0)) +// .collect(); +// let ptr = u16_vec.as_mut_ptr(); +// std::mem::forget(u16_vec); +// ptr +// } +// +// /// # Safety +// #[no_mangle] +// #[cfg(not(windows))] +// pub unsafe extern "C" fn u16_ptr_from_os_string(ptr: *mut c_void) -> *mut u16 { +// let _ = *Box::from_raw(ptr as *mut OsString); +// std::ptr::null_mut() +// } + +/// # Safety +/// Will drop the string +#[no_mangle] +pub unsafe extern "C" fn drop_string(string: *mut c_char) { + into_string(string); +} + +pub fn into_void(object: T) -> *mut c_void { + Box::into_raw(Box::new(object)) as *mut c_void +} diff --git a/kommand_core/src/io.rs b/kommand_core/src/io.rs new file mode 100644 index 0000000..ccc575a --- /dev/null +++ b/kommand_core/src/io.rs @@ -0,0 +1,55 @@ +use crate::ffi_util::as_string; +use std::ffi::{c_char, c_void}; +use std::io::{BufRead, Read, Write}; + +use crate::io::stdout::as_stdout_mut; +use crate::result::{UnitResult, VoidResult}; + +mod stderr; +mod stdin; +mod stdout; + +pub use stderr::drop_stderr; +pub use stdin::drop_stdin; +pub use stdout::drop_stdout; + +#[no_mangle] +pub extern "C" fn read_line_stdout(mut reader: *const c_void) -> VoidResult { + let reader = as_stdout_mut(&mut reader); + let mut line = String::new(); + reader.read_line(&mut line).map(|_| line).into() +} + +#[no_mangle] +pub extern "C" fn read_all_stdout(mut reader: *const c_void) -> VoidResult { + let reader = as_stdout_mut(&mut reader); + let mut line = String::new(); + reader.read_to_string(&mut line).map(|_| line).into() +} + +#[no_mangle] +pub extern "C" fn read_line_stderr(mut reader: *const c_void) -> VoidResult { + let reader = stderr::as_stderr_mut(&mut reader); + let mut line = String::new(); + reader.read_line(&mut line).map(|_| line).into() +} + +#[no_mangle] +pub extern "C" fn read_all_stderr(mut reader: *const c_void) -> VoidResult { + let reader = stderr::as_stderr_mut(&mut reader); + let mut line = String::new(); + reader.read_to_string(&mut line).map(|_| line).into() +} + +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn write_line_stdin( + mut writer: *const c_void, + line: *const c_char, +) -> UnitResult { + let writer = stdin::as_stdin_mut(&mut writer); + let line = as_string(line); + let result = writer.write_all(line.as_bytes()); + writer.flush().unwrap(); + result.into() +} diff --git a/kommand_core/src/io/stderr.rs b/kommand_core/src/io/stderr.rs new file mode 100644 index 0000000..0e28427 --- /dev/null +++ b/kommand_core/src/io/stderr.rs @@ -0,0 +1,16 @@ +use std::ffi::c_void; +use std::io::BufReader; +use std::process::ChildStderr; + +pub fn as_stderr_mut(reader: &mut *const c_void) -> &mut BufReader { + unsafe { &mut *(*reader as *mut BufReader) } +} + +pub fn into_stderr(reader: *mut c_void) -> BufReader { + unsafe { *Box::from_raw(reader as *mut BufReader) } +} + +#[no_mangle] +pub extern "C" fn drop_stderr(reader: *mut c_void) { + into_stderr(reader); +} diff --git a/kommand_core/src/io/stdin.rs b/kommand_core/src/io/stdin.rs new file mode 100644 index 0000000..9f43a0d --- /dev/null +++ b/kommand_core/src/io/stdin.rs @@ -0,0 +1,16 @@ +use std::ffi::c_void; +use std::io::BufWriter; +use std::process::ChildStdin; + +pub fn as_stdin_mut(reader: &mut *const c_void) -> &mut BufWriter { + unsafe { &mut *(*reader as *mut BufWriter) } +} + +pub fn into_stdin(reader: *mut c_void) -> BufWriter { + unsafe { *Box::from_raw(reader as *mut BufWriter) } +} + +#[no_mangle] +pub extern "C" fn drop_stdin(reader: *mut c_void) { + into_stdin(reader); +} diff --git a/kommand_core/src/io/stdout.rs b/kommand_core/src/io/stdout.rs new file mode 100644 index 0000000..ce058f5 --- /dev/null +++ b/kommand_core/src/io/stdout.rs @@ -0,0 +1,16 @@ +use std::ffi::c_void; +use std::io::BufReader; +use std::process::ChildStdout; + +pub fn as_stdout_mut(reader: &mut *const c_void) -> &mut BufReader { + unsafe { &mut *(*reader as *mut BufReader) } +} + +pub fn into_stdout(reader: *mut c_void) -> BufReader { + unsafe { *Box::from_raw(reader as *mut BufReader) } +} + +#[no_mangle] +pub extern "C" fn drop_stdout(reader: *mut c_void) { + into_stdout(reader); +} diff --git a/kommand_core/src/kommand.rs b/kommand_core/src/kommand.rs new file mode 100644 index 0000000..17cae63 --- /dev/null +++ b/kommand_core/src/kommand.rs @@ -0,0 +1,208 @@ +use std::ffi::{c_char, c_void}; +use std::process::Command; + +use crate::ffi_util::{as_string, into_cstring, into_void}; +use crate::output::Output; +use crate::result::{IntResult, VoidResult}; +use crate::stdio::Stdio; + +pub fn as_command(command_ptr: &*const c_void) -> &Command { + unsafe { &*(*command_ptr as *const Command) } +} + +pub fn as_command_mut(command_ptr: &mut *const c_void) -> &mut Command { + unsafe { &mut *(*command_ptr as *mut Command) } +} + +pub fn into_command(command_ptr: *mut c_void) -> Command { + unsafe { *Box::from_raw(command_ptr as *mut Command) } +} + +/// # Safety +/// Will not move the [name]'s ownership +/// You must drop the command with [drop_command] +/// +/// ```rust +/// use kommand_core::ffi_util::as_cstring; +/// use kommand_core::kommand::{drop_command, new_command}; +/// unsafe { +/// let command = new_command(as_cstring("pwd").as_ptr()); +/// drop_command(command); +/// } +/// ``` +#[no_mangle] +pub unsafe extern "C" fn new_command(name: *const c_char) -> *mut c_void { + let name = as_string(name); + let command = Command::new(name); + into_void(command) +} + +/// ```rust +/// use kommand_core::ffi_util::{as_cstring, drop_string}; +/// use kommand_core::kommand::{display_command, drop_command, new_command}; +/// unsafe { +/// let command = new_command(as_cstring("pwd").as_ptr()); +/// let display = display_command(command); +/// drop_string(display); +/// drop_command(command); +/// } +/// ``` +#[no_mangle] +pub extern "C" fn display_command(command: *const c_void) -> *mut c_char { + let command = as_command(&command); + into_cstring(format!("{:?}", command)) +} + +/// ```rust +/// use kommand_core::ffi_util::{as_cstring, drop_string}; +/// use kommand_core::kommand::{debug_command, drop_command, new_command}; +/// unsafe { +/// let command = new_command(as_cstring("pwd").as_ptr()); +/// let debug = debug_command(command); +/// drop_string(debug); +/// drop_command(command); +/// } +/// ``` +#[no_mangle] +pub extern "C" fn debug_command(command: *const c_void) -> *mut c_char { + let command = as_command(&command); + into_cstring(format!("{:#?}", command)) +} + +/// ```rust +/// use kommand_core::ffi_util::as_cstring; +/// use kommand_core::kommand::{drop_command, new_command}; +/// unsafe { +/// let command = new_command(as_cstring("pwd").as_ptr()); +/// drop_command(command); +/// } +/// ``` +#[no_mangle] +pub extern "C" fn drop_command(command: *mut c_void) { + into_command(command); +} + +/// # Safety +/// Will not take over ownership of arg +/// +/// ```rust +/// use kommand_core::ffi_util::as_cstring; +/// use kommand_core::kommand::{arg_command, drop_command, new_command}; +/// unsafe { +/// let command = new_command(as_cstring("ls").as_ptr()); +/// arg_command(command, as_cstring("-l").as_ptr()); +/// drop_command(command); +/// } +/// ``` +#[no_mangle] +pub unsafe extern "C" fn arg_command(mut command: *const c_void, arg: *const c_char) { + let command = as_command_mut(&mut command); + let arg = as_string(arg); + command.arg(arg); +} + +/// # Safety +/// Will not take over ownership of key & value +/// +/// ```rust +/// use kommand_core::ffi_util::as_cstring; +/// use kommand_core::kommand::{arg_command, drop_command, env_command, new_command}; +/// unsafe { +/// let command = new_command(as_cstring("echo").as_ptr()); +/// arg_command(command, as_cstring("$KOMMAND").as_ptr()); +/// env_command(command, as_cstring("KOMMAND").as_ptr(), as_cstring("kommand").as_ptr()); +/// drop_command(command); +/// } +/// ``` +#[no_mangle] +pub unsafe extern "C" fn env_command( + mut command: *const c_void, + key: *const c_char, + value: *const c_char, +) { + let command = as_command_mut(&mut command); + let key = as_string(key); + let value = as_string(value); + command.env(key, value); +} + +/// # Safety +/// Will not drop the key +#[no_mangle] +pub unsafe extern "C" fn remove_env_command(mut command: *const c_void, key: *const c_char) { + let command = as_command_mut(&mut command); + let key = as_string(key); + command.env_remove(key); +} + +#[no_mangle] +pub extern "C" fn env_clear_command(mut command: *const c_void) { + let command = as_command_mut(&mut command); + command.env_clear(); +} + +/// # Safety +/// Will not drop the path +#[no_mangle] +pub unsafe extern "C" fn current_dir_command(mut command: *const c_void, path: *const c_char) { + let command = as_command_mut(&mut command); + let path = as_string(path); + command.current_dir(path); +} + +#[no_mangle] +pub extern "C" fn stdin_command(mut command: *const c_void, stdio: Stdio) { + let command = as_command_mut(&mut command); + match stdio { + Stdio::Inherit => command.stdin(std::process::Stdio::inherit()), + Stdio::Null => command.stdin(std::process::Stdio::null()), + Stdio::Pipe => command.stdin(std::process::Stdio::piped()), + }; +} + +#[no_mangle] +pub extern "C" fn stdout_command(mut command: *const c_void, stdio: Stdio) { + let command = as_command_mut(&mut command); + match stdio { + Stdio::Inherit => command.stdout(std::process::Stdio::inherit()), + Stdio::Null => command.stdout(std::process::Stdio::null()), + Stdio::Pipe => command.stdout(std::process::Stdio::piped()), + }; +} + +#[no_mangle] +pub extern "C" fn stderr_command(mut command: *const c_void, stdio: Stdio) { + let command = as_command_mut(&mut command); + match stdio { + Stdio::Inherit => command.stderr(std::process::Stdio::inherit()), + Stdio::Null => command.stderr(std::process::Stdio::null()), + Stdio::Pipe => command.stderr(std::process::Stdio::piped()), + }; +} + +#[no_mangle] +pub extern "C" fn spawn_command(mut command: *const c_void) -> VoidResult { + let command = as_command_mut(&mut command); + command.spawn().into() +} + +#[no_mangle] +pub extern "C" fn output_command(mut command: *const c_void) -> VoidResult { + let command = as_command_mut(&mut command); + command.output().map(Output::from).into() +} + +#[no_mangle] +pub extern "C" fn status_command(mut command: *const c_void) -> IntResult { + let command = as_command_mut(&mut command); + command.status().into() +} + +/// [Command::get_program] returns a [OsString] which is not compatible with C. +/// unix-like: OsString is vec +/// windows: OsString is vec +#[no_mangle] +pub extern "C" fn get_program_command(command: *const c_void) -> *mut c_char { + let command = as_command(&command); + into_cstring(command.get_program().to_string_lossy()) +} diff --git a/kommand_core/src/lib.rs b/kommand_core/src/lib.rs new file mode 100644 index 0000000..7897965 --- /dev/null +++ b/kommand_core/src/lib.rs @@ -0,0 +1,11 @@ +//! This is a [std::process::Command] wrapper for ffi +//! +//! Export the ffi interface through [cbindgen] and use it in kotlin/native + +pub mod child; +pub mod ffi_util; +pub mod io; +pub mod kommand; +pub mod output; +pub mod result; +pub mod stdio; diff --git a/kommand_core/src/output.rs b/kommand_core/src/output.rs new file mode 100644 index 0000000..730ba4f --- /dev/null +++ b/kommand_core/src/output.rs @@ -0,0 +1,42 @@ +use std::ffi::{c_char, c_void}; +use std::os::raw::c_int; + +use crate::ffi_util::{into_cstring, into_string}; + +#[repr(C)] +pub struct Output { + pub exit_code: c_int, + pub stdout_content: *mut c_char, + pub stderr_content: *mut c_char, +} + +#[no_mangle] +pub extern "C" fn into_output(ptr: *mut c_void) -> Output { + unsafe { *Box::from_raw(ptr as *mut Output) } +} + +#[no_mangle] +pub extern "C" fn drop_output(output: Output) { + let stdout = output.stdout_content; + if !stdout.is_null() { + let _ = unsafe { into_string(stdout) }; + } + let stderr = output.stderr_content; + if !stderr.is_null() { + let _ = unsafe { into_string(stderr) }; + } +} + +impl From for Output { + fn from(value: std::process::Output) -> Self { + let exit_code = value.status.code().unwrap_or(-1); + let stdout = String::from_utf8_lossy(&value.stdout).to_string(); + let stderr = String::from_utf8_lossy(&value.stderr).to_string(); + + Output { + exit_code, + stdout_content: into_cstring(stdout), + stderr_content: into_cstring(stderr), + } + } +} diff --git a/kommand_core/src/result.rs b/kommand_core/src/result.rs new file mode 100644 index 0000000..a10c07b --- /dev/null +++ b/kommand_core/src/result.rs @@ -0,0 +1,126 @@ +use crate::ffi_util::{into_cstring, into_void}; +use std::ffi::{c_char, c_int, c_void}; +use std::io; + +#[repr(C)] +pub struct VoidResult { + pub ok: *mut c_void, + pub err: *mut c_char, + pub error_type: ErrorType, +} + +#[repr(C)] +pub struct IntResult { + pub ok: c_int, + pub err: *mut c_char, + pub error_type: ErrorType, +} + +#[repr(C)] +pub struct UnitResult { + pub ok: c_int, + pub err: *mut c_char, + pub error_type: ErrorType, +} + +#[repr(C)] +pub enum ErrorType { + None, + Io, + Utf8, + Unknown, +} + +impl From> for VoidResult { + //noinspection DuplicatedCode + fn from(result: io::Result) -> Self { + match result { + Ok(child) => VoidResult { + ok: into_void(child), + err: std::ptr::null_mut() as *mut c_char, + error_type: ErrorType::None, + }, + Err(e) => VoidResult { + ok: std::ptr::null_mut(), + err: into_cstring(e.to_string()), + error_type: ErrorType::Io, + }, + } + } +} + +impl From> for VoidResult { + //noinspection DuplicatedCode + fn from(value: io::Result) -> Self { + match value { + Ok(output) => VoidResult { + ok: into_void(output), + err: std::ptr::null_mut() as *mut c_char, + error_type: ErrorType::None, + }, + Err(e) => VoidResult { + ok: std::ptr::null_mut(), + err: into_cstring(e.to_string()), + error_type: ErrorType::Io, + }, + } + } +} + +impl From> for VoidResult { + fn from(value: io::Result) -> Self { + match value { + Ok(string) => VoidResult { + ok: into_cstring(string) as *mut c_void, + err: std::ptr::null_mut() as *mut c_char, + error_type: ErrorType::None, + }, + Err(e) => VoidResult { + ok: std::ptr::null_mut(), + err: into_cstring(e.to_string()), + error_type: ErrorType::Io, + }, + } + } +} + +impl From> for IntResult { + fn from(value: io::Result) -> Self { + match value { + Ok(status) => match status.code() { + None => IntResult { + ok: -1, + err: into_cstring("No exit code"), + error_type: ErrorType::None, + }, + Some(code) => IntResult { + ok: code, + err: std::ptr::null_mut() as *mut c_char, + error_type: ErrorType::None, + }, + }, + Err(e) => IntResult { + ok: -1, + err: into_cstring(e.to_string()), + error_type: ErrorType::Io, + }, + } + } +} + +impl From> for UnitResult { + fn from(value: io::Result<()>) -> Self { + match value { + Ok(_) => UnitResult { + ok: 0, + err: std::ptr::null_mut() as *mut c_char, + error_type: ErrorType::None, + }, + Err(e) => UnitResult { + ok: -1, + err: into_cstring(e.to_string()), + error_type: ErrorType::Io, + }, + } + } +} diff --git a/kommand_core/src/stdio.rs b/kommand_core/src/stdio.rs new file mode 100644 index 0000000..80b0b46 --- /dev/null +++ b/kommand_core/src/stdio.rs @@ -0,0 +1,6 @@ +#[repr(C)] +pub enum Stdio { + Inherit, + Null, + Pipe, +} diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock deleted file mode 100644 index ff32adc..0000000 --- a/kotlin-js-store/yarn.lock +++ /dev/null @@ -1,555 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ungap/promise-all-settled@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" - integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== - -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -camelcase@^6.0.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chokidar@3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -debug@4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -decamelize@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" - integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== - -diff@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-string-regexp@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-up@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -format-util@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" - integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -he@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-plain-obj@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -js-yaml@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -log-symbols@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -minimatch@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" - integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^3.0.4: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -mocha@10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9" - integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA== - dependencies: - "@ungap/promise-all-settled" "1.1.2" - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.3" - debug "4.3.4" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "7.2.0" - he "1.2.0" - js-yaml "4.1.0" - log-symbols "4.1.0" - minimatch "5.0.1" - ms "2.1.3" - nanoid "3.3.3" - serialize-javascript "6.0.0" - strip-json-comments "3.1.1" - supports-color "8.1.1" - workerpool "6.2.1" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -nanoid@3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" - integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -picomatch@^2.0.4, picomatch@^2.2.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -safe-buffer@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -serialize-javascript@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== - dependencies: - randombytes "^2.1.0" - -source-map-support@0.5.21: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-json-comments@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -supports-color@8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -workerpool@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" - integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-unparser@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" - integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== - dependencies: - camelcase "^6.0.0" - decamelize "^4.0.0" - flat "^5.0.2" - is-plain-obj "^2.1.0" - -yargs@16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/settings.gradle.kts b/settings.gradle.kts index 5f77412..666ccec 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,8 +1,8 @@ pluginManagement { plugins { - kotlin("jvm") version "1.9.0" apply false - kotlin("multiplatform") version "1.9.0" apply false - id("org.jetbrains.dokka") version "1.9.0" apply false + kotlin("jvm") version "1.9.21" apply false + kotlin("multiplatform") version "1.9.21" apply false + id("org.jetbrains.dokka") version "1.9.10" apply false id("io.github.gradle-nexus.publish-plugin") version "1.3.0" apply false } @@ -13,5 +13,3 @@ pluginManagement { } rootProject.name = "kommand" - -include(":sub_command") diff --git a/src/commonMain/kotlin/com/kgit2/Child.kt b/src/commonMain/kotlin/com/kgit2/Child.kt new file mode 100644 index 0000000..edbe4bb --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/Child.kt @@ -0,0 +1,5 @@ +package com.kgit2 + +expect class Child { + +} diff --git a/src/commonMain/kotlin/com/kgit2/Command.kt b/src/commonMain/kotlin/com/kgit2/Command.kt new file mode 100644 index 0000000..7e222e8 --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/Command.kt @@ -0,0 +1,45 @@ +package com.kgit2 + +expect class Command { + val command: String + + constructor(command: String) + + fun debugString(): String + + fun arg(arg: String): Command + + fun args(args: List): Command + + fun env(key: String, value: String): Command + + fun envs(envs: Map): Command + + fun removeEnv(key: String): Command + + fun envClear(): Command + + fun cwd(dir: String): Command + + fun stdin(stdio: Stdio): Command + + fun stdout(stdio: Stdio): Command + + fun stderr(stdio: Stdio): Command + + @Throws(KommandException::class) + fun spawn(): Child + + @Throws(KommandException::class) + fun output(): Output + + fun status(): Int? +} + +enum class Stdio { + Inherit, + Pipe, + Null, + ; + companion object +} diff --git a/src/commonMain/kotlin/com/kgit2/Exception.kt b/src/commonMain/kotlin/com/kgit2/Exception.kt new file mode 100644 index 0000000..ff09db2 --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/Exception.kt @@ -0,0 +1,15 @@ +package com.kgit2 + +class KommandException( + message: String?, + val errorType: ErrorType?, +) : Exception(message) + +enum class ErrorType { + None, + IO, + Utf8, + Unknown, + ; + companion object +} diff --git a/src/commonMain/kotlin/com/kgit2/Output.kt b/src/commonMain/kotlin/com/kgit2/Output.kt new file mode 100644 index 0000000..1f1481a --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/Output.kt @@ -0,0 +1,9 @@ +package com.kgit2 + +data class Output( + val status: Int?, + val stdout: String?, + val stderr: String?, +) { + companion object +} diff --git a/src/commonMain/kotlin/com/kgit2/io/Reader.kt b/src/commonMain/kotlin/com/kgit2/io/Reader.kt deleted file mode 100644 index 9e53ed3..0000000 --- a/src/commonMain/kotlin/com/kgit2/io/Reader.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.kgit2.io - -import io.ktor.utils.io.bits.* -import io.ktor.utils.io.core.* - -expect class PlatformReader { - fun closeSource() - - fun fill(destination: Memory, offset: Int, length: Int): Int -} - -open class Reader ( - private val platformReader: PlatformReader -) : Input( - // pool = DefaultBufferPool(Buffer.ReservedSize + 1, 1) -) { - - override fun closeSource() { - platformReader.closeSource() - } - - override fun fill(destination: Memory, offset: Int, length: Int): Int { - return platformReader.fill(destination, offset, length) - } - - fun readLine(): String? { - return readUTF8Line() - } - - fun lines(): Sequence { - return sequence { - while (endOfInput.not()) { - readUTF8Line()?.let { yield(it) } - } - } - } -} diff --git a/src/commonMain/kotlin/com/kgit2/io/Writer.kt b/src/commonMain/kotlin/com/kgit2/io/Writer.kt deleted file mode 100644 index a7f489a..0000000 --- a/src/commonMain/kotlin/com/kgit2/io/Writer.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.kgit2.io - -import io.ktor.utils.io.bits.* -import io.ktor.utils.io.core.* - -expect class PlatformWriter { - fun flush(source: Memory, offset: Int, length: Int) - fun close() -} - -open class Writer ( - private val platformWriter: PlatformWriter -) : Output() { - override fun closeDestination() { - platformWriter.close() - } - - override fun flush(source: Memory, offset: Int, length: Int) { - platformWriter.flush(source, offset, length) - } -} diff --git a/src/commonMain/kotlin/com/kgit2/process/Child.kt b/src/commonMain/kotlin/com/kgit2/process/Child.kt deleted file mode 100644 index 9e8c9cb..0000000 --- a/src/commonMain/kotlin/com/kgit2/process/Child.kt +++ /dev/null @@ -1,138 +0,0 @@ -package com.kgit2.process - -import com.kgit2.io.Reader -import com.kgit2.io.Writer -import io.ktor.utils.io.errors.* - -expect class Child( - command: String, - args: List, - envs: Map, - cwd: String? = null, - stdin: Stdio, - stdout: Stdio, - stderr: Stdio, -) { - val command: String - val args: List - val envs: Map - val cwd: String? - val stdin: Stdio - val stdout: Stdio - val stderr: Stdio - - var id: Int? - fun getChildStdin(): Writer? - fun getChildStdout(): Reader? - fun getChildStderr(): Reader? - - @Throws(IOException::class) - fun start(options: ChildOptions = ChildOptions.W_NOHANG) - - @Throws(IOException::class) - fun wait(): ChildExitStatus - - @Throws(IOException::class) - fun waitWithOutput(): String? - fun kill() - fun prompt(): String -} - -class ChildOptions private constructor( - val value: Int -) { - companion object { - val W_UNTRACED = ChildOptions(0x00000001) - val W_NOHANG = ChildOptions(0x00000002) - } - - infix fun or(other: ChildOptions): ChildOptions { - return ChildOptions(this.value or other.value) - } - - operator fun contains(other: ChildOptions): Boolean { - return this.value and other.value != 0 - } -} - -data class ChildExitStatus( - val code: Int, -) { - inline fun w_stopped(): Int { - return 0x7F - } - - inline fun w_coreflag(): Int { - return 0x80 - } - - inline fun w_status(x: Int): Int { - return x and w_stopped() - } - - inline fun w_stopsig(x: Int): Int { - return x shr 8 - } - - /** - * returns true if the child terminated normally, - * that is, by calling exit(3) or _exit(2), - * or by returning from main(). - */ - inline fun exited(): Boolean { - return w_status(code) == 0 - } - - /** - * returns the exit status of the child. - * This consists of the least significant 8 bits of the status argument - * that the child specified in a call to exit(3) or _exit(2) - * or as the argument for a return statement in main(). - * This macro should only be employed if WIFEXITED returned true. - */ - inline fun exitStatus(): Int { - return w_stopsig(code) and 0x000000FF - } - - /** - * returns true if the child process was terminated by a signal. - */ - inline fun signaled(): Boolean { - return (w_status(code) != w_stopped()) && (w_status(code) != 0) - } - - /** - * returns the number of the signal that caused the child process to terminate. - * This macro should only be employed if WIFSIGNALED returned true. - */ - inline fun signal(): Int { - return w_status(code) - } - - /** - * returns true if the child produced a core dump. - * This macro should only be employed if WIFSIGNALED returned true. - * This macro is not specified in POSIX.1-2001 - * and is not available on some UNIX implementations (e.g., AIX, SunOS). - * Only use this enclosed in #ifdef WCOREDUMP ... #endif. - */ - inline fun coreDump(): Boolean { - return code == w_coreflag() - } - - /** - * returns true if the child process was stopped by delivery of a signal; - * this is only possible if the call was done using WUNTRACED or when the child is being traced (see ptrace(2)). - */ - inline fun stopped(): Boolean { - return (w_status(code) == w_stopped()) && (w_stopsig(code) != 0x13) - } - - /** - * returns the number of the signal which caused the child to stop. - * This macro should only be employed if WIFSTOPPED returned true. - */ - inline fun stopSignal(): Int { - return w_stopsig(code) - } -} diff --git a/src/commonMain/kotlin/com/kgit2/process/Command.kt b/src/commonMain/kotlin/com/kgit2/process/Command.kt deleted file mode 100644 index 66e5428..0000000 --- a/src/commonMain/kotlin/com/kgit2/process/Command.kt +++ /dev/null @@ -1,103 +0,0 @@ -package com.kgit2.process - -public class Command( - val command: String -) { - private val args = mutableListOf() - private val envs = mutableMapOf() - private var cwd: String? = null - - private var stdin: Stdio = Stdio.Inherit - private var stdout: Stdio = Stdio.Inherit - private var stderr: Stdio = Stdio.Inherit - - public fun arg(arg: String): Command { - this.args.addAll(arg.split(" ")) - return this - } - - public fun args(vararg args: String): Command { - this.args.addAll(args) - return this - } - - public fun env(key: String, value: String): Command { - this.envs[key] = value - return this - } - - public fun envs(vararg envs: Pair): Command { - this.envs.putAll(envs) - return this - } - - public fun cwd(cwd: String?): Command { - this.cwd = cwd - return this - } - - - public fun stdin(stdin: Stdio): Command { - this.stdin = stdin - return this - } - - public fun stdout(io: Stdio): Command { - this.stdout = io - return this - } - - public fun stderr(io: Stdio): Command { - this.stderr = io - return this - } - - public fun spawn(): Child { - val child = Child( - command = command, - args = args, - envs = envs, - cwd = cwd, - stdin = stdin, - stdout = stdout, - stderr = stderr, - ) - child.start() - return child - } - - public fun output(): String? { - return spawn().waitWithOutput() - } - - public fun status(): ChildExitStatus { - return spawn().wait() - } - - public fun getArgs(): List { - return args - } - - public fun getEnvs(): Map { - return envs - } - - public fun getCwd(): String? { - return cwd - } - - override fun toString(): String { - return "Command(command='$command', args=$args, envs=$envs, cwd=$cwd, stdin=$stdin, stdout=$stdout, stderr=$stderr)" - } - - public fun prompt(): String { - return "$command ${args.joinToString(" ")}" - } -} - - -enum class Stdio { - Inherit, - Pipe, - Null, -} diff --git a/src/commonTest/kotlin/com/kgit2/CommandTest.kt b/src/commonTest/kotlin/com/kgit2/CommandTest.kt new file mode 100644 index 0000000..37ffa15 --- /dev/null +++ b/src/commonTest/kotlin/com/kgit2/CommandTest.kt @@ -0,0 +1,10 @@ +package com.kgit2 + +import kotlin.test.Test + +class CommandTest { + @Test + fun autoFree() { + println(Command("echo").debugString()) + } +} diff --git a/src/commonTest/kotlin/process/CommandTest.kt b/src/commonTest/kotlin/process/CommandTest.kt deleted file mode 100644 index 235deaa..0000000 --- a/src/commonTest/kotlin/process/CommandTest.kt +++ /dev/null @@ -1,148 +0,0 @@ -package process - -import com.kgit2.process.Command -import com.kgit2.process.Stdio -import kotlin.test.Test -import kotlin.test.assertEquals - -expect val eko: String - -expect fun shellTest() - -expect fun envVar(key: String): String? - -expect fun homeDir(): String? - -expect fun pwd(): Command - -class CommandTest { - - @Test - fun test() { - println("begin") - Command(eko) - .stdout(Stdio.Pipe) - .spawn() - println("end") - } - - @Test - fun testOutput() { - val expectString = "Hello, Kommand!\n" - val output = Command(eko) - .stdout(Stdio.Pipe) - .spawn() - .waitWithOutput() - assertEquals(expectString, output) - } - - @Test - fun testEcho() { - val expectString = "Hello, Kommand!" - val child = Command(eko) - .args("stdout") - .stdin(Stdio.Pipe) - .stdout(Stdio.Pipe) - .spawn() - val writer = child.getChildStdin()!! - writer.appendLine(expectString) - writer.close() - val reader = child.getChildStdout()!! - val output = reader.readLine() - assertEquals(expectString, output) - } - - @Test - fun testEchoMultiLine() { - val expectString = "Hello, Kommand!" - val child = Command(eko) - .args("stdout") - .stdin(Stdio.Pipe) - .stdout(Stdio.Pipe) - .spawn() - val writer = child.getChildStdin()!! - writer.appendLine(expectString) - writer.appendLine(expectString) - writer.flush() - writer.close() - val reader = child.getChildStdout()!! - val lines = reader.lines().toList() - lines.forEach { - assertEquals(expectString, it) - } - assertEquals(2, lines.count()) - } - - @Test - fun testError() { - val expectString = "Hello, Kommand!" - val child = Command(eko) - .args("stderr") - .stdin(Stdio.Pipe) - .stderr(Stdio.Pipe) - .spawn() - val writer = child.getChildStdin()!! - writer.appendLine(expectString) - writer.close() - val reader = child.getChildStderr()!! - val output = reader.readLine() - assertEquals(expectString, output) - } - - @Test - fun testInterval() { - val expectLineCount = 5 - var lineCount = 0 - Command(eko) - .args("interval") - .stdout(Stdio.Pipe) - .spawn() - .getChildStdout() - ?.lines()?.forEach { - println(it) - lineCount += 1 - } - assertEquals(expectLineCount, lineCount) - } - - @Test - fun testIntervalWithArgs() { - val expectLineCount = 10 - var lineCount = 0 - Command(eko) - .args("interval", "10") - .stdout(Stdio.Pipe) - .spawn() - .getChildStdout() - ?.lines()?.forEach { - println(it) - lineCount += 1 - } - assertEquals(expectLineCount, lineCount) - } - - @Test - fun shTest() { - shellTest() - } - - @Test - fun testCat() { - val expectString = "PREV — DATA\nNEXT" - val cmd = Command("echo").args(expectString).stdout(Stdio.Pipe).spawn() - cmd.getChildStdout()?.lines()?.joinToString("\n")?.let { - assertEquals(expectString, it) - } - } - - @Test - fun testCwd() { - val output = pwd() - .cwd(homeDir()) - .stdout(Stdio.Pipe) - .spawn() - .waitWithOutput() - ?.trim(); - assertEquals(homeDir()?.trimEnd('/'), output?.trimEnd('/')) - } -} diff --git a/src/jsMain/kotlin/Main.kt b/src/jsMain/kotlin/Main.kt deleted file mode 100644 index f8799c1..0000000 --- a/src/jsMain/kotlin/Main.kt +++ /dev/null @@ -1,13 +0,0 @@ -@file:Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") - -import child_process.ChildProcess -import child_process.ChildProcessByStdio - -fun main() { - val module = js("require('child_process')") - val options: dynamic = js("{stdio: [0, 1, 2]}") - val child: ChildProcess = module.spawn("ls", arrayOf("-l"), options) as ChildProcess - console.log(child) - // process.spawnargs() - // console.log(process) -} diff --git a/src/jsMain/kotlin/com/kgit2/io/PlatformReader.kt b/src/jsMain/kotlin/com/kgit2/io/PlatformReader.kt deleted file mode 100644 index c44be05..0000000 --- a/src/jsMain/kotlin/com/kgit2/io/PlatformReader.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.kgit2.io - -import io.ktor.utils.io.bits.* -import stream.internal - -actual class PlatformReader(readable: internal.Readable) { - var readable: internal.Readable? = readable - - init { - readable.setEncoding("utf8") - } - - actual fun closeSource() { - readable?.destroy() - } - - actual fun fill(destination: Memory, offset: Int, length: Int): Int { - val buffer = ByteArray(length) - var readed = 0 - while (readable?.readable == true) { - readable?.read() - } - return readed - } - - actual fun fillLine(destination: Memory, offset: Int, length: Int): Int { - TODO("Not yet implemented") - } - -} diff --git a/src/jsMain/kotlin/com/kgit2/io/PlatformWriter.kt b/src/jsMain/kotlin/com/kgit2/io/PlatformWriter.kt deleted file mode 100644 index ed6c422..0000000 --- a/src/jsMain/kotlin/com/kgit2/io/PlatformWriter.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.kgit2.io - -import io.ktor.utils.io.bits.* -import stream.internal - -actual class PlatformWriter(writeable: internal.Writable) { - var writeable: internal.Writable? = writeable - - @OptIn(ExperimentalUnsignedTypes::class) - actual fun flush(source: Memory, offset: Int, length: Int) { - val buffer = ByteArray(length) - source.copyTo(buffer, offset, length, 0) - writeable?.write(buffer.toUByteArray()) - } - - actual fun close() { - writeable?.destroy() - } -} diff --git a/src/jsMain/kotlin/com/kgit2/process/Child.kt b/src/jsMain/kotlin/com/kgit2/process/Child.kt deleted file mode 100644 index b15d8c9..0000000 --- a/src/jsMain/kotlin/com/kgit2/process/Child.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.kgit2.process - -import NodeJS.Dict -import NodeJS.ProcessEnv -import child_process.ChildProcess -import child_process.SpawnOptions -import child_process.SpawnSyncOptions -import child_process.spawn -import child_process.spawnSync -import com.kgit2.io.Reader -import com.kgit2.io.Writer -import io.ktor.utils.io.errors.* - -val module = js("require('child_process')") - -actual class Child actual constructor( - actual val command: String, - actual val args: List, - actual val envs: Map, - actual val cwd: String?, - actual val stdin: Stdio, - actual val stdout: Stdio, - actual val stderr: Stdio -) { - actual var id: Int? = null - - actual fun getChildStdin(): Writer? { - TODO("Not yet implemented") - } - - actual fun getChildStdout(): Reader? { - TODO("Not yet implemented") - } - - actual fun getChildStderr(): Reader? { - TODO("Not yet implemented") - } - - @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") - actual fun start(options: ChildOptions) { - val spawnOptions: SpawnOptions = js("{}") as SpawnOptions - spawnOptions.cwd = cwd - spawnOptions.env = envs.asDynamic() as ProcessEnv - spawnOptions.stdio = arrayOf(stdin, stdout, stderr).map { - when (it) { - Stdio.Inherit -> "inherit" - Stdio.Pipe -> "pipe" - Stdio.Null -> "ignore" - } - }.toTypedArray() - val process = spawn(command, spawnOptions) - this.id = process.pid.toInt() - process.stdin - } - - actual fun wait(): ChildExitStatus { - return ChildExitStatus(0) - } - - actual fun waitWithOutput(): String? { - TODO("Not yet implemented") - } - - actual fun kill() { - } - - actual fun prompt(): String { - TODO("Not yet implemented") - } - -} diff --git a/src/jsMain/resources/main.js b/src/jsMain/resources/main.js deleted file mode 100644 index 127d75c..0000000 --- a/src/jsMain/resources/main.js +++ /dev/null @@ -1,17 +0,0 @@ -(async () => { - var child_process = require('child_process'); - console.log(child_process) - let process = child_process.spawn('../../../sub_command/build/install/sub_command/bin/sub_command', ["echo"], {stdio: ['pipe', 'pipe', 'inherit']}) - console.log(process) - process.stdin.write("Hello World1\n") - process.stdin.write("Hello World2\n") - process.stdin.write("Hello World3\n") - - // process.stdout.on('message', (data) => { - // console.log(`stdout: ${data}`); - // }) - process.on('spawn', (data) => { - console.log(`stdout: ${data}`); - }) - // console.log(process.status) -})() diff --git a/src/jsTest/kotlin/process/subCommand.kt b/src/jsTest/kotlin/process/subCommand.kt deleted file mode 100644 index 245c081..0000000 --- a/src/jsTest/kotlin/process/subCommand.kt +++ /dev/null @@ -1,3 +0,0 @@ -package process - -actual val subCommand: String = "sub_command/build/install/sub_command/bin/sub_command" diff --git a/src/jvmMain/kotlin/com/kgit2/Child.jvm.kt b/src/jvmMain/kotlin/com/kgit2/Child.jvm.kt new file mode 100644 index 0000000..3d5f67f --- /dev/null +++ b/src/jvmMain/kotlin/com/kgit2/Child.jvm.kt @@ -0,0 +1,7 @@ +package com.kgit2 + +actual class Child( + private val process: Process, +) { + +} diff --git a/src/jvmMain/kotlin/com/kgit2/Command.jvm.kt b/src/jvmMain/kotlin/com/kgit2/Command.jvm.kt new file mode 100644 index 0000000..ea9fa3e --- /dev/null +++ b/src/jvmMain/kotlin/com/kgit2/Command.jvm.kt @@ -0,0 +1,97 @@ +package com.kgit2 + +import java.io.File + +actual class Command( + actual val command: String, + private val builder: ProcessBuilder, +) { + + actual constructor(command: String) : this(command, ProcessBuilder(command)) + + actual fun debugString(): String { + return "Command(command='$command', builder=${ + builder.command() + .toMutableList() + .apply { this.removeFirst() } + .joinToString(",", "[", "]") + })" + } + + actual fun arg(arg: String): Command { + builder.command(arg) + return this + } + + actual fun args(args: List): Command { + builder.command(args) + return this + } + + actual fun env(key: String, value: String): Command { + builder.environment()[key] = value + return this + } + + actual fun envs(envs: Map): Command { + builder.environment().putAll(envs) + return this + } + + actual fun removeEnv(key: String): Command { + builder.environment().remove(key) + return this + } + + actual fun envClear(): Command { + builder.environment().clear() + return this + } + + actual fun cwd(dir: String): Command { + builder.directory(File(dir)) + return this + } + + actual fun stdin(stdio: Stdio): Command { + builder.redirectInput(stdio.to()) + return this + } + + actual fun stdout(stdio: Stdio): Command { + builder.redirectOutput(stdio.to()) + return this + } + + actual fun stderr(stdio: Stdio): Command { + builder.redirectError(stdio.to()) + return this + } + + @Throws(KommandException::class) + actual fun spawn(): Child { + val process = builder.start() + return Child(process) + } + + @Throws(KommandException::class) + actual fun output(): Output { + val process = builder.start() + val stdoutContent = process.inputReader().readText() + val stderrContent = process.errorReader().readText() + val status = process.waitFor() + return Output(status, stdoutContent, stderrContent) + } + + actual fun status(): Int? { + return builder.start().waitFor() + } +} + +fun Stdio.to(): ProcessBuilder.Redirect { + return when (this) { + Stdio.Inherit -> ProcessBuilder.Redirect.INHERIT + Stdio.Null -> ProcessBuilder.Redirect.DISCARD + Stdio.Pipe -> ProcessBuilder.Redirect.PIPE + } +} diff --git a/src/jvmMain/kotlin/com/kgit2/io/PlatformReader.kt b/src/jvmMain/kotlin/com/kgit2/io/PlatformReader.kt deleted file mode 100644 index c46d1fd..0000000 --- a/src/jvmMain/kotlin/com/kgit2/io/PlatformReader.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.kgit2.io - -import io.ktor.utils.io.bits.* -import java.io.BufferedReader -import java.io.InputStream -import java.util.* -import kotlin.math.min - -actual class PlatformReader(inputStream: InputStream) { - private var inputStream: BufferedReader? = null - - init { - this.inputStream = inputStream.bufferedReader() - } - - actual fun closeSource() { - inputStream?.close() - } - - private val buffer: Queue = LinkedList() - - actual fun fill(destination: Memory, offset: Int, length: Int): Int { - var readed = 0 - val migrateBuffer = { - for (i in 0 until min(length, buffer.size)) { - destination.storeAt(offset + i, buffer.poll()) - readed += 1 - } - } - when { - length < buffer.size -> { - println("length < buffer.size") - migrateBuffer() - } - else -> { - inputStream?.readLine()?.apply { - buffer.addAll("$this\n".toByteArray(Charsets.UTF_8).toList()) - } - migrateBuffer() - } - } - return readed - } -} diff --git a/src/jvmMain/kotlin/com/kgit2/io/PlatformWriter.kt b/src/jvmMain/kotlin/com/kgit2/io/PlatformWriter.kt deleted file mode 100644 index dd1e314..0000000 --- a/src/jvmMain/kotlin/com/kgit2/io/PlatformWriter.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.kgit2.io - -import io.ktor.utils.io.bits.* -import java.io.OutputStream - -actual class PlatformWriter(outputStream: OutputStream) { - var outputStream: OutputStream? = outputStream - - actual fun flush(source: Memory, offset: Int, length: Int) { - val buffer = ByteArray(length) - source.copyTo(buffer, offset, length, 0) - outputStream?.write(buffer) - outputStream?.flush() - } - - actual fun close() { - outputStream?.close() - } -} diff --git a/src/jvmMain/kotlin/com/kgit2/process/Child.kt b/src/jvmMain/kotlin/com/kgit2/process/Child.kt deleted file mode 100644 index 34a3954..0000000 --- a/src/jvmMain/kotlin/com/kgit2/process/Child.kt +++ /dev/null @@ -1,188 +0,0 @@ -package com.kgit2.process - -import com.kgit2.io.PlatformReader -import com.kgit2.io.PlatformWriter -import com.kgit2.io.Reader -import com.kgit2.io.Writer -import io.ktor.utils.io.errors.* -import java.io.File - -actual class Child actual constructor( - actual val command: String, - actual val args: List, - actual val envs: Map, - actual val cwd: String?, - actual val stdin: Stdio, - actual val stdout: Stdio, - actual val stderr: Stdio, -) { - actual var id: Int? = null - - private var process: Process? = null - - private var stdinWriter: Writer? = null - private var stdoutReader: Reader? = null - private var stderrReader: Reader? = null - - actual fun getChildStdin(): Writer? { - return when (stdin) { - Stdio.Inherit, Stdio.Null -> null - Stdio.Pipe -> stdinWriter - } - } - - actual fun getChildStdout(): Reader? { - return when (stdout) { - Stdio.Inherit, Stdio.Null -> null - Stdio.Pipe -> stdoutReader - } - } - - actual fun getChildStderr(): Reader? { - return when (stderr) { - Stdio.Inherit, Stdio.Null -> null - Stdio.Pipe -> stderrReader - } - } - - @Throws(IOException::class) - actual fun start(options: ChildOptions) { - val processBuilder = ProcessBuilder(command, *args.toTypedArray()) - cwd?.let { processBuilder.directory(File(it)) } - redirectStdio(processBuilder) - runCatching { - process = processBuilder.start() - }.onFailure { - throw IOException(it) - } - this.id = process?.pid()?.toInt() - this.stdinWriter = createWriter(stdin, process!!) - this.stdoutReader = createReader(stdout, process!!) - this.stderrReader = createErrorReader(stderr, process!!) - } - - @Throws(IOException::class) - actual fun wait(): ChildExitStatus { - val exitCode = try { - stdinWriter?.close() - val exitCode = process!!.waitFor() - stdoutReader?.close() - stderrReader?.close() - exitCode - } catch (e: InterruptedException) { - 0x7F - } catch (e: java.io.IOException) { - throw IOException(e) - } - return ChildExitStatus(exitCode) - } - - @Throws(IOException::class) - actual fun waitWithOutput(): String? { - return if (stdout != Stdio.Pipe) { - stdinWriter?.close() - process!!.waitFor() - stderrReader?.close() - null - } else { - stdinWriter?.close() - process!!.waitFor() - val output = StringBuilder() - val reader = stdoutReader!! - while (!reader.endOfInput) { - output.append(reader.readText()) - } - stdoutReader?.close() - stderrReader?.close() - output.toString() - } - } - - actual fun kill() { - process?.destroy() - } - - private fun redirectStdio(processBuilder: ProcessBuilder) { - when (stdin) { - Stdio.Inherit -> { - processBuilder.redirectInput(ProcessBuilder.Redirect.INHERIT) - } - - Stdio.Null -> { - processBuilder.redirectInput(ProcessBuilder.Redirect.DISCARD) - } - - Stdio.Pipe -> { - processBuilder.redirectInput(ProcessBuilder.Redirect.PIPE) - } - } - when (stdout) { - Stdio.Inherit -> { - processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT) - } - - Stdio.Null -> { - processBuilder.redirectOutput(ProcessBuilder.Redirect.DISCARD) - } - - Stdio.Pipe -> { - processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE) - } - } - when (stderr) { - Stdio.Inherit -> { - processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT) - } - - Stdio.Null -> { - processBuilder.redirectError(ProcessBuilder.Redirect.DISCARD) - } - - Stdio.Pipe -> { - processBuilder.redirectError(ProcessBuilder.Redirect.PIPE) - } - } - } - - override fun toString(): String { - return "Child(command='$command', args=$args, envs=$envs, cwd=$cwd, stdin=$stdin, stdout=$stdout, stderr=$stderr, id=$id, process=$process)" - } - - actual fun prompt(): String { - return "$command ${args.joinToString(" ")}" - } - - companion object { - private fun createWriter(stdio: Stdio, process: Process): Writer? { - return when (stdio) { - Stdio.Inherit, Stdio.Null -> null - Stdio.Pipe -> { - val outputStream = process.outputStream - Writer(PlatformWriter(outputStream)) - } - } - } - - private fun createReader(stdio: Stdio, process: Process): Reader? { - return when (stdio) { - Stdio.Inherit, Stdio.Null -> null - Stdio.Pipe -> { - val inputStream = process.inputStream - Reader(PlatformReader(inputStream)) - } - } - } - - private fun createErrorReader(stdio: Stdio, process: Process): Reader? { - return when (stdio) { - Stdio.Inherit, Stdio.Null -> null - Stdio.Pipe -> { - val errorStream = process.errorStream - Reader(PlatformReader(errorStream)) - } - } - } - } - - -} diff --git a/src/jvmTest/kotlin/process/CommandTest.jvm.kt b/src/jvmTest/kotlin/process/CommandTest.jvm.kt deleted file mode 100644 index fae0fbe..0000000 --- a/src/jvmTest/kotlin/process/CommandTest.jvm.kt +++ /dev/null @@ -1,64 +0,0 @@ -package process - -import com.kgit2.process.Command -import com.kgit2.process.Stdio -import java.util.* -import kotlin.test.assertEquals - -enum class OS { - WIN, - MAC, - LINUX, -} - -val osName = System.getProperty("os.name").lowercase(Locale.getDefault()) -val os: OS? = if (osName.contains("win")) { - OS.WIN -} else if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) { - OS.LINUX -} else if (osName.contains("mac")) { - OS.MAC -} else null - -actual val eko: String = - when (os) { - OS.WIN -> "eko/target/release/eko.exe" - else -> "eko/target/release/eko" - } - -// actual val subCommand: String = -// when (os) { -// OS.WIN -> "sub_command/build/install/sub_command/bin/sub_command.bat" -// else -> "sub_command/build/install/sub_command/bin/sub_command" -// } - -actual fun shellTest() { - when (os) { - OS.WIN -> Unit - else -> { - val output = Command("sh") - .args("-c", "f() { echo username=a; echo password=b; }; f get") - .stdout(Stdio.Pipe) - .spawn() - .waitWithOutput() - assertEquals("username=a\npassword=b\n", output) - } - } -} - -actual fun envVar(key: String): String? { - return System.getenv(key) -} - -actual fun homeDir(): String? { - return envVar("HOME") -} - -actual fun pwd(): Command { - return when (os) { - OS.WIN -> Command("chdir") - OS.MAC -> Command("pwd") - OS.LINUX -> Command("pwd") - null -> throw Exception("unknown os") - } -} diff --git a/src/mingwX64Main/kotlin/com/kgit2/process/Child.kt b/src/mingwX64Main/kotlin/com/kgit2/process/Child.kt deleted file mode 100644 index ffb6edc..0000000 --- a/src/mingwX64Main/kotlin/com/kgit2/process/Child.kt +++ /dev/null @@ -1,337 +0,0 @@ -package com.kgit2.process - -import com.kgit2.io.PlatformReader -import com.kgit2.io.PlatformWriter -import com.kgit2.io.Reader -import com.kgit2.io.Writer -import io.ktor.utils.io.errors.* -import kotlinx.cinterop.Arena -import kotlinx.cinterop.ByteVar -import kotlinx.cinterop.CArrayPointer -import kotlinx.cinterop.CPointer -import kotlinx.cinterop.CValue -import kotlinx.cinterop.MemScope -import kotlinx.cinterop.UIntVar -import kotlinx.cinterop.UShortVar -import kotlinx.cinterop.alloc -import kotlinx.cinterop.allocArray -import kotlinx.cinterop.cValue -import kotlinx.cinterop.convert -import kotlinx.cinterop.invoke -import kotlinx.cinterop.memScoped -import kotlinx.cinterop.ptr -import kotlinx.cinterop.reinterpret -import kotlinx.cinterop.set -import kotlinx.cinterop.sizeOf -import kotlinx.cinterop.toKString -import kotlinx.cinterop.value -import platform.posix.FILE -import platform.posix._open_osfhandle -import platform.posix.fgets -import platform.posix.intptr_tVar -import platform.windows.CloseHandle -import platform.windows.CreatePipe -import platform.windows.CreateProcess -import platform.windows.GetExitCodeProcess -import platform.windows.GetLastError -import platform.windows.HANDLEVar -import platform.windows.HANDLE_FLAG_INHERIT -import platform.windows.INFINITE -import platform.windows.PROCESS_INFORMATION -import platform.windows.SECURITY_ATTRIBUTES -import platform.windows.STARTF_USESTDHANDLES -import platform.windows.STARTUPINFO -import platform.windows.SetHandleInformation -import platform.windows.WaitForSingleObject - -actual class Child actual constructor( - actual val command: String, - actual val args: List, - actual val envs: Map, - actual val cwd: String?, - actual val stdin: Stdio, - actual val stdout: Stdio, - actual val stderr: Stdio -) { - actual var id: Int? = null - - private var stdinWriter: Writer? = null - private var stdoutReader: Reader? = null - private var stderrReader: Reader? = null - - private val memory = Arena() - private var processInformation = memory.alloc() - - actual fun getChildStdin(): Writer? { - return when (stdin) { - Stdio.Inherit, Stdio.Null -> null - Stdio.Pipe -> stdinWriter - } - } - - actual fun getChildStdout(): Reader? { - return when (stdout) { - Stdio.Inherit, Stdio.Null -> null - Stdio.Pipe -> stdoutReader - } - } - - actual fun getChildStderr(): Reader? { - return when (stderr) { - Stdio.Inherit, Stdio.Null -> null - Stdio.Pipe -> stderrReader - } - } - - @Throws(IOException::class) - actual fun start(options: ChildOptions): Unit = memScoped { - val securityAttribute = createSecurityAttribute() - val pipes = createPipe(securityAttribute.ptr) - val startupInformation = createStartUpInformation(pipes) - val cmdLine = createCMDLine(this) - val cwd = createCurrentDirectory(this) - // create child process - val success = CreateProcess!!.invoke( - null, - cmdLine, - null, - null, - 1, - 0u, - null, - cwd, - startupInformation.ptr, - processInformation.ptr - ) - if (success == 0) { - val errorCode = GetLastError() - throw IOException("CreateProcess failed: $errorCode") - } - redirectPipeHandle(pipes) - id = processInformation.dwProcessId.toInt() - openFileDescriptor(pipes) - } - - @Throws(IOException::class) - actual fun wait(): ChildExitStatus { - stdinWriter?.close() - WaitForSingleObject(processInformation.hProcess, INFINITE) - val exitCode = memory.alloc() - GetExitCodeProcess(processInformation.hProcess, exitCode.ptr) - stdoutReader?.close() - stderrReader?.close() - val status = ChildExitStatus(exitCode.value.toInt()) - CloseHandle(processInformation.hProcess) - CloseHandle(processInformation.hThread) - memory.clear() - return status - } - - @Throws(IOException::class) - actual fun waitWithOutput(): String? { - return if (stdout != Stdio.Pipe) { - stdinWriter?.close() - val exitCode = memory.alloc() - GetExitCodeProcess(processInformation.hProcess, exitCode.ptr) - CloseHandle(processInformation.hProcess) - CloseHandle(processInformation.hThread) - memory.clear() - null - } else { - stdinWriter?.close() - val output = StringBuilder() - val reader = stdoutReader!! - while (!reader.endOfInput) { - output.append(reader.readText()) - } - WaitForSingleObject(processInformation.hProcess, INFINITE) - stdoutReader?.close() - stderrReader?.close() - CloseHandle(processInformation.hProcess) - CloseHandle(processInformation.hThread) - memory.clear() - output.toString() - } - } - - actual fun kill() { - } - - actual fun prompt(): String { - TODO("Not yet implemented") - } - - private fun createSecurityAttribute(): CValue { - return cValue() { - nLength = sizeOf().convert() - bInheritHandle = 1 - lpSecurityDescriptor = null - } - } - - private fun createPipe(saAttr: CPointer): CreatePipeResult { - val pipes = CreatePipeResult() - when (stdin) { - Stdio.Pipe, Stdio.Null -> { - pipes.stdinPipeReaderHandle = memory.alloc() - pipes.stdinPipeWriterHandle = memory.alloc() - // first param is read handle, second param is write handle - CreatePipe(pipes.stdinPipeReaderHandle!!.ptr, pipes.stdinPipeWriterHandle!!.ptr, saAttr, 0u) - // set handle information for parent process - SetHandleInformation(pipes.stdinPipeWriterHandle!!.value, HANDLE_FLAG_INHERIT.convert(), 0u) - } - else -> Unit - } - when (stdout) { - Stdio.Pipe, Stdio.Null -> { - pipes.stdoutPipeReaderHandle = memory.alloc() - pipes.stdoutPipeWriterHandle = memory.alloc() - CreatePipe(pipes.stdoutPipeReaderHandle!!.ptr, pipes.stdoutPipeWriterHandle!!.ptr, saAttr, 0u) - // set handle information for parent process - SetHandleInformation(pipes.stdoutPipeReaderHandle!!.value, HANDLE_FLAG_INHERIT.convert(), 0u) - } - else -> Unit - } - when (stderr) { - Stdio.Pipe, Stdio.Null -> { - pipes.stderrPipeReaderHandle = memory.alloc() - pipes.stderrPipeWriterHandle = memory.alloc() - CreatePipe(pipes.stderrPipeReaderHandle!!.ptr, pipes.stderrPipeWriterHandle!!.ptr, saAttr, 0u) - // set handle information for parent process - SetHandleInformation(pipes.stderrPipeReaderHandle!!.value, HANDLE_FLAG_INHERIT.convert(), 0u) - } - else -> Unit - } - return pipes - } - - private fun createStartUpInformation(pipes: CreatePipeResult): CValue { - return cValue { - cb = sizeOf().convert() - pipes.stdinPipeReaderHandle?.let { - hStdInput = it.value - } - pipes.stdoutPipeWriterHandle?.let { - hStdOutput = it.value - } - pipes.stderrPipeWriterHandle?.let { - hStdError = it.value - } - if (!(stdin == Stdio.Inherit && stdout == Stdio.Inherit && stderr == Stdio.Inherit)) { - dwFlags = dwFlags or STARTF_USESTDHANDLES.convert() - } - } - } - - private fun createCMDLine(memory: MemScope): CArrayPointer { - val cmdLineString = listOf(command, *args.toTypedArray()).joinToString(" ") - val cmdLine = memory.allocArray(cmdLineString.length.convert()) - cmdLineString.forEachIndexed { index, c -> - cmdLine[index] = c.code.toUShort() - } - return cmdLine - } - - private fun createCurrentDirectory(memory: MemScope): CPointer? { - return cwd?.let { - val currentDirectory = memory.allocArray(it.length.convert()) - it.forEachIndexed { index, c -> - currentDirectory[index] = c.code.toUShort() - } - currentDirectory - } - } - - private fun redirectPipeHandle(pipes: CreatePipeResult) { - when (stdin) { - Stdio.Pipe, Stdio.Null -> CloseHandle(pipes.stdinPipeReaderHandle!!.value) - Stdio.Inherit -> Unit - } - - when (stdout) { - Stdio.Pipe, Stdio.Null -> CloseHandle(pipes.stdoutPipeWriterHandle!!.value) - Stdio.Inherit -> Unit - } - - when (stderr) { - Stdio.Pipe, Stdio.Null -> CloseHandle(pipes.stderrPipeWriterHandle!!.value) - Stdio.Inherit -> Unit - } - } - - private fun openFileDescriptor(pipes: CreatePipeResult) { - when (stdin) { - Stdio.Pipe -> { - if (pipes.stdinPipeWriterHandle != null) { - val fd = _open_osfhandle(pipes.stdinPipeWriterHandle!!.reinterpret().value, 0x0001) - val file = fdopen(fd, "w") - stdinWriter = Writer(PlatformWriter(file)) - } - } - else -> Unit - } - - when (stdout) { - Stdio.Pipe -> { - if (pipes.stdoutPipeReaderHandle != null) { - val fd = _open_osfhandle(pipes.stdoutPipeReaderHandle!!.reinterpret().value, 0x0000) - val file = fdopen(fd, "r") - stdoutReader = Reader(PlatformReader(file)) - } - } - else -> Unit - } - - when (stderr) { - Stdio.Pipe -> { - if (pipes.stderrPipeReaderHandle != null) { - val fd = _open_osfhandle(pipes.stderrPipeReaderHandle!!.reinterpret().value, 0x0000) - val file = fdopen(fd, "r") - stderrReader = Reader(PlatformReader(file)) - } - } - else -> Unit - } - } - - @Throws(IOException::class) - private fun fdopen(fileDescriptor: Int, mode: String): CPointer { - return when (val file = platform.posix.fdopen(fileDescriptor, mode)) { - null -> throw IOException("Invalid mode.") - else -> file - } - } - - // just for debug step by step - private fun readFromFD(stdoutReader: HANDLEVar) { - val fd = _open_osfhandle(stdoutReader.reinterpret().value, 0) - println("fd: $fd") - val file = platform.posix.fdopen(fd, "r") - memScoped { - val buf = allocArray(4096) - while (true) { - val result = fgets(buf, 4096, file) - if (result != null) { - print(result.toKString()) - } else { - break - } - } - } - } -} - -data class CreatePipeResult( - // child process read from here for stdin - var stdinPipeReaderHandle: HANDLEVar? = null, - // parent process write to here for stdin - var stdinPipeWriterHandle: HANDLEVar? = null, - // parent process read from here for stdout - var stdoutPipeReaderHandle: HANDLEVar? = null, - // parent process write to here for stdout - var stdoutPipeWriterHandle: HANDLEVar? = null, - // parent process read from here for stderr - var stderrPipeReaderHandle: HANDLEVar? = null, - // parent process write to here for stderr - var stderrPipeWriterHandle: HANDLEVar? = null, -) diff --git a/src/mingwX64Test/kotlin/process/CommandTest.mingwX64.kt b/src/mingwX64Test/kotlin/process/CommandTest.mingwX64.kt deleted file mode 100644 index 4af4d2c..0000000 --- a/src/mingwX64Test/kotlin/process/CommandTest.mingwX64.kt +++ /dev/null @@ -1,42 +0,0 @@ -package process - -import com.kgit2.process.Command -import kotlinx.cinterop.UShortVar -import kotlinx.cinterop.allocArray -import kotlinx.cinterop.convert -import kotlinx.cinterop.get -import kotlinx.cinterop.invoke -import kotlinx.cinterop.memScoped -import kotlinx.cinterop.set -import platform.windows.GetEnvironmentVariable - -actual val eko: String = "eko/target/release/eko" - -actual fun shellTest() {} - -actual fun envVar(key: String): String? = memScoped { - val lpSize: UInt = 10240u - val lpBuffer = allocArray(10240) - val lpName = allocArray(key.length.convert()) - key.forEachIndexed { index, c -> - lpName[index] = c.code.toUShort() - } - val size = GetEnvironmentVariable!!.invoke(lpName, lpBuffer, lpSize) - if (size == 0u) { - return null - } else { - val buffer = CharArray(size.toInt()) - for (i in 0 until size.toInt()) { - buffer[i] = lpBuffer[i].toInt().toChar() - } - return buffer.joinToString("") - } -} - -actual fun homeDir(): String? { - return envVar("userprofile") -} - -actual fun pwd(): Command { - return Command("chdir") -} diff --git a/src/mingwX64Test/resources/sub_command/.idea/.gitignore b/src/mingwX64Test/resources/sub_command/.idea/.gitignore deleted file mode 100644 index 35410ca..0000000 --- a/src/mingwX64Test/resources/sub_command/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# 默认忽略的文件 -/shelf/ -/workspace.xml -# 基于编辑器的 HTTP 客户端请求 -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/src/nativeInterop/cinterop/linuxarm64.def b/src/nativeInterop/cinterop/linuxarm64.def new file mode 100644 index 0000000..01e441f --- /dev/null +++ b/src/nativeInterop/cinterop/linuxarm64.def @@ -0,0 +1,4 @@ +headers = kommand_core.h +staticLibraries = libkommand_core.a +compilerOpts = -Ikommand_core +libraryPaths = kommand_core/target/aarch64-unknown-linux-gnu/release diff --git a/src/nativeInterop/cinterop/linuxx64.def b/src/nativeInterop/cinterop/linuxx64.def new file mode 100644 index 0000000..8c44657 --- /dev/null +++ b/src/nativeInterop/cinterop/linuxx64.def @@ -0,0 +1,4 @@ +headers = kommand_core.h +staticLibraries = libkommand_core.a +compilerOpts = -Ikommand_core +libraryPaths = kommand_core/target/x86_64-unknown-linux-gnu/release diff --git a/src/nativeInterop/cinterop/macos.def b/src/nativeInterop/cinterop/macos.def new file mode 100644 index 0000000..73c15a5 --- /dev/null +++ b/src/nativeInterop/cinterop/macos.def @@ -0,0 +1,4 @@ +headers = kommand_core.h +staticLibraries = libkommand_core.a +compilerOpts = -Ikommand_core +libraryPaths = kommand_core/target/universal-apple-darwin/release diff --git a/src/nativeInterop/cinterop/mingw64.def b/src/nativeInterop/cinterop/mingw64.def new file mode 100644 index 0000000..91d8abc --- /dev/null +++ b/src/nativeInterop/cinterop/mingw64.def @@ -0,0 +1,5 @@ +headers = kommand_core.h +staticLibraries = libkommand_core.a +compilerOpts = -Ikommand_core +linkerOpts = -static -lws2_32 -lbcrypt -luserenv -lntdll -v +libraryPaths = kommand_core/target/x86_64-pc-windows-gnu/release diff --git a/src/nativeMain/kotlin/com/kgit2/Child.native.kt b/src/nativeMain/kotlin/com/kgit2/Child.native.kt new file mode 100644 index 0000000..d4bb787 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/Child.native.kt @@ -0,0 +1,27 @@ +package com.kgit2 + +import kommand_core.VoidResult +import kotlinx.cinterop.COpaquePointer +import kotlinx.cinterop.CValue +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed + +actual class Child( + private val inner: COpaquePointer? +) { + + companion object { + @Throws(KommandException::class) + fun from(result: CValue): Child = memScoped { + if (result.ptr.pointed.ok != null) { + return Child(result.ptr.pointed.ok) + } else { + println(result.ptr.pointed.error_type) + throw KommandException( + result.ptr.pointed.err?.asString(), + result.ptr.pointed.error_type.to() + ) + } + } + } +} diff --git a/src/nativeMain/kotlin/com/kgit2/Command.native.kt b/src/nativeMain/kotlin/com/kgit2/Command.native.kt new file mode 100644 index 0000000..a555b9e --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/Command.native.kt @@ -0,0 +1,110 @@ +package com.kgit2 + +import kommand_core.* +import kotlinx.cinterop.COpaquePointer +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlin.native.ref.createCleaner + +actual class Command( + actual val command: String, + private val inner: COpaquePointer?, +) { + actual constructor(command: String) : this(command, new_command(command)) + + private val cleaner = createCleaner(inner) { + drop_command(inner) + } + + override fun toString(): String { + return display_command(inner)?.asString() ?: "null" + } + + actual fun debugString(): String { + return debug_command(inner)?.asString() ?: "null" + } + + actual fun arg(arg: String): Command { + arg_command(inner, arg) + return this + } + + actual fun args(args: List): Command { + for (arg in args) { + arg_command(inner, arg) + } + return this + } + + actual fun env(key: String, value: String): Command { + env_command(inner, key, value) + return this + } + + actual fun envs(envs: Map): Command { + for ((key, value) in envs) { + env_command(inner, key, value) + } + return this + } + + actual fun removeEnv(key: String): Command { + remove_env_command(inner, key) + return this + } + + actual fun envClear(): Command { + env_clear_command(inner) + return this + } + + actual fun cwd(dir: String): Command { + current_dir_command(inner, dir) + return this + } + + actual fun stdin(stdio: Stdio): Command { + stdin_command(inner, stdio.to()) + return this + } + + actual fun stdout(stdio: Stdio): Command { + stdin_command(inner, stdio.to()) + return this + } + + actual fun stderr(stdio: Stdio): Command { + stdin_command(inner, stdio.to()) + return this + } + + @Throws(KommandException::class) + actual fun spawn(): Child { + val result = spawn_command(inner) + return run { Child.from(result) } + } + + @Throws(KommandException::class) + actual fun output(): Output { + val result = output_command(inner) + return run { Output.from(result) } + } + + actual fun status(): Int? = memScoped { + val result = status_command(inner) + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + result.ptr.pointed.ok + } + } +} + +fun Stdio.to(): kommand_core.Stdio { + return when (this) { + Stdio.Inherit -> kommand_core.Stdio.Inherit + Stdio.Pipe -> kommand_core.Stdio.Pipe + Stdio.Null -> kommand_core.Stdio.Null + } +} diff --git a/src/nativeMain/kotlin/com/kgit2/Exception.kt b/src/nativeMain/kotlin/com/kgit2/Exception.kt new file mode 100644 index 0000000..e3fcd89 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/Exception.kt @@ -0,0 +1,10 @@ +package com.kgit2 + +fun kommand_core.ErrorType.to(): ErrorType { + return when (this) { + kommand_core.ErrorType.None -> ErrorType.None + kommand_core.ErrorType.Io -> ErrorType.IO + kommand_core.ErrorType.Utf8 -> ErrorType.Utf8 + kommand_core.ErrorType.Unknown -> ErrorType.Unknown + } +} diff --git a/src/nativeMain/kotlin/com/kgit2/FFiUtil.kt b/src/nativeMain/kotlin/com/kgit2/FFiUtil.kt new file mode 100644 index 0000000..a459264 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/FFiUtil.kt @@ -0,0 +1,12 @@ +package com.kgit2 + +import kommand_core.drop_string +import kotlinx.cinterop.ByteVar +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.toKString + +inline fun CPointer.asString(): String { + val result = this.toKString() + drop_string(this) + return result +} diff --git a/src/nativeMain/kotlin/com/kgit2/Output.kt b/src/nativeMain/kotlin/com/kgit2/Output.kt new file mode 100644 index 0000000..1c8a267 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/Output.kt @@ -0,0 +1,24 @@ +package com.kgit2 + +import kommand_core.drop_output +import kommand_core.into_output +import kotlinx.cinterop.CValue +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.toKString + +fun Output.Companion.from(result: CValue): Output = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + val output = into_output(result.ptr.pointed.ok) + val newOutput = Output( + output.getPointer(memScope).pointed.exit_code, + output.getPointer(memScope).pointed.stdout_content?.toKString(), + output.getPointer(memScope).pointed.stderr_content?.toKString(), + ) + drop_output(output) + newOutput + } +} diff --git a/src/posixMain/kotlin/com/kgit2/io/PlatformReader.kt b/src/posixMain/kotlin/com/kgit2/io/PlatformReader.kt deleted file mode 100644 index 9baa92f..0000000 --- a/src/posixMain/kotlin/com/kgit2/io/PlatformReader.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.kgit2.io - -import io.ktor.utils.io.bits.* -import io.ktor.utils.io.charsets.* -import io.ktor.utils.io.core.* -import kotlinx.cinterop.ByteVar -import kotlinx.cinterop.CPointer -import kotlinx.cinterop.allocArray -import kotlinx.cinterop.memScoped -import kotlinx.cinterop.toKString -import platform.posix.FILE -import platform.posix.fclose -import platform.posix.fgets -import kotlin.math.min - -actual class PlatformReader(file: CPointer) { - private var file: CPointer? = file - private val queue: ArrayDeque = ArrayDeque() - - actual fun closeSource() { - fclose(file) - } - - actual fun fill(destination: Memory, offset: Int, length: Int): Int { - return memScoped { - var readed = 0 - val migrateBuffer = { - for (i in 0 until min(length, queue.size)) { - destination.storeAt(offset + i, queue.removeFirst()) - readed += 1 - } - } - when { - length < queue.size -> { - migrateBuffer() - } - else -> { - val buffer = allocArray(length) - val line = fgets(buffer, length, file) - line?.toKString()?.apply { - queue.addAll(this.toByteArray(Charsets.UTF_8).toList()) - migrateBuffer() - } - } - } - readed - } - } -} diff --git a/src/posixMain/kotlin/com/kgit2/io/PlatformWriter.kt b/src/posixMain/kotlin/com/kgit2/io/PlatformWriter.kt deleted file mode 100644 index 59af908..0000000 --- a/src/posixMain/kotlin/com/kgit2/io/PlatformWriter.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.kgit2.io - -import io.ktor.utils.io.bits.* -import kotlinx.cinterop.CPointer -import kotlinx.cinterop.addressOf -import kotlinx.cinterop.convert -import kotlinx.cinterop.usePinned -import platform.posix.FILE -import platform.posix.fclose -import platform.posix.fflush -import platform.posix.fwrite - -actual class PlatformWriter(file: CPointer) { - var file: CPointer? = file - - actual fun flush(source: Memory, offset: Int, length: Int) { - val buffer = ByteArray(length) - source.copyTo(buffer, offset, length, 0) - buffer.usePinned { - fwrite(it.addressOf(0), 1u, length.convert(), file) - } - fflush(file) - } - - actual fun close() { - fclose(file) - } -} diff --git a/src/posixTest/kotlin/process/CommandTest.posix.kt b/src/posixTest/kotlin/process/CommandTest.posix.kt deleted file mode 100644 index 571c0a4..0000000 --- a/src/posixTest/kotlin/process/CommandTest.posix.kt +++ /dev/null @@ -1,32 +0,0 @@ -package process - -import com.kgit2.process.Command -import com.kgit2.process.Stdio -import kotlinx.cinterop.toKStringFromUtf8 -import platform.posix.getenv -import kotlin.test.assertEquals - -actual val eko: String = "eko/target/release/eko" - -// actual val subCommand: String = "sub_command/build/install/sub_command/bin/sub_command" - -actual fun shellTest() { - val output = Command("sh") - .args("-c", "f() { echo username=a; echo password=b; }; f get") - .stdout(Stdio.Pipe) - .spawn() - .waitWithOutput() - assertEquals("username=a\npassword=b\n", output) -} - -actual fun envVar(key: String): String? { - return getenv(key)?.toKStringFromUtf8() -} - -actual fun homeDir(): String? { - return envVar("HOME") -} - -actual fun pwd(): Command { - return Command("pwd") -} diff --git a/src/unixLikeMain/kotlin/com/kgit2/process/Child.kt b/src/unixLikeMain/kotlin/com/kgit2/process/Child.kt deleted file mode 100644 index b91513f..0000000 --- a/src/unixLikeMain/kotlin/com/kgit2/process/Child.kt +++ /dev/null @@ -1,230 +0,0 @@ -package com.kgit2.process - -import com.kgit2.io.Reader -import com.kgit2.io.Writer -import com.kgit2.process.Stdio.Inherit -import com.kgit2.process.Stdio.Null -import com.kgit2.process.Stdio.Pipe -import io.ktor.utils.io.errors.* -import kotlinx.cinterop.CPointer -import kotlinx.cinterop.memScoped -import platform.posix.FILE -import platform.posix.SIGTERM -import platform.posix.STDERR_FILENO -import platform.posix.STDIN_FILENO -import platform.posix.STDOUT_FILENO - -const val READ_END = 0 -const val WRITE_END = 1 - -actual class Child actual constructor( - actual val command: String, - actual val args: List, - actual val envs: Map, - actual val cwd: String?, - actual val stdin: Stdio, - actual val stdout: Stdio, - actual val stderr: Stdio, -) { - actual var id: Int? = null - - private var stdinWriter: Writer? = null - private var stdoutReader: Reader? = null - private var stderrReader: Reader? = null - - private val stdinPipe = IntArray(2) - private val stdoutPipe = IntArray(2) - private val stderrPipe = IntArray(2) - - private var options = ChildOptions.W_UNTRACED - - actual fun getChildStdin(): Writer? { - return when (stdin) { - Inherit, Null -> null - Pipe -> stdinWriter - } - } - - actual fun getChildStdout(): Reader? { - return when (stdout) { - Inherit, Null -> null - Pipe -> stdoutReader - } - } - - actual fun getChildStderr(): Reader? { - return when (stderr) { - Inherit, Null -> null - Pipe -> stderrReader - } - } - - @Throws(IOException::class) - actual fun start(options: ChildOptions) { - this.options = options - memScoped { - createPipe() - val childPid = Posix.fork() - processChild(childPid) - processLocal(childPid) - } - } - - @Throws(IOException::class) - actual fun wait(): ChildExitStatus { - stdinWriter?.close() - val status = Posix.waitpid(id!!, options.value) - stdoutReader?.close() - stderrReader?.close() - return status - } - - @Throws(IOException::class) - actual fun waitWithOutput(): String? { - return if (stdout != Pipe) { - stdinWriter?.close() - Posix.waitpid(id!!, options.value) - null - } else { - stdinWriter?.close() - Posix.waitpid(id!!, options.value) - val output = StringBuilder() - val reader = stdoutReader!! - while (!reader.endOfInput) { - output.append(reader.readText()) - } - stdoutReader?.close() - stderrReader?.close() - output.toString() - } - } - - @Throws(IOException::class) - actual fun kill() { - assert(id != null) - Posix.kill(id!!, SIGTERM) - } - - @Throws(IOException::class) - private fun processChild(childPid: Int) { - if (childPid == 0) { - redirectFileDescriptor() - if (cwd != null) { - Posix.chdir(cwd) - } - val commands = listOf(command, *args.toTypedArray(), null) - Posix.execvp(commands) - } - } - - private fun processLocal(childPid: Int) { - if (childPid > 0) { - this@Child.id = childPid - val (stdinFile, stdoutFile, stderrFile) = openFileDescriptor() - if (stdinFile != null) { - this.stdinWriter = Posix.createWriter(stdinFile) - } - if (stdoutFile != null) { - this.stdoutReader = Posix.createReader(stdoutFile) - } - if (stderrFile != null) { - this.stderrReader = Posix.createReader(stderrFile) - } - } - } - - private fun createPipe() { - val pipes = mutableListOf>() - when (stdin) { - Pipe -> stdinPipe - else -> null - }?.also { pipes.add(it to "stdin") } - when (stdout) { - Pipe -> stdoutPipe - else -> null - }?.also { pipes.add(it to "stdout") } - when (stderr) { - Pipe -> stderrPipe - else -> null - }?.also { pipes.add(it to "stderr") } - pipes.forEach { - Posix.pipe(it.first) - } - } - - private fun redirectFileDescriptor() { - when (stdin) { - Null -> { - Posix.close(STDIN_FILENO) - } - - Pipe -> { - Posix.dup2(stdinPipe[READ_END], STDIN_FILENO) - Posix.close(stdinPipe[WRITE_END]) - } - - Inherit -> Unit - } - when (stdout) { - Null -> { - Posix.close(STDOUT_FILENO) - } - - Pipe -> { - Posix.dup2(stdoutPipe[WRITE_END], STDOUT_FILENO) - Posix.close(stdoutPipe[READ_END]) - } - - Inherit -> Unit - } - when (stderr) { - Null -> { - Posix.close(STDERR_FILENO) - } - - Pipe -> { - Posix.dup2(stderrPipe[WRITE_END], STDERR_FILENO) - Posix.close(stderrPipe[READ_END]) - } - - Inherit -> Unit - } - } - - @Throws(IOException::class) - private fun openFileDescriptor(): Triple?, CPointer?, CPointer?> { - val stdinFile = when (stdin) { - Pipe -> { - Posix.close(stdinPipe[READ_END]) - Posix.fdopen(stdinPipe[WRITE_END], "w") - } - - else -> null - } - val stdoutFile = when (stdout) { - Pipe -> { - Posix.close(stdoutPipe[WRITE_END]) - Posix.fdopen(stdoutPipe[READ_END], "r") - } - - else -> null - } - val stderrFile = when (stderr) { - Pipe -> { - Posix.close(stderrPipe[WRITE_END]) - Posix.fdopen(stderrPipe[READ_END], "r") - } - - else -> null - } - return Triple(stdinFile, stdoutFile, stderrFile) - } - - override fun toString(): String { - return "Child(command='$command', args=$args, envs=$envs, cwd=$cwd, stdin=$stdin, stdout=$stdout, stderr=$stderr, id=$id, stdinPipe=${stdinPipe.contentToString()}, stdoutPipe=${stdoutPipe.contentToString()}, stderrPipe=${stderrPipe.contentToString()}, options=$options)" - } - - actual fun prompt(): String { - return "$command ${args.joinToString(" ")}" - } -} diff --git a/src/unixLikeMain/kotlin/com/kgit2/process/Posix.kt b/src/unixLikeMain/kotlin/com/kgit2/process/Posix.kt deleted file mode 100644 index bdb002d..0000000 --- a/src/unixLikeMain/kotlin/com/kgit2/process/Posix.kt +++ /dev/null @@ -1,189 +0,0 @@ -package com.kgit2.process - -import com.kgit2.io.PlatformReader -import com.kgit2.io.PlatformWriter -import com.kgit2.io.Reader -import com.kgit2.io.Writer -import io.ktor.utils.io.errors.* -import kotlinx.cinterop.CPointer -import kotlinx.cinterop.IntVar -import kotlinx.cinterop.addressOf -import kotlinx.cinterop.alloc -import kotlinx.cinterop.allocArrayOf -import kotlinx.cinterop.cstr -import kotlinx.cinterop.memScoped -import kotlinx.cinterop.ptr -import kotlinx.cinterop.usePinned -import kotlinx.cinterop.value -import platform.posix.E2BIG -import platform.posix.EACCES -import platform.posix.EAGAIN -import platform.posix.EBADF -import platform.posix.EBUSY -import platform.posix.ECHILD -import platform.posix.EFAULT -import platform.posix.EINTR -import platform.posix.EINVAL -import platform.posix.EIO -import platform.posix.EISDIR -import platform.posix.ELOOP -import platform.posix.EMFILE -import platform.posix.ENAMETOOLONG -import platform.posix.ENFILE -import platform.posix.ENOENT -import platform.posix.ENOEXEC -import platform.posix.ENOMEM -import platform.posix.ENOSYS -import platform.posix.ENOTDIR -import platform.posix.EPERM -import platform.posix.ESRCH -import platform.posix.ETXTBSY -import platform.posix.FILE -import platform.posix.errno - -object Posix { - fun createWriter(file: CPointer): Writer { - return Writer(PlatformWriter(file)) - } - - fun createReader(file: CPointer): Reader { - return Reader(PlatformReader(file)) - } - - @Throws(IOException::class) - fun pipe(fd: IntArray) { - fd.usePinned { pin -> - when (val result = platform.posix.pipe(pin.addressOf(0))) { - EFAULT -> throw IOException("pipefd is not valid.") - EINVAL -> throw IOException("Invalid flags.") - EMFILE -> throw IOException("Too many open files") - ENFILE -> throw IOException("The system limit on the total number of open files has been reached.") - else -> Unit - } - } - } - - @Throws(IOException::class) - fun close(fileDescriptor: Int) { - when (platform.posix.close(fileDescriptor)) { - EBADF -> throw IOException("fd isn't a valid open file descriptor.") - EINTR -> throw IOException("The close() call was interrupted by a signal.") - EIO -> throw IOException("An I/O error occurred.") - else -> Unit - } - } - - @Throws(IOException::class) - fun dup2(oldFd: Int, newFd: Int) { - when (platform.posix.dup2(oldFd, newFd)) { - EBADF -> throw IOException("oldfd or newfd is not a valid file descriptor.") - EBUSY -> throw IOException("(Linux only) This may be returned by dup2() or dup3() during a race condition with open(2) and dup().") - EINTR -> throw IOException("The dup2() or dup3() call was interrupted by a signal; see signal(7).") - EINVAL -> throw IOException("(dup3()) flags contain an invalid value. Or, oldfd was equal to newfd.") - EMFILE -> throw IOException("The process already has the maximum number of file descriptors open and tried to open a new one.") - else -> Unit - } - } - - @Throws(IOException::class) - fun fork(): Int { - return when (val pid = platform.posix.fork()) { - EAGAIN -> throw IOException("fork() cannot allocate sufficient memory to copy the parent's page tables and allocate a task structure for the child.") - ENOMEM -> throw IOException("fork() failed to allocate the necessary kernel structures because memory is tight.") - ENOSYS -> throw IOException("fork() is not supported on this platform (for example, hardware without a Memory-Management Unit).") - else -> pid - } - } - - @Throws(IOException::class) - fun waitpid(pid: Int, options: Int): ChildExitStatus { - return memScoped { - val statusCode = alloc() - when (val status = platform.posix.waitpid(pid, statusCode.ptr, options)) { - ECHILD -> throw IOException("No child process with the specified pid exists.") - EINTR -> throw IOException("The waitpid() call was interrupted by a signal.") - EINVAL -> throw IOException("The options argument is not valid.") - else -> status - } - ChildExitStatus(statusCode.value) - } - } - - @Throws(IOException::class) - fun kill(pid: Int, signal: Int) { - when (platform.posix.kill(pid, signal)) { - EINVAL -> throw IOException("An invalid signal was specified.") - EPERM -> throw IOException("The process does not have permission to send the signal to any of the target processes.") - ESRCH -> throw IOException("The pid or process group does not exist. Note that an existing process might be a zombie, a process which already committed termination, but has not yet been wait(2)ed for.") - else -> Unit - } - } - - @Throws(IOException::class) - fun fdopen(fileDescriptor: Int, mode: String): CPointer { - return when (val file = platform.posix.fdopen(fileDescriptor, mode)) { - null -> throw IOException("Invalid mode.") - else -> file - } - } - - @Throws(IOException::class) - fun chdir(path: String) { - if (platform.posix.chdir(path) != 0) { - when (platform.posix.errno) { - EACCES -> throw IOException("Search permission is denied for one of the components of path.") - EFAULT -> throw IOException("path points outside your accessible address space.") - EIO -> throw IOException("An I/O error occurred.") - ELOOP -> throw IOException("Too many symbolic links were encountered in resolving path.") - ENAMETOOLONG -> throw IOException("path is too long.") - ENOENT -> throw IOException("The directory specified in path does not exist.") - ENOMEM -> throw IOException("Insufficient kernel memory was available.") - ENOTDIR -> throw IOException("A component of the path prefix is not a directory.") - EBADF -> throw IOException("fd is not a valid file descriptor.") - else -> Unit - } - } - } - - @Throws(IOException::class) - fun execvp(commands: List) { - memScoped { - platform.posix.execvp(commands[0], allocArrayOf(commands.map { it?.cstr?.getPointer(memScope) })) - when (errno) { - E2BIG -> IOException("The total number of bytes in the environment (envp) and argument list (argv) is too large.") - - EACCES -> IOException("The file or a script interpreter is not a regular file.\nOr Execute permission is denied for the file or a script or ELF interpreter.\nOr The file system is mounted noexec.") - - EFAULT -> IOException("filename points outside your accessible address space.") - - EINVAL -> IOException("An ELF executable had more than one PT_INTERP segment (i.e., tried to name more than one interpreter).") - - EIO -> IOException("An I/O error occurred.") - - EISDIR -> IOException("An ELF interpreter was a directory.") - - ELOOP -> IOException("Too many symbolic links were encountered in resolving filename or the name of a script or ELF interpreter.") - - EMFILE -> IOException("The process has the maximum number of files open.") - - ENAMETOOLONG -> IOException("filename is too long.") - - ENFILE -> IOException("The system limit on the total number of open files has been reached.") - - ENOENT -> IOException("The file filename or a script or ELF interpreter does not exist, or a shared library needed for file or interpreter cannot be found.") - - ENOEXEC -> IOException("An executable is not in a recognized format, is for the wrong architecture, or has some other format error that means it cannot be executed.") - - ENOMEM -> IOException("Insufficient kernel memory was available.") - - ENOTDIR -> IOException("A component of the path prefix of filename or a script or ELF interpreter is not a directory.") - - EPERM -> IOException("The file system is mounted nosuid, the user is not the superuser, and the file has the set-user-ID or set-group-ID bit set.") - - ETXTBSY -> IOException("Executable was open for writing by one or more processes.") - - else -> Unit - } - } - } -} diff --git a/src/unixLikeTest/kotlin/process/ProcessTest.kt b/src/unixLikeTest/kotlin/process/ProcessTest.kt deleted file mode 100644 index 1c2e159..0000000 --- a/src/unixLikeTest/kotlin/process/ProcessTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package process - -import com.kgit2.process.Command -import com.kgit2.process.Stdio -import kotlin.test.Test - -class ProcessTest { - // @Test - // fun pingTest() { - // val executor = Command("ping") - // .arg("-c") - // .args("5", "localhost") - // .stdout(Stdio.Pipe) - // .spawn() - // val stdoutReader = executor.getChildStdout()!! - // val lines = mutableListOf() - // stdoutReader.lines().forEach { - // // do something - // println(it) - // lines.add(it) - // } - // val exitStatus = runCatching { - // executor.wait() - // } - // assertTrue(exitStatus.isSuccess) - // } - - @Test - fun pipeTest() { - val child1 = Command("sh") - .args("-c", "unset count; count=0; while ((count < 10)) do ((count += 1));echo from child1:${"$"}count;done") - .stdout(Stdio.Pipe) - .spawn() - val child2 = Command("sh") - .args("-c", "while read line; do echo from child2:${"$"}line; done") - .stdin(Stdio.Pipe) - .stdout(Stdio.Inherit) - .spawn() - val child1StdoutReader = child1.getChildStdout()!! - val child2StdinWriter = child2.getChildStdin()!! - child1StdoutReader.lines().forEach { - child2StdinWriter.appendLine(it) - } - child2StdinWriter.close() - } -} diff --git a/src/unixLikeTest/kotlin/server/FileService.kt b/src/unixLikeTest/kotlin/server/FileService.kt deleted file mode 100644 index 0634980..0000000 --- a/src/unixLikeTest/kotlin/server/FileService.kt +++ /dev/null @@ -1,19 +0,0 @@ -package server - -import io.ktor.server.application.* -import io.ktor.server.cio.* -import io.ktor.server.engine.* -import io.ktor.server.response.* -import io.ktor.server.routing.* - -suspend fun startServer(port: Int, responseText: String): CIOApplicationEngine { - val engine = embeddedServer(CIO, port = port) { - routing { - get("/") { - call.respondText(responseText) - } - } - } - engine.start() - return engine -} diff --git a/sub_command/.gitignore b/sub_command/.gitignore deleted file mode 100644 index de0a616..0000000 --- a/sub_command/.gitignore +++ /dev/null @@ -1,629 +0,0 @@ -# Created by https://www.toptal.com/developers/gitignore/api/jetbrains+all,clion+all,macos,windows,linux,visualstudiocode,visualstudio -# Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains+all,clion+all,macos,windows,linux,visualstudiocode,visualstudio - -### CLion+all ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### CLion+all Patch ### -# Ignore everything but code style settings and run configurations -# that are supposed to be shared within teams. - -.idea/* - -!.idea/codeStyles -!.idea/runConfigurations - -### JetBrains+all ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff - -# AWS User-specific - -# Generated files - -# Sensitive or high-churn files - -# Gradle - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake - -# Mongo Explorer plugin - -# File-based project format - -# IntelliJ - -# mpeltonen/sbt-idea plugin - -# JIRA plugin - -# Cursive Clojure plugin - -# SonarLint plugin - -# Crashlytics plugin (for Android Studio and IntelliJ) - -# Editor-based Rest Client - -# Android studio 3.1+ serialized cache file - -### JetBrains+all Patch ### -# Ignore everything but code style settings and run configurations -# that are supposed to be shared within teams. - - - -### Linux ### -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -### macOS ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### macOS Patch ### -# iCloud generated files -*.icloud - -### VisualStudioCode ### -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/*.code-snippets - -# Local History for Visual Studio Code -.history/ - -# Built Visual Studio Code Extensions -*.vsix - -### VisualStudioCode Patch ### -# Ignore all local history of files -.history -.ionide - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -### VisualStudio ### -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.tlog -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# Visual Studio History (VSHistory) files -.vshistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -*.code-workspace - -# Local History for Visual Studio Code - -# Windows Installer files from build outputs - -# JetBrains Rider -*.sln.iml - -### VisualStudio Patch ### -# Additional files built by Visual Studio - -# End of https://www.toptal.com/developers/gitignore/api/jetbrains+all,clion+all,macos,windows,linux,visualstudiocode,visualstudio diff --git a/sub_command/build.gradle.kts b/sub_command/build.gradle.kts deleted file mode 100644 index 3e54d38..0000000 --- a/sub_command/build.gradle.kts +++ /dev/null @@ -1,12 +0,0 @@ -plugins { - kotlin("jvm") - application -} - -repositories { - mavenCentral() -} - -application { - mainClass.set("MainKt") -} diff --git a/sub_command/src/main/kotlin/Main.kt b/sub_command/src/main/kotlin/Main.kt deleted file mode 100644 index ebc27c7..0000000 --- a/sub_command/src/main/kotlin/Main.kt +++ /dev/null @@ -1,37 +0,0 @@ -import java.util.Scanner - -fun main(args: Array) { - if (args.isEmpty()) { - print("Hello, Kommand!\n") - return - } - when (args[0]) { - "echo" -> echo() - "error" -> error() - "interval" -> interval(if (args.size > 1) args[1].toInt() else null) - else -> System.err.println("Unknown command: ${args[0]}") - } -} - -fun echo() { - val scanner = Scanner(System.`in`) - while (scanner.hasNext()) { - val line = scanner.nextLine() - println(line) - } -} - -fun error() { - val scanner = Scanner(System.`in`) - while (scanner.hasNext()) { - val line = scanner.nextLine() - System.err.println(line) - } -} - -fun interval(count: Int?) { - repeat(count ?: 5) { - println(it) - Thread.sleep(100) - } -} From 2e6a2fa2583751241346d62002ae124c321439cd Mon Sep 17 00:00:00 2001 From: BppleMan Date: Fri, 22 Dec 2023 13:58:18 +0800 Subject: [PATCH 03/22] Implement platform --- build.gradle.kts | 42 +++---------------- src/commonMain/kotlin/com/kgit2/Platform.kt | 12 ++++++ .../kotlin/com/kgit2/PlatformTest.kt | 10 +++++ src/jvmMain/kotlin/com/kgit2/Platform.jvm.kt | 3 ++ .../kotlin/com/kgit2/Platform.linuxArm64.kt | 3 ++ .../kotlin/com/kgit2/Platform.linuxX64.kt | 3 ++ .../kotlin/com/kgit2/Platform.macosArm64.kt | 3 ++ .../kotlin/com/kgit2/Platform.macosX64.kt | 3 ++ .../kotlin/com/kgit2/Platform.mingwX64.kt | 3 ++ 9 files changed, 46 insertions(+), 36 deletions(-) create mode 100644 src/commonMain/kotlin/com/kgit2/Platform.kt create mode 100644 src/commonTest/kotlin/com/kgit2/PlatformTest.kt create mode 100644 src/jvmMain/kotlin/com/kgit2/Platform.jvm.kt create mode 100644 src/linuxArm64Main/kotlin/com/kgit2/Platform.linuxArm64.kt create mode 100644 src/linuxX64Main/kotlin/com/kgit2/Platform.linuxX64.kt create mode 100644 src/macosArm64Main/kotlin/com/kgit2/Platform.macosArm64.kt create mode 100644 src/macosX64Main/kotlin/com/kgit2/Platform.macosX64.kt create mode 100644 src/mingwX64Main/kotlin/com/kgit2/Platform.mingwX64.kt diff --git a/build.gradle.kts b/build.gradle.kts index b71f318..00fccb8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,12 +14,12 @@ group = "com.kgit2" version = "1.2.0" val mainHost = Platform.MACOS_X64 -// for debug -// val mainHost = Platform.MACOS_ARM64 -// val mainHost = Platform.LINUX_X64 -// val mainHost = Platform.LINUX_ARM64 -// val mainHost = Platform.MINGW_X64 val targetPlatform = Platform.valueOf(project.findProperty("targetPlatform")?.toString() ?: "MACOS_X64") +// for debug +// val targetPlatform = Platform.valueOf(project.findProperty("targetPlatform")?.toString() ?: "MACOS_ARM64") +// val targetPlatform = Platform.valueOf(project.findProperty("targetPlatform")?.toString() ?: "LINUX_X64") +// val targetPlatform = Platform.valueOf(project.findProperty("targetPlatform")?.toString() ?: "LINUX_ARM64") +// val targetPlatform = Platform.valueOf(project.findProperty("targetPlatform")?.toString() ?: "MINGW_X64") val ktorIO = "2.3.4" @@ -31,7 +31,7 @@ repositories { kotlin { jvm { compilations.all { - kotlinOptions.jvmTarget = "11" + kotlinOptions.jvmTarget = "17" } withJava() testRuns["test"].executionTask.configure { @@ -100,36 +100,6 @@ kotlin { val targetTest = create("${targetSourceSetName}Test") { dependsOn(commonTest) } - // val macosX64Main by creating { - // dependsOn(commonMain) - // } - // val macosX64Test by creating { - // dependsOn(commonTest) - // } - // val macosArm64Main by creating { - // dependsOn(commonMain) - // } - // val macosArm64Test by creating { - // dependsOn(commonTest) - // } - // val linuxX64Main by creating { - // dependsOn(commonMain) - // } - // val linuxX64Test by creating { - // dependsOn(commonTest) - // } - // val linuxArm64Main by creating { - // dependsOn(commonMain) - // } - // val linuxArm64Test by creating { - // dependsOn(commonTest) - // } - // val mingwX64Main by creating { - // dependsOn(commonMain) - // } - // val mingwX64Test by creating { - // dependsOn(commonTest) - // } val nativeMain by getting { dependsOn(targetMain) diff --git a/src/commonMain/kotlin/com/kgit2/Platform.kt b/src/commonMain/kotlin/com/kgit2/Platform.kt new file mode 100644 index 0000000..cf94baf --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/Platform.kt @@ -0,0 +1,12 @@ +package com.kgit2 + +enum class Platform { + MACOS_X64, + MACOS_ARM64, + LINUX_X64, + LINUX_ARM64, + WINDOWS_X64, + JVM +} + +expect val platform: Platform diff --git a/src/commonTest/kotlin/com/kgit2/PlatformTest.kt b/src/commonTest/kotlin/com/kgit2/PlatformTest.kt new file mode 100644 index 0000000..35c6c52 --- /dev/null +++ b/src/commonTest/kotlin/com/kgit2/PlatformTest.kt @@ -0,0 +1,10 @@ +package com.kgit2 + +import kotlin.test.Test + +class PlatformTest { + @Test + fun platform() { + println(platform) + } +} diff --git a/src/jvmMain/kotlin/com/kgit2/Platform.jvm.kt b/src/jvmMain/kotlin/com/kgit2/Platform.jvm.kt new file mode 100644 index 0000000..3c1d9d3 --- /dev/null +++ b/src/jvmMain/kotlin/com/kgit2/Platform.jvm.kt @@ -0,0 +1,3 @@ +package com.kgit2 + +actual val platform: Platform = Platform.JVM diff --git a/src/linuxArm64Main/kotlin/com/kgit2/Platform.linuxArm64.kt b/src/linuxArm64Main/kotlin/com/kgit2/Platform.linuxArm64.kt new file mode 100644 index 0000000..7428e42 --- /dev/null +++ b/src/linuxArm64Main/kotlin/com/kgit2/Platform.linuxArm64.kt @@ -0,0 +1,3 @@ +package com.kgit2 + +actual val platform: Platform = Platform.LINUX_ARM64 diff --git a/src/linuxX64Main/kotlin/com/kgit2/Platform.linuxX64.kt b/src/linuxX64Main/kotlin/com/kgit2/Platform.linuxX64.kt new file mode 100644 index 0000000..b0e57ab --- /dev/null +++ b/src/linuxX64Main/kotlin/com/kgit2/Platform.linuxX64.kt @@ -0,0 +1,3 @@ +package com.kgit2 + +actual val platform: Platform = Platform.LINUX_X64 diff --git a/src/macosArm64Main/kotlin/com/kgit2/Platform.macosArm64.kt b/src/macosArm64Main/kotlin/com/kgit2/Platform.macosArm64.kt new file mode 100644 index 0000000..4afea33 --- /dev/null +++ b/src/macosArm64Main/kotlin/com/kgit2/Platform.macosArm64.kt @@ -0,0 +1,3 @@ +package com.kgit2 + +actual val platform: Platform = Platform.MACOS_ARM64 diff --git a/src/macosX64Main/kotlin/com/kgit2/Platform.macosX64.kt b/src/macosX64Main/kotlin/com/kgit2/Platform.macosX64.kt new file mode 100644 index 0000000..43ded80 --- /dev/null +++ b/src/macosX64Main/kotlin/com/kgit2/Platform.macosX64.kt @@ -0,0 +1,3 @@ +package com.kgit2 + +actual val platform: Platform = Platform.MACOS_X64 diff --git a/src/mingwX64Main/kotlin/com/kgit2/Platform.mingwX64.kt b/src/mingwX64Main/kotlin/com/kgit2/Platform.mingwX64.kt new file mode 100644 index 0000000..7c9c613 --- /dev/null +++ b/src/mingwX64Main/kotlin/com/kgit2/Platform.mingwX64.kt @@ -0,0 +1,3 @@ +package com.kgit2 + +actual val platform: Platform = Platform.MINGW_X64 From c904ae52915e9bec1d6d4569e6811f544889f74f Mon Sep 17 00:00:00 2001 From: BppleMan Date: Fri, 22 Dec 2023 14:34:32 +0800 Subject: [PATCH 04/22] Organize source set dependent --- build.gradle.kts | 93 +++++++++++-------- gradle.properties | 1 - .../kotlin/com/kgit2/Command.native.kt | 72 +++++++------- src/nativeMain/kotlin/com/kgit2/Exception.kt | 16 ++-- src/nativeMain/kotlin/com/kgit2/Output.kt | 44 ++++----- .../kotlin/com/kgit2/wrapper/Child.kt | 2 + .../kotlin/com/kgit2/wrapper/Command.kt | 36 +++++++ .../kotlin/com/kgit2/wrapper/Output.kt | 2 + 8 files changed, 158 insertions(+), 108 deletions(-) create mode 100644 src/nativeMain/kotlin/com/kgit2/wrapper/Child.kt create mode 100644 src/nativeMain/kotlin/com/kgit2/wrapper/Command.kt create mode 100644 src/nativeMain/kotlin/com/kgit2/wrapper/Output.kt diff --git a/build.gradle.kts b/build.gradle.kts index 00fccb8..d255bf0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,29 +39,40 @@ kotlin { } } - val nativeTarget = when (targetPlatform) { - Platform.MACOS_X64 -> macosX64("native") - Platform.MACOS_ARM64 -> macosArm64("native") - Platform.LINUX_X64 -> linuxX64("native") - Platform.LINUX_ARM64 -> linuxArm64("native") - Platform.MINGW_X64 -> mingwX64("native") - } - - nativeTarget.apply { - compilations.getByName("main") { - cinterops { - val kommandCore by creating { - if (targetPlatform.toString().contains("macos")) { - defFile(project.file("src/nativeInterop/cinterop/macos.def")) - } else { - defFile(project.file("src/nativeInterop/cinterop/${targetPlatform}.def")) + // val nativeTarget = when (targetPlatform) { + // Platform.MACOS_X64 -> macosX64("native") + // Platform.MACOS_ARM64 -> macosArm64("native") + // Platform.LINUX_X64 -> linuxX64("native") + // Platform.LINUX_ARM64 -> linuxArm64("native") + // Platform.MINGW_X64 -> mingwX64("native") + // } + val nativeTargets = listOf( + macosX64() to Platform.MACOS_X64, + macosArm64() to Platform.MACOS_ARM64, + linuxX64() to Platform.LINUX_X64, + linuxArm64() to Platform.LINUX_ARM64, + mingwX64() to Platform.MINGW_X64, + ) + + nativeTargets.forEach { (nativeTarget, targetPlatform) -> + nativeTarget.apply { + compilations.getByName("main") { + cinterops { + val kommandCore by creating { + if (targetPlatform.toString().contains("macos")) { + defFile(project.file("src/nativeInterop/cinterop/macos.def")) + } else { + defFile(project.file("src/nativeInterop/cinterop/${targetPlatform}.def")) + } + packageName("kommand_core") } - packageName("kommand_core") } } } } + applyDefaultHierarchyTemplate() + sourceSets { // add opt-in all { @@ -83,30 +94,30 @@ kotlin { } } - val jvmMain by getting - val jvmTest by getting - - val targetSourceSetName = when (targetPlatform) { - Platform.MACOS_X64 -> "macosX64" - Platform.MACOS_ARM64 -> "macosArm64" - Platform.LINUX_X64 -> "linuxX64" - Platform.LINUX_ARM64 -> "linuxArm64" - Platform.MINGW_X64 -> "mingwX64" - } - - val targetMain = create("${targetSourceSetName}Main") { - dependsOn(commonMain) - } - val targetTest = create("${targetSourceSetName}Test") { - dependsOn(commonTest) - } - - val nativeMain by getting { - dependsOn(targetMain) - } - val nativeTest by getting { - dependsOn(targetTest) - } + // val jvmMain by getting + // val jvmTest by getting + // + // val targetSourceSetName = when (targetPlatform) { + // Platform.MACOS_X64 -> "macosX64" + // Platform.MACOS_ARM64 -> "macosArm64" + // Platform.LINUX_X64 -> "linuxX64" + // Platform.LINUX_ARM64 -> "linuxArm64" + // Platform.MINGW_X64 -> "mingwX64" + // } + // + // val targetMain = create("${targetSourceSetName}Main") { + // dependsOn(commonMain) + // } + // val targetTest = create("${targetSourceSetName}Test") { + // dependsOn(commonTest) + // } + // + // val nativeMain by getting { + // dependsOn(targetMain) + // } + // val nativeTest by getting { + // dependsOn(targetTest) + // } } } diff --git a/gradle.properties b/gradle.properties index 4b55a42..69638d3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,3 @@ kotlin.code.style=official org.gradle.jvmargs=-Xmx4096m -kotlin.mpp.applyDefaultHierarchyTemplate=false diff --git a/src/nativeMain/kotlin/com/kgit2/Command.native.kt b/src/nativeMain/kotlin/com/kgit2/Command.native.kt index a555b9e..4a0fb40 100644 --- a/src/nativeMain/kotlin/com/kgit2/Command.native.kt +++ b/src/nativeMain/kotlin/com/kgit2/Command.native.kt @@ -1,110 +1,110 @@ package com.kgit2 -import kommand_core.* +import com.kgit2.wrapper.* import kotlinx.cinterop.COpaquePointer -import kotlinx.cinterop.memScoped -import kotlinx.cinterop.pointed import kotlin.native.ref.createCleaner actual class Command( actual val command: String, private val inner: COpaquePointer?, ) { - actual constructor(command: String) : this(command, new_command(command)) + actual constructor(command: String) : this(command, newCommand(command)) private val cleaner = createCleaner(inner) { - drop_command(inner) + dropCommand(inner) } override fun toString(): String { - return display_command(inner)?.asString() ?: "null" + return displayCommand(inner) ?: "null" } actual fun debugString(): String { - return debug_command(inner)?.asString() ?: "null" + return debugCommand(inner) ?: "null" } actual fun arg(arg: String): Command { - arg_command(inner, arg) + argCommand(inner, arg) return this } actual fun args(args: List): Command { for (arg in args) { - arg_command(inner, arg) + argCommand(inner, arg) } return this } actual fun env(key: String, value: String): Command { - env_command(inner, key, value) + envCommand(inner, key, value) return this } actual fun envs(envs: Map): Command { for ((key, value) in envs) { - env_command(inner, key, value) + envCommand(inner, key, value) } return this } actual fun removeEnv(key: String): Command { - remove_env_command(inner, key) + removeEnvCommand(inner, key) return this } actual fun envClear(): Command { - env_clear_command(inner) + envClearCommand(inner) return this } actual fun cwd(dir: String): Command { - current_dir_command(inner, dir) + currentDirCommand(inner, dir) return this } actual fun stdin(stdio: Stdio): Command { - stdin_command(inner, stdio.to()) + stdinCommand(inner, stdio) return this } actual fun stdout(stdio: Stdio): Command { - stdin_command(inner, stdio.to()) + stdoutCommand(inner, stdio) return this } actual fun stderr(stdio: Stdio): Command { - stdin_command(inner, stdio.to()) + stderrCommand(inner, stdio) return this } @Throws(KommandException::class) actual fun spawn(): Child { - val result = spawn_command(inner) - return run { Child.from(result) } + return run { spawnCommand(inner) } } @Throws(KommandException::class) actual fun output(): Output { - val result = output_command(inner) - return run { Output.from(result) } + return run { outputCommand(inner) } } - actual fun status(): Int? = memScoped { - val result = status_command(inner) - if (result.ptr.pointed.err != null) { - val errPtr = result.ptr.pointed.err!! - throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) - } else { - result.ptr.pointed.ok - } + actual fun status(): Int? { + return statusCommand(inner) } -} -fun Stdio.to(): kommand_core.Stdio { - return when (this) { - Stdio.Inherit -> kommand_core.Stdio.Inherit - Stdio.Pipe -> kommand_core.Stdio.Pipe - Stdio.Null -> kommand_core.Stdio.Null - } + // actual fun status(): Int? = memScoped { + // val result = statusCommand(inner) + // if (result.ptr.pointed.err != null) { + // val errPtr = result.ptr.pointed.err!! + // throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + // } else { + // result.ptr.pointed.ok + // } + // } } + +// : kommand_core.Stdio { +// return when (this) { +// Stdio.Inherit -> kommand_core.Stdio.Inherit +// Stdio.Pipe -> kommand_core.Stdio.Pipe +// Stdio.Null -> kommand_core.Stdio.Null +// } +// } diff --git a/src/nativeMain/kotlin/com/kgit2/Exception.kt b/src/nativeMain/kotlin/com/kgit2/Exception.kt index e3fcd89..da44d1b 100644 --- a/src/nativeMain/kotlin/com/kgit2/Exception.kt +++ b/src/nativeMain/kotlin/com/kgit2/Exception.kt @@ -1,10 +1,10 @@ package com.kgit2 -fun kommand_core.ErrorType.to(): ErrorType { - return when (this) { - kommand_core.ErrorType.None -> ErrorType.None - kommand_core.ErrorType.Io -> ErrorType.IO - kommand_core.ErrorType.Utf8 -> ErrorType.Utf8 - kommand_core.ErrorType.Unknown -> ErrorType.Unknown - } -} +// fun kommand_core.ErrorType.to(): ErrorType { +// return when (this) { +// kommand_core.ErrorType.None -> ErrorType.None +// kommand_core.ErrorType.Io -> ErrorType.IO +// kommand_core.ErrorType.Utf8 -> ErrorType.Utf8 +// kommand_core.ErrorType.Unknown -> ErrorType.Unknown +// } +// } diff --git a/src/nativeMain/kotlin/com/kgit2/Output.kt b/src/nativeMain/kotlin/com/kgit2/Output.kt index 1c8a267..a78779e 100644 --- a/src/nativeMain/kotlin/com/kgit2/Output.kt +++ b/src/nativeMain/kotlin/com/kgit2/Output.kt @@ -1,24 +1,24 @@ package com.kgit2 -import kommand_core.drop_output -import kommand_core.into_output -import kotlinx.cinterop.CValue -import kotlinx.cinterop.memScoped -import kotlinx.cinterop.pointed -import kotlinx.cinterop.toKString - -fun Output.Companion.from(result: CValue): Output = memScoped { - if (result.ptr.pointed.err != null) { - val errPtr = result.ptr.pointed.err!! - throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) - } else { - val output = into_output(result.ptr.pointed.ok) - val newOutput = Output( - output.getPointer(memScope).pointed.exit_code, - output.getPointer(memScope).pointed.stdout_content?.toKString(), - output.getPointer(memScope).pointed.stderr_content?.toKString(), - ) - drop_output(output) - newOutput - } -} +// import kommand_core.drop_output +// import kommand_core.into_output +// import kotlinx.cinterop.CValue +// import kotlinx.cinterop.memScoped +// import kotlinx.cinterop.pointed +// import kotlinx.cinterop.toKString +// +// fun Output.Companion.from(result: CValue): Output = memScoped { +// if (result.ptr.pointed.err != null) { +// val errPtr = result.ptr.pointed.err!! +// throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) +// } else { +// val output = into_output(result.ptr.pointed.ok) +// val newOutput = Output( +// output.getPointer(memScope).pointed.exit_code, +// output.getPointer(memScope).pointed.stdout_content?.toKString(), +// output.getPointer(memScope).pointed.stderr_content?.toKString(), +// ) +// drop_output(output) +// newOutput +// } +// } diff --git a/src/nativeMain/kotlin/com/kgit2/wrapper/Child.kt b/src/nativeMain/kotlin/com/kgit2/wrapper/Child.kt new file mode 100644 index 0000000..f62e087 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/wrapper/Child.kt @@ -0,0 +1,2 @@ +package com.kgit2.wrapper + diff --git a/src/nativeMain/kotlin/com/kgit2/wrapper/Command.kt b/src/nativeMain/kotlin/com/kgit2/wrapper/Command.kt new file mode 100644 index 0000000..c6dc345 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/wrapper/Command.kt @@ -0,0 +1,36 @@ +package com.kgit2.wrapper + +import com.kgit2.Child +import com.kgit2.Output +import com.kgit2.Stdio +import kotlinx.cinterop.COpaquePointer + +expect fun newCommand(command: String): COpaquePointer? + +expect fun dropCommand(command: COpaquePointer?) + +expect fun displayCommand(command: COpaquePointer?): String? + +expect fun debugCommand(command: COpaquePointer?): String? + +expect fun argCommand(command: COpaquePointer?, arg: String) + +expect fun envCommand(command: COpaquePointer?, key: String, value: String) + +expect fun removeEnvCommand(command: COpaquePointer?, key: String) + +expect fun envClearCommand(command: COpaquePointer?) + +expect fun currentDirCommand(command: COpaquePointer?, dir: String) + +expect fun stdinCommand(command: COpaquePointer?, stdio: Stdio) + +expect fun stdoutCommand(command: COpaquePointer?, stdio: Stdio) + +expect fun stderrCommand(command: COpaquePointer?, stdio: Stdio) + +expect fun spawnCommand(command: COpaquePointer?): Child + +expect fun outputCommand(command: COpaquePointer?): Output + +expect fun statusCommand(command: COpaquePointer?): Int? diff --git a/src/nativeMain/kotlin/com/kgit2/wrapper/Output.kt b/src/nativeMain/kotlin/com/kgit2/wrapper/Output.kt new file mode 100644 index 0000000..f62e087 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/wrapper/Output.kt @@ -0,0 +1,2 @@ +package com.kgit2.wrapper + From 98687f790ced9f6c5d8a01bf97ee594952a38d5a Mon Sep 17 00:00:00 2001 From: bppleman Date: Sat, 23 Dec 2023 15:46:23 +0800 Subject: [PATCH 05/22] Implement full api --- .../default/virtualbox/action_set_name | 1 - .../machines/default/virtualbox/creator_uid | 1 - .vagrant/machines/default/virtualbox/id | 1 - .../machines/default/virtualbox/index_uuid | 1 - .../machines/default/virtualbox/vagrant_cwd | 1 - .vagrant/rgloader/loader.rb | 9 - build.gradle.kts | 70 +-- eko/.gitignore | 3 - eko/Cargo.lock | 7 - eko/justfile | 25 -- gradle.properties | 1 + kommand_core/.idea/workspace.xml | 126 +++++- kommand_core/Cargo.lock | 414 +++++++++++++++++- kommand_core/Cargo.toml | 17 +- kommand_core/justfile | 18 +- {eko => kommand_core/kommand-echo}/Cargo.toml | 7 +- .../kommand-echo}/src/main.rs | 0 kommand_core/kommand-watch/Cargo.toml | 15 + kommand_core/kommand-watch/src/main.rs | 134 ++++++ kommand_core/kommand_core.h | 4 + kommand_core/src/child.rs | 3 + kommand_core/src/ffi_util.rs | 57 +-- kommand_core/src/io.rs | 7 +- kommand_core/src/io/stderr.rs | 3 + kommand_core/src/io/stdin.rs | 3 + kommand_core/src/io/stdout.rs | 3 + kommand_core/src/kommand.rs | 3 + src/commonMain/kotlin/com/kgit2/Child.kt | 5 - src/commonMain/kotlin/com/kgit2/Platform.kt | 2 +- .../KommandException.kt} | 4 +- .../kotlin/com/kgit2/io/BufferedReader.kt | 11 + .../kotlin/com/kgit2/io/BufferedWriter.kt | 11 + .../kotlin/com/kgit2/{ => io}/Output.kt | 4 +- .../kotlin/com/kgit2/process/Child.kt | 23 + .../kotlin/com/kgit2/{ => process}/Command.kt | 10 +- .../kotlin/com/kgit2/CommandTest.kt | 1 + src/jvmMain/kotlin/com/kgit2/Child.jvm.kt | 7 - .../kotlin/com/kgit2/io/BufferedReader.jvm.kt | 11 + .../kotlin/com/kgit2/io/BufferedWriter.jvm.kt | 9 + .../kotlin/com/kgit2/process/Child.jvm.kt | 31 ++ .../com/kgit2/{ => process}/Command.jvm.kt | 9 +- .../com/kgit2/wrapper/Child.linuxArm64.kt | 50 +++ .../com/kgit2/wrapper/Command.linuxArm64.kt | 85 ++++ .../com/kgit2/wrapper/Extension.linuxArm64.kt | 99 +++++ .../kotlin/com/kgit2/wrapper/IO.kt | 55 +++ .../com/kgit2/wrapper/Child.linuxX64.kt | 50 +++ .../com/kgit2/wrapper/Command.linuxX64.kt | 85 ++++ .../com/kgit2/wrapper/Extension.linuxX64.kt | 99 +++++ .../kotlin/com/kgit2/wrapper/IO.kt | 55 +++ .../com/kgit2/wrapper/Child.macosArm64.kt | 50 +++ .../com/kgit2/wrapper/Command.macosArm64.kt | 85 ++++ .../com/kgit2/wrapper/Extension.macosArm64.kt | 99 +++++ .../kotlin/com/kgit2/wrapper/IO.kt | 55 +++ .../com/kgit2/wrapper/Child.macosX64.kt | 50 +++ .../com/kgit2/wrapper/Command.macosX64.kt | 85 ++++ .../com/kgit2/wrapper/Extension.macosX64.kt | 99 +++++ .../kotlin/com/kgit2/wrapper/IO.kt | 55 +++ .../com/kgit2/wrapper/Child.mingwX64.kt | 50 +++ .../com/kgit2/wrapper/Command.mingwX64.kt | 85 ++++ .../com/kgit2/wrapper/Extension.mingwX64.kt | 99 +++++ .../kotlin/com/kgit2/wrapper/IO.kt | 55 +++ .../kotlin/com/kgit2/Child.native.kt | 27 -- src/nativeMain/kotlin/com/kgit2/Exception.kt | 10 - src/nativeMain/kotlin/com/kgit2/FFiUtil.kt | 12 - src/nativeMain/kotlin/com/kgit2/Output.kt | 24 - .../com/kgit2/io/BufferedReader.native.kt | 58 +++ .../com/kgit2/io/BufferedWriter.native.kt | 26 ++ .../kotlin/com/kgit2/process/Child.native.kt | 70 +++ .../com/kgit2/{ => process}/Command.native.kt | 39 +- .../kotlin/com/kgit2/wrapper/Child.kt | 24 + .../kotlin/com/kgit2/wrapper/Command.kt | 12 +- src/nativeMain/kotlin/com/kgit2/wrapper/IO.kt | 28 ++ .../kotlin/com/kgit2/wrapper/Output.kt | 2 - 73 files changed, 2538 insertions(+), 311 deletions(-) delete mode 100644 .vagrant/machines/default/virtualbox/action_set_name delete mode 100644 .vagrant/machines/default/virtualbox/creator_uid delete mode 100644 .vagrant/machines/default/virtualbox/id delete mode 100644 .vagrant/machines/default/virtualbox/index_uuid delete mode 100644 .vagrant/machines/default/virtualbox/vagrant_cwd delete mode 100644 .vagrant/rgloader/loader.rb delete mode 100644 eko/.gitignore delete mode 100644 eko/Cargo.lock delete mode 100755 eko/justfile rename {eko => kommand_core/kommand-echo}/Cargo.toml (56%) rename {eko => kommand_core/kommand-echo}/src/main.rs (100%) create mode 100644 kommand_core/kommand-watch/Cargo.toml create mode 100644 kommand_core/kommand-watch/src/main.rs delete mode 100644 src/commonMain/kotlin/com/kgit2/Child.kt rename src/commonMain/kotlin/com/kgit2/{Exception.kt => exception/KommandException.kt} (77%) create mode 100644 src/commonMain/kotlin/com/kgit2/io/BufferedReader.kt create mode 100644 src/commonMain/kotlin/com/kgit2/io/BufferedWriter.kt rename src/commonMain/kotlin/com/kgit2/{ => io}/Output.kt (69%) create mode 100644 src/commonMain/kotlin/com/kgit2/process/Child.kt rename src/commonMain/kotlin/com/kgit2/{ => process}/Command.kt (79%) delete mode 100644 src/jvmMain/kotlin/com/kgit2/Child.jvm.kt create mode 100644 src/jvmMain/kotlin/com/kgit2/io/BufferedReader.jvm.kt create mode 100644 src/jvmMain/kotlin/com/kgit2/io/BufferedWriter.jvm.kt create mode 100644 src/jvmMain/kotlin/com/kgit2/process/Child.jvm.kt rename src/jvmMain/kotlin/com/kgit2/{ => process}/Command.jvm.kt (91%) create mode 100644 src/linuxArm64Main/kotlin/com/kgit2/wrapper/Child.linuxArm64.kt create mode 100644 src/linuxArm64Main/kotlin/com/kgit2/wrapper/Command.linuxArm64.kt create mode 100644 src/linuxArm64Main/kotlin/com/kgit2/wrapper/Extension.linuxArm64.kt create mode 100644 src/linuxArm64Main/kotlin/com/kgit2/wrapper/IO.kt create mode 100644 src/linuxX64Main/kotlin/com/kgit2/wrapper/Child.linuxX64.kt create mode 100644 src/linuxX64Main/kotlin/com/kgit2/wrapper/Command.linuxX64.kt create mode 100644 src/linuxX64Main/kotlin/com/kgit2/wrapper/Extension.linuxX64.kt create mode 100644 src/linuxX64Main/kotlin/com/kgit2/wrapper/IO.kt create mode 100644 src/macosArm64Main/kotlin/com/kgit2/wrapper/Child.macosArm64.kt create mode 100644 src/macosArm64Main/kotlin/com/kgit2/wrapper/Command.macosArm64.kt create mode 100644 src/macosArm64Main/kotlin/com/kgit2/wrapper/Extension.macosArm64.kt create mode 100644 src/macosArm64Main/kotlin/com/kgit2/wrapper/IO.kt create mode 100644 src/macosX64Main/kotlin/com/kgit2/wrapper/Child.macosX64.kt create mode 100644 src/macosX64Main/kotlin/com/kgit2/wrapper/Command.macosX64.kt create mode 100644 src/macosX64Main/kotlin/com/kgit2/wrapper/Extension.macosX64.kt create mode 100644 src/macosX64Main/kotlin/com/kgit2/wrapper/IO.kt create mode 100644 src/mingwX64Main/kotlin/com/kgit2/wrapper/Child.mingwX64.kt create mode 100644 src/mingwX64Main/kotlin/com/kgit2/wrapper/Command.mingwX64.kt create mode 100644 src/mingwX64Main/kotlin/com/kgit2/wrapper/Extension.mingwX64.kt create mode 100644 src/mingwX64Main/kotlin/com/kgit2/wrapper/IO.kt delete mode 100644 src/nativeMain/kotlin/com/kgit2/Child.native.kt delete mode 100644 src/nativeMain/kotlin/com/kgit2/Exception.kt delete mode 100644 src/nativeMain/kotlin/com/kgit2/FFiUtil.kt delete mode 100644 src/nativeMain/kotlin/com/kgit2/Output.kt create mode 100644 src/nativeMain/kotlin/com/kgit2/io/BufferedReader.native.kt create mode 100644 src/nativeMain/kotlin/com/kgit2/io/BufferedWriter.native.kt create mode 100644 src/nativeMain/kotlin/com/kgit2/process/Child.native.kt rename src/nativeMain/kotlin/com/kgit2/{ => process}/Command.native.kt (66%) create mode 100644 src/nativeMain/kotlin/com/kgit2/wrapper/IO.kt delete mode 100644 src/nativeMain/kotlin/com/kgit2/wrapper/Output.kt diff --git a/.vagrant/machines/default/virtualbox/action_set_name b/.vagrant/machines/default/virtualbox/action_set_name deleted file mode 100644 index b5dcc97..0000000 --- a/.vagrant/machines/default/virtualbox/action_set_name +++ /dev/null @@ -1 +0,0 @@ -1672061732 \ No newline at end of file diff --git a/.vagrant/machines/default/virtualbox/creator_uid b/.vagrant/machines/default/virtualbox/creator_uid deleted file mode 100644 index ec52cb8..0000000 --- a/.vagrant/machines/default/virtualbox/creator_uid +++ /dev/null @@ -1 +0,0 @@ -501 \ No newline at end of file diff --git a/.vagrant/machines/default/virtualbox/id b/.vagrant/machines/default/virtualbox/id deleted file mode 100644 index 4155507..0000000 --- a/.vagrant/machines/default/virtualbox/id +++ /dev/null @@ -1 +0,0 @@ -9e83fa97-934e-4dd2-beb5-eed3a84736e0 \ No newline at end of file diff --git a/.vagrant/machines/default/virtualbox/index_uuid b/.vagrant/machines/default/virtualbox/index_uuid deleted file mode 100644 index 13f8831..0000000 --- a/.vagrant/machines/default/virtualbox/index_uuid +++ /dev/null @@ -1 +0,0 @@ -4025459ae1a544fe887d25baabcfa405 \ No newline at end of file diff --git a/.vagrant/machines/default/virtualbox/vagrant_cwd b/.vagrant/machines/default/virtualbox/vagrant_cwd deleted file mode 100644 index f3cb59b..0000000 --- a/.vagrant/machines/default/virtualbox/vagrant_cwd +++ /dev/null @@ -1 +0,0 @@ -/Users/bppleman/kgit2/kommand \ No newline at end of file diff --git a/.vagrant/rgloader/loader.rb b/.vagrant/rgloader/loader.rb deleted file mode 100644 index c3c05b0..0000000 --- a/.vagrant/rgloader/loader.rb +++ /dev/null @@ -1,9 +0,0 @@ -# This file loads the proper rgloader/loader.rb file that comes packaged -# with Vagrant so that encoded files can properly run with Vagrant. - -if ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"] - require File.expand_path( - "rgloader/loader", ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]) -else - raise "Encoded files can't be read outside of the Vagrant installer." -end diff --git a/build.gradle.kts b/build.gradle.kts index d255bf0..8858f5f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,14 +13,6 @@ plugins { group = "com.kgit2" version = "1.2.0" -val mainHost = Platform.MACOS_X64 -val targetPlatform = Platform.valueOf(project.findProperty("targetPlatform")?.toString() ?: "MACOS_X64") -// for debug -// val targetPlatform = Platform.valueOf(project.findProperty("targetPlatform")?.toString() ?: "MACOS_ARM64") -// val targetPlatform = Platform.valueOf(project.findProperty("targetPlatform")?.toString() ?: "LINUX_X64") -// val targetPlatform = Platform.valueOf(project.findProperty("targetPlatform")?.toString() ?: "LINUX_ARM64") -// val targetPlatform = Platform.valueOf(project.findProperty("targetPlatform")?.toString() ?: "MINGW_X64") - val ktorIO = "2.3.4" repositories { @@ -39,13 +31,6 @@ kotlin { } } - // val nativeTarget = when (targetPlatform) { - // Platform.MACOS_X64 -> macosX64("native") - // Platform.MACOS_ARM64 -> macosArm64("native") - // Platform.LINUX_X64 -> linuxX64("native") - // Platform.LINUX_ARM64 -> linuxArm64("native") - // Platform.MINGW_X64 -> mingwX64("native") - // } val nativeTargets = listOf( macosX64() to Platform.MACOS_X64, macosArm64() to Platform.MACOS_ARM64, @@ -93,50 +78,23 @@ kotlin { implementation(kotlin("test")) } } - - // val jvmMain by getting - // val jvmTest by getting - // - // val targetSourceSetName = when (targetPlatform) { - // Platform.MACOS_X64 -> "macosX64" - // Platform.MACOS_ARM64 -> "macosArm64" - // Platform.LINUX_X64 -> "linuxX64" - // Platform.LINUX_ARM64 -> "linuxArm64" - // Platform.MINGW_X64 -> "mingwX64" - // } - // - // val targetMain = create("${targetSourceSetName}Main") { - // dependsOn(commonMain) - // } - // val targetTest = create("${targetSourceSetName}Test") { - // dependsOn(commonTest) - // } - // - // val nativeMain by getting { - // dependsOn(targetMain) - // } - // val nativeTest by getting { - // dependsOn(targetTest) - // } } } -val subCommandInstallDist = tasks.findByPath(":sub_command:installDist") - -val buildEko = tasks.create("buildEko") { - group = "build" +val buildKommandEcho = tasks.create("buildKommandEcho") { + group = "kommand_core" doLast { - ProcessBuilder("bash", "-c", "cargo build --release") - .directory(file("eko")) - .inheritIO() - .start() - .waitFor() + // ProcessBuilder("bash", "-c", "cargo build --release") + // .directory(file("eko")) + // .inheritIO() + // .start() + // .waitFor() } } tasks.forEach { if (it.group == "verification" || it.path.contains("Test")) { - it.dependsOn(buildEko) + it.dependsOn(buildKommandEcho) } } @@ -161,11 +119,7 @@ val ossrhPassword = runCatching { }.getOrNull() if (ossrhUsername != null && ossrhPassword != null) { - val keyId = project.findProperty("signing.keyId") as String? - val keyPass = project.findProperty("signing.password") as String? - val keyRingFile = project.findProperty("signing.secretKeyRingFile") as String? - - val dokkaOutputDir = "$buildDir/dokka" + val dokkaOutputDir = layout.buildDirectory.dir("dokka") tasks.getByName("dokkaHtml") { outputDirectory.set(file(dokkaOutputDir)) @@ -213,7 +167,7 @@ if (ossrhUsername != null && ossrhPassword != null) { licenses { license { name.set("The Apache License, Version 2.0") - url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") } } scm { @@ -233,6 +187,10 @@ if (ossrhUsername != null && ossrhPassword != null) { } signing { + // will default find the + // - signing.keyId + // - signing.password + // - signing.secretKeyRingFile sign(publishing.publications) } } diff --git a/eko/.gitignore b/eko/.gitignore deleted file mode 100644 index 615b90c..0000000 --- a/eko/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -target -.DS_Store -.idea diff --git a/eko/Cargo.lock b/eko/Cargo.lock deleted file mode 100644 index fa9863e..0000000 --- a/eko/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "eko" -version = "0.1.0" diff --git a/eko/justfile b/eko/justfile deleted file mode 100755 index bfb372b..0000000 --- a/eko/justfile +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env just --justfile - -release: - cargo build --release - -macosX64: - cargo build --release --target x86_64-apple-darwin - -macosArm64: - cargo build --release --target aarch64-apple-darwin - -linuxX64: - CROSS_ROOTLESS_CONTAINER_ENGINE=1 cross build --release --target x86_64-unknown-linux-gnu - -linuxArm64: - CROSS_ROOTLESS_CONTAINER_ENGINE=1 cross build --release --target aarch64-unknown-linux-gnu - -windowsX64: - CROSS_ROOTLESS_CONTAINER_ENGINE=1 cross build --release --target x86_64-pc-windows-gnu - -all: macosX64 macosArm64 linuxX64 linuxArm64 windowsX64 - -prepare: - cargo install cross - rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu x86_64-apple-darwin aarch64-apple-darwin x86_64-pc-windows-gnu diff --git a/gradle.properties b/gradle.properties index 69638d3..7e5a284 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ kotlin.code.style=official org.gradle.jvmargs=-Xmx4096m +org.gradle.caching=true diff --git a/kommand_core/.idea/workspace.xml b/kommand_core/.idea/workspace.xml index de80daf..1d79d7f 100644 --- a/kommand_core/.idea/workspace.xml +++ b/kommand_core/.idea/workspace.xml @@ -13,7 +13,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - { - "keyToString": { - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "RunOnceActivity.cidr.known.project.marker": "true", - "WebServerToolWindowFactoryState": "false", - "cf.first.check.clang-format": "false", - "cidr.known.project.marker": "true", - "dart.analysis.tool.window.visible": "false", - "git-widget-placeholder": "main", - "last_opened_file_path": "/Users/bppleman/kgit2/kommand2/kommand_core", - "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", - "settings.editor.selected.configurable": "terminal" + +}]]> + + + @@ -77,7 +149,7 @@ - + + + + @@ -158,7 +248,13 @@ + + + + + \ No newline at end of file diff --git a/kommand_core/Cargo.lock b/kommand_core/Cargo.lock index f9537a0..730787c 100644 --- a/kommand_core/Cargo.lock +++ b/kommand_core/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "atty" version = "0.2.14" @@ -19,6 +34,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -50,6 +80,15 @@ dependencies = [ "toml", ] +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -80,6 +119,52 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" +dependencies = [ + "cfg-if", +] + [[package]] name = "errno" version = "0.3.8" @@ -90,12 +175,55 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "eyre" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.52.0", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "hashbrown" version = "0.12.3" @@ -117,6 +245,12 @@ dependencies = [ "libc", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "1.9.3" @@ -127,6 +261,26 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "itoa" version = "1.0.10" @@ -134,12 +288,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] -name = "kommand_core" +name = "kommand-core" version = "0.1.0" dependencies = [ "cbindgen", ] +[[package]] +name = "kommand-echo" +version = "0.1.0" + +[[package]] +name = "kommand-watch" +version = "0.1.0" +dependencies = [ + "color-eyre", + "fs_extra", + "lazy_static", + "notify-debouncer-mini", + "tokio", + "walkdir", +] + +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.151" @@ -158,12 +354,96 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.4.1", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "walkdir", + "windows-sys 0.48.0", +] + +[[package]] +name = "notify-debouncer-mini" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43" +dependencies = [ + "crossbeam-channel", + "log", + "notify", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + [[package]] name = "os_str_bytes" version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + [[package]] name = "proc-macro2" version = "1.0.70" @@ -191,6 +471,12 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustix" version = "0.38.28" @@ -210,6 +496,15 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "serde" version = "1.0.193" @@ -241,6 +536,24 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "strsim" version = "0.10.0" @@ -297,6 +610,42 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tokio" +version = "1.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +dependencies = [ + "backtrace", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.42", +] + [[package]] name = "toml" version = "0.5.11" @@ -306,12 +655,75 @@ dependencies = [ "serde", ] +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi" version = "0.3.9" diff --git a/kommand_core/Cargo.toml b/kommand_core/Cargo.toml index 63f3c44..38522b1 100644 --- a/kommand_core/Cargo.toml +++ b/kommand_core/Cargo.toml @@ -1,9 +1,22 @@ -[package] -name = "kommand_core" +[workspace] +members = [ + "kommand-watch", + "kommand-echo", +] + +[workspace.package] version = "0.1.0" edition = "2021" +authors = ["BppleMan"] + +[package] +name = "kommand-core" +version.workspace = true +edition.workspace = true +authors.workspace = true [lib] +name = "kommand_core" crate-type = ["rlib", "staticlib"] [dependencies] diff --git a/kommand_core/justfile b/kommand_core/justfile index ee30509..79a7848 100755 --- a/kommand_core/justfile +++ b/kommand_core/justfile @@ -9,10 +9,10 @@ bench: time target/debug/leaks_test macosx64: - cargo build --release --target x86_64-apple-darwin + cargo build --release --package kommand-core --package kommand-echo --target x86_64-apple-darwin macosarm64: - cargo build --release --target aarch64-apple-darwin + cargo build --release --package kommand-core --package kommand-echo --target aarch64-apple-darwin macos: macosx64 macosarm64 [ -d target/universal-apple-darwin/release ] || mkdir -p target/universal-apple-darwin/release @@ -22,21 +22,21 @@ macos: macosx64 macosarm64 linuxx64: CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-unknown-linux-gnu-gcc \ - cargo build --release --target x86_64-unknown-linux-gnu + cargo build --release --package kommand-core --package kommand-echo --target x86_64-unknown-linux-gnu linuxarm64: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-unknown-linux-gnu-gcc \ - cargo build --release --target aarch64-unknown-linux-gnu + cargo build --release --package kommand-core --package kommand-echo --target aarch64-unknown-linux-gnu linux: linuxx64 linuxarm64 winx64: - cargo build --release --target x86_64-pc-windows-gnu - -xwinx64: - CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=/Users/bppleman/.konan/dependencies/apple-llvm-20200714-macos-x64-essentials/bin/clang++ \ - rustup run nightly cargo xbuild -Zbuild-std --release --target x86_64-pc-windows-gnu + cargo build --release --package kommand-core --package kommand-echo --target x86_64-pc-windows-gnu win: winx64 all: macos linux win + +watch: + cargo build --release --package kommand-watch + target/release/kommand-watch diff --git a/eko/Cargo.toml b/kommand_core/kommand-echo/Cargo.toml similarity index 56% rename from eko/Cargo.toml rename to kommand_core/kommand-echo/Cargo.toml index ddc1591..c31ec6e 100644 --- a/eko/Cargo.toml +++ b/kommand_core/kommand-echo/Cargo.toml @@ -1,7 +1,8 @@ [package] -name = "eko" -version = "0.1.0" -edition = "2021" +name = "kommand-echo" +version.workspace = true +edition.workspace = true +authors.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/eko/src/main.rs b/kommand_core/kommand-echo/src/main.rs similarity index 100% rename from eko/src/main.rs rename to kommand_core/kommand-echo/src/main.rs diff --git a/kommand_core/kommand-watch/Cargo.toml b/kommand_core/kommand-watch/Cargo.toml new file mode 100644 index 0000000..2292c4b --- /dev/null +++ b/kommand_core/kommand-watch/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "kommand-watch" +version.workspace = true +edition.workspace = true +authors.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +notify-debouncer-mini = "0.4.1" +color-eyre = "0.6.2" +tokio = { version = "1.35.1", features = ["rt", "signal", "macros"] } +lazy_static = "1.4.0" +fs_extra = "1.3.0" +walkdir = "2.4.0" diff --git a/kommand_core/kommand-watch/src/main.rs b/kommand_core/kommand-watch/src/main.rs new file mode 100644 index 0000000..c8c95cb --- /dev/null +++ b/kommand_core/kommand-watch/src/main.rs @@ -0,0 +1,134 @@ +use std::collections::{HashMap, HashSet}; +use std::ffi::OsStr; +use std::path::Path; +use std::time::Duration; + +use color_eyre::eyre::Context; +use color_eyre::{install, Result}; +use lazy_static::lazy_static; +use notify_debouncer_mini::notify::RecursiveMode; +use notify_debouncer_mini::{new_debouncer, DebounceEventResult}; +use std::path::PathBuf; +use walkdir::WalkDir; + +lazy_static! { + static ref ROOT_DIR: PathBuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .parent() + .unwrap() + .canonicalize() + .unwrap(); + static ref MAIN_DIR: PathBuf = ROOT_DIR.join("src/macosX64Main/kotlin/com/kgit2/wrapper"); + #[rustfmt::skip] + static ref CLUSTER_DIRS: HashMap = HashMap::from([ + ("macosArm64".to_string(), ROOT_DIR.join("src/macosArm64Main/kotlin/com/kgit2/wrapper")), + ("linuxX64".to_string(), ROOT_DIR.join("src/linuxX64Main/kotlin/com/kgit2/wrapper")), + ("linuxArm64".to_string(), ROOT_DIR.join("src/linuxArm64Main/kotlin/com/kgit2/wrapper")), + ("mingwX64".to_string(), ROOT_DIR.join("src/mingwX64Main/kotlin/com/kgit2/wrapper")), + ]); +} + +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<()> { + install()?; + + sync_dir(MAIN_DIR.as_path()); + let mut debouncer = new_debouncer(Duration::from_millis(500), process_file_change)?; + println!("Watching {}", MAIN_DIR.display()); + debouncer + .watcher() + .watch(&MAIN_DIR, RecursiveMode::Recursive)?; + + tokio::signal::ctrl_c().await?; + Ok(()) +} + +fn process_file_change(result: DebounceEventResult) { + match result { + Ok(result) => { + result + .into_iter() + .filter(|e| e.path.extension() == Some(OsStr::new("kt"))) + .for_each(|e| { + let path = e.path; + if path.exists() { + if path.is_file() { + sync_file(path); + } else { + sync_dir(path); + } + } else { + sync_remove(path); + } + }); + } + Err(error) => { + eprintln!("{:?}", error); + } + } +} + +fn sync_file(path: impl AsRef) { + println!("File changed {:?}", path.as_ref()); + let content = fs_extra::file::read_to_string(path.as_ref()).unwrap(); + let striped = path + .as_ref() + .strip_prefix(MAIN_DIR.as_path()) + .with_context(|| "Strip Error") + .unwrap(); + CLUSTER_DIRS.iter().for_each(|(name, cluster_dir)| { + let new_path = cluster_dir.join(striped.to_string_lossy().replace("macosX64", name)); + println!("Will write to {:?}", new_path); + if !new_path.exists() { + fs_extra::dir::create_all(new_path.parent().unwrap(), false).unwrap(); + } + fs_extra::file::write_all(&new_path, &content).unwrap(); + }); +} + +fn sync_dir(path: impl AsRef) { + println!("Dir changed {:?}", path.as_ref()); + let main_files = collect_dir(MAIN_DIR.as_path()); + let main_set = main_files + .iter() + .map(|file| { + file.strip_prefix(MAIN_DIR.as_path()) + .unwrap() + .to_string_lossy() + .to_string() + }) + .collect::>(); + CLUSTER_DIRS.iter().for_each(|(name, cluster_dir)| { + let cluster_files = collect_dir(cluster_dir.as_path()); + let cluster_set = cluster_files + .iter() + .map(|file| { + let file = file.strip_prefix(cluster_dir.as_path()).unwrap(); + if let Some(file_name) = file.file_name() { + file_name.to_string_lossy().replace(name, "macosX64") + } else { + file.to_string_lossy().to_string() + } + }) + .collect::>(); + let diff = cluster_set.difference(&main_set); + diff.for_each(|path| sync_remove(cluster_dir.join(path))); + #[rustfmt::skip] + main_set.iter().for_each(|path| sync_file(MAIN_DIR.join(path))); + }); +} + +fn sync_remove(path: impl AsRef) { + println!("Item removed {:?}", path.as_ref()); + fs_extra::remove_items(&[path.as_ref()]).unwrap(); +} + +fn collect_dir(path: impl AsRef) -> HashSet { + let walk_dir = WalkDir::new(path.as_ref()).min_depth(1); + walk_dir + .into_iter() + .flatten() + .map(|entry| entry.into_path()) + .collect::>() +} diff --git a/kommand_core/kommand_core.h b/kommand_core/kommand_core.h index 2a5c638..d85aee1 100644 --- a/kommand_core/kommand_core.h +++ b/kommand_core/kommand_core.h @@ -57,6 +57,8 @@ void drop_child(void *child); */ void drop_string(char *string); +char *void_to_string(void *ptr); + struct VoidResult read_line_stdout(const void *reader); struct VoidResult read_all_stdout(const void *reader); @@ -70,6 +72,8 @@ struct VoidResult read_all_stderr(const void *reader); */ struct UnitResult write_line_stdin(const void *writer, const char *line); +struct UnitResult flush_stdin(const void *writer); + void drop_stderr(void *reader); void drop_stdin(void *reader); diff --git a/kommand_core/src/child.rs b/kommand_core/src/child.rs index 9a40182..bb71241 100644 --- a/kommand_core/src/child.rs +++ b/kommand_core/src/child.rs @@ -76,5 +76,8 @@ pub extern "C" fn wait_with_output_child(child: *mut c_void) -> VoidResult { #[no_mangle] pub extern "C" fn drop_child(child: *mut c_void) { + if child.is_null() { + return; + } into_child(child); } diff --git a/kommand_core/src/ffi_util.rs b/kommand_core/src/ffi_util.rs index 6585e45..23b5517 100644 --- a/kommand_core/src/ffi_util.rs +++ b/kommand_core/src/ffi_util.rs @@ -27,59 +27,24 @@ pub fn into_cstring(str: impl AsRef) -> *mut c_char { .into_raw() } -// /// # Safety -// #[no_mangle] -// pub unsafe extern "C" fn into_i32(int: *mut i32) -> i32 { -// *Box::from_raw(int) -// } -// -// /// # Safety -// #[no_mangle] -// #[cfg(unix)] -// pub unsafe extern "C" fn u8_ptr_from_os_string(ptr: *mut c_void) -> *mut c_uchar { -// let os_string = *Box::from_raw(ptr as *mut OsString); -// let mut u8_vec = std::os::unix::prelude::OsStringExt::into_vec(os_string); -// let ptr = u8_vec.as_mut_ptr(); -// std::mem::forget(u8_vec); -// ptr -// } -// -// /// # Safety -// #[no_mangle] -// #[cfg(not(unix))] -// pub unsafe extern "C" fn u8_ptr_from_os_string(ptr: *mut c_void) -> *mut c_uchar { -// let _ = *Box::from_raw(ptr as *mut OsString); -// std::ptr::null_mut() -// } -// -// /// # Safety -// #[no_mangle] -// #[cfg(windows)] -// pub unsafe extern "C" fn u16_ptr_from_os_string(ptr: *mut c_void) -> *mut u16 { -// let os_string = *Box::from_raw(ptr as *mut OsString); -// let mut u16_vec: Vec = std::os::windows::ffi::OsStringExt::encode_wide(os_string) -// .chain(Some(0)) -// .collect(); -// let ptr = u16_vec.as_mut_ptr(); -// std::mem::forget(u16_vec); -// ptr -// } -// -// /// # Safety -// #[no_mangle] -// #[cfg(not(windows))] -// pub unsafe extern "C" fn u16_ptr_from_os_string(ptr: *mut c_void) -> *mut u16 { -// let _ = *Box::from_raw(ptr as *mut OsString); -// std::ptr::null_mut() -// } - /// # Safety /// Will drop the string #[no_mangle] pub unsafe extern "C" fn drop_string(string: *mut c_char) { + if string.is_null() { + return; + } into_string(string); } pub fn into_void(object: T) -> *mut c_void { Box::into_raw(Box::new(object)) as *mut c_void } + +#[no_mangle] +pub extern "C" fn void_to_string(ptr: *mut c_void) -> *mut c_char { + if ptr.is_null() { + return std::ptr::null_mut(); + } + ptr as *mut c_char +} diff --git a/kommand_core/src/io.rs b/kommand_core/src/io.rs index ccc575a..f715e52 100644 --- a/kommand_core/src/io.rs +++ b/kommand_core/src/io.rs @@ -50,6 +50,11 @@ pub unsafe extern "C" fn write_line_stdin( let writer = stdin::as_stdin_mut(&mut writer); let line = as_string(line); let result = writer.write_all(line.as_bytes()); - writer.flush().unwrap(); result.into() } + +#[no_mangle] +pub extern "C" fn flush_stdin(mut writer: *const c_void) -> UnitResult { + let writer = stdin::as_stdin_mut(&mut writer); + writer.flush().into() +} diff --git a/kommand_core/src/io/stderr.rs b/kommand_core/src/io/stderr.rs index 0e28427..6c77279 100644 --- a/kommand_core/src/io/stderr.rs +++ b/kommand_core/src/io/stderr.rs @@ -12,5 +12,8 @@ pub fn into_stderr(reader: *mut c_void) -> BufReader { #[no_mangle] pub extern "C" fn drop_stderr(reader: *mut c_void) { + if reader.is_null() { + return; + } into_stderr(reader); } diff --git a/kommand_core/src/io/stdin.rs b/kommand_core/src/io/stdin.rs index 9f43a0d..13b5288 100644 --- a/kommand_core/src/io/stdin.rs +++ b/kommand_core/src/io/stdin.rs @@ -12,5 +12,8 @@ pub fn into_stdin(reader: *mut c_void) -> BufWriter { #[no_mangle] pub extern "C" fn drop_stdin(reader: *mut c_void) { + if reader.is_null() { + return; + } into_stdin(reader); } diff --git a/kommand_core/src/io/stdout.rs b/kommand_core/src/io/stdout.rs index ce058f5..100b7d1 100644 --- a/kommand_core/src/io/stdout.rs +++ b/kommand_core/src/io/stdout.rs @@ -12,5 +12,8 @@ pub fn into_stdout(reader: *mut c_void) -> BufReader { #[no_mangle] pub extern "C" fn drop_stdout(reader: *mut c_void) { + if reader.is_null() { + return; + } into_stdout(reader); } diff --git a/kommand_core/src/kommand.rs b/kommand_core/src/kommand.rs index 17cae63..a566ae1 100644 --- a/kommand_core/src/kommand.rs +++ b/kommand_core/src/kommand.rs @@ -79,6 +79,9 @@ pub extern "C" fn debug_command(command: *const c_void) -> *mut c_char { /// ``` #[no_mangle] pub extern "C" fn drop_command(command: *mut c_void) { + if command.is_null() { + return; + } into_command(command); } diff --git a/src/commonMain/kotlin/com/kgit2/Child.kt b/src/commonMain/kotlin/com/kgit2/Child.kt deleted file mode 100644 index edbe4bb..0000000 --- a/src/commonMain/kotlin/com/kgit2/Child.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.kgit2 - -expect class Child { - -} diff --git a/src/commonMain/kotlin/com/kgit2/Platform.kt b/src/commonMain/kotlin/com/kgit2/Platform.kt index cf94baf..92d811b 100644 --- a/src/commonMain/kotlin/com/kgit2/Platform.kt +++ b/src/commonMain/kotlin/com/kgit2/Platform.kt @@ -5,7 +5,7 @@ enum class Platform { MACOS_ARM64, LINUX_X64, LINUX_ARM64, - WINDOWS_X64, + MINGW_X64, JVM } diff --git a/src/commonMain/kotlin/com/kgit2/Exception.kt b/src/commonMain/kotlin/com/kgit2/exception/KommandException.kt similarity index 77% rename from src/commonMain/kotlin/com/kgit2/Exception.kt rename to src/commonMain/kotlin/com/kgit2/exception/KommandException.kt index ff09db2..103cf43 100644 --- a/src/commonMain/kotlin/com/kgit2/Exception.kt +++ b/src/commonMain/kotlin/com/kgit2/exception/KommandException.kt @@ -1,4 +1,4 @@ -package com.kgit2 +package com.kgit2.exception class KommandException( message: String?, @@ -11,5 +11,5 @@ enum class ErrorType { Utf8, Unknown, ; - companion object + companion object; } diff --git a/src/commonMain/kotlin/com/kgit2/io/BufferedReader.kt b/src/commonMain/kotlin/com/kgit2/io/BufferedReader.kt new file mode 100644 index 0000000..cdfc4b9 --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/io/BufferedReader.kt @@ -0,0 +1,11 @@ +package com.kgit2.io + +import com.kgit2.exception.KommandException + +expect class BufferedReader { + @Throws(KommandException::class) + fun readLine(): String? + + @Throws(KommandException::class) + fun readAll(): String? +} diff --git a/src/commonMain/kotlin/com/kgit2/io/BufferedWriter.kt b/src/commonMain/kotlin/com/kgit2/io/BufferedWriter.kt new file mode 100644 index 0000000..375957e --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/io/BufferedWriter.kt @@ -0,0 +1,11 @@ +package com.kgit2.io + +import com.kgit2.exception.KommandException + +expect class BufferedWriter { + @Throws(KommandException::class) + fun writeLine(line: String) + + @Throws(KommandException::class) + fun flush() +} diff --git a/src/commonMain/kotlin/com/kgit2/Output.kt b/src/commonMain/kotlin/com/kgit2/io/Output.kt similarity index 69% rename from src/commonMain/kotlin/com/kgit2/Output.kt rename to src/commonMain/kotlin/com/kgit2/io/Output.kt index 1f1481a..517572d 100644 --- a/src/commonMain/kotlin/com/kgit2/Output.kt +++ b/src/commonMain/kotlin/com/kgit2/io/Output.kt @@ -1,9 +1,9 @@ -package com.kgit2 +package com.kgit2.io data class Output( val status: Int?, val stdout: String?, val stderr: String?, ) { - companion object + companion object; } diff --git a/src/commonMain/kotlin/com/kgit2/process/Child.kt b/src/commonMain/kotlin/com/kgit2/process/Child.kt new file mode 100644 index 0000000..cb3acb3 --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/process/Child.kt @@ -0,0 +1,23 @@ +package com.kgit2.process + +import com.kgit2.io.BufferedReader +import com.kgit2.io.BufferedWriter +import com.kgit2.exception.KommandException +import com.kgit2.io.Output + +expect class Child { + var stdin: BufferedWriter? + var stdout: BufferedReader? + var stderr: BufferedReader? + + fun id(): UInt + + @Throws(KommandException::class) + fun kill() + + @Throws(KommandException::class) + fun wait(): Int + + @Throws(KommandException::class) + fun waitWithOutput(): Output +} diff --git a/src/commonMain/kotlin/com/kgit2/Command.kt b/src/commonMain/kotlin/com/kgit2/process/Command.kt similarity index 79% rename from src/commonMain/kotlin/com/kgit2/Command.kt rename to src/commonMain/kotlin/com/kgit2/process/Command.kt index 7e222e8..43a8c23 100644 --- a/src/commonMain/kotlin/com/kgit2/Command.kt +++ b/src/commonMain/kotlin/com/kgit2/process/Command.kt @@ -1,4 +1,7 @@ -package com.kgit2 +package com.kgit2.process + +import com.kgit2.exception.KommandException +import com.kgit2.io.Output expect class Command { val command: String @@ -33,7 +36,8 @@ expect class Command { @Throws(KommandException::class) fun output(): Output - fun status(): Int? + @Throws(KommandException::class) + fun status(): Int } enum class Stdio { @@ -41,5 +45,5 @@ enum class Stdio { Pipe, Null, ; - companion object + companion object; } diff --git a/src/commonTest/kotlin/com/kgit2/CommandTest.kt b/src/commonTest/kotlin/com/kgit2/CommandTest.kt index 37ffa15..533c77b 100644 --- a/src/commonTest/kotlin/com/kgit2/CommandTest.kt +++ b/src/commonTest/kotlin/com/kgit2/CommandTest.kt @@ -1,5 +1,6 @@ package com.kgit2 +import com.kgit2.process.Command import kotlin.test.Test class CommandTest { diff --git a/src/jvmMain/kotlin/com/kgit2/Child.jvm.kt b/src/jvmMain/kotlin/com/kgit2/Child.jvm.kt deleted file mode 100644 index 3d5f67f..0000000 --- a/src/jvmMain/kotlin/com/kgit2/Child.jvm.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.kgit2 - -actual class Child( - private val process: Process, -) { - -} diff --git a/src/jvmMain/kotlin/com/kgit2/io/BufferedReader.jvm.kt b/src/jvmMain/kotlin/com/kgit2/io/BufferedReader.jvm.kt new file mode 100644 index 0000000..36bdc41 --- /dev/null +++ b/src/jvmMain/kotlin/com/kgit2/io/BufferedReader.jvm.kt @@ -0,0 +1,11 @@ +package com.kgit2.io + +actual class BufferedReader { + actual fun readLine(): String? { + return "" + } + + actual fun readAll(): String? { + return "" + } +} diff --git a/src/jvmMain/kotlin/com/kgit2/io/BufferedWriter.jvm.kt b/src/jvmMain/kotlin/com/kgit2/io/BufferedWriter.jvm.kt new file mode 100644 index 0000000..93e8288 --- /dev/null +++ b/src/jvmMain/kotlin/com/kgit2/io/BufferedWriter.jvm.kt @@ -0,0 +1,9 @@ +package com.kgit2.io + +actual class BufferedWriter { + actual fun writeLine(line: String) { + } + + actual fun flush() { + } +} diff --git a/src/jvmMain/kotlin/com/kgit2/process/Child.jvm.kt b/src/jvmMain/kotlin/com/kgit2/process/Child.jvm.kt new file mode 100644 index 0000000..7cd2318 --- /dev/null +++ b/src/jvmMain/kotlin/com/kgit2/process/Child.jvm.kt @@ -0,0 +1,31 @@ +package com.kgit2.process + +import com.kgit2.exception.KommandException +import com.kgit2.io.BufferedReader +import com.kgit2.io.BufferedWriter +import com.kgit2.io.Output + +actual class Child( + private val process: Process, +) { + actual var stdin: BufferedWriter? = null + actual var stdout: BufferedReader? = null + actual var stderr: BufferedReader? = null + + actual fun id(): UInt { + TODO("Not yet implemented") + } + + @Throws(KommandException::class) + actual fun kill() {} + + @Throws(KommandException::class) + actual fun wait(): Int { + return 0 + } + + @Throws(KommandException::class) + actual fun waitWithOutput(): Output { + TODO("Not yet implemented") + } +} diff --git a/src/jvmMain/kotlin/com/kgit2/Command.jvm.kt b/src/jvmMain/kotlin/com/kgit2/process/Command.jvm.kt similarity index 91% rename from src/jvmMain/kotlin/com/kgit2/Command.jvm.kt rename to src/jvmMain/kotlin/com/kgit2/process/Command.jvm.kt index ea9fa3e..52373bb 100644 --- a/src/jvmMain/kotlin/com/kgit2/Command.jvm.kt +++ b/src/jvmMain/kotlin/com/kgit2/process/Command.jvm.kt @@ -1,5 +1,7 @@ -package com.kgit2 +package com.kgit2.process +import com.kgit2.exception.KommandException +import com.kgit2.io.Output import java.io.File actual class Command( @@ -71,7 +73,7 @@ actual class Command( @Throws(KommandException::class) actual fun spawn(): Child { val process = builder.start() - return Child(process) + return com.kgit2.process.Child(process) } @Throws(KommandException::class) @@ -83,7 +85,8 @@ actual class Command( return Output(status, stdoutContent, stderrContent) } - actual fun status(): Int? { + @Throws(KommandException::class) + actual fun status(): Int { return builder.start().waitFor() } } diff --git a/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Child.linuxArm64.kt b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Child.linuxArm64.kt new file mode 100644 index 0000000..97c4b72 --- /dev/null +++ b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Child.linuxArm64.kt @@ -0,0 +1,50 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import com.kgit2.io.BufferedReader +import com.kgit2.io.BufferedWriter +import com.kgit2.io.Output +import com.kgit2.io.ReaderType +import kommand_core.drop_child +import kommand_core.id_child +import kommand_core.kill_child +import kommand_core.stderr_child +import kommand_core.stdin_child +import kommand_core.wait_child +import kommand_core.wait_with_output_child +import kotlinx.cinterop.COpaquePointer + +actual fun idChild(child: COpaquePointer?): UInt { + return id_child(child) +} + +actual fun dropChild(child: COpaquePointer?) { + drop_child(child) +} + +@Throws(KommandException::class) +actual fun killChild(child: COpaquePointer?) = run { + kill_child(child).unwrap() +} + +@Throws(KommandException::class) +actual fun waitChild(child: COpaquePointer?): Int = run { + Int.from(wait_child(child)) +} + +@Throws(KommandException::class) +actual fun waitWithOutputChild(child: COpaquePointer?): Output = run { + Output.from(wait_with_output_child(child)) +} + +actual fun stdinChild(child: COpaquePointer?): BufferedWriter? { + return BufferedWriter(stdin_child(child)) +} + +actual fun stdoutChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(stdin_child(child), ReaderType.STDOUT) +} + +actual fun stderrChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(stderr_child(child), ReaderType.STDERR) +} diff --git a/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Command.linuxArm64.kt b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Command.linuxArm64.kt new file mode 100644 index 0000000..dbe5540 --- /dev/null +++ b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Command.linuxArm64.kt @@ -0,0 +1,85 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import com.kgit2.io.Output +import com.kgit2.process.Child +import com.kgit2.process.Stdio +import kommand_core.arg_command +import kommand_core.current_dir_command +import kommand_core.debug_command +import kommand_core.display_command +import kommand_core.drop_command +import kommand_core.env_clear_command +import kommand_core.env_command +import kommand_core.new_command +import kommand_core.output_command +import kommand_core.remove_env_command +import kommand_core.spawn_command +import kommand_core.status_command +import kommand_core.stderr_command +import kommand_core.stdin_command +import kommand_core.stdout_command +import kotlinx.cinterop.COpaquePointer + +actual fun newCommand(command: String): COpaquePointer? { + return new_command(command) +} + +actual fun dropCommand(command: COpaquePointer?) { + return drop_command(command) +} + +actual fun displayCommand(command: COpaquePointer?): String? { + return display_command(command)?.asString() +} + +actual fun debugCommand(command: COpaquePointer?): String? { + return debug_command(command)?.asString() +} + +actual fun argCommand(command: COpaquePointer?, arg: String) { + return arg_command(command, arg) +} + +actual fun envCommand(command: COpaquePointer?, key: String, value: String) { + return env_command(command, key, value) +} + +actual fun removeEnvCommand(command: COpaquePointer?, key: String) { + return remove_env_command(command, key) +} + +actual fun envClearCommand(command: COpaquePointer?) { + return env_clear_command(command) +} + +actual fun currentDirCommand(command: COpaquePointer?, dir: String) { + return current_dir_command(command, dir) +} + +actual fun stdinCommand(command: COpaquePointer?, stdio: Stdio) { + stdin_command(command, stdio.to()) +} + +actual fun stdoutCommand(command: COpaquePointer?, stdio: Stdio) { + stdout_command(command, stdio.to()) +} + +actual fun stderrCommand(command: COpaquePointer?, stdio: Stdio) { + stderr_command(command, stdio.to()) +} + +@Throws(KommandException::class) +actual fun spawnCommand(command: COpaquePointer?): Child = run { + Child.from(spawn_command(command)) +} + +@Throws(KommandException::class) +actual fun outputCommand(command: COpaquePointer?): Output = run { + Output.from(output_command(command)) +} + +@Throws(KommandException::class) +actual fun statusCommand(command: COpaquePointer?): Int = run { + Int.from(status_command(command)) +} diff --git a/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Extension.linuxArm64.kt b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Extension.linuxArm64.kt new file mode 100644 index 0000000..d51b5e0 --- /dev/null +++ b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Extension.linuxArm64.kt @@ -0,0 +1,99 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.ErrorType +import com.kgit2.exception.KommandException +import com.kgit2.io.Output +import com.kgit2.process.Child +import com.kgit2.process.Stdio +import kommand_core.drop_output +import kommand_core.drop_string +import kommand_core.into_output +import kommand_core.void_to_string +import kotlinx.cinterop.ByteVar +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.CValue +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.toKString + +inline fun CPointer.asString(): String { + val result = this.toKString() + drop_string(this) + return result +} + +@Throws(KommandException::class) +fun Child.Companion.from(result: CValue): Child = memScoped { + if (result.ptr.pointed.ok != null) { + val child = Child(result.ptr.pointed.ok) + child.updateIO() + child + } else { + throw KommandException( + result.ptr.pointed.err?.asString(), + result.ptr.pointed.error_type.to() + ) + } +} + +@Throws(KommandException::class) +fun Output.Companion.from(result: CValue): Output = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + val output = into_output(result.ptr.pointed.ok) + val newOutput = Output( + output.getPointer(memScope).pointed.exit_code, + output.getPointer(memScope).pointed.stdout_content?.toKString(), + output.getPointer(memScope).pointed.stderr_content?.toKString(), + ) + drop_output(output) + newOutput + } +} + +@Throws(KommandException::class) +fun Int.Companion.from(result: CValue): Int = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + return result.ptr.pointed.ok + } +} + +@Throws(KommandException::class) +fun String.Companion.from(result: CValue): String? = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + return void_to_string(result.ptr.pointed.ok)?.asString() + } +} + +@Throws(KommandException::class) +fun CValue.unwrap() = memScoped { + if (this@unwrap.ptr.pointed.err != null) { + val errPtr = this@unwrap.ptr.pointed.err!! + throw KommandException(errPtr.asString(), this@unwrap.ptr.pointed.error_type.to()) + } +} + +fun Stdio.to(): kommand_core.Stdio { + return when (this) { + Stdio.Inherit -> kommand_core.Stdio.Inherit + Stdio.Pipe -> kommand_core.Stdio.Pipe + Stdio.Null -> kommand_core.Stdio.Null + } +} + +fun kommand_core.ErrorType.to(): ErrorType { + return when (this) { + kommand_core.ErrorType.None -> ErrorType.None + kommand_core.ErrorType.Io -> ErrorType.IO + kommand_core.ErrorType.Utf8 -> ErrorType.Utf8 + kommand_core.ErrorType.Unknown -> ErrorType.Unknown + } +} diff --git a/src/linuxArm64Main/kotlin/com/kgit2/wrapper/IO.kt b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/IO.kt new file mode 100644 index 0000000..35d023a --- /dev/null +++ b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/IO.kt @@ -0,0 +1,55 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import kommand_core.drop_stderr +import kommand_core.drop_stdin +import kommand_core.drop_stdout +import kommand_core.flush_stdin +import kommand_core.read_all_stderr +import kommand_core.read_all_stdout +import kommand_core.read_line_stderr +import kommand_core.read_line_stdout +import kommand_core.write_line_stdin +import kotlinx.cinterop.COpaquePointer + +@Throws(KommandException::class) +actual fun readLineStdout(stdout: COpaquePointer?): String? = run { + String.from(read_line_stdout(stdout)) +} + +@Throws(KommandException::class) +actual fun readAllStdout(stdout: COpaquePointer?): String? = run { + String.from(read_all_stdout(stdout)) +} + +@Throws(KommandException::class) +actual fun readLineStderr(stderr: COpaquePointer?): String? = run { + String.from(read_line_stderr(stderr)) +} + +@Throws(KommandException::class) +actual fun readAllStderr(stderr: COpaquePointer?): String? = run { + String.from(read_all_stderr(stderr)) +} + +@Throws(KommandException::class) +actual fun writeLineStdin(stdin: COpaquePointer?, line: String) = run { + write_line_stdin(stdin, line).unwrap() +} + +@Throws(KommandException::class) +actual fun flushStdin(stdin: COpaquePointer?) = run { + flush_stdin(stdin).unwrap() +} + +actual fun dropStdin(stdin: COpaquePointer?) { + drop_stdin(stdin) +} + +actual fun dropStdout(stdout: COpaquePointer?) { + drop_stdout(stdout) +} + +actual fun dropStderr(stderr: COpaquePointer?) { + drop_stderr(stderr) +} diff --git a/src/linuxX64Main/kotlin/com/kgit2/wrapper/Child.linuxX64.kt b/src/linuxX64Main/kotlin/com/kgit2/wrapper/Child.linuxX64.kt new file mode 100644 index 0000000..97c4b72 --- /dev/null +++ b/src/linuxX64Main/kotlin/com/kgit2/wrapper/Child.linuxX64.kt @@ -0,0 +1,50 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import com.kgit2.io.BufferedReader +import com.kgit2.io.BufferedWriter +import com.kgit2.io.Output +import com.kgit2.io.ReaderType +import kommand_core.drop_child +import kommand_core.id_child +import kommand_core.kill_child +import kommand_core.stderr_child +import kommand_core.stdin_child +import kommand_core.wait_child +import kommand_core.wait_with_output_child +import kotlinx.cinterop.COpaquePointer + +actual fun idChild(child: COpaquePointer?): UInt { + return id_child(child) +} + +actual fun dropChild(child: COpaquePointer?) { + drop_child(child) +} + +@Throws(KommandException::class) +actual fun killChild(child: COpaquePointer?) = run { + kill_child(child).unwrap() +} + +@Throws(KommandException::class) +actual fun waitChild(child: COpaquePointer?): Int = run { + Int.from(wait_child(child)) +} + +@Throws(KommandException::class) +actual fun waitWithOutputChild(child: COpaquePointer?): Output = run { + Output.from(wait_with_output_child(child)) +} + +actual fun stdinChild(child: COpaquePointer?): BufferedWriter? { + return BufferedWriter(stdin_child(child)) +} + +actual fun stdoutChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(stdin_child(child), ReaderType.STDOUT) +} + +actual fun stderrChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(stderr_child(child), ReaderType.STDERR) +} diff --git a/src/linuxX64Main/kotlin/com/kgit2/wrapper/Command.linuxX64.kt b/src/linuxX64Main/kotlin/com/kgit2/wrapper/Command.linuxX64.kt new file mode 100644 index 0000000..dbe5540 --- /dev/null +++ b/src/linuxX64Main/kotlin/com/kgit2/wrapper/Command.linuxX64.kt @@ -0,0 +1,85 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import com.kgit2.io.Output +import com.kgit2.process.Child +import com.kgit2.process.Stdio +import kommand_core.arg_command +import kommand_core.current_dir_command +import kommand_core.debug_command +import kommand_core.display_command +import kommand_core.drop_command +import kommand_core.env_clear_command +import kommand_core.env_command +import kommand_core.new_command +import kommand_core.output_command +import kommand_core.remove_env_command +import kommand_core.spawn_command +import kommand_core.status_command +import kommand_core.stderr_command +import kommand_core.stdin_command +import kommand_core.stdout_command +import kotlinx.cinterop.COpaquePointer + +actual fun newCommand(command: String): COpaquePointer? { + return new_command(command) +} + +actual fun dropCommand(command: COpaquePointer?) { + return drop_command(command) +} + +actual fun displayCommand(command: COpaquePointer?): String? { + return display_command(command)?.asString() +} + +actual fun debugCommand(command: COpaquePointer?): String? { + return debug_command(command)?.asString() +} + +actual fun argCommand(command: COpaquePointer?, arg: String) { + return arg_command(command, arg) +} + +actual fun envCommand(command: COpaquePointer?, key: String, value: String) { + return env_command(command, key, value) +} + +actual fun removeEnvCommand(command: COpaquePointer?, key: String) { + return remove_env_command(command, key) +} + +actual fun envClearCommand(command: COpaquePointer?) { + return env_clear_command(command) +} + +actual fun currentDirCommand(command: COpaquePointer?, dir: String) { + return current_dir_command(command, dir) +} + +actual fun stdinCommand(command: COpaquePointer?, stdio: Stdio) { + stdin_command(command, stdio.to()) +} + +actual fun stdoutCommand(command: COpaquePointer?, stdio: Stdio) { + stdout_command(command, stdio.to()) +} + +actual fun stderrCommand(command: COpaquePointer?, stdio: Stdio) { + stderr_command(command, stdio.to()) +} + +@Throws(KommandException::class) +actual fun spawnCommand(command: COpaquePointer?): Child = run { + Child.from(spawn_command(command)) +} + +@Throws(KommandException::class) +actual fun outputCommand(command: COpaquePointer?): Output = run { + Output.from(output_command(command)) +} + +@Throws(KommandException::class) +actual fun statusCommand(command: COpaquePointer?): Int = run { + Int.from(status_command(command)) +} diff --git a/src/linuxX64Main/kotlin/com/kgit2/wrapper/Extension.linuxX64.kt b/src/linuxX64Main/kotlin/com/kgit2/wrapper/Extension.linuxX64.kt new file mode 100644 index 0000000..d51b5e0 --- /dev/null +++ b/src/linuxX64Main/kotlin/com/kgit2/wrapper/Extension.linuxX64.kt @@ -0,0 +1,99 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.ErrorType +import com.kgit2.exception.KommandException +import com.kgit2.io.Output +import com.kgit2.process.Child +import com.kgit2.process.Stdio +import kommand_core.drop_output +import kommand_core.drop_string +import kommand_core.into_output +import kommand_core.void_to_string +import kotlinx.cinterop.ByteVar +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.CValue +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.toKString + +inline fun CPointer.asString(): String { + val result = this.toKString() + drop_string(this) + return result +} + +@Throws(KommandException::class) +fun Child.Companion.from(result: CValue): Child = memScoped { + if (result.ptr.pointed.ok != null) { + val child = Child(result.ptr.pointed.ok) + child.updateIO() + child + } else { + throw KommandException( + result.ptr.pointed.err?.asString(), + result.ptr.pointed.error_type.to() + ) + } +} + +@Throws(KommandException::class) +fun Output.Companion.from(result: CValue): Output = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + val output = into_output(result.ptr.pointed.ok) + val newOutput = Output( + output.getPointer(memScope).pointed.exit_code, + output.getPointer(memScope).pointed.stdout_content?.toKString(), + output.getPointer(memScope).pointed.stderr_content?.toKString(), + ) + drop_output(output) + newOutput + } +} + +@Throws(KommandException::class) +fun Int.Companion.from(result: CValue): Int = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + return result.ptr.pointed.ok + } +} + +@Throws(KommandException::class) +fun String.Companion.from(result: CValue): String? = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + return void_to_string(result.ptr.pointed.ok)?.asString() + } +} + +@Throws(KommandException::class) +fun CValue.unwrap() = memScoped { + if (this@unwrap.ptr.pointed.err != null) { + val errPtr = this@unwrap.ptr.pointed.err!! + throw KommandException(errPtr.asString(), this@unwrap.ptr.pointed.error_type.to()) + } +} + +fun Stdio.to(): kommand_core.Stdio { + return when (this) { + Stdio.Inherit -> kommand_core.Stdio.Inherit + Stdio.Pipe -> kommand_core.Stdio.Pipe + Stdio.Null -> kommand_core.Stdio.Null + } +} + +fun kommand_core.ErrorType.to(): ErrorType { + return when (this) { + kommand_core.ErrorType.None -> ErrorType.None + kommand_core.ErrorType.Io -> ErrorType.IO + kommand_core.ErrorType.Utf8 -> ErrorType.Utf8 + kommand_core.ErrorType.Unknown -> ErrorType.Unknown + } +} diff --git a/src/linuxX64Main/kotlin/com/kgit2/wrapper/IO.kt b/src/linuxX64Main/kotlin/com/kgit2/wrapper/IO.kt new file mode 100644 index 0000000..35d023a --- /dev/null +++ b/src/linuxX64Main/kotlin/com/kgit2/wrapper/IO.kt @@ -0,0 +1,55 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import kommand_core.drop_stderr +import kommand_core.drop_stdin +import kommand_core.drop_stdout +import kommand_core.flush_stdin +import kommand_core.read_all_stderr +import kommand_core.read_all_stdout +import kommand_core.read_line_stderr +import kommand_core.read_line_stdout +import kommand_core.write_line_stdin +import kotlinx.cinterop.COpaquePointer + +@Throws(KommandException::class) +actual fun readLineStdout(stdout: COpaquePointer?): String? = run { + String.from(read_line_stdout(stdout)) +} + +@Throws(KommandException::class) +actual fun readAllStdout(stdout: COpaquePointer?): String? = run { + String.from(read_all_stdout(stdout)) +} + +@Throws(KommandException::class) +actual fun readLineStderr(stderr: COpaquePointer?): String? = run { + String.from(read_line_stderr(stderr)) +} + +@Throws(KommandException::class) +actual fun readAllStderr(stderr: COpaquePointer?): String? = run { + String.from(read_all_stderr(stderr)) +} + +@Throws(KommandException::class) +actual fun writeLineStdin(stdin: COpaquePointer?, line: String) = run { + write_line_stdin(stdin, line).unwrap() +} + +@Throws(KommandException::class) +actual fun flushStdin(stdin: COpaquePointer?) = run { + flush_stdin(stdin).unwrap() +} + +actual fun dropStdin(stdin: COpaquePointer?) { + drop_stdin(stdin) +} + +actual fun dropStdout(stdout: COpaquePointer?) { + drop_stdout(stdout) +} + +actual fun dropStderr(stderr: COpaquePointer?) { + drop_stderr(stderr) +} diff --git a/src/macosArm64Main/kotlin/com/kgit2/wrapper/Child.macosArm64.kt b/src/macosArm64Main/kotlin/com/kgit2/wrapper/Child.macosArm64.kt new file mode 100644 index 0000000..97c4b72 --- /dev/null +++ b/src/macosArm64Main/kotlin/com/kgit2/wrapper/Child.macosArm64.kt @@ -0,0 +1,50 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import com.kgit2.io.BufferedReader +import com.kgit2.io.BufferedWriter +import com.kgit2.io.Output +import com.kgit2.io.ReaderType +import kommand_core.drop_child +import kommand_core.id_child +import kommand_core.kill_child +import kommand_core.stderr_child +import kommand_core.stdin_child +import kommand_core.wait_child +import kommand_core.wait_with_output_child +import kotlinx.cinterop.COpaquePointer + +actual fun idChild(child: COpaquePointer?): UInt { + return id_child(child) +} + +actual fun dropChild(child: COpaquePointer?) { + drop_child(child) +} + +@Throws(KommandException::class) +actual fun killChild(child: COpaquePointer?) = run { + kill_child(child).unwrap() +} + +@Throws(KommandException::class) +actual fun waitChild(child: COpaquePointer?): Int = run { + Int.from(wait_child(child)) +} + +@Throws(KommandException::class) +actual fun waitWithOutputChild(child: COpaquePointer?): Output = run { + Output.from(wait_with_output_child(child)) +} + +actual fun stdinChild(child: COpaquePointer?): BufferedWriter? { + return BufferedWriter(stdin_child(child)) +} + +actual fun stdoutChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(stdin_child(child), ReaderType.STDOUT) +} + +actual fun stderrChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(stderr_child(child), ReaderType.STDERR) +} diff --git a/src/macosArm64Main/kotlin/com/kgit2/wrapper/Command.macosArm64.kt b/src/macosArm64Main/kotlin/com/kgit2/wrapper/Command.macosArm64.kt new file mode 100644 index 0000000..dbe5540 --- /dev/null +++ b/src/macosArm64Main/kotlin/com/kgit2/wrapper/Command.macosArm64.kt @@ -0,0 +1,85 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import com.kgit2.io.Output +import com.kgit2.process.Child +import com.kgit2.process.Stdio +import kommand_core.arg_command +import kommand_core.current_dir_command +import kommand_core.debug_command +import kommand_core.display_command +import kommand_core.drop_command +import kommand_core.env_clear_command +import kommand_core.env_command +import kommand_core.new_command +import kommand_core.output_command +import kommand_core.remove_env_command +import kommand_core.spawn_command +import kommand_core.status_command +import kommand_core.stderr_command +import kommand_core.stdin_command +import kommand_core.stdout_command +import kotlinx.cinterop.COpaquePointer + +actual fun newCommand(command: String): COpaquePointer? { + return new_command(command) +} + +actual fun dropCommand(command: COpaquePointer?) { + return drop_command(command) +} + +actual fun displayCommand(command: COpaquePointer?): String? { + return display_command(command)?.asString() +} + +actual fun debugCommand(command: COpaquePointer?): String? { + return debug_command(command)?.asString() +} + +actual fun argCommand(command: COpaquePointer?, arg: String) { + return arg_command(command, arg) +} + +actual fun envCommand(command: COpaquePointer?, key: String, value: String) { + return env_command(command, key, value) +} + +actual fun removeEnvCommand(command: COpaquePointer?, key: String) { + return remove_env_command(command, key) +} + +actual fun envClearCommand(command: COpaquePointer?) { + return env_clear_command(command) +} + +actual fun currentDirCommand(command: COpaquePointer?, dir: String) { + return current_dir_command(command, dir) +} + +actual fun stdinCommand(command: COpaquePointer?, stdio: Stdio) { + stdin_command(command, stdio.to()) +} + +actual fun stdoutCommand(command: COpaquePointer?, stdio: Stdio) { + stdout_command(command, stdio.to()) +} + +actual fun stderrCommand(command: COpaquePointer?, stdio: Stdio) { + stderr_command(command, stdio.to()) +} + +@Throws(KommandException::class) +actual fun spawnCommand(command: COpaquePointer?): Child = run { + Child.from(spawn_command(command)) +} + +@Throws(KommandException::class) +actual fun outputCommand(command: COpaquePointer?): Output = run { + Output.from(output_command(command)) +} + +@Throws(KommandException::class) +actual fun statusCommand(command: COpaquePointer?): Int = run { + Int.from(status_command(command)) +} diff --git a/src/macosArm64Main/kotlin/com/kgit2/wrapper/Extension.macosArm64.kt b/src/macosArm64Main/kotlin/com/kgit2/wrapper/Extension.macosArm64.kt new file mode 100644 index 0000000..d51b5e0 --- /dev/null +++ b/src/macosArm64Main/kotlin/com/kgit2/wrapper/Extension.macosArm64.kt @@ -0,0 +1,99 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.ErrorType +import com.kgit2.exception.KommandException +import com.kgit2.io.Output +import com.kgit2.process.Child +import com.kgit2.process.Stdio +import kommand_core.drop_output +import kommand_core.drop_string +import kommand_core.into_output +import kommand_core.void_to_string +import kotlinx.cinterop.ByteVar +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.CValue +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.toKString + +inline fun CPointer.asString(): String { + val result = this.toKString() + drop_string(this) + return result +} + +@Throws(KommandException::class) +fun Child.Companion.from(result: CValue): Child = memScoped { + if (result.ptr.pointed.ok != null) { + val child = Child(result.ptr.pointed.ok) + child.updateIO() + child + } else { + throw KommandException( + result.ptr.pointed.err?.asString(), + result.ptr.pointed.error_type.to() + ) + } +} + +@Throws(KommandException::class) +fun Output.Companion.from(result: CValue): Output = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + val output = into_output(result.ptr.pointed.ok) + val newOutput = Output( + output.getPointer(memScope).pointed.exit_code, + output.getPointer(memScope).pointed.stdout_content?.toKString(), + output.getPointer(memScope).pointed.stderr_content?.toKString(), + ) + drop_output(output) + newOutput + } +} + +@Throws(KommandException::class) +fun Int.Companion.from(result: CValue): Int = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + return result.ptr.pointed.ok + } +} + +@Throws(KommandException::class) +fun String.Companion.from(result: CValue): String? = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + return void_to_string(result.ptr.pointed.ok)?.asString() + } +} + +@Throws(KommandException::class) +fun CValue.unwrap() = memScoped { + if (this@unwrap.ptr.pointed.err != null) { + val errPtr = this@unwrap.ptr.pointed.err!! + throw KommandException(errPtr.asString(), this@unwrap.ptr.pointed.error_type.to()) + } +} + +fun Stdio.to(): kommand_core.Stdio { + return when (this) { + Stdio.Inherit -> kommand_core.Stdio.Inherit + Stdio.Pipe -> kommand_core.Stdio.Pipe + Stdio.Null -> kommand_core.Stdio.Null + } +} + +fun kommand_core.ErrorType.to(): ErrorType { + return when (this) { + kommand_core.ErrorType.None -> ErrorType.None + kommand_core.ErrorType.Io -> ErrorType.IO + kommand_core.ErrorType.Utf8 -> ErrorType.Utf8 + kommand_core.ErrorType.Unknown -> ErrorType.Unknown + } +} diff --git a/src/macosArm64Main/kotlin/com/kgit2/wrapper/IO.kt b/src/macosArm64Main/kotlin/com/kgit2/wrapper/IO.kt new file mode 100644 index 0000000..35d023a --- /dev/null +++ b/src/macosArm64Main/kotlin/com/kgit2/wrapper/IO.kt @@ -0,0 +1,55 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import kommand_core.drop_stderr +import kommand_core.drop_stdin +import kommand_core.drop_stdout +import kommand_core.flush_stdin +import kommand_core.read_all_stderr +import kommand_core.read_all_stdout +import kommand_core.read_line_stderr +import kommand_core.read_line_stdout +import kommand_core.write_line_stdin +import kotlinx.cinterop.COpaquePointer + +@Throws(KommandException::class) +actual fun readLineStdout(stdout: COpaquePointer?): String? = run { + String.from(read_line_stdout(stdout)) +} + +@Throws(KommandException::class) +actual fun readAllStdout(stdout: COpaquePointer?): String? = run { + String.from(read_all_stdout(stdout)) +} + +@Throws(KommandException::class) +actual fun readLineStderr(stderr: COpaquePointer?): String? = run { + String.from(read_line_stderr(stderr)) +} + +@Throws(KommandException::class) +actual fun readAllStderr(stderr: COpaquePointer?): String? = run { + String.from(read_all_stderr(stderr)) +} + +@Throws(KommandException::class) +actual fun writeLineStdin(stdin: COpaquePointer?, line: String) = run { + write_line_stdin(stdin, line).unwrap() +} + +@Throws(KommandException::class) +actual fun flushStdin(stdin: COpaquePointer?) = run { + flush_stdin(stdin).unwrap() +} + +actual fun dropStdin(stdin: COpaquePointer?) { + drop_stdin(stdin) +} + +actual fun dropStdout(stdout: COpaquePointer?) { + drop_stdout(stdout) +} + +actual fun dropStderr(stderr: COpaquePointer?) { + drop_stderr(stderr) +} diff --git a/src/macosX64Main/kotlin/com/kgit2/wrapper/Child.macosX64.kt b/src/macosX64Main/kotlin/com/kgit2/wrapper/Child.macosX64.kt new file mode 100644 index 0000000..97c4b72 --- /dev/null +++ b/src/macosX64Main/kotlin/com/kgit2/wrapper/Child.macosX64.kt @@ -0,0 +1,50 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import com.kgit2.io.BufferedReader +import com.kgit2.io.BufferedWriter +import com.kgit2.io.Output +import com.kgit2.io.ReaderType +import kommand_core.drop_child +import kommand_core.id_child +import kommand_core.kill_child +import kommand_core.stderr_child +import kommand_core.stdin_child +import kommand_core.wait_child +import kommand_core.wait_with_output_child +import kotlinx.cinterop.COpaquePointer + +actual fun idChild(child: COpaquePointer?): UInt { + return id_child(child) +} + +actual fun dropChild(child: COpaquePointer?) { + drop_child(child) +} + +@Throws(KommandException::class) +actual fun killChild(child: COpaquePointer?) = run { + kill_child(child).unwrap() +} + +@Throws(KommandException::class) +actual fun waitChild(child: COpaquePointer?): Int = run { + Int.from(wait_child(child)) +} + +@Throws(KommandException::class) +actual fun waitWithOutputChild(child: COpaquePointer?): Output = run { + Output.from(wait_with_output_child(child)) +} + +actual fun stdinChild(child: COpaquePointer?): BufferedWriter? { + return BufferedWriter(stdin_child(child)) +} + +actual fun stdoutChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(stdin_child(child), ReaderType.STDOUT) +} + +actual fun stderrChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(stderr_child(child), ReaderType.STDERR) +} diff --git a/src/macosX64Main/kotlin/com/kgit2/wrapper/Command.macosX64.kt b/src/macosX64Main/kotlin/com/kgit2/wrapper/Command.macosX64.kt new file mode 100644 index 0000000..dbe5540 --- /dev/null +++ b/src/macosX64Main/kotlin/com/kgit2/wrapper/Command.macosX64.kt @@ -0,0 +1,85 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import com.kgit2.io.Output +import com.kgit2.process.Child +import com.kgit2.process.Stdio +import kommand_core.arg_command +import kommand_core.current_dir_command +import kommand_core.debug_command +import kommand_core.display_command +import kommand_core.drop_command +import kommand_core.env_clear_command +import kommand_core.env_command +import kommand_core.new_command +import kommand_core.output_command +import kommand_core.remove_env_command +import kommand_core.spawn_command +import kommand_core.status_command +import kommand_core.stderr_command +import kommand_core.stdin_command +import kommand_core.stdout_command +import kotlinx.cinterop.COpaquePointer + +actual fun newCommand(command: String): COpaquePointer? { + return new_command(command) +} + +actual fun dropCommand(command: COpaquePointer?) { + return drop_command(command) +} + +actual fun displayCommand(command: COpaquePointer?): String? { + return display_command(command)?.asString() +} + +actual fun debugCommand(command: COpaquePointer?): String? { + return debug_command(command)?.asString() +} + +actual fun argCommand(command: COpaquePointer?, arg: String) { + return arg_command(command, arg) +} + +actual fun envCommand(command: COpaquePointer?, key: String, value: String) { + return env_command(command, key, value) +} + +actual fun removeEnvCommand(command: COpaquePointer?, key: String) { + return remove_env_command(command, key) +} + +actual fun envClearCommand(command: COpaquePointer?) { + return env_clear_command(command) +} + +actual fun currentDirCommand(command: COpaquePointer?, dir: String) { + return current_dir_command(command, dir) +} + +actual fun stdinCommand(command: COpaquePointer?, stdio: Stdio) { + stdin_command(command, stdio.to()) +} + +actual fun stdoutCommand(command: COpaquePointer?, stdio: Stdio) { + stdout_command(command, stdio.to()) +} + +actual fun stderrCommand(command: COpaquePointer?, stdio: Stdio) { + stderr_command(command, stdio.to()) +} + +@Throws(KommandException::class) +actual fun spawnCommand(command: COpaquePointer?): Child = run { + Child.from(spawn_command(command)) +} + +@Throws(KommandException::class) +actual fun outputCommand(command: COpaquePointer?): Output = run { + Output.from(output_command(command)) +} + +@Throws(KommandException::class) +actual fun statusCommand(command: COpaquePointer?): Int = run { + Int.from(status_command(command)) +} diff --git a/src/macosX64Main/kotlin/com/kgit2/wrapper/Extension.macosX64.kt b/src/macosX64Main/kotlin/com/kgit2/wrapper/Extension.macosX64.kt new file mode 100644 index 0000000..d51b5e0 --- /dev/null +++ b/src/macosX64Main/kotlin/com/kgit2/wrapper/Extension.macosX64.kt @@ -0,0 +1,99 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.ErrorType +import com.kgit2.exception.KommandException +import com.kgit2.io.Output +import com.kgit2.process.Child +import com.kgit2.process.Stdio +import kommand_core.drop_output +import kommand_core.drop_string +import kommand_core.into_output +import kommand_core.void_to_string +import kotlinx.cinterop.ByteVar +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.CValue +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.toKString + +inline fun CPointer.asString(): String { + val result = this.toKString() + drop_string(this) + return result +} + +@Throws(KommandException::class) +fun Child.Companion.from(result: CValue): Child = memScoped { + if (result.ptr.pointed.ok != null) { + val child = Child(result.ptr.pointed.ok) + child.updateIO() + child + } else { + throw KommandException( + result.ptr.pointed.err?.asString(), + result.ptr.pointed.error_type.to() + ) + } +} + +@Throws(KommandException::class) +fun Output.Companion.from(result: CValue): Output = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + val output = into_output(result.ptr.pointed.ok) + val newOutput = Output( + output.getPointer(memScope).pointed.exit_code, + output.getPointer(memScope).pointed.stdout_content?.toKString(), + output.getPointer(memScope).pointed.stderr_content?.toKString(), + ) + drop_output(output) + newOutput + } +} + +@Throws(KommandException::class) +fun Int.Companion.from(result: CValue): Int = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + return result.ptr.pointed.ok + } +} + +@Throws(KommandException::class) +fun String.Companion.from(result: CValue): String? = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + return void_to_string(result.ptr.pointed.ok)?.asString() + } +} + +@Throws(KommandException::class) +fun CValue.unwrap() = memScoped { + if (this@unwrap.ptr.pointed.err != null) { + val errPtr = this@unwrap.ptr.pointed.err!! + throw KommandException(errPtr.asString(), this@unwrap.ptr.pointed.error_type.to()) + } +} + +fun Stdio.to(): kommand_core.Stdio { + return when (this) { + Stdio.Inherit -> kommand_core.Stdio.Inherit + Stdio.Pipe -> kommand_core.Stdio.Pipe + Stdio.Null -> kommand_core.Stdio.Null + } +} + +fun kommand_core.ErrorType.to(): ErrorType { + return when (this) { + kommand_core.ErrorType.None -> ErrorType.None + kommand_core.ErrorType.Io -> ErrorType.IO + kommand_core.ErrorType.Utf8 -> ErrorType.Utf8 + kommand_core.ErrorType.Unknown -> ErrorType.Unknown + } +} diff --git a/src/macosX64Main/kotlin/com/kgit2/wrapper/IO.kt b/src/macosX64Main/kotlin/com/kgit2/wrapper/IO.kt new file mode 100644 index 0000000..35d023a --- /dev/null +++ b/src/macosX64Main/kotlin/com/kgit2/wrapper/IO.kt @@ -0,0 +1,55 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import kommand_core.drop_stderr +import kommand_core.drop_stdin +import kommand_core.drop_stdout +import kommand_core.flush_stdin +import kommand_core.read_all_stderr +import kommand_core.read_all_stdout +import kommand_core.read_line_stderr +import kommand_core.read_line_stdout +import kommand_core.write_line_stdin +import kotlinx.cinterop.COpaquePointer + +@Throws(KommandException::class) +actual fun readLineStdout(stdout: COpaquePointer?): String? = run { + String.from(read_line_stdout(stdout)) +} + +@Throws(KommandException::class) +actual fun readAllStdout(stdout: COpaquePointer?): String? = run { + String.from(read_all_stdout(stdout)) +} + +@Throws(KommandException::class) +actual fun readLineStderr(stderr: COpaquePointer?): String? = run { + String.from(read_line_stderr(stderr)) +} + +@Throws(KommandException::class) +actual fun readAllStderr(stderr: COpaquePointer?): String? = run { + String.from(read_all_stderr(stderr)) +} + +@Throws(KommandException::class) +actual fun writeLineStdin(stdin: COpaquePointer?, line: String) = run { + write_line_stdin(stdin, line).unwrap() +} + +@Throws(KommandException::class) +actual fun flushStdin(stdin: COpaquePointer?) = run { + flush_stdin(stdin).unwrap() +} + +actual fun dropStdin(stdin: COpaquePointer?) { + drop_stdin(stdin) +} + +actual fun dropStdout(stdout: COpaquePointer?) { + drop_stdout(stdout) +} + +actual fun dropStderr(stderr: COpaquePointer?) { + drop_stderr(stderr) +} diff --git a/src/mingwX64Main/kotlin/com/kgit2/wrapper/Child.mingwX64.kt b/src/mingwX64Main/kotlin/com/kgit2/wrapper/Child.mingwX64.kt new file mode 100644 index 0000000..97c4b72 --- /dev/null +++ b/src/mingwX64Main/kotlin/com/kgit2/wrapper/Child.mingwX64.kt @@ -0,0 +1,50 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import com.kgit2.io.BufferedReader +import com.kgit2.io.BufferedWriter +import com.kgit2.io.Output +import com.kgit2.io.ReaderType +import kommand_core.drop_child +import kommand_core.id_child +import kommand_core.kill_child +import kommand_core.stderr_child +import kommand_core.stdin_child +import kommand_core.wait_child +import kommand_core.wait_with_output_child +import kotlinx.cinterop.COpaquePointer + +actual fun idChild(child: COpaquePointer?): UInt { + return id_child(child) +} + +actual fun dropChild(child: COpaquePointer?) { + drop_child(child) +} + +@Throws(KommandException::class) +actual fun killChild(child: COpaquePointer?) = run { + kill_child(child).unwrap() +} + +@Throws(KommandException::class) +actual fun waitChild(child: COpaquePointer?): Int = run { + Int.from(wait_child(child)) +} + +@Throws(KommandException::class) +actual fun waitWithOutputChild(child: COpaquePointer?): Output = run { + Output.from(wait_with_output_child(child)) +} + +actual fun stdinChild(child: COpaquePointer?): BufferedWriter? { + return BufferedWriter(stdin_child(child)) +} + +actual fun stdoutChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(stdin_child(child), ReaderType.STDOUT) +} + +actual fun stderrChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(stderr_child(child), ReaderType.STDERR) +} diff --git a/src/mingwX64Main/kotlin/com/kgit2/wrapper/Command.mingwX64.kt b/src/mingwX64Main/kotlin/com/kgit2/wrapper/Command.mingwX64.kt new file mode 100644 index 0000000..dbe5540 --- /dev/null +++ b/src/mingwX64Main/kotlin/com/kgit2/wrapper/Command.mingwX64.kt @@ -0,0 +1,85 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import com.kgit2.io.Output +import com.kgit2.process.Child +import com.kgit2.process.Stdio +import kommand_core.arg_command +import kommand_core.current_dir_command +import kommand_core.debug_command +import kommand_core.display_command +import kommand_core.drop_command +import kommand_core.env_clear_command +import kommand_core.env_command +import kommand_core.new_command +import kommand_core.output_command +import kommand_core.remove_env_command +import kommand_core.spawn_command +import kommand_core.status_command +import kommand_core.stderr_command +import kommand_core.stdin_command +import kommand_core.stdout_command +import kotlinx.cinterop.COpaquePointer + +actual fun newCommand(command: String): COpaquePointer? { + return new_command(command) +} + +actual fun dropCommand(command: COpaquePointer?) { + return drop_command(command) +} + +actual fun displayCommand(command: COpaquePointer?): String? { + return display_command(command)?.asString() +} + +actual fun debugCommand(command: COpaquePointer?): String? { + return debug_command(command)?.asString() +} + +actual fun argCommand(command: COpaquePointer?, arg: String) { + return arg_command(command, arg) +} + +actual fun envCommand(command: COpaquePointer?, key: String, value: String) { + return env_command(command, key, value) +} + +actual fun removeEnvCommand(command: COpaquePointer?, key: String) { + return remove_env_command(command, key) +} + +actual fun envClearCommand(command: COpaquePointer?) { + return env_clear_command(command) +} + +actual fun currentDirCommand(command: COpaquePointer?, dir: String) { + return current_dir_command(command, dir) +} + +actual fun stdinCommand(command: COpaquePointer?, stdio: Stdio) { + stdin_command(command, stdio.to()) +} + +actual fun stdoutCommand(command: COpaquePointer?, stdio: Stdio) { + stdout_command(command, stdio.to()) +} + +actual fun stderrCommand(command: COpaquePointer?, stdio: Stdio) { + stderr_command(command, stdio.to()) +} + +@Throws(KommandException::class) +actual fun spawnCommand(command: COpaquePointer?): Child = run { + Child.from(spawn_command(command)) +} + +@Throws(KommandException::class) +actual fun outputCommand(command: COpaquePointer?): Output = run { + Output.from(output_command(command)) +} + +@Throws(KommandException::class) +actual fun statusCommand(command: COpaquePointer?): Int = run { + Int.from(status_command(command)) +} diff --git a/src/mingwX64Main/kotlin/com/kgit2/wrapper/Extension.mingwX64.kt b/src/mingwX64Main/kotlin/com/kgit2/wrapper/Extension.mingwX64.kt new file mode 100644 index 0000000..d51b5e0 --- /dev/null +++ b/src/mingwX64Main/kotlin/com/kgit2/wrapper/Extension.mingwX64.kt @@ -0,0 +1,99 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.ErrorType +import com.kgit2.exception.KommandException +import com.kgit2.io.Output +import com.kgit2.process.Child +import com.kgit2.process.Stdio +import kommand_core.drop_output +import kommand_core.drop_string +import kommand_core.into_output +import kommand_core.void_to_string +import kotlinx.cinterop.ByteVar +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.CValue +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.toKString + +inline fun CPointer.asString(): String { + val result = this.toKString() + drop_string(this) + return result +} + +@Throws(KommandException::class) +fun Child.Companion.from(result: CValue): Child = memScoped { + if (result.ptr.pointed.ok != null) { + val child = Child(result.ptr.pointed.ok) + child.updateIO() + child + } else { + throw KommandException( + result.ptr.pointed.err?.asString(), + result.ptr.pointed.error_type.to() + ) + } +} + +@Throws(KommandException::class) +fun Output.Companion.from(result: CValue): Output = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + val output = into_output(result.ptr.pointed.ok) + val newOutput = Output( + output.getPointer(memScope).pointed.exit_code, + output.getPointer(memScope).pointed.stdout_content?.toKString(), + output.getPointer(memScope).pointed.stderr_content?.toKString(), + ) + drop_output(output) + newOutput + } +} + +@Throws(KommandException::class) +fun Int.Companion.from(result: CValue): Int = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + return result.ptr.pointed.ok + } +} + +@Throws(KommandException::class) +fun String.Companion.from(result: CValue): String? = memScoped { + if (result.ptr.pointed.err != null) { + val errPtr = result.ptr.pointed.err!! + throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) + } else { + return void_to_string(result.ptr.pointed.ok)?.asString() + } +} + +@Throws(KommandException::class) +fun CValue.unwrap() = memScoped { + if (this@unwrap.ptr.pointed.err != null) { + val errPtr = this@unwrap.ptr.pointed.err!! + throw KommandException(errPtr.asString(), this@unwrap.ptr.pointed.error_type.to()) + } +} + +fun Stdio.to(): kommand_core.Stdio { + return when (this) { + Stdio.Inherit -> kommand_core.Stdio.Inherit + Stdio.Pipe -> kommand_core.Stdio.Pipe + Stdio.Null -> kommand_core.Stdio.Null + } +} + +fun kommand_core.ErrorType.to(): ErrorType { + return when (this) { + kommand_core.ErrorType.None -> ErrorType.None + kommand_core.ErrorType.Io -> ErrorType.IO + kommand_core.ErrorType.Utf8 -> ErrorType.Utf8 + kommand_core.ErrorType.Unknown -> ErrorType.Unknown + } +} diff --git a/src/mingwX64Main/kotlin/com/kgit2/wrapper/IO.kt b/src/mingwX64Main/kotlin/com/kgit2/wrapper/IO.kt new file mode 100644 index 0000000..35d023a --- /dev/null +++ b/src/mingwX64Main/kotlin/com/kgit2/wrapper/IO.kt @@ -0,0 +1,55 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import kommand_core.drop_stderr +import kommand_core.drop_stdin +import kommand_core.drop_stdout +import kommand_core.flush_stdin +import kommand_core.read_all_stderr +import kommand_core.read_all_stdout +import kommand_core.read_line_stderr +import kommand_core.read_line_stdout +import kommand_core.write_line_stdin +import kotlinx.cinterop.COpaquePointer + +@Throws(KommandException::class) +actual fun readLineStdout(stdout: COpaquePointer?): String? = run { + String.from(read_line_stdout(stdout)) +} + +@Throws(KommandException::class) +actual fun readAllStdout(stdout: COpaquePointer?): String? = run { + String.from(read_all_stdout(stdout)) +} + +@Throws(KommandException::class) +actual fun readLineStderr(stderr: COpaquePointer?): String? = run { + String.from(read_line_stderr(stderr)) +} + +@Throws(KommandException::class) +actual fun readAllStderr(stderr: COpaquePointer?): String? = run { + String.from(read_all_stderr(stderr)) +} + +@Throws(KommandException::class) +actual fun writeLineStdin(stdin: COpaquePointer?, line: String) = run { + write_line_stdin(stdin, line).unwrap() +} + +@Throws(KommandException::class) +actual fun flushStdin(stdin: COpaquePointer?) = run { + flush_stdin(stdin).unwrap() +} + +actual fun dropStdin(stdin: COpaquePointer?) { + drop_stdin(stdin) +} + +actual fun dropStdout(stdout: COpaquePointer?) { + drop_stdout(stdout) +} + +actual fun dropStderr(stderr: COpaquePointer?) { + drop_stderr(stderr) +} diff --git a/src/nativeMain/kotlin/com/kgit2/Child.native.kt b/src/nativeMain/kotlin/com/kgit2/Child.native.kt deleted file mode 100644 index d4bb787..0000000 --- a/src/nativeMain/kotlin/com/kgit2/Child.native.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.kgit2 - -import kommand_core.VoidResult -import kotlinx.cinterop.COpaquePointer -import kotlinx.cinterop.CValue -import kotlinx.cinterop.memScoped -import kotlinx.cinterop.pointed - -actual class Child( - private val inner: COpaquePointer? -) { - - companion object { - @Throws(KommandException::class) - fun from(result: CValue): Child = memScoped { - if (result.ptr.pointed.ok != null) { - return Child(result.ptr.pointed.ok) - } else { - println(result.ptr.pointed.error_type) - throw KommandException( - result.ptr.pointed.err?.asString(), - result.ptr.pointed.error_type.to() - ) - } - } - } -} diff --git a/src/nativeMain/kotlin/com/kgit2/Exception.kt b/src/nativeMain/kotlin/com/kgit2/Exception.kt deleted file mode 100644 index da44d1b..0000000 --- a/src/nativeMain/kotlin/com/kgit2/Exception.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.kgit2 - -// fun kommand_core.ErrorType.to(): ErrorType { -// return when (this) { -// kommand_core.ErrorType.None -> ErrorType.None -// kommand_core.ErrorType.Io -> ErrorType.IO -// kommand_core.ErrorType.Utf8 -> ErrorType.Utf8 -// kommand_core.ErrorType.Unknown -> ErrorType.Unknown -// } -// } diff --git a/src/nativeMain/kotlin/com/kgit2/FFiUtil.kt b/src/nativeMain/kotlin/com/kgit2/FFiUtil.kt deleted file mode 100644 index a459264..0000000 --- a/src/nativeMain/kotlin/com/kgit2/FFiUtil.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.kgit2 - -import kommand_core.drop_string -import kotlinx.cinterop.ByteVar -import kotlinx.cinterop.CPointer -import kotlinx.cinterop.toKString - -inline fun CPointer.asString(): String { - val result = this.toKString() - drop_string(this) - return result -} diff --git a/src/nativeMain/kotlin/com/kgit2/Output.kt b/src/nativeMain/kotlin/com/kgit2/Output.kt deleted file mode 100644 index a78779e..0000000 --- a/src/nativeMain/kotlin/com/kgit2/Output.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.kgit2 - -// import kommand_core.drop_output -// import kommand_core.into_output -// import kotlinx.cinterop.CValue -// import kotlinx.cinterop.memScoped -// import kotlinx.cinterop.pointed -// import kotlinx.cinterop.toKString -// -// fun Output.Companion.from(result: CValue): Output = memScoped { -// if (result.ptr.pointed.err != null) { -// val errPtr = result.ptr.pointed.err!! -// throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) -// } else { -// val output = into_output(result.ptr.pointed.ok) -// val newOutput = Output( -// output.getPointer(memScope).pointed.exit_code, -// output.getPointer(memScope).pointed.stdout_content?.toKString(), -// output.getPointer(memScope).pointed.stderr_content?.toKString(), -// ) -// drop_output(output) -// newOutput -// } -// } diff --git a/src/nativeMain/kotlin/com/kgit2/io/BufferedReader.native.kt b/src/nativeMain/kotlin/com/kgit2/io/BufferedReader.native.kt new file mode 100644 index 0000000..41ffc4b --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/io/BufferedReader.native.kt @@ -0,0 +1,58 @@ +package com.kgit2.io + +import com.kgit2.exception.KommandException +import com.kgit2.wrapper.dropStderr +import com.kgit2.wrapper.dropStdout +import com.kgit2.wrapper.readAllStderr +import com.kgit2.wrapper.readAllStdout +import com.kgit2.wrapper.readLineStderr +import com.kgit2.wrapper.readLineStdout +import kotlinx.cinterop.COpaquePointer +import kotlin.native.ref.createCleaner + +actual class BufferedReader( + private val inner: COpaquePointer?, + private val type: ReaderType, +) { + + val cleaner = createCleaner(inner) { reader -> + when (type) { + ReaderType.STDOUT -> { + dropStdout(reader) + } + ReaderType.STDERR -> { + dropStderr(reader) + } + } + } + + @Throws(KommandException::class) + actual fun readLine(): String? = run { + when (type) { + ReaderType.STDOUT -> { + readLineStdout(inner) + } + ReaderType.STDERR -> { + readLineStderr(inner) + } + } + } + + @Throws(KommandException::class) + actual fun readAll(): String? = run { + when (type) { + ReaderType.STDOUT -> { + readAllStdout(inner) + } + ReaderType.STDERR -> { + readAllStderr(inner) + } + } + } +} + +enum class ReaderType { + STDOUT, + STDERR, + ; +} diff --git a/src/nativeMain/kotlin/com/kgit2/io/BufferedWriter.native.kt b/src/nativeMain/kotlin/com/kgit2/io/BufferedWriter.native.kt new file mode 100644 index 0000000..d3af791 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/io/BufferedWriter.native.kt @@ -0,0 +1,26 @@ +package com.kgit2.io + +import com.kgit2.exception.KommandException +import com.kgit2.wrapper.dropStdin +import com.kgit2.wrapper.flushStdin +import com.kgit2.wrapper.writeLineStdin +import kotlinx.cinterop.COpaquePointer +import kotlin.native.ref.createCleaner + +actual class BufferedWriter( + private val inner: COpaquePointer? +) { + val cleaner = createCleaner(inner) { writer -> + dropStdin(writer) + } + + @Throws(KommandException::class) + actual fun writeLine(line: String) = run { + writeLineStdin(inner, line) + } + + @Throws(KommandException::class) + actual fun flush() = run { + flushStdin(inner) + } +} diff --git a/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt b/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt new file mode 100644 index 0000000..1ebe62c --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt @@ -0,0 +1,70 @@ +package com.kgit2.process + +import com.kgit2.exception.KommandException +import com.kgit2.io.BufferedReader +import com.kgit2.io.BufferedWriter +import com.kgit2.io.Output +import com.kgit2.wrapper.dropChild +import com.kgit2.wrapper.idChild +import com.kgit2.wrapper.killChild +import com.kgit2.wrapper.stderrChild +import com.kgit2.wrapper.stdinChild +import com.kgit2.wrapper.stdoutChild +import com.kgit2.wrapper.waitChild +import com.kgit2.wrapper.waitWithOutputChild +import kotlinx.cinterop.COpaquePointer +import kotlin.native.ref.createCleaner + +actual class Child( + private val inner: COpaquePointer? +) { + actual var stdin: BufferedWriter? = null + actual var stdout: BufferedReader? = null + actual var stderr: BufferedReader? = null + + val cleaner = createCleaner(inner) { child -> + dropChild(child) + } + + companion object; + + actual fun id(): UInt { + return idChild(inner) + } + + @Throws(KommandException::class) + actual fun kill() { + return run { killChild(inner) } + } + + @Throws(KommandException::class) + actual fun wait(): Int { + return run { waitChild(inner) } + } + + @Throws(KommandException::class) + actual fun waitWithOutput(): Output { + return run { waitWithOutputChild(inner) } + } + + internal fun updateIO() { + updateStdin() + updateStdout() + updateStderr() + } + + @Suppress("MemberVisibilityCanBePrivate") + internal fun updateStdin() { + stdin = stdinChild(inner) + } + + @Suppress("MemberVisibilityCanBePrivate") + internal fun updateStdout() { + stdout = stdoutChild(inner) + } + + @Suppress("MemberVisibilityCanBePrivate") + internal fun updateStderr() { + stderr = stderrChild(inner) + } +} diff --git a/src/nativeMain/kotlin/com/kgit2/Command.native.kt b/src/nativeMain/kotlin/com/kgit2/process/Command.native.kt similarity index 66% rename from src/nativeMain/kotlin/com/kgit2/Command.native.kt rename to src/nativeMain/kotlin/com/kgit2/process/Command.native.kt index 4a0fb40..3199d41 100644 --- a/src/nativeMain/kotlin/com/kgit2/Command.native.kt +++ b/src/nativeMain/kotlin/com/kgit2/process/Command.native.kt @@ -1,5 +1,7 @@ -package com.kgit2 +package com.kgit2.process +import com.kgit2.exception.KommandException +import com.kgit2.io.Output import com.kgit2.wrapper.* import kotlinx.cinterop.COpaquePointer import kotlin.native.ref.createCleaner @@ -10,8 +12,8 @@ actual class Command( ) { actual constructor(command: String) : this(command, newCommand(command)) - private val cleaner = createCleaner(inner) { - dropCommand(inner) + private val cleaner = createCleaner(inner) { command -> + dropCommand(command) } override fun toString(): String { @@ -77,34 +79,17 @@ actual class Command( } @Throws(KommandException::class) - actual fun spawn(): Child { - return run { spawnCommand(inner) } + actual fun spawn(): Child = run { + spawnCommand(inner) } @Throws(KommandException::class) - actual fun output(): Output { - return run { outputCommand(inner) } + actual fun output(): Output = run { + outputCommand(inner) } - actual fun status(): Int? { - return statusCommand(inner) + @Throws(KommandException::class) + actual fun status(): Int = run { + statusCommand(inner) } - - // actual fun status(): Int? = memScoped { - // val result = statusCommand(inner) - // if (result.ptr.pointed.err != null) { - // val errPtr = result.ptr.pointed.err!! - // throw KommandException(errPtr.asString(), result.ptr.pointed.error_type.to()) - // } else { - // result.ptr.pointed.ok - // } - // } } - -// : kommand_core.Stdio { -// return when (this) { -// Stdio.Inherit -> kommand_core.Stdio.Inherit -// Stdio.Pipe -> kommand_core.Stdio.Pipe -// Stdio.Null -> kommand_core.Stdio.Null -// } -// } diff --git a/src/nativeMain/kotlin/com/kgit2/wrapper/Child.kt b/src/nativeMain/kotlin/com/kgit2/wrapper/Child.kt index f62e087..a0955dd 100644 --- a/src/nativeMain/kotlin/com/kgit2/wrapper/Child.kt +++ b/src/nativeMain/kotlin/com/kgit2/wrapper/Child.kt @@ -1,2 +1,26 @@ package com.kgit2.wrapper +import com.kgit2.exception.KommandException +import com.kgit2.io.BufferedReader +import com.kgit2.io.BufferedWriter +import com.kgit2.io.Output +import kotlinx.cinterop.COpaquePointer + +expect fun idChild(child: COpaquePointer?): UInt + +expect fun dropChild(child: COpaquePointer?) + +@Throws(KommandException::class) +expect fun killChild(child: COpaquePointer?) + +@Throws(KommandException::class) +expect fun waitChild(child: COpaquePointer?): Int + +@Throws(KommandException::class) +expect fun waitWithOutputChild(child: COpaquePointer?): Output + +expect fun stdinChild(child: COpaquePointer?): BufferedWriter? + +expect fun stdoutChild(child: COpaquePointer?): BufferedReader? + +expect fun stderrChild(child: COpaquePointer?): BufferedReader? diff --git a/src/nativeMain/kotlin/com/kgit2/wrapper/Command.kt b/src/nativeMain/kotlin/com/kgit2/wrapper/Command.kt index c6dc345..9ee6e2c 100644 --- a/src/nativeMain/kotlin/com/kgit2/wrapper/Command.kt +++ b/src/nativeMain/kotlin/com/kgit2/wrapper/Command.kt @@ -1,8 +1,9 @@ package com.kgit2.wrapper -import com.kgit2.Child -import com.kgit2.Output -import com.kgit2.Stdio +import com.kgit2.exception.KommandException +import com.kgit2.process.Child +import com.kgit2.io.Output +import com.kgit2.process.Stdio import kotlinx.cinterop.COpaquePointer expect fun newCommand(command: String): COpaquePointer? @@ -29,8 +30,11 @@ expect fun stdoutCommand(command: COpaquePointer?, stdio: Stdio) expect fun stderrCommand(command: COpaquePointer?, stdio: Stdio) +@Throws(KommandException::class) expect fun spawnCommand(command: COpaquePointer?): Child +@Throws(KommandException::class) expect fun outputCommand(command: COpaquePointer?): Output -expect fun statusCommand(command: COpaquePointer?): Int? +@Throws(KommandException::class) +expect fun statusCommand(command: COpaquePointer?): Int diff --git a/src/nativeMain/kotlin/com/kgit2/wrapper/IO.kt b/src/nativeMain/kotlin/com/kgit2/wrapper/IO.kt new file mode 100644 index 0000000..226b3eb --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/wrapper/IO.kt @@ -0,0 +1,28 @@ +package com.kgit2.wrapper + +import com.kgit2.exception.KommandException +import kotlinx.cinterop.COpaquePointer + +@Throws(KommandException::class) +expect fun readLineStdout(stdout: COpaquePointer?): String? + +@Throws(KommandException::class) +expect fun readAllStdout(stdout: COpaquePointer?): String? + +@Throws(KommandException::class) +expect fun readLineStderr(stderr: COpaquePointer?): String? + +@Throws(KommandException::class) +expect fun readAllStderr(stderr: COpaquePointer?): String? + +@Throws(KommandException::class) +expect fun writeLineStdin(stdin: COpaquePointer?, line: String) + +@Throws(KommandException::class) +expect fun flushStdin(stdin: COpaquePointer?) + +expect fun dropStdin(stdin: COpaquePointer?) + +expect fun dropStdout(stdout: COpaquePointer?) + +expect fun dropStderr(stderr: COpaquePointer?) diff --git a/src/nativeMain/kotlin/com/kgit2/wrapper/Output.kt b/src/nativeMain/kotlin/com/kgit2/wrapper/Output.kt deleted file mode 100644 index f62e087..0000000 --- a/src/nativeMain/kotlin/com/kgit2/wrapper/Output.kt +++ /dev/null @@ -1,2 +0,0 @@ -package com.kgit2.wrapper - From c612ab18024e10bc8241603fd74d59ffed18d927 Mon Sep 17 00:00:00 2001 From: bppleman Date: Sat, 23 Dec 2023 15:54:28 +0800 Subject: [PATCH 06/22] Rename kommand_core dir to kommand-core --- {kommand_core => kommand-core}/.gitignore | 0 {kommand_core => kommand-core}/.idea/vcs.xml | 0 {kommand_core => kommand-core}/Cargo.lock | 0 {kommand_core => kommand-core}/Cargo.toml | 0 {kommand_core => kommand-core}/build.rs | 0 {kommand_core => kommand-core}/justfile | 0 {kommand_core => kommand-core}/kommand-echo/Cargo.toml | 0 {kommand_core => kommand-core}/kommand-echo/src/main.rs | 0 {kommand_core => kommand-core}/kommand-watch/Cargo.toml | 0 {kommand_core => kommand-core}/kommand-watch/src/main.rs | 0 {kommand_core => kommand-core}/kommand_core.h | 0 {kommand_core => kommand-core}/rust-toolchain.toml | 0 {kommand_core => kommand-core}/src/bin/leaks_test.rs | 0 {kommand_core => kommand-core}/src/bin/normal_test.rs | 0 {kommand_core => kommand-core}/src/child.rs | 0 {kommand_core => kommand-core}/src/ffi_util.rs | 0 {kommand_core => kommand-core}/src/io.rs | 0 {kommand_core => kommand-core}/src/io/stderr.rs | 0 {kommand_core => kommand-core}/src/io/stdin.rs | 0 {kommand_core => kommand-core}/src/io/stdout.rs | 0 {kommand_core => kommand-core}/src/kommand.rs | 0 {kommand_core => kommand-core}/src/lib.rs | 0 {kommand_core => kommand-core}/src/output.rs | 0 {kommand_core => kommand-core}/src/result.rs | 0 {kommand_core => kommand-core}/src/stdio.rs | 0 25 files changed, 0 insertions(+), 0 deletions(-) rename {kommand_core => kommand-core}/.gitignore (100%) rename {kommand_core => kommand-core}/.idea/vcs.xml (100%) rename {kommand_core => kommand-core}/Cargo.lock (100%) rename {kommand_core => kommand-core}/Cargo.toml (100%) rename {kommand_core => kommand-core}/build.rs (100%) rename {kommand_core => kommand-core}/justfile (100%) rename {kommand_core => kommand-core}/kommand-echo/Cargo.toml (100%) rename {kommand_core => kommand-core}/kommand-echo/src/main.rs (100%) rename {kommand_core => kommand-core}/kommand-watch/Cargo.toml (100%) rename {kommand_core => kommand-core}/kommand-watch/src/main.rs (100%) rename {kommand_core => kommand-core}/kommand_core.h (100%) rename {kommand_core => kommand-core}/rust-toolchain.toml (100%) rename {kommand_core => kommand-core}/src/bin/leaks_test.rs (100%) rename {kommand_core => kommand-core}/src/bin/normal_test.rs (100%) rename {kommand_core => kommand-core}/src/child.rs (100%) rename {kommand_core => kommand-core}/src/ffi_util.rs (100%) rename {kommand_core => kommand-core}/src/io.rs (100%) rename {kommand_core => kommand-core}/src/io/stderr.rs (100%) rename {kommand_core => kommand-core}/src/io/stdin.rs (100%) rename {kommand_core => kommand-core}/src/io/stdout.rs (100%) rename {kommand_core => kommand-core}/src/kommand.rs (100%) rename {kommand_core => kommand-core}/src/lib.rs (100%) rename {kommand_core => kommand-core}/src/output.rs (100%) rename {kommand_core => kommand-core}/src/result.rs (100%) rename {kommand_core => kommand-core}/src/stdio.rs (100%) diff --git a/kommand_core/.gitignore b/kommand-core/.gitignore similarity index 100% rename from kommand_core/.gitignore rename to kommand-core/.gitignore diff --git a/kommand_core/.idea/vcs.xml b/kommand-core/.idea/vcs.xml similarity index 100% rename from kommand_core/.idea/vcs.xml rename to kommand-core/.idea/vcs.xml diff --git a/kommand_core/Cargo.lock b/kommand-core/Cargo.lock similarity index 100% rename from kommand_core/Cargo.lock rename to kommand-core/Cargo.lock diff --git a/kommand_core/Cargo.toml b/kommand-core/Cargo.toml similarity index 100% rename from kommand_core/Cargo.toml rename to kommand-core/Cargo.toml diff --git a/kommand_core/build.rs b/kommand-core/build.rs similarity index 100% rename from kommand_core/build.rs rename to kommand-core/build.rs diff --git a/kommand_core/justfile b/kommand-core/justfile similarity index 100% rename from kommand_core/justfile rename to kommand-core/justfile diff --git a/kommand_core/kommand-echo/Cargo.toml b/kommand-core/kommand-echo/Cargo.toml similarity index 100% rename from kommand_core/kommand-echo/Cargo.toml rename to kommand-core/kommand-echo/Cargo.toml diff --git a/kommand_core/kommand-echo/src/main.rs b/kommand-core/kommand-echo/src/main.rs similarity index 100% rename from kommand_core/kommand-echo/src/main.rs rename to kommand-core/kommand-echo/src/main.rs diff --git a/kommand_core/kommand-watch/Cargo.toml b/kommand-core/kommand-watch/Cargo.toml similarity index 100% rename from kommand_core/kommand-watch/Cargo.toml rename to kommand-core/kommand-watch/Cargo.toml diff --git a/kommand_core/kommand-watch/src/main.rs b/kommand-core/kommand-watch/src/main.rs similarity index 100% rename from kommand_core/kommand-watch/src/main.rs rename to kommand-core/kommand-watch/src/main.rs diff --git a/kommand_core/kommand_core.h b/kommand-core/kommand_core.h similarity index 100% rename from kommand_core/kommand_core.h rename to kommand-core/kommand_core.h diff --git a/kommand_core/rust-toolchain.toml b/kommand-core/rust-toolchain.toml similarity index 100% rename from kommand_core/rust-toolchain.toml rename to kommand-core/rust-toolchain.toml diff --git a/kommand_core/src/bin/leaks_test.rs b/kommand-core/src/bin/leaks_test.rs similarity index 100% rename from kommand_core/src/bin/leaks_test.rs rename to kommand-core/src/bin/leaks_test.rs diff --git a/kommand_core/src/bin/normal_test.rs b/kommand-core/src/bin/normal_test.rs similarity index 100% rename from kommand_core/src/bin/normal_test.rs rename to kommand-core/src/bin/normal_test.rs diff --git a/kommand_core/src/child.rs b/kommand-core/src/child.rs similarity index 100% rename from kommand_core/src/child.rs rename to kommand-core/src/child.rs diff --git a/kommand_core/src/ffi_util.rs b/kommand-core/src/ffi_util.rs similarity index 100% rename from kommand_core/src/ffi_util.rs rename to kommand-core/src/ffi_util.rs diff --git a/kommand_core/src/io.rs b/kommand-core/src/io.rs similarity index 100% rename from kommand_core/src/io.rs rename to kommand-core/src/io.rs diff --git a/kommand_core/src/io/stderr.rs b/kommand-core/src/io/stderr.rs similarity index 100% rename from kommand_core/src/io/stderr.rs rename to kommand-core/src/io/stderr.rs diff --git a/kommand_core/src/io/stdin.rs b/kommand-core/src/io/stdin.rs similarity index 100% rename from kommand_core/src/io/stdin.rs rename to kommand-core/src/io/stdin.rs diff --git a/kommand_core/src/io/stdout.rs b/kommand-core/src/io/stdout.rs similarity index 100% rename from kommand_core/src/io/stdout.rs rename to kommand-core/src/io/stdout.rs diff --git a/kommand_core/src/kommand.rs b/kommand-core/src/kommand.rs similarity index 100% rename from kommand_core/src/kommand.rs rename to kommand-core/src/kommand.rs diff --git a/kommand_core/src/lib.rs b/kommand-core/src/lib.rs similarity index 100% rename from kommand_core/src/lib.rs rename to kommand-core/src/lib.rs diff --git a/kommand_core/src/output.rs b/kommand-core/src/output.rs similarity index 100% rename from kommand_core/src/output.rs rename to kommand-core/src/output.rs diff --git a/kommand_core/src/result.rs b/kommand-core/src/result.rs similarity index 100% rename from kommand_core/src/result.rs rename to kommand-core/src/result.rs diff --git a/kommand_core/src/stdio.rs b/kommand-core/src/stdio.rs similarity index 100% rename from kommand_core/src/stdio.rs rename to kommand-core/src/stdio.rs From 5dc6a664c1718b3f14cd65140e3de09965bcf540 Mon Sep 17 00:00:00 2001 From: bppleman Date: Sun, 24 Dec 2023 02:15:30 +0800 Subject: [PATCH 07/22] Add many unit test --- .gitignore | 2 + build.gradle.kts | 8 +- gradle/wrapper/gradle-wrapper.jar | Bin 59821 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 41 ++- gradlew.bat | 15 +- justfile | 4 + kommand-core/.idea/workspace.xml | 309 ++++++++++++++++++ kommand-core/Cargo.lock | 138 +++++++- kommand-core/justfile | 4 + kommand-core/kommand-echo/Cargo.toml | 3 + kommand-core/kommand-echo/src/main.rs | 16 +- kommand-core/kommand-watch/Cargo.toml | 3 + kommand-core/kommand-watch/src/main.rs | 62 +++- kommand-core/kommand_core.h | 70 ++-- kommand-core/src/bin/leaks_test.rs | 55 ++-- kommand-core/src/bin/normal_test.rs | 28 +- kommand-core/src/bin/temp.rs | 10 + kommand-core/src/env.rs | 53 +++ kommand-core/src/lib.rs | 6 +- kommand-core/src/process.rs | 9 + kommand-core/src/{ => process}/child.rs | 16 +- kommand-core/src/{ => process}/kommand.rs | 16 +- kommand-core/src/{ => process}/output.rs | 0 kommand-core/src/{ => process}/stdio.rs | 1 + kommand-core/src/result.rs | 14 +- kommand_core/.idea/workspace.xml | 260 --------------- settings.gradle.kts | 7 + src/commonMain/kotlin/com/kgit2/Platform.kt | 1 - .../kotlin/com/kgit2/env/EnvVars.kt | 5 + .../kotlin/com/kgit2/io/BufferedWriter.kt | 3 + .../kotlin/com/kgit2/process/Child.kt | 12 +- src/commonTest/kotlin/com/kgit2/ChildTest.kt | 77 +++++ .../kotlin/com/kgit2/CommandTest.kt | 51 ++- src/commonTest/kotlin/com/kgit2/EnvTest.kt | 15 + .../kotlin/com/kgit2/PlatformTest.kt | 33 +- src/jvmMain/kotlin/com/kgit2/Platform.jvm.kt | 22 +- .../kotlin/com/kgit2/env/EnvVars.jvm.kt | 9 + .../kotlin/com/kgit2/process/Child.jvm.kt | 18 +- .../com/kgit2/wrapper/Child.linuxArm64.kt | 17 +- .../com/kgit2/wrapper/EnvVars.linuxArm64.kt | 30 ++ .../com/kgit2/wrapper/Extension.linuxArm64.kt | 8 +- .../kgit2/wrapper/{IO.kt => IO.linuxArm64.kt} | 0 .../com/kgit2/wrapper/Child.linuxX64.kt | 17 +- .../com/kgit2/wrapper/EnvVars.linuxX64.kt | 30 ++ .../com/kgit2/wrapper/Extension.linuxX64.kt | 8 +- .../kgit2/wrapper/{IO.kt => IO.linuxX64.kt} | 0 .../com/kgit2/wrapper/Child.macosArm64.kt | 17 +- .../com/kgit2/wrapper/EnvVars.macosArm64.kt | 30 ++ .../com/kgit2/wrapper/Extension.macosArm64.kt | 8 +- .../kgit2/wrapper/{IO.kt => IO.macosArm64.kt} | 0 .../com/kgit2/wrapper/Child.macosX64.kt | 17 +- .../com/kgit2/wrapper/EnvVars.macosX64.kt | 30 ++ .../com/kgit2/wrapper/Extension.macosX64.kt | 8 +- .../kgit2/wrapper/{IO.kt => IO.macosX64.kt} | 0 .../com/kgit2/wrapper/Child.mingwX64.kt | 17 +- .../com/kgit2/wrapper/EnvVars.mingwX64.kt | 30 ++ .../com/kgit2/wrapper/Extension.mingwX64.kt | 8 +- .../kgit2/wrapper/{IO.kt => IO.mingwX64.kt} | 0 src/nativeInterop/cinterop/linuxarm64.def | 4 +- src/nativeInterop/cinterop/linuxx64.def | 4 +- src/nativeInterop/cinterop/macos.def | 4 +- src/nativeInterop/cinterop/mingw64.def | 4 +- .../kotlin/com/kgit2/env/EnvVars.native.kt | 9 + .../com/kgit2/io/BufferedWriter.native.kt | 17 +- .../kotlin/com/kgit2/process/Child.native.kt | 80 +++-- .../com/kgit2/process/Command.native.kt | 16 +- .../kotlin/com/kgit2/wrapper/Child.kt | 6 +- .../kotlin/com/kgit2/wrapper/Command.kt | 2 +- .../kotlin/com/kgit2/wrapper/EnvVars.kt | 5 + 70 files changed, 1318 insertions(+), 508 deletions(-) create mode 100644 kommand-core/.idea/workspace.xml create mode 100644 kommand-core/src/bin/temp.rs create mode 100644 kommand-core/src/env.rs create mode 100644 kommand-core/src/process.rs rename kommand-core/src/{ => process}/child.rs (74%) rename kommand-core/src/{ => process}/kommand.rs (93%) rename kommand-core/src/{ => process}/output.rs (100%) rename kommand-core/src/{ => process}/stdio.rs (78%) delete mode 100644 kommand_core/.idea/workspace.xml create mode 100644 src/commonMain/kotlin/com/kgit2/env/EnvVars.kt create mode 100644 src/commonTest/kotlin/com/kgit2/ChildTest.kt create mode 100644 src/commonTest/kotlin/com/kgit2/EnvTest.kt create mode 100644 src/jvmMain/kotlin/com/kgit2/env/EnvVars.jvm.kt create mode 100644 src/linuxArm64Main/kotlin/com/kgit2/wrapper/EnvVars.linuxArm64.kt rename src/linuxArm64Main/kotlin/com/kgit2/wrapper/{IO.kt => IO.linuxArm64.kt} (100%) create mode 100644 src/linuxX64Main/kotlin/com/kgit2/wrapper/EnvVars.linuxX64.kt rename src/linuxX64Main/kotlin/com/kgit2/wrapper/{IO.kt => IO.linuxX64.kt} (100%) create mode 100644 src/macosArm64Main/kotlin/com/kgit2/wrapper/EnvVars.macosArm64.kt rename src/macosArm64Main/kotlin/com/kgit2/wrapper/{IO.kt => IO.macosArm64.kt} (100%) create mode 100644 src/macosX64Main/kotlin/com/kgit2/wrapper/EnvVars.macosX64.kt rename src/macosX64Main/kotlin/com/kgit2/wrapper/{IO.kt => IO.macosX64.kt} (100%) create mode 100644 src/mingwX64Main/kotlin/com/kgit2/wrapper/EnvVars.mingwX64.kt rename src/mingwX64Main/kotlin/com/kgit2/wrapper/{IO.kt => IO.mingwX64.kt} (100%) create mode 100644 src/nativeMain/kotlin/com/kgit2/env/EnvVars.native.kt create mode 100644 src/nativeMain/kotlin/com/kgit2/wrapper/EnvVars.kt diff --git a/.gitignore b/.gitignore index 46ec24b..037b02a 100644 --- a/.gitignore +++ b/.gitignore @@ -216,3 +216,5 @@ gradle-app.setting *.hprof # End of https://www.toptal.com/developers/gitignore/api/macos,intellij+iml,vim,visualstudiocode,gradle,kotlin + +build-cache diff --git a/build.gradle.kts b/build.gradle.kts index 8858f5f..fa666af 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -98,10 +98,16 @@ tasks.forEach { } } +tasks.withType(Test::class) { + testLogging { + showStandardStreams = true + } +} + tasks { val wrapper by getting(Wrapper::class) { distributionType = Wrapper.DistributionType.ALL - gradleVersion = "8.5" + gradleVersion = "8.2" } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927a4d4fb3f96a785543079b8df6723c946b..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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 literal 59821 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d0d403e..bf01c4d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f..6689b85 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/justfile b/justfile index 320c16f..a7b46f8 100755 --- a/justfile +++ b/justfile @@ -79,3 +79,7 @@ releaseSonatype: ./gradlew findSonatypeStagingRepository releaseSonatypeStagingRepository autoPublish: publishToSonatype closeSonatype releaseSonatype + +leaks: + ./gradlew :cleanMacosX64Test :macosX64Test + leaks -atExit -- build/bin/macosX64/debugTest/test.kexe diff --git a/kommand-core/.idea/workspace.xml b/kommand-core/.idea/workspace.xml new file mode 100644 index 0000000..9f7365a --- /dev/null +++ b/kommand-core/.idea/workspace.xml @@ -0,0 +1,309 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + "associatedIndex": 4 +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1703038493898 + + + + + + \ No newline at end of file diff --git a/kommand-core/Cargo.lock b/kommand-core/Cargo.lock index 730787c..df10d34 100644 --- a/kommand-core/Cargo.lock +++ b/kommand-core/Cargo.lock @@ -17,13 +17,22 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -146,6 +155,30 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "colored" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +dependencies = [ + "is-terminal", + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", +] + [[package]] name = "crossbeam-channel" version = "0.5.9" @@ -165,6 +198,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "errno" version = "0.3.8" @@ -245,6 +284,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + [[package]] name = "indenter" version = "0.3.3" @@ -281,6 +326,17 @@ dependencies = [ "libc", ] +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi 0.3.3", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "itoa" version = "1.0.10" @@ -297,12 +353,20 @@ dependencies = [ [[package]] name = "kommand-echo" version = "0.1.0" +dependencies = [ + "ansi_term", + "colored", + "console", +] [[package]] name = "kommand-watch" version = "0.1.0" dependencies = [ + "ansi_term", "color-eyre", + "colored", + "console", "fs_extra", "lazy_static", "notify-debouncer-mini", @@ -702,6 +766,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "valuable" version = "0.1.0" @@ -755,6 +825,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -773,6 +852,21 @@ dependencies = [ "windows-targets 0.52.0", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -803,6 +897,12 @@ dependencies = [ "windows_x86_64_msvc 0.52.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -815,6 +915,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -827,6 +933,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -839,6 +951,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -851,6 +969,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -863,6 +987,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -875,6 +1005,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/kommand-core/justfile b/kommand-core/justfile index 79a7848..c31897d 100755 --- a/kommand-core/justfile +++ b/kommand-core/justfile @@ -40,3 +40,7 @@ all: macos linux win watch: cargo build --release --package kommand-watch target/release/kommand-watch + +build-kommand-echo: + cargo build --package kommand-echo + cargo build --release --package kommand-echo diff --git a/kommand-core/kommand-echo/Cargo.toml b/kommand-core/kommand-echo/Cargo.toml index c31ec6e..41a284b 100644 --- a/kommand-core/kommand-echo/Cargo.toml +++ b/kommand-core/kommand-echo/Cargo.toml @@ -7,3 +7,6 @@ authors.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +colored = "2.0.4" +console = "0.15.7" +ansi_term = "0.12.1" diff --git a/kommand-core/kommand-echo/src/main.rs b/kommand-core/kommand-echo/src/main.rs index 807f932..f1f4036 100644 --- a/kommand-core/kommand-echo/src/main.rs +++ b/kommand-core/kommand-echo/src/main.rs @@ -1,3 +1,4 @@ +use colored::Colorize; use std::env; use std::io::stdin; use std::str::FromStr; @@ -5,7 +6,7 @@ use std::str::FromStr; fn main() { let args = env::args().collect::>(); if args.len() == 1 { - println!("Hello, Kommand!"); + print!("Hello, Kommand!"); return; } let cmd = &args[1]; @@ -13,6 +14,7 @@ fn main() { cmd if cmd == "echo" => echo(), cmd if cmd == "stdout" => stdout(), cmd if cmd == "stderr" => stderr(), + cmd if cmd == "color" => color(), cmd if cmd == "interval" => interval( args.get(2) .map(|s| u32::from_str(s).unwrap_or_else(|_| panic!("{} cannot convert to u32", s))) @@ -36,7 +38,7 @@ fn stdout() { match line { Ok(line) => println!("{}", line), Err(_) => { - eprintln!("Error reading stdin"); + eprint!("Error reading stdin"); break; } } @@ -46,7 +48,7 @@ fn stdout() { fn stderr() { for line in stdin().lines() { match line { - Ok(line) => eprintln!("{}", line), + Ok(line) => eprint!("{}", line), Err(_) => break, } } @@ -59,3 +61,11 @@ fn interval(count: u32) { std::thread::sleep(duration); } } + +fn color() { + colored::control::set_override(true); + println!("{}", "Hello, Kommand!".red()); + println!("{}", "Hello, Kommand!".green()); + println!("{}", "Hello, Kommand!".blue()); + colored::control::unset_override(); +} diff --git a/kommand-core/kommand-watch/Cargo.toml b/kommand-core/kommand-watch/Cargo.toml index 2292c4b..3711cb1 100644 --- a/kommand-core/kommand-watch/Cargo.toml +++ b/kommand-core/kommand-watch/Cargo.toml @@ -13,3 +13,6 @@ tokio = { version = "1.35.1", features = ["rt", "signal", "macros"] } lazy_static = "1.4.0" fs_extra = "1.3.0" walkdir = "2.4.0" +colored = "2.0.4" +console = "0.15.7" +ansi_term = "0.12.1" diff --git a/kommand-core/kommand-watch/src/main.rs b/kommand-core/kommand-watch/src/main.rs index c8c95cb..6d2204b 100644 --- a/kommand-core/kommand-watch/src/main.rs +++ b/kommand-core/kommand-watch/src/main.rs @@ -1,14 +1,16 @@ use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; +use std::fmt::Display; use std::path::Path; +use std::path::PathBuf; use std::time::Duration; use color_eyre::eyre::Context; +use color_eyre::owo_colors::OwoColorize; use color_eyre::{install, Result}; use lazy_static::lazy_static; use notify_debouncer_mini::notify::RecursiveMode; use notify_debouncer_mini::{new_debouncer, DebounceEventResult}; -use std::path::PathBuf; use walkdir::WalkDir; lazy_static! { @@ -32,10 +34,15 @@ lazy_static! { #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { install()?; + #[cfg(windows)] + ansi_term::enable_ansi_support().unwrap(); sync_dir(MAIN_DIR.as_path()); let mut debouncer = new_debouncer(Duration::from_millis(500), process_file_change)?; - println!("Watching {}", MAIN_DIR.display()); + format!("Watching {}", MAIN_DIR.display()) + .blue() + .bold() + .print(); debouncer .watcher() .watch(&MAIN_DIR, RecursiveMode::Recursive)?; @@ -64,13 +71,13 @@ fn process_file_change(result: DebounceEventResult) { }); } Err(error) => { - eprintln!("{:?}", error); + format!("{}", error).red().eprint(); } } } fn sync_file(path: impl AsRef) { - println!("File changed {:?}", path.as_ref()); + format!("File changed {:?}", path.as_ref()).green().print(); let content = fs_extra::file::read_to_string(path.as_ref()).unwrap(); let striped = path .as_ref() @@ -79,7 +86,7 @@ fn sync_file(path: impl AsRef) { .unwrap(); CLUSTER_DIRS.iter().for_each(|(name, cluster_dir)| { let new_path = cluster_dir.join(striped.to_string_lossy().replace("macosX64", name)); - println!("Will write to {:?}", new_path); + format!(" Will write to {:?}", new_path).yellow().print(); if !new_path.exists() { fs_extra::dir::create_all(new_path.parent().unwrap(), false).unwrap(); } @@ -88,7 +95,7 @@ fn sync_file(path: impl AsRef) { } fn sync_dir(path: impl AsRef) { - println!("Dir changed {:?}", path.as_ref()); + format!("Dir changed {:?}", path.as_ref()).green().print(); let main_files = collect_dir(MAIN_DIR.as_path()); let main_set = main_files .iter() @@ -113,15 +120,36 @@ fn sync_dir(path: impl AsRef) { }) .collect::>(); let diff = cluster_set.difference(&main_set); - diff.for_each(|path| sync_remove(cluster_dir.join(path))); + diff.for_each(|path| sync_remove(MAIN_DIR.join(path))); #[rustfmt::skip] main_set.iter().for_each(|path| sync_file(MAIN_DIR.join(path))); }); } fn sync_remove(path: impl AsRef) { - println!("Item removed {:?}", path.as_ref()); - fs_extra::remove_items(&[path.as_ref()]).unwrap(); + format!("Item removed {:?}", path.as_ref()).green().print(); + let striped = path + .as_ref() + .strip_prefix(MAIN_DIR.as_path()) + .with_context(|| "Strip Error") + .unwrap(); + CLUSTER_DIRS.iter().for_each(|(name, cluster_dir)| { + let new_path = cluster_dir.join(striped.to_string_lossy().replace("macosX64", name)); + if new_path.exists() { + format!(" Will remove {:?}", new_path).red().print(); + if new_path.is_file() { + fs_extra::file::remove(&new_path).unwrap(); + } else if new_path.is_dir() { + fs_extra::dir::remove(&new_path).unwrap(); + } else if new_path.is_symlink() { + fs_extra::file::remove(&new_path).unwrap(); + } + } else { + format!(" Will not remove (not exist) {:?}", new_path) + .magenta() + .eprint(); + } + }); } fn collect_dir(path: impl AsRef) -> HashSet { @@ -132,3 +160,19 @@ fn collect_dir(path: impl AsRef) -> HashSet { .map(|entry| entry.into_path()) .collect::>() } + +pub trait Print { + fn print(&self); + + fn eprint(&self); +} + +impl Print for T { + fn print(&self) { + println!("{}", self); + } + + fn eprint(&self) { + eprintln!("{}", self); + } +} diff --git a/kommand-core/kommand_core.h b/kommand-core/kommand_core.h index d85aee1..86f589c 100644 --- a/kommand-core/kommand_core.h +++ b/kommand-core/kommand_core.h @@ -11,6 +11,18 @@ typedef enum Stdio { Pipe, } Stdio; +typedef struct EnvVars { + char **names; + char **values; + unsigned long long len; +} EnvVars; + +typedef struct VoidResult { + void *ok; + char *err; + enum ErrorType error_type; +} VoidResult; + typedef struct UnitResult { int ok; char *err; @@ -23,33 +35,20 @@ typedef struct IntResult { enum ErrorType error_type; } IntResult; -typedef struct VoidResult { - void *ok; - char *err; - enum ErrorType error_type; -} VoidResult; - typedef struct Output { int exit_code; char *stdout_content; char *stderr_content; } Output; -void *stdin_child(const void *child); - -void *stdout_child(const void *child); - -void *stderr_child(const void *child); - -struct UnitResult kill_child(const void *child); - -unsigned int id_child(const void *child); - -struct IntResult wait_child(const void *child); +/** + * # Safety + */ +char *env_var(const char *name); -struct VoidResult wait_with_output_child(void *child); +struct EnvVars env_vars(void); -void drop_child(void *child); +void drop_env_vars(struct EnvVars env_vars); /** * # Safety @@ -80,6 +79,27 @@ void drop_stdin(void *reader); void drop_stdout(void *reader); +void *buffered_stdin_child(const void *child); + +void *buffered_stdout_child(const void *child); + +void *buffered_stderr_child(const void *child); + +struct UnitResult kill_child(const void *child); + +unsigned int id_child(const void *child); + +struct IntResult wait_child(const void *child); + +/** + * The returned [Output] will empty + * if convert the [Child.stdout] and [Child.stderr] to buffered + * with [buffered_stdout_child] and [buffered_stderr_child] + */ +struct VoidResult wait_with_output_child(void *child); + +void drop_child(void *child); + /** * # Safety * Will not move the [name]'s ownership @@ -87,7 +107,7 @@ void drop_stdout(void *reader); * * ```rust * use kommand_core::ffi_util::as_cstring; - * use kommand_core::kommand::{drop_command, new_command}; + * use kommand_core::process::{drop_command, new_command}; * unsafe { * let command = new_command(as_cstring("pwd").as_ptr()); * drop_command(command); @@ -99,7 +119,7 @@ void *new_command(const char *name); /** * ```rust * use kommand_core::ffi_util::{as_cstring, drop_string}; - * use kommand_core::kommand::{display_command, drop_command, new_command}; + * use kommand_core::process::{display_command, drop_command, new_command}; * unsafe { * let command = new_command(as_cstring("pwd").as_ptr()); * let display = display_command(command); @@ -113,7 +133,7 @@ char *display_command(const void *command); /** * ```rust * use kommand_core::ffi_util::{as_cstring, drop_string}; - * use kommand_core::kommand::{debug_command, drop_command, new_command}; + * use kommand_core::process::{debug_command, drop_command, new_command}; * unsafe { * let command = new_command(as_cstring("pwd").as_ptr()); * let debug = debug_command(command); @@ -127,7 +147,7 @@ char *debug_command(const void *command); /** * ```rust * use kommand_core::ffi_util::as_cstring; - * use kommand_core::kommand::{drop_command, new_command}; + * use kommand_core::process::{drop_command, new_command}; * unsafe { * let command = new_command(as_cstring("pwd").as_ptr()); * drop_command(command); @@ -142,7 +162,7 @@ void drop_command(void *command); * * ```rust * use kommand_core::ffi_util::as_cstring; - * use kommand_core::kommand::{arg_command, drop_command, new_command}; + * use kommand_core::process::{arg_command, drop_command, new_command}; * unsafe { * let command = new_command(as_cstring("ls").as_ptr()); * arg_command(command, as_cstring("-l").as_ptr()); @@ -158,7 +178,7 @@ void arg_command(const void *command, const char *arg); * * ```rust * use kommand_core::ffi_util::as_cstring; - * use kommand_core::kommand::{arg_command, drop_command, env_command, new_command}; + * use kommand_core::process::{arg_command, drop_command, env_command, new_command}; * unsafe { * let command = new_command(as_cstring("echo").as_ptr()); * arg_command(command, as_cstring("$KOMMAND").as_ptr()); diff --git a/kommand-core/src/bin/leaks_test.rs b/kommand-core/src/bin/leaks_test.rs index adfab20..50e8dda 100644 --- a/kommand-core/src/bin/leaks_test.rs +++ b/kommand-core/src/bin/leaks_test.rs @@ -1,21 +1,18 @@ use std::ffi::c_char; -use kommand_core::child::{drop_child, stdin_child, stdout_child, wait_child}; use kommand_core::ffi_util::{as_cstring, into_string}; -use kommand_core::io::{drop_stdin, drop_stdout, read_line_stdout, write_line_stdin}; -use kommand_core::kommand::{ - arg_command, drop_command, new_command, spawn_command, stdin_command, stdout_command, +use kommand_core::io::{drop_stdout, read_line_stdout}; +use kommand_core::process::{ + arg_command, drop_command, new_command, spawn_command, stdout_command, }; -use kommand_core::stdio::Stdio; +use kommand_core::process::{buffered_stdout_child, Stdio}; +use kommand_core::process::{drop_child, wait_child}; fn main() { unsafe { - (0..10000).for_each(|i| { - let command = new_command( - as_cstring("/Users/bppleman/kgit2/kommand/eko/target/release/eko").as_ptr(), - ); + (0..1).for_each(|i| { + let command = new_command(as_cstring("target/debug/kommand-echo").as_ptr()); arg_command(command, as_cstring("echo").as_ptr()); - stdin_command(command, Stdio::Pipe); stdout_command(command, Stdio::Pipe); let result = spawn_command(command); let child = if !result.ok.is_null() { @@ -25,37 +22,27 @@ fn main() { panic!("{}", into_string(result.err)) }; - let stdin = stdin_child(child); - if stdin.is_null() { - drop_child(child); - drop_command(command); - panic!("stdin is null"); - } - let result = write_line_stdin(stdin, as_cstring("Hello, Kommand!").as_ptr()); - if result.ok != 0 { - drop_child(child); - drop_command(command); - panic!("{}", into_string(result.err)) - } - drop_stdin(stdin); - - let stdout = stdout_child(child); + let stdout = buffered_stdout_child(child); if stdout.is_null() { panic!("stdout is null"); } - let result = read_line_stdout(stdout); - let line = if !result.ok.is_null() { - into_string(result.ok as *mut c_char) - } else { - panic!("{}", into_string(result.err)) - }; - println!("[{i}] line {}", line); + loop { + let result = read_line_stdout(stdout); + let line = if !result.ok.is_null() { + into_string(result.ok as *mut c_char) + } else { + panic!("{}", into_string(result.err)) + }; + println!("[{i}] line {}", line); + if line.is_empty() { + break; + } + } - drop_stdout(stdout); wait_child(child); + drop_stdout(stdout); drop_child(child); drop_command(command); - // std::thread::sleep(std::time::Duration::from_secs(1)); }); } } diff --git a/kommand-core/src/bin/normal_test.rs b/kommand-core/src/bin/normal_test.rs index ea76a9d..bb68c8d 100644 --- a/kommand-core/src/bin/normal_test.rs +++ b/kommand-core/src/bin/normal_test.rs @@ -1,24 +1,14 @@ -use std::io::{Read, Write}; +use std::io::{stdout, Write}; use std::process::{Command, Stdio}; fn main() { - (0..1).for_each(|i| { - let mut command = Command::new("/Users/bppleman/kgit2/kommand/eko/target/release/eko"); - command - .arg("echo") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()); - let mut child = command.spawn().unwrap(); - - { - let mut stdin = child.stdin.take().unwrap(); - stdin.write_all(b"Hello, Kommand!").unwrap(); - } - - let mut stdout = child.stdout.take().unwrap(); - let mut line = String::new(); - stdout.read_to_string(&mut line).unwrap(); - - println!("[{i}] line {}", line); + println!("{:?}", std::env::current_dir()); + (0..1).for_each(|_| { + let mut command = Command::new("target/debug/kommand-echo"); + command.arg("interval").stdout(Stdio::inherit()); + let child = command.spawn().unwrap(); + let output = child.wait_with_output().unwrap(); + println!("output"); + stdout().write_all(&output.stdout).unwrap(); }); } diff --git a/kommand-core/src/bin/temp.rs b/kommand-core/src/bin/temp.rs new file mode 100644 index 0000000..fb11f8c --- /dev/null +++ b/kommand-core/src/bin/temp.rs @@ -0,0 +1,10 @@ +use std::process::Command; + +fn main() { + println!("{:?}", std::env::current_dir()); + let output = Command::new("target/debug/kommand-echo") + .arg("color") + .output() + .unwrap(); + println!("{:?}", output); +} diff --git a/kommand-core/src/env.rs b/kommand-core/src/env.rs new file mode 100644 index 0000000..fb96a36 --- /dev/null +++ b/kommand-core/src/env.rs @@ -0,0 +1,53 @@ +use crate::ffi_util::{as_string, into_cstring, into_string}; +use std::ffi::{c_char, c_ulonglong}; + +#[repr(C)] +pub struct EnvVars { + pub names: *mut *mut c_char, + pub values: *mut *mut c_char, + pub len: c_ulonglong, +} + +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn env_var(name: *const c_char) -> *mut c_char { + let name = as_string(name); + into_cstring(std::env::var(name).unwrap_or_default()) +} + +#[no_mangle] +pub extern "C" fn env_vars() -> EnvVars { + let (mut names, mut values) = std::env::vars().fold( + (Vec::new(), Vec::new()), + |(mut names, mut values), (name, value)| { + names.push(into_cstring(name)); + values.push(into_cstring(value)); + (names, values) + }, + ); + let env_vars = EnvVars { + names: names.as_mut_ptr(), + values: values.as_mut_ptr(), + len: names.len() as u64, + }; + std::mem::forget(names); + std::mem::forget(values); + env_vars +} + +#[no_mangle] +pub extern "C" fn drop_env_vars(env_vars: EnvVars) { + unsafe { + let len = env_vars.len as usize; + Vec::from_raw_parts(env_vars.names, len, len) + .into_iter() + .for_each(|name| { + into_string(name); + }); + Vec::from_raw_parts(env_vars.values, len, len) + .into_iter() + .for_each(|value| { + into_string(value); + }); + } +} diff --git a/kommand-core/src/lib.rs b/kommand-core/src/lib.rs index 7897965..6cb3373 100644 --- a/kommand-core/src/lib.rs +++ b/kommand-core/src/lib.rs @@ -2,10 +2,8 @@ //! //! Export the ffi interface through [cbindgen] and use it in kotlin/native -pub mod child; +pub mod env; pub mod ffi_util; pub mod io; -pub mod kommand; -pub mod output; +pub mod process; pub mod result; -pub mod stdio; diff --git a/kommand-core/src/process.rs b/kommand-core/src/process.rs new file mode 100644 index 0000000..87e9a30 --- /dev/null +++ b/kommand-core/src/process.rs @@ -0,0 +1,9 @@ +mod child; +mod kommand; +mod output; +mod stdio; + +pub use child::*; +pub use kommand::*; +pub use output::*; +pub use stdio::*; diff --git a/kommand-core/src/child.rs b/kommand-core/src/process/child.rs similarity index 74% rename from kommand-core/src/child.rs rename to kommand-core/src/process/child.rs index bb71241..a7939f3 100644 --- a/kommand-core/src/child.rs +++ b/kommand-core/src/process/child.rs @@ -1,6 +1,6 @@ use crate::ffi_util::into_void; -use crate::output::Output; -use crate::result::{IntResult, UnitResult, VoidResult}; +use crate::process::Output; +use crate::result::{ErrorType, IntResult, UnitResult, VoidResult}; use std::ffi::{c_uint, c_void}; use std::io::{BufReader, BufWriter}; use std::process::Child; @@ -18,7 +18,7 @@ pub fn into_child(command_ptr: *mut c_void) -> Child { } #[no_mangle] -pub extern "C" fn stdin_child(mut child: *const c_void) -> *mut c_void { +pub extern "C" fn buffered_stdin_child(mut child: *const c_void) -> *mut c_void { let child = as_child_mut(&mut child); child .stdin @@ -29,7 +29,7 @@ pub extern "C" fn stdin_child(mut child: *const c_void) -> *mut c_void { } #[no_mangle] -pub extern "C" fn stdout_child(mut child: *const c_void) -> *mut c_void { +pub extern "C" fn buffered_stdout_child(mut child: *const c_void) -> *mut c_void { let child = as_child_mut(&mut child); child .stdout @@ -40,7 +40,7 @@ pub extern "C" fn stdout_child(mut child: *const c_void) -> *mut c_void { } #[no_mangle] -pub extern "C" fn stderr_child(mut child: *const c_void) -> *mut c_void { +pub extern "C" fn buffered_stderr_child(mut child: *const c_void) -> *mut c_void { let child = as_child_mut(&mut child); child .stderr @@ -68,8 +68,14 @@ pub extern "C" fn wait_child(mut child: *const c_void) -> IntResult { child.wait().into() } +/// The returned [Output] will empty +/// if convert the [Child.stdout] and [Child.stderr] to buffered +/// with [buffered_stdout_child] and [buffered_stderr_child] #[no_mangle] pub extern "C" fn wait_with_output_child(child: *mut c_void) -> VoidResult { + if child.is_null() { + return VoidResult::error("Child has been consumed", ErrorType::None); + } let child = into_child(child); child.wait_with_output().map(Output::from).into() } diff --git a/kommand-core/src/kommand.rs b/kommand-core/src/process/kommand.rs similarity index 93% rename from kommand-core/src/kommand.rs rename to kommand-core/src/process/kommand.rs index a566ae1..6da2c44 100644 --- a/kommand-core/src/kommand.rs +++ b/kommand-core/src/process/kommand.rs @@ -2,9 +2,9 @@ use std::ffi::{c_char, c_void}; use std::process::Command; use crate::ffi_util::{as_string, into_cstring, into_void}; -use crate::output::Output; +use crate::process::Output; +use crate::process::Stdio; use crate::result::{IntResult, VoidResult}; -use crate::stdio::Stdio; pub fn as_command(command_ptr: &*const c_void) -> &Command { unsafe { &*(*command_ptr as *const Command) } @@ -24,7 +24,7 @@ pub fn into_command(command_ptr: *mut c_void) -> Command { /// /// ```rust /// use kommand_core::ffi_util::as_cstring; -/// use kommand_core::kommand::{drop_command, new_command}; +/// use kommand_core::process::{drop_command, new_command}; /// unsafe { /// let command = new_command(as_cstring("pwd").as_ptr()); /// drop_command(command); @@ -39,7 +39,7 @@ pub unsafe extern "C" fn new_command(name: *const c_char) -> *mut c_void { /// ```rust /// use kommand_core::ffi_util::{as_cstring, drop_string}; -/// use kommand_core::kommand::{display_command, drop_command, new_command}; +/// use kommand_core::process::{display_command, drop_command, new_command}; /// unsafe { /// let command = new_command(as_cstring("pwd").as_ptr()); /// let display = display_command(command); @@ -55,7 +55,7 @@ pub extern "C" fn display_command(command: *const c_void) -> *mut c_char { /// ```rust /// use kommand_core::ffi_util::{as_cstring, drop_string}; -/// use kommand_core::kommand::{debug_command, drop_command, new_command}; +/// use kommand_core::process::{debug_command, drop_command, new_command}; /// unsafe { /// let command = new_command(as_cstring("pwd").as_ptr()); /// let debug = debug_command(command); @@ -71,7 +71,7 @@ pub extern "C" fn debug_command(command: *const c_void) -> *mut c_char { /// ```rust /// use kommand_core::ffi_util::as_cstring; -/// use kommand_core::kommand::{drop_command, new_command}; +/// use kommand_core::process::{drop_command, new_command}; /// unsafe { /// let command = new_command(as_cstring("pwd").as_ptr()); /// drop_command(command); @@ -90,7 +90,7 @@ pub extern "C" fn drop_command(command: *mut c_void) { /// /// ```rust /// use kommand_core::ffi_util::as_cstring; -/// use kommand_core::kommand::{arg_command, drop_command, new_command}; +/// use kommand_core::process::{arg_command, drop_command, new_command}; /// unsafe { /// let command = new_command(as_cstring("ls").as_ptr()); /// arg_command(command, as_cstring("-l").as_ptr()); @@ -109,7 +109,7 @@ pub unsafe extern "C" fn arg_command(mut command: *const c_void, arg: *const c_c /// /// ```rust /// use kommand_core::ffi_util::as_cstring; -/// use kommand_core::kommand::{arg_command, drop_command, env_command, new_command}; +/// use kommand_core::process::{arg_command, drop_command, env_command, new_command}; /// unsafe { /// let command = new_command(as_cstring("echo").as_ptr()); /// arg_command(command, as_cstring("$KOMMAND").as_ptr()); diff --git a/kommand-core/src/output.rs b/kommand-core/src/process/output.rs similarity index 100% rename from kommand-core/src/output.rs rename to kommand-core/src/process/output.rs diff --git a/kommand-core/src/stdio.rs b/kommand-core/src/process/stdio.rs similarity index 78% rename from kommand-core/src/stdio.rs rename to kommand-core/src/process/stdio.rs index 80b0b46..fe3c6de 100644 --- a/kommand-core/src/stdio.rs +++ b/kommand-core/src/process/stdio.rs @@ -1,4 +1,5 @@ #[repr(C)] +#[derive(Debug)] pub enum Stdio { Inherit, Null, diff --git a/kommand-core/src/result.rs b/kommand-core/src/result.rs index a10c07b..56d1b05 100644 --- a/kommand-core/src/result.rs +++ b/kommand-core/src/result.rs @@ -31,6 +31,16 @@ pub enum ErrorType { Unknown, } +impl VoidResult { + pub fn error(err: impl AsRef, error_type: ErrorType) -> Self { + VoidResult { + ok: std::ptr::null_mut(), + err: into_cstring(err.as_ref()), + error_type, + } + } +} + impl From> for VoidResult { //noinspection DuplicatedCode fn from(result: io::Result) -> Self { @@ -49,9 +59,9 @@ impl From> for VoidResult { } } -impl From> for VoidResult { +impl From> for VoidResult { //noinspection DuplicatedCode - fn from(value: io::Result) -> Self { + fn from(value: io::Result) -> Self { match value { Ok(output) => VoidResult { ok: into_void(output), diff --git a/kommand_core/.idea/workspace.xml b/kommand_core/.idea/workspace.xml deleted file mode 100644 index 1d79d7f..0000000 --- a/kommand_core/.idea/workspace.xml +++ /dev/null @@ -1,260 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - "associatedIndex": 4 -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1703038493898 - - - - - - \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 666ccec..a62a897 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,4 +12,11 @@ pluginManagement { } } +buildCache { + local { + directory = File(rootDir, "build-cache") + removeUnusedEntriesAfterDays = 30 + } +} + rootProject.name = "kommand" diff --git a/src/commonMain/kotlin/com/kgit2/Platform.kt b/src/commonMain/kotlin/com/kgit2/Platform.kt index 92d811b..4bf525c 100644 --- a/src/commonMain/kotlin/com/kgit2/Platform.kt +++ b/src/commonMain/kotlin/com/kgit2/Platform.kt @@ -6,7 +6,6 @@ enum class Platform { LINUX_X64, LINUX_ARM64, MINGW_X64, - JVM } expect val platform: Platform diff --git a/src/commonMain/kotlin/com/kgit2/env/EnvVars.kt b/src/commonMain/kotlin/com/kgit2/env/EnvVars.kt new file mode 100644 index 0000000..ed63277 --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/env/EnvVars.kt @@ -0,0 +1,5 @@ +package com.kgit2.env + +expect fun envVar(key: String): String? + +expect fun envVars(): Map? diff --git a/src/commonMain/kotlin/com/kgit2/io/BufferedWriter.kt b/src/commonMain/kotlin/com/kgit2/io/BufferedWriter.kt index 375957e..8b5a4fd 100644 --- a/src/commonMain/kotlin/com/kgit2/io/BufferedWriter.kt +++ b/src/commonMain/kotlin/com/kgit2/io/BufferedWriter.kt @@ -8,4 +8,7 @@ expect class BufferedWriter { @Throws(KommandException::class) fun flush() + + @Throws(KommandException::class) + fun close() } diff --git a/src/commonMain/kotlin/com/kgit2/process/Child.kt b/src/commonMain/kotlin/com/kgit2/process/Child.kt index cb3acb3..d16034c 100644 --- a/src/commonMain/kotlin/com/kgit2/process/Child.kt +++ b/src/commonMain/kotlin/com/kgit2/process/Child.kt @@ -1,17 +1,19 @@ package com.kgit2.process +import com.kgit2.exception.KommandException import com.kgit2.io.BufferedReader import com.kgit2.io.BufferedWriter -import com.kgit2.exception.KommandException import com.kgit2.io.Output expect class Child { - var stdin: BufferedWriter? - var stdout: BufferedReader? - var stderr: BufferedReader? - fun id(): UInt + fun bufferedStdin(): BufferedWriter? + + fun bufferedStdout(): BufferedReader? + + fun bufferedStderr(): BufferedReader? + @Throws(KommandException::class) fun kill() diff --git a/src/commonTest/kotlin/com/kgit2/ChildTest.kt b/src/commonTest/kotlin/com/kgit2/ChildTest.kt new file mode 100644 index 0000000..00d8964 --- /dev/null +++ b/src/commonTest/kotlin/com/kgit2/ChildTest.kt @@ -0,0 +1,77 @@ +package com.kgit2 + +import com.kgit2.process.Command +import com.kgit2.process.Stdio +import kotlin.test.Test +import kotlin.test.assertEquals + +class ChildTest { + @Test + fun spawnIntervalOutput() { + val command = Command(platformEchoPath()) + .arg("interval") + .stdout(Stdio.Pipe) + val child = command.spawn() + val output = child.waitWithOutput() + val expect = listOf("0", "1", "2", "3", "4").joinToString("\n") + "\n" + assertEquals(expect, output.stdout) + val expectFailure = runCatching { + child.waitWithOutput() + }.onFailure { + assertEquals("Child has been consumed", it.message) + }.isFailure + assertEquals(true, expectFailure) + } + + @Test + fun spawnIntervalStdout() { + val command = Command(platformEchoPath()) + .arg("interval") + .stdout(Stdio.Pipe) + val child = command.spawn() + var line = child.bufferedStdout()?.readLine() + var expect = 0 + while (!line.isNullOrEmpty()) { + assertEquals("$expect\n", line) + line = child.bufferedStdout()?.readLine() + expect += 1 + } + child.wait() + } + + @Test + fun spawnPipedEcho() { + for (i in 0 .. 1000) { + val command = Command(platformEchoPath()) + .arg("echo") + .stdin(Stdio.Pipe) + .stdout(Stdio.Pipe) + val child = command.spawn() + val expect = "Hello World!\n" + child.bufferedStdin()?.writeLine(expect) + child.bufferedStdin()?.flush() + val line = child.bufferedStdout()?.readLine() + assertEquals(expect, line) + child.wait() + } + } + + @Test + fun manyStdout() { + for (i in 0 .. 100) { + val command = Command(platformEchoPath()) + .arg("stdout") + .stdin(Stdio.Pipe) + .stdout(Stdio.Pipe) + val child = command.spawn() + val expect = "Hello World!\n" + for (j in 0 .. 100) { + child.bufferedStdin()?.writeLine("[$j]$expect") + child.bufferedStdin()?.flush() + val line = child.bufferedStdout()?.readLine() + assertEquals("[$j]$expect", line) + } + child.wait() + } + } +} diff --git a/src/commonTest/kotlin/com/kgit2/CommandTest.kt b/src/commonTest/kotlin/com/kgit2/CommandTest.kt index 533c77b..2c513a5 100644 --- a/src/commonTest/kotlin/com/kgit2/CommandTest.kt +++ b/src/commonTest/kotlin/com/kgit2/CommandTest.kt @@ -1,11 +1,60 @@ package com.kgit2 import com.kgit2.process.Command +import com.kgit2.process.Stdio import kotlin.test.Test +import kotlin.test.assertEquals class CommandTest { @Test fun autoFree() { - println(Command("echo").debugString()) + Command("echo").debugString() + } + + @Test + fun simpleEcho() { + val command = Command(platformEchoPath()) + val output = command.output() + assertEquals("Hello, Kommand!", output.stdout) + assertEquals("Hello, Kommand!", command.output().stdout) + assertEquals("Hello, Kommand!", command.output().stdout) + assertEquals("Hello, Kommand!", command.output().stdout) + val status = command.status() + assertEquals(0, status) + assertEquals(0, command.status()) + assertEquals(0, command.status()) + assertEquals(0, command.status()) + } + + @Test + fun discardIO() { + val command = Command(platformEchoPath()).stdout(Stdio.Null) + val output = command.output() + assertEquals("", output.stdout) + val status = command.status() + assertEquals(0, status) + } + + @Test + fun colorEcho() { + val command = Command(platformEchoPath()).arg("color") + val output = command.output() + val expect = listOf( + "\u001B[31mHello, Kommand!\u001B[0m", + "\u001B[32mHello, Kommand!\u001B[0m", + "\u001B[34mHello, Kommand!\u001B[0m", + ).joinToString("\n") + "\n" + assertEquals(expect, output.stdout) + val status = command.status() + assertEquals(0, status) + } + + @Test + fun currentWorkingDirectory() { + val command = platformCwd().cwd(homeDir()) + val output = command.output() + assertEquals(homeDir(), output.stdout?.trim()) + val status = command.status() + assertEquals(0, status) } } diff --git a/src/commonTest/kotlin/com/kgit2/EnvTest.kt b/src/commonTest/kotlin/com/kgit2/EnvTest.kt new file mode 100644 index 0000000..80ae0a0 --- /dev/null +++ b/src/commonTest/kotlin/com/kgit2/EnvTest.kt @@ -0,0 +1,15 @@ +package com.kgit2 + +import kotlin.test.Test + +class EnvTest { + @Test + fun envVar() { + com.kgit2.env.envVar("HOME") + } + + @Test + fun envVars() { + com.kgit2.env.envVars() + } +} diff --git a/src/commonTest/kotlin/com/kgit2/PlatformTest.kt b/src/commonTest/kotlin/com/kgit2/PlatformTest.kt index 35c6c52..85fe766 100644 --- a/src/commonTest/kotlin/com/kgit2/PlatformTest.kt +++ b/src/commonTest/kotlin/com/kgit2/PlatformTest.kt @@ -1,10 +1,41 @@ package com.kgit2 +import com.kgit2.process.Command import kotlin.test.Test class PlatformTest { @Test fun platform() { - println(platform) + platform + } +} + +fun platformEchoPath(): String { + return when (platform) { + Platform.MACOS_X64 -> "kommand-core/target/x86_64-apple-darwin/release/kommand-echo" + Platform.MACOS_ARM64 -> "kommand-core/target/aarch64-apple-darwin/release/kommand-echo" + Platform.LINUX_X64 -> "kommand-core/target/x86_64-unknown-linux-gnu/release/kommand-echo" + Platform.LINUX_ARM64 -> "kommand-core/target/aarch64-unknown-linux-gnu/release/kommand-echo" + Platform.MINGW_X64 -> "kommand-core/target/x86_64-pc-windows-gnu/release/kommand-echo" + } +} + +fun platformCwd(): Command { + return when (platform) { + Platform.MACOS_X64 -> Command("pwd") + Platform.MACOS_ARM64 -> Command("pwd") + Platform.LINUX_X64 -> Command("pwd") + Platform.LINUX_ARM64 -> Command("pwd") + Platform.MINGW_X64 -> Command("cmd").arg("/c").arg("chdir") + } +} + +fun homeDir(): String { + return when (platform) { + Platform.MACOS_X64 -> com.kgit2.env.envVar("HOME")!! + Platform.MACOS_ARM64 -> com.kgit2.env.envVar("HOME")!! + Platform.LINUX_X64 -> com.kgit2.env.envVar("HOME")!! + Platform.LINUX_ARM64 -> com.kgit2.env.envVar("HOME")!! + Platform.MINGW_X64 -> com.kgit2.env.envVar("userprofile")!! } } diff --git a/src/jvmMain/kotlin/com/kgit2/Platform.jvm.kt b/src/jvmMain/kotlin/com/kgit2/Platform.jvm.kt index 3c1d9d3..f94be62 100644 --- a/src/jvmMain/kotlin/com/kgit2/Platform.jvm.kt +++ b/src/jvmMain/kotlin/com/kgit2/Platform.jvm.kt @@ -1,3 +1,23 @@ package com.kgit2 -actual val platform: Platform = Platform.JVM +actual val platform: Platform + get() { + val os = System.getProperty("os.name") + val arch = System.getProperty("os.arch") + return when { + os.contains("Mac OS X") -> { + when { + arch.contains("aarch64") -> Platform.MACOS_ARM64 + else -> Platform.MACOS_X64 + } + } + os.contains("Linux") -> { + when { + arch.contains("aarch64") -> Platform.LINUX_ARM64 + else -> Platform.LINUX_X64 + } + } + os.contains("Windows") -> Platform.MINGW_X64 + else -> throw Exception("Unsupported platform: $os $arch") + } + } diff --git a/src/jvmMain/kotlin/com/kgit2/env/EnvVars.jvm.kt b/src/jvmMain/kotlin/com/kgit2/env/EnvVars.jvm.kt new file mode 100644 index 0000000..0eec3fc --- /dev/null +++ b/src/jvmMain/kotlin/com/kgit2/env/EnvVars.jvm.kt @@ -0,0 +1,9 @@ +package com.kgit2.env + +actual fun envVar(key: String): String? { + TODO("Not yet implemented") +} + +actual fun envVars(): Map? { + TODO("Not yet implemented") +} diff --git a/src/jvmMain/kotlin/com/kgit2/process/Child.jvm.kt b/src/jvmMain/kotlin/com/kgit2/process/Child.jvm.kt index 7cd2318..d662da8 100644 --- a/src/jvmMain/kotlin/com/kgit2/process/Child.jvm.kt +++ b/src/jvmMain/kotlin/com/kgit2/process/Child.jvm.kt @@ -8,14 +8,26 @@ import com.kgit2.io.Output actual class Child( private val process: Process, ) { - actual var stdin: BufferedWriter? = null - actual var stdout: BufferedReader? = null - actual var stderr: BufferedReader? = null + var stdin: BufferedWriter? = null + var stdout: BufferedReader? = null + var stderr: BufferedReader? = null actual fun id(): UInt { TODO("Not yet implemented") } + actual fun bufferedStdin(): BufferedWriter? { + TODO("Not yet implemented") + } + + actual fun bufferedStdout(): BufferedReader? { + TODO("Not yet implemented") + } + + actual fun bufferedStderr(): BufferedReader? { + TODO("Not yet implemented") + } + @Throws(KommandException::class) actual fun kill() {} diff --git a/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Child.linuxArm64.kt b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Child.linuxArm64.kt index 97c4b72..5b00b2c 100644 --- a/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Child.linuxArm64.kt +++ b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Child.linuxArm64.kt @@ -5,11 +5,12 @@ import com.kgit2.io.BufferedReader import com.kgit2.io.BufferedWriter import com.kgit2.io.Output import com.kgit2.io.ReaderType +import kommand_core.buffered_stderr_child +import kommand_core.buffered_stdin_child +import kommand_core.buffered_stdout_child import kommand_core.drop_child import kommand_core.id_child import kommand_core.kill_child -import kommand_core.stderr_child -import kommand_core.stdin_child import kommand_core.wait_child import kommand_core.wait_with_output_child import kotlinx.cinterop.COpaquePointer @@ -37,14 +38,14 @@ actual fun waitWithOutputChild(child: COpaquePointer?): Output = run { Output.from(wait_with_output_child(child)) } -actual fun stdinChild(child: COpaquePointer?): BufferedWriter? { - return BufferedWriter(stdin_child(child)) +actual fun bufferedStdinChild(child: COpaquePointer?): BufferedWriter? { + return BufferedWriter(buffered_stdin_child(child)) } -actual fun stdoutChild(child: COpaquePointer?): BufferedReader? { - return BufferedReader(stdin_child(child), ReaderType.STDOUT) +actual fun bufferedStdoutChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(buffered_stdout_child(child), ReaderType.STDOUT) } -actual fun stderrChild(child: COpaquePointer?): BufferedReader? { - return BufferedReader(stderr_child(child), ReaderType.STDERR) +actual fun bufferedStderrChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(buffered_stderr_child(child), ReaderType.STDERR) } diff --git a/src/linuxArm64Main/kotlin/com/kgit2/wrapper/EnvVars.linuxArm64.kt b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/EnvVars.linuxArm64.kt new file mode 100644 index 0000000..1e8cb23 --- /dev/null +++ b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/EnvVars.linuxArm64.kt @@ -0,0 +1,30 @@ +package com.kgit2.wrapper + +import kommand_core.drop_env_vars +import kommand_core.env_var +import kommand_core.env_vars +import kotlinx.cinterop.convert +import kotlinx.cinterop.get +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.toKString + +actual fun envVar(name: String): String? { + return env_var(name)?.asString() +} + +actual fun envVars(): Map = memScoped { + val envVars = env_vars() + val map = mutableMapOf() + val length = envVars.ptr.pointed.len + val envVarsValue = envVars.ptr.pointed + (0UL until length).forEach { i -> + val name = envVarsValue.names?.get(i.convert())?.toKString() + val value = envVarsValue.values?.get(i.convert())?.toKString() + if (name != null && value != null) { + map[name] = value + } + } + drop_env_vars(envVars) + map +} diff --git a/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Extension.linuxArm64.kt b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Extension.linuxArm64.kt index d51b5e0..4d3464c 100644 --- a/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Extension.linuxArm64.kt +++ b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/Extension.linuxArm64.kt @@ -25,14 +25,14 @@ inline fun CPointer.asString(): String { @Throws(KommandException::class) fun Child.Companion.from(result: CValue): Child = memScoped { if (result.ptr.pointed.ok != null) { - val child = Child(result.ptr.pointed.ok) - child.updateIO() - child - } else { + Child(result.ptr.pointed.ok) + } else if (result.ptr.pointed.err != null) { throw KommandException( result.ptr.pointed.err?.asString(), result.ptr.pointed.error_type.to() ) + } else { + throw KommandException("[spawn_command] return [result]'s [ok] & [err] are both null", ErrorType.Unknown) } } diff --git a/src/linuxArm64Main/kotlin/com/kgit2/wrapper/IO.kt b/src/linuxArm64Main/kotlin/com/kgit2/wrapper/IO.linuxArm64.kt similarity index 100% rename from src/linuxArm64Main/kotlin/com/kgit2/wrapper/IO.kt rename to src/linuxArm64Main/kotlin/com/kgit2/wrapper/IO.linuxArm64.kt diff --git a/src/linuxX64Main/kotlin/com/kgit2/wrapper/Child.linuxX64.kt b/src/linuxX64Main/kotlin/com/kgit2/wrapper/Child.linuxX64.kt index 97c4b72..5b00b2c 100644 --- a/src/linuxX64Main/kotlin/com/kgit2/wrapper/Child.linuxX64.kt +++ b/src/linuxX64Main/kotlin/com/kgit2/wrapper/Child.linuxX64.kt @@ -5,11 +5,12 @@ import com.kgit2.io.BufferedReader import com.kgit2.io.BufferedWriter import com.kgit2.io.Output import com.kgit2.io.ReaderType +import kommand_core.buffered_stderr_child +import kommand_core.buffered_stdin_child +import kommand_core.buffered_stdout_child import kommand_core.drop_child import kommand_core.id_child import kommand_core.kill_child -import kommand_core.stderr_child -import kommand_core.stdin_child import kommand_core.wait_child import kommand_core.wait_with_output_child import kotlinx.cinterop.COpaquePointer @@ -37,14 +38,14 @@ actual fun waitWithOutputChild(child: COpaquePointer?): Output = run { Output.from(wait_with_output_child(child)) } -actual fun stdinChild(child: COpaquePointer?): BufferedWriter? { - return BufferedWriter(stdin_child(child)) +actual fun bufferedStdinChild(child: COpaquePointer?): BufferedWriter? { + return BufferedWriter(buffered_stdin_child(child)) } -actual fun stdoutChild(child: COpaquePointer?): BufferedReader? { - return BufferedReader(stdin_child(child), ReaderType.STDOUT) +actual fun bufferedStdoutChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(buffered_stdout_child(child), ReaderType.STDOUT) } -actual fun stderrChild(child: COpaquePointer?): BufferedReader? { - return BufferedReader(stderr_child(child), ReaderType.STDERR) +actual fun bufferedStderrChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(buffered_stderr_child(child), ReaderType.STDERR) } diff --git a/src/linuxX64Main/kotlin/com/kgit2/wrapper/EnvVars.linuxX64.kt b/src/linuxX64Main/kotlin/com/kgit2/wrapper/EnvVars.linuxX64.kt new file mode 100644 index 0000000..1e8cb23 --- /dev/null +++ b/src/linuxX64Main/kotlin/com/kgit2/wrapper/EnvVars.linuxX64.kt @@ -0,0 +1,30 @@ +package com.kgit2.wrapper + +import kommand_core.drop_env_vars +import kommand_core.env_var +import kommand_core.env_vars +import kotlinx.cinterop.convert +import kotlinx.cinterop.get +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.toKString + +actual fun envVar(name: String): String? { + return env_var(name)?.asString() +} + +actual fun envVars(): Map = memScoped { + val envVars = env_vars() + val map = mutableMapOf() + val length = envVars.ptr.pointed.len + val envVarsValue = envVars.ptr.pointed + (0UL until length).forEach { i -> + val name = envVarsValue.names?.get(i.convert())?.toKString() + val value = envVarsValue.values?.get(i.convert())?.toKString() + if (name != null && value != null) { + map[name] = value + } + } + drop_env_vars(envVars) + map +} diff --git a/src/linuxX64Main/kotlin/com/kgit2/wrapper/Extension.linuxX64.kt b/src/linuxX64Main/kotlin/com/kgit2/wrapper/Extension.linuxX64.kt index d51b5e0..4d3464c 100644 --- a/src/linuxX64Main/kotlin/com/kgit2/wrapper/Extension.linuxX64.kt +++ b/src/linuxX64Main/kotlin/com/kgit2/wrapper/Extension.linuxX64.kt @@ -25,14 +25,14 @@ inline fun CPointer.asString(): String { @Throws(KommandException::class) fun Child.Companion.from(result: CValue): Child = memScoped { if (result.ptr.pointed.ok != null) { - val child = Child(result.ptr.pointed.ok) - child.updateIO() - child - } else { + Child(result.ptr.pointed.ok) + } else if (result.ptr.pointed.err != null) { throw KommandException( result.ptr.pointed.err?.asString(), result.ptr.pointed.error_type.to() ) + } else { + throw KommandException("[spawn_command] return [result]'s [ok] & [err] are both null", ErrorType.Unknown) } } diff --git a/src/linuxX64Main/kotlin/com/kgit2/wrapper/IO.kt b/src/linuxX64Main/kotlin/com/kgit2/wrapper/IO.linuxX64.kt similarity index 100% rename from src/linuxX64Main/kotlin/com/kgit2/wrapper/IO.kt rename to src/linuxX64Main/kotlin/com/kgit2/wrapper/IO.linuxX64.kt diff --git a/src/macosArm64Main/kotlin/com/kgit2/wrapper/Child.macosArm64.kt b/src/macosArm64Main/kotlin/com/kgit2/wrapper/Child.macosArm64.kt index 97c4b72..5b00b2c 100644 --- a/src/macosArm64Main/kotlin/com/kgit2/wrapper/Child.macosArm64.kt +++ b/src/macosArm64Main/kotlin/com/kgit2/wrapper/Child.macosArm64.kt @@ -5,11 +5,12 @@ import com.kgit2.io.BufferedReader import com.kgit2.io.BufferedWriter import com.kgit2.io.Output import com.kgit2.io.ReaderType +import kommand_core.buffered_stderr_child +import kommand_core.buffered_stdin_child +import kommand_core.buffered_stdout_child import kommand_core.drop_child import kommand_core.id_child import kommand_core.kill_child -import kommand_core.stderr_child -import kommand_core.stdin_child import kommand_core.wait_child import kommand_core.wait_with_output_child import kotlinx.cinterop.COpaquePointer @@ -37,14 +38,14 @@ actual fun waitWithOutputChild(child: COpaquePointer?): Output = run { Output.from(wait_with_output_child(child)) } -actual fun stdinChild(child: COpaquePointer?): BufferedWriter? { - return BufferedWriter(stdin_child(child)) +actual fun bufferedStdinChild(child: COpaquePointer?): BufferedWriter? { + return BufferedWriter(buffered_stdin_child(child)) } -actual fun stdoutChild(child: COpaquePointer?): BufferedReader? { - return BufferedReader(stdin_child(child), ReaderType.STDOUT) +actual fun bufferedStdoutChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(buffered_stdout_child(child), ReaderType.STDOUT) } -actual fun stderrChild(child: COpaquePointer?): BufferedReader? { - return BufferedReader(stderr_child(child), ReaderType.STDERR) +actual fun bufferedStderrChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(buffered_stderr_child(child), ReaderType.STDERR) } diff --git a/src/macosArm64Main/kotlin/com/kgit2/wrapper/EnvVars.macosArm64.kt b/src/macosArm64Main/kotlin/com/kgit2/wrapper/EnvVars.macosArm64.kt new file mode 100644 index 0000000..1e8cb23 --- /dev/null +++ b/src/macosArm64Main/kotlin/com/kgit2/wrapper/EnvVars.macosArm64.kt @@ -0,0 +1,30 @@ +package com.kgit2.wrapper + +import kommand_core.drop_env_vars +import kommand_core.env_var +import kommand_core.env_vars +import kotlinx.cinterop.convert +import kotlinx.cinterop.get +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.toKString + +actual fun envVar(name: String): String? { + return env_var(name)?.asString() +} + +actual fun envVars(): Map = memScoped { + val envVars = env_vars() + val map = mutableMapOf() + val length = envVars.ptr.pointed.len + val envVarsValue = envVars.ptr.pointed + (0UL until length).forEach { i -> + val name = envVarsValue.names?.get(i.convert())?.toKString() + val value = envVarsValue.values?.get(i.convert())?.toKString() + if (name != null && value != null) { + map[name] = value + } + } + drop_env_vars(envVars) + map +} diff --git a/src/macosArm64Main/kotlin/com/kgit2/wrapper/Extension.macosArm64.kt b/src/macosArm64Main/kotlin/com/kgit2/wrapper/Extension.macosArm64.kt index d51b5e0..4d3464c 100644 --- a/src/macosArm64Main/kotlin/com/kgit2/wrapper/Extension.macosArm64.kt +++ b/src/macosArm64Main/kotlin/com/kgit2/wrapper/Extension.macosArm64.kt @@ -25,14 +25,14 @@ inline fun CPointer.asString(): String { @Throws(KommandException::class) fun Child.Companion.from(result: CValue): Child = memScoped { if (result.ptr.pointed.ok != null) { - val child = Child(result.ptr.pointed.ok) - child.updateIO() - child - } else { + Child(result.ptr.pointed.ok) + } else if (result.ptr.pointed.err != null) { throw KommandException( result.ptr.pointed.err?.asString(), result.ptr.pointed.error_type.to() ) + } else { + throw KommandException("[spawn_command] return [result]'s [ok] & [err] are both null", ErrorType.Unknown) } } diff --git a/src/macosArm64Main/kotlin/com/kgit2/wrapper/IO.kt b/src/macosArm64Main/kotlin/com/kgit2/wrapper/IO.macosArm64.kt similarity index 100% rename from src/macosArm64Main/kotlin/com/kgit2/wrapper/IO.kt rename to src/macosArm64Main/kotlin/com/kgit2/wrapper/IO.macosArm64.kt diff --git a/src/macosX64Main/kotlin/com/kgit2/wrapper/Child.macosX64.kt b/src/macosX64Main/kotlin/com/kgit2/wrapper/Child.macosX64.kt index 97c4b72..5b00b2c 100644 --- a/src/macosX64Main/kotlin/com/kgit2/wrapper/Child.macosX64.kt +++ b/src/macosX64Main/kotlin/com/kgit2/wrapper/Child.macosX64.kt @@ -5,11 +5,12 @@ import com.kgit2.io.BufferedReader import com.kgit2.io.BufferedWriter import com.kgit2.io.Output import com.kgit2.io.ReaderType +import kommand_core.buffered_stderr_child +import kommand_core.buffered_stdin_child +import kommand_core.buffered_stdout_child import kommand_core.drop_child import kommand_core.id_child import kommand_core.kill_child -import kommand_core.stderr_child -import kommand_core.stdin_child import kommand_core.wait_child import kommand_core.wait_with_output_child import kotlinx.cinterop.COpaquePointer @@ -37,14 +38,14 @@ actual fun waitWithOutputChild(child: COpaquePointer?): Output = run { Output.from(wait_with_output_child(child)) } -actual fun stdinChild(child: COpaquePointer?): BufferedWriter? { - return BufferedWriter(stdin_child(child)) +actual fun bufferedStdinChild(child: COpaquePointer?): BufferedWriter? { + return BufferedWriter(buffered_stdin_child(child)) } -actual fun stdoutChild(child: COpaquePointer?): BufferedReader? { - return BufferedReader(stdin_child(child), ReaderType.STDOUT) +actual fun bufferedStdoutChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(buffered_stdout_child(child), ReaderType.STDOUT) } -actual fun stderrChild(child: COpaquePointer?): BufferedReader? { - return BufferedReader(stderr_child(child), ReaderType.STDERR) +actual fun bufferedStderrChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(buffered_stderr_child(child), ReaderType.STDERR) } diff --git a/src/macosX64Main/kotlin/com/kgit2/wrapper/EnvVars.macosX64.kt b/src/macosX64Main/kotlin/com/kgit2/wrapper/EnvVars.macosX64.kt new file mode 100644 index 0000000..1e8cb23 --- /dev/null +++ b/src/macosX64Main/kotlin/com/kgit2/wrapper/EnvVars.macosX64.kt @@ -0,0 +1,30 @@ +package com.kgit2.wrapper + +import kommand_core.drop_env_vars +import kommand_core.env_var +import kommand_core.env_vars +import kotlinx.cinterop.convert +import kotlinx.cinterop.get +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.toKString + +actual fun envVar(name: String): String? { + return env_var(name)?.asString() +} + +actual fun envVars(): Map = memScoped { + val envVars = env_vars() + val map = mutableMapOf() + val length = envVars.ptr.pointed.len + val envVarsValue = envVars.ptr.pointed + (0UL until length).forEach { i -> + val name = envVarsValue.names?.get(i.convert())?.toKString() + val value = envVarsValue.values?.get(i.convert())?.toKString() + if (name != null && value != null) { + map[name] = value + } + } + drop_env_vars(envVars) + map +} diff --git a/src/macosX64Main/kotlin/com/kgit2/wrapper/Extension.macosX64.kt b/src/macosX64Main/kotlin/com/kgit2/wrapper/Extension.macosX64.kt index d51b5e0..4d3464c 100644 --- a/src/macosX64Main/kotlin/com/kgit2/wrapper/Extension.macosX64.kt +++ b/src/macosX64Main/kotlin/com/kgit2/wrapper/Extension.macosX64.kt @@ -25,14 +25,14 @@ inline fun CPointer.asString(): String { @Throws(KommandException::class) fun Child.Companion.from(result: CValue): Child = memScoped { if (result.ptr.pointed.ok != null) { - val child = Child(result.ptr.pointed.ok) - child.updateIO() - child - } else { + Child(result.ptr.pointed.ok) + } else if (result.ptr.pointed.err != null) { throw KommandException( result.ptr.pointed.err?.asString(), result.ptr.pointed.error_type.to() ) + } else { + throw KommandException("[spawn_command] return [result]'s [ok] & [err] are both null", ErrorType.Unknown) } } diff --git a/src/macosX64Main/kotlin/com/kgit2/wrapper/IO.kt b/src/macosX64Main/kotlin/com/kgit2/wrapper/IO.macosX64.kt similarity index 100% rename from src/macosX64Main/kotlin/com/kgit2/wrapper/IO.kt rename to src/macosX64Main/kotlin/com/kgit2/wrapper/IO.macosX64.kt diff --git a/src/mingwX64Main/kotlin/com/kgit2/wrapper/Child.mingwX64.kt b/src/mingwX64Main/kotlin/com/kgit2/wrapper/Child.mingwX64.kt index 97c4b72..5b00b2c 100644 --- a/src/mingwX64Main/kotlin/com/kgit2/wrapper/Child.mingwX64.kt +++ b/src/mingwX64Main/kotlin/com/kgit2/wrapper/Child.mingwX64.kt @@ -5,11 +5,12 @@ import com.kgit2.io.BufferedReader import com.kgit2.io.BufferedWriter import com.kgit2.io.Output import com.kgit2.io.ReaderType +import kommand_core.buffered_stderr_child +import kommand_core.buffered_stdin_child +import kommand_core.buffered_stdout_child import kommand_core.drop_child import kommand_core.id_child import kommand_core.kill_child -import kommand_core.stderr_child -import kommand_core.stdin_child import kommand_core.wait_child import kommand_core.wait_with_output_child import kotlinx.cinterop.COpaquePointer @@ -37,14 +38,14 @@ actual fun waitWithOutputChild(child: COpaquePointer?): Output = run { Output.from(wait_with_output_child(child)) } -actual fun stdinChild(child: COpaquePointer?): BufferedWriter? { - return BufferedWriter(stdin_child(child)) +actual fun bufferedStdinChild(child: COpaquePointer?): BufferedWriter? { + return BufferedWriter(buffered_stdin_child(child)) } -actual fun stdoutChild(child: COpaquePointer?): BufferedReader? { - return BufferedReader(stdin_child(child), ReaderType.STDOUT) +actual fun bufferedStdoutChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(buffered_stdout_child(child), ReaderType.STDOUT) } -actual fun stderrChild(child: COpaquePointer?): BufferedReader? { - return BufferedReader(stderr_child(child), ReaderType.STDERR) +actual fun bufferedStderrChild(child: COpaquePointer?): BufferedReader? { + return BufferedReader(buffered_stderr_child(child), ReaderType.STDERR) } diff --git a/src/mingwX64Main/kotlin/com/kgit2/wrapper/EnvVars.mingwX64.kt b/src/mingwX64Main/kotlin/com/kgit2/wrapper/EnvVars.mingwX64.kt new file mode 100644 index 0000000..1e8cb23 --- /dev/null +++ b/src/mingwX64Main/kotlin/com/kgit2/wrapper/EnvVars.mingwX64.kt @@ -0,0 +1,30 @@ +package com.kgit2.wrapper + +import kommand_core.drop_env_vars +import kommand_core.env_var +import kommand_core.env_vars +import kotlinx.cinterop.convert +import kotlinx.cinterop.get +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.toKString + +actual fun envVar(name: String): String? { + return env_var(name)?.asString() +} + +actual fun envVars(): Map = memScoped { + val envVars = env_vars() + val map = mutableMapOf() + val length = envVars.ptr.pointed.len + val envVarsValue = envVars.ptr.pointed + (0UL until length).forEach { i -> + val name = envVarsValue.names?.get(i.convert())?.toKString() + val value = envVarsValue.values?.get(i.convert())?.toKString() + if (name != null && value != null) { + map[name] = value + } + } + drop_env_vars(envVars) + map +} diff --git a/src/mingwX64Main/kotlin/com/kgit2/wrapper/Extension.mingwX64.kt b/src/mingwX64Main/kotlin/com/kgit2/wrapper/Extension.mingwX64.kt index d51b5e0..4d3464c 100644 --- a/src/mingwX64Main/kotlin/com/kgit2/wrapper/Extension.mingwX64.kt +++ b/src/mingwX64Main/kotlin/com/kgit2/wrapper/Extension.mingwX64.kt @@ -25,14 +25,14 @@ inline fun CPointer.asString(): String { @Throws(KommandException::class) fun Child.Companion.from(result: CValue): Child = memScoped { if (result.ptr.pointed.ok != null) { - val child = Child(result.ptr.pointed.ok) - child.updateIO() - child - } else { + Child(result.ptr.pointed.ok) + } else if (result.ptr.pointed.err != null) { throw KommandException( result.ptr.pointed.err?.asString(), result.ptr.pointed.error_type.to() ) + } else { + throw KommandException("[spawn_command] return [result]'s [ok] & [err] are both null", ErrorType.Unknown) } } diff --git a/src/mingwX64Main/kotlin/com/kgit2/wrapper/IO.kt b/src/mingwX64Main/kotlin/com/kgit2/wrapper/IO.mingwX64.kt similarity index 100% rename from src/mingwX64Main/kotlin/com/kgit2/wrapper/IO.kt rename to src/mingwX64Main/kotlin/com/kgit2/wrapper/IO.mingwX64.kt diff --git a/src/nativeInterop/cinterop/linuxarm64.def b/src/nativeInterop/cinterop/linuxarm64.def index 01e441f..c2ab1ee 100644 --- a/src/nativeInterop/cinterop/linuxarm64.def +++ b/src/nativeInterop/cinterop/linuxarm64.def @@ -1,4 +1,4 @@ headers = kommand_core.h staticLibraries = libkommand_core.a -compilerOpts = -Ikommand_core -libraryPaths = kommand_core/target/aarch64-unknown-linux-gnu/release +compilerOpts = -Ikommand-core +libraryPaths = kommand-core/target/aarch64-unknown-linux-gnu/release diff --git a/src/nativeInterop/cinterop/linuxx64.def b/src/nativeInterop/cinterop/linuxx64.def index 8c44657..5de6138 100644 --- a/src/nativeInterop/cinterop/linuxx64.def +++ b/src/nativeInterop/cinterop/linuxx64.def @@ -1,4 +1,4 @@ headers = kommand_core.h staticLibraries = libkommand_core.a -compilerOpts = -Ikommand_core -libraryPaths = kommand_core/target/x86_64-unknown-linux-gnu/release +compilerOpts = -Ikommand-core +libraryPaths = kommand-core/target/x86_64-unknown-linux-gnu/release diff --git a/src/nativeInterop/cinterop/macos.def b/src/nativeInterop/cinterop/macos.def index 73c15a5..e94bdd1 100644 --- a/src/nativeInterop/cinterop/macos.def +++ b/src/nativeInterop/cinterop/macos.def @@ -1,4 +1,4 @@ headers = kommand_core.h staticLibraries = libkommand_core.a -compilerOpts = -Ikommand_core -libraryPaths = kommand_core/target/universal-apple-darwin/release +compilerOpts = -Ikommand-core +libraryPaths = kommand-core/target/universal-apple-darwin/release diff --git a/src/nativeInterop/cinterop/mingw64.def b/src/nativeInterop/cinterop/mingw64.def index 91d8abc..2ddc243 100644 --- a/src/nativeInterop/cinterop/mingw64.def +++ b/src/nativeInterop/cinterop/mingw64.def @@ -1,5 +1,5 @@ headers = kommand_core.h staticLibraries = libkommand_core.a -compilerOpts = -Ikommand_core +compilerOpts = -Ikommand-core linkerOpts = -static -lws2_32 -lbcrypt -luserenv -lntdll -v -libraryPaths = kommand_core/target/x86_64-pc-windows-gnu/release +libraryPaths = kommand-core/target/x86_64-pc-windows-gnu/release diff --git a/src/nativeMain/kotlin/com/kgit2/env/EnvVars.native.kt b/src/nativeMain/kotlin/com/kgit2/env/EnvVars.native.kt new file mode 100644 index 0000000..8c04fe0 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/env/EnvVars.native.kt @@ -0,0 +1,9 @@ +package com.kgit2.env + +actual fun envVar(key: String): String? { + return com.kgit2.wrapper.envVar(key) +} + +actual fun envVars(): Map? { + return com.kgit2.wrapper.envVars() +} diff --git a/src/nativeMain/kotlin/com/kgit2/io/BufferedWriter.native.kt b/src/nativeMain/kotlin/com/kgit2/io/BufferedWriter.native.kt index d3af791..c4b1677 100644 --- a/src/nativeMain/kotlin/com/kgit2/io/BufferedWriter.native.kt +++ b/src/nativeMain/kotlin/com/kgit2/io/BufferedWriter.native.kt @@ -4,14 +4,19 @@ import com.kgit2.exception.KommandException import com.kgit2.wrapper.dropStdin import com.kgit2.wrapper.flushStdin import com.kgit2.wrapper.writeLineStdin +import kotlinx.atomicfu.atomic import kotlinx.cinterop.COpaquePointer import kotlin.native.ref.createCleaner actual class BufferedWriter( - private val inner: COpaquePointer? + private var inner: COpaquePointer? ) { - val cleaner = createCleaner(inner) { writer -> - dropStdin(writer) + private val isClosed = atomic(false) + + private val cleaner = createCleaner(isClosed to inner) { (freed, writer) -> + if (freed.compareAndSet(expect = false, update = true)) { + dropStdin(writer) + } } @Throws(KommandException::class) @@ -23,4 +28,10 @@ actual class BufferedWriter( actual fun flush() = run { flushStdin(inner) } + + @Throws(KommandException::class) + actual fun close() { + dropStdin(inner) + isClosed.getAndSet(true) + } } diff --git a/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt b/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt index 1ebe62c..5b26081 100644 --- a/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt +++ b/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt @@ -7,23 +7,28 @@ import com.kgit2.io.Output import com.kgit2.wrapper.dropChild import com.kgit2.wrapper.idChild import com.kgit2.wrapper.killChild -import com.kgit2.wrapper.stderrChild -import com.kgit2.wrapper.stdinChild -import com.kgit2.wrapper.stdoutChild +import com.kgit2.wrapper.bufferedStderrChild +import com.kgit2.wrapper.bufferedStdinChild +import com.kgit2.wrapper.bufferedStdoutChild import com.kgit2.wrapper.waitChild import com.kgit2.wrapper.waitWithOutputChild +import kotlinx.atomicfu.atomic import kotlinx.cinterop.COpaquePointer import kotlin.native.ref.createCleaner actual class Child( - private val inner: COpaquePointer? + private var inner: COpaquePointer? ) { - actual var stdin: BufferedWriter? = null - actual var stdout: BufferedReader? = null - actual var stderr: BufferedReader? = null + var stdin: BufferedWriter? = null + var stdout: BufferedReader? = null + var stderr: BufferedReader? = null - val cleaner = createCleaner(inner) { child -> - dropChild(child) + private val isClosed = atomic(false) + + private val cleaner = createCleaner(isClosed to inner) { (freed, child) -> + if (freed.compareAndSet(expect = false, update = true)) { + dropChild(child) + } } companion object; @@ -32,39 +37,56 @@ actual class Child( return idChild(inner) } - @Throws(KommandException::class) - actual fun kill() { - return run { killChild(inner) } + actual fun bufferedStdin(): BufferedWriter? { + if (stdin == null) { + updateStdin() + } + return stdin + } + + actual fun bufferedStdout(): BufferedReader? { + if (stdout == null) { + updateStdout() + } + return stdout + } + + actual fun bufferedStderr(): BufferedReader? { + if (stderr == null) { + updateStderr() + } + return stderr } @Throws(KommandException::class) - actual fun wait(): Int { - return run { waitChild(inner) } + actual fun kill() = run { + killChild(inner) } @Throws(KommandException::class) - actual fun waitWithOutput(): Output { - return run { waitWithOutputChild(inner) } + actual fun wait(): Int = run { + stdin?.close() + waitChild(inner) } - internal fun updateIO() { - updateStdin() - updateStdout() - updateStderr() + @Throws(KommandException::class) + actual fun waitWithOutput(): Output = run { + stdin?.close() + val inner = this.inner + this.inner = null + isClosed.getAndSet(true) + waitWithOutputChild(inner) } - @Suppress("MemberVisibilityCanBePrivate") - internal fun updateStdin() { - stdin = stdinChild(inner) + private fun updateStdin() { + stdin = bufferedStdinChild(inner) } - @Suppress("MemberVisibilityCanBePrivate") - internal fun updateStdout() { - stdout = stdoutChild(inner) + private fun updateStdout() { + stdout = bufferedStdoutChild(inner) } - @Suppress("MemberVisibilityCanBePrivate") - internal fun updateStderr() { - stderr = stderrChild(inner) + private fun updateStderr() { + stderr = bufferedStderrChild(inner) } } diff --git a/src/nativeMain/kotlin/com/kgit2/process/Command.native.kt b/src/nativeMain/kotlin/com/kgit2/process/Command.native.kt index 3199d41..405c205 100644 --- a/src/nativeMain/kotlin/com/kgit2/process/Command.native.kt +++ b/src/nativeMain/kotlin/com/kgit2/process/Command.native.kt @@ -2,7 +2,21 @@ package com.kgit2.process import com.kgit2.exception.KommandException import com.kgit2.io.Output -import com.kgit2.wrapper.* +import com.kgit2.wrapper.argCommand +import com.kgit2.wrapper.currentDirCommand +import com.kgit2.wrapper.debugCommand +import com.kgit2.wrapper.displayCommand +import com.kgit2.wrapper.dropCommand +import com.kgit2.wrapper.envClearCommand +import com.kgit2.wrapper.envCommand +import com.kgit2.wrapper.newCommand +import com.kgit2.wrapper.outputCommand +import com.kgit2.wrapper.removeEnvCommand +import com.kgit2.wrapper.spawnCommand +import com.kgit2.wrapper.statusCommand +import com.kgit2.wrapper.stderrCommand +import com.kgit2.wrapper.stdinCommand +import com.kgit2.wrapper.stdoutCommand import kotlinx.cinterop.COpaquePointer import kotlin.native.ref.createCleaner diff --git a/src/nativeMain/kotlin/com/kgit2/wrapper/Child.kt b/src/nativeMain/kotlin/com/kgit2/wrapper/Child.kt index a0955dd..6c3bfee 100644 --- a/src/nativeMain/kotlin/com/kgit2/wrapper/Child.kt +++ b/src/nativeMain/kotlin/com/kgit2/wrapper/Child.kt @@ -19,8 +19,8 @@ expect fun waitChild(child: COpaquePointer?): Int @Throws(KommandException::class) expect fun waitWithOutputChild(child: COpaquePointer?): Output -expect fun stdinChild(child: COpaquePointer?): BufferedWriter? +expect fun bufferedStdinChild(child: COpaquePointer?): BufferedWriter? -expect fun stdoutChild(child: COpaquePointer?): BufferedReader? +expect fun bufferedStdoutChild(child: COpaquePointer?): BufferedReader? -expect fun stderrChild(child: COpaquePointer?): BufferedReader? +expect fun bufferedStderrChild(child: COpaquePointer?): BufferedReader? diff --git a/src/nativeMain/kotlin/com/kgit2/wrapper/Command.kt b/src/nativeMain/kotlin/com/kgit2/wrapper/Command.kt index 9ee6e2c..bb72f5e 100644 --- a/src/nativeMain/kotlin/com/kgit2/wrapper/Command.kt +++ b/src/nativeMain/kotlin/com/kgit2/wrapper/Command.kt @@ -1,8 +1,8 @@ package com.kgit2.wrapper import com.kgit2.exception.KommandException -import com.kgit2.process.Child import com.kgit2.io.Output +import com.kgit2.process.Child import com.kgit2.process.Stdio import kotlinx.cinterop.COpaquePointer diff --git a/src/nativeMain/kotlin/com/kgit2/wrapper/EnvVars.kt b/src/nativeMain/kotlin/com/kgit2/wrapper/EnvVars.kt new file mode 100644 index 0000000..4ebe1e0 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/wrapper/EnvVars.kt @@ -0,0 +1,5 @@ +package com.kgit2.wrapper + +expect fun envVar(name: String): String? + +expect fun envVars(): Map From 874bf287ca8df69e906767c49b1de94f78b1594c Mon Sep 17 00:00:00 2001 From: bppleman Date: Sun, 24 Dec 2023 02:32:24 +0800 Subject: [PATCH 08/22] Wrap the buffered io with atomic to prevent multi-threaded secondary creation --- .idea/.gitignore | 8 ++++ .idea/compiler.xml | 6 --- .idea/inspectionProfiles/Project_Default.xml | 10 ---- .idea/jarRepositories.xml | 25 ---------- .idea/jsonSchemas.xml | 25 ---------- .idea/kotlinc.xml | 9 ---- .idea/vcs.xml | 6 --- .../kotlin/com/kgit2/process/Child.native.kt | 46 +++++++------------ 8 files changed, 24 insertions(+), 111 deletions(-) create mode 100644 .idea/.gitignore delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/jarRepositories.xml delete mode 100644 .idea/jsonSchemas.xml delete mode 100644 .idea/kotlinc.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index b589d56..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 5c593f5..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index e40fc1c..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/jsonSchemas.xml b/.idea/jsonSchemas.xml deleted file mode 100644 index 2a0b4e8..0000000 --- a/.idea/jsonSchemas.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index c8378aa..0000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt b/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt index 5b26081..10458cb 100644 --- a/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt +++ b/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt @@ -13,15 +13,19 @@ import com.kgit2.wrapper.bufferedStdoutChild import com.kgit2.wrapper.waitChild import com.kgit2.wrapper.waitWithOutputChild import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.locks.SynchronizedObject +import kotlinx.atomicfu.locks.synchronized import kotlinx.cinterop.COpaquePointer +import kotlinx.coroutines.internal.synchronized +import kotlin.concurrent.AtomicReference import kotlin.native.ref.createCleaner actual class Child( private var inner: COpaquePointer? -) { - var stdin: BufferedWriter? = null - var stdout: BufferedReader? = null - var stderr: BufferedReader? = null +): SynchronizedObject() { + private var stdin: AtomicReference = AtomicReference(null) + private var stdout: AtomicReference = AtomicReference(null) + private var stderr: AtomicReference = AtomicReference(null) private val isClosed = atomic(false) @@ -38,24 +42,18 @@ actual class Child( } actual fun bufferedStdin(): BufferedWriter? { - if (stdin == null) { - updateStdin() - } - return stdin + stdin.compareAndSet(null, bufferedStdinChild(inner)) + return stdin.value } actual fun bufferedStdout(): BufferedReader? { - if (stdout == null) { - updateStdout() - } - return stdout + stdout.compareAndSet(null, bufferedStdoutChild(inner)) + return stdout.value } actual fun bufferedStderr(): BufferedReader? { - if (stderr == null) { - updateStderr() - } - return stderr + stderr.compareAndSet(null, bufferedStderrChild(inner)) + return stderr.value } @Throws(KommandException::class) @@ -65,28 +63,16 @@ actual class Child( @Throws(KommandException::class) actual fun wait(): Int = run { - stdin?.close() + stdin.getAndSet(null)?.close() waitChild(inner) } @Throws(KommandException::class) actual fun waitWithOutput(): Output = run { - stdin?.close() + stdin.getAndSet(null)?.close() val inner = this.inner this.inner = null isClosed.getAndSet(true) waitWithOutputChild(inner) } - - private fun updateStdin() { - stdin = bufferedStdinChild(inner) - } - - private fun updateStdout() { - stdout = bufferedStdoutChild(inner) - } - - private fun updateStderr() { - stderr = bufferedStderrChild(inner) - } } From b7bb2128b87e0cb6c9609961c3a6607cd62856e6 Mon Sep 17 00:00:00 2001 From: bppleman Date: Sun, 24 Dec 2023 02:40:21 +0800 Subject: [PATCH 09/22] Update idea config file --- .gitignore | 114 ---------- kommand-core/.idea/.gitignore | 8 + kommand-core/.idea/kommand-core.iml | 13 ++ kommand-core/.idea/modules.xml | 8 + kommand-core/.idea/workspace.xml | 309 ---------------------------- 5 files changed, 29 insertions(+), 423 deletions(-) create mode 100644 kommand-core/.idea/.gitignore create mode 100644 kommand-core/.idea/kommand-core.iml create mode 100644 kommand-core/.idea/modules.xml delete mode 100644 kommand-core/.idea/workspace.xml diff --git a/.gitignore b/.gitignore index 037b02a..de17a73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,120 +1,6 @@ # Created by https://www.toptal.com/developers/gitignore/api/macos,intellij+iml,vim,visualstudiocode,gradle,kotlin # Edit at https://www.toptal.com/developers/gitignore?templates=macos,intellij+iml,vim,visualstudiocode,gradle,kotlin -### Intellij+iml ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries -.idea/artifacts - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### Intellij+iml Patch ### -# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 - -*.iml -modules.xml -.idea/misc.xml -*.ipr - -### Kotlin ### -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -replay_pid* - ### macOS ### # General .DS_Store diff --git a/kommand-core/.idea/.gitignore b/kommand-core/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/kommand-core/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/kommand-core/.idea/kommand-core.iml b/kommand-core/.idea/kommand-core.iml new file mode 100644 index 0000000..6970db0 --- /dev/null +++ b/kommand-core/.idea/kommand-core.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/kommand-core/.idea/modules.xml b/kommand-core/.idea/modules.xml new file mode 100644 index 0000000..2455ebc --- /dev/null +++ b/kommand-core/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/kommand-core/.idea/workspace.xml b/kommand-core/.idea/workspace.xml deleted file mode 100644 index 9f7365a..0000000 --- a/kommand-core/.idea/workspace.xml +++ /dev/null @@ -1,309 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - "associatedIndex": 4 -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1703038493898 - - - - - - \ No newline at end of file From db7f4d71f09707d73725ed5a3ba51cf368780c84 Mon Sep 17 00:00:00 2001 From: bppleman Date: Sun, 24 Dec 2023 15:58:54 +0800 Subject: [PATCH 10/22] Implement full api for jvm and pass all tests --- .idea/artifacts/kommand_jvm_1_2_0.xml | 8 ++++ .idea/compiler.xml | 6 +++ .idea/gradle.xml | 16 ++++++++ .idea/jarRepositories.xml | 25 +++++++++++++ .idea/misc.xml | 8 ++++ .idea/modules.xml | 8 ++++ .idea/modules/kommand.iml | 9 +++++ .idea/vcs.xml | 6 +++ gradle.properties | 3 +- kommand-core/src/io.rs | 15 ++++++-- src/commonTest/kotlin/com/kgit2/ChildTest.kt | 6 +-- .../kotlin/com/kgit2/env/EnvVars.jvm.kt | 4 +- .../kotlin/com/kgit2/io/BufferedReader.jvm.kt | 16 ++++++-- .../kotlin/com/kgit2/io/BufferedWriter.jvm.kt | 16 +++++++- .../kotlin/com/kgit2/process/Child.jvm.kt | 37 ++++++++++++++----- .../kotlin/com/kgit2/process/Command.jvm.kt | 6 +-- .../kotlin/com/kgit2/process/Child.native.kt | 11 +++--- 17 files changed, 168 insertions(+), 32 deletions(-) create mode 100644 .idea/artifacts/kommand_jvm_1_2_0.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/modules/kommand.iml create mode 100644 .idea/vcs.xml diff --git a/.idea/artifacts/kommand_jvm_1_2_0.xml b/.idea/artifacts/kommand_jvm_1_2_0.xml new file mode 100644 index 0000000..d54754b --- /dev/null +++ b/.idea/artifacts/kommand_jvm_1_2_0.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..39ec4f5 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..e40fc1c --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..de0c428 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..7057f12 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/kommand.iml b/.idea/modules/kommand.iml new file mode 100644 index 0000000..2ed6250 --- /dev/null +++ b/.idea/modules/kommand.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 7e5a284..7e59c15 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ kotlin.code.style=official org.gradle.jvmargs=-Xmx4096m -org.gradle.caching=true +#org.gradle.caching=true +kotlin.mpp.enableCInteropCommonization=true diff --git a/kommand-core/src/io.rs b/kommand-core/src/io.rs index f715e52..91ba1dd 100644 --- a/kommand-core/src/io.rs +++ b/kommand-core/src/io.rs @@ -17,7 +17,10 @@ pub use stdout::drop_stdout; pub extern "C" fn read_line_stdout(mut reader: *const c_void) -> VoidResult { let reader = as_stdout_mut(&mut reader); let mut line = String::new(); - reader.read_line(&mut line).map(|_| line).into() + reader + .read_line(&mut line) + .map(|_| line.trim_end_matches('\n').to_string()) + .into() } #[no_mangle] @@ -31,7 +34,10 @@ pub extern "C" fn read_all_stdout(mut reader: *const c_void) -> VoidResult { pub extern "C" fn read_line_stderr(mut reader: *const c_void) -> VoidResult { let reader = stderr::as_stderr_mut(&mut reader); let mut line = String::new(); - reader.read_line(&mut line).map(|_| line).into() + reader + .read_line(&mut line) + .map(|_| line.trim_end_matches('\n').to_string()) + .into() } #[no_mangle] @@ -49,7 +55,10 @@ pub unsafe extern "C" fn write_line_stdin( ) -> UnitResult { let writer = stdin::as_stdin_mut(&mut writer); let line = as_string(line); - let result = writer.write_all(line.as_bytes()); + let result = writer + .write_all(line.as_bytes()) + .and_then(|_| writer.write(b"\n")) + .map(|_| ()); result.into() } diff --git a/src/commonTest/kotlin/com/kgit2/ChildTest.kt b/src/commonTest/kotlin/com/kgit2/ChildTest.kt index 00d8964..c58ff30 100644 --- a/src/commonTest/kotlin/com/kgit2/ChildTest.kt +++ b/src/commonTest/kotlin/com/kgit2/ChildTest.kt @@ -32,7 +32,7 @@ class ChildTest { var line = child.bufferedStdout()?.readLine() var expect = 0 while (!line.isNullOrEmpty()) { - assertEquals("$expect\n", line) + assertEquals(expect.toString(), line) line = child.bufferedStdout()?.readLine() expect += 1 } @@ -47,7 +47,7 @@ class ChildTest { .stdin(Stdio.Pipe) .stdout(Stdio.Pipe) val child = command.spawn() - val expect = "Hello World!\n" + val expect = "Hello World!" child.bufferedStdin()?.writeLine(expect) child.bufferedStdin()?.flush() val line = child.bufferedStdout()?.readLine() @@ -64,7 +64,7 @@ class ChildTest { .stdin(Stdio.Pipe) .stdout(Stdio.Pipe) val child = command.spawn() - val expect = "Hello World!\n" + val expect = "Hello World!" for (j in 0 .. 100) { child.bufferedStdin()?.writeLine("[$j]$expect") child.bufferedStdin()?.flush() diff --git a/src/jvmMain/kotlin/com/kgit2/env/EnvVars.jvm.kt b/src/jvmMain/kotlin/com/kgit2/env/EnvVars.jvm.kt index 0eec3fc..81625ff 100644 --- a/src/jvmMain/kotlin/com/kgit2/env/EnvVars.jvm.kt +++ b/src/jvmMain/kotlin/com/kgit2/env/EnvVars.jvm.kt @@ -1,9 +1,9 @@ package com.kgit2.env actual fun envVar(key: String): String? { - TODO("Not yet implemented") + return System.getenv(key) } actual fun envVars(): Map? { - TODO("Not yet implemented") + return System.getenv() } diff --git a/src/jvmMain/kotlin/com/kgit2/io/BufferedReader.jvm.kt b/src/jvmMain/kotlin/com/kgit2/io/BufferedReader.jvm.kt index 36bdc41..47ec8e6 100644 --- a/src/jvmMain/kotlin/com/kgit2/io/BufferedReader.jvm.kt +++ b/src/jvmMain/kotlin/com/kgit2/io/BufferedReader.jvm.kt @@ -1,11 +1,21 @@ package com.kgit2.io -actual class BufferedReader { +import com.kgit2.exception.KommandException + +actual class BufferedReader( + private val reader: java.io.BufferedReader, +) { + @Throws(KommandException::class) actual fun readLine(): String? { - return "" + return reader.readLine() } + @Throws(KommandException::class) actual fun readAll(): String? { - return "" + return reader.readText() + } + + fun close() { + reader.close() } } diff --git a/src/jvmMain/kotlin/com/kgit2/io/BufferedWriter.jvm.kt b/src/jvmMain/kotlin/com/kgit2/io/BufferedWriter.jvm.kt index 93e8288..c888d40 100644 --- a/src/jvmMain/kotlin/com/kgit2/io/BufferedWriter.jvm.kt +++ b/src/jvmMain/kotlin/com/kgit2/io/BufferedWriter.jvm.kt @@ -1,9 +1,23 @@ package com.kgit2.io -actual class BufferedWriter { +import com.kgit2.exception.KommandException + +actual class BufferedWriter( + private val writer: java.io.BufferedWriter, +) { + @Throws(KommandException::class) actual fun writeLine(line: String) { + writer.write(line) + writer.newLine() } + @Throws(KommandException::class) actual fun flush() { + writer.flush() + } + + @Throws(KommandException::class) + actual fun close() { + writer.close() } } diff --git a/src/jvmMain/kotlin/com/kgit2/process/Child.jvm.kt b/src/jvmMain/kotlin/com/kgit2/process/Child.jvm.kt index d662da8..625fb88 100644 --- a/src/jvmMain/kotlin/com/kgit2/process/Child.jvm.kt +++ b/src/jvmMain/kotlin/com/kgit2/process/Child.jvm.kt @@ -1,43 +1,60 @@ package com.kgit2.process +import com.kgit2.exception.ErrorType import com.kgit2.exception.KommandException import com.kgit2.io.BufferedReader import com.kgit2.io.BufferedWriter import com.kgit2.io.Output +import java.util.concurrent.atomic.AtomicReference actual class Child( private val process: Process, ) { - var stdin: BufferedWriter? = null - var stdout: BufferedReader? = null - var stderr: BufferedReader? = null + private var stdin: AtomicReference = AtomicReference(null) + private var stdout: AtomicReference = AtomicReference(null) + private var stderr: AtomicReference = AtomicReference(null) actual fun id(): UInt { - TODO("Not yet implemented") + return process.pid().toUInt() } actual fun bufferedStdin(): BufferedWriter? { - TODO("Not yet implemented") + stdin.compareAndSet(null, BufferedWriter(process.outputWriter())) + return stdin.get() } actual fun bufferedStdout(): BufferedReader? { - TODO("Not yet implemented") + stdout.compareAndSet(null, BufferedReader(process.inputReader())) + return stdout.get() } actual fun bufferedStderr(): BufferedReader? { - TODO("Not yet implemented") + stderr.compareAndSet(null, BufferedReader(process.errorReader())) + return stderr.get() } @Throws(KommandException::class) - actual fun kill() {} + actual fun kill() { + stdin.get()?.close() + process.destroy() + } @Throws(KommandException::class) actual fun wait(): Int { - return 0 + stdin.get()?.close() + return process.waitFor() } @Throws(KommandException::class) actual fun waitWithOutput(): Output { - TODO("Not yet implemented") + stdin.get()?.close() + val stdoutContent = runCatching { bufferedStdout()?.readAll() } + .getOrElse { throw KommandException("Child has been consumed", ErrorType.None) } + val stderrContent = runCatching { bufferedStderr()?.readAll() } + .getOrElse { throw KommandException("Child has been consumed", ErrorType.None) } + val status = process.waitFor() + stdout.get()?.close() + stderr.get()?.close() + return Output(status, stdoutContent, stderrContent) } } diff --git a/src/jvmMain/kotlin/com/kgit2/process/Command.jvm.kt b/src/jvmMain/kotlin/com/kgit2/process/Command.jvm.kt index 52373bb..b3921de 100644 --- a/src/jvmMain/kotlin/com/kgit2/process/Command.jvm.kt +++ b/src/jvmMain/kotlin/com/kgit2/process/Command.jvm.kt @@ -21,12 +21,12 @@ actual class Command( } actual fun arg(arg: String): Command { - builder.command(arg) + builder.command().add(arg) return this } actual fun args(args: List): Command { - builder.command(args) + builder.command().addAll(args) return this } @@ -73,7 +73,7 @@ actual class Command( @Throws(KommandException::class) actual fun spawn(): Child { val process = builder.start() - return com.kgit2.process.Child(process) + return Child(process) } @Throws(KommandException::class) diff --git a/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt b/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt index 10458cb..f03a9c2 100644 --- a/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt +++ b/src/nativeMain/kotlin/com/kgit2/process/Child.native.kt @@ -4,19 +4,18 @@ import com.kgit2.exception.KommandException import com.kgit2.io.BufferedReader import com.kgit2.io.BufferedWriter import com.kgit2.io.Output -import com.kgit2.wrapper.dropChild -import com.kgit2.wrapper.idChild -import com.kgit2.wrapper.killChild import com.kgit2.wrapper.bufferedStderrChild import com.kgit2.wrapper.bufferedStdinChild import com.kgit2.wrapper.bufferedStdoutChild +import com.kgit2.wrapper.dropChild +import com.kgit2.wrapper.idChild +import com.kgit2.wrapper.killChild import com.kgit2.wrapper.waitChild import com.kgit2.wrapper.waitWithOutputChild +import kommand_core.id_child import kotlinx.atomicfu.atomic import kotlinx.atomicfu.locks.SynchronizedObject -import kotlinx.atomicfu.locks.synchronized import kotlinx.cinterop.COpaquePointer -import kotlinx.coroutines.internal.synchronized import kotlin.concurrent.AtomicReference import kotlin.native.ref.createCleaner @@ -38,7 +37,7 @@ actual class Child( companion object; actual fun id(): UInt { - return idChild(inner) + return id_child(inner) } actual fun bufferedStdin(): BufferedWriter? { From defec9a0152e8106c9c67eab34cb3c8565286c91 Mon Sep 17 00:00:00 2001 From: bppleman Date: Sun, 24 Dec 2023 17:56:53 +0800 Subject: [PATCH 11/22] Test as many platforms as possible --- build.gradle.kts | 99 +++++++++++++++---- justfile | 74 ++++++-------- .../kotlin/com/kgit2/process/Command.kt | 4 +- 3 files changed, 111 insertions(+), 66 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index fa666af..f6b64f3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,7 @@ import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform import org.jetbrains.dokka.gradle.DokkaTask +import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink plugins { kotlin("multiplatform") @@ -66,6 +68,12 @@ kotlin { languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi") languageSettings.optIn("kotlin.native.runtime.NativeRuntimeApi") languageSettings.optIn("kotlin.ExperimentalStdlibApi") + + languageSettings { + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } + } } val commonMain by getting { @@ -81,34 +89,51 @@ kotlin { } } -val buildKommandEcho = tasks.create("buildKommandEcho") { - group = "kommand_core" - doLast { - // ProcessBuilder("bash", "-c", "cargo build --release") - // .directory(file("eko")) - // .inheritIO() - // .start() - // .waitFor() +tasks { + val wrapper by getting(Wrapper::class) { + distributionType = Wrapper.DistributionType.ALL + gradleVersion = "8.2" } -} -tasks.forEach { - if (it.group == "verification" || it.path.contains("Test")) { - it.dependsOn(buildKommandEcho) + val buildKommandEcho by creating { + group = "kommand_core" + doLast { + buildKommandCore() + } } -} -tasks.withType(Test::class) { - testLogging { - showStandardStreams = true + forEach { + if (it.group == "verification" || it.path.contains("Test")) { + it.dependsOn(buildKommandEcho) + } } -} -tasks { - val wrapper by getting(Wrapper::class) { - distributionType = Wrapper.DistributionType.ALL - gradleVersion = "8.2" + withType(Test::class) { + testLogging { + showStandardStreams = true + } } + + // withType(KotlinNativeCompile::class) { + // compilerOptions { + // freeCompilerArgs.add("-Xexpect-actual-classes") + // } + // } + + // withType(KotlinNativeLink::class) { + // doFirst { + // println(this.name) + // val targetPlatform = when (this.name) { + // "linkDebugTestMacosX64" -> Platform.MACOS_X64 + // "linkDebugTestMacosArm64" -> Platform.MACOS_ARM64 + // "linkDebugTestLinuxX64" -> Platform.LINUX_X64 + // "linkDebugTestLinuxArm64" -> Platform.LINUX_ARM64 + // "linkDebugTestMingwX64" -> Platform.MINGW_X64 + // else -> throw GradleException("Unknown platform") + // } + // buildKommandCore(this.outputs.files.asPath, targetPlatform) + // } + // } } val ossrhUrl: String = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" @@ -238,3 +263,35 @@ val platforms: List = listOf( Platform.LINUX_ARM64, Platform.MINGW_X64, ) + +fun buildKommandCore(targetPath: String? = null, targetPlatform: Platform? = null) { + ProcessBuilder("just", "all") + .directory(file("kommand-core")) + .inheritIO() + .start() + .waitFor() + if (targetPath != null && targetPlatform != null) { + when (targetPlatform) { + Platform.MACOS_X64 -> { + file("kommand-core/target/x86_64-apple-darwin/release/kommand-echo") + .copyTo(file(targetPath).resolve("kommand-echo"), true) + } + Platform.MACOS_ARM64 -> { + file("kommand-core/target/aarch64-apple-darwin/release/kommand-echo") + .copyTo(file(targetPath).resolve("kommand-echo"), true) + } + Platform.LINUX_X64 -> { + file("kommand-core/target/x86_64-unknown-linux-gnu/release/kommand-echo") + .copyTo(file(targetPath).resolve("kommand-echo"), true) + } + Platform.LINUX_ARM64 -> { + file("kommand-core/target/aarch64-unknown-linux-gnu/release/kommand-echo") + .copyTo(file(targetPath).resolve("kommand-echo"), true) + } + Platform.MINGW_X64 -> { + file("kommand-core/target/x86_64-pc-windows-gnu/release/kommand-echo.exe") + .copyTo(file(targetPath).resolve("kommand-echo.exe"), true) + } + } + } +} diff --git a/justfile b/justfile index a7b46f8..7915406 100755 --- a/justfile +++ b/justfile @@ -1,72 +1,62 @@ #!/usr/bin/env just --justfile -macosX64: - ./gradlew linkNative -PtargetPlatform=MACOS_X64 - -macosArm64: - ./gradlew linkNative -PtargetPlatform=MACOS_ARM64 - -macos: macosX64 macosArm64 - -linuxX64: - ./gradlew linkNative -PtargetPlatform=LINUX_X64 - -linuxArm64: - ./gradlew linkNative -PtargetPlatform=LINUX_ARM64 - -linux: linuxX64 linuxArm64 - -windowsX64: - ./gradlew linkNative -PtargetPlatform=MINGW_X64 - -windows: windowsX64 - -all: macos linux windows - clean: ./gradlew clean -linuxX64Test: linuxX64 +link-test target: + ./gradlew {{target}}TestBinaries + +linuxX64Test: + just link-test linuxX64 + # ignore the error exit code -docker run -itd --name linuxX64Test \ - -v ./build/bin/linuxX64:/kommand \ - -v ./eko/target/x86_64-unknown-linux-gnu/release:/kommand/eko/target/release \ + -v ./build/bin/linuxX64:/kommand/build/bin/linuxX64 \ + -v ./kommand-core/target/x86_64-unknown-linux-gnu/release/kommand-echo:/kommand/kommand-core/target/x86_64-unknown-linux-gnu/release/kommand-echo \ -w /kommand \ -e HTTP_PROXY=host.docker.internal:6152 \ -e HTTPS_PROXY=host.docker.internal:6152 \ -e ALL_PROXY=host.docker.internal:6153 \ --platform linux/amd64 \ - -m 900m \ - --cpus=1 \ - azul/zulu-openjdk:11-latest \ + -m 1g \ + --cpus=8 \ + azul/zulu-openjdk:17-latest \ bash sleep 1 - -docker exec linuxX64Test ./debugTest/test.kexe + -docker exec linuxX64Test build/bin/linuxX64/debugTest/test.kexe docker rm -f linuxX64Test -linuxArm64Test: linuxArm64 +linuxArm64Test: + just link-test linuxArm64 + # ignore the error exit code -docker run -itd --name linuxArm64Test \ - -v ./build/bin/linuxArm64:/kommand \ - -v ./eko/target/aarch64-unknown-linux-gnu/release:/kommand/eko/target/release \ + -v ./build/bin/linuxArm64:/kommand/build/bin/linuxArm64 \ + -v ./kommand-core/target/aarch64-unknown-linux-gnu/release/kommand-echo:/kommand/kommand-core/target/aarch64-unknown-linux-gnu/release/kommand-echo \ -w /kommand \ -e HTTP_PROXY=host.docker.internal:6152 \ -e HTTPS_PROXY=host.docker.internal:6152 \ -e ALL_PROXY=host.docker.internal:6153 \ --platform linux/arm64 \ - -m 900m \ - --cpus=1 \ - azul/zulu-openjdk:11-latest \ + -m 1g \ + --cpus=8 \ + azul/zulu-openjdk:17-latest \ bash sleep 1 - -docker exec linuxArm64Test ./debugTest/test.kexe + -docker exec linuxArm64Test build/bin/linuxArm64/debugTest/test.kexe docker rm -f linuxArm64Test -macosX64Test: macosX64 - ./gradlew macosX64Test +macosX64Test: + ./gradlew :cleanMacosX64Test :macosX64Test + leaks -atExit -- build/bin/macosX64/debugTest/test.kexe -macosArm64Test: macosArm64 - ./gradlew macosArm64Test +macosArm64Test: + just link-test macosArm64 + ssh mini-lan "mkdir -p ~/test-sandbox" + scp ./build/bin/macosArm64/debugTest/test.kexe mini-lan:~/test-sandbox/test.kexe + ssh mini-lan "mkdir -p ~/test-sandbox/kommand-core/target/aarch64-apple-darwin/release" + scp ./kommand-core/target/aarch64-apple-darwin/release/kommand-echo mini-lan:~/test-sandbox/kommand-core/target/aarch64-apple-darwin/release + ssh mini-lan "cd test-sandbox; ulimit -n 10240; ./test.kexe;" -windowsX64Test: windowsX64 +windowsX64Test: ./gradlew mingwX64Test publishToSonatype: diff --git a/src/commonMain/kotlin/com/kgit2/process/Command.kt b/src/commonMain/kotlin/com/kgit2/process/Command.kt index 43a8c23..3f0bae5 100644 --- a/src/commonMain/kotlin/com/kgit2/process/Command.kt +++ b/src/commonMain/kotlin/com/kgit2/process/Command.kt @@ -3,11 +3,9 @@ package com.kgit2.process import com.kgit2.exception.KommandException import com.kgit2.io.Output -expect class Command { +expect class Command(command: String) { val command: String - constructor(command: String) - fun debugString(): String fun arg(arg: String): Command From e61058b2885c90a9252cfc9d919d660bb42b8f9b Mon Sep 17 00:00:00 2001 From: bppleman Date: Mon, 25 Dec 2023 00:38:51 +0800 Subject: [PATCH 12/22] Add github workflow --- .github/workflows/gradle.yml | 79 +++++++++++++++++++ .idea/gradle.xml | 1 + README.md | 2 + build.gradle.kts | 44 ++--------- justfile | 7 ++ kommand-core/justfile | 24 +++--- .../{macos.def => aarch64-apple-darwin.def} | 2 +- ...rm64.def => aarch64-unknown-linux-gnu.def} | 0 .../cinterop/x86_64-apple-darwin.def | 4 + ...{mingw64.def => x86_64-pc-windows-gnu.def} | 0 ...uxx64.def => x86_64-unknown-linux-gnu.def} | 0 11 files changed, 113 insertions(+), 50 deletions(-) create mode 100644 .github/workflows/gradle.yml rename src/nativeInterop/cinterop/{macos.def => aarch64-apple-darwin.def} (57%) rename src/nativeInterop/cinterop/{linuxarm64.def => aarch64-unknown-linux-gnu.def} (100%) create mode 100644 src/nativeInterop/cinterop/x86_64-apple-darwin.def rename src/nativeInterop/cinterop/{mingw64.def => x86_64-pc-windows-gnu.def} (100%) rename src/nativeInterop/cinterop/{linuxx64.def => x86_64-unknown-linux-gnu.def} (100%) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..5748749 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,79 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: Kommand Test + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + test_matrix: + strategy: + fail-fast: false + matrix: + os: [ macos-12, ubuntu-22.04, windows-latest ] + include: + - os: macos-12 + target: x86_64-apple-darwin + task: macosX64Test + - os: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + task: linuxX64Test + gcc: true + - os: windows-latest + target: x86_64-pc-windows-gnu + task: mingwX64Test + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Set up Cargo + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: 1.69.0 + target: ${{ matrix.target }} + + - name: Set up JDK 17 + uses: actions/setup-java@v4.0.0 + with: + java-version: '17' + distribution: 'zulu' + cache: 'gradle' + + - if: ${{ matrix.gcc }} + name: Set up GCC + uses: Dup4/actions-setup-gcc@v1 + + - name: Set up just + uses: extractions/setup-just@v1 + + - name: Set up Gradle + uses: gradle/gradle-build-action@v2 + with: + gradle-version: '8.2' + + - name: Test Gradle availability + run: gradle build --dry-run + + - if: ${{ !matrix.gcc }} + name: Build kommand-core + run: just ${{ matrix.target }} + working-directory: ./kommand-core + + - if: ${{ matrix.gcc }} + name: Build kommand-core with GCC + run: just workflow ${{ matrix.target }} + working-directory: ./kommand-core + + - name: Test with Gradle Wrapper + run: gradle ${{ matrix.task }} diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 39ec4f5..7d3b3e8 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,5 +1,6 @@ +