@@ -24,26 +24,39 @@ const ManifestName = "Gopkg.toml"
2424
2525// Errors
2626var (
27- errInvalidConstraint = errors .New ("\" constraint\" must be a TOML array of tables" )
28- errInvalidOverride = errors .New ("\" override\" must be a TOML array of tables" )
29- errInvalidRequired = errors .New ("\" required\" must be a TOML list of strings" )
30- errInvalidIgnored = errors .New ("\" ignored\" must be a TOML list of strings" )
31- errInvalidProjectRoot = errors .New ("ProjectRoot name validation failed" )
27+ errInvalidConstraint = errors .Errorf ("%q must be a TOML array of tables" , "constraint" )
28+ errInvalidOverride = errors .Errorf ("%q must be a TOML array of tables" , "override" )
29+ errInvalidRequired = errors .Errorf ("%q must be a TOML list of strings" , "required" )
30+ errInvalidIgnored = errors .Errorf ("%q must be a TOML list of strings" , "ignored" )
31+ errInvalidPrune = errors .Errorf ("%q must be a TOML table of booleans" , "prune" )
32+
33+ errInvalidProjectRoot = errors .New ("ProjectRoot name validation failed" )
34+ errInvalidPruneValue = errors .New ("prune options values must be booleans" )
35+ errInvalidPruneProject = errors .Errorf ("%q must be a TOML array of tables" , "prune.project" )
36+ errPruneSubProject = errors .New ("prune projects should not contain sub projects" )
37+
38+ errInvalidRootPruneValue = errors .New ("root prune options must be omitted instead of being set to false" )
39+ errInvalidPruneProjectName = errors .Errorf ("%q in %q must be a string" , "name" , "prune.project" )
3240)
3341
3442// Manifest holds manifest file data and implements gps.RootManifest.
3543type Manifest struct {
3644 Constraints gps.ProjectConstraints
3745 Ovr gps.ProjectConstraints
38- Ignored []string
39- Required []string
46+
47+ Ignored []string
48+ Required []string
49+
50+ PruneOptions gps.PruneOptions
51+ PruneProjectOptions gps.PruneProjectOptions
4052}
4153
4254type rawManifest struct {
43- Constraints []rawProject `toml:"constraint,omitempty"`
44- Overrides []rawProject `toml:"override,omitempty"`
45- Ignored []string `toml:"ignored,omitempty"`
46- Required []string `toml:"required,omitempty"`
55+ Constraints []rawProject `toml:"constraint,omitempty"`
56+ Overrides []rawProject `toml:"override,omitempty"`
57+ Ignored []string `toml:"ignored,omitempty"`
58+ Required []string `toml:"required,omitempty"`
59+ PruneOptions rawPruneOptions `toml:"prune,omitempty"`
4760}
4861
4962type rawProject struct {
@@ -54,11 +67,33 @@ type rawProject struct {
5467 Source string `toml:"source,omitempty"`
5568}
5669
57- // NewManifest instantiates a new manifest.
70+ type rawPruneOptions struct {
71+ UnusedPackages bool `toml:"unused-packages,omitempty"`
72+ NonGoFiles bool `toml:"non-go,omitempty"`
73+ GoTests bool `toml:"go-tests,omitempty"`
74+
75+ Projects []rawPruneProjectOptions `toml:"project,omitempty"`
76+ }
77+
78+ type rawPruneProjectOptions struct {
79+ Name string `toml:"name"`
80+ UnusedPackages bool `toml:"unused-packages,omitempty"`
81+ NonGoFiles bool `toml:"non-go,omitempty"`
82+ GoTests bool `toml:"go-tests,omitempty"`
83+ }
84+
85+ const (
86+ pruneOptionUnusedPackages = "unused-packages"
87+ pruneOptionGoTests = "go-tests"
88+ pruneOptionNonGo = "non-go"
89+ )
90+
91+ // NewManifest instantites a new manifest.
5892func NewManifest () * Manifest {
5993 return & Manifest {
60- Constraints : make (gps.ProjectConstraints ),
61- Ovr : make (gps.ProjectConstraints ),
94+ Constraints : make (gps.ProjectConstraints ),
95+ Ovr : make (gps.ProjectConstraints ),
96+ PruneOptions : gps .PruneNestedVendorDirs ,
6297 }
6398}
6499
@@ -151,6 +186,12 @@ func validateManifest(s string) ([]error, error) {
151186 return warns , errInvalidRequired
152187 }
153188 }
189+ case "prune" :
190+ pruneWarns , err := validatePruneOptions (val , true )
191+ warns = append (warns , pruneWarns ... )
192+ if err != nil {
193+ return warns , err
194+ }
154195 default :
155196 warns = append (warns , fmt .Errorf ("unknown field in manifest: %v" , prop ))
156197 }
@@ -159,6 +200,70 @@ func validateManifest(s string) ([]error, error) {
159200 return warns , nil
160201}
161202
203+ func validatePruneOptions (val interface {}, root bool ) (warns []error , err error ) {
204+ if reflect .TypeOf (val ).Kind () != reflect .Map {
205+ return warns , errInvalidPrune
206+ }
207+
208+ for key , value := range val .(map [string ]interface {}) {
209+ switch key {
210+ case pruneOptionNonGo , pruneOptionGoTests , pruneOptionUnusedPackages :
211+ if option , ok := value .(bool ); ! ok {
212+ return warns , errInvalidPruneValue
213+ } else if root && ! option {
214+ return warns , errInvalidRootPruneValue
215+ }
216+ case "name" :
217+ if root {
218+ warns = append (warns , errors .Errorf ("%q should not include a name" , "prune" ))
219+ } else if _ , ok := value .(string ); ! ok {
220+ return warns , errInvalidPruneProjectName
221+ }
222+ case "project" :
223+ if ! root {
224+ return warns , errPruneSubProject
225+ }
226+ if reflect .TypeOf (value ).Kind () != reflect .Slice {
227+ return warns , errInvalidPruneProject
228+ }
229+ for _ , project := range value .([]interface {}) {
230+ projectWarns , err := validatePruneOptions (project , false )
231+ warns = append (warns , projectWarns ... )
232+ if err != nil {
233+ return nil , err
234+ }
235+ }
236+
237+ default :
238+ if root {
239+ warns = append (warns , errors .Errorf ("unknown field %q in %q" , key , "prune" ))
240+ } else {
241+ warns = append (warns , errors .Errorf ("unknown field %q in %q" , key , "prune.project" ))
242+ }
243+ }
244+ }
245+
246+ return warns , err
247+ }
248+
249+ func checkRedundantPruneOptions (raw rawManifest ) (warns []error ) {
250+ rootOptions := raw .PruneOptions
251+
252+ for _ , project := range raw .PruneOptions .Projects {
253+ if rootOptions .GoTests && project .GoTests {
254+ warns = append (warns , errors .Errorf ("redundant prune option %q set for %q" , pruneOptionGoTests , project .Name ))
255+ }
256+ if rootOptions .NonGoFiles && project .NonGoFiles {
257+ warns = append (warns , errors .Errorf ("redundant prune option %q set for %q" , pruneOptionNonGo , project .Name ))
258+ }
259+ if rootOptions .UnusedPackages && project .UnusedPackages {
260+ warns = append (warns , errors .Errorf ("redundant prune option %q set for %q" , pruneOptionUnusedPackages , project .Name ))
261+ }
262+ }
263+
264+ return warns
265+ }
266+
162267// ValidateProjectRoots validates the project roots present in manifest.
163268func ValidateProjectRoots (c * Ctx , m * Manifest , sm gps.SourceManager ) error {
164269 // Channel to receive all the errors
@@ -184,6 +289,10 @@ func ValidateProjectRoots(c *Ctx, m *Manifest, sm gps.SourceManager) error {
184289 wg .Add (1 )
185290 go validate (pr )
186291 }
292+ for pr := range m .PruneProjectOptions {
293+ wg .Add (1 )
294+ go validate (pr )
295+ }
187296
188297 wg .Wait ()
189298 close (errorCh )
@@ -220,6 +329,8 @@ func readManifest(r io.Reader) (*Manifest, []error, error) {
220329 return nil , warns , errors .Wrap (err , "unable to parse the manifest as TOML" )
221330 }
222331
332+ warns = append (warns , checkRedundantPruneOptions (raw )... )
333+
223334 m , err := fromRawManifest (raw )
224335 return m , warns , err
225336}
@@ -254,9 +365,43 @@ func fromRawManifest(raw rawManifest) (*Manifest, error) {
254365 m .Ovr [name ] = prj
255366 }
256367
368+ m .PruneOptions , m .PruneProjectOptions = fromRawPruneOptions (raw .PruneOptions )
369+
257370 return m , nil
258371}
259372
373+ func fromRawPruneOptions (raw rawPruneOptions ) (gps.PruneOptions , gps.PruneProjectOptions ) {
374+ rootOptions := gps .PruneNestedVendorDirs
375+ pruneProjects := make (gps.PruneProjectOptions )
376+
377+ if raw .UnusedPackages {
378+ rootOptions |= gps .PruneUnusedPackages
379+ }
380+ if raw .GoTests {
381+ rootOptions |= gps .PruneGoTestFiles
382+ }
383+ if raw .NonGoFiles {
384+ rootOptions |= gps .PruneNonGoFiles
385+ }
386+
387+ for _ , p := range raw .Projects {
388+ pr := gps .ProjectRoot (p .Name )
389+ pruneProjects [pr ] = gps .PruneNestedVendorDirs
390+
391+ if raw .UnusedPackages {
392+ pruneProjects [pr ] |= gps .PruneUnusedPackages
393+ }
394+ if raw .GoTests {
395+ pruneProjects [pr ] |= gps .PruneGoTestFiles
396+ }
397+ if raw .NonGoFiles {
398+ pruneProjects [pr ] |= gps .PruneNonGoFiles
399+ }
400+ }
401+
402+ return rootOptions , pruneProjects
403+ }
404+
260405// toProject interprets the string representations of project information held in
261406// a rawProject, converting them into a proper gps.ProjectProperties. An
262407// error is returned if the rawProject contains some invalid combination -
@@ -288,17 +433,27 @@ func toProject(raw rawProject) (n gps.ProjectRoot, pp gps.ProjectProperties, err
288433 }
289434
290435 pp .Source = raw .Source
436+
291437 return n , pp , nil
292438}
293439
440+ // MarshalTOML serializes this manifest into TOML via an intermediate raw form.
441+ func (m * Manifest ) MarshalTOML () ([]byte , error ) {
442+ raw := m .toRaw ()
443+ result , err := toml .Marshal (raw )
444+ return result , errors .Wrap (err , "unable to marshal the lock to a TOML string" )
445+ }
446+
294447// toRaw converts the manifest into a representation suitable to write to the manifest file
295448func (m * Manifest ) toRaw () rawManifest {
296449 raw := rawManifest {
297- Constraints : make ([]rawProject , 0 , len (m .Constraints )),
298- Overrides : make ([]rawProject , 0 , len (m .Ovr )),
299- Ignored : m .Ignored ,
300- Required : m .Required ,
450+ Constraints : make ([]rawProject , 0 , len (m .Constraints )),
451+ Overrides : make ([]rawProject , 0 , len (m .Ovr )),
452+ Ignored : m .Ignored ,
453+ Required : m .Required ,
454+ PruneOptions : rawPruneOptions {},
301455 }
456+
302457 for n , prj := range m .Constraints {
303458 raw .Constraints = append (raw .Constraints , toRawProject (n , prj ))
304459 }
@@ -309,6 +464,8 @@ func (m *Manifest) toRaw() rawManifest {
309464 }
310465 sort .Sort (sortedRawProjects (raw .Overrides ))
311466
467+ // TODO(ibrasho): write out prune options.
468+
312469 return raw
313470}
314471
@@ -329,13 +486,6 @@ func (s sortedRawProjects) Less(i, j int) bool {
329486 return l .Source < r .Source
330487}
331488
332- // MarshalTOML serializes this manifest into TOML via an intermediate raw form.
333- func (m * Manifest ) MarshalTOML () ([]byte , error ) {
334- raw := m .toRaw ()
335- result , err := toml .Marshal (raw )
336- return result , errors .Wrap (err , "Unable to marshal the lock to a TOML string" )
337- }
338-
339489func toRawProject (name gps.ProjectRoot , project gps.ProjectProperties ) rawProject {
340490 raw := rawProject {
341491 Name : string (name ),
@@ -363,6 +513,7 @@ func toRawProject(name gps.ProjectRoot, project gps.ProjectProperties) rawProjec
363513 // Has to be a semver range.
364514 raw .Version = project .Constraint .ImpliedCaretString ()
365515 }
516+
366517 return raw
367518}
368519
@@ -407,3 +558,11 @@ func (m *Manifest) RequiredPackages() map[string]bool {
407558
408559 return mp
409560}
561+
562+ func (m * Manifest ) PruneOptionsFor (pr gps.ProjectRoot ) gps.PruneOptions {
563+ if po , ok := m .PruneProjectOptions [pr ]; ok {
564+ return po
565+ }
566+
567+ return m .PruneOptions
568+ }
0 commit comments