fix: improved mkfifo/bash detection for ZIG_PROGRESS
This commit is contained in:
parent
7ee7e2f3d1
commit
cc7d1393d6
5 changed files with 115 additions and 36 deletions
|
@ -17,6 +17,11 @@ Changelog structure reference:
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Project
|
||||
- mkfifo/bash for zig progress visualization is now detected more reliably (fixes error on macOS)
|
||||
|
||||
## [23.0.0]
|
||||
|
||||
### Added
|
||||
|
|
|
@ -25,9 +25,10 @@ package com.falsepattern.zigbrains.direnv
|
|||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.util.EnvironmentUtil
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
|
||||
data class Env(val env: Map<String, String>) {
|
||||
|
@ -36,9 +37,11 @@ data class Env(val env: Map<String, String>) {
|
|||
private fun getVariable(name: @NonNls String) =
|
||||
env.getOrElse(name) { EnvironmentUtil.getValue(name) }
|
||||
|
||||
fun findExecutableOnPATH(exe: @NonNls String): Path? {
|
||||
suspend fun findExecutableOnPATH(exe: @NonNls String) = findAllExecutablesOnPATH(exe).firstOrNull()
|
||||
|
||||
fun findAllExecutablesOnPATH(exe: @NonNls String) = flow {
|
||||
val exeName = if (SystemInfo.isWindows) "$exe.exe" else exe
|
||||
val paths = path ?: return null
|
||||
val paths = path ?: return@flow
|
||||
for (dir in paths) {
|
||||
val path = dir.toNioPathOrNull()?.absolute() ?: continue
|
||||
if (!path.toFile().exists() || !path.isDirectory())
|
||||
|
@ -46,9 +49,8 @@ data class Env(val env: Map<String, String>) {
|
|||
val exePath = path.resolve(exeName).absolute()
|
||||
if (!exePath.isRegularFile() || !exePath.isExecutable())
|
||||
continue
|
||||
return exePath
|
||||
emit(exePath)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* This file is part of ZigBrains.
|
||||
*
|
||||
* Copyright (C) 2023-2025 FalsePattern
|
||||
* All Rights Reserved
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* ZigBrains is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, only version 3 of the License.
|
||||
*
|
||||
* ZigBrains is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.falsepattern.zigbrains.shared.ipc
|
||||
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import java.nio.file.Path
|
||||
|
||||
data class IPC(val cli: GeneralCommandLine, val fifoPath: Path, val fifoClose: AutoCloseable)
|
|
@ -27,55 +27,102 @@ import com.intellij.execution.configurations.GeneralCommandLine
|
|||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.util.io.awaitExit
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.file.Path
|
||||
import javax.swing.tree.DefaultMutableTreeNode
|
||||
import kotlin.io.path.deleteIfExists
|
||||
import kotlin.io.path.inputStream
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
/**
|
||||
* Zig build progress node IPC glue code
|
||||
*/
|
||||
object IPCUtil {
|
||||
val haveIPC = checkHaveIPC()
|
||||
|
||||
private fun checkHaveIPC(): Boolean {
|
||||
val haveIPC: Boolean get() = info != null
|
||||
|
||||
@JvmRecord
|
||||
data class IPCInfo(val mkfifo: MKFifo, val bash: String)
|
||||
|
||||
private val info: IPCInfo? by lazy { runBlocking {
|
||||
createInfo()
|
||||
} }
|
||||
|
||||
private suspend fun createInfo(): IPCInfo? {
|
||||
if (SystemInfo.isWindows) {
|
||||
return false;
|
||||
return null
|
||||
}
|
||||
val mkfifo = emptyEnv.findExecutableOnPATH("mkfifo")
|
||||
val bash = emptyEnv.findExecutableOnPATH("bash")
|
||||
return mkfifo != null && bash != null
|
||||
val mkfifo = emptyEnv
|
||||
.findAllExecutablesOnPATH("mkfifo")
|
||||
.map { it.pathString }
|
||||
.map(::MKFifo)
|
||||
.toList()
|
||||
.find { mkfifo ->
|
||||
val fifo = mkfifo.createTemp() ?: return@find false
|
||||
fifo.second.close()
|
||||
true
|
||||
} ?: return null
|
||||
|
||||
val selectedBash = emptyEnv
|
||||
.findAllExecutablesOnPATH("bash")
|
||||
.map { it.pathString }
|
||||
.filter {
|
||||
val cli = GeneralCommandLine(it)
|
||||
val tmpFile = FileUtil.createTempFile("zigbrains-bash-detection", null, true).toPath()
|
||||
try {
|
||||
cli.addParameters("-c", "exec {var}>${tmpFile.pathString}; echo foo >&\$var; exec {var}>&-")
|
||||
val process = cli.createProcess()
|
||||
val exitCode = process.awaitExit()
|
||||
if (exitCode != 0) {
|
||||
return@filter false
|
||||
}
|
||||
val text = tmpFile.inputStream().use { it.readAllBytes().toString(Charset.defaultCharset()).trim() }
|
||||
if (text != "foo") {
|
||||
return@filter false
|
||||
}
|
||||
true
|
||||
} finally {
|
||||
tmpFile.deleteIfExists()
|
||||
}
|
||||
}
|
||||
.firstOrNull() ?: return null
|
||||
|
||||
return IPCInfo(mkfifo, selectedBash)
|
||||
}
|
||||
|
||||
private suspend fun mkfifo(path: Path): AutoCloseable? {
|
||||
val cli = GeneralCommandLine("mkfifo", path.pathString)
|
||||
suspend fun wrapWithIPC(cli: GeneralCommandLine): IPC? {
|
||||
if (!haveIPC)
|
||||
return null
|
||||
val (fifoFile, fifo) = info!!.mkfifo.createTemp() ?: return null
|
||||
//FIFO created, hack cli
|
||||
val exePath = cli.exePath
|
||||
val args = "exec {var}>${fifoFile.pathString}; ZIG_PROGRESS=\$var $exePath ${cli.parametersList.parametersString}; exec {var}>&-"
|
||||
cli.withExePath(info!!.bash)
|
||||
cli.parametersList.clearAll()
|
||||
cli.addParameters("-c", args)
|
||||
return IPC(cli, fifoFile, fifo)
|
||||
}
|
||||
|
||||
@JvmRecord
|
||||
data class MKFifo(val exe: String) {
|
||||
suspend fun createTemp(): Pair<Path, AutoCloseable>? {
|
||||
val fifoFile = FileUtil.createTempFile("zigbrains-ipc-pipe", null, true).toPath()
|
||||
fifoFile.deleteIfExists()
|
||||
val fifo = create(fifoFile)
|
||||
if (fifo == null) {
|
||||
fifoFile.deleteIfExists()
|
||||
return null
|
||||
}
|
||||
return Pair(fifoFile, fifo)
|
||||
}
|
||||
suspend fun create(path: Path): AutoCloseable? {
|
||||
val cli = GeneralCommandLine(exe, path.pathString)
|
||||
val process = cli.createProcess()
|
||||
val exitCode = process.awaitExit()
|
||||
return if (exitCode == 0) AutoCloseable {
|
||||
path.deleteIfExists()
|
||||
} else null
|
||||
}
|
||||
|
||||
data class IPC(val cli: GeneralCommandLine, val fifoPath: Path, val fifoClose: AutoCloseable)
|
||||
|
||||
suspend fun wrapWithIPC(cli: GeneralCommandLine): IPC? {
|
||||
if (!haveIPC)
|
||||
return null
|
||||
val fifoFile = FileUtil.createTempFile("zigbrains-ipc-pipe", null, true).toPath()
|
||||
fifoFile.deleteIfExists()
|
||||
val fifo = mkfifo(fifoFile)
|
||||
if (fifo == null) {
|
||||
fifoFile.deleteIfExists()
|
||||
return null
|
||||
}
|
||||
//FIFO created, hack cli
|
||||
val exePath = cli.exePath
|
||||
val args = "exec {var}>${fifoFile.pathString}; ZIG_PROGRESS=\$var $exePath ${cli.parametersList.parametersString}; exec {var}>&-"
|
||||
cli.withExePath("bash")
|
||||
cli.parametersList.clearAll()
|
||||
cli.addParameters("-c", args)
|
||||
return IPC(cli, fifoFile, fifo)
|
||||
}
|
||||
|
||||
}
|
|
@ -22,14 +22,11 @@
|
|||
|
||||
package com.falsepattern.zigbrains.shared.ipc
|
||||
|
||||
import com.falsepattern.zigbrains.Icons
|
||||
import com.falsepattern.zigbrains.project.steps.ui.BaseNodeDescriptor
|
||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||
import com.falsepattern.zigbrains.shared.ipc.Payload.Companion.readPayload
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.google.common.io.LittleEndianDataInputStream
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
|
@ -93,7 +90,7 @@ class ZigIPCService(val project: Project) {
|
|||
return roots
|
||||
}
|
||||
|
||||
private suspend fun watch(ipc: IPCUtil.IPC, process: Process) {
|
||||
private suspend fun watch(ipc: IPC, process: Process) {
|
||||
val currentNode = IPCTreeNode(BaseNodeDescriptor<Any>(project, "pid: ${process.pid()}", AllIcons.Actions.InlayGear))
|
||||
mutex.withLock {
|
||||
nodes.add(currentNode)
|
||||
|
@ -125,7 +122,7 @@ class ZigIPCService(val project: Project) {
|
|||
}
|
||||
}
|
||||
|
||||
fun launchWatcher(ipc: IPCUtil.IPC, process: Process) {
|
||||
fun launchWatcher(ipc: IPC, process: Process) {
|
||||
project.zigCoroutineScope.launch {
|
||||
watch(ipc, process)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue