Search code examples
javagradlebuild.gradlejava-9java-platform-module-system

Gradle: Building a modularized library that is compatible with Java 8


So Java 9 is there, soon to be followed by Java 10. Time we should make our libraries ready for use in Java 9 projects. I did it in the following way:

  1. provide a module-info.java
  2. added the (experimental) jigsaw plugin in build.gradle
  3. Manually made changes according to the guide on the gradle site instead of using the jigsaw plugin.

So far, both approaches work fine, and I can use the generated Jar in Java 9 projects.

The problem is, the resulting Jar is not compatible with Java 8 although I used no Java 9 features except the module-info.java. When I set targetCompatibility = 8, an error message tells me to also set sourceCompatibility = 8 accordingly. Which then rejects the module-info.java for which I should set sourceCompatibility = 9.

How can this be solved?

I removed the jigsaw plugin again, and tried this, but am stuck:

  1. set sourceCompatibility = 8 and targetCompatibility = 8
  2. create a new source set moduleInfo that contains the single file module-info.java
  3. set sourceCompatibility = 9 and targetCompatibility = 9 for the new sourceset

Now compilation works, and Gradle uses Java 9 when it tries to compile the module-info.java. However, modules (in this case log4j) are missing, and I get this error:

:compileJava UP-TO-DATE
:processResources NO-SOURCE
:classes UP-TO-DATE
:jar UP-TO-DATE
:sourcesJar UP-TO-DATE
:assemble UP-TO-DATE
:spotbugsMain UP-TO-DATE
:compileModuleInfoJava
classpath:
compilerArgs: [--module-path, , --add-modules, ALL-SYSTEM]
D:\git\utility\src\module-info\java\module-info.java:14: error: module not found: org.apache.logging.log4j
    requires org.apache.logging.log4j;
                               ^
warning: using incubating module(s): jdk.incubator.httpclient
1 error
1 warning
:compileModuleInfoJava FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':compileModuleInfoJava'.
> Compilation failed; see the compiler error output for details.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1s
5 actionable tasks: 1 executed, 4 up-to-date

This is the build.gradle used (Gradle version is 4.5.1):

plugins {
  id "com.github.spotbugs" version "1.6.0"
}

apply plugin: 'maven'
apply plugin: 'maven-publish'
apply plugin: 'java-library'
apply plugin: 'com.github.spotbugs'

sourceCompatibility = 8
targetCompatibility = 8

group = 'com.dua3.utility'

repositories {
    mavenLocal()
    jcenter()
}

dependencies {
  compile     group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.10.0'
  testRuntime group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.10.0'

  // Use JUnit test framework
  testImplementation 'junit:junit:4.12'
}

ext.moduleName = 'com.dua3.utility' 

sourceSets {
    moduleInfo {
        java {
            srcDir 'src/module-info/java'            
        }
    }
}

compileModuleInfoJava {
    sourceCompatibility = 9
    targetCompatibility = 9

    inputs.property("moduleName", moduleName)

    doFirst {
        options.compilerArgs = [
            '--module-path', classpath.asPath,
            '--add-modules', 'ALL-SYSTEM'
        ]
        classpath = files()  
        System.out.println("classpath: "+classpath.asPath)
        System.out.println("compilerArgs: "+options.compilerArgs)
    }
}

tasks.withType(com.github.spotbugs.SpotBugsTask) {
    reports {
        xml.enabled false
        html.enabled true
    }
}

task sourcesJar(type: Jar, dependsOn: classes) {
    classifier = 'sources'
    from sourceSets.main.allSource
}

task javadocJar(type: Jar, dependsOn: javadoc) {
    classifier = 'javadoc'
    from javadoc.destinationDir
}

artifacts {
    archives sourcesJar
// fails with jigsaw:    archives javadocJar
}

defaultTasks 'build', 'publishToMavenLocal', 'install'

And this is module-info.java:

module com.dua3.utility {
    exports com.dua3.utility;
    exports com.dua3.utility.io;
    exports com.dua3.utility.jfx;
    exports com.dua3.utility.swing;
    exports com.dua3.utility.lang;
    exports com.dua3.utility.math;
    exports com.dua3.utility.text;

    requires javafx.controls;
    requires javafx.web;
    requires java.xml;
    requires java.desktop;
    requires org.apache.logging.log4j;
}

Solution

  • OK, I finally got it working. In case anyone else wants to know how to do it, this is what I have done:

    • set the Java version to 8, so that the library will be usable by Java 8 applications:

      sourceCompatibility = 8
      targetCompatibility = 8

    • configure the module name

      ext.moduleName = com.dua3.utility

    • add a new sourceset consisting only of module-info.java:

       sourceSets {
              moduleInfo {
                  java {
                      srcDir 'src/module-info/java'            
                  }
              }
          }
      
    • set compatibility to Java 9 for the moduleInfo, sourceSet, configure modules, and set the output directory:

       compileModuleInfoJava {
          sourceCompatibility = 9    
          targetCompatibility = 9
      
      inputs.property("moduleName", moduleName)
      
      doFirst {
          classpath += sourceSets.main.compileClasspath
      
          options.compilerArgs = [
              '--module-path', classpath.asPath,
              '--add-modules', 'ALL-SYSTEM,org.apache.logging.log4j',
              '-d', sourceSets.main.output.classesDirs.asPath
          ]
      }
      }
      
    • configure the jar task to include moduleInfo:

      jar 
      {
        from sourceSets.main.output
        from sourceSets.moduleInfo.output
      }
      

    In case you are using the SpotBugs plugin, you also have to configure the sourceSet explicitly because it will otherwise fail when it tries to process the ModuleInfo sourceSet.

    I finally ended up with this version of build.gradle:

    plugins {
      id "com.github.spotbugs" version "1.6.0"
    }
    
    apply plugin: 'maven'
    apply plugin: 'maven-publish'
    apply plugin: 'java-library'
    apply plugin: 'com.github.spotbugs'
    
    sourceCompatibility = 8
    targetCompatibility = 8
    
    group = 'com.dua3.utility'
    
    repositories {
        mavenLocal()
        jcenter()
    }
    
    dependencies {
      compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.10.0'
      testRuntime group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.10.0'
    
      // Use JUnit test framework
      testImplementation 'junit:junit:4.12'
    }
    
    ext.moduleName = 'com.dua3.utility' 
    
    sourceSets {
        moduleInfo {
            java {
                srcDir 'src/module-info/java'            
            }
        }
    }
    
    compileModuleInfoJava {
        sourceCompatibility = 9
        targetCompatibility = 9
    
        inputs.property("moduleName", moduleName)
    
        doFirst {
            classpath += sourceSets.main.compileClasspath
    
            options.compilerArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'ALL-SYSTEM',
                '-d', sourceSets.main.output.classesDirs.asPath
            ]
        }
    }
    
    jar 
    {
        from sourceSets.main.output
        from sourceSets.moduleInfo.output
    }
    
    spotbugs {
        sourceSets = [sourceSets.main]
    }
    
    tasks.withType(com.github.spotbugs.SpotBugsTask) {
        reports {
            xml.enabled false
            html.enabled true
        }
    }
    
    task sourcesJar(type: Jar, dependsOn: classes) {
        classifier = 'sources'
        from sourceSets.main.allSource
    }
    
    task javadocJar(type: Jar, dependsOn: javadoc) {
        classifier = 'javadoc'
        from javadoc.destinationDir
    }
    
    artifacts {
        archives sourcesJar
        archives javadocJar
    }
    
    defaultTasks 'build', 'publishToMavenLocal', 'install'