repo.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. /* Copyright 2017 The Bazel Authors. All rights reserved.
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. // Package repo provides functionality for managing Go repository rules.
  13. //
  14. // UNSTABLE: The exported APIs in this package may change. In the future,
  15. // language extensions should implement an interface for repository
  16. // rule management. The update-repos command will call interface methods,
  17. // and most if this package's functionality will move to language/go.
  18. // Moving this package to an internal directory would break existing
  19. // extensions, since RemoteCache is referenced through the resolve.Resolver
  20. // interface, which extensions are required to implement.
  21. package repo
  22. import (
  23. "fmt"
  24. "os"
  25. "path/filepath"
  26. "strings"
  27. "github.com/bazelbuild/bazel-gazelle/rule"
  28. )
  29. type byRuleName []*rule.Rule
  30. func (s byRuleName) Len() int { return len(s) }
  31. func (s byRuleName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
  32. func (s byRuleName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  33. // FindExternalRepo attempts to locate the directory where Bazel has fetched
  34. // the external repository with the given name. An error is returned if the
  35. // repository directory cannot be located.
  36. func FindExternalRepo(repoRoot, name string) (string, error) {
  37. // See https://docs.bazel.build/versions/master/output_directories.html
  38. // for documentation on Bazel directory layout.
  39. // We expect the bazel-out symlink in the workspace root directory to point to
  40. // <output-base>/execroot/<workspace-name>/bazel-out
  41. // We expect the external repository to be checked out at
  42. // <output-base>/external/<name>
  43. // Note that users can change the prefix for most of the Bazel symlinks with
  44. // --symlink_prefix, but this does not include bazel-out.
  45. externalPath := strings.Join([]string{repoRoot, "bazel-out", "..", "..", "..", "external", name}, string(os.PathSeparator))
  46. cleanPath, err := filepath.EvalSymlinks(externalPath)
  47. if err != nil {
  48. return "", err
  49. }
  50. st, err := os.Stat(cleanPath)
  51. if err != nil {
  52. return "", err
  53. }
  54. if !st.IsDir() {
  55. return "", fmt.Errorf("%s: not a directory", externalPath)
  56. }
  57. return cleanPath, nil
  58. }
  59. // ListRepositories extracts metadata about repositories declared in a
  60. // file.
  61. func ListRepositories(workspace *rule.File) (repos []*rule.Rule, repoFileMap map[string]*rule.File, err error) {
  62. repoIndexMap := make(map[string]int)
  63. repoFileMap = make(map[string]*rule.File)
  64. for _, repo := range workspace.Rules {
  65. if name := repo.Name(); name != "" {
  66. repos = append(repos, repo)
  67. repoFileMap[name] = workspace
  68. repoIndexMap[name] = len(repos) - 1
  69. }
  70. }
  71. extraRepos, err := parseRepositoryDirectives(workspace.Directives)
  72. if err != nil {
  73. return nil, nil, err
  74. }
  75. for _, repo := range extraRepos {
  76. if i, ok := repoIndexMap[repo.Name()]; ok {
  77. repos[i] = repo
  78. } else {
  79. repos = append(repos, repo)
  80. }
  81. repoFileMap[repo.Name()] = workspace
  82. }
  83. for _, d := range workspace.Directives {
  84. switch d.Key {
  85. case "repository_macro":
  86. f, defName, err := parseRepositoryMacroDirective(d.Value)
  87. if err != nil {
  88. return nil, nil, err
  89. }
  90. f = filepath.Join(filepath.Dir(workspace.Path), filepath.Clean(f))
  91. macroFile, err := rule.LoadMacroFile(f, "", defName)
  92. if err != nil {
  93. return nil, nil, err
  94. }
  95. for _, repo := range macroFile.Rules {
  96. if name := repo.Name(); name != "" {
  97. repos = append(repos, repo)
  98. repoFileMap[name] = macroFile
  99. repoIndexMap[name] = len(repos) - 1
  100. }
  101. }
  102. extraRepos, err = parseRepositoryDirectives(macroFile.Directives)
  103. if err != nil {
  104. return nil, nil, err
  105. }
  106. for _, repo := range extraRepos {
  107. if i, ok := repoIndexMap[repo.Name()]; ok {
  108. repos[i] = repo
  109. } else {
  110. repos = append(repos, repo)
  111. }
  112. repoFileMap[repo.Name()] = macroFile
  113. }
  114. }
  115. }
  116. return repos, repoFileMap, nil
  117. }
  118. func parseRepositoryDirectives(directives []rule.Directive) (repos []*rule.Rule, err error) {
  119. for _, d := range directives {
  120. switch d.Key {
  121. case "repository":
  122. vals := strings.Fields(d.Value)
  123. if len(vals) < 2 {
  124. return nil, fmt.Errorf("failure parsing repository: %s, expected repository kind and attributes", d.Value)
  125. }
  126. kind := vals[0]
  127. r := rule.NewRule(kind, "")
  128. for _, val := range vals[1:] {
  129. kv := strings.SplitN(val, "=", 2)
  130. if len(kv) != 2 {
  131. return nil, fmt.Errorf("failure parsing repository: %s, expected format for attributes is attr1_name=attr1_value", d.Value)
  132. }
  133. r.SetAttr(kv[0], kv[1])
  134. }
  135. if r.Name() == "" {
  136. return nil, fmt.Errorf("failure parsing repository: %s, expected a name attribute for the given repository", d.Value)
  137. }
  138. repos = append(repos, r)
  139. }
  140. }
  141. return repos, nil
  142. }
  143. func parseRepositoryMacroDirective(directive string) (string, string, error) {
  144. vals := strings.Split(directive, "%")
  145. if len(vals) != 2 {
  146. return "", "", fmt.Errorf("Failure parsing repository_macro: %s, expected format is macroFile%%defName", directive)
  147. }
  148. f := vals[0]
  149. if strings.HasPrefix(f, "..") {
  150. return "", "", fmt.Errorf("Failure parsing repository_macro: %s, macro file path %s should not start with \"..\"", directive, f)
  151. }
  152. return f, vals[1], nil
  153. }