flow/build.zig
CJ van den Berg 39b482b2e0
feat(gui): M2 - sokol_gfx GPU backend initialised from wio window
Implements milestone 2 of the wio+sokol_gfx cross-platform GUI renderer:

- build.zig.zon: add wio and sokol-zig as lazy path dependencies
- build.zig: wire -Drenderer=gui - creates modules for gpu, app, and the
  stub rasterizer; links sokol_clib against system GL/X11/Xi/Xcursor/ALSA
  (requires: libgl-dev libx11-dev libxi-dev libxcursor-dev libasound2-dev)
- src/gui/gpu/builtin.glsl.zig: hand-crafted GLCORE ShaderDesc with a
  full-screen-quad vertex stage and a cell-grid fragment stage; Y-flip via
  row_count*cell_size_y; FsParams as 4 INT uniforms; RGBA32UI cell texture
  and R8 glyph-atlas texture with NONFILTERING samplers
- src/gui/gpu/gpu.zig: sokol_gfx backend mirroring the d3d11 backend;
  TRIANGLE_STRIP pipeline, CPU-side glyph atlas shadow, per-frame cell
  texture update via sg.updateImage
- src/gui/rasterizer/stub.zig: blank-glyph stub rasterizer for M2 testing
- src/gui/wio/app.zig: wio event loop + thespian bridge; creates a core
  OpenGL 3.3 context, calls sg.setup/gpu.init, sends RDR messages for
  WindowCreated/Resize/keyboard/mouse events, renders on screen_pending
- src/gui/wio/input.zig: wio Button → Flow codepoint/mouse-button mapping
- src/renderer/gui/renderer.zig: standard renderer interface consumed by
  tui.zig; dispatches RDR events, delegates font/cursor/clipboard to app
2026-03-29 19:04:52 +02:00

