diff --git a/cmd/dep/ensure.go b/cmd/dep/ensure.go index 63e4868ad7..732fb8c0f2 100644 --- a/cmd/dep/ensure.go +++ b/cmd/dep/ensure.go @@ -270,7 +270,7 @@ func (cmd *ensureCommand) runDefault(ctx *dep.Ctx, args []string, p *dep.Project if !ctx.Verbose { logger = log.New(ioutil.Discard, "", 0) } - return errors.WithMessage(sw.Write(p.AbsRoot, sm, true, logger), "grouped write of manifest, lock and vendor") + return errors.WithMessage(sw.Write(p.AbsRoot, sm, true, false, logger), "grouped write of manifest, lock and vendor") } if cmd.noVendor && cmd.dryRun { @@ -299,7 +299,7 @@ func (cmd *ensureCommand) runDefault(ctx *dep.Ctx, args []string, p *dep.Project if !ctx.Verbose { logger = log.New(ioutil.Discard, "", 0) } - return errors.Wrap(sw.Write(p.AbsRoot, sm, false, logger), "grouped write of manifest, lock and vendor") + return errors.Wrap(sw.Write(p.AbsRoot, sm, false, false, logger), "grouped write of manifest, lock and vendor") } func (cmd *ensureCommand) runVendorOnly(ctx *dep.Ctx, args []string, p *dep.Project, sm gps.SourceManager, params gps.SolveParameters) error { @@ -325,7 +325,7 @@ func (cmd *ensureCommand) runVendorOnly(ctx *dep.Ctx, args []string, p *dep.Proj if !ctx.Verbose { logger = log.New(ioutil.Discard, "", 0) } - return errors.WithMessage(sw.Write(p.AbsRoot, sm, true, logger), "grouped write of manifest, lock and vendor") + return errors.WithMessage(sw.Write(p.AbsRoot, sm, true, false, logger), "grouped write of manifest, lock and vendor") } func (cmd *ensureCommand) runUpdate(ctx *dep.Ctx, args []string, p *dep.Project, sm gps.SourceManager, params gps.SolveParameters) error { @@ -390,7 +390,7 @@ func (cmd *ensureCommand) runUpdate(ctx *dep.Ctx, args []string, p *dep.Project, if !ctx.Verbose { logger = log.New(ioutil.Discard, "", 0) } - return errors.Wrap(sw.Write(p.AbsRoot, sm, false, logger), "grouped write of manifest, lock and vendor") + return errors.Wrap(sw.Write(p.AbsRoot, sm, false, false, logger), "grouped write of manifest, lock and vendor") } func (cmd *ensureCommand) runAdd(ctx *dep.Ctx, args []string, p *dep.Project, sm gps.SourceManager, params gps.SolveParameters) error { @@ -691,7 +691,7 @@ func (cmd *ensureCommand) runAdd(ctx *dep.Ctx, args []string, p *dep.Project, sm if !ctx.Verbose { logger = log.New(ioutil.Discard, "", 0) } - if err := errors.Wrap(sw.Write(p.AbsRoot, sm, true, logger), "grouped write of manifest, lock and vendor"); err != nil { + if err := errors.Wrap(sw.Write(p.AbsRoot, sm, true, false, logger), "grouped write of manifest, lock and vendor"); err != nil { return err } diff --git a/cmd/dep/init.go b/cmd/dep/init.go index a4f6637d55..49e1a8c337 100644 --- a/cmd/dep/init.go +++ b/cmd/dep/init.go @@ -22,6 +22,8 @@ import ( const initShortHelp = `Initialize a new project with manifest and lock files` const initLongHelp = ` +Wizard + Initialize the project at filepath root by parsing its dependencies, writing manifest and lock files, and vendoring the dependencies. If root isn't specified, use the current directory. @@ -183,10 +185,13 @@ func (cmd *initCommand) Run(ctx *dep.Ctx, args []string) error { return errors.Wrap(err, "prepare solver") } + partial := true + vendorBehavior := dep.VendorAlways soln, err := s.Solve() if err != nil { handleAllTheFailuresOfTheWorld(err) - return err + partial = true + vendorBehavior = dep.VendorNever } p.Lock = dep.LockFromSolution(soln) @@ -201,16 +206,18 @@ func (cmd *initCommand) Run(ctx *dep.Ctx, args []string) error { p.Lock.SolveMeta.InputsDigest = s.HashInputs() - // Pass timestamp (yyyyMMddHHmmss format) as suffix to backup name. - vendorbak, err := dep.BackupVendor(vpath, time.Now().Format("20060102150405")) - if err != nil { - return err - } - if vendorbak != "" { - ctx.Err.Printf("Old vendor backed up to %v", vendorbak) + if vendorBehavior == dep.VendorAlways { + // Pass timestamp (yyyyMMddHHmmss format) as suffix to backup name. + vendorbak, err := dep.BackupVendor(vpath, time.Now().Format("20060102150405")) + if err != nil { + return err + } + if vendorbak != "" { + ctx.Err.Printf("Old vendor backed up to %v", vendorbak) + } } - sw, err := dep.NewSafeWriter(p.Manifest, nil, p.Lock, dep.VendorAlways) + sw, err := dep.NewSafeWriter(p.Manifest, nil, p.Lock, vendorBehavior) if err != nil { return err } @@ -219,7 +226,7 @@ func (cmd *initCommand) Run(ctx *dep.Ctx, args []string) error { if !ctx.Verbose { logger = log.New(ioutil.Discard, "", 0) } - if err := sw.Write(root, sm, !cmd.noExamples, logger); err != nil { + if err := sw.Write(root, sm, !cmd.noExamples, partial, logger); err != nil { return errors.Wrap(err, "safe write of manifest and lock") } diff --git a/internal/gps/solver.go b/internal/gps/solver.go index a325101a9d..4fdd4d55b3 100644 --- a/internal/gps/solver.go +++ b/internal/gps/solver.go @@ -440,22 +440,20 @@ func (s *solver) Solve() (Solution, error) { all, err := s.solve() s.mtr.pop() - var soln solution - if err == nil { - soln = solution{ - att: s.attempts, - solv: s, - } - soln.analyzerInfo = s.rd.an.Info() - soln.hd = s.HashInputs() - - // Convert ProjectAtoms into LockedProjects - soln.p = make([]LockedProject, len(all)) - k := 0 - for pa, pl := range all { - soln.p[k] = pa2lp(pa, pl) - k++ - } + + soln := solution{ + att: s.attempts, + solv: s, + } + soln.analyzerInfo = s.rd.an.Info() + soln.hd = s.HashInputs() + + // Convert ProjectAtoms into LockedProjects + soln.p = make([]LockedProject, len(all)) + k := 0 + for pa, pl := range all { + soln.p[k] = pa2lp(pa, pl) + k++ } s.traceFinish(soln, err) @@ -465,6 +463,26 @@ func (s *solver) Solve() (Solution, error) { return soln, err } +func (s *solver) combine() map[atom]map[string]struct{} { + projs := make(map[atom]map[string]struct{}) + + // Skip the first project. It's always the root, and that shouldn't be + // included in results. + for _, sel := range s.sel.projects[1:] { + pm, exists := projs[sel.a.a] + if !exists { + pm = make(map[string]struct{}) + projs[sel.a.a] = pm + } + + for _, path := range sel.a.pl { + pm[path] = struct{}{} + } + } + + return projs +} + // solve is the top-level loop for the solving process. func (s *solver) solve() (map[atom]map[string]struct{}, error) { // Main solving loop @@ -496,7 +514,7 @@ func (s *solver) solve() (map[atom]map[string]struct{}, error) { // backtracking succeeded, move to the next unselected id continue } - return nil, err + return s.combine(), err } if queue.current() == nil { @@ -544,7 +562,7 @@ func (s *solver) solve() (map[atom]map[string]struct{}, error) { continue } s.mtr.pop() - return nil, err + return s.combine(), err } s.selectAtom(nawp, true) // We don't add anything to the stack of version queues because the @@ -556,22 +574,8 @@ func (s *solver) solve() (map[atom]map[string]struct{}, error) { // Getting this far means we successfully found a solution. Combine the // selected projects and packages. - projs := make(map[atom]map[string]struct{}) - // Skip the first project. It's always the root, and that shouldn't be - // included in results. - for _, sel := range s.sel.projects[1:] { - pm, exists := projs[sel.a.a] - if !exists { - pm = make(map[string]struct{}) - projs[sel.a.a] = pm - } - - for _, path := range sel.a.pl { - pm[path] = struct{}{} - } - } - return projs, nil + return s.combine(), nil } // selectRoot is a specialized selectAtom, used solely to initially diff --git a/txn_writer.go b/txn_writer.go index adb3c473dd..877bd92330 100644 --- a/txn_writer.go +++ b/txn_writer.go @@ -45,6 +45,13 @@ var exampleTOML = []byte(` `) +var partialTOML = []byte(` +# The solver has failed (probably due to conflicting constraints). +# The output below is a partial TOML file which represents the closest +# solution that could be found. + +`) + // String added on top of lock file var lockFileComment = []byte(`# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. @@ -261,7 +268,7 @@ func (sw SafeWriter) validate(root string, sm gps.SourceManager) error { // operations succeeded. It also does its best to roll back if any moves fail. // This mostly guarantees that dep cannot exit with a partial write that would // leave an undefined state on disk. -func (sw *SafeWriter) Write(root string, sm gps.SourceManager, examples bool, logger *log.Logger) error { +func (sw *SafeWriter) Write(root string, sm gps.SourceManager, examples bool, partial bool, logger *log.Logger) error { err := sw.validate(root, sm) if err != nil { return err @@ -295,6 +302,9 @@ func (sw *SafeWriter) Write(root string, sm gps.SourceManager, examples bool, lo if examples { initOutput = exampleTOML } + if partial { + initOutput = append(partialTOML, initOutput...) + } if err = ioutil.WriteFile(filepath.Join(td, ManifestName), append(initOutput, tb...), 0666); err != nil { return errors.Wrap(err, "failed to write manifest file to temp dir") diff --git a/txn_writer_test.go b/txn_writer_test.go index 141e1f8cc1..5708c0a404 100644 --- a/txn_writer_test.go +++ b/txn_writer_test.go @@ -26,7 +26,7 @@ func TestSafeWriter_BadInput_MissingRoot(t *testing.T) { defer pc.Release() sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged) - err := sw.Write("", pc.SourceManager, true, discardLogger()) + err := sw.Write("", pc.SourceManager, true, false, discardLogger()) if err == nil { t.Fatal("should have errored without a root path, but did not") @@ -44,7 +44,7 @@ func TestSafeWriter_BadInput_MissingSourceManager(t *testing.T) { pc.Load() sw, _ := NewSafeWriter(nil, nil, pc.Project.Lock, VendorAlways) - err := sw.Write(pc.Project.AbsRoot, nil, true, discardLogger()) + err := sw.Write(pc.Project.AbsRoot, nil, true, false, discardLogger()) if err == nil { t.Fatal("should have errored without a source manager when forceVendor is true, but did not") @@ -92,7 +92,7 @@ func TestSafeWriter_BadInput_NonexistentRoot(t *testing.T) { sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged) missingroot := filepath.Join(pc.Project.AbsRoot, "nonexistent") - err := sw.Write(missingroot, pc.SourceManager, true, discardLogger()) + err := sw.Write(missingroot, pc.SourceManager, true, false, discardLogger()) if err == nil { t.Fatal("should have errored with nonexistent dir for root path, but did not") @@ -110,7 +110,7 @@ func TestSafeWriter_BadInput_RootIsFile(t *testing.T) { sw, _ := NewSafeWriter(nil, nil, nil, VendorOnChanged) fileroot := pc.CopyFile("fileroot", "txn_writer/badinput_fileroot") - err := sw.Write(fileroot, pc.SourceManager, true, discardLogger()) + err := sw.Write(fileroot, pc.SourceManager, true, false, discardLogger()) if err == nil { t.Fatal("should have errored when root path is a file, but did not") @@ -145,7 +145,7 @@ func TestSafeWriter_Manifest(t *testing.T) { } // Write changes - err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, discardLogger()) + err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, false, discardLogger()) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -190,7 +190,7 @@ func TestSafeWriter_ManifestAndUnmodifiedLock(t *testing.T) { } // Write changes - err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, discardLogger()) + err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, false, discardLogger()) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -235,7 +235,7 @@ func TestSafeWriter_ManifestAndUnmodifiedLockWithForceVendor(t *testing.T) { } // Write changes - err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, discardLogger()) + err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, false, discardLogger()) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -285,7 +285,7 @@ func TestSafeWriter_ModifiedLock(t *testing.T) { } // Write changes - err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, discardLogger()) + err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, false, discardLogger()) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -335,7 +335,7 @@ func TestSafeWriter_ModifiedLockSkipVendor(t *testing.T) { } // Write changes - err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, discardLogger()) + err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, false, discardLogger()) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -363,7 +363,7 @@ func TestSafeWriter_ForceVendorWhenVendorAlreadyExists(t *testing.T) { pc.Load() sw, _ := NewSafeWriter(nil, pc.Project.Lock, pc.Project.Lock, VendorAlways) - err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, discardLogger()) + err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, false, discardLogger()) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify prepared actions @@ -381,7 +381,7 @@ func TestSafeWriter_ForceVendorWhenVendorAlreadyExists(t *testing.T) { t.Fatal("Expected the payload to contain the vendor directory ") } - err = sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, discardLogger()) + err = sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, false, discardLogger()) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -431,7 +431,7 @@ func TestSafeWriter_NewLock(t *testing.T) { } // Write changes - err = sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, discardLogger()) + err = sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, false, discardLogger()) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -478,7 +478,7 @@ func TestSafeWriter_NewLockSkipVendor(t *testing.T) { } // Write changes - err = sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, discardLogger()) + err = sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, false, discardLogger()) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes @@ -571,7 +571,7 @@ func TestSafeWriter_VendorDotGitPreservedWithForceVendor(t *testing.T) { t.Fatal("Expected the payload to contain the vendor directory") } - err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, discardLogger()) + err := sw.Write(pc.Project.AbsRoot, pc.SourceManager, true, false, discardLogger()) h.Must(errors.Wrap(err, "SafeWriter.Write failed")) // Verify file system changes