Unable to import C library after importing to zig build system

I moved my C dependency to a Zig package. It builds fine but when I try to import it into my project I’m getting an import error.

build.zig for project:

const std = @import("std");

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

    const optimize = b.standardOptimizeOption(.{});

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

    const flags = b.dependency("flags", .{
        .target = target,
        .optimize = optimize,
    });

    exe.root_module.addImport("flags", flags.module("flags"));

    const lodepngz = b.dependency("lodepngz", .{
        .target = target,
        .optimize = optimize,
    });

    exe.root_module.addImport("lodepngz", lodepngz.module("lodepngz"));
    
    // what I was using before
    // exe.addIncludePath(b.path("clib"));   
    // exe.addObjectFile(b.path("clib/lodepng.o"));

    exe.linkSystemLibrary("turbojpeg");

    exe.linkLibC();
    b.installArtifact(exe);

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

    const exe_unit_tests = b.addTest(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);

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

build.zig.zon for project:

.{
    .name = "tag",
    .version = "0.5.0",

    .dependencies = .{
        .flags = .{
            .url = "https://github.com/n0s4/flags/archive/refs/heads/main.tar.gz",
            .hash = "12202e9d5de187569b77064c66a4972e8a824488295fab2f5c8cb48331eab9877257",
        },
        .lodepngz = .{
            .url = "https://forge.ohnyo.com/coredump/lodepngz/archive/ddc283419fe9624627e1d975792c77aa61e5b496.tar.gz",
            .hash = "1220f6d26b2e6e1a9ad55b816a7584f0718fa9f78bff45e78e276457e1cb1f585b9d",
        },
    },
    .paths = .{
        "build.zig",
        "build.zig.zon",
        "src",
    },
}

build.zig for lib:

const std = @import("std");

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

    const optimize = b.standardOptimizeOption(.{});

    const lib = b.addStaticLibrary(.{
        .name = "lodepngz",
        .target = target,
        .optimize = optimize,
    });

    const flags = [_][]const u8{
        "-ansi",
        "-O3",
    };
    lib.linkLibC();
    lib.addIncludePath(b.path("lodepng/lodepng.h"));
    lib.addCSourceFile(.{.file = b.path("lodepng/lodepng.c"), .flags = &flags});
    b.installArtifact(lib);
}

build.zig.zon for lib:

.{
    .name = "lodepngz",
    .version = "1.0.0",
    .paths = .{"."},
}

error on compilation:

thread 32391 panic: unable to find module 'lodepngz'
/usr/lib/zig/lib/std/Build.zig:1896:18: 0x115bd07 in module (build)
            panic("unable to find module '{s}'", .{name});
                 ^
/home/wizard/fun/zig/learn/tag/build.zig:27:58: 0x1115f6f in build (build)
    exe.root_module.addImport("lodepngz", lodepngz.module("lodepngz"));
                                                         ^
/usr/lib/zig/lib/std/Build.zig:2155:33: 0x10fb2f3 in runBuild__anon_8801 (build)
        .Void => build_zig.build(b),
                                ^
/usr/lib/zig/lib/compiler/build_runner.zig:301:29: 0x10f649f in main (build)
        try builder.runBuild(root);
                            ^
/usr/lib/zig/lib/std/start.zig:515:37: 0x10ddea5 in posixCallMainAndExit (build)
            const result = root.main() catch |err| {
                                    ^
                                    ^
/usr/lib/zig/lib/std/start.zig:258:5: 0x10dd9c1 in _start (build)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x8 in ??? (???)
Unwind information for `???:0x8` was not available, trace may be incomplete

I need to compile the C lib in O3 otherwise it segfaults. I tried to not do this but it needs it. It is in the library’s build instructions to build with -O3 and -ansi. What am I doing wrong in regards to importing this library? I usually follow this pattern when importing pure Zig libraries. Do C dependent libraries require more steps?

before the change I would have to run the following if you are on a new machine:

cd clib
zig cc -ansi -c -O3 lodepng.c -o lodepng.o
zig build -Doptimize=ReleaseFast

I’m trying to get a clean build flow. By just running, zig build -Doptimize=ReleaseFast

You can wget the file in the zon file and it compiles fine:

[wizard@thinkchad tag]$ wget https://forge.ohnyo.com/coredump/lodepngz/archive/ddc283419fe9624627e1d975792c77aa61e5b496.tar.gz
[wizard@thinkchad tag]$ tar xf ddc283419fe9624627e1d975792c77aa61e5b496.tar.gz
[wizard@thinkchad tag]$ cd lodepngz/
[wizard@thinkchad lodepngz]$ zig build -Doptimize=ReleaseFast
[wizard@thinkchad lodepngz]$

I’m not 100% on the build system, so take this with a handful of salt, but I’m pretty sure you need to use b.addModule in order to expose it.

    const mod = b.addModule("lodepngz", .{
        // perhaps it is necessary to create a zig wrapper around your C API to use in the
        // package manager. I don't believe this can be a C file.
        .root_source_file = b.path("??? (src/lodepngz-wrapper.zig?)"),
        .link_libc = true,
    });

    // mod.addIncludePath(...)
    // mod.addCSourceFile(...)

PS: glad to see someone else using flags :).

