Building wasm with dependencies

Hi all! Long time listener, first time caller. Thanks in advance—and still kind of a n00b, so be gentle.

I’ve been fighting with the Zig build system for the past few days and I’ve made some progress, but have hit a wall and am finally asking for help. I fear I’m missing something obvious.

I’m trying to build a wasm module that includes dependencies.

So I try the incantation:

zig build-exe src/web.zig -target wasm32-freestanding -fno-entry -rdynamic

I get error: no module named 'jzignet' available within module web. (Why jzignet? I have my reasons. Ask and I’ll tell you.)

Fair enough: I need a build.zig/build.zig.zon setup to manage external dependencies. (And I can’t just copy files across into my project; jzignet is ultimately a wrapper around C code, which requires its own build.zig setup.)

So here’s my build.zig (still flailing on this; it may be a mess):

const std = @import("std");

pub fn build(b: *std.Build) void {
    const optimize = b.standardOptimizeOption(.{});
    const target = b.standardTargetOptions(.{});
    const wasm_target = b.resolveTargetQuery(.{
        .cpu_arch = .wasm32,
        .os_tag = .freestanding,
    });

    // here's the dependency in question
    const jz = b.dependency("jzignet", .{
        .target = target,
        .optimize = optimize,
    });

    // first, an exe using the standard target
    // this works!
    const exe = b.addExecutable(.{
        .name = "hello",
        .root_source_file = .{ .path = "src/hello.zig" },
        .target = target,
        .optimize = optimize,
    });

    exe.root_module.addImport("jzignet", jz.module("jzignet"));
    exe.linkLibrary(jz.artifact("jzignet"));

    b.installArtifact(exe);

    const run_cmd = b.addRunArtifact(exe);

    run_cmd.step.dependOn(b.getInstallStep());

    const run_step = b.step("run", "Run the thing");
    run_step.dependOn(&run_cmd.step);

    // now the wasm bundle
    // this does not work, errors detailed below
    const wasm = b.addExecutable(.{
        .name = "wasm",
        .root_source_file = .{ .path = "src/web.zig" },
        .target = wasm_target,
        .optimize = .ReleaseSmall,
    });

    wasm.root_module.addImport("jzignet", jz.module("jzignet"));
    wasm.linkLibrary(jz.artifact("jzignet"));

    const wasm_cmd = b.addRunArtifact(wasm);
    wasm_cmd.step.dependOn(b.getInstallStep());
    // here are my attempts at passing command line arguments; not the right place
    wasm_cmd.addArg("-fno-entry");
    wasm_cmd.addArg("-rdynamic");

    const wasm_step = b.step("wasm", "build wasm");
    wasm_step.dependOn(&wasm_cmd.step);

    b.installArtifact(wasm);
}

So I get the following error invoking zig build wasm:

error: wasm-ld: entry symbol not defined (pass --no-entry to suppress): _start

I can’t figure out where/how to pass -fno-entry or -rdynamic in the wasm case. The above represents my most recent (failing) attempt. I’ve tried in in the CLI invocation: zig build wasm -fno-entry -rdynamic and get unrecognized argument: '-fno-entry'. (I will note that the following command failed invocation dump for zig build wasm does not include -fno-entry or -rdynamic anywhere.)

That said, I am also confused because I tried to get just something to work, so I decided to give myself an entry point in the program: I added a main (and start) function for good measure into web.zig, and I’m still getting this same entry symbol not defined error.

I’ve consulted official build system docs, as well as this post on this forum: Build system tricks.

I’m not even getting new and interesting errors at this point.

I’m sorry for the block of text and code; this has been a frustrating few days. Building things is complicated. Thank you in advance. :face_holding_back_tears:

Welcome to Ziggit @kredati!

I don’t have practical knowledge with wasm yet, but this topic seems to use wasm_module.entry = .disabled;

1 Like

Indeed! Thank you SO MUCH. I knew it was easy, just not-well-documented.

The relevant options are fields on the wasm executable artifact: wasm.entry = .disabled and wasm.rdynamic = true.

1 Like

I don’t know whether this applies, but it seems this might be a different alternative to wasm.rdynamic = true in some cases?:

That one is useful to know but it’s specifically for WASI and has no effect on wasm32-freestanding. For freestanding Wasm you only need to override entry and rdynamic.

2 Likes

Thank you for the clarification, I think it will be helpful for others looking for answers, to have these links between these topics along with your explanation!

I think eventually it may make sense for somebody, more knowledgeable then me, about wasm, to create a docs-topic that bundles / links / organizes some of these infos. I enjoy your through answers, do you want to create a docs topic about wasm / WASI?
I think it would be a great addition.

1 Like

There’s not a lot to say about Wasm, the -fno-entry and -rdynamic/--export requirements are documented in the WebAssembly section of the language reference (you just need to find the corresponding fields in the build system) and if you’re targeting WASI you’re presumably already familiar with the command/reactor concepts. Outside of those specifics it’s more or less the same as any other target.

It may be more interesting to document Wasm from the side of interop with JavaScript and the browser, but I personally have no interest in writing about it until BYOOS (Bring-Your-Own-OS, which was removed in 0.12.0 with no replacement) is restored.

1 Like