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 }