github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/language/proto/config.go (about)

     1  /* Copyright 2018 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 proto
    17  
    18  import (
    19  	"flag"
    20  	"fmt"
    21  	"log"
    22  	"path"
    23  	"strings"
    24  
    25  	"github.com/bazelbuild/bazel-gazelle/config"
    26  	"github.com/bazelbuild/bazel-gazelle/pathtools"
    27  	"github.com/bazelbuild/bazel-gazelle/rule"
    28  )
    29  
    30  // ProtoConfig contains configuration values related to protos.
    31  //
    32  // This type is public because other languages need to generate rules based
    33  // on protos, so this configuration may be relevant to them.
    34  type ProtoConfig struct {
    35  	// Mode determines how rules are generated for protos.
    36  	Mode Mode
    37  
    38  	// ModeExplicit indicates whether the proto mode was set explicitly.
    39  	ModeExplicit bool
    40  
    41  	// GoPrefix is the current Go prefix (the Go extension may set this in the
    42  	// root directory only). Used to generate proto rule names in the root
    43  	// directory when there are no proto files or the proto package name
    44  	// can't be determined.
    45  	// TODO(jayconrod): deprecate and remove Go-specific behavior.
    46  	GoPrefix string
    47  
    48  	// groupOption is an option name that Gazelle will use to group .proto
    49  	// files into proto_library rules. If unset, the proto package name is used.
    50  	groupOption string
    51  
    52  	// StripImportPrefix The prefix to strip from the paths of the .proto files.
    53  	// If set, Gazelle will apply this value to the strip_import_prefix attribute
    54  	// within the proto_library_rule.
    55  	StripImportPrefix string
    56  
    57  	// ImportPrefix The prefix to add to the paths of the .proto files.
    58  	// If set, Gazelle will apply this value to the import_prefix attribute
    59  	// within the proto_library_rule.
    60  	ImportPrefix string
    61  }
    62  
    63  // GetProtoConfig returns the proto language configuration. If the proto
    64  // extension was not run, it will return nil.
    65  func GetProtoConfig(c *config.Config) *ProtoConfig {
    66  	pc := c.Exts[protoName]
    67  	if pc == nil {
    68  		return nil
    69  	}
    70  	return pc.(*ProtoConfig)
    71  }
    72  
    73  // Mode determines how proto rules are generated.
    74  type Mode int
    75  
    76  const (
    77  	// DefaultMode generates proto_library rules. Other languages should generate
    78  	// library rules based on these (e.g., go_proto_library) and should ignore
    79  	// checked-in generated files (e.g., .pb.go files) when there is a .proto
    80  	// file with a similar name.
    81  	DefaultMode Mode = iota
    82  
    83  	// DisableMode ignores .proto files and generates empty proto_library rules.
    84  	// Checked-in generated files (e.g., .pb.go files) should be treated as
    85  	// normal sources.
    86  	DisableMode
    87  
    88  	// DisableGlobalMode is similar to DisableMode, but it also prevents
    89  	// the use of special cases in dependency resolution for well known types
    90  	// and Google APIs.
    91  	DisableGlobalMode
    92  
    93  	// LegacyMode generates filegroups for .proto files if .pb.go files are
    94  	// present in the same directory.
    95  	LegacyMode
    96  
    97  	// PackageMode generates a proto_library for each set of .proto files with
    98  	// the same package name in each directory.
    99  	PackageMode
   100  
   101  	// FileMode generates a proto_library for each .proto file.
   102  	FileMode
   103  )
   104  
   105  func ModeFromString(s string) (Mode, error) {
   106  	switch s {
   107  	case "default":
   108  		return DefaultMode, nil
   109  	case "disable":
   110  		return DisableMode, nil
   111  	case "disable_global":
   112  		return DisableGlobalMode, nil
   113  	case "legacy":
   114  		return LegacyMode, nil
   115  	case "package":
   116  		return PackageMode, nil
   117  	case "file":
   118  		return FileMode, nil
   119  	default:
   120  		return 0, fmt.Errorf("unrecognized proto mode: %q", s)
   121  	}
   122  }
   123  
   124  func (m Mode) String() string {
   125  	switch m {
   126  	case DefaultMode:
   127  		return "default"
   128  	case DisableMode:
   129  		return "disable"
   130  	case DisableGlobalMode:
   131  		return "disable_global"
   132  	case LegacyMode:
   133  		return "legacy"
   134  	case PackageMode:
   135  		return "package"
   136  	case FileMode:
   137  		return "file"
   138  	default:
   139  		log.Panicf("unknown mode %d", m)
   140  		return ""
   141  	}
   142  }
   143  
   144  func (m Mode) ShouldGenerateRules() bool {
   145  	switch m {
   146  	case DisableMode, DisableGlobalMode, LegacyMode:
   147  		return false
   148  	default:
   149  		return true
   150  	}
   151  }
   152  
   153  func (m Mode) ShouldIncludePregeneratedFiles() bool {
   154  	switch m {
   155  	case DisableMode, DisableGlobalMode, LegacyMode:
   156  		return true
   157  	default:
   158  		return false
   159  	}
   160  }
   161  
   162  func (m Mode) ShouldUseKnownImports() bool {
   163  	return m != DisableGlobalMode
   164  }
   165  
   166  type modeFlag struct {
   167  	mode *Mode
   168  }
   169  
   170  func (f *modeFlag) Set(value string) error {
   171  	if mode, err := ModeFromString(value); err != nil {
   172  		return err
   173  	} else {
   174  		*f.mode = mode
   175  		return nil
   176  	}
   177  }
   178  
   179  func (f *modeFlag) String() string {
   180  	var mode Mode
   181  	if f != nil && f.mode != nil {
   182  		mode = *f.mode
   183  	}
   184  	return mode.String()
   185  }
   186  
   187  func (*protoLang) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {
   188  	pc := &ProtoConfig{}
   189  	c.Exts[protoName] = pc
   190  
   191  	// Note: the -proto flag does not set the ModeExplicit flag. We want to
   192  	// be able to switch to DisableMode in vendor directories, even when
   193  	// this is set for compatibility with older versions.
   194  	fs.Var(&modeFlag{&pc.Mode}, "proto", "default: generates a proto_library rule for one package\n\tpackage: generates a proto_library rule for for each package\n\tdisable: does not touch proto rules\n\tdisable_global: does not touch proto rules and does not use special cases for protos in dependency resolution")
   195  	fs.StringVar(&pc.groupOption, "proto_group", "", "option name used to group .proto files into proto_library rules")
   196  	fs.StringVar(&pc.ImportPrefix, "proto_import_prefix", "", "When set, .proto source files in the srcs attribute of the rule are accessible at their path with this prefix appended on.")
   197  }
   198  
   199  func (*protoLang) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
   200  	return nil
   201  }
   202  
   203  func (*protoLang) KnownDirectives() []string {
   204  	return []string{"proto", "proto_group", "proto_strip_import_prefix", "proto_import_prefix"}
   205  }
   206  
   207  func (*protoLang) Configure(c *config.Config, rel string, f *rule.File) {
   208  	pc := &ProtoConfig{}
   209  	*pc = *GetProtoConfig(c)
   210  	c.Exts[protoName] = pc
   211  	if f != nil {
   212  		for _, d := range f.Directives {
   213  			switch d.Key {
   214  			case "proto":
   215  				mode, err := ModeFromString(d.Value)
   216  				if err != nil {
   217  					log.Print(err)
   218  					continue
   219  				}
   220  				pc.Mode = mode
   221  				pc.ModeExplicit = true
   222  			case "proto_group":
   223  				pc.groupOption = d.Value
   224  			case "proto_strip_import_prefix":
   225  				pc.StripImportPrefix = d.Value
   226  				if err := checkStripImportPrefix(pc.StripImportPrefix, rel); err != nil {
   227  					log.Print(err)
   228  				}
   229  			case "proto_import_prefix":
   230  				pc.ImportPrefix = d.Value
   231  			}
   232  		}
   233  	}
   234  	inferProtoMode(c, rel, f)
   235  }
   236  
   237  // inferProtoMode sets ProtoConfig.Mode based on the directory name and the
   238  // contents of f. If the proto mode is set explicitly, this function does not
   239  // change it. If this is a vendor directory, or go_proto_library is loaded from
   240  // another file, proto rule generation is disabled.
   241  //
   242  // TODO(jayconrod): this logic is archaic, now that rules are generated by
   243  // separate language extensions. Proto rule generation should be independent
   244  // from Go.
   245  func inferProtoMode(c *config.Config, rel string, f *rule.File) {
   246  	pc := GetProtoConfig(c)
   247  	if pc.Mode != DefaultMode || pc.ModeExplicit {
   248  		return
   249  	}
   250  	if pc.GoPrefix == wellKnownTypesGoPrefix {
   251  		pc.Mode = LegacyMode
   252  		return
   253  	}
   254  	if path.Base(rel) == "vendor" {
   255  		pc.Mode = DisableMode
   256  		return
   257  	}
   258  	if f == nil {
   259  		return
   260  	}
   261  	mode := DefaultMode
   262  outer:
   263  	for _, l := range f.Loads {
   264  		name := l.Name()
   265  		if name == "@io_bazel_rules_go//proto:def.bzl" {
   266  			break
   267  		}
   268  		if name == "@io_bazel_rules_go//proto:go_proto_library.bzl" {
   269  			mode = LegacyMode
   270  			break
   271  		}
   272  		for _, sym := range l.Symbols() {
   273  			if sym == "go_proto_library" {
   274  				mode = DisableMode
   275  				break outer
   276  			}
   277  		}
   278  	}
   279  	if mode == DefaultMode || pc.Mode == mode || c.ShouldFix && mode == LegacyMode {
   280  		return
   281  	}
   282  	pc.Mode = mode
   283  }
   284  
   285  func checkStripImportPrefix(prefix, rel string) error {
   286  	if prefix == "" {
   287  		return nil
   288  	}
   289  	if !strings.HasPrefix(prefix, "/") {
   290  		return fmt.Errorf("proto_strip_import_prefix should start with '/' for a prefix relative to the repository root")
   291  	}
   292  	if rel != "" && !pathtools.HasPrefix(rel, prefix[1:]) {
   293  		return fmt.Errorf("proto_strip_import_prefix %q not in directory %s", prefix, rel)
   294  	}
   295  	return nil
   296  }