javanativeglibcjnamusl

Decide GNU or MUSL build of linux in Java


I have a Java desktop application which is supposed to run in both GNU Linux distributions (Debian and Ubuntu) and MUSL Linux distributions (Alpine). My application uses a native library also and native library build is different for both type of Linux distributions.

I will deliver both with my application in different folders. So at runtime Java program needs to pick the right distribution of native library to pick as per Linux (GNU or MUSL).

I don't find any mechanism to know that in the Java program, which Linux distribution JVM is running on.

One way I was thinking to read the OS file from /etc/ folder of Linux. But I don't think it would be a good solution (as some custom build might change this details), can someone suggest some better solution for this problem? Or how this can be done?


Solution

  • Using Java/JNA, you can map the gnu_get_libc_version() function and attempt to execute it after loading libc. If it works, you're on glibc (GNU). If you get an UnsatisfiedLinkError that the function is not found, you're on some other libc.

    Map the function:

    public interface Libc extends Library {
        Libc INSTANCE = Native.load("c", Libc.class);
    
        String gnu_get_libc_version();
    }
    

    Call it:

    public class GnuOrMusl {
        public static void main(String[] args) {
            try {
                System.out.println("On GNU libc version " + Libc.INSTANCE.gnu_get_libc_version());
            } catch (UnsatisfiedLinkError e) {
                System.out.println("Not on glibc!");
            }
        }
    }
    

    Note that this detection may indicate glibc if you're in a containerized environment with glibc compatibility, but that raises the question of which build of your library is appropriate there.

    There may be similar approaches with a unique function to distinguish other libc variants from MUSL, but as far as I'm aware, MUSL attempts to be so standards-compliant that it doesn't really allow identifying itself.


    Another option for finding GNU distributions is the uname -o command that you can execute with a ProcessBuilder.

    On non-GNU (Alpine) it is just "Linux" while on Ubuntu, Debian and OpenSUSE it is "GNU/Linux".


    You may also have success determining GNU vs. MUSL by iterating /lib* directories looking for libc variants. This is similar to the approach taken when compiling the JDK, which executes the ldd command and parses libraries from that output.

    For example, iterating the /lib directory in Alpine linux gives this link: libc.musl-x86_64.so.1 -> ld-musl-x86_64.so.1

    In Debian /lib32 has libc.so.6 -> libc-2.28.so, and in OpenSUSE /lib64 I see something similar: libc.so.6 -> libc-2.26.so, and Ubuntu /lib/aarch64-linux-gnu has libc-2.27.so.

    If you stay within Java, determining which /lib path to search may require some trial-and-error. Parsing the output of a command line such as ldd `which ls` will likely get you a string containing gnu or musl.


    As far as determining which Linux Distribution to use, reading from an /etc folder is a good intuition. I manage the Java-based Operating System and Hardware Information (OSHI) project, and went through pretty much all the options to identify which distribution you are running. You can see the results of all that labor in this class.

    I'll quote a comment in that file:

    There are two competing options for family/version information. Newer systems are adopting a standard /etc/os-release file: https://www.freedesktop.org/software/systemd/man/os-release.html

    Some systems are still using the lsb standard which parses a variety of /etc/*-release files and is most easily accessed via the commandline lsb_release -a, see here: https://linux.die.net/man/1/lsb_release In this case, the /etc/lsb-release file (if it exists) has optional overrides to the information in the /etc/distrib-release files, which show: "Distributor release x.x (Codename)"

    The code's logic goes:

    1. Attempt /etc/system-release
    2. Attempt /etc/os-release
    3. Run lsb_release command
    4. Read /etc/lsb-release
    5. Look for and read any /etc/*-release file.

    Those files contain keys like NAME that help you out.

    Feel free to copy and use that file or a variant, or just use that project as a dependency.