Getting access to the RISCV GNU Toolchain (riscv64-unknown-elf)

Hey all, I’m trying to get the RISC-V GNU Toolchain set up for use in a nix develop shell. Specifically, I need access to the riscv-unknown-elf-* tools.

I known that Nix supports tooling for cross-compilation like this:

outputs = { self, nixpkgs, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        riscv-toolchain = import nixpkgs {
          localSystem = "${system}";
          crossSystem = {
            config = "riscv64-unknown-linux-gnu";
          };
        };
      in
        {
          devShell = pkgs.mkShell {
            buildInputs = with pkgs; [
              riscv-toolchain.buildPackages.gcc
            ];
          };
        }
    );

Which gives me access to the riscv-unkown-linux-gnu-* tools. However, when I set riscv-toolchain.crossSystem.config to risc64-unknown-elf, I get the error Unknown kernel: unknown, which isn’t very helpful to say the least.

On a non-NixOS system I’d just build the repo myself from source, so I tried creating a typical mkDerivation, like so:

outputs = { self, nixpkgs, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        riscv-toolchain = pkgs.stdenv.mkDerivation {
          name = "riscv-toolchain";

          dontPatchELF = true;
          fetchSubmodules = true;

          src = pkgs.fetchFromGitHub {
            owner = "riscv";
            repo = "riscv-gnu-toolchain";
            rev = "b39e36160aa0649ba0dfb9aa314d375900d610fb";
            sha256 = "0qqp9w6595saw9gzq73n6plhdc1r1mrswjagxmqkzd1fxg7hwjzn";
          };

          configureFlags = "--with-arch=rv64g";

          buildInputs = with pkgs; [ gmp libmpc mpfr gawk bison flex texinfo gperf curl git flock ];
        };
      in
        {
          devShell = pkgs.mkShell {
            buildInputs = with pkgs; [
              riscv-toolchain
            ];
          };
        }
    );

But that just results in tis rather hard to comprehend error:

@nix { "action": "setPhase", "phase": "unpackPhase" }
unpacking sources
unpacking source archive /nix/store/q5fw16am9jjw53w9a8c1lzj23aymf5fr-source
source root is source
@nix { "action": "setPhase", "phase": "patchPhase" }
patching sources
@nix { "action": "setPhase", "phase": "configurePhase" }
configuring
configure flags: --prefix=/nix/store/kn63j4j1s0kpgxspyf37rn38sysl7hky-riscv-toolchain --with-arch=rv64g
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking for grep that handles long lines and -e... /nix/store/gpjwx73lj7bynf2z0gamlwy47dzxdbml-gnugrep-3.6/bin/grep
checking for fgrep... /nix/store/gpjwx73lj7bynf2z0gamlwy47dzxdbml-gnugrep-3.6/bin/grep -F
checking for grep that handles long lines and -e... (cached) /nix/store/gpjwx73lj7bynf2z0gamlwy47dzxdbml-gnugrep-3.6/bin/grep
checking for bash... /nix/store/wv35g5lff84rray15zlzarcqi9fxzz84-bash-4.4-p23/bin/bash
checking for __gmpz_init in -lgmp... yes
checking for mpfr_init in -lmpfr... yes
checking for mpc_init2 in -lmpc... yes
checking for curl... /nix/store/3npnq65by4nxvw103svzi73b5qd78igw-curl-7.76.1-bin/bin/curl
checking for wget... no
checking for ftp... no
configure: creating ./config.status
config.status: creating Makefile
config.status: creating scripts/wrapper/awk/awk
config.status: creating scripts/wrapper/sed/sed
@nix { "action": "setPhase", "phase": "buildPhase" }
building
cd /build/source && \
flock `git rev-parse --git-dir`/config git submodule init /build/source/riscv-gcc/ && \
flock `git rev-parse --git-dir`/config git submodule update /build/source/riscv-gcc/
cd /build/source && \
flock `git rev-parse --git-dir`/config git submodule init /build/source/riscv-gcc/ && \
flock `git rev-parse --git-dir`/config git submodule update /build/source/riscv-gcc/
fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
flock: cannot open lock file /config: Permission denied
flock: invalid input: Permission denied
make: *** [Makefile:247: /build/source/riscv-gcc/.git] Error 66
flock: cannot open lock file /config: Permission denied
flock: invalid input: Permission denied
make: *** [Makefile:247: /build/source/riscv-gcc/.git] Error 66

Does anyway have any experience with this? Am I going about this the wrong way?

Thanks!

2 Likes

I think it would help to first know what you’re trying to do. We can certainly package the cross-toolchain you linked, but nixpkgs does already have a RISC-V cross toolchain that may serve your needs.

