-
Notifications
You must be signed in to change notification settings - Fork 4
/
deps.go
236 lines (196 loc) · 6.71 KB
/
deps.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package generate
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/please-build/buildtools/build"
"github.com/please-build/puku/config"
"github.com/please-build/puku/fs"
"github.com/please-build/puku/kinds"
"github.com/please-build/puku/knownimports"
)
// resolveImport resolves an import path to a build target. It will return an empty string if the import is for a pkg in
// the go sdk. Otherwise, it will return the build target for that dependency, or an error if it can't be resolved. If
// the target can be resolved to a module that isn't currently added to this project, it will return the build target,
// and record the new module in `u.newModules`. These should later be written to the build graph.
func (u *updater) resolveImport(conf *config.Config, i string) (string, error) {
if t, ok := u.resolvedImports[i]; ok {
return t, nil
}
if t := conf.GetKnownTarget(i); t != "" {
return t, nil
}
t, err := u.reallyResolveImport(conf, i)
if err == nil {
u.resolvedImports[i] = t
}
return t, err
}
// reallyResolveImport actually does the resolution of an import path to a build target.
func (u *updater) reallyResolveImport(conf *config.Config, i string) (string, error) {
if knownimports.IsInGoRoot(i) {
return "", nil
}
if t := u.installs.Get(i); t != "" {
return t, nil
}
thirdPartyDir := conf.GetThirdPartyDir()
// Check to see if the target exists in the current repo
if fs.IsSubdir(u.plzConf.ImportPath(), i) || u.plzConf.ImportPath() == "" {
t, err := u.localDep(i)
if err != nil {
return "", err
}
if t != "" {
return t, nil
}
// The above isSubdir check only checks the import path. Modules can have import paths that contain the
// current module, so we should carry on here in case we can resolve this to a third party module
}
t := depTarget(u.modules, i, thirdPartyDir)
if t != "" {
return t, nil
}
// If we're using go_module, we can't automatically add new modules to the graph so we should give up here.
if u.usingGoModule {
return "", fmt.Errorf("module not found")
}
log.Infof("Resolving module for %v...", i)
// Otherwise try and resolve it to a new dep via the module proxy. We assume the module will contain the package.
// Please will error out in a reasonable way if it doesn't.
// TODO it would be more correct to download the module and check it actually contains the package
mod, err := u.proxy.ResolveModuleForPackage(i)
if err != nil {
return "", err
}
log.Infof("Resolved to %v... done", mod.Module)
// If the package belongs to this module, we should have found this package when resolving local imports above. We
// don't want to resolve this like a third party module, so we should return an error here.
if mod.Module == u.plzConf.ImportPath() {
return "", fmt.Errorf("can't find import %q", i)
}
u.newModules = append(u.newModules, mod)
u.modules = append(u.modules, mod.Module)
// TODO we can probably shortcut this and assume the target is in the above module
t = depTarget(u.modules, i, thirdPartyDir)
if t != "" {
return t, nil
}
return "", fmt.Errorf("module not found")
}
// isInScope returns true when the given path is in scope of the current run i.e. if we are going to format the BUILD
// file there.
func (u *updater) isInScope(path string) bool {
for _, p := range u.paths {
if p == path {
return true
}
}
return false
}
// localDep finds a dependency local to this repository, checking the BUILD file for a go_library target. Returns an
// empty string when no target is found.
func (u *updater) localDep(importPath string) (string, error) {
path := strings.Trim(strings.TrimPrefix(importPath, u.plzConf.ImportPath()), "/")
// If we're using GOPATH based resolution, we don't have a prefix to base whether a path is package local or not. In
// this case, we need to check if the directory exists. If it doesn't it's not a local import.
if _, err := os.Lstat(path); os.IsNotExist(err) {
return "", nil
}
file, err := u.graph.LoadFile(path)
if err != nil {
return "", fmt.Errorf("failed to parse BUILD files in %v: %v", path, err)
}
conf, err := config.ReadConfig(path)
if err != nil {
return "", err
}
var libTargets []*build.Rule
for _, rule := range file.Rules("") {
kind := conf.GetKind(rule.Kind())
if kind == nil {
continue
}
if kind.Type == kinds.Lib {
libTargets = append(libTargets, rule)
}
}
// If we can't find the lib target, and the target package is in scope for us to potentially generate it, check if
// we are going to generate it.
if len(libTargets) != 0 {
return BuildTarget(libTargets[0].Name(), path, ""), nil
}
if !u.isInScope(importPath) {
return "", fmt.Errorf("resolved %v to a local package, but no library target was found and it's not in scope to generate the target", importPath)
}
files, err := ImportDir(path)
if err != nil {
if os.IsNotExist(err) {
return "", nil
}
return "", fmt.Errorf("failed to import %v: %v", path, err)
}
// If there are any non-test sources, then we will generate a go_library here later on. Return that target name.
for _, f := range files {
if !f.IsTest() {
return BuildTarget(filepath.Base(importPath), path, ""), nil
}
}
return "", nil
}
func depTarget(modules []string, importPath, thirdPartyFolder string) string {
module := moduleForPackage(modules, importPath)
if module == "" {
// If we can't find this import, we can return nothing and the build rule will fail at build time reporting a
// sensible error. It may also be an import from the go SDK which is fine.
return ""
}
packageName := strings.TrimPrefix(strings.TrimPrefix(importPath, module), "/")
return SubrepoTarget(module, thirdPartyFolder, packageName)
}
func moduleForPackage(modules []string, importPath string) string {
module := ""
for _, mod := range modules {
ok := fs.IsSubdir(mod, importPath)
if ok && len(mod) > len(module) {
module = mod
}
}
return module
}
func SubrepoTarget(module, thirdPartyFolder, packageName string) string {
subrepoName := SubrepoName(module, thirdPartyFolder)
name := filepath.Base(packageName)
if packageName == "" {
name = filepath.Base(module)
}
return BuildTarget(name, packageName, subrepoName)
}
func SubrepoName(module, thirdPartyFolder string) string {
return filepath.Join(thirdPartyFolder, strings.ReplaceAll(module, "/", "_"))
}
func BuildTarget(name, pkgDir, subrepo string) string {
bs := new(strings.Builder)
if subrepo != "" {
bs.WriteString("///")
bs.WriteString(subrepo)
}
if pkgDir != "" || subrepo != "" {
bs.WriteString("//")
}
if pkgDir == "." {
pkgDir = ""
}
if pkgDir != "" {
bs.WriteString(pkgDir)
if filepath.Base(pkgDir) != name {
bs.WriteString(":")
bs.WriteString(name)
}
} else {
bs.WriteString(":")
bs.WriteString(name)
}
return bs.String()
}