javagradlejavafxxmlbeansjlink

Gradle jlink plugin fails during createMergedModule with "the service implementation does not have a default constructor"


I'm using Gradle (4.10.3, but I've tried most versions up through 5.4.1) with JDK 12.0.1 and the org.beryx.jlink plugin (2.10.4), but am running into this error every time I attempt to create the jlink image:

-> Task :createMergedModule

Cannot derive uses clause from service loader invocation in: com/fasterxml/jackson/databind/ObjectMapper$2.run().

Cannot derive uses clause from service loader invocation in: com/fasterxml/jackson/databind/ObjectMapper.secureGetServiceLoader().

Cannot derive uses clause from service loader invocation in: org/apache/commons/compress/utils/ServiceLoaderIterator.().

C:\Users\MyName\IdeaProjects\myapp\build\jlinkbase\tmpjars\myapp.merged.module\module-info.java:393: error: the service implementation does not have a default constructor: XBeansXPath provides org.apache.xmlbeans.impl.store.PathDelegate.SelectPathInterface with org.apache.xmlbeans.impl.xpath.saxon.XBeansXPath;

C:\Users\MyName\IdeaProjects\myapp\build\jlinkbase\tmpjars\myapp.merged.module\module-info.java:394: error: the service implementation does not have a default constructor: XBeansXQuery provides org.apache.xmlbeans.impl.store.QueryDelegate.QueryInterface with org.apache.xmlbeans.impl.xquery.saxon.XBeansXQuery; 2 errors

-> Task :createMergedModule FAILED

When I click through to the lines throwing the errors in the merged module-info.java, it points to these two:

provides org.apache.xmlbeans.impl.store.PathDelegate.SelectPathInterface with org.apache.xmlbeans.impl.xpath.saxon.XBeansXPath;
provides org.apache.xmlbeans.impl.store.QueryDelegate.QueryInterface with org.apache.xmlbeans.impl.xquery.saxon.XBeansXQuery;

My build.gradle file looks like this:

plugins {
    id 'application'
    id 'idea'
    id 'java'
    id 'org.openjfx.javafxplugin' version '0.0.7'
    id 'org.beryx.jlink' version '2.10.4'
}

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.apache.commons:commons-csv:1.6'
    compile 'org.apache.poi:poi-ooxml:4.1.0'
    compile 'com.fasterxml.jackson.core:jackson-core:2.9.9'
    compile 'com.fasterxml.jackson.core:jackson-annotations:2.9.9'
    compile 'com.fasterxml.jackson.core:jackson-databind:2.9.9'
    testCompile 'org.junit.jupiter:junit-jupiter-api:5.5.0-M1'
    testRuntime 'org.junit.jupiter:junit-jupiter-engine:5.5.0-M1'
}

// ### Application plugin settings
application {
    mainClassName = "$moduleName/path.to.myapp"
}

// ### Idea plugin settings
idea {
    module {
        outputDir = file("out/production/classes")
    }
}

// ### Java plugin settings
sourceCompatibility = JavaVersion.VERSION_11

compileJava {
    options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}

test {
    useJUnitPlatform()
    testLogging {
        events 'PASSED', 'FAILED', 'SKIPPED'
    }
}

// ### JavaFX plugin settings
javafx {
    version = "12.0.1"
    modules = ['javafx.controls', 'javafx.fxml']
}

// ### jlink plugin settings
jlink {
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
    launcher {
        name = 'myapp'
    }
}

Any ideas on how I can address this?


Solution

  • The following possible solution is based just on your build file, without knowing about your module-info or the project's code, so it might not work.

    It is also based on this issue at the jlink plugin's repository.

    First, let me explain how this plugin works: since the (JDK) jlink tool doesn't allow non-modular dependencies, the plugin will try to collect all of them and create on the fly a module, that can be added to the module-path. This module is named $yourModuleName.mergedModule.

    Now I'm adding your dependencies to a simple JavaFX project. When I run ./gradlew jlink I get the same errors you have posted. If you check the errors:

    myapp\build\jlinkbase\tmpjars\myapp.merged.module\module-info.java:394: error

    This shows that the created module is named myapp.merged.module, and that it has a module-info descriptor. If you open it you will see all the exports (the module will export every package of the dependencies by default), the requires and the provides:

    open module myapp.merged.module {
        exports com.fasterxml.jackson.annotation;
        exports com.fasterxml.jackson.core;
        exports com.fasterxml.jackson.core.async;
        ...
        exports schemaorg_apache_xmlbeans.system.sXMLTOOLS;
        requires java.xml.crypto;
        requires java.logging;
        requires java.sql;
        requires java.xml;
        requires java.desktop;
        requires java.security.jgss;
        requires jdk.javadoc;
        uses java.nio.file.spi.FileSystemProvider;
        provides com.fasterxml.jackson.core.JsonFactory with com.fasterxml.jackson.core.JsonFactory;
        provides com.fasterxml.jackson.core.ObjectCodec with com.fasterxml.jackson.databind.ObjectMapper;
        provides org.apache.xmlbeans.impl.store.QueryDelegate.QueryInterface with org.apache.xmlbeans.impl.xquery.saxon.XBeansXQuery;
        provides org.apache.xmlbeans.impl.store.PathDelegate.SelectPathInterface with org.apache.xmlbeans.impl.xpath.saxon.XBeansXPath;
    }
    

    So now the question is: should you add some more requirements to your own module? The answer is explained here: that will increase unnecessary your project's size. A better way is to use the mergedModule script block:

    The mergedModule block allows you to configure the module descriptor of the merged module

    And:

    the module descriptor is created using the algorithm implemented by the suggestMergedModuleInfo task.

    If you run ./gradlew suggestMergedModuleInfo you will basically get the same contents for the merged module descriptor as above.

    A possible solution is to start just adding some requires to the merged module descriptor, and try again.

    I started with:

    jlink {
    
        mergedModule {
            requires "java.xml"
        }
    
        options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
        launcher {
            name = 'myapp'
        }
    }
    

    and I could run ./gradlew jlink successfully.

    If you check the merged module descriptor now, it will look like:

    open module myapp.merged.module {
        requires java.xml;
    
        exports com.fasterxml.jackson.annotation;
        exports com.fasterxml.jackson.core;
        exports com.fasterxml.jackson.core.async;
        ...
        exports schemaorg_apache_xmlbeans.system.sXMLTOOLS;
    }
    

    containing only the explicit requires, all the exports, but without provides, so the errors will be gone.

    Since I don't have your code, I can't say if this will work for you, but it did for me with the same dependencies.