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]) }