Revert "replace nob with new custom noblike"

This reverts commit 06e1212483.
This commit is contained in:
2026-03-12 16:30:04 -04:00
parent 06e1212483
commit 48f2c51d92
6 changed files with 3945 additions and 1335 deletions

18
.vscode/tasks.json vendored
View File

@@ -4,15 +4,15 @@
{ {
"label": "bootstrap", "label": "bootstrap",
"type": "shell", "type": "shell",
"command": "c++", "command": "cc",
"args": ["build.cpp", "-o", "build"], "args": ["build.c", "-o", "build"],
"windows": { "windows": {
"command": "cl", "command": "cl",
"args": ["/nologo", "build.cpp"] "args": ["/nologo", "build.c"]
}, },
"group": "build", "group": "build",
"problemMatcher": [], "problemMatcher": [],
"detail": "One-time bootstrap: compile build.cpp" "detail": "One-time bootstrap: compile build.c"
}, },
{ {
"label": "build", "label": "build",
@@ -63,15 +63,15 @@
{ {
"label": "clean", "label": "clean",
"type": "shell", "type": "shell",
"command": "${workspaceFolder}/build", "command": "rm",
"args": ["clean"], "args": ["-rf", "build_debug", "build_release"],
"windows": { "windows": {
"command": "${workspaceFolder}/build.exe", "command": "PowerShell",
"args": ["clean"] "args": ["-Command", "Remove-Item -Recurse -Force -ErrorAction SilentlyContinue build_debug, build_release"]
}, },
"group": "build", "group": "build",
"problemMatcher": [], "problemMatcher": [],
"detail": "Clean build artifacts" "detail": "Remove build directories"
} }
] ]
} }

View File

