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