javagradlejavafxjavafx-gradle-plugin

Cant find javafx dependencies on Linux


We are using Java 21, JavaFx 21, Gradle 8.6 and JavaFx Gradle plugin. See Gradle 8.6 Compatibility Matrix.

We run unit tests for our application on Azure pipelines' hosted Windows agents and use a self hosted Linux agent.

The JavaFx Gradle plugin downloads the correct OS version of the JavaFx jars for each agent. The unit tests run correctly on the Windows agent but on the Linux agent, we get the exception below.

The native dependency which cannot be found is bundled in the javax-graphics jar so I'm not sure what, if anything, I have to do for the dependency to be picked up from the jar. It looks like it’s expecting the dependency to be available in the location specified for java.library.path.

java.lang.UnsatisfiedLinkError: no javafx_font_pango in java.library.path: /agent/_work/1/s/<path to java.library.path>
at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2458)
at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:916)
at java.base/java.lang.System.loadLibrary(System.java:2063)
at com.sun.glass.utils.NativeLibLoader.loadLibraryInternal(NativeLibLoader.java:170)
at com.sun.glass.utils.NativeLibLoader.loadLibrary(NativeLibLoader.java:58)
at com.sun.javafx.font.freetype.OSPango.lambda$static$0(OSPango.java:37)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:319)
at com.sun.javafx.font.freetype.OSPango.<clinit>(OSPango.java:36)
at com.sun.javafx.font.freetype.PangoGlyphLayout.<clinit>(PangoGlyphLayout.java:45)
at com.sun.javafx.font.freetype.FTFactory.createGlyphLayout(FTFactory.java:87)
at com.sun.javafx.text.GlyphLayout.newInstance(GlyphLayout.java:407)
at com.sun.javafx.text.GlyphLayout.<clinit>(GlyphLayout.java:402)
at com.sun.javafx.text.PrismTextLayout.buildRuns(PrismTextLayout.java:818)
at com.sun.javafx.text.PrismTextLayout.layout(PrismTextLayout.java:1070)
at com.sun.javafx.text.PrismTextLayout.ensureLayout(PrismTextLayout.java:230)
at com.sun.javafx.text.PrismTextLayout.getBounds(PrismTextLayout.java:256)
at javafx.scene.text.Text.getLogicalBounds(Text.java:432)
at javafx.scene.text.Text.doComputeLayoutBounds(Text.java:1137)
at javafx.scene.text.Text$1.doComputeLayoutBounds(Text.java:143)
at com.sun.javafx.scene.shape.TextHelper.computeLayoutBoundsImpl(TextHelper.java:95)
at com.sun.javafx.scene.NodeHelper.computeLayoutBounds(NodeHelper.java:108)
at javafx.scene.Node$13.computeBounds(Node.java:3474)
at javafx.scene.Node$LazyBoundsProperty.get(Node.java:9749)
at javafx.scene.Node$LazyBoundsProperty.get(Node.java:9740)
at javafx.scene.Node.getLayoutBounds(Node.java:3489)

Here are a couple of relevant portions of our gradle file

JavaFx gradle plugin:

javafx {
    version = project.properties['javafxVersion']
    modules = [ 'javafx.base', 'javafx.controls', 'javafx.graphics', 'javafx.swing', 'javafx.web', 'javafx.media' ]
    configurations = [ 'implementation', 'testImplementation', 'testRuntimeOnly', 'testFixturesImplementation', 'guiTestImplementation' ]
}

And the test task:

tasks.withType(Test) {
    doFirst {
        delete getReports().getJunitXml().getOutputLocation()
    }

    jvmArgs "-Djava.library.path=${project.nativeLibsDir}" // our custom native libs directory
}

test {
    useJUnitPlatform()
    ext.testSrcDirs = project.sourceSets.test.java.srcDirs
    jvmArgs "-Dglass.platform=Monocle", "-Dmonocle.platform=Headless", "-Dprism.order=sw", "-Dprism.text=t2k"
    forkEvery 100
    maxParallelForks = Runtime.getRuntime().availableProcessors() - 1

    testLogging {
        events "started", "passed", "skipped", "failed"
    }
}

Thanks.


