modules.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. /* Copyright 2018 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 repos
  13. import (
  14. "bytes"
  15. "encoding/json"
  16. "io"
  17. "io/ioutil"
  18. "os"
  19. "os/exec"
  20. "path/filepath"
  21. "regexp"
  22. "runtime"
  23. "strings"
  24. "github.com/bazelbuild/bazel-gazelle/internal/label"
  25. )
  26. type module struct {
  27. Path, Version string
  28. Main bool
  29. }
  30. var regexMixedVersioning = regexp.MustCompile(`^(.*?)-([0-9]{14})-([a-fA-F0-9]{12})$`)
  31. func toRepoRule(mod module) Repo {
  32. var tag, commit string
  33. if gr := regexMixedVersioning.FindStringSubmatch(mod.Version); gr != nil {
  34. commit = gr[3]
  35. } else {
  36. tag = strings.TrimSuffix(mod.Version, "+incompatible")
  37. }
  38. return Repo{
  39. Name: label.ImportPathToBazelRepoName(mod.Path),
  40. GoPrefix: mod.Path,
  41. Commit: commit,
  42. Tag: tag,
  43. }
  44. }
  45. func importRepoRulesModules(filename string) (repos []Repo, err error) {
  46. tempDir, err := copyGoModToTemp(filename)
  47. if err != nil {
  48. return nil, err
  49. }
  50. defer os.RemoveAll(tempDir)
  51. data, err := goListModulesFn(tempDir)
  52. if err != nil {
  53. return nil, err
  54. }
  55. dec := json.NewDecoder(bytes.NewReader(data))
  56. for dec.More() {
  57. var mod module
  58. if err := dec.Decode(&mod); err != nil {
  59. return nil, err
  60. }
  61. if mod.Main {
  62. continue
  63. }
  64. repos = append(repos, toRepoRule(mod))
  65. }
  66. return repos, nil
  67. }
  68. // goListModulesFn may be overridden by tests.
  69. var goListModulesFn = goListModules
  70. // goListModules invokes "go list" in a directory containing a go.mod file.
  71. func goListModules(dir string) ([]byte, error) {
  72. goTool := findGoTool()
  73. cmd := exec.Command(goTool, "list", "-m", "-json", "all")
  74. cmd.Stderr = os.Stderr
  75. cmd.Dir = dir
  76. data, err := cmd.Output()
  77. return data, err
  78. }
  79. // copyGoModToTemp copies to given go.mod file to a temporary directory.
  80. // go list tends to mutate go.mod files, but gazelle shouldn't do that.
  81. func copyGoModToTemp(filename string) (tempDir string, err error) {
  82. goModOrig, err := os.Open(filename)
  83. if err != nil {
  84. return "", err
  85. }
  86. defer goModOrig.Close()
  87. tempDir, err = ioutil.TempDir("", "gazelle-temp-gomod")
  88. if err != nil {
  89. return "", err
  90. }
  91. goModCopy, err := os.Create(filepath.Join(tempDir, "go.mod"))
  92. if err != nil {
  93. os.Remove(tempDir)
  94. return "", err
  95. }
  96. defer func() {
  97. if cerr := goModCopy.Close(); err == nil && cerr != nil {
  98. err = cerr
  99. }
  100. }()
  101. _, err = io.Copy(goModCopy, goModOrig)
  102. if err != nil {
  103. os.RemoveAll(tempDir)
  104. return "", err
  105. }
  106. return tempDir, err
  107. }
  108. // findGoTool attempts to locate the go executable. If GOROOT is set, we'll
  109. // prefer the one in there; otherwise, we'll rely on PATH. If the wrapper
  110. // script generated by the gazelle rule is invoked by Bazel, it will set
  111. // GOROOT to the configured SDK. We don't want to rely on the host SDK in
  112. // that situation.
  113. func findGoTool() string {
  114. path := "go" // rely on PATH by default
  115. if goroot, ok := os.LookupEnv("GOROOT"); ok {
  116. path = filepath.Join(goroot, "bin", "go")
  117. }
  118. if runtime.GOOS == "windows" {
  119. path += ".exe"
  120. }
  121. return path
  122. }