dep/internal/gps/hash.go

136 строки
3.9 KiB
Go

// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gps
import (
"bytes"
"crypto/sha256"
"io"
"sort"
"strconv"
"strings"
)
const wcIgnoreSuffix = "*"
// 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(s.stdLibFn) {
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(s.stdLibFn)
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 := s.rd.ir.ToSlice()
sort.Strings(ig)
for _, igp := range ig {
// Typical prefix comparison checks will erroneously fail if the wildcard
// is present. Trim it off, if present.
tigp := strings.TrimSuffix(igp, "*")
if !strings.HasPrefix(tigp, s.rd.rpt.ImportRoot) || !isPathPrefixOrEqual(s.rd.rpt.ImportRoot, tigp) {
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)
ai := s.rd.an.Info()
writeString(ai.Name)
writeString(strconv.Itoa(ai.Version))
}
// 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()
}