I am working on a modular Java project that needs physical unit support. I am currently using http://www.uom.systems, but it only works in when I remove my module-info.java
file.
Here is my build.gradle.kts
:
plugins {
application
}
repositories {
mavenCentral()
}
dependencies {
implementation("systems.uom:systems-common:2.1")
implementation("systems.uom:systems-quantity:2.1")
}
application {
mainClass.set("unitsuser.Main")
}
My module-info.java
:
module unitsuser {
requires si.uom.units;
}
And my main class (Main.java
):
package unitsuser;
import javax.measure.Quantity;
import javax.measure.quantity.Length;
import tech.units.indriya.quantity.Quantities;
import tech.units.indriya.unit.Units;
public class Main {
public static void main(String[] args) {
Quantity<Length> len = Quantities.getQuantity(10.0, Units.METRE);
System.out.println(len);
}
}
My folder structure:
.
├── src/
│ └── main/
│ └── java/
│ ├── unitsuser/
│ │ └── Main.java
│ └── module-info.java
└── build.gradle.kts
When I run ./gradlew run
, I get this error:
error: module not found: javax.inject
I assumed this meant I had to add javax.inject
as a dependency, so I tried adding implementation("javax.inject:javax.inject:1")
. This caused the same error.
Edit:
The tech.units.indriya
dependency requires the module javax.inject
, but the artifact it's downloading to fulfill that dependency doesn't have a module-info
file. So it's relying on an automatic module being created with the name javax.inject
. My project is placing the javax.inject
package in the unnamed module, when it should be an automatic module. As described on this Grade doc page, Gradle puts all artifacts that don't specify to use automatic modules on the unnamed module. To work around this, they recommend using artifact transformations. Does this make sense, or is there a better approach?
CodePudding user response:
This error is a result of an invalid module configuration in the indriya dependency. It requires the module called javax.inject
, which is an automatic module generated from this artifact. It assumes the automatic module will be available, but the manifest in the jar doesn't have the Automatic-Module-Name
entry. Some build systems might not care and still generate an automatic module based on the jar filename. In the Gradle documentation:
A third case are traditional libraries that provide no module information at all — for example commons-cli:commons-cli:1.4. Gradle puts such libraries on the classpath instead of the module path. The classpath is then treated as one module (the so called unnamed module) by Java.
So Gradle doesn't support using legacy libraries with modular projects. We need to make sure there is a javax.inject
module available on the module path. To achieve this, we can do one of several things:
- Use artifact transformation to add automatic module entry to the manifest,
- Find another artifact that creates the
javax.inject
module, and add that as a dependency to the project, - Find another artifact that creates a module and create an alias to it with the correct module name, or
- Make a new Maven artifact with the correct module name and upload it to a repository.
Artifact transformation is difficult to do with Gradle since you need plugins and extra code to unzip the jar and edit it. It definitely could be done, but I don't want to try it. I was unable to find any artifacts online that used the javax.inject
module name. Most of them used java.inject
or something else. Since I also don't want to go through the hassle of creating a new project and uploading it to a repository, I'll use the third option.
This repo by GitHub user pustike is a fork of the same dependency injection library that adds a module called java.inject
that we can use to make an alias. Here are the steps to making this work with the project at hand:
Step 1: Multi-Project
Since we need multiple modules in the project, Gradle requires that we use a multi-project build. Your directory structure should look like this:
.
├── app/
│ ├── src/
│ │ └── main/
│ │ └── java/
│ │ ├── unitsuser/
│ │ │ └── Main.java
│ │ └── module-info.java
│ └── build.gradle.kts
├── injectwrapper/
│ ├── src/
│ │ └── main/
│ │ └── java/
│ │ └── module-info.java
│ └── build.gradle.kts
└── settings.gradle.kts
Contents of settings.gradle.kts
:
rootProject.name = "unitsuser"
include("app")
include("injectwrapper")
Step 2: Configure inject wrapper
The inject wrapper project is the alias to the java.inject
module. This must be added as a transitive dependency so the app
project has access to the artifacts. The contents of injectwrapper/build.gradle.kts
should be:
plugins {
`java-library`
}
repositories {
mavenCentral()
}
dependencies {
api("io.github.pustike:javax.inject:1.1.0")
}
Add the transitive dependency injectwrapper/src/main/java/module-info.java
:
module javax.inject {
requires transitive java.inject;
}
Step 3: Use the alias and uom dependencies in the main project app
:
Add the dependencies to uom-systems and injectwrapper
and main module and main class with app/build.gradle.kts
:
plugins {
`application`
}
repositories {
mavenCentral()
}
dependencies {
implementation(project(":injectwrapper"))
implementation("systems.uom:systems-common:2.1")
implementation("systems.uom:systems-quantity:2.1")
implementation("systems.uom:systems-unicode:2.1")
}
application {
mainModule.set("unitsuser")
mainClass.set("unitsuser.Main")
}
app/src/main/java/module-info.java
must require uom-systems:
module unitsuser {
requires si.uom.units;
}
Now that the module path is satisfied, running ./gradlew :app:run
now ouptuts 10 m
.