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

     1  package gps
     2  
     3  import (
     4  	"sort"
     5  
     6  	"github.com/armon/go-radix"
     7  	"github.com/sdboyer/gps/internal"
     8  	"github.com/sdboyer/gps/pkgtree"
     9  )
    10  
    11  // rootdata holds static data and constraining rules from the root project for
    12  // use in solving.
    13  type rootdata struct {
    14  	// Path to the root of the project on which gps is operating.
    15  	dir string
    16  
    17  	// Map of packages to ignore.
    18  	ig map[string]bool
    19  
    20  	// Map of packages to require.
    21  	req map[string]bool
    22  
    23  	// A ProjectConstraints map containing the validated (guaranteed non-empty)
    24  	// overrides declared by the root manifest.
    25  	ovr ProjectConstraints
    26  
    27  	// A map of the ProjectRoot (local names) that should be allowed to change
    28  	chng map[ProjectRoot]struct{}
    29  
    30  	// Flag indicating all projects should be allowed to change, without regard
    31  	// for lock.
    32  	chngall bool
    33  
    34  	// A map of the project names listed in the root's lock.
    35  	rlm map[ProjectRoot]LockedProject
    36  
    37  	// A defensively copied instance of the root manifest.
    38  	rm SimpleManifest
    39  
    40  	// A defensively copied instance of the root lock.
    41  	rl safeLock
    42  
    43  	// A defensively copied instance of params.RootPackageTree
    44  	rpt pkgtree.PackageTree
    45  
    46  	// The ProjectAnalyzer to use for all GetManifestAndLock calls.
    47  	an ProjectAnalyzer
    48  }
    49  
    50  // externalImportList returns a list of the unique imports from the root data.
    51  // Ignores and requires are taken into consideration, stdlib is excluded, and
    52  // errors within the local set of package are not backpropagated.
    53  func (rd rootdata) externalImportList() []string {
    54  	rm, _ := rd.rpt.ToReachMap(true, true, false, rd.ig)
    55  	all := rm.Flatten(false)
    56  	reach := make([]string, 0, len(all))
    57  	for _, r := range all {
    58  		if !internal.IsStdLib(r) {
    59  			reach = append(reach, r)
    60  		}
    61  	}
    62  
    63  	// If there are any requires, slide them into the reach list, as well.
    64  	if len(rd.req) > 0 {
    65  		// Make a map of imports that are both in the import path list and the
    66  		// required list to avoid duplication.
    67  		skip := make(map[string]bool, len(rd.req))
    68  		for _, r := range reach {
    69  			if rd.req[r] {
    70  				skip[r] = true
    71  			}
    72  		}
    73  
    74  		for r := range rd.req {
    75  			if !skip[r] {
    76  				reach = append(reach, r)
    77  			}
    78  		}
    79  	}
    80  
    81  	sort.Strings(reach)
    82  	return reach
    83  }
    84  
    85  func (rd rootdata) getApplicableConstraints() []workingConstraint {
    86  	// Merge the normal and test constraints together
    87  	pc := rd.rm.DependencyConstraints().merge(rd.rm.TestDependencyConstraints())
    88  
    89  	// Ensure that overrides which aren't in the combined pc map already make it
    90  	// in. Doing so makes input hashes equal in more useful cases.
    91  	for pr, pp := range rd.ovr {
    92  		if _, has := pc[pr]; !has {
    93  			cpp := ProjectProperties{
    94  				Constraint: pp.Constraint,
    95  				Source:     pp.Source,
    96  			}
    97  			if cpp.Constraint == nil {
    98  				cpp.Constraint = anyConstraint{}
    99  			}
   100  
   101  			pc[pr] = cpp
   102  		}
   103  	}
   104  
   105  	// Now override them all to produce a consolidated workingConstraint slice
   106  	combined := rd.ovr.overrideAll(pc)
   107  
   108  	type wccount struct {
   109  		count int
   110  		wc    workingConstraint
   111  	}
   112  	xt := radix.New()
   113  	for _, wc := range combined {
   114  		xt.Insert(string(wc.Ident.ProjectRoot), wccount{wc: wc})
   115  	}
   116  
   117  	// Walk all dep import paths we have to consider and mark the corresponding
   118  	// wc entry in the trie, if any
   119  	for _, im := range rd.externalImportList() {
   120  		if internal.IsStdLib(im) {
   121  			continue
   122  		}
   123  
   124  		if pre, v, match := xt.LongestPrefix(im); match && isPathPrefixOrEqual(pre, im) {
   125  			wcc := v.(wccount)
   126  			wcc.count++
   127  			xt.Insert(pre, wcc)
   128  		}
   129  	}
   130  
   131  	var ret []workingConstraint
   132  
   133  	xt.Walk(func(s string, v interface{}) bool {
   134  		wcc := v.(wccount)
   135  		if wcc.count > 0 {
   136  			ret = append(ret, wcc.wc)
   137  		}
   138  		return false
   139  	})
   140  
   141  	return ret
   142  }
   143  
   144  func (rd rootdata) combineConstraints() []workingConstraint {
   145  	return rd.ovr.overrideAll(rd.rm.DependencyConstraints().merge(rd.rm.TestDependencyConstraints()))
   146  }
   147  
   148  // needVersionListFor indicates whether we need a version list for a given
   149  // project root, based solely on general solver inputs (no constraint checking
   150  // required). Assuming the argument is not the root project itself, this will be
   151  // true if any of the following conditions hold:
   152  //
   153  //  - ChangeAll is on
   154  //  - The project is not in the lock
   155  //  - The project is in the lock, but is also in the list of projects to change
   156  func (rd rootdata) needVersionsFor(pr ProjectRoot) bool {
   157  	if rd.isRoot(pr) {
   158  		return false
   159  	}
   160  
   161  	if rd.chngall {
   162  		return true
   163  	}
   164  
   165  	if _, has := rd.rlm[pr]; !has {
   166  		// not in the lock
   167  		return true
   168  	}
   169  
   170  	if _, has := rd.chng[pr]; has {
   171  		// in the lock, but marked for change
   172  		return true
   173  	}
   174  	// in the lock, not marked for change
   175  	return false
   176  
   177  }
   178  
   179  func (rd rootdata) isRoot(pr ProjectRoot) bool {
   180  	return pr == ProjectRoot(rd.rpt.ImportRoot)
   181  }
   182  
   183  // rootAtom creates an atomWithPackages that represents the root project.
   184  func (rd rootdata) rootAtom() atomWithPackages {
   185  	a := atom{
   186  		id: ProjectIdentifier{
   187  			ProjectRoot: ProjectRoot(rd.rpt.ImportRoot),
   188  		},
   189  		// This is a hack so that the root project doesn't have a nil version.
   190  		// It's sort of OK because the root never makes it out into the results.
   191  		// We may need a more elegant solution if we discover other side
   192  		// effects, though.
   193  		v: rootRev,
   194  	}
   195  
   196  	list := make([]string, 0, len(rd.rpt.Packages))
   197  	for path, pkg := range rd.rpt.Packages {
   198  		if pkg.Err != nil && !rd.ig[path] {
   199  			list = append(list, path)
   200  		}
   201  	}
   202  	sort.Strings(list)
   203  
   204  	return atomWithPackages{
   205  		a:  a,
   206  		pl: list,
   207  	}
   208  }