Skip to content

Commit 77b3cd6

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 77b3cd6

File tree

3 files changed

+226
-86
lines changed

3 files changed

+226
-86
lines changed

build.zig

Lines changed: 85 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,94 @@
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 emccPath(b: *std.Build, emsdk: Emsdk) std.Build.LazyPath {
20+
return emsdk.emsdk_dir.path(b, b.pathJoin(&.{
1621
"upstream",
1722
"emscripten",
1823
switch (builtin.target.os.tag) {
1924
.windows => "emcc.bat",
2025
else => "emcc",
2126
},
22-
}) catch unreachable;
27+
}));
2328
}
2429

25-
pub fn emrunPath(b: *std.Build) []const u8 {
26-
return std.fs.path.join(b.allocator, &.{
30+
pub fn emrunPath(b: *std.Build, emsdk: Emsdk) std.Build.LazyPath {
31+
return emsdk.emsdk_dir.path(b, b.pathJoin(&.{
2732
b.dependency("emsdk", .{}).path("").getPath(b),
2833
"upstream",
2934
"emscripten",
3035
switch (builtin.target.os.tag) {
3136
.windows => "emrun.bat",
3237
else => "emrun",
3338
},
34-
}) catch unreachable;
39+
}));
3540
}
3641

37-
pub fn htmlPath(b: *std.Build) []const u8 {
38-
return std.fs.path.join(b.allocator, &.{
42+
pub fn htmlPath(b: *std.Build, emsdk: Emsdk) std.Build.LazyPath {
43+
return emsdk.emsdk_dir.path(b, b.pathJoin(&.{
3944
b.dependency("emsdk", .{}).path("").getPath(b),
4045
"upstream",
4146
"emscripten",
4247
"src",
4348
"shell.html",
44-
}) catch unreachable;
49+
}));
4550
}
4651

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 });
52+
const ActivateEmsdkOptions = struct {
53+
emsdk: *std.Build.Dependency,
54+
zemscripten: *std.Build.Dependency,
55+
sdk_version: ?[]const u8 = null,
56+
};
5757

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-
}
58+
const Emsdk = struct {
59+
zemscripten: *std.Build.Dependency,
60+
step: *std.Build.Step,
61+
emsdk_dir: std.Build.LazyPath,
62+
};
6763

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,
64+
pub fn activateEmsdkStep(
65+
b: *std.Build,
66+
options: ActivateEmsdkOptions,
67+
) Emsdk {
68+
const version = options.sdk_version orelse "4.0.19";
69+
70+
const emsdk_installer = b.addExecutable(.{
71+
.name = "invoker",
72+
.root_module = b.createModule(.{
73+
.target = b.graph.host,
74+
.optimize = .ReleaseSafe,
75+
.root_source_file = options.zemscripten.path("src/emsdk_installer.zig"),
76+
}),
7977
});
8078

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-
}
79+
const emsdk_script_path = options.emsdk.path("").join(b.allocator, switch (builtin.target.os.tag) {
80+
.windows => "emsdk.bat",
81+
else => "emsdk",
82+
}) catch unreachable;
10283

103-
return step;
84+
const activate_and_install_step = b.addRunArtifact(emsdk_installer);
85+
activate_and_install_step.addFileArg(emsdk_script_path);
86+
activate_and_install_step.addArg(version);
87+
return .{
88+
.step = &activate_and_install_step.step,
89+
.emsdk_dir = activate_and_install_step.addOutputDirectoryArg("emsdk"),
90+
.zemscripten = options.zemscripten,
91+
};
10492
}
10593

10694
pub const EmccFlags = std.StringHashMap(void);
@@ -163,7 +151,6 @@ pub fn emccDefaultSettings(allocator: std.mem.Allocator, options: EmccDefaultSet
163151
},
164152
else => {},
165153
}
166-
settings.put("USE_OFFSET_CONVERTER", "1") catch unreachable;
167154
settings.put("MALLOC", @tagName(options.emsdk_allocator)) catch unreachable;
168155
return settings;
169156
}
@@ -186,12 +173,36 @@ pub const StepOptions = struct {
186173
install_dir: std.Build.InstallDir,
187174
};
188175

176+
fn addAllLinkedArtifacts(b: *std.Build, emcc: *std.Build.Step.Run, root_module: *std.Build.Module) void {
177+
for (root_module.getGraph().modules) |module| {
178+
for (module.link_objects.items) |link_object| {
179+
switch (link_object) {
180+
.other_step => |compile_step| {
181+
switch (compile_step.kind) {
182+
.lib => {
183+
emcc.addArtifactArg(compile_step);
184+
addAllLinkedArtifacts(b, emcc, compile_step.root_module);
185+
},
186+
else => {},
187+
}
188+
},
189+
.static_path => |static_path| {
190+
emcc.addFileArg(static_path);
191+
},
192+
else => {},
193+
}
194+
}
195+
}
196+
}
197+
189198
pub fn emccStep(
190199
b: *std.Build,
200+
emsdk: Emsdk,
191201
wasm: *std.Build.Step.Compile,
192202
options: StepOptions,
193203
) *std.Build.Step {
194-
var emcc = b.addSystemCommand(&.{emccPath(b)});
204+
var emcc = b.addRunArtifact(emsdk.zemscripten.artifact("passthrough"));
205+
emcc.addFileArg(emccPath(b, emsdk));
195206

196207
var iterFlags = options.flags.iterator();
197208
while (iterFlags.next()) |kvp| {
@@ -209,21 +220,7 @@ pub fn emccStep(
209220

210221
emcc.addArtifactArg(wasm);
211222
{
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-
}
223+
addAllLinkedArtifacts(b, emcc, wasm.root_module);
227224
}
228225

229226
emcc.addArg("-o");
@@ -287,13 +284,15 @@ pub fn emccStep(
287284

288285
pub fn emrunStep(
289286
b: *std.Build,
287+
emsdk: Emsdk,
290288
html_path: []const u8,
291289
extra_args: []const []const u8,
292290
) *std.Build.Step {
293-
var emrun = b.addSystemCommand(&.{emrunPath(b)});
291+
var emrun = b.addRunArtifact(emsdk.zemscripten.artifact("passthrough"));
292+
emrun.addFileArg(emrunPath(b, emsdk));
293+
294294
emrun.addArgs(extra_args);
295295
emrun.addArg(html_path);
296-
// emrun.addArg("--");
297296

298297
return &emrun.step;
299298
}

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)