Create Gazelle language for Starlark (#251)
This commit is contained in:
parent
b10f2cb0fc
commit
d35e8d7bc6
15
BUILD
15
BUILD
|
@ -4,6 +4,11 @@ licenses(["notice"])
|
||||||
|
|
||||||
package(default_visibility = ["//visibility:public"])
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
# gazelle:exclude internal_deps.bzl
|
||||||
|
# gazelle:exclude internal_setup.bzl
|
||||||
|
# buildifier: disable=skylark-comment
|
||||||
|
# gazelle:exclude skylark_library.bzl
|
||||||
|
|
||||||
exports_files(["LICENSE"])
|
exports_files(["LICENSE"])
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
|
@ -45,6 +50,16 @@ bzl_library(
|
||||||
srcs = ["bzl_library.bzl"],
|
srcs = ["bzl_library.bzl"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "version",
|
||||||
|
srcs = ["version.bzl"],
|
||||||
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "workspace",
|
||||||
|
srcs = ["workspace.bzl"],
|
||||||
|
)
|
||||||
|
|
||||||
# The files needed for distribution.
|
# The files needed for distribution.
|
||||||
# TODO(aiuto): We should strip this from the release, but there is no
|
# TODO(aiuto): We should strip this from the release, but there is no
|
||||||
# capability now to generate BUILD.foo from BUILD and have it appear in the
|
# capability now to generate BUILD.foo from BUILD and have it appear in the
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
* @c-parsons @laurentlb @jin @aiuto
|
* @c-parsons @laurentlb @jin @aiuto
|
||||||
distribution/ @aiuto @fwe
|
distribution/ @aiuto @fwe
|
||||||
rules/ @juliexxia
|
rules/ @juliexxia
|
||||||
|
gazelle/ @achew22 @jayconrod
|
||||||
|
|
27
WORKSPACE
27
WORKSPACE
|
@ -10,14 +10,20 @@ maybe(
|
||||||
url = "https://github.com/bazelbuild/bazel-federation/releases/download/0.0.1/bazel_federation-0.0.1.tar.gz",
|
url = "https://github.com/bazelbuild/bazel-federation/releases/download/0.0.1/bazel_federation-0.0.1.tar.gz",
|
||||||
)
|
)
|
||||||
|
|
||||||
load("@bazel_federation//:repositories.bzl", "bazel_skylib_deps")
|
load("@bazel_federation//:repositories.bzl", "bazel_skylib_deps", "rules_go")
|
||||||
|
|
||||||
bazel_skylib_deps()
|
bazel_skylib_deps()
|
||||||
|
|
||||||
|
rules_go()
|
||||||
|
|
||||||
load("@bazel_federation//setup:bazel_skylib.bzl", "bazel_skylib_setup")
|
load("@bazel_federation//setup:bazel_skylib.bzl", "bazel_skylib_setup")
|
||||||
|
|
||||||
bazel_skylib_setup()
|
bazel_skylib_setup()
|
||||||
|
|
||||||
|
load("@bazel_federation//setup:rules_go.bzl", "rules_go_setup")
|
||||||
|
|
||||||
|
rules_go_setup()
|
||||||
|
|
||||||
# Below this line is for documentation generation only,
|
# Below this line is for documentation generation only,
|
||||||
# and should thus not be included by dependencies on
|
# and should thus not be included by dependencies on
|
||||||
# bazel-skylib.
|
# bazel-skylib.
|
||||||
|
@ -40,3 +46,22 @@ maybe(
|
||||||
"https://github.com/bazelbuild/rules_cc/archive/cb2dfba6746bfa3c3705185981f3109f0ae1b893.zip",
|
"https://github.com/bazelbuild/rules_cc/archive/cb2dfba6746bfa3c3705185981f3109f0ae1b893.zip",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Provide a repository hint for Gazelle to inform it that the go package
|
||||||
|
# github.com/bazelbuild/rules_go is available from io_bazel_rules_go and it
|
||||||
|
# doesn't need to duplicatively fetch it.
|
||||||
|
# gazelle:repository go_repository name=io_bazel_rules_go importpath=github.com/bazelbuild/rules_go
|
||||||
|
http_archive(
|
||||||
|
name = "bazel_gazelle",
|
||||||
|
sha256 = "bfd86b3cbe855d6c16c6fce60d76bd51f5c8dbc9cfcaef7a2bb5c1aafd0710e8",
|
||||||
|
urls = [
|
||||||
|
"https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.21.0/bazel-gazelle-v0.21.0.tar.gz",
|
||||||
|
"https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.21.0/bazel-gazelle-v0.21.0.tar.gz",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
# Another Gazelle repository hint.
|
||||||
|
# gazelle:repository go_repository name=bazel_gazelle importpath=github.com/bazelbuild/bazel-gazelle/testtools
|
||||||
|
|
||||||
|
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
|
||||||
|
|
||||||
|
gazelle_dependencies()
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
load("@bazel_gazelle//:def.bzl", "gazelle", "gazelle_binary")
|
||||||
|
|
||||||
|
# gazelle:exclude testdata
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["gazelle.go"],
|
||||||
|
importpath = "github.com/bazelbuild/bazel-skylib/gazelle",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"@bazel_gazelle//config:go_default_library",
|
||||||
|
"@bazel_gazelle//label:go_default_library",
|
||||||
|
"@bazel_gazelle//language:go_default_library",
|
||||||
|
"@bazel_gazelle//pathtools:go_default_library",
|
||||||
|
"@bazel_gazelle//repo:go_default_library",
|
||||||
|
"@bazel_gazelle//resolve:go_default_library",
|
||||||
|
"@bazel_gazelle//rule:go_default_library",
|
||||||
|
"@com_github_bazelbuild_buildtools//build:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["gazelle_test.go"],
|
||||||
|
data = [
|
||||||
|
":gazelle-skylib",
|
||||||
|
] + glob([
|
||||||
|
"testdata/**",
|
||||||
|
]),
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"@bazel_gazelle//testtools:go_default_library",
|
||||||
|
"@io_bazel_rules_go//go/tools/bazel:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# This gazelle binary is used exclusively for testing the gazelle language
|
||||||
|
# extension and thus only has the skylib language installed.
|
||||||
|
gazelle_binary(
|
||||||
|
name = "gazelle-skylib",
|
||||||
|
languages = [":go_default_library"],
|
||||||
|
visibility = [
|
||||||
|
# Also make the binary available in the root of the repo for use, but
|
||||||
|
# not externally.
|
||||||
|
"//:__pkg__",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
gazelle(
|
||||||
|
name = "gazelle",
|
||||||
|
gazelle = "//gazelle:gazelle-skylib",
|
||||||
|
)
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Gazelle
|
||||||
|
|
||||||
|
Gazelle is a `BUILD` file generator for Bazel. This directory contains a
|
||||||
|
language extension for the Gazelle generator that allows it to automatically
|
||||||
|
parse valid `bzl_library` targets for all `.bzl` files in a repo in which it
|
||||||
|
runs. It will additionally include a `deps` entry tracking every `.bzl` that is
|
||||||
|
`load`ed into the primary file.
|
||||||
|
|
||||||
|
This can be used, for example, to generate
|
||||||
|
[`stardoc`](https://github.com/bazelbuild/stardoc) documentation for your
|
||||||
|
`.bzl` files, both simplify the task of and improve the quality of
|
||||||
|
documentation.
|
||||||
|
|
|
@ -0,0 +1,315 @@
|
||||||
|
/* Copyright 2020 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 gazelle generates a `bzl_library` target for every `.bzl` file in
|
||||||
|
// each package.
|
||||||
|
//
|
||||||
|
// The `bzl_library` rule is provided by
|
||||||
|
// https://github.com/bazelbuild/bazel-skylib.
|
||||||
|
//
|
||||||
|
// This extension is experimental and subject to change. It is not included
|
||||||
|
// in the default Gazelle binary.
|
||||||
|
package gazelle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bazelbuild/bazel-gazelle/config"
|
||||||
|
"github.com/bazelbuild/bazel-gazelle/label"
|
||||||
|
"github.com/bazelbuild/bazel-gazelle/language"
|
||||||
|
"github.com/bazelbuild/bazel-gazelle/pathtools"
|
||||||
|
"github.com/bazelbuild/bazel-gazelle/repo"
|
||||||
|
"github.com/bazelbuild/bazel-gazelle/resolve"
|
||||||
|
"github.com/bazelbuild/bazel-gazelle/rule"
|
||||||
|
|
||||||
|
"github.com/bazelbuild/buildtools/build"
|
||||||
|
)
|
||||||
|
|
||||||
|
const languageName = "starlark"
|
||||||
|
const fileType = ".bzl"
|
||||||
|
|
||||||
|
var ignoreSuffix = suffixes{
|
||||||
|
"_tests.bzl",
|
||||||
|
"_test.bzl",
|
||||||
|
}
|
||||||
|
|
||||||
|
type suffixes []string
|
||||||
|
|
||||||
|
func (s suffixes) Matches(test string) bool {
|
||||||
|
for _, v := range s {
|
||||||
|
if strings.HasSuffix(test, v) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type bzlLibraryLang struct{}
|
||||||
|
|
||||||
|
// NewLanguage is called by Gazelle to install this language extension in a binary.
|
||||||
|
func NewLanguage() language.Language {
|
||||||
|
return &bzlLibraryLang{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the language. This should be a prefix of the
|
||||||
|
// kinds of rules generated by the language, e.g., "go" for the Go extension
|
||||||
|
// since it generates "go_library" rules.
|
||||||
|
func (*bzlLibraryLang) Name() string { return languageName }
|
||||||
|
|
||||||
|
// The following methods are implemented to satisfy the
|
||||||
|
// https://pkg.go.dev/github.com/bazelbuild/bazel-gazelle/resolve?tab=doc#Resolver
|
||||||
|
// interface, but are otherwise unused.
|
||||||
|
func (*bzlLibraryLang) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {}
|
||||||
|
func (*bzlLibraryLang) CheckFlags(fs *flag.FlagSet, c *config.Config) error { return nil }
|
||||||
|
func (*bzlLibraryLang) KnownDirectives() []string { return nil }
|
||||||
|
func (*bzlLibraryLang) Configure(c *config.Config, rel string, f *rule.File) {}
|
||||||
|
|
||||||
|
// Kinds returns a map of maps rule names (kinds) and information on how to
|
||||||
|
// match and merge attributes that may be found in rules of those kinds. All
|
||||||
|
// kinds of rules generated for this language may be found here.
|
||||||
|
func (*bzlLibraryLang) Kinds() map[string]rule.KindInfo {
|
||||||
|
return kinds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loads returns .bzl files and symbols they define. Every rule generated by
|
||||||
|
// GenerateRules, now or in the past, should be loadable from one of these
|
||||||
|
// files.
|
||||||
|
func (*bzlLibraryLang) Loads() []rule.LoadInfo {
|
||||||
|
return []rule.LoadInfo{{
|
||||||
|
Name: "@bazel_skylib//:bzl_library.bzl",
|
||||||
|
Symbols: []string{"bzl_library"},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix repairs deprecated usage of language-specific rules in f. This is
|
||||||
|
// called before the file is indexed. Unless c.ShouldFix is true, fixes
|
||||||
|
// that delete or rename rules should not be performed.
|
||||||
|
func (*bzlLibraryLang) Fix(c *config.Config, f *rule.File) {}
|
||||||
|
|
||||||
|
// Imports returns a list of ImportSpecs that can be used to import the rule
|
||||||
|
// r. This is used to populate RuleIndex.
|
||||||
|
//
|
||||||
|
// If nil is returned, the rule will not be indexed. If any non-nil slice is
|
||||||
|
// returned, including an empty slice, the rule will be indexed.
|
||||||
|
func (b *bzlLibraryLang) Imports(c *config.Config, r *rule.Rule, f *rule.File) []resolve.ImportSpec {
|
||||||
|
srcs := r.AttrStrings("srcs")
|
||||||
|
imports := make([]resolve.ImportSpec, len(srcs))
|
||||||
|
|
||||||
|
for _, src := range srcs {
|
||||||
|
spec := resolve.ImportSpec{
|
||||||
|
// Lang is the language in which the import string appears (this should
|
||||||
|
// match Resolver.Name).
|
||||||
|
Lang: languageName,
|
||||||
|
// Imp is an import string for the library.
|
||||||
|
Imp: fmt.Sprintf("//%s:%s", f.Pkg, src),
|
||||||
|
}
|
||||||
|
|
||||||
|
imports = append(imports, spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
return imports
|
||||||
|
}
|
||||||
|
|
||||||
|
// Embeds returns a list of labels of rules that the given rule embeds. If
|
||||||
|
// a rule is embedded by another importable rule of the same language, only
|
||||||
|
// the embedding rule will be indexed. The embedding rule will inherit
|
||||||
|
// the imports of the embedded rule.
|
||||||
|
// Since SkyLark doesn't support embedding this should always return nil.
|
||||||
|
func (*bzlLibraryLang) Embeds(r *rule.Rule, from label.Label) []label.Label { return nil }
|
||||||
|
|
||||||
|
// Resolve translates imported libraries for a given rule into Bazel
|
||||||
|
// dependencies. Information about imported libraries is returned for each
|
||||||
|
// rule generated by language.GenerateRules in
|
||||||
|
// language.GenerateResult.Imports. Resolve generates a "deps" attribute (or
|
||||||
|
// the appropriate language-specific equivalent) for each import according to
|
||||||
|
// language-specific rules and heuristics.
|
||||||
|
func (*bzlLibraryLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repo.RemoteCache, r *rule.Rule, importsRaw interface{}, from label.Label) {
|
||||||
|
imports := importsRaw.([]string)
|
||||||
|
|
||||||
|
r.DelAttr("deps")
|
||||||
|
|
||||||
|
if len(imports) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deps := make([]string, 0, len(imports))
|
||||||
|
for _, imp := range imports {
|
||||||
|
if strings.HasPrefix(imp, "@") || !c.IndexLibraries {
|
||||||
|
// This is a dependency that is external to the current repo, or indexing
|
||||||
|
// is disabled so take a guess at what hte target name should be.
|
||||||
|
deps = append(deps, strings.TrimSuffix(imp, fileType))
|
||||||
|
} else {
|
||||||
|
res := resolve.ImportSpec{
|
||||||
|
Lang: languageName,
|
||||||
|
Imp: imp,
|
||||||
|
}
|
||||||
|
matches := ix.FindRulesByImport(res, languageName)
|
||||||
|
|
||||||
|
if len(matches) == 0 {
|
||||||
|
log.Printf("%s: %q was not found in dependency index. Skipping. This may result in an incomplete deps section and require manual BUILD file intervention.\n", from.String(), imp)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range matches {
|
||||||
|
deps = append(deps, m.Label.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(deps)
|
||||||
|
if len(deps) > 0 {
|
||||||
|
r.SetAttr("deps", deps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var kinds = map[string]rule.KindInfo{
|
||||||
|
"bzl_library": {
|
||||||
|
NonEmptyAttrs: map[string]bool{"srcs": true, "deps": true},
|
||||||
|
MergeableAttrs: map[string]bool{"srcs": true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateRules extracts build metadata from source files in a directory.
|
||||||
|
// GenerateRules is called in each directory where an update is requested
|
||||||
|
// in depth-first post-order.
|
||||||
|
//
|
||||||
|
// args contains the arguments for GenerateRules. This is passed as a
|
||||||
|
// struct to avoid breaking implementations in the future when new
|
||||||
|
// fields are added.
|
||||||
|
//
|
||||||
|
// A GenerateResult struct is returned. Optional fields may be added to this
|
||||||
|
// type in the future.
|
||||||
|
//
|
||||||
|
// Any non-fatal errors this function encounters should be logged using
|
||||||
|
// log.Print.
|
||||||
|
func (*bzlLibraryLang) GenerateRules(args language.GenerateArgs) language.GenerateResult {
|
||||||
|
var rules []*rule.Rule
|
||||||
|
var imports []interface{}
|
||||||
|
for _, f := range append(args.RegularFiles, args.GenFiles...) {
|
||||||
|
if !isBzlSourceFile(f) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := strings.TrimSuffix(f, fileType)
|
||||||
|
r := rule.NewRule("bzl_library", name)
|
||||||
|
|
||||||
|
r.SetAttr("srcs", []string{f})
|
||||||
|
|
||||||
|
if args.File == nil || !args.File.HasDefaultVisibility() {
|
||||||
|
inPrivateDir := pathtools.Index(args.Rel, "private") >= 0
|
||||||
|
if !inPrivateDir {
|
||||||
|
r.SetAttr("visibility", []string{"//visibility:public"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := filepath.Join(args.Dir, f)
|
||||||
|
loads, err := getBzlFileLoads(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s: contains syntax errors: %v", fullPath, err)
|
||||||
|
// Don't `continue` since it is reasonable to create a target even
|
||||||
|
// without deps.
|
||||||
|
}
|
||||||
|
|
||||||
|
rules = append(rules, r)
|
||||||
|
imports = append(imports, loads)
|
||||||
|
}
|
||||||
|
|
||||||
|
return language.GenerateResult{
|
||||||
|
Gen: rules,
|
||||||
|
Imports: imports,
|
||||||
|
Empty: generateEmpty(args),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBzlFileLoads(path string) ([]string, error) {
|
||||||
|
f, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ioutil.ReadFile(%q) error: %v", path, err)
|
||||||
|
}
|
||||||
|
ast, err := build.ParseBuild(path, f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("build.Parse(%q) error: %v", f, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var loads []string
|
||||||
|
build.WalkOnce(ast, func(expr *build.Expr) {
|
||||||
|
n := *expr
|
||||||
|
if l, ok := n.(*build.LoadStmt); ok {
|
||||||
|
loads = append(loads, l.Module.Value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
sort.Strings(loads)
|
||||||
|
|
||||||
|
return loads, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBzlSourceFile(f string) bool {
|
||||||
|
return strings.HasSuffix(f, fileType) && !ignoreSuffix.Matches(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateEmpty generates the list of rules that don't need to exist in the
|
||||||
|
// BUILD file any more.
|
||||||
|
// For each bzl_library rule in args.File that only has srcs that aren't in
|
||||||
|
// args.RegularFiles or args.GenFiles, add a bzl_library with no srcs or deps.
|
||||||
|
// That will let Gazelle delete bzl_library rules after the corresponding .bzl
|
||||||
|
// files are deleted.
|
||||||
|
func generateEmpty(args language.GenerateArgs) []*rule.Rule {
|
||||||
|
var ret []*rule.Rule
|
||||||
|
if args.File == nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
for _, r := range args.File.Rules {
|
||||||
|
if r.Kind() != "bzl_library" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := r.AttrString("name")
|
||||||
|
|
||||||
|
exists := make(map[string]bool)
|
||||||
|
for _, f := range args.RegularFiles {
|
||||||
|
exists[f] = true
|
||||||
|
}
|
||||||
|
for _, f := range args.GenFiles {
|
||||||
|
exists[f] = true
|
||||||
|
}
|
||||||
|
for _, r := range args.File.Rules {
|
||||||
|
srcsExist := false
|
||||||
|
for _, f := range r.AttrStrings("srcs") {
|
||||||
|
if exists[f] {
|
||||||
|
srcsExist = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !srcsExist {
|
||||||
|
ret = append(ret, rule.NewRule("bzl_library", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
type srcsList []string
|
||||||
|
|
||||||
|
func (s srcsList) Contains(m string) bool {
|
||||||
|
for _, e := range s {
|
||||||
|
if e == m {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
package gazelle
|
||||||
|
|
||||||
|
/* Copyright 2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/bazelbuild/bazel-gazelle/testtools"
|
||||||
|
"github.com/bazelbuild/rules_go/go/tools/bazel"
|
||||||
|
)
|
||||||
|
|
||||||
|
var gazellePath = findGazelle()
|
||||||
|
|
||||||
|
const testDataPath = "gazelle/testdata/"
|
||||||
|
|
||||||
|
// TestGazelleBinary runs a gazelle binary with starlib installed on each
|
||||||
|
// directory in `testdata/*`. Please see `testdata/README.md` for more
|
||||||
|
// information on each test.
|
||||||
|
func TestGazelleBinary(t *testing.T) {
|
||||||
|
tests := map[string][]bazel.RunfileEntry{}
|
||||||
|
|
||||||
|
files, err := bazel.ListRunfiles()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bazel.ListRunfiles() error: %v", err)
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
|
if strings.HasPrefix(f.ShortPath, testDataPath) {
|
||||||
|
relativePath := strings.TrimPrefix(f.ShortPath, testDataPath)
|
||||||
|
parts := strings.SplitN(relativePath, "/", 2)
|
||||||
|
if len(parts) < 2 {
|
||||||
|
// This file is not a part of a testcase since it must be in a dir that
|
||||||
|
// is the test case and then have a path inside of that.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tests[parts[0]] = append(tests[parts[0]], f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for testName, files := range tests {
|
||||||
|
testPath(t, testName, files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPath(t *testing.T, name string, files []bazel.RunfileEntry) {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
var inputs []testtools.FileSpec
|
||||||
|
var goldens []testtools.FileSpec
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
path := f.Path
|
||||||
|
trim := testDataPath + name + "/"
|
||||||
|
shortPath := strings.TrimPrefix(f.ShortPath, trim)
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("os.Stat(%q) error: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip dirs.
|
||||||
|
if info.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ioutil.ReadFile(%q) error: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now trim the common prefix off.
|
||||||
|
if strings.HasSuffix(shortPath, ".in") {
|
||||||
|
inputs = append(inputs, testtools.FileSpec{
|
||||||
|
Path: strings.TrimSuffix(shortPath, ".in"),
|
||||||
|
Content: string(content),
|
||||||
|
})
|
||||||
|
} else if strings.HasSuffix(shortPath, ".out") {
|
||||||
|
goldens = append(goldens, testtools.FileSpec{
|
||||||
|
Path: strings.TrimSuffix(shortPath, ".out"),
|
||||||
|
Content: string(content),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
inputs = append(inputs, testtools.FileSpec{
|
||||||
|
Path: shortPath,
|
||||||
|
Content: string(content),
|
||||||
|
})
|
||||||
|
goldens = append(goldens, testtools.FileSpec{
|
||||||
|
Path: shortPath,
|
||||||
|
Content: string(content),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, cleanup := testtools.CreateFiles(t, inputs)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
cmd := exec.Command(gazellePath, "-build_file_name=BUILD")
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Dir = dir
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testtools.CheckFiles(t, dir, goldens)
|
||||||
|
if t.Failed() {
|
||||||
|
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Logf("%q exists", path)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func findGazelle() string {
|
||||||
|
gazellePath, ok := bazel.FindBinary("gazelle", "gazelle-skylib")
|
||||||
|
if !ok {
|
||||||
|
panic("could not find gazelle binary")
|
||||||
|
}
|
||||||
|
return gazellePath
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Gazelle test cases
|
||||||
|
|
||||||
|
This directory contains a suite of test cases for the Skylark language plugin
|
||||||
|
for Gazelle.
|
||||||
|
|
||||||
|
Please note that there are no `BUILD` or `BUILD.bazel` files in subdirs, insted
|
||||||
|
there are `BUILD.in` and `BUILD.out` describing what the `BUILD` should look
|
||||||
|
like initially and what the `BUILD` file should look like after the run. These
|
||||||
|
names are special because they are not recognized by Bazel as a proper `BUILD`
|
||||||
|
file, and therefore are included in the data dependency by the recursive data
|
||||||
|
glob in `//gazelle:go_default_test`. If you would like to include any extremely
|
||||||
|
complicated tests that contain proper `BUILD` files you will need to manually
|
||||||
|
add them to the `//gazelle:go_default_test` target's `data` section.
|
||||||
|
|
||||||
|
## `simple`
|
||||||
|
|
||||||
|
Simple is a base test case that was used to validate the parser.
|
||||||
|
|
||||||
|
## `nobuildfiles`
|
||||||
|
|
||||||
|
A test just like `simple` that has no `BUILD` files at the beginning.
|
||||||
|
|
||||||
|
## `import`
|
||||||
|
|
||||||
|
Import is a test case that imports a `.bzl` from the same directory.
|
||||||
|
|
||||||
|
## `multidir`
|
||||||
|
|
||||||
|
Multidir is a test that has a `.bzl` that imports from a different dirrectory.
|
||||||
|
|
||||||
|
## `tests`
|
||||||
|
|
||||||
|
Using the skylib as an example, this test has `.bzl` files that end in
|
||||||
|
`_tests.bzl` which are `load`ed into `BUILD` files and never imported by
|
||||||
|
another repo.
|
||||||
|
|
||||||
|
## `private`
|
||||||
|
|
||||||
|
Using the skylib as an example, this test has `.bzl` files that live in a
|
||||||
|
directory called `private` which is used to indicate that they are repo private.
|
||||||
|
Note that this is distict from the expectations of go's `internal` where the
|
||||||
|
relative position in the hierarchy determines the visibility.
|
||||||
|
|
||||||
|
## `defaultvisibility`
|
||||||
|
|
||||||
|
If the package declares a `default_visibility` the generated `bzl_library`
|
||||||
|
should not set its own `visibility`.
|
||||||
|
|
||||||
|
## `external`
|
||||||
|
|
||||||
|
This test demonstrates that if you load from another repo, it is able to
|
||||||
|
generate a `deps` entry for the dependency.
|
||||||
|
|
||||||
|
## `empty`
|
||||||
|
|
||||||
|
Gazelle has the ability to remove old and unused targets. Test that.
|
|
@ -0,0 +1,8 @@
|
||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "foo",
|
||||||
|
srcs = ["foo.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//nested/dir:bar"],
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
"""
|
||||||
|
Doc string
|
||||||
|
"""
|
||||||
|
|
||||||
|
load("//nested/dir:bar.bzl", "func")
|
||||||
|
|
||||||
|
func()
|
|
@ -0,0 +1 @@
|
||||||
|
package(default_visibility = ["//:__pkg__"])
|
|
@ -0,0 +1,8 @@
|
||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
package(default_visibility = ["//:__pkg__"])
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "bar",
|
||||||
|
srcs = ["bar.bzl"],
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
"""
|
||||||
|
Doc string
|
||||||
|
"""
|
||||||
|
|
||||||
|
def asdf():
|
||||||
|
pass
|
|
@ -0,0 +1,13 @@
|
||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "weirdly_named_target_that_will_be_removed",
|
||||||
|
srcs = ["nonexistant.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "weirdly_named_target_that_will_be_renamed",
|
||||||
|
srcs = ["foo.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "foo",
|
||||||
|
srcs = ["foo.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,8 @@
|
||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
# Some comment to be preserved
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "allfiles",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
)
|
|
@ -0,0 +1,15 @@
|
||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
# Some comment to be preserved
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "allfiles",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "foo",
|
||||||
|
srcs = ["foo.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["@external_repo//path/to:file"],
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
"""
|
||||||
|
Test sample code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
load("@external_repo//path/to:file.bzl", "func")
|
||||||
|
|
||||||
|
func()
|
|
@ -0,0 +1,14 @@
|
||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "bar",
|
||||||
|
srcs = ["bar.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "foo",
|
||||||
|
srcs = ["foo.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//:bar"],
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
"""
|
||||||
|
Doc string
|
||||||
|
"""
|
||||||
|
|
||||||
|
def func():
|
||||||
|
pass
|
|
@ -0,0 +1,7 @@
|
||||||
|
"""
|
||||||
|
Doc string
|
||||||
|
"""
|
||||||
|
|
||||||
|
load("//:bar.bzl", "func")
|
||||||
|
|
||||||
|
func()
|
|
@ -0,0 +1,8 @@
|
||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "foo",
|
||||||
|
srcs = ["foo.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//nested/dir:bar"],
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
"""
|
||||||
|
Doc string
|
||||||
|
"""
|
||||||
|
|
||||||
|
load("//nested/dir:bar.bzl", "func")
|
||||||
|
|
||||||
|
func()
|
|
@ -0,0 +1,7 @@
|
||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "bar",
|
||||||
|
srcs = ["bar.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
"""
|
||||||
|
Doc string
|
||||||
|
"""
|
||||||
|
|
||||||
|
def func():
|
||||||
|
pass
|
|
@ -0,0 +1,7 @@
|
||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "foo",
|
||||||
|
srcs = ["foo.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,8 @@
|
||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "foo",
|
||||||
|
srcs = ["foo.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//private:bar"],
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
"""
|
||||||
|
Test sample code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
load("//private:bar.bzl", "func")
|
||||||
|
|
||||||
|
func()
|
|
@ -0,0 +1,6 @@
|
||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "bar",
|
||||||
|
srcs = ["bar.bzl"],
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
"""
|
||||||
|
Test sample code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def func():
|
||||||
|
pass
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Some comment to be preserved
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "allfiles",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
)
|
|
@ -0,0 +1,14 @@
|
||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
# Some comment to be preserved
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "allfiles",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "foo",
|
||||||
|
srcs = ["foo.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Some comment to be preserved
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "allfiles",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
)
|
|
@ -0,0 +1,14 @@
|
||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
# Some comment to be preserved
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "allfiles",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "foo",
|
||||||
|
srcs = ["foo.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -93,3 +93,8 @@ filegroup(
|
||||||
"//distribution:__pkg__",
|
"//distribution:__pkg__",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "old_sets",
|
||||||
|
srcs = ["old_sets.bzl"],
|
||||||
|
)
|
||||||
|
|
|
@ -81,3 +81,8 @@ exports_files(
|
||||||
glob(["*.bzl"]),
|
glob(["*.bzl"]),
|
||||||
visibility = ["//:__subpackages__"],
|
visibility = ["//:__subpackages__"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "select_file",
|
||||||
|
srcs = ["select_file.bzl"],
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue