Commit: ea6a876212d30d4c6b91a6edb11b44ef94f5cf7e
Parent: 11de3a3a3c5f01b38610d6a673651d41e39e7672
Author: Randy Palamar
Date:   Sat, 26 Apr 2025 20:04:19 -0600
build: replace build.sh with build.c
Diffstat:
8 files changed, 648 insertions(+), 195 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
@@ -9,17 +9,17 @@ jobs:
       matrix:
         os: [ubuntu-latest, ubuntu-24.04-arm]
         cc: ["gcc", "clang"]
-    env:
-      CC: "${{ matrix.cc }}"
-    runs-on: ${{ matrix.os }}
+    runs-on: ${{matrix.os}}
     steps:
       - uses: actions/checkout@v4
+        with:
+          submodules: 'true'
       - name: Install dependencies
         run: |
           sudo apt update
           sudo apt install libxkbcommon-dev xorg-dev
       - name: Build
-        run: ./build.sh
+        run: ${{matrix.cc}} -march=native -O3 build.c -Iexternal/include -o build && ./build
 
   windows:
     runs-on: windows-latest
@@ -29,13 +29,13 @@ jobs:
         include:
           - { sys: ucrt64,  env: ucrt-x86_64,  cc: "gcc" }
           - { sys: clang64, env: clang-x86_64, cc: "clang" }
-    env:
-      CC: "${{ matrix.cc }}"
     defaults:
       run:
         shell: msys2 {0}
     steps:
       - uses: actions/checkout@v4
+        with:
+          submodules: 'true'
       - uses: msys2/setup-msys2@v2
         with:
           msystem: ${{matrix.sys}}
@@ -43,4 +43,4 @@ jobs:
           install: git mingw-w64-${{matrix.env}}-${{matrix.cc}}
 
       - name: Build
-        run: NO_MATLAB=1 ./build.sh
+        run: ${{matrix.cc}} -march=native -O3 build.c -Iexternal/include -o build && ./build
diff --git a/.gitignore b/.gitignore
@@ -1,11 +1,11 @@
 *.dll
 *.exe
-*.mexa64
+*.old
 *.pdb
 *.rdi
 *.so*
+build
 external/include/r*.h
-external/lib
-helpers/helper
 ogl
+out
 x64
