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

Commit cf31c33

Browse files
committed
wip
Signed-off-by: Ibrahim AshShohail <[email protected]>
1 parent 53e80dc commit cf31c33

File tree

2 files changed

+607
-0
lines changed

2 files changed

+607
-0
lines changed

internal/gps/prune.go

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

0 commit comments

Comments
 (0)