mcap/first.jai
Musa Mahmood 5dd62ac201 [0.1.35] OCR on the GPU runs as many detector and recognizer runs at once as fit in its memory
The workers ran gpu_workers (4) runs at once whatever the images: the server
model, four large images at a time, took 15 GB of a 16 GB GPU and spilled into
shared system memory, and a smaller GPU would fare far worse.

modules/OCR/budget.jai: each run reserves what it is expected to need, and
waits while the runs in flight or the GPU's free memory leave too little; one
run always goes. The need is measured when the engine is made, from the arena's
peak (AllocatorGetStats MaxInUse) over runs on two input sizes, so it holds for
any model and GPU: the mobile detector 6 MB + 137 MB a megapixel, the server's
4 MB + 1015, against 511 and 522 MB measured for a 2560 x 1440 tile. The limit
is the GPU's free memory less 10% (at least 512 MB), read at the start of each
ocr_process call (cudaMemGetInfo, ort_cuda_memory); the free memory is also read
as each run starts, since finished runs' blocks stay in the arena until the
stage ends.

gpu-bench, RTX 5080 laptop: the server model 335 ms a capture (854 before),
peak 13.1 GB (15.4), passes within 4%; the mobile model unchanged (140 ms,
5.7 GB), as its runs fit.
2026-10-17 21:33:32 -04:00

546 lines
27 KiB
Plaintext

