No video device found when compiling SDL's castholm on linux

I am trying to build my oscilloscope project using build.zig and cmake. I can compile and run using cmake:

cmake_minimum_required(VERSION 3.16)

# set the output directory for built objects.
# This makes sure that the dynamic library goes into the build directory automatically.
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")

# prevent installing to system directories. 
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}" CACHE INTERNAL "")

# Declare the project
project(sdl-synth)

set(SDL_SHARED ON)

add_executable(v1)

# Add your sources to the target
target_sources(v1 
PRIVATE 
    v1/main.cpp

    imgui/imgui.cpp
    imgui/imgui_demo.cpp
    imgui/imgui_draw.cpp
    imgui/imgui_tables.cpp
    imgui/imgui_widgets.cpp

    imgui/backends/imgui_impl_sdl3.cpp
    imgui/backends/imgui_impl_sdlrenderer3.cpp

    implot/implot.cpp
    implot/implot.h
    implot/implot_internal.h
    implot/implot_demo.cpp
    implot/implot_items.cpp
)

target_include_directories(v1 PRIVATE imgui/ imgui/backends/ implot/ v1/ )

target_compile_definitions(v1 PRIVATE SDL_MAIN_USE_CALLBACKS)

# Configure SDL by calling its CMake file.
# we use EXCLUDE_FROM_ALL so that its install targets and configs don't
# pollute upwards into our configuration.
add_subdirectory(SDL)

# Link SDL to our executable. This also makes its include directory available to us. 
target_link_libraries(v1 PUBLIC 
    SDL3::SDL3              # If using satelite libraries, SDL must be the last item in the list. 
)

But when I run my app built with build.zig it errors out saying no video driver are availlable:

const builtin = @import("builtin");
const std = @import("std");

pub fn build(b: *std.Build) anyerror!void {
    // Get the default optimization level.
    const optimize = b.standardOptimizeOption(.{});

    // Get the default target, this is the host computer and os.
    const target = b.standardTargetOptions(.{
        .default_target = std.Target.Query{
            .os_tag = builtin.target.os.tag,
        },
    });

    const run = b.step("run", "Run v1");

    const sdl_dep = b.dependency("sdl", .{
        .target = target,
        .optimize = optimize,
    });
    const sdl_lib = sdl_dep.artifact("SDL3");

    const v1_zig_module = b.addModule("v_1_z", .{
        .root_source_file = b.path("v1/generator.zig"),
        .target = target,
        .optimize = optimize,
    });

    const v1_zig_lib = b.addObject(.{
        .root_module = v1_zig_module,
        .name = "v_1_z_lib",
    });

    const v1_cpp_module = b.addModule("v_1", .{
        .target = target,
        .optimize = optimize,
        .link_libcpp = true,
    });
    v1_cpp_module.addObject(v1_zig_lib);
    v1_cpp_module.linkLibrary(sdl_lib);

    const files: []const []const u8 = &.{
        // Main application code
        "v1/main.cpp",

        // Imgui
        "imgui/imgui.cpp",
        "imgui/imgui_demo.cpp",
        "imgui/imgui_draw.cpp",
        "imgui/imgui_tables.cpp",
        "imgui/imgui_widgets.cpp",

        // Imgui backends
        "imgui/backends/imgui_impl_sdl3.cpp",
        "imgui/backends/imgui_impl_sdlrenderer3.cpp",

        // Implot
        "implot/implot.cpp",
        "implot/implot_demo.cpp",
        "implot/implot_items.cpp",
    };

    const flags: []const []const u8 = &.{
        "-DSDL_MAIN_USE_CALLBACKS=1",
    };

    v1_cpp_module.addCSourceFiles(.{
        .files = files,
        .flags = flags,
        .language = .cpp,
        .root = null,
    });

    v1_cpp_module.addIncludePath(b.path("v1/"));
    v1_cpp_module.addIncludePath(b.path("imgui/"));
    v1_cpp_module.addIncludePath(b.path("imgui/backends/"));
    v1_cpp_module.addIncludePath(b.path("implot/"));

    const v1_exe = b.addExecutable(.{
        .root_module = v1_cpp_module,
        .name = "v_1",
    });

    b.installArtifact(v1_exe);

    const runExe = b.addRunArtifact(v1_exe);
    run.dependOn(&runExe.step);
}

I am on ubuntu Noble, using zig 0.15.2.

I’d remove this part completely.
Are you sure it’s compiling to your native os, that is x86_64-linux-gnu and not x86_64-linux-musl?

Funny that S.D.L can compile with musl, but you don’t get any video drivers ! there should be a warning for that.

There would be if you were running a OS that has mesa compiled for musl and you were using a GPU that has mesa drivers. :slight_smile:

SDL loads most video and audio system dependencies dynamically, and glibc and musl each have their own dynamic linker/loader implementations that aren’t interchangeable.

