bazelbuild / intellij

IntelliJ plugin for Bazel projects
https://ij.bazel.build/
Apache License 2.0
757 stars 298 forks source link

Breakpoints do not get triggered in Golang projects running C extensions via cgo #6416

Open rogerhu opened 2 months ago

rogerhu commented 2 months ago

Description of the bug:

This repo is meant to show the current challenges with the Bazel IntellIj plugin and the Golang cgo module. It was run on a MacOS laptop.

Try opening this repo with GoLand or IntelliJ Ultimate Edition. Set a breakpoint one of the lines. You'll notice that nothing triggers. Disabling the use of the import C module stops the problem.

The problem turns out to be related to rules_go that strips out absolute path line directives to support build hermeticity. Modifying these line directives to strip out the execroot directory causes cgo files that are not relative to the workspace root. As a result the Golang debugger, Delve, is unable to provide source code references.

We can also see in the IntelliJ logs that

2024-05-04 21:34:07,956 [  27736]   WARN - #com.google.idea.blaze.golang.run.BlazeDlvPositionConverter - Unable to find local file for debug path: _main/tst.go

I'm able to confirm by attaching a breakpoint to a live instance of the Goland and examining the BlazeDlvPositionConverter code. We can do so by setting custom VM Options on a Goland instance (see https://rogerhu.github.io/studying-android-studio-internals/), adding the Go IDE plugin to the Project Structure, and attaching a debugger from an IntelliJ instance.

Workaround

Delve provides a solution to these issues with the path substitution configuration. We can add a file such as ~/.dlv/config.yml with the following YAML:

substitute-path:
  - { from: "_main/", to: "/Users/rogerhu/projects/sample-cgo-repo" }

We can check by installing Delve locally:

brew install delve

And running:

bazel build //:test
dlv exec bazel-out/darwin_arm64-dbg/bin/test_/test

You can then see:

(dlv) b main.main
Breakpoint 1 set at 0x101022fd0 for main.main() ./tst.go:11
(dlv) c
> main.main() ./tst.go:11 (hits goroutine(1):1 total:1) (PC: 0x101022fd0)
     6: */
     7: import "C"
     8:
     9: import "fmt"
    10:
=>  11: func main() {
    12:         fmt.Println("hello")
    13:         result := C.keychain_get(C.CString("engflow"))
    14:         fmt.Printf(C.GoString(result))
    15: }

However, this logic only works with the Delve command-line tool, not with the Bazel IntelliJ plugin. VScode currently supports a variation of this logic here. The full logic should follow how it's currently done in Delve.

Which category does this issue belong to?

GoLand / IntellIj / go_rules bug

What's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.

See https://github.com/rogerhu/sample-cgo-repro

Which Intellij IDE are you using? Please provide the specific version.

GoLand 2024.1.1 and IntelliJ 2023.3.6

What programming languages and tools are you using? Please provide specific versions.

go version
go version go1.22.2 darwin/arm64

What Bazel plugin version are you using?

Bazel 7

Have you found anything relevant by searching the web?

I have a branch that attempts to introduce this logic using the same approach used in Delve's logic. However, I want to check with the maintainers if 1) this direction is acceptable 2) that we can rely on the global ~/.dlv/config.yml or a separate substitution logic needs to implemented.

Screenshot of working prototype:

image

If it's easier to change in the Golang rules, that would be another option. I'm checking on that possibility too, but because Delve path substitution adds this type of support, I think it makes sense to have similar functionality in the Bazel IntellIj plugin.

Any other information, logs, or outputs that you want to share?

No response