@@ -11,7 +11,7 @@ Requires MSVC (Visual Studio 2019 Build Tools or later) with the Windows SDK and
Open a Developer Command Prompt, then: Open a Developer Command Prompt, then:
``` ```
cl /nologo build.cpp cl /nologo build.c
build.exe debug build.exe debug
build_debug\autosample.exe build_debug\autosample.exe
``` ```
@@ -21,12 +21,12 @@ build_debug\autosample.exe
Requires Xcode Command Line Tools (`xcode-select --install`). Requires Xcode Command Line Tools (`xcode-select --install`).
``` ```
c++ build.cpp -o build cc build.c -o build
./build debug ./build debug
open build_debug/autosample.app open build_debug/autosample.app
``` ```
The first command is a one-time bootstrap. After that, `./build` detects changes to `build.cpp` and rebuilds itself automatically. Pass `debug` for a debug build (output in `build_debug/`) or omit for release (`build_release/`). Pass `clean` to wipe both build directories, vendor libraries, and generated sources. Invalid arguments produce an error with usage info. The first command is a one-time bootstrap. After that, `./build` detects changes to `build.c` and rebuilds itself automatically. Pass `debug` for a debug build (output in `build_debug/`) or omit for release (`build_release/`). Pass `clean` to wipe both build directories, vendor libraries, and generated sources. Invalid arguments produce an error with usage info.
## Architecture ## Architecture
@@ -96,13 +96,14 @@ MIDI device enumeration with real-time input monitoring.
## Project structure ## Project structure
``` ```
build.h Build system library (stb-style single header) build.c Build script (cc build.c -o build)
build.cpp Build script (c++ build.cpp -o build)
assets/ assets/
fonts/ fonts/
Inter-Regular.ttf Inter typeface (SIL Open Font License) Inter-Regular.ttf Inter typeface (SIL Open Font License)
OFL.txt Font license OFL.txt Font license
vendor/ vendor/
nob/
nob.h nob build system (vendored, single-header)
clay/ clay/
clay.h Clay v0.14 (modified for MSVC C++ compatibility) clay.h Clay v0.14 (modified for MSVC C++ compatibility)
freetype/ freetype/
@@ -166,6 +167,7 @@ Memory should be managed with arena allocators where possible rather than indivi
All dependencies are vendored as source. On Windows, nothing to install beyond the Windows SDK. On macOS, only Xcode Command Line Tools are needed — all frameworks (Metal, Cocoa, CoreAudio, CoreMIDI, etc.) ship with the OS. All dependencies are vendored as source. On Windows, nothing to install beyond the Windows SDK. On macOS, only Xcode Command Line Tools are needed — all frameworks (Metal, Cocoa, CoreAudio, CoreMIDI, etc.) ship with the OS.
- [nob.h](https://github.com/tsoding/nob.h) — build system
- [Clay](https://github.com/nicbarker/clay) — single-header C layout library (v0.14, with MSVC C++ patches) - [Clay](https://github.com/nicbarker/clay) — single-header C layout library (v0.14, with MSVC C++ patches)
- [FreeType](https://freetype.org/) — font rasterizer (v2.13.3, FreeType License). Compiled as a static library with a minimal module set (truetype, sfnt, smooth, autofit, psnames, pshinter, gzip). Replaces platform-specific font backends (GDI on Windows, Core Text on macOS) for consistent cross-platform text rendering. Static libraries are built into their vendor directories (`vendor/freetype/freetype.lib` on Windows, `vendor/freetype/libfreetype.a` on macOS); debug builds on Windows use a `_d` suffix to avoid CRT mismatch. - [FreeType](https://freetype.org/) — font rasterizer (v2.13.3, FreeType License). Compiled as a static library with a minimal module set (truetype, sfnt, smooth, autofit, psnames, pshinter, gzip). Replaces platform-specific font backends (GDI on Windows, Core Text on macOS) for consistent cross-platform text rendering. Static libraries are built into their vendor directories (`vendor/freetype/freetype.lib` on Windows, `vendor/freetype/libfreetype.a` on macOS); debug builds on Windows use a `_d` suffix to avoid CRT mismatch.
- [Inter](https://rsms.me/inter/) — typeface (v4.1, SIL Open Font License). Embedded at build time as a C byte array header so there are no runtime font file dependencies. - [Inter](https://rsms.me/inter/) — typeface (v4.1, SIL Open Font License). Embedded at build time as a C byte array header so there are no runtime font file dependencies.

773
build.c Normal file
View File

@@ -0,0 +1,773 @@
// Bootstrap:
// Windows: cl /nologo build.c
// macOS: cc build.c -o build
// After that, just run: ./build (or build.exe on Windows)
#define NOB_IMPLEMENTATION
#include "vendor/nob/nob.h"
////////////////////////////////
// lunasvg/plutovg static library build helpers
static const char *plutovg_sources[] = {
"vendor/lunasvg/plutovg/source/plutovg-blend.c",
"vendor/lunasvg/plutovg/source/plutovg-canvas.c",
"vendor/lunasvg/plutovg/source/plutovg-font.c",
"vendor/lunasvg/plutovg/source/plutovg-ft-math.c",
"vendor/lunasvg/plutovg/source/plutovg-ft-raster.c",
"vendor/lunasvg/plutovg/source/plutovg-ft-stroker.c",
"vendor/lunasvg/plutovg/source/plutovg-matrix.c",
"vendor/lunasvg/plutovg/source/plutovg-paint.c",
"vendor/lunasvg/plutovg/source/plutovg-path.c",
"vendor/lunasvg/plutovg/source/plutovg-rasterize.c",
"vendor/lunasvg/plutovg/source/plutovg-surface.c",
};
static const char *lunasvg_sources[] = {
"vendor/lunasvg/source/graphics.cpp",
"vendor/lunasvg/source/lunasvg.cpp",
"vendor/lunasvg/source/svgelement.cpp",
"vendor/lunasvg/source/svggeometryelement.cpp",
"vendor/lunasvg/source/svglayoutstate.cpp",
"vendor/lunasvg/source/svgpaintelement.cpp",
"vendor/lunasvg/source/svgparser.cpp",
"vendor/lunasvg/source/svgproperty.cpp",
"vendor/lunasvg/source/svgrenderstate.cpp",
"vendor/lunasvg/source/svgtextelement.cpp",
};
////////////////////////////////
// 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 (!nob_needs_rebuild1(header_path, ttf_path)) {
nob_log(NOB_INFO, "Font header %s is up to date", header_path);
return true;
}
Nob_String_Builder sb = {0};
if (!nob_read_entire_file(ttf_path, &sb)) return false;
FILE *out = fopen(header_path, "wb");
if (!out) {
nob_log(NOB_ERROR, "Could not open %s for writing", header_path);
nob_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);
nob_sb_free(sb);
nob_log(NOB_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_lunasvg_lib(const char *build_dir, bool debug) {
const char *obj_dir = nob_temp_sprintf("%s/lunasvg_obj", build_dir);
const char *lib_path = "vendor/lunasvg/liblunasvg.a";
// Collect all source paths to check if rebuild is needed
{
const char *all_sources[NOB_ARRAY_LEN(plutovg_sources) + NOB_ARRAY_LEN(lunasvg_sources)];
size_t n = 0;
for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++)
all_sources[n++] = plutovg_sources[i];
for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++)
all_sources[n++] = lunasvg_sources[i];
if (!nob_needs_rebuild(lib_path, all_sources, n)) {
nob_log(NOB_INFO, "lunasvg is up to date");
return true;
}
}
if (!nob_mkdir_if_not_exists(obj_dir)) return false;
// Compile plutovg C sources
for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) {
const char *src = plutovg_sources[i];
// Extract filename for .o
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 = nob_temp_sprintf("%s/%s", obj_dir, obj_name);
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "clang");
nob_cmd_append(&cmd, "-std=c11", "-c");
nob_cmd_append(&cmd, "-DPLUTOVG_BUILD", "-DPLUTOVG_BUILD_STATIC");
nob_cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/include");
nob_cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/source");
nob_cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter");
if (debug) {
nob_cmd_append(&cmd, "-g", "-O0");
} else {
nob_cmd_append(&cmd, "-O2");
}
nob_cmd_append(&cmd, "-o", obj_path);
nob_cmd_append(&cmd, src);
Nob_Cmd_Opt copt = {0};
if (!nob_cmd_run_opt(&cmd, copt)) return false;
}
// Compile lunasvg C++ sources
for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) {
const char *src = lunasvg_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 = nob_temp_sprintf("%s/%s", obj_dir, obj_name);
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "clang++");
nob_cmd_append(&cmd, "-std=c++17", "-c");
nob_cmd_append(&cmd, "-fno-exceptions", "-fno-rtti");
nob_cmd_append(&cmd, "-DLUNASVG_BUILD", "-DLUNASVG_BUILD_STATIC", "-DPLUTOVG_BUILD_STATIC");
nob_cmd_append(&cmd, "-Ivendor/lunasvg/include");
nob_cmd_append(&cmd, "-Ivendor/lunasvg/source");
nob_cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/include");
nob_cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter");
if (debug) {
nob_cmd_append(&cmd, "-g", "-O0");
} else {
nob_cmd_append(&cmd, "-O2");
}
nob_cmd_append(&cmd, "-o", obj_path);
nob_cmd_append(&cmd, src);
Nob_Cmd_Opt copt = {0};
if (!nob_cmd_run_opt(&cmd, copt)) return false;
}
// Archive into static library
{
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "ar", "rcs", lib_path);
for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) {
const char *src = plutovg_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'; }
nob_cmd_append(&cmd, nob_temp_sprintf("%s/%s", obj_dir, obj_name));
}
for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) {
const char *src = lunasvg_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'; }
nob_cmd_append(&cmd, nob_temp_sprintf("%s/%s", obj_dir, obj_name));
}
Nob_Cmd_Opt copt = {0};
if (!nob_cmd_run_opt(&cmd, copt)) return false;
}
// Clean up obj dir — only the .a is needed
{
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "rm", "-rf", obj_dir);
Nob_Cmd_Opt copt = {0};
nob_cmd_run_opt(&cmd, copt);
}
nob_log(NOB_INFO, "Built %s", lib_path);
return true;
}
static bool build_freetype_lib(const char *build_dir, bool debug) {
const char *obj_dir = nob_temp_sprintf("%s/freetype_obj", build_dir);
const char *lib_path = "vendor/freetype/libfreetype.a";
if (!nob_needs_rebuild(lib_path, freetype_sources, NOB_ARRAY_LEN(freetype_sources))) {
nob_log(NOB_INFO, "freetype is up to date");
return true;
}
if (!nob_mkdir_if_not_exists(obj_dir)) return false;
for (size_t i = 0; i < NOB_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 = nob_temp_sprintf("%s/%s", obj_dir, obj_name);
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "clang");
nob_cmd_append(&cmd, "-std=c11", "-c");
nob_cmd_append(&cmd, "-DFT2_BUILD_LIBRARY");
nob_cmd_append(&cmd, "-Ivendor/freetype/include");
nob_cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter");
if (debug) {
nob_cmd_append(&cmd, "-g", "-O0");
} else {
nob_cmd_append(&cmd, "-O2");
}
nob_cmd_append(&cmd, "-o", obj_path);
nob_cmd_append(&cmd, src);
Nob_Cmd_Opt copt = {0};
if (!nob_cmd_run_opt(&cmd, copt)) return false;
}
// Archive
{
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "ar", "rcs", lib_path);
for (size_t i = 0; i < NOB_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'; }
nob_cmd_append(&cmd, nob_temp_sprintf("%s/%s", obj_dir, obj_name));
}
Nob_Cmd_Opt copt = {0};
if (!nob_cmd_run_opt(&cmd, copt)) return false;
}
// Clean up obj dir
{
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "rm", "-rf", obj_dir);
Nob_Cmd_Opt copt = {0};
nob_cmd_run_opt(&cmd, copt);
}
nob_log(NOB_INFO, "Built %s", lib_path);
return true;
}
int main(int argc, char **argv) {
NOB_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 {
nob_log(NOB_ERROR, "unknown argument: %s", argv[i]);
nob_log(NOB_ERROR, "usage: %s [debug] [clean]", argv[0]);
return 1;
}
}
const char *build_dir = debug ? "build_debug" : "build_release";
if (clean) {
nob_log(NOB_INFO, "Cleaning build artifacts");
{ Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "rm", "-rf", "build_debug");
Nob_Cmd_Opt opt = {0}; nob_cmd_run_opt(&cmd, opt); }
{ Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "rm", "-rf", "build_release");
Nob_Cmd_Opt opt = {0}; nob_cmd_run_opt(&cmd, opt); }
remove("vendor/lunasvg/liblunasvg.a");
remove("vendor/freetype/libfreetype.a");
remove("src/renderer/font_inter.gen.h");
return 0;
}
// App bundle paths
const char *app_dir = nob_temp_sprintf("%s/autosample.app", build_dir);
const char *contents = nob_temp_sprintf("%s/Contents", app_dir);
const char *macos_dir = nob_temp_sprintf("%s/Contents/MacOS", app_dir);
const char *res_dir = nob_temp_sprintf("%s/Contents/Resources", app_dir);
const char *binary_path = nob_temp_sprintf("%s/Contents/MacOS/autosample", app_dir);
if (!nob_mkdir_if_not_exists(build_dir)) return 1;
if (!nob_mkdir_if_not_exists(app_dir)) return 1;
if (!nob_mkdir_if_not_exists(contents)) return 1;
if (!nob_mkdir_if_not_exists(macos_dir)) return 1;
if (!nob_mkdir_if_not_exists(res_dir)) return 1;
// Build static libraries
if (!build_lunasvg_lib(build_dir, debug)) return 1;
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.cpp (which #includes everything)
{
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "clang++");
nob_cmd_append(&cmd, "-std=c++20", "-x", "objective-c++");
nob_cmd_append(&cmd, "-fno-exceptions", "-fno-rtti");
nob_cmd_append(&cmd, "-Wall", "-Wextra", "-Wno-missing-field-initializers");
nob_cmd_append(&cmd, "-Wno-deprecated-declarations");
nob_cmd_append(&cmd, "-Isrc", "-Ivendor/clay");
nob_cmd_append(&cmd, "-Ivendor/lunasvg/include");
nob_cmd_append(&cmd, "-Ivendor/freetype/include");
nob_cmd_append(&cmd, "-DLUNASVG_BUILD_STATIC");
if (debug) {
nob_cmd_append(&cmd, "-g", "-O0", "-D_DEBUG");
} else {
nob_cmd_append(&cmd, "-O2", "-DNDEBUG");
}
nob_cmd_append(&cmd, "-o", binary_path);
nob_cmd_append(&cmd, "src/main.cpp");
// Reset language mode so .a is treated as a library, not source
nob_cmd_append(&cmd, "-x", "none");
nob_cmd_append(&cmd, "vendor/lunasvg/liblunasvg.a");
nob_cmd_append(&cmd, "vendor/freetype/libfreetype.a");
{
size_t i;
for (i = 0; i < NOB_ARRAY_LEN(frameworks); i++)
nob_cmd_append(&cmd, frameworks[i]);
}
nob_cmd_append(&cmd, "-lstdc++");
{ Nob_Cmd_Opt opt = {0}; if (!nob_cmd_run_opt(&cmd, opt)) return 1; }
}
// Write Info.plist
{
const char *plist_path = nob_temp_sprintf("%s/Contents/Info.plist", app_dir);
nob_write_entire_file(plist_path,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n"
" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">\n"
"<dict>\n"
" <key>CFBundleExecutable</key>\n"
" <string>autosample</string>\n"
" <key>CFBundleIdentifier</key>\n"
" <string>com.autosample.app</string>\n"
" <key>CFBundleName</key>\n"
" <string>autosample</string>\n"
" <key>CFBundleVersion</key>\n"
" <string>0.1</string>\n"
" <key>CFBundleShortVersionString</key>\n"
" <string>0.1</string>\n"
" <key>CFBundlePackageType</key>\n"
" <string>APPL</string>\n"
" <key>NSHighResolutionCapable</key>\n"
" <true/>\n"
" <key>NSSupportsAutomaticGraphicsSwitching</key>\n"
" <true/>\n"
"</dict>\n"
"</plist>\n",
strlen(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n"
" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">\n"
"<dict>\n"
" <key>CFBundleExecutable</key>\n"
" <string>autosample</string>\n"
" <key>CFBundleIdentifier</key>\n"
" <string>com.autosample.app</string>\n"
" <key>CFBundleName</key>\n"
" <string>autosample</string>\n"
" <key>CFBundleVersion</key>\n"
" <string>0.1</string>\n"
" <key>CFBundleShortVersionString</key>\n"
" <string>0.1</string>\n"
" <key>CFBundlePackageType</key>\n"
" <string>APPL</string>\n"
" <key>NSHighResolutionCapable</key>\n"
" <true/>\n"
" <key>NSSupportsAutomaticGraphicsSwitching</key>\n"
" <true/>\n"
"</dict>\n"
"</plist>\n"
));
}
nob_log(NOB_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) {
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, glslc_path, nob_temp_sprintf("-fshader-stage=%s", stage), "-o", spv_path, src);
Nob_Cmd_Opt opt = {0};
return nob_cmd_run_opt(&cmd, opt);
}
static bool embed_spirv(const char *spv_path, const char *header_path, const char *array_name) {
Nob_String_Builder sb = {0};
if (!nob_read_entire_file(spv_path, &sb)) return false;
FILE *out = fopen(header_path, "wb");
if (!out) {
nob_log(NOB_ERROR, "Could not open %s for writing", header_path);
nob_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 <stdint.h>\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);
nob_sb_free(sb);
nob_log(NOB_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 = nob_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 = nob_temp_sprintf("%s/ui_vert.spv", build_dir);
const char *frag_spv = nob_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 (nob_needs_rebuild1(vert_hdr, vert_src)) {
nob_log(NOB_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 {
nob_log(NOB_INFO, "Vertex shader is up to date");
}
if (nob_needs_rebuild1(frag_hdr, frag_src)) {
nob_log(NOB_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 {
nob_log(NOB_INFO, "Fragment shader is up to date");
}
return true;
}
static bool build_lunasvg_lib(const char *build_dir, bool debug) {
const char *obj_dir = nob_temp_sprintf("%s\\lunasvg_obj", build_dir);
const char *lib_path = debug ? "vendor\\lunasvg\\lunasvg_d.lib" : "vendor\\lunasvg\\lunasvg.lib";
// Check if rebuild is needed
{
const char *all_sources[NOB_ARRAY_LEN(plutovg_sources) + NOB_ARRAY_LEN(lunasvg_sources)];
size_t n = 0;
for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++)
all_sources[n++] = plutovg_sources[i];
for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++)
all_sources[n++] = lunasvg_sources[i];
if (!nob_needs_rebuild(lib_path, all_sources, n)) {
nob_log(NOB_INFO, "lunasvg is up to date");
return true;
}
}
if (!nob_mkdir_if_not_exists(obj_dir)) return false;
// Compile plutovg C sources
for (size_t i = 0; i < NOB_ARRAY_LEN(plutovg_sources); i++) {
const char *src = plutovg_sources[i];
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "cl.exe", "/nologo", "/c");
nob_cmd_append(&cmd, "/std:c11");
nob_cmd_append(&cmd, "/DPLUTOVG_BUILD", "/DPLUTOVG_BUILD_STATIC");
nob_cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/include");
nob_cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/source");
nob_cmd_append(&cmd, "/W3");
if (debug) {
nob_cmd_append(&cmd, "/MTd", "/Zi", "/Od");
} else {
nob_cmd_append(&cmd, "/MT", "/O2");
}
nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", obj_dir));
nob_cmd_append(&cmd, src);
Nob_Cmd_Opt copt = {0};
if (!nob_cmd_run_opt(&cmd, copt)) return false;
}
// Compile lunasvg C++ sources
for (size_t i = 0; i < NOB_ARRAY_LEN(lunasvg_sources); i++) {
const char *src = lunasvg_sources[i];
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "cl.exe", "/nologo", "/c");
nob_cmd_append(&cmd, "/std:c++17", "/EHsc");
nob_cmd_append(&cmd, "/DLUNASVG_BUILD", "/DLUNASVG_BUILD_STATIC", "/DPLUTOVG_BUILD_STATIC");
nob_cmd_append(&cmd, "/Ivendor/lunasvg/include");
nob_cmd_append(&cmd, "/Ivendor/lunasvg/source");
nob_cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/include");
nob_cmd_append(&cmd, "/W3");
if (debug) {
nob_cmd_append(&cmd, "/MTd", "/Zi", "/Od");
} else {
nob_cmd_append(&cmd, "/MT", "/O2");
}
nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", obj_dir));
nob_cmd_append(&cmd, src);
Nob_Cmd_Opt copt = {0};
if (!nob_cmd_run_opt(&cmd, copt)) return false;
}
// Archive into static library
{
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "lib.exe", "/nologo", nob_temp_sprintf("/OUT:%s", lib_path));
nob_cmd_append(&cmd, nob_temp_sprintf("%s/*.obj", obj_dir));
Nob_Cmd_Opt copt = {0};
if (!nob_cmd_run_opt(&cmd, copt)) return false;
}
// Clean up obj dir — only the .lib is needed
{
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "cmd.exe", "/c",
nob_temp_sprintf("if exist %s rmdir /s /q %s", obj_dir, obj_dir));
Nob_Cmd_Opt copt = {0};
nob_cmd_run_opt(&cmd, copt);
}
nob_log(NOB_INFO, "Built %s", lib_path);
return true;
}
static bool build_freetype_lib(const char *build_dir, bool debug) {
const char *obj_dir = nob_temp_sprintf("%s\\freetype_obj", build_dir);
const char *lib_path = debug ? "vendor\\freetype\\freetype_d.lib" : "vendor\\freetype\\freetype.lib";
if (!nob_needs_rebuild(lib_path, freetype_sources, NOB_ARRAY_LEN(freetype_sources))) {
nob_log(NOB_INFO, "freetype is up to date");
return true;
}
if (!nob_mkdir_if_not_exists(obj_dir)) return false;
for (size_t i = 0; i < NOB_ARRAY_LEN(freetype_sources); i++) {
const char *src = freetype_sources[i];
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "cl.exe", "/nologo", "/c");
nob_cmd_append(&cmd, "/std:c11");
nob_cmd_append(&cmd, "/DFT2_BUILD_LIBRARY");
nob_cmd_append(&cmd, "/Ivendor/freetype/include");
nob_cmd_append(&cmd, "/W3");
if (debug) {
nob_cmd_append(&cmd, "/MTd", "/Zi", "/Od");
} else {
nob_cmd_append(&cmd, "/MT", "/O2");
}
nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", obj_dir));
nob_cmd_append(&cmd, src);
Nob_Cmd_Opt copt = {0};
if (!nob_cmd_run_opt(&cmd, copt)) return false;
}
// Archive
{
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "lib.exe", "/nologo", nob_temp_sprintf("/OUT:%s", lib_path));
nob_cmd_append(&cmd, nob_temp_sprintf("%s/*.obj", obj_dir));
Nob_Cmd_Opt copt = {0};
if (!nob_cmd_run_opt(&cmd, copt)) return false;
}
// Clean up obj dir
{
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "cmd.exe", "/c",
nob_temp_sprintf("if exist %s rmdir /s /q %s", obj_dir, obj_dir));
Nob_Cmd_Opt copt = {0};
nob_cmd_run_opt(&cmd, copt);
}
nob_log(NOB_INFO, "Built %s", lib_path);
return true;
}
int main(int argc, char **argv) {
NOB_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 {
nob_log(NOB_ERROR, "unknown argument: %s", argv[i]);
nob_log(NOB_ERROR, "usage: %s [debug] [clean]", argv[0]);
return 1;
}
}
const char *build_dir = debug ? "build_debug" : "build_release";
if (clean) {
nob_log(NOB_INFO, "Cleaning build artifacts");
{ Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cmd.exe", "/c",
"if exist build_debug rmdir /s /q build_debug");
Nob_Cmd_Opt opt = {0}; nob_cmd_run_opt(&cmd, opt); }
{ Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cmd.exe", "/c",
"if exist build_release rmdir /s /q build_release");
Nob_Cmd_Opt opt = {0}; nob_cmd_run_opt(&cmd, opt); }
remove("vendor\\lunasvg\\lunasvg.lib");
remove("vendor\\lunasvg\\lunasvg_d.lib");
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 (!nob_mkdir_if_not_exists(build_dir)) return 1;
// Build static libraries
if (!build_lunasvg_lib(build_dir, debug)) return 1;
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_include = nob_temp_sprintf("/I%s/Include", vk_sdk);
const char *vk_lib = nob_temp_sprintf("%s/Lib/vulkan-1.lib", vk_sdk);
// Unity build: single cl.exe invocation compiles main.cpp (which #includes everything including the vulkan renderer)
{
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "cl.exe");
nob_cmd_append(&cmd, "/nologo", "/std:c++20", "/EHsc", "/W3");
nob_cmd_append(&cmd, "/Isrc", "/Ivendor/clay");
nob_cmd_append(&cmd, "/Ivendor/lunasvg/include");
nob_cmd_append(&cmd, "/Ivendor/freetype/include");
nob_cmd_append(&cmd, vk_include);
nob_cmd_append(&cmd, "/DLUNASVG_BUILD_STATIC");
if (debug) {
nob_cmd_append(&cmd, "/MTd", "/Zi", "/Od", "/D_DEBUG");
} else {
nob_cmd_append(&cmd, "/MT", "/Zi", "/O2", "/DNDEBUG");
}
nob_cmd_append(&cmd, nob_temp_sprintf("/Fe:%s/autosample.exe", build_dir));
nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", build_dir));
nob_cmd_append(&cmd, nob_temp_sprintf("/Fd:%s/autosample.pdb", build_dir));
nob_cmd_append(&cmd, "src/main.cpp");
nob_cmd_append(&cmd, "/link");
nob_cmd_append(&cmd, "/MACHINE:X64");
nob_cmd_append(&cmd, "/SUBSYSTEM:CONSOLE");
nob_cmd_append(&cmd, nob_temp_sprintf("/PDB:%s/autosample.pdb", build_dir));
nob_cmd_append(&cmd, "/DEBUG");
nob_cmd_append(&cmd, debug ? "vendor/lunasvg/lunasvg_d.lib" : "vendor/lunasvg/lunasvg.lib");
nob_cmd_append(&cmd, debug ? "vendor/freetype/freetype_d.lib" : "vendor/freetype/freetype.lib");
nob_cmd_append(&cmd, vk_lib);
{
size_t i;
for (i = 0; i < NOB_ARRAY_LEN(link_libs); i++)
nob_cmd_append(&cmd, link_libs[i]);
}
{ Nob_Cmd_Opt opt = {0}; if (!nob_cmd_run_opt(&cmd, opt)) return 1; }
}
// Clean up obj files
nob_delete_file(nob_temp_sprintf("%s/main.obj", build_dir));
nob_log(NOB_INFO, "Build complete: %s/autosample.exe", build_dir);
return 0;
}
#endif

