Files
2026-10-29 23:40:41 +01:00

216 lines
8.2 KiB
Plaintext

// 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";