code: func (c Caches) Prune() (int, error) {
  	counter := 0
  	for k, cache := range c {
  		err := afero.Walk(cache.Fs, "", func(name string, info os.FileInfo, err error) error {
  			if info == nil {
  				return nil
  			}
  
  			name = cleanID(name)
  
  			if info.IsDir() {
  				f, err := cache.Fs.Open(name)
  				if err != nil {
  					// This cache dir may not exist.
  					return nil
  				}
  				defer f.Close()
  				_, err = f.Readdirnames(1)
  				if err == io.EOF {
  					// Empty dir.
  					return cache.Fs.Remove(name)
  				}
  
  				return nil
  			}
  
  			shouldRemove := cache.isExpired(info.ModTime())
  
  			if !shouldRemove && len(cache.nlocker.seen) > 0 {
  				// Remove it if it's not been touched/used in the last build.
  				_, seen := cache.nlocker.seen[name]
  				shouldRemove = !seen
  			}
  
  			if shouldRemove {
  				err := cache.Fs.Remove(name)
  				if err == nil {
  					counter++
  				}
  				return err
  			}
  
  			return nil
  		})
  
  		if err != nil {
  			return counter, errors.Wrapf(err, "failed to prune cache %q", k)
  		}
  
  	}
  
  	return counter, nil
  }
docs: // Prune removes expired and unused items from this cache.
  // The last one requires a full build so the cache usage can be tracked.
  // Note that we operate directly on the filesystem here, so this is not
  // thread safe.