@@ -81,7 +81,9 @@ var ErrNoFunc = errors.New("no call stack information")
81
81
//
82
82
// It accepts the '+' and '#' flags for most of the verbs as follows.
83
83
//
84
- // %+s path of source file relative to the compile time GOPATH
84
+ // %+s path of source file relative to the compile time GOPATH,
85
+ // or the module path joined to the path of source file relative
86
+ // to module root
85
87
// %#s full path of source file
86
88
// %+n import path qualified function name
87
89
// %+k full package path
@@ -100,7 +102,7 @@ func (c Call) Format(s fmt.State, verb rune) {
100
102
case s .Flag ('#' ):
101
103
// done
102
104
case s .Flag ('+' ):
103
- file = file [ pkgIndex ( file , c .frame . Function ):]
105
+ file = pkgFilePath ( & c .frame )
104
106
default :
105
107
const sep = "/"
106
108
if i := strings .LastIndex (file , sep ); i != - 1 {
@@ -285,6 +287,80 @@ func pkgIndex(file, funcName string) int {
285
287
return i + len (sep )
286
288
}
287
289
290
+ // pkgFilePath returns the frame's filepath relative to the compile-time GOPATH,
291
+ // or its module path joined to its path relative to the module root.
292
+ //
293
+ // As of Go 1.11 there is no direct way to know the compile time GOPATH or
294
+ // module paths at runtime, but we can piece together the desired information
295
+ // from available information. We note that runtime.Frame.Function contains the
296
+ // function name qualified by the package path, which includes the module path
297
+ // but not the GOPATH. We can extract the package path from that and append the
298
+ // last segments of the file path to arrive at the desired package qualified
299
+ // file path. For example, given:
300
+ //
301
+ // GOPATH /home/user
302
+ // import path pkg/sub
303
+ // frame.File /home/user/src/pkg/sub/file.go
304
+ // frame.Function pkg/sub.Type.Method
305
+ // Desired return pkg/sub/file.go
306
+ //
307
+ // It appears that we simply need to trim ".Type.Method" from frame.Function and
308
+ // append "/" + path.Base(file).
309
+ //
310
+ // But there are other wrinkles. Although it is idiomatic to do so, the internal
311
+ // name of a package is not required to match the last segment of its import
312
+ // path. In addition, the introduction of modules in Go 1.11 allows working
313
+ // without a GOPATH. So we also must make these work right:
314
+ //
315
+ // GOPATH /home/user
316
+ // import path pkg/go-sub
317
+ // package name sub
318
+ // frame.File /home/user/src/pkg/go-sub/file.go
319
+ // frame.Function pkg/sub.Type.Method
320
+ // Desired return pkg/go-sub/file.go
321
+ //
322
+ // Module path pkg/v2
323
+ // import path pkg/v2/go-sub
324
+ // package name sub
325
+ // frame.File /home/user/cloned-pkg/go-sub/file.go
326
+ // frame.Function pkg/v2/sub.Type.Method
327
+ // Desired return pkg/v2/go-sub/file.go
328
+ //
329
+ // We can handle all of these situations by using the package path extracted
330
+ // from frame.Function up to, but not including, the last segment as the prefix
331
+ // and the last two segments of frame.File as the suffix of the returned path.
332
+ // This preserves the existing behavior when working in a GOPATH without modules
333
+ // and a semantically equivalent behavior when used in module aware project.
334
+ func pkgFilePath (frame * runtime.Frame ) string {
335
+ pre := pkgPrefix (frame .Function )
336
+ post := pathSuffix (frame .File )
337
+ if pre == "" {
338
+ return post
339
+ }
340
+ return pre + "/" + post
341
+ }
342
+
343
+ // pkgPrefix returns the import path of the function's package with the final
344
+ // segment removed.
345
+ func pkgPrefix (funcName string ) string {
346
+ const pathSep = "/"
347
+ end := strings .LastIndex (funcName , pathSep )
348
+ if end == - 1 {
349
+ return ""
350
+ }
351
+ return funcName [:end ]
352
+ }
353
+
354
+ // pathSuffix returns the last two segments of path.
355
+ func pathSuffix (path string ) string {
356
+ const pathSep = "/"
357
+ lastSep := strings .LastIndex (path , pathSep )
358
+ if lastSep == - 1 {
359
+ return path
360
+ }
361
+ return path [strings .LastIndex (path [:lastSep ], pathSep )+ 1 :]
362
+ }
363
+
288
364
var runtimePath string
289
365
290
366
func init () {
0 commit comments