Posting a little late, but I’ve been working on this long enough and it’s complicated and interesting enough I figure it warrants posting the work I’ve been doing on this for devember.
This started as getting frustrated trying to do web dev stuff for Minecraft mods, and wanting to use my web dev tooling to build real-time data interaction with a Minecraft server.
I found the Grakkit project that provides a JS runtime inside Minecraft, but it uses the GraalVM’s js runtime, which is pretty limited. Someone found what I spent a day googling for and never found, the Javet project that actually embeds Node.JS with modifications and JNI so there’s deep control over the NodeJS runtime, but can still do all my NodeJS stuff.
So here’s where the work I’ve been doing starts. There’s a chain of kind of projects that don’t quite work.
The person who tried combining Javet and Grakkit created Jakkit, but best I can tell mostly relied on intellisense info, since none of the best practices in the Javet docs were implemented, so it sometimes works but leaks memory, and since no one’s using it, there’s no docs or downloads. As well as that, Javet’s CI builds have been broken for a while so they dropped ARM and ARM64 builds, and I have friends who insist on hosting some of my friend groups’ MC servers on RaspberryPi’s.
So to get everything working here are my goals:
Javet:
- Get Javet’s ARM and ARM64 builds working
- Get Javet’s github actions working again (some other’s have tried helping with it while I’ve been doing this, but there’s still issues and a lot of optimizations I can contribute)
Jakkit:
- Write docs for them, can probably borrow a lot from Grakkit’s, but there’s lots of differences with edge cases and stuff
- Set up github actions to provide downloadable
jar
's for releases - Create ARM and ARM64 builds
Minecraft
- Get server update events to show in a browser
- Maybe get browser stuff to mutate the events
I won’t have enough time to write a minecraft mod, but if I do, I’m wanting to build a whole accounting system that conceptually resembles real ERP / main ledger systems, because I’ve been disappointed with the limitations imposed by the Vault APIs every other money mod uses. But doing more complex money stuff really requires tables and stuff, so I really needed web stuff, and the convenient premade UI components I already know how to use.
Where I’m at now:
First, figuring out cross-compilation of NodeJS and v8
So far, I’ve already got arm
and arm64
builds working for Javet, which was kind of a nightmare.
Javet compiles both NodeJS and Chromium’s V8 from source with some modifications, so first I tried using the docker build pipeline to emulate native builds for arm, but NodeJS and V8 no longer support native arm builds, so then I had to go the route of cross-compilation.
V8 has tools that mostly handled stuff for me, I just had to translate the naming of the different architecture targets.
For NodeJS I had to search around to find all dependencies I needed, but after enough trial and error I found them all, and got the arm
build working, though it required embedding a flag in the environment variable that points to the active cross-compiler. Also I had to figure out how to “install” their custom fork of the RasPi cross-compliers, which was to use a newer version of git than what ships with Ubuntu 20.04, to sparse-checkout
the specific directory that contains the cross-compiler I need without downloading many MB of others.
Getting the arm64
build working was less complicated from the point of view of the dependencies since it doesn’t require 34bit stuff, but in trying to do all this I found that the NodeJS build configuration scripts don’t have checks for what flags the targets and cross-compilers support, so I had to write my own python script to edit their build pre-configuration scripts to use or not use some specific flags.
Finally after all that I got both working. (I have up on ia32/i386 since there were a LOT of issues, and I didn’t really need it)
So now I’ve got build scripts running in a docker build pipeline that by changing parameters will build x64, arm, or arm64.
You can see what I’ve got so far here: GitHub - josh-hemphill/Javet at linux-arm64
And now docker
And now I get to address the partial fixes the maintainer of Javet and another contributor added to the github actions and dockerfiles while I was getting the cross-compilations working.
Originally the docker build was defined in two dockerfiles to separate the dev environment from the one that generates artifacts for download.
The main dockerfile that creates the environment and builds NodeJS and V8 is pretty long and complex, so the other contributor decided the correct approach was to split it into several different files and build separate images, one depending on another.
The maintainer expressed they preferred a single file, which I resonated with, and that they wanted to try to keep it to a single command so other can run it without too much trouble.
So on top of adding cross-compilation, I decided to also try to get the more recent feature of Docker/Podman working, multi-stage dockerfiles. (which did ultimately let me set defaults for everything so there’s a short simple command for getting started with it)
So multi-stage dockerfiles are kind of self-explanatory; I had to figure out how to combine layers and stages to limit the sizes, which was a lot of trial and error to make sure all the bash variables and commands were available where they needed to be, but eventually got that figured out.
But then, multi-stage dockerfiles support more complex dependency graphs. To really get down the size you can have stages build stuff, and then only copy specific files or directories into a new stage, resetting everything else to the state of base linux image; and there’s specifiers so I can lock the x64 cross-compile stages to only run on x64 even if it has to emulate it for people running docker on M series Macs, but other stages can be any/all supported architectures and just copy out the files it needs, so that’s what I did.
I ended up with a graph of stages you could simplify to:
I have a lot more stages than that, but that lets you resume a docker build from more granular points in the build pipeline.
Now I find a bug in Podman/Buildah…
(for those not familiar with Podman, it’s a 1-to-1 replacement for docker, so you can just alias docker to it, though it has some really nice extra features)
So to get this to work, all the stages except the x64-only build stage should run as what ever platforms you’re targeting, so you can run the library in a container on a raspi if you want, without emulation.
But apparently Podman (or it’s builder backend, Buildah) has a bug I discovered, that if you have that earlier common stage, all subsequent directives limiting stages to certain platforms/architectures are ignored… Completely defeating the purpose of all this. If you’re interested, here’s the issue: Multi-stage cross-platform sourcing does not respect `FROM --platform=` when source stage is shared · Issue #4464 · containers/buildah · GitHub
So now I’ve got to decide whether to wait for a fix, write the dockerfiles with a workaround, or stand up another machine or a VM with actual docker and buildkit instead…
Other than that, I think I’ve got the docker bit solved, now I just need to update the github actions to handle things a bit better by using buildkit and caching.
So that’s where I’m at so far …