// TaskForge build metaprogram. // // jai build.jai -> native Windows build into bin/ // jai build.jai - release -> optimized native build (LLVM) // jai build.jai - web -> WASM64 build into web/ // jai build.jai - server -> server for this OS into server/bin/ (optimized) // jai build.jai - server-linux -> Linux x64 server object file server/bin/taskforge-server.o, // linked on the droplet with: cc taskforge-server.o -o taskforge-server -lm -ldl -lpthread // // Every build also (re)bakes the font atlas from data/Karla-Regular.ttf using FreeType at compile time, // so the app itself never needs a font rasterizer at runtime (important for the browser build). #run build(); build :: () { set_build_options_dc(.{do_output = false}); args := get_build_options().compile_time_command_line; web := array_find(args, "web"); release := array_find(args, "release"); root := #filepath; if array_find(args, "server") || array_find(args, "server-linux") { build_server(root, array_find(args, "server-linux")); return; } font_bytes, ok := bake_fonts(tprint("%data/Karla-Regular.ttf", root)); if !ok { compiler_report("Font baking failed."); return; } out_dir := ifx web then "web/" else "bin/"; make_directory_if_it_does_not_exist(tprint("%1%2", root, out_dir)); write_entire_file(tprint("%1%2fonts.bin", root, out_dir), font_bytes); w := compiler_create_workspace(ifx web then "TaskForge Web" else "TaskForge"); options := get_build_options(w); copy_commonly_propagated_fields(get_build_options(), *options); options.output_path = out_dir; options.output_executable_name = ifx web then "taskforge" else "TaskForge"; import_paths: [..] string; array_add(*import_paths, tprint("%modules", root)); for options.import_path array_add(*import_paths, it); options.import_path = import_paths; if web { // Mirrors jai/examples/wasm. Needs a browser with WASM Memory64 (Chrome 133+, Firefox 134+). options.output_type = .EXECUTABLE; options.backend = .LLVM; options.os_target = .WASM; options.cpu_target = .CUSTOM; options.emit_debug_info = .NONE; options.backtrace_on_crash = .OFF; options.llvm_options.target_system_features = "+bulk-memory"; options.llvm_options.enable_split_modules = false; options.llvm_options.function_sections = true; STACK_SIZE :: 1024 * 1024; options.additional_linker_arguments = .["--stack-first", "-z", tprint("stack-size=%", STACK_SIZE), tprint("%data/walloc.o", root)]; if release set_optimization(*options, .OPTIMIZED); } else { if release { options.backend = .LLVM; set_optimization(*options, .OPTIMIZED); } } set_build_options(options, w); if web remap_import(w, "*", "Default_Allocator", "Walloc"); add_build_file(tprint("%src/main.jai", root), w); #if OS == .WINDOWS { // The native client uses libcurl (from the Jai distribution) to talk to the server. if !web for get_build_options().import_path { dll := tprint("%/Curl/windows/lib/libcurl.dll", it); if file_exists(dll) { copy_file(dll, tprint("%1%2libcurl.dll", root, out_dir)); break; } } } } build_server :: (root: string, linux: bool) { out_dir := tprint("%server/bin/", root); make_directory_if_it_does_not_exist(out_dir); w := compiler_create_workspace("TaskForge Server"); options := get_build_options(w); copy_commonly_propagated_fields(get_build_options(), *options); options.output_path = out_dir; options.output_executable_name = "taskforge-server"; options.backend = .LLVM; set_optimization(*options, .OPTIMIZED); if linux { // There are no Linux system libraries on this machine to link against, so we emit an object file // (with the runtime entry point) and link it on the server, where libc etc. exist. options.os_target = .LINUX; options.cpu_target = .X64; options.output_type = .OBJECT_FILE; options.runtime_support_definitions = .ENTRY_POINT_AND_INIT; } set_build_options(options, w); add_build_file(tprint("%server/server.jai", root), w); #if OS == .WINDOWS { if !linux copy_file(tprint("%server/dev/sqlite3.dll", root), tprint("%sqlite3.dll", out_dir)); } } // // Font atlas baking. // // File layout (little endian): // u32 magic 'TFNT', s32 atlas_w, s32 atlas_h, s32 font_count // per font: f32 size, f32 scale, f32 ascent, f32 descent, f32 line_height, s32 first_char, s32 glyph_count // per glyph: s16 x, y, w, h, bearing_x, bearing_y; f32 advance // u8 pixels[atlas_w * atlas_h] (single-channel coverage) // FONT_SIZES :: float.[12, 14, 16, 20, 26]; // Logical sizes used by the UI. Must match Font_Size in src/font.jai. FONT_SCALES :: float.[1, 2]; // Baked for 1x and 2x displays. FIRST_CHAR :: 32; LAST_CHAR :: 255; // ASCII + Latin-1. ATLAS_W :: 1024; ATLAS_MAX_H :: 4096; bake_fonts :: (ttf_path: string) -> string, bool { ttf, ok := read_entire_file(ttf_path); if !ok return "", false; lib: FT_Library; if FT_Init_FreeType(*lib) return "", false; face: FT_Face; if FT_New_Memory_Face(lib, ttf.data, cast(FT_Long) ttf.count, 0, *face) return "", false; pixels := NewArray(ATLAS_W * ATLAS_MAX_H, u8); meta: String_Builder; pen_x, pen_y, row_h := 1, 1, 0; font_count := 0; for scale: FONT_SCALES { for size: FONT_SIZES { px := cast(u32) (size * scale + 0.5); FT_Set_Pixel_Sizes(face, 0, px); m := face.size.metrics; put(*meta, size); put(*meta, scale); put(*meta, cast(float) m.ascender / 64.0); put(*meta, cast(float) m.descender / 64.0); put(*meta, cast(float) m.height / 64.0); put(*meta, cast(s32) FIRST_CHAR); put(*meta, cast(s32) (LAST_CHAR - FIRST_CHAR + 1)); for c: FIRST_CHAR..LAST_CHAR { gx, gy, gw, gh, bx, by: s16; advance: float; if FT_Load_Char(face, cast(FT_ULong) c, FT_LOAD_RENDER) == 0 { g := face.glyph; bm := g.bitmap; w := cast(int) bm.width; h := cast(int) bm.rows; if pen_x + w + 1 >= ATLAS_W { pen_x = 1; pen_y += row_h + 1; row_h = 0; } assert(pen_y + h < ATLAS_MAX_H); for y: 0..h-1 { src := bm.buffer + y * bm.pitch; dst := pixels.data + (pen_y + y) * ATLAS_W + pen_x; memcpy(dst, src, w); } gx, gy, gw, gh = xx pen_x, xx pen_y, xx w, xx h; bx, by = xx g.bitmap_left, xx g.bitmap_top; advance = cast(float) g.advance.x / 64.0; pen_x += w + 1; row_h = max(row_h, h); } put(*meta, gx); put(*meta, gy); put(*meta, gw); put(*meta, gh); put(*meta, bx); put(*meta, by); put(*meta, advance); } font_count += 1; } } atlas_h := pen_y + row_h + 1; atlas_h = (atlas_h + 3) & ~3; out: String_Builder; put(*out, cast(u32) 0x544E4654); // 'TFNT' put(*out, cast(s32) ATLAS_W); put(*out, cast(s32) atlas_h); put(*out, cast(s32) font_count); meta_string := builder_to_string(*meta); append(*out, meta_string); append(*out, pixels.data, ATLAS_W * atlas_h); FT_Done_Face(face); FT_Done_FreeType(lib); return builder_to_string(*out), true; } put :: (b: *String_Builder, value: $T) { v := value; append(b, cast(*u8) *v, size_of(T)); } #import "Basic"; #import "Compiler"; #import "File"; #import "File_Utilities"; #import "freetype";