Search code examples
static-librarieszig

How to link against a static library?


I've a compiled static library, but don't have the code of it. I know the library embeds lots of files on compilation and looks something like this:

//files.zig
const std = @import("std");
pub const file_paths = [_][]const u8{
    "www/x/in.txt",
    "www/emd.txt",
    "www/index.html",
    "www/e2.txt",
};

const EmbeddedFile = struct {
    path: []const u8,
    content: []const u8,
};

pub const embedded_files = blk: {
    var fs: [file_paths.len]EmbeddedFile = undefined;
    for (file_paths) |file_path, i| {
        const embedded_file = EmbeddedFile{
            .path = file_path,
            .content = @embedFile(file_path),
        };
        fs[i] = embedded_file;
    }
    break :blk fs;
};

How can I use the library in my app? For example, how can I display the content of the file www/index.html that is embeded in this library, from my app code.


Solution

  • Linking

    If you want to use internals of a static library, you have to link against it firstly. You can do it by using Zig build system or manually.

    Build System

    For the first option you have to edit build.zig file in the root of your project by adding addObjectFile("path") to your executable object.

    Here is the build file for zig v0.10.1 which is automatically generated by zig init-exe command with exe.addObjectFile function call added

    const std = @import("std");
    
    pub fn build(b: *std.build.Builder) void {
        const mode = b.standardReleaseOptions();
    
        const exe = b.addExecutable("test_exe", "src/main.zig");
        exe.setTarget(target);
        exe.setBuildMode(mode);
        exe.install();
    
        exe.addObjectFile("path/to/file.a(or .lib if on Windows)"); // linking the executable against a "file.a" static library.
    
        const run_cmd = exe.run();
        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);
    
        const exe_tests = b.addTest("src/main.zig");
        exe_tests.setTarget(target);
        exe_tests.setBuildMode(mode);
    
        const test_step = b.step("test", "Run unit tests");
        test_step.dependOn(&exe_tests.step);
    }
    

    Manual linking

    To link the library with only using Zig compiler you just need to add it's path into the list of targets like this:

    zig build-exe src/main.zig path/to/the/lib.a
    

    Usage

    To use exported functions/variables you have to declare them with the extern keyword. If you want to import something that uses non-primitive type, you should define it yourself.

    Usually, library developers provide header(for C) or package(for Zig) files if they want to distribute their library as a static or shared archive. These files contain all exported functions, variables, types, because a common user of a big library don't know anything about what is exported without them.

    const std = @import("std");
    
    const EmbeddedFile = struct {
        path: [*:0]const u8,
        content: [*:0]const u8,
    };
    
    // Notice the extern keyword. 
    // It tells the compiler embedded_files is taken from a shared or static library.
    extern const embedded_files: [4]EmbeddedFile;
    
    pub fn main() !void {
        for (embedded_files) |file| {
            std.debug.print("file:\t\t{s}\n", .{file.path});
            std.debug.print("content:\t{s}\n", .{file.content});
        }
    }
    

    Output:

    file:       www/x/in.txt
    content:    text
    file:       www/emd.txt
    content:    text
    file:       www/index.html
    content:    text
    file:       www/e2.txt
    content:    text
    

    Note

    The code you provided can't represent the actual internals of the library, because:

    • Nothing is exported here
    • Slices can not be exported

    So I assumed the library looks like this:

    const std = @import("std");
    
    const file_paths = [_][]const u8{
        "www/x/in.txt",
        "www/emd.txt",
        "www/index.html",
        "www/e2.txt",
    };
    
    const EmbeddedFile = extern struct {
        path: [*:0]const u8,
        content: [*:0]const u8,
    };
    
    export const embedded_files = blk: {
        var fs: [file_paths.len]EmbeddedFile = undefined;
        for (file_paths) |file_path, i| {
            const embedded_file = EmbeddedFile{
                .path = file_path[0.. :0],
                .content = @embedFile(file_path)[0.. :0],
            };
            fs[i] = embedded_file;
        }
        break :blk fs;
    };