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

Commit 23e8a3c

Browse files
committed
internal/gps: Add prune functions to gps
Signed-off-by: Ibrahim AshShohail <[email protected]>
1 parent 876083e commit 23e8a3c

File tree

2 files changed

+601
-0
lines changed

2 files changed

+601
-0
lines changed

internal/gps/prune.go

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

0 commit comments

Comments
 (0)