Use b.dependency() leads to "module not found"


I’m trying to understand a behavior identified when locating dependency/module in build.zig for a dependency defined as local path.


I have a hobby project zamgba, with a goal to create a library for learning GBA hardware. I also create an example client library consumezamgba as a demo.

In zamgba:src/build/arm.zig!addROM() I have a step, rom.root_module.addAnonymousImport() to implicitly let a rom target import zamgba. However, the statement was not what I want. My original plan was to use b.dependency() like below:

const std = @import("std");
const gba = @import("zamgba");

pub fn build(b: *std.Build) void {
    const optimize = b.standardOptimizeOption(.{});
    const zamgba = b.dependency("zamgba", .{}).module("zamgba");
    const rom = gba.arm.addROM(b, .{ .optimize = optimize, .name = "testcli", .root_source_file = "src/main.zig" });
    rom.root_module.addImport("zamgba", zamgba);

However, the “ideal” approach always complain that the zamgba module is unable to find. I tried to print zamgba.builder.modules. There’s 0 module in the hashmap.

Thus, I moved current design, that every addROM() call adds a reference to zamgba module. It’s doable because addROM() is defined in zamgba (the library part), it can reference to root source file of library’s gba.zig file. (Btw, the approach was learned from ZigGBA project.)

I’m thinking I missed something in build.zig of consumezamgba. However, I didn’t find anything significant when I compare my build.zig project with other projects like zls.

Does anyone see similar issue before? Yes, could you help pointing what I’m doing wrong? Any hint will be appreciated.

1 Like

short version:

b.dependency loads packages as dependencies from the description in build.zig.zon

long version:

  • zamgba project needs to addModule("zamgba",
  • consumezamgba project needs to:
    • have zamgba as package dependency in build.zig.zon
    • load zamgba package description using zamgba_package = b.dependency("zamgba", .{})
    • from your package dependency you can get your module using zamgba_module = zamgba_package.module("zamgba") and add it to a root_module.addImport("zamgba", zamgba_module)

Thanks @dimdin ! I followed your approach and it really works locally!

Before I commit my changes to github, there are two more questions in my mind:

  1. Is module always required in library (zamgba in my case)? Let’s say, if my library is just small enough, can I define no module, then client project (consumezamgba) can reference the library itself, instead of a module?
  2. Is my implementaton (call addAnonymouseImport() in addROM()) a correct approach? Does it cause any potential issue (e.g. repeated code, linkage issue, etc.)? Since I have implemented an addROM() helper function, it’s a temptation for me to return a rom target with dependency import configured, so any folks who use my library does not need to worry about adding a module.
  1. A module is required only if you are calling @import(“module-name”) and you export the module in a dependency.

Packages is a collection of modules and artifacts. An artifact can be any file, usually an executable or a library.

  1. addAnonymousImport is another way to have @import(“module-name”) with a not exposed module.

Ah I see. Thanks @dimdin !

Your answer is a bit different with what I’m looking for, but I prefer exposing a module name instead of setting it within addROM(). Below’s what I’m thinking about:

My decision is based on a fact that I may group different functionalities. So far I implement only GBA boot-up logic. I will add higher level abstractions such as sprite, music, etc. Given it is a learning project, I expect the group names keep changing (maybe) for years. If I hardcode module names in addROM() API, users have to change their @import statement scattered in their source code every time when I update, or their build just breaks. This is a very bad behavior. By exposing module, their build.zig can control the module name aliasing, which protects their code.

I also figure out the question confused me before. My project started from b.addStaticLibrary(). I created a staic library and link to my own demo executable. When I came to a concept of module, I noticed both static library and module require a root_source_file. I was confused because I though static library and module are in a super-set vs sub-set relationship, thus it makes no sense that they both need root_source_file. (Obviously a misunderstanding brought from C/C++ world, as you may see).

By checking Reddit I see the very answer: the static library is used only when a C developer want to use my code. Since I expect everything in Zig for now, there’s no need to introduce static library at all in current stage. If we do have such need, the C interface should also be carefully designed before exposing.

So, thanks again for all your suggestions, and Happy Lunar New Year! (greeting from China :slight_smile: