9

What are the different methods to run a non-nixos executable on NixOs? I'd like to see also the manual methods.

15

Here are several methods (the manual ones are mostly for educational purpose as most of the time writing a proper derivation is better). I'm not an expert at all, and I did this list also to learn nix, so if you have better methods, let me know!

So the main issue is that the executable call first a loader, and then needs some libraries to work, and nixos put both the loader and the libraries in /nix/store/.

This list gives all the methods I found so far. There are basically three "groups":

  • the full manual: interesting for educational purpose, and to understand what's going on, but that's all (don't use them in practice because nothing will prevent the derivations used to be garbage collected later)
  • the patched versions: these methods try to modify the executable (automatically when using the recommended method 4 with autoPatchelfHook) to make the point to the good library directly
  • the methods based on FHS, that basically fake a "normal linux" (more heavy to run than the patched version, so this should be avoided if possible).

I would recommend method 4 with autoPatchelfHook for a real, proper setup, and if you don't have time and just want to run a binary in one-line, you may be interested by the quick-and-dirty solution based on steam-run (method 7).

Method 1) Dirty manual method, no patch

You need to first find the loader with for example file:

$ file wolframscript
wolframscript: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.18, BuildID[sha1]=079684175aa38e3633b60544681b338c0e8831e0, stripped

Here the loader is /lib64/ld-linux-x86-64.so.2. To find the loader of nixos, you can do:

$ ls /nix/store/*glibc*/lib/ld-linux-x86-64.so.2
/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ld-linux-x86-64.so.2

You also need to find to find the libraries that your program require, for example with ldd:

$ ldd wolframscript
        linux-vdso.so.1 (0x00007ffe8fff9000)
        libpthread.so.0 => /nix/store/sw54ph775lw7b9g4hlfvpx6fmlvdy8qi-glibc-2.27/lib/libpthread.so.0 (0x00007f86aa321000)
        librt.so.1 => /nix/store/sw54ph775lw7b9g4hlfvpx6fmlvdy8qi-glibc-2.27/lib/librt.so.1 (0x00007f86aa317000)
        libdl.so.2 => /nix/store/sw54ph775lw7b9g4hlfvpx6fmlvdy8qi-glibc-2.27/lib/libdl.so.2 (0x00007f86aa312000)
        libstdc++.so.6 => not found
        libm.so.6 => /nix/store/sw54ph775lw7b9g4hlfvpx6fmlvdy8qi-glibc-2.27/lib/libm.so.6 (0x00007f86aa17c000)
        libgcc_s.so.1 => /nix/store/sw54ph775lw7b9g4hlfvpx6fmlvdy8qi-glibc-2.27/lib/libgcc_s.so.1 (0x00007f86a9f66000)
        libc.so.6 => /nix/store/sw54ph775lw7b9g4hlfvpx6fmlvdy8qi-glibc-2.27/lib/libc.so.6 (0x00007f86a9dae000)
        /lib64/ld-linux-x86-64.so.2 => /nix/store/sw54ph775lw7b9g4hlfvpx6fmlvdy8qi-glibc-2.27/lib64/ld-linux-x86-64.so.2 (0x00007f86aa344000)

Here, you see that most libraries are found except libstdc++.so.6. So let's find it:

$ find /nix/store -name libstdc++.so.6
/nix/store/12zhmzzhrwszdc8q3fwgifpwjkwi3mzc-gcc-7.3.0-lib/lib/libstdc++.so.6

Good. Now, we just need to run the program with the LD_LIBRARY_PATH configured to point to this file, and call the loader we determined at the first step on this file:

LD_LIBRARY_PATH=/nix/store/12zhmzzhrwszdc8q3fwgifpwjkwi3mzc-gcc-7.3.0-lib/lib/:$LD_LIBRARY_PATH /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ld-linux-x86-64.so.2 ./wolframscript

(make sure to use ./ before the script name, and to keep only the directory of the libraries. If you have several libraries, just use concat the path with colons)

Method 2) Dirty manual method, with patch

After installing (with nixenv -i or in your configuration.nix) patchelf, you can also directly modify the executable to pack the good loader and libraries. To change the loader just run:

patchelf --set-interpreter /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ld-linux-x86-64.so.2 wolframscript

and to check:

$ patchelf --print-interpreter wolframscript
/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ld-linux-x86-64.so.

and to change the path to the libraries hardcoded in the executable, first check what is the current rpath (empty for me):

$ patchelf --print-rpath wolframscript

