mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
Merge pull request #1461 from mitchellh/build-change
build: support multiple entrypoints, get bench exes building again, per-object build options
This commit is contained in:
106
build.zig
106
build.zig
@ -9,7 +9,8 @@ const font = @import("src/font/main.zig");
|
|||||||
const renderer = @import("src/renderer.zig");
|
const renderer = @import("src/renderer.zig");
|
||||||
const terminfo = @import("src/terminfo/main.zig");
|
const terminfo = @import("src/terminfo/main.zig");
|
||||||
const config_vim = @import("src/config/vim.zig");
|
const config_vim = @import("src/config/vim.zig");
|
||||||
const BuildConfig = @import("src/build_config.zig").BuildConfig;
|
const build_config = @import("src/build_config.zig");
|
||||||
|
const BuildConfig = build_config.BuildConfig;
|
||||||
const WasmTarget = @import("src/os/wasm/target.zig").Target;
|
const WasmTarget = @import("src/os/wasm/target.zig").Target;
|
||||||
const LibtoolStep = @import("src/build/LibtoolStep.zig");
|
const LibtoolStep = @import("src/build/LibtoolStep.zig");
|
||||||
const LipoStep = @import("src/build/LipoStep.zig");
|
const LipoStep = @import("src/build/LipoStep.zig");
|
||||||
@ -159,7 +160,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
"If not specified, git will be used. This must be a semantic version.",
|
"If not specified, git will be used. This must be a semantic version.",
|
||||||
);
|
);
|
||||||
|
|
||||||
const version: std.SemanticVersion = if (version_string) |v|
|
config.version = if (version_string) |v|
|
||||||
try std.SemanticVersion.parse(v)
|
try std.SemanticVersion.parse(v)
|
||||||
else version: {
|
else version: {
|
||||||
const vsn = try Version.detect(b);
|
const vsn = try Version.detect(b);
|
||||||
@ -198,7 +199,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
|
|
||||||
// Help exe. This must be run before any dependent executables because
|
// Help exe. This must be run before any dependent executables because
|
||||||
// otherwise the build will be cached without emit. That's clunky but meh.
|
// otherwise the build will be cached without emit. That's clunky but meh.
|
||||||
if (emit_helpgen) addHelp(b, null);
|
if (emit_helpgen) try addHelp(b, null, config);
|
||||||
|
|
||||||
// Add our benchmarks
|
// Add our benchmarks
|
||||||
try benchSteps(b, target, optimize, config, emit_bench);
|
try benchSteps(b, target, optimize, config, emit_bench);
|
||||||
@ -211,19 +212,8 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
}) else null;
|
}) else null;
|
||||||
|
|
||||||
const exe_options = b.addOptions();
|
|
||||||
config.addOptions(exe_options);
|
|
||||||
exe_options.addOption(std.SemanticVersion, "app_version", version);
|
|
||||||
exe_options.addOption([:0]const u8, "app_version_string", try std.fmt.allocPrintZ(
|
|
||||||
b.allocator,
|
|
||||||
"{}",
|
|
||||||
.{version},
|
|
||||||
));
|
|
||||||
|
|
||||||
// Exe
|
// Exe
|
||||||
if (exe_) |exe| {
|
if (exe_) |exe| {
|
||||||
exe.root_module.addOptions("build_options", exe_options);
|
|
||||||
|
|
||||||
// Add the shared dependencies
|
// Add the shared dependencies
|
||||||
_ = try addDeps(b, exe, config);
|
_ = try addDeps(b, exe, config);
|
||||||
|
|
||||||
@ -428,7 +418,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Documenation
|
// Documenation
|
||||||
if (emit_docs) buildDocumentation(b, version);
|
if (emit_docs) try buildDocumentation(b, config);
|
||||||
|
|
||||||
// App (Linux)
|
// App (Linux)
|
||||||
if (target.result.os.tag == .linux and config.app_runtime != .none) {
|
if (target.result.os.tag == .linux and config.app_runtime != .none) {
|
||||||
@ -464,7 +454,6 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.target = target,
|
.target = target,
|
||||||
});
|
});
|
||||||
lib.root_module.addOptions("build_options", exe_options);
|
|
||||||
_ = try addDeps(b, lib, config);
|
_ = try addDeps(b, lib, config);
|
||||||
|
|
||||||
const lib_install = b.addInstallLibFile(
|
const lib_install = b.addInstallLibFile(
|
||||||
@ -482,7 +471,6 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.target = target,
|
.target = target,
|
||||||
});
|
});
|
||||||
lib.root_module.addOptions("build_options", exe_options);
|
|
||||||
_ = try addDeps(b, lib, config);
|
_ = try addDeps(b, lib, config);
|
||||||
|
|
||||||
const lib_install = b.addInstallLibFile(
|
const lib_install = b.addInstallLibFile(
|
||||||
@ -510,7 +498,6 @@ pub fn build(b: *std.Build) !void {
|
|||||||
b,
|
b,
|
||||||
optimize,
|
optimize,
|
||||||
config,
|
config,
|
||||||
exe_options,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add our library to zig-out
|
// Add our library to zig-out
|
||||||
@ -526,7 +513,6 @@ pub fn build(b: *std.Build) !void {
|
|||||||
null,
|
null,
|
||||||
optimize,
|
optimize,
|
||||||
config,
|
config,
|
||||||
exe_options,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add our library to zig-out
|
// Add our library to zig-out
|
||||||
@ -542,7 +528,6 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.simulator,
|
.simulator,
|
||||||
optimize,
|
optimize,
|
||||||
config,
|
config,
|
||||||
exe_options,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add our library to zig-out
|
// Add our library to zig-out
|
||||||
@ -605,6 +590,11 @@ pub fn build(b: *std.Build) !void {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Whether we're using wasm shared memory. Some behaviors change.
|
||||||
|
// For now we require this but I wanted to make the code handle both
|
||||||
|
// up front.
|
||||||
|
const wasm_shared: bool = true;
|
||||||
|
|
||||||
// Modify our build configuration for wasm builds.
|
// Modify our build configuration for wasm builds.
|
||||||
const wasm_config: BuildConfig = config: {
|
const wasm_config: BuildConfig = config: {
|
||||||
var copy = config;
|
var copy = config;
|
||||||
@ -616,26 +606,19 @@ pub fn build(b: *std.Build) !void {
|
|||||||
// Backends that are fixed for wasm
|
// Backends that are fixed for wasm
|
||||||
copy.font_backend = .web_canvas;
|
copy.font_backend = .web_canvas;
|
||||||
|
|
||||||
|
// Wasm-specific options
|
||||||
|
copy.wasm_shared = wasm_shared;
|
||||||
|
copy.wasm_target = wasm_target;
|
||||||
|
|
||||||
break :config copy;
|
break :config copy;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Whether we're using wasm shared memory. Some behaviors change.
|
|
||||||
// For now we require this but I wanted to make the code handle both
|
|
||||||
// up front.
|
|
||||||
const wasm_shared: bool = true;
|
|
||||||
exe_options.addOption(bool, "wasm_shared", wasm_shared);
|
|
||||||
|
|
||||||
// We want to support alternate wasm targets in the future (i.e.
|
|
||||||
// server side) so we have this now although its hardcoded.
|
|
||||||
exe_options.addOption(WasmTarget, "wasm_target", wasm_target);
|
|
||||||
|
|
||||||
const wasm = b.addSharedLibrary(.{
|
const wasm = b.addSharedLibrary(.{
|
||||||
.name = "ghostty-wasm",
|
.name = "ghostty-wasm",
|
||||||
.root_source_file = .{ .path = "src/main_wasm.zig" },
|
.root_source_file = .{ .path = "src/main_wasm.zig" },
|
||||||
.target = b.resolveTargetQuery(wasm_crosstarget),
|
.target = b.resolveTargetQuery(wasm_crosstarget),
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
wasm.root_module.addOptions("build_options", exe_options);
|
|
||||||
|
|
||||||
// So that we can use web workers with our wasm binary
|
// So that we can use web workers with our wasm binary
|
||||||
wasm.import_memory = true;
|
wasm.import_memory = true;
|
||||||
@ -666,7 +649,6 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.target = b.resolveTargetQuery(wasm_crosstarget),
|
.target = b.resolveTargetQuery(wasm_crosstarget),
|
||||||
});
|
});
|
||||||
|
|
||||||
main_test.root_module.addOptions("build_options", exe_options);
|
|
||||||
_ = try addDeps(b, main_test, wasm_config);
|
_ = try addDeps(b, main_test, wasm_config);
|
||||||
test_step.dependOn(&main_test.step);
|
test_step.dependOn(&main_test.step);
|
||||||
}
|
}
|
||||||
@ -709,7 +691,6 @@ pub fn build(b: *std.Build) !void {
|
|||||||
copy.static = true;
|
copy.static = true;
|
||||||
break :config copy;
|
break :config copy;
|
||||||
});
|
});
|
||||||
main_test.root_module.addOptions("build_options", exe_options);
|
|
||||||
|
|
||||||
const test_run = b.addRunArtifact(main_test);
|
const test_run = b.addRunArtifact(main_test);
|
||||||
test_step.dependOn(&test_run.step);
|
test_step.dependOn(&test_run.step);
|
||||||
@ -755,7 +736,6 @@ fn createMacOSLib(
|
|||||||
b: *std.Build,
|
b: *std.Build,
|
||||||
optimize: std.builtin.OptimizeMode,
|
optimize: std.builtin.OptimizeMode,
|
||||||
config: BuildConfig,
|
config: BuildConfig,
|
||||||
exe_options: *std.Build.Step.Options,
|
|
||||||
) !struct { *std.Build.Step, std.Build.LazyPath } {
|
) !struct { *std.Build.Step, std.Build.LazyPath } {
|
||||||
// Modify our build configuration for macOS builds.
|
// Modify our build configuration for macOS builds.
|
||||||
const macos_config: BuildConfig = config: {
|
const macos_config: BuildConfig = config: {
|
||||||
@ -781,7 +761,6 @@ fn createMacOSLib(
|
|||||||
});
|
});
|
||||||
lib.bundle_compiler_rt = true;
|
lib.bundle_compiler_rt = true;
|
||||||
lib.linkLibC();
|
lib.linkLibC();
|
||||||
lib.root_module.addOptions("build_options", exe_options);
|
|
||||||
|
|
||||||
// Create a single static lib with all our dependencies merged
|
// Create a single static lib with all our dependencies merged
|
||||||
var lib_list = try addDeps(b, lib, macos_config);
|
var lib_list = try addDeps(b, lib, macos_config);
|
||||||
@ -810,7 +789,6 @@ fn createMacOSLib(
|
|||||||
});
|
});
|
||||||
lib.bundle_compiler_rt = true;
|
lib.bundle_compiler_rt = true;
|
||||||
lib.linkLibC();
|
lib.linkLibC();
|
||||||
lib.root_module.addOptions("build_options", exe_options);
|
|
||||||
|
|
||||||
// Create a single static lib with all our dependencies merged
|
// Create a single static lib with all our dependencies merged
|
||||||
var lib_list = try addDeps(b, lib, macos_config);
|
var lib_list = try addDeps(b, lib, macos_config);
|
||||||
@ -847,7 +825,6 @@ fn createIOSLib(
|
|||||||
abi: ?std.Target.Abi,
|
abi: ?std.Target.Abi,
|
||||||
optimize: std.builtin.OptimizeMode,
|
optimize: std.builtin.OptimizeMode,
|
||||||
config: BuildConfig,
|
config: BuildConfig,
|
||||||
exe_options: *std.Build.Step.Options,
|
|
||||||
) !struct { *std.Build.Step, std.Build.LazyPath } {
|
) !struct { *std.Build.Step, std.Build.LazyPath } {
|
||||||
const ios_config: BuildConfig = config: {
|
const ios_config: BuildConfig = config: {
|
||||||
var copy = config;
|
var copy = config;
|
||||||
@ -868,7 +845,6 @@ fn createIOSLib(
|
|||||||
});
|
});
|
||||||
lib.bundle_compiler_rt = true;
|
lib.bundle_compiler_rt = true;
|
||||||
lib.linkLibC();
|
lib.linkLibC();
|
||||||
lib.root_module.addOptions("build_options", exe_options);
|
|
||||||
|
|
||||||
// Create a single static lib with all our dependencies merged
|
// Create a single static lib with all our dependencies merged
|
||||||
var lib_list = try addDeps(b, lib, ios_config);
|
var lib_list = try addDeps(b, lib, ios_config);
|
||||||
@ -895,6 +871,11 @@ fn addDeps(
|
|||||||
step: *std.Build.Step.Compile,
|
step: *std.Build.Step.Compile,
|
||||||
config: BuildConfig,
|
config: BuildConfig,
|
||||||
) !LazyPathList {
|
) !LazyPathList {
|
||||||
|
// All object targets get access to a standard build_options module
|
||||||
|
const exe_options = b.addOptions();
|
||||||
|
try config.addOptions(exe_options);
|
||||||
|
step.root_module.addOptions("build_options", exe_options);
|
||||||
|
|
||||||
var static_libs = LazyPathList.init(b.allocator);
|
var static_libs = LazyPathList.init(b.allocator);
|
||||||
errdefer static_libs.deinit();
|
errdefer static_libs.deinit();
|
||||||
|
|
||||||
@ -1126,7 +1107,7 @@ fn addDeps(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addHelp(b, step);
|
try addHelp(b, step, config);
|
||||||
|
|
||||||
return static_libs;
|
return static_libs;
|
||||||
}
|
}
|
||||||
@ -1135,7 +1116,8 @@ fn addDeps(
|
|||||||
fn addHelp(
|
fn addHelp(
|
||||||
b: *std.Build,
|
b: *std.Build,
|
||||||
step_: ?*std.Build.Step.Compile,
|
step_: ?*std.Build.Step.Compile,
|
||||||
) void {
|
config: BuildConfig,
|
||||||
|
) !void {
|
||||||
// Our static state between runs. We memoize our help strings
|
// Our static state between runs. We memoize our help strings
|
||||||
// so that we only execute the help generation once.
|
// so that we only execute the help generation once.
|
||||||
const HelpState = struct {
|
const HelpState = struct {
|
||||||
@ -1150,6 +1132,15 @@ fn addHelp(
|
|||||||
});
|
});
|
||||||
if (step_ == null) b.installArtifact(help_exe);
|
if (step_ == null) b.installArtifact(help_exe);
|
||||||
|
|
||||||
|
const help_config = config: {
|
||||||
|
var copy = config;
|
||||||
|
copy.exe_entrypoint = .helpgen;
|
||||||
|
break :config copy;
|
||||||
|
};
|
||||||
|
const options = b.addOptions();
|
||||||
|
try help_config.addOptions(options);
|
||||||
|
help_exe.root_module.addOptions("build_options", options);
|
||||||
|
|
||||||
const help_run = b.addRunArtifact(help_exe);
|
const help_run = b.addRunArtifact(help_exe);
|
||||||
HelpState.generated = help_run.captureStdOut();
|
HelpState.generated = help_run.captureStdOut();
|
||||||
break :strings HelpState.generated.?;
|
break :strings HelpState.generated.?;
|
||||||
@ -1166,8 +1157,8 @@ fn addHelp(
|
|||||||
/// Generate documentation (manpages, etc.) from help strings
|
/// Generate documentation (manpages, etc.) from help strings
|
||||||
fn buildDocumentation(
|
fn buildDocumentation(
|
||||||
b: *std.Build,
|
b: *std.Build,
|
||||||
version: std.SemanticVersion,
|
config: BuildConfig,
|
||||||
) void {
|
) !void {
|
||||||
const manpages = [_]struct {
|
const manpages = [_]struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
section: []const u8,
|
section: []const u8,
|
||||||
@ -1179,15 +1170,22 @@ fn buildDocumentation(
|
|||||||
inline for (manpages) |manpage| {
|
inline for (manpages) |manpage| {
|
||||||
const generate_markdown = b.addExecutable(.{
|
const generate_markdown = b.addExecutable(.{
|
||||||
.name = "mdgen_" ++ manpage.name ++ "_" ++ manpage.section,
|
.name = "mdgen_" ++ manpage.name ++ "_" ++ manpage.section,
|
||||||
.root_source_file = .{
|
.root_source_file = .{ .path = "src/main.zig" },
|
||||||
.path = "src/mdgen_" ++ manpage.name ++ "_" ++ manpage.section ++ ".zig",
|
|
||||||
},
|
|
||||||
.target = b.host,
|
.target = b.host,
|
||||||
});
|
});
|
||||||
addHelp(b, generate_markdown);
|
try addHelp(b, generate_markdown, config);
|
||||||
|
|
||||||
|
const gen_config = config: {
|
||||||
|
var copy = config;
|
||||||
|
copy.exe_entrypoint = @field(
|
||||||
|
build_config.ExeEntrypoint,
|
||||||
|
"mdgen_" ++ manpage.name ++ "_" ++ manpage.section,
|
||||||
|
);
|
||||||
|
break :config copy;
|
||||||
|
};
|
||||||
|
|
||||||
const generate_markdown_options = b.addOptions();
|
const generate_markdown_options = b.addOptions();
|
||||||
generate_markdown_options.addOption(std.SemanticVersion, "version", version);
|
try gen_config.addOptions(generate_markdown_options);
|
||||||
generate_markdown.root_module.addOptions("build_options", generate_markdown_options);
|
generate_markdown.root_module.addOptions("build_options", generate_markdown_options);
|
||||||
|
|
||||||
const generate_markdown_step = b.addRunArtifact(generate_markdown);
|
const generate_markdown_step = b.addRunArtifact(generate_markdown);
|
||||||
@ -1254,24 +1252,26 @@ fn benchSteps(
|
|||||||
|
|
||||||
// Name of the conformance app and full path to the entrypoint.
|
// Name of the conformance app and full path to the entrypoint.
|
||||||
const name = entry.name[0..index];
|
const name = entry.name[0..index];
|
||||||
const path = try fs.path.join(b.allocator, &[_][]const u8{
|
|
||||||
c_dir_path,
|
|
||||||
entry.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Executable builder.
|
// Executable builder.
|
||||||
const bin_name = try std.fmt.allocPrint(b.allocator, "bench-{s}", .{name});
|
const bin_name = try std.fmt.allocPrint(b.allocator, "bench-{s}", .{name});
|
||||||
const c_exe = b.addExecutable(.{
|
const c_exe = b.addExecutable(.{
|
||||||
.name = bin_name,
|
.name = bin_name,
|
||||||
.root_source_file = .{ .path = path },
|
.root_source_file = .{ .path = "src/main.zig" },
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
// .main_pkg_path = .{ .path = "./src" },
|
|
||||||
});
|
});
|
||||||
if (install) b.installArtifact(c_exe);
|
if (install) b.installArtifact(c_exe);
|
||||||
_ = try addDeps(b, c_exe, config: {
|
_ = try addDeps(b, c_exe, config: {
|
||||||
var copy = config;
|
var copy = config;
|
||||||
copy.static = true;
|
copy.static = true;
|
||||||
|
|
||||||
|
var buf: [64]u8 = undefined;
|
||||||
|
copy.exe_entrypoint = std.meta.stringToEnum(
|
||||||
|
build_config.ExeEntrypoint,
|
||||||
|
try std.fmt.bufPrint(&buf, "bench_{s}", .{name}),
|
||||||
|
).?;
|
||||||
|
|
||||||
break :config copy;
|
break :config copy;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
const cli_args = @import("../cli_args.zig");
|
const cli = @import("../cli.zig");
|
||||||
const terminal = @import("../terminal/main.zig");
|
const terminal = @import("../terminal/main.zig");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
@ -29,7 +29,7 @@ pub fn main() !void {
|
|||||||
errdefer args.deinit();
|
errdefer args.deinit();
|
||||||
var iter = try std.process.argsWithAllocator(alloc);
|
var iter = try std.process.argsWithAllocator(alloc);
|
||||||
defer iter.deinit();
|
defer iter.deinit();
|
||||||
try cli_args.parse(Args, alloc, &args, &iter);
|
try cli.args.parse(Args, alloc, &args, &iter);
|
||||||
break :args args;
|
break :args args;
|
||||||
};
|
};
|
||||||
defer args.deinit();
|
defer args.deinit();
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const gen = @import("build/mdgen/mdgen.zig");
|
const gen = @import("mdgen.zig");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
const alloc = gpa.allocator();
|
const alloc = gpa.allocator();
|
||||||
|
|
||||||
const writer = std.io.getStdOut().writer();
|
const writer = std.io.getStdOut().writer();
|
||||||
try gen.substitute(alloc, @embedFile("build/mdgen/ghostty_1_header.md"), writer);
|
try gen.substitute(alloc, @embedFile("ghostty_1_header.md"), writer);
|
||||||
try gen.genActions(writer);
|
try gen.genActions(writer);
|
||||||
try gen.genConfig(writer, true);
|
try gen.genConfig(writer, true);
|
||||||
try gen.substitute(alloc, @embedFile("build/mdgen/ghostty_1_footer.md"), writer);
|
try gen.substitute(alloc, @embedFile("ghostty_1_footer.md"), writer);
|
||||||
}
|
}
|
@ -1,13 +1,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const gen = @import("build/mdgen/mdgen.zig");
|
const gen = @import("mdgen.zig");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
const alloc = gpa.allocator();
|
const alloc = gpa.allocator();
|
||||||
|
|
||||||
const output = std.io.getStdOut().writer();
|
const output = std.io.getStdOut().writer();
|
||||||
try gen.substitute(alloc, @embedFile("build/mdgen/ghostty_5_header.md"), output);
|
try gen.substitute(alloc, @embedFile("ghostty_5_header.md"), output);
|
||||||
try gen.genConfig(output, false);
|
try gen.genConfig(output, false);
|
||||||
try gen.genKeybindActions(output);
|
try gen.genKeybindActions(output);
|
||||||
try gen.substitute(alloc, @embedFile("build/mdgen/ghostty_5_footer.md"), output);
|
try gen.substitute(alloc, @embedFile("ghostty_5_footer.md"), output);
|
||||||
}
|
}
|
@ -1,23 +1,20 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const help_strings = @import("help_strings");
|
const help_strings = @import("help_strings");
|
||||||
const build_options = @import("build_options");
|
const build_config = @import("../../build_config.zig");
|
||||||
const Config = @import("../../config/Config.zig");
|
const Config = @import("../../config/Config.zig");
|
||||||
const Action = @import("../../cli/action.zig").Action;
|
const Action = @import("../../cli/action.zig").Action;
|
||||||
const KeybindAction = @import("../../input/Binding.zig").Action;
|
const KeybindAction = @import("../../input/Binding.zig").Action;
|
||||||
|
|
||||||
pub fn substitute(alloc: std.mem.Allocator, input: []const u8, writer: anytype) !void {
|
pub fn substitute(alloc: std.mem.Allocator, input: []const u8, writer: anytype) !void {
|
||||||
const version_string = try std.fmt.allocPrint(alloc, "{}", .{build_options.version});
|
|
||||||
defer alloc.free(version_string);
|
|
||||||
|
|
||||||
const output = try alloc.alloc(u8, std.mem.replacementSize(
|
const output = try alloc.alloc(u8, std.mem.replacementSize(
|
||||||
u8,
|
u8,
|
||||||
input,
|
input,
|
||||||
"@@VERSION@@",
|
"@@VERSION@@",
|
||||||
version_string,
|
build_config.version_string,
|
||||||
));
|
));
|
||||||
defer alloc.free(output);
|
defer alloc.free(output);
|
||||||
|
|
||||||
_ = std.mem.replace(u8, input, "@@VERSION@@", version_string, output);
|
_ = std.mem.replace(u8, input, "@@VERSION@@", build_config.version_string, output);
|
||||||
try writer.writeAll(output);
|
try writer.writeAll(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ const assert = std.debug.assert;
|
|||||||
const apprt = @import("apprt.zig");
|
const apprt = @import("apprt.zig");
|
||||||
const font = @import("font/main.zig");
|
const font = @import("font/main.zig");
|
||||||
const rendererpkg = @import("renderer.zig");
|
const rendererpkg = @import("renderer.zig");
|
||||||
|
const WasmTarget = @import("os/wasm/target.zig").Target;
|
||||||
|
|
||||||
/// The build configuratin options. This may not be all available options
|
/// The build configuratin options. This may not be all available options
|
||||||
/// to `zig build` but it contains all the options that the Ghostty source
|
/// to `zig build` but it contains all the options that the Ghostty source
|
||||||
@ -18,6 +19,7 @@ const rendererpkg = @import("renderer.zig");
|
|||||||
/// between options, make it easy to copy and mutate options for different
|
/// between options, make it easy to copy and mutate options for different
|
||||||
/// build types, etc.
|
/// build types, etc.
|
||||||
pub const BuildConfig = struct {
|
pub const BuildConfig = struct {
|
||||||
|
version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 },
|
||||||
static: bool = false,
|
static: bool = false,
|
||||||
flatpak: bool = false,
|
flatpak: bool = false,
|
||||||
libadwaita: bool = false,
|
libadwaita: bool = false,
|
||||||
@ -25,8 +27,17 @@ pub const BuildConfig = struct {
|
|||||||
renderer: rendererpkg.Impl = .opengl,
|
renderer: rendererpkg.Impl = .opengl,
|
||||||
font_backend: font.Backend = .freetype,
|
font_backend: font.Backend = .freetype,
|
||||||
|
|
||||||
|
/// The entrypoint for exe targets.
|
||||||
|
exe_entrypoint: ExeEntrypoint = .ghostty,
|
||||||
|
|
||||||
|
/// The target runtime for the wasm build and whether to use wasm shared
|
||||||
|
/// memory or not. These are both legacy wasm-specific options that we
|
||||||
|
/// will probably have to revisit when we get back to work on wasm.
|
||||||
|
wasm_target: WasmTarget = .browser,
|
||||||
|
wasm_shared: bool = true,
|
||||||
|
|
||||||
/// Configure the build options with our values.
|
/// Configure the build options with our values.
|
||||||
pub fn addOptions(self: BuildConfig, step: *std.Build.Step.Options) void {
|
pub fn addOptions(self: BuildConfig, step: *std.Build.Step.Options) !void {
|
||||||
// We need to break these down individual because addOption doesn't
|
// We need to break these down individual because addOption doesn't
|
||||||
// support all types.
|
// support all types.
|
||||||
step.addOption(bool, "flatpak", self.flatpak);
|
step.addOption(bool, "flatpak", self.flatpak);
|
||||||
@ -34,6 +45,19 @@ pub const BuildConfig = struct {
|
|||||||
step.addOption(apprt.Runtime, "app_runtime", self.app_runtime);
|
step.addOption(apprt.Runtime, "app_runtime", self.app_runtime);
|
||||||
step.addOption(font.Backend, "font_backend", self.font_backend);
|
step.addOption(font.Backend, "font_backend", self.font_backend);
|
||||||
step.addOption(rendererpkg.Impl, "renderer", self.renderer);
|
step.addOption(rendererpkg.Impl, "renderer", self.renderer);
|
||||||
|
step.addOption(ExeEntrypoint, "exe_entrypoint", self.exe_entrypoint);
|
||||||
|
step.addOption(WasmTarget, "wasm_target", self.wasm_target);
|
||||||
|
step.addOption(bool, "wasm_shared", self.wasm_shared);
|
||||||
|
|
||||||
|
// Our version. We also add the string version so we don't need
|
||||||
|
// to do any allocations at runtime.
|
||||||
|
var buf: [64]u8 = undefined;
|
||||||
|
step.addOption(std.SemanticVersion, "app_version", self.version);
|
||||||
|
step.addOption([:0]const u8, "app_version_string", try std.fmt.bufPrintZ(
|
||||||
|
&buf,
|
||||||
|
"{}",
|
||||||
|
.{self.version},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rehydrate our BuildConfig from the comptime options. Note that not all
|
/// Rehydrate our BuildConfig from the comptime options. Note that not all
|
||||||
@ -41,11 +65,15 @@ pub const BuildConfig = struct {
|
|||||||
/// to see what is and isn't available.
|
/// to see what is and isn't available.
|
||||||
pub fn fromOptions() BuildConfig {
|
pub fn fromOptions() BuildConfig {
|
||||||
return .{
|
return .{
|
||||||
|
.version = options.app_version,
|
||||||
.flatpak = options.flatpak,
|
.flatpak = options.flatpak,
|
||||||
.libadwaita = options.libadwaita,
|
.libadwaita = options.libadwaita,
|
||||||
.app_runtime = std.meta.stringToEnum(apprt.Runtime, @tagName(options.app_runtime)).?,
|
.app_runtime = std.meta.stringToEnum(apprt.Runtime, @tagName(options.app_runtime)).?,
|
||||||
.font_backend = std.meta.stringToEnum(font.Backend, @tagName(options.font_backend)).?,
|
.font_backend = std.meta.stringToEnum(font.Backend, @tagName(options.font_backend)).?,
|
||||||
.renderer = std.meta.stringToEnum(rendererpkg.Impl, @tagName(options.renderer)).?,
|
.renderer = std.meta.stringToEnum(rendererpkg.Impl, @tagName(options.renderer)).?,
|
||||||
|
.exe_entrypoint = std.meta.stringToEnum(ExeEntrypoint, @tagName(options.exe_entrypoint)).?,
|
||||||
|
.wasm_target = std.meta.stringToEnum(WasmTarget, @tagName(options.wasm_target)).?,
|
||||||
|
.wasm_shared = options.wasm_shared,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -62,6 +90,7 @@ pub const artifact = Artifact.detect();
|
|||||||
/// top-level so its a bit cleaner to use throughout the code. See the doc
|
/// top-level so its a bit cleaner to use throughout the code. See the doc
|
||||||
/// comments in BuildConfig for details on each.
|
/// comments in BuildConfig for details on each.
|
||||||
pub const config = BuildConfig.fromOptions();
|
pub const config = BuildConfig.fromOptions();
|
||||||
|
pub const exe_entrypoint = config.exe_entrypoint;
|
||||||
pub const flatpak = options.flatpak;
|
pub const flatpak = options.flatpak;
|
||||||
pub const app_runtime: apprt.Runtime = config.app_runtime;
|
pub const app_runtime: apprt.Runtime = config.app_runtime;
|
||||||
pub const font_backend: font.Backend = config.font_backend;
|
pub const font_backend: font.Backend = config.font_backend;
|
||||||
@ -94,3 +123,20 @@ pub const Artifact = enum {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The possible entrypoints for the exe artifact. This has no effect on
|
||||||
|
/// other artifact types (i.e. lib, wasm_module).
|
||||||
|
///
|
||||||
|
/// The whole existence of this enum is to workaround the fact that Zig
|
||||||
|
/// doesn't allow the main function to be in a file in a subdirctory
|
||||||
|
/// from the "root" of the module, and I don't want to pollute our root
|
||||||
|
/// directory with a bunch of individual zig files for each entrypoint.
|
||||||
|
///
|
||||||
|
/// Therefore, main.zig uses this to switch between the different entrypoints.
|
||||||
|
pub const ExeEntrypoint = enum {
|
||||||
|
ghostty,
|
||||||
|
helpgen,
|
||||||
|
mdgen_ghostty_1,
|
||||||
|
mdgen_ghostty_5,
|
||||||
|
bench_parser,
|
||||||
|
};
|
||||||
|
313
src/main.zig
313
src/main.zig
@ -1,309 +1,10 @@
|
|||||||
const std = @import("std");
|
|
||||||
const builtin = @import("builtin");
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const build_config = @import("build_config.zig");
|
const build_config = @import("build_config.zig");
|
||||||
const options = @import("build_options");
|
|
||||||
const glfw = @import("glfw");
|
|
||||||
const glslang = @import("glslang");
|
|
||||||
const macos = @import("macos");
|
|
||||||
const oni = @import("oniguruma");
|
|
||||||
const cli = @import("cli.zig");
|
|
||||||
const internal_os = @import("os/main.zig");
|
|
||||||
const xev = @import("xev");
|
|
||||||
const fontconfig = @import("fontconfig");
|
|
||||||
const harfbuzz = @import("harfbuzz");
|
|
||||||
const renderer = @import("renderer.zig");
|
|
||||||
const apprt = @import("apprt.zig");
|
|
||||||
|
|
||||||
const App = @import("App.zig");
|
// See build_config.ExeEntrypoint for why we do this.
|
||||||
const Ghostty = @import("main_c.zig").Ghostty;
|
pub usingnamespace switch (build_config.exe_entrypoint) {
|
||||||
|
.ghostty => @import("main_ghostty.zig"),
|
||||||
/// Global process state. This is initialized in main() for exe artifacts
|
.helpgen => @import("helpgen.zig"),
|
||||||
/// and by ghostty_init() for lib artifacts. This should ONLY be used by
|
.mdgen_ghostty_1 => @import("build/mdgen/main_ghostty_1.zig"),
|
||||||
/// the C API. The Zig API should NOT use any global state and should
|
.mdgen_ghostty_5 => @import("build/mdgen/main_ghostty_5.zig"),
|
||||||
/// rely on allocators being passed in as parameters.
|
.bench_parser => @import("bench/parser.zig"),
|
||||||
pub var state: GlobalState = undefined;
|
|
||||||
|
|
||||||
/// The return type for main() depends on the build artifact.
|
|
||||||
const MainReturn = switch (build_config.artifact) {
|
|
||||||
.lib => noreturn,
|
|
||||||
else => void,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() !MainReturn {
|
|
||||||
// We first start by initializing our global state. This will setup
|
|
||||||
// process-level state we need to run the terminal. The reason we use
|
|
||||||
// a global is because the C API needs to be able to access this state;
|
|
||||||
// no other Zig code should EVER access the global state.
|
|
||||||
state.init() catch |err| {
|
|
||||||
const stderr = std.io.getStdErr().writer();
|
|
||||||
defer std.os.exit(1);
|
|
||||||
const ErrSet = @TypeOf(err) || error{Unknown};
|
|
||||||
switch (@as(ErrSet, @errorCast(err))) {
|
|
||||||
error.MultipleActions => try stderr.print(
|
|
||||||
"Error: multiple CLI actions specified. You must specify only one\n" ++
|
|
||||||
"action starting with the `+` character.\n",
|
|
||||||
.{},
|
|
||||||
),
|
|
||||||
|
|
||||||
error.InvalidAction => try stderr.print(
|
|
||||||
"Error: unknown CLI action specified. CLI actions are specified with\n" ++
|
|
||||||
"the '+' character.\n",
|
|
||||||
.{},
|
|
||||||
),
|
|
||||||
|
|
||||||
else => try stderr.print("invalid CLI invocation err={}\n", .{err}),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
defer state.deinit();
|
|
||||||
const alloc = state.alloc;
|
|
||||||
|
|
||||||
if (comptime builtin.mode == .Debug) {
|
|
||||||
std.log.warn("This is a debug build. Performance will be very poor.", .{});
|
|
||||||
std.log.warn("You should only use a debug build for developing Ghostty.", .{});
|
|
||||||
std.log.warn("Otherwise, please rebuild in a release mode.", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute our action if we have one
|
|
||||||
if (state.action) |action| {
|
|
||||||
std.log.info("executing CLI action={}", .{action});
|
|
||||||
std.os.exit(action.run(alloc) catch |err| err: {
|
|
||||||
std.log.err("CLI action failed error={}", .{err});
|
|
||||||
break :err 1;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comptime build_config.app_runtime == .none) {
|
|
||||||
const stdout = std.io.getStdOut().writer();
|
|
||||||
try stdout.print("Usage: ghostty +<action> [flags]\n\n", .{});
|
|
||||||
try stdout.print(
|
|
||||||
\\This is the Ghostty helper CLI that accompanies the graphical Ghostty app.
|
|
||||||
\\To launch the terminal directly, please launch the graphical app
|
|
||||||
\\(i.e. Ghostty.app on macOS). This CLI can be used to perform various
|
|
||||||
\\actions such as inspecting the version, listing fonts, etc.
|
|
||||||
\\
|
|
||||||
\\We don't have proper help output yet, sorry! Please refer to the
|
|
||||||
\\source code or Discord community for help for now. We'll fix this in time.
|
|
||||||
,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
|
|
||||||
std.os.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create our app state
|
|
||||||
var app = try App.create(alloc);
|
|
||||||
defer app.destroy();
|
|
||||||
|
|
||||||
// Create our runtime app
|
|
||||||
var app_runtime = try apprt.App.init(app, .{});
|
|
||||||
defer app_runtime.terminate();
|
|
||||||
|
|
||||||
// Run the GUI event loop
|
|
||||||
try app_runtime.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const std_options = struct {
|
|
||||||
// Our log level is always at least info in every build mode.
|
|
||||||
pub const log_level: std.log.Level = switch (builtin.mode) {
|
|
||||||
.Debug => .debug,
|
|
||||||
else => .info,
|
|
||||||
};
|
|
||||||
|
|
||||||
// The function std.log will call.
|
|
||||||
pub fn logFn(
|
|
||||||
comptime level: std.log.Level,
|
|
||||||
comptime scope: @TypeOf(.EnumLiteral),
|
|
||||||
comptime format: []const u8,
|
|
||||||
args: anytype,
|
|
||||||
) void {
|
|
||||||
// Stuff we can do before the lock
|
|
||||||
const level_txt = comptime level.asText();
|
|
||||||
const prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
|
|
||||||
|
|
||||||
// Lock so we are thread-safe
|
|
||||||
std.debug.getStderrMutex().lock();
|
|
||||||
defer std.debug.getStderrMutex().unlock();
|
|
||||||
|
|
||||||
// On Mac, we use unified logging. To view this:
|
|
||||||
//
|
|
||||||
// sudo log stream --level debug --predicate 'subsystem=="com.mitchellh.ghostty"'
|
|
||||||
//
|
|
||||||
if (builtin.target.isDarwin()) {
|
|
||||||
// Convert our levels to Mac levels
|
|
||||||
const mac_level: macos.os.LogType = switch (level) {
|
|
||||||
.debug => .debug,
|
|
||||||
.info => .info,
|
|
||||||
.warn => .err,
|
|
||||||
.err => .fault,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize a logger. This is slow to do on every operation
|
|
||||||
// but we shouldn't be logging too much.
|
|
||||||
const logger = macos.os.Log.create("com.mitchellh.ghostty", @tagName(scope));
|
|
||||||
defer logger.release();
|
|
||||||
logger.log(std.heap.c_allocator, mac_level, format, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (state.logging) {
|
|
||||||
.disabled => {},
|
|
||||||
|
|
||||||
.stderr => {
|
|
||||||
// Always try default to send to stderr
|
|
||||||
const stderr = std.io.getStdErr().writer();
|
|
||||||
nosuspend stderr.print(level_txt ++ prefix ++ format ++ "\n", args) catch return;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// This represents the global process state. There should only
|
|
||||||
/// be one of these at any given moment. This is extracted into a dedicated
|
|
||||||
/// struct because it is reused by main and the static C lib.
|
|
||||||
pub const GlobalState = struct {
|
|
||||||
const GPA = std.heap.GeneralPurposeAllocator(.{});
|
|
||||||
|
|
||||||
gpa: ?GPA,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
action: ?cli.Action,
|
|
||||||
logging: Logging,
|
|
||||||
|
|
||||||
/// The app resources directory, equivalent to zig-out/share when we build
|
|
||||||
/// from source. This is null if we can't detect it.
|
|
||||||
resources_dir: ?[]const u8,
|
|
||||||
|
|
||||||
/// Where logging should go
|
|
||||||
pub const Logging = union(enum) {
|
|
||||||
disabled: void,
|
|
||||||
stderr: void,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Initialize the global state.
|
|
||||||
pub fn init(self: *GlobalState) !void {
|
|
||||||
// Initialize ourself to nothing so we don't have any extra state.
|
|
||||||
// IMPORTANT: this MUST be initialized before any log output because
|
|
||||||
// the log function uses the global state.
|
|
||||||
self.* = .{
|
|
||||||
.gpa = null,
|
|
||||||
.alloc = undefined,
|
|
||||||
.action = null,
|
|
||||||
.logging = .{ .stderr = {} },
|
|
||||||
.resources_dir = null,
|
|
||||||
};
|
|
||||||
errdefer self.deinit();
|
|
||||||
|
|
||||||
self.gpa = gpa: {
|
|
||||||
// Use the libc allocator if it is available because it is WAY
|
|
||||||
// faster than GPA. We only do this in release modes so that we
|
|
||||||
// can get easy memory leak detection in debug modes.
|
|
||||||
if (builtin.link_libc) {
|
|
||||||
if (switch (builtin.mode) {
|
|
||||||
.ReleaseSafe, .ReleaseFast => true,
|
|
||||||
|
|
||||||
// We also use it if we can detect we're running under
|
|
||||||
// Valgrind since Valgrind only instruments the C allocator
|
|
||||||
else => std.valgrind.runningOnValgrind() > 0,
|
|
||||||
}) break :gpa null;
|
|
||||||
}
|
|
||||||
|
|
||||||
break :gpa GPA{};
|
|
||||||
};
|
|
||||||
|
|
||||||
self.alloc = if (self.gpa) |*value|
|
|
||||||
value.allocator()
|
|
||||||
else if (builtin.link_libc)
|
|
||||||
std.heap.c_allocator
|
|
||||||
else
|
|
||||||
unreachable;
|
|
||||||
|
|
||||||
// We first try to parse any action that we may be executing.
|
|
||||||
self.action = try cli.Action.detectCLI(self.alloc);
|
|
||||||
|
|
||||||
// If we have an action executing, we disable logging by default
|
|
||||||
// since we write to stderr we don't want logs messing up our
|
|
||||||
// output.
|
|
||||||
if (self.action != null) self.logging = .{ .disabled = {} };
|
|
||||||
|
|
||||||
// For lib mode we always disable stderr logging by default.
|
|
||||||
if (comptime build_config.app_runtime == .none) {
|
|
||||||
self.logging = .{ .disabled = {} };
|
|
||||||
}
|
|
||||||
|
|
||||||
// I don't love the env var name but I don't have it in my heart
|
|
||||||
// to parse CLI args 3 times (once for actions, once for config,
|
|
||||||
// maybe once for logging) so for now this is an easy way to do
|
|
||||||
// this. Env vars are useful for logging too because they are
|
|
||||||
// easy to set.
|
|
||||||
if ((try internal_os.getenv(self.alloc, "GHOSTTY_LOG"))) |v| {
|
|
||||||
defer v.deinit(self.alloc);
|
|
||||||
if (v.value.len > 0) {
|
|
||||||
self.logging = .{ .stderr = {} };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output some debug information right away
|
|
||||||
std.log.info("ghostty version={s}", .{build_config.version_string});
|
|
||||||
std.log.info("runtime={}", .{build_config.app_runtime});
|
|
||||||
std.log.info("font_backend={}", .{build_config.font_backend});
|
|
||||||
std.log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()});
|
|
||||||
if (comptime build_config.font_backend.hasFontconfig()) {
|
|
||||||
std.log.info("dependency fontconfig={d}", .{fontconfig.version()});
|
|
||||||
}
|
|
||||||
std.log.info("renderer={}", .{renderer.Renderer});
|
|
||||||
std.log.info("libxev backend={}", .{xev.backend});
|
|
||||||
|
|
||||||
// First things first, we fix our file descriptors
|
|
||||||
internal_os.fixMaxFiles();
|
|
||||||
|
|
||||||
// We need to make sure the process locale is set properly. Locale
|
|
||||||
// affects a lot of behaviors in a shell.
|
|
||||||
try internal_os.ensureLocale(self.alloc);
|
|
||||||
|
|
||||||
// Initialize glslang for shader compilation
|
|
||||||
try glslang.init();
|
|
||||||
|
|
||||||
// Initialize oniguruma for regex
|
|
||||||
try oni.init(&.{oni.Encoding.utf8});
|
|
||||||
|
|
||||||
// Find our resources directory once for the app so every launch
|
|
||||||
// hereafter can use this cached value.
|
|
||||||
self.resources_dir = try internal_os.resourcesDir(self.alloc);
|
|
||||||
errdefer if (self.resources_dir) |dir| self.alloc.free(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cleans up the global state. This doesn't _need_ to be called but
|
|
||||||
/// doing so in dev modes will check for memory leaks.
|
|
||||||
pub fn deinit(self: *GlobalState) void {
|
|
||||||
if (self.resources_dir) |dir| self.alloc.free(dir);
|
|
||||||
|
|
||||||
if (self.gpa) |*value| {
|
|
||||||
// We want to ensure that we deinit the GPA because this is
|
|
||||||
// the point at which it will output if there were safety violations.
|
|
||||||
_ = value.deinit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
test {
|
|
||||||
_ = @import("circ_buf.zig");
|
|
||||||
_ = @import("pty.zig");
|
|
||||||
_ = @import("Command.zig");
|
|
||||||
_ = @import("font/main.zig");
|
|
||||||
_ = @import("apprt.zig");
|
|
||||||
_ = @import("renderer.zig");
|
|
||||||
_ = @import("termio.zig");
|
|
||||||
_ = @import("input.zig");
|
|
||||||
_ = @import("cli.zig");
|
|
||||||
_ = @import("surface_mouse.zig");
|
|
||||||
|
|
||||||
// Libraries
|
|
||||||
_ = @import("segmented_pool.zig");
|
|
||||||
_ = @import("inspector/main.zig");
|
|
||||||
_ = @import("terminal/main.zig");
|
|
||||||
_ = @import("terminfo/main.zig");
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
_ = @import("blocking_queue.zig");
|
|
||||||
_ = @import("config.zig");
|
|
||||||
_ = @import("lru.zig");
|
|
||||||
}
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
// This currently isn't supported as a general purpose embedding API.
|
// This currently isn't supported as a general purpose embedding API.
|
||||||
// This is currently used only to embed ghostty within a macOS app. However,
|
// This is currently used only to embed ghostty within a macOS app. However,
|
||||||
// it could be expanded to be general purpose in the future.
|
// it could be expanded to be general purpose in the future.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
315
src/main_ghostty.zig
Normal file
315
src/main_ghostty.zig
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
//! The main entrypoint for the `ghostty` application. This also serves
|
||||||
|
//! as the process initialization code for the `libghostty` library.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const build_config = @import("build_config.zig");
|
||||||
|
const options = @import("build_options");
|
||||||
|
const glfw = @import("glfw");
|
||||||
|
const glslang = @import("glslang");
|
||||||
|
const macos = @import("macos");
|
||||||
|
const oni = @import("oniguruma");
|
||||||
|
const cli = @import("cli.zig");
|
||||||
|
const internal_os = @import("os/main.zig");
|
||||||
|
const xev = @import("xev");
|
||||||
|
const fontconfig = @import("fontconfig");
|
||||||
|
const harfbuzz = @import("harfbuzz");
|
||||||
|
const renderer = @import("renderer.zig");
|
||||||
|
const apprt = @import("apprt.zig");
|
||||||
|
|
||||||
|
const App = @import("App.zig");
|
||||||
|
const Ghostty = @import("main_c.zig").Ghostty;
|
||||||
|
|
||||||
|
/// Global process state. This is initialized in main() for exe artifacts
|
||||||
|
/// and by ghostty_init() for lib artifacts. This should ONLY be used by
|
||||||
|
/// the C API. The Zig API should NOT use any global state and should
|
||||||
|
/// rely on allocators being passed in as parameters.
|
||||||
|
pub var state: GlobalState = undefined;
|
||||||
|
|
||||||
|
/// The return type for main() depends on the build artifact. The lib build
|
||||||
|
/// also calls "main" in order to run the CLI actions, but it calls it as
|
||||||
|
/// an API and not an entrypoint.
|
||||||
|
const MainReturn = switch (build_config.artifact) {
|
||||||
|
.lib => noreturn,
|
||||||
|
else => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() !MainReturn {
|
||||||
|
// We first start by initializing our global state. This will setup
|
||||||
|
// process-level state we need to run the terminal. The reason we use
|
||||||
|
// a global is because the C API needs to be able to access this state;
|
||||||
|
// no other Zig code should EVER access the global state.
|
||||||
|
state.init() catch |err| {
|
||||||
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
defer std.os.exit(1);
|
||||||
|
const ErrSet = @TypeOf(err) || error{Unknown};
|
||||||
|
switch (@as(ErrSet, @errorCast(err))) {
|
||||||
|
error.MultipleActions => try stderr.print(
|
||||||
|
"Error: multiple CLI actions specified. You must specify only one\n" ++
|
||||||
|
"action starting with the `+` character.\n",
|
||||||
|
.{},
|
||||||
|
),
|
||||||
|
|
||||||
|
error.InvalidAction => try stderr.print(
|
||||||
|
"Error: unknown CLI action specified. CLI actions are specified with\n" ++
|
||||||
|
"the '+' character.\n",
|
||||||
|
.{},
|
||||||
|
),
|
||||||
|
|
||||||
|
else => try stderr.print("invalid CLI invocation err={}\n", .{err}),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
defer state.deinit();
|
||||||
|
const alloc = state.alloc;
|
||||||
|
|
||||||
|
if (comptime builtin.mode == .Debug) {
|
||||||
|
std.log.warn("This is a debug build. Performance will be very poor.", .{});
|
||||||
|
std.log.warn("You should only use a debug build for developing Ghostty.", .{});
|
||||||
|
std.log.warn("Otherwise, please rebuild in a release mode.", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute our action if we have one
|
||||||
|
if (state.action) |action| {
|
||||||
|
std.log.info("executing CLI action={}", .{action});
|
||||||
|
std.os.exit(action.run(alloc) catch |err| err: {
|
||||||
|
std.log.err("CLI action failed error={}", .{err});
|
||||||
|
break :err 1;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comptime build_config.app_runtime == .none) {
|
||||||
|
const stdout = std.io.getStdOut().writer();
|
||||||
|
try stdout.print("Usage: ghostty +<action> [flags]\n\n", .{});
|
||||||
|
try stdout.print(
|
||||||
|
\\This is the Ghostty helper CLI that accompanies the graphical Ghostty app.
|
||||||
|
\\To launch the terminal directly, please launch the graphical app
|
||||||
|
\\(i.e. Ghostty.app on macOS). This CLI can be used to perform various
|
||||||
|
\\actions such as inspecting the version, listing fonts, etc.
|
||||||
|
\\
|
||||||
|
\\We don't have proper help output yet, sorry! Please refer to the
|
||||||
|
\\source code or Discord community for help for now. We'll fix this in time.
|
||||||
|
,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
|
||||||
|
std.os.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create our app state
|
||||||
|
var app = try App.create(alloc);
|
||||||
|
defer app.destroy();
|
||||||
|
|
||||||
|
// Create our runtime app
|
||||||
|
var app_runtime = try apprt.App.init(app, .{});
|
||||||
|
defer app_runtime.terminate();
|
||||||
|
|
||||||
|
// Run the GUI event loop
|
||||||
|
try app_runtime.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const std_options = struct {
|
||||||
|
// Our log level is always at least info in every build mode.
|
||||||
|
pub const log_level: std.log.Level = switch (builtin.mode) {
|
||||||
|
.Debug => .debug,
|
||||||
|
else => .info,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The function std.log will call.
|
||||||
|
pub fn logFn(
|
||||||
|
comptime level: std.log.Level,
|
||||||
|
comptime scope: @TypeOf(.EnumLiteral),
|
||||||
|
comptime format: []const u8,
|
||||||
|
args: anytype,
|
||||||
|
) void {
|
||||||
|
// Stuff we can do before the lock
|
||||||
|
const level_txt = comptime level.asText();
|
||||||
|
const prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
|
||||||
|
|
||||||
|
// Lock so we are thread-safe
|
||||||
|
std.debug.getStderrMutex().lock();
|
||||||
|
defer std.debug.getStderrMutex().unlock();
|
||||||
|
|
||||||
|
// On Mac, we use unified logging. To view this:
|
||||||
|
//
|
||||||
|
// sudo log stream --level debug --predicate 'subsystem=="com.mitchellh.ghostty"'
|
||||||
|
//
|
||||||
|
if (builtin.target.isDarwin()) {
|
||||||
|
// Convert our levels to Mac levels
|
||||||
|
const mac_level: macos.os.LogType = switch (level) {
|
||||||
|
.debug => .debug,
|
||||||
|
.info => .info,
|
||||||
|
.warn => .err,
|
||||||
|
.err => .fault,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize a logger. This is slow to do on every operation
|
||||||
|
// but we shouldn't be logging too much.
|
||||||
|
const logger = macos.os.Log.create("com.mitchellh.ghostty", @tagName(scope));
|
||||||
|
defer logger.release();
|
||||||
|
logger.log(std.heap.c_allocator, mac_level, format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (state.logging) {
|
||||||
|
.disabled => {},
|
||||||
|
|
||||||
|
.stderr => {
|
||||||
|
// Always try default to send to stderr
|
||||||
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
nosuspend stderr.print(level_txt ++ prefix ++ format ++ "\n", args) catch return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This represents the global process state. There should only
|
||||||
|
/// be one of these at any given moment. This is extracted into a dedicated
|
||||||
|
/// struct because it is reused by main and the static C lib.
|
||||||
|
pub const GlobalState = struct {
|
||||||
|
const GPA = std.heap.GeneralPurposeAllocator(.{});
|
||||||
|
|
||||||
|
gpa: ?GPA,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
action: ?cli.Action,
|
||||||
|
logging: Logging,
|
||||||
|
|
||||||
|
/// The app resources directory, equivalent to zig-out/share when we build
|
||||||
|
/// from source. This is null if we can't detect it.
|
||||||
|
resources_dir: ?[]const u8,
|
||||||
|
|
||||||
|
/// Where logging should go
|
||||||
|
pub const Logging = union(enum) {
|
||||||
|
disabled: void,
|
||||||
|
stderr: void,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Initialize the global state.
|
||||||
|
pub fn init(self: *GlobalState) !void {
|
||||||
|
// Initialize ourself to nothing so we don't have any extra state.
|
||||||
|
// IMPORTANT: this MUST be initialized before any log output because
|
||||||
|
// the log function uses the global state.
|
||||||
|
self.* = .{
|
||||||
|
.gpa = null,
|
||||||
|
.alloc = undefined,
|
||||||
|
.action = null,
|
||||||
|
.logging = .{ .stderr = {} },
|
||||||
|
.resources_dir = null,
|
||||||
|
};
|
||||||
|
errdefer self.deinit();
|
||||||
|
|
||||||
|
self.gpa = gpa: {
|
||||||
|
// Use the libc allocator if it is available because it is WAY
|
||||||
|
// faster than GPA. We only do this in release modes so that we
|
||||||
|
// can get easy memory leak detection in debug modes.
|
||||||
|
if (builtin.link_libc) {
|
||||||
|
if (switch (builtin.mode) {
|
||||||
|
.ReleaseSafe, .ReleaseFast => true,
|
||||||
|
|
||||||
|
// We also use it if we can detect we're running under
|
||||||
|
// Valgrind since Valgrind only instruments the C allocator
|
||||||
|
else => std.valgrind.runningOnValgrind() > 0,
|
||||||
|
}) break :gpa null;
|
||||||
|
}
|
||||||
|
|
||||||
|
break :gpa GPA{};
|
||||||
|
};
|
||||||
|
|
||||||
|
self.alloc = if (self.gpa) |*value|
|
||||||
|
value.allocator()
|
||||||
|
else if (builtin.link_libc)
|
||||||
|
std.heap.c_allocator
|
||||||
|
else
|
||||||
|
unreachable;
|
||||||
|
|
||||||
|
// We first try to parse any action that we may be executing.
|
||||||
|
self.action = try cli.Action.detectCLI(self.alloc);
|
||||||
|
|
||||||
|
// If we have an action executing, we disable logging by default
|
||||||
|
// since we write to stderr we don't want logs messing up our
|
||||||
|
// output.
|
||||||
|
if (self.action != null) self.logging = .{ .disabled = {} };
|
||||||
|
|
||||||
|
// For lib mode we always disable stderr logging by default.
|
||||||
|
if (comptime build_config.app_runtime == .none) {
|
||||||
|
self.logging = .{ .disabled = {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
// I don't love the env var name but I don't have it in my heart
|
||||||
|
// to parse CLI args 3 times (once for actions, once for config,
|
||||||
|
// maybe once for logging) so for now this is an easy way to do
|
||||||
|
// this. Env vars are useful for logging too because they are
|
||||||
|
// easy to set.
|
||||||
|
if ((try internal_os.getenv(self.alloc, "GHOSTTY_LOG"))) |v| {
|
||||||
|
defer v.deinit(self.alloc);
|
||||||
|
if (v.value.len > 0) {
|
||||||
|
self.logging = .{ .stderr = {} };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output some debug information right away
|
||||||
|
std.log.info("ghostty version={s}", .{build_config.version_string});
|
||||||
|
std.log.info("runtime={}", .{build_config.app_runtime});
|
||||||
|
std.log.info("font_backend={}", .{build_config.font_backend});
|
||||||
|
std.log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()});
|
||||||
|
if (comptime build_config.font_backend.hasFontconfig()) {
|
||||||
|
std.log.info("dependency fontconfig={d}", .{fontconfig.version()});
|
||||||
|
}
|
||||||
|
std.log.info("renderer={}", .{renderer.Renderer});
|
||||||
|
std.log.info("libxev backend={}", .{xev.backend});
|
||||||
|
|
||||||
|
// First things first, we fix our file descriptors
|
||||||
|
internal_os.fixMaxFiles();
|
||||||
|
|
||||||
|
// We need to make sure the process locale is set properly. Locale
|
||||||
|
// affects a lot of behaviors in a shell.
|
||||||
|
try internal_os.ensureLocale(self.alloc);
|
||||||
|
|
||||||
|
// Initialize glslang for shader compilation
|
||||||
|
try glslang.init();
|
||||||
|
|
||||||
|
// Initialize oniguruma for regex
|
||||||
|
try oni.init(&.{oni.Encoding.utf8});
|
||||||
|
|
||||||
|
// Find our resources directory once for the app so every launch
|
||||||
|
// hereafter can use this cached value.
|
||||||
|
self.resources_dir = try internal_os.resourcesDir(self.alloc);
|
||||||
|
errdefer if (self.resources_dir) |dir| self.alloc.free(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cleans up the global state. This doesn't _need_ to be called but
|
||||||
|
/// doing so in dev modes will check for memory leaks.
|
||||||
|
pub fn deinit(self: *GlobalState) void {
|
||||||
|
if (self.resources_dir) |dir| self.alloc.free(dir);
|
||||||
|
|
||||||
|
if (self.gpa) |*value| {
|
||||||
|
// We want to ensure that we deinit the GPA because this is
|
||||||
|
// the point at which it will output if there were safety violations.
|
||||||
|
_ = value.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test {
|
||||||
|
_ = @import("circ_buf.zig");
|
||||||
|
_ = @import("pty.zig");
|
||||||
|
_ = @import("Command.zig");
|
||||||
|
_ = @import("font/main.zig");
|
||||||
|
_ = @import("apprt.zig");
|
||||||
|
_ = @import("renderer.zig");
|
||||||
|
_ = @import("termio.zig");
|
||||||
|
_ = @import("input.zig");
|
||||||
|
_ = @import("cli.zig");
|
||||||
|
_ = @import("surface_mouse.zig");
|
||||||
|
|
||||||
|
// Libraries
|
||||||
|
_ = @import("segmented_pool.zig");
|
||||||
|
_ = @import("inspector/main.zig");
|
||||||
|
_ = @import("terminal/main.zig");
|
||||||
|
_ = @import("terminfo/main.zig");
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
_ = @import("blocking_queue.zig");
|
||||||
|
_ = @import("config.zig");
|
||||||
|
_ = @import("lru.zig");
|
||||||
|
}
|
Reference in New Issue
Block a user