From 1253f2873807053f3096b43c81121415b97109e5 Mon Sep 17 00:00:00 2001 From: Yuki Yugui Sonoda Date: Wed, 22 Jun 2016 10:49:32 +0900 Subject: [PATCH] Import minimal version of gazel, a BUILD file generator --- WORKSPACE | 11 +++ go/tools/gazel/gazel/BUILD | 10 ++ go/tools/gazel/gazel/main.go | 86 ++++++++++++++++ go/tools/gazel/generator/BUILD | 21 ++++ go/tools/gazel/generator/generator.go | 104 ++++++++++++++++++++ go/tools/gazel/generator/generator_test.go | 105 ++++++++++++++++++++ go/tools/gazel/packages/BUILD | 16 +++ go/tools/gazel/packages/doc.go | 17 ++++ go/tools/gazel/packages/walk.go | 54 +++++++++++ go/tools/gazel/packages/walk_test.go | 108 +++++++++++++++++++++ go/tools/gazel/rules/BUILD | 25 +++++ go/tools/gazel/rules/construct.go | 83 ++++++++++++++++ go/tools/gazel/rules/doc.go | 17 ++++ go/tools/gazel/rules/generator.go | 62 ++++++++++++ go/tools/gazel/rules/generator_test.go | 91 +++++++++++++++++ go/tools/gazel/testdata/BUILD | 12 +++ go/tools/gazel/testdata/repo/bin/main.go | 26 +++++ go/tools/gazel/testdata/repo/lib/doc.go | 17 ++++ go/tools/gazel/testdata/repo/lib/lib.go | 21 ++++ go/tools/gazel/testdata/testdata.go | 31 ++++++ 20 files changed, 917 insertions(+) create mode 100644 go/tools/gazel/gazel/BUILD create mode 100644 go/tools/gazel/gazel/main.go create mode 100644 go/tools/gazel/generator/BUILD create mode 100644 go/tools/gazel/generator/generator.go create mode 100644 go/tools/gazel/generator/generator_test.go create mode 100644 go/tools/gazel/packages/BUILD create mode 100644 go/tools/gazel/packages/doc.go create mode 100644 go/tools/gazel/packages/walk.go create mode 100644 go/tools/gazel/packages/walk_test.go create mode 100644 go/tools/gazel/rules/BUILD create mode 100644 go/tools/gazel/rules/construct.go create mode 100644 go/tools/gazel/rules/doc.go create mode 100644 go/tools/gazel/rules/generator.go create mode 100644 go/tools/gazel/rules/generator_test.go create mode 100644 go/tools/gazel/testdata/BUILD create mode 100644 go/tools/gazel/testdata/repo/bin/main.go create mode 100644 go/tools/gazel/testdata/repo/lib/doc.go create mode 100644 go/tools/gazel/testdata/repo/lib/lib.go create mode 100644 go/tools/gazel/testdata/testdata.go diff --git a/WORKSPACE b/WORKSPACE index 281d988d89..35780cf5a4 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -25,3 +25,14 @@ new_git_repository( commit = "23def4e6c14b4da8ac2ed8007337bc5eb5007998", remote = "https://github.com/golang/glog.git", ) + +git_repository( + name = "io_bazel_buildifier", + commit = "0ca1d7991357ae7a7555589af88930d82cf07c0a", + remote = "https://github.com/bazelbuild/buildifier.git", +) + +local_repository( + name = "io_bazel_rules_go", + path = ".", +) diff --git a/go/tools/gazel/gazel/BUILD b/go/tools/gazel/gazel/BUILD new file mode 100644 index 0000000000..0683fa947d --- /dev/null +++ b/go/tools/gazel/gazel/BUILD @@ -0,0 +1,10 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary") + +go_binary( + name = "gazel", + srcs = ["main.go"], + deps = [ + "@io_bazel_buildifier//core:go_default_library", + "//go/tools/gazel/generator:go_default_library", + ], +) diff --git a/go/tools/gazel/gazel/main.go b/go/tools/gazel/gazel/main.go new file mode 100644 index 0000000000..75e498668f --- /dev/null +++ b/go/tools/gazel/gazel/main.go @@ -0,0 +1,86 @@ +/* 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 gazel is a BUILD file generator for Go projects. +// See "gazel --help" for more details. +package main + +import ( + "flag" + "fmt" + "log" + "os" + + bzl "github.com/bazelbuild/buildifier/core" + "github.com/bazelbuild/rules_go/go/tools/gazel/generator" +) + +var ( + repoRoot = flag.String("repo_root", "", "path to a root directory of a repository") +) + +func run(dirs []string) error { + g, err := generator.New(*repoRoot) + if err != nil { + return err + } + + for _, d := range dirs { + files, err := g.Generate(d) + if err != nil { + return err + } + for _, f := range files { + if _, err := os.Stdout.Write(bzl.Format(f)); err != nil { + return err + } + } + } + return nil +} + +func usage() { + fmt.Fprintln(os.Stderr, `usage: gazel [flags...] [package-dirs...] + +Gazel is a BUILD file generator for Go projects. + +Currently its primary usage is to generate BUILD files for external dependencies +in a go_vendor repository rule. +You can still use Gazel for other purposes, but its interface can change without +notice. + +It takes a list of paths to Go package directories. +It recursively traverses its subpackages. +All the directories must be under the directory specified in -repo_root. + +FLAGS: +`) + flag.PrintDefaults() +} + +func main() { + flag.Usage = usage + flag.Parse() + + if *repoRoot == "" { + if flag.NArg() != 1 { + log.Fatal("-repo_root is required") + } + *repoRoot = flag.Arg(0) + } + if err := run(flag.Args()); err != nil { + log.Fatal(err) + } +} diff --git a/go/tools/gazel/generator/BUILD b/go/tools/gazel/generator/BUILD new file mode 100644 index 0000000000..ddd9ea83a5 --- /dev/null +++ b/go/tools/gazel/generator/BUILD @@ -0,0 +1,21 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["generator.go"], + visibility = ["//visibility:public"], + deps = [ + "@io_bazel_buildifier//core:go_default_library", + "//go/tools/gazel/packages:go_default_library", + "//go/tools/gazel/rules:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["generator_test.go"], + library = ":go_default_library", + deps = [ + "//go/tools/gazel/testdata:go_default_library", + ], +) diff --git a/go/tools/gazel/generator/generator.go b/go/tools/gazel/generator/generator.go new file mode 100644 index 0000000000..2dcb1fb4bf --- /dev/null +++ b/go/tools/gazel/generator/generator.go @@ -0,0 +1,104 @@ +/* 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. +*/ + +// Package generator provides core functionality of +// BUILD file generation in gazel. +package generator + +import ( + "fmt" + "go/build" + "path/filepath" + "strings" + + bzl "github.com/bazelbuild/buildifier/core" + "github.com/bazelbuild/rules_go/go/tools/gazel/packages" + "github.com/bazelbuild/rules_go/go/tools/gazel/rules" +) + +// Generator generates BUILD files for a Go repository. +type Generator struct { + repoRoot string + bctx build.Context + g rules.Generator +} + +// New returns a new Generator which is responsible for a Go repository. +// +// "repoRoot" is a path to the root directory of the repository. +func New(repoRoot string) (*Generator, error) { + bctx := build.Default + // Ignore source files in $GOROOT and $GOPATH + bctx.GOROOT = "" + bctx.GOPATH = "" + + repoRoot, err := filepath.Abs(repoRoot) + if err != nil { + return nil, err + } + return &Generator{ + repoRoot: filepath.Clean(repoRoot), + bctx: bctx, + g: rules.NewGenerator(), + }, nil +} + +// Generate generates a BUILD file for each Go package found under +// the given directory. +// The directory must be the repository root directory the caller +// passed to New, or its subdirectory. +func (g *Generator) Generate(dir string) ([]*bzl.File, error) { + dir, err := filepath.Abs(dir) + if err != nil { + return nil, err + } + dir = filepath.Clean(dir) + if !isDescendingDir(dir, g.repoRoot) { + return nil, fmt.Errorf("dir %s is not under the repository root %s", dir, g.repoRoot) + } + + var files []*bzl.File + err = packages.Walk(g.bctx, dir, func(pkg *build.Package) error { + rel, err := filepath.Rel(g.repoRoot, pkg.Dir) + if err != nil { + return err + } + if rel == "." { + rel = "" + } + + rs, err := g.g.Generate(filepath.ToSlash(rel), pkg) + if err != nil { + return err + } + file := &bzl.File{Path: filepath.Join(rel, "BUILD")} + for _, r := range rs { + file.Stmt = append(file.Stmt, r.Call) + } + files = append(files, file) + return nil + }) + if err != nil { + return nil, err + } + return files, nil +} + +func isDescendingDir(dir, root string) bool { + if dir == root { + return true + } + return strings.HasPrefix(dir, fmt.Sprintf("%s%c", root, filepath.Separator)) +} diff --git a/go/tools/gazel/generator/generator_test.go b/go/tools/gazel/generator/generator_test.go new file mode 100644 index 0000000000..6fff9fe377 --- /dev/null +++ b/go/tools/gazel/generator/generator_test.go @@ -0,0 +1,105 @@ +/* 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. +*/ + +package generator + +import ( + "fmt" + "go/build" + "path/filepath" + "reflect" + "sort" + "strings" + "testing" + + bzl "github.com/bazelbuild/buildifier/core" + "github.com/bazelbuild/rules_go/go/tools/gazel/testdata" +) + +func TestGenerator(t *testing.T) { + stub := stubRuleGen{ + fixtures: map[string][]*bzl.Rule{ + "lib": { + { + Call: &bzl.CallExpr{ + X: &bzl.LiteralExpr{Token: "go_library"}, + }, + }, + }, + "bin": { + { + Call: &bzl.CallExpr{ + X: &bzl.LiteralExpr{Token: "go_binary"}, + }, + }, + }, + }, + } + + repo := filepath.Join(testdata.Dir(), "repo") + g, err := New(repo) + if err != nil { + t.Errorf("New(%q) failed with %v; want success", repo, err) + return + } + g.g = stub + + got, err := g.Generate(repo) + if err != nil { + t.Errorf("g.Generate(%q) failed with %v; want success", repo, err) + } + sort.Sort(fileSlice(got)) + + want := []*bzl.File{ + { + Path: "lib/BUILD", + Stmt: []bzl.Expr{stub.fixtures["lib"][0].Call}, + }, + { + Path: "bin/BUILD", + Stmt: []bzl.Expr{stub.fixtures["bin"][0].Call}, + }, + } + sort.Sort(fileSlice(want)) + + if !reflect.DeepEqual(got, want) { + t.Errorf("g.Generate(%q) = %v; want %v", repo, prettyFiles(got), prettyFiles(want)) + } +} + +type prettyFiles []*bzl.File + +func (p prettyFiles) String() string { + var items []string + for _, f := range p { + items = append(items, fmt.Sprintf("{Path: %q, Stmt: %q", f.Path, string(bzl.Format(f)))) + } + return fmt.Sprintf("[%s]", strings.Join(items, ",")) +} + +type fileSlice []*bzl.File + +func (p fileSlice) Less(i, j int) bool { return strings.Compare(p[i].Path, p[j].Path) < 0 } +func (p fileSlice) Len() int { return len(p) } +func (p fileSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +// stubRuleGen is a test stub implementation of rules.Generator +type stubRuleGen struct { + fixtures map[string][]*bzl.Rule +} + +func (s stubRuleGen) Generate(rel string, pkg *build.Package) ([]*bzl.Rule, error) { + return s.fixtures[rel], nil +} diff --git a/go/tools/gazel/packages/BUILD b/go/tools/gazel/packages/BUILD new file mode 100644 index 0000000000..4ab93f3e2f --- /dev/null +++ b/go/tools/gazel/packages/BUILD @@ -0,0 +1,16 @@ +load("//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "walk.go", + ], + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_xtest", + srcs = ["walk_test.go"], + deps = [":go_default_library"], +) diff --git a/go/tools/gazel/packages/doc.go b/go/tools/gazel/packages/doc.go new file mode 100644 index 0000000000..c16f519653 --- /dev/null +++ b/go/tools/gazel/packages/doc.go @@ -0,0 +1,17 @@ +/* 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. +*/ + +// Package packages provides Go package traversal in a Bazel repository. +package packages diff --git a/go/tools/gazel/packages/walk.go b/go/tools/gazel/packages/walk.go new file mode 100644 index 0000000000..ae657741cb --- /dev/null +++ b/go/tools/gazel/packages/walk.go @@ -0,0 +1,54 @@ +/* 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. +*/ + +package packages + +import ( + "go/build" + "os" + "path/filepath" +) + +// A WalkFunc is a callback called by Walk for each package. +type WalkFunc func(pkg *build.Package) error + +// Walk walks through Go packages under the given dir. +// It calls back "f" for each package. +// +// It is similar to "golang.org/x/tools/go/buildutil".ForEachPackage, but +// it does not assume the standard Go tree because Bazel rules_go uses +// go_prefix instead of the standard tree. +func Walk(bctx build.Context, root string, f WalkFunc) error { + return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + return nil + } + if base := info.Name(); base == "" || base[0] == '.' || base[0] == '_' || base == "testdata" { + return filepath.SkipDir + } + + pkg, err := bctx.ImportDir(path, build.ImportComment) + if _, ok := err.(*build.NoGoError); ok { + return nil + } + if err != nil { + return err + } + return f(pkg) + }) +} diff --git a/go/tools/gazel/packages/walk_test.go b/go/tools/gazel/packages/walk_test.go new file mode 100644 index 0000000000..ec43d10105 --- /dev/null +++ b/go/tools/gazel/packages/walk_test.go @@ -0,0 +1,108 @@ +/* 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. +*/ + +package packages_test + +import ( + "go/build" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "sort" + "testing" + + "github.com/bazelbuild/rules_go/go/tools/gazel/packages" +) + +func tempDir() (string, error) { + return ioutil.TempDir(os.Getenv("TEST_TMPDIR"), "walk_test") +} + +func TestWalkSimple(t *testing.T) { + dir, err := tempDir() + if err != nil { + t.Fatalf("tempDir() failed with %v; want success", err) + } + defer os.RemoveAll(dir) + + fname := filepath.Join(dir, "lib.go") + if err := ioutil.WriteFile(fname, []byte("package lib"), 0600); err != nil { + t.Fatalf(`ioutil.WriteFile(%q, "package lib", 0600) failed with %v; want success`, fname, err) + } + + var n int + err = packages.Walk(build.Default, dir, func(pkg *build.Package) error { + if got, want := pkg.Name, "lib"; got != want { + t.Errorf("pkg.Name = %q; want %q", got, want) + } + n++ + return nil + }) + if err != nil { + t.Errorf("packages.Walk(build.Default, %q, func) failed with %v; want success", dir, err) + } + if got, want := n, 1; got != want { + t.Errorf("n = %d; want %d", got, want) + } +} + +func TestWalkNested(t *testing.T) { + dir, err := tempDir() + if err != nil { + t.Fatalf("tempDir() failed with %v; want success", err) + } + defer os.RemoveAll(dir) + + for _, p := range []struct { + path, content string + }{ + {path: "a/foo.go", content: "package a"}, + {path: "b/c/bar.go", content: "package c"}, + {path: "b/d/baz.go", content: "package main"}, + } { + path := filepath.Join(dir, p.path) + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { + t.Fatalf("os.MkdirAll(%q, 0700) failed with %v; want success", filepath.Dir(path), err) + } + if err := ioutil.WriteFile(path, []byte(p.content), 0600); err != nil { + t.Fatalf("ioutil.WriteFile(%q, %q, 0600) failed with %v; want success", path, p.content, err) + } + } + + var dirs, pkgs []string + err = packages.Walk(build.Default, dir, func(pkg *build.Package) error { + rel, err := filepath.Rel(dir, pkg.Dir) + if err != nil { + t.Errorf("filepath.Rel(%q, %q) failed with %v; want success", dir, pkg.Dir, err) + return err + } + dirs = append(dirs, filepath.ToSlash(rel)) + pkgs = append(pkgs, pkg.Name) + return nil + }) + if err != nil { + t.Errorf("packages.Walk(build.Default, %q, func) failed with %v; want success", dir, err) + } + + sort.Strings(dirs) + if got, want := dirs, []string{"a", "b/c", "b/d"}; !reflect.DeepEqual(got, want) { + t.Errorf("pkgs = %q; want %q", got, want) + } + sort.Strings(pkgs) + if got, want := pkgs, []string{"a", "c", "main"}; !reflect.DeepEqual(got, want) { + t.Errorf("pkgs = %q; want %q", got, want) + } +} diff --git a/go/tools/gazel/rules/BUILD b/go/tools/gazel/rules/BUILD new file mode 100644 index 0000000000..8e5579e303 --- /dev/null +++ b/go/tools/gazel/rules/BUILD @@ -0,0 +1,25 @@ +load("//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "construct.go", + "doc.go", + "generator.go", + ], + visibility = ["//visibility:public"], + deps = [ + "@io_bazel_buildifier//core:go_default_library", + ], +) + +go_test( + name = "go_default_xtest", + srcs = ["generator_test.go"], + data = glob(["testdata/**/*"]), + deps = [ + "@io_bazel_buildifier//core:go_default_library", + ":go_default_library", + "//go/tools/gazel/testdata:go_default_library", + ], +) diff --git a/go/tools/gazel/rules/construct.go b/go/tools/gazel/rules/construct.go new file mode 100644 index 0000000000..ee72abef51 --- /dev/null +++ b/go/tools/gazel/rules/construct.go @@ -0,0 +1,83 @@ +/* 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. +*/ + +package rules + +import ( + "fmt" + "reflect" + + bzl "github.com/bazelbuild/buildifier/core" +) + +type keyvalue struct { + key string + value interface{} +} + +func newRule(kind string, args []interface{}, kwargs []keyvalue) (*bzl.Rule, error) { + var list []bzl.Expr + for i, arg := range args { + expr, err := newValue(arg) + if err != nil { + return nil, fmt.Errorf("wrong arg %v at args[%d]: %v", arg, i, err) + } + list = append(list, expr) + } + for _, arg := range kwargs { + expr, err := newValue(arg.value) + if err != nil { + return nil, fmt.Errorf("wrong value %v at kwargs[%q]: %v", arg.value, arg.key, err) + } + list = append(list, &bzl.BinaryExpr{ + X: &bzl.LiteralExpr{Token: arg.key}, + Op: "=", + Y: expr, + }) + } + + return &bzl.Rule{ + Call: &bzl.CallExpr{ + X: &bzl.LiteralExpr{Token: kind}, + List: list, + }, + }, nil +} + +// newValue converts a Go value into the corresponding expression in Bazel BUILD file. +func newValue(val interface{}) (bzl.Expr, error) { + rv := reflect.ValueOf(val) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return &bzl.LiteralExpr{Token: fmt.Sprintf("%d", val)}, nil + case reflect.Float32, reflect.Float64: + return &bzl.LiteralExpr{Token: fmt.Sprintf("%f", val)}, nil + case reflect.String: + return &bzl.StringExpr{Value: val.(string)}, nil + case reflect.Slice, reflect.Array: + var list []bzl.Expr + for i := 0; i < rv.Len(); i++ { + elem, err := newValue(rv.Index(i).Interface()) + if err != nil { + return nil, err + } + list = append(list, elem) + } + return &bzl.ListExpr{List: list}, nil + default: + return nil, fmt.Errorf("not implemented %T", val) + } +} diff --git a/go/tools/gazel/rules/doc.go b/go/tools/gazel/rules/doc.go new file mode 100644 index 0000000000..8a361b0536 --- /dev/null +++ b/go/tools/gazel/rules/doc.go @@ -0,0 +1,17 @@ +/* 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. +*/ + +// Package rules provides Bazel rule generation for Go build targets. +package rules diff --git a/go/tools/gazel/rules/generator.go b/go/tools/gazel/rules/generator.go new file mode 100644 index 0000000000..97639d98f0 --- /dev/null +++ b/go/tools/gazel/rules/generator.go @@ -0,0 +1,62 @@ +/* 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. +*/ + +package rules + +import ( + "go/build" + "path" + + bzl "github.com/bazelbuild/buildifier/core" +) + +// Generator generates Bazel build rules for Go build targets +type Generator interface { + // Generate generates build rules for build targets in a Go package in a + // repository. + // + // "rel" is a relative slash-separated path from the repostiry root + // directory to the Go package directory. It is empty if the package + // directory is the repository root itself. + // "pkg" is a description about the package. + Generate(rel string, pkg *build.Package) ([]*bzl.Rule, error) +} + +// NewGenerator returns an implementation of Generator. +func NewGenerator() Generator { + return new(generator) +} + +type generator struct{} + +func (g *generator) Generate(rel string, pkg *build.Package) ([]*bzl.Rule, error) { + kind := "go_library" + name := "go_default_library" + if pkg.IsCommand() { + kind = "go_binary" + name = path.Base(pkg.Dir) + } + + var rules []*bzl.Rule + r, err := newRule(kind, nil, []keyvalue{ + {key: "name", value: name}, + {key: "srcs", value: pkg.GoFiles}, + }) + if err != nil { + return nil, err + } + rules = append(rules, r) + return rules, nil +} diff --git a/go/tools/gazel/rules/generator_test.go b/go/tools/gazel/rules/generator_test.go new file mode 100644 index 0000000000..ac7a998d30 --- /dev/null +++ b/go/tools/gazel/rules/generator_test.go @@ -0,0 +1,91 @@ +/* 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. +*/ + +package rules_test + +import ( + "go/build" + "path/filepath" + "testing" + + bzl "github.com/bazelbuild/buildifier/core" + "github.com/bazelbuild/rules_go/go/tools/gazel/rules" + "github.com/bazelbuild/rules_go/go/tools/gazel/testdata" +) + +func canonicalize(t *testing.T, filename, content string) string { + f, err := bzl.Parse(filename, []byte(content)) + if err != nil { + t.Fatalf("bzl.Parse(%q, %q) failed with %v; want success", filename, content, err) + } + return string(bzl.Format(f)) +} + +func format(rules []*bzl.Rule) string { + var f bzl.File + for _, r := range rules { + f.Stmt = append(f.Stmt, r.Call) + } + return string(bzl.Format(&f)) +} + +func packageFromDir(t *testing.T, dir string) *build.Package { + dir = filepath.Join(testdata.Dir(), "repo", dir) + pkg, err := build.ImportDir(dir, build.ImportComment) + if err != nil { + t.Fatalf("build.ImportDir(%q, build.ImportComment) failed with %v; want success", dir, err) + } + return pkg +} + +func TestGenerator(t *testing.T) { + g := rules.NewGenerator() + for _, spec := range []struct { + dir string + want string + }{ + { + dir: "lib", + want: ` + go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "lib.go", + ], + ) + `, + }, + { + dir: "bin", + want: ` + go_binary( + name = "bin", + srcs = ["main.go"], + ) + `, + }, + } { + pkg := packageFromDir(t, filepath.FromSlash(spec.dir)) + rules, err := g.Generate(spec.dir, pkg) + if err != nil { + t.Errorf("g.Generate(%q, %#v) failed with %v; want success", spec.dir, pkg, err) + } + + if got, want := format(rules), canonicalize(t, spec.dir+"/BUILD", spec.want); got != want { + t.Errorf("g.Generate(%q, %#v) = %s; want %s", spec.dir, pkg, got, want) + } + } +} diff --git a/go/tools/gazel/testdata/BUILD b/go/tools/gazel/testdata/BUILD new file mode 100644 index 0000000000..c9764ea04b --- /dev/null +++ b/go/tools/gazel/testdata/BUILD @@ -0,0 +1,12 @@ +package( + default_testonly = 1, + default_visibility = ["//go/tools/gazel:__subpackages__"], +) + +load("//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["testdata.go"], + data = glob(["repo/**/*.go"]), +) diff --git a/go/tools/gazel/testdata/repo/bin/main.go b/go/tools/gazel/testdata/repo/bin/main.go new file mode 100644 index 0000000000..122188d78d --- /dev/null +++ b/go/tools/gazel/testdata/repo/bin/main.go @@ -0,0 +1,26 @@ +/* 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. +*/ + +package main + +import ( + "fmt" + + "example.com/repo/lib" +) + +func main() { + fmt.Println(lib.Answer()) +} diff --git a/go/tools/gazel/testdata/repo/lib/doc.go b/go/tools/gazel/testdata/repo/lib/doc.go new file mode 100644 index 0000000000..9fd2f52f01 --- /dev/null +++ b/go/tools/gazel/testdata/repo/lib/doc.go @@ -0,0 +1,17 @@ +/* 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. +*/ + +// Package lib is an example go library package to be used as a test data of Gazel. +package lib diff --git a/go/tools/gazel/testdata/repo/lib/lib.go b/go/tools/gazel/testdata/repo/lib/lib.go new file mode 100644 index 0000000000..3ed9d8955a --- /dev/null +++ b/go/tools/gazel/testdata/repo/lib/lib.go @@ -0,0 +1,21 @@ +/* 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. +*/ + +package lib + +// Answer returns the ultimate answer to life, the universe and everything. +func Answer() int { + return 42 +} diff --git a/go/tools/gazel/testdata/testdata.go b/go/tools/gazel/testdata/testdata.go new file mode 100644 index 0000000000..79a6055c01 --- /dev/null +++ b/go/tools/gazel/testdata/testdata.go @@ -0,0 +1,31 @@ +/* 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. +*/ + +// Package testdata provides convenient access to testdata files +package testdata + +import ( + "os" + "path/filepath" +) + +// Dir returns a path to the testdata directory. +func Dir() string { + srcdir := os.Getenv("TEST_SRCDIR") + return filepath.Join( + srcdir, os.Getenv("TEST_WORKSPACE"), + "go", "tools", "gazel", "testdata", + ) +}