Build.Module resolved_target/optimize

What is the meaning of .target and .optimize that I pass to b.createModule? E.g., if, during my build, I want to compile my executable both in Debug and Release, I can’t create my root module once, and then pass it to two addExecutable, right? Because root’s module optimize is what matters.

But suppose my module has imports. Do I need to re-create all those modules as well, if I have two executables? It looks like I don’t have to, as I can leave target/optimize null.

But then, why won’t I always create executable via an intermediate module, whose only purpose is to set optimize, and then just re-export pub fn main from the “real” module?

Sorry that this is such a vague question, I just feel like I don’t grasp a big picture here…

2 Likes

One interesting angle of specifically .target being module-specific was that on embedded ARM, it allows you to compile mixed ARM-mode and Thumb-mode programs. I was surprised to learn that myself, but apparently that’s at least one possible use case.

2 Likes

Both .target and .optimize are optional. But null is only allowed in non-root modules.

For dependencies, you can override these (e.g. I have set .target = 'native' for nasm while cross-compiling). If you don’t set them, they are null; the actual value is derived from something that makes sense.

Try to not set these parameters for your non-root modules.

2 Likes

Having .target separately also allows you for instance building eBPF with the zig toolchain and then embedding this withing the real executable.

I quite also quite like having .optimize on a module level as this allows for instance for having a math heavy submodule always build with at least ReleaseSafe. I also use it for building some trusted dependencies in ReleaseFast.

1 Like

What bugs me is that they are not optional for the root module.

My mental model that module’s target/optimize an be “default” (specified by the final artifact we are linking) or “overridden” (specified by the module itself).

So I’d expect Step.Compile to have non-null target and optimize, and use that as a default for all modules, including root. Because I very much want to re-use the same root module for different Step.Compiles.

3 Likes

FWIW, in my game I just went with the other extreme – re-create all modules for every variant of the executable (my build.zig allows building for multiple platforms at once). Of course, the definitions for the more complicated ones are factored out into functions.

3 Likes

Similarly, needing a second, identical root module for zls that does not touch the install step feels suboptimal.

1 Like

FWIW, I’d be totally fine with that! My only problem is that I don’t understand whether I am supposed to re-use modules between different Compiles…

As far as I can tell, the Module struct is basically a representation of a bunch of arguments to be passed onto zig build-lib/zig build-exe during the make phase, so I personally don’t really think either solution is outright wrong. Slightly more wasteful in the re-creation case during configure, maybe, but in the grand scheme of things…?

Here, I’d probably look for any hint of planned future changes to that from the core team. I’m personally not aware of any as of now.

1 Like