mcap/modules/imgui-sdl/generate.jai

655 lines
25 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Instructions to build & bind:
//
// jai generate.jai - compile
// We don’t ship the Dear ImGui source with the compiler. Download a Dear ImGui release and update this path if you want to re-generate the bindings.
IMGUI_PATH :: "../imgui-source"; // v1.92.9-docking (WIP)
SDL3_INCLUDE_PATH :: "src/include"; // Used for SDL v3.2.14
FREETYPE_INCLUDE_PATH :: "src/include/freetype-2.12.1";
DECLARATIONS_TO_OMIT :: string.[
// We have custom declarations for these in module.jai:
"ImVec2",
"ImVec4",
"ImColor",
"ImVector",
"ImDrawCallback",
// Jai has its own, better "String_Builder"
"ImGuiTextBuffer",
"ImDrawCallback_ResetRenderState", // casting an integer to a function pointer is not supported in Jai.
];
macos_arm64_version_arg := "-mmacos-version-min=11.0"; // Earliest version that supports arm64; macOS is arm64 only
#run,stallable {
set_build_options_dc(.{do_output=false});
root_options := get_build_options();
args := root_options.compile_time_command_line;
if !generate_imgui_bindings(args) {
compiler_set_workspace_status(.FAILED);
}
}
generate_imgui_bindings :: (args: [] string) -> bool {
compile := array_find(args, "compile");
compile_debug := array_find(args, "debug");
compile_and_gen_backend_sdl3_gpu3 := array_find(args, "backend_sdl3_gpu3");
compile_and_gen_freetype := array_find(args, "freetype");
if compile {
imgui_src_files: [..] string;
array_add(*imgui_src_files, tprint("%/imgui.cpp", IMGUI_PATH));
array_add(*imgui_src_files, tprint("%/imgui_widgets.cpp", IMGUI_PATH));
array_add(*imgui_src_files, tprint("%/imgui_draw.cpp", IMGUI_PATH));
array_add(*imgui_src_files, tprint("%/imgui_tables.cpp", IMGUI_PATH));
// array_add(*imgui_src_files, tprint("%/imgui_demo.cpp", IMGUI_PATH));
success := true;
#if OS == .WINDOWS {
make_directory_if_it_does_not_exist("windows");
success &&= build_cpp_dynamic_lib(
"windows/ImGui",
..imgui_src_files,
extra=.[
"/DIMGUI_API=__declspec(dllexport)",
"/DIMGUI_USE_WCHAR32",
],
debug=compile_debug,
);
// We set IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS. Otherwise all users of the ImGui module have to link against "imm32".
success &&= build_cpp_static_lib(
"windows/ImGui.static",
..imgui_src_files,
extra=.[
"/DIMGUI_API=__declspec(dllexport)",
"/DIMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS",
"/DIMGUI_USE_WCHAR32",
],
debug=compile_debug,
);
} else #if OS == .LINUX {
make_directory_if_it_does_not_exist("linux");
// success &&= build_cpp_dynamic_lib("linux/ImGui", ..imgui_src_files, debug=compile_debug, extra=.["-DIMGUI_USE_WCHAR32"]);
// @Hack: Is -fno-threadsafe-statics really the way to go? Without it, we get linker errors when using the static library
// due to "__cxa_guard_acquire" et al not being defined. The library users probably need to link agains libc++ or something. But do we really want to enforce that?
success &&= build_cpp_static_lib("linux/ImGui", ..imgui_src_files, extra = .["-fno-exceptions", "-fno-rtti", "-fno-threadsafe-statics", "-fPIC", "-DIMGUI_USE_WCHAR32"], debug=compile_debug);
} else #if OS == .MACOS {
#import "Process";
make_directory_if_it_does_not_exist("macos");
success &&= build_cpp_dynamic_lib("macos/ImGui", ..imgui_src_files, extra = .["-arch", "arm64", "-std=c++20", macos_arm64_version_arg, "-DIMGUI_USE_WCHAR32"], debug=compile_debug);
// @Hack: Is -fno-threadsafe-statics really the way to go? Without it, we get linker errors when using the static library
// due to "__cxa_guard_acquire" et al not being defined. The library users probably need to link agains libc++ or something. But do we really want to enforce that?
success &&= build_cpp_static_lib( "macos/ImGui", ..imgui_src_files, extra = .["-arch", "arm64", "-std=c++20", "-fno-exceptions", "-fno-rtti", "-fno-threadsafe-statics", macos_arm64_version_arg, "-DIMGUI_USE_WCHAR32"], debug=compile_debug);
} else {
assert(false, "this OS is not supported, check the procedure % for details\n", #procedure_name());
}
if !success return false;
}
// We return if we generate a backend because bindings generator crashes if we do more than one generation per run
if compile_and_gen_backend_sdl3_gpu3 {
success := compile_and_generate_bindings_backend_sdl3_gpu3(compile_debug);
return success;
}
if compile_and_gen_freetype {
success := compile_and_generate_bindings_freetype(compile_debug);
return success;
}
defer {
deinit(*enums_for_typedefs);
array_reset(*begin_end_string_args);
}
output_filename: string;
opts: Generate_Bindings_Options;
{
using opts;
#if OS == .WINDOWS {
array_add(*libraries, .{filename="ImGui.static"});
array_add(*library_search_paths, "windows");
output_filename = "windows/windows.jai";
generate_library_declarations = false;
footer = #string EOF
imgui :: #library,no_dll "ImGui.static";
EOF
} else {
#if OS == .LINUX {
array_add(*library_search_paths, "linux");
} else #if OS == .MACOS {
array_add(*library_search_paths, "macos");
} else {
assert(false);
}
footer = FOOTER_UNIX;
output_filename = "unix.jai";
array_add(*libraries, .{filename="ImGui"});
generate_library_declarations = false;
}
array_add(*include_paths, IMGUI_PATH);
array_add(*source_files, tprint("%/imgui.h", IMGUI_PATH));
array_add(*extra_clang_arguments, "-x", "c++", "-DWIN32_LEAN_AND_MEAN", "-DIMGUI_USE_WCHAR32");
array_add(*flatten_namespaces, "ImGui");
array_add(*strip_prefixes, "ImGui");
auto_detect_enum_prefixes = false;
log_stripped_declarations = false;
generate_compile_time_struct_checks = false;
strip_flags |= .INLINED_FUNCTIONS; // "inline" functions are not present in the static library.
visitor = imgui_visitor;
get_func_args_for_printing = imgui_get_function_arg_for_printing;
}
success := generate_bindings(opts, output_filename);
return success;
}
compile_and_generate_bindings_backend_sdl3_gpu3 :: (compile_debug: bool) -> bool {
success := false;
array_add(*prefixes_to_keep, "ImGui_ImplSDL3_", "ImGui_ImplSDLGPU3_");
src_files := string.[
tprint("%/backends/imgui_impl_sdl3.cpp", IMGUI_PATH),
tprint("%/backends/imgui_impl_sdlgpu3.cpp", IMGUI_PATH),
];
// Generate binaries
#if OS == .WINDOWS {
make_directory_if_it_does_not_exist("backends/windows", recursive=true);
// We set IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS. Otherwise all users of the ImGui module have to link against "imm32".
success = build_cpp_static_lib("backends/windows/ImGui_sdl3_gpu3.static", ..src_files, extra = .["/DIMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS", "/DIMGUI_USE_WCHAR32", tprint("/I%", IMGUI_PATH), tprint("/I%", SDL3_INCLUDE_PATH)], debug=compile_debug);
if !success return false;
} else #if OS == .MACOS {
#import "Process";
make_directory_if_it_does_not_exist("backends/macos", recursive=true);
// @Hack: Is -fno-threadsafe-statics really the way to go? Without it, we get linker errors when using the static library
// due to "__cxa_guard_acquire" et al not being defined. The library users probably need to link agains libc++ or something. But do we really want to enforce that?
success = build_cpp_static_lib("backends/macos/ImGui_sdl3_gpu3", ..src_files, extra = .["-arch", "arm64", "-std=c++20", "-fno-exceptions", "-fno-rtti", "-fno-threadsafe-statics", "-DIMGUI_USE_WCHAR32", macos_arm64_version_arg, tprint("-I%", IMGUI_PATH), tprint("-I%", SDL3_INCLUDE_PATH)], debug=compile_debug);
assert(success, "build_cpp_static_lib('backends/macos/ImGui_sdl3_gpu3'");
} else #if OS == .LINUX {
#import "Process";
make_directory_if_it_does_not_exist("backends/linux", recursive=true);
// @Hack: Is -fno-threadsafe-statics really the way to go? Without it, we get linker errors when using the static library
// due to "__cxa_guard_acquire" et al not being defined. The library users probably need to link agains libc++ or something. But do we really want to enforce that?
success = build_cpp_static_lib("backends/linux/ImGui_sdl3_gpu3", ..src_files, extra = .["-std=c++20", "-fno-exceptions", "-fno-rtti", "-fno-threadsafe-statics", "-DIMGUI_USE_WCHAR32", tprint("-I%", IMGUI_PATH), tprint("-I%", SDL3_INCLUDE_PATH)], debug=compile_debug);
assert(success, "build_cpp_static_lib('backends/linux/ImGui_sdl3_gpu3'");
}
// Generate bindings
output_filename: string;
opts: Generate_Bindings_Options;
{
using opts;
#if OS == .WINDOWS {
array_add(*library_search_paths, "backends/windows");
array_add(*libraries, .{filename="ImGui_sdl3_gpu3.static"});
output_filename = "backends/windows/windows_sdl3_gpu3.jai";
generate_library_declarations = false;
footer = #string EOF
imgui_sdl3_gpu3 :: #library,no_dll "ImGui_sdl3_gpu3.static";
EOF
} else {
// "inline" functions are not present in the static library.
strip_flags |= .INLINED_FUNCTIONS;
#if OS == .LINUX {
array_add(*library_search_paths, "backends/linux");
} else #if OS == .MACOS {
array_add(*library_search_paths, "backends/macos");
// For some reason generation on macos doesn't find
// the library and so strips the functions we want.
//
// So for now we avoid stripping them and then do the horrible hack
// below to replace '#foreign __UnknownLib' and '#elsewhere __UnknownLib'
// with '#foreign imgui_sdl3_gpu3'
strip_flags &= ~.SYMBOLS_WITH_UNKNOWN_FOREIGN_LIBS;
} else {
assert(false);
}
array_add(*libraries, .{filename="ImGui_sdl3_gpu3"});
output_filename = "backends/unix_sdl3_gpu3.jai";
generate_library_declarations = false;
footer = #string END
#if OS == .MACOS imgui_sdl3_gpu3 :: #library,no_dll "macos/ImGui_sdl3_gpu3";
#if OS == .LINUX imgui_sdl3_gpu3 :: #library,no_dll "linux/ImGui_sdl3_gpu3";
END
}
array_add(*include_paths, IMGUI_PATH, SDL3_INCLUDE_PATH);
array_add(*source_files, tprint("%/backends/imgui_impl_sdl3.h", IMGUI_PATH), tprint("%/backends/imgui_impl_sdlgpu3.h", IMGUI_PATH));
array_add(*extra_clang_arguments, "-x", "c++", "-DWIN32_LEAN_AND_MEAN", "-DIMGUI_USE_WCHAR32");
array_add(*flatten_namespaces, "ImGui");
array_add(*strip_prefixes, "ImGui", "Imgui");
auto_detect_enum_prefixes = false;
log_stripped_declarations = false;
generate_compile_time_struct_checks = false;
visitor = imgui_visitor;
get_func_args_for_printing = imgui_get_function_arg_for_printing;
}
success = generate_bindings(opts, output_filename);
// Horrible hack but...
#if OS == .MACOS {
file_path := "backends/unix_sdl3_gpu3.jai";
defer {
if !success {
log("failed to replace invalid library name from generated MacOS bindings at '%'. You will have to manually replace '#foreign __UnknownLib' and '#elsewhere __UnknownLib' with '#foreign imgui_sdl3_gpu3' and '#elsewhere imgui_sdl3_gpu3'", file_path, flags=.ERROR);
}
}
contents, success= := read_entire_file(file_path);
if success {
defer free(contents);
replace1 := replace(contents, "#foreign __UnknownLib", "#foreign imgui_sdl3_gpu3");
defer free(replace1);
replace2 := replace(replace1, "#elsewhere __UnknownLib", "#elsewhere imgui_sdl3_gpu3");
defer free(replace2);
success = write_entire_file(file_path, replace2);
}
}
return success;
}
compile_and_generate_bindings_freetype :: (compile_debug: bool) -> bool {
success := false;
array_add(*prefixes_to_keep, "ImGuiFreeType", "ImGui_ImplFreeType");
src_files := string.[
tprint("%/misc/freetype/imgui_freetype.cpp", IMGUI_PATH),
];
// Generate binaries
#if OS == .WINDOWS {
make_directory_if_it_does_not_exist("misc/freetype/windows", recursive=true);
success = build_cpp_static_lib("misc/freetype/windows/ImGui_freetype.static", ..src_files,
extra = .[
tprint("/I%", IMGUI_PATH),
tprint("/I%", FREETYPE_INCLUDE_PATH),
tprint("/I%", join(IMGUI_PATH, "/misc/freetype",, temp)),
"/DIMGUI_USE_WCHAR32",
],
debug=compile_debug);
if !success return false;
} else #if OS == .LINUX {
#import "Process";
make_directory_if_it_does_not_exist("misc/freetype/linux");
success = build_cpp_static_lib("misc/freetype/linux/ImGui_freetype", ..src_files,
extra = .[
tprint("-I%", IMGUI_PATH),
tprint("-I%", FREETYPE_INCLUDE_PATH),
tprint("-I%", join(IMGUI_PATH, "/misc/freetype",, temp)),
"-fno-exceptions", "-fno-rtti", "-fno-threadsafe-statics", "-fPIC",
"-DIMGUI_USE_WCHAR32",
],
debug=compile_debug);
assert(success, "build_cpp_static_lib('misc/freetype/linux/ImGui_freetype'");
} else #if OS == .MACOS {
#import "Process";
make_directory_if_it_does_not_exist("misc/freetype/macos", recursive=true);
success = build_cpp_static_lib("misc/freetype/macos/ImGui_freetype", ..src_files,
extra = .[
"-arch", "arm64", "-std=c++20", "-fno-exceptions", "-fno-rtti", "-fno-threadsafe-statics",
macos_arm64_version_arg,
tprint("-I%", IMGUI_PATH),
tprint("-I%", FREETYPE_INCLUDE_PATH),
join("-I", IMGUI_PATH, "/misc/freetype",, temp),
"-DIMGUI_USE_WCHAR32",
],
debug=compile_debug);
assert(success, "build_cpp_static_lib('misc/freetype/macos/ImGui_freetype'");
} else {
assert(false, "this OS is not supported");
}
if !success return false;
// Generate bindings
output_filename: string;
opts: Generate_Bindings_Options;
{
using opts;
#if OS == .WINDOWS {
array_add(*library_search_paths, "misc/freetype/windows");
array_add(*libraries, .{filename="ImGui_freetype.static"});
output_filename = "misc/freetype/windows/windows_freetype.jai";
generate_library_declarations = false;
footer = #string EOF
imgui_freetype :: #library,no_dll "ImGui_freetype.static";
EOF
} else {
// "inline" functions are not present in the static library.
strip_flags |= .INLINED_FUNCTIONS;
#if OS == .LINUX {
array_add(*library_search_paths, "misc/freetype/linux");
} else #if OS == .MACOS {
array_add(*library_search_paths, "misc/freetype/macos");
// Same reason as SDL backend — macOS generator sometimes fails to find the lib
strip_flags &= ~.SYMBOLS_WITH_UNKNOWN_FOREIGN_LIBS;
} else {
assert(false);
}
array_add(*libraries, .{filename="ImGui_freetype"});
output_filename = "misc/freetype/unix_freetype.jai";
generate_library_declarations = false;
footer = #string END
#if OS == .MACOS imgui_freetype :: #library,no_dll "macos/ImGui_freetype";
#if OS == .LINUX imgui_freetype :: #library,no_dll "linux/ImGui_freetype";
END
}
array_add(*include_paths, IMGUI_PATH, join(IMGUI_PATH, "/misc/freetype"), FREETYPE_INCLUDE_PATH);
array_add(*source_files,
tprint("%/misc/freetype/imgui_freetype.h", IMGUI_PATH)
);
array_add(*extra_clang_arguments, "-x", "c++", "-DWIN32_LEAN_AND_MEAN", "-DIMGUI_USE_WCHAR32");
array_add(*strip_prefixes, "ImGui", "Imgui");
auto_detect_enum_prefixes = false;
log_stripped_declarations = false;
generate_compile_time_struct_checks = false;
visitor = imgui_visitor;
get_func_args_for_printing = imgui_get_function_arg_for_printing;
}
success = generate_bindings(opts, output_filename);
// Horrible macOS hack
#if OS == .MACOS {
file_path := "misc/freetype/unix_freetype.jai";
defer {
if !success {
log("failed to replace invalid library name from generated MacOS bindings at '%'. You will have to manually replace '#foreign __UnknownLib' and '#elsewhere __UnknownLib' with '#foreign imgui_freetype' and '#elsewhere imgui_freetype'", file_path, flags=.ERROR);
}
}
contents, read_ok := read_entire_file(file_path);
if read_ok {
defer free(contents);
replace1 := replace(contents, "#foreign __UnknownLib", "#foreign imgui_freetype");
defer free(replace1);
replace2 := replace(replace1, "#elsewhere __UnknownLib", "#elsewhere imgui_freetype");
defer free(replace2);
success = write_entire_file(file_path, replace2);
}
}
return success;
}
begin_end_string_args: [..]*Declaration;
enums_for_typedefs: Table(string, *Enum);
prefixes_to_keep: [..]string;
imgui_visitor :: (decl: *Declaration, parent_decl: *Declaration) -> Declaration_Visit_Result {
// The parent check is because if we have a struct to keep we want
// everything inside it to stay even if the names aren't to keep, and
// so we only apply this check on top-level declarations.
if prefixes_to_keep.count && !parent_decl {
found := false;
for prefixes_to_keep {
if begins_with(decl.name, it) {
found = true;
break;
}
}
if !found {
decl.decl_flags |= .OMIT_FROM_OUTPUT;
return .STOP;
}
}
// ImGui has "typedef ints" for each enum. but we want to just use the enums directly,
// so we get nice type safety, and .unary dots, etc.
//
// Look for declarations inside functions (i.e., arguments) or structs (i.e., fields)
get_associated_enum_name :: (name: string) -> string {
if name.count > 1 && name[name.count - 1] != #char "_" {
return tprint("%_", name);
}
return name;
}
if !parent_decl {
if array_find(DECLARATIONS_TO_OMIT, decl.name) {
decl.decl_flags |= .OMIT_FROM_OUTPUT;
return .STOP;
}
if decl.name == "ImGuiContext" {
decl.output_name = decl.name; // Don’t shorten ImGuiContext to Context as this would conflict
}
if decl.kind == .TYPEDEF {
old_name := decl.name;
if old_name {
new_name := get_associated_enum_name(old_name);
for context.generator.global_scope.members {
if it.kind != .ENUM || it.name != new_name continue;
en := cast(*Enum)it;
// Don't output the integer typedef
decl.decl_flags |= .OMIT_FROM_OUTPUT;
// Make sure the enums like "FocusFlags_" get outputted as "FocusFlags"
if en.output_name.count > 2 && en.output_name[en.output_name.count - 1] == #char "_" {
en.output_name.count -= 1;
}
// Keep the comment, if there is one:
if decl.comment.text && !en.comment.text {
en.comment = decl.comment;
}
table_add(*enums_for_typedefs, old_name, en);
break;
}
}
}
}
// Look for function arguments with "typedef" types.
if parent_decl &&
(parent_decl.kind == .FUNCTION || parent_decl.kind == .STRUCT) &&
decl.kind == .DECLARATION && decl.type.type_of_typedef != null
{
old_name := decl.type.type_of_typedef.name;
if old_name {
found, en := table_find(*enums_for_typedefs, old_name);
if found {
change_type_to_enum(decl, en);
}
}
}
if decl.kind == .ENUM {
// ImGui has lots of enums that should be enum flags. Luckily, they all end in "…Flags_".
if ends_with(decl.name, "Flags_") {
en := cast(*Enum)decl;
en.flags |= .IS_ENUM_FLAGS;
en.flags |= .VALUES_IN_HEX;
}
}
if decl.kind == .FUNCTION {
func := cast(*Function)decl;
type := func.type.type_of_function;
// ImGui also has functions with pairs of arguments like "text_begin" and "text_end"
// for the pointers to the beginning and end of a string. We'll generate wrappers for
// those function which take a jai string.
//
// Here we collect pointers to "_begin" arguments, and use them in
// get_func_args_for_printing below.
for type.arguments {
if it_index == 0 || !ends_with(it.name, "_end") continue;
arg_type := find_underlying_type(it.type);
if !arg_type.pointer_to || !(arg_type.pointer_to.number_flags & ._8BIT) continue; // Only convert *u8 ranges to string
name_part := slice(it.name, 0, it.name.count - "_end".count);
if !name_part continue;
// the first arg may appear as "text_begin" or just "text"
name_part_with_begin := tprint("%_begin", name_part);
prev_arg := type.arguments[it_index - 1];
if prev_arg.name == name_part || prev_arg.name == name_part_with_begin {
array_add(*begin_end_string_args, prev_arg);
func.decl_flags |= .NEEDS_ARGUMENT_WRAPPER;
}
}
}
return .RECURSE;
}
// Modify function with _begin and _end string arguments as they get printed.
// See the note above about "text_begin".
imgui_get_function_arg_for_printing :: (func: *Function, type: *Function_Type, mode: Arg_Print_Mode) -> []*Declaration {
args: [..]*Declaration;
for type.arguments {
if array_find(begin_end_string_args, it) {
if mode == {
case .OUTER_WRAPPER;
// For the outer wrapper, remove any "_begin" part of the argument, and
// change its type to a jai string
arg_copy := New(Declaration);
arg_copy.* = it.*;
arg_copy.type = context.generator.type_def_jai_string;
if ends_with(it.name, "_begin") {
arg_copy.output_name = slice(it.name, 0, it.name.count - "_begin".count);
}
array_add(*args, arg_copy);
it_index += 1; // skip the next argument-we collpase the _begin and _end args into one string arg
continue;
case .INNER_WRAPPER_CALL;
// For the inner wrapper function call, we pass the incoming arg
// as two arguments: "jai_string.data, jai_string.data + jai_string.count" -- corresponding
// to the _begin and _end parts.
base_arg_name := it.name;
if ends_with(it.name, "_begin") {
base_arg_name = slice(it.name, 0, it.name.count - "_begin".count);
}
arg_copy := New(Declaration);
arg_copy.* = it.*;
arg_copy.output_name = tprint("%.data", base_arg_name);
array_add(*args, arg_copy);
arg_copy_2 := New(Declaration);
arg_copy_2.* = it.*;
arg_copy_2.output_name = tprint("%.data + %.count", base_arg_name, base_arg_name);
array_add(*args, arg_copy_2);
it_index += 1; // skip the next arg, since we already added a second one above
continue;
}
}
array_add(*args, it);
}
return args;
}
#import "Basic";
#import "Bindings_Generator";
#import "BuildCpp";
#import "Compiler";
#import "File";
#import "Hash_Table";
#import "String";
FOOTER_UNIX :: #string END
#if OS == .MACOS imgui :: #library,no_dll "macos/ImGui";
#if OS == .LINUX imgui :: #library,no_dll "linux/ImGui";
END