blob: fa775405716ebee68444c7af885f1d1ed81ff7c4 [file] [log] [blame]
/* Copyright 2016 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.
*/
// Command gazelle is a BUILD file generator for Go projects.
// See "gazelle --help" for more details.
package main
import (
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
bf "github.com/bazelbuild/buildtools/build"
"github.com/bazelbuild/rules_go/go/tools/gazelle/config"
"github.com/bazelbuild/rules_go/go/tools/gazelle/merger"
"github.com/bazelbuild/rules_go/go/tools/gazelle/packages"
"github.com/bazelbuild/rules_go/go/tools/gazelle/rules"
"github.com/bazelbuild/rules_go/go/tools/gazelle/wspace"
)
type emitFunc func(*config.Config, *bf.File) error
var modeFromName = map[string]emitFunc{
"print": printFile,
"fix": fixFile,
"diff": diffFile,
}
func run(c *config.Config, emit emitFunc) {
g := rules.NewGenerator(c)
shouldProcessRoot := false
didProcessRoot := false
for _, dir := range c.Dirs {
if c.RepoRoot == dir {
shouldProcessRoot = true
}
packages.Walk(c, dir, func(pkg *packages.Package, oldFile *bf.File) {
if pkg.Rel == "" {
didProcessRoot = true
}
processPackage(c, g, emit, pkg, oldFile)
})
}
if shouldProcessRoot && !didProcessRoot {
// We did not process a package at the repository root. We need to put
// a go_prefix rule there, even if there are no .go files in that directory.
pkg := &packages.Package{Dir: c.RepoRoot}
var oldFile *bf.File
var oldData []byte
oldPath, err := findBuildFile(c, c.RepoRoot)
if os.IsNotExist(err) {
goto processRoot
}
if err != nil {
log.Print(err)
return
}
oldData, err = ioutil.ReadFile(oldPath)
if err != nil {
log.Print(err)
return
}
oldFile, err = bf.Parse(oldPath, oldData)
if err != nil {
log.Print(err)
return
}
processRoot:
processPackage(c, g, emit, pkg, oldFile)
}
}
func processPackage(c *config.Config, g rules.Generator, emit emitFunc, pkg *packages.Package, oldFile *bf.File) {
genFile := g.Generate(pkg)
if oldFile == nil {
// No existing file, so no merge required.
bf.Rewrite(genFile, nil) // have buildifier 'format' our rules.
if err := emit(c, genFile); err != nil {
log.Print(err)
}
return
}
// Existing file, so merge and replace the old one.
mergedFile := merger.MergeWithExisting(genFile, oldFile)
bf.Rewrite(mergedFile, nil) // have buildifier 'format' our rules.
if err := emit(c, mergedFile); err != nil {
log.Print(err)
return
}
}
func usage(fs *flag.FlagSet) {
fmt.Fprintln(os.Stderr, `usage: gazelle [flags...] [package-dirs...]
Gazelle is a BUILD file generator for Go projects.
Currently its primary usage is to generate BUILD files for external dependencies
in a go_repository rule.
You can still use Gazelle for other purposes, but its interface can change without
notice.
It takes a list of paths to Go package directories [defaults to . if none given].
It recursively traverses its subpackages.
All the directories must be under the directory specified in -repo_root.
[if -repo_root is not given, gazelle searches $pwd and up for the WORKSPACE file]
There are several modes of gazelle.
In print mode, gazelle prints reconciled BUILD files to stdout.
In fix mode, gazelle creates BUILD files or updates existing ones.
In diff mode, gazelle shows diff.
FLAGS:
`)
fs.PrintDefaults()
}
func main() {
log.SetPrefix("gazelle: ")
log.SetFlags(0) // don't print timestamps
c, emit, err := newConfiguration(os.Args[1:])
if err != nil {
log.Fatal(err)
}
run(c, emit)
}
func newConfiguration(args []string) (*config.Config, emitFunc, error) {
fs := flag.NewFlagSet("gazelle", flag.ContinueOnError)
// Flag will call this on any parse error. Don't print usage unless
// -h or -help were passed explicitly.
fs.Usage = func() {}
buildFileName := fs.String("build_file_name", "BUILD.bazel,BUILD", "comma-separated list of valid build file names.\nThe first element of the list is the name of output build files to generate.")
buildTags := fs.String("build_tags", "", "comma-separated list of build tags. If not specified, Gazelle will not\n\tfilter sources with build constraints.")
external := fs.String("external", "external", "external: resolve external packages with go_repository\n\tvendored: resolve external packages as packages in vendor/")
goPrefix := fs.String("go_prefix", "", "go_prefix of the target workspace")
repoRoot := fs.String("repo_root", "", "path to a directory which corresponds to go_prefix, otherwise gazelle searches for it.")
mode := fs.String("mode", "fix", "print: prints all of the updated BUILD files\n\tfix: rewrites all of the BUILD files in place\n\tdiff: computes the rewrite but then just does a diff")
if err := fs.Parse(args); err != nil {
if err == flag.ErrHelp {
usage(fs)
os.Exit(0)
}
// flag already prints the error; don't print it again.
log.Fatal("Try -help for more information.")
}
var c config.Config
var err error
c.Dirs = flag.Args()
if len(c.Dirs) == 0 {
c.Dirs = []string{"."}
}
for i := range c.Dirs {
c.Dirs[i], err = filepath.Abs(c.Dirs[i])
if err != nil {
return nil, nil, err
}
}
if *repoRoot != "" {
c.RepoRoot = *repoRoot
} else if len(c.Dirs) == 1 {
c.RepoRoot, err = wspace.Find(c.Dirs[0])
if err != nil {
return nil, nil, fmt.Errorf("-repo_root not specified, and WORKSPACE cannot be found: %v", err)
}
} else {
cwd, err := filepath.Abs(".")
if err != nil {
return nil, nil, err
}
c.RepoRoot, err = wspace.Find(cwd)
if err != nil {
return nil, nil, fmt.Errorf("-repo_root not specified, and WORKSPACE cannot be found: %v", err)
}
}
for _, dir := range c.Dirs {
if !isDescendingDir(dir, c.RepoRoot) {
return nil, nil, fmt.Errorf("dir %q is not a subdirectory of repo root %q", dir, c.RepoRoot)
}
}
c.ValidBuildFileNames = strings.Split(*buildFileName, ",")
if len(c.ValidBuildFileNames) == 0 {
return nil, nil, fmt.Errorf("no valid build file names specified")
}
c.GenericTags = make(config.BuildTags)
for _, t := range strings.Split(*buildTags, ",") {
if strings.HasPrefix(t, "!") {
return nil, nil, fmt.Errorf("build tags can't be negated: %s", t)
}
c.GenericTags[t] = true
}
c.Platforms = config.DefaultPlatformTags
c.PreprocessTags()
c.GoPrefix = *goPrefix
if c.GoPrefix == "" {
c.GoPrefix, err = loadGoPrefix(&c)
if err != nil {
return nil, nil, fmt.Errorf("-go_prefix not set and not root BUILD file found")
}
}
c.DepMode, err = config.DependencyModeFromString(*external)
if err != nil {
return nil, nil, err
}
emit, ok := modeFromName[*mode]
if !ok {
return nil, nil, fmt.Errorf("unrecognized emit mode: %q", *mode)
}
return &c, emit, err
}
func findBuildFile(c *config.Config, dir string) (string, error) {
for _, base := range c.ValidBuildFileNames {
p := filepath.Join(dir, base)
fi, err := os.Stat(p)
if err == nil {
if fi.Mode().IsRegular() {
return p, nil
}
continue
}
if !os.IsNotExist(err) {
return "", err
}
}
return "", os.ErrNotExist
}
func loadGoPrefix(c *config.Config) (string, error) {
p, err := findBuildFile(c, c.RepoRoot)
if err != nil {
return "", err
}
b, err := ioutil.ReadFile(p)
if err != nil {
return "", err
}
f, err := bf.Parse(p, b)
if err != nil {
return "", err
}
for _, s := range f.Stmt {
c, ok := s.(*bf.CallExpr)
if !ok {
continue
}
l, ok := c.X.(*bf.LiteralExpr)
if !ok {
continue
}
if l.Token != "go_prefix" {
continue
}
if len(c.List) != 1 {
return "", fmt.Errorf("found go_prefix(%v) with too many args", c.List)
}
v, ok := c.List[0].(*bf.StringExpr)
if !ok {
return "", fmt.Errorf("found go_prefix(%v) which is not a string", c.List)
}
return v.Value, nil
}
return "", errors.New("-go_prefix not set, and no go_prefix in root BUILD file")
}
func isDescendingDir(dir, root string) bool {
if dir == root {
return true
}
return strings.HasPrefix(dir, fmt.Sprintf("%s%c", root, filepath.Separator))
}