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
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
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!
dimdin
April 30, 2024, 4:10pm
17
There are two ways to add internal modules as import:
Using addAnonymousImport
unit_tests.root_module.addAnonymousImport(
"a",
.{ .root_source_file = b.path("src/a/a.zig") },
);
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:
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");
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;
dimdin
April 30, 2024, 4:40pm
20
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