diff --git a/build.c b/build.c index ff36de2..f977001 100644 --- a/build.c +++ b/build.c @@ -3,8 +3,8 @@ // macOS: cc build.c -o build // After that, just run: ./build (or build.exe on Windows) -#define NOB_IMPLEMENTATION -#include "vendor/nob/nob.h" +#define BUILD_IMPLEMENTATION +#include "build.h" //////////////////////////////// // FreeType source files @@ -31,18 +31,18 @@ static const char *freetype_sources[] = { // 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); + if (!needs_rebuild1(header_path, ttf_path)) { + build_log(LOG_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; + String_Builder sb = {0}; + if (!sb_read_file(&sb, ttf_path)) 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); + build_log(LOG_ERROR, "Could not open %s for writing", header_path); + sb_free(&sb); return false; } @@ -58,8 +58,8 @@ static bool embed_font_file(const char *ttf_path, const char *header_path) { 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); + sb_free(&sb); + build_log(LOG_INFO, "Generated %s (%zu bytes)", header_path, sb.count); return true; } @@ -78,17 +78,17 @@ static const char *frameworks[] = { }; 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 *obj_dir = 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"); + if (!needs_rebuild(lib_path, freetype_sources, ARRAY_LEN(freetype_sources))) { + build_log(LOG_INFO, "freetype is up to date"); return true; } - if (!nob_mkdir_if_not_exists(obj_dir)) return false; + if (!mkdir_if_not_exists(obj_dir)) return false; - for (size_t i = 0; i < NOB_ARRAY_LEN(freetype_sources); i++) { + 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; @@ -96,30 +96,29 @@ static bool build_freetype_lib(const char *build_dir, bool debug) { 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); + const char *obj_path = 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"); + Cmd cmd = {0}; + cmd_append(&cmd, "clang"); + cmd_append(&cmd, "-std=c11", "-c"); + cmd_append(&cmd, "-DFT2_BUILD_LIBRARY"); + cmd_append(&cmd, "-Ivendor/freetype/include"); + cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter"); if (debug) { - nob_cmd_append(&cmd, "-g", "-O0"); + cmd_append(&cmd, "-g", "-O0"); } else { - nob_cmd_append(&cmd, "-O2"); + 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; + cmd_append(&cmd, "-o", obj_path); + cmd_append(&cmd, src); + if (!cmd_run(&cmd)) 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++) { + Cmd cmd = {0}; + cmd_append(&cmd, "ar", "rcs", lib_path); + for (size_t i = 0; i < ARRAY_LEN(freetype_sources); i++) { const char *src = freetype_sources[i]; const char *base = strrchr(src, '/'); base = base ? base + 1 : src; @@ -127,26 +126,24 @@ static bool build_freetype_lib(const char *build_dir, bool debug) { 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)); + cmd__append(&cmd, 1, temp_sprintf("%s/%s", obj_dir, obj_name)); } - Nob_Cmd_Opt copt = {0}; - if (!nob_cmd_run_opt(&cmd, copt)) return false; + if (!cmd_run(&cmd)) 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); + Cmd cmd = {0}; + cmd_append(&cmd, "rm", "-rf", obj_dir); + cmd_run(&cmd); } - nob_log(NOB_INFO, "Built %s", lib_path); + build_log(LOG_INFO, "Built %s", lib_path); return true; } int main(int argc, char **argv) { - NOB_GO_REBUILD_URSELF(argc, argv); + GO_REBUILD_URSELF(argc, argv); bool debug = false; bool clean = false; @@ -154,8 +151,8 @@ int main(int argc, char **argv) { 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]); + build_log(LOG_ERROR, "unknown argument: %s", argv[i]); + build_log(LOG_ERROR, "usage: %s [debug] [clean]", argv[0]); return 1; } } @@ -163,28 +160,26 @@ int main(int argc, char **argv) { 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); } + build_log(LOG_INFO, "Cleaning build artifacts"); + { Cmd cmd = {0}; cmd_append(&cmd, "rm", "-rf", "build_debug"); cmd_run(&cmd); } + { Cmd cmd = {0}; cmd_append(&cmd, "rm", "-rf", "build_release"); cmd_run(&cmd); } remove("vendor/freetype/libfreetype.a"); remove("src/renderer/font_inter.gen.h"); return 0; } // App bundle paths - const char *app_dir = 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); + 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 (!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; + if (!mkdir_if_not_exists(build_dir)) return 1; + if (!mkdir_if_not_exists(app_dir)) return 1; + if (!mkdir_if_not_exists(contents)) return 1; + if (!mkdir_if_not_exists(macos_dir)) return 1; + if (!mkdir_if_not_exists(res_dir)) return 1; // Build static libraries if (!build_freetype_lib(build_dir, debug)) return 1; @@ -195,41 +190,41 @@ int main(int argc, char **argv) { // Unity build: single clang invocation compiles main.c (which #includes everything) { - Nob_Cmd cmd = {0}; - nob_cmd_append(&cmd, "clang"); - nob_cmd_append(&cmd, "-std=c11", "-x", "objective-c"); - 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/nanosvg"); - nob_cmd_append(&cmd, "-Ivendor/freetype/include"); + Cmd cmd = {0}; + cmd_append(&cmd, "clang"); + cmd_append(&cmd, "-std=c11", "-x", "objective-c"); + cmd_append(&cmd, "-Wall", "-Wextra", "-Wno-missing-field-initializers"); + cmd_append(&cmd, "-Wno-deprecated-declarations"); + cmd_append(&cmd, "-Isrc", "-Ivendor/clay"); + cmd_append(&cmd, "-Ivendor/nanosvg"); + cmd_append(&cmd, "-Ivendor/freetype/include"); if (debug) { - nob_cmd_append(&cmd, "-g", "-O0", "-D_DEBUG"); + cmd_append(&cmd, "-g", "-O0", "-D_DEBUG"); } else { - nob_cmd_append(&cmd, "-O2", "-DNDEBUG"); + cmd_append(&cmd, "-O2", "-DNDEBUG"); } - nob_cmd_append(&cmd, "-o", binary_path); - nob_cmd_append(&cmd, "src/main.c"); + cmd_append(&cmd, "-o", binary_path); + cmd_append(&cmd, "src/main.c"); // Reset language mode so .a is treated as a library, not source - nob_cmd_append(&cmd, "-x", "none"); - nob_cmd_append(&cmd, "vendor/freetype/libfreetype.a"); + cmd_append(&cmd, "-x", "none"); + 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]); + for (i = 0; i < ARRAY_LEN(frameworks); i++) + cmd__append(&cmd, 1, frameworks[i]); } - nob_cmd_append(&cmd, "-lm"); - { Nob_Cmd_Opt opt = {0}; if (!nob_cmd_run_opt(&cmd, opt)) return 1; } + cmd_append(&cmd, "-lm"); + if (!cmd_run(&cmd)) return 1; } // Write Info.plist { - const char *plist_path = nob_temp_sprintf("%s/Contents/Info.plist", app_dir); - nob_write_entire_file(plist_path, + const char *plist_path = temp_sprintf("%s/Contents/Info.plist", app_dir); + const char *plist = "\n" "\n" @@ -252,35 +247,11 @@ int main(int argc, char **argv) { " NSSupportsAutomaticGraphicsSwitching\n" " \n" "\n" - "\n", - strlen( - "\n" - "\n" - "\n" - "\n" - " CFBundleExecutable\n" - " autosample\n" - " CFBundleIdentifier\n" - " com.autosample.app\n" - " CFBundleName\n" - " autosample\n" - " CFBundleVersion\n" - " 0.1\n" - " CFBundleShortVersionString\n" - " 0.1\n" - " CFBundlePackageType\n" - " APPL\n" - " NSHighResolutionCapable\n" - " \n" - " NSSupportsAutomaticGraphicsSwitching\n" - " \n" - "\n" - "\n" - )); + "\n"; + write_entire_file(plist_path, plist, strlen(plist)); } - nob_log(NOB_INFO, "Build complete: %s", app_dir); + build_log(LOG_INFO, "Build complete: %s", app_dir); return 0; } @@ -309,20 +280,19 @@ static const char *link_libs[] = { // 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); + Cmd cmd = {0}; + cmd_append(&cmd, glslc_path, temp_sprintf("-fshader-stage=%s", stage), "-o", spv_path, src); + return cmd_run(&cmd); } static bool embed_spirv(const char *spv_path, const char *header_path, const char *array_name) { - Nob_String_Builder sb = {0}; - if (!nob_read_entire_file(spv_path, &sb)) return false; + String_Builder sb = {0}; + if (!sb_read_file(&sb, spv_path)) 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); + build_log(LOG_ERROR, "Could not open %s for writing", header_path); + sb_free(&sb); return false; } @@ -341,96 +311,93 @@ static bool embed_spirv(const char *spv_path, const char *header_path, const cha fprintf(out, "};\n"); fclose(out); - nob_sb_free(sb); - nob_log(NOB_INFO, "Generated %s (%zu bytes)", header_path, sb.count); + sb_free(&sb); + build_log(LOG_INFO, "Generated %s (%zu bytes)", header_path, sb.count); return true; } static bool compile_and_embed_shaders(const char *build_dir) { const char *vk_sdk = get_vulkan_sdk_path(); - const char *glslc = nob_temp_sprintf("%s\\Bin\\glslc.exe", vk_sdk); + 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 = 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_spv = temp_sprintf("%s/ui_vert.spv", build_dir); + const char *frag_spv = temp_sprintf("%s/ui_frag.spv", build_dir); const char *vert_hdr = "src/renderer/ui_vert.spv.h"; const char *frag_hdr = "src/renderer/ui_frag.spv.h"; // Check if rebuild needed - if (nob_needs_rebuild1(vert_hdr, vert_src)) { - nob_log(NOB_INFO, "Compiling vertex shader"); + 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 { - nob_log(NOB_INFO, "Vertex shader is up to date"); + build_log(LOG_INFO, "Vertex shader is up to date"); } - if (nob_needs_rebuild1(frag_hdr, frag_src)) { - nob_log(NOB_INFO, "Compiling fragment shader"); + 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 { - nob_log(NOB_INFO, "Fragment shader is up to date"); + build_log(LOG_INFO, "Fragment shader is up to date"); } return true; } static bool build_freetype_lib(const char *build_dir, bool debug) { - const char *obj_dir = nob_temp_sprintf("%s\\freetype_obj", build_dir); + 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 (!nob_needs_rebuild(lib_path, freetype_sources, NOB_ARRAY_LEN(freetype_sources))) { - nob_log(NOB_INFO, "freetype is up to date"); + if (!needs_rebuild(lib_path, freetype_sources, ARRAY_LEN(freetype_sources))) { + build_log(LOG_INFO, "freetype is up to date"); return true; } - if (!nob_mkdir_if_not_exists(obj_dir)) return false; + if (!mkdir_if_not_exists(obj_dir)) return false; - for (size_t i = 0; i < NOB_ARRAY_LEN(freetype_sources); i++) { + for (size_t i = 0; i < 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"); + Cmd cmd = {0}; + cmd_append(&cmd, "cl.exe", "/nologo", "/c"); + cmd_append(&cmd, "/std:c11"); + cmd_append(&cmd, "/DFT2_BUILD_LIBRARY"); + cmd_append(&cmd, "/Ivendor/freetype/include"); + cmd_append(&cmd, "/W3"); if (debug) { - nob_cmd_append(&cmd, "/MTd", "/Zi"); + cmd_append(&cmd, "/MTd", "/Zi"); } else { - nob_cmd_append(&cmd, "/MT", "/O2"); + 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; + cmd_append(&cmd, temp_sprintf("/Fo:%s/", obj_dir)); + cmd_append(&cmd, src); + if (!cmd_run(&cmd)) 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; + Cmd cmd = {0}; + cmd_append(&cmd, "lib.exe", "/nologo", temp_sprintf("/OUT:%s", lib_path)); + cmd_append(&cmd, temp_sprintf("%s/*.obj", obj_dir)); + if (!cmd_run(&cmd)) return false; } // Clean up obj dir { - 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); + Cmd cmd = {0}; + cmd_append(&cmd, "cmd.exe", "/c", + temp_sprintf("if exist %s rmdir /s /q %s", obj_dir, obj_dir)); + cmd_run(&cmd); } - nob_log(NOB_INFO, "Built %s", lib_path); + build_log(LOG_INFO, "Built %s", lib_path); return true; } int main(int argc, char **argv) { - NOB_GO_REBUILD_URSELF(argc, argv); + GO_REBUILD_URSELF(argc, argv); bool debug = false; bool clean = false; @@ -438,8 +405,8 @@ int main(int argc, char **argv) { 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]); + build_log(LOG_ERROR, "unknown argument: %s", argv[i]); + build_log(LOG_ERROR, "usage: %s [debug] [clean]", argv[0]); return 1; } } @@ -447,13 +414,13 @@ int main(int argc, char **argv) { 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", + build_log(LOG_INFO, "Cleaning build artifacts"); + { Cmd cmd = {0}; cmd_append(&cmd, "cmd.exe", "/c", "if exist build_debug rmdir /s /q build_debug"); - Nob_Cmd_Opt opt = {0}; nob_cmd_run_opt(&cmd, opt); } - { Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cmd.exe", "/c", + cmd_run(&cmd); } + { Cmd cmd = {0}; 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); } + cmd_run(&cmd); } remove("vendor\\freetype\\freetype.lib"); remove("vendor\\freetype\\freetype_d.lib"); remove("src\\renderer\\font_inter.gen.h"); @@ -462,7 +429,7 @@ int main(int argc, char **argv) { return 0; } - if (!nob_mkdir_if_not_exists(build_dir)) return 1; + if (!mkdir_if_not_exists(build_dir)) return 1; // Build static libraries if (!build_freetype_lib(build_dir, debug)) return 1; @@ -475,50 +442,50 @@ int main(int argc, char **argv) { 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); + 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.c (which #includes everything) { - Nob_Cmd cmd = {0}; - nob_cmd_append(&cmd, "cl.exe"); - nob_cmd_append(&cmd, "/nologo", "/std:c11", "/TC", "/W3"); - nob_cmd_append(&cmd, "/Isrc", "/Ivendor/clay"); - nob_cmd_append(&cmd, "/Ivendor/nanosvg"); - nob_cmd_append(&cmd, "/Ivendor/freetype/include"); - nob_cmd_append(&cmd, vk_include); + Cmd cmd = {0}; + cmd_append(&cmd, "cl.exe"); + cmd_append(&cmd, "/nologo", "/std:c11", "/TC", "/W3"); + cmd_append(&cmd, "/Isrc", "/Ivendor/clay"); + cmd_append(&cmd, "/Ivendor/nanosvg"); + cmd_append(&cmd, "/Ivendor/freetype/include"); + cmd_append(&cmd, vk_include); if (debug) { - nob_cmd_append(&cmd, "/MTd", "/Zi", "/D_DEBUG"); + cmd_append(&cmd, "/MTd", "/Zi", "/D_DEBUG"); } else { - nob_cmd_append(&cmd, "/MT", "/Zi", "/O2", "/DNDEBUG"); + 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)); + 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)); - nob_cmd_append(&cmd, "src/main.c"); + cmd_append(&cmd, "src/main.c"); - 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/freetype/freetype_d.lib" : "vendor/freetype/freetype.lib"); - nob_cmd_append(&cmd, vk_lib); + 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/freetype/freetype_d.lib" : "vendor/freetype/freetype.lib"); + 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]); + for (i = 0; i < ARRAY_LEN(link_libs); i++) + cmd__append(&cmd, 1, link_libs[i]); } - { Nob_Cmd_Opt opt = {0}; if (!nob_cmd_run_opt(&cmd, opt)) return 1; } + if (!cmd_run(&cmd)) return 1; } // Clean up obj files - nob_delete_file(nob_temp_sprintf("%s/main.obj", build_dir)); + delete_file(temp_sprintf("%s/main.obj", build_dir)); - nob_log(NOB_INFO, "Build complete: %s/autosample.exe", build_dir); + build_log(LOG_INFO, "Build complete: %s/autosample.exe", build_dir); return 0; } diff --git a/build.h b/build.h new file mode 100644 index 0000000..2ad0452 --- /dev/null +++ b/build.h @@ -0,0 +1,600 @@ +// build.h — Minimal 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.c +// macOS: cc build.c -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 +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include +# include +# include +#else +# include +# include +# include +#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 + +typedef enum { + LOG_INFO, + LOG_WARNING, + LOG_ERROR, +} Log_Level; + +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 + +typedef struct { + char *items; + size_t count; + size_t capacity; +} String_Builder; + +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 + +typedef struct { + const char **items; + size_t count; + size_t capacity; +} Cmd; + +// 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(NULL, 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 = NULL; + 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, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); + 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, NULL, NULL, &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, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); + 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, NULL, NULL, &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; + memset(&sb, 0, sizeof(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. +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\"") == NULL) { + 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; + memset(&si, 0, sizeof(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; + memset(&pi, 0, sizeof(pi)); + + String_Builder quoted = {0}; + cmd_quote_win32(cmd, "ed); + + BOOL ok = CreateProcessA(NULL, quoted.items, NULL, NULL, TRUE, 0, NULL, NULL, &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] = NULL; + 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 = NULL; + cmd->count = 0; + cmd->capacity = 0; +} + +//////////////////////////////// +// Self-rebuild + +#ifdef _WIN32 +# define REBUILD_CMD(binary, source) "cl.exe", "/nologo", temp_sprintf("/Fe:%s", binary), source +#else +# define REBUILD_CMD(binary, source) "cc", "-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 = {0}; + 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); + { + int i; + for (i = 1; i < argc; i++) { + cmd__append(&cmd, 1, argv[i]); + } + } + if (!cmd_run(&cmd)) exit(1); + exit(0); +} + +#endif // BUILD_IMPLEMENTATION diff --git a/vendor/nob/nob.h b/vendor/nob/nob.h deleted file mode 100644 index 2bdb18e..0000000 --- a/vendor/nob/nob.h +++ /dev/null @@ -1,3156 +0,0 @@ -/* nob - v3.2.2 - Public Domain - https://github.com/tsoding/nob.h - - This library is the next generation of the [NoBuild](https://github.com/tsoding/nobuild) idea. - - # Quick Example - - ```c - // nob.c - #define NOB_IMPLEMENTATION - #include "nob.h" - - int main(int argc, char **argv) - { - NOB_GO_REBUILD_URSELF(argc, argv); - Nob_Cmd cmd = {0}; - nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c"); - if (!nob_cmd_run(&cmd)) return 1; - return 0; - } - ``` - - ```console - $ cc -o nob nob.c - $ ./nob - ``` - - The `nob` automatically rebuilds itself if `nob.c` is modified thanks to - the `NOB_GO_REBUILD_URSELF` macro (don't forget to check out how it works below) - - # Stripping off `nob_` Prefixes - - Since Pure C does not have any namespaces we prefix each name of the API with the `nob_` to avoid any - potential conflicts with any other names in your code. But sometimes it is very annoying and makes - the code noisy. Because of that you can drop the `nob_` prefix. - - ```c - // nob.c - #define NOB_IMPLEMENTATION - #include "nob.h" - - int main(int argc, char **argv) - { - GO_REBUILD_URSELF(argc, argv); - Cmd cmd = {0}; - cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c"); - if (!cmd_run(&cmd)) return 1; - return 0; - } - ``` - - If the lack of prefixes causes any problems you can disable the prefix stripping by defining - `NOB_UNSTRIP_PREFIX` feature macro before including "nob.h". - - Not all the names have strippable prefixes. All the redefinable names like `NOB_REBUILD_URSELF` - for instance will retain their prefix always. Notable exception is the nob_log() function. Stripping - away the prefix results in log() which was historically always referring to the natural logarithmic - function that is already defined in math.h. So there is no reason to strip off the prefix for nob_log(). - Another exception is nob_rename() which collides with the widely known POSIX function rename(2) if you - strip the prefix off. - - The prefixes are stripped off only on the level of the preprocessor. The names of the functions in the - compiled object file will still retain the `nob_` prefix. Keep that in mind when you FFI with nob.h - from other languages (for whatever reason). - - If only few specific names create conflicts for you, you can just #undef those names after the - `#include ` without enabling `NOB_UNSTRIP_PREFIX` since they are macros anyway. - - # Macro Interface - - All these macros are `#define`d by the user before including nob.h - - ## Flags - - Enable or disable certain aspects of nob.h - - - NOB_IMPLEMENTATION - Enable definitions of the functions. By default only declarations are included. - See https://github.com/nothings/stb/blob/f58f558c120e9b32c217290b80bad1a0729fbb2c/docs/stb_howto.txt - for more info. - - NOB_WARN_DEPRECATED - Warn about the usage of deprecated function. We rarely actually remove deprecated functions, - but if you want to know what is discouraged you may want to enable this flag. - - NOB_EXPERIMENTAL_DELETE_OLD - Experimental feature that automatically removes `nob.old` files. It's unclear how well - it works on Windows, so it's experimental for now. - - NOB_UNSTRIP_PREFIX - do not strip the `nob_` prefixes from non-redefinable names. - - NOB_NO_ECHO - do not echo the actions various nob functions are doing (like nob_cmd_run(), nob_mkdir_if_not_exists(), etc). - - ## Redefinable Macros - - Redefine default behaviors of nob.h. - - - NOBDEF - Appends additional things to function declarations. You can do something like `#define NOBDEF static inline`. - - NOB_ASSERT(condition) - Redefine which assert() nob.h shall use. - - NOB_REALLOC(oldptr, size) - Redefine which realloc() nob.h shall use. - - NOB_FREE(ptr) - Redefine which free() nob.h shall use. - - NOB_DEPRECATED(message) - Redefine how nob.h shall mark functions as deprecated. - - NOB_DA_INIT_CAP - Redefine initial capacity of Dynamic Arrays. - - NOB_TEMP_CAPACITY - Redefine the capacity of the temporary storate. - - NOB_REBUILD_URSELF(binary_path, source_path) - redefine how nob.h shall rebuild itself. - - NOB_WIN32_ERR_MSG_SIZE - Redefine the capacity of the buffer for error message on Windows. -*/ - -#ifndef NOB_H_ -#define NOB_H_ -#ifdef _WIN32 -# ifndef _CRT_SECURE_NO_WARNINGS -# define _CRT_SECURE_NO_WARNINGS (1) -# endif // _CRT_SECURE_NO_WARNINGS -#endif // _WIN32 - -#ifndef NOBDEF -/* - Goes before declarations and definitions of the nob functions. Useful to `#define NOBDEF static inline` - if your source code is a single file and you want the compiler to remove unused functions. -*/ -#define NOBDEF -#endif /* NOBDEF */ - -#ifndef NOB_ASSERT -#include -#define NOB_ASSERT assert -#endif /* NOB_ASSERT */ - -#ifndef NOB_REALLOC -#include -#define NOB_REALLOC realloc -#endif /* NOB_REALLOC */ - -#ifndef NOB_FREE -#include -#define NOB_FREE free -#endif /* NOB_FREE */ - -#ifdef NOB_WARN_DEPRECATED -# ifndef NOB_DEPRECATED -# if defined(__GNUC__) || defined(__clang__) -# define NOB_DEPRECATED(message) __attribute__((deprecated(message))) -# elif defined(_MSC_VER) -# define NOB_DEPRECATED(message) __declspec(deprecated(message)) -# else -# define NOB_DEPRECATED(...) -# endif -# endif /* NOB_DEPRECATED */ -#else -# define NOB_DEPRECATED(...) -#endif /* NOB_WARN_DEPRECATED */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 -# define WIN32_LEAN_AND_MEAN -# define _WINUSER_ -# define _WINGDI_ -# define _IMM_ -# define _WINCON_ -# include -# include -# include -# include -#else -# ifdef __APPLE__ -# include -# endif -# ifdef __FreeBSD__ -# include -# endif -# include -# include -# include -# include -# include -# include -#endif - -#ifdef __HAIKU__ -# include -#endif - -#ifdef _WIN32 -# define NOB_LINE_END "\r\n" -#else -# define NOB_LINE_END "\n" -#endif - -#if defined(__GNUC__) || defined(__clang__) -// https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html -# ifdef __MINGW_PRINTF_FORMAT -# define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) __attribute__ ((format (__MINGW_PRINTF_FORMAT, STRING_INDEX, FIRST_TO_CHECK))) -# else -# define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) __attribute__ ((format (printf, STRING_INDEX, FIRST_TO_CHECK))) -# endif // __MINGW_PRINTF_FORMAT -#else -// TODO: implement NOB_PRINTF_FORMAT for MSVC -# define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) -#endif - -#define NOB_UNUSED(value) (void)(value) -#define NOB_TODO(message) do { fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); abort(); } while(0) -#define NOB_UNREACHABLE(message) do { fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); abort(); } while(0) - -#define NOB_ARRAY_LEN(array) (sizeof(array)/sizeof(array[0])) -#define NOB_ARRAY_GET(array, index) \ - (NOB_ASSERT((size_t)index < NOB_ARRAY_LEN(array)), array[(size_t)index]) - -typedef enum { - NOB_INFO, - NOB_WARNING, - NOB_ERROR, - NOB_NO_LOGS, -} Nob_Log_Level; - -// Any messages with the level below nob_minimal_log_level are going to be suppressed. -extern Nob_Log_Level nob_minimal_log_level; - -typedef void (nob_log_handler)(Nob_Log_Level level, const char *fmt, va_list args); - -NOBDEF void nob_set_log_handler(nob_log_handler *handler); -NOBDEF nob_log_handler *nob_get_log_handler(void); - -NOBDEF nob_log_handler nob_default_log_handler; -NOBDEF nob_log_handler nob_cancer_log_handler; - -NOBDEF void nob_log(Nob_Log_Level level, const char *fmt, ...) NOB_PRINTF_FORMAT(2, 3); - -// It is an equivalent of shift command from bash (do `help shift` in bash). It basically -// pops an element from the beginning of a sized array. -#define nob_shift(xs, xs_sz) (NOB_ASSERT((xs_sz) > 0), (xs_sz)--, *(xs)++) -// NOTE: nob_shift_args() is an alias for an old variant of nob_shift that only worked with -// the command line arguments passed to the main() function. nob_shift() is more generic. -// So nob_shift_args() is semi-deprecated, but I don't see much reason to urgently -// remove it. This alias does not hurt anybody. -#define nob_shift_args(argc, argv) nob_shift(*argv, *argc) - -typedef struct { - const char **items; - size_t count; - size_t capacity; -} Nob_File_Paths; - -typedef enum { - NOB_FILE_REGULAR = 0, - NOB_FILE_DIRECTORY, - NOB_FILE_SYMLINK, - NOB_FILE_OTHER, -} Nob_File_Type; - -NOBDEF bool nob_mkdir_if_not_exists(const char *path); -NOBDEF bool nob_copy_file(const char *src_path, const char *dst_path); -NOBDEF bool nob_copy_directory_recursively(const char *src_path, const char *dst_path); -NOBDEF bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children); -NOBDEF bool nob_write_entire_file(const char *path, const void *data, size_t size); -NOBDEF Nob_File_Type nob_get_file_type(const char *path); -NOBDEF bool nob_delete_file(const char *path); - -typedef enum { - // If the current file is a directory go inside of it. - NOB_WALK_CONT, - // If the current file is a directory do not go inside of it. - NOB_WALK_SKIP, - // Stop the recursive traversal process entirely. - NOB_WALK_STOP, -} Nob_Walk_Action; - -typedef struct { - // The path to the visited file - const char *path; - // The type of the visited file - Nob_File_Type type; - // How nested we currently are in the directory tree - size_t level; - // User data supplied in Nob_Walk_Dir_Opt.data - void *data; - // The action nob_walk_dir_opt() must perform after the Nob_Walk_Func has returned. - // Default is NOB_WALK_CONT. - Nob_Walk_Action *action; -} Nob_Walk_Entry; - -// A function that is called by nob_walk_dir_opt() on each visited file. -// Nob_Walk_Entry provides the details about the visited file and also -// expects you to modify the `action` in case you want to alter the -// usual behavior of the recursive walking algorithm. -// -// If the function returns `false`, an error is assumed which causes the entire -// recursive walking process to exit and nob_walk_dir_opt() return `false`. -typedef bool (*Nob_Walk_Func)(Nob_Walk_Entry entry); - -typedef struct { - // User data passed to Nob_Walk_Entry.data - void *data; - // Walk the directory in post-order visiting the leaf files first. - bool post_order; -} Nob_Walk_Dir_Opt; - -NOBDEF bool nob_walk_dir_opt(const char *root, Nob_Walk_Func func, Nob_Walk_Dir_Opt); - -#define nob_walk_dir(root, func, ...) nob_walk_dir_opt((root), (func), (Nob_Walk_Dir_Opt){__VA_ARGS__}) - -typedef struct { - char *name; - bool error; - - struct { -#ifdef _WIN32 - WIN32_FIND_DATA win32_data; - HANDLE win32_hFind; - bool win32_init; -#else - DIR *posix_dir; - struct dirent *posix_ent; -#endif // _WIN32 - } nob__private; // TODO: we don't have solid conventions regarding private struct fields -} Nob_Dir_Entry; - -// nob_dir_entry_open() - open the directory entry for iteration. -// RETURN: -// true - Sucess. -// false - Error. I will be logged automatically with nob_log(). -NOBDEF bool nob_dir_entry_open(const char *dir_path, Nob_Dir_Entry *dir); -// nob_dir_entry_next() - acquire the next file in the directory. -// RETURN: -// true - Successfully acquired the next file. -// false - Either failure or no more files to iterate. In case of failure dir->error is set to true. -NOBDEF bool nob_dir_entry_next(Nob_Dir_Entry *dir); -NOBDEF void nob_dir_entry_close(Nob_Dir_Entry dir); - -#define nob_return_defer(value) do { result = (value); goto defer; } while(0) - -// Initial capacity of a dynamic array -#ifndef NOB_DA_INIT_CAP -#define NOB_DA_INIT_CAP 256 -#endif - -#ifdef __cplusplus -#define NOB_DECLTYPE_CAST(T) (decltype(T)) -#else -#define NOB_DECLTYPE_CAST(T) -#endif // __cplusplus - -#define nob_da_reserve(da, expected_capacity) \ - do { \ - if ((expected_capacity) > (da)->capacity) { \ - if ((da)->capacity == 0) { \ - (da)->capacity = NOB_DA_INIT_CAP; \ - } \ - while ((expected_capacity) > (da)->capacity) { \ - (da)->capacity *= 2; \ - } \ - (da)->items = NOB_DECLTYPE_CAST((da)->items)NOB_REALLOC((da)->items, (da)->capacity * sizeof(*(da)->items)); \ - NOB_ASSERT((da)->items != NULL && "Buy more RAM lol"); \ - } \ - } while (0) - -// Append an item to a dynamic array -#define nob_da_append(da, item) \ - do { \ - nob_da_reserve((da), (da)->count + 1); \ - (da)->items[(da)->count++] = (item); \ - } while (0) - -#define nob_da_free(da) NOB_FREE((da).items) - -// Append several items to a dynamic array -#define nob_da_append_many(da, new_items, new_items_count) \ - do { \ - nob_da_reserve((da), (da)->count + (new_items_count)); \ - memcpy((da)->items + (da)->count, (new_items), (new_items_count)*sizeof(*(da)->items)); \ - (da)->count += (new_items_count); \ - } while (0) - -#define nob_da_resize(da, new_size) \ - do { \ - nob_da_reserve((da), new_size); \ - (da)->count = (new_size); \ - } while (0) - -#define nob_da_last(da) (da)->items[(NOB_ASSERT((da)->count > 0), (da)->count-1)] -#define nob_da_remove_unordered(da, i) \ - do { \ - size_t j = (i); \ - NOB_ASSERT(j < (da)->count); \ - (da)->items[j] = (da)->items[--(da)->count]; \ - } while(0) - -// Foreach over Dynamic Arrays. Example: -// ```c -// typedef struct { -// int *items; -// size_t count; -// size_t capacity; -// } Numbers; -// -// Numbers xs = {0}; -// -// nob_da_append(&xs, 69); -// nob_da_append(&xs, 420); -// nob_da_append(&xs, 1337); -// -// nob_da_foreach(int, x, &xs) { -// // `x` here is a pointer to the current element. You can get its index by taking a difference -// // between `x` and the start of the array which is `x.items`. -// size_t index = x - xs.items; -// nob_log(INFO, "%zu: %d", index, *x); -// } -// ``` -#define nob_da_foreach(Type, it, da) for (Type *it = (da)->items; it < (da)->items + (da)->count; ++it) - -// The Fixed Array append. `items` fields must be a fixed size array. Its size determines the capacity. -#define nob_fa_append(fa, item) \ - (NOB_ASSERT((fa)->count < NOB_ARRAY_LEN((fa)->items)), \ - (fa)->items[(fa)->count++] = (item)) - -typedef struct { - char *items; - size_t count; - size_t capacity; -} Nob_String_Builder; - -#define nob_swap(T, a, b) do { T t = a; a = b; b = t; } while (0) - -NOBDEF bool nob_read_entire_file(const char *path, Nob_String_Builder *sb); -NOBDEF int nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...) NOB_PRINTF_FORMAT(2, 3); -// Pads the String_Builder (sb) to the desired word size boundary with 0s. -// Imagine we have sb that contains 5 `a`-s: -// -// aaaa|a -// -// If we pad align it by size 4 it will look like this: -// -// aaaa|a000| <- padded with 0s to the next size 4 boundary -// -// Useful when you are building some sort of binary format using String_Builder. -NOBDEF void nob_sb_pad_align(Nob_String_Builder *sb, size_t size); - -// Append a sized buffer to a string builder -#define nob_sb_append_buf(sb, buf, size) nob_da_append_many(sb, buf, size) - -// Append a NULL-terminated string to a string builder -#define nob_sb_append_cstr(sb, cstr) \ - do { \ - const char *s = (cstr); \ - size_t n = strlen(s); \ - nob_da_append_many(sb, s, n); \ - } while (0) - -// Append a single NULL character at the end of a string builder. So then you can -// use it a NULL-terminated C string -#define nob_sb_append_null(sb) nob_da_append_many(sb, "", 1) - -#define nob_sb_append nob_da_append - -// Free the memory allocated by a string builder -#define nob_sb_free(sb) NOB_FREE((sb).items) - -// Process handle -#ifdef _WIN32 -typedef HANDLE Nob_Proc; -#define NOB_INVALID_PROC INVALID_HANDLE_VALUE -typedef HANDLE Nob_Fd; -#define NOB_INVALID_FD INVALID_HANDLE_VALUE -#else -typedef int Nob_Proc; -#define NOB_INVALID_PROC (-1) -typedef int Nob_Fd; -#define NOB_INVALID_FD (-1) -#endif // _WIN32 - -NOBDEF Nob_Fd nob_fd_open_for_read(const char *path); -NOBDEF Nob_Fd nob_fd_open_for_write(const char *path); -NOBDEF void nob_fd_close(Nob_Fd fd); - -typedef struct { - Nob_Fd read; - Nob_Fd write; -} Nob_Pipe; - -NOBDEF bool nob_pipe_create(Nob_Pipe *pp); - -typedef struct { - Nob_Proc *items; - size_t count; - size_t capacity; -} Nob_Procs; - -// Wait until the process has finished -NOBDEF bool nob_proc_wait(Nob_Proc proc); - -// Wait until all the processes have finished -NOBDEF bool nob_procs_wait(Nob_Procs procs); - -// Wait until all the processes have finished and empty the procs array. -NOBDEF bool nob_procs_flush(Nob_Procs *procs); - -// Alias to nob_procs_flush -NOB_DEPRECATED("Use `nob_procs_flush(&procs)` instead.") -NOBDEF bool nob_procs_wait_and_reset(Nob_Procs *procs); - -// Append a new process to procs array and if procs.count reaches max_procs_count call nob_procs_wait_and_reset() on it -NOB_DEPRECATED("Use `nob_cmd_run(&cmd, .async = &procs, .max_procs = )` instead") -NOBDEF bool nob_procs_append_with_flush(Nob_Procs *procs, Nob_Proc proc, size_t max_procs_count); - -// A command - the main workhorse of Nob. Nob is all about building commands and running them -typedef struct { - const char **items; - size_t count; - size_t capacity; -} Nob_Cmd; - -// Options for nob_cmd_run_opt() function. -typedef struct { - // Run the command asynchronously appending its Nob_Proc to the provided Nob_Procs array - Nob_Procs *async; - // Maximum processes allowed in the .async list. Zero implies nob_nprocs(). - size_t max_procs; - // Do not reset the command after execution. - bool dont_reset; - // Redirect stdin to file - const char *stdin_path; - // Redirect stdout to file - const char *stdout_path; - // Redirect stderr to file - const char *stderr_path; -} Nob_Cmd_Opt; - -// Run the command with options. -NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt); - -// Command Chains (in Shell Scripting they are know as Pipes) -// -// Usage: -// ```c -// Nob_Cmd cmd = {0}; -// Nob_Chain chain = {0}; -// if (!nob_chain_begin(&chain)) return 1; -// { -// nob_cmd_append(&cmd, "echo", "Hello, World"); -// if (!nob_chain_cmd(&chain, &cmd)) return 1; -// -// nob_cmd_append(&cmd, "rev"); -// if (!nob_chain_cmd(&chain, &cmd)) return 1; -// -// nob_cmd_append(&cmd, "xxd"); -// if (!nob_chain_cmd(&chain, &cmd)) return 1; -// } -// if (!nob_chain_end(&chain)) return 1; -// ``` -// -// The above is equivalent to a shell command: -// -// ```sh -// echo "Hello, World" | rev | xxd -// ``` -// -// After nob_chain_end() the Nob_Chain struct can be reused again. -// -// The fields of the Nob_Chain struct contain the intermediate state of the Command -// Chain that is being built with the nob_chain_cmd() calls and generally have no -// particular use for the user. -// -// The only memory dynamically allocated within Nob_Chain belongs to the .cmd field. -// So if you want to clean it all up you can just do free(chain.cmd.items). -typedef struct { - // The file descriptor of the output of the previous command. Will be used as the input for the next command. - Nob_Fd fdin; - // The command from the last nob_chain_cmd() call. - Nob_Cmd cmd; - // The value of the optional .err2out parameter from the last nob_chain_cmd() call. - bool err2out; -} Nob_Chain; - -typedef struct { - const char *stdin_path; -} Nob_Chain_Begin_Opt; -#define nob_chain_begin(chain, ...) nob_chain_begin_opt((chain), (Nob_Chain_Begin_Opt) { __VA_ARGS__ }) -NOBDEF bool nob_chain_begin_opt(Nob_Chain *chain, Nob_Chain_Begin_Opt opt); - -typedef struct { - bool err2out; - bool dont_reset; -} Nob_Chain_Cmd_Opt; -#define nob_chain_cmd(chain, cmd, ...) nob_chain_cmd_opt((chain), (cmd), (Nob_Chain_Cmd_Opt) { __VA_ARGS__ }) -NOBDEF bool nob_chain_cmd_opt(Nob_Chain *chain, Nob_Cmd *cmd, Nob_Chain_Cmd_Opt opt); - -typedef struct { - Nob_Procs *async; - size_t max_procs; - const char *stdout_path; - const char *stderr_path; -} Nob_Chain_End_Opt; -#define nob_chain_end(chain, ...) nob_chain_end_opt((chain), (Nob_Chain_End_Opt) { __VA_ARGS__ }) -NOBDEF bool nob_chain_end_opt(Nob_Chain *chain, Nob_Chain_End_Opt opt); - -// Get amount of processors on the machine. -NOBDEF int nob_nprocs(void); - -#define NOB_NANOS_PER_SEC (1000*1000*1000) - -// The maximum time span representable is 584 years. -NOBDEF uint64_t nob_nanos_since_unspecified_epoch(void); - -// Same as nob_cmd_run_opt but using cool variadic macro to set the default options. -// See https://x.com/vkrajacic/status/1749816169736073295 for more info on how to use such macros. -#define nob_cmd_run(cmd, ...) nob_cmd_run_opt((cmd), (Nob_Cmd_Opt){__VA_ARGS__}) - -// DEPRECATED: -// -// You were suppose to use this structure like this: -// -// ```c -// Nob_Fd fdin = nob_fd_open_for_read("input.txt"); -// if (fdin == NOB_INVALID_FD) fail(); -// Nob_Fd fdout = nob_fd_open_for_write("output.txt"); -// if (fdout == NOB_INVALID_FD) fail(); -// Nob_Cmd cmd = {0}; -// nob_cmd_append(&cmd, "cat"); -// if (!nob_cmd_run_sync_redirect_and_reset(&cmd, (Nob_Cmd_Redirect) { -// .fdin = &fdin, -// .fdout = &fdout -// })) fail(); -// ``` -// -// But these days you should do: -// -// ```c -// Nob_Cmd cmd = {0}; -// nob_cmd_append(&cmd, "cat"); -// if (!nob_cmd_run(&cmd, .stdin_path = "input.txt", .stdout_path = "output.txt")) fail(); -// ``` -typedef struct { - Nob_Fd *fdin; - Nob_Fd *fdout; - Nob_Fd *fderr; -} Nob_Cmd_Redirect; - -// Render a string representation of a command into a string builder. Keep in mind the the -// string builder is not NULL-terminated by default. Use nob_sb_append_null if you plan to -// use it as a C string. -NOBDEF void nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render); - -NOBDEF void nob__cmd_append(Nob_Cmd *cmd, size_t n, ...); -#define nob_cmd_append(cmd, ...) \ - nob__cmd_append(cmd, (sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*)), __VA_ARGS__) - -// TODO: nob_cmd_extend() evaluates other_cmd twice -// It can be fixed by turning nob_cmd_extend() call into a statement. -// But that may break backward compatibility of the API. -#define nob_cmd_extend(cmd, other_cmd) \ - nob_da_append_many(cmd, (other_cmd)->items, (other_cmd)->count) - -// Free all the memory allocated by command arguments -#define nob_cmd_free(cmd) NOB_FREE(cmd.items) - -// Run command asynchronously -NOB_DEPRECATED("Use `nob_cmd_run(&cmd, .async = &procs, .dont_reset = true)`.") -NOBDEF Nob_Proc nob_cmd_run_async(Nob_Cmd cmd); - -// nob_cmd_run_async_and_reset() is just like nob_cmd_run_async() except it also resets cmd.count to 0 -// so the Nob_Cmd instance can be seamlessly used several times in a row -NOB_DEPRECATED("Use `nob_cmd_run(&cmd, .async = &procs)` intead.") -NOBDEF Nob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd); - -// Run redirected command asynchronously -NOB_DEPRECATED("Use `nob_cmd_run(&cmd, " - ".async = &procs, " - ".stdin_path = \"path/to/stdin\", " - ".stdout_path = \"path/to/stdout\", " - ".stderr_path = \"path/to/stderr\", " - ".dont_reset = true" - ")` instead.") -NOBDEF Nob_Proc nob_cmd_run_async_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect); - -// Run redirected command asynchronously and set cmd.count to 0 and close all the opened files -NOB_DEPRECATED("Use `nob_cmd_run(&cmd, " - ".async = &procs, " - ".stdin_path = \"path/to/stdin\", " - ".stdout_path = \"path/to/stdout\", " - ".stderr_path = \"path/to/stderr\")` instead.") -NOBDEF Nob_Proc nob_cmd_run_async_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect); - -// Run command synchronously -NOB_DEPRECATED("Use `nob_cmd_run(&cmd, .dont_reset = true)` instead.") -NOBDEF bool nob_cmd_run_sync(Nob_Cmd cmd); - -// NOTE: nob_cmd_run_sync_and_reset() is just like nob_cmd_run_sync() except it also resets cmd.count to 0 -// so the Nob_Cmd instance can be seamlessly used several times in a row -NOB_DEPRECATED("Use `nob_cmd_run(&cmd)` instead.") -NOBDEF bool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd); - -// Run redirected command synchronously -NOB_DEPRECATED("Use `nob_cmd_run(&cmd, " - ".stdin_path = \"path/to/stdin\", " - ".stdout_path = \"path/to/stdout\", " - ".stderr_path = \"path/to/stderr\", " - ".dont_reset = true" - ")` instead.") -NOBDEF bool nob_cmd_run_sync_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect); - -// Run redirected command synchronously and set cmd.count to 0 and close all the opened files -NOB_DEPRECATED("Use `nob_cmd_run(&cmd, " - ".stdin_path = \"path/to/stdin\", " - ".stdout_path = \"path/to/stdout\", " - ".stderr_path = \"path/to/stderr\")` instead.") -NOBDEF bool nob_cmd_run_sync_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect); - -#ifndef NOB_TEMP_CAPACITY -#define NOB_TEMP_CAPACITY (8*1024*1024) -#endif // NOB_TEMP_CAPACITY -NOBDEF char *nob_temp_strdup(const char *cstr); -NOBDEF char *nob_temp_strndup(const char *cstr, size_t size); -NOBDEF void *nob_temp_alloc(size_t size); -NOBDEF char *nob_temp_sprintf(const char *format, ...) NOB_PRINTF_FORMAT(1, 2); -NOBDEF char *nob_temp_vsprintf(const char *format, va_list ap); -// nob_temp_reset() - Resets the entire temporary storage to 0. -// -// It is generally not recommended to call this function ever. What you usually want to do is let's say you have a loop, -// that allocates some temporary objects and cleans them up at the end of each iteration. You should use -// nob_temp_save() and nob_temp_rewind() to organize such loop like this: -// -// ```c -// char *message = nob_temp_sprintf("This message is still valid after the loop below"); -// while (!quit) { -// size_t mark = nob_temp_save(); -// nob_temp_alloc(69); -// nob_temp_alloc(420); -// nob_temp_alloc(1337); -// nob_temp_rewind(mark); -// } -// printf("%s\n", message); -// ``` -// -// That way all the temporary allocations created before the loop are still valid even after the loop. -// Such save/rewind blocks define lifetime boundaries of the temporary objects which also could be nested. -// This turns the temporary storage into kind of a second stack with a more manual management. -NOBDEF void nob_temp_reset(void); -NOBDEF size_t nob_temp_save(void); -NOBDEF void nob_temp_rewind(size_t checkpoint); - -// Given any path returns the last part of that path. -// "/path/to/a/file.c" -> "file.c"; "/path/to/a/directory" -> "directory" -NOBDEF const char *nob_path_name(const char *path); -NOBDEF bool nob_rename(const char *old_path, const char *new_path); -NOBDEF int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count); -NOBDEF int nob_needs_rebuild1(const char *output_path, const char *input_path); -NOBDEF int nob_file_exists(const char *file_path); -NOBDEF const char *nob_get_current_dir_temp(void); -NOBDEF bool nob_set_current_dir(const char *path); -// Returns you the directory part of the path allocated on the temporary storage. -NOBDEF char *nob_temp_dir_name(const char *path); -NOBDEF char *nob_temp_file_name(const char *path); -NOBDEF char *nob_temp_file_ext(const char *path); -NOBDEF char *nob_temp_running_executable_path(void); - -// TODO: we should probably document somewhere all the compilers we support - -// The nob_cc_* macros try to abstract away the specific compiler. -// They are verify basic and not particularly flexible, but you can redefine them if you need to -// or not use them at all and create your own abstraction on top of Nob_Cmd. - -#ifndef nob_cc -# if _WIN32 -# if defined(__GNUC__) -# define nob_cc(cmd) nob_cmd_append(cmd, "cc") -# elif defined(__clang__) -# define nob_cc(cmd) nob_cmd_append(cmd, "clang") -# elif defined(_MSC_VER) -# define nob_cc(cmd) nob_cmd_append(cmd, "cl.exe") -# elif defined(__TINYC__) -# define nob_cc(cmd) nob_cmd_append(cmd, "tcc") -# endif -# else -# define nob_cc(cmd) nob_cmd_append(cmd, "cc") -# endif -#endif // nob_cc - -#ifndef nob_cc_flags -# if defined(_MSC_VER) && !defined(__clang__) -# define nob_cc_flags(cmd) nob_cmd_append(cmd, "/W4", "/nologo", "/D_CRT_SECURE_NO_WARNINGS") -# else -# define nob_cc_flags(cmd) nob_cmd_append(cmd, "-Wall", "-Wextra") -# endif -#endif // nob_cc_flags - -#ifndef nob_cc_output -# if defined(_MSC_VER) && !defined(__clang__) -# define nob_cc_output(cmd, output_path) nob_cmd_append(cmd, nob_temp_sprintf("/Fe:%s", (output_path)), nob_temp_sprintf("/Fo:%s", (output_path))) -# else -# define nob_cc_output(cmd, output_path) nob_cmd_append(cmd, "-o", (output_path)) -# endif -#endif // nob_cc_output - -#ifndef nob_cc_inputs -# define nob_cc_inputs(cmd, ...) nob_cmd_append(cmd, __VA_ARGS__) -#endif // nob_cc_inputs - -// TODO: add MinGW support for Go Rebuild Urself™ Technology and all the nob_cc_* macros above -// Musializer contributors came up with a pretty interesting idea of an optional prefix macro which could be useful for -// MinGW support: -// https://github.com/tsoding/musializer/blob/b7578cc76b9ecb573d239acc9ccf5a04d3aba2c9/src_build/nob_win64_mingw.c#L3-L9 -// TODO: Maybe instead NOB_REBUILD_URSELF macro, the Go Rebuild Urself™ Technology should use the -// user defined nob_cc_* macros instead? -#ifndef NOB_REBUILD_URSELF -# if defined(_WIN32) -# if defined(__clang__) -# if defined(__cplusplus) -# define NOB_REBUILD_URSELF(binary_path, source_path) "clang", "-x", "c++", "-o", binary_path, source_path -# else -# define NOB_REBUILD_URSELF(binary_path, source_path) "clang", "-x", "c", "-o", binary_path, source_path -# endif -# elif defined(__GNUC__) -# if defined(__cplusplus) -# define NOB_REBUILD_URSELF(binary_path, source_path) "gcc", "-x", "c++", "-o", binary_path, source_path -# else -# define NOB_REBUILD_URSELF(binary_path, source_path) "gcc", "-x", "c", "-o", binary_path, source_path -# endif -# elif defined(_MSC_VER) -# define NOB_REBUILD_URSELF(binary_path, source_path) "cl.exe", nob_temp_sprintf("/Fe:%s", (binary_path)), source_path -# elif defined(__TINYC__) -# define NOB_REBUILD_URSELF(binary_path, source_path) "tcc", "-o", binary_path, source_path -# endif -# else -# if defined(__cplusplus) -# define NOB_REBUILD_URSELF(binary_path, source_path) "cc", "-x", "c++", "-o", binary_path, source_path -# else -# define NOB_REBUILD_URSELF(binary_path, source_path) "cc", "-x", "c", "-o", binary_path, source_path -# endif -# endif -#endif - -// Go Rebuild Urself™ Technology -// -// How to use it: -// int main(int argc, char** argv) { -// NOB_GO_REBUILD_URSELF(argc, argv); -// // actual work -// return 0; -// } -// -// After you added this macro every time you run ./nob it will detect -// that you modified its original source code and will try to rebuild itself -// before doing any actual work. So you only need to bootstrap your build system -// once. -// -// The modification is detected by comparing the last modified times of the executable -// and its source code. The same way the make utility usually does it. -// -// The rebuilding is done by using the NOB_REBUILD_URSELF macro which you can redefine -// if you need a special way of bootstraping your build system. (which I personally -// do not recommend since the whole idea of NoBuild is to keep the process of bootstrapping -// as simple as possible and doing all of the actual work inside of ./nob) -// -NOBDEF void nob__go_rebuild_urself(int argc, char **argv, const char *source_path, ...); -#define NOB_GO_REBUILD_URSELF(argc, argv) nob__go_rebuild_urself(argc, argv, __FILE__, NULL) -// Sometimes your nob.c includes additional files, so you want the Go Rebuild Urself™ Technology to check -// if they also were modified and rebuild nob.c accordingly. For that we have NOB_GO_REBUILD_URSELF_PLUS(): -// ```c -// #define NOB_IMPLEMENTATION -// #include "nob.h" -// -// #include "foo.c" -// #include "bar.c" -// -// int main(int argc, char **argv) -// { -// NOB_GO_REBUILD_URSELF_PLUS(argc, argv, "foo.c", "bar.c"); -// // ... -// return 0; -// } -#define NOB_GO_REBUILD_URSELF_PLUS(argc, argv, ...) nob__go_rebuild_urself(argc, argv, __FILE__, __VA_ARGS__, NULL); - -typedef struct { - size_t count; - const char *data; -} Nob_String_View; - -NOBDEF const char *nob_temp_sv_to_cstr(Nob_String_View sv); - -NOBDEF Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim); -NOBDEF Nob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n); -NOBDEF Nob_String_View nob_sv_trim(Nob_String_View sv); -NOBDEF Nob_String_View nob_sv_trim_left(Nob_String_View sv); -NOBDEF Nob_String_View nob_sv_trim_right(Nob_String_View sv); -NOBDEF bool nob_sv_eq(Nob_String_View a, Nob_String_View b); -NOBDEF bool nob_sv_end_with(Nob_String_View sv, const char *cstr); -NOBDEF bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_prefix); -NOBDEF Nob_String_View nob_sv_from_cstr(const char *cstr); -NOBDEF Nob_String_View nob_sv_from_parts(const char *data, size_t count); -// nob_sb_to_sv() enables you to just view Nob_String_Builder as Nob_String_View -#define nob_sb_to_sv(sb) nob_sv_from_parts((sb).items, (sb).count) - -// printf macros for String_View -#ifndef SV_Fmt -#define SV_Fmt "%.*s" -#endif // SV_Fmt -#ifndef SV_Arg -#define SV_Arg(sv) (int) (sv).count, (sv).data -#endif // SV_Arg -// USAGE: -// String_View name = ...; -// printf("Name: "SV_Fmt"\n", SV_Arg(name)); - -#ifdef _WIN32 - -NOBDEF char *nob_win32_error_message(DWORD err); - -#endif // _WIN32 - -#endif // NOB_H_ - -#ifdef NOB_IMPLEMENTATION - -// This is like nob_proc_wait() but waits asynchronously. Depending on the platform ms means different thing. -// On Windows it means timeout. On POSIX it means for how long to sleep after checking if the process exited, -// so to not peg the core too much. Since this API is kinda of weird, the function is private for now. -static int nob__proc_wait_async(Nob_Proc proc, int ms); - -// Starts the process for the command. Its main purpose is to be the base for nob_cmd_run() and nob_cmd_run_opt(). -static Nob_Proc nob__cmd_start_process(Nob_Cmd cmd, Nob_Fd *fdin, Nob_Fd *fdout, Nob_Fd *fderr); - -// Any messages with the level below nob_minimal_log_level are going to be suppressed. -Nob_Log_Level nob_minimal_log_level = NOB_INFO; - -NOBDEF void nob__cmd_append(Nob_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 *); - nob_da_append(cmd, arg); - } - va_end(args); -} - -#ifdef _WIN32 - -// Base on https://stackoverflow.com/a/75644008 -// > .NET Core uses 4096 * sizeof(WCHAR) buffer on stack for FormatMessageW call. And...thats it. -// > -// > https://github.com/dotnet/runtime/blob/3b63eb1346f1ddbc921374a5108d025662fb5ffd/src/coreclr/utilcode/posterror.cpp#L264-L265 -#ifndef NOB_WIN32_ERR_MSG_SIZE -#define NOB_WIN32_ERR_MSG_SIZE (4 * 1024) -#endif // NOB_WIN32_ERR_MSG_SIZE - -NOBDEF char *nob_win32_error_message(DWORD err) { - static char win32ErrMsg[NOB_WIN32_ERR_MSG_SIZE] = {0}; - DWORD errMsgSize = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, LANG_USER_DEFAULT, win32ErrMsg, - NOB_WIN32_ERR_MSG_SIZE, NULL); - - if (errMsgSize == 0) { - if (GetLastError() != ERROR_MR_MID_NOT_FOUND) { - if (sprintf(win32ErrMsg, "Could not get error message for 0x%lX", err) > 0) { - return (char *)&win32ErrMsg; - } else { - return NULL; - } - } else { - if (sprintf(win32ErrMsg, "Invalid Windows Error code (0x%lX)", err) > 0) { - return (char *)&win32ErrMsg; - } else { - return NULL; - } - } - } - - while (errMsgSize > 1 && isspace(win32ErrMsg[errMsgSize - 1])) { - win32ErrMsg[--errMsgSize] = '\0'; - } - - return win32ErrMsg; -} - -#endif // _WIN32 - -// The implementation idea is stolen from https://github.com/zhiayang/nabs -NOBDEF void nob__go_rebuild_urself(int argc, char **argv, const char *source_path, ...) -{ - const char *binary_path = nob_shift(argv, argc); -#ifdef _WIN32 - // On Windows executables almost always invoked without extension, so - // it's ./nob, not ./nob.exe. For renaming the extension is a must. - if (!nob_sv_end_with(nob_sv_from_cstr(binary_path), ".exe")) { - binary_path = nob_temp_sprintf("%s.exe", binary_path); - } -#endif - - Nob_File_Paths source_paths = {0}; - nob_da_append(&source_paths, source_path); - va_list args; - va_start(args, source_path); - for (;;) { - const char *path = va_arg(args, const char*); - if (path == NULL) break; - nob_da_append(&source_paths, path); - } - va_end(args); - - int rebuild_is_needed = nob_needs_rebuild(binary_path, source_paths.items, source_paths.count); - if (rebuild_is_needed < 0) exit(1); // error - if (!rebuild_is_needed) { // no rebuild is needed - NOB_FREE(source_paths.items); - return; - } - - Nob_Cmd cmd = {0}; - - const char *old_binary_path = nob_temp_sprintf("%s.old", binary_path); - - if (!nob_rename(binary_path, old_binary_path)) exit(1); - nob_cmd_append(&cmd, NOB_REBUILD_URSELF(binary_path, source_path)); - Nob_Cmd_Opt opt = {0}; - if (!nob_cmd_run_opt(&cmd, opt)) { - nob_rename(old_binary_path, binary_path); - exit(1); - } -#ifdef NOB_EXPERIMENTAL_DELETE_OLD - // TODO: this is an experimental behavior behind a compilation flag. - // Once it is confirmed that it does not cause much problems on both POSIX and Windows - // we may turn it on by default. - nob_delete_file(old_binary_path); -#endif // NOB_EXPERIMENTAL_DELETE_OLD - - nob_cmd_append(&cmd, binary_path); - nob_da_append_many(&cmd, argv, argc); - if (!nob_cmd_run_opt(&cmd, opt)) exit(1); - exit(0); -} - -static size_t nob_temp_size = 0; -static char nob_temp[NOB_TEMP_CAPACITY] = {0}; - -NOBDEF bool nob_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) { -#ifndef NOB_NO_ECHO - nob_log(NOB_INFO, "directory `%s` already exists", path); -#endif // NOB_NO_ECHO - return true; - } - nob_log(NOB_ERROR, "could not create directory `%s`: %s", path, strerror(errno)); - return false; - } - -#ifndef NOB_NO_ECHO - nob_log(NOB_INFO, "created directory `%s`", path); -#endif // NOB_NO_ECHO - return true; -} - -NOBDEF bool nob_copy_file(const char *src_path, const char *dst_path) -{ -#ifndef NOB_NO_ECHO - nob_log(NOB_INFO, "copying %s -> %s", src_path, dst_path); -#endif // NOB_NO_ECHO -#ifdef _WIN32 - if (!CopyFile(src_path, dst_path, FALSE)) { - nob_log(NOB_ERROR, "Could not copy file: %s", nob_win32_error_message(GetLastError())); - return false; - } - return true; -#else - int src_fd = -1; - int dst_fd = -1; - size_t buf_size = 32*1024; - char *buf = (char*)NOB_REALLOC(NULL, buf_size); - NOB_ASSERT(buf != NULL && "Buy more RAM lol!!"); - bool result = true; - - src_fd = open(src_path, O_RDONLY); - if (src_fd < 0) { - nob_log(NOB_ERROR, "Could not open file %s: %s", src_path, strerror(errno)); - nob_return_defer(false); - } - - struct stat src_stat; - if (fstat(src_fd, &src_stat) < 0) { - nob_log(NOB_ERROR, "Could not get mode of file %s: %s", src_path, strerror(errno)); - nob_return_defer(false); - } - - dst_fd = open(dst_path, O_CREAT | O_TRUNC | O_WRONLY, src_stat.st_mode); - if (dst_fd < 0) { - nob_log(NOB_ERROR, "Could not create file %s: %s", dst_path, strerror(errno)); - nob_return_defer(false); - } - - for (;;) { - ssize_t n = read(src_fd, buf, buf_size); - if (n == 0) break; - if (n < 0) { - nob_log(NOB_ERROR, "Could not read from file %s: %s", src_path, strerror(errno)); - nob_return_defer(false); - } - char *buf2 = buf; - while (n > 0) { - ssize_t m = write(dst_fd, buf2, n); - if (m < 0) { - nob_log(NOB_ERROR, "Could not write to file %s: %s", dst_path, strerror(errno)); - nob_return_defer(false); - } - n -= m; - buf2 += m; - } - } - -defer: - NOB_FREE(buf); - close(src_fd); - close(dst_fd); - return result; -#endif -} - -NOBDEF void nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render) -{ - for (size_t i = 0; i < cmd.count; ++i) { - const char *arg = cmd.items[i]; - if (arg == NULL) break; - if (i > 0) nob_sb_append_cstr(render, " "); - if (!strchr(arg, ' ')) { - nob_sb_append_cstr(render, arg); - } else { - nob_da_append(render, '\''); - nob_sb_append_cstr(render, arg); - nob_da_append(render, '\''); - } - } -} - -#ifdef _WIN32 -// https://learn.microsoft.com/en-gb/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way -static void nob__win32_cmd_quote(Nob_Cmd cmd, Nob_String_Builder *quoted) -{ - for (size_t i = 0; i < cmd.count; ++i) { - const char *arg = cmd.items[i]; - if (arg == NULL) break; - size_t len = strlen(arg); - if (i > 0) nob_da_append(quoted, ' '); - if (len != 0 && NULL == strpbrk(arg, " \t\n\v\"")) { - // no need to quote - nob_da_append_many(quoted, arg, len); - } else { - // we need to escape: - // 1. double quotes in the original arg - // 2. consequent backslashes before a double quote - size_t backslashes = 0; - nob_da_append(quoted, '\"'); - for (size_t j = 0; j < len; ++j) { - char x = arg[j]; - if (x == '\\') { - backslashes += 1; - } else { - if (x == '\"') { - // escape backslashes (if any) and the double quote - for (size_t k = 0; k < 1+backslashes; ++k) { - nob_da_append(quoted, '\\'); - } - } - backslashes = 0; - } - nob_da_append(quoted, x); - } - // escape backslashes (if any) - for (size_t k = 0; k < backslashes; ++k) { - nob_da_append(quoted, '\\'); - } - nob_da_append(quoted, '\"'); - } - } -} -#endif - -NOBDEF int nob_nprocs(void) -{ -#ifdef _WIN32 - SYSTEM_INFO siSysInfo; - GetSystemInfo(&siSysInfo); - return siSysInfo.dwNumberOfProcessors; -#else - return sysconf(_SC_NPROCESSORS_ONLN); -#endif -} - -NOBDEF bool nob_cmd_run_opt(Nob_Cmd *cmd, Nob_Cmd_Opt opt) -{ - bool result = true; - Nob_Fd fdin = NOB_INVALID_FD; - Nob_Fd fdout = NOB_INVALID_FD; - Nob_Fd fderr = NOB_INVALID_FD; - Nob_Fd *opt_fdin = NULL; - Nob_Fd *opt_fdout = NULL; - Nob_Fd *opt_fderr = NULL; - Nob_Proc proc = NOB_INVALID_PROC; - - size_t max_procs = opt.max_procs > 0 ? opt.max_procs : (size_t) nob_nprocs() + 1; - - if (opt.async && max_procs > 0) { - while (opt.async->count >= max_procs) { - for (size_t i = 0; i < opt.async->count; ++i) { - int ret = nob__proc_wait_async(opt.async->items[i], 1); - if (ret < 0) nob_return_defer(false); - if (ret) { - nob_da_remove_unordered(opt.async, i); - break; - } - } - } - } - - if (opt.stdin_path) { - fdin = nob_fd_open_for_read(opt.stdin_path); - if (fdin == NOB_INVALID_FD) nob_return_defer(false); - opt_fdin = &fdin; - } - if (opt.stdout_path) { - fdout = nob_fd_open_for_write(opt.stdout_path); - if (fdout == NOB_INVALID_FD) nob_return_defer(false); - opt_fdout = &fdout; - } - if (opt.stderr_path) { - fderr = nob_fd_open_for_write(opt.stderr_path); - if (fderr == NOB_INVALID_FD) nob_return_defer(false); - opt_fderr = &fderr; - } - proc = nob__cmd_start_process(*cmd, opt_fdin, opt_fdout, opt_fderr); - - if (opt.async) { - if (proc == NOB_INVALID_PROC) nob_return_defer(false); - nob_da_append(opt.async, proc); - } else { - if (!nob_proc_wait(proc)) nob_return_defer(false); - } - -defer: - if (opt_fdin) nob_fd_close(*opt_fdin); - if (opt_fdout) nob_fd_close(*opt_fdout); - if (opt_fderr) nob_fd_close(*opt_fderr); - if (!opt.dont_reset) cmd->count = 0; - return result; -} - -NOBDEF bool nob_chain_begin_opt(Nob_Chain *chain, Nob_Chain_Begin_Opt opt) -{ - chain->cmd.count = 0; - chain->err2out = false; - chain->fdin = NOB_INVALID_FD; - if (opt.stdin_path) { - chain->fdin = nob_fd_open_for_read(opt.stdin_path); - if (chain->fdin == NOB_INVALID_FD) return false; - } - return true; -} - -NOBDEF bool nob_chain_cmd_opt(Nob_Chain *chain, Nob_Cmd *cmd, Nob_Chain_Cmd_Opt opt) -{ - bool result = true; - Nob_Pipe pp = {0}; - struct { - Nob_Fd items[5]; // should be no more than 3, but we allocate 5 just in case - size_t count; - } fds = {0}; - - NOB_ASSERT(cmd->count > 0); - - if (chain->cmd.count != 0) { // not first cmd in the chain - Nob_Fd *pfdin = NULL; - if (chain->fdin != NOB_INVALID_FD) { - nob_fa_append(&fds, chain->fdin); - pfdin = &chain->fdin; - } - if (!nob_pipe_create(&pp)) nob_return_defer(false); - nob_fa_append(&fds, pp.write); - Nob_Fd *pfdout = &pp.write; - Nob_Fd *pfderr = chain->err2out ? pfdout : NULL; - - Nob_Proc proc = nob__cmd_start_process(chain->cmd, pfdin, pfdout, pfderr); - chain->cmd.count = 0; - if (proc == NOB_INVALID_PROC) { - nob_fa_append(&fds, pp.read); - nob_return_defer(false); - } - chain->fdin = pp.read; - } - - nob_da_append_many(&chain->cmd, cmd->items, cmd->count); - chain->err2out = opt.err2out; - -defer: - for (size_t i = 0; i < fds.count; ++i) { - nob_fd_close(fds.items[i]); - } - if (!opt.dont_reset) cmd->count = 0; - return result; -} - -static Nob_Fd nob__fd_stdout(void) -{ -#ifdef _WIN32 - return GetStdHandle(STD_OUTPUT_HANDLE); -#else - return STDOUT_FILENO; -#endif // _WIN32 -} - -NOBDEF bool nob_chain_end_opt(Nob_Chain *chain, Nob_Chain_End_Opt opt) -{ - bool result = true; - - Nob_Fd *pfdin = NULL; - struct { - Nob_Fd items[5]; // should be no more than 3, but we allocate 5 just in case - size_t count; - } fds = {0}; - - if (chain->fdin != NOB_INVALID_FD) { - nob_fa_append(&fds, chain->fdin); - pfdin = &chain->fdin; - } - - if (chain->cmd.count != 0) { // Non-empty chain case - size_t max_procs = opt.max_procs > 0 ? opt.max_procs : (size_t) nob_nprocs() + 1; - - if (opt.async && max_procs > 0) { - while (opt.async->count >= max_procs) { - for (size_t i = 0; i < opt.async->count; ++i) { - int ret = nob__proc_wait_async(opt.async->items[i], 1); - if (ret < 0) nob_return_defer(false); - if (ret) { - nob_da_remove_unordered(opt.async, i); - break; - } - } - } - } - - Nob_Fd fdout = nob__fd_stdout(); - if (opt.stdout_path) { - fdout = nob_fd_open_for_write(opt.stdout_path); - if (fdout == NOB_INVALID_FD) nob_return_defer(false); - nob_fa_append(&fds, fdout); - } - - Nob_Fd fderr = 0; - Nob_Fd *pfderr = NULL; - if (chain->err2out) pfderr = &fdout; - if (opt.stderr_path) { - if (pfderr == NULL) { - fderr = nob_fd_open_for_write(opt.stderr_path); - if (fderr == NOB_INVALID_FD) nob_return_defer(false); - nob_fa_append(&fds, fderr); - pfderr = &fderr; - } else { - // There was err2out set for the last command. - // All the stderr will go to stdout. - // So the stderr file is going to be empty. - NOB_ASSERT(chain->err2out); - if (!nob_write_entire_file(opt.stderr_path, NULL, 0)) nob_return_defer(false); - } - } - - Nob_Proc proc = nob__cmd_start_process(chain->cmd, pfdin, &fdout, pfderr); - chain->cmd.count = 0; - - if (opt.async) { - if (proc == NOB_INVALID_PROC) nob_return_defer(false); - nob_da_append(opt.async, proc); - } else { - if (!nob_proc_wait(proc)) nob_return_defer(false); - } - } - -defer: - for (size_t i = 0; i < fds.count; ++i) { - nob_fd_close(fds.items[i]); - } - return result; -} - -// The maximum time span representable is 584 years. -NOBDEF uint64_t nob_nanos_since_unspecified_epoch(void) -{ -#ifdef _WIN32 - LARGE_INTEGER Time; - QueryPerformanceCounter(&Time); - - static LARGE_INTEGER Frequency = {0}; - if (Frequency.QuadPart == 0) { - QueryPerformanceFrequency(&Frequency); - } - - uint64_t Secs = Time.QuadPart / Frequency.QuadPart; - uint64_t Nanos = Time.QuadPart % Frequency.QuadPart * NOB_NANOS_PER_SEC / Frequency.QuadPart; - return NOB_NANOS_PER_SEC * Secs + Nanos; -#else - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - - return NOB_NANOS_PER_SEC * ts.tv_sec + ts.tv_nsec; -#endif // _WIN32 -} - -NOBDEF Nob_Proc nob_cmd_run_async_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect) -{ - return nob__cmd_start_process(cmd, redirect.fdin, redirect.fdout, redirect.fderr); -} - -static Nob_Proc nob__cmd_start_process(Nob_Cmd cmd, Nob_Fd *fdin, Nob_Fd *fdout, Nob_Fd *fderr) -{ - if (cmd.count < 1) { - nob_log(NOB_ERROR, "Could not run empty command"); - return NOB_INVALID_PROC; - } - -#ifndef NOB_NO_ECHO - Nob_String_Builder sb = {0}; - nob_cmd_render(cmd, &sb); - nob_sb_append_null(&sb); - nob_log(NOB_INFO, "CMD: %s", sb.items); - nob_sb_free(sb); - memset(&sb, 0, sizeof(sb)); -#endif // NOB_NO_ECHO - -#ifdef _WIN32 - // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output - - STARTUPINFO siStartInfo; - ZeroMemory(&siStartInfo, sizeof(siStartInfo)); - siStartInfo.cb = sizeof(STARTUPINFO); - // NOTE: theoretically setting NULL to std handles should not be a problem - // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior - // TODO: check for errors in GetStdHandle - siStartInfo.hStdError = fderr ? *fderr : GetStdHandle(STD_ERROR_HANDLE); - siStartInfo.hStdOutput = fdout ? *fdout : GetStdHandle(STD_OUTPUT_HANDLE); - siStartInfo.hStdInput = fdin ? *fdin : GetStdHandle(STD_INPUT_HANDLE); - siStartInfo.dwFlags |= STARTF_USESTDHANDLES; - - PROCESS_INFORMATION piProcInfo; - ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); - - Nob_String_Builder quoted = {0}; - nob__win32_cmd_quote(cmd, "ed); - nob_sb_append_null("ed); - BOOL bSuccess = CreateProcessA(NULL, quoted.items, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo); - nob_sb_free(quoted); - - if (!bSuccess) { - nob_log(NOB_ERROR, "Could not create child process for %s: %s", cmd.items[0], nob_win32_error_message(GetLastError())); - return NOB_INVALID_PROC; - } - - CloseHandle(piProcInfo.hThread); - - return piProcInfo.hProcess; -#else - pid_t cpid = fork(); - if (cpid < 0) { - nob_log(NOB_ERROR, "Could not fork child process: %s", strerror(errno)); - return NOB_INVALID_PROC; - } - - if (cpid == 0) { - if (fdin) { - if (dup2(*fdin, STDIN_FILENO) < 0) { - nob_log(NOB_ERROR, "Could not setup stdin for child process: %s", strerror(errno)); - exit(1); - } - } - - if (fdout) { - if (dup2(*fdout, STDOUT_FILENO) < 0) { - nob_log(NOB_ERROR, "Could not setup stdout for child process: %s", strerror(errno)); - exit(1); - } - } - - if (fderr) { - if (dup2(*fderr, STDERR_FILENO) < 0) { - nob_log(NOB_ERROR, "Could not setup stderr for child process: %s", strerror(errno)); - exit(1); - } - } - - // NOTE: This leaks a bit of memory in the child process. - // But do we actually care? It's a one off leak anyway... - Nob_Cmd cmd_null = {0}; - nob_da_append_many(&cmd_null, cmd.items, cmd.count); - nob_cmd_append(&cmd_null, NULL); - - if (execvp(cmd.items[0], (char * const*) cmd_null.items) < 0) { - nob_log(NOB_ERROR, "Could not exec child process for %s: %s", cmd.items[0], strerror(errno)); - exit(1); - } - NOB_UNREACHABLE("nob_cmd_run_async_redirect"); - } - - return cpid; -#endif -} - -NOBDEF Nob_Proc nob_cmd_run_async(Nob_Cmd cmd) -{ - return nob__cmd_start_process(cmd, NULL, NULL, NULL); -} - -NOBDEF Nob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd) -{ - Nob_Proc proc = nob__cmd_start_process(*cmd, NULL, NULL, NULL); - cmd->count = 0; - return proc; -} - -NOBDEF Nob_Proc nob_cmd_run_async_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect) -{ - Nob_Proc proc = nob__cmd_start_process(*cmd, redirect.fdin, redirect.fdout, redirect.fderr); - cmd->count = 0; - if (redirect.fdin) { - nob_fd_close(*redirect.fdin); - *redirect.fdin = NOB_INVALID_FD; - } - if (redirect.fdout) { - nob_fd_close(*redirect.fdout); - *redirect.fdout = NOB_INVALID_FD; - } - if (redirect.fderr) { - nob_fd_close(*redirect.fderr); - *redirect.fderr = NOB_INVALID_FD; - } - return proc; -} - -NOBDEF Nob_Fd nob_fd_open_for_read(const char *path) -{ -#ifndef _WIN32 - Nob_Fd result = open(path, O_RDONLY); - if (result < 0) { - nob_log(NOB_ERROR, "Could not open file %s: %s", path, strerror(errno)); - return NOB_INVALID_FD; - } - return result; -#else - // https://docs.microsoft.com/en-us/windows/win32/fileio/opening-a-file-for-reading-or-writing - SECURITY_ATTRIBUTES saAttr = {0}; - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - - Nob_Fd result = CreateFile( - path, - GENERIC_READ, - 0, - &saAttr, - OPEN_EXISTING, - FILE_ATTRIBUTE_READONLY, - NULL); - - if (result == INVALID_HANDLE_VALUE) { - nob_log(NOB_ERROR, "Could not open file %s: %s", path, nob_win32_error_message(GetLastError())); - return NOB_INVALID_FD; - } - - return result; -#endif // _WIN32 -} - -NOBDEF Nob_Fd nob_fd_open_for_write(const char *path) -{ -#ifndef _WIN32 - Nob_Fd result = open(path, - O_WRONLY | O_CREAT | O_TRUNC, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (result < 0) { - nob_log(NOB_ERROR, "could not open file %s: %s", path, strerror(errno)); - return NOB_INVALID_FD; - } - return result; -#else - SECURITY_ATTRIBUTES saAttr = {0}; - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - - Nob_Fd result = CreateFile( - path, // name of the write - GENERIC_WRITE, // open for writing - 0, // do not share - &saAttr, // default security - CREATE_ALWAYS, // create always - FILE_ATTRIBUTE_NORMAL, // normal file - NULL // no attr. template - ); - - if (result == INVALID_HANDLE_VALUE) { - nob_log(NOB_ERROR, "Could not open file %s: %s", path, nob_win32_error_message(GetLastError())); - return NOB_INVALID_FD; - } - - return result; -#endif // _WIN32 -} - -NOBDEF void nob_fd_close(Nob_Fd fd) -{ -#ifdef _WIN32 - CloseHandle(fd); -#else - close(fd); -#endif // _WIN32 -} - -NOBDEF bool nob_pipe_create(Nob_Pipe *pp) -{ -#ifdef _WIN32 - // https://docs.microsoft.com/en-us/windows/win32/ProcThread/creating-a-child-process-with-redirected-input-and-output - - SECURITY_ATTRIBUTES saAttr = {0}; - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - - if (!CreatePipe(&pp->read, &pp->write, &saAttr, 0)) { - nob_log(NOB_ERROR, "Could not create pipe: %s", nob_win32_error_message(GetLastError())); - return false; - } - - return true; -#else - int pipefd[2]; - if (pipe(pipefd) < 0) { - nob_log(NOB_ERROR, "Could not create pipe: %s\n", strerror(errno)); - return false; - } - - pp->read = pipefd[0]; - pp->write = pipefd[1]; - - return true; -#endif // _WIN32 -} - -NOBDEF bool nob_procs_wait(Nob_Procs procs) -{ - bool success = true; - for (size_t i = 0; i < procs.count; ++i) { - success = nob_proc_wait(procs.items[i]) && success; - } - return success; -} - -NOBDEF bool nob_procs_flush(Nob_Procs *procs) -{ - bool success = nob_procs_wait(*procs); - procs->count = 0; - return success; -} - -NOBDEF bool nob_procs_wait_and_reset(Nob_Procs *procs) -{ - return nob_procs_flush(procs); -} - -NOBDEF bool nob_proc_wait(Nob_Proc proc) -{ - if (proc == NOB_INVALID_PROC) return false; - -#ifdef _WIN32 - DWORD result = WaitForSingleObject( - proc, // HANDLE hHandle, - INFINITE // DWORD dwMilliseconds - ); - - if (result == WAIT_FAILED) { - nob_log(NOB_ERROR, "could not wait on child process: %s", nob_win32_error_message(GetLastError())); - return false; - } - - DWORD exit_status; - if (!GetExitCodeProcess(proc, &exit_status)) { - nob_log(NOB_ERROR, "could not get process exit code: %s", nob_win32_error_message(GetLastError())); - return false; - } - - if (exit_status != 0) { - nob_log(NOB_ERROR, "command exited with exit code %lu", exit_status); - return false; - } - - CloseHandle(proc); - - return true; -#else - for (;;) { - int wstatus = 0; - if (waitpid(proc, &wstatus, 0) < 0) { - nob_log(NOB_ERROR, "could not wait on command (pid %d): %s", proc, strerror(errno)); - return false; - } - - if (WIFEXITED(wstatus)) { - int exit_status = WEXITSTATUS(wstatus); - if (exit_status != 0) { - nob_log(NOB_ERROR, "command exited with exit code %d", exit_status); - return false; - } - - break; - } - - if (WIFSIGNALED(wstatus)) { - nob_log(NOB_ERROR, "command process was terminated by signal %d", WTERMSIG(wstatus)); - return false; - } - } - - return true; -#endif -} - -static int nob__proc_wait_async(Nob_Proc proc, int ms) -{ - if (proc == NOB_INVALID_PROC) return false; - -#ifdef _WIN32 - DWORD result = WaitForSingleObject( - proc, // HANDLE hHandle, - ms // DWORD dwMilliseconds - ); - - if (result == WAIT_TIMEOUT) { - return 0; - } - - if (result == WAIT_FAILED) { - nob_log(NOB_ERROR, "could not wait on child process: %s", nob_win32_error_message(GetLastError())); - return -1; - } - - DWORD exit_status; - if (!GetExitCodeProcess(proc, &exit_status)) { - nob_log(NOB_ERROR, "could not get process exit code: %s", nob_win32_error_message(GetLastError())); - return -1; - } - - if (exit_status != 0) { - nob_log(NOB_ERROR, "command exited with exit code %lu", exit_status); - return -1; - } - - CloseHandle(proc); - - return 1; -#else - long ns = ms*1000*1000; - struct timespec duration = { - .tv_sec = ns/(1000*1000*1000), - .tv_nsec = ns%(1000*1000*1000), - }; - - int wstatus = 0; - pid_t pid = waitpid(proc, &wstatus, WNOHANG); - if (pid < 0) { - nob_log(NOB_ERROR, "could not wait on command (pid %d): %s", proc, strerror(errno)); - return -1; - } - - if (pid == 0) { - nanosleep(&duration, NULL); - return 0; - } - - if (WIFEXITED(wstatus)) { - int exit_status = WEXITSTATUS(wstatus); - if (exit_status != 0) { - nob_log(NOB_ERROR, "command exited with exit code %d", exit_status); - return -1; - } - - return 1; - } - - if (WIFSIGNALED(wstatus)) { - nob_log(NOB_ERROR, "command process was terminated by signal %d", WTERMSIG(wstatus)); - return -1; - } - - nanosleep(&duration, NULL); - return 0; -#endif -} - -NOBDEF bool nob_procs_append_with_flush(Nob_Procs *procs, Nob_Proc proc, size_t max_procs_count) -{ - nob_da_append(procs, proc); - - if (procs->count >= max_procs_count) { - if (!nob_procs_flush(procs)) return false; - } - - return true; -} - -NOBDEF bool nob_cmd_run_sync_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect) -{ - Nob_Proc p = nob__cmd_start_process(cmd, redirect.fdin, redirect.fdout, redirect.fderr); - return nob_proc_wait(p); -} - -NOBDEF bool nob_cmd_run_sync(Nob_Cmd cmd) -{ - Nob_Proc p = nob__cmd_start_process(cmd, NULL, NULL, NULL); - return nob_proc_wait(p); -} - -NOBDEF bool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd) -{ - Nob_Proc p = nob__cmd_start_process(*cmd, NULL, NULL, NULL); - cmd->count = 0; - return nob_proc_wait(p); -} - -NOBDEF bool nob_cmd_run_sync_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect) -{ - Nob_Proc p = nob__cmd_start_process(*cmd, redirect.fdin, redirect.fdout, redirect.fderr); - cmd->count = 0; - if (redirect.fdin) { - nob_fd_close(*redirect.fdin); - *redirect.fdin = NOB_INVALID_FD; - } - if (redirect.fdout) { - nob_fd_close(*redirect.fdout); - *redirect.fdout = NOB_INVALID_FD; - } - if (redirect.fderr) { - nob_fd_close(*redirect.fderr); - *redirect.fderr = NOB_INVALID_FD; - } - return nob_proc_wait(p); -} - -static nob_log_handler *nob__log_handler = &nob_default_log_handler; - -NOBDEF void nob_set_log_handler(nob_log_handler *handler) -{ - nob__log_handler = handler; -} - -NOBDEF nob_log_handler *nob_get_log_handler(void) -{ - return nob__log_handler; -} - -NOBDEF void nob_default_log_handler(Nob_Log_Level level, const char *fmt, va_list args) -{ - if (level < nob_minimal_log_level) return; - - switch (level) { - case NOB_INFO: - fprintf(stderr, "[INFO] "); - break; - case NOB_WARNING: - fprintf(stderr, "[WARNING] "); - break; - case NOB_ERROR: - fprintf(stderr, "[ERROR] "); - break; - case NOB_NO_LOGS: return; - default: - NOB_UNREACHABLE("Nob_Log_Level"); - } - - vfprintf(stderr, fmt, args); - fprintf(stderr, "\n"); -} - -NOBDEF void nob_cancer_log_handler(Nob_Log_Level level, const char *fmt, va_list args) -{ - switch (level) { - case NOB_INFO: - fprintf(stderr, "ℹ️ \x1b[36m[INFO]\x1b[0m "); - break; - case NOB_WARNING: - fprintf(stderr, "⚠️ \x1b[33m[WARNING]\x1b[0m "); - break; - case NOB_ERROR: - fprintf(stderr, "🚨 \x1b[31m[ERROR]\x1b[0m "); - break; - case NOB_NO_LOGS: return; - default: - NOB_UNREACHABLE("Nob_Log_Level"); - } - - vfprintf(stderr, fmt, args); - fprintf(stderr, "\n"); -} - -NOBDEF void nob_log(Nob_Log_Level level, const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - nob__log_handler(level, fmt, args); - va_end(args); -} - -NOBDEF bool nob_dir_entry_open(const char *dir_path, Nob_Dir_Entry *dir) -{ - memset(dir, 0, sizeof(*dir)); -#ifdef _WIN32 - size_t temp_mark = nob_temp_save(); - char *buffer = nob_temp_sprintf("%s\\*", dir_path); - dir->nob__private.win32_hFind = FindFirstFile(buffer, &dir->nob__private.win32_data); - nob_temp_rewind(temp_mark); - - if (dir->nob__private.win32_hFind == INVALID_HANDLE_VALUE) { - nob_log(NOB_ERROR, "Could not open directory %s: %s", dir_path, nob_win32_error_message(GetLastError())); - dir->error = true; - return false; - } -#else - dir->nob__private.posix_dir = opendir(dir_path); - if (dir->nob__private.posix_dir == NULL) { - nob_log(NOB_ERROR, "Could not open directory %s: %s", dir_path, strerror(errno)); - dir->error = true; - return false; - } -#endif // _WIN32 - return true; -} - -NOBDEF bool nob_dir_entry_next(Nob_Dir_Entry *dir) -{ -#ifdef _WIN32 - if (!dir->nob__private.win32_init) { - dir->nob__private.win32_init = true; - dir->name = dir->nob__private.win32_data.cFileName; - return true; - } - - if (!FindNextFile(dir->nob__private.win32_hFind, &dir->nob__private.win32_data)) { - if (GetLastError() == ERROR_NO_MORE_FILES) return false; - nob_log(NOB_ERROR, "Could not read next directory entry: %s", nob_win32_error_message(GetLastError())); - dir->error = true; - return false; - } - dir->name = dir->nob__private.win32_data.cFileName; -#else - errno = 0; - dir->nob__private.posix_ent = readdir(dir->nob__private.posix_dir); - if (dir->nob__private.posix_ent == NULL) { - if (errno == 0) return false; - nob_log(NOB_ERROR, "Could not read next directory entry: %s", strerror(errno)); - dir->error = true; - return false; - } - dir->name = dir->nob__private.posix_ent->d_name; -#endif // _WIN32 - return true; -} - -NOBDEF void nob_dir_entry_close(Nob_Dir_Entry dir) -{ -#ifdef _WIN32 - FindClose(dir.nob__private.win32_hFind); -#else - if (dir.nob__private.posix_dir) closedir(dir.nob__private.posix_dir); -#endif -} - -// On the moment of entering `nob__walk_dir_opt_impl()`, the `file_path` Nob_String_Builder is expected to be NULL-terminated. -// So you can freely pass `file_path->items` to functions that expect NULL-terminated file path. -// On existing `nob__walk_dir_opt_impl()` is expected to restore the original content of `file_path` -bool nob__walk_dir_opt_impl(Nob_String_Builder *file_path, Nob_Walk_Func func, size_t level, bool *stop, Nob_Walk_Dir_Opt opt) -{ - NOB_ASSERT(file_path->count > 0 && "file_path was probably not properly NULL-terminated"); - bool result = true; - - Nob_Dir_Entry dir = {0}; - size_t saved_file_path_count = file_path->count; - Nob_Walk_Action action = NOB_WALK_CONT; - - Nob_File_Type file_type = nob_get_file_type(file_path->items); - if (file_type < 0) nob_return_defer(false); - - // Pre-order walking - if (!opt.post_order) { - if (!func((Nob_Walk_Entry) { - .path = file_path->items, - .type = file_type, - .level = level, - .data = opt.data, - .action = &action, - })) nob_return_defer(false); - switch (action) { - case NOB_WALK_CONT: break; - case NOB_WALK_STOP: *stop = true; // fallthrough - case NOB_WALK_SKIP: nob_return_defer(true); - default: NOB_UNREACHABLE("Nob_Walk_Action"); - } - } - - if (file_type == NOB_FILE_DIRECTORY) { - if (!nob_dir_entry_open(file_path->items, &dir)) nob_return_defer(false); - for (;;) { - // Next entry - if (!nob_dir_entry_next(&dir)) { - if (!dir.error) break; - nob_return_defer(false); - } - - // Ignore . and .. - if (strcmp(dir.name, ".") == 0) continue; - if (strcmp(dir.name, "..") == 0) continue; - - // Prepare the new file_path - file_path->count = saved_file_path_count - 1; -#ifdef _WIN32 - nob_sb_appendf(file_path, "\\%s", dir.name); -#else - nob_sb_appendf(file_path, "/%s", dir.name); -#endif // _WIN32 - nob_sb_append_null(file_path); - - // Recurse - if (!nob__walk_dir_opt_impl(file_path, func, level+1, stop, opt)) nob_return_defer(false); - if (*stop) nob_return_defer(true); - } - file_path->count = saved_file_path_count; - nob_da_last(file_path) = '\0'; - } - - // Post-order walking - if (opt.post_order) { - if (!func((Nob_Walk_Entry) { - .path = file_path->items, - .type = file_type, - .level = level, - .data = opt.data, - .action = &action, - })) nob_return_defer(false); - switch (action) { - case NOB_WALK_CONT: break; - case NOB_WALK_STOP: *stop = true; // fallthrough - case NOB_WALK_SKIP: nob_return_defer(true); - default: NOB_UNREACHABLE("Nob_Walk_Action"); - } - } - -defer: - // Always reset the file_path back to what it was - file_path->count = saved_file_path_count; - nob_da_last(file_path) = '\0'; - - nob_dir_entry_close(dir); - return result; -} - -NOBDEF bool nob_walk_dir_opt(const char *root, Nob_Walk_Func func, Nob_Walk_Dir_Opt opt) -{ - Nob_String_Builder file_path = {0}; - - nob_sb_appendf(&file_path, "%s", root); - nob_sb_append_null(&file_path); - - bool stop = false; - bool ok = nob__walk_dir_opt_impl(&file_path, func, 0, &stop, opt); - free(file_path.items); - return ok; -} - -NOBDEF bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children) -{ - if (strlen(parent) == 0) { - nob_log(NOB_ERROR, "Cannot read empty path"); - return false; - } - bool result = true; - Nob_Dir_Entry dir = {0}; - if (!nob_dir_entry_open(parent, &dir)) nob_return_defer(false); - while (nob_dir_entry_next(&dir)) nob_da_append(children, nob_temp_strdup(dir.name)); - if (dir.error) nob_return_defer(false); -defer: - nob_dir_entry_close(dir); - return result; -} - -NOBDEF bool nob_write_entire_file(const char *path, const void *data, size_t size) -{ - bool result = true; - - const char *buf = NULL; - FILE *f = fopen(path, "wb"); - if (f == NULL) { - nob_log(NOB_ERROR, "Could not open file %s for writing: %s\n", path, strerror(errno)); - nob_return_defer(false); - } - - // len - // v - // aaaaaaaaaa - // ^ - // data - - buf = (const char*)data; - while (size > 0) { - size_t n = fwrite(buf, 1, size, f); - if (ferror(f)) { - nob_log(NOB_ERROR, "Could not write into file %s: %s\n", path, strerror(errno)); - nob_return_defer(false); - } - size -= n; - buf += n; - } - -defer: - if (f) fclose(f); - return result; -} - -NOBDEF Nob_File_Type nob_get_file_type(const char *path) -{ -#ifdef _WIN32 - DWORD attr = GetFileAttributesA(path); - if (attr == INVALID_FILE_ATTRIBUTES) { - nob_log(NOB_ERROR, "Could not get file attributes of %s: %s", path, nob_win32_error_message(GetLastError())); - return -1; - } - - if (attr & FILE_ATTRIBUTE_DIRECTORY) return NOB_FILE_DIRECTORY; - // TODO: detect symlinks on Windows (whatever that means on Windows anyway) - return NOB_FILE_REGULAR; -#else // _WIN32 - struct stat statbuf; - if (lstat(path, &statbuf) < 0) { - nob_log(NOB_ERROR, "Could not get stat of %s: %s", path, strerror(errno)); - return (Nob_File_Type)(-1); - } - - if (S_ISREG(statbuf.st_mode)) return NOB_FILE_REGULAR; - if (S_ISDIR(statbuf.st_mode)) return NOB_FILE_DIRECTORY; - if (S_ISLNK(statbuf.st_mode)) return NOB_FILE_SYMLINK; - return NOB_FILE_OTHER; -#endif // _WIN32 -} - -NOBDEF bool nob_delete_file(const char *path) -{ -#ifndef NOB_NO_ECHO - nob_log(NOB_INFO, "deleting %s", path); -#endif // NOB_NO_ECHO -#ifdef _WIN32 - Nob_File_Type type = nob_get_file_type(path); - switch (type) { - case NOB_FILE_DIRECTORY: - if (!RemoveDirectoryA(path)) { - nob_log(NOB_ERROR, "Could not delete directory %s: %s", path, nob_win32_error_message(GetLastError())); - return false; - } - break; - case NOB_FILE_REGULAR: - case NOB_FILE_SYMLINK: - case NOB_FILE_OTHER: - if (!DeleteFileA(path)) { - nob_log(NOB_ERROR, "Could not delete file %s: %s", path, nob_win32_error_message(GetLastError())); - return false; - } - break; - default: NOB_UNREACHABLE("Nob_File_Type"); - } - return true; -#else - if (remove(path) < 0) { - nob_log(NOB_ERROR, "Could not delete file %s: %s", path, strerror(errno)); - return false; - } - return true; -#endif // _WIN32 -} - -NOBDEF bool nob_copy_directory_recursively(const char *src_path, const char *dst_path) -{ - bool result = true; - Nob_File_Paths children = {0}; - Nob_String_Builder src_sb = {0}; - Nob_String_Builder dst_sb = {0}; - size_t temp_checkpoint = nob_temp_save(); - - Nob_File_Type type = nob_get_file_type(src_path); - if (type < 0) return false; - - switch (type) { - case NOB_FILE_DIRECTORY: { - if (!nob_mkdir_if_not_exists(dst_path)) nob_return_defer(false); - if (!nob_read_entire_dir(src_path, &children)) nob_return_defer(false); - - for (size_t i = 0; i < children.count; ++i) { - if (strcmp(children.items[i], ".") == 0) continue; - if (strcmp(children.items[i], "..") == 0) continue; - - src_sb.count = 0; - nob_sb_append_cstr(&src_sb, src_path); - nob_sb_append_cstr(&src_sb, "/"); - nob_sb_append_cstr(&src_sb, children.items[i]); - nob_sb_append_null(&src_sb); - - dst_sb.count = 0; - nob_sb_append_cstr(&dst_sb, dst_path); - nob_sb_append_cstr(&dst_sb, "/"); - nob_sb_append_cstr(&dst_sb, children.items[i]); - nob_sb_append_null(&dst_sb); - - if (!nob_copy_directory_recursively(src_sb.items, dst_sb.items)) { - nob_return_defer(false); - } - } - } break; - - case NOB_FILE_REGULAR: { - if (!nob_copy_file(src_path, dst_path)) { - nob_return_defer(false); - } - } break; - - case NOB_FILE_SYMLINK: { - nob_log(NOB_WARNING, "TODO: Copying symlinks is not supported yet"); - } break; - - case NOB_FILE_OTHER: { - nob_log(NOB_ERROR, "Unsupported type of file %s", src_path); - nob_return_defer(false); - } break; - - default: NOB_UNREACHABLE("nob_copy_directory_recursively"); - } - -defer: - nob_temp_rewind(temp_checkpoint); - nob_da_free(src_sb); - nob_da_free(dst_sb); - nob_da_free(children); - return result; -} - -NOBDEF char *nob_temp_strdup(const char *cstr) -{ - size_t n = strlen(cstr); - char *result = (char*)nob_temp_alloc(n + 1); - NOB_ASSERT(result != NULL && "Increase NOB_TEMP_CAPACITY"); - memcpy(result, cstr, n); - result[n] = '\0'; - return result; -} - -NOBDEF char *nob_temp_strndup(const char *s, size_t n) -{ - char *r = (char*)nob_temp_alloc(n + 1); - NOB_ASSERT(r != NULL && "Extend the size of the temporary allocator"); - memcpy(r, s, n); - r[n] = '\0'; - return r; -} - -NOBDEF void *nob_temp_alloc(size_t requested_size) -{ - size_t word_size = sizeof(uintptr_t); - size_t size = (requested_size + word_size - 1)/word_size*word_size; - if (nob_temp_size + size > NOB_TEMP_CAPACITY) return NULL; - void *result = &nob_temp[nob_temp_size]; - nob_temp_size += size; - return result; -} - -NOBDEF char *nob_temp_vsprintf(const char *format, va_list ap) -{ - va_list args; - va_copy(args, ap); - int n = vsnprintf(NULL, 0, format, args); - va_end(args); - - NOB_ASSERT(n >= 0); - char *result = (char*)nob_temp_alloc(n + 1); - NOB_ASSERT(result != NULL && "Extend the size of the temporary allocator"); - // TODO: use proper arenas for the temporary allocator; - va_copy(args, ap); - vsnprintf(result, n + 1, format, args); - va_end(args); - - return result; -} - -NOBDEF char *nob_temp_sprintf(const char *format, ...) -{ - va_list args; - va_start(args, format); - char *result = nob_temp_vsprintf(format, args); - va_end(args); - return result; -} - -NOBDEF void nob_temp_reset(void) -{ - nob_temp_size = 0; -} - -NOBDEF size_t nob_temp_save(void) -{ - return nob_temp_size; -} - -NOBDEF void nob_temp_rewind(size_t checkpoint) -{ - nob_temp_size = checkpoint; -} - -NOBDEF const char *nob_temp_sv_to_cstr(Nob_String_View sv) -{ - return nob_temp_strndup(sv.data, sv.count); -} - -NOBDEF int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count) -{ -#ifdef _WIN32 - BOOL bSuccess; - - HANDLE output_path_fd = CreateFile(output_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); - if (output_path_fd == INVALID_HANDLE_VALUE) { - // NOTE: if output does not exist it 100% must be rebuilt - if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1; - nob_log(NOB_ERROR, "Could not open file %s: %s", output_path, nob_win32_error_message(GetLastError())); - return -1; - } - FILETIME output_path_time; - bSuccess = GetFileTime(output_path_fd, NULL, NULL, &output_path_time); - CloseHandle(output_path_fd); - if (!bSuccess) { - nob_log(NOB_ERROR, "Could not get time of %s: %s", output_path, nob_win32_error_message(GetLastError())); - return -1; - } - - for (size_t i = 0; i < input_paths_count; ++i) { - const char *input_path = input_paths[i]; - HANDLE input_path_fd = CreateFile(input_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); - if (input_path_fd == INVALID_HANDLE_VALUE) { - // NOTE: non-existing input is an error cause it is needed for building in the first place - nob_log(NOB_ERROR, "Could not open file %s: %s", input_path, nob_win32_error_message(GetLastError())); - return -1; - } - FILETIME input_path_time; - bSuccess = GetFileTime(input_path_fd, NULL, NULL, &input_path_time); - CloseHandle(input_path_fd); - if (!bSuccess) { - nob_log(NOB_ERROR, "Could not get time of %s: %s", input_path, nob_win32_error_message(GetLastError())); - return -1; - } - - // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild - if (CompareFileTime(&input_path_time, &output_path_time) == 1) return 1; - } - - return 0; -#else - struct stat statbuf = {0}; - - if (stat(output_path, &statbuf) < 0) { - // NOTE: if output does not exist it 100% must be rebuilt - if (errno == ENOENT) return 1; - nob_log(NOB_ERROR, "could not stat %s: %s", output_path, strerror(errno)); - return -1; - } - time_t output_path_time = statbuf.st_mtime; - - for (size_t i = 0; i < input_paths_count; ++i) { - const char *input_path = input_paths[i]; - if (stat(input_path, &statbuf) < 0) { - // NOTE: non-existing input is an error cause it is needed for building in the first place - nob_log(NOB_ERROR, "could not stat %s: %s", input_path, strerror(errno)); - return -1; - } - time_t input_path_time = statbuf.st_mtime; - // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild - if (input_path_time > output_path_time) return 1; - } - - return 0; -#endif -} - -NOBDEF int nob_needs_rebuild1(const char *output_path, const char *input_path) -{ - return nob_needs_rebuild(output_path, &input_path, 1); -} - -NOBDEF const char *nob_path_name(const char *path) -{ -#ifdef _WIN32 - const char *p1 = strrchr(path, '/'); - const char *p2 = strrchr(path, '\\'); - const char *p = (p1 > p2)? p1 : p2; // NULL is ignored if the other search is successful - return p ? p + 1 : path; -#else - const char *p = strrchr(path, '/'); - return p ? p + 1 : path; -#endif // _WIN32 -} - -NOBDEF bool nob_rename(const char *old_path, const char *new_path) -{ -#ifndef NOB_NO_ECHO - nob_log(NOB_INFO, "renaming %s -> %s", old_path, new_path); -#endif // NOB_NO_ECHO -#ifdef _WIN32 - if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) { - nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, nob_win32_error_message(GetLastError())); - return false; - } -#else - if (rename(old_path, new_path) < 0) { - nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, strerror(errno)); - return false; - } -#endif // _WIN32 - return true; -} - -NOBDEF bool nob_read_entire_file(const char *path, Nob_String_Builder *sb) -{ - bool result = true; - - FILE *f = fopen(path, "rb"); - size_t new_count = 0; - long long m = 0; - if (f == NULL) nob_return_defer(false); - if (fseek(f, 0, SEEK_END) < 0) nob_return_defer(false); -#ifndef _WIN32 - m = ftell(f); -#else - m = _telli64(_fileno(f)); -#endif - if (m < 0) nob_return_defer(false); - if (fseek(f, 0, SEEK_SET) < 0) nob_return_defer(false); - - new_count = sb->count + m; - if (new_count > sb->capacity) { - sb->items = NOB_DECLTYPE_CAST(sb->items)NOB_REALLOC(sb->items, new_count); - NOB_ASSERT(sb->items != NULL && "Buy more RAM lool!!"); - sb->capacity = new_count; - } - - fread(sb->items + sb->count, m, 1, f); - if (ferror(f)) { - // TODO: Afaik, ferror does not set errno. So the error reporting in defer is not correct in this case. - nob_return_defer(false); - } - sb->count = new_count; - -defer: - if (!result) nob_log(NOB_ERROR, "Could not read file %s: %s", path, strerror(errno)); - if (f) fclose(f); - return result; -} - -NOBDEF int nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...) -{ - va_list args; - - va_start(args, fmt); - int n = vsnprintf(NULL, 0, fmt, args); - va_end(args); - - // NOTE: the new_capacity needs to be +1 because of the null terminator. - // However, further below we increase sb->count by n, not n + 1. - // This is because we don't want the sb to include the null terminator. The user can always sb_append_null() if they want it - nob_da_reserve(sb, sb->count + n + 1); - char *dest = sb->items + sb->count; - va_start(args, fmt); - vsnprintf(dest, n+1, fmt, args); - va_end(args); - - sb->count += n; - - return n; -} - -NOBDEF void nob_sb_pad_align(Nob_String_Builder *sb, size_t size) -{ - size_t rem = sb->count%size; - if (rem == 0) return; - for (size_t i = 0; i < size - rem; ++i) { - nob_da_append(sb, 0); - } -} - -NOBDEF Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim) -{ - size_t i = 0; - while (i < sv->count && sv->data[i] != delim) { - i += 1; - } - - Nob_String_View result = nob_sv_from_parts(sv->data, i); - - if (i < sv->count) { - sv->count -= i + 1; - sv->data += i + 1; - } else { - sv->count -= i; - sv->data += i; - } - - return result; -} - -NOBDEF Nob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n) -{ - if (n > sv->count) { - n = sv->count; - } - - Nob_String_View result = nob_sv_from_parts(sv->data, n); - - sv->data += n; - sv->count -= n; - - return result; -} - -NOBDEF Nob_String_View nob_sv_from_parts(const char *data, size_t count) -{ - Nob_String_View sv; - sv.count = count; - sv.data = data; - return sv; -} - -NOBDEF Nob_String_View nob_sv_trim_left(Nob_String_View sv) -{ - size_t i = 0; - while (i < sv.count && isspace(sv.data[i])) { - i += 1; - } - - return nob_sv_from_parts(sv.data + i, sv.count - i); -} - -NOBDEF Nob_String_View nob_sv_trim_right(Nob_String_View sv) -{ - size_t i = 0; - while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) { - i += 1; - } - - return nob_sv_from_parts(sv.data, sv.count - i); -} - -NOBDEF Nob_String_View nob_sv_trim(Nob_String_View sv) -{ - return nob_sv_trim_right(nob_sv_trim_left(sv)); -} - -NOBDEF Nob_String_View nob_sv_from_cstr(const char *cstr) -{ - return nob_sv_from_parts(cstr, strlen(cstr)); -} - -NOBDEF bool nob_sv_eq(Nob_String_View a, Nob_String_View b) -{ - if (a.count != b.count) { - return false; - } else { - return memcmp(a.data, b.data, a.count) == 0; - } -} - -NOBDEF bool nob_sv_end_with(Nob_String_View sv, const char *cstr) -{ - size_t cstr_count = strlen(cstr); - if (sv.count >= cstr_count) { - size_t ending_start = sv.count - cstr_count; - Nob_String_View sv_ending = nob_sv_from_parts(sv.data + ending_start, cstr_count); - return nob_sv_eq(sv_ending, nob_sv_from_cstr(cstr)); - } - return false; -} - - -NOBDEF bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_prefix) -{ - if (expected_prefix.count <= sv.count) { - Nob_String_View actual_prefix = nob_sv_from_parts(sv.data, expected_prefix.count); - return nob_sv_eq(expected_prefix, actual_prefix); - } - - return false; -} - -// RETURNS: -// 0 - file does not exists -// 1 - file exists -NOBDEF int nob_file_exists(const char *file_path) -{ -#if _WIN32 - return GetFileAttributesA(file_path) != INVALID_FILE_ATTRIBUTES; -#else - return access(file_path, F_OK) == 0; -#endif -} - -NOBDEF const char *nob_get_current_dir_temp(void) -{ -#ifdef _WIN32 - DWORD nBufferLength = GetCurrentDirectory(0, NULL); - if (nBufferLength == 0) { - nob_log(NOB_ERROR, "could not get current directory: %s", nob_win32_error_message(GetLastError())); - return NULL; - } - - char *buffer = (char*) nob_temp_alloc(nBufferLength); - if (GetCurrentDirectory(nBufferLength, buffer) == 0) { - nob_log(NOB_ERROR, "could not get current directory: %s", nob_win32_error_message(GetLastError())); - return NULL; - } - - return buffer; -#else - char *buffer = (char*) nob_temp_alloc(PATH_MAX); - if (getcwd(buffer, PATH_MAX) == NULL) { - nob_log(NOB_ERROR, "could not get current directory: %s", strerror(errno)); - return NULL; - } - - return buffer; -#endif // _WIN32 -} - -NOBDEF bool nob_set_current_dir(const char *path) -{ -#ifdef _WIN32 - if (!SetCurrentDirectory(path)) { - nob_log(NOB_ERROR, "could not set current directory to %s: %s", path, nob_win32_error_message(GetLastError())); - return false; - } - return true; -#else - if (chdir(path) < 0) { - nob_log(NOB_ERROR, "could not set current directory to %s: %s", path, strerror(errno)); - return false; - } - return true; -#endif // _WIN32 -} - -NOBDEF char *nob_temp_dir_name(const char *path) -{ -#ifndef _WIN32 - // Stolen from the musl's implementation of dirname. - // We are implementing our own one because libc vendors cannot agree on whether dirname(3) - // modifies the path or not. - if (!path || !*path) return nob_temp_strdup("."); - size_t i = strlen(path) - 1; - for (; path[i] == '/'; i--) if (!i) return nob_temp_strdup("/"); - for (; path[i] != '/'; i--) if (!i) return nob_temp_strdup("."); - for (; path[i] == '/'; i--) if (!i) return nob_temp_strdup("/"); - return nob_temp_strndup(path, i + 1); -#else - if (!path) path = ""; // Treating NULL as empty. - char *drive = nob_temp_alloc(_MAX_DRIVE); - char *dir = nob_temp_alloc(_MAX_DIR); - // https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/8e46eyt7(v=vs.100) - errno_t ret = _splitpath_s(path, drive, _MAX_DRIVE, dir, _MAX_DIR, NULL, 0, NULL, 0); - NOB_ASSERT(ret == 0); - return nob_temp_sprintf("%s%s", drive, dir); -#endif // _WIN32 -} - -NOBDEF char *nob_temp_file_name(const char *path) -{ -#ifndef _WIN32 - // Stolen from the musl's implementation of dirname. - // We are implementing our own one because libc vendors cannot agree on whether basename(3) - // modifies the path or not. - if (!path || !*path) return nob_temp_strdup("."); - char *s = nob_temp_strdup(path); - size_t i = strlen(s)-1; - for (; i&&s[i]=='/'; i--) s[i] = 0; - for (; i&&s[i-1]!='/'; i--); - return s+i; -#else - if (!path) path = ""; // Treating NULL as empty. - char *fname = nob_temp_alloc(_MAX_FNAME); - char *ext = nob_temp_alloc(_MAX_EXT); - // https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/8e46eyt7(v=vs.100) - errno_t ret = _splitpath_s(path, NULL, 0, NULL, 0, fname, _MAX_FNAME, ext, _MAX_EXT); - NOB_ASSERT(ret == 0); - return nob_temp_sprintf("%s%s", fname, ext); -#endif // _WIN32 -} - -NOBDEF char *nob_temp_file_ext(const char *path) -{ -#ifndef _WIN32 - return strrchr(nob_temp_file_name(path), '.'); -#else - if (!path) path = ""; // Treating NULL as empty. - char *ext = nob_temp_alloc(_MAX_EXT); - // https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/8e46eyt7(v=vs.100) - errno_t ret = _splitpath_s(path, NULL, 0, NULL, 0, NULL, 0, ext, _MAX_EXT); - NOB_ASSERT(ret == 0); - return ext; -#endif // _WIN32 -} - -NOBDEF char *nob_temp_running_executable_path(void) -{ -#if defined(__linux__) - char buf[4096]; - int length = readlink("/proc/self/exe", buf, NOB_ARRAY_LEN(buf)); - if (length < 0) return nob_temp_strdup(""); - return nob_temp_strndup(buf, length); -#elif defined(_WIN32) - char buf[MAX_PATH]; - int length = GetModuleFileNameA(NULL, buf, MAX_PATH); - return nob_temp_strndup(buf, length); -#elif defined(__APPLE__) - char buf[4096]; - uint32_t size = NOB_ARRAY_LEN(buf); - if (_NSGetExecutablePath(buf, &size) != 0) return nob_temp_strdup(""); - int length = strlen(buf); - return nob_temp_strndup(buf, length); -#elif defined(__FreeBSD__) - char buf[4096]; - int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; - size_t length = sizeof(buf); - if (sysctl(mib, 4, buf, &length, NULL, 0) < 0) return nob_temp_strdup(""); - return nob_temp_strndup(buf, length); -#elif defined(__HAIKU__) - int cookie = 0; - image_info info; - while (get_next_image_info(B_CURRENT_TEAM, &cookie, &info) == B_OK) - if (info.type == B_APP_IMAGE) - break; - return nob_temp_strndup(info.name, strlen(info.name)); -#else - fprintf(stderr, "%s:%d: TODO: nob_temp_running_executable_path is not implemented for this platform\n", __FILE__, __LINE__); - return nob_temp_strdup(""); -#endif -} - -#endif // NOB_IMPLEMENTATION - -#ifndef NOB_STRIP_PREFIX_GUARD_ -#define NOB_STRIP_PREFIX_GUARD_ - // NOTE: The name stripping should be part of the header so it's not accidentally included - // several times. At the same time, it should be at the end of the file so to not create any - // potential conflicts in the NOB_IMPLEMENTATION. The header obviously cannot be at the end - // of the file because NOB_IMPLEMENTATION needs the forward declarations from there. So the - // solution is to split the header into two parts where the name stripping part is at the - // end of the file after the NOB_IMPLEMENTATION. - #ifndef NOB_UNSTRIP_PREFIX - #define TODO NOB_TODO - #define UNREACHABLE NOB_UNREACHABLE - #define UNUSED NOB_UNUSED - #define ARRAY_LEN NOB_ARRAY_LEN - #define ARRAY_GET NOB_ARRAY_GET - #define INFO NOB_INFO - #define WARNING NOB_WARNING - #define ERROR NOB_ERROR - #define NO_LOGS NOB_NO_LOGS - #define Log_Level Nob_Log_Level - #define minimal_log_level nob_minimal_log_level - #define log_handler nob_log_handler - #define set_log_handler nob_set_log_handler - #define get_log_handler nob_get_log_handler - #define default_log_handler nob_default_log_handler - #define cancer_log_handler nob_cancer_log_handler - // NOTE: Name log is already defined in math.h and historically always was the natural logarithmic function. - // So there should be no reason to strip the `nob_` prefix in this specific case. - // #define log nob_log - #define shift nob_shift - #define shift_args nob_shift_args - #define GO_REBUILD_URSELF NOB_GO_REBUILD_URSELF - #define GO_REBUILD_URSELF_PLUS NOB_GO_REBUILD_URSELF_PLUS - #define File_Paths Nob_File_Paths - #define FILE_REGULAR NOB_FILE_REGULAR - #define FILE_DIRECTORY NOB_FILE_DIRECTORY - #define FILE_SYMLINK NOB_FILE_SYMLINK - #define FILE_OTHER NOB_FILE_OTHER - #define File_Type Nob_File_Type - #define mkdir_if_not_exists nob_mkdir_if_not_exists - #define copy_file nob_copy_file - #define copy_directory_recursively nob_copy_directory_recursively - #define read_entire_dir nob_read_entire_dir - #define WALK_CONT NOB_WALK_CONT - #define WALK_SKIP NOB_WALK_SKIP - #define WALK_STOP NOB_WALK_STOP - #define Walk_Action Nob_Walk_Action - #define Walk_Entry Nob_Walk_Entry - #define Walk_Func Nob_Walk_Func - #define Walk_Dir_Opt Nob_Walk_Dir_Opt - #define walk_dir nob_walk_dir - #define walk_dir_opt nob_walk_dir_opt - #define write_entire_file nob_write_entire_file - #define get_file_type nob_get_file_type - #define delete_file nob_delete_file - #define Dir_Entry Nob_Dir_Entry - #define dir_entry_open nob_dir_entry_open - #define dir_entry_next nob_dir_entry_next - #define dir_entry_close nob_dir_entry_close - #define return_defer nob_return_defer - #define da_append nob_da_append - #define da_free nob_da_free - #define da_append_many nob_da_append_many - #define da_resize nob_da_resize - #define da_reserve nob_da_reserve - #define da_last nob_da_last - #define da_remove_unordered nob_da_remove_unordered - #define da_foreach nob_da_foreach - #define fa_append nob_fa_append - #define swap nob_swap - #define String_Builder Nob_String_Builder - #define read_entire_file nob_read_entire_file - #define sb_appendf nob_sb_appendf - #define sb_append_buf nob_sb_append_buf - #define sb_append_cstr nob_sb_append_cstr - #define sb_append_null nob_sb_append_null - #define sb_append nob_sb_append - #define sb_pad_align nob_sb_pad_align - #define sb_free nob_sb_free - #define Proc Nob_Proc - #define INVALID_PROC NOB_INVALID_PROC - #define Fd Nob_Fd - #define Pipe Nob_Pipe - #define pipe_create nob_pipe_create - #define Chain Nob_Chain - #define Chain_Begin_Opt Nob_Chain_Begin_Opt - #define chain_begin nob_chain_begin - #define chain_begin_opt nob_chain_begin_opt - #define Chain_Cmd_Opt Nob_Chain_Cmd_Opt - #define chain_cmd nob_chain_cmd - #define chain_cmd_opt nob_chain_cmd_opt - #define Chain_End_Opt Nob_Chain_End_Opt - #define chain_end nob_chain_end - #define chain_end_opt nob_chain_end_opt - #define INVALID_FD NOB_INVALID_FD - #define fd_open_for_read nob_fd_open_for_read - #define fd_open_for_write nob_fd_open_for_write - #define fd_close nob_fd_close - #define Procs Nob_Procs - #define proc_wait nob_proc_wait - #define procs_wait nob_procs_wait - #define procs_wait_and_reset nob_procs_wait_and_reset - #define procs_append_with_flush nob_procs_append_with_flush - #define procs_flush nob_procs_flush - #define Cmd Nob_Cmd - #define Cmd_Redirect Nob_Cmd_Redirect - #define Cmd_Opt Nob_Cmd_Opt - #define cmd_run_opt nob_cmd_run_opt - #define cmd_run nob_cmd_run - #define cmd_render nob_cmd_render - #define cmd_append nob_cmd_append - #define cmd_extend nob_cmd_extend - #define cmd_free nob_cmd_free - #define cmd_run_async nob_cmd_run_async - #define cmd_run_async_and_reset nob_cmd_run_async_and_reset - #define cmd_run_async_redirect nob_cmd_run_async_redirect - #define cmd_run_async_redirect_and_reset nob_cmd_run_async_redirect_and_reset - #define cmd_run_sync nob_cmd_run_sync - #define cmd_run_sync_and_reset nob_cmd_run_sync_and_reset - #define cmd_run_sync_redirect nob_cmd_run_sync_redirect - #define cmd_run_sync_redirect_and_reset nob_cmd_run_sync_redirect_and_reset - #define temp_strdup nob_temp_strdup - #define temp_strndup nob_temp_strndup - #define temp_alloc nob_temp_alloc - #define temp_sprintf nob_temp_sprintf - #define temp_vsprintf nob_temp_vsprintf - #define temp_reset nob_temp_reset - #define temp_save nob_temp_save - #define temp_rewind nob_temp_rewind - #define path_name nob_path_name - // NOTE: rename(2) is widely known POSIX function. We never wanna collide with it. - // #define rename nob_rename - #define needs_rebuild nob_needs_rebuild - #define needs_rebuild1 nob_needs_rebuild1 - #define file_exists nob_file_exists - #define get_current_dir_temp nob_get_current_dir_temp - #define set_current_dir nob_set_current_dir - #define temp_dir_name nob_temp_dir_name - #define temp_file_name nob_temp_file_name - #define temp_file_ext nob_temp_file_ext - #define temp_running_executable_path nob_temp_running_executable_path - #define String_View Nob_String_View - #define temp_sv_to_cstr nob_temp_sv_to_cstr - #define sv_chop_by_delim nob_sv_chop_by_delim - #define sv_chop_left nob_sv_chop_left - #define sv_trim nob_sv_trim - #define sv_trim_left nob_sv_trim_left - #define sv_trim_right nob_sv_trim_right - #define sv_eq nob_sv_eq - #define sv_starts_with nob_sv_starts_with - #define sv_end_with nob_sv_end_with - #define sv_from_cstr nob_sv_from_cstr - #define sv_from_parts nob_sv_from_parts - #define sb_to_sv nob_sb_to_sv - #define win32_error_message nob_win32_error_message - #define nprocs nob_nprocs - #define nanos_since_unspecified_epoch nob_nanos_since_unspecified_epoch - #define NANOS_PER_SEC NOB_NANOS_PER_SEC - #endif // NOB_STRIP_PREFIX -#endif // NOB_STRIP_PREFIX_GUARD_ - -/* - Revision history: - - 3.2.2 (2026-02-06) Fix read_entire_dir crash on empty path (by @ysoftware) - 3.2.1 (2026-01-29) Fix the implicit declaration error when nob is included as a header (by @ysoftware) - 3.2.0 (2026-01-28) Introduce Chain API - - Nob_Chain - - Nob_Chain_Begin_Opt - - nob_chain_begin() - - nob_chain_begin_opt() - - Nob_Chain_Cmd_Opt - - nob_chain_cmd() - - nob_chain_cmd_opt() - - Nob_Chain_End_Opt - - nob_chain_end() - - nob_chain_end_opt() - Introduce some auxiliary things that were used in Chain API implementation, but might be useful outside of it: - - Nob_Pipe - - nob_pipe_create() - - nob_fa_append() - 3.1.0 (2026-01-22) Make nob_delete_file() be able to delete empty dir on Windows (by @rexim) - Introduce Directory Entry API - similar to POSIX dirent but with names that don't collide - - Nob_Dir_Entry - - nob_dir_entry_open() - - nob_dir_entry_next() - - nob_dir_entry_close() - Rewrite Directory Walking API using Directory Entry API - Introduce .post_order parameter to Nob_Walk_Dir_Opt which walks the directories in post order starting from leaf files - Rewrite nob_read_entire_dir() using Directory Entry API - 3.0.0 (2026-01-13) Improve C++ support (by @rexim) - - Fix various C++ compilers warnings and complains throughout the code. - - Reimplement nob_cmd_append() without taking a pointer to temporary array (some C++ compilers don't like that) - - Make default NOB_REBUILD_URSELF() try to recompile with C++ if __cplusplus macro is defined - Strip prefixes by default (by @rexim) - - Ignore NOB_STRIP_PREFIX macro - - Introduce NOB_UNSTRIP_PREFIX macro - BACKWARD INCOMPATIBLE CHANGE!!! If you had code that intentionally didn't enable NOB_STRIP_PREFIX - because all the names from nob.h were causing too many collisions for you, upgrading to 3.0.0 may break it. - In that case you should go and explicitly enable NOB_UNSTRIP_PREFIX where needed after upgrading. - Add nob_sb_append alias to nob_da_append (by @rexim) - 2.0.1 (2026-01-07) Fix Walk_Entry naming (by @Sinha-Ujjawal) - Using single String Builder in nob__walk_dir_opt_impl (by @Sinha-Ujjawal) - Add tcc to nob_cc_*() and NOB_REBUILD_URSELF() macros (by @vylsaz) - Fix building nob_read_entire_file() with tcc on windows (by @vylsaz) - Fix Y2038 in nob_needs_rebuild() (by @lnvitesace) - 2.0.0 (2026-01-06) Remove minirent.h (by @rexim) - BACKWARD INCOMPATIBLE CHANGE!!! If you were using minirent.h from this library - just use it directly from https://github.com/tsoding/minirent - or consider using the New Directory Walking API. - Introduce New Directory Walking API (by @rexim) - - NOB_WALK_CONT - - NOB_WALK_SKIP - - NOB_WALK_STOP - - Nob_Walk_Action - - Nob_Walk_Entry - - Nob_Walk_Func - - Nob_Walk_Dir_Opt - - nob_walk_dir() - - nob_walk_dir_opt() - Add support for Haiku to nob_temp_running_executable_path() (By @Cephon) - Make nob_file_exists() unfailable (By @rexim) - 1.27.0 (2025-12-30) Add .dont_reset option to cmd_run (by @Israel77) - Fix support for FreeBSD (by @cqundefine) - Strip prefixes from NOB_GO_REBUILD_URSELF and NOB_GO_REBUILD_URSELF_PLUS (by @huwwa) - Add /Fo flag to MSVC version of nob_cc_output() (by @ratchetfreak) - 1.26.0 (2025-12-28) Introduce customizable log handlers (by @rexim) - - Add nob_log_handler - - Add nob_set_log_handler - - Add nob_get_log_handler - - Add nob_default_log_handler - - Add nob_cancer_log_handler - Introduce nob_temp_vsprintf (by @rexim) - Fix compilation error on Windows when NOB_NO_ECHO is enabled (by @mlorenc227) - Do not redefine _CRT_SECURE_NO_WARNINGS if it's already defined (by @vylsaz) - 1.25.1 (2025-11-06) Fix forward declaration of _NSGetExecutablePath on MacOS (by @agss0) - 1.25.0 (2025-10-25) - Add nob_sb_pad_align() - - Add nob_swap() - - Add nob_temp_strndup() - - Add nob_temp_dir_name() - - Add nob_temp_file_name() - - Add nob_temp_file_ext() - - Add nob_temp_running_executable_path() - 1.24.0 (2025-10-23) Introduce NOB_NO_ECHO macro flag (@rexim) - 1.23.0 (2025-08-22) Introduce new API for running commands (by @rexim, @programmerlexi, @0x152a) - - Add nob_cmd_run() - - Add nob_cmd_run_opt() - - Add struct Nob_Cmd_Opt - - Add nob_procs_flush() - - Add nob_nprocs() - Deprecate old API for running commands. (by @rexim) - We do not plan to delete this API any time, but we believe that the new one is more convenient. - - Deprecate struct Nob_Cmd_Redirect{} (it's not explicitly marked with NOB_DEPRECATED, but functions that use it are) - - Turn nob_cmd_run_async() into a function (otherwise it's not deprecatable with NOB_DEPRECATED) - - Deprecate nob_cmd_run_async() - - Deprecate nob_cmd_run_async_and_reset() - - Deprecate nob_cmd_run_async_redirect() - - Deprecate nob_cmd_run_async_redirect_and_reset() - - Deprecate nob_cmd_run_sync() - - Deprecate nob_cmd_run_sync_and_reset() - - Deprecate nob_cmd_run_sync_redirect() - - Deprecate nob_cmd_run_sync_redirect_and_reset() - - Deprecate nob_procs_append_with_flush() - - Deprecate nob_procs_wait_and_reset() - Introduce deprecation mechanism (by @yuI4140, @rexim) - By default, deprecation warnings are not reported. You have to #define NOB_WARN_DEPRECATED to enable them. - - Add NOB_DEPRECATED() - - Add NOB_WARN_DEPRECATED - Add NOB_DECLTYPE_CAST() for C++-compatible casting of allocation results (by @rexim) - Introduce basic performance measuring mechanism (By @mikmart) - - Add nob_nanos_since_unspecified_epoch() - - Add NOB_NANOS_PER_SEC - 1.22.0 (2025-08-12) Add NOBDEF macro to the beginning of function declarations (by @minefreak19) - Add more flags to MSVC nob_cc_flags() (by @PieVieRo) - 1.21.0 (2025-08-11) Add NOB_NO_MINIRENT guard for "minirent.h" (by @fietec) - 1.20.9 (2025-08-11) Fix warnings on Windows: Define _CRT_SECURE_NO_WARNINGS, Rename mkdir to _mkdir (by @OetkenPurveyorOfCode) - 1.20.8 (2025-08-11) Fix the bug with nob_get_file_type() not identifying symlinks correctly on POSIX (By @samuellieberman) - 1.20.7 (2025-07-29) Align nob_temp_alloc() allocations by the word size (By @rexim) - 1.20.6 (2025-05-16) Never strip nob_* suffix from nob_rename (By @rexim) - 1.20.5 (2025-05-16) NOB_PRINTF_FORMAT() support for MinGW (By @KillerxDBr) - 1.20.4 (2025-05-16) More reliable rendering of the Windows command (By @vylsaz) - 1.20.3 (2025-05-16) Add check for __clang__ along with _MSC_VER checks (By @nashiora) - 1.20.2 (2025-04-24) Report the program name that failed to start up in nob_cmd_run_async_redirect() (By @rexim) - 1.20.1 (2025-04-16) Use vsnprintf() in nob_sb_appendf() instead of vsprintf() (By @LainLayer) - 1.20.0 (2025-04-16) Introduce nob_cc(), nob_cc_flags(), nob_cc_inputs(), nob_cc_output() macros (By @rexim) - 1.19.0 (2025-03-25) Add nob_procs_append_with_flush() (By @rexim and @anion155) - 1.18.0 (2025-03-24) Add nob_da_foreach() (By @rexim) - Allow file sizes greater than 2GB to be read on windows (By @satchelfrost and @KillerxDBr) - Fix nob_fd_open_for_write behaviour on windows so it truncates the opened files (By @twixuss) - 1.17.0 (2025-03-16) Factor out nob_da_reserve() (By @rexim) - Add nob_sb_appendf() (By @angelcaru) - 1.16.1 (2025-03-16) Make nob_da_resize() exponentially grow capacity similar to no_da_append_many() - 1.16.0 (2025-03-16) Introduce NOB_PRINTF_FORMAT - 1.15.1 (2025-03-16) Make nob.h compilable in gcc/clang with -std=c99 on POSIX. This includes: - not using strsignal() - using S_IS* stat macros instead of S_IF* flags - 1.15.0 (2025-03-03) Add nob_sv_chop_left() - 1.14.1 (2025-03-02) Add NOB_EXPERIMENTAL_DELETE_OLD flag that enables deletion of nob.old in Go Rebuild Urself™ Technology - 1.14.0 (2025-02-17) Add nob_da_last() - Add nob_da_remove_unordered() - 1.13.1 (2025-02-17) Fix segfault in nob_delete_file() (By @SileNce5k) - 1.13.0 (2025-02-11) Add nob_da_resize() (By @satchelfrost) - 1.12.0 (2025-02-04) Add nob_delete_file() - Add nob_sv_start_with() - 1.11.0 (2025-02-04) Add NOB_GO_REBUILD_URSELF_PLUS() (By @rexim) - 1.10.0 (2025-02-04) Make NOB_ASSERT, NOB_REALLOC, and NOB_FREE redefinable (By @OleksiiBulba) - 1.9.1 (2025-02-04) Fix signature of nob_get_current_dir_temp() (By @julianstoerig) - 1.9.0 (2024-11-06) Add Nob_Cmd_Redirect mechanism (By @rexim) - Add nob_path_name() (By @0dminnimda) - 1.8.0 (2024-11-03) Add nob_cmd_extend() (By @0dminnimda) - 1.7.0 (2024-11-03) Add nob_win32_error_message and NOB_WIN32_ERR_MSG_SIZE (By @KillerxDBr) - 1.6.0 (2024-10-27) Add nob_cmd_run_sync_and_reset() - Add nob_sb_to_sv() - Add nob_procs_wait_and_reset() - 1.5.1 (2024-10-25) Include limits.h for Linux musl libc (by @pgalkin) - 1.5.0 (2024-10-23) Add nob_get_current_dir_temp() - Add nob_set_current_dir() - 1.4.0 (2024-10-21) Fix UX issues with NOB_GO_REBUILD_URSELF on Windows when you call nob without the .exe extension (By @pgalkin) - Add nob_sv_end_with (By @pgalkin) - 1.3.2 (2024-10-21) Fix unreachable error in nob_log on passing NOB_NO_LOGS - 1.3.1 (2024-10-21) Fix redeclaration error for minimal_log_level (By @KillerxDBr) - 1.3.0 (2024-10-17) Add NOB_UNREACHABLE - 1.2.2 (2024-10-16) Fix compilation of nob_cmd_run_sync_and_reset on Windows (By @KillerxDBr) - 1.2.1 (2024-10-16) Add a separate include guard for NOB_STRIP_PREFIX. - 1.2.0 (2024-10-15) Make NOB_DA_INIT_CAP redefinable - Add NOB_STRIP_PREFIX which strips off nob_* prefix from all the user facing names - Add NOB_UNUSED macro - Add NOB_TODO macro - Add nob_sv_trim_left and nob_sv_trim_right declarations to the header part - 1.1.1 (2024-10-15) Remove forward declaration for is_path1_modified_after_path2 - 1.1.0 (2024-10-15) nob_minimal_log_level - nob_cmd_run_sync_and_reset - 1.0.0 (2024-10-15) first release based on https://github.com/tsoding/musializer/blob/4ac7cce9874bc19e02d8c160c8c6229de8919401/nob.h -*/ - -/* - Version Conventions: - - We are following https://semver.org/ so the version has a format MAJOR.MINOR.PATCH: - - Modifying comments does not update the version. - - PATCH is incremented in case of a bug fix or refactoring without touching the API. - - MINOR is incremented when new functions and/or types are added in a way that does - not break any existing user code. We want to do this in the majority of the situation. - If we want to delete a certain function or type in favor of another one we should - just add the new function/type and deprecate the old one in a backward compatible way - and let them co-exist for a while. - - MAJOR update should be just a periodic cleanup of the DEPRECATED functions and types - without really modifying any existing functionality. - - Breaking backward compatibility in a MINOR release should be considered a bug and - should be promptly fixed in the next PATCH release. - - API conventions: - - - All the user facing names should be prefixed with `nob_`, `NOB_`, or `Nob_` depending on the case. - - The prefixes of non-redefinable names should be stripped in NOB_STRIP_PREFIX_GUARD_ section, - unless explicitly stated otherwise like in case of nob_log() or nob_rename(). - - Internal (private) names should be prefixed with `nob__` (double underscore). The user code is discouraged - from using such names since they are allowed to be broken in a backward incompatible way even in PATCH - releases. (This is why they are internal) - - If a public macro uses an private function internally such function must be forward declared in the NOB_H_ - section. -*/ - -/* - ------------------------------------------------------------------------------ - This software is available under 2 licenses -- choose whichever you prefer. - ------------------------------------------------------------------------------ - ALTERNATIVE A - MIT License - Copyright (c) 2024 Alexey Kutepov - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies - of the Software, and to permit persons to whom the Software is furnished to do - so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - ------------------------------------------------------------------------------ - ALTERNATIVE B - Public Domain (www.unlicense.org) - This is free and unencumbered software released into the public domain. - Anyone is free to copy, modify, publish, use, compile, sell, or distribute this - software, either in source code form or as a compiled binary, for any purpose, - commercial or non-commercial, and by any means. - In jurisdictions that recognize copyright laws, the author or authors of this - software dedicate any and all copyright interest in the software to the public - domain. We make this dedication for the benefit of the public at large and to - the detriment of our heirs and successors. We intend this dedication to be an - overt act of relinquishment in perpetuity of all present and future rights to - this software under copyright law. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ------------------------------------------------------------------------------ -*/