Getting MounRiver Studio to Run on NixOS

Posted on May 23, 2024 by Richard Goulter
Tags:

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> {} }:

pkgs.callPackage ./mounriver.nix {}

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:

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.


Newer post Older post