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

     1  // Copyright 2017 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 gps
     6  
     7  import (
     8  	"fmt"
     9  	"sort"
    10  )
    11  
    12  // Lock represents data from a lock file (or however the implementing tool
    13  // chooses to store it) at a particular version that is relevant to the
    14  // satisfiability solving process.
    15  //
    16  // In general, the information produced by gps on finding a successful
    17  // solution is all that would be necessary to constitute a lock file, though
    18  // tools can include whatever other information they want in their storage.
    19  type Lock interface {
    20  	// Projects returns the list of LockedProjects contained in the lock data.
    21  	Projects() []LockedProject
    22  
    23  	// The set of imports (and required statements) that were the inputs that
    24  	// generated this Lock. It is acceptable to return a nil slice from this
    25  	// method if the information cannot reasonably be made available.
    26  	InputImports() []string
    27  }
    28  
    29  // sortLockedProjects returns a sorted copy of lps, or itself if already sorted.
    30  func sortLockedProjects(lps []LockedProject) []LockedProject {
    31  	if len(lps) <= 1 || sort.SliceIsSorted(lps, func(i, j int) bool {
    32  		return lps[i].Ident().Less(lps[j].Ident())
    33  	}) {
    34  		return lps
    35  	}
    36  	cp := make([]LockedProject, len(lps))
    37  	copy(cp, lps)
    38  	sort.Slice(cp, func(i, j int) bool {
    39  		return cp[i].Ident().Less(cp[j].Ident())
    40  	})
    41  	return cp
    42  }
    43  
    44  // LockedProject is a single project entry from a lock file. It expresses the
    45  // project's name, one or both of version and underlying revision, the network
    46  // URI for accessing it, the path at which it should be placed within a vendor
    47  // directory, and the packages that are used in it.
    48  type LockedProject interface {
    49  	Ident() ProjectIdentifier
    50  	Version() Version
    51  	Packages() []string
    52  	Eq(LockedProject) bool
    53  	String() string
    54  }
    55  
    56  // lockedProject is the default implementation of LockedProject.
    57  type lockedProject struct {
    58  	pi   ProjectIdentifier
    59  	v    UnpairedVersion
    60  	r    Revision
    61  	pkgs []string
    62  }
    63  
    64  // SimpleLock is a helper for tools to easily describe lock data when they know
    65  // that input imports are unavailable.
    66  type SimpleLock []LockedProject
    67  
    68  var _ Lock = SimpleLock{}
    69  
    70  // Projects returns the entire contents of the SimpleLock.
    71  func (l SimpleLock) Projects() []LockedProject {
    72  	return l
    73  }
    74  
    75  // InputImports returns a nil string slice, as SimpleLock does not provide a way
    76  // of capturing string slices.
    77  func (l SimpleLock) InputImports() []string {
    78  	return nil
    79  }
    80  
    81  // NewLockedProject creates a new LockedProject struct with a given
    82  // ProjectIdentifier (name and optional upstream source URL), version. and list
    83  // of packages required from the project.
    84  //
    85  // Note that passing a nil version will cause a panic. This is a correctness
    86  // measure to ensure that the solver is never exposed to a version-less lock
    87  // entry. Such a case would be meaningless - the solver would have no choice but
    88  // to simply dismiss that project. By creating a hard failure case via panic
    89  // instead, we are trying to avoid inflicting the resulting pain on the user by
    90  // instead forcing a decision on the Analyzer implementation.
    91  func NewLockedProject(id ProjectIdentifier, v Version, pkgs []string) LockedProject {
    92  	if v == nil {
    93  		panic("must provide a non-nil version to create a LockedProject")
    94  	}
    95  
    96  	lp := lockedProject{
    97  		pi:   id,
    98  		pkgs: pkgs,
    99  	}
   100  
   101  	switch tv := v.(type) {
   102  	case Revision:
   103  		lp.r = tv
   104  	case branchVersion:
   105  		lp.v = tv
   106  	case semVersion:
   107  		lp.v = tv
   108  	case plainVersion:
   109  		lp.v = tv
   110  	case versionPair:
   111  		lp.r = tv.r
   112  		lp.v = tv.v
   113  	}
   114  
   115  	return lp
   116  }
   117  
   118  // Ident returns the identifier describing the project. This includes both the
   119  // local name (the root name by which the project is referenced in import paths)
   120  // and the network name, where the upstream source lives.
   121  func (lp lockedProject) Ident() ProjectIdentifier {
   122  	return lp.pi
   123  }
   124  
   125  // Version assembles together whatever version and/or revision data is
   126  // available into a single Version.
   127  func (lp lockedProject) Version() Version {
   128  	if lp.r == "" {
   129  		return lp.v
   130  	}
   131  
   132  	if lp.v == nil {
   133  		return lp.r
   134  	}
   135  
   136  	return lp.v.Pair(lp.r)
   137  }
   138  
   139  // Eq checks if two LockedProject instances are equal. The implementation
   140  // assumes both Packages lists are already sorted lexicographically.
   141  func (lp lockedProject) Eq(lp2 LockedProject) bool {
   142  	if lp.pi != lp2.Ident() {
   143  		return false
   144  	}
   145  
   146  	var uv UnpairedVersion
   147  	switch tv := lp2.Version().(type) {
   148  	case Revision:
   149  		if lp.r != tv {
   150  			return false
   151  		}
   152  	case versionPair:
   153  		if lp.r != tv.r {
   154  			return false
   155  		}
   156  		uv = tv.v
   157  	case branchVersion, semVersion, plainVersion:
   158  		// For now, we're going to say that revisions must be present in order
   159  		// to indicate equality. We may need to change this later, as it may be
   160  		// more appropriate to enforce elsewhere.
   161  		return false
   162  	}
   163  
   164  	v1n := lp.v == nil
   165  	v2n := uv == nil
   166  
   167  	if v1n != v2n {
   168  		return false
   169  	}
   170  
   171  	if !v1n && !lp.v.Matches(uv) {
   172  		return false
   173  	}
   174  
   175  	opkgs := lp2.Packages()
   176  	if len(lp.pkgs) != len(opkgs) {
   177  		return false
   178  	}
   179  
   180  	for k, v := range lp.pkgs {
   181  		if opkgs[k] != v {
   182  			return false
   183  		}
   184  	}
   185  
   186  	return true
   187  }
   188  
   189  // Packages returns the list of packages from within the LockedProject that are
   190  // actually used in the import graph. Some caveats:
   191  //
   192  //  * The names given are relative to the root import path for the project. If
   193  //    the root package itself is imported, it's represented as ".".
   194  //  * Just because a package path isn't included in this list doesn't mean it's
   195  //    safe to remove - it could contain C files, or other assets, that can't be
   196  //    safely removed.
   197  //  * The slice is not a copy. If you need to modify it, copy it first.
   198  func (lp lockedProject) Packages() []string {
   199  	return lp.pkgs
   200  }
   201  
   202  func (lp lockedProject) String() string {
   203  	return fmt.Sprintf("%s@%s with packages: %v",
   204  		lp.Ident(), lp.Version(), lp.pkgs)
   205  }
   206  
   207  type safeLock struct {
   208  	p []LockedProject
   209  	i []string
   210  }
   211  
   212  func (sl safeLock) InputImports() []string {
   213  	return sl.i
   214  }
   215  
   216  func (sl safeLock) Projects() []LockedProject {
   217  	return sl.p
   218  }
   219  
   220  // prepLock ensures a lock is prepared and safe for use by the solver. This is
   221  // mostly about defensively ensuring that no outside routine can modify the lock
   222  // while the solver is in-flight.
   223  //
   224  // This is achieved by copying the lock's data into a new safeLock.
   225  func prepLock(l Lock) safeLock {
   226  	pl := l.Projects()
   227  
   228  	rl := safeLock{
   229  		p: make([]LockedProject, len(pl)),
   230  	}
   231  	copy(rl.p, pl)
   232  
   233  	rl.i = make([]string, len(l.InputImports()))
   234  	copy(rl.i, l.InputImports())
   235  
   236  	return rl
   237  }