github.com/wolfd/bazel-gazelle@v0.14.0/internal/merger/fix.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 merger 17 18 import ( 19 "fmt" 20 "strings" 21 22 "github.com/bazelbuild/bazel-gazelle/internal/rule" 23 ) 24 25 // FixLoads removes loads of unused go rules and adds loads of newly used rules. 26 // This should be called after FixFile and MergeFile, since symbols 27 // may be introduced that aren't loaded. 28 // 29 // This function calls File.Sync before processing loads. 30 func FixLoads(f *rule.File, knownLoads []rule.LoadInfo) { 31 knownFiles := make(map[string]bool) 32 knownKinds := make(map[string]string) 33 for _, l := range knownLoads { 34 knownFiles[l.Name] = true 35 for _, k := range l.Symbols { 36 knownKinds[k] = l.Name 37 } 38 } 39 40 // Sync the file. We need File.Loads and File.Rules to contain inserted 41 // statements and not deleted statements. 42 f.Sync() 43 44 // Scan load statements in the file. Keep track of loads of known files, 45 // since these may be changed. Keep track of symbols loaded from unknown 46 // files; we will not add loads for these. 47 var loads []*rule.Load 48 otherLoadedKinds := make(map[string]bool) 49 for _, l := range f.Loads { 50 if knownFiles[l.Name()] { 51 loads = append(loads, l) 52 continue 53 } 54 for _, sym := range l.Symbols() { 55 otherLoadedKinds[sym] = true 56 } 57 } 58 59 // Make a map of all the symbols from known files used in this file. 60 usedKinds := make(map[string]map[string]bool) 61 for _, r := range f.Rules { 62 kind := r.Kind() 63 if file, ok := knownKinds[kind]; ok && !otherLoadedKinds[kind] { 64 if usedKinds[file] == nil { 65 usedKinds[file] = make(map[string]bool) 66 } 67 usedKinds[file][kind] = true 68 } 69 } 70 71 // Fix the load statements. The order is important, so we iterate over 72 // knownLoads instead of knownFiles. 73 for _, known := range knownLoads { 74 file := known.Name 75 first := true 76 for _, l := range loads { 77 if l.Name() != file { 78 continue 79 } 80 if first { 81 fixLoad(l, file, usedKinds[file], knownKinds) 82 first = false 83 } else { 84 fixLoad(l, file, nil, knownKinds) 85 } 86 if l.IsEmpty() { 87 l.Delete() 88 } 89 } 90 if first { 91 load := fixLoad(nil, file, usedKinds[file], knownKinds) 92 if load != nil { 93 index := newLoadIndex(f, known.After) 94 load.Insert(f, index) 95 } 96 } 97 } 98 } 99 100 // fixLoad updates a load statement with the given symbols. If load is nil, 101 // a new load may be created and returned. Symbols in kinds will be added 102 // to the load if they're not already present. Known symbols not in kinds 103 // will be removed if present. Other symbols will be preserved. If load is 104 // empty, nil is returned. 105 func fixLoad(load *rule.Load, file string, kinds map[string]bool, knownKinds map[string]string) *rule.Load { 106 if load == nil { 107 if len(kinds) == 0 { 108 return nil 109 } 110 load = rule.NewLoad(file) 111 } 112 113 for k := range kinds { 114 load.Add(k) 115 } 116 for _, k := range load.Symbols() { 117 if knownKinds[k] != "" && !kinds[k] { 118 load.Remove(k) 119 } 120 } 121 return load 122 } 123 124 // newLoadIndex returns the index in stmts where a new load statement should 125 // be inserted. after is a list of function names that the load should not 126 // be inserted before. 127 func newLoadIndex(f *rule.File, after []string) int { 128 if len(after) == 0 { 129 return 0 130 } 131 index := 0 132 for _, r := range f.Rules { 133 for _, a := range after { 134 if r.Kind() == a && r.Index() >= index { 135 index = r.Index() + 1 136 } 137 } 138 } 139 return index 140 } 141 142 // FixWorkspace updates rules in the WORKSPACE file f that were used with an 143 // older version of rules_go or gazelle. 144 func FixWorkspace(f *rule.File) { 145 removeLegacyGoRepository(f) 146 } 147 148 // CheckGazelleLoaded searches the given WORKSPACE file for a repository named 149 // "bazel_gazelle". If no such repository is found *and* the repo is not 150 // declared with a directive *and* at least one load statement mentions 151 // the repository, a descriptive error will be returned. 152 // 153 // This should be called after modifications have been made to WORKSPACE 154 // (i.e., after FixLoads) before writing it to disk. 155 func CheckGazelleLoaded(f *rule.File) error { 156 needGazelle := false 157 for _, l := range f.Loads { 158 if strings.HasPrefix(l.Name(), "@bazel_gazelle//") { 159 needGazelle = true 160 } 161 } 162 if !needGazelle { 163 return nil 164 } 165 for _, r := range f.Rules { 166 if r.Name() == "bazel_gazelle" { 167 return nil 168 } 169 } 170 for _, d := range f.Directives { 171 if d.Key != "repo" { 172 continue 173 } 174 if fs := strings.Fields(d.Value); len(fs) > 0 && fs[0] == "bazel_gazelle" { 175 return nil 176 } 177 } 178 return fmt.Errorf(`%s: error: bazel_gazelle is not declared in WORKSPACE. 179 Without this repository, Gazelle cannot safely modify the WORKSPACE file. 180 See the instructions at https://github.com/bazelbuild/bazel-gazelle. 181 If the bazel_gazelle is declared inside a macro, you can suppress this error 182 by adding a comment like this to WORKSPACE: 183 # gazelle:repo bazel_gazelle 184 `, f.Path) 185 } 186 187 // removeLegacyGoRepository removes loads of go_repository from 188 // @io_bazel_rules_go. FixLoads should be called after this; it will load from 189 // @bazel_gazelle. 190 func removeLegacyGoRepository(f *rule.File) { 191 for _, l := range f.Loads { 192 if l.Name() == "@io_bazel_rules_go//go:def.bzl" { 193 l.Remove("go_repository") 194 if l.IsEmpty() { 195 l.Delete() 196 } 197 } 198 } 199 }