How can I build a c++ project with static lib SDL3?

Hey, trying to figure out how I can build a basic c++ project with a static library using Zig. Linking with AllYourCodebase’s SDL2 works fine, but I want to understand better how to link static libraries, to be able to do more.
So, for my project, I have 3 root folders, ‘include’, ‘lib’, and ‘src’, with ‘include’ and ‘lib’ containing ‘SDL3’ folders from SDL’s github, SDL3-devel-3.1.6-VC.zip. (I’m unaware if Zig’s build system can use MSVC or Mingw32’s libraries, I have tried both). I then I have code for the executable like this:

const exe = b.addExecutable(.{ .name = PROGRAM_NAME, .target = target });
exe.addCSourceFiles(.{
    .files = &[_][]const u8{"src/main.cpp"},
    .flags = &[_][]const u8{},
});
exe.addIncludePath(b.path("include"));
exe.linkLibC();
exe.linkLibCpp();

// Libs
if (target.query.isNativeOs() and target.result.os.tag == .windows) {
    exe.addLibraryPath(b.path("lib/SDL3"));
    b.installBinFile("lib/SDL3/SDL3.dll", "SDL3.dll");
} else {
    exe.linkSystemLibrary("SDL3");
}
b.installArtifact(exe);

Zig build can use msvc and mingw libs.

You posted a statement and a build.zig, but didn’t ask a question in the post. Is there a particular place you’re running into trouble?

I suppose I forgot to ask the question in the body, but it simply doesn’t work. I can’t get it to link with SDL3, and it says that the SDL3 functions are undefined symbols

That is because on windows you don’t link your exe with SDL3 at all, you just install the .dll and add a library path, but you still have to add something that does linking.

Is there a reason you don’t call

exe.linkSystemLibrary("SDL3");

on windows?

Also take a look at Build System Tricks III. 2)

The code I posted has comments and parts I didn’t think were necessary stripped out, I actually do have that commented below exe.addLibraryPath, but it’s commented because it doesn’t help with the “undefined symbol” issue (and for full clarity, it can’t find SDL_Init, SDLCreateWindow, etc). It’s able to build a binary if there are no SDL3 functions, so with just the header file, but it can’t find the dll’s functions at the moment. I’ll post my full build code below, the area of interest in the top linker function’s //libs section

const std = @import("std");

const PROGRAM_NAME: []const u8 = "main";

fn linker(exe: *std.Build.Step.Compile, files: []const []const u8, b: *std.Build, target: std.Build.ResolvedTarget) void {
    exe.addCSourceFiles(.{
        .files = files,
        .flags = &[_][]const u8{},
    });
    exe.addIncludePath(b.path("include"));
    exe.linkLibC();
    exe.linkLibCpp();

    // Libs
    if (target.query.isNativeOs() and target.result.os.tag == .windows) {
        // Solution 1
        // const sdl_dep = b.dependency("SDL", .{ // Add libs as needed
        //     .target = target,
        // });
        // exe.linkLibrary(sdl_dep.artifact("SDL2"));

        // Solution 2 -WIP-
        // This worked in the past?
        exe.addLibraryPath(b.path("lib/SDL3"));
        exe.linkSystemLibrary("SDL3");
        b.installBinFile("lib/SDL3/SDL3.dll", "SDL3.dll");
    } else {
        // exe.linkSystemLibrary("SDL2"); // Add libs as needed
    }
}

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

    const files = try findFiles("src", &[_][]const u8{});
    std.debug.print("Files built:\n{s}\n", .{files});

    const exe = b.addExecutable(.{ .name = PROGRAM_NAME, .target = target });
    linker(exe, files, b, target);

    b.installArtifact(exe);

    // ------ Run ------
    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);

    // ------ Tests ------
    var test_dir_exists = false;
    _ = std.fs.cwd().statFile("tests") catch |err| {
        // std.debug.print("{?}\n", .{err});
        if (err == error.IsDir) {
            test_dir_exists = true;
        }
    };
    
    if (test_dir_exists) {
        const test_files = try findFiles("tests", &[_][]const u8{"main.cpp"});
        std.debug.print("Test Files built:\n{s}\n", .{test_files});

        // combine with `files`, except src/main.c or src/main.cpp
        const tests_name = PROGRAM_NAME ++ "_tests";
        const tests_exe = b.addExecutable(.{ .name = tests_name, .target = target });

        linker(tests_exe, test_files, b, target);
        const googletest_dep = b.dependency("googletest", .{
            .target = target,
            // .optimize = b.standardOptimizeOption(.{}),
        });
        tests_exe.linkLibrary(googletest_dep.artifact("gtest"));

        b.installArtifact(tests_exe);

        const run_tests_exe = b.addRunArtifact(tests_exe);
        run_tests_exe.step.dependOn(b.getInstallStep());

        const test_step = b.step("test", "Run unit tests");
        test_step.dependOn(&run_tests_exe.step);
    }

    // ------ Clean ------
    const clean_step = b.step("clean", "Clean the directory");
    clean_step.dependOn(&b.addRemoveDirTree(b.path("zig-out")).step);
    clean_step.dependOn(&b.addRemoveDirTree(b.path(".zig-cache")).step);
}

