github.com/sdboyer/gps@v0.16.3/identifier.go (about)

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