and append them to the library path you determined before, eventually separated with colons:

$ patchelf --set-rpath /nix/store/12zhmzzhrwszdc8q3fwgifpwjkwi3mzc-gcc-7.3.0-lib/lib/ wolframscript
$ ./wolframscript

Method 3) Patch in a nix derivation

We can reproduce more or less the same thing in a nix derivation inspired by skypeforlinux

This example presents also an alternative, either you can use:

patchelf --set-interpreter ${glibc}/lib/ld-linux-x86-64.so.2 "$out/bin/wolframscript" || true

(which should be pretty clear once you understand the "manual" method), or

patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" "$out/bin/wolframscript" || true

This second method is a bit more subtle, but if you run:

$ nix-shell '<nixpkgs>' -A hello --run 'echo $NIX_CC/nix-support/dynamic-linker "->" $(cat $NIX_CC/nix-support/dynamic-linker)'
/nix/store/8zfm4i1aw4c3l5n6ay311ds6l8vd9983-gcc-wrapper-7.4.0/nix-support/dynamic-linker -> /nix/store/sw54ph775lw7b9g4hlfvpx6fmlvdy8qi-glibc-2.27/lib/ld-linux-x86-64.so.2

you will see that the file $NIX_CC/nix-support/dynamic-linker contains a path to the loader ld-linux-x86-64.so.2.

Put in derivation.nix, this is

{ stdenv, dpkg,glibc, gcc-unwrapped }:
let

  # Please keep the version x.y.0.z and do not update to x.y.76.z because the
  # source of the latter disappears much faster.
  version = "12.0.0";

  rpath = stdenv.lib.makeLibraryPath [
    gcc-unwrapped
    glibc
  ];
  # What is it for?
  # + ":${stdenv.cc.cc.lib}/lib64";

  src = ./WolframScript_12.0.0_LINUX64_amd64.deb;

