Building box2d for wasm32

I’m trying to use the allyourcodebase package of box2d to build a wasm32 static library that emscripten can link to when building my main project (that is written in C).

this is the command I’m running:

git clone https://github.com/allyourcodebase/box2d
cd box2d
cat >build.zig <<EOF 
const std = @import("std");

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

    const box2d_dep = b.dependency("box2d", .{});

    const lib = b.addStaticLibrary(.{
        .name = "box2d",
        .target = target,
        .optimize = optimize,
    });
    lib.linkLibC();
    lib.addIncludePath(box2d_dep.path("include"));
    lib.installHeadersDirectory(box2d_dep.path("include"), "", .{});
    lib.addCSourceFiles(.{
        .root = box2d_dep.path("src"),
        .flags = &.{
            "-std=c17",
        },
        .files = &.{
            "aabb.c",
            "array.c",
            "bitset.c",
            "body.c",
            "broad_phase.c",
            "constraint_graph.c",
            "contact.c",
            "contact_solver.c",
            "core.c",
            "distance.c",
            "distance_joint.c",
            "dynamic_tree.c",
            "geometry.c",
            "hull.c",
            "id_pool.c",
            "island.c",
            "joint.c",
            "manifold.c",
            "math_functions.c",
            "motor_joint.c",
            "mouse_joint.c",
            "prismatic_joint.c",
            "revolute_joint.c",
            "shape.c",
            "solver.c",
            "solver_set.c",
            "stack_allocator.c",
            "table.c",
            "timer.c",
            "types.c",
            "weld_joint.c",
            "wheel_joint.c",
            "world.c",
        },
    });
    b.installArtifact(lib);
}
EOF
zig build -Dtarget=wasm32-emscripten

which produces the error:

install
└─ install box2d
   └─ zig build-lib box2d Debug wasm32-freestanding 32 errors
/Users/daniel/.cache/zig/p/1220c6750412ebc698694cd08aca4fe87fda8e2df0d4ffa7d33e7de5b8ca5bebb643/include/box2d/math_functions.h:9:10: error: 'math.h' file not found
#include <math.h>
         ^~~~~~~~~
/Users/daniel/.cache/zig/p/1220c6750412ebc698694cd08aca4fe87fda8e2df0d4ffa7d33e7de5b8ca5bebb643/src/math_functions.c:4:10: note: in file included from /Users/daniel/.cache/zig/p/1220c6750412ebc698694cd08aca4fe87fda8e2df0d4ffa7d33e7de5b8ca5bebb643/src/math_functions.c:4:
#include "box2d/math_functions.h"

Any ideas how to get a wasm32 library built of box2d?

I believe Alex is working on a libc for zig that would work in this instance (see Remove `wasm32-freestanding-musl` by alexrp · Pull Request #22240 · ziglang/zig · GitHub).

I guess there use to be wasm32-freestanding-musl which would give access to the musl libc headers, but, it was removed since it was pretty broken.

You could make this work without changes to Zig if you’re up for the task. You can provide your own libc by calling setLibCFile on the box2d library. You could hook this up to your own implementation or even my ziglibc project, or just copy code from it if you like. I might be willing to pair with you on this via Discord if you like as well, I have same username on the various zig servers. (we could probably upstream this to the box2d repo as well)

Why do you use wasm32-freestanding when you are using emscripten?
Have you tried using wasm32-emscripten?

That doesn’t work either

Have you specified the --sysroot include to the emscripten sysroot?
In what way does it fail, what are the error messages?

For this game I had to manually add the sysroot include folder to the include pathes so that zig finds the header files for things like math.h when compiling for wasm32-emscripten: zig15game/build.zig at 5d0a33d753528df890f0ab5dd56cd04ff6d0e1c2 · SimonLSchlee/zig15game · GitHub

My guess would be that some similar variation could work for a library, that is linked later.

Here is the exact command I’m running so you can give it a try:

git clone https://github.com/allyourcodebase/box2d
cd box2d
cat >build.zig <<EOF 
const std = @import("std");

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

    const box2d_dep = b.dependency("box2d", .{});

    const lib = b.addStaticLibrary(.{
        .name = "box2d",
        .target = target,
        .optimize = optimize,
    });
    lib.linkLibC();
    lib.addIncludePath(box2d_dep.path("include"));
    lib.installHeadersDirectory(box2d_dep.path("include"), "", .{});
    lib.addCSourceFiles(.{
        .root = box2d_dep.path("src"),
        .flags = &.{
            "-std=c17",
        },
        .files = &.{
            "aabb.c",
            "array.c",
            "bitset.c",
            "body.c",
            "broad_phase.c",
            "constraint_graph.c",
            "contact.c",
            "contact_solver.c",
            "core.c",
            "distance.c",
            "distance_joint.c",
            "dynamic_tree.c",
            "geometry.c",
            "hull.c",
            "id_pool.c",
            "island.c",
            "joint.c",
            "manifold.c",
            "math_functions.c",
            "motor_joint.c",
            "mouse_joint.c",
            "prismatic_joint.c",
            "revolute_joint.c",
            "shape.c",
            "solver.c",
            "solver_set.c",
            "stack_allocator.c",
            "table.c",
            "timer.c",
            "types.c",
            "weld_joint.c",
            "wheel_joint.c",
            "world.c",
        },
    });
    b.installArtifact(lib);
}
EOF
zig build -Dtarget=wasm32-emscripten

