javagradlejavafxdependenciesjavacv

How to Gradle load JavaCV binaries only for current platform?


I'm building a Java desktop app using JavaCV.

  1. When my app builds, it's supermassive (1.2GB which is almost all unused JavaCV dependencies). I'm trying to pare down the deps that get loaded, since all I need are some entry-level ffmpeg GPL functions.

  2. Additionally, my test suite is failing with a slew of errors like

    Error occurred during initialization of boot layer java.lang.LayerInstantiationException: Package lib.x86 in both module org.bytedeco.artoolkitplus.android.x86 and module org.bytedeco.tesseract.android.x86

build.gradle

plugins {
  id 'java'
  id 'application'
  id 'distribution'
  //
  id 'de.undercouch.download' version "4.0.4"
  id "org.beryx.jlink" version "2.24.1"
  id 'io.spring.dependency-management' version '1.0.15.RELEASE'
  id 'org.openjfx.javafxplugin' version '0.0.14'
  id 'org.springframework.boot' version '3.1.2' // must match springBootVersion below
}

ext {
  javaFxVersion = '18.0.2'
  junitVersion = '5.9.2'
  logbackVersion = '1.4.11'
  springBootVersion = '3.1.2'
}

group = 'io.xj'

sourceCompatibility = JavaVersion.VERSION_18
targetCompatibility = JavaVersion.VERSION_18

mainClassName = 'io.xj.gui.WorkstationGuiApplication'

tasks.withType(JavaCompile).tap {
  configureEach {
    options.encoding = 'UTF-8'
  }
}

apply plugin: 'java'

configurations {
  implementation {
    exclude group: "commons-logging", module: "commons-logging"
    exclude group: 'com.google.code.findbugs', module: 'jsr305'
  }
}

repositories {
  mavenCentral()
  mavenLocal()
  maven {
    url = 'https://repo.maven.apache.org/maven2'
  }
  maven {
    url = 'https://packages.confluent.io/maven/'
  }
}

javafx {
  version = "${rootProject.ext.javaFxVersion}"
  modules = ['javafx.base', 'javafx.controls', 'javafx.fxml', 'javafx.graphics']
}

//noinspection GroovyAssignabilityCheck
ext.os = org.gradle.internal.os.OperatingSystem.current() as org.gradle.internal.os.OperatingSystem

jlink {
  // imageZip = file("$buildDir/image-zip/javacv-example.zip")
  // options = ['--bind-services', '--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
  addExtraDependencies 'javafx'

  launcher {
    name = 'javacv-example'
    noConsole = true
  }
  jpackage {
    if (os.windows) {
//      installerOptions = ['--win-per-user-install', '--win-dir-chooser', '--win-menu']
    }
  }
}

dependencies {
  implementation "ch.qos.logback:logback-classic:${rootProject.ext.logbackVersion}"
  implementation "ch.qos.logback:logback-core:${rootProject.ext.logbackVersion}"
  implementation "org.openjfx:javafx-base:${rootProject.ext.javaFxVersion}"
  implementation "org.openjfx:javafx-controls:${rootProject.ext.javaFxVersion}"
  implementation "org.openjfx:javafx-fxml:${rootProject.ext.javaFxVersion}"
  implementation "org.openjfx:javafx-graphics:${rootProject.ext.javaFxVersion}"
  implementation "org.springframework.boot:spring-boot-starter-logging:${rootProject.ext.springBootVersion}"
  implementation "org.springframework.boot:spring-boot-starter:${rootProject.ext.springBootVersion}"
  implementation 'commons-codec:commons-codec:1.15'
  implementation 'commons-io:commons-io:2.11.0'
  implementation 'org.bytedeco:ffmpeg-platform-gpl:6.0-1.5.9' // Optional GPL builds with (almost) everything enabled
  implementation 'org.bytedeco:flandmark-platform:1.07-1.5.8' // Required by org.bytedeco.javacv
  implementation 'org.bytedeco:javacv-platform:1.5.9'
  implementation 'org.reflections:reflections:0.10.2'
  testImplementation "org.junit.jupiter:junit-jupiter-api:${rootProject.ext.junitVersion}"
  testImplementation 'org.hamcrest:hamcrest-library:2.2'
  testImplementation 'org.mockito:mockito-junit-jupiter'
  testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${rootProject.ext.junitVersion}"
}

//noinspection ConfigurationAvoidance
task 'bootJarZip'(type: Zip) {
  dependsOn('bootJar')
  from "build/libs/"
  archiveBaseName = 'javacv-example'
  include "javacv-example-${rootProject.version}.jar"
  destinationDirectory = file("$buildDir/dist")
}

bootJar {
  launchScript()
  archiveBaseName = 'javacv-example'
}

test {
  useJUnitPlatform()
}

module-info.java

module main {
  requires ch.qos.logback.classic;
  requires ch.qos.logback.core;
  requires jakarta.annotation;
  requires java.desktop;
  requires javafx.base;
  requires javafx.controls;
  requires javafx.fxml;
  requires javafx.graphics;
  requires org.apache.commons.codec;
  requires org.apache.commons.io;
  requires org.bytedeco.ffmpeg;
  requires org.bytedeco.flandmark.platform;
  requires org.bytedeco.flandmark;
  requires org.bytedeco.javacv.platform;
  requires org.bytedeco.javacv;
  requires org.slf4j;
  requires spring.beans;
  requires spring.boot.autoconfigure;
  requires spring.boot;
  requires spring.context;
  requires spring.core;
  requires spring.jcl;

  opens io.xj.gui.controllers to javafx.graphics, javafx.base, javafx.fxml, javafx.controls, spring.beans;
  opens io.xj.gui.events to javafx.base, javafx.controls, javafx.fxml, javafx.graphics, spring.beans, spring.context, spring.core;
  opens io.xj.gui.listeners to javafx.base, javafx.controls, javafx.fxml, javafx.graphics, spring.beans, spring.context, spring.core, ch.qos.logback.core;
  opens io.xj.gui.services to javafx.base, javafx.controls, javafx.fxml, javafx.graphics, spring.beans, spring.context, spring.core;
  opens io.xj.gui to ch.qos.logback.core, javafx.base, javafx.controls, javafx.fxml, javafx.graphics, spring.beans, spring.context, spring.core;
}

Complete demonstration here


Solution

  • In brief: Gradle JavaCPP accomplishes this:

    plugins {
      id 'org.bytedeco.gradle-javacpp-platform' version "1.5.9"
    }
    
    // We can set this on the command line too this way: -PjavacppPlatform=linux-x86_64,macosx-x86_64,windows-x86_64,etc
    ext {
        javacppPlatform = 'linux-x86_64,macosx-x86_64,windows-x86_64,etc' // defaults to Loader.getPlatform()
    }