fn findFiles(src: []const u8, ignore_list: []const []const u8) ![]const []const u8 {
    var result = std.ArrayList([]const u8).init(std.heap.page_allocator);
    // todo: error handling for root (for test dir)
    var root = try std.fs.cwd().openDir(src, .{ .iterate = true });
    defer root.close();

    var iter = root.iterate();
    while (try iter.next()) |entry| {
        // ignore if on ignore list
        var ignore = false;
        for (ignore_list) |item|
            if (std.mem.indexOf(u8, entry.name, item) != null) {
                ignore = true;
                break;
            };
        if (ignore) {
            continue;
        }

        // Create item
        var item = std.ArrayList(u8).init(std.heap.page_allocator);
        try item.appendSlice(src);
        try item.append('/');
        try item.appendSlice(entry.name);
        if (entry.kind == .file) {
            const check_cpp = entry.name[entry.name.len - 4 ..];
            const check_c = entry.name[entry.name.len - 2 ..];
            if (std.mem.eql(u8, check_cpp, ".cpp") or std.mem.eql(u8, check_c, ".c")) {
                const path_u8 = try item.toOwnedSlice();
                try result.append(path_u8);
            }
        }
        if (entry.kind == .directory) {
            const dir_u8 = try item.toOwnedSlice();
            const files = try findFiles(dir_u8, ignore_list);
            for (files) |f|
                try result.append(f);
        }
    }
    const res = try result.toOwnedSlice();
    return res;
}


Edit: Just realized that the building is what fails for finding symbols.

error: lld-link: undefined symbol: SDL_Init
    note: referenced by D:\Code\C_CPP\BuildSysWin\zig\src\main.cpp:6
    note:               D:\Code\C_CPP\BuildSysWin\zig\.zig-cache\o\2b7431943186d9c11b5a58d4dbafa08e\main.obj:(main)
error: the following command failed with 1 compilation errors:
C:\zig-windows-x86_64-0.14.0-dev.1917+ecd5878b7\zig.exe build-exe D:\Code\C_CPP\BuildSysWin\zig\src/main.cpp -lSDL3 -ODebug -I D:\Code\C_CPP\BuildSysWin\zig\include -L D:\Code\C_CPP\BuildSysWin\zig\lib\SDL3 -Mroot -lc++ -lc --cache-dir D:\Code\C_CPP\BuildSysWin\zig\.zig-cache --global-cache-dir C:\Users\user\AppData\Local\zig --name main --zig-lib-dir C:\zig-windows-x86_64-0.14.0-dev.1917+ecd5878b7\lib\ --listen=-

I figured out the issue, I took the SDL3’s VC library files and separated them from the include files, which may be needed where they are. So I decided to just take the extracted file as-is and place it inside the lib folder, instead of just taking the files in the x64 folder. From there, all I appear to need are these two lines to link the library to my C++ code:

exe.addLibraryPath(b.path("lib/SDL3-3.1.6/lib/x64/"));
exe.linkSystemLibrary("SDL3");

Edit:
No, to run it I definitely need this line, lol: b.installBinFile("lib/SDL3-3.1.6/lib/x64/SDL3.dll", "SDL3.dll");