Skip to content
This repository has been archived by the owner on Sep 9, 2020. It is now read-only.

Commit

Permalink
Prune by rebuilding the vendor dir with WriteDepTree
Browse files Browse the repository at this point in the history
  • Loading branch information
tomwilkie committed Mar 15, 2017
1 parent 6241cad commit 555e8de
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 91 deletions.
95 changes: 7 additions & 88 deletions cmd/dep/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,19 @@ import (
"fmt"
"log"
"os"
"path/filepath"
"sort"
"strings"

"github.com/golang/dep"
"github.com/sdboyer/gps"

"github.com/pkg/errors"
)

const pruneShortHelp = `Prune the vendor tree of unused packages`
const pruneLongHelp = `
Prune is used to remove unused packages from your vendor tree. Its works by
following all imports from your project into the vendor tree and then
transitively following all import between packages in the vendor tree, to
calculate a set of packages to keep. It then delete everything else.
Prune is used to remove unused packages from your vendor tree.
`

type pruneCommand struct {
dryRun bool
}

func (cmd *pruneCommand) Name() string { return "prune" }
Expand All @@ -37,7 +32,6 @@ func (cmd *pruneCommand) LongHelp() string { return pruneLongHelp }
func (cmd *pruneCommand) Hidden() bool { return false }

func (cmd *pruneCommand) Register(fs *flag.FlagSet) {
fs.BoolVar(&cmd.dryRun, "n", false, "dry run, don't actually delete anything")
}

func (cmd *pruneCommand) Run(ctx *dep.Ctx, args []string) error {
Expand All @@ -53,25 +47,11 @@ func (cmd *pruneCommand) Run(ctx *dep.Ctx, args []string) error {
sm.UseDefaultSignalHandling()
defer sm.Release()

keep, err := cmd.checkLockAndGetDependencies(p, sm)
if err != nil {
return err
}

toDelete := cmd.calculatePrune(p, keep)
if err := cmd.deleteDirs(p, toDelete); err != nil {
return err
}

return nil
}

func (cmd *pruneCommand) checkLockAndGetDependencies(p *dep.Project, sm *gps.SourceMgr) ([]string, error) {
// While the network churns on ListVersions() requests, statically analyze
// code from the current project.
ptree, err := gps.ListPackages(p.AbsRoot, string(p.ImportRoot))
if err != nil {
return nil, fmt.Errorf("analysis of local packages failed: %v", err)
return errors.Wrap(err, "analysis of local packages failed: %v")
}

// Set up a solver in order to check the InputHash.
Expand All @@ -88,73 +68,12 @@ func (cmd *pruneCommand) checkLockAndGetDependencies(p *dep.Project, sm *gps.Sou

s, err := gps.Prepare(params, sm)
if err != nil {
return nil, fmt.Errorf("could not set up solver for input hashing: %s", err)
return errors.Wrap(err, "could not set up solver for input hashing")
}

if !bytes.Equal(s.HashInputs(), p.Lock.Memo) {
return nil, fmt.Errorf("lock hash doesn't match")
return fmt.Errorf("lock hash doesn't match")
}

var result []string
for _, project := range p.Lock.P {
for _, pkg := range project.Packages() {
fullPkg := filepath.Join(string(project.Ident().ProjectRoot), pkg)
verboseLn("-", fullPkg)
result = append(result, fullPkg)
}
}
sort.Strings(result)
return result, nil
return dep.PruneProject(p, sm)
}

func (cmd *pruneCommand) calculatePrune(p *dep.Project, keep []string) []string {
vendor := filepath.FromSlash(filepath.Join(p.AbsRoot, "vendor"))
toDelete := []string{}
filepath.Walk(vendor, func(path string, info os.FileInfo, err error) error {
if _, err := os.Lstat(path); err != nil {
return nil
}
if !info.IsDir() {
return nil
}
if path == vendor {
return nil
}

name := strings.TrimPrefix(path, vendor+"/")
i := sort.Search(len(keep), func(i int) bool {
return name <= keep[i]
})
if i >= len(keep) || !strings.HasPrefix(keep[i], name) {
toDelete = append(toDelete, path)
}
return nil
})
return toDelete
}

func (cmd *pruneCommand) deleteDirs(p *dep.Project, toDelete []string) error {
// sort by length so we delete sub dirs first
sort.Sort(byLen(toDelete))
for _, path := range toDelete {
verboseLn("rm -rf", strings.TrimPrefix(path, p.AbsRoot+"/"))
if !cmd.dryRun {
if err := os.RemoveAll(path); err != nil {
return err
}
}
}
return nil
}

func verboseLn(a ...interface{}) {
if *verbose {
fmt.Println(a...)
}
}

type byLen []string

func (a byLen) Len() int { return len(a) }
func (a byLen) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byLen) Less(i, j int) bool { return len(a[i]) > len(a[j]) }
46 changes: 45 additions & 1 deletion txn_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ func (sw *SafeWriter) Write(root string, sm gps.SourceManager) error {
}

if sw.Payload.HasVendor() {
err = gps.WriteDepTree(filepath.Join(td, "vendor"), sw.Payload.Lock, sm, true)
err = gps.WriteDepTree(filepath.Join(td, "vendor"), sw.Payload.Lock, sm, true, false)
if err != nil {
return errors.Wrap(err, "error while writing out vendor tree")
}
Expand Down Expand Up @@ -570,3 +570,47 @@ func diffProjects(lp1 gps.LockedProject, lp2 gps.LockedProject) *LockedProjectDi
}
return &diff
}

func PruneProject(p *Project, sm gps.SourceManager) error {
td, err := ioutil.TempDir(os.TempDir(), "dep")
if err != nil {
return errors.Wrap(err, "error while creating temp dir for writing manifest/lock/vendor")
}
defer os.RemoveAll(td)

if err := gps.WriteDepTree(td, p.Lock, sm, true, true); err != nil {
return err
}

vpath := filepath.Join(p.AbsRoot, "vendor")
vendorbak := vpath + ".orig"
var failerr error
if _, err := os.Stat(vpath); err == nil {
// Move out the old vendor dir. just do it into an adjacent dir, to
// try to mitigate the possibility of a pointless cross-filesystem
// move with a temp directory.
if _, err := os.Stat(vendorbak); err == nil {
// If the adjacent dir already exists, bite the bullet and move
// to a proper tempdir.
vendorbak = filepath.Join(td, "vendor.orig")
}
failerr = renameWithFallback(vpath, vendorbak)
if failerr != nil {
goto fail
}
}

// Move in the new one.
failerr = renameWithFallback(td, vpath)
if failerr != nil {
goto fail
}

os.RemoveAll(vendorbak)

return nil

fail:
renameWithFallback(vendorbak, vpath)
return failerr
}
53 changes: 51 additions & 2 deletions vendor/github.com/sdboyer/gps/result.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 555e8de

Please sign in to comment.