Hi, I asked a previous question about translating C headers with the build system but I’m still having issues for my use case.
I’m trying to import some types defined in a C library (PipeWire) that only appear in its header files, to make sure enum values match the upstream implementation. It’s only particular enums and #defines, and no runtime code. The way I’m doing it right now with addTranslateC is creating a few problems:
I can only pass one root source file to the translate-c step, but the enums I want are mostly spread out into several headers within spa/include/spa, which aren’t referenced by one clear root header that I can point translate-c towards. I could translate-c each one, but wouldn’t this create a lot of duplicate work and overlapped modules?
The headers reference a lot of extra code that I would want to be sure doesn’t actually get compiled into my binary. For example, if I need to reference a declaration in spa/param/audio/raw-types.h, the header #includes spa/utils/type.h, which #includes spa/utils/defs.h …, creating a lot of unnecessary dependencies. In every case, I only need the actual declaration in the header file and ideally all of the #includes are just ignored. This would be annoying but tolerable, except:
Many of the headers #include libc libraries, so I can’t compile my program, which only references the C headers for enum declarations, without linking libc.
I was hoping there might be some way to lazily or selectively translate in the needed declarations… I’m getting close to just hardcoding the enums and removing the upstream dependency, or maybe looking into generating modified headers in the build script. Is the latter the right solution? I thought I would post this first to see if I’m completely missing something.
One pattern a lot of people use is to write your own .h file that includes the files you need and use that as the root source file. Then you add all the include paths for that.
For point 2, Zig doesn’t process the headers differently than a C compiler would. It will follow all the includes and translate all the macros. I think this is more of a limitation with C and how it is parsed.
For point 3, I think you will need to link libc anyway if you are linking with the Pipewire library. I guess it depends on how you are using the pipewire stuff. We would need more context here in order to provide more guidance.
What I’m planning to try is a reimplementation of the client audio protocol in native Zig. So I don’t actually want to link in PipeWire, only reference its constants.
From how you describe the limitations, maybe the best thing to try is generating some new headers and deleting the unnecessary lines?
I think there are at least 2 ways to do this:
Manually translate the constants you want
Set up a build step to generate it based on the header files you care about.
It depends on how much these change. If it changes frequently or it must be accurate, then generating it from the header files makes sense. But if it’s only a few constants that are easy to keep up with, it’s simplest to translate it manually and keep it up to date.
Yes, I’ve already written a ~1000 line file of enums that set the backing ints to the matching constant values from PipeWire. I just can’t get the builder to expose them to the Zig module in the right way. Even though it’s all written out already, it still felt more foolproof to have them defined from the C source than manually setting them based on how C decides enum values, and is more responsive to upstream.
Do you mean like traversing the C headers line by line at build time to find the decls I need? I can’t find anything in the standard build library that fits what I’m trying to do.
The idea here is that you would write a .zig file that takes the files as input (or a path to them) and then process that to write a new .zig file that contains the generated values.
For example, the zig-wayland project has a scanner.zig file that uses the wayland-scanner output to generate zig buildings for wayland. This is probably more complex than what you need, but the idea is the same.
This will link libc only for the translate step. Your zig module can then import this without linking libc and you should be fine.
Because Zig is lazily evaluated, even though it is all translated, Zig will only inclued what is used.
The headers reference a lot of extra code that I would want to be sure doesn’t actually get compiled into my binary.
…and…
Many of the headers #include libc libraries, so I can’t compile my program, which only references the C headers for enum declarations, without linking libc.
…unless those are some seriously weird C headers there won’t be any overhead in the output binaries, no matter how big the headers are or what other headers they pull in.
Just including a header with C declarations doesn’t leave any traces in your binaries unless you actually use those declarations (like calling a function from those headers or ‘instantiating’ a struct), and this should also be true for any translateC’ed Zig code.
E.g. this C program will compile and link just fine:
extern void bla(void);
int main() {
return 0;
}
…only this will fail to link, because bla() is actually called:
extern void bla(void);
int main() {
bla();
return 0;
}