What are the different methods to run a non-nixos executable on NixOs? I'd like to see also the manual methods.
1 Answer
Short version
Quick and dirty: make sure steam-run
is installed (strange name, it has nothing to do with steam), e.g. nix-shell -p steam-run
, then:
$ steam-run ./your-binary
Here is a longer and more detailed explanation, together with various methods, often less dirty.
Long version
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
- https://github.com/NixOS/nixpkgs/blob/5a9eaf02ae3c6403ce6f23d33ae569be3f9ce644/pkgs/applications/video/lightworks/default.nix
- https://sandervanderburg.blogspot.com/2015/10/deploying-prebuilt-binary-software-with.html
- https://github.com/NixOS/nixpkgs/blob/35c3396f41ec73c5e968a11f46d79a94db4042d7/pkgs/applications/networking/dropbox/default.nix
-
-
1
-
@Busti Thanks, I was not expecting this answer to get much attention, I added your comment at the beginning of the answer. Mar 4 at 19:53