Advice on how to build a C project with Zig

Hey everyone!

I am writing an interpreter from scratch in Zig. I implemented a REPL as a way to interact with it. As a learning exercise, I wanted to incorporate a the GNU’s readline library as a way to get experience working with a C library and it has the happy side effect of improving the REPL.

Since my OS is Linux Mint, I simply installed the readline library with apt install readline and imported it into my interpreter with exe.linkSystemLibrary("readline"). So far, so good!

I wanted to challenge myself further and vendor the library so my (hypothetical) users won’t have to install a system library in order to build and use the interpreter. After watching a stream from Tsoding, I found a smaller drop-in replacement called editline. Here’s a link its repo.

I chose to vendor editline instead of readline because editline is in a GitHub repo that’s trivial to fork and editline has far fewer files and lines of code.

Edit: here’s the link to my forked repo.

Here’s my first stab at getting things started:

const std = @import("std");

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

    const lib_mod = b.createModule(.{
        .root_source_file = null,
        .target = target,
        .optimize = optimize,
        .link_libc = true,
    });
    lib_mod.addCSourceFiles(
        .{
            .files = &.{
                "src/editline.c",
                "src/complete.c",
                "src/sysos9.c",
                "src/sysunix.c",
            },
        },
    );
    lib_mod.addIncludePath(b.path("src"));

    const lib = b.addLibrary(.{
        .linkage = .static,
        .name = "editline",
        .root_module = lib_mod,
    });
    lib.installHeader(b.path("config.h"), "config.h");
    lib.installHeader(b.path("src/editline.h"), "editline.h");
    lib.installHeader(b.path("src/unix.h"), "unix.h");
    lib.installHeader(b.path("src/os9.h"), "os9.h");

    b.installArtifact(lib);
}

Naturally, I hit a few stags off the bat. This build script gives these errors:

install
└─ install editline
   └─ zig build-lib editline Debug native 4 errors
/home/theshinx317/Coding/C/editline/src/editline.h:26:10: error: 'config.h' file not found
#include <config.h>
         ^~~~~~~~~~~
/home/theshinx317/Coding/C/editline/src/sysos9.c:22:10: note: in file included from /home/theshinx317/Coding/C/editline/src/sysos9.c:22:
#include "editline.h"
         ^
/home/theshinx317/Coding/C/editline/src/editline.h:26:10: error: 'config.h' file not found
#include <config.h>
         ^~~~~~~~~~~
/home/theshinx317/Coding/C/editline/src/sysunix.c:24:10: note: in file included from /home/theshinx317/Coding/C/editline/src/sysunix.c:24:
#include "editline.h"
         ^
/home/theshinx317/Coding/C/editline/src/editline.h:26:10: error: 'config.h' file not found
#include <config.h>
         ^~~~~~~~~~~
/home/theshinx317/Coding/C/editline/src/complete.c:24:10: note: in file included from /home/theshinx317/Coding/C/editline/src/complete.c:24:
#include "editline.h"
         ^
/home/theshinx317/Coding/C/editline/src/editline.h:26:10: error: 'config.h' file not found
#include <config.h>
         ^~~~~~~~~~~
/home/theshinx317/Coding/C/editline/src/editline.c:27:10: note: in file included from /home/theshinx317/Coding/C/editline/src/editline.c:27:
#include "editline.h"
         ^
error: warning(compilation): failed to delete '/home/theshinx317/Coding/C/editline/.zig-cache/tmp/2060cee100fc8a98-sysos9.o.d': FileNotFound
warning(compilation): failed to delete '/home/theshinx317/Coding/C/editline/.zig-cache/tmp/95524cbf70fffc00-sysunix.o.d': FileNotFound
warning(compilation): failed to delete '/home/theshinx317/Coding/C/editline/.zig-cache/tmp/f9d6e30e75fe34a6-complete.o.d': FileNotFound
warning(compilation): failed to delete '/home/theshinx317/Coding/C/editline/.zig-cache/tmp/d0691e5a9a830392-editline.o.d': FileNotFound

It’s expecting a config header generated by autoconf.
It works by running autogen.sh which generates a config.h.in and configure.
The latter is an executable that outputs config.h based on config.h.in modified by args to configure.

What you want to do is use b.addConfigHeader to generate the config header like

    const autogen = b.addSystemCommand(&.{"./autogen.sh"});
    const config = b.addConfigHeader(.{ .style = .{ .autoconf_undef = b.path("config.h.in") } }, .{ .key = value});
    config.step.dependOn(&autogen.step);
    lib.step.dependOn(&config.step);

