diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e7c76ea79..ce59de46e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,16 @@ NEW FEATURES: BUG FIXES: -* Fix per-project prune option handling ([#1562](https://github.com/golang/dep/pull/1562)) +* Fix per-project prune option handling ([#1570](https://github.com/golang/dep/pull/1570)) IMPROVEMENTS: +# v0.4.1 + +BUG FIXES: + +* Fix per-project prune option handling ([#1570](https://github.com/golang/dep/pull/1570)) + # v0.4.0 NEW FEATURES: diff --git a/cmd/dep/init.go b/cmd/dep/init.go index 3c9ae4a503..fe90b8671b 100644 --- a/cmd/dep/init.go +++ b/cmd/dep/init.go @@ -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) diff --git a/cmd/dep/prune.go b/cmd/dep/prune.go index 43408d60f1..d7f93e8daf 100644 --- a/cmd/dep/prune.go +++ b/cmd/dep/prune.go @@ -5,9 +5,20 @@ package main import ( + "bytes" "flag" + "io/ioutil" + "log" + "os" + "path/filepath" + "sort" + "strings" "github.com/golang/dep" + "github.com/golang/dep/gps" + "github.com/golang/dep/gps/pkgtree" + "github.com/golang/dep/internal/fs" + "github.com/pkg/errors" ) const pruneShortHelp = `Pruning is now performed automatically by dep ensure.` @@ -17,20 +28,182 @@ Set prune options in the manifest and it will be applied after every ensure. dep prune will be removed in a future version of dep, causing this command to exit non-0. ` -type pruneCommand struct{} +type pruneCommand struct { +} func (cmd *pruneCommand) Name() string { return "prune" } func (cmd *pruneCommand) Args() string { return "" } func (cmd *pruneCommand) ShortHelp() string { return pruneShortHelp } func (cmd *pruneCommand) LongHelp() string { return pruneLongHelp } -func (cmd *pruneCommand) Hidden() bool { return true } +func (cmd *pruneCommand) Hidden() bool { return false } -func (cmd *pruneCommand) Register(fs *flag.FlagSet) {} +func (cmd *pruneCommand) Register(fs *flag.FlagSet) { +} func (cmd *pruneCommand) Run(ctx *dep.Ctx, args []string) error { ctx.Out.Printf("Pruning is now performed automatically by dep ensure.\n") ctx.Out.Printf("Set prune settings in %s and it it will be applied when running ensure.\n", dep.ManifestName) ctx.Out.Printf("\ndep prune will be removed in a future version, and this command will exit non-0.\nPlease update your scripts.\n") + p, err := ctx.LoadProject() + if err != nil { + return err + } + + sm, err := ctx.SourceManager() + if err != nil { + return err + } + sm.UseDefaultSignalHandling() + defer sm.Release() + + // While the network churns on ListVersions() requests, statically analyze + // code from the current project. + ptree, err := pkgtree.ListPackages(p.ResolvedAbsRoot, string(p.ImportRoot)) + if err != nil { + return errors.Wrap(err, "analysis of local packages failed: %v") + } + + // Set up a solver in order to check the InputHash. + params := p.MakeParams() + params.RootPackageTree = ptree + + if ctx.Verbose { + params.TraceLogger = ctx.Err + } + + s, err := gps.Prepare(params, sm) + if err != nil { + return errors.Wrap(err, "could not set up solver for input hashing") + } + + if p.Lock == nil { + return errors.Errorf("Gopkg.lock must exist for prune to know what files are safe to remove.") + } + + if !bytes.Equal(s.HashInputs(), p.Lock.SolveMeta.InputsDigest) { + return errors.Errorf("Gopkg.lock is out of sync; run dep ensure before pruning.") + } + + pruneLogger := ctx.Err + if !ctx.Verbose { + pruneLogger = log.New(ioutil.Discard, "", 0) + } + return pruneProject(p, sm, pruneLogger) +} + +// pruneProject removes unused packages from a project. +func pruneProject(p *dep.Project, sm gps.SourceManager, logger *log.Logger) error { + td, err := ioutil.TempDir(os.TempDir(), "dep") + if err != nil { + return errors.Wrap(err, "error while creating temp dir for writing manifest/lock/vendor") + } + defer os.RemoveAll(td) + + if err := gps.WriteDepTree(td, p.Lock, sm, gps.CascadingPruneOptions{DefaultOptions: gps.PruneNestedVendorDirs}, logger); err != nil { + return err + } + + var toKeep []string + for _, project := range p.Lock.Projects() { + projectRoot := string(project.Ident().ProjectRoot) + for _, pkg := range project.Packages() { + toKeep = append(toKeep, filepath.Join(projectRoot, pkg)) + } + } + + toDelete, err := calculatePrune(td, toKeep, logger) + if err != nil { + return err + } + + if len(toDelete) > 0 { + logger.Println("Calculated the following directories to prune:") + for _, d := range toDelete { + logger.Printf(" %s\n", d) + } + } else { + logger.Println("No directories found to prune") + } + + if err := deleteDirs(toDelete); err != nil { + return err + } + + vpath := filepath.Join(p.AbsRoot, "vendor") + vendorbak := vpath + ".orig" + var failerr error + if _, err := os.Stat(vpath); err == nil { + // Move out the old vendor dir. just do it into an adjacent dir, to + // try to mitigate the possibility of a pointless cross-filesystem + // move with a temp directory. + if _, err := os.Stat(vendorbak); err == nil { + // If the adjacent dir already exists, bite the bullet and move + // to a proper tempdir. + vendorbak = filepath.Join(td, "vendor.orig") + } + failerr = fs.RenameWithFallback(vpath, vendorbak) + if failerr != nil { + goto fail + } + } + + // Move in the new one. + failerr = fs.RenameWithFallback(td, vpath) + if failerr != nil { + goto fail + } + + os.RemoveAll(vendorbak) + return nil + +fail: + fs.RenameWithFallback(vendorbak, vpath) + return failerr } + +func calculatePrune(vendorDir string, keep []string, logger *log.Logger) ([]string, error) { + logger.Println("Calculating prune. Checking the following packages:") + sort.Strings(keep) + toDelete := []string{} + err := filepath.Walk(vendorDir, func(path string, info os.FileInfo, err error) error { + if _, err := os.Lstat(path); err != nil { + return nil + } + if !info.IsDir() { + return nil + } + if path == vendorDir { + return nil + } + + name := strings.TrimPrefix(path, vendorDir+string(filepath.Separator)) + logger.Printf(" %s", name) + i := sort.Search(len(keep), func(i int) bool { + return name <= keep[i] + }) + if i >= len(keep) || !strings.HasPrefix(keep[i], name) { + toDelete = append(toDelete, path) + } + return nil + }) + return toDelete, err +} + +func deleteDirs(toDelete []string) error { + // sort by length so we delete sub dirs first + sort.Sort(byLen(toDelete)) + for _, path := range toDelete { + if err := os.RemoveAll(path); err != nil { + return err + } + } + return nil +} + +type byLen []string + +func (a byLen) Len() int { return len(a) } +func (a byLen) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byLen) Less(i, j int) bool { return len(a[i]) > len(a[j]) } diff --git a/gps/prune.go b/gps/prune.go index 40d645460e..aa8671c414 100644 --- a/gps/prune.go +++ b/gps/prune.go @@ -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 @@ -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 { + return CascadingPruneOptions{ + DefaultOptions: PruneNestedVendorDirs, + PerProjectOptions: map[ProjectRoot]PruneOptionSet{}, + } } var ( diff --git a/gps/prune_test.go b/gps/prune_test.go index d4c7371237..0d202a8cac 100644 --- a/gps/prune_test.go +++ b/gps/prune_test.go @@ -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) + } + + } + }) } } diff --git a/gps/solution.go b/gps/solution.go index f737cab976..7eb419e40b 100644 --- a/gps/solution.go +++ b/gps/solution.go @@ -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") } @@ -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 { diff --git a/gps/solution_test.go b/gps/solution_test.go index e868958165..2bb2ab424e 100644 --- a/gps/solution_test.go +++ b/gps/solution_test.go @@ -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) } @@ -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) diff --git a/manifest.go b/manifest.go index f9202973d9..add9236fdf 100644 --- a/manifest.go +++ b/manifest.go @@ -51,7 +51,7 @@ type Manifest struct { Ignored []string Required []string - PruneOptions gps.RootPruneOptions + PruneOptions gps.CascadingPruneOptions } type rawManifest struct { @@ -75,14 +75,8 @@ type rawPruneOptions struct { NonGoFiles bool `toml:"non-go,omitempty"` GoTests bool `toml:"go-tests,omitempty"` - Projects []rawPruneProjectOptions `toml:"project,omitempty"` -} - -type rawPruneProjectOptions struct { - Name string `toml:"name"` - UnusedPackages bool `toml:"unused-packages,omitempty"` - NonGoFiles bool `toml:"non-go,omitempty"` - GoTests bool `toml:"go-tests,omitempty"` + //Projects []map[string]interface{} `toml:"project,omitempty"` + Projects []map[string]interface{} } const ( @@ -91,12 +85,22 @@ const ( pruneOptionNonGo = "non-go" ) +// Constants to represents per-project prune uint8 values. +const ( + pvnone uint8 = 0 // No per-project prune value was set in Gopkg.toml. + pvtrue uint8 = 1 // Per-project prune value was explicitly set to true. + pvfalse uint8 = 2 // Per-project prune value was explicitly set to false. +) + // NewManifest instantites a new manifest. func NewManifest() *Manifest { return &Manifest{ - Constraints: make(gps.ProjectConstraints), - Ovr: make(gps.ProjectConstraints), - PruneOptions: gps.DefaultRootPruneOptions(), + Constraints: make(gps.ProjectConstraints), + Ovr: make(gps.ProjectConstraints), + PruneOptions: gps.CascadingPruneOptions{ + DefaultOptions: gps.PruneNestedVendorDirs, + PerProjectOptions: map[gps.ProjectRoot]gps.PruneOptionSet{}, + }, } } @@ -259,18 +263,24 @@ func validatePruneOptions(val interface{}, root bool) (warns []error, err error) return warns, err } -func checkRedundantPruneOptions(raw rawManifest) (warns []error) { - rootOptions := raw.PruneOptions - - for _, project := range raw.PruneOptions.Projects { - if rootOptions.GoTests && project.GoTests { - warns = append(warns, errors.Errorf("redundant prune option %q set for %q", pruneOptionGoTests, project.Name)) +func checkRedundantPruneOptions(co gps.CascadingPruneOptions) (warns []error) { + for name, project := range co.PerProjectOptions { + if project.UnusedPackages != pvnone { + if (co.DefaultOptions&gps.PruneUnusedPackages != 0) == (project.UnusedPackages == pvtrue) { + warns = append(warns, errors.Errorf("redundant prune option %q set for %q", pruneOptionUnusedPackages, name)) + } } - if rootOptions.NonGoFiles && project.NonGoFiles { - warns = append(warns, errors.Errorf("redundant prune option %q set for %q", pruneOptionNonGo, project.Name)) + + if project.NonGoFiles != pvnone { + if (co.DefaultOptions&gps.PruneNonGoFiles != 0) == (project.NonGoFiles == pvtrue) { + warns = append(warns, errors.Errorf("redundant prune option %q set for %q", pruneOptionNonGo, name)) + } } - if rootOptions.UnusedPackages && project.UnusedPackages { - warns = append(warns, errors.Errorf("redundant prune option %q set for %q", pruneOptionUnusedPackages, project.Name)) + + if project.GoTests != pvnone { + if (co.DefaultOptions&gps.PruneGoTestFiles != 0) == (project.GoTests == pvtrue) { + warns = append(warns, errors.Errorf("redundant prune option %q set for %q", pruneOptionGoTests, name)) + } } } @@ -302,7 +312,7 @@ func ValidateProjectRoots(c *Ctx, m *Manifest, sm gps.SourceManager) error { wg.Add(1) go validate(pr) } - for pr := range m.PruneOptions.ProjectOptions { + for pr := range m.PruneOptions.PerProjectOptions { wg.Add(1) go validate(pr) } @@ -342,13 +352,16 @@ func readManifest(r io.Reader) (*Manifest, []error, error) { return nil, warns, errors.Wrap(err, "unable to parse the manifest as TOML") } - warns = append(warns, checkRedundantPruneOptions(raw)...) + m, err := fromRawManifest(raw, buf) + if err != nil { + return nil, warns, err + } - m, err := fromRawManifest(raw) - return m, warns, err + warns = append(warns, checkRedundantPruneOptions(m.PruneOptions)...) + return m, warns, nil } -func fromRawManifest(raw rawManifest) (*Manifest, error) { +func fromRawManifest(raw rawManifest, buf *bytes.Buffer) (*Manifest, error) { m := NewManifest() m.Constraints = make(gps.ProjectConstraints, len(raw.Constraints)) @@ -378,39 +391,66 @@ func fromRawManifest(raw rawManifest) (*Manifest, error) { m.Ovr[name] = prj } - m.PruneOptions = fromRawPruneOptions(raw.PruneOptions) + // TODO(sdboyer) it is awful that we have to do this manual extraction + tree, err := toml.Load(buf.String()) + if err != nil { + return nil, errors.Wrap(err, "unable to load TomlTree from string") + } + + iprunemap := tree.Get("prune") + if iprunemap == nil { + return m, nil + } + // Previous validation already guaranteed that, if it exists, it's this map + // type. + m.PruneOptions = fromRawPruneOptions(iprunemap.(*toml.Tree).ToMap()) return m, nil } -func fromRawPruneOptions(raw rawPruneOptions) gps.RootPruneOptions { - opts := gps.RootPruneOptions{ - PruneOptions: gps.PruneNestedVendorDirs, - ProjectOptions: make(gps.PruneProjectOptions), +func fromRawPruneOptions(prunemap map[string]interface{}) gps.CascadingPruneOptions { + opts := gps.CascadingPruneOptions{ + DefaultOptions: gps.PruneNestedVendorDirs, + PerProjectOptions: make(map[gps.ProjectRoot]gps.PruneOptionSet), } - if raw.UnusedPackages { - opts.PruneOptions |= gps.PruneUnusedPackages + if val, has := prunemap[pruneOptionUnusedPackages]; has && val.(bool) { + opts.DefaultOptions |= gps.PruneUnusedPackages } - if raw.GoTests { - opts.PruneOptions |= gps.PruneGoTestFiles + if val, has := prunemap[pruneOptionNonGo]; has && val.(bool) { + opts.DefaultOptions |= gps.PruneNonGoFiles } - if raw.NonGoFiles { - opts.PruneOptions |= gps.PruneNonGoFiles + if val, has := prunemap[pruneOptionGoTests]; has && val.(bool) { + opts.DefaultOptions |= gps.PruneGoTestFiles } - for _, p := range raw.Projects { - pr := gps.ProjectRoot(p.Name) - opts.ProjectOptions[pr] = gps.PruneNestedVendorDirs - - if p.UnusedPackages { - opts.ProjectOptions[pr] |= gps.PruneUnusedPackages - } - if p.GoTests { - opts.ProjectOptions[pr] |= gps.PruneGoTestFiles + trinary := func(v interface{}) uint8 { + b := v.(bool) + if b { + return pvtrue } - if p.NonGoFiles { - opts.ProjectOptions[pr] |= gps.PruneNonGoFiles + return pvfalse + } + + if projprunes, has := prunemap["project"]; has { + for _, proj := range projprunes.([]interface{}) { + var pr gps.ProjectRoot + // This should be redundant, but being explicit doesn't hurt. + pos := gps.PruneOptionSet{NestedVendor: pvtrue} + + for key, val := range proj.(map[string]interface{}) { + switch key { + case "name": + pr = gps.ProjectRoot(val.(string)) + case pruneOptionNonGo: + pos.NonGoFiles = trinary(val) + case pruneOptionGoTests: + pos.GoTests = trinary(val) + case pruneOptionUnusedPackages: + pos.UnusedPackages = trinary(val) + } + } + opts.PerProjectOptions[pr] = pos } } @@ -421,21 +461,21 @@ func fromRawPruneOptions(raw rawPruneOptions) gps.RootPruneOptions { // // Will panic if gps.RootPruneOption includes ProjectPruneOptions // See https://github.com/golang/dep/pull/1460#discussion_r158128740 for more information -func toRawPruneOptions(root gps.RootPruneOptions) rawPruneOptions { - if len(root.ProjectOptions) != 0 { +func toRawPruneOptions(co gps.CascadingPruneOptions) rawPruneOptions { + if len(co.PerProjectOptions) != 0 { panic("toRawPruneOptions cannot convert ProjectOptions to rawPruneOptions") } raw := rawPruneOptions{} - if (root.PruneOptions & gps.PruneUnusedPackages) != 0 { + if (co.DefaultOptions & gps.PruneUnusedPackages) != 0 { raw.UnusedPackages = true } - if (root.PruneOptions & gps.PruneNonGoFiles) != 0 { + if (co.DefaultOptions & gps.PruneNonGoFiles) != 0 { raw.NonGoFiles = true } - if (root.PruneOptions & gps.PruneGoTestFiles) != 0 { + if (co.DefaultOptions & gps.PruneGoTestFiles) != 0 { raw.GoTests = true } return raw diff --git a/manifest_test.go b/manifest_test.go index cd22c38313..28d5e43b9d 100644 --- a/manifest_test.go +++ b/manifest_test.go @@ -46,12 +46,9 @@ func TestReadManifest(t *testing.T) { }, }, Ignored: []string{"github.com/foo/bar"}, - PruneOptions: gps.RootPruneOptions{ - PruneOptions: gps.PruneNestedVendorDirs | gps.PruneNonGoFiles, - ProjectOptions: gps.PruneProjectOptions{ - gps.ProjectRoot("github.com/golang/dep"): gps.PruneNestedVendorDirs, - gps.ProjectRoot("github.com/babble/brook"): gps.PruneNestedVendorDirs | gps.PruneGoTestFiles, - }, + PruneOptions: gps.CascadingPruneOptions{ + DefaultOptions: gps.PruneNestedVendorDirs | gps.PruneNonGoFiles, + PerProjectOptions: make(map[gps.ProjectRoot]gps.PruneOptionSet), }, } @@ -64,6 +61,10 @@ func TestReadManifest(t *testing.T) { if !reflect.DeepEqual(got.Ignored, want.Ignored) { t.Error("Valid manifest's ignored did not parse as expected") } + if !reflect.DeepEqual(got.PruneOptions, want.PruneOptions) { + t.Error("Valid manifest's prune options did not parse as expected") + t.Error(got.PruneOptions, want.PruneOptions) + } } func TestWriteManifest(t *testing.T) { @@ -85,6 +86,10 @@ func TestWriteManifest(t *testing.T) { Constraint: gps.NewBranch("master"), } m.Ignored = []string{"github.com/foo/bar"} + m.PruneOptions = gps.CascadingPruneOptions{ + DefaultOptions: gps.PruneNestedVendorDirs | gps.PruneNonGoFiles, + PerProjectOptions: make(map[gps.ProjectRoot]gps.PruneOptionSet), + } got, err := m.MarshalTOML() if err != nil { @@ -463,29 +468,74 @@ func TestValidateManifest(t *testing.T) { func TestCheckRedundantPruneOptions(t *testing.T) { cases := []struct { name string - pruneOptions rawPruneOptions + pruneOptions gps.CascadingPruneOptions wantWarn []error }{ { - name: "redundant project prune options", - pruneOptions: rawPruneOptions{ - NonGoFiles: true, - Projects: []rawPruneProjectOptions{ - rawPruneProjectOptions{ - Name: "github.com/org/project", - NonGoFiles: true, + name: "all redundant on true", + pruneOptions: gps.CascadingPruneOptions{ + DefaultOptions: 15, + PerProjectOptions: map[gps.ProjectRoot]gps.PruneOptionSet{ + "github.com/golang/dep": gps.PruneOptionSet{ + NestedVendor: pvtrue, + UnusedPackages: pvtrue, + NonGoFiles: pvtrue, + GoTests: pvtrue, + }, + }, + }, + wantWarn: []error{ + fmt.Errorf("redundant prune option %q set for %q", "unused-packages", "github.com/golang/dep"), + fmt.Errorf("redundant prune option %q set for %q", "non-go", "github.com/golang/dep"), + fmt.Errorf("redundant prune option %q set for %q", "go-tests", "github.com/golang/dep"), + }, + }, + { + name: "all redundant on false", + pruneOptions: gps.CascadingPruneOptions{ + DefaultOptions: 1, + PerProjectOptions: map[gps.ProjectRoot]gps.PruneOptionSet{ + "github.com/golang/dep": gps.PruneOptionSet{ + NestedVendor: pvtrue, + UnusedPackages: pvfalse, + NonGoFiles: pvfalse, + GoTests: pvfalse, + }, + }, + }, + wantWarn: []error{ + fmt.Errorf("redundant prune option %q set for %q", "unused-packages", "github.com/golang/dep"), + fmt.Errorf("redundant prune option %q set for %q", "non-go", "github.com/golang/dep"), + fmt.Errorf("redundant prune option %q set for %q", "go-tests", "github.com/golang/dep"), + }, + }, + { + name: "redundancy mix across multiple projects", + pruneOptions: gps.CascadingPruneOptions{ + DefaultOptions: 7, + PerProjectOptions: map[gps.ProjectRoot]gps.PruneOptionSet{ + "github.com/golang/dep": gps.PruneOptionSet{ + NestedVendor: pvtrue, + NonGoFiles: pvtrue, + GoTests: pvtrue, + }, + "github.com/other/project": gps.PruneOptionSet{ + NestedVendor: pvtrue, + UnusedPackages: pvfalse, + GoTests: pvfalse, }, }, }, wantWarn: []error{ - fmt.Errorf("redundant prune option %q set for %q", "non-go", "github.com/org/project"), + fmt.Errorf("redundant prune option %q set for %q", "non-go", "github.com/golang/dep"), + fmt.Errorf("redundant prune option %q set for %q", "go-tests", "github.com/other/project"), }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - errs := checkRedundantPruneOptions(rawManifest{PruneOptions: c.pruneOptions}) + errs := checkRedundantPruneOptions(c.pruneOptions) // compare length of error slice if len(errs) != len(c.wantWarn) { @@ -608,78 +658,114 @@ func TestValidateProjectRoots(t *testing.T) { } } -func TestFromRawPruneOptions(t *testing.T) { - cases := []struct { - name string - rawPruneOptions rawPruneOptions - wantOptions gps.RootPruneOptions - }{ - { - name: "global all options project no options", - rawPruneOptions: rawPruneOptions{ - UnusedPackages: true, - NonGoFiles: true, - GoTests: true, - Projects: []rawPruneProjectOptions{ - { - Name: "github.com/golang/dep", - UnusedPackages: false, - NonGoFiles: false, - GoTests: false, - }, - }, - }, - wantOptions: gps.RootPruneOptions{ - PruneOptions: 15, - ProjectOptions: gps.PruneProjectOptions{ - "github.com/golang/dep": 1, - }, - }, - }, - { - name: "global no options project all options", - rawPruneOptions: rawPruneOptions{ - UnusedPackages: false, - NonGoFiles: false, - GoTests: false, - Projects: []rawPruneProjectOptions{ - { - Name: "github.com/golang/dep", - UnusedPackages: true, - NonGoFiles: true, - GoTests: true, - }, - }, - }, - wantOptions: gps.RootPruneOptions{ - PruneOptions: 1, - ProjectOptions: gps.PruneProjectOptions{ - "github.com/golang/dep": 15, - }, - }, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - opts := fromRawPruneOptions(c.rawPruneOptions) - - if !reflect.DeepEqual(opts, c.wantOptions) { - t.Fatalf("rawPruneOptions are not as expected:\n\t(GOT) %v\n\t(WNT) %v", opts, c.wantOptions) - } - }) - } -} +//func TestFromRawPruneOptions(t *testing.T) { +//cases := []struct { +//name string +//rawPruneOptions rawPruneOptions +//wantOptions gps.CascadingPruneOptions +//}{ +//{ +//name: "global all options project no options", +//rawPruneOptions: rawPruneOptions{ +//UnusedPackages: true, +//NonGoFiles: true, +//GoTests: true, +//Projects: []map[string]interface{}{ +//{ +//"name": "github.com/golang/dep", +//pruneOptionUnusedPackages: false, +//pruneOptionNonGo: false, +//pruneOptionGoTests: false, +//}, +//}, +//}, +//wantOptions: gps.CascadingPruneOptions{ +//DefaultOptions: 15, +//PerProjectOptions: map[gps.ProjectRoot]gps.PruneOptionSet{ +//"github.com/golang/dep": gps.PruneOptionSet{ +//NestedVendor: pvtrue, +//UnusedPackages: pvfalse, +//NonGoFiles: pvfalse, +//GoTests: pvfalse, +//}, +//}, +//}, +//}, +//{ +//name: "global all options project mixed options", +//rawPruneOptions: rawPruneOptions{ +//UnusedPackages: true, +//NonGoFiles: true, +//GoTests: true, +//Projects: []map[string]interface{}{ +//{ +//"name": "github.com/golang/dep", +//pruneOptionUnusedPackages: false, +//}, +//}, +//}, +//wantOptions: gps.CascadingPruneOptions{ +//DefaultOptions: 15, +//PerProjectOptions: map[gps.ProjectRoot]gps.PruneOptionSet{ +//"github.com/golang/dep": gps.PruneOptionSet{ +//NestedVendor: pvtrue, +//UnusedPackages: pvfalse, +//}, +//}, +//}, +//}, +//{ +//name: "global no options project all options", +//rawPruneOptions: rawPruneOptions{ +//UnusedPackages: false, +//NonGoFiles: false, +//GoTests: false, +//Projects: []map[string]interface{}{ +//{ +//"name": "github.com/golang/dep", +//pruneOptionUnusedPackages: true, +//pruneOptionNonGo: true, +//pruneOptionGoTests: true, +//}, +//}, +//}, +//wantOptions: gps.CascadingPruneOptions{ +//DefaultOptions: 1, +//PerProjectOptions: map[gps.ProjectRoot]gps.PruneOptionSet{ +//"github.com/golang/dep": gps.PruneOptionSet{ +//NestedVendor: pvtrue, +//UnusedPackages: pvtrue, +//NonGoFiles: pvtrue, +//GoTests: pvtrue, +//}, +//}, +//}, +//}, +//} + +//for _, c := range cases { +//t.Run(c.name, func(t *testing.T) { +//opts, err := fromRawPruneOptions(c.rawPruneOptions) +//if err != nil { +//t.Fatal(err) +//} + +//if !reflect.DeepEqual(opts, c.wantOptions) { +//t.Fatalf("rawPruneOptions are not as expected:\n\t(GOT) %v\n\t(WNT) %v", opts, c.wantOptions) +//} +//}) +//} +//} func TestToRawPruneOptions(t *testing.T) { cases := []struct { name string - pruneOptions gps.RootPruneOptions + pruneOptions gps.CascadingPruneOptions wantOptions rawPruneOptions }{ { name: "all options", - pruneOptions: gps.RootPruneOptions{PruneOptions: 15}, + pruneOptions: gps.CascadingPruneOptions{DefaultOptions: 15}, wantOptions: rawPruneOptions{ UnusedPackages: true, NonGoFiles: true, @@ -688,7 +774,7 @@ func TestToRawPruneOptions(t *testing.T) { }, { name: "no options", - pruneOptions: gps.RootPruneOptions{PruneOptions: 1}, + pruneOptions: gps.CascadingPruneOptions{DefaultOptions: 1}, wantOptions: rawPruneOptions{ UnusedPackages: false, NonGoFiles: false, @@ -709,9 +795,13 @@ func TestToRawPruneOptions(t *testing.T) { } func TestToRawPruneOptions_Panic(t *testing.T) { - pruneOptions := gps.RootPruneOptions{ - PruneOptions: 1, - ProjectOptions: gps.PruneProjectOptions{"github.com/carolynvs/deptest": 1}, + pruneOptions := gps.CascadingPruneOptions{ + DefaultOptions: 1, + PerProjectOptions: map[gps.ProjectRoot]gps.PruneOptionSet{ + "github.com/carolynvs/deptest": gps.PruneOptionSet{ + NestedVendor: pvtrue, + }, + }, } defer func() { if err := recover(); err == nil { diff --git a/testdata/manifest/golden.toml b/testdata/manifest/golden.toml index 2ba8dfa06e..62af53fabb 100644 --- a/testdata/manifest/golden.toml +++ b/testdata/manifest/golden.toml @@ -12,3 +12,6 @@ ignored = ["github.com/foo/bar"] branch = "master" name = "github.com/golang/dep" source = "https://github.com/golang/dep" + +[prune] + non-go = true diff --git a/txn_writer.go b/txn_writer.go index 60067092a0..0cb19706b7 100644 --- a/txn_writer.go +++ b/txn_writer.go @@ -66,7 +66,7 @@ type SafeWriter struct { lockDiff *gps.LockDiff writeVendor bool writeLock bool - pruneOptions gps.RootPruneOptions + pruneOptions gps.CascadingPruneOptions } // NewSafeWriter sets up a SafeWriter to write a set of manifest, lock, and @@ -84,7 +84,7 @@ type SafeWriter struct { // - If oldLock is provided without newLock, error. // // - If vendor is VendorAlways without a newLock, error. -func NewSafeWriter(manifest *Manifest, oldLock, newLock *Lock, vendor VendorBehavior, prune gps.RootPruneOptions) (*SafeWriter, error) { +func NewSafeWriter(manifest *Manifest, oldLock, newLock *Lock, vendor VendorBehavior, prune gps.CascadingPruneOptions) (*SafeWriter, error) { sw := &SafeWriter{ Manifest: manifest, lock: newLock, diff --git a/txn_writer_test.go b/txn_writer_test.go index 45181f5c11..4b55aa89fd 100644 --- a/txn_writer_test.go +++ b/txn_writer_test.go @@ -20,13 +20,20 @@ const safeWriterProject = "safewritertest" const safeWriterGoldenManifest = "txn_writer/expected_manifest.toml" const safeWriterGoldenLock = "txn_writer/expected_lock.toml" +func defaultCascadingPruneOptions() gps.CascadingPruneOptions { + return gps.CascadingPruneOptions{ + DefaultOptions: gps.PruneNestedVendorDirs, + PerProjectOptions: map[gps.ProjectRoot]gps.PruneOptionSet{}, + } +} + func TestSafeWriter_BadInput_MissingRoot(t *testing.T) { h := test.NewHelper(t) defer h.Cleanup() pc := NewTestProjectContext(h, safeWriterProject) defer pc.Release() - sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged, gps.DefaultRootPruneOptions()) + sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged, defaultCascadingPruneOptions()) err := sw.Write("", pc.SourceManager, true, discardLogger()) if err == nil { @@ -44,7 +51,7 @@ func TestSafeWriter_BadInput_MissingSourceManager(t *testing.T) { pc.CopyFile(LockName, safeWriterGoldenLock) pc.Load() - sw, _ := NewSafeWriter(nil, nil, pc.Project.Lock, VendorAlways, gps.DefaultRootPruneOptions()) + sw, _ := NewSafeWriter(nil, nil, pc.Project.Lock, VendorAlways, defaultCascadingPruneOptions()) err := sw.Write(pc.Project.AbsRoot, nil, true, discardLogger()) if err == nil { @@ -60,7 +67,7 @@ func TestSafeWriter_BadInput_ForceVendorMissingLock(t *testing.T) { pc := NewTestProjectContext(h, safeWriterProject) defer pc.Release() - _, err := NewSafeWriter(nil, nil, nil, VendorAlways, gps.DefaultRootPruneOptions()) + _, err := NewSafeWriter(nil, nil, nil, VendorAlways, defaultCascadingPruneOptions()) if err == nil { t.Fatal("should have errored without a lock when forceVendor is true, but did not") } else if !strings.Contains(err.Error(), "newLock") { @@ -76,7 +83,7 @@ func TestSafeWriter_BadInput_OldLockOnly(t *testing.T) { pc.CopyFile(LockName, safeWriterGoldenLock) pc.Load() - _, err := NewSafeWriter(nil, pc.Project.Lock, nil, VendorAlways, gps.DefaultRootPruneOptions()) + _, err := NewSafeWriter(nil, pc.Project.Lock, nil, VendorAlways, defaultCascadingPruneOptions()) if err == nil { t.Fatal("should have errored with only an old lock, but did not") } else if !strings.Contains(err.Error(), "oldLock") { @@ -90,7 +97,7 @@ func TestSafeWriter_BadInput_NonexistentRoot(t *testing.T) { pc := NewTestProjectContext(h, safeWriterProject) defer pc.Release() - sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged, gps.DefaultRootPruneOptions()) + sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged, defaultCascadingPruneOptions()) missingroot := filepath.Join(pc.Project.AbsRoot, "nonexistent") err := sw.Write(missingroot, pc.SourceManager, true, discardLogger()) @@ -108,7 +115,7 @@ func TestSafeWriter_BadInput_RootIsFile(t *testing.T) { pc := NewTestProjectContext(h, safeWriterProject) defer pc.Release() - sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged, gps.DefaultRootPruneOptions()) + sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged, defaultCascadingPruneOptions()) fileroot := pc.CopyFile("fileroot", "txn_writer/badinput_fileroot") err := sw.Write(fileroot, pc.SourceManager, true, discardLogger()) @@ -132,7 +139,7 @@ func TestSafeWriter_Manifest(t *testing.T) { pc.CopyFile(ManifestName, safeWriterGoldenManifest) pc.Load() - sw, _ := NewSafeWriter(pc.Project.Manifest, nil, nil, VendorOnChanged, gps.DefaultRootPruneOptions()) + sw, _ := NewSafeWriter(pc.Project.Manifest, nil, nil, VendorOnChanged, defaultCascadingPruneOptions()) // Verify prepared actions if !sw.HasManifest() { @@ -174,7 +181,7 @@ func TestSafeWriter_ManifestAndUnmodifiedLock(t *testing.T) { pc.CopyFile(LockName, safeWriterGoldenLock) pc.Load() - sw, _ := NewSafeWriter(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorOnChanged, gps.DefaultRootPruneOptions()) + sw, _ := NewSafeWriter(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorOnChanged, defaultCascadingPruneOptions()) // Verify prepared actions if !sw.HasManifest() { @@ -219,7 +226,7 @@ func TestSafeWriter_ManifestAndUnmodifiedLockWithForceVendor(t *testing.T) { pc.CopyFile(LockName, safeWriterGoldenLock) pc.Load() - sw, _ := NewSafeWriter(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorAlways, gps.DefaultRootPruneOptions()) + sw, _ := NewSafeWriter(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorAlways, defaultCascadingPruneOptions()) // Verify prepared actions if !sw.HasManifest() { @@ -269,7 +276,7 @@ func TestSafeWriter_ModifiedLock(t *testing.T) { originalLock := new(Lock) *originalLock = *pc.Project.Lock originalLock.SolveMeta.InputsDigest = []byte{} // zero out the input hash to ensure non-equivalency - sw, _ := NewSafeWriter(nil, originalLock, pc.Project.Lock, VendorOnChanged, gps.DefaultRootPruneOptions()) + sw, _ := NewSafeWriter(nil, originalLock, pc.Project.Lock, VendorOnChanged, defaultCascadingPruneOptions()) // Verify prepared actions if sw.HasManifest() { @@ -319,7 +326,7 @@ func TestSafeWriter_ModifiedLockSkipVendor(t *testing.T) { originalLock := new(Lock) *originalLock = *pc.Project.Lock originalLock.SolveMeta.InputsDigest = []byte{} // zero out the input hash to ensure non-equivalency - sw, _ := NewSafeWriter(nil, originalLock, pc.Project.Lock, VendorNever, gps.DefaultRootPruneOptions()) + sw, _ := NewSafeWriter(nil, originalLock, pc.Project.Lock, VendorNever, defaultCascadingPruneOptions()) // Verify prepared actions if sw.HasManifest() { @@ -363,12 +370,12 @@ func TestSafeWriter_ForceVendorWhenVendorAlreadyExists(t *testing.T) { pc.CopyFile(LockName, safeWriterGoldenLock) pc.Load() - sw, _ := NewSafeWriter(nil, pc.Project.Lock, pc.Project.Lock, VendorAlways, gps.DefaultRootPruneOptions()) + sw, _ := NewSafeWriter(nil, pc.Project.Lock, pc.Project.Lock, VendorAlways, defaultCascadingPruneOptions()) err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, discardLogger()) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify prepared actions - sw, _ = NewSafeWriter(nil, nil, pc.Project.Lock, VendorAlways, gps.DefaultRootPruneOptions()) + sw, _ = NewSafeWriter(nil, nil, pc.Project.Lock, VendorAlways, defaultCascadingPruneOptions()) if sw.HasManifest() { t.Fatal("Did not expect the payload to contain the manifest") } @@ -415,7 +422,7 @@ func TestSafeWriter_NewLock(t *testing.T) { defer lf.Close() newLock, err := readLock(lf) h.Must(err) - sw, _ := NewSafeWriter(nil, nil, newLock, VendorOnChanged, gps.DefaultRootPruneOptions()) + sw, _ := NewSafeWriter(nil, nil, newLock, VendorOnChanged, defaultCascadingPruneOptions()) // Verify prepared actions if sw.HasManifest() { @@ -462,7 +469,7 @@ func TestSafeWriter_NewLockSkipVendor(t *testing.T) { defer lf.Close() newLock, err := readLock(lf) h.Must(err) - sw, _ := NewSafeWriter(nil, nil, newLock, VendorNever, gps.DefaultRootPruneOptions()) + sw, _ := NewSafeWriter(nil, nil, newLock, VendorNever, defaultCascadingPruneOptions()) // Verify prepared actions if sw.HasManifest() { @@ -511,7 +518,7 @@ func TestSafeWriter_DiffLocks(t *testing.T) { updatedLock, err := readLock(ulf) h.Must(err) - sw, _ := NewSafeWriter(nil, pc.Project.Lock, updatedLock, VendorOnChanged, gps.DefaultRootPruneOptions()) + sw, _ := NewSafeWriter(nil, pc.Project.Lock, updatedLock, VendorOnChanged, defaultCascadingPruneOptions()) // Verify lock diff diff := sw.lockDiff @@ -556,7 +563,7 @@ func TestSafeWriter_VendorDotGitPreservedWithForceVendor(t *testing.T) { pc.CopyFile(LockName, safeWriterGoldenLock) pc.Load() - sw, _ := NewSafeWriter(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorAlways, gps.DefaultRootPruneOptions()) + sw, _ := NewSafeWriter(pc.Project.Manifest, pc.Project.Lock, pc.Project.Lock, VendorAlways, defaultCascadingPruneOptions()) // Verify prepared actions if !sw.HasManifest() { diff --git a/website/blog/2018-01-23-announce-v0.4.0.md b/website/blog/2018-01-23-announce-v0.4.0.md index 80008ddb27..bf431031f5 100644 --- a/website/blog/2018-01-23-announce-v0.4.0.md +++ b/website/blog/2018-01-23-announce-v0.4.0.md @@ -4,7 +4,9 @@ author: sam boyer authorURL: http://twitter.com/sdboyer --- -v0.4.0 of dep [has been released](https://github.com/golang/dep/releases/tag/v0.4.0) (with an immediate bugfix follow-up, v0.4.1) - and along with it, this site for documentation and announcements about dep! And, being that it's been nearly six months since [the last dep status update](https://sdboyer.io/dep-status/2017-08-17/) (which are now officially discontinued, in favor of this blog), and the roadmap hasn't been substantially updated in even longer, we'll use this release as an excuse to bring a bunch of things up to speed. +v0.4.0 of dep [has been released](https://github.com/golang/dep/releases/tag/v0.4.0) - and along with it, this site for documentation and announcements about dep! And, being that it's been nearly six months since [the last dep status update](https://sdboyer.io/dep-status/2017-08-17/) (which are now officially discontinued, in favor of this blog), and the roadmap hasn't been substantially updated in even longer, we'll use this release as an excuse to bring a bunch of things up to speed. + +_Note: there was [a significant omission](https://github.com/golang/dep/issues/1561) in v0.4.0's new pruning behavior, so we immediately shipped v0.4.1 with a fix._ ### A new dep release!