// Bootstrap:
// Windows: cl /nologo build.c
// macOS: cc build.c -o build
// After that, just run: ./build (or build.exe on Windows)
#define BUILD_IMPLEMENTATION
#include "build.h"
////////////////////////////////
// FreeType source files
static const char *freetype_sources[] = {
"vendor/freetype/src/base/ftsystem.c",
"vendor/freetype/src/base/ftinit.c",
"vendor/freetype/src/base/ftdebug.c",
"vendor/freetype/src/base/ftbase.c",
"vendor/freetype/src/base/ftbbox.c",
"vendor/freetype/src/base/ftglyph.c",
"vendor/freetype/src/base/ftbitmap.c",
"vendor/freetype/src/base/ftmm.c",
"vendor/freetype/src/truetype/truetype.c",
"vendor/freetype/src/sfnt/sfnt.c",
"vendor/freetype/src/smooth/smooth.c",
"vendor/freetype/src/autofit/autofit.c",
"vendor/freetype/src/psnames/psnames.c",
"vendor/freetype/src/pshinter/pshinter.c",
"vendor/freetype/src/gzip/ftgzip.c",
};
////////////////////////////////
// Font embedding — reads a .ttf and writes a C header with the data as a byte array
static bool embed_font_file(const char *ttf_path, const char *header_path) {
if (!needs_rebuild1(header_path, ttf_path)) {
build_log(LOG_INFO, "Font header %s is up to date", header_path);
return true;
}
String_Builder sb = {0};
if (!sb_read_file(&sb, ttf_path)) return false;
FILE *out = fopen(header_path, "wb");
if (!out) {
build_log(LOG_ERROR, "Could not open %s for writing", header_path);
sb_free(&sb);
return false;
}
fprintf(out, "// Auto-generated from %s — do not edit\n", ttf_path);
fprintf(out, "#pragma once\n\n");
fprintf(out, "static const unsigned char font_inter_data[] = {\n");
for (size_t i = 0; i < sb.count; i++) {
if (i % 16 == 0) fprintf(out, " ");
fprintf(out, "0x%02x,", (unsigned char)sb.items[i]);
if (i % 16 == 15 || i == sb.count - 1) fprintf(out, "\n");
}
fprintf(out, "};\n\n");
fprintf(out, "static const unsigned int font_inter_size = %zu;\n", sb.count);
fclose(out);
sb_free(&sb);
build_log(LOG_INFO, "Generated %s (%zu bytes)", header_path, sb.count);
return true;
}
#ifdef __APPLE__
////////////////////////////////
// macOS build (clang with Objective-C)
static const char *frameworks[] = {
"-framework", "Metal",
"-framework", "Cocoa",
"-framework", "CoreAudio",
"-framework", "AudioToolbox",
"-framework", "CoreMIDI",
"-framework", "QuartzCore",
"-framework", "CoreFoundation",
};
static bool build_freetype_lib(const char *build_dir, bool debug) {
const char *obj_dir = temp_sprintf("%s/freetype_obj", build_dir);
const char *lib_path = "vendor/freetype/libfreetype.a";
if (!needs_rebuild(lib_path, freetype_sources, ARRAY_LEN(freetype_sources))) {
build_log(LOG_INFO, "freetype is up to date");
return true;
}
if (!mkdir_if_not_exists(obj_dir)) return false;
for (size_t i = 0; i < ARRAY_LEN(freetype_sources); i++) {
const char *src = freetype_sources[i];
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
char obj_name[256];
snprintf(obj_name, sizeof(obj_name), "%s", base);
char *dot = strrchr(obj_name, '.');
if (dot) { dot[1] = 'o'; dot[2] = '\0'; }
const char *obj_path = temp_sprintf("%s/%s", obj_dir, obj_name);
Cmd cmd = {0};
cmd_append(&cmd, "clang");
cmd_append(&cmd, "-std=c11", "-c");
cmd_append(&cmd, "-DFT2_BUILD_LIBRARY");
cmd_append(&cmd, "-Ivendor/freetype/include");
cmd_append(&cmd, "-w");
if (debug) {
cmd_append(&cmd, "-g", "-O0");
} else {
cmd_append(&cmd, "-O2");
}
cmd_append(&cmd, "-o", obj_path);
cmd_append(&cmd, src);
if (!cmd_run(&cmd)) return false;
}
// Archive
{
Cmd cmd = {0};
cmd_append(&cmd, "ar", "rcs", lib_path);
for (size_t i = 0; i < ARRAY_LEN(freetype_sources); i++) {
const char *src = freetype_sources[i];
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
char obj_name[256];
snprintf(obj_name, sizeof(obj_name), "%s", base);
char *dot = strrchr(obj_name, '.');
if (dot) { dot[1] = 'o'; dot[2] = '\0'; }
cmd__append(&cmd, 1, temp_sprintf("%s/%s", obj_dir, obj_name));
}
if (!cmd_run(&cmd)) return false;
}
// Clean up obj dir
{
Cmd cmd = {0};
cmd_append(&cmd, "rm", "-rf", obj_dir);
cmd_run(&cmd);
}
build_log(LOG_INFO, "Built %s", lib_path);
return true;
}
int main(int argc, char **argv) {
GO_REBUILD_URSELF(argc, argv);
bool debug = false;
bool clean = false;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "debug") == 0) debug = true;
else if (strcmp(argv[i], "clean") == 0) clean = true;
else {
build_log(LOG_ERROR, "unknown argument: %s", argv[i]);
build_log(LOG_ERROR, "usage: %s [debug] [clean]", argv[0]);
return 1;
}
}
const char *build_dir = debug ? "build_debug" : "build_release";
if (clean) {
build_log(LOG_INFO, "Cleaning build artifacts");
{ Cmd cmd = {0}; cmd_append(&cmd, "rm", "-rf", "build_debug"); cmd_run(&cmd); }
{ Cmd cmd = {0}; cmd_append(&cmd, "rm", "-rf", "build_release"); cmd_run(&cmd); }
remove("vendor/freetype/libfreetype.a");
remove("src/renderer/font_inter.gen.h");
return 0;
}
// App bundle paths
const char *app_dir = temp_sprintf("%s/autosample.app", build_dir);
const char *contents = temp_sprintf("%s/Contents", app_dir);
const char *macos_dir = temp_sprintf("%s/Contents/MacOS", app_dir);
const char *res_dir = temp_sprintf("%s/Contents/Resources", app_dir);
const char *binary_path = temp_sprintf("%s/Contents/MacOS/autosample", app_dir);
if (!mkdir_if_not_exists(build_dir)) return 1;
if (!mkdir_if_not_exists(app_dir)) return 1;
if (!mkdir_if_not_exists(contents)) return 1;
if (!mkdir_if_not_exists(macos_dir)) return 1;
if (!mkdir_if_not_exists(res_dir)) return 1;
// Build static libraries
if (!build_freetype_lib(build_dir, debug)) return 1;
// Generate embedded font header
if (!embed_font_file("assets/fonts/Inter-Regular.ttf",
"src/renderer/font_inter.gen.h")) return 1;
// Unity build: single clang invocation compiles main.c (which #includes everything)
{
Cmd cmd = {0};
cmd_append(&cmd, "clang");
cmd_append(&cmd, "-std=c11", "-x", "objective-c");
cmd_append(&cmd, "-Wall", "-Wextra", "-Wno-missing-field-initializers");
cmd_append(&cmd, "-Wno-deprecated-declarations");
cmd_append(&cmd, "-Isrc");
cmd_append(&cmd, "-isystem", "vendor/clay");
cmd_append(&cmd, "-isystem", "vendor/nanosvg");
cmd_append(&cmd, "-isystem", "vendor/freetype/include");
if (debug) {
cmd_append(&cmd, "-g", "-O0", "-D_DEBUG");
} else {
cmd_append(&cmd, "-O2", "-DNDEBUG");
}
cmd_append(&cmd, "-o", binary_path);
cmd_append(&cmd, "src/daw/daw_main.c");
// Reset language mode so .a is treated as a library, not source
cmd_append(&cmd, "-x", "none");
cmd_append(&cmd, "vendor/freetype/libfreetype.a");
{
size_t i;
for (i = 0; i < ARRAY_LEN(frameworks); i++)
cmd__append(&cmd, 1, frameworks[i]);
}
cmd_append(&cmd, "-lm");
if (!cmd_run(&cmd)) return 1;
}
// Write Info.plist
{
const char *plist_path = temp_sprintf("%s/Contents/Info.plist", app_dir);
const char *plist =
"\n"
"\n"
"\n"
"\n"
" CFBundleExecutable\n"
" autosample\n"
" CFBundleIdentifier\n"
" com.autosample.app\n"
" CFBundleName\n"
" autosample\n"
" CFBundleVersion\n"
" 0.1\n"
" CFBundleShortVersionString\n"
" 0.1\n"
" CFBundlePackageType\n"
" APPL\n"
" NSHighResolutionCapable\n"
" \n"
" NSSupportsAutomaticGraphicsSwitching\n"
" \n"
"\n"
"\n";
write_entire_file(plist_path, plist, strlen(plist));
}
build_log(LOG_INFO, "Build complete: %s", app_dir);
return 0;
}
#else
////////////////////////////////
// Windows build (MSVC cl.exe)
// Vulkan SDK path — detected from environment or default install location
static const char *get_vulkan_sdk_path(void) {
const char *env = getenv("VULKAN_SDK");
if (env && env[0]) return env;
return "C:\\VulkanSDK\\1.4.341.1";
}
static const char *link_libs[] = {
"user32.lib",
"gdi32.lib",
"shell32.lib",
"ole32.lib",
"advapi32.lib",
"dwmapi.lib",
"winmm.lib",
};
////////////////////////////////
// SPIR-V shader compilation — compiles .glsl to .spv, then embeds as C header
static bool compile_shader(const char *glslc_path, const char *src, const char *spv_path, const char *stage) {
Cmd cmd = {0};
cmd_append(&cmd, glslc_path, temp_sprintf("-fshader-stage=%s", stage), "-o", spv_path, src);
return cmd_run(&cmd);
}
static bool embed_spirv(const char *spv_path, const char *header_path, const char *array_name) {
String_Builder sb = {0};
if (!sb_read_file(&sb, spv_path)) return false;
FILE *out = fopen(header_path, "wb");
if (!out) {
build_log(LOG_ERROR, "Could not open %s for writing", header_path);
sb_free(&sb);
return false;
}
fprintf(out, "// Auto-generated from %s — do not edit\n", spv_path);
fprintf(out, "#pragma once\n\n");
// SPIR-V is U32-aligned, emit as uint32_t array
size_t word_count = sb.count / 4;
fprintf(out, "#include \n\n");
fprintf(out, "static const uint32_t %s[] = {\n", array_name);
const uint32_t *words = (const uint32_t *)sb.items;
for (size_t i = 0; i < word_count; i++) {
if (i % 8 == 0) fprintf(out, " ");
fprintf(out, "0x%08x,", words[i]);
if (i % 8 == 7 || i == word_count - 1) fprintf(out, "\n");
}
fprintf(out, "};\n");
fclose(out);
sb_free(&sb);
build_log(LOG_INFO, "Generated %s (%zu bytes)", header_path, sb.count);
return true;
}
static bool compile_and_embed_shaders(const char *build_dir) {
const char *vk_sdk = get_vulkan_sdk_path();
const char *glslc = temp_sprintf("%s\\Bin\\glslc.exe", vk_sdk);
const char *vert_src = "src/renderer/ui.v.glsl";
const char *frag_src = "src/renderer/ui.f.glsl";
const char *vert_spv = temp_sprintf("%s/ui_vert.spv", build_dir);
const char *frag_spv = temp_sprintf("%s/ui_frag.spv", build_dir);
const char *vert_hdr = "src/renderer/ui_vert.spv.h";
const char *frag_hdr = "src/renderer/ui_frag.spv.h";
// Check if rebuild needed
if (needs_rebuild1(vert_hdr, vert_src)) {
build_log(LOG_INFO, "Compiling vertex shader");
if (!compile_shader(glslc, vert_src, vert_spv, "vertex")) return false;
if (!embed_spirv(vert_spv, vert_hdr, "ui_vert_spv")) return false;
} else {
build_log(LOG_INFO, "Vertex shader is up to date");
}
if (needs_rebuild1(frag_hdr, frag_src)) {
build_log(LOG_INFO, "Compiling fragment shader");
if (!compile_shader(glslc, frag_src, frag_spv, "fragment")) return false;
if (!embed_spirv(frag_spv, frag_hdr, "ui_frag_spv")) return false;
} else {
build_log(LOG_INFO, "Fragment shader is up to date");
}
return true;
}
static bool build_freetype_lib(const char *build_dir, bool debug) {
const char *obj_dir = temp_sprintf("%s\\freetype_obj", build_dir);
const char *lib_path = debug ? "vendor\\freetype\\freetype_d.lib" : "vendor\\freetype\\freetype.lib";
if (!needs_rebuild(lib_path, freetype_sources, ARRAY_LEN(freetype_sources))) {
build_log(LOG_INFO, "freetype is up to date");
return true;
}
if (!mkdir_if_not_exists(obj_dir)) return false;
for (size_t i = 0; i < ARRAY_LEN(freetype_sources); i++) {
const char *src = freetype_sources[i];
Cmd cmd = {0};
cmd_append(&cmd, "cl.exe", "/nologo", "/c");
cmd_append(&cmd, "/std:c11");
cmd_append(&cmd, "/DFT2_BUILD_LIBRARY");
cmd_append(&cmd, "/Ivendor/freetype/include");
cmd_append(&cmd, "/W0");
if (debug) {
cmd_append(&cmd, "/MTd", "/Zi");
} else {
cmd_append(&cmd, "/MT", "/O2");
}
cmd_append(&cmd, temp_sprintf("/Fo:%s/", obj_dir));
cmd_append(&cmd, src);
if (!cmd_run(&cmd)) return false;
}
// Archive
{
Cmd cmd = {0};
cmd_append(&cmd, "lib.exe", "/nologo", temp_sprintf("/OUT:%s", lib_path));
cmd_append(&cmd, temp_sprintf("%s/*.obj", obj_dir));
if (!cmd_run(&cmd)) return false;
}
// Clean up obj dir
{
Cmd cmd = {0};
cmd_append(&cmd, "cmd.exe", "/c",
temp_sprintf("if exist %s rmdir /s /q %s", obj_dir, obj_dir));
cmd_run(&cmd);
}
build_log(LOG_INFO, "Built %s", lib_path);
return true;
}
int main(int argc, char **argv) {
GO_REBUILD_URSELF(argc, argv);
bool debug = false;
bool clean = false;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "debug") == 0) debug = true;
else if (strcmp(argv[i], "clean") == 0) clean = true;
else {
build_log(LOG_ERROR, "unknown argument: %s", argv[i]);
build_log(LOG_ERROR, "usage: %s [debug] [clean]", argv[0]);
return 1;
}
}
const char *build_dir = debug ? "build_debug" : "build_release";
if (clean) {
build_log(LOG_INFO, "Cleaning build artifacts");
{ Cmd cmd = {0}; cmd_append(&cmd, "cmd.exe", "/c",
"if exist build_debug rmdir /s /q build_debug");
cmd_run(&cmd); }
{ Cmd cmd = {0}; cmd_append(&cmd, "cmd.exe", "/c",
"if exist build_release rmdir /s /q build_release");
cmd_run(&cmd); }
remove("vendor\\freetype\\freetype.lib");
remove("vendor\\freetype\\freetype_d.lib");
remove("src\\renderer\\font_inter.gen.h");
remove("src\\renderer\\ui_vert.spv.h");
remove("src\\renderer\\ui_frag.spv.h");
return 0;
}
if (!mkdir_if_not_exists(build_dir)) return 1;
// Build static libraries
if (!build_freetype_lib(build_dir, debug)) return 1;
// Generate embedded font header
if (!embed_font_file("assets/fonts/Inter-Regular.ttf",
"src\\renderer\\font_inter.gen.h")) return 1;
// Compile GLSL shaders to SPIR-V and embed as C headers
if (!compile_and_embed_shaders(build_dir)) return 1;
const char *vk_sdk = get_vulkan_sdk_path();
const char *vk_lib = temp_sprintf("%s/Lib/vulkan-1.lib", vk_sdk);
// Unity build: single cl.exe invocation compiles main.c (which #includes everything)
{
Cmd cmd = {0};
cmd_append(&cmd, "cl.exe");
cmd_append(&cmd, "/nologo", "/std:c11", "/TC", "/W3");
cmd_append(&cmd, "/Isrc");
cmd_append(&cmd, "/external:W0");
cmd_append(&cmd, "/external:Ivendor/clay");
cmd_append(&cmd, "/external:Ivendor/nanosvg");
cmd_append(&cmd, "/external:Ivendor/freetype/include");
cmd_append(&cmd, temp_sprintf("/external:I%s/Include", vk_sdk));
if (debug) {
cmd_append(&cmd, "/MTd", "/Zi", "/D_DEBUG");
} else {
cmd_append(&cmd, "/MT", "/Zi", "/O2", "/DNDEBUG");
}
cmd_append(&cmd, temp_sprintf("/Fe:%s/autosample.exe", build_dir));
cmd_append(&cmd, temp_sprintf("/Fo:%s/", build_dir));
cmd_append(&cmd, temp_sprintf("/Fd:%s/autosample.pdb", build_dir));
cmd_append(&cmd, "src/daw/daw_main.c");
cmd_append(&cmd, "/link");
cmd_append(&cmd, "/MACHINE:X64");
cmd_append(&cmd, debug ? "/SUBSYSTEM:CONSOLE" : "/SUBSYSTEM:WINDOWS");
if (!debug) cmd_append(&cmd, "/ENTRY:mainCRTStartup");
cmd_append(&cmd, temp_sprintf("/PDB:%s/autosample.pdb", build_dir));
cmd_append(&cmd, "/DEBUG");
cmd_append(&cmd, debug ? "vendor/freetype/freetype_d.lib" : "vendor/freetype/freetype.lib");
cmd_append(&cmd, vk_lib);
{
size_t i;
for (i = 0; i < ARRAY_LEN(link_libs); i++)
cmd__append(&cmd, 1, link_libs[i]);
}
if (!cmd_run(&cmd)) return 1;
}
// Clean up obj files
delete_file(temp_sprintf("%s/daw_main.obj", build_dir));
build_log(LOG_INFO, "Build complete: %s/autosample.exe", build_dir);
return 0;
}
#endif