ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.66k stars 2.53k forks source link

add build.zig API for linking against libstdc++ #3936

Open dcao opened 4 years ago

dcao commented 4 years ago

I'm attempting to build a project which links in the hyperscan regex library. The build.zig script links to the hyperscan library and I have a zig source file which includes the hyperscan header file in a @cImport block. However, when I attempt to build the project with zig build, I get this error message:

lld: error: undefined symbol: __gxx_personality_v0
>>> referenced by libunwind.cpp
>>>               /home/david/.local/share/zig/stage1/o/O52Tz8yjwKTfxA_7ZFqf7o46uv59sKmBhDgOE31UEeWVeMBV0cLPD4GnzhB-Hkqz/libunwind.o:(DW.ref.__gxx_personality_v0) in archive /home/david/.local/share/zig/stage1/o/5ImJwmxLqixH7RSFicg8PiRam4oQs9H1Xb3dmdM5J2Rni9aDl1cboZirbXRsFt5r/libunwind.a

The following command exited with error code 1:
/nix/store/v7gp1yjd5dz0gzadxckz18c9k0ygy9xc-zig-0.5.0/bin/zig build-exe /home/david/dev/wordsmith/src/main.zig --library c --library hs --cache-dir /home/david/dev/wordsmith/zig-cache --name ws -isystem /nix/store/66jnj8nqsk9ir44y4fi2kpxb7r3crmqn-hyperscan-5.2.1-dev/include/hs -L /nix/store/islpcsy87hf3jspb7mwlgzgwsfkkinpy-hyperscan-5.2.1/lib -isystem /nix/store/66jnj8nqsk9ir44y4fi2kpxb7r3crmqn-hyperscan-5.2.1-dev/include -isystem /nix/store/p52h688mi6yb62wfsw2ibj3xm6vqq98v-gdb-8.3.1/include -isystem /nix/store/i5wf0wfyd4n4lnvksbbgbwllpq1mq743-valgrind-3.15.0-dev/include -isystem /nix/store/95gcjjxrafz14lcm1qlr0c22cllq0z0g-compiler-rt-7.1.0-dev/include -isystem /nix/store/66jnj8nqsk9ir44y4fi2kpxb7r3crmqn-hyperscan-5.2.1-dev/include -isystem /nix/store/p52h688mi6yb62wfsw2ibj3xm6vqq98v-gdb-8.3.1/include -isystem /nix/store/i5wf0wfyd4n4lnvksbbgbwllpq1mq743-valgrind-3.15.0-dev/include -isystem /nix/store/95gcjjxrafz14lcm1qlr0c22cllq0z0g-compiler-rt-7.1.0-dev/include -rpath /nix/store/8yflb7qdsj3qb791jsiwg67zhcvbxirc-lorri-keep-env-hack-wordsmith/lib64 -rpath /nix/store/8yflb7qdsj3qb791jsiwg67zhcvbxirc-lorri-keep-env-hack-wordsmith/lib --library-path /nix/store/islpcsy87hf3jspb7mwlgzgwsfkkinpy-hyperscan-5.2.1/lib --library-path /nix/store/p52h688mi6yb62wfsw2ibj3xm6vqq98v-gdb-8.3.1/lib --library-path /nix/store/islpcsy87hf3jspb7mwlgzgwsfkkinpy-hyperscan-5.2.1/lib --library-path /nix/store/p52h688mi6yb62wfsw2ibj3xm6vqq98v-gdb-8.3.1/lib --cache on

Build failed. The following command failed:
/home/david/dev/wordsmith/zig-cache/o/z56kk2rvgVc7NHq34aIRdQ-vtWKZuuPxTQrscrdjfBnSZIyXKXsJ0VTog_KCdiXB/build /nix/store/v7gp1yjd5dz0gzadxckz18c9k0ygy9xc-zig-0.5.0/bin/zig /home/david/dev/wordsmith /home/david/dev/wordsmith/zig-cache

My hunch is that the remedy would involve using libstdc++ by passing in --stdlib=libc++ or something similar to clang; however, I'm not sure if this is correct, and even if it was, there's no way to pass arbitrary flags to clang via the zig cli or a build.zig file, as far as I can tell.

andrewrk commented 4 years ago

Yeah you need libstdc++ to the linker. I think we should have a build.zig API to support this use case; trying to link libstdc++ is pretty common, and is relevant for #853.

In the meantime, your best bet is probably looking at zig's own build.zig script, because the self-hosted compiler must link libstdc++.

dcao commented 4 years ago

Thanks for the pointers! I got a workaround working with the following code in my build script:

pub fn build(b: *Builder) !void {
    // snip
    try linkStdCpp(b, exe, ctx);
    // snip
}

const LibraryDep = struct {
    prefix: []const u8,
    libdirs: ArrayList([]const u8),
    libs: ArrayList([]const u8),
    system_libs: ArrayList([]const u8),
    includes: ArrayList([]const u8),
};

const Context = struct {
    cmake_binary_dir: []const u8,
    cxx_compiler: []const u8,
    llvm_config_exe: []const u8,
    lld_include_dir: []const u8,
    lld_libraries: []const u8,
    dia_guids_lib: []const u8,
    llvm: LibraryDep,
};

