Anonymous import not playing nicely with @embedFile

I have a small module that exports functionality which is used in the importing project’s build script. The module uses anonymous imports which are passed to @embedFile. The intended purpose in the original project is changing a string used by the program based on build options. Running zig build in the module’s own root runs fine, but the anonymous imports are not found when the module is used as a dependency. I’ve made a toy example that reproduces the behaviour. Am I misunderstanding something?

The importing project’s build.zig:

const std = @import("std");
const example_module = @import("example_module");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    _ = b.createModule(.{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    });
    example_module.printFoo();
}

build.zig.zon of importing project:

.{
    .name = .minimal_anon_import_repro,
    .version = "0.0.0",
    .fingerprint = 0xac8d4bd961888d11,
    .minimum_zig_version = "0.14.1",
    .dependencies = .{
        .example_module = .{
            .path = "./example_module/"
        }
    },
    .paths = .{
        "build.zig",
        "build.zig.zon",
        "src",
    },
}

build.zig of module:

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

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    const root_mod = b.addModule("example_module", .{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    });
    root_mod.addAnonymousImport("foo", .{ .root_source_file = b.path("foo.txt") } });
}

build.zig.zon of module:

.{
    .name = .example_module,
    .version = "0.0.0",
    .fingerprint = 0xadeb8b104ce18ea0,
    .minimum_zig_version = "0.14.1",
    .dependencies = .{
    },
    .paths = .{
        "build.zig",
        "build.zig.zon",
        "src",
        "foo.txt"
    },
}

root.zig of module:

const text = @embedFile("foo");
const std = @import("std");

pub fn printFoo() void {
    std.log.debug(text, .{});
}

The directory of the project looks like this:

├── build.zig
├── build.zig.zon
├── example_module
│   ├── build.zig
│   ├── build.zig.zon
│   ├── foo.txt
│   └── src
│       └── root.zig
└── src
    └── root.zig

zig build gives me the error:

minimal-anon-import-repro/example_module/src/root.zig:1:25: error: unable to open 'foo': FileNotFound
const text = @embedFile("foo");
                        ^~~~~

One thing you may need to do is add the foo.txt file to the paths here. These paths determine what the importing module is allowed to use. Since foo.txt is not in there, that may be what is causing the mismatch.

1 Like

Ah, I structured the example wrong haha. foo.txt should be in src. I have already tried this, unfortunately, and whether foo.txt is in src or not, and whether or not it’s in the zon paths, it still doesn’t work. But yes, if it’s in the top level, it should be in the zon paths.

1 Like

I have also tried accomplishing this via the module.addOptions route, but this also fails, with an error message about being unable to find the options module. Presumably this is the same underlying issue.

So in the Module, does it embed the file correctly? I’ve never seen @embedFile("module_name") done before, and the docs say:

path is absolute or relative to the current file

To me this seems like it only works with a string that is a path to the file. Does it work if you use foo.txt in there instead of “foo”?

2 Likes

This is supported (build system docs, relevant PR) and I am currently using it successfully in another project. @embedFile with a path name works normally.

1 Like

Today I learned. Cool.

One thought from looking at the build.zig files is that you are importing the example modules build.zig and using it from there. That might be the issue. The build.zig will not have the module resolved, I don’t think. Are you seeing this when you are using the module outside of build.zig?

1 Like

Not 100% sure what you mean by “using it outside of build.zig”? I’m not directly @import-ing it anywhere, if that’s what you mean. I think you’re right about module resolution, that explains why module.addOptions also isn’t working. I’m going to have to change my approach, I suppose…

In the importing project’s build.zig you have the following:

const example_module = @import("example_module");

// Later
example_module.printFoo();

This comes from the build.zig of the module where you are exporting it directly:

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

My current theory is that this goes around the anonymous import, resulting in the FileNotFound error inside the build.zig.

My question was: Do you get the FileNotFound issue if you use the example module in the root.zig of the importing project?

1 Like

But you don’t actually do that, instead you call printFoo in your build.zig:

Using functions imported from a dependencies build.zig is only for special cases (like here Importing custom modules in build.zig (outside fn build) - #3 by Sze), not for building normal applications, but instead to create helper functions that help while building the project.

Here you just want to embed some text inside something you are building, that doesn’t require importing the build.zig of any dependency.
I think instead your importing project should add the dependency normally:

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

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

    const mod = b.createModule(.{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    });
    mod.addImport("example_module", example_dep.module("example_module"));

    const exe = b.addExecutable(.{
        .name = "app",
        .root_module = mod,
    );
    b.installArtifact(exe);
    
    // run steps etc.
}

src/root.zig:

const example_module = @import("example_module");

pub fn main() {
    example_module.printFoo();
}
2 Likes

I can import the example module from the root.zig if I add the import through the importing build.zig:

root_mod.addImport("example_module", b.dependency("example_module", .{}).module("example_module"));

but that’s using the build system normally.

Yes, and this is a helper function for build.zig, my apologies for not clarifying.

The original project is intended to help the user create an Android manifest file, which can then be added to an APK during the build process. The idea with the anonymous import was to switch between using permission name strings directly from the Android JAR, if available, and a fallback set of strings built into the project. But it looks like my original approach will not work.

1 Like