Undefined symbol for glfw on macos

I’m trying to follow this Vulkan set-up: https://www.youtube.com/watch?v=vhGDwnjNb5M

But, before vulkan even gets involved, I’m getting errors about undefined symbols for glfw.

error: undefined symbol: _glfwGetVersion
    note: referenced by main.o:_main.main

I’m using GitHub - IridescenceTech/zglfw: A thin, idiomatic wrapper for GLFW. Written in Zig, for Zig! and ran brew install glfw beforehand.
This is my build.zig, I imagine I’m not pointing to the right location for the lib files?

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    
    const exe = b.addExecutable(.{
        .name = "crumble",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    
    const glfw = b.dependency("zglfw", .{
        .target = target,
        .optimize = optimize,
    });
    exe.root_module.addImport("glfw", glfw.module("glfw"));
    
    exe.addLibraryPath(.{ .cwd_relative = "/usr/local/lib/"});
    
    b.installArtifact(exe);
    ...

Apologies if this is something simple that I’m missing.

I don’t see an exe.linkLibrary line as in the zglfw README/examples:

    if (target.result.os.tag != .emscripten) {
        exe.linkLibrary(zglfw.artifact("glfw"));
    }

Now I’m getting thread 330445 panic: unable to find artifact glfw. Though where did you find these examples?
The github page I saw was quite barebones, with no mention of build.zig.
Not sure if there’s a different location for docs I should be checking?

EDIT: Realised you are referring to zig-gamedev/zglfw, I’d added which package I was using just as you were replying. Apologies.

just had a quick look at GitHub - IridescenceTech/zglfw: A thin, idiomatic wrapper for GLFW. Written in Zig, for Zig!. Sounds like the project only provides a zig API to glfw so you’re right: you need to point the build.zig to the correct location of the glfw library (usually libglfw3.a or libglfw.dylib). Sidenote: the naming is quite confusing given zig-gamedevs zglfw.

You already have that line:

exe.addLibraryPath(.{ .cwd_relative = "/usr/local/lib/"});

but if glfw has been installed via Homebrew it might actually be in a different location. For me it’s located under /opt/homebrew/Cellar/glfw/3.4/lib, which yields the following for build.zig:

exe.addLibraryPath(.{ .cwd_relative = "/opt/homebrew/Cellar/glfw/3.4/lib"});

brew --prefix glfw should give you the path where Homebrew installed glfw on your system.


btw a nice way to check for the presence of any filename in a folder is using grep:

$ cd /usr/local/lib
$ ls | grep glfw

so if the prompt is empty the folder doesn’t contain a file containing “glfw” in its name.

1 Like

Thanks, definitely learned some stuff here.
Still have the issue though ;(
I’ve replaced then added both LibraryPath lines, but still getting the same error of unknown symbols.
The .dylib files (which I asume is what the package needs are in both, they are symlinked from the directory I put to (my version of) the directory you referenced.

In /usr/local/lib:

mason@Masons-MacBook-Air lib % ls -la | grep glfw
lrwxr-xr-x     1 mason  admin     40  9 Sep 18:34 libglfw.3.4.dylib -> ../Cellar/glfw/3.4/lib/libglfw.3.4.dylib
lrwxr-xr-x     1 mason  admin     38  9 Sep 18:34 libglfw.3.dylib -> ../Cellar/glfw/3.4/lib/libglfw.3.dylib
lrwxr-xr-x     1 mason  admin     36  9 Sep 18:34 libglfw.dylib -> ../Cellar/glfw/3.4/lib/libglfw.dylib
lrwxr-xr-x     1 mason  admin     33  9 Sep 18:34 libglfw3.a -> ../Cellar/glfw/3.4/lib/libglfw3.a

In /usr/local/Cellar/glfw/3.4/lib:

mason@Masons-MacBook-Air lib % ls -la | grep glfw
-r--r--r--  1 mason  admin  251904  9 Sep 18:34 libglfw.3.4.dylib
lrwxr-xr-x  1 mason  admin      17  9 Sep 18:34 libglfw.3.dylib -> libglfw.3.4.dylib
lrwxr-xr-x  1 mason  admin      15  9 Sep 18:34 libglfw.dylib -> libglfw.3.dylib
-r--r--r--  1 mason  admin  324568  9 Sep 18:33 libglfw3.a

My guess is that it’s related to this line:

exe.addLibraryPath(.{ .cwd_relative = "/usr/local/lib/"});

It may not be considering /usr/ as an absolute path, and might be trying to look for your library relative to the current working directory, which would be a bad idea.
Considering that you are installing glfw as a library directly to your system, you could instead try using exe.root_module.linkSystemLibrary("libglfw3").

2 Likes

Slight edit to the line, but this worked. Thanks :slight_smile:

That function expected a second argument for options it seems, so I just passed an empty struct and it did the job.
Then it couldn’t find “liblibglfw3” so I removed the lib at the start.

Line I used:
exe.root_module.linkSystemLibrary("glfw3", .{})

Hopefully that’s the last of the issues with the set-up :grimacing:

1 Like

Tbh, you’d be much better off linking with GLFW as static library via some all-in-one Zig package (e.g. a Zig package which also pulls and compiles the GLFW C sources into a static link library), this will also automatically fix any cross-platform problems, and users don’t need to manually install a system-wide GLFW to make your code build and/or run on their machine.

4 Likes

I recently walked this path and ended up compiling the glfw source files I needed in my build.zig. It won’t be straightforward to copy since mine is for linux/wayland, but it might give you an idea of the process.

My workflow for getting this working was: while going through the glfw tutorial, call out to some extern fn and then see what the compiler complained about missing, then grep the glfw source for that definition and add the c file to my build.zig.

FWIW I just did a quick test with GitHub - zig-gamedev/zglfw: Zig build package and bindings for GLFW and that worked just fine (on my MBP).

build.zig.zon:

.{
    .name = .bla,
    .version = "0.0.0",
    .fingerprint = 0x47efb3399a892ddc, // Changing this has security and trust implications.
    .minimum_zig_version = "0.16.0-dev.27+83f773fc6",
    .dependencies = .{
        .zglfw = .{
            .url = "git+https://github.com/zig-gamedev/zglfw#82da052ccace5690f323876046d3062c9c573283",
            .hash = "zglfw-0.10.0-dev-zgVDNK6oIQAamIbSG6JGubpBiQSxrv_lymMIsub2DBNa",
        },
    },
    .paths = .{
        "build.zig",
        "build.zig.zon",
        "src",
    },
}

build.zig:

const std = @import("std");
pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    const zglfw = b.dependency("zglfw", .{});
    const exe = b.addExecutable(.{
        .name = "bla",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "glfw", .module = zglfw.module("root") },
            },
        }),
    });
    exe.linkLibrary(zglfw.artifact("glfw"));
    b.installArtifact(exe);
    const run_step = b.step("run", "Run the app");
    const run_cmd = b.addRunArtifact(exe);
    run_step.dependOn(&run_cmd.step);
    run_cmd.step.dependOn(b.getInstallStep());
}

…and src/main.zig:

const glfw = @import("glfw");

pub fn main() !void {
    try glfw.init();
    defer glfw.terminate();
    const window = try glfw.createWindow(600, 600, "zig-gamedev: minimal_glfw_gl", null);
    defer glfw.destroyWindow(window);
    while (!window.shouldClose()) {
        glfw.pollEvents();
        window.swapBuffers();
    }
}

…and then simply zig build run.

PS: the zglfw package missed an opportunity for simplification and link the C library to the zglfw module. Then this line wouldn’t be needed in the top-level build.zig:

exe.linkLibrary(zglfw.artifact("glfw"));

…e.g. that’s what I do in the sokol-zig package’s build.zig:

2 Likes