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

Commit f749c0c

Browse files
authored
Merge pull request #1020 from ibrasho-forks/add-prune-to-gps
internal/gps: Add prune functions to gps
2 parents c253f7e + 9b6892c commit f749c0c

File tree

4 files changed

+700
-49
lines changed

4 files changed

+700
-49
lines changed

internal/gps/prune.go

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
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+
"log"
9+
"os"
10+
"path/filepath"
11+
"strings"
12+
13+
"github.com/pkg/errors"
14+
)
15+
16+
// PruneOptions represents the pruning options used to write the dependecy tree.
17+
type PruneOptions uint8
18+
19+
const (
20+
// PruneNestedVendorDirs indicates if nested vendor directories should be pruned.
21+
PruneNestedVendorDirs = 1 << iota
22+
// PruneUnusedPackages indicates if unused Go packages should be pruned.
23+
PruneUnusedPackages
24+
// PruneNonGoFiles indicates if non-Go files should be pruned.
25+
// Files matching licenseFilePrefixes and legalFileSubstrings are kept in
26+
// an attempt to comply with legal requirements.
27+
PruneNonGoFiles
28+
// PruneGoTestFiles indicates if Go test files should be pruned.
29+
PruneGoTestFiles
30+
)
31+
32+
var (
33+
// licenseFilePrefixes is a list of name prefixes for license files.
34+
licenseFilePrefixes = []string{
35+
"license",
36+
"licence",
37+
"copying",
38+
"unlicense",
39+
"copyright",
40+
"copyleft",
41+
}
42+
// legalFileSubstrings contains substrings that are likey part of a legal
43+
// declaration file.
44+
legalFileSubstrings = []string{
45+
"authors",
46+
"contributors",
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+
// TODO(ibrasho) allow passing specific options per project
62+
for _, lp := range l.Projects() {
63+
projectDir := filepath.Join(baseDir, string(lp.Ident().ProjectRoot))
64+
err := PruneProject(projectDir, lp, options, logger)
65+
if err != nil {
66+
return err
67+
}
68+
}
69+
70+
return nil
71+
}
72+
73+
// PruneProject remove excess files according to the options passed, from
74+
// the lp directory in baseDir.
75+
func PruneProject(baseDir string, lp LockedProject, options PruneOptions, logger *log.Logger) error {
76+
projectDir := filepath.Join(baseDir, string(lp.Ident().ProjectRoot))
77+
78+
if (options & PruneNestedVendorDirs) != 0 {
79+
if err := pruneNestedVendorDirs(projectDir); err != nil {
80+
return err
81+
}
82+
}
83+
84+
if (options & PruneUnusedPackages) != 0 {
85+
if err := pruneUnusedPackages(lp, projectDir, logger); err != nil {
86+
return errors.Wrap(err, "failed to prune unused packages")
87+
}
88+
}
89+
90+
if (options & PruneNonGoFiles) != 0 {
91+
if err := pruneNonGoFiles(projectDir, logger); err != nil {
92+
return errors.Wrap(err, "failed to prune non-Go files")
93+
}
94+
}
95+
96+
if (options & PruneGoTestFiles) != 0 {
97+
if err := pruneGoTestFiles(projectDir, logger); err != nil {
98+
return errors.Wrap(err, "failed to prune Go test files")
99+
}
100+
}
101+
102+
return nil
103+
}
104+
105+
// pruneNestedVendorDirs deletes all nested vendor directories within baseDir.
106+
func pruneNestedVendorDirs(baseDir string) error {
107+
return filepath.Walk(baseDir, stripVendor)
108+
}
109+
110+
// pruneUnusedPackages deletes unimported packages found within baseDir.
111+
// Determining whether packages are imported or not is based on the passed LockedProject.
112+
func pruneUnusedPackages(lp LockedProject, projectDir string, logger *log.Logger) error {
113+
pr := string(lp.Ident().ProjectRoot)
114+
logger.Printf("Calculating unused packages in %s to prune.\n", pr)
115+
116+
unusedPackages, err := calculateUnusedPackages(lp, projectDir)
117+
if err != nil {
118+
return errors.Wrapf(err, "could not calculate unused packages in %s", pr)
119+
}
120+
121+
logger.Printf("Found the following unused packages in %s:\n", pr)
122+
for pkg := range unusedPackages {
123+
logger.Printf(" * %s\n", filepath.Join(pr, pkg))
124+
}
125+
126+
unusedPackagesFiles, err := collectUnusedPackagesFiles(projectDir, unusedPackages)
127+
if err != nil {
128+
return errors.Wrapf(err, "could not collect unused packages' files in %s", pr)
129+
}
130+
131+
if err := deleteFiles(unusedPackagesFiles); err != nil {
132+
return errors.Wrapf(err, "")
133+
}
134+
135+
return nil
136+
}
137+
138+
// calculateUnusedPackages generates a list of unused packages in lp.
139+
func calculateUnusedPackages(lp LockedProject, projectDir string) (map[string]struct{}, error) {
140+
// TODO(ibrasho): optimize this...
141+
unused := make(map[string]struct{})
142+
imported := make(map[string]struct{})
143+
for _, pkg := range lp.Packages() {
144+
imported[pkg] = struct{}{}
145+
}
146+
147+
err := filepath.Walk(projectDir, func(path string, info os.FileInfo, err error) error {
148+
if err != nil {
149+
return err
150+
}
151+
152+
// Ignore anything that's not a directory.
153+
if !info.IsDir() {
154+
return nil
155+
}
156+
157+
pkg, err := filepath.Rel(projectDir, path)
158+
if err != nil {
159+
return errors.Wrap(err, "unexpected error while calculating unused packages")
160+
}
161+
162+
pkg = filepath.ToSlash(pkg)
163+
if _, ok := imported[pkg]; !ok {
164+
unused[pkg] = struct{}{}
165+
}
166+
167+
return nil
168+
})
169+
170+
return unused, err
171+
}
172+
173+
// collectUnusedPackagesFiles returns a slice of all files in the unused packages in projectDir.
174+
func collectUnusedPackagesFiles(projectDir string, unusedPackages map[string]struct{}) ([]string, error) {
175+
// TODO(ibrasho): is this useful?
176+
files := make([]string, 0, len(unusedPackages))
177+
178+
err := filepath.Walk(projectDir, func(path string, info os.FileInfo, err error) error {
179+
if err != nil {
180+
return err
181+
}
182+
183+
// Ignore directories.
184+
if info.IsDir() {
185+
return nil
186+
}
187+
188+
// Ignore perserved files.
189+
if isPreservedFile(info.Name()) {
190+
return nil
191+
}
192+
193+
pkg, err := filepath.Rel(projectDir, filepath.Dir(path))
194+
if err != nil {
195+
return errors.Wrap(err, "unexpected error while calculating unused packages")
196+
}
197+
198+
pkg = filepath.ToSlash(pkg)
199+
if _, ok := unusedPackages[pkg]; ok {
200+
files = append(files, path)
201+
}
202+
203+
return nil
204+
})
205+
206+
return files, err
207+
}
208+
209+
// pruneNonGoFiles delete all non-Go files existing within baseDir.
210+
// Files with names that are prefixed by any entry in preservedNonGoFiles
211+
// are not deleted.
212+
func pruneNonGoFiles(baseDir string, logger *log.Logger) error {
213+
files, err := collectNonGoFiles(baseDir, logger)
214+
if err != nil {
215+
return errors.Wrap(err, "could not collect non-Go files")
216+
}
217+
218+
if err := deleteFiles(files); err != nil {
219+
return errors.Wrap(err, "could not prune Go test files")
220+
}
221+
222+
return nil
223+
}
224+
225+
// collectNonGoFiles returns a slice containing all non-Go files in baseDir.
226+
// Files meeting the checks in isPreservedFile are not returned.
227+
func collectNonGoFiles(baseDir string, logger *log.Logger) ([]string, error) {
228+
files := make([]string, 0)
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+
// Ignore perserved files.
246+
if isPreservedFile(info.Name()) {
247+
return nil
248+
}
249+
250+
files = append(files, path)
251+
252+
return nil
253+
})
254+
255+
return files, err
256+
}
257+
258+
// isPreservedFile checks if the file name indicates that the file should be
259+
// preserved based on licenseFilePrefixes or legalFileSubstrings.
260+
func isPreservedFile(name string) bool {
261+
name = strings.ToLower(name)
262+
263+
for _, prefix := range licenseFilePrefixes {
264+
if strings.HasPrefix(name, prefix) {
265+
return true
266+
}
267+
}
268+
269+
for _, substring := range legalFileSubstrings {
270+
if strings.Contains(name, substring) {
271+
return true
272+
}
273+
}
274+
275+
return false
276+
}
277+
278+
// pruneGoTestFiles deletes all Go test files (*_test.go) within baseDir.
279+
func pruneGoTestFiles(baseDir string, logger *log.Logger) error {
280+
files, err := collectGoTestFiles(baseDir)
281+
if err != nil {
282+
return errors.Wrap(err, "could not collect Go test files")
283+
}
284+
285+
if err := deleteFiles(files); err != nil {
286+
return errors.Wrap(err, "could not prune Go test files")
287+
}
288+
289+
return nil
290+
}
291+
292+
// collectGoTestFiles returns a slice contains all Go test files (any files
293+
// prefixed with _test.go) in baseDir.
294+
func collectGoTestFiles(baseDir string) ([]string, error) {
295+
files := make([]string, 0)
296+
297+
err := filepath.Walk(baseDir, func(path string, info os.FileInfo, err error) error {
298+
if err != nil {
299+
return err
300+
}
301+
302+
// Ignore directories.
303+
if info.IsDir() {
304+
return nil
305+
}
306+
307+
// Ignore any files that is not a Go test file.
308+
if strings.HasSuffix(info.Name(), "_test.go") {
309+
files = append(files, path)
310+
}
311+
312+
return nil
313+
})
314+
315+
return files, err
316+
}
317+
318+
func deleteFiles(paths []string) error {
319+
for _, path := range paths {
320+
if err := os.Remove(path); err != nil {
321+
return err
322+
}
323+
}
324+
return nil
325+
}

0 commit comments

Comments
 (0)