[ 
https://issues.apache.org/jira/browse/MNG-7855?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17748556#comment-17748556
 ] 

Martin Desruisseaux commented on MNG-7855:
------------------------------------------

Java  code for generating workarounds has been added in the [test case GitHub 
repository|https://github.com/Geomatys/MavenModulepathBug], in the 
{{workaround}} sub-directory. That code parses de {{module-info.class}} entries 
of all specified JAR files and generates a {{META-INF/services/}} directory 
with all service providers found. If a service provider declares a public 
static {{provider()}} method, then the program also generates a {{java}} 
sub-directory with Java code for wrappers. Those wrappers redirect all methods 
of the service interface to the same methods of the provider obtained by a call 
to the {{provider()}} static method.

This workaround is of course unsatisfying, especially the wrappers which will 
not work in all cases.

> Dependencies wrongly put on class-path rather than module-path
> --------------------------------------------------------------
>
>                 Key: MNG-7855
>                 URL: https://issues.apache.org/jira/browse/MNG-7855
>             Project: Maven
>          Issue Type: Bug
>          Components: Dependencies
>    Affects Versions: 3.8.6, 4.0.0-alpha-7
>            Reporter: Martin Desruisseaux
>            Priority: Blocker
>         Attachments: MavenModulepathBug.zip
>
>
> When invoking Java tools such as {{java}} or {{javac}}, the project 
> dependencies can be declared either in a {{\--class-path}} or 
> {{\--module-path}} option. Maven choose automatically the module-path if all 
> the following conditions are true:
> # the dependency is modularized (i.e. contains a {{module-info.class}} file 
> or an {{Automatic-Module-Name}} attribute in {{MANIFEST.MF}}), and
> # the project using the dependency is itself modularized.
> Condition #1 is fine, but #2 is problematic. The fact that a dependency is 
> declared on the class-path rather than the module-path changes the way that 
> {{java.util.ServiceLoader}} discovers the provided services.
> * If the dependency is on the class-path, {{ServiceLoader}} scans the content 
> of {{META-INF/services}} directory.
> * If the dependency is on the module-path, {{ServiceLoader}} uses the 
> declarations in {{module-info.class}}.
> Even if condition #2 is false (i.e. a project is not modularized), 
> modularized dependencies still need to be declared on the module-path _for 
> allowing the dependency to discover its own services, or the services of a 
> transitive modularized dependency_. If a modularized dependency is put on the 
> class-path instead, it has consequence not only for the project using that 
> dependency, *but also for the dependency itself, which become unable to use 
> its own {{module-info.class}}*.
> h1. Demonstration
> The attached test case contains two Maven modules, named {{service}} and 
> {{client}}. The first Maven module declares a dummy services with 4 
> providers, named A, B, C and D. Providers A and D are declared in 
> {{module-info}}. Providers B and C are declared in {{META-INF/services}}. A 
> {{ShowMyServices}} class lists the services discovered by 
> {{java.util.ServiceLoader}}.
> The second Maven module has only a main method invoking {{ShowMyServices}}. 
> This second module intentionally has no {{module-info.java}} file. The use 
> case is a big module that we cannot modularize immediately (because 
> modularization brings stronger encapsulation, which requires significant 
> changes in the project to modularize), but still want to use modularized 
> dependencies. The test case can be run with {{mvn install}}. During test 
> execution, the following is printed:
> {noformat}
> Running test.client.MainTest
> Start searching for services...
> Provider B declared in META-INF.
> Provider C declared in META-INF.
> Done.
> The dependency has been loaded as an unnamed module.
> Consequently its `module-info` file has been ignored,
> and the `META-INF/services` directory is used instead.
> {noformat}
> The above test demonstrates that {{module-info}} has been ignored in the 
> context of JUnit test execution. The same behaviour happens also with {{mvn 
> exec:java}} executed in the {{client}} sub-directory.
> h2. Expected behaviour
> The Maven behaviour can be reproduced on the command-line as below (Linux 
> convention). This command put everything on the class-path:
> {code:bash}
> java --class-path service/target/service-1.0.jar:client/target/client-1.0.jar 
> test.client.Main
> {code}
> The expected behaviour can be reproduced with the following command-line. 
> This command put the modularized dependency on the module-path while keeping 
> the non-modularized client on the class-path:
> {code:bash}
> java --module-path service/target/service-1.0.jar --class-path 
> client/target/client-1.0.jar --add-modules ALL-MODULE-PATH test.client.Main
> {code}
> The latter command produces the following output:
> {noformat}
> Start searching for services...
> Provider A declared in module-info.
> Provider D declared in module-info.
> Done.
> The dependency has been loaded as named module. Great!
> This is what we need for the `module-info` to be used.
> {noformat}
> h1. Discussion
> Unless Maven provides configuration options that we did not see, the way that 
> Maven decides what to put on {{\--class-path}} and what to put on 
> {{\--module-path}} is a blocker issue for gradual modularisation of large 
> projects. This is because Maven choices break usages of 
> {{java.util.ServiceLoader}} in the dependencies themselves, which developers 
> may not control. The workaround for library developers is to declare all 
> service providers in both {{module-info}} and {{META-INF/services}}, with the 
> risk of inconsistencies. This workaround forces developers to renounce to the 
> usage of {{provider()}} static methods (which was making possible to use 
> singleton provider instances), because {{provider()}} static method works 
> only for providers declared in {{module-info}}. If the library developers 
> didn't applied such workaround, then the library users are blocked if they 
> are not in capacity to modularize their own project immediately (unless those 
> users are experts capable to create workarounds themselves).
> Ideally, developers should have explicit control on whether to put a 
> dependency on the class-path or module-path. There are scenarios where a 
> developer way want to force Maven to put a dependency on the module-path even 
> for a non-modularized module, for example if the developer really wants 
> automatic module. Conversely, forcing a modularized dependency to be on the 
> class-path may be useful for testing purposes, for example for replacing the 
> service providers declared in that module by patched services declared in 
> {{META-INF/services}} elsewhere (it does not need to be in the patched 
> module).
> h2. Proposal
> Keep current behavior unchanged as the default behavior. Add somewhere an 
> option for declaring how to handle a dependency. I suggest to do that in the 
> {{<dependency>}} block. For example:
> {code:xml}
> <dependency>
>     <groupId>foo</groupId>
>     <artifactId>bar</artifactId>
>     <module>true</module>    <!-- Allowed values: true, false, auto -->
> </dependency>
> {code}
> More JPMS-related options could be added in the future. For example an 
> {{<imports>}} block meaning "when using this dependency, add 
> {{\--add-exports}} statement from this project to that dependency for the 
> packages listed inside this {{<imports>}} block. That would be useful for 
> JUnit testing among other. But this is not the purpose of the JIRA issue.
> *References:*
> * [Test case on GitHub|https://github.com/Geomatys/MavenModulepathBug]
> * [Post on user mailing 
> list|https://lists.apache.org/thread/2kn195rrpxnw2t2bvxk4drzoo0c04959]



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to