linkerldnixgnu-coreutilszig

Wrong dynamic linker used when building Zig from source using Nix


I made the following small Nix derivation to locally build the Zig toolchain from source for me to use locally as part of a Nix shell.

with import (fetchTarball {
  url = "https://github.com/nixos/nixpkgs/tarball/b139b6056c8ad4ef7e0cffb81304d59cf077589b";
  sha256 = "0sn9l19ckvdh227n0rfxk1cjnslhb5hr3g8czf3a436zkyfdl3if";
}) {};

let
  zig = stdenv.mkDerivation rec {

    name = "zig";

    src = fetchFromGitHub {
      owner = "ziglang";
      repo = "zig";
      rev = "476bdc8b0b02cbd09f6a856aa7dc548dea565109";
      hash = "sha256-VdMNzvoWNGvVFiblE7vajOetmHa0hyUWw5tWWVZjKEs=";
    };

    nativeBuildInputs = [
      cmake
      llvmPackages_15.llvm.dev
    ];

    buildInputs = [
      libxml2
      zlib
    ] ++ (with llvmPackages_15; [
      libclang
      lld
      llvm
    ]);

    preBuild = ''
      export HOME=$TMPDIR;
    '';

    doCheck = false;
  };
in
mkShell {
  nativeBuildInputs = [
    zig
  ];
}

My intent is that of improving my knowledge of Nix while also following the recommendation from the Zig getting started page:

It’s fine to evaluate Zig using a tagged version, but if you decide that you like Zig and want to dive deeper, we encourage you to upgrade to a nightly build, mainly because that way it will be easier for you to get help: most of the community and sites like ziglearn.org track the master branch for the reasons stated above.

