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]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Project
|
||||||
|
- mkfifo/bash for zig progress visualization is now detected more reliably (fixes error on macOS)
|
||||||
|
|
||||||
## [23.0.0]
|
## [23.0.0]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -25,9 +25,10 @@ package com.falsepattern.zigbrains.direnv
|
||||||
import com.intellij.openapi.util.SystemInfo
|
import com.intellij.openapi.util.SystemInfo
|
||||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||||
import com.intellij.util.EnvironmentUtil
|
import com.intellij.util.EnvironmentUtil
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import org.jetbrains.annotations.NonNls
|
import org.jetbrains.annotations.NonNls
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.*
|
import kotlin.io.path.*
|
||||||
|
|
||||||
data class Env(val env: Map<String, String>) {
|
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) =
|
private fun getVariable(name: @NonNls String) =
|
||||||
env.getOrElse(name) { EnvironmentUtil.getValue(name) }
|
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 exeName = if (SystemInfo.isWindows) "$exe.exe" else exe
|
||||||
val paths = path ?: return null
|
val paths = path ?: return@flow
|
||||||
for (dir in paths) {
|
for (dir in paths) {
|
||||||
val path = dir.toNioPathOrNull()?.absolute() ?: continue
|
val path = dir.toNioPathOrNull()?.absolute() ?: continue
|
||||||
if (!path.toFile().exists() || !path.isDirectory())
|
if (!path.toFile().exists() || !path.isDirectory())
|
||||||
|
@ -46,9 +49,8 @@ data class Env(val env: Map<String, String>) {
|
||||||
val exePath = path.resolve(exeName).absolute()
|
val exePath = path.resolve(exeName).absolute()
|
||||||
if (!exePath.isRegularFile() || !exePath.isExecutable())
|
if (!exePath.isRegularFile() || !exePath.isExecutable())
|
||||||
continue
|
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.SystemInfo
|
||||||
import com.intellij.openapi.util.io.FileUtil
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
import com.intellij.util.io.awaitExit
|
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 java.nio.file.Path
|
||||||
import javax.swing.tree.DefaultMutableTreeNode
|
|
||||||
import kotlin.io.path.deleteIfExists
|
import kotlin.io.path.deleteIfExists
|
||||||
|
import kotlin.io.path.inputStream
|
||||||
import kotlin.io.path.pathString
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zig build progress node IPC glue code
|
* Zig build progress node IPC glue code
|
||||||
*/
|
*/
|
||||||
object IPCUtil {
|
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) {
|
if (SystemInfo.isWindows) {
|
||||||
return false;
|
return null
|
||||||
}
|
}
|
||||||
val mkfifo = emptyEnv.findExecutableOnPATH("mkfifo")
|
val mkfifo = emptyEnv
|
||||||
val bash = emptyEnv.findExecutableOnPATH("bash")
|
.findAllExecutablesOnPATH("mkfifo")
|
||||||
return mkfifo != null && bash != null
|
.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? {
|
suspend fun wrapWithIPC(cli: GeneralCommandLine): IPC? {
|
||||||
val cli = GeneralCommandLine("mkfifo", path.pathString)
|
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 process = cli.createProcess()
|
||||||
val exitCode = process.awaitExit()
|
val exitCode = process.awaitExit()
|
||||||
return if (exitCode == 0) AutoCloseable {
|
return if (exitCode == 0) AutoCloseable {
|
||||||
path.deleteIfExists()
|
path.deleteIfExists()
|
||||||
} else null
|
} 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
|
package com.falsepattern.zigbrains.shared.ipc
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.Icons
|
|
||||||
import com.falsepattern.zigbrains.project.steps.ui.BaseNodeDescriptor
|
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.ipc.Payload.Companion.readPayload
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
import com.google.common.io.LittleEndianDataInputStream
|
import com.google.common.io.LittleEndianDataInputStream
|
||||||
import com.intellij.icons.AllIcons
|
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.components.service
|
import com.intellij.openapi.components.service
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
@ -93,7 +90,7 @@ class ZigIPCService(val project: Project) {
|
||||||
return roots
|
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))
|
val currentNode = IPCTreeNode(BaseNodeDescriptor<Any>(project, "pid: ${process.pid()}", AllIcons.Actions.InlayGear))
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
nodes.add(currentNode)
|
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 {
|
project.zigCoroutineScope.launch {
|
||||||
watch(ipc, process)
|
watch(ipc, process)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue