Zig keeps linking to lc++ help!

So I’m at work try to use zig to solve a very annoying problem which is that for one of the target that we have which is a raspberry pi zero 2w, running a custom version of debian which ships with a version of glibc that makes zig the perfect candidate for cross-compiling, I went through hell figuring out how to properly link everything, find where are all the headers, path, etc etc. But After all that trouble I’m stuck because I can’t find a way to tell zig to not link ‘-lc++’ and instead use the one in the system, because libcamera and opencv seems to be compiled with different symbols than the one in -lc++ anyway if anyone has some experience I’d be delighted.

const std = @import("std");

pub const gotowhite_directory: []const u8 = "src/fireforce";

pub const gotowhite_source: []const []const u8 = &.{
    "gotowhite.c",
};

pub const gotowhite_flags: []const []const u8 = &.{
    "-O3",
    "-pipe",
    "-ffast-math",
    "-fstrict-aliasing",
    "-ftree-vectorize",
    "-ffunction-sections",
    "-fdata-sections",
    "-DNDEBUG",
    "-D_GNU_SOURCE",
    "-mcpu=cortex-a53",
    "-mtune=cortex-a53",
    "-mfpu=neon-vfpv4",
    "-mfloat-abi=hard",
};

pub const camera_app_source: []const []const u8 = &.{
    "src/fireforce/camera/CameraHandler.cpp",
    "src/fireforce/camera/main.cpp",
    "src/fireforce/INIReader.cpp",
};

pub const camera_app_flags: []const []const u8 = &.{
    "-DBOOST_NO_CXX98_FUNCTION_BASE",
    "-Wno-deprecated-builtins",
    "-D_LIBCPP_DISABLE_MACROS",
    "-D_GNU_SOURCE",
    "-nostdinc++",
};

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

    const gotowhite = b.addExecutable(.{
        .name = "gotowhite",
        .root_module = b.createModule(.{
            .root_source_file = null,
            .target = target,
            .optimize = optimize,
            .link_libc = true,
        }),
    });
    gotowhite.addCSourceFiles(.{
        .root = b.path(gotowhite_directory),
        .files = gotowhite_source,
        .flags = gotowhite_flags,
        .language = .c,
    });
    gotowhite.linkLibC();
    b.installArtifact(gotowhite);

    const camera_app = b.addExecutable(.{
        .name = "camera_app",
        .root_module = b.createModule(.{
            .root_source_file = null,
            .target = target,
            .optimize = .ReleaseFast,
            .link_libc = true,
            .link_libcpp = false,
        }),
    });

    camera_app.root_module.addSystemIncludePath(b.path("../../sysroot/usr/include/c++/10"));
    camera_app.root_module.addSystemIncludePath(b.path("../../sysroot/usr/include/arm-linux-gnueabihf/c++/10"));
    camera_app.root_module.addLibraryPath(b.path("../../sysroot/usr/lib/gcc/arm-linux-gnueabihf/10"));
    camera_app.root_module.addLibraryPath(b.path("../../sysroot/usr/lib/arm-linux-gnueabihf"));

    camera_app.root_module.linkSystemLibrary("stdc++", .{});
    camera_app.root_module.linkSystemLibrary("m", .{});
    camera_app.root_module.linkSystemLibrary("gcc_s", .{});
    camera_app.root_module.linkSystemLibrary("gcc", .{});
    camera_app.root_module.linkSystemLibrary("c", .{});
    camera_app.root_module.linkSystemLibrary("dl", .{});

    camera_app.root_module.addSystemIncludePath(b.path("../../sysroot/usr/include/opencv4"));
    camera_app.root_module.linkSystemLibrary("opencv_core", .{});
    camera_app.root_module.linkSystemLibrary("opencv_imgproc", .{});
    camera_app.root_module.linkSystemLibrary("opencv_imgcodecs", .{});

    camera_app.root_module.addSystemIncludePath(b.path("../../sysroot/usr/include/libcamera"));
    camera_app.root_module.addSystemIncludePath(b.path("src/libcamera/libcamera-apps"));
    camera_app.root_module.linkSystemLibrary("camera", .{});
    camera_app.root_module.linkSystemLibrary("camera-base", .{});
    camera_app.root_module.linkSystemLibrary("camera_app", .{});

    camera_app.root_module.addSystemIncludePath(b.path("../../sysroot/usr/include"));
    camera_app.root_module.linkSystemLibrary("boost_program_options", .{});

    camera_app.addCSourceFiles(.{
        .files = camera_app_source,
        .flags = camera_app_flags,
    });

    b.installArtifact(camera_app);
}

and as you can see from the output

> zig build -Dtarget=arm-linux-gnueabihf.2.31 -Dcpu=cortex_a53
install
└─ install camera_app
   └─ compile exe camera_app ReleaseFast arm-linux-gnueabihf.2.31 3 errors
error: ld.lld: undefined symbol: cv::imwrite(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, cv::_InputArray const&, std::__1::vector<int, std::__1::allocator<int>> const&)
    note: referenced by CameraHandler.cpp:61 (src/fireforce/camera/CameraHandler.cpp:61)
    note:               .zig-cache/o/249a1f7f172f5f17e0ed3ff2edc84a46/CameraHandler.o:(CameraHandlerMt::save_image_atomic(cv::Mat const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&))
error: ld.lld: undefined symbol: cv::putText(cv::_InputOutputArray const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, cv::Point_<int>, int, double, cv::Scalar_<double>, int, int, bool)
    note: referenced by CameraHandler.cpp:421 (src/fireforce/camera/CameraHandler.cpp:421)
    note:               .zig-cache/o/249a1f7f172f5f17e0ed3ff2edc84a46/CameraHandler.o:(CameraHandlerMt::cameraConsumerFn(int))
error: ld.lld: undefined symbol: LibcameraApp::LibcameraApp(std::__1::unique_ptr<Options, std::__1::default_delete<Options>>)
    note: referenced by CameraHandler.cpp:167 (src/fireforce/camera/CameraHandler.cpp:167)
    note:               .zig-cache/o/249a1f7f172f5f17e0ed3ff2edc84a46/CameraHandler.o:(CameraHandlerMt::cameraProducerFn())
error: the following command failed with 3 compilation errors:
/home/pollivie/.local/opt/zig-v0.15.1/zig build-exe -lgcc_s -lgcc -lopencv_core -lopencv_imgproc -lopencv_imgcodecs -lcamera -lcamera-base -lcamera_app -lboost_program_options -cflags -DBOOST_NO_CXX98_FUNCTION_BASE -Wno-deprecated-builtins -D_LIBCPP_DISABLE_MACROS -D_GNU_SOURCE -nostdinc++ -- /home/pollivie/workspace/cross_comp/src/fireforce/camera/CameraHandler.cpp /home/pollivie/workspace/cross_comp/src/fireforce/camera/main.cpp /home/pollivie/workspace/cross_comp/src/fireforce/INIReader.cpp -OReleaseFast -target arm-linux-gnueabihf.2.31 -mcpu cortex_a53 -isystem /home/pollivie/workspace/cross_comp/../../sysroot/usr/include/c++/10 -isystem /home/pollivie/workspace/cross_comp/../../sysroot/usr/include/arm-linux-gnueabihf/c++/10 -isystem /home/pollivie/workspace/cross_comp/../../sysroot/usr/include/opencv4 -isystem /home/pollivie/workspace/cross_comp/../../sysroot/usr/include/libcamera -isystem /home/pollivie/workspace/cross_comp/src/libcamera/libcamera-apps -isystem /home/pollivie/workspace/cross_comp/../../sysroot/usr/include -L /home/pollivie/sysroot/usr/lib/gcc/arm-linux-gnueabihf/10 -L /home/pollivie/sysroot/usr/lib/arm-linux-gnueabihf -Mroot -lc++ -lc --cache-dir .zig-cache --global-cache-dir /home/pollivie/.cache/zig --name camera_app --zig-lib-dir /home/pollivie/.local/opt/zig-v0.15.1/lib/ --listen=-

It’s magically adding -lc++

Have you tried building with the --sysroot flag?

I am seeing your behaviour (-lc++ passed as a flag to the build despite .link_libcpp = false) when I tried to reproduce it with the same set of (admittedly empty) files and flags, but to me it seems like you’re trying to patch something that is set up incorrectly in the first place, if you’re passing paths to a sysroot manually in the build system.

for the sysroot I’m doing it manually with sshfs, but maybe there’s a correct way to do it ? I’m not sure I know my code is junk, and I know there’s probably a good way to do it, I just can’t figure it out, and I’m sure I’m doing something obviously wrong but I don’t know what it is, and C++ cryptic errors aren’t helping much too ahah. So how do you use the --sysroot flag ? do you just pass it to the compiler and how does it interracts with the path I give to it to find libraries and headers ?

The-lc++ is expected if you see this code:

Since zig detects, that you want to link the c++ std lib, this is equivalent to link_libcpp = true.

Also look at the isLibCLibName function. The calls to linkSystemLibrary with c, m and dl equivalent to link_libc = true.

Maybe the lib you are calling loads the libc++ for itself? Try ldd <the_lib> and look for something like libstdc++

1 Like

Hum, I mean I’m fine with libc, in fact it’s why zig was so great, is because it contains all the glibc version I need, but it’s the C++ heuristics, from my understanding both libcamera, and opencv are not compiled with this c++ lib and don’t have the same symbol mangling.

yes indeed I think that’s the problem I maybe encountering :slight_smile:

pi@raspberrypi /u/l/arm-linux-gnueabihf> ldd libcamera.so
        linux-vdso.so.1 (0x7eac7000)
        /usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so => /usr/lib/arm-linux-gnueabihf/libarmmem-v7l.so (0x76e2b000)
        libcamera-base.so.0.0 => /lib/arm-linux-gnueabihf/libcamera-base.so.0.0 (0x76de5000)
        libatomic.so.1 => /lib/arm-linux-gnueabihf/libatomic.so.1 (0x76dcc000)
        libgnutls.so.30 => /lib/arm-linux-gnueabihf/libgnutls.so.30 (0x76be1000)
        libdl.so.2 => /lib/arm-linux-gnueabihf/libdl.so.2 (0x76bcd000)
        libudev.so.1 => /lib/arm-linux-gnueabihf/libudev.so.1 (0x76b99000)
        libyaml-0.so.2 => /lib/arm-linux-gnueabihf/libyaml-0.so.2 (0x76b6f000)
-->        libstdc++.so.6 => /lib/arm-linux-gnueabihf/libstdc++.so.6 (0x769e7000)
        libm.so.6 => /lib/arm-linux-gnueabihf/libm.so.6 (0x76978000)
        libgcc_s.so.1 => /lib/arm-linux-gnueabihf/libgcc_s.so.1 (0x7694b000)
        libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0x767f8000)
        /lib/ld-linux-armhf.so.3 (0x76f8a000)
        libdw.so.1 => /lib/arm-linux-gnueabihf/libdw.so.1 (0x76758000)
        libunwind.so.8 => /lib/arm-linux-gnueabihf/libunwind.so.8 (0x76722000)
        libpthread.so.0 => /lib/arm-linux-gnueabihf/libpthread.so.0 (0x766f6000)
        libp11-kit.so.0 => /lib/arm-linux-gnueabihf/libp11-kit.so.0 (0x765e9000)
        libidn2.so.0 => /lib/arm-linux-gnueabihf/libidn2.so.0 (0x765ba000)
        libunistring.so.2 => /lib/arm-linux-gnueabihf/libunistring.so.2 (0x7643d000)
        libtasn1.so.6 => /lib/arm-linux-gnueabihf/libtasn1.so.6 (0x7641c000)
        libnettle.so.8 => /lib/arm-linux-gnueabihf/libnettle.so.8 (0x763c7000)
        libhogweed.so.6 => /lib/arm-linux-gnueabihf/libhogweed.so.6 (0x76379000)
        libgmp.so.10 => /lib/arm-linux-gnueabihf/libgmp.so.10 (0x76302000)
        libelf.so.1 => /lib/arm-linux-gnueabihf/libelf.so.1 (0x762d9000)
        libz.so.1 => /lib/arm-linux-gnueabihf/libz.so.1 (0x762b1000)
        liblzma.so.5 => /lib/arm-linux-gnueabihf/liblzma.so.5 (0x76280000)
        libbz2.so.1.0 => /lib/arm-linux-gnueabihf/libbz2.so.1.0 (0x76260000)
        libffi.so.7 => /lib/arm-linux-gnueabihf/libffi.so.7 (0x76248000)

Humm, if I’ve tried to completely remove the terms that could trigger a link to c++, but now I’m getting really bizare errors, it looks like the generic libc is not working, or is it again a case of trying to link 2 libc ?

[16:53:47] pollivie@DESKTOP-E77I644 /home/pollivie/workspace/cross_comp
> zig build -Dtarget=arm-linux-gnueabihf -Dcpu=cortex_a53
install
└─ install camera_app
   └─ compile exe camera_app ReleaseFast arm-linux-gnueabihf 40 errors
/home/pollivie/.local/opt/zig-v0.15.1/lib/libc/include/generic-glibc/bits/mathcalls-helper-functions.h:20:24: error: unknown type name '__fpclassify'
__MATHDECL_ALIAS (int, __fpclassify,, (_Mdouble_ __value), fpclassify)
                       ^
