Skip to content

Commit afcf51a

Browse files
committed
Add module support
1 parent 1a267a5 commit afcf51a

File tree

2 files changed

+79
-2
lines changed

2 files changed

+79
-2
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module github.com/go-stack/stack

stack.go

+78-2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ var ErrNoFunc = errors.New("no call stack information")
8181
//
8282
// It accepts the '+' and '#' flags for most of the verbs as follows.
8383
//
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
8587
// %#s full path of source file
8688
// %+n import path qualified function name
8789
// %+k full package path
@@ -100,7 +102,7 @@ func (c Call) Format(s fmt.State, verb rune) {
100102
case s.Flag('#'):
101103
// done
102104
case s.Flag('+'):
103-
file = file[pkgIndex(file, c.frame.Function):]
105+
file = pkgFilePath(&c.frame)
104106
default:
105107
const sep = "/"
106108
if i := strings.LastIndex(file, sep); i != -1 {
@@ -285,6 +287,80 @@ func pkgIndex(file, funcName string) int {
285287
return i + len(sep)
286288
}
287289

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+
288364
var runtimePath string
289365

290366
func init() {

0 commit comments

Comments
 (0)