ogl_beamforming

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

build.c (16422B)


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