and the first error I get:

/Users/daniel/.cache/zig/p/1220c6750412ebc698694cd08aca4fe87fda8e2df0d4ffa7d33e7de5b8ca5bebb643/include/box2d/math_functions.h:9:10: error: 'math.h' file not found
#include <math.h>
         ^~~~~~~~~
/Users/daniel/.cache/zig/p/1220c6750412ebc698694cd08aca4fe87fda8e2df0d4ffa7d33e7de5b8ca5bebb643/src/table.c:6:10: note: in file included from /Users/daniel/.cache/zig/p/1220c6750412ebc698694cd08aca4fe87fda8e2df0d4ffa7d33e7de5b8ca5bebb643/src/table.c:6:
#include "core.h"
         ^
/Users/daniel/.cache/zig/p/1220c6750412ebc698694cd08aca4fe87fda8e2df0d4ffa7d33e7de5b8ca5bebb643/src/core.h:6:10: note: in file included from /Users/daniel/.cache/zig/p/1220c6750412ebc698694cd08aca4fe87fda8e2df0d4ffa7d33e7de5b8ca5bebb643/src/core.h:6:
#include "box2d/math_functions.h"
         ^

I’ve hit the same regression porting my project from 0.8.1 to ‘0.14.0-dev.3217+5b9b5e45c’ whilst building LVGL for freestanding wasm.
The only hack I’ve found so far is to reference the linux C headers (in my case Asahi) directly.

wasm_lib.addIncludePath(b.path("../../../../../usr/include"));

Referencing the headers with an absolute path caused an error something about not being allowed to reference stuff above the build folder. I cant check exactly what the errors are, because today I’m getting some strange ‘failed to check cache’ error.

However I think the above hack works because in the case of LVGL the C dependencies are probably resolved via macros in the headers, ie simple std and string stuff.

I’ve encountered that problem too in my WASM Zig experiments - I guess Zig simply doesn’t have any C library support for wasm32-emscripten and wasm32-freestanding (wasm32-wasi works fine).

Since I’m depending on the Emscripten SDK anyway for linking and Emscripten-specific APIs, I basically use the Emsdk as a complete C sysroot and inject the Emscripten system include path into the C code that’s compiled via Zig, e.g.:

…and since include paths propagate through the dependency tree, other C dependencies just need to depend on the sokol C library to find the Emscripten C headers.

…oops I might have been wrong on that propagation thing… because this is how I inject the Emscripten sysroot into other C dependencies:

1 Like

The only Wasm target Zig provides a libc for is wasm32-wasi-musl (it used to claim to support wasm32-freestanding-musl but this never really worked in practice). For all other targets you have to explicitly supply the headers yourself and make sure they are added to the header search paths.

For Emscripten specifically the headers are located in the sysroot/include subpath of the Emscripten cache, which you can print the path to by running em-config CACHE. If you don’t want to hard-code the path I suggest using b.sysroot and requiring the user invoking zig build to explicitly provide a sysroot by passing --sysroot "$(em-config CACHE)/sysroot".

var emscripten_system_include_path: ?std.Build.LazyPath = null;
switch (target.result.os.tag) {
    .emscripten => {
        if (b.sysroot) |sysroot| {
            emscripten_system_include_path = .{ .cwd_relative = b.pathJoin(&.{ sysroot, "include" }) };
        } else {
            std.log.err("'--sysroot' is required when building for Emscripten", .{});
            std.process.exit(1);
        }
    },
    else => {},
}

// ...

if (emscripten_system_include_path) |path| {
    lib.addSystemIncludePath(path);
}

One caveat is that there’s unfortunately no catch-all way to add default header search paths for all artifacts (including those from dependencies), meaning that any packages you depend on will also need to explicitly handle --sysroot the same way in their build.zig files.

This should be enough to build Box2D. If you get errors like error: call to undeclared function 'clock_gettime' it’s because -std=c17 is disabling non-standards compliant features. Changing it to -std=gnu17 should fix it.

2 Likes