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/golang/dep/internal/fs"
16
+ "github.com/pkg/errors"
17
+ )
18
+
7
19
// PruneOptions represents the pruning options used to write the dependecy tree.
8
20
type PruneOptions uint8
9
21
@@ -20,8 +32,336 @@ const (
20
32
)
21
33
22
34
var (
23
- preservedNonGoFiles = []string {
24
- "LICENSE" ,
25
- "COPYING" ,
35
+ // licenseFilePrefixes is a list of name prefixes for licesnse files.
36
+ licenseFilePrefixes = []string {
37
+ "license" ,
38
+ "licence" ,
39
+ "copying" ,
40
+ "unlicense" ,
41
+ "copyright" ,
42
+ "copyleft" ,
43
+ }
44
+ // legalFileSubstrings contains substrings that are likey part of a legal
45
+ // declaration file.
46
+ legalFileSubstrings = []string {
47
+ "legal" ,
48
+ "notice" ,
49
+ "disclaimer" ,
50
+ "patent" ,
51
+ "third-party" ,
52
+ "thirdparty" ,
26
53
}
27
54
)
55
+
56
+ // Prune removes excess files from the dep tree whose root is baseDir based
57
+ // on the PruneOptions passed.
58
+ //
59
+ // A Lock must be passed if PruneUnusedPackages is toggled on.
60
+ func Prune (baseDir string , options PruneOptions , l Lock , logger * log.Logger ) error {
61
+ if (options & PruneNestedVendorDirs ) != 0 {
62
+ if err := pruneNestedVendorDirs (baseDir ); err != nil {
63
+ return err
64
+ }
65
+ }
66
+
67
+ if err := pruneEmptyDirs (baseDir , logger ); err != nil {
68
+ return errors .Wrap (err , "failed to prune empty dirs" )
69
+ }
70
+
71
+ if (options & PruneUnusedPackages ) != 0 {
72
+ if l == nil {
73
+ return errors .New ("pruning unused packages requires passing a non-nil Lock" )
74
+ }
75
+ if err := pruneUnusedPackages (baseDir , l , logger ); err != nil {
76
+ return errors .Wrap (err , "failed to prune unused packages" )
77
+ }
78
+ }
79
+
80
+ if (options & PruneNonGoFiles ) != 0 {
81
+ if err := pruneNonGoFiles (baseDir , logger ); err != nil {
82
+ return errors .Wrap (err , "failed to prune non-Go files" )
83
+ }
84
+ }
85
+
86
+ if (options & PruneGoTestFiles ) != 0 {
87
+ if err := pruneGoTestFiles (baseDir , logger ); err != nil {
88
+ return errors .Wrap (err , "failed to prune Go test files" )
89
+ }
90
+ }
91
+
92
+ // Delete all empty directories.
93
+ if err := pruneEmptyDirs (baseDir , logger ); err != nil {
94
+ return errors .Wrap (err , "failed to prune empty dirs" )
95
+ }
96
+
97
+ return nil
98
+ }
99
+
100
+ // pruneNestedVendorDirs deletes all nested vendor directories within baseDir.
101
+ func pruneNestedVendorDirs (baseDir string ) error {
102
+ return filepath .Walk (baseDir , stripNestedVendorDirs (baseDir ))
103
+ }
104
+
105
+ // pruneUnusedPackages deletes unimported packages found within baseDir.
106
+ // Determining whether packages are imported or not is based on the passed Lock.
107
+ func pruneUnusedPackages (baseDir string , l Lock , logger * log.Logger ) error {
108
+ unused , err := calculateUnusedPackages (baseDir , l , logger )
109
+ if err != nil {
110
+ return err
111
+ }
112
+
113
+ for _ , pkg := range unused {
114
+ pkgPath := filepath .Join (baseDir , pkg )
115
+
116
+ files , err := ioutil .ReadDir (pkgPath )
117
+ if err != nil {
118
+ // TODO(ibrasho) Handle this error properly.
119
+ // It happens when attempting to ioutil.ReadDir a submodule.
120
+ continue
121
+ }
122
+
123
+ // Delete *.go files in the package directory.
124
+ for _ , file := range files {
125
+ // Skip directories and files that don't have a .go suffix.
126
+ if file .IsDir () || ! strings .HasSuffix (file .Name (), ".go" ) {
127
+ continue
128
+ }
129
+
130
+ if err := os .Remove (filepath .Join (pkgPath , file .Name ())); err != nil {
131
+ return err
132
+ }
133
+ }
134
+ }
135
+
136
+ return nil
137
+ }
138
+
139
+ // calculateUnusedPackages generates a list of unused packages existing within
140
+ // baseDir depending on the imported packages found in the passed Lock.
141
+ func calculateUnusedPackages (baseDir string , l Lock , logger * log.Logger ) ([]string , error ) {
142
+ imported := calculateImportedPackages (l )
143
+ sort .Strings (imported )
144
+
145
+ var unused []string
146
+
147
+ if logger != nil {
148
+ logger .Println ("Calculating unused packages to prune." )
149
+ logger .Println ("Checking the following packages:" )
150
+ }
151
+
152
+ err := filepath .Walk (baseDir , func (path string , info os.FileInfo , err error ) error {
153
+ if err != nil {
154
+ return err
155
+ }
156
+
157
+ // Ignore baseDir and anything that's not a directory.
158
+ if path == baseDir || ! info .IsDir () {
159
+ return nil
160
+ }
161
+
162
+ pkg := strings .TrimPrefix (path , baseDir + string (filepath .Separator ))
163
+ if logger != nil {
164
+ logger .Printf (" %s" , pkg )
165
+ }
166
+
167
+ // If pkg is not a parent of an imported package, add it to the
168
+ // unused list.
169
+ i := sort .Search (len (imported ), func (i int ) bool {
170
+ return pkg <= imported [i ]
171
+ })
172
+ if i >= len (imported ) || ! strings .HasPrefix (imported [i ], pkg ) {
173
+ unused = append (unused , path )
174
+ }
175
+
176
+ return nil
177
+ })
178
+
179
+ return unused , err
180
+ }
181
+
182
+ // calculateImportedPackages generates a list of imported packages from
183
+ // the passed Lock.
184
+ func calculateImportedPackages (l Lock ) []string {
185
+ var imported []string
186
+
187
+ for _ , project := range l .Projects () {
188
+ projectRoot := string (project .Ident ().ProjectRoot )
189
+ for _ , pkg := range project .Packages () {
190
+ imported = append (imported , filepath .Join (projectRoot , pkg ))
191
+ }
192
+ }
193
+ return imported
194
+ }
195
+
196
+ // pruneNonGoFiles delete all non-Go files existing within baseDir.
197
+ // Files with names that are prefixed by any entry in preservedNonGoFiles
198
+ // are not deleted.
199
+ func pruneNonGoFiles (baseDir string , logger * log.Logger ) error {
200
+ files , err := calculateNonGoFiles (baseDir )
201
+ if err != nil {
202
+ return errors .Wrap (err , "could not prune non-Go files" )
203
+ }
204
+
205
+ if err := deleteFiles (files ); err != nil {
206
+ return err
207
+ }
208
+
209
+ return nil
210
+ }
211
+
212
+ // calculateNonGoFiles returns a list of all non-Go files within baseDir.
213
+ // Files with names that are prefixed by any entry in preservedNonGoFiles
214
+ // are not deleted.
215
+ func calculateNonGoFiles (baseDir string ) ([]string , error ) {
216
+ var files []string
217
+
218
+ err := filepath .Walk (baseDir , func (path string , info os.FileInfo , err error ) error {
219
+ if err != nil {
220
+ return err
221
+ }
222
+
223
+ // Ignore directories.
224
+ if info .IsDir () {
225
+ return nil
226
+ }
227
+
228
+ // Ignore all Go files.
229
+ if strings .HasSuffix (info .Name (), ".go" ) {
230
+ return nil
231
+ }
232
+
233
+ if ! isPreservedNonGoFile (info .Name ()) {
234
+ files = append (files , path )
235
+ }
236
+
237
+ return nil
238
+ })
239
+
240
+ return files , err
241
+ }
242
+
243
+ // isPreservedNonGoFile checks if the file name idicates that the file should be
244
+ // preserved. It assumes the file is not a Go file (doesn't have a .go suffix).
245
+ func isPreservedNonGoFile (name string ) bool {
246
+ name = strings .ToLower (name )
247
+
248
+ for _ , prefix := range licenseFilePrefixes {
249
+ if strings .HasPrefix (name , prefix ) {
250
+ return true
251
+ }
252
+ }
253
+
254
+ for _ , substring := range legalFileSubstrings {
255
+ if strings .Contains (name , substring ) {
256
+ return true
257
+ }
258
+ }
259
+
260
+ return false
261
+ }
262
+
263
+ // pruneGoTestFiles deletes all Go test files (*_test.go) within baseDirr.
264
+ func pruneGoTestFiles (baseDir string , logger * log.Logger ) error {
265
+ files , err := calculateGoTestFiles (baseDir )
266
+ if err != nil {
267
+ return errors .Wrap (err , "could not prune Go test files" )
268
+ }
269
+
270
+ if err := deleteFiles (files ); err != nil {
271
+ return err
272
+ }
273
+
274
+ return nil
275
+ }
276
+
277
+ // calculateGoTestFiles walks over baseDir and returns a list of all
278
+ // Go test files (any file that has the name *_test.go).
279
+ func calculateGoTestFiles (baseDir string ) ([]string , error ) {
280
+ var files []string
281
+
282
+ err := filepath .Walk (baseDir , func (path string , info os.FileInfo , err error ) error {
283
+ if err != nil {
284
+ return err
285
+ }
286
+
287
+ // Ignore directories.
288
+ if info .IsDir () {
289
+ return nil
290
+ }
291
+
292
+ // Ignore any files that is not a Go test file.
293
+ if ! strings .HasSuffix (info .Name (), "_test.go" ) {
294
+ return nil
295
+ }
296
+
297
+ files = append (files , path )
298
+
299
+ return nil
300
+ })
301
+
302
+ return files , err
303
+ }
304
+
305
+ // pruneEmptyDirs delete all empty directories within baseDir.
306
+ func pruneEmptyDirs (baseDir string , logger * log.Logger ) error {
307
+ empty , err := calculateEmptyDirs (baseDir )
308
+ if err != nil {
309
+ return err
310
+ }
311
+
312
+ if logger != nil {
313
+ logger .Println ("Deleting empty directories:" )
314
+ }
315
+
316
+ for _ , dir := range empty {
317
+ if logger != nil {
318
+ logger .Printf (" %s\n " , strings .TrimPrefix (dir , baseDir + string (os .PathSeparator )))
319
+ }
320
+ }
321
+ for _ , dir := range empty {
322
+ if err := os .Remove (dir ); err != nil {
323
+ return err
324
+ }
325
+ }
326
+
327
+ return nil
328
+ }
329
+
330
+ // calculateEmptyDirs walks over baseDir and returns a slice of empty directory paths.
331
+ func calculateEmptyDirs (baseDir string ) ([]string , error ) {
332
+ var empty []string
333
+
334
+ err := filepath .Walk (baseDir , func (path string , info os.FileInfo , err error ) error {
335
+ if err != nil {
336
+ return nil
337
+ }
338
+
339
+ if baseDir == path {
340
+ return nil
341
+ }
342
+
343
+ if ! info .IsDir () {
344
+ return nil
345
+ }
346
+
347
+ nonEmpty , err := fs .IsNonEmptyDir (path )
348
+ if err != nil {
349
+ return err
350
+ } else if ! nonEmpty {
351
+ empty = append (empty , path )
352
+ }
353
+
354
+ return nil
355
+ })
356
+
357
+ return empty , err
358
+ }
359
+
360
+ func deleteFiles (paths []string ) error {
361
+ for _ , path := range paths {
362
+ if err := os .Remove (path ); err != nil {
363
+ return err
364
+ }
365
+ }
366
+ return nil
367
+ }
0 commit comments