backport: 25.2.0

This commit is contained in:
FalsePattern 2025-04-20 15:56:21 +02:00
parent ce9692ecdb
commit 5e1f2e4bb2
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
13 changed files with 154 additions and 29 deletions

View file

@ -17,6 +17,23 @@ Changelog structure reference:
## [Unreleased] ## [Unreleased]
## [25.2.0]
### Added
- Debugger
- Notify the user if zig run / zig test debugging starts, but a build.zig is present
### Changed
- Project
- Line marker task suggestions for main/test now defer to Zig Build if build.zig file is detected.
### Fixed
- Debugger
- Compilation failures did not open the terminal properly and suppressed the error message
## [25.1.0] ## [25.1.0]
### Added ### Added

View file

@ -22,6 +22,7 @@
package com.falsepattern.zigbrains.debugger.runner.base package com.falsepattern.zigbrains.debugger.runner.base
import com.falsepattern.zigbrains.project.run.ZigProcessHandler
import com.falsepattern.zigbrains.shared.cli.startIPCAwareProcess import com.falsepattern.zigbrains.shared.cli.startIPCAwareProcess
import com.intellij.execution.ExecutionException import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.configurations.GeneralCommandLine
@ -40,7 +41,7 @@ import kotlinx.coroutines.withContext
class PreLaunchProcessListener(val console: ConsoleView) : ProcessListener { class PreLaunchProcessListener(val console: ConsoleView) : ProcessListener {
var isBuildFailed: Boolean = false var isBuildFailed: Boolean = false
private set private set
lateinit var processHandler: ProcessHandler lateinit var processHandler: ZigProcessHandler.IPCAware
private set private set
@Throws(ExecutionException::class) @Throws(ExecutionException::class)
@ -50,7 +51,7 @@ class PreLaunchProcessListener(val console: ConsoleView) : ProcessListener {
this@PreLaunchProcessListener.processHandler = processHandler this@PreLaunchProcessListener.processHandler = processHandler
hook(processHandler) hook(processHandler)
processHandler.startNotify() processHandler.startNotify()
withContext(Dispatchers.Default) { withContext(Dispatchers.IO) {
processHandler.process.awaitExit() processHandler.process.awaitExit()
} }
runInterruptible { runInterruptible {

View file

@ -27,6 +27,7 @@ import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
import com.intellij.execution.ExecutionException import com.intellij.execution.ExecutionException
import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.platform.util.progress.withProgressText import com.intellij.platform.util.progress.withProgressText
import com.intellij.util.containers.orNull import com.intellij.util.containers.orNull
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
@ -36,6 +37,7 @@ import java.io.File
import java.nio.file.Files import java.nio.file.Files
import kotlin.io.path.absolutePathString import kotlin.io.path.absolutePathString
import kotlin.io.path.isExecutable import kotlin.io.path.isExecutable
import kotlin.io.path.pathString
abstract class ZigDebugParametersEmitBinaryBase<ProfileState: ZigProfileState<*>>( abstract class ZigDebugParametersEmitBinaryBase<ProfileState: ZigProfileState<*>>(
driverConfiguration: DebuggerDriverConfiguration, driverConfiguration: DebuggerDriverConfiguration,
@ -49,13 +51,14 @@ abstract class ZigDebugParametersEmitBinaryBase<ProfileState: ZigProfileState<*>
@Throws(ExecutionException::class) @Throws(ExecutionException::class)
private suspend fun compileExe(listener: PreLaunchProcessListener): File { private suspend fun compileExe(listener: PreLaunchProcessListener): File {
val commandLine = profileState.getCommandLine(toolchain, true) val commandLine = profileState.getCommandLine(toolchain, true)
val cliString = commandLine.getCommandLineString(commandLine.exePath.toNioPathOrNull()?.fileName?.pathString)
val tmpDir = FileUtil.createTempDirectory("zigbrains_debug", "", true).toPath() val tmpDir = FileUtil.createTempDirectory("zigbrains_debug", "", true).toPath()
val exe = tmpDir.resolve("executable") val exe = tmpDir.resolve("executable")
commandLine.addParameters("-femit-bin=${exe.absolutePathString()}") commandLine.addParameters("-femit-bin=${exe.absolutePathString()}")
if (listener.executeCommandLineWithHook(profileState.environment.project, commandLine)) if (listener.executeCommandLineWithHook(profileState.environment.project, commandLine))
throw ExecutionException(ZigDebugBundle.message("debug.base.compile.failed.generic")) throw ExecutionException(ZigDebugBundle.message("debug.base.compile.failed.generic", cliString))
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
Files.list(tmpDir).use { stream -> Files.list(tmpDir).use { stream ->

View file

@ -24,6 +24,7 @@ package com.falsepattern.zigbrains.debugger.runner.base
import com.falsepattern.zigbrains.debugbridge.ZigDebuggerDriverConfigurationProviderBase import com.falsepattern.zigbrains.debugbridge.ZigDebuggerDriverConfigurationProviderBase
import com.falsepattern.zigbrains.debugger.ZigLocalDebugProcess import com.falsepattern.zigbrains.debugger.ZigLocalDebugProcess
import com.falsepattern.zigbrains.debugger.runner.build.ZigDebugRunnerBuild
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
import com.falsepattern.zigbrains.project.run.ZigProgramRunner import com.falsepattern.zigbrains.project.run.ZigProgramRunner
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
@ -41,6 +42,7 @@ import com.intellij.execution.ui.ConsoleView
import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.execution.ui.ConsoleViewContentType
import com.intellij.execution.ui.RunContentDescriptor import com.intellij.execution.ui.RunContentDescriptor
import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.project.guessProjectDir
import com.intellij.platform.util.progress.reportProgress import com.intellij.platform.util.progress.reportProgress
import com.intellij.xdebugger.XDebugProcess import com.intellij.xdebugger.XDebugProcess
import com.intellij.xdebugger.XDebugProcessStarter import com.intellij.xdebugger.XDebugProcessStarter
@ -82,11 +84,13 @@ abstract class ZigDebugRunnerBase<ProfileState : ZigProfileState<*>> : ZigProgra
} }
} catch (e: ExecutionException) { } catch (e: ExecutionException) {
console.print("\n", ConsoleViewContentType.ERROR_OUTPUT) console.print("\n", ConsoleViewContentType.ERROR_OUTPUT)
e.message?.let { listener.console.print(it, ConsoleViewContentType.SYSTEM_OUTPUT) } e.message?.let { listener.console.print(it, ConsoleViewContentType.ERROR_OUTPUT) }
throw e; if (this !is ZigDebugRunnerBuild && environment.project.guessProjectDir()?.children?.any { it.name == "build.zig" } == true) {
console.print("\n Warning: build.zig file detected in project.\n Did you want to use a Zig Build task instead?", ConsoleViewContentType.ERROR_OUTPUT)
}
} }
if (listener.isBuildFailed) { if (listener.isBuildFailed) {
val executionResult = DefaultExecutionResult(console, listener.processHandler) val executionResult = DefaultExecutionResult(console, listener.processHandler.unwrap())
return@reportProgress withEDTContext(ModalityState.any()) { return@reportProgress withEDTContext(ModalityState.any()) {
val runContentBuilder = RunContentBuilder(executionResult, environment) val runContentBuilder = RunContentBuilder(executionResult, environment)
runContentBuilder.showRunContent(null) runContentBuilder.showRunContent(null)

View file

@ -15,7 +15,7 @@ debugger.run.unavailable.reason.download.button=Download
debugger.run.unavailable.reason.update=Debugger is outdated debugger.run.unavailable.reason.update=Debugger is outdated
debugger.run.unavailable.reason.update.button=Update debugger.run.unavailable.reason.update.button=Update
debug.build.compile.failed.boilerplate={0}\nPlease edit this intellij build configuration and specify the path of the executable created by "zig build" directly! debug.build.compile.failed.boilerplate={0}\nPlease edit this intellij build configuration and specify the path of the executable created by "zig build" directly!
debug.base.compile.failed.generic=Failed to compile executable debug.base.compile.failed.generic=Failed to compile executable with command: {0}
debug.base.compile.failed.no-exe=Failed to find compiled binary debug.base.compile.failed.no-exe=Failed to find compiled binary
debug.build.compile.failed.multiple-exe=Multiple compiled binaries found debug.build.compile.failed.multiple-exe=Multiple compiled binaries found
debug.build.compile.failed.no-workdir=Cannot find working directory to run debugged executable debug.build.compile.failed.no-workdir=Cannot find working directory to run debugged executable

View file

@ -0,0 +1,56 @@
/*
* 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.project.execution.base
import com.falsepattern.zigbrains.zig.psi.ZigContainerMembers
import com.falsepattern.zigbrains.zig.psi.ZigFile
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.isFile
import com.intellij.psi.util.childrenOfType
fun ZigFile.hasMainFunction(): Boolean {
val members = childrenOfType<ZigContainerMembers>().firstOrNull() ?: return false
return members.containerDeclarationList.any { it.decl?.fnProto?.identifier?.textMatches("main") == true }
}
fun ZigFile.hasTests(): Boolean {
val members = childrenOfType<ZigContainerMembers>().firstOrNull() ?: return false
return members.containerDeclarationList.any { it.testDecl != null }
}
fun VirtualFile.findBuildZig(): VirtualFile? {
var parent = this.parent
while (parent != null) {
parent.children.forEach {
if (it.isFile && it.name == "build.zig") {
return it
}
}
parent = parent.parent
}
return null
}
fun VirtualFile.isBuildZig(): Boolean {
return name == "build.zig"
}

View file

@ -24,12 +24,16 @@ package com.falsepattern.zigbrains.project.execution.build
import com.falsepattern.zigbrains.ZigBrainsBundle import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.base.ZigConfigProducer import com.falsepattern.zigbrains.project.execution.base.ZigConfigProducer
import com.falsepattern.zigbrains.project.execution.base.findBuildZig
import com.falsepattern.zigbrains.project.execution.base.isBuildZig
import com.falsepattern.zigbrains.project.execution.firstConfigFactory import com.falsepattern.zigbrains.project.execution.firstConfigFactory
import com.falsepattern.zigbrains.zig.psi.ZigFile import com.falsepattern.zigbrains.zig.psi.ZigFile
import com.falsepattern.zigbrains.zig.psi.ZigTypes
import com.intellij.execution.actions.ConfigurationFromContext import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.configurations.ConfigurationFactory import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.util.elementType
import java.nio.file.Path import java.nio.file.Path
class ZigConfigProducerBuild: ZigConfigProducer<ZigExecConfigBuild>() { class ZigConfigProducerBuild: ZigConfigProducer<ZigExecConfigBuild>() {
@ -38,14 +42,39 @@ class ZigConfigProducerBuild: ZigConfigProducer<ZigExecConfigBuild>() {
} }
override fun setupConfigurationFromContext(configuration: ZigExecConfigBuild, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean { override fun setupConfigurationFromContext(configuration: ZigExecConfigBuild, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean {
if (theFile.name != "build.zig") if (theFile.isBuildZig()) {
return false configuration.name = ZigBrainsBundle.message("configuration.build.marker-run")
configuration.name = ZigBrainsBundle.message("configuration.build.marker-name") configuration.buildSteps.args = "run"
return true configuration.debugBuildSteps.args = "install"
return true
}
val buildZig = theFile.findBuildZig() ?: return false
configuration.workingDirectory.path = buildZig.parent.toNioPath()
if (element.elementType == ZigTypes.KEYWORD_TEST) {
configuration.name = ZigBrainsBundle.message("configuration.build.marker-test")
configuration.buildSteps.args = "test"
configuration.debugBuildSteps.args = "install_test"
return true
} else {
configuration.name = ZigBrainsBundle.message("configuration.build.marker-run")
configuration.buildSteps.args = "run"
configuration.debugBuildSteps.args = "install"
return true
}
} }
override fun isConfigurationFromContext(configuration: ZigExecConfigBuild, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean { override fun isConfigurationFromContext(configuration: ZigExecConfigBuild, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean {
return filePath.parent == (configuration.workingDirectory.path ?: return false) val dir = configuration.workingDirectory.path ?: return false
if (theFile.isBuildZig()) {
return filePath.parent == dir
} else {
if (element.elementType == ZigTypes.KEYWORD_TEST) {
if (configuration.buildSteps.args != "test")
return false
}
val buildZig = theFile.findBuildZig() ?: return false
return buildZig.parent.toNioPath() == dir
}
} }
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean { override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {

View file

@ -23,14 +23,14 @@
package com.falsepattern.zigbrains.project.execution.run package com.falsepattern.zigbrains.project.execution.run
import com.falsepattern.zigbrains.project.execution.base.ZigConfigProducer import com.falsepattern.zigbrains.project.execution.base.ZigConfigProducer
import com.falsepattern.zigbrains.project.execution.base.findBuildZig
import com.falsepattern.zigbrains.project.execution.base.hasMainFunction
import com.falsepattern.zigbrains.project.execution.firstConfigFactory import com.falsepattern.zigbrains.project.execution.firstConfigFactory
import com.falsepattern.zigbrains.zig.psi.ZigContainerMembers
import com.falsepattern.zigbrains.zig.psi.ZigFile import com.falsepattern.zigbrains.zig.psi.ZigFile
import com.intellij.execution.actions.ConfigurationFromContext import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.configurations.ConfigurationFactory import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.util.childrenOfType
import java.nio.file.Path import java.nio.file.Path
class ZigConfigProducerRun: ZigConfigProducer<ZigExecConfigRun>() { class ZigConfigProducerRun: ZigConfigProducer<ZigExecConfigRun>() {
@ -39,8 +39,10 @@ class ZigConfigProducerRun: ZigConfigProducer<ZigExecConfigRun>() {
} }
override fun setupConfigurationFromContext(configuration: ZigExecConfigRun, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean { override fun setupConfigurationFromContext(configuration: ZigExecConfigRun, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean {
val members = psiFile.childrenOfType<ZigContainerMembers>().firstOrNull() ?: return false if (!psiFile.hasMainFunction()) {
if (members.containerDeclarationList.none { it.decl?.fnProto?.identifier?.textMatches("main") == true }) { return false
}
if (theFile.findBuildZig() != null) {
return false return false
} }
configuration.filePath.path = filePath configuration.filePath.path = filePath
@ -56,5 +58,3 @@ class ZigConfigProducerRun: ZigConfigProducer<ZigExecConfigRun>() {
return self.configurationType is ZigConfigTypeRun return self.configurationType is ZigConfigTypeRun
} }
} }
private val LINE_MARKER = ZigLineMarkerRun()

View file

@ -24,14 +24,14 @@ package com.falsepattern.zigbrains.project.execution.test
import com.falsepattern.zigbrains.ZigBrainsBundle import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.base.ZigConfigProducer import com.falsepattern.zigbrains.project.execution.base.ZigConfigProducer
import com.falsepattern.zigbrains.project.execution.base.findBuildZig
import com.falsepattern.zigbrains.project.execution.base.hasTests
import com.falsepattern.zigbrains.project.execution.firstConfigFactory import com.falsepattern.zigbrains.project.execution.firstConfigFactory
import com.falsepattern.zigbrains.zig.psi.ZigContainerMembers
import com.falsepattern.zigbrains.zig.psi.ZigFile import com.falsepattern.zigbrains.zig.psi.ZigFile
import com.intellij.execution.actions.ConfigurationFromContext import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.configurations.ConfigurationFactory import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.util.childrenOfType
import java.nio.file.Path import java.nio.file.Path
class ZigConfigProducerTest: ZigConfigProducer<ZigExecConfigTest>() { class ZigConfigProducerTest: ZigConfigProducer<ZigExecConfigTest>() {
@ -40,8 +40,10 @@ class ZigConfigProducerTest: ZigConfigProducer<ZigExecConfigTest>() {
} }
override fun setupConfigurationFromContext(configuration: ZigExecConfigTest, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean { override fun setupConfigurationFromContext(configuration: ZigExecConfigTest, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean {
val members = psiFile.childrenOfType<ZigContainerMembers>().firstOrNull() ?: return false if (!psiFile.hasTests()) {
if (members.containerDeclarationList.none { it.testDecl != null }) { return false
}
if (theFile.findBuildZig() != null) {
return false return false
} }
configuration.filePath.path = filePath configuration.filePath.path = filePath

View file

@ -28,14 +28,25 @@ import com.intellij.execution.process.KillableProcessHandler
import com.pty4j.PtyProcess import com.pty4j.PtyProcess
import java.nio.charset.Charset import java.nio.charset.Charset
class ZigProcessHandler : KillableProcessHandler { open class ZigProcessHandler : KillableProcessHandler {
constructor(commandLine: GeneralCommandLine) : super(commandLine) { constructor(commandLine: GeneralCommandLine) : super(commandLine) {
setHasPty(commandLine is PtyCommandLine) setHasPty(commandLine is PtyCommandLine)
setShouldDestroyProcessRecursively(!hasPty()) setShouldDestroyProcessRecursively(!hasPty())
} }
constructor (process: Process, commandLine: String, charset: Charset) : super(process, commandLine, charset) { protected constructor (process: Process, commandLine: String, charset: Charset) : super(process, commandLine, charset) {
setHasPty(process is PtyProcess) setHasPty(process is PtyProcess)
setShouldDestroyProcessRecursively(!hasPty()) setShouldDestroyProcessRecursively(!hasPty())
} }
class IPCAware : ZigProcessHandler {
val originalCommandLine: String
constructor(originalCommandLine: String, commandLine: GeneralCommandLine) : super(commandLine) {
this.originalCommandLine = originalCommandLine
}
fun unwrap(): ZigProcessHandler {
return ZigProcessHandler(this.process, this.originalCommandLine, this.charset ?: Charsets.UTF_8)
}
}
} }

View file

@ -130,13 +130,14 @@ fun createCommandLineSafe(
} }
@Throws(ExecutionException::class) @Throws(ExecutionException::class)
suspend fun GeneralCommandLine.startIPCAwareProcess(project: Project?, emulateTerminal: Boolean = false): ZigProcessHandler { suspend fun GeneralCommandLine.startIPCAwareProcess(project: Project?, emulateTerminal: Boolean = false): ZigProcessHandler.IPCAware {
val original = this.commandLineString
val ipc = if (project != null && !emulateTerminal) IPCUtil.wrapWithIPC(this) else null val ipc = if (project != null && !emulateTerminal) IPCUtil.wrapWithIPC(this) else null
val cli = ipc?.cli ?: this val cli = ipc?.cli ?: this
if (emulateTerminal && OS.CURRENT != OS.Windows && !cli.environment.contains("TERM")) { if (emulateTerminal && OS.CURRENT != OS.Windows && !cli.environment.contains("TERM")) {
cli.withEnvironment("TERM", "xterm-256color") cli.withEnvironment("TERM", "xterm-256color")
} }
val handler = ZigProcessHandler(cli) val handler = ZigProcessHandler.IPCAware(original, cli)
ProcessTerminatedListener.attach(handler) ProcessTerminatedListener.attach(handler)
if (ipc != null) { if (ipc != null) {

View file

@ -91,7 +91,8 @@ configuration.test.marker-name=all tests in {0}
configuration.build.name=Zig build configuration.build.name=Zig build
configuration.build.suggested-name=Build configuration.build.suggested-name=Build
configuration.build.description=Execute "zig build" with custom steps configuration.build.description=Execute "zig build" with custom steps
configuration.build.marker-name=Build and Run configuration.build.marker-run=Build and Run
configuration.build.marker-test=Build and Test
settings.project.group.title=Zig Settings settings.project.group.title=Zig Settings
settings.project.label.direnv=Use direnv settings.project.label.direnv=Use direnv
settings.project.label.toolchain=Toolchain location settings.project.label.toolchain=Toolchain location

View file

@ -1,7 +1,7 @@
pluginName=ZigBrains pluginName=ZigBrains
pluginRepositoryUrl=https://github.com/FalsePattern/ZigBrains pluginRepositoryUrl=https://github.com/FalsePattern/ZigBrains
pluginVersion=25.1.0 pluginVersion=25.2.0
pluginSinceBuild=241 pluginSinceBuild=241
pluginUntilBuild=241.* pluginUntilBuild=241.*