Getting MounRiver Studio to Run on NixOS
Tags: programming.ch59x, programming.nixos, programming.mounriver-studio
In the previous post, I walked through a few different ways of how to get a pre-compiled binary running on NixOS.
In this post, I’ll share some notes on what it took to get MounRiver running on NixOS. The basic idea is the same, but in practice it felt much more difficult. (MounRiver Studio is an IDE used for WCH MCUs. I’ve discussed developing the EVT examples in a previous blogpost).
If the previous post was “draw a circle”, this post is “draw the rest of the owl”.
These notes are mostly “here are the commands I ran, here was the output”:
The Community RISC-V IDE can be downloaded from the MounRiver website. It’s a tarball with a beforeinstall
(with some .so
shared libraries to be copied into /usr/lib
), and an MRS_Community
directory which is a distribution of the Eclipse IDE.
First thing to try: just running the executable directly:
$ ./MounRiver_Studio_Community_Linux_x64_V160/MRS_Community/MounRiver\ Studio_Community
exec: Failed to execute process './MounRiver_Studio_Community_Linux_x64_V160/MRS_Community/MounRiver Studio_Community': The file exists and is executable. Check the interpreter or linker?
or with bash:
bash: ./MounRiver_Studio_Community_Linux_x64_V160/MRS_Community/MounRiver Studio_Community: cannot execute: required file not found
This “required file not found” should be familiar, having read the previous blogpost.
So, checking what ldd
says:
$ ldd ./MounRiver_Studio_Community_Linux_x64_V160/MRS_Community/MounRiver\ Studio_Community
linux-vdso.so.1 (0x00007ffd249d4000)
libpthread.so.0 => /nix/store/anlf335xlh41yjhm114swi87406mq5pw-glibc-2.38-44/lib/libpthread.so.0 (0x00007fc32c3d8000)
libdl.so.2 => /nix/store/anlf335xlh41yjhm114swi87406mq5pw-glibc-2.38-44/lib/libdl.so.2 (0x00007fc32c3d3000)
libc.so.6 => /nix/store/anlf335xlh41yjhm114swi87406mq5pw-glibc-2.38-44/lib/libc.so.6 (0x00007fc32c1ea000)
/lib64/ld-linux-x86-64.so.2 => /nix/store/anlf335xlh41yjhm114swi87406mq5pw-glibc-2.38-44/lib64/ld-linux-x86-64.so.2 (0x00007fc32c3df000)
Indeed, the dynamic linker is /lib64/ld-linux-x86-64.so.2
. (So far, this is the same as the hello world example from the previous post).
Approach 1: Using Nix Alien
In the previous post, we saw nix-alien could automatically work around some problems. Trying that:
$ nix-alien ./MounRiver_Studio_Community_Linux_x64_V160/MRS_Community/MounRiver\ Studio_Community
/home/rgoulter/playground/nix-mounriver-studio/MounRiver_Studio_Community_Linux_x64_V160/MRS_Community//plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_14.0.2.v20200815-0932/jre/bin/java: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory
MounRiver Studio_Community:
JVM terminated. Exit code=127
/home/rgoulter/playground/nix-mounriver-studio/MounRiver_Studio_Community_Linux_x64_V160/MRS_Community//plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_14.0.2.v20200815-0932/jre/bin/java
-Dosgi.requiredJavaVersion=11
-Dosgi.instance.area.default=@user.home/mrs_community-workspace
....
The key detail of this output being ... libz.so.1: cannot open ...
:
plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_14.0.2.v20200815-0932/jre/bin/java: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory
Checking ldd
on this java
binary:
$ ldd ..../jre/bin/java
linux-vdso.so.1 (0x00007ffe3abb5000)
libz.so.1 => not found
libjli.so => /home/rgoulter/playground/nix-mounriver-studio/MounRiver_Studio_Community_Linux_x64_V160/MRS_Community//plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_14.0.2.v20200815-0932/jre/bin/../lib/libjli.so (0x00007ff14489c000)
libpthread.so.0 => /nix/store/anlf335xlh41yjhm114swi87406mq5pw-glibc-2.38-44/lib/libpthread.so.0 (0x00007ff144897000)
libdl.so.2 => /nix/store/anlf335xlh41yjhm114swi87406mq5pw-glibc-2.38-44/lib/libdl.so.2 (0x00007ff144892000)
libc.so.6 => /nix/store/anlf335xlh41yjhm114swi87406mq5pw-glibc-2.38-44/lib/libc.so.6 (0x00007ff1446a7000)
/lib64/ld-linux-x86-64.so.2 => /nix/store/anlf335xlh41yjhm114swi87406mq5pw-glibc-2.38-44/lib64/ld-linux-x86-64.so.2 (0x00007ff1448b3000)
libz.so.1 => not found
Indeed, libz.so.1 => not found
.
We can add --additional-libs=libz.so.1
to the nix-alien
call, which helpfully lets us pick the package libz
. (We could instead use --additional-packages=libz
if we already know libz
is the name of the nix package with libz.so.1
). Re-running nix-alien
with this still runs into problems:
$ nix-alien --recreate --additional-libs=libz.so.1 ./MounRiver_Studio_Community_Linux_x64_V160/MRS_Community/MounRiver\ Studio_Community
MounRiver Studio_Community:
An error has occurred. See the log file
/home/rgoulter/playground/nix-mounriver-studio/MounRiver_Studio_Community_Linux_x64_V160/MRS_Community/configuration/1716348838071.log.
Checking the contents of that log file:
!SESSION 2024-05-22 03:33:57.978 -----------------------------------------------
eclipse.buildId=4.17.0.I20200902-1800
java.version=14.0.2
java.vendor=Oracle Corporation
BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=en_US
Framework arguments: -product org.eclipse.epp.package.embedcdt.product
Command-line arguments: -os linux -ws gtk -arch x86_64 -product org.eclipse.epp.package.embedcdt.product
!ENTRY org.eclipse.osgi 4 0 2024-05-22 03:33:59.069
!MESSAGE Application error
!STACK 1
java.lang.UnsatisfiedLinkError: Could not load SWT library. Reasons:
no swt-pi4-gtk-4936r26 in java.library.path: [/run/opengl-driver/lib, /run/opengl-driver-32/lib, /usr/java/packages/lib, /usr/lib64, /lib64, /lib, /usr/lib]
no swt-pi4-gtk in java.library.path: [/run/opengl-driver/lib, /run/opengl-driver-32/lib, /usr/java/packages/lib, /usr/lib64, /lib64, /lib, /usr/lib]
Can't load library: /home/rgoulter/.swt/lib/linux/x86_64/libswt-pi4-gtk-4936r26.so
Can't load library: /home/rgoulter/.swt/lib/linux/x86_64/libswt-pi4-gtk.so
....
~/.swt/lib/linux/x86_64/
doesn’t have the pi4
lib, but it does have a pi3
one:
$ ls /home/rgoulter/.swt/lib/linux/x86_64/
libswt-pi3-gtk-4936r26.so*
$ ldd /home/rgoulter/.swt/lib/linux/x86_64/libswt-pi3-gtk-4936r26.so
linux-vdso.so.1 (0x00007fffa2b60000)
libgtk-3.so.0 => not found
libgdk-3.so.0 => not found
libcairo.so.2 => not found
libgthread-2.0.so.0 => not found
libc.so.6 => /nix/store/anlf335xlh41yjhm114swi87406mq5pw-glibc-2.38-44/lib/libc.so.6 (0x00007fc0c9a17000)
/nix/store/anlf335xlh41yjhm114swi87406mq5pw-glibc-2.38-44/lib64/ld-linux-x86-64.so.2 (0x00007fc0ca004000)
Here, the libswt
shared library can’t load, because shared libraries it links against aren’t there.
With that, let’s add packages cairo
, glib
, gtk3
to the nix-alien
invocation.
$ nix-alien --recreate --additional-packages=libz -p cairo -p gtk3 -p glib ./MounRiver_Studio_Community_Linux_x64_V160/MRS_Community/MounRiver\ Studio_Community
And it runs!
Further invocations of nix-alien
can just run the binary directly:
$ nix-alien ./MounRiver_Studio_Community_Linux_x64_V160/MRS_Community/MounRiver\ Studio_Community
Approach 2: Using patchelf on the Binaries Directly
Although using nix-alien
works, I was curious to see what it would take to beat the thing into shape using patchelf
directly. (This is obviously a more tedious way of approaching the problem).
It’s possible to run patchelf
against everything that looks like it could be an executable binary, and let patchelf
sort out whether the interpreter needs to be set or not:
interpreter=/nix/store/....-glibc-2.39-5/lib64/ld-linux-x86-64.so.2
find ./MounRiver_Studio_Community_Linux_x64_V160 -type f -executable -exec file {} \; \
| grep ELF \
| cut -d: -f1 \
| xargs -i sh -c "patchelf --set-interpreter $interpreter \"{}\" || true"
(Find files which are executable, and run file
on them; filter the output to ELF files (binaries and libraries), take the output up till the first :
, and run this against patchelf
).
This will take care of the interpreter being set to /lib64/ld-linux-x86-64.so.2
in the binaries for running on this machine. (I’m not suggesting this is a clean approach to the problem).
I had also tried running patchelf --set-interpreter
against each each executable as it caused a problem. This lead to iteratively running the program, seeing what didn’t work, and then patching whatever binary couldn’t be executed due to its interpreter not being found.
This was a bit tedious, since it involved the eclipse binary, the JRE it uses, and the toolchains in the same distribution (make
, and then the GCC components).
I found the link between Eclipse and the toolchains in its distributions:
In the project settings, under Project -> C/C++ Build -> Settings -> Toolchains tab, the toolchains are set to the binaries in this MounRiver distribution.
I think plaintext files are simpler to understand than configuration via a UI. Eclipse seems to be oriented around configuration through the UI.
As with the nix-alien
solution above, of course the program also ran into problems with missing shared libraries when loading SWT.
I opted to set LD_LIBRARY_PATH
in a shell.nix
, and so would open MounRiver wtih this shell.nix
loaded:
{ pkgs ? import <nixpkgs> {} }:
let
libs = with pkgs; [
libz
cairo
glib
gtk3];
in
{
pkgs.mkShell LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath libs;
}
Using the MounRiver IDE for WCT EVT Examples
While trying the MounRiver IDE to build the CH592 EVT examples, I ran into a problem where it couldn’t find the CH59xBLE
lib.
$ ls /home/rgoulter/github/ch592/EVT_V1.0/EXAM/BLE/LIB
.... LIBCH59xBLE.a ....
This should be renamed to libCH59xBLE.a
in order for the linker to find it.
Presumably the developers distributing the EVT are using a case-insensitive filesystem.
Issues like that, and the distributed Eclipse containing whitespace in the executable/toolchain paths, smell sloppy to me.
Attempting to Package this in Nix
I expect using the buildFHSEnv
would likely result in a package which works. (Albeit, for distributing as a package, care would need to be taken for unpacking the binary).
However, it’s cleaner to write a Nix package using stdenv.mkDerivation
.
And by writing a package, it’s going to be easier for others to use, or for me to re-use without having to make assumptions about where I put different files.
For the first attempt, we copy the approach as with the autoPatchelfHook
example in the previous blogpost, e.g. mounriver.nix
:
{ stdenv
, autoPatchelfHook
, libz
, cairo
, glib
, gtk3
}:
{
stdenv.mkDerivation pname = "mounriver studio community";
version = "V160";
src = ./MounRiver_Studio_Community_Linux_x64_V160.tar.xz;
buildInputs = [
autoPatchelfHook
libz
cairo
glib
gtk3
libXtst
libsecret
alsa-lib];
# breaks on whitespace
dontUpdateAutotoolsGnuConfigScripts = true;
doBuild = false;
installPhase = ''
mkdir -p $out
cp -r ./MRS_Community/* $out
'';
}
and a default.nix
with:
{ pkgs ? import <nixpkgs> {} }:
./mounriver.nix {} pkgs.callPackage
that we build with nix-build
.
This results in some errors:
error: auto-patchelf could not satisfy dependency libXtst.so.6 wanted by /nix/store/vhjxnm2ris1rnnc3zgwbi55z5f3ah9ak-mounriver-studio-community-V160/plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_14.0.2.v20200815-0932/jre/lib/libawt_xawt.so
error: auto-patchelf could not satisfy dependency libasound.so.2 wanted by /nix/store/vhjxnm2ris1rnnc3zgwbi55z5f3ah9ak-mounriver-studio-community-V160/plugins/org.eclipse.justj.openjdk.hotspot.jre.full.linux.x86_64_14.0.2.v20200815-0932/jre/lib/libjsound.so
error: auto-patchelf could not satisfy dependency libsecret-1.so.0 wanted by /nix/store/vhjxnm2ris1rnnc3zgwbi55z5f3ah9ak-mounriver-studio-community-V160/plugins/org.eclipse.equinox.security.linux.x86_64_1.1.300.v20190830-1238/libkeystorelinuxnative.so
error: auto-patchelf could not satisfy dependency libusb-1.0.so.0 wanted by /nix/store/vhjxnm2ris1rnnc3zgwbi55z5f3ah9ak-mounriver-studio-community-V160/toolchain/OpenOCD/bin/openocd
error: auto-patchelf could not satisfy dependency libhidapi-hidraw.so.0 wanted by /nix/store/vhjxnm2ris1rnnc3zgwbi55z5f3ah9ak-mounriver-studio-community-V160/toolchain/OpenOCD/bin/openocd
error: auto-patchelf could not satisfy dependency libjaylink.so.0 wanted by /nix/store/vhjxnm2ris1rnnc3zgwbi55z5f3ah9ak-mounriver-studio-community-V160/toolchain/OpenOCD/bin/openocd
error: auto-patchelf could not satisfy dependency libncurses.so.5 wanted by /nix/store/vhjxnm2ris1rnnc3zgwbi55z5f3ah9ak-mounriver-studio-community-V160/toolchain/arm-none-eabi-gcc/bin/arm-none-eabi-gdb
error: auto-patchelf could not satisfy dependency libtinfo.so.5 wanted by /nix/store/vhjxnm2ris1rnnc3zgwbi55z5f3ah9ak-mounriver-studio-community-V160/toolchain/arm-none-eabi-gcc/bin/arm-none-eabi-gdb
error: auto-patchelf could not satisfy dependency libncurses.so.5 wanted by /nix/store/vhjxnm2ris1rnnc3zgwbi55z5f3ah9ak-mounriver-studio-community-V160/toolchain/arm-none-eabi-gcc/bin/arm-none-eabi-gdb-py
error: auto-patchelf could not satisfy dependency libtinfo.so.5 wanted by /nix/store/vhjxnm2ris1rnnc3zgwbi55z5f3ah9ak-mounriver-studio-community-V160/toolchain/arm-none-eabi-gcc/bin/arm-none-eabi-gdb-py
The libusb-1.0.so.0
, libhidapi
, libjaylink
, etc. helps explain why these shared libraries were included in the beforeinstall
directory. (They’re used by openocd and gdb).
Anyway, adding the libraries for these to the buildInputs
{ stdenv
, autoPatchelfHook
, libz
, cairo
, glib
, gtk3
, hidapi
, libjaylink
, libusb1
, ncurses5
, libXtst
, alsa-lib
, libsecret
}:
{
stdenv.mkDerivation pname = "mounriver studio community";
version = "V160";
src = ./MounRiver_Studio_Community_Linux_x64_V160.tar.xz;
buildInputs = [
autoPatchelfHook
libz
cairo
glib
gtk3
hidapi
libjaylink
libusb1
ncurses5
libXtst
libsecret
alsa-lib];
# breaks on whitespace
dontUpdateAutotoolsGnuConfigScripts = true;
doBuild = false;
installPhase = ''
mkdir -p $out
cp -r ./MRS_Community/* $out
'';
}
This package recipe is able to build, but the resulting Eclipse doesn’t successfully run.
Unsurprisingly, it can’t load the shared libraries for the SWT library.
nix-alien
solved this by adding cairo glib gtk3
to its FHS env.
Replacing the interpreter fixed this by adjusting LD_LIBRARY_PATH
.
Here, we can use wrapProgram
to adjust the LD_LIBRARY_PATH
with lib.makeLibraryPath [ cairo glib gtk3 ]
. This ensures LD_LIBRARY_PATH
runs with a value we want when we run the program.
Hence:
{ lib
, stdenv
, autoPatchelfHook
, makeWrapper
, libz
, cairo
, glib
, gtk3
, hidapi
, libjaylink
, libusb1
, ncurses5
, libXtst
, alsa-lib
, libsecret
}:
{
stdenv.mkDerivation pname = "mounriver studio community";
version = "V160";
src = ./MounRiver_Studio_Community_Linux_x64_V160.tar.xz;
buildInputs = [
autoPatchelfHook
makeWrapper
libz
cairo
glib
gtk3
hidapi
libjaylink
libusb1
ncurses5
libXtst
libsecret
alsa-lib];
# breaks on whitespace
dontUpdateAutotoolsGnuConfigScripts = true;
doBuild = false;
installPhase = ''
mkdir -p $out
cp -r ./MRS_Community/* $out
wrapProgram "$out/MounRiver Studio_Community" --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath [ cairo glib gtk3 ]}
'';
}
This runs, but compared to the MounRiver run by nix-alien
, the toolchains in the project settings are empty, and so the IDE doesn’t “just work” for building the WCH EVT examples.
In the MounRiver which did build the WCT EVT examples, the toolchain paths were set to values like ${eclipse_home}toolchain/make-3.8/usr/local/bin
or ${eclipse_home}/toolchain/RISC-V Embedded GCC/bin
.
Searching for these strings, in the MRS_Community
folder:
$ rg --hidden --fixed-strings '${eclipse_home}toolchain'
configuration/.settings/ilg.gnumcueclipse.managedbuild.cross.prefs
It’s not clear to me why the Eclipse running from this mounriver.nix
Nix package doesn’t pick these settings up.
My guess is something to do with the configuration/.settings
being read-only in the /nix/store
.
I noticed that copying the configuration
from the /nix/store
(or just the extracted MRS_Community
directory) to the directory in ~/.eclipse
did set the global toolchain paths; but I’m not sure where/how that directory in ~/.eclipse
comes into things, either. (Whereas, say, the .ini
file in MRS_Community
does define osgi.instance.area.default=@user.home/mrs_community-workspace
).
At this point, it’s worth looking at code in nixpkgs.
Searching for packages named “eclipse”, and then opening up the source code.
The build inputs & wrapping with LD_LIBRARY_PATH
looks familiar. This nixpkgs code has code to deal with packaging a gnome application, among other things.
The only thing that sticks out as related to configuration
is the argument is makeWrapper
-add-flags "-configuration \$HOME/.eclipse/''${productId}_${productVersion}/configuration"
.
My understanding of how Eclipse works is insufficient to figure out how I would write a Nix package this MounRiver distribution & also have the configuation/.settings
it provides get used, without requiring the user to just copy these files.
At this point, it’s enough for me to say that the approach taken by this distributed tarball is different enough from the way Nix prefers to package things. e.g. the tarball includes a java runtime, as well as toolchain binaries for make
and the RISC-V GCC toolchain; whereas Nix would package these separately.
For myself, getting MounRiver to run is mostly a curiosity. It would have been useful for checking the WCH EVT examples. In another blogpost, I discussed building this outside of MounRiver, and in that I linked to an example with a nix-shell provided.
Conclusion
Okay, so in the end, I was able to run the MounRiver Studio binary on NixOS. This required using nix-alien
, and figuring out which shared libraries were required to run the precompiled binaries. – This was a bit annoying, but not insurmountable. Getting it to run with nix-alien
was essentially no more difficult than the hello-world example in the previous blogpost.
I was not able to successfully write a nix package for MounRiver Studio such that the user experience was the same “just works” as is intended. The point here seems to be:
It seems the idiosyncracy of the eclipse home being read only caused issues reading the settings.
Understanding of how to work around issues like these may require a deep understanding of what the program is doing in order to work around them.
In my experience, cases like this are not common on NixOS, and it doesn’t dissuade me at all from using NixOS. However, this is also additional effort that you probably don’t have to expend on other Linux distributions.