Using dependencies during build time

Hi. I’m trying to use Ziggy and zmime dependencies in the build time. My goal is reading the user configuration file with Ziggy and detecting mime type of the frontend path that i got from Ziggy with zmime and writing the result to a file.

I tried something. But my code has some problems.

src/services/assets.zig:23:31: error: root source file struct 'build' has no member named 'detectFileInfoByFile'
        const info = try zmime.detectFileInfoByFile(file, name);
                         ~~~~~^~~~~~~~~~~~~~~~~~~~~
zig-pkg/zmime-0.3.0-Mse7gaeBAAAWfPAQjBEKQj7qBVofDgLHESoYxxsk_nI6/build.zig:1:1: note: struct declared here
const std = @import("std");
^~~~~
referenced by:
    setup: src/build_helper.zig:9:69
    build: build.zig:64:25
    6 reference(s) hidden; use '-freference-trace=8' to see all references
src/types/config.zig:113:25: error: root source file struct 'build' has no member named 'Deserializer'
        var meta: ziggy.Deserializer.Meta = .init;
                        ^~~~~~~~~~~~
zig-pkg/ziggy-0.2.0-kTg8v5XyBwAL-S3Ci2S2QXYbe0m30-xUIbblJHdFFkKd/build.zig:1:1: note: struct declared here
const std = @import("std");
^~~~~

Probably the error: root source file struct 'build' has no member named 'detectFileInfoByFile' error occurring because theres no export in zmime and ziggy inside build thing or something. i dont know.

here is my files:

build.zig
const std = @import("std");
const assets_service = @import("src/services/assets.zig");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const ziggy = b.dependency("ziggy", .{
        .target = target,
        .optimize = optimize,
    });

    const zmime = b.dependency("zmime", .{
        .target = target,
        .optimize = optimize,
    });

    const solenoid_build = @import("src/build_helper.zig");

    const mod = b.addModule("solenoid", .{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .imports = &.{
            .{ .name = "ziggy", .module = ziggy.module("ziggy") },
            .{ .name = "zmime", .module = zmime.module("zmime") },
        },
    });

    const hello = b.addExecutable(.{
        .name = "hello_world",
        .root_module = b.createModule(.{
            .root_source_file = b.path("examples/hello-world/backend/src/main.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "solenoid", .module = mod },
            },
        }),
    });

    solenoid_build.setup(b, hello) catch |err| {
        std.debug.print("Solenoid build error: {}\n", .{err});
    };

    b.installArtifact(hello);

    const run_cmd = b.addRunArtifact(hello);
    run_cmd.step.dependOn(b.getInstallStep());
    run_cmd.addPassthruArgs();

    const run_step = b.step("run", "Run hello-world example");
    run_step.dependOn(&run_cmd.step);
}
src/build_helper.zig
const std = @import("std");
const assets_service = @import("services/assets.zig");
const Config = @import("types/config.zig").Config;

pub fn setup(b: *std.Build, exe: *std.Build.Step.Compile) !void {
    const config, _ = Config.load(b.graph.io, b.graph.arena, .{ .auto = true });

    if (config.build.?.dir) |assets_path| {
        const assets_mod = try assets_service.generateFrontendAssets(b, assets_path);
        exe.root_module.addImport("assets", assets_mod);
    }
}
src/services/assets.zig
const std = @import("std");
const zmime = @import("zmime");

pub fn generateFrontendAssets(b: *std.Build, assets_dir: []const u8) !*std.Build.Module {
    var filenames = try std.ArrayList([]const u8).initCapacity(b.allocator, 0);

    var dir = try b.root.openDir(b.graph.io, assets_dir, .{ .iterate = true });
    defer dir.close(b.graph.io);

    var it = dir.iterate();
    while (try it.next(b.graph.io)) |entry| {
        if (entry.kind == .file) try filenames.append(b.allocator, try b.allocator.dupe(u8, entry.name));
    }

    var code = try std.ArrayList(u8).initCapacity(b.allocator, 0);
    try code.appendSlice(b.allocator, "pub const assets = [_]Asset{\n");

    for (filenames.items) |name| {
        const full_path = b.fmt("{s}/{s}", .{ assets_dir, name });
        const file = try b.root.openFile(b.graph.io, full_path, .{});
        defer file.close();

        const info = try zmime.detectFileInfoByFile(file, name);
        const mime = zmime.mimeToString(info.mime);

        try code.appendSlice(b.allocator, b.fmt("\n    .{ .path = \"/{s}\", .data = @embedFile(\"{s}\"), .mime = \"{s}\" },", .{ name, full_path, mime }));
    }
    try code.appendSlice(b.allocator, "\n};\n");

    const write_files_step = b.addWriteFiles();
    const generated_file = write_files_step.add("assets.zig", code.items);

    return b.createModule(.{ .root_source_file = generated_file });
}
src/types/config.zig (only needed area of code)

