| parent | Code Howtos |
|---|
The structure and dependency management in the JabRef project uses the
Java Module System (JPMS)
as the primary system for defining modules and their dependencies. For a smooth integration of JPMS and Gradle's
dependency management, the org.gradlex.java-module-dependencies plugin, and the additional notations it provides, are
utilized. Therefore, the means to define dependencies differ from traditional Gradle-based Java projects.
For more background information please refer to:
- This video series on Modularity in Java (with Gradle)
- The documentation of the
org.gradlex.java-module-dependenciesplugin
With the Java Module System (JPMS), dependencies between modules are
defined in the src/main/java/module-info.java file
that each module contains. A dependency to another module is defined by a requires statement and the other module
is identified by its Module Name there. For example, a dependency to the
jablib module is expressed by requires org.jabref.jablib.
A dependency to the 3rd party library tools.jackson.databind is expressed by requires tools.jackson.databind.
Each dependency definition contains a scope
– e.g. requires or requires transitive. If you are unsure about a scope, use requires when adding a dependency.
Then execute ./gradlew checkAllModuleInfo which runs a dependency scope check that analyzes the code to determine
which Java types are visible (and should be visible) to which modules. If the check fails, it will advise you how to
change the scope.
In addition to the production code of a module (located in src/main/java)
modules also contain test code (located in src/test/java).
From the JPMS perspective, the test code is a separate module, and could have its own src/test/java/module-info.java
file.
However, it is not possible to treat tests as a separate module if they break the encapsulation of the main module.
This is the case if the tests need access to internals (like protected methods) and are therefore placed in the same
Java package as the main code. This is also referred to as whitebox testing. The JabRef project currently has such
a whitebox testing setup, where the tests are patched into the main module for test runtime. To still keep the
dependency notations consistent, without a separate test module-info, you define requires of the test code in the
build.gradle.kts file.
testModuleInfo {
requires("org.junit.jupiter.api")
}If you use a 3rd party module like tools.jackson.databind, a version for that module needs to
be selected. For this, the versions/build.gradle.kts
defines a so-called Gradle platform (also called BOM) that contains the versions of all 3rd party
modules used. If you want to upgrade the version of a module, do this here. If you need to use a new 3rd party module
in a src/main/java/module-info.java file, you need to add the version here.
(If the new module is not completely JPMS compatible, you may also need to add or modify
patching rules).
Some 3rd party libraries are not yet fully Java Module System (JPMS) compatible. And sometimes 3rd party modules pull
in other modules that are not yet fully compatible (which we may be able to exclude). Situations like this are treated
as wrong/incomplete metadata in this Gradle setup and the file
org.jabref.gradle.base.dependency-rules.gradle.kts
contains the rules to adjust or extend the metadata of 3rd party modules to address such problems.
If an issue in this area occurs after modifying dependency versions, you will see an error like this:
> Failed to transform javax.inject-1.jar (javax.inject:javax.inject:1) to match attributes {artifactType=jar, javaModule=true, org.gradle.category=library, org.gradle.libraryelements=jar, org.gradle.status=release, org.gradle.usage=java-api}.
> Execution failed for ExtraJavaModuleInfoTransform: caches/modules-2/files-2.1/javax.inject/javax.inject/1/6975da39a7040257bd51d21a231b76c915872d38/javax.inject-1.jar.
> Not a module and no mapping defined: javax.inject-1.jarIn these cases, first determine if adding the new 3rd party module is really needed/intended. If yes, there are three levels of mapping/patching that can be performed:
- Add missing mapping to
modules.properties: In case the module is a real Java module, but not found by Gradle, you need to put a mapping from the Module Name to the Maven Coordinates (group:name) intogradle/modules.properties. Furthermore, if it is a module available on Maven Central, file a pull request against java-module-dependencies/.../modules.properties - Add missing (or modify existing)
module-info.class: This is done through theorg.gradlex.extra-java-module-infoplugin. Often it is sufficient to add a simple entry for the affected library. For example, to address the error above, you can addmodule("javax.inject:javax.inject", "javax.inject")to theextraJavaModuleInfoblock. For more details, refer to the org.gradlex.extra-java-module-info plugin documentation. - Adjust metadata (POM file) of dependency:
This is required to solve more severe issues with the metadata of a library using the Gradle concept of
Component Metadata Rules. For a convenient definition of such rules, we use the
patchnotation provided by theorg.gradlex.jvm-dependency-conflict-resolutionplugin. For more details, refer to the org.gradlex.jvm-dependency-conflict-resolution plugin documentation.