This works fine on my MacBook Pro (macOS Ventura 13.1.1 on M1 Pro), but when I try to build it on my Linux laptop (Ubuntu 22.04 on Lenovo T480) when the build is at the stage3 phase (IIUC that's when you have a fully bootstrapped Zig compiler which builds "itself") I get a lot of the following errors:

warning: Encountered error: FileNotFound, falling back to default ABI and dynamic linker.

Once the build is done, trying to run any zig subcommand (e.g. running zig version) causes a No such file or directory error to pop up. If I run ldd $(which zig) I see the following output:

    linux-vdso.so.1 (0x00007ffd05bfb000)
    libclang-cpp.so.15 => /nix/store/lchazlk4r4rb1qgifxx7w8fsqgwwd835-clang-15.0.7-lib/lib/libclang-cpp.so.15 (0x00007f5e99600000)
    libstdc++.so.6 => /nix/store/k88zxp7cvd5gpharprhg9ah0vhz2asq7-gcc-12.2.0-lib/lib/libstdc++.so.6 (0x00007f5e99200000)
    libLLVM-15.so => /nix/store/yvcnhajqbwcq35vpnsm1waw557dxcn5v-llvm-15.0.7-lib/lib/libLLVM-15.so (0x00007f5e91200000)
    libz.so.1 => /nix/store/9dz5lmff9ywas225g6cpn34s0wbldnxa-zlib-1.2.13/lib/libz.so.1 (0x00007f5e9d291000)
    libc.so.6 => /nix/store/lqz6hmd86viw83f9qll2ip87jhb7p1ah-glibc-2.35-224/lib/libc.so.6 (0x00007f5e90e00000)
    /lib/ld-musl-x86_64.so.1 => /nix/store/lqz6hmd86viw83f9qll2ip87jhb7p1ah-glibc-2.35-224/lib64/ld-linux-x86-64.so.2 (0x00007f5e9d2b1000)
    libm.so.6 => /nix/store/lqz6hmd86viw83f9qll2ip87jhb7p1ah-glibc-2.35-224/lib/libm.so.6 (0x00007f5e9d1af000)
    libgcc_s.so.1 => /nix/store/lqz6hmd86viw83f9qll2ip87jhb7p1ah-glibc-2.35-224/lib/libgcc_s.so.1 (0x00007f5e9d195000)
    libffi.so.8 => /nix/store/bmnh7b67zx6l5wi6vgjvsfwrzw7ivfph-libffi-3.4.4/lib/libffi.so.8 (0x00007f5e9d188000)
    librt.so.1 => /nix/store/lqz6hmd86viw83f9qll2ip87jhb7p1ah-glibc-2.35-224/lib/librt.so.1 (0x00007f5e9d181000)
    libdl.so.2 => /nix/store/lqz6hmd86viw83f9qll2ip87jhb7p1ah-glibc-2.35-224/lib/libdl.so.2 (0x00007f5e9d17c000)
    libncursesw.so.6 => /nix/store/35s126gfkfrwbiv49kv9kxqdpd9zvcvm-ncurses-6.4/lib/libncursesw.so.6 (0x00007f5e9d108000)
    libxml2.so.2 => /nix/store/yz7qfy85damywy6397vp7nqly0y4qm2r-libxml2-2.10.3/lib/libxml2.so.2 (0x00007f5e9949a000)

The problematic line seems to be the following, where there seems to be a mismatch:

    /lib/ld-musl-x86_64.so.1 => /nix/store/lqz6hmd86viw83f9qll2ip87jhb7p1ah-glibc-2.35-224/lib64/ld-linux-x86-64.so.2 (0x00007f5e9d2b1000)

Running sudo patchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 $(which zig) patches the problem, but I'd like to get to a clean solution whereby the right linker is found and used and the derivation works across operating systems.

I tried to add musl to the build inputs for the Zig compiler derivation but it started throwing a lot of compilation errors and I quickly backed off.

Assuming I need to point the Zig compiler to the right linker, how can I do that? If I'm wrong in my assumption, how can I solve this problem?


Solution

  • I originally based my derivation on the one I could find on the nixpkgs stable 22.11 channel which builds Zig 0.9.1 (which is quite old). Looking at the one in the unstable channel (which builds the latest stable release, 0.10.1) I noticed the following which was lacking in the derivation for the previous version:

      postPatch = ''
        # Zig's build looks at /usr/bin/env to find dynamic linking info. This
        # doesn't work in Nix' sandbox. Use env from our coreutils instead.
        substituteInPlace lib/std/zig/system/NativeTargetInfo.zig --replace "/usr/bin/env" "${coreutils}/bin/env"
      '';
    

    To the best of my understanding, the big change starting with Zig 0.10.x is that the Zig compiler is now bootstrapped, which is probably why I experienced this problem. I'm slightly more confused as to why this works on macOS, but that's probably a problem for another day.

    As such, I applied the following modifications:

    diff --git a/shell.nix b/shell.nix
    index f554c8d..bb466de 100644
    --- a/shell.nix
    +++ b/shell.nix
    @@ -21,6 +21,7 @@ let
         ];
     
         buildInputs = [
    +      coreutils
           libxml2
           zlib
         ] ++ (with llvmPackages_15; [
    @@ -33,7 +34,25 @@ let
           export HOME=$TMPDIR;
         '';
     
    -    doCheck = false;
    +    postPatch = ''
    +      # Zig's build looks at /usr/bin/env to find dynamic linking info. This
    +      # doesn't work in Nix' sandbox. Use env from our coreutils instead.
    +      substituteInPlace lib/std/zig/system/NativeTargetInfo.zig --replace "/usr/bin/env" "${coreutils}/bin/env"
    +    '';
    +
    +    cmakeFlags = [
    +      # file RPATH_CHANGE could not write new RPATH
    +      "-DCMAKE_SKIP_BUILD_RPATH=ON"
    +
    +      # ensure determinism in the compiler build
    +      "-DZIG_TARGET_MCPU=baseline"
    +    ];
    +
    +    doCheck = true;
    +    installCheckPhase = ''
    +      $out/bin/zig test --cache-dir "$TMPDIR" -I $src/test $src/test/behavior.zig
    +    '';
    +
       };
     in
     mkShell {
    

    And it works! Tested on both my Linux and macOS laptops!