Build fails due to "unknown file type" of object file

I’m trying to use stb_image.h, a C library fully contained in a single header file.

Here’s my build.zig:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "vulkan",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
        .link_libc = true,
    });

    exe.linkSystemLibrary("glfw");
    exe.linkSystemLibrary("vulkan");

    // Relevant:
    exe.addCSourceFile(.{ .file = b.path("vendor/stb_image.h") });
    exe.addIncludePath(b.path("vendor/"));

    b.installArtifact(exe);

    const shader_step = createShaderStep(b);
    exe.step.dependOn(shader_step);

    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }
    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

// ... remainder truncated

zig build fails with:

error: ld.lld: /home/joe/dev/vulkan/.zig-cache/o/f27ee214c582d247cd1583532d664e74/stb_image.o: unknown file type
error: the following command failed with 1 compilation errors:
/home/joe/.local/bin/zig build-exe -lglfw -lvulkan /home/joe/dev/vulkan/vendor/stb_image.h -ODebug -I /home/joe/dev/vulkan/vendor -Mroot=/home/joe/dev/vulkan/src/main.zig -lc --cache-dir /home/joe/dev/vulkan/.zig-cache --global-cache-dir /home/joe/.cache/zig --name vulkan --zig-lib-dir /home/joe/.local/lib/zig/ --listen=-

The file in question:

$ file .zig-cache/o/f27ee214c582d247cd1583532d664e74/stb_image.o
.zig-cache/o/f27ee214c582d247cd1583532d664e74/stb_image.o: LLVM Pre-compiled header file

The way I’ve seen most header-only things work is that in some C source module, you’ll #define an “implement_foo” then include the header, and where you want to reference the functions, you simply include the header. In this case, you’ll create a stub C file to provide that implementation and statically link it as a library. There are exceptions where everything is inline so you’d just @cInclude the header (not build it separately). The latter often doesn’t work due to the limitations of cImport in Zig. You’ll need to look at which case stb_image is, and where a simple direct import works or not.

Or, you can just look at other projects that consume stb_image and copy what they do (there are plenty around).

1 Like

You are correct in that I had forgotten to define STB_IMAGE_IMPLEMENTATION, however after this I also had to remove the b.addCSourceFile call in build.zig. So the steps are

  1. exe.AddIncludePath, to expose the directory the header is in.
  2. @cDefine("STB_IMAGE_IMPLEMENTATION", "")
  3. @cInclude("stb_image.h")

Still not entirely sure why I was getting that specific error, but thanks for the help.

addCSourceFile expects a .c file extension. It doesn’t work with .h.
The way you’re doing now is relying on cImport to translate the implementation for you. This has failed for me a couple of times, as Zig’s translation is still rather limited, working mostly for definitions only. If it’s working for you, that’s fine, but a more general approach would be to create a impl.c file with just:

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

Then you addCSourceFile this file (don’t forget that it needs stb_image.h to be in its include path).

3 Likes