github.com/golang/dep@v0.5.4/lock.go (about)

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package dep
     6  
     7  import (
     8  	"bytes"
     9  	"io"
    10  	"sort"
    11  
    12  	"github.com/golang/dep/gps"
    13  	"github.com/golang/dep/gps/verify"
    14  	"github.com/pelletier/go-toml"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  // LockName is the lock file name used by dep.
    19  const LockName = "Gopkg.lock"
    20  
    21  // Lock holds lock file data and implements gps.Lock.
    22  type Lock struct {
    23  	SolveMeta SolveMeta
    24  	P         []gps.LockedProject
    25  }
    26  
    27  // SolveMeta holds metadata about the solving process that created the lock that
    28  // is not specific to any individual project.
    29  type SolveMeta struct {
    30  	AnalyzerName    string
    31  	AnalyzerVersion int
    32  	SolverName      string
    33  	SolverVersion   int
    34  	InputImports    []string
    35  }
    36  
    37  type rawLock struct {
    38  	SolveMeta solveMeta          `toml:"solve-meta"`
    39  	Projects  []rawLockedProject `toml:"projects"`
    40  }
    41  
    42  type solveMeta struct {
    43  	AnalyzerName    string   `toml:"analyzer-name"`
    44  	AnalyzerVersion int      `toml:"analyzer-version"`
    45  	SolverName      string   `toml:"solver-name"`
    46  	SolverVersion   int      `toml:"solver-version"`
    47  	InputImports    []string `toml:"input-imports"`
    48  }
    49  
    50  type rawLockedProject struct {
    51  	Name      string   `toml:"name"`
    52  	Branch    string   `toml:"branch,omitempty"`
    53  	Revision  string   `toml:"revision"`
    54  	Version   string   `toml:"version,omitempty"`
    55  	Source    string   `toml:"source,omitempty"`
    56  	Packages  []string `toml:"packages"`
    57  	PruneOpts string   `toml:"pruneopts"`
    58  	Digest    string   `toml:"digest"`
    59  }
    60  
    61  func readLock(r io.Reader) (*Lock, error) {
    62  	buf := &bytes.Buffer{}
    63  	_, err := buf.ReadFrom(r)
    64  	if err != nil {
    65  		return nil, errors.Wrap(err, "Unable to read byte stream")
    66  	}
    67  
    68  	raw := rawLock{}
    69  	err = toml.Unmarshal(buf.Bytes(), &raw)
    70  	if err != nil {
    71  		return nil, errors.Wrap(err, "Unable to parse the lock as TOML")
    72  	}
    73  
    74  	return fromRawLock(raw)
    75  }
    76  
    77  func fromRawLock(raw rawLock) (*Lock, error) {
    78  	l := &Lock{
    79  		P: make([]gps.LockedProject, 0, len(raw.Projects)),
    80  	}
    81  
    82  	l.SolveMeta.AnalyzerName = raw.SolveMeta.AnalyzerName
    83  	l.SolveMeta.AnalyzerVersion = raw.SolveMeta.AnalyzerVersion
    84  	l.SolveMeta.SolverName = raw.SolveMeta.SolverName
    85  	l.SolveMeta.SolverVersion = raw.SolveMeta.SolverVersion
    86  	l.SolveMeta.InputImports = raw.SolveMeta.InputImports
    87  
    88  	for _, ld := range raw.Projects {
    89  		r := gps.Revision(ld.Revision)
    90  
    91  		var v gps.Version = r
    92  		if ld.Version != "" {
    93  			if ld.Branch != "" {
    94  				return nil, errors.Errorf("lock file specified both a branch (%s) and version (%s) for %s", ld.Branch, ld.Version, ld.Name)
    95  			}
    96  			v = gps.NewVersion(ld.Version).Pair(r)
    97  		} else if ld.Branch != "" {
    98  			v = gps.NewBranch(ld.Branch).Pair(r)
    99  		} else if r == "" {
   100  			return nil, errors.Errorf("lock file has entry for %s, but specifies no branch or version", ld.Name)
   101  		}
   102  
   103  		id := gps.ProjectIdentifier{
   104  			ProjectRoot: gps.ProjectRoot(ld.Name),
   105  			Source:      ld.Source,
   106  		}
   107  
   108  		var err error
   109  		vp := verify.VerifiableProject{
   110  			LockedProject: gps.NewLockedProject(id, v, ld.Packages),
   111  		}
   112  		if ld.Digest != "" {
   113  			vp.Digest, err = verify.ParseVersionedDigest(ld.Digest)
   114  			if err != nil {
   115  				return nil, err
   116  			}
   117  		}
   118  
   119  		po, err := gps.ParsePruneOptions(ld.PruneOpts)
   120  		if err != nil {
   121  			return nil, errors.Errorf("%s in prune options for %s", err.Error(), ld.Name)
   122  		}
   123  		// Add the vendor pruning bit so that gps doesn't get confused
   124  		vp.PruneOpts = po | gps.PruneNestedVendorDirs
   125  
   126  		l.P = append(l.P, vp)
   127  	}
   128  
   129  	return l, nil
   130  }
   131  
   132  // Projects returns the list of LockedProjects contained in the lock data.
   133  func (l *Lock) Projects() []gps.LockedProject {
   134  	if l == nil || l == (*Lock)(nil) {
   135  		return nil
   136  	}
   137  	return l.P
   138  }
   139  
   140  // InputImports reports the list of input imports that were used in generating
   141  // this Lock.
   142  func (l *Lock) InputImports() []string {
   143  	if l == nil || l == (*Lock)(nil) {
   144  		return nil
   145  	}
   146  	return l.SolveMeta.InputImports
   147  }
   148  
   149  // HasProjectWithRoot checks if the lock contains a project with the provided
   150  // ProjectRoot.
   151  //
   152  // This check is O(n) in the number of projects.
   153  func (l *Lock) HasProjectWithRoot(root gps.ProjectRoot) bool {
   154  	for _, p := range l.P {
   155  		if p.Ident().ProjectRoot == root {
   156  			return true
   157  		}
   158  	}
   159  
   160  	return false
   161  }
   162  
   163  func (l *Lock) dup() *Lock {
   164  	l2 := &Lock{
   165  		SolveMeta: l.SolveMeta,
   166  		P:         make([]gps.LockedProject, len(l.P)),
   167  	}
   168  
   169  	l2.SolveMeta.InputImports = make([]string, len(l.SolveMeta.InputImports))
   170  	copy(l2.SolveMeta.InputImports, l.SolveMeta.InputImports)
   171  	copy(l2.P, l.P)
   172  
   173  	return l2
   174  }
   175  
   176  // toRaw converts the manifest into a representation suitable to write to the lock file
   177  func (l *Lock) toRaw() rawLock {
   178  	raw := rawLock{
   179  		SolveMeta: solveMeta{
   180  			AnalyzerName:    l.SolveMeta.AnalyzerName,
   181  			AnalyzerVersion: l.SolveMeta.AnalyzerVersion,
   182  			InputImports:    l.SolveMeta.InputImports,
   183  			SolverName:      l.SolveMeta.SolverName,
   184  			SolverVersion:   l.SolveMeta.SolverVersion,
   185  		},
   186  		Projects: make([]rawLockedProject, 0, len(l.P)),
   187  	}
   188  
   189  	sort.Slice(l.P, func(i, j int) bool {
   190  		return l.P[i].Ident().Less(l.P[j].Ident())
   191  	})
   192  
   193  	for _, lp := range l.P {
   194  		id := lp.Ident()
   195  		ld := rawLockedProject{
   196  			Name:     string(id.ProjectRoot),
   197  			Source:   id.Source,
   198  			Packages: lp.Packages(),
   199  		}
   200  
   201  		v := lp.Version()
   202  		ld.Revision, ld.Branch, ld.Version = gps.VersionComponentStrings(v)
   203  
   204  		// This will panic if the lock isn't the expected dynamic type. We can
   205  		// relax this later if it turns out to create real problems, but there's
   206  		// no intended case in which this is untrue, so it's preferable to start
   207  		// by failing hard if those expectations aren't met.
   208  		vp := lp.(verify.VerifiableProject)
   209  		ld.Digest = vp.Digest.String()
   210  		ld.PruneOpts = (vp.PruneOpts & ^gps.PruneNestedVendorDirs).String()
   211  
   212  		raw.Projects = append(raw.Projects, ld)
   213  	}
   214  
   215  	return raw
   216  }
   217  
   218  // MarshalTOML serializes this lock into TOML via an intermediate raw form.
   219  func (l *Lock) MarshalTOML() ([]byte, error) {
   220  	raw := l.toRaw()
   221  	var buf bytes.Buffer
   222  	enc := toml.NewEncoder(&buf).ArraysWithOneElementPerLine(true)
   223  	err := enc.Encode(raw)
   224  	return buf.Bytes(), errors.Wrap(err, "Unable to marshal lock to TOML string")
   225  }
   226  
   227  // LockFromSolution converts a gps.Solution to dep's representation of a lock.
   228  // It makes sure that that the provided prune options are set correctly, as the
   229  // solver does not use VerifiableProjects for new selections it makes.
   230  //
   231  // Data is defensively copied wherever necessary to ensure the resulting *Lock
   232  // shares no memory with the input solution.
   233  func LockFromSolution(in gps.Solution, prune gps.CascadingPruneOptions) *Lock {
   234  	p := in.Projects()
   235  
   236  	l := &Lock{
   237  		SolveMeta: SolveMeta{
   238  			AnalyzerName:    in.AnalyzerName(),
   239  			AnalyzerVersion: in.AnalyzerVersion(),
   240  			InputImports:    in.InputImports(),
   241  			SolverName:      in.SolverName(),
   242  			SolverVersion:   in.SolverVersion(),
   243  		},
   244  		P: make([]gps.LockedProject, 0, len(p)),
   245  	}
   246  
   247  	for _, lp := range p {
   248  		if vp, ok := lp.(verify.VerifiableProject); ok {
   249  			l.P = append(l.P, vp)
   250  		} else {
   251  			l.P = append(l.P, verify.VerifiableProject{
   252  				LockedProject: lp,
   253  				PruneOpts:     prune.PruneOptionsFor(lp.Ident().ProjectRoot),
   254  			})
   255  		}
   256  	}
   257  
   258  	return l
   259  }