Skip to content

Commit b521a08

Browse files
committed
dap: detect breakpoints for files when the case differs
Case insensitive filesystems can cause breakpoints to not be seen or verified. This is particularly true on Windows where the drive letter can also participate in the filepath. Change the detection logic for a breakpoint to be case insensitive. At the same time, report the name of the source as part of the breakpoint so that the editor can be told which casing we're expecting to be used. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
1 parent bb48188 commit b521a08

3 files changed

Lines changed: 43 additions & 13 deletions

File tree

dap/adapter.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"path"
1010
"path/filepath"
1111
"slices"
12+
"strings"
1213
"sync"
1314
"sync/atomic"
1415

@@ -573,7 +574,9 @@ func (b *breakpointMap) Set(fname string, sbps []dap.SourceBreakpoint) (breakpoi
573574
// null back in the JSON if there are no breakpoints
574575
breakpoints = []dap.Breakpoint{}
575576

576-
prev := b.byPath[fname]
577+
// Use lowercase paths to normalize the case. We can only know the correct casing after
578+
// we intersect the breakpoint map. When we report the pending breakpoint to the editor,
579+
prev := b.getByPath(fname)
577580
for _, sbp := range sbps {
578581
index := slices.IndexFunc(prev, func(e dap.Breakpoint) bool {
579582
return sbp.Line >= e.Line && sbp.Line <= e.EndLine && sbp.Column >= e.Column && sbp.Column <= e.EndColumn
@@ -589,12 +592,16 @@ func (b *breakpointMap) Set(fname string, sbps []dap.SourceBreakpoint) (breakpoi
589592
EndLine: sbp.Line,
590593
Column: sbp.Column,
591594
EndColumn: sbp.Column,
592-
Reason: "pending",
595+
Source: &dap.Source{
596+
Name: path.Base(fname),
597+
Path: fname,
598+
},
599+
Reason: "pending",
593600
}
594601
}
595602
breakpoints = append(breakpoints, bp)
596603
}
597-
b.byPath[fname] = breakpoints
604+
b.setByPath(fname, breakpoints)
598605
return breakpoints
599606
}
600607

@@ -615,7 +622,7 @@ func (b *breakpointMap) Intersect(ctx Context, src *pb.Source, ws string) map[di
615622
for _, info := range src.Infos {
616623
fname := filepath.Join(ws, info.Filename)
617624

618-
bps := b.byPath[fname]
625+
bps := b.getByPath(fname)
619626
for _, bp := range bps {
620627
if !bp.Verified && bp.Reason != "failed" {
621628
bp.Reason = "failed"
@@ -656,7 +663,7 @@ func (b *breakpointMap) intersect(ctx Context, src *pb.Source, locs *pb.Location
656663
info := src.Infos[loc.SourceIndex]
657664
fname := filepath.Join(ws, info.Filename)
658665

659-
bps := b.byPath[fname]
666+
bps := b.getByPath(fname)
660667
if len(bps) == 0 {
661668
// No breakpoints for this file.
662669
continue
@@ -675,6 +682,13 @@ func (b *breakpointMap) intersect(ctx Context, src *pb.Source, locs *pb.Location
675682
bp.Verified = true
676683
bp.Reason = ""
677684

685+
// The path from the source might be different than the path
686+
// the editor sent to us just because of different casing.
687+
// Prefer the version given to us from buildkit and tell
688+
// the editor what the proper casing for this should be.
689+
bp.Source.Name = path.Base(fname)
690+
bp.Source.Path = fname
691+
678692
ctx.C() <- &dap.BreakpointEvent{
679693
Event: dap.Event{Event: "breakpoint"},
680694
Body: dap.BreakpointEventBody{
@@ -689,3 +703,11 @@ func (b *breakpointMap) intersect(ctx Context, src *pb.Source, locs *pb.Location
689703
}
690704
return 0
691705
}
706+
707+
func (b *breakpointMap) setByPath(fname string, bps []dap.Breakpoint) {
708+
b.byPath[strings.ToLower(fname)] = bps
709+
}
710+
711+
func (b *breakpointMap) getByPath(fname string) []dap.Breakpoint {
712+
return b.byPath[strings.ToLower(fname)]
713+
}

dap/adapter_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ func TestBreakpointMapIntersectVerified(t *testing.T) {
202202
assert.Len(t, digests, wantMatches)
203203

204204
expectedEvents := make(map[int]struct{})
205-
for i, bp := range bm.byPath[fpath] {
205+
for i, bp := range bm.getByPath(fpath) {
206206
if breakpointCases[i].expectVerified {
207207
expectedEvents[bp.Id] = struct{}{}
208208
}
@@ -226,7 +226,7 @@ func TestBreakpointMapIntersectVerified(t *testing.T) {
226226
}
227227
}
228228

229-
stored := bm.byPath[fpath]
229+
stored := bm.getByPath(fpath)
230230
if assert.Len(t, stored, len(breakpointCases)) {
231231
for i, bc := range breakpointCases {
232232
assert.Equal(t, bc.expectVerified, stored[i].Verified, "breakpoint %d (%s) mismatch", i, bc.desc)

tests/dap_build.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,18 +228,26 @@ func testDapBuildVerifiedBreakpoints(t *testing.T, sb integration.Sandbox) {
228228
{
229229
Reason: "changed",
230230
Breakpoint: dap.Breakpoint{
231-
Id: 1,
232-
Line: 2,
233-
EndLine: 2,
231+
Id: 1,
232+
Line: 2,
233+
EndLine: 2,
234+
Source: &dap.Source{
235+
Name: "Dockerfile",
236+
Path: path.Join(dir, "Dockerfile"),
237+
},
234238
Verified: true,
235239
},
236240
},
237241
{
238242
Reason: "changed",
239243
Breakpoint: dap.Breakpoint{
240-
Id: 2,
241-
Line: 10,
242-
EndLine: 10,
244+
Id: 2,
245+
Line: 10,
246+
EndLine: 10,
247+
Source: &dap.Source{
248+
Name: "Dockerfile",
249+
Path: path.Join(dir, "Dockerfile"),
250+
},
243251
Verified: false,
244252
Reason: "failed",
245253
},

0 commit comments

Comments
 (0)