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

     1  package gps
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/Masterminds/semver"
     8  )
     9  
    10  var (
    11  	none = noneConstraint{}
    12  	any  = anyConstraint{}
    13  )
    14  
    15  // A Constraint provides structured limitations on the versions that are
    16  // admissible for a given project.
    17  //
    18  // As with Version, it has a private method because the gps's internal
    19  // implementation of the problem is complete, and the system relies on type
    20  // magic to operate.
    21  type Constraint interface {
    22  	fmt.Stringer
    23  
    24  	// Matches indicates if the provided Version is allowed by the Constraint.
    25  	Matches(Version) bool
    26  
    27  	// MatchesAny indicates if the intersection of the Constraint with the
    28  	// provided Constraint would yield a Constraint that could allow *any*
    29  	// Version.
    30  	MatchesAny(Constraint) bool
    31  
    32  	// Intersect computes the intersection of the Constraint with the provided
    33  	// Constraint.
    34  	Intersect(Constraint) Constraint
    35  
    36  	// typedString emits the normal stringified representation of the provided
    37  	// constraint, prefixed with a string that uniquely identifies the type of
    38  	// the constraint.
    39  	//
    40  	// It also forces Constraint to be a private/sealed interface, which is a
    41  	// design goal of the system.
    42  	typedString() string
    43  }
    44  
    45  // NewSemverConstraint attempts to construct a semver Constraint object from the
    46  // input string.
    47  //
    48  // If the input string cannot be made into a valid semver Constraint, an error
    49  // is returned.
    50  func NewSemverConstraint(body string) (Constraint, error) {
    51  	c, err := semver.NewConstraint(body)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	// If we got a simple semver.Version, simplify by returning our
    56  	// corresponding type
    57  	if sv, ok := c.(*semver.Version); ok {
    58  		return semVersion{sv: sv}, nil
    59  	}
    60  	return semverConstraint{c: c}, nil
    61  }
    62  
    63  type semverConstraint struct {
    64  	c semver.Constraint
    65  }
    66  
    67  func (c semverConstraint) String() string {
    68  	return c.c.String()
    69  }
    70  
    71  func (c semverConstraint) typedString() string {
    72  	return fmt.Sprintf("svc-%s", c.c.String())
    73  }
    74  
    75  func (c semverConstraint) Matches(v Version) bool {
    76  	switch tv := v.(type) {
    77  	case versionTypeUnion:
    78  		for _, elem := range tv {
    79  			if c.Matches(elem) {
    80  				return true
    81  			}
    82  		}
    83  	case semVersion:
    84  		return c.c.Matches(tv.sv) == nil
    85  	case versionPair:
    86  		if tv2, ok := tv.v.(semVersion); ok {
    87  			return c.c.Matches(tv2.sv) == nil
    88  		}
    89  	}
    90  
    91  	return false
    92  }
    93  
    94  func (c semverConstraint) MatchesAny(c2 Constraint) bool {
    95  	return c.Intersect(c2) != none
    96  }
    97  
    98  func (c semverConstraint) Intersect(c2 Constraint) Constraint {
    99  	switch tc := c2.(type) {
   100  	case anyConstraint:
   101  		return c
   102  	case versionTypeUnion:
   103  		for _, elem := range tc {
   104  			if rc := c.Intersect(elem); rc != none {
   105  				return rc
   106  			}
   107  		}
   108  	case semverConstraint:
   109  		rc := c.c.Intersect(tc.c)
   110  		if !semver.IsNone(rc) {
   111  			return semverConstraint{c: rc}
   112  		}
   113  	case semVersion:
   114  		rc := c.c.Intersect(tc.sv)
   115  		if !semver.IsNone(rc) {
   116  			// If single version intersected with constraint, we know the result
   117  			// must be the single version, so just return it back out
   118  			return c2
   119  		}
   120  	case versionPair:
   121  		if tc2, ok := tc.v.(semVersion); ok {
   122  			rc := c.c.Intersect(tc2.sv)
   123  			if !semver.IsNone(rc) {
   124  				// same reasoning as previous case
   125  				return c2
   126  			}
   127  		}
   128  	}
   129  
   130  	return none
   131  }
   132  
   133  // IsAny indicates if the provided constraint is the wildcard "Any" constraint.
   134  func IsAny(c Constraint) bool {
   135  	_, ok := c.(anyConstraint)
   136  	return ok
   137  }
   138  
   139  // Any returns a constraint that will match anything.
   140  func Any() Constraint {
   141  	return anyConstraint{}
   142  }
   143  
   144  // anyConstraint is an unbounded constraint - it matches all other types of
   145  // constraints. It mirrors the behavior of the semver package's any type.
   146  type anyConstraint struct{}
   147  
   148  func (anyConstraint) String() string {
   149  	return "*"
   150  }
   151  
   152  func (anyConstraint) typedString() string {
   153  	return "any-*"
   154  }
   155  
   156  func (anyConstraint) Matches(Version) bool {
   157  	return true
   158  }
   159  
   160  func (anyConstraint) MatchesAny(Constraint) bool {
   161  	return true
   162  }
   163  
   164  func (anyConstraint) Intersect(c Constraint) Constraint {
   165  	return c
   166  }
   167  
   168  // noneConstraint is the empty set - it matches no versions. It mirrors the
   169  // behavior of the semver package's none type.
   170  type noneConstraint struct{}
   171  
   172  func (noneConstraint) String() string {
   173  	return ""
   174  }
   175  
   176  func (noneConstraint) typedString() string {
   177  	return "none-"
   178  }
   179  
   180  func (noneConstraint) Matches(Version) bool {
   181  	return false
   182  }
   183  
   184  func (noneConstraint) MatchesAny(Constraint) bool {
   185  	return false
   186  }
   187  
   188  func (noneConstraint) Intersect(Constraint) Constraint {
   189  	return none
   190  }
   191  
   192  // A ProjectConstraint combines a ProjectIdentifier with a Constraint. It
   193  // indicates that, if packages contained in the ProjectIdentifier enter the
   194  // depgraph, they must do so at a version that is allowed by the Constraint.
   195  type ProjectConstraint struct {
   196  	Ident      ProjectIdentifier
   197  	Constraint Constraint
   198  }
   199  
   200  // ProjectConstraints is a map of projects, as identified by their import path
   201  // roots (ProjectRoots) to the corresponding ProjectProperties.
   202  //
   203  // They are the standard form in which Manifests declare their required
   204  // dependency properties - constraints and network locations - as well as the
   205  // form in which RootManifests declare their overrides.
   206  type ProjectConstraints map[ProjectRoot]ProjectProperties
   207  
   208  type workingConstraint struct {
   209  	Ident                     ProjectIdentifier
   210  	Constraint                Constraint
   211  	overrNet, overrConstraint bool
   212  }
   213  
   214  func pcSliceToMap(l []ProjectConstraint, r ...[]ProjectConstraint) ProjectConstraints {
   215  	final := make(ProjectConstraints)
   216  
   217  	for _, pc := range l {
   218  		final[pc.Ident.ProjectRoot] = ProjectProperties{
   219  			Source:     pc.Ident.Source,
   220  			Constraint: pc.Constraint,
   221  		}
   222  	}
   223  
   224  	for _, pcs := range r {
   225  		for _, pc := range pcs {
   226  			if pp, exists := final[pc.Ident.ProjectRoot]; exists {
   227  				// Technically this should be done through a bridge for
   228  				// cross-version-type matching...but this is a one off for root and
   229  				// that's just ridiculous for this.
   230  				pp.Constraint = pp.Constraint.Intersect(pc.Constraint)
   231  				final[pc.Ident.ProjectRoot] = pp
   232  			} else {
   233  				final[pc.Ident.ProjectRoot] = ProjectProperties{
   234  					Source:     pc.Ident.Source,
   235  					Constraint: pc.Constraint,
   236  				}
   237  			}
   238  		}
   239  	}
   240  
   241  	return final
   242  }
   243  
   244  func (m ProjectConstraints) asSortedSlice() []ProjectConstraint {
   245  	pcs := make([]ProjectConstraint, len(m))
   246  
   247  	k := 0
   248  	for pr, pp := range m {
   249  		pcs[k] = ProjectConstraint{
   250  			Ident: ProjectIdentifier{
   251  				ProjectRoot: pr,
   252  				Source:      pp.Source,
   253  			},
   254  			Constraint: pp.Constraint,
   255  		}
   256  		k++
   257  	}
   258  
   259  	sort.Stable(sortedConstraints(pcs))
   260  	return pcs
   261  }
   262  
   263  // merge pulls in all the constraints from other ProjectConstraints map(s),
   264  // merging them with the receiver into a new ProjectConstraints map.
   265  //
   266  // If duplicate ProjectRoots are encountered, the constraints are intersected
   267  // together and the latter's NetworkName, if non-empty, is taken.
   268  func (m ProjectConstraints) merge(other ...ProjectConstraints) (out ProjectConstraints) {
   269  	plen := len(m)
   270  	for _, pcm := range other {
   271  		plen += len(pcm)
   272  	}
   273  
   274  	out = make(ProjectConstraints, plen)
   275  	for pr, pp := range m {
   276  		out[pr] = pp
   277  	}
   278  
   279  	for _, pcm := range other {
   280  		for pr, pp := range pcm {
   281  			if rpp, exists := out[pr]; exists {
   282  				pp.Constraint = pp.Constraint.Intersect(rpp.Constraint)
   283  				if pp.Source == "" {
   284  					pp.Source = rpp.Source
   285  				}
   286  			}
   287  			out[pr] = pp
   288  		}
   289  	}
   290  
   291  	return
   292  }
   293  
   294  // overrideAll treats the receiver ProjectConstraints map as a set of override
   295  // instructions, and applies overridden values to the ProjectConstraints.
   296  //
   297  // A slice of workingConstraint is returned, allowing differentiation between
   298  // values that were or were not overridden.
   299  func (m ProjectConstraints) overrideAll(pcm ProjectConstraints) (out []workingConstraint) {
   300  	out = make([]workingConstraint, len(pcm))
   301  	k := 0
   302  	for pr, pp := range pcm {
   303  		out[k] = m.override(pr, pp)
   304  		k++
   305  	}
   306  
   307  	sort.Stable(sortedWC(out))
   308  	return
   309  }
   310  
   311  // override replaces a single ProjectConstraint with a workingConstraint,
   312  // overriding its values if a corresponding entry exists in the
   313  // ProjectConstraints map.
   314  func (m ProjectConstraints) override(pr ProjectRoot, pp ProjectProperties) workingConstraint {
   315  	wc := workingConstraint{
   316  		Ident: ProjectIdentifier{
   317  			ProjectRoot: pr,
   318  			Source:      pp.Source,
   319  		},
   320  		Constraint: pp.Constraint,
   321  	}
   322  
   323  	if opp, has := m[pr]; has {
   324  		// The rule for overrides is that *any* non-zero value for the prop
   325  		// should be considered an override, even if it's equal to what's
   326  		// already there.
   327  		if opp.Constraint != nil {
   328  			wc.Constraint = opp.Constraint
   329  			wc.overrConstraint = true
   330  		}
   331  
   332  		// This may appear incorrect, because the solver encodes meaning into
   333  		// the empty string for NetworkName (it means that it would use the
   334  		// import path by default, but could be coerced into using an alternate
   335  		// URL). However, that 'coercion' can only happen if there's a
   336  		// disagreement between projects on where a dependency should be sourced
   337  		// from. Such disagreement is exactly what overrides preclude, so
   338  		// there's no need to preserve the meaning of "" here - thus, we can
   339  		// treat it as a zero value and ignore it, rather than applying it.
   340  		if opp.Source != "" {
   341  			wc.Ident.Source = opp.Source
   342  			wc.overrNet = true
   343  		}
   344  	}
   345  
   346  	return wc
   347  }
   348  
   349  type sortedConstraints []ProjectConstraint
   350  
   351  func (s sortedConstraints) Len() int           { return len(s) }
   352  func (s sortedConstraints) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   353  func (s sortedConstraints) Less(i, j int) bool { return s[i].Ident.less(s[j].Ident) }
   354  
   355  type sortedWC []workingConstraint
   356  
   357  func (s sortedWC) Len() int           { return len(s) }
   358  func (s sortedWC) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   359  func (s sortedWC) Less(i, j int) bool { return s[i].Ident.less(s[j].Ident) }