/* Copyright 2018 The Bazel Authors. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package repos import ( "bytes" "encoding/json" "io" "io/ioutil" "os" "os/exec" "path/filepath" "regexp" "runtime" "strings" "github.com/bazelbuild/bazel-gazelle/internal/label" ) type module struct { Path, Version string Main bool } var regexMixedVersioning = regexp.MustCompile(`^(.*?)-([0-9]{14})-([a-fA-F0-9]{12})$`) func toRepoRule(mod module) Repo { var tag, commit string if gr := regexMixedVersioning.FindStringSubmatch(mod.Version); gr != nil { commit = gr[3] } else { tag = strings.TrimSuffix(mod.Version, "+incompatible") } return Repo{ Name: label.ImportPathToBazelRepoName(mod.Path), GoPrefix: mod.Path, Commit: commit, Tag: tag, } } func importRepoRulesModules(filename string) (repos []Repo, err error) { tempDir, err := copyGoModToTemp(filename) if err != nil { return nil, err } defer os.RemoveAll(tempDir) data, err := goListModulesFn(tempDir) if err != nil { return nil, err } dec := json.NewDecoder(bytes.NewReader(data)) for dec.More() { var mod module if err := dec.Decode(&mod); err != nil { return nil, err } if mod.Main { continue } repos = append(repos, toRepoRule(mod)) } return repos, nil } // goListModulesFn may be overridden by tests. var goListModulesFn = goListModules // goListModules invokes "go list" in a directory containing a go.mod file. func goListModules(dir string) ([]byte, error) { goTool := findGoTool() cmd := exec.Command(goTool, "list", "-m", "-json", "all") cmd.Stderr = os.Stderr cmd.Dir = dir data, err := cmd.Output() return data, err } // copyGoModToTemp copies to given go.mod file to a temporary directory. // go list tends to mutate go.mod files, but gazelle shouldn't do that. func copyGoModToTemp(filename string) (tempDir string, err error) { goModOrig, err := os.Open(filename) if err != nil { return "", err } defer goModOrig.Close() tempDir, err = ioutil.TempDir("", "gazelle-temp-gomod") if err != nil { return "", err } goModCopy, err := os.Create(filepath.Join(tempDir, "go.mod")) if err != nil { os.Remove(tempDir) return "", err } defer func() { if cerr := goModCopy.Close(); err == nil && cerr != nil { err = cerr } }() _, err = io.Copy(goModCopy, goModOrig) if err != nil { os.RemoveAll(tempDir) return "", err } return tempDir, err } // findGoTool attempts to locate the go executable. If GOROOT is set, we'll // prefer the one in there; otherwise, we'll rely on PATH. If the wrapper // script generated by the gazelle rule is invoked by Bazel, it will set // GOROOT to the configured SDK. We don't want to rely on the host SDK in // that situation. func findGoTool() string { path := "go" // rely on PATH by default if goroot, ok := os.LookupEnv("GOROOT"); ok { path = filepath.Join(goroot, "bin", "go") } if runtime.GOOS == "windows" { path += ".exe" } return path }