ogl_beamforming

Ultrasound Beamforming Implemented with OpenGL
git clone anongit@rnpnr.xyz:ogl_beamforming.git
Log | Files | Refs | Feed | Submodules | README | LICENSE

build.c (22278B)


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