1 Commits

Author SHA1 Message Date
Mitchell Hashimoto
1ec74f8e39 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).
2025-10-01 14:03:49 -07:00
4 changed files with 202 additions and 295 deletions

View File

@@ -3,6 +3,7 @@ const GhosttyDist = @This();
const std = @import("std"); const std = @import("std");
const Config = @import("Config.zig"); const Config = @import("Config.zig");
const SharedDeps = @import("SharedDeps.zig"); const SharedDeps = @import("SharedDeps.zig");
const GhosttyFrameData = @import("GhosttyFrameData.zig");
/// The final source tarball. /// The final source tarball.
archive: std.Build.LazyPath, 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_c);
try resources.append(alloc, gtk.resources_h); 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 // git archive to create the final tarball. "git archive" is the
// easiest way I can find to create a tarball that ignores stuff // easiest way I can find to create a tarball that ignores stuff

View File

@@ -5,35 +5,25 @@ const GhosttyFrameData = @This();
const std = @import("std"); const std = @import("std");
const Config = @import("Config.zig"); const Config = @import("Config.zig");
const SharedDeps = @import("SharedDeps.zig"); const SharedDeps = @import("SharedDeps.zig");
const DistResource = @import("GhosttyDist.zig").Resource;
/// The exe.
exe: *std.Build.Step.Compile,
/// The output path for the compressed framedata zig file /// The output path for the compressed framedata zig file
output: std.Build.LazyPath, output: std.Build.LazyPath,
pub fn init(b: *std.Build) !GhosttyFrameData { pub fn init(b: *std.Build) !GhosttyFrameData {
const exe = b.addExecutable(.{ const dist = distResources(b);
.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 run = b.addRunArtifact(exe); // Generate the Zig source file that embeds the compressed data
// Both the compressed framedata and the Zig source file const wf = b.addWriteFiles();
// have to be put in the same directory, since the compressed file _ = wf.addCopyFile(dist.framedata.path(b), "framedata.compressed");
// has to be within the source file's include path. const zig_file = wf.add("framedata.zig",
const dir = run.addOutputDirectoryArg("framedata"); \\//! This file is auto-generated. Do not edit.
\\
\\pub const compressed = @embedFile("framedata.compressed");
\\
);
return .{ return .{ .output = zig_file };
.exe = exe,
.output = dir.path(b, "framedata.zig"),
};
} }
/// Add the "framedata" import. /// 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, .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,
},
};
}

145
src/build/framegen/main.c Normal file
View File

@@ -0,0 +1,145 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <errno.h>
#include <zlib.h>
#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 <frames_dir> <output_file>\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;
}

View File

@@ -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"),
};