[jira] [Commented] (MNG-7855) Dependencies wrongly put on class-path rather than module-path

2023-08-26 Thread Martin Desruisseaux (Jira)


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

Martin Desruisseaux commented on MNG-7855:
--

Another area where being on the class-path versus module-path makes a big 
difference is when loading other JAR files dynamically (after JVM startup). In 
a class-path world, we use {{URLClassLoader}}. In a module-path world, 
{{URLClassLoader}} does not work anymore and we have to use {{ModuleLayer}} 
instead. The two approaches are very different.

For making {{ServiceLoader}} compatible with both class-path and module-path, 
duplicating the {{module-info}} information into {{META-INF/services/}} is 
problematic but still sometime doable (not always, because the two approaches 
are not fully equivalent). However duplicating a dynamic JAR loading capability 
with one framework based on {{ModuleLayer}} and another framework based on 
{{ClassLoader}} is much more difficult.

For avoiding such bug prone situations, it is critical to be able to put a 
library on the module-path no matter if the client application is modularized 
or not.


> 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 
> 

[jira] [Commented] (MNG-7855) Dependencies wrongly put on class-path rather than module-path

2023-07-28 Thread Martin Desruisseaux (Jira)


[ 
https://issues.apache.org/jira/browse/MNG-7855?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel=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