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