956 lines
37 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
const optimize_deps = .ReleaseFast;
pub const Renderer = enum { terminal, gui, d3d11 };
pub fn build(b: *std.Build) void {
const all_targets = b.option(bool, "all_targets", "Build all known good targets during release builds (default: no)") orelse false;
const tracy_enabled = b.option(bool, "enable_tracy", "Enable tracy client library (default: no)") orelse false;
const use_tree_sitter = b.option(bool, "use_tree_sitter", "Enable tree-sitter (default: yes)") orelse true;
const strip = b.option(bool, "strip", "Disable debug information (default: no)");
const use_llvm = b.option(bool, "use_llvm", "Enable llvm backend (default: none)");
const pie = b.option(bool, "pie", "Produce an executable with position independent code (default: none)");
const renderer = b.option(Renderer, "renderer", "Renderer backend: terminal (TUI, default), d3d11 (GPU on Windows via DirectWrite), gui (GPU on Linux/macOS/Windows via wio+sokol_gfx)") orelse .terminal;
const test_filters = b.option([]const []const u8, "test-filter", "Skip tests that do not match any filter") orelse &[0][]const u8{};
const run_step = b.step("run", "Run the app");
const check_step = b.step("check", "Check the app");
const test_step = b.step("test", "Run unit tests");
const lint_step = b.step("lint", "Run lints");
var version: std.ArrayList(u8) = .empty;
defer version.deinit(b.allocator);
gen_version(b, version.writer(b.allocator)) catch |e| {
if (b.release_mode != .off)
std.debug.panic("gen_version failed: {any}", .{e});
version.clearAndFree(b.allocator);
version.appendSlice(b.allocator, "unknown") catch {};
};
const release = switch (b.release_mode) {
.off => false,
.any => blk: {
b.release_mode = .fast;
break :blk true;
},
else => true,
};
return (if (release) &build_release else &build_development)(
b,
run_step,
check_step,
test_step,
lint_step,
tracy_enabled,
use_tree_sitter,
strip,
use_llvm,
pie,
renderer,
version.items,
all_targets,
test_filters,
);
}
fn build_development(
b: *std.Build,
run_step: *std.Build.Step,
check_step: *std.Build.Step,
test_step: *std.Build.Step,
lint_step: *std.Build.Step,
tracy_enabled: bool,
use_tree_sitter: bool,
strip: ?bool,
use_llvm: ?bool,
pie: ?bool,
renderer: Renderer,
version: []const u8,
_: bool, // all_targets
test_filters: []const []const u8,
) void {
// The gui renderer links system GL/X11 libraries which are not available
// via the musl sysroot, so use the native ABI when building it.
const force_musl = builtin.os.tag == .linux and !tracy_enabled and renderer != .gui;
const target = b.standardTargetOptions(.{ .default_target = .{ .abi = if (force_musl) .musl else null } });
const optimize = b.standardOptimizeOption(.{});
return build_exe(
b,
run_step,
check_step,
test_step,
lint_step,
target,
optimize,
.{},
tracy_enabled,
use_tree_sitter,
strip orelse false,
use_llvm,
pie,
renderer,
version,
test_filters,
);
}
fn build_release(
b: *std.Build,
run_step: *std.Build.Step,
check_step: *std.Build.Step,
test_step: *std.Build.Step,
lint_step: *std.Build.Step,
tracy_enabled: bool,
use_tree_sitter: bool,
_: ?bool, //release builds control strip
use_llvm: ?bool,
pie: ?bool,
_: Renderer, //renderer
version: []const u8,
all_targets: bool,
test_filters: []const []const u8,
) void {
const targets: []const std.Target.Query = if (all_targets) &.{
.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl },
.{ .cpu_arch = .x86, .os_tag = .linux, .abi = .musl },
.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl },
.{ .cpu_arch = .arm, .os_tag = .linux, .abi = .musleabihf },
.{ .cpu_arch = .x86_64, .os_tag = .macos },
.{ .cpu_arch = .aarch64, .os_tag = .macos },
.{ .cpu_arch = .x86_64, .os_tag = .windows },
.{ .cpu_arch = .aarch64, .os_tag = .windows },
.{ .cpu_arch = .x86_64, .os_tag = .freebsd },
.{ .cpu_arch = .aarch64, .os_tag = .freebsd },
} else blk: {
const maybe_triple = b.option(
[]const u8,
"target",
"The CPU architecture, OS, and ABI to build for",
);
const triple = maybe_triple orelse {
const native_target = b.resolveTargetQuery(.{}).result;
break :blk &.{
.{ .cpu_arch = native_target.cpu.arch, .os_tag = native_target.os.tag },
};
};
const selected_target = std.Build.parseTargetQuery(.{
.arch_os_abi = triple,
}) catch |err| switch (err) {
error.ParseFailed => @panic("unknown target"),
};
break :blk &.{
.{ .cpu_arch = selected_target.cpu_arch, .os_tag = selected_target.os_tag, .abi = selected_target.abi },
};
};
const optimize = b.standardOptimizeOption(.{});
const optimize_release = optimize;
const optimize_debug = optimize;
const write_file_step = b.addWriteFiles();
const version_file = write_file_step.add("version", version);
b.getInstallStep().dependOn(&b.addInstallFile(version_file, "version").step);
for (targets) |t| {
const target = b.resolveTargetQuery(t);
var triple = std.mem.splitScalar(u8, t.zigTriple(b.allocator) catch unreachable, '-');
const arch = triple.next() orelse unreachable;
const os = triple.next() orelse unreachable;
const target_path = std.mem.join(b.allocator, "-", &[_][]const u8{ os, arch }) catch unreachable;
const target_path_debug = std.mem.join(b.allocator, "-", &[_][]const u8{ os, arch, "debug" }) catch unreachable;
build_exe(
b,
run_step,
check_step,
test_step,
lint_step,
target,
optimize_release,
.{ .dest_dir = .{ .override = .{ .custom = target_path } } },
tracy_enabled,
use_tree_sitter,
true, // strip release builds
use_llvm,
pie,
.terminal,
version,
test_filters,
);
build_exe(
b,
run_step,
check_step,
test_step,
lint_step,
target,
optimize_debug,
.{ .dest_dir = .{ .override = .{ .custom = target_path_debug } } },
tracy_enabled,
use_tree_sitter,
false, // don't strip debug builds
use_llvm,
pie,
.terminal,
version,
test_filters,
);
if (t.os_tag == .windows) {
build_exe(
b,
run_step,
check_step,
test_step,
lint_step,
target,
optimize_release,
.{ .dest_dir = .{ .override = .{ .custom = target_path } } },
tracy_enabled,
use_tree_sitter,
true, // strip release builds
use_llvm,
pie,
.d3d11,
version,
test_filters,
);
build_exe(
b,
run_step,
check_step,
test_step,
lint_step,
target,
optimize_debug,
.{ .dest_dir = .{ .override = .{ .custom = target_path_debug } } },
tracy_enabled,
use_tree_sitter,
false, // don't strip debug builds
use_llvm,
pie,
.d3d11,
version,
test_filters,
);
}
}
}
pub fn build_exe(
b: *std.Build,
run_step: *std.Build.Step,
check_step: *std.Build.Step,
test_step: *std.Build.Step,
lint_step: *std.Build.Step,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
exe_install_options: std.Build.Step.InstallArtifact.Options,
tracy_enabled: bool,
use_tree_sitter: bool,
strip: bool,
use_llvm: ?bool,
pie: ?bool,
renderer: Renderer,
version: []const u8,
test_filters: []const []const u8,
) void {
const options = b.addOptions();
options.addOption(bool, "enable_tracy", tracy_enabled);
options.addOption(bool, "use_tree_sitter", use_tree_sitter);
options.addOption(bool, "strip", strip);
options.addOption(bool, "gui", renderer != .terminal);
const options_mod = options.createModule();
std.fs.cwd().makeDir(".cache") catch |e| switch (e) {
error.PathAlreadyExists => {},
else => std.debug.panic("makeDir(\".cache\") failed: {any}", .{e}),
};
std.fs.cwd().makeDir(".cache/cdb") catch |e| switch (e) {
error.PathAlreadyExists => {},
else => std.debug.panic("makeDir(\".cache/cdb\") failed: {any}", .{e}),
};
var version_info: std.ArrayList(u8) = .empty;
defer version_info.deinit(b.allocator);
gen_version_info(b, target, version_info.writer(b.allocator), optimize) catch |e| {
if (b.release_mode != .off)
std.debug.panic("gen_version failed: {any}", .{e});
version_info.clearAndFree(b.allocator);
version_info.appendSlice(b.allocator, "unknown") catch {};
};
const wf = b.addWriteFiles();
const version_file = wf.add("version", version);
const version_info_file = wf.add("version_info", version_info.items);
const vaxis_dep = b.dependency("vaxis", .{
.target = target,
.optimize = optimize,
});
const vaxis_mod = vaxis_dep.module("vaxis");
const flags_dep = b.dependency("flags", .{
.target = target,
.optimize = optimize,
});
const dizzy_dep = b.dependency("dizzy", .{
.target = target,
.optimize = optimize,
});
const diffz_dep = b.dependency("diffz", .{
.target = target,
.optimize = optimize,
});
const fuzzig_dep = b.dependency("fuzzig", .{
.target = target,
.optimize = optimize,
});
const thespian_dep = b.dependency("thespian", .{
.target = target,
.optimize = optimize_deps,
.enable_tracy = tracy_enabled,
});
const thespian_mod = thespian_dep.module("thespian");
const cbor_dep = thespian_dep.builder.dependency("cbor", .{
.target = target,
.optimize = optimize_deps,
});
const cbor_mod = cbor_dep.module("cbor");
const tracy_dep = if (tracy_enabled) thespian_dep.builder.dependency("tracy", .{
.target = target,
.optimize = optimize,
}) else undefined;
const tracy_mod = if (tracy_enabled) tracy_dep.module("tracy") else b.createModule(.{
.root_source_file = b.path("src/tracy_noop.zig"),
});
const zeit_dep = b.dependency("zeit", .{
.target = target,
.optimize = optimize,
});
const zeit_mod = zeit_dep.module("zeit");
const themes_dep = b.dependency("themes", .{});
const syntax_dep = b.dependency("syntax", .{
.target = target,
.optimize = optimize_deps,
.use_tree_sitter = use_tree_sitter,
.use_llvm = use_llvm,
});
const syntax_mod = syntax_dep.module("syntax");
const help_mod = b.createModule(.{
.root_source_file = b.path("help.md"),
});
const soft_root_mod = b.createModule(.{
.root_source_file = b.path("src/soft_root.zig"),
.imports = &.{},
});
const time_fmt_mod = b.createModule(.{
.root_source_file = b.path("src/time_fmt.zig"),
});
const config_mod = b.createModule(.{
.root_source_file = b.path("src/config.zig"),
.imports = &.{
.{ .name = "cbor", .module = cbor_mod },
},
});
const gui_config_mod = b.createModule(.{
.root_source_file = b.path("src/gui_config.zig"),
.imports = &.{
.{ .name = "cbor", .module = cbor_mod },
},
});
const file_link_mod = b.createModule(.{
.root_source_file = b.path("src/file_link.zig"),
.imports = &.{
.{ .name = "soft_root", .module = soft_root_mod },
.{ .name = "thespian", .module = thespian_mod },
},
});
const file_type_config_mod = b.createModule(.{
.root_source_file = b.path("src/file_type_config.zig"),
.imports = &.{
.{ .name = "soft_root", .module = soft_root_mod },
.{ .name = "syntax", .module = syntax_mod },
},
});
const lsp_config_mod = b.createModule(.{
.root_source_file = b.path("src/lsp_config.zig"),
.imports = &.{
.{ .name = "soft_root", .module = soft_root_mod },
},
});
const log_mod = b.createModule(.{
.root_source_file = b.path("src/log.zig"),
.imports = &.{
.{ .name = "thespian", .module = thespian_mod },
},
});
const command_mod = b.createModule(.{
.root_source_file = b.path("src/command.zig"),
.imports = &.{
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "log", .module = log_mod },
.{ .name = "cbor", .module = cbor_mod },
},
});
const EventHandler_mod = b.createModule(.{
.root_source_file = b.path("src/EventHandler.zig"),
.imports = &.{
.{ .name = "thespian", .module = thespian_mod },
},
});
const VcsStatus_mod = b.createModule(.{
.root_source_file = b.path("src/VcsStatus.zig"),
.imports = &.{},
});
const VcsBlame_mod = b.createModule(.{
.root_source_file = b.path("src/VcsBlame.zig"),
.imports = &.{},
});
const color_mod = b.createModule(.{
.root_source_file = b.path("src/color.zig"),
});
const bin_path_mod = b.createModule(.{
.root_source_file = b.path("src/bin_path.zig"),
});
const snippet_mod = b.createModule(.{
.root_source_file = b.path("src/snippet.zig"),
});
const lsp_types_mod = b.createModule(.{
.root_source_file = b.path("src/lsp_types.zig"),
});
const TypedInt_mod = b.createModule(.{
.root_source_file = b.path("src/TypedInt.zig"),
.imports = &.{
.{ .name = "cbor", .module = cbor_mod },
},
});
const Buffer_mod = b.createModule(.{
.root_source_file = b.path("src/buffer/Buffer.zig"),
.imports = &.{
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "TypedInt", .module = TypedInt_mod },
.{ .name = "vaxis", .module = vaxis_mod },
.{ .name = "file_type_config", .module = file_type_config_mod },
.{ .name = "VcsBlame", .module = VcsBlame_mod },
},
});
const input_mod = b.createModule(.{
.root_source_file = b.path("src/renderer/vaxis/input.zig"),
.imports = &.{
.{ .name = "vaxis", .module = vaxis_mod },
},
});
const tui_renderer_mod = b.createModule(.{
.root_source_file = b.path("src/renderer/vaxis/renderer.zig"),
.imports = &.{
.{ .name = "vaxis", .module = vaxis_mod },
.{ .name = "input", .module = input_mod },
.{ .name = "theme", .module = themes_dep.module("theme") },
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "log", .module = log_mod },
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "Buffer", .module = Buffer_mod },
.{ .name = "color", .module = color_mod },
},
});
const renderer_mod = blk: {
switch (renderer) {
.terminal => break :blk tui_renderer_mod,
.d3d11 => {
const win32_dep = b.lazyDependency("win32", .{}) orelse break :blk tui_renderer_mod;
const win32_mod = win32_dep.module("win32");
const gui_mod = b.createModule(.{
.root_source_file = b.path("src/win32/gui.zig"),
.imports = &.{
.{ .name = "build_options", .module = options_mod },
.{ .name = "soft_root", .module = soft_root_mod },
.{ .name = "win32", .module = win32_mod },
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "input", .module = input_mod },
.{ .name = "vaxis", .module = vaxis_mod },
.{ .name = "color", .module = color_mod },
.{ .name = "gui_config", .module = gui_config_mod },
.{ .name = "tracy", .module = tracy_mod },
},
});
gui_mod.addIncludePath(b.path("src/win32"));
const mod = b.createModule(.{
.root_source_file = b.path("src/renderer/win32/renderer.zig"),
.imports = &.{
.{ .name = "theme", .module = themes_dep.module("theme") },
.{ .name = "soft_root", .module = soft_root_mod },
.{ .name = "win32", .module = win32_mod },
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "input", .module = input_mod },
.{ .name = "gui", .module = gui_mod },
.{ .name = "tuirenderer", .module = tui_renderer_mod },
.{ .name = "vaxis", .module = vaxis_mod },
},
});
break :blk mod;
},
.gui => {
const wio_dep = b.lazyDependency("wio", .{
.target = target,
.optimize = optimize_deps,
.enable_opengl = true,
}) orelse break :blk tui_renderer_mod;
const sokol_dep = b.lazyDependency("sokol", .{
.target = target,
.optimize = optimize_deps,
.gl = true,
// Requires system packages: libgl-dev libx11-dev libxi-dev
// libxcursor-dev libasound2-dev (Debian/Ubuntu)
}) orelse break :blk tui_renderer_mod;
const wio_mod = wio_dep.module("wio");
const sokol_mod = sokol_dep.module("sokol");
const gui_xy_mod = b.createModule(.{ .root_source_file = b.path("src/gui/xy.zig") });
const gui_cell_mod = b.createModule(.{ .root_source_file = b.path("src/gui/Cell.zig") });
const gui_glyph_cache_mod = b.createModule(.{ .root_source_file = b.path("src/gui/GlyphIndexCache.zig") });
const gui_xterm_mod = b.createModule(.{ .root_source_file = b.path("src/gui/xterm.zig") });
const stub_rasterizer_mod = b.createModule(.{
.root_source_file = b.path("src/gui/rasterizer/stub.zig"),
.imports = &.{
.{ .name = "xy", .module = gui_xy_mod },
},
});
const gpu_mod = b.createModule(.{
.root_source_file = b.path("src/gui/gpu/gpu.zig"),
.imports = &.{
.{ .name = "sokol", .module = sokol_mod },
.{ .name = "rasterizer", .module = stub_rasterizer_mod },
.{ .name = "xy", .module = gui_xy_mod },
.{ .name = "Cell", .module = gui_cell_mod },
.{ .name = "GlyphIndexCache", .module = gui_glyph_cache_mod },
},
});
const app_mod = b.createModule(.{
.root_source_file = b.path("src/gui/wio/app.zig"),
.imports = &.{
.{ .name = "wio", .module = wio_mod },
.{ .name = "sokol", .module = sokol_mod },
.{ .name = "gpu", .module = gpu_mod },
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "vaxis", .module = vaxis_mod },
.{ .name = "xterm", .module = gui_xterm_mod },
},
});
const mod = b.createModule(.{
.root_source_file = b.path("src/renderer/gui/renderer.zig"),
.imports = &.{
.{ .name = "theme", .module = themes_dep.module("theme") },
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "input", .module = input_mod },
.{ .name = "app", .module = app_mod },
.{ .name = "tuirenderer", .module = tui_renderer_mod },
.{ .name = "vaxis", .module = vaxis_mod },
},
});
break :blk mod;
},
}
};
const keybind_mod = b.createModule(.{
.root_source_file = b.path("src/keybind/keybind.zig"),
.imports = &.{
.{ .name = "soft_root", .module = soft_root_mod },
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "command", .module = command_mod },
.{ .name = "EventHandler", .module = EventHandler_mod },
.{ .name = "input", .module = input_mod },
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "Buffer", .module = Buffer_mod },
.{ .name = "config", .module = config_mod },
},
});
const keybind_test_run_cmd = blk: {
const tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/keybind/keybind.zig"),
.target = target,
.optimize = optimize,
}),
});
tests.root_module.addImport("cbor", cbor_mod);
tests.root_module.addImport("command", command_mod);
tests.root_module.addImport("EventHandler", EventHandler_mod);
tests.root_module.addImport("input", input_mod);
tests.root_module.addImport("thespian", thespian_mod);
tests.root_module.addImport("log", log_mod);
tests.root_module.addImport("Buffer", Buffer_mod);
tests.root_module.addImport("config", config_mod);
// b.installArtifact(tests);
break :blk b.addRunArtifact(tests);
};
const shell_mod = b.createModule(.{
.root_source_file = b.path("src/shell.zig"),
.imports = &.{
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "log", .module = log_mod },
},
});
const git_mod = b.createModule(.{
.root_source_file = b.path("src/git.zig"),
.imports = &.{
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "shell", .module = shell_mod },
.{ .name = "bin_path", .module = bin_path_mod },
},
});
const ripgrep_mod = b.createModule(.{
.root_source_file = b.path("src/ripgrep.zig"),
.imports = &.{
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "log", .module = log_mod },
.{ .name = "bin_path", .module = bin_path_mod },
},
});
const location_history_mod = b.createModule(.{
.root_source_file = b.path("src/location_history.zig"),
.imports = &.{
.{ .name = "thespian", .module = thespian_mod },
},
});
const project_manager_mod = b.createModule(.{
.root_source_file = b.path("src/project_manager.zig"),
.imports = &.{
.{ .name = "soft_root", .module = soft_root_mod },
.{ .name = "log", .module = log_mod },
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "Buffer", .module = Buffer_mod },
.{ .name = "tracy", .module = tracy_mod },
.{ .name = "file_type_config", .module = file_type_config_mod },
.{ .name = "lsp_config", .module = lsp_config_mod },
.{ .name = "dizzy", .module = dizzy_dep.module("dizzy") },
.{ .name = "fuzzig", .module = fuzzig_dep.module("fuzzig") },
.{ .name = "git", .module = git_mod },
.{ .name = "VcsStatus", .module = VcsStatus_mod },
},
});
const diff_mod = b.createModule(.{
.root_source_file = b.path("src/diff.zig"),
.imports = &.{
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "Buffer", .module = Buffer_mod },
.{ .name = "tracy", .module = tracy_mod },
.{ .name = "dizzy", .module = dizzy_dep.module("dizzy") },
.{ .name = "diffz", .module = diffz_dep.module("diffz") },
.{ .name = "log", .module = log_mod },
.{ .name = "cbor", .module = cbor_mod },
},
});
const text_manip_mod = b.createModule(.{
.root_source_file = b.path("src/text_manip.zig"),
.imports = &.{},
});
const tui_mod = b.createModule(.{
.root_source_file = b.path("src/tui/tui.zig"),
.imports = &.{
.{ .name = "soft_root", .module = soft_root_mod },
.{ .name = "file_link", .module = file_link_mod },
.{ .name = "renderer", .module = renderer_mod },
.{ .name = "input", .module = input_mod },
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "config", .module = config_mod },
.{ .name = "gui_config", .module = gui_config_mod },
.{ .name = "file_type_config", .module = file_type_config_mod },
.{ .name = "lsp_config", .module = lsp_config_mod },
.{ .name = "log", .module = log_mod },
.{ .name = "command", .module = command_mod },
.{ .name = "EventHandler", .module = EventHandler_mod },
.{ .name = "location_history", .module = location_history_mod },
.{ .name = "project_manager", .module = project_manager_mod },
.{ .name = "syntax", .module = syntax_mod },
.{ .name = "text_manip", .module = text_manip_mod },
.{ .name = "Buffer", .module = Buffer_mod },
.{ .name = "keybind", .module = keybind_mod },
.{ .name = "shell", .module = shell_mod },
.{ .name = "ripgrep", .module = ripgrep_mod },
.{ .name = "theme", .module = themes_dep.module("theme") },
.{ .name = "themes", .module = themes_dep.module("themes") },
.{ .name = "tracy", .module = tracy_mod },
.{ .name = "build_options", .module = options_mod },
.{ .name = "color", .module = color_mod },
.{ .name = "diff", .module = diff_mod },
.{ .name = "help.md", .module = help_mod },
.{ .name = "fuzzig", .module = fuzzig_dep.module("fuzzig") },
.{ .name = "zeit", .module = zeit_mod },
.{ .name = "VcsStatus", .module = VcsStatus_mod },
.{ .name = "bin_path", .module = bin_path_mod },
.{ .name = "snippet", .module = snippet_mod },
.{ .name = "lsp_types", .module = lsp_types_mod },
.{ .name = "time_fmt", .module = time_fmt_mod },
},
});
const exe_name = if (renderer != .terminal) "flow-gui" else "flow";
const exe = b.addExecutable(.{
.name = exe_name,
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.strip = strip,
}),
.win32_manifest = b.path("src/win32/flow.manifest"),
});
if (use_llvm) |value| {
exe.use_llvm = value;
exe.use_lld = value;
} else if (target.result.os.tag != .macos) {
exe.use_llvm = true;
exe.use_lld = true;
}
if (pie) |value| exe.pie = value;
exe.root_module.addImport("build_options", options_mod);
exe.root_module.addImport("soft_root", soft_root_mod);
exe.root_module.addImport("file_link", file_link_mod);
exe.root_module.addImport("flags", flags_dep.module("flags"));
exe.root_module.addImport("cbor", cbor_mod);
exe.root_module.addImport("config", config_mod);
exe.root_module.addImport("text_manip", text_manip_mod);
exe.root_module.addImport("Buffer", Buffer_mod);
exe.root_module.addImport("tui", tui_mod);
exe.root_module.addImport("thespian", thespian_mod);
exe.root_module.addImport("log", log_mod);
exe.root_module.addImport("tracy", tracy_mod);
exe.root_module.addImport("renderer", renderer_mod);
exe.root_module.addImport("input", input_mod);
exe.root_module.addImport("syntax", syntax_mod);
exe.root_module.addImport("file_type_config", file_type_config_mod);
exe.root_module.addImport("color", color_mod);
exe.root_module.addImport("bin_path", bin_path_mod);
exe.root_module.addImport("version", b.createModule(.{ .root_source_file = version_file }));
exe.root_module.addImport("version_info", b.createModule(.{ .root_source_file = version_info_file }));
if (target.result.os.tag == .windows) {
exe.addWin32ResourceFile(.{
.file = b.path("src/win32/flow.rc"),
});
if (renderer != .terminal) {
exe.subsystem = .Windows;
}
}
const exe_install = b.addInstallArtifact(exe, exe_install_options);
b.getInstallStep().dependOn(&exe_install.step);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
run_step.dependOn(&run_cmd.step);
const check_exe = b.addExecutable(.{
.name = exe_name,
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
check_exe.root_module.addImport("build_options", options_mod);
check_exe.root_module.addImport("file_link", file_link_mod);
check_exe.root_module.addImport("soft_root", soft_root_mod);
check_exe.root_module.addImport("flags", flags_dep.module("flags"));
check_exe.root_module.addImport("cbor", cbor_mod);
check_exe.root_module.addImport("config", config_mod);
check_exe.root_module.addImport("text_manip", text_manip_mod);
check_exe.root_module.addImport("Buffer", Buffer_mod);
check_exe.root_module.addImport("tui", tui_mod);
check_exe.root_module.addImport("thespian", thespian_mod);
check_exe.root_module.addImport("log", log_mod);
check_exe.root_module.addImport("tracy", tracy_mod);
check_exe.root_module.addImport("renderer", renderer_mod);
check_exe.root_module.addImport("input", input_mod);
check_exe.root_module.addImport("syntax", syntax_mod);
check_exe.root_module.addImport("file_type_config", file_type_config_mod);
check_exe.root_module.addImport("color", color_mod);
check_exe.root_module.addImport("bin_path", bin_path_mod);
check_exe.root_module.addImport("version", b.createModule(.{ .root_source_file = version_file }));
check_exe.root_module.addImport("version_info", b.createModule(.{ .root_source_file = version_info_file }));
check_step.dependOn(&check_exe.step);
const tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("test/tests.zig"),
.target = target,
.optimize = optimize,
.strip = strip,
}),
.use_llvm = use_llvm,
.use_lld = use_llvm,
.filters = test_filters,
});
tests.pie = pie;
tests.root_module.addImport("build_options", options_mod);
tests.root_module.addImport("soft_root", soft_root_mod);
tests.root_module.addImport("file_link", file_link_mod);
tests.root_module.addImport("log", log_mod);
tests.root_module.addImport("Buffer", Buffer_mod);
tests.root_module.addImport("color", color_mod);
tests.root_module.addImport("tui", tui_mod);
tests.root_module.addImport("command", command_mod);
tests.root_module.addImport("project_manager", project_manager_mod);
// b.installArtifact(tests);
const test_run_cmd = b.addRunArtifact(tests);
test_step.dependOn(&test_run_cmd.step);
test_step.dependOn(&keybind_test_run_cmd.step);
const lints = b.addFmt(.{
.paths = &.{ "src", "test", "build.zig" },
.check = true,
});
lint_step.dependOn(&lints.step);
b.default_step.dependOn(lint_step);
}
fn gen_version_info(
b: *std.Build,
target: std.Build.ResolvedTarget,
writer: anytype,
optimize: std.builtin.OptimizeMode,
) !void {
var code: u8 = 0;
const describe = try b.runAllowFail(&[_][]const u8{ "git", "describe", "--always", "--tags" }, &code, .Ignore);
const date_ = try b.runAllowFail(&[_][]const u8{ "git", "show", "-s", "--format=%ci", "HEAD" }, &code, .Ignore);
const branch_ = try b.runAllowFail(&[_][]const u8{ "git", "rev-parse", "--abbrev-ref", "HEAD" }, &code, .Ignore);
const branch = std.mem.trimRight(u8, branch_, "\r\n ");
const tracking_branch_ = blk: {
var buf: std.ArrayList(u8) = .empty;
defer buf.deinit(b.allocator);
try buf.appendSlice(b.allocator, branch);
try buf.appendSlice(b.allocator, "@{upstream}");
break :blk (b.runAllowFail(&[_][]const u8{ "git", "rev-parse", "--abbrev-ref", buf.items }, &code, .Ignore) catch "");
};
const tracking_remote_name = if (std.mem.indexOfScalar(u8, tracking_branch_, '/')) |pos| tracking_branch_[0..pos] else "";
const tracking_remote_ = if (tracking_remote_name.len > 0) blk: {
var remote_config_path: std.ArrayList(u8) = .empty;
defer remote_config_path.deinit(b.allocator);
try remote_config_path.writer(b.allocator).print("remote.{s}.url", .{tracking_remote_name});
break :blk b.runAllowFail(&[_][]const u8{ "git", "config", remote_config_path.items }, &code, .Ignore) catch "(remote not found)";
} else "";
const remote_ = b.runAllowFail(&[_][]const u8{ "git", "config", "remote.origin.url" }, &code, .Ignore) catch "(origin not found)";
const log_ = b.runAllowFail(&[_][]const u8{ "git", "log", "--pretty=oneline", "@{u}..." }, &code, .Ignore) catch "";
const diff_ = b.runAllowFail(&[_][]const u8{ "git", "diff", "--stat", "--patch", "HEAD" }, &code, .Ignore) catch "(git diff failed)";
const version = std.mem.trimRight(u8, describe, "\r\n ");
const date = std.mem.trimRight(u8, date_, "\r\n ");
const tracking_branch = std.mem.trimRight(u8, tracking_branch_, "\r\n ");
const tracking_remote = std.mem.trimRight(u8, tracking_remote_, "\r\n ");
const remote = std.mem.trimRight(u8, remote_, "\r\n ");
const base_commit_ = b.runAllowFail(&[_][]const u8{ "git", "merge-base", branch, tracking_branch }, &code, .Ignore) catch "";
const base_commit = std.mem.trimRight(u8, base_commit_, "\r\n ");
const describe_base_commit_ = try b.runAllowFail(&[_][]const u8{ "git", "describe", "--always", "--tags", base_commit }, &code, .Ignore);
const describe_base_commit = std.mem.trimRight(u8, describe_base_commit_, "\r\n ");
const log = std.mem.trimRight(u8, log_, "\r\n ");
const diff = std.mem.trimRight(u8, diff_, "\r\n ");
const target_triple = try target.result.zigTriple(b.allocator);
try writer.print("Flow Control: a programmer's text editor\n\nversion: {s}{s}\ncommitted: {s}\ntarget: {s}\n", .{
version,
if (diff.len > 0) "-dirty" else "",
date,
target_triple,
});
if (branch.len > 0) if (tracking_branch.len > 0)
try writer.print("branch: {s} tracking {s} at {s}\n", .{ branch, tracking_branch, tracking_remote })
else
try writer.print("branch: {s} at {s}\n", .{ branch, remote });
try writer.print("built-with: zig {s} ({t})\n", .{ builtin.zig_version_string, builtin.zig_backend });
try writer.print("build-mode: {t}\n", .{optimize});
if (log.len > 0)
try writer.print("\nbranched off {s} @ {s} with the following diverging commits:\n{s}\n", .{ tracking_branch, describe_base_commit, log });
if (diff.len > 0)
try writer.print("\nwith the following uncommited changes:\n\n{s}\n", .{diff});
}
fn gen_version(b: *std.Build, writer: anytype) !void {
var code: u8 = 0;
const describe = try b.runAllowFail(&[_][]const u8{ "git", "describe", "--always", "--tags" }, &code, .Ignore);
const diff_ = try b.runAllowFail(&[_][]const u8{ "git", "diff", "--stat", "--patch", "HEAD" }, &code, .Ignore);
const diff = std.mem.trimRight(u8, diff_, "\r\n ");
const version = std.mem.trimRight(u8, describe, "\r\n ");
try writer.print("{s}{s}", .{ version, if (diff.len > 0) "-dirty" else "" });
}