blob: 5dadad5d455c4d04dcf298b37ec760fa65b90b28 [file] [log] [blame]
/*
wtool augments your bazel WORKSPACE file with go_repository entries
Example Usage:
wtool com_github_golang_glog com_google_cloud_go
will add 2 go_repository rules to your WORKSPACE
by converting com_github_golang_glog -> github.com/golang/glog
and so forth and then doing a 'git ls-remote' to get
the latest commit.
If wtool cannot figure out the bazel -> Go mapping, try
Other Usage:
wtool -asis github.com/golang/glog
which takes an importpath, and computes the bazel name + ls-remote as above.
*/
package main
import (
"bufio"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
bf "github.com/bazelbuild/buildtools/build"
"github.com/bazelbuild/rules_go/go/tools/gazelle/rules"
"github.com/bazelbuild/rules_go/go/tools/gazelle/wspace"
"golang.org/x/tools/go/vcs"
)
var (
asis = flag.Bool("asis", false, "if true, leave the import names as-is (by default they are treated as bazel converted names like org_golang_x_net")
verbose = flag.Bool("verbose", false, "if true, logging extra information")
knownPaths = map[string]string{
"org_golang_google": "google.golang.org/",
"com_google_cloud": "cloud.google.com/",
}
)
func main() {
flag.Parse()
if err := run(flag.Args()); err != nil {
log.Fatal(err)
}
}
func run(args []string) error {
cwd, err := os.Getwd()
if err != nil {
return err
}
w, err := wspace.Find(cwd)
if err != nil {
return err
}
p := filepath.Join(w, "WORKSPACE")
b, err := ioutil.ReadFile(p)
if err != nil {
return err
}
f, err := bf.Parse(p, b)
if err != nil {
return err
}
for _, arg := range args {
imp, err := findImport(arg)
if err != nil {
return err
}
// TODO(pmbethe09): ignore or maybe update sha1 if already defined in workspace.
f.Stmt = append(f.Stmt, imp)
}
updateLoad(f)
bf.Rewrite(f, nil)
return ioutil.WriteFile(f.Path, bf.Format(f), 0644)
}
func nameAndImportpath(name string) (string, string, error) {
if *asis {
return rules.ImportPathToBazelRepoName(name), name, nil
}
s := strings.Split(name, "_")
if len(s) < 4 {
return "", "", fmt.Errorf("workspace names must be 4-parts or longer: %q", name)
}
rest := strings.Join(s[3:], "-")
for k, v := range knownPaths {
if strings.HasPrefix(name, k) {
return name, v + rest, nil
}
}
return name, strings.Join([]string{s[1] + "." + s[0], s[2], rest}, "/"), nil
}
func findImport(nameIn string) (bf.Expr, error) {
name, importpath, err := nameAndImportpath(nameIn)
if err != nil {
return nil, err
}
if *verbose {
log.Printf(importpath)
}
r, err := vcs.RepoRootForImportPath(importpath, false)
if err != nil {
return nil, err
}
if r.VCS.Cmd != "git" {
return nil, fmt.Errorf("only git supported, not %q", r.VCS.Cmd)
}
// TODO(pmbethe09): allow ref to be provided, e.g. com_github_golang_glog:mybranch
commit, err := lsRemote(r.Repo)
if err != nil {
return nil, err
}
return &bf.CallExpr{
X: &bf.LiteralExpr{Token: "go_repository"},
List: []bf.Expr{
attr("name", name),
attr("importpath", importpath),
attr("commit", commit),
},
}, nil
}
func attr(key, val string) *bf.BinaryExpr {
return &bf.BinaryExpr{
X: &bf.LiteralExpr{Token: key},
Op: "=",
Y: &bf.StringExpr{Value: val},
}
}
func lsRemote(repo string) (string, error) {
cmd := exec.Command("git", "ls-remote", repo, "HEAD")
r, err := cmd.StdoutPipe()
if err != nil {
return "", err
}
if err := cmd.Start(); err != nil {
return "", err
}
b := bufio.NewScanner(r)
if !b.Scan() {
if err := b.Err(); err != nil {
return "", err
}
return "", fmt.Errorf("nothing returned from ls-remote %q", repo)
}
if *verbose {
log.Printf(b.Text())
}
go cmd.Wait()
return strings.Split(b.Text(), "\t")[0], nil
}
func updateLoad(f *bf.File) {
for _, s := range f.Stmt {
call, ok := s.(*bf.CallExpr)
if !ok || len(call.List) == 0 {
continue
}
if x, ok := call.X.(*bf.LiteralExpr); !ok || x.Token != "load" {
continue
}
if label, ok := call.List[0].(*bf.StringExpr); !ok || label.Value != "@io_bazel_rules_go//go:def.bzl" {
continue
}
haveGoRepository := false
for _, arg := range call.List[1:] {
if sym, ok := arg.(*bf.StringExpr); ok && sym.Value == "go_repository" {
haveGoRepository = true
break
}
}
if !haveGoRepository {
call.List = append(call.List, &bf.StringExpr{Value: "go_repository"})
}
}
}