Skip to content

Instantly share code, notes, and snippets.

@jjwatt
Forked from seatedro/build.zig
Created September 19, 2024 00:47
Show Gist options
  • Save jjwatt/7a855ee110bf84f53ce2ba4d9b47f6a2 to your computer and use it in GitHub Desktop.
Save jjwatt/7a855ee110bf84f53ce2ba4d9b47f6a2 to your computer and use it in GitHub Desktop.
static ffmpeg zig build
const std = @import("std");
const builtin = @import("builtin");
pub fn build(b: *std.Build) !void {
const av = b.option(bool, "av", "Compile ffmpeg") orelse false;
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
const dep_stb = b.dependency("stb", .{});
const dep_ffmpeg = b.dependency("ffmpeg", .{});
// Add a new step for compiling Ffmpeg
if (av) {
try compileFfmpegForTarget(b, dep_ffmpeg, b.standardTargetOptionsQueryOnly(.{}));
} else {
const target = b.standardTargetOptionsQueryOnly(.{});
const ffmpeg_libs = try getFfmpegLibs(b, target);
try runZig(b, target, optimize, dep_stb, ffmpeg_libs, dep_ffmpeg);
}
}
fn compileFfmpegForTarget(b: *std.Build, dep_ffmpeg: *std.Build.Dependency, t: std.Target.Query) !void {
const cache_path = try getFfmpegCachePath(b, t);
const abs_path = cache_path[0];
// Check if cached Ffmpeg libraries exist
if (!std.meta.isError(std.fs.accessAbsolute(abs_path, .{}))) {
// Ffmpeg is already compiled and cached for this target
std.debug.print("Ffmpeg is already compiled and cached for this target\n", .{});
return;
}
const ffmpeg_path = dep_ffmpeg.path("").getPath(b);
const configure_path = dep_ffmpeg.path("configure").getPath(b);
// Compile x264 first
const dep_x264 = b.dependency("x264", .{});
const x264_path = try std.fmt.allocPrint(b.allocator, "--prefix={s}", .{abs_path});
const configure_x264_cmd = b.addSystemCommand(&.{
"./configure",
x264_path,
"--enable-static",
"--disable-cli",
});
configure_x264_cmd.cwd = dep_x264.path("");
const make_x264_cmd = b.addSystemCommand(&.{ "make", "-j4" });
make_x264_cmd.cwd = dep_x264.path("");
make_x264_cmd.step.dependOn(&configure_x264_cmd.step);
const install_x264_cmd = b.addSystemCommand(&.{ "make", "install" });
install_x264_cmd.cwd = dep_x264.path("");
install_x264_cmd.step.dependOn(&make_x264_cmd.step);
const extra_cflags = try std.fmt.allocPrint(b.allocator, "--extra-cflags=-I{s}/include", .{abs_path});
const extra_ldflags = try std.fmt.allocPrint(b.allocator, "--extra-ldflags=-L{s}/lib", .{abs_path});
// Configure Ffmpeg
const configure_cmd = b.addSystemCommand(&.{
configure_path,
"--disable-shared",
"--enable-static",
"--disable-programs",
"--disable-doc",
"--enable-small",
"--enable-libx264",
"--enable-gpl",
extra_cflags,
extra_ldflags,
});
configure_cmd.setCwd(dep_ffmpeg.path(""));
configure_cmd.step.dependOn(&install_x264_cmd.step);
// Make Ffmpeg
const make_cmd = b.addSystemCommand(&.{ "make", "-j4" });
make_cmd.setCwd(dep_ffmpeg.path(""));
make_cmd.step.dependOn(&configure_cmd.step);
// Create cache directory
const mkdir_cmd = b.addSystemCommand(&.{ "mkdir", "-p", abs_path });
mkdir_cmd.step.dependOn(&make_cmd.step);
// Copy Ffmpeg libraries to cache
const libs = [_][]const u8{ "libavcodec", "libavformat", "libavutil", "libswscale", "libswresample" };
var copy_steps = std.ArrayList(*std.Build.Step).init(b.allocator);
inline for (libs) |lib| {
const copy_cmd = b.addSystemCommand(&.{
"cp",
b.pathJoin(&.{ ffmpeg_path, lib, lib ++ ".a" }),
b.pathJoin(&.{ abs_path, lib ++ ".a" }),
});
copy_cmd.step.dependOn(&mkdir_cmd.step);
try copy_steps.append(&copy_cmd.step);
}
// Create a step that depends on all copy commands
// const copy_all = b.step("copy-ffmpeg-libs", "Copy all Ffmpeg libraries to cache");
for (copy_steps.items) |s| {
b.getInstallStep().dependOn(s);
}
// return copy_all;
}
fn getFfmpegCachePath(b: *std.Build, target: std.Target.Query) ![2][]u8 {
const abs_cache_dir = try std.fs.path.join(b.allocator, &.{ b.cache_root.path.?, "ffmpeg" });
const relative_cache_dir = try std.fs.path.relative(b.allocator, try std.fs.realpathAlloc(b.allocator, "."), abs_cache_dir);
const target_name = try target.zigTriple(b.allocator);
std.debug.print("Target name: {s}\n", .{target_name});
const relative_path = try std.fs.path.join(b.allocator, &.{ relative_cache_dir, target_name });
const absolute_path = try std.fs.path.join(b.allocator, &.{ abs_cache_dir, target_name });
std.debug.print("Cache dir: {s}\n", .{relative_path});
return .{ absolute_path, relative_path };
}
fn getFfmpegLibs(b: *std.Build, target: std.Target.Query) ![]const *std.Build.Step.Compile {
const cache_path = try getFfmpegCachePath(b, target);
const rel_path = cache_path[1];
const abs_path = cache_path[0];
if (!std.meta.isError(std.fs.accessAbsolute(abs_path, .{}))) {
// Return cached libraries
return loadCachedFfmpegLibs(b, rel_path);
} else {
std.debug.print("Ffmpeg libraries not found for {s}. Please run 'zig build compile-ffmpeg' first.\n", .{try target.zigTriple(b.allocator)});
return error.FfmpegLibrariesNotFound;
}
}
fn createCacheDir(cache_path: []const u8) !void {
const parent_dir = std.fs.path.dirname(cache_path) orelse return error.NoParentDir;
try std.fs.makeDirAbsolute(parent_dir);
const dir_name = std.fs.path.basename(cache_path);
var dir = try std.fs.openDirAbsolute(parent_dir, .{});
defer dir.close();
try dir.makeDir(dir_name);
}
fn loadCachedFfmpegLibs(b: *std.Build, cache_path: []const u8) ![]const *std.Build.Step.Compile {
var dir = try std.fs.cwd().openDir(cache_path, .{ .iterate = true });
defer dir.close();
var ffmpeg_libs = std.ArrayList(*std.Build.Step.Compile).init(b.allocator);
// libx264
const lib_path = try std.fmt.allocPrint(b.allocator, "{s}/lib", .{cache_path});
std.debug.print("lib_path: {s}\n", .{lib_path});
var x264_dir = try std.fs.cwd().openDir(lib_path, .{ .iterate = true });
var it = x264_dir.iterate();
while (try it.next()) |entry| {
if (entry.kind == .file) {
const lib_name = std.fs.path.stem(entry.name);
const lib_step = b.addStaticLibrary(.{
.name = lib_name,
.target = b.host,
.optimize = .ReleaseFast,
});
const object_path = b.path(b.pathJoin(&.{ cache_path, "lib", entry.name }));
lib_step.addObjectFile(object_path);
try ffmpeg_libs.append(lib_step);
}
}
// ffmpeg
it = dir.iterate();
while (try it.next()) |entry| {
if (entry.kind == .file) {
const lib_name = std.fs.path.stem(entry.name);
const lib_step = b.addStaticLibrary(.{
.name = lib_name,
.target = b.host,
.optimize = .ReleaseFast,
});
const object_path = b.path(b.pathJoin(&.{ cache_path, entry.name }));
lib_step.addObjectFile(object_path);
try ffmpeg_libs.append(lib_step);
}
}
return ffmpeg_libs.toOwnedSlice();
}
fn setupExecutable(
b: *std.Build,
name: []const u8,
target: std.Target.Query,
optimize: std.builtin.OptimizeMode,
dep_stb: *std.Build.Dependency,
ffmpeg_libs: []const *std.Build.Step.Compile,
dep_ffmpeg: *std.Build.Dependency,
link_libc: bool,
) !*std.Build.Step.Compile {
const exe = b.addExecutable(.{
.name = name,
.root_source_file = b.path("src/main.zig"),
.target = b.resolveTargetQuery(target),
.optimize = optimize,
.link_libc = link_libc,
});
const clap = b.dependency("clap", .{});
exe.root_module.addImport("clap", clap.module("clap"));
const cache_dir = std.fs.path.basename(b.cache_root.path.?);
linkFfmpeg(exe, ffmpeg_libs, dep_ffmpeg);
exe.addCSourceFile(.{ .file = b.path("stb/stb.c") });
exe.addIncludePath(b.path(b.pathJoin(&.{ cache_dir, "ffmpeg", "native", "include" })));
// exe.addObjectFile(b.path(b.pathJoin(&.{ cache_dir, "ffmpeg", "native", "" })));
exe.addIncludePath(dep_stb.path(""));
return exe;
}
fn linkFfmpeg(
exe: *std.Build.Step.Compile,
ffmpeg_libs: []const *std.Build.Step.Compile,
dep_ffmpeg: *std.Build.Dependency,
) void {
for (ffmpeg_libs) |lib| {
exe.linkLibrary(lib);
}
exe.addIncludePath(dep_ffmpeg.path("."));
exe.addIncludePath(dep_ffmpeg.path("libavcodec"));
exe.addIncludePath(dep_ffmpeg.path("libavformat"));
exe.addIncludePath(dep_ffmpeg.path("libavutil"));
exe.addIncludePath(dep_ffmpeg.path("libswscale"));
exe.addIncludePath(dep_ffmpeg.path("libswresample"));
// Add system-specific libraries and frameworks
switch (builtin.os.tag) {
.macos => {
// macOS frameworks
exe.linkFramework("CoreFoundation");
exe.linkFramework("CoreVideo");
exe.linkFramework("CoreMedia");
exe.linkFramework("VideoToolbox");
exe.linkFramework("AudioToolbox");
exe.linkFramework("Security");
// Additional system libraries
exe.linkSystemLibrary("z");
exe.linkSystemLibrary("bz2");
exe.linkSystemLibrary("iconv");
},
.linux => {
// Linux-specific libraries
exe.linkSystemLibrary("z");
exe.linkSystemLibrary("bz2");
},
.windows => {
// Windows-specific libraries
exe.linkSystemLibrary("bcrypt");
exe.linkSystemLibrary("advapi32");
exe.linkSystemLibrary("user32");
},
else => {},
}
}
fn runZig(
b: *std.Build,
target: std.Target.Query,
optimize: std.builtin.OptimizeMode,
dep_stb: *std.Build.Dependency,
ffmpeg_libs: []const *std.Build.Step.Compile,
dep_ffmpeg: *std.Build.Dependency,
) !void {
const exe = try setupExecutable(
b,
"asciigen",
target,
optimize,
dep_stb,
ffmpeg_libs,
dep_ffmpeg,
true,
);
const exe_check = try setupExecutable(
b,
"asciigen-check",
target,
optimize,
dep_stb,
ffmpeg_libs,
dep_ffmpeg,
false,
);
const check_step = b.step("check", "Run the check");
check_step.dependOn(&exe_check.step);
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment