Skip to content

Commit 532f4ab

Browse files
author
barathrm
committed
Refactor for improved build.zig integration
Only use LazyPaths to improve caching and rebuild triggering. To achieve this, don't use addSystemCommand. Instead we have to build small zig programs to act as runArtifacts which allow us to rely on dynamic input and output paths only. Tell me if you know a better way! Install SDKs into project-specific cache directory which should allow different projects using different emsdk versions more easily on the same build host. Recursively add a build target's dependencies to the emcc command.
1 parent 9b0d735 commit 532f4ab

File tree

3 files changed

+230
-86
lines changed

3 files changed

+230
-86
lines changed

build.zig

Lines changed: 89 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,98 @@
11
const builtin = @import("builtin");
22
const std = @import("std");
33

4-
pub const emsdk_ver_major = "4";
5-
pub const emsdk_ver_minor = "0";
6-
pub const emsdk_ver_tiny = "3";
7-
pub const emsdk_version = emsdk_ver_major ++ "." ++ emsdk_ver_minor ++ "." ++ emsdk_ver_tiny;
8-
94
pub fn build(b: *std.Build) void {
105
_ = b.addModule("root", .{ .root_source_file = b.path("src/zemscripten.zig") });
6+
const passthrough = b.addExecutable(
7+
.{
8+
.name = "passthrough",
9+
.root_module = b.createModule(.{
10+
.root_source_file = b.path("src/passthrough.zig"),
11+
.target = b.graph.host,
12+
.optimize = .ReleaseFast,
13+
}),
14+
},
15+
);
16+
b.installArtifact(passthrough);
1117
}
1218

13-
pub fn emccPath(b: *std.Build) []const u8 {
14-
return std.fs.path.join(b.allocator, &.{
15-
b.dependency("emsdk", .{}).path("").getPath(b),
19+
pub fn emsdkDependency(b: *std.Build) *std.Build.Dependency {
20+
return b.dependency("emsdk", .{});
21+
}
22+
23+
pub fn emccPath(b: *std.Build, emsdk: Emsdk) std.Build.LazyPath {
24+
return emsdk.emsdk_dir.path(b, b.pathJoin(&.{
1625
"upstream",
1726
"emscripten",
1827
switch (builtin.target.os.tag) {
1928
.windows => "emcc.bat",
2029
else => "emcc",
2130
},
22-
}) catch unreachable;
31+
}));
2332
}
2433

25-
pub fn emrunPath(b: *std.Build) []const u8 {
26-
return std.fs.path.join(b.allocator, &.{
34+
pub fn emrunPath(b: *std.Build, emsdk: Emsdk) std.Build.LazyPath {
35+
return emsdk.emsdk_dir.path(b, b.pathJoin(&.{
2736
b.dependency("emsdk", .{}).path("").getPath(b),
2837
"upstream",
2938
"emscripten",
3039
switch (builtin.target.os.tag) {
3140
.windows => "emrun.bat",
3241
else => "emrun",
3342
},
34-
}) catch unreachable;
43+
}));
3544
}
3645

37-
pub fn htmlPath(b: *std.Build) []const u8 {
38-
return std.fs.path.join(b.allocator, &.{
46+
pub fn htmlPath(b: *std.Build, emsdk: Emsdk) std.Build.LazyPath {
47+
return emsdk.emsdk_dir.path(b, b.pathJoin(&.{
3948
b.dependency("emsdk", .{}).path("").getPath(b),
4049
"upstream",
4150
"emscripten",
4251
"src",
4352
"shell.html",
44-
}) catch unreachable;
53+
}));
4554
}
4655

47-
pub fn activateEmsdkStep(b: *std.Build) *std.Build.Step {
48-
const emsdk_script_path = std.fs.path.join(b.allocator, &.{
49-
b.dependency("emsdk", .{}).path("").getPath(b),
50-
switch (builtin.target.os.tag) {
51-
.windows => "emsdk.bat",
52-
else => "emsdk",
53-
},
54-
}) catch unreachable;
55-
56-
var emsdk_install = b.addSystemCommand(&.{ emsdk_script_path, "install", emsdk_version });
56+
const ActivateEmsdkOptions = struct {
57+
emsdk: *std.Build.Dependency,
58+
zemscripten: *std.Build.Dependency,
59+
sdk_version: ?[]const u8 = null,
60+
};
5761

58-
switch (builtin.target.os.tag) {
59-
.linux, .macos => {
60-
emsdk_install.step.dependOn(&b.addSystemCommand(&.{ "chmod", "+x", emsdk_script_path }).step);
61-
},
62-
.windows => {
63-
emsdk_install.step.dependOn(&b.addSystemCommand(&.{ "takeown", "/f", emsdk_script_path }).step);
64-
},
65-
else => {},
66-
}
62+
const Emsdk = struct {
63+
zemscripten: *std.Build.Dependency,
64+
step: *std.Build.Step,
65+
emsdk_dir: std.Build.LazyPath,
66+
};
6767

68-
var emsdk_activate = b.addSystemCommand(&.{ emsdk_script_path, "activate", emsdk_version });
69-
emsdk_activate.step.dependOn(&emsdk_install.step);
70-
71-
const step = b.allocator.create(std.Build.Step) catch unreachable;
72-
step.* = std.Build.Step.init(.{
73-
.id = .custom,
74-
.name = "Activate EMSDK",
75-
.owner = b,
76-
.makeFn = &struct {
77-
fn make(_: *std.Build.Step, _: std.Build.Step.MakeOptions) anyerror!void {}
78-
}.make,
68+
pub fn activateEmsdkStep(
69+
b: *std.Build,
70+
options: ActivateEmsdkOptions,
71+
) Emsdk {
72+
const version = options.sdk_version orelse "4.0.19";
73+
74+
const emsdk_installer = b.addExecutable(.{
75+
.name = "invoker",
76+
.root_module = b.createModule(.{
77+
.target = b.graph.host,
78+
.optimize = .ReleaseSafe,
79+
.root_source_file = options.zemscripten.path("src/emsdk_installer.zig"),
80+
}),
7981
});
8082

81-
switch (builtin.target.os.tag) {
82-
.linux, .macos => {
83-
const chmod_emcc = b.addSystemCommand(&.{ "chmod", "+x", emccPath(b) });
84-
chmod_emcc.step.dependOn(&emsdk_activate.step);
85-
step.dependOn(&chmod_emcc.step);
86-
87-
const chmod_emrun = b.addSystemCommand(&.{ "chmod", "+x", emrunPath(b) });
88-
chmod_emrun.step.dependOn(&emsdk_activate.step);
89-
step.dependOn(&chmod_emrun.step);
90-
},
91-
.windows => {
92-
const takeown_emcc = b.addSystemCommand(&.{ "takeown", "/f", emccPath(b) });
93-
takeown_emcc.step.dependOn(&emsdk_activate.step);
94-
step.dependOn(&takeown_emcc.step);
95-
96-
const takeown_emrun = b.addSystemCommand(&.{ "takeown", "/f", emrunPath(b) });
97-
takeown_emrun.step.dependOn(&emsdk_activate.step);
98-
step.dependOn(&takeown_emrun.step);
99-
},
100-
else => {},
101-
}
83+
const emsdk_script_path = options.emsdk.path("").join(b.allocator, switch (builtin.target.os.tag) {
84+
.windows => "emsdk.bat",
85+
else => "emsdk",
86+
}) catch unreachable;
10287

103-
return step;
88+
const activate_and_install_step = b.addRunArtifact(emsdk_installer);
89+
activate_and_install_step.addFileArg(emsdk_script_path);
90+
activate_and_install_step.addArg(version);
91+
return .{
92+
.step = &activate_and_install_step.step,
93+
.emsdk_dir = activate_and_install_step.addOutputDirectoryArg("emsdk"),
94+
.zemscripten = options.zemscripten,
95+
};
10496
}
10597

10698
pub const EmccFlags = std.StringHashMap(void);
@@ -163,7 +155,6 @@ pub fn emccDefaultSettings(allocator: std.mem.Allocator, options: EmccDefaultSet
163155
},
164156
else => {},
165157
}
166-
settings.put("USE_OFFSET_CONVERTER", "1") catch unreachable;
167158
settings.put("MALLOC", @tagName(options.emsdk_allocator)) catch unreachable;
168159
return settings;
169160
}
@@ -186,12 +177,36 @@ pub const StepOptions = struct {
186177
install_dir: std.Build.InstallDir,
187178
};
188179

180+
fn addAllLinkedArtifacts(b: *std.Build, emcc: *std.Build.Step.Run, root_module: *std.Build.Module) void {
181+
for (root_module.getGraph().modules) |module| {
182+
for (module.link_objects.items) |link_object| {
183+
switch (link_object) {
184+
.other_step => |compile_step| {
185+
switch (compile_step.kind) {
186+
.lib => {
187+
emcc.addArtifactArg(compile_step);
188+
addAllLinkedArtifacts(b, emcc, compile_step.root_module);
189+
},
190+
else => {},
191+
}
192+
},
193+
.static_path => |static_path| {
194+
emcc.addFileArg(static_path);
195+
},
196+
else => {},
197+
}
198+
}
199+
}
200+
}
201+
189202
pub fn emccStep(
190203
b: *std.Build,
204+
emsdk: Emsdk,
191205
wasm: *std.Build.Step.Compile,
192206
options: StepOptions,
193207
) *std.Build.Step {
194-
var emcc = b.addSystemCommand(&.{emccPath(b)});
208+
var emcc = b.addRunArtifact(emsdk.zemscripten.artifact("passthrough"));
209+
emcc.addFileArg(emccPath(b, emsdk));
195210

196211
var iterFlags = options.flags.iterator();
197212
while (iterFlags.next()) |kvp| {
@@ -209,21 +224,7 @@ pub fn emccStep(
209224

210225
emcc.addArtifactArg(wasm);
211226
{
212-
for (wasm.root_module.getGraph().modules) |module| {
213-
for (module.link_objects.items) |link_object| {
214-
switch (link_object) {
215-
.other_step => |compile_step| {
216-
switch (compile_step.kind) {
217-
.lib => {
218-
emcc.addArtifactArg(compile_step);
219-
},
220-
else => {},
221-
}
222-
},
223-
else => {},
224-
}
225-
}
226-
}
227+
addAllLinkedArtifacts(b, emcc, wasm.root_module);
227228
}
228229

229230
emcc.addArg("-o");
@@ -287,13 +288,15 @@ pub fn emccStep(
287288

288289
pub fn emrunStep(
289290
b: *std.Build,
291+
emsdk: Emsdk,
290292
html_path: []const u8,
291293
extra_args: []const []const u8,
292294
) *std.Build.Step {
293-
var emrun = b.addSystemCommand(&.{emrunPath(b)});
295+
var emrun = b.addRunArtifact(emsdk.zemscripten.artifact("passthrough"));
296+
emrun.addFileArg(emrunPath(b, emsdk));
297+
294298
emrun.addArgs(extra_args);
295299
emrun.addArg(html_path);
296-
// emrun.addArg("--");
297300

298301
return &emrun.step;
299302
}

src/emsdk_installer.zig

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
const std = @import("std");
2+
const builtin = @import("builtin");
3+
4+
fn chmod(allocator: std.mem.Allocator, path: []const u8) !void {
5+
const argv: []const []const u8 = switch (builtin.target.os.tag) {
6+
.linux, .macos => &.{ "chmod", "+x", path },
7+
.windows => &.{ "takeown", "/f", path },
8+
else => return,
9+
};
10+
11+
var child = std.process.Child.init(
12+
argv,
13+
allocator,
14+
);
15+
if (try child.spawnAndWait() != .Exited)
16+
return error.FailedChmod;
17+
}
18+
19+
fn do_action(
20+
allocator: std.mem.Allocator,
21+
emsdk_path: []const u8,
22+
action: []const u8,
23+
version: []const u8,
24+
) !void {
25+
var child = std.process.Child.init(
26+
&.{
27+
emsdk_path,
28+
action,
29+
version,
30+
},
31+
allocator,
32+
);
33+
child.stdout_behavior = .Inherit;
34+
child.stderr_behavior = .Inherit;
35+
36+
try child.spawn();
37+
38+
return switch (try child.wait()) {
39+
.Exited => {},
40+
else => |term| {
41+
std.log.err(
42+
"running emsdk installer failed for action {s}: {}",
43+
.{ action, term },
44+
);
45+
return error.InstallerFailed;
46+
},
47+
};
48+
}
49+
50+
pub fn main() !void {
51+
var allocator_strategy = std.heap.ArenaAllocator.init(std.heap.page_allocator);
52+
const allocator = allocator_strategy.allocator();
53+
defer _ = allocator_strategy.deinit();
54+
55+
const argv = try std.process.argsAlloc(allocator);
56+
defer std.process.argsFree(allocator, argv);
57+
58+
if (argv.len != 4) {
59+
std.log.err(
60+
"usage: {s} <emsdk script path> <emsdk version> <sdk install path>",
61+
.{argv[0]},
62+
);
63+
return error.InvalidArguments;
64+
}
65+
66+
const emsdk_script_path_src = argv[1];
67+
const version = argv[2];
68+
const install_path = argv[3];
69+
70+
const emsdk_dir_src = std.fs.path.dirname(emsdk_script_path_src) orelse unreachable;
71+
72+
// Create copy of SDK installer in a dedicate output dir
73+
// FIXME Replace with zig implementation of cp -r
74+
{
75+
const emsdk_dir_contents_src = try std.fmt.allocPrint(allocator, "{s}/.", .{emsdk_dir_src});
76+
var child = std.process.Child.init(
77+
&.{
78+
"cp",
79+
"-r",
80+
emsdk_dir_contents_src,
81+
install_path,
82+
},
83+
allocator,
84+
);
85+
switch (try child.spawnAndWait()) {
86+
.Exited => {},
87+
else => |term| {
88+
std.log.err(
89+
"chmod failed with {} ",
90+
.{term},
91+
);
92+
return error.InstallerFailed;
93+
},
94+
}
95+
}
96+
97+
const emsdk_script_path_dst = try std.fmt.allocPrint(
98+
allocator,
99+
"{s}/emsdk",
100+
.{install_path},
101+
);
102+
103+
try chmod(allocator, emsdk_script_path_dst);
104+
try do_action(allocator, emsdk_script_path_dst, "install", version);
105+
try do_action(allocator, emsdk_script_path_dst, "activate", version);
106+
}

src/passthrough.zig

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const std = @import("std");
2+
3+
pub fn main() !void {
4+
var allocator_strategy = std.heap.ArenaAllocator.init(std.heap.page_allocator);
5+
const allocator = allocator_strategy.allocator();
6+
defer _ = allocator_strategy.deinit();
7+
8+
const argv = try std.process.argsAlloc(allocator);
9+
defer std.process.argsFree(allocator, argv);
10+
11+
if (argv.len < 2) {
12+
std.log.err("need at least one argument", .{});
13+
return error.InvalidArgument;
14+
}
15+
16+
var child = std.process.Child.init(
17+
argv[1..],
18+
allocator,
19+
);
20+
21+
child.stderr_behavior = .Inherit;
22+
child.stdout_behavior = .Inherit;
23+
child.stdin_behavior = .Inherit;
24+
25+
switch (try child.spawnAndWait()) {
26+
.Exited => {},
27+
else => |term| {
28+
std.log.err(
29+
"{s} failed with {} ",
30+
.{ argv[1], term },
31+
);
32+
return error.InstallerFailed;
33+
},
34+
}
35+
}

0 commit comments

Comments
 (0)