diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml new file mode 100644 index 0000000..8d46b78 --- /dev/null +++ b/.github/workflows/gradle-publish.yml @@ -0,0 +1,32 @@ +# 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 Publish + +on: + push: + tags: + - "*" + workflow_dispatch: + +permissions: + contents: read + +jobs: + publish: + runs-on: [self-hosted, macOS, ARM64, aarch64-apple-darwin] + steps: + - uses: actions/checkout@v4 + + - name: Set up just + uses: extractions/setup-just@v1 + + - name: Test Gradle availability + run: ./gradlew build --dry-run + + - name: Test with Docker + run: just autoPublish diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..7505805 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,98 @@ +# 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", "dev" ] + pull_request: + branches: [ "main" ] + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + strategy: + fail-fast: false + matrix: + os: + - macos-12 + - [self-hosted, macOS, ARM64, aarch64-apple-darwin] + - ubuntu-22.04 + - [self-hosted, macOS, ARM64, aarch64-unknown-linux-gnu] + - windows-latest + include: + - os: macos-12 + target: x86_64-apple-darwin + task: macosX64Test + - os: [self-hosted, macOS, ARM64, aarch64-apple-darwin] + target: aarch64-apple-darwin + task: macosArm64Test + - os: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + task: linuxX64Test + gcc: true + - os: [self-hosted, macOS, ARM64, aarch64-unknown-linux-gnu] + target: aarch64-unknown-linux-gnu + task: linuxArm64Test + cross: 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' + architecture: ${{ matrix.arch }} + 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 + + - if: ${{ !matrix.cross }} + name: Test with Gradle + run: gradle ${{ matrix.task }} jvmTest + + - if: ${{ matrix.cross }} + name: Test with Docker + run: just ${{ matrix.task }} diff --git a/.gitignore b/.gitignore index 46ec24b..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 @@ -216,3 +102,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/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..2b43a41 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +/artifacts +/uiDesigner.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/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..be4bb8e --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,21 @@ + + + + + + + \ 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/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/kommand.iml b/.idea/kommand.iml new file mode 100644 index 0000000..1dfe6e7 --- /dev/null +++ b/.idea/kommand.iml @@ -0,0 +1,8 @@ + + + + + + + + \ 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/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..2304d43 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e3257ea --- /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/.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/README-CN.md b/README-CN.md index de8b544..6937b46 100644 --- a/README-CN.md +++ b/README-CN.md @@ -1,32 +1,51 @@ +[![Kommand Test](https://github.com/kgit2/kommand/actions/workflows/gradle.yml/badge.svg)](https://github.com/kgit2/kommand/actions/workflows/gradle.yml) + ![logo](https://raw.githubusercontent.com/floater-git/Artist/main/kommand/logo.png) # Kommand + 一个可以将外部命令跑在子进程的库,用于Kotlin Native/JVM -# 架构示意 +# v2.0.0 + +Rust 是一门兼顾性能和工程性的优秀语言。 + +在 1.x 版本中,我们使用以下 API 来提供创建子进程的功能 -![architecture](https://raw.githubusercontent.com/floater-git/Artist/main/kommand/architecture_2.0.png) +- `fork` of [POSIX api] +- `CreateChildProcess` of [win32 api] +- `java.lang.ProcessBuilder` of JVM -# 源泉 -- 深受rust-std `Command`启发。 -- 基于ktor-io,可以使用管道处理进程间通信(IPC)。 -- kotlin多平台1.7.20,使用新的内存管理器。 +在 2.0 版本中,我们使用 Rust 标准库来提供创建子进程的功能。 -- ### Native for macOS/Linux +- `std::process::Command` of Rust +- `java.lang.ProcessBuilder` of JVM - 使用POSIX api的系统调用 +它将带来 -- ### Native for Mingw +- 更统一的 API +- 更易用的 API +- 性能依旧优秀 +- 更易于维护 +- 代码结构更清晰 - 使用Win32 api的系统调用 +# 支持的平台 -- ### JVM +- x86_64-apple-darwin +- aarch64-apple-darwin +- x86_64-unknown-linux-gnu +- aarch64-unknown-linux-gnu +- x86_64-pc-windows-gnu (mingw-w64) +- jvm - 基于 `java.lang.ProcessBuilder` +# 依赖于 -# Usage +- Rust Standard Library 1.69.0 +- Kotlin Multiplatform 1.9.21 -## Dependency +# 用法 + +## 依赖 [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.kgit2/kommand/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.kgit2/kommand) @@ -40,57 +59,103 @@ repositories { // …… dependencies { - implementation("com.kgit2:kommand:$lastVersion") + // should replace with the latest version + implementation("com.kgit2:kommand:2.x") } ``` -## Quick Start +## 快速上手 -### Inherit Standard I/O +### 继承标准 I/O -```kotlin -Command("ping") - .arg("-c") - .args("5", "localhost") - .spawn() - .wait() +https://github.com/kgit2/kommand/blob/af7c721f774550d0d5f72758f101074c12fae134/kommand-examples/example1/src/commonMain/kotlin/com/kgit2/kommand/Main.kt#L1-L12 + + +### 管道 I/O + +https://github.com/kgit2/kommand/blob/af7c721f774550d0d5f72758f101074c12fae134/kommand-examples/example2/src/commonMain/kotlin/com/kgit2/kommand/Main.kt#L1-L15 + + +### 屏蔽 I/O + +https://github.com/kgit2/kommand/blob/af7c721f774550d0d5f72758f101074c12fae134/kommand-examples/example3/src/commonMain/kotlin/com/kgit2/kommand/Main.kt#L1-L12 + + +## 自行编译 + +### 1. 依赖 + +- rust toolchain - <= 1.69.0 (https://rustup.rs) (建议) + - cross (install with `cargo install cross`) + - just (install with `cargo install just`) +- 交叉编译工具链 + - x86_64-apple-darwin + - aarch64-apple-darwin + - x86_64-unknown-linux-gnu + - aarch64-unknown-linux-gnu + - x86_64-pc-windows-gnu (mingw-w64) +- docker (可选) + +强烈推荐在 macOS 编译所有平台。 + +Kotlin Multiplatform 在 macOS 有更好的支持 + +> 如果你使用 macOS , 你可以用下述命令安装工具链 +> ```bash +> just prepare +> ``` +> 否则, 你需要自行安装工具链 + +### 2. 克隆仓库 + +```bash +git clone https://github.com/kgit2/kommand.git ``` +### 3. 编译 kommand-core -### Piped I/O +```bash +cd kommand-core +just all +``` -```kotlin -val child = Command("ping") - .args("-c", "5", "localhost") - .stdout(Stdio.Pipe) - .spawn() -val stdoutReader: com.kgit2.io.Reader? = child.getChildStdout() -val lines: Sequence = stdoutReader?.lines() -lines.forEach { - println(it) -} +### 4. 编译 kommand + +```bash +./gradlew build ``` -### Null I/O +### 5. 跨平台测试 -```kotlin -Command("gradle") - .arg("build") - .stdout(Stdio.Null) - .spawn() - .wait() +> 仅 linux 平台支持跨平台测试. + +* install docker + +[Install Docker Engine](https://docs.docker.com/engine/install/) + +* 运行测试 + +```bash +# for x86_64 +just linuxX64Test +# for aarch64 +just linuxArm64Test ``` -## 主要贡献者 +## Maintainers [@BppleMan](https://github.com/BppleMan). -[@XJMiada](https://github.com/XJMiada).(图片原创) +[@XJMiada](https://github.com/XJMiada).(Original Picture) -## 许可证 +## License [Apache2.0](LICENSE) © BppleMan -## 感谢 +## Credits - [![JetBrains Logo (Main) logo](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://jb.gg/OpenSourceSupport) + +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=kgit2/kommand&type=Date&theme=dark)](https://star-history.com/#kgit2/kommand&Date) diff --git a/README.md b/README.md index b37a442..e95272d 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,48 @@ +[![Kommand Test](https://github.com/kgit2/kommand/actions/workflows/gradle.yml/badge.svg)](https://github.com/kgit2/kommand/actions/workflows/gradle.yml) +[![Kommand Publish](https://github.com/kgit2/kommand/actions/workflows/gradle-publish.yml/badge.svg)](https://github.com/kgit2/kommand/actions/workflows/gradle-publish.yml) + ![logo](https://raw.githubusercontent.com/floater-git/Artist/main/kommand/logo.png) # Kommand -Kotlin Native library for create subprocesses and handle their I/O. - -# Supported Platforms +Kotlin Native library for create sub-process and redirect their I/O. -- macOS-X64 -- macOS-Arm64 -- linux-X64 -- linux-Arm64 -- mingw-X64 -- JVM +# v2.0.0 -# Architecture +Rust is an excellent language that takes into account both performance and engineering. -![architecture](assets/architecture_3.0.png) +In version 1.x, we use the following API to provide the function of creating child processes -# Dependent +- `fork` of [POSIX api] +- `CreateChildProcess` of [win32 api] +- `java.lang.ProcessBuilder` of JVM -- Heavily inspired by the rust-std `Command`. -- Based on the `ktor-io`, Inter-Process Communication(IPC) can be handled using pipes -- Kotlin Multiplatform 1.7.20 with new memory manager +In version 2.0, we use the Rust standard library to provide the function of creating child processes. -- ### Native for macOS/Linux +- `std::process::Command` of Rust +- `java.lang.ProcessBuilder` of JVM - System calls using POSIX api +It will bring -- ### Native for Mingw +- More unified API +- Easier to use API +- Performance is still excellent +- Easier to maintain +- Code structure is clearer - System calls using Win32 api +# Supported Platforms -- ### JVM +- x86_64-apple-darwin +- aarch64-apple-darwin +- x86_64-unknown-linux-gnu +- aarch64-unknown-linux-gnu +- x86_64-pc-windows-gnu (mingw-w64) +- jvm - Based `java.lang.ProcessBuilder` +# Dependent +- Rust Standard Library 1.69.0 +- Kotlin Multiplatform 1.9.21 # Usage @@ -52,7 +60,8 @@ repositories { // …… dependencies { - implementation("com.kgit2:kommand:$lastVersion") + // should replace with the latest version + implementation("com.kgit2:kommand:2.x") } ``` @@ -61,92 +70,77 @@ dependencies { ### Inherit Standard I/O -```kotlin -Command("ping") - .arg("-c") - .args("5", "localhost") - .spawn() - .wait() -``` +https://github.com/kgit2/kommand/blob/af7c721f774550d0d5f72758f101074c12fae134/kommand-examples/example1/src/commonMain/kotlin/com/kgit2/kommand/Main.kt#L1-L12 + ### Piped I/O -```kotlin -val child = Command("ping") - .args("-c", "5", "localhost") - .stdout(Stdio.Pipe) - .spawn() -val stdoutReader: com.kgit2.io.Reader? = child.getChildStdout() -val lines: Sequence = stdoutReader?.lines() -lines.forEach { - println(it) -} -child.wait() -``` +https://github.com/kgit2/kommand/blob/af7c721f774550d0d5f72758f101074c12fae134/kommand-examples/example2/src/commonMain/kotlin/com/kgit2/kommand/Main.kt#L1-L15 + ### Null I/O -```kotlin -Command("gradle") - .arg("build") - .stdout(Stdio.Null) - .spawn() - .wait() -``` +https://github.com/kgit2/kommand/blob/af7c721f774550d0d5f72758f101074c12fae134/kommand-examples/example3/src/commonMain/kotlin/com/kgit2/kommand/Main.kt#L1-L12 -## Build -### 1. clone this repo +## Build by yourself -```bash -git clone https://github.com/kgit2/kommand.gi -``` -Then you can build it with gradle +### 1. Dependencies -```bash -./gradlew build -``` +- rust toolchain - <= 1.69.0 (https://rustup.rs) (recommend) + - cross (install with `cargo install cross`) + - just (install with `cargo install just`) +- cross-compile toolchain + - x86_64-apple-darwin + - aarch64-apple-darwin + - x86_64-unknown-linux-gnu + - aarch64-unknown-linux-gnu + - x86_64-pc-windows-gnu (mingw-w64) +- docker (optional) + +Recommend build all platforms in macOS. -> If you want to unit test it, go on. +Kotlin Multiplatform gets the most complete support on macOS. -### 2. install dependencies +> If you are using macOS, you can install the cross-compile toolchain with +> ```bash +> just prepare +> ``` +> Otherwise, you need to install the cross-compile toolchain yourself. -* install rust toolchain +### 2. Clone this repo ```bash -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +git clone https://github.com/kgit2/kommand.git ``` +### 3. Build kommand-core -* install cross & justfile +```bash +cd kommand-core +just all +``` + +### 4. Build kommand ```bash -cargo install cross justfile +./gradlew build ``` +### 5. cross-platform test + +> Only linux support cross-platform test. + * install docker [Install Docker Engine](https://docs.docker.com/engine/install/) -* build `eko` - -```bash -cd eko -just prepare # install some toolchains -just all # for all platforms -# also can build for specific platform -just macosX64 -just linuxX64 -#or else -just --list -``` * test it ```bash +# for x86_64 just linuxX64Test -# or -just macosX64Test -# or else -just --list +# for aarch64 +just linuxArm64Test ``` ## Maintainers diff --git a/build.gradle.kts b/build.gradle.kts index 3d02587..da33507 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,28 +1,31 @@ +import org.apache.tools.ant.taskdefs.AbstractJarSignerTask import org.jetbrains.dokka.gradle.DokkaTask plugins { kotlin("multiplatform") id("org.jetbrains.dokka") - id("io.github.gradle-nexus.publish-plugin") `maven-publish` - application signing + id("io.github.gradle-nexus.publish-plugin") } group = "com.kgit2" -version = "1.2.0" - -val ktorIO = "2.3.4" +version = "2.0.0" repositories { mavenCentral() gradlePluginPortal() } +subprojects { + group = "com.kgit2" + version = "1.2.0" +} + kotlin { jvm { compilations.all { - kotlinOptions.jvmTarget = "11" + kotlinOptions.jvmTarget = "17" } withJava() testRuns["test"].executionTask.configure { @@ -31,100 +34,67 @@ kotlin { } val nativeTargets = listOf( - macosArm64(), - macosX64(), - linuxX64(), - linuxArm64(), - mingwX64(), + 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 { + create("kommandCore") { + defFile(project.file("src/nativeInterop/cinterop/${targetPlatform.archName}.def")) + packageName("kommand_core") + } + } + } + } + } + + applyDefaultHierarchyTemplate() + 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 { - dependencies { - implementation("io.ktor:ktor-io:2.3.4") + languageSettings { + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } } } - val commonTest by getting { + + commonMain { dependencies { - implementation(kotlin("test")) + implementation("org.jetbrains.kotlinx:atomicfu:0.23.1") } } - - val jvmMain by getting - val jvmTest by getting - - val posixMain by creating { - dependsOn(commonMain) - } - val posixTest by creating { - dependsOn(commonTest) + 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") + implementation(kotlin("test")) } } - - 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 macosX64Test by getting { - dependsOn(unixLikeTest) - } - 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 } } -val subCommandInstallDist = tasks.findByPath(":sub_command:installDist") - -val buildEko = tasks.create("buildEko") { - group = "build" - doLast { - ProcessBuilder("bash", "-c", "cargo build --release") - .directory(file("eko")) - .inheritIO() - .start() - .waitFor() +tasks { + withType(Wrapper::class) { + distributionType = Wrapper.DistributionType.ALL + gradleVersion = "8.2" } -} -tasks.forEach { - if (it.group == "verification" || it.path.contains("Test")) { - it.dependsOn(buildEko) + withType(Test::class) { + testLogging { + showStandardStreams = true + } } } @@ -142,26 +112,6 @@ 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" - - tasks.getByName("dokkaHtml") { - outputDirectory.set(file(dokkaOutputDir)) - } - - val deleteDokkaOutputDir by tasks.register("deleteDokkaOutputDirectory") { - delete(dokkaOutputDir) - } - - val javadocJar = tasks.register("javadocJar") { - dependsOn(deleteDokkaOutputDir, tasks.dokkaHtml) - archiveClassifier.set("javadoc") - from(dokkaOutputDir) - } - nexusPublishing { repositories { sonatype { @@ -186,7 +136,18 @@ if (ossrhUsername != null && ossrhPassword != null) { } publications { withType { - artifact(javadocJar.get()) + val dokkaJar = project.tasks.register("${name}DokkaJar", Jar::class) { + group = JavaBasePlugin.DOCUMENTATION_GROUP + description = "Assembles Kotlin docs with Dokka into a Javadoc jar" + archiveClassifier.set("javadoc") + from(tasks.dokkaHtml) + + // Each archive name should be distinct, to avoid implicit dependency issues. + // We use the same format as the sources Jar tasks. + // https://youtrack.jetbrains.com/issue/KT-46466 + archiveBaseName.set("${archiveBaseName.get()}-${name}") + } + artifact(dokkaJar) pom { name.set("kommand") description.set("A simple process library for Kotlin Multiplatform") @@ -194,7 +155,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 { @@ -214,6 +175,29 @@ if (ossrhUsername != null && ossrhPassword != null) { } signing { + // will default find the + // - signing.keyId + // - signing.password + // - signing.secretKeyRingFile 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"), + ; +} + +val platforms: List = listOf( + Platform.MACOS_X64, + Platform.MACOS_ARM64, + Platform.LINUX_X64, + Platform.LINUX_ARM64, + Platform.MINGW_X64, +) 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/Cargo.toml b/eko/Cargo.toml deleted file mode 100644 index ddc1591..0000000 --- a/eko/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "eko" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] 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 1da62bf..7e59c15 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,5 @@ kotlin.code.style=official -kotlin.mpp.enableGranularSourceSetsMetadata=true -kotlin.native.enableDependencyPropagation=false -kotlin.js.generate.executable.default=false org.gradle.jvmargs=-Xmx4096m +#org.gradle.caching=true +kotlin.mpp.enableCInteropCommonization=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927..d64cd49 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fc..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-7.4.2-bin.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 23a510a..548922d 100755 --- a/justfile +++ b/justfile @@ -1,68 +1,68 @@ #!/usr/bin/env just --justfile -macosX64: - ./gradlew macosX64TestBinaries - -macosArm64: - ./gradlew macosArm64TestBinaries - -linuxX64: - ./gradlew linuxX64TestBinaries - -linuxArm64: - ./gradlew linuxArm64TestBinaries - -windowsX64: - ./gradlew mingwX64TestBinaries - -all: macosX64 macosArm64 linuxX64 linuxArm64 windowsX64 +prepare: + brew install mingw-w64 + brew tap messense/macos-cross-toolchains + brew install x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu 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 \ + -m 256m \ --cpus=1 \ - azul/zulu-openjdk:11-latest \ + ubuntu \ 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 \ + -m 256m \ --cpus=1 \ - azul/zulu-openjdk:11-latest \ + ubuntu \ 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: + ./gradlew :cleanMacosArm64Test :macosArm64Test + leaks -atExit -- build/bin/macosArm64/debugTest/test.kexe -windowsX64Test: windowsX64 +windowsX64Test: ./gradlew mingwX64Test +build: + cd kommand-core && just all + publishToSonatype: ./gradlew publishToSonatype @@ -72,4 +72,15 @@ closeSonatype: releaseSonatype: ./gradlew findSonatypeStagingRepository releaseSonatypeStagingRepository -autoPublish: publishToSonatype closeSonatype releaseSonatype +autoPublish: build publishToSonatype closeSonatype releaseSonatype + +leaks: + ./gradlew :cleanMacosX64Test :macosX64Test + leaks -atExit -- build/bin/macosX64/debugTest/test.kexe + +teamcity: + #-v :/opt/teamcity/logs + docker run --name teamcity-server-instance \ + -v ./:/data/teamcity_server/kommand \ + -p 8111:8111 \ + jetbrains/teamcity-server 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/.gitignore b/kommand-core/.idea/.gitignore similarity index 100% rename from src/mingwX64Test/resources/sub_command/.idea/.gitignore rename to kommand-core/.idea/.gitignore 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/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/Cargo.lock b/kommand-core/Cargo.lock new file mode 100644 index 0000000..df10d34 --- /dev/null +++ b/kommand-core/Cargo.lock @@ -0,0 +1,1024 @@ +# This file is automatically @generated by Cargo. +# 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 = "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 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +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" +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 = "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" +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 = "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 = "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" +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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "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" +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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "kommand-core" +version = "0.1.0" +dependencies = [ + "cbindgen", +] + +[[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", + "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" +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 = "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" +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 = "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" +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 = "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" +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 = "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" +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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +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 = "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" +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" +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.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" +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.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" +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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[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..38522b1 --- /dev/null +++ b/kommand-core/Cargo.toml @@ -0,0 +1,30 @@ +[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] + +[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..de1bcb1 --- /dev/null +++ b/kommand-core/justfile @@ -0,0 +1,51 @@ +#!/usr/bin/env just --justfile + +prepare: + brew install mingw-w64 + brew tap messense/macos-cross-toolchains + brew install x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu + +leaks: + cargo build + leaks -atExit -- target/debug/leaks_test + +bench: + cargo build + time target/debug/leaks_test + +x86_64-apple-darwin: + cargo build --release --package kommand-core --package kommand-echo --target x86_64-apple-darwin + +aarch64-apple-darwin: + cargo build --release --package kommand-core --package kommand-echo --target aarch64-apple-darwin + +macos: x86_64-apple-darwin aarch64-apple-darwin + +x86_64-unknown-linux-gnu: + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-unknown-linux-gnu-gcc \ + cargo build --release --package kommand-core --package kommand-echo --target x86_64-unknown-linux-gnu + +aarch64-unknown-linux-gnu: + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-unknown-linux-gnu-gcc \ + cargo build --release --package kommand-core --package kommand-echo --target aarch64-unknown-linux-gnu + +linux: x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu + +x86_64-pc-windows-gnu: + cargo build --release --package kommand-core --package kommand-echo --target x86_64-pc-windows-gnu + +win: x86_64-pc-windows-gnu + +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 + +workflow dir: + cargo build --release --package kommand-core --package kommand-echo + cd target && mkdir {{dir}} && mv ./release ./{{dir}}/release/ diff --git a/kommand-core/kommand-echo/Cargo.toml b/kommand-core/kommand-echo/Cargo.toml new file mode 100644 index 0000000..41a284b --- /dev/null +++ b/kommand-core/kommand-echo/Cargo.toml @@ -0,0 +1,12 @@ +[package] +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 + +[dependencies] +colored = "2.0.4" +console = "0.15.7" +ansi_term = "0.12.1" diff --git a/eko/src/main.rs b/kommand-core/kommand-echo/src/main.rs similarity index 64% rename from eko/src/main.rs rename to kommand-core/kommand-echo/src/main.rs index 223d6c1..f1f4036 100644 --- a/eko/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,13 +6,15 @@ 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]; match cmd { + 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))) @@ -23,12 +26,19 @@ 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 { Ok(line) => println!("{}", line), Err(_) => { - eprintln!("Error reading stdin"); + eprint!("Error reading stdin"); break; } } @@ -38,7 +48,7 @@ fn stdout() { fn stderr() { for line in stdin().lines() { match line { - Ok(line) => eprintln!("{}", line), + Ok(line) => eprint!("{}", line), Err(_) => break, } } @@ -51,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 new file mode 100644 index 0000000..3711cb1 --- /dev/null +++ b/kommand-core/kommand-watch/Cargo.toml @@ -0,0 +1,18 @@ +[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" +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 new file mode 100644 index 0000000..26987ba --- /dev/null +++ b/kommand-core/kommand-watch/src/main.rs @@ -0,0 +1,178 @@ +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 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/kommand/wrapper"); + #[rustfmt::skip] + static ref CLUSTER_DIRS: HashMap = HashMap::from([ + ("macosArm64".to_string(), ROOT_DIR.join("src/macosArm64Main/kotlin/com/kgit2/kommand/wrapper")), + ("linuxX64".to_string(), ROOT_DIR.join("src/linuxX64Main/kotlin/com/kgit2/kommand/wrapper")), + ("linuxArm64".to_string(), ROOT_DIR.join("src/linuxArm64Main/kotlin/com/kgit2/kommand/wrapper")), + ("mingwX64".to_string(), ROOT_DIR.join("src/mingwX64Main/kotlin/com/kgit2/kommand/wrapper")), + ]); +} + +#[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)?; + format!("Watching {}", MAIN_DIR.display()) + .blue() + .bold() + .print(); + 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) => { + format!("{}", error).red().eprint(); + } + } +} + +fn sync_file(path: impl AsRef) { + 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() + .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)); + format!(" Will write to {:?}", new_path).yellow().print(); + 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) { + format!("Dir changed {:?}", path.as_ref()).green().print(); + 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(MAIN_DIR.join(path))); + #[rustfmt::skip] + main_set.iter().for_each(|path| sync_file(MAIN_DIR.join(path))); + }); +} + +fn sync_remove(path: impl AsRef) { + 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 { + let walk_dir = WalkDir::new(path.as_ref()).min_depth(1); + walk_dir + .into_iter() + .flatten() + .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 new file mode 100644 index 0000000..125d1bf --- /dev/null +++ b/kommand-core/kommand_core.h @@ -0,0 +1,239 @@ +typedef enum ErrorType { + None, + Io, + Utf8, + Unknown, +} ErrorType; + +typedef enum Stdio { + Inherit, + Null, + 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; + enum ErrorType error_type; +} UnitResult; + +typedef struct IntResult { + int ok; + char *err; + enum ErrorType error_type; +} IntResult; + +typedef struct Output { + int exit_code; + char *stdout_content; + char *stderr_content; +} Output; + +/** + * # Safety + */ +char *env_var(const char *name); + +struct EnvVars env_vars(void); + +void drop_env_vars(struct EnvVars env_vars); + +/** + * # Safety + * Will drop the string + */ +void drop_string(char *string); + +char *void_to_string(void *ptr); + +/** + * # Safety + */ +struct VoidResult read_line_stdout(const void *reader, unsigned long long *size); + +/** + * # Safety + */ +struct VoidResult read_all_stdout(const void *reader, unsigned long long *size); + +/** + * # Safety + */ +struct VoidResult read_line_stderr(const void *reader, unsigned long long *size); + +/** + * # Safety + */ +struct VoidResult read_all_stderr(const void *reader, unsigned long long *size); + +/** + * # Safety + */ +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); + +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 + * You must drop the command with [drop_command] + * + * ```rust + * use kommand_core::ffi_util::as_cstring; + * use kommand_core::process::{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::process::{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::process::{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::process::{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::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()); + * 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::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()); + * 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..645a19f --- /dev/null +++ b/kommand-core/src/bin/leaks_test.rs @@ -0,0 +1,48 @@ +use std::ffi::{c_char, c_ulonglong}; + +use kommand_core::ffi_util::{as_cstring, into_string}; +use kommand_core::io::{drop_stdout, read_line_stdout}; +use kommand_core::process::{buffered_stdout_child, Stdio}; +use kommand_core::process::{drop_child, wait_child}; +use kommand_core::process::{drop_command, new_command, spawn_command, stdout_command}; + +fn main() { + unsafe { + (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()); + 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 stdout = buffered_stdout_child(child); + if stdout.is_null() { + panic!("stdout is null"); + } + loop { + let mut size = 0u64; + let result = read_line_stdout(stdout, (&mut size) as *mut c_ulonglong); + println!("size: {}", size); + 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; + } + } + + wait_child(child); + drop_stdout(stdout); + drop_child(child); + drop_command(command); + }); + } +} diff --git a/kommand-core/src/bin/normal_test.rs b/kommand-core/src/bin/normal_test.rs new file mode 100644 index 0000000..bb68c8d --- /dev/null +++ b/kommand-core/src/bin/normal_test.rs @@ -0,0 +1,14 @@ +use std::io::{stdout, Write}; +use std::process::{Command, Stdio}; + +fn main() { + 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/ffi_util.rs b/kommand-core/src/ffi_util.rs new file mode 100644 index 0000000..23b5517 --- /dev/null +++ b/kommand-core/src/ffi_util.rs @@ -0,0 +1,50 @@ +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 +/// 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 new file mode 100644 index 0000000..522be3b --- /dev/null +++ b/kommand-core/src/io.rs @@ -0,0 +1,103 @@ +use crate::ffi_util::as_string; +use std::ffi::{c_char, c_ulonglong, 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; + +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn read_line_stdout( + mut reader: *const c_void, + size: *mut c_ulonglong, +) -> VoidResult { + let reader = as_stdout_mut(&mut reader); + let mut line = String::new(); + reader + .read_line(&mut line) + .map(|len| { + unsafe { *size = len as c_ulonglong }; + line.trim_end_matches('\n').to_string() + }) + .into() +} + +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn read_all_stdout( + mut reader: *const c_void, + size: *mut c_ulonglong, +) -> VoidResult { + let reader = as_stdout_mut(&mut reader); + let mut line = String::new(); + reader + .read_to_string(&mut line) + .map(|len| { + unsafe { *size = len as c_ulonglong }; + line + }) + .into() +} + +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn read_line_stderr( + mut reader: *const c_void, + size: *mut c_ulonglong, +) -> VoidResult { + let reader = stderr::as_stderr_mut(&mut reader); + let mut line = String::new(); + reader + .read_line(&mut line) + .map(|len| { + unsafe { *size = len as c_ulonglong }; + line.trim_end_matches('\n').to_string() + }) + .into() +} + +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn read_all_stderr( + mut reader: *const c_void, + size: *mut c_ulonglong, +) -> VoidResult { + let reader = stderr::as_stderr_mut(&mut reader); + let mut line = String::new(); + reader + .read_to_string(&mut line) + .map(|len| { + unsafe { *size = len as c_ulonglong }; + 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()) + .and_then(|_| writer.write(b"\n")) + .map(|_| ()); + 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 new file mode 100644 index 0000000..6c77279 --- /dev/null +++ b/kommand-core/src/io/stderr.rs @@ -0,0 +1,19 @@ +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) { + if reader.is_null() { + return; + } + 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..13b5288 --- /dev/null +++ b/kommand-core/src/io/stdin.rs @@ -0,0 +1,19 @@ +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) { + if reader.is_null() { + return; + } + 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..100b7d1 --- /dev/null +++ b/kommand-core/src/io/stdout.rs @@ -0,0 +1,19 @@ +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) { + if reader.is_null() { + return; + } + into_stdout(reader); +} diff --git a/kommand-core/src/lib.rs b/kommand-core/src/lib.rs new file mode 100644 index 0000000..6cb3373 --- /dev/null +++ b/kommand-core/src/lib.rs @@ -0,0 +1,9 @@ +//! This is a [std::process::Command] wrapper for ffi +//! +//! Export the ffi interface through [cbindgen] and use it in kotlin/native + +pub mod env; +pub mod ffi_util; +pub mod io; +pub mod process; +pub mod result; 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/process/child.rs b/kommand-core/src/process/child.rs new file mode 100644 index 0000000..a7939f3 --- /dev/null +++ b/kommand-core/src/process/child.rs @@ -0,0 +1,89 @@ +use crate::ffi_util::into_void; +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; + +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 buffered_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 buffered_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 buffered_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() +} + +/// 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() +} + +#[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/process/kommand.rs b/kommand-core/src/process/kommand.rs new file mode 100644 index 0000000..6da2c44 --- /dev/null +++ b/kommand-core/src/process/kommand.rs @@ -0,0 +1,211 @@ +use std::ffi::{c_char, c_void}; +use std::process::Command; + +use crate::ffi_util::{as_string, into_cstring, into_void}; +use crate::process::Output; +use crate::process::Stdio; +use crate::result::{IntResult, VoidResult}; + +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::process::{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::process::{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::process::{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::process::{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) { + if command.is_null() { + return; + } + into_command(command); +} + +/// # Safety +/// Will not take over ownership of arg +/// +/// ```rust +/// use kommand_core::ffi_util::as_cstring; +/// 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()); +/// 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::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()); +/// 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/process/output.rs b/kommand-core/src/process/output.rs new file mode 100644 index 0000000..730ba4f --- /dev/null +++ b/kommand-core/src/process/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/process/stdio.rs b/kommand-core/src/process/stdio.rs new file mode 100644 index 0000000..fe3c6de --- /dev/null +++ b/kommand-core/src/process/stdio.rs @@ -0,0 +1,7 @@ +#[repr(C)] +#[derive(Debug)] +pub enum Stdio { + Inherit, + Null, + Pipe, +} diff --git a/kommand-core/src/result.rs b/kommand-core/src/result.rs new file mode 100644 index 0000000..7744a62 --- /dev/null +++ b/kommand-core/src/result.rs @@ -0,0 +1,137 @@ +use crate::ffi_util::{into_cstring, into_void}; +use std::ffi::{c_char, c_int, c_void}; +use std::fmt::Display; +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 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 { + 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: 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-examples/build.gradle.kts b/kommand-examples/build.gradle.kts new file mode 100644 index 0000000..e69de29 diff --git a/kommand-examples/example1/build.gradle.kts b/kommand-examples/example1/build.gradle.kts new file mode 100644 index 0000000..cebe189 --- /dev/null +++ b/kommand-examples/example1/build.gradle.kts @@ -0,0 +1,77 @@ +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform + +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.") +} + +plugins { + kotlin("multiplatform") +} + +repositories { + mavenCentral() + gradlePluginPortal() +} + +kotlin { + jvm { + compilations.all { + kotlinOptions.jvmTarget = "17" + } + withJava() + testRuns["test"].executionTask.configure { + useJUnitPlatform() + } + } + + val nativeTarget = when (currentPlatform) { + Platform.MACOS_X64 -> macosX64() + Platform.MACOS_ARM64 -> macosArm64() + Platform.LINUX_X64 -> linuxX64() + Platform.LINUX_ARM64 -> linuxArm64() + Platform.MINGW_X64 -> mingwX64() + } + + nativeTarget.apply { + binaries { + executable { + entryPoint = "com.kgit2.kommand.main" + } + } + } + + applyDefaultHierarchyTemplate() + + sourceSets { + // add opt-in + all { + languageSettings.optIn("kotlinx.cinterop.UnsafeNumber") + languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi") + languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi") + languageSettings.optIn("kotlin.native.runtime.NativeRuntimeApi") + languageSettings.optIn("kotlin.ExperimentalStdlibApi") + } + + commonMain { + dependencies { + implementation(rootProject) + } + } + } +} + +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"), + ; +} diff --git a/kommand-examples/example1/src/commonMain/kotlin/com/kgit2/kommand/Main.kt b/kommand-examples/example1/src/commonMain/kotlin/com/kgit2/kommand/Main.kt new file mode 100644 index 0000000..facc180 --- /dev/null +++ b/kommand-examples/example1/src/commonMain/kotlin/com/kgit2/kommand/Main.kt @@ -0,0 +1,12 @@ +package com.kgit2.kommand + +import com.kgit2.kommand.process.Command +import com.kgit2.kommand.process.Stdio + +fun main() { + Command("ping") + .args(listOf("-c", "5", "localhost")) + .stdout(Stdio.Inherit) + .spawn() + .wait() +} diff --git a/kommand-examples/example2/build.gradle.kts b/kommand-examples/example2/build.gradle.kts new file mode 100644 index 0000000..cebe189 --- /dev/null +++ b/kommand-examples/example2/build.gradle.kts @@ -0,0 +1,77 @@ +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform + +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.") +} + +plugins { + kotlin("multiplatform") +} + +repositories { + mavenCentral() + gradlePluginPortal() +} + +kotlin { + jvm { + compilations.all { + kotlinOptions.jvmTarget = "17" + } + withJava() + testRuns["test"].executionTask.configure { + useJUnitPlatform() + } + } + + val nativeTarget = when (currentPlatform) { + Platform.MACOS_X64 -> macosX64() + Platform.MACOS_ARM64 -> macosArm64() + Platform.LINUX_X64 -> linuxX64() + Platform.LINUX_ARM64 -> linuxArm64() + Platform.MINGW_X64 -> mingwX64() + } + + nativeTarget.apply { + binaries { + executable { + entryPoint = "com.kgit2.kommand.main" + } + } + } + + applyDefaultHierarchyTemplate() + + sourceSets { + // add opt-in + all { + languageSettings.optIn("kotlinx.cinterop.UnsafeNumber") + languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi") + languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi") + languageSettings.optIn("kotlin.native.runtime.NativeRuntimeApi") + languageSettings.optIn("kotlin.ExperimentalStdlibApi") + } + + commonMain { + dependencies { + implementation(rootProject) + } + } + } +} + +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"), + ; +} diff --git a/kommand-examples/example2/src/commonMain/kotlin/com/kgit2/kommand/Main.kt b/kommand-examples/example2/src/commonMain/kotlin/com/kgit2/kommand/Main.kt new file mode 100644 index 0000000..95f911c --- /dev/null +++ b/kommand-examples/example2/src/commonMain/kotlin/com/kgit2/kommand/Main.kt @@ -0,0 +1,15 @@ +package com.kgit2.kommand + +import com.kgit2.kommand.process.Command +import com.kgit2.kommand.process.Stdio + +fun main() { + val child = Command("ping") + .args(listOf("-c", "5", "localhost")) + .stdout(Stdio.Pipe) + .spawn() + child.bufferedStdout()?.lines()?.forEach { line -> + println(line) + } + child.wait() +} diff --git a/kommand-examples/example3/build.gradle.kts b/kommand-examples/example3/build.gradle.kts new file mode 100644 index 0000000..cebe189 --- /dev/null +++ b/kommand-examples/example3/build.gradle.kts @@ -0,0 +1,77 @@ +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform + +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.") +} + +plugins { + kotlin("multiplatform") +} + +repositories { + mavenCentral() + gradlePluginPortal() +} + +kotlin { + jvm { + compilations.all { + kotlinOptions.jvmTarget = "17" + } + withJava() + testRuns["test"].executionTask.configure { + useJUnitPlatform() + } + } + + val nativeTarget = when (currentPlatform) { + Platform.MACOS_X64 -> macosX64() + Platform.MACOS_ARM64 -> macosArm64() + Platform.LINUX_X64 -> linuxX64() + Platform.LINUX_ARM64 -> linuxArm64() + Platform.MINGW_X64 -> mingwX64() + } + + nativeTarget.apply { + binaries { + executable { + entryPoint = "com.kgit2.kommand.main" + } + } + } + + applyDefaultHierarchyTemplate() + + sourceSets { + // add opt-in + all { + languageSettings.optIn("kotlinx.cinterop.UnsafeNumber") + languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi") + languageSettings.optIn("kotlin.experimental.ExperimentalNativeApi") + languageSettings.optIn("kotlin.native.runtime.NativeRuntimeApi") + languageSettings.optIn("kotlin.ExperimentalStdlibApi") + } + + commonMain { + dependencies { + implementation(rootProject) + } + } + } +} + +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"), + ; +} diff --git a/kommand-examples/example3/src/commonMain/kotlin/com/kgit2/kommand/Main.kt b/kommand-examples/example3/src/commonMain/kotlin/com/kgit2/kommand/Main.kt new file mode 100644 index 0000000..207e606 --- /dev/null +++ b/kommand-examples/example3/src/commonMain/kotlin/com/kgit2/kommand/Main.kt @@ -0,0 +1,12 @@ +package com.kgit2.kommand + +import com.kgit2.kommand.process.Command +import com.kgit2.kommand.process.Stdio + +fun main() { + Command("echo") + .arg("nothing") + .stdout(Stdio.Null) + .spawn() + .wait() +} 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..d5c3a30 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 } @@ -12,6 +12,16 @@ pluginManagement { } } +buildCache { + local { + directory = File(rootDir, "build-cache") + removeUnusedEntriesAfterDays = 30 + } +} + rootProject.name = "kommand" -include(":sub_command") +include(":kommand-examples") +include(":kommand-examples:example1") +include(":kommand-examples:example2") +include(":kommand-examples:example3") 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/kommand/Platform.kt b/src/commonMain/kotlin/com/kgit2/kommand/Platform.kt new file mode 100644 index 0000000..beb41a7 --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/kommand/Platform.kt @@ -0,0 +1,11 @@ +package com.kgit2.kommand + +enum class Platform { + MACOS_X64, + MACOS_ARM64, + LINUX_X64, + LINUX_ARM64, + MINGW_X64, +} + +expect val platform: Platform diff --git a/src/commonMain/kotlin/com/kgit2/kommand/env/EnvVars.kt b/src/commonMain/kotlin/com/kgit2/kommand/env/EnvVars.kt new file mode 100644 index 0000000..8e2fd29 --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/kommand/env/EnvVars.kt @@ -0,0 +1,5 @@ +package com.kgit2.kommand.env + +expect fun envVar(key: String): String? + +expect fun envVars(): Map? diff --git a/src/commonMain/kotlin/com/kgit2/kommand/exception/KommandException.kt b/src/commonMain/kotlin/com/kgit2/kommand/exception/KommandException.kt new file mode 100644 index 0000000..8d62e62 --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/kommand/exception/KommandException.kt @@ -0,0 +1,15 @@ +package com.kgit2.kommand.exception + +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/kommand/io/BufferedReader.kt b/src/commonMain/kotlin/com/kgit2/kommand/io/BufferedReader.kt new file mode 100644 index 0000000..be396d0 --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/kommand/io/BufferedReader.kt @@ -0,0 +1,14 @@ +package com.kgit2.kommand.io + +import com.kgit2.kommand.exception.KommandException + +expect class BufferedReader { + @Throws(KommandException::class) + fun readLine(): String? + + @Throws(KommandException::class) + fun readAll(): String? + + @Throws(KommandException::class) + fun lines(): Sequence +} diff --git a/src/commonMain/kotlin/com/kgit2/kommand/io/BufferedWriter.kt b/src/commonMain/kotlin/com/kgit2/kommand/io/BufferedWriter.kt new file mode 100644 index 0000000..ea68ac9 --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/kommand/io/BufferedWriter.kt @@ -0,0 +1,14 @@ +package com.kgit2.kommand.io + +import com.kgit2.kommand.exception.KommandException + +expect class BufferedWriter { + @Throws(KommandException::class) + fun writeLine(line: String) + + @Throws(KommandException::class) + fun flush() + + @Throws(KommandException::class) + fun close() +} diff --git a/src/commonMain/kotlin/com/kgit2/kommand/io/Output.kt b/src/commonMain/kotlin/com/kgit2/kommand/io/Output.kt new file mode 100644 index 0000000..a409ca7 --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/kommand/io/Output.kt @@ -0,0 +1,9 @@ +package com.kgit2.kommand.io + +data class Output( + val status: Int?, + val stdout: String?, + val stderr: String?, +) { + companion object; +} diff --git a/src/commonMain/kotlin/com/kgit2/kommand/process/Child.kt b/src/commonMain/kotlin/com/kgit2/kommand/process/Child.kt new file mode 100644 index 0000000..2496bba --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/kommand/process/Child.kt @@ -0,0 +1,25 @@ +package com.kgit2.kommand.process + +import com.kgit2.kommand.exception.KommandException +import com.kgit2.kommand.io.BufferedReader +import com.kgit2.kommand.io.BufferedWriter +import com.kgit2.kommand.io.Output + +expect class Child { + fun id(): UInt + + fun bufferedStdin(): BufferedWriter? + + fun bufferedStdout(): BufferedReader? + + fun bufferedStderr(): BufferedReader? + + @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/kommand/process/Command.kt b/src/commonMain/kotlin/com/kgit2/kommand/process/Command.kt new file mode 100644 index 0000000..9b7b09e --- /dev/null +++ b/src/commonMain/kotlin/com/kgit2/kommand/process/Command.kt @@ -0,0 +1,47 @@ +package com.kgit2.kommand.process + +import com.kgit2.kommand.exception.KommandException +import com.kgit2.kommand.io.Output + +expect class Command(command: String) { + val 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 + + @Throws(KommandException::class) + fun status(): Int +} + +enum class Stdio { + Inherit, + Pipe, + Null, + ; + companion object; +} 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/kommand/ChildTest.kt b/src/commonTest/kotlin/com/kgit2/kommand/ChildTest.kt new file mode 100644 index 0000000..a0dd607 --- /dev/null +++ b/src/commonTest/kotlin/com/kgit2/kommand/ChildTest.kt @@ -0,0 +1,77 @@ +package com.kgit2.kommand + +import com.kgit2.kommand.process.Command +import com.kgit2.kommand.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.toString(), 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!" + 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!" + 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/kommand/CommandTest.kt b/src/commonTest/kotlin/com/kgit2/kommand/CommandTest.kt new file mode 100644 index 0000000..04e6f9e --- /dev/null +++ b/src/commonTest/kotlin/com/kgit2/kommand/CommandTest.kt @@ -0,0 +1,60 @@ +package com.kgit2.kommand + +import com.kgit2.kommand.process.Command +import com.kgit2.kommand.process.Stdio +import kotlin.test.Test +import kotlin.test.assertEquals + +class CommandTest { + @Test + fun autoFree() { + 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/kommand/EnvTest.kt b/src/commonTest/kotlin/com/kgit2/kommand/EnvTest.kt new file mode 100644 index 0000000..cf984a8 --- /dev/null +++ b/src/commonTest/kotlin/com/kgit2/kommand/EnvTest.kt @@ -0,0 +1,15 @@ +package com.kgit2.kommand + +import kotlin.test.Test + +class EnvTest { + @Test + fun envVar() { + com.kgit2.kommand.env.envVar("HOME") + } + + @Test + fun envVars() { + com.kgit2.kommand.env.envVars() + } +} diff --git a/src/commonTest/kotlin/com/kgit2/kommand/PlatformTest.kt b/src/commonTest/kotlin/com/kgit2/kommand/PlatformTest.kt new file mode 100644 index 0000000..bdb8621 --- /dev/null +++ b/src/commonTest/kotlin/com/kgit2/kommand/PlatformTest.kt @@ -0,0 +1,42 @@ +package com.kgit2.kommand + +import com.kgit2.kommand.env.envVar +import com.kgit2.kommand.process.Command +import kotlin.test.Test + +class PlatformTest { + @Test + fun 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 -> envVar("HOME")!! + Platform.MACOS_ARM64 -> envVar("HOME")!! + Platform.LINUX_X64 -> envVar("HOME")!! + Platform.LINUX_ARM64 -> envVar("HOME")!! + Platform.MINGW_X64 -> envVar("userprofile")!! + } +} 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/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/kommand/Platform.jvm.kt b/src/jvmMain/kotlin/com/kgit2/kommand/Platform.jvm.kt new file mode 100644 index 0000000..f84c5f3 --- /dev/null +++ b/src/jvmMain/kotlin/com/kgit2/kommand/Platform.jvm.kt @@ -0,0 +1,23 @@ +package com.kgit2.kommand + +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/kommand/env/EnvVars.jvm.kt b/src/jvmMain/kotlin/com/kgit2/kommand/env/EnvVars.jvm.kt new file mode 100644 index 0000000..7ca8403 --- /dev/null +++ b/src/jvmMain/kotlin/com/kgit2/kommand/env/EnvVars.jvm.kt @@ -0,0 +1,9 @@ +package com.kgit2.kommand.env + +actual fun envVar(key: String): String? { + return System.getenv(key) +} + +actual fun envVars(): Map? { + return System.getenv() +} diff --git a/src/jvmMain/kotlin/com/kgit2/kommand/io/BufferedReader.jvm.kt b/src/jvmMain/kotlin/com/kgit2/kommand/io/BufferedReader.jvm.kt new file mode 100644 index 0000000..7fde319 --- /dev/null +++ b/src/jvmMain/kotlin/com/kgit2/kommand/io/BufferedReader.jvm.kt @@ -0,0 +1,26 @@ +package com.kgit2.kommand.io + +import com.kgit2.kommand.exception.KommandException + +actual class BufferedReader( + private val reader: java.io.BufferedReader, +) { + @Throws(KommandException::class) + actual fun readLine(): String? { + return reader.readLine() + } + + @Throws(KommandException::class) + actual fun readAll(): String? { + return reader.readText() + } + + @Throws(KommandException::class) + actual fun lines(): Sequence { + return reader.lineSequence() + } + + fun close() { + reader.close() + } +} diff --git a/src/jvmMain/kotlin/com/kgit2/kommand/io/BufferedWriter.jvm.kt b/src/jvmMain/kotlin/com/kgit2/kommand/io/BufferedWriter.jvm.kt new file mode 100644 index 0000000..a60cdb1 --- /dev/null +++ b/src/jvmMain/kotlin/com/kgit2/kommand/io/BufferedWriter.jvm.kt @@ -0,0 +1,23 @@ +package com.kgit2.kommand.io + +import com.kgit2.kommand.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/kommand/process/Child.jvm.kt b/src/jvmMain/kotlin/com/kgit2/kommand/process/Child.jvm.kt new file mode 100644 index 0000000..22c23cf --- /dev/null +++ b/src/jvmMain/kotlin/com/kgit2/kommand/process/Child.jvm.kt @@ -0,0 +1,60 @@ +package com.kgit2.kommand.process + +import com.kgit2.kommand.exception.ErrorType +import com.kgit2.kommand.exception.KommandException +import com.kgit2.kommand.io.BufferedReader +import com.kgit2.kommand.io.BufferedWriter +import com.kgit2.kommand.io.Output +import java.util.concurrent.atomic.AtomicReference + +actual class Child( + private val process: Process, +) { + private var stdin: AtomicReference = AtomicReference(null) + private var stdout: AtomicReference = AtomicReference(null) + private var stderr: AtomicReference = AtomicReference(null) + + actual fun id(): UInt { + return process.pid().toUInt() + } + + actual fun bufferedStdin(): BufferedWriter? { + stdin.compareAndSet(null, BufferedWriter(process.outputWriter())) + return stdin.get() + } + + actual fun bufferedStdout(): BufferedReader? { + stdout.compareAndSet(null, BufferedReader(process.inputReader())) + return stdout.get() + } + + actual fun bufferedStderr(): BufferedReader? { + stderr.compareAndSet(null, BufferedReader(process.errorReader())) + return stderr.get() + } + + @Throws(KommandException::class) + actual fun kill() { + stdin.get()?.close() + process.destroy() + } + + @Throws(KommandException::class) + actual fun wait(): Int { + stdin.get()?.close() + return process.waitFor() + } + + @Throws(KommandException::class) + actual fun waitWithOutput(): Output { + 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/kommand/process/Command.jvm.kt b/src/jvmMain/kotlin/com/kgit2/kommand/process/Command.jvm.kt new file mode 100644 index 0000000..e101c34 --- /dev/null +++ b/src/jvmMain/kotlin/com/kgit2/kommand/process/Command.jvm.kt @@ -0,0 +1,100 @@ +package com.kgit2.kommand.process + +import com.kgit2.kommand.exception.KommandException +import com.kgit2.kommand.io.Output +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().add(arg) + return this + } + + actual fun args(args: List): Command { + builder.command().addAll(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) + } + + @Throws(KommandException::class) + 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/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/nativeInterop/cinterop/aarch64-apple-darwin.def b/src/nativeInterop/cinterop/aarch64-apple-darwin.def new file mode 100644 index 0000000..83f691d --- /dev/null +++ b/src/nativeInterop/cinterop/aarch64-apple-darwin.def @@ -0,0 +1,4 @@ +headers = kommand_core.h +staticLibraries = libkommand_core.a +compilerOpts = -Ikommand-core +libraryPaths = kommand-core/target/aarch64-apple-darwin/release diff --git a/src/nativeInterop/cinterop/aarch64-unknown-linux-gnu.def b/src/nativeInterop/cinterop/aarch64-unknown-linux-gnu.def new file mode 100644 index 0000000..c2ab1ee --- /dev/null +++ b/src/nativeInterop/cinterop/aarch64-unknown-linux-gnu.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/x86_64-apple-darwin.def b/src/nativeInterop/cinterop/x86_64-apple-darwin.def new file mode 100644 index 0000000..c683f38 --- /dev/null +++ b/src/nativeInterop/cinterop/x86_64-apple-darwin.def @@ -0,0 +1,4 @@ +headers = kommand_core.h +staticLibraries = libkommand_core.a +compilerOpts = -Ikommand-core +libraryPaths = kommand-core/target/x86_64-apple-darwin/release diff --git a/src/nativeInterop/cinterop/x86_64-pc-windows-gnu.def b/src/nativeInterop/cinterop/x86_64-pc-windows-gnu.def new file mode 100644 index 0000000..2ddc243 --- /dev/null +++ b/src/nativeInterop/cinterop/x86_64-pc-windows-gnu.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/nativeInterop/cinterop/x86_64-unknown-linux-gnu.def b/src/nativeInterop/cinterop/x86_64-unknown-linux-gnu.def new file mode 100644 index 0000000..5de6138 --- /dev/null +++ b/src/nativeInterop/cinterop/x86_64-unknown-linux-gnu.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/nativeMain/kotlin/com/kgit2/kommand/Extension.kt b/src/nativeMain/kotlin/com/kgit2/kommand/Extension.kt new file mode 100644 index 0000000..29c2ac7 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/kommand/Extension.kt @@ -0,0 +1,100 @@ +package com.kgit2.kommand + +import com.kgit2.kommand.exception.ErrorType +import com.kgit2.kommand.exception.KommandException +import com.kgit2.kommand.io.Output +import com.kgit2.kommand.process.Child +import com.kgit2.kommand.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) { + com.kgit2.kommand.process.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", com.kgit2.kommand.exception.ErrorType.Unknown) + } +} + +@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 + else -> throw KommandException("Unknown error type: $this", ErrorType.Unknown) + } +} diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/Platform.native.kt b/src/nativeMain/kotlin/com/kgit2/kommand/Platform.native.kt new file mode 100644 index 0000000..1843f1b --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/kommand/Platform.native.kt @@ -0,0 +1,24 @@ +package com.kgit2.kommand + +actual val platform: Platform + get() { + val os = kotlin.native.Platform.osFamily + val arch = kotlin.native.Platform.cpuArchitecture + return when (os) { + OsFamily.MACOSX -> when (arch) { + CpuArchitecture.ARM64 -> Platform.MACOS_ARM64 + CpuArchitecture.X64 -> Platform.MACOS_X64 + else -> throw Exception("Unsupported platform: $os $arch") + } + OsFamily.LINUX -> when (arch) { + CpuArchitecture.ARM64 -> Platform.LINUX_ARM64 + CpuArchitecture.X64 -> Platform.LINUX_X64 + else -> throw Exception("Unsupported platform: $os $arch") + } + OsFamily.WINDOWS -> when (arch) { + CpuArchitecture.X64 -> Platform.MINGW_X64 + else -> throw Exception("Unsupported platform: $os $arch") + } + else -> throw Exception("Unsupported platform: $os $arch") + } + } diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/env/EnvVars.native.kt b/src/nativeMain/kotlin/com/kgit2/kommand/env/EnvVars.native.kt new file mode 100644 index 0000000..7a16597 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/kommand/env/EnvVars.native.kt @@ -0,0 +1,31 @@ +package com.kgit2.kommand.env + +import com.kgit2.kommand.asString +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(key: String): String? { + return env_var(key)?.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/nativeMain/kotlin/com/kgit2/kommand/io/BufferedReader.native.kt b/src/nativeMain/kotlin/com/kgit2/kommand/io/BufferedReader.native.kt new file mode 100644 index 0000000..210a3f7 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/kommand/io/BufferedReader.native.kt @@ -0,0 +1,89 @@ +package com.kgit2.kommand.io + +import com.kgit2.kommand.exception.KommandException +import com.kgit2.kommand.from +import kommand_core.drop_stderr +import kommand_core.drop_stdout +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 kotlinx.cinterop.COpaquePointer +import kotlinx.cinterop.alloc +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.ptr +import kotlinx.cinterop.value +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 -> { + drop_stdout(reader) + } + ReaderType.STDERR -> { + drop_stderr(reader) + } + } + } + + @Throws(KommandException::class) + actual fun readLine(): String? = run { + memScoped { + val size = alloc(0uL) + val result = when (type) { + ReaderType.STDOUT -> { + read_line_stdout(inner, size.ptr) + } + ReaderType.STDERR -> { + read_line_stderr(inner, size.ptr) + } + } + if (size.value == 0uL) { + null + } else { + String.from(result) + } + } + } + + @Throws(KommandException::class) + actual fun readAll(): String? = run { + memScoped { + val size = alloc(0uL) + val result = when (type) { + ReaderType.STDOUT -> { + read_all_stdout(inner, size.ptr) + } + ReaderType.STDERR -> { + read_all_stderr(inner, size.ptr) + } + } + if (size.value == 0uL) { + null + } else { + String.from(result) + } + } + } + + @Throws(KommandException::class) + actual fun lines(): Sequence = sequence { + do { + val line = readLine() + if (line != null) { + yield(line) + } + } while (line != null) + } +} + +enum class ReaderType { + STDOUT, + STDERR, + ; +} diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/io/BufferedWriter.native.kt b/src/nativeMain/kotlin/com/kgit2/kommand/io/BufferedWriter.native.kt new file mode 100644 index 0000000..e4e5868 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/kommand/io/BufferedWriter.native.kt @@ -0,0 +1,38 @@ +package com.kgit2.kommand.io + +import com.kgit2.kommand.exception.KommandException +import com.kgit2.kommand.unwrap +import kommand_core.drop_stdin +import kommand_core.flush_stdin +import kommand_core.write_line_stdin +import kotlinx.atomicfu.atomic +import kotlinx.cinterop.COpaquePointer +import kotlin.native.ref.createCleaner + +actual class BufferedWriter( + private var inner: COpaquePointer? +) { + private val isClosed = atomic(false) + + private val cleaner = createCleaner(isClosed to inner) { (freed, writter) -> + if (freed.compareAndSet(expect = false, update = true)) { + drop_stdin(writter) + } + } + + @Throws(KommandException::class) + actual fun writeLine(line: String) = run { + write_line_stdin(inner, line).unwrap() + } + + @Throws(KommandException::class) + actual fun flush() = run { + flush_stdin(inner).unwrap() + } + + @Throws(KommandException::class) + actual fun close() { + drop_stdin(inner) + isClosed.getAndSet(true) + } +} diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/process/Child.native.kt b/src/nativeMain/kotlin/com/kgit2/kommand/process/Child.native.kt new file mode 100644 index 0000000..1ea8f59 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/kommand/process/Child.native.kt @@ -0,0 +1,79 @@ +package com.kgit2.kommand.process + +import com.kgit2.kommand.exception.KommandException +import com.kgit2.kommand.from +import com.kgit2.kommand.io.BufferedReader +import com.kgit2.kommand.io.BufferedWriter +import com.kgit2.kommand.io.Output +import com.kgit2.kommand.io.ReaderType +import com.kgit2.kommand.unwrap +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.wait_child +import kommand_core.wait_with_output_child +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.locks.SynchronizedObject +import kotlinx.cinterop.COpaquePointer +import kotlin.concurrent.AtomicReference +import kotlin.native.ref.createCleaner + +actual class Child( + private var inner: COpaquePointer? +): SynchronizedObject() { + private var stdin: AtomicReference = AtomicReference(null) + private var stdout: AtomicReference = AtomicReference(null) + private var stderr: AtomicReference = AtomicReference(null) + + private val isClosed = atomic(false) + + private val cleaner = createCleaner(isClosed to inner) { (freed, child) -> + if (freed.compareAndSet(expect = false, update = true)) { + drop_child(child) + } + } + + companion object; + + actual fun id(): UInt { + return id_child(inner) + } + + actual fun bufferedStdin(): BufferedWriter? { + stdin.compareAndSet(null, BufferedWriter(buffered_stdin_child(inner))) + return stdin.value + } + + actual fun bufferedStdout(): BufferedReader? { + stdout.compareAndSet(null, BufferedReader(buffered_stdout_child(inner), ReaderType.STDOUT)) + return stdout.value + } + + actual fun bufferedStderr(): BufferedReader? { + stderr.compareAndSet(null, BufferedReader(buffered_stderr_child(inner), ReaderType.STDERR)) + return stderr.value + } + + @Throws(KommandException::class) + actual fun kill() = run { + kill_child(inner).unwrap() + } + + @Throws(KommandException::class) + actual fun wait(): Int = run { + stdin.getAndSet(null)?.close() + Int.from(wait_child(inner)) + } + + @Throws(KommandException::class) + actual fun waitWithOutput(): Output = run { + stdin.getAndSet(null)?.close() + val inner = this.inner + this.inner = null + isClosed.getAndSet(true) + Output.from(wait_with_output_child(inner)) + } +} diff --git a/src/nativeMain/kotlin/com/kgit2/kommand/process/Command.native.kt b/src/nativeMain/kotlin/com/kgit2/kommand/process/Command.native.kt new file mode 100644 index 0000000..67b0b03 --- /dev/null +++ b/src/nativeMain/kotlin/com/kgit2/kommand/process/Command.native.kt @@ -0,0 +1,111 @@ +package com.kgit2.kommand.process + +import com.kgit2.kommand.asString +import com.kgit2.kommand.exception.KommandException +import com.kgit2.kommand.from +import com.kgit2.kommand.io.Output +import com.kgit2.kommand.to +import kommand_core.arg_command +import kommand_core.current_dir_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 +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) { command -> + drop_command(command) + } + + override fun toString(): String { + return display_command(inner)?.asString() ?: "null" + } + + actual fun debugString(): String { + return display_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 { + stdout_command(inner, stdio.to()) + return this + } + + actual fun stderr(stdio: Stdio): Command { + stderr_command(inner, stdio.to()) + return this + } + + @Throws(KommandException::class) + actual fun spawn(): Child = run { + Child.from(spawn_command(inner)) + } + + @Throws(KommandException::class) + actual fun output(): Output = run { + Output.from(output_command(inner)) + } + + @Throws(KommandException::class) + actual fun status(): Int = run { + Int.from(status_command(inner)) + } +} 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) - } -}