Greetings and compliments.
I am looking for some guidance and ideas about deploying a Fat Multi-platform Jar to Maven, using GitHub and supporting at least Linux, Windows and MacOS 64 bit.
Using the Gradle `cpp-library` plugin, I will get my libraries in folder like `fpng/build/lib/main/debug/shared/linux/x86-64/libfpng.so`.
So I have a Gradle `copy` task to copy that to the JNA project:
task copyNativeLib(type: Copy) {
dependsOn assemble
from './build/lib/main/debug/shared'
from './build/lib/main/release/shared'
into '../fpng-java/build/resources/main/lib'
include '**/*.dll', '**/*.so', 'build/lib/**/*.dylib' // Adjust for the native library file extensions on different platforms
}
This works quite well and is ok (although I wonder, why Gradle does not provide any tooling for C/Native into Java project situations).
When building my JNA library, it contains a folder /lib with all the possible libraries for Linux, Windows and MacOS. However, based on Gradle's `cpp-library` that folder is not flat but contains sub-folder for each architecture, e. g. `linux/x86-64/libfpng.so`.
If I understand the situation correct, then I need to find this `lib` folder in the JAR file and copy all the libraries into the filesystem (e.g. `/tmp`). This can be done of course, see below.
But now it gets hairy. It looks like `Native.load()` wants to know exactly, which library to load, e.g. `/tmp/fpng-java/linux/x86-64/libfpng.so`.
In order to achieve this, I will need to know the parts `linux`, `x86-64` and `.so` while I decide about `/tmp/fpng-java` and `libfpng` (only).
I ended up with a quite stupid `if .. then .. else` based on `System.getProperty()` and it felt a lot like re-inventing the wheel.
So I wonder, why JNA does not provide more convenience and tooling around this problem? Why am I supposed to write this kind of boilerplate code every time for myself? (I don't mind writing it, but I do not know about the details of the other platforms Windows, MacOS, let alone BSD, ARM and whatever).
Please advise, thank you in advance.
Andreas
// copy a folder with all its content from inside the JAR to a filesystem destinantion (e.g. '/tmp/)
static void extractFilesFromURI(URI uri, String target) throws IOException {
Path uriPath = Paths.get(uri);
File targetFolder = new File(target);
targetFolder.deleteOnExit();
Files.walkFileTree(uriPath, EnumSet.noneOf(FileVisitOption.class), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.copy(file, targetFolder.toPath().resolve( uriPath.relativize(file) ) , StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Files.createDirectories( targetFolder.toPath().resolve( uriPath.relativize(dir)));
return FileVisitResult.CONTINUE;
}
});
}
String osName = System.getProperty("os.name");
String osArch = System.getProperty("os.arch");
String resourcePath = "/lib";
String extension = ".so";
String targetFolder = System.getProperty("java.io.tmpdir") + File.separator + FPNGEncoder.class.getSimpleName() + File.separator;
try {
extractFilesFromURI(FPNGEncoder.class.getResource("/lib").toURI(), targetFolder);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
// linux/x86-64
// linux/x86
// windows/x86-64
// macOS/x86-64
if ( osName.equalsIgnoreCase("linux") ) {
targetFolder += "linux";
} else if ( osName.equalsIgnoreCase("windows") ) {
targetFolder += "linux";
extension = ".dll";
} else if ( osName.equalsIgnoreCase("macos") ) {
targetFolder += "macos";
extension = ".dylib";
}
if ( osArch.contains("64") ) {
targetFolder += "/x86-64";
} else {
targetFolder += "/x86-32";
}
FPNGEncoder ENCODER = (FPNGEncoder) Native.load(targetFolder + "/libfpng" + extension, FPNGEncoder.class);