github.com/wolfd/bazel-gazelle@v0.14.0/internal/language/go/package.go (about)

     1  /* Copyright 2017 The Bazel Authors. All rights reserved.
     2  
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7     http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package golang
    17  
    18  import (
    19  	"fmt"
    20  	"log"
    21  	"path"
    22  	"sort"
    23  	"strings"
    24  
    25  	"github.com/bazelbuild/bazel-gazelle/internal/config"
    26  	"github.com/bazelbuild/bazel-gazelle/internal/language/proto"
    27  	"github.com/bazelbuild/bazel-gazelle/internal/rule"
    28  )
    29  
    30  // goPackage contains metadata for a set of .go and .proto files that can be
    31  // used to generate Go rules.
    32  type goPackage struct {
    33  	name, dir, rel        string
    34  	library, binary, test goTarget
    35  	proto                 protoTarget
    36  	hasTestdata           bool
    37  	importPath            string
    38  }
    39  
    40  // goTarget contains information used to generate an individual Go rule
    41  // (library, binary, or test).
    42  type goTarget struct {
    43  	sources, imports, copts, clinkopts platformStringsBuilder
    44  	cgo                                bool
    45  }
    46  
    47  // protoTarget contains information used to generate a go_proto_library rule.
    48  type protoTarget struct {
    49  	name        string
    50  	sources     platformStringsBuilder
    51  	imports     platformStringsBuilder
    52  	hasServices bool
    53  }
    54  
    55  // platformStringsBuilder is used to construct rule.PlatformStrings. Bazel
    56  // has some requirements for deps list (a dependency cannot appear in more
    57  // than one select expression; dependencies cannot be duplicated), so we need
    58  // to build these carefully.
    59  type platformStringsBuilder struct {
    60  	strs map[string]platformStringInfo
    61  }
    62  
    63  // platformStringInfo contains information about a single string (source,
    64  // import, or option).
    65  type platformStringInfo struct {
    66  	set       platformStringSet
    67  	oss       map[string]bool
    68  	archs     map[string]bool
    69  	platforms map[rule.Platform]bool
    70  }
    71  
    72  type platformStringSet int
    73  
    74  const (
    75  	genericSet platformStringSet = iota
    76  	osSet
    77  	archSet
    78  	platformSet
    79  )
    80  
    81  // addFile adds the file described by "info" to a target in the package "p" if
    82  // the file is buildable.
    83  //
    84  // "cgo" tells whether any ".go" file in the package contains cgo code. This
    85  // affects whether C files are added to targets.
    86  //
    87  // An error is returned if a file is buildable but invalid (for example, a
    88  // test .go file containing cgo code). Files that are not buildable will not
    89  // be added to any target (for example, .txt files).
    90  func (pkg *goPackage) addFile(c *config.Config, info fileInfo, cgo bool) error {
    91  	switch {
    92  	case info.ext == unknownExt || !cgo && (info.ext == cExt || info.ext == csExt):
    93  		return nil
    94  	case info.ext == protoExt:
    95  		if proto.GetProtoConfig(c).Mode == proto.LegacyMode {
    96  			// Only add files in legacy mode. This is used to generate a filegroup
    97  			// that contains all protos. In order modes, we get the .proto files
    98  			// from information emitted by the proto language extension.
    99  			pkg.proto.addFile(c, info)
   100  		}
   101  	case info.isTest:
   102  		if info.isCgo {
   103  			return fmt.Errorf("%s: use of cgo in test not supported", info.path)
   104  		}
   105  		pkg.test.addFile(c, info)
   106  	default:
   107  		pkg.library.addFile(c, info)
   108  	}
   109  
   110  	return nil
   111  }
   112  
   113  // isCommand returns true if the package name is "main".
   114  func (pkg *goPackage) isCommand() bool {
   115  	return pkg.name == "main"
   116  }
   117  
   118  // isBuildable returns true if anything in the package is buildable.
   119  // This is true if the package has Go code that satisfies build constraints
   120  // on any platform or has proto files not in legacy mode.
   121  func (pkg *goPackage) isBuildable(c *config.Config) bool {
   122  	return pkg.firstGoFile() != "" || !pkg.proto.sources.isEmpty()
   123  }
   124  
   125  // firstGoFile returns the name of a .go file if the package contains at least
   126  // one .go file, or "" otherwise.
   127  func (pkg *goPackage) firstGoFile() string {
   128  	goSrcs := []platformStringsBuilder{
   129  		pkg.library.sources,
   130  		pkg.binary.sources,
   131  		pkg.test.sources,
   132  	}
   133  	for _, sb := range goSrcs {
   134  		if sb.strs != nil {
   135  			for s := range sb.strs {
   136  				if strings.HasSuffix(s, ".go") {
   137  					return s
   138  				}
   139  			}
   140  		}
   141  	}
   142  	return ""
   143  }
   144  
   145  func (pkg *goPackage) haveCgo() bool {
   146  	return pkg.library.cgo || pkg.binary.cgo || pkg.test.cgo
   147  }
   148  
   149  func (pkg *goPackage) inferImportPath(c *config.Config) error {
   150  	if pkg.importPath != "" {
   151  		log.Panic("importPath already set")
   152  	}
   153  	gc := getGoConfig(c)
   154  	if !gc.prefixSet {
   155  		return fmt.Errorf("%s: go prefix is not set, so importpath can't be determined for rules. Set a prefix with a '# gazelle:prefix' comment or with -go_prefix on the command line", pkg.dir)
   156  	}
   157  	pkg.importPath = inferImportPath(gc, pkg.rel)
   158  
   159  	if pkg.rel == gc.prefixRel {
   160  		pkg.importPath = gc.prefix
   161  	} else {
   162  		fromPrefixRel := strings.TrimPrefix(pkg.rel, gc.prefixRel+"/")
   163  		pkg.importPath = path.Join(gc.prefix, fromPrefixRel)
   164  	}
   165  	return nil
   166  }
   167  
   168  func inferImportPath(gc *goConfig, rel string) string {
   169  	if rel == gc.prefixRel {
   170  		return gc.prefix
   171  	} else {
   172  		fromPrefixRel := strings.TrimPrefix(rel, gc.prefixRel+"/")
   173  		return path.Join(gc.prefix, fromPrefixRel)
   174  	}
   175  }
   176  
   177  func goProtoPackageName(pkg proto.Package) string {
   178  	if value, ok := pkg.Options["go_package"]; ok {
   179  		if strings.LastIndexByte(value, '/') == -1 {
   180  			return value
   181  		} else {
   182  			if i := strings.LastIndexByte(value, ';'); i != -1 {
   183  				return value[i+1:]
   184  			} else {
   185  				return path.Base(value)
   186  			}
   187  		}
   188  	}
   189  	return strings.Replace(pkg.Name, ".", "_", -1)
   190  }
   191  
   192  func goProtoImportPath(gc *goConfig, pkg proto.Package, rel string) string {
   193  	if value, ok := pkg.Options["go_package"]; ok {
   194  		if strings.LastIndexByte(value, '/') == -1 {
   195  			return inferImportPath(gc, rel)
   196  		} else if i := strings.LastIndexByte(value, ';'); i != -1 {
   197  			return value[:i]
   198  		} else {
   199  			return value
   200  		}
   201  	}
   202  	return inferImportPath(gc, rel)
   203  }
   204  
   205  func (t *goTarget) addFile(c *config.Config, info fileInfo) {
   206  	t.cgo = t.cgo || info.isCgo
   207  	add := getPlatformStringsAddFunction(c, info, nil)
   208  	add(&t.sources, info.name)
   209  	add(&t.imports, info.imports...)
   210  	for _, copts := range info.copts {
   211  		optAdd := add
   212  		if len(copts.tags) > 0 {
   213  			optAdd = getPlatformStringsAddFunction(c, info, copts.tags)
   214  		}
   215  		optAdd(&t.copts, copts.opts)
   216  	}
   217  	for _, clinkopts := range info.clinkopts {
   218  		optAdd := add
   219  		if len(clinkopts.tags) > 0 {
   220  			optAdd = getPlatformStringsAddFunction(c, info, clinkopts.tags)
   221  		}
   222  		optAdd(&t.clinkopts, clinkopts.opts)
   223  	}
   224  }
   225  
   226  func protoTargetFromProtoPackage(name string, pkg proto.Package) protoTarget {
   227  	target := protoTarget{name: name}
   228  	for f := range pkg.Files {
   229  		target.sources.addGenericString(f)
   230  	}
   231  	for i := range pkg.Imports {
   232  		target.imports.addGenericString(i)
   233  	}
   234  	target.hasServices = pkg.HasServices
   235  	return target
   236  }
   237  
   238  func (t *protoTarget) addFile(c *config.Config, info fileInfo) {
   239  	t.sources.addGenericString(info.name)
   240  	for _, imp := range info.imports {
   241  		t.imports.addGenericString(imp)
   242  	}
   243  	t.hasServices = t.hasServices || info.hasServices
   244  }
   245  
   246  // getPlatformStringsAddFunction returns a function used to add strings to
   247  // a *platformStringsBuilder under the same set of constraints. This is a
   248  // performance optimization to avoid evaluating constraints repeatedly.
   249  func getPlatformStringsAddFunction(c *config.Config, info fileInfo, cgoTags tagLine) func(sb *platformStringsBuilder, ss ...string) {
   250  	isOSSpecific, isArchSpecific := isOSArchSpecific(info, cgoTags)
   251  
   252  	switch {
   253  	case !isOSSpecific && !isArchSpecific:
   254  		if checkConstraints(c, "", "", info.goos, info.goarch, info.tags, cgoTags) {
   255  			return func(sb *platformStringsBuilder, ss ...string) {
   256  				for _, s := range ss {
   257  					sb.addGenericString(s)
   258  				}
   259  			}
   260  		}
   261  
   262  	case isOSSpecific && !isArchSpecific:
   263  		var osMatch []string
   264  		for _, os := range rule.KnownOSs {
   265  			if checkConstraints(c, os, "", info.goos, info.goarch, info.tags, cgoTags) {
   266  				osMatch = append(osMatch, os)
   267  			}
   268  		}
   269  		if len(osMatch) > 0 {
   270  			return func(sb *platformStringsBuilder, ss ...string) {
   271  				for _, s := range ss {
   272  					sb.addOSString(s, osMatch)
   273  				}
   274  			}
   275  		}
   276  
   277  	case !isOSSpecific && isArchSpecific:
   278  		var archMatch []string
   279  		for _, arch := range rule.KnownArchs {
   280  			if checkConstraints(c, "", arch, info.goos, info.goarch, info.tags, cgoTags) {
   281  				archMatch = append(archMatch, arch)
   282  			}
   283  		}
   284  		if len(archMatch) > 0 {
   285  			return func(sb *platformStringsBuilder, ss ...string) {
   286  				for _, s := range ss {
   287  					sb.addArchString(s, archMatch)
   288  				}
   289  			}
   290  		}
   291  
   292  	default:
   293  		var platformMatch []rule.Platform
   294  		for _, platform := range rule.KnownPlatforms {
   295  			if checkConstraints(c, platform.OS, platform.Arch, info.goos, info.goarch, info.tags, cgoTags) {
   296  				platformMatch = append(platformMatch, platform)
   297  			}
   298  		}
   299  		if len(platformMatch) > 0 {
   300  			return func(sb *platformStringsBuilder, ss ...string) {
   301  				for _, s := range ss {
   302  					sb.addPlatformString(s, platformMatch)
   303  				}
   304  			}
   305  		}
   306  	}
   307  
   308  	return func(_ *platformStringsBuilder, _ ...string) {}
   309  }
   310  
   311  func (sb *platformStringsBuilder) isEmpty() bool {
   312  	return sb.strs == nil
   313  }
   314  
   315  func (sb *platformStringsBuilder) hasGo() bool {
   316  	for s := range sb.strs {
   317  		if strings.HasSuffix(s, ".go") {
   318  			return true
   319  		}
   320  	}
   321  	return false
   322  }
   323  
   324  func (sb *platformStringsBuilder) addGenericString(s string) {
   325  	if sb.strs == nil {
   326  		sb.strs = make(map[string]platformStringInfo)
   327  	}
   328  	sb.strs[s] = platformStringInfo{set: genericSet}
   329  }
   330  
   331  func (sb *platformStringsBuilder) addOSString(s string, oss []string) {
   332  	if sb.strs == nil {
   333  		sb.strs = make(map[string]platformStringInfo)
   334  	}
   335  	si, ok := sb.strs[s]
   336  	if !ok {
   337  		si.set = osSet
   338  		si.oss = make(map[string]bool)
   339  	}
   340  	switch si.set {
   341  	case genericSet:
   342  		return
   343  	case osSet:
   344  		for _, os := range oss {
   345  			si.oss[os] = true
   346  		}
   347  	default:
   348  		si.convertToPlatforms()
   349  		for _, os := range oss {
   350  			for _, arch := range rule.KnownOSArchs[os] {
   351  				si.platforms[rule.Platform{OS: os, Arch: arch}] = true
   352  			}
   353  		}
   354  	}
   355  	sb.strs[s] = si
   356  }
   357  
   358  func (sb *platformStringsBuilder) addArchString(s string, archs []string) {
   359  	if sb.strs == nil {
   360  		sb.strs = make(map[string]platformStringInfo)
   361  	}
   362  	si, ok := sb.strs[s]
   363  	if !ok {
   364  		si.set = archSet
   365  		si.archs = make(map[string]bool)
   366  	}
   367  	switch si.set {
   368  	case genericSet:
   369  		return
   370  	case archSet:
   371  		for _, arch := range archs {
   372  			si.archs[arch] = true
   373  		}
   374  	default:
   375  		si.convertToPlatforms()
   376  		for _, arch := range archs {
   377  			for _, os := range rule.KnownArchOSs[arch] {
   378  				si.platforms[rule.Platform{OS: os, Arch: arch}] = true
   379  			}
   380  		}
   381  	}
   382  	sb.strs[s] = si
   383  }
   384  
   385  func (sb *platformStringsBuilder) addPlatformString(s string, platforms []rule.Platform) {
   386  	if sb.strs == nil {
   387  		sb.strs = make(map[string]platformStringInfo)
   388  	}
   389  	si, ok := sb.strs[s]
   390  	if !ok {
   391  		si.set = platformSet
   392  		si.platforms = make(map[rule.Platform]bool)
   393  	}
   394  	switch si.set {
   395  	case genericSet:
   396  		return
   397  	default:
   398  		si.convertToPlatforms()
   399  		for _, p := range platforms {
   400  			si.platforms[p] = true
   401  		}
   402  	}
   403  	sb.strs[s] = si
   404  }
   405  
   406  func (sb *platformStringsBuilder) build() rule.PlatformStrings {
   407  	var ps rule.PlatformStrings
   408  	for s, si := range sb.strs {
   409  		switch si.set {
   410  		case genericSet:
   411  			ps.Generic = append(ps.Generic, s)
   412  		case osSet:
   413  			if ps.OS == nil {
   414  				ps.OS = make(map[string][]string)
   415  			}
   416  			for os := range si.oss {
   417  				ps.OS[os] = append(ps.OS[os], s)
   418  			}
   419  		case archSet:
   420  			if ps.Arch == nil {
   421  				ps.Arch = make(map[string][]string)
   422  			}
   423  			for arch := range si.archs {
   424  				ps.Arch[arch] = append(ps.Arch[arch], s)
   425  			}
   426  		case platformSet:
   427  			if ps.Platform == nil {
   428  				ps.Platform = make(map[rule.Platform][]string)
   429  			}
   430  			for p := range si.platforms {
   431  				ps.Platform[p] = append(ps.Platform[p], s)
   432  			}
   433  		}
   434  	}
   435  	sort.Strings(ps.Generic)
   436  	if ps.OS != nil {
   437  		for _, ss := range ps.OS {
   438  			sort.Strings(ss)
   439  		}
   440  	}
   441  	if ps.Arch != nil {
   442  		for _, ss := range ps.Arch {
   443  			sort.Strings(ss)
   444  		}
   445  	}
   446  	if ps.Platform != nil {
   447  		for _, ss := range ps.Platform {
   448  			sort.Strings(ss)
   449  		}
   450  	}
   451  	return ps
   452  }
   453  
   454  func (sb *platformStringsBuilder) buildFlat() []string {
   455  	strs := make([]string, 0, len(sb.strs))
   456  	for s := range sb.strs {
   457  		strs = append(strs, s)
   458  	}
   459  	sort.Strings(strs)
   460  	return strs
   461  }
   462  
   463  func (si *platformStringInfo) convertToPlatforms() {
   464  	switch si.set {
   465  	case genericSet:
   466  		log.Panic("cannot convert generic string to platforms")
   467  	case platformSet:
   468  		return
   469  	case osSet:
   470  		si.set = platformSet
   471  		si.platforms = make(map[rule.Platform]bool)
   472  		for os := range si.oss {
   473  			for _, arch := range rule.KnownOSArchs[os] {
   474  				si.platforms[rule.Platform{OS: os, Arch: arch}] = true
   475  			}
   476  		}
   477  		si.oss = nil
   478  	case archSet:
   479  		si.set = platformSet
   480  		si.platforms = make(map[rule.Platform]bool)
   481  		for arch := range si.archs {
   482  			for _, os := range rule.KnownArchOSs[arch] {
   483  				si.platforms[rule.Platform{OS: os, Arch: arch}] = true
   484  			}
   485  		}
   486  		si.archs = nil
   487  	}
   488  }