ogl_beamforming

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

build.c (24420B)


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