Is it possible to add a dependency on a module with only C source files?

I’ve been making some improvements to my project’s build setup now that Move many settings from being per-Compilation to being per-Module by andrewrk · Pull Request #18160 · ziglang/zig · GitHub has been merged. One issue that I’ve run into is with using a module containing only C source files and no Zig source files. This looks like it should be possible, since root_source_file in std.Build.Module is optional, but consider the following example:

build.zig:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const exe = b.addExecutable(.{
        .name = "c-module",
        .root_source_file = .{ .path = "src/main.zig" },
        .target = b.host,
    });
    b.installArtifact(exe);

    const c_module = b.createModule(.{});
    c_module.addCSourceFile(.{ .file = .{ .path = "src/main.c" } });
    exe.root_module.addImport("c_module", c_module);
}

src/main.zig:

const std = @import("std");

extern fn get_number() c_int;

pub fn main() void {
    std.debug.print("The number is {}\n", .{get_number()});
}

src/main.c:

int get_number() { return 4; }

Attempting to build this gives the following error:

error: error: C source file '/var/home/ian/src/tmp/c-module/src/main.c' has no parent module

error: the following command exited with error code 1:
/var/home/ian/src/zig/build/stage3/bin/zig build-exe -ODebug --dep c_module --mod root /var/home/ian/src/tmp/c-module/src/main.zig /var/home/ian/src/tmp/c-module/src/main.c --cache-dir /var/home/ian/src/tmp/c-module/zig-cache --global-cache-dir /var/home/ian/.cache/zig --name c-module --listen=- 

This is a simplified example. The real use-case I have for this is I’m trying to add a build helper function to my GObject bindings library to compile a GResources file and return a module containing the resulting C code: feat: add helper function to compile GResources by ianprime0509 · Pull Request #25 · ianprime0509/zig-gobject · GitHub In my test of this functionality, I ended up getting a different error with seemingly the same root cause (the difference being that this real project has other modules involved):

error: error: module 'root' depends on non-existent module 'resources'

One workaround I could potentially use is to make root_source_file an empty Zig file, which does solve the issue, but it seems hacky.

9 Likes

Nice test case. This looks like a bug to me, and with these changes being fresh I think it should be pretty smooth for me to make the fix. Let me dig into it…

10 Likes

I was wondering whether if it’s bug, struggled with it for a bit, nice to know it would be fixed, btw, will some c link options like link_gc_sections be moved to per module? @andrewrk :

❯ zig build -Dtarget=aarch64-linux-musl                                                                                                                                                                                                                                          
install                                                                                                                                                                                                                                                                          
└─ install iw                                                                                                                                                                                                                                                                    
   └─ zig build-exe iw Debug aarch64-linux-musl failure                                                                                                                                                                                                                          
error: error: module 'phy' depends on non-existent module 'nl'

sadly the workaround won’t work for executable as it tries to find main function in module root, which is werid, as the module I depend on is just a c library:

❯ zig build -Dtarget=aarch64-linux-musl                                                                                                                                                                                                                                          
install                                                                                                                                                                                                                                                                          
└─ install iw                                                                                                                                                                                                                                                                    
   └─ zig build-exe iw Debug aarch64-linux-musl 1 errors                                                                                                                                                                                                                         
/usr/lib/zig/lib/std/start.zig:570:45: error: root struct of file 'root' has no member named 'main'                                                                                                                                                                              
    switch (@typeInfo(@typeInfo(@TypeOf(root.main)).Fn.return_type.?)) {                                                                                                                                                                                                         
                                        ~~~~^~~~~                                                                                                                                                                                                                                
/home/xxxxx/.cache/zig/p/122063fe3fb2a41e4e3393af7143e8f50c7712624a20940c760e3b9c24d024192e61/src/root.zig:1:1: note: struct declared here                                                                                                                                      
const std = @import("std");                                                                                                                                                                                                                                                      
^~~~~                                                                                                                                                                                                                                                                            
/usr/lib/zig/lib/std/start.zig:519:12: note: called from here                                                                                                                                                                                                                    
    return @call(.always_inline, callMain, .{});                                                                                                                                                                                                                                 
           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                                                                                                                                                                                                                                  
/usr/lib/zig/lib/std/start.zig:469:36: note: called from here                                                                                                                                                                                                                    
    return initEventLoopAndCallMain();                                                                                                                                                                                                                                           
           ~~~~~~~~~~~~~~~~~~~~~~~~^~                                                                                                                                                                                                                                            
/usr/lib/zig/lib/std/start.zig:425:17: note: called from here                                                                                                                                                                                                                    
    std.os.exit(@call(.always_inline, callMainWithArgs, .{ argc, argv, envp }));                                                                                                                                                                                                 
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When I said “this looks like a bug” what I meant was that the build system created an invalid CLI to pass to the compiler. I typed up a lengthy response to this use case - please see my comments here, and we can discuss further on the issue if you feel that there are more components to this use case that are unsatisfactory.

2 Likes

Why should it work in the first place?

We import C file as a module with a name c_module but never reference it in main.zig:

const std = @import("std");

extern fn get_number() c_int;

pub fn main() void {
    std.debug.print("The number is {}\n", .{get_number()});
}

How is compiler supposed to pick the get_number reference up if addImport doc says Adds an existing module to be used with @import (and we don’t use any @import)?

Awesome, thank you! This change solves the part of my use-case described in this thread nicely. In my case, I don’t need to expose any header files from the C module at all; the generated GResources file performs its initialization (resource registration) via a constructor, so the only thing that matters for this scenario is that the C file is included in the compilation.

I did run into an unrelated problem when testing this in my larger project, so I opened a GitHub issue for it: linkSystemLibrary on multiple modules only applies arguments to one of them · Issue #18628 · ziglang/zig · GitHub Thanks again for all your work in this build system overhaul; I’ve really been liking the results.

1 Like

The only reason I used addImport for this use-case is because I don’t see any other way to say “make this module depend on this other module” so it gets included in the compilation. You’re right that it isn’t actually meant to be imported via @import in main.zig; the “import” is really only expressing a module dependency in this case.

1 Like

Thanks! Indeed, I now see what Zig translates build.zig into:

zig build-exe -ODebug --dep c_module -Mroot=src/main.zig src/main.c -Mc_module ... --name c-module ...

But why didn’t you just do the following?

const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
    .name = "c-module",
    .root_source_file = .{ .path = "src/main.zig" },
    .target = b.host,
    .optimize = optimize,
});
b.installArtifact(exe);

const c_module = b.addStaticLibrary(.{
    .name = "c_module",
    .target = b.host,
    .optimize = optimize,
});
c_module.addCSourceFile(.{ .file = .{ .path = "src/main.c" } });
exe.linkLibrary(c_module);

(I know that this doesn’t bundle everything into a single zig build-exe and yet, I’m curious of potential problems with the above approach)

1 Like

That approach would also have worked, I assume, but I felt like it was overkill for my case (which is just a single C file expected to be compiled into the final executable), at least since it appeared that the root Zig source file of a module was optional.

Additionally, modules will inherit other configuration from the compilation they’re part of (in particular, the optimization mode), so using a module, those parameters don’t need to be passed by the user (the only parameters in my helper function to create a GResources module are the *std.Build context, the target, and a LazyPath to the resource XML file).

1 Like