MIN_COMPILER_VERSION :: Version_Info.{ major = 0, minor = 2, micro = 30 };
MAX_COMPILER_VERSION :: Version_Info.{ major = 0, minor = 2, micro = 99 };
// Rename the app here. APP_NAME is what people see: the window title, the tray tooltip and the
// macOS bundle. APP_FOLDER_NAME names the executable and the per-user data folder, so it should be
// one lowercase word. APP_BUNDLE_IDENTIFIER is macOS's reverse-DNS id for the bundle.
APP_NAME :: "mcap";
APP_FOLDER_NAME :: "mcap";
APP_BUNDLE_IDENTIFIER :: "com.example.mcap";
VERSION :: "0.1.35";
// Where everything goes: bin/, or bin-headless/ for builds made with no_crash_dialog, so automated
// builds never write over the ones you run by hand.
build_directory := "bin";
base_options: Build_Options;
// A workspace that has been started but whose COMPLETE message has not yet been seen.
Pending_Build :: struct {
workspace: Workspace;
executable_name: string;
optimized: bool;
failed: bool;
tool: bool; // a command-line tool, not the app: no icon, manifest or bundle
}
#run,stallable build_project();
build_project :: () {
check_compiler_version();
#if OS == .MACOS && CPU != .ARM64 {
compiler_report("This app builds for macOS on Apple silicon (arm64) only; run a native jai, not one under Rosetta (arch -x86_64).");
}
base_options = get_build_options();
args := base_options.compile_time_command_line;
full_release := array_find(args, "full_release");
build_release := full_release || array_find(args, "release");
build_debug := full_release || !build_release;
if full_release log("Building debug and release");
else if build_release log("Building in release mode");
else log("Building in debug mode");
if build_release && array_find(args, "compile_tests") log("Compiling tests into the release build");
// jai first.jai - no_crash_dialog [...]: for automated runs. NO_CRASH_DIALOG makes the app
// headless whatever its arguments (no message boxes; see runtime_support_headless), and the
// build goes to bin-headless/ instead of bin/.
if array_find(args, "no_crash_dialog") {
build_directory = "bin-headless";
log("Building with NO_CRASH_DIALOG into %/", build_directory);
}
set_build_options_dc(.{do_output = false});
set_working_directory(#filepath); // so that we load fonts correctly.
make_directory_if_it_does_not_exist(build_directory);
#if OS == .MACOS if !build_macos_shim() return;
// Workspaces compile concurrently once source is added, so every build is
// started before any is waited on.
pending: [..] Pending_Build;
if build_debug array_add(*pending, start_main_build(optimized = false));
if build_release array_add(*pending, start_main_build(optimized = true));
// jai first.jai - ocr_bench: also bin/ocr-bench, OCR timing and output over a folder of .mcap files.
if array_find(args, "ocr_bench") array_add(*pending, start_tool_build("ocr-bench", "tools/ocr_bench.jai", optimized = build_release));
// jai first.jai - image_probe: also bin/image-probe, what modules/Image_Decode reads from image files.
if array_find(args, "image_probe") array_add(*pending, start_tool_build("image-probe", "tools/image_probe.jai", optimized = build_release));
// jai first.jai - segment_probe: also bin/segment-probe, what modules/Segment makes of image files.
if array_find(args, "segment_probe") array_add(*pending, start_tool_build("segment-probe", "tools/segment_probe.jai", optimized = build_release));
// jai first.jai - record_probe: also bin/record-probe, a screen recorded with modules/Screen_Record (video-capture-plan.html, phase 1).
if array_find(args, "record_probe") array_add(*pending, start_tool_build("record-probe", "tools/record_probe.jai", optimized = build_release));
wait_for_builds(pending);
for pending if !it.failed {
if it.tool log("Built %/%", build_directory, it.executable_name);
else finish_main_build(it);
}
// The assets are the same for every configuration, so they are copied once.
for pending if !it.failed { copy_assets(); break; }
}
wait_for_builds :: (pending: [] Pending_Build) {
remaining := pending.count;
while remaining > 0 {
message := compiler_wait_for_message();
if message.kind != .COMPLETE continue;
for * pending {
if it.workspace != message.workspace continue;
m := cast(*Message_Complete) message;
it.failed = m.error_code == .COMPILATION_FAILED;
compiler_end_intercept(it.workspace);
remaining -= 1;
}
}
}
start_main_build :: (optimized: bool) -> Pending_Build {
log("Building main executable (%)...", ifx optimized then "release" else "debug");
options := base_options;
// Builds for the compiler's own CPU; on macOS that must be arm64 (see build_project).
#if OS == .MACOS
output_executable_name_temp := tprint("%-macos", APP_FOLDER_NAME);
#if OS == .LINUX
output_executable_name_temp := tprint("%-linux", APP_FOLDER_NAME);
#if OS == .WINDOWS
output_executable_name_temp := APP_FOLDER_NAME;
if (optimized) {
set_optimization(*options, .VERY_OPTIMIZED, preserve_debug_info=true);
options.backend = .LLVM;
options.llvm_options.enable_split_modules = false;
options.output_executable_name = output_executable_name_temp;
options.array_bounds_check = .ALWAYS;
options.null_pointer_check = .ON;
options.arithmetic_overflow_check = .FATAL;
options.cast_bounds_check = .FATAL;
options.dead_code_elimination = .ALL;
} else {
#if CPU == .X64 options.backend = .X64; // the fastest to compile; LLVM elsewhere
options.output_executable_name = tprint("%-debug", output_executable_name_temp);
}
#if OS == .MACOS options.minimum_os_version = MACOS_MINIMUM_VERSION;
#if OS == .WINDOWS {
file_delete(tprint("%/%.pdb", build_directory, options.output_executable_name));
file_delete(tprint("%/%.rdi", build_directory, options.output_executable_name));
}
options.temporary_storage_size = 64 * 4096;
options.output_path = build_directory;
w := compiler_create_workspace(options.output_executable_name);
set_build_options(options, w);
#if OS == .WINDOWS {
// Disable runtime console
if optimized {
set_build_options_dc(.{append_linker_arguments=.["/SUBSYSTEM:windows", "/ENTRY:mainCRTStartup", "/IGNORE:4099"]}, w);
} else {
set_build_options_dc(.{append_linker_arguments=.["msvcprtd.lib", "/IGNORE:4099"]}, w);
}
}
compiler_begin_intercept(w);
add_build_file("src/App.jai", w);
add_build_constants(w, optimized);
return .{ workspace = w, executable_name = options.output_executable_name, optimized = optimized };
}
start_tool_build :: (name: string, file: string, optimized: bool) -> Pending_Build {
log("Building % (%)...", name, ifx optimized then "release" else "debug");
options := base_options;
if optimized {
set_optimization(*options, .VERY_OPTIMIZED, preserve_debug_info=true);
options.backend = .LLVM;
options.output_executable_name = name;
} else {
#if CPU == .X64 options.backend = .X64;
options.output_executable_name = tprint("%-debug", name);
}
options.output_path = build_directory;
#if OS == .MACOS options.minimum_os_version = MACOS_MINIMUM_VERSION;
w := compiler_create_workspace(options.output_executable_name);
set_build_options(options, w);
#if OS == .WINDOWS set_build_options_dc(.{append_linker_arguments=.["/IGNORE:4099"]}, w);
compiler_begin_intercept(w);
add_build_file(file, w);
add_build_constants(w, optimized);
return .{ workspace = w, executable_name = options.output_executable_name, optimized = optimized, tool = true };
}
finish_main_build :: (build: Pending_Build) {
#if OS == .WINDOWS {
exe_path := tprint("%/%.exe", build_directory, build.executable_name);
if build.optimized {
ico_data := create_ico_file_from_bitmap_filename("assets/images/icon.png");
success := set_icon_by_data(exe_path, ico_data);
if !success {
log_error("ERROR: Couldn't set icon for '%'\n", exe_path);
}
manifest_options: Manifest_Options;
success = add_manifest_to_executable(exe_path, manifest_options);
if !success {
log_error("ERROR: Couldn't add manifest to executable '%'\n", exe_path);
}
}
} else #if OS == .MACOS {
create_macos_app_bundle(build.executable_name, build.optimized);
}
log("Built %/%", build_directory, build.executable_name);
}
#if OS == .MACOS {
// macOS: modules/Screen_Record/macos/capture.m, mcap's ScreenCaptureKit shim, compiled with clang
// into capture.a beside it whenever the source is newer, for the app to link (screen_capture_macos.jai).
// check_targets.jai has the same; keep the two in step.
build_macos_shim :: () -> bool {
FOLDER :: "modules/Screen_Record/macos";
source := tprint("%/capture.m", FOLDER);
object := tprint("%/capture.o", FOLDER);
library := tprint("%/capture.a", FOLDER);
source_time, _, source_ok := file_modtime_and_size(source);
header_time := file_modtime_and_size(tprint("%/capture.h", FOLDER));
library_time, _, library_ok := file_modtime_and_size(library);
if source_ok && library_ok && library_time >= source_time && library_time >= header_time return true;
result := run_command("clang", "-c", "-fobjc-arc", "-O2", "-Wall", "-mmacosx-version-min=14.0", source, "-o", object);
if result.exit_code != 0 { log_error("ERROR: clang could not compile %.", source); return false; }
file_delete(library);
result = run_command("ar", "rcs", library, object);
if result.exit_code != 0 { log_error("ERROR: ar could not make %.", library); return false; }
log("Built %", library);
return true;
}
// The oldest macOS it runs on: ScreenCaptureKit's screenshots need 14, and so does onnxruntime 1.30.
MACOS_MINIMUM_VERSION :: Build_Options.Os_Version.{ major = 14, minor = 0 };
// A self-signed code-signing identity, made once per Mac by Ex4's scripts/macos_dev_identity.sh
// and shared with it. macOS ties Screen Recording and the other privacy permissions to an app's
// signature, and an ad hoc one changes with every build, so they would be asked for again each
// time. Without it in the keychain, the bundle is signed ad hoc.
MACOS_SIGNING_IDENTITY :: "Ex4 Dev";
MACOS_BUNDLE_DYLIBS :: string.["libSDL3.0.dylib"];
// mcap.app for the release build, mcap-debug.app for the debug one, with an identifier of its own
// so that its permissions are granted apart. Each holds the executable, SDL3 and FFmpeg beside it,
// onnxruntime in Contents/Frameworks, and the icon, Info.plist and models in Contents/Resources.
// The app finds data/, logs/ and images/ in the folder holding the bundle (see
// app_install_directory), and the models in Contents/Resources (app_resource_directory).
//
// The bundle is updated in place, not made afresh: the models are some 900 MB, so they are copied
// only when changed, as APFS clones, which take no space of their own.
//
// Started with `open`, the bundle asks for permissions as itself; the bare executable started
// from a terminal would have them asked for, and granted, to the terminal.
create_macos_app_bundle :: (executable_name: string, optimized: bool) {
bundle_name := ifx optimized then tprint("%.app", APP_NAME) else tprint("%-debug.app", APP_NAME);
identifier := ifx optimized then APP_BUNDLE_IDENTIFIER else tprint("%.debug", APP_BUNDLE_IDENTIFIER);
display := ifx optimized then APP_NAME else tprint("% Debug", APP_NAME);
app := tprint("%/%", build_directory, bundle_name);
executable_folder := tprint("%/Contents/MacOS", app);
frameworks_folder := tprint("%/Contents/Frameworks", app);
resources_folder := tprint("%/Contents/Resources", app);
// The executable folder is made afresh, so a renamed executable leaves nothing behind.
run_command("rm", "-rf", executable_folder);
make_directory_if_it_does_not_exist(executable_folder, recursive = true);
make_directory_if_it_does_not_exist(frameworks_folder, recursive = true);
make_directory_if_it_does_not_exist(resources_folder, recursive = true);
copied := copy_file(tprint("%/%", build_directory, executable_name), tprint("%/%", executable_folder, executable_name));
for MACOS_BUNDLE_DYLIBS copied &&= copy_file(tprint("assets/%", it), tprint("%/%", executable_folder, it));
// FFmpeg, linked against, so beside the executable as SDL3 is (@loader_path).
for file_list("modules/ffmpeg/macos") if ends_with(it, ".dylib") copied &&= copy_file(it, tprint("%/%", executable_folder, path_filename(it)));
if !copied {
log_error("ERROR: Couldn't copy the executable and its dylibs into %\n", app);
return;
}
run_command("chmod", "+x", tprint("%/%", executable_folder, executable_name));
// onnxruntime, and the models, as copy_assets puts them beside the executable.
ORT_LIBRARY :: "modules/onnxruntime/macos/libonnxruntime.dylib";
if file_exists(ORT_LIBRARY) clone_if_changed(ORT_LIBRARY, tprint("%/libonnxruntime.dylib", frameworks_folder));
Model_Folder :: struct { module: string; folder: string; }
for Model_Folder.[.{"OCR", "ocr-models"}, .{"Labels", "label-models"}, .{"Segment", "segment-models"}] {
folder := tprint("%/%", resources_folder, it.folder);
make_directory_if_it_does_not_exist(folder);
clone_if_changed(tprint("modules/%/models.config", it.module), tprint("%/models.config", folder));
models := tprint("modules/%/models", it.module);
if file_exists(models) for file_list(models) clone_if_changed(it, tprint("%/%", folder, path_filename(it)));
if file_exists(models) clone_packages(models, folder);
}
// An .iconset of every size, made with sips, then iconutil: sips -s format icns fails on
// macOS 27 (error 13), and icon.png may be any square size.
icon := tprint("%/%.icns", resources_folder, APP_FOLDER_NAME);
if !file_exists(icon) {
iconset := tprint("%/%.iconset", build_directory, APP_FOLDER_NAME);
run_command("rm", "-rf", iconset);
make_directory_if_it_does_not_exist(iconset);
result: Process_Result;
for size: s32.[16, 32, 128, 256, 512] {
for scale: 1..2 {
pixels := tprint("%", size * scale);
name := ifx scale == 1 then tprint("icon_%x%.png", size, size) else tprint("icon_%x%@2x.png", size, size);
result = run_command("sips", "-z", pixels, pixels, "assets/images/icon.png", "--out", tprint("%/%", iconset, name));
if result.exit_code != 0 break size;
}
}
if result.exit_code == 0 result = run_command("iconutil", "-c", "icns", iconset, "-o", icon);
run_command("rm", "-rf", iconset);
if result.exit_code != 0 log_error("ERROR: Couldn't make the icon for %; the Dock will show the generic one.\n", app);
}
minimum := tprint("%.%", MACOS_MINIMUM_VERSION.major, MACOS_MINIMUM_VERSION.minor);
if !write_entire_file(tprint("%/Contents/Info.plist", app), tprint(MACOS_INFO_PLIST, display, display, executable_name, identifier, APP_FOLDER_NAME, VERSION, VERSION, minimum)) {
log_error("ERROR: Couldn't write %/Contents/Info.plist\n", app);
return;
}
log("Built % (signed %)", app, sign_macos_app_bundle(app));
}
// With MACOS_SIGNING_IDENTITY when the keychain has it, and otherwise ad hoc, which is still
// a signature over the whole bundle, as macOS expects. Returns what it was signed with.
sign_macos_app_bundle :: (app: string) -> string {
result, identities := run_command("security", "find-identity", "-p", "codesigning", capture_and_return_output = true);
if result.exit_code == 0 && contains(identities, tprint("\"%\"", MACOS_SIGNING_IDENTITY)) {
result = run_command("codesign", "--force", "--deep", "--sign", MACOS_SIGNING_IDENTITY, app);
if result.exit_code == 0 return MACOS_SIGNING_IDENTITY;
log_error("WARNING: Couldn't sign % as %; signing it ad hoc instead.\n", app, MACOS_SIGNING_IDENTITY);
} else {
log("No \"%\" identity in the keychain, so % is signed ad hoc, and macOS will ask for its permissions again after every build. Ex4's scripts/macos_dev_identity.sh makes one.", MACOS_SIGNING_IDENTITY, app);
}
result = run_command("codesign", "--force", "--deep", "--sign", "-", app);
if result.exit_code != 0 {
log_error("WARNING: Couldn't sign %; it may still run.\n", app);
return "not at all";
}
return "ad hoc";
}
// Core ML packages (.mlpackage, folders) in from, cloned whole into to: made afresh each build, which
// as clones costs no time or space.
clone_packages :: (from: string, to: string) {
visit_files(from, false, to, (info: *File_Visit_Info, to: string) {
if !ends_with(info.short_name, ".mlpackage") return;
target := tprint("%/%", to, info.short_name);
run_command("rm", "-rf", target);
result := run_command("cp", "-c", "-R", info.full_name, target);
if result.exit_code != 0 log_error("Could not copy % to %.", info.full_name, target);
}, visit_files = false, visit_directories = true);
}
// As copy_if_changed, but an APFS clone (cp -c), which shares the original's blocks.
clone_if_changed :: (from: string, to: string) {
from_time, from_size, from_ok := file_modtime_and_size(from);
to_time, to_size, to_ok := file_modtime_and_size(to);
if to_ok && from_ok && to_size == from_size && to_time >= from_time return;
file_delete(to);
result := run_command("cp", "-c", "-p", from, to);
if result.exit_code != 0 && !copy_file(from, to) log_error("Could not copy % to %.", from, to);
}
MACOS_INFO_PLIST :: #string PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key> <string>en</string>
<key>CFBundleDisplayName</key> <string>%</string>
<key>CFBundleName</key> <string>%</string>
<key>CFBundleExecutable</key> <string>%</string>
<key>CFBundleIdentifier</key> <string>%</string>
<key>CFBundleIconFile</key> <string>%.icns</string>
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
<key>CFBundlePackageType</key> <string>APPL</string>
<key>CFBundleShortVersionString</key> <string>%</string>
<key>CFBundleVersion</key> <string>%</string>
<key>LSMinimumSystemVersion</key> <string>%</string>
<key>NSHighResolutionCapable</key> <true/>
<key>NSMicrophoneUsageDescription</key> <string>mcap records the microphone into a screen recording when its Mic button is on.</string>
</dict>
</plist>
PLIST
}
copy_assets :: () {
copy_directory("assets", build_directory); // This puts the CONTENTS of assets into the build directory. Not sure if that's what we want, but I'll roll with it.
#if OS == .MACOS copy_file("assets/libSDL3.0.dylib", tprint("%/libSDL3.0.dylib", build_directory));
// OCR: onnxruntime beside the executable, where modules/onnxruntime looks for it first, and the
// models in bin/ocr-models. Both are fetched, not in git (see their fetch scripts), and large,
// so a file is only copied when it is missing or differs in size or time.
#if OS == .WINDOWS ORT_FOLDER :: "modules/onnxruntime/windows";
else #if OS == .LINUX ORT_FOLDER :: "modules/onnxruntime/linux";
else ORT_FOLDER :: "modules/onnxruntime/macos";
if file_exists(ORT_FOLDER) {
for file_list(ORT_FOLDER) {
name := path_filename(it);
if contains(name, ".dll") || contains(name, ".so") || contains(name, ".dylib") copy_if_changed(it, tprint("%/%", build_directory, name));
}
} else {
log("OCR: onnxruntime has not been fetched; run jai fetch.jai in modules/onnxruntime for OCR.");
}
make_directory_if_it_does_not_exist(tprint("%/ocr-models", build_directory));
copy_if_changed("modules/OCR/models.config", tprint("%/ocr-models/models.config", build_directory));
if file_exists("modules/OCR/models") {
for file_list("modules/OCR/models") copy_if_changed(it, tprint("%/ocr-models/%", build_directory, path_filename(it)));
#if OS == .MACOS clone_packages("modules/OCR/models", tprint("%/ocr-models", build_directory));
} else {
log("OCR: the models have not been fetched; run jai fetch_models.jai in modules/OCR for OCR.");
}
// Videos: FFmpeg's libraries beside the executable (modules/ffmpeg/windows and macos, in git), the
// same way. The executable finds the dylibs there through its @loader_path rpath.
#if OS == .WINDOWS {
for file_list("modules/ffmpeg/windows") if ends_with(it, ".dll") copy_if_changed(it, tprint("%/%", build_directory, path_filename(it)));
} else #if OS == .MACOS {
for file_list("modules/ffmpeg/macos") if ends_with(it, ".dylib") copy_if_changed(it, tprint("%/%", build_directory, path_filename(it)));
}
// Labels: its models in bin/label-models, the same way.
make_directory_if_it_does_not_exist(tprint("%/label-models", build_directory));
copy_if_changed("modules/Labels/models.config", tprint("%/label-models/models.config", build_directory));
if file_exists("modules/Labels/models") {
for file_list("modules/Labels/models") copy_if_changed(it, tprint("%/label-models/%", build_directory, path_filename(it)));
#if OS == .MACOS clone_packages("modules/Labels/models", tprint("%/label-models", build_directory));
} else {
log("Labels: the models have not been fetched; run jai fetch_models.jai in modules/Labels for labels.");
}
// Segmentation: its model in bin/segment-models, the same way (made by tools/export_mask2former.py).
make_directory_if_it_does_not_exist(tprint("%/segment-models", build_directory));
copy_if_changed("modules/Segment/models.config", tprint("%/segment-models/models.config", build_directory));
if file_exists("modules/Segment/models") {
for file_list("modules/Segment/models") copy_if_changed(it, tprint("%/segment-models/%", build_directory, path_filename(it)));
#if OS == .MACOS clone_packages("modules/Segment/models", tprint("%/segment-models", build_directory));
} else {
log("Segmentation: the model has not been made; see modules/Segment/README.md.");
}
}
copy_if_changed :: (from: string, to: string) {
from_time, from_size, from_ok := file_modtime_and_size(from);
to_time, to_size, to_ok := file_modtime_and_size(to);
if to_ok && from_ok && to_size == from_size && to_time >= from_time return;
if !copy_file(from, to) log_error("Could not copy % to %.", from, to);
}
add_build_constants :: (w: Workspace, optimized: bool) {
BUILD_DATE :: #run -> string {
current_time := to_calendar(current_time_consensus());
return calendar_to_string(current_time);
}
GIT_HASH :: #run -> string {
result, revision := run_command("git", "describe", "--always", "--dirty", "--exclude=*", capture_and_return_output = true);
if revision return trim(revision);
return "-";
}
GIT_BRANCH :: #run -> string {
result, branch_name := run_command("git", "branch", "--show-current", capture_and_return_output = true);
return trim(branch_name);
}
build_constants := tprint(#string STRING
APP_NAME :: "%";
APP_FOLDER_NAME :: "%";
GIT_BRANCH :: "%";
GIT_HASH :: "%";
VERSION :: "%";
BUILD_DATE :: "%";
DEBUG :: %;
COMPILE_TESTS :: %;
COMPILE_ARGS :: "%";
NO_CRASH_DIALOG :: %;
STRING,
escape_for_string_literal(APP_NAME),
escape_for_string_literal(APP_FOLDER_NAME),
escape_for_string_literal(GIT_BRANCH),
escape_for_string_literal(GIT_HASH),
VERSION,
BUILD_DATE,
ifx optimized then "false" else "true",
ifx !optimized || array_find(base_options.compile_time_command_line, "compile_tests") then "true" else "false",
escape_for_string_literal(join(..base_options.compile_time_command_line, " ")),
ifx array_find(base_options.compile_time_command_line, "no_crash_dialog") then "true" else "false"
);
add_build_string(build_constants, w);
}
check_compiler_version :: () {
version_info: Version_Info;
version_str := compiler_get_version_info(*version_info);
min_version_str := tprint("%.%.%", MIN_COMPILER_VERSION.major, MIN_COMPILER_VERSION.minor, MIN_COMPILER_VERSION.micro);
max_version_str := tprint("%.%.%", MAX_COMPILER_VERSION.major, MAX_COMPILER_VERSION.minor, MAX_COMPILER_VERSION.micro);
if compare_versions(version_info, MIN_COMPILER_VERSION) < 0 {
error := tprint("Compiler version '%' is below the minimum required version '%'", version_str, min_version_str);
compiler_report(error);
}
if compare_versions(version_info, MAX_COMPILER_VERSION) > 0 {
error := tprint("Compiler version '%' is above the maximum supported version '%'", version_str, max_version_str);
compiler_report(error);
}
}
// Lexicographic: a later component only matters when every earlier one is equal.
compare_versions :: (a: Version_Info, b: Version_Info) -> int {
if a.major != b.major return ifx a.major < b.major then -1 else 1;
if a.minor != b.minor return ifx a.minor < b.minor then -1 else 1;
if a.micro != b.micro return ifx a.micro < b.micro then -1 else 1;
return 0;
}
// For text spliced between double quotes in generated source.
escape_for_string_literal :: (s: string) -> string {
builder: String_Builder;
builder.allocator = temp;
for 0..s.count-1 {
c := s[it];
if c == {
case #char "\\"; append(*builder, "\\\\");
case #char "\""; append(*builder, "\\\"");
case #char "\n"; append(*builder, "\\n");
case #char "\r"; append(*builder, "\\r");
case; append(*builder, c);
}
}
return builder_to_string(*builder,, temp);
}
#import "Basic";
#import "Compiler";
#import "String";
#import "File";
#import "Process";
#import "File_Utilities";
#if OS == .WINDOWS {
#import "Ico_File";
#import "Windows_Resources";
#import "Windows_Utf8";
}