github.com/golang/dep@v0.5.4/cmd/dep/prune.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"flag"
     9  	"io/ioutil"
    10  	"log"
    11  	"os"
    12  	"path/filepath"
    13  	"sort"
    14  	"strings"
    15  
    16  	"github.com/golang/dep"
    17  	"github.com/golang/dep/gps"
    18  	"github.com/golang/dep/gps/pkgtree"
    19  	"github.com/golang/dep/internal/fs"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  const pruneShortHelp = `Pruning is now performed automatically by dep ensure.`
    24  const pruneLongHelp = `
    25  Prune was merged into the ensure command.
    26  Set prune options in the manifest and it will be applied after every ensure.
    27  dep prune will be removed in a future version of dep, causing this command to exit non-0.
    28  `
    29  
    30  type pruneCommand struct {
    31  }
    32  
    33  func (cmd *pruneCommand) Name() string      { return "prune" }
    34  func (cmd *pruneCommand) Args() string      { return "" }
    35  func (cmd *pruneCommand) ShortHelp() string { return pruneShortHelp }
    36  func (cmd *pruneCommand) LongHelp() string  { return pruneLongHelp }
    37  func (cmd *pruneCommand) Hidden() bool      { return true }
    38  
    39  func (cmd *pruneCommand) Register(fs *flag.FlagSet) {
    40  }
    41  
    42  func (cmd *pruneCommand) Run(ctx *dep.Ctx, args []string) error {
    43  	ctx.Err.Printf("Pruning is now performed automatically by dep ensure.\n")
    44  	ctx.Err.Printf("Set prune settings in %s and it will be applied when running ensure.\n", dep.ManifestName)
    45  	ctx.Err.Printf("\nThis command currently still prunes as it always has, to ease the transition.\n")
    46  	ctx.Err.Printf("However, it will be removed in a future version of dep.\n")
    47  	ctx.Err.Printf("\nNow is the time to update your Gopkg.toml and remove `dep prune` from any scripts.\n")
    48  	ctx.Err.Printf("\nFor more information, see: https://golang.github.io/dep/docs/Gopkg.toml.html#prune\n")
    49  
    50  	p, err := ctx.LoadProject()
    51  	if err != nil {
    52  		return err
    53  	}
    54  
    55  	sm, err := ctx.SourceManager()
    56  	if err != nil {
    57  		return err
    58  	}
    59  	sm.UseDefaultSignalHandling()
    60  	defer sm.Release()
    61  
    62  	// While the network churns on ListVersions() requests, statically analyze
    63  	// code from the current project.
    64  	ptree, err := pkgtree.ListPackages(p.ResolvedAbsRoot, string(p.ImportRoot))
    65  	if err != nil {
    66  		return errors.Wrap(err, "analysis of local packages failed: %v")
    67  	}
    68  
    69  	// Set up a solver in order to check the InputHash.
    70  	params := p.MakeParams()
    71  	params.RootPackageTree = ptree
    72  
    73  	if ctx.Verbose {
    74  		params.TraceLogger = ctx.Err
    75  	}
    76  
    77  	if p.Lock == nil {
    78  		return errors.Errorf("Gopkg.lock must exist for prune to know what files are safe to remove.")
    79  	}
    80  
    81  	pruneLogger := ctx.Err
    82  	if !ctx.Verbose {
    83  		pruneLogger = log.New(ioutil.Discard, "", 0)
    84  	}
    85  	return pruneProject(p, sm, pruneLogger)
    86  }
    87  
    88  // pruneProject removes unused packages from a project.
    89  func pruneProject(p *dep.Project, sm gps.SourceManager, logger *log.Logger) error {
    90  	td, err := ioutil.TempDir(os.TempDir(), "dep")
    91  	if err != nil {
    92  		return errors.Wrap(err, "error while creating temp dir for writing manifest/lock/vendor")
    93  	}
    94  	defer os.RemoveAll(td)
    95  
    96  	onWrite := func(progress gps.WriteProgress) {
    97  		logger.Println(progress)
    98  	}
    99  	if err := gps.WriteDepTree(td, p.Lock, sm, gps.CascadingPruneOptions{DefaultOptions: gps.PruneNestedVendorDirs}, onWrite); err != nil {
   100  		return err
   101  	}
   102  
   103  	var toKeep []string
   104  	for _, project := range p.Lock.Projects() {
   105  		projectRoot := string(project.Ident().ProjectRoot)
   106  		for _, pkg := range project.Packages() {
   107  			toKeep = append(toKeep, filepath.Join(projectRoot, pkg))
   108  		}
   109  	}
   110  
   111  	toDelete, err := calculatePrune(td, toKeep, logger)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	if len(toDelete) > 0 {
   117  		logger.Println("Calculated the following directories to prune:")
   118  		for _, d := range toDelete {
   119  			logger.Printf("  %s\n", d)
   120  		}
   121  	} else {
   122  		logger.Println("No directories found to prune")
   123  	}
   124  
   125  	if err := deleteDirs(toDelete); err != nil {
   126  		return err
   127  	}
   128  
   129  	vpath := filepath.Join(p.AbsRoot, "vendor")
   130  	vendorbak := vpath + ".orig"
   131  	var failerr error
   132  	if _, err := os.Stat(vpath); err == nil {
   133  		// Move out the old vendor dir. just do it into an adjacent dir, to
   134  		// try to mitigate the possibility of a pointless cross-filesystem
   135  		// move with a temp directory.
   136  		if _, err := os.Stat(vendorbak); err == nil {
   137  			// If the adjacent dir already exists, bite the bullet and move
   138  			// to a proper tempdir.
   139  			vendorbak = filepath.Join(td, "vendor.orig")
   140  		}
   141  		failerr = fs.RenameWithFallback(vpath, vendorbak)
   142  		if failerr != nil {
   143  			goto fail
   144  		}
   145  	}
   146  
   147  	// Move in the new one.
   148  	failerr = fs.RenameWithFallback(td, vpath)
   149  	if failerr != nil {
   150  		goto fail
   151  	}
   152  
   153  	os.RemoveAll(vendorbak)
   154  
   155  	return nil
   156  
   157  fail:
   158  	fs.RenameWithFallback(vendorbak, vpath)
   159  	return failerr
   160  }
   161  
   162  func calculatePrune(vendorDir string, keep []string, logger *log.Logger) ([]string, error) {
   163  	logger.Println("Calculating prune. Checking the following packages:")
   164  	sort.Strings(keep)
   165  	var toDelete []string
   166  	err := filepath.Walk(vendorDir, func(path string, info os.FileInfo, err error) error {
   167  		if _, err := os.Lstat(path); err != nil {
   168  			return nil
   169  		}
   170  		if !info.IsDir() {
   171  			return nil
   172  		}
   173  		if path == vendorDir {
   174  			return nil
   175  		}
   176  
   177  		name := strings.TrimPrefix(path, vendorDir+string(filepath.Separator))
   178  		logger.Printf("  %s", name)
   179  		i := sort.Search(len(keep), func(i int) bool {
   180  			return name <= keep[i]
   181  		})
   182  		if i >= len(keep) || !strings.HasPrefix(keep[i], name) {
   183  			toDelete = append(toDelete, path)
   184  		}
   185  		return nil
   186  	})
   187  	return toDelete, err
   188  }
   189  
   190  func deleteDirs(toDelete []string) error {
   191  	// sort by length so we delete sub dirs first
   192  	sort.Sort(byLen(toDelete))
   193  	for _, path := range toDelete {
   194  		if err := os.RemoveAll(path); err != nil {
   195  			return err
   196  		}
   197  	}
   198  	return nil
   199  }
   200  
   201  type byLen []string
   202  
   203  func (a byLen) Len() int           { return len(a) }
   204  func (a byLen) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   205  func (a byLen) Less(i, j int) bool { return len(a[i]) > len(a[j]) }