Full Code

        var meta: ziggy.Deserializer.Meta = .init;
        const cfg = ziggy.deserializeLeaky(Config, arena, src, &meta, .{}) catch |err| {
            if (err == error.OutOfMemory) fatal.oom();
            fatal.msg(
                \\Error while loading the Solenoid config file:
                \\
                \\{f}
                \\
                \\
            , .{meta.reportErrorsFmt(arena, .{}, config_file_basename, src, err)});
        };

        cfg.validate();

I read this post and tried its solution.

I dont wanna hardcode the path instead of reading config file with Ziggy. And using basical if-else chain for detecting mimetype. Hope theres a way for it.

Zig version: v0.17.0-dev.2320+1e770dbef

I don’t think I fully understood (nor am I going to be able to take the time to do so from the code you shared, so I’m very sorry in advance), so forgive the very high level response:

  • if you’re trying to use tools from zmime’s build script, you can do @import("zmime") in your build.zig file.
  • if instead you are trying to run code that uses functions implemented by zmime at build time, I’d encourage you to write a little CLI tool that executes that logic and use it and its output as a Run step from within your build script.

I updated post body. i removed unneccessary codes from build.zig.

Second one you said is my situation. I will try that. im waiting for more replies.

For ziggy, the problem is just a small difference in how it is imported.

Your current code:

const ziggy = @import("ziggy");

imports the package’s build.zig, not its library root. The build.zig of ziggy contains:

pub const ziggy = @import("src/root.zig");

So you need to access that exported declaration:

const ziggy = @import("ziggy").ziggy;

Now ziggy refers to the library root, so things like this should work:

ziggy.Deserializer.Meta

For zmime, however, its build.zig does not expose its library root in this way. So you cannot access its library API through the package from your build.zig.

If you need to use zmime at build time, you could fork it and expose its library root from the fork’s build.zig, e.g.:

pub const zmime = @import("src/root.zig");

and then:

const zmime = @import("zmime").zmime;

I’m already using my fork of zmime. i will update it as like you referred. If works, i’ll mark your answer as solution. Thanks!

https://codeberg.org/Erenay/zmime

It worked. Thank you so much! But i have another problem now.

build process works correctly. i import ziggy in my types/config.zig like this:

const ziggy = @import("ziggy").ziggy;

but its not working in runtime. because ziggy doesnt exporting ziggy itself again in root.zig file.

src/types/config.zig:5:31: error: root source file struct 'root' has no member named 'ziggy'
const ziggy = @import("ziggy").ziggy;
              ~~~~~~~~~~~~~~~~^~~~~~
zig-pkg/ziggy-0.2.0-kTg8v5XyBwAL-S3Ci2S2QXYbe0m30-xUIbblJHdFFkKd/src/root.zig:1:1: note: struct declared here
/// Useful for building Ziggy tooling, parses a Ziggy Document keeping
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

So, how can i check if the code is exeucting during build time or runtime? so i can import ziggy export in build time and root module in runtime. for example:

const builtin = @import("builtin");

const ziggy = if (is_buildtime) {
 return @import("ziggy").ziggy;
 } else {
 return @import("ziggy");
 }

(btw, if-else when importing ziggy is just a brainstorm. you can inform me if its a bad idea)

There is a difference between importing a dependency from your build.zig and importing it from your product code.

From your own build.zig, you can only access what the dependency’s build.zig explicitly exposes.

ziggy’s build.zig has:

pub const ziggy = @import("src/root.zig");

So from your build.zig, you need:

const ziggy = @import("ziggy").ziggy;

Here, .ziggy is the declaration exposed by ziggy’s build.zig.

Product code works differently. The dependency’s configured root_source_file is imported directly, so you don’t need to go through the dependency’s build.zig.

Therefore, in your product code:

const ziggy = @import("ziggy");

is all you need.

This is why const ziggy = @import("ziggy").ziggy; fails in your product code: there, @import("ziggy") already refers to the dependency’s root source file, which does not define a ziggy member.

But I’m not importing ziggy in my build.zig. I’m calling setup function in src/build_helper.zig and it imports src/types/config.zig both of production code and setup function calls Config.load(...) function. (you can see that with the codes i shared in the post body. i guess you didnt read it)

So, should I copy-paste the contents of the src/types/config.zig file into another file just to read the configuration during compilation? I dont think thats a very good approach.