in stdenv.mkDerivation {
  name = "wolframscript-${version}";

  system = "x86_64-linux";

  inherit src;

  nativeBuildInputs = [
  ];

  buildInputs = [ dpkg ];

  unpackPhase = "true";

  # Extract and copy executable in $out/bin
  installPhase = ''
    mkdir -p $out
    dpkg -x $src $out
    cp -av $out/opt/Wolfram/WolframScript/* $out
    rm -rf $out/opt
  '';

  postFixup = ''
    # Why does the following works?
    patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" "$out/bin/wolframscript" || true
    # or
    # patchelf --set-interpreter ${glibc}/lib/ld-linux-x86-64.so.2 "$out/bin/wolframscript" || true
    patchelf --set-rpath ${rpath} "$out/bin/wolframscript" || true
  '';

  meta = with stdenv.lib; {
    description = "Wolframscript";
    homepage = https://www.wolfram.com/wolframscript/;
    license = licenses.unfree;
    maintainers = with stdenv.lib.maintainers; [ ];
    platforms = [ "x86_64-linux" ];
  };
}

and in default.nix put:

{ pkgs ? import <nixpkgs> {} }:

pkgs.callPackage ./derivation.nix {}

Compile and run with

nix-build
result/bin/wolframscript

Method 4) Use autoPatchElf: simpler

All the previous methods need a bit of work (you need to find the executables, patch them...). NixOs did for us a special "hook" autoPatchelfHook that automatically patches everything for you! You just need to specify it in (native)BuildInputs, and nix does the magic.

{ stdenv, dpkg, glibc, gcc-unwrapped, autoPatchelfHook }:
let

  # Please keep the version x.y.0.z and do not update to x.y.76.z because the
  # source of the latter disappears much faster.
  version = "12.0.0";

  src = ./WolframScript_12.0.0_LINUX64_amd64.deb;

in stdenv.mkDerivation {
  name = "wolframscript-${version}";

  system = "x86_64-linux";

  inherit src;

  # Required for compilation
  nativeBuildInputs = [
    autoPatchelfHook # Automatically setup the loader, and do the magic
    dpkg
  ];

  # Required at running time
  buildInputs = [
    glibc
    gcc-unwrapped
  ];

  unpackPhase = "true";

  # Extract and copy executable in $out/bin
  installPhase = ''
    mkdir -p $out
    dpkg -x $src $out
    cp -av $out/opt/Wolfram/WolframScript/* $out
    rm -rf $out/opt
  '';

  meta = with stdenv.lib; {
    description = "Wolframscript";
    homepage = https://www.wolfram.com/wolframscript/;
    license = licenses.mit;
    maintainers = with stdenv.lib.maintainers; [ ];
    platforms = [ "x86_64-linux" ];
  };
}

Method 5) Use FHS to simulate a classic linux shell, and manually execute the files

Some sofware may be hard to package that way because they may heavily rely on the FHS file tree structure, or may check that the binary are unchanged. You can then also use buildFHSUserEnv to provide an FHS file structure (lightweight, using namespaces) for your application. Note that this method is heavier that the patch-based methods, and add significant startup time, so avoid it when possible

You can either just spawn a shell and then manually extract the archive and execute the file, or directly package your program for the FHS. Let's first see how to get a shell. Put in a file (say fhs-env.nix) the following:

let nixpkgs = import <nixpkgs> {};
in nixpkgs.buildFHSUserEnv {
   name = "fhs";
   targetPkgs = pkgs: [];
   multiPkgs = pkgs: [ pkgs.dpkg ];
   runScript = "bash";
}

and run:

nix-build fhs-env.nix
result/bin/fhs

You will then get a bash in a more standard-looking linux, and you can run commands to run your executable, like:

mkdir wolf_fhs/
dpkg -x WolframScript_12.0.0_LINUX64_amd64.deb wolf_fhs/
cd wolf_fhs/opt/Wolfram/WolframScript/bin/
./wolfram

If you need more libraries/programs as dependencies, just add them to multiPkgs (for all supported archs) or targetPkgs (for current arch only).

Bonus: you can also launch a fhs shell with a one line command, without creating a specifc file:

nix-build -E '(import <nixpkgs> {}).buildFHSUserEnv {name = "fhs";}' && ./result/bin/fhs

Method 6) Use FHS to simulate a classic linux shell, and pack the files inside

source: https://reflexivereflection.com/posts/2015-02-28-deb-installation-nixos.html

Method 7) steam-run

With buildFHSUserEnv you can run lot's of softwares, but you will need to specify manually all the required libraries. If you want a quick solution and you don't have time to check precisely what are the required libraries, you may want to try steam-run (despite the name, it is not linked directly with steam, and just packs lots of libraries), which is like buildFHSUserEnv with lot's of common libraries preinstalled (some of them may be non-free like steamrt that packs some nvidia code, thanks simpson!). To use it, just install steam-run, and then:

steam-run ./wolframscript

or if you want a full shell:

steam-run bash

Note that you may need to add nixpkgs.config.allowUnfree = true; (or whitelist this specific package) if you want to install it with nixos-rebuild, and if you want to run/install it with nix-shell/nix-env you need to put { allowUnfree = true; } in ~/.config/nixpkgs/config.nix.

It is not easy to "overwrite" packages or libraries to nix-shell, but if you want to make a wrapper around your script, you can either manually create a wrapper script:

#!/usr/bin/env nix-shell
#!nix-shell -i bash -p steam-run
exec steam-run ./wolframscript "$@"

or directly write it in a nixos derivation :

{ stdenv, steam-run, writeScriptBin }:
let
  src = ./opt/Wolfram/WolframScript/bin/wolframscript;
in writeScriptBin "wolf_wrapped_steam" ''
    exec ${steam-run}/bin/steam-run ${src} "$@"
  ''

or if you start from the .deb (here I used makeWrapper instead):

{ stdenv, steam-run, dpkg, writeScriptBin, makeWrapper }:
stdenv.mkDerivation {
  name = "wolframscript";
  src = ./WolframScript_12.0.0_LINUX64_amd64.deb;

  nativeBuildInputs = [
    dpkg makeWrapper
  ];
  unpackPhase = "true";
  installPhase = ''
    mkdir -p $out/bin
    dpkg -x $src $out
    cp -av $out/opt/Wolfram/WolframScript/bin/wolframscript $out/bin/.wolframscript-unwrapped
    makeWrapper ${steam-run}/bin/steam-run $out/bin/wolframscript --add-flags $out/bin/.wolframscript-unwrapped
    rm -rf $out/opt
  '';
}

(if you are too tired to write the usual default.nix, you can run directly nix-build -E "with import <nixpkgs> {}; callPackage ./derivation.nix {}")

Method 8) Using containers/Docker (much heavier)

TODO

Method 9) Rely on flatpack/appimage

https://nixos.org/nixos/manual/index.html#module-services-flatpak

appimage-run : To test with, ex, musescore

Sources or examples

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.