1 Like

Your library is great! Super easy to use! Tried some changes:

[wizard@thinkchad lodepngz]$ zig translate-c lodepng.c -lc > lodepng.zig
  const std = @import("std");

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

    const optimize = b.standardOptimizeOption(.{});

    const lib = b.addStaticLibrary(.{
        .name = "lodepngz",
        .target = target,
        .optimize = optimize,
    });

    const flags = [_][]const u8{
        "-ansi",
        "-O3",
    };
    const mod = b.addModule("lodepngz", .{
        .root_source_file = b.path("lodepng/lodepng.zig"),
        .link_libc = true,
    });
    lib.linkLibC();
    mod.addIncludePath(b.path("lodepng/lodepng.h"));
    mod.addCSourceFile(.{ .file = b.path("lodepng/lodepng.c"), .flags = &flags });

    b.installArtifact(lib);
}

I assume the manual translate to C should just work because that’s what happens when I link to the object file like I was before but I get this error.

compilation error:
error: the linker needs one or more objects to link

Thanks :smile: lmk if you have any gripes/suggestions.


I think you can actually just omit root_source_file when creating the module. The docs say you can do this if the module consists of just “link_objects”.

As for the linker error, is it coming from b.installArtifact(lib) when lib is no longer told to link libc? Try just deleting lib for now.

Small thing: you dont need to mod.linkLibC() since youre setting link_libc manually.

The best reference for using C libraries from Zig with the package manager that I’m aware of is @andrewrk’s personal project(s):

Using libsoundio as the example, its build.zig looks like this and it’s referenced in the build.zig of the parent Zig project like this:

    const soundio_dep = b.dependency("soundio", .{
        .target = target,
        .optimize = dep_optimize,
    });
    const libsoundio = soundio_dep.artifact("soundio");

which returns the static library, and is then linked via

player.linkLibrary(libsoundio);

For ffmpeg, a different approach is taken where it adds a Zig wrapper module in its build.zig that links the C static library, and then that’s whats used in the parent project.

2 Likes

helps. the compiler asked me to change my code like it actually acknowledged the change.

changes to build.zig:

    const lodepng_dep = b.dependency("lodepngz", .{
        .target = target,
        .optimize = .ReleaseFast,
    });

    const liblodepng = lodepng_dep.artifact("lodepngz");
    exe.linkLibrary(liblodepng);
    exe.root_module.addImport("lodepngz", lodepng_dep.module("lodepngz"));

changes to main.zig:

const lode = @import("lodepngz");
const c = @cImport({
    @cInclude("turbojpeg.h");
    // @cInclude("../clib/lodepng.h"); // had to comment this out finally
});
// rest of code...
        png = lode.lodepng_decode32_file(&image, &width, &height, name);

when you try to compile same error from the beginning:

zig build -Doptimize=ReleaseFast
thread 437 panic: unable to find module 'lodepngz'
/usr/lib/zig/lib/std/Build.zig:1896:18: 0x115c3f7 in module (build)
            panic("unable to find module '{s}'", .{name});
                 ^
/home/wizard/fun/zig/learn/tag/build.zig:31:61: 0x111666f in build (build)
    exe.root_module.addImport("lodepngz", lodepng_dep.module("lodepngz"));
                                                            ^
/usr/lib/zig/lib/std/Build.zig:2155:33: 0x10fb9c3 in runBuild__anon_8801 (build)
        .Void => build_zig.build(b),
                                ^
/usr/lib/zig/lib/compiler/build_runner.zig:301:29: 0x10f6b6f in main (build)
        try builder.runBuild(root);
                            ^