@Etienne_Parmentier I also just ran into this issue. I made a statically linked build to start letting some external folks privately test. I specifically wanted to switch from Debug + native target, to ReleaseSafe and x86-64-linux so I can try and make this as generic as possible. I don’t want to deal with my app not working on different distros, and given I can benefit from Zig’s single static binary + cross compilation, I’m confident I can achieve this goal for probably the majority (if not all) of the linux population .When I did this I noticed that the build succeeded, but SDL failed with No video devices found. After testing and researching for a bit, I tried x86_64-linux-musl and that failed with same issue (it may not have even compiled due to it seemingly being what is already being used by default - which I’m happy with). Then I tried x86_64-linux-gnu, and voila, it worked. For now I can use this to make private test builds. However, since my app will be 2-BSD licensed, I most likely can statically link glibc and legally still distribute my binary and not come into compliance issue with glibc’s LGPL license, although idk if every single library in the statically linked binary is compliant with that license. Although ideally I would just want to be on musl if possible.

@david_vanderson This is in relation to DVUI’s SDL backend. Do you have any tips on still getting the video backend working when linking musl as the main thing for DVUI or is glibc the recommended way to build the project? I may experiment with some of the things @Cloudef and @Etienne_Parmentier said above, but not sure if that would get me a “works on all linux distros” solution. I also found this issue on zig’s github tracker.

EDIT:

Based on what people previously said, I added .linkage = .dynamic to my build.zig.zon and built it for x86_64-linux-musl to see what would happen on my system. It built and I got a general protection error, so not sure if that is related to before or just a different issue altogether.

On Fedora 44 I first ran this and got the following:

$ zig build run -Dtarget=x86_64-linux-musl -Doptimize=ReleaseSafe
run                                                                                                                                 
└─ run exe Cactus failure                                                                                                           
error: the host system is unable to execute binaries from the target                                                                
         because the host dynamic linker is '/lib64/ld-linux-x86-64.so.2',                                                                   while the target dynamic linker is '/lib/ld-musl-x86_64.so.1'.                                                                      consider setting the dynamic linker or enabling skip_foreign_checks in the Run step                                        
failed command: /home/jon/Projects/Cactus/zig-out/bin/Cactus /home/jon/CactusRoot/

I then installed musl-libc and ran it again and got the following:

General protection exception (no address available)
???:?:?: 0x7fb40854a6d5 in ??? (/lib/ld-musl-x86_64.so.1)
???:?:?: 0x7fb40851cb5a in ??? (/lib/ld-musl-x86_64.so.1)
zig-pkg/sdl-0.4.2+3.4.4-SDL--tB1pQFLgwvCakiiOSoZIuVaz6a6-_gOZC66AGRD/src/dynapi/SDL_dynapi.c:518:21: 0x14f13e2 in SDL_InitDynamicAPILocked (/home/jon/Projects/Cactus/zig-pkg/sdl-0.4.2+3.4.4-SDL--tB1pQFLgwvCakiiOSoZIuVaz6a6-_gOZC66AGRD/src/dynapi/SDL_dynapi.c)
    char *libname = getenv(SDL_DYNAMIC_API_ENVVAR); // This should NOT be SDL_getenv()
                    ^
zig-pkg/sdl-0.4.2+3.4.4-SDL--tB1pQFLgwvCakiiOSoZIuVaz6a6-_gOZC66AGRD/src/dynapi/SDL_dynapi_procs.h:817:1: 0x14e76ef in SDL_SetAppMetadata_DEFAULT (/home/jon/Projects/Cactus/zig-pkg/sdl-0.4.2+3.4.4-SDL--tB1pQFLgwvCakiiOSoZIuVaz6a6-_gOZC66AGRD/src/dynapi/SDL_dynapi.c)
SDL_DYNAPI_PROC(bool,SDL_SetAppMetadata,(const char *a, const char *b, const char *c),(a,b,c),return)
^
/home/jon/Projects/Cactus/zig-pkg/dvui-0.5.0-dev-AQFJmTc_9wCyJsvAY3TqdGxWDcfbFbd9kDCCVqrl4Vy7/src/backends/sdl.zig:2092:33: 0x12b114e in main (Cactus)
        _ = c.SDL_SetAppMetadata("DVUI App Example", "0.1", "com.example.dvui.app");
                                ^
