| /* 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" |
| "log" |
| "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/packages" |
| ) |
| |
| const ( |
| // goRulesBzl is the label of the Skylark file which provides Go rules |
| goRulesBzl = "@io_bazel_rules_go//go:def.bzl" |
| // defaultLibName is the name of the default go_library rule in a Go |
| // package directory. It must be consistent to DEFAULT_LIB in go/private/common.bf. |
| defaultLibName = "go_default_library" |
| // defaultTestName is a name of an internal test corresponding to |
| // defaultLibName. It does not need to be consistent to something but it |
| // just needs to be unique in the Bazel package |
| defaultTestName = "go_default_test" |
| // defaultXTestName is a name of an external test corresponding to |
| // defaultLibName. |
| defaultXTestName = "go_default_xtest" |
| // defaultProtosName is the name of a filegroup created |
| // whenever the library contains .pb.go files |
| defaultProtosName = "go_default_library_protos" |
| // defaultCgoLibName is the name of the default cgo_library rule in a Go package directory. |
| defaultCgoLibName = "cgo_default_library" |
| ) |
| |
| // Generator generates Bazel build rules for Go build targets |
| type Generator interface { |
| // Generate generates a syntax tree of a BUILD file for "pkg". The file |
| // contains rules for each non-empty target in "pkg". It also contains |
| // "load" statements necessary for the rule constructors. If this is the |
| // top-level package in the repository, the file will contain a |
| // "go_prefix" rule. |
| Generate(pkg *packages.Package) *bf.File |
| } |
| |
| func NewGenerator(c *config.Config) Generator { |
| var ( |
| // TODO(yugui) Support another resolver to cover the pattern 2 in |
| // https://github.com/bazelbuild/rules_go/issues/16#issuecomment-216010843 |
| r = structuredResolver{goPrefix: c.GoPrefix} |
| ) |
| |
| var e labelResolver |
| switch c.DepMode { |
| case config.ExternalMode: |
| e = externalResolver{} |
| case config.VendorMode: |
| e = vendoredResolver{} |
| default: |
| return nil |
| } |
| |
| return &generator{ |
| c: c, |
| r: resolverFunc(func(importpath, dir string) (label, error) { |
| if importpath != c.GoPrefix && !strings.HasPrefix(importpath, c.GoPrefix+"/") && !isRelative(importpath) { |
| return e.resolve(importpath, dir) |
| } |
| return r.resolve(importpath, dir) |
| }), |
| } |
| } |
| |
| type generator struct { |
| c *config.Config |
| r labelResolver |
| } |
| |
| func (g *generator) Generate(pkg *packages.Package) *bf.File { |
| f := &bf.File{ |
| Path: filepath.Join(pkg.Dir, g.c.DefaultBuildFileName()), |
| } |
| rs := g.generateRules(pkg) |
| if load := g.generateLoad(rs); load != nil { |
| f.Stmt = append(f.Stmt, load) |
| } |
| for _, r := range rs { |
| f.Stmt = append(f.Stmt, r.Call) |
| } |
| return f |
| } |
| |
| func (g *generator) generateRules(pkg *packages.Package) []*bf.Rule { |
| var rules []*bf.Rule |
| if pkg.Rel == "" { |
| rules = append(rules, newRule("go_prefix", []interface{}{g.c.GoPrefix}, nil)) |
| } |
| |
| cgoLibrary, r := g.generateCgoLib(pkg) |
| if r != nil { |
| rules = append(rules, r) |
| } |
| |
| library, r := g.generateLib(pkg, cgoLibrary) |
| if r != nil { |
| rules = append(rules, r) |
| } |
| |
| if r := g.generateBin(pkg, library); r != nil { |
| rules = append(rules, r) |
| } |
| |
| if r := g.filegroup(pkg); r != nil { |
| rules = append(rules, r) |
| } |
| |
| if r := g.generateTest(pkg, library); r != nil { |
| rules = append(rules, r) |
| } |
| |
| if r := g.generateXTest(pkg, library); r != nil { |
| rules = append(rules, r) |
| } |
| |
| return rules |
| } |
| |
| func (g *generator) generateBin(pkg *packages.Package, library string) *bf.Rule { |
| if !pkg.IsCommand() || pkg.Binary.Sources.IsEmpty() && library == "" { |
| return nil |
| } |
| name := filepath.Base(pkg.Dir) |
| visibility := checkInternalVisibility(pkg.Rel, "//visibility:public") |
| return g.generateRule(pkg.Rel, "go_binary", name, visibility, library, false, pkg.Binary) |
| } |
| |
| func (g *generator) generateLib(pkg *packages.Package, cgoName string) (string, *bf.Rule) { |
| if !pkg.Library.HasGo() && cgoName == "" { |
| return "", nil |
| } |
| |
| name := defaultLibName |
| var visibility string |
| if pkg.IsCommand() { |
| // Libraries made for a go_binary should not be exposed to the public. |
| visibility = "//visibility:private" |
| } else { |
| visibility = checkInternalVisibility(pkg.Rel, "//visibility:public") |
| } |
| |
| rule := g.generateRule(pkg.Rel, "go_library", name, visibility, cgoName, false, pkg.Library) |
| return name, rule |
| } |
| |
| func (g *generator) generateCgoLib(pkg *packages.Package) (string, *bf.Rule) { |
| if !pkg.CgoLibrary.HasGo() { |
| return "", nil |
| } |
| |
| name := defaultCgoLibName |
| visibility := "//visibility:private" |
| rule := g.generateRule(pkg.Rel, "cgo_library", name, visibility, "", false, pkg.CgoLibrary) |
| return name, rule |
| } |
| |
| // checkInternalVisibility overrides the given visibility if the package is |
| // internal. |
| func checkInternalVisibility(rel, visibility string) string { |
| if i := strings.LastIndex(rel, "/internal/"); i >= 0 { |
| visibility = fmt.Sprintf("//%s:__subpackages__", rel[:i]) |
| } else if strings.HasPrefix(rel, "internal/") { |
| visibility = "//:__subpackages__" |
| } |
| return visibility |
| } |
| |
| // filegroup is a small hack for directories with pre-generated .pb.go files |
| // and also source .proto files. This creates a filegroup for the .proto in |
| // addition to the usual go_library for the .pb.go files. |
| func (g *generator) filegroup(pkg *packages.Package) *bf.Rule { |
| if !pkg.HasPbGo || len(pkg.Protos) == 0 { |
| return nil |
| } |
| return newRule("filegroup", nil, []keyvalue{ |
| {key: "name", value: defaultProtosName}, |
| {key: "srcs", value: pkg.Protos}, |
| {key: "visibility", value: []string{"//visibility:public"}}, |
| }) |
| } |
| |
| func (g *generator) generateTest(pkg *packages.Package, library string) *bf.Rule { |
| if !pkg.Test.HasGo() { |
| return nil |
| } |
| |
| var name string |
| if library == "" || library == defaultLibName { |
| name = defaultTestName |
| } else { |
| name = library + "_test" |
| } |
| |
| return g.generateRule(pkg.Rel, "go_test", name, "", library, pkg.HasTestdata, pkg.Test) |
| } |
| |
| func (g *generator) generateXTest(pkg *packages.Package, library string) *bf.Rule { |
| if !pkg.XTest.HasGo() { |
| return nil |
| } |
| |
| var name string |
| if library == "" || library == defaultLibName { |
| name = defaultXTestName |
| } else { |
| name = library + "_xtest" |
| } |
| |
| return g.generateRule(pkg.Rel, "go_test", name, "", "", pkg.HasTestdata, pkg.XTest) |
| } |
| |
| func (g *generator) generateRule(rel, kind, name, visibility, library string, hasTestdata bool, target packages.Target) *bf.Rule { |
| // Construct attrs in the same order that bf.Rewrite uses. See |
| // namePriority in github.com/bazelbuild/buildtools/build/rewrite.go. |
| attrs := []keyvalue{ |
| {"name", name}, |
| } |
| if !target.Sources.IsEmpty() { |
| attrs = append(attrs, keyvalue{"srcs", target.Sources}) |
| } |
| if !target.CLinkOpts.IsEmpty() { |
| attrs = append(attrs, keyvalue{"clinkopts", target.CLinkOpts}) |
| } |
| if !target.COpts.IsEmpty() { |
| attrs = append(attrs, keyvalue{"copts", target.COpts}) |
| } |
| if hasTestdata { |
| glob := globvalue{patterns: []string{"testdata/**"}} |
| attrs = append(attrs, keyvalue{"data", glob}) |
| } |
| if library != "" { |
| attrs = append(attrs, keyvalue{"library", ":" + library}) |
| } |
| if visibility != "" { |
| attrs = append(attrs, keyvalue{"visibility", []string{visibility}}) |
| } |
| if !target.Imports.IsEmpty() { |
| deps := g.dependencies(target.Imports, rel) |
| attrs = append(attrs, keyvalue{"deps", deps}) |
| } |
| return newRule(kind, nil, attrs) |
| } |
| |
| func (g *generator) generateLoad(rs []*bf.Rule) bf.Expr { |
| loadableKinds := []string{ |
| // keep sorted |
| "cgo_library", |
| "go_binary", |
| "go_library", |
| "go_prefix", |
| "go_test", |
| } |
| |
| kinds := make(map[string]bool) |
| for _, r := range rs { |
| kinds[r.Kind()] = true |
| } |
| args := make([]bf.Expr, 0, len(kinds)+1) |
| args = append(args, &bf.StringExpr{Value: goRulesBzl}) |
| for _, k := range loadableKinds { |
| if kinds[k] { |
| args = append(args, &bf.StringExpr{Value: k}) |
| } |
| } |
| if len(args) == 1 { |
| return nil |
| } |
| return &bf.CallExpr{ |
| X: &bf.LiteralExpr{Token: "load"}, |
| List: args, |
| ForceCompact: true, |
| } |
| } |
| |
| func (g *generator) dependencies(imports packages.PlatformStrings, dir string) packages.PlatformStrings { |
| resolve := func(imp string) (string, error) { |
| if l, err := g.r.resolve(imp, dir); err != nil { |
| return "", fmt.Errorf("in dir %q, could not resolve import path %q: %v", dir, imp, err) |
| } else { |
| return l.String(), nil |
| } |
| } |
| |
| deps, errors := imports.Map(resolve) |
| for _, err := range errors { |
| log.Print(err) |
| } |
| deps.Clean() |
| return deps |
| } |
| |
| // isRelative determines if an importpath is relative. |
| func isRelative(importpath string) bool { |
| return strings.HasPrefix(importpath, "./") || strings.HasPrefix(importpath, "..") |
| } |