diff --git a/build.c b/build.c
@@ -0,0 +1,570 @@
+/* See LICENSE for license details. */
+/* NOTE: inspired by nob: https://github.com/tsoding/nob.h */
+
+#define BASE_CFLAGS "-march=native", "-std=c11", "-Wall", "-Iexternal/include"
+
+#define BUILD_DEPS __FILE__, "os_win32.c", "os_linux.c", "util.c", "util.h"
+
+#include "util.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#define is_unix  0
+#define is_w32   0
+#define is_clang 0
+
+#if defined(__linux__)
+  #undef  is_unix
+  #define is_unix 1
+
+  #include <errno.h>
+  #include <string.h>
+  #include <sys/select.h>
+  #include <sys/wait.h>
+
+  #include "os_linux.c"
+
+  #define OS_MAIN    "main_linux.c"
+  #define OS_LIB_EXT ".so"
+
+#elif defined(_WIN32)
+  #undef  is_w32
+  #define is_w32 1
+
+  #include "os_win32.c"
+
+  #define OS_MAIN    "main_w32.c"
+  #define OS_LIB_EXT ".dll"
+
+#else
+  #error Unsupported Platform
+#endif
+
+#ifdef __clang__
+#undef  is_clang
+#define is_clang 1
+#define BUILD_COMMAND(output, input) "clang", BASE_CFLAGS, "-o", output, input
+#else
+#define BUILD_COMMAND(output, input) "cc",    BASE_CFLAGS, "-o", output, input
+#endif
+
+#define shift(list, count) ((count)--, *(list)++)
+
+#define da_append_count(a, s, items, item_count) do { \
+	da_reserve((a), (s), (s)->count + (item_count));                            \
+	mem_copy((s)->data + (s)->count, (items), sizeof(*(items)) * (item_count)); \
+	(s)->count += (item_count);                                                 \
+} while (0)
+
+#define cmd_append_count da_append_count
+#define cmd_append(a, s, ...) da_append_count(a, s, ((char *[]){__VA_ARGS__}), \
+                                             (sizeof((char *[]){__VA_ARGS__}) / sizeof(char *)))
+
+typedef struct {
+	char **data;
+	iz     count;
+	iz     capacity;
+} CommandList;
+
+typedef struct {
+	b32   debug;
+	b32   report;
+	b32   sanitize;
+} Options;
+
+#define die(fmt, ...) die_("%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
+function void __attribute__((noreturn))
+die_(char *format, ...)
+{
+	va_list ap;
+	va_start(ap, format);
+	/* TODO(rnp): proper log */
+	vfprintf(stderr, format, ap);
+	va_end(ap);
+	os_fatal(s8(""));
+}
+
+function b32
+s8_contains(s8 s, u8 byte)
+{
+	b32 result = 0;
+	for (iz i = 0 ; !result && i < s.len; i++)
+		result |= s.data[i] == byte;
+	return result;
+}
+
+function void
+stream_push_command(Stream *s, CommandList *c)
+{
+	if (!s->errors) {
+		for (iz i = 0; i < c->count; i++) {
+			s8 item    = c_str_to_s8(c->data[i]);
+			if (item.len) {
+				b32 escape = s8_contains(item, ' ') || s8_contains(item, '"');
+				if (escape) stream_append_byte(s, '\'');
+				stream_append_s8(s, item);
+				if (escape) stream_append_byte(s, '\'');
+				if (i != c->count - 1) stream_append_byte(s, ' ');
+			}
+		}
+	}
+}
+
+#if defined(__unix__)
+
+function b32
+os_rename_file(char *name, char *new)
+{
+	b32 result = rename(name, new) != -1;
+	return result;
+}
+
+function b32
+os_remove_file(char *name)
+{
+	b32 result = remove(name) != -1;
+	return result;
+}
+
+function void
+os_make_directory(char *name)
+{
+	mkdir(name, 0770);
+}
+
+function u64
+os_get_filetime(char *file)
+{
+	struct stat sb;
+	u64 result = (u64)-1;
+	if (stat(file, &sb) != -1)
+		result = sb.st_mtim.tv_sec;
+	return result;
+}
+
+function iptr
+os_spawn_process(CommandList *cmd, Stream sb)
+{
+	pid_t result = fork();
+	switch (result) {
+	case -1: die("failed to fork command: %s: %s\n", cmd->data[0], strerror(errno)); break;
+	case  0: {
+		if (execvp(cmd->data[0], cmd->data) == -1)
+			die("failed to exec command: %s: %s\n", cmd->data[0], strerror(errno));
+		unreachable();
+	} break;
+	}
+	return (iptr)result;
+}
+
+function b32
+os_wait_close_process(iptr handle)
+{
+	b32 result = 0;
+	for (;;) {
+		i32   status;
+		iptr wait_pid = (iptr)waitpid(handle, &status, 0);
+		if (wait_pid == -1)
+			die("failed to wait on child process: %s\n", strerror(errno));
+		if (wait_pid == handle) {
+			if (WIFEXITED(status)) {
+				status = WEXITSTATUS(status);
+				/* TODO(rnp): logging */
+				result = status == 0;
+				break;
+			}
+			if (WIFSIGNALED(status)) {
+				/* TODO(rnp): logging */
+				result = 0;
+				break;
+			}
+		} else {
+			/* TODO(rnp): handle multiple children */
+			INVALID_CODE_PATH;
+		}
+	}
+	return result;
+}
+
+#elif defined(_WIN32)
+
+#define MOVEFILE_REPLACE_EXISTING 0x01
+
+W32(b32) CreateDirectoryA(c8 *, void *);
+W32(b32) CreateProcessA(u8 *, u8 *, iptr, iptr, b32, u32, iptr, u8 *, iptr, iptr);
+W32(b32) GetExitCodeProcess(iptr handle, u32 *);
+W32(b32) GetFileTime(iptr, iptr, iptr, iptr);
+W32(b32) MoveFileExA(c8 *, c8 *, u32);
+W32(u32) WaitForSingleObject(iptr, u32);
+
+function void
+os_make_directory(char *name)
+{
+	CreateDirectoryA(name, 0);
+}
+
+function b32
+os_rename_file(char *name, char *new)
+{
+	b32 result = MoveFileExA(name, new, MOVEFILE_REPLACE_EXISTING) != 0;
+	return result;
+}
+
+function b32
+os_remove_file(char *name)
+{
+	b32 result = DeleteFileA(name);
+	return result;
+}
+
+function u64
+os_get_filetime(char *file)
+{
+	u64 result = (u64)-1;
+	iptr h = CreateFileA(file, 0, 0, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
+	if (h != INVALID_FILE) {
+		struct { u32 low, high; } w32_filetime;
+		GetFileTime(h, 0, 0, (iptr)&w32_filetime);
+		result = (u64)w32_filetime.high << 32ULL | w32_filetime.low;
+		CloseHandle(h);
+	}
+	return result;
+}
+
+function iptr
+os_spawn_process(CommandList *cmd, Stream sb)
+{
+	struct {
+		u32 cb;
+		u8 *reserved, *desktop, *title;
+		u32 x, y, x_size, y_size, x_count_chars, y_count_chars;
+		u32 fill_attr, flags;
+		u16 show_window, reserved_2;
+		u8 *reserved_3;
+		iptr std_input, std_output, std_error;
+	} w32_startup_info = {
+		.cb = sizeof(w32_startup_info),
+		.flags = 0x100,
+		.std_input  = GetStdHandle(STD_INPUT_HANDLE),
+		.std_output = GetStdHandle(STD_OUTPUT_HANDLE),
+		.std_error  = GetStdHandle(STD_ERROR_HANDLE),
+	};
+
+	struct {
+		iptr phandle, thandle;
+		u32  pid, tid;
+	} w32_process_info = {0};
+
+	/* TODO(rnp): warn if we need to clamp last string */
+	sb.widx = MIN(sb.widx, KB(32) - 1);
+	if (sb.widx < sb.cap) sb.data[sb.widx]     = 0;
+	else                  sb.data[sb.widx - 1] = 0;
+
+	iptr result = INVALID_FILE;
+	if (CreateProcessA(0, sb.data, 0, 0, 1, 0, 0, 0, (iptr)&w32_startup_info,
+	                   (iptr)&w32_process_info))
+	{
+		CloseHandle(w32_process_info.thandle);
+		result = w32_process_info.phandle;
+	}
+	return result;
+}
+
+function b32
+os_wait_close_process(iptr handle)
+{
+	b32 result = WaitForSingleObject(handle, -1) != 0xFFFFFFFFUL;
+	if (result) {
+		u32 status;
+		GetExitCodeProcess(handle, &status);
+		result = status == 0;
+	}
+	CloseHandle(handle);
+	return result;
+}
+
+#endif
+
+#define needs_rebuild(b, ...) needs_rebuild_(b, ((char *[]){__VA_ARGS__}), \
+                                             (sizeof((char *[]){__VA_ARGS__}) / sizeof(char *)))
+function b32
+needs_rebuild_(char *binary, char *deps[], iz deps_count)
+{
+	u64 binary_filetime = os_get_filetime(binary);
+	b32 result = binary_filetime == (u64)-1;
+	for (iz i = 0; i < deps_count; i++) {
+		u64 filetime = os_get_filetime(deps[i]);
+		result |= (filetime == (u64)-1) | (filetime > binary_filetime);
+	}
+	return result;
+}
+
+function b32
+run_synchronous(Arena a, CommandList *command)
+{
+	Stream sb = arena_stream(a);
+	stream_push_command(&sb, command);
+	printf("%.*s\n", (i32)sb.widx, sb.data);
+	return os_wait_close_process(os_spawn_process(command, sb));
+}
+
+function void
+check_rebuild_self(Arena arena, i32 argc, char *argv[])
+{
+	char *binary = shift(argv, argc);
+	if (needs_rebuild(binary, BUILD_DEPS)) {
+		Stream name = stream_alloc(&arena, KB(1));
+		stream_append_s8(&name, c_str_to_s8(binary));
+		stream_append_s8(&name, s8(".old"));
+		stream_append_byte(&name, 0);
+
+		if (!os_rename_file(binary, (char *)name.data))
+			die("failed to move: %s -> %s\n", binary, (char *)name.data);
+
+		CommandList c = {0};
+		cmd_append(&arena, &c, BUILD_COMMAND(binary, __FILE__));
+		cmd_append(&arena, &c, "-O3", "-Wno-unused-function", (void *)0);
+		if (!run_synchronous(arena, &c)) {
+			os_rename_file((char *)name.data, binary);
+			die("failed to rebuild self\n");
+		}
+		os_remove_file((char *)name.data);
+
+		c.count = 0;
+		cmd_append(&arena, &c, binary);
+		cmd_append_count(&arena, &c, argv, argc);
+		cmd_append(&arena, &c, (void *)0);
+		if (!run_synchronous(arena, &c))
+			os_exit(1);
+
+		os_exit(0);
+	}
+}
+
+function b32
+s8_equal(s8 a, s8 b)
+{
+	b32 result = a.len == b.len;
+	for (iz i = 0; result && i < a.len; i++)
+		result = a.data[i] == b.data[i];
+	return result;
+}
+
+function void
+usage(char *argv0)
+{
+	die("%s [--debug] [--report] [--sanitize]\n"
+	    "    --debug:       dynamically link and build with debug symbols\n"
+	    "    --report:      print compilation stats (clang only)\n"
+	    "    --sanitize:    build with ASAN and UBSAN\n"
+	    , argv0);
+}
+
+function Options
+parse_options(i32 argc, char *argv[])
+{
+	Options result = {0};
+
+	char *argv0 = shift(argv, argc);
+	while (argc > 0) {
+		char *arg = shift(argv, argc);
+		s8 str    = c_str_to_s8(arg);
+		if (s8_equal(str, s8("--debug"))) {
+			result.debug = 1;
+		} else if (s8_equal(str, s8("--report"))) {
+			result.report = 1;
+		} else if (s8_equal(str, s8("--sanitize"))) {
+			result.sanitize = 1;
+		} else {
+			usage(argv0);
+		}
+	}
+
+	return result;
+}
+
+function CommandList
+cmd_base(Arena *a, Options *o)
+{
+	CommandList result = {0};
+	cmd_append(a, &result, is_clang ? "clang" : "cc", BASE_CFLAGS);
+	if (o->debug) cmd_append(a, &result, "-O0", "-D_DEBUG", "-Wno-unused-function");
+	else          cmd_append(a, &result, "-O3");
+
+	if (o->debug && is_unix) cmd_append(a, &result, "-ggdb");
+
+	if (o->sanitize) cmd_append(a, &result, "-fsanitize=address,undefined");
+
+	if (o->report) {
+		if (is_clang) cmd_append(a, &result, "-fproc-stat-report");
+		else printf("warning: timing not supported with this compiler\n");
+		/* TODO(rnp): basic timing */
+	}
+
+	return result;
+}
+
+/* NOTE(rnp): produce pdbs on w32 */
+function void
+cmd_pdb(Arena *a, CommandList *cmd)
+{
+	if (is_w32 && is_clang)
+		cmd_append(a, cmd, "-fuse-ld=lld", "-g", "-gcodeview", "-Wl,--pdb=");
+}
+
+function void
+git_submodule_update(Arena a, char *name)
+{
+	CommandList git = {0};
+	/* NOTE(rnp): cryptic bs needed to get a simple exit code if name is dirty */
+	cmd_append(&a, &git, "git", "diff-index", "--quiet", "HEAD", "--", name, (void *)0);
+	if (!run_synchronous(a, &git)) {
+		git.count = 1;
+		cmd_append(&a, &git, "submodule", "update", "--init", "--depth=1", name, (void *)0);
+		if (!run_synchronous(a, &git))
+			die("failed to clone required module: %s\n", name);
+	}
+}
+
+function b32
+build_shared_library(Arena a, CommandList cc, char *name, char **deps, iz deps_count)
+{
+	b32 result = 0;
+	cmd_append(&a, &cc, "-fPIC", "-shared");
+	cmd_append_count(&a, &cc, deps, deps_count);
+	cmd_append(&a, &cc, "-o", name, (void *)0);
+	result = run_synchronous(a, &cc);
+	return result;
+}
+
+function b32
+build_static_library(Arena a, CommandList cc, char *name, char **deps, char **outputs, iz count)
+{
+	/* TODO(rnp): refactor to not need outputs */
+	b32 result = 0;
+	b32 all_success = 1;
+	cmd_append(&a, &cc, "-static");
+	for (iz i = 0; i < count; i++) {
+		cmd_append(&a, &cc, "-c", deps[i], "-o", outputs[i], (void *)0);
+		all_success &= run_synchronous(a, &cc);
+		cc.count -= 5;
+	}
+	if (all_success) {
+		CommandList ar = {0};
+		cmd_append(&a, &ar, "ar", "rc", name);
+		cmd_append_count(&a, &ar, outputs, count);
+		cmd_append(&a, &ar, (void *)0);
+		result = run_synchronous(a, &ar);
+	}
+	return result;
+}
+
+function void
+check_build_raylib(Arena a, CommandList cc, b32 shared)
+{
+	iz cc_count_start = cc.count;
+	char *libraylib[] = {"out/libraylib.a", "libraylib" OS_LIB_EXT};
+	char *libglfw[]   = {"out/libglfw.a",   "libglfw"   OS_LIB_EXT};
+
+	b32 rebuild_raylib = needs_rebuild(libraylib[shared], __FILE__, "external/include/rlgl.h",
+	                                   "external/raylib");
+	b32 rebuild_glfw   = needs_rebuild(libglfw[shared],   __FILE__, "external/include/rlgl.h",
+	                                   "external/raylib");
+	if (rebuild_glfw || rebuild_raylib) {
+		git_submodule_update(a, "external/raylib");
+		os_copy_file("external/raylib/src/rlgl.h", "external/include/rlgl.h");
+		if (!shared) os_make_directory("out");
+	}
+
+	if (rebuild_raylib) {
+		cc.count = cc_count_start;
+		cmd_append(&a, &cc, "-Wno-unused-but-set-variable");
+		cmd_append(&a, &cc, "-DPLATFORM_DESKTOP_GLFW", "-DGRAPHICS_API_OPENGL_43");
+		cmd_append(&a, &cc, "-Iexternal/raylib/src", "-Iexternal/raylib/src/external/glfw/include");
+
+		#define RAYLIB_SOURCES \
+			X(rshapes)   \
+			X(rtext)     \
+			X(rtextures) \
+			X(utils)
+		#define X(name) "external/raylib/src/" #name ".c",
+		char *srcs[] = {"external/rcore_extended.c", RAYLIB_SOURCES};
+		#undef X
+		#define X(name) "out/" #name ".o",
+		char *outs[] = {"out/rcore_extended.o", RAYLIB_SOURCES};
+		#undef X
+
+		b32 success;
+		if (shared) {
+			cmd_pdb(&a, &cc);
+			cmd_append(&a, &cc, "-DBUILD_LIBTYPE_SHARED");
+			success = build_shared_library(a, cc, libraylib[shared], srcs, countof(srcs));
+			cc.count--;
+		} else {
+			success = build_static_library(a, cc, libraylib[shared], srcs, outs, countof(srcs));
+		}
+		if (!success) die("failed to build libary: %s\n", libraylib[shared]);
+	}
+
+	if (rebuild_glfw) {
+		cc.count = cc_count_start;
+		if (is_unix) cmd_append(&a, &cc, "-D_GLFW_X11");
+		char *srcs[] = {"external/raylib/src/rglfw.c"};
+		char *outs[] = {"out/rglfw.o"};
+
+		b32 success;
+		if (shared) {
+			cmd_pdb(&a, &cc);
+			if (is_w32) cmd_append(&a, &cc, "-lgdi32", "-lwinmm");
+			success = build_shared_library(a, cc, libglfw[shared], srcs, countof(srcs));
+		} else {
+			success = build_static_library(a, cc, libglfw[shared], srcs, outs, countof(srcs));
+		}
+		if (!success) die("failed to build libary: %s\n", libglfw[shared]);
+	}
+}
+
+/* NOTE(rnp): gcc requires these to appear at the end for no reason at all */
+function void
+cmd_append_ldflags(Arena *a, CommandList *cc, b32 shared)
+{
+	cmd_pdb(a, cc);
+	cmd_append(a, cc, "-lm");
+	if (shared && !is_w32) cmd_append(a, cc, "-Wl,-rpath,.");
+	if (shared) cmd_append(a, cc, "-L.", "-lglfw", "-lraylib");
+	if (is_w32) cmd_append(a, cc, "-lgdi32", "-lwinmm", "-lSynchronization");
+}
+
+i32
+main(i32 argc, char *argv[])
+{
+	Arena arena = os_alloc_arena((Arena){0}, MB(2));
+	check_rebuild_self(arena, argc, argv);
+
+	Options options = parse_options(argc, argv);
+
+	CommandList c = cmd_base(&arena, &options);
+	check_build_raylib(arena, c, options.debug);
+
+	cmd_append(&arena, &c, "-Wno-unused-function");
+	if (!build_shared_library(arena, c, "helpers/ogl_beamformer_lib" OS_LIB_EXT,
+	                          (char *[]){"helpers/ogl_beamformer_lib.c"}, 1))
+	{
+		die("failed to build: helpers/ogl_beamformer_lib" OS_LIB_EXT "\n");
+	}
+	c.count--;
+
+	if (options.debug && !build_shared_library(arena, c, "beamformer" OS_LIB_EXT,
+	                                           (char *[]){"beamformer.c"}, 1))
+	{
+		die("failed to build: beamfomer" OS_LIB_EXT "\n");
+	}
+
+	cmd_append(&arena, &c, "-o", "ogl", OS_MAIN);
+	if (!options.debug) cmd_append(&arena, &c, "out/libglfw.a", "out/libraylib.a");
+	cmd_append_ldflags(&arena, &c, options.debug);
+	cmd_append(&arena, &c, (void *)0);
+
+	return !run_synchronous(arena, &c);
+}
diff --git a/build.sh b/build.sh
@@ -1,137 +0,0 @@
-#!/bin/sh
-
-# NOTE(rnp): to rebuild raylib run `touch build.sh` (or delete the so/dll)
-
-cflags="-march=native -std=c11 -Wall -I./external/include"
-#cflags="${cflags} -fsanitize=address,undefined"
-#cflags="${cflags} -fproc-stat-report"
-#cflags="${cflags} -Rpass-missed=.*"
-libcflags="${cflags} -fPIC -shared -Wno-unused-function -Wno-unused-variable"
-ldflags="-lm"
-
-cc=${CC:-cc}
-build=release
-
-for arg in $@; do
-	case "$arg" in
-	clang)   cc=clang      ;;
-	gcc)     cc=gcc        ;;
-	debug)   build=debug   ;;
-	release) build=release ;;
-	*) echo "usage: $0 [release|debug] [gcc|clang]"; exit 1;;
-	esac
-done
-
-case $(uname -sm) in
-MINGW64*)
-	win32=1
-	glfw="libglfw.dll"
-	glfw_flags="-lgdi32 -lwinmm"
-	raylib="libraylib.dll"
-	main="main_w32.c"
-	libname="beamformer.dll"
-	ldflags="${ldflags} -lgdi32 -lwinmm -lSynchronization"
-	extra_ldflags="-lSynchronization"
-	if [ ! ${NO_MATLAB} ]; then
-		libcflags="${libcflags} -DMATLAB_CONSOLE"
-		extra_ldflags="${extra_ldflags} -llibmat -llibmex"
-	fi
-	${cc} ${libcflags} -O3 helpers/ogl_beamformer_lib.c -o helpers/ogl_beamformer_lib.dll \
-		-L'C:/Program Files/MATLAB/R2022a/extern/lib/win64/microsoft' \
-		${extra_ldflags}
-	;;
-Linux*)
-	glfw="libglfw.so"
-	glfw_flags="-D_GLFW_X11"
-	raylib="libraylib.so"
-	main="main_linux.c"
-	libname="beamformer.so"
-	cflags="${cflags} -D_DEFAULT_SOURCE"
-
-	${cc} ${libcflags} -O3 helpers/ogl_beamformer_lib.c -o helpers/ogl_beamformer_lib.so
-	;;
-esac
-
-if [ ! -f external/raylib/README.md ] || [ "$(git status --short external/raylib)" ]; then
-	git submodule update --init --depth=1 external/raylib
-fi
-
-mkdir -p external/lib
-
-build_raylib()
-{
-	cp external/raylib/src/rlgl.h external/include/
-	cppflags="${2} -DPLATFORM_DESKTOP_GLFW -DGRAPHICS_API_OPENGL_43"
-	cppflags="${cppflags} -Iexternal/raylib/src -Iexternal/raylib/src/external/glfw/include"
-
-	case ${1} in
-	shared)
-		${cc} ${cflags} ${cppflags} -fPIC -shared -DBUILD_LIBTYPE_SHARED \
-		        external/rcore_extended.c \
-		        external/raylib/src/rshapes.c external/raylib/src/rtext.c \
-		        external/raylib/src/rtextures.c external/raylib/src/utils.c \
-			-o ${raylib}
-			;;
-	static)
-		${cc} ${cflags} ${cppflags} -c external/rcore_extended.c       -o external/lib/rcore.c.o
-		${cc} ${cflags} ${cppflags} -c external/raylib/src/rshapes.c   -o external/lib/rshapes.c.o
-		${cc} ${cflags} ${cppflags} -c external/raylib/src/rtext.c     -o external/lib/rtext.c.o
-		${cc} ${cflags} ${cppflags} -c external/raylib/src/rtextures.c -o external/lib/rtextures.c.o
-		${cc} ${cflags} ${cppflags} -c external/raylib/src/utils.c     -o external/lib/utils.c.o
-		ar rc external/lib/libraylib.a external/lib/rcore.c.o external/lib/rshapes.c.o \
-		      external/lib/rtext.c.o external/lib/rtextures.c.o external/lib/rtextures.c.o \
-		      external/lib/utils.c.o
-		;;
-	esac
-}
-
-check_and_rebuild_libs()
-{
-	# NOTE(rnp): we need to build glfw separately so that we can use functions from
-	# glfw directly - raylib doesn't let us open multiple opengl contexts even if
-	# we never plan on using them with raylib
-	case "${1}" in
-	static)
-		if [ "./build.sh" -nt "${glfw}" ] || [ ! -f ${glfw} ]; then
-			${cc} ${cflags} ${glfw_flags} -static  \
-				-c external/raylib/src/rglfw.c -o external/lib/rglfw.o
-			ar rc ${glfw} external/lib/rglfw.o
-		fi
-		;;
-	shared)
-		if [ "./build.sh" -nt "${glfw}" ] || [ ! -f "${glfw}" ]; then
-			[ "${win32}" ] && glfw_flags="${glfw_flags} -D_GLFW_BUILD_DLL"
-			${cc} ${cflags} ${glfw_flags} -fPIC -shared \
-				external/raylib/src/rglfw.c -o ${glfw}
-		fi
-		;;
-	esac
-	if [ "./build.sh" -nt "${raylib}" ] || [ ! -f "${raylib}" ]; then
-		[ ${1} = "static" ] && build_raylib ${1} "-static"
-		[ ${1} = "shared" ] && build_raylib ${1} "-L. -lglfw ${ldflags}"
-	fi
-}
-
-case "${build}" in
-debug)
-	cflags="${cflags} -O0 -D_DEBUG -Wno-unused-function"
-	if [ "${win32}" ]; then
-		# NOTE(rnp): export pdb on win32; requires clang
-		cflags="${cflags} -fuse-ld=lld -g -gcodeview -Wl,--pdb="
-	else
-		cflags="${cflags} -ggdb -Wl,-rpath,."
-	fi
-	check_and_rebuild_libs "shared"
-	ldflags="-L. -lglfw -lraylib ${ldflags}"
-	${cc} ${cflags} -fPIC -shared beamformer.c -o ${libname} ${ldflags}
-	;;
-release)
-	cflags="${cflags} -O3"
-	raylib="external/lib/libraylib.a"
-	glfw="external/lib/libglfw.a"
-	ldflags="${raylib} ${glfw} ${ldflags}"
-	check_and_rebuild_libs "static"
-	;;
-esac
-
-${cc} ${cflags} -o ogl ${main} ${ldflags}
diff --git a/main_linux.c b/main_linux.c
@@ -74,7 +74,7 @@ main(void)
 	OS_FNS
 	#undef X
 
