blob: e0cc1c2864a690f1f64182a169f650b6ca7ac560 [file] [log] [blame] [edit]
/* Copyright 2018 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 golang
import (
"fmt"
"go/build"
"log"
"path"
"path/filepath"
"sort"
"strings"
"sync"
"github.com/bazelbuild/bazel-gazelle/config"
"github.com/bazelbuild/bazel-gazelle/language"
"github.com/bazelbuild/bazel-gazelle/language/proto"
"github.com/bazelbuild/bazel-gazelle/pathtools"
"github.com/bazelbuild/bazel-gazelle/rule"
)
func (gl *goLang) GenerateRules(args language.GenerateArgs) language.GenerateResult {
// Extract information about proto files. We need this to exclude .pb.go
// files and generate go_proto_library rules.
c := args.Config
gc := getGoConfig(c)
pcMode := getProtoMode(c)
// This is a collection of proto_library rule names that have a corresponding
// go_proto_library rule already generated.
goProtoRules := make(map[string]struct{})
var protoRuleNames []string
protoPackages := make(map[string]proto.Package)
protoFileInfo := make(map[string]proto.FileInfo)
for _, r := range args.OtherGen {
if r.Kind() == "go_proto_library" {
if proto := r.AttrString("proto"); proto != "" {
goProtoRules[proto] = struct{}{}
}
if protos := r.AttrStrings("protos"); protos != nil {
for _, proto := range protos {
goProtoRules[proto] = struct{}{}
}
}
}
if r.Kind() != "proto_library" {
continue
}
pkg := r.PrivateAttr(proto.PackageKey).(proto.Package)
protoPackages[r.Name()] = pkg
for name, info := range pkg.Files {
protoFileInfo[name] = info
}
protoRuleNames = append(protoRuleNames, r.Name())
}
sort.Strings(protoRuleNames)
var emptyProtoRuleNames []string
for _, r := range args.OtherEmpty {
if r.Kind() == "proto_library" {
emptyProtoRuleNames = append(emptyProtoRuleNames, r.Name())
}
}
// If proto rule generation is enabled, exclude .pb.go files that correspond
// to any .proto files present.
regularFiles := append([]string{}, args.RegularFiles...)
genFiles := append([]string{}, args.GenFiles...)
if !pcMode.ShouldIncludePregeneratedFiles() {
keep := func(f string) bool {
for _, suffix := range []string{".pb.go", "_grpc.pb.go"} {
if strings.HasSuffix(f, suffix) {
if _, ok := protoFileInfo[strings.TrimSuffix(f, suffix)+".proto"]; ok {
return false
}
}
}
return true
}
filterFiles(&regularFiles, keep)
filterFiles(&genFiles, keep)
}
// Split regular files into files which can determine the package name and
// import path and other files.
var goFiles, otherFiles []string
for _, f := range regularFiles {
if strings.HasSuffix(f, ".go") {
goFiles = append(goFiles, f)
} else {
otherFiles = append(otherFiles, f)
}
}
// Look for a subdirectory named testdata. Only treat it as data if it does
// not contain a buildable package.
var hasTestdata bool
for _, sub := range args.Subdirs {
if sub == "testdata" {
_, ok := gl.goPkgRels[path.Join(args.Rel, "testdata")]
hasTestdata = !ok
break
}
}
// Build a set of packages from files in this directory.
srcdir := args.Rel
if gc.goRepositoryMode {
// cgo opts such as '-L${SRCDIR}/libs' should become
// '-Lexternal/my_repo~/libs' in an external repo.
// We obtain the path from the repo root to support both cases of
// --experimental_sibling_repository_layout.
slashPath := filepath.ToSlash(c.RepoRoot)
segments := strings.Split(slashPath, "/")
repoName := segments[len(segments)-1]
previousSegment := segments[len(segments)-2]
if previousSegment == "external" {
srcdir = path.Join("external", repoName, srcdir)
} else {
srcdir = path.Join("..", repoName, srcdir)
}
}
goFileInfos := make([]fileInfo, len(goFiles))
var er *embedResolver
for i, name := range goFiles {
path := filepath.Join(args.Dir, name)
goFileInfos[i] = goFileInfo(path, srcdir)
if len(goFileInfos[i].embeds) > 0 && er == nil {
er = newEmbedResolver(args.Dir, args.Rel, c.ValidBuildFileNames, gl.goPkgRels, args.Subdirs, args.RegularFiles, args.GenFiles)
}
}
goPackageMap, goFilesWithUnknownPackage := buildPackages(c, args.Dir, args.Rel, hasTestdata, er, goFileInfos)
// Select a package to generate rules for. If there is no package, create
// an empty package so we can generate empty rules.
var protoName string
pkg, err := selectPackage(c, args.Dir, goPackageMap)
if err != nil {
if _, ok := err.(*build.NoGoError); ok {
if len(protoPackages) == 1 {
for name, ppkg := range protoPackages {
if _, ok := goProtoRules[":"+name]; ok {
// if a go_proto_library rule already exists for this
// proto package, treat it as if the proto package
// doesn't exist.
pkg = emptyPackage(c, args.Dir, args.Rel, args.File)
break
}
pkg = &goPackage{
name: goProtoPackageName(ppkg),
importPath: goProtoImportPath(c, ppkg, args.Rel),
proto: protoTargetFromProtoPackage(name, ppkg),
}
protoName = name
break
}
} else {
pkg = emptyPackage(c, args.Dir, args.Rel, args.File)
}
} else {
log.Print(err)
}
}
// Try to link the selected package with a proto package.
if pkg != nil {
if pkg.importPath == "" {
if err := pkg.inferImportPath(c); err != nil && pkg.firstGoFile() != "" {
inferImportPathErrorOnce.Do(func() { log.Print(err) })
}
}
for _, name := range protoRuleNames {
ppkg := protoPackages[name]
if pkg.importPath == goProtoImportPath(c, ppkg, args.Rel) {
protoName = name
pkg.proto = protoTargetFromProtoPackage(name, ppkg)
break
}
}
}
// Generate rules for proto packages. These should come before the other
// Go rules.
g := &generator{
c: c,
rel: args.Rel,
shouldSetVisibility: shouldSetVisibility(args),
}
var res language.GenerateResult
var rules []*rule.Rule
var protoEmbeds []string
var protoEmbed string
if pcMode == proto.FileMode {
// get the list of protoRuleNames that are not already in goProtoRules
var newProtoRuleNames []string
for _, name := range protoRuleNames {
if _, ok := goProtoRules[":"+name]; !ok {
newProtoRuleNames = append(newProtoRuleNames, name)
}
}
// get the protoTargets for the new protoRuleNames
var importPathToProtoTargets = make(map[string][]protoTarget)
for _, name := range newProtoRuleNames {
ppkg := protoPackages[name]
importPath := goProtoImportPath(c, ppkg, args.Rel)
importPathToProtoTargets[importPath] = append(importPathToProtoTargets[importPath], protoTargetFromProtoPackage(name, ppkg))
}
// Deterministically sort by order of importpath.
importPaths := []string{}
for importPath, _ := range importPathToProtoTargets {
importPaths = append(importPaths, importPath)
}
sort.Strings(importPaths)
for _, importPath := range importPaths {
var rs []*rule.Rule
protoTargets := importPathToProtoTargets[importPath]
protoEmbed, rs = g.generateProto(pcMode, protoTargets, importPath)
if protoEmbed != "" {
// check if rs is non-empty and that the first rule is a go_proto_library with the same importPath
if len(rs) > 0 && rs[0].Kind() == "go_proto_library" && rs[0].AttrString("importpath") == pkg.importPath {
protoEmbeds = append(protoEmbeds, protoEmbed)
}
}
rules = append(rules, rs...)
}
} else {
for _, name := range protoRuleNames {
// if a go_proto_library rule exists for this proto_library rule
// already, skip creating another go_proto_library for it, assuming
// that a different gazelle extension is responsible for
// go_proto_library rule generation.
if _, ok := goProtoRules[":"+name]; ok {
continue
}
ppkg := protoPackages[name]
var rs []*rule.Rule
if name == protoName {
var protoTargets []protoTarget
if pkg != nil {
protoTargets = append(protoTargets, pkg.proto)
}
protoEmbed, rs = g.generateProto(pcMode, []protoTarget{pkg.proto}, pkg.importPath)
if protoEmbed != "" {
// check if rs is non-empty and that the first rule is a go_proto_library with the same importPath
if len(rs) > 0 && rs[0].Kind() == "go_proto_library" && rs[0].AttrString("importpath") == pkg.importPath {
protoEmbeds = append(protoEmbeds, protoEmbed)
}
}
} else {
target := protoTargetFromProtoPackage(name, ppkg)
importPath := goProtoImportPath(c, ppkg, args.Rel)
_, rs = g.generateProto(pcMode, []protoTarget{target}, importPath)
}
rules = append(rules, rs...)
}
}
for _, name := range emptyProtoRuleNames {
goProtoName := strings.TrimSuffix(name, "_proto") + goProtoSuffix
res.Empty = append(res.Empty, rule.NewRule("go_proto_library", goProtoName))
}
if pkg != nil && (pcMode == proto.PackageMode || pcMode == proto.FileMode) && pkg.firstGoFile() == "" {
// In proto package mode, don't generate a go_library embedding a
// go_proto_library unless there are actually go files.
protoEmbeds = nil
}
// Complete the Go package and generate rules for that.
if pkg != nil {
// Add files with unknown packages. This happens when there are parse
// or I/O errors. We should keep the file in the srcs list and let the
// compiler deal with the error.
cgo := pkg.haveCgo()
for _, info := range goFilesWithUnknownPackage {
if err := pkg.addFile(c, er, info, cgo); err != nil {
log.Print(err)
}
}
// Process the other static files.
for _, file := range otherFiles {
info := otherFileInfo(filepath.Join(args.Dir, file))
if err := pkg.addFile(c, er, info, cgo); err != nil {
log.Print(err)
}
}
// Process generated files. Note that generated files may have the same names
// as static files. Bazel will use the generated files, but we will look at
// the content of static files, assuming they will be the same.
regularFileSet := make(map[string]bool)
for _, f := range regularFiles {
regularFileSet[f] = true
}
// Some of the generated files may have been consumed by other rules
consumedFileSet := make(map[string]bool)
for _, r := range args.OtherGen {
for _, f := range r.AttrStrings("srcs") {
consumedFileSet[f] = true
}
if f := r.AttrString("src"); f != "" {
consumedFileSet[f] = true
}
}
for _, f := range genFiles {
if regularFileSet[f] || consumedFileSet[f] {
continue
}
info := fileNameInfo(filepath.Join(args.Dir, f))
if err := pkg.addFile(c, er, info, cgo); err != nil {
log.Print(err)
}
}
// Generate Go rules.
if protoName == "" {
// Empty proto rules for deletion.
_, rs := g.generateProto(pcMode, []protoTarget{pkg.proto}, pkg.importPath)
rules = append(rules, rs...)
}
lib := g.generateLib(pkg, protoEmbeds)
var libName string
if !lib.IsEmpty(goKinds[lib.Kind()]) {
libName = lib.Name()
}
rules = append(rules, lib)
g.maybePublishToolLib(lib, pkg)
if r := g.maybeGenerateExtraLib(lib, pkg); r != nil {
rules = append(rules, r)
}
if r := g.maybeGenerateAlias(pkg, libName); r != nil {
g.maybePublishToolLib(r, pkg)
rules = append(rules, r)
}
rules = append(rules, g.generateBin(pkg, libName))
rules = append(rules, g.generateTests(pkg, libName)...)
}
for _, r := range rules {
if r.IsEmpty(goKinds[r.Kind()]) {
res.Empty = append(res.Empty, r)
} else {
res.Gen = append(res.Gen, r)
res.Imports = append(res.Imports, r.PrivateAttr(config.GazelleImportsKey))
}
}
if args.File != nil || len(res.Gen) > 0 {
gl.goPkgRels[args.Rel] = true
} else {
for _, sub := range args.Subdirs {
if _, ok := gl.goPkgRels[path.Join(args.Rel, sub)]; ok {
gl.goPkgRels[args.Rel] = false
break
}
}
}
return res
}
func filterFiles(files *[]string, pred func(string) bool) {
w := 0
for r := 0; r < len(*files); r++ {
f := (*files)[r]
if pred(f) {
(*files)[w] = f
w++
}
}
*files = (*files)[:w]
}
func buildPackages(c *config.Config, dir, rel string, hasTestdata bool, er *embedResolver, goFiles []fileInfo) (packageMap map[string]*goPackage, goFilesWithUnknownPackage []fileInfo) {
// Process .go and .proto files first, since these determine the package name.
packageMap = make(map[string]*goPackage)
for _, f := range goFiles {
if f.packageName == "" {
goFilesWithUnknownPackage = append(goFilesWithUnknownPackage, f)
continue
}
if f.packageName == "documentation" {
// go/build ignores this package
continue
}
if _, ok := packageMap[f.packageName]; !ok {
packageMap[f.packageName] = &goPackage{
name: f.packageName,
dir: dir,
rel: rel,
hasTestdata: hasTestdata,
}
}
if err := packageMap[f.packageName].addFile(c, er, f, false); err != nil {
log.Print(err)
}
}
return packageMap, goFilesWithUnknownPackage
}
var inferImportPathErrorOnce sync.Once
// selectPackages selects one Go packages out of the buildable packages found
// in a directory. If multiple packages are found, it returns the package
// whose name matches the directory if such a package exists.
func selectPackage(c *config.Config, dir string, packageMap map[string]*goPackage) (*goPackage, error) {
buildablePackages := make(map[string]*goPackage)
for name, pkg := range packageMap {
if pkg.isBuildable(c) {
buildablePackages[name] = pkg
}
}
if len(buildablePackages) == 0 {
return nil, &build.NoGoError{Dir: dir}
}
if len(buildablePackages) == 1 {
for _, pkg := range buildablePackages {
return pkg, nil
}
}
if pkg, ok := buildablePackages[defaultPackageName(c, dir)]; ok {
return pkg, nil
}
err := &build.MultiplePackageError{Dir: dir}
for name, pkg := range buildablePackages {
// Add the first file for each package for the error message.
// Error() method expects these lists to be the same length. File
// lists must be non-empty. These lists are only created by
// buildPackage for packages with .go files present.
err.Packages = append(err.Packages, name)
err.Files = append(err.Files, pkg.firstGoFile())
}
return nil, err
}
func emptyPackage(c *config.Config, dir, rel string, f *rule.File) *goPackage {
var pkgName string
if fileContainsGoBinary(c, f) {
// If the file contained a go_binary, its library may have a "_lib" suffix.
// Set the package name to "main" so that we generate an empty library rule
// with that name.
pkgName = "main"
} else {
pkgName = defaultPackageName(c, dir)
}
pkg := &goPackage{
name: pkgName,
dir: dir,
rel: rel,
}
return pkg
}
func defaultPackageName(c *config.Config, rel string) string {
gc := getGoConfig(c)
return pathtools.RelBaseName(rel, gc.prefix, "")
}
type generator struct {
c *config.Config
rel string
shouldSetVisibility bool
}
func (g *generator) generateProto(mode proto.Mode, targets []protoTarget, importPath string) (string, []*rule.Rule) {
if !mode.ShouldGenerateRules() && mode != proto.LegacyMode {
// Don't create or delete proto rules in this mode. When proto mode is disabled,
// there may be hand-written rules or pre-generated Go files
return "", nil
}
gc := getGoConfig(g.c)
filegroupName := legacyProtoFilegroupName
var protoName string
if len(targets) == 1 {
protoName = targets[0].name
}
if protoName == "" {
if importPath == "" {
importPath = InferImportPath(g.c, g.rel)
}
protoName = proto.RuleName(importPath)
}
goProtoName := strings.TrimSuffix(protoName, "_proto") + goProtoSuffix
visibility := g.commonVisibility(importPath)
if mode == proto.LegacyMode {
filegroup := rule.NewRule("filegroup", filegroupName)
if targets[0].sources.isEmpty() {
return "", []*rule.Rule{filegroup}
}
filegroup.SetAttr("srcs", targets[0].sources.build())
if g.shouldSetVisibility {
filegroup.SetAttr("visibility", visibility)
}
return "", []*rule.Rule{filegroup}
}
var atLeastOneTargetHasSources bool
for _, target := range targets {
if !target.sources.isEmpty() {
atLeastOneTargetHasSources = true
break
}
}
if !atLeastOneTargetHasSources {
return "", []*rule.Rule{
rule.NewRule("filegroup", filegroupName),
rule.NewRule("go_proto_library", goProtoName),
}
}
goProtoLibrary := rule.NewRule("go_proto_library", goProtoName)
if len(targets) == 1 {
goProtoLibrary.SetAttr("proto", ":"+protoName)
} else {
// generate protoNames with prefix ":"
protoNames := make([]string, len(targets))
for i := range targets {
protoNames[i] = ":" + targets[i].name
}
goProtoLibrary.SetAttr("protos", protoNames)
}
g.setImportAttrs(goProtoLibrary, importPath)
var atLeastOneTargetHasServices bool
for _, target := range targets {
if target.hasServices {
atLeastOneTargetHasServices = true
break
}
}
if atLeastOneTargetHasServices {
goProtoLibrary.SetAttr("compilers", gc.goGrpcCompilers)
} else if gc.goProtoCompilersSet {
goProtoLibrary.SetAttr("compilers", gc.goProtoCompilers)
}
if g.shouldSetVisibility {
goProtoLibrary.SetAttr("visibility", visibility)
}
if len(targets) == 1 {
goProtoLibrary.SetPrivateAttr(config.GazelleImportsKey, targets[0].imports.build())
} else {
protoSources := make(map[string]struct{})
for _, target := range targets {
for _, src := range target.sources.build().Generic {
// if src starts with the repo root plus a slash, trim it
// so that src is relative to the repo root, which is
// needed for the dep resolution to work correctly
if strings.HasPrefix(src, g.c.RepoRoot+"/") {
src = src[len(g.c.RepoRoot)+1:]
}
protoSources[src] = struct{}{}
}
}
var combinedImports platformStringsBuilder
for _, target := range targets {
builtImports := target.imports.build()
// handle Generics
for _, genericString := range builtImports.Generic {
// if the generic string is not in the set of generic src strings, add it
if _, ok := protoSources[genericString]; !ok {
combinedImports.addGenericString(genericString)
}
}
}
goProtoLibrary.SetPrivateAttr(config.GazelleImportsKey, combinedImports.build())
}
return goProtoName, []*rule.Rule{goProtoLibrary}
}
func (g *generator) generateLib(pkg *goPackage, embeds []string) *rule.Rule {
gc := getGoConfig(g.c)
name := libNameByConvention(gc.goNamingConvention, pkg.importPath, pkg.name)
goLibrary := rule.NewRule("go_library", name)
if !pkg.library.sources.hasGo() && len(embeds) == 0 {
return goLibrary // empty
}
var visibility []string
if pkg.isCommand() {
// By default, libraries made for a go_binary should not be exposed to the public.
visibility = []string{"//visibility:private"}
if len(getGoConfig(g.c).goVisibility) > 0 {
visibility = getGoConfig(g.c).goVisibility
}
} else {
visibility = g.commonVisibility(pkg.importPath)
}
g.setCommonAttrs(goLibrary, pkg.rel, visibility, pkg.library, embeds)
g.setImportAttrs(goLibrary, pkg.importPath)
return goLibrary
}
func (g *generator) maybeGenerateAlias(pkg *goPackage, libName string) *rule.Rule {
if pkg.isCommand() || libName == "" {
return nil
}
gc := getGoConfig(g.c)
if gc.goNamingConvention == goDefaultLibraryNamingConvention {
return nil
}
alias := rule.NewRule("alias", defaultLibName)
alias.SetAttr("visibility", g.commonVisibility(pkg.importPath))
if gc.goNamingConvention == importAliasNamingConvention {
alias.SetAttr("actual", ":"+libName)
}
return alias
}
func (g *generator) generateBin(pkg *goPackage, library string) *rule.Rule {
gc := getGoConfig(g.c)
name := binName(pkg.rel, gc.prefix, g.c.RepoRoot)
goBinary := rule.NewRule("go_binary", name)
if !pkg.isCommand() || pkg.binary.sources.isEmpty() && library == "" {
return goBinary // empty
}
visibility := g.commonVisibility(pkg.importPath)
g.setCommonAttrs(goBinary, pkg.rel, visibility, pkg.binary, []string{library})
return goBinary
}
func (g *generator) generateTests(pkg *goPackage, library string) []*rule.Rule {
gc := getGoConfig(g.c)
tests := pkg.tests
if len(tests) == 0 && gc.testMode == defaultTestMode {
tests = []goTarget{goTarget{}}
}
var name func(goTarget) string
switch gc.testMode {
case defaultTestMode:
name = func(goTarget) string {
return testNameByConvention(gc.goNamingConvention, pkg.importPath)
}
case fileTestMode:
name = func(test goTarget) string {
if test.sources.hasGo() {
if srcs := test.sources.buildFlat(); len(srcs) == 1 {
return testNameFromSingleSource(srcs[0])
}
}
return testNameByConvention(gc.goNamingConvention, pkg.importPath)
}
}
var res []*rule.Rule
for i, test := range tests {
goTest := rule.NewRule("go_test", name(test))
hasGo := test.sources.hasGo()
if hasGo || i == 0 {
res = append(res, goTest)
if !hasGo {
continue
}
}
var embeds []string
if test.hasInternalTest {
if library != "" {
embeds = append(embeds, library)
}
}
g.setCommonAttrs(goTest, pkg.rel, nil, test, embeds)
if pkg.hasTestdata {
goTest.SetAttr("data", rule.GlobValue{Patterns: []string{"testdata/**"}})
}
}
return res
}
// maybePublishToolLib makes the given go_library rule public if needed for nogo.
// Updating it here automatically makes it easier to upgrade org_golang_x_tools.
func (g *generator) maybePublishToolLib(lib *rule.Rule, pkg *goPackage) {
if pkg.importPath == "golang.org/x/tools/go/analysis/internal/facts" || pkg.importPath == "golang.org/x/tools/internal/facts" {
// Imported by nogo main. We add a visibility exception.
lib.SetAttr("visibility", []string{"//visibility:public"})
}
}
// maybeGenerateExtraLib generates extra equivalent library targets for
// certain protobuf libraries. Historically, these "_gen" targets depend on Well Known Types
// built with go_proto_library and are used together with go_proto_library.
// However, these are no longer needed and are kept as aliases to be backward-compatible
func (g *generator) maybeGenerateExtraLib(lib *rule.Rule, pkg *goPackage) *rule.Rule {
gc := getGoConfig(g.c)
if gc.prefix != "github.com/golang/protobuf" || gc.prefixRel != "" {
return nil
}
var r *rule.Rule
switch pkg.importPath {
case "github.com/golang/protobuf/descriptor":
r = rule.NewRule("alias", "go_default_library_gen")
r.SetAttr("actual", ":go_default_library")
r.SetAttr("visibility", []string{"//visibility:public"})
case "github.com/golang/protobuf/jsonpb":
r = rule.NewRule("alias", "go_default_library_gen")
r.SetAttr("actual", ":go_default_library")
r.SetAttr("visibility", []string{"//visibility:public"})
case "github.com/golang/protobuf/protoc-gen-go/generator":
r = rule.NewRule("alias", "go_default_library_gen")
r.SetAttr("actual", ":go_default_library")
r.SetAttr("visibility", []string{"//visibility:public"})
case "github.com/golang/protobuf/ptypes":
r = rule.NewRule("alias", "go_default_library_gen")
r.SetAttr("actual", ":go_default_library")
r.SetAttr("visibility", []string{"//visibility:public"})
}
return r
}
func (g *generator) setCommonAttrs(r *rule.Rule, pkgRel string, visibility []string, target goTarget, embeds []string) {
if !target.sources.isEmpty() {
r.SetAttr("srcs", target.sources.buildFlat())
}
if !target.embedSrcs.isEmpty() {
r.SetAttr("embedsrcs", target.embedSrcs.build())
}
if target.cgo {
r.SetAttr("cgo", true)
}
if !target.clinkopts.isEmpty() {
r.SetAttr("clinkopts", g.options(target.clinkopts.build(), pkgRel))
}
if !target.cppopts.isEmpty() {
r.SetAttr("cppopts", g.options(target.cppopts.build(), pkgRel))
}
if !target.copts.isEmpty() {
r.SetAttr("copts", g.options(target.copts.build(), pkgRel))
}
if !target.cxxopts.isEmpty() {
r.SetAttr("cxxopts", g.options(target.cxxopts.build(), pkgRel))
}
if g.shouldSetVisibility && len(visibility) > 0 {
r.SetAttr("visibility", visibility)
}
if len(embeds) > 0 {
colonEmbeds := make([]string, 0, len(embeds))
for _, embed := range embeds {
colonEmbeds = append(colonEmbeds, ":"+embed)
}
r.SetAttr("embed", colonEmbeds)
}
r.SetPrivateAttr(config.GazelleImportsKey, target.imports.build())
}
func (g *generator) setImportAttrs(r *rule.Rule, importPath string) {
gc := getGoConfig(g.c)
r.SetAttr("importpath", importPath)
// Set importpath_aliases if we need minimal module compatibility.
// If a package is part of a module with a v2+ semantic import version
// suffix, packages that are not part of modules may import it without
// the suffix.
if gc.goRepositoryMode && gc.moduleMode && pathtools.HasPrefix(importPath, gc.prefix) && gc.prefixRel == "" {
if mmcImportPath := pathWithoutSemver(importPath); mmcImportPath != "" {
r.SetAttr("importpath_aliases", []string{mmcImportPath})
}
}
if gc.importMapPrefix != "" {
fromPrefixRel := pathtools.TrimPrefix(g.rel, gc.importMapPrefixRel)
importMap := path.Join(gc.importMapPrefix, fromPrefixRel)
if importMap != importPath {
r.SetAttr("importmap", importMap)
}
}
}
func (g *generator) commonVisibility(importPath string) []string {
// If the Bazel package name (rel) contains "internal", add visibility for
// subpackages of the parent.
// If the import path contains "internal" but rel does not, this is
// probably an internal submodule. Add visibility for all subpackages.
relIndex := pathtools.Index(g.rel, "internal")
importIndex := pathtools.Index(importPath, "internal")
visibility := getGoConfig(g.c).goVisibility
if relIndex >= 0 {
parent := strings.TrimSuffix(g.rel[:relIndex], "/")
visibility = append(visibility, fmt.Sprintf("//%s:__subpackages__", parent))
} else if importIndex >= 0 {
// This entire module is within an internal directory.
// Identify other repos which should have access too.
visibility = append(visibility, "//:__subpackages__")
for _, repo := range g.c.Repos {
if pathtools.HasPrefix(repo.AttrString("importpath"), importPath[:importIndex]) {
visibility = append(visibility, "@"+repo.Name()+"//:__subpackages__")
}
}
} else {
return []string{"//visibility:public"}
}
// Add visibility for any submodules that have the internal parent as
// a prefix of their module path.
if importIndex >= 0 {
gc := getGoConfig(g.c)
internalRoot := strings.TrimSuffix(importPath[:importIndex], "/")
for _, m := range gc.submodules {
if strings.HasPrefix(m.modulePath, internalRoot) {
visibility = append(visibility, fmt.Sprintf("@%s//:__subpackages__", m.repoName))
}
}
}
return visibility
}
var (
// shortOptPrefixes are strings that come at the beginning of an option
// argument that includes a path, e.g., -Ifoo/bar.
shortOptPrefixes = []string{"-I", "-L", "-F"}
// longOptPrefixes are separate arguments that come before a path argument,
// e.g., -iquote foo/bar.
longOptPrefixes = []string{"-I", "-L", "-F", "-iquote", "-isystem"}
)
// options transforms package-relative paths in cgo options into repository-
// root-relative paths that Bazel can understand. For example, if a cgo file
// in //foo declares an include flag in its copts: "-Ibar", this method
// will transform that flag into "-Ifoo/bar".
func (g *generator) options(opts rule.PlatformStrings, pkgRel string) rule.PlatformStrings {
fixPath := func(opt string) string {
if strings.HasPrefix(opt, "/") {
return opt
}
return path.Clean(path.Join(pkgRel, opt))
}
fixGroups := func(groups []string) ([]string, error) {
fixedGroups := make([]string, len(groups))
for i, group := range groups {
opts := strings.Split(group, optSeparator)
fixedOpts := make([]string, len(opts))
isPath := false
for j, opt := range opts {
if isPath {
opt = fixPath(opt)
isPath = false
goto next
}
for _, short := range shortOptPrefixes {
if strings.HasPrefix(opt, short) && len(opt) > len(short) {
opt = short + fixPath(opt[len(short):])
goto next
}
}
for _, long := range longOptPrefixes {
if opt == long {
isPath = true
goto next
}
}
next:
fixedOpts[j] = escapeOption(opt)
}
fixedGroups[i] = strings.Join(fixedOpts, " ")
}
return fixedGroups, nil
}
opts, errs := opts.MapSlice(fixGroups)
if errs != nil {
log.Panicf("unexpected error when transforming options with pkg %q: %v", pkgRel, errs)
}
return opts
}
func escapeOption(opt string) string {
return strings.NewReplacer(
`\`, `\\`,
`'`, `\'`,
`"`, `\"`,
` `, `\ `,
"\t", "\\\t",
"\n", "\\\n",
"\r", "\\\r",
"$(", "$(",
"$", "$$",
).Replace(opt)
}
func shouldSetVisibility(args language.GenerateArgs) bool {
if args.File != nil && args.File.HasDefaultVisibility() {
return false
}
for _, r := range args.OtherGen {
// This is kind of the same test as *File.HasDefaultVisibility(),
// but for previously defined rules.
if r.Kind() == "package" && r.Attr("default_visibility") != nil {
return false
}
}
return true
}