/usr/lib/zig/lib/std/start.zig:515:37: 0x10de575 in posixCallMainAndExit (build)
            const result = root.main() catch |err| {
                                    ^
/usr/lib/zig/lib/std/start.zig:258:5: 0x10de091 in _start (build)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x8 in ??? (???)
Unwind information for `???:0x8` was not available, trace may be incomplete

custom help text.

more specifically an option to change the usage text

Usage: tag [options]  // I want to change this I want something like: 
USAGE:  tag [options] <JPEG|PNG FILE>

// this is really cool 
tag only takes one image at a time
tag also only supports PNG and JPEG image formats.

// this is perfect!
Options:
  -f, --flip   Inverts the output
  -r, --resize Resizes image with provided width and height
  -W, --width  sets the width to be resized. resize needs to be passed
  -H, --height sets the height to be resized. resize needs to be passed
  -h, --help   Show this help and exit
1 Like

If the lodepngz module doesn’t exist, then don’t use this line.

error: ld.lld: duplicate symbol: lodepng_deflate
    note: defined in /home/wizard/fun/zig/learn/tag/.zig-cache/o/4a6617f8d720eb19bb0475455b06baa8/lodepng.o
    note: defined in /home/wizard/fun/zig/learn/tag/.zig-cache/o/7ecfe36d9e19171f9aa4e478ecfaecdb/tag.o
error: ld.lld: duplicate symbol: lodepng_convert_rgb
    note: defined in /home/wizard/fun/zig/learn/tag/.zig-cache/o/4a6617f8d720eb19bb0475455b06baa8/lodepng.o
    note: defined in /home/wizard/fun/zig/learn/tag/.zig-cache/o/7ecfe36d9e19171f9aa4e478ecfaecdb/tag.o
error: the following command failed with 73 compilation errors:
/usr/lib64/zig/9999/bin/zig build-exe -lturbojpeg -OReleaseFast --dep flags --dep lodepngz -Mroot=/home/wizard/fun/zig/learn/tag/src/main.zig -OReleaseFast -Mflags=/home/wizard/.cache/zig/p/12202e9d5de187569b77064c66a4972e8a824488295fab2f5c8cb48331eab9877257/src/root.zig -cflags -ansi -O3 -- /home/wizard/.cache/zig/p/12201b3ed122b44e9969f5a27e3038a5685691b6a9c23007737cd0b3371d45b0cc90/lodepng/lodepng.c -I /home/wizard/.cache/zig/p/12201b3ed122b44e9969f5a27e3038a5685691b6a9c23007737cd0b3371d45b0cc90/lodepng/lodepng.h -Mlodepngz=/home/wizard/.cache/zig/p/12201b3ed122b44e9969f5a27e3038a5685691b6a9c23007737cd0b3371d45b0cc90/lodepng/lodepng.zig -lc --cache-dir /home/wizard/fun/zig/learn/tag/.zig-cache --global-cache-dir /home/wizard/.cache/zig --name tag --listen=-
Build Summary: 0/3 steps succeeded; 1 failed (disable with --summary none)
install transitive failure

I got it to import but now I got this linker issue. Not too sure what would be causing this. I tried clearing the cache out. I don’t really understand how I’m getting this duplicate symbol error.

project 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 = "tag",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    const flags = b.dependency("flags", .{
        .target = target,
        .optimize = optimize,
    });

    exe.root_module.addImport("flags", flags.module("flags"));

    exe.linkLibC();

    const lodepngz = b.dependency("lodepngz", .{
        .target = target,
        .optimize = optimize,
    });

    exe.root_module.addImport("lodepngz", lodepngz.module("lodepngz"));

    // exe.addIncludePath(b.path("clib"));   // Look for C source files
    // exe.addObjectFile(b.path("clib/lodepng.o"));

    exe.linkSystemLibrary("turbojpeg");

    b.installArtifact(exe);

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

    const exe_unit_tests = b.addTest(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);

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

lib build.zig:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const flags = [_][]const u8{
        "-ansi",
        "-O3",
    };
    const png = b.addModule("lodepngz", .{
        .root_source_file = b.path("lodepng/lodepng.zig"),
    });
    png.addIncludePath(b.path("lodepng"));
    png.addCSourceFile(.{ .file = b.path("lodepng/lodepng.c"), .flags = &flags });

    const target = b.standardTargetOptions(.{});

    const optimize = b.standardOptimizeOption(.{});

    const lib = b.addStaticLibrary(.{
        .name = "lodepngz",
        .root_source_file = b.path("lodepng/lodepng.zig"),
        .target = target,
        .optimize = optimize,
    });

    lib.linkLibC();

    b.installArtifact(lib);
}

Read carefully the linker error message. It is telling you exactly the problem. You have two compilation units (e.g. C source files) that export the same functions. Your task is to figure out why that is the case.

3 Likes

You might also want to watch this (admittedly long) video@marler8997 deals with this exact library at some point.

2 Likes

This is super helpful. This helped a lot understanding compiling C/C++ libraries using ZON and the Zig build system. I’m working on figuring out how to expose lodepng as an import right now.

Will post my build file when I finally get it working.

1 Like