Skip to content

Commit eac61e8

Browse files
committed
files from monorepo @ 613d847104a072fc1d4442f58d5f50b3f9c3f27c
0 parents  commit eac61e8

File tree

6 files changed

+422
-0
lines changed

6 files changed

+422
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Ignore some special directories
2+
*zig-cache
3+
*zig-out
4+
5+
# Ignore some special OS files
6+
*.DS_Store

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 zig-gamedev contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# zemscripten
2+
3+
Zig build package and shims for [Emscripten](https://emscripten.org) emsdk
4+
5+
## How to use it
6+
7+
Add `zemscripten` and (optionally) `emsdk` to your build.zig.zon dependencies
8+
```sh
9+
zig fetch --save https://github.com/emscripten-core/emsdk/archive/refs/tags/3.1.52.tar.gz
10+
```
11+
12+
Emsdk must be activated before it can be used. You can use `activateEmsdkStep` to create a build step for that:
13+
```zig
14+
const activate_emsdk_step = @import("zemscripten").activateEmsdkStep(b);
15+
```
16+
17+
Add zemscripten's "root" module to your wasm compile target., then create an `emcc` build step. We use zemscripten's default flags and settings which can be overridden for your project specific requirements. Refer to the [emcc documentation](https://emscripten.org/docs/tools_reference/emcc.html). Example build.zig code:
18+
```zig
19+
const wasm = b.addStaticLibrary(.{
20+
.name = "MyGame",
21+
.root_source_file = b.path("src/main.zig"),
22+
.target = target,
23+
.optimize = optimize,
24+
});
25+
26+
const zemscripten = b.dependency("zemscripten", .{});
27+
wasm.root_module.addImport("zemscripten", zemscripten.module("root"));
28+
29+
const emcc_flags = @import("zemscripten").emccDefaultFlags(b.allocator, optimize);
30+
31+
var emcc_settings = @import("zemscripten").emccDefaultSettings(b.allocator, .{
32+
.optimize = optimize,
33+
});
34+
35+
try emcc_settings.put("ALLOW_MEMORY_GROWTH", "1");
36+
37+
const emcc_step = @import("zemscripten").emccStep(
38+
b,
39+
wasm,
40+
.{
41+
.optimize = optimize,
42+
.flags = emcc_flags,
43+
.settings = emcc_settings,
44+
.use_preload_plugins = true,
45+
.embed_paths = &.{},
46+
.preload_paths = &.{},
47+
.install_dir = .{ .custom = "web" },
48+
},
49+
);
50+
emcc_step.dependOn(activate_emsdk_step);
51+
52+
b.getInstallStep().dependOn(emcc_step);
53+
```
54+
55+
Now you can use the provided Zig panic and log overrides in your wasm's root module and define the entry point that invoked by the js output of `emcc` (by default it looks for a symbol named `main`). For example:
56+
```zig
57+
const std = @import("std");
58+
59+
const zemscripten = @import("zemscripten");
60+
pub const panic = zemscripten.panic;
61+
62+
pub const std_options = std.Options{
63+
.logFn = zemscripten.log,
64+
};
65+
66+
export fn main() c_int {
67+
std.log.info("hello, world.", .{});
68+
return 0;
69+
}
70+
```
71+
72+
You can also define a run step that invokes `emrun`. This will serve the html locally over HTTP and try to open it using your default browser. Example build.zig code:
73+
```zig
74+
const html_filename = try std.fmt.allocPrint(b.allocator, "{s}.html", .{wasm.name});
75+
76+
const emrun_args = .{};
77+
const emrun_step = @import("zemscripten").emrunStep(
78+
b,
79+
b.getInstallPath(.{ .custom = "web" }, html_filename),
80+
&emrun_args,
81+
);
82+
83+
emrun_step.dependOn(emcc_step);
84+
85+
b.step("emrun", "Build and open the web app locally using emrun").dependOn(emrun_step);
86+
```
87+
See the [emrun documentation](https://emscripten.org/docs/compiling/Running-html-files-with-emrun.html) for the difference args that can be used.

build.zig

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
const builtin = @import("builtin");
2+
const std = @import("std");
3+
4+
pub const emsdk_ver_major = "3";
5+
pub const emsdk_ver_minor = "1";
6+
pub const emsdk_ver_tiny = "52";
7+
pub const emsdk_version = emsdk_ver_major ++ "." ++ emsdk_ver_minor ++ "." ++ emsdk_ver_tiny;
8+
9+
pub fn build(b: *std.Build) void {
10+
_ = b.addModule("root", .{ .root_source_file = b.path("src/zemscripten.zig") });
11+
}
12+
13+
pub fn emccPath(b: *std.Build) []const u8 {
14+
return std.fs.path.join(b.allocator, &.{
15+
b.dependency("emsdk", .{}).path("").getPath(b),
16+
"upstream/emscripten/",
17+
switch (builtin.target.os.tag) {
18+
.windows => "emcc.bat",
19+
else => "emcc",
20+
},
21+
}) catch unreachable;
22+
}
23+
24+
pub fn emrunPath(b: *std.Build) []const u8 {
25+
return std.fs.path.join(b.allocator, &.{
26+
b.dependency("emsdk", .{}).path("").getPath(b),
27+
"upstream/emscripten/",
28+
switch (builtin.target.os.tag) {
29+
.windows => "emrun.bat",
30+
else => "emrun",
31+
},
32+
}) catch unreachable;
33+
}
34+
35+
pub fn activateEmsdkStep(b: *std.Build) *std.Build.Step {
36+
const emsdk_script_path = std.fs.path.join(b.allocator, &.{
37+
b.dependency("emsdk", .{}).path("").getPath(b),
38+
switch (builtin.target.os.tag) {
39+
.windows => "emsdk.bat",
40+
else => "emsdk",
41+
},
42+
}) catch unreachable;
43+
44+
var emsdk_install = b.addSystemCommand(&.{ emsdk_script_path, "install", emsdk_version });
45+
46+
switch (builtin.target.os.tag) {
47+
.linux, .macos => {
48+
emsdk_install.step.dependOn(&b.addSystemCommand(&.{ "chmod", "+x", emsdk_script_path }).step);
49+
},
50+
else => {},
51+
}
52+
53+
var emsdk_activate = b.addSystemCommand(&.{ emsdk_script_path, "activate", emsdk_version });
54+
emsdk_activate.step.dependOn(&emsdk_install.step);
55+
56+
const chmod_emcc = b.addSystemCommand(&.{ "chmod", "+x", emccPath(b) });
57+
chmod_emcc.step.dependOn(&emsdk_activate.step);
58+
59+
const chmod_emrun = b.addSystemCommand(&.{ "chmod", "+x", emrunPath(b) });
60+
chmod_emrun.step.dependOn(&emsdk_activate.step);
61+
62+
const step = b.allocator.create(std.Build.Step) catch unreachable;
63+
step.* = std.Build.Step.init(.{
64+
.id = .custom,
65+
.name = "Activate EMSDK",
66+
.owner = b,
67+
.makeFn = &struct {
68+
fn make(_: *std.Build.Step, _: std.Build.Step.MakeOptions) anyerror!void {}
69+
}.make,
70+
});
71+
step.dependOn(&chmod_emcc.step);
72+
step.dependOn(&chmod_emrun.step);
73+
return step;
74+
}
75+
76+
pub const EmccFlags = std.StringHashMap(void);
77+
78+
pub fn emccDefaultFlags(allocator: std.mem.Allocator, optimize: std.builtin.OptimizeMode) EmccFlags {
79+
var args = EmccFlags.init(allocator);
80+
if (optimize == .Debug) {
81+
args.put("-Og", {}) catch unreachable;
82+
args.put("-gsource-map", {}) catch unreachable;
83+
}
84+
return args;
85+
}
86+
87+
pub const EmccSettings = std.StringHashMap([]const u8);
88+
89+
pub fn emccDefaultSettings(
90+
allocator: std.mem.Allocator,
91+
options: struct {
92+
optimize: std.builtin.OptimizeMode,
93+
emsdk_allocator: enum {
94+
none,
95+
dlmalloc,
96+
emmalloc,
97+
@"emmalloc-debug",
98+
@"emmalloc-memvalidate",
99+
@"emmalloc-verbose",
100+
mimalloc,
101+
} = .emmalloc,
102+
shell_file: ?[]const u8 = null,
103+
},
104+
) EmccSettings {
105+
var settings = EmccSettings.init(allocator);
106+
switch (options.optimize) {
107+
.Debug, .ReleaseSafe => {
108+
settings.put("SAFE_HEAP", "1") catch unreachable;
109+
settings.put("STACK_OVERFLOW_CHECK", "1") catch unreachable;
110+
settings.put("ASSERTIONS", "1") catch unreachable;
111+
},
112+
else => {},
113+
}
114+
settings.put("USE_OFFSET_CONVERTER", "1") catch unreachable;
115+
settings.put("MALLOC", @tagName(options.emsdk_allocator)) catch unreachable;
116+
return settings;
117+
}
118+
119+
pub const EmccFilePath = struct {
120+
src_path: []const u8,
121+
virtual_path: ?[]const u8 = null,
122+
};
123+
124+
pub fn emccStep(
125+
b: *std.Build,
126+
wasm: *std.Build.Step.Compile,
127+
options: struct {
128+
optimize: std.builtin.OptimizeMode,
129+
flags: EmccFlags,
130+
settings: EmccSettings,
131+
use_preload_plugins: bool = false,
132+
embed_paths: ?[]const EmccFilePath = null,
133+
preload_paths: ?[]const EmccFilePath = null,
134+
shell_file_path: ?[]const u8 = null,
135+
install_dir: std.Build.InstallDir,
136+
},
137+
) *std.Build.Step {
138+
var emcc = b.addSystemCommand(&.{emccPath(b)});
139+
140+
var iterFlags = options.flags.iterator();
141+
while (iterFlags.next()) |kvp| {
142+
emcc.addArg(kvp.key_ptr.*);
143+
}
144+
145+
var iterSettings = options.settings.iterator();
146+
while (iterSettings.next()) |kvp| {
147+
emcc.addArg(std.fmt.allocPrint(
148+
b.allocator,
149+
"-s{s}={s}",
150+
.{ kvp.key_ptr.*, kvp.value_ptr.* },
151+
) catch unreachable);
152+
}
153+
154+
emcc.addArtifactArg(wasm);
155+
{
156+
var it = wasm.root_module.iterateDependencies(wasm, false);
157+
while (it.next()) |item| {
158+
for (item.module.link_objects.items) |link_object| {
159+
switch (link_object) {
160+
.other_step => |compile_step| {
161+
switch (compile_step.kind) {
162+
.lib => {
163+
emcc.addArtifactArg(compile_step);
164+
},
165+
else => {},
166+
}
167+
},
168+
else => {},
169+
}
170+
}
171+
}
172+
}
173+
174+
emcc.addArg("-o");
175+
const out_file = emcc.addOutputFileArg(b.fmt("{s}.html", .{wasm.name}));
176+
177+
if (options.use_preload_plugins) {
178+
emcc.addArg("--use-preload-plugins");
179+
}
180+
181+
if (options.embed_paths) |embed_paths| {
182+
for (embed_paths) |path| {
183+
const path_arg = if (path.virtual_path) |virtual_path|
184+
std.fmt.allocPrint(
185+
b.allocator,
186+
"{s}@{s}",
187+
.{ path.src_path, virtual_path },
188+
) catch unreachable
189+
else
190+
path.src_path;
191+
emcc.addArgs(&.{ "--embed-file", path_arg });
192+
}
193+
}
194+
195+
if (options.preload_paths) |preload_paths| {
196+
for (preload_paths) |path| {
197+
const path_arg = if (path.virtual_path) |virtual_path|
198+
std.fmt.allocPrint(
199+
b.allocator,
200+
"{s}@{s}",
201+
.{ path.src_path, virtual_path },
202+
) catch unreachable
203+
else
204+
path.src_path;
205+
emcc.addArgs(&.{ "--preload-file", path_arg });
206+
}
207+
}
208+
209+
if (options.shell_file_path) |shell_file_path| {
210+
emcc.addArgs(&.{ "--shell-file", shell_file_path });
211+
}
212+
213+
const install_step = b.addInstallDirectory(.{
214+
.source_dir = out_file.dirname(),
215+
.install_dir = options.install_dir,
216+
.install_subdir = "",
217+
});
218+
install_step.step.dependOn(&emcc.step);
219+
220+
return &install_step.step;
221+
}
222+
223+
pub fn emrunStep(
224+
b: *std.Build,
225+
html_path: []const u8,
226+
extra_args: []const []const u8,
227+
) *std.Build.Step {
228+
var emrun = b.addSystemCommand(&.{emrunPath(b)});
229+
emrun.addArgs(extra_args);
230+
emrun.addArg(html_path);
231+
// emrun.addArg("--");
232+
233+
return &emrun.step;
234+
}

build.zig.zon

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.{
2+
.name = "zemscripten",
3+
.version = "0.2.0-dev",
4+
.paths = .{
5+
"build.zig",
6+
"build.zig.zon",
7+
"src",
8+
"LICENSE",
9+
"README.md",
10+
},
11+
}

0 commit comments

Comments
 (0)