Building C Code with Zig

· diesisteintest's blog


Why would you want that #

My main reason for wanting to build c code with Zig is the easy cross compilation and use of Musl Libc.

1$ zig build -Dtarget=x86_64-linux-musl -Doptimize=ReleaseSmall

With that command you can easy build a project for x86_64-linux and linking with musl statically.

How to do it the wrong way #

If you want to only build a single c file with zig you can use zig cc and pass the same optioans as to gcc. At least for linking.

Lets assume we have a project which uses libcurl for making https request. The code looks like this:

 1#include <stdio.h>
 2#include <curl/curl.h>
 3#include <string.h>
 4
 5int main(int argc, char ** argv) {
 6    if (argc < 2) {
 7        fprintf(stderr, "ERROR: missing argument\n\tUsage: %s <url with out https://>\n", argv[0]);
 8        return -1;
 9    }
10    int domain_len = strlen(argv[1]);
11    char url[8 + domain_len + 1];
12    memset(url, 0, 8 + domain_len + 1);
13    memcpy(url, "https://", 8);
14    memcpy(url + 8, argv[1], domain_len);
15    url[8 + domain_len] = '\0';
16    curl_global_sslset(CURLSSLBACKEND_OPENSSL, NULL, NULL);
17
18    curl_global_init(CURL_GLOBAL_DEFAULT);
19    CURL* curl;
20    curl = curl_easy_init();
21
22    if( !curl) {
23        fprintf(stderr, "ERROR: Failed to initialize curl\n");
24        curl_global_cleanup();
25        return -1;
26    }
27    curl_easy_setopt(curl, CURLOPT_URL, url);
28    curl_easy_setopt(curl, CURLOPT_CA_CACHE_TIMEOUT, 604800L);
29
30    CURLcode res = curl_easy_perform(curl);
31    if (res != CURLE_OK) {
32        fprintf(stderr, "ERROR: curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
33        curl_global_cleanup();
34        return -1;
35    }
36    curl_easy_cleanup(curl);
37    curl_global_cleanup();
38}

compiling the code looks like this: cc $(pkg-config --libs libcurl) -g -Wall -Wextra $(pkg-config --cflags libcurl) -o curl-test main.c. When we want to use zig cc we just need to replace cc with zig cc: zig cc $(pkg-config --libs libcurl) -g -Wall -Wextra $(pkg-config --cflags libcurl) -o curl-test main.c

The correct way (build.zig) #

But i dont want to memorize a specific command, run CC="zig cc" make or tap my up arrow to get to the curect command. I want to just run zig build or zig build -Doptimize=ReleaseSmall for a release build.

To be able to do that we can make use of the build.zig file. The build.zig file configures a build system, and yes the file is just zig code.

 1const std = @import("std");
 2
 3pub fn build(b: *std.Build) !void {
 4    const target = b.standardTargetOptions(.{});
 5    const optimize = b.standardOptimizeOption(.{});
 6    const exe = b.addExecutable(.{
 7        .name = "curl-test",
 8        .target = target,
 9        .optimize = optimize,
10    });
11    exe.linkLibC();
12    exe.addCSourceFiles(&.{"main.c"}, &.{ "-Wall", "-Wextra" });
13    exe.linkSystemLibrary("libcurl");
14    b.installArtifact(exe);
15}

Lets go through what this file does.

First we need to import Zig's stdlib. We then define the function which configures the build system. We get the target and optimize options set by -Dtarget abd -Doptimize. The const exe = b.addExecutable file defines our executable. The name parameter defines how the file will be named. target and optimize you make the executable actually use the command line provided options. To use a c library like Libcurl we need to link libc, this is done by exe.linkLibC(). The next line addes C Source files and we then link a system library. This can be the pkg-config and the name the linker uses. In this case it could be libcurl (used by pkg-config) or curl (used by linker when running with arg -lcurl) and the last line tells the build system to install it to zig-out/bin/<our name defined in addExecutable>.


Copyright (C) 2024 diesisteintest

Codeblocks under 0BSD and Content under CC-BY-SA 4.0