Revert "replace nob with new custom noblike"
This reverts commit 06e1212483.
This commit is contained in:
18
.vscode/tasks.json
vendored
18
.vscode/tasks.json
vendored
@@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -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
773
build.c
Normal 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
726
build.cpp
@@ -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
595
build.h
@@ -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, "ed);
|
|
||||||
|
|
||||||
BOOL ok = CreateProcessA(nullptr, quoted.items, nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi);
|
|
||||||
sb_free("ed);
|
|
||||||
|
|
||||||
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
3156
vendor/nob/nob.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user