build.c (12226B)
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" 13 14 #define OUTDIR "out" 15 #define OUTPUT(s) OUTDIR "/" s 16 17 #include "compiler.h" 18 #include "util.h" 19 20 #include "options.h" 21 22 #include <stdarg.h> 23 #include <stdio.h> 24 25 #define is_aarch64 ARCH_ARM64 26 #define is_amd64 ARCH_X64 27 #define is_unix OS_LINUX 28 #define is_w32 OS_WINDOWS 29 #define is_clang COMPILER_CLANG 30 31 #if OS_LINUX 32 33 #include <errno.h> 34 #include <string.h> 35 #include <sys/select.h> 36 #include <sys/wait.h> 37 38 #include "os_linux.c" 39 40 #define OS_MAIN "main_linux.c" 41 42 #elif OS_WINDOWS 43 44 #include "os_win32.c" 45 46 #define OS_MAIN "main_w32.c" 47 48 #else 49 #error Unsupported Platform 50 #endif 51 52 #if COMPILER_CLANG 53 #define COMPILER "clang" 54 #elif COMPILER_MSVC 55 #define COMPILER "cl" 56 #else 57 #define COMPILER "cc" 58 #endif 59 60 #define shift(list, count) ((count)--, *(list)++) 61 62 #define da_append_count(a, s, items, item_count) do { \ 63 da_reserve((a), (s), (s)->count + (item_count)); \ 64 mem_copy((s)->data + (s)->count, (items), sizeof(*(items)) * (item_count)); \ 65 (s)->count += (item_count); \ 66 } while (0) 67 68 #define cmd_append_count da_append_count 69 #define cmd_append(a, s, ...) da_append_count(a, s, ((char *[]){__VA_ARGS__}), \ 70 (sizeof((char *[]){__VA_ARGS__}) / sizeof(char *))) 71 72 typedef struct { 73 char **data; 74 sz count; 75 sz capacity; 76 } CommandList; 77 78 typedef struct { 79 b32 debug; 80 b32 generic; 81 b32 report; 82 b32 sanitize; 83 84 b32 encode_video; 85 c8 *video_name; 86 } Options; 87 88 #define die(fmt, ...) die_("%s: " fmt, __FUNCTION__, ##__VA_ARGS__) 89 function void __attribute__((noreturn)) 90 die_(char *format, ...) 91 { 92 va_list ap; 93 va_start(ap, format); 94 /* TODO(rnp): proper log */ 95 vfprintf(stderr, format, ap); 96 va_end(ap); 97 os_fatal(str8("")); 98 } 99 100 function b32 101 str8_contains(str8 s, u8 byte) 102 { 103 b32 result = 0; 104 for (sz i = 0 ; !result && i < s.len; i++) 105 result |= s.data[i] == byte; 106 return result; 107 } 108 109 function void 110 stream_push_command(Stream *s, CommandList *c) 111 { 112 if (!s->errors) { 113 for (sz i = 0; i < c->count; i++) { 114 str8 item = c_str_to_str8(c->data[i]); 115 if (item.len) { 116 b32 escape = str8_contains(item, ' ') || str8_contains(item, '"'); 117 if (escape) stream_append_byte(s, '\''); 118 stream_append_str8(s, item); 119 if (escape) stream_append_byte(s, '\''); 120 if (i != c->count - 1) stream_append_byte(s, ' '); 121 } 122 } 123 } 124 } 125 126 #if OS_LINUX 127 128 function b32 129 os_rename_file(char *name, char *new) 130 { 131 b32 result = rename(name, new) != -1; 132 return result; 133 } 134 135 function b32 136 os_remove_file(char *name) 137 { 138 b32 result = remove(name) != -1; 139 return result; 140 } 141 142 function u64 143 os_get_filetime(char *file) 144 { 145 struct stat sb; 146 u64 result = (u64)-1; 147 if (stat(file, &sb) != -1) 148 result = sb.st_mtim.tv_sec; 149 return result; 150 } 151 152 function sptr 153 os_spawn_process(CommandList *cmd, Stream sb) 154 { 155 pid_t result = fork(); 156 switch (result) { 157 case -1: die("failed to fork command: %s: %s\n", cmd->data[0], strerror(errno)); break; 158 case 0: { 159 if (execvp(cmd->data[0], cmd->data) == -1) 160 die("failed to exec command: %s: %s\n", cmd->data[0], strerror(errno)); 161 unreachable(); 162 } break; 163 } 164 return (sptr)result; 165 } 166 167 function b32 168 os_wait_close_process(sptr handle) 169 { 170 b32 result = 0; 171 for (;;) { 172 s32 status; 173 sptr wait_pid = (sptr)waitpid(handle, &status, 0); 174 if (wait_pid == -1) 175 die("failed to wait on child process: %s\n", strerror(errno)); 176 if (wait_pid == handle) { 177 if (WIFEXITED(status)) { 178 status = WEXITSTATUS(status); 179 /* TODO(rnp): logging */ 180 result = status == 0; 181 break; 182 } 183 if (WIFSIGNALED(status)) { 184 /* TODO(rnp): logging */ 185 result = 0; 186 break; 187 } 188 } else { 189 /* TODO(rnp): handle multiple children */ 190 InvalidCodePath; 191 } 192 } 193 return result; 194 } 195 196 #elif OS_WINDOWS 197 198 enum { 199 MOVEFILE_REPLACE_EXISTING = 0x01, 200 }; 201 202 W32(b32) CreateProcessA(u8 *, u8 *, sptr, sptr, b32, u32, sptr, u8 *, sptr, sptr); 203 W32(b32) DeleteFileA(c8 *); 204 W32(b32) GetExitCodeProcess(sptr handle, u32 *); 205 W32(b32) GetFileTime(sptr, sptr, sptr, sptr); 206 W32(b32) MoveFileExA(c8 *, c8 *, u32); 207 W32(u32) WaitForSingleObject(sptr, u32); 208 209 function b32 210 os_rename_file(char *name, char *new) 211 { 212 b32 result = MoveFileExA(name, new, MOVEFILE_REPLACE_EXISTING) != 0; 213 return result; 214 } 215 216 function b32 217 os_remove_file(char *name) 218 { 219 b32 result = DeleteFileA(name); 220 return result; 221 } 222 223 function u64 224 os_get_filetime(char *file) 225 { 226 u64 result = (u64)-1; 227 sptr h = CreateFileA(file, 0, 0, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); 228 if (h != INVALID_FILE) { 229 struct { u32 low, high; } w32_filetime; 230 GetFileTime(h, 0, 0, (sptr)&w32_filetime); 231 result = (u64)w32_filetime.high << 32ULL | w32_filetime.low; 232 CloseHandle(h); 233 } 234 return result; 235 } 236 237 function sptr 238 os_spawn_process(CommandList *cmd, Stream sb) 239 { 240 struct { 241 u32 cb; 242 u8 *reserved, *desktop, *title; 243 u32 x, y, x_size, y_size, x_count_chars, y_count_chars; 244 u32 fill_attr, flags; 245 u16 show_window, reserved_2; 246 u8 *reserved_3; 247 sptr std_input, std_output, std_error; 248 } w32_startup_info = { 249 .cb = sizeof(w32_startup_info), 250 .flags = 0x100, 251 .std_input = GetStdHandle(STD_INPUT_HANDLE), 252 .std_output = GetStdHandle(STD_OUTPUT_HANDLE), 253 .std_error = GetStdHandle(STD_ERROR_HANDLE), 254 }; 255 256 struct { 257 sptr phandle, thandle; 258 u32 pid, tid; 259 } w32_process_info = {0}; 260 261 /* TODO(rnp): warn if we need to clamp last string */ 262 sb.widx = MIN(sb.widx, KB(32) - 1); 263 if (sb.widx < sb.cap) sb.data[sb.widx] = 0; 264 else sb.data[sb.widx - 1] = 0; 265 266 sptr result = INVALID_FILE; 267 if (CreateProcessA(0, sb.data, 0, 0, 1, 0, 0, 0, (sptr)&w32_startup_info, 268 (sptr)&w32_process_info)) 269 { 270 CloseHandle(w32_process_info.thandle); 271 result = w32_process_info.phandle; 272 } 273 return result; 274 } 275 276 function b32 277 os_wait_close_process(sptr handle) 278 { 279 b32 result = WaitForSingleObject(handle, -1) != 0xFFFFFFFFUL; 280 if (result) { 281 u32 status; 282 GetExitCodeProcess(handle, &status); 283 result = status == 0; 284 } 285 CloseHandle(handle); 286 return result; 287 } 288 289 #endif 290 291 #define needs_rebuild(b, ...) needs_rebuild_(b, ((char *[]){__VA_ARGS__}), \ 292 (sizeof((char *[]){__VA_ARGS__}) / sizeof(char *))) 293 function b32 294 needs_rebuild_(char *binary, char *deps[], sz deps_count) 295 { 296 u64 binary_filetime = os_get_filetime(binary); 297 b32 result = binary_filetime == (u64)-1; 298 for (sz i = 0; i < deps_count; i++) { 299 u64 filetime = os_get_filetime(deps[i]); 300 result |= (filetime == (u64)-1) | (filetime > binary_filetime); 301 } 302 return result; 303 } 304 305 function b32 306 run_synchronous(Arena a, CommandList *command) 307 { 308 Stream sb = arena_stream(a); 309 stream_push_command(&sb, command); 310 printf("%.*s\n", (s32)sb.widx, sb.data); 311 return os_wait_close_process(os_spawn_process(command, sb)); 312 } 313 314 function void 315 check_rebuild_self(Arena arena, s32 argc, char *argv[]) 316 { 317 char *binary = shift(argv, argc); 318 if (needs_rebuild(binary, __FILE__, "os_win32.c", "os_linux.c", "util.c", "util.h", "options.h")) { 319 Stream name_buffer = arena_stream(arena); 320 stream_append_str8s(&name_buffer, c_str_to_str8(binary), str8(".old")); 321 char *old_name = (char *)arena_stream_commit_zero(&arena, &name_buffer).data; 322 323 if (!os_rename_file(binary, old_name)) 324 die("failed to move: %s -> %s\n", binary, old_name); 325 326 CommandList c = {0}; 327 cmd_append(&arena, &c, COMPILER, "-march=native", "-O3", COMMON_FLAGS); 328 if (is_w32 && is_clang) cmd_append(&arena, &c, "-fms-extensions"); 329 cmd_append(&arena, &c, "-Wno-unused-function", __FILE__, "-o", binary, (void *)0); 330 if (!run_synchronous(arena, &c)) { 331 os_rename_file(old_name, binary); 332 die("failed to rebuild self\n"); 333 } 334 os_remove_file(old_name); 335 336 c.count = 0; 337 cmd_append(&arena, &c, binary); 338 cmd_append_count(&arena, &c, argv, argc); 339 cmd_append(&arena, &c, (void *)0); 340 if (!run_synchronous(arena, &c)) 341 os_exit(1); 342 343 os_exit(0); 344 } 345 } 346 347 function b32 348 str8_equal(str8 a, str8 b) 349 { 350 b32 result = a.len == b.len; 351 for (sz i = 0; result && i < a.len; i++) 352 result = a.data[i] == b.data[i]; 353 return result; 354 } 355 356 function void 357 usage(char *argv0) 358 { 359 die("%s [--debug] [--report] [--sanitize] [--encode-video 'output']\n" 360 " --debug: dynamically link and build with debug symbols\n" 361 " --generic: compile for a generic target (x86-64-v3 or armv8 with NEON)\n" 362 " --report: print compilation stats (clang only)\n" 363 " --sanitize: build with ASAN and UBSAN\n" 364 " --encode-video: encode '" RAW_OUTPUT_PATH "' to 'output'\n" 365 , argv0); 366 } 367 368 function Options 369 parse_options(s32 argc, char *argv[]) 370 { 371 Options result = {0}; 372 373 char *argv0 = shift(argv, argc); 374 while (argc > 0) { 375 char *arg = shift(argv, argc); 376 str8 str = c_str_to_str8(arg); 377 if (str8_equal(str, str8("--debug"))) { 378 result.debug = 1; 379 } else if (str8_equal(str, str8("--encode-video"))) { 380 result.encode_video = 1; 381 if (argc) result.video_name = shift(argv, argc); 382 else usage(argv0); 383 } else if (str8_equal(str, str8("--generic"))) { 384 result.generic = 1; 385 } else if (str8_equal(str, str8("--report"))) { 386 result.report = 1; 387 } else if (str8_equal(str, str8("--sanitize"))) { 388 result.sanitize = 1; 389 } else { 390 usage(argv0); 391 } 392 } 393 394 return result; 395 } 396 397 function CommandList 398 cmd_base(Arena *a, Options *o) 399 { 400 CommandList result = {0}; 401 cmd_append(a, &result, COMPILER); 402 403 /* TODO(rnp): support cross compiling with clang */ 404 if (!o->generic) cmd_append(a, &result, "-march=native"); 405 else if (is_amd64) cmd_append(a, &result, "-march=x86-64-v3"); 406 else if (is_aarch64) cmd_append(a, &result, "-march=armv8"); 407 408 cmd_append(a, &result, COMMON_FLAGS); 409 410 if (o->debug) cmd_append(a, &result, "-O0", "-D_DEBUG", "-Wno-unused-function"); 411 else cmd_append(a, &result, "-O3"); 412 413 if (is_w32 && is_clang) cmd_append(a, &result, "-fms-extensions"); 414 415 if (o->debug && is_unix) cmd_append(a, &result, "-gdwarf-4"); 416 417 if (o->sanitize) cmd_append(a, &result, "-fsanitize=address,undefined"); 418 419 if (o->report) { 420 if (is_clang) cmd_append(a, &result, "-fproc-stat-report"); 421 else printf("warning: timing not supported with this compiler\n"); 422 /* TODO(rnp): basic timing */ 423 } 424 425 return result; 426 } 427 428 /* NOTE(rnp): produce pdbs on w32 */ 429 function void 430 cmd_pdb(Arena *a, CommandList *cmd) 431 { 432 if (is_w32 && is_clang) 433 cmd_append(a, cmd, "-fuse-ld=lld", "-g", "-gcodeview", "-Wl,--pdb="); 434 } 435 436 /* NOTE(rnp): gcc requires these to appear at the end for no reason at all */ 437 function void 438 cmd_append_ldflags(Arena *a, CommandList *cc, b32 shared) 439 { 440 cmd_pdb(a, cc); 441 if (is_w32) cmd_append(a, cc, "-lopengl32", "-lgdi32", "-lwinmm"); 442 if (is_unix) cmd_append(a, cc, "-lGL"); 443 } 444 445 function CommandList 446 cmd_encode_video(Arena *a, c8 *output_name) 447 { 448 CommandList result = {0}; 449 cmd_append(a, &result, "ffmpeg", "-y", 450 "-framerate", str(OUTPUT_FRAME_RATE), 451 "-f", "rawvideo", 452 "-pix_fmt", "abgr", 453 "-s:v", str(RENDER_TARGET_WIDTH) "x" str(RENDER_TARGET_HEIGHT), 454 "-i", RAW_OUTPUT_PATH, 455 "-c:v", "libx265", 456 "-crf", "22", 457 output_name, (void *)0); 458 return result; 459 } 460 461 extern s32 462 main(s32 argc, char *argv[]) 463 { 464 Arena arena = os_alloc_arena(MB(8)); 465 check_rebuild_self(arena, argc, argv); 466 467 Options options = parse_options(argc, argv); 468 469 CommandList c; 470 if (!options.encode_video) { 471 c = cmd_base(&arena, &options); 472 if (is_unix) cmd_append(&arena, &c, "-D_GLFW_X11"); 473 cmd_append(&arena, &c, "-Iexternal/glfw/include"); 474 475 cmd_append(&arena, &c, OS_MAIN, "external/rglfw.c", "-o", "volviewer"); 476 cmd_append_ldflags(&arena, &c, options.debug); 477 cmd_append(&arena, &c, (void *)0); 478 } else { 479 c = cmd_encode_video(&arena, options.video_name); 480 } 481 482 return !run_synchronous(arena, &c); 483 }