Link errors while using zig for building a test application with yaml-cpp

Hello
I am new to zig so, sorry if this is a known issue / lack of knowledge but I have been trying to make this work for a while … with no results :frowning:

I am trying to use the zig toolchain for building a simple C++ application that uses yaml-cpp library

I am using Fedora 41 and zig 0.15.0-dev.471+369177f0b
I have installed the yaml-cpp-devel package in Fedora. The shared libraries are deployed to /usr/lib64

/usr/lib64/libyaml-cpp.so -> libyaml-cpp.so.0.7
/usr/lib64/libyaml-cpp.so.0.7 -> libyaml-cpp.so.0.7.0
/usr/lib64/libyaml-cpp.so.0.7.0

while the include files are deployed to /usr/include/yaml-cpp

The project is trivial, a simple main.cpp file with this content

#include <iostream>
#include "yaml-cpp/yaml.h"

int main(){
    YAML::Node config = YAML::LoadFile("sample.yaml");
    std::cout << config["series"];
}

I am able to build an run this with gcc

g++ main.cpp -g -o yamltest -lyaml-cpp

But I get linker problems when I use ZIG
This is my build.zig file

const std = @import("std");

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

    // C compliation options
    const cflags = [_][]const u8{
        "-D_GNU_SOURCE",
        "-g",
        "-O2",
        "-Wall",
        "-march=native",
        "-std=c++17",
    };

    // C source code
    const sourceCodeFiles = [_][]const u8{
        "./main.cpp",
    };

    const exe = b.addExecutable(.{
        .name = "yamltest",
        .target = target,
        .optimize = optimize,
    });

    // Add C++ source files
    exe.addCSourceFiles(.{ .files = &sourceCodeFiles, .flags = &cflags });

    // Add include dirs or libs
    //exe.addIncludePath("include");

    exe.addIncludePath(.{ .cwd_relative = "/usr/include/yaml-cpp/" });
    exe.addLibraryPath(.{ .cwd_relative = "/usr/lib64" });
    exe.linkSystemLibrary("yaml-cpp");
    //exe.linkSystemLibrary2("yaml-cpp", .{ .needed = true, .preferred_link_mode = .static });
    exe.linkLibC();
    exe.linkLibCpp();

    b.installArtifact(exe);
}

This is the result of the build. It looks like some problem in the linker stage??

zig build
install
└─ install yamltest
   └─ zig build-exe yamltest Debug native 5 errors
error: ld.lld: undefined symbol: YAML::LoadFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&)
    note: referenced by main.cpp:5 (./main.cpp:5)
    note:               /home/berto/Code/Cpp/yamltest/.zig-cache/o/7d0a39aeeddd62316cda3a72432ac595/main.o:(main)
error: ld.lld: undefined symbol: YAML::operator<<(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, YAML::Node const&)
    note: referenced by main.cpp:6 (./main.cpp:6)
    note:               /home/berto/Code/Cpp/yamltest/.zig-cache/o/7d0a39aeeddd62316cda3a72432ac595/main.o:(main)
error: ld.lld: undefined symbol: YAML::detail::node_data::convert_to_map(std::__1::shared_ptr<YAML::detail::memory_holder> const&)
    note: referenced by impl.h:154 (/usr/include/yaml-cpp/node/detail/impl.h:154)
    note:               /home/berto/Code/Cpp/yamltest/.zig-cache/o/7d0a39aeeddd62316cda3a72432ac595/main.o:(YAML::detail::node& YAML::detail::node_data::get<char [7]>(char const (&) [7], std::__1::shared_ptr<YAML::detail::memory_holder>))
error: ld.lld: undefined symbol: YAML::detail::node_data::empty_scalar()
    note: referenced by impl.h:169 (/usr/include/yaml-cpp/node/impl.h:169)
    note:               /home/berto/Code/Cpp/yamltest/.zig-cache/o/7d0a39aeeddd62316cda3a72432ac595/main.o:(YAML::Node::Scalar() const)
