github.com/wolfd/bazel-gazelle@v0.14.0/internal/language/go/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 golang 17 18 import ( 19 "flag" 20 "fmt" 21 "go/build" 22 "log" 23 "path" 24 "strings" 25 26 "github.com/bazelbuild/bazel-gazelle/internal/config" 27 gzflag "github.com/bazelbuild/bazel-gazelle/internal/flag" 28 "github.com/bazelbuild/bazel-gazelle/internal/language/proto" 29 "github.com/bazelbuild/bazel-gazelle/internal/rule" 30 bzl "github.com/bazelbuild/buildtools/build" 31 ) 32 33 // goConfig contains configuration values related to Go rules. 34 type goConfig struct { 35 // genericTags is a set of tags that Gazelle considers to be true. Set with 36 // -build_tags or # gazelle:build_tags. Some tags, like gc, are always on. 37 genericTags map[string]bool 38 39 // prefix is a prefix of an import path, used to generate importpath 40 // attributes. Set with -go_prefix or # gazelle:prefix. 41 prefix string 42 43 // prefixRel is the package name of the directory where the prefix was set 44 // ("" for the root directory). 45 prefixRel string 46 47 // prefixSet indicates whether the prefix was set explicitly. It is an error 48 // to infer an importpath for a rule without setting the prefix. 49 prefixSet bool 50 51 // importMapPrefix is a prefix of a package path, used to generate importmap 52 // attributes. Set with # gazelle:importmap_prefix. 53 importMapPrefix string 54 55 // importMapPrefixRel is the package name of the directory where importMapPrefix 56 // was set ("" for the root directory). 57 importMapPrefixRel string 58 59 // depMode determines how imports that are not standard, indexed, or local 60 // (under the current prefix) should be resolved. 61 depMode dependencyMode 62 } 63 64 func newGoConfig() *goConfig { 65 gc := &goConfig{} 66 gc.preprocessTags() 67 return gc 68 } 69 70 func getGoConfig(c *config.Config) *goConfig { 71 return c.Exts[goName].(*goConfig) 72 } 73 74 func (gc *goConfig) clone() *goConfig { 75 gcCopy := *gc 76 gcCopy.genericTags = make(map[string]bool) 77 for k, v := range gc.genericTags { 78 gcCopy.genericTags[k] = v 79 } 80 return &gcCopy 81 } 82 83 // preprocessTags adds some tags which are on by default before they are 84 // used to match files. 85 func (gc *goConfig) preprocessTags() { 86 if gc.genericTags == nil { 87 gc.genericTags = make(map[string]bool) 88 } 89 gc.genericTags["gc"] = true 90 } 91 92 // setBuildTags sets genericTags by parsing as a comma separated list. An 93 // error will be returned for tags that wouldn't be recognized by "go build". 94 // preprocessTags should be called before this. 95 func (gc *goConfig) setBuildTags(tags string) error { 96 if tags == "" { 97 return nil 98 } 99 for _, t := range strings.Split(tags, ",") { 100 if strings.HasPrefix(t, "!") { 101 return fmt.Errorf("build tags can't be negated: %s", t) 102 } 103 gc.genericTags[t] = true 104 } 105 return nil 106 } 107 108 // dependencyMode determines how imports of packages outside of the prefix 109 // are resolved. 110 type dependencyMode int 111 112 const ( 113 // externalMode indicates imports should be resolved to external dependencies 114 // (declared in WORKSPACE). 115 externalMode dependencyMode = iota 116 117 // vendorMode indicates imports should be resolved to libraries in the 118 // vendor directory. 119 vendorMode 120 ) 121 122 func (m dependencyMode) String() string { 123 if m == externalMode { 124 return "external" 125 } else { 126 return "vendored" 127 } 128 } 129 130 type externalFlag struct { 131 depMode *dependencyMode 132 } 133 134 func (f *externalFlag) Set(value string) error { 135 switch value { 136 case "external": 137 *f.depMode = externalMode 138 case "vendored": 139 *f.depMode = vendorMode 140 default: 141 return fmt.Errorf("unrecognized dependency mode: %q", value) 142 } 143 return nil 144 } 145 146 func (f *externalFlag) String() string { 147 if f == nil || f.depMode == nil { 148 return "external" 149 } 150 return f.depMode.String() 151 } 152 153 type tagsFlag func(string) error 154 155 func (f tagsFlag) Set(value string) error { 156 return f(value) 157 } 158 159 func (f tagsFlag) String() string { 160 return "" 161 } 162 163 func (_ *goLang) KnownDirectives() []string { 164 return []string{ 165 "build_tags", 166 "importmap_prefix", 167 "prefix", 168 } 169 } 170 171 func (_ *goLang) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) { 172 gc := newGoConfig() 173 switch cmd { 174 case "fix", "update": 175 fs.Var( 176 tagsFlag(gc.setBuildTags), 177 "build_tags", 178 "comma-separated list of build tags. If not specified, Gazelle will not\n\tfilter sources with build constraints.") 179 fs.Var( 180 &gzflag.ExplicitFlag{Value: &gc.prefix, IsSet: &gc.prefixSet}, 181 "go_prefix", 182 "prefix of import paths in the current workspace") 183 fs.Var( 184 &externalFlag{&gc.depMode}, 185 "external", 186 "external: resolve external packages with go_repository\n\tvendored: resolve external packages as packages in vendor/") 187 } 188 c.Exts[goName] = gc 189 } 190 191 func (_ *goLang) CheckFlags(fs *flag.FlagSet, c *config.Config) error { 192 // The base of the -go_prefix flag may be used to generate proto_library 193 // rule names when there are no .proto sources (empty rules to be deleted) 194 // or when the package name can't be determined. 195 // TODO(jayconrod): deprecate and remove this behavior. 196 gc := getGoConfig(c) 197 pc := proto.GetProtoConfig(c) 198 pc.GoPrefix = gc.prefix 199 return nil 200 } 201 202 func (_ *goLang) Configure(c *config.Config, rel string, f *rule.File) { 203 var gc *goConfig 204 if raw, ok := c.Exts[goName]; !ok { 205 gc = newGoConfig() 206 } else { 207 gc = raw.(*goConfig).clone() 208 } 209 c.Exts[goName] = gc 210 211 if path.Base(rel) == "vendor" { 212 gc.importMapPrefix = inferImportPath(gc, rel) 213 gc.importMapPrefixRel = rel 214 gc.prefix = "" 215 gc.prefixRel = rel 216 } 217 218 if f != nil { 219 setPrefix := func(prefix string) { 220 if err := checkPrefix(prefix); err != nil { 221 log.Print(err) 222 return 223 } 224 gc.prefix = prefix 225 gc.prefixSet = true 226 gc.prefixRel = rel 227 } 228 for _, d := range f.Directives { 229 switch d.Key { 230 case "build_tags": 231 if err := gc.setBuildTags(d.Value); err != nil { 232 log.Print(err) 233 continue 234 } 235 gc.preprocessTags() 236 gc.setBuildTags(d.Value) 237 case "importmap_prefix": 238 gc.importMapPrefix = d.Value 239 gc.importMapPrefixRel = rel 240 case "prefix": 241 setPrefix(d.Value) 242 } 243 } 244 if !gc.prefixSet { 245 for _, r := range f.Rules { 246 switch r.Kind() { 247 case "go_prefix": 248 args := r.Args() 249 if len(args) != 1 { 250 continue 251 } 252 s, ok := args[0].(*bzl.StringExpr) 253 if !ok { 254 continue 255 } 256 setPrefix(s.Value) 257 258 case "gazelle": 259 if prefix := r.AttrString("prefix"); prefix != "" { 260 setPrefix(prefix) 261 } 262 } 263 } 264 } 265 } 266 } 267 268 // checkPrefix checks that a string may be used as a prefix. We forbid local 269 // (relative) imports and those beginning with "/". We allow the empty string, 270 // but generated rules must not have an empty importpath. 271 func checkPrefix(prefix string) error { 272 if strings.HasPrefix(prefix, "/") || build.IsLocalImport(prefix) { 273 return fmt.Errorf("invalid prefix: %q", prefix) 274 } 275 return nil 276 }