/home/pollivie/workspace/cross_comp/src/fireforce/camera/main.cpp:1:10: note: in file included from /home/pollivie/workspace/cross_comp/src/fireforce/camera/main.cpp:1:
#include "CameraHandler.hpp"
         ^
/home/pollivie/workspace/cross_comp/src/fireforce/camera/CameraHandler.hpp:10:10: note: in file included from /home/pollivie/workspace/cross_comp/src/fireforce/camera/CameraHandler.hpp:10:
#include <opencv2/opencv.hpp>
         ^
/home/pollivie/workspace/cross_comp/../../sysroot/usr/include/opencv4/opencv2/opencv.hpp:52:10: note: in file included from /home/pollivie/workspace/cross_comp/../../sysroot/usr/include/opencv4/opencv2/opencv.hpp:52:
#include "opencv2/core.hpp"
         ^
/home/pollivie/workspace/cross_comp/../../sysroot/usr/include/opencv4/opencv2/core.hpp:53:10: note: in file included from /home/pollivie/workspace/cross_comp/../../sysroot/usr/include/opencv4/opencv2/core.hpp:53:
#include "opencv2/core/base.hpp"
         ^
/home/pollivie/workspace/cross_comp/../../sysroot/usr/include/opencv4/opencv2/core/base.hpp:58:10: note: in file included from /home/pollivie/workspace/cross_comp/../../sysroot/usr/include/opencv4/opencv2/core/base.hpp:58:
#include "opencv2/core/cvstd.hpp"
         ^
/home/pollivie/workspace/cross_comp/../../sysroot/usr/include/opencv4/opencv2/core/cvstd.hpp:62:12: note: in file included from /home/pollivie/workspace/cross_comp/../../sysroot/usr/include/opencv4/opencv2/core/cvstd.hpp:62:
#  include <cmath>

That’s are c++ include functions (inside the header files). I hate this and that’s one of the reason (for me) for using Zig. But I think you’ll have no chance. Except building the entire camera lib for yourself.

1 Like

sorry, didn’t reply to your task directly, see above.

1 Like

:smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear::smiling_face_with_tear:
I love C++, such a great ecosystem :smiling_face_with_tear:

3 Likes

Well, it is a C++ headers, not a C header.

So that’s to be expected.

In case you want to subvert the way Zig handles linking against C++ standard libraries (and e.g. want to dynamically link against them, which I have not yet figured out how to force; on my system it really likes static linking for some reason), you can add it via exe.root_module.addObjectFile(.{.cwd_relative = <absolute path to C++ standard library>});.

Not the nicest thing to do since it makes the path on your system important, but I haven’t yet figured out something better.

2 Likes

To be completely honest I still haven’t found a way to make Zig cross-link 100% correctly yet when using an alternate sysroot, but given there are multiple issues relating to the --sysroot flag, I’m sure it’ll get looked at eventually.

This is the closest I’ve gotten:

// build.zig
if (!target.query.isNative()) {
    const sysroot = b.sysroot orelse blk: {
        b.getInstallStep().dependOn(&b.addFail("Non-native targets must specify --sysroot").step);
        break :blk "/";
    };
    try b.addSearchPrefix(sysroot);
    // add more folders here
}

// snip

app.root_module.linkSystemLibrary("foo", .{});

Then build with

zig build -Dtarget=arm-linux-gnueabihf.2.31 --sysroot path/to/root

Now Zig will look in your sysroot as though it was the host system root:

error: error: unable to find dynamic system library 'foo' using strategy 'paths_first'. searched paths:
  path/to/root/lib/libfoo.so
  path/to/root/lib/libfoo.a

The problem is that this makes the executable depend on the sysroot path:

readelf -d zig-out/bin/FooApp 

Dynamic section at offset 0x115354 contains 24 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [path/to/root/lib/libfoo.so] //should be either [libfoo.so] or [/lib/libfoo.so]
// snip

We fix this with patchelf, which works fine, but I’d definitely consider it something Zig is doing wrong, or at least something that needs to be configurable somewhere.

1 Like

Now you have lost me. What should Zig do when you are implementing functions from a shared library, that links to other shared libraries?

Wrong was maybe a strong choice of words, but to fully support off-host builds, you need a way to configure library search paths relative to target root.

If --sysroot is /opt/snarf/sysroot, linking libfoo.so which exists at /opt/snarf/sysroot/lib/libfoo.so should be configurable so it searches in /opt/snarf/sysroot/lib on the host and /lib/ on the target.

Currently it’s easy to get a situation where linking works on the host but loading fails on the target.

3 Likes

thank you all for your help, I will get back to y’all one I’ve tried everything suggested in those comments of yours.