-	ctx.os.file_watch_context.handle = inotify_init1(O_NONBLOCK|O_CLOEXEC);
+	ctx.os.file_watch_context.handle = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
 	ctx.os.compute_worker.asleep     = 1;
 	ctx.os.stderr                    = STDERR_FILENO;
 
diff --git a/main_w32.c b/main_w32.c
@@ -5,20 +5,6 @@
 
 #include "beamformer.h"
 
-typedef struct {
-	iptr io_completion_handle;
-} w32_context;
-
-enum w32_io_events {
-	W32_IO_FILE_WATCH,
-	W32_IO_PIPE,
-};
-
-typedef struct {
-	u64  tag;
-	iptr context;
-} w32_io_completion_event;
-
 #include "os_win32.c"
 
 #define OS_DEBUG_LIB_NAME      ".\\beamformer.dll"
diff --git a/os_linux.c b/os_linux.c
@@ -16,6 +16,8 @@
 #include <sys/syscall.h>
 #include <unistd.h>
 
+/* NOTE(rnp): hidden behind feature flags -> screw compiler/standards idiots */
+i32 ftruncate(i32, i64);
 i64 syscall(i64, ...);
 
 #ifdef _DEBUG
@@ -42,15 +44,22 @@ static OS_WRITE_FILE_FN(os_write_file)
 	return 1;
 }
 
