Best practices for consuming C libraries

Say I wanted to create a library, that itself requires an SSH client. I’m a bit lost how to do that “properly” (Questions at the end).

I think I have these options:

  1. Dynamically link with an installed host package (e.g. libssh)
  2. Statically link with an installed host package
  3. Download and build libssh from build.zig and link statically
  4. Port it to Zig or write it from scratch

(1) is what you would normally do (outside zig world), in order to benefit from updates to libssh. The only painful aspect is that your deliverable has a runtime dependency, that you have to maintain if you’re packaging or you just silently depend on the library and your program fails.

In Zig world, doing that makes it impossible or hard (I never tried) to cross compile, because you don’t have the headers (or they may contain definitions specific to the compilation host or something).

A small inconvenience is also that you had to install development packages for libssh on the compilation host (not an issue for libssh, because it only has a dev package, but it’s just an example).

(2) Is like (1), but you wouldn’t have the runtime dependency and the benefit of the update. You would however use a curated build from the OS packaging and benefit from whatever build settings they used. That of course is of no value if you wanted to cross compile.

(3) Here you would let build.zig download the sources, build them and link them, maybe provide a wrapper to make it look like zig.

That of course is easier said than done, especially if you want to cross compile.

It’s quite a while since I build C projects, but most used something like autoconf where part of the build process consists of analyzing the host operating system and checking for various features. That doesn’t play well with cross compilation, so you would need to run the build in a VM or container if you want to use the project build process.

Because of that, it seems to be better to build via build.zig. But that seems to be a huge effort for complex projects. You had to do all the autoconf/cmake analysis mostly from scratch.

Then there is the problem of possible upstream changes breaking the zig build. So this needs to be maintained.

(4) Obviously the preferred choice, but time.

Neither of these four options look especially appealing to me, if my priority would be to just get access to some library. Since I’m currently thinking about a project that needs quite a lot of such libraries and need (well want really hard) the cross compilation feature, this is a bit scary.

There are some build.zig wrappers for libraries, but not many and off those, they do not appear to be maintained (someone did what needed to be done for a project, and that’s the end of it. And I really wouldn’t want to maintain each wrapper I might write in the future either).

This looks a lot like we would need something like homebrew, ports, debian source packages, or AUR to make using C dependencies feasible for all but the most hard core enthusiasts. This is a bit heartbreaking considering how much work all of these projects spend to get where they are, and yet here is a new beginning.

So here are my questions:

What would currently be canonical way to consume C libraries? Especially in the scenario where a zig library wraps/extends C and is then consumed by other zig projects.

Does Zig world have a strategy in mind how to handle this problem in the future? On one hand, it’s clear that Zig is aiming at replacing C progressively, but until there are zig ports for important functionality and Zig becomes much more popular, this is not helping yet.

Build.zig is a great concept, because it’s more powerful than all alternatives I know and still easy to use. But the real value of existing build tools for C lies in that field of autoconf and also in the work that has been done by distributions and package managers preconfiguring builds for target platforms. No matter how good the build tool is, the amount of work to be done to get access to libraries supporting zig features (that’s basically cross compilation insofar C libraries are concerned) is quite intimidating.

Lastly, I saw various videos and short tutorials explaining how to interface with C. But it would be great if there was a single detailed documentation showing the different options and demo code. Is there something like that (something that shows more than typical or trivial use cases)?

FYI linux headers can be downloaded from ubuntu mirror, I do link to libcurl and cross-compile from macos to linux and it works fine.

Here’s how I do it, but obviously it will require some adjustments for your case. IIRC the important part is to have .pc files in the correct path.

mkdir -p zig-out/usr/{lib,include}
cd zig-out

curl -L http://security.ubuntu.com/ubuntu/pool/main/c/curl/libcurl4_7.81.0-1ubuntu1.18_amd64.deb -o libcurl.deb
curl -L http://security.ubuntu.com/ubuntu/pool/main/c/curl/libcurl4-openssl-dev_7.81.0-1ubuntu1.18_amd64.deb -o libcurl-dev.deb

ar -x libcurl-dev.deb data.tar.zst
tar --use-compress-program=unzstd -xvf data.tar.zst
cp -r usr/include/x86_64-linux-gnu/* usr/include/

ar -x libcurl.deb data.tar.zst
tar --use-compress-program=unzstd -xvf data.tar.zst
cp -r usr/lib/x86_64-linux-gnu/* usr/lib/

cd ..
zig build -Doptimize=ReleaseSafe -Dtarget=x86_64-linux-gnu --search-prefix zig-out/usr
1 Like