I migrated my project Dusty to use std.Io exclusively, eliminating the dependency on zio. However, some functionality can’t really be implemented using std.Io and I want to bring zio back, but optionally. Zig build system really doesn’t like transitive dependencies, their version has to match exactly, it’s a pain to manage. So I was thinking, could my project still use @import("zio") even though it doesn’t declare the dependency on its own? Maybe the parent project could inject it somehow? Is that possible?
I’ve realized I can do this:
mod.addAnonymousImport("zio", .{
.root_source_file = b.path("src/zio_stub.zig"),
});
And then this in the app:
const dusty_mod = dusty.module("dusty");
dusty_mod.addImport("zio", zio.module("zio"));
Don’t know how hacky would this be considered, but it works.
That depends on if the api will stay with its semantics of overwriting imports. It is not documented so it is sketchy.
A less sketchy way would be for dusty’s build.zig, instead of exporting the module like normal, to expose a function to get the module where you can provide an optional zio module.
The implementation could be what you have now, but it lets you update it if the addImport semantics change without users of dusty having to change anything. If you care about that.
Not sure if this is the same problem .. but I’m doing this pattern, which may or may not help ?
Then it’s just an option at build time. Is that the same issue you are juggling ?
Your framework has a hard dependency on zio, which means it forces the user to a particular version. This is specially what I’ve been trying to avoid here.
I’ve hit this on Karl’s libraries before. They depended on a particular version of metrics.zig, sometimes different versions across libs, so if you use both libs in your app, the build system just printed strange error message and quits.
I wanted to not declare zio as dependency of dusty at all, to allow the user to use any version. With the iniected import, I can do comptime probing on the imported struct, see if it’s real zio or a stub, and then use it accordingly. I lose ZLS support, but I often use vim without ZLS, so it’s not a huge loss.
Yes, I’ve had those dependency issues with Karl’s libs too with metrics versions getting out of sync across multiple libs.
This is not the same though - the framework here has no hard dependency on zio .. this import above is only surfaced in a user app that imports the framework, then injects zio as the Io param to the framework init() fn. Different problem.
I see what are saying now about the problem at your end though, as you want zio in your actual lib, so that could get messy. Yeah, solution posted makes sense now.
Are you saying that even if you use b.dependency("zio"), the build system will actually ignore the dependency, if it’s not imported? I have hard time believing it, but if it really works, that would be better.
Hmmm .. good point. My case doesn’t really prove or disprove that, since this example code is in the same framework repo. Will experiment and find out what the story is and get back to you on that.
I wouldn’t mind finding a solid and non-hacky solution too, because this little issue crops up often in different situations, and it’s a pain to unravel.
Thx
In my experience, for examples, the solution is to have them as separate sub-dirs in examples/ with their own build system. So this mostly a problem for code publicly exposed, where you want to reduce the dependency chain.
Yep that’s sensible for examples. I should fix that.
Still leaves the problem of building libs that include other libs pinned at a version that mismatches similar imports the user might want to use. As a library author you have no control over what the user tries to do with it.
Or the Karl metrics problem too when you build up a whole arsenal of libs.
If the other lib is small, I’m tempted to simply vendor it into the codebase to avoid the hassle. (Works but not sustainable). Will have a play and see what works.
This might need a change to the build system. Transitive dependencies could be treated as only hints, the user’s direct dependency would have priority and override it. The worst thing that could happen, the project build fails, which is easier to deal with, than forking the libraries to patch the dependencies.