From fb0c1eebdad3918be12cc522d1264aab42c4e9e1 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Wed, 23 Sep 2026 23:25:34 +0300 Subject: [PATCH] automatically create zip file for wasm build --- build.zig | 44 ++++++++++++++++++------- build.zig.zon | 3 ++ libs/miniz/build.zig | 41 ++++++++++++++++++++++++ libs/miniz/build.zig.zon | 13 ++++++++ libs/miniz/scripts/remove-pragma.zig | 48 ++++++++++++++++++++++++++++ libs/miniz/src/root.zig | 37 +++++++++++++++++++++ scripts/create-zip.zig | 42 ++++++++++++++++++++++++ 7 files changed, 217 insertions(+), 11 deletions(-) create mode 100644 libs/miniz/build.zig create mode 100644 libs/miniz/build.zig.zon create mode 100644 libs/miniz/scripts/remove-pragma.zig create mode 100644 libs/miniz/src/root.zig create mode 100644 scripts/create-zip.zig diff --git a/build.zig b/build.zig index 5d66c64..1f4840a 100644 --- a/build.zig +++ b/build.zig @@ -2,12 +2,19 @@ const std = @import("std"); const Build = std.Build; const sokol = @import("sokol"); +const game_name = "sokol_template_v2"; + pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const dep_stb = b.dependency("stb", .{}); - const ctx = Context.init(b, dep_stb.module("stb_image")); + const dep_miniz = b.dependency("miniz", .{ + .target = b.graph.host, + }); + const ctx = Context.init(b, dep_stb.module("stb_image"), dep_miniz.module("miniz")); + + const pkg_install_dir: Build.InstallDir = .{ .custom = "pkg" }; var run_cmd_step: *std.Build.Step = undefined; switch (try buildProject(ctx, target, optimize)) { @@ -26,13 +33,19 @@ pub fn build(b: *std.Build) !void { test_step.dependOn(&run_exe_tests.step); } }, - .wasm => |build_step| { - b.getInstallStep().dependOn(build_step); + .wasm => |install_dir| { + b.getInstallStep().dependOn(&install_dir.step); + + var create_zip_run = b.addRunArtifact(ctx.create_zip); + create_zip_run.addDirectoryArg(install_dir.options.source_dir); + const wasm_zip = create_zip_run.addOutputFileArg("html.zip"); + var html_install_file = b.addInstallFileWithDir(wasm_zip, pkg_install_dir, b.fmt("{s}_html.zip", .{game_name})); + b.getInstallStep().dependOn(&html_install_file.step); const dep_sokol = b.dependency("sokol", .{}); const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{}); const emrun_step = sokol.emRunStep(b, .{ .name = "index", .emsdk = dep_emsdk }); - emrun_step.step.dependOn(build_step); + emrun_step.step.dependOn(&install_dir.step); run_cmd_step = &emrun_step.step; // add an emsdk install step @@ -48,14 +61,12 @@ pub fn build(b: *std.Build) !void { const ProjectResult = union(enum) { native: *std.Build.Step.Compile, - wasm: *std.Build.Step, + wasm: *std.Build.Step.InstallDir, }; fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !ProjectResult { const b = ctx.b; - const name = "sokol_template_v2"; - const assets_dir = b.path("assets"); const asset_files = [_][]const u8{ "sokoban/sokoban_tilesheet.png", @@ -183,7 +194,7 @@ fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builti }; } else { const exe = b.addExecutable(.{ - .name = name, + .name = game_name, .root_module = exe_mod, }); @@ -245,8 +256,9 @@ const Context = struct { b: *std.Build, png_to_icon: *Build.Step.Compile, create_win32_resource: *Build.Step.Compile, + create_zip: *Build.Step.Compile, - pub fn init(b: *std.Build, stb_image: *Build.Module) Context { + pub fn init(b: *std.Build, stb_image: *Build.Module, miniz: *Build.Module) Context { return Context{ .b = b, .png_to_icon = b.addExecutable(.{ @@ -265,6 +277,16 @@ const Context = struct { .target = b.graph.host, .root_source_file = b.path("scripts/create-win32-resource.zig"), }) + }), + .create_zip = b.addExecutable(.{ + .name = "create-zip", + .root_module = b.createModule(.{ + .target = b.graph.host, + .root_source_file = b.path("scripts/create-zip.zig"), + .imports = &.{ + .{ .name = "miniz", .module = miniz } + } + }) }) }; } @@ -294,7 +316,7 @@ const BuildWasmOptions = struct { dep_sokol: *std.Build.Dependency, }; -fn buildWasm(b: *std.Build, opts: BuildWasmOptions) *std.Build.Step { +fn buildWasm(b: *std.Build, opts: BuildWasmOptions) *std.Build.Step.InstallDir { opts.root_module.sanitize_c = .off; // build the main file into a library, this is because the WASM 'exe' @@ -329,7 +351,7 @@ fn buildWasm(b: *std.Build, opts: BuildWasmOptions) *std.Build.Step { .extra_args = &.{ "-sALLOW_MEMORY_GROWTH", "-sENVIRONMENT=web" } }) catch unreachable; - return &link_step.step; + return link_step; } fn patchWasmIncludeDirs( diff --git a/build.zig.zon b/build.zig.zon index 6c2eafc..fe8ec58 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -21,6 +21,9 @@ .tracy = .{ .path = "./libs/tracy", }, + .miniz = .{ + .path = "./libs/miniz", + }, }, .paths = .{ "build.zig", diff --git a/libs/miniz/build.zig b/libs/miniz/build.zig new file mode 100644 index 0000000..118084e --- /dev/null +++ b/libs/miniz/build.zig @@ -0,0 +1,41 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const src_dep = b.dependency("src", .{}); + + // TODO: This is a workaround to avoid a translate-c bug. + // Remove this when fixed, likely when 0.17 is released. + const remove_pragma_step = b.addRunArtifact(b.addExecutable(.{ + .name = "remove pragma", + .root_module = b.createModule(.{ + .root_source_file = b.path("scripts/remove-pragma.zig"), + .target = b.graph.host + }) + })); + remove_pragma_step.addFileArg(src_dep.path("miniz.h")); + var clean_miniz_h = remove_pragma_step.addOutputFileArg("miniz.h"); + + const mod = b.addModule("miniz", .{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("src/root.zig"), + .link_libc = true + }); + mod.addIncludePath(clean_miniz_h.dirname()); + mod.addCSourceFiles(.{ + .root = src_dep.path("."), + .files = &.{ "miniz.c" }, + }); + + const write_file_step = b.addWriteFiles(); + const translate_c = b.addTranslateC(.{ + .root_source_file = write_file_step.add("c.h", "#include \"miniz.h\""), + .target = target, + .optimize = optimize, + }); + translate_c.addIncludePath(clean_miniz_h.dirname()); + mod.addImport("c", translate_c.createModule()); +} diff --git a/libs/miniz/build.zig.zon b/libs/miniz/build.zig.zon new file mode 100644 index 0000000..a3f3ab0 --- /dev/null +++ b/libs/miniz/build.zig.zon @@ -0,0 +1,13 @@ +.{ + .name = .miniz, + .version = "0.0.1", + .dependencies = .{ + .src = .{ + .url = "https://github.com/richgel999/miniz/releases/download/3.1.2/miniz-3.1.2.zip", + .hash = "N-V-__8AACZ9BwBtpix1JS569yNZcDzDf9abe3Zimx8glq9u", + }, + }, + .minimum_zig_version = "0.17.0-dev.2264+230c63650", + .paths = .{""}, + .fingerprint = 0xa708528eb541dff2, +} diff --git a/libs/miniz/scripts/remove-pragma.zig b/libs/miniz/scripts/remove-pragma.zig new file mode 100644 index 0000000..8c90a9a --- /dev/null +++ b/libs/miniz/scripts/remove-pragma.zig @@ -0,0 +1,48 @@ +const std = @import("std"); +const Io = std.Io; + +pub fn main(init: std.process.Init) !void { + const io = init.io; + const arena = init.arena.allocator(); + const args = try init.minimal.args.toSlice(arena); + + if (args.len != 3) { + std.debug.print("Usage: ./remove-pragma ", .{}); + std.process.exit(1); + } + + const input_path = args[1]; + const output_path = args[2]; + + const cwd = Io.Dir.cwd(); + + var input_file = try cwd.openFile(io, input_path, .{ .mode = .read_only }); + defer input_file.close(io); + + var input_buffer: [4*4096]u8 = undefined; + var input_file_reader = input_file.reader(io, &input_buffer); + const reader = &input_file_reader.interface; + + var output_file = try cwd.createFile(io, output_path, .{ }); + defer output_file.close(io); + + var output_buffer: [4*4096]u8 = undefined; + var output_file_writer = output_file.writer(io, &output_buffer); + const writer = &output_file_writer.interface; + + while (true) { + const line = reader.takeDelimiterInclusive('\n') catch |err| switch (err) { + error.EndOfStream => break, + else => return err, + }; + + const trimmed_line = std.mem.trimStart(u8, line, &std.ascii.whitespace); + if (std.mem.startsWith(u8, trimmed_line, "#pragma ")) { + continue; + } + + try writer.writeAll(line); + } + + try writer.flush(); +} diff --git a/libs/miniz/src/root.zig b/libs/miniz/src/root.zig new file mode 100644 index 0000000..9d7429d --- /dev/null +++ b/libs/miniz/src/root.zig @@ -0,0 +1,37 @@ +const c = @import("c"); + +pub const Archive = struct { + archive: c.mz_zip_archive, + + pub fn initFile(self: *Archive, output: [*c]const u8) !void { + self.* = Archive{ + .archive = .{} + }; + if (c.mz_zip_writer_init_file(&self.archive, output, 0) == 0) { + return error.InitFileFailed; + } + } + + pub fn end(self: *Archive) void { + _ = c.mz_zip_writer_end(&self.archive); + } + + pub fn addFile(self: *Archive, name_in_archive: [*c]const u8, path: [*c]const u8) !void { + if (c.mz_zip_writer_add_file( + &self.archive, + name_in_archive, + path, + null, + 0, + @bitCast(c.MZ_DEFAULT_COMPRESSION), + ) == 0) { + return error.AddFileFailed; + } + } + + pub fn finalize(self: *Archive) !void { + if (c.mz_zip_writer_finalize_archive(&self.archive) == 0) { + return error.FinalizeFailed; + } + } +}; diff --git a/scripts/create-zip.zig b/scripts/create-zip.zig new file mode 100644 index 0000000..2e8e087 --- /dev/null +++ b/scripts/create-zip.zig @@ -0,0 +1,42 @@ +const std = @import("std"); +const Io = std.Io; + +const miniz = @import("miniz"); + +pub fn main(init: std.process.Init) !void { + const io = init.io; + const arena = init.arena.allocator(); + const args = try init.minimal.args.toSlice(arena); + + if (args.len != 3) { + std.debug.print("Usage: ./create-zip ", .{}); + std.process.exit(1); + } + + const input_directory_path = args[1]; + const output_zip_path = args[2]; + const cwd = Io.Dir.cwd(); + + var archive: miniz.Archive = undefined; + try archive.initFile(output_zip_path); + defer archive.end(); + + var input_dir = try cwd.openDir(io, input_directory_path, .{ .iterate = true }); + defer input_dir.close(io); + + var input_dir_iter = input_dir.iterate(); + while (try input_dir_iter.next(io)) |entry| { + if (entry.kind == .file) { + var path_buff: [std.fs.max_path_bytes+1]u8 = undefined; + const path_len = try input_dir.realPathFile(io, entry.name, path_buff[0..(path_buff.len-1)]); + path_buff[path_len] = 0; + + var namez_buff: [std.fs.max_name_bytes+1]u8 = undefined; + @memcpy(namez_buff[0..entry.name.len], entry.name); + namez_buff[entry.name.len] = 0; + try archive.addFile(namez_buff[0..entry.name.len :0], path_buff[0..path_len :0]); + } + } + + try archive.finalize(); +}