Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
101 lines
3.0 KiB
Go
101 lines
3.0 KiB
Go
package cache
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"hash"
|
|
)
|
|
|
|
// Digest identifies a blob by its SHA-256 hash and size, mirroring the
|
|
// Digest message from Bazel's remote execution API. The size is stored
|
|
// alongside the hash so that entries can be validated without reading blob
|
|
// contents; a negative size means the size is unknown, as with a checksum
|
|
// declared in a configuration file.
|
|
type Digest struct {
|
|
// Hash is the lowercase hex-encoded SHA-256 hash of the blob.
|
|
Hash string `json:"hash"`
|
|
// SizeBytes is the length of the blob in bytes, or negative if unknown.
|
|
SizeBytes int64 `json:"size_bytes"`
|
|
}
|
|
|
|
func (d Digest) String() string {
|
|
return fmt.Sprintf("sha256:%s/%d", d.Hash, d.SizeBytes)
|
|
}
|
|
|
|
func (d Digest) valid() bool {
|
|
if len(d.Hash) != 64 {
|
|
return false
|
|
}
|
|
_, err := hex.DecodeString(d.Hash)
|
|
return err == nil
|
|
}
|
|
|
|
// DigestOf returns the Digest of a blob.
|
|
func DigestOf(data []byte) Digest {
|
|
sum := sha256.Sum256(data)
|
|
return Digest{
|
|
Hash: hex.EncodeToString(sum[:]),
|
|
SizeBytes: int64(len(data)),
|
|
}
|
|
}
|
|
|
|
// SHA256Digest returns a Digest referencing a blob by a declared SHA-256
|
|
// checksum whose size is not known, suitable for looking up remotely fetched
|
|
// content in the CAS.
|
|
func SHA256Digest(hexhash string) Digest {
|
|
return Digest{
|
|
Hash: hexhash,
|
|
SizeBytes: -1,
|
|
}
|
|
}
|
|
|
|
// An Action describes a unit of cacheable work, playing the role of Bazel's
|
|
// Action message: a mnemonic naming the kind of work plus the complete set of
|
|
// inputs that determine its outputs. Two actions with the same digest are
|
|
// assumed to produce the same outputs.
|
|
//
|
|
// Inputs are hashed incrementally with length-prefixed framing so that the
|
|
// boundary between inputs is unambiguous ("ab"+"c" hashes differently from
|
|
// "a"+"bc").
|
|
type Action struct {
|
|
hasher hash.Hash
|
|
}
|
|
|
|
// NewAction starts building an action key for the given mnemonic, e.g.
|
|
// "QueryAnalysis". The sha256 of the sqlc binary itself is always the first
|
|
// input: the tool that executes an action determines its outputs just as
|
|
// much as the declared inputs do, so a rebuilt sqlc never reuses stale
|
|
// entries. The binary's digest is memoized in the cache — see toolDigest —
|
|
// which is why actions are created through a Cache.
|
|
func (c *Cache) NewAction(mnemonic string) *Action {
|
|
a := &Action{hasher: sha256.New()}
|
|
a.write([]byte(mnemonic))
|
|
a.AddInput("tool", c.toolDigest())
|
|
return a
|
|
}
|
|
|
|
// AddInput mixes a named input into the action key. Order matters: callers
|
|
// must add inputs in a deterministic order.
|
|
func (a *Action) AddInput(name string, data []byte) *Action {
|
|
a.write([]byte(name))
|
|
a.write(data)
|
|
return a
|
|
}
|
|
|
|
// Digest returns the action's digest, used as the action cache key.
|
|
func (a *Action) Digest() Digest {
|
|
return Digest{
|
|
Hash: hex.EncodeToString(a.hasher.Sum(nil)),
|
|
SizeBytes: 0,
|
|
}
|
|
}
|
|
|
|
func (a *Action) write(data []byte) {
|
|
var frame [8]byte
|
|
binary.LittleEndian.PutUint64(frame[:], uint64(len(data)))
|
|
a.hasher.Write(frame[:])
|
|
a.hasher.Write(data)
|
|
}
|