/home/jon/.config/Code/User/globalStorage/ziglang.vscode-zig/zig/x86_64-linux-0.16.0/lib/std/start.zig:737:30: 0x12b61a6 in callMain (Cactus)
    return wrapMain(root.main(.{
                             ^
???:?:?: 0x7fb40851c92f in ??? (/lib/ld-musl-x86_64.so.1)
???:?:?: 0x1293c05 in ??? (/home/jon/Projects/Cactus/zig-out/bin/Cactus)
Unwind error at address `/home/jon/Projects/Cactus/zig-out/bin/Cactus:0x1293c05` (unwind info unavailable), remaining frames may be incorrect
run
└─ run exe Cactus failure
error: process terminated with signal ABRT

Zig -glibc target dynamically links to glibc. You also wont get “works on all linux setups” for graphical apps that use GPU without targetting a specific runtime like one of various flatpak runtimes, or steam runtime and even then rarely there might be incompatibilites. Some people gamble with dynamic loader hacks or write their own to load libraries from a system while still being a static binary.

If your deps are low, linking SDL and shipping both glibc and musl binaries can get you quite far already (zig links to older glibc stubs by default and the exact version can be controlled)

Sadly no - this is an area that I have not explored much so I suspect you know more than I do already.

Thanks @Cloudef. I’ll need to run some experiments with a static and dynamic binary and see how easily they run in several standard Linux distros.

@david_vanderson All good.

for any NixOS user who’s looking for soluption “ERROR: No available video device” when building SDL from source (castholm’s sdl) and found this topic, it gets resolved when you link SDL dynamically. but for static linking, I use this shell.nix file I copied from SDL’s nix package

{ pkgs ? import <nixpkgs> {}, stdenv ? pkgs.stdenv }:

let
  # Dynamically determine feature flags based on the platform
  alsaSupport = stdenv.hostPlatform.isLinux && !stdenv.hostPlatform.isAndroid;
  dbusSupport = stdenv.hostPlatform.isLinux;
  drmSupport = stdenv.hostPlatform.isLinux;
  jackSupport = stdenv.hostPlatform.isLinux;
  libdecorSupport = stdenv.hostPlatform.isLinux;
  libudevSupport = stdenv.hostPlatform.isLinux;
  openglSupport = stdenv.hostPlatform.isLinux || stdenv.hostPlatform.isDarwin;
  pipewireSupport = stdenv.hostPlatform.isLinux;
  pulseaudioSupport = stdenv.hostPlatform.isLinux;
  sndioSupport = stdenv.hostPlatform.isLinux;
  waylandSupport = stdenv.hostPlatform.isLinux;
  x11Support = stdenv.hostPlatform.isLinux;

  # Collect runtime dependencies like SDL3 does
  dlopenBuildInputs = with pkgs; [
    vulkan-headers
    vulkan-loader
  ] ++ lib.optionals alsaSupport [ alsa-lib ]
    ++ lib.optionals dbusSupport [ dbus ]
    ++ lib.optionals drmSupport [ libdrm libgbm ]
    ++ lib.optionals jackSupport [ libjack2 ]
    ++ lib.optionals libdecorSupport [ libdecor ]
    ++ lib.optionals libudevSupport [ systemdLibs ]
    ++ lib.optionals openglSupport [ libGL ]
    ++ lib.optionals pipewireSupport [ pipewire ]
    ++ lib.optionals pulseaudioSupport [ libpulseaudio ]
    ++ lib.optionals sndioSupport [ sndio ]
    ++ lib.optionals waylandSupport [ libxkbcommon wayland ]
    ++ lib.optionals x11Support [
      xorg.libX11
      xorg.libXScrnSaver
      xorg.libXcursor
      xorg.libXext
      xorg.libXfixes
      xorg.libXi
      xorg.libXrandr
    ];

in
pkgs.mkShell {
  name = "video-device";

  nativeBuildInputs = with pkgs; [ pkg-config ];

  buildInputs = dlopenBuildInputs;

  shellHook = ''
    export LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath dlopenBuildInputs}:$LD_LIBRARY_PATH
  '';
}

With zig2nix, you can use something like this:

{
  description = "Zig project flake";

  inputs = {
    zig2nix.url = "github:Cloudef/zig2nix";
  };

  outputs = { zig2nix, ... }: let
    flake-utils = zig2nix.inputs.flake-utils;
  in (flake-utils.lib.eachDefaultSystem (system: let
      # Zig flake helper
      # Check the flake.nix in zig2nix project for more options:
      # <https://github.com/Cloudef/zig2nix/blob/master/flake.nix>
      env = zig2nix.outputs.zig-env.${system} { zig = zig2nix.outputs.packages.${system}.zig-0_16_0; };

      frontend_runtime_deps = with env.pkgs; [
        vulkan-loader
        libx11
        libxcursor
        libxi
        libxrandr
        libxkbcommon
        wayland
        alsa-lib
        libGL
      ];

    in {
      # nix run .
      apps.default = env.app [] "zig build run -- \"$@\"";

      # nix run .#build
      apps.build = env.app [] "zig build \"$@\"";

      # nix develop
      devShells.default = env.mkShell {
        nativeBuildInputs = frontend_runtime_deps;
        shellHook = ''
          export VK_LAYER_PATH="${env.pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d"
          '';
      };
    }));
}