Importation and dependencies

Hi there!

I’m new to Zig (the latest 0.12.0) and need some clarification about the module/package/build system…

Here is my example project :

build.zig

src/

-- subdir1

-- -- hello.zig

-- subdir2

-- -- print.zig

Each hello.zig and print.zig need each other functions, declarations, etc…

I’m trying to import with @import(“../subdir2/print.zig”) and I got this error :

error: import of file outside module path: '../subdir2/print.zig'
const print = @import("../subdir2/print.zig").Node;

I don’t understand how to manage this use case, please bring me some explications.

Thanks in advance for your help!

Oh sorry, thanks for the edit!

1 Like

No problem - welcome to Ziggit :slight_smile:

I take it you come from a background such as C from how you tried to structure that import? Either way, I can see why this would bother you.

The way I handle this is by declaring a module in build.zig and then linking that module to my project.

That way, you can just import it like const something = @import("something");

I’d suggest starting here, see if you find what you need, and if you run into more problems we can help as you go along: Build system tricks

Is this the good way to declare a module?

_ = b.addModule("hello", .{ .root_source_file = .{ .path = "src/subdir1/hello.zig" }

Close - you need to make sure you don’t drop the module you just created because you need to link it to something executable.

I’m still working on the build system for my machine learning library, but you can see an example here: Metaphor/build.zig at main · andrewCodeDev/Metaphor · GitHub

First, I do exactly what you’ve done but I capture the return:

const mp_module = b.addModule("metaphor", .{ 
    .root_source_file = .{ .path = b.pathJoin(&.{ "src", "metaphor.zig" }) } 
});

Then, when I am linking it to the examples, I use that module like so:

example.root_module.addImport("metaphor", mp_module);
1 Like

Ok thank you, but now it seems that the error occurs only in the context of tests. So I follow the same principle but with no effect this time…

const unit_tests = b.addTest(.{
        .root_source_file = .{ .path = "src/main.zig" },
        .target = target,
        .optimize = optimize,
    });

    unit_tests.root_module.addImport("mod", mod);

Are you trying to test the module or are you trying to write tests that use a module? I’ll point you to this thread here: How to test modules? - #2 by squeek502

I’m trying to write tests that use a module

Note that addModule provides the module for users of your project so they can import it in their projects. If you just want to use it privately in your own project, use createModule instead:

const hello_mod = b.createModule(.{
    .root_source_file = .{ .path = "src/subfir1/hello.zig" },
});

Then, instead of addImport you use addAnonymousImport:

exe.root_module.addAnonymousImport("hello", hello_mod);
3 Likes

This should work. What error are you getting?

1 Like

Yep, it’s working indeed, thank you! However, if I have 100 files or more I suppose I don’t have to add 100 modules?
Is there a package notion or it’s specific to libraries or codes that are supposed to be used by someone else?

I think you should add that bit to the “build system tricks” doc - that’s a good one.

1 Like

Good idea indeed. Done!

1 Like

Note that in Zig, you can directly import other Zig files in your same directory or in a subdirectory, so you could put a file at the upper ‘src’ directory level that imports the lower level files:

File: src/api.zig

pub const hello = @import("subdir1/hello.zig");
pub const print = @import("subdir2/print.zig");

And then crete the module using this file as the root_source_file. Let’s say you provide it via anonymous import to your other files as api, then you can for example:

File: src/subdir1/hello.zig

const print = @import("api").print;
// use print
4 Likes

Ok, I didn’t know what I was looking for, but it was exactly that!
Too simple to be confusing finally…

Thanks a lot!

1 Like

Hi there!

I’m back with my modules, despite the solution above works well for the executable itself, I repeated the same operation for the tests:

const unit_tests = b.addTest(.{
     .root_source_file = .{ .path = "src/tests/run.zig" },
     .target = target,
     .optimize = optimize,
 });

 unit_tests.root_module.addImport(“a”, a);
 unit_tests.root_module.addImport(“b”, b);
 unit_tests.root_module.addImport(“c”, c);

And I encountered an error:

error: no module named ‘a’ available within module test
const a = @import(“a”);

It’s clear I misunderstood something but I don’t know what.

I appreciate any help you can provide. Thanks!

There are two ways to add internal modules as import:

  1. Using addAnonymousImport
unit_tests.root_module.addAnonymousImport(
    "a", 
    .{ .root_source_file = b.path("src/a/a.zig") },
);
  1. Using createModule and addImport
const a_module = b.createModule(.{ .root_source_file = b.path("src/a/a.zig") });
unit_tests.root_module.addImport("a", a_module);
1 Like

Yes, sorry I didn’t provide enough details, for the executable artifact I wrote:

const optimize = b.standardOptimizeOption(.{});
// Modules declaration
const a = b.createModule(.{ .root_source_file = .{ .path = "src/a/mod.zig" } });
const b = b.createModule(.{ .root_source_file = .{ .path = "src/b/mod.zig" } });
 const c = b.createModule(.{ .root_source_file = .{ .path = "src/c/mod.zig" } });

exe.root_module.addImport(“a”, a);
c.addImport(“a”, a);
b.addImport(“a”, a);

exe.root_module.addImport(“b”, b);
a.addImport(“b”, b);

exe.root_module.addImport(“c”, c);
a.addImport(“c”, c);

And then, for the unit tests, I reuse those created modules with the code I just shared above.

Let me know if it’s not clear enough.

Joining the “api” example I posted above and @dimdin 's advice, I think the following would be a simpler solution for you:

  1. Create a file src/api.zig containing:
pub const a = @import("src/a/mod.zig");
pub const b = @import("src/b/mod.zig");
pub const c = @import("src/c/mod.zig");
  1. In build.zig
const api_mod = b.createModule(.{
    .{ .root_source_file = "src/api.zig" },
});

const paths = [_][]const u8{
    "src/a/mod.zig",
    "src/b/mod.zig",
    "src/c/mod.zig",
};

for (paths) |path| {
    _ = b.createModule(.{
        .root_source_file = path,
        .imports = &.{
            .{ .name = "api", .module = api_mod },
        },
    });
}

exe.root_module.addImport("api", api_mod);
unit_tests.root_module.addImport("api", api_mod);

Then in any of these files (where applicable) you can:

const a = @import("api").a;
const b = @import("api").b;
const c = @import("api").c;

The b is redeclared: const b = b.createModule(

You can import other modules in submodules, but you cannot have cycles.

    const a_module = b.createModule(.{
        .root_source_file = b.path("src/a/mod.zig"),
        .imports = &.{
            .{ .name = "b", .module = b_module },
            .{ .name = "c", .module = c_module },
        },
    });
1 Like