Slackbot
10/10/2023, 6:06 AMVampire
10/10/2023, 5:46 PMif anYes and no. If your project is a JPMS module and you didn't disable the module-path inferring, then explicit modules and also automatic modules with andependency contains an automatic module (e.g., Undertow, XNIO), then AFAIK, Gradle does NOT include that module on the module path by default.implementation
Automatic-Module-Name
in the manifest are put on the module path. JARs that have no knowledge about JPMS at all and would just be automatic modules with a module id derived from the jar name are put on the classpath.
(And while I don't fully understand the reasons why, I can vaguely infer that this is the correct default behavior.)That's highly disputable. Especially automatic modules without an explicit automatic module name are very problematic and should imho never be depended on from a proper JPMS module. You just need a different jar name and your definition breaks. Or a newer version is a proper module or gets an explicit automatic module name and your definiton breaks, .... For automatic modules without explicit automatic module name, I think putting them on the classpath is the better default choice.
But let's say that I want to include those two modules on the module path, so that my own module can require them. How do I do that?Again, better don't do it. But if you really must, just configure it manually. Exclude them from the classpath and use a jvm arg or jvm argument provider to set the
--module-path
explicitly instead of letting Gradle infer it.
Or you can use for example the plugin https://github.com/gradlex-org/extra-java-module-info to transform the non-JPMS jar to a proper module on-the-fly which might be the better option if you really need to do it.Mike Wacker
10/10/2023, 6:28 PMpackage org.example;
import org.immutables.value.Value;
@Value.Immutable
public interface Empty {}
If you have Guava as a dependency in your project, the generated code will use some annotations from the automatic module jsr305
. (There are ways to work around it, but not anything I would expect the typical Immutables user to know.) If you put requires jsr305;
in module-info.java
, though, you will get a "module not found" compilation error.
I just wish there was an easy way to say, "I'm OK including jsr305
from com.google.code.findbugs:jsr305
in the module path, even though it's an automatic module." Ditto if I want to require undertow.core
or xnio.api
.
(Re: extra-java-module-info, it could work, but in practice, if you merely enable the plugin, you will immediately get a bunch of "Not a module and no mapping defined:" errors for transitive dependencies. Which isn't the ideal developer experience.)Mike Wacker
10/10/2023, 6:31 PMVampire
10/10/2023, 6:40 PMI just wish there was an easy way to say, "I'm OK includingI already told you two easy ways. 1. just define the module-path yourself instead of using the Gradle default 2. use the plugin I mentioned Both are easy to use ways. Gradle is convention-over-configuration with usually sane defaults, but providing the power to easily adapt to your needs, so just use that power. :-)fromjsr305
in the module path, even though it's an automatic module." Ditto if I want to requirecom.google.code.findbugs:jsr305
orundertow.core
.xnio.api
(Re: extra-java-module-info, it could work, but in practice, if you merely enable the plugin, you will immediately get a bunch of "Not a module and no mapping defined:" errors for transitive dependencies. Which isn't the ideal developer experience.)Well, if that's the case, maybe open a feature request to that plugin to improve developer experience. 😉
I also vaguely recall that Maven will include automatic modules in the module path by defaultWell, quite possible that it does. Wouldn't be the first non-sense default that Maven uses where Gradle learned from and did it better. 😉
but it should be easy to override the default and put automatic modules on the module pathIt is, I told you two ways. Also, again, proper automatic modules are put on the module path by default. Just the implied automatic modules are not, as it does not make much sense and can also easily break the JARs logic. Due to the different handling of JARs on the classpath vs. module path, it can easily happen that it works on one but not on the other. What you ask for is to simply put everything on the module path and cross fingers that nothing breaks by doing that. And you can easily do that, just set the classpath to empty and put the wohle
runtimeClasspath
on the module path and you are done.
Well, except for the finger-crossing that nothing breaks by doing that.Mike Wacker
10/10/2023, 7:36 PMbuildSrc
...
extraJavaModuleInfo {
automaticModule("jsr305", "com.google.code.findbugs:jsr305")
}
...and then requires jsr305;
would just work. (And ideally, some Gradle documentation would explain how you could shoot yourself in the foot if you do this.)
(2) isn't easy for the reasons I explained; just enabling the plugin creates build errors for transitive dependencies. Which means you could also get new errors any time you add a dep...
(1) I've tried but haven't gotten it to work; is it supposed to be like this in buildSrc
? (I used this as a reference.)
tasks.withType<JavaCompile> {
doFirst {
options.compilerArgs.addAll(listOf("--module-path", classpath.asPath))
classpath = files()
}
}
But when you're asking developers to manage the classpath and/or module path themselves, I also wouldn't call that an easy option. (Easy is written from the perspective of a typical Gradle user, not a Gradle expert.) Also, this option seems to be akin to giving developers a blowtorch which puts everything on the module path, as opposed to a scalpel which adds specific implied automatic modules to the module path.Vampire
10/10/2023, 8:23 PMEasy in my mind would be, e.g., adding a plugin that requires very simple configuration inWhich is exactly what the plugin I told you is doing. It even helps you finding all cases to define by erroring out for plain jars that do not have an automatic module name. This is imho very user-friendly as you cannot forget to define some. And I told you, if you think differently, open a feature request to that plugin to have the option to change that behavior....buildSrc
And ideally, some Gradle documentation would explain how you could shoot yourself in the foot if you do this.That's not in the scope of Gradle documentation, but in the scope of JPMS documentation.
(2) isn't easy for the reasons I explained; just enabling the plugin creates build errors for transitive dependencies. Which means you could also get new errors any time you add a dep...Matter of point of view. The errors for transitive dependencies are imho good, because if you want to fully mularize your application, you should take care of all legacy JARs. By putting everything on the module-path regardless, you anyway do the same with a big hammer. But as I said, open a feature request to that plugin for a switch to disable that behavior.
(1) I've tried but haven't gotten it to work; is it supposed to be like this inDon't change the configuration of a task during its execution time, that is a big no-go, can cause a lot of trouble, and also is not allowed with configuration cache if you ever hope to use it.? (I used this as a reference.)buildSrc
But when you're asking developers to manage the classpath and/or module path themselves, I also wouldn't call that an easy option.I didn't ask you to do anything. I recommended to not depend on an implied automatic module, as it causes all sorts of trouble and you read everywhere not to do it. If you want to do it anyway, you shouldn't wonder that there is not an easy switch to shoot yourself in the foot. But at least Gradle gives you the power to easily shoot yourself if you insist, unlike for example Maven. And for more convenience, there are 3rd party plugins like the one I recommended.
Also, this option seems to be akin to giving developers a blowtorch which puts everything on the module path, as opposed to a scalpel which adds specific implied automatic modules to the module path.Yes, that's exactly what you asked for. To put all automatic modules on the module path. Which means each and every jar. Because every jar that is not an explicit module can be an automatic module, simply by putting it on the module path. That might break it's behavior and make it unusable, but well, that's the risk you want to take. If you do not want to put everything on the module path, but only specific things, then just put the things you want on the module path there and keep the rest on the classpath. That's the benefit of the power Gradle gives you, you can decide how you want it. That this is not "easy for a Gradle noob", might be. But a Gradle noob should probably just live with the sane defaults and should not follow the very dangerous practice to depend on implied automatic modules. If someone is keen and savvy enough to decide to do that, he should also be able to configure Gradle to do what he wants, or otherwise rethink of whether it really makes sense what he tries there. Btw. the blowtorch-approach you tried should probably more be something like
tasks.compileJava {
classpath = files()
options.compilerArgumentProviders.add(
object : CommandLineArgumentProvider {
@CompileClasspath
val compileClasspath = sourceSets.main.map { it.compileClasspath }
override fun asArguments() = listOf(
"--module-path",
compileClasspath.get().asPath
)
}
)
}
Vampire
10/10/2023, 8:28 PMMike Wacker
10/10/2023, 9:32 PM@Value.Immutable
interfaces (and generate their implementations).
3. It needs Google Guava as a dependency.
Here's a minimalistic Java file to test this out with (note that the generated code is different depending on whether Guava is present or not):
package org.example;
import org.immutables.value.Value;
@Value.Immutable
public interface Empty {}
Here's the corresponding `module-info.java`:
module org.example {
requires static org.immutables.value;
requires com.google.errorprone.annotations;
requires jsr305;
}
Now I only have a passing knowledge of Maven (I let IntelliJ auto-generate the pom.xml file and hacked it from there), but it didn't take me very long to get this code to build with Maven (with the Guava dep included):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="<http://maven.apache.org/POM/4.0.0>"
xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
xsi:schemaLocation="<http://maven.apache.org/POM/4.0.0> <http://maven.apache.org/xsd/maven-4.0.0.xsd>">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>maven-module</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.immutables</groupId>
<artifactId>value</artifactId>
<version>2.10.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.2-jre</version>
</dependency>
</dependencies>
</project>
Maybe Maven is doing some "impure" things w.r.t. implied automatic modules, but it just works out-of-the-box.
Now try getting that same code to compile with Gradle. It's a pain-in-the-ass to get this dead-simple example to work, much less a real-world project.
I'm not saying that you don't have valid points on why Gradle's defaults are correct for implied automatic modules. But at some point, demanding the perfect solution--as opposed to making it dead-easy to add implied automatic modules to the module path--will just encourage people to either not use Java modules or to use Maven instead.Vampire
10/10/2023, 9:58 PMThe blowtorch option seems to work with modules, but if a project doesn't have a module, it blows up.Not sure what you mean by "blow up". But either way, also not sure why you should use that snippet if you don't want its effect. Just use it if you need it and you are fine - well as fine as that approach will work anyway.
That's a non-starter if you're incrementally migrating multi-module Gradle builds to use Java modules.Then do not put it into the convention plugin you apply to all projects, but have a separate convention plugin for already modular projects where it is included. That's anyway how you use convention plugins. Or if you really don't like that, add a check for the presence of
module-info.java
in the source files and only do it if it is there for example.
as opposed to making it dead-easy to add implied automatic modules to the module pathWell, you should really never ever do that anyway, no matter whether you use Gradle, or Maven, or Ant, or Bazel, or plain javac, or whatever else. You will only get much problems with doing so, so just don't do it and you are fine. If people insist on doing that and for that reason decide to not use JPMS, that is then maybe the exact right decision. Because you try to use an outdated library that is not JPMS-compatible with JPMS. That is like when you try to put E10 gasoline into an oldtimer motor. That would be a very bad idea and not work out too well at all. It would be a much better decision not to use E10, but keep on using E5 even if E10 would generally be the better choice. Besides that, I already told you that such a "dead-easy shoot yourself in the head" switch is most probably coming with the linked issue.
It's a pain-in-the-ass to get this dead-simple example to workWhy? It cost me approximately 5 minutes to ramp up a working build. And most of that time was for creating files and copy&paste.
Vampire
10/10/2023, 9:59 PMVampire
10/10/2023, 10:00 PMplugins {
`java-library`
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
}
dependencies {
compileOnly("org.immutables:value:2.10.0")
annotationProcessor("org.immutables:value:2.10.0")
implementation("com.google.guava:guava:32.1.2-jre")
}
tasks.compileJava {
classpath = files()
options.compilerArgumentProviders.add(
object : CommandLineArgumentProvider {
@CompileClasspath
val compileClasspath = sourceSets.main.map { it.compileClasspath }
override fun asArguments() = listOf(
"--module-path",
compileClasspath.get().asPath
)
}
)
}
Mike Wacker
10/11/2023, 1:33 AMVampire
10/11/2023, 3:26 AMAgain, "easy" should be from the perspective of the average Gradle user, not from an expert.Also "again", it should imho not be easy, as you shoot yourself in the foot. It should not be done. If you need to use non-modular JARs, then don't write modules. If you want to write modules, use modular replacements or make the projects release modular jars instead. Besides that I already referred you multiple times to the issue that will make it dead simple to shoot your foot like hat in the future unfortunately.
When they run into the same issue for the first time, the average Gradle user won't instantly identify the problemThe question is not about the average Gradle user, because the average Gradle users does not write JPMS modules anyway. Be that good or not is a different topic. The question is about someone wanting to write a JPMS module. Someone wanting to write a JPMS module should have some knowledge about JPMS. Such a person should also be aware of the differences of classpath and module path. The average of such a person should usually also be able to understand where that error comes from, especially when he did the very questionable decision to depen on an implied automatic module, which usually should already be a sign that he even has above average knowledge of JPMS. And especially as Gradle clearly documents which JARs are put to the classpath and which are put to the module path.
and come up with the solution that you did in 5 minutes.You said that you are not able to get it running, or that it is PITA, even after I gave you the code, which is just wrong.
In any real-world project, it's simply not a realistic constraint to tell people that they can only take deps that use a named/explicit-automatic module if they want to use Java modules themselves.Sure it is, that is how JPMS is designed. That it has this emergency fallback for plain jars on the module path was in my eyes a great misdecision. Well, that decision was made and gives you the power to use it. But great power comes with great responsibility.
the arguments in favor of Maven (or for abandoning Java modules) are pretty simple: a list of every useful dependency that doesn't produce a named/explicit-automatic module.And abandoning Java modules is the exact right way in that case. And in parallel encouraging those dependencies to make their jars JPMS modules. You also cannot just put any jar on the module path. If you for example have split packages, you cannot put the jar on the module path. If you use service loader mechanism, you cannot put the jar on the module path. If you use deep reflection, you cannot put the jar on the module path. ...... Well, I told you multiple ways how to mitigate the defaults you don't like. And also tried to explain to you why - in my opinion - the Gradle folks decided to do it like that and that it makes sense, even if you don't want to agree. I even told you that it will get easier to follow that horrible practice in the future. Our conversation ended up in a cycle where you just repeat yourself constantly, so I'll break the cycle now and just stop writing here unless something new is said. 🙂
Mike Wacker
10/11/2023, 4:02 AMVampire
10/11/2023, 4:09 AMMike Wacker
10/12/2023, 7:56 PMVampire
10/12/2023, 8:00 PMMike Wacker
10/12/2023, 8:04 PMVampire
10/12/2023, 8:07 PMVampire
10/12/2023, 8:08 PMMike Wacker
10/12/2023, 8:21 PMVampire
10/12/2023, 8:24 PMMike Wacker
10/12/2023, 8:59 PMVampire
10/12/2023, 9:09 PMAgain, we've established that these are your own person opinions of the JPMS, not those of Project Jigsaw. I abide by the latter.That you shouldn't put those jars on the module path? That is not my opinion, that is established best practice that you can read everywhere. I've also seen that recommended multiple times in various places, and it just makes sense. If you personally do not agree, that's your decision. But it is also not the opinion of project Jigsaw that you should do so. That they purely provide the possibility does in no means mean that it is recommended to use it like that. There are many such examples too. There is
subprojects { ... }
, allprojects { ... }
, project(...) { ... }
, afterEvaluate { ... }
and some other things in Gradle for example.
But their pure existence does not mean it is recommended to use them. It is even officially discouraged to use them.
Or there are sun.*
classes in the JDK, but still it is discouraged to use them.
...
So no, you are not following the opinion of Jigsaw, you just follow your own opinion of how to do things, even if they are against best practices
... and that is fine for you.
You were the one who criticized me for filing a "duplicate" issueI didn't critize, I just asked you why you did it when I already gave you the other. Obviously, you do not agree that it is a duplicate. But honestly, I don't care too much, that's not my cup of tea. We will see how the Gradle folks decide. I just try to avoid waisting their time with needing to care about duplicates, as their resources are already constrained enough sadly.
Mike Wacker
10/12/2023, 9:24 PMequals()
but not `hashCode()`; the answer is simple: you should override both. There's no downside aside from a little extra work to implement hashCode()
. Since no significant tradeoffs exist, you should just do the right thing.
In the ideal world, yes, a modular project should not depend on an implied automatic module. In the real world, though, what do you do when many popular dependencies produce an implied automatic module? If we've established that the goal is that the JPMS "helps developers at all levels be more productive," per Oracle, what is the right set of tradeoffs?Vampire
10/12/2023, 9:35 PMequals
you MUST override hashCode
or you violate the contract.
There is no should here, if you don't do it, that is simply a bug.
what do you do when many popular dependencies produce an implied automatic module?If they are that popular, it shouldn't be a problem to make them proper modules or at least define an automatic module name if the jar otherwise is JPMS compatible, because then it is just a one-line change. If a dependency by now does not even have an automatic module name, it cannot be that popular, or is unmaintained and should thus not be used anyway. And besides that, again, for those insisting of depending on those from a module, they can configure Gradle to do what they want. And even if Jigsaw aims to help developers at all levels, this does not imply in any way that Gradle should do the same by having non-sense defaults like Maven does. 😉
Mike Wacker
10/19/2023, 8:32 PM[WARNING] *********************************************************************************************************************************************************************************************************************
[WARNING] * Required filename-based automodules detected: [jsr305-3.0.2.jar, undertow-core-2.3.9.Final.jar, xnio-api-3.8.8.Final.jar, javax.inject-1.jar]. Please don't publish this project to a public artifact repository! *
[WARNING] *********************************************************************************************************************************************************************************************************************
If you search for "Required filename-based..." on Google, you will get some reasonable search results which will help educate you on that topic.
If they are that popular, it shouldn't be a problem to make them proper modules or at least define an automatic module name if the jar otherwise is JPMS compatible, because then it is just a one-line change.
If a dependency by now does not even have an automatic module name, it cannot be that popular, or is unmaintained and should thus not be used anyway.This statement is so ridiculous and out-of-touch that it helped convince me to switch to Maven. Blaming developers because they didn't bend each and every one of their dependencies to your will is just a patently unrealistic expectation. Also, if you look at that warning, it includes
javax.inject
. So now you're telling me that javax.inject
is a dependency that "should thus not be used anyway"? Not saying I'm a fan of Maven over Gradle, but that's a great way to advertise for Maven.Vampire
10/20/2023, 3:38 PMjavax.inject
. So now you're telling me that javax.inject
is a dependency that "should thus not be used anyway"?
Well, you have at least the options described above.
But yes, that artifact is 14 years old, so no wonder it knows nothing about JPMS.
Fixing that would be trivial.
But besides that, it is superseded by Jakarta Inject API anyway.
And in this case it also is rather unproblematic anyway if you only annotate your code with these and not read them. You can simply requires static ...
it, as you do not have a runtime dependency on them and there use the implied automatic module name after adding that to the module path, or an own one assigned by using the above described plugin. The real problems with implied automatic modules just start when you not have requires static
but just requires
or requires transitive
.
> but that's a great way to advertise for Maven.
I don't know in which way my personal opinion on this topic has any influence to Gradle vs. Maven.
This has nothing to do with Maven vs. Gradle.
It is generally about putting implied automatic modules on the classpath or module path, no matter what build tool you use or wheter you use one at all.Mike Wacker
10/21/2023, 12:09 AMjavax.inject:javax.inject
changed. But Dagger does not yet support jakarta; these sorts of migrations don't happen instantaneously in the real word. Sure, I guess I could fork Dagger (or not use Dagger, or do whatever other option you just toss over the wall). The problem there is not that I don't understand how to fork Dagger; the problem is if you're going to make me fork Dagger , I'll just switch to Maven, where things just work without a fork (though an appropriate warning is generated). Which is exactly what I did.Vampire
10/22/2023, 10:02 PMMike Wacker
10/23/2023, 2:07 AMVampire
10/23/2023, 12:39 PMafter I explained why that solution did not work.I'm sorry if I missed that, as you then directly skipped to other details. But here we talk about Gradle for which it works, not about IDE bugs that you should report to JetBrains. But actually you can just not do it during IntelliJ sync and it works as I wrote in the issue you created.
that sort of professional criticism is a personal insult or a case of bullying, that's your problem, not mine...Of course professional and constructive criticism like "that does not work for me because ..." is not what I meant. But things like saying I'm living in a hermetic bubble and have no idea about realistic situations and real-world problems are pretty personal attacks and insults. If you don't see that, that is not my problem, but yours. 😉
when a customer is frustrated with your product, and those frustrations cause them to switch to a competitorGradle is not my product. I'm in no way affiliated to Gradle. I'm just a user like you, trying to help fellow Gradle users like you in my sparse spare time.