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  }