ogl_beamforming

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

build.c (27605B)


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