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

Honor per-project prune options correctly #1570

Merged
merged 7 commits into from
Jan 24, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/dep/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (cmd *initCommand) Run(ctx *dep.Ctx, args []string) error {
}

// Set default prune options for go-tests and unused-packages
p.Manifest.PruneOptions.PruneOptions = gps.PruneNestedVendorDirs + gps.PruneGoTestFiles + gps.PruneUnusedPackages
p.Manifest.PruneOptions.DefaultOptions = gps.PruneNestedVendorDirs | gps.PruneGoTestFiles | gps.PruneUnusedPackages

if cmd.gopath {
gs := newGopathScanner(ctx, directDeps, sm)
Expand Down
97 changes: 74 additions & 23 deletions gps/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,6 @@ import (
// PruneOptions represents the pruning options used to write the dependecy tree.
type PruneOptions uint8

// PruneProjectOptions is map of prune options per project name.
type PruneProjectOptions map[ProjectRoot]PruneOptions

// RootPruneOptions represents the root prune options for the project.
// It contains the global options and a map of options per project.
type RootPruneOptions struct {
PruneOptions PruneOptions
ProjectOptions PruneProjectOptions
}

const (
// PruneNestedVendorDirs indicates if nested vendor directories should be pruned.
PruneNestedVendorDirs PruneOptions = 1 << iota
Expand All @@ -41,24 +31,85 @@ const (
PruneGoTestFiles
)

// DefaultRootPruneOptions instantiates a copy of the default root prune options.
func DefaultRootPruneOptions() RootPruneOptions {
return RootPruneOptions{
PruneOptions: PruneNestedVendorDirs,
ProjectOptions: PruneProjectOptions{},
}
// PruneOptionSet represents trinary distinctions for each of the types of
// prune rules (as expressed via PruneOptions): nested vendor directories,
// unused packages, non-go files, and go test files.
//
// The three-way distinction is between "none", "true", and "false", represented
// by uint8 values of 0, 1, and 2, respectively.
//
// This trinary distinction is necessary in order to record, with full fidelity,
// a cascading tree of pruning values, as expressed in CascadingPruneOptions; a
// simple boolean cannot delineate between "false" and "none".
type PruneOptionSet struct {
NestedVendor uint8
UnusedPackages uint8
NonGoFiles uint8
GoTests uint8
}

// CascadingPruneOptions is a set of rules for pruning a dependency tree.
//
// The DefaultOptions are the global default pruning rules, expressed as a
// single PruneOptions bitfield. These global rules will cascade down to
// individual project rules, unless superseded.
type CascadingPruneOptions struct {
DefaultOptions PruneOptions
PerProjectOptions map[ProjectRoot]PruneOptionSet
}

// PruneOptionsFor returns the prune options for the passed project root.
// PruneOptionsFor returns the PruneOptions bits for the given project,
// indicating which pruning rules should be applied to the project's code.
//
// It will return the root prune options if the project does not have specific
// options or if it does not exist in the manifest.
func (o *RootPruneOptions) PruneOptionsFor(pr ProjectRoot) PruneOptions {
if po, ok := o.ProjectOptions[pr]; ok {
return po
// It computes the cascade from default to project-specific options (if any) on
// the fly.
func (o CascadingPruneOptions) PruneOptionsFor(pr ProjectRoot) PruneOptions {
po, has := o.PerProjectOptions[pr]
if !has {
return o.DefaultOptions
}

ops := o.DefaultOptions
if po.NestedVendor != 0 {
if po.NestedVendor == 1 {
ops |= PruneNestedVendorDirs
} else {
ops &^= PruneNestedVendorDirs
}
}

return o.PruneOptions
if po.UnusedPackages != 0 {
if po.UnusedPackages == 1 {
ops |= PruneUnusedPackages
} else {
ops &^= PruneUnusedPackages
}
}

if po.NonGoFiles != 0 {
if po.NonGoFiles == 1 {
ops |= PruneNonGoFiles
} else {
ops &^= PruneNonGoFiles
}
}

if po.GoTests != 0 {
if po.GoTests == 1 {
ops |= PruneGoTestFiles
} else {
ops &^= PruneGoTestFiles
}
}

return ops
}

func defaultCascadingPruneOptions() CascadingPruneOptions {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe export this instead of having multiple copies of it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i intentionally didn't export it, as i don't think it's worth polluting the package interface with it.

return CascadingPruneOptions{
DefaultOptions: PruneNestedVendorDirs,
PerProjectOptions: map[ProjectRoot]PruneOptionSet{},
}
}

var (
Expand Down
94 changes: 85 additions & 9 deletions gps/prune_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,94 @@ import (
"github.com/golang/dep/internal/test"
)

func TestRootPruneOptions_PruneOptionsFor(t *testing.T) {
pr := ProjectRoot("github.com/golang/dep")

o := RootPruneOptions{
PruneOptions: PruneNestedVendorDirs,
ProjectOptions: PruneProjectOptions{
pr: PruneGoTestFiles,
func TestCascadingPruneOptions(t *testing.T) {
cases := []struct {
name string
co CascadingPruneOptions
results map[ProjectRoot]PruneOptions
}{
{
name: "all empty values",
co: CascadingPruneOptions{
DefaultOptions: PruneNestedVendorDirs,
PerProjectOptions: map[ProjectRoot]PruneOptionSet{
ProjectRoot("github.com/golang/dep"): {},
},
},
results: map[ProjectRoot]PruneOptions{
ProjectRoot("github.com/golang/dep"): PruneNestedVendorDirs,
},
},
{
name: "all overriden",
co: CascadingPruneOptions{
DefaultOptions: PruneNestedVendorDirs,
PerProjectOptions: map[ProjectRoot]PruneOptionSet{
ProjectRoot("github.com/golang/dep"): {
NestedVendor: 2,
UnusedPackages: 1,
NonGoFiles: 1,
GoTests: 1,
},
},
},
results: map[ProjectRoot]PruneOptions{
ProjectRoot("github.com/golang/dep"): PruneUnusedPackages | PruneNonGoFiles | PruneGoTestFiles,
},
},
{
name: "all redundant",
co: CascadingPruneOptions{
DefaultOptions: PruneNestedVendorDirs,
PerProjectOptions: map[ProjectRoot]PruneOptionSet{
ProjectRoot("github.com/golang/dep"): {
NestedVendor: 1,
UnusedPackages: 2,
NonGoFiles: 2,
GoTests: 2,
},
},
},
results: map[ProjectRoot]PruneOptions{
ProjectRoot("github.com/golang/dep"): PruneNestedVendorDirs,
},
},
{
name: "multiple projects, all combos",
co: CascadingPruneOptions{
DefaultOptions: PruneNestedVendorDirs,
PerProjectOptions: map[ProjectRoot]PruneOptionSet{
ProjectRoot("github.com/golang/dep"): {
NestedVendor: 1,
UnusedPackages: 2,
NonGoFiles: 2,
GoTests: 2,
},
ProjectRoot("github.com/other/one"): {
NestedVendor: 2,
UnusedPackages: 1,
NonGoFiles: 1,
GoTests: 1,
},
},
},
results: map[ProjectRoot]PruneOptions{
ProjectRoot("github.com/golang/dep"): PruneNestedVendorDirs,
ProjectRoot("github.com/other/one"): PruneUnusedPackages | PruneNonGoFiles | PruneGoTestFiles,
ProjectRoot("not/there"): PruneNestedVendorDirs,
},
},
}

if (o.PruneOptionsFor(pr) & PruneGoTestFiles) != PruneGoTestFiles {
t.Fatalf("invalid prune options.\n\t(GOT): %d\n\t(WNT): %d", o.PruneOptionsFor(pr), PruneGoTestFiles)
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
for pr, wanted := range c.results {
if c.co.PruneOptionsFor(pr) != wanted {
t.Fatalf("did not get expected final PruneOptions value from cascade:\n\t(GOT): %d\n\t(WNT): %d", c.co.PruneOptionsFor(pr), wanted)
}

}
})
}
}

Expand Down
10 changes: 3 additions & 7 deletions gps/solution.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const concurrentWriters = 16
//
// It requires a SourceManager to do the work. Prune options are read from the
// passed manifest.
func WriteDepTree(basedir string, l Lock, sm SourceManager, rpo RootPruneOptions, logger *log.Logger) error {
func WriteDepTree(basedir string, l Lock, sm SourceManager, co CascadingPruneOptions, logger *log.Logger) error {
if l == nil {
return fmt.Errorf("must provide non-nil Lock to WriteDepTree")
}
Expand Down Expand Up @@ -95,16 +95,12 @@ func WriteDepTree(basedir string, l Lock, sm SourceManager, rpo RootPruneOptions
return errors.Wrapf(err, "failed to export %s", projectRoot)
}

err := PruneProject(to, p, rpo.PruneOptionsFor(ident.ProjectRoot), logger)
err := PruneProject(to, p, co.PruneOptionsFor(ident.ProjectRoot), logger)
if err != nil {
return errors.Wrapf(err, "failed to prune %s", projectRoot)
}

if err := ctx.Err(); err != nil {
return err
}

return nil
return ctx.Err()
}()

switch err {
Expand Down
6 changes: 3 additions & 3 deletions gps/solution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@ func testWriteDepTree(t *testing.T) {
}

// nil lock/result should err immediately
err = WriteDepTree(tmp, nil, sm, DefaultRootPruneOptions(), discardLogger())
err = WriteDepTree(tmp, nil, sm, defaultCascadingPruneOptions(), discardLogger())
if err == nil {
t.Errorf("Should error if nil lock is passed to WriteDepTree")
}

err = WriteDepTree(tmp, r, sm, DefaultRootPruneOptions(), discardLogger())
err = WriteDepTree(tmp, r, sm, defaultCascadingPruneOptions(), discardLogger())
if err != nil {
t.Errorf("Unexpected error while creating vendor tree: %s", err)
}
Expand Down Expand Up @@ -166,7 +166,7 @@ func BenchmarkCreateVendorTree(b *testing.B) {
// ease manual inspection
os.RemoveAll(exp)
b.StartTimer()
err = WriteDepTree(exp, r, sm, DefaultRootPruneOptions(), logger)
err = WriteDepTree(exp, r, sm, defaultCascadingPruneOptions(), logger)
b.StopTimer()
if err != nil {
b.Errorf("unexpected error after %v iterations: %s", i, err)
Expand Down
Loading