Solution

  • Solution

    As noted by the original poster in comments:

    My issue was fixed by:

    Historical info and analysis follows

    This isn't meant as an answer, it is just a collection of comments and apparent issues with your build setup (there may be others), but putting it as an answer beats a bunch of uneditable comments.


    Generally (for 99% of other people coming across this question), fetching usable JavaFX components from Maven Central using Gradle or Maven will just work (by following instructions as openjfx.io). This is because the Maven projects (pom.xml files) for JavaFX (from the openjfx project itself, not your code) are configured to find and download the correctly classified dependencies for your runtime environment.

    The difficulty you have is that you are trying to do something different than the standard usage of JavaFX (using the monocle system to run JavaFX in a headless mode with software rendering), so the standard classifier dependency resolution (that does not pick up headless monocle code) does not work.


    The JavaFX jars in maven central (those fetched by gradle) are different than those in the SDK. Maven hosted jars include native libs, unlike the SDK. When you use the maven hosted jars, they will, at runtime, extract the libs from the jar, put it in a cache directory on the file system and configure Java to load the native libs for JavaFX from cache. On my system (OS X Intel), this directory is: ~/.openjfx/cache/22.0.2+4/x86_64, which contains libglass.dylib libjavafx_font.dylib libprism_es2.dylib.

    What is the equivalent directory and content on your linux box? Does it contain the native libraries you expect?


    Did you edit the stack trace?

    Or did it really output

    no javafx_font_pango in java.library.path: /agent/_work/1/s/<path to java.library.path> 
    

    ?

    In which case it doesn't look like a valid path at all.

    Perhaps the javafx.library.path that you are setting is completely wrong or interferes with the default library loading in JavaFX.


    You are on pretty shaky ground using undocumented command line switches like:

    -Dprism.text=t2k
    

    t2k text was removed from JavaFX years ago, so I don't know what that would achieve. It may not be possible to bypass the native OS font integration in later JavaFX releases.


    You are trying to use monocle to use JavaFX in a headless mode. As far as I know (I have never used it), that requires different jar files with a classifer name that includes monocle (you don't do that) -> see the JavaFX 21.0.1 maven repository which includes monocle specific jars.

    It appears that not every JavaFX release to maven includes monocle jars, for instance monocle jars are not present for JavaFX 22.0.2 releases in maven.

    Perhaps support for monocle was dropped in later releases, at least for published maven jars (I don't know). Or perhaps monocle for other releases could be found in the downloaded SDK version instead (I didn't look).


    If you continue to have difficulty getting your current setup using Maven Central Jars to work, you may have better luck either

    OR

    JDK versions including JavaFX

    Azul "JDK FX" and Bellsoft "Full JDK" both include JavaFX. Or you can build a custom JDK that includes JavaFX by following instructions at openjfx.io, section: "Custom JDK+JavaFX image".

    Using the JavaFX SDK

    If you use the JavaFX SDK, then within the SDK distribution, the native code will be in separate files from the jar files, allowing you to explicitly set the Java library location to include that native code if needed (though I think if you leave the native libs in their default location, they will be found without additional settings).


    FAQ

    Is there an alternative to using the monocle system to run JavaFX in a headless mode?

    I don't know, probably not.

    I have never used headless mode.

    There is little documentation on headless mode (and what there is outdated).

    There is a downloadable pdf on Monocle which explains the architecture of how it works and what it does.

    Advice: look at TestFX

    You could consider TestFX (I haven't used it)

    You could try avoiding using headless mode unless you are sure you need it.

    If headless is needed, TestFX comes with its own Monocle implementation. The documentation for the TestFX Monocle implementation notes that it does not include required native libs, which you may need to install manually (I don't know).

    TestFX includes instructions for continuous integration using Travis CI on Ubuntu in a headless mode (which seems close to what you are trying to do), with support for LTS Java and JavaFX releases, such as JavaFX 17 and 21.

    The TestFX developers reference a chat link for support. You are more likely to get better support for what you are trying to do there than on StackOverflow.

    What Linux package dependencies does JavaFX have and how do you ensure they are installed?

    That will vary between Linux systems and Java/JavaFX versions.

    For RPM based systems, like Rocky Linux, you can check the dependencies in the Zulu JDK FX rpm that includes the JDK and JavaFX. Unfortunately it does not list libpangoft2-1.0 in the list of required dependencies, so the list of required packages is incomplete, otherwise you could just install the rpm using yum and it would automatically ensure all of the required dependent native code to support the JDK and JavaFX was installed.

    $ rpm -qp zulu21.36.17-ca-fx-jdk21.0.4-linux.x86_64.rpm --requires
    warning: zulu21.36.17-ca-fx-jdk21.0.4-linux.x86_64.rpm: Header V4 RSA/SHA256 Signature, key ID 219bd9c9: NOKEY
    /bin/sh
    /bin/sh
    /bin/sh
    /bin/sh
    libc.so.6()(64bit)
    libc.so.6(GLIBC_2.2.5)(64bit)
    libc.so.6(GLIBC_2.3)(64bit)
    libc.so.6(GLIBC_2.4)(64bit)
    libdl.so.2()(64bit)
    libdl.so.2(GLIBC_2.2.5)(64bit)
    libjli.so()(64bit)
    libpthread.so.0()(64bit)
    libpthread.so.0(GLIBC_2.2.5)(64bit)
    libz.so.1()(64bit)
    rpmlib(BuiltinLuaScripts) <= 4.2.2-1
    rpmlib(CompressedFileNames) <= 3.0.4-1
    rpmlib(FileDigests) <= 4.6.0-1
    rpmlib(PayloadFilesHavePrefix) <= 4.0-1
    rpmlib(PayloadIsXz) <= 5.2-1