Métodos diferentes para executar um executável não-nixos no Nixos

12

Quais são os diferentes métodos para executar um executável não-nixos no NixOs? Eu gostaria de ver também os métodos manuais.

tobiasBora
fonte

Respostas:

22

Aqui estão vários métodos (os manuais são principalmente para fins educacionais, pois na maioria das vezes é melhor escrever uma derivação adequada). Eu não sou um especialista, e fiz essa lista também para aprender o nix; portanto, se você tiver métodos melhores, informe-me!

Portanto, a questão principal é que o executável chama primeiro um carregador e, em seguida, precisa de algumas bibliotecas para funcionar, e o nixos coloca o carregador e as bibliotecas /nix/store/.

Esta lista fornece todos os métodos que encontrei até agora. Existem basicamente três "grupos":

  • o manual completo: interessante para fins educacionais e para entender o que está acontecendo, mas é tudo (não os use na prática, porque nada impedirá que as derivações sejam coletadas posteriormente no lixo)
  • as versões corrigidas: esses métodos tentam modificar o executável (automaticamente ao usar o método recomendado 4 com autoPatchelfHook) para apontar diretamente para a boa biblioteca
  • os métodos baseados no FHS, que basicamente falsificam um "linux normal" (mais pesado de executar do que a versão corrigida, portanto, isso deve ser evitado, se possível).

Eu recomendaria o método 4 autoPatchelfHookpara uma configuração real e adequada e, se você não tiver tempo e apenas desejar executar um binário em uma linha, poderá se interessar pela solução rápida e suja baseada em steam-run(método 7 )

Método 1) Método manual sujo, sem patch

Você precisa primeiro encontrar o carregador com, por exemplo 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

Aqui está o carregador /lib64/ld-linux-x86-64.so.2. Para encontrar o carregador de nixos, você pode:

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

Você também precisa encontrar as bibliotecas necessárias para o seu programa, por exemplo 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)

Aqui, você vê que a maioria das bibliotecas é encontrada, exceto libstdc++.so.6. Então, vamos encontrá-lo:

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

Boa. Agora, precisamos apenas executar o programa com o LD_LIBRARY_PATHconfigurado para apontar para esse arquivo e chamar o carregador que determinamos na primeira etapa deste arquivo:

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

(certifique-se de usar ./antes do nome do script e manter apenas o diretório das bibliotecas. Se você tiver várias bibliotecas, use concat o caminho com dois pontos)

Método 2) Método manual sujo, com patch

Após a instalação (com nixenv -iou no seu configuration.nix) patchelf, você também pode modificar diretamente o executável para compactar o bom carregador e as bibliotecas. Para mudar o carregador, basta executar:

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

e verificar:

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

e para alterar o caminho para as bibliotecas codificadas no executável, verifique primeiro qual é o rpath atual (vazio para mim):

$ patchelf --print-rpath wolframscript

e anexá-los ao caminho da biblioteca que você determinou antes, eventualmente separados por dois pontos:

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

Método 3) Patch em uma derivação nix

Podemos reproduzir mais ou menos a mesma coisa em uma derivação nix inspirada no skypeforlinux

Este exemplo também apresenta uma alternativa, você pode usar:

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

(que deve ficar bem claro depois que você entender o método "manual") ou

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

Este segundo método é um pouco mais sutil, mas se você executar:

$ 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

você verá que o arquivo $NIX_CC/nix-support/dynamic-linkercontém um caminho para o carregador ld-linux-x86-64.so.2.

Coloque derivation.nix, isso é

{ 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" ];
  };
}

e em default.nixcolocar:

{ pkgs ? import <nixpkgs> {} }:

pkgs.callPackage ./derivation.nix {}

Compile e execute com

nix-build
result/bin/wolframscript

Método 4) Use autoPatchElf: mais simples

Todos os métodos anteriores precisam de um pouco de trabalho (você precisa encontrar os executáveis, corrigi-los ...). O NixOs fez para nós um "gancho" especial que corrige autoPatchelfHooktudo automaticamente para você! Você só precisa especificá-lo (native)BuildInputs, e o nix faz a mágica.

{ 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" ];
  };
}

Método 5) Use o FHS para simular um shell linux clássico e execute manualmente os arquivos

Alguns softwares podem ser difíceis de compactar dessa maneira, porque podem depender fortemente da estrutura da árvore de arquivos do FHS ou podem verificar se os binários não foram alterados. Você também pode usar o buildFHSUserEnv para fornecer uma estrutura de arquivo FHS (leve, usando espaços para nome) para seu aplicativo. Observe que esse método é mais pesado que os métodos baseados em patches e adiciona um tempo de inicialização significativo, portanto evite-o quando possível

Você pode gerar um shell e extrair manualmente o arquivo morto e executá-lo, ou empacotar diretamente seu programa para o FHS. Vamos primeiro ver como obter um shell. Coloque em um arquivo (digamos fhs-env.nix) o seguinte:

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

e corra:

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

Você receberá um bash em um linux de aparência mais padrão e poderá executar comandos para executar seu executável, como:

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

Se você precisar de mais bibliotecas / programas como dependências, adicione-os a multiPkgs(para todos os arcos suportados) ou targetPkgs(apenas para o arco atual).

Bônus: você também pode iniciar um shell fhs com um comando de uma linha, sem criar um arquivo específico:

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

Método 6) Use o FHS para simular um shell linux clássico e empacote os arquivos dentro

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

Método 7) a vapor

Com buildFHSUserEnvvocê pode executar muitos softwares, mas você precisará especificar manualmente todas as bibliotecas necessárias. Se você deseja uma solução rápida e não tem tempo para verificar com precisão quais são as bibliotecas necessárias, tente steam-run(apesar do nome, ele não está diretamente vinculado ao steam e apenas comporta muitas bibliotecas), o que é como buildFHSUserEnvcom muitas bibliotecas comuns pré-instaladas (algumas delas podem não ser livres como steamrtessa que contém algum código da nvidia, obrigado simpson!). Para usá-lo, basta instalar steam-rune, em seguida:

steam-run ./wolframscript

ou se você quiser um shell completo:

steam-run bash

Note que você pode precisar adicionar nixpkgs.config.allowUnfree = true;(ou whitelist este pacote específico ), se você quiser instalá-lo com nixos-rebuild, e se você deseja executar / instalá-lo com nix-shell/ nix-envvocê precisa colocar { allowUnfree = true; }em ~/.config/nixpkgs/config.nix.

Não é fácil "sobrescrever" pacotes ou bibliotecas para o nix-shell, mas se você deseja criar um wrapper em torno de seu script, é possível criar manualmente um script de wrapper:

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

ou escreva-o diretamente em uma derivação nixos:

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

ou se você começar do .deb (aqui eu usei makeWrapper):

{ 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
  '';
}

(se você está cansado demais para escrever o habitual default.nix, pode correr diretamente nix-build -E "with import <nixpkgs> {}; callPackage ./derivation.nix {}")

Método 8) Usando contêineres / Docker (muito mais pesado)

FAÇAM

Método 9) Confie no flatpack / appimage

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

appimage-run: para testar com, ex, musescore

Fontes ou exemplos

tobiasBora
fonte