error: ld.lld: undefined symbol: YAML::detail::node_data::set_scalar(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&)
    note: referenced by node_ref.h:37 (/usr/include/yaml-cpp/node/detail/node_ref.h:37)
    note:               /home/berto/Code/Cpp/yamltest/.zig-cache/o/7d0a39aeeddd62316cda3a72432ac595/main.o:(YAML::Node::Assign(char const*))
error: the following command failed with 5 compilation errors:
/home/berto/zig/zig015/zig build-exe -cflags -D_GNU_SOURCE -g -O2 -Wall -march=native -std=c++17 -- /home/berto/Code/Cpp/yamltest/./main.cpp -lyaml-cpp -ODebug -I /usr/include/yaml-cpp -L /usr/lib64 -Mroot -lc++ -lc --cache-dir /home/berto/Code/Cpp/yamltest/.zig-cache --global-cache-dir /home/berto/.cache/zig --name yamltest --zig-lib-dir /home/berto/zig/zig015/lib/ --listen=- 
Build Summary: 0/3 steps succeeded; 1 failed

Any ideas please??

Thanks a lot in advance!

zig is using pkg-config to locate the libraries.
try pkg-config --libs yaml-cpp and pkg-config --libs yaml-cpp-static.
The package names or aliases that work with pkg-config also work with linkSystemLibrary, i.e. you don’t need addLibraryPath.
(I’ve got the names from: Tree - rpms/yaml-cpp - src.fedoraproject.org, in the lib directory locate the pkgconfig/yaml-cpp*.pc)

Looks suspiciously like a case where the linked library links against libstdc++ instead of libc++

Yes, maybe it is name mangling differences.
But it might be the difference in packages yaml-cpp-static vs yaml-cpp.

I got the same error and tried the libstdc++ theory, and it seems to be the case you’ll have to jump through some hoops, e.g. Troubleshooting for linking with pre-built static libraries - #4 by korke

If you additionally add the right include directories for c++, and link with unwind, it builds:

https://zigbin.io/647b65 (a variation on the addObjectFile trick above)

That said, I would rather build the external lib from source using the same libc++

2 Likes

Many thanks!
You were right, the problem seems to be that ZIG was not linking libstdc++
Following your advice I have installed the static version and added it using exe.addObjectFile(.{ .cwd_relative = "/usr/lib/gcc/x86_64-redhat-linux/14//libstdc++.a" });
Not sure the build file is completely correct & coherent but…it is compiling and working now :slight_smile:

const std = @import("std");

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

    // C compliation options
    const cflags = [_][]const u8{
        "-D_GNU_SOURCE",
        "-g",
        "-O2",
        "-Wall",
        "-march=native",
        "-std=c++17",
    };

    // C source code
    const sourceCodeFiles = [_][]const u8{
        "./main.cpp",
    };

    const exe = b.addExecutable(.{
        .name = "yamltest",
        .target = target,
        .optimize = optimize,
    });

    // Add C++ source files
    exe.addCSourceFiles(.{ .files = &sourceCodeFiles, .flags = &cflags });

    //Add required includes
    //exe.addIncludePath(.{ .cwd_relative = "/usr/include/yaml-cpp/" });
    exe.addIncludePath(.{ .cwd_relative = "/usr/include/c++/14" });
    exe.addIncludePath(.{ .cwd_relative = "/usr/include/c++/14/x86_64-redhat-linux" });

    //Add required libs
    exe.addLibraryPath(.{ .cwd_relative = "/usr/lib/gcc/x86_64-redhat-linux/14/" });
    exe.addObjectFile(.{ .cwd_relative = "/usr/lib/gcc/x86_64-redhat-linux/14//libstdc++.a" });

    exe.linkSystemLibrary("yaml-cpp");
    exe.linkSystemLibrary("unwind");
    exe.linkLibC();

    b.installArtifact(exe);
}

Anyway…and as you said…maybe it is better to compile yaml-cpp from source and the lib to the project…

Cheers!

2 Likes