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 }