Related error messages:
- Error: module-info.class not found for
module - Error: module
not found, required by - Error: automatic module cannot be used with jlink:
from
Problem and solution
The dependency is not ready to be integrated in a project that uses the module system. It does not have a module-info.
This can be solved in three steps:
- generating the module-info.java with jdeps
- compiling module-info.java into module-info.class with javac
- injecting the module-info.class in to the jar file
Solution on the commandline
Make sure that the directory that contains the jar file is actually in jlink's --module-path parameter.
The commands we need to turn the non-modular dependency in to a module look like the following:
jdeps --ignore-missing-deps --module-path <jar_dir_path> --add-modules <module_name --generate-module-info <out_dir_path> <jar_path>
javac --patch-module <module_name>=<jar_path> <module-info.java>
jar uf <jar_path> -C <module_name> <module-info.class>
Below we have the commands using jsoup as an example. "libs" is the directory containing the jar file, relative to the working directory.
jdeps --ignore-missing-deps --module-path libs --add-modules org.jsoup --generate-module-info libs/tmpOut libs/jsoup-1.15.4.jar
javac --patch-module org.jsoup=libs/jsoup-1.15.4.jar libs/tmpOut/module-info.java
jar uf libs/jsoup-1.15.4.jar -C libs/tmpOut/org.jsoup module-info.class
Note that jlink will not be able to see the newly created module if we don't have it in the --module path parameter.
As a gradle task
If this needs to be done as part of a proper build process it will take a bit more work, because it needs to be automated. I show my solution for gradle version 7.1 below. It starts by copying the resolved dependencies we need, based on a list. I ended up only needing to do this for jsoup, and the code below will need additional work to handle multiple dependencies.
task copySomeResolvedLibs() {
doFirst {
List<String> requestedLibs = Arrays.asList("jsoup-1.15.4.jar");
var outDirPath = Paths.get(project.projectDir.toPath().toAbsolutePath().toString(), 'build'+File.separator+'resolvedLibs'+File.separator)
String outDir = outDirPath.toString()
Files.createDirectories(outDirPath)
configurations.resolvableImpl.resolvedConfiguration.resolvedArtifacts.each {
if (requestedLibs.contains(it.getFile().getName())) {
var source = it.getFile().toPath().toAbsolutePath()
var target = Paths.get(outDir, it.getFile().getName()).toAbsolutePath()
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING)
}
}
}
}
task createModuleInfoForResolvedLibJsoup(type: Exec) {
dependsOn "copySomeResolvedLibs"
var dirPath = Paths.get(project.projectDir.toPath().toAbsolutePath().toString(), "build"+File.separator+"resolvedLibs"+File.separator);
var modInfoDirPathStr = Paths.get(dirPath.toString(), "tmpOut").toString()
var jarPathStr = Paths.get(dirPath.toString(), "jsoup-1.15.4.jar").toString();
group = "Execution"
description = "Will generate module.info for jsoup."
commandLine "jdeps", "--ignore-missing-deps", "--module-path", dirPath.toString(), "--add-modules", "org.jsoup", "--generate-module-info", modInfoDirPathStr, jarPathStr
}
task compileJsoupModuleInfo(type: Exec) {
dependsOn "createModuleInfoForResolvedLibJsoup"
var dirPath = Paths.get(project.projectDir.toPath().toAbsolutePath().toString(), "build"+File.separator+"resolvedLibs"+File.separator);
var modInfoPathStr = Paths.get(dirPath.toString(), "tmpOut"+File.separator+"org.jsoup"+File.separator+"module-info.java").toString()
var jarPathStr = Paths.get(dirPath.toString(), "jsoup-1.15.4.jar").toString();
group = "Execution"
description = "Will compile the module.info for jsoup."
commandLine "javac", "--patch-module", "org.jsoup="+jarPathStr, modInfoPathStr
}
task finalizeJsoupByInjectingModuleInfo(type: Exec) {
dependsOn "compileJsoupModuleInfo"
var dirPath = Paths.get(project.projectDir.toPath().toAbsolutePath().toString(), "build"+File.separator+"resolvedLibs"+File.separator);
var modInfoDirPathStr = Paths.get(dirPath.toString(), "tmpOut"+ File.separator+"org.jsoup").toString()
var jarPathStr = Paths.get(dirPath.toString(), "jsoup-1.15.4.jar").toString();
group = "Execution"
description = "Will inject module.info in to jsoup jar."
commandLine "jar", "uf", jarPathStr, "-C", modInfoDirPathStr, "module-info.class"
}
Make sure to actually call the final task, finalizeJsoupByInjectingModuleInfo, as part of the task that needs it. For example, using a dependsOn action:
task preparePackaging(type: Exec) {
dependsOn 'clean'
dependsOn 'build'
dependsOn 'finalizeJsoupByInjectingModuleInfo'
tasks.findByName('build').mustRunAfter 'clean'
tasks.findByName('finalizeJsoupByInjectingModuleInfo').mustRunAfter 'build'
...