build.c (27605B)
1 /* See LICENSE for license details. */ 2 /* NOTE: inspired by nob: https://github.com/tsoding/nob.h */ 3 4 /* TODO(rnp): 5 * [ ]: bake shaders and font data into binary 6 * - for shaders there is a way of making a separate data section and referring 7 * to it with extern from the C source (bake both data and size) 8 * - use objcopy, maybe need linker script maybe command line flags for ld will work 9 * [ ]: cross compile/override baked compiler 10 * [ ]: msvc build doesn't detect out of date files correctly 11 * [ ]: seperate dwarf debug info 12 */ 13 #include <stdarg.h> 14 #include <stdio.h> 15 16 #include "util.h" 17 18 #include "beamformer_parameters.h" 19 20 global char *g_argv0; 21 22 #define OUTDIR "out" 23 #define OUTPUT(s) OUTDIR "/" s 24 25 #if COMPILER_MSVC 26 #define COMMON_FLAGS "-nologo", "-std:c11", "-Fo:" OUTDIR "\\", "-Z7", "-Zo" 27 #define DEBUG_FLAGS "-Od", "-D_DEBUG" 28 #define OPTIMIZED_FLAGS "-O2" 29 #define EXTRA_FLAGS "" 30 #else 31 #define COMMON_FLAGS "-std=c11", "-pipe", "-Wall" 32 #define DEBUG_FLAGS "-O0", "-D_DEBUG", "-Wno-unused-function" 33 #define OPTIMIZED_FLAGS "-O3" 34 #define EXTRA_FLAGS "-Werror", "-Wextra", "-Wshadow", "-Wconversion", "-Wno-unused-parameter", \ 35 "-Wno-error=unused-function", "-funsafe-math-optimizations", "-fno-math-errno" 36 #endif 37 38 #define is_aarch64 ARCH_ARM64 39 #define is_amd64 ARCH_X64 40 #define is_unix OS_LINUX 41 #define is_w32 OS_WINDOWS 42 #define is_clang COMPILER_CLANG 43 #define is_gcc COMPILER_GCC 44 #define is_msvc COMPILER_MSVC 45 46 #if OS_LINUX 47 48 #include <errno.h> 49 #include <string.h> 50 #include <sys/select.h> 51 #include <sys/wait.h> 52 53 #include "os_linux.c" 54 55 #define W32_DECL(x) 56 57 #define OS_SHARED_LINK_LIB(s) "lib" s ".so" 58 #define OS_SHARED_LIB(s) s ".so" 59 #define OS_STATIC_LIB(s) s ".a" 60 #define OS_MAIN "main_linux.c" 61 62 #elif OS_WINDOWS 63 64 #include "os_win32.c" 65 66 #define W32_DECL(x) x 67 68 #define OS_SHARED_LINK_LIB(s) s ".dll" 69 #define OS_SHARED_LIB(s) s ".dll" 70 #define OS_STATIC_LIB(s) s ".lib" 71 #define OS_MAIN "main_w32.c" 72 73 #else 74 #error Unsupported Platform 75 #endif 76 77 #if COMPILER_CLANG 78 #define COMPILER "clang" 79 #elif COMPILER_MSVC 80 #define COMPILER "cl" 81 #else 82 #define COMPILER "cc" 83 #endif 84 85 #if COMPILER_MSVC 86 #define LINK_LIB(name) name ".lib" 87 #define OBJECT(name) name ".obj" 88 #define OUTPUT_DLL(name) "/LD", "/Fe:", name 89 #define OUTPUT_LIB(name) "/out:" OUTPUT(name) 90 #define OUTPUT_EXE(name) "/Fe:", name 91 #define STATIC_LIBRARY_BEGIN(name) "lib", "/nologo", name 92 #else 93 #define LINK_LIB(name) "-l" name 94 #define OBJECT(name) name ".o" 95 #define OUTPUT_DLL(name) "-fPIC", "-shared", "-o", name 96 #define OUTPUT_LIB(name) OUTPUT(name) 97 #define OUTPUT_EXE(name) "-o", name 98 #define STATIC_LIBRARY_BEGIN(name) "ar", "rc", name 99 #endif 100 101 #define shift(list, count) ((count)--, *(list)++) 102 103 #define da_append_count(a, s, items, item_count) do { \ 104 da_reserve((a), (s), (s)->count + (item_count)); \ 105 mem_copy((s)->data + (s)->count, (items), sizeof(*(items)) * (uz)(item_count)); \ 106 (s)->count += (item_count); \ 107 } while (0) 108 109 #define cmd_append_count da_append_count 110 #define cmd_append(a, s, ...) da_append_count(a, s, ((char *[]){__VA_ARGS__}), \ 111 (iz)(sizeof((char *[]){__VA_ARGS__}) / sizeof(char *))) 112 113 typedef struct { 114 char **data; 115 iz count; 116 iz capacity; 117 } CommandList; 118 119 typedef struct { 120 b32 debug; 121 b32 generic; 122 b32 sanitize; 123 b32 tests; 124 b32 time; 125 } Options; 126 127 #define BUILD_LOG_KINDS \ 128 X(Error, "\x1B[31m[ERROR]\x1B[0m ") \ 129 X(Warning, "\x1B[33m[WARNING]\x1B[0m ") \ 130 X(Info, "\x1B[32m[INFO]\x1B[0m ") \ 131 X(Command, "\x1B[36m[COMMAND]\x1B[0m ") 132 #define X(t, ...) BuildLogKind_##t, 133 typedef enum {BUILD_LOG_KINDS BuildLogKind_Count} BuildLogKind; 134 #undef X 135 136 function void 137 build_log_base(BuildLogKind kind, char *format, va_list args) 138 { 139 #define X(t, pre) pre, 140 read_only local_persist char *prefixes[BuildLogKind_Count + 1] = {BUILD_LOG_KINDS "[INVALID] "}; 141 #undef X 142 FILE *out = kind == BuildLogKind_Error? stderr : stdout; 143 fputs(prefixes[MIN(kind, BuildLogKind_Count)], out); 144 vfprintf(out, format, args); 145 fputc('\n', out); 146 } 147 148 #define build_log_failure(format, ...) build_log(BuildLogKind_Error, \ 149 "failed to build: " format, ##__VA_ARGS__) 150 #define build_log_info(...) build_log(BuildLogKind_Info, ##__VA_ARGS__) 151 #define build_log_command(...) build_log(BuildLogKind_Command, ##__VA_ARGS__) 152 #define build_log_warning(...) build_log(BuildLogKind_Warning, ##__VA_ARGS__) 153 function void 154 build_log(BuildLogKind kind, char *format, ...) 155 { 156 va_list ap; 157 va_start(ap, format); 158 build_log_base(kind, format, ap); 159 va_end(ap); 160 } 161 162 #define build_fatal(fmt, ...) build_fatal_("%s: " fmt, __FUNCTION__, ##__VA_ARGS__) 163 function no_return void 164 build_fatal_(char *format, ...) 165 { 166 va_list ap; 167 va_start(ap, format); 168 build_log_base(BuildLogKind_Error, format, ap); 169 va_end(ap); 170 os_exit(1); 171 } 172 173 function b32 174 s8_contains(s8 s, u8 byte) 175 { 176 b32 result = 0; 177 for (iz i = 0 ; !result && i < s.len; i++) 178 result |= s.data[i] == byte; 179 return result; 180 } 181 182 function void 183 stream_push_command(Stream *s, CommandList *c) 184 { 185 if (!s->errors) { 186 for (iz i = 0; i < c->count; i++) { 187 s8 item = c_str_to_s8(c->data[i]); 188 if (item.len) { 189 b32 escape = s8_contains(item, ' ') || s8_contains(item, '"'); 190 if (escape) stream_append_byte(s, '\''); 191 stream_append_s8(s, item); 192 if (escape) stream_append_byte(s, '\''); 193 if (i != c->count - 1) stream_append_byte(s, ' '); 194 } 195 } 196 } 197 } 198 199 #if OS_LINUX 200 201 function b32 202 os_rename_file(char *name, char *new) 203 { 204 b32 result = rename(name, new) != -1; 205 return result; 206 } 207 208 function b32 209 os_remove_file(char *name) 210 { 211 b32 result = remove(name) != -1; 212 return result; 213 } 214 215 function void 216 os_make_directory(char *name) 217 { 218 mkdir(name, 0770); 219 } 220 221 function u64 222 os_get_filetime(char *file) 223 { 224 struct stat sb; 225 u64 result = (u64)-1; 226 if (stat(file, &sb) != -1) 227 result = (u64)sb.st_mtim.tv_sec; 228 return result; 229 } 230 231 function iptr 232 os_spawn_process(CommandList *cmd, Stream sb) 233 { 234 pid_t result = fork(); 235 switch (result) { 236 case -1: build_fatal("failed to fork command: %s: %s", cmd->data[0], strerror(errno)); break; 237 case 0: { 238 if (execvp(cmd->data[0], cmd->data) == -1) 239 build_fatal("failed to exec command: %s: %s", cmd->data[0], strerror(errno)); 240 unreachable(); 241 } break; 242 } 243 return (iptr)result; 244 } 245 246 function b32 247 os_wait_close_process(iptr handle) 248 { 249 b32 result = 0; 250 for (;;) { 251 i32 status; 252 iptr wait_pid = (iptr)waitpid((i32)handle, &status, 0); 253 if (wait_pid == -1) 254 build_fatal("failed to wait on child process: %s", strerror(errno)); 255 if (wait_pid == handle) { 256 if (WIFEXITED(status)) { 257 status = WEXITSTATUS(status); 258 /* TODO(rnp): logging */ 259 result = status == 0; 260 break; 261 } 262 if (WIFSIGNALED(status)) { 263 /* TODO(rnp): logging */ 264 result = 0; 265 break; 266 } 267 } else { 268 /* TODO(rnp): handle multiple children */ 269 INVALID_CODE_PATH; 270 } 271 } 272 return result; 273 } 274 275 #elif OS_WINDOWS 276 277 enum { 278 MOVEFILE_REPLACE_EXISTING = 0x01, 279 }; 280 281 W32(b32) CreateDirectoryA(c8 *, void *); 282 W32(b32) CreateProcessA(u8 *, u8 *, iptr, iptr, b32, u32, iptr, u8 *, iptr, iptr); 283 W32(b32) GetExitCodeProcess(iptr handle, u32 *); 284 W32(b32) GetFileTime(iptr, iptr, iptr, iptr); 285 W32(b32) MoveFileExA(c8 *, c8 *, u32); 286 287 function void 288 os_make_directory(char *name) 289 { 290 CreateDirectoryA(name, 0); 291 } 292 293 function b32 294 os_rename_file(char *name, char *new) 295 { 296 b32 result = MoveFileExA(name, new, MOVEFILE_REPLACE_EXISTING) != 0; 297 return result; 298 } 299 300 function b32 301 os_remove_file(char *name) 302 { 303 b32 result = DeleteFileA(name); 304 return result; 305 } 306 307 function u64 308 os_get_filetime(char *file) 309 { 310 u64 result = (u64)-1; 311 iptr h = CreateFileA(file, 0, 0, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); 312 if (h != INVALID_FILE) { 313 struct { u32 low, high; } w32_filetime; 314 GetFileTime(h, 0, 0, (iptr)&w32_filetime); 315 result = (u64)w32_filetime.high << 32ULL | w32_filetime.low; 316 CloseHandle(h); 317 } 318 return result; 319 } 320 321 function iptr 322 os_spawn_process(CommandList *cmd, Stream sb) 323 { 324 struct { 325 u32 cb; 326 u8 *reserved, *desktop, *title; 327 u32 x, y, x_size, y_size, x_count_chars, y_count_chars; 328 u32 fill_attr, flags; 329 u16 show_window, reserved_2; 330 u8 *reserved_3; 331 iptr std_input, std_output, std_error; 332 } w32_startup_info = { 333 .cb = sizeof(w32_startup_info), 334 .flags = 0x100, 335 .std_input = GetStdHandle(STD_INPUT_HANDLE), 336 .std_output = GetStdHandle(STD_OUTPUT_HANDLE), 337 .std_error = GetStdHandle(STD_ERROR_HANDLE), 338 }; 339 340 struct { 341 iptr phandle, thandle; 342 u32 pid, tid; 343 } w32_process_info = {0}; 344 345 /* TODO(rnp): warn if we need to clamp last string */ 346 sb.widx = MIN(sb.widx, (i32)(KB(32) - 1)); 347 if (sb.widx < sb.cap) sb.data[sb.widx] = 0; 348 else sb.data[sb.widx - 1] = 0; 349 350 iptr result = INVALID_FILE; 351 if (CreateProcessA(0, sb.data, 0, 0, 1, 0, 0, 0, (iptr)&w32_startup_info, 352 (iptr)&w32_process_info)) 353 { 354 CloseHandle(w32_process_info.thandle); 355 result = w32_process_info.phandle; 356 } 357 return result; 358 } 359 360 function b32 361 os_wait_close_process(iptr handle) 362 { 363 b32 result = WaitForSingleObject(handle, (u32)-1) != 0xFFFFFFFFUL; 364 if (result) { 365 u32 status; 366 GetExitCodeProcess(handle, &status); 367 result = status == 0; 368 } 369 CloseHandle(handle); 370 return result; 371 } 372 373 #endif 374 375 #define needs_rebuild(b, ...) needs_rebuild_(b, ((char *[]){__VA_ARGS__}), \ 376 (sizeof((char *[]){__VA_ARGS__}) / sizeof(char *))) 377 function b32 378 needs_rebuild_(char *binary, char *deps[], iz deps_count) 379 { 380 u64 binary_filetime = os_get_filetime(binary); 381 u64 argv0_filetime = os_get_filetime(g_argv0); 382 b32 result = (binary_filetime == (u64)-1) | (argv0_filetime > binary_filetime); 383 for (iz i = 0; i < deps_count; i++) { 384 u64 filetime = os_get_filetime(deps[i]); 385 result |= (filetime == (u64)-1) | (filetime > binary_filetime); 386 } 387 return result; 388 } 389 390 function b32 391 run_synchronous(Arena a, CommandList *command) 392 { 393 Stream sb = arena_stream(a); 394 stream_push_command(&sb, command); 395 build_log_command("%.*s", (i32)sb.widx, sb.data); 396 return os_wait_close_process(os_spawn_process(command, sb)); 397 } 398 399 function CommandList 400 cmd_base(Arena *a, Options *o) 401 { 402 CommandList result = {0}; 403 cmd_append(a, &result, COMPILER); 404 405 if (!is_msvc) { 406 /* TODO(rnp): support cross compiling with clang */ 407 if (!o->generic) cmd_append(a, &result, "-march=native"); 408 else if (is_amd64) cmd_append(a, &result, "-march=x86-64-v3"); 409 else if (is_aarch64) cmd_append(a, &result, "-march=armv8"); 410 } 411 412 cmd_append(a, &result, COMMON_FLAGS, "-Iexternal/include"); 413 if (o->debug) cmd_append(a, &result, DEBUG_FLAGS); 414 else cmd_append(a, &result, OPTIMIZED_FLAGS); 415 416 /* NOTE: ancient gcc bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80454 */ 417 if (is_gcc) cmd_append(a, &result, "-Wno-missing-braces"); 418 419 if (is_w32 && is_clang) cmd_append(a, &result, "-fms-extensions"); 420 421 if (o->debug && is_unix) cmd_append(a, &result, "-ggdb"); 422 423 /* NOTE(rnp): need to avoid w32-gcc for ci */ 424 b32 sanitize = !is_msvc && (o->debug || o->sanitize) && !(is_w32 && is_gcc); 425 if (sanitize) { 426 cmd_append(a, &result, "-fsanitize=address,undefined"); 427 /* NOTE(rnp): impossible to autodetect on GCC versions < 14 (ci has 13) */ 428 cmd_append(a, &result, "-DASAN_ACTIVE=1"); 429 } else { 430 cmd_append(a, &result, "-DASAN_ACTIVE=0"); 431 } 432 if (!sanitize && o->sanitize) build_log_warning("santizers not supported with this compiler"); 433 434 return result; 435 } 436 437 function void 438 check_rebuild_self(Arena arena, i32 argc, char *argv[]) 439 { 440 char *binary = shift(argv, argc); 441 if (needs_rebuild(binary, __FILE__, "os_win32.c", "os_linux.c", "util.c", "util.h", "beamformer_parameters.h")) { 442 Stream name_buffer = arena_stream(arena); 443 stream_append_s8s(&name_buffer, c_str_to_s8(binary), s8(".old")); 444 char *old_name = (char *)arena_stream_commit_zero(&arena, &name_buffer).data; 445 446 if (!os_rename_file(binary, old_name)) 447 build_fatal("failed to move: %s -> %s", binary, old_name); 448 449 Options options = {0}; 450 CommandList c = cmd_base(&arena, &options); 451 cmd_append(&arena, &c, EXTRA_FLAGS); 452 if (!is_msvc) cmd_append(&arena, &c, "-Wno-unused-function"); 453 cmd_append(&arena, &c, __FILE__, OUTPUT_EXE(binary)); 454 if (is_msvc) cmd_append(&arena, &c, "/link", "-incremental:no", "-opt:ref"); 455 cmd_append(&arena, &c, (void *)0); 456 if (!run_synchronous(arena, &c)) { 457 os_rename_file(old_name, binary); 458 build_fatal("failed to rebuild self"); 459 } 460 os_remove_file(old_name); 461 462 c.count = 0; 463 cmd_append(&arena, &c, binary); 464 cmd_append_count(&arena, &c, argv, argc); 465 cmd_append(&arena, &c, (void *)0); 466 if (!run_synchronous(arena, &c)) 467 os_exit(1); 468 469 os_exit(0); 470 } 471 } 472 473 function b32 474 s8_equal(s8 a, s8 b) 475 { 476 b32 result = a.len == b.len; 477 for (iz i = 0; result && i < a.len; i++) 478 result = a.data[i] == b.data[i]; 479 return result; 480 } 481 482 function void 483 usage(char *argv0) 484 { 485 printf("%s [--debug] [--sanitize] [--time]\n" 486 " --debug: dynamically link and build with debug symbols\n" 487 " --generic: compile for a generic target (x86-64-v3 or armv8 with NEON)\n" 488 " --sanitize: build with ASAN and UBSAN\n" 489 " --tests: also build programs in tests/\n" 490 " --time: print build time\n" 491 , argv0); 492 os_exit(0); 493 } 494 495 function Options 496 parse_options(i32 argc, char *argv[]) 497 { 498 Options result = {0}; 499 500 char *argv0 = shift(argv, argc); 501 while (argc > 0) { 502 char *arg = shift(argv, argc); 503 s8 str = c_str_to_s8(arg); 504 if (s8_equal(str, s8("--debug"))) { 505 result.debug = 1; 506 } else if (s8_equal(str, s8("--generic"))) { 507 result.generic = 1; 508 } else if (s8_equal(str, s8("--sanitize"))) { 509 result.sanitize = 1; 510 } else if (s8_equal(str, s8("--tests"))) { 511 result.tests = 1; 512 } else if (s8_equal(str, s8("--time"))) { 513 result.time = 1; 514 } else { 515 usage(argv0); 516 } 517 } 518 519 return result; 520 } 521 522 /* NOTE(rnp): produce pdbs on w32 */ 523 function void 524 cmd_pdb(Arena *a, CommandList *cmd, char *name) 525 { 526 if (is_w32 && is_clang) { 527 cmd_append(a, cmd, "-fuse-ld=lld", "-g", "-gcodeview", "-Wl,--pdb="); 528 } else if (is_msvc) { 529 Stream sb = arena_stream(*a); 530 stream_append_s8s(&sb, s8("-PDB:"), c_str_to_s8(name), s8(".pdb")); 531 char *pdb = (char *)arena_stream_commit_zero(a, &sb).data; 532 cmd_append(a, cmd, "/link", "-incremental:no", "-opt:ref", "-DEBUG", pdb); 533 } 534 } 535 536 function void 537 git_submodule_update(Arena a, char *name) 538 { 539 Stream sb = arena_stream(a); 540 stream_append_s8s(&sb, c_str_to_s8(name), s8(OS_PATH_SEPARATOR), s8(".git")); 541 arena_stream_commit_zero(&a, &sb); 542 543 CommandList git = {0}; 544 /* NOTE(rnp): cryptic bs needed to get a simple exit code if name is dirty */ 545 cmd_append(&a, &git, "git", "diff-index", "--quiet", "HEAD", "--", name, (void *)0); 546 if (!os_file_exists((c8 *)sb.data) || !run_synchronous(a, &git)) { 547 git.count = 1; 548 cmd_append(&a, &git, "submodule", "update", "--init", "--depth=1", name, (void *)0); 549 if (!run_synchronous(a, &git)) 550 build_fatal("failed to clone required module: %s", name); 551 } 552 } 553 554 function b32 555 build_shared_library(Arena a, CommandList cc, char *name, char *output, char **libs, iz libs_count, char **srcs, iz srcs_count) 556 { 557 cmd_append_count(&a, &cc, srcs, srcs_count); 558 cmd_append(&a, &cc, OUTPUT_DLL(output)); 559 cmd_pdb(&a, &cc, name); 560 cmd_append_count(&a, &cc, libs, libs_count); 561 cmd_append(&a, &cc, (void *)0); 562 b32 result = run_synchronous(a, &cc); 563 if (!result) build_log_failure("%s", output); 564 return result; 565 } 566 567 function b32 568 cc_single_file(Arena a, CommandList cc, b32 exe, char *src, char *dest, char **tail, iz tail_count) 569 { 570 char *executable[] = {src, is_msvc? "/Fe:" : "-o", dest}; 571 char *object[] = {is_msvc? "/c" : "-c", src, is_msvc? "/Fo:" : "-o", dest}; 572 cmd_append_count(&a, &cc, exe? executable : object, 573 exe? countof(executable) : countof(object)); 574 cmd_append_count(&a, &cc, tail, tail_count); 575 cmd_append(&a, &cc, (void *)0); 576 b32 result = run_synchronous(a, &cc); 577 if (!result) build_log_failure("%s", dest); 578 return result; 579 } 580 581 function b32 582 build_static_library_from_objects(Arena a, char *name, char **flags, iz flags_count, char **objects, iz count) 583 { 584 CommandList ar = {0}; 585 cmd_append(&a, &ar, STATIC_LIBRARY_BEGIN(name)); 586 cmd_append_count(&a, &ar, flags, flags_count); 587 cmd_append_count(&a, &ar, objects, count); 588 cmd_append(&a, &ar, (void *)0); 589 b32 result = run_synchronous(a, &ar); 590 if (!result) build_log_failure("%s", name); 591 return result; 592 } 593 594 function b32 595 build_static_library(Arena a, CommandList cc, char *name, char **deps, char **outputs, iz count) 596 { 597 /* TODO(rnp): refactor to not need outputs */ 598 b32 result = 1; 599 for (iz i = 0; i < count; i++) 600 result &= cc_single_file(a, cc, 0, deps[i], outputs[i], 0, 0); 601 if (result) result = build_static_library_from_objects(a, name, 0, 0, outputs, count); 602 return result; 603 } 604 605 function b32 606 check_build_raylib(Arena a, CommandList cc, b32 shared) 607 { 608 b32 result = 1; 609 char *libraylib = shared ? OS_SHARED_LINK_LIB("raylib") : OUTPUT_LIB(OS_STATIC_LIB("raylib")); 610 if (needs_rebuild(libraylib, __FILE__, "external/include/rlgl.h", "external/raylib")) { 611 git_submodule_update(a, "external/raylib"); 612 os_copy_file("external/raylib/src/rlgl.h", "external/include/rlgl.h"); 613 614 if (is_unix) cmd_append(&a, &cc, "-D_GLFW_X11"); 615 cmd_append(&a, &cc, "-DPLATFORM_DESKTOP_GLFW"); 616 if (!is_msvc) cmd_append(&a, &cc, "-Wno-unused-but-set-variable"); 617 cmd_append(&a, &cc, "-Iexternal/raylib/src", "-Iexternal/raylib/src/external/glfw/include"); 618 #define RAYLIB_SOURCES \ 619 X(rglfw) \ 620 X(rshapes) \ 621 X(rtext) \ 622 X(rtextures) \ 623 X(utils) 624 #define X(name) "external/raylib/src/" #name ".c", 625 char *srcs[] = {"external/rcore_extended.c", RAYLIB_SOURCES}; 626 #undef X 627 #define X(name) OUTPUT(OBJECT(#name)), 628 char *outs[] = {OUTPUT(OBJECT("rcore_extended")), RAYLIB_SOURCES}; 629 #undef X 630 631 if (shared) { 632 char *libs[] = {LINK_LIB("user32"), LINK_LIB("shell32"), LINK_LIB("gdi32"), LINK_LIB("winmm")}; 633 iz libs_count = is_w32 ? countof(libs) : 0; 634 cmd_append(&a, &cc, "-DBUILD_LIBTYPE_SHARED", "-D_GLFW_BUILD_DLL"); 635 result = build_shared_library(a, cc, "raylib", libraylib, libs, libs_count, srcs, countof(srcs)); 636 } else { 637 result = build_static_library(a, cc, libraylib, srcs, outs, countof(srcs)); 638 } 639 } 640 return result; 641 } 642 643 function b32 644 build_helper_library(Arena arena, CommandList cc) 645 { 646 ///////////// 647 // library 648 char *library = OUTPUT(OS_SHARED_LIB("ogl_beamformer_lib")); 649 char *libs[] = {LINK_LIB("Synchronization")}; 650 iz libs_count = is_w32 ? countof(libs) : 0; 651 652 if (!is_msvc) cmd_append(&arena, &cc, "-Wno-unused-function"); 653 b32 result = build_shared_library(arena, cc, "ogl_beamformer_lib", library, 654 libs, libs_count, 655 arg_list(char *, "helpers/ogl_beamformer_lib.c")); 656 657 ///////////// 658 // header 659 char *lib_header_out = OUTPUT("ogl_beamformer_lib.h"); 660 if (needs_rebuild(lib_header_out, "beamformer_parameters.h", "helpers/ogl_beamformer_lib_base.h")) { 661 s8 parameters_header = os_read_whole_file(&arena, "beamformer_parameters.h"); 662 s8 base_header = os_read_whole_file(&arena, "helpers/ogl_beamformer_lib_base.h"); 663 result = parameters_header.len != 0 && base_header.len != 0 && 664 parameters_header.data + parameters_header.len == base_header.data; 665 if (result) { 666 s8 output_file = parameters_header; 667 output_file.len += base_header.len; 668 result &= os_write_new_file(lib_header_out, output_file); 669 } 670 if (!result) build_log_failure("%s", lib_header_out); 671 } 672 673 return result; 674 } 675 676 function b32 677 build_beamformer_as_library(Arena arena, CommandList cc) 678 { 679 char *library = OS_SHARED_LIB("beamformer"); 680 char *libs[] = {!is_msvc? "-L." : "", LINK_LIB("raylib"), LINK_LIB("gdi32"), 681 LINK_LIB("shell32"), LINK_LIB("user32"), LINK_LIB("opengl32"), 682 LINK_LIB("winmm"), LINK_LIB("Synchronization"), OUTPUT("main.lib")}; 683 iz libs_count = is_w32 ? countof(libs) : 0; 684 cmd_append(&arena, &cc, "-D_BEAMFORMER_DLL"); 685 b32 result = build_shared_library(arena, cc, "beamformer", library, 686 libs, libs_count, arg_list(char *, "beamformer.c")); 687 return result; 688 } 689 690 function b32 691 build_tests(Arena arena, CommandList cc) 692 { 693 #define TEST_PROGRAMS \ 694 X("throughput", LINK_LIB("zstd"), W32_DECL(LINK_LIB("Synchronization"))) 695 696 os_make_directory(OUTPUT("tests")); 697 if (!is_msvc) cmd_append(&arena, &cc, "-Wno-unused-function"); 698 cmd_append(&arena, &cc, "-I.", "-Ihelpers"); 699 700 b32 result = 1; 701 iz cc_count = cc.count; 702 #define X(prog, ...) \ 703 cmd_pdb(&arena, &cc, prog); \ 704 result &= cc_single_file(arena, cc, 1, "tests/" prog ".c", \ 705 OUTPUT("tests/" prog), \ 706 arg_list(char *, ##__VA_ARGS__)); \ 707 cc.count = cc_count; 708 TEST_PROGRAMS 709 #undef X 710 return result; 711 } 712 713 714 typedef struct { 715 Stream stream; 716 i32 indentation_level; 717 } MetaprogramContext; 718 719 function b32 720 meta_write_and_reset(MetaprogramContext *m, char *file) 721 { 722 b32 result = os_write_new_file(file, stream_to_s8(&m->stream)); 723 if (!result) build_log_failure("%s", file); 724 m->stream.widx = 0; 725 m->indentation_level = 0; 726 return result; 727 } 728 729 function void 730 meta_indent(MetaprogramContext *m) 731 { 732 for (i32 count = m->indentation_level; count > 0; count--) 733 stream_append_byte(&m->stream, '\t'); 734 } 735 736 function void 737 meta_begin_scope(MetaprogramContext *m, s8 line) 738 { 739 meta_indent(m); 740 m->indentation_level++; 741 stream_append_s8s(&m->stream, line, s8("\n")); 742 } 743 744 function void 745 meta_push_line(MetaprogramContext *m, s8 line) 746 { 747 meta_indent(m); 748 stream_append_s8s(&m->stream, line, s8("\n")); 749 } 750 751 function void 752 meta_end_scope(MetaprogramContext *m, s8 line) 753 { 754 m->indentation_level--; 755 meta_push_line(m, line); 756 } 757 758 #define meta_begin_matlab_class_cracker(_1, _2, FN, ...) FN 759 #define meta_begin_matlab_class_1(m, name) meta_begin_scope(m, s8("classdef " name)) 760 #define meta_begin_matlab_class_2(m, name, type) \ 761 meta_begin_scope(m, s8("classdef " name " < " type)) 762 763 #define meta_begin_matlab_class(m, ...) \ 764 meta_begin_matlab_class_cracker(__VA_ARGS__, \ 765 meta_begin_matlab_class_2, \ 766 meta_begin_matlab_class_1)(m, __VA_ARGS__) 767 768 function void 769 meta_push_matlab_property(MetaprogramContext *m, s8 name, i64 length) 770 { 771 meta_indent(m); 772 stream_append_s8s(&m->stream, name, s8("(1,")); 773 stream_append_i64(&m->stream, length); 774 stream_append_s8(&m->stream, s8(")\n")); 775 } 776 777 function b32 778 meta_end_and_write_matlab(MetaprogramContext *m, char *path) 779 { 780 while (m->indentation_level > 0) meta_end_scope((m), s8("end")); 781 b32 result = meta_write_and_reset(m, path); 782 return result; 783 } 784 785 function b32 786 build_matlab_bindings(Arena arena) 787 { 788 b32 result = 1; 789 os_make_directory(OUTPUT("matlab")); 790 791 char *out = OUTPUT("matlab/OGLBeamformerLiveFeedbackFlags.m"); 792 /* NOTE(rnp): if one file is outdated all files are outdated */ 793 if (needs_rebuild(out, "beamformer_parameters.h")) { 794 /* TODO(rnp): recreate/clear directory incase these file names change */ 795 MetaprogramContext m = {.stream = arena_stream(arena)}; 796 797 #define X(name, flag, ...) meta_push_line(&m, s8(#name " (" str(flag) ")")); 798 meta_begin_matlab_class(&m, "OGLBeamformerLiveFeedbackFlags", "int32"); 799 meta_begin_scope(&m, s8("enumeration")); 800 BEAMFORMER_LIVE_IMAGING_DIRTY_FLAG_LIST 801 result &= meta_end_and_write_matlab(&m, out); 802 803 meta_begin_matlab_class(&m, "OGLBeamformerShaderStage", "int32"); 804 meta_begin_scope(&m, s8("enumeration")); 805 COMPUTE_SHADERS 806 result &= meta_end_and_write_matlab(&m, OUTPUT("matlab/OGLBeamformerShaderStage.m")); 807 808 meta_begin_matlab_class(&m, "OGLBeamformerDataKind", "int32"); 809 meta_begin_scope(&m, s8("enumeration")); 810 BEAMFORMER_DATA_KIND_LIST 811 result &= meta_end_and_write_matlab(&m, OUTPUT("matlab/OGLBeamformerDataKind.m")); 812 #undef X 813 814 #define X(name, __t, __s, elements, ...) meta_push_line(&m, s8(#name "(1," #elements ")")); 815 meta_begin_matlab_class(&m, "OGLBeamformerParameters"); 816 meta_begin_scope(&m, s8("properties")); 817 BEAMFORMER_PARAMS_HEAD 818 BEAMFORMER_UI_PARAMS 819 BEAMFORMER_PARAMS_TAIL 820 result &= meta_end_and_write_matlab(&m, OUTPUT("matlab/OGLBeamformerParameters.m")); 821 822 meta_begin_matlab_class(&m, "OGLBeamformerParametersHead"); 823 meta_begin_scope(&m, s8("properties")); 824 BEAMFORMER_PARAMS_HEAD 825 result &= meta_end_and_write_matlab(&m, OUTPUT("matlab/OGLBeamformerParametersHead.m")); 826 827 meta_begin_matlab_class(&m, "OGLBeamformerParametersUI"); 828 meta_begin_scope(&m, s8("properties")); 829 BEAMFORMER_UI_PARAMS 830 result &= meta_end_and_write_matlab(&m, OUTPUT("matlab/OGLBeamformerParametersUI.m")); 831 #undef X 832 833 #define X(name, __t, __s, elements, ...) meta_push_matlab_property(&m, s8(#name), elements); 834 meta_begin_matlab_class(&m, "OGLBeamformerLiveImagingParameters"); 835 meta_begin_scope(&m, s8("properties")); 836 BEAMFORMER_LIVE_IMAGING_PARAMETERS_LIST 837 result &= meta_end_and_write_matlab(&m, OUTPUT("matlab/OGLBeamformerLiveImagingParameters.m")); 838 #undef X 839 } 840 841 return result; 842 } 843 844 i32 845 main(i32 argc, char *argv[]) 846 { 847 u64 start_time = os_get_timer_counter(); 848 g_argv0 = argv[0]; 849 850 b32 result = 1; 851 Arena arena = os_alloc_arena(MB(8)); 852 check_rebuild_self(arena, argc, argv); 853 854 Options options = parse_options(argc, argv); 855 856 os_make_directory(OUTDIR); 857 858 CommandList c = cmd_base(&arena, &options); 859 if (!check_build_raylib(arena, c, options.debug)) return 1; 860 861 ///////////////////////////////////// 862 // extra flags (unusable for raylib) 863 cmd_append(&arena, &c, EXTRA_FLAGS); 864 865 ///////////////// 866 // helpers/tests 867 result &= build_matlab_bindings(arena); 868 result &= build_helper_library(arena, c); 869 if (options.tests) result &= build_tests(arena, c); 870 871 ////////////////// 872 // static portion 873 iz c_count = c.count; 874 cmd_append(&arena, &c, OS_MAIN, OUTPUT_EXE("ogl")); 875 cmd_pdb(&arena, &c, "ogl"); 876 if (options.debug) { 877 /* NOTE(rnp): (gnu) ld doesn't properly export global symbols without this */ 878 if (is_gcc) cmd_append(&arena, &c, "-Wl,--export-dynamic"); 879 880 if (!is_w32) cmd_append(&arena, &c, "-Wl,-rpath,."); 881 if (!is_msvc) cmd_append(&arena, &c, "-L."); 882 cmd_append(&arena, &c, LINK_LIB("raylib")); 883 } else { 884 cmd_append(&arena, &c, OUTPUT(OS_STATIC_LIB("raylib"))); 885 } 886 if (!is_msvc) cmd_append(&arena, &c, "-lm"); 887 if (is_unix) cmd_append(&arena, &c, "-lGL"); 888 if (is_w32) { 889 cmd_append(&arena, &c, LINK_LIB("user32"), LINK_LIB("shell32"), LINK_LIB("gdi32"), 890 LINK_LIB("opengl32"), LINK_LIB("winmm"), LINK_LIB("Synchronization")); 891 if (!is_msvc) cmd_append(&arena, &c, "-Wl,--out-implib," OUTPUT(OS_STATIC_LIB("main"))); 892 } 893 cmd_append(&arena, &c, (void *)0); 894 895 result &= run_synchronous(arena, &c); 896 c.count = c_count; 897 898 ///////////////////////// 899 // hot reloadable portion 900 // 901 // NOTE: this is built after main because on w32 we need to export 902 // gl function pointers for the reloadable portion to import 903 if (options.debug) { 904 if (is_msvc) { 905 build_static_library_from_objects(arena, OUTPUT_LIB(OS_STATIC_LIB("main")), 906 arg_list(char *, "/def", "/name:ogl.exe"), 907 arg_list(char *, OUTPUT(OBJECT("main_w32")))); 908 } 909 result &= build_beamformer_as_library(arena, c); 910 } 911 912 if (options.time) { 913 f64 seconds = (f64)(os_get_timer_counter() - start_time) / (f64)os_get_timer_frequency(); 914 build_log_info("took %0.03f [s]", seconds); 915 } 916 917 return result != 1; 918 }