4
4
5
5
package gps
6
6
7
+ import (
8
+ "io/ioutil"
9
+ "log"
10
+ "os"
11
+ "path/filepath"
12
+ "sort"
13
+ "strings"
14
+
15
+ "github.com/pkg/errors"
16
+ )
17
+
7
18
// PruneOptions represents the pruning options used to write the dependecy tree.
8
19
type PruneOptions uint8
9
20
@@ -25,3 +36,280 @@ var (
25
36
"COPYING" ,
26
37
}
27
38
)
39
+
40
+ // Prune removes excess files from the dep tree whose root is baseDir based
41
+ // on the PruneOptions passed.
42
+ //
43
+ // A Lock must be passed if PruneUnusedPackages is toggled on.
44
+ func Prune (baseDir string , options PruneOptions , l Lock , logger * log.Logger ) error {
45
+ if (options & PruneNestedVendorDirs ) != 0 {
46
+ if err := pruneNestedVendorDirs (baseDir ); err != nil {
47
+ return err
48
+ }
49
+ }
50
+
51
+ if err := pruneEmptyDirs (baseDir , logger ); err != nil {
52
+ return errors .Wrap (err , "failed to prune empty dirs" )
53
+ }
54
+
55
+ if (options & PruneUnusedPackages ) != 0 {
56
+ if l == nil {
57
+ return errors .New ("pruning unused packages requires passing a non-nil Lock" )
58
+ }
59
+ if err := pruneUnusedPackages (baseDir , l , logger ); err != nil {
60
+ return errors .Wrap (err , "failed to prune unused packages" )
61
+ }
62
+ }
63
+
64
+ if (options & PruneNonGoFiles ) != 0 {
65
+ if err := pruneNonGoFiles (baseDir , logger ); err != nil {
66
+ return errors .Wrap (err , "failed to prune non-Go files" )
67
+ }
68
+ }
69
+
70
+ if (options & PruneGoTestFiles ) != 0 {
71
+ if err := pruneGoTestFiles (baseDir , logger ); err != nil {
72
+ return errors .Wrap (err , "failed to prune Go test files" )
73
+ }
74
+ }
75
+
76
+ // Delete all empty directories.
77
+ if err := pruneEmptyDirs (baseDir , logger ); err != nil {
78
+ return errors .Wrap (err , "failed to prune empty dirs" )
79
+ }
80
+
81
+ return nil
82
+ }
83
+
84
+ func pruneNestedVendorDirs (baseDir string ) error {
85
+ return filepath .Walk (baseDir , stripNestedVendorDirs (baseDir ))
86
+ }
87
+
88
+ func pruneUnusedPackages (baseDir string , l Lock , logger * log.Logger ) error {
89
+ unused , err := calculateUnusedPackages (baseDir , l , logger )
90
+ if err != nil {
91
+ return err
92
+ }
93
+
94
+ for _ , pkg := range unused {
95
+ pkgPath := filepath .Join (baseDir , pkg )
96
+
97
+ files , err := ioutil .ReadDir (pkgPath )
98
+ if err != nil {
99
+ // TODO(ibrasho) Handle this error properly.
100
+ // It happens when attempting to ioutil.ReadDir a submodule.
101
+ continue
102
+ }
103
+
104
+ // Delete *.go files in the package directory.
105
+ for _ , file := range files {
106
+ // Skip directories and files that don't have a .go suffix.
107
+ if file .IsDir () || ! strings .HasSuffix (file .Name (), ".go" ) {
108
+ continue
109
+ }
110
+
111
+ if err := os .Remove (filepath .Join (pkgPath , file .Name ())); err != nil {
112
+ return err
113
+ }
114
+ }
115
+ }
116
+
117
+ return nil
118
+ }
119
+
120
+ func calculateUnusedPackages (baseDir string , l Lock , logger * log.Logger ) ([]string , error ) {
121
+ imported := calculateImportedPackages (l )
122
+ sort .Strings (imported )
123
+
124
+ var unused []string
125
+
126
+ if logger != nil {
127
+ logger .Println ("Calculating unused packages to prune. Checking the following packages:" )
128
+ }
129
+
130
+ err := filepath .Walk (baseDir , func (path string , info os.FileInfo , err error ) error {
131
+ if err != nil {
132
+ return err
133
+ }
134
+
135
+ // Ignore baseDir and anything that's not a directory.
136
+ if path == baseDir || ! info .IsDir () {
137
+ return nil
138
+ }
139
+
140
+ pkg := strings .TrimPrefix (path , baseDir + string (filepath .Separator ))
141
+ if logger != nil {
142
+ logger .Printf (" %s" , pkg )
143
+ }
144
+
145
+ // If pkg is not a parent of an imported package, add it to the unused list.
146
+ i := sort .Search (len (imported ), func (i int ) bool {
147
+ return pkg <= imported [i ]
148
+ })
149
+ if i >= len (imported ) || ! strings .HasPrefix (imported [i ], pkg ) {
150
+ unused = append (unused , path )
151
+ }
152
+
153
+ return nil
154
+ })
155
+
156
+ return unused , err
157
+ }
158
+
159
+ func calculateImportedPackages (l Lock ) []string {
160
+ var imported []string
161
+
162
+ for _ , project := range l .Projects () {
163
+ projectRoot := string (project .Ident ().ProjectRoot )
164
+ for _ , pkg := range project .Packages () {
165
+ imported = append (imported , filepath .Join (projectRoot , pkg ))
166
+ }
167
+ }
168
+ return imported
169
+ }
170
+
171
+ func pruneNonGoFiles (baseDir string , logger * log.Logger ) error {
172
+ files , err := calculateNonGoFiles (baseDir )
173
+ if err != nil {
174
+ return errors .Wrap (err , "could not prune non-Go files" )
175
+ }
176
+
177
+ if err := deleteFiles (files ); err != nil {
178
+ return err
179
+ }
180
+
181
+ return nil
182
+ }
183
+
184
+ func calculateNonGoFiles (baseDir string ) ([]string , error ) {
185
+ var files []string
186
+
187
+ err := filepath .Walk (baseDir , func (path string , info os.FileInfo , err error ) error {
188
+ if err != nil {
189
+ return err
190
+ }
191
+
192
+ // Ignore directories.
193
+ if info .IsDir () {
194
+ return nil
195
+ }
196
+
197
+ // Ignore all Go files.
198
+ if strings .HasSuffix (info .Name (), ".go" ) {
199
+ return nil
200
+ }
201
+
202
+ // Ignore preserved non-Go files. We check for prefix incase the file
203
+ // has an extension. For example: LICENSE.md.
204
+ for _ , prefix := range preservedNonGoFiles {
205
+ if strings .HasPrefix (info .Name (), prefix ) {
206
+ return nil
207
+ }
208
+ }
209
+
210
+ files = append (files , path )
211
+
212
+ return nil
213
+ })
214
+
215
+ return files , err
216
+ }
217
+
218
+ func pruneGoTestFiles (baseDir string , logger * log.Logger ) error {
219
+ files , err := calculateGoTestFiles (baseDir )
220
+ if err != nil {
221
+ return errors .Wrap (err , "could not prune Go test files" )
222
+ }
223
+
224
+ if err := deleteFiles (files ); err != nil {
225
+ return err
226
+ }
227
+
228
+ return nil
229
+ }
230
+
231
+ func calculateGoTestFiles (baseDir string ) ([]string , error ) {
232
+ var files []string
233
+
234
+ err := filepath .Walk (baseDir , func (path string , info os.FileInfo , err error ) error {
235
+ if err != nil {
236
+ return err
237
+ }
238
+
239
+ // Ignore directories.
240
+ if info .IsDir () {
241
+ return nil
242
+ }
243
+
244
+ // Ignore any files that is not a Go test file.
245
+ if ! strings .HasSuffix (info .Name (), "_test.go" ) {
246
+ return nil
247
+ }
248
+
249
+ files = append (files , path )
250
+
251
+ return nil
252
+ })
253
+
254
+ return files , err
255
+ }
256
+
257
+ func deleteFiles (paths []string ) error {
258
+ for _ , path := range paths {
259
+ if err := os .Remove (path ); err != nil {
260
+ return err
261
+ }
262
+ }
263
+ return nil
264
+ }
265
+
266
+ func pruneEmptyDirs (baseDir string , logger * log.Logger ) error {
267
+ empty , err := calculateEmptyDirs (baseDir )
268
+ if err != nil {
269
+ return err
270
+ }
271
+
272
+ if logger != nil {
273
+ logger .Println ("Deleting empty directories:" )
274
+ }
275
+
276
+ for _ , dir := range empty {
277
+ if logger != nil {
278
+ logger .Printf (" %s\n " , strings .TrimPrefix (dir , baseDir + string (os .PathSeparator )))
279
+ }
280
+ }
281
+ for _ , dir := range empty {
282
+ if err := os .Remove (dir ); err != nil {
283
+ return err
284
+ }
285
+ }
286
+
287
+ return nil
288
+ }
289
+ func calculateEmptyDirs (baseDir string ) ([]string , error ) {
290
+ var empty []string
291
+
292
+ err := filepath .Walk (baseDir , func (path string , info os.FileInfo , err error ) error {
293
+ if err != nil {
294
+ return nil
295
+ }
296
+
297
+ if ! info .IsDir () {
298
+ return nil
299
+ }
300
+
301
+ // TODO(ibrasho) should we use fs.IsNonEmptyDir instead?
302
+ files , err := ioutil .ReadDir (path )
303
+ if err != nil {
304
+ return err
305
+ }
306
+
307
+ if len (files ) == 0 {
308
+ empty = append (empty , path )
309
+ }
310
+
311
+ return nil
312
+ })
313
+
314
+ return empty , err
315
+ }
0 commit comments