| // Copyright 2017 The LUCI Authors. All rights reserved. |
| // Use of this source code is governed under the Apache License, Version 2.0 |
| // that can be found in the LICENSE file. |
| |
| package spec |
| |
| import ( |
| "crypto/sha256" |
| "encoding/hex" |
| "fmt" |
| "sort" |
| |
| "github.com/luci/luci-go/vpython/api/vpython" |
| |
| "github.com/luci/luci-go/common/data/sortby" |
| "github.com/luci/luci-go/common/errors" |
| |
| "github.com/golang/protobuf/proto" |
| ) |
| |
| // Render creates a human-readable string from spec. |
| func Render(spec *vpython.Spec) string { return proto.MarshalTextString(spec) } |
| |
| // NormalizeEnvironment normalizes the supplied Environment such that two |
| // messages with identical meaning will have identical representation. |
| func NormalizeEnvironment(env *vpython.Environment) error { |
| if env.Spec == nil { |
| env.Spec = &vpython.Spec{} |
| } |
| if err := NormalizeSpec(env.Spec); err != nil { |
| return err |
| } |
| |
| if env.Runtime == nil { |
| env.Runtime = &vpython.Runtime{} |
| } |
| |
| sort.Sort(pep425TagSlice(env.Pep425Tag)) |
| return nil |
| } |
| |
| // NormalizeSpec normalizes the specification Message such that two messages |
| // with identical meaning will have identical representation. |
| func NormalizeSpec(spec *vpython.Spec) error { |
| sort.Sort(specPackageSlice(spec.Wheel)) |
| |
| // No duplicate packages. Since we're sorted, we can just check for no |
| // immediate repetitions. |
| for i, pkg := range spec.Wheel { |
| if i > 0 && pkg.Name == spec.Wheel[i-1].Name { |
| return errors.Reason("duplicate spec entries for package %(path)q"). |
| D("name", pkg.Name). |
| Err() |
| } |
| } |
| |
| return nil |
| } |
| |
| // Hash hashes the contents of the supplied "spec" and "rt" and returns the |
| // result as a hex-encoded string. |
| // |
| // If not empty, the contents of extra are prefixed to hash string. This can |
| // be used to factor additional influences into the spec hash. |
| func Hash(spec *vpython.Spec, rt *vpython.Runtime, extra string) string { |
| mustMarshal := func(msg proto.Message) []byte { |
| data, err := proto.Marshal(msg) |
| if err != nil { |
| panic(fmt.Errorf("failed to marshal proto: %v", err)) |
| } |
| return data |
| } |
| specData := mustMarshal(spec) |
| rtData := mustMarshal(rt) |
| |
| mustWrite := func(v int, err error) { |
| if err != nil { |
| panic(fmt.Errorf("impossible: %s", err)) |
| } |
| } |
| |
| hash := sha256.New() |
| if extra != "" { |
| mustWrite(fmt.Fprintf(hash, "%s:", extra)) |
| } |
| mustWrite(fmt.Fprintf(hash, "%s:", vpython.Version)) |
| mustWrite(hash.Write(specData)) |
| mustWrite(hash.Write([]byte(":"))) |
| mustWrite(hash.Write(rtData)) |
| return hex.EncodeToString(hash.Sum(nil)) |
| } |
| |
| type specPackageSlice []*vpython.Spec_Package |
| |
| func (s specPackageSlice) Len() int { return len(s) } |
| func (s specPackageSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
| |
| func (s specPackageSlice) Less(i, j int) bool { |
| return sortby.Chain{ |
| func(i, j int) bool { return s[i].Name < s[j].Name }, |
| func(i, j int) bool { return s[i].Version < s[j].Version }, |
| }.Use(i, j) |
| } |
| |
| type pep425TagSlice []*vpython.Pep425Tag |
| |
| func (s pep425TagSlice) Len() int { return len(s) } |
| func (s pep425TagSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
| |
| func (s pep425TagSlice) Less(i, j int) bool { |
| return sortby.Chain{ |
| func(i, j int) bool { return s[i].Version < s[j].Version }, |
| func(i, j int) bool { return s[i].Abi < s[j].Abi }, |
| func(i, j int) bool { return s[i].Arch < s[j].Arch }, |
| }.Use(i, j) |
| } |