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

     1  package gps
     2  
     3  // Manifest represents manifest-type data for a project at a particular version.
     4  // That means dependency constraints, both for normal dependencies and for
     5  // tests. The constraints expressed in a manifest determine the set of versions that
     6  // are acceptable to try for a given project.
     7  //
     8  // Expressing a constraint in a manifest does not guarantee that a particular
     9  // dependency will be present. It only guarantees that if packages in the
    10  // project specified by the dependency are discovered through static analysis of
    11  // the (transitive) import graph, then they will conform to the constraint.
    12  //
    13  // This does entail that manifests can express constraints on projects they do
    14  // not themselves import. This is by design, but its implications are complex.
    15  // See the gps docs for more information: https://github.com/sdboyer/gps/wiki
    16  type Manifest interface {
    17  	// Returns a list of project-level constraints.
    18  	DependencyConstraints() ProjectConstraints
    19  
    20  	// Returns a list of constraints applicable to test imports.
    21  	//
    22  	// These are applied only when tests are incorporated. Typically, that
    23  	// will only be for root manifests.
    24  	TestDependencyConstraints() ProjectConstraints
    25  }
    26  
    27  // RootManifest extends Manifest to add special controls over solving that are
    28  // only afforded to the root project.
    29  type RootManifest interface {
    30  	Manifest
    31  
    32  	// Overrides returns a list of ProjectConstraints that will unconditionally
    33  	// supercede any ProjectConstraint declarations made in either the root
    34  	// manifest, or in any dependency's manifest.
    35  	//
    36  	// Overrides are a special control afforded only to root manifests. Tool
    37  	// users should be encouraged to use them only as a last resort; they do not
    38  	// "play well with others" (that is their express goal), and overreliance on
    39  	// them can harm the ecosystem as a whole.
    40  	Overrides() ProjectConstraints
    41  
    42  	// IngoredPackages returns a set of import paths to ignore. These import
    43  	// paths can be within the root project, or part of other projects. Ignoring
    44  	// a package means that both it and its (unique) imports will be disregarded
    45  	// by all relevant solver operations.
    46  	//
    47  	// It is an error to include a package in both the ignored and required
    48  	// sets.
    49  	IgnoredPackages() map[string]bool
    50  
    51  	// RequiredPackages returns a set of import paths to require. These packages
    52  	// are required to be present in any solution. The list can include main
    53  	// packages.
    54  	//
    55  	// It is meaningless to specify packages that are within the
    56  	// PackageTree of the ProjectRoot (though not an error, because the
    57  	// RootManifest itself does not report a ProjectRoot).
    58  	//
    59  	// It is an error to include a package in both the ignored and required
    60  	// sets.
    61  	RequiredPackages() map[string]bool
    62  }
    63  
    64  // SimpleManifest is a helper for tools to enumerate manifest data. It's
    65  // generally intended for ephemeral manifests, such as those Analyzers create on
    66  // the fly for projects with no manifest metadata, or metadata through a foreign
    67  // tool's idioms.
    68  type SimpleManifest struct {
    69  	Deps, TestDeps ProjectConstraints
    70  }
    71  
    72  var _ Manifest = SimpleManifest{}
    73  
    74  // DependencyConstraints returns the project's dependencies.
    75  func (m SimpleManifest) DependencyConstraints() ProjectConstraints {
    76  	return m.Deps
    77  }
    78  
    79  // TestDependencyConstraints returns the project's test dependencies.
    80  func (m SimpleManifest) TestDependencyConstraints() ProjectConstraints {
    81  	return m.TestDeps
    82  }
    83  
    84  // simpleRootManifest exists so that we have a safe value to swap into solver
    85  // params when a nil Manifest is provided.
    86  //
    87  // Also, for tests.
    88  type simpleRootManifest struct {
    89  	c, tc, ovr ProjectConstraints
    90  	ig, req    map[string]bool
    91  }
    92  
    93  func (m simpleRootManifest) DependencyConstraints() ProjectConstraints {
    94  	return m.c
    95  }
    96  func (m simpleRootManifest) TestDependencyConstraints() ProjectConstraints {
    97  	return m.tc
    98  }
    99  func (m simpleRootManifest) Overrides() ProjectConstraints {
   100  	return m.ovr
   101  }
   102  func (m simpleRootManifest) IgnoredPackages() map[string]bool {
   103  	return m.ig
   104  }
   105  func (m simpleRootManifest) RequiredPackages() map[string]bool {
   106  	return m.req
   107  }
   108  func (m simpleRootManifest) dup() simpleRootManifest {
   109  	m2 := simpleRootManifest{
   110  		c:   make(ProjectConstraints, len(m.c)),
   111  		tc:  make(ProjectConstraints, len(m.tc)),
   112  		ovr: make(ProjectConstraints, len(m.ovr)),
   113  		ig:  make(map[string]bool, len(m.ig)),
   114  		req: make(map[string]bool, len(m.req)),
   115  	}
   116  
   117  	for k, v := range m.c {
   118  		m2.c[k] = v
   119  	}
   120  	for k, v := range m.tc {
   121  		m2.tc[k] = v
   122  	}
   123  	for k, v := range m.ovr {
   124  		m2.ovr[k] = v
   125  	}
   126  	for k, v := range m.ig {
   127  		m2.ig[k] = v
   128  	}
   129  	for k, v := range m.req {
   130  		m2.req[k] = v
   131  	}
   132  
   133  	return m2
   134  }
   135  
   136  // prepManifest ensures a manifest is prepared and safe for use by the solver.
   137  // This is mostly about ensuring that no outside routine can modify the manifest
   138  // while the solver is in-flight, but it also filters out any empty
   139  // ProjectProperties.
   140  //
   141  // This is achieved by copying the manifest's data into a new SimpleManifest.
   142  func prepManifest(m Manifest) SimpleManifest {
   143  	if m == nil {
   144  		return SimpleManifest{}
   145  	}
   146  
   147  	deps := m.DependencyConstraints()
   148  	ddeps := m.TestDependencyConstraints()
   149  
   150  	rm := SimpleManifest{
   151  		Deps:     make(ProjectConstraints, len(deps)),
   152  		TestDeps: make(ProjectConstraints, len(ddeps)),
   153  	}
   154  
   155  	for k, d := range deps {
   156  		// A zero-value ProjectProperties is equivalent to one with an
   157  		// anyConstraint{} in terms of how the solver will treat it. However, we
   158  		// normalize between these two by omitting such instances entirely, as
   159  		// it negates some possibility for false mismatches in input hashing.
   160  		if d.Constraint == nil {
   161  			if d.Source == "" {
   162  				continue
   163  			}
   164  			d.Constraint = anyConstraint{}
   165  		}
   166  
   167  		rm.Deps[k] = d
   168  	}
   169  
   170  	for k, d := range ddeps {
   171  		if d.Constraint == nil {
   172  			if d.Source == "" {
   173  				continue
   174  			}
   175  			d.Constraint = anyConstraint{}
   176  		}
   177  
   178  		rm.TestDeps[k] = d
   179  	}
   180  
   181  	return rm
   182  }