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