github.com/sdboyer/gps@v0.16.3/hash.go (about)

     1  package gps
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha256"
     6  	"io"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/sdboyer/gps/pkgtree"
    12  )
    13  
    14  // string headers used to demarcate sections in hash input creation
    15  const (
    16  	hhConstraints = "-CONSTRAINTS-"
    17  	hhImportsReqs = "-IMPORTS/REQS-"
    18  	hhIgnores     = "-IGNORES-"
    19  	hhOverrides   = "-OVERRIDES-"
    20  	hhAnalyzer    = "-ANALYZER-"
    21  )
    22  
    23  // HashInputs computes a hash digest of all data in SolveParams and the
    24  // RootManifest that act as function inputs to Solve().
    25  //
    26  // The digest returned from this function is the same as the digest that would
    27  // be included with a Solve() Result. As such, it's appropriate for comparison
    28  // against the digest stored in a lock file, generated by a previous Solve(): if
    29  // the digests match, then manifest and lock are in sync, and a Solve() is
    30  // unnecessary.
    31  //
    32  // (Basically, this is for memoization.)
    33  func (s *solver) HashInputs() (digest []byte) {
    34  	h := sha256.New()
    35  	s.writeHashingInputs(h)
    36  
    37  	hd := h.Sum(nil)
    38  	digest = hd[:]
    39  	return
    40  }
    41  
    42  func (s *solver) writeHashingInputs(w io.Writer) {
    43  	writeString := func(s string) {
    44  		// Skip zero-length string writes; it doesn't affect the real hash
    45  		// calculation, and keeps misleading newlines from showing up in the
    46  		// debug output.
    47  		if s != "" {
    48  			// All users of writeHashingInputs cannot error on Write(), so just
    49  			// ignore it
    50  			w.Write([]byte(s))
    51  		}
    52  	}
    53  
    54  	// We write "section headers" into the hash purely to ease scanning when
    55  	// debugging this input-constructing algorithm; as long as the headers are
    56  	// constant, then they're effectively a no-op.
    57  	writeString(hhConstraints)
    58  
    59  	// getApplicableConstraints will apply overrides, incorporate requireds,
    60  	// apply local ignores, drop stdlib imports, and finally trim out
    61  	// ineffectual constraints.
    62  	for _, pd := range s.rd.getApplicableConstraints() {
    63  		writeString(string(pd.Ident.ProjectRoot))
    64  		writeString(pd.Ident.Source)
    65  		writeString(pd.Constraint.typedString())
    66  	}
    67  
    68  	// Write out each discrete import, including those derived from requires.
    69  	writeString(hhImportsReqs)
    70  	imports := s.rd.externalImportList()
    71  	sort.Strings(imports)
    72  	for _, im := range imports {
    73  		writeString(im)
    74  	}
    75  
    76  	// Add ignores, skipping any that point under the current project root;
    77  	// those will have already been implicitly incorporated by the import
    78  	// lister.
    79  	writeString(hhIgnores)
    80  	ig := make([]string, 0, len(s.rd.ig))
    81  	for pkg := range s.rd.ig {
    82  		if !strings.HasPrefix(pkg, s.rd.rpt.ImportRoot) || !isPathPrefixOrEqual(s.rd.rpt.ImportRoot, pkg) {
    83  			ig = append(ig, pkg)
    84  		}
    85  	}
    86  	sort.Strings(ig)
    87  
    88  	for _, igp := range ig {
    89  		writeString(igp)
    90  	}
    91  
    92  	// Overrides *also* need their own special entry distinct from basic
    93  	// constraints, to represent the unique effects they can have on the entire
    94  	// solving process beyond root's immediate scope.
    95  	writeString(hhOverrides)
    96  	for _, pc := range s.rd.ovr.asSortedSlice() {
    97  		writeString(string(pc.Ident.ProjectRoot))
    98  		if pc.Ident.Source != "" {
    99  			writeString(pc.Ident.Source)
   100  		}
   101  		if pc.Constraint != nil {
   102  			writeString(pc.Constraint.typedString())
   103  		}
   104  	}
   105  
   106  	writeString(hhAnalyzer)
   107  	an, av := s.rd.an.Info()
   108  	writeString(an)
   109  	writeString(strconv.Itoa(av))
   110  }
   111  
   112  // bytes.Buffer wrapper that injects newlines after each call to Write().
   113  type nlbuf bytes.Buffer
   114  
   115  func (buf *nlbuf) Write(p []byte) (n int, err error) {
   116  	n, _ = (*bytes.Buffer)(buf).Write(p)
   117  	(*bytes.Buffer)(buf).WriteByte('\n')
   118  	return n + 1, nil
   119  }
   120  
   121  // HashingInputsAsString returns the raw input data used by Solver.HashInputs()
   122  // as a string.
   123  //
   124  // This is primarily intended for debugging purposes.
   125  func HashingInputsAsString(s Solver) string {
   126  	ts := s.(*solver)
   127  	buf := new(nlbuf)
   128  	ts.writeHashingInputs(buf)
   129  
   130  	return (*bytes.Buffer)(buf).String()
   131  }
   132  
   133  type sortPackageOrErr []pkgtree.PackageOrErr
   134  
   135  func (s sortPackageOrErr) Len() int      { return len(s) }
   136  func (s sortPackageOrErr) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   137  
   138  func (s sortPackageOrErr) Less(i, j int) bool {
   139  	a, b := s[i], s[j]
   140  	if a.Err != nil || b.Err != nil {
   141  		// Sort errors last.
   142  		if b.Err == nil {
   143  			return false
   144  		}
   145  		if a.Err == nil {
   146  			return true
   147  		}
   148  		// And then by string.
   149  		return a.Err.Error() < b.Err.Error()
   150  	}
   151  	// And finally, sort by import path.
   152  	return a.P.ImportPath < b.P.ImportPath
   153  }