github.com/golang/dep@v0.5.4/cmd/dep/check.go (about)

     1  // Copyright 2018 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 main
     6  
     7  import (
     8  	"bytes"
     9  	"flag"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"log"
    13  	"os"
    14  	"path/filepath"
    15  	"sort"
    16  	"strings"
    17  
    18  	"github.com/golang/dep"
    19  	"github.com/golang/dep/gps"
    20  	"github.com/golang/dep/gps/verify"
    21  	"github.com/pkg/errors"
    22  )
    23  
    24  const checkShortHelp = `Check if imports, Gopkg.toml, and Gopkg.lock are in sync`
    25  const checkLongHelp = `
    26  Check determines if your project is in a good state. If problems are found, it
    27  prints a description of each issue, then exits 1. Passing -q suppresses output.
    28  
    29  Flags control which specific checks will be run. By default, dep check verifies
    30  that Gopkg.lock is in sync with Gopkg.toml and the imports in your project's .go
    31  files, and that the vendor directory is in sync with Gopkg.lock. These checks
    32  can be disabled with -skip-lock and -skip-vendor, respectively.
    33  
    34  (See https://golang.github.io/dep/docs/ensure-mechanics.html#staying-in-sync for
    35  more information on what it means to be "in sync.")
    36  
    37  If your workflow necessitates that you modify the contents of vendor, you can
    38  force check to ignore hash mismatches on a per-project basis by naming
    39  project roots in Gopkg.toml's "noverify" list.
    40  `
    41  
    42  type checkCommand struct {
    43  	quiet                bool
    44  	skiplock, skipvendor bool
    45  }
    46  
    47  func (cmd *checkCommand) Name() string { return "check" }
    48  func (cmd *checkCommand) Args() string {
    49  	return "[-q] [-skip-lock] [-skip-vendor]"
    50  }
    51  func (cmd *checkCommand) ShortHelp() string { return checkShortHelp }
    52  func (cmd *checkCommand) LongHelp() string  { return checkLongHelp }
    53  func (cmd *checkCommand) Hidden() bool      { return false }
    54  
    55  func (cmd *checkCommand) Register(fs *flag.FlagSet) {
    56  	fs.BoolVar(&cmd.skiplock, "skip-lock", false, "Skip checking that imports and Gopkg.toml are in sync with Gopkg.lock")
    57  	fs.BoolVar(&cmd.skipvendor, "skip-vendor", false, "Skip checking that vendor is in sync with Gopkg.lock")
    58  	fs.BoolVar(&cmd.quiet, "q", false, "Suppress non-error output")
    59  }
    60  
    61  func (cmd *checkCommand) Run(ctx *dep.Ctx, args []string) error {
    62  	logger := ctx.Out
    63  	if cmd.quiet {
    64  		logger = log.New(ioutil.Discard, "", 0)
    65  	}
    66  
    67  	p, err := ctx.LoadProject()
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	sm, err := ctx.SourceManager()
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	sm.UseDefaultSignalHandling()
    78  	defer sm.Release()
    79  
    80  	var fail bool
    81  	if !cmd.skiplock {
    82  		if p.Lock == nil {
    83  			return errors.New("Gopkg.lock does not exist, cannot check it against imports and Gopkg.toml")
    84  		}
    85  
    86  		lsat := verify.LockSatisfiesInputs(p.Lock, p.Manifest, p.RootPackageTree)
    87  		delta := verify.DiffLocks(p.Lock, p.ChangedLock)
    88  		sat, changed := lsat.Satisfied(), delta.Changed(verify.PruneOptsChanged|verify.HashVersionChanged)
    89  
    90  		if changed || !sat {
    91  			fail = true
    92  			logger.Println("# Gopkg.lock is out of sync:")
    93  			if !sat {
    94  				logger.Printf("%s\n", sprintLockUnsat(lsat))
    95  			}
    96  			if changed {
    97  				// Sort, for deterministic output.
    98  				var ordered []string
    99  				for pr := range delta.ProjectDeltas {
   100  					ordered = append(ordered, string(pr))
   101  				}
   102  				sort.Strings(ordered)
   103  
   104  				for _, pr := range ordered {
   105  					lpd := delta.ProjectDeltas[gps.ProjectRoot(pr)]
   106  					// Only two possible changes right now are prune opts
   107  					// changing or a missing hash digest (for old Gopkg.lock
   108  					// files)
   109  					if lpd.PruneOptsChanged() {
   110  						// Override what's on the lockdiff with the extra info we have;
   111  						// this lets us excise PruneNestedVendorDirs and get the real
   112  						// value from the input param in place.
   113  						old := lpd.PruneOptsBefore & ^gps.PruneNestedVendorDirs
   114  						new := lpd.PruneOptsAfter & ^gps.PruneNestedVendorDirs
   115  						logger.Printf("%s: prune options changed (%s -> %s)\n", pr, old, new)
   116  					}
   117  					if lpd.HashVersionWasZero() {
   118  						logger.Printf("%s: no hash digest in lock\n", pr)
   119  					}
   120  				}
   121  			}
   122  		}
   123  	}
   124  
   125  	if !cmd.skipvendor {
   126  		if p.Lock == nil {
   127  			return errors.New("Gopkg.lock does not exist, cannot check vendor against it")
   128  		}
   129  
   130  		statuses, err := p.VerifyVendor()
   131  		if err != nil {
   132  			return errors.Wrap(err, "error while verifying vendor")
   133  		}
   134  
   135  		if fail {
   136  			logger.Println()
   137  		}
   138  
   139  		noverify := make(map[string]bool)
   140  		for _, skip := range p.Manifest.NoVerify {
   141  			noverify[skip] = true
   142  		}
   143  
   144  		var vendorfail, hasnoverify bool
   145  		// One full pass through, to see if we need to print the header, and to
   146  		// create an array of names to sort for deterministic output.
   147  		var ordered []string
   148  		for path, status := range statuses {
   149  			ordered = append(ordered, path)
   150  
   151  			switch status {
   152  			case verify.DigestMismatchInLock, verify.HashVersionMismatch, verify.EmptyDigestInLock, verify.NotInLock:
   153  				if noverify[path] {
   154  					hasnoverify = true
   155  					continue
   156  				}
   157  				fallthrough
   158  			case verify.NotInTree:
   159  				// NoVerify cannot be used to make dep check ignore the absence
   160  				// of a project entirely.
   161  				if noverify[path] {
   162  					delete(noverify, path)
   163  				}
   164  
   165  				fail = true
   166  				if !vendorfail {
   167  					vendorfail = true
   168  				}
   169  			}
   170  		}
   171  		sort.Strings(ordered)
   172  
   173  		var vfbuf, novbuf bytes.Buffer
   174  		var bufptr *bytes.Buffer
   175  
   176  		fmt.Fprintf(&vfbuf, "# vendor is out of sync:\n")
   177  		fmt.Fprintf(&novbuf, "# out of sync, but ignored, due to noverify in Gopkg.toml:\n")
   178  
   179  		for _, pr := range ordered {
   180  			if noverify[pr] {
   181  				bufptr = &novbuf
   182  			} else {
   183  				bufptr = &vfbuf
   184  			}
   185  
   186  			status := statuses[pr]
   187  			switch status {
   188  			case verify.NotInTree:
   189  				fmt.Fprintf(bufptr, "%s: missing from vendor\n", pr)
   190  			case verify.NotInLock:
   191  				fi, err := os.Stat(filepath.Join(p.AbsRoot, "vendor", pr))
   192  				if err != nil {
   193  					return errors.Wrap(err, "could not stat file that VerifyVendor claimed existed")
   194  				}
   195  				if fi.IsDir() {
   196  					fmt.Fprintf(bufptr, "%s: unused project\n", pr)
   197  				} else {
   198  					fmt.Fprintf(bufptr, "%s: orphaned file\n", pr)
   199  				}
   200  			case verify.DigestMismatchInLock:
   201  				fmt.Fprintf(bufptr, "%s: hash of vendored tree not equal to digest in Gopkg.lock\n", pr)
   202  			case verify.EmptyDigestInLock:
   203  				fmt.Fprintf(bufptr, "%s: no digest in Gopkg.lock to compare against hash of vendored tree\n", pr)
   204  			case verify.HashVersionMismatch:
   205  				// This will double-print if the hash version is zero, but
   206  				// that's a rare case that really only occurs before the first
   207  				// run with a version of dep >=0.5.0, so it's fine.
   208  				fmt.Fprintf(bufptr, "%s: hash algorithm mismatch, want version %v\n", pr, verify.HashVersion)
   209  			}
   210  		}
   211  
   212  		if vendorfail {
   213  			logger.Print(vfbuf.String())
   214  			if hasnoverify {
   215  				logger.Println()
   216  			}
   217  		}
   218  		if hasnoverify {
   219  			logger.Print(novbuf.String())
   220  		}
   221  	}
   222  
   223  	if fail {
   224  		return silentfail{}
   225  	}
   226  	return nil
   227  }
   228  
   229  func sprintLockUnsat(lsat verify.LockSatisfaction) string {
   230  	var buf bytes.Buffer
   231  	sort.Strings(lsat.MissingImports)
   232  	for _, missing := range lsat.MissingImports {
   233  		fmt.Fprintf(&buf, "%s: imported or required, but missing from Gopkg.lock's input-imports\n", missing)
   234  	}
   235  
   236  	sort.Strings(lsat.ExcessImports)
   237  	for _, excess := range lsat.ExcessImports {
   238  		fmt.Fprintf(&buf, "%s: in Gopkg.lock's input-imports, but neither imported nor required\n", excess)
   239  	}
   240  
   241  	var ordered []string
   242  	for pr := range lsat.UnmetOverrides {
   243  		ordered = append(ordered, string(pr))
   244  	}
   245  	sort.Strings(ordered)
   246  	for _, pr := range ordered {
   247  		unmatched := lsat.UnmetOverrides[gps.ProjectRoot(pr)]
   248  		fmt.Fprintf(&buf, "%s@%s: not allowed by override %s\n", pr, unmatched.V, unmatched.C)
   249  	}
   250  
   251  	ordered = ordered[:0]
   252  	for pr := range lsat.UnmetConstraints {
   253  		ordered = append(ordered, string(pr))
   254  	}
   255  	sort.Strings(ordered)
   256  	for _, pr := range ordered {
   257  		unmatched := lsat.UnmetConstraints[gps.ProjectRoot(pr)]
   258  		fmt.Fprintf(&buf, "%s@%s: not allowed by constraint %s\n", pr, unmatched.V, unmatched.C)
   259  	}
   260  	return strings.TrimSpace(buf.String())
   261  }