-static void __attribute__((noreturn))
+function void __attribute__((noreturn))
+os_exit(i32 code)
+{
+	_exit(code);
+	unreachable();
+}
+
+function void __attribute__((noreturn))
 os_fatal(s8 msg)
 {
 	os_write_file(STDERR_FILENO, msg);
-	_exit(1);
+	os_exit(1);
 	unreachable();
 }
 
-static OS_ALLOC_ARENA_FN(os_alloc_arena)
+function OS_ALLOC_ARENA_FN(os_alloc_arena)
 {
 	Arena result;
 	iz pagesize = sysconf(_SC_PAGESIZE);
@@ -147,36 +156,33 @@ os_open_shared_memory_area(char *name, iz cap)
 }
 
 /* NOTE: complete garbage because there is no standarized copyfile() in POSix */
-static b32
+function b32
 os_copy_file(char *name, char *new)
 {
 	b32 result = 0;
 	struct stat sb;
-	if (stat(name, &sb) < 0)
-		return 0;
-
-	i32 fd_old = open(name, O_RDONLY);
-	i32 fd_new = open(new, O_WRONLY|O_TRUNC, sb.st_mode);
-
-	if (fd_old < 0 || fd_new < 0)
-		goto ret;
-	u8 buf[4096];
-	iz copied = 0;
-	while (copied != sb.st_size) {
-		iz r = read(fd_old, buf, ARRAY_COUNT(buf));
-		if (r < 0) goto ret;
-		iz w = write(fd_new, buf, r);
-		if (w < 0) goto ret;
-		copied += w;
+	if (stat(name, &sb) == 0) {
+		i32 fd_old = open(name, O_RDONLY);
+		i32 fd_new = open(new,  O_WRONLY|O_CREAT, sb.st_mode);
+		if (fd_old >= 0 && fd_new >= 0) {
+			u8 buf[4096];
+			iz copied = 0;
+			while (copied != sb.st_size) {
+				iz r = read(fd_old, buf, countof(buf));
+				if (r < 0) break;
+				iz w = write(fd_new, buf, r);
+				if (w < 0) break;
+				copied += w;
+			}
+			result = copied == sb.st_size;
+		}
+		if (fd_old != -1) close(fd_old);
+		if (fd_new != -1) close(fd_new);
 	}
-	result = 1;
-ret:
-	if (fd_old != -1) close(fd_old);
-	if (fd_new != -1) close(fd_new);
 	return result;
 }
 
-static void *
+function void *
 os_load_library(char *name, char *temp_name, Stream *e)
 {
 	if (temp_name) {
diff --git a/os_win32.c b/os_win32.c
@@ -1,6 +1,7 @@
 /* See LICENSE for license details. */
 #include "util.h"
 
+#define STD_INPUT_HANDLE  -10
 #define STD_OUTPUT_HANDLE -11
 #define STD_ERROR_HANDLE  -12
 
@@ -73,6 +74,22 @@ typedef struct {
 	iptr event_handle;
 } w32_overlapped;
 
+typedef struct {
+	iptr io_completion_handle;
+	u64  timer_start_time;
+	u64  timer_frequency;
+} w32_context;
+
+typedef enum {
+	W32_IO_FILE_WATCH,
+	W32_IO_PIPE,
+} W32_IO_Event;
+
+typedef struct {
+	u64  tag;
+	iptr context;
+} w32_io_completion_event;
+
 #define W32(r) __declspec(dllimport) r __stdcall
 W32(b32)    CloseHandle(iptr);
 W32(b32)    CopyFileA(c8 *, c8 *, b32);
@@ -125,15 +142,22 @@ static OS_WRITE_FILE_FN(os_write_file)
 	return raw.len == wlen;
 }
 
-static void __attribute__((noreturn))
+function void __attribute__((noreturn))
+os_exit(i32 code)
+{
+	ExitProcess(1);
+	unreachable();
+}
+
+function void __attribute__((noreturn))
 os_fatal(s8 msg)
 {
 	os_write_file(GetStdHandle(STD_ERROR_HANDLE), msg);
-	ExitProcess(1);
+	os_exit(1);
 	unreachable();
 }
 
-static OS_ALLOC_ARENA_FN(os_alloc_arena)
+function OS_ALLOC_ARENA_FN(os_alloc_arena)
 {
 	Arena result;
 	w32_sys_info Info;
@@ -231,13 +255,17 @@ os_open_shared_memory_area(char *name, iz cap)
 	return result;
 }
 
-static void *
+function b32
+os_copy_file(char *name, char *new)
+{
+	return CopyFileA(name, new, 0);
+}
+
+function void *
 os_load_library(char *name, char *temp_name, Stream *e)
 {
-	if (temp_name) {
-		if (CopyFileA(name, temp_name, 0))
-			name = temp_name;
-	}
+	if (temp_name && os_copy_file(name, temp_name))
+		name = temp_name;
 
 	void *result = LoadLibraryA(name);
 	if (!result && e) {