8 ms·
I have misgivings about making interop with native code easier. In the node, python, and ruby ecosystems, native code dependencies are a horrorshow of brittle
by stickfigure 3y ago
I have misgivings about making interop with native code easier.
In the node, python, and ruby ecosystems, native code dependencies are a horrorshow of brittle builds. The amount of my life that has been wasted on stupid build issues is significant (damn you nokogiri).
The JVM has been a relative sea of tranquility. The ecosystem is so large, and JNI so unpleasant, that everything important has been built JVM-native. Builds just work, even when you walk away for two years.
I don't want native code in my projects, and I fear this will encourage it.
- irq-1 3y agoGolang followed the same path; no dynamic linking and everything was 're'-created in Go. It's effective. For FFM won't C access be isolated to the JAR? Maybe it's not normal to get pre-built JAR files?
- malkia 3y agoIt's effective... Until you need sqlite, or lmdb, or something like this.
- kaba0 3y agoI think Java is just so insanely big, that this Java-pureness will stick, and JNI/Panama will only be used, when absolutely necessary. This is a unique trait thanks to the way the language grew.
- geokon 3y agoOverall I agree.. I like not having native code.. I run the JVM so I don't have to worry about platform specific stuff. But this would be more-so an issue in the Java 8 world where your uberjar needs to have lib blobs for every platform it may be run on In the Java 11+ world you are no longer in the build-once-run-everywhere model and you unfortunately are semi-required to build platform specific bundles. And adding a C++ build step here wouldn't be a disaster - in theory. You could actually do it cleanly/safely with CMake + Hunter + platform-specific toolchain files. Unfortunately the C++ world hasn't converged to CMake/Hunter so if you're using a uncommon/weird lib then it might take work to make it work. But someone could then in theory clone your project in 20 years running BSDinux on a RISC-VI chip and it should build and run
- koito17 3y ago> you unfortunately are semi-required to build platform specific bundles Can you elaborate on this? It seems like a build tool specific issue to me. In Clojure, most projects use Leiningen (a build tool) and uberjars regardless of the target Java version will bundle native libraries for every platform you run on. You can exclude some of the libraries from your uberjar, but it's a manual process. This has an obvious drawback (e.g. depending on the SQLite JDBC driver bloats your JAR with dozens of native libraries), but it's still very much "build once, run everywhere". The closest I've been to "platform specific bundles" was dealing with a Gradle project where the developer hard-coded a target platform into a property, thus making it impossible to bundle libraries for multiple platforms.
- geokon 3y agoRight, both lein and depstar (or whatever the latest deps.edn one is) make uberjars. And this works great on Linux - which I'm pretty sure is the dev platform for almost all Clojurists. Linux distros basically always come with a OpenJDK JRE/JVM. But installing a JRE is actually discouraged by the Java people. You will notice on java.com you can't actually download a Java 11/17/+ JRE. You can only get a Java 8 JRE! You're supposed to use `jpackage`. It basically packages up the uberjar with all the necessary pieces of the JRE to run the uberjar (so a pared down JRE) and does platform specific bundling like adding meta data and creating an installer (so your application can have a place to save settings and whatnot between runs, have an icon in the Start menu/desktop and stuff like that) You can still do uberjars and get a JRE for Windows/Mac from third parties like Adaptium, but it's very unergonomic and looks kinda sketch (b/c it doesn't look official) My own anecdotal experience: I distributed a JavaFX/Java-17 app as an uberjar. My rational was: - I didn't need any app persistence between runs, so I just wanted a small double-clickage ".exe/bin" equivalent that people could download and try out (no one wants install some random app to try out from online). - `jpackage` doesn't allow you to make a simple double-clickable .exe (they can make something confusingly called an appimgage, which is not an Linux appimage - but it's similar!) - I had no way to do testing on Mac b/c I don't own an Apple machine. So I don't wanna be generating Mac executables I can't even test.. At least with the uberjar I can be semi-confident if it runs on my laptop it'll run on a Mac later (sorta true) The end result was a disaster.. 90% of user issues were from people that would who would go to java.com, "install Java" (ending up with a Java 8 JRE!) and then the app would just silently not run. The JRE doesn't produce any friendly error or warning saying anything about versions - it just silently fails. I have in bold on my landing page YOU NEED A JAVA 17+ JRE. No use.. people would still do it wrong
- dongping 3y agoBazel streamlines the build process with native code (C++/Rust) in Python, making the experience a breeze. However, the drawback is that you would need to transition to Bazel.
- malkia 3y agoI'm not sure if it's still true, but I believe back at Google the interop with C/C++ was even more integrated - if I'm not mistaken the "java" binary got recompiled from scratch (but thanks for caching, not much hit), and your C/C++ code got compiled in with it - so no ".so" loading - you still have single binary (and I think the .jar also went at the end of the file). Or maybe that was for python (e.g. compile CPython along with any ffi modules as one) + .zip file with classes. In any case, even if above was not 100% true, it's doable with system like bazel/buck/etc., and still allow you for smooth incremental development (or say in the default "fastbuild" .so files may still be used - locally, but in "opt" the full static link, or through a --config)
- hamandcheese 3y agoMy main issue with bazel is that adopting it means giving up good editor integrations for anything besides JVM (maybe C/C++ as well, I haven't touched that). And even then, only IntelliJ has good support. I think a lot of smaller (read: most) companies adopt bazel without realizing this. You will pay dearly in terms of developer experience in exchange for the benefits bazel purports to offer.
- gorset 3y agoThere are some workarounds. In my company we have a script that generated IDE files automatically. It uses `bazel query` and `bazel build` to find all external and non native dependencies, and then generates IDE config and files for rust, java, python, etc. Positives: your IDE experience becomes native and you don't need to interact with bazel for normal IDE work flows. Negatives: need to run the generate script anything non native changes. Also need to deal with bazel build files etc for the git workflow, obviously. We're small company, and this method has worked great, and we have a pretty complex build with python accessing rust and java (via jni). Java accessing rust and c (via jni).
- thayne 3y agoBut native code dependencies in those languages usually work similarly to JNI in java, where you write wrapper code in c or c++ to convert from native apis to the interpretor's interface. So interfacing with native code isn't fundamentally easier than for java. Python does have ctypes, but that isn't what libraries like numpy use, probably because of performance. I think there are a couple of other reasons why Java doesn't have the same problems with brittle builds. One is that for python and ruby and to a lesser extent node, it is more often necessary to use native code to get desirable performance, so there are more cases where native code is used. Another is that in the JVM ecosystem, it is more common for packages to be distributed in binary form rather than as sources, so you don't run into problems compiling dependencies, because you download a package that already has the compiled library in it.
- deepsun 3y ago> it is more common for packages to be distributed in binary form rather than as sources, so you don't run into problems compiling dependencies Not sure what you mean here. Java bytecode is similar to source code, just easier for machines to parse, also easy to decompile. Mainstream Java is an interpreted language (requires JVM), not compiled. Don't see what's the difference with packaging, say, python code to a zip archive.
- thayne 3y agoI mean that java libraries that do use native code don't ship packages with c source code, that the user is expected to compile locally, as is common in python, ruby, and node. But instead they typically have a jar that includes a pre-compiled binary library, or several to support multiple platforms. So as long as you are using one of the supported platforms, it just works. But if you are on a platform that doesn't have a pre-compiled library, you will probably need to build the package yourself.
- flakes 3y agoIts relatively common in Python for C/C++ code to be shipped pre-compiled as part of binary wheels (for at least the most common architectures). You do still end up falling back to sdists if your Python version or OS don't line up with any of the supplied binary wheels from the author. e.g. take a look at the pre-compiled set for pydantic-core https://pypi.org/project/pydantic-core/#files https://pypi.org/project/pydantic-core/#files
- pron 3y agoNote that FFM requires that you explicitly allow the use of native code with --enable-native-access (and soon JNI will, too: https://openjdk.org/jeps/8307341 https://openjdk.org/jeps/8307341). As JEP 454 (https://openjdk.org/jeps/454 https://openjdk.org/jeps/454) states: > To allow code in a module M to use unsafe methods without warnings, specify the --enable-native-access=M option on the java launcher command line. Specify multiple modules with a comma-separated list The warnings will become errors in a later release. This means that no library you're using (even some transitive dependency) can call native code without you, the application author, knowing about it. This restriction on native code is part of the work on "integrity by default": https://openjdk.org/jeps/8305968 https://openjdk.org/jeps/8305968
- nvm0n2 3y agoBut how is that connected to the concern expressed? Problem: If the JVM ecosystem starts to depend more on native code it gets less pleasant to work with. Answer: There is this command line flag that is used to activate the FFI. There doesn't seem to be any relation between these two things, unless you're trying to imply that because of the flag people just won't use the FFI at all and will write pure Java instead. Which would be strange, as the justification for investing so much into Panama in the first place is the expectation that it will be used, because native code is getting more important rather than less. There are two ways to avoid the JVM ecosystem becoming crappy due to native code: 1. Encourage the development of pure Java libraries. 2. Make native code work better. Panama tries to do (2) but it's got a lot of missing functionality that the scripting lang ecosystems nail. How do you compile a Java library that uses native code? Python/Node/Ruby worlds know how, but OpenJDK ignores the question. How do you ship a Java library that uses native code? Python/Node/Ruby worlds know, but OpenJDK ignores the question. These problems have been pointed out before and Panama guys just say not in scope. The community hasn't come together to solve these problems either. Maven/Gradle are too disorganized to come up with answers in the absence of any leadership from OpenJDK. Everyone reinvents the wheel when it comes to loading shared libs from JARs. It's a mess and nobody is solving it. So that leaves (1), encourage the development of pure Java libs. This also isn't working. People write in C/C++/Rust because that way everyone can access the functionality regardless of what ecosystem they work in, so such projects get the biggest collection of stakeholders. That's more important that implementation language, so Java is losing badly everywhere. Pure Java libraries are never best in class anymore (they once sometimes were), which is how you end up with situations like this: https://blogs.oracle.com/developers/post/open-sourcing-jipher https://blogs.oracle.com/developers/post/open-sourcing-jiphe... > We’ve chosen to base our products on OpenSSL because it is the most open and most widely used cryptographic toolkit on the planet. At Oracle, we make extensive use of the OpenSSL 3.0 FIPS 140 provider to operate in regulated markets. We like the OpenSSL FIPS provider so much we decided to build a Java cryptography toolkit on top of it called Jipher. By converging on a single toolkit (OpenSSL) we reduce our attack surface, simplify security patching, achieve assembly-optimized performance, and help our customers meet regulatory compliance requirements. It means the first use of Panama in the wild is to replace the built in memory safe Java SSL stack with OpenSSL, a giant pile of C with a long history of memory safety vulns, and it's Oracle itself doing this because OpenSSL is "the most widely used" library and nothing else matters. They even say replacing Java SSL with OpenSSL will reduce the attack surface. If even the well funded OpenJDK developers working in Java can't outcompete the poorly funded OpenSSL devs working in an unproductive language, then what chance do other Java library devs have? None at all! Native code will always win because it will be faster and have a bigger community, and in the end there's safety in numbers. This is very sad. It means Java's future is as a collection of thin wrappers around bug-prone C APIs accessed via Panama, which, as you are well aware pron, will make Loom virtually useless as it cannot work with native code by design. A long steady decline is the most likely path here, in which Java libs get hollowed out to try and keep up with the functionality and performance of native libs (e.g. http/3), and so Java devs steadily lose the benefits of the JVM. There are things Java could do to avoid this fate but the OpenJDK people are never going to do them.
- deleted 3y ago[deleted]
- 62951413 3y agoEvery time you touch anything ML-related you need it. Data scientists not only love their Python but also don't usually use libraries that have JVM bindings. I have not noticed much improvement in this area since 2018 and for some reason djl.ai is not popular in the wild. Other than that, I can think of only really niche use cases (e.g. calling ffmpeg, query engines using the C++ Arrow implementation, likely something trading-related).