fn findLLVM(b: *Builder, llvm_config_exe: []const u8) !LibraryDep {
    const shared_mode = try b.exec(&[_][]const u8{ llvm_config_exe, "--shared-mode" });
    const is_static = mem.startsWith(u8, shared_mode, "static");
    const libs_output = if (is_static)
        try b.exec(&[_][]const u8{
            llvm_config_exe,
            "--libfiles",
            "--system-libs",
        })
    else
        try b.exec(&[_][]const u8{
            llvm_config_exe,
            "--libs",
        });
    const includes_output = try b.exec(&[_][]const u8{ llvm_config_exe, "--includedir" });
    const libdir_output = try b.exec(&[_][]const u8{ llvm_config_exe, "--libdir" });
    const prefix_output = try b.exec(&[_][]const u8{ llvm_config_exe, "--prefix" });

    var result = LibraryDep{
        .prefix = mem.tokenize(prefix_output, " \r\n").next().?,
        .libs = ArrayList([]const u8).init(b.allocator),
        .system_libs = ArrayList([]const u8).init(b.allocator),
        .includes = ArrayList([]const u8).init(b.allocator),
        .libdirs = ArrayList([]const u8).init(b.allocator),
    };
    {
        var it = mem.tokenize(libs_output, " \r\n");
        while (it.next()) |lib_arg| {
            if (mem.startsWith(u8, lib_arg, "-l")) {
                try result.system_libs.append(lib_arg[2..]);
            } else {
                if (fs.path.isAbsolute(lib_arg)) {
                    try result.libs.append(lib_arg);
                } else {
                    if (mem.endsWith(u8, lib_arg, ".lib")) {
                        lib_arg = lib_arg[0 .. lib_arg.len - 4];
                    }
                    try result.system_libs.append(lib_arg);
                }
            }
        }
    }
    {
        var it = mem.tokenize(includes_output, " \r\n");
        while (it.next()) |include_arg| {
            if (mem.startsWith(u8, include_arg, "-I")) {
                try result.includes.append(include_arg[2..]);
            } else {
                try result.includes.append(include_arg);
            }
        }
    }
    {
        var it = mem.tokenize(libdir_output, " \r\n");
        while (it.next()) |libdir| {
            if (mem.startsWith(u8, libdir, "-L")) {
                try result.libdirs.append(libdir[2..]);
            } else {
                try result.libdirs.append(libdir);
            }
        }
    }
    return result;
}

fn nextValue(index: *usize, build_info: []const u8) []const u8 {
    const start = index.*;
    while (true) : (index.* += 1) {
        switch (build_info[index.*]) {
            '\n' => {
                const result = build_info[start..index.*];
                index.* += 1;
                return result;
            },
            '\r' => {
                const result = build_info[start..index.*];
                index.* += 2;
                return result;
            },
            else => continue,
        }
    }
}

fn getContext(b: *Builder) !Context {
    // find the stage0 build artifacts because we're going to re-use config.h and zig_cpp library
    const build_info = try b.exec(&[_][]const u8{
        b.zig_exe,
        "BUILD_INFO",
    });
    var index: usize = 0;
    var ctx = Context{
        .cmake_binary_dir = nextValue(&index, build_info),
        .cxx_compiler = nextValue(&index, build_info),
        .llvm_config_exe = nextValue(&index, build_info),
        .lld_include_dir = nextValue(&index, build_info),
        .lld_libraries = nextValue(&index, build_info),
        .dia_guids_lib = nextValue(&index, build_info),
        .llvm = undefined,
    };
    ctx.llvm = try findLLVM(b, ctx.llvm_config_exe);

    return ctx;
}

fn linkStdCpp(b: *Builder, exe: var, ctx: Context) !void {
    if (exe.target.getOs() == .linux) {
        try addCxxKnownPath(b, ctx, exe, "libstdc++.a",
            \\Unable to determine path to libstdc++.a
            \\On Fedora, install libstdc++-static and try again.
        );

        exe.linkSystemLibrary("pthread");
    } else if (exe.target.isFreeBSD()) {
        try addCxxKnownPath(b, ctx, exe, "libc++.a", null);
        exe.linkSystemLibrary("pthread");
    } else if (exe.target.isDarwin()) {
        if (addCxxKnownPath(b, ctx, exe, "libgcc_eh.a", "")) {
            // Compiler is GCC.
            try addCxxKnownPath(b, ctx, exe, "libstdc++.a", null);
            exe.linkSystemLibrary("pthread");
            // TODO LLD cannot perform this link.
            // See https://github.com/ziglang/zig/issues/1535
            exe.enableSystemLinkerHack();
        } else |err| switch (err) {
            error.RequiredLibraryNotFound => {
                // System compiler, not gcc.
                exe.linkSystemLibrary("c++");
            },
            else => return err,
        }
    }

    if (ctx.dia_guids_lib.len != 0) {
        exe.addObjectFile(ctx.dia_guids_lib);
    }

    exe.linkSystemLibrary("c");
}

fn addCxxKnownPath(
    b: *Builder,
    ctx: Context,
    exe: var,
    objname: []const u8,
    errtxt: ?[]const u8,
) !void {
    const path_padded = try b.exec(&[_][]const u8{
        ctx.cxx_compiler,
        b.fmt("-print-file-name={}", objname),
    });
    const path_unpadded = mem.tokenize(path_padded, "\r\n").next().?;
    if (mem.eql(u8, path_unpadded, objname)) {
        if (errtxt) |msg| {
            warn("{}", msg);
        } else {
            warn("Unable to determine path to {}\n", objname);
        }
        return error.RequiredLibraryNotFound;
    }
    exe.addObjectFile(path_unpadded);
}
andrewrk commented 4 years ago

Related: #4682

ashpil commented 1 year ago

I think we should have a build.zig API to support this use case

Is this still planned?