From 1ec74f8e396968a6487e6faae92b6340b12cc074 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 1 Oct 2025 13:21:16 -0700 Subject: [PATCH] Convert framegen to C, add compressed data to source tarball Zig 0.15 removed the ability to compress from the stdlib, which makes porting our framegen tool to Zig 0.15+ more work than it's worth. We already depend on and have the ability to build zlib, and Zig is a full blown C compiler, so let's just use C. The framegen C program doesn't free any memory, because it is meant to exit quickly. It otherwise behaves pretty much the same as the old Zig codebase. The build scripts were modified to build the C program and run it, but also to include the framedata in the generated source tarball so that downstream packagers don't have to do this (although they'll have all the deps anyways). --- src/build/GhosttyDist.zig | 5 + src/build/GhosttyFrameData.zig | 74 ++++++--- src/build/framegen/main.c | 145 +++++++++++++++++ src/build/framegen/main.zig | 273 --------------------------------- 4 files changed, 202 insertions(+), 295 deletions(-) create mode 100644 src/build/framegen/main.c delete mode 100644 src/build/framegen/main.zig diff --git a/src/build/GhosttyDist.zig b/src/build/GhosttyDist.zig index d889f2350..f8c221350 100644 --- a/src/build/GhosttyDist.zig +++ b/src/build/GhosttyDist.zig @@ -3,6 +3,7 @@ const GhosttyDist = @This(); const std = @import("std"); const Config = @import("Config.zig"); const SharedDeps = @import("SharedDeps.zig"); +const GhosttyFrameData = @import("GhosttyFrameData.zig"); /// The final source tarball. archive: std.Build.LazyPath, @@ -25,6 +26,10 @@ pub fn init(b: *std.Build, cfg: *const Config) !GhosttyDist { try resources.append(alloc, gtk.resources_c); try resources.append(alloc, gtk.resources_h); } + { + const framedata = GhosttyFrameData.distResources(b); + try resources.append(alloc, framedata.framedata); + } // git archive to create the final tarball. "git archive" is the // easiest way I can find to create a tarball that ignores stuff diff --git a/src/build/GhosttyFrameData.zig b/src/build/GhosttyFrameData.zig index 1644388bc..52c84a66c 100644 --- a/src/build/GhosttyFrameData.zig +++ b/src/build/GhosttyFrameData.zig @@ -5,35 +5,25 @@ const GhosttyFrameData = @This(); const std = @import("std"); const Config = @import("Config.zig"); const SharedDeps = @import("SharedDeps.zig"); - -/// The exe. -exe: *std.Build.Step.Compile, +const DistResource = @import("GhosttyDist.zig").Resource; /// The output path for the compressed framedata zig file output: std.Build.LazyPath, pub fn init(b: *std.Build) !GhosttyFrameData { - const exe = b.addExecutable(.{ - .name = "framegen", - .root_module = b.createModule(.{ - .root_source_file = b.path("src/build/framegen/main.zig"), - .target = b.graph.host, - .strip = false, - .omit_frame_pointer = false, - .unwind_tables = .sync, - }), - }); + const dist = distResources(b); - const run = b.addRunArtifact(exe); - // Both the compressed framedata and the Zig source file - // have to be put in the same directory, since the compressed file - // has to be within the source file's include path. - const dir = run.addOutputDirectoryArg("framedata"); + // Generate the Zig source file that embeds the compressed data + const wf = b.addWriteFiles(); + _ = wf.addCopyFile(dist.framedata.path(b), "framedata.compressed"); + const zig_file = wf.add("framedata.zig", + \\//! This file is auto-generated. Do not edit. + \\ + \\pub const compressed = @embedFile("framedata.compressed"); + \\ + ); - return .{ - .exe = exe, - .output = dir.path(b, "framedata.zig"), - }; + return .{ .output = zig_file }; } /// Add the "framedata" import. @@ -43,3 +33,43 @@ pub fn addImport(self: *const GhosttyFrameData, step: *std.Build.Step.Compile) v .root_source_file = self.output, }); } + +/// Creates the framedata resources that can be prebuilt for our dist build. +pub fn distResources(b: *std.Build) struct { + framedata: DistResource, +} { + const exe = b.addExecutable(.{ + .name = "framegen", + .target = b.graph.host, + }); + exe.addCSourceFile(.{ + .file = b.path("src/build/framegen/main.c"), + .flags = &.{}, + }); + exe.linkLibC(); + + if (b.systemIntegrationOption("zlib", .{})) { + exe.linkSystemLibrary2("zlib", .{ + .preferred_link_mode = .dynamic, + .search_strategy = .mode_first, + }); + } else { + if (b.lazyDependency("zlib", .{ + .target = b.graph.host, + .optimize = .ReleaseFast, + })) |zlib_dep| { + exe.linkLibrary(zlib_dep.artifact("z")); + } + } + + const run = b.addRunArtifact(exe); + run.addDirectoryArg(b.path("src/build/framegen/frames")); + const compressed_file = run.addOutputFileArg("framedata.compressed"); + + return .{ + .framedata = .{ + .dist = "src/build/framegen/framedata.compressed", + .generated = compressed_file, + }, + }; +} diff --git a/src/build/framegen/main.c b/src/build/framegen/main.c new file mode 100644 index 000000000..647768006 --- /dev/null +++ b/src/build/framegen/main.c @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include +#include +#include + +#define SEPARATOR '\x01' +#define CHUNK_SIZE 16384 + +static int filter_frames(const struct dirent *entry) { + const char *name = entry->d_name; + size_t len = strlen(name); + return len > 4 && strcmp(name + len - 4, ".txt") == 0; +} + +static int compare_frames(const struct dirent **a, const struct dirent **b) { + return strcmp((*a)->d_name, (*b)->d_name); +} + +static char *read_file(const char *path, size_t *out_size) { + FILE *f = fopen(path, "rb"); + if (!f) { + fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno)); + return NULL; + } + + fseek(f, 0, SEEK_END); + long size = ftell(f); + fseek(f, 0, SEEK_SET); + + char *buf = malloc(size); + if (!buf) { + return NULL; + } + + if (fread(buf, 1, size, f) != (size_t)size) { + fprintf(stderr, "Failed to read %s\n", path); + return NULL; + } + + fclose(f); + *out_size = size; + return buf; +} + +int main(int argc, char **argv) { + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + const char *frames_dir = argv[1]; + const char *output_file = argv[2]; + + struct dirent **namelist; + int n = scandir(frames_dir, &namelist, filter_frames, compare_frames); + if (n < 0) { + fprintf(stderr, "Failed to scan directory %s: %s\n", frames_dir, strerror(errno)); + return 1; + } + + if (n == 0) { + fprintf(stderr, "No frame files found in %s\n", frames_dir); + return 1; + } + + size_t total_size = 0; + char **frame_contents = calloc(n, sizeof(char*)); + size_t *frame_sizes = calloc(n, sizeof(size_t)); + + for (int i = 0; i < n; i++) { + char path[4096]; + snprintf(path, sizeof(path), "%s/%s", frames_dir, namelist[i]->d_name); + + frame_contents[i] = read_file(path, &frame_sizes[i]); + if (!frame_contents[i]) { + return 1; + } + + total_size += frame_sizes[i]; + if (i < n - 1) total_size++; + } + + char *joined = malloc(total_size); + if (!joined) { + fprintf(stderr, "Failed to allocate joined buffer\n"); + return 1; + } + + size_t offset = 0; + for (int i = 0; i < n; i++) { + memcpy(joined + offset, frame_contents[i], frame_sizes[i]); + offset += frame_sizes[i]; + if (i < n - 1) { + joined[offset++] = SEPARATOR; + } + } + + uLongf compressed_size = compressBound(total_size); + unsigned char *compressed = malloc(compressed_size); + if (!compressed) { + fprintf(stderr, "Failed to allocate compression buffer\n"); + return 1; + } + + z_stream stream = {0}; + stream.next_in = (unsigned char*)joined; + stream.avail_in = total_size; + stream.next_out = compressed; + stream.avail_out = compressed_size; + + // Use -MAX_WBITS for raw DEFLATE (no zlib wrapper) + int ret = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); + if (ret != Z_OK) { + fprintf(stderr, "deflateInit2 failed: %d\n", ret); + return 1; + } + + ret = deflate(&stream, Z_FINISH); + if (ret != Z_STREAM_END) { + fprintf(stderr, "deflate failed: %d\n", ret); + deflateEnd(&stream); + return 1; + } + + compressed_size = stream.total_out; + deflateEnd(&stream); + + FILE *out = fopen(output_file, "wb"); + if (!out) { + fprintf(stderr, "Failed to create %s: %s\n", output_file, strerror(errno)); + return 1; + } + + if (fwrite(compressed, 1, compressed_size, out) != compressed_size) { + fprintf(stderr, "Failed to write compressed data\n"); + return 1; + } + + fclose(out); + + return 0; +} diff --git a/src/build/framegen/main.zig b/src/build/framegen/main.zig deleted file mode 100644 index f4a7d9443..000000000 --- a/src/build/framegen/main.zig +++ /dev/null @@ -1,273 +0,0 @@ -const std = @import("std"); -const fs = std.fs; - -/// Generates a compressed file of all the ghostty frames -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - - var arg_iter = try std.process.argsWithAllocator(gpa.allocator()); - // Skip the exe name - _ = arg_iter.skip(); - - const out_dir_path = arg_iter.next() orelse return error.MissingOutputPath; - const compressed_out = "framedata.compressed"; - const zig_out = "framedata.zig"; - - const out_dir = try fs.cwd().openDir(out_dir_path, .{}); - const compressed_file = try out_dir.createFile(compressed_out, .{}); - - // Join the frames with a null byte. We'll split on this later - const all_frames = try std.mem.join(gpa.allocator(), "\x01", &frames); - var fbs = std.io.fixedBufferStream(all_frames); - - const reader = fbs.reader(); - try std.compress.flate.compress(reader, compressed_file.writer(), .{}); - - const compressed_path = try std.fs.path.join(gpa.allocator(), &.{ out_dir_path, compressed_out }); - - const zig_file = try out_dir.createFile(zig_out, .{}); - - try zig_file.writer().print( - \\//! This file is auto-generated. Do not edit. - \\ - \\pub const compressed = @embedFile("{s}"); - , .{compressed_path}); -} - -const frames = [_][]const u8{ - @embedFile("frames/frame_001.txt"), - @embedFile("frames/frame_002.txt"), - @embedFile("frames/frame_003.txt"), - @embedFile("frames/frame_004.txt"), - @embedFile("frames/frame_005.txt"), - @embedFile("frames/frame_006.txt"), - @embedFile("frames/frame_007.txt"), - @embedFile("frames/frame_008.txt"), - @embedFile("frames/frame_009.txt"), - @embedFile("frames/frame_010.txt"), - @embedFile("frames/frame_011.txt"), - @embedFile("frames/frame_012.txt"), - @embedFile("frames/frame_013.txt"), - @embedFile("frames/frame_014.txt"), - @embedFile("frames/frame_015.txt"), - @embedFile("frames/frame_016.txt"), - @embedFile("frames/frame_017.txt"), - @embedFile("frames/frame_018.txt"), - @embedFile("frames/frame_019.txt"), - @embedFile("frames/frame_020.txt"), - @embedFile("frames/frame_021.txt"), - @embedFile("frames/frame_022.txt"), - @embedFile("frames/frame_023.txt"), - @embedFile("frames/frame_024.txt"), - @embedFile("frames/frame_025.txt"), - @embedFile("frames/frame_026.txt"), - @embedFile("frames/frame_027.txt"), - @embedFile("frames/frame_028.txt"), - @embedFile("frames/frame_029.txt"), - @embedFile("frames/frame_030.txt"), - @embedFile("frames/frame_031.txt"), - @embedFile("frames/frame_032.txt"), - @embedFile("frames/frame_033.txt"), - @embedFile("frames/frame_034.txt"), - @embedFile("frames/frame_035.txt"), - @embedFile("frames/frame_036.txt"), - @embedFile("frames/frame_037.txt"), - @embedFile("frames/frame_038.txt"), - @embedFile("frames/frame_039.txt"), - @embedFile("frames/frame_040.txt"), - @embedFile("frames/frame_041.txt"), - @embedFile("frames/frame_042.txt"), - @embedFile("frames/frame_043.txt"), - @embedFile("frames/frame_044.txt"), - @embedFile("frames/frame_045.txt"), - @embedFile("frames/frame_046.txt"), - @embedFile("frames/frame_047.txt"), - @embedFile("frames/frame_048.txt"), - @embedFile("frames/frame_049.txt"), - @embedFile("frames/frame_050.txt"), - @embedFile("frames/frame_051.txt"), - @embedFile("frames/frame_052.txt"), - @embedFile("frames/frame_053.txt"), - @embedFile("frames/frame_054.txt"), - @embedFile("frames/frame_055.txt"), - @embedFile("frames/frame_056.txt"), - @embedFile("frames/frame_057.txt"), - @embedFile("frames/frame_058.txt"), - @embedFile("frames/frame_059.txt"), - @embedFile("frames/frame_060.txt"), - @embedFile("frames/frame_061.txt"), - @embedFile("frames/frame_062.txt"), - @embedFile("frames/frame_063.txt"), - @embedFile("frames/frame_064.txt"), - @embedFile("frames/frame_065.txt"), - @embedFile("frames/frame_066.txt"), - @embedFile("frames/frame_067.txt"), - @embedFile("frames/frame_068.txt"), - @embedFile("frames/frame_069.txt"), - @embedFile("frames/frame_070.txt"), - @embedFile("frames/frame_071.txt"), - @embedFile("frames/frame_072.txt"), - @embedFile("frames/frame_073.txt"), - @embedFile("frames/frame_074.txt"), - @embedFile("frames/frame_075.txt"), - @embedFile("frames/frame_076.txt"), - @embedFile("frames/frame_077.txt"), - @embedFile("frames/frame_078.txt"), - @embedFile("frames/frame_079.txt"), - @embedFile("frames/frame_080.txt"), - @embedFile("frames/frame_081.txt"), - @embedFile("frames/frame_082.txt"), - @embedFile("frames/frame_083.txt"), - @embedFile("frames/frame_084.txt"), - @embedFile("frames/frame_085.txt"), - @embedFile("frames/frame_086.txt"), - @embedFile("frames/frame_087.txt"), - @embedFile("frames/frame_088.txt"), - @embedFile("frames/frame_089.txt"), - @embedFile("frames/frame_090.txt"), - @embedFile("frames/frame_091.txt"), - @embedFile("frames/frame_092.txt"), - @embedFile("frames/frame_093.txt"), - @embedFile("frames/frame_094.txt"), - @embedFile("frames/frame_095.txt"), - @embedFile("frames/frame_096.txt"), - @embedFile("frames/frame_097.txt"), - @embedFile("frames/frame_098.txt"), - @embedFile("frames/frame_099.txt"), - @embedFile("frames/frame_100.txt"), - @embedFile("frames/frame_101.txt"), - @embedFile("frames/frame_102.txt"), - @embedFile("frames/frame_103.txt"), - @embedFile("frames/frame_104.txt"), - @embedFile("frames/frame_105.txt"), - @embedFile("frames/frame_106.txt"), - @embedFile("frames/frame_107.txt"), - @embedFile("frames/frame_108.txt"), - @embedFile("frames/frame_109.txt"), - @embedFile("frames/frame_110.txt"), - @embedFile("frames/frame_111.txt"), - @embedFile("frames/frame_112.txt"), - @embedFile("frames/frame_113.txt"), - @embedFile("frames/frame_114.txt"), - @embedFile("frames/frame_115.txt"), - @embedFile("frames/frame_116.txt"), - @embedFile("frames/frame_117.txt"), - @embedFile("frames/frame_118.txt"), - @embedFile("frames/frame_119.txt"), - @embedFile("frames/frame_120.txt"), - @embedFile("frames/frame_121.txt"), - @embedFile("frames/frame_122.txt"), - @embedFile("frames/frame_123.txt"), - @embedFile("frames/frame_124.txt"), - @embedFile("frames/frame_125.txt"), - @embedFile("frames/frame_126.txt"), - @embedFile("frames/frame_127.txt"), - @embedFile("frames/frame_128.txt"), - @embedFile("frames/frame_129.txt"), - @embedFile("frames/frame_130.txt"), - @embedFile("frames/frame_131.txt"), - @embedFile("frames/frame_132.txt"), - @embedFile("frames/frame_133.txt"), - @embedFile("frames/frame_134.txt"), - @embedFile("frames/frame_135.txt"), - @embedFile("frames/frame_136.txt"), - @embedFile("frames/frame_137.txt"), - @embedFile("frames/frame_138.txt"), - @embedFile("frames/frame_139.txt"), - @embedFile("frames/frame_140.txt"), - @embedFile("frames/frame_141.txt"), - @embedFile("frames/frame_142.txt"), - @embedFile("frames/frame_143.txt"), - @embedFile("frames/frame_144.txt"), - @embedFile("frames/frame_145.txt"), - @embedFile("frames/frame_146.txt"), - @embedFile("frames/frame_147.txt"), - @embedFile("frames/frame_148.txt"), - @embedFile("frames/frame_149.txt"), - @embedFile("frames/frame_150.txt"), - @embedFile("frames/frame_151.txt"), - @embedFile("frames/frame_152.txt"), - @embedFile("frames/frame_153.txt"), - @embedFile("frames/frame_154.txt"), - @embedFile("frames/frame_155.txt"), - @embedFile("frames/frame_156.txt"), - @embedFile("frames/frame_157.txt"), - @embedFile("frames/frame_158.txt"), - @embedFile("frames/frame_159.txt"), - @embedFile("frames/frame_160.txt"), - @embedFile("frames/frame_161.txt"), - @embedFile("frames/frame_162.txt"), - @embedFile("frames/frame_163.txt"), - @embedFile("frames/frame_164.txt"), - @embedFile("frames/frame_165.txt"), - @embedFile("frames/frame_166.txt"), - @embedFile("frames/frame_167.txt"), - @embedFile("frames/frame_168.txt"), - @embedFile("frames/frame_169.txt"), - @embedFile("frames/frame_170.txt"), - @embedFile("frames/frame_171.txt"), - @embedFile("frames/frame_172.txt"), - @embedFile("frames/frame_173.txt"), - @embedFile("frames/frame_174.txt"), - @embedFile("frames/frame_175.txt"), - @embedFile("frames/frame_176.txt"), - @embedFile("frames/frame_177.txt"), - @embedFile("frames/frame_178.txt"), - @embedFile("frames/frame_179.txt"), - @embedFile("frames/frame_180.txt"), - @embedFile("frames/frame_181.txt"), - @embedFile("frames/frame_182.txt"), - @embedFile("frames/frame_183.txt"), - @embedFile("frames/frame_184.txt"), - @embedFile("frames/frame_185.txt"), - @embedFile("frames/frame_186.txt"), - @embedFile("frames/frame_187.txt"), - @embedFile("frames/frame_188.txt"), - @embedFile("frames/frame_189.txt"), - @embedFile("frames/frame_190.txt"), - @embedFile("frames/frame_191.txt"), - @embedFile("frames/frame_192.txt"), - @embedFile("frames/frame_193.txt"), - @embedFile("frames/frame_194.txt"), - @embedFile("frames/frame_195.txt"), - @embedFile("frames/frame_196.txt"), - @embedFile("frames/frame_197.txt"), - @embedFile("frames/frame_198.txt"), - @embedFile("frames/frame_199.txt"), - @embedFile("frames/frame_200.txt"), - @embedFile("frames/frame_201.txt"), - @embedFile("frames/frame_202.txt"), - @embedFile("frames/frame_203.txt"), - @embedFile("frames/frame_204.txt"), - @embedFile("frames/frame_205.txt"), - @embedFile("frames/frame_206.txt"), - @embedFile("frames/frame_207.txt"), - @embedFile("frames/frame_208.txt"), - @embedFile("frames/frame_209.txt"), - @embedFile("frames/frame_210.txt"), - @embedFile("frames/frame_211.txt"), - @embedFile("frames/frame_212.txt"), - @embedFile("frames/frame_213.txt"), - @embedFile("frames/frame_214.txt"), - @embedFile("frames/frame_215.txt"), - @embedFile("frames/frame_216.txt"), - @embedFile("frames/frame_217.txt"), - @embedFile("frames/frame_218.txt"), - @embedFile("frames/frame_219.txt"), - @embedFile("frames/frame_220.txt"), - @embedFile("frames/frame_221.txt"), - @embedFile("frames/frame_222.txt"), - @embedFile("frames/frame_223.txt"), - @embedFile("frames/frame_224.txt"), - @embedFile("frames/frame_225.txt"), - @embedFile("frames/frame_226.txt"), - @embedFile("frames/frame_227.txt"), - @embedFile("frames/frame_228.txt"), - @embedFile("frames/frame_229.txt"), - @embedFile("frames/frame_230.txt"), - @embedFile("frames/frame_231.txt"), - @embedFile("frames/frame_232.txt"), - @embedFile("frames/frame_233.txt"), - @embedFile("frames/frame_234.txt"), - @embedFile("frames/frame_235.txt"), -};