github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/clean.go (about) 1 // Copyright 2011 Google Inc. 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 package nin 16 17 import ( 18 "fmt" 19 "os" 20 ) 21 22 // Cleaner cleans a build directory. 23 type Cleaner struct { 24 state *State 25 config *BuildConfig 26 dyndepLoader DyndepLoader 27 removed map[string]struct{} 28 cleaned map[*Node]struct{} 29 cleanedFilesCount int // Number of files cleaned. 30 di DiskInterface 31 status int 32 } 33 34 // NewCleaner returns an initialized cleaner. 35 func NewCleaner(state *State, config *BuildConfig, di DiskInterface) *Cleaner { 36 return &Cleaner{ 37 state: state, 38 config: config, 39 dyndepLoader: NewDyndepLoader(state, di), 40 removed: map[string]struct{}{}, 41 cleaned: map[*Node]struct{}{}, 42 di: di, 43 } 44 } 45 46 // @return whether the cleaner is in verbose mode. 47 func (c *Cleaner) isVerbose() bool { 48 return c.config.Verbosity != Quiet && (c.config.Verbosity == Verbose || c.config.DryRun) 49 } 50 51 // @returns whether the file @a path exists. 52 func (c *Cleaner) fileExists(path string) bool { 53 mtime, err := c.di.Stat(path) 54 if mtime == -1 { 55 errorf("%s", err) 56 } 57 return mtime > 0 // Treat Stat() errors as "file does not exist". 58 } 59 60 func (c *Cleaner) report(path string) { 61 // TODO(maruel): Move this out to the caller. 62 c.cleanedFilesCount++ 63 if c.isVerbose() { 64 fmt.Printf("Remove %s\n", path) 65 } 66 } 67 68 // Remove the given @a path file only if it has not been already removed. 69 func (c *Cleaner) remove(path string) { 70 if _, ok := c.removed[path]; !ok { 71 c.removed[path] = struct{}{} 72 if c.config.DryRun { 73 if c.fileExists(path) { 74 c.report(path) 75 } 76 } else { 77 if err := c.di.RemoveFile(path); err == nil { 78 c.report(path) 79 } else if !os.IsNotExist(err) { 80 c.status = 1 81 } 82 } 83 } 84 } 85 86 // Remove the depfile and rspfile for an Edge. 87 func (c *Cleaner) removeEdgeFiles(edge *Edge) { 88 depfile := edge.GetUnescapedDepfile() 89 if len(depfile) != 0 { 90 c.remove(depfile) 91 } 92 93 rspfile := edge.GetUnescapedRspfile() 94 if len(rspfile) != 0 { 95 c.remove(rspfile) 96 } 97 } 98 99 func (c *Cleaner) printHeader() { 100 if c.config.Verbosity == Quiet { 101 return 102 } 103 fmt.Printf("Cleaning...") 104 if c.isVerbose() { 105 fmt.Printf("\n") 106 } else { 107 fmt.Printf(" ") 108 } 109 // TODO(maruel): fflush(stdout) 110 } 111 112 func (c *Cleaner) printFooter() { 113 if c.config.Verbosity == Quiet { 114 return 115 } 116 fmt.Printf("%d files.\n", c.cleanedFilesCount) 117 } 118 119 // CleanAll cleans all built files, except for files created by generator rules. 120 // 121 // If generator is set, also clean files created by generator rules. 122 // 123 // Return non-zero if an error occurs. 124 func (c *Cleaner) CleanAll(generator bool) int { 125 c.Reset() 126 c.printHeader() 127 c.loadDyndeps() 128 for _, e := range c.state.Edges { 129 // Do not try to remove phony targets 130 if e.Rule == PhonyRule { 131 continue 132 } 133 // Do not remove generator's files unless generator specified. 134 if !generator && e.GetBinding("generator") != "" { 135 continue 136 } 137 for _, outNode := range e.Outputs { 138 c.remove(outNode.Path) 139 } 140 141 c.removeEdgeFiles(e) 142 } 143 c.printFooter() 144 return c.status 145 } 146 147 // CleanDead cleans the files produced by previous builds that are no longer in 148 // the manifest. 149 // 150 // Returns non-zero if an error occurs. 151 func (c *Cleaner) CleanDead(entries map[string]*LogEntry) int { 152 c.Reset() 153 c.printHeader() 154 for k := range entries { 155 n := c.state.Paths[k] 156 // Detecting stale outputs works as follows: 157 // 158 // - If it has no Node, it is not in the build graph, or the deps log 159 // anymore, hence is stale. 160 // 161 // - If it isn't an output or input for any edge, it comes from a stale 162 // entry in the deps log, but no longer referenced from the build 163 // graph. 164 // 165 if n == nil || (n.InEdge == nil && len(n.OutEdges) == 0) { 166 c.remove(k) 167 } 168 } 169 c.printFooter() 170 return c.status 171 } 172 173 // Helper recursive method for cleanTarget(). 174 func (c *Cleaner) doCleanTarget(target *Node) { 175 if e := target.InEdge; e != nil { 176 // Do not try to remove phony targets 177 if e.Rule != PhonyRule { 178 c.remove(target.Path) 179 c.removeEdgeFiles(e) 180 } 181 for _, next := range e.Inputs { 182 // call doCleanTarget recursively if this node has not been visited 183 if _, ok := c.cleaned[next]; !ok { 184 c.doCleanTarget(next) 185 } 186 } 187 } 188 189 // mark this target to be cleaned already 190 c.cleaned[target] = struct{}{} 191 } 192 193 // Clean the given target @a target. 194 // @return non-zero if an error occurs. 195 // Clean the given @a target and all the file built for it. 196 // @return non-zero if an error occurs. 197 func (c *Cleaner) cleanTargetNode(target *Node) int { 198 if target == nil { 199 panic("oops") 200 } 201 202 c.Reset() 203 c.printHeader() 204 c.loadDyndeps() 205 c.doCleanTarget(target) 206 c.printFooter() 207 return c.status 208 } 209 210 // Clean the given target @a target. 211 // @return non-zero if an error occurs. 212 // Clean the given @a target and all the file built for it. 213 // @return non-zero if an error occurs. 214 func (c *Cleaner) cleanTarget(target string) int { 215 if target == "" { 216 panic("oops") 217 } 218 219 c.Reset() 220 node := c.state.Paths[target] 221 if node != nil { 222 c.cleanTargetNode(node) 223 } else { 224 errorf("unknown target '%s'", target) 225 c.status = 1 226 } 227 return c.status 228 } 229 230 // CleanTargets cleans the given target targets. 231 // 232 // Return non-zero if an error occurs. 233 func (c *Cleaner) CleanTargets(targets []string) int { 234 // TODO(maruel): Not unit tested. 235 c.Reset() 236 c.printHeader() 237 c.loadDyndeps() 238 for _, targetName := range targets { 239 if targetName == "" { 240 errorf("failed to canonicalize '': empty path") 241 c.status = 1 242 continue 243 } 244 targetName = CanonicalizePath(targetName) 245 target := c.state.Paths[targetName] 246 if target != nil { 247 if c.isVerbose() { 248 fmt.Printf("Target %s\n", targetName) 249 } 250 c.doCleanTarget(target) 251 } else { 252 errorf("unknown target '%s'", targetName) 253 c.status = 1 254 } 255 } 256 c.printFooter() 257 return c.status 258 } 259 260 func (c *Cleaner) doCleanRule(rule *Rule) { 261 if rule == nil { 262 panic("oops") 263 } 264 265 for _, e := range c.state.Edges { 266 if e.Rule.Name == rule.Name { 267 for _, outNode := range e.Outputs { 268 c.remove(outNode.Path) 269 c.removeEdgeFiles(e) 270 } 271 } 272 } 273 } 274 275 // CleanRule cleans the file produced by the given rule. 276 // 277 // Returns non-zero if an error occurs. 278 func (c *Cleaner) CleanRule(rule *Rule) int { 279 c.Reset() 280 c.printHeader() 281 c.loadDyndeps() 282 c.doCleanRule(rule) 283 c.printFooter() 284 return c.status 285 } 286 287 // CleanRuleName cleans the file produced by the given rule. 288 // 289 // Returns non-zero if an error occurs. 290 func (c *Cleaner) CleanRuleName(rule string) int { 291 if rule == "" { 292 panic("oops") 293 } 294 295 c.Reset() 296 r := c.state.Bindings.LookupRule(rule) 297 if r != nil { 298 c.CleanRule(r) 299 } else { 300 errorf("unknown rule '%s'", rule) 301 c.status = 1 302 } 303 return c.status 304 } 305 306 // CleanRules cleans the file produced by the given rules. 307 // 308 // Returns non-zero if an error occurs. 309 func (c *Cleaner) CleanRules(rules []string) int { 310 // TODO(maruel): Not unit tested. 311 if len(rules) == 0 { 312 panic("oops") 313 } 314 315 c.Reset() 316 c.printHeader() 317 c.loadDyndeps() 318 for _, ruleName := range rules { 319 rule := c.state.Bindings.LookupRule(ruleName) 320 if rule != nil { 321 if c.isVerbose() { 322 fmt.Printf("Rule %s\n", ruleName) 323 } 324 c.doCleanRule(rule) 325 } else { 326 errorf("unknown rule '%s'", ruleName) 327 c.status = 1 328 } 329 } 330 c.printFooter() 331 return c.status 332 } 333 334 // Reset reinitializes the cleaner stats. 335 func (c *Cleaner) Reset() { 336 c.status = 0 337 c.cleanedFilesCount = 0 338 c.removed = map[string]struct{}{} 339 c.cleaned = map[*Node]struct{}{} 340 } 341 342 // Load dependencies from dyndep bindings. 343 func (c *Cleaner) loadDyndeps() { 344 // Load dyndep files that exist, before they are cleaned. 345 for _, e := range c.state.Edges { 346 if e.Dyndep != nil { 347 // Capture and ignore errors loading the dyndep file. 348 // We clean as much of the graph as we know. 349 _ = c.dyndepLoader.LoadDyndeps(e.Dyndep, DyndepFile{}) 350 } 351 } 352 }