Running autogen.sh and setting .autoconf_undef should be behind a build option as it requires autoconf to be installed on the build system, and providing the config.h.in just gives build errors when you are missing something. Look at config.h.in for explanations of options and run configure without args to see a default config.h

also since you dont seem to be doing it, you can add the repo as a dependency and use dep.path to get at files, its a little annoying to run scripts from dependencies though.

also also All Your Codebase · GitHub has build.zigs for many c/cpp projects you can yoink from, you can also submit yours if you are willing to maintain it.

found an autoconf port on the link above, they just copied the config.h.in to their repo and dont bother running autoconf, so thats one solution i guess. I dont think it changes but im not familiar with it to know that much, i got this far just messing around in a clone of the repo.

2 Likes

addConfigHeader really needs docs lol
messing around and more info/corrections
change code to this

    const config = b.addConfigHeader(.{ .style = .{ .autoconf_undef = b.path("config.h.in") } }, .{ .key = value});
    lib_mod.addConfigHeader(config);

things to know about config header values:

  • a value thats null is will not be defined
  • a value thats void will be defined
  • pointers to arrays of u8 or slice of u8 will be defined as strings, other pointers are not allowed
  • enums/enum literals will be defined as identifiers
  • other than enums, user defined types are not allowed

the only header you install is include/editline.h dont compile sysos9.c` unless you are building for os9.
though they dont let you configure that so it might not support it anymore

1 Like

Thanks for digging in to help!

also also All Your Codebase · GitHub has build.zigs for many c/cpp projects you can yoink from, you can also submit yours if you are willing to maintain it.

Great minds think alike! I already forked the original repo in preparations for this. I’ll retroactively add it the original post, but I’ll give it to you here for good measure.

What you want to do is use b.addConfigHeader to generate the config header like

const autogen = b.addSystemCommand(&.{"./autogen.sh"});
const config = b.addConfigHeader(.{ .style = .{ .autoconf_undef = b.path("config.h.in") } }, .{ .key = value});
config.step.dependOn(&autogen.step);
lib.step.dependOn(&config.step);

I’m a novice when it comes to working with addConfigHeader, so please bear with me :sweat_smile:

From what I’ve gathered, the autogen.sh (which is just the system package autoreconf behind a shell file) and configure are both scripts that help build both header files and Makefiles so that the same source files can be complied across different operating systems, architectures, etc. But now, we have Zig’s build system, so we can side step autogen.sh and just build config.h.in once and commit it to Git history.

The concept I haven’t grokked yet is where .{ .key = value } comes into play. I can appreciate the need for users to add their own configurations at build-time, but I haven’t connected the dots on how this tuple in my build.zig file gets to the config.h or the config.in.h file. I suspect once I get this figured out, I can properly diagnose these new build errors:

install
└─ install editline
   └─ zig build-lib editline Debug native
      └─ configure autoconf_undef header config.h.in to config.h failure
error: /home/theshinx317/Coding/C/editline/config.h.in:4: error: unspecified config header value: 'CLOSEDIR_VOID'
error: /home/theshinx317/Coding/C/editline/config.h.in:7: error: unspecified config header value: 'CONFIG_ANSI_ARROWS'
error: /home/theshinx317/Coding/C/editline/config.h.in:10: error: unspecified config header value: 'CONFIG_EOF'
error: /home/theshinx317/Coding/C/editline/config.h.in:13: error: unspecified config header value: 'CONFIG_SIGINT'
error: /home/theshinx317/Coding/C/editline/config.h.in:16: error: unspecified config header value: 'CONFIG_SIGSTOP'
error: /home/theshinx317/Coding/C/editline/config.h.in:19: error: unspecified config header value: 'CONFIG_TERMINAL_BELL'
error: /home/theshinx317/Coding/C/editline/config.h.in:22: error: unspecified config header value: 'CONFIG_UNIQUE_HISTORY'
error: /home/theshinx317/Coding/C/editline/config.h.in:25: error: unspecified config header value: 'CONFIG_USE_TERMCAP'
error: /home/theshinx317/Coding/C/editline/config.h.in:28: error: unspecified config header value: 'GWINSZ_IN_SYS_IOCTL'
error: /home/theshinx317/Coding/C/editline/config.h.in:32: error: unspecified config header value: 'HAVE_DIRENT_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:35: error: unspecified config header value: 'HAVE_DLFCN_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:38: error: unspecified config header value: 'HAVE_INTTYPES_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:41: error: unspecified config header value: 'HAVE_LIBCURSES'
error: /home/theshinx317/Coding/C/editline/config.h.in:44: error: unspecified config header value: 'HAVE_LIBNCURSES'
error: /home/theshinx317/Coding/C/editline/config.h.in:47: error: unspecified config header value: 'HAVE_LIBTERMCAP'
error: /home/theshinx317/Coding/C/editline/config.h.in:50: error: unspecified config header value: 'HAVE_LIBTERMINFO'
error: /home/theshinx317/Coding/C/editline/config.h.in:53: error: unspecified config header value: 'HAVE_LIBTINFO'
error: /home/theshinx317/Coding/C/editline/config.h.in:56: error: unspecified config header value: 'HAVE_MALLOC_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:59: error: unspecified config header value: 'HAVE_NDIR_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:62: error: unspecified config header value: 'HAVE_PERROR'
error: /home/theshinx317/Coding/C/editline/config.h.in:65: error: unspecified config header value: 'HAVE_SGTTY_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:68: error: unspecified config header value: 'HAVE_SIGNAL_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:72: error: unspecified config header value: 'HAVE_STAT_EMPTY_STRING_BUG'
error: /home/theshinx317/Coding/C/editline/config.h.in:75: error: unspecified config header value: 'HAVE_STDINT_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:78: error: unspecified config header value: 'HAVE_STDIO_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:81: error: unspecified config header value: 'HAVE_STDLIB_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:84: error: unspecified config header value: 'HAVE_STRCHR'
error: /home/theshinx317/Coding/C/editline/config.h.in:87: error: unspecified config header value: 'HAVE_STRDUP'
error: /home/theshinx317/Coding/C/editline/config.h.in:90: error: unspecified config header value: 'HAVE_STRINGS_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:93: error: unspecified config header value: 'HAVE_STRING_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:96: error: unspecified config header value: 'HAVE_STRRCHR'
error: /home/theshinx317/Coding/C/editline/config.h.in:100: error: unspecified config header value: 'HAVE_SYS_DIR_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:104: error: unspecified config header value: 'HAVE_SYS_NDIR_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:107: error: unspecified config header value: 'HAVE_SYS_STAT_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:110: error: unspecified config header value: 'HAVE_SYS_TYPES_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:113: error: unspecified config header value: 'HAVE_TCGETATTR'
error: /home/theshinx317/Coding/C/editline/config.h.in:116: error: unspecified config header value: 'HAVE_TERMCAP_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:119: error: unspecified config header value: 'HAVE_TERMIOS_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:122: error: unspecified config header value: 'HAVE_TERMIO_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:125: error: unspecified config header value: 'HAVE_UNISTD_H'
error: /home/theshinx317/Coding/C/editline/config.h.in:129: error: unspecified config header value: 'LSTAT_FOLLOWS_SLASHED_SYMLINK'
error: /home/theshinx317/Coding/C/editline/config.h.in:132: error: unspecified config header value: 'LT_OBJDIR'
error: /home/theshinx317/Coding/C/editline/config.h.in:135: error: unspecified config header value: 'PACKAGE'
error: /home/theshinx317/Coding/C/editline/config.h.in:138: error: unspecified config header value: 'PACKAGE_BUGREPORT'
error: /home/theshinx317/Coding/C/editline/config.h.in:141: error: unspecified config header value: 'PACKAGE_NAME'
error: /home/theshinx317/Coding/C/editline/config.h.in:144: error: unspecified config header value: 'PACKAGE_STRING'
error: /home/theshinx317/Coding/C/editline/config.h.in:147: error: unspecified config header value: 'PACKAGE_TARNAME'
error: /home/theshinx317/Coding/C/editline/config.h.in:150: error: unspecified config header value: 'PACKAGE_URL'
error: /home/theshinx317/Coding/C/editline/config.h.in:153: error: unspecified config header value: 'PACKAGE_VERSION'
error: /home/theshinx317/Coding/C/editline/config.h.in:156: error: unspecified config header value: 'STAT_MACROS_BROKEN'
error: /home/theshinx317/Coding/C/editline/config.h.in:161: error: unspecified config header value: 'STDC_HEADERS'
error: /home/theshinx317/Coding/C/editline/config.h.in:164: error: unspecified config header value: 'SYS_UNIX'
error: /home/theshinx317/Coding/C/editline/config.h.in:167: error: unspecified config header value: 'VERSION'
error: /home/theshinx317/Coding/C/editline/config.h.in:170: error: unspecified config header value: 'size_t'

the .{.key = value} gets turned into #define key value in a header file generated by b.addConfigHeader

look at my second comment, it clarifies and corrects some things

I would start by copying the values from config.h generated by configure with no args, as that is the defaults the current build system uses.

2 Likes

Thanks for the explanation!

After applying your feedback, I’m able to get the file libeditline.a to build.

To test everything is working, I wrote this toy app:

#include <editline/readline.h>
int main()
{
    char *inpt;
    int i = 0;
    while ( i < 10 )
    {
        inpt = readline("Enter text: ");
        add_history(inpt);
        printf("%s", inpt);
        printf("\n");
        ++i;
    }
    return 0;
}

To build this app, I installed the editline library by following the instructions from the original editline repo. I then copied the static library file and compiled the toy app with:

cp /usr/local/lib/libeditline.a libeditline.a && \
gcc demo.c -o demo libeditline.a

When I run the demo executable, everything works as intended.

However, when I copy the static library file from my Zig build and compiled the toy app with:

cp ../zig-out/lib/libeditline.a ./libeditline.a && \
gcc demo.c -o demo libeditline.a

I get hundreds of lines of errors. This is just a small snippet:

/usr/bin/ld: libeditline.a(/home/theshinx317/Coding/C/editline/.zig-cache/o/2462b7b02d858e0c12c2b1ae23e3f4db/editline.o): in function `rl_getc':
/home/theshinx317/Coding/C/editline/src/editline.c:240: undefined reference to `__ubsan_handle_type_mismatch_v1'
/usr/bin/ld: libeditline.a(/home/theshinx317/Coding/C/editline/.zig-cache/o/2462b7b02d858e0c12c2b1ae23e3f4db/editline.o): in function `el_print_columns':
/home/theshinx317/Coding/C/editline/src/editline.c:312: undefined reference to `__ubsan_handle_pointer_overflow'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:312: undefined reference to `__ubsan_handle_type_mismatch_v1'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:312: undefined reference to `__ubsan_handle_nonnull_arg'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:311: undefined reference to `__ubsan_handle_add_overflow'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:316: undefined reference to `__ubsan_handle_add_overflow'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:319: undefined reference to `__ubsan_handle_divrem_overflow'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:322: undefined reference to `__ubsan_handle_divrem_overflow'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:322: undefined reference to `__ubsan_handle_add_overflow'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:324: undefined reference to `__ubsan_handle_pointer_overflow'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:324: undefined reference to `__ubsan_handle_type_mismatch_v1'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:324: undefined reference to `__ubsan_handle_nonnull_arg'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:324: undefined reference to `__ubsan_handle_sub_overflow'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:325: undefined reference to `__ubsan_handle_type_mismatch_v1'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:324: undefined reference to `__ubsan_handle_pointer_overflow'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:327: undefined reference to `__ubsan_handle_add_overflow'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:328: undefined reference to `__ubsan_handle_add_overflow'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:323: undefined reference to `__ubsan_handle_add_overflow'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:322: undefined reference to `__ubsan_handle_add_overflow'
/usr/bin/ld: libeditline.a(/home/theshinx317/Coding/C/editline/.zig-cache/o/2462b7b02d858e0c12c2b1ae23e3f4db/editline.o): in function `tty_puts':
/home/theshinx317/Coding/C/editline/src/editline.c:193: undefined reference to `__ubsan_handle_type_mismatch_v1'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:194: undefined reference to `__ubsan_handle_pointer_overflow'
/usr/bin/ld: /home/theshinx317/Coding/C/editline/src/editline.c:194: undefined reference to `__ubsan_handle_type_mismatch_v1'
/usr/bin/ld: libeditline.a(/home/theshinx317/Coding/C/editline/.zig-cache/o/2462b7b02d858e0c12c2b1ae23e3f4db/editline.o): in function `tty_put':
/home/theshinx317/Coding/C/editline/src/editline.c:180: undefined reference to `__ubsan_handle_pointer_overflow'

My guess is GCC’s linker cannot find many symbols because of a missing header file in the Zig build script. Does this feel like the root cause? If so, how can I add the missing header file?

If it’s easier for those following the thread, here’s a perma-link to the code at the time of writing.

when building in debug mode (the default) zig enables ubsan for c code, essentially adds safety checks to the c code. if i build with release fast or small it works.
to make it work with debug builds just add .sanitize_c = false to createModule options.

1 Like

It finally compiles and works!!! Thank you so much!

1 Like