Merge pull request #52 from bluesillybeard/devel

Add web build support using emscripten
This commit is contained in:
Nikolas 2023-08-17 01:16:41 +02:00 committed by GitHub
commit 976fe48484
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 184 additions and 15 deletions

2
.gitignore vendored
View File

@ -1,7 +1,7 @@
zig-cache/
zig-out/
.idea/
Project/*
libraylib.a
raylib.h
raymath.h

View File

@ -62,6 +62,18 @@ To build all available examples simply `zig build examples`. To list available e
+ You can copy that folder anywhere you want and edit the source
+ Run `zig build run` at any time to test your project
## Exporting for web
To export your project for the web, first install emsdk.
Once emsdk is installed, set it up by running
`emsdk install latest`
Find the folder where it's installed and run
`zig build -Dtarget=wasm32-emscripten --sysroot [path to emsdk]/upstream/emscripten`
once that is finished, the exported project should be located at `zig-out/htmlout`
### When is the binding updated?
I plan on updating it every mayor release (2.5, 3.0, etc.). Keep in mind these are technically header files, so any implementation stuff should be updatable with some hacks on your side.

150
build.zig
View File

@ -2,6 +2,7 @@
const std = @import("std");
const rl = @This();
const builtin = @import("builtin");
const Program = struct {
name: []const u8,
@ -14,7 +15,6 @@ pub fn link(b: *std.Build, exe: *std.Build.Step.Compile, target: std.zig.CrossTa
.target = target,
.optimize = optimize,
});
var art = raylib.artifact("raylib");
const target_os = exe.target.toTarget().os.tag;
@ -43,6 +43,12 @@ pub fn link(b: *std.Build, exe: *std.Build.Step.Compile, target: std.zig.CrossTa
exe.linkSystemLibrary("Xxf86vm");
exe.linkSystemLibrary("Xcursor");
},
.emscripten, .wasi => {
//when using emscripten,
// the libries don't need to be linked
// because emscripten is going
// to do that later.
},
else => { // linux and possibly others
exe.linkSystemLibrary("GL");
exe.linkSystemLibrary("rt");
@ -91,7 +97,7 @@ pub const math = struct {
}
};
pub fn build(b: *std.Build) void {
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
@ -166,15 +172,151 @@ pub fn build(b: *std.Build) void {
var raylib_math = rl.math.getModuleInternal(b);
for (examples) |ex| {
const exe = b.addExecutable(.{ .name = ex.name, .root_source_file = .{ .path = ex.path }, .optimize = optimize, .target = target });
if (target.getOsTag() == .emscripten) {
const exe_lib = compileForEmscripten(b, ex.name, ex.path, target, optimize);
exe_lib.addModule("raylib", raylib);
exe_lib.addModule("raylib-math", raylib_math);
const raylib_artifact = getArtifact(b, target, optimize);
// Note that raylib itself isn't actually added to the exe_lib output file, so it also needs to be linked with emscripten.
exe_lib.linkLibrary(raylib_artifact);
const link_step = try linkWithEmscripten(b, &[_]*std.Build.Step.Compile{ exe_lib, raylib_artifact });
link_step.addArg("--embed-file");
link_step.addArg("resources/");
const run_step = try emscriptenRunStep(b);
run_step.step.dependOn(&link_step.step);
const run_option = b.step(ex.name, ex.desc);
run_option.dependOn(&run_step.step);
} else {
const exe = b.addExecutable(.{ .name = ex.name, .root_source_file = .{ .path = ex.path }, .optimize = optimize, .target = target });
rl.link(b, exe, target, optimize);
exe.addModule("raylib", raylib);
exe.addModule("raylib-math", raylib_math);
const run_cmd = b.addRunArtifact(exe);
const run_step = b.step(ex.name, ex.desc);
run_step.dependOn(&run_cmd.step);
examples_step.dependOn(&exe.step);
}
}
}
const emccOutputDir = "zig-out" ++ std.fs.path.sep_str ++ "htmlout" ++ std.fs.path.sep_str;
const emccOutputFile = "index.html";
pub fn emscriptenRunStep(b: *std.Build) !*std.Build.Step.Run {
//find emrun
if (b.sysroot == null) {
@panic("Pass '--sysroot \"[path to emsdk installation]/upstream/emscripten\"'");
}
//If compiling on windows , use emrun.bat
const emrunExe = switch (builtin.os.tag) {
.windows => "emrun.bat",
else => "emrun",
};
const emrun_run_arg = try b.allocator.alloc(u8, b.sysroot.?.len + emrunExe.len + 1);
defer b.allocator.free(emrun_run_arg);
_ = try std.fmt.bufPrint(emrun_run_arg, "{s}" ++ std.fs.path.sep_str ++ "{s}", .{ b.sysroot.?, emrunExe });
const run_cmd = b.addSystemCommand(&[_][]const u8{ emrun_run_arg, emccOutputDir ++ emccOutputFile });
return run_cmd;
}
//Creates the static library to build a project for Emscripten
pub fn compileForEmscripten(b: *std.Build, name: []const u8, root_source_file: []const u8, target: std.zig.CrossTarget, optimize: std.builtin.Mode) *std.Build.Step.Compile {
// TODO: It might be a good idea to create a custom compile step,
// that does both the compile to static library and the link with emcc
// By overidding the make function of the step,
// However it might also be a bad idea since it messes with the build system itself.
const new_target = updateTargetForWeb(target);
//the project is built as a library and linked later
const exe_lib = b.addStaticLibrary(.{ .name = name, .root_source_file = .{ .path = root_source_file }, .target = new_target, .optimize = optimize });
//There are some symbols that need to be defined in C.
const webhack_c_file_step = b.addWriteFiles();
const webhack_c_file = webhack_c_file_step.add("webhack.c", webhack_c);
exe_lib.addCSourceFile(.{ .file = webhack_c_file, .flags = &[_][]u8{} });
//Since it's creating a static library, the symbols raylib uses to webgl and glfw don't need to be linked by emscripten yet.
exe_lib.step.dependOn(&webhack_c_file_step.step);
return exe_lib;
}
//links a set of items together using emscripten.
// Will accept objects and static libraries as items to link
// As for files to include, it is recomended to have a single resources directory and just pass the entire directory
// instead of passing every file individually. The entire path given will be the path to read the file within the program.
// So, if "resources/image.png" is passed, your program will use "resources/image.png" as the path to load the file.
// TODO: test if shared libraries are accepted, I don't remember if emcc can link a shared library with a project or not
// TODO: add a parameter that allows a custom output directory
pub fn linkWithEmscripten(b: *std.Build, itemsToLink: []const *std.Build.Step.Compile) !*std.Build.Step.Run {
//Raylib uses --sysroot in order to find emscripten, so do the same here
if (b.sysroot == null) {
@panic("Pass '--sysroot \"[path to emsdk installation]/upstream/emscripten\"'");
}
const emccExe = switch (builtin.os.tag) {
.windows => "emcc.bat",
else => "emcc",
};
var emcc_run_arg = try b.allocator.alloc(u8, b.sysroot.?.len + emccExe.len + 1);
defer b.allocator.free(emcc_run_arg);
emcc_run_arg = try std.fmt.bufPrint(emcc_run_arg, "{s}" ++ std.fs.path.sep_str ++ "{s}", .{ b.sysroot.?, emccExe });
//create the output directory because emcc can't do it.
const mkdir_command = b.addSystemCommand(&[_][]const u8{ "mkdir", "-p", emccOutputDir });
//Actually link everything together
const emcc_command = b.addSystemCommand(&[_][]const u8{emcc_run_arg});
for (itemsToLink) |item| {
emcc_command.addFileArg(item.getEmittedBin());
emcc_command.step.dependOn(&item.step);
}
//This puts the file in zig-out/htmlout/index.html
emcc_command.step.dependOn(&mkdir_command.step);
emcc_command.addArgs(&[_][]const u8{ "-o", emccOutputDir ++ emccOutputFile, "-sFULL-ES3=1", "-sUSE_GLFW=3", "-sASYNCIFY", "-O3", "--emrun" });
return emcc_command;
}
//TODO: see if zig's standard library already has somehing like this
fn lastIndexOf(string: []const u8, character: u8) usize {
//interestingly, zig has no nice way of iterating a slice backwards
for (0..string.len) |i| {
const index = string.len - i - 1;
if (string[index] == character) return index;
}
return string.len - 1;
}
// TODO: each zig update, remove this and see if everything still works.
// https://github.com/ziglang/zig/issues/16776 is where the issue is submitted
fn updateTargetForWeb(target: std.zig.CrossTarget) std.zig.CrossTarget {
//zig building to emscripten doesn't work, because the zig standard library is missing some things in the C system.
// "std/c.zig" is missing fd_t, which causes compilation to fail.
// So build to wasi instead, until it gets fixed.
return std.zig.CrossTarget{
.cpu_arch = target.cpu_arch,
.cpu_model = target.cpu_model,
.cpu_features_add = target.cpu_features_add,
.cpu_features_sub = target.cpu_features_sub,
.os_tag = .wasi,
.os_version_min = target.os_version_min,
.os_version_max = target.os_version_max,
.glibc_version = target.glibc_version,
.abi = target.abi,
.dynamic_linker = target.dynamic_linker,
.ofmt = target.ofmt,
};
}
const webhack_c =
\\// Zig adds '__stack_chk_guard', '__stack_chk_fail', and 'errno',
\\// which emscripten doesn't actually support.
\\// Seems that zig ignores disabling stack checking,
\\// and I honestly don't know why emscripten doesn't have errno.
\\// TODO: when the updateTargetForWeb workaround gets removed, see if those are nessesary anymore
\\#include <stdint.h>
\\uintptr_t __stack_chk_guard;
\\//I'm not certain if this means buffer overflows won't be detected,
\\// However, zig is pretty safe from those, so don't worry about it too much.
\\void __stack_chk_fail(void){}
\\int errno;
;

View File

@ -12,12 +12,27 @@ echo "generating project files..."
echo 'const std = @import("std");
const rl = @import("raylib-zig/build.zig");
pub fn build(b: *std.Build) void {
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
var raylib = rl.getModule(b, "raylib-zig");
var raylib_math = rl.math.getModule(b, "raylib-zig");
//web exports are completely separate
if (target.getOsTag() == .emscripten) {
const exe_lib = rl.compileForEmscripten(b, "'$PROJECT_NAME'", "src/main.zig", target, optimize);
exe_lib.addModule("raylib", raylib);
exe_lib.addModule("raylib-math", raylib_math);
const raylib_artifact = rl.getArtifact(b, target, optimize);
// Note that raylib itself is not actually added to the exe_lib output file, so it also needs to be linked with emscripten.
exe_lib.linkLibrary(raylib_artifact);
const link_step = try rl.linkWithEmscripten(b, &[_]*std.Build.Step.Compile{ exe_lib, raylib_artifact });
b.getInstallStep().dependOn(&link_step.step);
const run_step = try rl.emscriptenRunStep(b);
run_step.step.dependOn(&link_step.step);
const run_option = b.step("run", "Run '$PROJECT_NAME'");
run_option.dependOn(&run_step.step);
return;
}
const exe = b.addExecutable(.{ .name = "'$PROJECT_NAME'", .root_source_file = .{ .path = "src/main.zig" }, .optimize = optimize, .target = target });