522 lines
25 KiB
Plaintext
522 lines
25 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.31";
|
|
// 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);
|
|
|
|
// 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 {
|
|
// 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/>
|
|
</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)));
|
|
} 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";
|
|
}
|