1
15
16
17
18
19
20
21
22
23
24
25 package repo
26
27 import (
28 "fmt"
29 "os"
30 "path/filepath"
31 "strings"
32
33 "github.com/bazelbuild/bazel-gazelle/rule"
34 )
35
36 const gazelleFromDirectiveKey = "_gazelle_from_directive"
37
38
39
40
41 func FindExternalRepo(repoRoot, name string) (string, error) {
42
43
44
45
46
47
48 externalPath := strings.Join([]string{repoRoot, "bazel-out", "..", "..", "..", "external", name}, string(os.PathSeparator))
49 cleanPath, err := filepath.EvalSymlinks(externalPath)
50 if err != nil {
51 return "", err
52 }
53 st, err := os.Stat(cleanPath)
54 if err != nil {
55 return "", err
56 }
57 if !st.IsDir() {
58 return "", fmt.Errorf("%s: not a directory", externalPath)
59 }
60 return cleanPath, nil
61 }
62
63 type macroKey struct {
64 file, def string
65 }
66
67 type loader struct {
68 repos []*rule.Rule
69 repoRoot string
70 repoFileMap map[string]*rule.File
71 repoIndexMap map[string]int
72 visited map[macroKey]struct{}
73 }
74
75
76 func IsFromDirective(repo *rule.Rule) bool {
77 b, ok := repo.PrivateAttr(gazelleFromDirectiveKey).(bool)
78 return ok && b
79 }
80
81
82
83
84
85
86 func (l *loader) add(file *rule.File, repo *rule.Rule) {
87 name := repo.Name()
88 if name == "" {
89 return
90 }
91
92 if i, ok := l.repoIndexMap[repo.Name()]; ok {
93 if IsFromDirective(l.repos[i]) && !IsFromDirective(repo) {
94
95 return
96 }
97
98 l.repos[i] = repo
99 } else {
100 l.repos = append(l.repos, repo)
101 l.repoIndexMap[name] = len(l.repos) - 1
102 }
103 l.repoFileMap[name] = file
104 }
105
106
107 func (l *loader) visit(file, function string) bool {
108 if _, ok := l.visited[macroKey{file, function}]; ok {
109 return false
110 }
111 l.visited[macroKey{file, function}] = struct{}{}
112 return true
113 }
114
115
116
117 func ListRepositories(workspace *rule.File) (repos []*rule.Rule, repoFileMap map[string]*rule.File, err error) {
118 l := &loader{
119 repoRoot: filepath.Dir(workspace.Path),
120 repoIndexMap: make(map[string]int),
121 repoFileMap: make(map[string]*rule.File),
122 visited: make(map[macroKey]struct{}),
123 }
124
125 for _, repo := range workspace.Rules {
126 l.add(workspace, repo)
127 }
128 if err := l.loadExtraRepos(workspace); err != nil {
129 return nil, nil, err
130 }
131
132 for _, d := range workspace.Directives {
133 switch d.Key {
134 case "repository_macro":
135 parsed, err := ParseRepositoryMacroDirective(d.Value)
136 if err != nil {
137 return nil, nil, err
138 }
139
140 if err := l.loadRepositoriesFromMacro(parsed); err != nil {
141 return nil, nil, err
142 }
143 }
144 }
145 return l.repos, l.repoFileMap, nil
146 }
147
148 func (l *loader) loadRepositoriesFromMacro(macro *RepoMacro) error {
149 f := filepath.Join(l.repoRoot, macro.Path)
150 if !l.visit(f, macro.DefName) {
151 return nil
152 }
153
154 macroFile, err := rule.LoadMacroFile(f, "", macro.DefName)
155 if err != nil {
156 return fmt.Errorf("failed to load %s in repoRoot %s: %w", f, l.repoRoot, err)
157 }
158 loads := map[string]*rule.Load{}
159 for _, load := range macroFile.Loads {
160 for _, name := range load.Symbols() {
161 loads[name] = load
162 }
163 }
164 for _, rule := range macroFile.Rules {
165
166 if rule.Name() != "" {
167 l.add(macroFile, rule)
168 continue
169 }
170 if !macro.Leveled {
171 continue
172 }
173
174
175
176
177 kind := rule.Kind()
178 load := loads[kind]
179 if load == nil {
180 continue
181 }
182 resolved := loadToMacroDef(load, l.repoRoot, kind)
183
184 if macro.Path == "" {
185 continue
186 }
187
188 if err := l.loadRepositoriesFromMacro(resolved); err != nil {
189 return err
190 }
191 }
192 return l.loadExtraRepos(macroFile)
193 }
194
195
196
197
198
199
200
201
202 func loadToMacroDef(l *rule.Load, repoRoot, defAlias string) *RepoMacro {
203 rel := strings.Replace(filepath.Clean(l.Name()), ":", string(filepath.Separator), 1)
204
205
206 defName := l.Unalias(defAlias)
207 return &RepoMacro{
208 Path: rel,
209 DefName: defName,
210 }
211 }
212
213 func (l *loader) loadExtraRepos(f *rule.File) error {
214 extraRepos, err := parseRepositoryDirectives(f.Directives)
215 if err != nil {
216 return err
217 }
218 for _, repo := range extraRepos {
219 l.add(f, repo)
220 }
221 return nil
222 }
223
224 func parseRepositoryDirectives(directives []rule.Directive) (repos []*rule.Rule, err error) {
225 for _, d := range directives {
226 switch d.Key {
227 case "repository":
228 vals := strings.Fields(d.Value)
229 if len(vals) < 2 {
230 return nil, fmt.Errorf("failure parsing repository: %s, expected repository kind and attributes", d.Value)
231 }
232 kind := vals[0]
233 r := rule.NewRule(kind, "")
234 r.SetPrivateAttr(gazelleFromDirectiveKey, true)
235 for _, val := range vals[1:] {
236 kv := strings.SplitN(val, "=", 2)
237 if len(kv) != 2 {
238 return nil, fmt.Errorf("failure parsing repository: %s, expected format for attributes is attr1_name=attr1_value", d.Value)
239 }
240 r.SetAttr(kv[0], kv[1])
241 }
242 if r.Name() == "" {
243 return nil, fmt.Errorf("failure parsing repository: %s, expected a name attribute for the given repository", d.Value)
244 }
245 repos = append(repos, r)
246 }
247 }
248 return repos, nil
249 }
250
251 type RepoMacro struct {
252 Path string
253 DefName string
254 Leveled bool
255 }
256
257
258
259
260 func ParseRepositoryMacroDirective(directive string) (*RepoMacro, error) {
261 vals := strings.Split(directive, "%")
262 if len(vals) != 2 {
263 return nil, fmt.Errorf("Failure parsing repository_macro: %s, expected format is macroFile%%defName", directive)
264 }
265 f := vals[0]
266 if strings.HasPrefix(f, "..") {
267 return nil, fmt.Errorf("Failure parsing repository_macro: %s, macro file path %s should not start with \"..\"", directive, f)
268 }
269 return &RepoMacro{
270 Path: strings.TrimPrefix(f, "+"),
271 DefName: vals[1],
272 Leveled: strings.HasPrefix(f, "+"),
273 }, nil
274 }
275
View as plain text