ogl_beamforming

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

build.c (16474B)


      1 /* See LICENSE for license details. */
      2 /* NOTE: inspired by nob: https://github.com/tsoding/nob.h */
      3 
      4 /* TODO(rnp):
      5  * [ ]: bake shaders and font data into binary
      6  *      - for shaders there is a way of making a separate data section and referring
      7  *        to it with extern from the C source (bake both data and size)
      8  *      - use objcopy, maybe need linker script maybe command line flags for ld will work
      9  * [ ]: cross compile/override baked compiler
     10  */
     11 
     12 #define COMMON_FLAGS "-std=c11", "-Wall", "-Iexternal/include"
     13 
     14 #define OUTDIR    "out"
     15 #define OUTPUT(s) OUTDIR "/" s
     16 
     17 #include "util.h"
     18 
     19 #include <stdarg.h>
     20 #include <stdio.h>
     21 
     22 #define is_aarch64 ARCH_ARM64
     23 #define is_amd64   ARCH_X64
     24 #define is_unix    OS_LINUX
     25 #define is_w32     OS_WINDOWS
     26 #define is_clang   COMPILER_CLANG
     27 
     28 #if OS_LINUX
     29 
     30   #include <errno.h>
     31   #include <string.h>
     32   #include <sys/select.h>
     33   #include <sys/wait.h>
     34 
     35   #include "os_linux.c"
     36 
     37   #define OS_SHARED_LIB(s)  s ".so"
     38   #define OS_MAIN "main_linux.c"
     39 
     40 #elif OS_WINDOWS
     41 
     42   #include "os_win32.c"
     43 
     44   #define OS_SHARED_LIB(s)  s ".dll"
     45   #define OS_MAIN "main_w32.c"
     46 
     47 #else
     48   #error Unsupported Platform
     49 #endif
     50 
     51 #if COMPILER_CLANG
     52   #define COMPILER "clang"
     53 #elif COMPILER_MSVC
     54   #define COMPILER "cl"
     55 #else
     56   #define COMPILER "cc"
     57 #endif
     58 
     59 #define shift(list, count) ((count)--, *(list)++)
     60 
     61 #define da_append_count(a, s, items, item_count) do { \
     62 	da_reserve((a), (s), (s)->count + (item_count));                            \
     63 	mem_copy((s)->data + (s)->count, (items), sizeof(*(items)) * (item_count)); \
     64 	(s)->count += (item_count);                                                 \
     65 } while (0)
     66 
     67 #define cmd_append_count da_append_count
     68 #define cmd_append(a, s, ...) da_append_count(a, s, ((char *[]){__VA_ARGS__}), \
     69                                              (sizeof((char *[]){__VA_ARGS__}) / sizeof(char *)))
     70 
     71 typedef struct {
     72 	char **data;
     73 	iz     count;
     74 	iz     capacity;
     75 } CommandList;
     76 
     77 typedef struct {
     78 	b32   debug;
     79 	b32   generic;
     80 	b32   report;
     81 	b32   sanitize;
     82 } Options;
     83 
     84 #define die(fmt, ...) die_("%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
     85 function no_return void
     86 die_(char *format, ...)
     87 {
     88 	va_list ap;
     89 	va_start(ap, format);
     90 	/* TODO(rnp): proper log */
     91 	vfprintf(stderr, format, ap);
     92 	va_end(ap);
     93 	os_fatal(s8(""));
     94 }
     95 
     96 function b32
     97 s8_contains(s8 s, u8 byte)
     98 {
     99 	b32 result = 0;
    100 	for (iz i = 0 ; !result && i < s.len; i++)
    101 		result |= s.data[i] == byte;
    102 	return result;
    103 }
    104 
    105 function void
    106 stream_push_command(Stream *s, CommandList *c)
    107 {
    108 	if (!s->errors) {
    109 		for (iz i = 0; i < c->count; i++) {
    110 			s8 item    = c_str_to_s8(c->data[i]);
    111 			if (item.len) {
    112 				b32 escape = s8_contains(item, ' ') || s8_contains(item, '"');
    113 				if (escape) stream_append_byte(s, '\'');
    114 				stream_append_s8(s, item);
    115 				if (escape) stream_append_byte(s, '\'');
    116 				if (i != c->count - 1) stream_append_byte(s, ' ');
    117 			}
    118 		}
    119 	}
    120 }
    121 
    122 #if OS_LINUX
    123 
    124 function b32
    125 os_rename_file(char *name, char *new)
    126 {
    127 	b32 result = rename(name, new) != -1;
    128 	return result;
    129 }
    130 
    131 function b32
    132 os_remove_file(char *name)
    133 {
    134 	b32 result = remove(name) != -1;
    135 	return result;
    136 }
    137 
    138 function void
    139 os_make_directory(char *name)
    140 {
    141 	mkdir(name, 0770);
    142 }
    143 
    144 function u64
    145 os_get_filetime(char *file)
    146 {
    147 	struct stat sb;
    148 	u64 result = (u64)-1;
    149 	if (stat(file, &sb) != -1)
    150 		result = sb.st_mtim.tv_sec;
    151 	return result;
    152 }
    153 
    154 function iptr
    155 os_spawn_process(CommandList *cmd, Stream sb)
    156 {
    157 	pid_t result = fork();
    158 	switch (result) {
    159 	case -1: die("failed to fork command: %s: %s\n", cmd->data[0], strerror(errno)); break;
    160 	case  0: {
    161 		if (execvp(cmd->data[0], cmd->data) == -1)
    162 			die("failed to exec command: %s: %s\n", cmd->data[0], strerror(errno));
    163 		unreachable();
    164 	} break;
    165 	}
    166 	return (iptr)result;
    167 }
    168 
    169 function b32
    170 os_wait_close_process(iptr handle)
    171 {
    172 	b32 result = 0;
    173 	for (;;) {
    174 		i32   status;
    175 		iptr wait_pid = (iptr)waitpid(handle, &status, 0);
    176 		if (wait_pid == -1)
    177 			die("failed to wait on child process: %s\n", strerror(errno));
    178 		if (wait_pid == handle) {
    179 			if (WIFEXITED(status)) {
    180 				status = WEXITSTATUS(status);
    181 				/* TODO(rnp): logging */
    182 				result = status == 0;
    183 				break;
    184 			}
    185 			if (WIFSIGNALED(status)) {
    186 				/* TODO(rnp): logging */
    187 				result = 0;
    188 				break;
    189 			}
    190 		} else {
    191 			/* TODO(rnp): handle multiple children */
    192 			INVALID_CODE_PATH;
    193 		}
    194 	}
    195 	return result;
    196 }
    197 
    198 #elif OS_WINDOWS
    199 
    200 enum {
    201 	MOVEFILE_REPLACE_EXISTING = 0x01,
    202 };
    203 
    204 W32(b32) CreateDirectoryA(c8 *, void *);
    205 W32(b32) CreateProcessA(u8 *, u8 *, iptr, iptr, b32, u32, iptr, u8 *, iptr, iptr);
    206 W32(b32) GetExitCodeProcess(iptr handle, u32 *);
    207 W32(b32) GetFileTime(iptr, iptr, iptr, iptr);
    208 W32(b32) MoveFileExA(c8 *, c8 *, u32);
    209 W32(u32) WaitForSingleObject(iptr, u32);
    210 
    211 function void
    212 os_make_directory(char *name)
    213 {
    214 	CreateDirectoryA(name, 0);
    215 }
    216 
    217 function b32
    218 os_rename_file(char *name, char *new)
    219 {
    220 	b32 result = MoveFileExA(name, new, MOVEFILE_REPLACE_EXISTING) != 0;
    221 	return result;
    222 }
    223 
    224 function b32
    225 os_remove_file(char *name)
    226 {
    227 	b32 result = DeleteFileA(name);
    228 	return result;
    229 }
    230 
    231 function u64
    232 os_get_filetime(char *file)
    233 {
    234 	u64 result = (u64)-1;
    235 	iptr h = CreateFileA(file, 0, 0, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
    236 	if (h != INVALID_FILE) {
    237 		struct { u32 low, high; } w32_filetime;
    238 		GetFileTime(h, 0, 0, (iptr)&w32_filetime);
    239 		result = (u64)w32_filetime.high << 32ULL | w32_filetime.low;
    240 		CloseHandle(h);
    241 	}
    242 	return result;
    243 }
    244 
    245 function iptr
    246 os_spawn_process(CommandList *cmd, Stream sb)
    247 {
    248 	struct {
    249 		u32 cb;
    250 		u8 *reserved, *desktop, *title;
    251 		u32 x, y, x_size, y_size, x_count_chars, y_count_chars;
    252 		u32 fill_attr, flags;
    253 		u16 show_window, reserved_2;
    254 		u8 *reserved_3;
    255 		iptr std_input, std_output, std_error;
    256 	} w32_startup_info = {
    257 		.cb = sizeof(w32_startup_info),
    258 		.flags = 0x100,
    259 		.std_input  = GetStdHandle(STD_INPUT_HANDLE),
    260 		.std_output = GetStdHandle(STD_OUTPUT_HANDLE),
    261 		.std_error  = GetStdHandle(STD_ERROR_HANDLE),
    262 	};
    263 
    264 	struct {
    265 		iptr phandle, thandle;
    266 		u32  pid, tid;
    267 	} w32_process_info = {0};
    268 
    269 	/* TODO(rnp): warn if we need to clamp last string */
    270 	sb.widx = MIN(sb.widx, KB(32) - 1);
    271 	if (sb.widx < sb.cap) sb.data[sb.widx]     = 0;
    272 	else                  sb.data[sb.widx - 1] = 0;
    273 
    274 	iptr result = INVALID_FILE;
    275 	if (CreateProcessA(0, sb.data, 0, 0, 1, 0, 0, 0, (iptr)&w32_startup_info,
    276 	                   (iptr)&w32_process_info))
    277 	{
    278 		CloseHandle(w32_process_info.thandle);
    279 		result = w32_process_info.phandle;
    280 	}
    281 	return result;
    282 }
    283 
    284 function b32
    285 os_wait_close_process(iptr handle)
    286 {
    287 	b32 result = WaitForSingleObject(handle, -1) != 0xFFFFFFFFUL;
    288 	if (result) {
    289 		u32 status;
    290 		GetExitCodeProcess(handle, &status);
    291 		result = status == 0;
    292 	}
    293 	CloseHandle(handle);
    294 	return result;
    295 }
    296 
    297 #endif
    298 
    299 #define needs_rebuild(b, ...) needs_rebuild_(b, ((char *[]){__VA_ARGS__}), \
    300                                              (sizeof((char *[]){__VA_ARGS__}) / sizeof(char *)))
    301 function b32
    302 needs_rebuild_(char *binary, char *deps[], iz deps_count)
    303 {
    304 	u64 binary_filetime = os_get_filetime(binary);
    305 	b32 result = binary_filetime == (u64)-1;
    306 	for (iz i = 0; i < deps_count; i++) {
    307 		u64 filetime = os_get_filetime(deps[i]);
    308 		result |= (filetime == (u64)-1) | (filetime > binary_filetime);
    309 	}
    310 	return result;
    311 }
    312 
    313 function b32
    314 run_synchronous(Arena a, CommandList *command)
    315 {
    316 	Stream sb = arena_stream(a);
    317 	stream_push_command(&sb, command);
    318 	printf("%.*s\n", (i32)sb.widx, sb.data);
    319 	return os_wait_close_process(os_spawn_process(command, sb));
    320 }
    321 
    322 function CommandList
    323 cmd_base(Arena *a, Options *o)
    324 {
    325 	CommandList result = {0};
    326 	cmd_append(a, &result, COMPILER);
    327 
    328 	/* TODO(rnp): support cross compiling with clang */
    329 	if (!o->generic)     cmd_append(a, &result, "-march=native");
    330 	else if (is_amd64)   cmd_append(a, &result, "-march=x86-64-v3");
    331 	else if (is_aarch64) cmd_append(a, &result, "-march=armv8");
    332 
    333 	cmd_append(a, &result, COMMON_FLAGS);
    334 
    335 	if (o->debug) cmd_append(a, &result, "-O0", "-D_DEBUG", "-Wno-unused-function");
    336 	else          cmd_append(a, &result, "-O3");
    337 
    338 	if (is_w32 && is_clang) cmd_append(a, &result, "-fms-extensions");
    339 
    340 	if (o->debug && is_unix) cmd_append(a, &result, "-ggdb");
    341 
    342 	if (o->sanitize) cmd_append(a, &result, "-fsanitize=address,undefined");
    343 
    344 	if (o->report) {
    345 		if (is_clang) cmd_append(a, &result, "-fproc-stat-report");
    346 		else printf("warning: timing not supported with this compiler\n");
    347 		/* TODO(rnp): basic timing */
    348 	}
    349 
    350 	return result;
    351 }
    352 
    353 function void
    354 check_rebuild_self(Arena arena, i32 argc, char *argv[])
    355 {
    356 	char *binary = shift(argv, argc);
    357 	if (needs_rebuild(binary, __FILE__, "os_win32.c", "os_linux.c", "util.c", "util.h")) {
    358 		Stream name_buffer = arena_stream(arena);
    359 		stream_append_s8s(&name_buffer, c_str_to_s8(binary), s8(".old"));
    360 		char *old_name = (char *)arena_stream_commit_zero(&arena, &name_buffer).data;
    361 
    362 		if (!os_rename_file(binary, old_name))
    363 			die("failed to move: %s -> %s\n", binary, old_name);
    364 
    365 		Options options = {0};
    366 		CommandList c = cmd_base(&arena, &options);
    367 		cmd_append(&arena, &c, "-Wno-unused-function", __FILE__, "-o", binary, (void *)0);
    368 		if (!run_synchronous(arena, &c)) {
    369 			os_rename_file(old_name, binary);
    370 			die("failed to rebuild self\n");
    371 		}
    372 		os_remove_file(old_name);
    373 
    374 		c.count = 0;
    375 		cmd_append(&arena, &c, binary);
    376 		cmd_append_count(&arena, &c, argv, argc);
    377 		cmd_append(&arena, &c, (void *)0);
    378 		if (!run_synchronous(arena, &c))
    379 			os_exit(1);
    380 
    381 		os_exit(0);
    382 	}
    383 }
    384 
    385 function b32
    386 s8_equal(s8 a, s8 b)
    387 {
    388 	b32 result = a.len == b.len;
    389 	for (iz i = 0; result && i < a.len; i++)
    390 		result = a.data[i] == b.data[i];
    391 	return result;
    392 }
    393 
    394 function void
    395 usage(char *argv0)
    396 {
    397 	die("%s [--debug] [--report] [--sanitize]\n"
    398 	    "    --debug:       dynamically link and build with debug symbols\n"
    399 	    "    --generic:     compile for a generic target (x86-64-v3 or armv8 with NEON)\n"
    400 	    "    --report:      print compilation stats (clang only)\n"
    401 	    "    --sanitize:    build with ASAN and UBSAN\n"
    402 	    , argv0);
    403 }
    404 
    405 function Options
    406 parse_options(i32 argc, char *argv[])
    407 {
    408 	Options result = {0};
    409 
    410 	char *argv0 = shift(argv, argc);
    411 	while (argc > 0) {
    412 		char *arg = shift(argv, argc);
    413 		s8 str    = c_str_to_s8(arg);
    414 		if (s8_equal(str, s8("--debug"))) {
    415 			result.debug = 1;
    416 		} else if (s8_equal(str, s8("--generic"))) {
    417 			result.generic = 1;
    418 		} else if (s8_equal(str, s8("--report"))) {
    419 			result.report = 1;
    420 		} else if (s8_equal(str, s8("--sanitize"))) {
    421 			result.sanitize = 1;
    422 		} else {
    423 			usage(argv0);
    424 		}
    425 	}
    426 
    427 	return result;
    428 }
    429 
    430 /* NOTE(rnp): produce pdbs on w32 */
    431 function void
    432 cmd_pdb(Arena *a, CommandList *cmd)
    433 {
    434 	if (is_w32 && is_clang)
    435 		cmd_append(a, cmd, "-fuse-ld=lld", "-g", "-gcodeview", "-Wl,--pdb=");
    436 }
    437 
    438 function void
    439 git_submodule_update(Arena a, char *name)
    440 {
    441 	Stream sb = arena_stream(a);
    442 	stream_append_s8s(&sb, c_str_to_s8(name), s8(OS_PATH_SEPARATOR), s8(".git"));
    443 	arena_stream_commit_zero(&a, &sb);
    444 
    445 	CommandList git = {0};
    446 	/* NOTE(rnp): cryptic bs needed to get a simple exit code if name is dirty */
    447 	cmd_append(&a, &git, "git", "diff-index", "--quiet", "HEAD", "--", name, (void *)0);
    448 	if (!os_file_exists((c8 *)sb.data) || !run_synchronous(a, &git)) {
    449 		git.count = 1;
    450 		cmd_append(&a, &git, "submodule", "update", "--init", "--depth=1", name, (void *)0);
    451 		if (!run_synchronous(a, &git))
    452 			die("failed to clone required module: %s\n", name);
    453 	}
    454 }
    455 
    456 #define build_shared_library(a, cc, name, ...) build_shared_library_(a, cc, name, arg_list(char *, ##__VA_ARGS__))
    457 function b32
    458 build_shared_library_(Arena a, CommandList cc, char *name, char **deps, iz deps_count)
    459 {
    460 	b32 result = 0;
    461 	cmd_append(&a, &cc, "-fPIC", "-shared");
    462 	cmd_append_count(&a, &cc, deps, deps_count);
    463 	cmd_append(&a, &cc, "-o", name, (void *)0);
    464 	result = run_synchronous(a, &cc);
    465 	return result;
    466 }
    467 
    468 function b32
    469 build_static_library(Arena a, CommandList cc, char *name, char **deps, char **outputs, iz count)
    470 {
    471 	/* TODO(rnp): refactor to not need outputs */
    472 	b32 result = 0;
    473 	b32 all_success = 1;
    474 	for (iz i = 0; i < count; i++) {
    475 		cmd_append(&a, &cc, "-c", deps[i], "-o", outputs[i], (void *)0);
    476 		all_success &= run_synchronous(a, &cc);
    477 		cc.count -= 5;
    478 	}
    479 	if (all_success) {
    480 		CommandList ar = {0};
    481 		cmd_append(&a, &ar, "ar", "rc", name);
    482 		cmd_append_count(&a, &ar, outputs, count);
    483 		cmd_append(&a, &ar, (void *)0);
    484 		result = run_synchronous(a, &ar);
    485 	}
    486 	return result;
    487 }
    488 
    489 function void
    490 check_build_raylib(Arena a, CommandList cc, b32 shared)
    491 {
    492 	char *libraylib = shared ? OS_SHARED_LIB("libraylib") : OUTPUT("libraylib.a");
    493 
    494 	if (needs_rebuild(libraylib, __FILE__, "external/include/rlgl.h", "external/raylib")) {
    495 		git_submodule_update(a, "external/raylib");
    496 		os_copy_file("external/raylib/src/rlgl.h", "external/include/rlgl.h");
    497 
    498 		if (is_unix) cmd_append(&a, &cc, "-D_GLFW_X11");
    499 		cmd_append(&a, &cc, "-DPLATFORM_DESKTOP_GLFW", "-DGRAPHICS_API_OPENGL_43");
    500 		cmd_append(&a, &cc, "-Wno-unused-but-set-variable");
    501 		cmd_append(&a, &cc, "-Iexternal/raylib/src", "-Iexternal/raylib/src/external/glfw/include");
    502 		#define RAYLIB_SOURCES \
    503 			X(rglfw)     \
    504 			X(rshapes)   \
    505 			X(rtext)     \
    506 			X(rtextures) \
    507 			X(utils)
    508 		#define X(name) "external/raylib/src/" #name ".c",
    509 		char *srcs[] = {"external/rcore_extended.c", RAYLIB_SOURCES};
    510 		#undef X
    511 		#define X(name) OUTPUT(#name ".o"),
    512 		char *outs[] = {OUTPUT("rcore_extended.o"), RAYLIB_SOURCES};
    513 		#undef X
    514 
    515 		b32 success;
    516 		if (shared) {
    517 			cmd_append(&a, &cc, "-DBUILD_LIBTYPE_SHARED", "-D_GLFW_BUILD_DLL");
    518 			cmd_append(&a, &cc, "-fPIC", "-shared");
    519 			cmd_pdb(&a, &cc);
    520 			cmd_append_count(&a, &cc, srcs, countof(srcs));
    521 			cmd_append(&a, &cc, "-o", libraylib);
    522 			if (is_w32) cmd_append(&a, &cc, "-L.", "-lgdi32", "-lwinmm");
    523 			cmd_append(&a, &cc, (void *)0);
    524 			success = run_synchronous(a, &cc);
    525 		} else {
    526 			success = build_static_library(a, cc, libraylib, srcs, outs, countof(srcs));
    527 		}
    528 		if (!success) die("failed to build libary: %s\n", libraylib);
    529 	}
    530 }
    531 
    532 /* NOTE(rnp): gcc requires these to appear at the end for no reason at all */
    533 function void
    534 cmd_append_ldflags(Arena *a, CommandList *cc, b32 shared)
    535 {
    536 	cmd_pdb(a, cc);
    537 	cmd_append(a, cc, "-lm");
    538 	if (shared && !is_w32) cmd_append(a, cc, "-Wl,-rpath,.");
    539 	if (shared) cmd_append(a, cc, "-L.", "-lraylib");
    540 	if (is_w32) cmd_append(a, cc, "-lgdi32", "-lwinmm", "-lSynchronization");
    541 }
    542 
    543 function b32
    544 build_helper_library(Arena arena, CommandList cc)
    545 {
    546 	/////////////
    547 	// library
    548 	char *library = OUTPUT(OS_SHARED_LIB("ogl_beamformer_lib"));
    549 	char *srcs[]  = {"helpers/ogl_beamformer_lib.c"};
    550 
    551 	cmd_append(&arena, &cc, "-Wno-unused-function", "-fPIC", "-shared");
    552 	cmd_append_count(&arena, &cc, srcs, countof(srcs));
    553 	cmd_append(&arena, &cc, "-o", library);
    554 	if (is_w32) cmd_append(&arena, &cc, "-lSynchronization");
    555 	cmd_append(&arena, &cc, (void *)0);
    556 	b32 result = run_synchronous(arena, &cc);
    557 	if (!result) fprintf(stderr, "failed to build: %s\n", library);
    558 
    559 	/////////////
    560 	// header
    561 	char *lib_header_out = OUTPUT("ogl_beamformer_lib.h");
    562 
    563 	b32 rebuild_lib_header = needs_rebuild(lib_header_out, "beamformer_parameters.h",
    564 	                                       "helpers/ogl_beamformer_lib_base.h");
    565 	if (rebuild_lib_header) {
    566 		s8 parameters_header = os_read_whole_file(&arena, "beamformer_parameters.h");
    567 		s8 base_header       = os_read_whole_file(&arena, "helpers/ogl_beamformer_lib_base.h");
    568 		if (parameters_header.len != 0 && base_header.len != 0 &&
    569 		    parameters_header.data + parameters_header.len == base_header.data)
    570 		{
    571 			s8 output_file   = parameters_header;
    572 			output_file.len += base_header.len;
    573 			os_write_new_file(lib_header_out, output_file);
    574 		} else {
    575 			result = 0;
    576 			fprintf(stderr, "failed to build: %s\n", lib_header_out);
    577 		}
    578 	}
    579 
    580 	return result;
    581 }
    582 
    583 i32
    584 main(i32 argc, char *argv[])
    585 {
    586 	Arena arena = os_alloc_arena((Arena){0}, MB(8));
    587 	check_rebuild_self(arena, argc, argv);
    588 
    589 	Options options = parse_options(argc, argv);
    590 
    591 	os_make_directory(OUTDIR);
    592 
    593 	CommandList c = cmd_base(&arena, &options);
    594 	check_build_raylib(arena, c, options.debug);
    595 
    596 	build_helper_library(arena, c);
    597 
    598 	/////////////////////////
    599 	// hot reloadable portion
    600 	if (options.debug) {
    601 		iz c_count = c.count;
    602 		if (is_w32) cmd_append_ldflags(&arena, &c, 1);
    603 		if (!build_shared_library(arena, c, OS_SHARED_LIB("beamformer"), "beamformer.c"))
    604 			die("failed to build: " OS_SHARED_LIB("beamfomer") "\n");
    605 		c.count = c_count;
    606 	}
    607 
    608 	//////////////////
    609 	// static portion
    610 	cmd_append(&arena, &c, OS_MAIN, "-o", "ogl");
    611 	if (!options.debug) cmd_append(&arena, &c, OUTPUT("libraylib.a"));
    612 	cmd_append_ldflags(&arena, &c, options.debug);
    613 	cmd_append(&arena, &c, (void *)0);
    614 
    615 	return !run_synchronous(arena, &c);
    616 }