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