build.c (16422B)
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 */ 11 12 #define COMMON_FLAGS "-std=c11", "-Wall", "-Iexternal/include" 13 14 #define OUTDIR "out" 15 #define OUTPUT(s) OUTDIR "/" s 16 17 #include "util.h" 18 19 #include <stdarg.h> 20 #include <stdio.h> 21 22 #define is_aarch64 0 23 #define is_amd64 0 24 #define is_unix 0 25 #define is_w32 0 26 #define is_clang 0 27 28 #ifdef __ARM_ARCH_ISA_A64 29 #undef is_aarch64 30 #define is_aarch64 1 31 #elif __x86_64__ 32 #undef is_amd64 33 #define is_amd64 1 34 #else 35 #error unsupported architecture 36 #endif 37 38 #if defined(__linux__) 39 40 #undef is_unix 41 #define is_unix 1 42 43 #include <errno.h> 44 #include <string.h> 45 #include <sys/select.h> 46 #include <sys/wait.h> 47 48 #include "os_linux.c" 49 50 #define OS_SHARED_LIB(s) s ".so" 51 #define OS_MAIN "main_linux.c" 52 53 #elif defined(_WIN32) 54 55 #undef is_w32 56 #define is_w32 1 57 58 #include "os_win32.c" 59 60 #define OS_SHARED_LIB(s) s ".dll" 61 #define OS_MAIN "main_w32.c" 62 63 #else 64 #error Unsupported Platform 65 #endif 66 67 #ifdef __clang__ 68 #undef is_clang 69 #define is_clang 1 70 #define COMPILER "clang" 71 #else 72 #define COMPILER "cc" 73 #endif 74 75 #define shift(list, count) ((count)--, *(list)++) 76 77 #define da_append_count(a, s, items, item_count) do { \ 78 da_reserve((a), (s), (s)->count + (item_count)); \ 79 mem_copy((s)->data + (s)->count, (items), sizeof(*(items)) * (item_count)); \ 80 (s)->count += (item_count); \ 81 } while (0) 82 83 #define cmd_append_count da_append_count 84 #define cmd_append(a, s, ...) da_append_count(a, s, ((char *[]){__VA_ARGS__}), \ 85 (sizeof((char *[]){__VA_ARGS__}) / sizeof(char *))) 86 87 typedef struct { 88 char **data; 89 iz count; 90 iz capacity; 91 } CommandList; 92 93 typedef struct { 94 b32 debug; 95 b32 generic; 96 b32 report; 97 b32 sanitize; 98 } Options; 99 100 #define die(fmt, ...) die_("%s: " fmt, __FUNCTION__, ##__VA_ARGS__) 101 function void __attribute__((noreturn)) 102 die_(char *format, ...) 103 { 104 va_list ap; 105 va_start(ap, format); 106 /* TODO(rnp): proper log */ 107 vfprintf(stderr, format, ap); 108 va_end(ap); 109 os_fatal(s8("")); 110 } 111 112 function b32 113 s8_contains(s8 s, u8 byte) 114 { 115 b32 result = 0; 116 for (iz i = 0 ; !result && i < s.len; i++) 117 result |= s.data[i] == byte; 118 return result; 119 } 120 121 function void 122 stream_push_command(Stream *s, CommandList *c) 123 { 124 if (!s->errors) { 125 for (iz i = 0; i < c->count; i++) { 126 s8 item = c_str_to_s8(c->data[i]); 127 if (item.len) { 128 b32 escape = s8_contains(item, ' ') || s8_contains(item, '"'); 129 if (escape) stream_append_byte(s, '\''); 130 stream_append_s8(s, item); 131 if (escape) stream_append_byte(s, '\''); 132 if (i != c->count - 1) stream_append_byte(s, ' '); 133 } 134 } 135 } 136 } 137 138 #if defined(__unix__) 139 140 function b32 141 os_rename_file(char *name, char *new) 142 { 143 b32 result = rename(name, new) != -1; 144 return result; 145 } 146 147 function b32 148 os_remove_file(char *name) 149 { 150 b32 result = remove(name) != -1; 151 return result; 152 } 153 154 function void 155 os_make_directory(char *name) 156 { 157 mkdir(name, 0770); 158 } 159 160 function u64 161 os_get_filetime(char *file) 162 { 163 struct stat sb; 164 u64 result = (u64)-1; 165 if (stat(file, &sb) != -1) 166 result = sb.st_mtim.tv_sec; 167 return result; 168 } 169 170 function iptr 171 os_spawn_process(CommandList *cmd, Stream sb) 172 { 173 pid_t result = fork(); 174 switch (result) { 175 case -1: die("failed to fork command: %s: %s\n", cmd->data[0], strerror(errno)); break; 176 case 0: { 177 if (execvp(cmd->data[0], cmd->data) == -1) 178 die("failed to exec command: %s: %s\n", cmd->data[0], strerror(errno)); 179 unreachable(); 180 } break; 181 } 182 return (iptr)result; 183 } 184 185 function b32 186 os_wait_close_process(iptr handle) 187 { 188 b32 result = 0; 189 for (;;) { 190 i32 status; 191 iptr wait_pid = (iptr)waitpid(handle, &status, 0); 192 if (wait_pid == -1) 193 die("failed to wait on child process: %s\n", strerror(errno)); 194 if (wait_pid == handle) { 195 if (WIFEXITED(status)) { 196 status = WEXITSTATUS(status); 197 /* TODO(rnp): logging */ 198 result = status == 0; 199 break; 200 } 201 if (WIFSIGNALED(status)) { 202 /* TODO(rnp): logging */ 203 result = 0; 204 break; 205 } 206 } else { 207 /* TODO(rnp): handle multiple children */ 208 INVALID_CODE_PATH; 209 } 210 } 211 return result; 212 } 213 214 #elif defined(_WIN32) 215 216 enum { 217 MOVEFILE_REPLACE_EXISTING = 0x01, 218 }; 219 220 W32(b32) CreateDirectoryA(c8 *, void *); 221 W32(b32) CreateProcessA(u8 *, u8 *, iptr, iptr, b32, u32, iptr, u8 *, iptr, iptr); 222 W32(b32) GetExitCodeProcess(iptr handle, u32 *); 223 W32(b32) GetFileTime(iptr, iptr, iptr, iptr); 224 W32(b32) MoveFileExA(c8 *, c8 *, u32); 225 W32(u32) WaitForSingleObject(iptr, u32); 226 227 function void 228 os_make_directory(char *name) 229 { 230 CreateDirectoryA(name, 0); 231 } 232 233 function b32 234 os_rename_file(char *name, char *new) 235 { 236 b32 result = MoveFileExA(name, new, MOVEFILE_REPLACE_EXISTING) != 0; 237 return result; 238 } 239 240 function b32 241 os_remove_file(char *name) 242 { 243 b32 result = DeleteFileA(name); 244 return result; 245 } 246 247 function u64 248 os_get_filetime(char *file) 249 { 250 u64 result = (u64)-1; 251 iptr h = CreateFileA(file, 0, 0, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); 252 if (h != INVALID_FILE) { 253 struct { u32 low, high; } w32_filetime; 254 GetFileTime(h, 0, 0, (iptr)&w32_filetime); 255 result = (u64)w32_filetime.high << 32ULL | w32_filetime.low; 256 CloseHandle(h); 257 } 258 return result; 259 } 260 261 function iptr 262 os_spawn_process(CommandList *cmd, Stream sb) 263 { 264 struct { 265 u32 cb; 266 u8 *reserved, *desktop, *title; 267 u32 x, y, x_size, y_size, x_count_chars, y_count_chars; 268 u32 fill_attr, flags; 269 u16 show_window, reserved_2; 270 u8 *reserved_3; 271 iptr std_input, std_output, std_error; 272 } w32_startup_info = { 273 .cb = sizeof(w32_startup_info), 274 .flags = 0x100, 275 .std_input = GetStdHandle(STD_INPUT_HANDLE), 276 .std_output = GetStdHandle(STD_OUTPUT_HANDLE), 277 .std_error = GetStdHandle(STD_ERROR_HANDLE), 278 }; 279 280 struct { 281 iptr phandle, thandle; 282 u32 pid, tid; 283 } w32_process_info = {0}; 284 285 /* TODO(rnp): warn if we need to clamp last string */ 286 sb.widx = MIN(sb.widx, KB(32) - 1); 287 if (sb.widx < sb.cap) sb.data[sb.widx] = 0; 288 else sb.data[sb.widx - 1] = 0; 289 290 iptr result = INVALID_FILE; 291 if (CreateProcessA(0, sb.data, 0, 0, 1, 0, 0, 0, (iptr)&w32_startup_info, 292 (iptr)&w32_process_info)) 293 { 294 CloseHandle(w32_process_info.thandle); 295 result = w32_process_info.phandle; 296 } 297 return result; 298 } 299 300 function b32 301 os_wait_close_process(iptr handle) 302 { 303 b32 result = WaitForSingleObject(handle, -1) != 0xFFFFFFFFUL; 304 if (result) { 305 u32 status; 306 GetExitCodeProcess(handle, &status); 307 result = status == 0; 308 } 309 CloseHandle(handle); 310 return result; 311 } 312 313 #endif 314 315 #define needs_rebuild(b, ...) needs_rebuild_(b, ((char *[]){__VA_ARGS__}), \ 316 (sizeof((char *[]){__VA_ARGS__}) / sizeof(char *))) 317 function b32 318 needs_rebuild_(char *binary, char *deps[], iz deps_count) 319 { 320 u64 binary_filetime = os_get_filetime(binary); 321 b32 result = binary_filetime == (u64)-1; 322 for (iz i = 0; i < deps_count; i++) { 323 u64 filetime = os_get_filetime(deps[i]); 324 result |= (filetime == (u64)-1) | (filetime > binary_filetime); 325 } 326 return result; 327 } 328 329 function b32 330 run_synchronous(Arena a, CommandList *command) 331 { 332 Stream sb = arena_stream(a); 333 stream_push_command(&sb, command); 334 printf("%.*s\n", (i32)sb.widx, sb.data); 335 return os_wait_close_process(os_spawn_process(command, sb)); 336 } 337 338 function void 339 check_rebuild_self(Arena arena, i32 argc, char *argv[]) 340 { 341 char *binary = shift(argv, argc); 342 if (needs_rebuild(binary, __FILE__, "os_win32.c", "os_linux.c", "util.c", "util.h")) { 343 Stream name_buffer = arena_stream(arena); 344 stream_append_s8s(&name_buffer, c_str_to_s8(binary), s8(".old")); 345 char *old_name = (char *)arena_stream_commit_zero(&arena, &name_buffer).data; 346 347 if (!os_rename_file(binary, old_name)) 348 die("failed to move: %s -> %s\n", binary, old_name); 349 350 CommandList c = {0}; 351 cmd_append(&arena, &c, COMPILER, "-march=native", "-O3", COMMON_FLAGS); 352 cmd_append(&arena, &c, "-Wno-unused-function", __FILE__, "-o", binary, (void *)0); 353 if (!run_synchronous(arena, &c)) { 354 os_rename_file(old_name, binary); 355 die("failed to rebuild self\n"); 356 } 357 os_remove_file(old_name); 358 359 c.count = 0; 360 cmd_append(&arena, &c, binary); 361 cmd_append_count(&arena, &c, argv, argc); 362 cmd_append(&arena, &c, (void *)0); 363 if (!run_synchronous(arena, &c)) 364 os_exit(1); 365 366 os_exit(0); 367 } 368 } 369 370 function b32 371 s8_equal(s8 a, s8 b) 372 { 373 b32 result = a.len == b.len; 374 for (iz i = 0; result && i < a.len; i++) 375 result = a.data[i] == b.data[i]; 376 return result; 377 } 378 379 function void 380 usage(char *argv0) 381 { 382 die("%s [--debug] [--report] [--sanitize]\n" 383 " --debug: dynamically link and build with debug symbols\n" 384 " --generic: compile for a generic target (x86-64-v3 or aarch64 with NEON)\n" 385 " --report: print compilation stats (clang only)\n" 386 " --sanitize: build with ASAN and UBSAN\n" 387 , argv0); 388 } 389 390 function Options 391 parse_options(i32 argc, char *argv[]) 392 { 393 Options result = {0}; 394 395 char *argv0 = shift(argv, argc); 396 while (argc > 0) { 397 char *arg = shift(argv, argc); 398 s8 str = c_str_to_s8(arg); 399 if (s8_equal(str, s8("--debug"))) { 400 result.debug = 1; 401 } else if (s8_equal(str, s8("--generic"))) { 402 result.generic = 1; 403 } else if (s8_equal(str, s8("--report"))) { 404 result.report = 1; 405 } else if (s8_equal(str, s8("--sanitize"))) { 406 result.sanitize = 1; 407 } else { 408 usage(argv0); 409 } 410 } 411 412 return result; 413 } 414 415 function CommandList 416 cmd_base(Arena *a, Options *o) 417 { 418 CommandList result = {0}; 419 cmd_append(a, &result, COMPILER); 420 421 /* TODO(rnp): support cross compiling with clang */ 422 if (!o->generic) cmd_append(a, &result, "-march=native"); 423 else if (is_amd64) cmd_append(a, &result, "-march=x86-64-v3"); 424 else if (is_aarch64) cmd_append(a, &result, "-march=aarch64"); 425 426 cmd_append(a, &result, COMMON_FLAGS); 427 428 if (o->debug) cmd_append(a, &result, "-O0", "-D_DEBUG", "-Wno-unused-function"); 429 else cmd_append(a, &result, "-O3"); 430 431 if (o->debug && is_unix) cmd_append(a, &result, "-ggdb"); 432 433 if (o->sanitize) cmd_append(a, &result, "-fsanitize=address,undefined"); 434 435 if (o->report) { 436 if (is_clang) cmd_append(a, &result, "-fproc-stat-report"); 437 else printf("warning: timing not supported with this compiler\n"); 438 /* TODO(rnp): basic timing */ 439 } 440 441 return result; 442 } 443 444 /* NOTE(rnp): produce pdbs on w32 */ 445 function void 446 cmd_pdb(Arena *a, CommandList *cmd) 447 { 448 if (is_w32 && is_clang) 449 cmd_append(a, cmd, "-fuse-ld=lld", "-g", "-gcodeview", "-Wl,--pdb="); 450 } 451 452 function void 453 git_submodule_update(Arena a, char *name) 454 { 455 CommandList git = {0}; 456 /* NOTE(rnp): cryptic bs needed to get a simple exit code if name is dirty */ 457 cmd_append(&a, &git, "git", "diff-index", "--quiet", "HEAD", "--", name, (void *)0); 458 if (!run_synchronous(a, &git)) { 459 git.count = 1; 460 cmd_append(&a, &git, "submodule", "update", "--init", "--depth=1", name, (void *)0); 461 if (!run_synchronous(a, &git)) 462 die("failed to clone required module: %s\n", name); 463 } 464 } 465 466 function b32 467 build_shared_library(Arena a, CommandList cc, char *name, char **deps, iz deps_count) 468 { 469 b32 result = 0; 470 cmd_append(&a, &cc, "-fPIC", "-shared"); 471 cmd_append_count(&a, &cc, deps, deps_count); 472 cmd_append(&a, &cc, "-o", name, (void *)0); 473 result = run_synchronous(a, &cc); 474 return result; 475 } 476 477 function b32 478 build_static_library(Arena a, CommandList cc, char *name, char **deps, char **outputs, iz count) 479 { 480 /* TODO(rnp): refactor to not need outputs */ 481 b32 result = 0; 482 b32 all_success = 1; 483 for (iz i = 0; i < count; i++) { 484 cmd_append(&a, &cc, "-c", deps[i], "-o", outputs[i], (void *)0); 485 all_success &= run_synchronous(a, &cc); 486 cc.count -= 5; 487 } 488 if (all_success) { 489 CommandList ar = {0}; 490 cmd_append(&a, &ar, "ar", "rc", name); 491 cmd_append_count(&a, &ar, outputs, count); 492 cmd_append(&a, &ar, (void *)0); 493 result = run_synchronous(a, &ar); 494 } 495 return result; 496 } 497 498 function void 499 check_build_raylib(Arena a, CommandList cc, b32 shared) 500 { 501 char *libraylib = shared ? OS_SHARED_LIB("libraylib") : OUTPUT("libraylib.a"); 502 503 if (needs_rebuild(libraylib, __FILE__, "external/include/rlgl.h", "external/raylib")) { 504 git_submodule_update(a, "external/raylib"); 505 os_copy_file("external/raylib/src/rlgl.h", "external/include/rlgl.h"); 506 507 if (is_unix) cmd_append(&a, &cc, "-D_GLFW_X11"); 508 cmd_append(&a, &cc, "-DPLATFORM_DESKTOP_GLFW", "-DGRAPHICS_API_OPENGL_43"); 509 cmd_append(&a, &cc, "-Wno-unused-but-set-variable"); 510 cmd_append(&a, &cc, "-Iexternal/raylib/src", "-Iexternal/raylib/src/external/glfw/include"); 511 #define RAYLIB_SOURCES \ 512 X(rglfw) \ 513 X(rshapes) \ 514 X(rtext) \ 515 X(rtextures) \ 516 X(utils) 517 #define X(name) "external/raylib/src/" #name ".c", 518 char *srcs[] = {"external/rcore_extended.c", RAYLIB_SOURCES}; 519 #undef X 520 #define X(name) OUTPUT(#name ".o"), 521 char *outs[] = {OUTPUT("rcore_extended.o"), RAYLIB_SOURCES}; 522 #undef X 523 524 b32 success; 525 if (shared) { 526 cmd_append(&a, &cc, "-DBUILD_LIBTYPE_SHARED", "-D_GLFW_BUILD_DLL"); 527 cmd_append(&a, &cc, "-fPIC", "-shared"); 528 cmd_pdb(&a, &cc); 529 cmd_append_count(&a, &cc, srcs, countof(srcs)); 530 cmd_append(&a, &cc, "-o", libraylib); 531 if (is_w32) cmd_append(&a, &cc, "-L.", "-lgdi32", "-lwinmm"); 532 cmd_append(&a, &cc, (void *)0); 533 success = run_synchronous(a, &cc); 534 } else { 535 success = build_static_library(a, cc, libraylib, srcs, outs, countof(srcs)); 536 } 537 if (!success) die("failed to build libary: %s\n", libraylib); 538 } 539 } 540 541 /* NOTE(rnp): gcc requires these to appear at the end for no reason at all */ 542 function void 543 cmd_append_ldflags(Arena *a, CommandList *cc, b32 shared) 544 { 545 cmd_pdb(a, cc); 546 cmd_append(a, cc, "-lm"); 547 if (shared && !is_w32) cmd_append(a, cc, "-Wl,-rpath,."); 548 if (shared) cmd_append(a, cc, "-L.", "-lraylib"); 549 if (is_w32) cmd_append(a, cc, "-lgdi32", "-lwinmm", "-lSynchronization"); 550 } 551 552 function b32 553 build_helper_library(Arena arena, CommandList cc) 554 { 555 ///////////// 556 // library 557 char *library = OUTPUT(OS_SHARED_LIB("ogl_beamformer_lib")); 558 char *srcs[] = {"helpers/ogl_beamformer_lib.c"}; 559 560 cmd_append(&arena, &cc, "-Wno-unused-function", "-fPIC", "-shared"); 561 cmd_append_count(&arena, &cc, srcs, countof(srcs)); 562 cmd_append(&arena, &cc, "-o", library); 563 if (is_w32) cmd_append(&arena, &cc, "-lSynchronization"); 564 cmd_append(&arena, &cc, (void *)0); 565 b32 result = run_synchronous(arena, &cc); 566 if (!result) fprintf(stderr, "failed to build: %s\n", library); 567 568 ///////////// 569 // header 570 char *lib_header_out = OUTPUT("ogl_beamformer_lib.h"); 571 572 b32 rebuild_lib_header = needs_rebuild(lib_header_out, "beamformer_parameters.h", 573 "helpers/ogl_beamformer_lib_base.h"); 574 if (rebuild_lib_header) { 575 s8 parameters_header = os_read_whole_file(&arena, "beamformer_parameters.h"); 576 s8 base_header = os_read_whole_file(&arena, "helpers/ogl_beamformer_lib_base.h"); 577 if (parameters_header.len != 0 && base_header.len != 0 && 578 parameters_header.data + parameters_header.len == base_header.data) 579 { 580 s8 output_file = parameters_header; 581 output_file.len += base_header.len; 582 os_write_new_file(lib_header_out, output_file); 583 } else { 584 result = 0; 585 fprintf(stderr, "failed to build: %s\n", lib_header_out); 586 } 587 } 588 589 return result; 590 } 591 592 i32 593 main(i32 argc, char *argv[]) 594 { 595 Arena arena = os_alloc_arena((Arena){0}, MB(8)); 596 check_rebuild_self(arena, argc, argv); 597 598 Options options = parse_options(argc, argv); 599 600 os_make_directory(OUTDIR); 601 602 CommandList c = cmd_base(&arena, &options); 603 check_build_raylib(arena, c, options.debug); 604 605 build_helper_library(arena, c); 606 607 ///////////////////////// 608 // hot reloadable portion 609 if (options.debug) { 610 iz c_count = c.count; 611 if (is_w32) cmd_append_ldflags(&arena, &c, 1); 612 if (!build_shared_library(arena, c, OS_SHARED_LIB("beamformer"), 613 (char *[]){"beamformer.c"}, 1)) 614 { 615 die("failed to build: " OS_SHARED_LIB("beamfomer") "\n"); 616 } 617 c.count = c_count; 618 } 619 620 ////////////////// 621 // static portion 622 cmd_append(&arena, &c, OS_MAIN, "-o", "ogl"); 623 if (!options.debug) cmd_append(&arena, &c, OUTPUT("libraylib.a")); 624 cmd_append_ldflags(&arena, &c, options.debug); 625 cmd_append(&arena, &c, (void *)0); 626 627 return !run_synchronous(arena, &c); 628 }