If you just want to cross-compile a package, such as coreutils, you can use the pkgsCross set to cross-compile it to RISC-V: nix-build '<nixpkgs>' -A pkgsCross.riscv64.coreutils.

I’m not necessarily looking to cross-compile a package per se, I’m just following a little tutorial that requires me to use build a few-test binaries for a RISC-V emulator. I think I primarily need gcc (obviously) and objcopy from this toolchain.

Wouldn’t using nix-build '<nixpkgs>' -A pkgsCross.riscv64.* compile to for riscv-unkown-linux-gnu?

This is because config apparently tries to be smart and understand both system doubles and autotools triples and a “kernel” part is always expected. Because elf is not a kernel part in nixpkgs, it expects the kernel part in the unknown place. nixpkgs does understand none as kernel, so maybe the following works for you (if I guessed correctly what you want):

let
  crossPkgs = 
    import <nixpkgs> {
      # uses GCC and newlib
      crossSystem = { system = "riscv64-none-elf"; }; 
    };
in

# use crossPkgs' mkShell to use the correct stdenv!
crossPkgs.mkShell {}
1 Like

Yeah, sorry, apologies to both you and @r-burns if I’m being a little unclear. This is all pretty new stuff to me as well.

Let me try to be a little more clear: I’m trying to get started building a RISC-V CPU emulator in Rust as a stupid hobby project. I’m following a short tutorial to get my development environment set up, which includes setting up the toolchain required to build a few test binaries (from C source files) that can be run by the emulator to see if it’s working correctly.

This requires me to have access to a gcc and objcopy that can compile on my host system (x86_64-pc-linux-gnu) with the emulator, which is essentially a riscv64-unkown-elf system, since that binary will be running on “bare metal” (not really but you get what I mean).

To be even clearer, if I were not on Nix/NixOS, I would just do

$ git clone https://github.com/riscv-collab/riscv-gnu-toolchain
$ cd riscv-gnu-toolchain
$ ./configure --prefix=/opt/riscv --with-arch=rv64g
$ make
$ make linux

… and that would compile the tools I needed.

As for your suggestion @sternenseemann, Nix does seem to accept that triple (though I’m still not quite sure if that’s the correct one), but I don’t need any cross-compiled binaries, I need the tools to actually cross-compile some C files in my nix-shell. I was imagining a scenario where I would be able to drop into a nix develop and have the riscv64-unknown-elf-* tools available.

Have a look at Developer environment with a cross compiler

shell.nix:

with import <nixpkgs> {};
pkgsCross.riscv64-embedded.mkShell {}

Then just run nix-shell.

3 Likes

Thank you, that gave me a lot of useful information. Your simple solution seems to work and it gives me all the tools I need.

As a final question, am I to assume then that riscv64-none-efi is the same as riscv64-unknwon-efi? I have never seen the none notation in a host triple before.

nixpkgs, by virtue of using Nix, expresses packages by how they are built. For end users we have package names like hello and these refer to a derivation which is a very detailed build description at the end of the day. As a result toolchains in nixpkgs are not primarily packages for people to install, but actually used for building packages. Specifically every nixpkgs-instantiation exposes a stdenv which inherits stdenv.cc from its buildPackages set (clang or gcc depending on configuration). This stdenv is then used to build most packages in the package set. stdenv implies build environment and, similarly, mkShell uses stdenv (i. e. the build environment) and not a cross-compiled toolchain (which would be targetPackages.stdenv…). Hope this helps a bit, it’s a bit confusing sometimes since stdenv belongs to the wrong package set in a sense.

In part this is also result of the declarative nature of Nix: It often helps to think more in terms of the thing you are trying to build instead in terms of the tools you need to build something else.

@ius’ suggestion is also correct, it is basically equivalent to mine, but riscv64-embedded is a bit more explicit about using newlib internally.

Yes, the riscv64-none-elf tuple (not triple!) is roughly <arch>-<kernel> and the triple riscv64-unknown-none-elf is roughly <arch>-<vendor>-<kernel>. LLVM triples allow unknown for the kernel part (I think they call it OS), but we follow autotools where only none is a valid kernel type.

Interestingly { config = "riscv64-unknown-none-elf"; } and { system = "riscv64-none-elf"; } behave differently (with the latter working with binutils’ configure script and the first not), may be a bug, not sure at this time.

Overall, it’s probably best to use pkgsCross.riscv64-embedded.

Thank you all for the very informative replies, I think I got a better handle on things now!

I completely understand what you’re saying @sternenseemann about approaching things from a declarative packaging/building standpoint rather than a tools standpoint.

Thanks again.