-
-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathbazel.go
142 lines (132 loc) · 5.04 KB
/
bazel.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package autogold
import (
"errors"
"os"
"runtime"
"strconv"
"strings"
"sync"
)
// Bazel build systems try to keep hermeticity by setting PATH="." - but Go does not like this as
// it is a security concern; almost all Go tooling relies on golang.org/x/tools/go/packages.Load
// which behind the scenes must invoke `go` and uses the secure version of x/sys/execabs, but
// ultimately this means Go tools like autogold cannot be run in Bazel:
//
// https://github.com/golang/go/issues/57304
//
// Autogold relies on `packages.Load` in order to determine the Go package name / path when writing
// out a Go AST representation of the value passed in; but the issue above means autogold cannot be
// used with Bazel without removing "." from your PATH, which Bazel claims breaks hermeticity (one
// of the whole reasons people use Bazel.)
//
// For Bazel users, we allow them to set ENABLE_BAZEL_PACKAGES_LOAD_HACK=true which causes autogold
// to guess/infer package names and paths using stack trace information and import paths. This is
// not perfect, it doesn't respect packages whose import paths donot match their defined
// `package foo` statement for example - but it's sufficient to enable autogold to be used in Bazel
// build environments where the above Go/Bazel bug is found.
//
// Additionally, we support using a global custom resolver. It is useful to handle packages whose
// import paths donot match their defined. You may hardcode the mapping for packages that are
// known to be problematic.
// Learn more from RegisterBazelPackageNameAndPathResolver.
func isBazel() bool {
hacks, _ := strconv.ParseBool(os.Getenv("ENABLE_BAZEL_PACKAGES_LOAD_HACK"))
return hacks
}
var (
registerResolverOnce sync.Once
resolver BazelPackagePathToNameResolverFunc
)
// RegisterBazelPackagePathToNameResolver registers a global custom resolver
// from package path to name.
// Returns empty to fallback to the existing generic handling
// You should call this method as early as possible during in `init()`
//
// We only support using a global resolver because this is a hack and it is much easier
// to track the usage when things are centralized.
func RegisterBazelPackagePathToNameResolver(f BazelPackagePathToNameResolverFunc) {
registerResolverOnce.Do(func() {
resolver = f
})
}
type BazelPackagePathToNameResolverFunc = func(path string) (name string)
// Guesses a package name and import path using Go debug stack trace information.
//
// It looks at the current goroutine's stack, finds the most recent function call in a `_test.go`
// file, and then guesses the package name and path based on the function name.
//
// This does not respect packages whose import path does not match their defined `package autogold_test`
// statement.
//
// This does not respect packages
func bazelGetPackageNameAndPath(dir string) (name, path string, err error) {
// Guesses an import path based on a function name like:
//
// github.com/hexops/autogold/v2.getPackageNameAndPath
// github.com/hexops/autogold/v2.Expect.func1
//
guessPkgPathFromFuncName := func(funcName string) string {
components := strings.Split(funcName, ".")
pkgPath := []string{}
for _, comp := range components {
pkgPath = append(pkgPath, comp)
if strings.Contains(comp, "/") {
break
}
}
return strings.Join(pkgPath, ".")
}
var (
file string
ok bool
pc uintptr
)
for caller := 1; caller < 10000; caller++ {
pc, file, _, ok = runtime.Caller(caller)
if !ok {
break
}
if !strings.Contains(file, "_test.go") {
continue
}
pkgPath := guessPkgPathFromFuncName(runtime.FuncForPC(pc).Name())
pkgName, _ := bazelPackagePathToName(pkgPath)
return pkgName, pkgPath, nil
}
return "", "", errors.New("unable to guess package name/path due to BAZEL_BAD=true")
}
// Guesses a Go package name based on the last component of a Go package path. e.g.:
//
// github.com/hexops/autogold/v2 -> autogold
// github.com/hexops/autogold -> autogold
// cmd/blobstore/internal/blobstore/blobstore_test_test -> blobstore_test
//
// Note that in the third case, Bazel appears to do some reckless renaming of Go package paths,
// where that package would otherwise have path "github.com/sourcegraph/sourcegraph/cmd/blobstore/internal/blobstore"
// and "package blobstore_test" as its name.
//
// This does not respect packages whose import path does not match their defined `package autogold_test`
// statement.
func bazelPackagePathToName(path string) (string, error) {
// try our best with custom resolver
// if empty, fallback to the best-guess implementation
if resolver != nil {
if n := resolver(path); n != "" {
return n, nil
}
}
components := strings.Split(path, "/")
last := components[len(components)-1]
if !strings.Contains(path, ".") {
// Third case.
return strings.TrimSuffix(last, "_test"), nil
}
if strings.HasPrefix(last, "v") {
if _, err := strconv.ParseUint(last[1:], 10, 32); err == nil {
// Package path has a version suffix, e.g. github.com/hexops/autogold/v2
// and we want the "autogold" component not "v2"
last = components[len(components)-2]
}
}
return last, nil
}