| // 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 ( |
| "encoding/binary" |
| "strings" |
| "time" |
| |
| "github.com/boltdb/bolt" |
| "github.com/golang/dep/gps/internal/pb" |
| "github.com/golang/dep/gps/pkgtree" |
| "github.com/golang/protobuf/proto" |
| "github.com/jmank88/nuts" |
| "github.com/pkg/errors" |
| ) |
| |
| var ( |
| cacheKeyComment = []byte("c") |
| cacheKeyConstraint = cacheKeyComment |
| cacheKeyError = []byte("e") |
| cacheKeyInputImports = []byte("m") |
| cacheKeyIgnored = []byte("i") |
| cacheKeyImport = cacheKeyIgnored |
| cacheKeyLock = []byte("l") |
| cacheKeyName = []byte("n") |
| cacheKeyOverride = []byte("o") |
| cacheKeyPTree = []byte("p") |
| cacheKeyRequired = []byte("r") |
| cacheKeyRevision = cacheKeyRequired |
| cacheKeyTestImport = []byte("t") |
| |
| cacheRevision = byte('r') |
| cacheVersion = byte('v') |
| ) |
| |
| // propertiesFromCache returns a new ProjectRoot and ProjectProperties with the fields from m. |
| func propertiesFromCache(m *pb.ProjectProperties) (ProjectRoot, ProjectProperties, error) { |
| ip := ProjectRoot(m.Root) |
| var pp ProjectProperties |
| pp.Source = m.Source |
| |
| if m.Constraint == nil { |
| pp.Constraint = Any() |
| } else { |
| c, err := constraintFromCache(m.Constraint) |
| if err != nil { |
| return "", ProjectProperties{}, err |
| } |
| pp.Constraint = c |
| } |
| |
| return ip, pp, nil |
| } |
| |
| // projectPropertiesMsgs is a convenience tuple. |
| type projectPropertiesMsgs struct { |
| pp pb.ProjectProperties |
| c pb.Constraint |
| } |
| |
| // copyFrom sets the ProjectPropertiesMsg fields from ip and pp. |
| func (ms *projectPropertiesMsgs) copyFrom(ip ProjectRoot, pp ProjectProperties) { |
| ms.pp.Root = string(ip) |
| ms.pp.Source = pp.Source |
| |
| if pp.Constraint != nil && !IsAny(pp.Constraint) { |
| pp.Constraint.copyTo(&ms.c) |
| ms.pp.Constraint = &ms.c |
| } else { |
| ms.pp.Constraint = nil |
| } |
| } |
| |
| // cachePutManifest stores a Manifest in the bolt.Bucket. |
| func cachePutManifest(b *bolt.Bucket, m Manifest) error { |
| var ppMsg projectPropertiesMsgs |
| |
| constraints := m.DependencyConstraints() |
| if len(constraints) > 0 { |
| cs, err := b.CreateBucket(cacheKeyConstraint) |
| if err != nil { |
| return err |
| } |
| key := make(nuts.Key, nuts.KeyLen(uint64(len(constraints)-1))) |
| var i uint64 |
| for ip, pp := range constraints { |
| ppMsg.copyFrom(ip, pp) |
| v, err := proto.Marshal(&ppMsg.pp) |
| if err != nil { |
| return err |
| } |
| key.Put(i) |
| i++ |
| if err := cs.Put(key, v); err != nil { |
| return err |
| } |
| } |
| } |
| |
| rm, ok := m.(RootManifest) |
| if !ok { |
| return nil |
| } |
| |
| ignored := rm.IgnoredPackages().ToSlice() |
| if len(ignored) > 0 { |
| ig, err := b.CreateBucket(cacheKeyIgnored) |
| if err != nil { |
| return err |
| } |
| key := make(nuts.Key, nuts.KeyLen(uint64(len(ignored)-1))) |
| var i uint64 |
| for _, ip := range ignored { |
| key.Put(i) |
| i++ |
| if err := ig.Put(key, []byte(ip)); err != nil { |
| return err |
| } |
| } |
| } |
| |
| overrides := rm.Overrides() |
| if len(overrides) > 0 { |
| ovr, err := b.CreateBucket(cacheKeyOverride) |
| if err != nil { |
| return err |
| } |
| key := make(nuts.Key, nuts.KeyLen(uint64(len(overrides)-1))) |
| var i uint64 |
| for ip, pp := range overrides { |
| ppMsg.copyFrom(ip, pp) |
| v, err := proto.Marshal(&ppMsg.pp) |
| if err != nil { |
| return err |
| } |
| key.Put(i) |
| i++ |
| if err := ovr.Put(key, v); err != nil { |
| return err |
| } |
| } |
| } |
| |
| required := rm.RequiredPackages() |
| if len(required) > 0 { |
| req, err := b.CreateBucket(cacheKeyRequired) |
| if err != nil { |
| return err |
| } |
| key := make(nuts.Key, nuts.KeyLen(uint64(len(required)-1))) |
| var i uint64 |
| for ip, ok := range required { |
| if ok { |
| key.Put(i) |
| i++ |
| if err := req.Put(key, []byte(ip)); err != nil { |
| return err |
| } |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| // cacheGetManifest returns a new RootManifest with the data retrieved from the bolt.Bucket. |
| func cacheGetManifest(b *bolt.Bucket) (RootManifest, error) { |
| //TODO consider storing slice/map lens to enable calling make() with capacity |
| m := &simpleRootManifest{ |
| c: make(ProjectConstraints), |
| ovr: make(ProjectConstraints), |
| req: make(map[string]bool), |
| } |
| |
| // Constraints |
| if cs := b.Bucket(cacheKeyConstraint); cs != nil { |
| var msg pb.ProjectProperties |
| err := cs.ForEach(func(_, v []byte) error { |
| if err := proto.Unmarshal(v, &msg); err != nil { |
| return err |
| } |
| ip, pp, err := propertiesFromCache(&msg) |
| if err != nil { |
| return err |
| } |
| m.c[ip] = pp |
| return nil |
| }) |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to get constraints") |
| } |
| } |
| |
| // Ignored |
| if ig := b.Bucket(cacheKeyIgnored); ig != nil { |
| var igslice []string |
| err := ig.ForEach(func(_, v []byte) error { |
| igslice = append(igslice, string(v)) |
| return nil |
| }) |
| m.ig = pkgtree.NewIgnoredRuleset(igslice) |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to get ignored") |
| } |
| } |
| |
| // Overrides |
| if os := b.Bucket(cacheKeyOverride); os != nil { |
| var msg pb.ProjectProperties |
| err := os.ForEach(func(_, v []byte) error { |
| if err := proto.Unmarshal(v, &msg); err != nil { |
| return err |
| } |
| ip, pp, err := propertiesFromCache(&msg) |
| if err != nil { |
| return err |
| } |
| m.ovr[ip] = pp |
| return nil |
| }) |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to get overrides") |
| } |
| } |
| |
| // Required |
| if req := b.Bucket(cacheKeyRequired); req != nil { |
| err := req.ForEach(func(_, v []byte) error { |
| m.req[string(v)] = true |
| return nil |
| }) |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to get required") |
| } |
| } |
| |
| return m, nil |
| } |
| |
| // copyTo returns a serializable representation of lp. |
| func (lp lockedProject) copyTo(msg *pb.LockedProject, c *pb.Constraint) { |
| if lp.v == nil { |
| msg.UnpairedVersion = nil |
| } else { |
| lp.v.copyTo(c) |
| msg.UnpairedVersion = c |
| } |
| |
| msg.Root = string(lp.pi.ProjectRoot) |
| msg.Source = lp.pi.Source |
| msg.Revision = string(lp.r) |
| msg.Packages = lp.pkgs |
| } |
| |
| // copyLockedProjectTo hydrates pointers to serializable representations of a |
| // LockedProject with the appropriate data. |
| func copyLockedProjectTo(lp LockedProject, msg *pb.LockedProject, c *pb.Constraint) { |
| if nlp, ok := lp.(lockedProject); ok { |
| nlp.copyTo(msg, c) |
| return |
| } |
| |
| v := lp.Version() |
| if v == nil { |
| msg.UnpairedVersion = nil |
| } else { |
| v.copyTo(c) |
| msg.UnpairedVersion = c |
| |
| switch tv := v.(type) { |
| case Revision: |
| msg.Revision = string(tv) |
| case versionPair: |
| msg.Revision = string(tv.r) |
| } |
| } |
| |
| pi := lp.Ident() |
| msg.Root = string(pi.ProjectRoot) |
| msg.Source = pi.Source |
| msg.Packages = lp.Packages() |
| } |
| |
| // lockedProjectFromCache returns a new LockedProject with fields from m. |
| func lockedProjectFromCache(m *pb.LockedProject) (LockedProject, error) { |
| var uv UnpairedVersion |
| var err error |
| if m.UnpairedVersion != nil { |
| uv, err = unpairedVersionFromCache(m.UnpairedVersion) |
| if err != nil { |
| return lockedProject{}, err |
| } |
| } |
| return lockedProject{ |
| pi: ProjectIdentifier{ |
| ProjectRoot: ProjectRoot(m.Root), |
| Source: m.Source, |
| }, |
| v: uv, |
| r: Revision(m.Revision), |
| pkgs: m.Packages, |
| }, nil |
| } |
| |
| // cachePutLock stores the Lock as fields in the bolt.Bucket. |
| func cachePutLock(b *bolt.Bucket, l Lock) error { |
| // Input imports, if present. |
| byt := []byte(strings.Join(l.InputImports(), "#")) |
| if err := b.Put(cacheKeyInputImports, byt); err != nil { |
| return errors.Wrap(err, "failed to put input imports") |
| } |
| |
| // Projects |
| if projects := l.Projects(); len(projects) > 0 { |
| lb, err := b.CreateBucket(cacheKeyLock) |
| if err != nil { |
| return err |
| } |
| key := make(nuts.Key, nuts.KeyLen(uint64(len(projects)-1))) |
| var msg pb.LockedProject |
| var cMsg pb.Constraint |
| for i, lp := range projects { |
| copyLockedProjectTo(lp, &msg, &cMsg) |
| v, err := proto.Marshal(&msg) |
| if err != nil { |
| return err |
| } |
| key.Put(uint64(i)) |
| if err := lb.Put(key, v); err != nil { |
| return err |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| // cacheGetLock returns a new *safeLock with the fields retrieved from the bolt.Bucket. |
| func cacheGetLock(b *bolt.Bucket) (*safeLock, error) { |
| l := &safeLock{} |
| if ii := b.Get(cacheKeyInputImports); len(ii) > 0 { |
| l.i = strings.Split(string(ii), "#") |
| } |
| |
| if locked := b.Bucket(cacheKeyLock); locked != nil { |
| var msg pb.LockedProject |
| err := locked.ForEach(func(_, v []byte) error { |
| if err := proto.Unmarshal(v, &msg); err != nil { |
| return err |
| } |
| lp, err := lockedProjectFromCache(&msg) |
| if err != nil { |
| return err |
| } |
| l.p = append(l.p, lp) |
| return nil |
| }) |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to get locked projects") |
| } |
| } |
| return l, nil |
| } |
| |
| // cachePutPackageOrError stores the pkgtree.PackageOrErr as fields in the bolt.Bucket. |
| // Package.ImportPath is ignored. |
| func cachePutPackageOrErr(b *bolt.Bucket, poe pkgtree.PackageOrErr) error { |
| if poe.Err != nil { |
| err := b.Put(cacheKeyError, []byte(poe.Err.Error())) |
| return errors.Wrapf(err, "failed to put error: %v", poe.Err) |
| } |
| if len(poe.P.CommentPath) > 0 { |
| err := b.Put(cacheKeyComment, []byte(poe.P.CommentPath)) |
| if err != nil { |
| return errors.Wrapf(err, "failed to put package: %v", poe.P) |
| } |
| } |
| if len(poe.P.Imports) > 0 { |
| ip, err := b.CreateBucket(cacheKeyImport) |
| if err != nil { |
| return err |
| } |
| key := make(nuts.Key, nuts.KeyLen(uint64(len(poe.P.Imports)-1))) |
| for i := range poe.P.Imports { |
| v := []byte(poe.P.Imports[i]) |
| key.Put(uint64(i)) |
| if err := ip.Put(key, v); err != nil { |
| return err |
| } |
| } |
| } |
| |
| if len(poe.P.Name) > 0 { |
| err := b.Put(cacheKeyName, []byte(poe.P.Name)) |
| if err != nil { |
| return errors.Wrapf(err, "failed to put package: %v", poe.P) |
| } |
| } |
| |
| if len(poe.P.TestImports) > 0 { |
| ip, err := b.CreateBucket(cacheKeyTestImport) |
| if err != nil { |
| return err |
| } |
| key := make(nuts.Key, nuts.KeyLen(uint64(len(poe.P.TestImports)-1))) |
| for i := range poe.P.TestImports { |
| v := []byte(poe.P.TestImports[i]) |
| key.Put(uint64(i)) |
| if err := ip.Put(key, v); err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| // cacheGetPackageOrErr returns a new pkgtree.PackageOrErr with fields retrieved |
| // from the bolt.Bucket. |
| func cacheGetPackageOrErr(b *bolt.Bucket) (pkgtree.PackageOrErr, error) { |
| if v := b.Get(cacheKeyError); len(v) > 0 { |
| return pkgtree.PackageOrErr{ |
| Err: errors.New(string(v)), |
| }, nil |
| } |
| |
| var p pkgtree.Package |
| p.CommentPath = string(b.Get(cacheKeyComment)) |
| if ip := b.Bucket(cacheKeyImport); ip != nil { |
| err := ip.ForEach(func(_, v []byte) error { |
| p.Imports = append(p.Imports, string(v)) |
| return nil |
| }) |
| if err != nil { |
| return pkgtree.PackageOrErr{}, err |
| } |
| } |
| p.Name = string(b.Get(cacheKeyName)) |
| if tip := b.Bucket(cacheKeyTestImport); tip != nil { |
| err := tip.ForEach(func(_, v []byte) error { |
| p.TestImports = append(p.TestImports, string(v)) |
| return nil |
| }) |
| if err != nil { |
| return pkgtree.PackageOrErr{}, err |
| } |
| } |
| return pkgtree.PackageOrErr{P: p}, nil |
| } |
| |
| // cacheTimestampedKey returns a prefixed key with a trailing timestamp. |
| func cacheTimestampedKey(pre byte, t time.Time) []byte { |
| b := make([]byte, 9) |
| b[0] = pre |
| binary.BigEndian.PutUint64(b[1:], uint64(t.Unix())) |
| return b |
| } |
| |
| // boltTxOrBucket is a minimal interface satisfied by bolt.Tx and bolt.Bucket. |
| type boltTxOrBucket interface { |
| Cursor() *bolt.Cursor |
| DeleteBucket([]byte) error |
| Bucket([]byte) *bolt.Bucket |
| } |
| |
| // cachePrefixDelete prefix scans and deletes each bucket. |
| func cachePrefixDelete(tob boltTxOrBucket, pre byte) error { |
| c := tob.Cursor() |
| for k, _ := c.Seek([]byte{pre}); len(k) > 0 && k[0] == pre; k, _ = c.Next() { |
| if err := tob.DeleteBucket(k); err != nil { |
| return errors.Wrapf(err, "failed to delete bucket: %s", k) |
| } |
| } |
| return nil |
| } |
| |
| // cacheFindLatestValid prefix scans for the latest bucket which is timestamped >= epoch, |
| // or returns nil if none exists. |
| func cacheFindLatestValid(tob boltTxOrBucket, pre byte, epoch int64) *bolt.Bucket { |
| c := tob.Cursor() |
| var latest []byte |
| for k, _ := c.Seek([]byte{pre}); len(k) > 0 && k[0] == pre; k, _ = c.Next() { |
| latest = k |
| } |
| if latest == nil { |
| return nil |
| } |
| ts := latest[1:] |
| if len(ts) != 8 { |
| return nil |
| } |
| if int64(binary.BigEndian.Uint64(ts)) < epoch { |
| return nil |
| } |
| return tob.Bucket(latest) |
| } |