Uploaded image for project: 'Maven'
  1. Maven
  2. MNG-5652

"supplies"/"provides"/"proffers" concept proposal

    Details

    • Type: New Feature
    • Status: Open
    • Priority: Major
    • Resolution: Unresolved
    • Affects Version/s: None
    • Fix Version/s: None
    • Component/s: FDPFC
    • Labels:
      None

      Description

      The exact name is still undecided. Some candidate names are: "supplies", "provides", and "proffers"

      "supplies" concept proposal

      ===========================

      Introduction

      ------------

      The following is a proposal for Maven in a post-modelVersion-4.0.0 era. The aim of this proposal is to simplify the management of dependency trees in the decentralised era of artifact production that we find ourselves in.

      The core issue is that different organisations can produce artifacts that may overlap. The easiest example is the servlet-api. If we restrict ourselves to version 2.5 of the servlet specification there are quite a few artifacts that all deliver the exact same content:

      • jetty:servlet-api:2.5-6.0.2
      • org.daisy.libs:servlet-api:2.5.0
      • org.mortbay.jetty:servlet-api-2.5:6.1.14
      • org.jboss.spec.javax.servlet:jboss-servlet-api_2.5_spec:1.0.1.Final
      • etc

      *Note:* this is a generic problem that is not restricted to the servlet-api, the servlet-api just provides the example that will be most familiar to everyone.

      So where these multiple artifacts supplying the equivalent content becomes a problem is when the dependency tree is being calculated. If you have two dependencies each declaring transitive dependencies on different artifacts that supply equivalent content, then you end up with two copies of the same JAR file in your classpath.

      In the case of the servlet-api, the hack most people use is to declare the servlet-api with scope `provided` thus preventing it from being transitive. This is, however, a hack. In a more ideal world it would be better to let the servlet-api be transitive and only when we get to the WAR module would we declare that a specific servlet-api is to be provided in the containers that the WAR is targets for deployment into.

      We can take a second example that does not have the luxury of a de facto hack.

      • javax.faces:jsf-api:2.1
      • org.jboss.spec.javax.faces:jboss-jsf-api_2.1_spec:2.1.28.Final
      • org.apache.myfaces.core:myfaces-api:2.1.13

      Now in the case of the JSF API, you are supposed to bundle the JSF API in your WAR file. So if I use three JSF component libraries, I could very well end up with three different but equivalent JSF API jars in my WAR file.

      Ideally what we want is some way of telling Maven that these artifacts are equivalent.

      Proposal
      --------

      Introduce the concept of "supplies" to the project model. The concept needs three changes to the project model:

      1. An explicit top level construct for a project to explicitly declare up-front artifacts that it knows - at the time the project is being authored - to contain equivalent content to at least a subset of the project's content. Declarations could include a claim from: `subset`, `superset`, `disjoint` and `equivalent` with the default being `disjoint`.
      2. An explicit sub-element of the `dependency` construct to allow consumers to post-facto declare a specific dependency as supplying equivalent content for other dependencies
      3. An extension to the `dependency/excludes/exclude` construct to allow consumers to remove claims a dependency makes with respect to supplying equivalent content

      By way of illustration, here are some examples of these constructs mapped to a Model Version 4.0.0 like XML schema. As the post-modelVersion-4.0.0 schema is not yet known, this represents the best way to illustrate how the concept will work, but note that this proposal does not suggest a schema for this concept.

      Example 1

      This illustrates how we would want, say, the `myfaces-api` project model to look.

      <project>
        <groupId>org.apache.myfaces.core</groupId>
        <artifactId>myfaces-api</artifactId>
        <version>2.1.3</version>
        ...
        <supplyManagement>
          <supplies>
            <groupId>javax.faces</groupId>
            <artifactId>jsf-api</artifactId>
            <version>[2.1,2.2)</version>
            <claim>superset</claim>
          <supplies>
          <supplies>
            <groupId>org.jboss.spec.javax.faces</groupId>
            <artifactId>jboss-jsf-api_2.1_spec</artifactId>
            <claim>equivalent</claim>
          <supplies>
        </supplyManagement>
        ...
      </project>
      

      This indicates that the myfaces-api artifact is intended to be useable as a drop-in replacement for either the javax.faces:jsf-api artifact within a bounded range or for any version of the org.jboss.spec.javax.faces:jboss-jsf-api_2.1_spec artifact. If you get a supplier conflict in your classpath, then Maven should fail the build.

      For example if somebody forked myfaces-api but did not list myfaces-api in the fork's supplyManagement and you end up with both myfaces-api and myfaces-fork-api in your classpath. Maven can detect that there are two dependencies that both claim to supply javax.faces:jsf-api and fail the build, thereby letting the user add either exclusions or additional supplies information to one of the artifacts and preventing duplicate artifacts on the classpath. The build need not be failed if the supplies claims provide a resolution. e.g. if the claim is equivalent then that implies that there is a 1:1 mapping and hence the artifacts are drop-in replacements. However where the claim is superset we cannot know that the extra content in our artifact is the same as the extra content in another artifact which has a superset of `javax.faces:jsf-api`.

      Example 2

      This illustrates a JSF component library that is working with the existing JSF APIs

      <project>
        ...
        <dependencies>
          <dependency>
            <groupId>javax.faces</groupId>
            <artifactId>jsf-api</artifactId>
            <version>2.1</version>
            <supplyManagement>
              <supplies>
                <groupId>org.jboss.spec.javax.faces</groupId>
                <artifactId>jboss-jsf-api_2.1_spec</artifactId>
                <claim>equivalent</claim>
              <supplies>
              <supplies>
                <groupId>org.apache.myfaces.core</groupId>
                <artifactId>myfaces-api</artifactId>
                <version>[2.1,2.2)</version>
                <claim>equivalent</claim>
              </supplies>
            </supplyManagement>
          <dependency>
        </dependencies>
        ...
      </project>
      

      In this case we are publishing a transitive dependency with additional supplyManagement injected. Consumers of this project would thus gain the benefit of collapsing their transitive dependencies for any of these three artifacts. As all artifacts are declared with `equivalent` claim, thus the nearest of those three artifacts to the project will win as per the standard dependency resolution rules when dealing with conflicting version requirements in the transitive dependency tree.

      Example 3

      Finally, there is the case where you need to correct an incorrect claim of supply

      <project>
        ...
        <dependencies>
          <dependency>
            <groupId>javax.faces</groupId>
            <artifactId>jsf-api</artifactId>
            <version>2.1</version>
            <exclusions>
              <exclusion>
                <groupId>org.jboss.spec.javax.faces</groupId>
                <artifactId>jboss-jsf-api_2.2_spec</artifactId>
                <scope>supplies</scope>
              <exclusion>
            </exclusions>
          <dependency>
        </dependencies>
        ...
      </project>
      

      This would typically be coupled with adding back in a correct supplies definition, but we need to allow for people to correct the graph after the fact of their dependencies being deployed to the remote repository.

      Claim conflict resolution

      The four classes of claim can be resolved using the following matrix

          A A A A
          subset equivalent superset disjoint
      B subset conflict A wins A wins conflict
      B equivalent B wins A or B A wins conflict
      B superset B wins B wins conflict conflict
      B disjoint conflict conflict conflict conflict

      The default unspecified claim is disjoint which indicates that some of the content is reproduced, but not all and there is additional content added. With such a claim there will always be conflict and the build should fail until the Project Model is updated to either remove some of the claims or resolve the dependency clash.

      The ideal claim is equivalent which indicates that the two artifacts are bi-directionally substitutable. This does not mean that the contents are identical. It does mean that they both deliver on the same contract in an equivalent fashion.

      The subset` and superset claims are for aggregation APIs. So for example the Java EE Web Profile API is a superset of the various spec APIs that make up the Java EE Web Profile and a subset of the full Java EE specification. The use of the subset` claim should be reserved to those cases that are strict subsets. If anything is added that is not in the supplied artifact then the correct claim is disjoint.

      Validation of supplies claims

      We do not want to introduce Java bias with this feature. As a result the validation of claims and supplies directives will be left to plugins. For the Java case we should probably provide either/both an enforcer rule or a maven hosted plugin to assist in checking JAR projects against the declared supplies declarations, but Maven core should not be concerned with solving the validation problem.

      Similarly, while there may be advantages in a more fine grained API contract negotiation between dependencies, to bind such a concept into the project model would significantly taint the Maven project model with more Java-centric concepts. Given that current software development typically uses at least two core languages: Java and JavaScript, we should be aiming to reduce Java-centric constructs from Maven rather than increase them.

      Backporting

      It will not be possible to fully express this concept in a modelVersion 4.0.0 project model. Thus if generating 4.0.0 compatible project models, the aim should be to fully resolve the dependencies of the project using all available information and express that as the transitive dependencies.

      Thus we will not expose the "supplies" information to modelVersion 4.0.0 parsers but we will expose the end results of that and present the final effective flattened dependency tree.

      modelVersion 4.0.0 consumers will thus be no worse off than they already are and those consumers understanding newer modelVersions can get the enhanced tree resolution that they would not get otherwise.

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                Unassigned
                Reporter:
                stephenc Stephen Connolly
              • Votes:
                1 Vote for this issue
                Watchers:
                3 Start watching this issue

                Dates

                • Created:
                  Updated: