diff --git a/build.zig b/build.zig index eb20733..b7fe173 100644 --- a/build.zig +++ b/build.zig @@ -1,29 +1,34 @@ const builtin = @import("builtin"); const std = @import("std"); -pub const emsdk_ver_major = "4"; -pub const emsdk_ver_minor = "0"; -pub const emsdk_ver_tiny = "3"; -pub const emsdk_version = emsdk_ver_major ++ "." ++ emsdk_ver_minor ++ "." ++ emsdk_ver_tiny; - pub fn build(b: *std.Build) void { _ = b.addModule("root", .{ .root_source_file = b.path("src/zemscripten.zig") }); + const passthrough = b.addExecutable( + .{ + .name = "passthrough", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/passthrough.zig"), + .target = b.graph.host, + .optimize = .ReleaseFast, + }), + }, + ); + b.installArtifact(passthrough); } -pub fn emccPath(b: *std.Build) []const u8 { - return std.fs.path.join(b.allocator, &.{ - b.dependency("emsdk", .{}).path("").getPath(b), +pub fn emccPath(b: *std.Build, emsdk: Emsdk) std.Build.LazyPath { + return emsdk.emsdk_dir.path(b, b.pathJoin(&.{ "upstream", "emscripten", switch (builtin.target.os.tag) { .windows => "emcc.bat", else => "emcc", }, - }) catch unreachable; + })); } -pub fn emrunPath(b: *std.Build) []const u8 { - return std.fs.path.join(b.allocator, &.{ +pub fn emrunPath(b: *std.Build, emsdk: Emsdk) std.Build.LazyPath { + return emsdk.emsdk_dir.path(b, b.pathJoin(&.{ b.dependency("emsdk", .{}).path("").getPath(b), "upstream", "emscripten", @@ -31,76 +36,59 @@ pub fn emrunPath(b: *std.Build) []const u8 { .windows => "emrun.bat", else => "emrun", }, - }) catch unreachable; + })); } -pub fn htmlPath(b: *std.Build) []const u8 { - return std.fs.path.join(b.allocator, &.{ +pub fn htmlPath(b: *std.Build, emsdk: Emsdk) std.Build.LazyPath { + return emsdk.emsdk_dir.path(b, b.pathJoin(&.{ b.dependency("emsdk", .{}).path("").getPath(b), "upstream", "emscripten", "src", "shell.html", - }) catch unreachable; + })); } -pub fn activateEmsdkStep(b: *std.Build) *std.Build.Step { - const emsdk_script_path = std.fs.path.join(b.allocator, &.{ - b.dependency("emsdk", .{}).path("").getPath(b), - switch (builtin.target.os.tag) { - .windows => "emsdk.bat", - else => "emsdk", - }, - }) catch unreachable; - - var emsdk_install = b.addSystemCommand(&.{ emsdk_script_path, "install", emsdk_version }); +const ActivateEmsdkOptions = struct { + emsdk: *std.Build.Dependency, + zemscripten: *std.Build.Dependency, + sdk_version: ?[]const u8 = null, +}; - switch (builtin.target.os.tag) { - .linux, .macos => { - emsdk_install.step.dependOn(&b.addSystemCommand(&.{ "chmod", "+x", emsdk_script_path }).step); - }, - .windows => { - emsdk_install.step.dependOn(&b.addSystemCommand(&.{ "takeown", "/f", emsdk_script_path }).step); - }, - else => {}, - } +const Emsdk = struct { + zemscripten: *std.Build.Dependency, + step: *std.Build.Step, + emsdk_dir: std.Build.LazyPath, +}; - var emsdk_activate = b.addSystemCommand(&.{ emsdk_script_path, "activate", emsdk_version }); - emsdk_activate.step.dependOn(&emsdk_install.step); - - const step = b.allocator.create(std.Build.Step) catch unreachable; - step.* = std.Build.Step.init(.{ - .id = .custom, - .name = "Activate EMSDK", - .owner = b, - .makeFn = &struct { - fn make(_: *std.Build.Step, _: std.Build.Step.MakeOptions) anyerror!void {} - }.make, +pub fn activateEmsdkStep( + b: *std.Build, + options: ActivateEmsdkOptions, +) Emsdk { + const version = options.sdk_version orelse "4.0.19"; + + const emsdk_installer = b.addExecutable(.{ + .name = "invoker", + .root_module = b.createModule(.{ + .target = b.graph.host, + .optimize = .ReleaseSafe, + .root_source_file = options.zemscripten.path("src/emsdk_installer.zig"), + }), }); - switch (builtin.target.os.tag) { - .linux, .macos => { - const chmod_emcc = b.addSystemCommand(&.{ "chmod", "+x", emccPath(b) }); - chmod_emcc.step.dependOn(&emsdk_activate.step); - step.dependOn(&chmod_emcc.step); - - const chmod_emrun = b.addSystemCommand(&.{ "chmod", "+x", emrunPath(b) }); - chmod_emrun.step.dependOn(&emsdk_activate.step); - step.dependOn(&chmod_emrun.step); - }, - .windows => { - const takeown_emcc = b.addSystemCommand(&.{ "takeown", "/f", emccPath(b) }); - takeown_emcc.step.dependOn(&emsdk_activate.step); - step.dependOn(&takeown_emcc.step); - - const takeown_emrun = b.addSystemCommand(&.{ "takeown", "/f", emrunPath(b) }); - takeown_emrun.step.dependOn(&emsdk_activate.step); - step.dependOn(&takeown_emrun.step); - }, - else => {}, - } + const emsdk_script_path = options.emsdk.path("").join(b.allocator, switch (builtin.target.os.tag) { + .windows => "emsdk.bat", + else => "emsdk", + }) catch unreachable; - return step; + const activate_and_install_step = b.addRunArtifact(emsdk_installer); + activate_and_install_step.addFileArg(emsdk_script_path); + activate_and_install_step.addArg(version); + return .{ + .step = &activate_and_install_step.step, + .emsdk_dir = activate_and_install_step.addOutputDirectoryArg("emsdk"), + .zemscripten = options.zemscripten, + }; } pub const EmccFlags = std.StringHashMap(void); @@ -163,7 +151,6 @@ pub fn emccDefaultSettings(allocator: std.mem.Allocator, options: EmccDefaultSet }, else => {}, } - settings.put("USE_OFFSET_CONVERTER", "1") catch unreachable; settings.put("MALLOC", @tagName(options.emsdk_allocator)) catch unreachable; return settings; } @@ -186,12 +173,36 @@ pub const StepOptions = struct { install_dir: std.Build.InstallDir, }; +fn addAllLinkedArtifacts(b: *std.Build, emcc: *std.Build.Step.Run, root_module: *std.Build.Module) void { + for (root_module.getGraph().modules) |module| { + for (module.link_objects.items) |link_object| { + switch (link_object) { + .other_step => |compile_step| { + switch (compile_step.kind) { + .lib => { + emcc.addArtifactArg(compile_step); + addAllLinkedArtifacts(b, emcc, compile_step.root_module); + }, + else => {}, + } + }, + .static_path => |static_path| { + emcc.addFileArg(static_path); + }, + else => {}, + } + } + } +} + pub fn emccStep( b: *std.Build, + emsdk: Emsdk, wasm: *std.Build.Step.Compile, options: StepOptions, ) *std.Build.Step { - var emcc = b.addSystemCommand(&.{emccPath(b)}); + var emcc = b.addRunArtifact(emsdk.zemscripten.artifact("passthrough")); + emcc.addFileArg(emccPath(b, emsdk)); var iterFlags = options.flags.iterator(); while (iterFlags.next()) |kvp| { @@ -209,21 +220,7 @@ pub fn emccStep( emcc.addArtifactArg(wasm); { - for (wasm.root_module.getGraph().modules) |module| { - for (module.link_objects.items) |link_object| { - switch (link_object) { - .other_step => |compile_step| { - switch (compile_step.kind) { - .lib => { - emcc.addArtifactArg(compile_step); - }, - else => {}, - } - }, - else => {}, - } - } - } + addAllLinkedArtifacts(b, emcc, wasm.root_module); } emcc.addArg("-o"); @@ -287,13 +284,15 @@ pub fn emccStep( pub fn emrunStep( b: *std.Build, + emsdk: Emsdk, html_path: []const u8, extra_args: []const []const u8, ) *std.Build.Step { - var emrun = b.addSystemCommand(&.{emrunPath(b)}); + var emrun = b.addRunArtifact(emsdk.zemscripten.artifact("passthrough")); + emrun.addFileArg(emrunPath(b, emsdk)); + emrun.addArgs(extra_args); emrun.addArg(html_path); - // emrun.addArg("--"); return &emrun.step; } diff --git a/src/emsdk_installer.zig b/src/emsdk_installer.zig new file mode 100644 index 0000000..71a23b7 --- /dev/null +++ b/src/emsdk_installer.zig @@ -0,0 +1,106 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +fn chmod(allocator: std.mem.Allocator, path: []const u8) !void { + const argv: []const []const u8 = switch (builtin.target.os.tag) { + .linux, .macos => &.{ "chmod", "+x", path }, + .windows => &.{ "takeown", "/f", path }, + else => return, + }; + + var child = std.process.Child.init( + argv, + allocator, + ); + if (try child.spawnAndWait() != .Exited) + return error.FailedChmod; +} + +fn do_action( + allocator: std.mem.Allocator, + emsdk_path: []const u8, + action: []const u8, + version: []const u8, +) !void { + var child = std.process.Child.init( + &.{ + emsdk_path, + action, + version, + }, + allocator, + ); + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + + try child.spawn(); + + return switch (try child.wait()) { + .Exited => {}, + else => |term| { + std.log.err( + "running emsdk installer failed for action {s}: {}", + .{ action, term }, + ); + return error.InstallerFailed; + }, + }; +} + +pub fn main() !void { + var allocator_strategy = std.heap.ArenaAllocator.init(std.heap.page_allocator); + const allocator = allocator_strategy.allocator(); + defer _ = allocator_strategy.deinit(); + + const argv = try std.process.argsAlloc(allocator); + defer std.process.argsFree(allocator, argv); + + if (argv.len != 4) { + std.log.err( + "usage: {s} ", + .{argv[0]}, + ); + return error.InvalidArguments; + } + + const emsdk_script_path_src = argv[1]; + const version = argv[2]; + const install_path = argv[3]; + + const emsdk_dir_src = std.fs.path.dirname(emsdk_script_path_src) orelse unreachable; + + // Create copy of SDK installer in a dedicate output dir + // FIXME Replace with zig implementation of cp -r + { + const emsdk_dir_contents_src = try std.fmt.allocPrint(allocator, "{s}/.", .{emsdk_dir_src}); + var child = std.process.Child.init( + &.{ + "cp", + "-r", + emsdk_dir_contents_src, + install_path, + }, + allocator, + ); + switch (try child.spawnAndWait()) { + .Exited => {}, + else => |term| { + std.log.err( + "chmod failed with {} ", + .{term}, + ); + return error.InstallerFailed; + }, + } + } + + const emsdk_script_path_dst = try std.fmt.allocPrint( + allocator, + "{s}/emsdk", + .{install_path}, + ); + + try chmod(allocator, emsdk_script_path_dst); + try do_action(allocator, emsdk_script_path_dst, "install", version); + try do_action(allocator, emsdk_script_path_dst, "activate", version); +} diff --git a/src/passthrough.zig b/src/passthrough.zig new file mode 100644 index 0000000..2c271be --- /dev/null +++ b/src/passthrough.zig @@ -0,0 +1,37 @@ +const std = @import("std"); + +pub fn main() !u8 { + var allocator_strategy = std.heap.ArenaAllocator.init(std.heap.page_allocator); + const allocator = allocator_strategy.allocator(); + defer _ = allocator_strategy.deinit(); + + const argv = try std.process.argsAlloc(allocator); + defer std.process.argsFree(allocator, argv); + + if (argv.len < 2) { + std.log.err("need at least one argument", .{}); + return error.InvalidArgument; + } + + var child = std.process.Child.init( + argv[1..], + allocator, + ); + + child.stderr_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stdin_behavior = .Inherit; + + switch (try child.spawnAndWait()) { + .Exited => |ret| { + return ret; + }, + else => |term| { + std.log.err( + "{s} failed with {} ", + .{ argv[1], term }, + ); + return error.InstallerFailed; + }, + } +}