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