github.com/golang/dep@v0.5.4/gps/identifier.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  	"math/rand"
    10  	"strconv"
    11  )
    12  
    13  // ProjectRoot is the topmost import path in a tree of other import paths - the
    14  // root of the tree. In gps' current design, ProjectRoots have to correspond to
    15  // a repository root (mostly), but their real purpose is to identify the root
    16  // import path of a "project", logically encompassing all child packages.
    17  //
    18  // Projects are a crucial unit of operation in gps. Constraints are declared by
    19  // a project's manifest, and apply to all packages in a ProjectRoot's tree.
    20  // Solving itself mostly proceeds on a project-by-project basis.
    21  //
    22  // Aliasing string types is usually a bit of an anti-pattern. gps does it here
    23  // as a means of clarifying API intent. This is important because Go's package
    24  // management domain has lots of different path-ish strings floating around:
    25  //
    26  //  actual directories:
    27  //	/home/sdboyer/go/src/github.com/sdboyer/gps/example
    28  //  URLs:
    29  //	https://github.com/sdboyer/gps
    30  //  import paths:
    31  //	github.com/sdboyer/gps/example
    32  //  portions of import paths that refer to a package:
    33  //	example
    34  //  portions that could not possibly refer to anything sane:
    35  //	github.com/sdboyer
    36  //  portions that correspond to a repository root:
    37  //	github.com/sdboyer/gps
    38  //
    39  // While not a panacea, having ProjectRoot allows gps to clearly indicate via
    40  // the type system when a path-ish string must have particular semantics.
    41  type ProjectRoot string
    42  
    43  // A ProjectIdentifier provides the name and source location of a dependency. It
    44  // is related to, but differs in two key ways from, a plain import path.
    45  //
    46  // First, ProjectIdentifiers do not identify a single package. Rather, they
    47  // encompass the whole tree of packages, including tree's root - the
    48  // ProjectRoot. In gps' current design, this ProjectRoot almost always
    49  // corresponds to the root of a repository.
    50  //
    51  // Second, ProjectIdentifiers can optionally carry a Source, which
    52  // identifies where the underlying source code can be located on the network.
    53  // These can be either a full URL, including protocol, or plain import paths.
    54  // So, these are all valid data for Source:
    55  //
    56  //  github.com/sdboyer/gps
    57  //  github.com/fork/gps
    58  //  git@github.com:sdboyer/gps
    59  //  https://github.com/sdboyer/gps
    60  //
    61  // With plain import paths, network addresses are derived purely through an
    62  // algorithm. By having an explicit network name, it becomes possible to, for
    63  // example, transparently substitute a fork for the original upstream source
    64  // repository.
    65  //
    66  // Note that gps makes no guarantees about the actual import paths contained in
    67  // a repository aligning with ImportRoot. If tools, or their users, specify an
    68  // alternate Source that contains a repository with incompatible internal
    69  // import paths, gps' solving operations will error. (gps does no import
    70  // rewriting.)
    71  //
    72  // Also note that if different projects' manifests report a different
    73  // Source for a given ImportRoot, it is a solve failure. Everyone has to
    74  // agree on where a given import path should be sourced from.
    75  //
    76  // If Source is not explicitly set, gps will derive the network address from
    77  // the ImportRoot using a similar algorithm to that utilized by `go get`.
    78  type ProjectIdentifier struct {
    79  	ProjectRoot ProjectRoot
    80  	Source      string
    81  }
    82  
    83  // Less compares by ProjectRoot then normalized Source.
    84  func (i ProjectIdentifier) Less(j ProjectIdentifier) bool {
    85  	if i.ProjectRoot < j.ProjectRoot {
    86  		return true
    87  	}
    88  	if j.ProjectRoot < i.ProjectRoot {
    89  		return false
    90  	}
    91  	return i.normalizedSource() < j.normalizedSource()
    92  }
    93  
    94  func (i ProjectIdentifier) eq(j ProjectIdentifier) bool {
    95  	if i.ProjectRoot != j.ProjectRoot {
    96  		return false
    97  	}
    98  	if i.Source == j.Source {
    99  		return true
   100  	}
   101  
   102  	if (i.Source == "" && j.Source == string(j.ProjectRoot)) ||
   103  		(j.Source == "" && i.Source == string(i.ProjectRoot)) {
   104  		return true
   105  	}
   106  
   107  	return false
   108  }
   109  
   110  // equiv will check if the two identifiers are "equivalent," under special
   111  // rules.
   112  //
   113  // Given that the ProjectRoots are equal (==), equivalency occurs if:
   114  //
   115  // 1. The Sources are equal (==), OR
   116  // 2. The LEFT (the receiver) Source is non-empty, and the right
   117  // Source is empty.
   118  //
   119  // *This is asymmetry in this binary relation is intentional.* It facilitates
   120  // the case where we allow for a ProjectIdentifier with an explicit Source
   121  // to match one without.
   122  func (i ProjectIdentifier) equiv(j ProjectIdentifier) bool {
   123  	if i.ProjectRoot != j.ProjectRoot {
   124  		return false
   125  	}
   126  	if i.Source == j.Source {
   127  		return true
   128  	}
   129  
   130  	if i.Source != "" && j.Source == "" {
   131  		return true
   132  	}
   133  
   134  	return false
   135  }
   136  
   137  func (i ProjectIdentifier) normalizedSource() string {
   138  	if i.Source == "" {
   139  		return string(i.ProjectRoot)
   140  	}
   141  	return i.Source
   142  }
   143  
   144  func (i ProjectIdentifier) String() string {
   145  	if i.Source == "" || i.Source == string(i.ProjectRoot) {
   146  		return string(i.ProjectRoot)
   147  	}
   148  	return fmt.Sprintf("%s (from %s)", i.ProjectRoot, i.Source)
   149  }
   150  
   151  func (i ProjectIdentifier) normalize() ProjectIdentifier {
   152  	if i.Source == "" {
   153  		i.Source = string(i.ProjectRoot)
   154  	}
   155  
   156  	return i
   157  }
   158  
   159  // ProjectProperties comprise the properties that can be attached to a
   160  // ProjectRoot.
   161  //
   162  // In general, these are declared in the context of a map of ProjectRoot to its
   163  // ProjectProperties; they make little sense without their corresponding
   164  // ProjectRoot.
   165  type ProjectProperties struct {
   166  	Source     string
   167  	Constraint Constraint
   168  }
   169  
   170  // bimodalIdentifiers are used to track work to be done in the unselected queue.
   171  type bimodalIdentifier struct {
   172  	id ProjectIdentifier
   173  	// List of packages required within/under the ProjectIdentifier
   174  	pl []string
   175  	// prefv is used to indicate a 'preferred' version. This is expected to be
   176  	// derived from a dep's lock data, or else is empty.
   177  	prefv Version
   178  	// Indicates that the bmi came from the root project originally
   179  	fromRoot bool
   180  }
   181  
   182  type atom struct {
   183  	id ProjectIdentifier
   184  	v  Version
   185  }
   186  
   187  // With a random revision and no name, collisions are...unlikely
   188  var nilpa = atom{
   189  	v: Revision(strconv.FormatInt(rand.Int63(), 36)),
   190  }
   191  
   192  type atomWithPackages struct {
   193  	a  atom
   194  	pl []string
   195  }
   196  
   197  // bmi converts an atomWithPackages into a bimodalIdentifier.
   198  //
   199  // This is mostly intended for (read-only) trace use, so the package list slice
   200  // is not copied. It is the callers responsibility to not modify the pl slice,
   201  // lest that backpropagate and cause inconsistencies.
   202  func (awp atomWithPackages) bmi() bimodalIdentifier {
   203  	return bimodalIdentifier{
   204  		id: awp.a.id,
   205  		pl: awp.pl,
   206  	}
   207  }
   208  
   209  // completeDep (name hopefully to change) provides the whole picture of a
   210  // dependency - the root (repo and project, since currently we assume the two
   211  // are the same) name, a constraint, and the actual packages needed that are
   212  // under that root.
   213  type completeDep struct {
   214  	// The base workingConstraint
   215  	workingConstraint
   216  	// The specific packages required from the ProjectDep
   217  	pl []string
   218  }
   219  
   220  // dependency represents an incomplete edge in the depgraph. It has a
   221  // fully-realized atom as the depender (the tail/source of the edge), and a set
   222  // of requirements that any atom to be attached at the head/target must satisfy.
   223  type dependency struct {
   224  	depender atom
   225  	dep      completeDep
   226  }