726
build.cpp
View File

@@ -1,726 +0,0 @@
// build.cpp — Build script for autosample
//
// Bootstrap (one-time):
// Windows: cl /nologo build.cpp
// macOS: c++ build.cpp -o build
// After that, just run: ./build (or build.exe on Windows)
#define BUILD_IMPLEMENTATION
#include "build.h"
////////////////////////////////
// lunasvg/plutovg static library build helpers
static const char *plutovg_sources[] = {
"vendor/lunasvg/plutovg/source/plutovg-blend.c",
"vendor/lunasvg/plutovg/source/plutovg-canvas.c",
"vendor/lunasvg/plutovg/source/plutovg-font.c",
"vendor/lunasvg/plutovg/source/plutovg-ft-math.c",
"vendor/lunasvg/plutovg/source/plutovg-ft-raster.c",
"vendor/lunasvg/plutovg/source/plutovg-ft-stroker.c",
"vendor/lunasvg/plutovg/source/plutovg-matrix.c",
"vendor/lunasvg/plutovg/source/plutovg-paint.c",
"vendor/lunasvg/plutovg/source/plutovg-path.c",
"vendor/lunasvg/plutovg/source/plutovg-rasterize.c",
"vendor/lunasvg/plutovg/source/plutovg-surface.c",
};
static const char *lunasvg_sources[] = {
"vendor/lunasvg/source/graphics.cpp",
"vendor/lunasvg/source/lunasvg.cpp",
"vendor/lunasvg/source/svgelement.cpp",
"vendor/lunasvg/source/svggeometryelement.cpp",
"vendor/lunasvg/source/svglayoutstate.cpp",
"vendor/lunasvg/source/svgpaintelement.cpp",
"vendor/lunasvg/source/svgparser.cpp",
"vendor/lunasvg/source/svgproperty.cpp",
"vendor/lunasvg/source/svgrenderstate.cpp",
"vendor/lunasvg/source/svgtextelement.cpp",
};
////////////////////////////////
// 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 = {};
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);
build_log(LOG_INFO, "Generated %s (%zu bytes)", header_path, sb.count);
sb_free(&sb);
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_lunasvg_lib(const char *build_dir, bool debug) {
const char *obj_dir = temp_sprintf("%s/lunasvg_obj", build_dir);
const char *lib_path = "vendor/lunasvg/liblunasvg.a";
// Collect all source paths to check if rebuild is needed
{
const char *all_sources[ARRAY_LEN(plutovg_sources) + ARRAY_LEN(lunasvg_sources)];
size_t n = 0;
for (size_t i = 0; i < ARRAY_LEN(plutovg_sources); i++)
all_sources[n++] = plutovg_sources[i];
for (size_t i = 0; i < ARRAY_LEN(lunasvg_sources); i++)
all_sources[n++] = lunasvg_sources[i];
if (!needs_rebuild(lib_path, all_sources, n)) {
build_log(LOG_INFO, "lunasvg is up to date");
return true;
}
}
if (!mkdir_if_not_exists(obj_dir)) return false;
// Compile plutovg C sources
for (size_t i = 0; i < ARRAY_LEN(plutovg_sources); i++) {
const char *src = plutovg_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 = {};
cmd_append(&cmd, "clang");
cmd_append(&cmd, "-std=c11", "-c");
cmd_append(&cmd, "-DPLUTOVG_BUILD", "-DPLUTOVG_BUILD_STATIC");
cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/include");
cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/source");
cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter");
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;
}
// Compile lunasvg C++ sources
for (size_t i = 0; i < ARRAY_LEN(lunasvg_sources); i++) {
const char *src = lunasvg_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 = {};
cmd_append(&cmd, "clang++");
cmd_append(&cmd, "-std=c++17", "-c");
cmd_append(&cmd, "-fno-exceptions", "-fno-rtti");
cmd_append(&cmd, "-DLUNASVG_BUILD", "-DLUNASVG_BUILD_STATIC", "-DPLUTOVG_BUILD_STATIC");
cmd_append(&cmd, "-Ivendor/lunasvg/include");
cmd_append(&cmd, "-Ivendor/lunasvg/source");
cmd_append(&cmd, "-Ivendor/lunasvg/plutovg/include");
cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter");
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 into static library
{
Cmd cmd = {};
cmd_append(&cmd, "ar", "rcs", lib_path);
for (size_t i = 0; i < ARRAY_LEN(plutovg_sources); i++) {
const char *src = plutovg_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));
}
for (size_t i = 0; i < ARRAY_LEN(lunasvg_sources); i++) {
const char *src = lunasvg_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 — only the .a is needed
{
Cmd cmd = {};
cmd_append(&cmd, "rm", "-rf", obj_dir);
cmd_run(&cmd);
}
build_log(LOG_INFO, "Built %s", lib_path);
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 = "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 = {};
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, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter");
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 = {};
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 = {};
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 = {}; cmd_append(&cmd, "rm", "-rf", "build_debug"); cmd_run(&cmd); }
{ Cmd cmd = {}; cmd_append(&cmd, "rm", "-rf", "build_release"); cmd_run(&cmd); }
remove("vendor/lunasvg/liblunasvg.a");
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_lunasvg_lib(build_dir, debug)) return 1;
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.cpp (which #includes everything)
{
Cmd cmd = {};
cmd_append(&cmd, "clang++");
cmd_append(&cmd, "-std=c++20", "-x", "objective-c++");
cmd_append(&cmd, "-fno-exceptions", "-fno-rtti");
cmd_append(&cmd, "-Wall", "-Wextra", "-Wno-missing-field-initializers");
cmd_append(&cmd, "-Wno-deprecated-declarations");
cmd_append(&cmd, "-Isrc", "-Ivendor/clay");
cmd_append(&cmd, "-Ivendor/lunasvg/include");
cmd_append(&cmd, "-Ivendor/freetype/include");
cmd_append(&cmd, "-DLUNASVG_BUILD_STATIC");
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/main.cpp");
// Reset language mode so .a is treated as a library, not source
cmd_append(&cmd, "-x", "none");
cmd_append(&cmd, "vendor/lunasvg/liblunasvg.a");
cmd_append(&cmd, "vendor/freetype/libfreetype.a");
for (size_t i = 0; i < ARRAY_LEN(frameworks); i++)
cmd__append(&cmd, 1, frameworks[i]);
cmd_append(&cmd, "-lstdc++");
if (!cmd_run(&cmd)) return 1;
}
// Write Info.plist
{
const char *plist_path = temp_sprintf("%s/Contents/Info.plist", app_dir);
const char *plist =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n"
" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">\n"
"<dict>\n"
" <key>CFBundleExecutable</key>\n"
" <string>autosample</string>\n"
" <key>CFBundleIdentifier</key>\n"
" <string>com.autosample.app</string>\n"
" <key>CFBundleName</key>\n"
" <string>autosample</string>\n"
" <key>CFBundleVersion</key>\n"
" <string>0.1</string>\n"
" <key>CFBundleShortVersionString</key>\n"
" <string>0.1</string>\n"
" <key>CFBundlePackageType</key>\n"
" <string>APPL</string>\n"
" <key>NSHighResolutionCapable</key>\n"
" <true/>\n"
" <key>NSSupportsAutomaticGraphicsSwitching</key>\n"
" <true/>\n"
"</dict>\n"
"</plist>\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 = {};
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 = {};
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 <stdint.h>\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);
build_log(LOG_INFO, "Generated %s (%zu bytes)", header_path, sb.count);
sb_free(&sb);
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";
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_lunasvg_lib(const char *build_dir, bool debug) {
const char *obj_dir = temp_sprintf("%s\\lunasvg_obj", build_dir);
const char *lib_path = debug ? "vendor\\lunasvg\\lunasvg_d.lib" : "vendor\\lunasvg\\lunasvg.lib";
// Check if rebuild is needed
{
const char *all_sources[ARRAY_LEN(plutovg_sources) + ARRAY_LEN(lunasvg_sources)];
size_t n = 0;
for (size_t i = 0; i < ARRAY_LEN(plutovg_sources); i++)
all_sources[n++] = plutovg_sources[i];
for (size_t i = 0; i < ARRAY_LEN(lunasvg_sources); i++)
all_sources[n++] = lunasvg_sources[i];
if (!needs_rebuild(lib_path, all_sources, n)) {
build_log(LOG_INFO, "lunasvg is up to date");
return true;
}
}
if (!mkdir_if_not_exists(obj_dir)) return false;
// Compile plutovg C sources
for (size_t i = 0; i < ARRAY_LEN(plutovg_sources); i++) {
const char *src = plutovg_sources[i];
Cmd cmd = {};
cmd_append(&cmd, "cl.exe", "/nologo", "/c");
cmd_append(&cmd, "/std:c11");
cmd_append(&cmd, "/DPLUTOVG_BUILD", "/DPLUTOVG_BUILD_STATIC");
cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/include");
cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/source");
cmd_append(&cmd, "/W3");
if (debug) {
cmd_append(&cmd, "/MTd", "/Zi", "/Od");
} 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;
}
// Compile lunasvg C++ sources
for (size_t i = 0; i < ARRAY_LEN(lunasvg_sources); i++) {
const char *src = lunasvg_sources[i];
Cmd cmd = {};
cmd_append(&cmd, "cl.exe", "/nologo", "/c");
cmd_append(&cmd, "/std:c++17", "/EHsc");
cmd_append(&cmd, "/DLUNASVG_BUILD", "/DLUNASVG_BUILD_STATIC", "/DPLUTOVG_BUILD_STATIC");
cmd_append(&cmd, "/Ivendor/lunasvg/include");
cmd_append(&cmd, "/Ivendor/lunasvg/source");
cmd_append(&cmd, "/Ivendor/lunasvg/plutovg/include");
cmd_append(&cmd, "/W3");
if (debug) {
cmd_append(&cmd, "/MTd", "/Zi", "/Od");
} 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 into static library
{
Cmd cmd = {};
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 — only the .lib is needed
{
Cmd cmd = {};
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;
}
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 = {};
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, "/W3");
if (debug) {
cmd_append(&cmd, "/MTd", "/Zi", "/Od");
} 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 = {};
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 = {};
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 = {}; cmd_append(&cmd, "cmd.exe", "/c",
"if exist build_debug rmdir /s /q build_debug");
cmd_run(&cmd); }
{ Cmd cmd = {}; cmd_append(&cmd, "cmd.exe", "/c",
"if exist build_release rmdir /s /q build_release");
cmd_run(&cmd); }
remove("vendor\\lunasvg\\lunasvg.lib");
remove("vendor\\lunasvg\\lunasvg_d.lib");
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_lunasvg_lib(build_dir, debug)) return 1;
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_include = temp_sprintf("/I%s/Include", vk_sdk);
const char *vk_lib = temp_sprintf("%s/Lib/vulkan-1.lib", vk_sdk);
// Unity build: single cl.exe invocation compiles main.cpp (which #includes everything including the vulkan renderer)
{
Cmd cmd = {};
cmd_append(&cmd, "cl.exe");
cmd_append(&cmd, "/nologo", "/std:c++20", "/EHsc", "/W3");
cmd_append(&cmd, "/Isrc", "/Ivendor/clay");
cmd_append(&cmd, "/Ivendor/lunasvg/include");
cmd_append(&cmd, "/Ivendor/freetype/include");
cmd_append(&cmd, vk_include);
cmd_append(&cmd, "/DLUNASVG_BUILD_STATIC");
if (debug) {
cmd_append(&cmd, "/MTd", "/Zi", "/Od", "/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/main.cpp");
cmd_append(&cmd, "/link");
cmd_append(&cmd, "/MACHINE:X64");
cmd_append(&cmd, "/SUBSYSTEM:CONSOLE");
cmd_append(&cmd, temp_sprintf("/PDB:%s/autosample.pdb", build_dir));
cmd_append(&cmd, "/DEBUG");
cmd_append(&cmd, debug ? "vendor/lunasvg/lunasvg_d.lib" : "vendor/lunasvg/lunasvg.lib");
cmd_append(&cmd, debug ? "vendor/freetype/freetype_d.lib" : "vendor/freetype/freetype.lib");
cmd_append(&cmd, vk_lib);
for (size_t 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/main.obj", build_dir));
build_log(LOG_INFO, "Build complete: %s/autosample.exe", build_dir);
return 0;
}
#endif

595
build.h
View File

@@ -1,595 +0,0 @@
// build.h — Minimal C-style C++ build system (stb-style single header)
//
// Define BUILD_IMPLEMENTATION in exactly one file before including this header.
//
// Bootstrap (one-time):
// Windows: cl /nologo build.cpp
// macOS: c++ build.cpp -o build
// After that, just run ./build (or build.exe) — it rebuilds itself.
#ifndef BUILD_H
#define BUILD_H
#ifdef _MSC_VER
# define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <errno.h>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <direct.h>
# include <io.h>
#else
# include <sys/stat.h>
# include <sys/wait.h>
# include <unistd.h>
#endif
////////////////////////////////
// Macros
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
// Self-rebuild: call at the top of main(). Recompiles the build script
// if the source file is newer than the running binary, then re-executes.
#define GO_REBUILD_URSELF(argc, argv) \
go_rebuild_urself((argc), (argv), __FILE__)
////////////////////////////////
// Logging
enum Log_Level {
LOG_INFO,
LOG_WARNING,
LOG_ERROR,
};
void build_log(Log_Level level, const char *fmt, ...);
////////////////////////////////
// Temp allocator — ring buffer for short-lived sprintf results
#ifndef TEMP_CAPACITY
#define TEMP_CAPACITY (8 * 1024 * 1024)
#endif
char *temp_sprintf(const char *fmt, ...);
void temp_reset(void);
////////////////////////////////
// String builder — growable byte buffer
struct String_Builder {
char *items = nullptr;
size_t count = 0;
size_t capacity = 0;
};
bool sb_read_file(String_Builder *sb, const char *path);
void sb_free(String_Builder *sb);
////////////////////////////////
// File I/O
bool write_entire_file(const char *path, const void *data, size_t size);
bool delete_file(const char *path);
bool rename_file(const char *old_path, const char *new_path);
bool mkdir_if_not_exists(const char *path);
////////////////////////////////
// Rebuild checking — returns 1 if rebuild needed, 0 if up to date, -1 on error
int needs_rebuild(const char *output, const char **inputs, size_t count);
int needs_rebuild1(const char *output, const char *input);
////////////////////////////////
// Command builder and runner
struct Cmd {
const char **items = nullptr;
size_t count = 0;
size_t capacity = 0;
};
// Appends arguments to a command. Use via the macro which auto-counts args.
void cmd__append(Cmd *cmd, size_t n, ...);
void cmd__append_arr(Cmd *cmd, const char **args, size_t n);
#define cmd_append(cmd, ...) do { \
const char *_cmd_args[] = {__VA_ARGS__}; \
cmd__append_arr((cmd), _cmd_args, sizeof(_cmd_args) / sizeof(_cmd_args[0])); \
} while(0)
// Runs the command synchronously, resets cmd->count to 0, returns success.
bool cmd_run(Cmd *cmd);
// Frees the command's allocated memory.
void cmd_free(Cmd *cmd);
////////////////////////////////
// Self-rebuild
void go_rebuild_urself(int argc, char **argv, const char *source);
#endif // BUILD_H
////////////////////////////////
// Implementation
////////////////////////////////
#ifdef BUILD_IMPLEMENTATION
////////////////////////////////
// Temp allocator
static size_t g_temp_size = 0;
static char g_temp[TEMP_CAPACITY];
void temp_reset(void) {
g_temp_size = 0;
}
static char *temp_alloc(size_t size) {
if (g_temp_size + size > TEMP_CAPACITY) {
g_temp_size = 0; // wrap around
}
char *result = g_temp + g_temp_size;
g_temp_size += size;
return result;
}
char *temp_sprintf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
va_list args2;
va_copy(args2, args);
int n = vsnprintf(nullptr, 0, fmt, args);
va_end(args);
char *result = temp_alloc(n + 1);
vsnprintf(result, n + 1, fmt, args2);
va_end(args2);
return result;
}
////////////////////////////////
// Logging
void build_log(Log_Level level, const char *fmt, ...) {
switch (level) {
case LOG_INFO: fprintf(stderr, "[INFO] "); break;
case LOG_WARNING: fprintf(stderr, "[WARNING] "); break;
case LOG_ERROR: fprintf(stderr, "[ERROR] "); break;
}
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
}
////////////////////////////////
// String builder
static void sb_ensure(String_Builder *sb, size_t needed) {
if (sb->count + needed <= sb->capacity) return;
size_t new_cap = sb->capacity ? sb->capacity * 2 : 256;
while (new_cap < sb->count + needed) new_cap *= 2;
sb->items = (char *)realloc(sb->items, new_cap);
sb->capacity = new_cap;
}
bool sb_read_file(String_Builder *sb, const char *path) {
FILE *f = fopen(path, "rb");
if (!f) {
build_log(LOG_ERROR, "Could not open %s: %s", path, strerror(errno));
return false;
}
fseek(f, 0, SEEK_END);
#ifdef _WIN32
long long m = _telli64(_fileno(f));
#else
long long m = ftell(f);
#endif
if (m < 0) {
build_log(LOG_ERROR, "Could not get size of %s: %s", path, strerror(errno));
fclose(f);
return false;
}
fseek(f, 0, SEEK_SET);
sb_ensure(sb, (size_t)m);
fread(sb->items + sb->count, (size_t)m, 1, f);
if (ferror(f)) {
build_log(LOG_ERROR, "Could not read %s: %s", path, strerror(errno));
fclose(f);
return false;
}
sb->count += (size_t)m;
fclose(f);
return true;
}
void sb_free(String_Builder *sb) {
free(sb->items);
sb->items = nullptr;
sb->count = 0;
sb->capacity = 0;
}
////////////////////////////////
// File I/O
bool write_entire_file(const char *path, const void *data, size_t size) {
FILE *f = fopen(path, "wb");
if (!f) {
build_log(LOG_ERROR, "Could not open %s for writing: %s", path, strerror(errno));
return false;
}
const char *buf = (const char *)data;
while (size > 0) {
size_t n = fwrite(buf, 1, size, f);
if (ferror(f)) {
build_log(LOG_ERROR, "Could not write to %s: %s", path, strerror(errno));
fclose(f);
return false;
}
size -= n;
buf += n;
}
fclose(f);
return true;
}
bool delete_file(const char *path) {
build_log(LOG_INFO, "Deleting %s", path);
#ifdef _WIN32
DWORD attr = GetFileAttributesA(path);
if (attr == INVALID_FILE_ATTRIBUTES) return true; // doesn't exist
if (attr & FILE_ATTRIBUTE_DIRECTORY) {
if (!RemoveDirectoryA(path)) {
build_log(LOG_ERROR, "Could not delete directory %s", path);
return false;
}
} else {
if (!DeleteFileA(path)) {
build_log(LOG_ERROR, "Could not delete file %s", path);
return false;
}
}
#else
if (remove(path) < 0 && errno != ENOENT) {
build_log(LOG_ERROR, "Could not delete %s: %s", path, strerror(errno));
return false;
}
#endif
return true;
}
bool rename_file(const char *old_path, const char *new_path) {
build_log(LOG_INFO, "Renaming %s -> %s", old_path, new_path);
#ifdef _WIN32
if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) {
build_log(LOG_ERROR, "Could not rename %s to %s", old_path, new_path);
return false;
}
#else
if (rename(old_path, new_path) < 0) {
build_log(LOG_ERROR, "Could not rename %s to %s: %s", old_path, new_path, strerror(errno));
return false;
}
#endif
return true;
}
bool mkdir_if_not_exists(const char *path) {
#ifdef _WIN32
int result = _mkdir(path);
#else
int result = mkdir(path, 0755);
#endif
if (result < 0) {
if (errno == EEXIST) return true;
build_log(LOG_ERROR, "Could not create directory %s: %s", path, strerror(errno));
return false;
}
build_log(LOG_INFO, "Created directory %s", path);
return true;
}
////////////////////////////////
// Rebuild checking
int needs_rebuild(const char *output, const char **inputs, size_t count) {
#ifdef _WIN32
HANDLE out_h = CreateFile(output, GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, nullptr);
if (out_h == INVALID_HANDLE_VALUE) {
if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1;
build_log(LOG_ERROR, "Could not open %s", output);
return -1;
}
FILETIME out_time;
if (!GetFileTime(out_h, nullptr, nullptr, &out_time)) {
CloseHandle(out_h);
build_log(LOG_ERROR, "Could not get time of %s", output);
return -1;
}
CloseHandle(out_h);
for (size_t i = 0; i < count; i++) {
HANDLE in_h = CreateFile(inputs[i], GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, nullptr);
if (in_h == INVALID_HANDLE_VALUE) {
build_log(LOG_ERROR, "Could not open %s", inputs[i]);
return -1;
}
FILETIME in_time;
if (!GetFileTime(in_h, nullptr, nullptr, &in_time)) {
CloseHandle(in_h);
build_log(LOG_ERROR, "Could not get time of %s", inputs[i]);
return -1;
}
CloseHandle(in_h);
if (CompareFileTime(&in_time, &out_time) == 1) return 1;
}
return 0;
#else
struct stat sb = {};
if (stat(output, &sb) < 0) {
if (errno == ENOENT) return 1;
build_log(LOG_ERROR, "Could not stat %s: %s", output, strerror(errno));
return -1;
}
time_t out_time = sb.st_mtime;
for (size_t i = 0; i < count; i++) {
if (stat(inputs[i], &sb) < 0) {
build_log(LOG_ERROR, "Could not stat %s: %s", inputs[i], strerror(errno));
return -1;
}
if (sb.st_mtime > out_time) return 1;
}
return 0;
#endif
}
int needs_rebuild1(const char *output, const char *input) {
return needs_rebuild(output, &input, 1);
}
////////////////////////////////
// Command builder and runner
static void cmd__grow(Cmd *cmd) {
size_t new_cap = cmd->capacity ? cmd->capacity * 2 : 32;
cmd->items = (const char **)realloc(cmd->items, new_cap * sizeof(const char *));
cmd->capacity = new_cap;
}
void cmd__append(Cmd *cmd, size_t n, ...) {
va_list args;
va_start(args, n);
for (size_t i = 0; i < n; i++) {
const char *arg = va_arg(args, const char *);
if (cmd->count >= cmd->capacity) cmd__grow(cmd);
cmd->items[cmd->count++] = arg;
}
va_end(args);
}
void cmd__append_arr(Cmd *cmd, const char **args, size_t n) {
for (size_t i = 0; i < n; i++) {
if (cmd->count >= cmd->capacity) cmd__grow(cmd);
cmd->items[cmd->count++] = args[i];
}
}
static void cmd_render(Cmd *cmd, char *buf, size_t buf_size) {
size_t pos = 0;
for (size_t i = 0; i < cmd->count && pos < buf_size - 1; i++) {
if (i > 0 && pos < buf_size - 1) buf[pos++] = ' ';
const char *arg = cmd->items[i];
size_t len = strlen(arg);
if (pos + len < buf_size - 1) {
memcpy(buf + pos, arg, len);
pos += len;
}
}
buf[pos] = '\0';
}
#ifdef _WIN32
// Properly quote a command line for CreateProcess on Windows.
// See: https://learn.microsoft.com/en-gb/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
static void cmd_quote_win32(Cmd *cmd, String_Builder *quoted) {
for (size_t i = 0; i < cmd->count; i++) {
const char *arg = cmd->items[i];
size_t len = strlen(arg);
if (i > 0) {
sb_ensure(quoted, 1);
quoted->items[quoted->count++] = ' ';
}
if (len != 0 && strpbrk(arg, " \t\n\v\"") == nullptr) {
sb_ensure(quoted, len);
memcpy(quoted->items + quoted->count, arg, len);
quoted->count += len;
} else {
sb_ensure(quoted, len * 2 + 3);
quoted->items[quoted->count++] = '"';
size_t backslashes = 0;
for (size_t j = 0; j < len; j++) {
char c = arg[j];
if (c == '\\') {
backslashes++;
} else {
if (c == '"') {
for (size_t k = 0; k < backslashes + 1; k++)
quoted->items[quoted->count++] = '\\';
}
backslashes = 0;
}
quoted->items[quoted->count++] = c;
}
for (size_t k = 0; k < backslashes; k++)
quoted->items[quoted->count++] = '\\';
quoted->items[quoted->count++] = '"';
}
}
sb_ensure(quoted, 1);
quoted->items[quoted->count] = '\0';
}
#endif
bool cmd_run(Cmd *cmd) {
if (cmd->count == 0) {
build_log(LOG_ERROR, "Cannot run empty command");
return false;
}
// Log the command
{
char render_buf[4096];
cmd_render(cmd, render_buf, sizeof(render_buf));
build_log(LOG_INFO, "CMD: %s", render_buf);
}
#ifdef _WIN32
STARTUPINFO si = {};
si.cb = sizeof(si);
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
si.dwFlags |= STARTF_USESTDHANDLES;
PROCESS_INFORMATION pi = {};
String_Builder quoted = {};
cmd_quote_win32(cmd, &quoted);
BOOL ok = CreateProcessA(nullptr, quoted.items, nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi);
sb_free(&quoted);
cmd->count = 0;
if (!ok) {
build_log(LOG_ERROR, "Could not create process for %s", cmd->items[0]);
return false;
}
CloseHandle(pi.hThread);
WaitForSingleObject(pi.hProcess, INFINITE);
DWORD exit_code;
if (!GetExitCodeProcess(pi.hProcess, &exit_code)) {
build_log(LOG_ERROR, "Could not get exit code");
CloseHandle(pi.hProcess);
return false;
}
CloseHandle(pi.hProcess);
if (exit_code != 0) {
build_log(LOG_ERROR, "Command exited with code %lu", exit_code);
return false;
}
return true;
#else
pid_t pid = fork();
if (pid < 0) {
build_log(LOG_ERROR, "Could not fork: %s", strerror(errno));
cmd->count = 0;
return false;
}
if (pid == 0) {
// Child: build null-terminated argv
const char **argv_null = (const char **)malloc((cmd->count + 1) * sizeof(const char *));
memcpy(argv_null, cmd->items, cmd->count * sizeof(const char *));
argv_null[cmd->count] = nullptr;
execvp(argv_null[0], (char *const *)argv_null);
build_log(LOG_ERROR, "Could not exec %s: %s", argv_null[0], strerror(errno));
exit(1);
}
cmd->count = 0;
int wstatus;
for (;;) {
if (waitpid(pid, &wstatus, 0) < 0) {
build_log(LOG_ERROR, "Could not wait on pid %d: %s", pid, strerror(errno));
return false;
}
if (WIFEXITED(wstatus)) {
int code = WEXITSTATUS(wstatus);
if (code != 0) {
build_log(LOG_ERROR, "Command exited with code %d", code);
return false;
}
return true;
}
if (WIFSIGNALED(wstatus)) {
build_log(LOG_ERROR, "Command killed by signal %d", WTERMSIG(wstatus));
return false;
}
}
#endif
}
void cmd_free(Cmd *cmd) {
free(cmd->items);
cmd->items = nullptr;
cmd->count = 0;
cmd->capacity = 0;
}
////////////////////////////////
// Self-rebuild
#ifdef _WIN32
# define REBUILD_CMD(binary, source) "cl.exe", "/nologo", "/std:c++20", temp_sprintf("/Fe:%s", binary), source
#else
# define REBUILD_CMD(binary, source) "c++", "-std=c++20", "-o", binary, source
#endif
void go_rebuild_urself(int argc, char **argv, const char *source) {
const char *binary = argv[0];
#ifdef _WIN32
// Ensure .exe extension for Windows
size_t len = strlen(binary);
if (len < 4 || strcmp(binary + len - 4, ".exe") != 0) {
binary = temp_sprintf("%s.exe", binary);
}
#endif
int rebuild = needs_rebuild1(binary, source);
if (rebuild < 0) exit(1);
if (rebuild == 0) return;
const char *old_binary = temp_sprintf("%s.old", binary);
if (!rename_file(binary, old_binary)) exit(1);
Cmd cmd = {};
cmd_append(&cmd, REBUILD_CMD(binary, source));
if (!cmd_run(&cmd)) {
rename_file(old_binary, binary);
exit(1);
}
// Re-execute with the new binary
cmd_append(&cmd, binary);
for (int i = 1; i < argc; i++) {
cmd__append(&cmd, 1, argv[i]);
}
if (!cmd_run(&cmd)) exit(1);
exit(0);
}
#endif // BUILD_IMPLEMENTATION

3156
vendor/nob/nob.h vendored Normal file

File diff suppressed because it is too large Load Diff