1
16
17
18 package bzlmod
19
20 import (
21 "strings"
22
23 "github.com/bazelbuild/buildtools/build"
24 )
25
26
27
28
29
30 func Proxies(f *build.File, rawExtBzlFile string, extName string, dev bool) []string {
31 apparentModuleName := getApparentModuleName(f)
32 extBzlFile := normalizeLabelString(rawExtBzlFile, apparentModuleName)
33
34 var proxies []string
35 for _, stmt := range f.Stmt {
36 proxy, rawBzlFile, name, isDev, isIsolated := parseUseExtension(stmt)
37 if proxy == "" || isDev != dev || isIsolated {
38 continue
39 }
40 bzlFile := normalizeLabelString(rawBzlFile, apparentModuleName)
41 if bzlFile == extBzlFile && name == extName {
42 proxies = append(proxies, proxy)
43 }
44 }
45
46 return proxies
47 }
48
49
50
51
52
53
54
55
56 func AllProxies(f *build.File, proxy string) []string {
57 for _, stmt := range f.Stmt {
58 proxyCandidate, rawBzlFile, name, isDev, isIsolated := parseUseExtension(stmt)
59 if proxyCandidate == proxy {
60 if isIsolated {
61 return []string{proxy}
62 }
63 return Proxies(f, rawBzlFile, name, isDev)
64 }
65 }
66 return nil
67 }
68
69
70 func UseRepos(f *build.File, proxies []string) []*build.CallExpr {
71 proxiesSet := make(map[string]struct{})
72 for _, p := range proxies {
73 proxiesSet[p] = struct{}{}
74 }
75
76 var useRepos []*build.CallExpr
77 for _, stmt := range f.Stmt {
78 if _, ok := stmt.(*build.CallExpr); !ok {
79 continue
80 }
81 call := stmt.(*build.CallExpr)
82 if _, ok := call.X.(*build.Ident); !ok {
83 continue
84 }
85 if call.X.(*build.Ident).Name != "use_repo" || len(call.List) < 1 {
86 continue
87 }
88 proxy, ok := call.List[0].(*build.Ident)
89 if !ok {
90 continue
91 }
92 if _, ok := proxiesSet[proxy.Name]; !ok {
93 continue
94 }
95 useRepos = append(useRepos, call)
96 }
97
98 return useRepos
99 }
100
101
102
103 func NewUseRepo(f *build.File, proxies []string) (*build.File, *build.CallExpr) {
104 lastUsage, proxy := lastProxyUsage(f, proxies)
105 if lastUsage == -1 {
106 return f, nil
107 }
108
109 useRepo := &build.CallExpr{
110 X: &build.Ident{Name: "use_repo"},
111 List: []build.Expr{
112 &build.Ident{Name: proxy},
113 },
114 }
115 stmt := append(f.Stmt[:lastUsage+1], append([]build.Expr{useRepo}, f.Stmt[lastUsage+1:]...)...)
116
117 return &build.File{Path: f.Path, Comments: f.Comments, Stmt: stmt, Type: build.TypeModule}, useRepo
118 }
119
120
121
122
123
124 func AddRepoUsages(useRepos []*build.CallExpr, repos ...string) {
125 if len(repos) == 0 {
126 return
127 }
128 if len(useRepos) == 0 {
129 panic("useRepos must not be empty")
130 }
131
132 seen := make(map[string]struct{})
133 for _, useRepo := range useRepos {
134 if len(useRepo.List) == 0 {
135
136 continue
137 }
138 for _, arg := range useRepo.List[1:] {
139 seen[repoFromUseRepoArg(arg)] = struct{}{}
140 }
141 }
142
143 lastUseRepo := getLastUseRepo(useRepos)
144 for _, repo := range repos {
145 if _, ok := seen[repo]; ok {
146 continue
147 }
148
149
150 lastUseRepo.List = append(lastUseRepo.List, &build.StringExpr{Value: repo})
151 }
152 }
153
154
155
156
157 func RemoveRepoUsages(useRepos []*build.CallExpr, repos ...string) {
158 if len(useRepos) == 0 || len(repos) == 0 {
159 return
160 }
161
162 toRemove := make(map[string]struct{})
163 for _, repo := range repos {
164 toRemove[repo] = struct{}{}
165 }
166
167 for _, useRepo := range useRepos {
168 if len(useRepo.List) == 0 {
169
170 continue
171 }
172 var args []build.Expr
173
174 for _, arg := range useRepo.List[1:] {
175 repo := repoFromUseRepoArg(arg)
176 if _, remove := toRemove[repo]; !remove {
177 args = append(args, arg)
178 }
179 }
180 useRepo.List = append(useRepo.List[:1], args...)
181 }
182 }
183
184 func getLastUseRepo(useRepos []*build.CallExpr) *build.CallExpr {
185 var lastUseRepo *build.CallExpr
186 for _, useRepo := range useRepos {
187 if lastUseRepo == nil || useRepo.Pos.Byte > lastUseRepo.Pos.Byte {
188 lastUseRepo = useRepo
189 }
190 }
191 return lastUseRepo
192 }
193
194
195
196 func repoFromUseRepoArg(arg build.Expr) string {
197 switch arg := arg.(type) {
198 case *build.StringExpr:
199
200 return arg.Value
201 case *build.AssignExpr:
202
203 if repo, ok := arg.RHS.(*build.StringExpr); ok {
204 return repo.Value
205 }
206 }
207 return ""
208 }
209
210
211
212 func getApparentModuleName(f *build.File) string {
213 apparentName := ""
214
215 for _, module := range f.Rules("module") {
216 if repoName := module.AttrString("repo_name"); repoName != "" {
217 apparentName = repoName
218 } else if name := module.AttrString("name"); name != "" {
219 apparentName = name
220 }
221 }
222
223 return apparentName
224 }
225
226
227 func normalizeLabelString(rawLabel, apparentModuleName string) string {
228
229
230
231 if strings.HasPrefix(rawLabel, "//") {
232
233 return "@" + apparentModuleName + rawLabel
234 } else if strings.HasPrefix(rawLabel, "@//") {
235
236
237 return "@" + apparentModuleName + rawLabel[1:]
238 } else {
239 return rawLabel
240 }
241 }
242
243 func parseUseExtension(stmt build.Expr) (proxy string, bzlFile string, name string, dev bool, isolate bool) {
244 assign, ok := stmt.(*build.AssignExpr)
245 if !ok {
246 return
247 }
248 if _, ok = assign.LHS.(*build.Ident); !ok {
249 return
250 }
251 if _, ok = assign.RHS.(*build.CallExpr); !ok {
252 return
253 }
254 call := assign.RHS.(*build.CallExpr)
255 if call.X.(*build.Ident).Name != "use_extension" {
256 return
257 }
258 if len(call.List) < 2 {
259
260 return
261 }
262 bzlFileExpr, ok := call.List[0].(*build.StringExpr)
263 if !ok {
264 return
265 }
266 nameExpr, ok := call.List[1].(*build.StringExpr)
267 if !ok {
268 return
269 }
270
271 if len(call.List) > 2 {
272 for _, arg := range call.List[2:] {
273 dev = dev || parseBooleanKeywordArg(arg, "dev_dependency")
274 isolate = isolate || parseBooleanKeywordArg(arg, "isolate")
275 }
276 }
277 return assign.LHS.(*build.Ident).Name, bzlFileExpr.Value, nameExpr.Value, dev, isolate
278 }
279
280
281
282 func parseBooleanKeywordArg(arg build.Expr, name string) bool {
283 keywordArg, ok := arg.(*build.AssignExpr)
284 if !ok {
285 return false
286 }
287 argName, ok := keywordArg.LHS.(*build.Ident)
288 if !ok || argName.Name != name {
289 return false
290 }
291 argValue, ok := keywordArg.RHS.(*build.Ident)
292
293
294
295 if ok && argValue.Name == "False" {
296 return false
297 }
298 return true
299 }
300
301 func parseTag(stmt build.Expr) string {
302 call, ok := stmt.(*build.CallExpr)
303 if !ok {
304 return ""
305 }
306 if _, ok := call.X.(*build.DotExpr); !ok {
307 return ""
308 }
309 dot := call.X.(*build.DotExpr)
310 if _, ok := dot.X.(*build.Ident); !ok {
311 return ""
312 }
313 return dot.X.(*build.Ident).Name
314 }
315
316
317
318
319 func lastProxyUsage(f *build.File, proxies []string) (lastUsage int, lastProxy string) {
320 proxiesSet := make(map[string]struct{})
321 for _, p := range proxies {
322 proxiesSet[p] = struct{}{}
323 }
324
325 lastUsage = -1
326 for i, stmt := range f.Stmt {
327 proxy, _, _, _, _ := parseUseExtension(stmt)
328 if proxy != "" {
329 _, isUsage := proxiesSet[proxy]
330 if isUsage {
331 lastUsage = i
332 lastProxy = proxy
333 continue
334 }
335 }
336
337 proxy = parseTag(stmt)
338 if proxy != "" {
339 _, isUsage := proxiesSet[proxy]
340 if isUsage {
341 lastUsage = i
342 lastProxy = proxy
343 continue
344 }
345 }
346 }
347
348 return lastUsage, lastProxy
349 }
350
View as plain text