github.com/afking/bazel-gazelle@v0.0.0-20180301150245-c02bc0f529e8/internal/config/directives.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 config 17 18 import ( 19 "log" 20 "path" 21 "regexp" 22 "strings" 23 24 bf "github.com/bazelbuild/buildtools/build" 25 ) 26 27 // Directive is a key-value pair extracted from a top-level comment in 28 // a build file. Directives have the following format: 29 // 30 // # gazelle:key value 31 // 32 // Keys may not contain spaces. Values may be empty and may contain spaces, 33 // but surrounding space is trimmed. 34 type Directive struct { 35 Key, Value string 36 } 37 38 // Top-level directives apply to the whole package or build file. They must 39 // appear before the first statement. 40 var knownTopLevelDirectives = map[string]bool{ 41 "build_file_name": true, 42 "build_tags": true, 43 "exclude": true, 44 "prefix": true, 45 "ignore": true, 46 "proto": true, 47 } 48 49 // TODO(jayconrod): annotation directives will apply to an individual rule. 50 // They must appear in the block of comments above that rule. 51 52 // ParseDirectives scans f for Gazelle directives. The full list of directives 53 // is returned. Errors are reported for unrecognized directives and directives 54 // out of place (after the first statement). 55 func ParseDirectives(f *bf.File) []Directive { 56 var directives []Directive 57 parseComment := func(com bf.Comment) { 58 match := directiveRe.FindStringSubmatch(com.Token) 59 if match == nil { 60 return 61 } 62 key, value := match[1], match[2] 63 if _, ok := knownTopLevelDirectives[key]; !ok { 64 log.Printf("%s:%d: unknown directive: %s", f.Path, com.Start.Line, com.Token) 65 return 66 } 67 directives = append(directives, Directive{key, value}) 68 } 69 70 for _, s := range f.Stmt { 71 coms := s.Comment() 72 for _, com := range coms.Before { 73 parseComment(com) 74 } 75 for _, com := range coms.After { 76 parseComment(com) 77 } 78 } 79 return directives 80 } 81 82 var directiveRe = regexp.MustCompile(`^#\s*gazelle:(\w+)\s*(.*?)\s*$`) 83 84 // ApplyDirectives applies directives that modify the configuration to a copy of 85 // c, which is returned. If there are no configuration directives, c is returned 86 // unmodified. 87 func ApplyDirectives(c *Config, directives []Directive, rel string) *Config { 88 modified := *c 89 didModify := false 90 for _, d := range directives { 91 switch d.Key { 92 case "build_tags": 93 if err := modified.SetBuildTags(d.Value); err != nil { 94 log.Print(err) 95 modified.GenericTags = c.GenericTags 96 } else { 97 modified.PreprocessTags() 98 didModify = true 99 } 100 case "build_file_name": 101 modified.ValidBuildFileNames = strings.Split(d.Value, ",") 102 didModify = true 103 case "prefix": 104 if err := CheckPrefix(d.Value); err != nil { 105 log.Print(err) 106 continue 107 } 108 modified.GoPrefix = d.Value 109 modified.GoPrefixRel = rel 110 didModify = true 111 case "proto": 112 protoMode, err := ProtoModeFromString(d.Value) 113 if err != nil { 114 log.Print(err) 115 continue 116 } 117 modified.ProtoMode = protoMode 118 modified.ProtoModeExplicit = true 119 didModify = true 120 } 121 } 122 if !didModify { 123 return c 124 } 125 return &modified 126 } 127 128 // InferProtoMode sets Config.ProtoMode, based on the contents of f. If the 129 // proto mode is already set to something other than the default, or if the mode 130 // is set explicitly in directives, this function does not change it. If the 131 // legacy go_proto_library.bzl is loaded, or if this is the Well Known Types 132 // repository, legacy mode is used. If go_proto_library is loaded from another 133 // file, proto rule generation is disabled. 134 func InferProtoMode(c *Config, rel string, f *bf.File, directives []Directive) *Config { 135 if c.ProtoMode != DefaultProtoMode || c.ProtoModeExplicit { 136 return c 137 } 138 for _, d := range directives { 139 if d.Key == "proto" { 140 return c 141 } 142 } 143 if c.GoPrefix == WellKnownTypesGoPrefix { 144 // Use legacy mode in this repo. We don't need proto_library or 145 // go_proto_library, since we get that from @com_google_protobuf. 146 // Legacy rules still refer to .proto files in here, which need are 147 // exposed by filegroup. go_library rules from .pb.go files will be 148 // generated, which are depended upon by the new rules. 149 modified := *c 150 modified.ProtoMode = LegacyProtoMode 151 return &modified 152 } 153 if path.Base(rel) == "vendor" { 154 modified := *c 155 modified.ProtoMode = DisableProtoMode 156 return &modified 157 } 158 if f == nil { 159 return c 160 } 161 mode := DefaultProtoMode 162 for _, stmt := range f.Stmt { 163 c, ok := stmt.(*bf.CallExpr) 164 if !ok { 165 continue 166 } 167 x, ok := c.X.(*bf.LiteralExpr) 168 if !ok || x.Token != "load" || len(c.List) == 0 { 169 continue 170 } 171 name, ok := c.List[0].(*bf.StringExpr) 172 if !ok { 173 continue 174 } 175 if name.Value == "@io_bazel_rules_go//proto:def.bzl" { 176 break 177 } 178 if name.Value == "@io_bazel_rules_go//proto:go_proto_library.bzl" { 179 mode = LegacyProtoMode 180 break 181 } 182 for _, arg := range c.List[1:] { 183 if sym, ok := arg.(*bf.StringExpr); ok && sym.Value == "go_proto_library" { 184 mode = DisableProtoMode 185 break 186 } 187 kwarg, ok := arg.(*bf.BinaryExpr) 188 if !ok || kwarg.Op != "=" { 189 continue 190 } 191 if key, ok := kwarg.X.(*bf.LiteralExpr); ok && key.Token == "go_proto_library" { 192 mode = DisableProtoMode 193 break 194 } 195 } 196 } 197 if mode == DefaultProtoMode || c.ProtoMode == mode || c.ShouldFix && mode == LegacyProtoMode { 198 return c 199 } 200 modified := *c 201 modified.ProtoMode = mode 202 return &modified 203 }